From 53082a72f12a676905ef198478883e51398f1d40 Mon Sep 17 00:00:00 2001 From: Axel Date: Mon, 6 Jul 2026 13:37:25 +1000 Subject: [PATCH 1/8] scaffold app to test chunk rotation bug --- eslint.config.js | 1 + .../version-chunk-rotation/app/package.json | 22 ++++++++++++++ .../version-chunk-rotation/app/src/app.d.ts | 5 ++++ .../version-chunk-rotation/app/src/app.html | 11 +++++++ .../app/src/lib/shared.js | 6 ++++ .../app/src/routes/+page.svelte | 7 +++++ .../app/src/routes/about/+page.svelte | 7 +++++ .../app/svelte.config.js | 14 +++++++++ .../version-chunk-rotation/app/tsconfig.json | 8 +++++ .../version-chunk-rotation/app/vite.config.js | 23 ++++++++++++++ .../test/version-chunk-rotation/package.json | 13 ++++++++ pnpm-lock.yaml | 30 +++++++++++++++++++ pnpm-workspace.yaml | 2 ++ 13 files changed, 149 insertions(+) create mode 100644 packages/kit/test/version-chunk-rotation/app/package.json create mode 100644 packages/kit/test/version-chunk-rotation/app/src/app.d.ts create mode 100644 packages/kit/test/version-chunk-rotation/app/src/app.html create mode 100644 packages/kit/test/version-chunk-rotation/app/src/lib/shared.js create mode 100644 packages/kit/test/version-chunk-rotation/app/src/routes/+page.svelte create mode 100644 packages/kit/test/version-chunk-rotation/app/src/routes/about/+page.svelte create mode 100644 packages/kit/test/version-chunk-rotation/app/svelte.config.js create mode 100644 packages/kit/test/version-chunk-rotation/app/tsconfig.json create mode 100644 packages/kit/test/version-chunk-rotation/app/vite.config.js create mode 100644 packages/kit/test/version-chunk-rotation/package.json diff --git a/eslint.config.js b/eslint.config.js index 4c4aaefb917c..a55cb822d478 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -77,6 +77,7 @@ export default [ 'packages/kit/test/apps/**/*', 'packages/kit/test/build-errors/**/*', 'packages/kit/test/prerendering/**/*', + 'packages/kit/test/version-chunk-rotation/**/*', 'packages/test-redirect-importer/index.js', 'packages/adapter-netlify/test/preview.js' ] diff --git a/packages/kit/test/version-chunk-rotation/app/package.json b/packages/kit/test/version-chunk-rotation/app/package.json new file mode 100644 index 000000000000..a61bae71f858 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/package.json @@ -0,0 +1,22 @@ +{ + "name": "test-version-chunk-rotation-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "svelte-kit sync && vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync", + "check": "svelte-kit sync && tsc && svelte-check" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "workspace:^", + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "svelte": "catalog:", + "svelte-check": "catalog:", + "typescript": "catalog:", + "vite": "catalog:" + } +} diff --git a/packages/kit/test/version-chunk-rotation/app/src/app.d.ts b/packages/kit/test/version-chunk-rotation/app/src/app.d.ts new file mode 100644 index 000000000000..6285566846a0 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/src/app.d.ts @@ -0,0 +1,5 @@ +declare global { + namespace App {} +} + +export {}; diff --git a/packages/kit/test/version-chunk-rotation/app/src/app.html b/packages/kit/test/version-chunk-rotation/app/src/app.html new file mode 100644 index 000000000000..d533c5e31716 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/packages/kit/test/version-chunk-rotation/app/src/lib/shared.js b/packages/kit/test/version-chunk-rotation/app/src/lib/shared.js new file mode 100644 index 000000000000..15f94603b63a --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/src/lib/shared.js @@ -0,0 +1,6 @@ +export const sharedMessage = 'shared between routes'; + +/** @param {number} n */ +export function double(n) { + return n * 2; +} diff --git a/packages/kit/test/version-chunk-rotation/app/src/routes/+page.svelte b/packages/kit/test/version-chunk-rotation/app/src/routes/+page.svelte new file mode 100644 index 000000000000..731197e95f21 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/src/routes/+page.svelte @@ -0,0 +1,7 @@ + + +

home

+

{sharedMessage}: {double(21)}

+about diff --git a/packages/kit/test/version-chunk-rotation/app/src/routes/about/+page.svelte b/packages/kit/test/version-chunk-rotation/app/src/routes/about/+page.svelte new file mode 100644 index 000000000000..c4ca72613c2f --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/src/routes/about/+page.svelte @@ -0,0 +1,7 @@ + + +

about

+

{sharedMessage}: {double(50)}

+home diff --git a/packages/kit/test/version-chunk-rotation/app/svelte.config.js b/packages/kit/test/version-chunk-rotation/app/svelte.config.js new file mode 100644 index 000000000000..238a97f61d88 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/svelte.config.js @@ -0,0 +1,14 @@ +import adapter from '../../../../adapter-auto/index.js'; +import process from 'node:process'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter(), + version: { + name: process.env.SK_VERSION || 'control' + } + } +}; + +export default config; diff --git a/packages/kit/test/version-chunk-rotation/app/tsconfig.json b/packages/kit/test/version-chunk-rotation/app/tsconfig.json new file mode 100644 index 000000000000..b1096bf168cd --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/kit/test/version-chunk-rotation/app/vite.config.js b/packages/kit/test/version-chunk-rotation/app/vite.config.js new file mode 100644 index 000000000000..4b90a7655ee5 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/vite.config.js @@ -0,0 +1,23 @@ +import * as path from 'node:path'; +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + build: { + minify: false + }, + + clearScreen: false, + + logLevel: 'silent', + + plugins: [sveltekit()], + + server: { + fs: { + allow: [path.resolve('../../../src')] + } + } +}; + +export default config; diff --git a/packages/kit/test/version-chunk-rotation/package.json b/packages/kit/test/version-chunk-rotation/package.json new file mode 100644 index 000000000000..16fecb69ead9 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/package.json @@ -0,0 +1,13 @@ +{ + "name": "test-version-chunk-rotation", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "test": "vitest run", + "test:cross-platform:build": "vitest run" + }, + "devDependencies": { + "vitest": "catalog:" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 63592d05eb73..705514e3cc94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1392,6 +1392,36 @@ importers: specifier: 'catalog:' version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.9)(jsdom@26.1.0)(vite@6.4.3(@types/node@18.19.130)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.3)) + packages/kit/test/version-chunk-rotation: + devDependencies: + vitest: + specifier: 'catalog:' + version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.9)(jsdom@26.1.0)(vite@6.4.3(@types/node@18.19.130)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.3)) + + packages/kit/test/version-chunk-rotation/app: + devDependencies: + '@sveltejs/adapter-auto': + specifier: workspace:^ + version: link:../../../../adapter-auto + '@sveltejs/kit': + specifier: workspace:^ + version: link:../../.. + '@sveltejs/vite-plugin-svelte': + specifier: 'catalog:' + version: 6.2.4(svelte@5.56.3(@typescript-eslint/types@8.61.1))(vite@6.4.3(@types/node@18.19.130)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.3)) + svelte: + specifier: 'catalog:' + version: 5.56.3(@typescript-eslint/types@8.61.1) + svelte-check: + specifier: 'catalog:' + version: 4.4.6(picomatch@4.0.4)(svelte@5.56.3(@typescript-eslint/types@8.61.1))(typescript@6.0.2) + typescript: + specifier: 'catalog:' + version: 6.0.2 + vite: + specifier: 'catalog:' + version: 6.4.3(@types/node@18.19.130)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.3) + packages/package: dependencies: chokidar: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b59b301bd9cb..6c5f895622cd 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -25,6 +25,8 @@ packages: - packages/kit/test/prerendering/* - packages/kit/test/build-errors/** - packages/kit/test/build-errors/apps/* + - packages/kit/test/version-chunk-rotation + - packages/kit/test/version-chunk-rotation/app - '!.test-tmp/**' - playgrounds/* allowBuilds: From 407c963fa0ab15f320be452d11af7649e056a40c Mon Sep 17 00:00:00 2001 From: Axel Date: Mon, 6 Jul 2026 14:06:52 +1000 Subject: [PATCH 2/8] add classifiers and tests in prepartion for fix creating the test harness to validate client chunk rotation issue by testing chunk file names as well as chunk content --- .../test/version-chunk-rotation/classify.js | 88 +++++++++++++++++++ .../version-chunk-rotation/classify.spec.js | 72 +++++++++++++++ .../version-chunk-rotation/rotation.spec.js | 81 +++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 packages/kit/test/version-chunk-rotation/classify.js create mode 100644 packages/kit/test/version-chunk-rotation/classify.spec.js create mode 100644 packages/kit/test/version-chunk-rotation/rotation.spec.js diff --git a/packages/kit/test/version-chunk-rotation/classify.js b/packages/kit/test/version-chunk-rotation/classify.js new file mode 100644 index 000000000000..7a6b8386da09 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/classify.js @@ -0,0 +1,88 @@ +import { createHash } from 'node:crypto'; +import fs from 'node:fs'; +import path from 'node:path'; + +/** + * @typedef {object} Chunk + * @property {string} bytes_hash + * @property {boolean} contains_version + */ + +const VERSION_DERIVED_PAYLOAD_KEY = /__sveltekit_(?!dev\b)[A-Za-z0-9]+/; + +/** @param {string} code */ +const bytes_hash = (code) => createHash('sha256').update(code).digest('hex').slice(0, 12); + +/** + * @param {string} dir + * @param {string} [base] + * @param {string[]} [found] + * @returns {string[]} + */ +function js_files(dir, base = dir, found = []) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const abs = path.join(dir, entry.name); + if (entry.isDirectory()) js_files(abs, base, found); + else if (entry.name.endsWith('.js')) found.push(path.relative(base, abs)); + } + return found.sort(); +} + +/** + * @param {string} dir + * @param {string} version + * @returns {Map} + */ +export function snapshot(dir, version) { + /** @type {Map} */ + const chunks = new Map(); + + for (const filename of js_files(dir)) { + const code = fs.readFileSync(path.join(dir, filename), 'utf-8'); + + chunks.set(filename, { + bytes_hash: bytes_hash(code), + contains_version: code.includes(version) || VERSION_DERIVED_PAYLOAD_KEY.test(code) + }); + } + + return chunks; +} + +/** + * @param {Map} before + * @param {Map} after + */ +export function compare(before, after) { + /** @type {Array<{ name: string, contains_version: boolean }>} */ + const rotated = []; + /** @type {Array<{ name: string, contains_version: boolean }>} */ + const mutable = []; + + for (const [name, chunk] of before) { + const replacement = after.get(name); + if (!replacement) { + rotated.push({ name, contains_version: chunk.contains_version }); + } else if (replacement.bytes_hash !== chunk.bytes_hash) { + mutable.push({ + name, + contains_version: chunk.contains_version || replacement.contains_version + }); + } + } + + return { total: before.size, rotated, mutable }; +} + +/** @param {ReturnType} result */ +export function format_report(result) { + /** @param {{ name: string, contains_version: boolean }} finding */ + const line = ({ name, contains_version }) => + `${name}${contains_version ? ' ← contains the version' : ''}`; + + return [ + `${result.total} chunks total · ${result.rotated.length} rotated · ${result.mutable.length} mutable (same name, new bytes)`, + ...result.mutable.map((finding) => ` MUTABLE ${line(finding)}`), + ...result.rotated.map((finding) => ` ROTATED ${line(finding)}`) + ].join('\n'); +} diff --git a/packages/kit/test/version-chunk-rotation/classify.spec.js b/packages/kit/test/version-chunk-rotation/classify.spec.js new file mode 100644 index 000000000000..272a454e26fa --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/classify.spec.js @@ -0,0 +1,72 @@ +import { assert, test } from 'vitest'; +import { compare } from './classify.js'; + +/** + * @param {object} [options] + * @param {string} [options.bytes] + * @param {boolean} [options.contains_version] + * @returns {import('./classify.js').Chunk} + */ +function chunk({ bytes = 'same-bytes', contains_version = false } = {}) { + return { bytes_hash: bytes, contains_version }; +} + +/** @param {Record} chunks */ +const build = (chunks) => new Map(Object.entries(chunks)); + +test('identical builds produce no findings', () => { + const output = build({ + 'chunks/AAAAAAAA.js': chunk(), + 'entry/app.BBBBBBBB.js': chunk({ contains_version: true }) + }); + + const result = compare(output, output); + + assert.deepEqual(result.rotated, []); + assert.deepEqual(result.mutable, []); +}); + +test('a chunk whose bytes change under a stable filename is mutable', () => { + const before = build({ 'chunks/AAAAAAAA.js': chunk({ bytes: 'old', contains_version: true }) }); + const after = build({ 'chunks/AAAAAAAA.js': chunk({ bytes: 'new', contains_version: true }) }); + + const result = compare(before, after); + + assert.deepEqual(result.rotated, []); + assert.deepEqual(result.mutable, [{ name: 'chunks/AAAAAAAA.js', contains_version: true }]); +}); + +test('a rotated chunk is reported with its version flag', () => { + const before = build({ + 'chunks/AAAAAAAA.js': chunk({ contains_version: true }), + 'nodes/0.BBBBBBBB.js': chunk({ bytes: 'imports-AAAAAAAA' }) + }); + const after = build({ + 'chunks/CCCCCCCC.js': chunk({ contains_version: true }), + 'nodes/0.DDDDDDDD.js': chunk({ bytes: 'imports-CCCCCCCC' }) + }); + + const result = compare(before, after); + + assert.deepEqual(result.rotated, [ + { name: 'chunks/AAAAAAAA.js', contains_version: true }, + { name: 'nodes/0.BBBBBBBB.js', contains_version: false } + ]); + assert.deepEqual(result.mutable, []); +}); + +test('an unchanged chunk is not reported even when siblings rotate', () => { + const before = build({ + 'chunks/AAAAAAAA.js': chunk(), + 'chunks/BBBBBBBB.js': chunk({ contains_version: true }) + }); + const after = build({ + 'chunks/AAAAAAAA.js': chunk(), + 'chunks/CCCCCCCC.js': chunk({ contains_version: true }) + }); + + const result = compare(before, after); + + assert.deepEqual(result.rotated, [{ name: 'chunks/BBBBBBBB.js', contains_version: true }]); + assert.deepEqual(result.mutable, []); +}); diff --git a/packages/kit/test/version-chunk-rotation/rotation.spec.js b/packages/kit/test/version-chunk-rotation/rotation.spec.js new file mode 100644 index 000000000000..c0ac91af46ad --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/rotation.spec.js @@ -0,0 +1,81 @@ +// Reproduction gate for sveltejs/kit#12260: `kit.version.name` leaks into +// content-hashed client chunks, so a version bump rotates their hashed filenames — and +// every chunk that imports them — even when the app code is byte-identical. That +// defeats the immutable caching of `_app/immutable/**`. +// +// This suite builds the fixture app under ./app three times and diffs the emitted +// client chunks: twice at the SAME version (CONTROL — proves the build is otherwise +// deterministic) and once at a DIFFERENT version (VARIABLE — a version bump must +// rotate 0 chunks and mutate 0 chunks). +// +// EXPECTED STATE: the VARIABLE test is RED on kit without the version-decoupling fix — +// it reproduces the bug. It turns GREEN, with no edits here, once the fix keeps the +// version off the client import graph. The CONTROL test and classify.spec.js are GREEN +// today. (If red CI is noisy before the fix lands, `test.skip` the VARIABLE test with a +// TODO referencing the follow-up — but the default is an honest, un-skipped gate.) + +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; +import { assert, beforeAll, test } from 'vitest'; +import { compare, format_report, snapshot } from './classify.js'; + +const timeout = 180_000; + +const app_dir = fileURLToPath(new URL('./app', import.meta.url)); +const immutable_dir = path.join(app_dir, '.svelte-kit/output/client/_app/immutable'); + +/** + * Build the fixture with the given `kit.version.name` and snapshot its client + * bundle. Output is cleaned first so the snapshot reflects only this build. + * @param {string} version + */ +function build(version) { + fs.rmSync(path.join(app_dir, '.svelte-kit/output'), { recursive: true, force: true }); + execSync('pnpm build', { + cwd: app_dir, + stdio: 'pipe', + timeout, + env: { ...process.env, SK_VERSION: version } + }); + return snapshot(immutable_dir, version); +} + +/** @type {ReturnType} */ +let control_a; +/** @type {ReturnType} */ +let control_b; +/** @type {ReturnType} */ +let bumped; + +beforeAll(() => { + control_a = build('version-alpha'); + control_b = build('version-alpha'); + bumped = build('version-bravo'); +}, timeout); + +test('the same version builds an identical client bundle', () => { + const r = compare(control_a, control_b); + assert.equal( + r.rotated.length, + 0, + `build is nondeterministic at a fixed version — ${r.rotated.length} chunk(s) rotated:\n${format_report(r)}` + ); + assert.equal(r.mutable.length, 0, `mutable chunks at a fixed version:\n${format_report(r)}`); +}); + +test('bumping kit.version.name rotates no client chunks', () => { + const r = compare(control_a, bumped); + assert.equal( + r.mutable.length, + 0, + `chunks changed bytes under a stable filename (breaks immutable caching):\n${format_report(r)}` + ); + assert.equal( + r.rotated.length, + 0, + `a version bump rotated ${r.rotated.length} client chunk(s) with no app-code change (#12260):\n${format_report(r)}` + ); +}); From 0f179a7630b9c36dac257220a222dab746b54986 Mon Sep 17 00:00:00 2001 From: Axel Date: Mon, 6 Jul 2026 14:53:46 +1000 Subject: [PATCH 3/8] rewrite payload to be package name + config base path + config appDir --- packages/kit/src/core/sync/write_server.js | 5 ++-- packages/kit/src/core/utils.js | 32 ++++++++++++++++++++++ packages/kit/src/exports/vite/index.js | 4 +-- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index 764783f11f87..7d4ebba48375 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -1,10 +1,9 @@ import path from 'node:path'; import process from 'node:process'; -import { hash } from '../../utils/hash.js'; import { posixify, resolve_entry } from '../../utils/filesystem.js'; import { s } from '../../utils/misc.js'; import { load_error_page, load_template } from '../config/index.js'; -import { runtime_directory } from '../utils.js'; +import { runtime_directory, payload_hash } from '../utils.js'; import { isSvelte5Plus, write_if_changed } from './utils.js'; import colors from 'kleur'; import { escape_html } from '../../utils/escape.js'; @@ -63,7 +62,7 @@ export const options = { )}, error }, - version_hash: ${s(hash(config.kit.version.name))} + version_hash: ${s(payload_hash(config.kit))} }; export async function get_hooks() { diff --git a/packages/kit/src/core/utils.js b/packages/kit/src/core/utils.js index 3bbcdea712ff..f1aaa432098d 100644 --- a/packages/kit/src/core/utils.js +++ b/packages/kit/src/core/utils.js @@ -5,6 +5,38 @@ import { fileURLToPath } from 'node:url'; import colors from 'kleur'; import { posixify, to_fs } from '../utils/filesystem.js'; import { noop } from '../utils/functions.js'; +import { hash } from '../utils/hash.js'; + +/** + * Returns a deterministic identifier for the `globalThis.__sveltekit_${payload_hash}` + * payload global. + * + * It must not be derived from `kit.version.name`: the name is inlined into + * content-hashed client chunks in `__SVELTEKIT_PAYLOAD__` via the + * `$env/dynamic/public` virtual module, so a version-derived identifier rotates the + * hashed filename of every chunk referencing it on each deploy (#12260). + * + * It must still distinguish SvelteKit apps from different projects embedded in the + * same document (#9576), so it is derived from the project's `package.json` name + * plus `paths.base` and `appDir` — all deterministic across repeated config loads + * within a build and across machines. + * + * @param {import('types').ValidatedKitConfig} kit + * @returns {string} + */ +export function payload_hash(kit) { + const root = path.dirname(kit.outDir); + + let name = ''; + + try { + name = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf-8')).name ?? ''; + } catch { + // TODO log error or similiar + } + + return hash(`${name}\n${kit.paths.base}\n${kit.appDir}`); +} /** * Resolved path of the `runtime` directory diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 1ab5f669759c..737ab41f5623 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -21,7 +21,7 @@ import { } from '../../core/env.js'; import * as sync from '../../core/sync/sync.js'; import { create_assets } from '../../core/sync/create_manifest_data/index.js'; -import { runtime_directory, logger } from '../../core/utils.js'; +import { runtime_directory, logger, payload_hash } from '../../core/utils.js'; import { load_svelte_config, process_config, split_config } from '../../core/config/index.js'; import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; @@ -244,7 +244,7 @@ async function kit({ svelte_config }) { /** The base directory for the Vite builds */ const out = `${out_dir}/output`; - const version_hash = hash(kit.version.name); + const version_hash = payload_hash(kit); /** @type {ResolvedConfig} */ let vite_config; From 5cc6d5fad5ceac61f3edb822145ef5b14d297644 Mon Sep 17 00:00:00 2001 From: Axel Date: Mon, 6 Jul 2026 15:22:59 +1000 Subject: [PATCH 4/8] seperate how we read the version in different client/server contexts In the client we now read via the global version property rather than changing the name of the global object itself which was the source of bundle rotations --- packages/kit/package.json | 4 ++ packages/kit/src/runtime/app/env/internal.js | 2 +- .../kit/src/runtime/app/env/version/client.js | 1 + .../kit/src/runtime/app/env/version/server.js | 1 + .../kit/src/runtime/server/page/render.js | 5 +- packages/kit/src/types/global-private.d.ts | 2 + .../app/src/routes/prerendered/+page.js | 1 + .../app/src/routes/prerendered/+page.svelte | 6 ++ .../version-chunk-rotation/rotation.spec.js | 72 ++++++++++--------- 9 files changed, 58 insertions(+), 36 deletions(-) create mode 100644 packages/kit/src/runtime/app/env/version/client.js create mode 100644 packages/kit/src/runtime/app/env/version/server.js create mode 100644 packages/kit/test/version-chunk-rotation/app/src/routes/prerendered/+page.js create mode 100644 packages/kit/test/version-chunk-rotation/app/src/routes/prerendered/+page.svelte diff --git a/packages/kit/package.json b/packages/kit/package.json index bf426a8f6958..a59e693b6146 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -98,6 +98,10 @@ "#app/env/public": { "browser": "./src/runtime/app/env/public/client.js", "default": "./src/runtime/app/env/public/server.js" + }, + "#app/env/version": { + "browser": "./src/runtime/app/env/version/client.js", + "default": "./src/runtime/app/env/version/server.js" } }, "exports": { diff --git a/packages/kit/src/runtime/app/env/internal.js b/packages/kit/src/runtime/app/env/internal.js index 30a47983df52..2e01afa316c5 100644 --- a/packages/kit/src/runtime/app/env/internal.js +++ b/packages/kit/src/runtime/app/env/internal.js @@ -1,4 +1,4 @@ -export const version = __SVELTEKIT_APP_VERSION__; +export { version } from '#app/env/version'; export let building = false; export let prerendering = false; diff --git a/packages/kit/src/runtime/app/env/version/client.js b/packages/kit/src/runtime/app/env/version/client.js new file mode 100644 index 000000000000..331d50116840 --- /dev/null +++ b/packages/kit/src/runtime/app/env/version/client.js @@ -0,0 +1 @@ +export const version = /** @type {string} */ (__SVELTEKIT_PAYLOAD__?.version); diff --git a/packages/kit/src/runtime/app/env/version/server.js b/packages/kit/src/runtime/app/env/version/server.js new file mode 100644 index 000000000000..6c0d694c02c0 --- /dev/null +++ b/packages/kit/src/runtime/app/env/version/server.js @@ -0,0 +1 @@ +export const version = __SVELTEKIT_APP_VERSION__; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index ca7e044248e5..266d03b722b0 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -412,7 +412,10 @@ export async function render_response({ const blocks = []; - const properties = [`base: ${base_expression}`]; + const properties = [ + `base: ${base_expression}`, + `version: ${devalue.uneval(__SVELTEKIT_APP_VERSION__)}` + ]; if (paths.assets) { properties.push(`assets: ${s(paths.assets)}`); diff --git a/packages/kit/src/types/global-private.d.ts b/packages/kit/src/types/global-private.d.ts index bace6ee385aa..a947ab51e37c 100644 --- a/packages/kit/src/types/global-private.d.ts +++ b/packages/kit/src/types/global-private.d.ts @@ -37,6 +37,8 @@ declare global { const __SVELTEKIT_PAYLOAD__: { /** The basepath, usually relative to the current page */ base: string; + /** determinsitically computed from package name + config.appDir + config.base */ + version?: string; /** Path to externally-hosted assets */ assets?: string; /** Public environment variables */ diff --git a/packages/kit/test/version-chunk-rotation/app/src/routes/prerendered/+page.js b/packages/kit/test/version-chunk-rotation/app/src/routes/prerendered/+page.js new file mode 100644 index 000000000000..189f71e2e1b3 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/src/routes/prerendered/+page.js @@ -0,0 +1 @@ +export const prerender = true; diff --git a/packages/kit/test/version-chunk-rotation/app/src/routes/prerendered/+page.svelte b/packages/kit/test/version-chunk-rotation/app/src/routes/prerendered/+page.svelte new file mode 100644 index 000000000000..5839f7d6d262 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/app/src/routes/prerendered/+page.svelte @@ -0,0 +1,6 @@ + + +

