Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
#!/usr/bin/env node
const fs = require('fs');
const yargs = require('yargs');
const defaults = require('../src/defaults');
const concurrently = require('../index');
const args = yargs
.usage('$0 [options] <command ...>')
.help('h')
.alias('h', 'help')
.version('v', require('../package.json').version)
.alias('v', 'V')
.alias('v', 'version')
.options({
// General
'm': {
alias: 'max-processes',
describe:
'How many processes should run at once.\n' +
'New processes only spawn after all restart tries of a process.',
type: 'number'
},
'n': {
alias: 'names',
describe:
'List of custom names to be used in prefix template.\n' +
'Example names: "main,browser,server"',
type: 'string'
},
'name-separator': {
describe:
'The character to split <names> on. Example usage:\n' +
'concurrently -n "styles|scripts|server" --name-separator "|"',
default: defaults.nameSeparator,
},
's': {
alias: 'success',
describe:
'Return exit code of zero or one based on the success or failure ' +
'of the "first" child to terminate, the "last child", or succeed ' +
'only if "all" child processes succeed.',
choices: ['first', 'last', 'all'],
default: defaults.success
},
'r': {
alias: 'raw',
describe:
'Output only raw output of processes, disables prettifying ' +
'and concurrently coloring.',
type: 'boolean'
},
// This one is provided for free. Chalk reads this itself and removes colours.
// https://www.npmjs.com/package/chalk#chalksupportscolor
'no-color': {
describe: 'Disables colors from logging',
type: 'boolean'
},
'hide': {
describe:
'Comma-separated list of processes to hide the output.\n' +
'The processes can be identified by their name or index.',
default: defaults.hide,
type: 'string'
},
// Kill others
'k': {
alias: 'kill-others',
describe: 'kill other processes if one exits or dies',
type: 'boolean'
},
'kill-others-on-fail': {
describe: 'kill other processes if one exits with non zero status code',
type: 'boolean'
},
// Prefix
'p': {
alias: 'prefix',
describe:
'Prefix used in logging for each process.\n' +
'Possible values: index, pid, time, command, name, none, or a template. ' +
'Example template: "{time}-{pid}"',
defaultDescription: 'index or name (when --names is set)',
type: 'string'
},
'c': {
alias: 'prefix-colors',
describe:
'Comma-separated list of chalk colors to use on prefixes. ' +
'If there are more commands than colors, the last color will be repeated.\n' +
'- Available modifiers: reset, bold, dim, italic, underline, inverse, hidden, strikethrough\n' +
'- Available colors: black, red, green, yellow, blue, magenta, cyan, white, gray \n' +
'or any hex values for colors, eg #23de43\n' +
'- Available background colors: bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite\n' +
'See https://www.npmjs.com/package/chalk for more information.',
default: defaults.prefixColors,
type: 'string'
},
'l': {
alias: 'prefix-length',
describe:
'Limit how many characters of the command is displayed in prefix. ' +
'The option can be used to shorten the prefix when it is set to "command"',
default: defaults.prefixLength,
type: 'number'
},
't': {
alias: 'timestamp-format',
describe: 'Specify the timestamp in moment/date-fns format.',
default: defaults.timestampFormat,
type: 'string'
},
// Restarting
'restart-tries': {
describe:
'How many times a process that died should restart.\n' +
'Negative numbers will make the process restart forever.',
default: defaults.restartTries,
type: 'number'
},
'restart-after': {
describe: 'Delay time to respawn the process, in milliseconds.',
default: defaults.restartDelay,
type: 'number'
},
// Input
'i': {
alias: 'handle-input',
describe:
'Whether input should be forwarded to the child processes. ' +
'See examples for more information.',
type: 'boolean'
},
'default-input-target': {
default: defaults.defaultInputTarget,
describe:
'Identifier for child process to which input on stdin ' +
'should be sent if not specified at start of input.\n' +
'Can be either the index or the name of the process.'
}
})
.group(['m', 'n', 'name-separator', 'raw', 's', 'no-color', 'hide'], 'General')
.group(['p', 'c', 'l', 't'], 'Prefix styling')
.group(['i', 'default-input-target'], 'Input handling')
.group(['k', 'kill-others-on-fail'], 'Killing other processes')
.group(['restart-tries', 'restart-after'], 'Restarting')
// Too much text to write as JS strings, .txt file is better
.epilogue(fs.readFileSync(__dirname + '/epilogue.txt', { encoding: 'utf8' }))
.argv;
const names = (args.names || '').split(args.nameSeparator);
concurrently(args._.map((command, index) => ({
command,
name: names[index]
})), {
handleInput: args.handleInput,
defaultInputTarget: args.defaultInputTarget,
killOthers: args.killOthers
? ['success', 'failure']
: (args.killOthersOnFail ? ['failure'] : []),
maxProcesses: args.maxProcesses,
raw: args.raw,
hide: args.hide.split(','),
prefix: args.prefix,
prefixColors: args.prefixColors.split(','),
prefixLength: args.prefixLength,
restartDelay: args.restartAfter,
restartTries: args.restartTries,
successCondition: args.success,
timestampFormat: args.timestampFormat,
}).then(
() => process.exit(0),
() => process.exit(1)
);

