@pikku/cli 0.12.34 → 0.12.36

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 (109) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-Dxl3JsMK.js +233 -0
  3. package/console-app/index.html +1 -1
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
  8. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  9. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  10. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +6 -0
  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 +2 -2
  18. package/dist/.pikku/function/pikku-function-types.gen.js +17 -3
  19. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  20. package/dist/.pikku/function/pikku-functions-meta.gen.json +238 -221
  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.gen.d.ts +1 -1
  40. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  41. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  42. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +5 -4
  43. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  44. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  45. package/dist/.pikku/schemas/register.gen.js +17 -13
  46. package/dist/.pikku/schemas/schemas/DbGenerateInput.schema.json +1 -0
  47. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  48. package/dist/.pikku/schemas/schemas/PikkuFunctionTypesInput.schema.json +1 -0
  49. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  50. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  51. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  52. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  53. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  54. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  55. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  56. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  57. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  58. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  59. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +22 -4
  60. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  61. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  62. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  63. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  64. package/dist/bin/pikku-bin.mjs +2 -2
  65. package/dist/src/cli.wiring.js +5 -0
  66. package/dist/src/fabric/functions/login.function.d.ts +1 -1
  67. package/dist/src/fabric/functions/login.function.js +1 -1
  68. package/dist/src/functions/commands/bootstrap.js +1 -1
  69. package/dist/src/functions/commands/db-generate.d.ts +1 -0
  70. package/dist/src/functions/commands/db-generate.js +45 -0
  71. package/dist/src/functions/commands/db-migrate.js +13 -1
  72. package/dist/src/functions/db/annotation-parser.d.ts +27 -16
  73. package/dist/src/functions/db/annotation-parser.js +50 -110
  74. package/dist/src/functions/db/better-auth-schema.d.ts +23 -0
  75. package/dist/src/functions/db/better-auth-schema.js +122 -0
  76. package/dist/src/functions/db/coercion-plugin.d.ts +1 -1
  77. package/dist/src/functions/db/coercion-plugin.js +4 -0
  78. package/dist/src/functions/db/db-codegen.d.ts +13 -1
  79. package/dist/src/functions/db/db-codegen.js +142 -31
  80. package/dist/src/functions/db/db-introspector.d.ts +6 -0
  81. package/dist/src/functions/db/local-db.d.ts +33 -0
  82. package/dist/src/functions/db/local-db.js +221 -79
  83. package/dist/src/functions/db/postgres/postgres-introspector.js +2 -0
  84. package/dist/src/functions/db/zod-codegen.d.ts +38 -0
  85. package/dist/src/functions/db/zod-codegen.js +153 -38
  86. package/dist/src/functions/validate/workspace-validate.js +1 -1
  87. package/dist/src/functions/wirings/auth/pikku-command-auth.js +30 -4
  88. package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +33 -1
  89. package/dist/src/functions/wirings/auth/serialize-auth-gen.js +122 -88
  90. package/dist/src/functions/wirings/auth/serialize-auth-meta.d.ts +32 -0
  91. package/dist/src/functions/wirings/auth/serialize-auth-meta.js +23 -0
  92. package/dist/src/functions/wirings/auth/serialize-auth-types.d.ts +27 -0
  93. package/dist/src/functions/wirings/auth/serialize-auth-types.js +58 -0
  94. package/dist/src/functions/wirings/functions/pikku-command-function-types.d.ts +7 -1
  95. package/dist/src/functions/wirings/functions/pikku-command-function-types.js +16 -3
  96. package/dist/src/functions/wirings/functions/pikku-command-services.d.ts +1 -1
  97. package/dist/src/functions/wirings/functions/pikku-command-services.js +9 -2
  98. package/dist/src/functions/wirings/functions/serialize-function-types.js +17 -3
  99. package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.d.ts +1 -1
  100. package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.js +2 -1
  101. package/dist/src/functions/workflows/all.workflow.js +16 -2
  102. package/dist/src/scaffold/rpc-remote.gen.js +1 -1
  103. package/dist/src/services.js +8 -0
  104. package/dist/src/utils/pikku-cli-config.js +12 -0
  105. package/dist/tsconfig.tsbuildinfo +1 -1
  106. package/package.json +4 -3
  107. package/skills/pikku-better-auth/SKILL.md +211 -0
  108. package/console-app/assets/index-DsW0T00Z.js +0 -233
  109. package/skills/pikku-auth-js/SKILL.md +0 -339
