feat(core): prompt for available generators (#10463)

This commit is contained in:
Jason Jean 2022-05-27 17:46:43 -04:00 committed by GitHub
parent affa979642
commit 3bcaa185ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 203 additions and 28 deletions

View File

@ -1,4 +1,5 @@
{ {
"$schema": "packages/nx/schemas/nx-schema.json",
"implicitDependencies": { "implicitDependencies": {
"package.json": "*", "package.json": "*",
".eslintrc.json": "*", ".eslintrc.json": "*",

View File

@ -11,6 +11,12 @@
"version": "14.2.0-beta.0", "version": "14.2.0-beta.0",
"description": "Add JSON Schema to Nx configuration files", "description": "Add JSON Schema to Nx configuration files",
"factory": "./src/migrations/update-14-2-0/add-json-schema" "factory": "./src/migrations/update-14-2-0/add-json-schema"
},
"14-2-0-remove-default-collection": {
"cli": "nx",
"version": "14.2.0-beta.0",
"description": "Remove default collection from configuration to switch to prompts for collection",
"factory": "./src/migrations/update-14-2-0/remove-default-collection"
} }
} }
} }

View File

@ -213,6 +213,9 @@ async function runSchematic(
type AngularJsonConfiguration = WorkspaceJsonConfiguration & type AngularJsonConfiguration = WorkspaceJsonConfiguration &
Pick<NxJsonConfiguration, 'cli' | 'defaultProject' | 'generators'> & { Pick<NxJsonConfiguration, 'cli' | 'defaultProject' | 'generators'> & {
schematics?: NxJsonConfiguration['generators']; schematics?: NxJsonConfiguration['generators'];
cli?: NxJsonConfiguration['cli'] & {
schematicCollections?: string[];
};
}; };
export class NxScopedHost extends virtualFs.ScopedHost<any> { export class NxScopedHost extends virtualFs.ScopedHost<any> {
protected __nxInMemoryWorkspace: WorkspaceJsonConfiguration | null; protected __nxInMemoryWorkspace: WorkspaceJsonConfiguration | null;
@ -432,6 +435,7 @@ export class NxScopedHost extends virtualFs.ScopedHost<any> {
if (formatted) { if (formatted) {
const { cli, generators, defaultProject, ...workspaceJson } = const { cli, generators, defaultProject, ...workspaceJson } =
formatted; formatted;
delete cli.schematicCollections;
return merge( return merge(
this.writeWorkspaceConfigFiles(context, workspaceJson), this.writeWorkspaceConfigFiles(context, workspaceJson),
cli || generators || defaultProject cli || generators || defaultProject
@ -446,6 +450,7 @@ export class NxScopedHost extends virtualFs.ScopedHost<any> {
defaultProject, defaultProject,
...angularJson ...angularJson
} = w; } = w;
delete cli.schematicCollections;
return merge( return merge(
this.writeWorkspaceConfigFiles(context, angularJson), this.writeWorkspaceConfigFiles(context, angularJson),
cli || schematics cli || schematics
@ -461,6 +466,8 @@ export class NxScopedHost extends virtualFs.ScopedHost<any> {
} }
const { cli, schematics, generators, defaultProject, ...angularJson } = const { cli, schematics, generators, defaultProject, ...angularJson } =
config; config;
delete cli.schematicCollections;
return merge( return merge(
this.writeWorkspaceConfigFiles(context, angularJson), this.writeWorkspaceConfigFiles(context, angularJson),
this.__saveNxJsonProps({ this.__saveNxJsonProps({

View File

@ -11,6 +11,8 @@ import * as chalk from 'chalk';
import { workspaceRoot } from '../utils/app-root'; import { workspaceRoot } from '../utils/app-root';
import { NxJsonConfiguration } from '../config/nx-json'; import { NxJsonConfiguration } from '../config/nx-json';
import { printHelp } from '../utils/print-help'; import { printHelp } from '../utils/print-help';
import { prompt } from 'enquirer';
import { readJsonFile } from 'nx/src/utils/fileutils';
export interface GenerateOptions { export interface GenerateOptions {
collectionName: string; collectionName: string;
@ -34,21 +36,121 @@ function printChanges(fileChanges: FileChange[]) {
}); });
} }
function convertToGenerateOptions( async function promptForCollection(
generatorOptions: { [k: string]: any }, generatorName: string,
ws: Workspaces,
interactive: boolean
) {
const packageJson = readJsonFile(`${workspaceRoot}/package.json`);
const collections = Array.from(
new Set([
...Object.keys(packageJson.dependencies || {}),
...Object.keys(packageJson.devDependencies || {}),
])
);
const choices = collections
.map((collectionName) => {
try {
const generator = ws.readGenerator(collectionName, generatorName);
return `${collectionName}:${generator.normalizedGeneratorName}`;
} catch {
return null;
}
})
.filter((c) => !!c);
if (choices.length === 1) {
return choices[0];
} else if (!interactive && choices.length > 1) {
throwInvalidInvocation(choices);
} else if (interactive && choices.length > 1) {
const noneOfTheAbove = `None of the above`;
choices.push(noneOfTheAbove);
let { generator, customCollection } = await prompt<{
generator: string;
customCollection?: string;
}>([
{
name: 'generator',
message: `Which generator would you like to use?`,
type: 'autocomplete',
choices,
},
{
name: 'customCollection',
type: 'input',
message: `Which collection would you like to use?`,
skip: function () {
// Skip this question if the user did not answer None of the above
return this.state.answers.generator !== noneOfTheAbove;
},
validate: function (value) {
if (this.skipped) {
return true;
}
try {
ws.readGenerator(value, generatorName);
return true;
} catch {
logger.error(`\nCould not find ${value}:${generatorName}`);
return false;
}
},
},
]);
return customCollection
? `${customCollection}:${generatorName}`
: generator;
} else {
throw new Error(`Could not find any generators named "${generatorName}"`);
}
}
function parseGeneratorString(value: string): {
collection?: string;
generator: string;
} {
const separatorIndex = value.lastIndexOf(':');
if (separatorIndex > 0) {
return {
collection: value.slice(0, separatorIndex),
generator: value.slice(separatorIndex + 1),
};
} else {
return {
generator: value,
};
}
}
async function convertToGenerateOptions(
generatorOptions: { [p: string]: any },
ws: Workspaces,
defaultCollectionName: string, defaultCollectionName: string,
mode: 'generate' | 'new' mode: 'generate' | 'new'
): GenerateOptions { ): Promise<GenerateOptions> {
let collectionName: string | null = null; let collectionName: string | null = null;
let generatorName: string | null = null; let generatorName: string | null = null;
const interactive = generatorOptions.interactive as boolean;
if (mode === 'generate') { if (mode === 'generate') {
const generatorDescriptor = generatorOptions['generator'] as string; const generatorDescriptor = generatorOptions['generator'] as string;
const separatorIndex = generatorDescriptor.lastIndexOf(':'); const { collection, generator } = parseGeneratorString(generatorDescriptor);
if (separatorIndex > 0) { if (collection) {
collectionName = generatorDescriptor.slice(0, separatorIndex); collectionName = collection;
generatorName = generatorDescriptor.slice(separatorIndex + 1); generatorName = generator;
} else if (!defaultCollectionName) {
const generatorString = await promptForCollection(
generatorDescriptor,
ws,
interactive
);
const parsedGeneratorString = parseGeneratorString(generatorString);
collectionName = parsedGeneratorString.collection;
generatorName = parsedGeneratorString.generator;
} else { } else {
collectionName = defaultCollectionName; collectionName = defaultCollectionName;
generatorName = generatorDescriptor; generatorName = generatorDescriptor;
@ -59,16 +161,18 @@ function convertToGenerateOptions(
} }
if (!collectionName) { if (!collectionName) {
throwInvalidInvocation(); throwInvalidInvocation(['@nrwl/workspace:library']);
} }
logger.info(`NX Generating ${collectionName}:${generatorName}`);
const res = { const res = {
collectionName, collectionName,
generatorName, generatorName,
generatorOptions, generatorOptions,
help: generatorOptions.help as boolean, help: generatorOptions.help as boolean,
dryRun: generatorOptions.dryRun as boolean, dryRun: generatorOptions.dryRun as boolean,
interactive: generatorOptions.interactive as boolean, interactive,
defaults: generatorOptions.defaults as boolean, defaults: generatorOptions.defaults as boolean,
}; };
@ -86,9 +190,11 @@ function convertToGenerateOptions(
return res; return res;
} }
function throwInvalidInvocation() { function throwInvalidInvocation(availableGenerators: string[]) {
throw new Error( throw new Error(
`Specify the generator name (e.g., nx generate @nrwl/workspace:library)` `Specify the generator name (e.g., nx generate ${availableGenerators.join(
', '
)})`
); );
} }
@ -122,7 +228,7 @@ export async function newWorkspace(cwd: string, args: { [k: string]: any }) {
const isVerbose = args['verbose']; const isVerbose = args['verbose'];
return handleErrors(isVerbose, async () => { return handleErrors(isVerbose, async () => {
const opts = convertToGenerateOptions(args, null, 'new'); const opts = await convertToGenerateOptions(args, ws, null, 'new');
const { normalizedGeneratorName, schema, implementationFactory } = const { normalizedGeneratorName, schema, implementationFactory } =
ws.readGenerator(opts.collectionName, opts.generatorName); ws.readGenerator(opts.collectionName, opts.generatorName);
@ -172,8 +278,9 @@ export async function generate(cwd: string, args: { [k: string]: any }) {
return handleErrors(isVerbose, async () => { return handleErrors(isVerbose, async () => {
const workspaceDefinition = ws.readWorkspaceConfiguration(); const workspaceDefinition = ws.readWorkspaceConfiguration();
const opts = convertToGenerateOptions( const opts = await convertToGenerateOptions(
args, args,
ws,
readDefaultCollection(workspaceDefinition), readDefaultCollection(workspaceDefinition),
'generate' 'generate'
); );

View File

@ -0,0 +1,44 @@
import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace';
import type { Tree } from '../../generators/tree';
import removeDefaultCollection from './remove-default-collection';
import {
readWorkspaceConfiguration,
updateWorkspaceConfiguration,
} from '../../generators/utils/project-configuration';
describe('remove-default-collection', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace(2);
});
it('should remove default collection from nx.json', async () => {
const config = readWorkspaceConfiguration(tree);
config.cli = {
defaultCollection: 'default-collection',
defaultProjectName: 'default-project',
};
updateWorkspaceConfiguration(tree, config);
await removeDefaultCollection(tree);
expect(readWorkspaceConfiguration(tree).cli).toEqual({
defaultProjectName: 'default-project',
});
});
it('should remove cli entirely if defaultCollection was the only setting', async () => {
const config = readWorkspaceConfiguration(tree);
config.cli = {
defaultCollection: 'default-collection',
};
updateWorkspaceConfiguration(tree, config);
await removeDefaultCollection(tree);
expect(
readWorkspaceConfiguration(tree).cli?.defaultCollection
).toBeUndefined();
});
});

View File

@ -0,0 +1,19 @@
import { Tree } from '../../generators/tree';
import {
readWorkspaceConfiguration,
updateWorkspaceConfiguration,
} from '../../generators/utils/project-configuration';
import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available';
export default async function (tree: Tree) {
const workspaceConfiguration = readWorkspaceConfiguration(tree);
delete workspaceConfiguration.cli?.defaultCollection;
if (Object.keys(workspaceConfiguration.cli).length === 0) {
delete workspaceConfiguration.cli;
}
updateWorkspaceConfiguration(tree, workspaceConfiguration);
await formatChangedFilesWithPrettierIfAvailable(tree);
}

View File

@ -1,21 +1,12 @@
export interface PluginGenerator { import {
factory: string; ExecutorsJsonEntry,
schema: string; GeneratorsJsonEntry,
description: string; } from '../../config/misc-interfaces';
aliases: string;
hidden: boolean;
}
export interface PluginExecutor {
implementation: string;
schema: string;
description: string;
}
export interface PluginCapabilities { export interface PluginCapabilities {
name: string; name: string;
executors: { [name: string]: PluginExecutor }; executors: { [name: string]: ExecutorsJsonEntry };
generators: { [name: string]: PluginGenerator }; generators: { [name: string]: GeneratorsJsonEntry };
} }
export interface CorePlugin { export interface CorePlugin {