diff --git a/packages/void/src/cli/wrapper.ts b/packages/void/src/cli/wrapper.ts index 4ae34a9c7..f43bdf95e 100644 --- a/packages/void/src/cli/wrapper.ts +++ b/packages/void/src/cli/wrapper.ts @@ -250,6 +250,15 @@ function generateWrapperSource( lines.push('async function wrappedFetch(request, env, ctx) {'); lines.push(' const url = new URL(request.url);'); + // Readiness probe — platform polls this to confirm the worker is live + // after WfP upload. Handled here (before original.fetch) so the user's + // framework handler doesn't route it to env.ASSETS, which returns CF's + // "Assets have not yet been deployed" stub during asset propagation + // and would trip the platform's probe. + lines.push(' if (request.method === "GET" && url.pathname === "/__void/ready") {'); + lines.push(' return Response.json({ ready: true });'); + lines.push(' }'); + // Migration handler if (migrations && migrations.length > 0) { lines.push(' if (request.method === "POST" && url.pathname === "/__void/migrate") {'); diff --git a/packages/void/test/unit/wrapper.test.ts b/packages/void/test/unit/wrapper.test.ts index a38ec4f7d..52e81dddc 100644 --- a/packages/void/test/unit/wrapper.test.ts +++ b/packages/void/test/unit/wrapper.test.ts @@ -153,6 +153,42 @@ describe('generateWrapper with migrations', () => { expect(existsSync(join(workerDir, '__void_migration_handler.mjs'))).toBe(true); }); + it('intercepts GET /__void/ready with {ready:true} when wrapper is generated', async () => { + // Platform's deploy-time readiness probe (packages/api/src/routes/deploy.ts) + // expects a JSON 200 from GET /__void/ready before dispatching migrations. + // Without this, the request falls through to the user's framework handler + // (e.g. SvelteKit -> env.ASSETS), which returns CF's "Assets have not yet + // been deployed to this script." stub during WfP propagation, causing the + // probe to retry until exhaustion. + mkdirSync(workerDir, { recursive: true }); + writeFileSync( + join(workerDir, 'index.mjs'), + 'export default { fetch() { return new Response("ok"); } };', + ); + + mkdirSync(join(projectRoot, 'db', 'migrations'), { recursive: true }); + writeFileSync( + join(projectRoot, 'db', 'migrations', '001_test.sql'), + 'CREATE TABLE test (id INTEGER PRIMARY KEY);', + ); + + await generateWrapper(workerDir, 'index.mjs', projectRoot, { + ...base, + hasMigrations: true, + migrationDialect: 'sqlite', + }); + + const wrapper = readFileSync(join(workerDir, 'index.mjs'), 'utf-8'); + expect(wrapper).toContain('/__void/ready'); + expect(wrapper).toMatch(/Response\.json\(\s*\{\s*ready:\s*true\s*\}\s*\)/); + // Must be checked before delegating to original.fetch, otherwise the + // user's framework handler will see the request. + const readyIdx = wrapper.indexOf('/__void/ready'); + const originalIdx = wrapper.indexOf('original.fetch'); + expect(readyIdx).toBeGreaterThanOrEqual(0); + expect(originalIdx).toBeGreaterThan(readyIdx); + }); + it('does not generate migration handler when hasMigrations is false', async () => { mkdirSync(workerDir, { recursive: true }); writeFileSync(