nx/packages/workspace/src/executors/run-commands/run-commands.impl.spec.ts

436 lines
12 KiB
TypeScript

import { readFileSync, unlinkSync, writeFileSync } from 'fs';
import { relative } from 'path';
import { dirSync, fileSync } from 'tmp';
import runCommands, { LARGE_BUFFER } from './run-commands.impl';
function normalize(p: string) {
return p.startsWith('/private') ? p.substring(8) : p;
}
function readFile(f: string) {
return readFileSync(f).toString().replace(/\s/g, '');
}
describe('Command Runner Builder', () => {
const context = {} as any;
beforeEach(() => {
jest.clearAllMocks();
});
it('should run one command', async () => {
const f = fileSync().name;
const result = await runCommands({ command: `echo 1 >> ${f}` }, context);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(readFile(f)).toEqual('1');
});
it('should interpolate provided --args', async () => {
const f = fileSync().name;
const result = await runCommands(
{ command: `echo {args.key} >> ${f}`, args: '--key=123' },
context
);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(readFile(f)).toEqual('123');
});
it('should interpolate all unknown args as if they were --args', async () => {
const f = fileSync().name;
const result = await runCommands(
{
command: `echo {args.key} >> ${f}`,
key: 123,
},
context
);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(readFile(f)).toEqual('123');
});
it('should add all args to the command if no interpolation in the command', async () => {
const exec = jest.spyOn(require('child_process'), 'execSync');
await runCommands(
{
command: `echo`,
a: 123,
b: 456,
},
context
);
expect(exec).toHaveBeenCalledWith(`echo --a=123 --b=456`, {
stdio: [0, 1, 2],
cwd: undefined,
env: process.env,
maxBuffer: LARGE_BUFFER,
});
});
it('should forward args by default when using commands (plural)', async () => {
const exec = jest.spyOn(require('child_process'), 'exec');
await runCommands(
{
commands: [{ command: 'echo' }, { command: 'echo foo' }],
parallel: true,
a: 123,
b: 456,
},
context
);
expect(exec).toHaveBeenCalledTimes(2);
expect(exec).toHaveBeenNthCalledWith(1, 'echo --a=123 --b=456', {
maxBuffer: LARGE_BUFFER,
env: { ...process.env },
});
expect(exec).toHaveBeenNthCalledWith(2, 'echo foo --a=123 --b=456', {
maxBuffer: LARGE_BUFFER,
env: { ...process.env },
});
});
it('should forward args when forwardAllArgs is set to true', async () => {
const exec = jest.spyOn(require('child_process'), 'exec');
await runCommands(
{
commands: [
{ command: 'echo', forwardAllArgs: true },
{ command: 'echo foo', forwardAllArgs: true },
],
parallel: true,
a: 123,
b: 456,
},
context
);
expect(exec).toHaveBeenCalledTimes(2);
expect(exec).toHaveBeenNthCalledWith(1, 'echo --a=123 --b=456', {
maxBuffer: LARGE_BUFFER,
env: { ...process.env },
});
expect(exec).toHaveBeenNthCalledWith(2, 'echo foo --a=123 --b=456', {
maxBuffer: LARGE_BUFFER,
env: { ...process.env },
});
});
it('should not forward args when forwardAllArgs is set to false', async () => {
const exec = jest.spyOn(require('child_process'), 'exec');
await runCommands(
{
commands: [
{ command: 'echo', forwardAllArgs: false },
{ command: 'echo foo', forwardAllArgs: false },
],
parallel: true,
a: 123,
b: 456,
},
context
);
expect(exec).toHaveBeenCalledTimes(2);
expect(exec).toHaveBeenNthCalledWith(1, 'echo', {
maxBuffer: LARGE_BUFFER,
env: { ...process.env },
});
expect(exec).toHaveBeenNthCalledWith(2, 'echo foo', {
maxBuffer: LARGE_BUFFER,
env: { ...process.env },
});
});
it('should throw when invalid args', async () => {
try {
await runCommands(
{
command: `echo {args.key}`,
args: 'key=value',
},
context
);
} catch (e) {
expect(e.message).toEqual('Invalid args: key=value');
}
});
it('should run commands serially', async () => {
const f = fileSync().name;
const result = await runCommands(
{
commands: [`sleep 0.2 && echo 1 >> ${f}`, `echo 2 >> ${f}`],
parallel: false,
},
context
);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(readFile(f)).toEqual('12');
});
it('should run commands in parallel', async () => {
const f = fileSync().name;
const result = await runCommands(
{
commands: [
{
command: `echo 1 >> ${f}`,
},
{
command: `echo 2 >> ${f}`,
},
],
parallel: true,
},
context
);
expect(result).toEqual(expect.objectContaining({ success: true }));
const contents = readFile(f);
expect(contents).toContain('1');
expect(contents).toContain('2');
});
describe('readyWhen', () => {
it('should error when parallel = false', async () => {
try {
await runCommands(
{
commands: [{ command: 'echo foo' }, { command: 'echo bar' }],
parallel: false,
readyWhen: 'READY',
},
context
);
fail('should throw');
} catch (e) {
expect(e.message).toEqual(
`ERROR: Bad executor config for @nrwl/run-commands - "readyWhen" can only be used when "parallel=true".`
);
}
});
it('should return success true when the string specified as ready condition is found', async () => {
const f = fileSync().name;
const result = await runCommands(
{
commands: [`echo READY && sleep 0.1 && echo 1 >> ${f}`, `echo foo`],
parallel: true,
readyWhen: 'READY',
},
context
);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(readFile(f)).toEqual('');
setTimeout(() => {
expect(readFile(f)).toEqual('1');
}, 150);
});
});
it('should stop execution and fail when a command fails', async () => {
const f = fileSync().name;
try {
await runCommands(
{
commands: [`echo 1 >> ${f} && exit 1`, `echo 2 >> ${f}`],
parallel: false,
},
context
);
fail('should fail when a command fails');
} catch (e) {}
expect(readFile(f)).toEqual('1');
});
describe('--color', () => {
it('should not set FORCE_COLOR=true', async () => {
const exec = jest.spyOn(require('child_process'), 'exec');
await runCommands(
{
commands: [`echo 'Hello World'`, `echo 'Hello Universe'`],
parallel: true,
},
context
);
expect(exec).toHaveBeenCalledTimes(2);
expect(exec).toHaveBeenNthCalledWith(1, `echo 'Hello World'`, {
maxBuffer: LARGE_BUFFER,
env: { ...process.env },
});
expect(exec).toHaveBeenNthCalledWith(2, `echo 'Hello Universe'`, {
maxBuffer: LARGE_BUFFER,
env: { ...process.env },
});
});
it('should set FORCE_COLOR=true when running with --color', async () => {
const exec = jest.spyOn(require('child_process'), 'exec');
await runCommands(
{
commands: [`echo 'Hello World'`, `echo 'Hello Universe'`],
parallel: true,
color: true,
},
context
);
expect(exec).toHaveBeenCalledTimes(2);
expect(exec).toHaveBeenNthCalledWith(1, `echo 'Hello World'`, {
maxBuffer: LARGE_BUFFER,
env: { ...process.env, FORCE_COLOR: `true` },
});
expect(exec).toHaveBeenNthCalledWith(2, `echo 'Hello Universe'`, {
maxBuffer: LARGE_BUFFER,
env: { ...process.env, FORCE_COLOR: `true` },
});
});
});
describe('cwd', () => {
it('should run the task in the workspace root when no cwd is specified', async () => {
const root = dirSync().name;
const f = fileSync().name;
const result = await runCommands(
{
commands: [
{
command: `pwd >> ${f}`,
},
],
parallel: true,
},
{ root } as any
);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(normalize(readFile(f))).toBe(root);
});
it('should run the task in the specified cwd relative to the workspace root when cwd is not an absolute path', async () => {
const root = dirSync().name;
const childFolder = dirSync({ dir: root }).name;
const cwd = relative(root, childFolder);
const f = fileSync().name;
const result = await runCommands(
{
commands: [
{
command: `pwd >> ${f}`,
},
],
cwd,
parallel: true,
},
{ root } as any
);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(normalize(readFile(f))).toBe(childFolder);
});
it('should run the task in the specified absolute cwd', async () => {
const root = dirSync().name;
const childFolder = dirSync({ dir: root }).name;
const f = fileSync().name;
const result = await runCommands(
{
commands: [
{
command: `pwd >> ${f}`,
},
],
cwd: childFolder,
parallel: true,
},
{ root } as any
);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(normalize(readFile(f))).toBe(childFolder);
});
});
describe('dotenv', () => {
beforeAll(() => {
writeFileSync('.env', 'NRWL_SITE=https://nrwl.io/');
});
beforeEach(() => {
delete process.env.NRWL_SITE;
delete process.env.NX_SITE;
});
afterAll(() => {
unlinkSync('.env');
});
it('should load the root .env file by default if there is one', async () => {
let f = fileSync().name;
const result = await runCommands(
{
commands: [
{
command: `echo $NRWL_SITE >> ${f}`,
},
],
},
context
);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(readFile(f)).toEqual('https://nrwl.io/');
});
it('should load the specified .env file instead of the root one', async () => {
const devEnv = fileSync().name;
writeFileSync(devEnv, 'NX_SITE=https://nx.dev/');
let f = fileSync().name;
const result = await runCommands(
{
commands: [
{
command: `echo $NX_SITE >> ${f} && echo $NRWL_SITE >> ${f}`,
},
],
envFile: devEnv,
},
context
);
expect(result).toEqual(expect.objectContaining({ success: true }));
expect(readFile(f)).toEqual('https://nx.dev/');
});
it('should error if the specified .env file does not exist', async () => {
let f = fileSync().name;
try {
await runCommands(
{
commands: [
{
command: `echo $NX_SITE >> ${f} && echo $NRWL_SITE >> ${f}`,
},
],
envFile: '/somePath/.fakeEnv',
},
context
);
fail('should not reach');
} catch (e) {
expect(e.message).toContain(
`no such file or directory, open '/somePath/.fakeEnv'`
);
}
});
});
});