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": {
|
"implicitDependencies": {
|
||||||
"package.json": "*",
|
"package.json": "*",
|
||||||
".eslintrc.json": "*",
|
".eslintrc.json": "*",
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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'
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 {
|
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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user