feat: add --env-file flag to develop.sh (#25621)

Adds `--env-file` to `scripts/develop.sh` to allow reading environment 
from a given file. This makes it easier to configure things like external 
auth providers, access URLs, and other dev-time settings without 
exporting a wall of environment variables in every shell session.

> Generated with [Coder Agents](https://coder.com/agents)
This commit is contained in:
Cian Johnston
2026-05-25 11:54:57 +01:00
committed by GitHub
parent ffc51ec8b3
commit a4afb9dfc6
4 changed files with 203 additions and 3 deletions
+131
View File
@@ -855,3 +855,134 @@ func TestPrometheusBannerEntry(t *testing.T) {
})
}
}
//nolint:paralleltest // loadEnvFile mutates process-global environment.
func TestLoadEnvFile(t *testing.T) {
t.Run("LoadsVariablesFromFile", func(t *testing.T) {
tmpDir := t.TempDir()
envFile := filepath.Join(tmpDir, ".env")
err := os.WriteFile(envFile, []byte(strings.Join([]string{
"# Comment line",
"",
"FOO_TEST_VAR=bar",
"export BAZ_TEST_VAR=qux",
`QUOTED_TEST_VAR="hello world"`,
"SINGLE_QUOTED_TEST_VAR='single quoted'",
}, "\n")), 0o600)
require.NoError(t, err)
// Ensure none are set beforehand.
t.Setenv("FOO_TEST_VAR", "")
os.Unsetenv("FOO_TEST_VAR")
t.Setenv("BAZ_TEST_VAR", "")
os.Unsetenv("BAZ_TEST_VAR")
t.Setenv("QUOTED_TEST_VAR", "")
os.Unsetenv("QUOTED_TEST_VAR")
t.Setenv("SINGLE_QUOTED_TEST_VAR", "")
os.Unsetenv("SINGLE_QUOTED_TEST_VAR")
n, err := loadEnvFile(envFile)
require.NoError(t, err)
assert.Equal(t, 4, n)
assert.Equal(t, "bar", os.Getenv("FOO_TEST_VAR"))
assert.Equal(t, "qux", os.Getenv("BAZ_TEST_VAR"))
assert.Equal(t, "hello world", os.Getenv("QUOTED_TEST_VAR"))
assert.Equal(t, "single quoted", os.Getenv("SINGLE_QUOTED_TEST_VAR"))
})
t.Run("DoesNotOverrideExisting", func(t *testing.T) {
tmpDir := t.TempDir()
envFile := filepath.Join(tmpDir, ".env")
err := os.WriteFile(envFile, []byte("EXISTING_TEST_VAR=new\n"), 0o600)
require.NoError(t, err)
t.Setenv("EXISTING_TEST_VAR", "original")
n, err := loadEnvFile(envFile)
require.NoError(t, err)
assert.Equal(t, 0, n)
assert.Equal(t, "original", os.Getenv("EXISTING_TEST_VAR"))
})
t.Run("ErrorsOnMissingFile", func(t *testing.T) {
_, err := loadEnvFile("/nonexistent/path/.env")
require.Error(t, err)
})
t.Run("ErrorsOnEmptyPath", func(t *testing.T) {
// This tests the caller logic (main), but we verify loadEnvFile
// would error on empty path since godotenv.Read("") fails.
_, err := loadEnvFile("")
require.Error(t, err)
})
}
//nolint:paralleltest // parseEnvFileFlag mutates process-global os.Args.
func TestParseEnvFileFlag(t *testing.T) {
t.Run("FlagWithSpace", func(t *testing.T) {
orig := os.Args
t.Cleanup(func() { os.Args = orig })
os.Args = []string{"develop", "--env-file", "/tmp/test.env", "--port", "3000"}
result, err := parseEnvFileFlag()
require.NoError(t, err)
assert.Equal(t, "/tmp/test.env", result)
})
t.Run("FlagWithEquals", func(t *testing.T) {
orig := os.Args
t.Cleanup(func() { os.Args = orig })
os.Args = []string{"develop", "--env-file=/tmp/test.env", "--port", "3000"}
result, err := parseEnvFileFlag()
require.NoError(t, err)
assert.Equal(t, "/tmp/test.env", result)
})
t.Run("FallsBackToEnvVar", func(t *testing.T) {
orig := os.Args
t.Cleanup(func() { os.Args = orig })
os.Args = []string{"develop", "--port", "3000"}
t.Setenv("CODER_DEV_ENV_FILE", "/tmp/from-env.env")
result, err := parseEnvFileFlag()
require.NoError(t, err)
assert.Equal(t, "/tmp/from-env.env", result)
})
t.Run("FlagTakesPrecedenceOverEnvVar", func(t *testing.T) {
orig := os.Args
t.Cleanup(func() { os.Args = orig })
os.Args = []string{"develop", "--env-file", "/tmp/from-flag.env"}
t.Setenv("CODER_DEV_ENV_FILE", "/tmp/from-env.env")
result, err := parseEnvFileFlag()
require.NoError(t, err)
assert.Equal(t, "/tmp/from-flag.env", result)
})
t.Run("ReturnsEmptyWhenUnset", func(t *testing.T) {
orig := os.Args
t.Cleanup(func() { os.Args = orig })
os.Args = []string{"develop", "--port", "3000"}
t.Setenv("CODER_DEV_ENV_FILE", "")
os.Unsetenv("CODER_DEV_ENV_FILE")
result, err := parseEnvFileFlag()
require.NoError(t, err)
assert.Equal(t, "", result)
})
t.Run("ErrorsWhenValueMissing", func(t *testing.T) {
orig := os.Args
t.Cleanup(func() { os.Args = orig })
os.Args = []string{"develop", "--env-file"}
_, err := parseEnvFileFlag()
require.Error(t, err)
assert.Contains(t, err.Error(), "--env-file requires a value")
})
}