feat!: remove requiresBuild from the lockfile (#7710)

close #7707
This commit is contained in:
Zoltan Kochan
2024-02-27 11:50:43 +01:00
committed by GitHub
parent 75840d6570
commit 0e6b757cf5
48 changed files with 178 additions and 365 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/exec.pkg-requires-build": major
---
Initial release.

View File

@@ -41,8 +41,7 @@ export interface DependenciesGraphNode {
optional: boolean
depPath: string // this option is only needed for saving pendingBuild when running with --ignore-scripts flag
isBuilt?: boolean
requiresBuild: boolean
prepare: boolean
requiresBuild?: boolean
hasBin: boolean
filesIndexFile?: string
patchFile?: PatchFile
@@ -195,8 +194,6 @@ export async function lockfileToDepGraph (
name: pkgName,
optional: !!pkgSnapshot.optional,
optionalDependencies: new Set(Object.keys(pkgSnapshot.optionalDependencies ?? {})),
prepare: pkgSnapshot.prepare === true,
requiresBuild: pkgSnapshot.requiresBuild === true,
patchFile: opts.patchedDependencies?.[`${pkgName}@${pkgVersion}`],
}
pkgSnapshotByLocation[dir] = pkgSnapshot

View File

@@ -50,6 +50,7 @@ export async function fetchNode (fetch: FetchFromRegistry, version: string, targ
filesResponse: {
filesIndex: filesIndex as Record<string, string>,
resolvedFrom: 'remote',
requiresBuild: false,
},
force: true,
})

View File

@@ -1,7 +0,0 @@
# @pnpm/exec.files-include-install-scripts
## 1.0.0
### Major Changes
- cfc017ee3: Initial release.

View File

@@ -1,15 +0,0 @@
# @pnpm/exec.files-include-install-scripts
> Checks if a package has files indicating that it needs to be built during installation
[![npm version](https://img.shields.io/npm/v/@pnpm/exec.files-include-install-scripts.svg)](https://www.npmjs.com/package/@pnpm/exec.files-include-install-scripts)
## Installation
```sh
pnpm add @pnpm/exec.files-include-install-scripts
```
## License
MIT

View File

@@ -1,4 +0,0 @@
export function filesIncludeInstallScripts (filesIndex: Record<string, unknown>): boolean {
return filesIndex['binding.gyp'] != null ||
Object.keys(filesIndex).some((filename) => !(filename.match(/^[.]hooks[\\/]/) == null)) // TODO: optimize this
}

View File

@@ -0,0 +1,15 @@
# @pnpm/exec.pkg-requires-build
> Checks if a package requires to be built
[![npm version](https://img.shields.io/npm/v/@pnpm/exec.pkg-requires-build.svg)](https://www.npmjs.com/package/@pnpm/exec.pkg-requires-build)
## Installation
```sh
pnpm add @pnpm/exec.pkg-requires-build
```
## License
MIT

View File

@@ -1,7 +1,7 @@
{
"name": "@pnpm/exec.files-include-install-scripts",
"version": "1.0.0",
"description": "Checks if a package has files indicating that it needs to be built during installation",
"name": "@pnpm/exec.pkg-requires-build",
"version": "0.0.0",
"description": "Checks if a package requires to be built",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
@@ -18,7 +18,7 @@
"fix": "tslint -c tslint.json src/**/*.ts test/**/*.ts --fix",
"compile": "tsc --build && pnpm run lint --fix"
},
"repository": "https://github.com/pnpm/pnpm/blob/main/exec/files-include-install-scripts",
"repository": "https://github.com/pnpm/pnpm/blob/main/exec/pkg-requires-build",
"keywords": [
"pnpm9",
"pnpm"
@@ -27,9 +27,12 @@
"bugs": {
"url": "https://github.com/pnpm/pnpm/issues"
},
"homepage": "https://github.com/pnpm/pnpm/blob/main/exec/files-include-install-scripts#readme",
"homepage": "https://github.com/pnpm/pnpm/blob/main/exec/pkg-requires-build#readme",
"dependencies": {
"@pnpm/types": "workspace:*"
},
"devDependencies": {
"@pnpm/exec.files-include-install-scripts": "workspace:*"
"@pnpm/exec.pkg-requires-build": "workspace:*"
},
"funding": "https://opencollective.com/pnpm",
"exports": {

View File

@@ -0,0 +1,17 @@
import { type DependencyManifest } from '@pnpm/types'
export function pkgRequiresBuild (manifest: Partial<DependencyManifest> | undefined, filesIndex: Record<string, unknown>) {
return Boolean(
manifest?.scripts != null && (
Boolean(manifest.scripts.preinstall) ||
Boolean(manifest.scripts.install) ||
Boolean(manifest.scripts.postinstall)
) ||
filesIncludeInstallScripts(filesIndex)
)
}
function filesIncludeInstallScripts (filesIndex: Record<string, unknown>): boolean {
return filesIndex['binding.gyp'] != null ||
Object.keys(filesIndex).some((filename) => !(filename.match(/^[.]hooks[\\/]/) == null)) // TODO: optimize this
}

View File

@@ -8,6 +8,10 @@
"src/**/*.ts",
"../../__typings__/**/*.d.ts"
],
"references": [],
"references": [
{
"path": "../../packages/types"
}
],
"composite": true
}

View File

@@ -34,6 +34,7 @@
"@pnpm/logger": "^5.0.0"
},
"dependencies": {
"@pnpm/exec.pkg-requires-build": "workspace:*",
"@pnpm/fetcher-base": "workspace:*",
"@pnpm/fs.packlist": "workspace:*",
"@pnpm/read-project-manifest": "workspace:*",

View File

@@ -1,5 +1,6 @@
import { promises as fs, type Stats } from 'fs'
import path from 'path'
import { pkgRequiresBuild } from '@pnpm/exec.pkg-requires-build'
import type { DirectoryFetcher, DirectoryFetcherOptions } from '@pnpm/fetcher-base'
import { logger } from '@pnpm/logger'
import { packlist } from '@pnpm/fs.packlist'
@@ -21,7 +22,7 @@ export function createDirectoryFetcher (
const directoryFetcher: DirectoryFetcher = (cafs, resolution, opts) => {
const dir = path.join(opts.lockfileDir, resolution.directory)
return fetchFromDir(dir, opts)
return fetchFromDir(dir)
}
return {
@@ -36,30 +37,28 @@ export async function fetchFromDir (
opts: FetchFromDirOpts & CreateDirectoryFetcherOptions
) {
if (opts.includeOnlyPackageFiles) {
return fetchPackageFilesFromDir(dir, opts)
return fetchPackageFilesFromDir(dir)
}
const readFileStat: ReadFileStat = opts?.resolveSymlinks === true ? realFileStat : fileStat
return fetchAllFilesFromDir(readFileStat, dir, opts)
return fetchAllFilesFromDir(readFileStat, dir)
}
async function fetchAllFilesFromDir (
readFileStat: ReadFileStat,
dir: string,
opts: FetchFromDirOpts
dir: string
) {
const filesIndex = await _fetchAllFilesFromDir(readFileStat, dir)
let manifest: DependencyManifest | undefined
if (opts.readManifest) {
// In a regular pnpm workspace it will probably never happen that a dependency has no package.json file.
// Safe read was added to support the Bit workspace in which the components have no package.json files.
// Related PR in Bit: https://github.com/teambit/bit/pull/5251
manifest = await safeReadProjectManifestOnly(dir) as DependencyManifest ?? undefined
}
// In a regular pnpm workspace it will probably never happen that a dependency has no package.json file.
// Safe read was added to support the Bit workspace in which the components have no package.json files.
// Related PR in Bit: https://github.com/teambit/bit/pull/5251
const manifest = await safeReadProjectManifestOnly(dir) as DependencyManifest ?? undefined
const requiresBuild = pkgRequiresBuild(manifest, filesIndex)
return {
local: true as const,
filesIndex,
packageImportMethod: 'hardlink' as const,
manifest,
requiresBuild,
}
}
@@ -124,23 +123,19 @@ async function fileStat (filePath: string): Promise<{ filePath: string, stat: St
}
}
async function fetchPackageFilesFromDir (
dir: string,
opts: FetchFromDirOpts
) {
async function fetchPackageFilesFromDir (dir: string) {
const files = await packlist(dir)
const filesIndex: Record<string, string> = Object.fromEntries(files.map((file) => [file, path.join(dir, file)]))
let manifest: DependencyManifest | undefined
if (opts.readManifest) {
// In a regular pnpm workspace it will probably never happen that a dependency has no package.json file.
// Safe read was added to support the Bit workspace in which the components have no package.json files.
// Related PR in Bit: https://github.com/teambit/bit/pull/5251
manifest = await safeReadProjectManifestOnly(dir) as DependencyManifest ?? undefined
}
// In a regular pnpm workspace it will probably never happen that a dependency has no package.json file.
// Safe read was added to support the Bit workspace in which the components have no package.json files.
// Related PR in Bit: https://github.com/teambit/bit/pull/5251
const manifest = await safeReadProjectManifestOnly(dir) as DependencyManifest ?? undefined
const requiresBuild = pkgRequiresBuild(manifest, filesIndex)
return {
local: true as const,
filesIndex,
packageImportMethod: 'hardlink' as const,
manifest,
requiresBuild,
}
}

View File

@@ -12,6 +12,9 @@
{
"path": "../../__utils__/test-fixtures"
},
{
"path": "../../exec/pkg-requires-build"
},
{
"path": "../../fs/packlist"
},

View File

@@ -26,6 +26,7 @@ export interface FetchResult {
local?: boolean
manifest?: DependencyManifest
filesIndex: Record<string, string>
requiresBuild: boolean
}
export interface GitFetcherOptions {
@@ -34,7 +35,13 @@ export interface GitFetcherOptions {
pkg?: PkgNameVersion
}
export type GitFetcher = FetchFunction<GitResolution, GitFetcherOptions, { filesIndex: Record<string, string>, manifest?: DependencyManifest }>
export interface GitFetcherResult {
filesIndex: Record<string, string>
manifest?: DependencyManifest
requiresBuild: boolean
}
export type GitFetcher = FetchFunction<GitResolution, GitFetcherOptions, GitFetcherResult>
export interface DirectoryFetcherOptions {
lockfileDir: string
@@ -46,6 +53,7 @@ export interface DirectoryFetcherResult {
filesIndex: Record<string, string>
packageImportMethod: 'hardlink'
manifest?: DependencyManifest
requiresBuild: boolean
}
export type DirectoryFetcher = FetchFunction<DirectoryResolution, DirectoryFetcherOptions, DirectoryFetcherResult>

View File

@@ -25,7 +25,7 @@ export interface CreateGitHostedTarballFetcher {
export function createGitHostedTarballFetcher (fetchRemoteTarball: FetchFunction, fetcherOpts: CreateGitHostedTarballFetcher): FetchFunction {
const fetch = async (cafs: Cafs, resolution: Resolution, opts: FetchOptions) => {
const tempIndexFile = pathTemp(opts.filesIndexFile)
const { filesIndex, manifest } = await fetchRemoteTarball(cafs, resolution, {
const { filesIndex, manifest, requiresBuild } = await fetchRemoteTarball(cafs, resolution, {
...opts,
filesIndexFile: tempIndexFile,
})
@@ -37,6 +37,7 @@ export function createGitHostedTarballFetcher (fetchRemoteTarball: FetchFunction
return {
filesIndex: prepareResult.filesIndex,
manifest: prepareResult.manifest ?? manifest,
requiresBuild,
}
} catch (err: any) { // eslint-disable-line
err.message = `Failed to prepare git-hosted package fetched from "${resolution.tarball}": ${err.message}`
@@ -67,6 +68,7 @@ async function prepareGitHostedPkg (
filesResponse: {
filesIndex,
resolvedFrom: 'remote',
requiresBuild: false,
},
force: true,
})

View File

@@ -74,16 +74,6 @@ function normalizeLockfile (lockfile: InlineSpecifiersLockfile, opts: NormalizeL
if ((lockfileToSave.patchedDependencies != null) && isEmpty(lockfileToSave.patchedDependencies)) {
delete lockfileToSave.patchedDependencies
}
if (lockfileToSave.neverBuiltDependencies != null) {
if (isEmpty(lockfileToSave.neverBuiltDependencies)) {
delete lockfileToSave.neverBuiltDependencies
} else {
lockfileToSave.neverBuiltDependencies = lockfileToSave.neverBuiltDependencies.sort()
}
}
if (lockfileToSave.onlyBuiltDependencies != null) {
lockfileToSave.onlyBuiltDependencies = lockfileToSave.onlyBuiltDependencies.sort()
}
if (!lockfileToSave.packageExtensionsChecksum) {
delete lockfileToSave.packageExtensionsChecksum
}

View File

@@ -35,8 +35,6 @@ type RootKey = keyof LockfileFile
const ROOT_KEYS: readonly RootKey[] = [
'lockfileVersion',
'settings',
'neverBuiltDependencies',
'onlyBuiltDependencies',
'overrides',
'packageExtensionsChecksum',
'pnpmfileChecksum',

View File

@@ -4,10 +4,7 @@ import { convertToLockfileFile } from '../lib/lockfileFormatConverters'
test('empty overrides and neverBuiltDependencies are removed during lockfile normalization', () => {
expect(convertToLockfileFile({
lockfileVersion: LOCKFILE_VERSION,
// but this should be preserved.
onlyBuiltDependencies: [],
overrides: {},
neverBuiltDependencies: [],
patchedDependencies: {},
packages: {},
importers: {
@@ -24,7 +21,6 @@ test('empty overrides and neverBuiltDependencies are removed during lockfile nor
forceSharedFormat: false,
})).toStrictEqual({
lockfileVersion: LOCKFILE_VERSION,
onlyBuiltDependencies: [],
importers: {
foo: {
dependencies: {

View File

@@ -14,8 +14,6 @@ export interface Lockfile {
lockfileVersion: number | string
time?: Record<string, string>
packages?: PackageSnapshots
neverBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
overrides?: Record<string, string>
packageExtensionsChecksum?: string
patchedDependencies?: Record<string, PatchFile>
@@ -77,9 +75,7 @@ export interface PackageSnapshot {
id?: string
dev?: true | false
optional?: true
requiresBuild?: true
patched?: true
prepare?: true
hasBin?: true
// name and version are only needed
// for packages that are hosted not in the npm registry

View File

@@ -339,8 +339,6 @@ export async function mutateModules (
autoInstallPeers: opts.autoInstallPeers,
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
overrides: opts.overrides,
neverBuiltDependencies: opts.neverBuiltDependencies,
onlyBuiltDependencies: opts.onlyBuiltDependencies,
packageExtensionsChecksum,
patchedDependencies,
pnpmfileChecksum,
@@ -360,8 +358,6 @@ export async function mutateModules (
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
}
ctx.wantedLockfile.overrides = opts.overrides
ctx.wantedLockfile.neverBuiltDependencies = opts.neverBuiltDependencies
ctx.wantedLockfile.onlyBuiltDependencies = opts.onlyBuiltDependencies
ctx.wantedLockfile.packageExtensionsChecksum = packageExtensionsChecksum
ctx.wantedLockfile.pnpmfileChecksum = pnpmfileChecksum
ctx.wantedLockfile.patchedDependencies = patchedDependencies
@@ -711,7 +707,6 @@ async function calcPatchHashes (patches: Record<string, string>, lockfileDir: st
function getOutdatedLockfileSetting (
lockfile: Lockfile,
{
neverBuiltDependencies,
onlyBuiltDependencies,
overrides,
packageExtensionsChecksum,
@@ -720,7 +715,6 @@ function getOutdatedLockfileSetting (
excludeLinksFromLockfile,
pnpmfileChecksum,
}: {
neverBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
overrides?: Record<string, string>
packageExtensionsChecksum?: string
@@ -733,12 +727,6 @@ function getOutdatedLockfileSetting (
if (!equals(lockfile.overrides ?? {}, overrides ?? {})) {
return 'overrides'
}
if (!equals((lockfile.neverBuiltDependencies ?? []).sort(), (neverBuiltDependencies ?? []).sort())) {
return 'neverBuiltDependencies'
}
if (!equals(onlyBuiltDependencies?.sort(), lockfile.onlyBuiltDependencies)) {
return 'onlyBuiltDependencies'
}
if (lockfile.packageExtensionsChecksum !== packageExtensionsChecksum) {
return 'packageExtensionsChecksum'
}
@@ -994,7 +982,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
let {
dependenciesGraph,
dependenciesByProjectId,
finishLockfileUpdates,
linkedDependenciesByProjectId,
newLockfile,
outdatedDependencies,
@@ -1129,7 +1116,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
}
)
stats = result.stats
await finishLockfileUpdates()
if (opts.enablePnp) {
const importerNames = Object.fromEntries(
projects.map(({ manifest, id }) => [id, manifest.name ?? id])
@@ -1150,13 +1136,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
// we can use concat here because we always only append new packages, which are guaranteed to not be there by definition
ctx.pendingBuilds = ctx.pendingBuilds
.concat(
await pFilter(result.newDepPaths,
(depPath) => {
const requiresBuild = dependenciesGraph[depPath].requiresBuild
if (typeof requiresBuild === 'function') return requiresBuild()
return requiresBuild
}
)
result.newDepPaths.filter((depPath) => dependenciesGraph[depPath].requiresBuild)
)
}
if (!opts.ignoreScripts || Object.keys(opts.patchedDependencies ?? {}).length > 0) {
@@ -1331,7 +1311,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
)
}
} else {
await finishLockfileUpdates()
if (opts.useLockfile && !isInstallationOnlyForLockfileCheck) {
await writeWantedLockfile(ctx.lockfileDir, newLockfile, lockfileOpts)
}

View File

@@ -447,9 +447,7 @@ async function linkAllPkgs (
depNodes.map(async (depNode) => {
const { files } = await depNode.fetching()
if (typeof depNode.requiresBuild === 'function') {
depNode.requiresBuild = await depNode.requiresBuild()
}
depNode.requiresBuild = files.requiresBuild
let sideEffectsCacheKey: string | undefined
if (opts.sideEffectsCacheRead && files.sideEffects && !isEmpty(files.sideEffects)) {
sideEffectsCacheKey = calcDepState(opts.depGraph, opts.depsStateCache, depNode.depPath, {
@@ -462,7 +460,7 @@ async function linkAllPkgs (
filesResponse: files,
force: opts.force,
sideEffectsCacheKey,
requiresBuild: depNode.patchFile != null || (depNode.optional ? (depNode.requiresBuild ? undefined : false) : depNode.requiresBuild),
requiresBuild: depNode.patchFile != null || depNode.requiresBuild,
})
if (importMethod) {
progressLogger.debug({

View File

@@ -34,7 +34,6 @@ test('fix broken lockfile with --fix-lockfile', async () => {
resolution: {
integrity: 'sha512-oxKe64UH049mJqrKkynWp6Vu0Rlm/BTXO/bJZuN2mmR3RtOFNepLlSWDd1eo16PzHpQAoNG97rLU1V/YxesJjw==',
},
// requiresBuild: true,
// dev: true
},
},
@@ -59,7 +58,6 @@ test('fix broken lockfile with --fix-lockfile', async () => {
expect(lockfile.packages?.['/core-js-pure@3.16.2']?.resolution).toEqual({
integrity: 'sha512-oxKe64UH049mJqrKkynWp6Vu0Rlm/BTXO/bJZuN2mmR3RtOFNepLlSWDd1eo16PzHpQAoNG97rLU1V/YxesJjw==',
})
expect(lockfile.packages?.['/core-js-pure@3.16.2']?.requiresBuild).toBeTruthy()
expect(lockfile.packages?.['/core-js-pure@3.16.2']?.dev).toBeTruthy()
})
@@ -141,12 +139,10 @@ test('--fix-lockfile should preserve all locked dependencies version', async ()
},
'/core-js-pure@3.17.2': {
resolution: { integrity: 'sha512-2VV7DlIbooyTI7Bh+yzOOWL9tGwLnQKHno7qATE+fqZzDKYr6llVjVQOzpD/QLZFgXDPb8T71pJokHEZHEYJhQ==' },
// requiresBuild: true,
dev: false,
},
'/core-js-pure@3.17.3': {
// resolution: { integrity: 'sha512-YusrqwiOTTn8058JDa0cv9unbXdIiIgcgI9gXso0ey4WgkFLd3lYlV9rp9n7nDCsYxXsMDTjA4m1h3T348mdlQ==' },
// requiresBuild: true,
// dev: false
},
'/regenerator-runtime@0.13.9': {
@@ -217,14 +213,12 @@ test('--fix-lockfile should preserve all locked dependencies version', async ()
expect(lockfile.packages?.['/core-js-pure@3.17.2']).toBeTruthy()
expect(lockfile.packages?.['/core-js-pure@3.17.2']?.resolution).toHaveProperty('integrity', 'sha512-2VV7DlIbooyTI7Bh+yzOOWL9tGwLnQKHno7qATE+fqZzDKYr6llVjVQOzpD/QLZFgXDPb8T71pJokHEZHEYJhQ==')
expect(lockfile.packages?.['/core-js-pure@3.17.2']?.requiresBuild).toBeTruthy()
expect(lockfile.packages?.['/core-js-pure@3.17.2']?.dev).toBeFalsy()
expect(lockfile.packages?.['/core-js-pure@3.17.3']).toBeTruthy()
expect(lockfile.packages?.['/core-js-pure@3.17.3']?.resolution).toEqual({
integrity: 'sha512-YusrqwiOTTn8058JDa0cv9unbXdIiIgcgI9gXso0ey4WgkFLd3lYlV9rp9n7nDCsYxXsMDTjA4m1h3T348mdlQ==',
})
expect(lockfile.packages?.['/core-js-pure@3.17.3']?.requiresBuild).toBeTruthy()
expect(lockfile.packages?.['/core-js-pure@3.17.3']?.dev).toBeFalsy()
expect(lockfile.packages?.['/regenerator-runtime@0.13.9']).toBeTruthy()

View File

@@ -53,9 +53,6 @@ test('run pre/postinstall scripts', async () => {
const generatedByPostinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall')
expect(typeof generatedByPostinstall).toBe('function')
}
const lockfile = project.readLockfile()
expect(lockfile.packages['/@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'].requiresBuild)
})
test('run pre/postinstall scripts, when PnP is used and no symlinks', async () => {
@@ -291,9 +288,6 @@ test('run prepare script for git-hosted dependencies', async () => {
'install',
'postinstall',
])
const lockfile = project.readLockfile()
expect(lockfile.packages['github.com/pnpm/test-git-fetch/d222f6bfbdea55c032fdb5f0538d52b2a484bbbf'].prepare === true).toBeTruthy()
})
test('lifecycle scripts run before linking bins', async () => {
@@ -411,7 +405,7 @@ test('scripts have access to unlisted bins when hoisting is used', async () => {
})
test('selectively ignore scripts in some dependencies by neverBuiltDependencies', async () => {
const project = prepareEmpty()
prepareEmpty()
const neverBuiltDependencies = ['@pnpm.e2e/pre-and-postinstall-scripts-example']
const manifest = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
@@ -422,11 +416,6 @@ test('selectively ignore scripts in some dependencies by neverBuiltDependencies'
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
const lockfile = project.readLockfile()
expect(lockfile.neverBuiltDependencies).toStrictEqual(neverBuiltDependencies)
expect(lockfile.packages['/@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'].requiresBuild).toBe(undefined)
expect(lockfile.packages['/@pnpm.e2e/install-script-example@1.0.0'].requiresBuild).toBeTruthy()
rimraf('node_modules')
await install(manifest, testDefaults({ fastUnpack: false, frozenLockfile: true, neverBuiltDependencies }))
@@ -449,7 +438,7 @@ test('throw an exception when both neverBuiltDependencies and onlyBuiltDependenc
})
test('selectively allow scripts in some dependencies by onlyBuiltDependencies', async () => {
const project = prepareEmpty()
prepareEmpty()
const onlyBuiltDependencies = ['@pnpm.e2e/install-script-example']
const manifest = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
@@ -460,11 +449,6 @@ test('selectively allow scripts in some dependencies by onlyBuiltDependencies',
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
const lockfile = project.readLockfile()
expect(lockfile.onlyBuiltDependencies).toStrictEqual(onlyBuiltDependencies)
expect(lockfile.packages['/@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'].requiresBuild).toBe(undefined)
expect(lockfile.packages['/@pnpm.e2e/install-script-example@1.0.0'].requiresBuild).toBe(true)
rimraf('node_modules')
await install(manifest, testDefaults({ fastUnpack: false, frozenLockfile: true, onlyBuiltDependencies }))
@@ -517,78 +501,6 @@ test('selectively allow scripts in some dependencies by onlyBuiltDependenciesFil
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
})
test('lockfile is updated if neverBuiltDependencies is changed', async () => {
const project = prepareEmpty()
const manifest = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({ fastUnpack: false })
)
{
const lockfile = project.readLockfile()
expect(lockfile.neverBuiltDependencies).toBeFalsy()
expect(lockfile.packages['/@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'].requiresBuild).toBeTruthy()
expect(lockfile.packages['/@pnpm.e2e/install-script-example@1.0.0'].requiresBuild).toBeTruthy()
}
const neverBuiltDependencies = ['@pnpm.e2e/pre-and-postinstall-scripts-example']
await mutateModulesInSingleProject({
manifest,
mutation: 'install',
rootDir: process.cwd(),
}, testDefaults({ neverBuiltDependencies }))
{
const lockfile = project.readLockfile()
expect(lockfile.neverBuiltDependencies).toStrictEqual(neverBuiltDependencies)
expect(lockfile.packages['/@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'].requiresBuild).toBe(undefined)
expect(lockfile.packages['/@pnpm.e2e/install-script-example@1.0.0'].requiresBuild).toBeTruthy()
}
})
test('lockfile is updated if onlyBuiltDependencies is changed', async () => {
const project = prepareEmpty()
const manifest = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({ fastUnpack: false })
)
{
const lockfile = project.readLockfile()
expect(lockfile.onlyBuiltDependencies).toBeFalsy()
expect(lockfile.packages['/@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'].requiresBuild).toBeTruthy()
expect(lockfile.packages['/@pnpm.e2e/install-script-example@1.0.0'].requiresBuild).toBeTruthy()
}
const onlyBuiltDependencies: string[] = []
await mutateModulesInSingleProject({
manifest,
mutation: 'install',
rootDir: process.cwd(),
}, testDefaults({ onlyBuiltDependencies }))
{
const lockfile = project.readLockfile()
expect(lockfile.onlyBuiltDependencies).toStrictEqual(onlyBuiltDependencies)
expect(lockfile.packages['/@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'].requiresBuild).toBe(undefined)
expect(lockfile.packages['/@pnpm.e2e/install-script-example@1.0.0'].requiresBuild).toBe(undefined)
}
onlyBuiltDependencies.push('@pnpm.e2e/pre-and-postinstall-scripts-example')
await mutateModulesInSingleProject({
manifest,
mutation: 'install',
rootDir: process.cwd(),
}, testDefaults({ onlyBuiltDependencies }))
{
const lockfile = project.readLockfile()
expect(lockfile.onlyBuiltDependencies).toStrictEqual(onlyBuiltDependencies)
expect(lockfile.packages['/@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'].requiresBuild).toBe(true)
expect(lockfile.packages['/@pnpm.e2e/install-script-example@1.0.0'].requiresBuild).toBe(undefined)
}
})
test('lifecycle scripts have access to package\'s own binary by binary name', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({},

View File

@@ -546,14 +546,11 @@ test('bin specified in the directories property symlinked to .bin folder when pr
})
testOnNonWindows('building native addons', async () => {
const project = prepareEmpty()
prepareEmpty()
await addDependenciesToPackage({}, ['diskusage@1.1.3'], testDefaults({ fastUnpack: false }))
expect(fs.existsSync('node_modules/diskusage/build')).toBeTruthy()
const lockfile = project.readLockfile()
expect(lockfile.packages).toHaveProperty(['/diskusage@1.1.3', 'requiresBuild'], true)
})
test('should update subdep on second install', async () => {

View File

@@ -75,10 +75,6 @@ test('skip optional dependency that does not support the current OS', async () =
const lockfile = project.readLockfile()
expect(lockfile.packages['/@pnpm.e2e/not-compatible-with-any-os@1.0.0']).toBeTruthy()
// optional dependencies always get requiresBuild: true
// this is to resolve https://github.com/pnpm/pnpm/issues/2038
expect(lockfile.packages['/@pnpm.e2e/not-compatible-with-any-os@1.0.0'].requiresBuild).toBeTruthy()
expect(lockfile.packages['/@pnpm.e2e/dep-of-optional-pkg@1.0.0']).toBeTruthy()
const currentLockfile = project.readCurrentLockfile()

View File

@@ -828,6 +828,7 @@ async function linkAllPkgs (
throw err
}
depNode.requiresBuild = filesResponse.requiresBuild
let sideEffectsCacheKey: string | undefined
if (opts.sideEffectsCacheRead && filesResponse.sideEffects && !isEmpty(filesResponse.sideEffects)) {
sideEffectsCacheKey = calcDepState(opts.depGraph, opts.depsStateCache, depNode.dir, {
@@ -839,7 +840,7 @@ async function linkAllPkgs (
filesResponse,
force: opts.force,
disableRelinkLocalDirDeps: opts.disableRelinkLocalDirDeps,
requiresBuild: depNode.patchFile != null || (depNode.optional ? (depNode.requiresBuild ? undefined : false) : depNode.requiresBuild),
requiresBuild: depNode.patchFile != null || depNode.requiresBuild,
sideEffectsCacheKey,
})
if (importMethod) {

View File

@@ -111,6 +111,7 @@ async function linkAllPkgsInOrder (
throw err
}
depNode.requiresBuild = filesResponse.requiresBuild
let sideEffectsCacheKey: string | undefined
if (opts.sideEffectsCacheRead && filesResponse.sideEffects && !isEmpty(filesResponse.sideEffects)) {
sideEffectsCacheKey = _calcDepState(dir, {
@@ -127,7 +128,7 @@ async function linkAllPkgsInOrder (
force: true,
disableRelinkLocalDirDeps: opts.disableRelinkLocalDirDeps,
keepModulesDir: true,
requiresBuild: depNode.patchFile != null || (depNode.optional ? (depNode.requiresBuild ? undefined : false) : depNode.requiresBuild),
requiresBuild: depNode.patchFile != null || depNode.requiresBuild,
sideEffectsCacheKey,
})
if (importMethod) {

View File

@@ -247,8 +247,6 @@ async function fetchDeps (
name: pkgName,
optional: !!pkgSnapshot.optional,
optionalDependencies: new Set(Object.keys(pkgSnapshot.optionalDependencies ?? {})),
prepare: pkgSnapshot.prepare === true,
requiresBuild: pkgSnapshot.requiresBuild === true,
patchFile: opts.patchedDependencies?.[`${pkgName}@${pkgVersion}`],
}
if (!opts.pkgLocationsByDepPath[depPath]) {

View File

@@ -320,7 +320,7 @@ function fetchToStore (
readPkgFromCafs: (
filesIndexFile: string,
readManifest?: boolean
) => Promise<{ verified: boolean, pkgFilesIndex: PackageFilesIndex, manifest?: DependencyManifest }>
) => Promise<{ verified: boolean, pkgFilesIndex: PackageFilesIndex, manifest?: DependencyManifest, requiresBuild: boolean }>
fetch: (
packageId: string,
resolution: Resolution,
@@ -459,7 +459,7 @@ function fetchToStore (
) &&
!isLocalPkg
) {
const { verified, pkgFilesIndex, manifest } = await ctx.readPkgFromCafs(filesIndexFile, opts.fetchRawManifest)
const { verified, pkgFilesIndex, manifest, requiresBuild } = await ctx.readPkgFromCafs(filesIndexFile, opts.fetchRawManifest)
if (verified) {
if (
(
@@ -488,6 +488,7 @@ Actual package in the store by the given integrity: ${pkgFilesIndex.name}@${pkgF
filesIndex: pkgFilesIndex.files,
resolvedFrom: 'store',
sideEffects: pkgFilesIndex.sideEffects,
requiresBuild,
},
bundledManifest: manifest == null ? manifest : normalizeBundledManifest(manifest),
})
@@ -549,6 +550,7 @@ Actual package in the store by the given integrity: ${pkgFilesIndex.name}@${pkgF
resolvedFrom: fetchedPackage.local ? 'local-dir' : 'remote',
filesIndex: fetchedPackage.filesIndex,
packageImportMethod: (fetchedPackage as DirectoryFetcherResult).packageImportMethod,
requiresBuild: fetchedPackage.requiresBuild,
},
bundledManifest: fetchedPackage.manifest == null ? fetchedPackage.manifest : normalizeBundledManifest(fetchedPackage.manifest),
})

View File

@@ -372,7 +372,7 @@ test('fetchPackageToStore()', async () => {
})
const { files, bundledManifest } = await fetchResult.fetching()
expect(bundledManifest).toBeFalsy()
expect(bundledManifest).toBeTruthy() // we always read the bundled manifest
expect(Object.keys(files.filesIndex).sort()).toStrictEqual(['package.json', 'index.js', 'license', 'readme.md'].sort())
expect(files.resolvedFrom).toBe('remote')

View File

@@ -33,7 +33,6 @@
"@pnpm/core-loggers": "workspace:*",
"@pnpm/dependency-path": "workspace:*",
"@pnpm/error": "workspace:*",
"@pnpm/exec.files-include-install-scripts": "workspace:*",
"@pnpm/lockfile-types": "workspace:*",
"@pnpm/lockfile-utils": "workspace:*",
"@pnpm/manifest-utils": "workspace:*",

View File

@@ -1,12 +1,10 @@
import path from 'path'
import { PnpmError } from '@pnpm/error'
import { filesIncludeInstallScripts } from '@pnpm/exec.files-include-install-scripts'
import {
packageManifestLogger,
} from '@pnpm/core-loggers'
import { globalWarn } from '@pnpm/logger'
import {
type Lockfile,
type ProjectSnapshot,
} from '@pnpm/lockfile-types'
import {
@@ -21,7 +19,6 @@ import {
type DependencyManifest,
type ProjectManifest,
} from '@pnpm/types'
import promiseShare from 'promise-share'
import difference from 'ramda/src/difference'
import zipWith from 'ramda/src/zipWith'
import isSubdir from 'is-subdir'
@@ -270,7 +267,7 @@ export async function resolveDependencies (
}
}
const { newLockfile, pendingRequiresBuilds } = updateLockfile({
const { newLockfile } = updateLockfile({
dependenciesGraph,
lockfile: opts.wantedLockfile,
prefix: opts.virtualStoreDir,
@@ -284,17 +281,6 @@ export async function resolveDependencies (
}
}
if (opts.forceFullResolution && opts.wantedLockfile != null) {
for (const [depPath, pkg] of Object.entries(dependenciesGraph)) {
if (
(opts.allowBuild != null && !opts.allowBuild(pkg.name)) ||
(opts.wantedLockfile.packages?.[depPath] == null) ||
pkg.requiresBuild === true
) continue
pendingRequiresBuilds.push(depPath)
}
}
// waiting till package requests are finished
const waitTillAllFetchingsFinish = async () => Promise.all(Object.values(resolvedPackagesByDepPath).map(async ({ fetching }) => {
try {
@@ -305,7 +291,6 @@ export async function resolveDependencies (
return {
dependenciesByProjectId,
dependenciesGraph,
finishLockfileUpdates: promiseShare(finishLockfileUpdates(dependenciesGraph, pendingRequiresBuilds, newLockfile)),
outdatedDependencies,
linkedDependenciesByProjectId,
newLockfile,
@@ -338,49 +323,6 @@ function verifyPatches (
})
}
async function finishLockfileUpdates (
dependenciesGraph: DependenciesGraph,
pendingRequiresBuilds: string[],
newLockfile: Lockfile
) {
return Promise.all(pendingRequiresBuilds.map(async (depPath) => {
const depNode = dependenciesGraph[depPath]
if (!depNode) return
try {
let requiresBuild!: boolean
if (depNode.optional) {
// We assume that all optional dependencies have to be built.
// Optional dependencies are not always downloaded, so there is no way to know whether they need to be built or not.
requiresBuild = true
} else {
// The npm team suggests to always read the package.json for deciding whether the package has lifecycle scripts
const { files, bundledManifest: pkgJson } = await depNode.fetching()
requiresBuild = Boolean(
pkgJson?.scripts != null && (
Boolean(pkgJson.scripts.preinstall) ||
Boolean(pkgJson.scripts.install) ||
Boolean(pkgJson.scripts.postinstall)
) ||
filesIncludeInstallScripts(files.filesIndex)
)
}
if (typeof depNode.requiresBuild === 'function') {
depNode.requiresBuild['resolve'](requiresBuild)
}
// TODO: try to cover with unit test the case when entry is no longer available in lockfile
// It is an edge that probably happens if the entry is removed during lockfile prune
if (requiresBuild && newLockfile.packages?.[depPath]) {
newLockfile.packages[depPath].requiresBuild = true
}
} catch (err: any) { // eslint-disable-line
if (typeof depNode.requiresBuild === 'function') {
depNode.requiresBuild['reject'](err)
}
}
}))
}
function addDirectDependenciesToLockfile (
newManifest: ProjectManifest,
projectSnapshot: ProjectSnapshot,

View File

@@ -56,7 +56,6 @@ import {
splitNodeId,
} from './nodeIdUtils'
import { wantedDepIsLocallyAvailable } from './wantedDepIsLocallyAvailable'
import safePromiseDefer, { type SafePromiseDefer } from 'safe-promise-defer'
const dependencyResolvedLogger = logger('_dependency_resolved')
@@ -224,7 +223,7 @@ export interface ResolvedPackage {
patchFile?: PatchFile
prepare: boolean
depPath: string
requiresBuild: boolean | SafePromiseDefer<boolean>
requiresBuild?: boolean
additionalInfo: {
deprecated?: string
bundleDependencies?: string[] | boolean
@@ -1250,7 +1249,6 @@ async function resolveDependency (
// This can be removed if we implement something like peerDependenciesMeta.transitive: true
(currentPkg.dependencyLockfile.peerDependencies == null)
) {
prepare = currentPkg.dependencyLockfile.prepare === true
hasBin = currentPkg.dependencyLockfile.hasBin === true
pkg = {
...nameVerFromPkgSnapshot(currentPkg.depPath, currentPkg.dependencyLockfile),
@@ -1448,10 +1446,6 @@ function getResolvedPackage (
): ResolvedPackage {
const peerDependencies = peerDependenciesWithoutOwn(options.pkg)
const requiresBuild = (options.allowBuild == null || options.allowBuild(options.pkg.name))
? ((options.dependencyLockfile != null) ? Boolean(options.dependencyLockfile.requiresBuild) : safePromiseDefer<boolean>())
: false
return {
additionalInfo: {
bundledDependencies: options.pkg.bundledDependencies,
@@ -1477,7 +1471,6 @@ function getResolvedPackage (
peerDependencies,
prepare: options.prepare,
prod: !options.wantedDependency.dev && !options.wantedDependency.optional,
requiresBuild,
resolution: options.pkgResponse.body.resolution,
version: options.pkg.version,
}

View File

@@ -36,6 +36,7 @@ export interface GenericDependenciesGraphNode {
isBuilt?: boolean
isPure: boolean
resolvedPeerNames: Set<string>
requiresBuild?: boolean
}
export type PartialResolvedPackage = Pick<ResolvedPackage,

View File

@@ -11,7 +11,6 @@ import * as dp from '@pnpm/dependency-path'
import getNpmTarballUrl from 'get-npm-tarball-url'
import { type KeyValuePair } from 'ramda'
import partition from 'ramda/src/partition'
import { type SafePromiseDefer } from 'safe-promise-defer'
import { depPathToRef } from './depPathToRef'
import { type ResolvedPackage } from './resolveDependencies'
import { type DependenciesGraph } from '.'
@@ -166,29 +165,6 @@ function toLockfileDependency (
if (pkg.patchFile) {
result['patched'] = true
}
const requiresBuildIsKnown = typeof pkg.requiresBuild === 'boolean'
let pending = false
if (requiresBuildIsKnown) {
if (pkg.requiresBuild) {
result['requiresBuild'] = true
}
} else if (opts.prevSnapshot != null) {
if (opts.prevSnapshot.requiresBuild) {
result['requiresBuild'] = opts.prevSnapshot.requiresBuild
}
if (opts.prevSnapshot.prepare) {
result['prepare'] = opts.prevSnapshot.prepare
}
} else if (pkg.prepare) {
result['prepare'] = true
result['requiresBuild'] = true
} else {
pendingRequiresBuilds.push(opts.depPath)
pending = true
}
if (!requiresBuildIsKnown && !pending) {
(pkg.requiresBuild as SafePromiseDefer<boolean>).resolve(result.requiresBuild ?? false)
}
return result
}

View File

@@ -12,9 +12,6 @@
{
"path": "../../config/pick-registry-for-package"
},
{
"path": "../../exec/files-include-install-scripts"
},
{
"path": "../../fetching/pick-fetcher"
},

29
pnpm-lock.yaml generated
View File

@@ -1149,12 +1149,6 @@ importers:
specifier: 0.28.20
version: 0.28.20
exec/files-include-install-scripts:
devDependencies:
'@pnpm/exec.files-include-install-scripts':
specifier: workspace:*
version: 'link:'
exec/lifecycle:
dependencies:
'@pnpm/core-loggers':
@@ -1210,6 +1204,16 @@ importers:
specifier: ^6.2.0
version: 6.2.0
exec/pkg-requires-build:
dependencies:
'@pnpm/types':
specifier: workspace:*
version: link:../../packages/types
devDependencies:
'@pnpm/exec.pkg-requires-build':
specifier: workspace:*
version: 'link:'
exec/plugin-commands-rebuild:
dependencies:
'@pnpm/builder.policy':
@@ -1537,6 +1541,9 @@ importers:
fetching/directory-fetcher:
dependencies:
'@pnpm/exec.pkg-requires-build':
specifier: workspace:*
version: link:../../exec/pkg-requires-build
'@pnpm/fetcher-base':
specifier: workspace:*
version: link:../fetcher-base
@@ -4161,9 +4168,6 @@ importers:
'@pnpm/error':
specifier: workspace:*
version: link:../../packages/error
'@pnpm/exec.files-include-install-scripts':
specifier: workspace:*
version: link:../../exec/files-include-install-scripts
'@pnpm/lockfile-types':
specifier: workspace:*
version: link:../../lockfile/lockfile-types
@@ -5737,9 +5741,9 @@ importers:
store/create-cafs-store:
dependencies:
'@pnpm/exec.files-include-install-scripts':
'@pnpm/exec.pkg-requires-build':
specifier: workspace:*
version: link:../../exec/files-include-install-scripts
version: link:../../exec/pkg-requires-build
'@pnpm/fetcher-base':
specifier: workspace:*
version: link:../../fetching/fetcher-base
@@ -6260,6 +6264,9 @@ importers:
'@pnpm/error':
specifier: workspace:*
version: link:../packages/error
'@pnpm/exec.pkg-requires-build':
specifier: workspace:*
version: link:../exec/pkg-requires-build
'@pnpm/fs.hard-link-dir':
specifier: workspace:*
version: link:../fs/hard-link-dir

View File

@@ -14,6 +14,7 @@ export type PackageFilesResponse = {
resolvedFrom: ResolvedFrom
packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy'
sideEffects?: Record<string, Record<string, PackageFileInfo>>
requiresBuild: boolean
} & ({
unprocessed?: false
filesIndex: Record<string, string>

View File

@@ -19,6 +19,8 @@ export interface VerifyResult {
manifest?: DependencyManifest
}
export type SideEffects = Record<string, Record<string, PackageFileInfo>>
export interface PackageFilesIndex {
// name and version are nullable for backward compatibility
// the initial specs of pnpm store v3 did not require these fields.
@@ -26,9 +28,10 @@ export interface PackageFilesIndex {
// have the name/version fields, like the local tarball dependencies.
name?: string
version?: string
requiresBuild?: boolean
files: Record<string, PackageFileInfo>
sideEffects?: Record<string, Record<string, PackageFileInfo>>
sideEffects?: SideEffects
}
export function checkPkgFilesIntegrity (

View File

@@ -5,6 +5,7 @@ import { addFilesFromTarball } from './addFilesFromTarball'
import {
checkPkgFilesIntegrity,
type PackageFilesIndex,
type SideEffects,
type VerifyResult,
} from './checkPkgFilesIntegrity'
import { readManifestFromStore } from './readManifestFromStore'
@@ -27,6 +28,7 @@ export {
getFilePathInCafs,
type PackageFileInfo,
type PackageFilesIndex,
type SideEffects,
optimisticRenameOverwrite,
type FilesIndex,
type VerifyResult,

View File

@@ -15,7 +15,7 @@
"@pnpm/logger": "^5.0.0"
},
"dependencies": {
"@pnpm/exec.files-include-install-scripts": "workspace:*",
"@pnpm/exec.pkg-requires-build": "workspace:*",
"@pnpm/fetcher-base": "workspace:*",
"@pnpm/fs.indexed-pkg-importer": "workspace:*",
"@pnpm/store-controller-types": "workspace:*",

View File

@@ -1,6 +1,5 @@
import { promises as fs, readFileSync } from 'fs'
import { promises as fs } from 'fs'
import path from 'path'
import { filesIncludeInstallScripts } from '@pnpm/exec.files-include-install-scripts'
import {
type CafsLocker,
createCafs,
@@ -35,7 +34,7 @@ export function createPackageImporterAsync (
const gfm = getFlatMap.bind(null, opts.cafsDir)
return async (to, opts) => {
const { filesMap, isBuilt } = gfm(opts.filesResponse, opts.sideEffectsCacheKey)
const willBeBuilt = !isBuilt && (opts.requiresBuild ?? pkgRequiresBuild(filesMap))
const willBeBuilt = !isBuilt && opts.requiresBuild
const pkgImportMethod = willBeBuilt
? 'clone-or-copy'
: (opts.filesResponse.packageImportMethod ?? packageImportMethod)
@@ -65,7 +64,7 @@ function createPackageImporter (
const gfm = getFlatMap.bind(null, opts.cafsDir)
return (to, opts) => {
const { filesMap, isBuilt } = gfm(opts.filesResponse, opts.sideEffectsCacheKey)
const willBeBuilt = !isBuilt && (opts.requiresBuild ?? pkgRequiresBuild(filesMap))
const willBeBuilt = !isBuilt && opts.requiresBuild
const pkgImportMethod = willBeBuilt
? 'clone-or-copy'
: (opts.filesResponse.packageImportMethod ?? packageImportMethod)
@@ -131,16 +130,3 @@ export function createCafsStore (
},
}
}
function pkgRequiresBuild (filesMap: Record<string, string>) {
return filesIncludeInstallScripts(filesMap) ||
filesMap['package.json'] && pkgJsonHasInstallScripts(filesMap['package.json'])
}
function pkgJsonHasInstallScripts (file: string): boolean {
const pkgJson = JSON.parse(readFileSync(file, 'utf8'))
if (!pkgJson.scripts) return false
return Boolean(pkgJson.scripts.preinstall) ||
Boolean(pkgJson.scripts.install) ||
Boolean(pkgJson.scripts.postinstall)
}

View File

@@ -13,7 +13,7 @@
"path": "../../__utils__/prepare"
},
{
"path": "../../exec/files-include-install-scripts"
"path": "../../exec/pkg-requires-build"
},
{
"path": "../../fetching/fetcher-base"

View File

@@ -35,6 +35,7 @@
"@pnpm/cafs-types": "workspace:*",
"@pnpm/create-cafs-store": "workspace:*",
"@pnpm/error": "workspace:*",
"@pnpm/exec.pkg-requires-build": "workspace:*",
"@pnpm/fs.hard-link-dir": "workspace:*",
"@pnpm/graceful-fs": "workspace:*",
"@pnpm/store.cafs": "workspace:*",

View File

@@ -55,7 +55,7 @@ export async function addFilesFromDir (
workerPool = createTarballWorkerPool()
}
const localWorker = await workerPool.checkoutWorkerAsync(true)
return new Promise<{ filesIndex: Record<string, string>, manifest: DependencyManifest }>((resolve, reject) => {
return new Promise<{ filesIndex: Record<string, string>, manifest: DependencyManifest, requiresBuild: boolean }>((resolve, reject) => {
localWorker.once('message', ({ status, error, value }) => {
workerPool!.checkinWorker(localWorker)
if (status === 'error') {
@@ -120,7 +120,7 @@ export async function addFilesFromTarball (
workerPool = createTarballWorkerPool()
}
const localWorker = await workerPool.checkoutWorkerAsync(true)
return new Promise<{ filesIndex: Record<string, string>, manifest: DependencyManifest }>((resolve, reject) => {
return new Promise<{ filesIndex: Record<string, string>, manifest: DependencyManifest, requiresBuild: boolean }>((resolve, reject) => {
localWorker.once('message', ({ status, error, value }) => {
workerPool!.checkinWorker(localWorker)
if (status === 'error') {
@@ -153,12 +153,12 @@ export async function readPkgFromCafs (
verifyStoreIntegrity: boolean,
filesIndexFile: string,
readManifest?: boolean
): Promise<{ verified: boolean, pkgFilesIndex: PackageFilesIndex, manifest?: DependencyManifest }> {
): Promise<{ verified: boolean, pkgFilesIndex: PackageFilesIndex, manifest?: DependencyManifest, requiresBuild: boolean }> {
if (!workerPool) {
workerPool = createTarballWorkerPool()
}
const localWorker = await workerPool.checkoutWorkerAsync(true)
return new Promise<{ verified: boolean, pkgFilesIndex: PackageFilesIndex }>((resolve, reject) => {
return new Promise<{ verified: boolean, pkgFilesIndex: PackageFilesIndex, requiresBuild: boolean }>((resolve, reject) => {
localWorker.once('message', ({ status, error, value }) => {
workerPool!.checkinWorker(localWorker)
if (status === 'error') {

View File

@@ -3,18 +3,21 @@ import fs from 'fs'
import gfs from '@pnpm/graceful-fs'
import * as crypto from 'crypto'
import { createCafsStore } from '@pnpm/create-cafs-store'
import { pkgRequiresBuild } from '@pnpm/exec.pkg-requires-build'
import { hardLinkDir } from '@pnpm/fs.hard-link-dir'
import {
checkPkgFilesIntegrity,
createCafs,
type PackageFileInfo,
type PackageFilesIndex,
type SideEffects,
type FilesIndex,
optimisticRenameOverwrite,
readManifestFromStore,
type VerifyResult,
} from '@pnpm/store.cafs'
import { symlinkDependencySync } from '@pnpm/symlink-dependency'
import { type DependencyManifest } from '@pnpm/types'
import { sync as loadJsonFile } from 'load-json-file'
import { parentPort } from 'worker_threads'
import {
@@ -56,7 +59,7 @@ async function handleMessage (
break
}
case 'readPkgFromCafs': {
const { cafsDir, filesIndexFile, readManifest, verifyStoreIntegrity } = message
let { cafsDir, filesIndexFile, readManifest, verifyStoreIntegrity } = message
let pkgFilesIndex: PackageFilesIndex | undefined
try {
pkgFilesIndex = loadJsonFile<PackageFilesIndex>(filesIndexFile)
@@ -74,6 +77,9 @@ async function handleMessage (
return
}
let verifyResult: VerifyResult | undefined
if (pkgFilesIndex.requiresBuild == null) {
readManifest = true
}
if (verifyStoreIntegrity) {
verifyResult = checkPkgFilesIntegrity(cafsDir, pkgFilesIndex, readManifest)
} else {
@@ -82,12 +88,14 @@ async function handleMessage (
manifest: readManifest ? readManifestFromStore(cafsDir, pkgFilesIndex) : undefined,
}
}
const requiresBuild = pkgFilesIndex.requiresBuild ?? pkgRequiresBuild(verifyResult.manifest, pkgFilesIndex.files)
parentPort!.postMessage({
status: 'success',
value: {
verified: verifyResult.passed,
manifest: verifyResult.manifest,
pkgFilesIndex,
requiresBuild,
},
})
break
@@ -136,10 +144,10 @@ function addTarballToStore ({ buffer, cafsDir, integrity, filesIndexFile, pkg, r
cafsCache.set(cafsDir, createCafs(cafsDir))
}
const cafs = cafsCache.get(cafsDir)!
const { filesIndex, manifest } = cafs.addFilesFromTarball(buffer, Boolean(readManifest) || !pkg?.name || !pkg.version)
const { filesIndex, manifest } = cafs.addFilesFromTarball(buffer, true)
const { filesIntegrity, filesMap } = processFilesIndex(filesIndex)
writeFilesIndexFile(filesIndexFile, { pkg: pkg ?? manifest ?? {}, files: filesIntegrity })
return { status: 'success', value: { filesIndex: filesMap, manifest } }
const requiresBuild = writeFilesIndexFile(filesIndexFile, { manifest: manifest ?? {}, files: filesIntegrity })
return { status: 'success', value: { filesIndex: filesMap, manifest, requiresBuild } }
}
function addFilesFromDir ({ dir, cafsDir, filesIndexFile, sideEffectsCacheKey, pkg, readManifest, files }: AddDirToStoreMessage) {
@@ -149,24 +157,29 @@ function addFilesFromDir ({ dir, cafsDir, filesIndexFile, sideEffectsCacheKey, p
const cafs = cafsCache.get(cafsDir)!
const { filesIndex, manifest } = cafs.addFilesFromDir(dir, {
files,
readManifest: Boolean(readManifest) || !pkg?.name || !pkg.version,
readManifest: true,
})
const { filesIntegrity, filesMap } = processFilesIndex(filesIndex)
let requiresBuild: boolean
if (sideEffectsCacheKey) {
let filesIndex!: PackageFilesIndex
try {
filesIndex = loadJsonFile<PackageFilesIndex>(filesIndexFile)
} catch {
pkg = pkg ?? manifest
filesIndex = { name: pkg?.name, version: pkg?.version, files: filesIntegrity }
filesIndex = { name: manifest?.name, version: manifest?.version, files: filesIntegrity }
}
filesIndex.sideEffects = filesIndex.sideEffects ?? {}
filesIndex.sideEffects[sideEffectsCacheKey] = filesIntegrity
if (filesIndex.requiresBuild == null) {
requiresBuild = pkgRequiresBuild(manifest, filesIntegrity)
} else {
requiresBuild = filesIndex.requiresBuild
}
writeJsonFile(filesIndexFile, filesIndex)
} else {
writeFilesIndexFile(filesIndexFile, { pkg: pkg ?? manifest ?? {}, files: filesIntegrity })
requiresBuild = writeFilesIndexFile(filesIndexFile, { manifest: manifest ?? {}, files: filesIntegrity })
}
return { status: 'success', value: { filesIndex: filesMap, manifest } }
return { status: 'success', value: { filesIndex: filesMap, manifest, requiresBuild } }
}
function processFilesIndex (filesIndex: FilesIndex) {
@@ -224,16 +237,22 @@ function symlinkAllModules (opts: SymlinkAllModulesMessage) {
function writeFilesIndexFile (
filesIndexFile: string,
{ pkg, files }: {
pkg: { name?: string, version?: string }
{ manifest, files, sideEffects }: {
manifest: Partial<DependencyManifest>
files: Record<string, PackageFileInfo>
sideEffects?: SideEffects
}
) {
writeJsonFile(filesIndexFile, {
name: pkg.name,
version: pkg.version,
): boolean {
const requiresBuild = pkgRequiresBuild(manifest, files)
const filesIndex: PackageFilesIndex = {
name: manifest.name,
version: manifest.version,
requiresBuild,
files,
})
sideEffects,
}
writeJsonFile(filesIndexFile, filesIndex)
return requiresBuild
}
function writeJsonFile (filePath: string, data: unknown) {

View File

@@ -9,6 +9,9 @@
"../../__typings__/**/*.d.ts"
],
"references": [
{
"path": "../exec/pkg-requires-build"
},
{
"path": "../fs/graceful-fs"
},