Skip to content

Commit 8e72d2c

Browse files
authored
Merge pull request #268 from SolidOS/release
publish-release
2 parents 27d8c9c + 3a9fbad commit 8e72d2c

13 files changed

Lines changed: 1814 additions & 97 deletions

.github/workflows/release.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ on:
1111
options:
1212
- test
1313
- stable
14+
- stable-prepare-pr
15+
- stable-publish
1416
config:
1517
description: Config file path
1618
required: true
@@ -46,11 +48,24 @@ jobs:
4648
- name: Update npm to latest
4749
run: npm install -g npm@latest
4850

51+
- name: Preflight GitHub token permissions
52+
env:
53+
GH_TOKEN: ${{ secrets.GIT_PUSH_TOKEN }}
54+
run: |
55+
if [ -z "$GH_TOKEN" ]; then
56+
echo "GIT_PUSH_TOKEN secret is missing" >&2
57+
exit 1
58+
fi
59+
gh --version
60+
gh auth status -h github.com
61+
gh api repos/SolidOS/solid-logic --jq '.full_name + " permissions=" + (if .permissions.push then "push" else "no-push" end)'
62+
4963
- name: Run release orchestrator
5064
env:
5165
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
52-
GITHUB_TOKEN: ${{ github.token }}
66+
GITHUB_TOKEN: ${{ secrets.GIT_PUSH_TOKEN }}
5367
GIT_PUSH_TOKEN: ${{ secrets.GIT_PUSH_TOKEN }}
68+
GH_TOKEN: ${{ secrets.GIT_PUSH_TOKEN }}
5469
run: |
5570
BRANCH_ARG=""
5671
if [ -n "${{ inputs.branch }}" ]; then

