Skip to content
Open
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
150 changes: 120 additions & 30 deletions .agents/skills/canary/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,158 @@
---
name: canary
description: Triggers a canary release for a Storybook PR. Use when the user wants to publish a canary version, create a pre-release, or test a PR via npm.
description: Finds or publishes a pkg.pr.new canary release for a Storybook branch. Use when the user wants the canary package specifier for a branch or needs to trigger the canary workflow manually.
allowed-tools: Bash
---

# Publish Canary Release
# Find Or Publish Canary Release

Publishes a canary version of Storybook from a PR to npm.
Use this skill to get a branch-specific canary build from `pkg.pr.new`.

## Usage
Canary publishes are driven by the `publish-canary.yml` workflow.

To trigger a canary release, run:
The labels that trigger automatic canary publishes on PRs are:

- `ci:normal`
- `ci:merged`
- `ci:daily`

## Version string

The canary version string is constructed like this:

```text
storybook@https://pkg.pr.new/storybookjs/storybook/storybook@<SHA>
```

Replace `<SHA>` with the full commit SHA.

## Check whether a canary already exists

```bash
gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>
SHA=$(git rev-parse HEAD)
curl -I "https://pkg.pr.new/storybookjs/storybook/storybook@$SHA"
```

## What happens
An HTTP `200` status code means the canary already exists for that commit.

## Decision flow

Use this skill with the following if-then behavior.

1. GitHub Actions builds and publishes the PR as `0.0.0-pr-<PR_NUMBER>-sha-<SHORT_SHA>`
2. The version is published to npm with the `canary` tag
3. The PR body is updated with the exact version and install instructions
### A. If the branch already has a PR with one of the CI labels

## Version format
If the branch already has an associated PR labeled `ci:normal`, `ci:merged`, or `ci:daily`, do not trigger anything manually first. Reuse the workflow run that should already exist.

The canary version follows a **predictable structure**:
Find the labeled PR for the current branch:

```bash
BRANCH=$(git branch --show-current)

gh pr list \
--repo storybookjs/storybook \
--head "$BRANCH" \
--state open \
--json number,title,labels,url \
--jq '.[] | select(any(.labels[]?; .name == "ci:normal" or .name == "ci:merged" or .name == "ci:daily"))'
```
0.0.0-pr-<PR_NUMBER>-sha-<SHORT_SHA>

Find the latest successful canary workflow run for that branch:

```bash
BRANCH=$(git branch --show-current)

RUN_ID=$(gh run list \
--repo storybookjs/storybook \
--workflow publish-canary.yml \
--branch "$BRANCH" \
--event pull_request \
--json databaseId,conclusion \
--jq '.[] | select(.conclusion == "success") | .databaseId' \
| head -n 1)

gh run view "$RUN_ID" --repo storybookjs/storybook
```

Pull the SHA from that run and construct the version string from it:

```bash
RUN_SHA=$(gh run view "$RUN_ID" --repo storybookjs/storybook --json headSha --jq '.headSha')
echo "storybook@https://pkg.pr.new/storybookjs/storybook/storybook@$RUN_SHA"
```

- `<PR_NUMBER>`: The PR number (e.g., `33526`)
- `<SHORT_SHA>`: First 8 characters of the commit SHA (e.g., `a2e09fa2`)
Optionally confirm the package is live:

**Example:** For PR #33526 with commit `a2e09fa284a...`, the canary version is:
`0.0.0-pr-33526-sha-a2e09fa2`
```bash
curl -I "https://pkg.pr.new/storybookjs/storybook/storybook@$RUN_SHA"
```

You can construct the version yourself if you know the PR number and the latest commit SHA on that PR.
### B. If the branch does not have a PR with one of the CI labels

## After publishing
Trigger the canary workflow manually on the branch and watch it finish. It usually takes about 10 minutes.

Check the PR body for the published version. It will show something like:
```bash
BRANCH=$(git branch --show-current)

> This pull request has been released as version `0.0.0-pr-33365-sha-b6656566`
gh workflow run --repo storybookjs/storybook publish-canary.yml --ref "$BRANCH"
```

Then test with:
Find the new workflow run and watch it:

