mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
chore(pnpm): gate pnpm-lock on package-lock and declare hoisted imports (#11625)
* chore(pnpm): gate pnpm-lock on package-lock and declare hoisted imports - Lockfile check: every pnpm-lock version must be locked in package-lock (one documented npm-lock gap, failing if it goes stale), and every dependency npm runs an install script for needs an explicit allowBuilds decision (fsevents: false records current behaviour). - Regenerate pnpm-lock with `pnpm import`, which is deterministic and drops 18 versions npm never locked; document it as the regeneration step. - Enforce import/no-extraneous-dependencies on shipped sources and declare the 15 imports that only resolved through hoisting, at already-locked versions. - Run the pnpm smoke workflow when package-lock.json changes. - Add the Stage 2 verification brief. Refs #10444 * chore(pnpm): regenerate vscode NOTICES and pin the new smoke path - NOTICES.txt: core's new OpenTelemetry declarations make the generator visit six Apache-2.0 entries in a different order. Same 655 entries, same text; only their order changes (6 header lines). - package-scripts test: expect package-lock.json in the pnpm smoke paths. * fix(pnpm): close lockfile-gate gaps found in review - fail closed when pnpm-lock.yaml has no packages section instead of printing the strongest success line over an unread graph - match git/file: dependency keys against npm's resolved field, whose pnpm key is the source rather than a version - count an allowBuilds entry only when its value is boolean, so pnpm's own 'set this to true or false' placeholder no longer passes the gate - drop the inert package-lock.json trigger from the pnpm smoke workflow; no step in that job reads it, and npm/pnpm agreement is already gated by ci.yml's Check lockfile step on the full profile - stage-2 evidence recipe: cover nested workspace roots in the reflink arm, sample peak worktree count at most 5 minutes apart, run the parity matrix in a throwaway workflow, and give the npm baseline the same skip guards the pnpm arm has - drive every gate branch with fixture-based negative tests * docs(verification): correct pnpm stage-2 measurement recipe Quiesce the df window, name the worktree sampler's working directory and forbid host cron, and register the throwaway workflow's size baseline so the parity run's test:ci does not fail under both installers. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmty6iqs0zv * docs(verification): fix stage-2 recipe's dispatch and macOS fs probe Step 2 now pushes the scratch branch before gh workflow run --ref (the ref is resolved server-side), notes the workflow token scope, and gives the merge-to- default / pull_request fallback when a branch-only dispatch is refused. Step 1 probes the checkout's own volume with diskutil info "$(pwd)" instead of the sealed read-only / system volume. Pushing, dispatching, and posting the results comment are marked operator actions. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmty8nwk9zy * test(lockfile): cover stale-gap and alias arms of check-lockfile Add two failure-branch cases: a knownNpmLockGaps entry that leaves the pnpm graph (the !pnpmVersions.includes arm), and an aliased install script reported under its real package name (the npmPackageName details.name arm). Both were untested and stayed green if the arm was deleted. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmty8nwk9zy * fix(lockfile): close the empty-env seam and name the overrides remedy - `CHECK_LOCKFILE_ROOT` was read with `??`, so an exported-but-empty variable made `root` the empty string and every path in this gate cwd-relative: it would then report on whatever lockfiles sit in that directory as if they were the repository's, and pass quietly when the cwd happens to be the repo root. Truthiness keeps the fixture seam and closes the empty case. - The version-agreement failure named exactly one remedy, `corepack pnpm import`, while pnpm-workspace.yaml carries eight `overrides` (three of them pin typescript) that no npm-side regeneration can reproduce. The message now names that layer, so a still-red gate points at what actually decides the value. Verified: `node scripts/check-lockfile.js` exits 0 on the committed tree, prettier and eslint are clean on the file, and both suites that execute the gate pass. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: qwen-code-ci-bot <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]>
This commit is contained in:
13 files changed
+1378
-1120
No files matched your search
@@ -90,12 +90,15 @@ npm run build
|
||||
**pnpm worktree bootstrap (opt-in):** an additional Git worktree can install
|
||||
dependencies with `node scripts/setup-worktree.js`, which runs the pinned
|
||||
pnpm with `--frozen-lockfile` (warm store ≈ 99 MiB on copy-on-write
|
||||
filesystems such as APFS, btrfs, and XFS with reflink; without reflink it is
|
||||
≈ 1.2 GiB). npm remains the authoritative path for build, CI, packaging, and
|
||||
release; the pnpm layout is install-only for now. When dependencies change,
|
||||
regenerate the pnpm lockfile with
|
||||
`corepack pnpm install --lockfile-only --no-frozen-lockfile` and commit both
|
||||
lockfiles together.
|
||||
filesystems such as APFS, btrfs, and XFS with reflink; without reflink, as on
|
||||
ext4, it is ≈ 1.2 GiB, close to a plain npm install). The bootstrap skips the
|
||||
`prepare` build, so run `npm run build` before package tests. npm remains the
|
||||
authoritative path for build, CI, packaging, and release; the pnpm layout is
|
||||
install-only for now. When dependencies change, update `package-lock.json`
|
||||
with npm first, then regenerate the pnpm lockfile from it with
|
||||
`corepack pnpm import` and commit both lockfiles together;
|
||||
`npm run check:lockfile` fails when pnpm resolves a version npm has not
|
||||
locked.
|
||||
|
||||
**Run individual test files** (always preferred):
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# pnpm Stage 2 go/no-go evidence
|
||||
|
||||
The post-Stage 1 review on
|
||||
[#10444](https://github.com/QwenLM/qwen-code/issues/10444#issuecomment-5630188013)
|
||||
recommends holding Stage 2 (pnpm for development and CI) until two
|
||||
measurements exist. Neither could run on the machine that wrote this brief,
|
||||
because it does not run builds or test suites. Every number below that is not
|
||||
marked as measured comes from #10444 and has not been reproduced here.
|
||||
|
||||
Report results as a comment on #10444, then add a `results.md` next to this
|
||||
file. Pushing the scratch branch, dispatching the workflow, and posting the
|
||||
results comment are operator actions, not steps for an agent to take
|
||||
unattended.
|
||||
|
||||
## Already measured
|
||||
|
||||
On `main` at `642d36e`, Linux, Node 22.23.2, pnpm 11.24.0:
|
||||
|
||||
- Two consecutive `corepack pnpm import` runs produced byte-identical
|
||||
`pnpm-lock.yaml` output, about 51 s each with a cold metadata cache.
|
||||
`corepack pnpm install --frozen-lockfile --lockfile-only` accepts the
|
||||
result.
|
||||
- After the import, every `name@version` in `pnpm-lock.yaml` is also locked
|
||||
in `package-lock.json`, except `[email protected]`. That exception is a gap in
|
||||
the npm lockfile; `scripts/check-lockfile.js` explains it.
|
||||
|
||||
## 1. Per-worktree disk on the hosts that run worktrees
|
||||
|
||||
**Premise under test:** pnpm's 93% disk saving (1,472 → 99 MiB per worktree)
|
||||
depends on a copy-on-write filesystem, because `packageImportMethod` is
|
||||
`clone-or-copy`. On ext4 the saving should shrink to roughly 1.47 → 1.2 GiB.
|
||||
A reflink clone of the primary checkout's `node_modules` should match pnpm on
|
||||
CoW filesystems without pnpm.
|
||||
|
||||
Run this on every host class that creates worktrees: the ECS runner hosts and
|
||||
developer Macs.
|
||||
|
||||
1. Record the filesystem. On Linux, run `df -T <repo>`. On macOS, run
|
||||
`diskutil info "$(pwd)" | grep -E 'Personality|Mount Point'` from the
|
||||
checkout and record the mount point beside the personality. Probing `/`
|
||||
reports the sealed read-only APFS system volume on every Mac regardless
|
||||
of where the checkout actually lives; the repo sits on the Data volume
|
||||
or under `/Volumes`. When the repo is on the internal disk,
|
||||
`diskutil info /System/Volumes/Data` is the equivalent.
|
||||
|
||||
2. Prepare three fresh worktrees of the same commit from the primary
|
||||
checkout. The primary checkout must already have a completed `npm ci`.
|
||||
Warm the pnpm store once, in a throwaway worktree, before measuring.
|
||||
|
||||
3. For each method below, record the change in the `df` used column and the
|
||||
wall time. Use `df`, not `du`: `du` counts cloned files at full size.
|
||||
`df` reports the whole mount, so a concurrent `npm ci` or a swept
|
||||
worktree inside the timed window lands in the recorded delta: quiesce the
|
||||
window (pause or stop the runner service, or record that no other job
|
||||
ran), take the `df` reading immediately before and after the command, and
|
||||
in the report note whether concurrent host activity was observed in any
|
||||
window.
|
||||
|
||||
| Method | Command in the new worktree |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| npm | `QWEN_SKIP_PREPARE=1 QWEN_SKIP_NOTICE_GENERATION=1 npm ci --prefer-offline` (the guard pair `scripts/setup-worktree.js` sets, so the step only installs dependencies) |
|
||||
| pnpm | `node scripts/setup-worktree.js` |
|
||||
| reflink copy | copy the primary's `node_modules` and every workspace root's own — `packages/*/node_modules`, `packages/channels/*/node_modules`, `integrations/*/node_modules` — using `cp -c -R` on APFS or `cp -R --reflink=always` on btrfs/XFS reflink |
|
||||
|
||||
4. For the reflink copy, confirm the workspace links point into the new
|
||||
worktree:
|
||||
- `readlink node_modules/@qwen-code/qwen-code-core` prints
|
||||
`../../packages/core`.
|
||||
- `node -p "require.resolve('@qwen-code/qwen-code-core/package.json')"`,
|
||||
run in the worktree, prints a path inside that worktree.
|
||||
- `node -p "require.resolve('@larksuiteoapi/node-sdk/package.json', { paths: ['packages/channels/feishu'] })"`
|
||||
prints a path inside that worktree. The two checks above resolve
|
||||
through the root `node_modules/@qwen-code/qwen-code-core` symlink, so
|
||||
they pass even when a nested workspace root's `node_modules` was not
|
||||
copied; this one does not.
|
||||
|
||||
5. Record the peak number of concurrent worktrees per host over a week. The
|
||||
sampler is installed and removed by the operator, is time-boxed to the
|
||||
measurement week, and writes only inside the measurement's own directory;
|
||||
do not add a host cron/rc entry — the product's permission policy treats
|
||||
crontab edits as unauthorized persistence, and a scheduler that is never
|
||||
removed keeps firing after the measurement ends. `git worktree list` is
|
||||
repository-scoped, so sample once per clone: run
|
||||
`git -C <primary-checkout> worktree list | wc -l` at most 5 minutes apart
|
||||
and keep the maximum, and have the sampler exit non-zero when git fails
|
||||
rather than record a count (a bare `git worktree list` from a directory
|
||||
that is not a clone fails but still feeds `wc` a 0). Sum the per-clone
|
||||
counts for the per-host peak. Worktrees on these hosts can live for under
|
||||
an hour — review worktrees under `.qwen/tmp` are created and swept within
|
||||
a single run — so a daily sample reports the floor between runs, not the
|
||||
peak.
|
||||
|
||||
**Report:** a table of host, filesystem, method, `df` delta and wall time;
|
||||
the peak concurrent worktree count; and free disk per host.
|
||||
|
||||
**Decision this feeds:** disk alone justifies Stage 2 only if the peak count
|
||||
times the npm delta exceeds the host's disk budget, and the reflink copy is
|
||||
not an option on that filesystem.
|
||||
|
||||
## 2. Hoisted parity: the CI matrix on a pnpm-installed tree
|
||||
|
||||
**Premise under test:** with `nodeLinker: hoisted`, the existing npm scripts
|
||||
run unchanged on a pnpm-installed tree. Stage 2 would then need almost no
|
||||
script changes.
|
||||
|
||||
1. On a scratch branch, create a throwaway workflow: a copy of
|
||||
`.github/workflows/pnpm-worktree-smoke.yml` restricted to
|
||||
`on: workflow_dispatch`, a single `runs-on: ubuntu-latest` job (no
|
||||
matrix), and `timeout-minutes: 150`. Do not edit the smoke workflow
|
||||
itself — its job runs on three operating systems under a 20-minute
|
||||
ceiling the chain below far exceeds, and the scripts tests assert it
|
||||
contains no build step. Record the new file's byte size in
|
||||
`.github/workflows/.size-baseline`, or `npm run test:ci` fails on the
|
||||
workflow-size ratchet under both installers before the steps below run.
|
||||
After the install step, add `npm run build`,
|
||||
`npm run typecheck`, `npm run lint:ci`, `npm run test:ci`,
|
||||
`npm run bundle`, and `npm run check:serve-fast-path-bundle`.
|
||||
2. Push the scratch branch, then dispatch the run. `--ref <branch>` is
|
||||
resolved server-side, so a branch that exists only locally is not a ref
|
||||
GitHub can dispatch: push it first — `git push origin <branch>`, with a
|
||||
token carrying `workflow` scope (the branch adds a file under
|
||||
`.github/workflows/`) — then run
|
||||
`gh workflow run <workflow-file> --ref <branch>`. If a workflow file
|
||||
that exists only on a scratch branch is not dispatchable, merge the
|
||||
throwaway workflow to the default branch or trigger the run via
|
||||
`pull_request` instead.
|
||||
3. Repeat the run with the install step replaced by
|
||||
`QWEN_SKIP_PREPARE=1 QWEN_SKIP_NOTICE_GENERATION=1 npm ci --prefer-offline`,
|
||||
the same guard pair `scripts/setup-worktree.js` sets — a bare `npm ci`
|
||||
also runs the root `prepare` (husky, `npm run build`, `npm run bundle`)
|
||||
inside the step being timed. This is the npm baseline on the same runner
|
||||
class.
|
||||
|
||||
**Report:** pass or fail and duration for each step under both installers,
|
||||
the first failure's log for any step that fails only under pnpm, and the
|
||||
install step duration for each installer.
|
||||
|
||||
**Decision this feeds:** the real Stage 2 change surface, and the CI time
|
||||
benefit. Do not extrapolate that benefit from the worktree benchmark.
|
||||
|
||||
## Claims these results may contradict
|
||||
|
||||
- The ext4 per-worktree estimate of about 1.2 GiB, which comes from the
|
||||
AGENTS.md note.
|
||||
- "npm scripts run unchanged on a hoisted pnpm tree."
|
||||
- The recommendation to freeze at Stage 1.
|
||||
@@ -34,6 +34,21 @@ const generalRestrictedSyntaxSelectors = [
|
||||
},
|
||||
];
|
||||
|
||||
// Undeclared imports are checked in shipped sources only: tests resolve
|
||||
// shared tooling such as vitest from the root manifest by design.
|
||||
const extraneousDependencyTestFiles = [
|
||||
'**/*.test.{ts,tsx}',
|
||||
'**/*.spec.{ts,tsx}',
|
||||
'**/__tests__/**',
|
||||
'**/test/**',
|
||||
'**/tests/**',
|
||||
];
|
||||
const extraneousDependencyOptions = {
|
||||
devDependencies: true,
|
||||
optionalDependencies: true,
|
||||
peerDependencies: true,
|
||||
};
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
// Global ignores
|
||||
@@ -221,6 +236,39 @@ export default tseslint.config(
|
||||
'default-case': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
// A package must declare what its own sources import. npm and the hoisted
|
||||
// pnpm layout both resolve a sibling's or the root's dependency, so a
|
||||
// missing declaration stays invisible until the package is installed on
|
||||
// its own — the check an isolated node_modules layout would add. Type-only
|
||||
// imports are exempt because they disappear at build time.
|
||||
files: [
|
||||
'packages/**/src/**/*.{ts,tsx}',
|
||||
'integrations/**/src/**/*.{ts,tsx}',
|
||||
],
|
||||
ignores: extraneousDependencyTestFiles,
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
extraneousDependencyOptions,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// export-html and insight carry a package.json only for "type" and their
|
||||
// build script; web-templates declares what they import.
|
||||
files: ['packages/web-templates/src/**/*.{ts,tsx}'],
|
||||
ignores: extraneousDependencyTestFiles,
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
...extraneousDependencyOptions,
|
||||
packageDir: `${import.meta.dirname}/packages/web-templates`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// The rule itself exempts tests, __tests__, and fixtures; repeating that
|
||||
// here would give the exemption two sources of truth. The utils-upward
|
||||
|
||||
Generated
+14
-1
@@ -27494,7 +27494,9 @@
|
||||
"@qwen-code/qwen-code-core": "file:../core",
|
||||
"@qwen-code/sdk": "file:../sdk-typescript",
|
||||
"@qwen-code/web-templates": "file:../web-templates",
|
||||
"ansi-escapes": "^7.3.0",
|
||||
"ansi-regex": "^6.2.2",
|
||||
"chalk": "^4.1.2",
|
||||
"chokidar": "^4.0.3",
|
||||
"command-exists": "^1.2.9",
|
||||
"diff": "^7.0.0",
|
||||
@@ -27511,6 +27513,7 @@
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"lowlight": "^3.3.0",
|
||||
"markdown-it": "^15.0.0",
|
||||
"open": "^10.2.0",
|
||||
"p-limit": "^7.3.0",
|
||||
"prompts": "^2.4.2",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
@@ -27523,6 +27526,7 @@
|
||||
"remark-parse": "^11.0.0",
|
||||
"remend": "^1.3.1",
|
||||
"saxes": "6.0.0",
|
||||
"semver": "^7.7.2",
|
||||
"shell-quote": "^1.9.0",
|
||||
"simple-git": "^3.36.0",
|
||||
"string-width": "^7.1.0",
|
||||
@@ -27935,6 +27939,7 @@
|
||||
"@modelcontextprotocol/core": "2.0.0",
|
||||
"@modelcontextprotocol/sdk": "^1.30.0",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.221.0",
|
||||
"@opentelemetry/core": "^2.0.1",
|
||||
"@opentelemetry/exporter-logs-otlp-grpc": "^0.221.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
|
||||
@@ -27944,7 +27949,13 @@
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.221.0",
|
||||
"@opentelemetry/instrumentation-undici": "^0.31.0",
|
||||
"@opentelemetry/otlp-exporter-base": "^0.221.0",
|
||||
"@opentelemetry/resources": "^2.10.0",
|
||||
"@opentelemetry/sdk-logs": "^0.221.0",
|
||||
"@opentelemetry/sdk-metrics": "^2.10.0",
|
||||
"@opentelemetry/sdk-node": "^0.221.0",
|
||||
"@opentelemetry/sdk-trace-node": "^2.10.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.36.0",
|
||||
"@xterm/headless": "5.5.0",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.0",
|
||||
@@ -27982,7 +27993,8 @@
|
||||
"web-tree-sitter": "^0.24.7",
|
||||
"ws": "^8.18.0",
|
||||
"yaml": "^2.8.1",
|
||||
"yauzl": "^2.10.0"
|
||||
"yauzl": "^2.10.0",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/diff": "^7.0.2",
|
||||
@@ -31028,6 +31040,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.1.0",
|
||||
"express": "^5.1.0",
|
||||
"lucide-react": "^1.24.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"punycode": "^2.3.1",
|
||||
"react": "^19.2.4",
|
||||
|
||||
@@ -60,7 +60,9 @@
|
||||
"@qwen-code/qwen-code-core": "file:../core",
|
||||
"@qwen-code/sdk": "file:../sdk-typescript",
|
||||
"@qwen-code/web-templates": "file:../web-templates",
|
||||
"ansi-escapes": "^7.3.0",
|
||||
"ansi-regex": "^6.2.2",
|
||||
"chalk": "^4.1.2",
|
||||
"chokidar": "^4.0.3",
|
||||
"command-exists": "^1.2.9",
|
||||
"diff": "^7.0.0",
|
||||
@@ -77,6 +79,7 @@
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"lowlight": "^3.3.0",
|
||||
"markdown-it": "^15.0.0",
|
||||
"open": "^10.2.0",
|
||||
"p-limit": "^7.3.0",
|
||||
"prompts": "^2.4.2",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
@@ -88,6 +91,7 @@
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remend": "^1.3.1",
|
||||
"semver": "^7.7.2",
|
||||
"shell-quote": "^1.9.0",
|
||||
"simple-git": "^3.36.0",
|
||||
"saxes": "6.0.0",
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
"@modelcontextprotocol/core": "2.0.0",
|
||||
"@modelcontextprotocol/sdk": "^1.30.0",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.221.0",
|
||||
"@opentelemetry/core": "^2.0.1",
|
||||
"@opentelemetry/exporter-logs-otlp-grpc": "^0.221.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
|
||||
@@ -102,7 +103,13 @@
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.221.0",
|
||||
"@opentelemetry/instrumentation-undici": "^0.31.0",
|
||||
"@opentelemetry/otlp-exporter-base": "^0.221.0",
|
||||
"@opentelemetry/resources": "^2.10.0",
|
||||
"@opentelemetry/sdk-logs": "^0.221.0",
|
||||
"@opentelemetry/sdk-metrics": "^2.10.0",
|
||||
"@opentelemetry/sdk-node": "^0.221.0",
|
||||
"@opentelemetry/sdk-trace-node": "^2.10.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.36.0",
|
||||
"@xterm/headless": "5.5.0",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.0",
|
||||
@@ -140,7 +147,8 @@
|
||||
"web-tree-sitter": "^0.24.7",
|
||||
"ws": "^8.18.0",
|
||||
"yaml": "^2.8.1",
|
||||
"yauzl": "^2.10.0"
|
||||
"yauzl": "^2.10.0",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@lydell/node-pty": "1.2.0-beta.10",
|
||||
|
||||
+207
-207
@@ -6404,6 +6404,213 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
limitations under the License.
|
||||
|
||||
|
||||
============================================================
|
||||
@opentelemetry/[email protected]
|
||||
(https://github.com/open-telemetry/opentelemetry-js)
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
============================================================
|
||||
@opentelemetry/[email protected]
|
||||
(https://github.com/open-telemetry/opentelemetry-js)
|
||||
@@ -7439,213 +7646,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
limitations under the License.
|
||||
|
||||
|
||||
============================================================
|
||||
@opentelemetry/[email protected]
|
||||
(https://github.com/open-telemetry/opentelemetry-js)
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
============================================================
|
||||
@opentelemetry/[email protected]
|
||||
(https://github.com/open-telemetry/opentelemetry-js)
|
||||
|
||||
@@ -295,6 +295,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.1.0",
|
||||
"express": "^5.1.0",
|
||||
"lucide-react": "^1.24.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"punycode": "^2.3.1",
|
||||
"react": "^19.2.4",
|
||||
|
||||
Generated
+580
-902
File diff suppressed because it is too large.
Load diff
@@ -53,6 +53,10 @@ allowBuilds:
|
||||
'@qwen-code/qwen-code-core@file:packages/core': true
|
||||
'@vscode/vsce-sign': true
|
||||
esbuild: true
|
||||
# Darwin-only optional dependency whose install script is only
|
||||
# `node-gyp rebuild`; the macOS smoke install reports no ignored build for
|
||||
# it, so false records the behaviour pnpm already has.
|
||||
fsevents: false
|
||||
keytar: true
|
||||
msw: true
|
||||
protobufjs: true
|
||||
|
||||
+136
-1
@@ -11,7 +11,12 @@ import { fileURLToPath } from 'node:url';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const root = join(__dirname, '..');
|
||||
// Tests point the gate at a fixture root; the default stays the repository.
|
||||
// Truthiness, not `??`: an exported-but-empty variable would otherwise make
|
||||
// every path below cwd-relative, and the gate would report on whatever
|
||||
// lockfiles happen to sit in that directory as if they were the repository's.
|
||||
const envRoot = process.env.CHECK_LOCKFILE_ROOT?.trim();
|
||||
const root = envRoot ? envRoot : join(__dirname, '..');
|
||||
const lockfilePath = join(root, 'package-lock.json');
|
||||
|
||||
function readJsonFile(filePath) {
|
||||
@@ -249,3 +254,133 @@ if (parityErrors.length > 0) {
|
||||
} else {
|
||||
console.log('Playwright parity check passed.');
|
||||
}
|
||||
|
||||
// The dependency name behind a package-lock.json location. An aliased install
|
||||
// (`"string-width-cjs": "npm:string-width@…"`) sits under the alias and
|
||||
// records the real name in `name`.
|
||||
function npmPackageName(location, details) {
|
||||
if (details.name) return details.name;
|
||||
const marker = 'node_modules/';
|
||||
return location.slice(location.lastIndexOf(marker) + marker.length);
|
||||
}
|
||||
|
||||
// pnpm keys are `name@version` (allowBuilds also accepts `name@spec`); a
|
||||
// scoped name starts with its own `@`.
|
||||
function pnpmPackageName(key) {
|
||||
const at = key.indexOf('@', 1);
|
||||
return at === -1 ? key : key.slice(0, at);
|
||||
}
|
||||
|
||||
console.log('Checking pnpm lockfile against package-lock.json...');
|
||||
|
||||
const npmLockedVersions = new Set();
|
||||
const npmLockedSources = new Set();
|
||||
for (const [location, details] of Object.entries(packages)) {
|
||||
if (details.link === true || !location.includes('node_modules/')) {
|
||||
continue;
|
||||
}
|
||||
npmLockedVersions.add(
|
||||
`${npmPackageName(location, details)}@${details.version}`,
|
||||
);
|
||||
// pnpm keys a git or file: dependency by its source instead of a version
|
||||
// (`name@git+https://…#hash`), so those keys can only ever match npm's
|
||||
// `resolved`.
|
||||
if (
|
||||
details.resolved?.startsWith('git') ||
|
||||
details.resolved?.startsWith('file:')
|
||||
) {
|
||||
npmLockedSources.add(
|
||||
`${npmPackageName(location, details)}@${details.resolved}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// form-data nests [email protected], which requires exactly mime-db 1.52.0,
|
||||
// but package-lock.json locks no nested mime-db there, so npm serves it the
|
||||
// hoisted 1.54.0 while pnpm honours the pin. Drop the entry once npm locks
|
||||
// 1.52.0 or form-data moves off mime-types@2.
|
||||
const knownNpmLockGaps = new Set(['[email protected]']);
|
||||
|
||||
// pnpm dedupes where npm keeps nested copies (npm locks esbuild 0.25.6 at the
|
||||
// root and 0.25.12 nested; pnpm uses 0.25.12 for both), so the two graphs are
|
||||
// never equal. The direction that matters is this one: a pnpm worktree must
|
||||
// not run a dependency version that CI's npm install has not locked.
|
||||
const pnpmVersions = Object.keys(pnpmLockfile?.packages ?? {});
|
||||
if (pnpmVersions.length === 0) {
|
||||
console.error(
|
||||
'Error: pnpm-lock.yaml has no packages section; the version agreement gate read nothing.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
const unlockedPnpmVersions = pnpmVersions.filter(
|
||||
(key) =>
|
||||
!npmLockedVersions.has(key) &&
|
||||
!npmLockedSources.has(key) &&
|
||||
!knownNpmLockGaps.has(key),
|
||||
);
|
||||
const staleNpmLockGaps = [...knownNpmLockGaps].filter(
|
||||
(key) => !pnpmVersions.includes(key) || npmLockedVersions.has(key),
|
||||
);
|
||||
|
||||
if (unlockedPnpmVersions.length > 0) {
|
||||
console.error(
|
||||
'\nError: pnpm-lock.yaml resolves versions that package-lock.json does not lock. Regenerate it from package-lock.json with `corepack pnpm import`. If the divergence survives that, a pnpm-workspace.yaml `overrides:` entry is deciding the version (three pin typescript today) and no npm-side regeneration can match it:',
|
||||
);
|
||||
unlockedPnpmVersions.forEach((key) => console.error(`- ${key}`));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
if (staleNpmLockGaps.length > 0) {
|
||||
console.error(
|
||||
'\nError: remove these entries from knownNpmLockGaps in scripts/check-lockfile.js; they no longer describe a gap:',
|
||||
);
|
||||
staleNpmLockGaps.forEach((key) => console.error(`- ${key}`));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
if (unlockedPnpmVersions.length === 0 && staleNpmLockGaps.length === 0) {
|
||||
console.log('pnpm lockfile matches package-lock.json.');
|
||||
}
|
||||
|
||||
console.log('Checking pnpm build approvals...');
|
||||
|
||||
const pnpmWorkspacePath = join(root, 'pnpm-workspace.yaml');
|
||||
let pnpmWorkspace;
|
||||
try {
|
||||
pnpmWorkspace = parseYaml(fs.readFileSync(pnpmWorkspacePath, 'utf-8'));
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing ${pnpmWorkspacePath}:`, error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// npm runs every dependency install script; pnpm runs one only when
|
||||
// allowBuilds approves it. Requiring an entry for each script npm runs keeps
|
||||
// that difference a reviewed decision instead of a silent one.
|
||||
const decidedBuilds = new Set(
|
||||
Object.entries(pnpmWorkspace?.allowBuilds ?? {})
|
||||
// pnpm itself records an undecided entry as the string 'set this to true
|
||||
// or false'; only a boolean runs or skips a build.
|
||||
.filter(([, decision]) => typeof decision === 'boolean')
|
||||
.map(([key]) => pnpmPackageName(key)),
|
||||
);
|
||||
const undecidedBuilds = new Set();
|
||||
for (const [location, details] of Object.entries(packages)) {
|
||||
if (
|
||||
details.hasInstallScript !== true ||
|
||||
!location.includes('node_modules/')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const name = npmPackageName(location, details);
|
||||
if (!decidedBuilds.has(name)) {
|
||||
undecidedBuilds.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (undecidedBuilds.size > 0) {
|
||||
console.error(
|
||||
'\nError: these dependencies have install scripts but no allowBuilds entry in pnpm-workspace.yaml; add each with true (run it) or false (skip it):',
|
||||
);
|
||||
[...undecidedBuilds].sort().forEach((name) => console.error(`- ${name}`));
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log('pnpm build approvals cover every install script.');
|
||||
}
|
||||
@@ -35,6 +35,7 @@ const FILES = [
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'pnpm-lock.yaml',
|
||||
'pnpm-workspace.yaml',
|
||||
'packages/web-shell/package.json',
|
||||
];
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import {
|
||||
chmodSync,
|
||||
copyFileSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
@@ -19,7 +20,7 @@ import path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parse } from 'yaml';
|
||||
import { parse, stringify } from 'yaml';
|
||||
|
||||
import { hooks as pnpmHooks, workspacePackageNames } from '../../.pnpmfile.mjs';
|
||||
import { getPinnedPnpmPackage } from '../pnpm-package.js';
|
||||
@@ -153,7 +154,7 @@ describe('package scripts', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('checks both lockfiles for integrity', () => {
|
||||
it('checks both lockfiles for integrity and agreement', () => {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[path.join(root, 'scripts/check-lockfile.js')],
|
||||
@@ -163,12 +164,224 @@ describe('package scripts', () => {
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain('Lockfile check passed.');
|
||||
expect(result.stdout).toContain('pnpm lockfile check passed.');
|
||||
expect(result.stdout).toContain('pnpm lockfile matches package-lock.json.');
|
||||
expect(result.stdout).toContain(
|
||||
'pnpm build approvals cover every install script.',
|
||||
);
|
||||
// Pins the Playwright parity block's existence and happy path: deleting it,
|
||||
// or returning before it, goes red here. Its drift arms live in
|
||||
// check-lockfile.test.js, which runs the script against perturbed fixtures.
|
||||
expect(result.stdout).toContain('Playwright parity check passed.');
|
||||
});
|
||||
|
||||
describe('check-lockfile failure branches', () => {
|
||||
// Each case runs the real script against a temp root holding copies of
|
||||
// the committed lockfiles with one mutation, so the detectors — not
|
||||
// today's lockfile data — are what the assertions pin.
|
||||
function runCheckLockfile(mutate) {
|
||||
const fixtureRoot = mkdtempSync(path.join(tmpdir(), 'check-lockfile-'));
|
||||
try {
|
||||
// The Playwright parity section reads the pinned manifests as
|
||||
// well as the lockfiles, so the fixture carries them too: without
|
||||
// them the run dies on a missing package.json before reaching the
|
||||
// branch under test.
|
||||
for (const file of [
|
||||
'package-lock.json',
|
||||
'pnpm-lock.yaml',
|
||||
'pnpm-workspace.yaml',
|
||||
'package.json',
|
||||
'packages/web-shell/package.json',
|
||||
]) {
|
||||
const to = path.join(fixtureRoot, file);
|
||||
mkdirSync(path.dirname(to), { recursive: true });
|
||||
copyFileSync(path.join(root, file), to);
|
||||
}
|
||||
mutate(fixtureRoot);
|
||||
return spawnSync(
|
||||
process.execPath,
|
||||
[path.join(root, 'scripts/check-lockfile.js')],
|
||||
{
|
||||
encoding: 'utf8',
|
||||
env: { ...process.env, CHECK_LOCKFILE_ROOT: fixtureRoot },
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
rmSync(fixtureRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function mutatePackageLock(fixtureRoot, mutate) {
|
||||
const file = path.join(fixtureRoot, 'package-lock.json');
|
||||
const lock = JSON.parse(readFileSync(file, 'utf8'));
|
||||
mutate(lock);
|
||||
writeFileSync(file, JSON.stringify(lock));
|
||||
}
|
||||
|
||||
function mutatePnpmLock(fixtureRoot, mutate) {
|
||||
const file = path.join(fixtureRoot, 'pnpm-lock.yaml');
|
||||
const lock = parse(readFileSync(file, 'utf8'));
|
||||
mutate(lock);
|
||||
writeFileSync(file, stringify(lock));
|
||||
}
|
||||
|
||||
function mutatePnpmWorkspace(fixtureRoot, mutate) {
|
||||
const file = path.join(fixtureRoot, 'pnpm-workspace.yaml');
|
||||
const workspace = parse(readFileSync(file, 'utf8'));
|
||||
mutate(workspace);
|
||||
writeFileSync(file, stringify(workspace));
|
||||
}
|
||||
|
||||
it('fails when pnpm resolves a version npm has not locked', () => {
|
||||
const result = runCheckLockfile((fixtureRoot) =>
|
||||
mutatePnpmLock(fixtureRoot, (lock) => {
|
||||
lock.packages['[email protected]'] = {
|
||||
resolution: { integrity: 'sha512-bogus' },
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain(
|
||||
'versions that package-lock.json does not lock',
|
||||
);
|
||||
expect(result.stderr).toContain('- [email protected]');
|
||||
});
|
||||
|
||||
it('fails when a knownNpmLockGaps entry no longer describes a gap', () => {
|
||||
// [email protected] is the committed exception; npm locking it makes the
|
||||
// exception stale.
|
||||
const result = runCheckLockfile((fixtureRoot) =>
|
||||
mutatePackageLock(fixtureRoot, (lock) => {
|
||||
lock.packages['node_modules/mime-types/node_modules/mime-db'] = {
|
||||
version: '1.52.0',
|
||||
resolved: 'https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz',
|
||||
integrity: 'sha512-bogus',
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain(
|
||||
'remove these entries from knownNpmLockGaps',
|
||||
);
|
||||
expect(result.stderr).toContain('- [email protected]');
|
||||
});
|
||||
|
||||
it('fails when a knownNpmLockGaps entry leaves the pnpm graph', () => {
|
||||
// Deleting the pnpm key exercises the `!pnpmVersions.includes(key)`
|
||||
// arm: the allowlist entry survives with nothing to describe, and the
|
||||
// gate must say so instead of passing the version agreement.
|
||||
const result = runCheckLockfile((fixtureRoot) =>
|
||||
mutatePnpmLock(fixtureRoot, (lock) => {
|
||||
delete lock.packages['[email protected]'];
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain(
|
||||
'remove these entries from knownNpmLockGaps',
|
||||
);
|
||||
expect(result.stderr).toContain('- [email protected]');
|
||||
});
|
||||
|
||||
it('fails when an install script has no allowBuilds decision', () => {
|
||||
const result = runCheckLockfile((fixtureRoot) =>
|
||||
mutatePnpmWorkspace(fixtureRoot, (workspace) => {
|
||||
delete workspace.allowBuilds.esbuild;
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain('no allowBuilds entry');
|
||||
expect(result.stderr).toContain('- esbuild');
|
||||
});
|
||||
|
||||
it('reports an aliased install script under its real package name', () => {
|
||||
// npmPackageName returns `details.name` for an aliased install
|
||||
// (`"build-tool-cjs": "npm:build-tool@…"`). The build-approval loop
|
||||
// must compare the real name, not the alias, so an allowBuilds entry
|
||||
// under the alias cannot silence the gate.
|
||||
const result = runCheckLockfile((fixtureRoot) =>
|
||||
mutatePackageLock(fixtureRoot, (lock) => {
|
||||
lock.packages['node_modules/build-tool-cjs'] = {
|
||||
name: 'build-tool',
|
||||
version: '1.0.0',
|
||||
resolved:
|
||||
'https://registry.npmjs.org/build-tool/-/build-tool-1.0.0.tgz',
|
||||
integrity: 'sha512-bogus',
|
||||
hasInstallScript: true,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain('no allowBuilds entry');
|
||||
expect(result.stderr).toContain('- build-tool');
|
||||
expect(result.stderr).not.toContain('- build-tool-cjs');
|
||||
});
|
||||
|
||||
it('fails closed when pnpm-lock.yaml has no packages section', () => {
|
||||
const result = runCheckLockfile((fixtureRoot) => {
|
||||
writeFileSync(
|
||||
path.join(fixtureRoot, 'pnpm-lock.yaml'),
|
||||
"lockfileVersion: '9.0'\n",
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain('has no packages section');
|
||||
});
|
||||
|
||||
it('accepts a git dependency pnpm keys by source instead of version', () => {
|
||||
const resolved = 'git+https://github.com/example/git-dep.git#7ae66ab2';
|
||||
const result = runCheckLockfile((fixtureRoot) => {
|
||||
mutatePackageLock(fixtureRoot, (lock) => {
|
||||
lock.packages['node_modules/git-dep'] = {
|
||||
version: '1.2.3',
|
||||
resolved,
|
||||
};
|
||||
});
|
||||
mutatePnpmLock(fixtureRoot, (lock) => {
|
||||
lock.packages[`git-dep@${resolved}`] = {
|
||||
resolution: { type: 'git' },
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain(
|
||||
'pnpm lockfile matches package-lock.json.',
|
||||
);
|
||||
});
|
||||
|
||||
it('counts a spec-scoped allowBuilds key as a decision for the package', () => {
|
||||
const result = runCheckLockfile((fixtureRoot) =>
|
||||
mutatePnpmWorkspace(fixtureRoot, (workspace) => {
|
||||
delete workspace.allowBuilds.esbuild;
|
||||
workspace.allowBuilds['esbuild@^0.25.0'] = true;
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain(
|
||||
'pnpm build approvals cover every install script.',
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores an allowBuilds placeholder that decides nothing', () => {
|
||||
const result = runCheckLockfile((fixtureRoot) =>
|
||||
mutatePnpmWorkspace(fixtureRoot, (workspace) => {
|
||||
// pnpm writes this string for an install whose build it skipped.
|
||||
workspace.allowBuilds.esbuild = 'set this to true or false';
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain('no allowBuilds entry');
|
||||
expect(result.stderr).toContain('- esbuild');
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the internal release-age exception independent of the version', () => {
|
||||
const workspace = parse(
|
||||
readFileSync(path.join(root, 'pnpm-workspace.yaml'), 'utf8'),
|
||||
@@ -784,6 +997,10 @@ describe('package scripts', () => {
|
||||
const workflow = parse(
|
||||
readWorkflow('.github/workflows/pnpm-worktree-smoke.yml'),
|
||||
);
|
||||
// package-lock.json is deliberately absent: no step in this job reads
|
||||
// it (the install validates pnpm-lock.yaml against the workspace
|
||||
// manifests), and npm/pnpm version agreement is gated by the 'Check
|
||||
// lockfile' step in ci.yml.
|
||||
const expectedPaths = [
|
||||
'.github/workflows/pnpm-worktree-smoke.yml',
|
||||
'.npmrc',
|
||||
|
||||
Reference in new issue
Block a user