Skip to content

fix(repo): install npm-alias closure edges from their packed tarball in the e2e bake - #237

Merged
kiro-systemf[bot] merged 3 commits into
mainfrom
stryker/e2e-closure-runner
Oct 8, 2026
Merged

kiro-systemf[bot] merged 3 commits into
mainfrom
stryker/e2e-closure-runner

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Why every e2e job fails on the release PR

CI run 37744072050 on #236 (cb7dde2) fails all five e2e jobs in the fixture bake with npm error notarget No matching version found for @systemfsoftware/stryker-js-vitest-runner@^9.0.0.

The manifest that asks for the runner: packages/stryker-js/package.json:49 (the same line on main 1910752 and on cb7dde2):

"@systemfsoftware/stryker-js-vm-runner": "workspace:@systemfsoftware/stryker-js-vitest-runner@^"

That's a pnpm workspace alias, not a catalog: spec. pnpm pack rewrites it to an npm alias. On main the packed CLI manifest says "@systemfsoftware/stryker-js-vm-runner": "npm:@systemfsoftware/stryker-js-vitest-runner@^8.1.1". On cb7dde2 the runner is 9.0.0, so it says ^9.0.0.

Why the packed copy isn't used: test/e2e/tests/__fixtures__/bake-fixtures.sh:10 runs npm install /packs/*.tgz. npm installs the runner tarball under its own name (stryker-js-vitest-runner). An npm: alias edge is a separate dependency under a different name, and npm resolves it from the registry even when the target's tarball is in the same install. The closure itself is complete: the runner is an entry package, so its tarball was always in /packs. What fails is the install, which assumes npm matches every edge by package name.

On main the registry has 8.1.1, so the install succeeds. Lines 11-18 then cp -R the packed runner over the alias folder, so the runner code under test was the workspace build. Its dependency tree, though, came from the published 8.1.1 manifest, and every bake pulled that from the registry. On the release tree ^9.0.0 isn't on the registry and never will be, so the install fails before the copy runs.

The bump isn't the cause: pnpm-release-management wrote 9.0.0, and ^9.0.0 is the correct range.

Fix

  • test/e2e-core/src/install-closure.workflow.ts (new): installClosure plans the bake's install from the packed manifests and the fixture manifests. The plan is every packed tarball, plus <alias>@file:<target tarball> for each npm: alias edge between closure members. Edges are what npm installs: dependencies, optionalDependencies, and peers not marked optional in peerDependenciesMeta. The plan refuses, naming the edge, instead of reaching the registry, in three cases:
    • UnpackedWorkspaceDependency: a member's edge targets a workspace package the closure didn't pack.
    • ConflictingAliasTargets: one alias name would install two different packages. npm keeps whichever spec comes last.
    • FixtureNamesWorkspacePackage: a fixture manifest names a workspace package. The fixture's own npm install runs before the closure specs, so it would resolve that package from the registry.
  • test/e2e-core/src/install-closure.schema.ts (new): the packed-manifest and fixture-manifest schemas.
  • test/e2e/src/Harness/fixture-cache.service.ts: on a cold bake only, gets the list of workspace package names (pnpm ls -r --depth -1 --json), reads each tarball's package/package.json and each fixture manifest, runs the plan, and passes the specs to the bake script as arguments. The tarball lookup now matches <scope>-<name>-<semver>.tgz exactly. It used to match a name prefix, so @systemfsoftware/stryker-js could pick up …-stryker-js-vitest-runner-*.tgz. The UnreadableVersion case this made unreachable is deleted.
  • test/e2e/tests/__fixtures__/bake-fixtures.sh: installs "$@". The alias copy workaround is removed.
  • Test: test/e2e-core/tests/install-closure.integration.test.ts, which runs in pnpm test.
    • One scenario packs a three-package closure with the production alias shape, at a version the registry doesn't have. It installs the plan with npm install --package-lock-only --offline, an empty cache and an unreachable registry, then requires every workspace @systemfsoftware/* lockfile entry to resolve to file:.
    • The other scenarios cover each refusal and the optional-peer exemption.
  • Docs: test/e2e/README.md and docs/solutions/build-errors/e2e-bake-resolved-an-npm-alias-edge-from-the-registry.md.

No changeset: only private test packages change (@systemfsoftware/stryker-e2e, @systemfsoftware/stryker-e2e-core).

Evidence

The test against main's install rule (1910752, bake-fixtures.sh:10, tarballs only). This is the current test file, run in a throwaway checkout of 1910752 with the branch's workflow module minus its alias-spec line, so the plan is exactly main's tarball list:

 FAIL  tests/install-closure.integration.test.ts > Installing the packed workspace closure > A member that names a sibling through an npm alias installs that sibling from its packed tarball
