@pikku/inspector 0.12.75 → 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.
- package/CHANGELOG.md +330 -0
- package/dist/add/add-analytics.d.ts +9 -0
- package/dist/add/add-analytics.js +178 -0
- package/dist/add/add-auth.js +0 -54
- package/dist/add/add-feature-flag.d.ts +2 -0
- package/dist/add/add-feature-flag.js +162 -0
- package/dist/add/add-wire-addon.js +1 -0
- package/dist/add/add-wire-remote-addon.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/inspector.js +4 -0
- package/dist/surface/collect-surface.d.ts +58 -0
- package/dist/surface/collect-surface.js +613 -0
- package/dist/types.d.ts +20 -11
- package/dist/utils/filter-inspector-state.js +14 -0
- package/dist/utils/post-process.js +15 -0
- package/dist/utils/read-module-specifiers.d.ts +8 -0
- package/dist/utils/read-module-specifiers.js +23 -0
- package/dist/utils/read-tsconfig-out-dir.d.ts +5 -0
- package/dist/utils/read-tsconfig-out-dir.js +12 -0
- package/dist/utils/resolve-deploy-target.d.ts +7 -0
- package/dist/utils/resolve-deploy-target.js +15 -7
- package/dist/utils/serialize-inspector-state.d.ts +5 -0
- package/dist/utils/serialize-inspector-state.js +8 -0
- package/dist/visit.js +4 -0
- package/package.json +16 -6
- package/run-tests.sh +21 -6
- package/src/add/add-addon-bans.ts +0 -85
- package/src/add/add-agent-scorer.test.ts +0 -197
- package/src/add/add-agent-scorer.ts +0 -83
- package/src/add/add-agent.test.ts +0 -87
- package/src/add/add-agent.ts +0 -638
- package/src/add/add-approval-description.ts +0 -76
- package/src/add/add-auth.test.ts +0 -791
- package/src/add/add-auth.ts +0 -424
- package/src/add/add-channel.ts +0 -778
- package/src/add/add-cli-renderers.test.ts +0 -74
- package/src/add/add-cli.ts +0 -1006
- package/src/add/add-credential.ts +0 -186
- package/src/add/add-feature.test.ts +0 -306
- package/src/add/add-feature.ts +0 -253
- package/src/add/add-file-extends-core-type.ts +0 -66
- package/src/add/add-file-with-config.ts +0 -45
- package/src/add/add-file-with-factory.test.ts +0 -134
- package/src/add/add-file-with-factory.ts +0 -288
- package/src/add/add-functions.test.ts +0 -743
- package/src/add/add-functions.ts +0 -1548
- package/src/add/add-gateway.ts +0 -143
- package/src/add/add-http-route-ref.test.ts +0 -149
- package/src/add/add-http-route.ts +0 -530
- package/src/add/add-http-routes.ts +0 -471
- package/src/add/add-keyed-wiring.ts +0 -180
- package/src/add/add-mcp-prompt.ts +0 -157
- package/src/add/add-mcp-resource.ts +0 -175
- package/src/add/add-middleware.test.ts +0 -196
- package/src/add/add-middleware.ts +0 -879
- package/src/add/add-permission-auth.test.ts +0 -59
- package/src/add/add-permission.ts +0 -311
- package/src/add/add-personas.test.ts +0 -406
- package/src/add/add-personas.ts +0 -287
- package/src/add/add-queue-worker.ts +0 -117
- package/src/add/add-rpc-invocations.test.ts +0 -287
- package/src/add/add-rpc-invocations.ts +0 -221
- package/src/add/add-scenario-step.test.ts +0 -509
- package/src/add/add-schedule.ts +0 -120
- package/src/add/add-scope.test.ts +0 -419
- package/src/add/add-scope.ts +0 -221
- package/src/add/add-secret.ts +0 -10
- package/src/add/add-subject-scenario-steps.test.ts +0 -161
- package/src/add/add-system-role.test.ts +0 -297
- package/src/add/add-system-role.ts +0 -162
- package/src/add/add-trigger.ts +0 -165
- package/src/add/add-variable.ts +0 -10
- package/src/add/add-wire-addon.test.ts +0 -185
- package/src/add/add-wire-addon.ts +0 -146
- package/src/add/add-wire-remote-addon.ts +0 -80
- package/src/add/add-workflow-edge-cases.test.ts +0 -201
- package/src/add/add-workflow-fanout.test.ts +0 -166
- package/src/add/add-workflow-graph-foreach.test.ts +0 -174
- package/src/add/add-workflow-graph-input.test.ts +0 -94
- package/src/add/add-workflow-graph-notes.test.ts +0 -74
- package/src/add/add-workflow-graph.ts +0 -644
- package/src/add/add-workflow-on-error.test.ts +0 -118
- package/src/add/add-workflow-return-switch.test.ts +0 -121
- package/src/add/add-workflow-silent-drops.test.ts +0 -261
- package/src/add/add-workflow-sleep-expression.test.ts +0 -96
- package/src/add/add-workflow.test.ts +0 -155
- package/src/add/add-workflow.ts +0 -482
- package/src/add/addon-bans.test.ts +0 -121
- package/src/add/addon-contracts.test.ts +0 -221
- package/src/add/allow-escape-hatches.test.ts +0 -144
- package/src/add/audit-meta.test.ts +0 -75
- package/src/add/classified-sink-check.test.ts +0 -347
- package/src/add/config-value-metadata.test.ts +0 -263
- package/src/add/dynamic-import-check.test.ts +0 -119
- package/src/add/expect-eventually-scenario-only.test.ts +0 -108
- package/src/add/function-body-span.test.ts +0 -96
- package/src/add/inline-wiring-function.test.ts +0 -104
- package/src/add/pii-check.test.ts +0 -258
- package/src/add/rpc-type-cast.test.ts +0 -123
- package/src/add/scenario-instrumentation.test.ts +0 -89
- package/src/add/scenario-instrumentation.ts +0 -42
- package/src/add/scenario-marker.test.ts +0 -89
- package/src/add/scenario-not-extractable.test.ts +0 -159
- package/src/add/scenario-services.test.ts +0 -103
- package/src/add/scenario-skip.test.ts +0 -83
- package/src/add/validate-workflow-graph-addons.test.ts +0 -102
- package/src/add/wire-name-literal.test.ts +0 -117
- package/src/error-codes-emitted.test.ts +0 -76
- package/src/error-codes.ts +0 -145
- package/src/index.ts +0 -66
- package/src/inspector.ts +0 -575
- package/src/schema-generator.ts +0 -1
- package/src/source-file-cache.ts +0 -73
- package/src/tsconfig-paths.test.ts +0 -90
- package/src/types-map.ts +0 -141
- package/src/types.ts +0 -775
- package/src/utils/annotate-http-route-auth.test.ts +0 -161
- package/src/utils/annotate-http-route-auth.ts +0 -53
- package/src/utils/check-classified-sinks.ts +0 -215
- package/src/utils/check-pii-output.ts +0 -120
- package/src/utils/compute-diagnostics.test.ts +0 -106
- package/src/utils/compute-required-schemas.ts +0 -48
- package/src/utils/contract-hashes.test.ts +0 -553
- package/src/utils/contract-hashes.ts +0 -387
- package/src/utils/custom-types-generator.test.ts +0 -124
- package/src/utils/custom-types-generator.ts +0 -150
- package/src/utils/detect-schema-vendor.test.ts +0 -123
- package/src/utils/detect-schema-vendor.ts +0 -120
- package/src/utils/does-type-extend-core-type.ts +0 -53
- package/src/utils/ensure-function-metadata.ts +0 -385
- package/src/utils/extract-function-name.ts +0 -612
- package/src/utils/extract-function-node.ts +0 -70
- package/src/utils/extract-node-value.test.ts +0 -117
- package/src/utils/extract-node-value.ts +0 -172
- package/src/utils/extract-secret-usage.test.ts +0 -81
- package/src/utils/extract-secret-usage.ts +0 -73
- package/src/utils/extract-services.ts +0 -70
- package/src/utils/extract-surface-usage.test.ts +0 -147
- package/src/utils/extract-surface-usage.ts +0 -121
- package/src/utils/filter-inspector-state.test.ts +0 -2232
- package/src/utils/filter-inspector-state.ts +0 -1298
- package/src/utils/filter-utils.test.ts +0 -843
- package/src/utils/filter-utils.ts +0 -161
- package/src/utils/find-root-dir.ts +0 -68
- package/src/utils/find-zod-transform.test.ts +0 -70
- package/src/utils/find-zod-transform.ts +0 -103
- package/src/utils/get-exported-variable-name.ts +0 -48
- package/src/utils/get-files-and-methods.ts +0 -157
- package/src/utils/get-property-value.test.ts +0 -141
- package/src/utils/get-property-value.ts +0 -331
- package/src/utils/hash.ts +0 -26
- package/src/utils/literal-properties.ts +0 -97
- package/src/utils/load-addon-functions-meta.test.ts +0 -157
- package/src/utils/load-addon-functions-meta.ts +0 -508
- package/src/utils/load-addon-scopes.test.ts +0 -138
- package/src/utils/middleware.test.ts +0 -204
- package/src/utils/middleware.ts +0 -303
- package/src/utils/nested-project-filter.test.ts +0 -53
- package/src/utils/nested-project-filter.ts +0 -28
- package/src/utils/permissions-shorthand.test.ts +0 -79
- package/src/utils/permissions.test.ts +0 -125
- package/src/utils/permissions.ts +0 -129
- package/src/utils/post-process.test.ts +0 -1094
- package/src/utils/post-process.ts +0 -1604
- package/src/utils/resolve-addon-package.ts +0 -106
- package/src/utils/resolve-core-type.test.ts +0 -142
- package/src/utils/resolve-core-type.ts +0 -35
- package/src/utils/resolve-deploy-target.test.ts +0 -135
- package/src/utils/resolve-deploy-target.ts +0 -65
- package/src/utils/resolve-function-meta.ts +0 -25
- package/src/utils/resolve-function-types.ts +0 -42
- package/src/utils/resolve-identifier.ts +0 -46
- package/src/utils/resolve-ref-contract.ts +0 -71
- package/src/utils/resolve-versions.test.ts +0 -499
- package/src/utils/resolve-versions.ts +0 -228
- package/src/utils/schema-cache.test.ts +0 -124
- package/src/utils/schema-generator.test.ts +0 -272
- package/src/utils/schema-generator.ts +0 -780
- package/src/utils/secret-alias-services.test.ts +0 -146
- package/src/utils/secret-alias-services.ts +0 -58
- package/src/utils/serialize-inspector-state.ts +0 -742
- package/src/utils/serialize-mcp-json.ts +0 -150
- package/src/utils/serialize-middleware-groups-meta.ts +0 -36
- package/src/utils/serialize-openapi-json.ts +0 -277
- package/src/utils/serialize-permissions-groups-meta.ts +0 -7
- package/src/utils/single-declaration.ts +0 -57
- package/src/utils/type-utils.ts +0 -145
- package/src/utils/validate-auth-sessionless.ts +0 -30
- package/src/utils/validate-exposed-functions-gated.test.ts +0 -230
- package/src/utils/validate-exposed-functions-gated.ts +0 -103
- package/src/utils/validate-tags-resolve-to-middleware.test.ts +0 -108
- package/src/utils/validate-tags-resolve-to-middleware.ts +0 -48
- package/src/utils/workflow/derive-workflow-plan.test.ts +0 -178
- package/src/utils/workflow/derive-workflow-plan.ts +0 -117
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +0 -1465
- package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +0 -71
- package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +0 -99
- package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +0 -161
- package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +0 -121
- package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +0 -116
- package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +0 -145
- package/src/utils/workflow/dsl/extract-approval-policy.test.ts +0 -71
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +0 -2254
- package/src/utils/workflow/dsl/index.ts +0 -11
- package/src/utils/workflow/dsl/patterns.ts +0 -440
- package/src/utils/workflow/dsl/validation.ts +0 -203
- package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +0 -35
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +0 -543
- package/src/utils/workflow/graph/fanout-node-identity.test.ts +0 -105
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +0 -310
- package/src/utils/workflow/graph/finalize-workflows.test.ts +0 -50
- package/src/utils/workflow/graph/finalize-workflows.ts +0 -130
- package/src/utils/workflow/graph/index.ts +0 -28
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +0 -240
- package/src/utils/workflow/graph/workflow-graph.types.ts +0 -281
- package/src/visit.ts +0 -181
- package/tsconfig.json +0 -19
- package/tsconfig.tsbuildinfo +0 -1
package/src/add/add-auth.ts
DELETED
|
@@ -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
|
-
}
|