nx/packages/tao/src/commands/generate.ts

248 lines
6.4 KiB
TypeScript

import * as minimist from 'minimist';
import {
combineOptionsForGenerator,
convertToCamelCase,
handleErrors,
Options,
Schema,
} from '../shared/params';
import { printHelp } from '../shared/print-help';
import { WorkspaceConfiguration, Workspaces } from '../shared/workspace';
import { statSync, unlinkSync, writeFileSync } from 'fs';
import { mkdirpSync, rmdirSync } from 'fs-extra';
import * as path from 'path';
import { FileChange, FsTree } from '../shared/tree';
import { logger } from '../shared/logger';
const chalk = require('chalk');
export interface GenerateOptions {
collectionName: string;
generatorName: string;
generatorOptions: Options;
help: boolean;
debug: boolean;
dryRun: boolean;
force: boolean;
interactive: boolean;
defaults: boolean;
}
function throwInvalidInvocation() {
throw new Error(
`Specify the generator name (e.g., nx generate @nrwl/workspace:library)`
);
}
function parseGenerateOpts(
args: string[],
mode: 'generate' | 'new',
defaultCollection: string | null
): GenerateOptions {
const generatorOptions = convertToCamelCase(
minimist(args, {
boolean: ['help', 'dryRun', 'debug', 'force', 'interactive'],
alias: {
dryRun: 'dry-run',
d: 'dryRun',
},
default: {
debug: false,
dryRun: false,
interactive: true,
},
})
);
let collectionName: string | null = null;
let generatorName: string | null = null;
if (mode === 'generate') {
if (
!generatorOptions['_'] ||
(generatorOptions['_'] as string[]).length === 0
) {
throwInvalidInvocation();
}
const generatorDescriptor = (generatorOptions['_'] as string[]).shift();
const separatorIndex = generatorDescriptor.lastIndexOf(':');
if (separatorIndex > 0) {
collectionName = generatorDescriptor.substr(0, separatorIndex);
generatorName = generatorDescriptor.substr(separatorIndex + 1);
} else {
collectionName = defaultCollection;
generatorName = generatorDescriptor;
}
} else {
collectionName = generatorOptions.collection as string;
generatorName = 'new';
}
if (!collectionName) {
throwInvalidInvocation();
}
const res = {
collectionName,
generatorName,
generatorOptions,
help: generatorOptions.help as boolean,
debug: generatorOptions.debug as boolean,
dryRun: generatorOptions.dryRun as boolean,
force: generatorOptions.force as boolean,
interactive: generatorOptions.interactive as boolean,
defaults: generatorOptions.defaults as boolean,
};
delete generatorOptions.debug;
delete generatorOptions.d;
delete generatorOptions.dryRun;
delete generatorOptions.force;
delete generatorOptions.interactive;
delete generatorOptions.defaults;
delete generatorOptions.help;
delete generatorOptions['--'];
return res;
}
export function printGenHelp(opts: GenerateOptions, schema: Schema) {
printHelp(`nx generate ${opts.collectionName}:${opts.generatorName}`, {
...schema,
properties: {
...schema.properties,
dryRun: {
type: 'boolean',
default: false,
description: `Runs through and reports activity without writing to disk.`,
},
},
});
}
function readDefaultCollection(workspace: WorkspaceConfiguration) {
return workspace.cli ? workspace.cli.defaultCollection : null;
}
export function flushChanges(root: string, fileChanges: FileChange[]) {
fileChanges.forEach((f) => {
const fpath = path.join(root, f.path);
if (f.type === 'CREATE') {
mkdirpSync(path.dirname(fpath));
writeFileSync(fpath, f.content);
} else if (f.type === 'UPDATE') {
writeFileSync(fpath, f.content);
} else if (f.type === 'DELETE') {
try {
const stat = statSync(fpath);
if (stat.isDirectory()) {
rmdirSync(fpath, { recursive: true });
} else {
unlinkSync(fpath);
}
} catch (e) {}
}
});
}
function printChanges(fileChanges: FileChange[]) {
fileChanges.forEach((f) => {
if (f.type === 'CREATE') {
console.log(`${chalk.green('CREATE')} ${f.path}`);
} else if (f.type === 'UPDATE') {
console.log(`${chalk.white('UPDATE')} ${f.path}`);
} else if (f.type === 'DELETE') {
console.log(`${chalk.yellow('DELETE')} ${f.path}`);
}
});
}
export async function taoNew(root: string, args: string[], isVerbose = false) {
const ws = new Workspaces(root);
return handleErrors(isVerbose, async () => {
const opts = parseGenerateOpts(args, 'new', null);
const { schema, implementation } = ws.readGenerator(
opts.collectionName,
opts.generatorName
);
const combinedOpts = await combineOptionsForGenerator(
opts.generatorOptions,
opts.collectionName,
opts.generatorName,
null,
schema,
opts.interactive
);
return (await import('./ngcli-adapter')).invokeNew(
root,
{
...opts,
generatorOptions: combinedOpts,
},
isVerbose
);
});
}
export async function generate(
root: string,
args: string[],
isVerbose = false
) {
const ws = new Workspaces(root);
return handleErrors(isVerbose, async () => {
const workspaceDefinition = ws.readWorkspaceConfiguration();
const opts = parseGenerateOpts(
args,
'generate',
readDefaultCollection(workspaceDefinition)
);
const { schema, implementation } = ws.readGenerator(
opts.collectionName,
opts.generatorName
);
if (opts.help) {
printGenHelp(opts, schema);
return 0;
}
const combinedOpts = await combineOptionsForGenerator(
opts.generatorOptions,
opts.collectionName,
opts.generatorName,
workspaceDefinition,
schema,
opts.interactive
);
if (ws.isNxGenerator(opts.collectionName, opts.generatorName)) {
const host = new FsTree(root, isVerbose);
const task = await implementation(host, combinedOpts);
const changes = host.listChanges();
printChanges(changes);
if (!opts.dryRun) {
flushChanges(root, changes);
if (task) {
await task(host);
}
} else {
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
}
} else {
return (await import('./ngcli-adapter')).generate(
root,
{
...opts,
generatorOptions: combinedOpts,
},
isVerbose
);
}
});
}