@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { ErrorCode } from '../error-codes.js';
|
|
3
3
|
import { extractStringLiteral } from '../utils/extract-node-value.js';
|
|
4
|
-
function extractAstValue(expr, refParamName, templateParamName) {
|
|
4
|
+
function extractAstValue(expr, refParamName, templateParamName, itemParamName) {
|
|
5
5
|
// `'GET' as const` / parenthesised values wrap the real initializer — see
|
|
6
6
|
// through them or the whole property is silently dropped from the meta.
|
|
7
7
|
while (ts.isAsExpression(expr) || ts.isParenthesizedExpression(expr)) {
|
|
@@ -31,6 +31,11 @@ function extractAstValue(expr, refParamName, templateParamName) {
|
|
|
31
31
|
const path = args[1] && ts.isStringLiteral(args[1]) ? args[1].text : undefined;
|
|
32
32
|
return { $ref: nodeId, path };
|
|
33
33
|
}
|
|
34
|
+
if (itemParamName && callee.text === itemParamName) {
|
|
35
|
+
const pathArg = expr.arguments[0];
|
|
36
|
+
const path = pathArg && ts.isStringLiteral(pathArg) ? pathArg.text : undefined;
|
|
37
|
+
return { $ref: '$item', path };
|
|
38
|
+
}
|
|
34
39
|
if (templateParamName && callee.text === templateParamName) {
|
|
35
40
|
const templateStr = expr.arguments[0] && ts.isStringLiteral(expr.arguments[0])
|
|
36
41
|
? expr.arguments[0].text
|
|
@@ -39,7 +44,7 @@ function extractAstValue(expr, refParamName, templateParamName) {
|
|
|
39
44
|
const refs = [];
|
|
40
45
|
if (refsArg && ts.isArrayLiteralExpression(refsArg)) {
|
|
41
46
|
for (const el of refsArg.elements) {
|
|
42
|
-
const resolved = extractAstValue(el, refParamName, templateParamName);
|
|
47
|
+
const resolved = extractAstValue(el, refParamName, templateParamName, itemParamName);
|
|
43
48
|
if (typeof resolved === 'object' &&
|
|
44
49
|
resolved !== null &&
|
|
45
50
|
'$ref' in resolved) {
|
|
@@ -64,7 +69,7 @@ function extractAstValue(expr, refParamName, templateParamName) {
|
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
if (ts.isArrayLiteralExpression(expr)) {
|
|
67
|
-
return expr.elements.map((el) => extractAstValue(el, refParamName, templateParamName));
|
|
72
|
+
return expr.elements.map((el) => extractAstValue(el, refParamName, templateParamName, itemParamName));
|
|
68
73
|
}
|
|
69
74
|
if (ts.isObjectLiteralExpression(expr)) {
|
|
70
75
|
const obj = {};
|
|
@@ -78,7 +83,7 @@ function extractAstValue(expr, refParamName, templateParamName) {
|
|
|
78
83
|
: null;
|
|
79
84
|
if (!key)
|
|
80
85
|
continue;
|
|
81
|
-
obj[key] = extractAstValue(prop.initializer, refParamName, templateParamName);
|
|
86
|
+
obj[key] = extractAstValue(prop.initializer, refParamName, templateParamName, itemParamName);
|
|
82
87
|
}
|
|
83
88
|
return obj;
|
|
84
89
|
}
|
|
@@ -125,6 +130,11 @@ function extractInputMapping(node, _checker) {
|
|
|
125
130
|
const templateParamName = node.parameters.length > 1 && ts.isIdentifier(node.parameters[1].name)
|
|
126
131
|
? node.parameters[1].name.text
|
|
127
132
|
: 'template';
|
|
133
|
+
// `$item` is appended after `template` so neither existing arrow shape —
|
|
134
|
+
// `(ref) => ...` nor `(ref, template) => ...` — shifts a parameter.
|
|
135
|
+
const itemParamName = node.parameters.length > 2 && ts.isIdentifier(node.parameters[2].name)
|
|
136
|
+
? node.parameters[2].name.text
|
|
137
|
+
: undefined;
|
|
128
138
|
const input = {};
|
|
129
139
|
for (const prop of bodyObj.properties) {
|
|
130
140
|
if (!ts.isPropertyAssignment(prop))
|
|
@@ -136,13 +146,48 @@ function extractInputMapping(node, _checker) {
|
|
|
136
146
|
: null;
|
|
137
147
|
if (!key)
|
|
138
148
|
continue;
|
|
139
|
-
const value = extractAstValue(prop.initializer, refParamName, templateParamName);
|
|
149
|
+
const value = extractAstValue(prop.initializer, refParamName, templateParamName, itemParamName);
|
|
140
150
|
if (value !== undefined) {
|
|
141
151
|
input[key] = value;
|
|
142
152
|
}
|
|
143
153
|
}
|
|
144
154
|
return input;
|
|
145
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Extract a `forEach` source, authored either as a bare node id (`'listRows'`)
|
|
158
|
+
* or as a callback returning a ref (`(ref) => ref('listRows', 'rows')`).
|
|
159
|
+
*/
|
|
160
|
+
function extractForEachConfig(node) {
|
|
161
|
+
while (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) {
|
|
162
|
+
node = node.expression;
|
|
163
|
+
}
|
|
164
|
+
if (ts.isStringLiteral(node)) {
|
|
165
|
+
return { $ref: node.text };
|
|
166
|
+
}
|
|
167
|
+
if (ts.isArrowFunction(node)) {
|
|
168
|
+
const refParamName = node.parameters.length > 0 && ts.isIdentifier(node.parameters[0].name)
|
|
169
|
+
? node.parameters[0].name.text
|
|
170
|
+
: 'ref';
|
|
171
|
+
let body = node.body;
|
|
172
|
+
if (ts.isBlock(body)) {
|
|
173
|
+
for (const stmt of body.statements) {
|
|
174
|
+
if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
175
|
+
body = stmt.expression;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (!ts.isExpression(body))
|
|
180
|
+
return undefined;
|
|
181
|
+
const value = extractAstValue(body, refParamName, undefined);
|
|
182
|
+
if (typeof value === 'object' &&
|
|
183
|
+
value !== null &&
|
|
184
|
+
'$ref' in value &&
|
|
185
|
+
typeof value.$ref === 'string') {
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
146
191
|
/**
|
|
147
192
|
* Extract next config (string, array, or record)
|
|
148
193
|
*/
|
|
@@ -301,6 +346,8 @@ function extractGraphFromNewFormat(nodesNode, configNode, checker, state) {
|
|
|
301
346
|
nodeId,
|
|
302
347
|
rpcName,
|
|
303
348
|
input: {},
|
|
349
|
+
forEach: undefined,
|
|
350
|
+
mode: undefined,
|
|
304
351
|
next: undefined,
|
|
305
352
|
onError: undefined,
|
|
306
353
|
retries: undefined,
|
|
@@ -326,6 +373,8 @@ function extractGraphFromNewFormat(nodesNode, configNode, checker, state) {
|
|
|
326
373
|
nodes[nodeId].next = nodeConfig.next;
|
|
327
374
|
nodes[nodeId].onError = nodeConfig.onError;
|
|
328
375
|
nodes[nodeId].input = nodeConfig.input;
|
|
376
|
+
nodes[nodeId].forEach = nodeConfig.forEach;
|
|
377
|
+
nodes[nodeId].mode = nodeConfig.mode;
|
|
329
378
|
nodes[nodeId].retries = nodeConfig.retries;
|
|
330
379
|
nodes[nodeId].retryDelay = nodeConfig.retryDelay;
|
|
331
380
|
nodes[nodeId].notes = nodeConfig.notes;
|
|
@@ -342,6 +391,8 @@ function extractNodeConfigFromObject(obj, checker) {
|
|
|
342
391
|
let next = undefined;
|
|
343
392
|
let onError = undefined;
|
|
344
393
|
let input = {};
|
|
394
|
+
let forEach = undefined;
|
|
395
|
+
let mode = undefined;
|
|
345
396
|
let retries = undefined;
|
|
346
397
|
let retryDelay = undefined;
|
|
347
398
|
let notes = undefined;
|
|
@@ -358,6 +409,12 @@ function extractNodeConfigFromObject(obj, checker) {
|
|
|
358
409
|
else if (propName === 'input') {
|
|
359
410
|
input = extractInputMapping(prop.initializer, checker);
|
|
360
411
|
}
|
|
412
|
+
else if (propName === 'forEach') {
|
|
413
|
+
forEach = extractForEachConfig(prop.initializer);
|
|
414
|
+
}
|
|
415
|
+
else if (propName === 'mode') {
|
|
416
|
+
mode = extractStringLiteral(prop.initializer, checker);
|
|
417
|
+
}
|
|
361
418
|
else if (propName === 'retries') {
|
|
362
419
|
if (ts.isNumericLiteral(prop.initializer)) {
|
|
363
420
|
retries = Number(prop.initializer.text);
|
|
@@ -375,7 +432,7 @@ function extractNodeConfigFromObject(obj, checker) {
|
|
|
375
432
|
notes = extractStringLiteral(prop.initializer, checker);
|
|
376
433
|
}
|
|
377
434
|
}
|
|
378
|
-
return { next, onError, input, retries, retryDelay, notes };
|
|
435
|
+
return { next, onError, input, forEach, mode, retries, retryDelay, notes };
|
|
379
436
|
}
|
|
380
437
|
/**
|
|
381
438
|
* Inspector for pikkuWorkflowGraph() calls
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare enum ErrorCode {
|
|
|
20
20
|
CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = "PKU672",
|
|
21
21
|
SCENARIO_HAS_SERVICES = "PKU673",
|
|
22
22
|
EXPECT_EVENTUALLY_SCENARIO_ONLY = "PKU675",
|
|
23
|
-
|
|
23
|
+
SCENARIO_STEP_NEEDS_ACTOR = "PKU677",
|
|
24
24
|
SCENARIO_STEP_TARGET_NOT_STATIC = "PKU678",
|
|
25
25
|
SCENARIO_NOT_EXTRACTABLE = "PKU679",
|
|
26
26
|
SCENARIO_HAS_NO_ASSERTION = "PKU680",
|
|
@@ -30,6 +30,7 @@ export declare enum ErrorCode {
|
|
|
30
30
|
COMPLEX_WORKFLOW_NOT_ALLOWED = "PKU643",
|
|
31
31
|
REMOTE_ADDON_NOT_DEV_DEPENDENCY = "PKU338",
|
|
32
32
|
REMOTE_ADDON_AUTH_UNRESOLVED = "PKU339",
|
|
33
|
+
ADDON_NOT_INSTALLED = "PKU340",
|
|
33
34
|
DB_COLUMN_NAME_TYPE_CONTRADICTION = "PKU480",
|
|
34
35
|
DB_JSON_COLUMN_UNTYPED = "PKU481",
|
|
35
36
|
DB_FORMAT_ON_NON_STRING = "PKU482",
|
|
@@ -70,6 +71,7 @@ export declare enum ErrorCode {
|
|
|
70
71
|
DUPLICATE_CORE_VERSION = "PKU717",
|
|
71
72
|
CORE_VERSION_SKEW = "PKU718",
|
|
72
73
|
SPLIT_TYPE_IDENTITY = "PKU719",
|
|
74
|
+
SINGLETON_SERVICES_TYPE_UNRESOLVED = "PKU724",
|
|
73
75
|
PII_IN_OUTPUT = "PKU910",
|
|
74
76
|
ADDON_WIRING_NOT_ALLOWED = "PKU920",
|
|
75
77
|
ADDON_CONTRACT_HANDLERS_NOT_ALLOWED = "PKU921",
|
package/dist/error-codes.js
CHANGED
|
@@ -22,7 +22,7 @@ export var ErrorCode;
|
|
|
22
22
|
ErrorCode["CLI_CLIENTSIDE_RENDERER_HAS_SERVICES"] = "PKU672";
|
|
23
23
|
ErrorCode["SCENARIO_HAS_SERVICES"] = "PKU673";
|
|
24
24
|
ErrorCode["EXPECT_EVENTUALLY_SCENARIO_ONLY"] = "PKU675";
|
|
25
|
-
ErrorCode["
|
|
25
|
+
ErrorCode["SCENARIO_STEP_NEEDS_ACTOR"] = "PKU677";
|
|
26
26
|
ErrorCode["SCENARIO_STEP_TARGET_NOT_STATIC"] = "PKU678";
|
|
27
27
|
ErrorCode["SCENARIO_NOT_EXTRACTABLE"] = "PKU679";
|
|
28
28
|
ErrorCode["SCENARIO_HAS_NO_ASSERTION"] = "PKU680";
|
|
@@ -33,6 +33,7 @@ export var ErrorCode;
|
|
|
33
33
|
// Remote addon (wireRemoteAddon) validation
|
|
34
34
|
ErrorCode["REMOTE_ADDON_NOT_DEV_DEPENDENCY"] = "PKU338";
|
|
35
35
|
ErrorCode["REMOTE_ADDON_AUTH_UNRESOLVED"] = "PKU339";
|
|
36
|
+
ErrorCode["ADDON_NOT_INSTALLED"] = "PKU340";
|
|
36
37
|
// Database schema codegen warnings
|
|
37
38
|
ErrorCode["DB_COLUMN_NAME_TYPE_CONTRADICTION"] = "PKU480";
|
|
38
39
|
ErrorCode["DB_JSON_COLUMN_UNTYPED"] = "PKU481";
|
|
@@ -91,6 +92,7 @@ export var ErrorCode;
|
|
|
91
92
|
ErrorCode["DUPLICATE_CORE_VERSION"] = "PKU717";
|
|
92
93
|
ErrorCode["CORE_VERSION_SKEW"] = "PKU718";
|
|
93
94
|
ErrorCode["SPLIT_TYPE_IDENTITY"] = "PKU719";
|
|
95
|
+
ErrorCode["SINGLETON_SERVICES_TYPE_UNRESOLVED"] = "PKU724";
|
|
94
96
|
// Data classification errors
|
|
95
97
|
ErrorCode["PII_IN_OUTPUT"] = "PKU910";
|
|
96
98
|
// Addon authoring errors
|
package/dist/index.d.ts
CHANGED
|
@@ -12,9 +12,11 @@ export type { SerializableInspectorState } from './utils/serialize-inspector-sta
|
|
|
12
12
|
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
|
13
13
|
export { resolveDeployTarget } from './utils/resolve-deploy-target.js';
|
|
14
14
|
export { generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
|
|
15
|
+
export { unresolvedSchemaReferences } from './utils/post-process.js';
|
|
16
|
+
export type { SurfaceUsageCounts } from './utils/extract-surface-usage.js';
|
|
15
17
|
export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
|
|
16
18
|
export type { ContractEntry, VersionHashEntry, VersionValidateError, VersionManifest, VersionManifestEntry, } from './utils/contract-hashes.js';
|
|
17
|
-
export type { InspectorFeature, InspectorFeatureEntry
|
|
19
|
+
export type { InspectorFeature, InspectorFeatureEntry } from './types.js';
|
|
18
20
|
export { serializeMCPJson } from './utils/serialize-mcp-json.js';
|
|
19
21
|
export type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
|
|
20
22
|
export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { serializeInspectorState, deserializeInspectorState, } from './utils/ser
|
|
|
6
6
|
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
|
7
7
|
export { resolveDeployTarget } from './utils/resolve-deploy-target.js';
|
|
8
8
|
export { generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
|
|
9
|
+
export { unresolvedSchemaReferences } from './utils/post-process.js';
|
|
9
10
|
export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
|
|
10
11
|
export { serializeMCPJson } from './utils/serialize-mcp-json.js';
|
|
11
12
|
export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
|
package/dist/inspector.js
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 { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
@@ -11,12 +11,13 @@ import { validateExposedFunctionsGated } from './utils/validate-exposed-function
|
|
|
11
11
|
import { validateTagsResolveToMiddleware } from './utils/validate-tags-resolve-to-middleware.js';
|
|
12
12
|
import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js';
|
|
13
13
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
14
|
-
import { pikkuState } from '@pikku/core/
|
|
14
|
+
import { pikkuState } from '@pikku/core/state';
|
|
15
15
|
import { resolveLatestVersions } from './utils/resolve-versions.js';
|
|
16
16
|
import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js';
|
|
17
17
|
import { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './utils/workflow/graph/finalize-workflow-wires.js';
|
|
18
18
|
import { generateAllSchemas } from './utils/schema-generator.js';
|
|
19
19
|
import { extractSecretUsage } from './utils/extract-secret-usage.js';
|
|
20
|
+
import { accumulateSurfaceUsage, surfaceUsageArea, } from './utils/extract-surface-usage.js';
|
|
20
21
|
import { loadAddonFunctionsMeta, loadAddonSchemas, } from './utils/load-addon-functions-meta.js';
|
|
21
22
|
import { computeContractHashes, extractContractsFromMeta, updateManifest, createEmptyManifest, validateContracts, } from './utils/contract-hashes.js';
|
|
22
23
|
/**
|
|
@@ -142,6 +143,7 @@ export function getInitialInspectorState(rootDir) {
|
|
|
142
143
|
files: new Set(),
|
|
143
144
|
usage: new Map(),
|
|
144
145
|
},
|
|
146
|
+
surfaceUsage: {},
|
|
145
147
|
credentials: {
|
|
146
148
|
definitions: [],
|
|
147
149
|
files: new Set(),
|
|
@@ -299,11 +301,19 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
299
301
|
.sort((a, b) => a.fileName < b.fileName ? -1 : a.fileName > b.fileName ? 1 : 0);
|
|
300
302
|
logger.debug(`Got source files in ${(performance.now() - startSourceFiles).toFixed(2)}ms`);
|
|
301
303
|
const state = getInitialInspectorState(rootDir);
|
|
304
|
+
const surfaceAreaCache = new Map();
|
|
302
305
|
for (const sourceFile of sourceFiles) {
|
|
303
306
|
const usage = extractSecretUsage(sourceFile);
|
|
304
307
|
if (usage.keys.length > 0 || usage.dynamic.length > 0) {
|
|
305
308
|
state.secrets.usage.set(sourceFile.fileName, usage);
|
|
306
309
|
}
|
|
310
|
+
// Counted in the sweep that already has this file open. The public surface
|
|
311
|
+
// is documentation, and documentation may not cost a project a second pass
|
|
312
|
+
// over its source.
|
|
313
|
+
accumulateSurfaceUsage(sourceFile, {
|
|
314
|
+
area: surfaceUsageArea(sourceFile.fileName, rootDir, surfaceAreaCache),
|
|
315
|
+
file: relative(rootDir, sourceFile.fileName),
|
|
316
|
+
}, state.surfaceUsage);
|
|
307
317
|
}
|
|
308
318
|
// First sweep: add all functions
|
|
309
319
|
const startSetup = performance.now();
|
package/dist/types.d.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import type * as ts from 'typescript';
|
|
2
2
|
import type { SecretUsage } from './utils/extract-secret-usage.js';
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
12
|
-
import type {
|
|
13
|
-
import type {
|
|
14
|
-
import type {
|
|
15
|
-
import type {
|
|
16
|
-
import type {
|
|
17
|
-
import type {
|
|
18
|
-
import type {
|
|
19
|
-
import type {
|
|
20
|
-
import type {
|
|
21
|
-
import type {
|
|
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';
|
|
11
|
+
import type { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
|
|
12
|
+
import type { AgentsMeta } from '@pikku/core/agent';
|
|
13
|
+
import type { ScorerMeta } from '@pikku/core/agent-scorer';
|
|
14
|
+
import type { CLIMeta } from '@pikku/core/cli';
|
|
15
|
+
import type { CLICommandMeta } from '@pikku/core/cli';
|
|
16
|
+
import type { NodesMeta } from '@pikku/core/node';
|
|
17
|
+
import type { SecretDefinitions } from '@pikku/core/secret';
|
|
18
|
+
import type { CredentialDefinitions } from '@pikku/core/credential';
|
|
19
|
+
import type { ScopeDefinitions } from '@pikku/core/scope';
|
|
20
|
+
import type { SystemRoleDefinitions } from '@pikku/core/role';
|
|
21
|
+
import type { PersonaDefinitions } from '@pikku/core/persona';
|
|
22
|
+
import type { VariableDefinitions } from '@pikku/core/variable';
|
|
22
23
|
import type { TypesMap } from './types-map.js';
|
|
23
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
24
|
-
import type { FunctionServicesMeta, FunctionWiresMeta
|
|
24
|
+
import type { FunctionsMeta } from '@pikku/core/services';
|
|
25
|
+
import type { FunctionServicesMeta, FunctionWiresMeta } from '@pikku/core/function';
|
|
26
|
+
import type { JSONValue } from '@pikku/core/utils';
|
|
25
27
|
import type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
|
|
26
28
|
import type { ErrorCode, CodedDiagnostic } from './error-codes.js';
|
|
27
29
|
import type { VersionManifest, VersionValidateError } from './utils/contract-hashes.js';
|
|
@@ -593,6 +595,8 @@ export interface InspectorState {
|
|
|
593
595
|
/** Secret reads found per source file, keyed by absolute path. */
|
|
594
596
|
usage: Map<string, SecretUsage>;
|
|
595
597
|
};
|
|
598
|
+
/** How often each `#pikku/*` export is imported, and from where. */
|
|
599
|
+
surfaceUsage: SurfaceUsageCounts;
|
|
596
600
|
credentials: {
|
|
597
601
|
definitions: CredentialDefinitions;
|
|
598
602
|
files: Set<string>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveFunctionMeta } from './resolve-function-meta.js';
|
|
1
2
|
/**
|
|
2
3
|
* Resolve, per HTTP route, whether reaching it requires a session.
|
|
3
4
|
*
|
|
@@ -22,7 +23,7 @@ export function annotateHttpRouteAuth(state) {
|
|
|
22
23
|
const gatedPackages = packagesGatedByTheirAddon(state);
|
|
23
24
|
for (const routes of Object.values(state.http?.meta ?? {})) {
|
|
24
25
|
for (const route of Object.values(routes)) {
|
|
25
|
-
const meta = state
|
|
26
|
+
const meta = resolveFunctionMeta(state, route.pikkuFuncId);
|
|
26
27
|
route.requiresSession =
|
|
27
28
|
// A pikkuFunc always requires a session, on every path.
|
|
28
29
|
meta?.sessionless === false ||
|
|
@@ -38,7 +38,8 @@ const SINKS = [
|
|
|
38
38
|
export function findRevealedSecretSinks(checker, handler) {
|
|
39
39
|
const violations = [];
|
|
40
40
|
const visit = (node) => {
|
|
41
|
-
if (ts.isCallExpression(node) &&
|
|
41
|
+
if (ts.isCallExpression(node) &&
|
|
42
|
+
ts.isPropertyAccessExpression(node.expression)) {
|
|
42
43
|
const sink = describeSink(checker, node.expression);
|
|
43
44
|
if (sink) {
|
|
44
45
|
for (const arg of node.arguments) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
1
|
+
import type { FunctionsMeta } from '@pikku/core/services';
|
|
2
2
|
import type { TypesMap } from '../types-map.js';
|
|
3
3
|
import type { SchemaRef } from '../types.js';
|
|
4
4
|
export declare function computeRequiredSchemas(functionsMeta: FunctionsMeta, typesMap: TypesMap, additionalTypes?: string[], schemaLookup?: Map<string, SchemaRef>): Set<string>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
2
|
-
import type { JSONValue } from '@pikku/core/
|
|
1
|
+
import type { FunctionsMeta } from '@pikku/core/services';
|
|
2
|
+
import type { JSONValue } from '@pikku/core/utils';
|
|
3
3
|
import type { TypesMap } from '../types-map.js';
|
|
4
4
|
import { ErrorCode } from '../error-codes.js';
|
|
5
5
|
export type ContractEntry = {
|
|
@@ -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
|
* Deterministic placeholder id for an anonymous/unnamed pikku function or
|
|
7
7
|
* permission. Derived from the call expression's source location (relative path
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import type { FunctionServicesMeta, FunctionWiresMeta } from '@pikku/core/
|
|
2
|
+
import type { FunctionServicesMeta, FunctionWiresMeta } from '@pikku/core/function';
|
|
3
3
|
/**
|
|
4
4
|
* Extract services from a function's first parameter destructuring pattern
|
|
5
5
|
*/
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* How often each `#pikku/*` export is imported, the source areas it was
|
|
4
|
+
* imported from, and the files those imports are written in. Counters only:
|
|
5
|
+
* this is accumulated during the sweep that already visits every source file,
|
|
6
|
+
* so nothing here may retain a node.
|
|
7
|
+
*/
|
|
8
|
+
export type SurfaceUsageCounts = Record<string, Record<string, {
|
|
9
|
+
imports: number;
|
|
10
|
+
seenIn: string[];
|
|
11
|
+
files: string[];
|
|
12
|
+
}>>;
|
|
13
|
+
/**
|
|
14
|
+
* A label a person recognises: the workspace package a file belongs to, or the
|
|
15
|
+
* top-level source directory it sits under when the project is one package.
|
|
16
|
+
* The root package is skipped deliberately — naming it would label every file
|
|
17
|
+
* in a single-package project identically.
|
|
18
|
+
*/
|
|
19
|
+
export declare const surfaceUsageArea: (fileName: string, rootDir: string, cache: Map<string, string | null>) => string;
|
|
20
|
+
/**
|
|
21
|
+
* Counts the `#pikku/*` imports of one file into `into`. Only the file's own
|
|
22
|
+
* top-level statements are looked at — an import declaration can appear nowhere
|
|
23
|
+
* else — so this costs a single pass over the statement list and retains
|
|
24
|
+
* nothing.
|
|
25
|
+
*/
|
|
26
|
+
export declare const accumulateSurfaceUsage: (sourceFile: ts.SourceFile, { area, file }: {
|
|
27
|
+
area: string;
|
|
28
|
+
file: string;
|
|
29
|
+
}, into: SurfaceUsageCounts) => void;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { basename, dirname, relative, sep } from 'path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
/**
|
|
5
|
+
* The leaf a specifier belongs to. `#pikku/workflow` and the deep
|
|
6
|
+
* `#pikku/workflow/pikku-workflow-types.gen.js` are the same door, so both
|
|
7
|
+
* count against `#pikku/workflow`.
|
|
8
|
+
*/
|
|
9
|
+
const leafSpecifierOf = (specifier) => {
|
|
10
|
+
if (!specifier.startsWith('#pikku/'))
|
|
11
|
+
return null;
|
|
12
|
+
const leaf = specifier.slice('#pikku/'.length).split('/')[0];
|
|
13
|
+
// A root-level generated file, e.g. `#pikku/pikku-bootstrap.gen.js`, is not a
|
|
14
|
+
// leaf and has no editorial entry to count against.
|
|
15
|
+
if (!leaf || leaf.endsWith('.js'))
|
|
16
|
+
return null;
|
|
17
|
+
return `#pikku/${leaf}`;
|
|
18
|
+
};
|
|
19
|
+
const packageNameAt = (directory, cache) => {
|
|
20
|
+
const cached = cache.get(directory);
|
|
21
|
+
if (cached !== undefined)
|
|
22
|
+
return cached;
|
|
23
|
+
let name = null;
|
|
24
|
+
const path = `${directory}${sep}package.json`;
|
|
25
|
+
if (existsSync(path)) {
|
|
26
|
+
try {
|
|
27
|
+
name =
|
|
28
|
+
JSON.parse(readFileSync(path, 'utf8')).name ??
|
|
29
|
+
null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
name = null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
cache.set(directory, name);
|
|
36
|
+
return name;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* A label a person recognises: the workspace package a file belongs to, or the
|
|
40
|
+
* top-level source directory it sits under when the project is one package.
|
|
41
|
+
* The root package is skipped deliberately — naming it would label every file
|
|
42
|
+
* in a single-package project identically.
|
|
43
|
+
*/
|
|
44
|
+
export const surfaceUsageArea = (fileName, rootDir, cache) => {
|
|
45
|
+
let directory = dirname(fileName);
|
|
46
|
+
while (directory.startsWith(rootDir) && directory !== rootDir) {
|
|
47
|
+
const name = packageNameAt(directory, cache);
|
|
48
|
+
if (name)
|
|
49
|
+
return name;
|
|
50
|
+
directory = dirname(directory);
|
|
51
|
+
}
|
|
52
|
+
const [segment] = relative(rootDir, fileName).split(sep);
|
|
53
|
+
return segment && segment !== basename(fileName) ? segment : basename(rootDir);
|
|
54
|
+
};
|
|
55
|
+
const namesOf = (clause) => {
|
|
56
|
+
if (!clause)
|
|
57
|
+
return [];
|
|
58
|
+
if (ts.isNamedImports(clause) || ts.isNamedExports(clause)) {
|
|
59
|
+
return clause.elements.map((element) => (element.propertyName ?? element.name).text);
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Counts the `#pikku/*` imports of one file into `into`. Only the file's own
|
|
65
|
+
* top-level statements are looked at — an import declaration can appear nowhere
|
|
66
|
+
* else — so this costs a single pass over the statement list and retains
|
|
67
|
+
* nothing.
|
|
68
|
+
*/
|
|
69
|
+
export const accumulateSurfaceUsage = (sourceFile, { area, file }, into) => {
|
|
70
|
+
for (const statement of sourceFile.statements) {
|
|
71
|
+
let specifier;
|
|
72
|
+
let names = [];
|
|
73
|
+
if (ts.isImportDeclaration(statement)) {
|
|
74
|
+
specifier = statement.moduleSpecifier;
|
|
75
|
+
names = namesOf(statement.importClause?.namedBindings);
|
|
76
|
+
}
|
|
77
|
+
else if (ts.isExportDeclaration(statement) && statement.moduleSpecifier) {
|
|
78
|
+
specifier = statement.moduleSpecifier;
|
|
79
|
+
names = namesOf(statement.exportClause);
|
|
80
|
+
}
|
|
81
|
+
if (!specifier || names.length === 0 || !ts.isStringLiteral(specifier)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const leaf = leafSpecifierOf(specifier.text);
|
|
85
|
+
if (!leaf)
|
|
86
|
+
continue;
|
|
87
|
+
const symbols = (into[leaf] ??= {});
|
|
88
|
+
for (const name of names) {
|
|
89
|
+
const usage = (symbols[name] ??= { imports: 0, seenIn: [], files: [] });
|
|
90
|
+
usage.imports += 1;
|
|
91
|
+
if (!usage.seenIn.includes(area))
|
|
92
|
+
usage.seenIn.push(area);
|
|
93
|
+
if (!usage.files.includes(file))
|
|
94
|
+
usage.files.push(file);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|