Skip to content

Commit c5f45fe

Browse files
fix(setup): write app-behavior flags to every env file the app can start from
The wizard wrote the Chat opt-out only to the env file its own mode owns, so choosing compose put it in the root `.env` while `bun run dev` reads `apps/sim/.env` and never saw it. Skipping the chat key appeared to do nothing. Mirror values that change how the app behaves — as opposed to where it connects — across both targets. Connection settings deliberately do not go through this: DATABASE_URL and friends differ between the compose stack and a local dev run, which is why this takes an explicit set of values rather than the whole batch. The mirrored file is written even when absent, since missing is exactly the case that stranded the flag, but with seeding suppressed so a compose run leaves a one-line apps/sim/.env instead of a full .env.example for a stack the user is not running. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
1 parent 2ec4cbb commit c5f45fe

5 files changed

Lines changed: 47 additions & 6 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"skills:sync": "bun run scripts/sync-skills.ts",
6666
"skills:check": "bun run scripts/sync-skills.ts --check",
6767
"setup": "bun install && bun run scripts/setup/index.ts setup",
68-
"sim": "bun run scripts/setup/index.ts",
68+
"sim": "bun install && bun run scripts/setup/index.ts",
6969
"doctor": "bun run scripts/setup/index.ts doctor",
7070
"agent-stream-docs:generate": "bun run scripts/sync-agent-stream-docs.ts",
7171
"agent-stream-docs:check": "bun run scripts/sync-agent-stream-docs.ts --check",

scripts/setup/env-files.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,25 @@ export function upsertEnv(content: string, key: string, value: string): string {
105105
return lines.join('\n')
106106
}
107107

108-
/** Writes values into an env file, seeding a missing file from its .env.example. */
109-
export function writeEnvValues(target: EnvTarget, values: Record<string, string>): void {
108+
/**
109+
* Writes values into an env file, seeding a missing file from its `.env.example`.
110+
*
111+
* `seedFromExample: false` creates a missing file holding only `values` instead.
112+
* Used when mirroring one flag into a target the user did not choose, where
113+
* materializing a whole example for a stack they are not running would be
114+
* surprising.
115+
*/
116+
export function writeEnvValues(
117+
target: EnvTarget,
118+
values: Record<string, string>,
119+
{ seedFromExample = true }: { seedFromExample?: boolean } = {}
120+
): void {
110121
const filePath = ENV_PATHS[target]
111122
let content: string
112123
if (existsSync(filePath)) {
113124
content = readFileSync(filePath, 'utf8')
114125
} else {
115-
const example = EXAMPLE_PATHS[target]
126+
const example = seedFromExample ? EXAMPLE_PATHS[target] : undefined
116127
content = example && existsSync(example) ? readFileSync(example, 'utf8') : ''
117128
}
118129
for (const [key, value] of Object.entries(values)) {

scripts/setup/modes/compose.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
promptSignInProviders,
1818
promptStorage,
1919
promptUnlocks,
20+
writeAppBehaviorValues,
2021
} from '../steps.ts'
2122
import { glyph, theme } from '../theme.ts'
2223

@@ -113,7 +114,7 @@ export async function runComposeMode(detection: Detection, quick: boolean): Prom
113114
Object.assign(values, mothershipOverride())
114115
const copilotKey = await promptCopilotKey(root.vars.get('COPILOT_API_KEY'))
115116
if (copilotKey) values.COPILOT_API_KEY = copilotKey
116-
Object.assign(values, chatFlagValues(copilotKey))
117+
writeAppBehaviorValues('root', chatFlagValues(copilotKey))
117118
Object.assign(values, await promptLlmKeys(detection, !quick))
118119
if (!quick) {
119120
const storage = await promptStorage(root.vars, true)

scripts/setup/modes/dev.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
promptSignInProviders,
2020
promptStorage,
2121
promptUnlocks,
22+
writeAppBehaviorValues,
2223
} from '../steps.ts'
2324
import { glyph, theme } from '../theme.ts'
2425

@@ -125,7 +126,7 @@ export async function runDevMode(
125126
Object.assign(values, mothershipOverride())
126127
const copilotKey = await promptCopilotKey(simAfter.vars.get('COPILOT_API_KEY'))
127128
if (copilotKey) values.COPILOT_API_KEY = copilotKey
128-
Object.assign(values, chatFlagValues(copilotKey))
129+
writeAppBehaviorValues('sim', chatFlagValues(copilotKey))
129130
Object.assign(values, await promptLlmKeys(detection, !quick))
130131

131132
// Redis is set up in every mode, quick included. Storage falls back to

scripts/setup/steps.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { browserKeyFlow } from './cli-auth.ts'
22
import type { Detection } from './detect.ts'
33
import {
44
type EnvFile,
5+
type EnvTarget,
56
generateSecret,
67
isPlaceholder,
78
isTruthy,
89
isUsableSecret,
910
SECRET_KEYS,
1011
secretRequirement,
12+
writeEnvValues,
1113
} from './env-files.ts'
1214
import * as p from './prompter.ts'
1315
import { link, theme } from './theme.ts'
@@ -80,6 +82,32 @@ export function chatFlagValues(copilotKey: string | null): Record<string, string
8082
return { NEXT_PUBLIC_CHAT_DISABLED: copilotKey ? 'false' : 'true' }
8183
}
8284

85+
/**
86+
* Env files the app reads its own configuration from: `apps/sim/.env` when
87+
* started with `bun run dev`, the root `.env` when started through
88+
* docker-compose.
89+
*/
90+
const APP_ENV_TARGETS = ['sim', 'root'] as const satisfies readonly EnvTarget[]
91+
92+
/**
93+
* Writes values that change how the app behaves — as opposed to where it
94+
* connects — to every env file the app might be started from, so the outcome
95+
* follows what the user answered rather than which command they later use to
96+
* launch Sim. Connection settings must NOT go through this: `DATABASE_URL` and
97+
* friends legitimately differ between the compose stack and a local dev run.
98+
*
99+
* Mirrored targets are written even when absent, because the file being missing
100+
* is precisely the case that strands the flag — but without seeding from
101+
* `.env.example`, so a compose run leaves behind a one-line `apps/sim/.env`
102+
* rather than a full example for a stack the user is not running.
103+
*/
104+
export function writeAppBehaviorValues(primary: EnvTarget, values: Record<string, string>): void {
105+
writeEnvValues(primary, values)
106+
for (const target of APP_ENV_TARGETS) {
107+
if (target !== primary) writeEnvValues(target, values, { seedFromExample: false })
108+
}
109+
}
110+
83111
/**
84112
* Escape hatch for Sim devs pointing an install at a non-prod mothership:
85113
*

0 commit comments

Comments
 (0)