feat(core): add second way of capturing output

This commit is contained in:
Victor Savkin 2020-06-15 14:08:19 -04:00 committed by Victor Savkin
parent a34af96093
commit 96a7f69d33
7 changed files with 135 additions and 25 deletions

View File

@ -26,9 +26,13 @@ forEachCli(() => {
runCLIAsync(`generate @nrwl/angular:component test --project ${mylib}`),
]);
const appResult = await runCLIAsync(`test ${myapp} --no-watch`);
expect(appResult.stderr).toContain('Test Suites: 3 passed, 3 total');
expect(appResult.combinedOutput).toContain(
'Test Suites: 3 passed, 3 total'
);
const libResult = await runCLIAsync(`test ${mylib}`);
expect(libResult.stderr).toContain('Test Suites: 3 passed, 3 total');
expect(libResult.combinedOutput).toContain(
'Test Suites: 3 passed, 3 total'
);
done();
}, 45000);
@ -66,7 +70,9 @@ forEachCli(() => {
);
const appResult = await runCLIAsync(`test ${mylib} --no-watch`);
expect(appResult.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(appResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
done();
}, 45000);
@ -84,7 +90,9 @@ forEachCli(() => {
`
);
const appResult = await runCLIAsync(`test ${mylib} --no-watch`);
expect(appResult.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(appResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
done();
}, 45000);
});

View File

@ -232,7 +232,9 @@ async function checkApp(
}
const testResults = await runCLIAsync(`test ${appName}`);
expect(testResults.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(testResults.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
if (supportUi()) {
const e2eResults = runCLI(`e2e ${appName}-e2e --headless`);

View File

@ -82,7 +82,9 @@ forEachCli((currentCLIName) => {
updateFile(`apps/${nodeapp}/src/assets/file.txt`, ``);
const jestResult = await runCLIAsync(`test ${nodeapp}`);
expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(
@ -192,7 +194,9 @@ forEachCli((currentCLIName) => {
updateFile(`apps/${nestapp}/src/assets/file.txt`, ``);
const jestResult = await runCLIAsync(`test ${nestapp}`);
expect(jestResult.stderr).toContain('Test Suites: 2 passed, 2 total');
expect(jestResult.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
await runCLIAsync(`build ${nestapp}`);
@ -256,7 +260,9 @@ forEachCli((currentCLIName) => {
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nodelib}`);
expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
}, 60000);
it('should be able to generate a publishable node library', async () => {
@ -358,7 +364,9 @@ forEachCli((currentCLIName) => {
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
}, 60000);
it('should be able to generate a nest library w/ controller', async () => {
@ -371,7 +379,9 @@ forEachCli((currentCLIName) => {
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
}, 60000);
it('should be able to generate a nest library w/ controller and service', async () => {
@ -384,7 +394,9 @@ forEachCli((currentCLIName) => {
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.stderr).toContain('Test Suites: 2 passed, 2 total');
expect(jestResult.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
}, 60000);
});

View File

@ -36,7 +36,9 @@ forEachCli((currentCLIName) => {
updateFile(mainPath, `import '@proj/${libName}';\n` + readFile(mainPath));
const libTestResults = await runCLIAsync(`test ${libName}`);
expect(libTestResults.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(libTestResults.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
await testGeneratedApp(appName, {
checkStyles: true,
@ -210,10 +212,14 @@ forEachCli((currentCLIName) => {
runCLI(`g @nrwl/react:redux orange --project=${libName}`);
const appTestResults = await runCLIAsync(`test ${appName}`);
expect(appTestResults.stderr).toContain('Test Suites: 2 passed, 2 total');
expect(appTestResults.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
const libTestResults = await runCLIAsync(`test ${libName}`);
expect(libTestResults.stderr).toContain('Test Suites: 2 passed, 2 total');
expect(libTestResults.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
}, 120000);
it('should be able to use JSX', async () => {
@ -306,7 +312,9 @@ forEachCli((currentCLIName) => {
}
const testResults = await runCLIAsync(`test ${appName}`);
expect(testResults.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(testResults.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
if (opts.checkE2E) {
const e2eResults = runCLI(`e2e ${appName}-e2e`);

View File

@ -172,19 +172,19 @@ export function runCommandAsync(
silenceError: false,
env: process.env,
}
): Promise<{ stdout: string; stderr: string }> {
): Promise<{ stdout: string; stderr: string; combinedOutput: string }> {
return new Promise((resolve, reject) => {
exec(
command,
{
cwd: tmpProjPath(),
env: process.env,
env: { ...process.env, FORCE_COLOR: 'false' },
},
(err, stdout, stderr) => {
if (!opts.silenceError && err) {
reject(err);
}
resolve({ stdout, stderr });
resolve({ stdout, stderr, combinedOutput: `${stdout}${stderr}` });
}
);
});
@ -196,7 +196,7 @@ export function runCLIAsync(
silenceError: false,
env: process.env,
}
): Promise<{ stdout: string; stderr: string }> {
): Promise<{ stdout: string; stderr: string; combinedOutput: string }> {
return runCommandAsync(`./node_modules/.bin/nx ${command}`, opts);
}

View File

@ -48,7 +48,9 @@ forEachCli((currentCLIName) => {
`<link rel="stylesheet" href="styles.css">`
);
const testResults = await runCLIAsync(`test ${appName}`);
expect(testResults.stderr).toContain('Test Suites: 1 passed, 1 total');
expect(testResults.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
const lintE2eResults = runCLI(`lint ${appName}-e2e`);
expect(lintE2eResults).toContain('All files pass linting.');

View File

@ -42,8 +42,10 @@ export class TaskOrchestrator {
function takeFromQueue() {
if (left.length > 0) {
const task = left.pop();
return that
.forkProcess(task)
const p = that.pipeOutputCapture(task)
? that.forkProcessPipeOutputCapture(task)
: that.forkProcessDirectOutputCapture(task);
return p
.then((code) => {
res.push({
task,
@ -122,7 +124,82 @@ export class TaskOrchestrator {
}, []);
}
private forkProcess(task: Task) {
private pipeOutputCapture(task: Task) {
try {
const p = this.projectGraph.nodes[task.target.project];
const b = p.data.architect[task.target.target].builder;
// this is temporary. we simply want to assess if pipeOutputCapture
// works well before making it configurable
return (
this.cache.temporaryOutputPath(task) &&
(b === '@nrwl/workspace:run-commands' || b === '@nrwl/cypress:cypress')
);
} catch (e) {
return false;
}
}
private forkProcessPipeOutputCapture(task: Task) {
const taskOutputs = getOutputs(this.projectGraph.nodes, task);
const outputPath = this.cache.temporaryOutputPath(task);
return new Promise((res, rej) => {
try {
this.options.lifeCycle.startTask(task);
const forwardOutput = this.shouldForwardOutput(outputPath, task);
const env = this.envForForkedProcess(task, undefined, forwardOutput);
const args = this.getCommandArgs(task);
const commandLine = `${this.cli} ${args.join(' ')}`;
if (forwardOutput) {
output.logCommand(commandLine);
}
const p = fork(this.getCommand(), args, {
stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
env,
});
let out = [];
let outWithErr = [];
p.stdout.on('data', (chunk) => {
process.stdout.write(chunk);
out.push(chunk.toString());
outWithErr.push(chunk.toString());
});
p.stderr.on('data', (chunk) => {
process.stderr.write(chunk);
outWithErr.push(chunk.toString());
});
p.on('exit', (code) => {
// we didn't print any output as we were running the command
// print all the collected output|
if (!forwardOutput) {
output.logCommand(commandLine);
process.stdout.write(outWithErr.join(''));
}
if (outputPath && code === 0) {
fs.writeFileSync(outputPath, outWithErr.join(''));
this.cache
.put(task, outputPath, taskOutputs)
.then(() => {
this.options.lifeCycle.endTask(task, code);
res(code);
})
.catch((e) => {
rej(e);
});
} else {
this.options.lifeCycle.endTask(task, code);
res(code);
}
});
} catch (e) {
console.error(e);
rej(e);
}
});
}
private forkProcessDirectOutputCapture(task: Task) {
const taskOutputs = getOutputs(this.projectGraph.nodes, task);
const outputPath = this.cache.temporaryOutputPath(task);
return new Promise((res, rej) => {
@ -187,8 +264,9 @@ export class TaskOrchestrator {
...parseEnv(`${task.projectRoot}/.env`),
...parseEnv(`${task.projectRoot}/.local.env`),
};
const env = { ...envsFromFiles, ...process.env };
const forceColor =
process.env.FORCE_COLOR === undefined ? 'true' : process.env.FORCE_COLOR;
const env = { ...envsFromFiles, FORCE_COLOR: forceColor, ...process.env };
if (outputPath) {
env.NX_TERMINAL_OUTPUT_PATH = outputPath;
if (this.options.captureStderr) {