@sidebase/base-config 0.2.0 → 0.2.2
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/README.md +76 -43
- package/dist/config.d.mts +53 -67
- package/dist/config.d.ts +53 -67
- package/dist/config.mjs +3 -1
- package/dist/eslint/index.d.mts +3 -6
- package/dist/eslint/index.d.ts +3 -6
- package/dist/eslint/index.mjs +25 -56
- package/dist/prisma/index.d.mts +12 -32
- package/dist/prisma/index.d.ts +12 -32
- package/dist/shared/{base-config.CuUhyvQo.d.mts → base-config.BoberVUk.d.mts} +4 -8
- package/dist/shared/{base-config.CuUhyvQo.d.ts → base-config.BoberVUk.d.ts} +4 -8
- package/docs/migration.md +12 -13
- package/package.json +1 -1
- package/presets/base/AGENTS.md +9 -0
- package/presets/base/dockerignore +7 -8
- package/presets/base/github/workflows/streamctl-upgrade.yml +341 -70
- package/presets/base/gitignore +4 -6
- package/presets/base/oxlintrc.json +3 -2
- package/presets/base/pnpm-workspace.yaml +9 -16
- package/presets/base/preset.json +5 -2
- package/presets/config.template.ts +2 -2
- package/presets/nuxt-app/Dockerfile +15 -20
- package/presets/nuxt-app/github/workflows/ci.yml +5 -6
- package/presets/nuxt-app/github/workflows/pr-preview-cleanup.yml +2 -2
- package/presets/nuxt-app/preset.json +1 -1
- package/presets/nuxt-app/templates/ci/e2e-job.yml +2 -5
- package/presets/base/CLAUDE.md +0 -3
package/docs/migration.md
CHANGED
|
@@ -334,10 +334,10 @@ the managed file and is reverted on the next sync.
|
|
|
334
334
|
warns you. `streamctl check` stays green, the file looks deliberate, and a package whose
|
|
335
335
|
install scripts are blocked still installs.
|
|
336
336
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
337
|
+
The consequence is narrower than it first appears. The usual suspects (`sharp`,
|
|
338
|
+
`@tailwindcss/oxide`, `unrs-resolver`, `@parcel/watcher`) ship their native binary as a
|
|
339
|
+
prebuilt optional dependency, so on a platform with a prebuild they keep working whether or
|
|
340
|
+
not they are approved, cold install included. Verified across pnpm
|
|
341
341
|
10.28.1, 10.29.1 and 10.29.3. Where the loss is real:
|
|
342
342
|
|
|
343
343
|
- architectures with no prebuild, where the binary genuinely has to be compiled
|
|
@@ -546,13 +546,12 @@ too, so on first sync the payload appends its block to whatever your repository
|
|
|
546
546
|
Your existing lines stay above it. Nothing is overwritten and nothing is reported.
|
|
547
547
|
|
|
548
548
|
Most of the leftovers are harmless. Ignore patterns are additive, so a duplicated `node_modules`
|
|
549
|
-
or `.env` above the block is
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
time.
|
|
549
|
+
or `.env` above the block is inert; it changes nothing about what the build context carries.
|
|
550
|
+
Delete them anyway, and not for tidiness. The detector below is the only thing that will ever
|
|
551
|
+
tell you a payload entry has started shadowing something of yours, and it is not a one-shot
|
|
552
|
+
check. A future payload version can add an entry that collides with a line you kept above the
|
|
553
|
+
block. A file with leftover duplicates reads `AFFECTED` forever, so it can no longer distinguish
|
|
554
|
+
that from the noise. Clearing them keeps the check able to answer next time.
|
|
556
555
|
|
|
557
556
|
**The case that bites is a negation.** `.dockerignore` is last-match-wins, and the managed block
|
|
558
557
|
is appended at the BOTTOM, so every payload entry beats anything you wrote above it. A repository
|
|
@@ -822,5 +821,5 @@ keeps its own command this way.
|
|
|
822
821
|
|
|
823
822
|
Nothing here is destructive if you work on a branch. `sync` rewrites tracked files, so
|
|
824
823
|
`git diff` shows everything it did and `git restore` undoes it. Keep the migration on its
|
|
825
|
-
own branch and its own commit so that if the sync diff turns out to be larger than
|
|
826
|
-
expected, reverting it is one operation
|
|
824
|
+
own branch and its own commit, so that if the sync diff turns out to be larger than
|
|
825
|
+
expected, reverting it is one operation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sidebase/base-config",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Shared @sidebase base configuration for Nuxt repos: ESLint / Prisma / tsconfig factories on npm, plus the streamctl file-sync preset payload",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sidebase",
|
package/presets/base/AGENTS.md
CHANGED
|
@@ -10,6 +10,15 @@ follow. They are the same across every repo in the fleet.
|
|
|
10
10
|
- Never commit secrets, credentials, or generated build artifacts.
|
|
11
11
|
- Keep all code, comments and identifiers in English.
|
|
12
12
|
|
|
13
|
+
## Comments & wording
|
|
14
|
+
|
|
15
|
+
- Keep comments short. Say WHY the code is the way it is, not what it plainly does.
|
|
16
|
+
- ASCII only: no em dash, en dash, arrow, ellipsis, curly quote, or emoji.
|
|
17
|
+
- One idea per sentence. Cut filler openers ("It is worth noting") and inflated words
|
|
18
|
+
("leverage", "utilize", "robust", "seamless").
|
|
19
|
+
- Do not leave a comment describing an alternative you rejected, or internal ticket
|
|
20
|
+
shorthand a reader cannot resolve.
|
|
21
|
+
|
|
13
22
|
## Types & correctness
|
|
14
23
|
|
|
15
24
|
- Use precise types; avoid `any` and unchecked casts. Parse and validate external input at the boundary instead of trusting it downstream.
|
|
@@ -12,16 +12,15 @@ tests
|
|
|
12
12
|
.env
|
|
13
13
|
.env.*
|
|
14
14
|
!.env.example
|
|
15
|
-
# The build
|
|
16
|
-
#
|
|
17
|
-
# is intentionally allowed through.
|
|
15
|
+
# The build stage globs `.npmrc*`, so keep token-bearing backups like
|
|
16
|
+
# `.npmrc.local` out of the build context. `.npmrc` itself is allowed through.
|
|
18
17
|
.npmrc.*
|
|
19
18
|
.DS_Store
|
|
20
|
-
# Local dev state
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# .gitignore), so `**/local.*` is required to reach the nested paths.
|
|
19
|
+
# Local dev state from `pnpm db` or `docker compose up`. `prisma/pglite-data` is
|
|
20
|
+
# tens of MB, and root-owned `prisma/local.postgres_data` fails a rootless build.
|
|
21
|
+
# Root-anchored so `src/path/local.ts` still reaches the build context.
|
|
24
22
|
prisma/client
|
|
25
23
|
prisma/pglite-data
|
|
26
24
|
pgliteHealthz
|
|
27
|
-
|
|
25
|
+
local.*
|
|
26
|
+
prisma/local.*
|
|
@@ -1,49 +1,30 @@
|
|
|
1
|
-
# Managed by streamctl
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# TOKEN: the PR is opened with `secrets.STREAMCTL_PR_TOKEN` (a GitHub App
|
|
6
|
-
# installation token or a machine-account PAT), NOT the default `GITHUB_TOKEN`.
|
|
7
|
-
# A PR created with `GITHUB_TOKEN` does NOT trigger `pull_request` workflows, so
|
|
8
|
-
# this repo's own `check` gate would never run on the bot's PR. The bot identity
|
|
9
|
-
# for the fleet is still being decided; treat this file as inert and provide the
|
|
10
|
-
# secret before enabling. No registry token is needed: the payload is public npm.
|
|
11
|
-
# A repo with OTHER private dependencies must opt this file out
|
|
12
|
-
# (`files: { ".github/workflows/streamctl-upgrade.yml": "off" }`) and wire its own auth.
|
|
13
|
-
#
|
|
14
|
-
# Actions are pinned by full commit SHA (this workflow handles a token); the version
|
|
15
|
-
# tag is in the trailing comment.
|
|
1
|
+
# Managed by streamctl. Enabled by `automation.upgradePr`.
|
|
2
|
+
# Repos with private dependencies should own this workflow.
|
|
3
|
+
# Workflow updates need the configured token with Contents, Pull requests, and Workflows write.
|
|
16
4
|
name: streamctl upgrade
|
|
17
5
|
|
|
18
6
|
on:
|
|
19
7
|
schedule:
|
|
20
|
-
- cron: "0 6 * * 1"
|
|
8
|
+
- cron: "0 6 * * 1"
|
|
21
9
|
workflow_dispatch:
|
|
22
10
|
|
|
23
|
-
# Least privilege: every PR/issue write below uses STREAMCTL_PR_TOKEN, so the
|
|
24
|
-
# default token only needs to read the repo for checkout.
|
|
25
11
|
permissions:
|
|
26
|
-
contents:
|
|
12
|
+
contents: write
|
|
13
|
+
issues: write
|
|
14
|
+
pull-requests: write
|
|
27
15
|
|
|
28
16
|
env:
|
|
17
|
+
ALLOW_WORKFLOW_UPDATES: "${ALLOW_WORKFLOW_UPDATES}"
|
|
29
18
|
NODE_VERSION: "${NODE_VERSION}"
|
|
30
19
|
|
|
31
20
|
jobs:
|
|
32
21
|
upgrade:
|
|
33
22
|
runs-on: ubuntu-latest
|
|
34
23
|
steps:
|
|
35
|
-
# A bot identity is REQUIRED for the PR (see the header). To mint an App token
|
|
36
|
-
# in-workflow instead of storing a PAT, uncomment and wire the App credentials,
|
|
37
|
-
# then use `${{ steps.bot.outputs.token }}` in place of STREAMCTL_PR_TOKEN below:
|
|
38
|
-
#
|
|
39
|
-
# - id: bot
|
|
40
|
-
# uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
|
41
|
-
# with:
|
|
42
|
-
# app-id: ${{ secrets.STREAMCTL_APP_ID }}
|
|
43
|
-
# private-key: ${{ secrets.STREAMCTL_APP_PRIVATE_KEY }}
|
|
44
|
-
|
|
45
24
|
- name: Checkout
|
|
46
25
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
26
|
+
with:
|
|
27
|
+
persist-credentials: false
|
|
47
28
|
|
|
48
29
|
- name: Set up pnpm
|
|
49
30
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
|
@@ -59,72 +40,362 @@ jobs:
|
|
|
59
40
|
- name: Install dependencies
|
|
60
41
|
run: pnpm install --frozen-lockfile
|
|
61
42
|
|
|
62
|
-
|
|
63
|
-
|
|
43
|
+
- name: Capture managed-file state
|
|
44
|
+
run: pnpm streamctl status --json > "$RUNNER_TEMP/streamctl-status.json"
|
|
45
|
+
|
|
64
46
|
- id: check
|
|
65
47
|
name: Check for a payload update
|
|
66
48
|
run: |
|
|
67
49
|
set +e
|
|
68
|
-
pnpm streamctl check --fail-on
|
|
69
|
-
|
|
50
|
+
pnpm streamctl check --fail-on any --json > "$RUNNER_TEMP/streamctl-check.json"
|
|
51
|
+
code=$?
|
|
70
52
|
set -e
|
|
71
|
-
|
|
53
|
+
echo "code=$code" >> "$GITHUB_OUTPUT"
|
|
54
|
+
echo "target=$(jq -r '.error.details.updateAvailable.latest // empty' "$RUNNER_TEMP/streamctl-check.json")" >> "$GITHUB_OUTPUT"
|
|
55
|
+
cat "$RUNNER_TEMP/streamctl-check.json"
|
|
56
|
+
|
|
57
|
+
- name: Fail on an unexpected check result
|
|
58
|
+
if: ${{ steps.check.outputs.code != '0' && steps.check.outputs.code != '3' && steps.check.outputs.code != '4' }}
|
|
59
|
+
run: exit 1
|
|
72
60
|
|
|
73
|
-
- name:
|
|
61
|
+
- name: Require an update target
|
|
62
|
+
if: ${{ steps.check.outputs.code == '4' }}
|
|
63
|
+
env:
|
|
64
|
+
TARGET: ${{ steps.check.outputs.target }}
|
|
65
|
+
run: test -n "$TARGET"
|
|
66
|
+
|
|
67
|
+
- name: Ensure workflow labels
|
|
68
|
+
if: ${{ steps.check.outputs.code == '3' || steps.check.outputs.code == '4' }}
|
|
69
|
+
env:
|
|
70
|
+
GH_TOKEN: ${{ github.token }}
|
|
71
|
+
run: |
|
|
72
|
+
gh label create streamctl --color 0E8A16 --description "Managed by streamctl" --force
|
|
73
|
+
gh label create needs-interactive-upgrade --color D93F0B --description "Manual upgrade work required" --force
|
|
74
|
+
|
|
75
|
+
- name: Require the PR token
|
|
76
|
+
if: ${{ steps.check.outputs.code == '4' && env.ALLOW_WORKFLOW_UPDATES == 'true' }}
|
|
77
|
+
env:
|
|
78
|
+
PR_TOKEN: ${{ secrets.${PR_TOKEN_SECRET} }}
|
|
79
|
+
run: test -n "$PR_TOKEN"
|
|
80
|
+
|
|
81
|
+
- name: Open or update the drift issue
|
|
74
82
|
if: ${{ steps.check.outputs.code == '3' }}
|
|
75
83
|
env:
|
|
76
|
-
GH_TOKEN: ${{
|
|
84
|
+
GH_TOKEN: ${{ github.token }}
|
|
77
85
|
run: |
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
title="streamctl: managed files have drifted"
|
|
87
|
+
body="The automated upgrade was skipped because managed files already differ. Run \`pnpm streamctl sync\` locally, commit the result, then rerun this workflow. Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
88
|
+
issue_number=$(gh issue list --state open --label streamctl --search "\"$title\" in:title" --json number --jq '.[0].number // empty')
|
|
89
|
+
if [[ -n "$issue_number" ]]; then
|
|
90
|
+
gh issue edit "$issue_number" --body "$body"
|
|
91
|
+
else
|
|
92
|
+
gh issue create --title "$title" --label streamctl --body "$body"
|
|
93
|
+
fi
|
|
82
94
|
|
|
83
|
-
# Only upgrade when an update is actually available (check exit 4).
|
|
84
95
|
- id: upgrade
|
|
85
96
|
name: Upgrade the payload
|
|
86
97
|
if: ${{ steps.check.outputs.code == '4' }}
|
|
98
|
+
env:
|
|
99
|
+
CI: "false"
|
|
100
|
+
TARGET: ${{ steps.check.outputs.target }}
|
|
87
101
|
run: |
|
|
88
102
|
set +e
|
|
89
|
-
pnpm streamctl upgrade --json > streamctl-upgrade.
|
|
90
|
-
|
|
103
|
+
pnpm streamctl upgrade --to "$TARGET" --yes --json > "$RUNNER_TEMP/streamctl-upgrade.raw"
|
|
104
|
+
code=$?
|
|
91
105
|
set -e
|
|
92
|
-
|
|
106
|
+
tail -n 1 "$RUNNER_TEMP/streamctl-upgrade.raw" > "$RUNNER_TEMP/streamctl-upgrade.json"
|
|
107
|
+
echo "code=$code" >> "$GITHUB_OUTPUT"
|
|
108
|
+
cat "$RUNNER_TEMP/streamctl-upgrade.raw"
|
|
109
|
+
|
|
110
|
+
- name: Fail on an unexpected upgrade result
|
|
111
|
+
if: ${{ steps.upgrade.outputs.code != '' && steps.upgrade.outputs.code != '0' && steps.upgrade.outputs.code != '2' }}
|
|
112
|
+
run: exit 1
|
|
93
113
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# reconciles with `streamctl sync --interactive`.
|
|
97
|
-
- name: Prepare a plan-only bump (interactive sync required)
|
|
114
|
+
- id: conflicts
|
|
115
|
+
name: Classify upgrade conflicts
|
|
98
116
|
if: ${{ steps.upgrade.outputs.code == '2' }}
|
|
99
|
-
run:
|
|
117
|
+
run: |
|
|
118
|
+
jq -r --slurpfile status "$RUNNER_TEMP/streamctl-status.json" '
|
|
119
|
+
(.error.details.conflicted // [])[]
|
|
120
|
+
| select((
|
|
121
|
+
(.kind == "edit" or .kind == "adoption")
|
|
122
|
+
and (.path as $path | any($status[0].data.files[];
|
|
123
|
+
.path == $path and .strategy == "full" and .state == "in-sync"
|
|
124
|
+
))
|
|
125
|
+
) | not)
|
|
126
|
+
| .path
|
|
127
|
+
' "$RUNNER_TEMP/streamctl-upgrade.json" | sort -u > "$RUNNER_TEMP/streamctl-unsafe.txt"
|
|
128
|
+
if [[ ! -s "$RUNNER_TEMP/streamctl-unsafe.txt" ]] && jq -e '
|
|
129
|
+
.error.code == "CONFLICTS_PENDING"
|
|
130
|
+
and .error.details.rolledBack == true
|
|
131
|
+
and (.error.details.conflicted | length > 0)
|
|
132
|
+
' "$RUNNER_TEMP/streamctl-upgrade.json" > /dev/null; then
|
|
133
|
+
echo "safe=true" >> "$GITHUB_OUTPUT"
|
|
134
|
+
else
|
|
135
|
+
echo "safe=false" >> "$GITHUB_OUTPUT"
|
|
136
|
+
fi
|
|
100
137
|
|
|
101
|
-
-
|
|
102
|
-
|
|
138
|
+
- id: force
|
|
139
|
+
name: Apply safe generated replacements
|
|
140
|
+
if: ${{ steps.conflicts.outputs.safe == 'true' }}
|
|
141
|
+
env:
|
|
142
|
+
CI: "false"
|
|
143
|
+
TARGET: ${{ steps.check.outputs.target }}
|
|
144
|
+
run: |
|
|
145
|
+
set +e
|
|
146
|
+
pnpm streamctl upgrade --to "$TARGET" --force --json > "$RUNNER_TEMP/streamctl-force.raw"
|
|
147
|
+
code=$?
|
|
148
|
+
set -e
|
|
149
|
+
tail -n 1 "$RUNNER_TEMP/streamctl-force.raw" > "$RUNNER_TEMP/streamctl-force.json"
|
|
150
|
+
echo "code=$code" >> "$GITHUB_OUTPUT"
|
|
151
|
+
cat "$RUNNER_TEMP/streamctl-force.raw"
|
|
152
|
+
|
|
153
|
+
- name: Fail when safe replacement fails
|
|
154
|
+
if: ${{ steps.conflicts.outputs.safe == 'true' && steps.force.outputs.code != '0' }}
|
|
155
|
+
run: exit 1
|
|
156
|
+
|
|
157
|
+
- id: outcome
|
|
158
|
+
name: Record the upgrade outcome
|
|
159
|
+
if: ${{ steps.check.outputs.code == '4' }}
|
|
160
|
+
env:
|
|
161
|
+
FORCE_CODE: ${{ steps.force.outputs.code }}
|
|
162
|
+
UPGRADE_CODE: ${{ steps.upgrade.outputs.code }}
|
|
163
|
+
run: |
|
|
164
|
+
if [[ "$UPGRADE_CODE" == "0" || "$FORCE_CODE" == "0" ]]; then
|
|
165
|
+
echo "completed=true" >> "$GITHUB_OUTPUT"
|
|
166
|
+
else
|
|
167
|
+
echo "completed=false" >> "$GITHUB_OUTPUT"
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
- id: workflow_changes
|
|
171
|
+
name: Exclude workflow changes by default
|
|
172
|
+
if: ${{ steps.outcome.outputs.completed == 'true' }}
|
|
173
|
+
run: |
|
|
174
|
+
jq -r '
|
|
175
|
+
(.data.plan // .error.details.plan // [])[]
|
|
176
|
+
| .path
|
|
177
|
+
| select(startswith(".github/workflows/"))
|
|
178
|
+
' "$RUNNER_TEMP/streamctl-upgrade.json" | sort -u > "$RUNNER_TEMP/streamctl-workflows.txt"
|
|
179
|
+
echo "blocked=false" >> "$GITHUB_OUTPUT"
|
|
180
|
+
if [[ "$ALLOW_WORKFLOW_UPDATES" == "true" || ! -s "$RUNNER_TEMP/streamctl-workflows.txt" ]]; then
|
|
181
|
+
exit 0
|
|
182
|
+
fi
|
|
183
|
+
while IFS= read -r path; do
|
|
184
|
+
if git cat-file -e "HEAD:$path" 2>/dev/null; then
|
|
185
|
+
git restore --source=HEAD --worktree -- "$path"
|
|
186
|
+
else
|
|
187
|
+
rm -f -- "$path"
|
|
188
|
+
fi
|
|
189
|
+
done < "$RUNNER_TEMP/streamctl-workflows.txt"
|
|
190
|
+
echo "blocked=true" >> "$GITHUB_OUTPUT"
|
|
191
|
+
|
|
192
|
+
- id: manual
|
|
193
|
+
name: Prepare the manual upgrade
|
|
194
|
+
if: ${{ steps.upgrade.outputs.code == '2' && steps.conflicts.outputs.safe == 'false' }}
|
|
195
|
+
env:
|
|
196
|
+
CI: "false"
|
|
197
|
+
TARGET: ${{ steps.check.outputs.target }}
|
|
198
|
+
run: |
|
|
199
|
+
set +e
|
|
200
|
+
pnpm streamctl upgrade --to "$TARGET" --force --json
|
|
201
|
+
code=$?
|
|
202
|
+
set -e
|
|
203
|
+
# Faults and malformed files cannot be forced; fall back to the pin bump.
|
|
204
|
+
if [[ "$code" != "0" ]]; then
|
|
205
|
+
pnpm streamctl upgrade --to "$TARGET" --no-install --json
|
|
206
|
+
pnpm install --lockfile-only --ignore-scripts
|
|
207
|
+
echo "applied=false" >> "$GITHUB_OUTPUT"
|
|
208
|
+
exit 0
|
|
209
|
+
fi
|
|
210
|
+
: > "$RUNNER_TEMP/streamctl-workflows.txt"
|
|
211
|
+
if [[ "$ALLOW_WORKFLOW_UPDATES" != "true" ]]; then
|
|
212
|
+
jq -r '
|
|
213
|
+
(.data.plan // .error.details.plan // [])[]
|
|
214
|
+
| .path
|
|
215
|
+
| select(startswith(".github/workflows/"))
|
|
216
|
+
' "$RUNNER_TEMP/streamctl-upgrade.json" | sort -u > "$RUNNER_TEMP/streamctl-workflows.txt"
|
|
217
|
+
fi
|
|
218
|
+
sort -u "$RUNNER_TEMP/streamctl-unsafe.txt" "$RUNNER_TEMP/streamctl-workflows.txt" > "$RUNNER_TEMP/streamctl-held.txt"
|
|
219
|
+
while IFS= read -r path; do
|
|
220
|
+
if git cat-file -e "HEAD:$path" 2>/dev/null; then
|
|
221
|
+
git restore --source=HEAD --worktree -- "$path"
|
|
222
|
+
else
|
|
223
|
+
rm -f -- "$path"
|
|
224
|
+
fi
|
|
225
|
+
done < "$RUNNER_TEMP/streamctl-held.txt"
|
|
226
|
+
echo "applied=true" >> "$GITHUB_OUTPUT"
|
|
227
|
+
|
|
228
|
+
- name: Validate the completed upgrade
|
|
229
|
+
if: ${{ steps.outcome.outputs.completed == 'true' && steps.workflow_changes.outputs.blocked == 'false' }}
|
|
230
|
+
run: |
|
|
231
|
+
pnpm streamctl check
|
|
232
|
+
pnpm run --if-present lint
|
|
233
|
+
pnpm run --if-present typecheck
|
|
234
|
+
pnpm run --if-present test
|
|
235
|
+
pnpm run --if-present build
|
|
236
|
+
|
|
237
|
+
- name: Validate the partial upgrade
|
|
238
|
+
if: ${{ steps.workflow_changes.outputs.blocked == 'true' }}
|
|
239
|
+
run: |
|
|
240
|
+
set +e
|
|
241
|
+
pnpm streamctl check --fail-on drift --json > "$RUNNER_TEMP/streamctl-partial-check.raw"
|
|
242
|
+
code=$?
|
|
243
|
+
set -e
|
|
244
|
+
tail -n 1 "$RUNNER_TEMP/streamctl-partial-check.raw" > "$RUNNER_TEMP/streamctl-partial-check.json"
|
|
245
|
+
test "$code" -eq 3
|
|
246
|
+
jq -Rsc 'split("\n") | map(select(length > 0)) | sort' \
|
|
247
|
+
"$RUNNER_TEMP/streamctl-workflows.txt" > "$RUNNER_TEMP/streamctl-workflows.json"
|
|
248
|
+
jq -e --slurpfile expected "$RUNNER_TEMP/streamctl-workflows.json" '
|
|
249
|
+
.error.code == "DRIFT_DETECTED"
|
|
250
|
+
and (.error.details.structuralFaults | length == 0)
|
|
251
|
+
and (.error.details.versionSkew | length == 0)
|
|
252
|
+
and (([.error.details.drift[].path] | sort) == $expected[0])
|
|
253
|
+
' "$RUNNER_TEMP/streamctl-partial-check.json" > /dev/null
|
|
254
|
+
pnpm run --if-present lint
|
|
255
|
+
pnpm run --if-present typecheck
|
|
256
|
+
pnpm run --if-present test
|
|
257
|
+
pnpm run --if-present build
|
|
258
|
+
|
|
259
|
+
- name: Prepare the completed-upgrade description
|
|
260
|
+
if: ${{ steps.outcome.outputs.completed == 'true' && steps.workflow_changes.outputs.blocked == 'false' }}
|
|
261
|
+
env:
|
|
262
|
+
TARGET: ${{ steps.check.outputs.target }}
|
|
263
|
+
run: |
|
|
264
|
+
from=$(jq -r '.data.payload.pinned' "$RUNNER_TEMP/streamctl-status.json")
|
|
265
|
+
{
|
|
266
|
+
echo "## Summary"
|
|
267
|
+
echo
|
|
268
|
+
printf -- '- Updated the streamctl payload from `%s` to `%s`.\n' "$from" "$TARGET"
|
|
269
|
+
echo "- Reconciled all managed files."
|
|
270
|
+
echo "- Updated dependencies and the lockfile."
|
|
271
|
+
echo
|
|
272
|
+
echo "## Validation"
|
|
273
|
+
echo
|
|
274
|
+
echo '- `pnpm streamctl check`'
|
|
275
|
+
echo "- Available lint, typecheck, test, and build scripts"
|
|
276
|
+
} > "$RUNNER_TEMP/streamctl-pr.md"
|
|
277
|
+
|
|
278
|
+
- name: Open the completed upgrade PR
|
|
279
|
+
if: ${{ steps.outcome.outputs.completed == 'true' && steps.workflow_changes.outputs.blocked == 'false' }}
|
|
103
280
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
|
104
281
|
with:
|
|
105
|
-
token: ${{ secrets.
|
|
106
|
-
branch: chore/streamctl-upgrade
|
|
107
|
-
commit-message: "chore: upgrade
|
|
108
|
-
title: "chore: upgrade
|
|
109
|
-
body:
|
|
110
|
-
Automated payload upgrade: the managed files were reconciled cleanly by
|
|
111
|
-
`streamctl upgrade`. Review the diff; `streamctl-upgrade.json` (in the run
|
|
112
|
-
logs) has the full sync summary and dependency bumps.
|
|
282
|
+
token: ${{ env.ALLOW_WORKFLOW_UPDATES == 'true' && secrets.${PR_TOKEN_SECRET} || github.token }}
|
|
283
|
+
branch: chore/streamctl-upgrade-${{ steps.check.outputs.target }}
|
|
284
|
+
commit-message: "chore: upgrade streamctl payload to ${{ steps.check.outputs.target }}"
|
|
285
|
+
title: "chore: upgrade streamctl payload to ${{ steps.check.outputs.target }}"
|
|
286
|
+
body-path: ${{ runner.temp }}/streamctl-pr.md
|
|
113
287
|
labels: streamctl
|
|
114
288
|
|
|
115
|
-
- name:
|
|
116
|
-
if: ${{ steps.
|
|
289
|
+
- name: Prepare the resolve instructions
|
|
290
|
+
if: ${{ steps.workflow_changes.outputs.blocked == 'true' || steps.manual.outputs.applied != '' }}
|
|
291
|
+
env:
|
|
292
|
+
TARGET: ${{ steps.check.outputs.target }}
|
|
293
|
+
run: |
|
|
294
|
+
branch="chore/streamctl-upgrade-$TARGET"
|
|
295
|
+
{
|
|
296
|
+
echo "## Resolve"
|
|
297
|
+
echo
|
|
298
|
+
echo '```sh'
|
|
299
|
+
printf 'git fetch origin %s\n' "$branch"
|
|
300
|
+
printf 'git switch --create %s --track origin/%s\n' "$branch" "$branch"
|
|
301
|
+
echo 'pnpm install --frozen-lockfile'
|
|
302
|
+
echo 'pnpm streamctl sync --interactive'
|
|
303
|
+
echo 'pnpm streamctl check'
|
|
304
|
+
echo 'pnpm run --if-present lint'
|
|
305
|
+
echo 'pnpm run --if-present typecheck'
|
|
306
|
+
echo 'pnpm run --if-present test'
|
|
307
|
+
echo 'pnpm run --if-present build'
|
|
308
|
+
echo 'git add .'
|
|
309
|
+
echo 'git commit -m "chore: finish streamctl upgrade"'
|
|
310
|
+
echo 'git push'
|
|
311
|
+
echo '```'
|
|
312
|
+
} > "$RUNNER_TEMP/streamctl-resolve.md"
|
|
313
|
+
|
|
314
|
+
- name: Prepare the workflow-update description
|
|
315
|
+
if: ${{ steps.workflow_changes.outputs.blocked == 'true' }}
|
|
316
|
+
env:
|
|
317
|
+
TARGET: ${{ steps.check.outputs.target }}
|
|
318
|
+
run: |
|
|
319
|
+
from=$(jq -r '.data.payload.pinned' "$RUNNER_TEMP/streamctl-status.json")
|
|
320
|
+
{
|
|
321
|
+
echo "## Status"
|
|
322
|
+
echo
|
|
323
|
+
echo "This update omits GitHub Actions workflow changes. Do not merge this pull request yet."
|
|
324
|
+
echo
|
|
325
|
+
printf -- '- Updated the streamctl payload from `%s` to `%s`.\n' "$from" "$TARGET"
|
|
326
|
+
echo "- Applied every other safe managed-file change."
|
|
327
|
+
echo "- Updated dependencies and the lockfile."
|
|
328
|
+
echo
|
|
329
|
+
echo "## Omitted workflow files"
|
|
330
|
+
echo
|
|
331
|
+
sed 's|^|- `|; s|$|`|' "$RUNNER_TEMP/streamctl-workflows.txt"
|
|
332
|
+
echo
|
|
333
|
+
cat "$RUNNER_TEMP/streamctl-resolve.md"
|
|
334
|
+
} > "$RUNNER_TEMP/streamctl-workflow-pr.md"
|
|
335
|
+
|
|
336
|
+
- name: Open the workflow-update PR
|
|
337
|
+
if: ${{ steps.workflow_changes.outputs.blocked == 'true' }}
|
|
338
|
+
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
|
339
|
+
with:
|
|
340
|
+
token: ${{ env.ALLOW_WORKFLOW_UPDATES == 'true' && secrets.${PR_TOKEN_SECRET} || github.token }}
|
|
341
|
+
branch: chore/streamctl-upgrade-${{ steps.check.outputs.target }}
|
|
342
|
+
commit-message: "chore: prepare streamctl payload ${{ steps.check.outputs.target }}"
|
|
343
|
+
title: "chore: upgrade streamctl payload to ${{ steps.check.outputs.target }}"
|
|
344
|
+
body-path: ${{ runner.temp }}/streamctl-workflow-pr.md
|
|
345
|
+
draft: true
|
|
346
|
+
labels: |
|
|
347
|
+
streamctl
|
|
348
|
+
needs-interactive-upgrade
|
|
349
|
+
|
|
350
|
+
- name: Prepare the manual-upgrade description
|
|
351
|
+
if: ${{ steps.upgrade.outputs.code == '2' && steps.conflicts.outputs.safe == 'false' }}
|
|
352
|
+
env:
|
|
353
|
+
APPLIED: ${{ steps.manual.outputs.applied }}
|
|
354
|
+
TARGET: ${{ steps.check.outputs.target }}
|
|
355
|
+
run: |
|
|
356
|
+
from=$(jq -r '.data.payload.pinned' "$RUNNER_TEMP/streamctl-status.json")
|
|
357
|
+
{
|
|
358
|
+
echo "## Status"
|
|
359
|
+
echo
|
|
360
|
+
echo "This update needs manual conflict resolution. Do not merge this pull request yet."
|
|
361
|
+
echo
|
|
362
|
+
printf -- '- Updated the streamctl payload from `%s` to `%s`.\n' "$from" "$TARGET"
|
|
363
|
+
if [[ "$APPLIED" == "true" ]]; then
|
|
364
|
+
echo "- Applied every safe managed-file change."
|
|
365
|
+
echo "- Left conflicting managed files unchanged."
|
|
366
|
+
else
|
|
367
|
+
echo "- Did not update managed files because \`streamctl upgrade --force\` failed. See the workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
368
|
+
fi
|
|
369
|
+
echo "- Updated dependencies and the lockfile."
|
|
370
|
+
echo
|
|
371
|
+
echo "## Conflicts"
|
|
372
|
+
echo
|
|
373
|
+
jq -r --arg applied "$APPLIED" --rawfile unsafe "$RUNNER_TEMP/streamctl-unsafe.txt" '
|
|
374
|
+
($unsafe | split("\n")) as $held
|
|
375
|
+
| .error.details.conflicted[]
|
|
376
|
+
| select($applied != "true" or (.path | IN($held[])))
|
|
377
|
+
| "- `\(.path)` (`\(.kind)`): \(.reason)"
|
|
378
|
+
' "$RUNNER_TEMP/streamctl-upgrade.json"
|
|
379
|
+
echo
|
|
380
|
+
if [[ -s "$RUNNER_TEMP/streamctl-workflows.txt" ]]; then
|
|
381
|
+
echo "## Omitted workflow files"
|
|
382
|
+
echo
|
|
383
|
+
sed 's|^|- `|; s|$|`|' "$RUNNER_TEMP/streamctl-workflows.txt"
|
|
384
|
+
echo
|
|
385
|
+
fi
|
|
386
|
+
cat "$RUNNER_TEMP/streamctl-resolve.md"
|
|
387
|
+
} > "$RUNNER_TEMP/streamctl-conflict-pr.md"
|
|
388
|
+
|
|
389
|
+
- name: Open the manual upgrade PR
|
|
390
|
+
if: ${{ steps.upgrade.outputs.code == '2' && steps.conflicts.outputs.safe == 'false' }}
|
|
117
391
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
|
118
392
|
with:
|
|
119
|
-
token: ${{ secrets.
|
|
120
|
-
branch: chore/streamctl-upgrade
|
|
121
|
-
commit-message: "chore:
|
|
122
|
-
title: "chore: streamctl payload
|
|
123
|
-
body:
|
|
124
|
-
|
|
125
|
-
only the payload pin + devDependency were bumped here. Finish the upgrade by
|
|
126
|
-
hand: check out this branch, run `pnpm streamctl sync --interactive`, resolve
|
|
127
|
-
each conflict, and push.
|
|
393
|
+
token: ${{ env.ALLOW_WORKFLOW_UPDATES == 'true' && secrets.${PR_TOKEN_SECRET} || github.token }}
|
|
394
|
+
branch: chore/streamctl-upgrade-${{ steps.check.outputs.target }}
|
|
395
|
+
commit-message: "chore: prepare streamctl payload ${{ steps.check.outputs.target }}"
|
|
396
|
+
title: "chore: upgrade streamctl payload to ${{ steps.check.outputs.target }}"
|
|
397
|
+
body-path: ${{ runner.temp }}/streamctl-conflict-pr.md
|
|
398
|
+
draft: true
|
|
128
399
|
labels: |
|
|
129
400
|
streamctl
|
|
130
401
|
needs-interactive-upgrade
|
package/presets/base/gitignore
CHANGED
|
@@ -11,9 +11,7 @@ pglite-debug.log
|
|
|
11
11
|
prisma/*.db
|
|
12
12
|
prisma/migrations/dev
|
|
13
13
|
.DS_Store
|
|
14
|
-
# streamctl
|
|
15
|
-
local.*
|
|
16
|
-
local.*/
|
|
17
|
-
|
|
18
|
-
# filename everywhere would silently untrack it. Force-allow it here.
|
|
19
|
-
!CLAUDE.md
|
|
14
|
+
# streamctl scratch. Root-anchored so `src/path/local.ts` stays tracked.
|
|
15
|
+
/local.*
|
|
16
|
+
/local.*/
|
|
17
|
+
prisma/local.*
|
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
# Managed by streamctl
|
|
1
|
+
# Managed by streamctl. pnpm's own settings file.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# it gates fresh resolution only, so `--frozen-lockfile` installs are unaffected.
|
|
7
|
-
# `@sidebase/*` is exempt: the first-party scope (this payload + the streamctl CLI),
|
|
8
|
-
# published by the org itself, so a payload release reaches the fleet the same day.
|
|
3
|
+
# Cooldown: a version must be ${MIN_RELEASE_AGE} minutes old before pnpm resolves it,
|
|
4
|
+
# giving a compromised release time to be yanked. Needs pnpm >= 10.16. Gates fresh
|
|
5
|
+
# resolution only, so `--frozen-lockfile` is unaffected. `@sidebase/*` is exempt.
|
|
9
6
|
#
|
|
10
|
-
# `packages: []` is
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# A real monorepo owns this key itself, so opt the file out with
|
|
14
|
-
# `files: { "pnpm-workspace.yaml": "off" }` in .streamctl/config.ts.
|
|
7
|
+
# `packages: []` is required: pnpm otherwise defaults it to `**`, promoting every nested
|
|
8
|
+
# package.json to a workspace project and breaking `--frozen-lockfile`. A monorepo should
|
|
9
|
+
# opt out with `files: { "pnpm-workspace.yaml": "off" }`.
|
|
15
10
|
#
|
|
16
|
-
# `onlyBuiltDependencies`
|
|
17
|
-
#
|
|
18
|
-
# needs; add extras via `pnpm: { onlyBuiltDependencies: [...] }` rather than running
|
|
19
|
-
# `pnpm approve-builds` (which writes here and would be reverted on the next sync).
|
|
11
|
+
# `onlyBuiltDependencies` allowlists postinstall scripts. Add extras through the `pnpm`
|
|
12
|
+
# knob, not `pnpm approve-builds`, which writes here and is reverted on the next sync.
|
|
20
13
|
packages: []
|
|
21
14
|
minimumReleaseAge: ${MIN_RELEASE_AGE}
|
|
22
15
|
minimumReleaseAgeExclude:
|
package/presets/base/preset.json
CHANGED
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
{ "path": ".vscode/settings.json", "strategy": "merge", "source": "base/vscode/settings.json", "render": "editor", "projectFields": ["editor.fontSize", "editor.rulers", "editor.formatOnSave", "files.exclude", "search.exclude", "files.watcherExclude"] },
|
|
11
11
|
{ "path": ".vscode/extensions.json", "strategy": "merge", "source": "base/vscode/extensions.json", "projectFields": ["unwantedRecommendations"] },
|
|
12
12
|
{ "path": "AGENTS.md", "strategy": "full", "source": "base/AGENTS.md", "adoption": "expected" },
|
|
13
|
-
{ "path": "CLAUDE.md", "strategy": "scaffold", "source": "base/CLAUDE.md", "adoption": "expected" },
|
|
14
13
|
{ "path": ".github/workflows/streamctl-upgrade.yml", "strategy": "full", "source": "base/github/workflows/streamctl-upgrade.yml", "render": "automation-upgrade", "enabledBy": "automation.upgradePr", "adoption": "unexpected" }
|
|
15
14
|
],
|
|
16
15
|
"renders": {
|
|
17
16
|
"automation-upgrade": {
|
|
18
17
|
"placeholders": {
|
|
19
|
-
"
|
|
18
|
+
"ALLOW_WORKFLOW_UPDATES": { "configPath": "automation.allowWorkflowUpdates", "default": "false", "pattern": "^(?:true|false)$" },
|
|
19
|
+
"NODE_VERSION": { "configPath": "versions.node", "default": "24.13.0", "pattern": "^[\\w.+-]+$" },
|
|
20
|
+
"PR_TOKEN_SECRET": { "configPath": "automation.prTokenSecret", "default": "STREAMCTL_PR_TOKEN", "pattern": "^(?![Gg][Ii][Tt][Hh][Uu][Bb]_)[A-Za-z_][A-Za-z0-9_]*$" }
|
|
20
21
|
}
|
|
21
22
|
},
|
|
22
23
|
"editor": {
|
|
@@ -34,6 +35,8 @@
|
|
|
34
35
|
}
|
|
35
36
|
},
|
|
36
37
|
"configKeys": {
|
|
38
|
+
"automation.allowWorkflowUpdates": "boolean",
|
|
39
|
+
"automation.prTokenSecret": "string",
|
|
37
40
|
"automation.upgradePr": "boolean",
|
|
38
41
|
"editor.i18nSourceLanguage": "string",
|
|
39
42
|
"security.minimumReleaseAge": "string",
|