Skip to content

ci: bump wallet-api versions in ledger-live's pnpm catalog - #604

Open
Justkant wants to merge 3 commits into
mainfrom
fix/ledger-live-catalog-bump
Open

ci: bump wallet-api versions in ledger-live's pnpm catalog#604
Justkant wants to merge 3 commits into
mainfrom
fix/ledger-live-catalog-bump

Conversation

@Justkant

@Justkant Justkant commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

ledger-live has centralised the @ledgerhq/wallet-api-* versions in its pnpm catalog (LedgerHQ/ledger-live#20521, currently open). Consumers now declare:

"@ledgerhq/wallet-api-client": "catalog:"

and the range lives once in pnpm-workspace.yaml.

The update-ledger-live job bumps versions with a sed loop over every consuming package.json. Against a cataloged consumer that sed matches the literal "catalog:" specifier and rewrites it to ^<version>, while never touching pnpm-workspace.yaml. Every release would silently undo the catalog indirection and leave five stale catalog entries behind. pnpm install still succeeds, so nothing fails — the PR just quietly reverts the change each time.

Change

The step now runs a node script (node is already set up in the job; the script is written to $RUNNER_TEMP so it never lands in the ledger-live PR) that:

  1. Bumps the catalog entry in pnpm-workspace.yaml, editing line by line so comments, key order and quoting style survive. Only the top-level catalog: block is in scope — same-named keys under packages:, overrides: or catalogs: are left alone. Keys are compared exactly after unquoting, so wallet-api-client cannot hit wallet-api-client-react.
  2. Falls back to the package.json rewrite for published packages with no catalog entry. node_modules is skipped and workspace: / catalog: / link: / file: specifiers are never rewritten.
  3. Fails the job loudly when a package.json asks for "catalog:" but no catalog entry was found to bump — otherwise that consumer stays silently pinned to the old version. Nothing is written when this fires.
  4. Skips published packages ledger-live does not consume, without counting them as bumped.

Unchanged: the bumped step output (false ⇒ no PR), BUMP_SUMMARY (now also naming where each bump landed), and the 5× retry around pnpm install --lockfile-only.

Verification

The step body was extracted from this workflow and executed under bash with a stubbed pnpm — 43 assertions across 7 fixtures, all passing: cataloged package (workspace bumped, package.json untouched), non-cataloged fallback, orphan catalog: ref (job fails, nothing written), no catalog block (legacy behaviour), prefix collisions, catalogs:/overrides: isolation, node_modules skipped, unconsumed package skipped, and both bumped=false exits.

Also run against the real ledger-live files:

  • PR #20521 head — 3-line diff in pnpm-workspace.yaml, comments and untouched entries intact, every package.json byte-identical, wallet-api-tools (not consumed) skipped:
   # Wallet API
-  "@ledgerhq/wallet-api-client": ^1.15.3
+  "@ledgerhq/wallet-api-client": ^1.16.0
   "@ledgerhq/wallet-api-client-react": ^1.4.34
-  "@ledgerhq/wallet-api-core": ^2.0.0
+  "@ledgerhq/wallet-api-core": ^2.1.0
   "@ledgerhq/wallet-api-server": ^3.4.3
-  "@ledgerhq/wallet-api-simulator": ^2.3.3
+  "@ledgerhq/wallet-api-simulator": ^2.4.0
  • today's develop (no wallet-api catalog entries yet) — fallback path fires, pnpm-workspace.yaml untouched, workspace:^ module deps left alone.

So this is correct both before and after #20521 merges.

ledger-live centralised the @LedgerHQ/wallet-api-* versions in its pnpm
catalog (LedgerHQ/ledger-live#20521): consumers now declare "catalog:" and
the range lives once in pnpm-workspace.yaml.

The sed loop matched the literal "catalog:" specifier and replaced it with
^<version>, silently reverting the indirection on every release while the
five catalog entries went stale. pnpm install still succeeded, so nothing
failed — the PR just quietly undid the change each time.

Replace it with a node script that:
- bumps the entry in the top-level catalog: block of pnpm-workspace.yaml,
  line by line so comments, key order and quoting survive; keys under
  packages:/overrides:/catalogs: are out of scope and exact-matched, so
  wallet-api-client does not hit wallet-api-client-react
- falls back to the package.json rewrite for non-cataloged consumers,
  skipping node_modules and never clobbering workspace:/catalog: specifiers
- fails the job when a package.json asks for "catalog:" but no catalog entry
  was bumped, instead of leaving that consumer pinned to the old version
- skips published packages ledger-live does not consume, without counting
  them as bumped

The bumped output, BUMP_SUMMARY and the 5x lockfile retry are unchanged.
@Justkant
Justkant requested review from a team and ComradeAERGO as code owners August 6, 2026 15:16
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
wallet-api-wallet-api-tools Ready Ready Preview Aug 12, 2026 3:18pm

Request Review

@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4ecd8b5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Updates the release automation that opens/updates the downstream ledger-live dependency bump PRs, adapting it to pnpm catalog-managed @ledgerhq/wallet-api-* versions so releases don’t accidentally de-catalog consumers.

Changes:

  • Replaces the sed/grep loop with a Node script that bumps the top-level catalog: entries in pnpm-workspace.yaml (preserving comments/order/quoting) and only falls back to package.json edits when no catalog entry exists.
  • Adds guardrails: skips node_modules, never rewrites workspace:/link:/file:/portal:/catalog: specifiers, and fails loudly if "catalog:" is referenced but no matching catalog entry exists.
  • Improves bump reporting by emitting a TSV summary (bumped.tsv) that records where each bump landed and uses it to populate BUMP_SUMMARY.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

The catalog was bumped before the references were resolved, so a leftover
entry alone was enough to rewrite pnpm-workspace.yaml, count as bumped and
open a PR for a version nothing resolves. Resolve the references first and
gate the catalog on the package being consumed at all — by "catalog:" or by
a literal range — so a stale entry is reported and left alone, while an
entry backing literal package.json refs still gets refreshed.

Alongside that:
- support named catalogs: catalogs: blocks are now indexed and catalog:<name>
  (plus the catalog:default alias) resolves to the right one, instead of
  failing the release with "no entry in the top-level 'catalog:' block"
- inherit the range operator from the entry being replaced. ledger-live pins
  its catalog exactly, and a ^ there would let an unrelated install drift
  onto a wallet-api version this release never validated
- keep a separator after the colon: an entry with an empty value produced
  "key":^1.0.0, which pnpm's YAML parsers reject or read as a plain string
- set an exit code instead of process.exit(1), which can drop the orphan
  diagnostic still queued on stderr — that message is the whole point of
  failing fast
- strip the carriage returns before matching: "." never matches \r, so on a
  CRLF checkout every entry was invisible and the job failed claiming the
  catalog entry was missing

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (3)

.github/workflows/release.yml:252

  • The package.json rewrite path increments refs and records the file as rewritten even when range(current, version) produces the same string (so next === text). This makes the bump summary/location accounting inaccurate and can contribute to reporting a bump when there was no effective change.
                refs++;
                return `${head}${range(current, version)}${tail}`;
              });
              if (!refs) continue;
              rewritten.push(file);

