Skip to content

Commit

Permalink
feat(core): run commands directly
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongemi committed Feb 26, 2024
1 parent 4c5654d commit c2ec654
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 60 deletions.
18 changes: 18 additions & 0 deletions docs/generated/devkit/Task.md
Expand Up @@ -8,9 +8,11 @@ A representation of the invocation of an Executor

- [cache](../../devkit/documents/Task#cache): boolean
- [endTime](../../devkit/documents/Task#endtime): number
- [executor](../../devkit/documents/Task#executor): string
- [hash](../../devkit/documents/Task#hash): string
- [hashDetails](../../devkit/documents/Task#hashdetails): Object
- [id](../../devkit/documents/Task#id): string
- [options](../../devkit/documents/Task#options): Record<string, any>
- [outputs](../../devkit/documents/Task#outputs): string[]
- [overrides](../../devkit/documents/Task#overrides): any
- [projectRoot](../../devkit/documents/Task#projectroot): string
Expand All @@ -35,6 +37,14 @@ Unix timestamp of when a Batch Task ends

---

### executor

`Optional` **executor**: `string`

the executor to use to run the task

---

### hash

`Optional` **hash**: `string`
Expand Down Expand Up @@ -68,6 +78,14 @@ Unique ID

---

### options

`Optional` **options**: `Record`\<`string`, `any`\>

The options of the executor

---

### outputs

**outputs**: `string`[]
Expand Down
6 changes: 6 additions & 0 deletions docs/generated/packages/nx/executors/run-commands.json
Expand Up @@ -121,6 +121,12 @@
"items": { "type": "string" },
"$default": { "$source": "unparsed" },
"x-priority": "internal"
},
"mode": {
"type": "string",
"description": "Mode to trigger the command.",
"enum": ["run-one", "run-many"],
"x-priority": "internal"
}
},
"additionalProperties": true,
Expand Down
8 changes: 8 additions & 0 deletions packages/nx/src/config/task-graph.ts
Expand Up @@ -27,6 +27,14 @@ export interface Task {
* Overrides for the configured options of the target
*/
overrides: any;
/**
* the executor to use to run the task
*/
executor?: string;
/**
* The options of the executor
*/
options?: Record<string, any>;

/**
* The outputs the task may produce
Expand Down
118 changes: 76 additions & 42 deletions packages/nx/src/executors/run-commands/run-commands.impl.ts
Expand Up @@ -6,6 +6,7 @@ import { ExecutorContext } from '../../config/misc-interfaces';
import * as chalk from 'chalk';
import { runCommand } from '../../native';
import { PseudoTtyProcess } from '../../utils/child-process';
import { getPackageManagerCommand } from '../../utils/package-manager';

export const LARGE_BUFFER = 1024 * 1000000;

Expand Down Expand Up @@ -51,6 +52,7 @@ export interface RunCommandsOptions extends Json {
args?: string | string[];
envFile?: string;
__unparsed__: string[];
mode?: 'run-one' | 'run-many';
}

const propKeys = [
Expand Down Expand Up @@ -80,6 +82,7 @@ export default async function (
context: ExecutorContext
): Promise<{
success: boolean;
terminalOutput: string;
}> {
await loadEnvVars(options.envFile);
const normalized = normalizeOptions(options);
Expand All @@ -100,10 +103,10 @@ export default async function (
}

try {
const success = options.parallel
const result = options.parallel
? await runInParallel(normalized, context)
: await runSerially(normalized, context);
return { success };
return result;
} catch (e) {
if (process.env.NX_VERBOSE_LOGGING === 'true') {
console.error(e);
Expand All @@ -117,43 +120,56 @@ export default async function (
async function runInParallel(
options: NormalizedRunCommandsOptions,
context: ExecutorContext
) {
): Promise<{ success: boolean; terminalOutput: string }> {
const procs = options.commands.map((c) =>
createProcess(
c,
options.readyWhen,
options.color,
calculateCwd(options.cwd, context),
options.env ?? {},
true
).then((result) => ({
true,
options.mode
).then((result: { success: boolean; terminalOutput: string }) => ({
result,
command: c.command,
}))
);

if (options.readyWhen) {
const r = await Promise.race(procs);
if (!r.result) {
const r: {
result: { success: boolean; terminalOutput: string };
command: string;
} = await Promise.race(procs);
if (!r.result.success) {
process.stderr.write(
`Warning: command "${r.command}" exited with non-zero status code`
);
return false;
return { success: false, terminalOutput: r.result.terminalOutput };
} else {
return true;
return { success: true, terminalOutput: r.result.terminalOutput };
}
} else {
const r = await Promise.all(procs);
const failed = r.filter((v) => !v.result);
const r: {
result: { success: boolean; terminalOutput: string };
command: string;
}[] = await Promise.all(procs);
const failed = r.filter((v) => !v.result.success);
if (failed.length > 0) {
failed.forEach((f) => {
process.stderr.write(
`Warning: command "${f.command}" exited with non-zero status code`
);
});
return false;
return {
success: false,
terminalOutput: r.map((f) => f.result.terminalOutput).join(''),
};
} else {
return true;
return {
success: true,
terminalOutput: r.map((f) => f.result.terminalOutput).join(''),
};
}
}
}
Expand Down Expand Up @@ -181,32 +197,40 @@ function normalizeOptions(
options as NormalizedRunCommandsOptions,
c.forwardAllArgs ?? true
);
const pmc = getPackageManagerCommand();
if (!c.command.startsWith(`${pmc.exec} `)) {
c.command = `${pmc.exec} ${c.command}`;
}
});
return options as NormalizedRunCommandsOptions;
}

async function runSerially(
options: NormalizedRunCommandsOptions,
context: ExecutorContext
) {
): Promise<{ success: boolean; terminalOutput: string }> {
let terminalOutput = '';
for (const c of options.commands) {
const success = await createProcess(
c,
undefined,
options.color,
calculateCwd(options.cwd, context),
options.env ?? {},
false
);
if (!success) {
const result: { success: boolean; terminalOutput: string } =
await createProcess(
c,
undefined,
options.color,
calculateCwd(options.cwd, context),
options.env ?? {},
false,
options.mode
);
terminalOutput += result.terminalOutput;
if (!result.success) {
process.stderr.write(
`Warning: command "${c.command}" exited with non-zero status code`
);
return false;
return { success: false, terminalOutput: result.terminalOutput };
}
}

return true;
return { success: true, terminalOutput: terminalOutput };
}

async function createProcess(
Expand All @@ -220,41 +244,45 @@ async function createProcess(
color: boolean,
cwd: string,
env: Record<string, string>,
isParallel: boolean
): Promise<boolean> {
isParallel: boolean,
mode: 'run-one' | 'run-many' = 'run-one'
): Promise<{ success: boolean; terminalOutput: string }> {
env = processEnv(color, cwd, env);
// The rust runCommand is always a tty, so it will not look nice in parallel and if we need prefixes
// currently does not work properly in windows
if (
process.env.NX_NATIVE_COMMAND_RUNNER !== 'false' &&
process.stdout.isTTY &&
!commandConfig.prefix &&
!isParallel
!isParallel &&
mode !== 'run-many'
) {
const cp = new PseudoTtyProcess(
runCommand(commandConfig.command, cwd, env)
);

return new Promise((res) => {
let terminalOutput = '';
cp.onOutput((output) => {
terminalOutput += output;
if (readyWhen && output.indexOf(readyWhen) > -1) {
res(true);
res({ success: true, terminalOutput });
}
});

cp.onExit((code) => {
if (code === 0) {
res(true);
res({ success: true, terminalOutput });
} else if (code >= 128) {
process.exit(code);
} else {
res(false);
res({ success: false, terminalOutput });
}
});
});
}

return nodeProcess(commandConfig, color, cwd, env, readyWhen);
return nodeProcess(commandConfig, cwd, env, readyWhen);
}

function nodeProcess(
Expand All @@ -264,11 +292,10 @@ function nodeProcess(
bgColor?: string;
prefix?: string;
},
color: boolean,
cwd: string,
env: Record<string, string>,
readyWhen: string
): Promise<boolean> {
): Promise<{ success: boolean; terminalOutput: string }> {
return new Promise((res) => {
const childProcess = exec(commandConfig.command, {
maxBuffer: LARGE_BUFFER,
Expand All @@ -286,25 +313,32 @@ function nodeProcess(
process.on('SIGINT', processExitListener);
process.on('SIGQUIT', processExitListener);

let terminalOutput = '';
childProcess.stdout.on('data', (data) => {
process.stdout.write(addColorAndPrefix(data, commandConfig));
const output = addColorAndPrefix(data, commandConfig);
terminalOutput += output;
process.stdout.write(output);
if (readyWhen && data.toString().indexOf(readyWhen) > -1) {
res(true);
res({ success: true, terminalOutput });
}
});
childProcess.stderr.on('data', (err) => {
process.stderr.write(addColorAndPrefix(err, commandConfig));
const output = addColorAndPrefix(err, commandConfig);
terminalOutput += output;
process.stderr.write(output);
if (readyWhen && err.toString().indexOf(readyWhen) > -1) {
res(true);
res({ success: true, terminalOutput });
}
});
childProcess.on('error', (err) => {
process.stderr.write(addColorAndPrefix(err.toString(), commandConfig));
res(false);
const ouptput = addColorAndPrefix(err.toString(), commandConfig);
terminalOutput += ouptput;
process.stderr.write(ouptput);
res({ success: false, terminalOutput });
});
childProcess.on('exit', (code) => {
if (!readyWhen) {
res(code === 0);
res({ success: code === 0, terminalOutput });
}
});
});
Expand Down Expand Up @@ -364,7 +398,7 @@ export function interpolateArgsIntoCommand(
'args' | 'parsedArgs' | '__unparsed__'
>,
forwardAllArgs: boolean
) {
): string {
if (command.indexOf('{args.') > -1) {
const regex = /{args\.([^}]+)}/g;
return command.replace(regex, (_, group: string) =>
Expand Down
6 changes: 6 additions & 0 deletions packages/nx/src/executors/run-commands/schema.json
Expand Up @@ -135,6 +135,12 @@
"$source": "unparsed"
},
"x-priority": "internal"
},
"mode": {
"type": "string",
"description": "Mode to trigger the command.",
"enum": ["run-one", "run-many"],
"x-priority": "internal"
}
},
"additionalProperties": true,
Expand Down

0 comments on commit c2ec654

Please sign in to comment.