nx/packages/tao/src/commands/generate.ts
2020-11-17 19:31:08 -05:00

223 lines
5.8 KiB
TypeScript

import * as minimist from 'minimist';
import { getLogger } from '../shared/logger';
import {
combineOptionsForSchematic,
convertToCamelCase,
handleErrors,
Options,
Schema,
} from '../shared/params';
import { commandName, printHelp } from '../shared/print-help';
import { WorkspaceDefinition, 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';
const chalk = require('chalk');
export interface GenerateOptions {
collectionName: string;
schematicName: string;
schematicOptions: Options;
help: boolean;
debug: boolean;
dryRun: boolean;
force: boolean;
interactive: boolean;
defaults: boolean;
}
function throwInvalidInvocation() {
throw new Error(
`Specify the schematic name (e.g., ${commandName} generate collection-name:schematic-name)`
);
}
function parseGenerateOpts(
args: string[],
mode: 'generate' | 'new',
defaultCollection: string | null
): GenerateOptions {
const schematicOptions = 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 schematicName: string | null = null;
if (mode === 'generate') {
if (
!schematicOptions['_'] ||
(schematicOptions['_'] as string[]).length === 0
) {
throwInvalidInvocation();
}
[collectionName, schematicName] = (schematicOptions['_'] as string[])
.shift()
.split(':');
if (!schematicName) {
schematicName = collectionName;
collectionName = defaultCollection;
}
} else {
collectionName = schematicOptions.collection as string;
schematicName = '';
}
if (!collectionName) {
throwInvalidInvocation();
}
const res = {
collectionName,
schematicName,
schematicOptions,
help: schematicOptions.help as boolean,
debug: schematicOptions.debug as boolean,
dryRun: schematicOptions.dryRun as boolean,
force: schematicOptions.force as boolean,
interactive: schematicOptions.interactive as boolean,
defaults: schematicOptions.defaults as boolean,
};
delete schematicOptions.debug;
delete schematicOptions.d;
delete schematicOptions.dryRun;
delete schematicOptions.force;
delete schematicOptions.interactive;
delete schematicOptions.defaults;
delete schematicOptions.help;
delete schematicOptions['--'];
return res;
}
export function printGenHelp(
opts: GenerateOptions,
schema: Schema,
logger: Console
) {
printHelp(
`${commandName} generate ${opts.collectionName}:${opts.schematicName}`,
{
...schema,
properties: {
...schema.properties,
dryRun: {
type: 'boolean',
default: false,
description: `Runs through and reports activity without writing to disk.`,
},
},
},
logger as any
);
}
function readDefaultCollection(workspace: WorkspaceDefinition) {
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 logger = getLogger(isVerbose);
return handleErrors(logger, isVerbose, async () => {
const opts = parseGenerateOpts(args, 'new', null);
return (await import('./ngcli-adapter')).invokeNew(logger, root, opts);
});
}
export async function generate(
root: string,
args: string[],
isVerbose = false
) {
const logger = getLogger(isVerbose);
const ws = new Workspaces();
return handleErrors(logger, isVerbose, async () => {
const workspaceDefinition = await ws.readWorkspaceConfiguration(root);
const opts = parseGenerateOpts(
args,
'generate',
readDefaultCollection(workspaceDefinition)
);
if (ws.isNxSchematic(opts.collectionName, opts.schematicName)) {
const { schema, implementation } = ws.readSchematic(
opts.collectionName,
opts.schematicName
);
if (opts.help) {
printGenHelp(opts, schema, logger as any);
return 0;
}
const combinedOpts = await combineOptionsForSchematic(
opts.schematicOptions,
opts.collectionName,
opts.schematicName,
workspaceDefinition,
schema,
opts.interactive
);
const host = new FsTree(root, isVerbose, logger);
await implementation(combinedOpts)(host);
const changes = host.listChanges();
printChanges(changes);
if (!opts.dryRun) {
flushChanges(root, changes);
} else {
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
}
} else {
return (await import('./ngcli-adapter')).generate(logger, root, opts);
}
});
}