From 972e295b2b385e55d2d261eb5950225b4bdc57fe Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Sat, 11 Apr 2026 12:40:50 -0500 Subject: [PATCH 1/2] Replaced deep workspace imports with package exports in Admin react apps (#27347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit no ref - Add `exports` field to posts, stats, and activitypub package.json with subpath exports - Create `src/api.ts` in posts and stats as the public cross-package API - Update admin to import from `@tryghost/posts/api`, `@tryghost/stats/api`, `@tryghost/activitypub/api` instead of reaching into `src/` Admin was importing directly into other workspace packages' `src/` directories (`@tryghost/posts/src/providers/...`). This is a cross-package boundary violation — it bypasses the package's public API and couples admin to internal file structure; ultimately not a great pattern, and this makes it a bit easier when we want to combine these apps. This is also prep work for `pnpm deploy` with `injectWorkspacePackages=true`. The subpath exports currently point at source files (`./src/api.ts`), which works for normal workspace resolution (pnpm symlinks to the full directory). When `injectWorkspacePackages` is enabled, the `files` field in each package will need to include `src/` (or the exports will need to point at built outputs in `dist/`). That change belongs in the pnpm deploy PR. --- apps/activitypub/package.json | 7 +++++++ apps/admin/src/layout/app-sidebar/nav-main.tsx | 2 +- apps/admin/src/layout/app-sidebar/shared-views.ts | 4 ++-- apps/admin/src/routes.tsx | 12 +++++------- apps/posts/package.json | 8 ++++++++ apps/posts/src/api.ts | 8 ++++++++ apps/stats/package.json | 7 +++++++ apps/stats/src/api.ts | 6 ++++++ 8 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 apps/posts/src/api.ts create mode 100644 apps/stats/src/api.ts diff --git a/apps/activitypub/package.json b/apps/activitypub/package.json index 6fa2d381298..d09c1e41cd9 100644 --- a/apps/activitypub/package.json +++ b/apps/activitypub/package.json @@ -14,6 +14,13 @@ ], "main": "./dist/activitypub.umd.cjs", "module": "./dist/activitypub.js", + "exports": { + ".": { + "import": "./dist/activitypub.js", + "require": "./dist/activitypub.umd.cjs" + }, + "./api": "./src/index.tsx" + }, "private": false, "scripts": { "dev": "vite build --watch", diff --git a/apps/admin/src/layout/app-sidebar/nav-main.tsx b/apps/admin/src/layout/app-sidebar/nav-main.tsx index 1f54c21e617..63c092365f0 100644 --- a/apps/admin/src/layout/app-sidebar/nav-main.tsx +++ b/apps/admin/src/layout/app-sidebar/nav-main.tsx @@ -7,7 +7,7 @@ import { useCurrentUser } from "@tryghost/admin-x-framework/api/current-user"; import { useBrowseSettings } from "@tryghost/admin-x-framework/api/settings"; import { getSettingValue } from "@tryghost/admin-x-framework/api/settings"; import { hasAdminAccess } from "@tryghost/admin-x-framework/api/users"; -import { useNotificationsCountForUser } from "@tryghost/activitypub/src/index"; +import { useNotificationsCountForUser } from "@tryghost/activitypub/api"; import NetworkIcon from "./icons/network-icon"; import { NavMenuItem } from "./nav-menu-item"; import { useIsActiveLink } from "./use-is-active-link"; diff --git a/apps/admin/src/layout/app-sidebar/shared-views.ts b/apps/admin/src/layout/app-sidebar/shared-views.ts index e08102ccb3d..bb2e097261c 100644 --- a/apps/admin/src/layout/app-sidebar/shared-views.ts +++ b/apps/admin/src/layout/app-sidebar/shared-views.ts @@ -1,8 +1,8 @@ import {useMemo} from 'react'; -import {parseAllSharedViewsJSON} from '@tryghost/posts/src/views/members/shared-views'; +import {parseAllSharedViewsJSON} from '@tryghost/posts/api'; import {getSettingValue, useBrowseSettings} from '@tryghost/admin-x-framework/api/settings'; -export type {SharedView} from '@tryghost/posts/src/views/members/shared-views'; +export type {SharedView} from '@tryghost/posts/api'; export function getColorHex(color: string): string { const colorMap: Record = { diff --git a/apps/admin/src/routes.tsx b/apps/admin/src/routes.tsx index 86992ee5347..0a0a2175926 100644 --- a/apps/admin/src/routes.tsx +++ b/apps/admin/src/routes.tsx @@ -1,15 +1,13 @@ import {type RouteObject, Outlet, lazyComponent, redirect} from "@tryghost/admin-x-framework"; // ActivityPub -import { FeatureFlagsProvider, routes as activityPubRoutes } from "@tryghost/activitypub/src/index"; +import { FeatureFlagsProvider, routes as activityPubRoutes } from "@tryghost/activitypub/api"; // Posts (aka tags and post analytics) -import PostsAppContextProvider from "@tryghost/posts/src/providers/posts-app-context"; -import { routes as postRoutes } from "@tryghost/posts/src/routes"; +import { PostsAppContextProvider, routes as postRoutes } from "@tryghost/posts/api"; // Stats (aka analytics) -import GlobalDataProvider from "@tryghost/stats/src/providers/global-data-provider"; -import { routes as statsRoutes } from "@tryghost/stats/src/routes"; +import { GlobalDataProvider, routes as statsRoutes } from "@tryghost/stats/api"; import MyProfileRedirect from "./my-profile-redirect"; // Ember @@ -63,11 +61,11 @@ const membersRoute: RouteObject = { children: [ { index: true, - lazy: lazyComponent(() => import("@tryghost/posts/src/views/members/members")) + lazy: lazyComponent(() => import("@tryghost/posts/members")) }, { path: "import", - lazy: lazyComponent(() => import("@tryghost/posts/src/views/members/members")) + lazy: lazyComponent(() => import("@tryghost/posts/members")) } ] }; diff --git a/apps/posts/package.json b/apps/posts/package.json index 6a66b179099..06874f35ba7 100644 --- a/apps/posts/package.json +++ b/apps/posts/package.json @@ -14,6 +14,14 @@ ], "main": "./dist/posts.umd.cjs", "module": "./dist/posts.js", + "exports": { + ".": { + "import": "./dist/posts.js", + "require": "./dist/posts.umd.cjs" + }, + "./api": "./src/api.ts", + "./members": "./src/views/members/members.tsx" + }, "private": true, "scripts": { "dev": "vite build --watch", diff --git a/apps/posts/src/api.ts b/apps/posts/src/api.ts new file mode 100644 index 00000000000..39a2d4f80d3 --- /dev/null +++ b/apps/posts/src/api.ts @@ -0,0 +1,8 @@ +/** + * Public API for cross-package imports. + * Admin uses these exports instead of reaching into src/ directly. + */ +export {default as PostsAppContextProvider} from './providers/posts-app-context'; +export {routes} from './routes'; +export {parseAllSharedViewsJSON} from './views/members/shared-views'; +export type {SharedView, AllSharedViewsParseResult} from './views/members/shared-views'; diff --git a/apps/stats/package.json b/apps/stats/package.json index 995dcbb6f34..7d476548032 100644 --- a/apps/stats/package.json +++ b/apps/stats/package.json @@ -14,6 +14,13 @@ ], "main": "./dist/stats.umd.cjs", "module": "./dist/stats.js", + "exports": { + ".": { + "import": "./dist/stats.js", + "require": "./dist/stats.umd.cjs" + }, + "./api": "./src/api.ts" + }, "private": true, "scripts": { "dev": "vite build --watch", diff --git a/apps/stats/src/api.ts b/apps/stats/src/api.ts new file mode 100644 index 00000000000..469b956e4d8 --- /dev/null +++ b/apps/stats/src/api.ts @@ -0,0 +1,6 @@ +/** + * Public API for cross-package imports. + * Admin uses these exports instead of reaching into src/ directly. + */ +export {default as GlobalDataProvider} from './providers/global-data-provider'; +export {routes} from './routes'; From fe4ef54b201c70cfea521b12a02c77c4d764a5f2 Mon Sep 17 00:00:00 2001 From: Austin Burdine Date: Sat, 11 Apr 2026 13:52:42 -0400 Subject: [PATCH 2/2] Improve Nx base commit detection, remove unnecessary CI steps (#27297) no issue - add nrwl/nx-set-shas github action to set HEAD/BASE commit SHAs on main branch CI runs. - simplify unit test job by adding build as a dependent target for test:unit scripts. --- .github/workflows/ci.yml | 54 +++++++++++++++++++--------------------- nx.json | 3 ++- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a505a787aa5..91f03adb0ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,14 +45,16 @@ jobs: IS_SIX: ${{ github.ref == 'refs/heads/6.x' }} IS_SIX_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == '6.x' }} permissions: + actions: read contents: read - pull-requests: read steps: - name: Checkout current commit uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ env.HEAD_COMMIT }} - fetch-depth: 2 + fetch-depth: 0 + # fetch a treeless clone to improve checkout speed, the job will fetch contents later if needed + filter: 'tree:0' - name: Output GitHub context if: env.RUNNER_DEBUG == '1' @@ -60,19 +62,11 @@ jobs: echo "GITHUB_EVENT_NAME: ${{ github.event_name }}" echo "GITHUB_CONTEXT: ${{ toJson(github.event) }}" - - name: Get metadata (push) - if: github.event_name == 'push' - run: | - NUMBER_OF_COMMITS=$(printf "%s\n" '${{ toJson(github.event.commits.*.id) }}' | jq length) - echo "There are $NUMBER_OF_COMMITS commits in this push." - echo "BASE_COMMIT=$(git rev-parse HEAD~$NUMBER_OF_COMMITS)" >> $GITHUB_ENV - - - name: Get metadata (pull_request) - if: github.event_name == 'pull_request' - run: | - BASE_COMMIT=$(curl --location --request GET 'https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}' --header 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' | jq -r .base.sha) - echo "Setting BASE_COMMIT to $BASE_COMMIT" - echo "BASE_COMMIT=$BASE_COMMIT" >> $GITHUB_ENV + - name: Set SHAs for Nx Commands + uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1 + with: + main-branch-name: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }} + error-on-no-successful-workflow: ${{ env.IS_MAIN == 'true' }} - name: Check user org membership id: check_user_org_membership @@ -95,6 +89,7 @@ jobs: uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: added with: + base: ${{ env.NX_BASE }} filters: | new-package: - added: 'ghost/**/package.json' @@ -103,6 +98,7 @@ jobs: uses: AurorNZ/paths-filter@c9dd42e99db87803313ff6f4b1150cc9f6c836af # v5.0.0 id: changed with: + base: ${{ env.NX_BASE }} filters: | shared: &shared - '.github/**' @@ -194,8 +190,14 @@ jobs: - name: Install dependencies run: bash .github/scripts/install-deps.sh + - name: Determine Affected Projects + id: affected + run: | + AFFECTED_PROJECTS=$(pnpm -s nx show projects --affected --json | tr -d '\n') + echo "affected_projects=$AFFECTED_PROJECTS" >> "$GITHUB_OUTPUT" outputs: + affected_projects: ${{ steps.affected.outputs.affected_projects }} changed_admin: ${{ steps.changed.outputs.admin }} changed_core: ${{ steps.changed.outputs.core }} changed_admin_x_settings: ${{ steps.changed.outputs.admin-x-settings }} @@ -209,7 +211,6 @@ jobs: changed_tinybird_datafiles: ${{ steps.changed.outputs.tinybird-datafiles }} changed_any_code: ${{ steps.changed.outputs.any-code }} changed_new_package: ${{ steps.added.outputs.new-package }} - base_commit: ${{ env.BASE_COMMIT }} is_main: ${{ env.IS_MAIN }} is_tag: ${{ env.IS_TAG }} is_development: ${{ env.IS_DEVELOPMENT }} @@ -219,6 +220,7 @@ jobs: dependency_cache_key: ${{ env.cachekey }} node_version: ${{ env.NODE_VERSION }} node_test_matrix: ${{ steps.node_matrix.outputs.matrix }} + nx_base: ${{ env.NX_BASE }} job_app_version_bump_check: name: Check app version bump @@ -267,7 +269,10 @@ jobs: path: ghost/**/.eslintcache key: eslint-cache - - run: pnpm nx affected -t lint --base=${{ needs.job_setup.outputs.BASE_COMMIT }} + - run: pnpm nx affected -t lint + env: + NX_BASE: ${{ needs.job_setup.outputs.nx_base }} + NX_HEAD: ${{ env.HEAD_COMMIT }} - uses: tryghost/actions/actions/slack-build@0cbdcbeb9030f46b109d5e6e44c14933026d8ca5 # main if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' @@ -373,24 +378,15 @@ jobs: with: timezoneLinux: "America/New_York" - - name: Determine affected unit-test projects - id: affected_unit_projects - run: | - PROJECTS=$(pnpm --silent nx show projects --affected --withTarget=test:unit --base=${{ needs.job_setup.outputs.BASE_COMMIT }} --sep=, | tr -d '\n') - echo "projects=$PROJECTS" >> "$GITHUB_OUTPUT" - - # Build only projects that will run unit tests - - name: Build assets for affected unit tests - if: steps.affected_unit_projects.outputs.projects != '' - run: pnpm nx run-many -t build --projects="${{ steps.affected_unit_projects.outputs.projects }}" - - name: Run unit tests - run: pnpm nx affected -t test:unit --base=${{ needs.job_setup.outputs.BASE_COMMIT }} + run: pnpm nx affected -t test:unit env: FORCE_COLOR: 0 GHOST_UNIT_TEST_VARIANT: ci NX_SKIP_LOG_GROUPING: true logging__level: fatal + NX_BASE: ${{ needs.job_setup.outputs.nx_base }} + NX_HEAD: ${{ env.HEAD_COMMIT }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 if: matrix.node == env.NODE_VERSION diff --git a/nx.json b/nx.json index 22ca1416f04..190af49118e 100644 --- a/nx.json +++ b/nx.json @@ -29,7 +29,8 @@ "cache": true }, "test:unit": { - "cache": true + "cache": true, + "dependsOn": ["build"] }, "dev": { "dependsOn": ["^dev"],