@sidebase/base-config 0.1.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/LICENSE +21 -0
- package/README.md +554 -0
- package/dist/config.d.mts +189 -0
- package/dist/config.d.ts +189 -0
- package/dist/config.mjs +46 -0
- package/dist/eslint/index.d.mts +55 -0
- package/dist/eslint/index.d.ts +55 -0
- package/dist/eslint/index.mjs +278 -0
- package/dist/prisma/index.d.mts +74 -0
- package/dist/prisma/index.d.ts +74 -0
- package/dist/prisma/index.mjs +61 -0
- package/dist/shared/base-config.CuUhyvQo.d.mts +47 -0
- package/dist/shared/base-config.CuUhyvQo.d.ts +47 -0
- package/docs/migration.md +764 -0
- package/package.json +94 -0
- package/presets/base/AGENTS.md +31 -0
- package/presets/base/CLAUDE.md +3 -0
- package/presets/base/dockerignore +18 -0
- package/presets/base/editorconfig +12 -0
- package/presets/base/github/workflows/streamctl-upgrade.yml +130 -0
- package/presets/base/gitignore +19 -0
- package/presets/base/oxlintrc.json +31 -0
- package/presets/base/pnpm-workspace.yaml +27 -0
- package/presets/base/preset.json +38 -0
- package/presets/base/templates/pnpm/only-built-dependency.yml +1 -0
- package/presets/base/tsconfig.json +3 -0
- package/presets/base/vscode/extensions.json +8 -0
- package/presets/base/vscode/settings.json +43 -0
- package/presets/config.template.ts +18 -0
- package/presets/manifest.json +17 -0
- package/presets/nuxt-app/Dockerfile +63 -0
- package/presets/nuxt-app/eslint.config.ts +5 -0
- package/presets/nuxt-app/github/workflows/ci.yml +66 -0
- package/presets/nuxt-app/github/workflows/pr-preview-cleanup.yml +41 -0
- package/presets/nuxt-app/preset.json +67 -0
- package/presets/nuxt-app/prisma.config.ts +6 -0
- package/presets/nuxt-app/templates/ci/e2e-job.yml +39 -0
- package/presets/nuxt-app/templates/ci/test-job.yml +13 -0
- package/presets/nuxt-app/tsconfig.json +6 -0
- package/tsconfig.base.json +19 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
# Migrating from `@sidestream-tech/nuxt-config` to `@sidebase/base-config`
|
|
2
|
+
|
|
3
|
+
An ordered runbook for moving one repository onto the renamed payload.
|
|
4
|
+
|
|
5
|
+
The rename is not a version bump. `@sidebase/base-config` is a new npm package with its
|
|
6
|
+
own name, scope, and version line, so no automated upgrade path crosses it. You do the
|
|
7
|
+
steps below by hand, once, per repository.
|
|
8
|
+
|
|
9
|
+
**Read "Why the order matters" below before you start.** Step 2 must happen before step 4. Getting
|
|
10
|
+
that wrong does not produce an error. It produces a repository that reports "all clean"
|
|
11
|
+
and exits 0 while receiving no dependency updates at all.
|
|
12
|
+
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
This is written for a real migration. At the time of writing, the known adopters are all
|
|
16
|
+
on unmerged branches, and none of them can install at all: every one pins both the payload
|
|
17
|
+
and the CLI, through `pnpm.overrides` or a direct `file:` dependency, into a `/tmp`
|
|
18
|
+
directory that no longer exists. For a branch in that state, re-running
|
|
19
|
+
`streamctl init` against the published package on a fresh branch is usually less work than
|
|
20
|
+
migrating it. Use this runbook when you have a working checkout worth preserving.
|
|
21
|
+
|
|
22
|
+
## CLI prerequisite
|
|
23
|
+
|
|
24
|
+
Your `@sidebase/streamctl` must satisfy two separate requirements. They landed in that
|
|
25
|
+
order, so the second one is the binding constraint:
|
|
26
|
+
|
|
27
|
+
1. **The undeclared-profile guard.** Makes a profile name that the payload does not declare
|
|
28
|
+
a hard error, instead of an empty baseline that reconciles nothing and fails no command.
|
|
29
|
+
This is what protects step 4.
|
|
30
|
+
2. **The `scripts.*` reconcile widening** (`RECONCILABLE_KEY_PATTERN`, streamctl commit
|
|
31
|
+
`090c674`). This payload's version baseline includes `scripts.lint`, and a CLI without
|
|
32
|
+
the widening rejects the whole payload at load time with `CONFIG_INVALID`, so `init`,
|
|
33
|
+
`sync`, `check`, `upgrade`, and `status` all fail.
|
|
34
|
+
|
|
35
|
+
The widening came after the guard, so **a build containing the widening also contains the
|
|
36
|
+
guard, and the widening is the effective minimum.** Requiring it is sufficient.
|
|
37
|
+
|
|
38
|
+
> **Minimum version: `0.2.0`.**
|
|
39
|
+
> That release carries both prerequisites above, and one more that binds harder: it reads
|
|
40
|
+
> `streamctl.config.ts` from the repo root, which is the layout this payload's fixtures,
|
|
41
|
+
> docs, and `ignoresTypeAware` default all assume. `0.1.0` already has the widening, so on
|
|
42
|
+
> that build the payload loads — it just cannot find a root config file.
|
|
43
|
+
|
|
44
|
+
### Verify your CLI has both, without needing the version number
|
|
45
|
+
|
|
46
|
+
You do not need a version number to check this. The payload itself is the test. Run this
|
|
47
|
+
once you have completed step 3 and the `package:` half of step 4, and before you change
|
|
48
|
+
`profile:`:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
pnpm streamctl check
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Read the result against this table:
|
|
55
|
+
|
|
56
|
+
| What you see | What it means |
|
|
57
|
+
| ------------ | ------------- |
|
|
58
|
+
| `[ERROR] Invalid presets/nuxt-app/preset.json: ... "scripts.lint" must be a reconcilable version key ...` | The widening is MISSING. Upgrade the CLI. Nothing else will work until you do. |
|
|
59
|
+
| `[ERROR] profile "nuxt" is not declared in presets/manifest.json profiles[]; declared: nuxt-4.` with exit 1 | Both requirements are met. This error is the guard doing its job. Continue to the `profile:` edit. |
|
|
60
|
+
| `all clean.` with exit 0, while `profile:` is still `nuxt` | The guard is MISSING. Stop. Do not continue. Upgrade the CLI first. See "Why the order matters". This should be impossible on any build cut from the current source, since the widening implies the guard; if you see it, you are on an older published build that predates both. |
|
|
61
|
+
|
|
62
|
+
The middle row is the expected outcome on a correct CLI. It looks like a failure and is
|
|
63
|
+
not one: you are deliberately in a half-migrated state, and the CLI is telling you the
|
|
64
|
+
profile name has not been updated yet.
|
|
65
|
+
|
|
66
|
+
## Why the order matters
|
|
67
|
+
|
|
68
|
+
**Skip this section if you already confirmed your CLI with the table above.** The failure it
|
|
69
|
+
describes cannot happen on a CLI that has the guard. It is here because every adopter branch
|
|
70
|
+
today runs a build that does not, and because the failure is invisible if you meet it.
|
|
71
|
+
|
|
72
|
+
The CLI resolves a version baseline with `versionProfiles?.[profile] ?? {}`. A profile name
|
|
73
|
+
that no preset declares therefore resolves to an empty object rather than raising an error.
|
|
74
|
+
An empty baseline reconciles nothing.
|
|
75
|
+
|
|
76
|
+
On a CLI **with** the guard, an undeclared profile is a hard error and exit 1. You cannot
|
|
77
|
+
get into the bad state.
|
|
78
|
+
|
|
79
|
+
On a CLI **without** the guard, renaming the profile to `nuxt-4` before the payload that
|
|
80
|
+
declares `nuxt-4` is resolvable gives you a repository where:
|
|
81
|
+
|
|
82
|
+
- `streamctl check` prints `all clean.` and exits 0,
|
|
83
|
+
- `streamctl sync` reports that everything is up to date and writes nothing,
|
|
84
|
+
- every managed file really is in sync, so the report is not lying about files,
|
|
85
|
+
- and yet `eslint`, `jiti`, `prisma`, `@prisma/client`, `typescript`, and the
|
|
86
|
+
`scripts.postinstall` and `scripts.lint` entries are never reconciled again.
|
|
87
|
+
|
|
88
|
+
Exactly one thing surfaces, a non-blocking warning on stderr:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
[WARN] profile "nuxt-4" does not match the profile detected from package.json: "nuxt" (nuxt ^4.3.0 in devDependencies).
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Do not count on that line to save you. It does not fail the command, it does not change the
|
|
95
|
+
exit code, and it prints directly above a green `all clean.` in a CI log that nobody opens
|
|
96
|
+
when the job passes. Treat it as the only warning you will get, not as a safety net.
|
|
97
|
+
|
|
98
|
+
The repository quietly stops receiving the version floors the fleet relies on, and the next
|
|
99
|
+
person to look sees a green check. This was reproduced deliberately, not theorised: on a
|
|
100
|
+
guardless CLI with the profile renamed ahead of the payload, `jiti` was hand-edited down to
|
|
101
|
+
`^1.0.0`, `eslint` down to `^8.0.0`, and `scripts.postinstall` deleted outright. The next
|
|
102
|
+
`sync` restored none of the three and reported nothing to write, and `check` still exited 0
|
|
103
|
+
with `all clean.`
|
|
104
|
+
|
|
105
|
+
If you suspect a repository is already in this state, do not trust `check`. Open
|
|
106
|
+
`package.json` and confirm the pins actually match the baseline in
|
|
107
|
+
`presets/nuxt-app/preset.json`.
|
|
108
|
+
|
|
109
|
+
## Why `streamctl upgrade` cannot do this
|
|
110
|
+
|
|
111
|
+
`upgrade` moves the version pin and nothing else. Internally it builds the new config as
|
|
112
|
+
`{ ...config, version: toVersion }`, so `package:` is carried through untouched, and it
|
|
113
|
+
bumps the devDependency under that same unchanged name. There is no code path in which it
|
|
114
|
+
rewrites `package:` from one npm name to another.
|
|
115
|
+
|
|
116
|
+
So `upgrade` can take you from `@sidebase/base-config@0.1.0` to a later `@sidebase/base-config`,
|
|
117
|
+
but it cannot take you from `@sidestream-tech/nuxt-config` to `@sidebase/base-config`. That
|
|
118
|
+
is why this runbook exists and why step 4 is a manual edit.
|
|
119
|
+
|
|
120
|
+
## The runbook
|
|
121
|
+
|
|
122
|
+
### 0. Remove any dead local override
|
|
123
|
+
|
|
124
|
+
Skip this if `pnpm install` already works. If it does not, this is almost certainly why, and
|
|
125
|
+
every later step runs an install.
|
|
126
|
+
|
|
127
|
+
A repository that installed the payload through a local override, for example
|
|
128
|
+
`pnpm.overrides` pointing at a checkout or a tarball path, cannot install once that path
|
|
129
|
+
disappears. Every known adopter branch is in exactly this state, pinned into a `/tmp`
|
|
130
|
+
directory that no longer exists.
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
grep -n "file:" package.json
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If a `file:` entry points somewhere that no longer exists, there is nothing to repoint and
|
|
137
|
+
nothing to preserve: delete it. Note that `pnpm remove` in step 3 does NOT clear a matching
|
|
138
|
+
`pnpm.overrides` entry, so the override survives the swap unless you remove it by hand.
|
|
139
|
+
|
|
140
|
+
If the path still exists and you are deliberately testing a local build, repoint it at the
|
|
141
|
+
artifact you actually want before continuing. The CLI never rewrites an override for you,
|
|
142
|
+
because that is package-manager specific.
|
|
143
|
+
|
|
144
|
+
### 1. Get to a clean check first
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
pnpm streamctl check
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Expected before you go any further: exit 0 and `all clean.`
|
|
151
|
+
|
|
152
|
+
If the repository has pre-existing drift, `check` exits 3 and prints the drifted paths:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
streamctl check
|
|
156
|
+
|
|
157
|
+
[x] drift 1 AGENTS.md (content)
|
|
158
|
+
|
|
159
|
+
DRIFT_DETECTED. Run `streamctl sync` to reconcile.
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Resolve it now, either by accepting the payload version with `pnpm streamctl sync`, or by
|
|
163
|
+
opting the file out (see the escapes section) if the repository genuinely owns it. Do not
|
|
164
|
+
carry drift into a rename: any automated path takes the drift branch instead of the
|
|
165
|
+
upgrade branch, and hand-resolving a conflict at the same time as a rename makes it much
|
|
166
|
+
harder to tell which change caused what.
|
|
167
|
+
|
|
168
|
+
### 2. Upgrade the CLI
|
|
169
|
+
|
|
170
|
+
Do this BEFORE touching the config.
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
pnpm add -D @sidebase/streamctl@^0.2.0
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 3. Swap the payload package
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
pnpm remove @sidestream-tech/nuxt-config
|
|
180
|
+
pnpm add -D @sidebase/base-config
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 4. Edit `streamctl.config.ts`
|
|
184
|
+
|
|
185
|
+
Do this in two parts so you get the CLI verification for free.
|
|
186
|
+
|
|
187
|
+
First change `package:` and `version:`, and leave `profile:` alone:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
export default defineNuxtBaseConfig({
|
|
191
|
+
package: "@sidebase/base-config", // was @sidestream-tech/nuxt-config
|
|
192
|
+
base: "nuxt-app", // unchanged
|
|
193
|
+
version: "0.1.0", // the @sidebase/base-config version you installed
|
|
194
|
+
profile: "nuxt", // still the OLD name, on purpose, for one command
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Run `pnpm streamctl check` and read it against the table in the CLI prerequisite section.
|
|
199
|
+
You want the `profile "nuxt" is not declared` error with exit 1.
|
|
200
|
+
|
|
201
|
+
If you instead get `CONFIG_INVALID` naming a key in your own config, that key was removed in
|
|
202
|
+
this release; `ci.deploy` and `ci.migrationLint` are the two. The CLI shape-checks the config
|
|
203
|
+
against the payload's declared `configKeys`, so a key that no longer exists is a hard error
|
|
204
|
+
rather than an ignored field. Delete it and re-run.
|
|
205
|
+
|
|
206
|
+
Then change the profile:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
profile: "nuxt-4",
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The import in that file changes too, since `defineNuxtBaseConfig` now comes from the new
|
|
213
|
+
package:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { defineNuxtBaseConfig } from "@sidebase/base-config";
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 5. Update the other import specifiers
|
|
220
|
+
|
|
221
|
+
Two files import from the payload by name. The ESLint factory was also RENAMED in this
|
|
222
|
+
release, from `createStreamctlEslint` to `createSidebaseEslint`, so that call changes too:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// eslint.config.ts
|
|
226
|
+
import { createSidebaseEslint } from "@sidebase/base-config/eslint";
|
|
227
|
+
|
|
228
|
+
export default createSidebaseEslint();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// prisma.config.ts
|
|
233
|
+
import { applyPrismaDevEnv, buildPrismaConfig } from "@sidebase/base-config/prisma";
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`eslint.config.ts` is a scaffold, written once and then owned by your project, so streamctl
|
|
237
|
+
will not rewrite this for you. If you skip it, ESLint fails to load the config because the
|
|
238
|
+
old name no longer exists.
|
|
239
|
+
|
|
240
|
+
Check for any others:
|
|
241
|
+
|
|
242
|
+
```sh
|
|
243
|
+
grep -rn "@sidestream-tech/nuxt-config\|createStreamctlEslint" . --exclude-dir=node_modules --exclude=pnpm-lock.yaml
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
That command should return nothing when you are done.
|
|
247
|
+
|
|
248
|
+
`pnpm-lock.yaml` is excluded on purpose. It still names the old package until both new
|
|
249
|
+
packages are published and you can regenerate it, so leaving it in would make a correctly
|
|
250
|
+
migrated repository fail its own check. Do not hand-edit the lockfile to make hits go away.
|
|
251
|
+
|
|
252
|
+
### 6. Decide how you own `pnpm-workspace.yaml`, BEFORE you sync
|
|
253
|
+
|
|
254
|
+
Do this first. Once sync has adopted the file, the content you need is gone.
|
|
255
|
+
|
|
256
|
+
`pnpm-workspace.yaml` is managed as a WHOLE FILE, not a block, so adopting it replaces
|
|
257
|
+
everything in it. This step applies to a fresh `streamctl init` just as much as to a
|
|
258
|
+
migration; the only difference is that a fresh repository may have nothing to lose.
|
|
259
|
+
|
|
260
|
+
Look at what yours currently holds:
|
|
261
|
+
|
|
262
|
+
```sh
|
|
263
|
+
cat pnpm-workspace.yaml
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
If the file does not exist, there is nothing to preserve. Skip to step 7 and let sync
|
|
267
|
+
create the managed one. Otherwise take exactly one of the two branches below.
|
|
268
|
+
|
|
269
|
+
**Branch A: your file declares a real workspace.** That means a `packages:` key with
|
|
270
|
+
entries in it, or an `ignoredBuiltDependencies` key, or any other pnpm setting the payload
|
|
271
|
+
does not render. A real example, from a repository that would have lost its monorepo:
|
|
272
|
+
|
|
273
|
+
```yaml
|
|
274
|
+
packages:
|
|
275
|
+
- modules/*
|
|
276
|
+
|
|
277
|
+
ignoredBuiltDependencies:
|
|
278
|
+
- puppeteer
|
|
279
|
+
|
|
280
|
+
onlyBuiltDependencies:
|
|
281
|
+
- '@parcel/watcher'
|
|
282
|
+
# ...nine more
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Opt the file out entirely:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
files: { "pnpm-workspace.yaml": "off" },
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Do NOT use the `pnpm.onlyBuiltDependencies` knob for this case. The managed file hardcodes
|
|
292
|
+
`packages: []`, so adopting it deletes your workspace definition and every setting the
|
|
293
|
+
payload does not know about, and the knob only carries the build allowlist back. A real
|
|
294
|
+
monorepo that adopts this file stops being a monorepo. Opting out preserves the workspace,
|
|
295
|
+
the ignore lists, and the full build allowlist in one move.
|
|
296
|
+
|
|
297
|
+
The cost of opting out is that you no longer receive the supply-chain cooldown or any
|
|
298
|
+
future change to this file. If you want the cooldown, copy these into your own file and
|
|
299
|
+
maintain them yourself:
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
minimumReleaseAge: 10080
|
|
303
|
+
minimumReleaseAgeExclude:
|
|
304
|
+
- "@sidebase/*"
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Branch B: your file is only an `onlyBuiltDependencies` list.** Adopt the managed file and
|
|
308
|
+
carry your approvals across. `onlyBuiltDependencies` is the allowlist of packages permitted
|
|
309
|
+
to run install lifecycle scripts, and the payload's baseline is just three entries:
|
|
310
|
+
`@prisma/client`, `esbuild`, and `prisma`. Everything else you have is project-specific and
|
|
311
|
+
must be re-added, or it is dropped.
|
|
312
|
+
|
|
313
|
+
Record the list first:
|
|
314
|
+
|
|
315
|
+
```sh
|
|
316
|
+
sed -n '/onlyBuiltDependencies:/,/^[^ -]/p' pnpm-workspace.yaml
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Then add the non-baseline entries to `streamctl.config.ts`. The knob is additive on top of
|
|
320
|
+
the baseline, so list only the extras:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
pnpm: {
|
|
324
|
+
onlyBuiltDependencies: ["@parcel/watcher", "@prisma/engines", "@tailwindcss/oxide", "sharp", "unrs-resolver", "vue-demi"],
|
|
325
|
+
},
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Those six are a real example, from a repository that had approved nine and would have been
|
|
329
|
+
left with three. Use the knob rather than `pnpm approve-builds`, which writes straight into
|
|
330
|
+
the managed file and is reverted on the next sync.
|
|
331
|
+
|
|
332
|
+
**Why this is worth doing before anything else: losing an approval is silent.** Nothing
|
|
333
|
+
warns you. `streamctl check` stays green, the file looks deliberate, and a package whose
|
|
334
|
+
install scripts are blocked still installs.
|
|
335
|
+
|
|
336
|
+
Be precise about the consequence, because it is narrower than it first appears. The usual
|
|
337
|
+
suspects (`sharp`, `@tailwindcss/oxide`, `unrs-resolver`, `@parcel/watcher`) ship their
|
|
338
|
+
native binary as a prebuilt optional dependency, so on a platform with a prebuild they keep
|
|
339
|
+
working whether or not they are approved, cold install included. Verified across pnpm
|
|
340
|
+
10.28.1, 10.29.1 and 10.29.3. Where the loss is real:
|
|
341
|
+
|
|
342
|
+
- architectures with no prebuild, where the binary genuinely has to be compiled
|
|
343
|
+
- packages whose postinstall does essential work that is not a native build
|
|
344
|
+
|
|
345
|
+
So this is not usually a broken CI on day one. It is an unrequested, unannounced change to
|
|
346
|
+
a config your project owns, whose cost lands later and somewhere else. Re-add the entries.
|
|
347
|
+
|
|
348
|
+
### 7. Sync, then install
|
|
349
|
+
|
|
350
|
+
```sh
|
|
351
|
+
pnpm streamctl sync
|
|
352
|
+
pnpm install
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Expect sync to stop the first time, without writing anything.** On any repository
|
|
356
|
+
migrating from `@sidestream-tech/nuxt-config` it reports something like:
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
[!] adoption (4) pre-existing file(s) streamctl now manages
|
|
360
|
+
tsconfig.json - pre-existing file streamctl now manages; adopting is expected
|
|
361
|
+
.dockerignore - pre-existing file streamctl now manages; adopting is expected
|
|
362
|
+
pnpm-workspace.yaml - pre-existing file streamctl now manages; adopting is expected
|
|
363
|
+
Dockerfile - pre-existing file streamctl now manages; adopting is expected
|
|
364
|
+
-> `sync --interactive` to adopt per file, `sync --force` to take ownership, or `--only <glob>` to scope
|
|
365
|
+
|
|
366
|
+
4 conflict(s) pending; see the per-kind guidance above.
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
This is not a failure and it is not something to route around. A fully-managed file whose
|
|
370
|
+
on-disk content differs from what the payload would write becomes an adoption conflict, and
|
|
371
|
+
the managed content changed substantially between the old payload and this one, so every
|
|
372
|
+
migrating repository hits it. The stop is the CLI refusing to overwrite files it has not
|
|
373
|
+
been told it owns.
|
|
374
|
+
|
|
375
|
+
Resolve it deliberately, in this order:
|
|
376
|
+
|
|
377
|
+
1. Finish step 6 first if you have not. `pnpm-workspace.yaml` is in that conflict list, and
|
|
378
|
+
adopting it is exactly what discards your build approvals, or your whole workspace
|
|
379
|
+
definition if you are a monorepo. If you took branch A and opted the file out, it will
|
|
380
|
+
not appear in the conflict list at all.
|
|
381
|
+
2. Adopt. Only `--interactive` and `--force` actually resolve a conflict; without one of
|
|
382
|
+
them the command repeats the same message and writes nothing.
|
|
383
|
+
- `pnpm streamctl sync --interactive` walks the files one at a time. Best if you are not
|
|
384
|
+
sure what your repository has customised.
|
|
385
|
+
- `pnpm streamctl sync --force` takes ownership of all of them at once. Only reach for
|
|
386
|
+
this once you have read what it will overwrite, because it is what silently replaces
|
|
387
|
+
your `onlyBuiltDependencies` list, and your `packages:` entries with it.
|
|
388
|
+
- `--only <glob>` scopes WHICH files are considered; it does not adopt anything by
|
|
389
|
+
itself. Combine it with `--force` to take one file at a time and read each diff
|
|
390
|
+
separately, which is the reviewable middle ground if you have no terminal for
|
|
391
|
+
`--interactive`:
|
|
392
|
+
|
|
393
|
+
```sh
|
|
394
|
+
pnpm streamctl sync --force --only 'Dockerfile'
|
|
395
|
+
git diff Dockerfile
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
One more thing `--force` does: it overrides the refusal to sync when streamctl-owned
|
|
399
|
+
files have uncommitted changes. Commit or stash your work first, so `git diff` after
|
|
400
|
+
each adoption shows only what sync did.
|
|
401
|
+
3. `pnpm install` afterwards. It is separate and required, because `sync` edits
|
|
402
|
+
`package.json` but does not install. **Commit the regenerated `pnpm-lock.yaml` with the
|
|
403
|
+
rest of the migration**, then confirm it is actually usable:
|
|
404
|
+
|
|
405
|
+
```sh
|
|
406
|
+
pnpm install --frozen-lockfile --ignore-scripts --offline
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
That is what CI and the managed Dockerfile run. It fails before any network call, so it
|
|
410
|
+
needs no registry token. A stale lockfile fails here with `ERR_PNPM_OUTDATED_LOCKFILE`
|
|
411
|
+
(or `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` if you also changed `pnpm.overrides`), long
|
|
412
|
+
before anyone sees it in CI.
|
|
413
|
+
|
|
414
|
+
Expect a large diff regardless: it carries every payload change since the adoption branch
|
|
415
|
+
was cut, including the `scripts.lint` reconcile and the renamed Dockerfile stage. If any
|
|
416
|
+
deploy workflow or compose file builds with `--target pro`, update it to `--target
|
|
417
|
+
production` now, because the final stage was renamed.
|
|
418
|
+
|
|
419
|
+
Read the diff before committing. The GitHub release notes for the version you pinned say
|
|
420
|
+
what changed and what each change requires of you.
|
|
421
|
+
|
|
422
|
+
### 8. Reconcile `.editorconfig` by hand
|
|
423
|
+
|
|
424
|
+
**This is not migration-specific.** It happens to any repository that already has an
|
|
425
|
+
`.editorconfig` when the payload first manages one, including a fresh `streamctl init`. The
|
|
426
|
+
block writer appends its managed block without looking at what is already in the file, so
|
|
427
|
+
whatever was there stays above the block, duplicated or contradicted by it.
|
|
428
|
+
|
|
429
|
+
Check whether anything at all sits above the managed block:
|
|
430
|
+
|
|
431
|
+
```sh
|
|
432
|
+
awk '/# BEGIN streamctl MANAGED BLOCK editorconfig/{exit} {print}' .editorconfig \
|
|
433
|
+
| command grep -qv '^[[:space:]]*\([#;].*\)\?$' && echo AFFECTED || echo clean
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Use `command grep`, not bare `grep`. Some shells (and some dotfiles) alias or wrap `grep`
|
|
437
|
+
with a tool that does not preserve `-q` exit codes, and this detector reads that exit code
|
|
438
|
+
as its entire answer. A wrapped `grep` can return `clean` on an affected file, which is the
|
|
439
|
+
same silent miss the step exists to catch.
|
|
440
|
+
|
|
441
|
+
`clean` means there is nothing to do. `AFFECTED` means which fix you need depends on whether
|
|
442
|
+
your existing settings AGREE with the payload's. Paste this helper, then run it:
|
|
443
|
+
|
|
444
|
+
```sh
|
|
445
|
+
# Flatten an .editorconfig into "section<TAB>key = value" pairs. Comparing whole
|
|
446
|
+
# lines is not enough: sorting separates a setting from its section header, and a
|
|
447
|
+
# header that also appears in the managed block gets subtracted away, so a value
|
|
448
|
+
# can be reported under the wrong section.
|
|
449
|
+
ec_pairs() {
|
|
450
|
+
awk '
|
|
451
|
+
/^[[:space:]]*[#;]/ { next }
|
|
452
|
+
/^[[:space:]]*$/ { next }
|
|
453
|
+
/^[[:space:]]*\[/ { s=$0; gsub(/^[[:space:]]+|[[:space:]]+$/,"",s); next }
|
|
454
|
+
{ l=$0; gsub(/^[[:space:]]+|[[:space:]]+$/,"",l)
|
|
455
|
+
print (s==""?"(preamble)":s) "\t" l }
|
|
456
|
+
' | sort -u
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
BLOCK=$(mktemp)
|
|
460
|
+
sed -n '/# BEGIN streamctl MANAGED BLOCK editorconfig/,/# END streamctl MANAGED BLOCK editorconfig/p' \
|
|
461
|
+
.editorconfig | sed '1d;$d' > "$BLOCK"
|
|
462
|
+
|
|
463
|
+
# Every setting above the block that the block does not already provide, either
|
|
464
|
+
# under the same section or globally via [*].
|
|
465
|
+
awk -F'\t' 'NR==FNR{star[$0];next} !($2 in star)' \
|
|
466
|
+
<(ec_pairs < "$BLOCK" | awk -F'\t' '$1=="[*]"{print $2}') \
|
|
467
|
+
<(comm -23 \
|
|
468
|
+
<(awk '/# BEGIN streamctl MANAGED BLOCK editorconfig/{exit} {print}' .editorconfig | ec_pairs) \
|
|
469
|
+
<(ec_pairs < "$BLOCK"))
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Every line it prints carries its own section, so there is never any doubt about where a
|
|
473
|
+
setting belongs:
|
|
474
|
+
|
|
475
|
+
```
|
|
476
|
+
[*.md] indent_size = 4
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Do not test this by counting `root = true`. That key is conventional, not required, and a
|
|
480
|
+
pre-existing file without it still leaves the count at `1` after sync while its settings are
|
|
481
|
+
being silently overridden. The `awk` above the block is deliberate for a related reason: a
|
|
482
|
+
`sed` end-regex is searched from the line AFTER the start, so if the marker lands on line 1
|
|
483
|
+
the range never closes and the whole file is printed. (The `sed` range that extracts the
|
|
484
|
+
block is safe, because `# BEGIN` cannot be the last line of a well-formed file.)
|
|
485
|
+
|
|
486
|
+
**If it prints nothing, your copy is redundant.** Delete everything ABOVE the
|
|
487
|
+
`# BEGIN streamctl MANAGED BLOCK editorconfig` line, keeping the managed block and anything
|
|
488
|
+
below the `# END` line that your project genuinely owns.
|
|
489
|
+
|
|
490
|
+
**If it prints anything, do NOT just delete your copy: you would be throwing away a real
|
|
491
|
+
preference.** Recreate each printed line below the `# END` marker, under the section header
|
|
492
|
+
printed beside it, then delete the rest of the pre-block content. Order matters here, and it
|
|
493
|
+
is the whole reason the payload wants project overrides underneath: EditorConfig applies
|
|
494
|
+
later matching sections over earlier ones, so a section below the block wins and a section
|
|
495
|
+
above it loses.
|
|
496
|
+
|
|
497
|
+
A repository with `indent_size = 4` above the block, against the payload's `indent_size = 2`
|
|
498
|
+
inside it, is silently switched to 2. Nothing warns, and the file still contains the 4 you
|
|
499
|
+
wrote, which makes it look intentional. The result should be:
|
|
500
|
+
|
|
501
|
+
```
|
|
502
|
+
# BEGIN streamctl MANAGED BLOCK editorconfig
|
|
503
|
+
...payload settings, including indent_size = 2...
|
|
504
|
+
# END streamctl MANAGED BLOCK editorconfig
|
|
505
|
+
|
|
506
|
+
[*]
|
|
507
|
+
indent_size = 4
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Either way, finish by re-running the `AFFECTED`/`clean` detector above. It should now say
|
|
511
|
+
`clean`.
|
|
512
|
+
|
|
513
|
+
**Also check your pre-adoption `.editorconfig`, not only the current one.** If this
|
|
514
|
+
repository already synced the payload at some earlier version, and that version managed
|
|
515
|
+
`.editorconfig` with the `full` strategy rather than `block`, your settings were replaced
|
|
516
|
+
wholesale instead of being left above the block. The detector then reports `clean` while a
|
|
517
|
+
preference is already gone, because the current file no longer contains the evidence. Run
|
|
518
|
+
the same comparison against the version from before the first sync:
|
|
519
|
+
|
|
520
|
+
```sh
|
|
521
|
+
git show origin/main:.editorconfig > /tmp/ec-before # or the commit before your first sync
|
|
522
|
+
awk -F'\t' 'NR==FNR{star[$0];next} !($2 in star)' \
|
|
523
|
+
<(ec_pairs < .editorconfig | awk -F'\t' '$1=="[*]"{print $2}') \
|
|
524
|
+
<(comm -23 <(ec_pairs < /tmp/ec-before) <(ec_pairs < .editorconfig))
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Note this one compares against the WHOLE current file, not just `$BLOCK`, so it also sees
|
|
528
|
+
the overrides you have already put below `# END`. That makes it self-verifying: it goes
|
|
529
|
+
empty once you have restored everything, whereas a block-only comparison would keep
|
|
530
|
+
reporting your own fix back at you forever.
|
|
531
|
+
|
|
532
|
+
Anything it prints was lost earlier and should be restored below `# END` the same way. A
|
|
533
|
+
real case: a repository whose old file set `[*.md] indent_size = 4` had it replaced by a
|
|
534
|
+
block that sets only `trim_trailing_whitespace` for `[*.md]`, so its markdown silently
|
|
535
|
+
started following the `[*]` value of 2.
|
|
536
|
+
|
|
537
|
+
**`streamctl check` exits 0 either way, so it will not catch this for you.** The block
|
|
538
|
+
strategy only inspects its own markers, and the block is present and correct; anything
|
|
539
|
+
outside it is invisible to the check. That is why this is a manual step rather than
|
|
540
|
+
something drift detection reports.
|
|
541
|
+
|
|
542
|
+
### 9. Clean up what the payload no longer manages
|
|
543
|
+
|
|
544
|
+
Two different kinds of leftover, with the same cause: streamctl manages by path, so
|
|
545
|
+
anything it managed under an old path or an old payload is simply dropped from the
|
|
546
|
+
managed set rather than removed. `check` reports clean either way, because it only
|
|
547
|
+
inspects paths that are currently managed. Nothing will ever tell you these are there.
|
|
548
|
+
|
|
549
|
+
#### 9a. The two orphaned workflow files
|
|
550
|
+
|
|
551
|
+
Do this only if your repository synced the payload before the workflow extensions were
|
|
552
|
+
normalized, which is the case for every adoption branch cut before this release. Check:
|
|
553
|
+
|
|
554
|
+
```sh
|
|
555
|
+
ls .github/workflows/
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
If you see `ci.yaml` or `pr-preview-cleanup.yaml` sitting next to `ci.yml` and
|
|
559
|
+
`pr-preview-cleanup.yml`, remove the `.yaml` pair:
|
|
560
|
+
|
|
561
|
+
```sh
|
|
562
|
+
git rm --ignore-unmatch .github/workflows/ci.yaml .github/workflows/pr-preview-cleanup.yaml
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
`--ignore-unmatch` matters: the condition above is "either file", but `git rm` is atomic, so
|
|
566
|
+
naming a path that does not exist aborts with `fatal: pathspec ... did not match any files`
|
|
567
|
+
and removes NEITHER. A repository that opted one workflow out, or never enabled preview
|
|
568
|
+
cleanup, would skim that error and keep running both copies of the other.
|
|
569
|
+
|
|
570
|
+
This is required, not tidying. The payload renamed those two managed files from `.yaml` to
|
|
571
|
+
`.yml`. streamctl manages files by path, so the sync in step 7 wrote the new `.yml` files
|
|
572
|
+
but did NOT delete the old `.yaml` ones: they are simply no longer in the managed set.
|
|
573
|
+
GitHub Actions runs every file in `.github/workflows/`, so if you leave them the
|
|
574
|
+
repository runs BOTH copies. That means duplicate CI on every push, and two
|
|
575
|
+
`pr-preview-cleanup` jobs racing to tear down the same preview environment. The leftover
|
|
576
|
+
copies are also frozen forever, so they never receive later fixes, including security
|
|
577
|
+
updates to pinned action SHAs.
|
|
578
|
+
|
|
579
|
+
While you are here, if your config opts out of either workflow by its old path, for
|
|
580
|
+
example `files: { ".github/workflows/ci.yaml": "off" }`, re-key it to the `.yml` path.
|
|
581
|
+
An opt-out naming a path that is no longer managed does nothing, so the file you meant to
|
|
582
|
+
suppress would start arriving on the next sync.
|
|
583
|
+
|
|
584
|
+
#### 9b. Orphaned managed BLOCKS
|
|
585
|
+
|
|
586
|
+
The same problem in a harder-to-see form. A `block` file keeps its markers after the
|
|
587
|
+
payload stops managing that path, so the block sits there forever: never updated, never
|
|
588
|
+
reported, and indistinguishable from a block that is still live. The two `.yaml`
|
|
589
|
+
workflows above are the known orphaned FILES for this release; orphaned blocks are not
|
|
590
|
+
enumerable in advance, because they depend on which payload version your repository
|
|
591
|
+
adopted first.
|
|
592
|
+
|
|
593
|
+
Do not hardcode a list. Ask the repository instead: every file carrying a streamctl
|
|
594
|
+
marker, minus everything the payload currently manages.
|
|
595
|
+
|
|
596
|
+
```sh
|
|
597
|
+
LC_ALL=C comm -23 \
|
|
598
|
+
<(git grep -lI "BEGIN streamctl MANAGED BLOCK" -- . | LC_ALL=C sort -u) \
|
|
599
|
+
<(pnpm streamctl status --json \
|
|
600
|
+
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{
|
|
601
|
+
JSON.parse(s).data.files.forEach(f=>console.log(f.path));});' \
|
|
602
|
+
| LC_ALL=C sort -u)
|
|
603
|
+
# anything printed is an orphaned block
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
**Do not add a comment prefix to that marker string.** streamctl picks the comment syntax
|
|
607
|
+
from the file extension, so the same block opens three different ways:
|
|
608
|
+
|
|
609
|
+
| Extension | Marker |
|
|
610
|
+
| --------- | ------ |
|
|
611
|
+
| `.js`, `.mjs`, `.cjs`, `.ts`, `.mts`, `.cts`, `.json`, `.jsonc`, `.json5` | `// BEGIN streamctl MANAGED BLOCK x` |
|
|
612
|
+
| `.md`, `.markdown`, `.html`, `.htm`, `.vue` | `<!-- BEGIN streamctl MANAGED BLOCK x -->` |
|
|
613
|
+
| everything else (`.editorconfig`, `.gitignore`, `.npmrc`, yaml, toml, ini, sh, Dockerfile) | `# BEGIN streamctl MANAGED BLOCK x` |
|
|
614
|
+
|
|
615
|
+
Anchoring on `# BEGIN ...`, which is what step 8 above uses because it is specifically
|
|
616
|
+
about `.editorconfig`, silently returns clean for an orphan in any `.md`, `.ts`, `.json`
|
|
617
|
+
or `.vue`. Seeded against a repository with one orphan in each of the three syntaxes:
|
|
618
|
+
|
|
619
|
+
```
|
|
620
|
+
un-prefixed anchor (correct) -> .npmrc, NOTES.md, legacy.ts
|
|
621
|
+
# BEGIN ... (hash-anchored) -> .npmrc
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Both current block-strategy files are hash-syntax, so the wrong version gives the right
|
|
625
|
+
answer on every repository we have today and fails on the first one nobody has yet. The
|
|
626
|
+
whole point of this step is repositories migrating from OLDER payloads whose block files
|
|
627
|
+
cannot be enumerated.
|
|
628
|
+
|
|
629
|
+
`status` rather than `sync --dry-run`: it reports the managed set directly, including
|
|
630
|
+
files this repository has disabled with `files: { ... : "off" }` -- a disabled file is
|
|
631
|
+
still managed, and treating it as unmanaged would report it as an orphan. It also writes
|
|
632
|
+
nothing and prints no plan diff.
|
|
633
|
+
|
|
634
|
+
**Judge this by its OUTPUT, not its exit code.** `streamctl status --help` claims it
|
|
635
|
+
always exits 0; it does not -- it exits 1 when the payload is declared but not installed.
|
|
636
|
+
That state does not arise here, because step 7 installed two steps ago, but do not build
|
|
637
|
+
an `if` around the exit status.
|
|
638
|
+
|
|
639
|
+
`LC_ALL=C` on **all three** commands, not just the sorts. `comm` validates its input
|
|
640
|
+
against the collation of the locale it is running in, and the default UTF-8 collation
|
|
641
|
+
orders `AGENTS.md` before `.dockerignore` while `C` does the reverse. Mixing them makes
|
|
642
|
+
`comm` emit `file 2 is not in sorted order` and compare two orderings it cannot reconcile.
|
|
643
|
+
|
|
644
|
+
`git grep` searches tracked files only. That is the right scope here -- an orphaned block
|
|
645
|
+
is by definition pre-existing committed content -- but it means a block in an untracked
|
|
646
|
+
file will not be reported.
|
|
647
|
+
|
|
648
|
+
**What to do with a hit depends on what the block provides. Do not delete on sight.**
|
|
649
|
+
|
|
650
|
+
- **The block is still doing a job.** The registry block is the case that actually
|
|
651
|
+
occurs: a `@sidestream-tech:registry=...` line in `.npmrc` is no longer managed by this
|
|
652
|
+
payload, but if anything in the repository still resolves from that scope, deleting it
|
|
653
|
+
breaks `pnpm install` on the next cold run and the failure surfaces in CI rather than
|
|
654
|
+
here. Check before touching it:
|
|
655
|
+
|
|
656
|
+
```sh
|
|
657
|
+
if [ ! -f package.json ] || [ ! -f pnpm-lock.yaml ]; then
|
|
658
|
+
echo "CANNOT TELL - a file this check reads is missing. Keep the block."
|
|
659
|
+
elif command grep -rn '@sidestream-tech/' package.json pnpm-lock.yaml; then
|
|
660
|
+
echo "IN USE - keep the block."
|
|
661
|
+
else
|
|
662
|
+
echo "UNUSED - safe to delete."
|
|
663
|
+
fi
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**The existence guard is the load-bearing part, not the grep.** A missing
|
|
667
|
+
`pnpm-lock.yaml` is not evidence that nothing uses the scope; it is evidence that the
|
|
668
|
+
question cannot be answered yet. Silencing the error with a bare
|
|
669
|
+
`2>/dev/null` produces empty output and routes straight to "nothing depends on it,
|
|
670
|
+
delete" -- the one action this section warns against, in exactly the situation that
|
|
671
|
+
makes the answer unknowable. Missing input must reach the "you cannot tell" branch
|
|
672
|
+
below, which keeps the block.
|
|
673
|
+
|
|
674
|
+
Note which way the unguarded version failed: `grep: pnpm-lock.yaml: No such file or
|
|
675
|
+
directory` on stderr reads as output, so it produced **keep**. That was wrong for the
|
|
676
|
+
wrong reason but landed on the safe action. Both states are defects; only one of them
|
|
677
|
+
breaks an install.
|
|
678
|
+
|
|
679
|
+
If you keep it, consider removing the streamctl markers around the block so it reads
|
|
680
|
+
as the repository's own content, which is now what it is -- the markers are the only
|
|
681
|
+
thing implying something else maintains it.
|
|
682
|
+
|
|
683
|
+
- **Nothing depends on it any more.** Delete the block, markers included. If the block
|
|
684
|
+
was the entire file, delete the file.
|
|
685
|
+
|
|
686
|
+
- **You cannot tell.** Leave it and note it in the PR. An orphaned block is inert; a
|
|
687
|
+
wrongly deleted one is a broken install. The asymmetry favours leaving it.
|
|
688
|
+
|
|
689
|
+
### 10. Verify
|
|
690
|
+
|
|
691
|
+
```sh
|
|
692
|
+
pnpm streamctl check
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Expected:
|
|
696
|
+
|
|
697
|
+
```
|
|
698
|
+
streamctl check
|
|
699
|
+
|
|
700
|
+
[+] in sync all managed files match
|
|
701
|
+
|
|
702
|
+
all clean.
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
Exit 0. Then confirm the migration actually reconciled, which `check` alone does not tell
|
|
706
|
+
you:
|
|
707
|
+
|
|
708
|
+
```sh
|
|
709
|
+
grep -E '"(@prisma/client|eslint|jiti|prisma|typescript)"' package.json
|
|
710
|
+
grep -E '"(lint|postinstall)"' package.json
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
The versions should match the `nuxt-4` baseline in
|
|
714
|
+
`node_modules/@sidebase/base-config/presets/nuxt-app/preset.json`, and `scripts.lint`
|
|
715
|
+
should now be the oxlint-then-eslint command. If `check` says clean but these did not
|
|
716
|
+
move, you are in the silent-failure state described above.
|
|
717
|
+
|
|
718
|
+
Finally, run the repository's own gates:
|
|
719
|
+
|
|
720
|
+
```sh
|
|
721
|
+
pnpm lint
|
|
722
|
+
pnpm test
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
## If you keep a `file:` override after migrating
|
|
726
|
+
|
|
727
|
+
Step 0 covers removing a dead one. If you deliberately keep a local override, for example to
|
|
728
|
+
test an unreleased payload build, later `streamctl upgrade` runs need `--to <version>`
|
|
729
|
+
explicitly: with an override in place the CLI cannot resolve the latest release from the
|
|
730
|
+
registry and fails with `CONFIG_INVALID` asking for a target. It also refuses if the
|
|
731
|
+
override embeds a version different from the target, so repoint it first.
|
|
732
|
+
|
|
733
|
+
## Two per-repo escapes
|
|
734
|
+
|
|
735
|
+
A migrating repository often needs one or both of these. Both go in `streamctl.config.ts`.
|
|
736
|
+
|
|
737
|
+
**Stop managing one file.**
|
|
738
|
+
|
|
739
|
+
```ts
|
|
740
|
+
files: { "Dockerfile": "off" },
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
Use this when the repository genuinely owns a file the payload also manages. The cost is
|
|
744
|
+
total: that file stops receiving every future payload fix, including security ones. Prefer
|
|
745
|
+
the `docker.*` knobs or the `.editorconfig` managed block over opting out, when the change
|
|
746
|
+
you need is only part of a file.
|
|
747
|
+
|
|
748
|
+
**Keep your own value for one reconciled key.**
|
|
749
|
+
|
|
750
|
+
```ts
|
|
751
|
+
versionSyncExclude: ["scripts.lint"],
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
Use this when the repository needs a different value for a specific key. `scripts.lint` is
|
|
755
|
+
the common one, because the reconcile is a plain overwrite with no version floor: every
|
|
756
|
+
sync replaces whatever you have. A repository that runs type-aware linting, for example,
|
|
757
|
+
keeps its own command this way.
|
|
758
|
+
|
|
759
|
+
## Rollback
|
|
760
|
+
|
|
761
|
+
Nothing here is destructive if you work on a branch. `sync` rewrites tracked files, so
|
|
762
|
+
`git diff` shows everything it did and `git restore` undoes it. Keep the migration on its
|
|
763
|
+
own branch and its own commit so that if the sync diff turns out to be larger than
|
|
764
|
+
expected, reverting it is one operation rather than an archaeology exercise.
|