mirror of
https://github.com/pnpm/pnpm.git
synced 2026-01-15 02:18:31 -05:00
fix: reporting ignored dependency builds (#10276)
This commit is contained in:
10
.changeset/easy-toys-design.md
Normal file
10
.changeset/easy-toys-design.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-rebuild": major
|
||||
"@pnpm/modules-yaml": major
|
||||
"@pnpm/headless": major
|
||||
"@pnpm/build-modules": major
|
||||
"@pnpm/core": major
|
||||
"@pnpm/exec.build-commands": major
|
||||
---
|
||||
|
||||
`ignoreBuilds` is now a set of DepPath.
|
||||
5
.changeset/sharp-snakes-love.md
Normal file
5
.changeset/sharp-snakes-love.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/types": minor
|
||||
---
|
||||
|
||||
Add type for IgnoredBuilds.
|
||||
5
.changeset/tender-socks-show.md
Normal file
5
.changeset/tender-socks-show.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Improved reporting of ignored dependency scripts [#10276](https://github.com/pnpm/pnpm/pull/10276).
|
||||
@@ -34,6 +34,7 @@
|
||||
"dependencies": {
|
||||
"@pnpm/config": "workspace:*",
|
||||
"@pnpm/config.config-writer": "workspace:*",
|
||||
"@pnpm/dependency-path": "workspace:*",
|
||||
"@pnpm/modules-yaml": "workspace:*",
|
||||
"@pnpm/plugin-commands-rebuild": "workspace:*",
|
||||
"@pnpm/prepare-temp-dir": "workspace:*",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from 'path'
|
||||
import { parse } from '@pnpm/dependency-path'
|
||||
import { type Modules, readModulesManifest } from '@pnpm/modules-yaml'
|
||||
import { type IgnoredBuildsCommandOpts } from './ignoredBuilds.js'
|
||||
|
||||
@@ -11,8 +12,18 @@ export interface GetAutomaticallyIgnoredBuildsResult {
|
||||
export async function getAutomaticallyIgnoredBuilds (opts: IgnoredBuildsCommandOpts): Promise<GetAutomaticallyIgnoredBuildsResult> {
|
||||
const modulesDir = getModulesDir(opts)
|
||||
const modulesManifest = await readModulesManifest(modulesDir)
|
||||
let automaticallyIgnoredBuilds: null | string[]
|
||||
if (modulesManifest?.ignoredBuilds) {
|
||||
const ignoredPkgNames = new Set<string>()
|
||||
for (const depPath of modulesManifest?.ignoredBuilds) {
|
||||
ignoredPkgNames.add(parse(depPath).name ?? depPath)
|
||||
}
|
||||
automaticallyIgnoredBuilds = Array.from(ignoredPkgNames)
|
||||
} else {
|
||||
automaticallyIgnoredBuilds = null
|
||||
}
|
||||
return {
|
||||
automaticallyIgnoredBuilds: modulesManifest && (modulesManifest.ignoredBuilds ?? []),
|
||||
automaticallyIgnoredBuilds,
|
||||
modulesDir,
|
||||
modulesManifest,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { type RebuildCommandOpts } from '@pnpm/plugin-commands-rebuild'
|
||||
import { prepare } from '@pnpm/prepare'
|
||||
import { type ProjectManifest } from '@pnpm/types'
|
||||
import { getConfig } from '@pnpm/config'
|
||||
import { type Modules, readModulesManifest } from '@pnpm/modules-yaml'
|
||||
import { readModulesManifest } from '@pnpm/modules-yaml'
|
||||
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
|
||||
import { jest } from '@jest/globals'
|
||||
import { sync as loadJsonFile } from 'load-json-file'
|
||||
@@ -123,7 +123,7 @@ test('approve no builds', async () => {
|
||||
expect(fs.readdirSync('node_modules/@pnpm.e2e/install-script-example')).not.toContain('generated-by-install.js')
|
||||
|
||||
// Covers https://github.com/pnpm/pnpm/issues/9296
|
||||
expect(await readModulesManifest('node_modules')).not.toHaveProperty(['ignoredBuilds' satisfies keyof Modules])
|
||||
expect((await readModulesManifest('node_modules'))!.ignoredBuilds).toBeUndefined()
|
||||
})
|
||||
|
||||
test("works when root project manifest doesn't exist in a workspace", async () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from 'fs'
|
||||
import { ignoredBuilds } from '@pnpm/exec.build-commands'
|
||||
import { tempDir } from '@pnpm/prepare-temp-dir'
|
||||
import { writeModulesManifest } from '@pnpm/modules-yaml'
|
||||
import { type DepPath } from '@pnpm/types'
|
||||
|
||||
const DEFAULT_MODULES_MANIFEST = {
|
||||
hoistedDependencies: {},
|
||||
@@ -30,7 +31,7 @@ test('ignoredBuilds lists automatically ignored dependencies', async () => {
|
||||
fs.mkdirSync(modulesDir, { recursive: true })
|
||||
await writeModulesManifest(modulesDir, {
|
||||
...DEFAULT_MODULES_MANIFEST,
|
||||
ignoredBuilds: ['foo'],
|
||||
ignoredBuilds: new Set(['foo@1.0.0' as DepPath]),
|
||||
})
|
||||
const output = await ignoredBuilds.handler({
|
||||
dir,
|
||||
@@ -46,7 +47,7 @@ test('ignoredBuilds lists explicitly ignored dependencies', async () => {
|
||||
fs.mkdirSync(modulesDir, { recursive: true })
|
||||
await writeModulesManifest(modulesDir, {
|
||||
...DEFAULT_MODULES_MANIFEST,
|
||||
ignoredBuilds: [],
|
||||
ignoredBuilds: new Set(),
|
||||
})
|
||||
const output = await ignoredBuilds.handler({
|
||||
dir,
|
||||
@@ -66,7 +67,7 @@ test('ignoredBuilds lists both automatically and explicitly ignored dependencies
|
||||
fs.mkdirSync(modulesDir, { recursive: true })
|
||||
await writeModulesManifest(modulesDir, {
|
||||
...DEFAULT_MODULES_MANIFEST,
|
||||
ignoredBuilds: ['foo', 'bar'],
|
||||
ignoredBuilds: new Set(['foo@1.0.0', 'bar@1.0.0'] as DepPath[]),
|
||||
})
|
||||
const output = await ignoredBuilds.handler({
|
||||
dir,
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
{
|
||||
"path": "../../config/config-writer"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/dependency-path"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/types"
|
||||
},
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"@pnpm/calc-dep-state": "workspace:*",
|
||||
"@pnpm/config": "workspace:*",
|
||||
"@pnpm/core-loggers": "workspace:*",
|
||||
"@pnpm/dependency-path": "workspace:*",
|
||||
"@pnpm/deps.graph-sequencer": "workspace:*",
|
||||
"@pnpm/fs.hard-link-dir": "workspace:*",
|
||||
"@pnpm/lifecycle": "workspace:*",
|
||||
|
||||
@@ -4,6 +4,7 @@ import util from 'util'
|
||||
import { calcDepState, type DepsStateCache } from '@pnpm/calc-dep-state'
|
||||
import { getWorkspaceConcurrency } from '@pnpm/config'
|
||||
import { skippedOptionalDependencyLogger } from '@pnpm/core-loggers'
|
||||
import * as dp from '@pnpm/dependency-path'
|
||||
import { runPostinstallHooks } from '@pnpm/lifecycle'
|
||||
import { linkBins, linkBinsOfPackages } from '@pnpm/link-bins'
|
||||
import { logger } from '@pnpm/logger'
|
||||
@@ -11,7 +12,12 @@ import { hardLinkDir } from '@pnpm/worker'
|
||||
import { readPackageJsonFromDir, safeReadPackageJsonFromDir } from '@pnpm/read-package-json'
|
||||
import { type StoreController } from '@pnpm/store-controller-types'
|
||||
import { applyPatchToDir } from '@pnpm/patching.apply-patch'
|
||||
import { type AllowBuild, type DependencyManifest } from '@pnpm/types'
|
||||
import {
|
||||
type AllowBuild,
|
||||
type DependencyManifest,
|
||||
type DepPath,
|
||||
type IgnoredBuilds,
|
||||
} from '@pnpm/types'
|
||||
import pDefer, { type DeferredPromise } from 'p-defer'
|
||||
import pickBy from 'ramda/src/pickBy'
|
||||
import runGroups from 'run-groups'
|
||||
@@ -47,7 +53,7 @@ export async function buildModules<T extends string> (
|
||||
rootModulesDir: string
|
||||
hoistedLocations?: Record<string, string[]>
|
||||
}
|
||||
): Promise<{ ignoredBuilds?: string[] }> {
|
||||
): Promise<{ ignoredBuilds?: IgnoredBuilds }> {
|
||||
if (!rootDepPaths.length) return {}
|
||||
const warn = (message: string) => {
|
||||
logger.warn({ message, prefix: opts.lockfileDir })
|
||||
@@ -61,7 +67,7 @@ export async function buildModules<T extends string> (
|
||||
}
|
||||
const chunks = buildSequence<T>(depGraph, rootDepPaths)
|
||||
if (!chunks.length) return {}
|
||||
const ignoredPkgs = new Set<string>()
|
||||
let ignoredBuilds = new Set<DepPath>()
|
||||
const allowBuild = opts.allowBuild ?? (() => true)
|
||||
const groups = chunks.map((chunk) => {
|
||||
chunk = chunk.filter((depPath) => {
|
||||
@@ -77,7 +83,7 @@ export async function buildModules<T extends string> (
|
||||
let ignoreScripts = Boolean(buildDepOpts.ignoreScripts)
|
||||
if (!ignoreScripts) {
|
||||
if (depGraph[depPath].requiresBuild && !allowBuild(depGraph[depPath].name, depGraph[depPath].version)) {
|
||||
ignoredPkgs.add(depGraph[depPath].name)
|
||||
ignoredBuilds.add(depGraph[depPath].depPath)
|
||||
ignoreScripts = true
|
||||
}
|
||||
}
|
||||
@@ -90,14 +96,15 @@ export async function buildModules<T extends string> (
|
||||
})
|
||||
await runGroups(getWorkspaceConcurrency(opts.childConcurrency), groups)
|
||||
if (opts.ignoredBuiltDependencies?.length) {
|
||||
for (const ignoredBuild of opts.ignoredBuiltDependencies) {
|
||||
// We already ignore the build of this dependency.
|
||||
// No need to report it.
|
||||
ignoredPkgs.delete(ignoredBuild)
|
||||
}
|
||||
// We already ignore the build of these dependencies.
|
||||
// No need to report them.
|
||||
ignoredBuilds = new Set(Array.from(ignoredBuilds).filter((ignoredPkgDepPath) =>
|
||||
!opts.ignoredBuiltDependencies!.some((ignoredInSettings) =>
|
||||
(ignoredInSettings === ignoredPkgDepPath) || (dp.parse(ignoredPkgDepPath).name === ignoredInSettings)
|
||||
)
|
||||
))
|
||||
}
|
||||
const packageNames = Array.from(ignoredPkgs)
|
||||
return { ignoredBuilds: packageNames }
|
||||
return { ignoredBuilds }
|
||||
}
|
||||
|
||||
async function buildDependency<T extends string> (
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
{
|
||||
"path": "../../packages/core-loggers"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/dependency-path"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/logger"
|
||||
},
|
||||
|
||||
@@ -26,7 +26,13 @@ import { lockfileWalker, type LockfileWalkerStep } from '@pnpm/lockfile.walker'
|
||||
import { logger, streamParser } from '@pnpm/logger'
|
||||
import { writeModulesManifest } from '@pnpm/modules-yaml'
|
||||
import { createOrConnectStoreController } from '@pnpm/store-connection-manager'
|
||||
import { type DepPath, type ProjectManifest, type ProjectId, type ProjectRootDir } from '@pnpm/types'
|
||||
import {
|
||||
type DepPath,
|
||||
type IgnoredBuilds,
|
||||
type ProjectManifest,
|
||||
type ProjectId,
|
||||
type ProjectRootDir,
|
||||
} from '@pnpm/types'
|
||||
import { createAllowBuildFunction } from '@pnpm/builder.policy'
|
||||
import { pkgRequiresBuild } from '@pnpm/exec.pkg-requires-build'
|
||||
import * as dp from '@pnpm/dependency-path'
|
||||
@@ -92,7 +98,7 @@ export async function rebuildSelectedPkgs (
|
||||
projects: Array<{ buildIndex: number, manifest: ProjectManifest, rootDir: ProjectRootDir }>,
|
||||
pkgSpecs: string[],
|
||||
maybeOpts: RebuildOptions
|
||||
): Promise<{ ignoredBuilds?: string[] }> {
|
||||
): Promise<{ ignoredBuilds?: IgnoredBuilds }> {
|
||||
const reporter = maybeOpts?.reporter
|
||||
if ((reporter != null) && typeof reporter === 'function') {
|
||||
streamParser.on('data', reporter)
|
||||
@@ -269,7 +275,7 @@ async function _rebuild (
|
||||
extraNodePaths: string[]
|
||||
} & Pick<PnpmContext, 'modulesFile'>,
|
||||
opts: StrictRebuildOptions
|
||||
): Promise<{ pkgsThatWereRebuilt: Set<string>, ignoredPkgs: string[] }> {
|
||||
): Promise<{ pkgsThatWereRebuilt: Set<string>, ignoredPkgs: IgnoredBuilds }> {
|
||||
const depGraph = lockfileToDepGraph(ctx.currentLockfile)
|
||||
const depsStateCache: DepsStateCache = {}
|
||||
const pkgsThatWereRebuilt = new Set<string>()
|
||||
@@ -309,12 +315,12 @@ async function _rebuild (
|
||||
logger.info({ message, prefix: opts.dir })
|
||||
}
|
||||
|
||||
const ignoredPkgs: string[] = []
|
||||
const ignoredPkgs = new Set<DepPath>()
|
||||
const _allowBuild = createAllowBuildFunction(opts) ?? (() => true)
|
||||
const allowBuild = (pkgName: string, version: string) => {
|
||||
const allowBuild = (pkgName: string, version: string, depPath: DepPath) => {
|
||||
if (_allowBuild(pkgName, version)) return true
|
||||
if (!opts.ignoredBuiltDependencies?.includes(pkgName)) {
|
||||
ignoredPkgs.push(pkgName)
|
||||
ignoredPkgs.add(depPath)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -370,7 +376,7 @@ async function _rebuild (
|
||||
requiresBuild = pkgRequiresBuild(pgkManifest, {})
|
||||
}
|
||||
|
||||
const hasSideEffects = requiresBuild && allowBuild(pkgInfo.name, pkgInfo.version) && await runPostinstallHooks({
|
||||
const hasSideEffects = requiresBuild && allowBuild(pkgInfo.name, pkgInfo.version, depPath) && await runPostinstallHooks({
|
||||
depPath,
|
||||
extraBinPaths,
|
||||
extraEnv: opts.extraEnv,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
parse,
|
||||
refToRelative,
|
||||
tryGetPackageId,
|
||||
removeSuffix,
|
||||
} from '@pnpm/dependency-path'
|
||||
import { type DepPath } from '@pnpm/types'
|
||||
|
||||
@@ -139,3 +140,7 @@ test('getPkgIdWithPatchHash', () => {
|
||||
// Scoped packages with both patch hash and peer dependencies
|
||||
expect(getPkgIdWithPatchHash('@foo/bar@1.0.0(patch_hash=zzzz)(@types/node@18.0.0)' as DepPath)).toBe('@foo/bar@1.0.0(patch_hash=zzzz)')
|
||||
})
|
||||
|
||||
test('removeSuffix', () => {
|
||||
expect(removeSuffix('foo@1.0.0(patch_hash=0000)(@types/babel__core@7.1.14)')).toBe('foo@1.0.0')
|
||||
})
|
||||
@@ -42,3 +42,5 @@ export type PinnedVersion =
|
||||
| 'patch'
|
||||
| 'minor'
|
||||
| 'major'
|
||||
|
||||
export type IgnoredBuilds = Set<DepPath>
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"@pnpm/store-controller-types": "workspace:*",
|
||||
"@pnpm/symlink-dependency": "workspace:*",
|
||||
"@pnpm/types": "workspace:*",
|
||||
"@pnpm/util.lex-comparator": "catalog:",
|
||||
"@zkochan/rimraf": "catalog:",
|
||||
"enquirer": "catalog:",
|
||||
"is-inner-link": "catalog:",
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
summaryLogger,
|
||||
} from '@pnpm/core-loggers'
|
||||
import { hashObjectNullableWithPrefix } from '@pnpm/crypto.object-hasher'
|
||||
import * as dp from '@pnpm/dependency-path'
|
||||
import {
|
||||
calcPatchHashes,
|
||||
createOverridesMapFromParsed,
|
||||
@@ -67,12 +68,14 @@ import {
|
||||
type DepPath,
|
||||
type DependenciesField,
|
||||
type DependencyManifest,
|
||||
type IgnoredBuilds,
|
||||
type PeerDependencyIssues,
|
||||
type ProjectId,
|
||||
type ProjectManifest,
|
||||
type ReadPackageHook,
|
||||
type ProjectRootDir,
|
||||
} from '@pnpm/types'
|
||||
import { lexCompare } from '@pnpm/util.lex-comparator'
|
||||
import isSubdir from 'is-subdir'
|
||||
import pLimit from 'p-limit'
|
||||
import mapValues from 'ramda/src/map'
|
||||
@@ -154,7 +157,7 @@ export interface InstallResult {
|
||||
*/
|
||||
updatedCatalogs: Catalogs | undefined
|
||||
updatedManifest: ProjectManifest
|
||||
ignoredBuilds: string[] | undefined
|
||||
ignoredBuilds: IgnoredBuilds | undefined
|
||||
}
|
||||
|
||||
export async function install (
|
||||
@@ -207,7 +210,7 @@ export type MutateModulesOptions = InstallOptions & {
|
||||
export interface MutateModulesInSingleProjectResult {
|
||||
updatedCatalogs: Catalogs | undefined
|
||||
updatedProject: UpdatedProject
|
||||
ignoredBuilds: string[] | undefined
|
||||
ignoredBuilds: IgnoredBuilds | undefined
|
||||
}
|
||||
|
||||
export async function mutateModulesInSingleProject (
|
||||
@@ -249,7 +252,7 @@ export interface MutateModulesResult {
|
||||
updatedProjects: UpdatedProject[]
|
||||
stats: InstallationResultStats
|
||||
depsRequiringBuild?: DepPath[]
|
||||
ignoredBuilds: string[] | undefined
|
||||
ignoredBuilds: IgnoredBuilds | undefined
|
||||
}
|
||||
|
||||
const pickCatalogSpecifier: CatalogResultMatcher<string | undefined> = {
|
||||
@@ -350,10 +353,14 @@ export async function mutateModules (
|
||||
}
|
||||
|
||||
let ignoredBuilds = result.ignoredBuilds
|
||||
if (!opts.ignoreScripts && ignoredBuilds?.length) {
|
||||
if (!opts.ignoreScripts && ignoredBuilds?.size) {
|
||||
ignoredBuilds = await runUnignoredDependencyBuilds(opts, ignoredBuilds)
|
||||
}
|
||||
ignoredScriptsLogger.debug({ packageNames: ignoredBuilds })
|
||||
if (!opts.neverBuiltDependencies) {
|
||||
ignoredScriptsLogger.debug({
|
||||
packageNames: ignoredBuilds ? dedupePackageNamesFromIgnoredBuilds(ignoredBuilds) : [],
|
||||
})
|
||||
}
|
||||
|
||||
if ((reporter != null) && typeof reporter === 'function') {
|
||||
streamParser.removeListener('data', reporter)
|
||||
@@ -372,7 +379,7 @@ export async function mutateModules (
|
||||
readonly updatedProjects: UpdatedProject[]
|
||||
readonly stats?: InstallationResultStats
|
||||
readonly depsRequiringBuild?: DepPath[]
|
||||
readonly ignoredBuilds: string[] | undefined
|
||||
readonly ignoredBuilds: IgnoredBuilds | undefined
|
||||
}
|
||||
|
||||
async function _install (): Promise<InnerInstallResult> {
|
||||
@@ -862,17 +869,19 @@ Note that in CI environments, this setting is enabled by default.`,
|
||||
}
|
||||
}
|
||||
|
||||
async function runUnignoredDependencyBuilds (opts: StrictInstallOptions, previousIgnoredBuilds: string[]): Promise<string[]> {
|
||||
async function runUnignoredDependencyBuilds (opts: StrictInstallOptions, previousIgnoredBuilds: IgnoredBuilds): Promise<Set<DepPath>> {
|
||||
if (!opts.onlyBuiltDependencies?.length) {
|
||||
return previousIgnoredBuilds
|
||||
}
|
||||
const onlyBuiltDeps = createPackageVersionPolicy(opts.onlyBuiltDependencies)
|
||||
const pkgsToBuild = previousIgnoredBuilds.flatMap((ignoredPkg) => {
|
||||
const matchResult = onlyBuiltDeps(ignoredPkg)
|
||||
const pkgsToBuild = Array.from(previousIgnoredBuilds).flatMap((ignoredPkg) => {
|
||||
const ignoredPkgName = dp.parse(ignoredPkg).name
|
||||
if (!ignoredPkgName) return []
|
||||
const matchResult = onlyBuiltDeps(ignoredPkgName)
|
||||
if (matchResult === true) {
|
||||
return [ignoredPkg]
|
||||
return [ignoredPkgName]
|
||||
} else if (Array.isArray(matchResult)) {
|
||||
return matchResult.map(version => `${ignoredPkg}@${version}`)
|
||||
return matchResult.map(version => `${ignoredPkgName}@${version}`)
|
||||
}
|
||||
return []
|
||||
})
|
||||
@@ -1052,7 +1061,7 @@ interface InstallFunctionResult {
|
||||
projects: UpdatedProject[]
|
||||
stats?: InstallationResultStats
|
||||
depsRequiringBuild: DepPath[]
|
||||
ignoredBuilds?: string[]
|
||||
ignoredBuilds?: IgnoredBuilds
|
||||
}
|
||||
|
||||
type InstallFunction = (
|
||||
@@ -1271,7 +1280,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
}
|
||||
let stats: InstallationResultStats | undefined
|
||||
const allowBuild = createAllowBuildFunction(opts)
|
||||
let ignoredBuilds: string[] | undefined
|
||||
let ignoredBuilds: IgnoredBuilds | undefined
|
||||
if (!opts.lockfileOnly && !isInstallationOnlyForLockfileCheck && opts.enableModulesDir) {
|
||||
const result = await linkPackages(
|
||||
projects,
|
||||
@@ -1371,8 +1380,13 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
unsafePerm: opts.unsafePerm,
|
||||
userAgent: opts.userAgent,
|
||||
})).ignoredBuilds
|
||||
if (ignoredBuilds == null && ctx.modulesFile?.ignoredBuilds?.length) {
|
||||
ignoredBuilds = ctx.modulesFile.ignoredBuilds
|
||||
if (ctx.modulesFile?.ignoredBuilds?.size) {
|
||||
ignoredBuilds ??= new Set()
|
||||
for (const ignoredBuild of ctx.modulesFile.ignoredBuilds.values()) {
|
||||
if (result.currentLockfile.packages?.[ignoredBuild]) {
|
||||
ignoredBuilds.add(ignoredBuild)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1703,3 +1717,16 @@ async function linkAllBins (
|
||||
depNodes.map(async depNode => limitLinking(async () => linkBinsOfDependencies(depNode, depGraph, opts)))
|
||||
)
|
||||
}
|
||||
|
||||
export class IgnoredBuildsError extends PnpmError {
|
||||
constructor (ignoredBuilds: IgnoredBuilds) {
|
||||
const packageNames = dedupePackageNamesFromIgnoredBuilds(ignoredBuilds)
|
||||
super('IGNORED_BUILDS', `Ignored build scripts: ${packageNames.join(', ')}`, {
|
||||
hint: 'Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function dedupePackageNamesFromIgnoredBuilds (ignoredBuilds: IgnoredBuilds): string[] {
|
||||
return Array.from(new Set(Array.from(ignoredBuilds ?? []).map(dp.removeSuffix))).sort(lexCompare)
|
||||
}
|
||||
|
||||
@@ -485,7 +485,7 @@ test('selectively allow scripts in some dependencies by onlyBuiltDependencies',
|
||||
|
||||
{
|
||||
const ignoredPkgsLog = reporter.getCalls().find((call) => call.firstArg.name === 'pnpm:ignored-scripts')?.firstArg
|
||||
expect(ignoredPkgsLog.packageNames).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example'])
|
||||
expect(ignoredPkgsLog.packageNames).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'])
|
||||
}
|
||||
reporter.resetHistory()
|
||||
|
||||
@@ -526,7 +526,7 @@ test('selectively allow scripts in some dependencies by onlyBuiltDependencies us
|
||||
|
||||
{
|
||||
const ignoredPkgsLog = reporter.getCalls().find((call) => call.firstArg.name === 'pnpm:ignored-scripts')?.firstArg
|
||||
expect(ignoredPkgsLog.packageNames).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example'])
|
||||
expect(ignoredPkgsLog.packageNames).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'])
|
||||
}
|
||||
reporter.resetHistory()
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
type DepPath,
|
||||
type DependencyManifest,
|
||||
type HoistedDependencies,
|
||||
type IgnoredBuilds,
|
||||
type ProjectId,
|
||||
type ProjectManifest,
|
||||
type Registries,
|
||||
@@ -191,7 +192,7 @@ export interface InstallationResultStats {
|
||||
|
||||
export interface InstallationResult {
|
||||
stats: InstallationResultStats
|
||||
ignoredBuilds: string[] | undefined
|
||||
ignoredBuilds: IgnoredBuilds | undefined
|
||||
}
|
||||
|
||||
export async function headlessInstall (opts: HeadlessOptions): Promise<InstallationResult> {
|
||||
@@ -517,7 +518,7 @@ export async function headlessInstall (opts: HeadlessOptions): Promise<Installat
|
||||
.map(({ depPath }) => depPath)
|
||||
)
|
||||
}
|
||||
let ignoredBuilds: string[] | undefined
|
||||
let ignoredBuilds: IgnoredBuilds | undefined
|
||||
if ((!opts.ignoreScripts || Object.keys(opts.patchedDependencies ?? {}).length > 0) && opts.enableModulesDir !== false) {
|
||||
const directNodes = new Set<string>()
|
||||
for (const id of union(importerIds, ['.'])) {
|
||||
@@ -562,8 +563,13 @@ export async function headlessInstall (opts: HeadlessOptions): Promise<Installat
|
||||
unsafePerm: opts.unsafePerm,
|
||||
userAgent: opts.userAgent,
|
||||
})).ignoredBuilds
|
||||
if (ignoredBuilds == null && opts.modulesFile?.ignoredBuilds?.length) {
|
||||
ignoredBuilds = opts.modulesFile.ignoredBuilds
|
||||
if (opts.modulesFile?.ignoredBuilds?.size) {
|
||||
ignoredBuilds ??= new Set()
|
||||
for (const ignoredBuild of opts.modulesFile.ignoredBuilds.values()) {
|
||||
if (filteredLockfile.packages?.[ignoredBuild]) {
|
||||
ignoredBuilds.add(ignoredBuild)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import path from 'path'
|
||||
import { type DepPath, type DependenciesField, type HoistedDependencies, type Registries } from '@pnpm/types'
|
||||
import {
|
||||
type DepPath,
|
||||
type DependenciesField,
|
||||
type HoistedDependencies,
|
||||
type IgnoredBuilds,
|
||||
type Registries,
|
||||
} from '@pnpm/types'
|
||||
import readYamlFile from 'read-yaml-file'
|
||||
import mapValues from 'ramda/src/map'
|
||||
import isWindows from 'is-windows'
|
||||
@@ -13,7 +19,7 @@ export type IncludedDependencies = {
|
||||
[dependenciesField in DependenciesField]: boolean
|
||||
}
|
||||
|
||||
export interface Modules {
|
||||
interface ModulesRaw {
|
||||
hoistedAliases?: { [depPath: DepPath]: string[] } // for backward compatibility
|
||||
hoistedDependencies: HoistedDependencies
|
||||
hoistPattern?: string[]
|
||||
@@ -22,7 +28,7 @@ export interface Modules {
|
||||
nodeLinker?: 'hoisted' | 'isolated' | 'pnp'
|
||||
packageManager: string
|
||||
pendingBuilds: string[]
|
||||
ignoredBuilds?: string[]
|
||||
ignoredBuilds?: DepPath[]
|
||||
prunedAt: string
|
||||
registries?: Registries // nullable for backward compatibility
|
||||
shamefullyHoist?: boolean // for backward compatibility
|
||||
@@ -35,18 +41,26 @@ export interface Modules {
|
||||
hoistedLocations?: Record<string, string[]>
|
||||
}
|
||||
|
||||
export type Modules = Omit<ModulesRaw, 'ignoredBuilds'> & {
|
||||
ignoredBuilds?: IgnoredBuilds
|
||||
}
|
||||
|
||||
export async function readModulesManifest (modulesDir: string): Promise<Modules | null> {
|
||||
const modulesYamlPath = path.join(modulesDir, MODULES_FILENAME)
|
||||
let modules!: Modules
|
||||
let modulesRaw!: ModulesRaw
|
||||
try {
|
||||
modules = await readYamlFile<Modules>(modulesYamlPath)
|
||||
if (!modules) return modules
|
||||
modulesRaw = await readYamlFile<ModulesRaw>(modulesYamlPath)
|
||||
if (!modulesRaw) return modulesRaw
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
return null
|
||||
}
|
||||
const modules = {
|
||||
...modulesRaw,
|
||||
ignoredBuilds: modulesRaw.ignoredBuilds ? new Set<DepPath>(modulesRaw.ignoredBuilds) : undefined,
|
||||
}
|
||||
if (!modules.virtualStoreDir) {
|
||||
modules.virtualStoreDir = path.join(modulesDir, '.pnpm')
|
||||
} else if (!path.isAbsolute(modules.virtualStoreDir)) {
|
||||
@@ -107,7 +121,7 @@ export async function writeModulesManifest (
|
||||
}
|
||||
): Promise<void> {
|
||||
const modulesYamlPath = path.join(modulesDir, MODULES_FILENAME)
|
||||
const saveModules = { ...modules }
|
||||
const saveModules = { ...modules, ignoredBuilds: modules.ignoredBuilds ? Array.from(modules.ignoredBuilds) : undefined }
|
||||
if (saveModules.skipped) saveModules.skipped.sort()
|
||||
|
||||
if (saveModules.hoistPattern == null || (saveModules.hoistPattern as unknown) === '') {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
/// <reference path="../../../__typings__/index.d.ts"/>
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { readModulesManifest, writeModulesManifest } from '@pnpm/modules-yaml'
|
||||
import { readModulesManifest, writeModulesManifest, type StrictModules } from '@pnpm/modules-yaml'
|
||||
import { sync as readYamlFile } from 'read-yaml-file'
|
||||
import isWindows from 'is-windows'
|
||||
import tempy from 'tempy'
|
||||
|
||||
test('writeModulesManifest() and readModulesManifest()', async () => {
|
||||
const modulesDir = tempy.directory()
|
||||
const modulesYaml = {
|
||||
const modulesYaml: StrictModules = {
|
||||
hoistedDependencies: {},
|
||||
included: {
|
||||
dependencies: true,
|
||||
devDependencies: true,
|
||||
optionalDependencies: true,
|
||||
},
|
||||
ignoredBuilds: [],
|
||||
ignoredBuilds: new Set(),
|
||||
layoutVersion: 1,
|
||||
packageManager: 'pnpm@2',
|
||||
pendingBuilds: [],
|
||||
@@ -66,14 +66,14 @@ test('backward compatible read of .modules.yaml created with shamefully-hoist=fa
|
||||
|
||||
test('readModulesManifest() should not create a node_modules directory if it does not exist', async () => {
|
||||
const modulesDir = path.join(tempy.directory(), 'node_modules')
|
||||
const modulesYaml = {
|
||||
const modulesYaml: StrictModules = {
|
||||
hoistedDependencies: {},
|
||||
included: {
|
||||
dependencies: true,
|
||||
devDependencies: true,
|
||||
optionalDependencies: true,
|
||||
},
|
||||
ignoredBuilds: [],
|
||||
ignoredBuilds: new Set(),
|
||||
layoutVersion: 1,
|
||||
packageManager: 'pnpm@2',
|
||||
pendingBuilds: [],
|
||||
@@ -94,14 +94,14 @@ test('readModulesManifest() should not create a node_modules directory if it doe
|
||||
|
||||
test('readModulesManifest() should create a node_modules directory if makeModuleDir is set to true', async () => {
|
||||
const modulesDir = path.join(tempy.directory(), 'node_modules')
|
||||
const modulesYaml = {
|
||||
const modulesYaml: StrictModules = {
|
||||
hoistedDependencies: {},
|
||||
included: {
|
||||
dependencies: true,
|
||||
devDependencies: true,
|
||||
optionalDependencies: true,
|
||||
},
|
||||
ignoredBuilds: [],
|
||||
ignoredBuilds: new Set(),
|
||||
layoutVersion: 1,
|
||||
packageManager: 'pnpm@2',
|
||||
pendingBuilds: [],
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
|
||||
export class IgnoredBuildsError extends PnpmError {
|
||||
constructor (ignoredBuilds: string[]) {
|
||||
super('IGNORED_BUILDS', `Ignored build scripts: ${ignoredBuilds.join(', ')}`, {
|
||||
hint: 'Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.',
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { rebuildProjects } from '@pnpm/plugin-commands-rebuild'
|
||||
import { createOrConnectStoreController, type CreateStoreControllerOptions } from '@pnpm/store-connection-manager'
|
||||
import { type IncludedDependencies, type Project, type ProjectsGraph, type ProjectRootDir, type PrepareExecutionEnv } from '@pnpm/types'
|
||||
import {
|
||||
IgnoredBuildsError,
|
||||
install,
|
||||
mutateModulesInSingleProject,
|
||||
type MutateModulesOptions,
|
||||
@@ -26,7 +27,6 @@ import { updateWorkspaceManifest } from '@pnpm/workspace.manifest-writer'
|
||||
import { createPkgGraph } from '@pnpm/workspace.pkgs-graph'
|
||||
import { updateWorkspaceState, type WorkspaceStateSettings } from '@pnpm/workspace.state'
|
||||
import isSubdir from 'is-subdir'
|
||||
import { IgnoredBuildsError } from './errors.js'
|
||||
import { getPinnedVersion } from './getPinnedVersion.js'
|
||||
import { getSaveType } from './getSaveType.js'
|
||||
import { getNodeExecPath } from './nodeExecPath.js'
|
||||
@@ -343,7 +343,7 @@ when running add/update with the --workspace option')
|
||||
configDependencies: opts.configDependencies,
|
||||
})
|
||||
}
|
||||
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
||||
if (opts.strictDepBuilds && ignoredBuilds?.size) {
|
||||
throw new IgnoredBuildsError(ignoredBuilds)
|
||||
}
|
||||
return
|
||||
@@ -364,7 +364,7 @@ when running add/update with the --workspace option')
|
||||
}),
|
||||
])
|
||||
}
|
||||
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
||||
if (opts.strictDepBuilds && ignoredBuilds?.size) {
|
||||
throw new IgnoredBuildsError(ignoredBuilds)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import { requireHooks } from '@pnpm/pnpmfile'
|
||||
import { sortPackages } from '@pnpm/sort-packages'
|
||||
import { createOrConnectStoreController, type CreateStoreControllerOptions } from '@pnpm/store-connection-manager'
|
||||
import {
|
||||
type IgnoredBuilds,
|
||||
type IncludedDependencies,
|
||||
type PackageManifest,
|
||||
type Project,
|
||||
@@ -34,6 +35,7 @@ import {
|
||||
import { updateWorkspaceManifest } from '@pnpm/workspace.manifest-writer'
|
||||
import {
|
||||
addDependenciesToPackage,
|
||||
IgnoredBuildsError,
|
||||
install,
|
||||
type InstallOptions,
|
||||
type MutatedProject,
|
||||
@@ -50,7 +52,6 @@ import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './u
|
||||
import { getSaveType } from './getSaveType.js'
|
||||
import { getPinnedVersion } from './getPinnedVersion.js'
|
||||
import { type PreferredVersions } from '@pnpm/resolver-base'
|
||||
import { IgnoredBuildsError } from './errors.js'
|
||||
|
||||
export type RecursiveOptions = CreateStoreControllerOptions & Pick<Config,
|
||||
| 'bail'
|
||||
@@ -299,7 +300,7 @@ export async function recursive (
|
||||
}))
|
||||
await Promise.all(promises)
|
||||
}
|
||||
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
||||
if (opts.strictDepBuilds && ignoredBuilds?.size) {
|
||||
throw new IgnoredBuildsError(ignoredBuilds)
|
||||
}
|
||||
return true
|
||||
@@ -355,7 +356,7 @@ export async function recursive (
|
||||
interface ActionResult {
|
||||
updatedCatalogs?: Catalogs
|
||||
updatedManifest: ProjectManifest
|
||||
ignoredBuilds: string[] | undefined
|
||||
ignoredBuilds: IgnoredBuilds | undefined
|
||||
}
|
||||
|
||||
type ActionFunction = (manifest: PackageManifest | ProjectManifest, opts: ActionOpts) => Promise<ActionResult>
|
||||
@@ -419,7 +420,7 @@ export async function recursive (
|
||||
Object.assign(updatedCatalogs, newCatalogsAddition)
|
||||
}
|
||||
}
|
||||
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
||||
if (opts.strictDepBuilds && ignoredBuilds?.size) {
|
||||
throw new IgnoredBuildsError(ignoredBuilds)
|
||||
}
|
||||
result[rootDir].status = 'passed'
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -2472,6 +2472,9 @@ importers:
|
||||
'@pnpm/config.config-writer':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/config-writer
|
||||
'@pnpm/dependency-path':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/dependency-path
|
||||
'@pnpm/logger':
|
||||
specifier: 'catalog:'
|
||||
version: 1001.0.0
|
||||
@@ -2545,6 +2548,9 @@ importers:
|
||||
'@pnpm/core-loggers':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core-loggers
|
||||
'@pnpm/dependency-path':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/dependency-path
|
||||
'@pnpm/deps.graph-sequencer':
|
||||
specifier: workspace:*
|
||||
version: link:../../deps/graph-sequencer
|
||||
@@ -4978,6 +4984,9 @@ importers:
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
'@pnpm/util.lex-comparator':
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.2
|
||||
'@pnpm/worker':
|
||||
specifier: workspace:^
|
||||
version: link:../../worker
|
||||
|
||||
@@ -383,8 +383,8 @@ test('the list of ignored builds is preserved after a repeat install', async ()
|
||||
expect(result.stdout.toString()).toContain('Ignored build scripts:')
|
||||
|
||||
const modulesManifest = project.readModulesManifest()
|
||||
expect(modulesManifest?.ignoredBuilds?.sort()).toStrictEqual([
|
||||
'@pnpm.e2e/pre-and-postinstall-scripts-example',
|
||||
'esbuild',
|
||||
expect(Array.from(modulesManifest!.ignoredBuilds!).sort()).toStrictEqual([
|
||||
'@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0',
|
||||
'esbuild@0.25.0',
|
||||
])
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user