@sidebase/base-config 0.2.1 → 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 +48 -19
- package/dist/config.d.mts +4 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.mjs +3 -1
- package/package.json +1 -1
- package/presets/base/github/workflows/streamctl-upgrade.yml +341 -60
- package/presets/base/gitignore +0 -3
- package/presets/base/preset.json +5 -2
- package/presets/base/CLAUDE.md +0 -3
package/README.md
CHANGED
|
@@ -590,15 +590,18 @@ sync. A repo that genuinely needs a different layout should opt the file out
|
|
|
590
590
|
|
|
591
591
|
The base preset ships `.github/workflows/streamctl-upgrade.yml`, a scheduled workflow
|
|
592
592
|
that opens a PR when a payload update is available. It is an `enabledBy`-gated managed
|
|
593
|
-
file, off by default
|
|
594
|
-
required token exists.
|
|
593
|
+
file, off by default. It only lands once a repo opts in.
|
|
595
594
|
|
|
596
595
|
Enable it in `streamctl.config.ts`, then `streamctl sync`:
|
|
597
596
|
|
|
598
597
|
```ts
|
|
599
598
|
export default {
|
|
600
599
|
// ...
|
|
601
|
-
automation: {
|
|
600
|
+
automation: {
|
|
601
|
+
upgradePr: true,
|
|
602
|
+
// allowWorkflowUpdates: true,
|
|
603
|
+
// prTokenSecret: "STREAMCTL_PR_TOKEN",
|
|
604
|
+
},
|
|
602
605
|
// optional: version pins, default to the payload's baseline. `node` feeds
|
|
603
606
|
// ci.yml + the Dockerfile; `pnpm` feeds the Dockerfile only (CI pins pnpm
|
|
604
607
|
// via package.json#packageManager):
|
|
@@ -606,24 +609,50 @@ export default {
|
|
|
606
609
|
};
|
|
607
610
|
```
|
|
608
611
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
| ------ | ------- |
|
|
613
|
-
| `STREAMCTL_PR_TOKEN` | A GitHub App installation token or machine-account PAT used to open the PR. **Not** the default `GITHUB_TOKEN`: a PR opened with `GITHUB_TOKEN` does not trigger `pull_request` workflows, so the repo's own `check` gate would never run on the bot PR. |
|
|
614
|
-
|
|
615
|
-
That is the only one, because the payload is public npm, so the install step needs no registry
|
|
616
|
-
token. A repo whose *other* dependencies are private must opt this full-owned workflow
|
|
617
|
-
out and wire its own auth.
|
|
612
|
+
By default, the workflow uses `GITHUB_TOKEN` and omits changes under
|
|
613
|
+
`.github/workflows/**`. If an update contains workflow changes, it opens a draft PR
|
|
614
|
+
with every other safe change and lists the omitted files and completion commands.
|
|
618
615
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
`
|
|
616
|
+
Set `automation.allowWorkflowUpdates` to `true` to include workflow changes. Add a
|
|
617
|
+
`STREAMCTL_PR_TOKEN` repository secret with `Contents`, `Pull requests`, and
|
|
618
|
+
`Workflows` write access. Change `automation.prTokenSecret` when the secret has another
|
|
619
|
+
name. A fine-grained PAT or GitHub App token works. The built-in `GITHUB_TOKEN` cannot
|
|
620
|
+
receive `Workflows` permission.
|
|
624
621
|
|
|
625
|
-
|
|
626
|
-
|
|
622
|
+
| Knob | Default | Effect |
|
|
623
|
+
| ---- | ------- | ------ |
|
|
624
|
+
| `automation.upgradePr` | `false` | Sync the scheduled upgrade workflow |
|
|
625
|
+
| `automation.allowWorkflowUpdates` | `false` | Include `.github/workflows/**` changes in upgrade PRs |
|
|
626
|
+
| `automation.prTokenSecret` | `"STREAMCTL_PR_TOKEN"` | Secret used when workflow updates are enabled |
|
|
627
|
+
|
|
628
|
+
The upgrade job runs `streamctl check` and every available lint, typecheck, test, and
|
|
629
|
+
build script before opening a ready PR. PRs created with `GITHUB_TOKEN` do not trigger
|
|
630
|
+
their own `pull_request` workflows, so these inline checks are required. The external
|
|
631
|
+
token allows the new PR to trigger them normally.
|
|
632
|
+
|
|
633
|
+
The workflow handles CLI results as follows:
|
|
634
|
+
|
|
635
|
+
- `check` exit 0: stop without a PR.
|
|
636
|
+
- `check` exit 3: open or update one drift issue.
|
|
637
|
+
- `check` exit 4: attempt the upgrade.
|
|
638
|
+
- `upgrade` exit 0: validate and open a ready PR, or a draft when workflow files were omitted.
|
|
639
|
+
- `upgrade` exit 2: inspect conflicts using the pre-upgrade status.
|
|
640
|
+
- Any other exit: fail the workflow.
|
|
641
|
+
|
|
642
|
+
A conflict is safe to apply when the same path was previously `full` managed and
|
|
643
|
+
`in-sync`. The workflow retries those generated replacements with `--force`. Any new,
|
|
644
|
+
modified, malformed, or structurally conflicting path produces a draft PR instead.
|
|
645
|
+
Workflow changes also produce a draft unless explicitly allowed. Drafts contain every
|
|
646
|
+
safe change, keep conflicting and omitted workflow files at their current content, list
|
|
647
|
+
what remains, and give the commands needed to finish the upgrade. When a malformed or
|
|
648
|
+
structurally invalid file blocks `--force`, the draft only updates the payload version
|
|
649
|
+
and lockfile.
|
|
650
|
+
|
|
651
|
+
Diagnostic JSON stays under the runner's temporary directory. Upgrade branches include
|
|
652
|
+
the target version, so a later release does not overwrite an older open PR.
|
|
653
|
+
|
|
654
|
+
The payload is public npm. A repo with other private dependencies must opt this workflow
|
|
655
|
+
out and provide its own install authentication.
|
|
627
656
|
|
|
628
657
|
### Action pinning policy
|
|
629
658
|
|
package/dist/config.d.mts
CHANGED
|
@@ -99,6 +99,10 @@ interface NuxtBaseDockerConfig {
|
|
|
99
99
|
interface NuxtBaseAutomationConfig {
|
|
100
100
|
/** Sync the weekly streamctl upgrade-PR workflow (default: off, no workflow lands). */
|
|
101
101
|
upgradePr?: boolean;
|
|
102
|
+
/** Let upgrade PRs change GitHub Actions workflows (default: false). */
|
|
103
|
+
allowWorkflowUpdates?: boolean;
|
|
104
|
+
/** Repository secret used when workflow updates are allowed. */
|
|
105
|
+
prTokenSecret?: string;
|
|
102
106
|
}
|
|
103
107
|
/**
|
|
104
108
|
* Supply-chain knobs (`config.security.*`): the pnpm install cooldown rendered into
|
package/dist/config.d.ts
CHANGED
|
@@ -99,6 +99,10 @@ interface NuxtBaseDockerConfig {
|
|
|
99
99
|
interface NuxtBaseAutomationConfig {
|
|
100
100
|
/** Sync the weekly streamctl upgrade-PR workflow (default: off, no workflow lands). */
|
|
101
101
|
upgradePr?: boolean;
|
|
102
|
+
/** Let upgrade PRs change GitHub Actions workflows (default: false). */
|
|
103
|
+
allowWorkflowUpdates?: boolean;
|
|
104
|
+
/** Repository secret used when workflow updates are allowed. */
|
|
105
|
+
prTokenSecret?: string;
|
|
102
106
|
}
|
|
103
107
|
/**
|
|
104
108
|
* Supply-chain knobs (`config.security.*`): the pnpm install cooldown rendered into
|
package/dist/config.mjs
CHANGED
|
@@ -24,7 +24,9 @@ const NUXT_BASE_DOCKER_KEYS = knobKeys({
|
|
|
24
24
|
startCommand: 0
|
|
25
25
|
});
|
|
26
26
|
const NUXT_BASE_AUTOMATION_KEYS = knobKeys({
|
|
27
|
-
upgradePr: 0
|
|
27
|
+
upgradePr: 0,
|
|
28
|
+
allowWorkflowUpdates: 0,
|
|
29
|
+
prTokenSecret: 0
|
|
28
30
|
});
|
|
29
31
|
const NUXT_BASE_SECURITY_KEYS = knobKeys({
|
|
30
32
|
minimumReleaseAge: 0
|
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",
|
|
@@ -1,41 +1,30 @@
|
|
|
1
|
-
# Managed by streamctl.
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
# Needs `secrets.STREAMCTL_PR_TOKEN`: a PR opened with `GITHUB_TOKEN` does not trigger
|
|
5
|
-
# `pull_request` workflows, so the repo's own `check` gate would never run on it.
|
|
6
|
-
#
|
|
7
|
-
# A repo with private dependencies must opt this file out and wire its own auth:
|
|
8
|
-
# `files: { ".github/workflows/streamctl-upgrade.yml": "off" }`.
|
|
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.
|
|
9
4
|
name: streamctl upgrade
|
|
10
5
|
|
|
11
6
|
on:
|
|
12
7
|
schedule:
|
|
13
|
-
- cron: "0 6 * * 1"
|
|
8
|
+
- cron: "0 6 * * 1"
|
|
14
9
|
workflow_dispatch:
|
|
15
10
|
|
|
16
|
-
# Every PR and issue write below uses STREAMCTL_PR_TOKEN, so the default token
|
|
17
|
-
# only needs to read the repo for checkout.
|
|
18
11
|
permissions:
|
|
19
|
-
contents:
|
|
12
|
+
contents: write
|
|
13
|
+
issues: write
|
|
14
|
+
pull-requests: write
|
|
20
15
|
|
|
21
16
|
env:
|
|
17
|
+
ALLOW_WORKFLOW_UPDATES: "${ALLOW_WORKFLOW_UPDATES}"
|
|
22
18
|
NODE_VERSION: "${NODE_VERSION}"
|
|
23
19
|
|
|
24
20
|
jobs:
|
|
25
21
|
upgrade:
|
|
26
22
|
runs-on: ubuntu-latest
|
|
27
23
|
steps:
|
|
28
|
-
# To mint an App token in-workflow instead of storing a PAT, uncomment this
|
|
29
|
-
# and use `${{ steps.bot.outputs.token }}` in place of STREAMCTL_PR_TOKEN:
|
|
30
|
-
#
|
|
31
|
-
# - id: bot
|
|
32
|
-
# uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
|
33
|
-
# with:
|
|
34
|
-
# app-id: ${{ secrets.STREAMCTL_APP_ID }}
|
|
35
|
-
# private-key: ${{ secrets.STREAMCTL_APP_PRIVATE_KEY }}
|
|
36
|
-
|
|
37
24
|
- name: Checkout
|
|
38
25
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
26
|
+
with:
|
|
27
|
+
persist-credentials: false
|
|
39
28
|
|
|
40
29
|
- name: Set up pnpm
|
|
41
30
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
|
@@ -51,70 +40,362 @@ jobs:
|
|
|
51
40
|
- name: Install dependencies
|
|
52
41
|
run: pnpm install --frozen-lockfile
|
|
53
42
|
|
|
54
|
-
|
|
43
|
+
- name: Capture managed-file state
|
|
44
|
+
run: pnpm streamctl status --json > "$RUNNER_TEMP/streamctl-status.json"
|
|
45
|
+
|
|
55
46
|
- id: check
|
|
56
47
|
name: Check for a payload update
|
|
57
48
|
run: |
|
|
58
49
|
set +e
|
|
59
|
-
pnpm streamctl check --fail-on
|
|
60
|
-
|
|
50
|
+
pnpm streamctl check --fail-on any --json > "$RUNNER_TEMP/streamctl-check.json"
|
|
51
|
+
code=$?
|
|
61
52
|
set -e
|
|
62
|
-
|
|
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
|
|
63
60
|
|
|
64
|
-
- 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
|
|
65
82
|
if: ${{ steps.check.outputs.code == '3' }}
|
|
66
83
|
env:
|
|
67
|
-
GH_TOKEN: ${{
|
|
84
|
+
GH_TOKEN: ${{ github.token }}
|
|
68
85
|
run: |
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
73
94
|
|
|
74
|
-
# Only upgrade when an update is actually available (check exit 4).
|
|
75
95
|
- id: upgrade
|
|
76
96
|
name: Upgrade the payload
|
|
77
97
|
if: ${{ steps.check.outputs.code == '4' }}
|
|
98
|
+
env:
|
|
99
|
+
CI: "false"
|
|
100
|
+
TARGET: ${{ steps.check.outputs.target }}
|
|
78
101
|
run: |
|
|
79
102
|
set +e
|
|
80
|
-
pnpm streamctl upgrade --json > streamctl-upgrade.
|
|
81
|
-
|
|
103
|
+
pnpm streamctl upgrade --to "$TARGET" --yes --json > "$RUNNER_TEMP/streamctl-upgrade.raw"
|
|
104
|
+
code=$?
|
|
82
105
|
set -e
|
|
83
|
-
|
|
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
|
|
84
113
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- name: Prepare a plan-only bump (interactive sync required)
|
|
114
|
+
- id: conflicts
|
|
115
|
+
name: Classify upgrade conflicts
|
|
88
116
|
if: ${{ steps.upgrade.outputs.code == '2' }}
|
|
89
|
-
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
|
|
90
137
|
|
|
91
|
-
-
|
|
92
|
-
|
|
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' }}
|
|
93
280
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
|
94
281
|
with:
|
|
95
|
-
token: ${{ secrets.
|
|
96
|
-
branch: chore/streamctl-upgrade
|
|
97
|
-
commit-message: "chore: upgrade
|
|
98
|
-
title: "chore: upgrade
|
|
99
|
-
body:
|
|
100
|
-
Automated payload upgrade: the managed files were reconciled cleanly by
|
|
101
|
-
`streamctl upgrade`. Review the diff; `streamctl-upgrade.json` (in the run
|
|
102
|
-
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
|
|
103
287
|
labels: streamctl
|
|
104
288
|
|
|
105
|
-
- name:
|
|
106
|
-
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' }}
|
|
107
391
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
|
108
392
|
with:
|
|
109
|
-
token: ${{ secrets.
|
|
110
|
-
branch: chore/streamctl-upgrade
|
|
111
|
-
commit-message: "chore:
|
|
112
|
-
title: "chore: streamctl payload
|
|
113
|
-
body:
|
|
114
|
-
|
|
115
|
-
only the payload pin + devDependency were bumped here. Finish the upgrade by
|
|
116
|
-
hand: check out this branch, run `pnpm streamctl sync --interactive`, resolve
|
|
117
|
-
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
|
|
118
399
|
labels: |
|
|
119
400
|
streamctl
|
|
120
401
|
needs-interactive-upgrade
|
package/presets/base/gitignore
CHANGED
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",
|
package/presets/base/CLAUDE.md
DELETED