@@ -1,9 +1,10 @@
1
- import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, } from 'node:fs';
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, readdirSync, } from 'node:fs';
2
2
  import { resolve, isAbsolute, relative, dirname, join } from 'node:path';
3
- import { execSync } from 'node:child_process';
4
3
  import { createRequire } from 'node:module';
5
- import { fileURLToPath } from 'node:url';
4
+ import { runInNewContext } from 'node:vm';
5
+ import { transformSync } from 'esbuild';
6
6
  import { migrate } from './db-migrator.js';
7
+ import { loadAuthOptions, getAuthMigrations } from './better-auth-schema.js';
7
8
  import { generateSchemaTypes } from './db-codegen.js';
8
9
  import { generateZodTypes } from './zod-codegen.js';
9
10
  import { createCoercionPlugin } from './coercion-plugin.js';
@@ -42,7 +43,11 @@ export function resolveDb(userConfig, rootDir, outDir, runtimeDir) {
42
43
  classificationMapFile: join(outDir, 'db', 'classification-map.gen.d.ts'),
43
44
  schemaJsonFile: join(outDir, 'db', 'pikku-db-schema.gen.json'),
44
45
  classificationsFile: join(rootDir, 'db', 'annotations.ts'),
45
- classificationsGenJsonFile: join(outDir, 'db', 'annotations.gen.json'),
46
+ // Compiled sidecar lives beside the authored annotations.ts in db/ — this is
47
+ // where both consumers read it: the codegen's loadAnnotations() and the
48
+ // pikku-console addon (db/annotations.gen.json). Writing it into outDir
49
+ // (.pikku) would leave both readers looking at a file that never appears.
50
+ classificationsGenJsonFile: join(rootDir, 'db', 'annotations.gen.json'),
46
51
  zodFile: join(outDir, 'db', 'zod.gen.ts'),
47
52
  camelCase: true,
48
53
  });
@@ -87,6 +92,9 @@ function resolveAgainst(root, p) {
87
92
  export async function migrateAndCodegen(resolved) {
88
93
  let migrateResult;
89
94
  let codegenResult;
95
+ // Compile any authored db/annotations.ts → sidecar BEFORE codegen so edits
96
+ // reflect in a single `db migrate` (codegen reads the sidecar).
97
+ compileClassifications(resolved.classificationsFile, resolved.classificationsGenJsonFile);
90
98
  if (resolved.dialect === 'sqlite') {
91
99
  mkdirSync(dirname(resolved.dbFile), { recursive: true });
92
100
  const runtime = await loadSqliteRuntime();
@@ -103,7 +111,7 @@ export async function migrateAndCodegen(resolved) {
103
111
  schemaJsonFile: resolved.schemaJsonFile,
104
112
  camelCase: resolved.camelCase,
105
113
  rootDir: resolved.rootDir,
106
- migrationsDir: resolved.migrationsDir,
114
+ dialect: 'sqlite',
107
115
  });
108
116
  }
109
117
  finally {
@@ -129,7 +137,7 @@ export async function migrateAndCodegen(resolved) {
129
137
  schemaJsonFile: resolved.schemaJsonFile,
130
138
  camelCase: resolved.camelCase,
131
139
  rootDir: resolved.rootDir,
132
- migrationsDir: resolved.migrationsDir,
140
+ dialect: 'postgres',
133
141
  });
134
142
  }
