Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 38 additions & 10 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ name: Publish to npm
# Trusted publishing (OIDC): no npm token is stored anywhere. npm accepts the publish
# because this repo + this workflow file are registered as the package's trusted publisher
# (`npm trust github opencode-context-tree --file publish.yml --repo navbytes/opencode-tree`).
# Registering a new package: `npm publish` its v0.x by hand once with a real token, then
# `npm trust github <name> --file publish.yml --repo navbytes/opencode-tree`.
on:
release:
types: [published]
workflow_dispatch: # dispatched on the new tag by release.yml
workflow_dispatch: # dispatched on the new tag by release.yml, or run directly
inputs:
package:
description: "Package to publish (only used when run directly, not via release)"
type: choice
default: context-tree
options: [context-tree]

permissions:
id-token: write # mint the OIDC token npm verifies
Expand Down Expand Up @@ -35,16 +43,36 @@ jobs:
- run: bun run typecheck
- run: bun test

# The tag is the version: stamp it into package.json in this checkout only. A prerelease
# (0.3.0-beta.1) goes under the `beta` dist-tag so `latest` keeps pointing at the last stable.
- name: Version from the release tag
id: version
# The tag is <package>-v<version>: derive both, stamp the version into that package's
# package.json in this checkout only. A prerelease (0.3.0-beta.1) goes under the `beta`
# dist-tag so `latest` keeps pointing at the last stable. Old-style bare `v*` tags (the
# transition case) have no `-v` in the middle and resolve to context-tree.
- name: Determine package and version
id: meta
run: |
tag="${GITHUB_REF_NAME#v}"
echo "$tag" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$' || { echo "ref '$GITHUB_REF_NAME' is not a version tag" >&2; exit 1; }
npm version "$tag" --no-git-tag-version --allow-same-version
case "$tag" in *-*) echo "dist_tag=beta" ;; *) echo "dist_tag=latest" ;; esac >> "$GITHUB_OUTPUT"
set -euo pipefail
case "$GITHUB_REF_NAME" in
*-v*)
package="${GITHUB_REF_NAME%-v*}"
version="${GITHUB_REF_NAME#*-v}"
;;
v*)
package="context-tree"
version="${GITHUB_REF_NAME#v}"
;;
*)
package="${{ inputs.package }}"
version=""
;;
esac
package="${package:-context-tree}"
echo "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$' || { echo "ref '$GITHUB_REF_NAME' is not a version tag (package=$package, version=$version)" >&2; exit 1; }
case "$version" in *-*) dist_tag=beta ;; *) dist_tag=latest ;; esac
cd "packages/$package" && npm version "$version" --no-git-tag-version --allow-same-version
echo "package=$package" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "dist_tag=$dist_tag" >> "$GITHUB_OUTPUT"

# OIDC trusted publishing needs the package to already exist on npm: the very first
# version of a new package is published once by hand, every later one lands here.
- run: npm publish --provenance --access public --tag "${{ steps.version.outputs.dist_tag }}"
- run: cd "packages/${{ steps.meta.outputs.package }}" && npm publish --provenance --access public --tag "${{ steps.meta.outputs.dist_tag }}"
50 changes: 36 additions & 14 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ name: Release
on:
workflow_dispatch:
inputs:
package:
description: "Package to release"
type: choice
default: context-tree
options: [context-tree]
bump:
description: "Version bump relative to the latest v* tag"
description: "Version bump relative to the latest <package>-v* tag"
type: choice
default: patch
options: [patch, minor, major]
Expand All @@ -26,7 +31,7 @@ permissions:
contents: write # push the tag, create the release
actions: write # dispatch publish.yml

concurrency: release
concurrency: release-${{ inputs.package }}

jobs:
release:
Expand All @@ -48,8 +53,16 @@ jobs:
id: v
run: |
set -euo pipefail
latest="$(git tag --list 'v*' --sort=-v:refname | head -1)"
latest="${latest#v}"; latest="${latest:-0.0.0}"
package="${{ inputs.package }}"
prefix="${package}-v"
latest_tag="$(git tag --list "${prefix}*" --sort=-v:refname | head -1)"
latest="${latest_tag#$prefix}"
if [ -z "$latest" ] && [ "$package" = context-tree ]; then
# transition shim: context-tree's pre-monorepo history used bare v* tags
latest_tag="$(git tag --list 'v*' --sort=-v:refname | head -1)"
latest="${latest_tag#v}"
fi
latest="${latest:-0.0.0}"
explicit="${{ inputs.version }}"
if [ -n "$explicit" ]; then
next="${explicit#v}"
Expand All @@ -62,30 +75,39 @@ jobs:
esac
fi
echo "$next" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$' || { echo "not a semver version: $next" >&2; exit 1; }
git rev-parse -q --verify "refs/tags/v$next" >/dev/null && { echo "tag v$next already exists" >&2; exit 1; }
if ! grep -q "^## ${next}" CHANGELOG.md; then
echo "::warning::CHANGELOG.md has no '## $next' section — add one on main before or after the release"
git rev-parse -q --verify "refs/tags/${prefix}${next}" >/dev/null && { echo "tag ${prefix}${next} already exists" >&2; exit 1; }
if ! grep -q "^## ${next}" "packages/${package}/CHANGELOG.md"; then
echo "::warning::packages/${package}/CHANGELOG.md has no '## $next' section — add one on main before or after the release"
fi
echo "package=$package" >> "$GITHUB_OUTPUT"
echo "prefix=$prefix" >> "$GITHUB_OUTPUT"
echo "latest=$latest" >> "$GITHUB_OUTPUT"
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
echo "next=$next" >> "$GITHUB_OUTPUT"
echo "Latest tag v$latest → releasing v$next from $(git rev-parse --short HEAD)"
echo "Latest tag ${latest_tag:-none} → releasing ${prefix}${next} from $(git rev-parse --short HEAD)"

- name: Tag, release, publish
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
package="${{ steps.v.outputs.package }}"
prefix="${{ steps.v.outputs.prefix }}"
v="${{ steps.v.outputs.next }}"
tag="${prefix}${v}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "v$v" -m "v$v"
git push origin "v$v"
git tag -a "$tag" -m "$tag"
git push origin "$tag"
prerelease=""; case "$v" in *-*) prerelease="--prerelease" ;; esac
gh release create "v$v" --title "v$v" --generate-notes $prerelease
gh workflow run publish.yml --ref "v$v"
echo "Released v$v; publish workflow dispatched on the tag."
notes_args=()
latest_tag="${{ steps.v.outputs.latest_tag }}"
[ -n "$latest_tag" ] && notes_args=(--notes-start-tag "$latest_tag")
gh release create "$tag" --title "$tag" --generate-notes "${notes_args[@]}" $prerelease
gh workflow run publish.yml --ref "$tag" -f package="$package"
echo "Released $tag; publish workflow dispatched on the tag."

- name: Dry run
if: ${{ inputs.dry_run }}
run: echo "Dry run — would tag v${{ steps.v.outputs.next }} (latest v${{ steps.v.outputs.latest }}), create the release, and dispatch publish.yml."
run: echo "Dry run — would tag ${{ steps.v.outputs.prefix }}${{ steps.v.outputs.next }} (latest ${{ steps.v.outputs.latest_tag || 'none' }}), create the release, and dispatch publish.yml."
Loading
Loading