@sidebase/base-config 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -134,9 +134,10 @@ The payload is validated and dogfooded against the CLI, so `@sidebase/streamctl`
134
134
  `validate:presets` imports the CLI's exported manifest schema and the e2e dry-run drives the
135
135
  real CLI binary, so both resolve `@sidebase/streamctl` from the registry.
136
136
 
137
- The root `streamctl.config.ts` this payload documents needs **streamctl >= 0.2.0**, so the
138
- pin and the `pnpm-workspace.yaml` c12 override both move when that release lands. See
139
- "Config location" below.
137
+ This payload needs **streamctl >= 0.3.0**: its manifest declares `schemaVersion` 3 (the
138
+ `fromDependency` placeholder behind the derived Prisma ARG), which a 0.2.x CLI rejects with
139
+ `SCHEMA_UNSUPPORTED`. The root `streamctl.config.ts` location it documents arrived in
140
+ 0.2.0 — see "Config location" below.
140
141
 
141
142
  ## Typed config
142
143
 
@@ -174,7 +175,7 @@ covers both.
174
175
 
175
176
  `defineNuxtBaseConfig` returns the config unchanged. Its job is typed editor inference
176
177
  for this payload's knobs: completion and type checking on `ci`, `versions`, `docker`,
177
- `automation`, `security`, `pnpm`, and the whole `eslint` option surface, plus `profile`
178
+ `automation`, `security`, `pnpm`, `editor`, and the whole `eslint` option surface, plus `profile`
178
179
  narrowed to the profile names that actually exist. Without it the object is an untyped
179
180
  literal, so a typo such as `ci: { unitTest: true }` stays silent in your editor.
180
181
 
@@ -448,6 +449,39 @@ the old list before you adopt and re-add it via `pnpm.onlyBuiltDependencies` abo
448
449
  files: { "pnpm-workspace.yaml": "off" },
449
450
  ```
450
451
 
452
+ ## CI knobs
453
+
454
+ The `nuxt-app` preset ships a managed `.github/workflows/ci.yml`. Its `lint-typecheck` and
455
+ `build` jobs always run; the rest is knobs:
456
+
457
+ | Knob | Default | Effect |
458
+ | ---- | ------- | ------ |
459
+ | `ci.unitTests` | `false` | Add the `test` job (`pnpm test`) |
460
+ | `ci.e2e` | `false` | Add the `e2e` job, with a health-checked postgres service |
461
+ | `ci.aptPackages` | `[]` | System packages `apt-get`-installed in the `test` and `e2e` jobs |
462
+
463
+ `ci.aptPackages` is the CI counterpart to `docker.aptPackages` below: extra system packages
464
+ on top of what the runner image already carries, for a binary the suite needs and
465
+ `ubuntu-latest` does not ship. The motivating case is `libxml2-utils`, for `xmllint`.
466
+
467
+ It applies to the `test` and `e2e` jobs **only** -- never `lint-typecheck` or `build` -- and
468
+ installs directly after `checkout`, before `pnpm install`, so a package needed by an install
469
+ lifecycle script is covered as well as one needed by the tests. Leave it unset and the step
470
+ is skipped entirely: it renders with a guard on the value being non-empty, so the default
471
+ costs a no-op step rather than an `apt-get` call.
472
+
473
+ ```ts
474
+ export default {
475
+ // ...
476
+ ci: { unitTests: true, e2e: true, aptPackages: ["libxml2-utils"] },
477
+ };
478
+ ```
479
+
480
+ Package names only. Every element is rejected if it contains a shell metacharacter, and the
481
+ joined string is then re-validated against the Debian package-name character set (lowercase,
482
+ digits, `+`, `-`, `.`). Both checks exist because the rendered command deliberately leaves
483
+ the value unquoted, which is how several packages word-split into one `apt-get` call.
484
+
451
485
  ## Dockerfile knobs
452
486
 
453
487
  The `nuxt-app` preset ships a managed `Dockerfile`. Beyond `docker.aptPackages`
@@ -464,6 +498,16 @@ out:
464
498
  | `docker.prismaRuntime` | final stage | copy the schema + install the `migrate deploy` CLI |
465
499
  | `docker.startCommand` | inside `CMD` | `prisma migrate deploy` then the node server |
466
500
 
501
+ A seventh knob is different in kind — a validated version, not raw text:
502
+
503
+ | Knob | Position | Default |
504
+ | ---- | -------- | ------- |
505
+ | `docker.prismaVersion` | the `ARG PRISMA_VERSION=` default inside `docker.prismaRuntime` | derived from the repo's `prisma` pin (its range floor); the baseline only when no full `x.y.z` pin exists |
506
+
507
+ It is pattern-checked (`x.y.z`, optional prerelease), so unlike the six raw-text
508
+ knobs it cannot inject arbitrary Dockerfile content. `--build-arg PRISMA_VERSION=`
509
+ still overrides at build time.
510
+
467
511
  ```ts