.github/workflows/unpublish.yml

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ on:
1111
description: Exact version to unpublish (e.g. 4.0.5)
1212
required: true
1313
type: string
14+
action:
15+
description: Choose operation (unpublish, deprecate, or auto fallback)
16+
required: true
17+
default: auto
18+
type: choice
19+
options:
20+
- auto
21+
- unpublish
22+
- deprecate
23+
deprecate_message:
24+
description: Message used when deprecating (required for deprecate/auto fallback)
25+
required: true
26+
default: "Deprecated due to release rollback. Please use the latest stable version."
27+
type: string
1428
dry_run:
1529
description: Dry run only (do not execute unpublish)
1630
required: true
@@ -52,29 +66,88 @@ jobs:
5266
- name: Show target
5367
run: |
5468
echo "Target: ${{ inputs.package_name }}@${{ inputs.version }}"
69+
echo "Action: ${{ inputs.action }}"
5570
echo "Dry run: ${{ inputs.dry_run }}"
5671
5772
- name: Dry run check
5873
if: ${{ inputs.dry_run }}
5974
run: |
6075
echo "Dry run enabled. No unpublish command executed."
61-
echo "Would run: npm unpublish ${{ inputs.package_name }}@${{ inputs.version }} --force"
76+
if [ "${{ inputs.action }}" = "deprecate" ]; then
77+
echo "Would run: npm deprecate -f '${{ inputs.package_name }}@${{ inputs.version }}' '${{ inputs.deprecate_message }}'"
78+
elif [ "${{ inputs.action }}" = "unpublish" ]; then
79+
echo "Would run: npm unpublish ${{ inputs.package_name }}@${{ inputs.version }} --force"
80+
else
81+
echo "Would run: npm unpublish ${{ inputs.package_name }}@${{ inputs.version }} --force"
82+
echo "If blocked by npm policy, would run: npm deprecate -f '${{ inputs.package_name }}@${{ inputs.version }}' '${{ inputs.deprecate_message }}'"
83+
fi
6284
63-
- name: Unpublish exact version
85+
- name: Execute package retirement action
6486
if: ${{ !inputs.dry_run }}
6587
env:
6688
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
6789
run: |
68-
npm unpublish ${{ inputs.package_name }}@${{ inputs.version }} --force
90+
set -euo pipefail
91+
target="${{ inputs.package_name }}@${{ inputs.version }}"
92+
message="${{ inputs.deprecate_message }}"
93+
94+
if [ "${{ inputs.action }}" = "deprecate" ]; then
95+
npm deprecate -f "$target" "$message"
96+
exit 0
97+
fi
98+
99+
if [ "${{ inputs.action }}" = "unpublish" ]; then
100+
npm unpublish "$target" --force
101+
exit 0
102+
fi
103+
104+
set +e
105+
output=$(npm unpublish "$target" --force 2>&1)
106+
status=$?
107+
set -e
69108
70-
- name: Verify version is no longer available
109+
echo "$output"
110+
if [ $status -eq 0 ]; then
111+
echo "Unpublish succeeded."
112+
exit 0
113+
fi
114+
115+
if echo "$output" | grep -qi "You can no longer unpublish this package\|Failed criteria\|E405"; then
116+
echo "Unpublish blocked by npm policy; falling back to deprecate..."
117+
npm deprecate -f "$target" "$message"
118+
exit 0
119+
fi
120+
121+
echo "Unpublish failed for a reason other than npm policy; not falling back."
122+
exit $status
123+
124+
- name: Verify outcome
71125
if: ${{ !inputs.dry_run }}
72126
run: |
127+
if [ "${{ inputs.action }}" = "deprecate" ]; then
128+
npm view ${{ inputs.package_name }}@${{ inputs.version }} deprecated
129+
exit 0
130+
fi
131+
132+
if [ "${{ inputs.action }}" = "auto" ]; then
133+
# In auto mode, either unpublish (version no longer resolves) or deprecate (deprecated message exists) is acceptable.
134+
set +e
135+
npm view ${{ inputs.package_name }}@${{ inputs.version }} version >/dev/null 2>&1
136+
exists_status=$?
137+
set -e
138+
if [ $exists_status -ne 0 ]; then
139+
echo "Version no longer resolvable (unpublished)."
140+
exit 0
141+
fi
142+
npm view ${{ inputs.package_name }}@${{ inputs.version }} deprecated
143+
exit 0
144+
fi
145+
73146
set +e
74147
npm view ${{ inputs.package_name }}@${{ inputs.version }} version
75148
status=$?
76149
if [ $status -eq 0 ]; then
77-
echo "Version still resolvable from npm registry."
150+
echo "Version still resolvable from npm registry (unpublish did not occur)."
78151
exit 1
79152
fi
80153
echo "Unpublish appears successful (version not resolvable)."

RELEASE-HOWTO.md

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ Before the release workflow can publish stable versions from GitHub Actions:
2727
3. In `solidos/solidos` → Settings → Secrets and variables → Actions, add:
2828
- `GIT_PUSH_TOKEN`
2929
- `NPM_TOKEN`
30-
4. Confirm target repos allow the token/account to push to `main`, or decide to use a PR-based merge flow instead.
31-
5. Run the `Solidos Release` workflow manually with:
30+
4. For **protected branches on main**: Use `mode=stable-publish` (no extra config needed—it auto-merges PRs)
31+
5. For **unprotected main branch**: Use `mode=stable` (requires direct push access)
32+
6. Run the `Solidos Release` workflow manually with:
3233
- `mode=test` for prerelease publishing
33-
- `mode=stable` for `@latest`
34+
- `mode=stable-publish` for `@latest` (protected branch)
35+
- `mode=stable` for `@latest` (direct push)
3436

3537
## How It Works
3638

@@ -40,40 +42,31 @@ Before the release workflow can publish stable versions from GitHub Actions:
4042
|----------|---------|-------|-----------|
4143
| **Local Testing** | `node scripts/release-orchestrator.js --dry-run=true` | Your computer | ❌ No (prints what would happen) |
4244
| **Test Release** | Manual trigger: mode=test | GitHub Actions | ✅ Yes (@test tag) |
43-
| **Stable Release** | Manual trigger: mode=stable | GitHub Actions | ✅ Yes (@latest tag) |
45+
| **Stable Release (Direct Push)** | Manual trigger: mode=stable | GitHub Actions | ✅ Yes (@latest tag) |
46+
| **Stable Release (Protected Branch)** | Manual trigger: mode=stable-publish | GitHub Actions | ✅ Yes (@latest tag, auto-merges PR) |
4447

