
Security News
The Next Open Source Security Race: Triage at Machine Speed
Claude Opus 4.6 has uncovered more than 500 open source vulnerabilities, raising new considerations for disclosure, triage, and patching at scale.
Command Line E2E Testing.
Aiming to make end-to-end testing for command-line apps as simple as possible.
import { runner, KEYS } from 'clet';
import request from 'supertest';
describe('command-line end-to-end testing', () => {
// test your boilerplate with prompts
it('should works with boilerplate', async () => {
await runner()
.cwd('/path/to/dir', { init: true })
.spawn('npm init')
.stdin(/name:/, 'example') // wait for stdout, then respond
.stdin(/version:/, new Array(9).fill(KEYS.ENTER)) // don't care about others, just enter
.stdout(/"name": "example"/) // validate stdout
.file('package.json', { name: 'example', version: '1.0.0' }); // validate file content, relative to cwd
});
// test your commander
it('should works with command-line apps', async () => {
const baseDir = path.resolve(fixtures, 'example');
await runner()
.cwd(baseDir)
.fork('bin/cli.js', [ '--name=test' ], { execArgv: [ '--no-deprecation' ] })
.stdout('this is example bin')
.stdout(`cwd=${baseDir}`)
.stdout(/argv=\["--name=\w+"\]/)
.stdout(/execArgv=\["--no-deprecation"\]/)
.stderr(/this is a warning/);
});
// test your long-run apps such as http server or build tools
it('should works with long-run apps', async () => {
const baseDir = path.resolve(fixtures, 'server');
await runner()
.cwd(baseDir)
.fork('bin/cli.js')
.wait('stdout', /server started/)
.expect(async () => {
// using supertest
return request('http://localhost:3000')
.get('/')
.query({ name: 'tz' })
.expect(200)
.expect('hi, tz');
})
.kill(); // long-run server will not auto exit, so kill it manually after test
});
});
npm i --save clet
Execute a Node.js script as a child process.
it('should fork', async () => {
await runner()
.cwd(fixtures)
.fork('example.js', [ '--name=test' ], { execArgv: [ '--no-deprecation' ] })
.stdout('this is example bin')
.stdout(/argv=\["--name=\w+"\]/)
.stdout(/execArgv=\["--no-deprecation"\]/)
.stderr(/this is a warning/)
.code(0);
});
Options:
timeout: {Number} - will kill after timeout.execArgv: {Array} - pass to child process's execArgv, default to process.execArgv.cwd: {String} - working directory, prefer to use .cwd() instead of this.env: {Object} - prefer to use .env() instead of this.extendEnv: {Boolean} - whether extend process.env, default to true.Execute a shell script as a child process.
it('should support spawn', async () => {
await runner()
.spawn('node -v')
.stdout(/v\d+\.\d+\.\d+/)
.code(0);
});
Change the current working directory.
Notice: it will affect
fork()script relative path,file(),mkdir()etc.
it('support cwd()', async () => {
await runner()
.cwd(targetDir)
.fork(cliPath);
});
Support options:
init: will delete and create directory before test.clean: will delete directory after test.Use
trashinstead offs.rmdue to the consideration of preventing misoperation.
it('support cwd() with opts', async () => {
await runner()
.cwd(targetDir, { init: true, clean: true })
.fork(cliPath)
.notFile('should-delete.md')
.file('test.md', /# test/);
});
Set environment variables.
Notice: if you don't want to extend the environment variables, set
opts.extendEnvto false.
it('support env', async () => {
await runner()
.env('DEBUG', 'CLI')
.fork('./example.js', [], { extendEnv: false });
});
Set a timeout, will kill SIGTERM then SIGKILL.
it('support timeout', async () => {
await runner()
.timeout(5000)
.fork('./example.js');
});
Wait for some condition, then resume the chain, useful for tesing long-run http server apps.
type: {String} - support message / stdout / stderr / closeexpected: {String|RegExp|Object|Function}
Notice: don't forgot to
wait('end')orkill()later.
it('should wait', async () => {
await runner()
.fork('./wait.js')
.wait('stdout', /server started/)
// .wait('message', { action: 'egg-ready' }) // ipc message
.file('logs/web.log')
.kill();
});
Kill the child process.
useful for manually end long-run server after validate.
Notice: when kill, exit code maybe undefined if the command don't hook signal event.
it('should kill() manually after test server', async () => {
await runner()
.cwd(fixtures)
.fork('server.js')
.wait('stdout', /server started/)
.kill();
});
Detect a prompt, then respond to it.
expected: {String|RegExp} - test stdout with regexp match or string includes.respond: {String|Array} - respond content, if set to array then write each with a delayYou could use KEYS.UP / KEYS.DOWN to respond to choices prompt.
import { runner, KEYS } from '../lib/runner.js';
it('should support stdin respond', async () => {
await runner()
.cwd(fixtures)
.fork('./prompt.js')
.stdin(/Name:/, 'tz')
.stdin(/Email:/, 'tz@eggjs.com')
.stdin(/Gender:/, [ KEYS.DOWN + KEYS.DOWN ])
.stdout(/Author: tz <tz@eggjs.com>/)
.stdout(/Gender: unknown/)
.code(0);
});
Validate stdout, support regexp and string.includes.
it('should support stdout()', async () => {
await runner()
.spawn('node -v')
.stdout(/v\d+\.\d+\.\d+/) // regexp match
.stdout(process.version) // string includes;
});
Opposite of stdout()
Validate stdout, support regexp and string.includes.
it('should support stderr()', async () => {
await runner()
.cwd(fixtures)
.fork('example.js')
.stderr(/a warning/)
.stderr('this is a warning');
});
Opposite of stderr()
Validate process exit code.
will auto check whether proc is exit unexpected by default, so only use this if you want to validate fail exitCode.
Notice: when proc is kill, exit code maybe undefined if you don't hook signal events.
it('should support code()', async () => {
await runner()
.spawn('node --unknown-argv')
.code(1);
});
Validate file.
file(filePath): check whether file is existsfile(filePath, 'some string'): check whether file content includes specified stringfile(filePath, /some regexp/): checke whether file content match regexpfile(filePath, {}): checke whether file content partial includes specified JSONit('should support file()', async () => {
await runner()
.cwd('/path/to/dir', { init: true })
.spawn('npm init -y')
.file('package.json')
.file('package.json', /"name":/)
.file('package.json', { name: 'example', config: { port: 8080 } });
});
Opposite of file()
Notice:
.notFile('not-exist.md', 'abc')will throw due to file is not exists.
Validate with custom function.
Provide useful assert method ctx.assert.
it('should support expect()', async () => {
await runner()
.spawn('node -v')
.expect(ctx => {
const { assert, result } = ctx;
assert.match(result.stdout, /v\d+\.\d+\.\d+/);
});
});
Print log for debugging, support formator and dot path.
it('should support log()', async () => {
await runner()
.spawn('node -v')
.log('result: %j', 'result')
.log('result.stdout')
.stdout(/v\d+\.\d+\.\d+/);
});
Tap a method to chain sequence.
it('should support tap()', async () => {
await runner()
.spawn('node -v')
.tap(async ({ result, assert}) => {
assert(result.stdout, /v\d+\.\d+\.\d+/);
});
});
it('should support sleep()', async () => {
await runner()
.fork(cliPath)
.sleep(2000)
.log('result.stdout');
});
Run a shell, useful for npm install after boilerplate init.
it('should support shell', async () => {
await runner()
.cwd('/path/to/dir', { init: true })
.spawn('npm init -y')
.file('package.json', { name: 'shell', version: '1.0.0' })
.shell('npm version minor --no-git-tag-version')
.file('package.json', { version: '1.1.0' });
});
Act like mkdir -p.
it('should support mkdir', async () => {
await runner()
.cwd(tmpDir, { init: true })
.mkdir('a/b')
.file('a/b')
.spawn('npm -v');
});
Move dir/file to trash.
it('should support rm', async () => {
await runner()
.cwd(tmpDir, { init: true })
.mkdir('a/b')
.rm('a/b')
.notFile('a/b')
.spawn('npm -v');
});
Write content to file, support JSON and PlainText.
it('should support writeFile', async () => {
await runner()
.cwd(tmpDir, { init: true })
.writeFile('test.json', { name: 'writeFile' })
.writeFile('test.md', 'this is a test')
.file('test.json', /"name": "writeFile"/)
.file('test.md', /this is a test/)
.spawn('npm -v');
});
MIT
FAQs
Command Line E2E Testing
The npm package clet receives a total of 66 weekly downloads. As such, clet popularity was classified as not popular.
We found that clet demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Claude Opus 4.6 has uncovered more than 500 open source vulnerabilities, raising new considerations for disclosure, triage, and patching at scale.

Research
/Security News
Malicious dYdX client packages were published to npm and PyPI after a maintainer compromise, enabling wallet credential theft and remote code execution.

Security News
gem.coop is testing registry-level dependency cooldowns to limit exposure during the brief window when malicious gems are most likely to spread.