From 66a23d98352b672621b1713be7da3f3f6bee67d4 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sun, 5 Jan 2025 14:43:00 +0100 Subject: [PATCH] chore: migrate CLI execution to execa --- README.md | 22 ++-- benchmark/compare-bench-results.bench-test.ts | 43 +++++++- benchmark/multiple-file-replace.bench.ts | 40 +------ bin/cli.mjs | 2 +- bin/cli.spec.ts | 103 +++++++++--------- package.json | 3 +- pnpm-lock.yaml | 88 +++++++++++++++ 7 files changed, 199 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 2f058c0..5e52ca0 100644 --- a/README.md +++ b/README.md @@ -227,17 +227,17 @@ const result = sync({ ### input as glob pattern [40 files] -| Rank | Library | Average latency [ms] | Difference percentage (comparing to best average latency) | -| --- | --- | --- | --- | -| 1 | @frsource/frs-replace (async) | 0.63 ± 2.61% | +0.00% | -| 2 | @frsource/frs-replace (sync) | 0.69 ± 0.84% | +9.48% | -| 3 | replace-in-file (async) | 0.80 ± 1.14% | +28.41% | -| 4 | replace-in-file (async) | 0.88 ± 1.49% | +40.16% | +| Rank | Library | Average latency [ms] | Difference percentage (comparing to best average latency) | +| ---- | ----------------------------- | -------------------- | ----------------------------------------------------------------------------- | +| 1 | @frsource/frs-replace (async) | 0.63 ± 1.11% | +0.00% | +| 2 | @frsource/frs-replace (sync) | 0.72 ± 0.76% | +14.38% | +| 3 | replace-in-file (async) | 0.85 ± 0.74% | +33.88% | +| 4 | replace-in-file (async) | 0.92 ± 1.36% | +44.73% | ### input & replacement as strings -| Rank | Library | Average latency [ms] | Difference percentage (comparing to best average latency) | -| --- | --- | --- | --- | -| 1 | @frsource/frs-replace (sync) | 0.00 ± 0.31% | +0.00% | -| 2 | @frsource/frs-replace (async) | 0.01 ± 0.38% | +13.63% | -| 3 | replaceString | 0.03 ± 0.73% | +560.57% | +| Rank | Library | Average latency [ms] | Difference percentage (comparing to best average latency) | +| ---- | ----------------------------- | -------------------- | ----------------------------------------------------------------------------- | +| 1 | @frsource/frs-replace (sync) | 0.01 ± 0.33% | +0.00% | +| 2 | @frsource/frs-replace (async) | 0.01 ± 0.32% | +12.15% | +| 3 | replaceString | 0.03 ± 0.72% | +517.31% | diff --git a/benchmark/compare-bench-results.bench-test.ts b/benchmark/compare-bench-results.bench-test.ts index b92823c..91284e7 100644 --- a/benchmark/compare-bench-results.bench-test.ts +++ b/benchmark/compare-bench-results.bench-test.ts @@ -1,6 +1,9 @@ -import { expect, test } from 'vitest'; +import { afterAll, expect, test } from 'vitest'; +import { readFileSync, writeFileSync } from 'fs'; import results from './results.json'; +afterAll(updateReadmeBenchmarkContent); + results.files[0].groups.forEach(({ fullName, benchmarks }) => { const testName = fullName.split(' > ').slice(1).join(' > '); test(`${testName} > frs-replace should be the fastest framework`, () => { @@ -8,3 +11,41 @@ results.files[0].groups.forEach(({ fullName, benchmarks }) => { expect(sortedResults[0].name).toContain('frs-replace'); }); }); + +function updateReadmeBenchmarkContent() { + let benchResultsContent = ''; + for (const testGroup of results.files[0].groups) { + const groupName = testGroup.fullName.split(' > ').slice(1).join(' > '); + benchResultsContent += + '\n### ' + + groupName + + '\n\n' + + '| Rank | Library | Average latency [ms] | Difference percentage (comparing to best average latency) |\n' + + '| --- | --- | --- | --- |\n' + + testGroup.benchmarks + .sort((a, b) => a.rank - b.rank) + .reduce( + (p, v) => + p + + '| ' + + v.rank + + ' | ' + + v.name + + ' | ' + + `${v.mean.toFixed(2)} \xb1 ${v.rme.toFixed(2)}%` + + ' | ' + + `+${((v.mean / testGroup.benchmarks[0].mean - 1) * 100).toFixed(2)}%` + + ' |\n', + '', + ); + } + + const readmeContent = readFileSync('./README.md') + .toString() + .replace( + /(##\s:chart_with_upwards_trend:\sBenchmarks)[\s\S]*?(?:$|(?:\s##\s))/, + `$1\n\n> Tested on Node ${process.version}.\n${benchResultsContent}`, + ); + + writeFileSync('./README.md', readmeContent); +} diff --git a/benchmark/multiple-file-replace.bench.ts b/benchmark/multiple-file-replace.bench.ts index 19b1a50..baeab54 100644 --- a/benchmark/multiple-file-replace.bench.ts +++ b/benchmark/multiple-file-replace.bench.ts @@ -2,7 +2,7 @@ import { join } from 'path'; import { FileResult, dirSync, file } from 'tmp-promise'; import { afterAll, beforeAll, bench, describe } from 'vitest'; import { sync } from 'fast-glob'; -import { readFileSync, unlinkSync, writeFileSync } from 'fs'; +import { unlinkSync } from 'fs'; import { appendFile } from 'fs/promises'; const regex = /'^[adjox]/gm; @@ -51,44 +51,6 @@ beforeAll(async () => { afterAll(async () => { inputs.forEach((input) => input.cleanup); inputs = []; - - const { default: benchResults } = await import('./results.json'); - - let benchResultsContent = ''; - for (const testGroup of benchResults.files[0].groups) { - const groupName = testGroup.fullName.split(' > ').slice(1).join(' > '); - benchResultsContent += - '\n### ' + - groupName + - '\n\n' + - '| Rank | Library | Average latency [ms] | Difference percentage (comparing to best average latency) |\n' + - '| --- | --- | --- | --- |\n' + - testGroup.benchmarks - .sort((a, b) => a.rank - b.rank) - .reduce( - (p, v) => - p + - '| ' + - v.rank + - ' | ' + - v.name + - ' | ' + - `${v.mean.toFixed(2)} \xb1 ${v.rme.toFixed(2)}%` + - ' | ' + - `+${((v.mean / testGroup.benchmarks[0].mean - 1) * 100).toFixed(2)}%` + - ' |\n', - '', - ); - } - - const readmeContent = readFileSync('./README.md') - .toString() - .replace( - /(##\s:chart_with_upwards_trend:\sBenchmarks)[\s\S]*?(?:$|(?:\s##\s))/, - `$1\n\n> Tested on Node ${process.version}.\n${benchResultsContent}`, - ); - - writeFileSync('./README.md', readmeContent); }); describe(`input as glob pattern [${inputFilesNo} files]`, () => { diff --git a/bin/cli.mjs b/bin/cli.mjs index 4e2ef34..9f37002 100644 --- a/bin/cli.mjs +++ b/bin/cli.mjs @@ -4,7 +4,7 @@ import yargs from 'yargs/yargs'; import { hideBin } from 'yargs/helpers'; import { sync } from '../dist/index.mjs'; -(process.env.CI || ~process.argv.indexOf('--no-stdin') +(~process.argv.indexOf('--no-stdin') ? Promise.resolve() : (await import('get-stdin')).default() ).then(async (stdin) => { diff --git a/bin/cli.spec.ts b/bin/cli.spec.ts index d3340c5..0dfadde 100644 --- a/bin/cli.spec.ts +++ b/bin/cli.spec.ts @@ -1,8 +1,8 @@ -import childProcess from 'child_process'; import fs from 'fs'; import tmp from 'tmp-promise'; import glob from 'fast-glob'; import path from 'path'; +import { SyncOptions, execaSync } from 'execa'; import { afterAll, afterEach, @@ -19,7 +19,7 @@ const tmpPrefixes = { }; const defaultOptions = { timeout: 2000, - encoding: 'utf-8', + reject: false, } as const; const content = `aąbcćdeęfg%hi jklmn @@ -62,7 +62,7 @@ afterEach(() => { test('no arguments', () => { const result = runCli(); - expect(result.status, 'process should send error status (1)').toEqual(1); + expect(result.exitCode, 'process should send error status (1)').toEqual(1); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); expect( result.parsedError, @@ -71,8 +71,8 @@ test('no arguments', () => { }); test('one argument', () => { - const result = runCli('sth'); - expect(result.status, 'process should send error status (1)').toEqual(1); + const result = runCli(['sth']); + expect(result.exitCode, 'process should send error status (1)').toEqual(1); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); expect( result.parsedError, @@ -82,7 +82,7 @@ test('one argument', () => { test('two arguments', () => { const result = runCli(['sth', 'sth']); - expect(result.status, 'process should send error status (1)').toEqual(1); + expect(result.exitCode, 'process should send error status (1)').toEqual(1); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); expect(result.parsedError).toEqual('Missing required argument: i'); }); @@ -93,7 +93,7 @@ describe('content argument', () => { ['-c', '--content'], content, (result) => { - expect(result.status, 'process should send success status (0)').toEqual( + expect(result.exitCode, 'process should send success status (0)').toEqual( 0, ); expect( @@ -107,7 +107,7 @@ describe('content argument', () => { test('no stdout argument', () => { const result = runCli([needle, replacement, '--content', content]); - expect(result.status, 'process should send success status (0)').toEqual(0); + expect(result.exitCode, 'process should send success status (0)').toEqual(0); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); expect(result.parsedError, 'stderr should be empty').toEqual(''); }); @@ -120,7 +120,7 @@ test('stdout argument', () => { content, '--stdout', ]); - expect(result.status, 'process should send success status (0)').toEqual(0); + expect(result.exitCode, 'process should send success status (0)').toEqual(0); expect(result.parsedOutput, 'stdout should contain replaced string').toEqual( expectedOutput, ); @@ -174,9 +174,10 @@ describe('input argument', async () => { ['-i', '--input'], () => input!.path, (result) => { - expect(result.status, 'process should send success status (0)').toEqual( - 0, - ); + expect( + result.exitCode, + 'process should send success status (0)', + ).toEqual(0); expect( result.parsedOutput, 'stdout should contain replaced string', @@ -203,9 +204,10 @@ describe('input argument', async () => { ['-i', '--input'], () => [input!.path, input2!.path], (result) => { - expect(result.status, 'process should send success status (0)').toEqual( - 0, - ); + expect( + result.exitCode, + 'process should send success status (0)', + ).toEqual(0); expect( result.parsedOutput, 'stdout should contain replaced string', @@ -253,9 +255,10 @@ describe('input argument', async () => { ['-i', '--input'], () => [input!.path, input2!.path], (result) => { - expect(result.status, 'process should send success status (0)').toEqual( - 0, - ); + expect( + result.exitCode, + 'process should send success status (0)', + ).toEqual(0); expect( result.parsedOutput, 'stdout should contain replaced string', @@ -294,9 +297,10 @@ describe('input argument', async () => { ['-i', '--input'], `${dir}/${tmpPrefixes.input}*`, (result) => { - expect(result.status, 'process should send success status (0)').toEqual( - 0, - ); + expect( + result.exitCode, + 'process should send success status (0)', + ).toEqual(0); expect( result.parsedOutput, 'stdout should contain replaced string', @@ -341,7 +345,7 @@ describe('i-read-opts argument', async () => { { input: content }, ); - expect(result.status, 'process should send error status (1)').toEqual(1); + expect(result.exitCode, 'process should send error status (1)').toEqual(1); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); expect( result.parsedError, @@ -360,7 +364,7 @@ describe('i-read-opts argument', async () => { '--stdout', ]); - expect(result.status, 'process should send error status (1)').toEqual(1); + expect(result.exitCode, 'process should send error status (1)').toEqual(1); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); if (process.version.includes('v16')) { @@ -398,7 +402,9 @@ describe('i-read-opts argument', async () => { '--stdout', ]); - expect(result.status, 'process should send success status (0)').toEqual(0); + expect(result.exitCode, 'process should send success status (0)').toEqual( + 0, + ); expect( result.parsedOutput, 'stdout should contain replaced string', @@ -458,7 +464,7 @@ describe('i-glob-opts argument', async () => { { input: content }, ); - expect(result.status, 'process should send error status (1)').toEqual(1); + expect(result.exitCode, 'process should send error status (1)').toEqual(1); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); expect( result.parsedError, @@ -477,7 +483,9 @@ describe('i-glob-opts argument', async () => { '--stdout', ]); - expect(result.status, 'process should send success status (0)').toEqual(0); + expect(result.exitCode, 'process should send success status (0)').toEqual( + 0, + ); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); expect(result.parsedError, 'stderr should be empty').toEqual(''); }); @@ -535,7 +543,9 @@ describe('o-join-str argument', () => { '--stdout', ]); - expect(result.status, 'process should send success status (0)').toEqual(0); + expect(result.exitCode, 'process should send success status (0)').toEqual( + 0, + ); expect( result.parsedOutput, 'stdout should contain replaced string', @@ -551,7 +561,7 @@ describe('output argument', async () => { ['-o', '--output'], outputPath, (result) => { - expect(result.status, 'process should send success status (0)').toEqual( + expect(result.exitCode, 'process should send success status (0)').toEqual( 0, ); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); @@ -580,7 +590,7 @@ describe('input options argument', async () => { { input: content }, ); - expect(result.status, 'process should send error status (1)').toEqual(1); + expect(result.exitCode, 'process should send error status (1)').toEqual(1); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); expect( result.parsedError, @@ -588,7 +598,7 @@ describe('input options argument', async () => { ).toEqual('Missing required argument: i'); }); - test('correct with input argument', () => { + test('correct with output argument', () => { const result = runCli( [ needle, @@ -602,9 +612,9 @@ describe('input options argument', async () => { { input: content }, ); - console.log('zxczxc', result); - - expect(result.status, 'process should send success status (0)').toEqual(0); + expect(result.exitCode, 'process should send success status (0)').toEqual( + 0, + ); expect(result.parsedOutput, 'stdout should be empty').toEqual(''); expect(result.parsedError, 'stderr should be empty').toEqual(''); @@ -623,7 +633,7 @@ describe('stdin && output argument', () => { ['-o', '--output'], outputPath, (result) => { - expect(result.status, 'process should send success status (0)').toEqual( + expect(result.exitCode, 'process should send success status (0)').toEqual( 0, ); expect( @@ -653,7 +663,7 @@ describe('flags argument', async () => { ['-f', '--flags'], flags, (result) => { - expect(result.status, 'process should send success status (0)').toEqual( + expect(result.exitCode, 'process should send success status (0)').toEqual( 0, ); expect( @@ -702,7 +712,9 @@ describe('replace-fn argument', async () => { }); checkEachArgCombination(args, ['-r', '--replace-fn'], undefined, (result) => { - expect(result.status, 'process should send success status (0)').toEqual(0); + expect(result.exitCode, 'process should send success status (0)').toEqual( + 0, + ); expect( result.parsedOutput, 'stdout should contain replaced string', @@ -719,7 +731,7 @@ describe('replace-fn argument', async () => { test('stdin stream as input argument (like piped stream)', async () => { const result = runCli([needle, replacement, '--stdout'], { input: content }); - expect(result.status, 'process should send success status (0)').toEqual(0); + expect(result.exitCode, 'process should send success status (0)').toEqual(0); expect(result.parsedOutput, 'stdout should contain replaced string').toEqual( expectedOutput, ); @@ -745,21 +757,14 @@ function checkEachArgCombination( } } -function runCli( - _args?: string | string[], - _options?: Omit, -) { - _options = Object.assign({}, defaultOptions, _options); - const result = childProcess.spawnSync( - 'node', - ['./bin/cli.mjs'].concat(_args || []), - _options, - ); +function runCli(args: string[] = [], options?: SyncOptions) { + const _options = Object.assign({}, defaultOptions, options); + const result = execaSync(_options)`node ${['./bin/cli.mjs', ...args]}`; return { ...result, - parsedOutput: result.stdout.toString().trim(), + parsedOutput: result.stdout?.toString().trim() || '', parsedError: - result.stderr.toString().trim().split('\n').pop()?.trim() ?? '', + result.stderr?.toString().trim().split('\n').pop()?.trim() ?? '', }; } diff --git a/package.json b/package.json index 7ac9b3e..0d530f1 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "release:ci": "pnpm release --yes", "test": "vitest", "coverage": "vitest --coverage", - "test:ci": "vitest run --coverage && vitest bench --run && TEST_TYPE=bench vitest run", + "test:ci": "vitest run --coverage --allowOnly && vitest bench --run --allowOnly && TEST_TYPE=bench vitest run --allowOnly", "eslint": "eslint .", "prettier": "prettier . --check", "lint": "pnpm eslint && pnpm prettier", @@ -74,6 +74,7 @@ "@vitest/coverage-v8": "1.6.0", "@vitest/ui": "1.6.0", "eslint": "^9.2.0", + "execa": "^9.5.2", "globals": "^15.14.0", "globals-vitest": "^1.6.0", "microbundle": "^0.15.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 690a96b..30000b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,9 @@ devDependencies: eslint: specifier: ^9.2.0 version: 9.2.0 + execa: + specifier: ^9.5.2 + version: 9.5.2 globals: specifier: ^15.14.0 version: 15.14.0 @@ -2149,6 +2152,10 @@ packages: dev: true optional: true + /@sec-ant/readable-stream@0.4.1: + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + dev: true + /@semantic-release/changelog@6.0.3(semantic-release@23.0.8): resolution: {integrity: sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==} engines: {node: '>=14.17'} @@ -2293,6 +2300,11 @@ packages: engines: {node: '>=18'} dev: true + /@sindresorhus/merge-streams@4.0.0: + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + dev: true + /@surma/rollup-plugin-off-main-thread@2.2.3: resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} dependencies: @@ -3980,6 +3992,24 @@ packages: strip-final-newline: 3.0.0 dev: true + /execa@9.5.2: + resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} + engines: {node: ^18.19.0 || >=20.5.0} + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.3 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.0 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.2.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.1 + dev: true + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -4277,6 +4307,14 @@ packages: engines: {node: '>=16'} dev: true + /get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + dev: true + /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} @@ -4586,6 +4624,11 @@ packages: engines: {node: '>=16.17.0'} dev: true + /human-signals@8.0.0: + resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==} + engines: {node: '>=18.18.0'} + dev: true + /icss-replace-symbols@1.1.0: resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} dev: true @@ -4835,6 +4878,11 @@ packages: engines: {node: '>=8'} dev: true + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: true + /is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} dependencies: @@ -4872,6 +4920,11 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + dev: true + /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -5551,6 +5604,14 @@ packages: path-key: 4.0.0 dev: true + /npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + dev: true + /npm@10.7.0: resolution: {integrity: sha512-FXylyYSXNjgXx3l82BT8RSQvCoGIQ3h8YdRFGKNvo3Pv/bKscK4pdWkx/onwTpHDqGw+oeLf4Rxln9WVyxAxlQ==} engines: {node: ^18.17.0 || >=20.5.0} @@ -5879,6 +5940,11 @@ packages: type-fest: 4.18.1 dev: true + /parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + dev: true + /parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} dependencies: @@ -6423,6 +6489,13 @@ packages: react-is: 18.2.0 dev: true + /pretty-ms@9.2.0: + resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} + engines: {node: '>=18'} + dependencies: + parse-ms: 4.0.0 + dev: true + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true @@ -7162,6 +7235,11 @@ packages: engines: {node: '>=12'} dev: true + /strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + dev: true + /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -7574,6 +7652,11 @@ packages: engines: {node: '>=18'} dev: true + /unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + dev: true + /unique-string@3.0.0: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} @@ -7882,3 +7965,8 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} dev: true + + /yoctocolors@2.1.1: + resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} + engines: {node: '>=18'} + dev: true