@pikku/inspector 0.12.22 → 0.12.23
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 +10 -0
- package/dist/add/add-addon-bans.d.ts +7 -0
- package/dist/add/add-addon-bans.js +65 -0
- package/dist/add/add-channel.js +47 -6
- package/dist/add/add-cli.js +17 -0
- package/dist/add/add-http-route.d.ts +11 -1
- package/dist/add/add-http-route.js +37 -0
- package/dist/add/add-http-routes.d.ts +0 -3
- package/dist/add/add-http-routes.js +179 -36
- package/dist/error-codes.d.ts +3 -1
- package/dist/error-codes.js +3 -0
- package/dist/inspector.js +17 -5
- package/dist/types.d.ts +43 -1
- package/dist/utils/get-exported-variable-name.d.ts +2 -0
- package/dist/utils/get-exported-variable-name.js +34 -0
- package/dist/utils/load-addon-functions-meta.js +98 -0
- package/dist/utils/resolve-addon-package.js +3 -1
- package/dist/utils/resolve-ref-contract.d.ts +21 -0
- package/dist/utils/resolve-ref-contract.js +46 -0
- package/dist/utils/serialize-inspector-state.d.ts +1 -0
- package/dist/utils/serialize-inspector-state.js +9 -0
- package/dist/visit.js +24 -19
- package/package.json +1 -1
- package/src/add/add-addon-bans.ts +84 -0
- package/src/add/add-channel.ts +66 -7
- package/src/add/add-cli.ts +30 -0
- package/src/add/add-http-route.ts +75 -1
- package/src/add/add-http-routes.ts +283 -41
- package/src/add/addon-bans.test.ts +121 -0
- package/src/add/addon-contracts.test.ts +221 -0
- package/src/error-codes.ts +4 -0
- package/src/inspector.ts +17 -5
- package/src/types.ts +65 -1
- package/src/utils/get-exported-variable-name.ts +48 -0
- package/src/utils/load-addon-functions-meta.ts +164 -0
- package/src/utils/resolve-addon-package.ts +6 -1
- package/src/utils/resolve-ref-contract.ts +71 -0
- package/src/utils/serialize-inspector-state.ts +10 -0
- package/src/visit.ts +26 -19
- package/tsconfig.tsbuildinfo +1 -1
package/src/add/add-channel.ts
CHANGED
|
@@ -21,6 +21,8 @@ import { resolveIdentifier } from '../utils/resolve-identifier.js'
|
|
|
21
21
|
import { resolveFunctionMeta } from '../utils/resolve-function-meta.js'
|
|
22
22
|
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
23
23
|
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js'
|
|
24
|
+
import { getExportedVariableName } from '../utils/get-exported-variable-name.js'
|
|
25
|
+
import { resolveRefContract } from '../utils/resolve-ref-contract.js'
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Safely get the "initializer" expression of a property-like AST node:
|
|
@@ -40,6 +42,16 @@ function getInitializerOf(
|
|
|
40
42
|
return undefined
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
function getObjectPropertyName(
|
|
46
|
+
name: ts.PropertyName | undefined
|
|
47
|
+
): string | null {
|
|
48
|
+
if (!name) return null
|
|
49
|
+
if (ts.isIdentifier(name)) return name.text
|
|
50
|
+
if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text
|
|
51
|
+
if (ts.isComputedPropertyName(name)) return null
|
|
52
|
+
return name.getText()
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
/**
|
|
44
56
|
* Resolve a handler expression (Identifier, CallExpression, or { func })
|
|
45
57
|
* into its underlying function name.
|
|
@@ -116,6 +128,27 @@ function getHandlerNameFromExpression(
|
|
|
116
128
|
return null
|
|
117
129
|
}
|
|
118
130
|
|
|
131
|
+
function extractExportedChannelRoutes(
|
|
132
|
+
logger: {
|
|
133
|
+
error: (msg: string) => void
|
|
134
|
+
critical: (code: ErrorCode, msg: string) => void
|
|
135
|
+
},
|
|
136
|
+
routes: ts.ObjectLiteralExpression,
|
|
137
|
+
state: InspectorState,
|
|
138
|
+
checker: ts.TypeChecker
|
|
139
|
+
): Record<string, ChannelMessageMeta> {
|
|
140
|
+
const wrapper = ts.factory.createObjectLiteralExpression([
|
|
141
|
+
ts.factory.createPropertyAssignment(
|
|
142
|
+
'onMessageWiring',
|
|
143
|
+
ts.factory.createObjectLiteralExpression([
|
|
144
|
+
ts.factory.createPropertyAssignment('contract', routes),
|
|
145
|
+
])
|
|
146
|
+
),
|
|
147
|
+
])
|
|
148
|
+
|
|
149
|
+
return addMessagesRoutes(logger, wrapper, state, checker).contract ?? {}
|
|
150
|
+
}
|
|
151
|
+
|
|
119
152
|
/**
|
|
120
153
|
* Build out the nested message-routes by looking up each handler
|
|
121
154
|
* in state.functions.meta instead of re-inferring it here.
|
|
@@ -155,9 +188,24 @@ export function addMessagesRoutes(
|
|
|
155
188
|
}
|
|
156
189
|
}
|
|
157
190
|
|
|
191
|
+
const refContract = resolveRefContract(
|
|
192
|
+
chanInit,
|
|
193
|
+
'refChannel',
|
|
194
|
+
state.exportedContracts.addonChannel
|
|
195
|
+
)
|
|
196
|
+
if (refContract) {
|
|
197
|
+
const refChannelKey = getObjectPropertyName(chanElem.name)
|
|
198
|
+
if (!refChannelKey) continue
|
|
199
|
+
result[refChannelKey] = {
|
|
200
|
+
...refContract.contract,
|
|
201
|
+
}
|
|
202
|
+
continue
|
|
203
|
+
}
|
|
204
|
+
|
|
158
205
|
if (!ts.isObjectLiteralExpression(chanInit)) continue
|
|
159
206
|
|
|
160
|
-
const channelKey = chanElem.name
|
|
207
|
+
const channelKey = getObjectPropertyName(chanElem.name)
|
|
208
|
+
if (!channelKey) continue
|
|
161
209
|
result[channelKey] = {}
|
|
162
210
|
|
|
163
211
|
for (const routeElem of chanInit.properties) {
|
|
@@ -168,11 +216,8 @@ export function addMessagesRoutes(
|
|
|
168
216
|
const routeName = routeElem.name
|
|
169
217
|
if (!routeName) continue
|
|
170
218
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (ts.isStringLiteral(routeName)) {
|
|
174
|
-
routeKey = routeName.text
|
|
175
|
-
}
|
|
219
|
+
const routeKey = getObjectPropertyName(routeName)
|
|
220
|
+
if (!routeKey) continue
|
|
176
221
|
|
|
177
222
|
// For shorthand properties, we need to resolve the identifier to its declaration
|
|
178
223
|
if (ts.isShorthandPropertyAssignment(routeElem)) {
|
|
@@ -529,6 +574,18 @@ export const addChannel: AddWiring = (
|
|
|
529
574
|
options
|
|
530
575
|
) => {
|
|
531
576
|
if (!ts.isCallExpression(node)) return
|
|
577
|
+
if (
|
|
578
|
+
ts.isIdentifier(node.expression) &&
|
|
579
|
+
node.expression.text === 'defineChannelRoutes'
|
|
580
|
+
) {
|
|
581
|
+
const exportName = getExportedVariableName(node, options.sourceFile)
|
|
582
|
+
const [firstArg] = node.arguments
|
|
583
|
+
if (exportName && firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
584
|
+
state.exportedContracts.channel[exportName] =
|
|
585
|
+
extractExportedChannelRoutes(logger, firstArg, state, checker)
|
|
586
|
+
}
|
|
587
|
+
return
|
|
588
|
+
}
|
|
532
589
|
const { expression, arguments: args } = node
|
|
533
590
|
if (!ts.isIdentifier(expression) || expression.text !== 'wireChannel') return
|
|
534
591
|
const first = args[0]
|
|
@@ -664,7 +721,9 @@ export const addChannel: AddWiring = (
|
|
|
664
721
|
state.serviceAggregation.usedFunctions.add(message.pikkuFuncId)
|
|
665
722
|
}
|
|
666
723
|
|
|
667
|
-
for (const channelHandlers of Object.values(
|
|
724
|
+
for (const channelHandlers of Object.values(
|
|
725
|
+
messageWirings as Record<string, Record<string, ChannelMessageMeta>>
|
|
726
|
+
)) {
|
|
668
727
|
for (const handler of Object.values(channelHandlers)) {
|
|
669
728
|
state.serviceAggregation.usedFunctions.add(handler.pikkuFuncId)
|
|
670
729
|
}
|
package/src/add/add-cli.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { resolveIdentifier } from '../utils/resolve-identifier.js'
|
|
|
19
19
|
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
20
20
|
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js'
|
|
21
21
|
import { extractServicesFromFunction } from '../utils/extract-services.js'
|
|
22
|
+
import { getExportedVariableName } from '../utils/get-exported-variable-name.js'
|
|
23
|
+
import { resolveRefContract } from '../utils/resolve-ref-contract.js'
|
|
22
24
|
|
|
23
25
|
// Track if we've warned about missing Config type to avoid duplicate warnings
|
|
24
26
|
const configTypeWarningShown = new Set<string>()
|
|
@@ -34,6 +36,25 @@ export const addCLI: AddWiring = (
|
|
|
34
36
|
options
|
|
35
37
|
) => {
|
|
36
38
|
if (!ts.isCallExpression(node)) return
|
|
39
|
+
if (
|
|
40
|
+
ts.isIdentifier(node.expression) &&
|
|
41
|
+
node.expression.text === 'defineCLICommands'
|
|
42
|
+
) {
|
|
43
|
+
const exportName = getExportedVariableName(node, options.sourceFile)
|
|
44
|
+
const [firstArg] = node.arguments
|
|
45
|
+
if (exportName && firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
46
|
+
inspectorState.exportedContracts.cli[exportName] = processCommands(
|
|
47
|
+
logger,
|
|
48
|
+
firstArg,
|
|
49
|
+
node.getSourceFile(),
|
|
50
|
+
typeChecker,
|
|
51
|
+
exportName,
|
|
52
|
+
inspectorState,
|
|
53
|
+
options
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
return
|
|
57
|
+
}
|
|
37
58
|
// Check if this is a wireCLI call
|
|
38
59
|
if (!node || !node.expression) {
|
|
39
60
|
return
|
|
@@ -214,6 +235,15 @@ function processCommands(
|
|
|
214
235
|
programTags
|
|
215
236
|
)
|
|
216
237
|
Object.assign(commands, spreadCommands)
|
|
238
|
+
} else {
|
|
239
|
+
const refCommands = resolveRefContract(
|
|
240
|
+
prop.expression,
|
|
241
|
+
'refCLI',
|
|
242
|
+
inspectorState.exportedContracts.addonCli
|
|
243
|
+
)
|
|
244
|
+
if (refCommands) {
|
|
245
|
+
Object.assign(commands, refCommands.contract)
|
|
246
|
+
}
|
|
217
247
|
}
|
|
218
248
|
continue
|
|
219
249
|
}
|
|
@@ -13,7 +13,11 @@ import {
|
|
|
13
13
|
getPropertyAssignmentInitializer,
|
|
14
14
|
extractTypeKeys,
|
|
15
15
|
} from '../utils/type-utils.js'
|
|
16
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
AddWiring,
|
|
18
|
+
ExportedHTTPRouteConfigMeta,
|
|
19
|
+
InspectorState,
|
|
20
|
+
} from '../types.js'
|
|
17
21
|
import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js'
|
|
18
22
|
import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js'
|
|
19
23
|
import { extractWireNames } from '../utils/post-process.js'
|
|
@@ -40,6 +44,16 @@ export interface RegisterHTTPRouteParams {
|
|
|
40
44
|
inheritedAuth?: boolean
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
export interface RegisterHTTPRouteMetaParams {
|
|
48
|
+
route: ExportedHTTPRouteConfigMeta
|
|
49
|
+
state: InspectorState
|
|
50
|
+
logger: InspectorLogger
|
|
51
|
+
sourceFile: ts.SourceFile
|
|
52
|
+
basePath?: string
|
|
53
|
+
inheritedTags?: string[]
|
|
54
|
+
inheritedAuth?: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
/**
|
|
44
58
|
* Extract header schema reference from headers property
|
|
45
59
|
*/
|
|
@@ -421,6 +435,66 @@ export function registerHTTPRoute({
|
|
|
421
435
|
}
|
|
422
436
|
}
|
|
423
437
|
|
|
438
|
+
export function registerHTTPRouteMeta({
|
|
439
|
+
route,
|
|
440
|
+
state,
|
|
441
|
+
logger,
|
|
442
|
+
sourceFile,
|
|
443
|
+
basePath = '',
|
|
444
|
+
inheritedTags = [],
|
|
445
|
+
inheritedAuth,
|
|
446
|
+
}: RegisterHTTPRouteMetaParams): void {
|
|
447
|
+
const method = route.method.toLowerCase()
|
|
448
|
+
const fullRoute = basePath + route.route
|
|
449
|
+
const tags = [...inheritedTags, ...(route.tags || [])]
|
|
450
|
+
const funcName = route.func.pikkuFuncId
|
|
451
|
+
const fnMeta = resolveFunctionMeta(state, funcName)
|
|
452
|
+
|
|
453
|
+
if (!fnMeta) {
|
|
454
|
+
logger.critical(
|
|
455
|
+
ErrorCode.FUNCTION_METADATA_NOT_FOUND,
|
|
456
|
+
`No function metadata found for '${funcName}'.`
|
|
457
|
+
)
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
let params: string[] = []
|
|
462
|
+
try {
|
|
463
|
+
const keys = pathToRegexp(fullRoute).keys
|
|
464
|
+
params = keys.filter((k) => k.type === 'param').map((k) => k.name)
|
|
465
|
+
} catch (error) {
|
|
466
|
+
logger.error(
|
|
467
|
+
`Failed to parse route '${fullRoute}': ${error instanceof Error ? error.message : error}`
|
|
468
|
+
)
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (!route.func.packageName) {
|
|
473
|
+
computeInputTypes(
|
|
474
|
+
state.http.metaInputTypes,
|
|
475
|
+
method,
|
|
476
|
+
fnMeta.inputs?.[0] || null,
|
|
477
|
+
[],
|
|
478
|
+
params
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
state.serviceAggregation.usedFunctions.add(funcName)
|
|
483
|
+
state.http.files.add(sourceFile.fileName)
|
|
484
|
+
state.http.meta[method][fullRoute] = {
|
|
485
|
+
pikkuFuncId: funcName,
|
|
486
|
+
...(route.func.packageName && { packageName: route.func.packageName }),
|
|
487
|
+
route: fullRoute,
|
|
488
|
+
sourceFile: sourceFile.fileName,
|
|
489
|
+
method: method as HTTPMethod,
|
|
490
|
+
params: params.length > 0 ? params : undefined,
|
|
491
|
+
inputTypes: undefined,
|
|
492
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
493
|
+
sse: route.sse ? true : undefined,
|
|
494
|
+
groupBasePath: basePath || undefined,
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
424
498
|
/**
|
|
425
499
|
* Process wireHTTP calls
|
|
426
500
|
*/
|
|
@@ -1,45 +1,63 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
2
|
import { getPropertyValue } from '../utils/get-property-value.js'
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
AddWiring,
|
|
5
|
+
ExportedHTTPRouteConfigMeta,
|
|
6
|
+
ExportedHTTPRouteMapMeta,
|
|
7
|
+
ExportedHTTPRoutesGroupMeta,
|
|
8
|
+
InspectorLogger,
|
|
9
|
+
InspectorState,
|
|
10
|
+
} from '../types.js'
|
|
11
|
+
import { registerHTTPRoute, registerHTTPRouteMeta } from './add-http-route.js'
|
|
5
12
|
import { resolveIdentifier } from '../utils/resolve-identifier.js'
|
|
13
|
+
import { extractFunctionName } from '../utils/extract-function-name.js'
|
|
14
|
+
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
15
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
16
|
+
import {
|
|
17
|
+
resolveRefContract,
|
|
18
|
+
type RefContractResolution,
|
|
19
|
+
} from '../utils/resolve-ref-contract.js'
|
|
20
|
+
import { getExportedVariableName } from '../utils/get-exported-variable-name.js'
|
|
6
21
|
|
|
7
|
-
/**
|
|
8
|
-
* Group configuration extracted from wireHTTPRoutes or defineHTTPRoutes
|
|
9
|
-
*/
|
|
10
22
|
interface GroupConfig {
|
|
11
23
|
basePath: string
|
|
12
24
|
tags: string[]
|
|
13
25
|
auth?: boolean
|
|
14
26
|
}
|
|
15
27
|
|
|
16
|
-
/**
|
|
17
|
-
* Process wireHTTPRoutes calls
|
|
18
|
-
*/
|
|
19
28
|
export const addHTTPRoutes: AddWiring = (
|
|
20
29
|
logger,
|
|
21
30
|
node,
|
|
22
31
|
checker,
|
|
23
32
|
state,
|
|
24
|
-
|
|
33
|
+
options
|
|
25
34
|
) => {
|
|
26
35
|
if (!ts.isCallExpression(node)) return
|
|
27
36
|
|
|
28
37
|
const { expression, arguments: args } = node
|
|
29
|
-
if (!ts.isIdentifier(expression)
|
|
38
|
+
if (!ts.isIdentifier(expression)) return
|
|
39
|
+
|
|
40
|
+
if (expression.text === 'defineHTTPRoutes') {
|
|
41
|
+
const exportName = getExportedVariableName(node, options.sourceFile)
|
|
42
|
+
const firstArg = args[0]
|
|
43
|
+
if (exportName && firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
44
|
+
const contract = serializeHTTPRoutesContract(firstArg, checker, state)
|
|
45
|
+
if (contract) {
|
|
46
|
+
state.exportedContracts.http[exportName] = contract
|
|
47
|
+
}
|
|
48
|
+
}
|
|
30
49
|
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (expression.text !== 'wireHTTPRoutes') return
|
|
31
53
|
|
|
32
54
|
const firstArg = args[0]
|
|
33
55
|
if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) return
|
|
34
56
|
|
|
35
|
-
// Extract group config
|
|
36
57
|
const groupConfig = extractGroupConfig(firstArg)
|
|
37
|
-
|
|
38
|
-
// Get routes property
|
|
39
58
|
const routesProp = getPropertyAssignment(firstArg, 'routes')
|
|
40
59
|
if (!routesProp) return
|
|
41
60
|
|
|
42
|
-
// Process routes recursively
|
|
43
61
|
processRoutes(
|
|
44
62
|
routesProp.initializer,
|
|
45
63
|
groupConfig,
|
|
@@ -50,9 +68,6 @@ export const addHTTPRoutes: AddWiring = (
|
|
|
50
68
|
)
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
/**
|
|
54
|
-
* Get a property assignment from an object literal
|
|
55
|
-
*/
|
|
56
71
|
function getPropertyAssignment(
|
|
57
72
|
obj: ts.ObjectLiteralExpression,
|
|
58
73
|
propName: string
|
|
@@ -69,9 +84,6 @@ function getPropertyAssignment(
|
|
|
69
84
|
return undefined
|
|
70
85
|
}
|
|
71
86
|
|
|
72
|
-
/**
|
|
73
|
-
* Extract group configuration from an object literal
|
|
74
|
-
*/
|
|
75
87
|
function extractGroupConfig(obj: ts.ObjectLiteralExpression): GroupConfig {
|
|
76
88
|
const basePath = (getPropertyValue(obj, 'basePath') as string) || ''
|
|
77
89
|
const tags = (getPropertyValue(obj, 'tags') as string[]) || []
|
|
@@ -84,9 +96,6 @@ function extractGroupConfig(obj: ts.ObjectLiteralExpression): GroupConfig {
|
|
|
84
96
|
}
|
|
85
97
|
}
|
|
86
98
|
|
|
87
|
-
/**
|
|
88
|
-
* Merge two group configs following cascading rules
|
|
89
|
-
*/
|
|
90
99
|
function mergeConfigs(parent: GroupConfig, child: GroupConfig): GroupConfig {
|
|
91
100
|
return {
|
|
92
101
|
basePath: parent.basePath + child.basePath,
|
|
@@ -95,9 +104,6 @@ function mergeConfigs(parent: GroupConfig, child: GroupConfig): GroupConfig {
|
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
/**
|
|
99
|
-
* Check if a value is a route config (has method, func, and route)
|
|
100
|
-
*/
|
|
101
107
|
function isRouteConfig(obj: ts.ObjectLiteralExpression): boolean {
|
|
102
108
|
let hasMethod = false
|
|
103
109
|
let hasFunc = false
|
|
@@ -114,9 +120,6 @@ function isRouteConfig(obj: ts.ObjectLiteralExpression): boolean {
|
|
|
114
120
|
return hasMethod && hasFunc && hasRoute
|
|
115
121
|
}
|
|
116
122
|
|
|
117
|
-
/**
|
|
118
|
-
* Check if a value is a route contract (has routes property but no method/func)
|
|
119
|
-
*/
|
|
120
123
|
function isRouteContract(obj: ts.ObjectLiteralExpression): boolean {
|
|
121
124
|
let hasRoutes = false
|
|
122
125
|
let hasMethod = false
|
|
@@ -133,9 +136,6 @@ function isRouteContract(obj: ts.ObjectLiteralExpression): boolean {
|
|
|
133
136
|
return hasRoutes && !hasMethod && !hasFunc
|
|
134
137
|
}
|
|
135
138
|
|
|
136
|
-
/**
|
|
137
|
-
* Recursively process routes - handles nested maps, contracts, and identifiers
|
|
138
|
-
*/
|
|
139
139
|
function processRoutes(
|
|
140
140
|
node: ts.Node,
|
|
141
141
|
parentConfig: GroupConfig,
|
|
@@ -144,7 +144,6 @@ function processRoutes(
|
|
|
144
144
|
logger: InspectorLogger,
|
|
145
145
|
sourceFile: ts.SourceFile
|
|
146
146
|
): void {
|
|
147
|
-
// Handle array of routes
|
|
148
147
|
if (ts.isArrayLiteralExpression(node)) {
|
|
149
148
|
for (const element of node.elements) {
|
|
150
149
|
if (ts.isObjectLiteralExpression(element) && isRouteConfig(element)) {
|
|
@@ -154,15 +153,12 @@ function processRoutes(
|
|
|
154
153
|
return
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
// Handle object literal
|
|
158
156
|
if (ts.isObjectLiteralExpression(node)) {
|
|
159
|
-
// Check if this is a route config
|
|
160
157
|
if (isRouteConfig(node)) {
|
|
161
158
|
processRoute(node, parentConfig, state, checker, logger, sourceFile)
|
|
162
159
|
return
|
|
163
160
|
}
|
|
164
161
|
|
|
165
|
-
// Check if this is a route contract
|
|
166
162
|
if (isRouteContract(node)) {
|
|
167
163
|
const contractConfig = extractGroupConfig(node)
|
|
168
164
|
const mergedConfig = mergeConfigs(parentConfig, contractConfig)
|
|
@@ -180,9 +176,17 @@ function processRoutes(
|
|
|
180
176
|
return
|
|
181
177
|
}
|
|
182
178
|
|
|
183
|
-
// Otherwise it's a nested map - process each property
|
|
184
179
|
for (const prop of node.properties) {
|
|
185
180
|
if (ts.isPropertyAssignment(prop)) {
|
|
181
|
+
const ref = resolveRefContract(
|
|
182
|
+
prop.initializer,
|
|
183
|
+
'refHTTP',
|
|
184
|
+
state.exportedContracts.addonHttp
|
|
185
|
+
)
|
|
186
|
+
if (ref) {
|
|
187
|
+
processRefHTTPContract(ref, parentConfig, state, logger, sourceFile)
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
186
190
|
processRoutes(
|
|
187
191
|
prop.initializer,
|
|
188
192
|
parentConfig,
|
|
@@ -196,7 +200,18 @@ function processRoutes(
|
|
|
196
200
|
return
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
|
|
203
|
+
if (ts.isCallExpression(node)) {
|
|
204
|
+
const ref = resolveRefContract(
|
|
205
|
+
node,
|
|
206
|
+
'refHTTP',
|
|
207
|
+
state.exportedContracts.addonHttp
|
|
208
|
+
)
|
|
209
|
+
if (ref) {
|
|
210
|
+
processRefHTTPContract(ref, parentConfig, state, logger, sourceFile)
|
|
211
|
+
}
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
200
215
|
if (ts.isIdentifier(node)) {
|
|
201
216
|
const resolved = resolveIdentifier(node, checker, ['defineHTTPRoutes'])
|
|
202
217
|
if (resolved) {
|
|
@@ -205,9 +220,236 @@ function processRoutes(
|
|
|
205
220
|
}
|
|
206
221
|
}
|
|
207
222
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
223
|
+
function processRefHTTPContract(
|
|
224
|
+
ref: RefContractResolution<ExportedHTTPRoutesGroupMeta>,
|
|
225
|
+
parentConfig: GroupConfig,
|
|
226
|
+
state: InspectorState,
|
|
227
|
+
logger: InspectorLogger,
|
|
228
|
+
sourceFile: ts.SourceFile
|
|
229
|
+
): void {
|
|
230
|
+
const basePath =
|
|
231
|
+
ref.basePath !== undefined ? ref.basePath : ref.contract.basePath || ''
|
|
232
|
+
processExportedRouteMap(
|
|
233
|
+
ref.contract.routes,
|
|
234
|
+
mergeConfigs(parentConfig, {
|
|
235
|
+
basePath,
|
|
236
|
+
tags: ref.contract.tags || [],
|
|
237
|
+
auth: ref.contract.auth,
|
|
238
|
+
}),
|
|
239
|
+
state,
|
|
240
|
+
logger,
|
|
241
|
+
sourceFile
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function processExportedRouteMap(
|
|
246
|
+
routes: ExportedHTTPRouteMapMeta,
|
|
247
|
+
parentConfig: GroupConfig,
|
|
248
|
+
state: InspectorState,
|
|
249
|
+
logger: InspectorLogger,
|
|
250
|
+
sourceFile: ts.SourceFile
|
|
251
|
+
): void {
|
|
252
|
+
for (const value of Object.values(routes)) {
|
|
253
|
+
if (isExportedRouteConfig(value)) {
|
|
254
|
+
registerHTTPRouteMeta({
|
|
255
|
+
route: value,
|
|
256
|
+
state,
|
|
257
|
+
logger,
|
|
258
|
+
sourceFile,
|
|
259
|
+
basePath: parentConfig.basePath,
|
|
260
|
+
inheritedTags: parentConfig.tags,
|
|
261
|
+
inheritedAuth: parentConfig.auth,
|
|
262
|
+
})
|
|
263
|
+
continue
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (isExportedRouteContract(value)) {
|
|
267
|
+
processExportedRouteMap(
|
|
268
|
+
value.routes,
|
|
269
|
+
mergeConfigs(parentConfig, {
|
|
270
|
+
basePath: value.basePath || '',
|
|
271
|
+
tags: value.tags || [],
|
|
272
|
+
auth: value.auth,
|
|
273
|
+
}),
|
|
274
|
+
state,
|
|
275
|
+
logger,
|
|
276
|
+
sourceFile
|
|
277
|
+
)
|
|
278
|
+
continue
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
processExportedRouteMap(value, parentConfig, state, logger, sourceFile)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function isExportedRouteConfig(
|
|
286
|
+
value: ExportedHTTPRouteMapMeta[string]
|
|
287
|
+
): value is ExportedHTTPRouteConfigMeta {
|
|
288
|
+
return (
|
|
289
|
+
typeof value === 'object' &&
|
|
290
|
+
value !== null &&
|
|
291
|
+
'method' in value &&
|
|
292
|
+
'route' in value &&
|
|
293
|
+
'func' in value
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isExportedRouteContract(
|
|
298
|
+
value: ExportedHTTPRouteMapMeta[string]
|
|
299
|
+
): value is ExportedHTTPRoutesGroupMeta {
|
|
300
|
+
return (
|
|
301
|
+
typeof value === 'object' &&
|
|
302
|
+
value !== null &&
|
|
303
|
+
'routes' in value &&
|
|
304
|
+
!('method' in value)
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function serializeHTTPRoutesContract(
|
|
309
|
+
node: ts.ObjectLiteralExpression,
|
|
310
|
+
checker: ts.TypeChecker,
|
|
311
|
+
state: InspectorState
|
|
312
|
+
): ExportedHTTPRoutesGroupMeta | null {
|
|
313
|
+
if (isRouteContract(node)) {
|
|
314
|
+
const routesProp = getPropertyAssignment(node, 'routes')
|
|
315
|
+
if (!routesProp || !ts.isObjectLiteralExpression(routesProp.initializer)) {
|
|
316
|
+
return null
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
...extractGroupConfig(node),
|
|
321
|
+
routes: serializeHTTPRouteMap(routesProp.initializer, checker, state),
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
routes: serializeHTTPRouteMap(node, checker, state),
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function serializeHTTPRouteMap(
|
|
331
|
+
node: ts.ObjectLiteralExpression,
|
|
332
|
+
checker: ts.TypeChecker,
|
|
333
|
+
state: InspectorState
|
|
334
|
+
): ExportedHTTPRouteMapMeta {
|
|
335
|
+
const result: ExportedHTTPRouteMapMeta = {}
|
|
336
|
+
|
|
337
|
+
for (const prop of node.properties) {
|
|
338
|
+
if (!ts.isPropertyAssignment(prop)) continue
|
|
339
|
+
|
|
340
|
+
const key = prop.name.getText().replace(/^['"]|['"]$/g, '')
|
|
341
|
+
const value = prop.initializer
|
|
342
|
+
|
|
343
|
+
if (ts.isObjectLiteralExpression(value)) {
|
|
344
|
+
if (isRouteConfig(value)) {
|
|
345
|
+
const route = serializeHTTPRouteConfig(value, checker, state)
|
|
346
|
+
if (route) {
|
|
347
|
+
result[key] = route
|
|
348
|
+
}
|
|
349
|
+
continue
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (isRouteContract(value)) {
|
|
353
|
+
const routeContract = serializeHTTPRoutesContract(value, checker, state)
|
|
354
|
+
if (routeContract) {
|
|
355
|
+
result[key] = routeContract
|
|
356
|
+
}
|
|
357
|
+
continue
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
result[key] = serializeHTTPRouteMap(value, checker, state)
|
|
361
|
+
continue
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (ts.isIdentifier(value)) {
|
|
365
|
+
const resolved = resolveIdentifier(value, checker, ['defineHTTPRoutes'])
|
|
366
|
+
if (resolved && ts.isObjectLiteralExpression(resolved)) {
|
|
367
|
+
if (isRouteContract(resolved)) {
|
|
368
|
+
const routeContract = serializeHTTPRoutesContract(
|
|
369
|
+
resolved,
|
|
370
|
+
checker,
|
|
371
|
+
state
|
|
372
|
+
)
|
|
373
|
+
if (routeContract) {
|
|
374
|
+
result[key] = routeContract
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
result[key] = serializeHTTPRouteMap(resolved, checker, state)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return result
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function serializeHTTPRouteConfig(
|
|
387
|
+
obj: ts.ObjectLiteralExpression,
|
|
388
|
+
checker: ts.TypeChecker,
|
|
389
|
+
state: InspectorState
|
|
390
|
+
): ExportedHTTPRouteConfigMeta | null {
|
|
391
|
+
const method = getPropertyValue(obj, 'method') as string | null
|
|
392
|
+
const route = getPropertyValue(obj, 'route') as string | null
|
|
393
|
+
const funcInitializer = getPropertyAssignmentInitializer(
|
|
394
|
+
obj,
|
|
395
|
+
'func',
|
|
396
|
+
true,
|
|
397
|
+
checker
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
if (!method || !route || !funcInitializer) {
|
|
401
|
+
return null
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let pikkuFuncId = extractFunctionName(
|
|
405
|
+
funcInitializer,
|
|
406
|
+
checker,
|
|
407
|
+
state.rootDir
|
|
408
|
+
).pikkuFuncId
|
|
409
|
+
let packageName: string | undefined
|
|
410
|
+
|
|
411
|
+
if (
|
|
412
|
+
ts.isCallExpression(funcInitializer) &&
|
|
413
|
+
ts.isIdentifier(funcInitializer.expression) &&
|
|
414
|
+
funcInitializer.expression.text === 'ref'
|
|
415
|
+
) {
|
|
416
|
+
const [firstArg] = funcInitializer.arguments
|
|
417
|
+
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
418
|
+
pikkuFuncId = firstArg.text
|
|
419
|
+
const addonNamespace = pikkuFuncId.includes(':')
|
|
420
|
+
? pikkuFuncId.split(':')[0]
|
|
421
|
+
: null
|
|
422
|
+
packageName = addonNamespace
|
|
423
|
+
? state.rpc.wireAddonDeclarations.get(addonNamespace)?.package
|
|
424
|
+
: undefined
|
|
425
|
+
}
|
|
426
|
+
} else if (ts.isIdentifier(funcInitializer)) {
|
|
427
|
+
packageName =
|
|
428
|
+
resolveAddonName(
|
|
429
|
+
funcInitializer,
|
|
430
|
+
checker,
|
|
431
|
+
state.rpc.wireAddonDeclarations
|
|
432
|
+
) || undefined
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
auth: getPropertyValue(obj, 'auth') as boolean | undefined,
|
|
437
|
+
contentType: getPropertyValue(obj, 'contentType') as string | undefined,
|
|
438
|
+
headers:
|
|
439
|
+
(getPropertyValue(obj, 'headers') as unknown as Record<string, string>) ||
|
|
440
|
+
undefined,
|
|
441
|
+
method,
|
|
442
|
+
route,
|
|
443
|
+
sse: getPropertyValue(obj, 'sse') as boolean | undefined,
|
|
444
|
+
tags: (getPropertyValue(obj, 'tags') as string[]) || undefined,
|
|
445
|
+
timeout: getPropertyValue(obj, 'timeout') as number | undefined,
|
|
446
|
+
func: {
|
|
447
|
+
pikkuFuncId,
|
|
448
|
+
...(packageName && { packageName }),
|
|
449
|
+
},
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
211
453
|
function processRoute(
|
|
212
454
|
obj: ts.ObjectLiteralExpression,
|
|
213
455
|
groupConfig: GroupConfig,
|