@pikku/cli 0.12.34 → 0.12.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-Dxl3JsMK.js +233 -0
  3. package/console-app/index.html +1 -1
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
  8. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  9. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  10. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +6 -0
  12. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  13. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  15. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  16. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  17. package/dist/.pikku/function/pikku-function-types.gen.d.ts +2 -2
  18. package/dist/.pikku/function/pikku-function-types.gen.js +17 -3
  19. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  20. package/dist/.pikku/function/pikku-functions-meta.gen.json +238 -221
  21. package/dist/.pikku/function/pikku-functions.gen.js +3 -1
  22. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  23. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  24. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  25. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  26. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  27. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  28. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  29. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  30. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  31. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  32. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  33. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  34. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  35. package/dist/.pikku/pikku-types.gen.js +1 -1
  36. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  37. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  38. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  39. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  40. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  41. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  42. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +5 -4
  43. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  44. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  45. package/dist/.pikku/schemas/register.gen.js +17 -13
  46. package/dist/.pikku/schemas/schemas/DbGenerateInput.schema.json +1 -0
  47. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  48. package/dist/.pikku/schemas/schemas/PikkuFunctionTypesInput.schema.json +1 -0
  49. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  50. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  51. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  52. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  53. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  54. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  55. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  56. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  57. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  58. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  59. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +22 -4
  60. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  61. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  62. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  63. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  64. package/dist/bin/pikku-bin.mjs +2 -2
  65. package/dist/src/cli.wiring.js +5 -0
  66. package/dist/src/fabric/functions/login.function.d.ts +1 -1
  67. package/dist/src/fabric/functions/login.function.js +1 -1
  68. package/dist/src/functions/commands/bootstrap.js +1 -1
  69. package/dist/src/functions/commands/db-generate.d.ts +1 -0
  70. package/dist/src/functions/commands/db-generate.js +45 -0
  71. package/dist/src/functions/commands/db-migrate.js +13 -1
  72. package/dist/src/functions/db/annotation-parser.d.ts +27 -16
  73. package/dist/src/functions/db/annotation-parser.js +50 -110
  74. package/dist/src/functions/db/better-auth-schema.d.ts +23 -0
  75. package/dist/src/functions/db/better-auth-schema.js +122 -0
  76. package/dist/src/functions/db/coercion-plugin.d.ts +1 -1
  77. package/dist/src/functions/db/coercion-plugin.js +4 -0
  78. package/dist/src/functions/db/db-codegen.d.ts +13 -1
  79. package/dist/src/functions/db/db-codegen.js +142 -31
  80. package/dist/src/functions/db/db-introspector.d.ts +6 -0
  81. package/dist/src/functions/db/local-db.d.ts +33 -0
  82. package/dist/src/functions/db/local-db.js +221 -79
  83. package/dist/src/functions/db/postgres/postgres-introspector.js +2 -0
  84. package/dist/src/functions/db/zod-codegen.d.ts +38 -0
  85. package/dist/src/functions/db/zod-codegen.js +153 -38
  86. package/dist/src/functions/validate/workspace-validate.js +1 -1
  87. package/dist/src/functions/wirings/auth/pikku-command-auth.js +30 -4
  88. package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +33 -1
  89. package/dist/src/functions/wirings/auth/serialize-auth-gen.js +122 -88
  90. package/dist/src/functions/wirings/auth/serialize-auth-meta.d.ts +32 -0
  91. package/dist/src/functions/wirings/auth/serialize-auth-meta.js +23 -0
  92. package/dist/src/functions/wirings/auth/serialize-auth-types.d.ts +27 -0
  93. package/dist/src/functions/wirings/auth/serialize-auth-types.js +58 -0
  94. package/dist/src/functions/wirings/functions/pikku-command-function-types.d.ts +7 -1
  95. package/dist/src/functions/wirings/functions/pikku-command-function-types.js +16 -3
  96. package/dist/src/functions/wirings/functions/pikku-command-services.d.ts +1 -1
  97. package/dist/src/functions/wirings/functions/pikku-command-services.js +9 -2
  98. package/dist/src/functions/wirings/functions/serialize-function-types.js +17 -3
  99. package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.d.ts +1 -1
  100. package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.js +2 -1
  101. package/dist/src/functions/workflows/all.workflow.js +16 -2
  102. package/dist/src/scaffold/rpc-remote.gen.js +1 -1
  103. package/dist/src/services.js +8 -0
  104. package/dist/src/utils/pikku-cli-config.js +12 -0
  105. package/dist/tsconfig.tsbuildinfo +1 -1
  106. package/package.json +4 -3
  107. package/skills/pikku-better-auth/SKILL.md +211 -0
  108. package/console-app/assets/index-DsW0T00Z.js +0 -233
  109. package/skills/pikku-auth-js/SKILL.md +0 -339
