@pikku/inspector 0.12.3 → 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 +32 -0
- package/dist/add/add-middleware.js +6 -10
- package/dist/add/add-permission.js +10 -12
- 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/inspector.js +14 -7
- package/dist/types.d.ts +2 -0
- package/dist/utils/custom-types-generator.js +1 -0
- package/dist/utils/post-process.d.ts +9 -0
- package/dist/utils/post-process.js +46 -0
- package/dist/utils/schema-generator.js +26 -6
- package/dist/utils/serialize-inspector-state.js +1 -0
- 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/package.json +4 -3
- package/src/add/add-middleware.ts +6 -14
- package/src/add/add-permission.ts +10 -16
- package/src/add/add-workflow.ts +11 -1
- package/src/error-codes.ts +3 -0
- package/src/inspector.ts +21 -6
- package/src/types.ts +2 -0
- package/src/utils/custom-types-generator.ts +1 -0
- package/src/utils/post-process.ts +59 -0
- package/src/utils/schema-generator.ts +38 -10
- package/src/utils/serialize-inspector-state.ts +1 -0
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +1 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
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'
|
|
@@ -203,6 +204,7 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
203
204
|
openAPISpec: null,
|
|
204
205
|
diagnostics: [],
|
|
205
206
|
addonFunctions: {},
|
|
207
|
+
program: null,
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
210
|
|
|
@@ -211,8 +213,7 @@ export const inspect = async (
|
|
|
211
213
|
routeFiles: string[],
|
|
212
214
|
options: InspectorOptions = {}
|
|
213
215
|
): Promise<InspectorState> => {
|
|
214
|
-
const
|
|
215
|
-
const program = ts.createProgram(routeFiles, {
|
|
216
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
216
217
|
target: ts.ScriptTarget.ESNext,
|
|
217
218
|
module: ts.ModuleKind.Node16,
|
|
218
219
|
skipLibCheck: true,
|
|
@@ -221,9 +222,16 @@ export const inspect = async (
|
|
|
221
222
|
types: [],
|
|
222
223
|
allowJs: false,
|
|
223
224
|
checkJs: false,
|
|
224
|
-
}
|
|
225
|
+
}
|
|
226
|
+
const startProgram = performance.now()
|
|
227
|
+
const program = ts.createProgram(
|
|
228
|
+
routeFiles,
|
|
229
|
+
compilerOptions,
|
|
230
|
+
undefined, // host
|
|
231
|
+
options.oldProgram
|
|
232
|
+
)
|
|
225
233
|
logger.debug(
|
|
226
|
-
`Created program in ${(performance.now() - startProgram).toFixed(
|
|
234
|
+
`Created program in ${(performance.now() - startProgram).toFixed(0)}ms (${routeFiles.length} files${options.oldProgram ? ', incremental' : ''})`
|
|
227
235
|
)
|
|
228
236
|
|
|
229
237
|
const startChecker = performance.now()
|
|
@@ -255,7 +263,7 @@ export const inspect = async (
|
|
|
255
263
|
)
|
|
256
264
|
}
|
|
257
265
|
logger.debug(
|
|
258
|
-
`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(
|
|
266
|
+
`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(0)}ms`
|
|
259
267
|
)
|
|
260
268
|
|
|
261
269
|
// Load addon function metadata so wirings can reference addon functions
|
|
@@ -270,17 +278,21 @@ export const inspect = async (
|
|
|
270
278
|
)
|
|
271
279
|
}
|
|
272
280
|
logger.debug(
|
|
273
|
-
`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(
|
|
281
|
+
`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(0)}ms`
|
|
274
282
|
)
|
|
275
283
|
|
|
276
284
|
resolveLatestVersions(state, logger)
|
|
277
285
|
|
|
278
286
|
if (options.schemaConfig) {
|
|
287
|
+
const startSchemas = performance.now()
|
|
279
288
|
state.schemas = await generateAllSchemas(
|
|
280
289
|
logger,
|
|
281
290
|
options.schemaConfig,
|
|
282
291
|
state
|
|
283
292
|
)
|
|
293
|
+
logger.debug(
|
|
294
|
+
`generateAllSchemas took ${(performance.now() - startSchemas).toFixed(0)}ms`
|
|
295
|
+
)
|
|
284
296
|
computeContractHashes(
|
|
285
297
|
state.schemas,
|
|
286
298
|
state.functions.typesMap,
|
|
@@ -323,6 +335,7 @@ export const inspect = async (
|
|
|
323
335
|
computeMiddlewareGroupsMeta(state)
|
|
324
336
|
computePermissionsGroupsMeta(state)
|
|
325
337
|
computeDiagnostics(state)
|
|
338
|
+
validateSchemaWiringSeparation(logger, state)
|
|
326
339
|
|
|
327
340
|
if (options.openAPI) {
|
|
328
341
|
state.openAPISpec = await generateOpenAPISpec(
|
|
@@ -341,5 +354,7 @@ export const inspect = async (
|
|
|
341
354
|
validateVariableOverrides(logger, state)
|
|
342
355
|
}
|
|
343
356
|
|
|
357
|
+
state.program = program
|
|
358
|
+
|
|
344
359
|
return state
|
|
345
360
|
}
|
package/src/types.ts
CHANGED
|
@@ -225,6 +225,7 @@ export type InspectorOptions = Partial<{
|
|
|
225
225
|
tags: string[]
|
|
226
226
|
manifest: VersionManifest
|
|
227
227
|
modelConfig: InspectorModelConfig
|
|
228
|
+
oldProgram: ts.Program | undefined
|
|
228
229
|
}>
|
|
229
230
|
|
|
230
231
|
export interface InspectorLogger {
|
|
@@ -420,4 +421,5 @@ export interface InspectorState {
|
|
|
420
421
|
openAPISpec: Record<string, any> | null
|
|
421
422
|
diagnostics: InspectorDiagnostic[]
|
|
422
423
|
addonFunctions: Record<string, FunctionsMeta> // namespace -> addon's function metadata
|
|
424
|
+
program: ts.Program | null // Retained for incremental re-inspection
|
|
423
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)
|
|
@@ -492,6 +492,65 @@ export function validateAgentOverrides(
|
|
|
492
492
|
}
|
|
493
493
|
}
|
|
494
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
|
+
|
|
495
554
|
export function computeDiagnostics(state: InspectorState): void {
|
|
496
555
|
const diagnostics: InspectorDiagnostic[] = []
|
|
497
556
|
|
|
@@ -63,24 +63,37 @@ function primitiveTypeToSchema(typeStr: string): JSONValue | null {
|
|
|
63
63
|
return null
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Cached state for schema program reuse across inspect() calls
|
|
67
|
+
let cachedSchemaProgram: ts.Program | undefined
|
|
68
|
+
let cachedParsedConfig: ts.ParsedCommandLine | undefined
|
|
69
|
+
let cachedTsconfigPath: string | undefined
|
|
70
|
+
let cachedCustomTypesContent: string | undefined
|
|
71
|
+
let cachedTSSchemas: Record<string, JSONValue> | undefined
|
|
72
|
+
|
|
66
73
|
function createProgramWithVirtualFile(
|
|
67
74
|
tsconfig: string,
|
|
68
75
|
virtualFilePath: string,
|
|
69
76
|
virtualFileContent: string
|
|
70
77
|
): ts.Program {
|
|
71
78
|
const configPath = resolve(tsconfig)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
configFile.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
|
|
80
|
+
// Cache the parsed tsconfig — it doesn't change between runs
|
|
81
|
+
if (!cachedParsedConfig || cachedTsconfigPath !== configPath) {
|
|
82
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile)
|
|
83
|
+
const basePath = dirname(configPath)
|
|
84
|
+
cachedParsedConfig = ts.parseJsonConfigFileContent(
|
|
85
|
+
configFile.config,
|
|
86
|
+
ts.sys,
|
|
87
|
+
basePath
|
|
88
|
+
)
|
|
89
|
+
cachedTsconfigPath = configPath
|
|
90
|
+
cachedSchemaProgram = undefined
|
|
91
|
+
}
|
|
79
92
|
|
|
80
93
|
const resolvedVirtualPath = resolve(virtualFilePath)
|
|
81
|
-
const fileNames = [...
|
|
94
|
+
const fileNames = [...cachedParsedConfig.fileNames, resolvedVirtualPath]
|
|
82
95
|
|
|
83
|
-
const defaultHost = ts.createCompilerHost(
|
|
96
|
+
const defaultHost = ts.createCompilerHost(cachedParsedConfig.options)
|
|
84
97
|
const customHost: ts.CompilerHost = {
|
|
85
98
|
...defaultHost,
|
|
86
99
|
getSourceFile(
|
|
@@ -113,7 +126,14 @@ function createProgramWithVirtualFile(
|
|
|
113
126
|
},
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
|
|
129
|
+
const program = ts.createProgram(
|
|
130
|
+
fileNames,
|
|
131
|
+
cachedParsedConfig.options,
|
|
132
|
+
customHost,
|
|
133
|
+
cachedSchemaProgram // reuse previous program for incremental compilation
|
|
134
|
+
)
|
|
135
|
+
cachedSchemaProgram = program
|
|
136
|
+
return program
|
|
117
137
|
}
|
|
118
138
|
|
|
119
139
|
function generateTSSchemas(
|
|
@@ -313,6 +333,11 @@ export async function generateAllSchemas(
|
|
|
313
333
|
requiredTypes
|
|
314
334
|
)
|
|
315
335
|
|
|
336
|
+
if (cachedTSSchemas && cachedCustomTypesContent === customTypesContent) {
|
|
337
|
+
logger.debug('Reusing cached TS schemas (types unchanged)')
|
|
338
|
+
return { ...cachedTSSchemas, ...zodSchemas }
|
|
339
|
+
}
|
|
340
|
+
|
|
316
341
|
const tsSchemas = generateTSSchemas(
|
|
317
342
|
logger,
|
|
318
343
|
config.tsconfig,
|
|
@@ -325,5 +350,8 @@ export async function generateAllSchemas(
|
|
|
325
350
|
state.schemaLookup
|
|
326
351
|
)
|
|
327
352
|
|
|
353
|
+
cachedCustomTypesContent = customTypesContent
|
|
354
|
+
cachedTSSchemas = tsSchemas
|
|
355
|
+
|
|
328
356
|
return { ...tsSchemas, ...zodSchemas }
|
|
329
357
|
}
|
|
@@ -192,6 +192,8 @@ export interface SerializedWorkflowGraph {
|
|
|
192
192
|
description?: string
|
|
193
193
|
/** Tags for organization */
|
|
194
194
|
tags?: string[]
|
|
195
|
+
/** If true, workflow always executes inline without queues */
|
|
196
|
+
inline?: boolean
|
|
195
197
|
/** Workflow context/state variables (from Zod schema) */
|
|
196
198
|
context?: WorkflowContext
|
|
197
199
|
/** Serialized nodes */
|