@pikku/inspector 0.12.19 → 0.12.20

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/CHANGELOG.md CHANGED
@@ -1,3 +1,77 @@
1
+ ## 0.12.20
2
+
3
+ ### Patch Changes
4
+
5
+ - a027a8e: feat: emit auth provider + plugin metadata as `auth-meta.gen.json` for the console SSO page
6
+
7
+ The enabled social providers and Better Auth plugins are now extracted statically
8
+ and written to a generated `auth-meta.gen.json`, replacing the runtime
9
+ `setAuthRegistry`/`getAuthRegistry` approach — so the console can show them without
10
+ evaluating the Better Auth factory.
11
+ - **inspector**: the `pikkuBetterAuth` inspector now reads the `plugins` array from
12
+ the `betterAuth({ ... })` config and records each plugin id (the callee name of
13
+ each `plugins: [organization(), bearer()]` entry) on the auth definition.
14
+ - **cli**: `pikku auth` (and `pikku all`) emit `auth/pikku-auth-meta.gen.json` (path
15
+ configurable via `authMetaJsonFile`) containing `basePath`, `hasCredentials`, the
16
+ enabled `providers` (`id` + `displayName` + `secretId`), and the enabled `plugins`
17
+ (`id` + `displayName`). The previous `setAuthRegistry(...)` runtime wiring is
18
+ removed from the generated `auth.gen.ts`.
19
+ - **better-auth**: exports a `PLUGIN_REGISTRY` and `pluginDisplayName(id)` helper so
20
+ plugin ids resolve to human-readable names.
21
+ - **core**: removes the unreleased `setAuthRegistry`/`getAuthRegistry` runtime auth
22
+ registry (now superseded by `auth-meta.gen.json`).
23
+ - **addon-console**: `getAuthProviders` reads `auth-meta.gen.json` and returns the
24
+ configured providers, plugins, and `hasCredentials` flag.
25
+ - **console**: the Auth Providers (SSO) page fetches `console:getAuthProviders` and
26
+ marks each provider configured/unconfigured, lists email+password credentials as a
27
+ provider, and shows the enabled Better Auth plugins.
28
+
29
+ - a027a8e: fix: address Better Auth review findings (secret/variable batch typing, auth init, guards)
30
+ - **core**: `SecretService.getSecrets` / `VariablesService.getVariables` (and the
31
+ Local/Typed/Scoped/AWS implementations) now return `Partial<T>`, honestly
32
+ reflecting that missing keys are omitted at runtime rather than typing partial
33
+ data as fully populated. `ScopedSecretService.getSecrets` now throws on a
34
+ disallowed key instead of silently filtering it out.
35
+ - **cli**: the generated `services.auth()` thunk clears its memoised promise on
36
+ rejection, so a transient Better Auth/Kysely startup failure no longer
37
+ permanently poisons auth for the process lifetime.
38
+ - **inspector**: the `pikkuBetterAuth` export guard now requires an exported
39
+ `const` (rejects `export let`/`export var`), matching its error message.
40
+ - **console**: the Microsoft auth provider's `callbackId` is `microsoft` (the
41
+ Better Auth provider id) rather than `microsoft-entra-id`.
42
+
43
+ - a027a8e: feat(auth): migrate auth integration from Auth.js to Better Auth
44
+
45
+ The auth integration is now built on [Better Auth](https://better-auth.com)
46
+ and ships as a single package, `@pikku/better-auth` (replacing the former
47
+ `@pikku/auth-js`). There is exactly one auth package now.
48
+ - `pikkuBetterAuth(async ({ secrets, variables }) => betterAuth({ ... }))` is the new
49
+ single entry point. The CLI inspects the `betterAuth(...)` call and generates:
50
+ - `auth.gen.ts` — a catch-all `${basePath}{/*splat}` HTTP route per method and
51
+ a global `betterAuthSession({ auth })` middleware that bridges the Better
52
+ Auth session into the Pikku wire session.
53
+ - `auth-secrets.gen.ts` — `wireSecret(BETTER_AUTH_SECRET)` plus a
54
+ `<PROVIDER>_OAUTH` secret for each configured social provider, and
55
+ `wireVariable` for non-secret provider config (e.g. `MICROSOFT_TENANT_ID`,
56
+ `COGNITO_DOMAIN`/`REGION`/`USER_POOL_ID`).
57
+ - `auth.types.ts` — a typed `pikkuBetterAuth` re-export.
58
+ - `add-auth` (inspector) walks into the `betterAuth(...)` options to discover the
59
+ configured providers and required secrets/variables.
60
+ - The auth secret is now auto-wired by codegen from `BETTER_AUTH_SECRET` — it no
61
+ longer needs to be registered as a JWT signing key in `services.ts`.
62
+
63
+ CLI fix included: scaffold files generated outside `srcDirectories` (e.g. an
64
+ `auth.gen.ts` under a project's `pikku/` dir) are now added to the inspector's
65
+ wiring files, so their routes and secret metadata are picked up. The generated
66
+ wiring imports Pikku types via a resolved relative path instead of a hardcoded
67
+ `#pikku` specifier, so templates without a `#pikku` import map type-check.
68
+
69
+ - Updated dependencies [a027a8e]
70
+ - Updated dependencies [a027a8e]
71
+ - Updated dependencies [a027a8e]
72
+ - Updated dependencies [a027a8e]
73
+ - @pikku/core@0.12.32
74
+
1
75
  ## 0.12.19
2
76
 
3
77
  ### Patch Changes
@@ -1,2 +1,30 @@
1
1
  import type { AddWiring } from '../types.js';
2
+ /**
3
+ * The pikku function id of the single shared auth handler the CLI generates
4
+ * (`export const authHandler = pikkuSessionlessFunc(...)` in auth.gen.ts). An
5
+ * exported top-level const collapses the catch-all `/api/auth/**` route onto one
6
+ * worker, and the export name becomes the function id. Shared with the CLI
7
+ * codegen and the post-process service stamp so all three agree on the same id
8
+ * without the inspector having to import `@pikku/better-auth`.
9
+ */
10
+ export declare const AUTH_HANDLER_FUNC_ID = "authHandler";
11
+ /**
12
+ * Detects `pikkuBetterAuth((services) => betterAuth({...}))` calls.
13
+ *
14
+ * `pikkuBetterAuth` is pure: it wraps a factory that returns a configured better-auth
15
+ * instance and has NO side effects. The user assigns it to an exported binding,
16
+ * e.g.
17
+ *
18
+ * export const auth = pikkuBetterAuth(async (services) => betterAuth({ ... }))
19
+ *
20
+ * The pikku CLI discovers that single export and generates a catch-all
21
+ * `auth.gen.ts` that wires `${basePath}/**` to one shared handler, registers the
22
+ * better-auth session middleware, and emits a `wireSecret` for every configured
23
+ * social provider — so the auth routes and secret requirements flow through
24
+ * normal inspection into the deploy manifest.
25
+ *
26
+ * This add-wiring records the exported binding name, source file, basePath, the
27
+ * `socialProviders` keys, whether email/password is enabled, and the services
28
+ * the factory touches. Exactly one `pikkuBetterAuth` is allowed per codebase.
29
+ */
2
30
  export declare const addAuth: AddWiring;
@@ -1,34 +1,188 @@
1
1
  import * as ts from 'typescript';
2
2
  import { ErrorCode } from '../error-codes.js';
3
+ import { extractServicesFromFunction } from '../utils/extract-services.js';
4
+ /**
5
+ * The pikku function id of the single shared auth handler the CLI generates
6
+ * (`export const authHandler = pikkuSessionlessFunc(...)` in auth.gen.ts). An
7
+ * exported top-level const collapses the catch-all `/api/auth/**` route onto one
8
+ * worker, and the export name becomes the function id. Shared with the CLI
9
+ * codegen and the post-process service stamp so all three agree on the same id
10
+ * without the inspector having to import `@pikku/better-auth`.
11
+ */
12
+ export const AUTH_HANDLER_FUNC_ID = 'authHandler';
13
+ /** The default better-auth base path when `basePath` is not configured. */
14
+ const DEFAULT_BASE_PATH = '/api/auth';
15
+ /**
16
+ * Find the first `betterAuth({...})` call anywhere inside the `pikkuBetterAuth`
17
+ * factory body. Supports both `(s) => betterAuth({...})` and
18
+ * `(s) => { ...; return betterAuth({...}) }`.
19
+ */
20
+ const findBetterAuthCall = (node) => {
21
+ let found;
22
+ const visit = (n) => {
23
+ if (found)
24
+ return;
25
+ if (ts.isCallExpression(n) &&
26
+ ts.isIdentifier(n.expression) &&
27
+ n.expression.text === 'betterAuth') {
28
+ found = n;
29
+ return;
30
+ }
31
+ ts.forEachChild(n, visit);
32
+ };
33
+ visit(node);
34
+ return found;
35
+ };
36
+ /** Read a string-literal property off an object literal, if present. */
37
+ const readStringProp = (obj, name) => {
38
+ const prop = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
39
+ (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
40
+ p.name.text === name);
41
+ if (prop && ts.isStringLiteral(prop.initializer))
42
+ return prop.initializer.text;
43
+ return undefined;
44
+ };
45
+ /** Find an object-literal-valued property off an object literal, if present. */
46
+ const readObjectProp = (obj, name) => {
47
+ const prop = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
48
+ (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
49
+ p.name.text === name);
50
+ if (prop && ts.isObjectLiteralExpression(prop.initializer))
51
+ return prop.initializer;
52
+ return undefined;
53
+ };
54
+ /** Find an array-literal-valued property off an object literal, if present. */
55
+ const readArrayProp = (obj, name) => {
56
+ const prop = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
57
+ (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
58
+ p.name.text === name);
59
+ if (prop && ts.isArrayLiteralExpression(prop.initializer))
60
+ return prop.initializer;
61
+ return undefined;
62
+ };
63
+ /**
64
+ * Read the callee name of a `plugins: [...]` entry. better-auth plugins are
65
+ * factory calls (`bearer()`, `twoFactor({ ... })`, `admin()`); the entry's id
66
+ * is the called function's name. Member-expression callees (`foo.bar()`) and
67
+ * non-call entries are ignored.
68
+ */
69
+ const readPluginId = (el) => {
70
+ if (ts.isCallExpression(el) && ts.isIdentifier(el.expression))
71
+ return el.expression.text;
72
+ return undefined;
73
+ };
74
+ /**
75
+ * Detects `pikkuBetterAuth((services) => betterAuth({...}))` calls.
76
+ *
77
+ * `pikkuBetterAuth` is pure: it wraps a factory that returns a configured better-auth
78
+ * instance and has NO side effects. The user assigns it to an exported binding,
79
+ * e.g.
80
+ *
81
+ * export const auth = pikkuBetterAuth(async (services) => betterAuth({ ... }))
82
+ *
83
+ * The pikku CLI discovers that single export and generates a catch-all
84
+ * `auth.gen.ts` that wires `${basePath}/**` to one shared handler, registers the
85
+ * better-auth session middleware, and emits a `wireSecret` for every configured
86
+ * social provider — so the auth routes and secret requirements flow through
87
+ * normal inspection into the deploy manifest.
88
+ *
89
+ * This add-wiring records the exported binding name, source file, basePath, the
90
+ * `socialProviders` keys, whether email/password is enabled, and the services
91
+ * the factory touches. Exactly one `pikkuBetterAuth` is allowed per codebase.
92
+ */
3
93
  export const addAuth = (logger, node, _checker, state) => {
4
94
  if (!ts.isCallExpression(node))
5
95
  return;
6
96
  const expression = node.expression;
7
- if (!ts.isIdentifier(expression) || expression.text !== 'wireAuth')
97
+ if (!ts.isIdentifier(expression) || expression.text !== 'pikkuBetterAuth')
8
98
  return;
9
- const firstArg = node.arguments[0];
10
- if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
11
- return;
12
- const providersProp = firstArg.properties.find((p) => ts.isPropertyAssignment(p) &&
13
- (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
14
- p.name.text === 'providers');
15
99
  const sourceFile = node.getSourceFile().fileName;
16
- state.auth.files.add(sourceFile);
17
- if (!providersProp) {
100
+ // Walk up to the `export const <name> = pikkuBetterAuth(...)` binding.
101
+ const varDecl = node.parent;
102
+ if (!ts.isVariableDeclaration(varDecl) || !ts.isIdentifier(varDecl.name)) {
103
+ logger.critical(ErrorCode.AUTH_NOT_EXPORTED, `pikkuBetterAuth(...) must be assigned to an exported const, e.g. \`export const auth = pikkuBetterAuth((services) => betterAuth({...}))\` in ${sourceFile}`);
18
104
  return;
19
105
  }
20
- if (!ts.isArrayLiteralExpression(providersProp.initializer)) {
21
- logger.critical(ErrorCode.MISSING_NAME, 'wireAuth: providers must be an array literal of string literals.');
106
+ const exportName = varDecl.name.text;
107
+ // VariableDeclaration -> VariableDeclarationList -> VariableStatement
108
+ const declList = varDecl.parent;
109
+ const varStatement = declList?.parent;
110
+ const isConst = ts.isVariableDeclarationList(declList) &&
111
+ (declList.flags & ts.NodeFlags.Const) !== 0;
112
+ const isExported = varStatement &&
113
+ ts.isVariableStatement(varStatement) &&
114
+ varStatement.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
115
+ if (!isExported || !isConst) {
116
+ logger.critical(ErrorCode.AUTH_NOT_EXPORTED, `pikkuBetterAuth(...) must be assigned to an exported const so the CLI can import it. Use \`export const ${exportName} = pikkuBetterAuth(...)\` in ${sourceFile}`);
22
117
  return;
23
118
  }
24
- for (const element of providersProp.initializer
25
- .elements) {
26
- if (!ts.isStringLiteral(element)) {
27
- logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, `wireAuth: each provider must be a string literal. Found: ${element.getText()}`);
28
- return;
119
+ if (state.auth.definition) {
120
+ logger.critical(ErrorCode.DUPLICATE_AUTH_DEFINITION, `Only one pikkuBetterAuth(...) is allowed per codebase. Found a second in ${sourceFile} (first: ${state.auth.definition.sourceFile}).`);
121
+ return;
122
+ }
123
+ // The single argument must be the factory: (services) => betterAuth({...}).
124
+ const factory = node.arguments[0];
125
+ if (!factory ||
126
+ (!ts.isArrowFunction(factory) && !ts.isFunctionExpression(factory))) {
127
+ logger.critical(ErrorCode.MISSING_NAME, `pikkuBetterAuth(...) must take a factory function returning betterAuth(...), e.g. \`pikkuBetterAuth((services) => betterAuth({...}))\` in ${sourceFile}`);
128
+ return;
129
+ }
130
+ state.auth.files.add(sourceFile);
131
+ // Derive the services the factory touches from its destructured first param —
132
+ // the same convention as every other pikku wiring. A non-destructured param
133
+ // yields optimized:false (handler gets all singleton services), with the
134
+ // standard diagnostic steering the user to destructure.
135
+ const services = extractServicesFromFunction(factory);
136
+ // Find the inner betterAuth({...}) call to read providers/basePath/credentials.
137
+ let basePath = DEFAULT_BASE_PATH;
138
+ let hasCredentials = false;
139
+ const betterAuthCall = findBetterAuthCall(factory);
140
+ const config = betterAuthCall?.arguments[0];
141
+ if (config && ts.isObjectLiteralExpression(config)) {
142
+ basePath = readStringProp(config, 'basePath') ?? DEFAULT_BASE_PATH;
143
+ const emailAndPassword = readObjectProp(config, 'emailAndPassword');
144
+ if (emailAndPassword) {
145
+ // `emailAndPassword: { enabled: true }` — treat a present block without an
146
+ // explicit `enabled: false` as credentials being available.
147
+ const enabledProp = emailAndPassword.properties.find((p) => ts.isPropertyAssignment(p) &&
148
+ ts.isIdentifier(p.name) &&
149
+ p.name.text === 'enabled');
150
+ hasCredentials =
151
+ !enabledProp ||
152
+ enabledProp.initializer.kind !== ts.SyntaxKind.FalseKeyword;
29
153
  }
30
- if (!state.auth.providers.includes(element.text)) {
31
- state.auth.providers.push(element.text);
154
+ const socialProviders = readObjectProp(config, 'socialProviders');
155
+ if (socialProviders) {
156
+ for (const prop of socialProviders.properties) {
157
+ const key = (ts.isPropertyAssignment(prop) ||
158
+ ts.isShorthandPropertyAssignment(prop)) &&
159
+ (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))
160
+ ? prop.name.text
161
+ : undefined;
162
+ if (key && !state.auth.providers.includes(key)) {
163
+ state.auth.providers.push(key);
164
+ }
165
+ }
32
166
  }
167
+ const plugins = readArrayProp(config, 'plugins');
168
+ if (plugins) {
169
+ for (const el of plugins.elements) {
170
+ const id = readPluginId(el);
171
+ if (id && !state.auth.plugins.includes(id)) {
172
+ state.auth.plugins.push(id);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ else {
178
+ logger.warn(`pikkuBetterAuth in ${sourceFile}: could not statically find a betterAuth({...}) call inside the factory — social provider secrets will not be auto-wired.`);
33
179
  }
180
+ state.auth.definition = {
181
+ exportName,
182
+ sourceFile,
183
+ basePath,
184
+ hasCredentials,
185
+ plugins: [...state.auth.plugins],
186
+ services,
187
+ };
34
188
  };
@@ -30,6 +30,8 @@ export declare enum ErrorCode {
30
30
  INLINE_SCHEMA = "PKU489",
31
31
  FUNCTION_METADATA_NOT_FOUND = "PKU559",
32
32
  HANDLER_NOT_RESOLVED = "PKU568",
33
+ DUPLICATE_AUTH_DEFINITION = "PKU581",
34
+ AUTH_NOT_EXPORTED = "PKU582",
33
35
  ROUTE_PARAM_MISMATCH = "PKU571",
34
36
  ROUTE_QUERY_MISMATCH = "PKU572",
35
37
  AUTH_DISABLED_REQUIRES_SESSIONLESS = "PKU573",
@@ -34,6 +34,9 @@ export var ErrorCode;
34
34
  // Function errors
35
35
  ErrorCode["FUNCTION_METADATA_NOT_FOUND"] = "PKU559";
36
36
  ErrorCode["HANDLER_NOT_RESOLVED"] = "PKU568";
37
+ // Auth errors
38
+ ErrorCode["DUPLICATE_AUTH_DEFINITION"] = "PKU581";
39
+ ErrorCode["AUTH_NOT_EXPORTED"] = "PKU582";
37
40
  // HTTP Route errors
38
41
  ErrorCode["ROUTE_PARAM_MISMATCH"] = "PKU571";
39
42
  ErrorCode["ROUTE_QUERY_MISMATCH"] = "PKU572";
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export type { TypesMap } from './types-map.js';
3
3
  export type * from './types.js';
4
4
  export type { FilesAndMethodsErrors } from './utils/get-files-and-methods.js';
5
5
  export { ErrorCode } from './error-codes.js';
6
+ export { AUTH_HANDLER_FUNC_ID } from './add/add-auth.js';
6
7
  export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
7
8
  export type { SerializableInspectorState } from './utils/serialize-inspector-state.js';
8
9
  export { filterInspectorState } from './utils/filter-inspector-state.js';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { inspect, getInitialInspectorState } from './inspector.js';
2
2
  export { ErrorCode } from './error-codes.js';
3
+ export { AUTH_HANDLER_FUNC_ID } from './add/add-auth.js';
3
4
  export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
4
5
  export { filterInspectorState } from './utils/filter-inspector-state.js';
5
6
  export { resolveDeployTarget } from './utils/resolve-deploy-target.js';
package/dist/inspector.js CHANGED
@@ -4,7 +4,7 @@ import { visitSetup, visitRoutes } from './visit.js';
4
4
  import { TypesMap } from './types-map.js';
5
5
  import { getFilesAndMethods } from './utils/get-files-and-methods.js';
6
6
  import { findCommonAncestor } from './utils/find-root-dir.js';
7
- import { aggregateRequiredServices, validateAgentModels, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, } from './utils/post-process.js';
7
+ import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, } from './utils/post-process.js';
8
8
  import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
9
9
  import { pikkuState } from '@pikku/core/internal';
10
10
  import { resolveLatestVersions } from './utils/resolve-versions.js';
@@ -117,7 +117,9 @@ export function getInitialInspectorState(rootDir) {
117
117
  },
118
118
  auth: {
119
119
  providers: [],
120
+ plugins: [],
120
121
  files: new Set(),
122
+ definition: null,
121
123
  },
122
124
  secrets: {
123
125
  definitions: [],
@@ -254,6 +256,9 @@ export const inspect = async (logger, routeFiles, options = {}) => {
254
256
  logger.debug(`Get files and methods completed in ${(performance.now() - startFilesAndMethods).toFixed(2)}ms`);
255
257
  if (!options.setupOnly) {
256
258
  const startAggregate = performance.now();
259
+ // Apply the inspected auth handler service set before aggregation so it
260
+ // flows into requiredServices (the generated handler's own func is opaque).
261
+ stampAuthHandlerServices(state);
257
262
  aggregateRequiredServices(state);
258
263
  logger.debug(`Aggregate required services completed in ${(performance.now() - startAggregate).toFixed(2)}ms`);
259
264
  computeResolvedIOTypes(state);
package/dist/types.d.ts CHANGED
@@ -246,6 +246,37 @@ export interface InspectorDiagnostic {
246
246
  sourceFile: string;
247
247
  position: number;
248
248
  }
249
+ /** A single discovered `export const X = pikkuBetterAuth((services) => betterAuth({...}))`. */
250
+ export interface AuthDefinition {
251
+ /** The exported binding name the CLI imports (`export const <exportName>`). */
252
+ exportName: string;
253
+ /** Absolute path of the file declaring it. */
254
+ sourceFile: string;
255
+ /** better-auth base path (the `basePath` option, default `/api/auth`). */
256
+ basePath: string;
257
+ /** Whether email/password auth is enabled (`emailAndPassword.enabled`). Written
258
+ * into the generated `auth-meta.gen.json` so the console knows credentials are
259
+ * available alongside the OAuth providers. */
260
+ hasCredentials: boolean;
261
+ /** better-auth plugin ids used in the config's `plugins: [...]` array, read
262
+ * from each entry's callee name (e.g. `bearer()` → `'bearer'`). Written into
263
+ * `auth-meta.gen.json` so the console SSO page can show which plugins are
264
+ * enabled. */
265
+ plugins: string[];
266
+ /**
267
+ * Singleton services the generated auth handler must have available at
268
+ * runtime — the services the `pikkuBetterAuth` factory reaches for (either
269
+ * destructured from its first param, or accessed as `services.<name>` in its
270
+ * body). better-auth's factory typically touches `secrets` and the DB.
271
+ *
272
+ * The generated `authHandler` calls `createAuthHandler(...).func`, an opaque
273
+ * property access the inspector can't see through; without this stamp the
274
+ * deployed auth worker would instantiate none of these services and the
275
+ * factory would receive an undefined `kysely`. Re-derived every inspect and
276
+ * applied to the handler meta before service aggregation runs.
277
+ */
278
+ services: FunctionServicesMeta;
279
+ }
249
280
  export interface InspectorState {
250
281
  rootDir: string;
251
282
  singletonServicesTypeImportMap: PathToNameAndType;
@@ -341,7 +372,12 @@ export interface InspectorState {
341
372
  };
342
373
  auth: {
343
374
  providers: string[];
375
+ plugins: string[];
344
376
  files: Set<string>;
377
+ /** The single `export const X = pikkuBetterAuth({...})` discovered in the
378
+ * codebase, if any. The CLI generates the `/auth/*` HTTP wiring from it.
379
+ * More than one `pikkuBetterAuth` is a critical error. */
380
+ definition: AuthDefinition | null;
345
381
  };
346
382
  secrets: {
347
383
  definitions: SecretDefinitions;
@@ -1,5 +1,19 @@
1
1
  import type { InspectorState, InspectorLogger, InspectorOptions } from '../types.js';
2
2
  import type { MiddlewareMetadata, PermissionMetadata } from '@pikku/core';
3
+ /**
4
+ * Stamp the inspected authorize/callbacks service set onto the generated auth
5
+ * handler's function meta.
6
+ *
7
+ * The CLI generates `export const authHandler = pikkuSessionlessFunc({ func:
8
+ * createAuthHandler(...).func })`. That `func` is an opaque property access, so
9
+ * normal extraction records zero services for the handler — which would leave
10
+ * the deployed auth worker without `kysely`/`variables`/`secrets` and break
11
+ * `authorize` at runtime. `add-auth` already computed the real dependency set
12
+ * (from the pikkuBetterAuth source) into `state.auth.definition.services`; copy it
13
+ * onto the handler meta. Re-derived every inspect and ordered BEFORE
14
+ * `aggregateRequiredServices` so it flows into `requiredServices`.
15
+ */
16
+ export declare function stampAuthHandlerServices(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
3
17
  /**
4
18
  * Helper to extract wire-level middleware/permission names from metadata.
5
19
  * Only extracts type:'wire' variants (individual middleware/permissions).
@@ -1,5 +1,31 @@
1
1
  import { extractTypeKeys } from './type-utils.js';
2
2
  import { ErrorCode } from '../error-codes.js';
3
+ import { AUTH_HANDLER_FUNC_ID } from '../add/add-auth.js';
4
+ /**
5
+ * Stamp the inspected authorize/callbacks service set onto the generated auth
6
+ * handler's function meta.
7
+ *
8
+ * The CLI generates `export const authHandler = pikkuSessionlessFunc({ func:
9
+ * createAuthHandler(...).func })`. That `func` is an opaque property access, so
10
+ * normal extraction records zero services for the handler — which would leave
11
+ * the deployed auth worker without `kysely`/`variables`/`secrets` and break
12
+ * `authorize` at runtime. `add-auth` already computed the real dependency set
13
+ * (from the pikkuBetterAuth source) into `state.auth.definition.services`; copy it
14
+ * onto the handler meta. Re-derived every inspect and ordered BEFORE
15
+ * `aggregateRequiredServices` so it flows into `requiredServices`.
16
+ */
17
+ export function stampAuthHandlerServices(state) {
18
+ const definition = state.auth.definition;
19
+ if (!definition)
20
+ return;
21
+ const handlerMeta = state.functions.meta[AUTH_HANDLER_FUNC_ID];
22
+ if (!handlerMeta)
23
+ return;
24
+ handlerMeta.services = {
25
+ optimized: definition.services.optimized,
26
+ services: [...definition.services.services],
27
+ };
28
+ }
3
29
  /**
4
30
  * Helper to extract wire-level middleware/permission names from metadata.
5
31
  * Only extracts type:'wire' variants (individual middleware/permissions).
@@ -205,7 +205,9 @@ export interface SerializableInspectorState {
205
205
  };
206
206
  auth: {
207
207
  providers: string[];
208
+ plugins: string[];
208
209
  files: string[];
210
+ definition: InspectorState['auth']['definition'];
209
211
  };
210
212
  secrets: {
211
213
  definitions: InspectorState['secrets']['definitions'];
@@ -100,7 +100,9 @@ export function serializeInspectorState(state) {
100
100
  },
101
101
  auth: {
102
102
  providers: state.auth.providers,
103
+ plugins: state.auth.plugins,
103
104
  files: Array.from(state.auth.files),
105
+ definition: state.auth.definition,
104
106
  },
105
107
  secrets: {
106
108
  definitions: state.secrets.definitions,
@@ -252,7 +254,9 @@ export function deserializeInspectorState(data) {
252
254
  },
253
255
  auth: {
254
256
  providers: data.auth?.providers || [],
257
+ plugins: data.auth?.plugins || [],
255
258
  files: new Set(data.auth?.files || []),
259
+ definition: data.auth?.definition || null,
256
260
  },
257
261
  secrets: {
258
262
  definitions: data.secrets?.definitions || [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/inspector",
3
- "version": "0.12.19",
3
+ "version": "0.12.20",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "BUSL-1.1",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
38
- "@pikku/core": "^0.12.31",
38
+ "@pikku/core": "^0.12.32",
39
39
  "path-to-regexp": "^8.3.0",
40
40
  "ts-json-schema-generator": "^2.5.0",
41
41
  "tsx": "^4.21.0",