@pikku/cli 0.12.27 → 0.12.28

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.
Files changed (86) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-Ca6xJwNm.js +229 -0
  3. package/console-app/assets/{index-CQ29NRyR.css → index-DwUzVI5k.css} +1 -1
  4. package/console-app/index.html +2 -2
  5. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  7. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-channel.js +1 -1
  9. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  10. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  12. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  13. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  15. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  16. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  17. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  18. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  19. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  20. package/dist/.pikku/function/pikku-functions-meta.gen.json +155 -133
  21. package/dist/.pikku/function/pikku-functions.gen.js +3 -1
  22. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  23. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  24. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  25. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  26. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  27. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  28. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  29. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  30. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  31. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  32. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  33. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  34. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  35. package/dist/.pikku/pikku-types.gen.js +1 -1
  36. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  37. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  38. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  39. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.json +4 -0
  40. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  42. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  43. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +10 -9
  44. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  45. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  46. package/dist/.pikku/schemas/register.gen.js +7 -7
  47. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  48. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  49. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  50. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  51. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  52. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  53. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  54. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  55. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  56. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  57. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  58. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +8 -2
  59. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  60. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  61. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  62. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  63. package/dist/bin/pikku-bin.mjs +2 -2
  64. package/dist/src/fabric/functions/validate.function.js +23 -7
  65. package/dist/src/functions/commands/tests-coverage.js +4 -2
  66. package/dist/src/functions/db/annotation-parser.d.ts +7 -7
  67. package/dist/src/functions/db/annotation-parser.js +61 -11
  68. package/dist/src/functions/db/db-codegen.d.ts +4 -0
  69. package/dist/src/functions/db/db-codegen.js +117 -15
  70. package/dist/src/functions/db/local-db.d.ts +7 -1
  71. package/dist/src/functions/db/local-db.js +137 -37
  72. package/dist/src/functions/db/postgres/postgres-introspector.d.ts +8 -2
  73. package/dist/src/functions/db/postgres/postgres-introspector.js +26 -14
  74. package/dist/src/functions/wirings/auth/pikku-command-auth.d.ts +1 -0
  75. package/dist/src/functions/wirings/auth/pikku-command-auth.js +22 -0
  76. package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +1 -0
  77. package/dist/src/functions/wirings/auth/serialize-auth-gen.js +115 -0
  78. package/dist/src/functions/workflows/all.workflow.js +1 -0
  79. package/dist/src/scaffold/rpc-remote.gen.js +1 -1
  80. package/dist/src/utils/pikku-cli-config.js +3 -0
  81. package/dist/tsconfig.tsbuildinfo +1 -1
  82. package/package.json +7 -4
  83. package/skills/pikku-auth-js/SKILL.md +137 -117
  84. package/skills/pikku-middleware/SKILL.md +2 -2
  85. package/skills/pikku-services/SKILL.md +44 -7
  86. package/console-app/assets/index-BERGDBO9.js +0 -228
