Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add privatePrefix for private environment variables #9996

Merged
merged 12 commits into from
Jun 28, 2023
5 changes: 5 additions & 0 deletions .changeset/stupid-mayflies-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: add `privatePrefix` to `config.kit.env`
3 changes: 2 additions & 1 deletion packages/kit/src/core/config/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ const get_defaults = (prefix = '') => ({
embedded: false,
env: {
dir: process.cwd(),
publicPrefix: 'PUBLIC_'
publicPrefix: 'PUBLIC_',
privatePrefix: ''
},
files: {
assets: join(prefix, 'static'),
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/core/config/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ const options = object(

env: object({
dir: string(process.cwd()),
publicPrefix: string('PUBLIC_')
publicPrefix: string('PUBLIC_'),
privatePrefix: string('')
}),

files: object({
Expand Down
21 changes: 15 additions & 6 deletions packages/kit/src/core/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,30 @@ export function create_static_types(id, env) {
/**
* @param {EnvType} id
* @param {import('types').Env} env
* @param {string} prefix
* @param {{
* public_prefix: string;
* private_prefix: string;
* }} prefixes
* @returns {string}
*/
export function create_dynamic_types(id, env, prefix) {
export function create_dynamic_types(id, env, { public_prefix, private_prefix }) {
const properties = Object.keys(env[id])
.filter((k) => valid_identifier.test(k))
.map((k) => `${k}: string;`);

const prefixed = `[key: \`${prefix}\${string}\`]`;
const public_prefixed = `[key: \`${public_prefix}\${string}\`]`;
const private_prefixed = `[key: \`${private_prefix}\${string}\`]`;

if (id === 'private') {
properties.push(`${prefixed}: undefined;`);
properties.push('[key: string]: string | undefined;');
if (public_prefix) {
properties.push(`${public_prefixed}: undefined;`);
}
properties.push(`${private_prefixed}: string | undefined;`);
} else {
properties.push(`${prefixed}: string | undefined;`);
if (private_prefix) {
properties.push(`${private_prefixed}: undefined;`);
}
properties.push(`${public_prefixed}: string | undefined;`);
}

return dedent`
Expand Down
8 changes: 4 additions & 4 deletions packages/kit/src/core/postbuild/analyse.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { forked } from '../../utils/fork.js';
import { should_polyfill } from '../../utils/platform.js';
import { installPolyfills } from '../../exports/node/polyfills.js';
import { resolvePath } from '../../exports/index.js';
import { filter_private_env, filter_public_env } from '../../utils/env.js';

export default forked(import.meta.url, analyse);

Expand Down Expand Up @@ -43,10 +44,9 @@ async function analyse({ manifest_path, env }) {
internal.set_building(true);

// set env, in case it's used in initialisation
const entries = Object.entries(env);
const prefix = config.env.publicPrefix;
internal.set_private_env(Object.fromEntries(entries.filter(([k]) => !k.startsWith(prefix))));
internal.set_public_env(Object.fromEntries(entries.filter(([k]) => k.startsWith(prefix))));
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env;
internal.set_private_env(filter_private_env(env, { public_prefix, private_prefix }));
internal.set_public_env(filter_public_env(env, { public_prefix, private_prefix }));

/** @type {import('types').ServerMetadata} */
const metadata = {
Expand Down
14 changes: 9 additions & 5 deletions packages/kit/src/core/sync/write_ambient.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ function read_description(filename) {

/**
* @param {import('types').Env} env
* @param {string} prefix
* @param {{
* public_prefix: string;
* private_prefix: string;
* }} prefixes
*/
const template = (env, prefix) => `
const template = (env, prefixes) => `
${GENERATED_COMMENT}

/// <reference types="@sveltejs/kit" />
Expand All @@ -36,10 +39,10 @@ ${read_description('$env+static+public.md')}
${create_static_types('public', env)}

${read_description('$env+dynamic+private.md')}
${create_dynamic_types('private', env, prefix)}
${create_dynamic_types('private', env, prefixes)}

${read_description('$env+dynamic+public.md')}
${create_dynamic_types('public', env, prefix)}
${create_dynamic_types('public', env, prefixes)}
`;

/**
Expand All @@ -51,9 +54,10 @@ ${create_dynamic_types('public', env, prefix)}
*/
export function write_ambient(config, mode) {
const env = get_env(config.env, mode);
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env;

write_if_changed(
path.join(config.outDir, 'ambient.d.ts'),
template(env, config.env.publicPrefix)
template(env, { public_prefix, private_prefix })
);
}
1 change: 1 addition & 0 deletions packages/kit/src/core/sync/write_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const options = {
track_server_fetches: ${s(config.kit.dangerZone.trackServerFetches)},
embedded: ${config.kit.embedded},
env_public_prefix: '${config.kit.env.publicPrefix}',
env_private_prefix: '${config.kit.env.privatePrefix}',
hooks: null, // added lazily, via \`get_hooks\`
preload_strategy: ${s(config.kit.output.preloadStrategy)},
root,
Expand Down
5 changes: 5 additions & 0 deletions packages/kit/src/exports/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ export interface KitConfig {
* @default "PUBLIC_"
*/
publicPrefix?: string;
/**
* A prefix that signals that an environment variable is unsafe to expose to client-side code. See [`$env/static/private`](/docs/modules#$env-static-private) and [`$env/dynamic/private`](/docs/modules#$env-dynamic-private).
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved
* @default ""
*/
privatePrefix?: string;
};
/**
* Where to find various files within your project.
Expand Down
8 changes: 5 additions & 3 deletions packages/kit/src/exports/vite/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'node:path';
import { loadEnv } from 'vite';
import { posixify } from '../../utils/filesystem.js';
import { negotiate } from '../../utils/http.js';
import { filter_private_env, filter_public_env } from '../../utils/env.js';

/**
* Transforms kit.alias to a valid vite.resolve.alias array.
Expand Down Expand Up @@ -56,11 +57,12 @@ function escape_for_regexp(str) {
* @param {string} mode
*/
export function get_env(env_config, mode) {
const entries = Object.entries(loadEnv(mode, env_config.dir, ''));
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = env_config;
const env = loadEnv(mode, env_config.dir, '');

return {
public: Object.fromEntries(entries.filter(([k]) => k.startsWith(env_config.publicPrefix))),
private: Object.fromEntries(entries.filter(([k]) => !k.startsWith(env_config.publicPrefix)))
public: filter_public_env(env, { public_prefix, private_prefix }),
private: filter_private_env(env, { public_prefix, private_prefix })
};
}

Expand Down
22 changes: 14 additions & 8 deletions packages/kit/src/runtime/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { respond } from './respond.js';
import { set_private_env, set_public_env } from '../shared-server.js';
import { options, get_hooks } from '__SERVER__/internal.js';
import { DEV } from 'esm-env';
import { filter_private_env, filter_public_env } from '../../utils/env.js';

export class Server {
/** @type {import('types').SSROptions} */
Expand All @@ -26,14 +27,19 @@ export class Server {
// Take care: Some adapters may have to call `Server.init` per-request to set env vars,
// so anything that shouldn't be rerun should be wrapped in an `if` block to make sure it hasn't
// been done already.
const entries = Object.entries(env);

const prefix = this.#options.env_public_prefix;
const prv = Object.fromEntries(entries.filter(([k]) => !k.startsWith(prefix)));
const pub = Object.fromEntries(entries.filter(([k]) => k.startsWith(prefix)));

set_private_env(prv);
set_public_env(pub);
// set env, in case it's used in initialisation
set_private_env(
filter_private_env(env, {
public_prefix: this.#options.env_public_prefix,
private_prefix: this.#options.env_private_prefix
})
);
set_public_env(
filter_public_env(env, {
public_prefix: this.#options.env_public_prefix,
private_prefix: this.#options.env_private_prefix
})
);

if (!this.#options.hooks) {
try {
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/types/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ export interface SSROptions {
track_server_fetches: boolean;
embedded: boolean;
env_public_prefix: string;
env_private_prefix: string;
hooks: ServerHooks;
preload_strategy: ValidatedConfig['kit']['output']['preloadStrategy'];
root: SSRComponent['default'];
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/types/synthetic/$env+dynamic+private.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) (or running [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env).
This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) (or running [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://kit.svelte.dev/docs/configuration#env) (if configured).

This module cannot be imported into client-side code.

Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/types/synthetic/$env+static+private.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), this module cannot be imported into client-side code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env).
Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), this module cannot be imported into client-side code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://kit.svelte.dev/docs/configuration#env) (if configured).

_Unlike_ [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), the values exported from this module are statically injected into your bundle at build time, enabling optimisations like dead code elimination.

Expand Down
33 changes: 33 additions & 0 deletions packages/kit/src/utils/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @param {Record<string, string>} env
* @param {{
* public_prefix: string;
* private_prefix: string;
* }} prefixes
* @returns {Record<string, string>}
*/
export function filter_private_env(env, { public_prefix, private_prefix }) {
return Object.fromEntries(
Object.entries(env).filter(
([k]) =>
k.startsWith(private_prefix) && (public_prefix === '' || !k.startsWith(public_prefix))
)
);
}

/**
* @param {Record<string, string>} env
* @param {{
* public_prefix: string;
* private_prefix: string;
* }} prefixes
* @returns {Record<string, string>}
*/
export function filter_public_env(env, { public_prefix, private_prefix }) {
return Object.fromEntries(
Object.entries(env).filter(
([k]) =>
k.startsWith(public_prefix) && (private_prefix === '' || !k.startsWith(private_prefix))
)
);
}
2 changes: 1 addition & 1 deletion packages/kit/test/apps/embed/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "test-options",
"name": "test-embed",
"private": true,
"version": "0.0.1",
"scripts": {
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/test/apps/options/env-dir/.env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
GO_AWAY_PLEASE=and thank you
GO_AWAY_PLEASE=and thank you
TOP_SECRET_SHH_PLS=shhhh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TOP_SECRET_SHH_PLS } from '$env/static/private';

export function load() {
return { TOP_SECRET_SHH_PLS };
}
4 changes: 3 additions & 1 deletion packages/kit/test/apps/options/source/pages/env/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script>
import { GO_AWAY_PLEASE } from '$env/static/public';
export let data;
</script>

<p>{GO_AWAY_PLEASE}</p>
<p id="public">{GO_AWAY_PLEASE}</p>
<p id="private">{data.TOP_SECRET_SHH_PLS}</p>
3 changes: 2 additions & 1 deletion packages/kit/test/apps/options/svelte.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const config = {
},
env: {
dir: './env-dir',
publicPrefix: 'GO_AWAY_'
publicPrefix: 'GO_AWAY_',
privatePrefix: 'TOP_SECRET_SHH'
}
}
};
Expand Down
6 changes: 5 additions & 1 deletion packages/kit/test/apps/options/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ test.describe('Custom extensions', () => {
test.describe('env', () => {
test('resolves downwards', async ({ page }) => {
await page.goto('/path-base/env');
expect(await page.textContent('p')).toBe('and thank you');
expect(await page.textContent('#public')).toBe('and thank you');
});
test('respects private prefix', async ({ page }) => {
await page.goto('/path-base/env');
expect(await page.textContent('#private')).toBe('shhhh');
});
});

Expand Down