feat(exec): check dependencies before running scripts#8645
Conversation
|
Filtered install writes the full lockfile at the root of the project but a filtered lockfile at |
|
Well, 9cf363b should skip check on filtered install. |
|
actually, it looks like even when filtered install is used, the importers field in the inner lockfile will contain all projects and all dependencies. So, if you check that the importers of the two lockfiles deeply equal, you can say that node_modules is up to date. |
| if (opts.allProjects && opts.workspaceDir) { | ||
| await updatePackagesList({ | ||
| allProjects: opts.allProjects, | ||
| catalogs: opts.catalogs, | ||
| filtered: (opts.filter?.length ?? 0) > 0, | ||
| lastValidatedTimestamp: Date.now(), | ||
| workspaceDir: opts.workspaceDir, | ||
| }) | ||
| } |
There was a problem hiding this comment.
how can you set lastValidatedTimestamp at this point if installation wasn't yet performed? This file should be written at the end of the function. Maybe even moved to @pnpm/core.
There was a problem hiding this comment.
IIRC, we don't need lastValidatedTimestamp to to newer than any of the lockfiles, we only need it to be newer than the manifest files.
There was a problem hiding this comment.
I think both. But in any case, at this point installation didn't finish and you cannot know in advance if it will succeed, right? They are validated only if installation succeeded.
There was a problem hiding this comment.
I had hoped that using .modules.yaml to store packages list would have solved this problem, but now that it proves to not make sense, I wonder where should this code be moved to. Is there a place in the code where all successful install will execute?
There was a problem hiding this comment.
Unfortunately, the fact that we have non-shared lockfile workspaces complicates things. We can't do it inside @pnpm/core. We should save the file somewhere in @pnpm/plugin-commands-installation after mutateModules finishes.
There was a problem hiding this comment.
I initially wanted to add it to recursive directly, but it has too many return statement, so I think wrapper is nicer.
There was a problem hiding this comment.
There is already a state file at node_modules/.modules.yaml. Should there be a new file?
There was a problem hiding this comment.
I have experimented with the idea of storing packages list in .modules.yaml. It work fine when the lockfile is shared, but when the workspace doesn't use a shared lockfile, it fails as there is no shared .modules.yaml either.
I have push the code of the experiment to a branched named tmp.save-packages-list-in-modules-file, its last commit is fa237d0.
There was a problem hiding this comment.
I see. That is unfortunate. Let's give the file some more meaningful name maybe. Like .pnpm-workspace-state.json.
There was a problem hiding this comment.
Let's give the file some more meaningful name maybe. Like
.pnpm-workspace-state.json.
0764be4 has reverted the feature that ignores filtered install. |
| @@ -0,0 +1,49 @@ | |||
| { | |||
| "name": "@pnpm/workspace.packages-list-cache", | |||
There was a problem hiding this comment.
maybe call it
| "name": "@pnpm/workspace.packages-list-cache", | |
| "name": "@pnpm/workspace.state", |
There was a problem hiding this comment.
Pull request overview
This PR implements a feature to check that dependencies are up-to-date before running scripts, addressing issue #8585. When enabled via the --config.verify-deps-before-run=true flag, pnpm will verify that lockfiles and dependencies match the manifest files before executing pnpm run or pnpm exec commands. This helps catch outdated dependencies early and ensures scripts run with the correct dependency versions.
Key changes:
- Introduces two new packages:
@pnpm/workspace.statefor caching workspace package metadata, and@pnpm/deps.statusfor checking lockfile and dependency status - Modifies
pnpm runandpnpm execcommands to optionally validate dependencies before execution - Refactors lockfile verification utilities to support the new checking functionality
Reviewed changes
Copilot reviewed 54 out of 55 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| workspace/state/* | New package that tracks and caches workspace package paths and catalog versions to optimize repeated checks |
| deps/status/* | New package that validates lockfiles, manifests, and installed dependencies are synchronized |
| lockfile/verification/src/linkedPackagesAreUpToDate.ts | Extracted function for checking if workspace-linked packages are current |
| lockfile/verification/src/getWorkspacePackagesByDirectory.ts | Extracted helper for mapping workspace packages by directory |
| lockfile/verification/src/allProjectsAreUpToDate.ts | Refactored to use extracted functions, improving modularity |
| exec/plugin-commands-script-runners/src/run.ts | Added dependency checking before script execution with environment variable to prevent nested checks |
| exec/plugin-commands-script-runners/src/exec.ts | Added dependency checking before exec command execution |
| exec/plugin-commands-script-runners/src/shouldRunCheck.ts | New utility determining when to skip dependency checks (lifecycle scripts, nested runs) |
| pkg-manager/plugin-commands-installation/src/installDeps.ts | Updates workspace state cache after recursive installations |
| pkg-manager/plugin-commands-installation/src/recursive.ts | Exports types needed by other modules |
| packages/constants/src/index.ts | Adds MANIFEST_BASE_NAMES constant for package.json variants |
| config/config/src/Config.ts | Adds verifyDepsBeforeRun configuration option |
| pnpm/test/verifyDepsBeforeRun/* | Comprehensive integration tests for single/multi-project workspaces, exec command, and edge cases |
| pnpm/test/utils/execPnpm.ts | Enhanced with cwd support, expectSuccess option for better test debugging |
| Various package.json and tsconfig.json | Dependency and reference updates for new packages |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
lockfile/verification/src/allProjectsAreUpToDate.ts:54
- Variable 'importer' cannot be of type null, but it is compared to an expression of type null.
return importer != null &&
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const stderr = execResult.stderr?.toString().split('\n').map(line => `\t${line}`).join('\n') | ||
| let message = `Process exits with code ${execResult.status}` | ||
| if (stdout.trim()) message += `\nSTDOUT:\n${stdout}` | ||
| if (stderr.trim()) message += `\nSTDOUT:\n${stderr}` |
There was a problem hiding this comment.
The error message incorrectly labels stderr output as "STDOUT". This should be "STDERR" to accurately reflect what is being displayed.
| manifests.baz = { | ||
| name: 'bar', |
There was a problem hiding this comment.
The variable name 'baz' is used but the manifest has the name 'bar', which creates confusion. Either the variable should be named differently (e.g., 'bazManifest') or the manifest name property should be 'baz' to match the variable name and location.
| manifests.baz = { | ||
| name: 'bar', |
There was a problem hiding this comment.
The variable name 'baz' is used but the manifest has the name 'bar', which creates confusion. Either the variable should be named differently or the manifest name property should be 'baz' to match the variable name and location.
| manifests.baz = { | ||
| name: 'bar', | ||
| private: true, |
There was a problem hiding this comment.
The variable name 'baz' is used but the manifest has the name 'bar', which creates confusion. Either the variable should be named differently or the manifest name property should be 'baz' to match the variable name and location.
| { | ||
| "name": "@pnpm/workspace.state", | ||
| "version": "0.0.0", | ||
| "description": "Track the list of actual paths of workspace packages in a cache", | ||
| "main": "lib/index.js", | ||
| "types": "lib/index.d.ts", | ||
| "engines": { | ||
| "node": ">=18.12" | ||
| }, | ||
| "files": [ | ||
| "lib", | ||
| "!*.map" | ||
| ], | ||
| "scripts": { | ||
| "test": "pnpm run compile && pnpm run _test", | ||
| "_test": "jest", | ||
| "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"", | ||
| "prepublishOnly": "pnpm run compile", | ||
| "compile": "tsc --build && pnpm run lint --fix" | ||
| }, | ||
| "repository": "https://github.com/pnpm/pnpm/blob/main/workspace/state", | ||
| "keywords": [ | ||
| "pnpm10", | ||
| "pnpm", | ||
| "mtime" | ||
| ], | ||
| "license": "MIT", | ||
| "bugs": { | ||
| "url": "https://github.com/pnpm/pnpm/issues" | ||
| }, | ||
| "homepage": "https://github.com/pnpm/pnpm/blob/main/workspace/state#readme", | ||
| "funding": "https://opencollective.com/pnpm", | ||
| "dependencies": { | ||
| "@pnpm/catalogs.types": "workspace:*", | ||
| "@pnpm/logger": "workspace:*", | ||
| "@pnpm/types": "workspace:*", | ||
| "ramda": "catalog:" | ||
| }, | ||
| "devDependencies": { | ||
| "@pnpm/prepare": "workspace:*", | ||
| "@pnpm/workspace.state": "workspace:*" | ||
| }, | ||
| "exports": { | ||
| ".": "./lib/index.js" | ||
| }, | ||
| "jest": { | ||
| "preset": "@pnpm/jest-config" | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing 'funding' field in package.json. Other similar packages in this PR (like deps/status/package.json) include a 'funding' field pointing to the OpenCollective page. This should be added for consistency.
|
I requested a review just to get a summary from copilot. |
Resolves #8585