@@ -111,8 +111,8 @@ export const pikkuTestsCoverage = pikkuSessionlessFunc({
111
111
  logger.error(`Verbose metadata not found at ${verboseMetaPath}. Run 'pikku all' first.`);
112
112
  process.exit(1);
113
113
  }
114
- const coverageFinal = join(functionsDir, 'coverage', 'coverage-final.json');
115
- const outDir = join(ftestDir, 'coverage');
114
+ const coverageFinal = join(functionsDir, '.coverage', 'coverage-final.json');
115
+ const outDir = join(ftestDir, '.coverage');
116
116
  const outFile = join(outDir, 'function-coverage.json');
117
117
  if (!noRun) {
118
118
  const findBin = (name, searchFrom) => {
@@ -153,6 +153,8 @@ export const pikkuTestsCoverage = pikkuSessionlessFunc({
153
153
  'src',
154
154
  '--include',
155
155
  'src/**',
156
+ '--report-dir',
157
+ '.coverage',
156
158
  '--reporter',
157
159
  'json',
158
160
  '--reporter',
@@ -1,5 +1,5 @@
1
1
  import type { ColumnKind } from './coercion-plugin.js';
2
- type Classification = 'public' | 'private' | 'secret';
2
+ type Classification = 'public' | 'private' | 'pii' | 'secret';
3
3
  type AnonymizeStrategy = 'fake:email' | 'fake:name' | 'hash' | 'keep' | null;
4
4
  export interface ColAnnotation {
5
5
  kind?: ColumnKind;
@@ -19,13 +19,13 @@ export declare function annotationFromName(colName: string): {
19
19
  kind: ColumnKind;
20
20
  } | null;
21
21
  /**
22
- * Parse `-- @bool | @date | @json [TsType] | @public | @private[:strategy] | @secret[:strategy]`
22
+ * Parse `-- @bool | @date | @json [TsType] | @public | @private[:strategy] | @pii[:strategy] | @secret[:strategy]`
23
23
  * inline annotations from migration SQL files in `migrationsDir`.
24
- *
25
- * Multiple annotations on the same comment line are supported, e.g.:
26
- * `deleted_at TIMESTAMP -- @date @private:keep`
27
- *
28
- * Covers both CREATE TABLE body lines and ALTER TABLE ... ADD [COLUMN] statements.
29
24
  */
30
25
  export declare function parseAnnotations(migrationsDir: string): AnnotationMap;
26
+ /**
27
+ * Load annotations for a project. Tries `db/annotations.ts` sidecar first;
28
+ * falls back to SQL comment parsing from `migrationsDir` if not found.
29
+ */
30
+ export declare function loadAnnotations(rootDir: string, migrationsDir?: string): AnnotationMap;
31
31
  export {};
@@ -1,4 +1,4 @@
1
- import { readFileSync, readdirSync } from 'node:fs';
1
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  /**
4
4
  * Determine column kind from naming conventions:
@@ -12,11 +12,51 @@ export function annotationFromName(colName) {
12
12
  return { kind: 'bool' };
13
13
  return null;
14
14
  }
15
+ // ── Load from db/annotations.gen.json sidecar ────────────────────────────────
16
+ /**
17
+ * Try to load annotations from a `db/annotations.gen.json` sidecar generated
18
+ * by `yarn db:types`. Returns null if the file doesn't exist.
19
+ *
20
+ * The JSON file uses snake_case keys (raw DB names) so it can be read
21
+ * directly without any conversion. It is emitted by bin/db-classify.ts.
22
+ */
23
+ function loadAnnotationsSidecar(rootDir) {
24
+ const jsonPath = join(rootDir, 'db', 'annotations.gen.json');
25
+ if (!existsSync(jsonPath))
26
+ return null;
27
+ try {
28
+ const raw = JSON.parse(readFileSync(jsonPath, 'utf8'));
29
+ const result = {};
30
+ for (const [table, cols] of Object.entries(raw)) {
31
+ result[table] = {};
32
+ for (const [col, ann] of Object.entries(cols)) {
33
+ if (!ann)
34
+ continue;
35
+ const entry = {};
36
+ if (ann.kind === 'bool' || ann.kind === 'date' || ann.kind === 'json')
37
+ entry.kind = ann.kind;
38
+ if (ann.tsType)
39
+ entry.tsType = ann.tsType;
40
+ const vis = ann.visibility;
41
+ if (vis === 'public' || vis === 'private' || vis === 'secret')
42
+ entry.classification = vis;
43
+ result[table][col] = entry;
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ // ── SQL comment parsing (fallback) ───────────────────────────────────────────
15
53
  function parseStrategy(s) {
16
54
  if (!s)
17
55
  return null;
18
56
  const valid = ['fake:email', 'fake:name', 'hash', 'keep'];
19
- return valid.includes(s) ? s : null;
57
+ return valid.includes(s)
58
+ ? s
59
+ : null;
20
60
  }
21
61
  function parseComment(comment) {
22
62
  const ann = {};
@@ -34,26 +74,25 @@ function parseComment(comment) {
34
74
  ann.tsType = jsonM[1].trim();
35
75
  }
36
76
  }
37
- const classM = comment.match(/@(public|private|secret)(?::([^\s@]+))?/i);
77
+ const classM = comment.match(/@(public|private|pii|secret)(?::([^\s@]+))?/i);
38
78
  if (classM) {
39
79
  ann.classification = classM[1].toLowerCase();
40
- ann.anonymize = parseStrategy(classM[2]);
80
+ const strategy = parseStrategy(classM[2]);
81
+ if (strategy !== null)
82
+ ann.anonymize = strategy;
41
83
  }
42
84
  return ann;
43
85
  }
44
86
  /**
45
- * Parse `-- @bool | @date | @json [TsType] | @public | @private[:strategy] | @secret[:strategy]`
87
+ * Parse `-- @bool | @date | @json [TsType] | @public | @private[:strategy] | @pii[:strategy] | @secret[:strategy]`
46
88
  * inline annotations from migration SQL files in `migrationsDir`.
47
- *
48
- * Multiple annotations on the same comment line are supported, e.g.:
49
- * `deleted_at TIMESTAMP -- @date @private:keep`
50
- *
51
- * Covers both CREATE TABLE body lines and ALTER TABLE ... ADD [COLUMN] statements.
52
89
  */
53
90
  export function parseAnnotations(migrationsDir) {
54
91
  let files;
55
92
  try {
56
- files = readdirSync(migrationsDir).filter((f) => f.endsWith('.sql')).sort();
93
+ files = readdirSync(migrationsDir)
94
+ .filter((f) => f.endsWith('.sql'))
95
+ .sort();
57
96
  }
58
97
  catch {
59
98
  return {};
@@ -91,3 +130,14 @@ export function parseAnnotations(migrationsDir) {
91
130
  }
92
131
  return result;
93
132
  }
133
+ // ── Public entry point ────────────────────────────────────────────────────────
134
+ /**
135
+ * Load annotations for a project. Tries `db/annotations.ts` sidecar first;
136
+ * falls back to SQL comment parsing from `migrationsDir` if not found.
137
+ */
138
+ export function loadAnnotations(rootDir, migrationsDir) {
139
+ const sidecar = loadAnnotationsSidecar(rootDir);
140
+ if (sidecar)
141
+ return sidecar;
142
+ return migrationsDir ? parseAnnotations(migrationsDir) : {};
143
+ }
@@ -3,16 +3,20 @@ export interface CodegenOptions {
3
3
  outFile: string;
4
4
  coercionFile: string;
5
5
  manifestFile?: string;
6
+ classificationMapFile?: string;
6
7
  camelCase?: boolean;
8
+ rootDir?: string;
7
9
  migrationsDir?: string;
8
10
  }
9
11
  export interface CodegenResult {
10
12
  outFile: string;
11
13
  coercionFile: string;
12
14
  manifestFile?: string;
15
+ classificationMapFile?: string;
13
16
  written: boolean;
14
17
  coercionWritten: boolean;
15
18
  manifestWritten: boolean;
19
+ classificationMapWritten: boolean;
16
20
  tables: string[];
17
21
  }
18
22
  /**
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
- import { parseAnnotations, annotationFromName, } from './annotation-parser.js';
3
+ import { loadAnnotations, parseAnnotations, annotationFromName, } from './annotation-parser.js';
4
4
  // ─── Name helpers ─────────────────────────────────────────────────────────────
5
5
  function snakeToPascal(name) {
6
6
  return name
@@ -23,7 +23,9 @@ function mapType(sqlType) {
23
23
  return 'string';
24
24
  if (upper.includes('BLOB') || upper === 'BYTEA')
25
25
  return 'Buffer';
26
- if (upper.includes('REAL') || upper.includes('FLOA') || upper.includes('DOUB'))
26
+ if (upper.includes('REAL') ||
27
+ upper.includes('FLOA') ||
28
+ upper.includes('DOUB'))
27
29
  return 'number';
28
30
  if (upper.includes('NUMERIC') || upper.includes('DECIMAL'))
29
31
  return 'number';
@@ -88,7 +90,11 @@ function columnTypeExpression(col, annotation, classification) {
88
90
  return `Generated<${base}${nullable ? ' | null' : ''}>`;
89
91
  return nullable ? `${base} | null` : base;
90
92
  }
91
- const B = classification === 'secret' ? 'Secret' : 'Private';
93
+ const B = classification === 'secret'
94
+ ? 'Secret'
95
+ : classification === 'pii'
96
+ ? 'Pii'
97
+ : 'Private';
92
98
  const sBase = selectBase(annotation, col);
93
99
  const iBase = insertBase(annotation, col);
94
100
  const selectT = nullable ? `${B}<${sBase}> | null` : `${B}<${sBase}>`;
@@ -98,9 +104,14 @@ function columnTypeExpression(col, annotation, classification) {
98
104
  const updateT = nullable ? `${iBase} | null` : iBase;
99
105
  return `ColumnType<${selectT}, ${insertT}, ${updateT}>`;
100
106
  }
107
+ /** Strip optional schema prefix (e.g. "app.user" → "user"). */
108
+ function bareTableName(name) {
109
+ const dot = name.indexOf('.');
110
+ return dot >= 0 ? name.slice(dot + 1) : name;
111
+ }
101
112
  function emitInterface(table, camelCase, explicitAnnotations) {
102
113
  const ifaceName = snakeToPascal(table.name);
103
- const tableCols = explicitAnnotations[table.name] ?? {};
114
+ const tableCols = explicitAnnotations[bareTableName(table.name)] ?? {};
104
115
  const fields = table.columns
105
116
  .map((col) => {
106
117
  const fieldName = camelCase ? snakeToCamel(col.name) : col.name;
@@ -122,7 +133,7 @@ function emitInterface(table, camelCase, explicitAnnotations) {
122
133
  function emitManifest(tables, explicitAnnotations) {
123
134
  const tableEntries = tables
124
135
  .map((table) => {
125
- const tableCols = explicitAnnotations[table.name] ?? {};
136
+ const tableCols = explicitAnnotations[bareTableName(table.name)] ?? {};
126
137
  const colEntries = table.columns
127
138
  .map((col) => {
128
139
  const ann = tableCols[col.name];
@@ -149,6 +160,63 @@ function emitManifest(tables, explicitAnnotations) {
149
160
  ``,
150
161
  ].join('\n');
151
162
  }
163
+ // ─── Classification map type emitter ─────────────────────────────────────────
164
+ /**
165
+ * Emits a `DbClassificationMap` type declaration that the developer's
166
+ * hand-authored `db/classifications.ts` must satisfy. Every table and column
167
+ * present in the current schema appears as a required key — TypeScript will
168
+ * flag added or removed columns.
169
+ */
170
+ function emitClassificationMap(tables) {
171
+ const colEntry = ` security: 'public' | 'private' | 'pii' | 'secret' | 'encrypted'\n classification?: 'fake:email' | 'fake:name' | 'hash' | 'keep'\n description?: string`;
172
+ // Group tables by schema (for postgres schema.table names)
173
+ const schemaMap = new Map();
174
+ for (const table of tables) {
175
+ const dot = table.name.indexOf('.');
176
+ const schema = dot >= 0 ? table.name.slice(0, dot) : '';
177
+ const bare = dot >= 0 ? table.name.slice(dot + 1) : table.name;
178
+ if (!schemaMap.has(schema))
179
+ schemaMap.set(schema, new Map());
180
+ schemaMap.get(schema).set(bare, table.columns.map((c) => c.name));
181
+ }
182
+ const lines = [
183
+ `// Generated by @pikku/cli — do not edit by hand.`,
184
+ `// Run \`pikku db migrate\` to refresh.`,
185
+ `// Use this type in db/classifications.ts:`,
186
+ `// import type { DbClassificationMap } from './.pikku/db/classification-map.gen.d.ts'`,
187
+ `// export const classifications = { ... } satisfies DbClassificationMap`,
188
+ ``,
189
+ `export type ColumnEntry = {`,
190
+ `${colEntry}`,
191
+ `}`,
192
+ ``,
193
+ `export type DbClassificationMap = {`,
194
+ ];
195
+ for (const [schema, tables] of schemaMap) {
196
+ if (schema) {
197
+ lines.push(` ${JSON.stringify(schema)}: {`);
198
+ for (const [table, cols] of tables) {
199
+ lines.push(` ${JSON.stringify(table)}: {`);
200
+ for (const col of cols) {
201
+ lines.push(` ${JSON.stringify(col)}: ColumnEntry`);
202
+ }
203
+ lines.push(` }`);
204
+ }
205
+ lines.push(` }`);
206
+ }
207
+ else {
208
+ for (const [table, cols] of tables) {
209
+ lines.push(` ${JSON.stringify(table)}: {`);
210
+ for (const col of cols) {
211
+ lines.push(` ${JSON.stringify(col)}: ColumnEntry`);
212
+ }
213
+ lines.push(` }`);
214
+ }
215
+ }
216
+ }
217
+ lines.push(`}`, ``);
218
+ return lines.join('\n');
219
+ }
152
220
  /**
153
221
  * Introspect `introspector` and emit:
154
222
  * - `schema.d.ts` Kysely DB type with classification brands
@@ -162,9 +230,11 @@ export async function generateSchemaTypes(introspector, options) {
162
230
  name,
163
231
  columns: await introspector.getColumns(name),
164
232
  })));
165
- const explicitAnnotations = options.migrationsDir
166
- ? parseAnnotations(options.migrationsDir)
167
- : {};
233
+ const explicitAnnotations = options.rootDir
234
+ ? loadAnnotations(options.rootDir, options.migrationsDir)
235
+ : options.migrationsDir
236
+ ? parseAnnotations(options.migrationsDir)
237
+ : {};
168
238
  // ── schema.d.ts ─────────────────────────────────────────────────────────────
169
239
  const interfaces = tables
170
240
  .map((t) => emitInterface(t, camelCase, explicitAnnotations))
@@ -188,8 +258,9 @@ export async function generateSchemaTypes(introspector, options) {
188
258
  ` ? ColumnType<S, I | undefined, U>`,
189
259
  ` : ColumnType<T, T | undefined, T>`,
190
260
  ``,
191
- `export type Private<T> = T & { readonly __pii__: 'private' }`,
192
- `export type Secret<T> = T & { readonly __pii__: 'secret' }`,
261
+ `export type Private<T> = T & { readonly __classification__: 'private' }`,
262
+ `export type Pii<T> = T & { readonly __classification__: 'pii' }`,
263
+ `export type Secret<T> = T & { readonly __classification__: 'secret' }`,
193
264
  ``,
194
265
  interfaces,
195
266
  ``,
@@ -201,7 +272,7 @@ export async function generateSchemaTypes(introspector, options) {
201
272
  // ── coercion.gen.ts ──────────────────────────────────────────────────────────
202
273
  const coercionMap = {};
203
274
  for (const table of tables) {
204
- const tableCols = explicitAnnotations[table.name] ?? {};
275
+ const tableCols = explicitAnnotations[bareTableName(table.name)] ?? {};
205
276
  for (const col of table.columns) {
206
277
  const sqlAnn = tableCols[col.name];
207
278
  const kind = sqlAnn?.kind ?? annotationFromName(col.name)?.kind;
@@ -230,28 +301,51 @@ export async function generateSchemaTypes(introspector, options) {
230
301
  ``,
231
302
  ].join('\n');
232
303
  // ── classification.gen.ts ───────────────────────────────────────────────────
233
- const manifestBody = options.manifestFile ? emitManifest(tables, explicitAnnotations) : null;
304
+ const manifestBody = options.manifestFile
305
+ ? emitManifest(tables, explicitAnnotations)
306
+ : null;
307
+ // ── classification-map.gen.d.ts ──────────────────────────────────────────────
308
+ const classificationMapBody = options.classificationMapFile
309
+ ? emitClassificationMap(tables)
310
+ : null;
234
311
  // ── write files ───────────────────────────────────────────────────────────────
235
312
  let existingSchema = null;
236
313
  let existingCoercion = null;
237
314
  let existingManifest = null;
315
+ let existingClassificationMap = null;
238
316
  try {
239
317
  existingSchema = readFileSync(options.outFile, 'utf8');
240
318
  }
241
- catch { /* ok */ }
319
+ catch {
320
+ /* ok */
321
+ }
242
322
  try {
243
323
  existingCoercion = readFileSync(options.coercionFile, 'utf8');
244
324
  }
245
- catch { /* ok */ }
325
+ catch {
326
+ /* ok */
327
+ }
246
328
  if (options.manifestFile) {
247
329
  try {
248
330
  existingManifest = readFileSync(options.manifestFile, 'utf8');
249
331
  }
250
- catch { /* ok */ }
332
+ catch {
333
+ /* ok */
334
+ }
335
+ }
336
+ if (options.classificationMapFile) {
337
+ try {
338
+ existingClassificationMap = readFileSync(options.classificationMapFile, 'utf8');
339
+ }
340
+ catch {
341
+ /* ok */
342
+ }
251
343
  }
252
344
  const schemaChanged = existingSchema !== schemaBody;
253
345
  const coercionChanged = existingCoercion !== coercionBody;
254
346
  const manifestChanged = manifestBody !== null && existingManifest !== manifestBody;
347
+ const classificationMapChanged = classificationMapBody !== null &&
348
+ existingClassificationMap !== classificationMapBody;
255
349
  if (schemaChanged) {
256
350
  mkdirSync(dirname(options.outFile), { recursive: true });
257
351
  writeFileSync(options.outFile, schemaBody, 'utf8');
@@ -264,13 +358,21 @@ export async function generateSchemaTypes(introspector, options) {
264
358
  mkdirSync(dirname(options.manifestFile), { recursive: true });
265
359
  writeFileSync(options.manifestFile, manifestBody, 'utf8');
266
360
  }
361
+ if (classificationMapChanged &&
362
+ options.classificationMapFile &&
363
+ classificationMapBody) {
364
+ mkdirSync(dirname(options.classificationMapFile), { recursive: true });
365
+ writeFileSync(options.classificationMapFile, classificationMapBody, 'utf8');
366
+ }
267
367
  return {
268
368
  outFile: options.outFile,
269
369
  coercionFile: options.coercionFile,
270
370
  manifestFile: options.manifestFile,
371
+ classificationMapFile: options.classificationMapFile,
271
372
  written: schemaChanged,
272
373
  coercionWritten: coercionChanged,
273
374
  manifestWritten: manifestChanged,
375
+ classificationMapWritten: classificationMapChanged,
274
376
  tables: tables.map((t) => t.name),
275
377
  };
276
378
  }
@@ -5,11 +5,14 @@ import { type ZodCodegenResult } from './zod-codegen.js';
5
5
  import { type SeedResult } from './sqlite/seed.js';
6
6
  import type { UserConfigShape } from '../commands/db-shared.js';
7
7
  interface ResolvedDbBase {
8
+ rootDir: string;
8
9
  migrationsDir: string;
9
- seedFile: string;
10
10
  schemaFile: string;
11
11
  coercionFile: string;
12
12
  manifestFile: string;
13
+ classificationMapFile: string;
14
+ classificationsFile: string;
15
+ classificationsGenJsonFile: string;
13
16
  zodFile: string;
14
17
  camelCase: boolean;
15
18
  }
@@ -17,6 +20,7 @@ export interface ResolvedSqliteDb extends ResolvedDbBase {
17
20
  dialect: 'sqlite';
18
21
  dbFile: string;
19
22
  runtimeDir: string;
23
+ seedFile: string;
20
24
  }
21
25
  export interface ResolvedPostgresDb extends ResolvedDbBase {
22
26
  dialect: 'postgres';
@@ -34,6 +38,8 @@ export interface MigrateAndCodegenOutcome {
34
38
  migrate: MigrateResult;
35
39
  codegen: CodegenResult;
36
40
  zod: ZodCodegenResult;
41
+ classificationsScaffolded: boolean;
42
+ classificationsJsonWritten: boolean;
37
43
  }
38
44
  export declare function migrateAndCodegen(resolved: ResolvedDb): Promise<MigrateAndCodegenOutcome>;
39
45
  export declare function seed(resolved: ResolvedSqliteDb): Promise<SeedResult>;