import * as minimist from 'minimist'; import { combineOptionsForExecutor, convertToCamelCase, handleErrors, Options, Schema, } from '../shared/params'; import { printHelp } from '../shared/print-help'; import { ExecutorContext, ProjectConfiguration, TargetConfiguration, WorkspaceConfiguration, Workspaces, } from '../shared/workspace'; import * as chalk from 'chalk'; import { logger } from '../shared/logger'; import { eachValueFrom } from 'rxjs-for-await'; export interface Target { project: string; target: string; configuration?: string; } export interface RunOptions extends Target { help: boolean; runOptions: Options; } function throwInvalidInvocation() { throw new Error( `Specify the project name and the target (e.g., nx run proj:build)` ); } function parseRunOpts( cwd: string, args: string[], defaultProjectName: string | null ): RunOptions { const runOptions = convertToCamelCase( minimist(args, { boolean: ['help', 'prod'], string: ['configuration', 'project'], alias: { c: 'configuration', }, }) ); const help = runOptions.help as boolean; if (!runOptions._ || !runOptions._[0]) { throwInvalidInvocation(); } // eslint-disable-next-line prefer-const let [project, target, configuration]: [ string, string, string ] = runOptions._[0].split(':'); if (!project && defaultProjectName) { logger.debug( `No project name specified. Using default project : ${chalk.bold( defaultProjectName )}` ); project = defaultProjectName; } if (runOptions.configuration) { configuration = runOptions.configuration as string; } if (runOptions.prod) { configuration = 'production'; } if (runOptions.project) { project = runOptions.project as string; } if (!project || !target) { throwInvalidInvocation(); } const res = { project, target, configuration, help, runOptions }; delete runOptions['help']; delete runOptions['_']; delete runOptions['c']; delete runOptions['configuration']; delete runOptions['prod']; delete runOptions['project']; return res; } export function printRunHelp( opts: { project: string; target: string }, schema: Schema ) { printHelp(`nx run ${opts.project}:${opts.target}`, schema); } export function validateProject( workspace: WorkspaceConfiguration, projectName: string ) { const project = workspace.projects[projectName]; if (!project) { throw new Error(`Could not find project "${projectName}"`); } } function isPromise( v: Promise<{ success: boolean }> | AsyncIterableIterator<{ success: boolean }> ): v is Promise<{ success: boolean }> { return typeof (v as any).then === 'function'; } async function* promiseToIterator( v: Promise<{ success: boolean }> ): AsyncIterableIterator<{ success: boolean }> { yield await v; } async function iteratorToProcessStatusCode( i: AsyncIterableIterator<{ success: boolean }> ): Promise { let r; for await (r of i) { } if (!r) { throw new Error('NX Executor has not returned or yielded a response.'); } return r.success ? 0 : 1; } function createImplicitTargetConfig( proj: ProjectConfiguration, targetName: string ): TargetConfiguration { return { executor: '@nrwl/workspace:run-script', options: { script: targetName, }, }; } async function runExecutorInternal( { project, target, configuration, }: { project: string; target: string; configuration?: string; }, options: { [k: string]: any }, root: string, cwd: string, workspace: WorkspaceConfiguration, isVerbose: boolean, printHelp: boolean ): Promise> { validateProject(workspace, project); const ws = new Workspaces(root); const proj = workspace.projects[project]; const targetConfig = proj.targets && proj.targets[target] ? proj.targets[target] : createImplicitTargetConfig(proj, target); const [nodeModule, executor] = targetConfig.executor.split(':'); const { schema, implementationFactory } = ws.readExecutor( nodeModule, executor ); if (printHelp) { printRunHelp({ project, target }, schema); process.exit(0); } const combinedOptions = combineOptionsForExecutor( options, configuration, targetConfig, schema, project, ws.relativeCwd(cwd) ); if (ws.isNxExecutor(nodeModule, executor)) { const implementation = implementationFactory(); const r = implementation(combinedOptions, { root: root, target: targetConfig, workspace: workspace, projectName: project, targetName: target, configurationName: configuration, cwd: cwd, isVerbose: isVerbose, }); return (isPromise(r) ? promiseToIterator(r) : r) as any; } else { const observable = await (await import('./ngcli-adapter')).scheduleTarget( root, { project, target, configuration, runOptions: combinedOptions, }, isVerbose ); return eachValueFrom(observable as any); } } /** * Loads and invokes executor. * * This is analogous to invoking executor from the terminal, with the exception * that the params aren't parsed from the string, but instead provided parsed already. * * Apart from that, it works the same way: * * - it will load the workspace configuration * - it will resolve the target * - it will load the executor and the schema * - it will load the options for the appropriate configuration * - it will run the validations and will set the default * - and, of course, it will invoke the executor * * Example: * * ```typescript * for await (const s of await runExecutor({project: 'myproj', target: 'serve'}, {watch: true}, context)) { * // s.success * } * ``` * * Note that the return value is a promise of an iterator, so you need to await before iterating over it. */ export async function runExecutor( targetDescription: { project: string; target: string; configuration?: string; }, options: { [k: string]: any }, context: ExecutorContext ): Promise> { return await runExecutorInternal( targetDescription, options, context.root, context.cwd, context.workspace, context.isVerbose, false ); } export async function run( cwd: string, root: string, args: string[], isVerbose: boolean ) { const ws = new Workspaces(root); return handleErrors(isVerbose, async () => { const workspace = ws.readWorkspaceConfiguration(); const defaultProjectName = ws.calculateDefaultProjectName(cwd, workspace); const opts = parseRunOpts(cwd, args, defaultProjectName); return iteratorToProcessStatusCode( await runExecutorInternal( opts, opts.runOptions, root, cwd, workspace, isVerbose, opts.help ) ); }); }