@sidebase/base-config 0.2.0 → 0.2.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.
@@ -107,30 +107,27 @@ function buildEslintLayers(options) {
107
107
  imports: "always-multiline"
108
108
  }],
109
109
  "space-infix-ops": ["error"],
110
- // `node/prefer-global/process` is NOT set here: antfu 7.4.3 registers
111
- // eslint-plugin-n (`node`) only inside its own files-scoped object, so setting
112
- // the rule in this plugin-less layer makes ESLint 10 ABORT. It is instead
113
- // neutralized to `"off"` via `.override("antfu/node/rules")` in
114
- // `createSidebaseEslint`, inside antfu's node-scoped object where the plugin
115
- // is registered (see the comment there). The `no-restricted-properties`
116
- // process.env ban below carries the real guard.
110
+ // `node/prefer-global/process` is deliberately not set here: antfu registers
111
+ // eslint-plugin-n only inside its own files-scoped object, so setting the rule in
112
+ // this plugin-less layer makes ESLint 10 abort. It is neutralized via
113
+ // `.override("antfu/node/rules")` in `createSidebaseEslint` instead. The
114
+ // `no-restricted-properties` process.env ban below carries the real guard.
117
115
  "no-unused-expressions": ["error"],
118
116
  "no-param-reassign": ["error"],
119
117
  "no-fallthrough": ["error"],
120
118
  "require-await": ["error"],
121
119
  "ts/no-non-null-assertion": ["error"],
122
- // antfu leaves `ts/no-explicit-any` off; this baseline enforces it, matching the
123
- // shipped `AGENTS.md` ban on `any`. Breaking for consumers (Q5, Option A).
120
+ // antfu leaves `ts/no-explicit-any` off. This baseline enforces it, matching the
121
+ // shipped `AGENTS.md` ban on `any`. Breaking for consumers on adoption.
124
122
  "ts/no-explicit-any": "error",
125
123
  // Type-aware rules antfu enables that this baseline turns off.
126
124
  "ts/no-misused-promises": "off",
127
125
  "ts/no-unsafe-call": "off",
128
126
  "ts/strict-boolean-expressions": "off",
129
127
  "ts/promise-function-async": "off",
130
- // Newer antfu rules outside the established house baseline. `prefer-static-regex`
131
- // is a perf nudge the consuming apps never adopted; `custom-event-name-casing`
132
- // is actively wrong for naive-ui's kebab `update:*` v-model events. Off by
133
- // default; a repo can opt back in via `.append()`.
128
+ // Newer antfu rules outside the house baseline. `prefer-static-regex` is a perf
129
+ // nudge the apps never adopted, and `custom-event-name-casing` is wrong for
130
+ // naive-ui's kebab `update:*` v-model events. Opt back in via `.append()`.
134
131
  "e18e/prefer-static-regex": "off",
135
132
  "vue/custom-event-name-casing": "off"
136
133
  }
@@ -141,17 +138,14 @@ function buildEslintLayers(options) {
141
138
  });
142
139
  configs.push({
143
140
  name: "sidebase/console",
144
- // Source files only: antfu's markdown processor lints fenced code
145
- // blocks as virtual `*.md/*` files, so an un-scoped `no-console` fires inside
146
- // README/doc examples. This carve-out leaves real `.ts`/`.vue`/... enforcement intact.
141
+ // Source files only. antfu's markdown processor lints fenced code blocks as virtual
142
+ // `*.md/*` files, so an un-scoped `no-console` fires inside doc examples.
147
143
  ignores: ["**/*.md/**"],
148
144
  rules: {
149
- // Always pass an explicit options object: a severity-only override would
150
- // retain antfu's `{ allow: ["warn", "error"] }`. `"error"` (hard-ban)
151
- // emits `{}` (no-console's schema rejects an empty `allow` array), which
152
- // replaces those options so every `console.*` is banned. An empty
153
- // allow-list was normalized to `"error"` in resolveEslintOptions, so the
154
- // array branch here is always a non-empty allow-list (no cast needed).
145
+ // Always pass an explicit options object, or a severity-only override retains
146
+ // antfu's `{ allow: ["warn", "error"] }`. A hard ban emits `{}`, since no-console
147
+ // rejects an empty `allow` array. `resolveEslintOptions` already normalized an
148
+ // empty allow-list to `"error"`, so the array branch is always non-empty.
155
149
  "no-console": options.console === "error" ? ["error", {}] : ["error", { allow: options.console }]
156
150
  }
157
151
  });
@@ -229,48 +223,23 @@ function createSidebaseEslint(options = {}) {
229
223
  return antfu(
230
224
  {
231
225
  type: "app",
232
- // `local.*` files and directories are personal, git-ignored scratch space
233
- // and are never linted.
234
- ignores: ["**/local.*", "**/local.*/**"],
235
- // Org house style: 2-space indent, single quotes, Vue on,
236
- // JSON/YAML linting off (handled elsewhere).
226
+ // Personal scratch space. Root-anchored so `src/path/local.ts` stays linted.
227
+ ignores: ["local.*", "local.*/**", "prisma/local.*", "prisma/local.*/**"],
228
+ // House style. JSON and YAML linting are handled elsewhere.
237
229
  stylistic: { indent: 2, quotes: "single" },
238
230
  vue: true,
239
231
  jsonc: false,
240
232
  yaml: false,
241
- // Type-aware is gated behind `LINT_TYPEAWARE`; when on, point at the repo
242
- // tsconfig and exclude the configured paths from the type-aware program.
233
+ // Gated behind `LINT_TYPEAWARE`. When on, point at the repo tsconfig and
234
+ // exclude the configured paths from the type-aware program.
243
235
  typescript: resolved.typeAware ? { tsconfigPath: "tsconfig.json", ignoresTypeAware: resolved.ignoresTypeAware } : true
244
236
  },
245
237
  ...buildEslintLayers(resolved)
246
238
  ).override("antfu/node/rules", {
247
- // antfu 7.4.3's node layer defaults `node/prefer-global/process` to
248
- // `["error", "never"]` (demands `require("process")`), which flags EVERY legit
249
- // global `process` use in an ESM Nuxt app (env modules, server plugins, loggers).
250
- // Leaving the rule unset is not enough: antfu's `never` then wins in consumer
251
- // resolution. Neutralize it HERE, inside antfu's own node-scoped config where the
252
- // `node` plugin IS registered; setting it in a plugin-less object makes ESLint 10
253
- // abort. `"off"`, not `["error", "always"]`, because `"always"` would wrongly flag
254
- // the generated Prisma client's `import * as process from 'node:process'`.
255
- //
256
- // Note what this does NOT mean. prefer-global is not merely redundant here:
257
- // `["error", "always"]` would also close a real hole, because
258
- // `no-restricted-properties` keys on the `process.env` MEMBER EXPRESSION, and any
259
- // import that renames the binding erases the shape it matches. Measured against
260
- // this factory, the property ban alone catches `process.env.X`,
261
- // `const { env } = process`, and a default import still named `process`, but NOT
262
- // `import { env }`, `import { env as e }`, `import * as proc`, or
263
- // `import proc from "node:process"`. Those are closed at the import site instead,
264
- // by the `node:process` / `process` entries in `paths` (see `importRestrictions` in
265
- // rules.ts): narrower than flipping prefer-global, and free of the Prisma-client
266
- // false positive.
267
- //
268
- // With `importNames: ["env", "default"]` that is now the COMPLETE set, not a sample.
269
- // Every way to reach `process.env` is covered by one rule or the other: the member
270
- // expression and an un-renamed default import at the USAGE site, and the named,
271
- // renamed-named, namespace and renamed-default imports at the IMPORT site. The
272
- // un-renamed default being caught at the usage site rather than by the import ban is
273
- // the asymmetry that hid the renamed-default hole for two rounds of review.
239
+ // antfu's `"never"` default flags every legitimate global `process` use. Neutralize
240
+ // it here, inside antfu's node-scoped config: a plugin-less object aborts ESLint 10.
241
+ // `"off"` and not `"always"`, which would flag the generated Prisma client. The
242
+ // `process.env` hole is covered at the import site by `importRestrictions`.
274
243
  rules: { "node/prefer-global/process": "off" }
275
244
  });
276
245
  }
@@ -6,27 +6,12 @@ interface SchemaEngineDatasource {
6
6
  shadowDatabaseUrl?: string;
7
7
  }
8
8
  /**
9
- * Seed the three Prisma connection env vars with localhost dev defaults when UNSET.
10
- * This is a deliberate SIDE EFFECT on `env` (defaults to `process.env`). Necessary because
11
- * once a `prisma.config.ts` exists Prisma stops auto-loading `.env`, so a schema that
12
- * reads `env("DATABASE_URL")` / `env("DIRECT_DATABASE_URL")` would fail at rest.
9
+ * Seed the Prisma connection env vars with localhost dev defaults when unset.
10
+ * Deliberately mutates `env`: once a `prisma.config.ts` exists Prisma stops auto-loading
11
+ * `.env`, so a schema reading `env("DATABASE_URL")` would fail at rest.
13
12
  *
14
- * Call it (after `import "dotenv/config"`) in the config file so the Prisma CLI runs
15
- * with ZERO env; a real `.env`/shell value always wins. `buildPrismaConfig` then reads
16
- * the seeded env and derives its direct-connection + shadow datasource.
17
- *
18
- * TWO paths, not one:
19
- * - **Bare, or an explicit `DIRECT_DATABASE_URL`.** Seeds all three. Defaults:
20
- * direct = local postgres, pooled = direct + `pgbouncer=1`, shadow = direct with a
21
- * `/prisma-shadow` database.
22
- * - **A real `DATABASE_URL` and no direct URL.** Seeds ONLY `DIRECT_DATABASE_URL`,
23
- * derived from the pooled URL, and returns early. No shadow is seeded on purpose:
24
- * deriving one would name a `/prisma-shadow` database on the production host, which
25
- * Prisma CREATEs and DROPs.
26
- *
27
- * An empty-string value counts as unset throughout; `docker-compose` produces `""` for an
28
- * undefined variable, and treating it as present is how the localhost default used to
29
- * reach production.
13
+ * Call it after `import "dotenv/config"`. A real value always wins, and an empty string
14
+ * counts as unset.
30
15
  */
31
16
  declare function applyPrismaDevEnv(env?: Record<string, string | undefined>): void;
32
17
  /**
@@ -35,13 +20,12 @@ declare function applyPrismaDevEnv(env?: Record<string, string | undefined>): vo
35
20
  */
36
21
  declare function stripPgbouncerParams(url: string): string;
37
22
  /**
38
- * Resolve the schema engine's datasource (Pattern B):
39
- * - direct URL = `DIRECT_DATABASE_URL`, else `DATABASE_URL` with pgbouncer params stripped;
40
- * - shadow DB = `SHADOW_DATABASE_URL` when present.
23
+ * Resolve the schema engine's datasource:
24
+ * - direct URL = `DIRECT_DATABASE_URL`, else `DATABASE_URL` with pooler params stripped
25
+ * - shadow DB = `SHADOW_DATABASE_URL` when present
41
26
  *
42
- * Returns `undefined` when neither a direct nor a pooled URL is set, so the
43
- * config falls back to the schema's own `datasource` block (e.g. during
44
- * `prisma generate` without a database).
27
+ * Returns `undefined` when neither is set, so the config falls back to the schema's own
28
+ * `datasource` block, e.g. during `prisma generate` without a database.
45
29
  */
46
30
  declare function resolveSchemaEngineDatasource(env: Record<string, string | undefined>): SchemaEngineDatasource | undefined;
47
31
 
@@ -54,8 +38,8 @@ interface BuildPrismaConfigOptions {
54
38
  typedSql?: boolean;
55
39
  }
56
40
  /**
57
- * Build the shared `@sidebase` Prisma config (Prisma 6.19 "Pattern B").
58
- * Wrap the result in `defineConfig`:
41
+ * Build the shared `@sidebase` Prisma config. Gives the schema engine a pooler-free
42
+ * direct connection. Prisma 7's config shape is not covered.
59
43
  *
60
44
  * ```ts
61
45
  * // prisma.config.ts
@@ -63,10 +47,6 @@ interface BuildPrismaConfigOptions {
63
47
  * import { buildPrismaConfig } from "@sidebase/base-config/prisma";
64
48
  * export default defineConfig(buildPrismaConfig({ views: true, typedSql: true }));
65
49
  * ```
66
- *
67
- * Reads `DATABASE_URL` / `DIRECT_DATABASE_URL` / `SHADOW_DATABASE_URL` (the
68
- * schema engine gets a pgbouncer-free direct connection). Prisma 7's changed
69
- * config shape is out of scope for v1.
70
50
  */
71
51
  declare function buildPrismaConfig(options?: BuildPrismaConfigOptions, env?: Record<string, string | undefined>): PrismaConfig;
72
52
 
@@ -6,27 +6,12 @@ interface SchemaEngineDatasource {
6
6
  shadowDatabaseUrl?: string;
7
7
  }
8
8
  /**
9
- * Seed the three Prisma connection env vars with localhost dev defaults when UNSET.
10
- * This is a deliberate SIDE EFFECT on `env` (defaults to `process.env`). Necessary because
11
- * once a `prisma.config.ts` exists Prisma stops auto-loading `.env`, so a schema that
12
- * reads `env("DATABASE_URL")` / `env("DIRECT_DATABASE_URL")` would fail at rest.
9
+ * Seed the Prisma connection env vars with localhost dev defaults when unset.
10
+ * Deliberately mutates `env`: once a `prisma.config.ts` exists Prisma stops auto-loading
11
+ * `.env`, so a schema reading `env("DATABASE_URL")` would fail at rest.
13
12
  *
14
- * Call it (after `import "dotenv/config"`) in the config file so the Prisma CLI runs
15
- * with ZERO env; a real `.env`/shell value always wins. `buildPrismaConfig` then reads
16
- * the seeded env and derives its direct-connection + shadow datasource.
17
- *
18
- * TWO paths, not one:
19
- * - **Bare, or an explicit `DIRECT_DATABASE_URL`.** Seeds all three. Defaults:
20
- * direct = local postgres, pooled = direct + `pgbouncer=1`, shadow = direct with a
21
- * `/prisma-shadow` database.
22
- * - **A real `DATABASE_URL` and no direct URL.** Seeds ONLY `DIRECT_DATABASE_URL`,
23
- * derived from the pooled URL, and returns early. No shadow is seeded on purpose:
24
- * deriving one would name a `/prisma-shadow` database on the production host, which
25
- * Prisma CREATEs and DROPs.
26
- *
27
- * An empty-string value counts as unset throughout; `docker-compose` produces `""` for an
28
- * undefined variable, and treating it as present is how the localhost default used to
29
- * reach production.
13
+ * Call it after `import "dotenv/config"`. A real value always wins, and an empty string
14
+ * counts as unset.
30
15
  */
31
16
  declare function applyPrismaDevEnv(env?: Record<string, string | undefined>): void;
32
17
  /**
@@ -35,13 +20,12 @@ declare function applyPrismaDevEnv(env?: Record<string, string | undefined>): vo
35
20
  */
36
21
  declare function stripPgbouncerParams(url: string): string;
37
22
  /**
38
- * Resolve the schema engine's datasource (Pattern B):
39
- * - direct URL = `DIRECT_DATABASE_URL`, else `DATABASE_URL` with pgbouncer params stripped;
40
- * - shadow DB = `SHADOW_DATABASE_URL` when present.
23
+ * Resolve the schema engine's datasource:
24
+ * - direct URL = `DIRECT_DATABASE_URL`, else `DATABASE_URL` with pooler params stripped
25
+ * - shadow DB = `SHADOW_DATABASE_URL` when present
41
26
  *
42
- * Returns `undefined` when neither a direct nor a pooled URL is set, so the
43
- * config falls back to the schema's own `datasource` block (e.g. during
44
- * `prisma generate` without a database).
27
+ * Returns `undefined` when neither is set, so the config falls back to the schema's own
28
+ * `datasource` block, e.g. during `prisma generate` without a database.
45
29
  */
46
30
  declare function resolveSchemaEngineDatasource(env: Record<string, string | undefined>): SchemaEngineDatasource | undefined;
47
31
 
@@ -54,8 +38,8 @@ interface BuildPrismaConfigOptions {
54
38
  typedSql?: boolean;
55
39
  }
56
40
  /**
57
- * Build the shared `@sidebase` Prisma config (Prisma 6.19 "Pattern B").
58
- * Wrap the result in `defineConfig`:
41
+ * Build the shared `@sidebase` Prisma config. Gives the schema engine a pooler-free
42
+ * direct connection. Prisma 7's config shape is not covered.
59
43
  *
60
44
  * ```ts
61
45
  * // prisma.config.ts
@@ -63,10 +47,6 @@ interface BuildPrismaConfigOptions {
63
47
  * import { buildPrismaConfig } from "@sidebase/base-config/prisma";
64
48
  * export default defineConfig(buildPrismaConfig({ views: true, typedSql: true }));
65
49
  * ```
66
- *
67
- * Reads `DATABASE_URL` / `DIRECT_DATABASE_URL` / `SHADOW_DATABASE_URL` (the
68
- * schema engine gets a pgbouncer-free direct connection). Prisma 7's changed
69
- * config shape is out of scope for v1.
70
50
  */
71
51
  declare function buildPrismaConfig(options?: BuildPrismaConfigOptions, env?: Record<string, string | undefined>): PrismaConfig;
72
52
 
@@ -1,7 +1,4 @@
1
- /**
2
- * Options for the published ESLint factory (`@sidebase/base-config/eslint`).
3
- * Covers the per-repo variance found across the consuming repos.
4
- */
1
+ /** Options for the published ESLint factory (`@sidebase/base-config/eslint`). */
5
2
  interface CreateSidebaseEslintOptions {
6
3
  /** Zod `.extend()/.merge()/.passthrough()` bans and/or `import * as z` enforcement. Default `"none"` (opt-in). */
7
4
  zod?: "full" | "import-style" | "none";
@@ -36,10 +33,9 @@ interface CreateSidebaseEslintOptions {
36
33
  testFilePattern?: string[];
37
34
  }
38
35
  /**
39
- * Runtime list of every {@link CreateSidebaseEslintOptions} key, for config
40
- * validation/iteration. The `satisfies Record<keyof ..., 0>` map makes this
41
- * exhaustive: adding a future option to the interface fails to compile until
42
- * the key is listed here too, so the validator allow-list can never drift.
36
+ * Runtime list of every {@link CreateSidebaseEslintOptions} key. The `satisfies` map
37
+ * keeps it exhaustive: a new option fails to compile until it is listed here, so the
38
+ * validator allow-list cannot drift.
43
39
  */
44
40
  declare const ESLINT_OPTION_KEYS: string[];
45
41
 
@@ -1,7 +1,4 @@
1
- /**
2
- * Options for the published ESLint factory (`@sidebase/base-config/eslint`).
3
- * Covers the per-repo variance found across the consuming repos.
4
- */
1
+ /** Options for the published ESLint factory (`@sidebase/base-config/eslint`). */
5
2
  interface CreateSidebaseEslintOptions {
6
3
  /** Zod `.extend()/.merge()/.passthrough()` bans and/or `import * as z` enforcement. Default `"none"` (opt-in). */
7
4
  zod?: "full" | "import-style" | "none";
@@ -36,10 +33,9 @@ interface CreateSidebaseEslintOptions {
36
33
  testFilePattern?: string[];
37
34
  }
38
35
  /**
39
- * Runtime list of every {@link CreateSidebaseEslintOptions} key, for config
40
- * validation/iteration. The `satisfies Record<keyof ..., 0>` map makes this
41
- * exhaustive: adding a future option to the interface fails to compile until
42
- * the key is listed here too, so the validator allow-list can never drift.
36
+ * Runtime list of every {@link CreateSidebaseEslintOptions} key. The `satisfies` map
37
+ * keeps it exhaustive: a new option fails to compile until it is listed here, so the
38
+ * validator allow-list cannot drift.
43
39
  */
44
40
  declare const ESLINT_OPTION_KEYS: string[];
45
41
 
package/docs/migration.md CHANGED
@@ -334,10 +334,10 @@ the managed file and is reverted on the next sync.
334
334
  warns you. `streamctl check` stays green, the file looks deliberate, and a package whose
335
335
  install scripts are blocked still installs.
336
336
 
337
- Be precise about the consequence, because it is narrower than it first appears. The usual
338
- suspects (`sharp`, `@tailwindcss/oxide`, `unrs-resolver`, `@parcel/watcher`) ship their
339
- native binary as a prebuilt optional dependency, so on a platform with a prebuild they keep
340
- working whether or not they are approved, cold install included. Verified across pnpm
337
+ The consequence is narrower than it first appears. The usual suspects (`sharp`,
338
+ `@tailwindcss/oxide`, `unrs-resolver`, `@parcel/watcher`) ship their native binary as a
339
+ prebuilt optional dependency, so on a platform with a prebuild they keep working whether or
340
+ not they are approved, cold install included. Verified across pnpm
341
341
  10.28.1, 10.29.1 and 10.29.3. Where the loss is real:
342
342
 
343
343
  - architectures with no prebuild, where the binary genuinely has to be compiled
@@ -546,13 +546,12 @@ too, so on first sync the payload appends its block to whatever your repository
546
546
  Your existing lines stay above it. Nothing is overwritten and nothing is reported.
547
547
 
548
548
  Most of the leftovers are harmless. Ignore patterns are additive, so a duplicated `node_modules`
549
- or `.env` above the block is 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.
549
+ or `.env` above the block is inert; it changes nothing about what the build context carries.
550
+ Delete them anyway, and not for tidiness. The detector below is the only thing that will ever
551
+ tell you a payload entry has started shadowing something of yours, and it is not a one-shot
552
+ check. A future payload version can add an entry that collides with a line you kept above the
553
+ block. A file with leftover duplicates reads `AFFECTED` forever, so it can no longer distinguish
554
+ that from the noise. Clearing them keeps the check able to answer next time.
556
555
 
557
556
  **The case that bites is a negation.** `.dockerignore` is last-match-wins, and the managed block
558
557
  is appended at the BOTTOM, so every payload entry beats anything you wrote above it. A repository
@@ -822,5 +821,5 @@ keeps its own command this way.
822
821
 
823
822
  Nothing here is destructive if you work on a branch. `sync` rewrites tracked files, so
824
823
  `git diff` shows everything it did and `git restore` undoes it. Keep the migration on its
825
- own branch and its own commit so that if the sync diff turns out to be larger than
826
- expected, reverting it is one operation rather than an archaeology exercise.
824
+ own branch and its own commit, so that if the sync diff turns out to be larger than
825
+ expected, reverting it is one operation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sidebase/base-config",
3
- "version": "0.2.0",
3
+ "version": "0.2.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",
@@ -10,6 +10,15 @@ follow. They are the same across every repo in the fleet.
10
10
  - Never commit secrets, credentials, or generated build artifacts.
11
11
  - Keep all code, comments and identifiers in English.
12
12
 
13
+ ## Comments & wording
14
+
15
+ - Keep comments short. Say WHY the code is the way it is, not what it plainly does.
16
+ - ASCII only: no em dash, en dash, arrow, ellipsis, curly quote, or emoji.
17
+ - One idea per sentence. Cut filler openers ("It is worth noting") and inflated words
18
+ ("leverage", "utilize", "robust", "seamless").
19
+ - Do not leave a comment describing an alternative you rejected, or internal ticket
20
+ shorthand a reader cannot resolve.
21
+
13
22
  ## Types & correctness
14
23
 
15
24
  - Use precise types; avoid `any` and unchecked casts. Parse and validate external input at the boundary instead of trusting it downstream.
@@ -12,16 +12,15 @@ tests
12
12
  .env
13
13
  .env.*
14
14
  !.env.example
15
- # The build-stage COPY globs `.npmrc*`; keep token-bearing backups
16
- # (`.npmrc.local`, `.npmrc.bak`, ...) out of the build context. `.npmrc` itself
17
- # is intentionally allowed through.
15
+ # The build stage globs `.npmrc*`, so keep token-bearing backups like
16
+ # `.npmrc.local` out of the build context. `.npmrc` itself is allowed through.
18
17
  .npmrc.*
19
18
  .DS_Store
20
- # Local dev state 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.
19
+ # Local dev state from `pnpm db` or `docker compose up`. `prisma/pglite-data` is
20
+ # tens of MB, and root-owned `prisma/local.postgres_data` fails a rootless build.
21
+ # Root-anchored so `src/path/local.ts` still reaches the build context.
24
22
  prisma/client
25
23
  prisma/pglite-data
26
24
  pgliteHealthz
27
- **/local.*
25
+ local.*
26
+ prisma/local.*
@@ -1,18 +1,11 @@
1
- # Managed by streamctl (full-own, render: automation-upgrade). OFF BY DEFAULT: this
2
- # file only lands when a repo sets `automation: { upgradePr: true }` in
3
- # streamctl.config.ts. See the payload README "Upgrade-PR workflow" for enabling.
1
+ # Managed by streamctl. Off by default; lands only when a repo sets
2
+ # `automation: { upgradePr: true }`. See the README section "Upgrade-PR workflow".
4
3
  #
5
- # TOKEN: the PR is opened with `secrets.STREAMCTL_PR_TOKEN` (a GitHub App
6
- # installation token or a machine-account PAT), NOT the default `GITHUB_TOKEN`.
7
- # A PR created with `GITHUB_TOKEN` does NOT trigger `pull_request` workflows, so
8
- # this repo's own `check` gate would never run on the bot's PR. The bot identity
9
- # for the fleet is still being decided; treat this file as inert and provide the
10
- # secret before enabling. No registry token is needed: the payload is public npm.
11
- # A repo with OTHER private dependencies must opt this file out
12
- # (`files: { ".github/workflows/streamctl-upgrade.yml": "off" }`) and wire its own auth.
4
+ # Needs `secrets.STREAMCTL_PR_TOKEN`: a PR opened with `GITHUB_TOKEN` does not trigger
5
+ # `pull_request` workflows, so the repo's own `check` gate would never run on it.
13
6
  #
14
- # Actions are pinned by full commit SHA (this workflow handles a token); the version
15
- # tag is in the trailing comment.
7
+ # A repo with private dependencies must opt this file out and wire its own auth:
8
+ # `files: { ".github/workflows/streamctl-upgrade.yml": "off" }`.
16
9
  name: streamctl upgrade
17
10
 
18
11
  on:
@@ -20,8 +13,8 @@ on:
20
13
  - cron: "0 6 * * 1" # Mondays 06:00 UTC
21
14
  workflow_dispatch:
22
15
 
23
- # Least privilege: every PR/issue write below uses STREAMCTL_PR_TOKEN, so the
24
- # default token only needs to read the repo for checkout.
16
+ # Every PR and issue write below uses STREAMCTL_PR_TOKEN, so the default token
17
+ # only needs to read the repo for checkout.
25
18
  permissions:
26
19
  contents: read
27
20
 
@@ -32,9 +25,8 @@ jobs:
32
25
  upgrade:
33
26
  runs-on: ubuntu-latest
34
27
  steps:
35
- # A bot identity is REQUIRED for the PR (see the header). To mint an App token
36
- # in-workflow instead of storing a PAT, uncomment and wire the App credentials,
37
- # then use `${{ steps.bot.outputs.token }}` in place of STREAMCTL_PR_TOKEN below:
28
+ # To mint an App token in-workflow instead of storing a PAT, uncomment this
29
+ # and use `${{ steps.bot.outputs.token }}` in place of STREAMCTL_PR_TOKEN:
38
30
  #
39
31
  # - id: bot
40
32
  # uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
@@ -59,8 +51,7 @@ jobs:
59
51
  - name: Install dependencies
60
52
  run: pnpm install --frozen-lockfile
61
53
 
62
- # Exit codes drive the branch: 4 = an update is available (proceed); 0 = up to
63
- # date (stop, no PR); 3 = the repo already has drift (open an issue, not a PR).
54
+ # Exit codes: 4 = update available, 0 = up to date, 3 = repo already drifted.
64
55
  - id: check
65
56
  name: Check for a payload update
66
57
  run: |
@@ -91,9 +82,8 @@ jobs:
91
82
  set -e
92
83
  cat streamctl-upgrade.json
93
84
 
94
- # exit 2 = the chained sync hit conflicts and ROLLED BACK. Re-run bumping only the
95
- # pin + devDependency (no sync) so the PR carries a reviewable diff a human then
96
- # reconciles with `streamctl sync --interactive`.
85
+ # Exit 2 means the sync hit conflicts and rolled back. Bump only the pin and
86
+ # devDependency so the PR carries a diff a human reconciles interactively.
97
87
  - name: Prepare a plan-only bump (interactive sync required)
98
88
  if: ${{ steps.upgrade.outputs.code == '2' }}
99
89
  run: pnpm streamctl upgrade --no-install --json > streamctl-upgrade.json
@@ -11,9 +11,10 @@ pglite-debug.log
11
11
  prisma/*.db
12
12
  prisma/migrations/dev
13
13
  .DS_Store
14
- # streamctl local scratch dirs/files (e.g. local.concept) stay untracked.
15
- local.*
16
- local.*/
17
- # CLAUDE.md is managed by streamctl; a user-global gitignore that ignores the
18
- # filename everywhere would silently untrack it. Force-allow it here.
14
+ # streamctl scratch. Root-anchored so `src/path/local.ts` stays tracked.
15
+ /local.*
16
+ /local.*/
17
+ prisma/local.*
18
+ # CLAUDE.md is managed by streamctl. A user-global gitignore matching the name
19
+ # everywhere would silently untrack it, so force-allow it.
19
20
  !CLAUDE.md
@@ -3,8 +3,9 @@
3
3
  "browser": true
4
4
  },
5
5
  "ignorePatterns": [
6
- "local.*",
7
- "local.*/"
6
+ "/local.*",
7
+ "/local.*/",
8
+ "prisma/local.*"
8
9
  ],
9
10
  "plugins": [
10
11
  "unicorn",
@@ -1,22 +1,15 @@
1
- # Managed by streamctl (full-own, render: security). pnpm's own settings file.
1
+ # Managed by streamctl. pnpm's own settings file.
2
2
  #
3
- # SUPPLY-CHAIN COOLDOWN: a version must have been published for ${MIN_RELEASE_AGE}
4
- # minutes before pnpm will resolve it, so a compromised release has time to be caught
5
- # and yanked before the fleet installs it. Requires pnpm >= 10.16 (baseline: 10.28.1);
6
- # it gates fresh resolution only, so `--frozen-lockfile` installs are unaffected.
7
- # `@sidebase/*` is exempt: the first-party scope (this payload + the streamctl CLI),
8
- # published by the org itself, so a payload release reaches the fleet the same day.
3
+ # Cooldown: a version must be ${MIN_RELEASE_AGE} minutes old before pnpm resolves it,
4
+ # giving a compromised release time to be yanked. Needs pnpm >= 10.16. Gates fresh
5
+ # resolution only, so `--frozen-lockfile` is unaffected. `@sidebase/*` is exempt.
9
6
  #
10
- # `packages: []` is REQUIRED, not decorative: pnpm defaults it to `**` whenever a
11
- # pnpm-workspace.yaml exists, which would silently promote every nested package.json
12
- # (test fixtures, examples) to a workspace project and break `--frozen-lockfile`.
13
- # A real monorepo owns this key itself, so opt the file out with
14
- # `files: { "pnpm-workspace.yaml": "off" }` in .streamctl/config.ts.
7
+ # `packages: []` is required: pnpm otherwise defaults it to `**`, promoting every nested
8
+ # package.json to a workspace project and breaking `--frozen-lockfile`. A monorepo should
9
+ # opt out with `files: { "pnpm-workspace.yaml": "off" }`.
15
10
  #
16
- # `onlyBuiltDependencies` is the postinstall-script allowlist: everything else installs
17
- # with its lifecycle scripts blocked. The baseline covers what the fleet universally
18
- # needs; add extras via `pnpm: { onlyBuiltDependencies: [...] }` rather than running
19
- # `pnpm approve-builds` (which writes here and would be reverted on the next sync).
11
+ # `onlyBuiltDependencies` allowlists postinstall scripts. Add extras through the `pnpm`
12
+ # knob, not `pnpm approve-builds`, which writes here and is reverted on the next sync.
20
13
  packages: []
21
14
  minimumReleaseAge: ${MIN_RELEASE_AGE}
22
15
  minimumReleaseAgeExclude:
@@ -1,7 +1,7 @@
1
1
  import { defineNuxtBaseConfig } from '@sidebase/base-config'
2
2
 
3
- // Scaffolded by `streamctl init`. `defineNuxtBaseConfig` gives typed editor
4
- // inference for this payload's knobs; the CLI validates the file on every sync.
3
+ // Scaffolded by `streamctl init`. `defineNuxtBaseConfig` types the knobs below.
4
+ // The CLI validates this file on every sync.
5
5
  export default defineNuxtBaseConfig({
6
6
  package: '__PACKAGE__',
7
7
  base: '__BASE__',