Repository navigation
176 lines (158 loc) · 6.61 KB
/
Copy pathrelease.yml
File metadata and controls
176 lines (158 loc) · 6.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
name: release
# The same pipeline as fabric-agent-adapter's. OFF by default: `release` runs only when
# the repo variable RELEASE_ENABLED is "true", `publish` only when PUBLISH_NPMJS is "true".
#
# Trigger: a v* tag, or a manual run naming an existing tag. Steps: validate -> tag
# reachable from the default branch -> version sync -> GitHub release carrying the
# CHANGELOG section -> vendor every member FROM ITS OWN REPOSITORY at its pinned tag ->
# packed lifecycle smoke (fake Claude, temporary home) -> npm publish -> registry receipt.
#
# Members are cloned over HTTPS. GITHUB_TOKEN reads only this repository, so a private
# member needs the MEMBERS_READ_TOKEN secret (a fine-grained token with Contents: read on
# the member repositories); a public member needs nothing.
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: "Existing vX.Y.Z tag to release and publish (defaults to the ref this runs on)"
required: false
type: string
jobs:
validate:
uses: ./.github/workflows/validate.yml
with:
ref: ${{ inputs.tag || github.ref_name }}
release:
needs: validate
if: vars.RELEASE_ENABLED == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Resolve the tag to release
id: t
env:
TAG: ${{ inputs.tag || github.ref_name }}
run: |
set -eu
[[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "expected a vX.Y.Z tag"; exit 1; }
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "ver=${TAG#v}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
ref: ${{ steps.t.outputs.tag }}
fetch-depth: 0
- name: The tag must be reachable from the default branch
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
# A tag pushed without its branch is a release a fresh clone cannot reach.
DEF="$(gh api "repos/$GITHUB_REPOSITORY" --jq .default_branch)"
git fetch --no-tags origin "$DEF"
git merge-base --is-ancestor HEAD "origin/$DEF" || {
echo "::error::${{ steps.t.outputs.tag }} is not an ancestor of origin/$DEF; push the branch, then re-run."
exit 1
}
- name: Tag must match the manifests (version sync)
run: |
set -eu
for f in package.json plugin/passioncode/.claude-plugin/plugin.json; do
V=$(node -p "require('./$f').version")
[ "$V" = "${{ steps.t.outputs.ver }}" ] || { echo "tag ${{ steps.t.outputs.tag }} != $f $V"; exit 1; }
done
- name: Extract this version's CHANGELOG section
run: |
set -eu
awk -v v="${{ steps.t.outputs.ver }}" '
$0 ~ "^## \\[?v?" v "\\]?([^0-9]|$)" {f=1; next}
/^## \[?v?[0-9]+\.[0-9]+\.[0-9]+/ {f=0}
f {print}
' CHANGELOG.md > /tmp/notes.md
test -s /tmp/notes.md || { echo "no CHANGELOG section for ${{ steps.t.outputs.ver }}"; exit 1; }
- name: Create or refresh the GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
if gh release view "${{ steps.t.outputs.tag }}" >/dev/null 2>&1; then
gh release edit "${{ steps.t.outputs.tag }}" --notes-file /tmp/notes.md
else
gh release create "${{ steps.t.outputs.tag }}" --title "${{ steps.t.outputs.tag }} — PassionCode.ai" \
--notes-file /tmp/notes.md --latest
fi
publish:
# Auth: npm trusted publishing (OIDC, `id-token: write`) is the intended path — the
# package names this workflow as its trusted publisher and no long-lived credential
# exists. NPM_TOKEN (a granular automation token) is honoured while that is being set up.
needs: release
if: vars.PUBLISH_NPMJS == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Resolve the tag to publish
id: t
env:
TAG: ${{ inputs.tag || github.ref_name }}
run: |
set -eu
[[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "expected a vX.Y.Z tag"; exit 1; }
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "ver=${TAG#v}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
ref: ${{ steps.t.outputs.tag }}
- uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: npm must be new enough for trusted publishing
run: npm install -g npm@latest && npm --version
- name: Is this version already on the registry?
id: pub
run: |
set -eu
NAME=$(node -p "require('./package.json').name")
echo "name=$NAME" >> "$GITHUB_OUTPUT"
if npm view "$NAME@${{ steps.t.outputs.ver }}" version >/dev/null 2>&1; then
echo "skip=true" >> "$GITHUB_OUTPUT"; echo "$NAME@${{ steps.t.outputs.ver }} already published"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Vendor every member from its own repository at its tag
if: steps.pub.outputs.skip == 'false'
env:
READ_TOKEN: ${{ secrets.MEMBERS_READ_TOKEN || github.token }}
run: |
set -eu
# The token rides in the clone URL of a throwaway runner and is never printed:
# vendor.mjs reports the member name and ref, not the URL it cloned.
PASSIONCODE_VENDOR_CLONE=1 PASSIONCODE_GIT_BASE="https://x-access-token:${READ_TOKEN}@github.com/" \
node scripts/vendor.mjs --release
npm test
- name: Packed lifecycle smoke with fake Claude in a temporary home
if: steps.pub.outputs.skip == 'false'
run: node scripts/smoke-pack.mjs
- name: npm publish
if: steps.pub.outputs.skip == 'false'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ github.token }}
run: |
set -eu
# prepublishOnly would vendor again from local checkouts; the payload above is
# the one that was tested, so publish it as is.
if [ "$(gh api "repos/$GITHUB_REPOSITORY" --jq .private)" = "false" ]; then
npm publish --ignore-scripts --provenance
else
echo "repository is private — publishing without provenance"
npm publish --ignore-scripts
fi
- name: The registry must actually serve verified tarball bytes
run: |
set -eu
node scripts/check-registry.mjs "${{ steps.pub.outputs.name }}" "${{ steps.t.outputs.ver }}"