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:
authored and GitHub committed 2026-09-12 14:47:44 +00:00
1 parent 8a21551599
commit 1e768979c5
13 files changed
+1378 -1120

No files matched your search

+9 -6
View File
@@ -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.
+48
View File
@@ -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
+14 -1
View File
@@ -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",
+4
View File
@@ -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",
+9 -1
View File
@@ -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
View File
@@ -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",
+580 -902
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -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
View File
@@ -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.');
}
+1
View File
@@ -35,6 +35,7 @@ const FILES = [
'package.json',
'package-lock.json',
'pnpm-lock.yaml',
'pnpm-workspace.yaml',
'packages/web-shell/package.json',
];
+219 -2
View File
@@ -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',