Skip to content

Conversation

@rmgpinto
Copy link
Contributor

@rmgpinto rmgpinto commented Dec 4, 2025

ref https://linear.app/ghost/issue/BER-3095

  • Extracted the public files code from the middleware to the router. This avoids route comparison on every requests Ghost receives.

Note

Switches public file handling to explicit router endpoints, adding theme-aware sitemap/robots fallbacks and updating site wiring.

  • Frontend Web Router:
    • Introduces servePublicFiles(siteApp) in core/frontend/web/routers/serve-public-file.js to register explicit routes for sitemap.xsl, robots.txt, public/ghost*.css, public/ghost-stats.min.js, public/cards.min.{css,js}, public/comment-counts.min.js, public/member-attribution.min.js, and /.well-known/recommendations.json with caching and card asset middleware.
    • Adds theme-aware fallbacks for sitemap.xsl and robots.txt (checks active theme; respects is_private for robots).
    • Exposes and retains servePublicFile and createPublicFileMiddleware helpers.
  • Site Setup (core/frontend/web/site.js):
    • Replaces multiple mw.servePublicFile(...) usages with servePublicFiles(siteApp); removes direct cardAssets usage.
    • Keeps favicon and theme static handling; removes robots/public asset middleware wiring.
  • Middleware Index: removes servePublicFile export.
  • Tests: update import path to router-based servePublicFile.

Written by Cursor Bugbot for commit c3c22a7. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Updated a nested submodule pointer. Removed servePublicFile from the middleware index and moved centralized public-asset route registration into a new servePublicFiles(siteApp) function in the routers module. The routers module now exports servePublicFiles as the primary export while still exposing servePublicFile and createPublicFileMiddleware as properties. site.js now calls servePublicFiles(siteApp) instead of wiring individual public routes. Unit tests were updated to import servePublicFile from the routers module.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Inspect servePublicFiles route registration logic (sitemap.xsl, robots.txt, CSS/JS, card assets, comment-counts, member attribution, recommendations) for correctness and parity with previously wired routes.
  • Verify theme-aware and privacy-aware fallbacks (theme file existence checks and settings-cache usage).
  • Review integration points in site.js to ensure no removed routes or behavior regressions.
  • Check middleware index changes for unresolved imports or broken consumers after removing the export.
  • Review new imports/usage of cardAssets, themeEngine, and settingsCache for side effects and error handling.
  • Run and validate updated unit tests referencing servePublicFile from the routers module.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: moving servePublicFile functionality from middleware to router architecture.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, referencing the issue, explaining the motivation, and describing the key architectural changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch serve-public-file-router

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.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@rmgpinto rmgpinto force-pushed the serve-public-file-router branch from f8eceda to a9c8b59 Compare December 4, 2025 10:07
Copy link
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: 0

🧹 Nitpick comments (1)
ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js (1)

4-4: Import path updated correctly; consider relocating test file.

The import path change correctly reflects the module move from middleware to routers. However, this test file remains in test/unit/frontend/web/middleware/ while testing code that now lives in routers/. Consider moving this test to test/unit/frontend/web/routers/serve-public-file.test.js for consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8eceda and a9c8b59.

📒 Files selected for processing (5)
  • ghost/core/content/themes/source (1 hunks)
  • ghost/core/core/frontend/web/middleware/index.js (0 hunks)
  • ghost/core/core/frontend/web/routers/serve-public-file.js (2 hunks)
  • ghost/core/core/frontend/web/site.js (2 hunks)
  • ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js (1 hunks)
💤 Files with no reviewable changes (1)
  • ghost/core/core/frontend/web/middleware/index.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/content/themes/source
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Admin-x React apps build to `apps/*/dist` using Vite, which are then copied by `ghost/admin/lib/asset-delivery` to `ghost/core/core/built/admin/assets/*`

Applied to files:

  • ghost/core/core/frontend/web/site.js
  • ghost/core/core/frontend/web/routers/serve-public-file.js
📚 Learning: 2025-04-23T15:44:52.549Z
Learnt from: 9larsons
Repo: TryGhost/Ghost PR: 21866
File: ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js:10-19
Timestamp: 2025-04-23T15:44:52.549Z
Learning: The existing implementation in `ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js` using `path.parse(req.url).base` is secure against path traversal attacks as it properly extracts only the filename component without any directory parts.

Applied to files:

  • ghost/core/core/frontend/web/site.js
  • ghost/core/core/frontend/web/routers/serve-public-file.js
  • ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Applies to ghost/i18n/locales/en/{portal,comments,signup-form,search}.json : Add UI translations to namespace-specific files in `ghost/i18n/locales/en/` for public UI apps (portal.json for Portal, comments.json for Comments UI, etc.)

Applied to files:

  • ghost/core/core/frontend/web/site.js
  • ghost/core/core/frontend/web/routers/serve-public-file.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Ghost admin serves admin-x apps from `/ghost/assets/{app-name}/{app-name}.js` URLs

