fix(core): list local plugins (#11410)
This commit is contained in:
parent
f8da1ad42b
commit
db97728793
@ -1,23 +1,26 @@
|
|||||||
|
import * as chalk from 'chalk';
|
||||||
|
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 { logger } from '../utils/logger';
|
||||||
import {
|
import {
|
||||||
combineOptionsForGenerator,
|
combineOptionsForGenerator,
|
||||||
handleErrors,
|
handleErrors,
|
||||||
Options,
|
Options,
|
||||||
Schema,
|
Schema,
|
||||||
} from '../utils/params';
|
} from '../utils/params';
|
||||||
import { Workspaces } from '../config/workspaces';
|
import { getLocalWorkspacePlugins } from '../utils/plugins/local-plugins';
|
||||||
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 { printHelp } from '../utils/print-help';
|
||||||
import { prompt } from 'enquirer';
|
import { workspaceRoot } from '../utils/workspace-root';
|
||||||
import { readJsonFile } from 'nx/src/utils/fileutils';
|
|
||||||
import {
|
|
||||||
createProjectGraphAsync,
|
|
||||||
readProjectsConfigurationFromProjectGraph,
|
|
||||||
} from '../project-graph/project-graph';
|
|
||||||
import { readNxJson } from '../config/configuration';
|
|
||||||
|
|
||||||
export interface GenerateOptions {
|
export interface GenerateOptions {
|
||||||
collectionName: string;
|
collectionName: string;
|
||||||
@ -44,18 +47,21 @@ export function printChanges(fileChanges: FileChange[]) {
|
|||||||
async function promptForCollection(
|
async function promptForCollection(
|
||||||
generatorName: string,
|
generatorName: string,
|
||||||
ws: Workspaces,
|
ws: Workspaces,
|
||||||
interactive: boolean
|
interactive: boolean,
|
||||||
) {
|
projectsConfiguration: ProjectsConfigurations
|
||||||
|
): Promise<string> {
|
||||||
const packageJson = readJsonFile(`${workspaceRoot}/package.json`);
|
const packageJson = readJsonFile(`${workspaceRoot}/package.json`);
|
||||||
const collections = Array.from(
|
const localPlugins = getLocalWorkspacePlugins(projectsConfiguration);
|
||||||
|
|
||||||
|
const installedCollections = Array.from(
|
||||||
new Set([
|
new Set([
|
||||||
...Object.keys(packageJson.dependencies || {}),
|
...Object.keys(packageJson.dependencies || {}),
|
||||||
...Object.keys(packageJson.devDependencies || {}),
|
...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 {
|
try {
|
||||||
const { resolvedCollectionName, normalizedGeneratorName } =
|
const { resolvedCollectionName, normalizedGeneratorName } =
|
||||||
ws.readGenerator(collectionName, generatorName);
|
ws.readGenerator(collectionName, generatorName);
|
||||||
@ -64,14 +70,44 @@ async function promptForCollection(
|
|||||||
} catch {}
|
} 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) {
|
if (choices.length === 1) {
|
||||||
return choices[0];
|
return typeof choices[0] === 'string' ? choices[0] : choices[0].value;
|
||||||
} else if (!interactive && choices.length > 1) {
|
} else if (!interactive && choices.length > 1) {
|
||||||
throwInvalidInvocation(choices);
|
throwInvalidInvocation(Array.from(choicesMap));
|
||||||
} else if (interactive && choices.length > 1) {
|
} else if (interactive && choices.length > 1) {
|
||||||
const noneOfTheAbove = `None of the above`;
|
const noneOfTheAbove = `\nNone of the above`;
|
||||||
choices.push(noneOfTheAbove);
|
choices.push(noneOfTheAbove);
|
||||||
let { generator, customCollection } = await prompt<{
|
let { generator, customCollection } = await prompt<{
|
||||||
generator: string;
|
generator: string;
|
||||||
@ -81,7 +117,8 @@ async function promptForCollection(
|
|||||||
name: 'generator',
|
name: 'generator',
|
||||||
message: `Which generator would you like to use?`,
|
message: `Which generator would you like to use?`,
|
||||||
type: 'autocomplete',
|
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',
|
name: 'customCollection',
|
||||||
@ -135,7 +172,8 @@ async function convertToGenerateOptions(
|
|||||||
generatorOptions: { [p: string]: any },
|
generatorOptions: { [p: string]: any },
|
||||||
ws: Workspaces,
|
ws: Workspaces,
|
||||||
defaultCollectionName: string,
|
defaultCollectionName: string,
|
||||||
mode: 'generate' | 'new'
|
mode: 'generate' | 'new',
|
||||||
|
projectsConfiguration?: ProjectsConfigurations
|
||||||
): Promise<GenerateOptions> {
|
): Promise<GenerateOptions> {
|
||||||
let collectionName: string | null = null;
|
let collectionName: string | null = null;
|
||||||
let generatorName: string | null = null;
|
let generatorName: string | null = null;
|
||||||
@ -152,7 +190,8 @@ async function convertToGenerateOptions(
|
|||||||
const generatorString = await promptForCollection(
|
const generatorString = await promptForCollection(
|
||||||
generatorDescriptor,
|
generatorDescriptor,
|
||||||
ws,
|
ws,
|
||||||
interactive
|
interactive,
|
||||||
|
projectsConfiguration
|
||||||
);
|
);
|
||||||
const parsedGeneratorString = parseGeneratorString(generatorString);
|
const parsedGeneratorString = parseGeneratorString(generatorString);
|
||||||
collectionName = parsedGeneratorString.collection;
|
collectionName = parsedGeneratorString.collection;
|
||||||
@ -297,7 +336,8 @@ export async function generate(cwd: string, args: { [k: string]: any }) {
|
|||||||
args,
|
args,
|
||||||
ws,
|
ws,
|
||||||
readDefaultCollection(nxJson),
|
readDefaultCollection(nxJson),
|
||||||
'generate'
|
'generate',
|
||||||
|
projectsConfiguration
|
||||||
);
|
);
|
||||||
const { normalizedGeneratorName, schema, implementationFactory, aliases } =
|
const { normalizedGeneratorName, schema, implementationFactory, aliases } =
|
||||||
ws.readGenerator(opts.collectionName, opts.generatorName);
|
ws.readGenerator(opts.collectionName, opts.generatorName);
|
||||||
|
|||||||
@ -9,6 +9,14 @@ import {
|
|||||||
listInstalledPlugins,
|
listInstalledPlugins,
|
||||||
listPluginCapabilities,
|
listPluginCapabilities,
|
||||||
} from '../utils/plugins';
|
} from '../utils/plugins';
|
||||||
|
import {
|
||||||
|
getLocalWorkspacePlugins,
|
||||||
|
listLocalWorkspacePlugins,
|
||||||
|
} from '../utils/plugins/local-plugins';
|
||||||
|
import {
|
||||||
|
createProjectGraphAsync,
|
||||||
|
readProjectsConfigurationFromProjectGraph,
|
||||||
|
} from '../project-graph/project-graph';
|
||||||
|
|
||||||
export interface ListArgs {
|
export interface ListArgs {
|
||||||
/** The name of an installed plugin to query */
|
/** The name of an installed plugin to query */
|
||||||
@ -36,12 +44,18 @@ export async function listHandler(args: ListArgs): Promise<void> {
|
|||||||
|
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
const projectGraph = await createProjectGraphAsync();
|
||||||
|
|
||||||
|
const localPlugins = getLocalWorkspacePlugins(
|
||||||
|
readProjectsConfigurationFromProjectGraph(projectGraph)
|
||||||
|
);
|
||||||
const installedPlugins = getInstalledPluginsFromPackageJson(
|
const installedPlugins = getInstalledPluginsFromPackageJson(
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
corePlugins,
|
corePlugins,
|
||||||
communityPlugins
|
communityPlugins
|
||||||
);
|
);
|
||||||
|
|
||||||
|
listLocalWorkspacePlugins(localPlugins);
|
||||||
listInstalledPlugins(installedPlugins);
|
listInstalledPlugins(installedPlugins);
|
||||||
listCorePlugins(installedPlugins, corePlugins);
|
listCorePlugins(installedPlugins, corePlugins);
|
||||||
listCommunityPlugins(installedPlugins, communityPlugins);
|
listCommunityPlugins(installedPlugins, communityPlugins);
|
||||||
|
|||||||
@ -8,6 +8,11 @@ import {
|
|||||||
} from '../utils/package-manager';
|
} from '../utils/package-manager';
|
||||||
import { readJsonFile } from '../utils/fileutils';
|
import { readJsonFile } from '../utils/fileutils';
|
||||||
import { PackageJson, readModulePackageJson } from '../utils/package-json';
|
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 = [
|
export const packagesWeCareAbout = [
|
||||||
'nx',
|
'nx',
|
||||||
@ -49,7 +54,7 @@ export const patternsWeIgnoreInCommunityReport: Array<string | RegExp> = [
|
|||||||
* Must be run within an Nx workspace
|
* Must be run within an Nx workspace
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function reportHandler() {
|
export async function reportHandler() {
|
||||||
const pm = detectPackageManager();
|
const pm = detectPackageManager();
|
||||||
const pmVersion = getPackageManagerVersion(pm);
|
const pmVersion = getPackageManagerVersion(pm);
|
||||||
|
|
||||||
@ -66,6 +71,22 @@ export function reportHandler() {
|
|||||||
|
|
||||||
bodyLines.push('---------------------------------------');
|
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();
|
const communityPlugins = findInstalledCommunityPlugins();
|
||||||
bodyLines.push('Community plugins:');
|
bodyLines.push('Community plugins:');
|
||||||
communityPlugins.forEach((p) => {
|
communityPlugins.forEach((p) => {
|
||||||
|
|||||||
@ -6,7 +6,11 @@ import { Workspaces } from '../config/workspaces';
|
|||||||
|
|
||||||
import { workspaceRoot } from './workspace-root';
|
import { workspaceRoot } from './workspace-root';
|
||||||
import { readJsonFile } from '../utils/fileutils';
|
import { readJsonFile } from '../utils/fileutils';
|
||||||
import { PackageJson, readModulePackageJson } from './package-json';
|
import {
|
||||||
|
PackageJson,
|
||||||
|
readModulePackageJson,
|
||||||
|
readModulePackageJsonWithoutFallbacks,
|
||||||
|
} from './package-json';
|
||||||
import { registerTsProject } from './register';
|
import { registerTsProject } from './register';
|
||||||
import {
|
import {
|
||||||
ProjectConfiguration,
|
ProjectConfiguration,
|
||||||
@ -118,7 +122,7 @@ export function readPluginPackageJson(
|
|||||||
json: PackageJson;
|
json: PackageJson;
|
||||||
} {
|
} {
|
||||||
try {
|
try {
|
||||||
const result = readModulePackageJson(pluginName, paths);
|
const result = readModulePackageJsonWithoutFallbacks(pluginName, paths);
|
||||||
return {
|
return {
|
||||||
json: result.packageJson,
|
json: result.packageJson,
|
||||||
path: result.path,
|
path: result.path,
|
||||||
|
|||||||
@ -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(
|
export function readModulePackageJson(
|
||||||
moduleSpecifier: string,
|
moduleSpecifier: string,
|
||||||
requirePaths = [workspaceRoot]
|
requirePaths = [workspaceRoot]
|
||||||
@ -117,14 +152,13 @@ export function readModulePackageJson(
|
|||||||
let packageJson: PackageJson;
|
let packageJson: PackageJson;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
packageJsonPath = require.resolve(`${moduleSpecifier}/package.json`, {
|
({ path: packageJsonPath, packageJson } =
|
||||||
paths: requirePaths,
|
readModulePackageJsonWithoutFallbacks(moduleSpecifier, requirePaths));
|
||||||
});
|
|
||||||
packageJson = readJsonFile(packageJsonPath);
|
|
||||||
} catch {
|
} catch {
|
||||||
const entryPoint = require.resolve(moduleSpecifier, {
|
const entryPoint = require.resolve(moduleSpecifier, {
|
||||||
paths: requirePaths,
|
paths: requirePaths,
|
||||||
});
|
});
|
||||||
|
|
||||||
let moduleRootPath = dirname(entryPoint);
|
let moduleRootPath = dirname(entryPoint);
|
||||||
packageJsonPath = join(moduleRootPath, 'package.json');
|
packageJsonPath = join(moduleRootPath, 'package.json');
|
||||||
|
|
||||||
|
|||||||
70
packages/nx/src/utils/plugins/local-plugins.ts
Normal file
70
packages/nx/src/utils/plugins/local-plugins.ts
Normal file
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -6,7 +6,7 @@ import type { PluginCapabilities } from './models';
|
|||||||
import { hasElements } from './shared';
|
import { hasElements } from './shared';
|
||||||
import { readJsonFile } from '../fileutils';
|
import { readJsonFile } from '../fileutils';
|
||||||
import { getPackageManagerCommand } from '../package-manager';
|
import { getPackageManagerCommand } from '../package-manager';
|
||||||
import { readModulePackageJson } from '../package-json';
|
import { readPluginPackageJson } from '../nx-plugin';
|
||||||
|
|
||||||
function tryGetCollection<T extends object>(
|
function tryGetCollection<T extends object>(
|
||||||
packageJsonPath: string,
|
packageJsonPath: string,
|
||||||
@ -30,8 +30,8 @@ export function getPluginCapabilities(
|
|||||||
pluginName: string
|
pluginName: string
|
||||||
): PluginCapabilities | null {
|
): PluginCapabilities | null {
|
||||||
try {
|
try {
|
||||||
const { packageJson, path: packageJsonPath } =
|
const { json: packageJson, path: packageJsonPath } =
|
||||||
readModulePackageJson(pluginName);
|
readPluginPackageJson(pluginName);
|
||||||
return {
|
return {
|
||||||
name: pluginName,
|
name: pluginName,
|
||||||
generators:
|
generators:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user