|
4 | 4 | workflow_dispatch: |
5 | 5 | inputs: |
6 | 6 | confirm_stable_release: |
7 | | - description: Type "publish-stable" to publish the latest dist-tag. |
| 7 | + description: Type "publish-stable" to publish to npm latest. |
8 | 8 | required: true |
9 | 9 | default: alpha-only |
| 10 | + stable_version: |
| 11 | + description: Stable version to publish. First stable should be 0.0.1. |
| 12 | + required: true |
| 13 | + default: 0.0.1 |
10 | 14 |
|
11 | 15 | permissions: |
12 | 16 | contents: write |
13 | | - pull-requests: write |
14 | 17 | id-token: write |
15 | 18 |
|
16 | 19 | env: |
17 | 20 | FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true |
18 | 21 |
|
19 | 22 | concurrency: |
20 | | - group: release |
| 23 | + group: stable-release |
21 | 24 | cancel-in-progress: false |
22 | 25 |
|
23 | 26 | jobs: |
24 | | - release: |
25 | | - if: github.ref_name == 'main' && inputs.confirm_stable_release == 'publish-stable' |
| 27 | + guard: |
| 28 | + name: 1. Release guard |
| 29 | + runs-on: ubuntu-latest |
| 30 | + outputs: |
| 31 | + stable_version: ${{ steps.guard.outputs.stable_version }} |
| 32 | + steps: |
| 33 | + - name: Validate release inputs |
| 34 | + id: guard |
| 35 | + run: | |
| 36 | + if [ "${{ github.ref_name }}" != "main" ]; then |
| 37 | + echo "Stable releases must run from main. Current ref: ${{ github.ref_name }}" >&2 |
| 38 | + exit 1 |
| 39 | + fi |
| 40 | +
|
| 41 | + if [ "${{ inputs.confirm_stable_release }}" != "publish-stable" ]; then |
| 42 | + echo "Stable release is locked. Use confirm_stable_release=publish-stable to publish." >&2 |
| 43 | + exit 1 |
| 44 | + fi |
| 45 | +
|
| 46 | + if ! printf '%s' "${{ inputs.stable_version }}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then |
| 47 | + echo "stable_version must be a stable semver version like 0.0.1." >&2 |
| 48 | + exit 1 |
| 49 | + fi |
| 50 | +
|
| 51 | + echo "stable_version=${{ inputs.stable_version }}" >> "$GITHUB_OUTPUT" |
| 52 | +
|
| 53 | + verify: |
| 54 | + name: 2. Verify source |
| 55 | + needs: guard |
26 | 56 | runs-on: ubuntu-latest |
27 | 57 | steps: |
28 | | - - name: Checkout |
| 58 | + - name: Checkout main |
29 | 59 | uses: actions/checkout@v4 |
30 | 60 |
|
31 | 61 | - name: Setup pnpm |
32 | 62 | uses: pnpm/action-setup@v4 |
33 | 63 | with: |
34 | 64 | version: 10.30.2 |
35 | 65 |
|
36 | | - - name: Setup Node |
| 66 | + - name: Setup Node 24 |
37 | 67 | uses: actions/setup-node@v4 |
38 | 68 | with: |
39 | 69 | node-version: 24 |
40 | 70 | cache: pnpm |
41 | | - registry-url: https://registry.npmjs.org |
42 | 71 |
|
43 | | - - name: Install |
| 72 | + - name: Install dependencies |
| 73 | + id: install |
44 | 74 | run: pnpm install --frozen-lockfile |
45 | 75 |
|
46 | | - - name: Verify |
| 76 | + - name: Typecheck |
| 77 | + id: typecheck |
| 78 | + run: pnpm typecheck |
| 79 | + |
| 80 | + - name: Tests |
| 81 | + id: test |
| 82 | + run: pnpm test |
| 83 | + |
| 84 | + - name: Build package |
| 85 | + id: build |
| 86 | + run: pnpm build |
| 87 | + |
| 88 | + - name: Build docs |
| 89 | + id: docs |
| 90 | + run: pnpm docs:build |
| 91 | + |
| 92 | + - name: README check |
| 93 | + id: readme |
| 94 | + run: pnpm readme:check |
| 95 | + |
| 96 | + - name: Size report |
| 97 | + id: size |
| 98 | + run: pnpm size |
| 99 | + |
| 100 | + - name: Pack dry run |
| 101 | + id: pack |
| 102 | + run: pnpm pack --dry-run |
| 103 | + |
| 104 | + - name: Verify summary |
| 105 | + if: always() |
47 | 106 | run: | |
48 | | - pnpm typecheck |
49 | | - pnpm test |
50 | | - pnpm build |
| 107 | + { |
| 108 | + echo "## Stable release verify" |
| 109 | + echo |
| 110 | + echo "| Stage | Result |" |
| 111 | + echo "| --- | --- |" |
| 112 | + echo "| Install | ${{ steps.install.outcome }} |" |
| 113 | + echo "| Typecheck | ${{ steps.typecheck.outcome }} |" |
| 114 | + echo "| Tests | ${{ steps.test.outcome }} |" |
| 115 | + echo "| Package build | ${{ steps.build.outcome }} |" |
| 116 | + echo "| Docs build | ${{ steps.docs.outcome }} |" |
| 117 | + echo "| README check | ${{ steps.readme.outcome }} |" |
| 118 | + echo "| Size report | ${{ steps.size.outcome }} |" |
| 119 | + echo "| Pack dry run | ${{ steps.pack.outcome }} |" |
| 120 | + } >> "$GITHUB_STEP_SUMMARY" |
| 121 | +
|
| 122 | + publish: |
| 123 | + name: 3. Publish npm latest |
| 124 | + needs: [guard, verify] |
| 125 | + runs-on: ubuntu-latest |
| 126 | + outputs: |
| 127 | + package_version: ${{ steps.version.outputs.package_version }} |
| 128 | + steps: |
| 129 | + - name: Checkout main |
| 130 | + uses: actions/checkout@v4 |
| 131 | + |
| 132 | + - name: Setup pnpm |
| 133 | + uses: pnpm/action-setup@v4 |
| 134 | + with: |
| 135 | + version: 10.30.2 |
51 | 136 |
|
52 | | - - name: Create release PR or publish |
53 | | - uses: changesets/action@v1 |
| 137 | + - name: Setup Node 24 |
| 138 | + uses: actions/setup-node@v4 |
54 | 139 | with: |
55 | | - publish: pnpm release |
| 140 | + node-version: 24 |
| 141 | + cache: pnpm |
| 142 | + registry-url: https://registry.npmjs.org |
| 143 | + |
| 144 | + - name: Install dependencies |
| 145 | + run: pnpm install --frozen-lockfile |
| 146 | + |
| 147 | + - name: Set stable package version |
| 148 | + id: version |
| 149 | + env: |
| 150 | + STABLE_VERSION: ${{ needs.guard.outputs.stable_version }} |
| 151 | + run: | |
| 152 | + node -e "const fs = require('node:fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.version = process.env.STABLE_VERSION; pkg.publishConfig = { access: 'public' }; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');" |
| 153 | + PACKAGE_NAME=$(node -p "require('./package.json').name") |
| 154 | + PACKAGE_VERSION=$(node -p "require('./package.json').version") |
| 155 | + echo "package_version=$PACKAGE_NAME@$PACKAGE_VERSION" >> "$GITHUB_OUTPUT" |
| 156 | +
|
| 157 | + - name: Fail if version already exists |
| 158 | + env: |
| 159 | + PACKAGE_NAME: ${{ steps.version.outputs.package_version }} |
| 160 | + run: | |
| 161 | + if npm view "$PACKAGE_NAME" version >/dev/null 2>&1; then |
| 162 | + echo "$PACKAGE_NAME is already published." >&2 |
| 163 | + exit 1 |
| 164 | + fi |
| 165 | +
|
| 166 | + - name: Build final package |
| 167 | + run: pnpm build |
| 168 | + |
| 169 | + - name: Publish latest |
| 170 | + run: pnpm publish --tag latest --access public --no-git-checks |
56 | 171 | env: |
57 | | - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
58 | 172 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} |
| 173 | + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |
59 | 174 |
|
60 | | - stable-release-locked: |
61 | | - if: inputs.confirm_stable_release != 'publish-stable' |
| 175 | + github-release: |
| 176 | + name: 4. Create GitHub release |
| 177 | + needs: [guard, publish] |
62 | 178 | runs-on: ubuntu-latest |
63 | 179 | steps: |
64 | | - - name: Stable release is locked |
| 180 | + - name: Checkout main |
| 181 | + uses: actions/checkout@v4 |
| 182 | + |
| 183 | + - name: Create GitHub release |
| 184 | + env: |
| 185 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 186 | + STABLE_VERSION: ${{ needs.guard.outputs.stable_version }} |
| 187 | + PACKAGE_VERSION: ${{ needs.publish.outputs.package_version }} |
| 188 | + run: | |
| 189 | + cat > release-notes.md <<EOF |
| 190 | + Stable npm release for $PACKAGE_VERSION. |
| 191 | +
|
| 192 | + Install: |
| 193 | +
|
| 194 | + \`\`\`sh |
| 195 | + npm install @crup/react-timer-hook@$STABLE_VERSION |
| 196 | + \`\`\` |
| 197 | +
|
| 198 | + Docs: https://crup.github.io/react-timer-hook/ |
| 199 | + EOF |
| 200 | +
|
| 201 | + gh release create "v$STABLE_VERSION" \ |
| 202 | + --title "v$STABLE_VERSION" \ |
| 203 | + --notes-file release-notes.md \ |
| 204 | + --target "${{ github.sha }}" |
| 205 | +
|
| 206 | + - name: Release summary |
65 | 207 | run: | |
66 | | - echo "Stable npm publishing is disabled by default." |
67 | | - echo "Use the Prerelease workflow for alpha releases." |
68 | | - echo "To intentionally publish latest, rerun this workflow with confirm_stable_release=publish-stable on main." |
| 208 | + { |
| 209 | + echo "## Stable release published" |
| 210 | + echo |
| 211 | + echo "| Field | Value |" |
| 212 | + echo "| --- | --- |" |
| 213 | + echo "| Package | ${{ needs.publish.outputs.package_version }} |" |
| 214 | + echo "| Dist tag | latest |" |
| 215 | + echo "| GitHub release | v${{ needs.guard.outputs.stable_version }} |" |
| 216 | + } >> "$GITHUB_STEP_SUMMARY" |
0 commit comments