@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.
Files changed (136) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/OPTIMIZATION-PLAN.md +19 -19
  3. package/dist/add/add-auth.js +1 -1
  4. package/dist/add/add-channel.d.ts +1 -1
  5. package/dist/add/add-cli.js +5 -0
  6. package/dist/add/add-functions.js +10 -1
  7. package/dist/add/add-http-route.js +18 -18
  8. package/dist/add/add-keyed-wiring.d.ts +2 -0
  9. package/dist/add/add-keyed-wiring.js +3 -1
  10. package/dist/add/add-personas.js +4 -1
  11. package/dist/add/add-secret.js +1 -0
  12. package/dist/add/add-variable.js +1 -0
  13. package/dist/add/add-wire-remote-addon.js +2 -1
  14. package/dist/add/add-workflow-graph.js +63 -6
  15. package/dist/add/add-workflow.d.ts +1 -1
  16. package/dist/error-codes.d.ts +3 -1
  17. package/dist/error-codes.js +3 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +1 -0
  20. package/dist/inspector.js +12 -2
  21. package/dist/types.d.ts +25 -21
  22. package/dist/utils/annotate-http-route-auth.js +2 -1
  23. package/dist/utils/check-secret-sinks.js +2 -1
  24. package/dist/utils/compute-required-schemas.d.ts +1 -1
  25. package/dist/utils/contract-hashes.d.ts +2 -2
  26. package/dist/utils/contract-hashes.js +1 -1
  27. package/dist/utils/extract-function-name.js +1 -1
  28. package/dist/utils/extract-services.d.ts +1 -1
  29. package/dist/utils/extract-surface-usage.d.ts +29 -0
  30. package/dist/utils/extract-surface-usage.js +97 -0
  31. package/dist/utils/filter-inspector-state.js +53 -16
  32. package/dist/utils/filter-utils.d.ts +1 -1
  33. package/dist/utils/get-property-value.js +42 -3
  34. package/dist/utils/load-addon-functions-meta.js +31 -0
  35. package/dist/utils/middleware.d.ts +1 -1
  36. package/dist/utils/permissions.d.ts +1 -1
  37. package/dist/utils/post-process.d.ts +27 -7
  38. package/dist/utils/post-process.js +79 -14
  39. package/dist/utils/resolve-deploy-target.d.ts +1 -1
  40. package/dist/utils/resolve-function-meta.d.ts +1 -1
  41. package/dist/utils/resolve-function-types.d.ts +1 -1
  42. package/dist/utils/resolve-versions.js +1 -1
  43. package/dist/utils/schema-generator.d.ts +1 -1
  44. package/dist/utils/serialize-inspector-state.d.ts +2 -1
  45. package/dist/utils/serialize-inspector-state.js +2 -0
  46. package/dist/utils/serialize-openapi-json.d.ts +2 -2
  47. package/dist/utils/validate-auth-sessionless.js +2 -1
  48. package/dist/utils/workflow/derive-workflow-plan.d.ts +1 -1
  49. package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
  50. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +1 -1
  51. package/dist/utils/workflow/graph/finalize-workflows.js +1 -1
  52. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +6 -1
  53. package/dist/utils/workflow/graph/serialize-workflow-graph.js +26 -2
  54. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +11 -2
  55. package/package.json +10 -10
  56. package/run-tests.sh +0 -0
  57. package/src/add/add-agent.ts +2 -4
  58. package/src/add/add-auth.ts +2 -2
  59. package/src/add/add-channel.ts +1 -4
  60. package/src/add/add-cli.ts +7 -1
  61. package/src/add/add-credential.ts +3 -6
  62. package/src/add/add-file-with-factory.ts +2 -6
  63. package/src/add/add-functions.ts +24 -31
  64. package/src/add/add-gateway.ts +1 -1
  65. package/src/add/add-http-route-ref.test.ts +149 -0
  66. package/src/add/add-http-route.ts +26 -29
  67. package/src/add/add-keyed-wiring.ts +8 -7
  68. package/src/add/add-permission.ts +1 -1
  69. package/src/add/add-personas.test.ts +26 -2
  70. package/src/add/add-personas.ts +4 -3
  71. package/src/add/add-scenario-step.test.ts +37 -2
  72. package/src/add/add-scope.test.ts +15 -15
  73. package/src/add/add-scope.ts +5 -9
  74. package/src/add/add-secret.ts +1 -0
  75. package/src/add/add-system-role.test.ts +4 -4
  76. package/src/add/add-system-role.ts +2 -4
  77. package/src/add/add-variable.ts +1 -0
  78. package/src/add/add-wire-remote-addon.ts +4 -1
  79. package/src/add/add-workflow-graph-foreach.test.ts +174 -0
  80. package/src/add/add-workflow-graph-input.test.ts +1 -1
  81. package/src/add/add-workflow-graph.ts +78 -6
  82. package/src/add/add-workflow-sleep-expression.test.ts +3 -4
  83. package/src/add/add-workflow.ts +1 -4
  84. package/src/add/addon-contracts.test.ts +1 -1
  85. package/src/add/config-value-metadata.test.ts +94 -0
  86. package/src/add/secret-sink-check.test.ts +4 -1
  87. package/src/error-codes.ts +3 -1
  88. package/src/index.ts +3 -4
  89. package/src/inspector.ts +19 -2
  90. package/src/types.ts +25 -28
  91. package/src/utils/annotate-http-route-auth.test.ts +5 -7
  92. package/src/utils/annotate-http-route-auth.ts +2 -1
  93. package/src/utils/check-secret-sinks.ts +4 -1
  94. package/src/utils/compute-required-schemas.ts +1 -1
  95. package/src/utils/contract-hashes.test.ts +1 -1
  96. package/src/utils/contract-hashes.ts +3 -3
  97. package/src/utils/extract-function-name.ts +1 -1
  98. package/src/utils/extract-services.ts +1 -2
  99. package/src/utils/extract-surface-usage.test.ts +147 -0
  100. package/src/utils/extract-surface-usage.ts +121 -0
  101. package/src/utils/filter-inspector-state.test.ts +138 -12
  102. package/src/utils/filter-inspector-state.ts +82 -21
  103. package/src/utils/filter-utils.ts +1 -1
  104. package/src/utils/get-property-value.ts +43 -5
  105. package/src/utils/load-addon-functions-meta.ts +40 -0
  106. package/src/utils/middleware.ts +1 -1
  107. package/src/utils/permissions.ts +1 -1
  108. package/src/utils/post-process.test.ts +59 -2
  109. package/src/utils/post-process.ts +88 -25
  110. package/src/utils/resolve-deploy-target.ts +1 -1
  111. package/src/utils/resolve-function-meta.ts +1 -4
  112. package/src/utils/resolve-function-types.ts +1 -1
  113. package/src/utils/resolve-versions.ts +2 -5
  114. package/src/utils/schema-generator.ts +3 -3
  115. package/src/utils/serialize-inspector-state.ts +4 -1
  116. package/src/utils/serialize-mcp-json.ts +1 -1
  117. package/src/utils/serialize-openapi-json.ts +2 -2
  118. package/src/utils/validate-auth-sessionless.ts +2 -1
  119. package/src/utils/validate-tags-resolve-to-middleware.test.ts +4 -1
  120. package/src/utils/workflow/derive-workflow-plan.test.ts +1 -1
  121. package/src/utils/workflow/derive-workflow-plan.ts +1 -1
  122. package/src/utils/workflow/dsl/extract-approval-policy.test.ts +1 -1
  123. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +2 -2
  124. package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +1 -1
  125. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +1 -4
  126. package/src/utils/workflow/graph/finalize-workflow-wires.ts +1 -1
  127. package/src/utils/workflow/graph/finalize-workflows.ts +2 -2
  128. package/src/utils/workflow/graph/serialize-workflow-graph.ts +34 -3
  129. package/src/utils/workflow/graph/workflow-graph.types.ts +12 -2
  130. package/tsconfig.tsbuildinfo +1 -1
  131. package/dist/add/add-ai-agent.d.ts +0 -4
  132. package/dist/add/add-ai-agent.js +0 -442
  133. package/dist/add/add-ai-scorer.d.ts +0 -2
  134. package/dist/add/add-ai-scorer.js +0 -61
  135. package/dist/utils/secret-brokers.d.ts +0 -2
  136. 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
