Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/four-rings-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tryghost/kg-utils": patch
"@tryghost/kg-markdown-html-renderer": patch
---

Updated dependencies
5 changes: 5 additions & 0 deletions .changeset/funky-rice-shine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tryghost/koenig-lexical": patch
---

Updated dependencies
5 changes: 5 additions & 0 deletions .changeset/petite-forks-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tryghost/koenig-lexical": patch
---

Updated dependencies
5 changes: 5 additions & 0 deletions .changeset/swift-guests-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tryghost/kg-unsplash-selector": patch
---

Updated dependencies
20 changes: 10 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ jobs:
- name: Lint boundaries
run: pnpm nx run ghost-monorepo:lint:boundaries

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down Expand Up @@ -511,7 +511,7 @@ jobs:
name: admin-coverage
path: apps/*/coverage/cobertura-coverage.xml

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down Expand Up @@ -729,7 +729,7 @@ jobs:
exit 1
fi

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down Expand Up @@ -878,7 +878,7 @@ jobs:
ghost/*/coverage-e2e/cobertura-coverage.xml
ghost/*/coverage-integration/cobertura-coverage.xml

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down Expand Up @@ -968,7 +968,7 @@ jobs:
exit 1
fi

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down Expand Up @@ -1054,7 +1054,7 @@ jobs:
path: ${{ steps.app_name.outputs.root }}/playwright-report
retention-days: 30

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down Expand Up @@ -1125,7 +1125,7 @@ jobs:
run: |
[ -f ~/.ghost/logs/*.log ] && cat ~/.ghost/logs/*.log

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down Expand Up @@ -1207,7 +1207,7 @@ jobs:
retention-days: 7
if-no-files-found: error

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down Expand Up @@ -1290,7 +1290,7 @@ jobs:
retention-days: 7
if-no-files-found: error

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down Expand Up @@ -1927,7 +1927,7 @@ jobs:
path: e2e/test-results
retention-days: 7

- uses: tryghost/actions/actions/slack-build@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/slack-build@12da0671df2e249a65c467340262e6c4251d9565 # main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/label-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
runs-on: ubuntu-slim
if: github.repository_owner == 'TryGhost'
steps:
- uses: tryghost/actions/actions/label-actions@d5080c41ac3ab68c4115bcd11fad5379778a7c70 # main
- uses: tryghost/actions/actions/label-actions@12da0671df2e249a65c467340262e6c4251d9565 # main
45 changes: 40 additions & 5 deletions .pnpmfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import {glob, readFile} from 'node:fs/promises';

// Global pnpm hooks for the Ghost monorepo.
//
// Only `beforePacking` is defined. It runs during `pnpm pack` / `pnpm publish`
// and mutates the package.json written *into the tarball* — the on-disk
// manifest is never touched, and dependency resolution / the shared lockfile
// are unaffected (no `readPackage`/`afterAllResolved` hook here).
// `beforePacking` runs during `pnpm pack` / `pnpm publish` and mutates the
// package.json written *into the tarball* — the on-disk manifest is never
// touched. `readPackage` runs during resolution, so it *does* feed the shared
// lockfile.
//
// Applied to every packed/published package:
// - drop `nx` — Nx target config, meaningless to consumers
Expand Down Expand Up @@ -65,6 +65,41 @@ function beforePacking(pkg) {
return pkg;
}

function readPackage(pkg) {
// consolidate declares 48 template engines as optional peers. pnpm links any
// that another workspace package happens to satisfy, so react, react-dom and
// @babel/core rode into ghost's production deploy closure via
// nodemailer-mailgun-transport — the only thing that pulls consolidate in, and
// it never renders through it. packageExtensions can only add, so dropping the
// peers outright needs this hook.
if (pkg.name === 'consolidate') {
delete pkg.peerDependencies;
delete pkg.peerDependenciesMeta;
}

// knex declares sqlite3 as an optional peer dep, and we don't use it/don't
// want to install it in production, so we'll remove it from the knex peer
// deps
if (pkg.name === 'knex') {
delete pkg.peerDependencies?.sqlite3;
delete pkg.peerDependenciesMeta?.sqlite3;
}

// these deps pull in typescript as an optional peer dep, which ends up
// being included in Ghost's production image because of the way pnpm hoists
// optional peers. We don't want to ship ts in the prod image so we delete
// it from the manifest
//
// NOTE: auto-install-peers: false doesn't solve the problem here unfortunately,
// and it causes more issues with other deps
if (['viem', 'ox', 'abitype'].includes(pkg.name)) {
delete pkg.peerDependencies?.typescript;
delete pkg.peerDependenciesMeta?.typescript;
}

return pkg;
}

/**
* Dynamic config update function to automatically exclude "private" packages
* from pnpm's changelog detection. We can't remove the version fields
Expand Down Expand Up @@ -105,4 +140,4 @@ async function updateConfig(config) {
return config;
}

export const hooks = {beforePacking, updateConfig};
export const hooks = {beforePacking, readPackage, updateConfig};
6 changes: 6 additions & 0 deletions Dockerfile.production
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ RUN pnpm --filter-prod "ghost^..." -r run build && \
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,id=pnpm-store \
pnpm --filter=ghost --config.inject-workspace-packages=true deploy --prod /home/ghost

# Strip type-time, doc and native-build-input files the runtime never loads — see
# ghost/core/scripts/prune.mts for the rules and the exclusions they carry. The COPY
# layer below is extracted single-threaded, once per CI E2E shard, and that cost
# scales with file count: this removes roughly half of them.
RUN node ghost/core/scripts/prune.mts /home/ghost --profile=image

# Fail the build now if the native module didn't install correctly (missing/broken
# prebuilt binary) rather than at container runtime.
RUN cd /home/ghost && node -e "require('better-sqlite3'); console.log('better-sqlite3 OK')"
Expand Down
2 changes: 1 addition & 1 deletion apps/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"@tryghost/string": "catalog:",
"@tryghost/timezone-data": "catalog:",
"@uiw/react-codemirror": "catalog:",
"@xyflow/react": "12.11.1",
"@xyflow/react": "12.11.2",
"clsx": "catalog:",
"dequal": "catalog:",
"i18n-iso-countries": "7.14.0",
Expand Down
2 changes: 1 addition & 1 deletion apps/portal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@testing-library/jest-dom": "catalog:",
"@testing-library/react": "catalog:react17",
"@types/react": "catalog:react17",
"@testing-library/user-event": "14.6.1",
"@testing-library/user-event": "14.6.3",
"@tryghost/i18n": "workspace:*",
"@vitest/coverage-v8": "catalog:",
"dompurify": "catalog:",
Expand Down
33 changes: 0 additions & 33 deletions ghost/core/core/frontend/services/theme-engine/i18n/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ class I18n {
return 'en';
}

supportedLocales() {
return [this.defaultLocale()];
}

/**
* Exporting the current locale (e.g. "en") to make it available for other files as well,
* such as core/frontend/helpers/date.js and core/frontend/helpers/lang.js
Expand Down Expand Up @@ -92,8 +88,6 @@ class I18n {
*/
init() {
this._strings = this._loadStrings();

this._initializeIntl();
}

