@pikku/cli 0.12.37 → 0.12.39
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/.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 +1 -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.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 +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +164 -164
- package/dist/.pikku/function/pikku-functions.gen.js +1 -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 +1 -1
- 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 +7 -7
- 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/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/fabric/functions/validate-core.js +7 -7
- package/dist/src/fabric/functions/validate.function.js +36 -28
- package/dist/src/fabric/lib/config.d.ts +4 -4
- package/dist/src/fabric/lib/config.js +2 -2
- package/dist/src/functions/db/better-auth-schema.js +33 -11
- package/dist/src/functions/db/db-codegen.js +9 -5
- package/dist/src/functions/db/local-db.d.ts +2 -2
- package/dist/src/functions/db/local-db.js +123 -6
- package/dist/src/functions/db/sqlite/sqlite-runtime-node.js +2 -1
- package/dist/src/scaffold/rpc-remote.gen.js +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
3
3
|
import { readdirSync, statSync, readFileSync, existsSync } from 'node:fs';
|
|
4
4
|
import { join, extname, dirname } from 'node:path';
|
|
5
5
|
import { PIKKU_BETTER_AUTH } from '@pikku/better-auth';
|
|
6
|
+
import { LocalSecretService, LocalVariablesService } from '@pikku/core/services';
|
|
6
7
|
import { loadUserModule } from '../commands/load-user-project.js';
|
|
7
8
|
let cachedGetMigrations = null;
|
|
8
9
|
async function loadGetMigrations() {
|
|
@@ -87,24 +88,43 @@ async function loadAuthFactory(sourceFile) {
|
|
|
87
88
|
return null;
|
|
88
89
|
}
|
|
89
90
|
function schemaServicesStub(kysely, logger) {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
91
|
+
const variables = new LocalVariablesService();
|
|
92
|
+
const secrets = new LocalSecretService(variables);
|
|
92
93
|
const base = {
|
|
93
94
|
kysely,
|
|
94
95
|
logger,
|
|
95
|
-
secrets
|
|
96
|
-
|
|
97
|
-
getSecrets: async (keys) => fromKeys(keys),
|
|
98
|
-
},
|
|
99
|
-
variables: {
|
|
100
|
-
getVariable: async () => dummy,
|
|
101
|
-
getVariables: async (keys) => fromKeys(keys),
|
|
102
|
-
},
|
|
96
|
+
secrets,
|
|
97
|
+
variables,
|
|
103
98
|
};
|
|
104
99
|
return new Proxy(base, {
|
|
105
100
|
get: (target, prop) => typeof prop === 'string' && prop in target ? target[prop] : undefined,
|
|
106
101
|
});
|
|
107
102
|
}
|
|
103
|
+
function findUserConfigFactoryFile(rootDir, srcDirectories) {
|
|
104
|
+
for (const srcDir of srcDirectories) {
|
|
105
|
+
for (const name of ['config.ts', 'config.js']) {
|
|
106
|
+
const candidate = join(rootDir, srcDir, name);
|
|
107
|
+
if (existsSync(candidate))
|
|
108
|
+
return candidate;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const name of ['config.ts', 'config.js']) {
|
|
112
|
+
const candidate = join(rootDir, name);
|
|
113
|
+
if (existsSync(candidate))
|
|
114
|
+
return candidate;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
async function loadAuthConfig(opts) {
|
|
119
|
+
const configFactoryFile = findUserConfigFactoryFile(opts.rootDir, opts.srcDirectories);
|
|
120
|
+
if (!configFactoryFile)
|
|
121
|
+
return undefined;
|
|
122
|
+
const configModule = await loadUserModule(configFactoryFile);
|
|
123
|
+
const userCreateConfig = configModule.createConfig;
|
|
124
|
+
if (typeof userCreateConfig !== 'function')
|
|
125
|
+
return undefined;
|
|
126
|
+
return userCreateConfig(new LocalVariablesService());
|
|
127
|
+
}
|
|
108
128
|
export async function loadAuthOptions(opts) {
|
|
109
129
|
const sourceFile = findAuthSourceFile(opts.rootDir, opts.srcDirectories);
|
|
110
130
|
if (!sourceFile)
|
|
@@ -112,7 +132,9 @@ export async function loadAuthOptions(opts) {
|
|
|
112
132
|
const factory = await loadAuthFactory(sourceFile);
|
|
113
133
|
if (!factory)
|
|
114
134
|
return null;
|
|
115
|
-
const
|
|
135
|
+
const services = schemaServicesStub(opts.kysely, opts.logger);
|
|
136
|
+
services.config = await loadAuthConfig(opts);
|
|
137
|
+
const instance = await factory(services);
|
|
116
138
|
const options = instance.options;
|
|
117
139
|
return options ?? null;
|
|
118
140
|
}
|
|
@@ -28,6 +28,12 @@ function snakeToPascal(name) {
|
|
|
28
28
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
29
29
|
.join('');
|
|
30
30
|
}
|
|
31
|
+
function tableToInterfaceName(name) {
|
|
32
|
+
return name
|
|
33
|
+
.split('.')
|
|
34
|
+
.map((part) => snakeToPascal(part))
|
|
35
|
+
.join('');
|
|
36
|
+
}
|
|
31
37
|
function snakeToCamel(name) {
|
|
32
38
|
return name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
33
39
|
}
|
|
@@ -110,9 +116,7 @@ function columnTypeExpression(col, annotation, classification) {
|
|
|
110
116
|
return wrap(nullable ? 'Uuid | null' : 'Uuid');
|
|
111
117
|
}
|
|
112
118
|
if (annotation?.tsType) {
|
|
113
|
-
const base = nullable
|
|
114
|
-
? `${annotation.tsType} | null`
|
|
115
|
-
: annotation.tsType;
|
|
119
|
+
const base = nullable ? `${annotation.tsType} | null` : annotation.tsType;
|
|
116
120
|
return wrap(base);
|
|
117
121
|
}
|
|
118
122
|
if (annotation?.kind === 'json') {
|
|
@@ -146,7 +150,7 @@ function bareTableName(name) {
|
|
|
146
150
|
return dot >= 0 ? name.slice(dot + 1) : name;
|
|
147
151
|
}
|
|
148
152
|
function emitInterface(table, camelCase, explicitAnnotations, dialect, enumByName, formatHints, warnings) {
|
|
149
|
-
const ifaceName =
|
|
153
|
+
const ifaceName = tableToInterfaceName(table.name);
|
|
150
154
|
const bare = bareTableName(table.name);
|
|
151
155
|
const tableCols = explicitAnnotations[bare] ?? {};
|
|
152
156
|
const fields = table.columns
|
|
@@ -352,7 +356,7 @@ export async function generateSchemaTypes(introspector, options) {
|
|
|
352
356
|
const safe = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableKey)
|
|
353
357
|
? tableKey
|
|
354
358
|
: JSON.stringify(tableKey);
|
|
355
|
-
return ` ${safe}: ${
|
|
359
|
+
return ` ${safe}: ${tableToInterfaceName(t.name)}`;
|
|
356
360
|
})
|
|
357
361
|
.join('\n');
|
|
358
362
|
const schemaBody = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Kysely } from 'kysely';
|
|
2
2
|
import { type MigrateResult } from './db-migrator.js';
|
|
3
3
|
import { type CodegenResult } from './db-codegen.js';
|
|
4
4
|
import { type ZodCodegenResult } from './zod-codegen.js';
|
|
@@ -58,7 +58,7 @@ export interface DesiredAuthSchema {
|
|
|
58
58
|
tables: SchemaMap;
|
|
59
59
|
sql: string;
|
|
60
60
|
}
|
|
61
|
-
export declare function desiredAuthSchema(rootDir: string, srcDirectories: string[], logger: {
|
|
61
|
+
export declare function desiredAuthSchema(resolved: ResolvedDb, rootDir: string, srcDirectories: string[], logger: {
|
|
62
62
|
error: (msg: string) => void;
|
|
63
63
|
}): Promise<DesiredAuthSchema | null>;
|
|
64
64
|
export declare function introspectSchema(resolved: ResolvedDb): Promise<SchemaMap>;
|
|
@@ -3,6 +3,7 @@ import { resolve, isAbsolute, relative, dirname, join } from 'node:path';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { runInNewContext } from 'node:vm';
|
|
5
5
|
import { transformSync } from 'esbuild';
|
|
6
|
+
import { CamelCasePlugin, CompiledQuery, Kysely, PostgresDialect } from 'kysely';
|
|
6
7
|
import { migrate } from './db-migrator.js';
|
|
7
8
|
import { loadAuthOptions, getAuthMigrations } from './better-auth-schema.js';
|
|
8
9
|
import { generateSchemaTypes } from './db-codegen.js';
|
|
@@ -307,8 +308,19 @@ async function introspectorToMap(intro) {
|
|
|
307
308
|
function diffSchemas(desired, actual) {
|
|
308
309
|
const missingTables = [];
|
|
309
310
|
const missingColumns = [];
|
|
311
|
+
const findSchemaQualifiedMatch = (table) => {
|
|
312
|
+
if (table.includes('.'))
|
|
313
|
+
return undefined;
|
|
314
|
+
const matches = [...actual.entries()].filter(([actualTable]) => {
|
|
315
|
+
const parts = actualTable.split('.');
|
|
316
|
+
return parts.length === 2 && parts[1] === table;
|
|
317
|
+
});
|
|
318
|
+
if (matches.length !== 1)
|
|
319
|
+
return undefined;
|
|
320
|
+
return matches[0][1];
|
|
321
|
+
};
|
|
310
322
|
for (const [table, cols] of desired) {
|
|
311
|
-
const actualCols = actual.get(table);
|
|
323
|
+
const actualCols = actual.get(table) ?? findSchemaQualifiedMatch(table);
|
|
312
324
|
if (!actualCols) {
|
|
313
325
|
missingTables.push(table);
|
|
314
326
|
continue;
|
|
@@ -319,14 +331,114 @@ function diffSchemas(desired, actual) {
|
|
|
319
331
|
}
|
|
320
332
|
return { missingTables, missingColumns };
|
|
321
333
|
}
|
|
322
|
-
|
|
334
|
+
function isPostgresAuthDatabase(options) {
|
|
335
|
+
return options.database?.type === 'postgres';
|
|
336
|
+
}
|
|
337
|
+
function createScratchPostgresSchemaName() {
|
|
338
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
339
|
+
return `pikku_auth_${Date.now().toString(36)}_${random}`;
|
|
340
|
+
}
|
|
341
|
+
async function postgresSchemaToMap(connectionString, schema) {
|
|
342
|
+
const { Client } = await import('pg');
|
|
343
|
+
const client = new Client({ connectionString });
|
|
344
|
+
await client.connect();
|
|
345
|
+
try {
|
|
346
|
+
const tablesResult = await client.query(`SELECT table_name
|
|
347
|
+
FROM information_schema.tables
|
|
348
|
+
WHERE table_schema = $1
|
|
349
|
+
AND table_type = 'BASE TABLE'
|
|
350
|
+
ORDER BY table_name`, [schema]);
|
|
351
|
+
const map = new Map();
|
|
352
|
+
for (const { table_name } of tablesResult.rows) {
|
|
353
|
+
const columnsResult = await client.query(`SELECT column_name
|
|
354
|
+
FROM information_schema.columns
|
|
355
|
+
WHERE table_schema = $1
|
|
356
|
+
AND table_name = $2
|
|
357
|
+
ORDER BY ordinal_position`, [schema, table_name]);
|
|
358
|
+
map.set(table_name, new Set(columnsResult.rows.map((c) => c.column_name)));
|
|
359
|
+
}
|
|
360
|
+
return map;
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
await client.end();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async function desiredPostgresAuthSchema(resolved, rootDir, srcDirectories, logger) {
|
|
367
|
+
const { Pool } = await import('pg');
|
|
368
|
+
const schema = createScratchPostgresSchemaName();
|
|
369
|
+
const pool = new Pool({
|
|
370
|
+
connectionString: resolved.connectionString,
|
|
371
|
+
max: 1,
|
|
372
|
+
});
|
|
373
|
+
try {
|
|
374
|
+
const admin = await pool.connect();
|
|
375
|
+
try {
|
|
376
|
+
await admin.query(`CREATE SCHEMA "${schema}"`);
|
|
377
|
+
await admin.query(`SET search_path TO "${schema}"`);
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
admin.release();
|
|
381
|
+
}
|
|
382
|
+
const kysely = new Kysely({
|
|
383
|
+
dialect: new PostgresDialect({
|
|
384
|
+
pool,
|
|
385
|
+
onReserveConnection: async (connection) => {
|
|
386
|
+
await connection.executeQuery(CompiledQuery.raw(`SET search_path TO "${schema}"`));
|
|
387
|
+
},
|
|
388
|
+
}),
|
|
389
|
+
plugins: [new CamelCasePlugin()],
|
|
390
|
+
}).withSchema(schema);
|
|
391
|
+
try {
|
|
392
|
+
const options = await loadAuthOptions({
|
|
393
|
+
rootDir,
|
|
394
|
+
srcDirectories,
|
|
395
|
+
kysely,
|
|
396
|
+
logger,
|
|
397
|
+
});
|
|
398
|
+
if (!options)
|
|
399
|
+
return null;
|
|
400
|
+
const { runMigrations, compileMigrations } = await getAuthMigrations(options);
|
|
401
|
+
await runMigrations();
|
|
402
|
+
const tables = await postgresSchemaToMap(resolved.connectionString, schema);
|
|
403
|
+
const sql = await compileMigrations();
|
|
404
|
+
return { tables, sql };
|
|
405
|
+
}
|
|
406
|
+
finally {
|
|
407
|
+
await kysely.destroy();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
finally {
|
|
411
|
+
const cleanup = new Pool({
|
|
412
|
+
connectionString: resolved.connectionString,
|
|
413
|
+
max: 1,
|
|
414
|
+
});
|
|
415
|
+
try {
|
|
416
|
+
await cleanup.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
417
|
+
}
|
|
418
|
+
finally {
|
|
419
|
+
await cleanup.end();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
export async function desiredAuthSchema(resolved, rootDir, srcDirectories, logger) {
|
|
323
424
|
const runtime = await loadSqliteRuntime();
|
|
324
425
|
const db = runtime.open(':memory:');
|
|
325
426
|
try {
|
|
326
427
|
const kysely = createSqliteKysely({ db, camelCase: true });
|
|
327
|
-
const options = await loadAuthOptions({
|
|
428
|
+
const options = await loadAuthOptions({
|
|
429
|
+
rootDir,
|
|
430
|
+
srcDirectories,
|
|
431
|
+
kysely,
|
|
432
|
+
logger,
|
|
433
|
+
});
|
|
328
434
|
if (!options)
|
|
329
435
|
return null;
|
|
436
|
+
if (isPostgresAuthDatabase(options)) {
|
|
437
|
+
if (resolved.dialect !== 'postgres') {
|
|
438
|
+
throw new Error('Better Auth database.type is postgres, but the resolved app database is not postgres.');
|
|
439
|
+
}
|
|
440
|
+
return desiredPostgresAuthSchema(resolved, rootDir, srcDirectories, logger);
|
|
441
|
+
}
|
|
330
442
|
const { runMigrations, compileMigrations } = await getAuthMigrations(options);
|
|
331
443
|
await runMigrations();
|
|
332
444
|
const tables = await introspectorToMap(new SqliteIntrospector(db));
|
|
@@ -369,9 +481,14 @@ async function coveredSqliteSchema(migrationsDir) {
|
|
|
369
481
|
}
|
|
370
482
|
}
|
|
371
483
|
export async function computeAuthDrift(resolved, rootDir, srcDirectories, logger) {
|
|
372
|
-
const desired = await desiredAuthSchema(rootDir, srcDirectories, logger);
|
|
484
|
+
const desired = await desiredAuthSchema(resolved, rootDir, srcDirectories, logger);
|
|
373
485
|
if (!desired) {
|
|
374
|
-
return {
|
|
486
|
+
return {
|
|
487
|
+
hasAuth: false,
|
|
488
|
+
inSync: true,
|
|
489
|
+
missingTables: [],
|
|
490
|
+
missingColumns: [],
|
|
491
|
+
};
|
|
375
492
|
}
|
|
376
493
|
const actual = await introspectSchema(resolved);
|
|
377
494
|
const { missingTables, missingColumns } = diffSchemas(desired.tables, actual);
|
|
@@ -401,7 +518,7 @@ function nextMigrationFile(migrationsDir, label) {
|
|
|
401
518
|
export async function generateAuthMigration(resolved, rootDir, srcDirectories, logger) {
|
|
402
519
|
if (resolved.dialect !== 'sqlite')
|
|
403
520
|
return { status: 'unsupported-dialect' };
|
|
404
|
-
const desired = await desiredAuthSchema(rootDir, srcDirectories, logger);
|
|
521
|
+
const desired = await desiredAuthSchema(resolved, rootDir, srcDirectories, logger);
|
|
405
522
|
if (!desired)
|
|
406
523
|
return { status: 'no-auth' };
|
|
407
524
|
const covered = await coveredSqliteSchema(resolved.migrationsDir);
|