@pikku/inspector 0.12.41 → 0.12.43

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 (108) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/add/add-addon-bans.js +1 -0
  3. package/dist/add/add-ai-agent.d.ts +3 -1
  4. package/dist/add/add-ai-agent.js +82 -0
  5. package/dist/add/add-credential.js +3 -1
  6. package/dist/add/add-functions.js +10 -9
  7. package/dist/add/add-gateway.js +3 -0
  8. package/dist/add/add-http-route.js +2 -2
  9. package/dist/add/add-mcp-prompt.js +0 -1
  10. package/dist/add/add-mcp-resource.js +0 -1
  11. package/dist/add/add-permission.d.ts +1 -1
  12. package/dist/add/add-permission.js +3 -177
  13. package/dist/add/add-queue-worker.js +3 -0
  14. package/dist/add/add-schedule.js +3 -0
  15. package/dist/add/add-scope.d.ts +2 -0
  16. package/dist/add/add-scope.js +146 -0
  17. package/dist/add/add-trigger.js +3 -0
  18. package/dist/add/add-wire-remote-addon.d.ts +10 -0
  19. package/dist/add/add-wire-remote-addon.js +66 -0
  20. package/dist/add/add-workflow-graph.js +32 -3
  21. package/dist/error-codes.d.ts +3 -0
  22. package/dist/error-codes.js +4 -0
  23. package/dist/inspector.js +19 -7
  24. package/dist/types.d.ts +19 -13
  25. package/dist/utils/custom-types-generator.js +2 -1
  26. package/dist/utils/ensure-function-metadata.d.ts +15 -0
  27. package/dist/utils/ensure-function-metadata.js +24 -0
  28. package/dist/utils/filter-inspector-state.js +9 -0
  29. package/dist/utils/get-property-value.d.ts +7 -2
  30. package/dist/utils/get-property-value.js +48 -1
  31. package/dist/utils/load-addon-functions-meta.js +81 -6
  32. package/dist/utils/nested-project-filter.d.ts +1 -0
  33. package/dist/utils/nested-project-filter.js +29 -0
  34. package/dist/utils/permissions.d.ts +1 -21
  35. package/dist/utils/permissions.js +17 -108
  36. package/dist/utils/post-process.d.ts +30 -1
  37. package/dist/utils/post-process.js +150 -72
  38. package/dist/utils/serialize-inspector-state.d.ts +13 -8
  39. package/dist/utils/serialize-inspector-state.js +12 -6
  40. package/dist/utils/serialize-permissions-groups-meta.d.ts +0 -2
  41. package/dist/utils/serialize-permissions-groups-meta.js +0 -26
  42. package/dist/utils/workflow/derive-workflow-plan.js +9 -3
  43. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +56 -1
  44. package/dist/utils/workflow/dsl/patterns.d.ts +10 -0
  45. package/dist/utils/workflow/dsl/patterns.js +24 -8
  46. package/dist/utils/workflow/dsl/validation.js +3 -4
  47. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +14 -0
  48. package/dist/utils/workflow/graph/finalize-workflows.d.ts +7 -0
  49. package/dist/utils/workflow/graph/finalize-workflows.js +11 -2
  50. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +2 -0
  51. package/dist/utils/workflow/graph/serialize-workflow-graph.js +4 -0
  52. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +6 -2
  53. package/dist/visit.js +4 -0
  54. package/package.json +3 -3
  55. package/src/add/add-addon-bans.ts +1 -0
  56. package/src/add/add-ai-agent.test.ts +87 -0
  57. package/src/add/add-ai-agent.ts +122 -0
  58. package/src/add/add-credential.ts +6 -0
  59. package/src/add/add-functions.ts +10 -12
  60. package/src/add/add-gateway.ts +11 -0
  61. package/src/add/add-http-route.ts +2 -8
  62. package/src/add/add-mcp-prompt.ts +0 -1
  63. package/src/add/add-mcp-resource.ts +0 -1
  64. package/src/add/add-permission.ts +4 -242
  65. package/src/add/add-queue-worker.ts +11 -0
  66. package/src/add/add-schedule.ts +11 -0
  67. package/src/add/add-scope.test.ts +346 -0
  68. package/src/add/add-scope.ts +225 -0
  69. package/src/add/add-trigger.ts +11 -0
  70. package/src/add/add-wire-remote-addon.ts +77 -0
  71. package/src/add/add-workflow-graph-input.test.ts +94 -0
  72. package/src/add/add-workflow-graph-notes.test.ts +74 -0
  73. package/src/add/add-workflow-graph.ts +35 -3
  74. package/src/add/inline-wiring-function.test.ts +104 -0
  75. package/src/add/validate-workflow-graph-addons.test.ts +102 -0
  76. package/src/error-codes.ts +5 -0
  77. package/src/inspector.ts +22 -6
  78. package/src/types.ts +16 -17
  79. package/src/utils/custom-types-generator.test.ts +26 -1
  80. package/src/utils/custom-types-generator.ts +2 -1
  81. package/src/utils/ensure-function-metadata.ts +42 -0
  82. package/src/utils/filter-inspector-state.test.ts +0 -2
  83. package/src/utils/filter-inspector-state.ts +9 -0
  84. package/src/utils/get-property-value.test.ts +141 -0
  85. package/src/utils/get-property-value.ts +62 -1
  86. package/src/utils/load-addon-functions-meta.test.ts +157 -0
  87. package/src/utils/load-addon-functions-meta.ts +94 -6
  88. package/src/utils/load-addon-scopes.test.ts +138 -0
  89. package/src/utils/nested-project-filter.test.ts +53 -0
  90. package/src/utils/nested-project-filter.ts +28 -0
  91. package/src/utils/permissions.test.ts +7 -232
  92. package/src/utils/permissions.ts +20 -160
  93. package/src/utils/post-process.test.ts +269 -4
  94. package/src/utils/post-process.ts +216 -95
  95. package/src/utils/serialize-inspector-state.ts +22 -25
  96. package/src/utils/serialize-permissions-groups-meta.ts +0 -28
  97. package/src/utils/workflow/derive-workflow-plan.test.ts +41 -3
  98. package/src/utils/workflow/derive-workflow-plan.ts +11 -4
  99. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +66 -0
  100. package/src/utils/workflow/dsl/patterns.ts +34 -8
  101. package/src/utils/workflow/dsl/validation.ts +3 -4
  102. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +15 -0
  103. package/src/utils/workflow/graph/finalize-workflows.test.ts +50 -0
  104. package/src/utils/workflow/graph/finalize-workflows.ts +13 -2
  105. package/src/utils/workflow/graph/serialize-workflow-graph.ts +6 -0
  106. package/src/utils/workflow/graph/workflow-graph.types.ts +8 -0
  107. package/src/visit.ts +4 -0
  108. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,104 @@
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 { ErrorCode, InspectorLogger } from '../types.js'
8
+
9
+ const silentLogger = (): InspectorLogger => ({
10
+ debug: () => {},
11
+ info: () => {},
12
+ warn: () => {},
13
+ error: () => {},
14
+ diagnostic: () => {},
15
+ critical: (_code: ErrorCode, _message: string) => {},
16
+ hasCriticalErrors: () => false,
17
+ })
18
+
19
+ // A `func:` inlined into a wiring has no exported name, so addFunctions skips
20
+ // it and only the wiring's context-based id exists. The wiring visitor must
21
+ // register the function metadata under that id (via ensureInlineWiringFunction)
22
+ // or the transport resolves to nothing at runtime ("Missing generated metadata
23
+ // for ...") and, once registered, every invocation would 403 unless the
24
+ // sessionless flag is carried across from the helper used.
25
+ const WIRINGS = [
26
+ {
27
+ label: 'wireQueueWorker',
28
+ wire: 'wireQueueWorker',
29
+ props: "name: 'inline-queue',",
30
+ expectedId: 'queue:inline-queue',
31
+ },
32
+ {
33
+ label: 'wireScheduler',
34
+ wire: 'wireScheduler',
35
+ props: "name: 'inline-cron', schedule: '* * * * *',",
36
+ expectedId: 'scheduler:inline-cron',
37
+ },
38
+ {
39
+ label: 'wireTrigger',
40
+ wire: 'wireTrigger',
41
+ props: "name: 'inline-trigger',",
42
+ expectedId: 'trigger:inline-trigger',
43
+ },
44
+ {
45
+ label: 'wireGateway',
46
+ wire: 'wireGateway',
47
+ props: "name: 'inline-gw', type: 'slack',",
48
+ expectedId: 'gateway:inline-gw',
49
+ },
50
+ ]
51
+
52
+ const write = (wire: string, props: string, helper: string) =>
53
+ [
54
+ `import { ${helper}, ${wire} } from '@pikku/core'`,
55
+ `${wire}({`,
56
+ ` ${props}`,
57
+ ` func: ${helper}<{ url: string }, void>({ func: async () => {} }),`,
58
+ '})',
59
+ ].join('\n')
60
+
61
+ describe('inline wiring functions register metadata', () => {
62
+ for (const { label, wire, props, expectedId } of WIRINGS) {
63
+ test(`${label} registers a sessionless func inlined into it`, async () => {
64
+ const rootDir = await mkdtemp(join(tmpdir(), 'pikku-inline-'))
65
+ const file = join(rootDir, 'wire.ts')
66
+ await writeFile(file, write(wire, props, 'pikkuSessionlessFunc'))
67
+
68
+ try {
69
+ const state = await inspect(silentLogger(), [file], { rootDir })
70
+ const funcMeta = state.functions.meta[expectedId]
71
+ assert.ok(
72
+ funcMeta,
73
+ `${label}: metadata must exist under the id the wiring references`
74
+ )
75
+ assert.equal(
76
+ funcMeta!.sessionless,
77
+ true,
78
+ `${label}: pikkuSessionlessFunc must stay sessionless when inlined`
79
+ )
80
+ } finally {
81
+ await rm(rootDir, { recursive: true, force: true })
82
+ }
83
+ })
84
+
85
+ test(`${label} keeps an inlined pikkuFunc session-required`, async () => {
86
+ const rootDir = await mkdtemp(join(tmpdir(), 'pikku-inline-auth-'))
87
+ const file = join(rootDir, 'wire.ts')
88
+ await writeFile(file, write(wire, props, 'pikkuFunc'))
89
+
90
+ try {
91
+ const state = await inspect(silentLogger(), [file], { rootDir })
92
+ const funcMeta = state.functions.meta[expectedId]
93
+ assert.ok(funcMeta, `${label}: metadata must exist`)
94
+ assert.equal(
95
+ funcMeta!.sessionless,
96
+ false,
97
+ `${label}: pikkuFunc must not be marked sessionless`
98
+ )
99
+ } finally {
100
+ await rm(rootDir, { recursive: true, force: true })
101
+ }
102
+ })
103
+ }
104
+ })
@@ -0,0 +1,102 @@
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 } from '../types.js'
8
+ import { ErrorCode } from '../error-codes.js'
9
+
10
+ function makeLogger(
11
+ criticals: Array<{ code: string; message: string }>
12
+ ): InspectorLogger {
13
+ return {
14
+ debug: () => {},
15
+ info: () => {},
16
+ warn: () => {},
17
+ error: () => {},
18
+ diagnostic: () => {},
19
+ critical: (code, message) => criticals.push({ code, message }),
20
+ hasCriticalErrors: () => criticals.length > 0,
21
+ }
22
+ }
23
+
24
+ describe('validateWorkflowGraphAddons', () => {
25
+ test('a graph node referencing graph: without @pikku/addon-graph wired raises PKU642', async () => {
26
+ const rootDir = await mkdtemp(join(tmpdir(), 'pikku-graph-addon-'))
27
+ const stepFile = join(rootDir, 'my.steps.ts')
28
+ const graphFile = join(rootDir, 'my.graph.ts')
29
+
30
+ await writeFile(
31
+ stepFile,
32
+ [
33
+ "import { pikkuSessionlessFunc } from '@pikku/core'",
34
+ 'export const notify = pikkuSessionlessFunc({ func: async () => ({ sent: true }) })',
35
+ ].join('\n')
36
+ )
37
+ await writeFile(
38
+ graphFile,
39
+ [
40
+ "import { pikkuWorkflowGraph } from '@pikku/core/workflow'",
41
+ 'export const myGraph = pikkuWorkflowGraph({',
42
+ " name: 'my-graph',",
43
+ ' nodes: {',
44
+ " setFields: 'graph:editFields',",
45
+ " notify: 'notify',",
46
+ ' },',
47
+ " config: { setFields: { next: 'notify' }, notify: {} },",
48
+ '})',
49
+ ].join('\n')
50
+ )
51
+
52
+ try {
53
+ const criticals: Array<{ code: string; message: string }> = []
54
+ await inspect(makeLogger(criticals), [stepFile, graphFile], { rootDir })
55
+ const hit = criticals.find(
56
+ (c) => c.code === ErrorCode.WORKFLOW_GRAPH_ADDON_NOT_WIRED
57
+ )
58
+ assert.ok(hit, 'expected a WORKFLOW_GRAPH_ADDON_NOT_WIRED critical')
59
+ assert.match(hit!.message, /@pikku\/addon-graph is not wired/)
60
+ assert.match(hit!.message, /scaffold/)
61
+ } finally {
62
+ await rm(rootDir, { recursive: true, force: true })
63
+ }
64
+ })
65
+
66
+ test('a graph with only user RPCs raises no addon-graph critical', async () => {
67
+ const rootDir = await mkdtemp(join(tmpdir(), 'pikku-graph-addon-ok-'))
68
+ const stepFile = join(rootDir, 'my.steps.ts')
69
+ const graphFile = join(rootDir, 'my.graph.ts')
70
+
71
+ await writeFile(
72
+ stepFile,
73
+ [
74
+ "import { pikkuSessionlessFunc } from '@pikku/core'",
75
+ 'export const assess = pikkuSessionlessFunc({ func: async () => ({ ok: true }) })',
76
+ 'export const notify = pikkuSessionlessFunc({ func: async () => ({ sent: true }) })',
77
+ ].join('\n')
78
+ )
79
+ await writeFile(
80
+ graphFile,
81
+ [
82
+ "import { pikkuWorkflowGraph } from '@pikku/core/workflow'",
83
+ 'export const myGraph = pikkuWorkflowGraph({',
84
+ " name: 'my-graph',",
85
+ " nodes: { assess: 'assess', notify: 'notify' },",
86
+ " config: { assess: { next: 'notify' }, notify: {} },",
87
+ '})',
88
+ ].join('\n')
89
+ )
90
+
91
+ try {
92
+ const criticals: Array<{ code: string; message: string }> = []
93
+ await inspect(makeLogger(criticals), [stepFile, graphFile], { rootDir })
94
+ const hit = criticals.find(
95
+ (c) => c.code === ErrorCode.WORKFLOW_GRAPH_ADDON_NOT_WIRED
96
+ )
97
+ assert.equal(hit, undefined, 'no addon-graph critical expected')
98
+ } finally {
99
+ await rm(rootDir, { recursive: true, force: true })
100
+ }
101
+ })
102
+ })
@@ -24,6 +24,11 @@ export enum ErrorCode {
24
24
  EXPECT_EVENTUALLY_SCENARIO_ONLY = 'PKU675',
25
25
  WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = 'PKU600',
26
26
  INVALID_DSL_WORKFLOW = 'PKU641',
27
+ WORKFLOW_GRAPH_ADDON_NOT_WIRED = 'PKU642',
28
+
29
+ // Remote addon (wireRemoteAddon) validation
30
+ REMOTE_ADDON_NOT_DEV_DEPENDENCY = 'PKU338',
31
+ REMOTE_ADDON_AUTH_UNRESOLVED = 'PKU339',
27
32
 
28
33
  // Database schema codegen warnings
29
34
  DB_COLUMN_NAME_TYPE_CONTRADICTION = 'PKU480',
package/src/inspector.ts CHANGED
@@ -10,6 +10,7 @@ import type {
10
10
  } from './types.js'
11
11
  import { getFilesAndMethods } from './utils/get-files-and-methods.js'
12
12
  import { findCommonAncestor } from './utils/find-root-dir.js'
13
+ import { createNestedProjectFilter } from './utils/nested-project-filter.js'
13
14
  import {
14
15
  aggregateRequiredServices,
15
16
  stampAuthHandlerServices,
@@ -17,12 +18,16 @@ import {
17
18
  validateSecretOverrides,
18
19
  validateVariableOverrides,
19
20
  validateCredentialOverrides,
21
+ validateRemoteAddonDependencies,
22
+ validateRemoteAddonAuth,
23
+ validateScopeReferences,
20
24
  computeResolvedIOTypes,
21
25
  computeMiddlewareGroupsMeta,
22
26
  computePermissionsGroupsMeta,
23
27
  computeRequiredSchemas,
24
28
  computeDiagnostics,
25
29
  validateSchemaWiringSeparation,
30
+ validateWorkflowGraphAddons,
26
31
  } from './utils/post-process.js'
27
32
  import { generateOpenAPISpec } from './utils/serialize-openapi-json.js'
28
33
  import { pikkuState } from '@pikku/core/internal'
@@ -89,7 +94,6 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
89
94
  },
90
95
  files: new Set(),
91
96
  routeMiddleware: new Map(),
92
- routePermissions: new Map(),
93
97
  },