468
512
  export default {
469
513
  // ...
@@ -502,6 +546,42 @@ Global installs in `docker.preInstall` must use `npm i -g <tool>`, not
502
546
  `pnpm add -g`: the image sets no `PNPM_HOME`, so pnpm's global bin dir is
503
547
  undefined.
504
548
 
549
+ ## Editor settings (`.vscode/settings.json`)
550
+
551
+ The base preset owns its own keys in `.vscode/settings.json` and leaves the rest of the file
552
+ to the project. Among them is i18n-ally, configured for the fleet's standard setup:
553
+ `@nuxtjs/i18n` with flat dotted keys under `i18n/locales`.
554
+
555
+ Everything the payload does not write is yours and survives a sync untouched -- your
556
+ `editor.fontSize`, your `files.exclude`, any unrelated key at all. Six of those are named in
557
+ the manifest as project-owned on top of that (`editor.fontSize`, `editor.rulers`,
558
+ `editor.formatOnSave`, `files.exclude`, `search.exclude`, `files.watcherExclude`), which
559
+ keeps them yours even if a later payload version starts setting them. Only the keys the
560
+ payload writes are reverted, and they are the ones below.
561
+
562
+ | Knob | Default | Effect |
563
+ | ---- | ------- | ------ |
564
+ | `editor.i18nSourceLanguage` | `"de"` | The locale i18n-ally translates FROM (`i18n-ally.sourceLanguage`). Payload-enforced -- see below |
565
+
566
+ ```ts
567
+ export default {
568
+ // ...
569
+ editor: { i18nSourceLanguage: "en" },
570
+ };
571
+ ```
572
+
573
+ **If your repo authors its source strings in English, set this.** The source language is
574
+ payload-owned, so leaving it unset does not mean "keep what I have" -- every sync writes
575
+ `"de"` into the file, over the top of an `"en"` you put there by hand. Nothing warns, and
576
+ `streamctl check` stays green afterwards, because the value it finds is the value the payload
577
+ intends. Setting the knob is the only thing that makes `"en"` survive a sync.
578
+
579
+ `i18n-ally.localesPaths` and `i18n-ally.keystyle` are deliberately NOT configurable: the
580
+ locales path and the flat key style are fleet-wide conventions, so they are enforced rather
581
+ than offered. Editing either one in your own `.vscode/settings.json` is reverted on the next
582
+ sync. A repo that genuinely needs a different layout should opt the file out
583
+ (`files: { ".vscode/settings.json": "off" }`) rather than fight the merge.
584
+
505
585
  ## Upgrade-PR workflow
506
586
 
507
587
  The base preset ships `.github/workflows/streamctl-upgrade.yml`, a scheduled workflow
package/dist/config.d.mts CHANGED
@@ -20,6 +20,16 @@ interface NuxtBaseCiConfig {
20
20
  unitTests?: boolean;
21
21
  /** Include the e2e job + postgres service. */
22
22
  e2e?: boolean;
23
+ /**
24
+ * System packages apt-installed in the `test` and `e2e` jobs before
25
+ * `pnpm install`, for a binary the suite needs that `ubuntu-latest` does not
26
+ * ship (the motivating case: `libxml2-utils` for `xmllint`). Empty by default,
27
+ * which renders the install step inert rather than absent. Package names only:
28
+ * each element is metacharacter-checked, then the joined string is re-validated
29
+ * by the manifest pattern, because the rendered `run:` line is deliberately
30
+ * unquoted so word splitting reaches apt-get.
31
+ */
32
+ aptPackages?: string[];
23
33
  }
24
34
  /**
25
35
  * Optional runtime version pins (`config.versions.*`). `node` feeds every
@@ -78,6 +88,11 @@ interface NuxtBaseDockerConfig {
78
88
  * this knob, so an override cannot re-root the container. Lands verbatim.
79
89
  */
80
90
  prismaRuntime?: string;
91
+ /**
92
+ * Override for the runtime Prisma CLI `ARG PRISMA_VERSION` default. Derived
93
+ * from the repo's `prisma` pin when unset.
94
+ */
95
+ prismaVersion?: string;
81
96
  /**
82
97
  * The `sh -c` argument of the final `CMD`. Defaults to
83
98
  * `prisma migrate deploy` then the node server; override e.g. for `db push`
@@ -121,6 +136,20 @@ interface NuxtBasePnpmConfig {
121
136
  */
122
137
  onlyBuiltDependencies?: string[];
123
138
  }
139
+ /**
140
+ * Optional editor knobs (`config.editor.*`) rendered into the managed
141
+ * `.vscode/settings.json`. Only the i18n-ally source language varies across the
142
+ * fleet; the locales path and key style are payload-enforced.
143
+ */
144
+ interface NuxtBaseEditorConfig {
145
+ /**
146
+ * Source locale i18n-ally translates FROM (`i18n-ally.sourceLanguage`).
147
+ * Default `"de"`, the fleet majority; repos authoring in English set `"en"`.
148
+ * A BCP-47-ish tag (`de`, `pt-BR`, `zh-Hans-CN`); the manifest's `pattern`
149
+ * rejects anything that could break the rendered JSON string.
150
+ */
151
+ i18nSourceLanguage?: string;
152
+ }
124
153
  /**
125
154
  * The typed `streamctl.config.ts` shape for the `@sidebase/base-config` payload:
126
155
  * the CLI-universal fields plus this payload's declared knobs (`ci`, `versions`,
@@ -155,6 +184,8 @@ interface NuxtBaseConfig {
155
184
  security?: NuxtBaseSecurityConfig;
156
185
  /** Optional pnpm knobs (extra build-script allowances). */
