Skip to content

Commit eab73cf

Browse files
authored
Merge pull request #23508 from storybookjs/canary-release-workflow
Release tooling: Support canary releases
2 parents 9bb9018 + 0b488ca commit eab73cf

File tree

3 files changed

+154
-8
lines changed

3 files changed

+154
-8
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,13 @@ Closes #
3737
Everybody: Please submit all PRs to the `next` branch unless they are specific to the current release. Storybook maintainers cherry-pick bug and documentation fixes into the `main` branch as part of the release process, so you shouldn't need to worry about this. For additional guidance: https://storybook.js.org/docs/react/contribute/how-to-contribute
3838
3939
-->
40+
41+
### 🦋 Canary release
42+
43+
<!-- CANARY_RELEASE_SECTION -->
44+
45+
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.
46+
47+
_core team members can create a canary release [here](https://github.com/storybookjs/storybook/actions/workflows/canary-release-pr.yml) or locally with `gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=<PR_NUMBER>`_
48+
49+
<!-- CANARY_RELEASE_SECTION -->

.github/workflows/canary-release-pr.yml

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
workflow_dispatch:
66
inputs:
77
pr:
8-
description: 'Which pull request number to create a canary release for'
8+
description: 'Pull request number to create a canary release for'
99
required: true
1010
type: number
1111

@@ -14,7 +14,7 @@ env:
1414
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
1515

1616
concurrency:
17-
group: ${{ github.workflow }}-${{ inputs.pr }}
17+
group: ${{ github.workflow }}-${{ github.event.inputs.pr }}
1818
cancel-in-progress: true
1919

2020
permissions:
@@ -25,9 +25,100 @@ jobs:
2525
name: Release canary version
2626
runs-on: ubuntu-latest
2727
environment: release
28-
defaults:
29-
run:
30-
working-directory: scripts
3128
steps:
32-
- name: Do nothing
33-
run: echo "I'm not doing anything"
29+
- name: Fail if triggering actor is not administrator
30+
uses: prince-chrismc/check-actor-permissions-action@v2.0.4
31+
with:
32+
permission: admin
33+
34+
- name: Get pull request information
35+
id: info
36+
env:
37+
GH_TOKEN: ${{ secrets.GH_TOKEN }}
38+
run: |
39+
PR_INFO=$(gh pr view ${{ inputs.pr }} --repo ${{ github.repository }} --json isCrossRepository,headRefOid,headRefName,headRepository,headRepositoryOwner --jq '{isFork: .isCrossRepository, owner: .headRepositoryOwner.login, repoName: .headRepository.name, branch: .headRefName, sha: .headRefOid}')
40+
echo $PR_INFO
41+
# Loop through each key-value pair in PR_INFO and set as step output
42+
for key in $(echo "$PR_INFO" | jq -r 'keys[]'); do
43+
value=$(echo "$PR_INFO" | jq -r ".$key")
44+
echo "$key=$value" >> "$GITHUB_OUTPUT"
45+
done
46+
echo "repository=$(echo "$PR_INFO" | jq -r ".owner")/$(echo "$PR_INFO" | jq -r ".repoName")" >> $GITHUB_OUTPUT
47+
echo "shortSha=$(echo "$PR_INFO" | jq -r ".sha" | cut -c 1-8)" >> $GITHUB_OUTPUT
48+
echo "date=$(date)" >> $GITHUB_OUTPUT
49+
echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
50+
51+
- name: Checkout
52+
uses: actions/checkout@v3
53+
with:
54+
repository: ${{ steps.info.outputs.isFork == 'true' && steps.info.outputs.repository || null }}
55+
ref: ${{ steps.info.outputs.sha }}
56+
token: ${{ secrets.GH_TOKEN }}
57+
58+
- name: Setup Node.js
59+
uses: actions/setup-node@v3
60+
with:
61+
node-version: '16'
62+
63+
- name: Cache dependencies
64+
uses: actions/cache@v3
65+
with:
66+
path: |
67+
~/.yarn/berry/cache
68+
key: yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }}
69+
restore-keys: |
70+
yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }}
71+
yarn-v1-${{ hashFiles('scripts/yarn.lock') }}
72+
yarn-v1
73+
74+
- name: Install dependencies
75+
run: yarn task --task=install --start-from=install
76+
77+
- name: Set version
78+
id: version
79+
working-directory: scripts
80+
run: |
81+
yarn release:version --release-type prerelease --pre-id canary-${{ inputs.pr }}-${{ steps.info.outputs.timestamp }}-${{ steps.info.outputs.shortSha }} --verbose
82+
83+
- name: Publish v${{ steps.version.outputs.next-version }}
84+
env:
85+
YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
86+
working-directory: scripts
87+
run: yarn release:publish --tag canary --verbose
88+
89+
- name: Replace Pull Request Body
90+
# TODO: replace with ivangabriele/find-and-replace-pull-request-body@vX when https://github.com/ivangabriele/find-and-replace-pull-request-body/pull/11 has been released
91+
uses: mcky/find-and-replace-pull-request-body@v1.1.6-mcky
92+
with:
93+
githubToken: ${{ secrets.GH_TOKEN }}
94+
prNumber: ${{ inputs.pr }}
95+
find: 'CANARY_RELEASE_SECTION'
96+
isHtmlCommentTag: true
97+
replace: |
98+
This pull request has been released as version [`${{ steps.version.outputs.next-version }}`](https://npmjs.com/package/@storybook/cli/v/${{ steps.version.outputs.next-version }}). Install it by pinning all your Storybook dependencies to that version.
99+
<details>
100+
<summary>More information</summary>
101+
102+
| | |
103+
| --- | --- |
104+
| **Published version** | [`${{ steps.version.outputs.next-version }}`](https://npmjs.com/package/@storybook/cli/v/${{ steps.version.outputs.next-version }}) |
105+
| **Triggered by** | @${{ github.triggering_actor }} |
106+
| **Repository** | [${{ steps.info.outputs.repository }}](https://github.com/${{ steps.info.outputs.repository }}) |
107+
| **Branch** | [`${{ steps.info.outputs.branch }}`](https://github.com/${{ steps.info.outputs.repository }}/tree/${{ steps.info.outputs.branch }}) |
108+
| **Commit** | [`${{ steps.info.outputs.shortSha }}`](https://github.com/${{ steps.info.outputs.repository }}/commit/${{ steps.info.outputs.sha }}) |
109+
| **Datetime** | ${{ steps.info.outputs.date }} (`${{ steps.info.outputs.timestamp }}`) |
110+
| **Workflow run** | [${{ github.run_id }}](https://github.com/storybookjs/storybook/actions/runs/${{ github.run_id }}) |
111+
112+
To request a new release of this pull request, mention the `@storybookjs/core` team.
113+
114+
_core team members can create a new canary release [here](https://github.com/storybookjs/storybook/actions/workflows/canary-release-pr.yml) or locally with `gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=${{ inputs.pr }}`_
115+
</details>
116+
117+
- name: Create failing comment on PR
118+
if: failure()
119+
env:
120+
GH_TOKEN: ${{ secrets.GH_TOKEN }}
121+
run: |
122+
gh pr comment ${{ inputs.pr }}\
123+
--repo "${{github.repository }}"\
124+
--body "Failed to publish canary version of this pull request, triggered by @${{ github.triggering_actor }}. See the failed workflow run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"

CONTRIBUTING/RELEASING.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,52 @@ Before you start you should make sure that your working tree is clean and the re
385385

386386
## Canary Releases
387387

388-
Not implemented yet. Still work in progress, stay tuned.
388+
It's possible to release any pull request as a canary release multiple times during development. This is an effective way to try out changes in standalone projects without linking projects together via package managers.
389+
390+
To create a canary release, a core team member (or anyone else with administrator privileges) must manually trigger the canary release workflow.
391+
392+
**Before creating a canary release from contributors, the core team member must ensure that the code being released is not malicious.**
393+
394+
Creating a canary release can either be done via GitHub's UI or the [CLI](https://cli.github.com/):
395+
396+
### With GitHub UI
397+
398+
1. Open the workflow UI at https://github.com/storybookjs/storybook/actions/workflows/canary-release-pr.yml
399+
2. On the top right corner, click "Run workflow"
400+
3. For "branch", **always select `next`**, regardless of which branch your pull request is on
401+
4. For the pull request number, input the number for the pull request **without a leading #**
402+
403+
### With the CLI
404+
405+
The following command will trigger a workflow run - replace `<PR_NUMBER>` with the actual pull request number:
406+
407+
```bash
408+
gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=<PR_NUMBER>
409+
```
410+
411+
When the release succeeds, it will update the "Canary release" section of the pull request with information about the release and how to use it (see example [here](https://github.com/storybookjs/storybook/pull/23508)). If it fails, it will create a comment on the pull request, tagging the triggering actor to let them know that it failed (see example [here](https://github.com/storybookjs/storybook/pull/23508#issuecomment-1642850467)).
412+
413+
The canary release will have the following version format: `<CURRENT_VERSION>-canary-<PR_NUMBER>-<TIMESTAMP>-<COMMIT_SHA>.0`, e.g., `7.1.1-canary-23508-1689802571-5ec8c1c3.0`.
414+
415+
- The current version has no actual meaning but softly indicates which version the pull request is based on (e.g., a pull request based on v7.1.0 will get released as a canary version of v7.1.1).
416+
- The timestamp ensures that any subsequent releases are always considered newer.
417+
- The commit hash indicates which exact code has been released.
418+
419+
> ** Note **
420+
> All canary releases are released under the same "canary" dist tag. This means you'll technically be able to install it with `npm install @storybook/cli@canary`. However, this doesn't make sense, as releases from subsequent pull requests will overwrite that tag quickly. Therefore you should always install the specific version string, e.g., `npm install @storybook/cli@7.1.1-canary-23508-1689802571-5ec8c1c3.0.
421+
422+
<details>
423+
<summary>Isn't there a simpler/smarter way to do this?</summary>
424+
425+
The simple approach would be to release canaries for all pull requests automatically; however, this would be insecure as any contributor with Write privileges to the repository (200+ users) could create a malicious pull request that alters the release script to release a malicious release (e.g., release a patch version that adds a crypto miner).
426+
427+
To alleviate this, we only allow the "Release" GitHub environment that contains the npm token to be accessible from workflows running on the protected branches (`next`, `main`, etc.).
428+
429+
You could also be tempted to require approval from admins before running the workflows. However, this would spam the core team with GitHub notifications for workflow runs seeking approval - even when a core team member triggered the workflow. Therefore we are doing it the other way around, requiring contributors and maintainers to ask for a canary release to be created explicitly.
430+
431+
Instead of triggering the workflow manually, you could also do something smart, like trigger it when there's a specific label on the pull request or when someone writes a specific comment on the pull request. However, this would create a lot of unnecessary workflow runs because there isn't a way to filter workflow runs based on labels or comment content. The only way to achieve this would be to trigger the workflow on every comment/labeling, then cancel it if it didn't contain the expected content, which is inefficient.
432+
433+
</details>
389434

390435
## Versioning Scenarios
391436

0 commit comments

Comments
 (0)