From 435ecebd0bd786f0d32b3ebe70341d8ea8282433 Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Sun, 12 Jul 2026 10:42:59 -0700 Subject: [PATCH] feat(env): add github-actions output format Add a --format github-actions option to the env command so resolved variables can be piped straight into $GITHUB_ENV. Single-line values use KEY=value; values containing a newline use the KEY< --- cli/src/cmd/app/commands/env.go | 97 ++++++++++++++++++++++++++-- cli/src/cmd/app/commands/env_test.go | 70 ++++++++++++++++++++ 2 files changed, 161 insertions(+), 6 deletions(-) diff --git a/cli/src/cmd/app/commands/env.go b/cli/src/cmd/app/commands/env.go index 305c1ee6c..ad7619a58 100644 --- a/cli/src/cmd/app/commands/env.go +++ b/cli/src/cmd/app/commands/env.go @@ -2,6 +2,8 @@ package commands import ( "context" + "crypto/rand" + "encoding/hex" "encoding/json" "fmt" "os" @@ -16,9 +18,10 @@ import ( ) const ( - envFormatDotenv = "dotenv" - envFormatShell = "shell" - envFormatJSON = "json" + envFormatDotenv = "dotenv" + envFormatShell = "shell" + envFormatJSON = "json" + envFormatGitHubActions = "github-actions" ) var ( @@ -50,7 +53,9 @@ for example when piping the output into another command. Pass --all to print the resolved environment for every service in one run. The dotenv and shell formats group each service under a "# " header; the -json format emits an object keyed by service name. +json format emits an object keyed by service name; the github-actions format +emits plain KEY=value and heredoc lines with no headers so the output stays +valid for $GITHUB_ENV. Examples: # Resolved environment for the api service (KEY=value lines) @@ -62,6 +67,9 @@ Examples: # JSON object (also selected by the global --json flag) azd app env api --format json + # GitHub Actions env lines for $GITHUB_ENV (multiline values use heredocs) + azd app env api --format github-actions --no-mask >> $GITHUB_ENV + # Raw values, no masking azd app env api --no-mask @@ -88,7 +96,7 @@ Examples: ValidArgsFunction: completeServiceArgs, } - cmd.Flags().StringVar(&envFormat, "format", envFormatDotenv, "Output format: dotenv, shell, or json") + cmd.Flags().StringVar(&envFormat, "format", envFormatDotenv, "Output format: dotenv, shell, json, or github-actions") cmd.Flags().BoolVar(&envNoMask, "no-mask", false, "Print raw values instead of masking secret-shaped values") cmd.Flags().StringVar(&envFile, "env-file", "", "Path to a .env file to merge, matching azd app run") cmd.Flags().BoolVar(&envAll, "all", false, "Print the resolved environment for every service") @@ -210,6 +218,15 @@ func runEnv(_ *cobra.Command, args []string) error { return cliout.PrintJSON(maskEnv(resolved, mask)) } + if format == envFormatGitHubActions { + out, err := formatGitHubEnv(resolved, mask) + if err != nil { + return err + } + fmt.Print(out) + return nil + } + fmt.Print(formatEnv(resolved, format, mask)) return nil } @@ -318,6 +335,20 @@ func renderAllEnv(resolvedByService map[string]map[string]string, names []string return nil } + // GitHub Actions parses $GITHUB_ENV line by line and rejects lines that are + // not KEY=VALUE or heredoc blocks, so the "# " headers used by the + // other formats are omitted here. + if format == envFormatGitHubActions { + for _, name := range names { + block, err := formatGitHubEnv(resolvedByService[name], mask) + if err != nil { + return err + } + fmt.Print(block) + } + return nil + } + for i, name := range names { if i > 0 { fmt.Println() @@ -422,6 +453,9 @@ func envFileContent(resolved map[string]string, format string, mask bool) (strin } return string(b) + "\n", nil } + if format == envFormatGitHubActions { + return formatGitHubEnv(resolved, mask) + } return formatEnv(resolved, format, mask), nil } @@ -518,8 +552,10 @@ func resolveEnvFormat(format string) (string, error) { return envFormatShell, nil case envFormatJSON: return envFormatJSON, nil + case envFormatGitHubActions: + return envFormatGitHubActions, nil default: - return "", fmt.Errorf("invalid --format %q: expected dotenv, shell, or json", format) + return "", fmt.Errorf("invalid --format %q: expected dotenv, shell, json, or github-actions", format) } } @@ -570,3 +606,52 @@ func shellQuoteDouble(v string) string { ) return `"` + replacer.Replace(v) + `"` } + +// formatGitHubEnv renders the environment for appending to the GitHub Actions +// $GITHUB_ENV file. Single-line values use KEY=value; values that contain a +// newline use the KEY<