@sidebase/base-config 0.3.0 → 0.3.1
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 +165 -591
- package/dist/config.d.mts +1 -1
- package/dist/config.d.ts +1 -1
- package/package.json +1 -1
- package/presets/base/github/workflows/streamctl-upgrade.yml +4 -2
- package/presets/base/preset.json +1 -1
package/README.md
CHANGED
|
@@ -1,237 +1,161 @@
|
|
|
1
1
|
# @sidebase/base-config
|
|
2
2
|
|
|
3
|
-
Shared
|
|
3
|
+
Shared setup for sidebase Nuxt 4 apps. [`@sidebase/streamctl`](https://github.com/sidebase/streamctl)
|
|
4
|
+
syncs this package's config files (CI, Dockerfile, tsconfig, lint config, editor settings, ...) into your
|
|
5
|
+
repo and keeps them up to date. The package also exports ESLint, Prisma and tsconfig helpers.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
`@sidebase/base-config/config`, `@sidebase/base-config/eslint`,
|
|
7
|
-
`@sidebase/base-config/prisma`, and `@sidebase/base-config/tsconfig.base`.
|
|
8
|
-
- file-sync channel: the bundled `presets/` payload read by the [`@sidebase/streamctl`](https://github.com/sidebase/streamctl) CLI.
|
|
7
|
+
## Quick start
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
For the npm channel, install the package plus what the subpaths you use require:
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
pnpm add -D @sidebase/base-config eslint jiti
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
`eslint` (`^10.5.0`) and `jiti` (`>= 2.2.0`) are what `/eslint` needs; jiti is only
|
|
19
|
-
required because the config file is TypeScript. `prisma` and `@prisma/client` (`^6.19`)
|
|
20
|
-
are optional peers, install them only if you use `/prisma`.
|
|
21
|
-
|
|
22
|
-
**`jiti` is not a declared peer.** `peerDependencies` is `eslint`, `prisma` and
|
|
23
|
-
`@prisma/client`. It is in the command above because `/eslint` genuinely needs it. Since
|
|
24
|
-
nothing declares it, pnpm emits no missing-peer warning if you leave it out. This README is
|
|
25
|
-
the only thing that will tell you. See Requirements below.
|
|
26
|
-
|
|
27
|
-
For the file-sync channel, see Adopting below. The CLI wires the dependency itself.
|
|
28
|
-
|
|
29
|
-
## Adopting
|
|
30
|
-
|
|
31
|
-
The payload *is* the install: [`@sidebase/streamctl`](https://github.com/sidebase/streamctl)
|
|
32
|
-
reads `presets/` straight from this package in a consuming repo's `node_modules`.
|
|
9
|
+
Requires Nuxt 4, Node >= 24.21 and pnpm.
|
|
33
10
|
|
|
34
11
|
```sh
|
|
35
|
-
pnpm
|
|
36
|
-
pnpm streamctl
|
|
37
|
-
pnpm streamctl upgrade --to <version> # move the pin forward
|
|
12
|
+
pnpm add -D @sidebase/streamctl
|
|
13
|
+
pnpm streamctl init --package @sidebase/base-config
|
|
38
14
|
```
|
|
39
15
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Coming from `@sidestream-tech/nuxt-config`? That rename is a manual migration, not an
|
|
44
|
-
upgrade: `streamctl upgrade` moves the version pin and never rewrites `package:`. Follow
|
|
45
|
-
[`docs/migration.md`](docs/migration.md), and note that the step order is load-bearing.
|
|
46
|
-
|
|
47
|
-
### Presets and profiles
|
|
48
|
-
|
|
49
|
-
Two separate axes in `streamctl.config.ts`:
|
|
50
|
-
|
|
51
|
-
- `base` is the preset, the set of managed files you get. `nuxt-app` names the repo
|
|
52
|
-
*type*: it ships a Dockerfile, a postgres/prisma CI pipeline, and PR-preview cleanup.
|
|
53
|
-
- `profile` is the version baseline, the dependency pins reconciled into your
|
|
54
|
-
`package.json`. It names the framework and its major: `nuxt-4`.
|
|
55
|
-
|
|
56
|
-
The major is in the profile name on purpose. Each profile carries its own
|
|
57
|
-
`detect.majorIs`, so `init` picks the right one from your `package.json` and a future
|
|
58
|
-
`nuxt-5` profile can sit alongside `nuxt-4` while repos migrate one at a time.
|
|
16
|
+
`init` adds `@sidebase/base-config`, writes `streamctl.config.ts`, and runs the first sync.
|
|
17
|
+
Review the changes and commit them.
|
|
59
18
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"No version profile could be detected", because no profile's `detect.majorIs` matches the
|
|
63
|
-
`nuxt` version in your `package.json`. Move the repo to Nuxt 4 before adopting.
|
|
19
|
+
Coming from `@sidestream-tech/nuxt-config`? Follow [`docs/migration.md`](docs/migration.md)
|
|
20
|
+
instead.
|
|
64
21
|
|
|
65
|
-
|
|
66
|
-
CLI fails with `CONFIG_INVALID`: `profile "..." is not declared in presets/manifest.json
|
|
67
|
-
profiles[]`. `pnpm validate:presets` catches the same mismatch at build time, so a broken
|
|
68
|
-
profile name cannot ship in the first place.
|
|
22
|
+
## What you get
|
|
69
23
|
|
|
70
|
-
|
|
24
|
+
| File | Managed as | What it is |
|
|
25
|
+
| ---- | ---------- | ---------- |
|
|
26
|
+
| `.github/workflows/ci.yml` | owned | Lint, typecheck and build, plus optional test and e2e jobs |
|
|
27
|
+
| `.github/workflows/pr-preview-cleanup.yml` | owned | Removes the `preview-deployed` label when `preview` is removed |
|
|
28
|
+
| `.github/workflows/streamctl-upgrade.yml` | owned, opt-in | Weekly upgrade PRs (see [Upgrades](#upgrades)) |
|
|
29
|
+
| `Dockerfile` | owned | Production image, runs `prisma migrate deploy` and then the server |
|
|
30
|
+
| `tsconfig.json` | owned | Extends the shared strict base and Nuxt's generated config |
|
|
31
|
+
| `pnpm-workspace.yaml` | owned | 7-day release cooldown and the install-script allowlist |
|
|
32
|
+
| `.oxlintrc.json` | owned | oxlint rules, run before ESLint |
|
|
33
|
+
| `AGENTS.md` | owned | Conventions for coding agents |
|
|
34
|
+
| `.editorconfig`, `.gitignore`, `.dockerignore` | block | A marked block; the rest of the file is yours |
|
|
35
|
+
| `.vscode/settings.json`, `.vscode/extensions.json` | merged | Only the keys the payload sets; your other keys stay |
|
|
36
|
+
| `eslint.config.ts`, `prisma.config.ts` | scaffolded | Written once, then yours |
|
|
71
37
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
38
|
+
- **owned:** streamctl writes the whole file. Local edits show up as drift in `check`.
|
|
39
|
+
- **block:** only the region between the `BEGIN`/`END streamctl MANAGED BLOCK` markers is
|
|
40
|
+
managed.
|
|
75
41
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
42
|
+
`sync` also updates `package.json`:
|
|
43
|
+
- `engines`, `packageManager`, and the version ranges of nuxt, typescript, eslint, oxlint,
|
|
44
|
+
prisma, `@prisma/client`, jiti, json-sort-cli and vitest. A range is only raised, never
|
|
45
|
+
lowered.
|
|
46
|
+
- `scripts.postinstall` (`nuxt prepare`) and `scripts.lint` (oxlint, then ESLint).
|
|
81
47
|
|
|
82
|
-
|
|
83
|
-
export default {
|
|
84
|
-
// ...
|
|
85
|
-
versionSyncExclude: ["scripts.lint"],
|
|
86
|
-
};
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
#### Overriding a managed file
|
|
90
|
-
|
|
91
|
-
`.editorconfig` is **block**-managed: the payload owns a marked region, delimited by
|
|
92
|
-
`# BEGIN streamctl MANAGED BLOCK editorconfig` / `# END ...`, and preserves everything
|
|
93
|
-
outside it byte-for-byte. On first sync the managed block is appended to the end of your
|
|
94
|
-
file. Because EditorConfig applies later matching sections over earlier ones, you keep a
|
|
95
|
-
project override by placing it **below** the managed block:
|
|
48
|
+
## Everyday use
|
|
96
49
|
|
|
97
|
-
```
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
# END streamctl MANAGED BLOCK editorconfig
|
|
102
|
-
|
|
103
|
-
# your override wins because it comes after the managed block
|
|
104
|
-
[*]
|
|
105
|
-
indent_size = 4
|
|
50
|
+
```sh
|
|
51
|
+
pnpm streamctl sync # apply the payload after changing the config
|
|
52
|
+
pnpm streamctl check # CI gate: fails when managed files drifted
|
|
53
|
+
pnpm streamctl upgrade # move to the latest payload version and re-sync
|
|
106
54
|
```
|
|
107
55
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
`indent_size = 2`, so keep overrides below the `END` marker.)
|
|
56
|
+
`sync` stops on conflicts, e.g. a file you edited by hand. Resolve them with
|
|
57
|
+
`pnpm streamctl sync --interactive`.
|
|
111
58
|
|
|
112
|
-
|
|
113
|
-
without looking at what is there, so all of your existing content ends up *above* the block
|
|
114
|
-
and therefore loses. `streamctl check` exits 0 either way, because the block strategy only
|
|
115
|
-
inspects its own markers. This applies to a fresh `streamctl init` just as much as to a
|
|
116
|
-
migration, and you have to fix it by hand once. Step 8 of
|
|
117
|
-
[`docs/migration.md`](docs/migration.md) has the detector and the two-case fix.
|
|
59
|
+
### Upgrades
|
|
118
60
|
|
|
119
|
-
|
|
61
|
+
Upgrade by hand with `pnpm streamctl upgrade`, or let a weekly workflow open the PRs:
|
|
120
62
|
|
|
121
63
|
```ts
|
|
122
|
-
|
|
64
|
+
automation: { upgradePr: true },
|
|
123
65
|
```
|
|
124
66
|
|
|
125
|
-
The
|
|
126
|
-
|
|
127
|
-
format corrections. Prefer the block override above when you only need to change part of
|
|
128
|
-
a file.
|
|
129
|
-
|
|
130
|
-
## This repo's CLI dependency
|
|
67
|
+
The workflow runs `streamctl check`, lint, typecheck, test and build before it opens the PR.
|
|
68
|
+
Files that need manual work end up in a draft PR with instructions.
|
|
131
69
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
`
|
|
135
|
-
|
|
70
|
+
By default it uses `GITHUB_TOKEN`, which may not change `.github/workflows/**`, so
|
|
71
|
+
workflow changes are left out of the PR. To include them, set
|
|
72
|
+
`automation.allowWorkflowUpdates: true` and add a `STREAMCTL_PR_TOKEN` secret (a
|
|
73
|
+
fine-grained PAT or GitHub App token with Contents, Pull requests and Workflows write).
|
|
74
|
+
That token also makes the PR trigger your normal CI.
|
|
136
75
|
|
|
137
|
-
|
|
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.
|
|
76
|
+
## Configuration
|
|
141
77
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
The root export `@sidebase/base-config` and the subpath `@sidebase/base-config/config`
|
|
145
|
-
are the same module. Both provide `defineNuxtBaseConfig` and the `NuxtBaseConfig` type,
|
|
146
|
-
which is what makes a `streamctl.config.ts` typed. Every consuming repo has this file,
|
|
147
|
-
and `streamctl init` scaffolds it importing from the root export:
|
|
78
|
+
Everything is configured in `streamctl.config.ts`. `defineNuxtBaseConfig` gives you
|
|
79
|
+
completion and type checking for every option:
|
|
148
80
|
|
|
149
81
|
```ts
|
|
150
|
-
// streamctl.config.ts
|
|
151
82
|
import { defineNuxtBaseConfig } from "@sidebase/base-config";
|
|
152
83
|
|
|
153
84
|
export default defineNuxtBaseConfig({
|
|
154
85
|
package: "@sidebase/base-config",
|
|
155
86
|
base: "nuxt-app",
|
|
156
|
-
version: "0.
|
|
87
|
+
version: "0.3.0",
|
|
157
88
|
profile: "nuxt-4",
|
|
158
|
-
|
|
89
|
+
|
|
90
|
+
ci: { unitTests: true, e2e: true },
|
|
91
|
+
docker: { aptPackages: ["ffmpeg"] },
|
|
92
|
+
automation: { upgradePr: true },
|
|
93
|
+
editor: { i18nSourceLanguage: "en" },
|
|
159
94
|
});
|
|
160
95
|
```
|
|
161
96
|
|
|
162
|
-
###
|
|
163
|
-
|
|
164
|
-
streamctl >= 0.2.0 reads `streamctl.config.ts` from the repo root, and `init` scaffolds it
|
|
165
|
-
there. The older `.streamctl/config.ts` is still resolved, so an existing repo keeps working
|
|
166
|
-
untouched and can move the file whenever it likes.
|
|
167
|
-
|
|
168
|
-
Move it and nothing else changes: the contents are identical, and the CLI warns rather
|
|
169
|
-
than guesses if both exist, naming the root file as the one it read. Keeping the legacy
|
|
170
|
-
path is fine too. The one thing not to do is leave both in place, since only the root file
|
|
171
|
-
is read and edits to the other will look like they do nothing.
|
|
172
|
-
|
|
173
|
-
If you set `ignoresTypeAware` yourself, list whichever path your repo uses. The default
|
|
174
|
-
covers both.
|
|
175
|
-
|
|
176
|
-
`defineNuxtBaseConfig` returns the config unchanged. Its job is typed editor inference
|
|
177
|
-
for this payload's knobs: completion and type checking on `ci`, `versions`, `docker`,
|
|
178
|
-
`automation`, `security`, `pnpm` and `editor`, plus `profile` narrowed to the profile
|
|
179
|
-
names that actually exist. Without it the object is an untyped literal, so a typo such as
|
|
180
|
-
`ci: { unitTest: true }` stays silent in your editor.
|
|
181
|
-
|
|
182
|
-
The CLI shape-checks the config against the manifest when it loads it, so a sync still
|
|
183
|
-
works without the helper. The helper is what moves that feedback into your editor.
|
|
97
|
+
### Options
|
|
184
98
|
|
|
185
|
-
|
|
99
|
+
| Option | Default | Effect |
|
|
100
|
+
| ------ | ------- | ------ |
|
|
101
|
+
| `ci.unitTests` | `false` | Add a `test` job (`pnpm test`) |
|
|
102
|
+
| `ci.e2e` | `false` | Add an `e2e` job with a postgres service |
|
|
103
|
+
| `ci.aptPackages` | `[]` | apt packages for the `test`/`e2e` jobs and upgrade validation |
|
|
104
|
+
| `docker.aptPackages` | `[]` | apt packages for the image, on top of `openssl` |
|
|
105
|
+
| `docker.preInstall` | `""` | Dockerfile lines before `pnpm install` |
|
|
106
|
+
| `docker.buildArgs` | `""` | Dockerfile lines before `COPY . .` |
|
|
107
|
+
| `docker.buildSteps` | `nuxi prepare`, `prisma generate`, `build` | The build commands |
|
|
108
|
+
| `docker.finalStage` | `""` | Dockerfile lines in the final stage, before `CMD` |
|
|
109
|
+
| `docker.prismaRuntime` | copy schema, install Prisma CLI | Prisma setup in the final stage |
|
|
110
|
+
| `docker.prismaVersion` | your `prisma` version | Prisma CLI version in the image |
|
|
111
|
+
| `docker.startCommand` | `npm exec prisma migrate deploy && exec node ...` | The `CMD`, one line |
|
|
112
|
+
| `versions.node` | `24.21.0` | Node in CI, the Dockerfile and the upgrade workflow |
|
|
113
|
+
| `versions.pnpm` | `10.34.5` | pnpm in the Dockerfile (CI uses `packageManager`) |
|
|
114
|
+
| `automation.upgradePr` | `false` | Add the weekly upgrade workflow |
|
|
115
|
+
| `automation.allowWorkflowUpdates` | `false` | Include workflow changes in upgrade PRs (needs a token) |
|
|
116
|
+
| `automation.prTokenSecret` | `"STREAMCTL_PR_TOKEN"` | Secret name for that token |
|
|
117
|
+
| `automation.prepare` | `pnpm prisma generate` if Prisma is installed | Command run before validating an upgrade; `""` skips |
|
|
118
|
+
| `security.minimumReleaseAge` | `"10080"` | Minutes a release must be public before pnpm installs it; `"0"` disables |
|
|
119
|
+
| `pnpm.onlyBuiltDependencies` | `[]` | Extra packages allowed to run install scripts |
|
|
120
|
+
| `editor.i18nSourceLanguage` | `"de"` | i18n-ally source language |
|
|
121
|
+
|
|
122
|
+
Things to know:
|
|
123
|
+
|
|
124
|
+
- **A `docker.*` text option replaces the default.** It does not append, so a `buildSteps`
|
|
125
|
+
override must list every build command you still need.
|
|
126
|
+
- **`docker.prismaRuntime` and `docker.startCommand` go together.** Without Prisma, set
|
|
127
|
+
`prismaRuntime: ""` and a `startCommand` without `migrate deploy`, e.g.
|
|
128
|
+
`exec node .output/server/index.mjs`. Keep the `exec`, so node gets SIGTERM.
|
|
129
|
+
- **Repos with English source strings must set `editor.i18nSourceLanguage: "en"`.**
|
|
130
|
+
Otherwise every sync writes `"de"` back.
|
|
131
|
+
- **Adopting `pnpm-workspace.yaml` replaces your existing file**, including any
|
|
132
|
+
`onlyBuiltDependencies`. Move those entries to `pnpm.onlyBuiltDependencies` instead of
|
|
133
|
+
running `pnpm approve-builds`, which the next sync reverts.
|
|
134
|
+
|
|
135
|
+
### Customizing managed files
|
|
136
|
+
|
|
137
|
+
**Block files:** put your own settings below the `END` marker. For `.editorconfig`,
|
|
138
|
+
later sections win, so an override below the block takes precedence. On the first sync,
|
|
139
|
+
the block is appended *after* your existing content, so move that content below the
|
|
140
|
+
block once.
|
|
141
|
+
|
|
142
|
+
**Opt a file out** when the options are not enough:
|
|
186
143
|
|
|
187
144
|
```ts
|
|
188
|
-
|
|
145
|
+
files: { "Dockerfile": "off" },
|
|
189
146
|
```
|
|
190
147
|
|
|
191
|
-
|
|
148
|
+
streamctl then leaves the file alone, including all future fixes to it.
|
|
192
149
|
|
|
193
|
-
|
|
194
|
-
JSON file so a `tsconfig.json` can extend it by package name. The `nuxt-app` preset ships
|
|
195
|
-
a managed `tsconfig.json` that chains it with Nuxt's generated config:
|
|
150
|
+
**Keep your own `scripts.lint`** (or any other synced `package.json` field):
|
|
196
151
|
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
"extends": [
|
|
200
|
-
"@sidebase/base-config/tsconfig.base",
|
|
201
|
-
"./.nuxt/tsconfig.json"
|
|
202
|
-
]
|
|
203
|
-
}
|
|
152
|
+
```ts
|
|
153
|
+
versionSyncExclude: ["scripts.lint"],
|
|
204
154
|
```
|
|
205
155
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
## Naming convention
|
|
209
|
-
|
|
210
|
-
Two rules cover every exported name, so you can predict them:
|
|
156
|
+
## Helpers
|
|
211
157
|
|
|
212
|
-
|
|
213
|
-
`NuxtBaseCiConfig`, `defineNuxtBaseConfig`, and the `NUXT_BASE_*_KEYS` lists. The prefix
|
|
214
|
-
is doing real work here, because these types describe the config of this specific
|
|
215
|
-
payload rather than anything generic.
|
|
216
|
-
- **Everything else is domain-named with no package prefix**: `buildPrismaConfig`,
|
|
217
|
-
`applyPrismaDevEnv`, `createSidebaseEslint`, `resolveEslintOptions`. The import path
|
|
218
|
-
already states where a symbol comes from, so repeating it in the identifier adds
|
|
219
|
-
nothing.
|
|
220
|
-
|
|
221
|
-
`streamctl` is reserved for the CLI (`@sidebase/streamctl`) and the files it owns:
|
|
222
|
-
`streamctl.config.ts` in the repo root, and the legacy `.streamctl/` directory it still
|
|
223
|
-
reads. Nothing exported from this package carries that name, because nothing
|
|
224
|
-
exported from this package comes from the CLI. The ESLint layers this package builds are
|
|
225
|
-
named `sidebase/*` (`sidebase/defaults`, `sidebase/console`, and so on), which is what you
|
|
226
|
-
see in lint output and what you target if you `.override()` a layer by name. They are
|
|
227
|
-
built at runtime by this package; the CLI never sees them.
|
|
228
|
-
|
|
229
|
-
## ESLint factory
|
|
230
|
-
|
|
231
|
-
`@sidebase/base-config/eslint` exports `createSidebaseEslint(options?)`, which wraps
|
|
232
|
-
[`@antfu/eslint-config`](https://github.com/antfu/eslint-config) (a dependency of this
|
|
233
|
-
package, so **do not add `@antfu/eslint-config` directly**) and returns its
|
|
234
|
-
`FlatConfigComposer`, so you chain `.append()` / `.override()` for local rules:
|
|
158
|
+
### ESLint
|
|
235
159
|
|
|
236
160
|
```ts
|
|
237
161
|
// eslint.config.ts
|
|
@@ -241,432 +165,82 @@ export default createSidebaseEslint({ zod: "full", trpcGuard: true })
|
|
|
241
165
|
.append({ rules: { "vue/multi-word-component-names": "off" } });
|
|
242
166
|
```
|
|
243
167
|
|
|
244
|
-
|
|
168
|
+
Wraps [`@antfu/eslint-config`](https://github.com/antfu/eslint-config), which is a
|
|
169
|
+
dependency of this package; don't install it yourself. Needs `eslint` and `jiti` (for the
|
|
170
|
+
TypeScript config file) in your devDependencies; `sync` adds both.
|
|
245
171
|
|
|
246
172
|
| Option | Default | Effect |
|
|
247
173
|
| ------ | ------- | ------ |
|
|
248
|
-
| `zod` | `"none"` | `"
|
|
249
|
-
| `console` | `"error"` |
|
|
174
|
+
| `zod` | `"none"` | `"import-style"`: enforce `import * as z`. `"full"`: also ban `.extend()`, `.merge()`, `.passthrough()` |
|
|
175
|
+
| `console` | `"error"` | Ban all `console.*`, or pass allowed methods, e.g. `["warn", "error"]` |
|
|
250
176
|
| `trpcGuard` | `false` | Ban `publicProcedure` in `server/trpc/routers/**` |
|
|
251
|
-
| `prismaImportGuard` | `false` | Ban `~~/prisma/` client imports
|
|
252
|
-
| `typeDefStyle` | `"interface"` | `
|
|
253
|
-
| `autoImportPaths` | `["utils/", "composables/", "~~/shared/types/"]` | Paths
|
|
254
|
-
| `autoImportTypeOnly` | `[]` |
|
|
255
|
-
| `ignoresTypeAware` |
|
|
256
|
-
| `testFilePattern` |
|
|
177
|
+
| `prismaImportGuard` | `false` | Ban `~~/prisma/` client imports outside server code |
|
|
178
|
+
| `typeDefStyle` | `"interface"` | Prefer `interface` or `type` |
|
|
179
|
+
| `autoImportPaths` | `["utils/", "composables/", "~~/shared/types/"]` | Paths Nuxt auto-imports, so direct imports are banned |
|
|
180
|
+
| `autoImportTypeOnly` | `[]` | Of those, paths where `import type` stays allowed |
|
|
181
|
+
| `ignoresTypeAware` | config files | Files excluded from type-aware linting |
|
|
182
|
+
| `testFilePattern` | `*.test`, `*.spec`, `*.stories` | Files where the `process.env` and auto-import bans are off |
|
|
257
183
|
|
|
258
|
-
|
|
184
|
+
Type-aware linting is off by default; run with `LINT_TYPEAWARE=true` to enable it.
|
|
259
185
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
The custom bans map to these ESLint rule IDs. Use the exact ID in an
|
|
263
|
-
`// eslint-disable-next-line ...` directive to suppress one line:
|
|
186
|
+
To silence a single line, use the rule ID:
|
|
264
187
|
|
|
265
188
|
| Ban | Rule ID |
|
|
266
189
|
| --- | ------- |
|
|
267
|
-
|
|
|
268
|
-
|
|
|
269
|
-
| Auto-import
|
|
270
|
-
|
|
|
271
|
-
| Zod named-`z` import style (`zod: "import-style"`/`"full"`) | `no-restricted-syntax` |
|
|
272
|
-
| Zod `.extend()/.merge()/.passthrough()` (`zod: "full"`) | `no-restricted-syntax` |
|
|
273
|
-
| `publicProcedure` in routers (`trpcGuard`) | `no-restricted-syntax` |
|
|
274
|
-
|
|
275
|
-
"Re-export" is not a figure of speech: a barrel file doing `export * from "node:process"`
|
|
276
|
-
reports, as do `export { env } from "node:process"` and `export { default as p } from
|
|
277
|
-
"node:process"`. The one shape that does **not** report is a bare side-effect import with
|
|
278
|
-
no bindings, `import "node:process"`. It cannot reach `env`, so there is nothing to ban.
|
|
279
|
-
|
|
280
|
-
The `process.env` ban is enforced by **two** rules, and which one reports depends on the
|
|
281
|
-
shape, so suppressing the wrong one fails twice over: the directive does not suppress
|
|
282
|
-
anything, and ESLint additionally warns that it was unused.
|
|
283
|
-
|
|
284
|
-
```
|
|
285
|
-
// eslint-disable-next-line no-restricted-properties
|
|
286
|
-
import { env } from 'node:process'
|
|
287
|
-
|
|
288
|
-
1:1 warning Unused eslint-disable directive (no problems were reported from 'no-restricted-properties')
|
|
289
|
-
2:10 error 'env' import from 'node:process' is restricted... ts/no-restricted-imports
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Requirements
|
|
293
|
-
|
|
294
|
-
`eslint` is a peer dependency (`^10.5.0`), so consumers of `/eslint` must supply ESLint
|
|
295
|
-
themselves. The peer range matches the version baseline, so `streamctl` version-sync
|
|
296
|
-
keeps a repo's ESLint in step with what this package is tested against.
|
|
297
|
-
|
|
298
|
-
Flat config only. To load a TypeScript `eslint.config.ts` on Node, consumers add
|
|
299
|
-
`jiti` (`>= 2.2.0`) as a devDependency: ESLint has no native TS loader, and ESLint 10
|
|
300
|
-
rejects jiti below 2.2.0. Writing `eslint.config.mjs` instead drops the jiti requirement
|
|
301
|
-
entirely, as does running on Deno or Bun, which import TypeScript directly.
|
|
302
|
-
|
|
303
|
-
#### Type-aware linting prerequisite
|
|
304
|
-
|
|
305
|
-
Type-aware linting is gated behind `LINT_TYPEAWARE=true` (off by default), so this only
|
|
306
|
-
matters on the opt-in and CI path. When type-aware lint is on, the Prisma client and Nuxt
|
|
307
|
-
types must be generated **before** `lint` runs, otherwise type-aware rules fail on missing
|
|
308
|
-
generated types. Ordering satisfies this, not a preflight check:
|
|
309
|
-
|
|
310
|
-
- the managed `ci.yml` runs `prisma generate` (and `nuxi prepare`) before `pnpm lint`, and
|
|
311
|
-
- `scripts.postinstall: "nuxt prepare"` regenerates Nuxt types on install.
|
|
312
|
-
|
|
313
|
-
A standard `pnpm install` plus the managed CI ordering covers the prerequisite. No separate
|
|
314
|
-
check runs.
|
|
315
|
-
|
|
316
|
-
## Prisma factory
|
|
317
|
-
|
|
318
|
-
`@sidebase/base-config/prisma` exports `buildPrismaConfig({ views?, typedSql? })`,
|
|
319
|
-
which returns a Prisma 6.19 config object you wrap in `defineConfig`:
|
|
320
|
-
|
|
321
|
-
```ts
|
|
322
|
-
// prisma.config.ts
|
|
323
|
-
import { defineConfig } from "prisma/config";
|
|
324
|
-
import { buildPrismaConfig } from "@sidebase/base-config/prisma";
|
|
325
|
-
|
|
326
|
-
export default defineConfig(buildPrismaConfig({ views: true, typedSql: true }));
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
It reads `DATABASE_URL` / `DIRECT_DATABASE_URL` / `SHADOW_DATABASE_URL` from the
|
|
330
|
-
environment (Pattern B): the schema engine gets a pgbouncer-free direct
|
|
331
|
-
connection: `DIRECT_DATABASE_URL` if set, otherwise `DATABASE_URL` with
|
|
332
|
-
pooler-only query params (`pgbouncer`, `connection_limit`, and so on) stripped via `ufo`.
|
|
333
|
-
`SHADOW_DATABASE_URL` is wired only when present; with no database URL the engine
|
|
334
|
-
block is omitted so the schema's own `datasource` applies.
|
|
335
|
-
|
|
336
|
-
| Option | Default | Effect |
|
|
337
|
-
| ------ | ------- | ------ |
|
|
338
|
-
| `views` | `false` | Configure the `views` feature (`prisma/views`) |
|
|
339
|
-
| `typedSql` | `false` | Configure the `typedSql` preview feature (`prisma/sql`) |
|
|
340
|
-
|
|
341
|
-
### `applyPrismaDevEnv`
|
|
190
|
+
| `process.env.X` | `no-restricted-properties` |
|
|
191
|
+
| `import { env } from "node:process"` and similar | `ts/no-restricted-imports` |
|
|
192
|
+
| Auto-import paths, app-side Prisma client | `ts/no-restricted-imports` |
|
|
193
|
+
| Zod import style and methods, `publicProcedure` | `no-restricted-syntax` |
|
|
342
194
|
|
|
343
|
-
|
|
344
|
-
It seeds `DIRECT_DATABASE_URL`, `DATABASE_URL`, and `SHADOW_DATABASE_URL` with localhost
|
|
345
|
-
defaults, but only when they are unset, so a real `.env` or shell value always wins. It
|
|
346
|
-
writes to the environment record you pass and defaults to `process.env`, which is a
|
|
347
|
-
deliberate side effect.
|
|
348
|
-
|
|
349
|
-
You need it because once a `prisma.config.ts` exists, Prisma stops auto-loading `.env`.
|
|
350
|
-
A schema that reads `env("DATABASE_URL")` then fails with no environment at all. Call it
|
|
351
|
-
in the config file, after loading dotenv, and the Prisma CLI runs against local postgres
|
|
352
|
-
with zero setup:
|
|
195
|
+
### Prisma
|
|
353
196
|
|
|
354
197
|
```ts
|
|
355
198
|
// prisma.config.ts
|
|
356
199
|
import "dotenv/config";
|
|
357
|
-
import { defineConfig } from "prisma/config";
|
|
358
200
|
import { applyPrismaDevEnv, buildPrismaConfig } from "@sidebase/base-config/prisma";
|
|
201
|
+
import { defineConfig } from "prisma/config";
|
|
359
202
|
|
|
360
|
-
applyPrismaDevEnv();
|
|
361
|
-
|
|
362
|
-
export default defineConfig(buildPrismaConfig());
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
`buildPrismaConfig` alone is enough wherever the connection variables are already set,
|
|
366
|
-
which is every deployed environment and CI. Add `applyPrismaDevEnv` when you want
|
|
367
|
-
`prisma migrate dev` and friends to work on a fresh checkout without a `.env` file. The
|
|
368
|
-
defaults are direct = local postgres, pooled = the direct URL plus `pgbouncer=1`, and
|
|
369
|
-
shadow = the direct URL on a `/prisma-shadow` database.
|
|
370
|
-
|
|
371
|
-
The URL helpers never log a connection string and never put one in an error message, so a
|
|
372
|
-
password cannot reach your terminal or CI output through them. Keep that property if you
|
|
373
|
-
wrap them.
|
|
374
|
-
|
|
375
|
-
`prisma` / `@prisma/client` are peer dependencies (`^6.19`).
|
|
376
|
-
|
|
377
|
-
> Prisma 7 is out of scope for now. v7 changes the config shape (`env()` helper,
|
|
378
|
-
> `directUrl` becomes `url`, no `package.json` prisma block); a `buildPrismaConfig` v7
|
|
379
|
-
> variant is a deliberate later bump.
|
|
380
|
-
|
|
381
|
-
## Supply-chain cooldown (`pnpm-workspace.yaml`)
|
|
382
|
-
|
|
383
|
-
The base preset fully owns `pnpm-workspace.yaml`, pnpm's settings file. It ships two
|
|
384
|
-
supply-chain controls, both on by default for every repo:
|
|
385
|
-
|
|
386
|
-
```yaml
|
|
387
|
-
packages: []
|
|
388
|
-
minimumReleaseAge: 10080 # 7 days, in minutes
|
|
389
|
-
minimumReleaseAgeExclude:
|
|
390
|
-
- "@sidebase/*"
|
|
391
|
-
onlyBuiltDependencies: # the postinstall-script allowlist
|
|
392
|
-
- "@prisma/client"
|
|
393
|
-
- "@prisma/engines"
|
|
394
|
-
- "esbuild"
|
|
395
|
-
- "prisma"
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
`minimumReleaseAge` refuses to resolve a version until it has been published for
|
|
399
|
-
7 days, so a compromised release has time to be caught and yanked before the fleet
|
|
400
|
-
installs it. It needs pnpm 10.16 or newer and gates *fresh resolution only*, so
|
|
401
|
-
`pnpm install --frozen-lockfile` replays the lockfile untouched and CI is unaffected.
|
|
402
|
-
|
|
403
|
-
The pnpm baseline shipped to consumers is 10.34.5 (`presets/*/preset.json`). This repo's
|
|
404
|
-
own `packageManager` pin tracks separately and is usually newer; the two are independent
|
|
405
|
-
by design, not drift.
|
|
406
|
-
|
|
407
|
-
Exclusions match **package names, not dependency trees**: an excluded package's own
|
|
408
|
-
dependencies still face the cooldown, and pnpm resolves the newest *eligible* older
|
|
409
|
-
version in range. Only when nothing in range is old enough does resolution fail, with
|
|
410
|
-
a misleading `ERR_PNPM_NO_MATCHING_VERSION` ([pnpm#9998](https://github.com/pnpm/pnpm/issues/9998)).
|
|
411
|
-
|
|
412
|
-
`@sidebase/*` is exempt: the first-party scope covering both this payload and the
|
|
413
|
-
`streamctl` CLI, published by the org itself, so a payload release reaches the fleet
|
|
414
|
-
the same day instead of waiting out its own cooldown.
|
|
415
|
-
|
|
416
|
-
**`packages: []` is load-bearing.** pnpm defaults it to `**` whenever a
|
|
417
|
-
`pnpm-workspace.yaml` exists, which would silently promote every nested `package.json`
|
|
418
|
-
(test fixtures, examples) to a workspace project and break `--frozen-lockfile`.
|
|
419
|
-
|
|
420
|
-
`onlyBuiltDependencies` is the install-script allowlist. Everything else installs
|
|
421
|
-
with its lifecycle scripts blocked. Add extras through the config, **not** through
|
|
422
|
-
`pnpm approve-builds` (which writes to the managed file and is reverted on the next
|
|
423
|
-
sync):
|
|
424
|
-
|
|
425
|
-
```ts
|
|
426
|
-
export default {
|
|
427
|
-
// ...
|
|
428
|
-
security: { minimumReleaseAge: "10080" }, // minutes; "0" disables the cooldown
|
|
429
|
-
pnpm: { onlyBuiltDependencies: ["sharp"] }, // on top of the baked-in baseline
|
|
430
|
-
};
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
| Option | Default | Effect |
|
|
434
|
-
| ------ | ------- | ------ |
|
|
435
|
-
| `security.minimumReleaseAge` | `"10080"` | Minutes a release must age before pnpm installs it. A string, because the manifest's `configKeys` has no number type, same as the `versions.*` pins. |
|
|
436
|
-
| `pnpm.onlyBuiltDependencies` | `[]` | Extra packages allowed to run install scripts, appended below the baked-in baseline |
|
|
437
|
-
|
|
438
|
-
Because the file is `full`-owned, a repo that already has a `pnpm-workspace.yaml`
|
|
439
|
-
raises a one-time adoption conflict on the first sync; reconcile it with
|
|
440
|
-
`streamctl sync --interactive`.
|
|
441
|
-
|
|
442
|
-
**Adopting it discards your existing keys.** The rendered file is the payload's, so an
|
|
443
|
-
existing `onlyBuiltDependencies` allowlist and `ignoredBuiltDependencies` are both replaced
|
|
444
|
-
by the baked-in baseline, and **nothing warns about it**. Most affected packages ship a
|
|
445
|
-
prebuilt native binary and keep working either way, so the practical breakage is limited to
|
|
446
|
-
architectures with no prebuild and to postinstalls doing essential non-native work. Capture
|
|
447
|
-
the old list before you adopt and re-add it via `pnpm.onlyBuiltDependencies` above. A repo
|
|
448
|
-
that genuinely needs its own `packages:` list (a real monorepo) or its own exclude list
|
|
449
|
-
should opt the file out entirely instead:
|
|
450
|
-
|
|
451
|
-
```ts
|
|
452
|
-
files: { "pnpm-workspace.yaml": "off" },
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
## CI knobs
|
|
456
|
-
|
|
457
|
-
The `nuxt-app` preset ships a managed `.github/workflows/ci.yml`. Its `lint-typecheck` and
|
|
458
|
-
`build` jobs always run; the rest is knobs:
|
|
459
|
-
|
|
460
|
-
| Knob | Default | Effect |
|
|
461
|
-
| ---- | ------- | ------ |
|
|
462
|
-
| `ci.unitTests` | `false` | Add the `test` job (`pnpm test`) |
|
|
463
|
-
| `ci.e2e` | `false` | Add the `e2e` job, with a health-checked postgres service |
|
|
464
|
-
| `ci.aptPackages` | `[]` | System packages `apt-get`-installed in the `test` and `e2e` jobs and the upgrade validation |
|
|
465
|
-
|
|
466
|
-
`ci.aptPackages` is the CI counterpart to `docker.aptPackages` below: extra system packages
|
|
467
|
-
on top of what the runner image already carries, for a binary the suite needs and
|
|
468
|
-
`ubuntu-latest` does not ship. The motivating case is `libxml2-utils`, for `xmllint`.
|
|
469
|
-
|
|
470
|
-
It applies to the `test` and `e2e` jobs only, never `lint-typecheck` or `build`. The step
|
|
471
|
-
installs directly after `checkout`, before `pnpm install`, so a package needed by an install
|
|
472
|
-
lifecycle script is covered as well as one needed by the tests. Leave it unset and the step
|
|
473
|
-
is skipped entirely: it renders with a guard on the value being non-empty, so the default
|
|
474
|
-
costs a no-op step rather than an `apt-get` call.
|
|
475
|
-
|
|
476
|
-
```ts
|
|
477
|
-
export default {
|
|
478
|
-
// ...
|
|
479
|
-
ci: { unitTests: true, e2e: true, aptPackages: ["libxml2-utils"] },
|
|
480
|
-
};
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
Package names only. Every element is rejected if it contains a shell metacharacter, and the
|
|
484
|
-
joined string is then re-validated against the Debian package-name character set (lowercase,
|
|
485
|
-
digits, `+`, `-`, `.`). Both checks exist because the rendered command deliberately leaves
|
|
486
|
-
the value unquoted, which is how several packages word-split into one `apt-get` call.
|
|
487
|
-
|
|
488
|
-
## Dockerfile knobs
|
|
489
|
-
|
|
490
|
-
The `nuxt-app` preset ships a managed `Dockerfile`. Beyond `docker.aptPackages`
|
|
491
|
-
(an `openssl`-plus allowlist), six `docker.*` knobs inject raw text at fixed
|
|
492
|
-
positions so every fleet repo can express its own build without opting the file
|
|
493
|
-
out:
|
|
494
|
-
|
|
495
|
-
| Knob | Position | Default |
|
|
496
|
-
| ---- | -------- | ------- |
|
|
497
|
-
| `docker.preInstall` | build stage, before `pnpm install` | `""` |
|
|
498
|
-
| `docker.buildArgs` | build stage, before `COPY . .` | `""` |
|
|
499
|
-
| `docker.buildSteps` | the build-command block | `nuxi prepare` / `prisma generate` / `run build` |
|
|
500
|
-
| `docker.finalStage` | final stage, before `CMD` | `""` |
|
|
501
|
-
| `docker.prismaRuntime` | final stage | copy the schema + install the `migrate deploy` CLI |
|
|
502
|
-
| `docker.startCommand` | shell-form `CMD`, one line | `prisma migrate deploy` then `exec node ...` |
|
|
503
|
-
|
|
504
|
-
A seventh knob is different in kind: a validated version, not raw text.
|
|
505
|
-
|
|
506
|
-
| Knob | Position | Default |
|
|
507
|
-
| ---- | -------- | ------- |
|
|
508
|
-
| `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 |
|
|
509
|
-
|
|
510
|
-
It is pattern-checked (`x.y.z`, optional prerelease), so unlike the six raw-text
|
|
511
|
-
knobs it cannot inject arbitrary Dockerfile content. `--build-arg PRISMA_VERSION=`
|
|
512
|
-
still overrides at build time.
|
|
513
|
-
|
|
514
|
-
```ts
|
|
515
|
-
export default {
|
|
516
|
-
// ...
|
|
517
|
-
docker: {
|
|
518
|
-
preInstall: "COPY ./vendor ./vendor", // vendored dep needed at install time
|
|
519
|
-
buildSteps: "RUN pnpm nuxi prepare\nRUN pnpm run build", // a repo with no Prisma
|
|
520
|
-
prismaRuntime: "", // drop Prisma entirely
|
|
521
|
-
startCommand: "exec node .output/server/index.mjs",
|
|
522
|
-
},
|
|
523
|
-
};
|
|
203
|
+
applyPrismaDevEnv(); // optional: localhost defaults for unset DB URLs
|
|
204
|
+
export default defineConfig(buildPrismaConfig({ views: true, typedSql: true }));
|
|
524
205
|
```
|
|
525
206
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
> `.output` ownership stays FIXED outside every knob. The container itself runs
|
|
532
|
-
> as **root**: a fixed `USER node` broke every deployment that overrides the image
|
|
533
|
-
> CMD with a root-requiring command, so a repo that wants an unprivileged runtime
|
|
534
|
-
> opts in with `docker.finalStage: "USER node"`.
|
|
535
|
-
|
|
536
|
-
Setting a knob replaces its default outright, it does not append. So a `docker.buildSteps`
|
|
537
|
-
override must restate every build command you still want, and `docker.prismaRuntime: ""`
|
|
538
|
-
removes the Prisma runtime block entirely. The three knobs that default to real content
|
|
539
|
-
(`buildSteps`, `prismaRuntime`, `startCommand`) are the ones where this matters; the other
|
|
540
|
-
three default to `""`.
|
|
541
|
-
|
|
542
|
-
**`prismaRuntime` and `startCommand` are coupled.** Override one and you must override the
|
|
543
|
-
other. `prismaRuntime` is what installs the Prisma CLI into the final stage, and the
|
|
544
|
-
DEFAULT `startCommand` is `npm exec prisma migrate deploy && exec node ...`. Setting only
|
|
545
|
-
`prismaRuntime: ""` therefore produces an image whose `CMD` invokes a CLI that is no longer
|
|
546
|
-
installed, and the container fails to start. The example above overrides both, which is why
|
|
547
|
-
it is safe to copy; a single-knob change is not. Drop the `migrate deploy` half of
|
|
548
|
-
`startCommand` at the same time.
|
|
549
|
-
|
|
550
|
-
Global installs in `docker.preInstall` must use `npm i -g <tool>`, not
|
|
551
|
-
`pnpm add -g`: the image sets no `PNPM_HOME`, so pnpm's global bin dir is
|
|
552
|
-
undefined.
|
|
553
|
-
|
|
554
|
-
## Editor settings (`.vscode/settings.json`)
|
|
555
|
-
|
|
556
|
-
The base preset owns its own keys in `.vscode/settings.json` and leaves the rest of the file
|
|
557
|
-
to the project. Among them is i18n-ally, configured for the fleet's standard setup:
|
|
558
|
-
`@nuxtjs/i18n` with flat dotted keys under `i18n/locales`.
|
|
559
|
-
|
|
560
|
-
Everything the payload does not write is yours and survives a sync untouched: your
|
|
561
|
-
`editor.fontSize`, your `files.exclude`, any unrelated key at all. Six of those are named in
|
|
562
|
-
the manifest as project-owned on top of that (`editor.fontSize`, `editor.rulers`,
|
|
563
|
-
`editor.formatOnSave`, `files.exclude`, `search.exclude`, `files.watcherExclude`), which
|
|
564
|
-
keeps them yours even if a later payload version starts setting them. Only the keys the
|
|
565
|
-
payload writes are reverted, and they are the ones below.
|
|
566
|
-
|
|
567
|
-
| Knob | Default | Effect |
|
|
568
|
-
| ---- | ------- | ------ |
|
|
569
|
-
| `editor.i18nSourceLanguage` | `"de"` | The locale i18n-ally translates FROM (`i18n-ally.sourceLanguage`). Payload-enforced; see below |
|
|
207
|
+
- `buildPrismaConfig` reads `DATABASE_URL`, `DIRECT_DATABASE_URL` and `SHADOW_DATABASE_URL`.
|
|
208
|
+
Migrations use `DIRECT_DATABASE_URL`, or `DATABASE_URL` with the pooler parameters removed.
|
|
209
|
+
`views` and `typedSql` enable those Prisma features.
|
|
210
|
+
- `applyPrismaDevEnv` sets localhost defaults for the URLs that are unset, so
|
|
211
|
+
`prisma migrate dev` works on a fresh checkout without a `.env`.
|
|
570
212
|
|
|
571
|
-
|
|
572
|
-
export default {
|
|
573
|
-
// ...
|
|
574
|
-
editor: { i18nSourceLanguage: "en" },
|
|
575
|
-
};
|
|
576
|
-
```
|
|
213
|
+
Prisma 6.19 only; `prisma` and `@prisma/client` are optional peer dependencies.
|
|
577
214
|
|
|
578
|
-
|
|
579
|
-
payload-owned, so leaving it unset does not mean "keep what I have". Every sync writes
|
|
580
|
-
`"de"` into the file, over the top of an `"en"` you put there by hand. Nothing warns, and
|
|
581
|
-
`streamctl check` stays green afterwards, because the value it finds is the value the payload
|
|
582
|
-
intends. Setting the knob is the only thing that makes `"en"` survive a sync.
|
|
215
|
+
### tsconfig
|
|
583
216
|
|
|
584
|
-
`
|
|
585
|
-
|
|
586
|
-
than offered. Editing either one in your own `.vscode/settings.json` is reverted on the next
|
|
587
|
-
sync. A repo that genuinely needs a different layout should opt the file out
|
|
588
|
-
(`files: { ".vscode/settings.json": "off" }`) rather than fight the merge.
|
|
217
|
+
The managed `tsconfig.json` extends `@sidebase/base-config/tsconfig.base`, the shared
|
|
218
|
+
strict config, before Nuxt's generated one. You can extend it directly as well.
|
|
589
219
|
|
|
590
|
-
##
|
|
220
|
+
## Troubleshooting
|
|
591
221
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
222
|
+
- **`No version profile could be detected`:** the repo is not on Nuxt 4. Nuxt 3 is not
|
|
223
|
+
supported.
|
|
224
|
+
- **`pnpm approve-builds` changes disappear:** use `pnpm.onlyBuiltDependencies`.
|
|
225
|
+
- **`.editorconfig` settings ignored after the first sync:** your old content sits above the
|
|
226
|
+
managed block. Move it below.
|
|
227
|
+
- **`eslint.config.ts` fails to load:** add `jiti` to devDependencies.
|
|
228
|
+
- **Repo with private npm dependencies:** the upgrade workflow installs without registry
|
|
229
|
+
credentials. Opt it out and use your own.
|
|
595
230
|
|
|
596
|
-
|
|
231
|
+
## Developing this package
|
|
597
232
|
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
By default, the workflow uses `GITHUB_TOKEN` and omits changes under
|
|
614
|
-
`.github/workflows/**`. If an update contains workflow changes, it opens a draft PR
|
|
615
|
-
with every other safe change and lists the omitted files and completion commands.
|
|
616
|
-
|
|
617
|
-
Set `automation.allowWorkflowUpdates` to `true` to include workflow changes. Add a
|
|
618
|
-
`STREAMCTL_PR_TOKEN` repository secret with `Contents`, `Pull requests`, and
|
|
619
|
-
`Workflows` write access. Change `automation.prTokenSecret` when the secret has another
|
|
620
|
-
name. A fine-grained PAT or GitHub App token works. The built-in `GITHUB_TOKEN` cannot
|
|
621
|
-
receive `Workflows` permission.
|
|
622
|
-
|
|
623
|
-
| Knob | Default | Effect |
|
|
624
|
-
| ---- | ------- | ------ |
|
|
625
|
-
| `automation.upgradePr` | `false` | Sync the scheduled upgrade workflow |
|
|
626
|
-
| `automation.allowWorkflowUpdates` | `false` | Include `.github/workflows/**` changes in upgrade PRs |
|
|
627
|
-
| `automation.prTokenSecret` | `"STREAMCTL_PR_TOKEN"` | Secret used when workflow updates are enabled |
|
|
628
|
-
| `automation.prepare` | `"pnpm prisma generate"` | One-line command run before validation |
|
|
629
|
-
|
|
630
|
-
`automation.prepare` generates what `pnpm install` does not, before validation. One line;
|
|
631
|
-
chain with `&&`. Set `""` for a repo without Prisma.
|
|
632
|
-
|
|
633
|
-
The upgrade job runs `streamctl check` and every available lint, typecheck, test, and
|
|
634
|
-
build script before opening a ready PR. PRs created with `GITHUB_TOKEN` do not trigger
|
|
635
|
-
their own `pull_request` workflows, so these inline checks are required. The external
|
|
636
|
-
token allows the new PR to trigger them normally.
|
|
637
|
-
|
|
638
|
-
The workflow handles CLI results as follows:
|
|
639
|
-
|
|
640
|
-
- `check` exit 0: stop without a PR.
|
|
641
|
-
- `check` exit 3: open or update one drift issue.
|
|
642
|
-
- `check` exit 4: attempt the upgrade.
|
|
643
|
-
- `upgrade` exit 0: validate and open a ready PR, or a draft when workflow files were omitted.
|
|
644
|
-
- `upgrade` exit 2: inspect conflicts using the pre-upgrade status.
|
|
645
|
-
- Any other exit: fail the workflow.
|
|
646
|
-
|
|
647
|
-
A conflict is safe to apply when the same path was previously `full` managed and
|
|
648
|
-
`in-sync`. The workflow retries those generated replacements with `--force`. Any new,
|
|
649
|
-
modified, malformed, or structurally conflicting path produces a draft PR instead.
|
|
650
|
-
Workflow changes also produce a draft unless explicitly allowed. Drafts contain every
|
|
651
|
-
safe change, keep conflicting and omitted workflow files at their current content, list
|
|
652
|
-
what remains, and give the commands needed to finish the upgrade. When a malformed or
|
|
653
|
-
structurally invalid file blocks `--force`, the draft only updates the payload version
|
|
654
|
-
and lockfile.
|
|
655
|
-
|
|
656
|
-
Diagnostic JSON stays under the runner's temporary directory. Upgrade branches include
|
|
657
|
-
the target version, so a later release does not overwrite an older open PR.
|
|
658
|
-
|
|
659
|
-
The payload is public npm. A repo with other private dependencies must opt this workflow
|
|
660
|
-
out and provide its own install authentication.
|
|
661
|
-
|
|
662
|
-
### Action pinning policy
|
|
663
|
-
|
|
664
|
-
Every action in every managed workflow and CI job fragment is pinned by full commit SHA,
|
|
665
|
-
with the version tag in a trailing comment. Moving tags can be re-pointed, so tags are
|
|
666
|
-
never trusted, secrets or not. `test/payload.test.ts` renders every managed workflow
|
|
667
|
-
and fails on any `uses:` that is not a 40-hex SHA.
|
|
668
|
-
|
|
669
|
-
The shipped pins track `actions/checkout` v6, `actions/setup-node` v6, and
|
|
670
|
-
`pnpm/action-setup` v6. These majors run on the node24 action runtime, which needs a
|
|
671
|
-
GitHub Actions runner `>= v2.327.1`. GitHub-hosted `ubuntu-latest` is far past that; only
|
|
672
|
-
a repo that adds its own **self-hosted** runner needs to keep it above that floor.
|
|
233
|
+
```sh
|
|
234
|
+
pnpm lint && pnpm typecheck && pnpm test
|
|
235
|
+
pnpm validate:presets # manifests and cross-file invariants
|
|
236
|
+
pnpm e2e:dry-run # pack, install into a scratch consumer, sync and check
|
|
237
|
+
pnpm e2e:docker # build and start the rendered Dockerfile (needs Docker)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- The payload lives in `presets/` (`base`, and `nuxt-app` on top of it). The manifests in
|
|
241
|
+
`preset.json` define files, options and version baselines.
|
|
242
|
+
- Tests check contracts derived from the manifests, so changing a preset file rarely needs
|
|
243
|
+
a test change.
|
|
244
|
+
- Actions in managed workflows are pinned by commit SHA; the tests enforce it.
|
|
245
|
+
- Requires streamctl >= 0.3.0 (manifest schema version 3).
|
|
246
|
+
- Releases: [`docs/release.md`](docs/release.md). Agent conventions: [`AGENTS.md`](AGENTS.md).
|
package/dist/config.d.mts
CHANGED
|
@@ -100,7 +100,7 @@ interface NuxtBaseAutomationConfig {
|
|
|
100
100
|
allowWorkflowUpdates?: boolean;
|
|
101
101
|
/** Repository secret used when workflow updates are allowed. */
|
|
102
102
|
prTokenSecret?: string;
|
|
103
|
-
/** One-line shell command run before validation (default: `pnpm prisma generate
|
|
103
|
+
/** One-line shell command run before validation (default: `pnpm prisma generate` when Prisma is installed, `""` skips). */
|
|
104
104
|
prepare?: string;
|
|
105
105
|
}
|
|
106
106
|
/**
|
package/dist/config.d.ts
CHANGED
|
@@ -100,7 +100,7 @@ interface NuxtBaseAutomationConfig {
|
|
|
100
100
|
allowWorkflowUpdates?: boolean;
|
|
101
101
|
/** Repository secret used when workflow updates are allowed. */
|
|
102
102
|
prTokenSecret?: string;
|
|
103
|
-
/** One-line shell command run before validation (default: `pnpm prisma generate
|
|
103
|
+
/** One-line shell command run before validation (default: `pnpm prisma generate` when Prisma is installed, `""` skips). */
|
|
104
104
|
prepare?: string;
|
|
105
105
|
}
|
|
106
106
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sidebase/base-config",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
@@ -332,7 +332,8 @@ jobs:
|
|
|
332
332
|
{
|
|
333
333
|
echo "## Status"
|
|
334
334
|
echo
|
|
335
|
-
echo "
|
|
335
|
+
echo "> [!CAUTION]"
|
|
336
|
+
echo "> This update omits GitHub Actions workflow changes. Do not merge this pull request yet."
|
|
336
337
|
echo
|
|
337
338
|
printf -- '- Updated the streamctl payload from `%s` to `%s`.\n' "$from" "$TARGET"
|
|
338
339
|
echo "- Applied every other safe managed-file change."
|
|
@@ -369,7 +370,8 @@ jobs:
|
|
|
369
370
|
{
|
|
370
371
|
echo "## Status"
|
|
371
372
|
echo
|
|
372
|
-
echo "
|
|
373
|
+
echo "> [!CAUTION]"
|
|
374
|
+
echo "> This update needs manual conflict resolution. Do not merge this pull request yet."
|
|
373
375
|
echo
|
|
374
376
|
printf -- '- Updated the streamctl payload from `%s` to `%s`.\n' "$from" "$TARGET"
|
|
375
377
|
if [[ "$APPLIED" == "true" ]]; then
|
package/presets/base/preset.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"CI_APT_PACKAGES": { "configPath": "ci.aptPackages", "default": "", "pattern": "^$|^[a-z0-9][a-z0-9 +.-]*$", "join": "space" },
|
|
20
20
|
"NODE_VERSION": { "configPath": "versions.node", "default": "24.21.0", "pattern": "^[\\w.+-]+$" },
|
|
21
21
|
"PR_TOKEN_SECRET": { "configPath": "automation.prTokenSecret", "default": "STREAMCTL_PR_TOKEN", "pattern": "^(?![Gg][Ii][Tt][Hh][Uu][Bb]_)[A-Za-z_][A-Za-z0-9_]*$" },
|
|
22
|
-
"PREPARE_COMMAND": { "configPath": "automation.prepare", "default": "pnpm prisma generate", "pattern": "^[^\\r\\n]*$" }
|
|
22
|
+
"PREPARE_COMMAND": { "configPath": "automation.prepare", "default": "if [ -e node_modules/.bin/prisma ]; then pnpm prisma generate; fi", "pattern": "^[^\\r\\n]*$" }
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"editor": {
|