```bash
npx storybook@<VERSION_FROM_PR> sandbox
BRANCH=$(git branch --show-current)

RUN_ID=$(gh run list \
--repo storybookjs/storybook \
--workflow publish-canary.yml \
--branch "$BRANCH" \
--event workflow_dispatch \
--json databaseId \
--jq '.[0].databaseId')

gh run watch "$RUN_ID" --repo storybookjs/storybook
```

When it finishes successfully, pull the SHA from the run and construct the version string:

```bash
RUN_SHA=$(gh run view "$RUN_ID" --repo storybookjs/storybook --json headSha --jq '.headSha')
echo "storybook@https://pkg.pr.new/storybookjs/storybook/storybook@$RUN_SHA"
```

Optionally confirm the package is live:

```bash
curl -I "https://pkg.pr.new/storybookjs/storybook/storybook@$RUN_SHA"
```

Or upgrade an existing project:
## Use the canary

For a new project:

```bash
npm create storybook@https://pkg.pr.new/storybookjs/storybook/storybook@<SHA>
```

For an existing project:

```bash
npx storybook@<VERSION_FROM_PR> upgrade
npx storybook@https://pkg.pr.new/storybookjs/storybook/storybook@<SHA> upgrade
```

## Requirements

- You must have admin permissions on the storybookjs/storybook repo
- The PR must exist and be open
- You need `gh` CLI authenticated
- You need `gh` CLI authenticated for `storybookjs/storybook`
- You need permission to run workflows in the repository for manual dispatch
- The canary workflow is `publish-canary.yml`

## Monitor progress

Watch the workflow run at:
https://github.com/storybookjs/storybook/actions/workflows/publish.yml
Workflow page:

- https://github.com/storybookjs/storybook/actions/workflows/publish-canary.yml

CLI:

```bash
gh run list --repo storybookjs/storybook --workflow publish-canary.yml
```
8 changes: 4 additions & 4 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ Thank you for contributing to Storybook! Please submit all PRs to the `next` bra

</details>

### 🦋 Canary release
<!-- CANARY_RELEASE_HEADING -->
## 🦋 Canary Release - 🚫 Not run
<!-- CANARY_RELEASE_HEADING -->

<!-- CANARY_RELEASE_SECTION -->

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the `@storybookjs/core` team here.

_core team members can create a canary release [here](https://github.com/storybookjs/storybook/actions/workflows/publish.yml) or locally with `gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>`_
This PR does not have a canary release associated. Canary releases are automatically created when one of `ci:normal`, `ci:merged`, or `ci:daily` labels are added to the PR.

<!-- CANARY_RELEASE_SECTION -->

Expand Down
158 changes: 158 additions & 0 deletions .github/workflows/publish-canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
name: Publish Canary Packages

on:
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened, labeled]

env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1

permissions:
contents: read
pull-requests: write

concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.ref_name || github.event.pull_request.number }}
cancel-in-progress: true

jobs:
publish-canary:
name: Publish Canary Packages
if: |
github.event_name == 'workflow_dispatch' ||
(
(github.event.action == 'labeled' && contains(fromJSON('["ci:normal","ci:merged","ci:daily"]'), github.event.label.name)) ||
(github.event.action != 'labeled' && (
contains(github.event.pull_request.labels.*.name, 'ci:normal') ||
contains(github.event.pull_request.labels.*.name, 'ci:merged') ||
contains(github.event.pull_request.labels.*.name, 'ci:daily')
))
)
runs-on: ubuntu-latest
steps:
- name: Build Canary Release Status
id: canary-status
if: ${{ github.event_name == 'pull_request' }}
env:
REPOSITORY: ${{ github.repository }}
RUN_ID: ${{ github.run_id }}
run: |
echo "run_url=https://github.com/$REPOSITORY/actions/runs/$RUN_ID" >> "$GITHUB_OUTPUT"

- name: "Update Canary Release Heading: Pending"
id: replace-canary-heading-pending
if: ${{ github.event_name == 'pull_request' }}
continue-on-error: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
uses: ivangabriele/find-and-replace-pull-request-body@9d26f37218b2649daea37dbc04bfd092b9ebeeca # v1.1.5
with:
githubToken: ${{ github.token }}
find: "CANARY_RELEASE_HEADING"
isHtmlCommentTag: true
replace: "## 🦋 Canary Release - ⏳ [Pending](${{ steps.canary-status.outputs.run_url }})"

