diff --git a/packages/adapter-node/package.json b/packages/adapter-node/package.json index 88db65370177..fe5615841250 100644 --- a/packages/adapter-node/package.json +++ b/packages/adapter-node/package.json @@ -35,11 +35,13 @@ "scripts": { "dev": "rolldown -cw", "build": "rolldown -c", - "test": "vitest run", + "test": "pnpm test:unit && pnpm test:apps", "check": "tsc", "lint": "prettier --check .", "format": "pnpm lint --write", - "prepublishOnly": "pnpm build" + "prepublishOnly": "pnpm build", + "test:unit": "vitest run", + "test:apps": "pnpm build && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test" }, "devDependencies": { "@polka/url": "catalog:", diff --git a/packages/adapter-node/test/apps/instrumentation/.gitignore b/packages/adapter-node/test/apps/instrumentation/.gitignore new file mode 100644 index 000000000000..762b1c80fa9d --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +node_modules +/.svelte-kit +/build diff --git a/packages/adapter-node/test/apps/instrumentation/package.json b/packages/adapter-node/test/apps/instrumentation/package.json new file mode 100644 index 000000000000..35a1f2f8c7e6 --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/package.json @@ -0,0 +1,21 @@ +{ + "name": "test-adapter-node-instrumentation", + "version": "0.0.1", + "private": true, + "type": "module", + "imports": { + "#lib/*": "./src/lib/*" + }, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "prepare": "svelte-kit sync || echo ''", + "test": "MY_BASE_URL=https://api.example pnpm build && node test/boot.js" + }, + "devDependencies": { + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "svelte": "catalog:", + "vite": "catalog:" + } +} diff --git a/packages/adapter-node/test/apps/instrumentation/src/app.html b/packages/adapter-node/test/apps/instrumentation/src/app.html new file mode 100644 index 000000000000..26bc25de4e52 --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/src/app.html @@ -0,0 +1,12 @@ + + + + + + %sveltekit.head% + + + +
%sveltekit.body%
+ + diff --git a/packages/adapter-node/test/apps/instrumentation/src/env.js b/packages/adapter-node/test/apps/instrumentation/src/env.js new file mode 100644 index 000000000000..301360a310b3 --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/src/env.js @@ -0,0 +1,5 @@ +import { defineEnvVars } from '@sveltejs/kit/env'; + +export const variables = defineEnvVars({ + MY_BASE_URL: { public: false, static: false } +}); diff --git a/packages/adapter-node/test/apps/instrumentation/src/instrumentation.server.js b/packages/adapter-node/test/apps/instrumentation/src/instrumentation.server.js new file mode 100644 index 000000000000..23bb7c6f0195 --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/src/instrumentation.server.js @@ -0,0 +1,4 @@ +// The mere presence of this file makes adapter-node call `builder.instrument()` +// (with `env: 'process.env'` on the eager-env branch), generating the env-init +// facade — the code path under test. +console.log('[instrumentation] evaluated'); diff --git a/packages/adapter-node/test/apps/instrumentation/src/lib/server/api-client.js b/packages/adapter-node/test/apps/instrumentation/src/lib/server/api-client.js new file mode 100644 index 000000000000..9df3c06c4af0 --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/src/lib/server/api-client.js @@ -0,0 +1,5 @@ +import { MY_BASE_URL } from '$app/env/private'; + +// Module-scope read — mirrors configuring an API client with `baseUrl` from dynamic +// env. Correct only if env is populated before this module evaluates. +export const CAPTURED_AT_MODULE_SCOPE = MY_BASE_URL; diff --git a/packages/adapter-node/test/apps/instrumentation/src/routes/+page.svelte b/packages/adapter-node/test/apps/instrumentation/src/routes/+page.svelte new file mode 100644 index 000000000000..c45ce6b6da49 --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/src/routes/+page.svelte @@ -0,0 +1 @@ +

adapter-node instrumentation test app

