Skip to content
Open
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
72 changes: 72 additions & 0 deletions lib/build-pages/page-data-vars-catch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { test } from 'node:test'
import assert from 'node:assert'
import { mkdtemp, writeFile, rm } from 'node:fs/promises'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import { PageData } from './page-data.js'

const fakeLayout = {
name: 'default',
render: async (/** @type {any} */ { children }) => String(children),
layoutStylePath: null,
layoutClientPath: null,
}

test.describe('PageData.vars catch block wrapping', () => {
test('wraps error from spread merge with page path and preserves cause', async () => {
const dir = await mkdtemp(join(tmpdir(), 'domstack-pagedata-test-'))
const mdFile = join(dir, 'test.md')

try {
await writeFile(mdFile, '# Test\n\nContent.')

const pd = new PageData({
pageInfo: /** @type {any} */ ({
path: 'blog/post',
outputName: 'index.html',
type: 'md',
pageFile: { filepath: mdFile },
}),
globalVars: { layout: 'default' },
globalStyle: undefined,
globalClient: undefined,
defaultStyle: null,
defaultClient: null,
builderOptions: /** @type {any} */ ({}),
})

await pd.init({ layouts: { default: fakeLayout } })

// After init, replace pageVars with a proxy that throws on property access
// to trigger the catch block in the vars getter.
const originalError = new Error('getter exploded')
pd.pageVars = new Proxy({}, {
ownKeys: () => ['exploding-key'],
getOwnPropertyDescriptor: () => ({ enumerable: true, configurable: true, writable: true, value: undefined }),
get (_target, key) {
if (typeof key === 'symbol') return undefined
throw originalError
},
})

assert.throws(
() => pd.vars,
(err) => {
assert.ok(err instanceof Error, 'throws an Error')
assert.ok(
err.message.includes('blog/post'),
`message should include page path, got: "${err.message}"`
)
assert.ok(
err.message.includes('getter exploded'),
`message should include original error message, got: "${err.message}"`
)
assert.strictEqual(err.cause, originalError, 'err.cause should be the original error')
return true
}
)
} finally {
await rm(dir, { recursive: true, force: true })
}
})
})
23 changes: 15 additions & 8 deletions lib/build-pages/page-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,21 @@ export class PageData {
* @return {T} globalVars, pageVars, and buildVars merged together
*/
get vars () {
if (!this.#initialized) throw new Error('Initialize PageData before accessing vars')
const { globalVars, globalDataVars, pageVars, builderVars } = this
// @ts-ignore
return {
...globalVars,
...globalDataVars,
...pageVars,
...builderVars,
if (!this.#initialized) throw new Error(`Initialize PageData before accessing vars for page "${this.pageInfo?.path ?? '<unknown page>'}"`)
try {
const { globalVars, globalDataVars, pageVars, builderVars } = this
// @ts-ignore
return {
...globalVars,
...globalDataVars,
...pageVars,
...builderVars,
}
} catch (err) {
throw new Error(
`Failed to resolve vars for page "${this.pageInfo?.path ?? '<unknown page>'}": ${err instanceof Error ? err.message : String(err)}`,
{ cause: err }
)
Comment thread
bcomnes marked this conversation as resolved.
}
}

Expand Down
53 changes: 53 additions & 0 deletions lib/build-pages/page-data.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { test } from 'node:test'
import assert from 'node:assert'
import { PageData } from './page-data.js'

test.describe('PageData.vars', () => {
test('throws with page path before initialization', () => {
const pd = new PageData({
pageInfo: /** @type {any} */ ({ path: 'blog/test-post' }),
Comment thread
bcomnes marked this conversation as resolved.
globalVars: {},
globalStyle: undefined,
globalClient: undefined,
defaultStyle: null,
defaultClient: null,
builderOptions: /** @type {any} */ ({}),
})

assert.throws(
() => pd.vars,
(err) => {
assert.ok(err instanceof Error, 'throws an Error')
assert.ok(
err.message.includes('blog/test-post'),
`error message should include the page path, got: "${err.message}"`
)
return true
}
)
})

test('error message includes unknown page fallback when pageInfo has no path', () => {
const pd = new PageData({
pageInfo: /** @type {any} */ ({}),
globalVars: {},
globalStyle: undefined,
globalClient: undefined,
defaultStyle: null,
defaultClient: null,
builderOptions: /** @type {any} */ ({}),
})

assert.throws(
() => pd.vars,
(err) => {
assert.ok(err instanceof Error, 'throws an Error')
assert.ok(
err.message.includes('<unknown page>'),
`error message should include fallback text, got: "${err.message}"`
)
return true
}
)
})
})