|
1 | 1 | /** |
2 | | - * Copies missing env files from their .example templates, and warns about |
3 | | - * any keys present in the example but not set in the environment. |
4 | | - * Also warns about any VITE_ vars set in the environment that aren't listed |
5 | | - * in any example file. |
| 2 | + * Ensures `.env.local` (and mode-specific `.env.desktop.local` / `.env.saas.local`) |
| 3 | + * files exist so developers have a place to put overrides (API keys, machine-specific |
| 4 | + * settings) without touching the committed `.env` / `.env.desktop` / `.env.saas` files. |
| 5 | + * |
| 6 | + * Vite automatically layers these `.local` files on top of the committed ones. |
6 | 7 | * |
7 | 8 | * Usage: |
8 | | - * tsx scripts/setup-env.ts # checks .env |
9 | | - * tsx scripts/setup-env.ts --desktop # also checks .env.desktop |
10 | | - * tsx scripts/setup-env.ts --saas # also checks .env.saas |
| 9 | + * tsx scripts/setup-env.ts # ensures .env.local |
| 10 | + * tsx scripts/setup-env.ts --desktop # also ensures .env.desktop.local |
| 11 | + * tsx scripts/setup-env.ts --saas # also ensures .env.saas.local |
11 | 12 | */ |
12 | 13 |
|
13 | | -import { existsSync, copyFileSync, readFileSync } from "fs"; |
| 14 | +import { existsSync, writeFileSync } from "fs"; |
14 | 15 | import { join } from "path"; |
15 | | -import { config, parse } from "dotenv"; |
16 | 16 |
|
17 | 17 | // npm scripts run from the directory containing package.json (frontend/) |
18 | 18 | const root = process.cwd(); |
19 | 19 | const args = process.argv.slice(2); |
20 | 20 | const isDesktop = args.includes("--desktop"); |
21 | 21 | const isSaas = args.includes("--saas"); |
22 | 22 |
|
23 | | -console.log( |
24 | | - "setup-env: see frontend/README.md#environment-variables for documentation", |
25 | | -); |
26 | | - |
27 | | -function getExampleKeys(exampleFile: string): string[] { |
28 | | - const examplePath = join(root, exampleFile); |
29 | | - if (!existsSync(examplePath)) return []; |
30 | | - return Object.keys(parse(readFileSync(examplePath, "utf-8"))); |
| 23 | +function template(parent: string): string { |
| 24 | + return [ |
| 25 | + "###############################################################################", |
| 26 | + `# Local overrides for \`frontend/${parent}\``, |
| 27 | + "# Put API keys and machine-specific settings here. Any variable defined here", |
| 28 | + `# takes precedence over the committed \`${parent}\``, |
| 29 | + "###############################################################################", |
| 30 | + "", |
| 31 | + ].join("\n"); |
31 | 32 | } |
32 | 33 |
|
33 | | -function ensureEnvFile(envFile: string, exampleFile: string): boolean { |
34 | | - const envPath = join(root, envFile); |
35 | | - const examplePath = join(root, exampleFile); |
36 | | - |
37 | | - if (!existsSync(examplePath)) { |
38 | | - console.warn(`setup-env: ${exampleFile} not found, skipping ${envFile}`); |
39 | | - return false; |
40 | | - } |
41 | | - |
42 | | - if (!existsSync(envPath)) { |
43 | | - copyFileSync(examplePath, envPath); |
44 | | - console.log(`setup-env: created ${envFile} from ${exampleFile}`); |
45 | | - } |
46 | | - |
47 | | - config({ path: envPath }); |
48 | | - |
49 | | - const missing = getExampleKeys(exampleFile).filter( |
50 | | - (k) => !(k in process.env), |
51 | | - ); |
52 | | - |
53 | | - if (missing.length > 0) { |
54 | | - console.error( |
55 | | - `setup-env: ${envFile} is missing keys from ${exampleFile}:\n` + |
56 | | - missing.map((k) => ` ${k}`).join("\n") + |
57 | | - "\n Add them manually or delete your local file to re-copy from the example.", |
58 | | - ); |
59 | | - return true; |
| 34 | +function ensureLocalFile(localFile: string, parentFile: string): void { |
| 35 | + const localPath = join(root, localFile); |
| 36 | + if (!existsSync(localPath)) { |
| 37 | + writeFileSync(localPath, template(parentFile)); |
| 38 | + console.log(`setup-env: created empty ${localFile} for local overrides`); |
60 | 39 | } |
61 | | - |
62 | | - return false; |
63 | | -} |
64 | | - |
65 | | -let failed = false; |
66 | | -failed = ensureEnvFile(".env", "config/.env.example") || failed; |
67 | | - |
68 | | -if (isDesktop) { |
69 | | - failed = |
70 | | - ensureEnvFile(".env.desktop", "config/.env.desktop.example") || failed; |
71 | | -} |
72 | | - |
73 | | -if (isSaas) { |
74 | | - failed = ensureEnvFile(".env.saas", "config/.env.saas.example") || failed; |
75 | | -} |
76 | | - |
77 | | -// Warn about any VITE_ vars set in the environment that aren't listed in any example file. |
78 | | -const allExampleKeys = new Set([ |
79 | | - ...getExampleKeys("config/.env.example"), |
80 | | - ...getExampleKeys("config/.env.desktop.example"), |
81 | | - ...getExampleKeys("config/.env.saas.example"), |
82 | | -]); |
83 | | -const unknownViteVars = Object.keys(process.env).filter( |
84 | | - (k) => k.startsWith("VITE_") && !allExampleKeys.has(k), |
85 | | -); |
86 | | -if (unknownViteVars.length > 0) { |
87 | | - console.warn( |
88 | | - "setup-env: the following VITE_ vars are set but not listed in any example file:\n" + |
89 | | - unknownViteVars.map((k) => ` ${k}`).join("\n") + |
90 | | - "\n Add them to the appropriate config/.env.*.example file if they are required.", |
91 | | - ); |
92 | 40 | } |
93 | 41 |
|
94 | | -if (failed) process.exit(1); |
| 42 | +ensureLocalFile(".env.local", ".env"); |
| 43 | +if (isDesktop) ensureLocalFile(".env.desktop.local", ".env.desktop"); |
| 44 | +if (isSaas) ensureLocalFile(".env.saas.local", ".env.saas"); |
0 commit comments