135
143
  finally {
@@ -143,9 +151,13 @@ export async function migrateAndCodegen(resolved) {
143
151
  const zodResult = generateZodTypes({
144
152
  schemaFile: resolved.schemaFile,
145
153
  outFile: resolved.zodFile,
154
+ formats: codegenResult.zodFormats,
146
155
  });
147
156
  // ── Classifications step ──────────────────────────────────────────────────
148
- const { scaffolded, jsonWritten } = syncClassifications(resolved.classificationsFile, resolved.classificationsGenJsonFile, codegenResult.tables);
157
+ // Scaffold the authored file if missing (needs the table list), then compile
158
+ // it to the sidecar so a freshly-scaffolded file is captured too.
159
+ const scaffolded = scaffoldClassificationsFile(resolved.classificationsFile, codegenResult.tables);
160
+ const jsonWritten = compileClassifications(resolved.classificationsFile, resolved.classificationsGenJsonFile);
149
161
  return {
150
162
  migrate: migrateResult,
151
163
  codegen: codegenResult,
@@ -179,86 +191,93 @@ export function reset(resolved, rootDir) {
179
191
  }
180
192
  // ── Classification sync ───────────────────────────────────────────────────────
181
193
  /**
182
- * Loads `db/classifications.ts` via tsx, serialises it to
183
- * `.pikku/db/classifications.gen.json` for runtime consumption (console, etc.).
184
- *
185
- * If `db/classifications.ts` doesn't exist yet, writes a scaffold with every
186
- * table defaulting to `private` so the developer has a starting point.
194
+ * If `db/annotations.ts` doesn't exist yet, write a scaffold listing every
195
+ * table/column so the developer has a typed starting point. Every field of
196
+ * `ColumnEntry` is optional, so the empty-per-table scaffold is valid and means
197
+ * "everything default `private`". Returns whether a scaffold was written.
187
198
  */
188
- function syncClassifications(classificationsFile, genJsonFile, tableNames) {
189
- let scaffolded = false;
190
- if (!existsSync(classificationsFile)) {
191
- const relMap = join(dirname(classificationsFile), '..', '.pikku', 'db', 'classification-map.gen.d.ts');
192
- const relMapPosix = relMap.replace(/\\/g, '/');
193
- const groups = new Map();
194
- for (const name of tableNames) {
195
- const dot = name.indexOf('.');
196
- const schema = dot >= 0 ? name.slice(0, dot) : '';
197
- const table = dot >= 0 ? name.slice(dot + 1) : name;
198
- if (!groups.has(schema))
199
- groups.set(schema, []);
200
- groups.get(schema).push(table);
201
- }
202
- const bodyLines = [
203
- `import type { DbClassificationMap } from '${relMapPosix}'`,
204
- ``,
205
- `export const classifications = {`,
206
- ];
207
- for (const [schema, tables] of groups) {
208
- if (schema)
209
- bodyLines.push(` ${JSON.stringify(schema)}: {`);
210
- for (const table of tables) {
211
- bodyLines.push(schema
212
- ? ` ${JSON.stringify(table)}: {`
213
- : ` ${JSON.stringify(table)}: {`);
214
- bodyLines.push(schema ? ` },` : ` },`);
215
- }
216
- if (schema)
217
- bodyLines.push(` },`);
199
+ function scaffoldClassificationsFile(classificationsFile, tableNames) {
200
+ if (existsSync(classificationsFile))
201
+ return false;
202
+ const relMap = join(dirname(classificationsFile), '..', '.pikku', 'db', 'classification-map.gen.d.ts');
203
+ const relMapPosix = relMap.replace(/\\/g, '/');
204
+ const groups = new Map();
205
+ for (const name of tableNames) {
206
+ const dot = name.indexOf('.');
207
+ const schema = dot >= 0 ? name.slice(0, dot) : '';
208
+ const table = dot >= 0 ? name.slice(dot + 1) : name;
209
+ if (!groups.has(schema))
210
+ groups.set(schema, []);
211
+ groups.get(schema).push(table);
212
+ }
213
+ const bodyLines = [
214
+ `import type { DbClassificationMap } from '${relMapPosix}'`,
215
+ ``,
216
+ `export const classifications = {`,
217
+ ];
218
+ for (const [schema, tables] of groups) {
219
+ if (schema)
220
+ bodyLines.push(` ${JSON.stringify(schema)}: {`);
221
+ for (const table of tables) {
222
+ bodyLines.push(schema
223
+ ? ` ${JSON.stringify(table)}: {`
224
+ : ` ${JSON.stringify(table)}: {`);
225
+ bodyLines.push(schema ? ` },` : ` },`);
218
226
  }
219
- bodyLines.push(`} satisfies DbClassificationMap`, ``);
220
- mkdirSync(dirname(classificationsFile), { recursive: true });
221
- writeFileSync(classificationsFile, bodyLines.join('\n'), 'utf8');
222
- scaffolded = true;
227
+ if (schema)
228
+ bodyLines.push(` },`);
223
229
  }
224
- // Resolve tsx from the CLI package's own node_modules so it works regardless
225
- // of whether the user's project has tsx installed.
226
- const _require = createRequire(fileURLToPath(import.meta.url));
227
- let tsxEsmPath = null;
230
+ bodyLines.push(`} satisfies DbClassificationMap`, ``);
231
+ mkdirSync(dirname(classificationsFile), { recursive: true });
232
+ writeFileSync(classificationsFile, bodyLines.join('\n'), 'utf8');
233
+ return true;
234
+ }
235
+ /**
236
+ * Compile `db/annotations.ts` into the `annotations.gen.json` sidecar that the
237
+ * codegen and the pikku-console addon read. No-op if the authored file doesn't
238
+ * exist (nothing to compile yet). Returns whether the sidecar changed on disk.
239
+ *
240
+ * Uses esbuild (a CLI dependency) to transpile the TS in-process and a `vm`
241
+ * sandbox to evaluate it — no subprocess and no tsx. The previous `node --import
242
+ * tsx/esm` subprocess silently fails on Node ≥ 23 (ERR_REQUIRE_CYCLE_MODULE),
243
+ * which is why this sidecar never materialised before.
244
+ *
245
+ * Run BEFORE codegen so authored edits reflect in a single `db migrate` (and
246
+ * again after, to capture a freshly-scaffolded file).
247
+ */
248
+ function compileClassifications(classificationsFile, genJsonFile) {
249
+ if (!existsSync(classificationsFile))
250
+ return false;
251
+ let value;
228
252
  try {
229
- tsxEsmPath = _require.resolve('tsx/esm');
253
+ const src = readFileSync(classificationsFile, 'utf8');
254
+ const { code } = transformSync(src, {
255
+ loader: 'ts',
256
+ format: 'cjs',
257
+ });
258
+ const mod = { exports: {} };
259
+ runInNewContext(code, {
260
+ module: mod,
261
+ exports: mod.exports,
262
+ require: createRequire(classificationsFile),
263
+ });
264
+ value = Object.values(mod.exports)[0];
230
265
  }
231
266
  catch {
232
- // tsx not bundled with this CLI install — skip JSON emit
267
+ return false; // syntax/transform error — skip JSON emit
233
268
  }
234
- const script = [
235
- `import * as mod from ${JSON.stringify(classificationsFile)}`,
236
- `const val = Object.values(mod)[0]`,
237
- `process.stdout.write(JSON.stringify(val))`,
238
- ].join('\n');
239
- let jsonWritten = false;
240
- if (tsxEsmPath) {
241
- try {
242
- const json = execSync(`node --import ${JSON.stringify(tsxEsmPath)} --input-type=module`, {
243
- input: script,
244
- encoding: 'utf8',
245
- stdio: ['pipe', 'pipe', 'pipe'],
246
- });
247
- const existing = existsSync(genJsonFile)
248
- ? readFileSync(genJsonFile, 'utf8')
249
- : null;
250
- const next = JSON.stringify(JSON.parse(json), null, 2) + '\n';
251
- if (existing !== next) {
252
- mkdirSync(dirname(genJsonFile), { recursive: true });
253
- writeFileSync(genJsonFile, next, 'utf8');
254
- jsonWritten = true;
255
- }
256
- }
257
- catch {
258
- // annotations file has syntax errors — skip JSON emit
259
- }
269
+ if (value === undefined)
270
+ return false;
271
+ const next = JSON.stringify(value, null, 2) + '\n';
272
+ const existing = existsSync(genJsonFile)
273
+ ? readFileSync(genJsonFile, 'utf8')
274
+ : null;
275
+ if (existing !== next) {
276
+ mkdirSync(dirname(genJsonFile), { recursive: true });
277
+ writeFileSync(genJsonFile, next, 'utf8');
278
+ return true;
260
279
  }
261
- return { scaffolded, jsonWritten };
280
+ return false;
262
281
  }
263
282
  export async function createKysely(resolved) {
264
283
  mkdirSync(dirname(resolved.dbFile), { recursive: true });
@@ -277,3 +296,126 @@ export async function createKysely(resolved) {
277
296
  plugins: coercionMap ? [createCoercionPlugin({ map: coercionMap })] : [],
278
297
  });
279
298
  }
299
+ async function introspectorToMap(intro) {
300
+ const map = new Map();
301
+ for (const table of await intro.listTables()) {
302
+ const cols = await intro.getColumns(table);
303
+ map.set(table, new Set(cols.map((c) => c.name)));
304
+ }
305
+ return map;
306
+ }
307
+ function diffSchemas(desired, actual) {
308
+ const missingTables = [];
309
+ const missingColumns = [];
310
+ for (const [table, cols] of desired) {
311
+ const actualCols = actual.get(table);
312
+ if (!actualCols) {
313
+ missingTables.push(table);
314
+ continue;
315
+ }
316
+ const missing = [...cols].filter((c) => !actualCols.has(c));
317
+ if (missing.length)
318
+ missingColumns.push({ table, columns: missing });
319
+ }
320
+ return { missingTables, missingColumns };
321
+ }
322
+ export async function desiredAuthSchema(rootDir, srcDirectories, logger) {
323
+ const runtime = await loadSqliteRuntime();
324
+ const db = runtime.open(':memory:');
325
+ try {
326
+ const kysely = createSqliteKysely({ db, camelCase: true });
327
+ const options = await loadAuthOptions({ rootDir, srcDirectories, kysely, logger });
328
+ if (!options)
329
+ return null;
330
+ const { runMigrations, compileMigrations } = await getAuthMigrations(options);
331
+ await runMigrations();
332
+ const tables = await introspectorToMap(new SqliteIntrospector(db));
333
+ const sql = await compileMigrations();
334
+ return { tables, sql };
335
+ }
336
+ finally {
337
+ db.close();
338
+ }
339
+ }
340
+ export async function introspectSchema(resolved) {
341
+ if (resolved.dialect === 'sqlite') {
342
+ const runtime = await loadSqliteRuntime();
343
+ const db = runtime.open(resolved.dbFile);
344
+ try {
345
+ return await introspectorToMap(new SqliteIntrospector(db));
346
+ }
347
+ finally {
348
+ db.close();
349
+ }
350
+ }
351
+ const intro = new PostgresIntrospector(resolved.connectionString);
352
+ await intro.connect();
353
+ try {
354
+ return await introspectorToMap(intro);
355
+ }
356
+ finally {
357
+ await intro.close();
358
+ }
359
+ }
360
+ async function coveredSqliteSchema(migrationsDir) {
361
+ const runtime = await loadSqliteRuntime();
362
+ const db = runtime.open(':memory:');
363
+ try {
364
+ await migrate(new SqliteMigrationExecutor(db), migrationsDir);
365
+ return await introspectorToMap(new SqliteIntrospector(db));
366
+ }
367
+ finally {
368
+ db.close();
369
+ }
370
+ }
371
+ export async function computeAuthDrift(resolved, rootDir, srcDirectories, logger) {
372
+ const desired = await desiredAuthSchema(rootDir, srcDirectories, logger);
373
+ if (!desired) {
374
+ return { hasAuth: false, inSync: true, missingTables: [], missingColumns: [] };
375
+ }
376
+ const actual = await introspectSchema(resolved);
377
+ const { missingTables, missingColumns } = diffSchemas(desired.tables, actual);
378
+ return {
379
+ hasAuth: true,
380
+ inSync: missingTables.length === 0 && missingColumns.length === 0,
381
+ missingTables,
382
+ missingColumns,
383
+ };
384
+ }
385
+ function nextMigrationFile(migrationsDir, label) {
386
+ mkdirSync(migrationsDir, { recursive: true });
387
+ let max = 0;
388
+ try {
389
+ for (const file of readdirSync(migrationsDir)) {
390
+ const m = /^(\d+)/.exec(file);
391
+ if (m)
392
+ max = Math.max(max, parseInt(m[1], 10));
393
+ }
394
+ }
395
+ catch {
396
+ max = 0;
397
+ }
398
+ const num = String(max + 1).padStart(4, '0');
399
+ return join(migrationsDir, `${num}-${label}.sql`);
400
+ }
401
+ export async function generateAuthMigration(resolved, rootDir, srcDirectories, logger) {
402
+ if (resolved.dialect !== 'sqlite')
403
+ return { status: 'unsupported-dialect' };
404
+ const desired = await desiredAuthSchema(rootDir, srcDirectories, logger);
405
+ if (!desired)
406
+ return { status: 'no-auth' };
407
+ const covered = await coveredSqliteSchema(resolved.migrationsDir);
408
+ const { missingTables, missingColumns } = diffSchemas(desired.tables, covered);
409
+ if (missingTables.length === 0 && missingColumns.length === 0) {
410
+ return { status: 'up-to-date' };
411
+ }
412
+ const coveredHasAnyAuthTable = [...desired.tables.keys()].some((t) => covered.has(t));
413
+ if (coveredHasAnyAuthTable) {
414
+ return { status: 'incremental-unsupported', missingTables, missingColumns };
415
+ }
416
+ const file = nextMigrationFile(resolved.migrationsDir, 'better-auth');
417
+ const header = '-- Generated by `pikku db generate` from pikkuBetterAuth (Better Auth).\n' +
418
+ '-- Re-run the command after changing the auth config.\n\n';
419
+ writeFileSync(file, header + desired.sql + '\n', 'utf8');
420
+ return { status: 'written', file, missingTables };
421
+ }
@@ -33,6 +33,7 @@ export class PostgresIntrospector {
33
33
  const result = await this.client.query(`SELECT
34
34
  c.column_name,
35
35
  c.data_type,
36
+ c.udt_name,
36
37
  c.is_nullable,
37
38
  c.column_default,
38
39
  c.is_generated,
@@ -54,6 +55,7 @@ export class PostgresIntrospector {
54
55
  return result.rows.map((r) => ({
55
56
  name: r.column_name,
56
57
  type: r.data_type,
58
+ udtName: r.udt_name,
57
59
  notNull: r.is_nullable === 'NO',
58
60
  pk: Boolean(r.is_pk),
59
61
  defaultValue: r.column_default,
@@ -1,6 +1,44 @@
1
+ /**
2
+ * Canonical map of `format` tokens (authored in `db/annotations.ts`) to the zod
3
+ * expression they emit. These are all *string* refinements — they do not change
4
+ * the column's TypeScript type (it stays `string`), only the runtime validator.
5
+ * This is the single source of truth: `annotation-parser` imports it to validate
6
+ * authored values and `db-codegen` imports the key list to emit the `format`
7
+ * union in `ColumnEntry`. All confirmed present in zod 4.
8
+ */
9
+ export declare const ZOD_FORMATS: {
10
+ readonly email: "z.email()";
11
+ readonly url: "z.url()";
12
+ readonly emoji: "z.emoji()";
13
+ readonly e164: "z.e164()";
14
+ readonly jwt: "z.jwt()";
15
+ readonly cuid: "z.cuid()";
16
+ readonly cuid2: "z.cuid2()";
17
+ readonly ulid: "z.ulid()";
18
+ readonly nanoid: "z.nanoid()";
19
+ readonly base64: "z.base64()";
20
+ readonly base64url: "z.base64url()";
21
+ readonly ipv4: "z.ipv4()";
22
+ readonly ipv6: "z.ipv6()";
23
+ readonly cidrv4: "z.cidrv4()";
24
+ readonly cidrv6: "z.cidrv6()";
25
+ readonly isoDate: "z.iso.date()";
26
+ readonly isoTime: "z.iso.time()";
27
+ readonly isoDatetime: "z.iso.datetime()";
28
+ readonly isoDuration: "z.iso.duration()";
29
+ };
30
+ export type ZodFormat = keyof typeof ZOD_FORMATS;
1
31
  export interface ZodCodegenOptions {
2
32
  schemaFile: string;
3
33
  outFile: string;
34
+ /**
35
+ * Per-interface, per-field `format` overrides. Keyed by the *interface* name
36
+ * (PascalCase, as it appears in `schema.d.ts`) and the *field* name (camelCase),
37
+ * matching how `parseTables` reads the schema. Computed by `db-codegen` (which
38
+ * owns the snake→Pascal/camel name mapping) so this emitter stays a dumb
39
+ * string→zod translator.
40
+ */
41
+ formats?: Record<string, Record<string, ZodFormat>>;
4
42
  }
5
43
  export interface ZodCodegenResult {
6
44
  outFile: string;