157
186
  pnpm?: NuxtBasePnpmConfig;
187
+ /** Optional editor knobs (the i18n-ally source language). */
188
+ editor?: NuxtBaseEditorConfig;
158
189
  /** ESLint factory options, typed with the payload's own option surface. */
159
190
  eslint?: CreateSidebaseEslintOptions;
160
191
  }
@@ -176,6 +207,8 @@ declare const NUXT_BASE_AUTOMATION_KEYS: string[];
176
207
  declare const NUXT_BASE_SECURITY_KEYS: string[];
177
208
  /** Runtime list of every {@link NuxtBasePnpmConfig} key. */
178
209
  declare const NUXT_BASE_PNPM_KEYS: string[];
210
+ /** Runtime list of every {@link NuxtBaseEditorConfig} key. */
211
+ declare const NUXT_BASE_EDITOR_KEYS: string[];
179
212
  /**
180
213
  * Every payload KNOB dot-path {@link NuxtBaseConfig} declares (the `ci.*` and
181
214
  * `automation.*` leaves plus the top-level knobs); excludes the CLI-universal
@@ -185,5 +218,5 @@ declare const NUXT_BASE_PNPM_KEYS: string[];
185
218
  */
186
219
  declare const NUXT_BASE_CONFIG_KEYS: string[];
187
220
 
188
- export { NUXT_BASE_AUTOMATION_KEYS, NUXT_BASE_CI_KEYS, NUXT_BASE_CONFIG_KEYS, NUXT_BASE_DOCKER_KEYS, NUXT_BASE_PNPM_KEYS, NUXT_BASE_SECURITY_KEYS, NUXT_BASE_VERSIONS_KEYS, defineNuxtBaseConfig };
189
- export type { NuxtBaseAutomationConfig, NuxtBaseCiConfig, NuxtBaseConfig, NuxtBaseDockerConfig, NuxtBasePnpmConfig, NuxtBaseSecurityConfig, NuxtBaseVersionsConfig, Profile };
221
+ export { NUXT_BASE_AUTOMATION_KEYS, NUXT_BASE_CI_KEYS, NUXT_BASE_CONFIG_KEYS, NUXT_BASE_DOCKER_KEYS, NUXT_BASE_EDITOR_KEYS, NUXT_BASE_PNPM_KEYS, NUXT_BASE_SECURITY_KEYS, NUXT_BASE_VERSIONS_KEYS, defineNuxtBaseConfig };
222
+ export type { NuxtBaseAutomationConfig, NuxtBaseCiConfig, NuxtBaseConfig, NuxtBaseDockerConfig, NuxtBaseEditorConfig, NuxtBasePnpmConfig, NuxtBaseSecurityConfig, NuxtBaseVersionsConfig, Profile };
package/dist/config.d.ts CHANGED
@@ -20,6 +20,16 @@ interface NuxtBaseCiConfig {
20
20
  unitTests?: boolean;
21
21
  /** Include the e2e job + postgres service. */
22
22
  e2e?: boolean;
23
+ /**
24
+ * System packages apt-installed in the `test` and `e2e` jobs before
25
+ * `pnpm install`, for a binary the suite needs that `ubuntu-latest` does not
26
+ * ship (the motivating case: `libxml2-utils` for `xmllint`). Empty by default,
27
+ * which renders the install step inert rather than absent. Package names only:
28
+ * each element is metacharacter-checked, then the joined string is re-validated
29
+ * by the manifest pattern, because the rendered `run:` line is deliberately
30
+ * unquoted so word splitting reaches apt-get.
31
+ */
32
+ aptPackages?: string[];
23
33
  }
24
34
  /**
25
35
  * Optional runtime version pins (`config.versions.*`). `node` feeds every
@@ -78,6 +88,11 @@ interface NuxtBaseDockerConfig {
78
88
  * this knob, so an override cannot re-root the container. Lands verbatim.
79
89
  */
80
90
  prismaRuntime?: string;
91
+ /**
92
+ * Override for the runtime Prisma CLI `ARG PRISMA_VERSION` default. Derived
93
+ * from the repo's `prisma` pin when unset.
94
+ */
95
+ prismaVersion?: string;
81
96
  /**
82
97
  * The `sh -c` argument of the final `CMD`. Defaults to
83
98
  * `prisma migrate deploy` then the node server; override e.g. for `db push`
@@ -121,6 +136,20 @@ interface NuxtBasePnpmConfig {
121
136
  */
122
137
  onlyBuiltDependencies?: string[];
123
138
  }