@@ -1,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
- fields.push({ name: field[1], type: field[2].trim() });
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
- switch (inner) {
133
- case 'string':
134
- schema = 'z.string()';
135
- break;
136
- case 'number':
137
- schema = 'z.number()';
138
- break;
139
- case 'boolean':
140
- schema = 'z.boolean()';
141
- break;
142
- case 'Date':
143
- schema = 'z.date()';
144
- break;
145
- case 'unknown':
146
- schema = 'z.unknown()';
147
- break;
148
- default:
149
- if (inner.endsWith('[]')) {
150
- schema = `z.array(${scalarSchema(inner.slice(0, -2).trim())})`;
151
- }
152
- else {
153
- schema = 'z.unknown()';
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
- break;
211
+ }
156
212
  }
157
213
  if (nullable) {
158
214
  schema += '.nullable()';
159
215
  }
160
216
  return schema;
161
217
  }
162
- /** Split the generic argument list of `Foo<a, b, c>` on top-level commas. */
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 (char === '<')
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 === 'authJsSession');
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
- if (state.auth.providers.length === 0)
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 content = serializeAuthGen(state.auth.providers);
14
- await writeFileInDir(logger, authFile, content);
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
- export declare const serializeAuthGen: (providers: string[]) => string;
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-js';
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
- export const serializeAuthGen = (providers) => {
12
- const known = providers.filter((p) => PROVIDER_REGISTRY[p]);
13
- const unknown = providers.filter((p) => !PROVIDER_REGISTRY[p]);
14
- if (unknown.length > 0) {
15
- throw new Error(`wireAuth: unknown providers: ${unknown.join(', ')}. Supported: ${Object.keys(PROVIDER_REGISTRY).join(', ')}`);
16
- }
17
- const lines = ['// AUTO-GENERATED by pikku CLI do not edit', ''];
18
- // Provider imports
19
- for (const name of known) {
20
- const def = PROVIDER_REGISTRY[name];
21
- lines.push(`import ${def.importName} from '${def.importPath}'`);
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
- if (known.length > 0)
24
- lines.push('');
25
- lines.push(`import { wireSecret } from '@pikku/core/secret'`);
26
- lines.push(`import { wireVariable } from '@pikku/core/variable'`);
27
- lines.push(`import { wireHTTPRoutes } from '@pikku/core/http'`);
28
- lines.push(`import { createAuthRoutes } from '@pikku/auth-js'`);
29
- lines.push(`import { z } from 'zod'`);
30
- lines.push('');
31
- // Zod schemas for each provider
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
- lines.push(`export const ${schemaName} = z.object({`);
37
- lines.push(...fieldLines);
38
- lines.push(`})`);
39
- lines.push('');
75
+ secrets.push(`export const ${schemaName} = z.object({`);
76
+ secrets.push(...fieldLines);
77
+ secrets.push(`})`);
78
+ secrets.push('');
40
79
  }
41
- // wireSecret for AUTH_SECRET
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
- lines.push(`wireSecret({`);
58
- lines.push(` name: '${secretName}',`);
59
- lines.push(` displayName: '${def.displayName}',`);
60
- lines.push(` secretId: '${def.secretId}',`);
61
- lines.push(` schema: ${schemaName},`);
62
- lines.push(`})`);
63
- lines.push('');
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
- lines.push(`wireVariable({`);
72
- lines.push(` name: '${name}_${field}',`);
73
- lines.push(` displayName: '${def.displayName} ${capitalize(field)}',`);
74
- lines.push(` description: '${meta.description}',`);
75
- lines.push(` variableId: '${meta.variableId}',`);
76
- lines.push(` schema: z.string(),`);
77
- lines.push(`})`);
78
- lines.push('');
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
- // Auth route setup
82
- lines.push(`const authRoutes = createAuthRoutes(async (services) => {`);
83
- lines.push(` const secretIds = ['AUTH_SECRET', ${known.map((n) => `'${PROVIDER_REGISTRY[n].secretId}'`).join(', ')}]`);
84
- lines.push(` const secretsMap = new Map(Object.entries(await services.secrets.getSecrets(secretIds)))`);
85
- lines.push('');
86
- lines.push(` const authSecret = secretsMap.get('AUTH_SECRET') as string | undefined`);
87
- lines.push(` const providers: any[] = []`);
88
- lines.push('');
89
- for (const name of known) {
90
- const def = PROVIDER_REGISTRY[name];
91
- const varName = providerSecretName(name);
92
- const hasVariables = def.variables && Object.keys(def.variables).length > 0;
93
- lines.push(` const ${varName}Secrets = secretsMap.get('${def.secretId}')`);
94
- lines.push(` if (${varName}Secrets) {`);
95
- lines.push(` const ${varName}Config = { ...${varName}Secrets as any }`);
96
- if (hasVariables && def.variables) {
97
- for (const [field, meta] of Object.entries(def.variables)) {
98
- lines.push(` const ${varName}${capitalize(field)} = await services.variables.get('${meta.variableId}')`);
99
- lines.push(` if (${varName}${capitalize(field)} !== undefined) ${varName}Config.${field} = ${varName}${capitalize(field)}`);
100
- }
101
- }
102
- lines.push(` providers.push(${def.importName}(${varName}Config))`);
103
- lines.push(` }`);
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
- lines.push('');
106
- lines.push(` return {`);
107
- lines.push(` providers,`);
108
- lines.push(` secret: authSecret,`);
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;