From 32eae1df376960a15f8f8a283055fcc1f51aa406 Mon Sep 17 00:00:00 2001 From: Naor Sabag <32329815+naorsabag@users.noreply.github.com> Date: Sun, 10 May 2026 08:45:16 +0000 Subject: [PATCH 1/3] ci(publish): auto-create git tag + GitHub Release on each npm publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Triggered when either web or cli publishes. Tag uses the CLI version (v0.1.0-beta.5 etc) since that's what users `npx`. Release body lists whichever package(s) shipped this run, so a web-only or cli-only bump still produces a record. --generate-notes appends auto-summarized commit history. This is what populates the Releases tab on the GitHub repo — without this, npm publishes leave no trace on github.com beyond the bump commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 48 ++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5ec1fbb..1bbc233 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ on: workflow_dispatch: permissions: - contents: read + contents: write # tag + release creation id-token: write concurrency: @@ -81,3 +81,49 @@ jobs: run: npm publish --tag beta --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + # Tag + GitHub Release whenever ANY publish happened. The tag tracks + # the CLI version since that's the user-visible artifact (`npx + # openhop@beta …`); the release notes list whatever shipped this run + # so a web-only or cli-only bump is still recorded. + - name: Create tag + GitHub Release + if: | + steps.web_status.outputs.publish == 'true' || + steps.cli_status.outputs.publish == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WEB_VERSION: ${{ steps.versions.outputs.web }} + CLI_VERSION: ${{ steps.versions.outputs.cli }} + WEB_PUBLISHED: ${{ steps.web_status.outputs.publish }} + CLI_PUBLISHED: ${{ steps.cli_status.outputs.publish }} + run: | + tag="v$CLI_VERSION" + # If the tag already exists (rare — re-running a workflow on a + # commit whose CLI version was already tagged), skip cleanly. + if gh release view "$tag" > /dev/null 2>&1; then + echo "::notice::release $tag already exists, skipping" + exit 0 + fi + + { + echo "## Published to npm (\`beta\` tag)" + if [ "$WEB_PUBLISHED" = "true" ]; then + echo "- [\`@openhop/web@$WEB_VERSION\`](https://www.npmjs.com/package/@openhop/web/v/$WEB_VERSION)" + fi + if [ "$CLI_PUBLISHED" = "true" ]; then + echo "- [\`openhop@$CLI_VERSION\`](https://www.npmjs.com/package/openhop/v/$CLI_VERSION)" + fi + echo + echo "## Try it" + echo '```sh' + echo "npx openhop@beta demo" + echo '```' + echo + echo "Auto-generated notes (commit history) below." + } > /tmp/release-body.md + + gh release create "$tag" \ + --title "$tag" \ + --notes-file /tmp/release-body.md \ + --generate-notes \ + --target "$GITHUB_SHA" From 2d544e1e1ab97997c5575726e258dff6a5041d62 Mon Sep 17 00:00:00 2001 From: Naor Sabag <32329815+naorsabag@users.noreply.github.com> Date: Sun, 10 May 2026 08:52:34 +0000 Subject: [PATCH 2/3] ci(publish): branch tag scheme on which package shipped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the workflow always tagged v$CLI_VERSION. A web-only bump (CLI version unchanged) would match an existing tag from a prior combined release and skip — leaving the web-only publish without a GitHub Release record. (Caught by CodeRabbit on #104.) New scheme: both shipped → v$CLI_VERSION (combined) cli only → cli/v$CLI_VERSION web only → web/v$WEB_VERSION The skip-if-exists check now runs against the tag we're actually about to create, not an unrelated one. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1bbc233..5462316 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -82,10 +82,13 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - # Tag + GitHub Release whenever ANY publish happened. The tag tracks - # the CLI version since that's the user-visible artifact (`npx - # openhop@beta …`); the release notes list whatever shipped this run - # so a web-only or cli-only bump is still recorded. + # Tag + GitHub Release whenever ANY publish happened. Tag scheme + # branches on what actually shipped so a web-only bump can't collide + # with a CLI tag from a prior run: + # both → v$CLI_VERSION (combined release; CLI is the + # user-facing handle for `npx`) + # cli only → cli/v$CLI_VERSION + # web only → web/v$WEB_VERSION - name: Create tag + GitHub Release if: | steps.web_status.outputs.publish == 'true' || @@ -97,9 +100,17 @@ jobs: WEB_PUBLISHED: ${{ steps.web_status.outputs.publish }} CLI_PUBLISHED: ${{ steps.cli_status.outputs.publish }} run: | - tag="v$CLI_VERSION" - # If the tag already exists (rare — re-running a workflow on a - # commit whose CLI version was already tagged), skip cleanly. + if [ "$WEB_PUBLISHED" = "true" ] && [ "$CLI_PUBLISHED" = "true" ]; then + tag="v$CLI_VERSION" + elif [ "$CLI_PUBLISHED" = "true" ]; then + tag="cli/v$CLI_VERSION" + else + tag="web/v$WEB_VERSION" + fi + + # If THIS tag already exists (re-run of a workflow whose tag + # already landed), skip cleanly. Per-package tags mean we never + # check for an unrelated tag. if gh release view "$tag" > /dev/null 2>&1; then echo "::notice::release $tag already exists, skipping" exit 0 From 54b0a0eed3786ef5c4efa7736ac11ff605454b0c Mon Sep 17 00:00:00 2001 From: Naor Sabag <32329815+naorsabag@users.noreply.github.com> Date: Sun, 10 May 2026 08:58:00 +0000 Subject: [PATCH 3/3] ci(publish): gate release on actual publish-step success, not pre-check intent Previously the release step gated on whether we planned to publish (`steps.<>_status.outputs.publish == 'true'`). A failed `npm publish` (network, auth, registry rejection) would still trigger the release step and produce a "successful publish" record on GitHub. Now: - The publish steps have stable ids (`publish_web`, `publish_cli`). - The release step's `if:` keys off `steps.publish_*.outcome == 'success'`, with `always()` so it stays eligible after a conditionally-skipped publish. - WEB_PUBLISHED / CLI_PUBLISHED env vars come from the same outcome expressions, so the release notes only list packages that actually shipped. (Caught by CodeRabbit on #104.) Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5462316..3209641 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -69,6 +69,7 @@ jobs: # Publish web first — the CLI's package.json depends on the web version. - name: Publish @openhop/web + id: publish_web if: steps.web_status.outputs.publish == 'true' working-directory: packages/web run: npm publish --tag beta --access public @@ -76,6 +77,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Publish openhop CLI + id: publish_cli if: steps.cli_status.outputs.publish == 'true' working-directory: packages/cli run: npm publish --tag beta --access public @@ -90,15 +92,22 @@ jobs: # cli only → cli/v$CLI_VERSION # web only → web/v$WEB_VERSION - name: Create tag + GitHub Release + # Gate on actual step outcomes, not the pre-check intent — a failed + # `npm publish` (network, auth, registry rejection) shouldn't still + # produce a "successful publish" release record. `always()` keeps + # this step eligible to run even though the upstream publish step + # may have been conditionally skipped. if: | - steps.web_status.outputs.publish == 'true' || - steps.cli_status.outputs.publish == 'true' + always() && ( + steps.publish_web.outcome == 'success' || + steps.publish_cli.outcome == 'success' + ) env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} WEB_VERSION: ${{ steps.versions.outputs.web }} CLI_VERSION: ${{ steps.versions.outputs.cli }} - WEB_PUBLISHED: ${{ steps.web_status.outputs.publish }} - CLI_PUBLISHED: ${{ steps.cli_status.outputs.publish }} + WEB_PUBLISHED: ${{ steps.publish_web.outcome == 'success' }} + CLI_PUBLISHED: ${{ steps.publish_cli.outcome == 'success' }} run: | if [ "$WEB_PUBLISHED" = "true" ] && [ "$CLI_PUBLISHED" = "true" ]; then tag="v$CLI_VERSION"