From 2511c82cd2fa4e02dcf0052f8488257e1c07dd03 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 9 Nov 2021 11:59:56 +0200 Subject: [PATCH] feat: `pnpm:devPreinstall` script (#3968) ref #3836 --- .changeset/pink-chefs-attack.md | 6 ++++ packages/core/src/install/index.ts | 36 +++++++++++++------ .../core/test/install/lifecycleScripts.ts | 2 ++ 3 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 .changeset/pink-chefs-attack.md diff --git a/.changeset/pink-chefs-attack.md b/.changeset/pink-chefs-attack.md new file mode 100644 index 0000000000..ecf9ceb92b --- /dev/null +++ b/.changeset/pink-chefs-attack.md @@ -0,0 +1,6 @@ +--- +"@pnpm/core": minor +"pnpm": minor +--- + +Added support for a new lifecycle script: `pnpm:devPreinstall`. This script works only in the root `package.json` file, only during local development, and runs before installation happens. diff --git a/packages/core/src/install/index.ts b/packages/core/src/install/index.ts index 874116dff6..4466344915 100644 --- a/packages/core/src/install/index.ts +++ b/packages/core/src/install/index.ts @@ -13,7 +13,7 @@ import { import PnpmError from '@pnpm/error' import getContext, { PnpmContext, ProjectOptions } from '@pnpm/get-context' import headless from '@pnpm/headless' -import { +import runLifecycleHook, { makeNodeRequireOption, runLifecycleHooksConcurrently, RunLifecycleHooksConcurrentlyOptions, @@ -82,6 +82,8 @@ const BROKEN_LOCKFILE_INTEGRITY_ERRORS = new Set([ 'ERR_PNPM_TARBALL_INTEGRITY', ]) +const DEV_PREINSTALL = 'pnpm:devPreinstall' + export type DependenciesMutation = ( { buildIndex: number @@ -200,6 +202,28 @@ export async function mutateModules ( return result async function _install (): Promise> { + const scriptsOpts: RunLifecycleHooksConcurrentlyOptions = { + extraBinPaths: opts.extraBinPaths, + rawConfig: opts.rawConfig, + scriptShell: opts.scriptShell, + shellEmulator: opts.shellEmulator, + stdio: opts.ownLifecycleHooksStdio, + storeController: opts.storeController, + unsafePerm: opts.unsafePerm || false, + } + + if (!opts.ignoreScripts && !opts.ignorePackageManifest && rootProjectManifest?.scripts?.[DEV_PREINSTALL]) { + await runLifecycleHook( + DEV_PREINSTALL, + rootProjectManifest, + { + ...scriptsOpts, + depPath: opts.lockfileDir, + pkgRoot: opts.lockfileDir, + rootModulesDir: ctx.rootModulesDir, + } + ) + } const packageExtensionsChecksum = isEmpty(packageExtensions ?? {}) ? undefined : createObjectChecksum(packageExtensions!) let needsFullResolution = !maybeOpts.ignorePackageManifest && ( !equals(ctx.wantedLockfile.overrides ?? {}, overrides ?? {}) || @@ -321,16 +345,6 @@ export async function mutateModules ( const projectsToInstall = [] as ImporterToUpdate[] const projectsToBeInstalled = ctx.projects.filter(({ mutation }) => mutation === 'install') as ProjectToBeInstalled[] - const scriptsOpts: RunLifecycleHooksConcurrentlyOptions = { - extraBinPaths: opts.extraBinPaths, - rawConfig: opts.rawConfig, - scriptShell: opts.scriptShell, - shellEmulator: opts.shellEmulator, - stdio: opts.ownLifecycleHooksStdio, - storeController: opts.storeController, - unsafePerm: opts.unsafePerm || false, - } - let preferredSpecs: Record | null = null // TODO: make it concurrent diff --git a/packages/core/test/install/lifecycleScripts.ts b/packages/core/test/install/lifecycleScripts.ts index 8fda26e3d3..b0977e1858 100644 --- a/packages/core/test/install/lifecycleScripts.ts +++ b/packages/core/test/install/lifecycleScripts.ts @@ -97,6 +97,7 @@ test('run install scripts in the current project', async () => { prepareEmpty() const manifest = await addDependenciesToPackage({ scripts: { + 'pnpm:devPreinstall': 'node -e "require(\'fs\').writeFileSync(\'test.txt\', \'\', \'utf-8\')"', install: 'node -e "process.stdout.write(\'install\')" | json-append output.json', postinstall: 'node -e "process.stdout.write(\'postinstall\')" | json-append output.json', preinstall: 'node -e "process.stdout.write(\'preinstall\')" | json-append output.json', @@ -107,6 +108,7 @@ test('run install scripts in the current project', async () => { const output = await loadJsonFile('output.json') expect(output).toStrictEqual(['preinstall', 'install', 'postinstall']) + expect(await exists('test.txt')).toBeTruthy() }) test('run install scripts in the current project when its name is different than its directory', async () => {