feat: add support for stripping inline comments in .env files and make Docker host port configurable via DS2API_HOST_PORT

This commit is contained in:
CJACK
2026-04-04 22:30:57 +08:00
parent eab197f4d9
commit a6836455dc
9 changed files with 130 additions and 14 deletions

View File

@@ -47,7 +47,7 @@ func loadDotEnvFromPath(path string) error {
if _, exists := os.LookupEnv(key); exists {
continue
}
if err := os.Setenv(key, normalizeDotEnvValue(strings.TrimSpace(value))); err != nil {
if err := os.Setenv(key, normalizeDotEnvValue(trimDotEnvValue(strings.TrimSpace(value)))); err != nil {
return fmt.Errorf("%s:%d set env %q: %w", path, i+1, key, err)
}
}
@@ -55,6 +55,62 @@ func loadDotEnvFromPath(path string) error {
return nil
}
// Preserve quoted values, but drop Compose-style inline comments from unquoted values.
func trimDotEnvValue(raw string) string {
if raw == "" {
return raw
}
switch raw[0] {
case '"':
if trimmed, ok := trimQuotedDotEnvValue(raw, '"'); ok {
return trimmed
}
case '\'':
if trimmed, ok := trimQuotedDotEnvValue(raw, '\''); ok {
return trimmed
}
default:
if idx := inlineDotEnvCommentStart(raw); idx >= 0 {
return strings.TrimSpace(raw[:idx])
}
}
return raw
}
func trimQuotedDotEnvValue(raw string, quote byte) (string, bool) {
escaped := false
for i := 1; i < len(raw); i++ {
ch := raw[i]
if quote == '"' && escaped {
escaped = false
continue
}
if quote == '"' && ch == '\\' {
escaped = true
continue
}
if ch == quote {
return strings.TrimSpace(raw[:i+1]), true
}
}
return raw, false
}
func inlineDotEnvCommentStart(raw string) int {
for i := 1; i < len(raw); i++ {
if raw[i] == '#' && isDotEnvCommentSpacer(raw[i-1]) {
return i
}
}
return -1
}
func isDotEnvCommentSpacer(b byte) bool {
return b == ' ' || b == '\t'
}
func normalizeDotEnvValue(raw string) string {
if len(raw) < 2 {
return raw

View File

@@ -3,6 +3,7 @@ package config
import (
"os"
"path/filepath"
"strings"
"testing"
)
@@ -67,6 +68,57 @@ func TestLoadDotEnvIgnoresMissingFile(t *testing.T) {
}
}
func TestLoadDotEnvStripsInlineCommentsFromUnquotedValues(t *testing.T) {
dir := t.TempDir()
oldWD, err := os.Getwd()
if err != nil {
t.Fatalf("getwd: %v", err)
}
if err := os.Chdir(dir); err != nil {
t.Fatalf("chdir temp dir: %v", err)
}
t.Cleanup(func() {
_ = os.Chdir(oldWD)
})
const plainKey = "DS2API_TEST_DOTENV_PLAIN"
const hashKey = "DS2API_TEST_DOTENV_HASH"
const quotedKey = "DS2API_TEST_DOTENV_QUOTED_COMMENT"
const exportKey = "DS2API_TEST_DOTENV_EXPORT"
unsetEnv(t, plainKey)
unsetEnv(t, hashKey)
unsetEnv(t, quotedKey)
unsetEnv(t, exportKey)
content := strings.Join([]string{
plainKey + "=5001 # local",
hashKey + "=5001#local",
quotedKey + `="5001 # local" # keep the inner hash`,
"export " + exportKey + "=enabled # exported",
}, "\n") + "\n"
if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(content), 0o644); err != nil {
t.Fatalf("write .env: %v", err)
}
if err := LoadDotEnv(); err != nil {
t.Fatalf("LoadDotEnv() error: %v", err)
}
if got := os.Getenv(plainKey); got != "5001" {
t.Fatalf("expected inline comment to be stripped, got %q", got)
}
if got := os.Getenv(hashKey); got != "5001#local" {
t.Fatalf("expected hash without preceding whitespace to remain, got %q", got)
}
if got := os.Getenv(quotedKey); got != "5001 # local" {
t.Fatalf("expected quoted value to preserve hash text, got %q", got)
}
if got := os.Getenv(exportKey); got != "enabled" {
t.Fatalf("expected export syntax to load, got %q", got)
}
}
func unsetEnv(t *testing.T, key string) {
t.Helper()
old, had := os.LookupEnv(key)