.github/workflows/release.yml:317

  • bumped.tsv being non-empty currently gates the rest of the step, but the script can still emit entries even when no files actually changed (e.g., reruns after ledger-live is already updated). Add an explicit git diff --quiet guard so the workflow exits with bumped=false when there is no effective diff, avoiding no-op lockfile refreshes and PR attempts.
          if [[ ! -s "$BUMPED_FILE" ]]; then
            echo "No published package is consumed by ledger-live — nothing to bump."
            echo "bumped=false" >> "$GITHUB_OUTPUT"
            exit 0
          fi

.github/workflows/release.yml:203

  • bumpCatalog() rewrites the catalog line and sets ws.dirty = true even when the computed value is identical to the existing value. On workflow re-runs (or if ledger-live is already at the released version), this can cause unnecessary file writes/CRLF churn and can contribute to no-op PR creation.

This issue also appears in the following locations of the same file:

  • line 248
  • line 313
              ws.lines[i] = `${indent}${key}${sep}${quote}${range(unquote(value), version)}${quote}${trailer}`;
            }
            ws.dirty = true;
            return true;

An entry already on the published version rewrites to the very same line,
yet still counted as a bump: re-running a release whose ledger-live PR had
merged handed create-pull-request an empty diff, after regenerating the
lockfile for nothing.

Split "entry found" from "entry moved" — the first still drives the orphan
check, the second the summary — and do the same for package.json refs,
where `rewritten` had come to mean "references it", not "was rewritten".

Also drop the specifier allow-list for a colon test. A semver range cannot
hold one (prerelease and build identifiers are [0-9A-Za-z-]), so it covers
`npm:`/`jsr:` aliases and git URLs too, which the list clobbered into a
plain range.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.github/workflows/release.yml:259

  • The fallback rewrite treats “contains a colon” as the only indicator of an indirection. That means non-semver specifiers without : (e.g. git shorthands like user/repo#..., dist-tags like latest, etc.) would be rewritten to a version range, which can corrupt the dependency spec.

Safer approach: only rewrite values that look like semver pins/ranges (optionally prefixed by a known operator), and leave everything else untouched.

                if (current.includes(":")) return match;
                refs++;
                return `${head}${range(current, version)}${tail}`;

.github/workflows/release.yml:125

  • range() only preserves ~ and ^. If a dependency (catalog or package.json) uses a comparator range like ">=1.2.3" / "<2.0.0", the code currently falls back to ^ and changes the meaning of the constraint.

Consider preserving common comparator prefixes when present (and only defaulting to ^ when the existing spec doesn’t look semver-ish).

This issue also appears on line 257 of the same file.

          const range = (current, version) => {
            const prefix = /^([~^]?)\d/.exec(current.trim());
            return `${prefix ? prefix[1] : "^"}${version}`;
          };

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.

3 participants