Applied to files:

  • ghost/core/core/frontend/web/site.js
  • ghost/core/core/frontend/web/routers/serve-public-file.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Core Ghost backend logic should be placed in `ghost/core/core/server/` directory structure organized by purpose (api, services, models, data/schema, etc.)

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: API routes should be defined in `ghost/core/core/server/api/`

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Public app configuration should be passed via data attributes in `<script>` tags injected by `{{ghost_head}}`

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Public UI apps (Portal, Comments UI, Signup Form, Sodo Search, Announcement Bar) should be built as UMD bundles for CDN distribution

Applied to files:

  • ghost/core/core/frontend/web/site.js
🧬 Code graph analysis (2)
ghost/core/core/frontend/web/site.js (2)
ghost/core/core/frontend/web/routers/serve-public-file.js (1)
  • require (8-8)
ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js (1)
  • require (4-4)
ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js (1)
ghost/core/core/frontend/web/routers/serve-public-file.js (1)
  • require (8-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Acceptance tests (Node 22.18.0, sqlite3)
  • GitHub Check: Legacy tests (Node 22.18.0, sqlite3)
  • GitHub Check: Ghost-CLI tests
  • GitHub Check: Acceptance tests (Node 22.18.0, mysql8)
  • GitHub Check: Unit tests (Node 22.18.0)
  • GitHub Check: Legacy tests (Node 22.18.0, mysql8)
  • GitHub Check: Lint
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Build & Push
🔇 Additional comments (5)
ghost/core/core/frontend/web/site.js (1)

13-13: Clean centralization of public file serving.

The refactor correctly consolidates scattered public asset route registrations into a single servePublicFiles(siteApp) call. The import path and invocation are properly aligned with the new router module.

Regarding the previously flagged robots.txt ordering concern: the new implementation in serve-public-file.js explicitly checks for a theme-provided robots.txt and calls next() to defer to staticTheme middleware when found, which should preserve the intended fallback behavior.

Also applies to: 68-69

ghost/core/core/frontend/web/routers/serve-public-file.js (4)

8-10: New dependencies added for theme-aware robots.txt serving.

The imports for cardAssets, themeEngine, and settingsCache are necessary for the centralized route handling and theme-aware robots.txt logic.


119-142: Route registrations are well-structured.

The explicit GET route registrations achieve the PR objective of avoiding path comparisons on every request. The card asset routes correctly chain cardAssets.serveMiddleware() to ensure on-the-fly building, and recommendations.json appropriately disables server caching for dynamic content.


143-165: Robots.txt theme fallback logic correctly implemented.

The implementation addresses the route ordering concern by:

  1. Checking is_private and deferring to filterPrivateRoutes
  2. Using fs.access to detect theme-provided robots.txt
  3. Calling next() when theme has its own file, allowing staticTheme middleware (registered later in site.js) to serve it

One minor note: fs.access is asynchronous, which is appropriate here. The error-first callback pattern is correctly handled.


168-170: Export structure maintains backward compatibility.

The module correctly exports servePublicFiles as the default while preserving servePublicFile and createPublicFileMiddleware as named exports. This allows the test file to continue importing servePublicFile via destructuring.

@rmgpinto rmgpinto requested a review from cmraible December 4, 2025 13:12
@rmgpinto rmgpinto force-pushed the serve-public-file-router branch from a9c8b59 to ad7cad3 Compare December 4, 2025 16:46
@github-actions
Copy link
Contributor

github-actions bot commented Dec 4, 2025

Ember E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 19936713309 -n playwright-report-ember -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@github-actions
Copy link
Contributor

github-actions bot commented Dec 4, 2025

React E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 19936713309 -n playwright-report-react -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@github-actions
Copy link
Contributor

github-actions bot commented Dec 4, 2025

Ember E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 19936713309 -n playwright-report-ember -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@github-actions
Copy link
Contributor

github-actions bot commented Dec 4, 2025

React E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 19936713309 -n playwright-report-react -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

ref https://linear.app/ghost/issue/BER-3095

- Extracted the public files code from the middleware to the router.
This avoids route comparison on every requests Ghost receives.
@rmgpinto rmgpinto force-pushed the serve-public-file-router branch from ad7cad3 to c3c22a7 Compare December 8, 2025 10:35
Copy link
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: 0

♻️ Duplicate comments (2)
ghost/core/core/frontend/web/routers/serve-public-file.js (2)

119-137: Theme fallback for sitemap.xsl is correctly implemented.

The handler properly checks for a theme-provided sitemap.xsl using fs.access and calls next() to delegate to staticTheme middleware when the file exists. This addresses the concern from the past review comment—the implementation does support theme overrides.


159-181: Theme and privacy handling for robots.txt is correctly implemented.

The handler correctly prioritizes privacy settings (is_private) and then checks for theme-provided robots.txt, calling next() to delegate to downstream middleware when appropriate. This addresses the concern from the past review comment—theme overrides are properly supported.

🧹 Nitpick comments (1)
ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js (1)

4-4: Import path correctly updated.

The destructured import from the new router module location is correct. Consider whether this test file should be relocated from middleware/ to routers/ to better reflect the new module structure, though this is a minor organizational detail that can be deferred.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad7cad3 and c3c22a7.

📒 Files selected for processing (5)
  • ghost/core/content/themes/source (1 hunks)
  • ghost/core/core/frontend/web/middleware/index.js (0 hunks)
  • ghost/core/core/frontend/web/routers/serve-public-file.js (2 hunks)
  • ghost/core/core/frontend/web/site.js (2 hunks)
  • ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js (1 hunks)
💤 Files with no reviewable changes (1)
  • ghost/core/core/frontend/web/middleware/index.js
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.
📚 Learning: 2025-05-29T07:45:35.714Z
Learnt from: ErisDS
Repo: TryGhost/Ghost PR: 23582
File: ghost/core/.c8rc.json:24-24
Timestamp: 2025-05-29T07:45:35.714Z
Learning: In Ghost project, app.js files under core/server/web are intentionally excluded from unit test coverage because they are not easily unit-testable due to being entry points with initialization code and side effects.

Applied to files:

  • ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js
📚 Learning: 2025-04-23T15:44:52.549Z
Learnt from: 9larsons
Repo: TryGhost/Ghost PR: 21866
File: ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js:10-19
Timestamp: 2025-04-23T15:44:52.549Z
Learning: The existing implementation in `ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js` using `path.parse(req.url).base` is secure against path traversal attacks as it properly extracts only the filename component without any directory parts.

Applied to files:

  • ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js
  • ghost/core/core/frontend/web/routers/serve-public-file.js
  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Admin-x React apps build to `apps/*/dist` using Vite, which are then copied by `ghost/admin/lib/asset-delivery` to `ghost/core/core/built/admin/assets/*`

Applied to files:

  • ghost/core/core/frontend/web/routers/serve-public-file.js
  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Ghost admin serves admin-x apps from `/ghost/assets/{app-name}/{app-name}.js` URLs

Applied to files:

  • ghost/core/core/frontend/web/routers/serve-public-file.js
  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Applies to ghost/i18n/locales/en/{portal,comments,signup-form,search}.json : Add UI translations to namespace-specific files in `ghost/i18n/locales/en/` for public UI apps (portal.json for Portal, comments.json for Comments UI, etc.)

Applied to files:

  • ghost/core/core/frontend/web/routers/serve-public-file.js
  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Core Ghost backend logic should be placed in `ghost/core/core/server/` directory structure organized by purpose (api, services, models, data/schema, etc.)

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: API routes should be defined in `ghost/core/core/server/api/`

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Public app configuration should be passed via data attributes in `<script>` tags injected by `{{ghost_head}}`

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Public UI apps (Portal, Comments UI, Signup Form, Sodo Search, Announcement Bar) should be built as UMD bundles for CDN distribution

Applied to files:

  • ghost/core/core/frontend/web/site.js
🧬 Code graph analysis (2)
ghost/core/core/frontend/web/routers/serve-public-file.js (1)
ghost/core/core/frontend/web/site.js (3)
  • require (5-5)
  • themeEngine (14-14)
  • servePublicFiles (13-13)
ghost/core/core/frontend/web/site.js (3)
ghost/core/core/frontend/web/routers/serve-public-file.js (1)
  • require (8-8)
ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js (1)
  • require (4-4)
ghost/core/core/frontend/helpers/ghost_head.js (5)
  • require (5-5)
  • require (6-6)
  • require (7-7)
  • require (10-10)
  • require (16-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Setup
  • GitHub Check: Setup
  • GitHub Check: Build & Push
🔇 Additional comments (6)
ghost/core/content/themes/source (1)

1-1: Submodule pointer update looks fine

This file only updates the tracked commit for the source submodule. There’s no executable code to review here; as long as this SHA corresponds to the intended theme revision and CI passes with it, this change is good to go.

ghost/core/core/frontend/web/site.js (1)

13-13: LGTM - Clean consolidation of public file serving.

The refactor from individual middleware calls to a centralized servePublicFiles(siteApp) function call is well-structured. The new router module handles theme fallback logic internally, so the registration order before staticTheme() is intentional and correct—the route handlers check for theme-provided files and call next() when appropriate to defer to staticTheme.

Also applies to: 68-69

ghost/core/core/frontend/web/routers/serve-public-file.js (4)

8-10: Appropriate imports for the new functionality.

The new imports are correctly scoped for the theme-aware and privacy-aware routing logic.


140-155: Well-structured public asset route registrations.

The explicit route registrations for static assets are clear and follow a consistent pattern. The card assets correctly chain cardAssets.serveMiddleware() to ensure on-the-fly building before serving.


156-157: Correct cache configuration for recommendations.json.

Using disableServerCache: true is appropriate for this endpoint since recommendations can change dynamically.


184-186: Export structure supports both usage patterns.

The default export for servePublicFiles enables clean imports in site.js, while named exports for servePublicFile and createPublicFileMiddleware maintain backward compatibility for existing tests and potential internal usage.

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.

2 participants