Skip to content

test(publish): add non-dry-run publish e2e coverage - #588

Merged
goosewobbler merged 1 commit into
mainfrom
test/publish-non-dry-run-e2e
Jul 19, 2026
Merged

goosewobbler merged 1 commit into
mainfrom
test/publish-non-dry-run-e2e

Conversation

@goosewobbler

@goosewobbler goosewobbler commented Jul 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds packages/publish/test/integration/publish-e2e.spec.ts — end-to-end coverage of the non-dry-run publish pipeline, the path every existing test layer bypasses (Examples Smoke uses --dry-run, the Action harness sets SKIP_PUBLISH, unit specs mock the registries). That blind spot is exactly why #578 shipped despite a green suite.

The spec drives the real runPipeline with dryRun: false:

  • npm publishes to a minimal in-process registry (verdaccio-style) so real npm publish + npm view + Bearer-token auth run without touching npmjs. The registry is serviced asynchronously (the pipeline shells out via async execFile, so the event loop stays free) and sends Cache-Control: no-store so npm re-queries on each npm view instead of serving a stale heuristic-fresh pre-publish 404.
  • cargo has its cargo publish exec stubbed on PATH; the value under test is auth + discovery + ordering, not the upload.
  • git push targets a local bare remote and gh release create is stubbed.

