feat(core): running one task uses the same tasks runner as run-many
This commit is contained in:
parent
66634804ad
commit
c3fdf2e702
@ -268,11 +268,11 @@ forEachCli(() => {
|
||||
const build = runCommand(
|
||||
`npm run affected:build -- --files="apps/${myapp}/src/main.ts,libs/${mypublishablelib}/src/index.ts" --parallel`
|
||||
);
|
||||
console.log(build);
|
||||
|
||||
// make sure that the package is done building before we start building the app
|
||||
expect(
|
||||
build.indexOf('Built Angular Package!') <
|
||||
build.indexOf(`"build" "${myapp}"`)
|
||||
build.indexOf(`Generating ES5 bundles for differential loading.`)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ forEachCli(() => {
|
||||
});
|
||||
|
||||
describe('Cache', () => {
|
||||
it('should not use cache when it is not enabled', async () => {
|
||||
it('should cache command execution', async () => {
|
||||
ensureProject();
|
||||
|
||||
const myapp1 = uniq('myapp1');
|
||||
@ -128,6 +128,12 @@ forEachCli(() => {
|
||||
'read the output from cache'
|
||||
);
|
||||
|
||||
// build individual project with caching
|
||||
const individualBuildWithCache = runCommand(
|
||||
`npm run nx -- build ${myapp1}`
|
||||
);
|
||||
expect(individualBuildWithCache).toContain('Cached Output');
|
||||
|
||||
// run lint with caching
|
||||
// --------------------------------------------
|
||||
const outputWithNoLintCached = runCommand(
|
||||
@ -1,5 +1,7 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { Workspace } from './workspace';
|
||||
import { parseRunOneOptions } from './parse-run-one-options';
|
||||
|
||||
/**
|
||||
* Nx is being run inside a workspace.
|
||||
@ -7,6 +9,11 @@ import { Workspace } from './workspace';
|
||||
* @param workspace Relevant local workspace properties
|
||||
*/
|
||||
export function initLocal(workspace: Workspace) {
|
||||
const supportedNxCommands = require('@nrwl/workspace/src/command-line/supported-nx-commands')
|
||||
.supportedNxCommands;
|
||||
const runOpts = runOneOptions(workspace);
|
||||
|
||||
if (supportedNxCommands.includes(process.argv[2])) {
|
||||
// required to make sure nrwl/workspace import works
|
||||
if (workspace.type === 'nx') {
|
||||
require(path.join(
|
||||
@ -19,13 +26,18 @@ export function initLocal(workspace: Workspace) {
|
||||
'compat.js'
|
||||
));
|
||||
}
|
||||
require('@nrwl/workspace/src/command-line/nx-commands').commandsObject.argv;
|
||||
} else {
|
||||
if (runOpts === false || process.env.NX_SKIP_TASKS_RUNNER) {
|
||||
loadCli(workspace);
|
||||
} else {
|
||||
require('@nrwl/workspace/src/command-line/run-one').runOne(runOpts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The commandsObject is a Yargs object declared in `nx-commands.ts`,
|
||||
// It is exposed and bootstrapped here to provide CLI features.
|
||||
const w = require('@nrwl/workspace');
|
||||
if (w.supportedNxCommands.includes(process.argv[2])) {
|
||||
w.commandsObject.argv;
|
||||
} else if (workspace.type === 'nx') {
|
||||
function loadCli(workspace: Workspace) {
|
||||
if (workspace.type === 'nx') {
|
||||
require(path.join(
|
||||
workspace.dir,
|
||||
'node_modules',
|
||||
@ -34,9 +46,6 @@ export function initLocal(workspace: Workspace) {
|
||||
'index.js'
|
||||
));
|
||||
} else if (workspace.type === 'angular') {
|
||||
w.output.note({
|
||||
title: `Nx didn't recognize the command, forwarding on to the Angular CLI.`
|
||||
});
|
||||
require(path.join(
|
||||
workspace.dir,
|
||||
'node_modules',
|
||||
@ -45,5 +54,37 @@ export function initLocal(workspace: Workspace) {
|
||||
'lib',
|
||||
'init.js'
|
||||
));
|
||||
} else {
|
||||
console.error(`Cannot recognize the workspace type.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function runOneOptions(
|
||||
workspace: Workspace
|
||||
): false | { project; target; configuration; overrides } {
|
||||
try {
|
||||
const nxJson = JSON.parse(
|
||||
fs.readFileSync(path.join(workspace.dir, 'nx.json')).toString()
|
||||
);
|
||||
|
||||
const workspaceConfigJson = JSON.parse(
|
||||
fs
|
||||
.readFileSync(
|
||||
path.join(
|
||||
workspace.dir,
|
||||
workspace.type === 'nx' ? 'workspace.json' : 'angular.json'
|
||||
)
|
||||
)
|
||||
.toString()
|
||||
);
|
||||
|
||||
return parseRunOneOptions(
|
||||
nxJson,
|
||||
workspaceConfigJson,
|
||||
process.argv.slice(2)
|
||||
);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
77
packages/cli/lib/parse-run-one-options.spec.ts
Normal file
77
packages/cli/lib/parse-run-one-options.spec.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { parseRunOneOptions } from './parse-run-one-options';
|
||||
|
||||
describe('parseRunOneOptions', () => {
|
||||
const nxJson = { tasksRunnerOptions: { default: { runner: 'somerunner' } } };
|
||||
const workspaceJson = { projects: { myproj: { architect: { build: {} } } } };
|
||||
const args = ['build', 'myproj', '--configuration=production', '--flag=true'];
|
||||
|
||||
it('should work', () => {
|
||||
expect(parseRunOneOptions(nxJson, workspaceJson, args)).toEqual({
|
||||
project: 'myproj',
|
||||
target: 'build',
|
||||
configuration: 'production',
|
||||
overrides: { flag: 'true' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with run syntax', () => {
|
||||
expect(
|
||||
parseRunOneOptions(nxJson, workspaceJson, [
|
||||
'run',
|
||||
'myproj:build:production',
|
||||
'--flag=true'
|
||||
])
|
||||
).toEqual({
|
||||
project: 'myproj',
|
||||
target: 'build',
|
||||
configuration: 'production',
|
||||
overrides: { flag: 'true' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should use defaultProjectName when no provided', () => {
|
||||
expect(
|
||||
parseRunOneOptions(
|
||||
nxJson,
|
||||
{ ...workspaceJson, cli: { defaultProjectName: 'myproj' } },
|
||||
['build', '--flag=true']
|
||||
)
|
||||
).toEqual({
|
||||
project: 'myproj',
|
||||
target: 'build',
|
||||
overrides: { flag: 'true' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false when no runner is set', () => {
|
||||
expect(parseRunOneOptions({}, workspaceJson, args)).toBe(false);
|
||||
expect(
|
||||
parseRunOneOptions({ tasksRunnerOptions: {} }, workspaceJson, args)
|
||||
).toBe(false);
|
||||
expect(
|
||||
parseRunOneOptions(
|
||||
{ tasksRunnerOptions: { default: {} } },
|
||||
workspaceJson,
|
||||
args
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when the task is not recognized', () => {
|
||||
expect(parseRunOneOptions(nxJson, {}, args)).toBe(false);
|
||||
expect(parseRunOneOptions(nxJson, { projects: {} }, args)).toBe(false);
|
||||
expect(
|
||||
parseRunOneOptions(nxJson, { projects: { architect: {} } }, args)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when cannot find the right project', () => {
|
||||
expect(
|
||||
parseRunOneOptions(nxJson, workspaceJson, ['build', 'wrongproj'])
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when no project specified', () => {
|
||||
expect(parseRunOneOptions(nxJson, workspaceJson, ['build'])).toBe(false);
|
||||
});
|
||||
});
|
||||
76
packages/cli/lib/parse-run-one-options.ts
Normal file
76
packages/cli/lib/parse-run-one-options.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import yargsParser = require('yargs-parser');
|
||||
|
||||
export function parseRunOneOptions(
|
||||
nxJson: any,
|
||||
workspaceConfigJson: any,
|
||||
args: string[]
|
||||
): false | { project; target; configuration; overrides } {
|
||||
// custom runner is not set, no tasks runner
|
||||
if (
|
||||
!nxJson.tasksRunnerOptions ||
|
||||
!nxJson.tasksRunnerOptions.default ||
|
||||
!nxJson.tasksRunnerOptions.default.runner
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// the list of all possible tasks doesn't include the given name, no tasks runner
|
||||
let allPossibleTasks = ['run'];
|
||||
Object.values(workspaceConfigJson.projects || {}).forEach((p: any) => {
|
||||
allPossibleTasks.push(...Object.keys(p.architect || {}));
|
||||
});
|
||||
if (allPossibleTasks.indexOf(args[0]) === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let defaultProjectName = null;
|
||||
try {
|
||||
defaultProjectName = workspaceConfigJson.cli.defaultProjectName;
|
||||
} catch (e) {}
|
||||
|
||||
const overrides = yargsParser(args, {
|
||||
boolean: ['prod'],
|
||||
string: ['configuration', 'project']
|
||||
});
|
||||
|
||||
let project;
|
||||
let target;
|
||||
let configuration;
|
||||
|
||||
if (overrides._[0] === 'run') {
|
||||
[project, target, configuration] = overrides._[1].split(':');
|
||||
} else {
|
||||
target = overrides._[0];
|
||||
project = overrides._[1];
|
||||
}
|
||||
|
||||
if (!project && defaultProjectName) {
|
||||
project = defaultProjectName;
|
||||
}
|
||||
|
||||
if (overrides.configuration) {
|
||||
configuration = overrides.configuration;
|
||||
}
|
||||
if (overrides.prod) {
|
||||
configuration = 'production';
|
||||
}
|
||||
if (overrides.project) {
|
||||
project = overrides.project;
|
||||
}
|
||||
|
||||
// we need both to be able to run a target, no tasks runner
|
||||
if (!project || !target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we need both to be able to run a target, no tasks runner
|
||||
if (!workspaceConfigJson.projects[project]) return false;
|
||||
|
||||
const res = { project, target, configuration, overrides };
|
||||
delete overrides['_'];
|
||||
delete overrides['configuration'];
|
||||
delete overrides['prod'];
|
||||
delete overrides['project'];
|
||||
|
||||
return res;
|
||||
}
|
||||
70
packages/cli/lib/run-cli.ts
Normal file
70
packages/cli/lib/run-cli.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { findWorkspaceRoot } from './find-workspace-root';
|
||||
|
||||
const workspace = findWorkspaceRoot(process.cwd());
|
||||
|
||||
setUpOutputWatching();
|
||||
requireCli();
|
||||
|
||||
function requireCli() {
|
||||
if (workspace.type === 'nx') {
|
||||
require(path.join(
|
||||
workspace.dir,
|
||||
'node_modules',
|
||||
'@nrwl',
|
||||
'tao',
|
||||
'index.js'
|
||||
));
|
||||
} else {
|
||||
require(path.join(
|
||||
workspace.dir,
|
||||
'node_modules',
|
||||
'@angular',
|
||||
'cli',
|
||||
'lib',
|
||||
'init.js'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to collect all stdout and stderr and store it, so the caching mechanism
|
||||
* could store it.
|
||||
*
|
||||
* Writing stdout and stderr into different stream is too risky when using TTY.
|
||||
*
|
||||
* So we are simply monkey-patching the Javascript object. In this case the actual output will always be correct.
|
||||
* And the cached output should be correct unless the CLI bypasses process.stdout or console.log and uses some
|
||||
* C-binary to write to stdout.
|
||||
*/
|
||||
function setUpOutputWatching() {
|
||||
const stdoutWrite = process.stdout._write;
|
||||
const stderrWrite = process.stderr._write;
|
||||
|
||||
let out = [];
|
||||
|
||||
process.stdout._write = (
|
||||
chunk: any,
|
||||
encoding: string,
|
||||
callback: Function
|
||||
) => {
|
||||
out.push(chunk.toString());
|
||||
stdoutWrite.apply(process.stdout, [chunk, encoding, callback]);
|
||||
};
|
||||
|
||||
process.stderr._write = (
|
||||
chunk: any,
|
||||
encoding: string,
|
||||
callback: Function
|
||||
) => {
|
||||
out.push(chunk.toString());
|
||||
stderrWrite.apply(process.stderr, [chunk, encoding, callback]);
|
||||
};
|
||||
|
||||
process.on('exit', code => {
|
||||
if (code === 0) {
|
||||
fs.writeFileSync(process.env.NX_TERMINAL_OUTPUT_PATH, out.join(''));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -51,7 +51,7 @@ class InsightsRemoteCache implements RemoteCache {
|
||||
if (e.response && e.response.status === 404) {
|
||||
// cache miss. print nothing
|
||||
} else if (e.code === 'ECONNREFUSED') {
|
||||
console.error(`Error: Cannot cannot to remote cache.`);
|
||||
console.error(`Error: Cannot connect to remote cache.`);
|
||||
} else {
|
||||
console.error(e.message);
|
||||
}
|
||||
@ -79,7 +79,7 @@ class InsightsRemoteCache implements RemoteCache {
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e.code === 'ECONNREFUSED') {
|
||||
console.error(`Error: Cannot cannot to remote cache.`);
|
||||
console.error(`Error: Cannot connect to remote cache.`);
|
||||
} else {
|
||||
console.error(e.message);
|
||||
}
|
||||
|
||||
@ -57,9 +57,6 @@ function parseRunOpts(
|
||||
);
|
||||
project = defaultProjectName;
|
||||
}
|
||||
if (!project || !target) {
|
||||
throwInvalidInvocation();
|
||||
}
|
||||
if (runOptions.configuration) {
|
||||
configuration = runOptions.configuration;
|
||||
}
|
||||
@ -69,6 +66,9 @@ function parseRunOpts(
|
||||
if (runOptions.project) {
|
||||
project = runOptions.project;
|
||||
}
|
||||
if (!project || !target) {
|
||||
throwInvalidInvocation();
|
||||
}
|
||||
const res = { project, target, configuration, help, runOptions };
|
||||
delete runOptions['help'];
|
||||
delete runOptions['_'];
|
||||
|
||||
@ -20,10 +20,8 @@ export {
|
||||
resolveUserExistingPrettierConfig
|
||||
} from './src/utils/common';
|
||||
export { output } from './src/utils/output';
|
||||
export {
|
||||
commandsObject,
|
||||
supportedNxCommands
|
||||
} from './src/command-line/nx-commands';
|
||||
export { commandsObject } from './src/command-line/nx-commands';
|
||||
export { supportedNxCommands } from './src/command-line/supported-nx-commands';
|
||||
export { readWorkspaceJson, readNxJson } from './src/core/file-utils';
|
||||
export { NxJson } from './src/core/shared-interfaces';
|
||||
export {
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
import { calculateFileChanges, readEnvironment } from '../core/file-utils';
|
||||
import { printAffected } from './print-affected';
|
||||
import { projectHasTargetAndConfiguration } from '../utils/project-has-target-and-configuration';
|
||||
import { DefaultReporter } from '../tasks-runner/default-reporter';
|
||||
|
||||
export function affected(command: string, parsedArgs: yargs.Arguments): void {
|
||||
const { nxArgs, overrides } = splitArgsIntoNxArgsAndOverrides(parsedArgs);
|
||||
@ -109,7 +110,8 @@ export function affected(command: string, parsedArgs: yargs.Arguments): void {
|
||||
projectGraph,
|
||||
env,
|
||||
nxArgs,
|
||||
overrides
|
||||
overrides,
|
||||
new DefaultReporter()
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -14,31 +14,6 @@ import { runMany } from './run-many';
|
||||
|
||||
const noop = (yargs: yargs.Argv): yargs.Argv => yargs;
|
||||
|
||||
export const supportedNxCommands = [
|
||||
'affected',
|
||||
'affected:apps',
|
||||
'affected:libs',
|
||||
'affected:build',
|
||||
'affected:test',
|
||||
'affected:e2e',
|
||||
'affected:dep-graph',
|
||||
'affected:lint',
|
||||
'print-affected',
|
||||
'dep-graph',
|
||||
'format',
|
||||
'format:check',
|
||||
'format:write',
|
||||
'workspace-schematic',
|
||||
'workspace-lint',
|
||||
'migrate',
|
||||
'report',
|
||||
'run-many',
|
||||
'list',
|
||||
'help',
|
||||
'--help',
|
||||
'--version'
|
||||
];
|
||||
|
||||
/**
|
||||
* Exposing the Yargs commands object so the documentation generator can
|
||||
* parse it. The CLI will consume it and call the `.argv` to bootstrapped
|
||||
|
||||
@ -10,13 +10,21 @@ import {
|
||||
} from '../core/project-graph';
|
||||
import { readEnvironment } from '../core/file-utils';
|
||||
import { projectHasTargetAndConfiguration } from '../utils/project-has-target-and-configuration';
|
||||
import { DefaultReporter } from '../tasks-runner/default-reporter';
|
||||
|
||||
export function runMany(parsedArgs: yargs.Arguments): void {
|
||||
const { nxArgs, overrides } = splitArgsIntoNxArgsAndOverrides(parsedArgs);
|
||||
const env = readEnvironment(nxArgs.target);
|
||||
const projectGraph = createProjectGraph();
|
||||
const projects = projectsToRun(nxArgs, projectGraph);
|
||||
runCommand(projects, projectGraph, env, nxArgs, overrides);
|
||||
runCommand(
|
||||
projects,
|
||||
projectGraph,
|
||||
env,
|
||||
nxArgs,
|
||||
overrides,
|
||||
new DefaultReporter()
|
||||
);
|
||||
}
|
||||
|
||||
function projectsToRun(nxArgs: NxArgs, projectGraph: ProjectGraph) {
|
||||
|
||||
23
packages/workspace/src/command-line/run-one.ts
Normal file
23
packages/workspace/src/command-line/run-one.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { runCommand } from '../tasks-runner/run-command';
|
||||
import { createProjectGraph } from '../core/project-graph';
|
||||
import { readEnvironment } from '../core/file-utils';
|
||||
import { EmptyReporter } from '../tasks-runner/empty-reporter';
|
||||
|
||||
export function runOne(opts: {
|
||||
project: string;
|
||||
target: string;
|
||||
configuration: string;
|
||||
overrides: any;
|
||||
}): void {
|
||||
const env = readEnvironment(opts.target);
|
||||
const projectGraph = createProjectGraph();
|
||||
const projects = [projectGraph.nodes[opts.project]];
|
||||
runCommand(
|
||||
projects,
|
||||
projectGraph,
|
||||
env,
|
||||
opts,
|
||||
opts.overrides,
|
||||
new EmptyReporter()
|
||||
);
|
||||
}
|
||||
24
packages/workspace/src/command-line/supported-nx-commands.ts
Normal file
24
packages/workspace/src/command-line/supported-nx-commands.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export const supportedNxCommands = [
|
||||
'affected',
|
||||
'affected:apps',
|
||||
'affected:libs',
|
||||
'affected:build',
|
||||
'affected:test',
|
||||
'affected:e2e',
|
||||
'affected:dep-graph',
|
||||
'affected:lint',
|
||||
'print-affected',
|
||||
'dep-graph',
|
||||
'format',
|
||||
'format:check',
|
||||
'format:write',
|
||||
'workspace-schematic',
|
||||
'workspace-lint',
|
||||
'migrate',
|
||||
'report',
|
||||
'run-many',
|
||||
'list',
|
||||
'help',
|
||||
'--help',
|
||||
'--version'
|
||||
];
|
||||
@ -2,13 +2,7 @@ import { appRootPath } from '../utils/app-root';
|
||||
import { ProjectGraph } from '../core/project-graph';
|
||||
import { NxJson } from '../core/shared-interfaces';
|
||||
import { Task } from './tasks-runner';
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
rmdirSync,
|
||||
writeFileSync
|
||||
} from 'fs';
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { Hasher } from './hasher';
|
||||
import * as fsExtra from 'fs-extra';
|
||||
@ -17,10 +11,28 @@ import { DefaultTasksRunnerOptions } from './tasks-runner-v2';
|
||||
export type CachedResult = { terminalOutput: string; outputsPath: string };
|
||||
export type TaskWithCachedResult = { task: Task; cachedResult: CachedResult };
|
||||
|
||||
class CacheConfig {
|
||||
constructor(private readonly options: DefaultTasksRunnerOptions) {}
|
||||
|
||||
isCacheableTask(task: Task) {
|
||||
return (
|
||||
this.options.cacheableOperations &&
|
||||
this.options.cacheableOperations.indexOf(task.target.target) > -1 &&
|
||||
!this.longRunningTask(task)
|
||||
);
|
||||
}
|
||||
|
||||
private longRunningTask(task: Task) {
|
||||
return task.overrides['watch'] !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class Cache {
|
||||
root = appRootPath;
|
||||
cachePath = this.createCacheDir();
|
||||
terminalOutputsDir = this.createTerminalOutputsDir();
|
||||
hasher = new Hasher(this.projectGraph, this.nxJson);
|
||||
cacheConfig = new CacheConfig(this.options);
|
||||
|
||||
constructor(
|
||||
private readonly projectGraph: ProjectGraph,
|
||||
@ -29,7 +41,7 @@ export class Cache {
|
||||
) {}
|
||||
|
||||
async get(task: Task): Promise<CachedResult> {
|
||||
if (!this.cacheable(task)) return null;
|
||||
if (!this.cacheConfig.isCacheableTask(task)) return null;
|
||||
|
||||
const res = await this.getFromLocalDir(task);
|
||||
|
||||
@ -47,8 +59,9 @@ export class Cache {
|
||||
}
|
||||
}
|
||||
|
||||
async put(task: Task, terminalOutput: string, folders: string[]) {
|
||||
if (!this.cacheable(task)) return;
|
||||
async put(task: Task, terminalOutputPath: string, folders: string[]) {
|
||||
if (!this.cacheConfig.isCacheableTask(task)) return;
|
||||
const terminalOutput = readFileSync(terminalOutputPath).toString();
|
||||
const hash = await this.hasher.hash(task);
|
||||
const td = join(this.cachePath, hash);
|
||||
const tdCommit = join(this.cachePath, `${hash}.commit`);
|
||||
@ -101,6 +114,10 @@ export class Cache {
|
||||
});
|
||||
}
|
||||
|
||||
async temporaryOutputPath(task: Task) {
|
||||
return join(this.terminalOutputsDir, await this.hasher.hash(task));
|
||||
}
|
||||
|
||||
private async getFromLocalDir(task: Task) {
|
||||
const hash = await this.hasher.hash(task);
|
||||
const tdCommit = join(this.cachePath, `${hash}.commit`);
|
||||
@ -116,13 +133,6 @@ export class Cache {
|
||||
}
|
||||
}
|
||||
|
||||
private cacheable(task: Task) {
|
||||
return (
|
||||
this.options.cacheableOperations &&
|
||||
this.options.cacheableOperations.indexOf(task.target.target) > -1
|
||||
);
|
||||
}
|
||||
|
||||
private createCacheDir() {
|
||||
let dir;
|
||||
if (this.options.cacheDirectory) {
|
||||
@ -139,4 +149,10 @@ export class Cache {
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
private createTerminalOutputsDir() {
|
||||
const path = join(this.cachePath, 'terminalOutputs');
|
||||
mkdirSync(path, { recursive: true });
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ import { readJsonFile } from '../utils/fileutils';
|
||||
import { getCommand, getCommandAsString } from './utils';
|
||||
import { cliCommand } from '../core/file-utils';
|
||||
import { ProjectGraph } from '../core/project-graph';
|
||||
import { NxJson } from '@nrwl/workspace/src/core/shared-interfaces';
|
||||
import { NxJson } from '../core/shared-interfaces';
|
||||
|
||||
export interface DefaultTasksRunnerOptions {
|
||||
parallel?: boolean;
|
||||
|
||||
5
packages/workspace/src/tasks-runner/empty-reporter.ts
Normal file
5
packages/workspace/src/tasks-runner/empty-reporter.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class EmptyReporter {
|
||||
beforeRun() {}
|
||||
|
||||
printResults() {}
|
||||
}
|
||||
@ -1,13 +1,12 @@
|
||||
import { AffectedEventType, Task, TasksRunner } from './tasks-runner';
|
||||
import { defaultTasksRunner } from './default-tasks-runner';
|
||||
import { isRelativePath } from '../utils/fileutils';
|
||||
import { join } from 'path';
|
||||
import { appRootPath } from '../utils/app-root';
|
||||
import { DefaultReporter, ReporterArgs } from './default-reporter';
|
||||
import { ReporterArgs } from './default-reporter';
|
||||
import * as yargs from 'yargs';
|
||||
import { ProjectGraph, ProjectGraphNode } from '../core/project-graph';
|
||||
import { Environment, NxJson } from '../core/shared-interfaces';
|
||||
import { NxArgs } from '@nrwl/workspace/src/command-line/utils';
|
||||
import { isRelativePath } from '../utils/fileutils';
|
||||
|
||||
type RunArgs = yargs.Arguments & ReporterArgs;
|
||||
|
||||
@ -16,9 +15,9 @@ export function runCommand<T extends RunArgs>(
|
||||
projectGraph: ProjectGraph,
|
||||
{ nxJson, workspace }: Environment,
|
||||
nxArgs: NxArgs,
|
||||
overrides: any
|
||||
overrides: any,
|
||||
reporter: any
|
||||
) {
|
||||
const reporter = new DefaultReporter();
|
||||
reporter.beforeRun(projectsToRun.map(p => p.name), nxArgs, overrides);
|
||||
const tasks: Task[] = projectsToRun.map(project =>
|
||||
createTask({
|
||||
@ -122,15 +121,17 @@ export function getRunner(
|
||||
tasksOptions: unknown;
|
||||
} {
|
||||
if (!nxJson.tasksRunnerOptions) {
|
||||
const t = require('./default-tasks-runner');
|
||||
return {
|
||||
tasksRunner: defaultTasksRunner,
|
||||
tasksRunner: t.defaultTasksRunner,
|
||||
tasksOptions: overrides
|
||||
};
|
||||
}
|
||||
|
||||
if (!runner && !nxJson.tasksRunnerOptions.default) {
|
||||
const t = require('./default-tasks-runner');
|
||||
return {
|
||||
tasksRunner: defaultTasksRunner,
|
||||
tasksRunner: t.defaultTasksRunner,
|
||||
tasksOptions: overrides
|
||||
};
|
||||
}
|
||||
|
||||
@ -4,15 +4,16 @@ import { NxJson } from '../core/shared-interfaces';
|
||||
import { ProjectGraph } from '../core/project-graph';
|
||||
import { AffectedEventType, Task } from './tasks-runner';
|
||||
import { getCommand, getOutputs } from './utils';
|
||||
import { basename } from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import { fork, spawn } from 'child_process';
|
||||
import { DefaultTasksRunnerOptions } from './tasks-runner-v2';
|
||||
import { output } from '../utils/output';
|
||||
import * as path from 'path';
|
||||
import { appRootPath } from '../utils/app-root';
|
||||
|
||||
export class TaskOrchestrator {
|
||||
workspaceRoot = appRootPath;
|
||||
cache = new Cache(this.projectGraph, this.nxJson, this.options);
|
||||
cli = cliCommand();
|
||||
isYarn = basename(process.env.npm_execpath || 'npm').startsWith('yarn');
|
||||
|
||||
constructor(
|
||||
private readonly nxJson: NxJson,
|
||||
@ -40,7 +41,7 @@ export class TaskOrchestrator {
|
||||
if (left.length > 0) {
|
||||
const task = left.pop();
|
||||
return that
|
||||
.spawnProcess(task)
|
||||
.forkProcess(task)
|
||||
.then(code => {
|
||||
res.push({
|
||||
task,
|
||||
@ -103,39 +104,56 @@ export class TaskOrchestrator {
|
||||
}, []);
|
||||
}
|
||||
|
||||
private spawnProcess(task: Task) {
|
||||
private forkProcess(task: Task) {
|
||||
const taskOutputs = getOutputs(this.projectGraph.nodes, task);
|
||||
return new Promise(res => {
|
||||
const command = this.isYarn ? 'yarn' : 'npm';
|
||||
const commandArgs = this.isYarn
|
||||
? getCommand(this.cli, this.isYarn, task)
|
||||
: ['run', ...getCommand(this.cli, this.isYarn, task)];
|
||||
const p = spawn(command, commandArgs, {
|
||||
stdio: [process.stdin, 'pipe', 'pipe'],
|
||||
env: { ...process.env, FORCE_COLOR: 'true' }
|
||||
return this.cache.temporaryOutputPath(task).then(outputPath => {
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
const p = fork(this.getCommand(), this.getCommandArgs(task), {
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
||||
env: { ...process.env, NX_TERMINAL_OUTPUT_PATH: outputPath }
|
||||
});
|
||||
|
||||
let out = [];
|
||||
|
||||
p.stdout.on('data', data => {
|
||||
out.push(data);
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
p.stderr.on('data', data => {
|
||||
out.push(data);
|
||||
process.stderr.write(data);
|
||||
});
|
||||
|
||||
p.on('close', code => {
|
||||
if (code === 0) {
|
||||
this.cache.put(task, out.join(''), taskOutputs).then(() => {
|
||||
this.cache.put(task, outputPath, taskOutputs).then(() => {
|
||||
res(code);
|
||||
});
|
||||
} else {
|
||||
res(code);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getCommand() {
|
||||
return path.join(
|
||||
this.workspaceRoot,
|
||||
'node_modules',
|
||||
'@nrwl',
|
||||
'cli',
|
||||
'lib',
|
||||
'run-cli.js'
|
||||
);
|
||||
}
|
||||
|
||||
private getCommandArgs(task: Task) {
|
||||
const args = Object.entries(task.overrides || {}).map(
|
||||
([prop, value]) => `--${prop}=${value}`
|
||||
);
|
||||
|
||||
const config = task.target.configuration
|
||||
? `:${task.target.configuration}`
|
||||
: '';
|
||||
|
||||
return [
|
||||
'run',
|
||||
`${task.target.project}:${task.target.target}${config}`,
|
||||
...args
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,9 +5,6 @@ import {
|
||||
TaskCompleteEvent,
|
||||
TasksRunner
|
||||
} from './tasks-runner';
|
||||
import { output } from '../utils/output';
|
||||
import { readJsonFile } from '../utils/fileutils';
|
||||
import { cliCommand } from '../core/file-utils';
|
||||
import { ProjectGraph } from '../core/project-graph';
|
||||
import { NxJson } from '../core/shared-interfaces';
|
||||
import { TaskOrderer } from './task-orderer';
|
||||
@ -52,7 +49,6 @@ async function runAllTasks(
|
||||
options: DefaultTasksRunnerOptions,
|
||||
context: { target: string; projectGraph: ProjectGraph; nxJson: NxJson }
|
||||
): Promise<Array<{ task: Task; type: any; success: boolean }>> {
|
||||
assertPackageJsonScriptExists();
|
||||
const stages = new TaskOrderer(
|
||||
context.target,
|
||||
context.projectGraph
|
||||
@ -91,25 +87,4 @@ function tasksToStatuses(tasks: Task[], success: boolean) {
|
||||
}));
|
||||
}
|
||||
|
||||
function assertPackageJsonScriptExists() {
|
||||
const cli = cliCommand();
|
||||
// Make sure the `package.json` has the `nx: "nx"`
|
||||
const packageJson = readJsonFile('./package.json');
|
||||
if (!packageJson.scripts || !packageJson.scripts[cli]) {
|
||||
output.error({
|
||||
title: `The "scripts" section of your 'package.json' must contain "${cli}": "${cli}"`,
|
||||
bodyLines: [
|
||||
output.colors.gray('...'),
|
||||
' "scripts": {',
|
||||
output.colors.gray(' ...'),
|
||||
` "${cli}": "${cli}"`,
|
||||
output.colors.gray(' ...'),
|
||||
' }',
|
||||
output.colors.gray('...')
|
||||
]
|
||||
});
|
||||
return process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export default tasksRunnerV2;
|
||||
|
||||
@ -19,4 +19,4 @@ jest --maxWorkers=1 ./build/e2e/run-many.test.js &&
|
||||
jest --maxWorkers=1 ./build/e2e/storybook.test.js &&
|
||||
jest --maxWorkers=1 ./build/e2e/upgrade-module.test.js &&
|
||||
jest --maxWorkers=1 ./build/e2e/web.test.js &&
|
||||
jest --maxWorkers=1 ./build/e2e/default-tasks-runner.test.js
|
||||
jest --maxWorkers=1 ./build/e2e/tasks-runner-v2.test.js
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user