@pikku/inspector 0.12.60 → 0.12.61
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 +301 -0
- package/OPTIMIZATION-PLAN.md +19 -19
- package/dist/add/add-auth.js +1 -1
- package/dist/add/add-channel.d.ts +1 -1
- package/dist/add/add-cli.js +5 -0
- package/dist/add/add-functions.js +10 -1
- package/dist/add/add-http-route.js +18 -18
- package/dist/add/add-keyed-wiring.d.ts +2 -0
- package/dist/add/add-keyed-wiring.js +3 -1
- package/dist/add/add-personas.js +4 -1
- package/dist/add/add-secret.js +1 -0
- package/dist/add/add-variable.js +1 -0
- package/dist/add/add-wire-remote-addon.js +2 -1
- package/dist/add/add-workflow-graph.js +63 -6
- package/dist/add/add-workflow.d.ts +1 -1
- package/dist/error-codes.d.ts +3 -1
- package/dist/error-codes.js +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/inspector.js +12 -2
- package/dist/types.d.ts +25 -21
- package/dist/utils/annotate-http-route-auth.js +2 -1
- package/dist/utils/check-secret-sinks.js +2 -1
- package/dist/utils/compute-required-schemas.d.ts +1 -1
- package/dist/utils/contract-hashes.d.ts +2 -2
- package/dist/utils/contract-hashes.js +1 -1
- package/dist/utils/extract-function-name.js +1 -1
- package/dist/utils/extract-services.d.ts +1 -1
- package/dist/utils/extract-surface-usage.d.ts +29 -0
- package/dist/utils/extract-surface-usage.js +97 -0
- package/dist/utils/filter-inspector-state.js +53 -16
- package/dist/utils/filter-utils.d.ts +1 -1
- package/dist/utils/get-property-value.js +42 -3
- package/dist/utils/load-addon-functions-meta.js +31 -0
- package/dist/utils/middleware.d.ts +1 -1
- package/dist/utils/permissions.d.ts +1 -1
- package/dist/utils/post-process.d.ts +27 -7
- package/dist/utils/post-process.js +79 -14
- package/dist/utils/resolve-deploy-target.d.ts +1 -1
- package/dist/utils/resolve-function-meta.d.ts +1 -1
- package/dist/utils/resolve-function-types.d.ts +1 -1
- package/dist/utils/resolve-versions.js +1 -1
- package/dist/utils/schema-generator.d.ts +1 -1
- package/dist/utils/serialize-inspector-state.d.ts +2 -1
- package/dist/utils/serialize-inspector-state.js +2 -0
- package/dist/utils/serialize-openapi-json.d.ts +2 -2
- package/dist/utils/validate-auth-sessionless.js +2 -1
- package/dist/utils/workflow/derive-workflow-plan.d.ts +1 -1
- package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +1 -1
- package/dist/utils/workflow/graph/finalize-workflows.js +1 -1
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +6 -1
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +26 -2
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +11 -2
- package/package.json +10 -10
- package/run-tests.sh +0 -0
- package/src/add/add-agent.ts +2 -4
- package/src/add/add-auth.ts +2 -2
- package/src/add/add-channel.ts +1 -4
- package/src/add/add-cli.ts +7 -1
- package/src/add/add-credential.ts +3 -6
- package/src/add/add-file-with-factory.ts +2 -6
- package/src/add/add-functions.ts +24 -31
- package/src/add/add-gateway.ts +1 -1
- package/src/add/add-http-route-ref.test.ts +149 -0
- package/src/add/add-http-route.ts +26 -29
- package/src/add/add-keyed-wiring.ts +8 -7
- package/src/add/add-permission.ts +1 -1
- package/src/add/add-personas.test.ts +26 -2
- package/src/add/add-personas.ts +4 -3
- package/src/add/add-scenario-step.test.ts +37 -2
- package/src/add/add-scope.test.ts +15 -15
- package/src/add/add-scope.ts +5 -9
- package/src/add/add-secret.ts +1 -0
- package/src/add/add-system-role.test.ts +4 -4
- package/src/add/add-system-role.ts +2 -4
- package/src/add/add-variable.ts +1 -0
- package/src/add/add-wire-remote-addon.ts +4 -1
- package/src/add/add-workflow-graph-foreach.test.ts +174 -0
- package/src/add/add-workflow-graph-input.test.ts +1 -1
- package/src/add/add-workflow-graph.ts +78 -6
- package/src/add/add-workflow-sleep-expression.test.ts +3 -4
- package/src/add/add-workflow.ts +1 -4
- package/src/add/addon-contracts.test.ts +1 -1
- package/src/add/config-value-metadata.test.ts +94 -0
- package/src/add/secret-sink-check.test.ts +4 -1
- package/src/error-codes.ts +3 -1
- package/src/index.ts +3 -4
- package/src/inspector.ts +19 -2
- package/src/types.ts +25 -28
- package/src/utils/annotate-http-route-auth.test.ts +5 -7
- package/src/utils/annotate-http-route-auth.ts +2 -1
- package/src/utils/check-secret-sinks.ts +4 -1
- package/src/utils/compute-required-schemas.ts +1 -1
- package/src/utils/contract-hashes.test.ts +1 -1
- package/src/utils/contract-hashes.ts +3 -3
- package/src/utils/extract-function-name.ts +1 -1
- package/src/utils/extract-services.ts +1 -2
- package/src/utils/extract-surface-usage.test.ts +147 -0
- package/src/utils/extract-surface-usage.ts +121 -0
- package/src/utils/filter-inspector-state.test.ts +138 -12
- package/src/utils/filter-inspector-state.ts +82 -21
- package/src/utils/filter-utils.ts +1 -1
- package/src/utils/get-property-value.ts +43 -5
- package/src/utils/load-addon-functions-meta.ts +40 -0
- package/src/utils/middleware.ts +1 -1
- package/src/utils/permissions.ts +1 -1
- package/src/utils/post-process.test.ts +59 -2
- package/src/utils/post-process.ts +88 -25
- package/src/utils/resolve-deploy-target.ts +1 -1
- package/src/utils/resolve-function-meta.ts +1 -4
- package/src/utils/resolve-function-types.ts +1 -1
- package/src/utils/resolve-versions.ts +2 -5
- package/src/utils/schema-generator.ts +3 -3
- package/src/utils/serialize-inspector-state.ts +4 -1
- package/src/utils/serialize-mcp-json.ts +1 -1
- package/src/utils/serialize-openapi-json.ts +2 -2
- package/src/utils/validate-auth-sessionless.ts +2 -1
- package/src/utils/validate-tags-resolve-to-middleware.test.ts +4 -1
- package/src/utils/workflow/derive-workflow-plan.test.ts +1 -1
- package/src/utils/workflow/derive-workflow-plan.ts +1 -1
- package/src/utils/workflow/dsl/extract-approval-policy.test.ts +1 -1
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +2 -2
- package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +1 -1
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +1 -4
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +1 -1
- package/src/utils/workflow/graph/finalize-workflows.ts +2 -2
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +34 -3
- package/src/utils/workflow/graph/workflow-graph.types.ts +12 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-ai-agent.d.ts +0 -4
- package/dist/add/add-ai-agent.js +0 -442
- package/dist/add/add-ai-scorer.d.ts +0 -2
- package/dist/add/add-ai-scorer.js +0 -61
- package/dist/utils/secret-brokers.d.ts +0 -2
- package/dist/utils/secret-brokers.js +0 -9
|
@@ -10,7 +10,8 @@ import type {
|
|
|
10
10
|
function extractAstValue(
|
|
11
11
|
expr: ts.Expression,
|
|
12
12
|
refParamName: string,
|
|
13
|
-
templateParamName: string | undefined
|
|
13
|
+
templateParamName: string | undefined,
|
|
14
|
+
itemParamName?: string
|
|
14
15
|
): unknown {
|
|
15
16
|
// `'GET' as const` / parenthesised values wrap the real initializer — see
|
|
16
17
|
// through them or the whole property is silently dropped from the meta.
|
|
@@ -43,6 +44,12 @@ function extractAstValue(
|
|
|
43
44
|
args[1] && ts.isStringLiteral(args[1]) ? args[1].text : undefined
|
|
44
45
|
return { $ref: nodeId, path } as DataRef
|
|
45
46
|
}
|
|
47
|
+
if (itemParamName && callee.text === itemParamName) {
|
|
48
|
+
const pathArg = expr.arguments[0]
|
|
49
|
+
const path =
|
|
50
|
+
pathArg && ts.isStringLiteral(pathArg) ? pathArg.text : undefined
|
|
51
|
+
return { $ref: '$item', path } as DataRef
|
|
52
|
+
}
|
|
46
53
|
if (templateParamName && callee.text === templateParamName) {
|
|
47
54
|
const templateStr =
|
|
48
55
|
expr.arguments[0] && ts.isStringLiteral(expr.arguments[0])
|
|
@@ -55,7 +62,8 @@ function extractAstValue(
|
|
|
55
62
|
const resolved = extractAstValue(
|
|
56
63
|
el,
|
|
57
64
|
refParamName,
|
|
58
|
-
templateParamName
|
|
65
|
+
templateParamName,
|
|
66
|
+
itemParamName
|
|
59
67
|
)
|
|
60
68
|
if (
|
|
61
69
|
typeof resolved === 'object' &&
|
|
@@ -84,7 +92,7 @@ function extractAstValue(
|
|
|
84
92
|
}
|
|
85
93
|
if (ts.isArrayLiteralExpression(expr)) {
|
|
86
94
|
return expr.elements.map((el) =>
|
|
87
|
-
extractAstValue(el, refParamName, templateParamName)
|
|
95
|
+
extractAstValue(el, refParamName, templateParamName, itemParamName)
|
|
88
96
|
)
|
|
89
97
|
}
|
|
90
98
|
if (ts.isObjectLiteralExpression(expr)) {
|
|
@@ -100,7 +108,8 @@ function extractAstValue(
|
|
|
100
108
|
obj[key] = extractAstValue(
|
|
101
109
|
prop.initializer,
|
|
102
110
|
refParamName,
|
|
103
|
-
templateParamName
|
|
111
|
+
templateParamName,
|
|
112
|
+
itemParamName
|
|
104
113
|
)
|
|
105
114
|
}
|
|
106
115
|
return obj
|
|
@@ -160,6 +169,13 @@ function extractInputMapping(
|
|
|
160
169
|
? node.parameters[1].name.text
|
|
161
170
|
: 'template'
|
|
162
171
|
|
|
172
|
+
// `$item` is appended after `template` so neither existing arrow shape —
|
|
173
|
+
// `(ref) => ...` nor `(ref, template) => ...` — shifts a parameter.
|
|
174
|
+
const itemParamName =
|
|
175
|
+
node.parameters.length > 2 && ts.isIdentifier(node.parameters[2].name)
|
|
176
|
+
? node.parameters[2].name.text
|
|
177
|
+
: undefined
|
|
178
|
+
|
|
163
179
|
const input: Record<string, unknown | DataRef> = {}
|
|
164
180
|
|
|
165
181
|
for (const prop of bodyObj.properties) {
|
|
@@ -176,7 +192,8 @@ function extractInputMapping(
|
|
|
176
192
|
const value = extractAstValue(
|
|
177
193
|
prop.initializer,
|
|
178
194
|
refParamName,
|
|
179
|
-
templateParamName
|
|
195
|
+
templateParamName,
|
|
196
|
+
itemParamName
|
|
180
197
|
)
|
|
181
198
|
if (value !== undefined) {
|
|
182
199
|
input[key] = value
|
|
@@ -186,6 +203,49 @@ function extractInputMapping(
|
|
|
186
203
|
return input
|
|
187
204
|
}
|
|
188
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Extract a `forEach` source, authored either as a bare node id (`'listRows'`)
|
|
208
|
+
* or as a callback returning a ref (`(ref) => ref('listRows', 'rows')`).
|
|
209
|
+
*/
|
|
210
|
+
function extractForEachConfig(node: ts.Node): DataRef | undefined {
|
|
211
|
+
while (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) {
|
|
212
|
+
node = node.expression
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (ts.isStringLiteral(node)) {
|
|
216
|
+
return { $ref: node.text }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (ts.isArrowFunction(node)) {
|
|
220
|
+
const refParamName =
|
|
221
|
+
node.parameters.length > 0 && ts.isIdentifier(node.parameters[0].name)
|
|
222
|
+
? node.parameters[0].name.text
|
|
223
|
+
: 'ref'
|
|
224
|
+
|
|
225
|
+
let body: ts.Node = node.body
|
|
226
|
+
if (ts.isBlock(body)) {
|
|
227
|
+
for (const stmt of body.statements) {
|
|
228
|
+
if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
229
|
+
body = stmt.expression
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!ts.isExpression(body)) return undefined
|
|
234
|
+
|
|
235
|
+
const value = extractAstValue(body, refParamName, undefined)
|
|
236
|
+
if (
|
|
237
|
+
typeof value === 'object' &&
|
|
238
|
+
value !== null &&
|
|
239
|
+
'$ref' in value &&
|
|
240
|
+
typeof (value as DataRef).$ref === 'string'
|
|
241
|
+
) {
|
|
242
|
+
return value as DataRef
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return undefined
|
|
247
|
+
}
|
|
248
|
+
|
|
189
249
|
/**
|
|
190
250
|
* Extract next config (string, array, or record)
|
|
191
251
|
*/
|
|
@@ -380,6 +440,8 @@ function extractGraphFromNewFormat(
|
|
|
380
440
|
nodeId,
|
|
381
441
|
rpcName,
|
|
382
442
|
input: {},
|
|
443
|
+
forEach: undefined,
|
|
444
|
+
mode: undefined,
|
|
383
445
|
next: undefined,
|
|
384
446
|
onError: undefined,
|
|
385
447
|
retries: undefined,
|
|
@@ -410,6 +472,8 @@ function extractGraphFromNewFormat(
|
|
|
410
472
|
nodes[nodeId].next = nodeConfig.next
|
|
411
473
|
nodes[nodeId].onError = nodeConfig.onError
|
|
412
474
|
nodes[nodeId].input = nodeConfig.input
|
|
475
|
+
nodes[nodeId].forEach = nodeConfig.forEach
|
|
476
|
+
nodes[nodeId].mode = nodeConfig.mode
|
|
413
477
|
nodes[nodeId].retries = nodeConfig.retries
|
|
414
478
|
nodes[nodeId].retryDelay = nodeConfig.retryDelay
|
|
415
479
|
nodes[nodeId].notes = nodeConfig.notes
|
|
@@ -431,6 +495,8 @@ function extractNodeConfigFromObject(
|
|
|
431
495
|
next: any
|
|
432
496
|
onError: any
|
|
433
497
|
input: Record<string, any>
|
|
498
|
+
forEach: DataRef | undefined
|
|
499
|
+
mode: string | undefined
|
|
434
500
|
retries: number | undefined
|
|
435
501
|
retryDelay: string | number | undefined
|
|
436
502
|
notes: string | undefined
|
|
@@ -438,6 +504,8 @@ function extractNodeConfigFromObject(
|
|
|
438
504
|
let next: any = undefined
|
|
439
505
|
let onError: any = undefined
|
|
440
506
|
let input: Record<string, any> = {}
|
|
507
|
+
let forEach: DataRef | undefined = undefined
|
|
508
|
+
let mode: string | undefined = undefined
|
|
441
509
|
let retries: number | undefined = undefined
|
|
442
510
|
let retryDelay: string | number | undefined = undefined
|
|
443
511
|
let notes: string | undefined = undefined
|
|
@@ -453,6 +521,10 @@ function extractNodeConfigFromObject(
|
|
|
453
521
|
onError = extractNextConfig(prop.initializer, checker)
|
|
454
522
|
} else if (propName === 'input') {
|
|
455
523
|
input = extractInputMapping(prop.initializer, checker)
|
|
524
|
+
} else if (propName === 'forEach') {
|
|
525
|
+
forEach = extractForEachConfig(prop.initializer)
|
|
526
|
+
} else if (propName === 'mode') {
|
|
527
|
+
mode = extractStringLiteral(prop.initializer, checker)
|
|
456
528
|
} else if (propName === 'retries') {
|
|
457
529
|
if (ts.isNumericLiteral(prop.initializer)) {
|
|
458
530
|
retries = Number(prop.initializer.text)
|
|
@@ -468,7 +540,7 @@ function extractNodeConfigFromObject(
|
|
|
468
540
|
}
|
|
469
541
|
}
|
|
470
542
|
|
|
471
|
-
return { next, onError, input, retries, retryDelay, notes }
|
|
543
|
+
return { next, onError, input, forEach, mode, retries, retryDelay, notes }
|
|
472
544
|
}
|
|
473
545
|
|
|
474
546
|
/**
|
|
@@ -77,10 +77,9 @@ describe('sleep with a duration that is not a literal', () => {
|
|
|
77
77
|
|
|
78
78
|
test('a literal duration still round-trips as a value', async () => {
|
|
79
79
|
const { graph, diags } = await run(
|
|
80
|
-
[
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
].join('\n')
|
|
80
|
+
[" await workflow.sleep('Wait', '5s')", ' return { sent: true }'].join(
|
|
81
|
+
'\n'
|
|
82
|
+
)
|
|
84
83
|
)
|
|
85
84
|
|
|
86
85
|
assert.deepEqual(diags, [])
|
package/src/add/add-workflow.ts
CHANGED
|
@@ -3,10 +3,7 @@ import type { AddWiring, InspectorState } from '../types.js'
|
|
|
3
3
|
import { extractFunctionName } from '../utils/extract-function-name.js'
|
|
4
4
|
import { extractFunctionNode } from '../utils/extract-function-node.js'
|
|
5
5
|
import { ErrorCode } from '../error-codes.js'
|
|
6
|
-
import type {
|
|
7
|
-
WorkflowStepMeta,
|
|
8
|
-
WorkflowContext,
|
|
9
|
-
} from '@pikku/core/ecosystem/workflow'
|
|
6
|
+
import type { WorkflowStepMeta, WorkflowContext } from '@pikku/core/workflow'
|
|
10
7
|
import {
|
|
11
8
|
extractStringLiteral,
|
|
12
9
|
isStringLike,
|
|
@@ -178,7 +178,7 @@ describe('addon contract metadata', () => {
|
|
|
178
178
|
await writeFile(
|
|
179
179
|
appFile,
|
|
180
180
|
[
|
|
181
|
-
"import { wireAddon } from '@pikku/core/
|
|
181
|
+
"import { wireAddon } from '@pikku/core/addon'",
|
|
182
182
|
"import { wireHTTPRoutes } from '@pikku/core/http'",
|
|
183
183
|
"import { wireChannel } from '@pikku/core/channel'",
|
|
184
184
|
"import { wireCLI } from '@pikku/core/cli'",
|
|
@@ -167,3 +167,97 @@ describe('config value metadata (docsUrl)', () => {
|
|
|
167
167
|
}
|
|
168
168
|
})
|
|
169
169
|
})
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* `optional` and `required` are the deploy gate's whole input. A flag the
|
|
173
|
+
* inspector drops leaves the gate seeing a bare declaration, which is how an
|
|
174
|
+
* optional secret suspends a deploy over a value production is meant NOT to
|
|
175
|
+
* have.
|
|
176
|
+
*/
|
|
177
|
+
describe('config value gating flags', () => {
|
|
178
|
+
test('carries optional from defineSecret into the definition', async () => {
|
|
179
|
+
const rootDir = await mkdtemp(`${fixtureRoot}-secret-optional-`)
|
|
180
|
+
const file = join(rootDir, 'secret.ts')
|
|
181
|
+
|
|
182
|
+
await writeFile(
|
|
183
|
+
file,
|
|
184
|
+
[
|
|
185
|
+
"import { defineSecret } from '@pikku/core'",
|
|
186
|
+
"import { z } from 'zod'",
|
|
187
|
+
'export const ActorSchema = z.string()',
|
|
188
|
+
'export const StripeSchema = z.string()',
|
|
189
|
+
'defineSecret({',
|
|
190
|
+
" name: 'scenarioActor',",
|
|
191
|
+
" displayName: 'Scenario Actor Secret',",
|
|
192
|
+
" secretId: 'SCENARIO_ACTOR_SECRET',",
|
|
193
|
+
' optional: true,',
|
|
194
|
+
' schema: ActorSchema,',
|
|
195
|
+
'})',
|
|
196
|
+
'defineSecret({',
|
|
197
|
+
" name: 'stripe',",
|
|
198
|
+
" displayName: 'Stripe',",
|
|
199
|
+
" secretId: 'STRIPE_KEY',",
|
|
200
|
+
' schema: StripeSchema,',
|
|
201
|
+
'})',
|
|
202
|
+
].join('\n')
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
const criticals: Array<{ code: ErrorCode; message: string }> = []
|
|
206
|
+
try {
|
|
207
|
+
const state = await inspect(makeLogger(criticals), [file], { rootDir })
|
|
208
|
+
const optional = state.secrets.definitions.find(
|
|
209
|
+
(d) => d.secretId === 'SCENARIO_ACTOR_SECRET'
|
|
210
|
+
)
|
|
211
|
+
const required = state.secrets.definitions.find(
|
|
212
|
+
(d) => d.secretId === 'STRIPE_KEY'
|
|
213
|
+
)
|
|
214
|
+
assert.equal(optional!.optional, true)
|
|
215
|
+
assert.equal(required!.optional, undefined)
|
|
216
|
+
} finally {
|
|
217
|
+
await rm(rootDir, { recursive: true, force: true })
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('carries required from defineVariable into the definition', async () => {
|
|
222
|
+
const rootDir = await mkdtemp(`${fixtureRoot}-variable-required-`)
|
|
223
|
+
const file = join(rootDir, 'variable.ts')
|
|
224
|
+
|
|
225
|
+
await writeFile(
|
|
226
|
+
file,
|
|
227
|
+
[
|
|
228
|
+
"import { defineVariable } from '@pikku/core'",
|
|
229
|
+
"import { z } from 'zod'",
|
|
230
|
+
'export const ConsoleUrlSchema = z.string()',
|
|
231
|
+
'export const CorsSchema = z.string()',
|
|
232
|
+
'defineVariable({',
|
|
233
|
+
" name: 'consoleUrl',",
|
|
234
|
+
" displayName: 'Console URL',",
|
|
235
|
+
" variableId: 'CONSOLE_URL',",
|
|
236
|
+
' required: true,',
|
|
237
|
+
' schema: ConsoleUrlSchema,',
|
|
238
|
+
'})',
|
|
239
|
+
'defineVariable({',
|
|
240
|
+
" name: 'corsOrigins',",
|
|
241
|
+
" displayName: 'Allowed Browser Origins',",
|
|
242
|
+
" variableId: 'CORS_ORIGINS',",
|
|
243
|
+
' schema: CorsSchema,',
|
|
244
|
+
'})',
|
|
245
|
+
].join('\n')
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
const criticals: Array<{ code: ErrorCode; message: string }> = []
|
|
249
|
+
try {
|
|
250
|
+
const state = await inspect(makeLogger(criticals), [file], { rootDir })
|
|
251
|
+
const required = state.variables.definitions.find(
|
|
252
|
+
(d) => d.variableId === 'CONSOLE_URL'
|
|
253
|
+
)
|
|
254
|
+
const optional = state.variables.definitions.find(
|
|
255
|
+
(d) => d.variableId === 'CORS_ORIGINS'
|
|
256
|
+
)
|
|
257
|
+
assert.equal(required!.required, true)
|
|
258
|
+
assert.equal(optional!.required, undefined)
|
|
259
|
+
} finally {
|
|
260
|
+
await rm(rootDir, { recursive: true, force: true })
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
})
|
|
@@ -75,7 +75,10 @@ async function runInspect(source: string) {
|
|
|
75
75
|
await writeFile(file, PRELUDE + source)
|
|
76
76
|
const { logger, diagnostics } = makeLogger()
|
|
77
77
|
try {
|
|
78
|
-
await inspect(logger, [file], {
|
|
78
|
+
await inspect(logger, [file], {
|
|
79
|
+
rootDir: tmpDir,
|
|
80
|
+
classificationCheck: true,
|
|
81
|
+
})
|
|
79
82
|
} finally {
|
|
80
83
|
await rm(tmpDir, { recursive: true, force: true })
|
|
81
84
|
}
|
package/src/error-codes.ts
CHANGED
|
@@ -22,7 +22,7 @@ export enum ErrorCode {
|
|
|
22
22
|
CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = 'PKU672',
|
|
23
23
|
SCENARIO_HAS_SERVICES = 'PKU673',
|
|
24
24
|
EXPECT_EVENTUALLY_SCENARIO_ONLY = 'PKU675',
|
|
25
|
-
|
|
25
|
+
SCENARIO_STEP_NEEDS_ACTOR = 'PKU677',
|
|
26
26
|
SCENARIO_STEP_TARGET_NOT_STATIC = 'PKU678',
|
|
27
27
|
SCENARIO_NOT_EXTRACTABLE = 'PKU679',
|
|
28
28
|
SCENARIO_HAS_NO_ASSERTION = 'PKU680',
|
|
@@ -34,6 +34,7 @@ export enum ErrorCode {
|
|
|
34
34
|
// Remote addon (wireRemoteAddon) validation
|
|
35
35
|
REMOTE_ADDON_NOT_DEV_DEPENDENCY = 'PKU338',
|
|
36
36
|
REMOTE_ADDON_AUTH_UNRESOLVED = 'PKU339',
|
|
37
|
+
ADDON_NOT_INSTALLED = 'PKU340',
|
|
37
38
|
|
|
38
39
|
// Database schema codegen warnings
|
|
39
40
|
DB_COLUMN_NAME_TYPE_CONTRADICTION = 'PKU480',
|
|
@@ -106,6 +107,7 @@ export enum ErrorCode {
|
|
|
106
107
|
DUPLICATE_CORE_VERSION = 'PKU717',
|
|
107
108
|
CORE_VERSION_SKEW = 'PKU718',
|
|
108
109
|
SPLIT_TYPE_IDENTITY = 'PKU719',
|
|
110
|
+
SINGLETON_SERVICES_TYPE_UNRESOLVED = 'PKU724',
|
|
109
111
|
|
|
110
112
|
// Data classification errors
|
|
111
113
|
PII_IN_OUTPUT = 'PKU910',
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,8 @@ export {
|
|
|
21
21
|
generateCustomTypes,
|
|
22
22
|
sanitizeTypeName,
|
|
23
23
|
} from './utils/custom-types-generator.js'
|
|
24
|
+
export { unresolvedSchemaReferences } from './utils/post-process.js'
|
|
25
|
+
export type { SurfaceUsageCounts } from './utils/extract-surface-usage.js'
|
|
24
26
|
export {
|
|
25
27
|
createEmptyManifest,
|
|
26
28
|
serializeManifest,
|
|
@@ -32,10 +34,7 @@ export type {
|
|
|
32
34
|
VersionManifest,
|
|
33
35
|
VersionManifestEntry,
|
|
34
36
|
} from './utils/contract-hashes.js'
|
|
35
|
-
export type {
|
|
36
|
-
InspectorFeature,
|
|
37
|
-
InspectorFeatureEntry,
|
|
38
|
-
} from './types.js'
|
|
37
|
+
export type { InspectorFeature, InspectorFeatureEntry } from './types.js'
|
|
39
38
|
export { serializeMCPJson } from './utils/serialize-mcp-json.js'
|
|
40
39
|
export type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js'
|
|
41
40
|
export {
|
package/src/inspector.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
2
|
import { performance } from 'perf_hooks'
|
|
3
|
-
import { dirname, resolve } from 'path'
|
|
3
|
+
import { dirname, relative, resolve } from 'path'
|
|
4
4
|
import { visitSetup, visitFunctions, visitRoutes } from './visit.js'
|
|
5
5
|
import { TypesMap } from './types-map.js'
|
|
6
6
|
import type {
|
|
@@ -42,7 +42,7 @@ import { validateExposedFunctionsGated } from './utils/validate-exposed-function
|
|
|
42
42
|
import { validateTagsResolveToMiddleware } from './utils/validate-tags-resolve-to-middleware.js'
|
|
43
43
|
import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js'
|
|
44
44
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js'
|
|
45
|
-
import { pikkuState } from '@pikku/core/
|
|
45
|
+
import { pikkuState } from '@pikku/core/state'
|
|
46
46
|
import { resolveLatestVersions } from './utils/resolve-versions.js'
|
|
47
47
|
import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js'
|
|
48
48
|
import {
|
|
@@ -51,6 +51,10 @@ import {
|
|
|
51
51
|
} from './utils/workflow/graph/finalize-workflow-wires.js'
|
|
52
52
|
import { generateAllSchemas } from './utils/schema-generator.js'
|
|
53
53
|
import { extractSecretUsage } from './utils/extract-secret-usage.js'
|
|
54
|
+
import {
|
|
55
|
+
accumulateSurfaceUsage,
|
|
56
|
+
surfaceUsageArea,
|
|
57
|
+
} from './utils/extract-surface-usage.js'
|
|
54
58
|
import {
|
|
55
59
|
loadAddonFunctionsMeta,
|
|
56
60
|
loadAddonSchemas,
|
|
@@ -186,6 +190,7 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
186
190
|
files: new Set(),
|
|
187
191
|
usage: new Map(),
|
|
188
192
|
},
|
|
193
|
+
surfaceUsage: {},
|
|
189
194
|
credentials: {
|
|
190
195
|
definitions: [],
|
|
191
196
|
files: new Set(),
|
|
@@ -378,11 +383,23 @@ export const inspect = async (
|
|
|
378
383
|
|
|
379
384
|
const state = getInitialInspectorState(rootDir)
|
|
380
385
|
|
|
386
|
+
const surfaceAreaCache = new Map<string, string | null>()
|
|
381
387
|
for (const sourceFile of sourceFiles) {
|
|
382
388
|
const usage = extractSecretUsage(sourceFile)
|
|
383
389
|
if (usage.keys.length > 0 || usage.dynamic.length > 0) {
|
|
384
390
|
state.secrets.usage.set(sourceFile.fileName, usage)
|
|
385
391
|
}
|
|
392
|
+
// Counted in the sweep that already has this file open. The public surface
|
|
393
|
+
// is documentation, and documentation may not cost a project a second pass
|
|
394
|
+
// over its source.
|
|
395
|
+
accumulateSurfaceUsage(
|
|
396
|
+
sourceFile,
|
|
397
|
+
{
|
|
398
|
+
area: surfaceUsageArea(sourceFile.fileName, rootDir, surfaceAreaCache),
|
|
399
|
+
file: relative(rootDir, sourceFile.fileName),
|
|
400
|
+
},
|
|
401
|
+
state.surfaceUsage
|
|
402
|
+
)
|
|
386
403
|
}
|
|
387
404
|
|
|
388
405
|
// First sweep: add all functions
|
package/src/types.ts
CHANGED
|
@@ -1,41 +1,36 @@
|
|
|
1
1
|
import type * as ts from 'typescript'
|
|
2
2
|
import type { SecretUsage } from './utils/extract-secret-usage.js'
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from '@pikku/core/
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
11
|
-
TriggerMeta,
|
|
12
|
-
TriggerSourceMeta,
|
|
13
|
-
} from '@pikku/core/ecosystem/trigger'
|
|
14
|
-
import type { QueueWorkersMeta } from '@pikku/core/ecosystem/queue'
|
|
15
|
-
import type { WorkflowsMeta } from '@pikku/core/ecosystem/workflow'
|
|
3
|
+
import type { SurfaceUsageCounts } from './utils/extract-surface-usage.js'
|
|
4
|
+
import type { ChannelMessageMeta, ChannelsMeta } from '@pikku/core/channel'
|
|
5
|
+
import type { GatewaysMeta } from '@pikku/core/gateway'
|
|
6
|
+
import type { HTTPWiringsMeta } from '@pikku/core/http'
|
|
7
|
+
import type { ScheduledTasksMeta } from '@pikku/core/scheduler'
|
|
8
|
+
import type { TriggerMeta, TriggerSourceMeta } from '@pikku/core/trigger'
|
|
9
|
+
import type { QueueWorkersMeta } from '@pikku/core/queue'
|
|
10
|
+
import type { WorkflowsMeta } from '@pikku/core/workflow'
|
|
16
11
|
import type {
|
|
17
12
|
MCPResourceMeta,
|
|
18
13
|
MCPToolMeta,
|
|
19
14
|
MCPPromptMeta,
|
|
20
|
-
} from '@pikku/core/
|
|
21
|
-
import type { AgentsMeta } from '@pikku/core/
|
|
22
|
-
import type { ScorerMeta } from '@pikku/core/
|
|
23
|
-
import type { CLIMeta } from '@pikku/core/
|
|
24
|
-
import type { CLICommandMeta } from '@pikku/core/
|
|
25
|
-
import type { NodesMeta } from '@pikku/core/
|
|
26
|
-
import type { SecretDefinitions } from '@pikku/core/
|
|
27
|
-
import type { CredentialDefinitions } from '@pikku/core/
|
|
28
|
-
import type { ScopeDefinitions } from '@pikku/core/
|
|
29
|
-
import type { SystemRoleDefinitions } from '@pikku/core/
|
|
30
|
-
import type { PersonaDefinitions } from '@pikku/core/
|
|
31
|
-
import type { VariableDefinitions } from '@pikku/core/
|
|
15
|
+
} from '@pikku/core/mcp'
|
|
16
|
+
import type { AgentsMeta } from '@pikku/core/agent'
|
|
17
|
+
import type { ScorerMeta } from '@pikku/core/agent-scorer'
|
|
18
|
+
import type { CLIMeta } from '@pikku/core/cli'
|
|
19
|
+
import type { CLICommandMeta } from '@pikku/core/cli'
|
|
20
|
+
import type { NodesMeta } from '@pikku/core/node'
|
|
21
|
+
import type { SecretDefinitions } from '@pikku/core/secret'
|
|
22
|
+
import type { CredentialDefinitions } from '@pikku/core/credential'
|
|
23
|
+
import type { ScopeDefinitions } from '@pikku/core/scope'
|
|
24
|
+
import type { SystemRoleDefinitions } from '@pikku/core/role'
|
|
25
|
+
import type { PersonaDefinitions } from '@pikku/core/persona'
|
|
26
|
+
import type { VariableDefinitions } from '@pikku/core/variable'
|
|
32
27
|
import type { TypesMap } from './types-map.js'
|
|
33
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
28
|
+
import type { FunctionsMeta } from '@pikku/core/services'
|
|
34
29
|
import type {
|
|
35
30
|
FunctionServicesMeta,
|
|
36
31
|
FunctionWiresMeta,
|
|
37
|
-
|
|
38
|
-
} from '@pikku/core/
|
|
32
|
+
} from '@pikku/core/function'
|
|
33
|
+
import type { JSONValue } from '@pikku/core/utils'
|
|
39
34
|
import type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js'
|
|
40
35
|
import type { ErrorCode, CodedDiagnostic } from './error-codes.js'
|
|
41
36
|
import type {
|
|
@@ -660,6 +655,8 @@ export interface InspectorState {
|
|
|
660
655
|
/** Secret reads found per source file, keyed by absolute path. */
|
|
661
656
|
usage: Map<string, SecretUsage>
|
|
662
657
|
}
|
|
658
|
+
/** How often each `#pikku/*` export is imported, and from where. */
|
|
659
|
+
surfaceUsage: SurfaceUsageCounts
|
|
663
660
|
credentials: {
|
|
664
661
|
definitions: CredentialDefinitions
|
|
665
662
|
files: Set<string>
|
|
@@ -108,18 +108,16 @@ describe('annotateHttpRouteAuth', () => {
|
|
|
108
108
|
|
|
109
109
|
test('a ref route resolves against its target', () => {
|
|
110
110
|
const state = {
|
|
111
|
-
functions: {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
target: { pikkuFuncId: 'target', sessionless: false },
|
|
115
|
-
},
|
|
111
|
+
functions: { meta: {} },
|
|
112
|
+
addonFunctions: {
|
|
113
|
+
console: { target: { pikkuFuncId: 'target', sessionless: false } },
|
|
116
114
|
},
|
|
117
115
|
http: {
|
|
118
116
|
meta: {
|
|
119
117
|
get: {
|
|
120
118
|
'/thing': {
|
|
121
|
-
pikkuFuncId: '
|
|
122
|
-
|
|
119
|
+
pikkuFuncId: 'console:target',
|
|
120
|
+
packageName: ADDON_PACKAGE,
|
|
123
121
|
route: '/thing',
|
|
124
122
|
method: 'get',
|
|
125
123
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { InspectorState } from '../types.js'
|
|
2
|
+
import { resolveFunctionMeta } from './resolve-function-meta.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Resolve, per HTTP route, whether reaching it requires a session.
|
|
@@ -25,7 +26,7 @@ export function annotateHttpRouteAuth(state: InspectorState): void {
|
|
|
25
26
|
|
|
26
27
|
for (const routes of Object.values(state.http?.meta ?? {})) {
|
|
27
28
|
for (const route of Object.values(routes)) {
|
|
28
|
-
const meta = state
|
|
29
|
+
const meta = resolveFunctionMeta(state, route.pikkuFuncId)
|
|
29
30
|
|
|
30
31
|
route.requiresSession =
|
|
31
32
|
// A pikkuFunc always requires a session, on every path.
|
|
@@ -53,7 +53,10 @@ export function findRevealedSecretSinks(
|
|
|
53
53
|
const violations: SecretSinkViolation[] = []
|
|
54
54
|
|
|
55
55
|
const visit = (node: ts.Node): void => {
|
|
56
|
-
if (
|
|
56
|
+
if (
|
|
57
|
+
ts.isCallExpression(node) &&
|
|
58
|
+
ts.isPropertyAccessExpression(node.expression)
|
|
59
|
+
) {
|
|
57
60
|
const sink = describeSink(checker, node.expression)
|
|
58
61
|
if (sink) {
|
|
59
62
|
for (const arg of node.arguments) {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
extractContractsFromMeta,
|
|
11
11
|
} from './contract-hashes.js'
|
|
12
12
|
import type { ContractEntry, VersionManifest } from './contract-hashes.js'
|
|
13
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
13
|
+
import type { FunctionsMeta } from '@pikku/core/services'
|
|
14
14
|
import { ErrorCode } from '../error-codes.js'
|
|
15
15
|
|
|
16
16
|
function makeContracts(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
2
|
-
import type { JSONValue } from '@pikku/core/
|
|
3
|
-
import { parseVersionedId } from '@pikku/core/
|
|
1
|
+
import type { FunctionsMeta } from '@pikku/core/services'
|
|
2
|
+
import type { JSONValue } from '@pikku/core/utils'
|
|
3
|
+
import { parseVersionedId } from '@pikku/core/utils'
|
|
4
4
|
import type { TypesMap } from '../types-map.js'
|
|
5
5
|
import { ErrorCode } from '../error-codes.js'
|
|
6
6
|
import { canonicalJSON, hashString } from './hash.js'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
2
|
import { createHash } from 'crypto'
|
|
3
3
|
import { relative } from 'path'
|
|
4
|
-
import { formatVersionedId } from '@pikku/core/
|
|
4
|
+
import { formatVersionedId } from '@pikku/core/utils'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Deterministic placeholder id for an anonymous/unnamed pikku function or
|