Skip to content

Commit 55071fa

Browse files
zrosenbauerclaude
andauthored
feat(bundler): disable Bun env autoloading in compiled binaries (#168)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6f1cccd commit 55071fa

File tree

10 files changed

+115
-2
lines changed

10 files changed

+115
-2
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@kidd-cli/config': minor
3+
'@kidd-cli/bundler': minor
4+
---
5+
6+
Disable Bun's automatic `.env` and `bunfig.toml` loading in compiled binaries by default. Adds `autoloadDotenv` option to compile config for opt-in `.env` loading. `bunfig.toml` loading is always disabled.

packages/bundler/src/bundler.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ export async function createBundler(params: CreateBundlerParams): Promise<Bundle
6363
watch: async (overrides: WatchOverrides = {}): AsyncBundlerResult<void> => {
6464
const lifecycle = resolveLifecycle(baseLifecycle, overrides)
6565
await lifecycle.onStart({ phase: 'watch' })
66-
const result = await watch({ onSuccess: overrides.onSuccess, resolved, verbose: overrides.verbose })
66+
const result = await watch({
67+
onSuccess: overrides.onSuccess,
68+
resolved,
69+
verbose: overrides.verbose,
70+
})
6771
await lifecycle.onFinish({ phase: 'watch' })
6872
return result
6973
},

packages/bundler/src/compile/compile.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const noopLifecycle = {
2727
function makeResolved(overrides?: {
2828
readonly targets?: readonly string[]
2929
readonly name?: string
30+
readonly autoloadDotenv?: boolean
3031
}): Parameters<typeof compile>[0]['resolved'] {
3132
return {
3233
entry: '/project/src/index.ts',
@@ -42,6 +43,7 @@ function makeResolved(overrides?: {
4243
define: {},
4344
},
4445
compile: {
46+
autoloadDotenv: overrides?.autoloadDotenv ?? false,
4547
targets: (overrides?.targets ?? []) as readonly CompileTarget[],
4648
name: overrides?.name ?? 'cli',
4749
},
@@ -257,6 +259,62 @@ describe('compile operation', () => {
257259
expect(stepFinishes).toContain('linux-x64')
258260
})
259261

262+
it('should always pass --no-compile-autoload-bunfig', async () => {
263+
await compile({
264+
resolved: makeResolved({ targets: ['linux-x64'], name: 'my-app' }),
265+
lifecycle: noopLifecycle,
266+
})
267+
268+
expect(mockProcessExec).toHaveBeenCalledWith({
269+
cmd: 'bun',
270+
args: expect.arrayContaining(['--no-compile-autoload-bunfig']),
271+
cwd: '/project',
272+
})
273+
})
274+
275+
it('should pass --no-compile-autoload-dotenv by default when autoloadDotenv is not configured', async () => {
276+
await compile({
277+
resolved: makeResolved({ targets: ['linux-x64'], name: 'my-app' }),
278+
lifecycle: noopLifecycle,
279+
})
280+
281+
expect(mockProcessExec).toHaveBeenCalledWith({
282+
cmd: 'bun',
283+
args: expect.arrayContaining(['--no-compile-autoload-dotenv']),
284+
cwd: '/project',
285+
})
286+
})
287+
288+
it('should pass --no-compile-autoload-dotenv when autoloadDotenv is explicitly false', async () => {
289+
await compile({
290+
resolved: makeResolved({ targets: ['linux-x64'], name: 'my-app', autoloadDotenv: false }),
291+
lifecycle: noopLifecycle,
292+
})
293+
294+
expect(mockProcessExec).toHaveBeenCalledWith({
295+
cmd: 'bun',
296+
args: expect.arrayContaining(['--no-compile-autoload-dotenv']),
297+
cwd: '/project',
298+
})
299+
})
300+
301+
it('should not pass --no-compile-autoload-dotenv when autoloadDotenv is true', async () => {
302+
await compile({
303+
resolved: makeResolved({
304+
targets: ['linux-x64'],
305+
name: 'my-app',
306+
autoloadDotenv: true,
307+
}),
308+
lifecycle: noopLifecycle,
309+
})
310+
311+
expect(mockProcessExec).toHaveBeenCalledWith({
312+
cmd: 'bun',
313+
args: expect.not.arrayContaining(['--no-compile-autoload-dotenv']),
314+
cwd: '/project',
315+
})
316+
})
317+
260318
it('should invoke bun with --compile and --outfile args', async () => {
261319
await compile({
262320
resolved: makeResolved({ name: 'my-app' }),

packages/bundler/src/compile/compile.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export async function compile(params: {
5252
const isMultiTarget = targets.length > 1
5353

5454
const results = await compileTargetsSequentially({
55+
autoloadDotenv: params.resolved.compile.autoloadDotenv,
5556
bundledEntry,
5657
cwd: params.resolved.cwd,
5758
isMultiTarget,
@@ -99,6 +100,7 @@ export function resolveTargetLabel(target: CompileTarget): string {
99100
* @returns The accumulated result tuples for each target.
100101
*/
101102
async function compileTargetsSequentially(params: {
103+
readonly autoloadDotenv: boolean
102104
readonly bundledEntry: string
103105
readonly cwd: string
104106
readonly isMultiTarget: boolean
@@ -118,6 +120,7 @@ async function compileTargetsSequentially(params: {
118120
}
119121

120122
const result = await compileSingleTarget({
123+
autoloadDotenv: params.autoloadDotenv,
121124
bundledEntry: params.bundledEntry,
122125
cwd: params.cwd,
123126
isMultiTarget: params.isMultiTarget,
@@ -143,6 +146,7 @@ async function compileTargetsSequentially(params: {
143146
* @returns A result tuple with the compiled binary info or an error.
144147
*/
145148
async function compileSingleTarget(params: {
149+
readonly autoloadDotenv: boolean
146150
readonly bundledEntry: string
147151
readonly cwd: string
148152
readonly outDir: string
@@ -162,6 +166,7 @@ async function compileSingleTarget(params: {
162166
const args = [
163167
'build',
164168
'--compile',
169+
...resolveAutoloadFlags({ autoloadDotenv: params.autoloadDotenv }),
165170
params.bundledEntry,
166171
'--outfile',
167172
outfile,
@@ -258,6 +263,26 @@ function formatCompileError(target: CompileTarget, execError: Error, verbose: bo
258263
return header
259264
}
260265

266+
/**
267+
* Build the CLI flags that control Bun's compile-time config autoloading.
268+
*
269+
* `bunfig.toml` loading is always disabled — kidd CLIs should never load
270+
* Bun runtime config. `.env` loading is controlled by the `autoloadDotenv`
271+
* option (disabled by default).
272+
*
273+
* @private
274+
* @param params - The autoload settings.
275+
* @returns An array of CLI flag strings.
276+
*/
277+
function resolveAutoloadFlags(params: { readonly autoloadDotenv: boolean }): readonly string[] {
278+
const candidates = [
279+
{ enabled: false, flag: '--no-compile-autoload-bunfig' },
280+
{ enabled: params.autoloadDotenv, flag: '--no-compile-autoload-dotenv' },
281+
] as const
282+
283+
return candidates.filter((c) => !c.enabled).map((c) => c.flag)
284+
}
285+
261286
/**
262287
* Remove temporary `.bun-build` files that `bun build --compile` leaves behind.
263288
*

packages/bundler/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface ResolvedBuildOptions {
1919
* Fully resolved compile options with all defaults applied.
2020
*/
2121
export interface ResolvedCompileOptions {
22+
readonly autoloadDotenv: boolean
2223
readonly targets: readonly CompileTarget[]
2324
readonly name: string
2425
}

packages/bundler/src/utils/resolve-config.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ describe('config resolution', () => {
4646
})
4747
})
4848

49+
it('should default autoloadDotenv to false', () => {
50+
expect(resolved.compile.autoloadDotenv).toBeFalsy()
51+
})
52+
4953
it('should use binaryName as compile name when no config name', () => {
5054
expect(resolved.compile.name).toBe('cli')
5155
})
@@ -75,6 +79,7 @@ describe('config resolution', () => {
7579
},
7680
commands: './src/commands',
7781
compile: {
82+
autoloadDotenv: true,
7883
name: 'my-cli',
7984
out: './bin',
8085
targets: ['darwin-arm64'],
@@ -114,6 +119,10 @@ describe('config resolution', () => {
114119
})
115120
})
116121

122+
it('should use custom autoloadDotenv value', () => {
123+
expect(resolved.compile.autoloadDotenv).toBeTruthy()
124+
})
125+
117126
it('should prefer config compile name over binaryName', () => {
118127
expect(resolved.compile.name).toBe('my-cli')
119128
})

packages/bundler/src/utils/resolve-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export function resolveConfig(params: {
7171
buildOutDir,
7272
commands,
7373
compile: {
74+
autoloadDotenv: compileOpts.autoloadDotenv ?? false,
7475
name: compileOpts.name ?? params.binaryName,
7576
targets: compileOpts.targets ?? [],
7677
},

packages/config/src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ export interface CompileOptions {
6060
* Binary name. Defaults to cli name.
6161
*/
6262
name?: string
63+
/**
64+
* Load `.env` files at runtime in the compiled binary. Default: false.
65+
*
66+
* When disabled, the compiled binary will not auto-load `.env` files from
67+
* the working directory. Use the kidd auth dotenv strategy for explicit
68+
* `.env` loading instead.
69+
*/
70+
readonly autoloadDotenv?: boolean
6371
}
6472

6573
/**

packages/config/src/utils/schema.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe('KiddConfigSchema schema', () => {
1313
const result = KiddConfigSchema.safeParse({
1414
build: { external: ['pg'], minify: true, out: './dist', sourcemap: false, target: 'node20' },
1515
commands: './commands',
16-
compile: { name: 'my-cli', out: './bin', targets: ['linux-x64', 'darwin-arm64'] },
16+
compile: { autoloadDotenv: true, name: 'my-cli', out: './bin', targets: ['linux-x64', 'darwin-arm64'] },
1717
entry: './src/index.ts',
1818
include: ['assets/**'],
1919
})

packages/config/src/utils/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const BuildOptionsSchema = z
3939
*/
4040
const CompileOptionsSchema = z
4141
.object({
42+
autoloadDotenv: z.boolean().optional(),
4243
name: z.string().optional(),
4344
out: z.string().optional(),
4445
targets: z.array(CompileTargetSchema).optional(),

0 commit comments

Comments
 (0)