Skip to content

Moved @tryghost framework pins to pnpm catalog#27533

Merged
9larsons merged 3 commits intomainfrom
catalog-tryghost-framework-pins
Apr 23, 2026
Merged

Moved @tryghost framework pins to pnpm catalog#27533
9larsons merged 3 commits intomainfrom
catalog-tryghost-framework-pins

Conversation

@9larsons
Copy link
Copy Markdown
Contributor

Summary

Moves three @tryghost/* packages to pnpm-workspace.yaml's catalog: entry so the version is declared in one place instead of being mirrored across up to seven workspaces and pnpm.overrides.

Package Previous locations New
@tryghost/debug 7 workspaces (ghost/core, ghost/i18n, e2e, + four public apps) catalog:
@tryghost/errors ghost/core pin + root pnpm.overrides catalog:
@tryghost/logging ghost/core, e2e, + root pnpm.overrides catalog:

Extends the pattern already used for eslint in pnpm-workspace.yaml. The catalog comment points to the matching .github/renovate.json5 group so the two stay in sync.

Why now

Framework-repo catch-up is still in flight. Wave 4 is @tryghost/errors → 3.1.0 and @tryghost/logging → 4.1.0 — with this migration in place first, Wave 4 becomes three version edits (one per catalog entry) instead of eight coordinated package.json changes. It also prevents silent drift (e.g. ghost/core at 2.1.0 while an app lingers at an older pin).

Mechanical notes

  • No version changes for any consumer — every workspace still resolves the exact same versions (debug 2.1.0, errors 1.3.13, logging 2.5.5).
  • The root pnpm.overrides entry for @tryghost/errors tightens from the range ^1.3.7 to the catalog's exact 1.3.13. Since every consumer was already at 1.3.13, this is a behaviour-neutral tightening.
  • Four published apps had their package.json touched (via the debug pin swap), so patch-bumped each: portal 2.68.16, comments-ui 1.4.9, signup-form 0.3.17, sodo-search 1.8.14.
  • Renovate's grouping (matchPackageNames in .github/renovate.json5) targets package names, so it keeps working regardless of where the dep is declared. Worth watching on the next renovate run for this grouping to confirm PR shape is unchanged.

Test plan

  • pnpm install — clean, 21-line lockfile diff (all specifier updates, zero package adds/removes)
  • cd ghost/core && pnpm test:unit — 6141 passing
  • cd ghost/core && pnpm test:integration — 244 passing
  • cd ghost/core && pnpm test:e2e — 1573 passing
  • cd ghost/core && pnpm test:legacy — 451 passing
  • cd ghost/core && pnpm lint — clean
  • pnpm --filter @tryghost/i18n test — 30 passing
  • pnpm --filter @tryghost/comments-ui test:unit — 202 passing
  • pnpm --filter @tryghost/portal test — 507 passing
  • pnpm --filter @tryghost/sodo-search test — 2 files passing
  • pnpm --filter @tryghost/signup-form run build — clean
  • CI green

- @tryghost/debug (7 workspaces) -> catalog:
- @tryghost/errors (ghost/core + root pnpm.overrides) -> catalog:
- @tryghost/logging (ghost/core, e2e, root pnpm.overrides) -> catalog:

- one source of truth for three @tryghost/* packages that were pinned
  identically across the monorepo; future framework bumps become a
  single-line catalog edit rather than seven (debug) or three
  (logging) coordinated package.json changes
- extends the pattern already in use for eslint in pnpm-workspace.yaml
- set up before wave 4 (@tryghost/errors 3.1.0 + @tryghost/logging
  4.1.0) so that wave lands cleanly
- root pnpm.overrides can reference catalog entries too (pnpm 10.x),
  so the override for errors tightens from ^1.3.7 to the catalog's
  1.3.13 — no behaviour change since every consumer was already
  resolving to 1.3.13
- patch-bumped the four published apps whose package.json was
  touched: portal 2.68.16, comments-ui 1.4.9, signup-form 0.3.17,
  sodo-search 1.8.14
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Walkthrough

Added a pnpm dependency catalog with explicit versions for @tryghost/debug, @tryghost/errors, and @tryghost/logging in pnpm-workspace.yaml. Updated root package.json pnpm overrides to use catalog:. Bumped package versions in apps: comments-ui 1.4.8→1.4.9, portal 2.68.15→2.68.16, signup-form 0.3.16→0.3.17, and sodo-search 1.8.13→1.8.14. Replaced fixed version strings with catalog: specifiers for @tryghost/debug, @tryghost/errors, and @tryghost/logging across multiple package.json files. ghost/core/scripts/pack.js now resolves catalog: entries and uses pnpm pack.

Possibly related issues

  • TryGhost/update issue 25: dependency version updates and pnpm catalog configuration.
  • TryGhost/Pro-Packages issue 53: migrating @tryghost/debug to catalog-based resolution.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Moved @tryghost framework pins to pnpm catalog' clearly summarizes the main change: migrating framework dependency pins to a centralized pnpm catalog.
Description check ✅ Passed The description thoroughly explains the purpose, scope, and testing of the changes, directly relating to the migration of @tryghost packages to pnpm catalog.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch catalog-tryghost-framework-pins

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

The comment implied catalog entries and renovate's grouping rule had
to stay in sync; they don't. Renovate groups by package name and
picks up any @tryghost/* dep regardless of where it's declared, and
the concern (does this package belong in the renovate group?) applies
to every @tryghost/* pin anywhere in the repo, not just catalog ones.
@9larsons 9larsons enabled auto-merge (squash) April 23, 2026 13:15
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/sodo-search/package.json`:
- Line 17: The package.json contains a pnpm-only dependency specifier
"@tryghost/debug": "catalog:" which will break npm publish; update the
publishing flow so the ship script (the "ship" npm script) performs pnpm publish
(or pnpm pack) after committing the version bump or rewrites package.json to
replace "catalog:" specifiers before calling npm publish. Concretely, modify the
ship pipeline to run "pnpm publish" for the package (or run a manifest rewrite
step to convert "catalog:" deps to real versions) after the commit, ensuring
"@tryghost/debug" is not left as "catalog:" when published.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 74a18ac0-5390-44f1-94c9-2a47eaf9f61f

📥 Commits

Reviewing files that changed from the base of the PR and between 0c29d20 and 7a9be38.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • apps/comments-ui/package.json
  • apps/portal/package.json
  • apps/signup-form/package.json
  • apps/sodo-search/package.json
  • e2e/package.json
  • ghost/core/package.json
  • ghost/i18n/package.json
  • package.json
  • pnpm-workspace.yaml

},
"dependencies": {
"@tryghost/debug": "2.1.0",
"@tryghost/debug": "catalog:",
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Package manager declared at the repo root:"
if [ -f package.json ]; then
  jq -r '.packageManager // "(no packageManager field)"' package.json
fi

echo
echo "Catalog entry for `@tryghost/debug`:"
rg -n -C2 '@tryghost/debug|catalog:' pnpm-workspace.yaml apps/sodo-search/package.json

echo
echo "Release script publish/pack calls:"
if [ -f .github/scripts/release-apps.js ]; then
  rg -n -C4 '\b(pnpm|npm|yarn)\b|pack|publish|spawn|exec|execa' .github/scripts/release-apps.js
else
  echo ".github/scripts/release-apps.js not found"
fi

Repository: TryGhost/Ghost

Length of output: 3094


🏁 Script executed:

wc -l .github/scripts/release-apps.js
cat -n .github/scripts/release-apps.js | tail -20

Repository: TryGhost/Ghost

Length of output: 894


🏁 Script executed:

fd -e yml -e yaml | xargs rg -l "publish|sodo-search" .github/workflows/

Repository: TryGhost/Ghost

Length of output: 169


🏁 Script executed:

cat -n .github/workflows/release-apps.yml 2>/dev/null | head -60

Repository: TryGhost/Ghost

Length of output: 40


🏁 Script executed:

rg -r "release-apps" .github/

Repository: TryGhost/Ghost

Length of output: 1586


🏁 Script executed:

find . -name "*.md" -type f | xargs rg -l "release|publish|sodo-search" | head -5

Repository: TryGhost/Ghost

Length of output: 268


🏁 Script executed:

cat -n apps/sodo-search/package.json

Repository: TryGhost/Ghost

Length of output: 3471


🏁 Script executed:

rg -B5 -A5 "publishConfig|npm publish|pnpm publish" apps/sodo-search/package.json

Repository: TryGhost/Ghost

Length of output: 269


Verify that publishing uses pnpm publish or rewrites catalog: dependencies.

The @tryghost/debug dependency at line 17 uses catalog:, which is valid in pnpm workspaces. However, catalog: is not understood by npm and must be removed before publishing. The custom ship script (line 34) commits the version change but does not call pnpm publish, pnpm pack, or rewrite the manifest. Confirm the actual publishing step (after the commit) uses pnpm publish (which automatically removes catalog: specifiers) or performs an equivalent manifest rewrite before npm publication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/sodo-search/package.json` at line 17, The package.json contains a
pnpm-only dependency specifier "@tryghost/debug": "catalog:" which will break
npm publish; update the publishing flow so the ship script (the "ship" npm
script) performs pnpm publish (or pnpm pack) after committing the version bump
or rewrites package.json to replace "catalog:" specifiers before calling npm
publish. Concretely, modify the ship pipeline to run "pnpm publish" for the
package (or run a manifest rewrite step to convert "catalog:" deps to real
versions) after the commit, ensuring "@tryghost/debug" is not left as "catalog:"
when published.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.00%. Comparing base (0c29d20) to head (7e1eaef).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #27533   +/-   ##
=======================================
  Coverage   73.00%   73.00%           
=======================================
  Files        1556     1556           
  Lines      125898   125898           
  Branches    15244    15244           
=======================================
+ Hits        91909    91911    +2     
+ Misses      33029    33028    -1     
+ Partials      960      959    -1     
Flag Coverage Δ
admin-tests 49.80% <ø> (+0.01%) ⬆️
e2e-tests 73.00% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- ghost-cli installs Ghost outside the pnpm workspace, where
  catalog: specifiers can't resolve - pnpm fails with
  ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER
- two places were leaking catalog: refs into the tarball:
  1) component tarballs packed with `npm pack` kept the workspace
     package.json verbatim; switched to `pnpm pack` which
     substitutes workspace: and catalog: with concrete versions
  2) the root pnpm.overrides merged into the packaged package.json
     still contained catalog: entries; resolve them via
     pnpm-workspace.yaml before merging
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot

See analysis details on SonarQube Cloud

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
ghost/core/scripts/pack.js (2)

102-117: Parse pnpm pack's stdout to obtain the actual tarball filename instead of reconstructing it.

The script hand-constructs tgzName as key.replaceAll('@','').replaceAll('/','-') + '-' + version + '.tgz' and uses it to reference the packed component in the deploy package.json. While pnpm pack does produce this filename (matching npm pack's behavior for scoped packages), relying on reconstructed filenames is fragile if pnpm's behavior changes. Since pnpm pack prints the tarball path to stdout, capture and use it as the source of truth instead. Alternatively, add an assertion fs.existsSync(path.join(componentsDir, tgzName)) after packing to catch filename mismatches early rather than failing silently at install time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/scripts/pack.js` around lines 102 - 117, The pack script currently
hand-constructs tgzName from key and depPkg.version and trusts that pnpm
produced that filename; change the execFileSync call for 'pnpm pack' in the
pack.js block (where depPkg, slug and tgzName are defined) to capture pnpm's
stdout (or use execFileSync with encoding to get stdout) and parse the printed
tarball path to derive the actual filename to assign into pkg.dependencies[key]
(instead of using the reconstructed tgzName); as a safety fallback, after
packing assert the file exists with fs.existsSync(path.join(componentsDir,
actualTgzName)) and throw/log a clear error if not found so mismatches are
detected early.

37-49: Catalog resolver doesn't support named catalogs or selector-based overrides.

The function reads only workspaceYaml.catalog (the default catalog) and ignores workspaceYaml.catalogs (named catalogs). In pnpm's catalog protocol:

  • catalog: / catalog:default references the default catalog (under top-level catalog: key)
  • catalog:<name> references a named catalog (under top-level catalogs: key, keyed by catalog name)

The condition spec === \catalog:${name}`is semantically incorrect:nameis a package name (e.g.,@tryghost/errors) or selector (e.g., react>some-dep), not a catalog name. This branch can never match for valid pnpm specs. If any override uses a >selector with a catalog reference (e.g.,"react>some-dep": "catalog:"`), the lookup will throw.

Not a blocker for current overrides (all simple names, default catalog only), but tighten to prevent breaking on future named-catalog or selector-based usage.

♻️ Suggested refactoring
 const workspaceYaml = yaml.load(fs.readFileSync(path.join(ROOT_DIR, 'pnpm-workspace.yaml'), 'utf8'));
-const catalog = workspaceYaml.catalog || {};
+const defaultCatalog = workspaceYaml.catalog || {};
+const namedCatalogs = workspaceYaml.catalogs || {};

 function resolveCatalogSpec(name, spec) {
-    if (spec === 'catalog:' || spec === `catalog:${name}` || spec === 'catalog:default') {
-        const resolved = catalog[name];
-        if (!resolved) {
-            throw new Error(`Catalog reference for ${name} not found in pnpm-workspace.yaml`);
-        }
-        return resolved;
-    }
-    return spec;
+    if (typeof spec !== 'string' || !spec.startsWith('catalog:')) {
+        return spec;
+    }
+    const catalogName = spec.slice('catalog:'.length);
+    const source = (!catalogName || catalogName === 'default')
+        ? defaultCatalog
+        : (namedCatalogs[catalogName] || {});
+    const resolved = source[name];
+    if (!resolved) {
+        throw new Error(`Catalog reference ${spec} for ${name} not found in pnpm-workspace.yaml`);
+    }
+    return resolved;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/scripts/pack.js` around lines 37 - 49, The resolver wrongly
compares spec to `catalog:${name}` and only reads `workspaceYaml.catalog`;
change resolveCatalogSpec to detect any spec that starts with "catalog:" (use
prefix check), extract the catalog key after the colon (empty or "default" ->
use workspaceYaml.catalog, otherwise treat it as a named catalog and look it up
under workspaceYaml.catalogs[catalogKey]), and throw a clear error if the named
catalog is missing; update references to `workspaceYaml.catalogs` and keep the
existing behavior for plain specs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ghost/core/scripts/pack.js`:
- Around line 102-117: The pack script currently hand-constructs tgzName from
key and depPkg.version and trusts that pnpm produced that filename; change the
execFileSync call for 'pnpm pack' in the pack.js block (where depPkg, slug and
tgzName are defined) to capture pnpm's stdout (or use execFileSync with encoding
to get stdout) and parse the printed tarball path to derive the actual filename
to assign into pkg.dependencies[key] (instead of using the reconstructed
tgzName); as a safety fallback, after packing assert the file exists with
fs.existsSync(path.join(componentsDir, actualTgzName)) and throw/log a clear
error if not found so mismatches are detected early.
- Around line 37-49: The resolver wrongly compares spec to `catalog:${name}` and
only reads `workspaceYaml.catalog`; change resolveCatalogSpec to detect any spec
that starts with "catalog:" (use prefix check), extract the catalog key after
the colon (empty or "default" -> use workspaceYaml.catalog, otherwise treat it
as a named catalog and look it up under workspaceYaml.catalogs[catalogKey]), and
throw a clear error if the named catalog is missing; update references to
`workspaceYaml.catalogs` and keep the existing behavior for plain specs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 25f1210a-e90f-448d-8f2e-d2adaa610844

📥 Commits

Reviewing files that changed from the base of the PR and between 06982ae and 7e1eaef.

📒 Files selected for processing (1)
  • ghost/core/scripts/pack.js

@9larsons 9larsons merged commit 3057c59 into main Apr 23, 2026
45 of 46 checks passed
@9larsons 9larsons deleted the catalog-tryghost-framework-pins branch April 23, 2026 14:15
9larsons added a commit that referenced this pull request Apr 23, 2026
Reverts #27533.

The catalog resolutions do not work because `catalog:` does not work
outside of a workspace, so `npm i` is broken for these packages. This
largely should not matter, because anyone working on these should be
doing so by cloning the TryGhost/Ghost repo, which is a workspace, and
the built assets are also unaffected.

I will follow up with more holistic changes to adopt the pnpm catalog
after dependencies are brought more up to date.
9larsons added a commit that referenced this pull request Apr 23, 2026
- wave 4 of the @tryghost/framework catch-up, scoped to logging only
- @tryghost/logging source diff vs old version is entirely formatting;
  transitive deps (bunyan-loggly, fs-extra, lodash) get patch bumps
  and framework-sibling packages logging depends on (elasticsearch,
  http-stream, pretty-stream, request, validator) move to their
  current v2/v3/v5 majors internally - none of that surface crosses
  into ghost/core's app code
- @tryghost/errors intentionally not bumped. the override in
  pnpm.overrides forces the tree to errors@1 because errors@3 is
  node-only (imports Node's crypto module) and @tryghost/limit-service
  drags it into admin-x-settings' browser bundle via a runtime import
  in use-limiter.tsx, breaking the vite build. dropping the override
  fragments the tree into five errors versions and breaks instanceof
  checks in ghost/core unit tests. proper fix is in @tryghost/limit-
  service or admin-x-settings, not in @tryghost/errors
- FRAMEWORK-DEPS-FOLLOWUPS.md captures this plus the catalog issue
  from the earlier reverted attempt (#27533 / #27535) so a future
  dedicated effort has the full picture
@9larsons 9larsons mentioned this pull request Apr 23, 2026
8 tasks
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