Skip to content

feat(exec): check dependencies before running scripts#8645

Merged
zkochan merged 155 commits intomainfrom
ensure-lockfile-up-to-date-before-executing-scripts-8585
Nov 15, 2024
Merged

feat(exec): check dependencies before running scripts#8645
zkochan merged 155 commits intomainfrom
ensure-lockfile-up-to-date-before-executing-scripts-8585

Conversation

@KSXGitHub
Copy link
Copy Markdown
Contributor

@KSXGitHub KSXGitHub commented Oct 14, 2024

Resolves #8585

@KSXGitHub KSXGitHub requested a review from zkochan November 5, 2024 15:23
@zkochan
Copy link
Copy Markdown
Member

zkochan commented Nov 5, 2024

Filtered install writes the full lockfile at the root of the project but a filtered lockfile at node_modules/.pnpm/lock.yaml. The hidden lockfile inside .pnpm mirrors what is inside node_modules. It only lists the installed packages.

@KSXGitHub
Copy link
Copy Markdown
Contributor Author

Well, 9cf363b should skip check on filtered install.

@zkochan
Copy link
Copy Markdown
Member

zkochan commented Nov 6, 2024

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.

Comment on lines +165 to +173
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,
})
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should fix it: 6c1809e

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially wanted to add it to recursive directly, but it has too many return statement, so I think wrapper is nicer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a state file at node_modules/.modules.yaml. Should there be a new file?

Copy link
Copy Markdown
Contributor Author

@KSXGitHub KSXGitHub Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. That is unfortunate. Let's give the file some more meaningful name maybe. Like .pnpm-workspace-state.json.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's give the file some more meaningful name maybe. Like .pnpm-workspace-state.json.

1be0b40

@KSXGitHub
Copy link
Copy Markdown
Contributor Author

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.

0764be4 has reverted the feature that ignores filtered install.

@@ -0,0 +1,49 @@
{
"name": "@pnpm/workspace.packages-list-cache",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe call it

Suggested change
"name": "@pnpm/workspace.packages-list-cache",
"name": "@pnpm/workspace.state",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KSXGitHub KSXGitHub requested a review from zkochan November 12, 2024 18:14
@zkochan zkochan merged commit 19d5b51 into main Nov 15, 2024
@zkochan zkochan deleted the ensure-lockfile-up-to-date-before-executing-scripts-8585 branch November 15, 2024 00:01
@zkochan zkochan added this to the v10.0 milestone Nov 15, 2024
@zkochan zkochan requested a review from Copilot January 6, 2026 23:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.state for caching workspace package metadata, and @pnpm/deps.status for checking lockfile and dependency status
  • Modifies pnpm run and pnpm exec commands 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}`
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message incorrectly labels stderr output as "STDOUT". This should be "STDERR" to accurately reflect what is being displayed.

Copilot uses AI. Check for mistakes.
Comment on lines +235 to +236
manifests.baz = {
name: 'bar',
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +503 to +504
manifests.baz = {
name: 'bar',
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +302 to +304
manifests.baz = {
name: 'bar',
private: true,
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +49
{
"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"
}
}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@zkochan
Copy link
Copy Markdown
Member

zkochan commented Jan 6, 2026

I requested a review just to get a summary from copilot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Check that dependencies are install before running scripts

4 participants