@pikku/inspector 0.12.74 → 0.12.81

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 (219) hide show
  1. package/CHANGELOG.md +347 -0
  2. package/dist/add/add-analytics.d.ts +9 -0
  3. package/dist/add/add-analytics.js +178 -0
  4. package/dist/add/add-auth.js +0 -54
  5. package/dist/add/add-feature-flag.d.ts +2 -0
  6. package/dist/add/add-feature-flag.js +162 -0
  7. package/dist/add/add-wire-addon.js +1 -0
  8. package/dist/add/add-wire-remote-addon.js +1 -0
  9. package/dist/index.d.ts +3 -1
  10. package/dist/index.js +3 -1
  11. package/dist/inspector.js +4 -0
  12. package/dist/surface/collect-surface.d.ts +58 -0
  13. package/dist/surface/collect-surface.js +613 -0
  14. package/dist/types.d.ts +20 -11
  15. package/dist/utils/filter-inspector-state.js +23 -13
  16. package/dist/utils/post-process.js +15 -0
  17. package/dist/utils/read-module-specifiers.d.ts +8 -0
  18. package/dist/utils/read-module-specifiers.js +23 -0
  19. package/dist/utils/read-tsconfig-out-dir.d.ts +5 -0
  20. package/dist/utils/read-tsconfig-out-dir.js +12 -0
  21. package/dist/utils/resolve-deploy-target.d.ts +7 -0
  22. package/dist/utils/resolve-deploy-target.js +15 -7
  23. package/dist/utils/serialize-inspector-state.d.ts +5 -0
  24. package/dist/utils/serialize-inspector-state.js +8 -0
  25. package/dist/visit.js +4 -0
  26. package/package.json +16 -6
  27. package/run-tests.sh +21 -6
  28. package/src/add/add-addon-bans.ts +0 -85
  29. package/src/add/add-agent-scorer.test.ts +0 -197
  30. package/src/add/add-agent-scorer.ts +0 -83
  31. package/src/add/add-agent.test.ts +0 -87
  32. package/src/add/add-agent.ts +0 -638
  33. package/src/add/add-approval-description.ts +0 -76
  34. package/src/add/add-auth.test.ts +0 -791
  35. package/src/add/add-auth.ts +0 -424
  36. package/src/add/add-channel.ts +0 -778
  37. package/src/add/add-cli-renderers.test.ts +0 -74
  38. package/src/add/add-cli.ts +0 -1006
  39. package/src/add/add-credential.ts +0 -186
  40. package/src/add/add-feature.test.ts +0 -306
  41. package/src/add/add-feature.ts +0 -253
  42. package/src/add/add-file-extends-core-type.ts +0 -66
  43. package/src/add/add-file-with-config.ts +0 -45
  44. package/src/add/add-file-with-factory.test.ts +0 -134
  45. package/src/add/add-file-with-factory.ts +0 -288
  46. package/src/add/add-functions.test.ts +0 -743
  47. package/src/add/add-functions.ts +0 -1548
  48. package/src/add/add-gateway.ts +0 -143
  49. package/src/add/add-http-route-ref.test.ts +0 -149
  50. package/src/add/add-http-route.ts +0 -530
  51. package/src/add/add-http-routes.ts +0 -471
  52. package/src/add/add-keyed-wiring.ts +0 -180
  53. package/src/add/add-mcp-prompt.ts +0 -157
  54. package/src/add/add-mcp-resource.ts +0 -175
  55. package/src/add/add-middleware.test.ts +0 -196
  56. package/src/add/add-middleware.ts +0 -879
  57. package/src/add/add-permission-auth.test.ts +0 -59
  58. package/src/add/add-permission.ts +0 -311
  59. package/src/add/add-personas.test.ts +0 -406
  60. package/src/add/add-personas.ts +0 -287
  61. package/src/add/add-queue-worker.ts +0 -117
  62. package/src/add/add-rpc-invocations.test.ts +0 -287
  63. package/src/add/add-rpc-invocations.ts +0 -221
  64. package/src/add/add-scenario-step.test.ts +0 -509
  65. package/src/add/add-schedule.ts +0 -120
  66. package/src/add/add-scope.test.ts +0 -419
  67. package/src/add/add-scope.ts +0 -221
  68. package/src/add/add-secret.ts +0 -10
  69. package/src/add/add-subject-scenario-steps.test.ts +0 -161
  70. package/src/add/add-system-role.test.ts +0 -297
  71. package/src/add/add-system-role.ts +0 -162
  72. package/src/add/add-trigger.ts +0 -165
  73. package/src/add/add-variable.ts +0 -10
  74. package/src/add/add-wire-addon.test.ts +0 -185
  75. package/src/add/add-wire-addon.ts +0 -146
  76. package/src/add/add-wire-remote-addon.ts +0 -80
  77. package/src/add/add-workflow-edge-cases.test.ts +0 -201
  78. package/src/add/add-workflow-fanout.test.ts +0 -166
  79. package/src/add/add-workflow-graph-foreach.test.ts +0 -174
  80. package/src/add/add-workflow-graph-input.test.ts +0 -94
  81. package/src/add/add-workflow-graph-notes.test.ts +0 -74
  82. package/src/add/add-workflow-graph.ts +0 -644
  83. package/src/add/add-workflow-on-error.test.ts +0 -118
  84. package/src/add/add-workflow-return-switch.test.ts +0 -121
  85. package/src/add/add-workflow-silent-drops.test.ts +0 -261
  86. package/src/add/add-workflow-sleep-expression.test.ts +0 -96
  87. package/src/add/add-workflow.test.ts +0 -155
  88. package/src/add/add-workflow.ts +0 -482
  89. package/src/add/addon-bans.test.ts +0 -121
  90. package/src/add/addon-contracts.test.ts +0 -221
  91. package/src/add/allow-escape-hatches.test.ts +0 -144
  92. package/src/add/audit-meta.test.ts +0 -75
  93. package/src/add/classified-sink-check.test.ts +0 -347
  94. package/src/add/config-value-metadata.test.ts +0 -263
  95. package/src/add/dynamic-import-check.test.ts +0 -119
  96. package/src/add/expect-eventually-scenario-only.test.ts +0 -108
  97. package/src/add/function-body-span.test.ts +0 -96
  98. package/src/add/inline-wiring-function.test.ts +0 -104
  99. package/src/add/pii-check.test.ts +0 -258
  100. package/src/add/rpc-type-cast.test.ts +0 -123
  101. package/src/add/scenario-instrumentation.test.ts +0 -89
  102. package/src/add/scenario-instrumentation.ts +0 -42
  103. package/src/add/scenario-marker.test.ts +0 -89
  104. package/src/add/scenario-not-extractable.test.ts +0 -159
  105. package/src/add/scenario-services.test.ts +0 -103
  106. package/src/add/scenario-skip.test.ts +0 -83
  107. package/src/add/validate-workflow-graph-addons.test.ts +0 -102
  108. package/src/add/wire-name-literal.test.ts +0 -117
  109. package/src/error-codes-emitted.test.ts +0 -76
  110. package/src/error-codes.ts +0 -145
  111. package/src/index.ts +0 -66
  112. package/src/inspector.ts +0 -575
  113. package/src/schema-generator.ts +0 -1
  114. package/src/source-file-cache.ts +0 -73
  115. package/src/tsconfig-paths.test.ts +0 -90
  116. package/src/types-map.ts +0 -141
  117. package/src/types.ts +0 -775
  118. package/src/utils/annotate-http-route-auth.test.ts +0 -161
  119. package/src/utils/annotate-http-route-auth.ts +0 -53
  120. package/src/utils/check-classified-sinks.ts +0 -215
  121. package/src/utils/check-pii-output.ts +0 -120
  122. package/src/utils/compute-diagnostics.test.ts +0 -106
  123. package/src/utils/compute-required-schemas.ts +0 -48
  124. package/src/utils/contract-hashes.test.ts +0 -553
  125. package/src/utils/contract-hashes.ts +0 -387
  126. package/src/utils/custom-types-generator.test.ts +0 -124
  127. package/src/utils/custom-types-generator.ts +0 -150
  128. package/src/utils/detect-schema-vendor.test.ts +0 -123
  129. package/src/utils/detect-schema-vendor.ts +0 -120
  130. package/src/utils/does-type-extend-core-type.ts +0 -53
  131. package/src/utils/ensure-function-metadata.ts +0 -385
  132. package/src/utils/extract-function-name.ts +0 -612
  133. package/src/utils/extract-function-node.ts +0 -70
  134. package/src/utils/extract-node-value.test.ts +0 -117
  135. package/src/utils/extract-node-value.ts +0 -172
  136. package/src/utils/extract-secret-usage.test.ts +0 -81
  137. package/src/utils/extract-secret-usage.ts +0 -73
  138. package/src/utils/extract-services.ts +0 -70
  139. package/src/utils/extract-surface-usage.test.ts +0 -147
  140. package/src/utils/extract-surface-usage.ts +0 -121
  141. package/src/utils/filter-inspector-state.test.ts +0 -2207
  142. package/src/utils/filter-inspector-state.ts +0 -1302
  143. package/src/utils/filter-utils.test.ts +0 -843
  144. package/src/utils/filter-utils.ts +0 -161
  145. package/src/utils/find-root-dir.ts +0 -68
  146. package/src/utils/find-zod-transform.test.ts +0 -70
  147. package/src/utils/find-zod-transform.ts +0 -103
  148. package/src/utils/get-exported-variable-name.ts +0 -48
  149. package/src/utils/get-files-and-methods.ts +0 -157
  150. package/src/utils/get-property-value.test.ts +0 -141
  151. package/src/utils/get-property-value.ts +0 -331
  152. package/src/utils/hash.ts +0 -26
  153. package/src/utils/literal-properties.ts +0 -97
  154. package/src/utils/load-addon-functions-meta.test.ts +0 -157
  155. package/src/utils/load-addon-functions-meta.ts +0 -508
  156. package/src/utils/load-addon-scopes.test.ts +0 -138
  157. package/src/utils/middleware.test.ts +0 -204
  158. package/src/utils/middleware.ts +0 -303
  159. package/src/utils/nested-project-filter.test.ts +0 -53
  160. package/src/utils/nested-project-filter.ts +0 -28
  161. package/src/utils/permissions-shorthand.test.ts +0 -79
  162. package/src/utils/permissions.test.ts +0 -125
  163. package/src/utils/permissions.ts +0 -129
  164. package/src/utils/post-process.test.ts +0 -1094
  165. package/src/utils/post-process.ts +0 -1604
  166. package/src/utils/resolve-addon-package.ts +0 -106
  167. package/src/utils/resolve-core-type.test.ts +0 -142
  168. package/src/utils/resolve-core-type.ts +0 -35
  169. package/src/utils/resolve-deploy-target.test.ts +0 -135
  170. package/src/utils/resolve-deploy-target.ts +0 -65
  171. package/src/utils/resolve-function-meta.ts +0 -25
  172. package/src/utils/resolve-function-types.ts +0 -42
  173. package/src/utils/resolve-identifier.ts +0 -46
  174. package/src/utils/resolve-ref-contract.ts +0 -71
  175. package/src/utils/resolve-versions.test.ts +0 -499
  176. package/src/utils/resolve-versions.ts +0 -228
  177. package/src/utils/schema-cache.test.ts +0 -124
  178. package/src/utils/schema-generator.test.ts +0 -272
  179. package/src/utils/schema-generator.ts +0 -780
  180. package/src/utils/secret-alias-services.test.ts +0 -146
  181. package/src/utils/secret-alias-services.ts +0 -58
  182. package/src/utils/serialize-inspector-state.ts +0 -742
  183. package/src/utils/serialize-mcp-json.ts +0 -150
  184. package/src/utils/serialize-middleware-groups-meta.ts +0 -36
  185. package/src/utils/serialize-openapi-json.ts +0 -277
  186. package/src/utils/serialize-permissions-groups-meta.ts +0 -7
  187. package/src/utils/single-declaration.ts +0 -57
  188. package/src/utils/type-utils.ts +0 -145
  189. package/src/utils/validate-auth-sessionless.ts +0 -30
  190. package/src/utils/validate-exposed-functions-gated.test.ts +0 -230
  191. package/src/utils/validate-exposed-functions-gated.ts +0 -103
  192. package/src/utils/validate-tags-resolve-to-middleware.test.ts +0 -108
  193. package/src/utils/validate-tags-resolve-to-middleware.ts +0 -48
  194. package/src/utils/workflow/derive-workflow-plan.test.ts +0 -178
  195. package/src/utils/workflow/derive-workflow-plan.ts +0 -117
  196. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +0 -1465
  197. package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +0 -71
  198. package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +0 -99
  199. package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +0 -161
  200. package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +0 -121
  201. package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +0 -116
  202. package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +0 -145
  203. package/src/utils/workflow/dsl/extract-approval-policy.test.ts +0 -71
  204. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +0 -2254
  205. package/src/utils/workflow/dsl/index.ts +0 -11
  206. package/src/utils/workflow/dsl/patterns.ts +0 -440
  207. package/src/utils/workflow/dsl/validation.ts +0 -203
  208. package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +0 -35
  209. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +0 -543
  210. package/src/utils/workflow/graph/fanout-node-identity.test.ts +0 -105
  211. package/src/utils/workflow/graph/finalize-workflow-wires.ts +0 -310
  212. package/src/utils/workflow/graph/finalize-workflows.test.ts +0 -50
  213. package/src/utils/workflow/graph/finalize-workflows.ts +0 -130
  214. package/src/utils/workflow/graph/index.ts +0 -28
  215. package/src/utils/workflow/graph/serialize-workflow-graph.ts +0 -240
  216. package/src/utils/workflow/graph/workflow-graph.types.ts +0 -281
  217. package/src/visit.ts +0 -181
  218. package/tsconfig.json +0 -19
  219. package/tsconfig.tsbuildinfo +0 -1
@@ -1,424 +0,0 @@
1
- import * as ts from 'typescript'
2
- import type { FunctionServicesMeta } from '@pikku/core/function'
3
- import type { AddWiring } from '../types.js'
4
- import { ErrorCode } from '../error-codes.js'
5
- import { extractServicesFromFunction } from '../utils/extract-services.js'
6
-
7
- /**
8
- * The pikku function id of the single shared auth handler the CLI generates
9
- * (`export const authHandler = pikkuSessionlessFunc(...)` in auth.gen.ts). An
10
- * exported top-level const collapses the catch-all `/api/auth/**` route onto one
11
- * worker, and the export name becomes the function id. Shared with the CLI
12
- * codegen and the post-process service stamp so all three agree on the same id
13
- * without the inspector having to import `@pikku/better-auth`.
14
- */
15
- export const AUTH_HANDLER_FUNC_ID = 'authHandler'
16
-
17
- /** The default better-auth base path when `basePath` is not configured. */
18
- const DEFAULT_BASE_PATH = '/api/auth'
19
-
20
- /**
21
- * Find the first `betterAuth({...})` call anywhere inside the `pikkuBetterAuth`
22
- * factory body. Supports both `(s) => betterAuth({...})` and
23
- * `(s) => { ...; return betterAuth({...}) }`.
24
- */
25
- const findBetterAuthCall = (node: ts.Node): ts.CallExpression | undefined => {
26
- let found: ts.CallExpression | undefined
27
- const visit = (n: ts.Node) => {
28
- if (found) return
29
- if (
30
- ts.isCallExpression(n) &&
31
- ts.isIdentifier(n.expression) &&
32
- n.expression.text === 'betterAuth'
33
- ) {
34
- found = n
35
- return
36
- }
37
- ts.forEachChild(n, visit)
38
- }
39
- visit(node)
40
- return found
41
- }
42
-
43
- /** Read a string-literal property off an object literal, if present. */
44
- const readStringProp = (
45
- obj: ts.ObjectLiteralExpression,
46
- name: string
47
- ): string | undefined => {
48
- const prop = obj.properties.find(
49
- (p) =>
50
- ts.isPropertyAssignment(p) &&
51
- (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
52
- p.name.text === name
53
- ) as ts.PropertyAssignment | undefined
54
- if (prop && ts.isStringLiteral(prop.initializer)) return prop.initializer.text
55
- return undefined
56
- }
57
-
58
- /** Find an object-literal-valued property off an object literal, if present. */
59
- const readObjectProp = (
60
- obj: ts.ObjectLiteralExpression,
61
- name: string
62
- ): ts.ObjectLiteralExpression | undefined => {
63
- const prop = obj.properties.find(
64
- (p) =>
65
- ts.isPropertyAssignment(p) &&
66
- (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
67
- p.name.text === name
68
- ) as ts.PropertyAssignment | undefined
69
- if (prop && ts.isObjectLiteralExpression(prop.initializer))
70
- return prop.initializer
71
- return undefined
72
- }
73
-
74
- /** Find an array-literal-valued property off an object literal, if present. */
75
- const readArrayProp = (
76
- obj: ts.ObjectLiteralExpression,
77
- name: string
78
- ): ts.ArrayLiteralExpression | undefined => {
79
- const prop = obj.properties.find(
80
- (p) =>
81
- ts.isPropertyAssignment(p) &&
82
- (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
83
- p.name.text === name
84
- ) as ts.PropertyAssignment | undefined
85
- if (prop && ts.isArrayLiteralExpression(prop.initializer))
86
- return prop.initializer
87
- return undefined
88
- }
89
-
90
- /** better-auth's own plugin entrypoint. */
91
- const BETTER_AUTH_PLUGINS_MODULE = 'better-auth/plugins'
92
-
93
- /**
94
- * What the names used in a `plugins: [...]` array were bound to by the file's
95
- * imports of {@link BETTER_AUTH_PLUGINS_MODULE}, so a project's local helper
96
- * called `admin` is not mistaken for better-auth's.
97
- */
98
- type PluginBindings = {
99
- /** Local name → the name it is exported under by better-auth. */
100
- named: Map<string, string>
101
- /** Locals bound by `import * as x from 'better-auth/plugins'`. */
102
- namespaces: Set<string>
103
- }
104
-
105
- const readPluginBindings = (source: ts.SourceFile): PluginBindings => {
106
- const named = new Map<string, string>()
107
- const namespaces = new Set<string>()
108
- for (const statement of source.statements) {
109
- if (
110
- !ts.isImportDeclaration(statement) ||
111
- !ts.isStringLiteral(statement.moduleSpecifier) ||
112
- statement.moduleSpecifier.text !== BETTER_AUTH_PLUGINS_MODULE
113
- ) {
114
- continue
115
- }
116
- const bindings = statement.importClause?.namedBindings
117
- if (!bindings) continue
118
- if (ts.isNamespaceImport(bindings)) {
119
- namespaces.add(bindings.name.text)
120
- continue
121
- }
122
- for (const element of bindings.elements) {
123
- named.set(element.name.text, (element.propertyName ?? element.name).text)
124
- }
125
- }
126
- return { named, namespaces }
127
- }
128
-
129
- /**
130
- * Read a `plugins: [...]` entry. better-auth plugins are factory calls —
131
- * `bearer()`, `twoFactor({ ... })`, `pikkuBan()`, or `plugins.bearer()` through a
132
- * namespace import; non-call entries are ignored. `betterAuthExport` is the
133
- * name better-auth exports the plugin under, and is absent for a plugin that
134
- * came from somewhere else, which no policy here applies to.
135
- */
136
- const readPluginEntry = (
137
- el: ts.Expression,
138
- bindings: PluginBindings
139
- ): { id: string; betterAuthExport?: string } | undefined => {
140
- if (!ts.isCallExpression(el)) return undefined
141
- const callee = el.expression
142
- if (ts.isIdentifier(callee)) {
143
- return {
144
- id: callee.text,
145
- betterAuthExport: bindings.named.get(callee.text),
146
- }
147
- }
148
- if (
149
- ts.isPropertyAccessExpression(callee) &&
150
- ts.isIdentifier(callee.expression) &&
151
- bindings.namespaces.has(callee.expression.text)
152
- ) {
153
- return { id: callee.name.text, betterAuthExport: callee.name.text }
154
- }
155
- return undefined
156
- }
157
-
158
- /**
159
- * True when `node` sits inside a GLOBAL middleware registration — i.e. an actual
160
- * global registration, not a bare standalone call or a route-scoped one.
161
- *
162
- * `addGlobalMiddleware(...)` is always global. `addHTTPMiddleware` is global only
163
- * in its array form (`addHTTPMiddleware([...])`) or with the `'*'` wildcard
164
- * pattern; a specific route pattern (`addHTTPMiddleware('/api/admin/*', [...])`)
165
- * scopes the middleware to that route and must NOT count as a global stateless
166
- * registration (#754).
167
- */
168
- const isInsideGlobalMiddlewareRegistration = (node: ts.Node): boolean => {
169
- let parent: ts.Node | undefined = node.parent
170
- while (parent) {
171
- if (ts.isCallExpression(parent) && ts.isIdentifier(parent.expression)) {
172
- const fn = parent.expression.text
173
- if (fn === 'addGlobalMiddleware') return true
174
- if (fn === 'addHTTPMiddleware') {
175
- const first = parent.arguments[0]
176
- if (!first) return false
177
- // String first arg → route pattern (global only when '*'); otherwise
178
- // the array form, which is global.
179
- return ts.isStringLiteral(first) ? first.text === '*' : true
180
- }
181
- }
182
- parent = parent.parent
183
- }
184
- return false
185
- }
186
-
187
- /**
188
- * Refusal for better-auth's `admin()`, which pikku does not support.
189
- *
190
- * `admin()` authorizes its endpoints against a `user.role` column, so wiring it
191
- * means running a second authorization model alongside pikku's scopes and
192
- * keeping one projected onto the other — coarsely, since any single
193
- * `admin:users:*` scope has to project to `role='admin'` and thereby unlocks
194
- * every one of its endpoints underneath. Pikku dropped that projection: user
195
- * management is `@pikku/addon-admin`'s scoped RPCs, and the one capability
196
- * `admin()` had that nothing else did — refusing a session to a banned user —
197
- * is `pikkuBan()`.
198
- *
199
- * Thrown rather than warned because the failure it prevents is silent. An app
200
- * that drops `admin()` without wiring `pikkuBan()` keeps its ban columns and its
201
- * UI, and simply stops enforcing bans.
202
- */
203
- const UNSUPPORTED_ADMIN_PLUGIN = (sourceFile: string): string =>
204
- `better-auth's admin() plugin is not supported by pikku (${sourceFile}).
205
-
206
- It authorizes against a 'user.role' column, while pikku authorizes on scopes —
207
- wiring both means one has to be projected onto the other, and the projection is
208
- coarser than the scopes it stands in for.
209
-
210
- Use pikkuBan() instead:
211
-
212
- import { pikkuBan } from '@pikku/better-auth'
213
- betterAuth({ plugins: [pikkuBan()] })
214
-
215
- pikkuBan() keeps the part of admin() that pikku cannot supply from outside
216
- better-auth — the banned/banReason/banExpires columns and the session hook that
217
- refuses a banned user a session. Everything else admin() offered is already
218
- scoped RPCs in @pikku/addon-admin: list, create, ban, remove, revoke sessions
219
- and set password.`
220
-
221
- /**
222
- * Detects `pikkuBetterAuth((services) => betterAuth({...}))` calls.
223
- *
224
- * `pikkuBetterAuth` is pure: it wraps a factory that returns a configured better-auth
225
- * instance and has NO side effects. The user assigns it to an exported binding,
226
- * e.g.
227
- *
228
- * export const auth = pikkuBetterAuth(async (services) => betterAuth({ ... }))
229
- *
230
- * The pikku CLI discovers that single export and generates a catch-all
231
- * `auth.gen.ts` that wires `${basePath}/**` to one shared handler, registers the
232
- * better-auth session middleware, and emits a `defineSecret` for every configured
233
- * social provider — so the auth routes and secret requirements flow through
234
- * normal inspection into the deploy manifest.
235
- *
236
- * This add-wiring records the exported binding name, source file, basePath, the
237
- * `socialProviders` keys, whether email/password is enabled, and the services
238
- * the factory touches. Exactly one `pikkuBetterAuth` is allowed per codebase.
239
- */
240
- export const addAuth: AddWiring = (logger, node, _checker, state) => {
241
- if (!ts.isCallExpression(node)) return
242
-
243
- const expression = node.expression
244
-
245
- // A user-registered stateless session middleware (custom mapSession) means the
246
- // CLI must NOT auto-generate its own default-map one — the generated one runs
247
- // first and pre-empts the user's via the `if (session) next()` short-circuit
248
- // (pikkujs/pikku#754). Only a GLOBAL registration counts (inside
249
- // addHTTPMiddleware/addGlobalMiddleware) — a bare betterAuthStatelessSession()
250
- // call (e.g. a test harness) is not a registration. Ignore generated files so
251
- // the emitted middleware can't self-trigger the skip.
252
- if (
253
- ts.isIdentifier(expression) &&
254
- expression.text === 'betterAuthStatelessSession' &&
255
- !node.getSourceFile().fileName.endsWith('.gen.ts') &&
256
- isInsideGlobalMiddlewareRegistration(node)
257
- ) {
258
- state.auth.userStatelessSession = true
259
- }
260
-
261
- // Same rule for the stateful variant: a user-registered global
262
- // betterAuthSession(...) (custom mapSession/impersonation/apiKey) means the CLI
263
- // must NOT auto-generate its own default one — the generated one runs first and
264
- // pre-empts the user's via the `if (session) next()` short-circuit. Stateful
265
- // analogue of the betterAuthStatelessSession skip above.
266
- if (
267
- ts.isIdentifier(expression) &&
268
- expression.text === 'betterAuthSession' &&
269
- !node.getSourceFile().fileName.endsWith('.gen.ts') &&
270
- isInsideGlobalMiddlewareRegistration(node)
271
- ) {
272
- state.auth.hasUserSessionMiddleware = true
273
- }
274
-
275
- if (!ts.isIdentifier(expression) || expression.text !== 'pikkuBetterAuth')
276
- return
277
-
278
- const sourceFile = node.getSourceFile().fileName
279
-
280
- // Walk up to the `export const <name> = pikkuBetterAuth(...)` binding.
281
- const varDecl = node.parent
282
- if (!ts.isVariableDeclaration(varDecl) || !ts.isIdentifier(varDecl.name)) {
283
- logger.critical(
284
- ErrorCode.AUTH_NOT_EXPORTED,
285
- `pikkuBetterAuth(...) must be assigned to an exported const, e.g. \`export const auth = pikkuBetterAuth((services) => betterAuth({...}))\` in ${sourceFile}`
286
- )
287
- return
288
- }
289
- const exportName = varDecl.name.text
290
-
291
- // VariableDeclaration -> VariableDeclarationList -> VariableStatement
292
- const declList = varDecl.parent
293
- const varStatement = declList?.parent
294
- const isConst =
295
- ts.isVariableDeclarationList(declList) &&
296
- (declList.flags & ts.NodeFlags.Const) !== 0
297
- const isExported =
298
- varStatement &&
299
- ts.isVariableStatement(varStatement) &&
300
- varStatement.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
301
- if (!isExported || !isConst) {
302
- logger.critical(
303
- ErrorCode.AUTH_NOT_EXPORTED,
304
- `pikkuBetterAuth(...) must be assigned to an exported const so the CLI can import it. Use \`export const ${exportName} = pikkuBetterAuth(...)\` in ${sourceFile}`
305
- )
306
- return
307
- }
308
-
309
- if (state.auth.definition) {
310
- logger.critical(
311
- ErrorCode.DUPLICATE_AUTH_DEFINITION,
312
- `Only one pikkuBetterAuth(...) is allowed per codebase. Found a second in ${sourceFile} (first: ${state.auth.definition.sourceFile}).`
313
- )
314
- return
315
- }
316
-
317
- // The single argument must be the factory: (services) => betterAuth({...}).
318
- const factory = node.arguments[0]
319
- if (
320
- !factory ||
321
- (!ts.isArrowFunction(factory) && !ts.isFunctionExpression(factory))
322
- ) {
323
- logger.critical(
324
- ErrorCode.MISSING_NAME,
325
- `pikkuBetterAuth(...) must take a factory function returning betterAuth(...), e.g. \`pikkuBetterAuth((services) => betterAuth({...}))\` in ${sourceFile}`
326
- )
327
- return
328
- }
329
-
330
- state.auth.files.add(sourceFile)
331
-
332
- // Derive the services the factory touches from its destructured first param —
333
- // the same convention as every other pikku wiring. A non-destructured param
334
- // yields optimized:false (handler gets all singleton services), with the
335
- // standard diagnostic steering the user to destructure.
336
- const services: FunctionServicesMeta = extractServicesFromFunction(factory)
337
-
338
- // Find the inner betterAuth({...}) call to read providers/basePath/credentials.
339
- let basePath = DEFAULT_BASE_PATH
340
- let hasCredentials = false
341
- let cookieCache = false
342
- const betterAuthCall = findBetterAuthCall(factory)
343
- const config = betterAuthCall?.arguments[0]
344
-
345
- if (config && ts.isObjectLiteralExpression(config)) {
346
- basePath = readStringProp(config, 'basePath') ?? DEFAULT_BASE_PATH
347
-
348
- // Detect `session.cookieCache.enabled` → drives the stateless middleware split.
349
- const session = readObjectProp(config, 'session')
350
- if (session) {
351
- const cookieCacheBlock = readObjectProp(session, 'cookieCache')
352
- if (cookieCacheBlock) {
353
- const enabledProp = cookieCacheBlock.properties.find(
354
- (p) =>
355
- ts.isPropertyAssignment(p) &&
356
- (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
357
- p.name.text === 'enabled'
358
- ) as ts.PropertyAssignment | undefined
359
- cookieCache =
360
- !enabledProp ||
361
- enabledProp.initializer.kind !== ts.SyntaxKind.FalseKeyword
362
- }
363
- }
364
-
365
- const emailAndPassword = readObjectProp(config, 'emailAndPassword')
366
- if (emailAndPassword) {
367
- // `emailAndPassword: { enabled: true }` — treat a present block without an
368
- // explicit `enabled: false` as credentials being available.
369
- const enabledProp = emailAndPassword.properties.find(
370
- (p) =>
371
- ts.isPropertyAssignment(p) &&
372
- ts.isIdentifier(p.name) &&
373
- p.name.text === 'enabled'
374
- ) as ts.PropertyAssignment | undefined
375
- hasCredentials =
376
- !enabledProp ||
377
- enabledProp.initializer.kind !== ts.SyntaxKind.FalseKeyword
378
- }
379
-
380
- const socialProviders = readObjectProp(config, 'socialProviders')
381
- if (socialProviders) {
382
- for (const prop of socialProviders.properties) {
383
- const key =
384
- (ts.isPropertyAssignment(prop) ||
385
- ts.isShorthandPropertyAssignment(prop)) &&
386
- (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))
387
- ? prop.name.text
388
- : undefined
389
- if (key && !state.auth.providers.includes(key)) {
390
- state.auth.providers.push(key)
391
- }
392
- }
393
- }
394
-
395
- const plugins = readArrayProp(config, 'plugins')
396
- if (plugins) {
397
- const bindings = readPluginBindings(node.getSourceFile())
398
- for (const el of plugins.elements) {
399
- const entry = readPluginEntry(el, bindings)
400
- if (!entry) continue
401
- if (entry.betterAuthExport === 'admin') {
402
- throw new Error(UNSUPPORTED_ADMIN_PLUGIN(sourceFile))
403
- }
404
- if (!state.auth.plugins.includes(entry.id)) {
405
- state.auth.plugins.push(entry.id)
406
- }
407
- }
408
- }
409
- } else {
410
- logger.warn(
411
- `pikkuBetterAuth in ${sourceFile}: could not statically find a betterAuth({...}) call inside the factory — social provider secrets will not be auto-wired.`
412
- )
413
- }
414
-
415
- state.auth.definition = {
416
- exportName,
417
- sourceFile,
418
- basePath,
419
- hasCredentials,
420
- cookieCache,
421
- plugins: [...state.auth.plugins],
422
- services,
423
- }
424
- }