From 39e9c72a70339ecd8a67bb0fadb68ba4a88ab590 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 11:43:15 -0700 Subject: [PATCH 1/3] Inject pageUrl into page vars automatically Every page now gets a pageUrl derived from pageInfo.path so layouts can build canonical URLs without manually computing the path-to-URL conversion. User-supplied pageUrl in pageVars or globalVars still takes precedence. Closes #238 --- lib/build-pages/page-data.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/build-pages/page-data.js b/lib/build-pages/page-data.js index 0cafe0f..b52d726 100644 --- a/lib/build-pages/page-data.js +++ b/lib/build-pages/page-data.js @@ -141,6 +141,7 @@ export class PageData { const { globalVars, globalDataVars, pageVars, builderVars } = this // @ts-ignore return { + pageUrl: this.pageInfo.path ? `/${this.pageInfo.path}/` : '/', ...globalVars, ...globalDataVars, ...pageVars, From cd323d7c5fb7809beabaaade62a59c85ae54e1cf Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 11:58:40 -0700 Subject: [PATCH 2/3] Fix pageUrl for loose markdown pages and Windows path separators Loose markdown files (not page.md/README.md) have outputName set to their slug.html rather than index.html, so the previous formula produced an incorrect directory-style URL for them. The URL is now derived from outputName: index pages get a trailing slash, non-index pages include the filename. fsPathToUrlPath is used to normalize any OS-specific path separators. --- lib/build-pages/page-data.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/build-pages/page-data.js b/lib/build-pages/page-data.js index b52d726..d380768 100644 --- a/lib/build-pages/page-data.js +++ b/lib/build-pages/page-data.js @@ -6,6 +6,7 @@ import { resolveVars, resolvePostVars } from './resolve-vars.js' import { pageBuilders } from './page-builders/index.js' +import { fsPathToUrlPath } from './page-builders/fs-path-to-url.js' // @ts-expect-error import pretty from 'pretty' @@ -141,7 +142,7 @@ export class PageData { const { globalVars, globalDataVars, pageVars, builderVars } = this // @ts-ignore return { - pageUrl: this.pageInfo.path ? `/${this.pageInfo.path}/` : '/', + pageUrl: this.#computePageUrl(), ...globalVars, ...globalDataVars, ...pageVars, @@ -157,6 +158,19 @@ export class PageData { return this.workerFiles } + /** + * Derive the canonical URL path for this page from its filesystem path and output name. + * Index pages get a trailing-slash URL; other outputs include the filename. + * @return {string} + */ + #computePageUrl () { + const { path, outputName } = this.pageInfo + if (outputName === 'index.html') { + return path ? fsPathToUrlPath(path) + '/' : '/' + } + return path ? fsPathToUrlPath(path) + '/' + outputName : '/' + outputName + } + /** * [init description] * @param {object} params - Parameters required to initialize From 10a928485138760be263deb6a4e45cbd4053ab5b Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 19:49:55 -0700 Subject: [PATCH 3/3] Extract computePageUrl helper and add unit tests for URL derivation rules Co-Authored-By: Claude Sonnet 4.6 --- lib/build-pages/compute-page-url.js | 17 +++++++++++++++++ lib/build-pages/page-data.js | 8 ++------ lib/build-pages/page-data.test.js | 21 +++++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 lib/build-pages/compute-page-url.js create mode 100644 lib/build-pages/page-data.test.js diff --git a/lib/build-pages/compute-page-url.js b/lib/build-pages/compute-page-url.js new file mode 100644 index 0000000..94f3f01 --- /dev/null +++ b/lib/build-pages/compute-page-url.js @@ -0,0 +1,17 @@ +import { fsPathToUrlPath } from './page-builders/fs-path-to-url.js' + +/** + * Derive the canonical URL path for a page from its filesystem path and output name. + * Index pages get a trailing-slash URL; other outputs include the filename. + * + * @param {object} params + * @param {string} params.path - The page's directory path relative to src root + * @param {string} params.outputName - The output filename (e.g. 'index.html' or 'loose-md.html') + * @returns {string} + */ +export function computePageUrl ({ path, outputName }) { + if (outputName === 'index.html') { + return path ? fsPathToUrlPath(path) + '/' : '/' + } + return path ? fsPathToUrlPath(path) + '/' + outputName : '/' + outputName +} diff --git a/lib/build-pages/page-data.js b/lib/build-pages/page-data.js index d380768..af078b9 100644 --- a/lib/build-pages/page-data.js +++ b/lib/build-pages/page-data.js @@ -6,7 +6,7 @@ import { resolveVars, resolvePostVars } from './resolve-vars.js' import { pageBuilders } from './page-builders/index.js' -import { fsPathToUrlPath } from './page-builders/fs-path-to-url.js' +import { computePageUrl } from './compute-page-url.js' // @ts-expect-error import pretty from 'pretty' @@ -164,11 +164,7 @@ export class PageData { * @return {string} */ #computePageUrl () { - const { path, outputName } = this.pageInfo - if (outputName === 'index.html') { - return path ? fsPathToUrlPath(path) + '/' : '/' - } - return path ? fsPathToUrlPath(path) + '/' + outputName : '/' + outputName + return computePageUrl(this.pageInfo) } /** diff --git a/lib/build-pages/page-data.test.js b/lib/build-pages/page-data.test.js new file mode 100644 index 0000000..3daf573 --- /dev/null +++ b/lib/build-pages/page-data.test.js @@ -0,0 +1,21 @@ +import { test } from 'node:test' +import assert from 'node:assert' +import { computePageUrl } from './compute-page-url.js' + +test.describe('computePageUrl', () => { + test('root index.html maps to /', () => { + assert.strictEqual(computePageUrl({ path: '', outputName: 'index.html' }), '/') + }) + + test('nested index.html gets a trailing-slash URL', () => { + assert.strictEqual(computePageUrl({ path: 'blog/post', outputName: 'index.html' }), '/blog/post/') + }) + + test('non-index output includes filename in URL', () => { + assert.strictEqual(computePageUrl({ path: 'md-page', outputName: 'loose-md.html' }), '/md-page/loose-md.html') + }) + + test('non-index file at root includes filename only', () => { + assert.strictEqual(computePageUrl({ path: '', outputName: 'robots.txt' }), '/robots.txt') + }) +})