45-
**Workflow:**
48+
**Workflow (stable-publish mode with protected branches):**
4649
```
47-
You click "Run workflow" button in GitHub Actions (mode=test or mode=stable)
48-
49-
release.yml starts
50-
51-
Runs: node scripts/release-orchestrator.js --mode <test|stable> ...
52-
53-
Script reads release.config.json (list of repos to release)
54-
50+
You click "Run workflow" → mode=stable-publish
51+
↓ Step 1/3: Create Release PR
52+
Creates release branch from dev → Merges dev into main → Pushes branch → Opens PR
53+
↓ Step 2/3: Auto-Merge PR
54+
Waits for CI checks to pass → Auto-merges PR with --squash → Fetches updated main
55+
↓ Step 3/3: Publish
5556
For each repo listed:
56-
- Clone if missing (optional)
57-
- Checkout branch (dev for test, main for stable)
5857
- npm install
59-
- afterInstall with @test or @latest tags (with fallback)
60-
61-
[Stable mode only: Check skip logic]
62-
- Compare origin/dev vs main
63-
- If dev has new commits → merge origin/dev into main with [skip ci]
64-
- If no changes and --branch not specified → skip this repo
65-
66-
[Test mode: always continues]
67-
68-
- npm test
69-
- npm run build
70-
- npm version (bump patch/minor/major/prerelease)
71-
- npm publish (to npm registry with @test or @latest tag)
72-
- git push + tags (stable only)
73-
74-
Generates release-summary.json
58+
- npm version (bump patch/minor/major)
59+
- npm publish (@latest tag)
60+
- git push + tags all at once
7561
```
7662

63+
**Old two-step flow (still supported for manual workflows):**
64+
- Step 1: `mode=stable-prepare-pr` - creates PR, human reviews/merges
65+
- Step 2: `mode=stable-publish` - requires manual merge first
66+
67+
**New unified flow (recommended):**
68+
- Single `mode=stable-publish` - does all three: create PR, auto-merge, publish
69+
7770
## Key Points
7871

7972
- **Individual repos need nothing special** — they just need `package.json` and npm scripts
@@ -115,15 +108,30 @@ node scripts/release-orchestrator.js --mode test --dry-run=true
115108
- **Always publishes** (no skip logic)
116109
- **Use case:** Pre-release versions for testing from dev branch
117110

118-
**Scenario 3: GitHub Stable Release**
111+
**Scenario 3: GitHub Stable Release (Protected Branches)**
112+
- Click Actions → "Solidos Release" → Run workflow
113+
- Inputs: mode=stable-publish, dry_run=false
114+
- **Single command that does everything:**
115+
1. Creates release branch from dev
116+
2. Opens PR to main
117+
3. Waits for CI checks to pass
118+
4. Auto-merges PR (squash merge)
119+
5. Publishes all packages to npm with `@latest` tag
120+
6. Pushes git tags to main
121+
- Eliminates manual PR merge step
122+
- **Perfect for:** Organizations with branch protection rules on `main`
123+
- **Use case:** Automated stable releases without human intervention on PR merge
124+
125+
**Scenario 4: GitHub Stable Release (Direct Push)**
119126
- Click Actions → "Solidos Release" → Run workflow
120127
- Inputs: mode=stable, dry_run=false
121128
- Automatically merges origin/dev → main if dev has new commits
122129
- Publishes to npm with `@latest` tag
123130
- Creates git tags and pushes to GitHub
124131
- Results in GitHub Actions logs and artifacts
125132
- Skips if dev has no new commits (unless --branch=main specified)
126-
- **Use case:** Production releases to @latest
133+
- **WARNING:** Requires write access to protected branches, may fail with 403
134+
- **Use case:** Repositories without branch protection on `main`
127135

128136
Local dry-run
129137
- Show the exact commands without running them:

0 commit comments

Comments
 (0)