290 lines
6.9 KiB
TypeScript

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<number> {
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<T extends { success: boolean }>(
{
project,
target,
configuration,
}: {
project: string;
target: string;
configuration?: string;
},
options: { [k: string]: any },
root: string,
cwd: string,
workspace: WorkspaceConfiguration,
isVerbose: boolean,
printHelp: boolean
): Promise<AsyncIterableIterator<T>> {
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<T>(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<T extends { success: boolean }>(
targetDescription: {
project: string;
target: string;
configuration?: string;
},
options: { [k: string]: any },
context: ExecutorContext
): Promise<AsyncIterableIterator<T>> {
return await runExecutorInternal<T>(
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
)
);
});
}