- name: Checkout Pull Request Head
uses: actions/checkout@v4
with:
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
persist-credentials: false

- name: Setup Node.js and Install Dependencies
uses: ./.github/actions/setup-node-and-install
with:
install-code-deps: true

- name: Build
working-directory: code
run: yarn task --task compile --start-from=auto --no-link

- name: Publish
id: publish
working-directory: scripts
run: |
yarn workspaces list --json --no-private \
| jq -j '"../" + .location + "\u0000"' \
| xargs -0 -r yarn pkg-pr-new publish --no-template --comment=off --json=./pkg-pr-new-output.json

test -s ./pkg-pr-new-output.json

- name: Build Canary Release Content
id: canary-content
if: ${{ github.event_name == 'pull_request' }}
working-directory: scripts
env:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
run: |
storybook_package_url=$(jq -r '.packages[] | select(.name == "storybook") | .url' ./pkg-pr-new-output.json)

if [[ -z "$storybook_package_url" ]]; then
storybook_package_url="https://pkg.pr.new/storybookjs/storybook/storybook@$COMMIT_SHA"
fi

echo 'heading<<EOF' >> "$GITHUB_OUTPUT"
printf '## 🦋 Canary Release - 🚢 Released `%s`\n' "$storybook_package_url" >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"

echo 'body<<EOF' >> "$GITHUB_OUTPUT"
cat <<EOF >> "$GITHUB_OUTPUT"
This pull request has been released as canary packages. Try it out in a new project or update an existing project with the commands below.

```sh
# For a new project
npm create storybook@$storybook_package_url

# or for an existing project
npx storybook@$storybook_package_url upgrade
```

EOF
echo 'EOF' >> "$GITHUB_OUTPUT"

- name: "Update Canary Release Heading: Released"
id: replace-canary-heading-released
if: ${{ github.event_name == 'pull_request' && steps.canary-content.outcome == 'success' }}
continue-on-error: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
uses: ivangabriele/find-and-replace-pull-request-body@9d26f37218b2649daea37dbc04bfd092b9ebeeca # v1.1.5
with:
githubToken: ${{ github.token }}
find: "CANARY_RELEASE_HEADING"
isHtmlCommentTag: true
replace: ${{ steps.canary-content.outputs.heading }}

- name: Update Canary Release Body
id: replace-pr-body
if: ${{ github.event_name == 'pull_request' }}
continue-on-error: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
uses: ivangabriele/find-and-replace-pull-request-body@9d26f37218b2649daea37dbc04bfd092b9ebeeca # v1.1.5
with:
githubToken: ${{ github.token }}
find: "CANARY_RELEASE_SECTION"
isHtmlCommentTag: true
replace: ${{ steps.canary-content.outputs.body }}

- name: "Update Canary Release Heading: Failed"
id: replace-canary-heading-failed
if: ${{ github.event_name == 'pull_request' && failure() && steps.publish.outcome != 'success' }}
continue-on-error: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
uses: ivangabriele/find-and-replace-pull-request-body@9d26f37218b2649daea37dbc04bfd092b9ebeeca # v1.1.5
with:
githubToken: ${{ github.token }}
find: "CANARY_RELEASE_HEADING"
isHtmlCommentTag: true
replace: "## 🦋 Canary Release - 💥 [Failed](${{ steps.canary-status.outputs.run_url }})"

- name: Create Failure Comment on PR
if: ${{ github.event_name == 'pull_request' && failure() && steps.publish.outcome != 'success' && steps.replace-pr-body.outcome != 'failure' }}
continue-on-error: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPOSITORY: ${{ github.repository }}
TRIGGERING_ACTOR: ${{ github.triggering_actor }}
RUN_ID: ${{ github.run_id }}
run: |
gh pr comment "$PR_NUMBER" \
--repo "$REPOSITORY" \
--body "Failed to publish canary packages for this pull request, triggered by @$TRIGGERING_ACTOR. See the failed workflow run at: https://github.com/$REPOSITORY/actions/runs/$RUN_ID"
Loading
Loading