-
-
Notifications
You must be signed in to change notification settings - Fork 11.4k
Replaced cdnUrl config with script-origin asset base derivation #26555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rob-ghost
wants to merge
1
commit into
main
Choose a base branch
from
feat/ghost-cdn-url-meta-tag
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+174
−33
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import ghostPaths from 'ghost-admin/utils/ghost-paths'; | ||
|
|
||
| let _assetBase = null; | ||
|
|
||
| /** | ||
| * Resolve the asset base URL from script elements in the given document root. | ||
| * Exported for direct testing — callers should use the default export instead. | ||
| * | ||
| * @param {Document} doc The document to search for script tags | ||
| * @returns {string} Absolute URL with trailing slash | ||
| */ | ||
| export function resolveAssetBase(doc) { | ||
| // Find the Ember app script — its src tells us where assets are served from. | ||
| // The browser always resolves script.src to an absolute URL. | ||
| // Matches both non-fingerprinted (ghost.js) and fingerprinted (ghost-{hash}.js). | ||
| const script = doc.querySelector('script[src*="assets/ghost"]'); | ||
|
|
||
| if (script && script.src) { | ||
| try { | ||
| const url = new URL(script.src); | ||
| const assetsIdx = url.pathname.indexOf('/assets/'); | ||
| if (assetsIdx > 0) { | ||
| return `${url.origin}${url.pathname.substring(0, assetsIdx)}/`; | ||
| } | ||
| } catch (e) { | ||
| // Fall through to ghostPaths | ||
| } | ||
| } | ||
|
|
||
| // Fallback: absolute URL from the current origin so new URL() never throws | ||
| return `${window.location.origin}${ghostPaths().adminRoot}`; | ||
| } | ||
|
|
||
| /** | ||
| * Derives the asset base URL from where the Ember scripts were loaded. | ||
| * If loaded from a CDN, returns the CDN base. If local, returns the admin root. | ||
| * | ||
| * Always returns an absolute URL (with origin and trailing slash) so callers | ||
| * can safely pass the result to `new URL()`. Examples: | ||
| * CDN: "https://assets.ghost.io/admin-forward/" | ||
| * Local: "http://localhost:2368/ghost/" | ||
| */ | ||
| export default function assetBase() { | ||
| if (_assetBase !== null) { | ||
| return _assetBase; | ||
| } | ||
|
|
||
| _assetBase = resolveAssetBase(document); | ||
| return _assetBase; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import {describe, it} from 'mocha'; | ||
| import {expect} from 'chai'; | ||
| import {resolveAssetBase} from 'ghost-admin/utils/asset-base'; | ||
|
|
||
| /** | ||
| * Create a minimal document fragment containing a <script> tag with the | ||
| * given src. Uses a real DOMParser so the querySelector logic in | ||
| * resolveAssetBase runs against actual DOM nodes. | ||
| */ | ||
| function docWithScript(src) { | ||
| const doc = document.implementation.createHTMLDocument('test'); | ||
| const script = doc.createElement('script'); | ||
| script.src = src; | ||
| doc.body.appendChild(script); | ||
| return doc; | ||
| } | ||
|
|
||
| function emptyDoc() { | ||
| return document.implementation.createHTMLDocument('test'); | ||
| } | ||
|
|
||
| describe('Unit: Util: asset-base', function () { | ||
| describe('script detection', function () { | ||
| it('extracts base from a non-fingerprinted script (ghost.js)', function () { | ||
| const doc = docWithScript('http://localhost:2368/ghost/assets/ghost.js'); | ||
| const result = resolveAssetBase(doc); | ||
|
|
||
| expect(result).to.equal('http://localhost:2368/ghost/'); | ||
| }); | ||
|
|
||
| it('extracts base from a fingerprinted script (ghost-{hash}.js)', function () { | ||
| const doc = docWithScript('http://127.0.0.1:2368/ghost/assets/ghost-a1b2c3d4e5.js'); | ||
| const result = resolveAssetBase(doc); | ||
|
|
||
| expect(result).to.equal('http://127.0.0.1:2368/ghost/'); | ||
| }); | ||
|
|
||
| it('extracts CDN origin from a CDN-hosted script', function () { | ||
| const doc = docWithScript('https://cdn.example.com/admin-forward/assets/ghost-a1b2c3d4e5.js'); | ||
| const result = resolveAssetBase(doc); | ||
|
|
||
| expect(result).to.equal('https://cdn.example.com/admin-forward/'); | ||
| }); | ||
|
|
||
| it('handles a subdirectory install', function () { | ||
| const doc = docWithScript('http://example.com/blog/ghost/assets/ghost.js'); | ||
| const result = resolveAssetBase(doc); | ||
|
|
||
| expect(result).to.equal('http://example.com/blog/ghost/'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('fallback', function () { | ||
| it('falls back when no script is found', function () { | ||
| const result = resolveAssetBase(emptyDoc()); | ||
|
|
||
| expect(result).to.match(/^https?:\/\//); | ||
| expect(result).to.include('/ghost/'); | ||
| expect(result).to.match(/\/$/); | ||
| }); | ||
|
|
||
| it('falls back when script has no admin root prefix (test/dev environment)', function () { | ||
| // In the Ember test environment, ghost.js is served at /assets/ghost.js | ||
| // without the /ghost/ admin root prefix. The function should fall through | ||
| // to ghostPaths rather than returning a bare origin with no admin root. | ||
| const doc = docWithScript('/assets/ghost.js'); | ||
| const result = resolveAssetBase(doc); | ||
|
|
||
| expect(result).to.match(/^https?:\/\//); | ||
| expect(result).to.include('/ghost/'); | ||
| expect(result).to.match(/\/$/); | ||
| }); | ||
| }); | ||
|
|
||
| describe('new URL() safety', function () { | ||
| it('script-derived result works with new URL()', function () { | ||
| const doc = docWithScript('http://localhost:2368/ghost/assets/ghost-abc123.js'); | ||
| const base = resolveAssetBase(doc); | ||
|
|
||
| const koenigUrl = new URL(`${base}assets/koenig-lexical/koenig-lexical.umd.js`); | ||
| expect(koenigUrl.href).to.equal( | ||
| 'http://localhost:2368/ghost/assets/koenig-lexical/koenig-lexical.umd.js' | ||
| ); | ||
| }); | ||
|
|
||
| it('fallback result works with new URL()', function () { | ||
| const base = resolveAssetBase(emptyDoc()); | ||
|
|
||
| // This is the exact pattern used by fetchKoenigLexical and importComponent — | ||
| // a relative path is NOT valid input for new URL(), so the base must be absolute. | ||
| expect(() => new URL(`${base}assets/koenig-lexical/koenig-lexical.umd.js`)).to.not.throw(); | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.