diff --git a/packages/adapter-node/test/apps/instrumentation/src/routes/env/+server.js b/packages/adapter-node/test/apps/instrumentation/src/routes/env/+server.js new file mode 100644 index 000000000000..ce060e8890dc --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/src/routes/env/+server.js @@ -0,0 +1,10 @@ +import process from 'node:process'; +import { json } from '@sveltejs/kit'; +import { CAPTURED_AT_MODULE_SCOPE } from '#lib/server/api-client.js'; + +export function GET() { + return json({ + captured: CAPTURED_AT_MODULE_SCOPE ?? null, + live: process.env.MY_BASE_URL ?? null + }); +} diff --git a/packages/adapter-node/test/apps/instrumentation/test/boot.js b/packages/adapter-node/test/apps/instrumentation/test/boot.js new file mode 100644 index 000000000000..ae502f7ae841 --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/test/boot.js @@ -0,0 +1,66 @@ +// Boots the adapter-node build output with an instrumentation file present and +// asserts that (1) the server starts at all — regression for the env-init facade +// resolving `set_env` from the adapter's own `env.js` (sveltejs/kit#16303), +// and (2) a module-scope `$app/env/private` read observes the runtime value — +// regression for sveltejs/kit#16288. +import { spawn } from 'node:child_process'; +import process from 'node:process'; + +const PORT = 3399; +const EXPECTED = 'https://api.example'; + +const server = spawn(process.execPath, ['build/index.js'], { + env: { + ...process.env, + MY_BASE_URL: EXPECTED, + PORT: String(PORT), + HOST: '127.0.0.1' + }, + stdio: ['ignore', 'pipe', 'pipe'] +}); + +let output = ''; +server.stdout.on('data', (chunk) => (output += chunk)); +server.stderr.on('data', (chunk) => (output += chunk)); + +let exited = false; +server.on('exit', () => (exited = true)); + +function fail(message) { + console.error(`FAIL: ${message}`); + console.error('--- server output ---'); + console.error(output || '(none)'); + server.kill(); + process.exit(1); +} + +try { + let response; + for (let attempt = 0; attempt < 40; attempt++) { + if (exited) fail('server exited before responding (startup crash)'); + await new Promise((resolve) => setTimeout(resolve, 250)); + try { + response = await fetch(`http://127.0.0.1:${PORT}/env`); + break; + } catch { + // not up yet + } + } + if (!response) fail('server never came up'); + + const { captured, live } = await response.json(); + if (live !== EXPECTED) + fail(`live env read is ${JSON.stringify(live)}, expected ${JSON.stringify(EXPECTED)}`); + if (captured !== EXPECTED) { + fail( + `module-scope $app/env/private read captured ${JSON.stringify(captured)} — evaluated before env was set` + ); + } + console.log( + 'PASS: server booted with instrumentation and module-scope env read observed the runtime value' + ); + server.kill(); + process.exit(0); +} catch (error) { + fail(error.message); +} diff --git a/packages/adapter-node/test/apps/instrumentation/tsconfig.json b/packages/adapter-node/test/apps/instrumentation/tsconfig.json new file mode 100644 index 000000000000..c2db702f9bb1 --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "moduleResolution": "bundler" + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/adapter-node/test/apps/instrumentation/vite.config.js b/packages/adapter-node/test/apps/instrumentation/vite.config.js new file mode 100644 index 000000000000..876e6414dc30 --- /dev/null +++ b/packages/adapter-node/test/apps/instrumentation/vite.config.js @@ -0,0 +1,14 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import adapter from '../../../index.js'; + +/** @type {import('vite').UserConfig} */ +export default { + build: { + minify: false + }, + plugins: [ + sveltekit({ + adapter: adapter() + }) + ] +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d296f27ddb2..87e56bebed43 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -351,6 +351,21 @@ importers: specifier: 'catalog:' version: 8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) + packages/adapter-node/test/apps/instrumentation: + devDependencies: + '@sveltejs/kit': + specifier: workspace:^ + version: link:../../../../kit + '@sveltejs/vite-plugin-svelte': + specifier: 'catalog:' + version: 7.0.0(svelte@5.56.3(@typescript-eslint/types@8.61.1))(vite@8.1.5(@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) + vite: + specifier: 'catalog:' + version: 8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) + packages/adapter-static: devDependencies: '@playwright/test':