diff --git a/.changeset/real-badgers-mate.md b/.changeset/real-badgers-mate.md new file mode 100644 index 0000000000..4b2c25b860 --- /dev/null +++ b/.changeset/real-badgers-mate.md @@ -0,0 +1,6 @@ +--- +"@pnpm/headless": patch +"pnpm": patch +--- + +The postinstall scripts of dependencies should be executed after the root dependencies of the project are symlinked [#4018](https://github.com/pnpm/pnpm/issues/4018). diff --git a/package.json b/package.json index 61e05203db..a39f1784fe 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@commitlint/prompt-cli": "^15.0.0", "@pnpm/eslint-config": "workspace:*", "@pnpm/meta-updater": "0.0.6", - "@pnpm/registry-mock": "^2.9.0", + "@pnpm/registry-mock": "^2.10.0", "@pnpm/tsconfig": "workspace:*", "@types/jest": "^26.0.24", "@types/node": "^14.17.32", diff --git a/packages/core/test/install/lifecycleScripts.ts b/packages/core/test/install/lifecycleScripts.ts index 56fb3fc031..91556f7b04 100644 --- a/packages/core/test/install/lifecycleScripts.ts +++ b/packages/core/test/install/lifecycleScripts.ts @@ -511,3 +511,42 @@ test('lifecycle scripts have access to package\'s own binary by binary name', as await project.isExecutable('.pnpm/runs-own-bin@1.0.0/node_modules/runs-own-bin/node_modules/.bin/runs-own-bin') }) + +test('lifecycle scripts run after linking root dependencies', async () => { + prepareEmpty() + + const manifest = { + dependencies: { + 'is-positive': '1.0.0', + 'postinstall-requires-is-positive': '1.0.0', + }, + } + + await mutateModules( + [ + { + buildIndex: 0, + manifest, + mutation: 'install', + rootDir: process.cwd(), + }, + ], + await testDefaults({ fastUnpack: false }) + ) + + await rimraf('node_modules') + + await mutateModules( + [ + { + buildIndex: 0, + manifest, + mutation: 'install', + rootDir: process.cwd(), + }, + ], + await testDefaults({ fastUnpack: false, frozenLockfile: true }) + ) + + // if there was no exception, the test passed +}) diff --git a/packages/headless/src/index.ts b/packages/headless/src/index.ts index 35d45c038e..bb2bc68699 100644 --- a/packages/headless/src/index.ts +++ b/packages/headless/src/index.ts @@ -259,6 +259,7 @@ export default async (opts: HeadlessOptions) => { }) } + let newHoistedDependencies!: HoistedDependencies if (opts.enableModulesDir !== false) { await Promise.all(depNodes.map(async (depNode) => fs.mkdir(depNode.modules, { recursive: true }))) await Promise.all([ @@ -280,7 +281,6 @@ export default async (opts: HeadlessOptions) => { stage: 'importing_done', }) - let newHoistedDependencies!: HoistedDependencies if (opts.ignorePackageManifest !== true && (opts.hoistPattern != null || opts.publicHoistPattern != null)) { // It is important to keep the skipped packages in the lockfile which will be saved as the "current lockfile". // pnpm is comparing the current lockfile to the wanted one and they should much. @@ -304,84 +304,11 @@ export default async (opts: HeadlessOptions) => { newHoistedDependencies = {} } - if (opts.ignoreScripts) { - for (const { id, manifest } of opts.projects) { - if (opts.ignoreScripts && ((manifest?.scripts) != null) && - (manifest.scripts.preinstall ?? manifest.scripts.prepublish ?? - manifest.scripts.install ?? - manifest.scripts.postinstall ?? - manifest.scripts.prepare) - ) { - opts.pendingBuilds.push(id) - } - } - // we can use concat here because we always only append new packages, which are guaranteed to not be there by definition - opts.pendingBuilds = opts.pendingBuilds - .concat( - depNodes - .filter(({ requiresBuild }) => requiresBuild) - .map(({ depPath }) => depPath) - ) - } else { - const directNodes = new Set() - for (const id of importerIds) { - Object - .values(directDependenciesByImporterId[id]) - .filter((loc) => graph[loc]) - .forEach((loc) => { - directNodes.add(loc) - }) - } - const extraBinPaths = [...opts.extraBinPaths ?? []] - if (opts.hoistPattern != null) { - extraBinPaths.unshift(path.join(virtualStoreDir, 'node_modules/.bin')) - } - let extraEnv: Record | undefined - if (opts.enablePnp) { - extraEnv = makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs')) - } - await buildModules(graph, Array.from(directNodes), { - childConcurrency: opts.childConcurrency, - extraBinPaths, - extendNodePath: opts.extendNodePath, - extraEnv, - lockfileDir, - optional: opts.include.optionalDependencies, - rawConfig: opts.rawConfig, - rootModulesDir: virtualStoreDir, - scriptsPrependNodePath: opts.scriptsPrependNodePath, - scriptShell: opts.scriptShell, - shellEmulator: opts.shellEmulator, - sideEffectsCacheWrite: opts.sideEffectsCacheWrite, - storeController: opts.storeController, - unsafePerm: opts.unsafePerm, - userAgent: opts.userAgent, - }) - } - - await writeModulesYaml(rootModulesDir, { - hoistedDependencies: newHoistedDependencies, - hoistPattern: opts.hoistPattern, - included: opts.include, - layoutVersion: LAYOUT_VERSION, - packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`, - pendingBuilds: opts.pendingBuilds, - publicHoistPattern: opts.publicHoistPattern, - prunedAt: opts.pruneVirtualStore === true || opts.prunedAt == null - ? new Date().toUTCString() - : opts.prunedAt, - registries: opts.registries, - skipped: Array.from(skipped), - storeDir: opts.storeDir, - virtualStoreDir, - }) - await linkAllBins(graph, { extendNodePath: opts.extendNodePath, optional: opts.include.optionalDependencies, warn }) if ((currentLockfile != null) && !equals(importerIds.sort(), Object.keys(filteredLockfile.importers).sort())) { Object.assign(filteredLockfile.packages, currentLockfile.packages) } - await writeCurrentLockfile(virtualStoreDir, filteredLockfile) /** Skip linking and due to no project manifest */ if (!opts.ignorePackageManifest) { @@ -405,7 +332,67 @@ export default async (opts: HeadlessOptions) => { updated: manifest, }) })) + } + } + if (opts.ignoreScripts) { + for (const { id, manifest } of opts.projects) { + if (opts.ignoreScripts && ((manifest?.scripts) != null) && + (manifest.scripts.preinstall ?? manifest.scripts.prepublish ?? + manifest.scripts.install ?? + manifest.scripts.postinstall ?? + manifest.scripts.prepare) + ) { + opts.pendingBuilds.push(id) + } + } + // we can use concat here because we always only append new packages, which are guaranteed to not be there by definition + opts.pendingBuilds = opts.pendingBuilds + .concat( + depNodes + .filter(({ requiresBuild }) => requiresBuild) + .map(({ depPath }) => depPath) + ) + } else { + const directNodes = new Set() + for (const id of importerIds) { + Object + .values(directDependenciesByImporterId[id]) + .filter((loc) => graph[loc]) + .forEach((loc) => { + directNodes.add(loc) + }) + } + const extraBinPaths = [...opts.extraBinPaths ?? []] + if (opts.hoistPattern != null) { + extraBinPaths.unshift(path.join(virtualStoreDir, 'node_modules/.bin')) + } + let extraEnv: Record | undefined + if (opts.enablePnp) { + extraEnv = makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs')) + } + await buildModules(graph, Array.from(directNodes), { + childConcurrency: opts.childConcurrency, + extraBinPaths, + extendNodePath: opts.extendNodePath, + extraEnv, + lockfileDir, + optional: opts.include.optionalDependencies, + rawConfig: opts.rawConfig, + rootModulesDir: virtualStoreDir, + scriptsPrependNodePath: opts.scriptsPrependNodePath, + scriptShell: opts.scriptShell, + shellEmulator: opts.shellEmulator, + sideEffectsCacheWrite: opts.sideEffectsCacheWrite, + storeController: opts.storeController, + unsafePerm: opts.unsafePerm, + userAgent: opts.userAgent, + }) + } + + if (opts.enableModulesDir !== false) { + /** Skip linking and due to no project manifest */ + if (!opts.ignorePackageManifest) { await Promise.all(opts.projects.map(async (project) => { if (opts.publicHoistPattern?.length && path.relative(opts.lockfileDir, project.rootDir) === '') { await linkBinsOfImporter(project, { extendNodePath: opts.extendNodePath }) @@ -427,7 +414,25 @@ export default async (opts: HeadlessOptions) => { } })) } + await writeModulesYaml(rootModulesDir, { + hoistedDependencies: newHoistedDependencies, + hoistPattern: opts.hoistPattern, + included: opts.include, + layoutVersion: LAYOUT_VERSION, + packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`, + pendingBuilds: opts.pendingBuilds, + publicHoistPattern: opts.publicHoistPattern, + prunedAt: opts.pruneVirtualStore === true || opts.prunedAt == null + ? new Date().toUTCString() + : opts.prunedAt, + registries: opts.registries, + skipped: Array.from(skipped), + storeDir: opts.storeDir, + virtualStoreDir, + }) + await writeCurrentLockfile(virtualStoreDir, filteredLockfile) } + // waiting till package requests are finished await Promise.all(depNodes.map(({ finishing }) => finishing)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c3be0a413..1ec664afa4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: '@commitlint/prompt-cli': ^15.0.0 '@pnpm/eslint-config': workspace:* '@pnpm/meta-updater': 0.0.6 - '@pnpm/registry-mock': ^2.9.0 + '@pnpm/registry-mock': ^2.10.0 '@pnpm/tsconfig': workspace:* '@types/jest': ^26.0.24 '@types/node': ^14.17.32 @@ -71,7 +71,7 @@ importers: '@commitlint/prompt-cli': 15.0.0 '@pnpm/eslint-config': link:utils/eslint-config '@pnpm/meta-updater': 0.0.6 - '@pnpm/registry-mock': 2.9.0 + '@pnpm/registry-mock': 2.10.0 '@pnpm/tsconfig': link:utils/tsconfig '@types/jest': 26.0.24 '@types/node': 14.17.34 @@ -4645,15 +4645,15 @@ packages: load-json-file: 6.2.0 dev: true - /@pnpm/cli-utils/0.6.31_@pnpm+logger@4.0.0: - resolution: {integrity: sha512-SrCdFnD5dbyyAzqm5hnOY2S/6/+zxs7nkuVQWYdhNWiAmwxlVy5ip9ZYlwMzhUHVnlTXtOwelrJm7GlaVFu84g==} + /@pnpm/cli-utils/0.6.32_@pnpm+logger@4.0.0: + resolution: {integrity: sha512-zHX4ozJeuEWWH/i9/nekXxpKduXJ9buRNEppR9X3A4Ir/a4je4Feyjf9gPXwa/ZB5dbOerweQYlK0SB5nQuUKg==} engines: {node: '>=12.17'} peerDependencies: '@pnpm/logger': ^4.0.0 dependencies: '@pnpm/cli-meta': 2.0.0 - '@pnpm/config': 13.4.2_@pnpm+logger@4.0.0 - '@pnpm/default-reporter': 8.3.6_@pnpm+logger@4.0.0 + '@pnpm/config': 13.5.0_@pnpm+logger@4.0.0 + '@pnpm/default-reporter': 8.3.7_@pnpm+logger@4.0.0 '@pnpm/error': 2.0.0 '@pnpm/logger': 4.0.0 '@pnpm/manifest-utils': 2.1.2_@pnpm+logger@4.0.0 @@ -4670,8 +4670,8 @@ packages: chalk: 4.1.2 dev: false - /@pnpm/config/13.4.2_@pnpm+logger@4.0.0: - resolution: {integrity: sha512-SO1EwqIJFE3DdeihtNAPlqodxR0hRZ/XY++qc8wJKdnbHlX4nuLT7eaQ2t60l66EVSA05yaYzMjUwahPdYFVUA==} + /@pnpm/config/13.5.0_@pnpm+logger@4.0.0: + resolution: {integrity: sha512-CGcTb8hQtAAhCQ22MFiJ5lx/B67ZYnE9Xo+n1jDCJBy2z9YRqzXv7YpKd8ez2HsHkqEB1mehheD1pN5vxuaPJg==} engines: {node: '>=12.17'} dependencies: '@pnpm/constants': 5.0.0 @@ -4705,11 +4705,11 @@ packages: '@pnpm/types': 7.6.0 dev: true - /@pnpm/default-reporter/8.3.6_@pnpm+logger@4.0.0: - resolution: {integrity: sha512-dq0vb3Brv+MxvcH8TSBLVaasgS3XvJPL+ORD/nPvinDnmiSiT+qGn/OIwO0Ib6lpIzvWXv93+OPPuQ9Jnpb/vA==} + /@pnpm/default-reporter/8.3.7_@pnpm+logger@4.0.0: + resolution: {integrity: sha512-J1B+Q77AvAW81e3x6nNBZEbVudeEbaUrILtf9UJQLVJy+wU2nUHaF0uWVK++B/0utx262DRt0DieDeQMJaZ60Q==} engines: {node: '>=12.17'} dependencies: - '@pnpm/config': 13.4.2_@pnpm+logger@4.0.0 + '@pnpm/config': 13.5.0_@pnpm+logger@4.0.0 '@pnpm/core-loggers': 6.0.6_@pnpm+logger@4.0.0 '@pnpm/error': 2.0.0 '@pnpm/types': 7.6.0 @@ -4752,11 +4752,11 @@ packages: find-up: 5.0.0 dev: true - /@pnpm/find-workspace-packages/3.1.23_@pnpm+logger@4.0.0: - resolution: {integrity: sha512-22z6tgybQ9I/UA9FNPJ1S3wtYa36RxF2xpjfqZNv3l7Q9P651duhevP7uUWyUYeQ0xT8259D+PpdOTHQhSpcpQ==} + /@pnpm/find-workspace-packages/3.1.24_@pnpm+logger@4.0.0: + resolution: {integrity: sha512-Ik8FsWp3sXGQHGQPGqM9MAr3WEoaIZXIec/k58/F9RHqmaTfKmP60B444grpS8xGVeNXyYkKqKCvsf7Wr/tHag==} engines: {node: '>=12.17'} dependencies: - '@pnpm/cli-utils': 0.6.31_@pnpm+logger@4.0.0 + '@pnpm/cli-utils': 0.6.32_@pnpm+logger@4.0.0 '@pnpm/constants': 5.0.0 '@pnpm/types': 7.6.0 find-packages: 8.0.7 @@ -4812,7 +4812,7 @@ packages: hasBin: true dependencies: '@pnpm/find-workspace-dir': 3.0.1 - '@pnpm/find-workspace-packages': 3.1.23_@pnpm+logger@4.0.0 + '@pnpm/find-workspace-packages': 3.1.24_@pnpm+logger@4.0.0 '@pnpm/logger': 4.0.0 '@pnpm/types': 7.6.0 load-json-file: 6.2.0 @@ -4893,8 +4893,8 @@ packages: strip-bom: 4.0.0 dev: true - /@pnpm/registry-mock/2.9.0: - resolution: {integrity: sha512-o3jkMVvyZ/kGZXVQ4f+8ENH2p2A/AIH4kAz0d4ULxtCeXS5ujRIqCfwaBYIU2NfJrZP8lSMYsLBat1egXJV+GA==} + /@pnpm/registry-mock/2.10.0: + resolution: {integrity: sha512-96frA3W36rjd6BRIjKmkXKTLIlpZFogvkbKnYp68+K2RWBoqDQd789ZZbJvz6Tz3fNWYcKpz6lUI4Bm3S+jtwA==} engines: {node: '>=10.13'} hasBin: true dependencies: