From 413fb09c5b9289e1704786227ad7bd450e891083 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sun, 1 Jan 2023 02:41:01 +0900 Subject: [PATCH 01/32] refactor: extract the process to run script command into a separated module --- .../plugin-commands-script-runners/src/run.ts | 38 +++++++++++-------- .../src/runRecursive.ts | 17 +-------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index c6e8e6e399a..e0713b51fea 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -203,21 +203,7 @@ so you may run "pnpm -w run ${scriptName}"`, } } try { - if ( - opts.enablePrePostScripts && - manifest.scripts?.[`pre${scriptName}`] && - !manifest.scripts[scriptName].includes(`pre${scriptName}`) - ) { - await runLifecycleHook(`pre${scriptName}`, manifest, lifecycleOpts) - } - await runLifecycleHook(scriptName, manifest, { ...lifecycleOpts, args: passedThruArgs }) - if ( - opts.enablePrePostScripts && - manifest.scripts?.[`post${scriptName}`] && - !manifest.scripts[scriptName].includes(`post${scriptName}`) - ) { - await runLifecycleHook(`post${scriptName}`, manifest, lifecycleOpts) - } + await runScript(scriptName, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) } catch (err: any) { // eslint-disable-line if (opts.bail !== false) { throw err @@ -300,6 +286,28 @@ ${renderCommands(rootScripts)}` return output } +interface RunScriptOptions { + enablePrePostScripts: boolean +} + +export const runScript: (scriptName: string, manifest: ProjectManifest, lifecycleOpts: RunLifecycleHookOptions, runScriptOptions: RunScriptOptions, passedThruArgs: string[]) => Promise = async function(scriptName, manifest, lifecycleOpts, runScriptOptions, passedThruArgs) { + if ( + runScriptOptions.enablePrePostScripts && + manifest.scripts?.[`pre${scriptName}`] && + !manifest.scripts[scriptName].includes(`pre${scriptName}`) + ) { + await runLifecycleHook(`pre${scriptName}`, manifest, lifecycleOpts) + } + await runLifecycleHook(scriptName, manifest, { ...lifecycleOpts, args: passedThruArgs }) + if ( + runScriptOptions.enablePrePostScripts && + manifest.scripts?.[`post${scriptName}`] && + !manifest.scripts[scriptName].includes(`post${scriptName}`) + ) { + await runLifecycleHook(`post${scriptName}`, manifest, lifecycleOpts) + } +} + function renderCommands (commands: string[][]) { return commands.map(([scriptName, script]) => ` ${scriptName}\n ${script}`).join('\n') } diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index d295c7f88e6..115f706fd7e 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -13,6 +13,7 @@ import pLimit from 'p-limit' import realpathMissing from 'realpath-missing' import { existsInDir } from './existsInDir' import { getResumedPackageChunks } from './exec' +import { runScript } from './run' export type RecursiveRunOpts = Pick Date: Sun, 1 Jan 2023 22:17:45 +0900 Subject: [PATCH 02/32] feat: run script command parallel if multiple script commands is specified with selector --- .../plugin-commands-script-runners/src/run.ts | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index e0713b51fea..af52a23f775 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -1,4 +1,5 @@ import path from 'path' +import pLimit from 'p-limit' import { docsUrl, readProjectManifestOnly, @@ -159,7 +160,13 @@ export async function handler ( : undefined return printProjectCommands(manifest, rootManifest ?? undefined) } - if (scriptName !== 'start' && !manifest.scripts?.[scriptName]) { + + const multiScriptSelectorSpecified = scriptName.slice(-2) === ':*' + // extract XXX:YYY: from XXX:YYY:ZZZ + const multiScriptSelectorPrefix = scriptName.slice(0, -1) + const specifiedScriptsWithSelector = Object.keys(manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) + + if (scriptName !== 'start' && !manifest.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithSelector.length > 0)) { if (opts.ifPresent) return if (opts.fallbackCommandUsed) { if (opts.argv == null) throw new Error('Could not fallback because opts.argv.original was not passed to the script runner') @@ -170,7 +177,7 @@ export async function handler ( } if (opts.workspaceDir) { const { manifest: rootManifest } = await tryReadProjectManifest(opts.workspaceDir, opts) - if (rootManifest?.scripts?.[scriptName]) { + if (rootManifest?.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithSelector.length > 0)) { throw new PnpmError('NO_SCRIPT', `Missing script: ${scriptName}`, { hint: `But ${scriptName} is present in the root of the workspace, so you may run "pnpm -w run ${scriptName}"`, @@ -203,7 +210,13 @@ so you may run "pnpm -w run ${scriptName}"`, } } try { - await runScript(scriptName, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) + if (multiScriptSelectorSpecified) { + const limitRun = pLimit(1) + + await Promise.all(specifiedScriptsWithSelector.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) + } else { + await runScript(scriptName, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) + } } catch (err: any) { // eslint-disable-line if (opts.bail !== false) { throw err @@ -286,11 +299,11 @@ ${renderCommands(rootScripts)}` return output } -interface RunScriptOptions { +export interface RunScriptOptions { enablePrePostScripts: boolean } -export const runScript: (scriptName: string, manifest: ProjectManifest, lifecycleOpts: RunLifecycleHookOptions, runScriptOptions: RunScriptOptions, passedThruArgs: string[]) => Promise = async function(scriptName, manifest, lifecycleOpts, runScriptOptions, passedThruArgs) { +export const runScript: (scriptName: string, manifest: ProjectManifest, lifecycleOpts: RunLifecycleHookOptions, runScriptOptions: RunScriptOptions, passedThruArgs: string[]) => Promise = async function (scriptName, manifest, lifecycleOpts, runScriptOptions, passedThruArgs) { if ( runScriptOptions.enablePrePostScripts && manifest.scripts?.[`pre${scriptName}`] && From 25fae543193ad466cb5ebfa95bd8d7af1b9f2527 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Mon, 2 Jan 2023 17:30:47 +0900 Subject: [PATCH 03/32] feat: run script command parallel if multiple script command is specified with selector in recursive --- .../src/runRecursive.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index 115f706fd7e..6f68b89049c 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -3,7 +3,6 @@ import { RecursiveSummary, throwOnCommandFail } from '@pnpm/cli-utils' import { Config } from '@pnpm/config' import { PnpmError } from '@pnpm/error' import { - runLifecycleHook, makeNodeRequireOption, RunLifecycleHookOptions, } from '@pnpm/lifecycle' @@ -69,12 +68,15 @@ export async function runRecursive ( const existsPnp = existsInDir.bind(null, '.pnp.cjs') const workspacePnpPath = opts.workspaceDir && await existsPnp(opts.workspaceDir) + const multiScriptSelectorSpecified = scriptName.slice(-2) === ':*' + const multiScriptSelectorPrefix = scriptName.slice(0, -1) + const requiredScripts = opts.rootProjectManifest?.pnpm?.requiredScripts ?? [] if (requiredScripts.includes(scriptName)) { const missingScriptPackages: string[] = packageChunks .flat() .map((prefix) => opts.selectedProjectsGraph[prefix]) - .filter((pkg) => !pkg.package.manifest.scripts?.[scriptName]) + .filter((pkg) => multiScriptSelectorSpecified ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) : !pkg.package.manifest.scripts?.[scriptName]) .map((pkg) => pkg.package.manifest.name ?? pkg.package.dir) if (missingScriptPackages.length) { throw new PnpmError('RECURSIVE_RUN_NO_SCRIPT', `Missing script "${scriptName}" in packages: ${missingScriptPackages.join(', ')}`) @@ -85,8 +87,9 @@ export async function runRecursive ( await Promise.all(chunk.map(async (prefix: string) => limitRun(async () => { const pkg = opts.selectedProjectsGraph[prefix] + const specifiedScriptsWithSelector = Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) if ( - !pkg.package.manifest.scripts?.[scriptName] || + !((pkg.package.manifest.scripts?.[scriptName] ?? multiScriptSelectorSpecified) || specifiedScriptsWithSelector.length > 0) || process.env.npm_lifecycle_event === scriptName && process.env.PNPM_SCRIPT_SRC_DIR === prefix ) { @@ -114,7 +117,13 @@ export async function runRecursive ( ...makeNodeRequireOption(pnpPath), } } - await runScript(scriptName, pkg.package.manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) + if (multiScriptSelectorSpecified) { + const limitRunForMultiScriptSelector = pLimit(3) + + await Promise.all(specifiedScriptsWithSelector.map(script => limitRunForMultiScriptSelector(() => runScript(script, pkg.package.manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) + } else { + await runScript(scriptName, pkg.package.manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) + } result.passes++ } catch (err: any) { // eslint-disable-line logger.info(err) From 776dd2456ae63c6853f893af20746c81856ebd5d Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Mon, 2 Jan 2023 20:26:31 +0900 Subject: [PATCH 04/32] test: run unit test for multi script commands selector --- .../test/index.ts | 69 ++++++++++++++++++ .../test/runRecursive.ts | 70 +++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index 416b3dba053..b80049c7b94 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -465,3 +465,72 @@ test('pnpm run with custom shell', async () => { expect((await import(path.resolve('shell-input.json'))).default).toStrictEqual(['-c', 'foo bar']) }) + +test('pnpm run with multiple script selector should work', async () => { + prepare({ + scripts: { + 'build:a': 'node -e "process.stdout.write(\'a\')" | json-append ./output-a.json', + 'build:b': 'node -e "process.stdout.write(\'b\')" | json-append ./output-b.json', + 'build:c': 'node -e "process.stdout.write(\'c\')" | json-append ./output-c.json', + }, + }) + + await execa('pnpm', ['add', 'json-append@1']) + + await run.handler({ + dir: process.cwd(), + extraBinPaths: [], + extraEnv: {}, + rawConfig: {}, + }, ['build:*']) + + expect((await import(path.resolve('output-a.json'))).default).toStrictEqual(['a']) + expect((await import(path.resolve('output-b.json'))).default).toStrictEqual(['b']) + expect((await import(path.resolve('output-c.json'))).default).toStrictEqual(['c']) +}) + +test('pnpm run with multiple script selector should work also for pre/post script', async () => { + prepare({ + scripts: { + 'build:a': 'node -e "process.stdout.write(\'a\')" | json-append ./output-a.json', + 'prebuild:a': 'node -e "process.stdout.write(\'pre-a\')" | json-append ./output-pre-a.json', + }, + }) + + await execa('pnpm', ['add', 'json-append@1']) + + await run.handler({ + dir: process.cwd(), + extraBinPaths: [], + extraEnv: {}, + rawConfig: {}, + enablePrePostScripts: true, + }, ['build:*']) + + expect((await import(path.resolve('output-a.json'))).default).toStrictEqual(['a']) + expect((await import(path.resolve('output-pre-a.json'))).default).toStrictEqual(['pre-a']) +}) + +test('pnpm run with multiple script selector should work with parallel mode', async () => { + prepare({ + scripts: { + 'build:a': 'node -e "let i = 20;setInterval(() => {if (!--i) process.exit(0); require(\'json-append\').append(Date.now(),\'./output-a.json\');},50)"', + 'build:b': 'node -e "let i = 40;setInterval(() => {if (!--i) process.exit(0); require(\'json-append\').append(Date.now(),\'./output-b.json\');},25)"', + }, + }) + + await execa('pnpm', ['add', 'json-append@1']) + + await run.handler({ + dir: process.cwd(), + extraBinPaths: [], + extraEnv: {}, + rawConfig: {}, + maxParallel: 2, + }, ['build:*']) + + const { default: outputsA } = await import(path.resolve('output-a.json')) + const { default: outputsB } = await import(path.resolve('output-b.json')) + + expect(Math.max(outputsA[0], outputsB[0]) < Math.min(outputsA[outputsA.length - 1], outputsB[outputsB.length - 1])).toBeTruthy() +}) diff --git a/exec/plugin-commands-script-runners/test/runRecursive.ts b/exec/plugin-commands-script-runners/test/runRecursive.ts index 2ccc80d2921..f6219c689c1 100644 --- a/exec/plugin-commands-script-runners/test/runRecursive.ts +++ b/exec/plugin-commands-script-runners/test/runRecursive.ts @@ -890,3 +890,73 @@ test('`pnpm -r --resume-from run` should executed from given package', async () expect(output1).toContain('project-2') expect(output1).toContain('project-3') }) + +test('pnpm run with multiple script selector should work on recursive', async () => { + preparePackages([ + { + name: 'project-1', + version: '1.0.0', + dependencies: { + 'json-append': '1', + }, + scripts: { + 'build:a': 'node -e "process.stdout.write(\'1/a\')" | json-append ../output-1-a.json', + 'build:b': 'node -e "process.stdout.write(\'1/b\')" | json-append ../output-1-b.json', + 'build:c': 'node -e "process.stdout.write(\'1/c\')" | json-append ../output-1-c.json', + }, + }, + { + name: 'project-2', + version: '1.0.0', + dependencies: { + 'json-append': '1', + }, + scripts: { + 'build:a': 'node -e "process.stdout.write(\'2/a\')" | json-append ../output-2-a.json', + 'build:b': 'node -e "process.stdout.write(\'2/b\')" | json-append ../output-2-b.json', + 'build:c': 'node -e "process.stdout.write(\'2/c\')" | json-append ../output-2-c.json', + }, + }, + { + name: 'project-3', + version: '1.0.0', + dependencies: { + 'json-append': '1', + }, + scripts: { + 'build:a': 'node -e "process.stdout.write(\'3/a\')" | json-append ../output-3-a.json', + 'build:b': 'node -e "process.stdout.write(\'3/b\')" | json-append ../output-3-b.json', + 'build:c': 'node -e "process.stdout.write(\'3/c\')" | json-append ../output-3-c.json', + }, + }, + ]) + + await execa(pnpmBin, [ + 'install', + '-r', + '--registry', + REGISTRY_URL, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) + await run.handler({ + ...DEFAULT_OPTS, + ...await readProjects(process.cwd(), [{ namePattern: '*' }]), + dir: process.cwd(), + recursive: true, + rootProjectManifest: { + name: 'test-workspaces', + private: true, + }, + workspaceDir: process.cwd(), + }, ['build:*']) + expect((await import(path.resolve('output-1-a.json'))).default).toStrictEqual(['1/a']) + expect((await import(path.resolve('output-1-b.json'))).default).toStrictEqual(['1/b']) + expect((await import(path.resolve('output-1-c.json'))).default).toStrictEqual(['1/c']) + expect((await import(path.resolve('output-2-a.json'))).default).toStrictEqual(['2/a']) + expect((await import(path.resolve('output-2-b.json'))).default).toStrictEqual(['2/b']) + expect((await import(path.resolve('output-2-c.json'))).default).toStrictEqual(['2/c']) + expect((await import(path.resolve('output-3-a.json'))).default).toStrictEqual(['3/a']) + expect((await import(path.resolve('output-3-b.json'))).default).toStrictEqual(['3/b']) + expect((await import(path.resolve('output-3-c.json'))).default).toStrictEqual(['3/c']) +}) From 8424515b0458f382cfc58cd16e2a6e2574213109 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Tue, 3 Jan 2023 15:25:53 +0900 Subject: [PATCH 05/32] fixup! test: run unit test for multi script commands selector --- exec/plugin-commands-script-runners/test/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index b80049c7b94..9cb511c14fa 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -526,7 +526,7 @@ test('pnpm run with multiple script selector should work with parallel mode', as extraBinPaths: [], extraEnv: {}, rawConfig: {}, - maxParallel: 2, + workspaceConcurrency: 2, }, ['build:*']) const { default: outputsA } = await import(path.resolve('output-a.json')) From f256571a6b5be65622adf03fafb303f71a34cae7 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Tue, 3 Jan 2023 15:42:49 +0900 Subject: [PATCH 06/32] feat: run selected scripts parallel with the limits specified with workspace-concurrency --- .../plugin-commands-script-runners/src/run.ts | 2 +- .../src/runRecursive.ts | 26 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index af52a23f775..cd6ebb2a4c3 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -211,7 +211,7 @@ so you may run "pnpm -w run ${scriptName}"`, } try { if (multiScriptSelectorSpecified) { - const limitRun = pLimit(1) + const limitRun = pLimit(opts.workspaceConcurrency ?? 1) await Promise.all(specifiedScriptsWithSelector.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) } else { diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index 6f68b89049c..0ab8d8d5b31 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -84,12 +84,25 @@ export async function runRecursive ( } for (const chunk of packageChunks) { - await Promise.all(chunk.map(async (prefix: string) => + const selectedScripts = chunk.map(prefix => { + if (!multiScriptSelectorSpecified) { + return { + prefix, + scriptName, + } + } + + const pkg = opts.selectedProjectsGraph[prefix] + const specifiedScriptsWithSelector = Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) + + return specifiedScriptsWithSelector.map(script => ({ prefix, scriptName: script })) + }).flat() + + await Promise.all(selectedScripts.map(async ({ prefix, scriptName }) => limitRun(async () => { const pkg = opts.selectedProjectsGraph[prefix] - const specifiedScriptsWithSelector = Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) if ( - !((pkg.package.manifest.scripts?.[scriptName] ?? multiScriptSelectorSpecified) || specifiedScriptsWithSelector.length > 0) || + !pkg.package.manifest.scripts?.[scriptName] || process.env.npm_lifecycle_event === scriptName && process.env.PNPM_SCRIPT_SRC_DIR === prefix ) { @@ -117,13 +130,8 @@ export async function runRecursive ( ...makeNodeRequireOption(pnpPath), } } - if (multiScriptSelectorSpecified) { - const limitRunForMultiScriptSelector = pLimit(3) - await Promise.all(specifiedScriptsWithSelector.map(script => limitRunForMultiScriptSelector(() => runScript(script, pkg.package.manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) - } else { - await runScript(scriptName, pkg.package.manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) - } + await runScript(scriptName, pkg.package.manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) result.passes++ } catch (err: any) { // eslint-disable-line logger.info(err) From b92e3bb513e8035e768285346b502534f36f9c4e Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Tue, 3 Jan 2023 17:03:16 +0900 Subject: [PATCH 07/32] fixup! test: run unit test for multi script commands selector --- .../test/index.ts | 27 +++++------ .../test/runRecursive.ts | 46 ++++++++----------- 2 files changed, 30 insertions(+), 43 deletions(-) diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index 9cb511c14fa..6d23a5540f5 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -469,14 +469,12 @@ test('pnpm run with custom shell', async () => { test('pnpm run with multiple script selector should work', async () => { prepare({ scripts: { - 'build:a': 'node -e "process.stdout.write(\'a\')" | json-append ./output-a.json', - 'build:b': 'node -e "process.stdout.write(\'b\')" | json-append ./output-b.json', - 'build:c': 'node -e "process.stdout.write(\'c\')" | json-append ./output-c.json', + 'build:a': 'node -e "require(\'fs\').writeFileSync(\'./output-a.txt\', \'a\', \'utf8\')"', + 'build:b': 'node -e "require(\'fs\').writeFileSync(\'./output-b.txt\', \'b\', \'utf8\')"', + 'build:c': 'node -e "require(\'fs\').writeFileSync(\'./output-c.txt\', \'c\', \'utf8\')"', }, }) - await execa('pnpm', ['add', 'json-append@1']) - await run.handler({ dir: process.cwd(), extraBinPaths: [], @@ -484,21 +482,19 @@ test('pnpm run with multiple script selector should work', async () => { rawConfig: {}, }, ['build:*']) - expect((await import(path.resolve('output-a.json'))).default).toStrictEqual(['a']) - expect((await import(path.resolve('output-b.json'))).default).toStrictEqual(['b']) - expect((await import(path.resolve('output-c.json'))).default).toStrictEqual(['c']) + expect(await fs.readFile('output-a.txt', { encoding: 'utf-8' })).toEqual('a') + expect(await fs.readFile('output-b.txt', { encoding: 'utf-8' })).toEqual('b') + expect(await fs.readFile('output-c.txt', { encoding: 'utf-8' })).toEqual('c') }) test('pnpm run with multiple script selector should work also for pre/post script', async () => { prepare({ scripts: { - 'build:a': 'node -e "process.stdout.write(\'a\')" | json-append ./output-a.json', - 'prebuild:a': 'node -e "process.stdout.write(\'pre-a\')" | json-append ./output-pre-a.json', + 'build:a': 'node -e "require(\'fs\').writeFileSync(\'./output-a.txt\', \'a\', \'utf8\')"', + 'prebuild:a': 'node -e "require(\'fs\').writeFileSync(\'./output-pre-a.txt\', \'pre-a\', \'utf8\')"', }, }) - await execa('pnpm', ['add', 'json-append@1']) - await run.handler({ dir: process.cwd(), extraBinPaths: [], @@ -507,11 +503,11 @@ test('pnpm run with multiple script selector should work also for pre/post scrip enablePrePostScripts: true, }, ['build:*']) - expect((await import(path.resolve('output-a.json'))).default).toStrictEqual(['a']) - expect((await import(path.resolve('output-pre-a.json'))).default).toStrictEqual(['pre-a']) + expect(await fs.readFile('output-a.txt', { encoding: 'utf-8' })).toEqual('a') + expect(await fs.readFile('output-pre-a.txt', { encoding: 'utf-8' })).toEqual('pre-a') }) -test('pnpm run with multiple script selector should work with parallel mode', async () => { +test('pnpm run with multiple script selector should work parallel as a default behavior (parallel execution limits number is four)', async () => { prepare({ scripts: { 'build:a': 'node -e "let i = 20;setInterval(() => {if (!--i) process.exit(0); require(\'json-append\').append(Date.now(),\'./output-a.json\');},50)"', @@ -526,7 +522,6 @@ test('pnpm run with multiple script selector should work with parallel mode', as extraBinPaths: [], extraEnv: {}, rawConfig: {}, - workspaceConcurrency: 2, }, ['build:*']) const { default: outputsA } = await import(path.resolve('output-a.json')) diff --git a/exec/plugin-commands-script-runners/test/runRecursive.ts b/exec/plugin-commands-script-runners/test/runRecursive.ts index f6219c689c1..c3bdd094977 100644 --- a/exec/plugin-commands-script-runners/test/runRecursive.ts +++ b/exec/plugin-commands-script-runners/test/runRecursive.ts @@ -1,3 +1,4 @@ +import { promises as fs } from 'fs' import path from 'path' import { preparePackages } from '@pnpm/prepare' import { run } from '@pnpm/plugin-commands-script-runners' @@ -896,37 +897,28 @@ test('pnpm run with multiple script selector should work on recursive', async () { name: 'project-1', version: '1.0.0', - dependencies: { - 'json-append': '1', - }, scripts: { - 'build:a': 'node -e "process.stdout.write(\'1/a\')" | json-append ../output-1-a.json', - 'build:b': 'node -e "process.stdout.write(\'1/b\')" | json-append ../output-1-b.json', - 'build:c': 'node -e "process.stdout.write(\'1/c\')" | json-append ../output-1-c.json', + 'build:a': 'node -e "require(\'fs\').writeFileSync(\'../output-1-a.txt\', \'1-a\', \'utf8\')"', + 'build:b': 'node -e "require(\'fs\').writeFileSync(\'../output-1-b.txt\', \'1-b\', \'utf8\')"', + 'build:c': 'node -e "require(\'fs\').writeFileSync(\'../output-1-c.txt\', \'1-c\', \'utf8\')"', }, }, { name: 'project-2', version: '1.0.0', - dependencies: { - 'json-append': '1', - }, scripts: { - 'build:a': 'node -e "process.stdout.write(\'2/a\')" | json-append ../output-2-a.json', - 'build:b': 'node -e "process.stdout.write(\'2/b\')" | json-append ../output-2-b.json', - 'build:c': 'node -e "process.stdout.write(\'2/c\')" | json-append ../output-2-c.json', + 'build:a': 'node -e "require(\'fs\').writeFileSync(\'../output-2-a.txt\', \'2-a\', \'utf8\')"', + 'build:b': 'node -e "require(\'fs\').writeFileSync(\'../output-2-b.txt\', \'2-b\', \'utf8\')"', + 'build:c': 'node -e "require(\'fs\').writeFileSync(\'../output-2-c.txt\', \'2-c\', \'utf8\')"', }, }, { name: 'project-3', version: '1.0.0', - dependencies: { - 'json-append': '1', - }, scripts: { - 'build:a': 'node -e "process.stdout.write(\'3/a\')" | json-append ../output-3-a.json', - 'build:b': 'node -e "process.stdout.write(\'3/b\')" | json-append ../output-3-b.json', - 'build:c': 'node -e "process.stdout.write(\'3/c\')" | json-append ../output-3-c.json', + 'build:a': 'node -e "require(\'fs\').writeFileSync(\'../output-3-a.txt\', \'3-a\', \'utf8\')"', + 'build:b': 'node -e "require(\'fs\').writeFileSync(\'../output-3-b.txt\', \'3-b\', \'utf8\')"', + 'build:c': 'node -e "require(\'fs\').writeFileSync(\'../output-3-c.txt\', \'3-c\', \'utf8\')"', }, }, ]) @@ -950,13 +942,13 @@ test('pnpm run with multiple script selector should work on recursive', async () }, workspaceDir: process.cwd(), }, ['build:*']) - expect((await import(path.resolve('output-1-a.json'))).default).toStrictEqual(['1/a']) - expect((await import(path.resolve('output-1-b.json'))).default).toStrictEqual(['1/b']) - expect((await import(path.resolve('output-1-c.json'))).default).toStrictEqual(['1/c']) - expect((await import(path.resolve('output-2-a.json'))).default).toStrictEqual(['2/a']) - expect((await import(path.resolve('output-2-b.json'))).default).toStrictEqual(['2/b']) - expect((await import(path.resolve('output-2-c.json'))).default).toStrictEqual(['2/c']) - expect((await import(path.resolve('output-3-a.json'))).default).toStrictEqual(['3/a']) - expect((await import(path.resolve('output-3-b.json'))).default).toStrictEqual(['3/b']) - expect((await import(path.resolve('output-3-c.json'))).default).toStrictEqual(['3/c']) + expect(await fs.readFile('output-1-a.txt', { encoding: 'utf-8' })).toEqual('1-a') + expect(await fs.readFile('output-1-b.txt', { encoding: 'utf-8' })).toEqual('1-b') + expect(await fs.readFile('output-1-c.txt', { encoding: 'utf-8' })).toEqual('1-c') + expect(await fs.readFile('output-2-a.txt', { encoding: 'utf-8' })).toEqual('2-a') + expect(await fs.readFile('output-2-b.txt', { encoding: 'utf-8' })).toEqual('2-b') + expect(await fs.readFile('output-2-c.txt', { encoding: 'utf-8' })).toEqual('2-c') + expect(await fs.readFile('output-3-a.txt', { encoding: 'utf-8' })).toEqual('3-a') + expect(await fs.readFile('output-3-b.txt', { encoding: 'utf-8' })).toEqual('3-b') + expect(await fs.readFile('output-3-c.txt', { encoding: 'utf-8' })).toEqual('3-c') }) From 514986ff785d369fb6ebedd6db13df907cb99678 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Tue, 3 Jan 2023 17:21:23 +0900 Subject: [PATCH 08/32] feat: run multi scripts parallel per 4 as a default behavior --- .../plugin-commands-script-runners/src/run.ts | 12 +++++++++- .../test/index.ts | 24 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index cd6ebb2a4c3..389605533b8 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -44,6 +44,12 @@ export const RESUME_FROM_OPTION_HELP = { name: '--resume-from', } +export const SEQUENTIAL_OPTION_HELP = { + description: 'Run a multiple scripts specified with workspace selector or a wildcard script selector sequential.\ +This is the preferred flag for tasks which has dependency on execution order.', + name: '--sequential', +} + export const shorthands = { parallel: [ '--workspace-concurrency=Infinity', @@ -51,6 +57,9 @@ export const shorthands = { '--stream', '--recursive', ], + sequential: [ + '--workspace-concurrency=1', + ], } export function rcOptionsTypes () { @@ -113,6 +122,7 @@ For options that may be used with `-r`, see "pnpm help recursive"', PARALLEL_OPTION_HELP, RESUME_FROM_OPTION_HELP, ...UNIVERSAL_OPTIONS, + SEQUENTIAL_OPTION_HELP, ], }, FILTERING, @@ -211,7 +221,7 @@ so you may run "pnpm -w run ${scriptName}"`, } try { if (multiScriptSelectorSpecified) { - const limitRun = pLimit(opts.workspaceConcurrency ?? 1) + const limitRun = pLimit(opts.workspaceConcurrency ?? 4) await Promise.all(specifiedScriptsWithSelector.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) } else { diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index 6d23a5540f5..8e9c72e4e00 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -529,3 +529,27 @@ test('pnpm run with multiple script selector should work parallel as a default b expect(Math.max(outputsA[0], outputsB[0]) < Math.min(outputsA[outputsA.length - 1], outputsB[outputsB.length - 1])).toBeTruthy() }) + +test('pnpm run with multiple script selector should work sequentially with --workspace-concurrency=1', async () => { + prepare({ + scripts: { + 'build:a': 'node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); require(\'json-append\').append(Date.now(),\'./output-a.json\');},16)"', + 'build:b': 'node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); require(\'json-append\').append(Date.now(),\'./output-b.json\');},16)"', + }, + }) + + await execa('pnpm', ['add', 'json-append@1']) + + await run.handler({ + dir: process.cwd(), + extraBinPaths: [], + extraEnv: {}, + rawConfig: {}, + workspaceConcurrency: 1, + }, ['build:*']) + + const { default: outputsA } = await import(path.resolve('output-a.json')) + const { default: outputsB } = await import(path.resolve('output-b.json')) + + expect(outputsA[0] < outputsB[0] && outputsA[1] < outputsB[1]).toBeTruthy() +}) From bd0aef22e09fe8eca9a073d997abdb8944a84b11 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sat, 7 Jan 2023 13:03:06 +0900 Subject: [PATCH 09/32] fix: use regexp for multi scripts wildcard selector --- exec/plugin-commands-script-runners/src/run.ts | 10 +++++----- .../plugin-commands-script-runners/src/runRecursive.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index 389605533b8..cd8f66c2140 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -173,10 +173,10 @@ export async function handler ( const multiScriptSelectorSpecified = scriptName.slice(-2) === ':*' // extract XXX:YYY: from XXX:YYY:ZZZ - const multiScriptSelectorPrefix = scriptName.slice(0, -1) - const specifiedScriptsWithSelector = Object.keys(manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) + const multiScriptSelectorRegExp = new RegExp(scriptName) + const specifiedScriptsWithWildcardSelector = Object.keys(manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) - if (scriptName !== 'start' && !manifest.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithSelector.length > 0)) { + if (scriptName !== 'start' && !manifest.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithWildcardSelector.length > 0)) { if (opts.ifPresent) return if (opts.fallbackCommandUsed) { if (opts.argv == null) throw new Error('Could not fallback because opts.argv.original was not passed to the script runner') @@ -187,7 +187,7 @@ export async function handler ( } if (opts.workspaceDir) { const { manifest: rootManifest } = await tryReadProjectManifest(opts.workspaceDir, opts) - if (rootManifest?.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithSelector.length > 0)) { + if (rootManifest?.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithWildcardSelector.length > 0)) { throw new PnpmError('NO_SCRIPT', `Missing script: ${scriptName}`, { hint: `But ${scriptName} is present in the root of the workspace, so you may run "pnpm -w run ${scriptName}"`, @@ -223,7 +223,7 @@ so you may run "pnpm -w run ${scriptName}"`, if (multiScriptSelectorSpecified) { const limitRun = pLimit(opts.workspaceConcurrency ?? 4) - await Promise.all(specifiedScriptsWithSelector.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) + await Promise.all(specifiedScriptsWithWildcardSelector.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) } else { await runScript(scriptName, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) } diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index 0ab8d8d5b31..fe20a1cf679 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -69,14 +69,14 @@ export async function runRecursive ( const workspacePnpPath = opts.workspaceDir && await existsPnp(opts.workspaceDir) const multiScriptSelectorSpecified = scriptName.slice(-2) === ':*' - const multiScriptSelectorPrefix = scriptName.slice(0, -1) + const multiScriptSelectorRegExp = new RegExp(scriptName) const requiredScripts = opts.rootProjectManifest?.pnpm?.requiredScripts ?? [] if (requiredScripts.includes(scriptName)) { const missingScriptPackages: string[] = packageChunks .flat() .map((prefix) => opts.selectedProjectsGraph[prefix]) - .filter((pkg) => multiScriptSelectorSpecified ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) : !pkg.package.manifest.scripts?.[scriptName]) + .filter((pkg) => multiScriptSelectorSpecified ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.match(multiScriptSelectorRegExp)) : !pkg.package.manifest.scripts?.[scriptName]) .map((pkg) => pkg.package.manifest.name ?? pkg.package.dir) if (missingScriptPackages.length) { throw new PnpmError('RECURSIVE_RUN_NO_SCRIPT', `Missing script "${scriptName}" in packages: ${missingScriptPackages.join(', ')}`) @@ -93,7 +93,7 @@ export async function runRecursive ( } const pkg = opts.selectedProjectsGraph[prefix] - const specifiedScriptsWithSelector = Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) + const specifiedScriptsWithSelector = Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) return specifiedScriptsWithSelector.map(script => ({ prefix, scriptName: script })) }).flat() From b7e6428bcbbeafb908b7c673a28cf725be7b4cc2 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sat, 7 Jan 2023 13:04:42 +0900 Subject: [PATCH 10/32] chore: add changeset --- .changeset/silly-flies-care.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/silly-flies-care.md diff --git a/.changeset/silly-flies-care.md b/.changeset/silly-flies-care.md new file mode 100644 index 00000000000..7c7fd61d8d5 --- /dev/null +++ b/.changeset/silly-flies-care.md @@ -0,0 +1,5 @@ +--- +"@pnpm/plugin-commands-script-runners": minor +--- + +feat: support wildcard selector to specify multiple scripts to execute" From 7608448ec57c581254a373e4ef79ad7146303586 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Mon, 9 Jan 2023 18:38:18 +0900 Subject: [PATCH 11/32] Revert "fix: use regexp for multi scripts wildcard selector" This reverts commit bd0aef22e09fe8eca9a073d997abdb8944a84b11. --- exec/plugin-commands-script-runners/src/run.ts | 10 +++++----- .../plugin-commands-script-runners/src/runRecursive.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index cd8f66c2140..389605533b8 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -173,10 +173,10 @@ export async function handler ( const multiScriptSelectorSpecified = scriptName.slice(-2) === ':*' // extract XXX:YYY: from XXX:YYY:ZZZ - const multiScriptSelectorRegExp = new RegExp(scriptName) - const specifiedScriptsWithWildcardSelector = Object.keys(manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) + const multiScriptSelectorPrefix = scriptName.slice(0, -1) + const specifiedScriptsWithSelector = Object.keys(manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) - if (scriptName !== 'start' && !manifest.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithWildcardSelector.length > 0)) { + if (scriptName !== 'start' && !manifest.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithSelector.length > 0)) { if (opts.ifPresent) return if (opts.fallbackCommandUsed) { if (opts.argv == null) throw new Error('Could not fallback because opts.argv.original was not passed to the script runner') @@ -187,7 +187,7 @@ export async function handler ( } if (opts.workspaceDir) { const { manifest: rootManifest } = await tryReadProjectManifest(opts.workspaceDir, opts) - if (rootManifest?.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithWildcardSelector.length > 0)) { + if (rootManifest?.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithSelector.length > 0)) { throw new PnpmError('NO_SCRIPT', `Missing script: ${scriptName}`, { hint: `But ${scriptName} is present in the root of the workspace, so you may run "pnpm -w run ${scriptName}"`, @@ -223,7 +223,7 @@ so you may run "pnpm -w run ${scriptName}"`, if (multiScriptSelectorSpecified) { const limitRun = pLimit(opts.workspaceConcurrency ?? 4) - await Promise.all(specifiedScriptsWithWildcardSelector.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) + await Promise.all(specifiedScriptsWithSelector.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) } else { await runScript(scriptName, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) } diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index fe20a1cf679..0ab8d8d5b31 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -69,14 +69,14 @@ export async function runRecursive ( const workspacePnpPath = opts.workspaceDir && await existsPnp(opts.workspaceDir) const multiScriptSelectorSpecified = scriptName.slice(-2) === ':*' - const multiScriptSelectorRegExp = new RegExp(scriptName) + const multiScriptSelectorPrefix = scriptName.slice(0, -1) const requiredScripts = opts.rootProjectManifest?.pnpm?.requiredScripts ?? [] if (requiredScripts.includes(scriptName)) { const missingScriptPackages: string[] = packageChunks .flat() .map((prefix) => opts.selectedProjectsGraph[prefix]) - .filter((pkg) => multiScriptSelectorSpecified ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.match(multiScriptSelectorRegExp)) : !pkg.package.manifest.scripts?.[scriptName]) + .filter((pkg) => multiScriptSelectorSpecified ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) : !pkg.package.manifest.scripts?.[scriptName]) .map((pkg) => pkg.package.manifest.name ?? pkg.package.dir) if (missingScriptPackages.length) { throw new PnpmError('RECURSIVE_RUN_NO_SCRIPT', `Missing script "${scriptName}" in packages: ${missingScriptPackages.join(', ')}`) @@ -93,7 +93,7 @@ export async function runRecursive ( } const pkg = opts.selectedProjectsGraph[prefix] - const specifiedScriptsWithSelector = Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) + const specifiedScriptsWithSelector = Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) return specifiedScriptsWithSelector.map(script => ({ prefix, scriptName: script })) }).flat() From ec8e4ee7321ceabd8d265e83f3d2149a6a4b8962 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Fri, 13 Jan 2023 00:31:36 +0900 Subject: [PATCH 12/32] fixup! test: run unit test for multi script commands selector --- .../test/index.ts | 28 ++++++-- .../test/runRecursive.ts | 72 ++++++++++++++----- 2 files changed, 76 insertions(+), 24 deletions(-) diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index 8e9c72e4e00..e404c5d8063 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -469,9 +469,14 @@ test('pnpm run with custom shell', async () => { test('pnpm run with multiple script selector should work', async () => { prepare({ scripts: { - 'build:a': 'node -e "require(\'fs\').writeFileSync(\'./output-a.txt\', \'a\', \'utf8\')"', - 'build:b': 'node -e "require(\'fs\').writeFileSync(\'./output-b.txt\', \'b\', \'utf8\')"', - 'build:c': 'node -e "require(\'fs\').writeFileSync(\'./output-c.txt\', \'c\', \'utf8\')"', + 'build:a': 'node -e "require(\'fs\').writeFileSync(\'./output-build-a.txt\', \'a\', \'utf8\')"', + 'build:b': 'node -e "require(\'fs\').writeFileSync(\'./output-build-b.txt\', \'b\', \'utf8\')"', + 'build:c': 'node -e "require(\'fs\').writeFileSync(\'./output-build-c.txt\', \'c\', \'utf8\')"', + build: 'node -e "require(\'fs\').writeFileSync(\'./output-build-a.txt\', \'should not run\', \'utf8\')"', + 'lint:a': 'node -e "require(\'fs\').writeFileSync(\'./output-lint-a.txt\', \'a\', \'utf8\')"', + 'lint:b': 'node -e "require(\'fs\').writeFileSync(\'./output-lint-b.txt\', \'b\', \'utf8\')"', + 'lint:c': 'node -e "require(\'fs\').writeFileSync(\'./output-lint-c.txt\', \'c\', \'utf8\')"', + lint: 'node -e "require(\'fs\').writeFileSync(\'./output-lint-a.txt\', \'should not run\', \'utf8\')"', }, }) @@ -482,9 +487,20 @@ test('pnpm run with multiple script selector should work', async () => { rawConfig: {}, }, ['build:*']) - expect(await fs.readFile('output-a.txt', { encoding: 'utf-8' })).toEqual('a') - expect(await fs.readFile('output-b.txt', { encoding: 'utf-8' })).toEqual('b') - expect(await fs.readFile('output-c.txt', { encoding: 'utf-8' })).toEqual('c') + expect(await fs.readFile('output-build-a.txt', { encoding: 'utf-8' })).toEqual('a') + expect(await fs.readFile('output-build-b.txt', { encoding: 'utf-8' })).toEqual('b') + expect(await fs.readFile('output-build-c.txt', { encoding: 'utf-8' })).toEqual('c') + + await run.handler({ + dir: process.cwd(), + extraBinPaths: [], + extraEnv: {}, + rawConfig: {}, + }, ['lint:*']) + + expect(await fs.readFile('output-lint-a.txt', { encoding: 'utf-8' })).toEqual('a') + expect(await fs.readFile('output-lint-b.txt', { encoding: 'utf-8' })).toEqual('b') + expect(await fs.readFile('output-lint-c.txt', { encoding: 'utf-8' })).toEqual('c') }) test('pnpm run with multiple script selector should work also for pre/post script', async () => { diff --git a/exec/plugin-commands-script-runners/test/runRecursive.ts b/exec/plugin-commands-script-runners/test/runRecursive.ts index c3bdd094977..153b2c108b2 100644 --- a/exec/plugin-commands-script-runners/test/runRecursive.ts +++ b/exec/plugin-commands-script-runners/test/runRecursive.ts @@ -898,27 +898,42 @@ test('pnpm run with multiple script selector should work on recursive', async () name: 'project-1', version: '1.0.0', scripts: { - 'build:a': 'node -e "require(\'fs\').writeFileSync(\'../output-1-a.txt\', \'1-a\', \'utf8\')"', - 'build:b': 'node -e "require(\'fs\').writeFileSync(\'../output-1-b.txt\', \'1-b\', \'utf8\')"', - 'build:c': 'node -e "require(\'fs\').writeFileSync(\'../output-1-c.txt\', \'1-c\', \'utf8\')"', + 'build:a': 'node -e "require(\'fs\').writeFileSync(\'../output-build-1-a.txt\', \'1-a\', \'utf8\')"', + 'build:b': 'node -e "require(\'fs\').writeFileSync(\'../output-build-1-b.txt\', \'1-b\', \'utf8\')"', + 'build:c': 'node -e "require(\'fs\').writeFileSync(\'../output-build-1-c.txt\', \'1-c\', \'utf8\')"', + build: 'node -e "require(\'fs\').writeFileSync(\'../output-build-1-a.txt\', \'should not run\', \'utf8\')"', + 'lint:a': 'node -e "require(\'fs\').writeFileSync(\'../output-lint-1-a.txt\', \'1-a\', \'utf8\')"', + 'lint:b': 'node -e "require(\'fs\').writeFileSync(\'../output-lint-1-b.txt\', \'1-b\', \'utf8\')"', + 'lint:c': 'node -e "require(\'fs\').writeFileSync(\'../output-lint-1-c.txt\', \'1-c\', \'utf8\')"', + lint: 'node -e "require(\'fs\').writeFileSync(\'../output-lint-1-a.txt\', \'should not run\', \'utf8\')"', }, }, { name: 'project-2', version: '1.0.0', scripts: { - 'build:a': 'node -e "require(\'fs\').writeFileSync(\'../output-2-a.txt\', \'2-a\', \'utf8\')"', - 'build:b': 'node -e "require(\'fs\').writeFileSync(\'../output-2-b.txt\', \'2-b\', \'utf8\')"', - 'build:c': 'node -e "require(\'fs\').writeFileSync(\'../output-2-c.txt\', \'2-c\', \'utf8\')"', + 'build:a': 'node -e "require(\'fs\').writeFileSync(\'../output-build-2-a.txt\', \'2-a\', \'utf8\')"', + 'build:b': 'node -e "require(\'fs\').writeFileSync(\'../output-build-2-b.txt\', \'2-b\', \'utf8\')"', + 'build:c': 'node -e "require(\'fs\').writeFileSync(\'../output-build-2-c.txt\', \'2-c\', \'utf8\')"', + build: 'node -e "require(\'fs\').writeFileSync(\'../output-build-2-a.txt\', \'should not run\', \'utf8\')"', + 'lint:a': 'node -e "require(\'fs\').writeFileSync(\'../output-lint-2-a.txt\', \'2-a\', \'utf8\')"', + 'lint:b': 'node -e "require(\'fs\').writeFileSync(\'../output-lint-2-b.txt\', \'2-b\', \'utf8\')"', + 'lint:c': 'node -e "require(\'fs\').writeFileSync(\'../output-lint-2-c.txt\', \'2-c\', \'utf8\')"', + lint: 'node -e "require(\'fs\').writeFileSync(\'../output-lint-2-a.txt\', \'should not run\', \'utf8\')"', }, }, { name: 'project-3', version: '1.0.0', scripts: { - 'build:a': 'node -e "require(\'fs\').writeFileSync(\'../output-3-a.txt\', \'3-a\', \'utf8\')"', - 'build:b': 'node -e "require(\'fs\').writeFileSync(\'../output-3-b.txt\', \'3-b\', \'utf8\')"', - 'build:c': 'node -e "require(\'fs\').writeFileSync(\'../output-3-c.txt\', \'3-c\', \'utf8\')"', + 'build:a': 'node -e "require(\'fs\').writeFileSync(\'../output-build-3-a.txt\', \'3-a\', \'utf8\')"', + 'build:b': 'node -e "require(\'fs\').writeFileSync(\'../output-build-3-b.txt\', \'3-b\', \'utf8\')"', + 'build:c': 'node -e "require(\'fs\').writeFileSync(\'../output-build-3-c.txt\', \'3-c\', \'utf8\')"', + build: 'node -e "require(\'fs\').writeFileSync(\'../output-build-3-a.txt\', \'should not run\', \'utf8\')"', + 'lint:a': 'node -e "require(\'fs\').writeFileSync(\'../output-lint-3-a.txt\', \'3-a\', \'utf8\')"', + 'lint:b': 'node -e "require(\'fs\').writeFileSync(\'../output-lint-3-b.txt\', \'3-b\', \'utf8\')"', + 'lint:c': 'node -e "require(\'fs\').writeFileSync(\'../output-lint-3-c.txt\', \'3-c\', \'utf8\')"', + lint: 'node -e "require(\'fs\').writeFileSync(\'../output-lint-3-a.txt\', \'should not run\', \'utf8\')"', }, }, ]) @@ -942,13 +957,34 @@ test('pnpm run with multiple script selector should work on recursive', async () }, workspaceDir: process.cwd(), }, ['build:*']) - expect(await fs.readFile('output-1-a.txt', { encoding: 'utf-8' })).toEqual('1-a') - expect(await fs.readFile('output-1-b.txt', { encoding: 'utf-8' })).toEqual('1-b') - expect(await fs.readFile('output-1-c.txt', { encoding: 'utf-8' })).toEqual('1-c') - expect(await fs.readFile('output-2-a.txt', { encoding: 'utf-8' })).toEqual('2-a') - expect(await fs.readFile('output-2-b.txt', { encoding: 'utf-8' })).toEqual('2-b') - expect(await fs.readFile('output-2-c.txt', { encoding: 'utf-8' })).toEqual('2-c') - expect(await fs.readFile('output-3-a.txt', { encoding: 'utf-8' })).toEqual('3-a') - expect(await fs.readFile('output-3-b.txt', { encoding: 'utf-8' })).toEqual('3-b') - expect(await fs.readFile('output-3-c.txt', { encoding: 'utf-8' })).toEqual('3-c') + expect(await fs.readFile('output-build-1-a.txt', { encoding: 'utf-8' })).toEqual('1-a') + expect(await fs.readFile('output-build-1-b.txt', { encoding: 'utf-8' })).toEqual('1-b') + expect(await fs.readFile('output-build-1-c.txt', { encoding: 'utf-8' })).toEqual('1-c') + expect(await fs.readFile('output-build-2-a.txt', { encoding: 'utf-8' })).toEqual('2-a') + expect(await fs.readFile('output-build-2-b.txt', { encoding: 'utf-8' })).toEqual('2-b') + expect(await fs.readFile('output-build-2-c.txt', { encoding: 'utf-8' })).toEqual('2-c') + expect(await fs.readFile('output-build-3-a.txt', { encoding: 'utf-8' })).toEqual('3-a') + expect(await fs.readFile('output-build-3-b.txt', { encoding: 'utf-8' })).toEqual('3-b') + expect(await fs.readFile('output-build-3-c.txt', { encoding: 'utf-8' })).toEqual('3-c') + + await run.handler({ + ...DEFAULT_OPTS, + ...await readProjects(process.cwd(), [{ namePattern: '*' }]), + dir: process.cwd(), + recursive: true, + rootProjectManifest: { + name: 'test-workspaces', + private: true, + }, + workspaceDir: process.cwd(), + }, ['lint:*']) + expect(await fs.readFile('output-lint-1-a.txt', { encoding: 'utf-8' })).toEqual('1-a') + expect(await fs.readFile('output-lint-1-b.txt', { encoding: 'utf-8' })).toEqual('1-b') + expect(await fs.readFile('output-lint-1-c.txt', { encoding: 'utf-8' })).toEqual('1-c') + expect(await fs.readFile('output-lint-2-a.txt', { encoding: 'utf-8' })).toEqual('2-a') + expect(await fs.readFile('output-lint-2-b.txt', { encoding: 'utf-8' })).toEqual('2-b') + expect(await fs.readFile('output-lint-2-c.txt', { encoding: 'utf-8' })).toEqual('2-c') + expect(await fs.readFile('output-lint-3-a.txt', { encoding: 'utf-8' })).toEqual('3-a') + expect(await fs.readFile('output-lint-3-b.txt', { encoding: 'utf-8' })).toEqual('3-b') + expect(await fs.readFile('output-lint-3-c.txt', { encoding: 'utf-8' })).toEqual('3-c') }) From bc23e6bc1d7a259fcd5670c1501a8ce015781226 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Wed, 18 Jan 2023 02:00:21 +0900 Subject: [PATCH 13/32] fix: use regexp selector instaed of original syntax --- .../src/regexp-command.ts | 16 ++++++++++++++++ .../plugin-commands-script-runners/src/run.ts | 19 +++++++------------ .../src/runRecursive.ts | 17 +++++------------ .../test/index.ts | 15 ++++----------- .../test/runRecursive.ts | 13 +------------ 5 files changed, 33 insertions(+), 47 deletions(-) create mode 100644 exec/plugin-commands-script-runners/src/regexp-command.ts diff --git a/exec/plugin-commands-script-runners/src/regexp-command.ts b/exec/plugin-commands-script-runners/src/regexp-command.ts new file mode 100644 index 00000000000..d83bde264d9 --- /dev/null +++ b/exec/plugin-commands-script-runners/src/regexp-command.ts @@ -0,0 +1,16 @@ +export function isRegExpCommand ( + command: string +) { + const flags = ['d', 'g', 'i', 'm', 's', 'u', 'y'] + return (new RegExp(`^/.+/(${flags.join('|')})*$`)).test(command) +} + +export function buildRegExpFromCommand ( + command: string +): RegExp | null { + if (!isRegExpCommand(command)) { + return null + } + + return new RegExp(command.slice(0, command.lastIndexOf('/')).slice(1)) +} \ No newline at end of file diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index 389605533b8..470cf850949 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -21,6 +21,7 @@ import renderHelp from 'render-help' import { runRecursive, RecursiveRunOpts } from './runRecursive' import { existsInDir } from './existsInDir' import { handler as exec } from './exec' +import { buildRegExpFromCommand } from './regexp-command' export const IF_PRESENT_OPTION = { 'if-present': Boolean, @@ -171,12 +172,10 @@ export async function handler ( return printProjectCommands(manifest, rootManifest ?? undefined) } - const multiScriptSelectorSpecified = scriptName.slice(-2) === ':*' - // extract XXX:YYY: from XXX:YYY:ZZZ - const multiScriptSelectorPrefix = scriptName.slice(0, -1) - const specifiedScriptsWithSelector = Object.keys(manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) + const multiScriptSelectorRegExp = buildRegExpFromCommand(scriptName) + const specifiedScripts = multiScriptSelectorRegExp ? Object.keys(manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) : manifest.scripts?.[scriptName] ? [scriptName] : [] - if (scriptName !== 'start' && !manifest.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithSelector.length > 0)) { + if (scriptName !== 'start' && specifiedScripts.length < 1) { if (opts.ifPresent) return if (opts.fallbackCommandUsed) { if (opts.argv == null) throw new Error('Could not fallback because opts.argv.original was not passed to the script runner') @@ -187,7 +186,7 @@ export async function handler ( } if (opts.workspaceDir) { const { manifest: rootManifest } = await tryReadProjectManifest(opts.workspaceDir, opts) - if (rootManifest?.scripts?.[scriptName] && !(multiScriptSelectorSpecified && specifiedScriptsWithSelector.length > 0)) { + if (rootManifest?.scripts?.[scriptName] && specifiedScripts.length < 1) { throw new PnpmError('NO_SCRIPT', `Missing script: ${scriptName}`, { hint: `But ${scriptName} is present in the root of the workspace, so you may run "pnpm -w run ${scriptName}"`, @@ -220,13 +219,9 @@ so you may run "pnpm -w run ${scriptName}"`, } } try { - if (multiScriptSelectorSpecified) { - const limitRun = pLimit(opts.workspaceConcurrency ?? 4) + const limitRun = pLimit(opts.workspaceConcurrency ?? 4) - await Promise.all(specifiedScriptsWithSelector.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) - } else { - await runScript(scriptName, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) - } + await Promise.all(specifiedScripts.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) } catch (err: any) { // eslint-disable-line if (opts.bail !== false) { throw err diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index 0ab8d8d5b31..6d37b5ef798 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -13,6 +13,7 @@ import realpathMissing from 'realpath-missing' import { existsInDir } from './existsInDir' import { getResumedPackageChunks } from './exec' import { runScript } from './run' +import { buildRegExpFromCommand } from './regexp-command' export type RecursiveRunOpts = Pick opts.selectedProjectsGraph[prefix]) - .filter((pkg) => multiScriptSelectorSpecified ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) : !pkg.package.manifest.scripts?.[scriptName]) + .filter((pkg) => multiScriptSelectorRegExp ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.match(multiScriptSelectorRegExp)) : !pkg.package.manifest.scripts?.[scriptName]) .map((pkg) => pkg.package.manifest.name ?? pkg.package.dir) if (missingScriptPackages.length) { throw new PnpmError('RECURSIVE_RUN_NO_SCRIPT', `Missing script "${scriptName}" in packages: ${missingScriptPackages.join(', ')}`) @@ -85,17 +85,10 @@ export async function runRecursive ( for (const chunk of packageChunks) { const selectedScripts = chunk.map(prefix => { - if (!multiScriptSelectorSpecified) { - return { - prefix, - scriptName, - } - } - const pkg = opts.selectedProjectsGraph[prefix] - const specifiedScriptsWithSelector = Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.startsWith(multiScriptSelectorPrefix) && script !== multiScriptSelectorPrefix) + const specifiedScripts = multiScriptSelectorRegExp ? Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) : [scriptName] - return specifiedScriptsWithSelector.map(script => ({ prefix, scriptName: script })) + return specifiedScripts.map(script => ({ prefix, scriptName: script })) }).flat() await Promise.all(selectedScripts.map(async ({ prefix, scriptName }) => diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index e404c5d8063..a5ca7862721 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -485,19 +485,12 @@ test('pnpm run with multiple script selector should work', async () => { extraBinPaths: [], extraEnv: {}, rawConfig: {}, - }, ['build:*']) + }, ['/^(lint|build):.*/']) expect(await fs.readFile('output-build-a.txt', { encoding: 'utf-8' })).toEqual('a') expect(await fs.readFile('output-build-b.txt', { encoding: 'utf-8' })).toEqual('b') expect(await fs.readFile('output-build-c.txt', { encoding: 'utf-8' })).toEqual('c') - await run.handler({ - dir: process.cwd(), - extraBinPaths: [], - extraEnv: {}, - rawConfig: {}, - }, ['lint:*']) - expect(await fs.readFile('output-lint-a.txt', { encoding: 'utf-8' })).toEqual('a') expect(await fs.readFile('output-lint-b.txt', { encoding: 'utf-8' })).toEqual('b') expect(await fs.readFile('output-lint-c.txt', { encoding: 'utf-8' })).toEqual('c') @@ -517,7 +510,7 @@ test('pnpm run with multiple script selector should work also for pre/post scrip extraEnv: {}, rawConfig: {}, enablePrePostScripts: true, - }, ['build:*']) + }, ['/build:.*/']) expect(await fs.readFile('output-a.txt', { encoding: 'utf-8' })).toEqual('a') expect(await fs.readFile('output-pre-a.txt', { encoding: 'utf-8' })).toEqual('pre-a') @@ -538,7 +531,7 @@ test('pnpm run with multiple script selector should work parallel as a default b extraBinPaths: [], extraEnv: {}, rawConfig: {}, - }, ['build:*']) + }, ['/build:.*/']) const { default: outputsA } = await import(path.resolve('output-a.json')) const { default: outputsB } = await import(path.resolve('output-b.json')) @@ -562,7 +555,7 @@ test('pnpm run with multiple script selector should work sequentially with --wor extraEnv: {}, rawConfig: {}, workspaceConcurrency: 1, - }, ['build:*']) + }, ['/build:.*/']) const { default: outputsA } = await import(path.resolve('output-a.json')) const { default: outputsB } = await import(path.resolve('output-b.json')) diff --git a/exec/plugin-commands-script-runners/test/runRecursive.ts b/exec/plugin-commands-script-runners/test/runRecursive.ts index 153b2c108b2..74341e9e8e4 100644 --- a/exec/plugin-commands-script-runners/test/runRecursive.ts +++ b/exec/plugin-commands-script-runners/test/runRecursive.ts @@ -956,7 +956,7 @@ test('pnpm run with multiple script selector should work on recursive', async () private: true, }, workspaceDir: process.cwd(), - }, ['build:*']) + }, ['/^(lint|build):.*/']) expect(await fs.readFile('output-build-1-a.txt', { encoding: 'utf-8' })).toEqual('1-a') expect(await fs.readFile('output-build-1-b.txt', { encoding: 'utf-8' })).toEqual('1-b') expect(await fs.readFile('output-build-1-c.txt', { encoding: 'utf-8' })).toEqual('1-c') @@ -967,17 +967,6 @@ test('pnpm run with multiple script selector should work on recursive', async () expect(await fs.readFile('output-build-3-b.txt', { encoding: 'utf-8' })).toEqual('3-b') expect(await fs.readFile('output-build-3-c.txt', { encoding: 'utf-8' })).toEqual('3-c') - await run.handler({ - ...DEFAULT_OPTS, - ...await readProjects(process.cwd(), [{ namePattern: '*' }]), - dir: process.cwd(), - recursive: true, - rootProjectManifest: { - name: 'test-workspaces', - private: true, - }, - workspaceDir: process.cwd(), - }, ['lint:*']) expect(await fs.readFile('output-lint-1-a.txt', { encoding: 'utf-8' })).toEqual('1-a') expect(await fs.readFile('output-lint-1-b.txt', { encoding: 'utf-8' })).toEqual('1-b') expect(await fs.readFile('output-lint-1-c.txt', { encoding: 'utf-8' })).toEqual('1-c') From 274a0766f733e7655e65aa2dc3273e4800b24c1b Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Thu, 19 Jan 2023 20:23:29 +0900 Subject: [PATCH 14/32] fixup! fix: use regexp selector instaed of original syntax --- .../src/regexp-command.ts | 19 +++++++++---------- .../plugin-commands-script-runners/src/run.ts | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/regexp-command.ts b/exec/plugin-commands-script-runners/src/regexp-command.ts index d83bde264d9..933de387e50 100644 --- a/exec/plugin-commands-script-runners/src/regexp-command.ts +++ b/exec/plugin-commands-script-runners/src/regexp-command.ts @@ -1,16 +1,15 @@ -export function isRegExpCommand ( - command: string -) { - const flags = ['d', 'g', 'i', 'm', 's', 'u', 'y'] - return (new RegExp(`^/.+/(${flags.join('|')})*$`)).test(command) -} - export function buildRegExpFromCommand ( command: string ): RegExp | null { - if (!isRegExpCommand(command)) { + if (command.length < 3) { + return null + } + if (command[0] !== '/' || command.lastIndexOf('/') < 1) { + return null + } + try { + return new RegExp(command.slice(0, command.lastIndexOf('/')).slice(1)) + } catch { return null } - - return new RegExp(command.slice(0, command.lastIndexOf('/')).slice(1)) } \ No newline at end of file diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index 470cf850949..45774ed7d5f 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -173,7 +173,7 @@ export async function handler ( } const multiScriptSelectorRegExp = buildRegExpFromCommand(scriptName) - const specifiedScripts = multiScriptSelectorRegExp ? Object.keys(manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) : manifest.scripts?.[scriptName] ? [scriptName] : [] + const specifiedScripts = multiScriptSelectorRegExp ? Object.keys(manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) : (!!manifest.scripts?.[scriptName] || scriptName === 'start') ? [scriptName] : [] if (scriptName !== 'start' && specifiedScripts.length < 1) { if (opts.ifPresent) return From 671a063f899668b65a70650b82599619ecb08ddf Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sat, 28 Jan 2023 04:22:27 +0900 Subject: [PATCH 15/32] fixup! fix: use regexp selector instaed of original syntax --- .../src/{regexp-command.ts => regexpCommand.ts} | 0 exec/plugin-commands-script-runners/src/run.ts | 2 +- exec/plugin-commands-script-runners/src/runRecursive.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename exec/plugin-commands-script-runners/src/{regexp-command.ts => regexpCommand.ts} (100%) diff --git a/exec/plugin-commands-script-runners/src/regexp-command.ts b/exec/plugin-commands-script-runners/src/regexpCommand.ts similarity index 100% rename from exec/plugin-commands-script-runners/src/regexp-command.ts rename to exec/plugin-commands-script-runners/src/regexpCommand.ts diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index 45774ed7d5f..da18745fb36 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -21,7 +21,7 @@ import renderHelp from 'render-help' import { runRecursive, RecursiveRunOpts } from './runRecursive' import { existsInDir } from './existsInDir' import { handler as exec } from './exec' -import { buildRegExpFromCommand } from './regexp-command' +import { buildRegExpFromCommand } from './regexpCommand' export const IF_PRESENT_OPTION = { 'if-present': Boolean, diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index 6d37b5ef798..e64238b99ec 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -13,7 +13,7 @@ import realpathMissing from 'realpath-missing' import { existsInDir } from './existsInDir' import { getResumedPackageChunks } from './exec' import { runScript } from './run' -import { buildRegExpFromCommand } from './regexp-command' +import { buildRegExpFromCommand } from './regexpCommand' export type RecursiveRunOpts = Pick Date: Sat, 28 Jan 2023 04:30:20 +0900 Subject: [PATCH 16/32] fix: update help message of --sequential option more simple --- exec/plugin-commands-script-runners/src/run.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index da18745fb36..cee679b4654 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -46,8 +46,7 @@ export const RESUME_FROM_OPTION_HELP = { } export const SEQUENTIAL_OPTION_HELP = { - description: 'Run a multiple scripts specified with workspace selector or a wildcard script selector sequential.\ -This is the preferred flag for tasks which has dependency on execution order.', + description: 'Run specified scripts one after one.', name: '--sequential', } From e14053198946d5dcf95f758916337f5fe39c45cb Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sat, 28 Jan 2023 04:33:12 +0900 Subject: [PATCH 17/32] fixup! fix: use regexp selector instaed of original syntax --- exec/plugin-commands-script-runners/src/regexpCommand.ts | 2 +- exec/plugin-commands-script-runners/src/run.ts | 6 +++--- exec/plugin-commands-script-runners/src/runRecursive.ts | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/regexpCommand.ts b/exec/plugin-commands-script-runners/src/regexpCommand.ts index 933de387e50..eb5bef54fd3 100644 --- a/exec/plugin-commands-script-runners/src/regexpCommand.ts +++ b/exec/plugin-commands-script-runners/src/regexpCommand.ts @@ -1,4 +1,4 @@ -export function buildRegExpFromCommand ( +export function tryBuildRegExpFromCommand ( command: string ): RegExp | null { if (command.length < 3) { diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index cee679b4654..802f54a8f36 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -21,7 +21,7 @@ import renderHelp from 'render-help' import { runRecursive, RecursiveRunOpts } from './runRecursive' import { existsInDir } from './existsInDir' import { handler as exec } from './exec' -import { buildRegExpFromCommand } from './regexpCommand' +import { tryBuildRegExpFromCommand } from './regexpCommand' export const IF_PRESENT_OPTION = { 'if-present': Boolean, @@ -171,8 +171,8 @@ export async function handler ( return printProjectCommands(manifest, rootManifest ?? undefined) } - const multiScriptSelectorRegExp = buildRegExpFromCommand(scriptName) - const specifiedScripts = multiScriptSelectorRegExp ? Object.keys(manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) : (!!manifest.scripts?.[scriptName] || scriptName === 'start') ? [scriptName] : [] + const scriptSelector = tryBuildRegExpFromCommand(scriptName) + const specifiedScripts = scriptSelector ? Object.keys(manifest.scripts ?? {}).filter(script => script.match(scriptSelector)) : (!!manifest.scripts?.[scriptName] || scriptName === 'start') ? [scriptName] : [] if (scriptName !== 'start' && specifiedScripts.length < 1) { if (opts.ifPresent) return diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index e64238b99ec..d7cba50c6c6 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -13,7 +13,7 @@ import realpathMissing from 'realpath-missing' import { existsInDir } from './existsInDir' import { getResumedPackageChunks } from './exec' import { runScript } from './run' -import { buildRegExpFromCommand } from './regexpCommand' +import { tryBuildRegExpFromCommand } from './regexpCommand' export type RecursiveRunOpts = Pick opts.selectedProjectsGraph[prefix]) - .filter((pkg) => multiScriptSelectorRegExp ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.match(multiScriptSelectorRegExp)) : !pkg.package.manifest.scripts?.[scriptName]) + .filter((pkg) => scriptSelector ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.match(scriptSelector)) : !pkg.package.manifest.scripts?.[scriptName]) .map((pkg) => pkg.package.manifest.name ?? pkg.package.dir) if (missingScriptPackages.length) { throw new PnpmError('RECURSIVE_RUN_NO_SCRIPT', `Missing script "${scriptName}" in packages: ${missingScriptPackages.join(', ')}`) @@ -86,7 +86,7 @@ export async function runRecursive ( for (const chunk of packageChunks) { const selectedScripts = chunk.map(prefix => { const pkg = opts.selectedProjectsGraph[prefix] - const specifiedScripts = multiScriptSelectorRegExp ? Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.match(multiScriptSelectorRegExp)) : [scriptName] + const specifiedScripts = scriptSelector ? Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.match(scriptSelector)) : [scriptName] return specifiedScripts.map(script => ({ prefix, scriptName: script })) }).flat() From f6fe3cfd3d838972e54f327f360efa3bbd03e8c2 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sat, 28 Jan 2023 04:53:26 +0900 Subject: [PATCH 18/32] fixup! fix: use regexp selector instaed of original syntax --- .../plugin-commands-script-runners/src/run.ts | 27 ++++++++++++++++--- .../src/runRecursive.ts | 22 ++++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index 802f54a8f36..e63dc09d16a 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -14,7 +14,7 @@ import { makeNodeRequireOption, RunLifecycleHookOptions, } from '@pnpm/lifecycle' -import { ProjectManifest } from '@pnpm/types' +import { PackageScripts, ProjectManifest } from '@pnpm/types' import pick from 'ramda/src/pick' import realpathMissing from 'realpath-missing' import renderHelp from 'render-help' @@ -171,8 +171,7 @@ export async function handler ( return printProjectCommands(manifest, rootManifest ?? undefined) } - const scriptSelector = tryBuildRegExpFromCommand(scriptName) - const specifiedScripts = scriptSelector ? Object.keys(manifest.scripts ?? {}).filter(script => script.match(scriptSelector)) : (!!manifest.scripts?.[scriptName] || scriptName === 'start') ? [scriptName] : [] + const specifiedScripts = getSpecifiedScripts(manifest.scripts ?? {}, scriptName) if (scriptName !== 'start' && specifiedScripts.length < 1) { if (opts.ifPresent) return @@ -328,3 +327,25 @@ export const runScript: (scriptName: string, manifest: ProjectManifest, lifecycl function renderCommands (commands: string[][]) { return commands.map(([scriptName, script]) => ` ${scriptName}\n ${script}`).join('\n') } + +function getSpecifiedScripts (scripts: PackageScripts, scriptName: string) { + const scriptSelector = tryBuildRegExpFromCommand(scriptName) + + // if scriptName which a user passes is RegExp (like /build:.*/), multiple scripts to execute will be selected with RegExp + if (scriptSelector) { + const scriptKeys = Object.keys(scripts) + return scriptKeys.filter(script => script.match(scriptSelector)) + } + + // if scripts in package.json has script which is equal to scriptName a user passes, return it. + if (scripts[scriptName]) { + return [scriptName] + } + + // if a user passes start command as scriptName, `node server.js` will be executed as a fallback, so return start command even if start command is not defined in package.json + if (scriptName === 'start') { + return [scriptName] + } + + return [] +} diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index d7cba50c6c6..d213bcdf9b3 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -14,6 +14,7 @@ import { existsInDir } from './existsInDir' import { getResumedPackageChunks } from './exec' import { runScript } from './run' import { tryBuildRegExpFromCommand } from './regexpCommand' +import { PackageScripts } from '@pnpm/types' export type RecursiveRunOpts = Pick opts.selectedProjectsGraph[prefix]) - .filter((pkg) => scriptSelector ? !Object.keys(pkg.package.manifest.scripts ?? {}).some(script => script.match(scriptSelector)) : !pkg.package.manifest.scripts?.[scriptName]) + .filter((pkg) => getSpecifiedScripts(pkg.package.manifest.scripts ?? {}, scriptName).length < 1) .map((pkg) => pkg.package.manifest.name ?? pkg.package.dir) if (missingScriptPackages.length) { throw new PnpmError('RECURSIVE_RUN_NO_SCRIPT', `Missing script "${scriptName}" in packages: ${missingScriptPackages.join(', ')}`) @@ -86,7 +85,7 @@ export async function runRecursive ( for (const chunk of packageChunks) { const selectedScripts = chunk.map(prefix => { const pkg = opts.selectedProjectsGraph[prefix] - const specifiedScripts = scriptSelector ? Object.keys(pkg.package.manifest.scripts ?? {}).filter(script => script.match(scriptSelector)) : [scriptName] + const specifiedScripts = getSpecifiedScripts(pkg.package.manifest.scripts ?? {}, scriptName) return specifiedScripts.map(script => ({ prefix, scriptName: script })) }).flat() @@ -161,3 +160,18 @@ export async function runRecursive ( throwOnCommandFail('pnpm recursive run', result) } + +function getSpecifiedScripts (scripts: PackageScripts, scriptName: string) { + const scriptSelector = tryBuildRegExpFromCommand(scriptName) + + if (scriptSelector) { + const scriptKeys = Object.keys(scripts) + return scriptKeys.filter(script => script.match(scriptSelector)) + } + + if (scripts[scriptName]) { + return [scriptName] + } + + return [] +} From 8a70297dca054139c177c94dd501d4d5098d17f7 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sat, 28 Jan 2023 05:16:49 +0900 Subject: [PATCH 19/32] fixup! fix: use regexp selector instaed of original syntax --- exec/plugin-commands-script-runners/src/run.ts | 6 +++--- exec/plugin-commands-script-runners/test/index.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index e63dc09d16a..4c891cbcf48 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -173,7 +173,7 @@ export async function handler ( const specifiedScripts = getSpecifiedScripts(manifest.scripts ?? {}, scriptName) - if (scriptName !== 'start' && specifiedScripts.length < 1) { + if (specifiedScripts.length < 1) { if (opts.ifPresent) return if (opts.fallbackCommandUsed) { if (opts.argv == null) throw new Error('Could not fallback because opts.argv.original was not passed to the script runner') @@ -184,9 +184,9 @@ export async function handler ( } if (opts.workspaceDir) { const { manifest: rootManifest } = await tryReadProjectManifest(opts.workspaceDir, opts) - if (rootManifest?.scripts?.[scriptName] && specifiedScripts.length < 1) { + if (getSpecifiedScripts(rootManifest.scripts ?? {}, scriptName).length > 0 && specifiedScripts.length < 1) { throw new PnpmError('NO_SCRIPT', `Missing script: ${scriptName}`, { - hint: `But ${scriptName} is present in the root of the workspace, + hint: `But script matched with ${scriptName} is present in the root of the workspace, so you may run "pnpm -w run ${scriptName}"`, }) } diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index a5ca7862721..8fe4f51c170 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -411,7 +411,7 @@ test('if a script is not found but is present in the root, print an info message } expect(err).toBeTruthy() - expect(err.hint).toMatch(/But build is present in the root/) + expect(err.hint).toMatch(/But script matched with build is present in the root/) }) test('scripts work with PnP', async () => { From f917b1ff7a5beb356277ffad711c65b5f052f37f Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sat, 28 Jan 2023 05:22:19 +0900 Subject: [PATCH 20/32] fix: make the process in map callback to invoke runScript more simple --- .../plugin-commands-script-runners/src/run.ts | 29 ++++++++++++------- .../src/runRecursive.ts | 3 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index 4c891cbcf48..d56edeaed36 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -219,7 +219,9 @@ so you may run "pnpm -w run ${scriptName}"`, try { const limitRun = pLimit(opts.workspaceConcurrency ?? 4) - await Promise.all(specifiedScripts.map(script => limitRun(() => runScript(script, manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs)))) + const _runScript = runScript.bind(null, { manifest, lifecycleOpts, runScriptOptions: { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs }) + + await Promise.all(specifiedScripts.map(script => limitRun(() => _runScript(script)))) } catch (err: any) { // eslint-disable-line if (opts.bail !== false) { throw err @@ -306,21 +308,26 @@ export interface RunScriptOptions { enablePrePostScripts: boolean } -export const runScript: (scriptName: string, manifest: ProjectManifest, lifecycleOpts: RunLifecycleHookOptions, runScriptOptions: RunScriptOptions, passedThruArgs: string[]) => Promise = async function (scriptName, manifest, lifecycleOpts, runScriptOptions, passedThruArgs) { +export const runScript: (opts: { + manifest: ProjectManifest + lifecycleOpts: RunLifecycleHookOptions + runScriptOptions: RunScriptOptions + passedThruArgs: string[] +}, scriptName: string) => Promise = async function (opts, scriptName) { if ( - runScriptOptions.enablePrePostScripts && - manifest.scripts?.[`pre${scriptName}`] && - !manifest.scripts[scriptName].includes(`pre${scriptName}`) + opts.runScriptOptions.enablePrePostScripts && + opts.manifest.scripts?.[`pre${scriptName}`] && + !opts.manifest.scripts[scriptName].includes(`pre${scriptName}`) ) { - await runLifecycleHook(`pre${scriptName}`, manifest, lifecycleOpts) + await runLifecycleHook(`pre${scriptName}`, opts.manifest, opts.lifecycleOpts) } - await runLifecycleHook(scriptName, manifest, { ...lifecycleOpts, args: passedThruArgs }) + await runLifecycleHook(scriptName, opts.manifest, { ...opts.lifecycleOpts, args: opts.passedThruArgs }) if ( - runScriptOptions.enablePrePostScripts && - manifest.scripts?.[`post${scriptName}`] && - !manifest.scripts[scriptName].includes(`post${scriptName}`) + opts.runScriptOptions.enablePrePostScripts && + opts.manifest.scripts?.[`post${scriptName}`] && + !opts.manifest.scripts[scriptName].includes(`post${scriptName}`) ) { - await runLifecycleHook(`post${scriptName}`, manifest, lifecycleOpts) + await runLifecycleHook(`post${scriptName}`, opts.manifest, opts.lifecycleOpts) } } diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index d213bcdf9b3..4845a0957be 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -123,7 +123,8 @@ export async function runRecursive ( } } - await runScript(scriptName, pkg.package.manifest, lifecycleOpts, { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs) + const _runScript = runScript.bind(null, { manifest: pkg.package.manifest, lifecycleOpts, runScriptOptions: { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs }) + await _runScript(scriptName) result.passes++ } catch (err: any) { // eslint-disable-line logger.info(err) From c02be84adbdae0048dc5ca33fe321099e671ffb9 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sat, 28 Jan 2023 13:15:36 +0900 Subject: [PATCH 21/32] fixup! fixup! fix: use regexp selector instaed of original syntax --- exec/plugin-commands-script-runners/src/run.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index d56edeaed36..089b20a2406 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -184,7 +184,7 @@ export async function handler ( } if (opts.workspaceDir) { const { manifest: rootManifest } = await tryReadProjectManifest(opts.workspaceDir, opts) - if (getSpecifiedScripts(rootManifest.scripts ?? {}, scriptName).length > 0 && specifiedScripts.length < 1) { + if (getSpecifiedScripts(rootManifest?.scripts ?? {}, scriptName).length > 0 && specifiedScripts.length < 1) { throw new PnpmError('NO_SCRIPT', `Missing script: ${scriptName}`, { hint: `But script matched with ${scriptName} is present in the root of the workspace, so you may run "pnpm -w run ${scriptName}"`, From 4802e465b66d9e8e1fd828919b7990bdcec4b73b Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Sun, 29 Jan 2023 17:04:41 +0900 Subject: [PATCH 22/32] fixup! chore: add changeset --- .changeset/silly-flies-care.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/silly-flies-care.md b/.changeset/silly-flies-care.md index 7c7fd61d8d5..29d4d18fcc3 100644 --- a/.changeset/silly-flies-care.md +++ b/.changeset/silly-flies-care.md @@ -1,5 +1,6 @@ --- "@pnpm/plugin-commands-script-runners": minor +"pnpm": minor --- -feat: support wildcard selector to specify multiple scripts to execute" +feat: support script selector with RegExp such as `pnpm run /build:.*/` and execute the matched scripts with the RegExp. From 421a693ae25ff2900f74ea58662f3f25ecbdb771 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Mon, 30 Jan 2023 00:12:44 +0900 Subject: [PATCH 23/32] fixup! fix: use regexp selector instaed of original syntax --- exec/plugin-commands-script-runners/src/regexpCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exec/plugin-commands-script-runners/src/regexpCommand.ts b/exec/plugin-commands-script-runners/src/regexpCommand.ts index eb5bef54fd3..847433d448b 100644 --- a/exec/plugin-commands-script-runners/src/regexpCommand.ts +++ b/exec/plugin-commands-script-runners/src/regexpCommand.ts @@ -12,4 +12,4 @@ export function tryBuildRegExpFromCommand ( } catch { return null } -} \ No newline at end of file +} From 7db6957d4e3de04b03fc625479374b28733dc3c6 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Mon, 30 Jan 2023 01:38:23 +0900 Subject: [PATCH 24/32] fixup! fix: use regexp selector instaed of original syntax --- exec/plugin-commands-script-runners/src/run.ts | 15 ++------------- .../src/runRecursive.ts | 4 +++- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index 089b20a2406..ce98b2fd2d5 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -336,21 +336,10 @@ function renderCommands (commands: string[][]) { } function getSpecifiedScripts (scripts: PackageScripts, scriptName: string) { - const scriptSelector = tryBuildRegExpFromCommand(scriptName) - - // if scriptName which a user passes is RegExp (like /build:.*/), multiple scripts to execute will be selected with RegExp - if (scriptSelector) { - const scriptKeys = Object.keys(scripts) - return scriptKeys.filter(script => script.match(scriptSelector)) - } - - // if scripts in package.json has script which is equal to scriptName a user passes, return it. - if (scripts[scriptName]) { - return [scriptName] - } + const specifiedSelector = getSpecifiedScripts(scripts, scriptName) // if a user passes start command as scriptName, `node server.js` will be executed as a fallback, so return start command even if start command is not defined in package.json - if (scriptName === 'start') { + if (specifiedSelector.length < 1 && scriptName === 'start') { return [scriptName] } diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index 4845a0957be..3680ad1fd28 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -162,14 +162,16 @@ export async function runRecursive ( throwOnCommandFail('pnpm recursive run', result) } -function getSpecifiedScripts (scripts: PackageScripts, scriptName: string) { +export function getSpecifiedScripts (scripts: PackageScripts, scriptName: string) { const scriptSelector = tryBuildRegExpFromCommand(scriptName) + // if scriptName which a user passes is RegExp (like /build:.*/), multiple scripts to execute will be selected with RegExp if (scriptSelector) { const scriptKeys = Object.keys(scripts) return scriptKeys.filter(script => script.match(scriptSelector)) } + // if scripts in package.json has script which is equal to scriptName a user passes, return it. if (scripts[scriptName]) { return [scriptName] } From acf55fd2ebe6c9fe9e9956b6690adc1d7ee40a3a Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Mon, 30 Jan 2023 01:39:29 +0900 Subject: [PATCH 25/32] fixup! fixup! fix: use regexp selector instaed of original syntax --- .../plugin-commands-script-runners/src/runRecursive.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index 3680ad1fd28..aadf2c3861b 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -165,16 +165,16 @@ export async function runRecursive ( export function getSpecifiedScripts (scripts: PackageScripts, scriptName: string) { const scriptSelector = tryBuildRegExpFromCommand(scriptName) + // if scripts in package.json has script which is equal to scriptName a user passes, return it. + if (scripts[scriptName]) { + return [scriptName] + } + // if scriptName which a user passes is RegExp (like /build:.*/), multiple scripts to execute will be selected with RegExp if (scriptSelector) { const scriptKeys = Object.keys(scripts) return scriptKeys.filter(script => script.match(scriptSelector)) } - // if scripts in package.json has script which is equal to scriptName a user passes, return it. - if (scripts[scriptName]) { - return [scriptName] - } - return [] } From cbc19e7557edf9a2f5286947c5cef473fa8244cc Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Mon, 30 Jan 2023 21:05:44 +0900 Subject: [PATCH 26/32] fixup! fixup! fixup! fix: use regexp selector instaed of original syntax --- exec/plugin-commands-script-runners/src/run.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index ce98b2fd2d5..bb904500683 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -18,10 +18,9 @@ import { PackageScripts, ProjectManifest } from '@pnpm/types' import pick from 'ramda/src/pick' import realpathMissing from 'realpath-missing' import renderHelp from 'render-help' -import { runRecursive, RecursiveRunOpts } from './runRecursive' +import { runRecursive, RecursiveRunOpts, getSpecifiedScripts as getSpecifiedScriptWithoutStartCommand } from './runRecursive' import { existsInDir } from './existsInDir' import { handler as exec } from './exec' -import { tryBuildRegExpFromCommand } from './regexpCommand' export const IF_PRESENT_OPTION = { 'if-present': Boolean, @@ -336,7 +335,7 @@ function renderCommands (commands: string[][]) { } function getSpecifiedScripts (scripts: PackageScripts, scriptName: string) { - const specifiedSelector = getSpecifiedScripts(scripts, scriptName) + const specifiedSelector = getSpecifiedScriptWithoutStartCommand(scripts, scriptName) // if a user passes start command as scriptName, `node server.js` will be executed as a fallback, so return start command even if start command is not defined in package.json if (specifiedSelector.length < 1 && scriptName === 'start') { From 0d81ca469fe9e48ae7e0f4e9486827aee736676f Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Mon, 30 Jan 2023 21:20:46 +0900 Subject: [PATCH 27/32] fixup! fixup! fixup! fix: use regexp selector instaed of original syntax --- exec/plugin-commands-script-runners/src/run.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index bb904500683..f156049141f 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -337,8 +337,12 @@ function renderCommands (commands: string[][]) { function getSpecifiedScripts (scripts: PackageScripts, scriptName: string) { const specifiedSelector = getSpecifiedScriptWithoutStartCommand(scripts, scriptName) + if (specifiedSelector.length > 0) { + return specifiedSelector + } + // if a user passes start command as scriptName, `node server.js` will be executed as a fallback, so return start command even if start command is not defined in package.json - if (specifiedSelector.length < 1 && scriptName === 'start') { + if (scriptName === 'start') { return [scriptName] } From 2b337da7c66a2602ce7f9df2fa6ae1ab3ddb51dc Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Tue, 31 Jan 2023 23:29:08 +0900 Subject: [PATCH 28/32] fix: update test description for regexp script selector --- exec/plugin-commands-script-runners/test/index.ts | 8 ++++---- exec/plugin-commands-script-runners/test/runRecursive.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index 8fe4f51c170..32aaad95850 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -466,7 +466,7 @@ test('pnpm run with custom shell', async () => { expect((await import(path.resolve('shell-input.json'))).default).toStrictEqual(['-c', 'foo bar']) }) -test('pnpm run with multiple script selector should work', async () => { +test('pnpm run with RegExp script selector should work', async () => { prepare({ scripts: { 'build:a': 'node -e "require(\'fs\').writeFileSync(\'./output-build-a.txt\', \'a\', \'utf8\')"', @@ -496,7 +496,7 @@ test('pnpm run with multiple script selector should work', async () => { expect(await fs.readFile('output-lint-c.txt', { encoding: 'utf-8' })).toEqual('c') }) -test('pnpm run with multiple script selector should work also for pre/post script', async () => { +test('pnpm run with RegExp script selector should work also for pre/post script', async () => { prepare({ scripts: { 'build:a': 'node -e "require(\'fs\').writeFileSync(\'./output-a.txt\', \'a\', \'utf8\')"', @@ -516,7 +516,7 @@ test('pnpm run with multiple script selector should work also for pre/post scrip expect(await fs.readFile('output-pre-a.txt', { encoding: 'utf-8' })).toEqual('pre-a') }) -test('pnpm run with multiple script selector should work parallel as a default behavior (parallel execution limits number is four)', async () => { +test('pnpm run with RegExp script selector should work parallel as a default behavior (parallel execution limits number is four)', async () => { prepare({ scripts: { 'build:a': 'node -e "let i = 20;setInterval(() => {if (!--i) process.exit(0); require(\'json-append\').append(Date.now(),\'./output-a.json\');},50)"', @@ -539,7 +539,7 @@ test('pnpm run with multiple script selector should work parallel as a default b expect(Math.max(outputsA[0], outputsB[0]) < Math.min(outputsA[outputsA.length - 1], outputsB[outputsB.length - 1])).toBeTruthy() }) -test('pnpm run with multiple script selector should work sequentially with --workspace-concurrency=1', async () => { +test('pnpm run with RegExp script selector should work sequentially with --workspace-concurrency=1', async () => { prepare({ scripts: { 'build:a': 'node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); require(\'json-append\').append(Date.now(),\'./output-a.json\');},16)"', diff --git a/exec/plugin-commands-script-runners/test/runRecursive.ts b/exec/plugin-commands-script-runners/test/runRecursive.ts index 74341e9e8e4..a06d8f3955a 100644 --- a/exec/plugin-commands-script-runners/test/runRecursive.ts +++ b/exec/plugin-commands-script-runners/test/runRecursive.ts @@ -892,7 +892,7 @@ test('`pnpm -r --resume-from run` should executed from given package', async () expect(output1).toContain('project-3') }) -test('pnpm run with multiple script selector should work on recursive', async () => { +test('pnpm run with RegExp script selector should work on recursive', async () => { preparePackages([ { name: 'project-1', From 67f62a1fab401a6657f877899a046d7d2eccb1e1 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Tue, 31 Jan 2023 23:45:21 +0900 Subject: [PATCH 29/32] fix: report error if passed regexp script command includes flag --- .../src/regexpCommand.ts | 22 ++++++++++++------ .../test/index.ts | 23 +++++++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/regexpCommand.ts b/exec/plugin-commands-script-runners/src/regexpCommand.ts index 847433d448b..d9f88a82784 100644 --- a/exec/plugin-commands-script-runners/src/regexpCommand.ts +++ b/exec/plugin-commands-script-runners/src/regexpCommand.ts @@ -1,14 +1,22 @@ -export function tryBuildRegExpFromCommand ( - command: string -): RegExp | null { - if (command.length < 3) { +import { PnpmError } from '@pnpm/error' + +export function tryBuildRegExpFromCommand (command: string): RegExp | null { + // https://github.com/stdlib-js/regexp-regexp/blob/6428051ac9ef7c9d03468b19bdbb1dc6fc2a5509/lib/regexp.js + const regExpDetectRegExpScriptCommand = /^\/((?:\\\/|[^/])+)\/([dgimuys]*)$/ + const match = command.match(regExpDetectRegExpScriptCommand) + + // if the passed script selector is not in the format of RegExp literal like /build:.*/, return null and handle it as a string script command + if (!match) { return null } - if (command[0] !== '/' || command.lastIndexOf('/') < 1) { - return null + + // if the passed RegExp script selector includes flag, report the error because RegExp flag is not useful for script selector and pnpm does not support this. + if (match[2]) { + throw new PnpmError('UNSUPPORTED_SCRIPT_COMMAND_FORMAT', 'RegExp flag is not supported in script command selector') } + try { - return new RegExp(command.slice(0, command.lastIndexOf('/')).slice(1)) + return new RegExp(match[1]) } catch { return null } diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index 32aaad95850..f63a79879ed 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -562,3 +562,26 @@ test('pnpm run with RegExp script selector should work sequentially with --works expect(outputsA[0] < outputsB[0] && outputsA[1] < outputsB[1]).toBeTruthy() }) + +test('pnpm run with RegExp script selector with flag should throw error', async () => { + prepare({ + scripts: { + 'build:a': 'node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); require(\'json-append\').append(Date.now(),\'./output-a.json\');},16)"', + 'build:b': 'node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); require(\'json-append\').append(Date.now(),\'./output-b.json\');},16)"', + }, + }) + + let err!: Error + try { + await run.handler({ + dir: process.cwd(), + extraBinPaths: [], + extraEnv: {}, + rawConfig: {}, + workspaceConcurrency: 1, + }, ['/build:.*/i']) + } catch (_err: any) { // eslint-disable-line + err = _err + } + expect(err.message).toBe('RegExp flag is not supported in script command selector') +}) From d9bd1696355d65e6e5ecca89ecd5e022455b8d0d Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Wed, 1 Feb 2023 03:00:58 +0200 Subject: [PATCH 30/32] refactor: update text --- .changeset/silly-flies-care.md | 2 +- exec/plugin-commands-script-runners/src/regexpCommand.ts | 2 +- exec/plugin-commands-script-runners/src/run.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/silly-flies-care.md b/.changeset/silly-flies-care.md index 29d4d18fcc3..dc88c0fc85d 100644 --- a/.changeset/silly-flies-care.md +++ b/.changeset/silly-flies-care.md @@ -3,4 +3,4 @@ "pnpm": minor --- -feat: support script selector with RegExp such as `pnpm run /build:.*/` and execute the matched scripts with the RegExp. +Support script selector with RegExp such as `pnpm run /build:.*/` and execute the matched scripts with the RegExp [#5871](https://github.com/pnpm/pnpm/pull/5871). diff --git a/exec/plugin-commands-script-runners/src/regexpCommand.ts b/exec/plugin-commands-script-runners/src/regexpCommand.ts index d9f88a82784..1519d6cddc5 100644 --- a/exec/plugin-commands-script-runners/src/regexpCommand.ts +++ b/exec/plugin-commands-script-runners/src/regexpCommand.ts @@ -12,7 +12,7 @@ export function tryBuildRegExpFromCommand (command: string): RegExp | null { // if the passed RegExp script selector includes flag, report the error because RegExp flag is not useful for script selector and pnpm does not support this. if (match[2]) { - throw new PnpmError('UNSUPPORTED_SCRIPT_COMMAND_FORMAT', 'RegExp flag is not supported in script command selector') + throw new PnpmError('UNSUPPORTED_SCRIPT_COMMAND_FORMAT', 'RegExp flags are not supported in script command selector') } try { diff --git a/exec/plugin-commands-script-runners/src/run.ts b/exec/plugin-commands-script-runners/src/run.ts index f156049141f..b7741bd68e8 100644 --- a/exec/plugin-commands-script-runners/src/run.ts +++ b/exec/plugin-commands-script-runners/src/run.ts @@ -45,7 +45,7 @@ export const RESUME_FROM_OPTION_HELP = { } export const SEQUENTIAL_OPTION_HELP = { - description: 'Run specified scripts one after one.', + description: 'Run the specified scripts one by one', name: '--sequential', } From 46522b3a2c17ea75cccac6ba01248d0006cd493f Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Thu, 2 Feb 2023 00:13:06 +0900 Subject: [PATCH 31/32] fixup! fixup! fixup! fixup! fix: use regexp selector instaed of original syntax --- exec/plugin-commands-script-runners/src/runRecursive.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exec/plugin-commands-script-runners/src/runRecursive.ts b/exec/plugin-commands-script-runners/src/runRecursive.ts index aadf2c3861b..8b52af21ba1 100644 --- a/exec/plugin-commands-script-runners/src/runRecursive.ts +++ b/exec/plugin-commands-script-runners/src/runRecursive.ts @@ -163,13 +163,13 @@ export async function runRecursive ( } export function getSpecifiedScripts (scripts: PackageScripts, scriptName: string) { - const scriptSelector = tryBuildRegExpFromCommand(scriptName) - // if scripts in package.json has script which is equal to scriptName a user passes, return it. if (scripts[scriptName]) { return [scriptName] } + const scriptSelector = tryBuildRegExpFromCommand(scriptName) + // if scriptName which a user passes is RegExp (like /build:.*/), multiple scripts to execute will be selected with RegExp if (scriptSelector) { const scriptKeys = Object.keys(scripts) From 1485f79b296a8d7a08e0f71c9e6a68437c1eec57 Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Thu, 2 Feb 2023 00:13:33 +0900 Subject: [PATCH 32/32] fixup! refactor: update text --- exec/plugin-commands-script-runners/test/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exec/plugin-commands-script-runners/test/index.ts b/exec/plugin-commands-script-runners/test/index.ts index f63a79879ed..26a5f6528ef 100644 --- a/exec/plugin-commands-script-runners/test/index.ts +++ b/exec/plugin-commands-script-runners/test/index.ts @@ -583,5 +583,5 @@ test('pnpm run with RegExp script selector with flag should throw error', async } catch (_err: any) { // eslint-disable-line err = _err } - expect(err.message).toBe('RegExp flag is not supported in script command selector') + expect(err.message).toBe('RegExp flags are not supported in script command selector') })