@@ -1,5 +1,5 @@
1
1
  import type { AddWiring } from '../types.js';
2
- import type { WorkflowStepMeta } from '@pikku/core/ecosystem/workflow';
2
+ import type { WorkflowStepMeta } from '@pikku/core/workflow';
3
3
  /**
4
4
  * Recursively collect all RPC names from workflow steps
5
5
  */
@@ -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
- SCENARIO_BROWSER_STEP_NEEDS_ACTOR = "PKU677",
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",
@@ -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["SCENARIO_BROWSER_STEP_NEEDS_ACTOR"] = "PKU677";
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, } from './types.js';
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/ecosystem';
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 { ChannelMessageMeta, ChannelsMeta } from '@pikku/core/ecosystem/channel';
4
- import type { GatewaysMeta } from '@pikku/core/ecosystem/gateway';
5
- import type { HTTPWiringsMeta } from '@pikku/core/ecosystem/http';
6
- import type { ScheduledTasksMeta } from '@pikku/core/ecosystem/scheduler';
7
- import type { TriggerMeta, TriggerSourceMeta } from '@pikku/core/ecosystem/trigger';
8
- import type { QueueWorkersMeta } from '@pikku/core/ecosystem/queue';
9
- import type { WorkflowsMeta } from '@pikku/core/ecosystem/workflow';
10
- import type { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/ecosystem/mcp';
11
- import type { AgentsMeta } from '@pikku/core/ecosystem/agent';
12
- import type { ScorerMeta } from '@pikku/core/ecosystem/agent-scorer';
13
- import type { CLIMeta } from '@pikku/core/ecosystem/cli';
14
- import type { CLICommandMeta } from '@pikku/core/ecosystem/cli';
15
- import type { NodesMeta } from '@pikku/core/ecosystem/node';
16
- import type { SecretDefinitions } from '@pikku/core/ecosystem/secret';
17
- import type { CredentialDefinitions } from '@pikku/core/ecosystem/credential';
18
- import type { ScopeDefinitions } from '@pikku/core/ecosystem/scope';
19
- import type { SystemRoleDefinitions } from '@pikku/core/ecosystem/role';
20
- import type { PersonaDefinitions } from '@pikku/core/ecosystem/persona';
21
- import type { VariableDefinitions } from '@pikku/core/ecosystem/variable';
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/ecosystem/services';
24
- import type { FunctionServicesMeta, FunctionWiresMeta, JSONValue } from '@pikku/core/ecosystem/types';
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.functions.meta[route.refTarget ?? route.pikkuFuncId];
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) && ts.isPropertyAccessExpression(node.expression)) {
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/ecosystem/services';
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/ecosystem/services';
2
- import type { JSONValue } from '@pikku/core/ecosystem/types';
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,4 +1,4 @@
1
- import { parseVersionedId } from '@pikku/core/ecosystem/types';
1
+ import { parseVersionedId } from '@pikku/core/utils';
2
2
  import { ErrorCode } from '../error-codes.js';
3
3
  import { canonicalJSON, hashString } from './hash.js';
4
4
  function isHashEntry(v) {
@@ -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/ecosystem/types';
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/ecosystem/types';
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
+ };