mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 07:38:12 -05:00
feat: add workspace-concurrency cli option for pack and publish command (#9493)
* refactor: set the default `workspaceConcurrency` to `Math.min(os.availableParallelism(), 4)` * feat(plugin-commands-publishing): add `workspace-concurrency` cli option for pack and publish * feat(recursive): add support for `recursive pack` * feat: get default workspaceConcurrency from config package * test(config): mock cpus to support Node.js 18
This commit is contained in:
10
.changeset/hot-tires-report.md
Normal file
10
.changeset/hot-tires-report.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-installation": patch
|
||||
"@pnpm/plugin-commands-script-runners": patch
|
||||
"@pnpm/plugin-commands-rebuild": patch
|
||||
"@pnpm/plugin-commands-releasing": patch
|
||||
"@pnpm/build-modules": patch
|
||||
"@pnpm/config": patch
|
||||
---
|
||||
|
||||
Set the default `workspaceConcurrency` to `Math.min(os.availableParallelism(), 4)`
|
||||
5
.changeset/new-waves-remain.md
Normal file
5
.changeset/new-waves-remain.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Add support for `recursive pack`
|
||||
5
.changeset/tricky-wolves-trade.md
Normal file
5
.changeset/tricky-wolves-trade.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-publishing": patch
|
||||
---
|
||||
|
||||
Add `workspace-concurrency` cli option for pack and publish
|
||||
@@ -1,12 +1,32 @@
|
||||
import os from 'os'
|
||||
|
||||
const MaxDefaultWorkspaceConcurrency: number = 4
|
||||
|
||||
let cacheAvailableParallelism: number | undefined
|
||||
|
||||
export function getAvailableParallelism (cache: boolean = true): number {
|
||||
if (cache && Number(cacheAvailableParallelism) > 0) {
|
||||
return cacheAvailableParallelism!
|
||||
}
|
||||
cacheAvailableParallelism = Math.max(1, os.availableParallelism?.() ?? os.cpus().length)
|
||||
return cacheAvailableParallelism
|
||||
}
|
||||
|
||||
export function resetAvailableParallelismCache (): void {
|
||||
cacheAvailableParallelism = undefined
|
||||
}
|
||||
|
||||
export function getDefaultWorkspaceConcurrency (cache?: boolean): number {
|
||||
return Math.min(MaxDefaultWorkspaceConcurrency, getAvailableParallelism(cache))
|
||||
}
|
||||
|
||||
export function getWorkspaceConcurrency (option: number | undefined): number {
|
||||
if (typeof option !== 'number') return 4
|
||||
if (typeof option !== 'number') return getDefaultWorkspaceConcurrency()
|
||||
|
||||
if (option <= 0) {
|
||||
// If option is <= 0, it uses the amount of cores minus the absolute of the number given
|
||||
// but always returning at least 1
|
||||
return Math.max(1, (os.availableParallelism?.() ?? os.cpus().length) - Math.abs(option))
|
||||
return Math.max(1, getAvailableParallelism() - Math.abs(option))
|
||||
}
|
||||
|
||||
return option
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
type VerifyDepsBeforeRun,
|
||||
type WantedPackageManager,
|
||||
} from './Config'
|
||||
import { getWorkspaceConcurrency } from './concurrency'
|
||||
import { getDefaultWorkspaceConcurrency, getWorkspaceConcurrency } from './concurrency'
|
||||
import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest'
|
||||
|
||||
import { types } from './types'
|
||||
@@ -38,6 +38,7 @@ export { types }
|
||||
|
||||
export { getOptionsFromRootManifest, getOptionsFromPnpmSettings, type OptionsFromRootManifest } from './getOptionsFromRootManifest'
|
||||
export * from './readLocalConfig'
|
||||
export { getDefaultWorkspaceConcurrency, getWorkspaceConcurrency } from './concurrency'
|
||||
|
||||
export type { Config, UniversalOptions, WantedPackageManager, VerifyDepsBeforeRun }
|
||||
|
||||
@@ -191,7 +192,7 @@ export async function getConfig (opts: {
|
||||
'verify-deps-before-run': false,
|
||||
'verify-store-integrity': true,
|
||||
'virtual-store-dir': 'node_modules/.pnpm',
|
||||
'workspace-concurrency': 4,
|
||||
'workspace-concurrency': getDefaultWorkspaceConcurrency(),
|
||||
'workspace-prefix': opts.workspaceDir,
|
||||
'embed-readme': false,
|
||||
'registry-supports-time-field': false,
|
||||
|
||||
@@ -1,8 +1,47 @@
|
||||
import { cpus } from 'os'
|
||||
import { getWorkspaceConcurrency } from '../lib/concurrency'
|
||||
import os, { cpus } from 'os'
|
||||
import { getDefaultWorkspaceConcurrency, resetAvailableParallelismCache, getWorkspaceConcurrency } from '../lib/concurrency'
|
||||
|
||||
const hostCores = cpus().length
|
||||
|
||||
beforeEach(() => {
|
||||
resetAvailableParallelismCache()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
resetAvailableParallelismCache()
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
function mockAvailableParallelism (value: number) {
|
||||
if ('availableParallelism' in os) {
|
||||
jest.spyOn(os, 'availableParallelism').mockReturnValue(value)
|
||||
}
|
||||
jest.spyOn(os, 'cpus').mockReturnValue(Array(value).fill(cpus()[0]))
|
||||
}
|
||||
|
||||
test('getDefaultWorkspaceConcurrency: cpu num < 4', () => {
|
||||
mockAvailableParallelism(1)
|
||||
expect(getDefaultWorkspaceConcurrency(false)).toBe(1)
|
||||
})
|
||||
|
||||
test('getDefaultWorkspaceConcurrency: cpu num > 4', () => {
|
||||
mockAvailableParallelism(5)
|
||||
expect(getDefaultWorkspaceConcurrency(false)).toBe(4)
|
||||
})
|
||||
|
||||
test('getDefaultWorkspaceConcurrency: cpu num = 4', () => {
|
||||
mockAvailableParallelism(4)
|
||||
expect(getDefaultWorkspaceConcurrency(false)).toBe(4)
|
||||
})
|
||||
|
||||
test('getDefaultWorkspaceConcurrency: using cache', () => {
|
||||
mockAvailableParallelism(4)
|
||||
expect(getDefaultWorkspaceConcurrency()).toBe(4)
|
||||
|
||||
mockAvailableParallelism(5)
|
||||
expect(getDefaultWorkspaceConcurrency()).toBe(4)
|
||||
})
|
||||
|
||||
test('default workspace concurrency', () => {
|
||||
const n = getWorkspaceConcurrency(undefined)
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/calc-dep-state": "workspace:*",
|
||||
"@pnpm/config": "workspace:*",
|
||||
"@pnpm/core-loggers": "workspace:*",
|
||||
"@pnpm/deps.graph-sequencer": "workspace:*",
|
||||
"@pnpm/fs.hard-link-dir": "workspace:*",
|
||||
|
||||
@@ -2,6 +2,7 @@ import assert from 'assert'
|
||||
import path from 'path'
|
||||
import util from 'util'
|
||||
import { calcDepState, type DepsStateCache } from '@pnpm/calc-dep-state'
|
||||
import { getWorkspaceConcurrency } from '@pnpm/config'
|
||||
import { skippedOptionalDependencyLogger, ignoredScriptsLogger } from '@pnpm/core-loggers'
|
||||
import { runPostinstallHooks } from '@pnpm/lifecycle'
|
||||
import { linkBins, linkBinsOfPackages } from '@pnpm/link-bins'
|
||||
@@ -87,7 +88,7 @@ export async function buildModules<T extends string> (
|
||||
}
|
||||
)
|
||||
})
|
||||
await runGroups(opts.childConcurrency ?? 4, groups)
|
||||
await runGroups(getWorkspaceConcurrency(opts.childConcurrency), groups)
|
||||
if (opts.ignoredBuiltDependencies?.length) {
|
||||
for (const ignoredBuild of opts.ignoredBuiltDependencies) {
|
||||
// We already ignore the build of this dependency.
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../config/config"
|
||||
},
|
||||
{
|
||||
"path": "../../deps/graph-sequencer"
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import {
|
||||
type Config,
|
||||
readLocalConfig,
|
||||
getWorkspaceConcurrency,
|
||||
} from '@pnpm/config'
|
||||
import { logger } from '@pnpm/logger'
|
||||
import { sortPackages } from '@pnpm/sort-packages'
|
||||
@@ -110,7 +111,7 @@ export async function recursiveRebuild (
|
||||
)
|
||||
return
|
||||
}
|
||||
const limitRebuild = pLimit(opts.workspaceConcurrency ?? 4)
|
||||
const limitRebuild = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency))
|
||||
for (const chunk of chunks) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await Promise.all(chunk.map(async (rootDir) =>
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from 'path'
|
||||
import { docsUrl, type RecursiveSummary, throwOnCommandFail, readProjectManifestOnly } from '@pnpm/cli-utils'
|
||||
import { type LifecycleMessage, lifecycleLogger } from '@pnpm/core-loggers'
|
||||
import { FILTERING, UNIVERSAL_OPTIONS } from '@pnpm/common-cli-options-help'
|
||||
import { type Config, types } from '@pnpm/config'
|
||||
import { type Config, types, getWorkspaceConcurrency } from '@pnpm/config'
|
||||
import { type CheckDepsStatusOptions } from '@pnpm/deps.status'
|
||||
import { makeNodeRequireOption } from '@pnpm/lifecycle'
|
||||
import { logger } from '@pnpm/logger'
|
||||
@@ -174,7 +174,7 @@ export async function handler (
|
||||
if (!params[0]) {
|
||||
throw new PnpmError('EXEC_MISSING_COMMAND', '\'pnpm exec\' requires a command to run')
|
||||
}
|
||||
const limitRun = pLimit(opts.workspaceConcurrency ?? 4)
|
||||
const limitRun = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency))
|
||||
|
||||
if (opts.verifyDepsBeforeRun) {
|
||||
await runDepsStatusCheck(opts)
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { type CompletionFunc } from '@pnpm/command'
|
||||
import { prepareExecutionEnv } from '@pnpm/plugin-commands-env'
|
||||
import { FILTERING, UNIVERSAL_OPTIONS } from '@pnpm/common-cli-options-help'
|
||||
import { type Config, types as allTypes } from '@pnpm/config'
|
||||
import { type Config, types as allTypes, getWorkspaceConcurrency } from '@pnpm/config'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { type CheckDepsStatusOptions } from '@pnpm/deps.status'
|
||||
import {
|
||||
@@ -264,7 +264,7 @@ so you may run "pnpm -w run ${scriptName}"`,
|
||||
hint: buildCommandNotFoundHint(scriptName, manifest.scripts),
|
||||
})
|
||||
}
|
||||
const concurrency = opts.workspaceConcurrency ?? 4
|
||||
const concurrency = getWorkspaceConcurrency(opts.workspaceConcurrency)
|
||||
|
||||
const lifecycleOpts: RunLifecycleHookOptions = {
|
||||
depPath: dir,
|
||||
|
||||
@@ -2,7 +2,7 @@ import assert from 'assert'
|
||||
import path from 'path'
|
||||
import util from 'util'
|
||||
import { throwOnCommandFail } from '@pnpm/cli-utils'
|
||||
import { type Config } from '@pnpm/config'
|
||||
import { type Config, getWorkspaceConcurrency } from '@pnpm/config'
|
||||
import { prepareExecutionEnv } from '@pnpm/plugin-commands-env'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import {
|
||||
@@ -64,7 +64,7 @@ export async function runRecursive (
|
||||
})
|
||||
}
|
||||
|
||||
const limitRun = pLimit(opts.workspaceConcurrency ?? 4)
|
||||
const limitRun = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency))
|
||||
const stdio =
|
||||
!opts.stream &&
|
||||
(opts.workspaceConcurrency === 1 ||
|
||||
@@ -145,7 +145,7 @@ export async function runRecursive (
|
||||
workspaceDir: opts.workspaceDir,
|
||||
}
|
||||
const _runScript = runScript.bind(null, { manifest: pkg.package.manifest, lifecycleOpts, runScriptOptions, passedThruArgs })
|
||||
const groupEnd = (opts.workspaceConcurrency ?? 4) > 1
|
||||
const groupEnd = getWorkspaceConcurrency(opts.workspaceConcurrency) > 1
|
||||
? undefined
|
||||
: groupStart(formatSectionName({
|
||||
name: pkg.package.manifest.name,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type Config,
|
||||
type OptionsFromRootManifest,
|
||||
getOptionsFromRootManifest,
|
||||
getWorkspaceConcurrency,
|
||||
readLocalConfig,
|
||||
} from '@pnpm/config'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
@@ -294,7 +295,7 @@ export async function recursive (
|
||||
|
||||
const pkgPaths = (Object.keys(opts.selectedProjectsGraph) as ProjectRootDir[]).sort()
|
||||
|
||||
const limitInstallation = pLimit(opts.workspaceConcurrency ?? 4)
|
||||
const limitInstallation = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency))
|
||||
await Promise.all(pkgPaths.map(async (rootDir) =>
|
||||
limitInstallation(async () => {
|
||||
const hooks = opts.ignorePnpmfile
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -2312,6 +2312,9 @@ importers:
|
||||
'@pnpm/calc-dep-state':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/calc-dep-state
|
||||
'@pnpm/config':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/config
|
||||
'@pnpm/core-loggers':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core-loggers
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { docsUrl } from '@pnpm/cli-utils'
|
||||
import { FILTERING } from '@pnpm/common-cli-options-help'
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { getDefaultWorkspaceConcurrency } from '@pnpm/config'
|
||||
import renderHelp from 'render-help'
|
||||
|
||||
export const rcOptionsTypes = (): Record<string, unknown> => ({})
|
||||
@@ -70,6 +71,10 @@ and must recompile all your C++ addons with the new binary.',
|
||||
description: 'Publishes packages to the npm registry. Only publishes a package if its version is not taken in the registry.',
|
||||
name: 'publish [--tag <tag>] [--access <public|restricted>]',
|
||||
},
|
||||
{
|
||||
description: 'Create tarballs for each package.',
|
||||
name: 'pack [-- <args>...]',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -81,7 +86,7 @@ and must recompile all your C++ addons with the new binary.',
|
||||
name: '--no-bail',
|
||||
},
|
||||
{
|
||||
description: 'Set the maximum number of concurrency. Default is 4. For unlimited concurrency use Infinity.',
|
||||
description: `Set the maximum number of concurrency. Default is ${getDefaultWorkspaceConcurrency()}. For unlimited concurrency use Infinity.`,
|
||||
name: '--workspace-concurrency <number>',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from 'path'
|
||||
import { createGzip } from 'zlib'
|
||||
import { type Catalogs } from '@pnpm/catalogs.types'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { types as allTypes, type UniversalOptions, type Config } from '@pnpm/config'
|
||||
import { types as allTypes, type UniversalOptions, type Config, getWorkspaceConcurrency, getDefaultWorkspaceConcurrency } from '@pnpm/config'
|
||||
import { readProjectManifest } from '@pnpm/cli-utils'
|
||||
import { createExportableManifest } from '@pnpm/exportable-manifest'
|
||||
import { packlist } from '@pnpm/fs.packlist'
|
||||
@@ -41,6 +41,7 @@ export function cliOptionsTypes (): Record<string, unknown> {
|
||||
'pack-destination',
|
||||
'pack-gzip-level',
|
||||
'json',
|
||||
'workspace-concurrency',
|
||||
], allTypes),
|
||||
}
|
||||
}
|
||||
@@ -73,6 +74,10 @@ export function help (): string {
|
||||
name: '--recursive',
|
||||
shortAlias: '-r',
|
||||
},
|
||||
{
|
||||
description: `Set the maximum number of concurrency. Default is ${getDefaultWorkspaceConcurrency()}. For unlimited concurrency use Infinity.`,
|
||||
name: '--workspace-concurrency <number>',
|
||||
},
|
||||
],
|
||||
},
|
||||
FILTERING,
|
||||
@@ -132,7 +137,7 @@ export async function handler (opts: PackOptions): Promise<string> {
|
||||
|
||||
const chunks = sortPackages(selectedProjectsGraph)
|
||||
|
||||
const limitPack = pLimit(opts.workspaceConcurrency ?? 4)
|
||||
const limitPack = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency))
|
||||
const resolvedOpts = { ...opts }
|
||||
if (opts.out) {
|
||||
resolvedOpts.out = path.resolve(opts.dir, opts.out)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { promises as fs, existsSync } from 'fs'
|
||||
import path from 'path'
|
||||
import { docsUrl, readProjectManifest } from '@pnpm/cli-utils'
|
||||
import { FILTERING } from '@pnpm/common-cli-options-help'
|
||||
import { type Config, types as allTypes } from '@pnpm/config'
|
||||
import { type Config, types as allTypes, getDefaultWorkspaceConcurrency } from '@pnpm/config'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { runLifecycleHook, type RunLifecycleHookOptions } from '@pnpm/lifecycle'
|
||||
import { runNpm } from '@pnpm/run-npm'
|
||||
@@ -32,6 +32,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
|
||||
'tag',
|
||||
'unsafe-perm',
|
||||
'embed-readme',
|
||||
'workspace-concurrency',
|
||||
], allTypes)
|
||||
}
|
||||
|
||||
@@ -101,6 +102,10 @@ export function help (): string {
|
||||
name: '--recursive',
|
||||
shortAlias: '-r',
|
||||
},
|
||||
{
|
||||
description: `Set the maximum number of concurrency. Default is ${getDefaultWorkspaceConcurrency()}. For unlimited concurrency use Infinity.`,
|
||||
name: '--workspace-concurrency <number>',
|
||||
},
|
||||
],
|
||||
},
|
||||
FILTERING,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'path'
|
||||
import { createResolver } from '@pnpm/client'
|
||||
import { type Config } from '@pnpm/config'
|
||||
import { type Config, getWorkspaceConcurrency } from '@pnpm/config'
|
||||
import { logger } from '@pnpm/logger'
|
||||
import { pickRegistryForPackage } from '@pnpm/pick-registry-for-package'
|
||||
import { type ResolveFunction } from '@pnpm/resolver-base'
|
||||
@@ -105,7 +105,7 @@ export async function recursivePublish (
|
||||
appendedArgs.push(`--otp=${opts.cliOptions['otp'] as string}`)
|
||||
}
|
||||
const chunks = sortPackages(opts.selectedProjectsGraph)
|
||||
const limitPublish = pLimit(opts.workspaceConcurrency ?? 4)
|
||||
const limitPublish = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency))
|
||||
const tag = opts.tag ?? 'latest'
|
||||
for (const chunk of chunks) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
|
||||
Reference in New Issue
Block a user