diff --git a/e2e/e2e.test.mts b/e2e/e2e.test.mts index 55ea64c..05d04b1 100644 --- a/e2e/e2e.test.mts +++ b/e2e/e2e.test.mts @@ -277,9 +277,17 @@ describe('middleware (hybrid)', () => { beforeAll(async () => { await execFile('pnpm', ['install'], execOpts) await execFile('pnpm', ['run', 'clean'], execOpts) - const { stdout: buildStdout } = await execFile('pnpm', ['run', 'build'], execOpts) + const { stdout: buildStdout } = await execFile( + 'pnpm', + ['run', 'build'], + execOpts, + ) expect(buildStdout).toMatch(/run the build step again/) - const { stdout: buildStdout2 } = await execFile('pnpm', ['run', 'build'], execOpts) + const { stdout: buildStdout2 } = await execFile( + 'pnpm', + ['run', 'build'], + execOpts, + ) expect(buildStdout2).not.toMatch(/run the build step again/) }) @@ -317,6 +325,8 @@ describe('middleware (hybrid)', () => { } it('patches inline resources for dynamically generated pages referring static resources', async () => { - await checkHtmlIsPatched('/', { '/code.js': 'sha256-X7QGGDHgf6XMoabXvV9pW7gl3ALyZhZlgKq1s3pwmME=' }) + await checkHtmlIsPatched('/', { + '/code.js': 'sha256-X7QGGDHgf6XMoabXvV9pW7gl3ALyZhZlgKq1s3pwmME=', + }) }) }) diff --git a/package.json b/package.json index 407957d..a5392f4 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,7 @@ "default": "./src/state.mjs" } }, - "files": [ - "src/*" - ], + "files": ["src/*"], "scripts": { "format": "biome format --write .", "install-githooks": "if [ -d .git ]; then git config core.hooksPath .hooks; fi", diff --git a/src/core.mjs b/src/core.mjs index 927d540..62371ac 100644 --- a/src/core.mjs +++ b/src/core.mjs @@ -485,10 +485,17 @@ export const scanForNestedResources = async (logger, dirPath, h) => { } /** + * @param {Logger} logger * @param {HashesCollection} h * @param {string} sriHashesModule + * @param {boolean} enableMiddleware_SRI */ -export async function generateSRIHashesModule(h, sriHashesModule) { +export async function generateSRIHashesModule( + logger, + h, + sriHashesModule, + enableMiddleware_SRI, +) { let persistHashes = false const inlineScriptHashes = Array.from(h.inlineScriptHashes).sort() @@ -539,6 +546,12 @@ export async function generateSRIHashesModule(h, sriHashesModule) { } if (persistHashes) { + if (enableMiddleware_SRI) { + logger.warn( + 'SRI hashes have changed for static resources that may be used in dynamic pages. You should run the build step again', + ) + } + let hashesFileContent = '// Do not edit this file manually\n\n' hashesFileContent += `export const inlineScriptHashes = /** @type {string[]} */ ([${inlineScriptHashes .map(h => `\n\t'${h}',`) @@ -589,7 +602,7 @@ export async function generateSRIHashesModule(h, sriHashesModule) { */ export const processStaticFiles = async ( logger, - { distDir, sriHashesModule }, + { distDir, sriHashesModule, enableMiddleware_SRI }, ) => { const h = /** @satisfies {HashesCollection} */ { inlineScriptHashes: new Set(), @@ -618,7 +631,12 @@ export const processStaticFiles = async ( return } - await generateSRIHashesModule(h, sriHashesModule) + await generateSRIHashesModule( + logger, + h, + sriHashesModule, + enableMiddleware_SRI, + ) } /** @@ -673,23 +691,23 @@ const loadVirtualMiddlewareModule = async ( sriHashesModule && (await doesFileExist(sriHashesModule)) ) { - extraImports = `import * as hashesModule from '${sriHashesModule}'` + extraImports = `import { perResourceSriHashes } from '${sriHashesModule}'` staticHashesModuleLoader = ` try { - if (hashesModule.perResourceSriHashes) { + if (perResourceSriHashes) { for (const [key, value] of Object.entries( - hashesModule.perResourceSriHashes?.scripts ?? {}, + perResourceSriHashes.scripts ?? {}, )) { globalHashes.scripts.set(key, value) } for (const [key, value] of Object.entries( - hashesModule.perResourceSriHashes?.styles ?? {}, + perResourceSriHashes.styles ?? {}, )) { globalHashes.styles.set(key, value) } } } catch (err) { - console.warn('Failed to load static hashes module:', err) + console.error('Failed to load static hashes module:', err) } ` } else if (enableStatic_SRI && sriHashesModule) { @@ -725,7 +743,7 @@ const loadVirtualHashesModule = async (enableStatic_SRI, sriHashesModule) => { sriHashesModule && (await doesFileExist(sriHashesModule)) ) { - return `export * from '${sriHashesModule}'` + return await readFile(sriHashesModule, 'utf8') } return ` export const inlineScriptHashes = [] diff --git a/src/main.d.ts b/src/main.d.ts index 81d3286..7676584 100644 --- a/src/main.d.ts +++ b/src/main.d.ts @@ -36,7 +36,10 @@ export type ShieldOptions = { */ sriHashesModule?: string | undefined } -export type StrictShieldOptions = ShieldOptions & { distDir: string } +export type StrictShieldOptions = ShieldOptions & { + distDir: string + enableMiddleware_SRI: boolean +} // Main Integration // ----------------------------------------------------------------------------- diff --git a/src/main.mjs b/src/main.mjs index ffaf5e0..b4b980c 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -24,21 +24,29 @@ export const shield = ({ enableStatic_SRI, sriHashesModule, }) => { - const astroBuildDone = - /** @satisfies {AstroHooks['astro:build:done']} */ async ({ - dir, - logger, - }) => + /** + * @param {boolean} enableMiddleware_SRI + * @returns {NonNullable} + */ + const getAstroBuildDone = + enableMiddleware_SRI => + /** @satisfies {NonNullable} */ + async ({ dir, logger }) => await processStaticFiles(logger, { distDir: fileURLToPath(dir), sriHashesModule, + enableMiddleware_SRI, }) return /** @satisfies {AstroIntegration} */ { name: '@kindspells/astro-shield', hooks: { ...((enableStatic_SRI ?? true) === true - ? { 'astro:build:done': astroBuildDone } + ? { + 'astro:build:done': getAstroBuildDone( + enableMiddleware_SRI ?? false, + ), + } : undefined), ...(enableMiddleware_SRI === true ? { diff --git a/tests/core.test.mts b/tests/core.test.mts index 4166bcd..e13dd08 100644 --- a/tests/core.test.mts +++ b/tests/core.test.mts @@ -362,7 +362,7 @@ describe('updateStaticPageSriHashes', () => { My Test Page - + ` @@ -379,7 +379,7 @@ describe('updateStaticPageSriHashes', () => { expect(h.extScriptHashes.size).toBe(1) expect( h.extScriptHashes.has( - 'sha256-etOR/kKV9aCSESe7t5JeBixVQA1DjUU2Zxk13wsPU8M=', + 'sha256-KnjtswtmvdHQSShp8mURE9kt/62bvYGd5jCdjmbFDiI=', ), ).toBe(true) expect(h.inlineScriptHashes.size).toBe(0) @@ -892,7 +892,7 @@ describe('generateSRIHashesModule', () => { expect(await doesFileExist(modulePath)).toBe(false) const h = getEmptyHashes() - await generateSRIHashesModule(h, modulePath) + await generateSRIHashesModule(console, h, modulePath, false) expect(await doesFileExist(modulePath)).toBe(true)