diff --git a/.changeset/old-boxes-invite.md b/.changeset/old-boxes-invite.md new file mode 100644 index 000000000000..bbba9f632849 --- /dev/null +++ b/.changeset/old-boxes-invite.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes nondeterministic ordering of the server manifest's `assets` array, ensuring builds with identical inputs produce byte-identical output diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 282d666e7001..af5acec6db1e 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -427,7 +427,7 @@ async function buildManifest( clientDirectives: Array.from(settings.clientDirectives), entryModules, inlinedScripts: Array.from(internals.inlinedScripts), - assets: staticFiles.map(prefixAssetPath), + assets: staticFiles.map(prefixAssetPath).sort(), i18n: i18nManifest, buildFormat: settings.config.build.format, checkOrigin: diff --git a/packages/astro/test/units/build/plugin-manifest.test.ts b/packages/astro/test/units/build/plugin-manifest.test.ts index e3ffb1043ebe..0e41cbec2f4d 100644 --- a/packages/astro/test/units/build/plugin-manifest.test.ts +++ b/packages/astro/test/units/build/plugin-manifest.test.ts @@ -41,6 +41,102 @@ function enableSsrMinification(): Plugin { } describe('Build: Manifest injection', () => { + it('serializes manifest assets in sorted order', async () => { + const root = new URL('./_temp-fixtures/', import.meta.url); + const rootPath = fileURLToPath(root); + + // Create public files in multiple directories to seed the assets array + const publicDir = path.join(rootPath, 'public'); + for (const dir of ['zeta', 'alpha', 'mu']) { + const dirPath = path.join(publicDir, dir); + await fs.mkdir(dirPath, { recursive: true }); + for (let i = 1; i <= 3; i++) { + await fs.writeFile(path.join(dirPath, `f${i}.txt`), `${dir} ${i}`); + } + } + + const settings = await createBasicSettings({ + root: rootPath, + output: 'server', + adapter: { + name: 'test-adapter', + hooks: { + 'astro:config:done': ({ setAdapter }) => { + setAdapter({ + name: 'test-adapter', + serverEntrypoint: 'astro/app', + exports: ['manifest', 'createApp'], + supportedAstroFeatures: { + serverOutput: 'stable', + }, + adapterFeatures: { + buildOutput: 'server', + }, + }); + }, + }, + }, + vite: { + plugins: [ + virtualAstroModules(root, { + 'src/pages/index.astro': [ + '---', + '---', + '', + '