Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/scripts/packed-consumer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ try {
)
.trim()
.split('\n'),
['node_modules/dependency', '.env', '.env.local', '.env.production'],
'Init must ignore local environment files while allowing the example',
['node_modules/dependency', '.env', '.env.local', '.env.production', '.env.example'],
'Init must ignore local environment files',
);
writeFileSync(
join(fixture, '.npmrc'),
Expand Down
3 changes: 2 additions & 1 deletion packages/void/src/cli/env-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
findLegacyEnvFiles,
findReservedEnvKeys,
isClientEnvKey,
legacyEnvExampleHint,
loadLocalEnv,
LOCAL_ENV_FILE,
reservedEnvKeysMessage,
Expand Down Expand Up @@ -110,7 +111,7 @@ export async function validateProdEnv(
const legacyFiles = findLegacyEnvFiles(root);
if (legacyFiles.length > 0) {
throw new Error(
`env: only .env is supported for local development. Remove ${legacyFiles.join(', ')} and follow the env migration guide.`,
`env: only .env is supported for local development. Remove ${legacyFiles.join(', ')} and follow the env migration guide.${legacyEnvExampleHint(legacyFiles)}`,
);
}
// Same rule as the Vite plugin, which stops dev and build on these names.
Expand Down
1 change: 0 additions & 1 deletion packages/void/src/cli/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export const SCAFFOLD_GITIGNORE_LINES = [
'.void-wrangler.jsonc',
'.env',
'.env.*',
'!.env.example',
'*.tsbuildinfo',
'.DS_Store',
] as const;
Expand Down
3 changes: 2 additions & 1 deletion packages/void/src/plugin-env-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
findLegacyEnvFiles,
findReservedEnvKeys,
isClientEnvKey,
legacyEnvExampleHint,
loadLocalEnv,
LOCAL_ENV_FILE,
reservedEnvKeysMessage,
Expand Down Expand Up @@ -328,7 +329,7 @@ export function envSchemaPlugin(root: string, options: EnvSchemaPluginOptions =
const legacyFiles = findLegacyEnvFiles(root);
if (legacyFiles.length > 0) {
throw new Error(
`void/env supports one local file: .env. Remove ${legacyFiles.join(', ')} and follow the env migration guide.`,
`void/env supports one local file: .env. Remove ${legacyFiles.join(', ')} and follow the env migration guide.${legacyEnvExampleHint(legacyFiles)}`,
);
}
// Every mode (serve, build, preview), with or without env.ts. Only the
Expand Down
10 changes: 10 additions & 0 deletions packages/void/src/shared/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ export function findLegacyEnvFiles(root: string): Array<string> {
}
}

/**
* Sentence to append to a legacy-file error when `.env.example` is among the
* files, since it is usually added out of habit to document expected keys.
*/
export function legacyEnvExampleHint(legacyFiles: ReadonlyArray<string>): string {
return legacyFiles.includes('.env.example')
? ' env.ts lists the keys this app expects, so .env.example is not needed.'
: '';
}

/**
* Resolve whether the plugin should run in "remote" mode (proxy real
* D1/KV/R2 via the platform) based on the project config plus an optional
Expand Down
10 changes: 10 additions & 0 deletions packages/void/test/integration/env-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ describe('void env check (subprocess)', () => {
expect(status).toBe(1);
expect(stdout).toContain('only .env is supported');
expect(stdout).toContain('.env.local');
expect(stdout).not.toContain('env.ts lists the keys');
});

it('points .env.example users at env.ts', () => {
const root = createProject('{ STRIPE_KEY: string() }');
writeFileSync(join(root, '.env.example'), 'STRIPE_KEY=\n');
const { stdout, status } = runVoid(root, ['env', 'check']);
expect(status).toBe(1);
expect(stdout).toContain('Remove .env.example');
expect(stdout).toContain('env.ts lists the keys');
});
});

Expand Down
10 changes: 10 additions & 0 deletions packages/void/test/integration/plugin-env-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ describe('envSchemaPlugin auto-discovery', () => {
writeFileSync(join(root, '.env.production'), 'API_URL=https://example.com\n');

await expect(runConfig(envSchemaPlugin(root))).rejects.toThrow('supports one local file: .env');
await expect(runConfig(envSchemaPlugin(root))).rejects.not.toThrow('env.ts lists the keys');
});

it('points .env.example users at env.ts', async () => {
const root = createTempRoot();
writeFileSync(join(root, '.env.example'), 'API_URL=\n');

await expect(runConfig(envSchemaPlugin(root))).rejects.toThrow(
'Remove .env.example and follow the env migration guide. env.ts lists the keys',
);
});

it('removes local .env client values from production builds', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/void/test/unit/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2620,7 +2620,7 @@ export default config;
expect(gitignore).toContain('.wrangler');
expect(gitignore).toContain('.env');
expect(gitignore.split('\n')).toContain('.env.*');
expect(gitignore.split('\n')).toContain('!.env.example');
expect(gitignore.split('\n')).not.toContain('!.env.example');
} finally {
rmSync(root, { recursive: true, force: true });
}
Expand Down