StepError: Then "every workspace package in the lockfile, the aliased runner included, resolves from a packed tarball" failed: AssertionError: expected { exitCode: 1, …(3) } to strictly equal { exitCode: +0, stderr: '', …(2) }
Failing step: Then every workspace package in the lockfile, the aliased runner included, resolves from a packed tarball   /home/ryan/Projects/systemfsoftware/stryker-js-effect.worktrees/unitH-main/test/e2e-core/tests/install-closure.integration.test.ts:181

- Expected
+ Received

  {
-   "exitCode": 0,
-   "installed": [
-     "@systemfsoftware/stryker-js",
-     "@systemfsoftware/stryker-js-plugin-interface",
-     "@systemfsoftware/stryker-js-vitest-runner",
-     "@systemfsoftware/stryker-js-vitest-runner",
-   ],
+   "exitCode": 1,
+   "installed": [],
    "registryResolved": [],
-   "stderr": "",
+   "stderr": "npm error code ENOTCACHED
+ npm error request to http://127.0.0.1:9/@systemfsoftware%2fstryker-js-vitest-runner failed: cache mode is 'only-if-cached' but no cached response is available.
+ npm error A complete log of this run can be found in: /tmp/install-closure-VZrI8b/npm-cache/_logs/2026-10-08T19_28_23_469Z-debug-0.log
+ ",
  }

 Test Files  1 failed (1)
      Tests  1 failed | 4 passed (5)

The same test on this branch: Test Files 1 passed (1) / Tests 5 passed (5).

The e2e bake on #236's tree (a throwaway checkout of cb7dde2 with this branch's diff applied, the baked cache removed first, pnpm lgtm:up, then pnpm test:e2e -- tests/vm-vitest.e2e.test.ts). Global setup baked all 7 fixtures in this run (the entry mtimes fall after the run start). Raw output:

$ date -d @$(cat /tmp/unitH-trace-start)  # e2e run start
2026-10-08T19:23:21Z
$ ls -la --time-style=+%FT%TZ <baked root>
total 0
drwxr-xr-x 1 root root 1200 2026-10-08T19:26:10Z .
drwxr-xr-x 1 root root  128 2026-10-08T19:25:32Z ..
drwxr-xr-x 1 root root  154 2026-10-08T19:23:42Z calc-fixture.e48c11345702437b7d02e86cc62dde2d2d3a7a95b59c3fd666471c9a26ad536e
drwxr-xr-x 1 root root  416 2026-10-08T19:24:09Z enterprise-monorepo-fixture.355fb4c9730ea5e050e8970f673144a4ddc42a3df5ab59192d2ef117ee7769eb
drwxr-xr-x 1 root root  154 2026-10-08T19:24:22Z failing-fixture.fa8e1d92f3102d8ae286263a5f26983c81c5f3eba9516583c871c7563f431296
drwxr-xr-x 1 root root  150 2026-10-08T19:24:42Z svelte-app-fixture.17b15c9a1c28fcc964cb5f595dace7333e2ecaaac3753b3e2485d44a69dba1e3
drwxr-xr-x 1 root root  644 2026-10-08T19:24:54Z typescript-checker-fixture.e265237d49c0ec29fa02d0df0b3e917b7482cdf91abe5d93013a65004af96886
drwxr-xr-x 1 root root  164 2026-10-08T19:25:06Z vitest-nested-describe-fixture.e750649ca8d62a71afad7608f3947d0e8bcbc90f5879d1cc35bec5fe7d5879e9
drwxr-xr-x 1 root root  122 2026-10-08T19:25:21Z vm-vitest-fixture.7359e3f8e54b539c4d28e56e4ed60e5d15fc742730e9f327eaf990387fe8b66b
$ per fixture lockfile: every @systemfsoftware entry (name <- resolved)
calc-fixture	@systemfsoftware/stryker-js-vm-runner	@systemfsoftware/stryker-js-vitest-runner@9.0.0	file:../../packs/systemfsoftware-stryker-js-vitest-runner-9.0.0.tgz
enterprise-monorepo-fixture	@systemfsoftware/stryker-js-vm-runner	@systemfsoftware/stryker-js-vitest-runner@9.0.0	file:../../packs/systemfsoftware-stryker-js-vitest-runner-9.0.0.tgz
failing-fixture	@systemfsoftware/stryker-js-vm-runner	@systemfsoftware/stryker-js-vitest-runner@9.0.0	file:../../packs/systemfsoftware-stryker-js-vitest-runner-9.0.0.tgz
svelte-app-fixture	@systemfsoftware/stryker-js-vm-runner	@systemfsoftware/stryker-js-vitest-runner@9.0.0	file:../../packs/systemfsoftware-stryker-js-vitest-runner-9.0.0.tgz
typescript-checker-fixture	@systemfsoftware/stryker-js-vm-runner	@systemfsoftware/stryker-js-vitest-runner@9.0.0	file:../../packs/systemfsoftware-stryker-js-vitest-runner-9.0.0.tgz
vitest-nested-describe-fixture	@systemfsoftware/stryker-js-vm-runner	@systemfsoftware/stryker-js-vitest-runner@9.0.0	file:../../packs/systemfsoftware-stryker-js-vitest-runner-9.0.0.tgz
vm-vitest-fixture	@systemfsoftware/stryker-js-vm-runner	@systemfsoftware/stryker-js-vitest-runner@9.0.0	file:../../packs/systemfsoftware-stryker-js-vitest-runner-9.0.0.tgz
# all 7 fixtures: 119 non-link @systemfsoftware/* lockfile entries, 112 resolved file:../../packs/*.tgz;
# the other 7 are @systemfsoftware/effect-cell-types@12.0.0 from the registry (one per fixture), which is not one of the 23 workspace packages

