@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 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
@@ -413,6 +413,7 @@ export interface InspectorState {
413
413
  exportedName: string;
414
414
  }>;
415
415
  invokedFunctions: Set<string>;
416
+ invokedFunctionsByFile: Map<string, Set<string>>;
416
417
  usedAddons: Set<string>;
417
418
  wireAddonDeclarations: Map<string, {
418
419
  package: string;
@@ -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
- catch {
149
- // No addon meta or no serverlessIncompatible declared — that's fine
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
- logger.debug(`Loaded ${servicesModule.requiredParentServices.length} required parent services for '${namespace}' from ${decl.package}`);
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
- // No services gen — addon may not have requiredParentServices
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
- // These are required ONLY by units that actually deploy an addon function;
257
- // a unit that merely calls the addon over RPC (or never touches it) must not
258
- // carry them, or every per-unit bundle would over-include the addon's
259
- // parent-service dependencies (e.g. aiAgentRunner, deploymentService) and
260
- // defeat per-unit tree-shaking.
261
- const addonFuncIds = new Set();
262
- for (const fns of Object.values(state.addonFunctions ?? {})) {
263
- for (const id of Object.keys(fns))
264
- addonFuncIds.add(id);
265
- }
266
- const unitDeploysAddonFn = [...usedFunctions].some((fn) => addonFuncIds.has(fn));
267
- if (unitDeploysAddonFn) {
268
- for (const service of state.addonRequiredParentServices ?? []) {
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
  }
@@ -178,6 +178,7 @@ export interface SerializableInspectorState {
178
178
  exportedName: string;
179
179
  }]>;
180
180
  invokedFunctions: string[];
181
+ invokedFunctionsByFile?: Array<[string, string[]]>;
181
182
  usedAddons: string[];
182
183
  wireAddonDeclarations: Array<[
183
184
  string,
@@ -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.34",
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.50",
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="node --import tsx --test"
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="$node_cmd --watch"
45
+ node_cmd+=(--watch)
46
46
  fi
47
47
 
48
48
  if [ "$coverage_mode" = true ]; then
49
- node_cmd="$node_cmd --test-coverage-include=\"src/**/*.{ts,js}\" --test-coverage-exclude=\"**/dist/**\" --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info"
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,