@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,31 +1,42 @@
|
|
|
1
1
|
import type { ColumnKind } from './coercion-plugin.js';
|
|
2
|
+
import { type ZodFormat } from './zod-codegen.js';
|
|
2
3
|
type Classification = 'public' | 'private' | 'pii' | 'secret';
|
|
3
4
|
type AnonymizeStrategy = 'fake:email' | 'fake:name' | 'hash' | 'keep' | null;
|
|
4
5
|
export interface ColAnnotation {
|
|
6
|
+
/** Column kind override: `date`, `bool`, `json`, or `uuid`. */
|
|
5
7
|
kind?: ColumnKind;
|
|
6
|
-
/** TypeScript type string
|
|
8
|
+
/** TypeScript type string that overrides the inferred column type, e.g. `string[]`. */
|
|
7
9
|
tsType?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Zod string-format validator (`email`, `url`, …). Refines the zod schema only;
|
|
12
|
+
* the TypeScript type stays `string`. Applied by the codegen only when the
|
|
13
|
+
* column's resolved type is plain `string`.
|
|
14
|
+
*/
|
|
15
|
+
format?: ZodFormat;
|
|
8
16
|
classification?: Classification;
|
|
9
17
|
anonymize?: AnonymizeStrategy;
|
|
10
18
|
}
|
|
11
|
-
/** Per-table, per-column annotation map
|
|
19
|
+
/** Per-table, per-column annotation map sourced from `db/annotations.ts`. */
|
|
12
20
|
export type AnnotationMap = Record<string, Record<string, ColAnnotation>>;
|
|
13
21
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
22
|
+
* Warn-only naming heuristic. We no longer *infer* a column's kind from its
|
|
23
|
+
* name (it produced wrong types — e.g. SQLite stores `*_at` as ISO TEXT, not a
|
|
24
|
+
* `Date`). Instead the codegen warns when a column name looks like it wants a
|
|
25
|
+
* `kind` but none is declared in `db/annotations.ts`, so the developer can opt
|
|
26
|
+
* in explicitly. Returns the *suggested* kind, or null.
|
|
17
27
|
*/
|
|
18
|
-
export declare function
|
|
19
|
-
kind: ColumnKind;
|
|
20
|
-
} | null;
|
|
28
|
+
export declare function nameSuggestsKind(colName: string): ColumnKind | null;
|
|
21
29
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
30
|
+
* Load annotations from the `db/annotations.gen.json` sidecar, which is
|
|
31
|
+
* compiled from the developer-authored `db/annotations.ts` (`DbClassificationMap`)
|
|
32
|
+
* by `syncClassifications`. This is the single source of column classification
|
|
33
|
+
* and type-override information — there is no SQL-comment fallback.
|
|
34
|
+
*
|
|
35
|
+
* The authored `ColumnEntry` shape is:
|
|
36
|
+
* { security?, classification?: <anonymize strategy>, kind?, tsType?, description? }
|
|
37
|
+
* where `security` is the privacy level and `classification` is the anonymize
|
|
38
|
+
* strategy. Returns `{}` if the sidecar doesn't exist yet (first migrate run,
|
|
39
|
+
* before it has been generated).
|
|
24
40
|
*/
|
|
25
|
-
export declare function
|
|
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;
|
|
41
|
+
export declare function loadAnnotations(rootDir: string): AnnotationMap;
|
|
31
42
|
export {};
|
|
@@ -1,29 +1,44 @@
|
|
|
1
|
-
import { readFileSync,
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { ZOD_FORMATS } from './zod-codegen.js';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Warn-only naming heuristic. We no longer *infer* a column's kind from its
|
|
6
|
+
* name (it produced wrong types — e.g. SQLite stores `*_at` as ISO TEXT, not a
|
|
7
|
+
* `Date`). Instead the codegen warns when a column name looks like it wants a
|
|
8
|
+
* `kind` but none is declared in `db/annotations.ts`, so the developer can opt
|
|
9
|
+
* in explicitly. Returns the *suggested* kind, or null.
|
|
7
10
|
*/
|
|
8
|
-
export function
|
|
11
|
+
export function nameSuggestsKind(colName) {
|
|
9
12
|
if (/_at$|_on$/.test(colName))
|
|
10
|
-
return
|
|
13
|
+
return 'date';
|
|
11
14
|
if (/^is_|^has_|^can_/.test(colName))
|
|
12
|
-
return
|
|
15
|
+
return 'bool';
|
|
13
16
|
return null;
|
|
14
17
|
}
|
|
15
|
-
|
|
18
|
+
function parseStrategy(s) {
|
|
19
|
+
if (!s)
|
|
20
|
+
return null;
|
|
21
|
+
const valid = ['fake:email', 'fake:name', 'hash', 'keep'];
|
|
22
|
+
return valid.includes(s)
|
|
23
|
+
? s
|
|
24
|
+
: null;
|
|
25
|
+
}
|
|
16
26
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
27
|
+
* Load annotations from the `db/annotations.gen.json` sidecar, which is
|
|
28
|
+
* compiled from the developer-authored `db/annotations.ts` (`DbClassificationMap`)
|
|
29
|
+
* by `syncClassifications`. This is the single source of column classification
|
|
30
|
+
* and type-override information — there is no SQL-comment fallback.
|
|
19
31
|
*
|
|
20
|
-
* The
|
|
21
|
-
*
|
|
32
|
+
* The authored `ColumnEntry` shape is:
|
|
33
|
+
* { security?, classification?: <anonymize strategy>, kind?, tsType?, description? }
|
|
34
|
+
* where `security` is the privacy level and `classification` is the anonymize
|
|
35
|
+
* strategy. Returns `{}` if the sidecar doesn't exist yet (first migrate run,
|
|
36
|
+
* before it has been generated).
|
|
22
37
|
*/
|
|
23
|
-
function
|
|
38
|
+
export function loadAnnotations(rootDir) {
|
|
24
39
|
const jsonPath = join(rootDir, 'db', 'annotations.gen.json');
|
|
25
40
|
if (!existsSync(jsonPath))
|
|
26
|
-
return
|
|
41
|
+
return {};
|
|
27
42
|
try {
|
|
28
43
|
const raw = JSON.parse(readFileSync(jsonPath, 'utf8'));
|
|
29
44
|
const result = {};
|
|
@@ -33,111 +48,36 @@ function loadAnnotationsSidecar(rootDir) {
|
|
|
33
48
|
if (!ann)
|
|
34
49
|
continue;
|
|
35
50
|
const entry = {};
|
|
36
|
-
if (ann.kind === 'bool' ||
|
|
51
|
+
if (ann.kind === 'bool' ||
|
|
52
|
+
ann.kind === 'date' ||
|
|
53
|
+
ann.kind === 'json' ||
|
|
54
|
+
ann.kind === 'uuid')
|
|
37
55
|
entry.kind = ann.kind;
|
|
38
56
|
if (ann.tsType)
|
|
39
57
|
entry.tsType = ann.tsType;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
if (ann.format && ann.format in ZOD_FORMATS)
|
|
59
|
+
entry.format = ann.format;
|
|
60
|
+
// `security` is the privacy level. `encrypted` brands as `secret`.
|
|
61
|
+
switch (ann.security) {
|
|
62
|
+
case 'public':
|
|
63
|
+
case 'private':
|
|
64
|
+
case 'pii':
|
|
65
|
+
case 'secret':
|
|
66
|
+
entry.classification = ann.security;
|
|
67
|
+
break;
|
|
68
|
+
case 'encrypted':
|
|
69
|
+
entry.classification = 'secret';
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
const strategy = parseStrategy(ann.classification);
|
|
73
|
+
if (strategy !== null)
|
|
74
|
+
entry.anonymize = strategy;
|
|
43
75
|
result[table][col] = entry;
|
|
44
76
|
}
|
|
45
77
|
}
|
|
46
78
|
return result;
|
|
47
79
|
}
|
|
48
|
-
catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// ── SQL comment parsing (fallback) ───────────────────────────────────────────
|
|
53
|
-
function parseStrategy(s) {
|
|
54
|
-
if (!s)
|
|
55
|
-
return null;
|
|
56
|
-
const valid = ['fake:email', 'fake:name', 'hash', 'keep'];
|
|
57
|
-
return valid.includes(s)
|
|
58
|
-
? s
|
|
59
|
-
: null;
|
|
60
|
-
}
|
|
61
|
-
function parseComment(comment) {
|
|
62
|
-
const ann = {};
|
|
63
|
-
if (/@bool\b/i.test(comment)) {
|
|
64
|
-
ann.kind = 'bool';
|
|
65
|
-
}
|
|
66
|
-
else if (/@date\b/i.test(comment)) {
|
|
67
|
-
ann.kind = 'date';
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
const jsonM = comment.match(/@json\b(?:\s+([^\s@]+))?/i);
|
|
71
|
-
if (jsonM) {
|
|
72
|
-
ann.kind = 'json';
|
|
73
|
-
if (jsonM[1])
|
|
74
|
-
ann.tsType = jsonM[1].trim();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const classM = comment.match(/@(public|private|pii|secret)(?::([^\s@]+))?/i);
|
|
78
|
-
if (classM) {
|
|
79
|
-
ann.classification = classM[1].toLowerCase();
|
|
80
|
-
const strategy = parseStrategy(classM[2]);
|
|
81
|
-
if (strategy !== null)
|
|
82
|
-
ann.anonymize = strategy;
|
|
83
|
-
}
|
|
84
|
-
return ann;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Parse `-- @bool | @date | @json [TsType] | @public | @private[:strategy] | @pii[:strategy] | @secret[:strategy]`
|
|
88
|
-
* inline annotations from migration SQL files in `migrationsDir`.
|
|
89
|
-
*/
|
|
90
|
-
export function parseAnnotations(migrationsDir) {
|
|
91
|
-
let files;
|
|
92
|
-
try {
|
|
93
|
-
files = readdirSync(migrationsDir)
|
|
94
|
-
.filter((f) => f.endsWith('.sql'))
|
|
95
|
-
.sort();
|
|
96
|
-
}
|
|
97
80
|
catch {
|
|
98
81
|
return {};
|
|
99
82
|
}
|
|
100
|
-
const result = {};
|
|
101
|
-
function merge(tableName, colName, partial) {
|
|
102
|
-
if (!partial.kind && partial.classification === undefined)
|
|
103
|
-
return;
|
|
104
|
-
if (!result[tableName])
|
|
105
|
-
result[tableName] = {};
|
|
106
|
-
result[tableName][colName] = { ...result[tableName][colName], ...partial };
|
|
107
|
-
}
|
|
108
|
-
for (const file of files) {
|
|
109
|
-
const content = readFileSync(join(migrationsDir, file), 'utf8');
|
|
110
|
-
const createTablePattern = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?"?(\w+)"?\s*\(([^;]+)\)/gis;
|
|
111
|
-
let tableMatch;
|
|
112
|
-
while ((tableMatch = createTablePattern.exec(content)) !== null) {
|
|
113
|
-
const tableName = tableMatch[1].toLowerCase();
|
|
114
|
-
const body = tableMatch[2];
|
|
115
|
-
for (const line of body.split('\n')) {
|
|
116
|
-
const trimmed = line.trim();
|
|
117
|
-
if (/^(PRIMARY|UNIQUE|CHECK|FOREIGN|CONSTRAINT)/i.test(trimmed))
|
|
118
|
-
continue;
|
|
119
|
-
const lineMatch = trimmed.match(/^(\w+)\s+\w[^-]*--\s*(.+?)\s*,?\s*$/);
|
|
120
|
-
if (!lineMatch)
|
|
121
|
-
continue;
|
|
122
|
-
merge(tableName, lineMatch[1].toLowerCase(), parseComment(lineMatch[2]));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const alterPattern = /ALTER\s+TABLE\s+"?(\w+)"?\s+ADD\s+(?:COLUMN\s+)?"?(\w+)"?\s+\w[^;\n-]*(?:;\s*)?--\s*(.+?)(?:\r?\n|$)/gim;
|
|
126
|
-
let alterMatch;
|
|
127
|
-
while ((alterMatch = alterPattern.exec(content)) !== null) {
|
|
128
|
-
merge(alterMatch[1].toLowerCase(), alterMatch[2].toLowerCase(), parseComment(alterMatch[3]));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return result;
|
|
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
83
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
export interface BetterAuthOptionsLike {
|
|
3
|
+
database?: {
|
|
4
|
+
db?: unknown;
|
|
5
|
+
type?: string;
|
|
6
|
+
};
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface GetMigrationsResult {
|
|
10
|
+
toBeCreated: unknown[];
|
|
11
|
+
toBeAdded: unknown[];
|
|
12
|
+
runMigrations: () => Promise<void>;
|
|
13
|
+
compileMigrations: () => Promise<string>;
|
|
14
|
+
}
|
|
15
|
+
export declare function loadAuthOptions(opts: {
|
|
16
|
+
rootDir: string;
|
|
17
|
+
srcDirectories: string[];
|
|
18
|
+
kysely: Kysely<any>;
|
|
19
|
+
logger: {
|
|
20
|
+
error: (msg: string) => void;
|
|
21
|
+
};
|
|
22
|
+
}): Promise<BetterAuthOptionsLike | null>;
|
|
23
|
+
export declare function getAuthMigrations(authOptions: BetterAuthOptionsLike): Promise<GetMigrationsResult>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { readdirSync, statSync, readFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { join, extname, dirname } from 'node:path';
|
|
5
|
+
import { PIKKU_BETTER_AUTH } from '@pikku/better-auth';
|
|
6
|
+
import { loadUserModule } from '../commands/load-user-project.js';
|
|
7
|
+
let cachedGetMigrations = null;
|
|
8
|
+
async function loadGetMigrations() {
|
|
9
|
+
if (cachedGetMigrations)
|
|
10
|
+
return cachedGetMigrations;
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const mainEntry = require.resolve('better-auth');
|
|
13
|
+
let root = dirname(mainEntry);
|
|
14
|
+
while (!existsSync(join(root, 'package.json'))) {
|
|
15
|
+
const parent = dirname(root);
|
|
16
|
+
if (parent === root) {
|
|
17
|
+
throw new Error('Could not locate the better-auth package root');
|
|
18
|
+
}
|
|
19
|
+
root = parent;
|
|
20
|
+
}
|
|
21
|
+
const modUrl = pathToFileURL(join(root, 'dist/db/get-migration.mjs')).href;
|
|
22
|
+
const mod = await import(modUrl);
|
|
23
|
+
cachedGetMigrations = mod.getMigrations;
|
|
24
|
+
return cachedGetMigrations;
|
|
25
|
+
}
|
|
26
|
+
const SKIP_DIRS = new Set([
|
|
27
|
+
'node_modules',
|
|
28
|
+
'.pikku',
|
|
29
|
+
'.git',
|
|
30
|
+
'dist',
|
|
31
|
+
'.pikku-runtime',
|
|
32
|
+
]);
|
|
33
|
+
function findAuthSourceFile(rootDir, srcDirectories) {
|
|
34
|
+
const walk = (dir) => {
|
|
35
|
+
let entries;
|
|
36
|
+
try {
|
|
37
|
+
entries = readdirSync(dir);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (SKIP_DIRS.has(entry))
|
|
44
|
+
continue;
|
|
45
|
+
const full = join(dir, entry);
|
|
46
|
+
let st;
|
|
47
|
+
try {
|
|
48
|
+
st = statSync(full);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (st.isDirectory()) {
|
|
54
|
+
const found = walk(full);
|
|
55
|
+
if (found)
|
|
56
|
+
return found;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (extname(full) !== '.ts')
|
|
60
|
+
continue;
|
|
61
|
+
let src;
|
|
62
|
+
try {
|
|
63
|
+
src = readFileSync(full, 'utf8');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (/\bpikkuBetterAuth\s*\(/.test(src))
|
|
69
|
+
return full;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
};
|
|
73
|
+
for (const srcDir of srcDirectories) {
|
|
74
|
+
const found = walk(join(rootDir, srcDir));
|
|
75
|
+
if (found)
|
|
76
|
+
return found;
|
|
77
|
+
}
|
|
78
|
+
return walk(rootDir);
|
|
79
|
+
}
|
|
80
|
+
async function loadAuthFactory(sourceFile) {
|
|
81
|
+
const mod = await loadUserModule(sourceFile);
|
|
82
|
+
for (const value of Object.values(mod)) {
|
|
83
|
+
if (typeof value === 'function' && value[PIKKU_BETTER_AUTH]) {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function schemaServicesStub(kysely, logger) {
|
|
90
|
+
const dummy = 'x'.repeat(32);
|
|
91
|
+
const fromKeys = (keys) => Object.fromEntries(keys.map((k) => [k, dummy]));
|
|
92
|
+
const base = {
|
|
93
|
+
kysely,
|
|
94
|
+
logger,
|
|
95
|
+
secrets: {
|
|
96
|
+
getSecret: async () => dummy,
|
|
97
|
+
getSecrets: async (keys) => fromKeys(keys),
|
|
98
|
+
},
|
|
99
|
+
variables: {
|
|
100
|
+
getVariable: async () => dummy,
|
|
101
|
+
getVariables: async (keys) => fromKeys(keys),
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
return new Proxy(base, {
|
|
105
|
+
get: (target, prop) => typeof prop === 'string' && prop in target ? target[prop] : undefined,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export async function loadAuthOptions(opts) {
|
|
109
|
+
const sourceFile = findAuthSourceFile(opts.rootDir, opts.srcDirectories);
|
|
110
|
+
if (!sourceFile)
|
|
111
|
+
return null;
|
|
112
|
+
const factory = await loadAuthFactory(sourceFile);
|
|
113
|
+
if (!factory)
|
|
114
|
+
return null;
|
|
115
|
+
const instance = await factory(schemaServicesStub(opts.kysely, opts.logger));
|
|
116
|
+
const options = instance.options;
|
|
117
|
+
return options ?? null;
|
|
118
|
+
}
|
|
119
|
+
export async function getAuthMigrations(authOptions) {
|
|
120
|
+
const getMigrations = await loadGetMigrations();
|
|
121
|
+
return getMigrations(authOptions);
|
|
122
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { KyselyPlugin } from 'kysely';
|
|
2
|
-
export type ColumnKind = 'date' | 'bool' | 'json';
|
|
2
|
+
export type ColumnKind = 'date' | 'bool' | 'json' | 'uuid';
|
|
3
3
|
export type CoercionMap = Record<string, Record<string, ColumnKind>>;
|
|
4
4
|
export interface CreateCoercionPluginOptions {
|
|
5
5
|
map: CoercionMap;
|
|
@@ -23,6 +23,10 @@ function fromDb(value, kind) {
|
|
|
23
23
|
catch {
|
|
24
24
|
return value;
|
|
25
25
|
}
|
|
26
|
+
case 'uuid':
|
|
27
|
+
// UUIDs are strings in both Postgres and SQLite — no runtime coercion.
|
|
28
|
+
// (Codegen also omits `uuid` from the coercion map; this is defensive.)
|
|
29
|
+
return value;
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
function snakeToCamel(name) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { DbIntrospector } from './db-introspector.js';
|
|
2
|
+
import { type ZodFormat } from './zod-codegen.js';
|
|
3
|
+
type Dialect = 'sqlite' | 'postgres';
|
|
2
4
|
export interface CodegenOptions {
|
|
3
5
|
outFile: string;
|
|
4
6
|
coercionFile: string;
|
|
@@ -7,7 +9,8 @@ export interface CodegenOptions {
|
|
|
7
9
|
schemaJsonFile?: string;
|
|
8
10
|
camelCase?: boolean;
|
|
9
11
|
rootDir?: string;
|
|
10
|
-
|
|
12
|
+
/** DB dialect — drives real-type-aware date typing. Defaults to 'sqlite'. */
|
|
13
|
+
dialect?: Dialect;
|
|
11
14
|
}
|
|
12
15
|
export interface CodegenResult {
|
|
13
16
|
outFile: string;
|
|
@@ -19,6 +22,14 @@ export interface CodegenResult {
|
|
|
19
22
|
manifestWritten: boolean;
|
|
20
23
|
classificationMapWritten: boolean;
|
|
21
24
|
tables: string[];
|
|
25
|
+
/** Non-fatal codegen warnings (e.g. name looks like a date but unannotated). */
|
|
26
|
+
warnings: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Per-interface, per-field zod `format` overrides for the zod codegen. Keyed
|
|
29
|
+
* by interface name (PascalCase) and field name (camelCase), matching the
|
|
30
|
+
* shapes the zod emitter parses out of `schema.d.ts`.
|
|
31
|
+
*/
|
|
32
|
+
zodFormats: Record<string, Record<string, ZodFormat>>;
|
|
22
33
|
}
|
|
23
34
|
/**
|
|
24
35
|
* Introspect `introspector` and emit:
|
|
@@ -27,3 +38,4 @@ export interface CodegenResult {
|
|
|
27
38
|
* - `classification.gen.ts` Data-classification manifest (when manifestFile set)
|
|
28
39
|
*/
|
|
29
40
|
export declare function generateSchemaTypes(introspector: DbIntrospector, options: CodegenOptions): Promise<CodegenResult>;
|
|
41
|
+
export {};
|