@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.
- package/CHANGELOG.md +104 -0
- package/dist/add/add-addon-bans.js +1 -0
- package/dist/add/add-ai-agent.d.ts +3 -1
- package/dist/add/add-ai-agent.js +82 -0
- package/dist/add/add-credential.js +3 -1
- package/dist/add/add-functions.js +10 -9
- package/dist/add/add-gateway.js +3 -0
- package/dist/add/add-http-route.js +2 -2
- package/dist/add/add-mcp-prompt.js +0 -1
- package/dist/add/add-mcp-resource.js +0 -1
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +3 -177
- package/dist/add/add-queue-worker.js +3 -0
- package/dist/add/add-schedule.js +3 -0
- package/dist/add/add-scope.d.ts +2 -0
- package/dist/add/add-scope.js +146 -0
- package/dist/add/add-trigger.js +3 -0
- package/dist/add/add-wire-remote-addon.d.ts +10 -0
- package/dist/add/add-wire-remote-addon.js +66 -0
- package/dist/add/add-workflow-graph.js +32 -3
- package/dist/error-codes.d.ts +3 -0
- package/dist/error-codes.js +4 -0
- package/dist/inspector.js +19 -7
- package/dist/types.d.ts +19 -13
- package/dist/utils/custom-types-generator.js +2 -1
- package/dist/utils/ensure-function-metadata.d.ts +15 -0
- package/dist/utils/ensure-function-metadata.js +24 -0
- package/dist/utils/filter-inspector-state.js +9 -0
- package/dist/utils/get-property-value.d.ts +7 -2
- package/dist/utils/get-property-value.js +48 -1
- package/dist/utils/load-addon-functions-meta.js +81 -6
- package/dist/utils/nested-project-filter.d.ts +1 -0
- package/dist/utils/nested-project-filter.js +29 -0
- package/dist/utils/permissions.d.ts +1 -21
- package/dist/utils/permissions.js +17 -108
- package/dist/utils/post-process.d.ts +30 -1
- package/dist/utils/post-process.js +150 -72
- package/dist/utils/serialize-inspector-state.d.ts +13 -8
- package/dist/utils/serialize-inspector-state.js +12 -6
- package/dist/utils/serialize-permissions-groups-meta.d.ts +0 -2
- package/dist/utils/serialize-permissions-groups-meta.js +0 -26
- package/dist/utils/workflow/derive-workflow-plan.js +9 -3
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +56 -1
- package/dist/utils/workflow/dsl/patterns.d.ts +10 -0
- package/dist/utils/workflow/dsl/patterns.js +24 -8
- package/dist/utils/workflow/dsl/validation.js +3 -4
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +14 -0
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +7 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +11 -2
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +2 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +4 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +6 -2
- package/dist/visit.js +4 -0
- package/package.json +3 -3
- package/src/add/add-addon-bans.ts +1 -0
- package/src/add/add-ai-agent.test.ts +87 -0
- package/src/add/add-ai-agent.ts +122 -0
- package/src/add/add-credential.ts +6 -0
- package/src/add/add-functions.ts +10 -12
- package/src/add/add-gateway.ts +11 -0
- package/src/add/add-http-route.ts +2 -8
- package/src/add/add-mcp-prompt.ts +0 -1
- package/src/add/add-mcp-resource.ts +0 -1
- package/src/add/add-permission.ts +4 -242
- package/src/add/add-queue-worker.ts +11 -0
- package/src/add/add-schedule.ts +11 -0
- package/src/add/add-scope.test.ts +346 -0
- package/src/add/add-scope.ts +225 -0
- package/src/add/add-trigger.ts +11 -0
- package/src/add/add-wire-remote-addon.ts +77 -0
- package/src/add/add-workflow-graph-input.test.ts +94 -0
- package/src/add/add-workflow-graph-notes.test.ts +74 -0
- package/src/add/add-workflow-graph.ts +35 -3
- package/src/add/inline-wiring-function.test.ts +104 -0
- package/src/add/validate-workflow-graph-addons.test.ts +102 -0
- package/src/error-codes.ts +5 -0
- package/src/inspector.ts +22 -6
- package/src/types.ts +16 -17
- package/src/utils/custom-types-generator.test.ts +26 -1
- package/src/utils/custom-types-generator.ts +2 -1
- package/src/utils/ensure-function-metadata.ts +42 -0
- package/src/utils/filter-inspector-state.test.ts +0 -2
- package/src/utils/filter-inspector-state.ts +9 -0
- package/src/utils/get-property-value.test.ts +141 -0
- package/src/utils/get-property-value.ts +62 -1
- package/src/utils/load-addon-functions-meta.test.ts +157 -0
- package/src/utils/load-addon-functions-meta.ts +94 -6
- package/src/utils/load-addon-scopes.test.ts +138 -0
- package/src/utils/nested-project-filter.test.ts +53 -0
- package/src/utils/nested-project-filter.ts +28 -0
- package/src/utils/permissions.test.ts +7 -232
- package/src/utils/permissions.ts +20 -160
- package/src/utils/post-process.test.ts +269 -4
- package/src/utils/post-process.ts +216 -95
- package/src/utils/serialize-inspector-state.ts +22 -25
- package/src/utils/serialize-permissions-groups-meta.ts +0 -28
- package/src/utils/workflow/derive-workflow-plan.test.ts +41 -3
- package/src/utils/workflow/derive-workflow-plan.ts +11 -4
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +66 -0
- package/src/utils/workflow/dsl/patterns.ts +34 -8
- package/src/utils/workflow/dsl/validation.ts +3 -4
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +15 -0
- package/src/utils/workflow/graph/finalize-workflows.test.ts +50 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +13 -2
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +6 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +8 -0
- package/src/visit.ts +4 -0
- 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
|
+
})
|
package/src/error-codes.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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)) {
|