From 3c4b0fd1b9496625def5fb2d6040d528700d8925 Mon Sep 17 00:00:00 2001 From: ziebam <31964869+ziebam@users.noreply.github.com> Date: Sat, 7 Sep 2024 11:28:05 +0200 Subject: [PATCH] chore: replace `execa` with `tinyexec` and Node's `child_process.spawnSync` (#4134) * chore(utils): replace `execa` with `tinyexec` * chore(commitlint): replace `execa` with `tinyexec` * chore(read): replace `execa` with `tinyexec` * chore(travis-cli): replace `execa` with `tinyexec` * chore(prompt-cli): replace `execa` with `tinyexec` * chore(cli): replace `execa` with `tinyexec` * chore(prompt-cli): fix the test * chore(test): replace `execa` with `tinyexec` * chore(rules): replace `execa` with a native implementation * chore: fix the test --- @alias/commitlint/cli.test.js | 17 +- @alias/commitlint/package.json | 2 +- @commitlint/cli/package.json | 2 +- @commitlint/cli/src/cli.test.ts | 450 +++++++++++++----------- @commitlint/cli/src/cli.ts | 26 +- @commitlint/prompt-cli/cli.js | 10 +- @commitlint/prompt-cli/cli.test.js | 17 +- @commitlint/prompt-cli/package.json | 4 +- @commitlint/read/package.json | 4 +- @commitlint/read/src/read.test.ts | 88 +++-- @commitlint/read/src/read.ts | 13 +- @commitlint/rules/package.json | 3 +- @commitlint/rules/src/trailer-exists.ts | 8 +- @commitlint/travis-cli/package.json | 2 +- @commitlint/travis-cli/src/cli.test.ts | 28 +- @commitlint/travis-cli/src/cli.ts | 37 +- @packages/test/package.json | 2 +- @packages/test/src/git.ts | 16 +- @packages/utils/dep-check.js | 4 +- @packages/utils/package.json | 2 +- @packages/utils/pkg-check.js | 6 +- yarn.lock | 5 + 22 files changed, 417 insertions(+), 329 deletions(-) diff --git a/@alias/commitlint/cli.test.js b/@alias/commitlint/cli.test.js index debea53ba2..8a04c7314b 100644 --- a/@alias/commitlint/cli.test.js +++ b/@alias/commitlint/cli.test.js @@ -3,7 +3,7 @@ import {createRequire} from 'module'; import path from 'path'; import {fileURLToPath} from 'url'; -import {execa} from 'execa'; +import {x} from 'tinyexec'; import {fix} from '@commitlint/test'; const require = createRequire(import.meta.url); @@ -13,12 +13,17 @@ const __dirname = path.resolve(fileURLToPath(import.meta.url), '..'); const bin = require.resolve('./cli.js'); function cli(args, options, input) { - const c = execa(bin, args, { - cwd: options.cwd, - env: options.env, - input: input, + const result = x(bin, args, { + nodeOptions: { + cwd: options.cwd, + env: options.env, + }, }); - return c.catch((err) => err); + + result.process.stdin.write(input); + result.process.stdin.end(); + + return result; } const fixBootstrap = (fixture) => fix.bootstrap(fixture, __dirname); diff --git a/@alias/commitlint/package.json b/@alias/commitlint/package.json index 7400c14cf4..8aafa5e247 100644 --- a/@alias/commitlint/package.json +++ b/@alias/commitlint/package.json @@ -42,7 +42,7 @@ "devDependencies": { "@commitlint/test": "^19.0.0", "@commitlint/utils": "^19.0.0", - "execa": "^8.0.1" + "tinyexec": "^0.3.0" }, "gitHead": "70f7f4688b51774e7ac5e40e896cdaa3f132b2bc" } diff --git a/@commitlint/cli/package.json b/@commitlint/cli/package.json index b9f56c5203..0e6dae7dd5 100644 --- a/@commitlint/cli/package.json +++ b/@commitlint/cli/package.json @@ -53,7 +53,7 @@ "@commitlint/load": "^19.4.0", "@commitlint/read": "^19.4.0", "@commitlint/types": "^19.0.3", - "execa": "^8.0.1", + "tinyexec": "^0.3.0", "yargs": "^17.0.0" }, "gitHead": "70f7f4688b51774e7ac5e40e896cdaa3f132b2bc" diff --git a/@commitlint/cli/src/cli.test.ts b/@commitlint/cli/src/cli.test.ts index 90e5e67c50..f045b1c856 100644 --- a/@commitlint/cli/src/cli.test.ts +++ b/@commitlint/cli/src/cli.test.ts @@ -4,9 +4,9 @@ import path from 'path'; import {fileURLToPath} from 'url'; import {fix, git} from '@commitlint/test'; -import {execa} from 'execa'; import fs from 'fs-extra'; import merge from 'lodash.merge'; +import {x} from 'tinyexec'; const require = createRequire(import.meta.url); @@ -21,12 +21,17 @@ interface TestOptions { const cli = (args: string[], options: TestOptions) => { return (input = '') => { - return execa(bin, args, { - cwd: options.cwd, - env: options.env, - input: input, - reject: false, + const result = x(bin, args, { + nodeOptions: { + cwd: options.cwd, + env: options.env, + }, }); + + result.process?.stdin?.write(input); + result.process?.stdin?.end(); + + return result; }; }; @@ -35,153 +40,175 @@ const fixBootstrap = (fixture: string) => fix.bootstrap(fixture, __dirname); test('should throw when called without [input]', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli([], {cwd})(); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})(); + await result; + expect(result.exitCode).toBe(1); }); test('should reprint input from stdin', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli([], {cwd})('foo: bar'); - expect(actual.stdout).toContain('foo: bar'); + const result = cli([], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toContain('foo: bar'); }); test('should produce success output with --verbose flag', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli(['--verbose'], {cwd})('type: bar'); - expect(actual.stdout).toContain('0 problems, 0 warnings'); - expect(actual.stderr).toEqual(''); + const result = cli(['--verbose'], {cwd})('type: bar'); + const output = await result; + expect(output.stdout.trim()).toContain('0 problems, 0 warnings'); + expect(output.stderr).toEqual(''); }); test('should produce last commit and success output with --verbose flag', async () => { const cwd = await gitBootstrap('fixtures/simple'); - await execa('git', ['add', 'commitlint.config.js'], {cwd}); - await execa('git', ['commit', '-m', '"test: this should work"'], {cwd}); - const actual = await cli(['--last', '--verbose'], {cwd})(); - expect(actual.stdout).toContain('0 problems, 0 warnings'); - expect(actual.stdout).toContain('test: this should work'); - expect(actual.stderr).toEqual(''); + await x('git', ['add', 'commitlint.config.js'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', '"test: this should work"'], { + nodeOptions: {cwd}, + }); + const result = cli(['--last', '--verbose'], {cwd})(); + const output = await result; + expect(output.stdout.trim()).toContain('0 problems, 0 warnings'); + expect(output.stdout.trim()).toContain('test: this should work'); + expect(output.stderr).toEqual(''); }); test('regression test for running with --last flag', async () => { const cwd = await gitBootstrap('fixtures/last-flag-regression'); - await execa('git', ['add', 'commitlint.config.js'], {cwd}); - await execa('git', ['commit', '-m', '"test: this should work"'], {cwd}); - const actual = await cli(['--last', '--verbose'], {cwd})(); - expect(actual.stdout).toContain('0 problems, 0 warnings'); - expect(actual.stdout).toContain('test: this should work'); - expect(actual.stderr).toEqual(''); + await x('git', ['add', 'commitlint.config.js'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', '"test: this should work"'], { + nodeOptions: {cwd}, + }); + const result = cli(['--last', '--verbose'], {cwd})(); + const output = await result; + expect(output.stdout.trim()).toContain('0 problems, 0 warnings'); + expect(output.stdout.trim()).toContain('test: this should work'); + expect(output.stderr).toEqual(''); }); test('should produce no output with --quiet flag', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli(['--quiet'], {cwd})('foo: bar'); - expect(actual.stdout).toEqual(''); - expect(actual.stderr).toEqual(''); + const result = cli(['--quiet'], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toEqual(''); + expect(output.stderr).toEqual(''); }); test('should produce no output with -q flag', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli(['-q'], {cwd})('foo: bar'); - expect(actual.stdout).toEqual(''); - expect(actual.stderr).toEqual(''); + const result = cli(['-q'], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toEqual(''); + expect(output.stderr).toEqual(''); }); test('should produce help for empty config', async () => { const cwd = await gitBootstrap('fixtures/empty'); - const actual = await cli([], {cwd})('foo: bar'); - expect(actual.stdout).toContain('Please add rules'); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toContain('Please add rules'); + expect(result.exitCode).toBe(1); }); test('should produce help for problems', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli([], {cwd})('foo: bar'); - expect(actual.stdout).toContain( + const result = cli([], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toContain( 'Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint' ); - expect(actual.exitCode).toBe(1); + expect(result.exitCode).toBe(1); }); test('should produce help for problems with correct helpurl', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli( + const result = cli( ['-H https://github.com/conventional-changelog/commitlint/#testhelpurl'], {cwd} )('foo: bar'); - expect(actual.stdout).toContain( + const output = await result; + expect(output.stdout.trim()).toContain( 'Get help: https://github.com/conventional-changelog/commitlint/#testhelpurl' ); - expect(actual.exitCode).toBe(1); + expect(result.exitCode).toBe(1); }); test('should fail for input from stdin without rules', async () => { const cwd = await gitBootstrap('fixtures/empty'); - const actual = await cli([], {cwd})('foo: bar'); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})('foo: bar'); + await result; + expect(result.exitCode).toBe(1); }); test('should succeed for input from stdin with rules', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli([], {cwd})('type: bar'); - expect(actual.exitCode).toBe(0); + const result = cli([], {cwd})('type: bar'); + await result; + expect(result.exitCode).toBe(0); }); test('should fail for input from stdin with rule from rc', async () => { const cwd = await gitBootstrap('fixtures/simple'); - const actual = await cli([], {cwd})('foo: bar'); - expect(actual.stdout).toContain('type must not be one of [foo]'); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toContain('type must not be one of [foo]'); + expect(result.exitCode).toBe(1); }); test('should work with --config option', async () => { const file = 'config/commitlint.config.js'; const cwd = await gitBootstrap('fixtures/specify-config-file'); - const actual = await cli(['--config', file], {cwd})('foo: bar'); - expect(actual.stdout).toContain('type must not be one of [foo]'); - expect(actual.exitCode).toBe(1); + const result = cli(['--config', file], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toContain('type must not be one of [foo]'); + expect(result.exitCode).toBe(1); }); test('should fail for input from stdin with rule from js', async () => { const cwd = await gitBootstrap('fixtures/extends-root'); - const actual = await cli(['--extends', './extended'], {cwd})('foo: bar'); - expect(actual.stdout).toContain('type must not be one of [foo]'); - expect(actual.exitCode).toBe(1); + const result = cli(['--extends', './extended'], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toContain('type must not be one of [foo]'); + expect(result.exitCode).toBe(1); }); test('should output help URL defined in config file', async () => { const cwd = await gitBootstrap('fixtures/help-url'); - const actual = await cli([], {cwd})('foo: bar'); - expect(actual.stdout).toContain('Get help: https://www.example.com/foo'); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toContain( + 'Get help: https://www.example.com/foo' + ); + expect(result.exitCode).toBe(1); }); test('should produce no error output with --quiet flag', async () => { const cwd = await gitBootstrap('fixtures/simple'); - const actual = await cli(['--quiet'], {cwd})('foo: bar'); - expect(actual.stdout).toEqual(''); - expect(actual.stderr).toEqual(''); - expect(actual.exitCode).toBe(1); + const result = cli(['--quiet'], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toEqual(''); + expect(output.stderr).toEqual(''); + expect(result.exitCode).toBe(1); }); test('should produce no error output with -q flag', async () => { const cwd = await gitBootstrap('fixtures/simple'); - const actual = await cli(['-q'], {cwd})('foo: bar'); - expect(actual.stdout).toEqual(''); - expect(actual.stderr).toEqual(''); - expect(actual.exitCode).toBe(1); + const result = cli(['-q'], {cwd})('foo: bar'); + const output = await result; + expect(output.stdout.trim()).toEqual(''); + expect(output.stderr).toEqual(''); + expect(result.exitCode).toBe(1); }); test('should work with husky commitmsg hook and git commit', async () => { const cwd = await gitBootstrap('fixtures/husky/integration'); await writePkg({husky: {hooks: {'commit-msg': `'${bin}' -e`}}}, {cwd}); - // await execa('npm', ['install'], {cwd}); // npm install is failing on windows machines - await execa('git', ['add', 'package.json'], {cwd}); - const commit = await execa( - 'git', - ['commit', '-m', '"test: this should work"'], - {cwd} - ); + // await x('npm', ['install'], { nodeOptions: {cwd}}); // npm install is failing on windows machines + await x('git', ['add', 'package.json'], {nodeOptions: {cwd}}); + const commit = await x('git', ['commit', '-m', '"test: this should work"'], { + nodeOptions: {cwd}, + }); expect(commit).toBeTruthy(); }); @@ -191,13 +218,11 @@ test('should work with husky commitmsg hook in sub packages', async () => { const cwd = path.join(upper, 'integration'); await writePkg({husky: {hooks: {'commit-msg': `'${bin}' -e`}}}, {cwd: upper}); - // await execa('npm', ['install'], {cwd}); // npm install is failing on windows machines - await execa('git', ['add', 'package.json'], {cwd}); - const commit = await execa( - 'git', - ['commit', '-m', '"test: this should work"'], - {cwd} - ); + // await x('npm', ['install'], { nodeOptions: {cwd}}); // npm install is failing on windows machines + await x('git', ['add', 'package.json'], {nodeOptions: {cwd}}); + const commit = await x('git', ['commit', '-m', '"test: this should work"'], { + nodeOptions: {cwd}, + }); expect(commit).toBeTruthy(); }); @@ -208,13 +233,11 @@ test('should work with husky via commitlint -e $GIT_PARAMS', async () => { {cwd} ); - // await execa('npm', ['install'], {cwd}); // npm install is failing on windows machines - await execa('git', ['add', 'package.json'], {cwd}); - const commit = await execa( - 'git', - ['commit', '-m', '"test: this should work"'], - {cwd} - ); + // await x('npm', ['install'], { nodeOptions: {cwd}}); // npm install is failing on windows machines + await x('git', ['add', 'package.json'], {nodeOptions: {cwd}}); + const commit = await x('git', ['commit', '-m', '"test: this should work"'], { + nodeOptions: {cwd}, + }); expect(commit).toBeTruthy(); }); @@ -225,13 +248,11 @@ test('should work with husky via commitlint -e %GIT_PARAMS%', async () => { {cwd} ); - // await execa('npm', ['install'], {cwd}); // npm install is failing on windows machines - await execa('git', ['add', 'package.json'], {cwd}); - const commit = await execa( - 'git', - ['commit', '-m', '"test: this should work"'], - {cwd} - ); + // await x('npm', ['install'], { nodeOptions: {cwd}}); // npm install is failing on windows machines + await x('git', ['add', 'package.json'], {nodeOptions: {cwd}}); + const commit = await x('git', ['commit', '-m', '"test: this should work"'], { + nodeOptions: {cwd}, + }); expect(commit).toBeTruthy(); }); @@ -242,13 +263,11 @@ test('should work with husky via commitlint -e $HUSKY_GIT_PARAMS', async () => { {cwd} ); - // await execa('npm', ['install'], {cwd}); // npm install is failing on windows machines - await execa('git', ['add', 'package.json'], {cwd}); - const commit = await execa( - 'git', - ['commit', '-m', '"test: this should work"'], - {cwd} - ); + // await x('npm', ['install'], { nodeOptions: {cwd}}); // npm install is failing on windows machines + await x('git', ['add', 'package.json'], {nodeOptions: {cwd}}); + const commit = await x('git', ['commit', '-m', '"test: this should work"'], { + nodeOptions: {cwd}, + }); expect(commit).toBeTruthy(); }); @@ -259,24 +278,23 @@ test('should work with husky via commitlint -e %HUSKY_GIT_PARAMS%', async () => {cwd} ); - // await execa('npm', ['install'], {cwd}); // npm install is failing on windows machines - await execa('git', ['add', 'package.json'], {cwd}); - const commit = await execa( - 'git', - ['commit', '-m', '"test: this should work"'], - {cwd} - ); + // await x('npm', ['install'], { nodeOptions: {cwd}}); // npm install is failing on windows machines + await x('git', ['add', 'package.json'], {nodeOptions: {cwd}}); + const commit = await x('git', ['commit', '-m', '"test: this should work"'], { + nodeOptions: {cwd}, + }); expect(commit).toBeTruthy(); }); test('should allow reading of environment variables for edit file, succeeding if valid', async () => { const cwd = await gitBootstrap('fixtures/simple'); await fs.writeFile(path.join(cwd, 'commit-msg-file'), 'foo'); - const actual = await cli(['--env', 'variable'], { + const result = cli(['--env', 'variable'], { cwd, env: {variable: 'commit-msg-file'}, })(); - expect(actual.exitCode).toBe(0); + await result; + expect(result.exitCode).toBe(0); }); test('should allow reading of environment variables for edit file, failing if invalid', async () => { @@ -285,74 +303,81 @@ test('should allow reading of environment variables for edit file, failing if in path.join(cwd, 'commit-msg-file'), 'foo: bar\n\nFoo bar bizz buzz.\n\nCloses #123.' ); - const actual = await cli(['--env', 'variable'], { + const result = cli(['--env', 'variable'], { cwd, env: {variable: 'commit-msg-file'}, })(); - expect(actual.exitCode).toBe(1); + await result; + expect(result.exitCode).toBe(1); }); test('should pick up parser preset and fail accordingly', async () => { const cwd = await gitBootstrap('fixtures/parser-preset'); - const actual = await cli(['--parser-preset', './parser-preset'], {cwd})( + const result = cli(['--parser-preset', './parser-preset'], {cwd})( 'type(scope): subject' ); - expect(actual.exitCode).toBe(1); - expect(actual.stdout).toContain('may not be empty'); + const output = await result; + expect(output.stdout.trim()).toContain('may not be empty'); + expect(result.exitCode).toBe(1); }); test('should pick up parser preset and succeed accordingly', async () => { const cwd = await gitBootstrap('fixtures/parser-preset'); - const actual = await cli(['--parser-preset', './parser-preset'], {cwd})( + const result = cli(['--parser-preset', './parser-preset'], {cwd})( '----type(scope): subject' ); - expect(actual.exitCode).toBe(0); + await result; + expect(result.exitCode).toBe(0); }); test('should pick up config from outside git repo and fail accordingly', async () => { const outer = await fixBootstrap('fixtures/outer-scope'); const cwd = await git.init(path.join(outer, 'inner-scope')); - const actual = await cli([], {cwd})('inner: bar'); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})('inner: bar'); + await result; + expect(result.exitCode).toBe(1); }); test('should pick up config from outside git repo and succeed accordingly', async () => { const outer = await fixBootstrap('fixtures/outer-scope'); const cwd = await git.init(path.join(outer, 'inner-scope')); - const actual = await cli([], {cwd})('outer: bar'); - expect(actual.exitCode).toBe(0); + const result = cli([], {cwd})('outer: bar'); + await result; + expect(result.exitCode).toBe(0); }); test('should pick up config from inside git repo with precedence and succeed accordingly', async () => { const outer = await fixBootstrap('fixtures/inner-scope'); const cwd = await git.init(path.join(outer, 'inner-scope')); - const actual = await cli([], {cwd})('inner: bar'); - expect(actual.exitCode).toBe(0); + const result = cli([], {cwd})('inner: bar'); + await result; + expect(result.exitCode).toBe(0); }); test('should pick up config from inside git repo with precedence and fail accordingly', async () => { const outer = await fixBootstrap('fixtures/inner-scope'); const cwd = await git.init(path.join(outer, 'inner-scope')); - const actual = await cli([], {cwd})('outer: bar'); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})('outer: bar'); + await result; + expect(result.exitCode).toBe(1); }); test('should handle --amend with signoff', async () => { const cwd = await gitBootstrap('fixtures/signoff'); await writePkg({husky: {hooks: {'commit-msg': `'${bin}' -e`}}}, {cwd}); - // await execa('npm', ['install'], {cwd}); // npm install is failing on windows machines - await execa('git', ['add', 'package.json'], {cwd}); - await execa( - 'git', - ['commit', '-m', '"test: this should work"', '--signoff'], - {cwd} - ); - const commit = await execa('git', ['commit', '--amend', '--no-edit'], {cwd}); + // await x('npm', ['install'], { nodeOptions: {cwd}}); // npm install is failing on windows machines + await x('git', ['add', 'package.json'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', '"test: this should work"', '--signoff'], { + nodeOptions: {cwd}, + }); + const commit = await x('git', ['commit', '--amend', '--no-edit'], { + nodeOptions: {cwd}, + }); expect(commit).toBeTruthy(); }, 10000); @@ -361,9 +386,10 @@ test('it uses parserOpts.commentChar when not using edit mode', async () => { const cwd = await gitBootstrap('fixtures/comment-char'); const input = 'header: foo\n$body\n'; - const actual = await cli([], {cwd})(input); - expect(actual.stdout).toContain('[body-empty]'); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})(input); + const output = await result; + expect(output.stdout.trim()).toContain('[body-empty]'); + expect(result.exitCode).toBe(1); }); test("it doesn't use parserOpts.commentChar when using edit mode", async () => { @@ -373,22 +399,26 @@ test("it doesn't use parserOpts.commentChar when using edit mode", async () => { 'header: foo\n\n$body\n' ); - const actual = await cli(['--edit', '.git/COMMIT_EDITMSG'], {cwd})(); - expect(actual.stdout).not.toContain('[body-empty]'); - expect(actual.exitCode).toBe(0); + const result = cli(['--edit', '.git/COMMIT_EDITMSG'], {cwd})(); + const output = await result; + expect(output.stdout.trim()).not.toContain('[body-empty]'); + expect(result.exitCode).toBe(0); }); test('it uses core.commentChar git config when using edit mode', async () => { const cwd = await gitBootstrap('fixtures/comment-char'); - await execa('git', ['config', '--local', 'core.commentChar', '$'], {cwd}); + await x('git', ['config', '--local', 'core.commentChar', '$'], { + nodeOptions: {cwd}, + }); await fs.writeFile( path.join(cwd, '.git', 'COMMIT_EDITMSG'), 'header: foo\n\n$body\n' ); - const actual = await cli(['--edit', '.git/COMMIT_EDITMSG'], {cwd})(); - expect(actual.stdout).toContain('[body-empty]'); - expect(actual.exitCode).toBe(1); + const result = cli(['--edit', '.git/COMMIT_EDITMSG'], {cwd})(); + const output = await result; + expect(output.stdout.trim()).toContain('[body-empty]'); + expect(result.exitCode).toBe(1); }); test('it falls back to # for core.commentChar when using edit mode', async () => { @@ -398,89 +428,96 @@ test('it falls back to # for core.commentChar when using edit mode', async () => 'header: foo\n\n#body\n' ); - const actual = await cli(['--edit', '.git/COMMIT_EDITMSG'], {cwd})(); - expect(actual.stdout).toContain('[body-empty]'); - expect(actual.stderr).toEqual(''); - expect(actual.exitCode).toBe(1); + const result = cli(['--edit', '.git/COMMIT_EDITMSG'], {cwd})(); + const output = await result; + expect(output.stdout.trim()).toContain('[body-empty]'); + expect(output.stderr).toEqual(''); + expect(result.exitCode).toBe(1); }); test('should handle linting with issue prefixes', async () => { const cwd = await gitBootstrap('fixtures/issue-prefixes'); - const actual = await cli([], {cwd})('foobar REF-1'); - expect(actual.exitCode).toBe(0); + const result = cli([], {cwd})('foobar REF-1'); + await result; + expect(result.exitCode).toBe(0); }, 10000); test('should print full commit message when input from stdin fails', async () => { const cwd = await gitBootstrap('fixtures/simple'); const input = 'foo: bar\n\nFoo bar bizz buzz.\n\nCloses #123.'; // output text in plain text so we can compare it - const actual = await cli(['--color=false'], {cwd})(input); - - expect(actual.stdout).toContain(input); - expect(actual.exitCode).toBe(1); + const result = cli(['--color=false'], {cwd})(input); + const output = await result; + expect(output.stdout.trim()).toContain(input); + expect(result.exitCode).toBe(1); }); test('should not print commit message fully or partially when input succeeds', async () => { const cwd = await gitBootstrap('fixtures/default'); const message = 'type: bar\n\nFoo bar bizz buzz.\n\nCloses #123.'; // output text in plain text so we can compare it - const actual = await cli(['--color=false'], {cwd})(message); - - expect(actual.stdout).not.toContain(message); - expect(actual.stdout).not.toContain(message.split('\n')[0]); - expect(actual.exitCode).toBe(0); + const result = cli(['--color=false'], {cwd})(message); + const output = await result; + expect(output.stdout.trim()).not.toContain(message); + expect(output.stdout.trim()).not.toContain(message.split('\n')[0]); + expect(result.exitCode).toBe(0); }); test('should fail for invalid formatters from configuration', async () => { const cwd = await gitBootstrap('fixtures/custom-formatter'); - const actual = await cli([], {cwd})('foo: bar'); - - expect(actual.stderr).toContain( + const result = cli([], {cwd})('foo: bar'); + const output = await result; + expect(output.stderr).toContain( 'Using format custom-formatter, but cannot find the module' ); - expect(actual.stdout).toEqual(''); - expect(actual.exitCode).toBe(1); + expect(output.stdout.trim()).toEqual(''); + expect(result.exitCode).toBe(1); }); test('should skip linting if message matches ignores config', async () => { const cwd = await gitBootstrap('fixtures/ignores'); - const actual = await cli([], {cwd})('WIP'); - expect(actual.exitCode).toBe(0); + const result = cli([], {cwd})('WIP'); + await result; + expect(result.exitCode).toBe(0); }); test('should not skip linting if message does not match ignores config', async () => { const cwd = await gitBootstrap('fixtures/ignores'); - const actual = await cli([], {cwd})('foo'); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})('foo'); + await result; + expect(result.exitCode).toBe(1); }); test('should not skip linting if defaultIgnores is false', async () => { const cwd = await gitBootstrap('fixtures/default-ignores-false'); - const actual = await cli([], {cwd})('fixup! foo: bar'); - expect(actual.exitCode).toBe(1); + const result = cli([], {cwd})('fixup! foo: bar'); + await result; + expect(result.exitCode).toBe(1); }); test('should skip linting if defaultIgnores is true', async () => { const cwd = await gitBootstrap('fixtures/default-ignores-true'); - const actual = await cli([], {cwd})('fixup! foo: bar'); - expect(actual.exitCode).toBe(0); + const result = cli([], {cwd})('fixup! foo: bar'); + await result; + expect(result.exitCode).toBe(0); }); test('should skip linting if defaultIgnores is unset', async () => { const cwd = await gitBootstrap('fixtures/default-ignores-unset'); - const actual = await cli([], {cwd})('fixup! foo: bar'); - expect(actual.exitCode).toBe(0); + const result = cli([], {cwd})('fixup! foo: bar'); + await result; + expect(result.exitCode).toBe(0); }); test('should fail for invalid formatters from flags', async () => { const cwd = await gitBootstrap('fixtures/custom-formatter'); - const actual = await cli(['--format', 'through-flag'], {cwd})('foo: bar'); - - expect(actual.stderr).toContain( + const result = cli(['--format', 'through-flag'], {cwd})('foo: bar'); + const output = await result; + expect(output.stderr).toContain( 'Using format through-flag, but cannot find the module' ); - expect(actual.stdout).toEqual(''); - expect(actual.exitCode).toBe(1); + expect(output.stdout.trim()).toEqual(''); + expect(result.exitCode).toBe(1); }); test('should work with absolute formatter path', async () => { @@ -489,12 +526,12 @@ test('should work with absolute formatter path', async () => { '../fixtures/custom-formatter/formatters/custom.js' ); const cwd = await gitBootstrap('fixtures/custom-formatter'); - const actual = await cli(['--format', formatterPath], {cwd})( + const result = cli(['--format', formatterPath], {cwd})( 'test: this should work' ); - - expect(actual.stdout).toContain('custom-formatter-ok'); - expect(actual.exitCode).toBe(0); + const output = await result; + expect(output.stdout.trim()).toContain('custom-formatter-ok'); + expect(result.exitCode).toBe(0); }); test('should work with relative formatter path', async () => { @@ -502,37 +539,41 @@ test('should work with relative formatter path', async () => { await gitBootstrap('fixtures/custom-formatter'), './formatters' ); - const actual = await cli(['--format', './custom.js'], {cwd})( + const result = cli(['--format', './custom.js'], {cwd})( 'test: this should work' ); - - expect(actual.stdout).toContain('custom-formatter-ok'); - expect(actual.exitCode).toBe(0); + const output = await result; + expect(output.stdout.trim()).toContain('custom-formatter-ok'); + expect(result.exitCode).toBe(0); }); test('strict: should exit with 3 on error', async () => { const cwd = await gitBootstrap('fixtures/warning'); - const actual = await cli(['--strict'], {cwd})('foo: abcdef'); - expect(actual.exitCode).toBe(3); + const result = cli(['--strict'], {cwd})('foo: abcdef'); + await result; + expect(result.exitCode).toBe(3); }); test('strict: should exit with 2 on warning', async () => { const cwd = await gitBootstrap('fixtures/warning'); - const actual = await cli(['--strict'], {cwd})('feat: abcdef'); - expect(actual.exitCode).toBe(2); + const result = cli(['--strict'], {cwd})('feat: abcdef'); + await result; + expect(result.exitCode).toBe(2); }); test('strict: should exit with 0 on success', async () => { const cwd = await gitBootstrap('fixtures/warning'); - const actual = await cli(['--strict'], {cwd})('feat: abc'); - expect(actual.exitCode).toBe(0); + const result = cli(['--strict'], {cwd})('feat: abc'); + await result; + expect(result.exitCode).toBe(0); }); test('should print help', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli(['--help'], {cwd})(); + const result = cli(['--help'], {cwd})(); + const output = await result; const version = require('../package.json').version; - const stdout = actual.stdout.replace(`@${version}`, '@dev'); + const stdout = output.stdout.trim().replace(`@${version}`, '@dev'); expect(stdout).toMatchInlineSnapshot(` "@commitlint/cli@dev - Lint your commit messages @@ -565,16 +606,18 @@ test('should print help', async () => { test('should print version', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli(['--version'], {cwd})(); - expect(actual.stdout).toMatch('@commitlint/cli@'); + const result = cli(['--version'], {cwd})(); + const output = await result; + expect(output.stdout.trim()).toMatch('@commitlint/cli@'); }); describe('should print config', () => { test('should print config when flag is present but without value', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli(['--print-config', 'text', '--no-color'], {cwd})(); - - const stdout = actual.stdout + const result = cli(['--print-config', 'text', '--no-color'], {cwd})(); + const output = await result; + const stdout = output.stdout + .trim() .replace(/^{[^\n]/g, '{\n ') .replace(/[^\n]}$/g, '\n}') .replace(/(helpUrl:)\n[ ]+/, '$1 '); @@ -595,9 +638,10 @@ describe('should print config', () => { test('should print config when flag has `text` value', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli(['--print-config=text', '--no-color'], {cwd})(); - - const stdout = actual.stdout + const result = cli(['--print-config=text', '--no-color'], {cwd})(); + const output = await result; + const stdout = output.stdout + .trim() .replace(/^{[^\n]/g, '{\n ') .replace(/[^\n]}$/g, '\n}') .replace(/(helpUrl:)\n[ ]+/, '$1 '); @@ -618,9 +662,9 @@ describe('should print config', () => { test('should print config when flag has `json` value', async () => { const cwd = await gitBootstrap('fixtures/default'); - const actual = await cli(['--print-config=json', '--no-color'], {cwd})(); - - expect(actual.stdout).toMatchInlineSnapshot( + const result = cli(['--print-config=json', '--no-color'], {cwd})(); + const output = await result; + expect(output.stdout.trim()).toMatchInlineSnapshot( `"{"extends":[],"formatter":"@commitlint/format","plugins":{},"rules":{"type-enum":[2,"never",["foo"]]},"helpUrl":"https://github.com/conventional-changelog/commitlint/#what-is-commitlint","prompt":{}}"` ); }); diff --git a/@commitlint/cli/src/cli.ts b/@commitlint/cli/src/cli.ts index 5375fa6f2e..a74e18a98b 100644 --- a/@commitlint/cli/src/cli.ts +++ b/@commitlint/cli/src/cli.ts @@ -15,7 +15,7 @@ import type { UserConfig, } from '@commitlint/types'; import type {Options} from 'conventional-commits-parser'; -import {execa, type ExecaError} from 'execa'; +import {x} from 'tinyexec'; import yargs, {type Arguments} from 'yargs'; import {CliFlags} from './types.js'; @@ -297,20 +297,18 @@ async function main(args: MainArgs): Promise { // If reading from `.git/COMMIT_EDIT_MSG`, strip comments using // core.commentChar from git configuration, falling back to '#'. if (flags.edit) { - try { - const {stdout} = await execa('git', ['config', 'core.commentChar']); - opts.parserOpts.commentChar = stdout.trim() || gitDefaultCommentChar; - } catch (e) { - const execaError = e as ExecaError; - // git config returns exit code 1 when the setting is unset, - // don't warn in this case. - if (!execaError.failed || execaError.exitCode !== 1) { - console.warn( - 'Could not determine core.commentChar git configuration', - e - ); - } + const result = x('git', ['config', 'core.commentChar']); + const output = await result; + + if (result.exitCode && result.exitCode > 1) { + console.warn( + 'Could not determine core.commentChar git configuration', + output.stderr + ); opts.parserOpts.commentChar = gitDefaultCommentChar; + } else { + opts.parserOpts.commentChar = + output.stdout.trim() || gitDefaultCommentChar; } } diff --git a/@commitlint/prompt-cli/cli.js b/@commitlint/prompt-cli/cli.js index 36f45ef90e..155819526e 100755 --- a/@commitlint/prompt-cli/cli.js +++ b/@commitlint/prompt-cli/cli.js @@ -1,7 +1,7 @@ #!/usr/bin/env node import {prompter} from '@commitlint/prompt'; -import {execa} from 'execa'; import inquirer from 'inquirer'; +import {x} from 'tinyexec'; main().catch((err) => { setTimeout(() => { @@ -23,11 +23,11 @@ function main() { } function isStageEmpty() { - return execa('git', ['diff', '--cached']).then((r) => r.stdout === ''); + return x('git', ['diff', '--cached']).then((r) => r.stdout === ''); } function commit(message) { - const c = execa('git', ['commit', '-m', message]); - c.stdout.pipe(process.stdout); - c.stderr.pipe(process.stderr); + const result = x('git', ['commit', '-m', message]); + result.process.stdout.pipe(process.stdout); + result.process.stderr.pipe(process.stderr); } diff --git a/@commitlint/prompt-cli/cli.test.js b/@commitlint/prompt-cli/cli.test.js index 4414ace62c..0dc4085f87 100644 --- a/@commitlint/prompt-cli/cli.test.js +++ b/@commitlint/prompt-cli/cli.test.js @@ -1,7 +1,7 @@ import {test, expect} from 'vitest'; import {createRequire} from 'module'; import {git} from '@commitlint/test'; -import {execa} from 'execa'; +import {x} from 'tinyexec'; const require = createRequire(import.meta.url); @@ -9,12 +9,17 @@ const bin = require.resolve('./cli.js'); const cli = (args, options) => { return (input = '') => { - return execa(bin, args, { - cwd: options.cwd, - env: options.env, - input: input, - reject: false, + const result = x(bin, args, { + nodeOptions: { + cwd: options.cwd, + env: options.env, + }, }); + + result.process.stdin.write(input); + result.process.stdin.end(); + + return result; }; }; diff --git a/@commitlint/prompt-cli/package.json b/@commitlint/prompt-cli/package.json index a22d0a8952..bc2d2aba75 100644 --- a/@commitlint/prompt-cli/package.json +++ b/@commitlint/prompt-cli/package.json @@ -39,8 +39,8 @@ }, "dependencies": { "@commitlint/prompt": "^19.4.1", - "execa": "^8.0.1", - "inquirer": "^9.2.15" + "inquirer": "^9.2.15", + "tinyexec": "^0.3.0" }, "gitHead": "70f7f4688b51774e7ac5e40e896cdaa3f132b2bc" } diff --git a/@commitlint/read/package.json b/@commitlint/read/package.json index 73f0d6f20b..643759574f 100644 --- a/@commitlint/read/package.json +++ b/@commitlint/read/package.json @@ -44,9 +44,9 @@ "dependencies": { "@commitlint/top-level": "^19.0.0", "@commitlint/types": "^19.0.3", - "execa": "^8.0.1", "git-raw-commits": "^4.0.0", - "minimist": "^1.2.8" + "minimist": "^1.2.8", + "tinyexec": "^0.3.0" }, "gitHead": "70f7f4688b51774e7ac5e40e896cdaa3f132b2bc" } diff --git a/@commitlint/read/src/read.test.ts b/@commitlint/read/src/read.test.ts index dc15042916..9d7fa9a12e 100644 --- a/@commitlint/read/src/read.test.ts +++ b/@commitlint/read/src/read.test.ts @@ -2,7 +2,7 @@ import {test, expect} from 'vitest'; import fs from 'fs/promises'; import path from 'path'; import {git} from '@commitlint/test'; -import {execa} from 'execa'; +import {x} from 'tinyexec'; import read from './read.js'; @@ -20,8 +20,8 @@ test('get edit commit message from git root', async () => { const cwd: string = await git.bootstrap(); await fs.writeFile(path.join(cwd, 'alpha.txt'), 'alpha'); - await execa('git', ['add', '.'], {cwd}); - await execa('git', ['commit', '-m', 'alpha'], {cwd}); + await x('git', ['add', '.'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', 'alpha'], {nodeOptions: {cwd}}); const expected = ['alpha\n\n']; const actual = await read({edit: true, cwd}); expect(actual).toEqual(expected); @@ -30,10 +30,10 @@ test('get edit commit message from git root', async () => { test('get history commit messages', async () => { const cwd: string = await git.bootstrap(); await fs.writeFile(path.join(cwd, 'alpha.txt'), 'alpha'); - await execa('git', ['add', 'alpha.txt'], {cwd}); - await execa('git', ['commit', '-m', 'alpha'], {cwd}); - await execa('git', ['rm', 'alpha.txt'], {cwd}); - await execa('git', ['commit', '-m', 'remove alpha'], {cwd}); + await x('git', ['add', 'alpha.txt'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', 'alpha'], {nodeOptions: {cwd}}); + await x('git', ['rm', 'alpha.txt'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', 'remove alpha'], {nodeOptions: {cwd}}); const expected = ['remove alpha\n\n', 'alpha\n\n']; const actual = await read({cwd}); @@ -45,8 +45,8 @@ test('get edit commit message from git subdirectory', async () => { await fs.mkdir(path.join(cwd, 'beta')); await fs.writeFile(path.join(cwd, 'beta/beta.txt'), 'beta'); - await execa('git', ['add', '.'], {cwd}); - await execa('git', ['commit', '-m', 'beta'], {cwd}); + await x('git', ['add', '.'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', 'beta'], {nodeOptions: {cwd}}); const expected = ['beta\n\n']; const actual = await read({edit: true, cwd}); @@ -59,14 +59,14 @@ test('get edit commit message while skipping first commit', async () => { await fs.writeFile(path.join(cwd, 'beta/beta.txt'), 'beta'); await fs.writeFile(path.join(cwd, 'alpha.txt'), 'alpha'); - await execa('git', ['add', 'alpha.txt'], {cwd}); - await execa('git', ['commit', '-m', 'alpha'], {cwd}); + await x('git', ['add', 'alpha.txt'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', 'alpha'], {nodeOptions: {cwd}}); await fs.writeFile(path.join(cwd, 'beta.txt'), 'beta'); - await execa('git', ['add', 'beta.txt'], {cwd}); - await execa('git', ['commit', '-m', 'beta'], {cwd}); + await x('git', ['add', 'beta.txt'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', 'beta'], {nodeOptions: {cwd}}); await fs.writeFile(path.join(cwd, 'gamma.txt'), 'gamma'); - await execa('git', ['add', 'gamma.txt'], {cwd}); - await execa('git', ['commit', '-m', 'gamma'], {cwd}); + await x('git', ['add', 'gamma.txt'], {nodeOptions: {cwd}}); + await x('git', ['commit', '-m', 'gamma'], {nodeOptions: {cwd}}); const expected = ['beta\n\n']; const actual = await read({from: 'HEAD~2', cwd, gitLogArgs: '--skip 1'}); @@ -76,9 +76,15 @@ test('get edit commit message while skipping first commit', async () => { test('should only read the last commit', async () => { const cwd: string = await git.bootstrap(); - await execa('git', ['commit', '--allow-empty', '-m', 'commit Z'], {cwd}); - await execa('git', ['commit', '--allow-empty', '-m', 'commit Y'], {cwd}); - await execa('git', ['commit', '--allow-empty', '-m', 'commit X'], {cwd}); + await x('git', ['commit', '--allow-empty', '-m', 'commit Z'], { + nodeOptions: {cwd}, + }); + await x('git', ['commit', '--allow-empty', '-m', 'commit Y'], { + nodeOptions: {cwd}, + }); + await x('git', ['commit', '--allow-empty', '-m', 'commit X'], { + nodeOptions: {cwd}, + }); const result = await read({cwd, last: true}); @@ -88,14 +94,18 @@ test('should only read the last commit', async () => { test('should read commits from the last annotated tag', async () => { const cwd: string = await git.bootstrap(); - await execa( - 'git', - ['commit', '--allow-empty', '-m', 'chore: release v1.0.0'], - {cwd} - ); - await execa('git', ['tag', 'v1.0.0', '--annotate', '-m', 'v1.0.0'], {cwd}); - await execa('git', ['commit', '--allow-empty', '-m', 'commit 1'], {cwd}); - await execa('git', ['commit', '--allow-empty', '-m', 'commit 2'], {cwd}); + await x('git', ['commit', '--allow-empty', '-m', 'chore: release v1.0.0'], { + nodeOptions: {cwd}, + }); + await x('git', ['tag', 'v1.0.0', '--annotate', '-m', 'v1.0.0'], { + nodeOptions: {cwd}, + }); + await x('git', ['commit', '--allow-empty', '-m', 'commit 1'], { + nodeOptions: {cwd}, + }); + await x('git', ['commit', '--allow-empty', '-m', 'commit 2'], { + nodeOptions: {cwd}, + }); const result = await read({cwd, fromLastTag: true}); @@ -105,14 +115,18 @@ test('should read commits from the last annotated tag', async () => { test('should read commits from the last lightweight tag', async () => { const cwd: string = await git.bootstrap(); - await execa( + await x( 'git', ['commit', '--allow-empty', '-m', 'chore: release v9.9.9-alpha.1'], - {cwd} + {nodeOptions: {cwd}} ); - await execa('git', ['tag', 'v9.9.9-alpha.1'], {cwd}); - await execa('git', ['commit', '--allow-empty', '-m', 'commit A'], {cwd}); - await execa('git', ['commit', '--allow-empty', '-m', 'commit B'], {cwd}); + await x('git', ['tag', 'v9.9.9-alpha.1'], {nodeOptions: {cwd}}); + await x('git', ['commit', '--allow-empty', '-m', 'commit A'], { + nodeOptions: {cwd}, + }); + await x('git', ['commit', '--allow-empty', '-m', 'commit B'], { + nodeOptions: {cwd}, + }); const result = await read({cwd, fromLastTag: true}); @@ -122,9 +136,15 @@ test('should read commits from the last lightweight tag', async () => { test('should not read any commits when there are no tags', async () => { const cwd: string = await git.bootstrap(); - await execa('git', ['commit', '--allow-empty', '-m', 'commit 7'], {cwd}); - await execa('git', ['commit', '--allow-empty', '-m', 'commit 8'], {cwd}); - await execa('git', ['commit', '--allow-empty', '-m', 'commit 9'], {cwd}); + await x('git', ['commit', '--allow-empty', '-m', 'commit 7'], { + nodeOptions: {cwd}, + }); + await x('git', ['commit', '--allow-empty', '-m', 'commit 8'], { + nodeOptions: {cwd}, + }); + await x('git', ['commit', '--allow-empty', '-m', 'commit 9'], { + nodeOptions: {cwd}, + }); const result = await read({cwd, fromLastTag: true}); diff --git a/@commitlint/read/src/read.ts b/@commitlint/read/src/read.ts index 4d483aa474..0e5c422720 100644 --- a/@commitlint/read/src/read.ts +++ b/@commitlint/read/src/read.ts @@ -4,7 +4,7 @@ import type {GitOptions} from 'git-raw-commits'; import {getHistoryCommits} from './get-history-commits.js'; import {getEditCommit} from './get-edit-commit.js'; -import {execa} from 'execa'; +import {x} from 'tinyexec'; interface GetCommitMessageOptions { cwd?: string; @@ -28,12 +28,12 @@ export default async function getCommitMessages( } if (last) { - const gitCommandResult = await execa( + const gitCommandResult = await x( 'git', ['log', '-1', '--pretty=format:%B'], - {cwd} + {nodeOptions: {cwd}} ); - let output = gitCommandResult.stdout; + let output = gitCommandResult.stdout.trim(); // strip output of extra quotation marks ("") if (output[0] == '"' && output[output.length - 1] == '"') output = output.slice(1, -1); @@ -41,7 +41,7 @@ export default async function getCommitMessages( } if (!from && fromLastTag) { - const {stdout} = await execa( + const output = await x( 'git', [ 'describe', @@ -51,8 +51,9 @@ export default async function getCommitMessages( '--long', '--tags', ], - {cwd} + {nodeOptions: {cwd}} ); + const stdout = output.stdout.trim(); if (stdout.length === 40) { // Hash only means no last tag. Use that as the from ref which diff --git a/@commitlint/rules/package.json b/@commitlint/rules/package.json index 3421e4c510..8b1322ce40 100644 --- a/@commitlint/rules/package.json +++ b/@commitlint/rules/package.json @@ -46,8 +46,7 @@ "@commitlint/ensure": "^19.0.3", "@commitlint/message": "^19.0.0", "@commitlint/to-lines": "^19.0.0", - "@commitlint/types": "^19.0.3", - "execa": "^8.0.1" + "@commitlint/types": "^19.0.3" }, "gitHead": "70f7f4688b51774e7ac5e40e896cdaa3f132b2bc" } diff --git a/@commitlint/rules/src/trailer-exists.ts b/@commitlint/rules/src/trailer-exists.ts index 4bf2427989..8ca7f7ae56 100644 --- a/@commitlint/rules/src/trailer-exists.ts +++ b/@commitlint/rules/src/trailer-exists.ts @@ -1,4 +1,4 @@ -import {execaSync} from 'execa'; +import {spawnSync} from 'child_process'; import message from '@commitlint/message'; import toLines from '@commitlint/to-lines'; import {SyncRule} from '@commitlint/types'; @@ -8,11 +8,13 @@ export const trailerExists: SyncRule = ( when = 'always', value = '' ) => { - const trailers = execaSync('git', ['interpret-trailers', '--parse'], { + const trailers = spawnSync('git', ['interpret-trailers', '--parse'], { input: parsed.raw || '', }).stdout; - const matches = toLines(trailers).filter((ln) => ln.startsWith(value)).length; + const matches = toLines(trailers.toString()).filter((ln) => + ln.startsWith(value) + ).length; const negated = when === 'never'; const hasTrailer = matches > 0; diff --git a/@commitlint/travis-cli/package.json b/@commitlint/travis-cli/package.json index 537e392b3c..c3ce50a75e 100644 --- a/@commitlint/travis-cli/package.json +++ b/@commitlint/travis-cli/package.json @@ -44,7 +44,7 @@ }, "dependencies": { "@commitlint/cli": "^19.4.1", - "execa": "^8.0.1" + "tinyexec": "^0.3.0" }, "gitHead": "70f7f4688b51774e7ac5e40e896cdaa3f132b2bc" } diff --git a/@commitlint/travis-cli/src/cli.test.ts b/@commitlint/travis-cli/src/cli.test.ts index 59795d5513..c6d05e881f 100644 --- a/@commitlint/travis-cli/src/cli.test.ts +++ b/@commitlint/travis-cli/src/cli.test.ts @@ -1,10 +1,12 @@ +import {SpawnOptions} from 'node:child_process'; + import {test, expect} from 'vitest'; import {createRequire} from 'module'; import path from 'path'; import {fileURLToPath} from 'url'; import {git} from '@commitlint/test'; -import {Options, execa} from 'execa'; +import {x} from 'tinyexec'; const require = createRequire(import.meta.url); @@ -27,22 +29,8 @@ const validBaseEnv = { TRAVIS_PULL_REQUEST_SLUG: 'TRAVIS_PULL_REQUEST_SLUG', }; -const cli = async (config: Options = {}, args: string[] = []) => { - try { - return await execa(bin, args, config); - } catch (err: any) { - if ( - typeof err.stdout !== 'undefined' && - typeof err.stderr !== 'undefined' - ) { - throw new Error([err.stdout, err.stderr].join('\n')); - } else { - throw new Error( - `An unknown error occured while running '${bin} ${args.join(' ')}'` - ); - } - } -}; +const cli = async (nodeOptions: SpawnOptions = {}, args: string[] = []) => + x(bin, args, {nodeOptions}); test('should throw when not on travis ci', async () => { const env = { @@ -50,7 +38,8 @@ test('should throw when not on travis ci', async () => { TRAVIS: 'false', }; - await expect(cli({env})).rejects.toThrow( + const output = await cli({env}); + expect(output.stderr).toContain( '@commitlint/travis-cli is intended to be used on Travis CI' ); }); @@ -61,7 +50,8 @@ test('should throw when on travis ci, but env vars are missing', async () => { CI: 'true', }; - await expect(cli({env})).rejects.toThrow( + const output = await cli({env}); + expect(output.stderr).toContain( 'TRAVIS_COMMIT, TRAVIS_COMMIT_RANGE, TRAVIS_EVENT_TYPE, TRAVIS_REPO_SLUG, TRAVIS_PULL_REQUEST_SLUG' ); }); diff --git a/@commitlint/travis-cli/src/cli.ts b/@commitlint/travis-cli/src/cli.ts index 7d13a28c69..12ce0cf9f8 100644 --- a/@commitlint/travis-cli/src/cli.ts +++ b/@commitlint/travis-cli/src/cli.ts @@ -1,6 +1,8 @@ +import {SpawnOptions} from 'node:child_process'; + import {createRequire} from 'module'; -import {Options, execa} from 'execa'; +import {x} from 'tinyexec'; const require = createRequire(import.meta.url); @@ -24,7 +26,7 @@ const RANGE = process.env.TRAVIS_COMMIT_RANGE; const IS_PR = process.env.TRAVIS_EVENT_TYPE === 'pull_request'; main().catch((err) => { - console.log(err); + console.error(err); process.exit(1); }); @@ -53,14 +55,16 @@ async function main() { await lint(['--from', start, '--to', end, ...args]); } else { const input = await log(COMMIT); - await lint(args, {input}); + await lint(args, {}, input); } } -async function git(args: string[], options: Options = {}) { - return execa(GIT, args, { - stdio: 'inherit', - ...options, +async function git(args: string[], nodeOptions: SpawnOptions = {}) { + return x(GIT, args, { + nodeOptions: { + stdio: 'inherit', + ...nodeOptions, + }, }); } @@ -76,11 +80,22 @@ async function isClean() { return !(result.stdout && result.stdout.trim()); } -async function lint(args: string[], options: Options = {}) { - return execa(COMMITLINT, args, { - stdio: ['pipe', 'inherit', 'inherit'], - ...options, +async function lint( + args: string[], + nodeOptions: SpawnOptions = {}, + input: string = '' +) { + const result = x(COMMITLINT, args, { + nodeOptions: { + stdio: ['pipe', 'inherit', 'inherit'], + ...nodeOptions, + }, }); + + result.process?.stdin?.write(input); + result.process?.stdin?.end(); + + return result; } async function log(hash: string) { diff --git a/@packages/test/package.json b/@packages/test/package.json index 36ac0715c1..31bed37d25 100644 --- a/@packages/test/package.json +++ b/@packages/test/package.json @@ -34,10 +34,10 @@ "dependencies": { "@types/fs-extra": "^11.0.3", "@types/tmp": "^0.2.5", - "execa": "^8.0.1", "fs-extra": "^11.0.0", "pkg-dir": "^8.0.0", "resolve-pkg": "^2.0.0", + "tinyexec": "^0.3.0", "tmp": "^0.2.1" }, "gitHead": "71f0194f33943954a8dac1c458be47e5049717cd" diff --git a/@packages/test/src/git.ts b/@packages/test/src/git.ts index 3ea0c5863f..e58c7f46ed 100644 --- a/@packages/test/src/git.ts +++ b/@packages/test/src/git.ts @@ -1,4 +1,4 @@ -import {execa} from 'execa'; +import {x} from 'tinyexec'; import * as fix from './fix.js'; @@ -17,24 +17,26 @@ export async function clone( ) { const cwd = await fix.bootstrap(undefined, directory); - await execa(gitCommand, ['clone', ...args, source, cwd]); + await x(gitCommand, ['clone', ...args, source, cwd]); await setup(cwd, gitCommand); return cwd; } export async function init(cwd: string) { - await execa('git', ['init', cwd]); + await x('git', ['init', cwd]); await setup(cwd); return cwd; } async function setup(cwd: string, gitCommand = 'git') { try { - await execa(gitCommand, ['config', 'user.name', 'ava'], {cwd}); - await execa(gitCommand, ['config', 'user.email', 'test@example.com'], { - cwd, + await x(gitCommand, ['config', 'user.name', 'ava'], {nodeOptions: {cwd}}); + await x(gitCommand, ['config', 'user.email', 'test@example.com'], { + nodeOptions: {cwd}, + }); + await x(gitCommand, ['config', 'commit.gpgsign', 'false'], { + nodeOptions: {cwd}, }); - await execa(gitCommand, ['config', 'commit.gpgsign', 'false'], {cwd}); } catch (err: any) { if (typeof err === 'object' && typeof err.message === 'object') { console.warn(`git config in ${cwd} failed`, err.message); diff --git a/@packages/utils/dep-check.js b/@packages/utils/dep-check.js index 02269e8607..cc9c3bdfe3 100755 --- a/@packages/utils/dep-check.js +++ b/@packages/utils/dep-check.js @@ -1,6 +1,6 @@ #!/usr/bin/env node import path from 'path'; -import {execa} from 'execa'; +import {x} from 'tinyexec'; const cwd = process.cwd(); @@ -14,7 +14,7 @@ function main() { } function check(args) { - return execa('dependency-check', args, {cwd}); + return x('dependency-check', args, {nodeOptions: {cwd}}); } main().then((args) => { diff --git a/@packages/utils/package.json b/@packages/utils/package.json index 048828a8ea..562f946c84 100644 --- a/@packages/utils/package.json +++ b/@packages/utils/package.json @@ -44,10 +44,10 @@ "@types/yargs": "^17.0.29" }, "dependencies": { - "execa": "^8.0.1", "read-pkg": "^9.0.1", "require-from-string": "^2.0.2", "tar-fs": "^3.0.5", + "tinyexec": "^0.3.0", "tmp": "^0.2.1", "yargs": "^17.0.0" }, diff --git a/@packages/utils/pkg-check.js b/@packages/utils/pkg-check.js index dec4081c6d..8d6b16df32 100755 --- a/@packages/utils/pkg-check.js +++ b/@packages/utils/pkg-check.js @@ -2,10 +2,10 @@ import path from 'path'; import fs from 'fs'; -import {execa} from 'execa'; import readPkg from 'read-pkg'; import requireFromString from 'require-from-string'; import tar from 'tar-fs'; +import {x} from 'tinyexec'; import tmp from 'tmp'; import yargs from 'yargs'; import zlib from 'zlib'; @@ -146,7 +146,9 @@ async function getTarballFiles(source, options) { }); const cwd = tmpDir.name; const tarball = path.join(cwd, 'test-archive.tgz'); - await execa('yarn', ['pack', '--filename', tarball], {cwd: source}); + await x('yarn', ['pack', '--filename', tarball], { + nodeOptions: {cwd: source}, + }); return getArchiveFiles(tarball, options); } diff --git a/yarn.lock b/yarn.lock index c51bbf0fd5..aa7501ff56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8549,6 +8549,11 @@ tinybench@^2.8.0: resolved "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== +tinyexec@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz#ed60cfce19c17799d4a241e06b31b0ec2bee69e6" + integrity sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg== + tinypool@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz#a68965218e04f4ad9de037d2a1cd63cda9afb238"