@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,11 +1,40 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Canonical map of `format` tokens (authored in `db/annotations.ts`) to the zod
|
|
5
|
+
* expression they emit. These are all *string* refinements — they do not change
|
|
6
|
+
* the column's TypeScript type (it stays `string`), only the runtime validator.
|
|
7
|
+
* This is the single source of truth: `annotation-parser` imports it to validate
|
|
8
|
+
* authored values and `db-codegen` imports the key list to emit the `format`
|
|
9
|
+
* union in `ColumnEntry`. All confirmed present in zod 4.
|
|
10
|
+
*/
|
|
11
|
+
export const ZOD_FORMATS = {
|
|
12
|
+
email: 'z.email()',
|
|
13
|
+
url: 'z.url()',
|
|
14
|
+
emoji: 'z.emoji()',
|
|
15
|
+
e164: 'z.e164()',
|
|
16
|
+
jwt: 'z.jwt()',
|
|
17
|
+
cuid: 'z.cuid()',
|
|
18
|
+
cuid2: 'z.cuid2()',
|
|
19
|
+
ulid: 'z.ulid()',
|
|
20
|
+
nanoid: 'z.nanoid()',
|
|
21
|
+
base64: 'z.base64()',
|
|
22
|
+
base64url: 'z.base64url()',
|
|
23
|
+
ipv4: 'z.ipv4()',
|
|
24
|
+
ipv6: 'z.ipv6()',
|
|
25
|
+
cidrv4: 'z.cidrv4()',
|
|
26
|
+
cidrv6: 'z.cidrv6()',
|
|
27
|
+
isoDate: 'z.iso.date()',
|
|
28
|
+
isoTime: 'z.iso.time()',
|
|
29
|
+
isoDatetime: 'z.iso.datetime()',
|
|
30
|
+
isoDuration: 'z.iso.duration()',
|
|
31
|
+
};
|
|
3
32
|
const INTERFACE_RE = /export\s+interface\s+(\w+)\s*\{([^}]*)\}/g;
|
|
4
33
|
const FIELD_RE = /^\s*(\w+)\s*:\s*(.+?)\s*$/gm;
|
|
5
34
|
export function generateZodTypes(options) {
|
|
6
35
|
const src = readFileSync(options.schemaFile, 'utf8');
|
|
7
36
|
const tables = parseTables(src);
|
|
8
|
-
const body = emitZodModule(tables);
|
|
37
|
+
const body = emitZodModule(tables, options.formats ?? {});
|
|
9
38
|
let existing = null;
|
|
10
39
|
try {
|
|
11
40
|
existing = readFileSync(options.outFile, 'utf8');
|
|
@@ -28,18 +57,21 @@ function parseTables(src) {
|
|
|
28
57
|
const tables = [];
|
|
29
58
|
for (const match of src.matchAll(INTERFACE_RE)) {
|
|
30
59
|
const name = match[1];
|
|
31
|
-
if (name === 'DB')
|
|
60
|
+
if (!name || name === 'DB')
|
|
32
61
|
continue;
|
|
33
|
-
const body = match[2];
|
|
62
|
+
const body = match[2] ?? '';
|
|
34
63
|
const fields = [];
|
|
35
64
|
for (const field of body.matchAll(FIELD_RE)) {
|
|
36
|
-
|
|
65
|
+
const fieldName = field[1];
|
|
66
|
+
if (!fieldName)
|
|
67
|
+
continue;
|
|
68
|
+
fields.push({ name: fieldName, type: (field[2] ?? '').trim() });
|
|
37
69
|
}
|
|
38
70
|
tables.push({ name, fields });
|
|
39
71
|
}
|
|
40
72
|
return tables;
|
|
41
73
|
}
|
|
42
|
-
function emitZodModule(tables) {
|
|
74
|
+
function emitZodModule(tables, formats) {
|
|
43
75
|
const lines = [];
|
|
44
76
|
lines.push('// Generated by @pikku/cli — do not edit by hand.');
|
|
45
77
|
lines.push('// Run `pikku db migrate` to refresh.');
|
|
@@ -49,8 +81,9 @@ function emitZodModule(tables) {
|
|
|
49
81
|
for (const table of tables) {
|
|
50
82
|
const rowFields = [];
|
|
51
83
|
const insertFields = [];
|
|
84
|
+
const tableFormats = formats[table.name] ?? {};
|
|
52
85
|
for (const field of table.fields) {
|
|
53
|
-
const { schema, generated } = zodForType(field.type);
|
|
86
|
+
const { schema, generated } = zodForType(field.type, tableFormats[field.name]);
|
|
54
87
|
rowFields.push(` ${field.name}: ${schema},`);
|
|
55
88
|
insertFields.push(` ${field.name}: ${schema}${generated ? '.optional()' : ''},`);
|
|
56
89
|
}
|
|
@@ -83,13 +116,13 @@ function emitZodModule(tables) {
|
|
|
83
116
|
* and the column is insert-optional when `Insert` admits `undefined` (Kysely's
|
|
84
117
|
* encoding for default/auto/generated columns).
|
|
85
118
|
*/
|
|
86
|
-
function zodForType(tsType) {
|
|
119
|
+
function zodForType(tsType, format) {
|
|
87
120
|
let inner = tsType.trim();
|
|
88
121
|
let generated = false;
|
|
89
122
|
// Peel a single `Generated<…>` wrapper. For public bool/date columns this
|
|
90
123
|
// wraps a `ColumnType<…>`, so the unwrapped inner is handled below.
|
|
91
124
|
const generatedMatch = inner.match(/^Generated<(.+)>$/);
|
|
92
|
-
if (generatedMatch) {
|
|
125
|
+
if (generatedMatch?.[1]) {
|
|
93
126
|
generated = true;
|
|
94
127
|
inner = generatedMatch[1].trim();
|
|
95
128
|
}
|
|
@@ -102,9 +135,9 @@ function zodForType(tsType) {
|
|
|
102
135
|
if (unionIncludesUndefined(insertT)) {
|
|
103
136
|
generated = true;
|
|
104
137
|
}
|
|
105
|
-
return { schema: scalarSchema(selectT), generated };
|
|
138
|
+
return { schema: scalarSchema(selectT, format), generated };
|
|
106
139
|
}
|
|
107
|
-
return { schema: scalarSchema(inner), generated };
|
|
140
|
+
return { schema: scalarSchema(inner, format), generated };
|
|
108
141
|
}
|
|
109
142
|
/**
|
|
110
143
|
* Resolve a scalar/select type expression to a zod expression. Handles a
|
|
@@ -112,11 +145,11 @@ function zodForType(tsType) {
|
|
|
112
145
|
* and the known scalar bases. Unknown bases fall back to `z.unknown()` so
|
|
113
146
|
* generation stays total.
|
|
114
147
|
*/
|
|
115
|
-
function scalarSchema(tsType) {
|
|
148
|
+
function scalarSchema(tsType, format) {
|
|
116
149
|
let inner = tsType.trim();
|
|
117
150
|
// Defensive: a Select arg may itself be `Generated<…>` in older schemas.
|
|
118
151
|
const generatedMatch = inner.match(/^Generated<(.+)>$/);
|
|
119
|
-
if (generatedMatch) {
|
|
152
|
+
if (generatedMatch?.[1]) {
|
|
120
153
|
inner = generatedMatch[1].trim();
|
|
121
154
|
}
|
|
122
155
|
const nullable = inner.endsWith(' | null');
|
|
@@ -125,48 +158,130 @@ function scalarSchema(tsType) {
|
|
|
125
158
|
}
|
|
126
159
|
// Unwrap a classification brand: `Private<T>` / `Pii<T>` / `Secret<T>` → T.
|
|
127
160
|
const brandMatch = inner.match(/^(?:Private|Pii|Secret)<(.+)>$/);
|
|
128
|
-
if (brandMatch) {
|
|
161
|
+
if (brandMatch?.[1]) {
|
|
129
162
|
inner = brandMatch[1].trim();
|
|
130
163
|
}
|
|
131
164
|
let schema;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
165
|
+
// A `format` override (e.g. `email`, `url`) replaces the string base with a
|
|
166
|
+
// refined string validator. `db-codegen` only emits a format hint for columns
|
|
167
|
+
// whose select type is plain `string`, so this never collides with Date/enum.
|
|
168
|
+
if (format) {
|
|
169
|
+
schema = ZOD_FORMATS[format];
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// An enum column is emitted as a union of string literals, e.g.
|
|
173
|
+
// `'admin' | 'user'`. Detect it (after brand-unwrap + null-peel) and map to
|
|
174
|
+
// `z.enum([...])` (or `z.literal(...)` for a single value).
|
|
175
|
+
const literals = stringLiteralUnion(inner);
|
|
176
|
+
if (literals) {
|
|
177
|
+
schema =
|
|
178
|
+
literals.length === 1
|
|
179
|
+
? `z.literal('${literals[0]}')`
|
|
180
|
+
: `z.enum([${literals.map((l) => `'${l}'`).join(', ')}])`;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
switch (inner) {
|
|
184
|
+
case 'string':
|
|
185
|
+
schema = 'z.string()';
|
|
186
|
+
break;
|
|
187
|
+
case 'number':
|
|
188
|
+
schema = 'z.number()';
|
|
189
|
+
break;
|
|
190
|
+
case 'boolean':
|
|
191
|
+
schema = 'z.boolean()';
|
|
192
|
+
break;
|
|
193
|
+
case 'Date':
|
|
194
|
+
schema = 'z.date()';
|
|
195
|
+
break;
|
|
196
|
+
case 'Uuid':
|
|
197
|
+
schema = 'z.uuid()';
|
|
198
|
+
break;
|
|
199
|
+
case 'unknown':
|
|
200
|
+
schema = 'z.unknown()';
|
|
201
|
+
break;
|
|
202
|
+
default:
|
|
203
|
+
if (inner.endsWith('[]')) {
|
|
204
|
+
schema = `z.array(${scalarSchema(inner.slice(0, -2).trim())})`;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
schema = 'z.unknown()';
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
154
210
|
}
|
|
155
|
-
|
|
211
|
+
}
|
|
156
212
|
}
|
|
157
213
|
if (nullable) {
|
|
158
214
|
schema += '.nullable()';
|
|
159
215
|
}
|
|
160
216
|
return schema;
|
|
161
217
|
}
|
|
162
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* If `s` is a union of single-quoted string literals (`'a' | 'b' | 'c'`),
|
|
220
|
+
* return the unescaped-as-written label list; otherwise null. Quote-aware so a
|
|
221
|
+
* label containing `|` does not split the union. Re-emitting wraps each captured
|
|
222
|
+
* group back in quotes, so escaping is preserved verbatim.
|
|
223
|
+
*/
|
|
224
|
+
function stringLiteralUnion(s) {
|
|
225
|
+
const parts = splitUnion(s);
|
|
226
|
+
const labels = [];
|
|
227
|
+
for (const part of parts) {
|
|
228
|
+
const match = part.trim().match(/^'((?:[^'\\]|\\.)*)'$/);
|
|
229
|
+
if (!match)
|
|
230
|
+
return null;
|
|
231
|
+
labels.push(match[1]);
|
|
232
|
+
}
|
|
233
|
+
return labels.length > 0 ? labels : null;
|
|
234
|
+
}
|
|
235
|
+
/** Split a union on top-level `|`, skipping `|` inside `'…'` strings or `<>`/`()`. */
|
|
236
|
+
function splitUnion(s) {
|
|
237
|
+
const parts = [];
|
|
238
|
+
let depth = 0;
|
|
239
|
+
let inStr = false;
|
|
240
|
+
let start = 0;
|
|
241
|
+
for (let i = 0; i < s.length; i++) {
|
|
242
|
+
const char = s[i];
|
|
243
|
+
if (inStr) {
|
|
244
|
+
if (char === '\\')
|
|
245
|
+
i++;
|
|
246
|
+
else if (char === "'")
|
|
247
|
+
inStr = false;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (char === "'")
|
|
251
|
+
inStr = true;
|
|
252
|
+
else if (char === '<' || char === '(')
|
|
253
|
+
depth++;
|
|
254
|
+
else if (char === '>' || char === ')')
|
|
255
|
+
depth--;
|
|
256
|
+
else if (char === '|' && depth === 0) {
|
|
257
|
+
parts.push(s.slice(start, i));
|
|
258
|
+
start = i + 1;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
parts.push(s.slice(start));
|
|
262
|
+
return parts;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Split the generic argument list of `Foo<a, b, c>` on top-level commas.
|
|
266
|
+
* Quote-aware so a comma inside an enum label literal (`'a,b'`) does not split.
|
|
267
|
+
*/
|
|
163
268
|
function splitGenericArgs(args) {
|
|
164
269
|
const parts = [];
|
|
165
270
|
let depth = 0;
|
|
271
|
+
let inStr = false;
|
|
166
272
|
let start = 0;
|
|
167
273
|
for (let i = 0; i < args.length; i++) {
|
|
168
274
|
const char = args[i];
|
|
169
|
-
if (
|
|
275
|
+
if (inStr) {
|
|
276
|
+
if (char === '\\')
|
|
277
|
+
i++;
|
|
278
|
+
else if (char === "'")
|
|
279
|
+
inStr = false;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (char === "'")
|
|
283
|
+
inStr = true;
|
|
284
|
+
else if (char === '<')
|
|
170
285
|
depth++;
|
|
171
286
|
else if (char === '>')
|
|
172
287
|
depth--;
|
|
@@ -61,7 +61,7 @@ async function hasAuthSessionMiddleware(fnDir) {
|
|
|
61
61
|
const meta = await readJsonSafe(metaPath);
|
|
62
62
|
if (!meta?.instances)
|
|
63
63
|
return false;
|
|
64
|
-
return Object.values(meta.instances).some((instance) => instance.definitionId === '
|
|
64
|
+
return Object.values(meta.instances).some((instance) => instance.definitionId === 'betterAuthSession');
|
|
65
65
|
}
|
|
66
66
|
function migrationCreatesTable(sql, tableName) {
|
|
67
67
|
const escapedTable = tableName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
@@ -1,17 +1,43 @@
|
|
|
1
|
+
import { join, dirname } from 'node:path';
|
|
1
2
|
import { pikkuSessionlessFunc } from '#pikku';
|
|
2
3
|
import { writeFileInDir } from '../../../utils/file-writer.js';
|
|
3
4
|
import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
|
|
4
5
|
import { serializeAuthGen } from './serialize-auth-gen.js';
|
|
6
|
+
import { serializeAuthTypes } from './serialize-auth-types.js';
|
|
7
|
+
import { serializeAuthMeta } from './serialize-auth-meta.js';
|
|
5
8
|
export const pikkuAuth = pikkuSessionlessFunc({
|
|
6
9
|
func: async ({ logger, config, getInspectorState }) => {
|
|
7
|
-
const { authFile } = config;
|
|
10
|
+
const { authFile, authTypesFile, authMetaJsonFile, functionTypesFile, typesDeclarationFile, secretsFile: secretsServiceFile, variablesFile, packageMappings, } = config;
|
|
8
11
|
if (!authFile)
|
|
9
12
|
return;
|
|
10
13
|
const state = await getInspectorState();
|
|
11
|
-
|
|
14
|
+
// Only generate when the project declares auth via `pikkuBetterAuth`. Gating on
|
|
15
|
+
// the definition (not provider count) means credentials-only auth — which
|
|
16
|
+
// has no OAuth providers — still generates its /auth/* wiring.
|
|
17
|
+
if (!state.auth.definition)
|
|
12
18
|
return;
|
|
13
|
-
const
|
|
14
|
-
|
|
19
|
+
const { wiring, secrets } = serializeAuthGen(state.auth.definition, state.auth.providers, authFile, typesDeclarationFile, packageMappings ?? {});
|
|
20
|
+
// The secrets file sits alongside authFile so re-inspection rediscovers it.
|
|
21
|
+
// It is kept separate from the wiring file because the CLI forbids Zod
|
|
22
|
+
// schemas and HTTP wiring (wireHTTPRoutes) in the same file (PKU490).
|
|
23
|
+
const secretsFile = join(dirname(authFile), 'auth-secrets.gen.ts');
|
|
24
|
+
await writeFileInDir(logger, authFile, wiring);
|
|
25
|
+
await writeFileInDir(logger, secretsFile, secrets);
|
|
26
|
+
// Static metadata of the enabled providers/plugins for the console SSO page,
|
|
27
|
+
// following the `*-meta.gen.json` convention. Read at runtime by the console
|
|
28
|
+
// getAuthProviders function instead of a runtime registry.
|
|
29
|
+
if (authMetaJsonFile) {
|
|
30
|
+
const meta = serializeAuthMeta(state.auth.definition, state.auth.providers);
|
|
31
|
+
await writeFileInDir(logger, authMetaJsonFile, JSON.stringify(meta, null, 2));
|
|
32
|
+
}
|
|
33
|
+
// Generate the typed pikkuBetterAuth re-export consumed by `import { pikkuBetterAuth } from '#pikku'`.
|
|
34
|
+
if (authTypesFile &&
|
|
35
|
+
functionTypesFile &&
|
|
36
|
+
secretsServiceFile &&
|
|
37
|
+
variablesFile) {
|
|
38
|
+
const authTypes = serializeAuthTypes(authTypesFile, functionTypesFile, secretsServiceFile, variablesFile, packageMappings ?? {});
|
|
39
|
+
await writeFileInDir(logger, authTypesFile, authTypes);
|
|
40
|
+
}
|
|
15
41
|
},
|
|
16
42
|
middleware: [
|
|
17
43
|
logCommandInfoAndTime({
|
|
@@ -1 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AuthDefinition } from '@pikku/inspector';
|
|
2
|
+
/**
|
|
3
|
+
* The two files generated from a `pikkuBetterAuth` export.
|
|
4
|
+
*
|
|
5
|
+
* They are split because the CLI's schema/wiring-separation rule (PKU490)
|
|
6
|
+
* forbids a file from declaring Zod schemas AND `wireHTTPRoutes` together —
|
|
7
|
+
* schema files are imported at runtime, which would fire HTTP wiring
|
|
8
|
+
* side-effects without a server context. `wireSecret`/`wireVariable` are NOT
|
|
9
|
+
* HTTP wiring, so schemas may sit alongside them; only the route wiring must be
|
|
10
|
+
* separated out.
|
|
11
|
+
*/
|
|
12
|
+
export interface AuthGenOutput {
|
|
13
|
+
/** The HTTP wiring file (authFile): handler + catch-all routes + session middleware. */
|
|
14
|
+
wiring: string;
|
|
15
|
+
/** The secrets file: Zod schemas + wireSecret/wireVariable. */
|
|
16
|
+
secrets: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generates the `auth.gen.ts` (HTTP wiring) and `auth-secrets.gen.ts` (schemas +
|
|
20
|
+
* secret/variable wiring) files from a `pikkuBetterAuth((services) => betterAuth(...))`
|
|
21
|
+
* export.
|
|
22
|
+
*
|
|
23
|
+
* The wiring file side-effect imports the user's auth file (so `pikkuBetterAuth`
|
|
24
|
+
* registers its factory), builds ONE shared sessionless handler that reads the
|
|
25
|
+
* resolved `services.auth` and delegates to better-auth's fetch handler, wires a
|
|
26
|
+
* catch-all `${basePath}{/*splat}` route per method to it, and registers the
|
|
27
|
+
* better-auth session-bridge middleware globally. Provider/plugin metadata for
|
|
28
|
+
* the console is emitted separately as `auth-meta.gen.json` (see
|
|
29
|
+
* serializeAuthMeta). Because this is normal, statically inspectable HTTP
|
|
30
|
+
* wiring, the routes flow through inspection into the deploy manifest (one
|
|
31
|
+
* worker for all auth routes).
|
|
32
|
+
*/
|
|
33
|
+
export declare const serializeAuthGen: (definition: AuthDefinition, providers: string[], authFile: string, typesDeclarationFile: string, packageMappings: Record<string, string>) => AuthGenOutput;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import { PROVIDER_REGISTRY } from '@pikku/auth
|
|
1
|
+
import { PROVIDER_REGISTRY } from '@pikku/better-auth';
|
|
2
|
+
import { AUTH_HANDLER_FUNC_ID } from '@pikku/inspector';
|
|
3
|
+
import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
|
|
4
|
+
// better-auth uses GET and POST for all of its endpoints (sign-in, callbacks,
|
|
5
|
+
// session, sign-out, plugin routes). A single catch-all per method forwards
|
|
6
|
+
// everything under basePath to better-auth's own internal router.
|
|
7
|
+
const AUTH_METHODS = ['get', 'post'];
|
|
2
8
|
function capitalize(s) {
|
|
3
9
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
4
10
|
}
|
|
@@ -8,110 +14,138 @@ function providerSchemaName(name) {
|
|
|
8
14
|
function providerSecretName(name) {
|
|
9
15
|
return `${name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}OAuth`;
|
|
10
16
|
}
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
function variableSchemaName(name, field) {
|
|
18
|
+
const camel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
19
|
+
return `${capitalize(camel(name))}${capitalize(field)}VariableSchema`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generates the `auth.gen.ts` (HTTP wiring) and `auth-secrets.gen.ts` (schemas +
|
|
23
|
+
* secret/variable wiring) files from a `pikkuBetterAuth((services) => betterAuth(...))`
|
|
24
|
+
* export.
|
|
25
|
+
*
|
|
26
|
+
* The wiring file side-effect imports the user's auth file (so `pikkuBetterAuth`
|
|
27
|
+
* registers its factory), builds ONE shared sessionless handler that reads the
|
|
28
|
+
* resolved `services.auth` and delegates to better-auth's fetch handler, wires a
|
|
29
|
+
* catch-all `${basePath}{/*splat}` route per method to it, and registers the
|
|
30
|
+
* better-auth session-bridge middleware globally. Provider/plugin metadata for
|
|
31
|
+
* the console is emitted separately as `auth-meta.gen.json` (see
|
|
32
|
+
* serializeAuthMeta). Because this is normal, statically inspectable HTTP
|
|
33
|
+
* wiring, the routes flow through inspection into the deploy manifest (one
|
|
34
|
+
* worker for all auth routes).
|
|
35
|
+
*/
|
|
36
|
+
export const serializeAuthGen = (definition, providers, authFile, typesDeclarationFile, packageMappings) => {
|
|
37
|
+
const known = providers.filter((p) => p in PROVIDER_REGISTRY);
|
|
38
|
+
const basePath = definition.basePath;
|
|
39
|
+
// Side-effect import of the user's auth file so `pikkuBetterAuth` runs and
|
|
40
|
+
// registers its factory into pikkuState before singleton services are built.
|
|
41
|
+
const configImportPath = getFileImportRelativePath(authFile, definition.sourceFile, packageMappings);
|
|
42
|
+
// Resolve the pikku types file relative to the scaffold location instead of
|
|
43
|
+
// hardcoding the `#pikku` subpath import — the scaffold dir may live outside
|
|
44
|
+
// the package's `imports` map (e.g. a project's `pikku/` dir), where `#pikku`
|
|
45
|
+
// would not resolve. This mirrors how the console/agent scaffolds import it.
|
|
46
|
+
const pikkuTypesImportPath = getFileImportRelativePath(authFile, typesDeclarationFile, packageMappings);
|
|
47
|
+
// ─── Secrets file: Zod schemas + wireSecret/wireVariable ──────────────────
|
|
48
|
+
const secrets = [
|
|
49
|
+
'// AUTO-GENERATED by pikku CLI — do not edit',
|
|
50
|
+
'',
|
|
51
|
+
`import { wireSecret } from '@pikku/core/secret'`,
|
|
52
|
+
];
|
|
53
|
+
const hasVariables = known.some((name) => PROVIDER_REGISTRY[name].variables);
|
|
54
|
+
if (hasVariables) {
|
|
55
|
+
secrets.push(`import { wireVariable } from '@pikku/core/variable'`);
|
|
22
56
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
57
|
+
secrets.push(`import { z } from 'zod'`, '');
|
|
58
|
+
// better-auth's session-signing secret is always required (its BETTER_AUTH_SECRET
|
|
59
|
+
// env convention). Wire it so the platform collects it, regardless of providers.
|
|
60
|
+
secrets.push(`export const BetterAuthSecretSchema = z.string()`);
|
|
61
|
+
secrets.push('');
|
|
62
|
+
secrets.push(`wireSecret({`);
|
|
63
|
+
secrets.push(` name: 'betterAuthSecret',`);
|
|
64
|
+
secrets.push(` displayName: 'Better Auth Secret',`);
|
|
65
|
+
secrets.push(` description: 'Signing secret for better-auth sessions',`);
|
|
66
|
+
secrets.push(` secretId: 'BETTER_AUTH_SECRET',`);
|
|
67
|
+
secrets.push(` schema: BetterAuthSecretSchema,`);
|
|
68
|
+
secrets.push(`})`);
|
|
69
|
+
secrets.push('');
|
|
70
|
+
// Zod schemas for each provider's OAuth credentials secret.
|
|
32
71
|
for (const name of known) {
|
|
33
72
|
const def = PROVIDER_REGISTRY[name];
|
|
34
73
|
const schemaName = providerSchemaName(name);
|
|
35
74
|
const fieldLines = Object.entries(def.fields).map(([field, zodExpr]) => ` ${field}: ${zodExpr},`);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
75
|
+
secrets.push(`export const ${schemaName} = z.object({`);
|
|
76
|
+
secrets.push(...fieldLines);
|
|
77
|
+
secrets.push(`})`);
|
|
78
|
+
secrets.push('');
|
|
40
79
|
}
|
|
41
|
-
// wireSecret for
|
|
42
|
-
lines.push(`export const AuthSecretSchema = z.string()`);
|
|
43
|
-
lines.push('');
|
|
44
|
-
lines.push(`wireSecret({`);
|
|
45
|
-
lines.push(` name: 'authSecret',`);
|
|
46
|
-
lines.push(` displayName: 'Auth Secret',`);
|
|
47
|
-
lines.push(` description: 'JWT signing secret for Auth.js sessions',`);
|
|
48
|
-
lines.push(` secretId: 'AUTH_SECRET',`);
|
|
49
|
-
lines.push(` schema: AuthSecretSchema,`);
|
|
50
|
-
lines.push(`})`);
|
|
51
|
-
lines.push('');
|
|
52
|
-
// wireSecret for each provider
|
|
80
|
+
// wireSecret for each provider.
|
|
53
81
|
for (const name of known) {
|
|
54
82
|
const def = PROVIDER_REGISTRY[name];
|
|
55
83
|
const schemaName = providerSchemaName(name);
|
|
56
84
|
const secretName = providerSecretName(name);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
secrets.push(`wireSecret({`);
|
|
86
|
+
secrets.push(` name: '${secretName}',`);
|
|
87
|
+
secrets.push(` displayName: '${def.displayName}',`);
|
|
88
|
+
secrets.push(` secretId: '${def.secretId}',`);
|
|
89
|
+
secrets.push(` schema: ${schemaName},`);
|
|
90
|
+
secrets.push(`})`);
|
|
91
|
+
secrets.push('');
|
|
64
92
|
}
|
|
65
|
-
// wireVariable for providers with non-secret config (issuer, tenantId, etc.)
|
|
93
|
+
// wireVariable for providers with non-secret config (issuer, tenantId, etc.).
|
|
66
94
|
for (const name of known) {
|
|
67
95
|
const def = PROVIDER_REGISTRY[name];
|
|
68
96
|
if (!def.variables)
|
|
69
97
|
continue;
|
|
70
98
|
for (const [field, meta] of Object.entries(def.variables)) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
// PKU111 requires `schema` to be a variable reference, not an inline
|
|
100
|
+
// expression — so each variable gets a named schema const.
|
|
101
|
+
const schemaName = variableSchemaName(name, field);
|
|
102
|
+
secrets.push(`export const ${schemaName} = z.string()`);
|
|
103
|
+
secrets.push('');
|
|
104
|
+
secrets.push(`wireVariable({`);
|
|
105
|
+
secrets.push(` name: '${name}_${field}',`);
|
|
106
|
+
secrets.push(` displayName: '${def.displayName} ${capitalize(field)}',`);
|
|
107
|
+
secrets.push(` description: '${meta.description}',`);
|
|
108
|
+
secrets.push(` variableId: '${meta.variableId}',`);
|
|
109
|
+
secrets.push(` schema: ${schemaName},`);
|
|
110
|
+
secrets.push(`})`);
|
|
111
|
+
secrets.push('');
|
|
79
112
|
}
|
|
80
113
|
}
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
// ─── Wiring file: handler + catch-all routes + session middleware ─────────
|
|
115
|
+
// Provider/plugin metadata for the console SSO page is emitted separately as
|
|
116
|
+
// `auth-meta.gen.json` (see serializeAuthMeta) rather than wired into the
|
|
117
|
+
// runtime, so it is available without evaluating the better-auth factory.
|
|
118
|
+
const wiring = [
|
|
119
|
+
'// AUTO-GENERATED by pikku CLI — do not edit',
|
|
120
|
+
'',
|
|
121
|
+
`import { pikkuSessionlessFunc, wireHTTPRoutes, addHTTPMiddleware } from '${pikkuTypesImportPath}'`,
|
|
122
|
+
`import { createAuthHandler, betterAuthSession } from '@pikku/better-auth'`,
|
|
123
|
+
`import '${configImportPath}'`,
|
|
124
|
+
'',
|
|
125
|
+
// createAuthHandler is called once at module load; the exported handler is a
|
|
126
|
+
// plain arrow (so the inspector can resolve a valid `func`) that delegates to
|
|
127
|
+
// it. The handler's required services are stamped onto its meta by the
|
|
128
|
+
// inspector from the pikkuBetterAuth factory.
|
|
129
|
+
`const authConfigHandler = createAuthHandler()`,
|
|
130
|
+
`export const ${AUTH_HANDLER_FUNC_ID} = pikkuSessionlessFunc({`,
|
|
131
|
+
` func: (services: any, data: any, interaction: any) =>`,
|
|
132
|
+
` authConfigHandler.func(services, data, interaction),`,
|
|
133
|
+
`})`,
|
|
134
|
+
'',
|
|
135
|
+
// Bridge the better-auth session into the Pikku session on every request.
|
|
136
|
+
`addHTTPMiddleware('*', [betterAuthSession()])`,
|
|
137
|
+
'',
|
|
138
|
+
`wireHTTPRoutes({`,
|
|
139
|
+
` routes: {`,
|
|
140
|
+
];
|
|
141
|
+
// One catch-all route per method. `{/*splat}` matches both the bare basePath
|
|
142
|
+
// and every sub-path under it, so better-auth's internal router sees the full
|
|
143
|
+
// request and handles all of its own endpoints.
|
|
144
|
+
for (const method of AUTH_METHODS) {
|
|
145
|
+
wiring.push(` ${method}AuthCatchAll: { method: '${method}', route: '${basePath}{/*splat}', func: ${AUTH_HANDLER_FUNC_ID}, auth: false },`);
|
|
104
146
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
lines.push(` trustHost: true,`);
|
|
110
|
-
lines.push(` basePath: '/auth',`);
|
|
111
|
-
lines.push(` }`);
|
|
112
|
-
lines.push(`})`);
|
|
113
|
-
lines.push('');
|
|
114
|
-
lines.push(`wireHTTPRoutes({ routes: { auth: authRoutes } })`);
|
|
115
|
-
lines.push('');
|
|
116
|
-
return lines.join('\n');
|
|
147
|
+
wiring.push(` },`);
|
|
148
|
+
wiring.push(`})`);
|
|
149
|
+
wiring.push('');
|
|
150
|
+
return { wiring: wiring.join('\n'), secrets: secrets.join('\n') };
|
|
117
151
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AuthDefinition } from '@pikku/inspector';
|
|
2
|
+
export interface AuthMetaProvider {
|
|
3
|
+
id: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
secretId: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AuthMetaPlugin {
|
|
8
|
+
id: string;
|
|
9
|
+
displayName: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The contents of `auth-meta.gen.json` — the static description of a project's
|
|
13
|
+
* Better Auth configuration the console SSO page reads (via getAuthProviders) to
|
|
14
|
+
* show which social providers and plugins are enabled. Replaces the previous
|
|
15
|
+
* runtime `setAuthRegistry`/`getAuthRegistry` mechanism with a generated file
|
|
16
|
+
* following the `*-meta.gen.json` convention.
|
|
17
|
+
*/
|
|
18
|
+
export interface AuthMeta {
|
|
19
|
+
basePath: string;
|
|
20
|
+
hasCredentials: boolean;
|
|
21
|
+
providers: AuthMetaProvider[];
|
|
22
|
+
plugins: AuthMetaPlugin[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Builds the `auth-meta.gen.json` payload from the inspected auth definition.
|
|
26
|
+
*
|
|
27
|
+
* Only providers known to {@link PROVIDER_REGISTRY} are emitted (the same ones
|
|
28
|
+
* the CLI wires a secret for); unknown keys (e.g. wired via the genericOAuth
|
|
29
|
+
* plugin) are dropped since they have no secret metadata. Every plugin id from
|
|
30
|
+
* the config is emitted, enriched with a display name.
|
|
31
|
+
*/
|
|
32
|
+
export declare const serializeAuthMeta: (definition: AuthDefinition, providers: string[]) => AuthMeta;
|