Skip to content

Commit

Permalink
fix(core): list local plugins (#11410)
Browse files Browse the repository at this point in the history
  • Loading branch information
AgentEnder committed Aug 12, 2022
1 parent f8da1ad commit db97728
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 37 deletions.
94 changes: 67 additions & 27 deletions packages/nx/src/command-line/generate.ts
@@ -1,23 +1,26 @@
import {
combineOptionsForGenerator,
handleErrors,
Options,
Schema,
} from '../utils/params';
import { Workspaces } from '../config/workspaces';
import { FileChange, flushChanges, FsTree } from '../generators/tree';
import { logger } from '../utils/logger';
import * as chalk from 'chalk';
import { workspaceRoot } from '../utils/workspace-root';
import { NxJsonConfiguration } from '../config/nx-json';
import { printHelp } from '../utils/print-help';
import { prompt } from 'enquirer';
import { readJsonFile } from 'nx/src/utils/fileutils';

import { readNxJson } from '../config/configuration';
import { NxJsonConfiguration } from '../config/nx-json';
import { ProjectsConfigurations } from '../config/workspace-json-project-json';
import { Workspaces } from '../config/workspaces';
import { FileChange, flushChanges, FsTree } from '../generators/tree';
import {
createProjectGraphAsync,
readProjectsConfigurationFromProjectGraph,
} from '../project-graph/project-graph';
import { readNxJson } from '../config/configuration';
import { logger } from '../utils/logger';
import {
combineOptionsForGenerator,
handleErrors,
Options,
Schema,
} from '../utils/params';
import { getLocalWorkspacePlugins } from '../utils/plugins/local-plugins';
import { printHelp } from '../utils/print-help';
import { workspaceRoot } from '../utils/workspace-root';

export interface GenerateOptions {
collectionName: string;
Expand All @@ -44,18 +47,21 @@ export function printChanges(fileChanges: FileChange[]) {
async function promptForCollection(
generatorName: string,
ws: Workspaces,
interactive: boolean
) {
interactive: boolean,
projectsConfiguration: ProjectsConfigurations
): Promise<string> {
const packageJson = readJsonFile(`${workspaceRoot}/package.json`);
const collections = Array.from(
const localPlugins = getLocalWorkspacePlugins(projectsConfiguration);

const installedCollections = Array.from(
new Set([
...Object.keys(packageJson.dependencies || {}),
...Object.keys(packageJson.devDependencies || {}),
])
);
const choicesMap = new Set<string>();

for (const collectionName of collections) {
const choicesMap = new Set<string>();
for (const collectionName of installedCollections) {
try {
const { resolvedCollectionName, normalizedGeneratorName } =
ws.readGenerator(collectionName, generatorName);
Expand All @@ -64,14 +70,44 @@ async function promptForCollection(
} catch {}
}

const choices = Array.from(choicesMap);

const choicesFromLocalPlugins: {
name: string;
message: string;
value: string;
}[] = [];
for (const [name] of localPlugins) {
try {
const { resolvedCollectionName, normalizedGeneratorName } =
ws.readGenerator(name, generatorName);
const value = `${resolvedCollectionName}:${normalizedGeneratorName}`;
if (!choicesMap.has(value)) {
choicesFromLocalPlugins.push({
name: value,
message: chalk.bold(value),
value,
});
}
} catch {}
}
if (choicesFromLocalPlugins.length) {
choicesFromLocalPlugins[choicesFromLocalPlugins.length - 1].message += '\n';
}
const choices = (
choicesFromLocalPlugins as (
| string
| {
name: string;
message: string;
value: string;
}
)[]
).concat(...choicesMap);
if (choices.length === 1) {
return choices[0];
return typeof choices[0] === 'string' ? choices[0] : choices[0].value;
} else if (!interactive && choices.length > 1) {
throwInvalidInvocation(choices);
throwInvalidInvocation(Array.from(choicesMap));
} else if (interactive && choices.length > 1) {
const noneOfTheAbove = `None of the above`;
const noneOfTheAbove = `\nNone of the above`;
choices.push(noneOfTheAbove);
let { generator, customCollection } = await prompt<{
generator: string;
Expand All @@ -81,7 +117,8 @@ async function promptForCollection(
name: 'generator',
message: `Which generator would you like to use?`,
type: 'autocomplete',
choices,
// enquirer's typings are incorrect here... It supports (string | Choice)[], but is typed as (string[] | Choice[])
choices: choices as string[],
},
{
name: 'customCollection',
Expand Down Expand Up @@ -135,7 +172,8 @@ async function convertToGenerateOptions(
generatorOptions: { [p: string]: any },
ws: Workspaces,
defaultCollectionName: string,
mode: 'generate' | 'new'
mode: 'generate' | 'new',
projectsConfiguration?: ProjectsConfigurations
): Promise<GenerateOptions> {
let collectionName: string | null = null;
let generatorName: string | null = null;
Expand All @@ -152,7 +190,8 @@ async function convertToGenerateOptions(
const generatorString = await promptForCollection(
generatorDescriptor,
ws,
interactive
interactive,
projectsConfiguration
);
const parsedGeneratorString = parseGeneratorString(generatorString);
collectionName = parsedGeneratorString.collection;
Expand Down Expand Up @@ -297,7 +336,8 @@ export async function generate(cwd: string, args: { [k: string]: any }) {
args,
ws,
readDefaultCollection(nxJson),
'generate'
'generate',
projectsConfiguration
);
const { normalizedGeneratorName, schema, implementationFactory, aliases } =
ws.readGenerator(opts.collectionName, opts.generatorName);
Expand Down
14 changes: 14 additions & 0 deletions packages/nx/src/command-line/list.ts
Expand Up @@ -9,6 +9,14 @@ import {
listInstalledPlugins,
listPluginCapabilities,
} from '../utils/plugins';
import {
getLocalWorkspacePlugins,
listLocalWorkspacePlugins,
} from '../utils/plugins/local-plugins';
import {
createProjectGraphAsync,
readProjectsConfigurationFromProjectGraph,
} from '../project-graph/project-graph';

export interface ListArgs {
/** The name of an installed plugin to query */
Expand Down Expand Up @@ -36,12 +44,18 @@ export async function listHandler(args: ListArgs): Promise<void> {

return [];
});
const projectGraph = await createProjectGraphAsync();

const localPlugins = getLocalWorkspacePlugins(
readProjectsConfigurationFromProjectGraph(projectGraph)
);
const installedPlugins = getInstalledPluginsFromPackageJson(
workspaceRoot,
corePlugins,
communityPlugins
);

listLocalWorkspacePlugins(localPlugins);
listInstalledPlugins(installedPlugins);
listCorePlugins(installedPlugins, corePlugins);
listCommunityPlugins(installedPlugins, communityPlugins);
Expand Down
23 changes: 22 additions & 1 deletion packages/nx/src/command-line/report.ts
Expand Up @@ -8,6 +8,11 @@ import {
} from '../utils/package-manager';
import { readJsonFile } from '../utils/fileutils';
import { PackageJson, readModulePackageJson } from '../utils/package-json';
import { getLocalWorkspacePlugins } from '../utils/plugins/local-plugins';
import {
createProjectGraphAsync,
readProjectsConfigurationFromProjectGraph,
} from '../project-graph/project-graph';

export const packagesWeCareAbout = [
'nx',
Expand Down Expand Up @@ -49,7 +54,7 @@ export const patternsWeIgnoreInCommunityReport: Array<string | RegExp> = [
* Must be run within an Nx workspace
*
*/
export function reportHandler() {
export async function reportHandler() {
const pm = detectPackageManager();
const pmVersion = getPackageManagerVersion(pm);

Expand All @@ -66,6 +71,22 @@ export function reportHandler() {

bodyLines.push('---------------------------------------');

try {
const projectGraph = await createProjectGraphAsync();
bodyLines.push('Local workspace plugins:');
const plugins = getLocalWorkspacePlugins(
readProjectsConfigurationFromProjectGraph(projectGraph)
).keys();
for (const plugin of plugins) {
bodyLines.push(`\t ${chalk.green(plugin)}`);
}
bodyLines.push(...plugins);
} catch {
bodyLines.push('Unable to construct project graph');
}

bodyLines.push('---------------------------------------');

const communityPlugins = findInstalledCommunityPlugins();
bodyLines.push('Community plugins:');
communityPlugins.forEach((p) => {
Expand Down
8 changes: 6 additions & 2 deletions packages/nx/src/utils/nx-plugin.ts
Expand Up @@ -6,7 +6,11 @@ import { Workspaces } from '../config/workspaces';

import { workspaceRoot } from './workspace-root';
import { readJsonFile } from '../utils/fileutils';
import { PackageJson, readModulePackageJson } from './package-json';
import {
PackageJson,
readModulePackageJson,
readModulePackageJsonWithoutFallbacks,
} from './package-json';
import { registerTsProject } from './register';
import {
ProjectConfiguration,
Expand Down Expand Up @@ -118,7 +122,7 @@ export function readPluginPackageJson(
json: PackageJson;
} {
try {
const result = readModulePackageJson(pluginName, paths);
const result = readModulePackageJsonWithoutFallbacks(pluginName, paths);
return {
json: result.packageJson,
path: result.path,
Expand Down
42 changes: 38 additions & 4 deletions packages/nx/src/utils/package-json.ts
Expand Up @@ -106,6 +106,41 @@ export function buildTargetFromScript(
};
}

/**
* Uses `require.resolve` to read the package.json for a module.
*
* This will fail if the module doesn't export package.json
*
* @returns package json contents and path
*/
export function readModulePackageJsonWithoutFallbacks(
moduleSpecifier: string,
requirePaths = [workspaceRoot]
): {
packageJson: PackageJson;
path: string;
} {
const packageJsonPath: string = require.resolve(
`${moduleSpecifier}/package.json`,
{
paths: requirePaths,
}
);
const packageJson: PackageJson = readJsonFile(packageJsonPath);

return {
path: packageJsonPath,
packageJson,
};
}

/**
* Reads the package.json file for a specified module.
*
* Includes a fallback that accounts for modules that don't export package.json
*
* @returns package json contents and path
*/
export function readModulePackageJson(
moduleSpecifier: string,
requirePaths = [workspaceRoot]
Expand All @@ -117,14 +152,13 @@ export function readModulePackageJson(
let packageJson: PackageJson;

try {
packageJsonPath = require.resolve(`${moduleSpecifier}/package.json`, {
paths: requirePaths,
});
packageJson = readJsonFile(packageJsonPath);
({ path: packageJsonPath, packageJson } =
readModulePackageJsonWithoutFallbacks(moduleSpecifier, requirePaths));
} catch {
const entryPoint = require.resolve(moduleSpecifier, {
paths: requirePaths,
});

let moduleRootPath = dirname(entryPoint);
packageJsonPath = join(moduleRootPath, 'package.json');

Expand Down
70 changes: 70 additions & 0 deletions packages/nx/src/utils/plugins/local-plugins.ts
@@ -0,0 +1,70 @@
import * as chalk from 'chalk';
import { output } from '../output';
import type { CommunityPlugin, CorePlugin, PluginCapabilities } from './models';
import { getPluginCapabilities } from './plugin-capabilities';
import { hasElements } from './shared';
import { readJsonFile } from '../fileutils';
import { PackageJson, readModulePackageJson } from '../package-json';
import { ProjectsConfigurations } from 'nx/src/config/workspace-json-project-json';
import { join } from 'path';
import { workspaceRoot } from '../workspace-root';
import { existsSync } from 'fs';
import { ExecutorsJson, GeneratorsJson } from 'nx/src/config/misc-interfaces';

export function getLocalWorkspacePlugins(
projectsConfiguration: ProjectsConfigurations
): Map<string, PluginCapabilities> {
const plugins: Map<string, PluginCapabilities> = new Map();
for (const project of Object.values(projectsConfiguration.projects)) {
const packageJsonPath = join(workspaceRoot, project.root, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJson: PackageJson = readJsonFile(packageJsonPath);
const capabilities: Partial<PluginCapabilities> = {};
const generatorsPath = packageJson.generators ?? packageJson.schematics;
const executorsPath = packageJson.executors ?? packageJson.builders;
if (generatorsPath) {
const file = readJsonFile<GeneratorsJson>(
join(workspaceRoot, project.root, generatorsPath)
);
capabilities.generators = file.generators ?? file.schematics;
}
if (executorsPath) {
const file = readJsonFile<ExecutorsJson>(
join(workspaceRoot, project.root, executorsPath)
);
capabilities.executors = file.executors ?? file.builders;
}
if (capabilities.executors || capabilities.generators) {
plugins.set(packageJson.name, {
executors: capabilities.executors ?? {},
generators: capabilities.generators ?? {},
name: packageJson.name,
});
}
}
}

return plugins;
}

export function listLocalWorkspacePlugins(
installedPlugins: Map<string, PluginCapabilities>
) {
const bodyLines: string[] = [];

for (const [, p] of installedPlugins) {
const capabilities = [];
if (hasElements(p.executors)) {
capabilities.push('executors');
}
if (hasElements(p.generators)) {
capabilities.push('generators');
}
bodyLines.push(`${chalk.bold(p.name)} (${capabilities.join()})`);
}

output.log({
title: `Local workspace plugins:`,
bodyLines: bodyLines,
});
}

1 comment on commit db97728

@vercel
Copy link

@vercel vercel bot commented on db97728 Aug 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-nrwl.vercel.app
nx-five.vercel.app
nx-dev-git-master-nrwl.vercel.app
nx.dev

Please sign in to comment.