Skip to content

feat(ci): auto-dispatch publish workflows; fast-winget --submit#361

Merged
rianjs merged 2 commits into
mainfrom
fix/360-auto-publish-cfl-jtk
May 13, 2026
Merged

feat(ci): auto-dispatch publish workflows; fast-winget --submit#361
rianjs merged 2 commits into
mainfrom
fix/360-auto-publish-cfl-jtk

Conversation

@rianjs
Copy link
Copy Markdown
Contributor

@rianjs rianjs commented May 13, 2026

Closes #360

Summary

  • New `trigger-publish` job at the end of `release-cfl.yml` / `release-jtk.yml` dispatches that CLI's chocolatey + winget publishes via `gh workflow run` once GoReleaser and the tag-rename step finish.
  • Migrates both winget publish workflows from the manual fork-clone PR dance to `wingetcreate update --submit` — cuts winget publish wall-clock from ~6 min to ~2.
  • Uses the built-in `GITHUB_TOKEN` with explicit `permissions: actions: write` on the dispatch job (per GitHub's `workflow_dispatch` exception, no PAT needed). Step-level `continue-on-error` on each dispatch ensures a chocolatey failure doesn't skip the winget dispatch.

Pattern source

Adapts open-cli-collective/google-readonly#118 to this monorepo. GoReleaser's `monorepo:` block is Pro-only, so the tag-rename dance and goreleaser configs are left unchanged; instead of relying on `release: published` fanout, the release workflow explicitly dispatches its CLI's publishers. Selective publishing is automatic because each release workflow only invokes its own pair.

Why a Go nudge in each tool?

`auto-release-{cfl,jtk}.yml` gates on `tools/{cfl,jtk}/` or `shared/` Go changes plus a `feat:`/`fix:` prefix on the squash commit. To exercise the new auto-publish path end-to-end on merge, this PR touches one comment line in each `cmd/{cfl,jtk}/main.go` (visible in package docs and traceable to #360). The PR title is `feat(ci): ...` so both auto-release workflows fire on the squash commit. Both releases will publish — that's the test.

TAP_GITHUB_TOKEN preflight

Not relevant here. The new dispatch step uses the built-in `GITHUB_TOKEN` with `permissions: actions: write` scoped to the trigger-publish job. `TAP_GITHUB_TOKEN` is unchanged from its existing tag-push / Homebrew-tap roles.

Test plan

  • `actionlint` clean diff (no new warnings; pre-existing SC2086 baseline preserved)
  • `cd tools/cfl && go build ./... && go test ./...` — 992 tests pass
  • `cd tools/jtk && go build ./... && go test ./...` — 1805 tests pass
  • After merge: both `auto-release-{cfl,jtk}.yml` fire → tags pushed → `release-{cfl,jtk}.yml` run → `trigger-publish` dispatches choco + winget for each CLI → 4 publish runs total, all `event: workflow_dispatch`, all green
  • Confirm new versions on chocolatey.org and Microsoft Winget for both `confluence-cli` and `jira-ticket-cli`
  • Winget wall-clock drops to ~2 min (was ~6 min)

…bmit

Adds a trigger-publish job at the end of release-cfl.yml and release-jtk.yml
that uses gh workflow run to dispatch each CLI's chocolatey and winget
publish workflows once GoReleaser and the tag-rename step finish. Uses the
built-in GITHUB_TOKEN with permissions actions write, and each dispatch
step is independent (step-level continue-on-error) so a chocolatey failure
does not skip the winget dispatch. Job-level continue-on-error preserves
the existing decoupling: publish failures do not fail the release run.

Migrates both winget publish workflows from the manual fork-clone PR dance
to a single wingetcreate update --submit invocation, matching the gro
pattern in open-cli-collective/google-readonly#118. Cuts the winget publish
wall-clock from ~6 minutes to ~2.

Also refreshes the workflow guidance docs to reflect that publish
workflows are no longer manual in the common path.

Closes #360
@rianjs
Copy link
Copy Markdown
Contributor Author

rianjs commented May 13, 2026

No findings.

The PR matches the revised architecture: GoReleaser config and tag rename stay untouched, publish fanout happens after the release job via workflow_dispatch, dispatch uses job-scoped GITHUB_TOKEN with actions: write, both dispatch steps are independently continue-on-error, JTK uses the existing OpenCLICollective.jira-ticket-cli ID, and the Winget clone dance is replaced by wingetcreate update --submit.

Residual risk is limited to the planned post-merge e2e path, which is inherent to this change because the publish dispatch is only observable after the real release workflows run.

@rianjs
Copy link
Copy Markdown
Contributor Author

rianjs commented May 13, 2026

TDD assessment

Verdict: no unit tests warranted. This is correctly an infra-only change.

What the diff actually contains

  • Two new trigger-publish jobs in release-{cfl,jtk}.yml (12 lines each of gh workflow run calls).
  • A rewrite of winget-publish-{cfl,jtk}.yml collapsing ~120 lines of PowerShell (fork-sync, checksum extraction, manual PR submit) into a single wingetcreate update --submit invocation.
  • Doc updates in CLAUDE.md / .github/workflows/CLAUDE.md.
  • Two 3-line package-doc comments in cmd/{cfl,jtk}/main.go — comments only, no behavior.

Unit-testable surface

None. The Go diff is //-prefixed package documentation; there is nothing to assert against. The dispatch logic is a one-liner gh workflow run shell call per target — wrapping that in a shell script just to test it would be cargo-culted coverage. actionlint already gates YAML syntax (per the test plan), and there is no extracted helper or composite action that would benefit from isolated testing.

E2E acceptance criteria

The PR's verification model is the only one that meaningfully exercises this change:

  1. Squash to main fires both auto-release-* workflows (gated by feat: prefix + touched Go files — the comment nudges satisfy this deliberately).
  2. Each release workflow's new trigger-publish job dispatches its CLI's choco + winget pair.
  3. Expect 4 workflow_dispatch publish runs, all green; winget run drops from ~6 min to ~2 min.

This is verifiable post-merge via gh run list and the registries. The continue-on-error: true on each dispatch step is the right call — a choco failure shouldn't suppress winget — though it does mean a silently-failed dispatch won't fail the release. Worth eyeballing the trigger-publish job status explicitly on the first run rather than trusting overall-green.

Recommendation

Ship as-is. No tests to add.

Copy link
Copy Markdown

@monit-reviewer monit-reviewer left a comment

Choose a reason for hiding this comment

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

Automated PR Review

Reviewed commit: 6cda87f

Summary

Reviewer Findings
harness-engineering:harness-architecture-reviewer 3
harness-engineering:harness-enforcement-reviewer 1
harness-engineering:harness-architecture-reviewer (3 findings)

⚠️ Should Fix - .github/workflows/winget-publish-cfl.yml:29

--token ${{ secrets.WINGET_GITHUB_TOKEN }} passes the PAT as a CLI argument, making it visible in /proc//cmdline to other processes on the runner. This is a regression from the removed code, which correctly passed the token via the WINGET_CREATE_GITHUB_TOKEN environment variable that wingetcreate reads natively. Restore the env-var approach and drop the --token flag.

⚠️ Should Fix - .github/workflows/winget-publish-jtk.yml:29

Same token-as-CLI-arg issue as winget-publish-cfl.yml: --token ${{ secrets.WINGET_GITHUB_TOKEN }} exposes the PAT in the process table. The removed code used WINGET_CREATE_GITHUB_TOKEN env var — restore that pattern.

⚠️ Should Fix - .github/workflows/release-cfl.yml:63

VERSION: ${{ needs.goreleaser.outputs.version }} depends on the goreleaser job declaring an outputs: version: block, which is not visible in the diff. If the output is undeclared, VERSION will be an empty string and gh workflow run dispatches both publish workflows with -f version="". Because trigger-publish has continue-on-error: true at both the job and step level, this failure is completely invisible. Verify goreleaser declares this output, or add a preflight step that explicitly fails if VERSION is empty.

harness-engineering:harness-enforcement-reviewer (1 findings)

⚠️ Should Fix - .github/workflows/release-jtk.yml:80

Same goreleaser.outputs.version concern as release-cfl.yml: if the goreleaser job does not declare a version output, VERSION is empty and both jtk publish workflows are dispatched silently with an empty version string, swallowed entirely by the dual continue-on-error.

6 info-level observations excluded. Run with --verbose to include.

2 PR discussion threads considered.


Completed in 2m 59s | $0.78 | sonnet | daemon 0.2.116 | Glorfindel
Field Value
Model sonnet
Reviewers hybrid-synthesis, documentation:docs-reviewer, harness-engineering:harness-architecture-reviewer, harness-engineering:harness-enforcement-reviewer, harness-engineering:harness-knowledge-reviewer, harness-engineering:harness-self-documenting-code-reviewer, security:security-code-auditor
Engine claude · sonnet
Reviewed by pr-review-daemon · monit-pr-reviewer
Duration 2m 59s wall · 6m 41s compute (Reviewers: 1m 52s · Synthesis: 1m 04s)
Cost $0.78
Tokens 285.4k in / 32.7k out
Turns 14

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost
hybrid-synthesis sonnet 32.6k 4.9k 13.6k 19.0k (1h) $0.16
documentation:docs-reviewer haiku 7.1k 13.3k 0 7.1k (1h) $0.08
harness-engineering:harness-architecture-reviewer sonnet 65.0k 4.9k 46.5k 18.5k (1h) $0.17
harness-engineering:harness-enforcement-reviewer sonnet 64.3k 3.2k 46.1k 18.2k (1h) $0.14
harness-engineering:harness-knowledge-reviewer sonnet 88.4k 4.2k 69.5k 18.9k (1h) $0.16
harness-engineering:harness-self-documenting-code-reviewer sonnet 13.8k 398 4.0k 9.7k (1h) $0.05
security:security-code-auditor haiku 14.2k 1.8k 0 14.2k (1h) $0.03

Re-reviews only run when @monit-reviewer is re-requested as a reviewer — push as many commits as you need, then re-request when ready. PRs targeting branches other than main, master are skipped, even when @monit-reviewer is re-requested.

--title "$packageId version $version" `
--body "Automated winget manifest update for $packageId version $version."
--submit `
--token ${{ secrets.WINGET_GITHUB_TOKEN }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium (harness-engineering:harness-architecture-reviewer): --token ${{ secrets.WINGET_GITHUB_TOKEN }} passes the PAT as a CLI argument, making it visible in /proc//cmdline to other processes on the runner. This is a regression from the removed code, which correctly passed the token via the WINGET_CREATE_GITHUB_TOKEN environment variable that wingetcreate reads natively. Restore the env-var approach and drop the --token flag.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the follow-up commit — switched to WINGET_CREATE_GITHUB_TOKEN env var, dropped the --token CLI flag.

--title "$packageId version $version" `
--body "Automated winget manifest update for $packageId version $version."
--submit `
--token ${{ secrets.WINGET_GITHUB_TOKEN }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium (harness-engineering:harness-architecture-reviewer): Same token-as-CLI-arg issue as winget-publish-cfl.yml: --token ${{ secrets.WINGET_GITHUB_TOKEN }} exposes the PAT in the process table. The removed code used WINGET_CREATE_GITHUB_TOKEN env var — restore that pattern.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the same commit alongside the cfl side.


trigger-publish:
needs: goreleaser
runs-on: ubuntu-latest
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium (harness-engineering:harness-architecture-reviewer): VERSION: ${{ needs.goreleaser.outputs.version }} depends on the goreleaser job declaring an outputs: version: block, which is not visible in the diff. If the output is undeclared, VERSION will be an empty string and gh workflow run dispatches both publish workflows with -f version="". Because trigger-publish has continue-on-error: true at both the job and step level, this failure is completely invisible. Verify goreleaser declares this output, or add a preflight step that explicitly fails if VERSION is empty.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The output IS declared — see release-cfl.yml lines 14-15 (unchanged by this PR):

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get_version.outputs.version }}

It's not visible in the diff because that block wasn't touched, but needs.goreleaser.outputs.version resolves correctly. Acknowledging the daemon couldn't see it from the diff alone.


trigger-publish:
needs: goreleaser
runs-on: ubuntu-latest
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium (harness-engineering:harness-enforcement-reviewer): Same goreleaser.outputs.version concern as release-cfl.yml: if the goreleaser job does not declare a version output, VERSION is empty and both jtk publish workflows are dispatched silently with an empty version string, swallowed entirely by the dual continue-on-error.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same as the cfl thread — release-jtk.yml lines 14-15 declare the version output identically to release-cfl.yml. Unchanged by this PR; the value is real, not empty.

Wingetcreate accepts the token via WINGET_CREATE_GITHUB_TOKEN env var as
well as the --token flag; the env-var form avoids exposing the PAT in the
process command line.
@rianjs
Copy link
Copy Markdown
Contributor Author

rianjs commented May 13, 2026

These are low-value, please approve the PR

@monit-reviewer monit-reviewer dismissed their stale review May 13, 2026 11:15

Superseded by updated review

Copy link
Copy Markdown

@monit-reviewer monit-reviewer left a comment

Choose a reason for hiding this comment

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

Automated PR Review

Reviewed commit: 6b7fb9b

Human Override

Approved at the request of @rianjs (comment).

These are low-value, please approve the PR

No automated reviewers were run.

@rianjs rianjs merged commit cc0deba into main May 13, 2026
7 checks passed
@rianjs rianjs deleted the fix/360-auto-publish-cfl-jtk branch May 13, 2026 11:17
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.

Auto-trigger chocolatey & winget publish on release (cfl & jtk)

2 participants