@sveltejs/kit 2.62.0 → 2.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/package.json +12 -1
  2. package/src/cli.js +32 -6
  3. package/src/core/adapt/builder.js +26 -5
  4. package/src/core/adapt/index.js +5 -2
  5. package/src/core/config/index.js +7 -5
  6. package/src/core/config/options.js +1 -0
  7. package/src/core/env.js +313 -8
  8. package/src/core/postbuild/analyse.js +2 -1
  9. package/src/core/postbuild/prerender.js +2 -1
  10. package/src/core/sync/sync.js +16 -0
  11. package/src/core/sync/write_ambient.js +12 -6
  12. package/src/core/sync/write_env.js +32 -0
  13. package/src/core/sync/write_root.js +1 -1
  14. package/src/core/sync/write_server.js +3 -2
  15. package/src/core/sync/write_tsconfig.js +1 -0
  16. package/src/exports/hooks/index.js +13 -0
  17. package/src/exports/internal/env.js +71 -0
  18. package/src/exports/internal/types.d.ts +3 -0
  19. package/src/exports/public.d.ts +40 -0
  20. package/src/exports/vite/build/build_service_worker.js +20 -4
  21. package/src/exports/vite/dev/index.js +2 -2
  22. package/src/exports/vite/index.js +127 -29
  23. package/src/exports/vite/module_ids.js +10 -1
  24. package/src/exports/vite/utils.js +6 -0
  25. package/src/runtime/app/env/index.js +2 -0
  26. package/src/runtime/app/env/internal.js +11 -0
  27. package/src/runtime/app/env/private.js +1 -0
  28. package/src/runtime/app/env/public/client.js +7 -0
  29. package/src/runtime/app/env/public/index.js +1 -0
  30. package/src/runtime/app/env/public/server.js +7 -0
  31. package/src/runtime/app/env/standard-schema.d.ts +0 -0
  32. package/src/runtime/app/env/types.d.ts +19 -0
  33. package/src/runtime/app/environment/index.js +10 -2
  34. package/src/runtime/app/server/remote/query.js +1 -1
  35. package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
  36. package/src/runtime/client/utils.js +1 -1
  37. package/src/runtime/server/env_module.js +13 -3
  38. package/src/runtime/server/index.js +2 -0
  39. package/src/runtime/server/page/render.js +11 -3
  40. package/src/runtime/server/respond.js +1 -1
  41. package/src/types/ambient-private.d.ts +25 -9
  42. package/src/types/global-private.d.ts +2 -0
  43. package/src/types/internal.d.ts +1 -0
  44. package/src/utils/import.js +2 -2
  45. package/src/version.js +1 -1
  46. package/types/index.d.ts +70 -1
  47. package/types/index.d.ts.map +7 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.62.0",
3
+ "version": "2.63.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -76,6 +76,10 @@
76
76
  "#app/paths": {
77
77
  "browser": "./src/runtime/app/paths/client.js",
78
78
  "default": "./src/runtime/app/paths/server.js"
79
+ },
80
+ "#app/env/public": {
81
+ "browser": "./src/runtime/app/env/public/client.js",
82
+ "default": "./src/runtime/app/env/public/server.js"
79
83
  }
80
84
  },
81
85
  "exports": {
@@ -88,6 +92,13 @@
88
92
  "types": "./types/index.d.ts",
89
93
  "import": "./src/exports/internal/index.js"
90
94
  },
95
+ "./internal/env": {
96
+ "types": "./types/index.d.ts",
97
+ "import": "./src/exports/internal/env.js"
98
+ },
99
+ "./internal/types": {
100
+ "import": "./src/exports/internal/types.js"
101
+ },
91
102
  "./internal/server": {
92
103
  "types": "./types/index.d.ts",
93
104
  "import": "./src/exports/internal/server.js"
package/src/cli.js CHANGED
@@ -4,6 +4,7 @@ import { parseArgs } from 'node:util';
4
4
  import colors from 'kleur';
5
5
  import { load_config } from './core/config/index.js';
6
6
  import { coalesce_to_error } from './utils/error.js';
7
+ import { resolve_explicit_env_entry } from './core/env.js';
7
8
 
8
9
  /** @param {unknown} e */
9
10
  function handle_error(e) {
@@ -73,20 +74,45 @@ if (!command) {
73
74
  }
74
75
 
75
76
  if (command === 'sync') {
76
- const config_files = ['js', 'ts']
77
- .map((ext) => `svelte.config.${ext}`)
78
- .filter((f) => fs.existsSync(f));
79
- if (config_files.length === 0) {
80
- console.warn(`Missing Svelte config file in ${process.cwd()} — skipping`);
81
- process.exit(0);
77
+ // create placeholder .svelte-kit/tsconfig.json if necessary, to squelch warnings.
78
+ // this isn't bulletproof — if someone has some esoteric config, it will continue
79
+ // to harmlessly warn — but we handle the 90% case and clean up after ourselves
80
+ const sveltekit_dir = '.svelte-kit';
81
+ const base_tsconfig = `${sveltekit_dir}/tsconfig.json`;
82
+ const base_tsconfig_json = '{}';
83
+
84
+ const sveltekit_dir_exists = fs.existsSync(sveltekit_dir);
85
+ const base_tsconfig_exists = fs.existsSync(base_tsconfig);
86
+
87
+ if (!base_tsconfig_exists) {
88
+ try {
89
+ fs.mkdirSync('.svelte-kit');
90
+ } catch {
91
+ // ignore
92
+ }
93
+
94
+ fs.writeFileSync(base_tsconfig, base_tsconfig_json);
82
95
  }
83
96
 
84
97
  try {
85
98
  const config = await load_config();
86
99
  const sync = await import('./core/sync/sync.js');
87
100
  sync.all_types(config, values.mode);
101
+
102
+ const explicit_env_entry = resolve_explicit_env_entry(config.kit);
103
+ await sync.env(config.kit, explicit_env_entry, values.mode);
88
104
  } catch (error) {
89
105
  handle_error(error);
106
+ } finally {
107
+ // if we errored, or accidentally created the wrong file
108
+ // (could happen!) then clean up after ourselves
109
+ if (fs.readFileSync(base_tsconfig, 'utf-8') === base_tsconfig_json) {
110
+ fs.unlinkSync(base_tsconfig);
111
+ }
112
+
113
+ if (!sveltekit_dir_exists && fs.readdirSync(sveltekit_dir).length === 0) {
114
+ fs.rmSync(sveltekit_dir, { recursive: true });
115
+ }
90
116
  }
91
117
  } else {
92
118
  console.error(colors.bold().red(`> Unknown command: ${command}`));
@@ -1,8 +1,10 @@
1
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
1
2
  /** @import { Builder } from '@sveltejs/kit' */
2
3
  /** @import { ResolvedConfig } from 'vite' */
3
- /** @import { RouteDefinition } from '@sveltejs/kit' */
4
+ /** @import { RouteDefinition, EnvVarConfig } from '@sveltejs/kit' */
4
5
  /** @import { RouteData, ValidatedConfig, BuildData, ServerMetadata, ServerMetadataRoute, Prerendered, PrerenderMap, Logger, RemoteChunk } from 'types' */
5
6
  import colors from 'kleur';
7
+ import * as devalue from 'devalue';
6
8
  import { createReadStream, createWriteStream, existsSync, statSync } from 'node:fs';
7
9
  import { extname, resolve, join, dirname, relative } from 'node:path';
8
10
  import { pipeline } from 'node:stream';
@@ -17,6 +19,7 @@ import { write } from '../sync/utils.js';
17
19
  import { list_files } from '../utils.js';
18
20
  import { find_server_assets } from '../generate_manifest/find_server_assets.js';
19
21
  import { reserved } from '../env.js';
22
+ import { handle_issues, validate } from '../../exports/internal/env.js';
20
23
 
21
24
  const pipe = promisify(pipeline);
22
25
  const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.wasm', '.txt'];
@@ -32,7 +35,8 @@ const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.w
32
35
  * prerender_map: PrerenderMap;
33
36
  * log: Logger;
34
37
  * vite_config: ResolvedConfig;
35
- * remotes: RemoteChunk[]
38
+ * remotes: RemoteChunk[];
39
+ * explicit_env_config: Record<string, EnvVarConfig<any>> | null;
36
40
  * }} opts
37
41
  * @returns {Builder}
38
42
  */
@@ -45,7 +49,8 @@ export function create_builder({
45
49
  prerender_map,
46
50
  log,
47
51
  vite_config,
48
- remotes
52
+ remotes,
53
+ explicit_env_config
49
54
  }) {
50
55
  /** @type {Map<RouteDefinition, RouteData>} */
51
56
  const lookup = new Map();
@@ -168,7 +173,7 @@ export function create_builder({
168
173
 
169
174
  const fallback = await generate_fallback({
170
175
  manifest_path,
171
- env: { ...env.private, ...env.public },
176
+ env: env.all,
172
177
  out_dir: config.kit.outDir,
173
178
  origin: config.kit.prerender.origin,
174
179
  assets: config.kit.files.assets
@@ -193,7 +198,23 @@ export function create_builder({
193
198
  const dest = `${config.kit.outDir}/output/prerendered/dependencies/${config.kit.appDir}/env.js`;
194
199
  const env = get_env(config.kit.env, vite_config.mode);
195
200
 
196
- write(dest, `export const env=${JSON.stringify(env.public)}`);
201
+ const values = config.kit.experimental.explicitEnvironmentVariables ? {} : env.public;
202
+
203
+ if (config.kit.experimental.explicitEnvironmentVariables) {
204
+ const variables = explicit_env_config ?? {};
205
+
206
+ /** @type {Record<string, StandardSchemaV1.Issue[]>} */
207
+ const issues = {};
208
+
209
+ for (const [name, config] of Object.entries(variables)) {
210
+ if (config.static || !config.public) continue;
211
+ values[name] = validate(variables, env.all[name], name, issues);
212
+ }
213
+
214
+ handle_issues(issues);
215
+ }
216
+
217
+ write(dest, `export const env=${devalue.uneval(values)}`);
197
218
  },
198
219
 
199
220
  generateManifest({ relativePath, routes: subset }) {
@@ -10,6 +10,7 @@ import { create_builder } from './builder.js';
10
10
  * @param {import('types').Logger} log
11
11
  * @param {import('types').RemoteChunk[]} remotes
12
12
  * @param {import('vite').ResolvedConfig} vite_config
13
+ * @param {Record<string, import('@sveltejs/kit').EnvVarConfig<any>> | null} explicit_env_config
13
14
  */
14
15
  export async function adapt(
15
16
  config,
@@ -19,7 +20,8 @@ export async function adapt(
19
20
  prerender_map,
20
21
  log,
21
22
  remotes,
22
- vite_config
23
+ vite_config,
24
+ explicit_env_config
23
25
  ) {
24
26
  // This is only called when adapter is truthy, so the cast is safe
25
27
  const { name, adapt } = /** @type {import('@sveltejs/kit').Adapter} */ (config.kit.adapter);
@@ -35,7 +37,8 @@ export async function adapt(
35
37
  prerender_map,
36
38
  log,
37
39
  remotes,
38
- vite_config
40
+ vite_config,
41
+ explicit_env_config
39
42
  });
40
43
 
41
44
  await adapt(builder);
@@ -33,11 +33,13 @@ export function load_template(cwd, { kit }) {
33
33
  }
34
34
  });
35
35
 
36
- for (const match of contents.matchAll(/%sveltekit\.env\.([^%]+)%/g)) {
37
- if (!match[1].startsWith(env.publicPrefix)) {
38
- throw new Error(
39
- `Environment variables in ${relative} must start with ${env.publicPrefix} (saw %sveltekit.env.${match[1]}%)`
40
- );
36
+ if (!kit.experimental.explicitEnvironmentVariables) {
37
+ for (const match of contents.matchAll(/%sveltekit\.env\.([^%]+)%/g)) {
38
+ if (!match[1].startsWith(env.publicPrefix)) {
39
+ throw new Error(
40
+ `Environment variables in ${relative} must start with ${env.publicPrefix} (saw %sveltekit.env.${match[1]}%)`
41
+ );
42
+ }
41
43
  }
42
44
  }
43
45
 
@@ -139,6 +139,7 @@ const options = object(
139
139
  instrumentation: object({
140
140
  server: boolean(false)
141
141
  }),
142
+ explicitEnvironmentVariables: boolean(false),
142
143
  remoteFunctions: boolean(false),
143
144
  forkPreloads: boolean(false),
144
145
  handleRenderingErrors: boolean(false)
package/src/core/env.js CHANGED
@@ -1,19 +1,126 @@
1
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
2
+ /** @import { EnvVarConfig } from '@sveltejs/kit' */
3
+ /** @import { ValidatedKitConfig } from 'types' */
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import * as vite from 'vite';
7
+ import * as devalue from 'devalue';
1
8
  import { GENERATED_COMMENT } from '../constants.js';
2
9
  import { dedent } from './sync/utils.js';
3
- import { runtime_base } from './utils.js';
10
+ import { runtime_base, runtime_directory } from './utils.js';
11
+ import { resolve_entry } from '../utils/filesystem.js';
12
+ import { handle_issues, validate } from '../exports/internal/env.js';
13
+ import { get_config_aliases } from '../exports/vite/utils.js';
4
14
 
5
15
  /**
6
16
  * @typedef {'public' | 'private'} EnvType
7
17
  */
8
18
 
19
+ let warned = false;
20
+
21
+ /**
22
+ * @param {import('types').ValidatedKitConfig} config
23
+ * @returns {string | null}
24
+ */
25
+ export function resolve_explicit_env_entry(config) {
26
+ const resolved = resolve_entry(path.join(config.files.src, 'env'));
27
+
28
+ if (resolved) {
29
+ if (config.experimental.explicitEnvironmentVariables) {
30
+ return resolved;
31
+ }
32
+
33
+ if (!warned) {
34
+ console.warn(
35
+ `${path.relative(process.cwd(), resolved)} requires the \`experimental.explicitEnvironmentVariables\` flag to be set`
36
+ );
37
+ warned = true;
38
+ }
39
+ } else if (config.experimental.explicitEnvironmentVariables) {
40
+ console.warn(
41
+ 'experimental.explicitEnvironmentVariables was set, but no src/env.ts or src/env.js file could be found'
42
+ );
43
+ }
44
+
45
+ return null;
46
+ }
47
+
48
+ /**
49
+ * @param {ValidatedKitConfig} kit
50
+ * @param {string | null} file
51
+ * @param {string} mode
52
+ * @returns {Promise<Record<string, EnvVarConfig<any>> | null>}
53
+ */
54
+ export async function load_explicit_env(kit, file, mode) {
55
+ if (!file) return null;
56
+
57
+ const server = await vite.createServer({
58
+ configFile: false,
59
+ logLevel: 'silent',
60
+ mode,
61
+ define: {
62
+ __SVELTEKIT_APP_VERSION__: JSON.stringify(kit.version.name) // needed by $app/env
63
+ },
64
+ resolve: {
65
+ alias: [
66
+ { find: '$app/env', replacement: `${runtime_directory}/app/env` },
67
+ ...get_config_aliases(kit)
68
+ ]
69
+ }
70
+ });
71
+
72
+ /** @type {Record<string, EnvVarConfig<any>>} */
73
+ let variables;
74
+
75
+ try {
76
+ ({ variables } = await server.ssrLoadModule(file));
77
+
78
+ if (!variables || typeof variables !== 'object') {
79
+ throw new Error(`${file} must export a variables object`);
80
+ }
81
+
82
+ // validate
83
+ for (const name of Object.keys(variables)) {
84
+ if (!valid_identifier.test(name) || reserved.has(name)) {
85
+ throw new Error(`Invalid environment variable name ${JSON.stringify(name)}`);
86
+ }
87
+ }
88
+ } catch (e) {
89
+ const error = /** @type {any} */ (e || {});
90
+
91
+ if (
92
+ error.code === 'ERR_MODULE_NOT_FOUND' &&
93
+ error.message?.includes(`Cannot find module '$app`)
94
+ ) {
95
+ throw new Error(
96
+ `Cannot import \`$app/*\` modules other than \`$app/env\` inside \`src/env\``,
97
+ { cause: e }
98
+ );
99
+ }
100
+
101
+ throw error;
102
+ } finally {
103
+ await server.close();
104
+ }
105
+
106
+ return variables;
107
+ }
108
+
9
109
  /**
10
110
  * @param {string} id
11
111
  * @param {Record<string, string>} env
112
+ * @param {boolean} disabled
12
113
  * @returns {string}
13
114
  */
14
- export function create_static_module(id, env) {
115
+ export function create_static_module(id, env, disabled) {
15
116
  /** @type {string[]} */
16
- const declarations = [];
117
+ const statements = [];
118
+
119
+ if (disabled) {
120
+ statements.push(
121
+ `throw new Error('Cannot import \`${id}\` when \`experimental.explicitEnvironmentVariables\` is enabled. Use \`${id.replace('$env/static', '$app/env')}\` instead.');`
122
+ );
123
+ }
17
124
 
18
125
  for (const key in env) {
19
126
  if (!valid_identifier.test(key) || reserved.has(key)) {
@@ -23,24 +130,199 @@ export function create_static_module(id, env) {
23
130
  const comment = `/** @type {import('${id}').${key}} */`;
24
131
  const declaration = `export const ${key} = ${JSON.stringify(env[key])};`;
25
132
 
26
- declarations.push(`${comment}\n${declaration}`);
133
+ statements.push(`${comment}\n${declaration}`);
27
134
  }
28
135
 
29
- return GENERATED_COMMENT + declarations.join('\n\n');
136
+ return GENERATED_COMMENT + statements.join('\n\n');
30
137
  }
31
138
 
32
139
  /**
33
140
  * @param {EnvType} type
34
141
  * @param {Record<string, string> | undefined} dev_values If in a development mode, values to pre-populate the module with.
142
+ * @param {boolean} disabled
35
143
  */
36
- export function create_dynamic_module(type, dev_values) {
144
+ export function create_dynamic_module(type, dev_values, disabled) {
145
+ const prelude = disabled
146
+ ? `throw new Error('Cannot import \`$env/dynamic/${type}\` when \`experimental.explicitEnvironmentVariables\` is enabled. Use \`$app/env/${type}\` instead.');\n\n`
147
+ : '';
148
+
37
149
  if (dev_values) {
38
150
  const keys = Object.entries(dev_values).map(
39
151
  ([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`
40
152
  );
41
- return `export const env = {\n${keys.join(',\n')}\n}`;
153
+ return `${prelude}export const env = {\n${keys.join(',\n')}\n}`;
154
+ }
155
+ return `${prelude}export { ${type}_env as env } from '${runtime_base}/shared-server.js';`;
156
+ }
157
+
158
+ /**
159
+ * Creates the `__sveltekit/env` module
160
+ * @param {Record<string, EnvVarConfig<any>> | null} variables
161
+ * @param {Record<string, string>} env
162
+ * @param {string | null} entry
163
+ */
164
+ export function create_sveltekit_env(variables, env, entry) {
165
+ const imports = entry
166
+ ? [
167
+ `import { variables } from ${JSON.stringify(entry)};`,
168
+ `import { validate, handle_issues } from '@sveltejs/kit/internal/env';`
169
+ ]
170
+ : [`const variables = {};`, `const handle_issues = () => {};`];
171
+
172
+ const declarations = [];
173
+ const setters = [];
174
+
175
+ /** @type {Record<string, StandardSchemaV1.Issue[]>} */
176
+ const issues = {};
177
+
178
+ for (const [name, config] of Object.entries(variables ?? {})) {
179
+ if (config.static) {
180
+ if (config.public) {
181
+ const value = validate(variables ?? {}, env[name], name, issues);
182
+ declarations.push(`explicit_public_env.${name} = ${devalue.uneval(value)};`);
183
+ }
184
+ } else {
185
+ setters.push(
186
+ `const ${name} = validate(variables, env.${name}, ${JSON.stringify(name)}, issues);`
187
+ );
188
+
189
+ if (config.public) {
190
+ setters.push(`explicit_public_env.${name} = ${name};`);
191
+ setters.push(`rendered_env.${name} = ${name};`);
192
+ } else {
193
+ setters.push(`dynamic_private_env.${name} = ${name};`);
194
+ }
195
+ }
196
+ }
197
+
198
+ handle_issues(issues);
199
+
200
+ const blocks = [
201
+ GENERATED_COMMENT,
202
+ imports.join('\n'),
203
+ `const issues = {};`,
204
+ 'export { variables }',
205
+ 'export const dynamic_private_env = {};',
206
+ 'export const explicit_public_env = {};',
207
+ 'export const rendered_env = {};',
208
+ ...declarations,
209
+ `handle_issues(issues);`,
210
+ dedent`
211
+ export function set_env(env) {
212
+ const issues = {};
213
+ ${setters.join('\n')}
214
+ handle_issues(issues);
215
+ }`
216
+ ];
217
+
218
+ const module = blocks.join('\n\n');
219
+
220
+ return module;
221
+ }
222
+
223
+ /**
224
+ * Creates the `__sveltekit/env/private` module
225
+ * @param {Record<string, EnvVarConfig<any>> | null} variables
226
+ * @param {Record<string, string>} env
227
+ */
228
+ export function create_sveltekit_env_private(variables, env) {
229
+ if (!variables) {
230
+ return '';
231
+ }
232
+
233
+ /** @type {Record<string, StandardSchemaV1.Issue[]>} */
234
+ const issues = {};
235
+
236
+ /** @type {string[]} */
237
+ const exports = [];
238
+
239
+ for (const [name, config] of Object.entries(variables)) {
240
+ if (config.public) continue;
241
+
242
+ const value = config.static
243
+ ? devalue.uneval(validate(variables, env[name], name, issues))
244
+ : `env.${name}`;
245
+
246
+ exports.push(`export const ${name} = ${value};\n`);
247
+ }
248
+
249
+ handle_issues(issues);
250
+
251
+ return `import { dynamic_private_env as env } from '__sveltekit/env';\n\n${exports.join('')}`;
252
+ }
253
+
254
+ /**
255
+ * Creates the `__sveltekit/env/public/*` modules
256
+ * @param {Record<string, EnvVarConfig<any>> | null} variables
257
+ * @param {Record<string, string>} env
258
+ * @param {string} prelude
259
+ */
260
+ export function create_sveltekit_env_public(variables, env, prelude) {
261
+ if (!variables) {
262
+ return '';
263
+ }
264
+
265
+ /** @type {Record<string, StandardSchemaV1.Issue[]>} */
266
+ const issues = {};
267
+
268
+ /** @type {string[]} */
269
+ const exports = [];
270
+
271
+ for (const [name, config] of Object.entries(variables)) {
272
+ if (!config.public) continue;
273
+
274
+ const value = config.static
275
+ ? devalue.uneval(validate(variables, env[name], name, issues))
276
+ : `env.${name}`;
277
+
278
+ exports.push(`export const ${name} = ${value};\n`);
42
279
  }
43
- return `export { ${type}_env as env } from '${runtime_base}/shared-server.js';`;
280
+
281
+ handle_issues(issues);
282
+
283
+ return `${prelude}\n\n${exports.join('')}`;
284
+ }
285
+
286
+ /**
287
+ * Creates the `__sveltekit/env/service-worker` module used in development
288
+ * (but not in prod, which goes through build_service_worker instead)
289
+ * @param {Record<string, EnvVarConfig<any>> | null} variables
290
+ * @param {Record<string, string>} env
291
+ * @param {string} global
292
+ */
293
+ export function create_sveltekit_env_service_worker_dev(variables, env, global) {
294
+ /** @type {string[]} */
295
+ const properties = [];
296
+
297
+ /** @type {Record<string, StandardSchemaV1.Issue[]>} */
298
+ const issues = {};
299
+
300
+ for (const [name, config] of Object.entries(variables ?? {})) {
301
+ if (!config.public) continue;
302
+
303
+ const value = validate(variables ?? {}, env[name], name, issues);
304
+ properties.push(`${name}: ${devalue.uneval(value)}`);
305
+ }
306
+
307
+ handle_issues(issues);
308
+
309
+ return dedent`
310
+ globalThis.__SVELTEKIT_EXPERIMENTAL_EXPLICIT_ENVIRONMENT_VARIABLES__ = true;
311
+
312
+ ${global} = {
313
+ env: {
314
+ ${properties.join(',\n\t\t') || '// empty'}
315
+ }
316
+ };
317
+ `;
318
+ }
319
+
320
+ /** @param {string} description */
321
+ function create_jsdoc(description) {
322
+ return `/**\n${description
323
+ .split('\n')
324
+ .map((line) => ` * ${line.replaceAll('*/', '*\\/')}`)
325
+ .join('\n')}\n */`;
44
326
  }
45
327
 
46
328
  /**
@@ -98,6 +380,29 @@ export function create_dynamic_types(id, env, { public_prefix, private_prefix })
98
380
  `;
99
381
  }
100
382
 
383
+ /**
384
+ * @param {Record<string, EnvVarConfig<any>>} variables
385
+ * @param {string} relative
386
+ * @param {EnvType} type
387
+ */
388
+ export function create_explicit_env_types(variables, relative, type) {
389
+ const declarations = Object.entries(variables)
390
+ .filter(([_, config]) => !!config.public === (type === 'public'))
391
+ .map(([name, config]) => {
392
+ const comment = config.description ? `${create_jsdoc(config.description)}\n` : '';
393
+ const type = config.schema
394
+ ? `import('@sveltejs/kit/internal/types').StandardSchemaV1.InferOutput<typeof import('${relative}').variables.${name}.schema>`
395
+ : 'string';
396
+ return `${comment}export const ${name}: ${type};`;
397
+ });
398
+
399
+ return dedent`
400
+ declare module '$app/env/${type}' {
401
+ ${declarations.join('\n') || `// no ${type} environment variables were defined`}
402
+ }
403
+ `;
404
+ }
405
+
101
406
  export const reserved = new Set([
102
407
  'do',
103
408
  'if',
@@ -50,7 +50,7 @@ async function analyse({
50
50
 
51
51
  installPolyfills();
52
52
 
53
- // configure `import { building } from '$app/environment'` —
53
+ // configure `import { building } from '$app/environment'` and `$app/env`
54
54
  // essential we do this before analysing the code
55
55
  internal.set_building();
56
56
 
@@ -60,6 +60,7 @@ async function analyse({
60
60
  const public_env = filter_env(env, public_prefix, private_prefix);
61
61
  internal.set_private_env(private_env);
62
62
  internal.set_public_env(public_env);
63
+ internal.set_env(env);
63
64
  internal.set_manifest(manifest);
64
65
  internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`));
65
66
 
@@ -46,7 +46,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
46
46
  /** @type {import('types').ServerModule} */
47
47
  const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
48
48
 
49
- // configure `import { building } from '$app/environment'` —
49
+ // configure `import { building } from '$app/environment'` and `$app/env`
50
50
  // essential we do this before analysing the code
51
51
  internal.set_building();
52
52
  internal.set_prerendering();
@@ -497,6 +497,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
497
497
  const public_env = filter_env(env, public_prefix, private_prefix);
498
498
  internal.set_private_env(private_env);
499
499
  internal.set_public_env(public_env);
500
+ internal.set_env(env);
500
501
  internal.set_manifest(manifest);
501
502
  internal.set_read_implementation((file) => createReadableStream(`${out}/server/${file}`));
502
503
 
@@ -11,6 +11,8 @@ import {
11
11
  create_node_analyser,
12
12
  get_page_options
13
13
  } from '../../exports/vite/static_analysis/index.js';
14
+ import { load_explicit_env } from '../env.js';
15
+ import { write_env } from './write_env.js';
14
16
 
15
17
  /**
16
18
  * Initialize SvelteKit's generated files that only depend on the config and mode.
@@ -87,6 +89,20 @@ export function all_types(config, mode) {
87
89
  write_non_ambient(config.kit, manifest_data);
88
90
  }
89
91
 
92
+ /**
93
+ * Generate modules and types for explicit env vars
94
+ * @param {import('types').ValidatedKitConfig} kit
95
+ * @param {string | null} entry
96
+ * @param {string} mode The Vite mode
97
+ */
98
+ export async function env(kit, entry, mode) {
99
+ const env_config = await load_explicit_env(kit, entry, mode);
100
+
101
+ write_env(kit, entry, env_config);
102
+
103
+ return env_config;
104
+ }
105
+
90
106
  /**
91
107
  * Regenerate __SERVER__/internal.js in response to src/{app.html,error.html,service-worker.js} changing
92
108
  * @param {import('types').ValidatedConfig} config
@@ -53,11 +53,17 @@ ${create_dynamic_types('public', env, prefixes)}
53
53
  * @param {string} mode The Vite mode
54
54
  */
55
55
  export function write_ambient(config, mode) {
56
- const env = get_env(config.env, mode);
57
- const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env;
56
+ /** @type {string} */
57
+ let content;
58
58
 
59
- write_if_changed(
60
- path.join(config.outDir, 'ambient.d.ts'),
61
- template(env, { public_prefix, private_prefix })
62
- );
59
+ if (config.experimental.explicitEnvironmentVariables) {
60
+ content = `${GENERATED_COMMENT}\n/// <reference types="@sveltejs/kit" />`;
61
+ } else {
62
+ const env = get_env(config.env, mode);
63
+ const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env;
64
+
65
+ content = template(env, { public_prefix, private_prefix });
66
+ }
67
+
68
+ write_if_changed(path.join(config.outDir, 'ambient.d.ts'), content);
63
69
  }