139
+ /**
140
+ * Optional editor knobs (`config.editor.*`) rendered into the managed
141
+ * `.vscode/settings.json`. Only the i18n-ally source language varies across the
142
+ * fleet; the locales path and key style are payload-enforced.
143
+ */
144
+ interface NuxtBaseEditorConfig {
145
+ /**
146
+ * Source locale i18n-ally translates FROM (`i18n-ally.sourceLanguage`).
147
+ * Default `"de"`, the fleet majority; repos authoring in English set `"en"`.
148
+ * A BCP-47-ish tag (`de`, `pt-BR`, `zh-Hans-CN`); the manifest's `pattern`
149
+ * rejects anything that could break the rendered JSON string.
150
+ */
151
+ i18nSourceLanguage?: string;
152
+ }
124
153
  /**
125
154
  * The typed `streamctl.config.ts` shape for the `@sidebase/base-config` payload:
126
155
  * the CLI-universal fields plus this payload's declared knobs (`ci`, `versions`,
@@ -155,6 +184,8 @@ interface NuxtBaseConfig {
155
184
  security?: NuxtBaseSecurityConfig;
156
185
  /** Optional pnpm knobs (extra build-script allowances). */
157
186
  pnpm?: NuxtBasePnpmConfig;
187
+ /** Optional editor knobs (the i18n-ally source language). */
188
+ editor?: NuxtBaseEditorConfig;
158
189
  /** ESLint factory options, typed with the payload's own option surface. */
159
190
  eslint?: CreateSidebaseEslintOptions;
160
191
  }
@@ -176,6 +207,8 @@ declare const NUXT_BASE_AUTOMATION_KEYS: string[];
176
207
  declare const NUXT_BASE_SECURITY_KEYS: string[];
177
208
  /** Runtime list of every {@link NuxtBasePnpmConfig} key. */
178
209
  declare const NUXT_BASE_PNPM_KEYS: string[];
210
+ /** Runtime list of every {@link NuxtBaseEditorConfig} key. */
211
+ declare const NUXT_BASE_EDITOR_KEYS: string[];
179
212
  /**
180
213
  * Every payload KNOB dot-path {@link NuxtBaseConfig} declares (the `ci.*` and
181
214
  * `automation.*` leaves plus the top-level knobs); excludes the CLI-universal
@@ -185,5 +218,5 @@ declare const NUXT_BASE_PNPM_KEYS: string[];
185
218
  */
186
219
  declare const NUXT_BASE_CONFIG_KEYS: string[];
187
220
 
188
- export { NUXT_BASE_AUTOMATION_KEYS, NUXT_BASE_CI_KEYS, NUXT_BASE_CONFIG_KEYS, NUXT_BASE_DOCKER_KEYS, NUXT_BASE_PNPM_KEYS, NUXT_BASE_SECURITY_KEYS, NUXT_BASE_VERSIONS_KEYS, defineNuxtBaseConfig };
189
- export type { NuxtBaseAutomationConfig, NuxtBaseCiConfig, NuxtBaseConfig, NuxtBaseDockerConfig, NuxtBasePnpmConfig, NuxtBaseSecurityConfig, NuxtBaseVersionsConfig, Profile };
221
+ export { NUXT_BASE_AUTOMATION_KEYS, NUXT_BASE_CI_KEYS, NUXT_BASE_CONFIG_KEYS, NUXT_BASE_DOCKER_KEYS, NUXT_BASE_EDITOR_KEYS, NUXT_BASE_PNPM_KEYS, NUXT_BASE_SECURITY_KEYS, NUXT_BASE_VERSIONS_KEYS, defineNuxtBaseConfig };
222
+ export type { NuxtBaseAutomationConfig, NuxtBaseCiConfig, NuxtBaseConfig, NuxtBaseDockerConfig, NuxtBaseEditorConfig, NuxtBasePnpmConfig, NuxtBaseSecurityConfig, NuxtBaseVersionsConfig, Profile };
package/dist/config.mjs CHANGED
@@ -6,7 +6,8 @@ function knobKeys(shape) {
6
6
  }
7
7
  const NUXT_BASE_CI_KEYS = knobKeys({
8
8
  unitTests: 0,
9
- e2e: 0
9
+ e2e: 0,
10
+ aptPackages: 0
10
11
  });