$ pnpm test:e2e -- tests/vm-vitest.e2e.test.ts   # exit 0
@systemfsoftware/stryker-e2e:test:e2e:  Test Files  1 passed (1)
@systemfsoftware/stryker-e2e:test:e2e:       Tests  1 passed (1)

The only @systemfsoftware/* package that still comes from the registry is @systemfsoftware/effect-cell-types@12.0.0. It isn't a workspace package (it's not one of the 23 in pnpm ls -r; this repo pins it through the catalog at ^12.0.0), so the closure has no tarball for it.

…in the e2e bake

The stryker-js manifest names the vm runner through a workspace alias
("@systemfsoftware/stryker-js-vm-runner": "workspace:@systemfsoftware/stryker-js-vitest-runner@^"),
which pnpm pack rewrites to npm:@systemfsoftware/stryker-js-vitest-runner@^<version>.
The bake ran `npm install /packs/*.tgz`. npm places the runner tarball under its
own name, so nothing fills the alias slot and npm resolves the npm: spec from the
registry. On main that silently downloaded the published runner (then copied the
packed one over it); on the 18.0.0 release PR the runner is 9.0.0, which the
registry does not have, and every e2e job failed with ETARGET

installClosure (stryker-e2e-core) now plans the install from the packed
manifests: every tarball plus `<alias>@file:<target tarball>` for each npm-alias
edge between closure members, and a refusal (UnpackedWorkspaceDependency) for an
edge to a workspace package the closure did not pack. The harness passes the plan
to bake-fixtures.sh, whose copy-over-the-alias step is gone. The packed tarball
lookup now matches <scope>-<name>-<semver>.tgz exactly; the prefix match let
@systemfsoftware/stryker-js pick up a sibling's tarball

install-closure.integration.test.ts packs a closure with the same alias shape and
installs the plan offline with an empty cache and an unreachable registry, then
requires file: provenance for every workspace package in the lockfile

Verdict-Semantics: unchanged
@systemfsoftware-maker systemfsoftware-maker changed the title stryker/e2e closure runner fix(repo): install npm-alias closure edges from their packed tarball in the e2e bake Oct 8, 2026
…e the e2e bake installs

Review of the closure install plan found three edges it let through to the
registry or to the wrong tarball. A fixture manifest naming a workspace package
is resolved by the fixture's own npm install before the closure specs run, so
the plan now refuses it (FixtureNamesWorkspacePackage). Two alias edges with one
name and different targets left npm to keep the last spec, so the plan refuses
them (ConflictingAliasTargets). Optional peers npm never installs are no longer
edges. The plan runs only when a fixture needs baking, and the packed-manifest
schemas move to install-closure.schema.ts

Verdict-Semantics: unchanged

@kiro-systemf kiro-systemf Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verified: cause at packages/stryker-js/package.json:49 + bake-fixtures.sh:10; failing-before on 1910752; bake on cb7dde2 resolves every workspace package from packed tarballs; CI 11/11 on a15c036; fresh review approve.

@kiro-systemf
kiro-systemf Bot merged commit 3af1c18 into main Oct 8, 2026
11 checks passed
@kiro-systemf
kiro-systemf Bot deleted the stryker/e2e-closure-runner branch October 8, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant