@pikku/inspector 0.12.34 → 0.12.35
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 +15 -0
- package/dist/add/add-http-route.js +4 -1
- package/dist/add/add-rpc-invocations.js +7 -0
- package/dist/inspector.js +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils/filter-inspector-state.js +43 -0
- package/dist/utils/load-addon-functions-meta.js +20 -14
- package/dist/utils/post-process.js +38 -13
- package/dist/utils/serialize-inspector-state.d.ts +1 -0
- package/dist/utils/serialize-inspector-state.js +2 -0
- package/package.json +2 -2
- package/run-tests.sh +4 -4
- package/src/add/add-http-route.ts +4 -1
- package/src/add/add-rpc-invocations.test.ts +113 -0
- package/src/add/add-rpc-invocations.ts +8 -0
- package/src/inspector.ts +1 -0
- package/src/types.ts +1 -0
- package/src/utils/filter-inspector-state.test.ts +231 -0
- package/src/utils/filter-inspector-state.ts +56 -0
- package/src/utils/load-addon-functions-meta.ts +26 -16
- package/src/utils/post-process.test.ts +254 -0
- package/src/utils/post-process.ts +41 -14
- package/src/utils/serialize-inspector-state.ts +11 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## 0.12.35
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 7ebea62: Tree-shake addon registrations in filtered inspector states (per-unit deploy codegen).
|
|
6
|
+
- `filterInspectorState` drops an addon's `wireAddonDeclarations`/`usedAddons` unless something kept actually references it (kept wiring targeting `namespace:*`, kept agent/MCP tool, or a body-level `rpc.invoke('namespace:*')` from a file that still contains a kept function). The generated per-unit bootstrap no longer imports unused addon package bootstraps — previously every deploy unit registered every addon's entire function surface, which pulled dev-only code (e.g. `@pikku/addon-console`'s static `node:fs` imports) into Cloudflare Worker bundles and failed upload with `No such module "node:fs"`.
|
|
7
|
+
- Body-level `rpc.invoke()` targets are now tracked per source file (`rpc.invokedFunctionsByFile`) so wiring-level `ref()` targets no longer pin an addon into every unit.
|
|
8
|
+
- `aggregateRequiredServices` computes addon parent services per used addon function (from the addon's shipped per-function `services` meta) instead of blanket-adding `addonRequiredParentServices` — and matches namespaced ids only, so bare project function names colliding with addon function names no longer force the blanket.
|
|
9
|
+
- Addon builds keep per-function `services` in the shipped `pikku-functions-meta.gen.json` so parent projects can do the above; addons built before this fall back to the blanket.
|
|
10
|
+
- HTTP route meta records `refTarget` for `ref('namespace:fn')`-wired routes, so per-unit filtering keeps the addon registration (and only that function's services) when the route deploys.
|
|
11
|
+
|
|
12
|
+
- Updated dependencies [7ebea62]
|
|
13
|
+
- Updated dependencies [e57dd65]
|
|
14
|
+
- @pikku/core@0.12.51
|
|
15
|
+
|
|
1
16
|
## 0.12.34
|
|
2
17
|
|
|
3
18
|
### Patch Changes
|
|
@@ -220,8 +220,10 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
|
|
|
220
220
|
const middleware = resolveHTTPMiddlewareFromObject(state, fullRoute, obj, tags, checker);
|
|
221
221
|
// Resolve permissions
|
|
222
222
|
const permissions = resolveHTTPPermissionsFromObject(state, fullRoute, obj, tags, checker);
|
|
223
|
-
// Track used functions/middleware/permissions for service aggregation
|
|
224
223
|
state.serviceAggregation.usedFunctions.add(funcName);
|
|
224
|
+
if (refAddonTarget) {
|
|
225
|
+
state.serviceAggregation.usedFunctions.add(refAddonTarget);
|
|
226
|
+
}
|
|
225
227
|
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
226
228
|
extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
|
|
227
229
|
// Check for SSE
|
|
@@ -232,6 +234,7 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
|
|
|
232
234
|
state.http.files.add(sourceFile.fileName);
|
|
233
235
|
state.http.meta[method][fullRoute] = {
|
|
234
236
|
pikkuFuncId: funcName,
|
|
237
|
+
...(refAddonTarget && { refTarget: refAddonTarget }),
|
|
235
238
|
...(packageName && { packageName }),
|
|
236
239
|
route: fullRoute,
|
|
237
240
|
sourceFile: sourceFile.fileName,
|
|
@@ -85,10 +85,17 @@ export function addRPCInvocations(node, state, logger) {
|
|
|
85
85
|
const functionRef = firstArg.text;
|
|
86
86
|
logger.debug(`• Found RPC invocation: ${functionRef}`);
|
|
87
87
|
state.rpc.invokedFunctions.add(functionRef);
|
|
88
|
+
let byFile = state.rpc.invokedFunctionsByFile.get(sourceFileName);
|
|
89
|
+
if (!byFile) {
|
|
90
|
+
byFile = new Set();
|
|
91
|
+
state.rpc.invokedFunctionsByFile.set(sourceFileName, byFile);
|
|
92
|
+
}
|
|
93
|
+
byFile.add(functionRef);
|
|
88
94
|
const namespace = extractNamespace(functionRef);
|
|
89
95
|
if (namespace) {
|
|
90
96
|
logger.debug(` → Addon detected: ${namespace}`);
|
|
91
97
|
state.rpc.usedAddons.add(namespace);
|
|
98
|
+
state.serviceAggregation.usedFunctions.add(functionRef);
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
// Handle template literals like `function-${name}`
|
package/dist/inspector.js
CHANGED
|
@@ -93,6 +93,7 @@ export function getInitialInspectorState(rootDir) {
|
|
|
93
93
|
exposedMeta: {},
|
|
94
94
|
exposedFiles: new Map(),
|
|
95
95
|
invokedFunctions: new Set(),
|
|
96
|
+
invokedFunctionsByFile: new Map(),
|
|
96
97
|
usedAddons: new Set(),
|
|
97
98
|
wireAddonDeclarations: new Map(),
|
|
98
99
|
wireAddonFiles: new Set(),
|
package/dist/types.d.ts
CHANGED
|
@@ -366,6 +366,9 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
366
366
|
// Track used functions/middleware/permissions
|
|
367
367
|
if (routeMeta.pikkuFuncId) {
|
|
368
368
|
filteredState.serviceAggregation.usedFunctions.add(routeMeta.pikkuFuncId);
|
|
369
|
+
if (routeMeta.refTarget) {
|
|
370
|
+
filteredState.serviceAggregation.usedFunctions.add(routeMeta.refTarget);
|
|
371
|
+
}
|
|
369
372
|
// For workflow/agent routes, also add the base name
|
|
370
373
|
// so the workflow/agent definition survives pruning
|
|
371
374
|
const colonIdx = routeMeta.pikkuFuncId.indexOf(':');
|
|
@@ -796,6 +799,46 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
796
799
|
if (filteredState.serviceAggregation.requiredServices.has('workflowService')) {
|
|
797
800
|
filteredState.serviceAggregation.requiredServices.add('queueService');
|
|
798
801
|
}
|
|
802
|
+
if ((filteredState.rpc.wireAddonDeclarations?.size ?? 0) > 0) {
|
|
803
|
+
const referencedIds = new Set(filteredState.serviceAggregation.usedFunctions);
|
|
804
|
+
const keptFiles = new Set();
|
|
805
|
+
for (const funcId of filteredState.serviceAggregation.usedFunctions) {
|
|
806
|
+
const file = filteredState.functions.files.get(funcId);
|
|
807
|
+
if (file?.path)
|
|
808
|
+
keptFiles.add(file.path);
|
|
809
|
+
}
|
|
810
|
+
for (const [file, invoked] of state.rpc.invokedFunctionsByFile ??
|
|
811
|
+
new Map()) {
|
|
812
|
+
if (!keptFiles.has(file))
|
|
813
|
+
continue;
|
|
814
|
+
for (const id of invoked) {
|
|
815
|
+
referencedIds.add(id);
|
|
816
|
+
if (id.includes(':')) {
|
|
817
|
+
filteredState.serviceAggregation.usedFunctions.add(id);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
for (const toolMeta of Object.values(filteredState.mcpEndpoints?.toolsMeta ?? {})) {
|
|
822
|
+
if (toolMeta.pikkuFuncId)
|
|
823
|
+
referencedIds.add(toolMeta.pikkuFuncId);
|
|
824
|
+
}
|
|
825
|
+
for (const agentMeta of Object.values(filteredState.agents?.agentsMeta ?? {})) {
|
|
826
|
+
for (const tool of agentMeta.tools ?? [])
|
|
827
|
+
referencedIds.add(tool);
|
|
828
|
+
}
|
|
829
|
+
const keptNamespaces = new Set();
|
|
830
|
+
for (const namespace of filteredState.rpc.wireAddonDeclarations.keys()) {
|
|
831
|
+
const prefix = `${namespace}:`;
|
|
832
|
+
for (const id of referencedIds) {
|
|
833
|
+
if (id.startsWith(prefix)) {
|
|
834
|
+
keptNamespaces.add(namespace);
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
filteredState.rpc.wireAddonDeclarations = new Map([...filteredState.rpc.wireAddonDeclarations].filter(([namespace]) => keptNamespaces.has(namespace)));
|
|
840
|
+
filteredState.rpc.usedAddons = new Set([...filteredState.rpc.usedAddons].filter((namespace) => keptNamespaces.has(namespace)));
|
|
841
|
+
}
|
|
799
842
|
// Recalculate requiredServices based on filtered functions/middleware/permissions
|
|
800
843
|
// Need to cast to InspectorState temporarily for aggregateRequiredServices
|
|
801
844
|
const stateForAggregation = filteredState;
|
|
@@ -135,6 +135,7 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
135
135
|
// No variables meta — that's fine
|
|
136
136
|
}
|
|
137
137
|
// Load addon serverlessIncompatible service names from pikku-addon-meta.gen.json
|
|
138
|
+
let loadedParentServices = false;
|
|
138
139
|
try {
|
|
139
140
|
const addonMetaPath = require.resolve(`${decl.package}/.pikku/console/pikku-addon-meta.gen.json`);
|
|
140
141
|
const addonMetaRaw = await readFile(addonMetaPath, 'utf-8');
|
|
@@ -144,24 +145,29 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
144
145
|
state.addonServerlessIncompatible.set(namespace, addonMeta.serverlessIncompatible);
|
|
145
146
|
logger.debug(`Addon '${namespace}' marks [${addonMeta.serverlessIncompatible.join(', ')}] as serverless-incompatible`);
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
// Load addon required parent services from pikku-services.gen
|
|
152
|
-
try {
|
|
153
|
-
const servicesGenPath = require.resolve(`${decl.package}/.pikku/pikku-services.gen.js`);
|
|
154
|
-
const servicesModule = await import(servicesGenPath);
|
|
155
|
-
if (servicesModule.requiredParentServices &&
|
|
156
|
-
Array.isArray(servicesModule.requiredParentServices)) {
|
|
157
|
-
for (const service of servicesModule.requiredParentServices) {
|
|
148
|
+
if (Array.isArray(addonMeta.requiredParentServices) &&
|
|
149
|
+
addonMeta.requiredParentServices.length > 0) {
|
|
150
|
+
for (const service of addonMeta.requiredParentServices) {
|
|
158
151
|
state.addonRequiredParentServices.push(service);
|
|
159
152
|
}
|
|
160
|
-
|
|
153
|
+
loadedParentServices = true;
|
|
154
|
+
logger.debug(`Loaded ${addonMeta.requiredParentServices.length} required parent services for '${namespace}' from addon meta`);
|
|
161
155
|
}
|
|
162
156
|
}
|
|
163
|
-
catch {
|
|
164
|
-
|
|
157
|
+
catch { }
|
|
158
|
+
if (!loadedParentServices) {
|
|
159
|
+
try {
|
|
160
|
+
const servicesGenPath = require.resolve(`${decl.package}/.pikku/pikku-services.gen.js`);
|
|
161
|
+
const servicesModule = await import(servicesGenPath);
|
|
162
|
+
if (servicesModule.requiredParentServices &&
|
|
163
|
+
Array.isArray(servicesModule.requiredParentServices)) {
|
|
164
|
+
for (const service of servicesModule.requiredParentServices) {
|
|
165
|
+
state.addonRequiredParentServices.push(service);
|
|
166
|
+
}
|
|
167
|
+
logger.debug(`Loaded ${servicesModule.requiredParentServices.length} required parent services for '${namespace}' from ${decl.package}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch { }
|
|
165
171
|
}
|
|
166
172
|
try {
|
|
167
173
|
const httpContractsPath = require.resolve(`${decl.package}/.pikku/http/pikku-http-contracts-meta.gen.json`);
|
|
@@ -253,19 +253,44 @@ export function aggregateRequiredServices(state) {
|
|
|
253
253
|
requiredServices.add('eventHub');
|
|
254
254
|
}
|
|
255
255
|
// 7. Services that consumed addons need from the parent project.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
256
|
+
const addonFnServices = new Map();
|
|
257
|
+
for (const [namespace, fns] of Object.entries(state.addonFunctions ?? {})) {
|
|
258
|
+
for (const [id, meta] of Object.entries(fns)) {
|
|
259
|
+
addonFnServices.set(`${namespace}:${id}`, meta?.services?.services);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const parentDeclared = state.addonRequiredParentServices ?? [];
|
|
263
|
+
const parentDeclaredSet = new Set(parentDeclared);
|
|
264
|
+
const defaultServices = new Set([
|
|
265
|
+
'config',
|
|
266
|
+
'logger',
|
|
267
|
+
'variables',
|
|
268
|
+
'schema',
|
|
269
|
+
'secrets',
|
|
270
|
+
]);
|
|
271
|
+
let usesAddonFn = false;
|
|
272
|
+
let addonFactoryNeeded = false;
|
|
273
|
+
for (const funcId of usedFunctions) {
|
|
274
|
+
if (!addonFnServices.has(funcId))
|
|
275
|
+
continue;
|
|
276
|
+
usesAddonFn = true;
|
|
277
|
+
const services = addonFnServices.get(funcId);
|
|
278
|
+
if (!services) {
|
|
279
|
+
addonFactoryNeeded = true;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
for (const service of services) {
|
|
283
|
+
if (parentDeclaredSet.has(service)) {
|
|
284
|
+
requiredServices.add(service);
|
|
285
|
+
}
|
|
286
|
+
else if (!internalServices.has(service) &&
|
|
287
|
+
!defaultServices.has(service)) {
|
|
288
|
+
addonFactoryNeeded = true;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (usesAddonFn && addonFactoryNeeded) {
|
|
293
|
+
for (const service of parentDeclared) {
|
|
269
294
|
requiredServices.add(service);
|
|
270
295
|
}
|
|
271
296
|
}
|
|
@@ -78,6 +78,7 @@ export function serializeInspectorState(state) {
|
|
|
78
78
|
exposedMeta: state.rpc.exposedMeta,
|
|
79
79
|
exposedFiles: Array.from(state.rpc.exposedFiles.entries()),
|
|
80
80
|
invokedFunctions: Array.from(state.rpc.invokedFunctions),
|
|
81
|
+
invokedFunctionsByFile: Array.from((state.rpc.invokedFunctionsByFile ?? new Map()).entries()).map(([file, fns]) => [file, Array.from(fns)]),
|
|
81
82
|
usedAddons: Array.from(state.rpc.usedAddons),
|
|
82
83
|
wireAddonDeclarations: Array.from(state.rpc.wireAddonDeclarations.entries()),
|
|
83
84
|
wireAddonFiles: Array.from(state.rpc.wireAddonFiles),
|
|
@@ -235,6 +236,7 @@ export function deserializeInspectorState(data) {
|
|
|
235
236
|
exposedMeta: data.rpc.exposedMeta,
|
|
236
237
|
exposedFiles: new Map(data.rpc.exposedFiles),
|
|
237
238
|
invokedFunctions: new Set(data.rpc.invokedFunctions),
|
|
239
|
+
invokedFunctionsByFile: new Map((data.rpc.invokedFunctionsByFile || []).map(([file, fns]) => [file, new Set(fns)])),
|
|
238
240
|
usedAddons: new Set(data.rpc.usedAddons || []),
|
|
239
241
|
wireAddonDeclarations: new Map(data.rpc.wireAddonDeclarations || []),
|
|
240
242
|
wireAddonFiles: new Set(data.rpc.wireAddonFiles || []),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.35",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
|
|
38
|
-
"@pikku/core": "^0.12.
|
|
38
|
+
"@pikku/core": "^0.12.51",
|
|
39
39
|
"openapi-types": "^12.1.3",
|
|
40
40
|
"path-to-regexp": "^8.3.0",
|
|
41
41
|
"ts-json-schema-generator": "2.9.1-next.16",
|
package/run-tests.sh
CHANGED
|
@@ -38,16 +38,16 @@ if [ ${#files[@]} -eq 0 ]; then
|
|
|
38
38
|
fi
|
|
39
39
|
|
|
40
40
|
# Construct the node command
|
|
41
|
-
node_cmd=
|
|
41
|
+
node_cmd=(node --import tsx --test)
|
|
42
42
|
|
|
43
43
|
# Append options based on flags
|
|
44
44
|
if [ "$watch_mode" = true ]; then
|
|
45
|
-
node_cmd
|
|
45
|
+
node_cmd+=(--watch)
|
|
46
46
|
fi
|
|
47
47
|
|
|
48
48
|
if [ "$coverage_mode" = true ]; then
|
|
49
|
-
node_cmd
|
|
49
|
+
node_cmd+=(--test-coverage-include="src/**/*.{ts,js}" --test-coverage-exclude="**/dist/**" --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info)
|
|
50
50
|
fi
|
|
51
51
|
|
|
52
52
|
# Execute the node command with the expanded list of files
|
|
53
|
-
$node_cmd "${files[@]}"
|
|
53
|
+
"${node_cmd[@]}" "${files[@]}"
|
|
@@ -389,8 +389,10 @@ export function registerHTTPRoute({
|
|
|
389
389
|
checker
|
|
390
390
|
)
|
|
391
391
|
|
|
392
|
-
// Track used functions/middleware/permissions for service aggregation
|
|
393
392
|
state.serviceAggregation.usedFunctions.add(funcName)
|
|
393
|
+
if (refAddonTarget) {
|
|
394
|
+
state.serviceAggregation.usedFunctions.add(refAddonTarget)
|
|
395
|
+
}
|
|
394
396
|
extractWireNames(middleware).forEach((name) =>
|
|
395
397
|
state.serviceAggregation.usedMiddleware.add(name)
|
|
396
398
|
)
|
|
@@ -415,6 +417,7 @@ export function registerHTTPRoute({
|
|
|
415
417
|
state.http.files.add(sourceFile.fileName)
|
|
416
418
|
state.http.meta[method][fullRoute] = {
|
|
417
419
|
pikkuFuncId: funcName,
|
|
420
|
+
...(refAddonTarget && { refTarget: refAddonTarget }),
|
|
418
421
|
...(packageName && { packageName }),
|
|
419
422
|
route: fullRoute,
|
|
420
423
|
sourceFile: sourceFile.fileName,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { strict as assert } from 'assert'
|
|
2
|
+
import { describe, test } from 'node:test'
|
|
3
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { inspect } from '../inspector.js'
|
|
7
|
+
import type { InspectorLogger, InspectorState } from '../types.js'
|
|
8
|
+
|
|
9
|
+
function makeLogger(): InspectorLogger {
|
|
10
|
+
return {
|
|
11
|
+
debug: () => {},
|
|
12
|
+
info: () => {},
|
|
13
|
+
warn: () => {},
|
|
14
|
+
error: () => {},
|
|
15
|
+
diagnostic: () => {},
|
|
16
|
+
critical: () => {},
|
|
17
|
+
hasCriticalErrors: () => false,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function inspectFiles(
|
|
22
|
+
files: Record<string, string>
|
|
23
|
+
): Promise<{ state: InspectorState; dir: string }> {
|
|
24
|
+
const dir = await mkdtemp(join(tmpdir(), 'pikku-rpc-invocations-test-'))
|
|
25
|
+
const paths: string[] = []
|
|
26
|
+
for (const [name, source] of Object.entries(files)) {
|
|
27
|
+
const path = join(dir, name)
|
|
28
|
+
await writeFile(path, source)
|
|
29
|
+
paths.push(path)
|
|
30
|
+
}
|
|
31
|
+
const state = await inspect(makeLogger(), paths, { rootDir: dir })
|
|
32
|
+
return { state, dir }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('add-rpc-invocations — invokedFunctionsByFile', () => {
|
|
36
|
+
test('body-level rpc.invoke() is attributed to its source file', async () => {
|
|
37
|
+
const { state, dir } = await inspectFiles({
|
|
38
|
+
'caller.ts': `
|
|
39
|
+
declare const rpc: { invoke: (name: string, data?: unknown) => Promise<unknown> }
|
|
40
|
+
export async function doWork() {
|
|
41
|
+
return rpc.invoke('console:getSchema')
|
|
42
|
+
}
|
|
43
|
+
`,
|
|
44
|
+
'other.ts': `
|
|
45
|
+
export const unrelated = () => 'no invocations here'
|
|
46
|
+
`,
|
|
47
|
+
})
|
|
48
|
+
try {
|
|
49
|
+
assert.ok(state.rpc.invokedFunctions.has('console:getSchema'))
|
|
50
|
+
const byFile = state.rpc.invokedFunctionsByFile
|
|
51
|
+
assert.strictEqual(byFile.size, 1)
|
|
52
|
+
const [file, invoked] = [...byFile.entries()][0]!
|
|
53
|
+
assert.ok(file.endsWith('caller.ts'))
|
|
54
|
+
assert.deepStrictEqual([...invoked], ['console:getSchema'])
|
|
55
|
+
} finally {
|
|
56
|
+
await rm(dir, { recursive: true, force: true })
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('namespaced body invoke joins serviceAggregation.usedFunctions', async () => {
|
|
61
|
+
const { state, dir } = await inspectFiles({
|
|
62
|
+
'caller.ts': `
|
|
63
|
+
declare const rpc: { invoke: (name: string, data?: unknown) => Promise<unknown> }
|
|
64
|
+
export async function doWork() {
|
|
65
|
+
await rpc.invoke('ext:goodbye')
|
|
66
|
+
return rpc.invoke('localHelper')
|
|
67
|
+
}
|
|
68
|
+
`,
|
|
69
|
+
})
|
|
70
|
+
try {
|
|
71
|
+
assert.ok(state.serviceAggregation.usedFunctions.has('ext:goodbye'))
|
|
72
|
+
assert.ok(!state.serviceAggregation.usedFunctions.has('localHelper'))
|
|
73
|
+
} finally {
|
|
74
|
+
await rm(dir, { recursive: true, force: true })
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('multiple invocations in one file accumulate under that file', async () => {
|
|
79
|
+
const { state, dir } = await inspectFiles({
|
|
80
|
+
'caller.ts': `
|
|
81
|
+
declare const rpc: { invoke: (name: string, data?: unknown) => Promise<unknown> }
|
|
82
|
+
export async function doWork() {
|
|
83
|
+
await rpc.invoke('console:getSchema')
|
|
84
|
+
return rpc.invoke('listTasks')
|
|
85
|
+
}
|
|
86
|
+
`,
|
|
87
|
+
})
|
|
88
|
+
try {
|
|
89
|
+
const invoked = [...state.rpc.invokedFunctionsByFile.values()][0]!
|
|
90
|
+
assert.deepStrictEqual([...invoked].sort(), [
|
|
91
|
+
'console:getSchema',
|
|
92
|
+
'listTasks',
|
|
93
|
+
])
|
|
94
|
+
} finally {
|
|
95
|
+
await rm(dir, { recursive: true, force: true })
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('wiring-level ref() lands in invokedFunctions but NOT in the by-file map', async () => {
|
|
100
|
+
const { state, dir } = await inspectFiles({
|
|
101
|
+
'wiring.ts': `
|
|
102
|
+
declare const ref: (name: string) => unknown
|
|
103
|
+
export const routes = { stream: { func: ref('console:streamWorkflowRun') } }
|
|
104
|
+
`,
|
|
105
|
+
})
|
|
106
|
+
try {
|
|
107
|
+
assert.ok(state.rpc.invokedFunctions.has('console:streamWorkflowRun'))
|
|
108
|
+
assert.strictEqual(state.rpc.invokedFunctionsByFile.size, 0)
|
|
109
|
+
} finally {
|
|
110
|
+
await rm(dir, { recursive: true, force: true })
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
})
|
|
@@ -115,10 +115,18 @@ export function addRPCInvocations(
|
|
|
115
115
|
logger.debug(`• Found RPC invocation: ${functionRef}`)
|
|
116
116
|
state.rpc.invokedFunctions.add(functionRef)
|
|
117
117
|
|
|
118
|
+
let byFile = state.rpc.invokedFunctionsByFile.get(sourceFileName)
|
|
119
|
+
if (!byFile) {
|
|
120
|
+
byFile = new Set()
|
|
121
|
+
state.rpc.invokedFunctionsByFile.set(sourceFileName, byFile)
|
|
122
|
+
}
|
|
123
|
+
byFile.add(functionRef)
|
|
124
|
+
|
|
118
125
|
const namespace = extractNamespace(functionRef)
|
|
119
126
|
if (namespace) {
|
|
120
127
|
logger.debug(` → Addon detected: ${namespace}`)
|
|
121
128
|
state.rpc.usedAddons.add(namespace)
|
|
129
|
+
state.serviceAggregation.usedFunctions.add(functionRef)
|
|
122
130
|
}
|
|
123
131
|
}
|
|
124
132
|
// Handle template literals like `function-${name}`
|
package/src/inspector.ts
CHANGED
|
@@ -124,6 +124,7 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
124
124
|
exposedMeta: {},
|
|
125
125
|
exposedFiles: new Map(),
|
|
126
126
|
invokedFunctions: new Set(),
|
|
127
|
+
invokedFunctionsByFile: new Map(),
|
|
127
128
|
usedAddons: new Set(),
|
|
128
129
|
wireAddonDeclarations: new Map(),
|
|
129
130
|
wireAddonFiles: new Set(),
|
package/src/types.ts
CHANGED
|
@@ -482,6 +482,7 @@ export interface InspectorState {
|
|
|
482
482
|
exposedMeta: Record<string, string>
|
|
483
483
|
exposedFiles: Map<string, { path: string; exportedName: string }>
|
|
484
484
|
invokedFunctions: Set<string>
|
|
485
|
+
invokedFunctionsByFile: Map<string, Set<string>>
|
|
485
486
|
usedAddons: Set<string>
|
|
486
487
|
wireAddonDeclarations: Map<
|
|
487
488
|
string,
|