prerendered

+

{sharedMessage}

diff --git a/packages/kit/test/version-chunk-rotation/rotation.spec.js b/packages/kit/test/version-chunk-rotation/rotation.spec.js index c0ac91af46ad..ee2985bbf7ec 100644 --- a/packages/kit/test/version-chunk-rotation/rotation.spec.js +++ b/packages/kit/test/version-chunk-rotation/rotation.spec.js @@ -1,19 +1,3 @@ -// Reproduction gate for sveltejs/kit#12260: `kit.version.name` leaks into -// content-hashed client chunks, so a version bump rotates their hashed filenames — and -// every chunk that imports them — even when the app code is byte-identical. That -// defeats the immutable caching of `_app/immutable/**`. -// -// This suite builds the fixture app under ./app three times and diffs the emitted -// client chunks: twice at the SAME version (CONTROL — proves the build is otherwise -// deterministic) and once at a DIFFERENT version (VARIABLE — a version bump must -// rotate 0 chunks and mutate 0 chunks). -// -// EXPECTED STATE: the VARIABLE test is RED on kit without the version-decoupling fix — -// it reproduces the bug. It turns GREEN, with no edits here, once the fix keeps the -// version off the client import graph. The CONTROL test and classify.spec.js are GREEN -// today. (If red CI is noisy before the fix lands, `test.skip` the VARIABLE test with a -// TODO referencing the follow-up — but the default is an honest, un-skipped gate.) - import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; @@ -27,11 +11,7 @@ const timeout = 180_000; const app_dir = fileURLToPath(new URL('./app', import.meta.url)); const immutable_dir = path.join(app_dir, '.svelte-kit/output/client/_app/immutable'); -/** - * Build the fixture with the given `kit.version.name` and snapshot its client - * bundle. Output is cleaned first so the snapshot reflects only this build. - * @param {string} version - */ +/** @param {string} version */ function build(version) { fs.rmSync(path.join(app_dir, '.svelte-kit/output'), { recursive: true, force: true }); execSync('pnpm build', { @@ -40,14 +20,22 @@ function build(version) { timeout, env: { ...process.env, SK_VERSION: version } }); - return snapshot(immutable_dir, version); + + return { + version, + chunks: snapshot(immutable_dir, version), + prerendered_page: fs.readFileSync( + path.join(app_dir, '.svelte-kit/output/prerendered/pages/prerendered.html'), + 'utf-8' + ) + }; } -/** @type {ReturnType} */ +/** @type {ReturnType} */ let control_a; -/** @type {ReturnType} */ +/** @type {ReturnType} */ let control_b; -/** @type {ReturnType} */ +/** @type {ReturnType} */ let bumped; beforeAll(() => { @@ -57,25 +45,41 @@ beforeAll(() => { }, timeout); test('the same version builds an identical client bundle', () => { - const r = compare(control_a, control_b); + const result = compare(control_a.chunks, control_b.chunks); + assert.equal( - r.rotated.length, + result.rotated.length, 0, - `build is nondeterministic at a fixed version — ${r.rotated.length} chunk(s) rotated:\n${format_report(r)}` + `build is nondeterministic at a fixed version — ${result.rotated.length} chunk(s) rotated:\n${format_report(result)}` + ); + assert.equal( + result.mutable.length, + 0, + `mutable chunks at a fixed version:\n${format_report(result)}` ); - assert.equal(r.mutable.length, 0, `mutable chunks at a fixed version:\n${format_report(r)}`); }); test('bumping kit.version.name rotates no client chunks', () => { - const r = compare(control_a, bumped); + const result = compare(control_a.chunks, bumped.chunks); + assert.equal( - r.mutable.length, + result.mutable.length, 0, - `chunks changed bytes under a stable filename (breaks immutable caching):\n${format_report(r)}` + `chunks changed bytes under a stable filename (breaks immutable caching):\n${format_report(result)}` ); assert.equal( - r.rotated.length, + result.rotated.length, 0, - `a version bump rotated ${r.rotated.length} client chunk(s) with no app-code change (#12260):\n${format_report(r)}` + `a version bump rotated ${result.rotated.length} client chunk(s) with no app-code change (#12260):\n${format_report(result)}` ); }); + +test('the server-rendered payload carries the version of its own build', () => { + for (const { version, prerendered_page } of [control_a, bumped]) { + assert.include( + prerendered_page, + `version: "${version}"`, + `the payload of the "${version}" build should carry that version` + ); + } +}); From 8caf74f9f28720c61fe3d817e3887eb0ef7eaa94 Mon Sep 17 00:00:00 2001 From: Axel Date: Mon, 6 Jul 2026 15:49:09 +1000 Subject: [PATCH 5/8] fallback to default --- packages/kit/test/version-chunk-rotation/app/svelte.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/version-chunk-rotation/app/svelte.config.js b/packages/kit/test/version-chunk-rotation/app/svelte.config.js index 238a97f61d88..a07478a29c4b 100644 --- a/packages/kit/test/version-chunk-rotation/app/svelte.config.js +++ b/packages/kit/test/version-chunk-rotation/app/svelte.config.js @@ -6,7 +6,7 @@ const config = { kit: { adapter: adapter(), version: { - name: process.env.SK_VERSION || 'control' + name: process.env.SK_VERSION || Date.now().toString() } } }; From 471be87bc67311ad8a6ec4ab9161260e4493d8c3 Mon Sep 17 00:00:00 2001 From: Axel Date: Mon, 6 Jul 2026 16:32:40 +1000 Subject: [PATCH 6/8] add edge case handling for embedded apps and name resolution --- packages/kit/src/core/utils.js | 4 + packages/kit/src/exports/vite/index.js | 19 ++++ .../consistency.spec.js | 56 ++++++++++++ .../payload-hash.spec.js | 91 +++++++++++++++++++ .../version-chunk-rotation/rotation.spec.js | 42 +++------ .../version-chunk-rotation/vitest.config.js | 9 ++ 6 files changed, 193 insertions(+), 28 deletions(-) create mode 100644 packages/kit/test/version-chunk-rotation/consistency.spec.js create mode 100644 packages/kit/test/version-chunk-rotation/payload-hash.spec.js create mode 100644 packages/kit/test/version-chunk-rotation/vitest.config.js diff --git a/packages/kit/src/core/utils.js b/packages/kit/src/core/utils.js index f1aaa432098d..3f8c95e81ca0 100644 --- a/packages/kit/src/core/utils.js +++ b/packages/kit/src/core/utils.js @@ -25,6 +25,8 @@ import { hash } from '../utils/hash.js'; * @returns {string} */ export function payload_hash(kit) { + if (kit.embedded) return hash(kit.version.name); + const root = path.dirname(kit.outDir); let name = ''; @@ -35,6 +37,8 @@ export function payload_hash(kit) { // TODO log error or similiar } + if (!name) name = path.basename(root); + return hash(`${name}\n${kit.paths.base}\n${kit.appDir}`); } diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 737ab41f5623..c6606b51c697 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -212,6 +212,12 @@ export async function sveltekit(config) { let secondary_build_started = false; +/** + * config loads twice so we cache the value of the first load so client and server bundles read the same value + * @type {string | undefined} + */ +let resolved_version_name; + /** @type {import('types').ManifestData} */ let manifest_data; @@ -299,6 +305,19 @@ async function kit({ svelte_config }) { vite_config_env = config_env; is_build = config_env.command === 'build'; + if (is_build) { + if (resolved_version_name === undefined) { + resolved_version_name = kit.version.name; + } else if (kit.version.name !== resolved_version_name) { + console.warn( + `svelte.config.js computed a different kit.version.name on each load ` + + `("${resolved_version_name}" vs "${kit.version.name}"). Using the first value ` + + `for the whole build — make version.name deterministic to silence this warning.` + ); + } + kit.version.name = resolved_version_name; + } + env = get_env(kit.env, vite_config_env.mode); const allow = new Set([ diff --git a/packages/kit/test/version-chunk-rotation/consistency.spec.js b/packages/kit/test/version-chunk-rotation/consistency.spec.js new file mode 100644 index 000000000000..75b0aa4ff850 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/consistency.spec.js @@ -0,0 +1,56 @@ +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; +import { assert, test } from 'vitest'; + +const timeout = 180_000; + +const app_dir = fileURLToPath(new URL('./app', import.meta.url)); + +/** @param {string} [version] omit to build with the fixture's per-config-load default */ +function build(version) { + const env = { ...process.env }; + if (version === undefined) delete env.SK_VERSION; + else env.SK_VERSION = version; + + fs.rmSync(path.join(app_dir, '.svelte-kit/output'), { recursive: true, force: true }); + execSync('pnpm build', { cwd: app_dir, stdio: 'pipe', timeout, env }); + + return { + prerendered_page: fs.readFileSync( + path.join(app_dir, '.svelte-kit/output/prerendered/pages/prerendered.html'), + 'utf-8' + ), + version_json: /** @type {{ version: string }} */ ( + JSON.parse( + fs.readFileSync(path.join(app_dir, '.svelte-kit/output/client/_app/version.json'), 'utf-8') + ) + ) + }; +} + +test('a version containing cannot terminate the payload script', { timeout }, () => { + const dangerous_version = ''; + + const { prerendered_page, version_json } = build(dangerous_version); + + assert.notInclude(prerendered_page, dangerous_version); + assert.include(prerendered_page, '\\u003C/script>', 'the version is escaped, not dropped'); + assert.equal(version_json.version, dangerous_version); +}); + +test( + 'a version computed per config load resolves to one value for the whole build (#14166)', + { timeout }, + () => { + const { prerendered_page, version_json } = build(); + + assert.include( + prerendered_page, + `version: "${version_json.version}"`, + 'the payload version and _app/version.json must agree' + ); + } +); diff --git a/packages/kit/test/version-chunk-rotation/payload-hash.spec.js b/packages/kit/test/version-chunk-rotation/payload-hash.spec.js new file mode 100644 index 000000000000..8655f258592d --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/payload-hash.spec.js @@ -0,0 +1,91 @@ +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { assert, test } from 'vitest'; +import { payload_hash } from '../../src/core/utils.js'; +import { hash } from '../../src/utils/hash.js'; + +/** @param {{ name?: string, package_json?: boolean }} [options] */ +function project({ name, package_json = true } = {}) { + const root = mkdtempSync(join(tmpdir(), 'kit-payload-hash-')); + if (package_json) writeFileSync(join(root, 'package.json'), JSON.stringify({ name })); + return root; +} + +/** + * @param {object} options + * @param {string} options.root + * @param {string} [options.base] + * @param {string} [options.app_dir] + * @param {string} [options.version] + * @param {boolean} [options.embedded] + * @returns {import('types').ValidatedKitConfig} + */ +function kit_config({ root, base = '', app_dir = '_app', version = 'v1', embedded = false }) { + return /** @type {import('types').ValidatedKitConfig} */ ( + /** @type {unknown} */ ({ + outDir: join(root, '.svelte-kit'), + appDir: app_dir, + paths: { base }, + version: { name: version }, + embedded + }) + ); +} + +test('is independent of version.name', () => { + const root = project({ name: 'my-app' }); + + assert.equal( + payload_hash(kit_config({ root, version: 'v-alpha' })), + payload_hash(kit_config({ root, version: 'v-bravo' })) + ); +}); + +test('differs between projects with different package names', () => { + assert.notEqual( + payload_hash(kit_config({ root: project({ name: 'app-one' }) })), + payload_hash(kit_config({ root: project({ name: 'app-two' }) })) + ); +}); + +test('differs when paths.base differs', () => { + const root = project({ name: 'my-app' }); + + assert.notEqual( + payload_hash(kit_config({ root, base: '' })), + payload_hash(kit_config({ root, base: '/docs' })) + ); +}); + +test('differs when appDir differs', () => { + const root = project({ name: 'my-app' }); + + assert.notEqual( + payload_hash(kit_config({ root, app_dir: '_app' })), + payload_hash(kit_config({ root, app_dir: '_custom' })) + ); +}); + +test('embedded apps keep the version-derived key for cross-deploy isolation', () => { + const root = project({ name: 'my-app' }); + + assert.equal( + payload_hash(kit_config({ root, embedded: true, version: 'v-alpha' })), + hash('v-alpha') + ); +}); + +test('projects without a package.json still get distinct keys', () => { + assert.notEqual( + payload_hash(kit_config({ root: project({ package_json: false }) })), + payload_hash(kit_config({ root: project({ package_json: false }) })) + ); +}); + +test('projects with a nameless package.json still get distinct keys', () => { + assert.notEqual( + payload_hash(kit_config({ root: project() })), + payload_hash(kit_config({ root: project() })) + ); +}); diff --git a/packages/kit/test/version-chunk-rotation/rotation.spec.js b/packages/kit/test/version-chunk-rotation/rotation.spec.js index ee2985bbf7ec..f677e9801518 100644 --- a/packages/kit/test/version-chunk-rotation/rotation.spec.js +++ b/packages/kit/test/version-chunk-rotation/rotation.spec.js @@ -31,6 +31,15 @@ function build(version) { }; } +/** + * @param {ReturnType} result + * @param {string} when + */ +function assert_bundle_unchanged(result, when) { + assert.equal(result.mutable.length, 0, `chunks mutated ${when}:\n${format_report(result)}`); + assert.equal(result.rotated.length, 0, `chunks rotated ${when}:\n${format_report(result)}`); +} + /** @type {ReturnType} */ let control_a; /** @type {ReturnType} */ @@ -45,41 +54,18 @@ beforeAll(() => { }, timeout); test('the same version builds an identical client bundle', () => { - const result = compare(control_a.chunks, control_b.chunks); - - assert.equal( - result.rotated.length, - 0, - `build is nondeterministic at a fixed version — ${result.rotated.length} chunk(s) rotated:\n${format_report(result)}` - ); - assert.equal( - result.mutable.length, - 0, - `mutable chunks at a fixed version:\n${format_report(result)}` - ); + assert_bundle_unchanged(compare(control_a.chunks, control_b.chunks), 'at a fixed version'); }); test('bumping kit.version.name rotates no client chunks', () => { - const result = compare(control_a.chunks, bumped.chunks); - - assert.equal( - result.mutable.length, - 0, - `chunks changed bytes under a stable filename (breaks immutable caching):\n${format_report(result)}` - ); - assert.equal( - result.rotated.length, - 0, - `a version bump rotated ${result.rotated.length} client chunk(s) with no app-code change (#12260):\n${format_report(result)}` + assert_bundle_unchanged( + compare(control_a.chunks, bumped.chunks), + 'after a version bump with no app-code change' ); }); test('the server-rendered payload carries the version of its own build', () => { for (const { version, prerendered_page } of [control_a, bumped]) { - assert.include( - prerendered_page, - `version: "${version}"`, - `the payload of the "${version}" build should carry that version` - ); + assert.include(prerendered_page, `version: "${version}"`); } }); diff --git a/packages/kit/test/version-chunk-rotation/vitest.config.js b/packages/kit/test/version-chunk-rotation/vitest.config.js new file mode 100644 index 000000000000..91bbdfedda78 --- /dev/null +++ b/packages/kit/test/version-chunk-rotation/vitest.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // rotation.spec.js and consistency.spec.js both build ./app in place — + // concurrent builds in the same directory would corrupt each other's output + fileParallelism: false + } +}); From be25498e9944f5cf2aff6599d9b5661935c3b086 Mon Sep 17 00:00:00 2001 From: Axel Date: Wed, 8 Jul 2026 10:40:42 +1000 Subject: [PATCH 7/8] followup fixes from merge for targetting version-3 --- packages/kit/package.json | 2 +- packages/kit/src/core/sync/write_server.js | 4 ++-- packages/kit/src/exports/vite/index.js | 4 +++- .../version-chunk-rotation/app/svelte.config.js | 14 -------------- .../test/version-chunk-rotation/app/vite.config.js | 11 ++++++++++- pnpm-lock.yaml | 14 +++++++------- 6 files changed, 23 insertions(+), 26 deletions(-) delete mode 100644 packages/kit/test/version-chunk-rotation/app/svelte.config.js diff --git a/packages/kit/package.json b/packages/kit/package.json index 9782cc0633bc..73f75e46414a 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -102,7 +102,7 @@ "#app/env/version": { "browser": "./src/runtime/app/env/version/client.js", "default": "./src/runtime/app/env/version/server.js" - }, + }, "#internal": { "browser": "./src/exports/internal/client.js", "default": "./src/exports/internal/server/index.js" diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index bf1892116733..d07340cbf839 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -1,11 +1,11 @@ import path from 'node:path'; import { styleText } from 'node:util'; -import { hash } from '../../utils/hash.js'; import { resolve_entry } from '../../utils/filesystem.js'; import { posixify } from '../../utils/os.js'; import { s } from '../../utils/misc.js'; import { load_error_page, load_template } from '../config/index.js'; -import { write_if_changed, payload_hash } from './utils.js'; +import { payload_hash } from '../utils.js'; +import { write_if_changed } from './utils.js'; import { escape_html } from '../../utils/escape.js'; /** diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 87da05d27a8c..957d53ca4aa4 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -280,7 +280,7 @@ function kit({ svelte_config }) { let out; /** @type {string} */ - const version_hash = payload_hash(kit); + let version_hash; /** @type {ResolvedConfig} */ let vite_config; @@ -358,6 +358,8 @@ function kit({ svelte_config }) { kit.version.name = resolved_version_name; } + version_hash = payload_hash(kit); + kit_global = is_build ? `globalThis.__sveltekit_${version_hash}` : 'globalThis.__sveltekit_dev'; diff --git a/packages/kit/test/version-chunk-rotation/app/svelte.config.js b/packages/kit/test/version-chunk-rotation/app/svelte.config.js deleted file mode 100644 index a07478a29c4b..000000000000 --- a/packages/kit/test/version-chunk-rotation/app/svelte.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import adapter from '../../../../adapter-auto/index.js'; -import process from 'node:process'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - kit: { - adapter: adapter(), - version: { - name: process.env.SK_VERSION || Date.now().toString() - } - } -}; - -export default config; diff --git a/packages/kit/test/version-chunk-rotation/app/vite.config.js b/packages/kit/test/version-chunk-rotation/app/vite.config.js index 4b90a7655ee5..0322e21e7f0d 100644 --- a/packages/kit/test/version-chunk-rotation/app/vite.config.js +++ b/packages/kit/test/version-chunk-rotation/app/vite.config.js @@ -1,5 +1,7 @@ import * as path from 'node:path'; +import process from 'node:process'; import { sveltekit } from '@sveltejs/kit/vite'; +import adapter from '../../../../adapter-auto/index.js'; /** @type {import('vite').UserConfig} */ const config = { @@ -11,7 +13,14 @@ const config = { logLevel: 'silent', - plugins: [sveltekit()], + plugins: [ + sveltekit({ + adapter: adapter(), + version: { + name: process.env.SK_VERSION || Date.now().toString() + } + }) + ], server: { fs: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f23d128d8816..606048de79da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1257,7 +1257,7 @@ importers: specifier: 'catalog:' version: 8.0.16(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) - packages/kit/test/build-errors/apps/server-only-shared: + packages/kit/test/build-errors/apps/server-only-module-dynamic-import: devDependencies: '@sveltejs/kit': specifier: workspace:^ @@ -1278,7 +1278,7 @@ importers: specifier: 'catalog:' version: 8.0.16(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) - packages/kit/test/build-errors/apps/server-only-module-dynamic-import: + packages/kit/test/build-errors/apps/server-only-shared: devDependencies: '@sveltejs/kit': specifier: workspace:^ @@ -1396,7 +1396,7 @@ importers: devDependencies: vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.9)(jsdom@26.1.0)(vite@6.4.3(@types/node@18.19.130)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-playwright@4.1.9)(jsdom@29.1.1)(vite@8.0.16(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0)) packages/kit/test/version-chunk-rotation/app: devDependencies: @@ -1408,19 +1408,19 @@ importers: version: link:../../.. '@sveltejs/vite-plugin-svelte': specifier: 'catalog:' - version: 6.2.4(svelte@5.56.3(@typescript-eslint/types@8.61.1))(vite@6.4.3(@types/node@18.19.130)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.3)) + version: 7.0.0(svelte@5.56.3(@typescript-eslint/types@8.61.1))(vite@8.0.16(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0)) svelte: specifier: 'catalog:' version: 5.56.3(@typescript-eslint/types@8.61.1) svelte-check: specifier: 'catalog:' - version: 4.4.6(picomatch@4.0.4)(svelte@5.56.3(@typescript-eslint/types@8.61.1))(typescript@6.0.2) + version: 4.7.0(picomatch@4.0.4)(svelte@5.56.3(@typescript-eslint/types@8.61.1))(typescript@6.0.3) typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 vite: specifier: 'catalog:' - version: 6.4.3(@types/node@18.19.130)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.3) + version: 8.0.16(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) packages/package: dependencies: From 37b34b0afcf35dc139ec60ae351a4eb2e4699fd9 Mon Sep 17 00:00:00 2001 From: Axel Date: Mon, 13 Jul 2026 16:12:25 +1000 Subject: [PATCH 8/8] fix leftover merge conflict markers --- packages/kit/src/core/utils.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/kit/src/core/utils.js b/packages/kit/src/core/utils.js index ed454327dc9e..b320cb33ddd5 100644 --- a/packages/kit/src/core/utils.js +++ b/packages/kit/src/core/utils.js @@ -8,7 +8,6 @@ import { hash } from '../utils/hash.js'; import { posixify } from '../utils/os.js'; /** -<<<<<<< version-chunk-rotation-fix * Returns a deterministic identifier for the `globalThis.__sveltekit_${payload_hash}` * payload global. * @@ -44,10 +43,7 @@ export function payload_hash(kit) { } /** - * Resolved path of the `runtime` directory -======= * Resolved path of the `runtime` directory posix-ified ->>>>>>> version-3 * * TODO Windows issue: * Vite or sth else somehow sets the driver letter inconsistently to lower or upper case depending on the run environment.