94
98
  channels: {
95
99
  files: new Set(),
@@ -124,6 +128,8 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
124
128
  internalFiles: new Map(),
125
129
  exposedMeta: {},
126
130
  exposedFiles: new Map(),
131
+ remoteMeta: {},
132
+ remoteFiles: new Map(),
127
133
  invokedFunctions: new Set(),
128
134
  invokedFunctionsByFile: new Map(),
129
135
  usedAddons: new Set(),
@@ -165,6 +171,10 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
165
171
  definitions: [],
166
172
  files: new Set(),
167
173
  },
174
+ scopes: {
175
+ definitions: [],
176
+ files: new Set(),
177
+ },
168
178
  variables: {
169
179
  definitions: [],
170
180
  files: new Set(),
@@ -190,7 +200,6 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
190
200
  permissions: {
191
201
  definitions: {},
192
202
  instances: {},
193
- tagPermissions: new Map(),
194
203
  },
195
204
  serviceAggregation: {
196
205
  requiredServices: new Set(),
@@ -214,8 +223,6 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
214
223
  },
215
224
  permissionsGroupsMeta: {
216
225
  definitions: {},
217
- httpGroups: {},
218
- tagGroups: {},
219
226
  },
220
227
  requiredSchemas: new Set(),
221
228
  openAPISpec: null,
@@ -272,19 +279,24 @@ export const inspect = async (
272
279
  const startSourceFiles = performance.now()
273
280
  // node_modules under rootDir (e.g. a locally-installed addon) is a
274
281
  // dependency, not project source — scanning it double-counts the addon's
275
- // own application types (CoreConfig/Services/SingletonServices).
282
+ // own application types (CoreConfig/Services/SingletonServices). Nested
283
+ // pikku projects (a workspace addon at packages/<addon>) are the same case:
284
+ // TS resolves the node_modules symlink to its realpath under rootDir, so
285
+ // the node_modules check alone misses them.
276
286
  // Sort by file name so the sweeps populate state in a stable order. The
277
287
  // program's own file order depends on glob + import-graph resolution, which
278
288
  // varies run to run — leaving generated meta keys (and anything serialized
279
289
  // in insertion order) non-reproducible across identical `pikku all` runs.
280
290
  // Safe because function registration is a dedicated pass (visitFunctions)
281
291
  // that completes before any order-sensitive wiring resolution in visitRoutes.
292
+ const isNestedProjectFile = createNestedProjectFilter(rootDir)
282
293
  const sourceFiles = program
283
294
  .getSourceFiles()
284
295
  .filter(
285
296
  (sf) =>
286
297
  sf.fileName.startsWith(rootDir) &&
287
- !sf.fileName.includes('/node_modules/')
298
+ !sf.fileName.includes('/node_modules/') &&
299
+ !isNestedProjectFile(sf.fileName)
288
300
  )
289
301
  .sort((a, b) =>
290
302
  a.fileName < b.fileName ? -1 : a.fileName > b.fileName ? 1 : 0
@@ -394,6 +406,7 @@ export const inspect = async (
394
406
  computePermissionsGroupsMeta(state)
395
407
  computeDiagnostics(state)
396
408
  validateSchemaWiringSeparation(logger, state)
409
+ validateWorkflowGraphAddons(logger, state)
397
410
 
398
411
  if (options.openAPI) {
399
412
  state.openAPISpec = await generateOpenAPISpec(
@@ -410,6 +423,9 @@ export const inspect = async (
410
423
  validateSecretOverrides(logger, state)
411
424
  validateVariableOverrides(logger, state)
412
425
  validateCredentialOverrides(logger, state)
426
+ validateRemoteAddonDependencies(logger, state)
427
+ validateRemoteAddonAuth(logger, state)
428
+ validateScopeReferences(logger, state)
413
429
  }
414
430
 
415
431
  state.program = program
package/src/types.ts CHANGED
@@ -17,6 +17,7 @@ import type { CLICommandMeta } from '@pikku/core/cli'
17
17
  import type { NodesMeta } from '@pikku/core/node'
18
18
  import type { SecretDefinitions } from '@pikku/core/secret'
19
19
  import type { CredentialDefinitions } from '@pikku/core/credential'
20
+ import type { ScopeDefinitions } from '@pikku/core/scope'
20
21
  import type { VariableDefinitions } from '@pikku/core/variable'
21
22
  import type { TypesMap } from './types-map.js'
22
23
  import type {
@@ -57,16 +58,6 @@ export interface MiddlewareGroupMeta {
57
58
  isFactory: boolean // true if wrapped in () => add...()
58
59
  }
59
60
 
60
- export interface PermissionGroupMeta {
61
- exportName: string | null // null if not exported
62
- sourceFile: string
63
- position: number
64
- services: FunctionServicesMeta
65
- count: number
66
- instanceIds: string[]
67
- isFactory: boolean // true if wrapped in () => add...()
68
- }
69
-
70
61
  export interface InspectorHTTPState {
71
62
  metaInputTypes: MetaInputTypes
72
63
  meta: HTTPWiringsMeta
@@ -75,10 +66,6 @@ export interface InspectorHTTPState {
75
66
  // Pattern '*' matches all routes (from addHTTPMiddleware('*', [...]))
76
67
  // Pattern '/api/*' matches specific routes (from addHTTPMiddleware('/api/*', [...]))
77
68
  routeMiddleware: Map<string, MiddlewareGroupMeta>
78
- // HTTP permission calls tracking - route pattern -> group metadata
79
- // Pattern '*' matches all routes (from addHTTPPermission('*', [...]))
80
- // Pattern '/api/*' matches specific routes (from addHTTPPermission('/api/*', [...]))
81
- routePermissions: Map<string, PermissionGroupMeta>
82
69
  }
83
70
 
84
71
  /**
@@ -241,7 +228,6 @@ export interface InspectorPermissionInstance {
241
228
  export interface InspectorPermissionState {
242
229
  definitions: Record<string, InspectorPermissionDefinition>
243
230
  instances: Record<string, InspectorPermissionInstance>
244
- tagPermissions: Map<string, PermissionGroupMeta>
245
231
  }
246
232
 
247
233
  export type InspectorFilters = {
@@ -487,6 +473,9 @@ export interface InspectorState {
487
473
  internalFiles: Map<string, { path: string; exportedName: string }>
488
474
  exposedMeta: Record<string, string>
489
475
  exposedFiles: Map<string, { path: string; exportedName: string }>
476
+ /** Functions marked `remote: true` — the surface a wireRemoteAddon consumer imports */
477
+ remoteMeta: Record<string, string>
478
+ remoteFiles: Map<string, { path: string; exportedName: string }>
490
479
  invokedFunctions: Set<string>
491
480
  invokedFunctionsByFile: Map<string, Set<string>>
492
481
  usedAddons: Set<string>
@@ -496,6 +485,14 @@ export interface InspectorState {
496
485
  package: string
497
486
  rpcEndpoint?: string
498
487
  mcp?: boolean
488
+ /** True when declared via `wireRemoteAddon` — import the addon's `.remote.gen` map, not `.internal.gen` */
489
+ remote?: boolean
490
+ /** wireRemoteAddon: whether an `auth` binding was supplied (vs a public surface) */
491
+ hasAuth?: boolean
492
+ /** wireRemoteAddon: statically-known `auth.credentialId`, for verify */
493
+ authCredentialId?: string
494
+ /** wireRemoteAddon: statically-known `auth.secretId`, for verify */
495
+ authSecretId?: string
499
496
  secretOverrides?: Record<string, string>
500
497
  variableOverrides?: Record<string, string>
501
498
  credentialOverrides?: Record<string, string>
@@ -549,6 +546,10 @@ export interface InspectorState {
549
546
  definitions: CredentialDefinitions
550
547
  files: Set<string>
551
548
  }
549
+ scopes: {
550
+ definitions: ScopeDefinitions
551
+ files: Set<string>
552
+ }
552
553
  variables: {
553
554
  definitions: VariableDefinitions
554
555
  files: Set<string>
@@ -584,8 +585,6 @@ export interface InspectorState {
584
585
  }
585
586
  permissionsGroupsMeta: {
586
587
  definitions: Record<string, InspectorPermissionDefinition>
587
- httpGroups: Record<string, PermissionGroupMeta>
588
- tagGroups: Record<string, PermissionGroupMeta>
589
588
  }
590
589
  requiredSchemas: Set<string>
591
590
  openAPISpec: Record<string, any> | null
@@ -1,6 +1,9 @@
1
1
  import { test, describe } from 'node:test'
2
2
  import { strict as assert } from 'node:assert'
3
- import { generateCustomTypes } from './custom-types-generator.js'
3
+ import {
4
+ generateCustomTypes,
5
+ sanitizeTypeName,
6
+ } from './custom-types-generator.js'
4
7
  import { TypesMap } from '../types-map.js'
5
8
 
6
9
  function makeTypesMap(
@@ -97,3 +100,25 @@ describe('generateCustomTypes — classification wrapper stripping', () => {
97
100
  )
98
101
  })
99
102
  })
103
+
104
+ describe('sanitizeTypeName — valid JS identifier', () => {
105
+ test('replaces disallowed characters with underscores', () => {
106
+ assert.equal(
107
+ sanitizeTypeName('Add Beehiiv subscribers'),
108
+ 'Add_Beehiiv_subscribers'
109
+ )
110
+ })
111
+
112
+ test('prefixes an underscore when the name starts with a digit', () => {
113
+ // A name like "2. Add Beehiiv newsletter subscribers" otherwise produces
114
+ // `2__Add_...`, an invalid JS identifier that breaks the generated
115
+ // `import <ident> from ...` in pikku-workflow-wirings-meta.gen.ts.
116
+ const id = sanitizeTypeName('2. Add Beehiiv newsletter subscribers')
117
+ assert.equal(id, '_2__Add_Beehiiv_newsletter_subscribers')
118
+ assert.match(id, /^[A-Za-z_$]/, 'must start with a valid identifier char')
119
+ })
120
+
121
+ test('leaves an already-valid identifier unchanged', () => {
122
+ assert.equal(sanitizeTypeName('UserEmail'), 'UserEmail')
123
+ })
124
+ })
@@ -8,7 +8,8 @@ import type { TypesMap } from '../types-map.js'
8
8
  */
9
9
 
10
10
  export function sanitizeTypeName(name: string): string {
11
- return name.replace(/[^a-zA-Z0-9_$]/g, '_')
11
+ const sanitized = name.replace(/[^a-zA-Z0-9_$]/g, '_')
12
+ return /^[0-9]/.test(sanitized) ? `_${sanitized}` : sanitized
12
13
  }
13
14
 
14
15
  const CLASSIFICATION_WRAPPERS = new Set(['Private', 'Pii', 'Secret'])
@@ -309,6 +309,48 @@ export function ensureFunctionMetadata(
309
309
  }
310
310
  }
311
311
 
312
+ /**
313
+ * Register metadata for a `func:` inlined into a wiring (queue/scheduler/
314
+ * trigger/gateway/…).
315
+ *
316
+ * An inline function has no exported name, so `addFunctions` skips it (it bails
317
+ * on `__temp_` ids) and only the wiring's context-based id exists. Without this
318
+ * the wiring points at a function that was never registered and the worker
319
+ * resolves to nothing at runtime ("Missing generated metadata for ...").
320
+ *
321
+ * The stub also carries no session info, so the runtime would treat it as
322
+ * session-required and every invocation would fail with "Authentication
323
+ * required". Derive `sessionless` from the helper the function was built with —
324
+ * the same rule `addFunctions` applies to named ones (`!== 'pikkuFunc'`).
325
+ */
326
+ export function ensureInlineWiringFunction(
327
+ state: InspectorState,
328
+ pikkuFuncId: string,
329
+ fallbackName: string | undefined,
330
+ funcInitializer: ts.Node,
331
+ checker: ts.TypeChecker,
332
+ isHelper?: boolean
333
+ ): void {
334
+ ensureFunctionMetadata(
335
+ state,
336
+ pikkuFuncId,
337
+ fallbackName,
338
+ funcInitializer,
339
+ checker,
340
+ isHelper
341
+ )
342
+
343
+ const meta = state.functions.meta[pikkuFuncId]
344
+ if (
345
+ meta &&
346
+ meta.sessionless === undefined &&
347
+ ts.isCallExpression(funcInitializer) &&
348
+ ts.isIdentifier(funcInitializer.expression)
349
+ ) {
350
+ meta.sessionless = funcInitializer.expression.text !== 'pikkuFunc'
351
+ }
352
+ }
353
+
312
354
  function populateTypesLookup(
313
355
  state: InspectorState,
314
356
  pikkuFuncId: string,
@@ -82,7 +82,6 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
82
82
  },
83
83
  files: new Set(['/test/project/src/api/users.ts']),
84
84
  routeMiddleware: new Map(),
85
- routePermissions: new Map(),
86
85
  },
87
86
  functions: {
88
87
  typesMap: new Map(),
@@ -332,7 +331,6 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
332
331
  permissions: {
333
332
  definitions: {},
334
333
  instances: {},
335
- tagPermissions: new Map(),
336
334
  },
337
335
  serviceAggregation: {
338
336
  requiredServices: new Set(),
@@ -425,6 +425,8 @@ export function filterInspectorState(
425
425
  internalFiles: new Map(state.rpc.internalFiles),
426
426
  exposedMeta: { ...state.rpc.exposedMeta },
427
427
  exposedFiles: new Map(state.rpc.exposedFiles),
428
+ remoteMeta: { ...state.rpc.remoteMeta },
429
+ remoteFiles: new Map(state.rpc.remoteFiles),
428
430
  invokedFunctions: new Set(state.rpc.invokedFunctions),
429
431
  },
430
432
  cli: {
@@ -1052,6 +1054,13 @@ export function filterInspectorState(
1052
1054
  filteredState.rpc.exposedFiles.delete(key)
1053
1055
  }
1054
1056
  }
1057
+ for (const key of Object.keys(filteredState.rpc.remoteMeta)) {
1058
+ const targetFuncId = filteredState.rpc.remoteMeta[key]
1059
+ if (!survivingFuncIds.has(targetFuncId) && !survivingFuncIds.has(key)) {
1060
+ delete filteredState.rpc.remoteMeta[key]
1061
+ filteredState.rpc.remoteFiles.delete(key)
1062
+ }
1063
+ }
1055
1064
  // Prune invokedFunctions to match surviving functions
1056
1065
  for (const funcId of filteredState.rpc.invokedFunctions) {
1057
1066
  if (!survivingFuncIds.has(funcId)) {