11
12
  const NUXT_BASE_VERSIONS_KEYS = knobKeys({
12
13
  node: 0,
@@ -19,6 +20,7 @@ const NUXT_BASE_DOCKER_KEYS = knobKeys({
19
20
  buildSteps: 0,
20
21
  finalStage: 0,
21
22
  prismaRuntime: 0,
23
+ prismaVersion: 0,
22
24
  startCommand: 0
23
25
  });
24
26
  const NUXT_BASE_AUTOMATION_KEYS = knobKeys({
@@ -30,6 +32,9 @@ const NUXT_BASE_SECURITY_KEYS = knobKeys({
30
32
  const NUXT_BASE_PNPM_KEYS = knobKeys({
31
33
  onlyBuiltDependencies: 0
32
34
  });
35
+ const NUXT_BASE_EDITOR_KEYS = knobKeys({
36
+ i18nSourceLanguage: 0
37
+ });
33
38
  const NUXT_BASE_TOP_LEVEL_KNOBS = knobKeys({
34
39
  eslint: 0
35
40
  });
@@ -40,7 +45,8 @@ const NUXT_BASE_CONFIG_KEYS = [
40
45
  ...NUXT_BASE_AUTOMATION_KEYS.map((key) => `automation.${key}`),
41
46
  ...NUXT_BASE_SECURITY_KEYS.map((key) => `security.${key}`),
42
47
  ...NUXT_BASE_PNPM_KEYS.map((key) => `pnpm.${key}`),
48
+ ...NUXT_BASE_EDITOR_KEYS.map((key) => `editor.${key}`),
43
49
  ...NUXT_BASE_TOP_LEVEL_KNOBS
44
50
  ];
45
51
 
46
- export { NUXT_BASE_AUTOMATION_KEYS, NUXT_BASE_CI_KEYS, NUXT_BASE_CONFIG_KEYS, NUXT_BASE_DOCKER_KEYS, NUXT_BASE_PNPM_KEYS, NUXT_BASE_SECURITY_KEYS, NUXT_BASE_VERSIONS_KEYS, defineNuxtBaseConfig };
52
+ export { NUXT_BASE_AUTOMATION_KEYS, NUXT_BASE_CI_KEYS, NUXT_BASE_CONFIG_KEYS, NUXT_BASE_DOCKER_KEYS, NUXT_BASE_EDITOR_KEYS, NUXT_BASE_PNPM_KEYS, NUXT_BASE_SECURITY_KEYS, NUXT_BASE_VERSIONS_KEYS, defineNuxtBaseConfig };
package/docs/migration.md CHANGED
@@ -35,11 +35,12 @@ order, so the second one is the binding constraint:
35
35
  The widening came after the guard, so **a build containing the widening also contains the
36
36
  guard, and the widening is the effective minimum.** Requiring it is sufficient.
37
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.
38
+ > **Minimum version: `0.3.0`.**
39
+ > The binding constraint is the payload manifest's `schemaVersion: 3` (the
40
+ > `PRISMA_VERSION_DEFAULT` placeholder uses `fromDependency`, a schema-3 feature): a 0.2.x
41
+ > CLI rejects the whole payload with `SCHEMA_UNSUPPORTED` ("Upgrade the CLI or the
42
+ > payload"). 0.2.0's own prerequisites (both above, plus reading `streamctl.config.ts`
43
+ > from the repo root) are of course included.
43
44
 
44
45
  ### Verify your CLI has both, without needing the version number
45
46
 
@@ -170,7 +171,7 @@ harder to tell which change caused what.
170
171
  Do this BEFORE touching the config.
171
172
 
172
173
  ```sh
173
- pnpm add -D @sidebase/streamctl@^0.2.0
174
+ pnpm add -D @sidebase/streamctl@^0.3.0
174
175
  ```
175
176
 
176
177
  ### 3. Swap the payload package
@@ -356,14 +357,13 @@ pnpm install
356
357
  migrating from `@sidestream-tech/nuxt-config` it reports something like:
357
358
 
358
359
  ```
359
- [!] adoption (4) pre-existing file(s) streamctl now manages
360
+ [!] adoption (3) pre-existing file(s) streamctl now manages
360
361
  tsconfig.json - pre-existing file streamctl now manages; adopting is expected
361
- .dockerignore - pre-existing file streamctl now manages; adopting is expected
362
362
  pnpm-workspace.yaml - pre-existing file streamctl now manages; adopting is expected
363
363
  Dockerfile - pre-existing file streamctl now manages; adopting is expected
364
364
  -> `sync --interactive` to adopt per file, `sync --force` to take ownership, or `--only <glob>` to scope
365
365
 
366
- 4 conflict(s) pending; see the per-kind guidance above.
366
+ 3 conflict(s) pending; see the per-kind guidance above.
367
367
  ```
368
368
 
369
369
  This is not a failure and it is not something to route around. A fully-managed file whose
@@ -539,6 +539,68 @@ strategy only inspects its own markers, and the block is present and correct; an
539
539
  outside it is invisible to the check. That is why this is a manual step rather than
540
540
  something drift detection reports.
541
541
 
542
+ ### 8b. Reconcile `.dockerignore` by hand
543
+
544
+ **Same cause as step 8, and equally not migration-specific.** `.dockerignore` is block-managed
545
+ too, so on first sync the payload appends its block to whatever your repository already had.
546
+ Your existing lines stay above it. Nothing is overwritten and nothing is reported.
547
+
548
+ Most of the leftovers are harmless. Ignore patterns are additive, so a duplicated `node_modules`
549
+ or `.env` above the block is genuinely inert -- it changes nothing about what the build context
550
+ carries. Delete them anyway, for a reason that is not tidiness: the detector below is the only
551
+ thing that will ever tell you a payload entry has started shadowing something of yours, and it
552
+ is not a one-shot check. A future payload version can add an entry that collides with a line you
553
+ kept above the block, and a file with leftover duplicates reads `AFFECTED` forever, so it can no
554
+ longer distinguish that from the noise. Clearing them is what keeps the check able to answer next
555
+ time.
556
+
557
+ **The case that bites is a negation.** `.dockerignore` is last-match-wins, and the managed block
558
+ is appended at the BOTTOM, so every payload entry beats anything you wrote above it. A repository
559
+ that deliberately un-ignored something now silently loses that:
560
+
561
+ ```
562
+ !tests <- yours, above the block. Now dead.
563
+
564
+ # BEGIN streamctl MANAGED BLOCK dockerignore
565
+ ...
566
+ tests <- payload's, below yours. Wins.
567
+ # END streamctl MANAGED BLOCK dockerignore
568
+ ```
569
+
570
+ The build context stops carrying `tests/`, and the failure surfaces later and somewhere else --
571
+ as a `COPY` that lands nothing, or an in-image test run that cannot find its fixtures. The build
572
+ itself succeeds.
573
+
574
+ Check what sits above the block:
575
+
576
+ ```sh
577
+ awk '/# BEGIN streamctl MANAGED BLOCK dockerignore/{exit} {print}' .dockerignore \
578
+ | command grep -qv '^[[:space:]]*\(#.*\)\?$' && echo AFFECTED || echo clean
579
+ ```
580
+
581
+ As in step 8, use `command grep` -- the detector reads its exit code as its entire answer, and a
582
+ wrapped `grep` can report `clean` on an affected file.
583
+
584
+ `AFFECTED` does not mean something broke; it means read those lines and decide. Anything you
585
+ actually need, and every negation without exception, moves **below** the `# END` marker, which is
586
+ the supported extension point:
587
+
588
+ ```
589
+ # END streamctl MANAGED BLOCK dockerignore
590
+
591
+ !tests
592
+ my-project-specific-dir
593
+ ```
594
+
595
+ Finish by re-running the `AFFECTED`/`clean` detector above; it only says `clean` once nothing but
596
+ comments and blank lines is left above the block, so clear the harmless duplicates out too rather
597
+ than keeping a detector that can never go green.
598
+
599
+ **`streamctl check` will never flag any of this.** Block drift detection inspects only the marker
600
+ region, so the block is present and correct and the check exits 0 while your dead negation sits
601
+ three lines above it, permanently. This is the one step here that no tooling will ever remind you
602
+ to do.
603
+
542
604
  ### 9. Clean up what the payload no longer manages
543
605
 
544
606
  Two different kinds of leftover, with the same cause: streamctl manages by path, so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sidebase/base-config",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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",
@@ -52,6 +52,16 @@
52
52
  "engines": {
53
53
  "node": ">=22"
54
54
  },
55
+ "packageManager": "pnpm@10.29.1",
56
+ "scripts": {
57
+ "build": "unbuild",
58
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc -p tsconfig.test.json",
59
+ "test": "vitest run",
60
+ "lint": "eslint .",
61
+ "validate:presets": "node scripts/validate-presets.mjs",
62
+ "e2e:dry-run": "pnpm build && node scripts/e2e-dry-run.mjs",
63
+ "prepack": "pnpm build"
64
+ },
55
65
  "dependencies": {
56
66
  "@antfu/eslint-config": "^7.4",
57
67
  "ufo": "^1.6.4"
@@ -72,7 +82,7 @@
72
82
  "devDependencies": {
73
83
  "@arethetypeswrong/cli": "^0.18.5",
74
84
  "@prisma/client": "^6.19.3",
75
- "@sidebase/streamctl": "^0.2.0",
85
+ "@sidebase/streamctl": "^0.3.0",
76
86
  "@types/node": "^24.0.0",
77
87
  "eslint": "^10.5.0",
78
88
  "jiti": "^2.7.0",
@@ -82,13 +92,5 @@
82
92
  "unbuild": "^3.5.0",
83
93
  "vitest": "^3.0.0",
84
94
  "yaml": "^2.9.0"
85
- },
86
- "scripts": {
87
- "build": "unbuild",
88
- "typecheck": "tsc --noEmit -p tsconfig.json && tsc -p tsconfig.test.json",
89
- "test": "vitest run",
90
- "lint": "eslint .",
91
- "validate:presets": "node scripts/validate-presets.mjs",
92
- "e2e:dry-run": "pnpm build && node scripts/e2e-dry-run.mjs"
93
95
  }
94
- }
96
+ }
@@ -6,6 +6,7 @@ npm-debug*
6
6
  .output
7
7
  .data
8
8
  dist
9
+ test
9
10
  tests
10
11
  *.log
11
12
  .env
@@ -16,3 +17,11 @@ tests
16
17
  # is intentionally allowed through.
17
18
  .npmrc.*
18
19
  .DS_Store
20
+ # Local dev state written by `pnpm db` / `docker compose up`. `prisma/pglite-data`
21
+ # is tens of MB; `prisma/local.postgres_data` is root-owned, which fails a rootless
22
+ # `docker build` outright. Patterns are anchored to the context root here (unlike
23
+ # .gitignore), so `**/local.*` is required to reach the nested paths.
24
+ prisma/client
25
+ prisma/pglite-data
26
+ pgliteHealthz
27
+ **/local.*
@@ -24,6 +24,7 @@
24
24
  "max-depth": "off",
25
25
  "max-nested-callbacks": "off",
26
26
  "max-lines": "off",
27
+ "no-underscore-dangle": "off",
27
28
  "no-warning-comments": "off",
28
29
  "no-inline-comments": "off",
29
30
  "no-map-spread": "off"
@@ -3,11 +3,11 @@
3
3
  "files": [
4
4
  { "path": ".editorconfig", "strategy": "block", "source": "base/editorconfig", "blockMark": "editorconfig" },
5
5
  { "path": "tsconfig.json", "strategy": "full", "source": "base/tsconfig.json", "adoption": "expected" },
6
- { "path": ".dockerignore", "strategy": "full", "source": "base/dockerignore", "adoption": "expected" },
6
+ { "path": ".dockerignore", "strategy": "block", "source": "base/dockerignore", "blockMark": "dockerignore" },
7
7
  { "path": "pnpm-workspace.yaml", "strategy": "full", "source": "base/pnpm-workspace.yaml", "render": "security", "adoption": "expected" },
8
8
  { "path": ".gitignore", "strategy": "block", "source": "base/gitignore", "blockMark": "core" },
9
9
  { "path": ".oxlintrc.json", "strategy": "full", "source": "base/oxlintrc.json", "adoption": "expected" },
10
- { "path": ".vscode/settings.json", "strategy": "merge", "source": "base/vscode/settings.json", "projectFields": ["editor.fontSize", "editor.rulers", "editor.formatOnSave", "files.exclude", "search.exclude", "files.watcherExclude"] },
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
13
  { "path": "CLAUDE.md", "strategy": "scaffold", "source": "base/CLAUDE.md", "adoption": "expected" },
@@ -19,6 +19,11 @@
19
19
  "NODE_VERSION": { "configPath": "versions.node", "default": "24.13.0", "pattern": "^[\\w.+-]+$" }
20
20
  }
21
21
  },
22
+ "editor": {
23
+ "placeholders": {
24
+ "I18N_SOURCE_LANGUAGE": { "configPath": "editor.i18nSourceLanguage", "default": "de", "pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$" }
25
+ }
26
+ },
22
27
  "security": {
23
28
  "placeholders": {
24
29
  "MIN_RELEASE_AGE": { "configPath": "security.minimumReleaseAge", "default": "10080", "pattern": "^\\d+$" }
@@ -30,6 +35,7 @@
30
35
  },
31
36
  "configKeys": {
32
37
  "automation.upgradePr": "boolean",
38
+ "editor.i18nSourceLanguage": "string",
33
39
  "security.minimumReleaseAge": "string",
34
40
  "pnpm.onlyBuiltDependencies": "string[]",
35
41
  "versions.node": "string",
@@ -39,5 +39,11 @@
39
39
  "toml",
40
40
  "gql",
41
41
  "graphql"
42
- ]
42
+ ],
43
+
44
+ // i18n-ally: the fleet standard is @nuxtjs/i18n with flat dotted keys in
45
+ // i18n/locales. sourceLanguage is the one field that varies, so it is a knob.
46
+ "i18n-ally.localesPaths": ["i18n/locales"],
47
+ "i18n-ally.keystyle": "flat",
48
+ "i18n-ally.sourceLanguage": "${I18N_SOURCE_LANGUAGE}"
43
49
  }
@@ -9,10 +9,11 @@ export default defineNuxtBaseConfig({
9
9
  profile: '__PROFILE__',
10
10
 
11
11
  // Optional knobs, uncomment to enable:
12
- // ci: { unitTests: true, e2e: true },
12
+ // ci: { unitTests: true, e2e: true, aptPackages: ['libxml2-utils'] }, // apt extras for the test/e2e jobs
13
13
  // versions: { node: '24.13.0', pnpm: '10.28.1' },
14
14
  // docker: { aptPackages: ['ffmpeg'] }, // extras; openssl is always installed
15
15
  // security: { minimumReleaseAge: '10080' }, // minutes before pnpm installs a release; '0' disables
16
16
  // pnpm: { onlyBuiltDependencies: ['sharp'] }, // extras on top of prisma + esbuild
17
+ // editor: { i18nSourceLanguage: 'en' }, // i18n-ally source language (default: 'de')
17
18
  // eslint: { trpcGuard: true },
18
19
  })
@@ -1,5 +1,5 @@
1
1
  {
2
- "schemaVersion": 2,
2
+ "schemaVersion": 3,
3
3
  "presets": [
4
4
  "base",
5
5
  "nuxt-app"
@@ -31,7 +31,7 @@ ${DOCKER_BUILD_ARGS}
31
31
  # copy the app, note .dockerignore
32
32
  COPY . .
33
33
 
34
- # split commands into seperate RUN to not invalidate entire cache layers
34
+ # split commands into separate RUN to not invalidate entire cache layers
35
35
  ${DOCKER_BUILD_STEPS}
36
36
 
37
37
  # --- FINAL PRODUCTION STAGE ---
@@ -19,6 +19,9 @@ concurrency:
19
19
 
20
20
  env:
21
21
  NODE_VERSION: "${NODE_VERSION}"
22
+ # Extra apt packages for the test/e2e jobs (ci.aptPackages). Empty by default;
23
+ # the install step is skipped when unset.
24
+ CI_APT_PACKAGES: "${CI_APT_PACKAGES}"
22
25
 
23
26
  jobs:
24
27
  lint-typecheck:
@@ -28,14 +28,14 @@ jobs:
28
28
  const { owner, repo } = context.repo;
29
29
  const issue_number = context.issue.number;
30
30
 
31
- // check first if the label exists on the PR
32
- const { data: labels } = await github.rest.issues.listLabelsOnIssue({
33
- owner,
34
- repo,
35
- issue_number
36
- });
31
+ // check first if the label exists on the PR
32
+ const { data: labels } = await github.rest.issues.listLabelsOnIssue({
33
+ owner,
34
+ repo,
35
+ issue_number
36
+ });
37
37
 
38
- const hasPreviewDeployedLabel = labels.some(label => label.name === 'preview-deployed');
39
- if (hasPreviewDeployedLabel) {
40
- await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'preview-deployed'});
41
- }
38
+ const hasPreviewDeployedLabel = labels.some(label => label.name === 'preview-deployed');
39
+ if (hasPreviewDeployedLabel) {
40
+ await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'preview-deployed'});
41
+ }
@@ -12,6 +12,7 @@
12
12
  "renders": {
13
13
  "ci": {
14
14
  "placeholders": {
15
+ "CI_APT_PACKAGES": { "configPath": "ci.aptPackages", "default": "", "pattern": "^$|^[a-z0-9][a-z0-9 +.-]*$", "join": "space" },
15
16
  "NODE_VERSION": { "configPath": "versions.node", "default": "24.13.0", "pattern": "^[\\w.+-]+$" }
16
17
  },
17
18
  "fragments": [
@@ -28,7 +29,8 @@
28
29
  "DOCKER_BUILD_ARGS": { "configPath": "docker.buildArgs", "default": "" },
29
30
  "DOCKER_BUILD_STEPS": { "configPath": "docker.buildSteps", "default": "RUN pnpm nuxi prepare\nRUN pnpm prisma generate\nRUN pnpm run build" },
30
31
  "DOCKER_FINAL_STAGE": { "configPath": "docker.finalStage", "default": "" },
31
- "DOCKER_PRISMA_RUNTIME": { "configPath": "docker.prismaRuntime", "default": "COPY --chown=node:node --from=production-base /app/prisma /app/prisma\n\n# Prisma CLI version for `migrate deploy`. Defaults to the baseline; override with --build-arg.\nARG PRISMA_VERSION=6.19.1\n\n# install prisma to run the `migrate deploy`. Runs as root because /app is\n# root-owned from WORKDIR; hand the install output to the runtime user in the\n# same layer so no later chown duplicates node_modules.\nRUN npm i -D prisma@${PRISMA_VERSION} && chown -R node:node /app/node_modules" },
32
+ "DOCKER_PRISMA_RUNTIME": { "configPath": "docker.prismaRuntime", "default": "COPY --chown=node:node --from=production-base /app/prisma /app/prisma\n\n# Prisma CLI version for `migrate deploy`. Defaults to the baseline; override with --build-arg.\nARG PRISMA_VERSION=${PRISMA_VERSION_DEFAULT}\n\n# install prisma to run the `migrate deploy`. Runs as root because /app is\n# root-owned from WORKDIR; hand the install output to the runtime user in the\n# same layer so no later chown duplicates node_modules.\nRUN npm i -D prisma@${PRISMA_VERSION} && chown -R node:node /app/node_modules" },
33
+ "PRISMA_VERSION_DEFAULT": { "configPath": "docker.prismaVersion", "fromDependency": "prisma", "default": "6.19.1", "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$" },
32
34
  "DOCKER_START_COMMAND": { "configPath": "docker.startCommand", "default": "npm exec prisma migrate deploy && node --max-old-space-size-percentage=75 .output/server/index.mjs" }
33
35
  },
34
36
  "passthrough": ["PRISMA_VERSION"]
@@ -37,12 +39,14 @@
37
39
  "configKeys": {
38
40
  "ci.unitTests": "boolean",
39
41
  "ci.e2e": "boolean",
42
+ "ci.aptPackages": "string[]",
40
43
  "docker.aptPackages": "string[]",
41
44
  "docker.preInstall": "string",
42
45
  "docker.buildArgs": "string",
43
46
  "docker.buildSteps": "string",
44
47
  "docker.finalStage": "string",
45
48
  "docker.prismaRuntime": "string",
49
+ "docker.prismaVersion": "string",
46
50
  "docker.startCommand": "string",
47
51
  "eslint": "object"
48
52
  },
@@ -23,6 +23,9 @@
23
23
  --health-retries 5
24
24
  steps:
25
25
  - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
26
+ - name: Install apt packages
27
+ if: ${{ env.CI_APT_PACKAGES != '' }}
28
+ run: sudo apt-get update && sudo apt-get install -y --no-install-recommends $CI_APT_PACKAGES
26
29
  - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
27
30
  - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
28
31
  with:
@@ -2,6 +2,9 @@
2
2
  runs-on: ubuntu-latest
3
3
  steps:
4
4
  - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
5
+ - name: Install apt packages
6
+ if: ${{ env.CI_APT_PACKAGES != '' }}
7
+ run: sudo apt-get update && sudo apt-get install -y --no-install-recommends $CI_APT_PACKAGES
5
8
  - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
6
9
  - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
7
10
  with: