@pikku/inspector 0.12.2 → 0.12.4
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 +46 -0
- package/dist/add/add-ai-agent.js +4 -0
- package/dist/add/add-approval-description.d.ts +5 -0
- package/dist/add/add-approval-description.js +52 -0
- package/dist/add/add-channel.js +42 -4
- package/dist/add/add-cli.js +73 -13
- package/dist/add/add-file-with-factory.js +1 -0
- package/dist/add/add-functions.js +22 -3
- package/dist/add/add-gateway.js +5 -0
- package/dist/add/add-http-route.js +5 -0
- package/dist/add/add-mcp-prompt.js +5 -0
- package/dist/add/add-mcp-resource.js +5 -0
- package/dist/add/add-middleware.js +6 -10
- package/dist/add/add-permission.js +10 -12
- package/dist/add/add-queue-worker.js +5 -0
- package/dist/add/add-schedule.js +5 -0
- package/dist/add/add-wire-addon.js +7 -0
- package/dist/add/add-workflow.js +7 -1
- package/dist/error-codes.d.ts +1 -0
- package/dist/error-codes.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +21 -7
- package/dist/types.d.ts +12 -0
- package/dist/utils/custom-types-generator.js +1 -0
- package/dist/utils/load-addon-functions-meta.d.ts +12 -0
- package/dist/utils/load-addon-functions-meta.js +76 -0
- package/dist/utils/post-process.d.ts +9 -0
- package/dist/utils/post-process.js +72 -0
- package/dist/utils/resolve-function-meta.d.ts +11 -0
- package/dist/utils/resolve-function-meta.js +17 -0
- package/dist/utils/schema-generator.js +26 -6
- package/dist/utils/serialize-inspector-state.d.ts +2 -0
- package/dist/utils/serialize-inspector-state.js +5 -0
- package/dist/utils/serialize-mcp-json.js +13 -7
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +1 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +2 -0
- package/dist/visit.js +2 -0
- package/package.json +4 -3
- package/src/add/add-ai-agent.ts +6 -0
- package/src/add/add-approval-description.ts +76 -0
- package/src/add/add-channel.ts +44 -4
- package/src/add/add-cli.ts +108 -21
- package/src/add/add-file-with-factory.ts +1 -0
- package/src/add/add-functions.ts +28 -3
- package/src/add/add-gateway.ts +6 -0
- package/src/add/add-http-route.ts +6 -0
- package/src/add/add-mcp-prompt.ts +6 -0
- package/src/add/add-mcp-resource.ts +6 -0
- package/src/add/add-middleware.ts +6 -14
- package/src/add/add-permission.ts +10 -16
- package/src/add/add-queue-worker.ts +6 -0
- package/src/add/add-schedule.ts +6 -0
- package/src/add/add-wire-addon.ts +8 -0
- package/src/add/add-workflow.ts +11 -1
- package/src/error-codes.ts +3 -0
- package/src/index.ts +1 -0
- package/src/inspector.ts +33 -6
- package/src/types.ts +13 -0
- package/src/utils/custom-types-generator.ts +1 -0
- package/src/utils/load-addon-functions-meta.ts +94 -0
- package/src/utils/post-process.ts +84 -0
- package/src/utils/resolve-function-meta.ts +25 -0
- package/src/utils/schema-generator.ts +38 -10
- package/src/utils/serialize-inspector-state.ts +7 -0
- package/src/utils/serialize-mcp-json.ts +12 -7
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +1 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +2 -0
- package/src/visit.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -38,13 +38,15 @@ function renameTempDefinitions(
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function
|
|
41
|
+
function isInsidePermissionContainer(node: ts.Node): boolean {
|
|
42
42
|
let current = node.parent
|
|
43
43
|
while (current) {
|
|
44
44
|
if (
|
|
45
45
|
ts.isCallExpression(current) &&
|
|
46
46
|
ts.isIdentifier(current.expression) &&
|
|
47
|
-
current.expression.text === 'pikkuPermissionFactory'
|
|
47
|
+
(current.expression.text === 'pikkuPermissionFactory' ||
|
|
48
|
+
current.expression.text === 'addPermission' ||
|
|
49
|
+
current.expression.text === 'addHTTPPermission')
|
|
48
50
|
) {
|
|
49
51
|
return true
|
|
50
52
|
}
|
|
@@ -69,7 +71,7 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
69
71
|
// Handle pikkuPermission(...) - individual permission function definition
|
|
70
72
|
if (expression.text === 'pikkuPermission') {
|
|
71
73
|
// Skip if nested inside pikkuPermissionFactory — the factory handler extracts services itself
|
|
72
|
-
if (
|
|
74
|
+
if (isInsidePermissionContainer(node)) return
|
|
73
75
|
|
|
74
76
|
const arg = args[0]
|
|
75
77
|
if (!arg) return
|
|
@@ -156,7 +158,7 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
if (expression.text === 'pikkuAuth') {
|
|
159
|
-
if (
|
|
161
|
+
if (isInsidePermissionContainer(node)) return
|
|
160
162
|
|
|
161
163
|
const arg = args[0]
|
|
162
164
|
if (!arg) return
|
|
@@ -373,13 +375,10 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
373
375
|
state.rootDir
|
|
374
376
|
)
|
|
375
377
|
|
|
376
|
-
if (permissionNames.length
|
|
377
|
-
|
|
378
|
-
return
|
|
378
|
+
if (permissionNames.length > 0) {
|
|
379
|
+
renameTempDefinitions(state, permissionNames, 'tag', tag)
|
|
379
380
|
}
|
|
380
381
|
|
|
381
|
-
renameTempDefinitions(state, permissionNames, 'tag', tag)
|
|
382
|
-
|
|
383
382
|
const allServices = new Set<string>()
|
|
384
383
|
for (const permissionName of permissionNames) {
|
|
385
384
|
const permissionMeta = state.permissions.definitions[permissionName]
|
|
@@ -479,15 +478,10 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
479
478
|
state.rootDir
|
|
480
479
|
)
|
|
481
480
|
|
|
482
|
-
if (permissionNames.length
|
|
483
|
-
|
|
484
|
-
`• addHTTPPermission('${pattern}', ...) has empty permissions array`
|
|
485
|
-
)
|
|
486
|
-
return
|
|
481
|
+
if (permissionNames.length > 0) {
|
|
482
|
+
renameTempDefinitions(state, permissionNames, 'http', pattern)
|
|
487
483
|
}
|
|
488
484
|
|
|
489
|
-
renameTempDefinitions(state, permissionNames, 'http', pattern)
|
|
490
|
-
|
|
491
485
|
const allServices = new Set<string>()
|
|
492
486
|
for (const permissionName of permissionNames) {
|
|
493
487
|
const permissionMeta = state.permissions.definitions[permissionName]
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
12
12
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
13
13
|
import { extractWireNames } from '../utils/post-process.js'
|
|
14
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
14
15
|
import { ErrorCode } from '../error-codes.js'
|
|
15
16
|
|
|
16
17
|
export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
@@ -65,6 +66,10 @@ export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
|
65
66
|
pikkuFuncId = makeContextBasedId('queue', name)
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
70
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
71
|
+
: null
|
|
72
|
+
|
|
68
73
|
if (!name) {
|
|
69
74
|
logger.critical(
|
|
70
75
|
ErrorCode.MISSING_QUEUE_NAME,
|
|
@@ -85,6 +90,7 @@ export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
|
85
90
|
state.queueWorkers.files.add(node.getSourceFile().fileName)
|
|
86
91
|
state.queueWorkers.meta[name] = {
|
|
87
92
|
pikkuFuncId,
|
|
93
|
+
...(packageName && { packageName }),
|
|
88
94
|
name,
|
|
89
95
|
summary,
|
|
90
96
|
description,
|
package/src/add/add-schedule.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
12
12
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
13
13
|
import { extractWireNames } from '../utils/post-process.js'
|
|
14
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
14
15
|
|
|
15
16
|
import { ErrorCode } from '../error-codes.js'
|
|
16
17
|
export const addSchedule: AddWiring = (
|
|
@@ -71,6 +72,10 @@ export const addSchedule: AddWiring = (
|
|
|
71
72
|
pikkuFuncId = makeContextBasedId('scheduler', nameValue)
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
76
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
77
|
+
: null
|
|
78
|
+
|
|
74
79
|
if (!nameValue || !scheduleValue) {
|
|
75
80
|
return
|
|
76
81
|
}
|
|
@@ -87,6 +92,7 @@ export const addSchedule: AddWiring = (
|
|
|
87
92
|
state.scheduledTasks.files.add(node.getSourceFile().fileName)
|
|
88
93
|
state.scheduledTasks.meta[nameValue] = {
|
|
89
94
|
pikkuFuncId,
|
|
95
|
+
...(packageName && { packageName }),
|
|
90
96
|
name: nameValue,
|
|
91
97
|
schedule: scheduleValue,
|
|
92
98
|
summary,
|
|
@@ -40,6 +40,7 @@ export function addWireAddon(
|
|
|
40
40
|
let name: string | undefined
|
|
41
41
|
let pkg: string | undefined
|
|
42
42
|
let rpcEndpoint: string | undefined
|
|
43
|
+
let mcp: boolean | undefined
|
|
43
44
|
let secretOverrides: Record<string, string> | undefined
|
|
44
45
|
let variableOverrides: Record<string, string> | undefined
|
|
45
46
|
|
|
@@ -53,6 +54,12 @@ export function addWireAddon(
|
|
|
53
54
|
pkg = prop.initializer.text
|
|
54
55
|
} else if (key === 'rpcEndpoint' && ts.isStringLiteral(prop.initializer)) {
|
|
55
56
|
rpcEndpoint = prop.initializer.text
|
|
57
|
+
} else if (
|
|
58
|
+
key === 'mcp' &&
|
|
59
|
+
(prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
60
|
+
prop.initializer.kind === ts.SyntaxKind.FalseKeyword)
|
|
61
|
+
) {
|
|
62
|
+
mcp = prop.initializer.kind === ts.SyntaxKind.TrueKeyword
|
|
56
63
|
} else if (
|
|
57
64
|
key === 'secretOverrides' &&
|
|
58
65
|
ts.isObjectLiteralExpression(prop.initializer)
|
|
@@ -72,6 +79,7 @@ export function addWireAddon(
|
|
|
72
79
|
state.rpc.wireAddonDeclarations.set(name, {
|
|
73
80
|
package: pkg,
|
|
74
81
|
rpcEndpoint,
|
|
82
|
+
mcp,
|
|
75
83
|
secretOverrides,
|
|
76
84
|
variableOverrides,
|
|
77
85
|
})
|
package/src/add/add-workflow.ts
CHANGED
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
extractDescription,
|
|
12
12
|
extractDuration,
|
|
13
13
|
} from '../utils/extract-node-value.js'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getCommonWireMetaData,
|
|
16
|
+
getPropertyValue,
|
|
17
|
+
} from '../utils/get-property-value.js'
|
|
15
18
|
import { extractDSLWorkflow } from '../utils/workflow/dsl/extract-dsl-workflow.js'
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -206,6 +209,7 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
206
209
|
let summary: string | undefined
|
|
207
210
|
let description: string | undefined
|
|
208
211
|
let errors: string[] | undefined
|
|
212
|
+
let inline: boolean | undefined
|
|
209
213
|
|
|
210
214
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
211
215
|
const metadata = getCommonWireMetaData(
|
|
@@ -219,6 +223,11 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
219
223
|
summary = metadata.summary
|
|
220
224
|
description = metadata.description
|
|
221
225
|
errors = metadata.errors
|
|
226
|
+
|
|
227
|
+
const inlineProp = getPropertyValue(firstArg, 'inline')
|
|
228
|
+
if (inlineProp === true) {
|
|
229
|
+
inline = true
|
|
230
|
+
}
|
|
222
231
|
}
|
|
223
232
|
|
|
224
233
|
// Validate that we got a valid function
|
|
@@ -324,5 +333,6 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
324
333
|
description,
|
|
325
334
|
errors,
|
|
326
335
|
tags,
|
|
336
|
+
inline,
|
|
327
337
|
}
|
|
328
338
|
}
|
package/src/error-codes.ts
CHANGED
|
@@ -66,6 +66,9 @@ export enum ErrorCode {
|
|
|
66
66
|
MISSING_MODEL = 'PKU145',
|
|
67
67
|
INVALID_MODEL = 'PKU146',
|
|
68
68
|
|
|
69
|
+
// File structure errors
|
|
70
|
+
SCHEMA_AND_WIRING_COLOCATED = 'PKU490',
|
|
71
|
+
|
|
69
72
|
// Optimization diagnostics
|
|
70
73
|
SERVICES_NOT_DESTRUCTURED = 'PKU410',
|
|
71
74
|
WIRES_NOT_DESTRUCTURED = 'PKU411',
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export {
|
|
|
32
32
|
deserializeAllDslWorkflows,
|
|
33
33
|
} from './utils/workflow/dsl/index.js'
|
|
34
34
|
export { getFilesAndMethods } from './utils/get-files-and-methods.js'
|
|
35
|
+
export { resolveFunctionMeta } from './utils/resolve-function-meta.js'
|
|
35
36
|
export type {
|
|
36
37
|
SerializedWorkflowGraph,
|
|
37
38
|
SerializedWorkflowGraphs,
|
package/src/inspector.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
computePermissionsGroupsMeta,
|
|
21
21
|
computeRequiredSchemas,
|
|
22
22
|
computeDiagnostics,
|
|
23
|
+
validateSchemaWiringSeparation,
|
|
23
24
|
} from './utils/post-process.js'
|
|
24
25
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js'
|
|
25
26
|
import { pikkuState } from '@pikku/core/internal'
|
|
@@ -30,6 +31,10 @@ import {
|
|
|
30
31
|
finalizeWorkflowWires,
|
|
31
32
|
} from './utils/workflow/graph/finalize-workflow-wires.js'
|
|
32
33
|
import { generateAllSchemas } from './utils/schema-generator.js'
|
|
34
|
+
import {
|
|
35
|
+
loadAddonFunctionsMeta,
|
|
36
|
+
loadAddonSchemas,
|
|
37
|
+
} from './utils/load-addon-functions-meta.js'
|
|
33
38
|
import {
|
|
34
39
|
computeContractHashes,
|
|
35
40
|
extractContractsFromMeta,
|
|
@@ -63,6 +68,7 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
63
68
|
typesMap: new TypesMap(),
|
|
64
69
|
meta: {},
|
|
65
70
|
files: new Map(),
|
|
71
|
+
approvalDescriptions: {},
|
|
66
72
|
},
|
|
67
73
|
http: {
|
|
68
74
|
metaInputTypes: new Map(),
|
|
@@ -197,6 +203,8 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
197
203
|
requiredSchemas: new Set(),
|
|
198
204
|
openAPISpec: null,
|
|
199
205
|
diagnostics: [],
|
|
206
|
+
addonFunctions: {},
|
|
207
|
+
program: null,
|
|
200
208
|
}
|
|
201
209
|
}
|
|
202
210
|
|
|
@@ -205,8 +213,7 @@ export const inspect = async (
|
|
|
205
213
|
routeFiles: string[],
|
|
206
214
|
options: InspectorOptions = {}
|
|
207
215
|
): Promise<InspectorState> => {
|
|
208
|
-
const
|
|
209
|
-
const program = ts.createProgram(routeFiles, {
|
|
216
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
210
217
|
target: ts.ScriptTarget.ESNext,
|
|
211
218
|
module: ts.ModuleKind.Node16,
|
|
212
219
|
skipLibCheck: true,
|
|
@@ -215,9 +222,16 @@ export const inspect = async (
|
|
|
215
222
|
types: [],
|
|
216
223
|
allowJs: false,
|
|
217
224
|
checkJs: false,
|
|
218
|
-
}
|
|
225
|
+
}
|
|
226
|
+
const startProgram = performance.now()
|
|
227
|
+
const program = ts.createProgram(
|
|
228
|
+
routeFiles,
|
|
229
|
+
compilerOptions,
|
|
230
|
+
undefined, // host
|
|
231
|
+
options.oldProgram
|
|
232
|
+
)
|
|
219
233
|
logger.debug(
|
|
220
|
-
`Created program in ${(performance.now() - startProgram).toFixed(
|
|
234
|
+
`Created program in ${(performance.now() - startProgram).toFixed(0)}ms (${routeFiles.length} files${options.oldProgram ? ', incremental' : ''})`
|
|
221
235
|
)
|
|
222
236
|
|
|
223
237
|
const startChecker = performance.now()
|
|
@@ -249,9 +263,12 @@ export const inspect = async (
|
|
|
249
263
|
)
|
|
250
264
|
}
|
|
251
265
|
logger.debug(
|
|
252
|
-
`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(
|
|
266
|
+
`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(0)}ms`
|
|
253
267
|
)
|
|
254
268
|
|
|
269
|
+
// Load addon function metadata so wirings can reference addon functions
|
|
270
|
+
await loadAddonFunctionsMeta(logger, state)
|
|
271
|
+
|
|
255
272
|
if (!options.setupOnly) {
|
|
256
273
|
// Second sweep: add all transports
|
|
257
274
|
const startRoutes = performance.now()
|
|
@@ -261,17 +278,21 @@ export const inspect = async (
|
|
|
261
278
|
)
|
|
262
279
|
}
|
|
263
280
|
logger.debug(
|
|
264
|
-
`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(
|
|
281
|
+
`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(0)}ms`
|
|
265
282
|
)
|
|
266
283
|
|
|
267
284
|
resolveLatestVersions(state, logger)
|
|
268
285
|
|
|
269
286
|
if (options.schemaConfig) {
|
|
287
|
+
const startSchemas = performance.now()
|
|
270
288
|
state.schemas = await generateAllSchemas(
|
|
271
289
|
logger,
|
|
272
290
|
options.schemaConfig,
|
|
273
291
|
state
|
|
274
292
|
)
|
|
293
|
+
logger.debug(
|
|
294
|
+
`generateAllSchemas took ${(performance.now() - startSchemas).toFixed(0)}ms`
|
|
295
|
+
)
|
|
275
296
|
computeContractHashes(
|
|
276
297
|
state.schemas,
|
|
277
298
|
state.functions.typesMap,
|
|
@@ -280,6 +301,9 @@ export const inspect = async (
|
|
|
280
301
|
computeRequiredSchemas(state, options)
|
|
281
302
|
}
|
|
282
303
|
|
|
304
|
+
// Re-load addon schemas (generateAllSchemas replaces state.schemas)
|
|
305
|
+
await loadAddonSchemas(logger, state)
|
|
306
|
+
|
|
283
307
|
state.manifest.initial = options.manifest ?? null
|
|
284
308
|
const contracts = extractContractsFromMeta(state.functions.meta)
|
|
285
309
|
const baseManifest = state.manifest.initial ?? createEmptyManifest()
|
|
@@ -311,6 +335,7 @@ export const inspect = async (
|
|
|
311
335
|
computeMiddlewareGroupsMeta(state)
|
|
312
336
|
computePermissionsGroupsMeta(state)
|
|
313
337
|
computeDiagnostics(state)
|
|
338
|
+
validateSchemaWiringSeparation(logger, state)
|
|
314
339
|
|
|
315
340
|
if (options.openAPI) {
|
|
316
341
|
state.openAPISpec = await generateOpenAPISpec(
|
|
@@ -329,5 +354,7 @@ export const inspect = async (
|
|
|
329
354
|
validateVariableOverrides(logger, state)
|
|
330
355
|
}
|
|
331
356
|
|
|
357
|
+
state.program = program
|
|
358
|
+
|
|
332
359
|
return state
|
|
333
360
|
}
|
package/src/types.ts
CHANGED
|
@@ -98,6 +98,7 @@ export interface InspectorFunctionState {
|
|
|
98
98
|
typesMap: TypesMap
|
|
99
99
|
meta: FunctionsMeta
|
|
100
100
|
files: Map<string, { path: string; exportedName: string }>
|
|
101
|
+
approvalDescriptions: Record<string, InspectorApprovalDescriptionDefinition>
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
export interface InspectorChannelState {
|
|
@@ -140,6 +141,14 @@ export interface InspectorAIMiddlewareState {
|
|
|
140
141
|
definitions: Record<string, InspectorMiddlewareDefinition>
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
export interface InspectorApprovalDescriptionDefinition {
|
|
145
|
+
services: FunctionServicesMeta
|
|
146
|
+
wires?: FunctionWiresMeta
|
|
147
|
+
sourceFile: string
|
|
148
|
+
position: number
|
|
149
|
+
exportedName: string | null
|
|
150
|
+
}
|
|
151
|
+
|
|
143
152
|
export interface InspectorPermissionDefinition {
|
|
144
153
|
services: FunctionServicesMeta
|
|
145
154
|
wires?: FunctionWiresMeta
|
|
@@ -216,6 +225,7 @@ export type InspectorOptions = Partial<{
|
|
|
216
225
|
tags: string[]
|
|
217
226
|
manifest: VersionManifest
|
|
218
227
|
modelConfig: InspectorModelConfig
|
|
228
|
+
oldProgram: ts.Program | undefined
|
|
219
229
|
}>
|
|
220
230
|
|
|
221
231
|
export interface InspectorLogger {
|
|
@@ -340,6 +350,7 @@ export interface InspectorState {
|
|
|
340
350
|
{
|
|
341
351
|
package: string
|
|
342
352
|
rpcEndpoint?: string
|
|
353
|
+
mcp?: boolean
|
|
343
354
|
secretOverrides?: Record<string, string>
|
|
344
355
|
variableOverrides?: Record<string, string>
|
|
345
356
|
}
|
|
@@ -409,4 +420,6 @@ export interface InspectorState {
|
|
|
409
420
|
requiredSchemas: Set<string>
|
|
410
421
|
openAPISpec: Record<string, any> | null
|
|
411
422
|
diagnostics: InspectorDiagnostic[]
|
|
423
|
+
addonFunctions: Record<string, FunctionsMeta> // namespace -> addon's function metadata
|
|
424
|
+
program: ts.Program | null // Retained for incremental re-inspection
|
|
412
425
|
}
|
|
@@ -16,6 +16,7 @@ export function generateCustomTypes(
|
|
|
16
16
|
requiredTypes: Set<string>
|
|
17
17
|
) {
|
|
18
18
|
const typeDeclarations = Array.from(typesMap.customTypes.entries())
|
|
19
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
19
20
|
.filter(([_name, { type }]) => {
|
|
20
21
|
const hasUndefinedGeneric =
|
|
21
22
|
/\b(Name|In|Out|Key)\b/.test(type) && /\[.*\]/.test(type)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFile, readdir } from 'fs/promises'
|
|
2
|
+
import { createRequire } from 'module'
|
|
3
|
+
import { join, dirname } from 'path'
|
|
4
|
+
import type { InspectorState, InspectorLogger } from '../types.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* After the setup sweep discovers wireAddon() declarations, load each addon
|
|
8
|
+
* package's function metadata so that wiring handlers (channels, HTTP routes,
|
|
9
|
+
* schedules, etc.) can look up addon function types during the routes sweep.
|
|
10
|
+
*/
|
|
11
|
+
export async function loadAddonFunctionsMeta(
|
|
12
|
+
logger: InspectorLogger,
|
|
13
|
+
state: InspectorState
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const { wireAddonDeclarations } = state.rpc
|
|
16
|
+
if (wireAddonDeclarations.size === 0) return
|
|
17
|
+
|
|
18
|
+
const require = createRequire(join(state.rootDir, 'package.json'))
|
|
19
|
+
|
|
20
|
+
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
21
|
+
try {
|
|
22
|
+
const metaPath = require.resolve(
|
|
23
|
+
`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`
|
|
24
|
+
)
|
|
25
|
+
const raw = await readFile(metaPath, 'utf-8')
|
|
26
|
+
const meta = JSON.parse(raw)
|
|
27
|
+
state.addonFunctions[namespace] = meta
|
|
28
|
+
logger.debug(
|
|
29
|
+
`Loaded ${Object.keys(meta).length} addon functions for '${namespace}' from ${decl.package}`
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// If wireAddon has mcp: true, expose addon functions with mcp: true as MCP tools
|
|
33
|
+
if (decl.mcp) {
|
|
34
|
+
for (const [funcName, funcMeta] of Object.entries<any>(meta)) {
|
|
35
|
+
if (funcMeta.mcp) {
|
|
36
|
+
const toolName = `${namespace}:${funcName}`
|
|
37
|
+
state.mcpEndpoints.toolsMeta[toolName] = {
|
|
38
|
+
pikkuFuncId: `${namespace}:${funcName}`,
|
|
39
|
+
name: toolName,
|
|
40
|
+
description: funcMeta.description || funcMeta.title || funcName,
|
|
41
|
+
inputSchema: funcMeta.inputSchemaName ?? null,
|
|
42
|
+
outputSchema: funcMeta.outputSchemaName ?? null,
|
|
43
|
+
tags: funcMeta.tags,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (error: any) {
|
|
49
|
+
logger.warn(
|
|
50
|
+
`Failed to load addon function metadata for '${namespace}' (${decl.package}): ${error.message}`
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load addon schemas into state.schemas. Called after generateAllSchemas
|
|
58
|
+
* to ensure addon schemas aren't overwritten.
|
|
59
|
+
*/
|
|
60
|
+
export async function loadAddonSchemas(
|
|
61
|
+
logger: InspectorLogger,
|
|
62
|
+
state: InspectorState
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
const { wireAddonDeclarations } = state.rpc
|
|
65
|
+
if (wireAddonDeclarations.size === 0) return
|
|
66
|
+
|
|
67
|
+
const require = createRequire(join(state.rootDir, 'package.json'))
|
|
68
|
+
|
|
69
|
+
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
70
|
+
try {
|
|
71
|
+
const metaPath = require.resolve(
|
|
72
|
+
`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`
|
|
73
|
+
)
|
|
74
|
+
const schemasDir = join(dirname(metaPath), '..', 'schemas', 'schemas')
|
|
75
|
+
try {
|
|
76
|
+
const schemaFiles = await readdir(schemasDir)
|
|
77
|
+
for (const file of schemaFiles) {
|
|
78
|
+
if (!file.endsWith('.schema.json')) continue
|
|
79
|
+
const schemaName = file.replace('.schema.json', '')
|
|
80
|
+
if (!state.schemas[schemaName]) {
|
|
81
|
+
const schemaRaw = await readFile(join(schemasDir, file), 'utf-8')
|
|
82
|
+
state.schemas[schemaName] = JSON.parse(schemaRaw)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// No schemas directory — that's fine
|
|
87
|
+
}
|
|
88
|
+
} catch (error: any) {
|
|
89
|
+
logger.warn(
|
|
90
|
+
`Failed to load addon schemas for '${namespace}' (${decl.package}): ${error.message}`
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -296,6 +296,31 @@ export function computeResolvedIOTypes(state: InspectorState): void {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
state.resolvedIOTypes[pikkuFuncId] = { inputType, outputType }
|
|
299
|
+
|
|
300
|
+
if (meta.inputSchemaName && inputType !== 'null') {
|
|
301
|
+
meta.inputSchemaName = inputType
|
|
302
|
+
}
|
|
303
|
+
if (meta.outputSchemaName && outputType !== 'null') {
|
|
304
|
+
meta.outputSchemaName = outputType
|
|
305
|
+
}
|
|
306
|
+
if (meta.inputs) {
|
|
307
|
+
meta.inputs = meta.inputs.map((name) => {
|
|
308
|
+
try {
|
|
309
|
+
return functions.typesMap.getTypeMeta(name).uniqueName
|
|
310
|
+
} catch {
|
|
311
|
+
return name
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
if (meta.outputs) {
|
|
316
|
+
meta.outputs = meta.outputs.map((name) => {
|
|
317
|
+
try {
|
|
318
|
+
return functions.typesMap.getTypeMeta(name).uniqueName
|
|
319
|
+
} catch {
|
|
320
|
+
return name
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
}
|
|
299
324
|
}
|
|
300
325
|
}
|
|
301
326
|
|
|
@@ -467,6 +492,65 @@ export function validateAgentOverrides(
|
|
|
467
492
|
}
|
|
468
493
|
}
|
|
469
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Validates that Zod schemas and wiring side-effects (wireHTTPRoutes,
|
|
497
|
+
* addPermission, addHTTPMiddleware, etc.) do not coexist in the same file.
|
|
498
|
+
*
|
|
499
|
+
* The CLI uses tsImport to extract Zod schemas at runtime, which executes
|
|
500
|
+
* all top-level code in the file. Wiring calls crash during this process
|
|
501
|
+
* because the pikku state metadata doesn't exist in the CLI context.
|
|
502
|
+
*/
|
|
503
|
+
export function validateSchemaWiringSeparation(
|
|
504
|
+
logger: InspectorLogger,
|
|
505
|
+
state: InspectorState
|
|
506
|
+
): void {
|
|
507
|
+
// Collect files that contain schemas
|
|
508
|
+
const schemaFiles = new Set<string>()
|
|
509
|
+
for (const ref of state.schemaLookup.values()) {
|
|
510
|
+
schemaFiles.add(ref.sourceFile)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Collect files that contain wiring side-effects
|
|
514
|
+
const wiringFiles = new Set<string>()
|
|
515
|
+
|
|
516
|
+
// HTTP route wirings
|
|
517
|
+
for (const file of state.http.files) {
|
|
518
|
+
wiringFiles.add(file)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Permission wirings (addPermission calls)
|
|
522
|
+
for (const meta of state.permissions.tagPermissions.values()) {
|
|
523
|
+
wiringFiles.add(meta.sourceFile)
|
|
524
|
+
}
|
|
525
|
+
for (const meta of state.http.routePermissions.values()) {
|
|
526
|
+
wiringFiles.add(meta.sourceFile)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Middleware wirings (addHTTPMiddleware calls)
|
|
530
|
+
for (const meta of state.http.routeMiddleware.values()) {
|
|
531
|
+
wiringFiles.add(meta.sourceFile)
|
|
532
|
+
}
|
|
533
|
+
for (const meta of state.middleware.tagMiddleware.values()) {
|
|
534
|
+
wiringFiles.add(meta.sourceFile)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Check for overlap
|
|
538
|
+
for (const file of schemaFiles) {
|
|
539
|
+
if (wiringFiles.has(file)) {
|
|
540
|
+
const schemas = Array.from(state.schemaLookup.entries())
|
|
541
|
+
.filter(([, ref]) => ref.sourceFile === file)
|
|
542
|
+
.map(([name]) => name)
|
|
543
|
+
|
|
544
|
+
logger.critical(
|
|
545
|
+
ErrorCode.SCHEMA_AND_WIRING_COLOCATED,
|
|
546
|
+
`File '${file}' contains both Zod schemas (${schemas.join(', ')}) and wiring calls (wireHTTPRoutes, addPermission, etc.). ` +
|
|
547
|
+
`These must be in separate files because the CLI imports schema files at runtime, which triggers wiring side-effects that crash without server context. ` +
|
|
548
|
+
`Move the route/wiring definitions to a dedicated wiring file.`
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
470
554
|
export function computeDiagnostics(state: InspectorState): void {
|
|
471
555
|
const diagnostics: InspectorDiagnostic[] = []
|
|
472
556
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FunctionMeta, FunctionsMeta } from '@pikku/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Look up function metadata by pikkuFuncId, checking both local functions
|
|
5
|
+
* and addon functions. Addon functions use namespaced IDs like 'namespace:funcName'.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveFunctionMeta(
|
|
8
|
+
state: {
|
|
9
|
+
functions: { meta: FunctionsMeta }
|
|
10
|
+
addonFunctions: Record<string, FunctionsMeta>
|
|
11
|
+
},
|
|
12
|
+
pikkuFuncId: string
|
|
13
|
+
): FunctionMeta | undefined {
|
|
14
|
+
// Check local functions first
|
|
15
|
+
const local = state.functions.meta[pikkuFuncId]
|
|
16
|
+
if (local) return local
|
|
17
|
+
|
|
18
|
+
// Check addon functions (namespaced like 'swaggerPetstore:addPet')
|
|
19
|
+
const colonIndex = pikkuFuncId.indexOf(':')
|
|
20
|
+
if (colonIndex === -1) return undefined
|
|
21
|
+
|
|
22
|
+
const namespace = pikkuFuncId.substring(0, colonIndex)
|
|
23
|
+
const funcName = pikkuFuncId.substring(colonIndex + 1)
|
|
24
|
+
return state.addonFunctions[namespace]?.[funcName]
|
|
25
|
+
}
|