feat(core): prompt for available generators (#10463)
This commit is contained in:
parent
affa979642
commit
3bcaa185ff
1
nx.json
1
nx.json
@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "packages/nx/schemas/nx-schema.json",
|
||||
"implicitDependencies": {
|
||||
"package.json": "*",
|
||||
".eslintrc.json": "*",
|
||||
|
||||
@ -11,6 +11,12 @@
|
||||
"version": "14.2.0-beta.0",
|
||||
"description": "Add JSON Schema to Nx configuration files",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,6 +213,9 @@ async function runSchematic(
|
||||
type AngularJsonConfiguration = WorkspaceJsonConfiguration &
|
||||
Pick<NxJsonConfiguration, 'cli' | 'defaultProject' | 'generators'> & {
|
||||
schematics?: NxJsonConfiguration['generators'];
|
||||
cli?: NxJsonConfiguration['cli'] & {
|
||||
schematicCollections?: string[];
|
||||
};
|
||||
};
|
||||
export class NxScopedHost extends virtualFs.ScopedHost<any> {
|
||||
protected __nxInMemoryWorkspace: WorkspaceJsonConfiguration | null;
|
||||
@ -432,6 +435,7 @@ export class NxScopedHost extends virtualFs.ScopedHost<any> {
|
||||
if (formatted) {
|
||||
const { cli, generators, defaultProject, ...workspaceJson } =
|
||||
formatted;
|
||||
delete cli.schematicCollections;
|
||||
return merge(
|
||||
this.writeWorkspaceConfigFiles(context, workspaceJson),
|
||||
cli || generators || defaultProject
|
||||
@ -446,6 +450,7 @@ export class NxScopedHost extends virtualFs.ScopedHost<any> {
|
||||
defaultProject,
|
||||
...angularJson
|
||||
} = w;
|
||||
delete cli.schematicCollections;
|
||||
return merge(
|
||||
this.writeWorkspaceConfigFiles(context, angularJson),
|
||||
cli || schematics
|
||||
@ -461,6 +466,8 @@ export class NxScopedHost extends virtualFs.ScopedHost<any> {
|
||||
}
|
||||
const { cli, schematics, generators, defaultProject, ...angularJson } =
|
||||
config;
|
||||
delete cli.schematicCollections;
|
||||
|
||||
return merge(
|
||||
this.writeWorkspaceConfigFiles(context, angularJson),
|
||||
this.__saveNxJsonProps({
|
||||
|
||||
@ -11,6 +11,8 @@ import * as chalk from 'chalk';
|
||||
import { workspaceRoot } from '../utils/app-root';
|
||||
import { NxJsonConfiguration } from '../config/nx-json';
|
||||
import { printHelp } from '../utils/print-help';
|
||||
import { prompt } from 'enquirer';
|
||||
import { readJsonFile } from 'nx/src/utils/fileutils';
|
||||
|
||||
export interface GenerateOptions {
|
||||
collectionName: string;
|
||||
@ -34,21 +36,121 @@ function printChanges(fileChanges: FileChange[]) {
|
||||
});
|
||||
}
|
||||
|
||||
function convertToGenerateOptions(
|
||||
generatorOptions: { [k: string]: any },
|
||||
async function promptForCollection(
|
||||
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,
|
||||
mode: 'generate' | 'new'
|
||||
): GenerateOptions {
|
||||
): Promise<GenerateOptions> {
|
||||
let collectionName: string | null = null;
|
||||
let generatorName: string | null = null;
|
||||
const interactive = generatorOptions.interactive as boolean;
|
||||
|
||||
if (mode === 'generate') {
|
||||
const generatorDescriptor = generatorOptions['generator'] as string;
|
||||
const separatorIndex = generatorDescriptor.lastIndexOf(':');
|
||||
const { collection, generator } = parseGeneratorString(generatorDescriptor);
|
||||
|
||||
if (separatorIndex > 0) {
|
||||
collectionName = generatorDescriptor.slice(0, separatorIndex);
|
||||
generatorName = generatorDescriptor.slice(separatorIndex + 1);
|
||||
if (collection) {
|
||||
collectionName = collection;
|
||||
generatorName = generator;
|
||||
} else if (!defaultCollectionName) {
|
||||
const generatorString = await promptForCollection(
|
||||
generatorDescriptor,
|
||||
ws,
|
||||
interactive
|
||||
);
|
||||
const parsedGeneratorString = parseGeneratorString(generatorString);
|
||||
collectionName = parsedGeneratorString.collection;
|
||||
generatorName = parsedGeneratorString.generator;
|
||||
} else {
|
||||
collectionName = defaultCollectionName;
|
||||
generatorName = generatorDescriptor;
|
||||
@ -59,16 +161,18 @@ function convertToGenerateOptions(
|
||||
}
|
||||
|
||||
if (!collectionName) {
|
||||
throwInvalidInvocation();
|
||||
throwInvalidInvocation(['@nrwl/workspace:library']);
|
||||
}
|
||||
|
||||
logger.info(`NX Generating ${collectionName}:${generatorName}`);
|
||||
|
||||
const res = {
|
||||
collectionName,
|
||||
generatorName,
|
||||
generatorOptions,
|
||||
help: generatorOptions.help as boolean,
|
||||
dryRun: generatorOptions.dryRun as boolean,
|
||||
interactive: generatorOptions.interactive as boolean,
|
||||
interactive,
|
||||
defaults: generatorOptions.defaults as boolean,
|
||||
};
|
||||
|
||||
@ -86,9 +190,11 @@ function convertToGenerateOptions(
|
||||
return res;
|
||||
}
|
||||
|
||||
function throwInvalidInvocation() {
|
||||
function throwInvalidInvocation(availableGenerators: string[]) {
|
||||
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'];
|
||||
|
||||
return handleErrors(isVerbose, async () => {
|
||||
const opts = convertToGenerateOptions(args, null, 'new');
|
||||
const opts = await convertToGenerateOptions(args, ws, null, 'new');
|
||||
const { normalizedGeneratorName, schema, implementationFactory } =
|
||||
ws.readGenerator(opts.collectionName, opts.generatorName);
|
||||
|
||||
@ -172,8 +278,9 @@ export async function generate(cwd: string, args: { [k: string]: any }) {
|
||||
|
||||
return handleErrors(isVerbose, async () => {
|
||||
const workspaceDefinition = ws.readWorkspaceConfiguration();
|
||||
const opts = convertToGenerateOptions(
|
||||
const opts = await convertToGenerateOptions(
|
||||
args,
|
||||
ws,
|
||||
readDefaultCollection(workspaceDefinition),
|
||||
'generate'
|
||||
);
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
@ -1,21 +1,12 @@
|
||||
export interface PluginGenerator {
|
||||
factory: string;
|
||||
schema: string;
|
||||
description: string;
|
||||
aliases: string;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
export interface PluginExecutor {
|
||||
implementation: string;
|
||||
schema: string;
|
||||
description: string;
|
||||
}
|
||||
import {
|
||||
ExecutorsJsonEntry,
|
||||
GeneratorsJsonEntry,
|
||||
} from '../../config/misc-interfaces';
|
||||
|
||||
export interface PluginCapabilities {
|
||||
name: string;
|
||||
executors: { [name: string]: PluginExecutor };
|
||||
generators: { [name: string]: PluginGenerator };
|
||||
executors: { [name: string]: ExecutorsJsonEntry };
|
||||
generators: { [name: string]: GeneratorsJsonEntry };
|
||||
}
|
||||
|
||||
export interface CorePlugin {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user