View File

@@ -0,0 +1,385 @@
const readline = require('readline');
const _ = require('lodash');
const Rx = require('rxjs');
const { buffer, map } = require('rxjs/operators');
const spawn = require('spawn-command');
const isWindows = process.platform === 'win32';
const createKillMessage = prefix => new RegExp(
_.escapeRegExp(prefix) +
' exited with code ' +
(isWindows ? 1 : '(SIGTERM|143)')
);
const run = args => {
const child = spawn('node ./concurrently.js ' + args, {
cwd: __dirname,
env: Object.assign({}, process.env, {
// When upgrading from jest 23 -> 24, colors started printing in the test output.
// They are forcibly disabled here
FORCE_COLOR: 0
}),
});
const stdout = readline.createInterface({
input: child.stdout,
output: null
});
const stderr = readline.createInterface({
input: child.stderr,
output: null
});
const close = Rx.fromEvent(child, 'close');
const log = Rx.merge(
Rx.fromEvent(stdout, 'line'),
Rx.fromEvent(stderr, 'line')
).pipe(map(data => data.toString()));
return {
close,
log,
stdin: child.stdin,
pid: child.pid
};
};
it('has help command', done => {
run('--help').close.subscribe(event => {
expect(event[0]).toBe(0);
done();
}, done);
});
it('has version command', done => {
Rx.combineLatest(
run('--version').close,
run('-V').close,
run('-v').close
).subscribe(events => {
expect(events[0][0]).toBe(0);
expect(events[1][0]).toBe(0);
expect(events[2][0]).toBe(0);
done();
}, done);
});
describe('exiting conditions', () => {
it('is of success by default when running successful commands', done => {
run('"echo foo" "echo bar"')
.close
.subscribe(exit => {
expect(exit[0]).toBe(0);
done();
}, done);
});
it('is of failure by default when one of the command fails', done => {
run('"echo foo" "exit 1"')
.close
.subscribe(exit => {
expect(exit[0]).toBeGreaterThan(0);
done();
}, done);
});
it('is of success when --success=first and first command to exit succeeds', done => {
run('--success=first "echo foo" "sleep 0.5 && exit 1"')
.close
.subscribe(exit => {
expect(exit[0]).toBe(0);
done();
}, done);
});
it('is of failure when --success=first and first command to exit fails', done => {
run('--success=first "exit 1" "sleep 0.5 && echo foo"')
.close
.subscribe(exit => {
expect(exit[0]).toBeGreaterThan(0);
done();
}, done);
});
it('is of success when --success=last and last command to exit succeeds', done => {
run('--success=last "exit 1" "sleep 0.5 && echo foo"')
.close
.subscribe(exit => {
expect(exit[0]).toBe(0);
done();
}, done);
});
it('is of failure when --success=last and last command to exit fails', done => {
run('--success=last "echo foo" "sleep 0.5 && exit 1"')
.close
.subscribe(exit => {
expect(exit[0]).toBeGreaterThan(0);
done();
}, done);
});
it.skip('is of success when a SIGINT is sent', done => {
const child = run('"node fixtures/read-echo.js"');
child.close.subscribe(exit => {
// TODO This is null within Node, but should be 0 outside (eg from real terminal)
expect(exit[0]).toBe(0);
done();
}, done);
process.kill(child.pid, 'SIGINT');
});
it('is aliased to -s', done => {
run('-s last "exit 1" "sleep 0.5 && echo foo"')
.close
.subscribe(exit => {
expect(exit[0]).toBe(0);
done();
}, done);
});
});
describe('--raw', () => {
it('is aliased to -r', done => {
const child = run('-r "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toHaveLength(2);
expect(lines).toContainEqual(expect.stringContaining('foo'));
expect(lines).toContainEqual(expect.stringContaining('bar'));
done();
}, done);
});
it('does not log any extra output', done => {
const child = run('--raw "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toHaveLength(2);
expect(lines).toContainEqual(expect.stringContaining('foo'));
expect(lines).toContainEqual(expect.stringContaining('bar'));
done();
}, done);
});
});
describe('--hide', () => {
it('hides the output of a process by its index', done => {
const child = run('--hide 1 "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('foo'));
expect(lines).not.toContainEqual(expect.stringContaining('bar'));
done();
}, done);
});
it('hides the output of a process by its name', done => {
const child = run('-n foo,bar --hide bar "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('foo'));
expect(lines).not.toContainEqual(expect.stringContaining('bar'));
done();
}, done);
});
});
describe('--names', () => {
it('is aliased to -n', done => {
const child = run('-n foo,bar "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[foo] foo'));
expect(lines).toContainEqual(expect.stringContaining('[bar] bar'));
done();
}, done);
});
it('prefixes with names', done => {
const child = run('--names foo,bar "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[foo] foo'));
expect(lines).toContainEqual(expect.stringContaining('[bar] bar'));
done();
}, done);
});
it('is split using --name-separator arg', done => {
const child = run('--names "foo|bar" --name-separator "|" "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[foo] foo'));
expect(lines).toContainEqual(expect.stringContaining('[bar] bar'));
done();
}, done);
});
});
describe('--prefix', () => {
it('is aliased to -p', done => {
const child = run('-p command "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[echo foo] foo'));
expect(lines).toContainEqual(expect.stringContaining('[echo bar] bar'));
done();
}, done);
});
it('specifies custom prefix', done => {
const child = run('--prefix command "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[echo foo] foo'));
expect(lines).toContainEqual(expect.stringContaining('[echo bar] bar'));
done();
}, done);
});
});
describe('--prefix-length', () => {
it('is aliased to -l', done => {
const child = run('-p command -l 5 "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[ec..o] foo'));
expect(lines).toContainEqual(expect.stringContaining('[ec..r] bar'));
done();
}, done);
});
it('specifies custom prefix length', done => {
const child = run('--prefix command --prefix-length 5 "echo foo" "echo bar"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[ec..o] foo'));
expect(lines).toContainEqual(expect.stringContaining('[ec..r] bar'));
done();
}, done);
});
});
describe('--restart-tries', () => {
it('changes how many times a command will restart', done => {
const child = run('--restart-tries 1 "exit 1"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toEqual([
expect.stringContaining('[0] exit 1 exited with code 1'),
expect.stringContaining('[0] exit 1 restarted'),
expect.stringContaining('[0] exit 1 exited with code 1'),
]);
done();
}, done);
});
});
describe('--kill-others', () => {
it('is aliased to -k', done => {
const child = run('-k "sleep 10" "exit 0"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[1] exit 0 exited with code 0'));
expect(lines).toContainEqual(expect.stringContaining('Sending SIGTERM to other processes'));
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] sleep 10')));
done();
}, done);
});
it('kills on success', done => {
const child = run('--kill-others "sleep 10" "exit 0"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[1] exit 0 exited with code 0'));
expect(lines).toContainEqual(expect.stringContaining('Sending SIGTERM to other processes'));
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] sleep 10')));
done();
}, done);
});
it('kills on failure', done => {
const child = run('--kill-others "sleep 10" "exit 1"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[1] exit 1 exited with code 1'));
expect(lines).toContainEqual(expect.stringContaining('Sending SIGTERM to other processes'));
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] sleep 10')));
done();
}, done);
});
});
describe('--kill-others-on-fail', () => {
it('does not kill on success', done => {
const child = run('--kill-others-on-fail "sleep 0.5" "exit 0"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[1] exit 0 exited with code 0'));
expect(lines).toContainEqual(expect.stringContaining('[0] sleep 0.5 exited with code 0'));
done();
}, done);
});
it('kills on failure', done => {
const child = run('--kill-others-on-fail "sleep 10" "exit 1"');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[1] exit 1 exited with code 1'));
expect(lines).toContainEqual(expect.stringContaining('Sending SIGTERM to other processes'));
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] sleep 10')));
done();
}, done);
});
});
describe('--handle-input', () => {
it('is aliased to -i', done => {
const child = run('-i "node fixtures/read-echo.js"');
child.log.subscribe(line => {
if (/READING/.test(line)) {
child.stdin.write('stop\n');
}
if (/\[0\] stop/.test(line)) {
done();
}
}, done);
});
it('forwards input to first process by default', done => {
const child = run('--handle-input "node fixtures/read-echo.js"');
child.log.subscribe(line => {
if (/READING/.test(line)) {
child.stdin.write('stop\n');
}
if (/\[0\] stop/.test(line)) {
done();
}
}, done);
});
it('forwards input to process --default-input-target', done => {
const lines = [];
const child = run('-ki --default-input-target 1 "node fixtures/read-echo.js" "node fixtures/read-echo.js"');
child.log.subscribe(line => {
lines.push(line);
if (/\[1\] READING/.test(line)) {
child.stdin.write('stop\n');
}
}, done);
child.close.subscribe(exit => {
expect(exit[0]).toBeGreaterThan(0);
expect(lines).toContainEqual(expect.stringContaining('[1] stop'));
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] node fixtures/read-echo.js')));
done();
}, done);
});
it('forwards input to specified process', done => {
const lines = [];
const child = run('-ki "node fixtures/read-echo.js" "node fixtures/read-echo.js"');
child.log.subscribe(line => {
lines.push(line);
if (/\[1\] READING/.test(line)) {
child.stdin.write('1:stop\n');
}
}, done);
child.close.subscribe(exit => {
expect(exit[0]).toBeGreaterThan(0);
expect(lines).toContainEqual(expect.stringContaining('[1] stop'));
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] node fixtures/read-echo.js')));
done();
}, done);
});
});

View File

@@ -0,0 +1,42 @@
Examples:
- Output nothing more than stdout+stderr of child processes
$ $0 --raw "npm run watch-less" "npm run watch-js"
- Normal output but without colors e.g. when logging to file
$ $0 --no-color "grunt watch" "http-server" > log
- Custom prefix
$ $0 --prefix "{time}-{pid}" "npm run watch" "http-server"
- Custom names and colored prefixes
$ $0 --names "HTTP,WATCH" -c "bgBlue.bold,bgMagenta.bold" "http-server" "npm run watch"
- Send input to default
$ $0 --handle-input "nodemon" "npm run watch-js"
rs # Sends rs command to nodemon process
- Send input to specific child identified by index
$ $0 --handle-input "npm run watch-js" nodemon
1:rs
- Send input to specific child identified by name
$ $0 --handle-input -n js,srv "npm run watch-js" nodemon
srv:rs
- Shortened NPM run commands
$ $0 npm:watch-node npm:watch-js npm:watch-css
- Shortened NPM run command with wildcard
$ $0 npm:watch-*
For more details, visit https://github.com/open-cli-tools/concurrently