pgserve 1.1.9 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.genie/AGENTS.md +2 -0
- package/.genie/code/AGENTS.md +2 -0
- package/.genie/wishes/release-system-genie-pattern/WISH.md +268 -0
- package/.genie/wishes/release-system-genie-pattern/validation.md +172 -0
- package/.github/workflows/release.yml +233 -111
- package/.github/workflows/{build-all-platforms.yml → version.yml} +30 -6
- package/AGENTS.md +11 -7
- package/Makefile +18 -41
- package/SECURITY.md +109 -0
- package/bin/pgserve-wrapper.cjs +105 -0
- package/knip.json +1 -1
- package/package.json +3 -2
- package/scripts/test-bun-self-heal.sh +163 -0
- package/src/postgres.js +54 -0
- package/src/router.js +70 -5
- package/tests/multi-tenant.test.js +164 -0
- package/.github/release.yml +0 -30
- package/scripts/release.cjs +0 -198
|
@@ -1,138 +1,253 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Single-branch release pipeline modeled on khal-os/desktop.
|
|
4
|
+
#
|
|
5
|
+
# Two trigger paths into the same workflow:
|
|
6
|
+
#
|
|
7
|
+
# 1. push to main with a hand-bumped package.json (no [skip ci] marker)
|
|
8
|
+
# -> auto-detect path: prepare reads package.json, checks if v${version}
|
|
9
|
+
# tag exists, builds + publishes + creates GitHub Release if not.
|
|
10
|
+
#
|
|
11
|
+
# 2. workflow_dispatch with bump=patch|minor|major
|
|
12
|
+
# -> bump job runs `npm version`, commits with [skip ci], tags, pushes.
|
|
13
|
+
# prepare/build/release continue inline in the same workflow run.
|
|
14
|
+
#
|
|
15
|
+
# Bot-loop guard: bump commits carry [skip ci]. Push of those commits is
|
|
16
|
+
# filtered out by the prepare gate, so the bot's own push does not retrigger.
|
|
17
|
+
#
|
|
18
|
+
# Auth: npm OIDC Trusted Publishing (configured via build-all-platforms.yml).
|
|
2
19
|
|
|
3
20
|
on:
|
|
4
|
-
|
|
5
|
-
types: [closed]
|
|
21
|
+
push:
|
|
6
22
|
branches: [main]
|
|
7
23
|
workflow_dispatch:
|
|
8
24
|
inputs:
|
|
9
|
-
|
|
10
|
-
description:
|
|
25
|
+
bump:
|
|
26
|
+
description: "Version bump type"
|
|
11
27
|
required: true
|
|
12
28
|
type: choice
|
|
13
29
|
options:
|
|
14
|
-
-
|
|
15
|
-
-
|
|
30
|
+
- patch
|
|
31
|
+
- minor
|
|
32
|
+
- major
|
|
16
33
|
|
|
17
34
|
concurrency:
|
|
18
|
-
group:
|
|
35
|
+
group: release-${{ github.ref }}
|
|
19
36
|
cancel-in-progress: false
|
|
20
37
|
|
|
21
38
|
permissions:
|
|
22
39
|
contents: write
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
id-token: write # required so the reusable `version.yml` workflow can mint
|
|
41
|
+
# the OIDC token for npm Trusted Publishing — without this,
|
|
42
|
+
# GH rejects the workflow at parse time (startup_failure)
|
|
43
|
+
# because the called job's `id-token: write` permission
|
|
44
|
+
# exceeds what the caller has granted.
|
|
25
45
|
|
|
26
46
|
jobs:
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
(github.event.pull_request.merged == true &&
|
|
34
|
-
(contains(github.event.pull_request.labels.*.name, 'rc') ||
|
|
35
|
-
contains(github.event.pull_request.labels.*.name, 'stable')))
|
|
36
|
-
outputs:
|
|
37
|
-
should_run: ${{ steps.check.outputs.should_run }}
|
|
38
|
-
action: ${{ steps.detect.outputs.action }}
|
|
39
|
-
|
|
40
|
-
steps:
|
|
41
|
-
- name: Check for bot commits
|
|
42
|
-
id: check
|
|
43
|
-
run: |
|
|
44
|
-
# Skip if this is a bot commit (prevents infinite loops)
|
|
45
|
-
if [[ "${{ github.actor }}" == "github-actions[bot]" ]]; then
|
|
46
|
-
echo "Skipping: bot commit detected"
|
|
47
|
-
echo "should_run=false" >> $GITHUB_OUTPUT
|
|
48
|
-
else
|
|
49
|
-
echo "should_run=true" >> $GITHUB_OUTPUT
|
|
50
|
-
fi
|
|
51
|
-
|
|
52
|
-
- name: Detect action
|
|
53
|
-
id: detect
|
|
54
|
-
if: steps.check.outputs.should_run == 'true'
|
|
55
|
-
run: |
|
|
56
|
-
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
|
57
|
-
echo "action=${{ inputs.action }}" >> $GITHUB_OUTPUT
|
|
58
|
-
echo "Action from dispatch: ${{ inputs.action }}"
|
|
59
|
-
elif [[ "${{ contains(github.event.pull_request.labels.*.name, 'stable') }}" == "true" ]]; then
|
|
60
|
-
echo "action=promote" >> $GITHUB_OUTPUT
|
|
61
|
-
echo "Action from label: promote (stable)"
|
|
62
|
-
elif [[ "${{ contains(github.event.pull_request.labels.*.name, 'rc') }}" == "true" ]]; then
|
|
63
|
-
echo "action=bump-rc" >> $GITHUB_OUTPUT
|
|
64
|
-
echo "Action from label: bump-rc"
|
|
65
|
-
else
|
|
66
|
-
echo "No release label found"
|
|
67
|
-
echo "action=" >> $GITHUB_OUTPUT
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
# Version: Bump version and create tag
|
|
71
|
-
version:
|
|
72
|
-
name: Bump Version
|
|
73
|
-
needs: gate
|
|
74
|
-
if: needs.gate.outputs.should_run == 'true' && needs.gate.outputs.action != ''
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Bump (workflow_dispatch only): npm version, commit [skip ci], tag, push.
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
bump:
|
|
51
|
+
name: Bump version
|
|
52
|
+
if: github.event_name == 'workflow_dispatch'
|
|
75
53
|
runs-on: ubuntu-latest
|
|
54
|
+
timeout-minutes: 5
|
|
76
55
|
outputs:
|
|
77
56
|
version: ${{ steps.bump.outputs.version }}
|
|
78
57
|
tag: ${{ steps.bump.outputs.tag }}
|
|
79
|
-
npm_tag: ${{ steps.bump.outputs.npm_tag }}
|
|
80
|
-
is_promote: ${{ steps.bump.outputs.is_promote }}
|
|
81
|
-
|
|
82
58
|
steps:
|
|
83
|
-
-
|
|
84
|
-
uses: actions/checkout@v4
|
|
59
|
+
- uses: actions/checkout@v4
|
|
85
60
|
with:
|
|
61
|
+
ref: main
|
|
86
62
|
fetch-depth: 0
|
|
87
63
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
88
64
|
|
|
89
|
-
-
|
|
90
|
-
uses: actions/setup-node@v4
|
|
65
|
+
- uses: actions/setup-node@v4
|
|
91
66
|
with:
|
|
92
|
-
node-version:
|
|
67
|
+
node-version: "22"
|
|
93
68
|
|
|
94
|
-
- name: Configure
|
|
69
|
+
- name: Configure git
|
|
95
70
|
run: |
|
|
96
71
|
git config user.name "github-actions[bot]"
|
|
97
72
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
98
73
|
|
|
99
|
-
- name:
|
|
74
|
+
- name: Bump version
|
|
100
75
|
id: bump
|
|
101
|
-
run:
|
|
76
|
+
run: |
|
|
77
|
+
npm version "${{ inputs.bump }}" --no-git-tag-version
|
|
78
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
79
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
80
|
+
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
|
81
|
+
echo "Bumped to ${VERSION}"
|
|
82
|
+
|
|
83
|
+
- name: Commit, tag, push
|
|
84
|
+
run: |
|
|
85
|
+
VERSION="${{ steps.bump.outputs.version }}"
|
|
86
|
+
TAG="${{ steps.bump.outputs.tag }}"
|
|
87
|
+
git add package.json
|
|
88
|
+
git commit -m "[skip ci] release ${TAG}"
|
|
89
|
+
git tag "${TAG}"
|
|
90
|
+
git push origin HEAD --follow-tags
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Prepare: resolve version, skip if tag already exists, build changelog.
|
|
94
|
+
#
|
|
95
|
+
# Gate handles both event types:
|
|
96
|
+
# - push: bump is skipped; the !cancelled() && !failure() guard lets
|
|
97
|
+
# this job run regardless. The [skip ci] check filters the
|
|
98
|
+
# bot's own bump-commit push so it does not retrigger.
|
|
99
|
+
# - dispatch: bump succeeded; the workflow_dispatch branch of the OR
|
|
100
|
+
# short-circuits the [skip ci] check (the dispatch event
|
|
101
|
+
# itself does not carry the bump's commit message).
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
prepare:
|
|
104
|
+
name: Prepare release
|
|
105
|
+
needs: bump
|
|
106
|
+
if: |
|
|
107
|
+
!cancelled() && !failure() &&
|
|
108
|
+
(github.event_name == 'workflow_dispatch' ||
|
|
109
|
+
(github.event_name == 'push' &&
|
|
110
|
+
!startsWith(github.event.head_commit.message, '[skip ci]')))
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
timeout-minutes: 5
|
|
113
|
+
outputs:
|
|
114
|
+
version: ${{ steps.ver.outputs.version }}
|
|
115
|
+
tag: ${{ steps.ver.outputs.tag }}
|
|
116
|
+
skip: ${{ steps.ver.outputs.skip }}
|
|
117
|
+
changelog: ${{ steps.changelog.outputs.notes }}
|
|
118
|
+
# Surface the resolved checkout-ref so downstream jobs (build, release)
|
|
119
|
+
# can use it. They cannot reference `needs.bump.outputs.*` directly
|
|
120
|
+
# because they only have `needs: prepare` (or [prepare, build]) — not
|
|
121
|
+
# `bump` — in their needs context.
|
|
122
|
+
ref: ${{ needs.bump.outputs.tag || github.sha }}
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@v4
|
|
125
|
+
with:
|
|
126
|
+
# On dispatch, check out the freshly-pushed tag; on push, the
|
|
127
|
+
# triggering SHA already has the bumped package.json.
|
|
128
|
+
# (Prepare cannot reference its own `outputs.ref` here — that's
|
|
129
|
+
# only available to downstream jobs.)
|
|
130
|
+
ref: ${{ needs.bump.outputs.tag || github.sha }}
|
|
131
|
+
fetch-depth: 0
|
|
132
|
+
|
|
133
|
+
- name: Resolve version
|
|
134
|
+
id: ver
|
|
135
|
+
env:
|
|
136
|
+
GH_TOKEN: ${{ github.token }}
|
|
137
|
+
run: |
|
|
138
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
139
|
+
TAG="v${VERSION}"
|
|
140
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
141
|
+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
142
|
+
|
|
143
|
+
if gh release view "${TAG}" > /dev/null 2>&1; then
|
|
144
|
+
echo "Release ${TAG} already exists — skipping"
|
|
145
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
146
|
+
else
|
|
147
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
- name: Find previous release tag
|
|
151
|
+
id: prev
|
|
152
|
+
if: steps.ver.outputs.skip == 'false'
|
|
153
|
+
env:
|
|
154
|
+
GH_TOKEN: ${{ github.token }}
|
|
155
|
+
run: |
|
|
156
|
+
TAG="${{ steps.ver.outputs.tag }}"
|
|
157
|
+
PREV=$(gh release list --limit 50 --json tagName -q '.[].tagName' | grep -v "^${TAG}$" | head -1)
|
|
158
|
+
if [ -n "$PREV" ] && git merge-base --is-ancestor "$PREV" HEAD 2>/dev/null; then
|
|
159
|
+
echo "tag=${PREV}" >> "$GITHUB_OUTPUT"
|
|
160
|
+
echo "Previous tag: ${PREV}"
|
|
161
|
+
else
|
|
162
|
+
echo "tag=" >> "$GITHUB_OUTPUT"
|
|
163
|
+
echo "No previous tag reachable from HEAD"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
- name: Generate changelog
|
|
167
|
+
id: changelog
|
|
168
|
+
if: steps.ver.outputs.skip == 'false' && steps.prev.outputs.tag != ''
|
|
169
|
+
run: |
|
|
170
|
+
PREV="${{ steps.prev.outputs.tag }}"
|
|
171
|
+
NOTES=$(git log --oneline "${PREV}..HEAD" --pretty="- %s" | head -50)
|
|
172
|
+
{
|
|
173
|
+
echo "notes<<EOF"
|
|
174
|
+
echo "$NOTES"
|
|
175
|
+
echo "EOF"
|
|
176
|
+
} >> "$GITHUB_OUTPUT"
|
|
102
177
|
|
|
103
|
-
-
|
|
178
|
+
# Echo the resolved outputs so downstream skip/no-skip decisions are
|
|
179
|
+
# debuggable from the run log without re-running with step debug.
|
|
180
|
+
- name: Debug resolved outputs
|
|
104
181
|
run: |
|
|
105
|
-
|
|
106
|
-
|
|
182
|
+
echo "version=${{ steps.ver.outputs.version }}"
|
|
183
|
+
echo "tag=${{ steps.ver.outputs.tag }}"
|
|
184
|
+
echo "skip=${{ steps.ver.outputs.skip }}"
|
|
185
|
+
echo "prev=${{ steps.prev.outputs.tag }}"
|
|
107
186
|
|
|
108
|
-
#
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# Build & Publish: matrix build of platform binaries + npm publish via OIDC.
|
|
189
|
+
#
|
|
190
|
+
# The reusable workflow filename is `version.yml` because npm Trusted
|
|
191
|
+
# Publisher is configured against that exact path. Renaming requires
|
|
192
|
+
# updating the npmjs.com Trusted Publisher entry first.
|
|
193
|
+
#
|
|
194
|
+
# The `if:` uses `always() && needs.prepare.result == 'success' &&
|
|
195
|
+
# needs.prepare.outputs.skip != 'true'`. This is the bulletproof pattern
|
|
196
|
+
# for reusable-workflow callers when any upstream job in the `needs:`
|
|
197
|
+
# chain was SKIPPED. With the simpler `needs.prepare.outputs.skip != 'true'`
|
|
198
|
+
# alone, GH Actions silently evaluated the gate as false even though the
|
|
199
|
+
# debug step in `prepare` proved the output was actually `'false'` — a
|
|
200
|
+
# known GH Actions quirk: when a job's transitive `needs:` chain includes
|
|
201
|
+
# a skipped job (here, `bump` is skipped on push events), the reusable-
|
|
202
|
+
# workflow caller's expression evaluator treats `needs.<job>.outputs.<x>`
|
|
203
|
+
# as null/missing regardless of the actual value.
|
|
204
|
+
#
|
|
205
|
+
# `always()` opts out of the implicit success() filter; the explicit
|
|
206
|
+
# `result == 'success'` reinstates it correctly; the outputs check then
|
|
207
|
+
# works as intended.
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
109
209
|
build:
|
|
110
210
|
name: Build & Publish
|
|
111
|
-
needs:
|
|
112
|
-
|
|
211
|
+
needs: prepare
|
|
212
|
+
if: |
|
|
213
|
+
always() &&
|
|
214
|
+
needs.prepare.result == 'success' &&
|
|
215
|
+
needs.prepare.outputs.skip != 'true'
|
|
216
|
+
uses: ./.github/workflows/version.yml
|
|
113
217
|
with:
|
|
114
|
-
version: ${{ needs.
|
|
115
|
-
npm_tag:
|
|
116
|
-
|
|
218
|
+
version: ${{ needs.prepare.outputs.version }}
|
|
219
|
+
npm_tag: latest
|
|
220
|
+
# Use prepare.outputs.ref (which resolves to the bump-job tag on
|
|
221
|
+
# dispatch, or `github.sha` on push). Build cannot reference
|
|
222
|
+
# `needs.bump.*` directly — only `prepare` is in its `needs:` chain.
|
|
223
|
+
# On the push path nobody creates the tag before this runs; the
|
|
224
|
+
# SHA-based checkout works because the merge commit already has the
|
|
225
|
+
# bumped package.json. The release job creates the tag at the end
|
|
226
|
+
# via `gh release create`.
|
|
227
|
+
ref: ${{ needs.prepare.outputs.ref }}
|
|
117
228
|
secrets: inherit
|
|
118
229
|
|
|
119
|
-
#
|
|
120
|
-
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
# Release: download artifacts, create GitHub Release with cliff-free notes.
|
|
232
|
+
# ---------------------------------------------------------------------------
|
|
233
|
+
release:
|
|
121
234
|
name: Create GitHub Release
|
|
122
|
-
needs: [
|
|
123
|
-
if:
|
|
235
|
+
needs: [prepare, build]
|
|
236
|
+
if: |
|
|
237
|
+
always() &&
|
|
238
|
+
needs.prepare.result == 'success' &&
|
|
239
|
+
needs.build.result == 'success' &&
|
|
240
|
+
needs.prepare.outputs.skip != 'true'
|
|
124
241
|
runs-on: ubuntu-latest
|
|
125
|
-
|
|
126
|
-
contents: write
|
|
127
|
-
|
|
242
|
+
timeout-minutes: 10
|
|
128
243
|
steps:
|
|
129
|
-
-
|
|
130
|
-
uses: actions/checkout@v4
|
|
244
|
+
- uses: actions/checkout@v4
|
|
131
245
|
with:
|
|
132
|
-
|
|
246
|
+
# Same as the build job: prefer the bump job's tag (dispatch
|
|
247
|
+
# path) but fall back to the SHA (push path, no tag exists yet).
|
|
248
|
+
ref: ${{ needs.prepare.outputs.ref }}
|
|
133
249
|
|
|
134
|
-
- name: Download
|
|
135
|
-
if: needs.build.result == 'success'
|
|
250
|
+
- name: Download binaries
|
|
136
251
|
uses: actions/download-artifact@v4
|
|
137
252
|
with:
|
|
138
253
|
path: dist/
|
|
@@ -140,28 +255,35 @@ jobs:
|
|
|
140
255
|
merge-multiple: true
|
|
141
256
|
|
|
142
257
|
- name: List artifacts
|
|
258
|
+
run: ls -la dist/
|
|
259
|
+
|
|
260
|
+
- name: Create release
|
|
261
|
+
env:
|
|
262
|
+
GH_TOKEN: ${{ github.token }}
|
|
263
|
+
RELEASE_NOTES: ${{ needs.prepare.outputs.changelog }}
|
|
143
264
|
run: |
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
265
|
+
TAG="${{ needs.prepare.outputs.tag }}"
|
|
266
|
+
VERSION="${{ needs.prepare.outputs.version }}"
|
|
267
|
+
|
|
268
|
+
if [ -z "$RELEASE_NOTES" ]; then
|
|
269
|
+
RELEASE_NOTES="Release ${TAG}"
|
|
149
270
|
fi
|
|
150
271
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
272
|
+
{
|
|
273
|
+
echo "$RELEASE_NOTES"
|
|
274
|
+
echo ""
|
|
275
|
+
echo "## Install"
|
|
276
|
+
echo ""
|
|
277
|
+
echo '```bash'
|
|
278
|
+
echo "npm install pgserve@${VERSION}"
|
|
279
|
+
echo "bunx pgserve@${VERSION}"
|
|
280
|
+
echo '```'
|
|
281
|
+
} > /tmp/release-notes.md
|
|
282
|
+
|
|
283
|
+
# The tag already exists (created by bump job on dispatch, or by the
|
|
284
|
+
# human commit on push). gh release create resolves --target via the
|
|
285
|
+
# tag automatically when omitted.
|
|
286
|
+
gh release create "${TAG}" \
|
|
287
|
+
--title "${TAG}" \
|
|
288
|
+
--notes-file /tmp/release-notes.md \
|
|
166
289
|
dist/*
|
|
167
|
-
fail_on_unmatched_files: false
|
|
@@ -112,7 +112,12 @@ jobs:
|
|
|
112
112
|
needs: build
|
|
113
113
|
runs-on: ubuntu-latest
|
|
114
114
|
if: inputs.version != ''
|
|
115
|
-
environment: npm-publish
|
|
115
|
+
# Note: `environment: npm-publish` was removed because the npmjs.com
|
|
116
|
+
# Trusted Publisher entry for `pgserve` does not declare an environment
|
|
117
|
+
# name. With the env gate present here, the OIDC token's environment
|
|
118
|
+
# claim did not match the registry's expectation and `npm publish`
|
|
119
|
+
# returned 404. Re-add this line if/when the Trusted Publisher entry
|
|
120
|
+
# has its Environment Name field set to `npm-publish`.
|
|
116
121
|
permissions:
|
|
117
122
|
id-token: write
|
|
118
123
|
contents: read
|
|
@@ -123,10 +128,14 @@ jobs:
|
|
|
123
128
|
with:
|
|
124
129
|
ref: ${{ inputs.ref || github.ref }}
|
|
125
130
|
|
|
131
|
+
# Node 24 ships npm 11.5+ which has built-in OIDC trusted-publisher
|
|
132
|
+
# support. Avoids the `npm install -g npm@latest` self-upgrade bug
|
|
133
|
+
# (Arborist clobbering its own promise-retry dep mid-install) that
|
|
134
|
+
# broke rlmx's OIDC publish on Node 22.
|
|
126
135
|
- name: Setup Node.js
|
|
127
136
|
uses: actions/setup-node@v4
|
|
128
137
|
with:
|
|
129
|
-
node-version: '
|
|
138
|
+
node-version: '24'
|
|
130
139
|
registry-url: 'https://registry.npmjs.org'
|
|
131
140
|
|
|
132
141
|
- name: Setup Bun
|
|
@@ -137,6 +146,16 @@ jobs:
|
|
|
137
146
|
- name: Install dependencies
|
|
138
147
|
run: bun install
|
|
139
148
|
|
|
149
|
+
- name: Verify npm version supports OIDC trusted publishing
|
|
150
|
+
run: |
|
|
151
|
+
NPM_VERSION=$(npm --version)
|
|
152
|
+
echo "npm version: ${NPM_VERSION}"
|
|
153
|
+
MAJOR=$(echo "${NPM_VERSION}" | cut -d. -f1)
|
|
154
|
+
if [ "${MAJOR}" -lt 11 ]; then
|
|
155
|
+
echo "::error::npm ${NPM_VERSION} too old — OIDC requires >= 11.5.1. Bump node-version above."
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
|
|
140
159
|
- name: Download all artifacts
|
|
141
160
|
uses: actions/download-artifact@v4
|
|
142
161
|
with:
|
|
@@ -179,13 +198,18 @@ jobs:
|
|
|
179
198
|
echo "published=false" >> $GITHUB_OUTPUT
|
|
180
199
|
fi
|
|
181
200
|
|
|
182
|
-
- name: Publish to npm
|
|
201
|
+
- name: Publish to npm via OIDC
|
|
183
202
|
if: steps.check.outputs.published == 'false'
|
|
203
|
+
env:
|
|
204
|
+
HUSKY: "0"
|
|
205
|
+
# npm auto-enables provenance in any CI env with `id-token: write`,
|
|
206
|
+
# regardless of the --provenance CLI flag. On some runners the
|
|
207
|
+
# server-side sigstore check fails with 422; disable explicitly.
|
|
208
|
+
# OIDC token exchange still happens.
|
|
209
|
+
NPM_CONFIG_PROVENANCE: "false"
|
|
184
210
|
run: |
|
|
185
|
-
echo "Publishing version ${{ inputs.version }} with tag ${{ inputs.npm_tag }}"
|
|
211
|
+
echo "Publishing version ${{ inputs.version }} with tag ${{ inputs.npm_tag }} via OIDC"
|
|
186
212
|
npm publish --access public --tag ${{ inputs.npm_tag }}
|
|
187
|
-
env:
|
|
188
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
189
213
|
|
|
190
214
|
- name: Verify publish
|
|
191
215
|
if: steps.check.outputs.published == 'false'
|
package/AGENTS.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
> **Shared rules in `~/.claude/rules/agent-bible.md`. Read it.**
|
|
2
|
+
|
|
1
3
|
# Genie Agent Framework
|
|
2
4
|
|
|
3
5
|
## Core Identity
|
|
@@ -188,16 +190,18 @@ Before editing ANY implementation file, Base Genie must check:
|
|
|
188
190
|
|
|
189
191
|
**Protocol:** `@.genie/spells/orchestration-boundary-protocol.md`
|
|
190
192
|
|
|
191
|
-
**Release Workflow Protocol:**
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
- ✅
|
|
195
|
-
-
|
|
193
|
+
**Release Workflow Protocol (push-to-main, single tier):**
|
|
194
|
+
- ✅ Manual path: bump locally with `npm version patch|minor|major`, commit, PR to main. Merge → `release.yml` fires automatically.
|
|
195
|
+
- ✅ Bot path: `gh workflow run release.yml -f bump=patch` (or `minor`/`major`). Bot bumps, tags, builds binaries, publishes to npm via OIDC.
|
|
196
|
+
- ✅ Skip: any commit message starting with `[skip ci]` is filtered by the prepare gate.
|
|
197
|
+
- ❌ No `rc`/`stable` PR labels (legacy — removed). No `scripts/release.cjs` (deleted).
|
|
198
|
+
- ❌ Don't edit `package.json` `version` directly on `main` outside the `npm version` flow above.
|
|
199
|
+
- ⚙️ npm publish runs from `.github/workflows/version.yml` (the file npmjs.com Trusted Publisher is bound to).
|
|
196
200
|
|
|
197
|
-
**Documented Violations:**
|
|
201
|
+
**Documented Violations (history):**
|
|
198
202
|
- Bug #168, task b51db539, 2025-10-21 (duplicate implementation)
|
|
199
203
|
- 2025-10-26 (claimed release implementation steps without investigating automation)
|
|
200
|
-
- 2025-12-08 (manually set version to 1.1.0 + triggered workflow_dispatch → version jumped to 1.1.1-rc.1)
|
|
204
|
+
- 2025-12-08 (manually set version to 1.1.0 + triggered workflow_dispatch → version jumped to 1.1.1-rc.1; this class of error is no longer possible — the bump path goes through `gh workflow run`, not direct package.json edits)
|
|
201
205
|
|
|
202
206
|
### 4. Task State Optimization - Live State, Not Documentation 🔴 CRITICAL
|
|
203
207
|
**Rule:** Task state is ephemeral runtime data, not permanent documentation
|
package/Makefile
CHANGED
|
@@ -28,15 +28,13 @@ help: ## Show this help
|
|
|
28
28
|
@echo "$(CYAN)Embedded PostgreSQL server with multi-tenant support$(RESET)"
|
|
29
29
|
@echo ""
|
|
30
30
|
@echo "$(BOLD)Quick Commands:$(RESET)"
|
|
31
|
-
@echo " $(PURPLE)release-rc$(RESET) Create RC release locally"
|
|
32
|
-
@echo " $(PURPLE)release-stable$(RESET) Promote RC to stable"
|
|
33
31
|
@echo " $(PURPLE)test-local$(RESET) Test server locally"
|
|
34
32
|
@echo " $(PURPLE)pm2-start$(RESET) Start server with PM2"
|
|
35
33
|
@echo ""
|
|
36
|
-
@echo "$(BOLD)
|
|
37
|
-
@echo "
|
|
38
|
-
@echo "
|
|
39
|
-
@echo "
|
|
34
|
+
@echo "$(BOLD)Releasing:$(RESET)"
|
|
35
|
+
@echo " Manual: bump locally with 'npm version patch|minor|major', PR to main."
|
|
36
|
+
@echo " Bot: 'gh workflow run release.yml -f bump=patch' (or minor/major)."
|
|
37
|
+
@echo " Skip: any commit message starting with [skip ci] is ignored."
|
|
40
38
|
@echo ""
|
|
41
39
|
@echo "$(BOLD)Build Executables:$(RESET)"
|
|
42
40
|
@echo " $(PURPLE)build$(RESET) Build for current platform"
|
|
@@ -211,35 +209,19 @@ clean-dist: ## Clean build artifacts
|
|
|
211
209
|
@echo "$(GREEN)✅ Dist cleaned!$(RESET)"
|
|
212
210
|
|
|
213
211
|
# ==========================================
|
|
214
|
-
# 🚀
|
|
212
|
+
# 🚀 Releasing
|
|
215
213
|
# ==========================================
|
|
216
|
-
# Releases are
|
|
217
|
-
# - 'rc' label → Creates RC release (1.0.8 → 1.0.9-rc.1)
|
|
218
|
-
# - 'stable' label → Promotes RC to stable (1.0.9-rc.1 → 1.0.9)
|
|
214
|
+
# Releases are driven by .github/workflows/release.yml on push to main.
|
|
219
215
|
#
|
|
220
|
-
#
|
|
216
|
+
# Manual: bump locally with `npm version patch|minor|major`, commit, PR
|
|
217
|
+
# to main. Merge -> release fires automatically.
|
|
218
|
+
# Bot: `gh workflow run release.yml -f bump=patch` (or minor/major).
|
|
219
|
+
# The bot bumps, tags, builds binaries, publishes to npm via OIDC.
|
|
220
|
+
# Skip: any commit message starting with [skip ci] is ignored.
|
|
221
|
+
#
|
|
222
|
+
# There are no Make targets for releases — versioning is intentionally
|
|
223
|
+
# centralized in CI to keep the local-vs-prod workflow paths identical.
|
|
221
224
|
# ==========================================
|
|
222
|
-
.PHONY: release-rc release-stable release-dry
|
|
223
|
-
|
|
224
|
-
release-rc: ## Create RC release locally (for testing)
|
|
225
|
-
@echo "$(CYAN)🔢 Creating RC release...$(RESET)"
|
|
226
|
-
@node scripts/release.cjs --action bump-rc
|
|
227
|
-
@echo ""
|
|
228
|
-
@echo "$(GREEN)✅ RC release created!$(RESET)"
|
|
229
|
-
@echo "$(YELLOW)Push with: git push && git push --tags$(RESET)"
|
|
230
|
-
|
|
231
|
-
release-stable: ## Promote RC to stable locally (for testing)
|
|
232
|
-
@echo "$(CYAN)🎉 Promoting to stable...$(RESET)"
|
|
233
|
-
@node scripts/release.cjs --action promote
|
|
234
|
-
@echo ""
|
|
235
|
-
@echo "$(GREEN)✅ Stable release created!$(RESET)"
|
|
236
|
-
@echo "$(YELLOW)Push with: git push && git push --tags$(RESET)"
|
|
237
|
-
|
|
238
|
-
release-dry: ## Dry-run release (no changes)
|
|
239
|
-
@echo "$(CYAN)🔍 Dry-run release...$(RESET)"
|
|
240
|
-
@node scripts/release.cjs --action bump-rc --dry-run
|
|
241
|
-
@echo ""
|
|
242
|
-
@echo "$(GREEN)✅ Dry-run complete (no changes made)$(RESET)"
|
|
243
225
|
|
|
244
226
|
# ==========================================
|
|
245
227
|
# 📦 Manual Publish (Deprecated)
|
|
@@ -254,19 +236,14 @@ publish-dry: pre-publish ## Dry-run publish (test without actually publishing)
|
|
|
254
236
|
@echo "$(GREEN)✅ Dry-run successful!$(RESET)"
|
|
255
237
|
@echo "$(YELLOW)To actually publish, run: make publish$(RESET)"
|
|
256
238
|
|
|
257
|
-
publish: ## ⚠️ [DEPRECATED]
|
|
239
|
+
publish: ## ⚠️ [DEPRECATED] Releases run from CI on push to main
|
|
258
240
|
@echo ""
|
|
259
241
|
@echo "$(YELLOW)$(BOLD)╔═══════════════════════════════════════════════════════════════╗$(RESET)"
|
|
260
242
|
@echo "$(YELLOW)$(BOLD)║ ⚠️ Manual publish is DEPRECATED ║$(RESET)"
|
|
261
243
|
@echo "$(YELLOW)$(BOLD)║ ║$(RESET)"
|
|
262
|
-
@echo "$(YELLOW)$(BOLD)║
|
|
263
|
-
@echo "$(YELLOW)$(BOLD)║
|
|
264
|
-
@echo "$(YELLOW)$(BOLD)║
|
|
265
|
-
@echo "$(YELLOW)$(BOLD)║ ║$(RESET)"
|
|
266
|
-
@echo "$(YELLOW)$(BOLD)║ Local testing: ║$(RESET)"
|
|
267
|
-
@echo "$(YELLOW)$(BOLD)║ make release-rc Create RC locally ║$(RESET)"
|
|
268
|
-
@echo "$(YELLOW)$(BOLD)║ make release-stable Promote locally ║$(RESET)"
|
|
269
|
-
@echo "$(YELLOW)$(BOLD)║ make release-dry Dry-run (no changes) ║$(RESET)"
|
|
244
|
+
@echo "$(YELLOW)$(BOLD)║ Releases are driven by .github/workflows/release.yml: ║$(RESET)"
|
|
245
|
+
@echo "$(YELLOW)$(BOLD)║ Manual: npm version patch|minor|major, commit, PR to main ║$(RESET)"
|
|
246
|
+
@echo "$(YELLOW)$(BOLD)║ Bot: gh workflow run release.yml -f bump=patch ║$(RESET)"
|
|
270
247
|
@echo "$(YELLOW)$(BOLD)╚═══════════════════════════════════════════════════════════════╝$(RESET)"
|
|
271
248
|
@echo ""
|
|
272
249
|
|