From 36c1e0697da9fdc453a7a9a3c84e0e79cd0cb376 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 18 Dec 2024 14:01:56 +0000 Subject: [PATCH] fix: clear the content layer cache when the Astro config changes (#12767) * fix: clear the content layer cache when the Astro config changes * Use deterministic-object-hash * Switch back to safe-stringify * Whitespace --- .changeset/shaggy-dancers-run.md | 5 ++++ packages/astro/src/content/content-layer.ts | 26 ++++++++++++++++--- packages/astro/src/content/utils.ts | 23 ++++++++++++++++ packages/astro/test/content-layer.test.js | 14 +++++++++- .../fixtures/content-layer/astro.config.mjs | 3 ++- 5 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 .changeset/shaggy-dancers-run.md diff --git a/.changeset/shaggy-dancers-run.md b/.changeset/shaggy-dancers-run.md new file mode 100644 index 000000000000..e95c16dd7be1 --- /dev/null +++ b/.changeset/shaggy-dancers-run.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Clears the content layer cache when the Astro config is changed diff --git a/packages/astro/src/content/content-layer.ts b/packages/astro/src/content/content-layer.ts index e8c772a2c98f..c0395295367b 100644 --- a/packages/astro/src/content/content-layer.ts +++ b/packages/astro/src/content/content-layer.ts @@ -19,6 +19,7 @@ import { getEntryConfigByExtMap, getEntryDataAndImages, globalContentConfigObserver, + safeStringify, } from './utils.js'; export interface ContentLayerOptions { @@ -87,7 +88,7 @@ export class ContentLayer { // It uses wasm, so we need to load it asynchronously. const { h64ToString } = await xxhash(); - this.#generateDigest = (data: Record | string) => { + this.#generateDigest = (data: unknown) => { const dataString = typeof data === 'string' ? data : JSON.stringify(data); return h64ToString(dataString); }; @@ -143,12 +144,28 @@ export class ContentLayer { } logger.info('Syncing content'); + const { + vite: _vite, + integrations: _integrations, + adapter: _adapter, + ...hashableConfig + } = this.#settings.config; + + const astroConfigDigest = safeStringify(hashableConfig); + const { digest: currentConfigDigest } = contentConfig.config; this.#lastConfigDigest = currentConfigDigest; let shouldClear = false; - const previousConfigDigest = await this.#store.metaStore().get('config-digest'); + const previousConfigDigest = await this.#store.metaStore().get('content-config-digest'); + const previousAstroConfigDigest = await this.#store.metaStore().get('astro-config-digest'); const previousAstroVersion = await this.#store.metaStore().get('astro-version'); + + if (previousAstroConfigDigest && previousAstroConfigDigest !== astroConfigDigest) { + logger.info('Astro config changed'); + shouldClear = true; + } + if (currentConfigDigest && previousConfigDigest !== currentConfigDigest) { logger.info('Content config changed'); shouldClear = true; @@ -165,7 +182,10 @@ export class ContentLayer { await this.#store.metaStore().set('astro-version', process.env.ASTRO_VERSION); } if (currentConfigDigest) { - await this.#store.metaStore().set('config-digest', currentConfigDigest); + await this.#store.metaStore().set('content-config-digest', currentConfigDigest); + } + if (astroConfigDigest) { + await this.#store.metaStore().set('astro-config-digest', astroConfigDigest); } await Promise.all( Object.entries(contentConfig.config.collections).map(async ([name, collection]) => { diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index 3b9588c0b674..32d53f5db819 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -832,3 +832,26 @@ export function contentModuleToId(fileName: string) { params.set(CONTENT_MODULE_FLAG, 'true'); return `${DEFERRED_MODULE}?${params.toString()}`; } + +// Based on https://github.com/sindresorhus/safe-stringify +function safeStringifyReplacer(seen: WeakSet) { + return function (_key: string, value: unknown) { + if (!(value !== null && typeof value === 'object')) { + return value; + } + if (seen.has(value)) { + return '[Circular]'; + } + seen.add(value); + const newValue = Array.isArray(value) ? [] : {}; + for (const [key2, value2] of Object.entries(value)) { + (newValue as Record)[key2] = safeStringifyReplacer(seen)(key2, value2); + } + seen.delete(value); + return newValue; + }; +} +export function safeStringify(value: unknown) { + const seen = new WeakSet(); + return JSON.stringify(value, safeStringifyReplacer(seen)); +} diff --git a/packages/astro/test/content-layer.test.js b/packages/astro/test/content-layer.test.js index 41d2f0fce692..0a7e2b289297 100644 --- a/packages/astro/test/content-layer.test.js +++ b/packages/astro/test/content-layer.test.js @@ -288,7 +288,7 @@ describe('Content Layer', () => { assert.equal(newJson.entryWithReference.data.something?.content, 'transform me'); }); - it('clears the store on new build if the config has changed', async () => { + it('clears the store on new build if the content config has changed', async () => { let newJson = devalue.parse(await fixture.readFile('/collections.json')); assert.equal(newJson.increment.data.lastValue, 1); await fixture.editFile('src/content.config.ts', (prev) => { @@ -299,6 +299,18 @@ describe('Content Layer', () => { assert.equal(newJson.increment.data.lastValue, 1); await fixture.resetAllFiles(); }); + + it('clears the store on new build if the Astro config has changed', async () => { + let newJson = devalue.parse(await fixture.readFile('/collections.json')); + assert.equal(newJson.increment.data.lastValue, 1); + await fixture.editFile('astro.config.mjs', (prev) => { + return prev.replace('Astro content layer', 'Astro more content layer'); + }); + await fixture.build(); + newJson = devalue.parse(await fixture.readFile('/collections.json')); + assert.equal(newJson.increment.data.lastValue, 1); + await fixture.resetAllFiles(); + }); }); describe('Dev', () => { diff --git a/packages/astro/test/fixtures/content-layer/astro.config.mjs b/packages/astro/test/fixtures/content-layer/astro.config.mjs index 6b56f41fd311..06f4f45d99ec 100644 --- a/packages/astro/test/fixtures/content-layer/astro.config.mjs +++ b/packages/astro/test/fixtures/content-layer/astro.config.mjs @@ -3,7 +3,8 @@ import { defineConfig } from 'astro/config'; import { fileURLToPath } from 'node:url'; export default defineConfig({ - integrations: [mdx(), { + name: 'Astro content layer', + integrations: [mdx(), { name: '@astrojs/my-integration', hooks: { 'astro:server:setup': async ({ server, refreshContent }) => {