@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.
- package/cli.schema.json +1 -1
- package/console-app/assets/index-Dxl3JsMK.js +233 -0
- package/console-app/index.html +1 -1
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +6 -0
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +2 -2
- package/dist/.pikku/function/pikku-function-types.gen.js +17 -3
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +238 -221
- package/dist/.pikku/function/pikku-functions.gen.js +3 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +5 -4
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +17 -13
- package/dist/.pikku/schemas/schemas/DbGenerateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuFunctionTypesInput.schema.json +1 -0
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +22 -4
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +5 -0
- package/dist/src/fabric/functions/login.function.d.ts +1 -1
- package/dist/src/fabric/functions/login.function.js +1 -1
- package/dist/src/functions/commands/bootstrap.js +1 -1
- package/dist/src/functions/commands/db-generate.d.ts +1 -0
- package/dist/src/functions/commands/db-generate.js +45 -0
- package/dist/src/functions/commands/db-migrate.js +13 -1
- package/dist/src/functions/db/annotation-parser.d.ts +27 -16
- package/dist/src/functions/db/annotation-parser.js +50 -110
- package/dist/src/functions/db/better-auth-schema.d.ts +23 -0
- package/dist/src/functions/db/better-auth-schema.js +122 -0
- package/dist/src/functions/db/coercion-plugin.d.ts +1 -1
- package/dist/src/functions/db/coercion-plugin.js +4 -0
- package/dist/src/functions/db/db-codegen.d.ts +13 -1
- package/dist/src/functions/db/db-codegen.js +142 -31
- package/dist/src/functions/db/db-introspector.d.ts +6 -0
- package/dist/src/functions/db/local-db.d.ts +33 -0
- package/dist/src/functions/db/local-db.js +221 -79
- package/dist/src/functions/db/postgres/postgres-introspector.js +2 -0
- package/dist/src/functions/db/zod-codegen.d.ts +38 -0
- package/dist/src/functions/db/zod-codegen.js +153 -38
- package/dist/src/functions/validate/workspace-validate.js +1 -1
- package/dist/src/functions/wirings/auth/pikku-command-auth.js +30 -4
- package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +33 -1
- package/dist/src/functions/wirings/auth/serialize-auth-gen.js +122 -88
- package/dist/src/functions/wirings/auth/serialize-auth-meta.d.ts +32 -0
- package/dist/src/functions/wirings/auth/serialize-auth-meta.js +23 -0
- package/dist/src/functions/wirings/auth/serialize-auth-types.d.ts +27 -0
- package/dist/src/functions/wirings/auth/serialize-auth-types.js +58 -0
- package/dist/src/functions/wirings/functions/pikku-command-function-types.d.ts +7 -1
- package/dist/src/functions/wirings/functions/pikku-command-function-types.js +16 -3
- package/dist/src/functions/wirings/functions/pikku-command-services.d.ts +1 -1
- package/dist/src/functions/wirings/functions/pikku-command-services.js +9 -2
- package/dist/src/functions/wirings/functions/serialize-function-types.js +17 -3
- package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.d.ts +1 -1
- package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.js +2 -1
- package/dist/src/functions/workflows/all.workflow.js +16 -2
- package/dist/src/scaffold/rpc-remote.gen.js +1 -1
- package/dist/src/services.js +8 -0
- package/dist/src/utils/pikku-cli-config.js +12 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/skills/pikku-better-auth/SKILL.md +211 -0
- package/console-app/assets/index-DsW0T00Z.js +0 -233
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
writeFileSync(classificationsFile, bodyLines.join('\n'), 'utf8');
|
|
222
|
-
scaffolded = true;
|
|
227
|
+
if (schema)
|
|
228
|
+
bodyLines.push(` },`);
|
|
223
229
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
267
|
+
return false; // syntax/transform error — skip JSON emit
|
|
233
268
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
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;
|