Skip to content

DS-5850 Add qc-stable and beta builds, installable in parallel - #22

Merged
ndenny merged 1 commit into
developfrom
DS-5850-add-qc-stable-build-configuration-for-cli
Aug 12, 2026
Merged

DS-5850 Add qc-stable and beta builds, installable in parallel#22
ndenny merged 1 commit into
developfrom
DS-5850-add-qc-stable-build-configuration-for-cli

Conversation

@ndenny

@ndenny ndenny commented Aug 12, 2026

Copy link
Copy Markdown
Member

DS-5850

Adds qc-stable and beta as build targets, and makes every environment's
build 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.

Environment Build tag Command API host
production prod apimetrics client.apimetrics.io
beta beta apimetrics-beta beta-client.apimetrics.io
qc (default) apimetrics-qc qc-client.apimetrics.io
qc-stable qcstable apimetrics-qc-stable qc-stable.apimetrics.io
dev dev apimetrics-dev localhost:8080

Beta points at the beta API host but authenticates against production Auth0,
so a beta login is a real production login.

--version now reports which environment a binary was built against:

$ apimetrics-qc-stable --version
apimetrics-qc-stable version 0.3.0-qc-stable-1
Environment:      qc-stable (https://qc-stable.apimetrics.io)
Config directory: /Users/you/Library/Application Support/apimetrics-qc-stable
API spec version: ...

Also maps hyphens to underscores when deriving the <APPNAME>_CONFIG_DIR /
<APPNAME>_CACHE_DIR overrides, so APIMETRICS_QC_STABLE_CONFIG_DIR works.

Notarized environment releases

Merges to develop are unchanged — fast, unnotarized artifacts. Tagging now
runs 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:

Tag Environment Release
v0.1.0 production normal, Homebrew + WinGet updated
v0.1.0-beta-2 beta prerelease
v0.1.0-qc-3 qc prerelease
v0.1.0-qc-stable-1 qc-stable prerelease
v0.1.0-rc-1 production normal (unrecognized suffixes fall back)

For non-production tags the main-branch check is skipped, the release is marked
a GitHub prerelease titled <tag> (<environment>) so it never shows as "Latest
release", and Homebrew/WinGet are left alone. Everything else — Developer ID
cert, hardened runtime, notarytool --wait, publish-on-success — is identical
to a production release.

Non-production archive names drop the prerelease suffix
(apimetrics-qc-stable-0.3.0-darwin-arm64.tar.gz) since the environment is
already in the project name; the full tag is still baked into the binary, so
--version distinguishes -1 from -2. Snapshot builds keep their
short-commit filenames.

Notes for reviewers

  • Existing QC installs will need to log in again. Every build previously
    called cli.Init("apimetrics", …), so prod and QC shared one config
    directory — that's the bug being fixed, but it means a current QC user's
    token sits at the old path.
  • -beta-N now selects the beta environment. The historical
    v0.0.1-beta-1..3 tags predate this and were production builds. Use a
    non-environment suffix (e.g. -rc-1) for pre-1.0 production milestones.
    Existing tags are unaffected; the workflow only reacts to newly pushed tags.
  • Notarization tickets aren't stapled (notarytool can't staple a bare
    executable), so first-run validation is an online Gatekeeper check. That's
    the pre-existing production behaviour, unchanged here.

Verification

  • All five build variants compile; go test ./... and go vet pass.
  • Confirmed on disk that each build gets its own config and cache directory.
  • Tag→environment resolution dry-run over both counter styles and every
    existing tag in the repo.
  • Archive naming verified by running goreleaser v2.12.7 (the version CI pins)
    in both snapshot and tagged mode; goreleaser check passes on all four
    configs. Every run: block in release.yml passes bash -n.

🤖 Generated with Claude Code

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>
Copilot AI lite review requested due to automatic review settings August 12, 2026 00:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_*.go build-tag variants (including new beta and qcstable) and wires main.go to initialize the CLI using appName + envName.
  • Updates CLI runtime behavior to (a) derive *_CONFIG_DIR / *_CACHE_DIR env vars from the command name with hyphens mapped to underscores and (b) show environment + config directory in --version extra 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-production uses dot-notation access for an output name containing a hyphen, which will not resolve correctly in GitHub Actions expressions. Use bracket syntax for is-production.
        env:
          IS_PRODUCTION: ${{ steps.env.outputs.is-production }}
          SNAPSHOT: ${{ inputs.snapshot }}

.github/workflows/release.yml:210

  • IS_PRODUCTION and PRERELEASE reference 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-production is 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-production uses 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-name uses dot notation for an output name that contains a hyphen, which can break expression evaluation. Use bracket syntax when referencing artifact-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-name uses 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.

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/develop.yml
Comment thread .github/workflows/develop.yml
@ndenny

ndenny commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

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 outputs['is-production'] bracket syntax.

That isn't correct, and this PR happens to have already disproved it on a real run. I tagged v0.0.1-qc-1 off this branch and the Release workflow completed successfully: run 31550526690.

From that run's log, the resolved env: block of the Create GitHub draft release step:

ENVIRONMENT: qc
IS_PRODUCTION: false
PRERELEASE: --prerelease

IS_PRODUCTION comes from ${{ steps.env.outputs.is-production }} and PRERELEASE from ${{ steps.env.outputs.prerelease-flag }} — both hyphenated, both dot notation, both resolved. If the expressions had failed to evaluate they'd be empty strings; the same block shows what a genuinely empty value looks like (SNAPSHOT: with nothing after it, since there's no dispatch input on a tag push).

The downstream effect is visible too: the release is marked prerelease: true, which only happens if $PRERELEASE expanded to --prerelease. gh release create does not infer prerelease status from the tag.

Two follow-ups on the parts the comments raise that the run doesn't directly cover:

  • needs.release.outputs.is-production (Homebrew/WinGet gates) is only used in if: conditions, which don't log their values. Both gates did skip as intended. Property access uses the same expression grammar regardless of whether the context is steps or needs, so the proof above covers it — if a.b-c parsed as subtraction it would have failed in the steps case too.
  • develop.yml hasn't been exercised yet: workflow_dispatch isn't on the default branch until this merges, so it can't be dispatched to test. Same expression pattern, but flagging that it's unverified by a run.

Leaving these as-is. Worth noting for anyone reading later: bracket syntax is equally valid, it just isn't required here.

@ndenny
ndenny merged commit da3875f into develop Aug 12, 2026
6 checks passed
@ndenny
ndenny deleted the DS-5850-add-qc-stable-build-configuration-for-cli branch August 12, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants