@sidebase/base-config 0.1.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.
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 };
@@ -1,5 +1,5 @@
1
- import { C as CreateSidebaseEslintOptions } from '../shared/base-config.CuUhyvQo.mjs';
2
- export { E as ESLINT_OPTION_KEYS } from '../shared/base-config.CuUhyvQo.mjs';
1
+ import { C as CreateSidebaseEslintOptions } from '../shared/base-config.BoberVUk.mjs';
2
+ export { E as ESLINT_OPTION_KEYS } from '../shared/base-config.BoberVUk.mjs';
3
3
  import antfu, { TypedFlatConfigItem } from '@antfu/eslint-config';
4
4
 
5
5
  /** {@link CreateSidebaseEslintOptions} with all defaults applied + the computed type-aware gate. */
@@ -20,10 +20,7 @@ interface ResolvedEslintOptions {
20
20
  testFilePattern: string[];
21
21
  typeAware: boolean;
22
22
  }
23
- /**
24
- * Whether type-aware linting is on: gated behind `LINT_TYPEAWARE=true` so the
25
- * slower type-aware pass stays opt-in (CI/local override).
26
- */
23
+ /** Type-aware linting is gated behind `LINT_TYPEAWARE=true` because it is slow. */
27
24
  declare function isTypeAware(): boolean;
28
25
  /** Apply the documented option defaults and resolve the type-aware gate. */
29
26
  declare function resolveEslintOptions(options?: CreateSidebaseEslintOptions): ResolvedEslintOptions;
@@ -1,5 +1,5 @@
1
- import { C as CreateSidebaseEslintOptions } from '../shared/base-config.CuUhyvQo.js';
2
- export { E as ESLINT_OPTION_KEYS } from '../shared/base-config.CuUhyvQo.js';
1
+ import { C as CreateSidebaseEslintOptions } from '../shared/base-config.BoberVUk.js';
2
+ export { E as ESLINT_OPTION_KEYS } from '../shared/base-config.BoberVUk.js';
3
3
  import antfu, { TypedFlatConfigItem } from '@antfu/eslint-config';
4
4
 
5
5
  /** {@link CreateSidebaseEslintOptions} with all defaults applied + the computed type-aware gate. */
@@ -20,10 +20,7 @@ interface ResolvedEslintOptions {
20
20
  testFilePattern: string[];
21
21
  typeAware: boolean;
22
22
  }
23
- /**
24
- * Whether type-aware linting is on: gated behind `LINT_TYPEAWARE=true` so the
25
- * slower type-aware pass stays opt-in (CI/local override).
26
- */
23
+ /** Type-aware linting is gated behind `LINT_TYPEAWARE=true` because it is slow. */
27
24
  declare function isTypeAware(): boolean;
28
25
  /** Apply the documented option defaults and resolve the type-aware gate. */
29
26
  declare function resolveEslintOptions(options?: CreateSidebaseEslintOptions): ResolvedEslintOptions;
@@ -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
@@ -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
@@ -333,10 +334,10 @@ the managed file and is reverted on the next sync.
333
334
  warns you. `streamctl check` stays green, the file looks deliberate, and a package whose
334
335
  install scripts are blocked still installs.
335
336
 
336
- Be precise about the consequence, because it is narrower than it first appears. The usual
337
- suspects (`sharp`, `@tailwindcss/oxide`, `unrs-resolver`, `@parcel/watcher`) ship their
338
- native binary as a prebuilt optional dependency, so on a platform with a prebuild they keep
339
- working whether or not they are approved, cold install included. Verified across pnpm
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
340
341
  10.28.1, 10.29.1 and 10.29.3. Where the loss is real:
341
342
 
342
343
  - architectures with no prebuild, where the binary genuinely has to be compiled
@@ -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,67 @@ 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 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.
555
+
556
+ **The case that bites is a negation.** `.dockerignore` is last-match-wins, and the managed block
557
+ is appended at the BOTTOM, so every payload entry beats anything you wrote above it. A repository
558
+ that deliberately un-ignored something now silently loses that:
559
+
560
+ ```
561
+ !tests <- yours, above the block. Now dead.
562
+
563
+ # BEGIN streamctl MANAGED BLOCK dockerignore
564
+ ...
565
+ tests <- payload's, below yours. Wins.
566
+ # END streamctl MANAGED BLOCK dockerignore
567
+ ```
568
+
569
+ The build context stops carrying `tests/`, and the failure surfaces later and somewhere else --
570
+ as a `COPY` that lands nothing, or an in-image test run that cannot find its fixtures. The build
571
+ itself succeeds.
572
+
573
+ Check what sits above the block:
574
+
575
+ ```sh
576
+ awk '/# BEGIN streamctl MANAGED BLOCK dockerignore/{exit} {print}' .dockerignore \
577
+ | command grep -qv '^[[:space:]]*\(#.*\)\?$' && echo AFFECTED || echo clean
578
+ ```
579
+
580
+ As in step 8, use `command grep` -- the detector reads its exit code as its entire answer, and a
581
+ wrapped `grep` can report `clean` on an affected file.
582
+
583
+ `AFFECTED` does not mean something broke; it means read those lines and decide. Anything you
584
+ actually need, and every negation without exception, moves **below** the `# END` marker, which is
585
+ the supported extension point:
586
+
587
+ ```
588
+ # END streamctl MANAGED BLOCK dockerignore
589
+
590
+ !tests
591
+ my-project-specific-dir
592
+ ```
593
+
594
+ Finish by re-running the `AFFECTED`/`clean` detector above; it only says `clean` once nothing but
595
+ comments and blank lines is left above the block, so clear the harmless duplicates out too rather
596
+ than keeping a detector that can never go green.
597
+
598
+ **`streamctl check` will never flag any of this.** Block drift detection inspects only the marker
599
+ region, so the block is present and correct and the check exits 0 while your dead negation sits
600
+ three lines above it, permanently. This is the one step here that no tooling will ever remind you
601
+ to do.
602
+
542
603
  ### 9. Clean up what the payload no longer manages
543
604
 
544
605
  Two different kinds of leftover, with the same cause: streamctl manages by path, so
@@ -760,5 +821,5 @@ keeps its own command this way.
760
821
 
761
822
  Nothing here is destructive if you work on a branch. `sync` rewrites tracked files, so
762
823
  `git diff` shows everything it did and `git restore` undoes it. Keep the migration on its
763
- own branch and its own commit so that if the sync diff turns out to be larger than
764
- expected, reverting it is one operation rather than an archaeology exercise.
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.1.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",
@@ -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
+ }
@@ -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.
@@ -6,13 +6,21 @@ npm-debug*
6
6
  .output
7
7
  .data
8
8
  dist
9
+ test
9
10
  tests
10
11
  *.log
11
12
  .env
12
13
  .env.*
13
14
  !.env.example
14
- # The build-stage COPY globs `.npmrc*`; keep token-bearing backups
15
- # (`.npmrc.local`, `.npmrc.bak`, ...) out of the build context. `.npmrc` itself
16
- # 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.
17
17
  .npmrc.*
18
18
  .DS_Store
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.
22
+ prisma/client
23
+ prisma/pglite-data
24
+ pgliteHealthz
25
+ local.*
26
+ prisma/local.*