From 77ffe6e14d7e8e4c848a0257f1ce66d84a40eac5 Mon Sep 17 00:00:00 2001 From: Shinya Fujino Date: Sat, 7 Nov 2026 01:38:47 +0900 Subject: [PATCH] fix(deploy): keep SvelteKit job bundles out of uploaded assets SvelteKit's Worker directory is also its asset directory, so a native deploy lists the files Void adds there in `.assetsignore`. The list named the cron and queue bundle `__void_handlers.mjs`, but in an ES module package, which is what `sv create` makes, Vite names it `__void_handlers.js`, so the compiled job code was uploaded with the static assets. SvelteKit's Worker runs first and does not serve it. Co-Authored-By: Claude Opus 5.5 --- .../void/src/cli/deploy-cloudflare-static.ts | 2 + .../void/test/unit/cloudflare-deploy.test.ts | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/packages/void/src/cli/deploy-cloudflare-static.ts b/packages/void/src/cli/deploy-cloudflare-static.ts index 168bb8e06..fc6c76d64 100644 --- a/packages/void/src/cli/deploy-cloudflare-static.ts +++ b/packages/void/src/cli/deploy-cloudflare-static.ts @@ -156,6 +156,8 @@ export async function prepareCloudflareFrameworkArtifact(options: { '_routes.json', '__original.js', '__original.mjs', + // Vite names the job bundle `.js` in an ES module package, `.mjs` otherwise. + '__void_handlers.js', '__void_handlers.mjs', '__void_isr*.mjs', '__void_migration_handler*.mjs', diff --git a/packages/void/test/unit/cloudflare-deploy.test.ts b/packages/void/test/unit/cloudflare-deploy.test.ts index 86a5c1360..ab4b814ae 100644 --- a/packages/void/test/unit/cloudflare-deploy.test.ts +++ b/packages/void/test/unit/cloudflare-deploy.test.ts @@ -5,6 +5,7 @@ import { readFileSync, readdirSync, rmSync, + statSync, symlinkSync, writeFileSync, } from 'node:fs'; @@ -20,6 +21,7 @@ import { type CloudflareOperation, } from '@void/deploy-cloudflare'; import { log } from '@voidzero-dev/vite-plus-prompts'; +import ignore from 'ignore'; import { toSlash } from 'pathslash'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'; import { activateCloudflareVersion as activateCloudflareVersionPrimitive } from '../../../deploy-cloudflare/src/index.ts'; @@ -7784,6 +7786,47 @@ describe('runCloudflareDeploy — framework artifact guard', () => { expect(readFileSync(join(root, 'wrangler.jsonc'), 'utf-8')).toContain('// keep this comment'); }); + // SvelteKit's Worker directory is also its asset directory. In an ES module package, which is + // what `sv create` makes, Vite names the cron and queue bundle `__void_handlers.js`. + it('keeps the modules it adds to an ES module SvelteKit Worker out of its assets', async () => { + const root = makeProject({ + 'package.json': JSON.stringify({ + name: 'skapp', + type: 'module', + dependencies: { '@sveltejs/kit': '2.0.0' }, + }), + 'void.json': JSON.stringify({}), + 'wrangler.jsonc': JSON.stringify({ name: 'skapp', account_id: 'acc123' }), + '.svelte-kit/cloudflare/_worker.js': + 'export default { fetch() { return new Response("ok") } };\n', + '.svelte-kit/cloudflare/_app/immutable/app.js': 'console.log("client");\n', + 'crons/hourly.ts': [ + 'import { defineScheduled } from "void";', + 'export const cron = "0 * * * *";', + 'export default defineScheduled(async function hourly() {});', + ].join('\n'), + }); + + await prepareCloudflareFrameworkArtifact({ + root, + configPath: join(root, 'wrangler.jsonc'), + accountId: 'acc123', + workerName: 'skapp', + voidConfig: readConfig(root), + framework: { name: 'sveltekit', class: 'b' }, + preset: FRAMEWORK_PRESETS.sveltekit, + }); + + const directory = join(root, '.svelte-kit/cloudflare'); + expect(existsSync(join(directory, '__void_handlers.js'))).toBe(true); + const ignored = ignore().add(readFileSync(join(directory, '.assetsignore'), 'utf-8')); + const assets = readdirSync(directory, { recursive: true, encoding: 'utf8' }) + .map((file) => toSlash(file)) + .filter((file) => statSync(join(directory, file)).isFile()) + .filter((file) => file !== '.assetsignore' && !ignored.ignores(file)); + expect(assets).toEqual(['_app/immutable/app.js']); + }); + it('prepares Class A wrapper handlers without replacing its generated config pointer', async () => { const root = makeProject({ 'package.json': JSON.stringify({