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
6 changes: 3 additions & 3 deletions .claude/commands/create-pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The previous flow outsourced PR-description authoring to a GitHub Action that on

This is `@robosystems/client` — a **published, post-1.0 TypeScript SDK**, largely generated from the RoboSystems API's OpenAPI document and GraphQL schema. Its description is read by integrators deciding whether an upgrade is safe, so precision about the public surface matters more here than in an application repo.

**This repository is public.** The PR title and body are world-readable the moment they're pushed — and, because publishing is triggered by a push to `release/**` rather than by a merge, the text is often public well before the version that carries it. Treat the description as a publication.
**This repository is public.** The PR title and body are world-readable the moment they're pushed — and, because publishing happens only when a release is dispatched rather than on merge, the text is often public well before the version that carries it. Treat the description as a publication.

## Instructions

Expand All @@ -26,7 +26,7 @@ TARGET=${1:-main} # override target via the first argument
```

- **Never PR from the default branch.** If `CURRENT` is `main` (or `master`/`staging`), stop and tell the user to switch to a feature branch first. New branches are created via `npm run feature:create`, not by hand.
- **Never target a release branch.** `release/**` is what `publish.yml` watches; a PR into one is a publish trigger, not a code review. Target `main`.
- **Never target a release branch.** `release/<version>` is a cut record that `create-release.yml` tags and publishes from; a PR into one changes nothing on `main` and is not a code review. Target `main`.
- **Source ≠ target.** If `CURRENT == TARGET`, stop.
- **Uncommitted changes.** Run `git status --porcelain`. If there are uncommitted/staged changes, surface them and ask whether to commit them (respecting the repo's commit rules — never on `main`, stage files by name, no `git add -A`) or proceed without them. The PR description must reflect committed state.
- **Existing PR.** Check `gh pr list --head "$CURRENT" --base "$TARGET" --json url,number`. If a PR already exists, do **not** create a duplicate — offer to update its title/body with `gh pr edit` instead.
Expand Down Expand Up @@ -66,7 +66,7 @@ This is the whole point — ground the description in what actually happened:

A regeneration is not automatically additive: an API schema change can narrow a type or drop a field, and that reaches consumers as a break even though no hand-written line changed. Diff the emitted types before classifying.

- **Version and publish are not this PR's job.** `create-release.yml` bumps the version on `main` and cuts `release/<version>`; the push to that branch is what triggers `publish.yml`. Never bump `package.json` in a feature PR and never imply the PR publishes anything.
- **Version and publish are not this PR's job.** `create-release.yml` bumps the version on `main` and cuts `release/<version>`, tags it, and publishes from the tag in the same run. Never bump `package.json` in a feature PR and never imply the PR publishes anything.

- **Security-fix disclosure.** If the PR fixes a security issue, the prose is often _more_ actionable than the diff — keep it terse and non-actionable. Name the area hardened, never the mechanism. No exploit mechanics, attack scenarios, endpoint enumerations, or payloads. For coordinated disclosure use a private GitHub Security Advisory, never a public issue.

Expand Down
21 changes: 11 additions & 10 deletions .claude/commands/publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ Monitor a release and publish run — pinpoint why it failed, and confirm the ve

## How a release actually happens here

Two workflows, and the trigger between them is the part that surprises people:
One workflow, `create-release.yml` (`workflow_dispatch`, or `npm run release:create`), with three jobs in sequence:

1. **`create-release.yml`** (`workflow_dispatch`, or `npm run release:create`) — reads the current version from `package.json`, computes the next one from the requested bump, commits the bump **to `main`**, cuts `release/<version>` from that commit, and tags it.
2. **`publish.yml`** — triggered by **a push to `release/**`**, not by a merge and not by the tag. It reads the version from `package.json`, checks whether that version already exists on npm, and if not, publishes with `npm publish --provenance --access public` over OIDC trusted publishing.
1. **`create-release`** — reads the current version from `package.json`, computes the next one from the requested bump, commits the bump **to `main`**, and cuts `release/<version>` from that commit.
2. **`create-tag`** — `tag-release.yml` tags that branch and cuts the GitHub Release.
3. **`publish`** — runs once the tag exists, under the `production` environment, so its required-reviewer prompt lands on this same run. It checks out the tag, verifies the version is the one just cut, checks whether that version already exists on npm, and if not, publishes with `npm publish --provenance --access public` over OIDC trusted publishing. npm's trusted publisher is registered against `create-release.yml` and the `production` environment.

So: **merging a PR to `main` publishes nothing.** The release branch push is the publishing event. And because `publish.yml` short-circuits when the version already exists on npm, a re-run of a successful publish is a no-op rather than an error — useful, but it also means "the run went green" is not by itself proof that _this_ run published anything.
So: **merging a PR to `main` publishes nothing.** Dispatching the workflow is the publishing event, and nothing uploads until the deployment is approved on the run page. A failed publish is retried with **Re-run failed jobs** on that run — the version and tag are already cut, and a new release is warranted only when the code itself is at fault. Because the job short-circuits when the version already exists on npm, a re-run after a successful publish is a no-op rather than an error — useful, but it also means "the run went green" is not by itself proof that _this_ run published anything.

`tag-release.yml` writes the GitHub release body separately; see `/release-notes` for the curated-notes override.

Expand All @@ -26,7 +27,6 @@ So: **merging a PR to `main` publishes nothing.** The release branch push is the
## 1. Find the run

```bash
gh run list --workflow=publish.yml --limit 5
gh run list --workflow=create-release.yml --limit 5
gh run view <run-id>
gh run watch <run-id> # live, if it's in flight
Expand All @@ -42,10 +42,11 @@ Classify by stage:

- **`create-release.yml` — branch already exists.** The workflow checks for `release/<version>` before creating it. A failure here usually means a previous run got partway, and the fix is to resolve the leftover branch, not to re-dispatch blindly.
- **`create-release.yml` — push to `main` rejected.** The version bump commits directly to a protected branch and needs `ACTIONS_TOKEN`; a permissions failure here looks like an auth error at the push step.
- **`publish.yml` — "already published".** Not a failure. The version exists on npm, so every subsequent step is skipped by condition. Read it as "nothing to do," and if you expected a publish, the version wasn't bumped.
- **`publish.yml` — install or build.** `npm install` then `prepare:publish` (which runs the `tsc` build). A build failure here is a real code problem that CI on `main` should have caught — check whether the release branch carries something `main` didn't.
- **`publish.yml` — regeneration.** Only runs when dispatched manually with `regenerate: true`, and it generates against the **production API** (`https://api.robosystems.ai`). A failure means the API was unreachable or its schema changed under you; a _success_ here is more dangerous, because it can publish types nobody reviewed. Prefer regenerating in a PR.
- **`publish.yml` — `npm publish`.** OIDC trusted publishing with provenance. Failures are usually the npm-side trust configuration or a version/name mismatch, not the code.
- **`publish` — waiting.** The job is paused on the `production` environment's required reviewer. Nothing is wrong; approve it from the run page (_Review deployments_).
- **`publish` — "checked out version X, expected Y".** The tag the job checked out does not carry the version this run cut. Nothing was published; look at what `create-tag` tagged.
- **`publish` — "already published".** Not a failure. The version exists on npm, so every subsequent step is skipped by condition. Read it as "nothing to do," and if you expected a publish, the version wasn't bumped.
- **`publish` — install or build.** `npm install` then `prepare:publish` (which runs the `tsc` build). A build failure here is a real code problem that CI on `main` should have caught — check whether the release branch carries something `main` didn't.
- **`publish` — `npm publish`.** OIDC trusted publishing with provenance. Failures are usually the npm-side trust configuration (the publisher must name `create-release.yml`) or a version/name mismatch, not the code.

## 3. Verify it actually landed

Expand All @@ -67,6 +68,6 @@ If the version is a major, the consuming apps (`robosystems-app`, `roboledger-ap

## Output

A short status: which workflow, what failed and at which step, the root cause, the re-run link if any, and the verified published version from `npm view`. If nothing failed, say so — don't manufacture work.
A short status: which job, what failed and at which step, the root cause, the re-run link if any, and the verified published version from `npm view`. If nothing failed, say so — don't manufacture work.

$ARGUMENTS
2 changes: 1 addition & 1 deletion .claude/commands/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ Write `.github/release-notes/v<version>.md` — **body only**:

### 6. Hand off — sequencing matters

The file must exist **at the tagged ref**, and there is no window to add it late: `create-release.yml` bumps the version on `main`, cuts `release/<version>` from the result, and tags it in the same run. Pushing that release branch is also what triggers `publish.yml`, so by the time the package is on npm the notes are already fixed. They have to be **merged into `main` before the workflow is dispatched**.
The file must exist **at the tagged ref**, and there is no window to add it late: `create-release.yml` bumps the version on `main`, cuts `release/<version>` from the result, and tags it in the same run. The same run then publishes from that tag, so by the time the package is on npm the notes are already fixed. They have to be **merged into `main` before the workflow is dispatched**.

Write the draft on a feature branch (created via `npm run feature:create`), never on `main`. Present it for review and leave the merge and the dispatch to the user.
2 changes: 1 addition & 1 deletion .claude/commands/staged-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Post-1.0, the emitted type surface **is** the contract. For anything staged here
## Packaging

- Changes to `prepare.js`, `package.json` `files`/`exports`/`types`, or the build output change **what ships**. A wrong `exports` map breaks consumers in ways nothing in this repo tests — cross-check against how the apps actually import.
- Never stage a `package.json` version bump in a feature branch: `create-release.yml` owns the bump on `main`, and pushing `release/**` is what triggers `publish.yml`.
- Never stage a `package.json` version bump in a feature branch: `create-release.yml` owns the bump on `main` and publishes from the tag it cuts.

## Testing

Expand Down
95 changes: 89 additions & 6 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,90 @@ jobs:
project_kind: 'TypeScript SDK'
secrets: inherit

create-summary:
# npm's trusted publisher is pinned to this workflow file and the production
# environment, whose protection rules (required reviewer, deployment
# branches) gate every publish. The approval prompt lands on this run.
publish:
needs: [create-release, create-tag]
runs-on: ubuntu-24.04
environment: production

permissions:
contents: read
id-token: write

steps:
- name: Checkout release tag
uses: actions/checkout@v7
with:
ref: ${{ needs.create-tag.outputs.tag_name }}

- name: Get version from package.json
id: version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Current version: $VERSION"

- name: Verify the checkout is the released version
run: |
EXPECTED="${{ needs.create-release.outputs.new_version }}"
ACTUAL="${{ steps.version.outputs.version }}"

if [ "$ACTUAL" != "$EXPECTED" ]; then
echo "❌ Checked out version $ACTUAL, expected $EXPECTED"
exit 1
fi
echo "✅ Checked out the released version $ACTUAL"

- name: Check if already published
id: check-published
run: |
# Check if this version is already published to npm
if npm view @robosystems/client@${{ steps.version.outputs.version }} version 2>/dev/null; then
echo "already_published=true" >> $GITHUB_OUTPUT
echo "Version ${{ steps.version.outputs.version }} is already published"
else
echo "already_published=false" >> $GITHUB_OUTPUT
echo "Version ${{ steps.version.outputs.version }} is not yet published"
fi

- name: Setup Node.js
if: steps.check-published.outputs.already_published == 'false'
uses: actions/setup-node@v7
with:
node-version: '24'

- name: Install dependencies
if: steps.check-published.outputs.already_published == 'false'
run: npm install

# Skip build and tests - this is a Typescript Client that users build
# The prepare script will handle necessary file generation for publishing

- name: Prepare for publishing
if: steps.check-published.outputs.already_published == 'false'
run: npm run prepare:publish

- name: Publish to npm
if: steps.check-published.outputs.already_published == 'false'
run: npm publish --provenance --access public

- name: Create summary
if: steps.check-published.outputs.already_published == 'false'
run: |
echo "## 📦 Published to npm" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Package:** [@robosystems/client](https://www.npmjs.com/package/@robosystems/client)" >> $GITHUB_STEP_SUMMARY
echo "**Version:** v${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Installation" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "npm install @robosystems/client@${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

create-summary:
needs: [create-release, create-tag, publish]
if: always()
runs-on: ubuntu-24.04
permissions:
Expand All @@ -145,10 +227,11 @@ jobs:
echo "**Tag:** \`${{ needs.create-tag.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Branch:** \`${{ needs.create-release.outputs.branch_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Type:** ${{ inputs.version_type }}" >> $GITHUB_STEP_SUMMARY
echo "**Publish:** ${{ needs.publish.result }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Release URL:** ${{ needs.create-tag.outputs.release_url }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Next Steps" >> $GITHUB_STEP_SUMMARY
echo "The npm package will be automatically published when the release tag is created." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Monitor the publish workflow: [View Workflows](https://github.com/RoboFinSystems/robosystems-typescript-client/actions/workflows/publish.yml)" >> $GITHUB_STEP_SUMMARY

if [ "${{ needs.publish.result }}" != "success" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "The version and tag are cut but the package is not on npm. Fix the cause, then **Re-run failed jobs** on this run; a new release is only needed when the code itself is at fault." >> $GITHUB_STEP_SUMMARY
fi
87 changes: 0 additions & 87 deletions .github/workflows/publish.yml

This file was deleted.

Loading