/**
Expand Down Expand Up @@ -228,33 +222,6 @@ class I18n {
return msg;
}

/**
* [Private] Setup i18n support:
* - Polyfill node.js if it does not have Intl support or support for a particular locale
*/
_initializeIntl() {
let hasBuiltInLocaleData;
let IntlPolyfill;

if (global.Intl) {
// Determine if the built-in `Intl` has the locale data we need.
hasBuiltInLocaleData = this.supportedLocales().every(function (locale) {
return Intl.NumberFormat.supportedLocalesOf(locale)[0] === locale &&
Intl.DateTimeFormat.supportedLocalesOf(locale)[0] === locale;
});
if (!hasBuiltInLocaleData) {
// `Intl` exists, but it doesn't have the data we need, so load the
// polyfill and replace the constructors with need with the polyfill's.
IntlPolyfill = require('intl');
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
}
} else {
// No `Intl`, so use and load the polyfill.
global.Intl = require('intl');
}
}

_handleUninitialisedError(key) {
logging.warn(`i18n was used before it was initialised with key ${key}`);
this.init();
Expand Down
3 changes: 1 addition & 2 deletions ghost/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@
"human-number": "3.0.0",
"iconv-lite": "0.7.2",
"image-size": "1.2.1",
"intl": "1.2.5",
"intl-messageformat": "5.4.3",
"js-yaml": "catalog:",
"jsdom": "catalog:",
Expand Down Expand Up @@ -257,7 +256,7 @@
"@types/js-yaml": "4.0.9",
"@types/jsdom": "28.0.3",
"@types/jsonwebtoken": "9.0.10",
"@types/lodash": "4.17.24",
"@types/lodash": "4.17.25",
"@types/lodash-es": "4.17.12",
"@types/mime-types": "3.0.1",
"@types/nconf": "catalog:",
Expand Down
21 changes: 17 additions & 4 deletions ghost/core/scripts/pack.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import os from 'node:os';
import {execFile} from 'node:child_process';
import {promisify} from 'node:util';
import yaml from 'js-yaml';
import {prune, reportPrune} from './prune.mts';

const execFileAsync = promisify(execFile);

Expand Down Expand Up @@ -169,14 +170,20 @@ if (!buildFiles.some(isLicenseFile)) {
// native module post-install scripts like better-sqlite3, sharp, re2)
// - overrides + packageExtensions (root dependency policy must apply to the
// standalone install too)
// - ignoredOptionalDependencies: the archive gets no .pnpmfile.mjs, so the
// readPackage hook that strips knex's optional sqlite3 peer never runs here.
// This is what keeps sqlite3 out — dropping the provider (knex-migrator's
// optionalDependencies) leaves knex's optional peer with nothing to bind to.
// Without it the end-user install pulls sqlite3 back in and fails on
// ERR_PNPM_IGNORED_BUILDS, since allowBuilds no longer permits its build.
// We drop:
// - packages: relative paths that don't exist in the standalone dir
// - minimumReleaseAge, blockExoticSubdeps, catalogMode: source-repo
// supply-chain policies not meaningful at end-user install (the bundled
// @tryghost/* component tarballs aren't on npm, so an age check would 404)
console.log('\nWriting pnpm-workspace.yaml...');
const buildWorkspace = {};
for (const key of ['catalog', 'catalogs', 'allowBuilds', 'strictDepBuilds', 'overrides', 'packageExtensions']) {
for (const key of ['catalog', 'catalogs', 'allowBuilds', 'strictDepBuilds', 'overrides', 'packageExtensions', 'ignoredOptionalDependencies']) {
if (rootWorkspace[key] !== undefined) {
buildWorkspace[key] = rootWorkspace[key];
}
Expand Down Expand Up @@ -205,9 +212,16 @@ await pnpm(
{cwd: BUILD_DIR}
);

// 5. Validate before tarring — guard against a valid-looking but broken archive.
// 5. Prune, then validate — the checks below have to see the tree that actually
// ships, so a prune that ate the entry point, the install metadata or a component
// tarball fails here rather than at a consumer's install.
await fs.rm(path.join(BUILD_DIR, 'node_modules'), {recursive: true, force: true});

console.log('\nPruning build output...');
reportPrune(await prune(BUILD_DIR, {profile: 'archive'}));

console.log('\nValidating build output...');
const requiredFiles = ['pnpm-workspace.yaml', 'pnpm-lock.yaml', 'package.json'];
const requiredFiles = ['pnpm-workspace.yaml', 'pnpm-lock.yaml', 'package.json', 'index.js'];
const [packagedPkg, packagedWorkspace, missingFiles, componentTgzCount, packagedFiles] = await Promise.all([
readJson(pkgPath),
readYaml(path.join(BUILD_DIR, 'pnpm-workspace.yaml')),
Expand Down Expand Up @@ -249,7 +263,6 @@ if (!packagedWorkspace?.overrides || Object.keys(packagedWorkspace.overrides).le
// 6. Create the tarball (npm layout: top-level package/ dir, no node_modules).
const version = pkg.version;
const tgzPath = path.join(CORE_DIR, `ghost-${version}.tgz`);
await fs.rm(path.join(BUILD_DIR, 'node_modules'), {recursive: true, force: true});

console.log(`\nCreating tarball: ghost-${version}.tgz`);
await execFileAsync('tar', ['czf', tgzPath, 'package'], {cwd: CORE_DIR});
Expand Down
Loading
Loading