Files
bun.sh/test/cli/run/no-envfile.test.ts
robobun 509a97a435 Add --no-env-file flag to disable automatic .env loading (#24767)
## Summary

Implements `--no-env-file` CLI flag and bunfig configuration options to
disable automatic `.env` file loading at runtime and in the bundler.

## Motivation

Users may want to disable automatic `.env` file loading for:
- Production environments where env vars are managed externally
- CI/CD pipelines where .env files should be ignored
- Testing scenarios where explicit env control is needed
- Security contexts where .env files should not be trusted

## Changes

### CLI Flag
- Added `--no-env-file` flag that disables loading of default .env files
- Still respects explicit `--env-file` arguments for intentional env
loading

### Bunfig Configuration
Added support for disabling .env loading via `bunfig.toml`:
- `env = false` - disables default .env file loading
- `env = null` - disables default .env file loading  
- `env.file = false` - disables default .env file loading
- `env.file = null` - disables default .env file loading

### Implementation
- Added `disable_default_env_files` field to `api.TransformOptions` with
serialization support
- Added `disable_default_env_files` field to `options.Env` struct
- Implemented `loadEnvConfig` in bunfig parser to handle env
configuration
- Wired up flag throughout runtime and bundler code paths
- Preserved package.json script runner behavior (always skips default
.env files)

## Tests

Added comprehensive test suite (`test/cli/run/no-envfile.test.ts`) with
9 tests covering:
- `--no-env-file` flag with `.env`, `.env.local`,
`.env.development.local`
- Bunfig configurations: `env = false`, `env.file = false`, `env = true`
- `--no-env-file` with `-e` eval flag
- `--no-env-file` combined with `--env-file` (explicit files still load)
- Production mode behavior

All tests pass with debug bun and fail with system bun (as expected).

## Example Usage

```bash
# Disable all default .env files
bun --no-env-file index.js

# Disable defaults but load explicit file
bun --no-env-file --env-file .env.production index.js

# Disable via bunfig.toml
cat > bunfig.toml << 'CONFIG'
env = false
CONFIG
bun index.js
```

## Files Changed
- `src/cli/Arguments.zig` - CLI flag parsing
- `src/api/schema.zig` - API schema field with encode/decode
- `src/options.zig` - Env struct field and wiring
- `src/bunfig.zig` - Config parsing with loadEnvConfig
- `src/transpiler.zig` - Runtime wiring
- `src/bun.js.zig` - Runtime wiring
- `src/cli/exec_command.zig` - Runtime wiring
- `src/cli/run_command.zig` - Preserved package.json script runner
behavior
- `test/cli/run/no-envfile.test.ts` - Comprehensive test suite

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-17 15:04:42 -05:00

266 lines
7.0 KiB
TypeScript

import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
test("--no-env-file disables .env loading", async () => {
using dir = tempDir("no-env-file", {
".env": "FOO=bar",
"index.js": "console.log(process.env.FOO);",
});
// Without --no-env-file, .env should be loaded
{
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("bar");
expect(exitCode).toBe(0);
}
// With --no-env-file, .env should NOT be loaded
{
await using proc = Bun.spawn({
cmd: [bunExe(), "--no-env-file", "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("undefined");
expect(exitCode).toBe(0);
}
});
test("--no-env-file disables .env.local loading", async () => {
using dir = tempDir("no-env-file-local", {
".env": "FOO=bar",
".env.local": "FOO=local",
"index.js": "console.log(process.env.FOO);",
});
// Without --no-env-file, .env.local should override .env
{
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("local");
expect(exitCode).toBe(0);
}
// With --no-env-file, neither should be loaded
{
await using proc = Bun.spawn({
cmd: [bunExe(), "--no-env-file", "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("undefined");
expect(exitCode).toBe(0);
}
});
test("--no-env-file disables .env.development.local loading", async () => {
using dir = tempDir("no-env-file-dev-local", {
".env": "FOO=bar",
".env.development.local": "FOO=dev-local",
"index.js": "console.log(process.env.FOO);",
});
// Without --no-env-file, .env.development.local should be loaded
{
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("dev-local");
expect(exitCode).toBe(0);
}
// With --no-env-file, it should NOT be loaded
{
await using proc = Bun.spawn({
cmd: [bunExe(), "--no-env-file", "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("undefined");
expect(exitCode).toBe(0);
}
});
test("bunfig env.file = false disables .env loading", async () => {
using dir = tempDir("bunfig-env-file-false", {
".env": "FOO=bar",
"bunfig.toml": `
[env]
file = false
`,
"index.js": "console.log(process.env.FOO);",
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("undefined");
expect(exitCode).toBe(0);
});
test("bunfig env = false disables .env loading", async () => {
using dir = tempDir("bunfig-env-false", {
".env": "FOO=bar",
"bunfig.toml": `
env = false
`,
"index.js": "console.log(process.env.FOO);",
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("undefined");
expect(exitCode).toBe(0);
});
test("--no-env-file with -e flag", async () => {
using dir = tempDir("no-env-file-eval", {
".env": "FOO=bar",
});
await using proc = Bun.spawn({
cmd: [bunExe(), "--no-env-file", "-e", "console.log(process.env.FOO)"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("undefined");
expect(exitCode).toBe(0);
});
test("--no-env-file combined with --env-file still loads explicit file", async () => {
using dir = tempDir("no-env-file-with-env-file", {
".env": "FOO=bar",
".env.custom": "FOO=custom",
"index.js": "console.log(process.env.FOO);",
});
// --no-env-file should skip .env but --env-file should load .env.custom
await using proc = Bun.spawn({
cmd: [bunExe(), "--no-env-file", "--env-file", ".env.custom", "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("custom");
expect(exitCode).toBe(0);
});
test("bunfig env = true still loads .env files", async () => {
using dir = tempDir("bunfig-env-true", {
".env": "FOO=bar",
"bunfig.toml": `
env = true
`,
"index.js": "console.log(process.env.FOO);",
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("bar");
expect(exitCode).toBe(0);
});
test("--no-env-file in production mode", async () => {
using dir = tempDir("no-env-file-production", {
".env": "FOO=bar",
".env.production": "FOO=prod",
"index.js": "console.log(process.env.FOO);",
});
await using proc = Bun.spawn({
cmd: [bunExe(), "--no-env-file", "index.js"],
env: { ...bunEnv, NODE_ENV: "production" },
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("undefined");
expect(exitCode).toBe(0);
});