DS-5850 Add qc-stable and beta builds, installable in parallel - #22
Conversation
Each environment now bakes in its own command name, which also names the config and cache directories, so the builds can be installed side by side and stay logged in to different environments at the same time. - Add qc-stable (build tag `qcstable`) and beta (build tag `beta`) configurations. Beta points at beta-client.apimetrics.io but authenticates against production Auth0. This also fixes the duplicate build tag that made config_qc_stable.go collide with config_qc.go. - Derive the config and cache directories from the per-build app name, and map hyphens to underscores when building the <APPNAME>_CONFIG_DIR and <APPNAME>_CACHE_DIR overrides so hyphenated names remain usable. - Report the environment, API host and config directory in --version. - Add goreleaser configs for qc-stable and beta, and an environment selector to the develop workflow. - Sign and notarize tagged environment builds: a semver prerelease suffix on a v0.* tag (-beta-N, -qc-N, -qc-stable-N) selects the environment and publishes a GitHub prerelease. Homebrew and WinGet stay production-only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds explicit build “environments” (prod/beta/qc/qc-stable/dev) to the APImetrics CLI so each build bakes in its own binary name and config/cache directories, enabling parallel installs with independent logins. It also extends the release automation so tagged builds for non-production environments can be signed/notarized and published as GitHub prereleases.
Changes:
- Introduces environment-specific
config_*.gobuild-tag variants (including newbetaandqcstable) and wiresmain.goto initialize the CLI usingappName+envName. - Updates CLI runtime behavior to (a) derive
*_CONFIG_DIR/*_CACHE_DIRenv vars from the command name with hyphens mapped to underscores and (b) show environment + config directory in--versionextra info. - Expands GoReleaser configs and GitHub Actions workflows to build/sign/notarize/publish per-environment artifacts (with prerelease handling for non-production tags).
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
RELEASING.md |
Documents environment targets, tag-to-environment mapping, and release workflow behavior. |
README.md |
Documents non-production builds, parallel installs, env var overrides, and --version output. |
main.go |
Uses build-selected envName/appName, sets environment, and initializes CLI per-build. |
config_qc.go |
Makes QC the default build with explicit envName/appName and updated build constraint. |
config_qc_stable.go |
Adds qc-stable build target and endpoints under qcstable build tag. |
config_prod.go |
Adds explicit production envName/appName. |
config_dev.go |
Adds explicit dev envName/appName. |
config_beta.go |
Adds beta build target (beta API host with production Auth0 settings). |
cli/config.go |
Updates a user-facing message to reference the actual root command name. |
cli/cli.go |
Adds hyphen→underscore env var prefixing for config/cache overrides. |
cli/apiconfig.go |
Records build environment for later display. |
cli/api.go |
Extends --version extra info to include environment and config directory. |
.goreleaser/config-qc.yaml |
Adjusts archive naming to drop prerelease suffix for non-snapshot builds. |
.goreleaser/config-qc-stable.yaml |
Adds GoReleaser config for qc-stable environment builds. |
.goreleaser/config-beta.yaml |
Adds GoReleaser config for beta environment builds. |
.github/workflows/release.yml |
Adds environment resolution from tags/inputs and prerelease handling for non-production releases. |
.github/workflows/develop.yml |
Adds manual environment selection and dynamic artifact naming for develop builds. |
Suppressed comments (6)
.github/workflows/release.yml:125
steps.env.outputs.is-productionuses dot-notation access for an output name containing a hyphen, which will not resolve correctly in GitHub Actions expressions. Use bracket syntax foris-production.
env:
IS_PRODUCTION: ${{ steps.env.outputs.is-production }}
SNAPSHOT: ${{ inputs.snapshot }}
.github/workflows/release.yml:210
IS_PRODUCTIONandPRERELEASEreference step outputs whose names contain hyphens. These must be accessed via bracket syntax (e.g.steps.env.outputs['is-production']) or the workflow expression evaluation can fail.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ENVIRONMENT: ${{ steps.env.outputs.environment }}
IS_PRODUCTION: ${{ steps.env.outputs.is-production }}
PRERELEASE: ${{ steps.env.outputs.prerelease-flag }}
run: |
.github/workflows/release.yml:511
- Same issue as above:
needs.release.outputs.is-productionis accessed with dot notation but the output name contains a hyphen. Use bracket syntax to ensure the expression evaluates correctly.
- name: Publish Homebrew formula
if: ${{ startsWith(github.ref, 'refs/tags/') && steps.secrets_check.outputs.has_signing_cert == 'true' && needs.release.outputs.is-production == 'true' }}
env:
HOMEBREW_TAP_GITHUB_TOKEN: ${{ steps.tap-token.outputs.token }}
run: |
.github/workflows/release.yml:581
needs.release.outputs.is-productionuses dot notation for an output name with a hyphen. Use bracket syntax so this job gate works reliably.
release-winget:
name: Submit WinGet package update
runs-on: windows-latest
needs: [release, release-macos]
if: ${{ startsWith(github.ref, 'refs/tags/') && inputs.snapshot != true && needs.release.outputs.is-production == 'true' }}
timeout-minutes: 15
.github/workflows/develop.yml:77
steps.env.outputs.artifact-nameuses dot notation for an output name that contains a hyphen, which can break expression evaluation. Use bracket syntax when referencingartifact-name.
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ steps.env.outputs.artifact-name }}
if-no-files-found: error
.github/workflows/develop.yml:196
needs.build.outputs.artifact-nameuses dot notation for an output name containing a hyphen. Use bracket syntax to ensure the signed-artifact upload uses the correct base name.
- name: Upload signed macOS artifacts
if: ${{ steps.secrets_check.outputs.has_signing_cert == 'true' }}
uses: actions/upload-artifact@v4
with:
name: ${{ needs.build.outputs.artifact-name }}-macos-signed
if-no-files-found: error
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Thanks @copilot-pull-request-reviewer. All 10 comments (4 inline + 6 suppressed) make the same claim — that output names containing hyphens can't be read with dot notation in Actions expressions and need That isn't correct, and this PR happens to have already disproved it on a real run. I tagged From that run's log, the resolved
The downstream effect is visible too: the release is marked Two follow-ups on the parts the comments raise that the run doesn't directly cover:
Leaving these as-is. Worth noting for anyone reading later: bracket syntax is equally valid, it just isn't required here. |
DS-5850
Adds
qc-stableandbetaas build targets, and makes every environment'sbuild installable alongside the others with its own login.
Parallel installs
Each environment bakes in its own command name, which also names the config and
cache directories. Since the cached OAuth token, project state and spec cache
all live in those directories, the builds no longer clobber each other.
prodapimetricsclient.apimetrics.iobetaapimetrics-betabeta-client.apimetrics.ioapimetrics-qcqc-client.apimetrics.ioqcstableapimetrics-qc-stableqc-stable.apimetrics.iodevapimetrics-devlocalhost:8080Beta points at the beta API host but authenticates against production Auth0,
so a beta login is a real production login.
--versionnow reports which environment a binary was built against:Also maps hyphens to underscores when deriving the
<APPNAME>_CONFIG_DIR/<APPNAME>_CACHE_DIRoverrides, soAPIMETRICS_QC_STABLE_CONFIG_DIRworks.Notarized environment releases
Merges to
developare unchanged — fast, unnotarized artifacts. Tagging nowruns the full sign + notarize pipeline for any environment, so testers can
download from the release assets and run without a Gatekeeper fight.
The environment rides on the tag's semver prerelease suffix, so goreleaser still
sees an ordinary semver tag:
v0.1.0v0.1.0-beta-2v0.1.0-qc-3v0.1.0-qc-stable-1v0.1.0-rc-1For non-production tags the main-branch check is skipped, the release is marked
a GitHub prerelease titled
<tag> (<environment>)so it never shows as "Latestrelease", and Homebrew/WinGet are left alone. Everything else — Developer ID
cert, hardened runtime,
notarytool --wait, publish-on-success — is identicalto a production release.
Non-production archive names drop the prerelease suffix
(
apimetrics-qc-stable-0.3.0-darwin-arm64.tar.gz) since the environment isalready in the project name; the full tag is still baked into the binary, so
--versiondistinguishes-1from-2. Snapshot builds keep theirshort-commit filenames.
Notes for reviewers
called
cli.Init("apimetrics", …), so prod and QC shared one configdirectory — that's the bug being fixed, but it means a current QC user's
token sits at the old path.
-beta-Nnow selects the beta environment. The historicalv0.0.1-beta-1..3tags predate this and were production builds. Use anon-environment suffix (e.g.
-rc-1) for pre-1.0 production milestones.Existing tags are unaffected; the workflow only reacts to newly pushed tags.
notarytoolcan't staple a bareexecutable), so first-run validation is an online Gatekeeper check. That's
the pre-existing production behaviour, unchanged here.
Verification
go test ./...andgo vetpass.existing tag in the repo.
in both snapshot and tagged mode;
goreleaser checkpasses on all fourconfigs. Every
run:block inrelease.ymlpassesbash -n.🤖 Generated with Claude Code