Scenarios covered:

  1. Enabled-but-empty cargo/pub stages no-op without demanding credentials (the Release fails on cargo auth for repos with no crates (regression from #563 default flip) #578 regression class — the key guard).
  2. Idempotent re-publish skip on a partial-failure re-run (no second PUT).
  3. Per-registry auth detection — npm errors when a target exists but no credential is present; cargo demands a token only when a crate is present; token-present publishes for real.
  4. Post-publish tag + GitHub-Release creation (commit → tag → publish → push → gh release create, asserted end to end).

Test-harness robustness: the real-npm publish cases run the publish subprocess with a hermetic environment — every npm_config_* key is stripped and HOME/npm_config_globalconfig/npm_config_cache are pinned to temp paths. Without this, running under pnpm --filter … test (the CI path) leaks an env-level npm_config_registry that outranks the pipeline's userconfig in npm's precedence, so npm publish would target npmjs.org and ENEEDAUTH. afterEach restores the full env.

Test plan

  • pnpm turbo run lint typecheck test — green (32/32); publish shows 288 passed + 3 skipped (the gated e2e is skipped by default so the default gate stays fast/no-network).
  • pnpm lint — exit 0.
  • CI-exact RELEASEKIT_PUBLISH_E2E=1 pnpm --filter @releasekit/publish test — 291/291, run 3× consecutively, 0 failed (verified by the orchestrator). A dedicated CI job can set that flag to run the real-publish scenarios on demand.

Harness choice

A vitest integration test (not the shell harness): the existing publish integration specs and the real-tool gated cargo-lock-sync.spec.ts are all vitest, and driving runPipeline in-process lets the test stand up an in-process registry, PATH stubs, and a local bare git remote while reusing the existing describe.skipIf gating idiom. The shell harness exercises the Action/CLI wrapper and always sets SKIP_PUBLISH, so it's the wrong layer for the publish internals.

Closes #580

🤖 Generated with Claude Code

https://claude.ai/code/session_01BT6fVBZrmExXZj7vQ29881

Greptile Summary

This PR adds publish-e2e.spec.ts, a new integration spec that drives runPipeline with dryRun: false to close the coverage gap that let #578 (cargo auth demanded on a crate-less repo) ship with a green suite. The two previous review concerns — putCount() being incremented before the version-conflict check and the absence of a version-specific GET /<pkg>/<ver> handler — are both addressed in the submitted code.

  • Lightweight guards (run by default, no network): verify that enabled-but-empty cargo/pub stages no-op without demanding credentials, that npm rejects with an auth error when no token is present, and that a token-absent cargo run fails precisely when a crate is discovered.
  • Opt-in real-publish tests (behind RELEASEKIT_PUBLISH_E2E=1): run npm publish against an in-process registry with hermetic env stripping to prevent pnpm-injected npm_config_registry from leaking, assert idempotent re-publish skip, and stub cargo publish on PATH to verify auth + ordering without touching crates.io.

Confidence Score: 5/5

Safe to merge — adds test coverage only, no production code changes.

The change is purely additive test code. Both issues flagged in prior review threads are resolved: putCount() is incremented after the conflict check, and the registry handles the version-specific GET endpoint. The harness design is solid with hermetic env stripping, local bare git remote, describe.skipIf gating, and proper afterEach env restore.

No files require special attention.

Important Files Changed

Filename Overview
packages/publish/test/integration/publish-e2e.spec.ts New e2e integration spec covering the non-dry-run publish pipeline; both previous thread concerns (putCount increment position and version-specific GET endpoint) are addressed; harness design is sound with proper env save/restore and hermetic npm env stripping

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant T as Test
    participant P as runPipeline
    participant R as LocalRegistry (in-process)
    participant N as npm CLI (subprocess)
    participant C as cargo stub (PATH)
    participant G as gh stub (PATH)
    participant BG as bare git remote

    Note over T,BG: Lightweight guards (default gate — no network)
    T->>P: dryRun:false, cargo+pub enabled, no crates/pkgs
    P-->>T: resolve (publishSucceeded:true, cargo:[], pub:[])

    T->>P: dryRun:false, npm enabled, no NPM_TOKEN
    P-->>T: reject (failedStage:npm-publish, /authentication/)

    T->>P: dryRun:false, cargo enabled + crate present, no CARGO_REGISTRY_TOKEN
    P-->>T: reject (failedStage:cargo-publish, /CARGO_REGISTRY_TOKEN/)

    Note over T,BG: E2E (RELEASEKIT_PUBLISH_E2E=1)
    T->>R: startLocalRegistry()
    R-->>T: url + putCount()

    T->>P: dryRun:false, npm token auth, real npm client
    P->>N: npm publish (Bearer token)
    N->>R: PUT /pkg (Bearer: token)
    R-->>N: 201 ok
    N-->>P: exit 0
    P->>G: gh release create v1.0.1
    G-->>P: exit 0 (stubbed)
    P->>BG: git push origin main --tags
    P-->>T: resolve (npm[0].success:true, putCount:1)

    T->>P: re-run same version (idempotency)
    P->>N: "npm view pkg@ver"
    N->>R: GET /pkg
    R-->>N: 200 packument (Cache-Control:no-store)
    N-->>P: version already published
    P-->>T: resolve (skipped:true, putCount still 1)

    T->>P: cargo, CARGO_REGISTRY_TOKEN present
    P->>C: cargo publish (stubbed)
    C-->>P: exit 0
    P-->>T: resolve (cargo[0].success:true)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant T as Test
    participant P as runPipeline
    participant R as LocalRegistry (in-process)
    participant N as npm CLI (subprocess)
    participant C as cargo stub (PATH)
    participant G as gh stub (PATH)
    participant BG as bare git remote

    Note over T,BG: Lightweight guards (default gate — no network)
    T->>P: dryRun:false, cargo+pub enabled, no crates/pkgs
    P-->>T: resolve (publishSucceeded:true, cargo:[], pub:[])

    T->>P: dryRun:false, npm enabled, no NPM_TOKEN
    P-->>T: reject (failedStage:npm-publish, /authentication/)

    T->>P: dryRun:false, cargo enabled + crate present, no CARGO_REGISTRY_TOKEN
    P-->>T: reject (failedStage:cargo-publish, /CARGO_REGISTRY_TOKEN/)

    Note over T,BG: E2E (RELEASEKIT_PUBLISH_E2E=1)
    T->>R: startLocalRegistry()
    R-->>T: url + putCount()

    T->>P: dryRun:false, npm token auth, real npm client
    P->>N: npm publish (Bearer token)
    N->>R: PUT /pkg (Bearer: token)
    R-->>N: 201 ok
    N-->>P: exit 0
    P->>G: gh release create v1.0.1
    G-->>P: exit 0 (stubbed)
    P->>BG: git push origin main --tags
    P-->>T: resolve (npm[0].success:true, putCount:1)

    T->>P: re-run same version (idempotency)
    P->>N: "npm view pkg@ver"
    N->>R: GET /pkg
    R-->>N: 200 packument (Cache-Control:no-store)
    N-->>P: version already published
    P-->>T: resolve (skipped:true, putCount still 1)

    T->>P: cargo, CARGO_REGISTRY_TOKEN present
    P->>C: cargo publish (stubbed)
    C-->>P: exit 0
    P-->>T: resolve (cargo[0].success:true)
Loading

Reviews (2): Last reviewed commit: "test(publish): add non-dry-run publish e..." | Re-trigger Greptile

@goosewobbler goosewobbler added this to the 0.42.0 milestone Jul 19, 2026
@github-actions

github-actions Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Standing release PR: #582 · v0.41.1 queued (4 packages) · open 2h 10m · ✅ ready to merge

Release Preview — v0.41.0 → v0.41.1

Note: Labels on this PR are advisory in standing-pr mode. Bumps come from conventional commits in the standing PR; override by editing labels on the standing PR itself. Add release:immediate to bypass the standing PR and release this PR directly.

These changes will be added to the release PR (#582) when merged:

Changelog

monorepo 0.41.0 → 0.41.1

Changed

  • add non-dry-run publish e2e coverage (publish) (#580)

All packages → 0.41.1 (sync versioning)

  • @releasekit/version
  • @releasekit/notes
  • @releasekit/publish
  • @releasekit/release

After merge — predicted release

No version escalation — this PR's changes will be included in the queued release without affecting the projected versions.

Package Standing PR This PR After merge
monorepo 0.41.1 0.41.1 0.41.1

Updated automatically by ReleaseKit

Comment thread packages/publish/test/integration/publish-e2e.spec.ts
Comment thread packages/publish/test/integration/publish-e2e.spec.ts
Every existing layer bypasses the real publish path: Examples Smoke runs
--dry-run (auth is dry-run-exempt), the Action harness sets SKIP_PUBLISH,
and unit specs mock the registries. That gap let #578 (cargo auth demanded
on a crateless repo) ship despite a green suite.

Add a vitest integration spec that drives the real runPipeline with
dryRun:false. npm publishes to an in-process verdaccio-style registry so
npm publish + npm view + token auth run for real without hitting npmjs;
cargo's publish exec is stubbed on PATH (the value is auth/discovery/
ordering, not the upload). Covers: enabled-but-empty stages no-op without
demanding credentials (the #578 class), idempotent re-publish skip on a
partial-failure re-run, per-registry auth detection, and post-publish tag +
GitHub-Release creation. The mock registry sends Cache-Control: no-store so
npm re-queries on each npm view (else heuristic freshness can serve a stale
pre-publish 404 and defeat the idempotency skip).

The lightweight guards run in the default gate (no network); the heavier
real-npm-publish cases are opt-in via RELEASEKIT_PUBLISH_E2E=1.

Closes #580

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BT6fVBZrmExXZj7vQ29881
@goosewobbler
goosewobbler force-pushed the test/publish-non-dry-run-e2e branch from 9c7e262 to 3484337 Compare July 19, 2026 23:17
@goosewobbler
goosewobbler merged commit 186241c into main Jul 19, 2026
31 checks passed
@goosewobbler
goosewobbler deleted the test/publish-non-dry-run-e2e branch July 19, 2026 23:25
@github-actions github-actions Bot mentioned this pull request Jul 19, 2026
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.

No end-to-end coverage of the non-dry-run publish path (auth + stage ordering)

1 participant