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 21, 2024
1 parent 317ef2e commit 2202894
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 58 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
111 changes: 70 additions & 41 deletions packages/nx/src/executors/run-commands/run-commands.impl.ts
Expand Up @@ -51,6 +51,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 +81,7 @@ export default async function (
context: ExecutorContext
): Promise<{
success: boolean;
terminalOutput: string;
}> {
await loadEnvVars(options.envFile);
const normalized = normalizeOptions(options);
Expand All @@ -100,10 +102,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 +119,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 @@ -188,25 +203,29 @@ function normalizeOptions(
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 +239,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 +287,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 +308,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
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 2202894

Please sign in to comment.