@pikku/inspector 0.12.74 → 0.12.81
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 +347 -0
- package/dist/add/add-analytics.d.ts +9 -0
- package/dist/add/add-analytics.js +178 -0
- package/dist/add/add-auth.js +0 -54
- package/dist/add/add-feature-flag.d.ts +2 -0
- package/dist/add/add-feature-flag.js +162 -0
- package/dist/add/add-wire-addon.js +1 -0
- package/dist/add/add-wire-remote-addon.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/inspector.js +4 -0
- package/dist/surface/collect-surface.d.ts +58 -0
- package/dist/surface/collect-surface.js +613 -0
- package/dist/types.d.ts +20 -11
- package/dist/utils/filter-inspector-state.js +23 -13
- package/dist/utils/post-process.js +15 -0
- package/dist/utils/read-module-specifiers.d.ts +8 -0
- package/dist/utils/read-module-specifiers.js +23 -0
- package/dist/utils/read-tsconfig-out-dir.d.ts +5 -0
- package/dist/utils/read-tsconfig-out-dir.js +12 -0
- package/dist/utils/resolve-deploy-target.d.ts +7 -0
- package/dist/utils/resolve-deploy-target.js +15 -7
- package/dist/utils/serialize-inspector-state.d.ts +5 -0
- package/dist/utils/serialize-inspector-state.js +8 -0
- package/dist/visit.js +4 -0
- package/package.json +16 -6
- package/run-tests.sh +21 -6
- package/src/add/add-addon-bans.ts +0 -85
- package/src/add/add-agent-scorer.test.ts +0 -197
- package/src/add/add-agent-scorer.ts +0 -83
- package/src/add/add-agent.test.ts +0 -87
- package/src/add/add-agent.ts +0 -638
- package/src/add/add-approval-description.ts +0 -76
- package/src/add/add-auth.test.ts +0 -791
- package/src/add/add-auth.ts +0 -424
- package/src/add/add-channel.ts +0 -778
- package/src/add/add-cli-renderers.test.ts +0 -74
- package/src/add/add-cli.ts +0 -1006
- package/src/add/add-credential.ts +0 -186
- package/src/add/add-feature.test.ts +0 -306
- package/src/add/add-feature.ts +0 -253
- package/src/add/add-file-extends-core-type.ts +0 -66
- package/src/add/add-file-with-config.ts +0 -45
- package/src/add/add-file-with-factory.test.ts +0 -134
- package/src/add/add-file-with-factory.ts +0 -288
- package/src/add/add-functions.test.ts +0 -743
- package/src/add/add-functions.ts +0 -1548
- package/src/add/add-gateway.ts +0 -143
- package/src/add/add-http-route-ref.test.ts +0 -149
- package/src/add/add-http-route.ts +0 -530
- package/src/add/add-http-routes.ts +0 -471
- package/src/add/add-keyed-wiring.ts +0 -180
- package/src/add/add-mcp-prompt.ts +0 -157
- package/src/add/add-mcp-resource.ts +0 -175
- package/src/add/add-middleware.test.ts +0 -196
- package/src/add/add-middleware.ts +0 -879
- package/src/add/add-permission-auth.test.ts +0 -59
- package/src/add/add-permission.ts +0 -311
- package/src/add/add-personas.test.ts +0 -406
- package/src/add/add-personas.ts +0 -287
- package/src/add/add-queue-worker.ts +0 -117
- package/src/add/add-rpc-invocations.test.ts +0 -287
- package/src/add/add-rpc-invocations.ts +0 -221
- package/src/add/add-scenario-step.test.ts +0 -509
- package/src/add/add-schedule.ts +0 -120
- package/src/add/add-scope.test.ts +0 -419
- package/src/add/add-scope.ts +0 -221
- package/src/add/add-secret.ts +0 -10
- package/src/add/add-subject-scenario-steps.test.ts +0 -161
- package/src/add/add-system-role.test.ts +0 -297
- package/src/add/add-system-role.ts +0 -162
- package/src/add/add-trigger.ts +0 -165
- package/src/add/add-variable.ts +0 -10
- package/src/add/add-wire-addon.test.ts +0 -185
- package/src/add/add-wire-addon.ts +0 -146
- package/src/add/add-wire-remote-addon.ts +0 -80
- package/src/add/add-workflow-edge-cases.test.ts +0 -201
- package/src/add/add-workflow-fanout.test.ts +0 -166
- package/src/add/add-workflow-graph-foreach.test.ts +0 -174
- package/src/add/add-workflow-graph-input.test.ts +0 -94
- package/src/add/add-workflow-graph-notes.test.ts +0 -74
- package/src/add/add-workflow-graph.ts +0 -644
- package/src/add/add-workflow-on-error.test.ts +0 -118
- package/src/add/add-workflow-return-switch.test.ts +0 -121
- package/src/add/add-workflow-silent-drops.test.ts +0 -261
- package/src/add/add-workflow-sleep-expression.test.ts +0 -96
- package/src/add/add-workflow.test.ts +0 -155
- package/src/add/add-workflow.ts +0 -482
- package/src/add/addon-bans.test.ts +0 -121
- package/src/add/addon-contracts.test.ts +0 -221
- package/src/add/allow-escape-hatches.test.ts +0 -144
- package/src/add/audit-meta.test.ts +0 -75
- package/src/add/classified-sink-check.test.ts +0 -347
- package/src/add/config-value-metadata.test.ts +0 -263
- package/src/add/dynamic-import-check.test.ts +0 -119
- package/src/add/expect-eventually-scenario-only.test.ts +0 -108
- package/src/add/function-body-span.test.ts +0 -96
- package/src/add/inline-wiring-function.test.ts +0 -104
- package/src/add/pii-check.test.ts +0 -258
- package/src/add/rpc-type-cast.test.ts +0 -123
- package/src/add/scenario-instrumentation.test.ts +0 -89
- package/src/add/scenario-instrumentation.ts +0 -42
- package/src/add/scenario-marker.test.ts +0 -89
- package/src/add/scenario-not-extractable.test.ts +0 -159
- package/src/add/scenario-services.test.ts +0 -103
- package/src/add/scenario-skip.test.ts +0 -83
- package/src/add/validate-workflow-graph-addons.test.ts +0 -102
- package/src/add/wire-name-literal.test.ts +0 -117
- package/src/error-codes-emitted.test.ts +0 -76
- package/src/error-codes.ts +0 -145
- package/src/index.ts +0 -66
- package/src/inspector.ts +0 -575
- package/src/schema-generator.ts +0 -1
- package/src/source-file-cache.ts +0 -73
- package/src/tsconfig-paths.test.ts +0 -90
- package/src/types-map.ts +0 -141
- package/src/types.ts +0 -775
- package/src/utils/annotate-http-route-auth.test.ts +0 -161
- package/src/utils/annotate-http-route-auth.ts +0 -53
- package/src/utils/check-classified-sinks.ts +0 -215
- package/src/utils/check-pii-output.ts +0 -120
- package/src/utils/compute-diagnostics.test.ts +0 -106
- package/src/utils/compute-required-schemas.ts +0 -48
- package/src/utils/contract-hashes.test.ts +0 -553
- package/src/utils/contract-hashes.ts +0 -387
- package/src/utils/custom-types-generator.test.ts +0 -124
- package/src/utils/custom-types-generator.ts +0 -150
- package/src/utils/detect-schema-vendor.test.ts +0 -123
- package/src/utils/detect-schema-vendor.ts +0 -120
- package/src/utils/does-type-extend-core-type.ts +0 -53
- package/src/utils/ensure-function-metadata.ts +0 -385
- package/src/utils/extract-function-name.ts +0 -612
- package/src/utils/extract-function-node.ts +0 -70
- package/src/utils/extract-node-value.test.ts +0 -117
- package/src/utils/extract-node-value.ts +0 -172
- package/src/utils/extract-secret-usage.test.ts +0 -81
- package/src/utils/extract-secret-usage.ts +0 -73
- package/src/utils/extract-services.ts +0 -70
- package/src/utils/extract-surface-usage.test.ts +0 -147
- package/src/utils/extract-surface-usage.ts +0 -121
- package/src/utils/filter-inspector-state.test.ts +0 -2207
- package/src/utils/filter-inspector-state.ts +0 -1302
- package/src/utils/filter-utils.test.ts +0 -843
- package/src/utils/filter-utils.ts +0 -161
- package/src/utils/find-root-dir.ts +0 -68
- package/src/utils/find-zod-transform.test.ts +0 -70
- package/src/utils/find-zod-transform.ts +0 -103
- package/src/utils/get-exported-variable-name.ts +0 -48
- package/src/utils/get-files-and-methods.ts +0 -157
- package/src/utils/get-property-value.test.ts +0 -141
- package/src/utils/get-property-value.ts +0 -331
- package/src/utils/hash.ts +0 -26
- package/src/utils/literal-properties.ts +0 -97
- package/src/utils/load-addon-functions-meta.test.ts +0 -157
- package/src/utils/load-addon-functions-meta.ts +0 -508
- package/src/utils/load-addon-scopes.test.ts +0 -138
- package/src/utils/middleware.test.ts +0 -204
- package/src/utils/middleware.ts +0 -303
- package/src/utils/nested-project-filter.test.ts +0 -53
- package/src/utils/nested-project-filter.ts +0 -28
- package/src/utils/permissions-shorthand.test.ts +0 -79
- package/src/utils/permissions.test.ts +0 -125
- package/src/utils/permissions.ts +0 -129
- package/src/utils/post-process.test.ts +0 -1094
- package/src/utils/post-process.ts +0 -1604
- package/src/utils/resolve-addon-package.ts +0 -106
- package/src/utils/resolve-core-type.test.ts +0 -142
- package/src/utils/resolve-core-type.ts +0 -35
- package/src/utils/resolve-deploy-target.test.ts +0 -135
- package/src/utils/resolve-deploy-target.ts +0 -65
- package/src/utils/resolve-function-meta.ts +0 -25
- package/src/utils/resolve-function-types.ts +0 -42
- package/src/utils/resolve-identifier.ts +0 -46
- package/src/utils/resolve-ref-contract.ts +0 -71
- package/src/utils/resolve-versions.test.ts +0 -499
- package/src/utils/resolve-versions.ts +0 -228
- package/src/utils/schema-cache.test.ts +0 -124
- package/src/utils/schema-generator.test.ts +0 -272
- package/src/utils/schema-generator.ts +0 -780
- package/src/utils/secret-alias-services.test.ts +0 -146
- package/src/utils/secret-alias-services.ts +0 -58
- package/src/utils/serialize-inspector-state.ts +0 -742
- package/src/utils/serialize-mcp-json.ts +0 -150
- package/src/utils/serialize-middleware-groups-meta.ts +0 -36
- package/src/utils/serialize-openapi-json.ts +0 -277
- package/src/utils/serialize-permissions-groups-meta.ts +0 -7
- package/src/utils/single-declaration.ts +0 -57
- package/src/utils/type-utils.ts +0 -145
- package/src/utils/validate-auth-sessionless.ts +0 -30
- package/src/utils/validate-exposed-functions-gated.test.ts +0 -230
- package/src/utils/validate-exposed-functions-gated.ts +0 -103
- package/src/utils/validate-tags-resolve-to-middleware.test.ts +0 -108
- package/src/utils/validate-tags-resolve-to-middleware.ts +0 -48
- package/src/utils/workflow/derive-workflow-plan.test.ts +0 -178
- package/src/utils/workflow/derive-workflow-plan.ts +0 -117
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +0 -1465
- package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +0 -71
- package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +0 -99
- package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +0 -161
- package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +0 -121
- package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +0 -116
- package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +0 -145
- package/src/utils/workflow/dsl/extract-approval-policy.test.ts +0 -71
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +0 -2254
- package/src/utils/workflow/dsl/index.ts +0 -11
- package/src/utils/workflow/dsl/patterns.ts +0 -440
- package/src/utils/workflow/dsl/validation.ts +0 -203
- package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +0 -35
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +0 -543
- package/src/utils/workflow/graph/fanout-node-identity.test.ts +0 -105
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +0 -310
- package/src/utils/workflow/graph/finalize-workflows.test.ts +0 -50
- package/src/utils/workflow/graph/finalize-workflows.ts +0 -130
- package/src/utils/workflow/graph/index.ts +0 -28
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +0 -240
- package/src/utils/workflow/graph/workflow-graph.types.ts +0 -281
- package/src/visit.ts +0 -181
- package/tsconfig.json +0 -19
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { strict as assert } from 'assert'
|
|
2
|
-
import { describe, test, before, after } from 'node:test'
|
|
3
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
|
|
4
|
-
import { tmpdir } from 'os'
|
|
5
|
-
import { join } from 'path'
|
|
6
|
-
import { loadAddonFunctionsMeta } from './load-addon-functions-meta.js'
|
|
7
|
-
import type { InspectorState, InspectorLogger } from '../types.js'
|
|
8
|
-
|
|
9
|
-
const ADDON = '@addon/slack'
|
|
10
|
-
|
|
11
|
-
const writeAddonFixture = (rootDir: string) => {
|
|
12
|
-
writeFileSync(
|
|
13
|
-
join(rootDir, 'package.json'),
|
|
14
|
-
JSON.stringify({ name: 'consumer' })
|
|
15
|
-
)
|
|
16
|
-
const addonDir = join(rootDir, 'node_modules', ADDON)
|
|
17
|
-
const pikku = join(addonDir, '.pikku')
|
|
18
|
-
mkdirSync(join(pikku, 'function'), { recursive: true })
|
|
19
|
-
mkdirSync(join(pikku, 'secrets'), { recursive: true })
|
|
20
|
-
mkdirSync(join(pikku, 'variables'), { recursive: true })
|
|
21
|
-
mkdirSync(join(pikku, 'credentials'), { recursive: true })
|
|
22
|
-
writeFileSync(join(addonDir, 'package.json'), JSON.stringify({ name: ADDON }))
|
|
23
|
-
writeFileSync(
|
|
24
|
-
join(pikku, 'function', 'pikku-functions-meta.gen.json'),
|
|
25
|
-
JSON.stringify({})
|
|
26
|
-
)
|
|
27
|
-
// Real addon secrets/variables have a logical name that DIFFERS from the id
|
|
28
|
-
// the addon reads by (secretId/variableId) — overrides key on the id.
|
|
29
|
-
writeFileSync(
|
|
30
|
-
join(pikku, 'secrets', 'pikku-secrets-meta.gen.json'),
|
|
31
|
-
JSON.stringify({ slack: { name: 'slack', secretId: 'SLACK_TOKEN' } })
|
|
32
|
-
)
|
|
33
|
-
writeFileSync(
|
|
34
|
-
join(pikku, 'variables', 'pikku-variables-meta.gen.json'),
|
|
35
|
-
JSON.stringify({ region: { name: 'region', variableId: 'REGION' } })
|
|
36
|
-
)
|
|
37
|
-
writeFileSync(
|
|
38
|
-
join(pikku, 'credentials', 'pikku-credentials-meta.gen.json'),
|
|
39
|
-
JSON.stringify({
|
|
40
|
-
slackOAuth: { name: 'slackOAuth', type: 'singleton' },
|
|
41
|
-
})
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const makeState = (
|
|
46
|
-
rootDir: string,
|
|
47
|
-
wireAddonDeclarations: Map<string, any>
|
|
48
|
-
): InspectorState =>
|
|
49
|
-
({
|
|
50
|
-
rootDir,
|
|
51
|
-
rpc: { wireAddonDeclarations },
|
|
52
|
-
addonFunctions: {},
|
|
53
|
-
secrets: { definitions: [] },
|
|
54
|
-
variables: { definitions: [] },
|
|
55
|
-
credentials: { definitions: [] },
|
|
56
|
-
mcpEndpoints: { toolsMeta: {} },
|
|
57
|
-
addonServerlessIncompatible: new Map(),
|
|
58
|
-
addonRequiredParentServices: [],
|
|
59
|
-
exportedContracts: { addonHttp: {}, addonCli: {}, addonChannel: {} },
|
|
60
|
-
}) as unknown as InspectorState
|
|
61
|
-
|
|
62
|
-
const logger = {
|
|
63
|
-
debug: () => {},
|
|
64
|
-
info: () => {},
|
|
65
|
-
warn: () => {},
|
|
66
|
-
error: () => {},
|
|
67
|
-
critical: () => {},
|
|
68
|
-
diagnostic: () => {},
|
|
69
|
-
} as unknown as InspectorLogger
|
|
70
|
-
|
|
71
|
-
describe('loadAddonFunctionsMeta — per-instance secret/variable overrides', () => {
|
|
72
|
-
let rootDir: string
|
|
73
|
-
|
|
74
|
-
before(() => {
|
|
75
|
-
rootDir = mkdtempSync(join(tmpdir(), 'pikku-addon-meta-'))
|
|
76
|
-
writeAddonFixture(rootDir)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
after(() => {
|
|
80
|
-
rmSync(rootDir, { recursive: true, force: true })
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('registers the override target names, one per instance, not the shared logical name', async () => {
|
|
84
|
-
const state = makeState(
|
|
85
|
-
rootDir,
|
|
86
|
-
new Map<string, any>([
|
|
87
|
-
[
|
|
88
|
-
'slack-marketing',
|
|
89
|
-
{
|
|
90
|
-
package: ADDON,
|
|
91
|
-
// Overrides key on the secretId/variableId, not the logical name.
|
|
92
|
-
secretOverrides: { SLACK_TOKEN: 'SLACK_MARKETING_TOKEN' },
|
|
93
|
-
variableOverrides: { REGION: 'MARKETING_REGION' },
|
|
94
|
-
credentialOverrides: { slackOAuth: 'slack_marketing_oauth' },
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
[
|
|
98
|
-
'slack-support',
|
|
99
|
-
{
|
|
100
|
-
package: ADDON,
|
|
101
|
-
secretOverrides: { SLACK_TOKEN: 'SLACK_SUPPORT_TOKEN' },
|
|
102
|
-
credentialOverrides: { slackOAuth: 'slack_support_oauth' },
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
])
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
await loadAddonFunctionsMeta(logger, state)
|
|
109
|
-
|
|
110
|
-
const secretIds = state.secrets.definitions
|
|
111
|
-
.map((d: any) => d.secretId)
|
|
112
|
-
.sort()
|
|
113
|
-
assert.deepEqual(secretIds, [
|
|
114
|
-
'SLACK_MARKETING_TOKEN',
|
|
115
|
-
'SLACK_SUPPORT_TOKEN',
|
|
116
|
-
])
|
|
117
|
-
|
|
118
|
-
// slack-marketing overrides region; slack-support has no override, so it
|
|
119
|
-
// falls back to the addon's default variableId.
|
|
120
|
-
const variableIds = state.variables.definitions
|
|
121
|
-
.map((d: any) => d.variableId)
|
|
122
|
-
.sort()
|
|
123
|
-
assert.deepEqual(variableIds, ['MARKETING_REGION', 'REGION'])
|
|
124
|
-
|
|
125
|
-
// Each instance's credentialOverride surfaces a distinct credential name
|
|
126
|
-
// (which doubles as the better-auth providerId) — no shared account pool.
|
|
127
|
-
const credentialNames = state.credentials.definitions
|
|
128
|
-
.map((d: any) => d.name)
|
|
129
|
-
.sort()
|
|
130
|
-
assert.deepEqual(credentialNames, [
|
|
131
|
-
'slack_marketing_oauth',
|
|
132
|
-
'slack_support_oauth',
|
|
133
|
-
])
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
test('falls back to the addon logical name when no override is provided', async () => {
|
|
137
|
-
const state = makeState(
|
|
138
|
-
rootDir,
|
|
139
|
-
new Map<string, any>([['slack-plain', { package: ADDON }]])
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
await loadAddonFunctionsMeta(logger, state)
|
|
143
|
-
|
|
144
|
-
assert.deepEqual(
|
|
145
|
-
state.secrets.definitions.map((d: any) => d.secretId),
|
|
146
|
-
['SLACK_TOKEN']
|
|
147
|
-
)
|
|
148
|
-
assert.deepEqual(
|
|
149
|
-
state.variables.definitions.map((d: any) => d.variableId),
|
|
150
|
-
['REGION']
|
|
151
|
-
)
|
|
152
|
-
assert.deepEqual(
|
|
153
|
-
state.credentials.definitions.map((d: any) => d.name),
|
|
154
|
-
['slackOAuth']
|
|
155
|
-
)
|
|
156
|
-
})
|
|
157
|
-
})
|
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'fs'
|
|
2
|
-
import { readFile, readdir } from 'fs/promises'
|
|
3
|
-
import { createRequire } from 'module'
|
|
4
|
-
import { join, dirname } from 'path'
|
|
5
|
-
import type { InspectorState, InspectorLogger } from '../types.js'
|
|
6
|
-
import type {
|
|
7
|
-
ExportedChannelContractsMeta,
|
|
8
|
-
ExportedHTTPRouteConfigMeta,
|
|
9
|
-
ExportedHTTPRouteEntryMeta,
|
|
10
|
-
ExportedHTTPRoutesGroupMeta,
|
|
11
|
-
ExportedCLIContractsMeta,
|
|
12
|
-
ExportedHTTPContractsMeta,
|
|
13
|
-
ExportedHTTPRouteMapMeta,
|
|
14
|
-
} from '../types.js'
|
|
15
|
-
|
|
16
|
-
const isHTTPRouteConfig = (
|
|
17
|
-
value: ExportedHTTPRouteEntryMeta
|
|
18
|
-
): value is ExportedHTTPRouteConfigMeta =>
|
|
19
|
-
typeof value === 'object' &&
|
|
20
|
-
value !== null &&
|
|
21
|
-
'method' in value &&
|
|
22
|
-
'func' in value &&
|
|
23
|
-
'route' in value
|
|
24
|
-
|
|
25
|
-
const isHTTPRouteGroup = (
|
|
26
|
-
value: ExportedHTTPRouteEntryMeta
|
|
27
|
-
): value is ExportedHTTPRoutesGroupMeta =>
|
|
28
|
-
typeof value === 'object' &&
|
|
29
|
-
value !== null &&
|
|
30
|
-
'routes' in value &&
|
|
31
|
-
!('method' in value)
|
|
32
|
-
|
|
33
|
-
const applyPackageToHTTPRouteMap = (
|
|
34
|
-
routes: ExportedHTTPRouteMapMeta,
|
|
35
|
-
packageName: string,
|
|
36
|
-
namespace?: string
|
|
37
|
-
) => {
|
|
38
|
-
for (const value of Object.values(routes)) {
|
|
39
|
-
if (!value || typeof value !== 'object') continue
|
|
40
|
-
if (isHTTPRouteConfig(value)) {
|
|
41
|
-
if (!value.func.packageName) {
|
|
42
|
-
value.func.packageName = packageName
|
|
43
|
-
}
|
|
44
|
-
if (namespace && !value.func.pikkuFuncId.includes(':')) {
|
|
45
|
-
value.func.pikkuFuncId = `${namespace}:${value.func.pikkuFuncId}`
|
|
46
|
-
}
|
|
47
|
-
continue
|
|
48
|
-
}
|
|
49
|
-
if (isHTTPRouteGroup(value)) {
|
|
50
|
-
applyPackageToHTTPRouteMap(value.routes, packageName, namespace)
|
|
51
|
-
continue
|
|
52
|
-
}
|
|
53
|
-
applyPackageToHTTPRouteMap(
|
|
54
|
-
value as ExportedHTTPRouteMapMeta,
|
|
55
|
-
packageName,
|
|
56
|
-
namespace
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const applyPackageToHTTPContracts = (
|
|
62
|
-
contracts: ExportedHTTPContractsMeta,
|
|
63
|
-
packageName: string,
|
|
64
|
-
namespace: string
|
|
65
|
-
) => {
|
|
66
|
-
for (const contract of Object.values(contracts)) {
|
|
67
|
-
applyPackageToHTTPRouteMap(contract.routes, packageName, namespace)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const applyPackageToCLICommands = (
|
|
72
|
-
commands: Record<string, any>,
|
|
73
|
-
packageName: string,
|
|
74
|
-
namespace?: string
|
|
75
|
-
) => {
|
|
76
|
-
for (const command of Object.values(commands)) {
|
|
77
|
-
if (command && typeof command === 'object') {
|
|
78
|
-
if (!command.packageName && command.pikkuFuncId) {
|
|
79
|
-
command.packageName = packageName
|
|
80
|
-
}
|
|
81
|
-
if (
|
|
82
|
-
namespace &&
|
|
83
|
-
typeof command.pikkuFuncId === 'string' &&
|
|
84
|
-
!command.pikkuFuncId.includes(':')
|
|
85
|
-
) {
|
|
86
|
-
command.pikkuFuncId = `${namespace}:${command.pikkuFuncId}`
|
|
87
|
-
}
|
|
88
|
-
if (command.subcommands) {
|
|
89
|
-
applyPackageToCLICommands(command.subcommands, packageName, namespace)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const applyPackageToCLIContracts = (
|
|
96
|
-
contracts: ExportedCLIContractsMeta,
|
|
97
|
-
packageName: string,
|
|
98
|
-
namespace: string
|
|
99
|
-
) => {
|
|
100
|
-
for (const commands of Object.values(contracts)) {
|
|
101
|
-
applyPackageToCLICommands(commands, packageName, namespace)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const applyPackageToChannelContracts = (
|
|
106
|
-
contracts: ExportedChannelContractsMeta,
|
|
107
|
-
packageName: string,
|
|
108
|
-
namespace: string
|
|
109
|
-
) => {
|
|
110
|
-
for (const routes of Object.values(contracts)) {
|
|
111
|
-
for (const route of Object.values(routes)) {
|
|
112
|
-
if (!route.packageName) {
|
|
113
|
-
route.packageName = packageName
|
|
114
|
-
}
|
|
115
|
-
if (!route.pikkuFuncId.includes(':')) {
|
|
116
|
-
route.pikkuFuncId = `${namespace}:${route.pikkuFuncId}`
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Resolves an addon's generated metadata, preferring the verbose copy.
|
|
124
|
-
*
|
|
125
|
-
* An addon publishes both: the minimal one its own bootstrap imports, and the
|
|
126
|
-
* verbose one for anything that needs the fields stripped out of it. A consumer
|
|
127
|
-
* is squarely in the second category — it is reading the addon to describe it,
|
|
128
|
-
* not to run it.
|
|
129
|
-
*/
|
|
130
|
-
const resolveAddonMeta = (
|
|
131
|
-
require: NodeRequire,
|
|
132
|
-
packageName: string,
|
|
133
|
-
baseName: string
|
|
134
|
-
): string | null => {
|
|
135
|
-
for (const suffix of ['-verbose.gen.json', '.gen.json']) {
|
|
136
|
-
try {
|
|
137
|
-
return require.resolve(`${packageName}/.pikku/${baseName}${suffix}`)
|
|
138
|
-
} catch {
|
|
139
|
-
continue
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return null
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Give the addon's input/output type names a declaration file to be imported
|
|
147
|
-
* from, so a wiring that references an addon function can name its contract.
|
|
148
|
-
*
|
|
149
|
-
* Without this the only thing that knows where `GetScenarioArtifactInput` lives
|
|
150
|
-
* is the type checker, and it only knows once the consumer's *own* rpc map has
|
|
151
|
-
* already been generated with the addon's routes in it — which is to say on the
|
|
152
|
-
* second run. A first, cold run resolved `ref('console:getScenarioArtifact')`
|
|
153
|
-
* against a map that did not mention it yet and fell back to the whole
|
|
154
|
-
* `FlattenedRPCMap`, so the same source produced a different http-map depending
|
|
155
|
-
* on whether `.pikku` happened to be there. The addon publishes these types at a
|
|
156
|
-
* fixed subpath; reading them from the metadata makes the first run agree with
|
|
157
|
-
* the second.
|
|
158
|
-
*/
|
|
159
|
-
const registerAddonTypes = (
|
|
160
|
-
state: InspectorState,
|
|
161
|
-
metaPath: string,
|
|
162
|
-
meta: Record<string, any>
|
|
163
|
-
): void => {
|
|
164
|
-
// Derived from the metadata's own location rather than resolved as a package
|
|
165
|
-
// subpath: the addon exports that declaration under a `types`-only condition,
|
|
166
|
-
// which `require.resolve` will not follow.
|
|
167
|
-
const typesPath = join(
|
|
168
|
-
dirname(metaPath),
|
|
169
|
-
'..',
|
|
170
|
-
'rpc',
|
|
171
|
-
'pikku-rpc-wirings-map.internal.gen.d.ts'
|
|
172
|
-
)
|
|
173
|
-
if (!existsSync(typesPath)) return
|
|
174
|
-
|
|
175
|
-
for (const funcMeta of Object.values(meta)) {
|
|
176
|
-
for (const name of [funcMeta.inputSchemaName, funcMeta.outputSchemaName]) {
|
|
177
|
-
if (!name || state.functions.typesMap.exists(name, typesPath)) continue
|
|
178
|
-
state.functions.typesMap.addType(name, typesPath)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* After the setup sweep discovers wireAddon() declarations, load each addon
|
|
185
|
-
* package's function metadata so that wiring handlers (channels, HTTP routes,
|
|
186
|
-
* schedules, etc.) can look up addon function types during the routes sweep.
|
|
187
|
-
*/
|
|
188
|
-
export async function loadAddonFunctionsMeta(
|
|
189
|
-
logger: InspectorLogger,
|
|
190
|
-
state: InspectorState
|
|
191
|
-
): Promise<void> {
|
|
192
|
-
const { wireAddonDeclarations } = state.rpc
|
|
193
|
-
if (wireAddonDeclarations.size === 0) return
|
|
194
|
-
|
|
195
|
-
const require = createRequire(join(state.rootDir, 'package.json'))
|
|
196
|
-
|
|
197
|
-
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
198
|
-
// Remote addons (wireRemoteAddon) ship as a devDependency: types only.
|
|
199
|
-
// Their functions, secrets, variables, schemas and services live on the
|
|
200
|
-
// HOST that runs them — never load or require any of that here.
|
|
201
|
-
if (decl.remote) continue
|
|
202
|
-
try {
|
|
203
|
-
// Verbose first. `description` is one of the fields stripped from the
|
|
204
|
-
// minimal copy, and it is what an addon's function is offered to a model
|
|
205
|
-
// under — both as an MCP tool below and as an agent tool. Read the minimal
|
|
206
|
-
// one and every addon function looks undescribed.
|
|
207
|
-
const metaPath = resolveAddonMeta(
|
|
208
|
-
require,
|
|
209
|
-
decl.package,
|
|
210
|
-
'function/pikku-functions-meta'
|
|
211
|
-
)
|
|
212
|
-
if (!metaPath) throw new Error('no function metadata')
|
|
213
|
-
const raw = await readFile(metaPath, 'utf-8')
|
|
214
|
-
const meta = JSON.parse(raw)
|
|
215
|
-
state.addonFunctions[namespace] = meta
|
|
216
|
-
logger.debug(
|
|
217
|
-
`Loaded ${Object.keys(meta).length} addon functions for '${namespace}' from ${decl.package}`
|
|
218
|
-
)
|
|
219
|
-
registerAddonTypes(state, metaPath, meta)
|
|
220
|
-
|
|
221
|
-
// If wireAddon has mcp: true, expose addon functions with mcp: true as MCP tools
|
|
222
|
-
if (decl.mcp) {
|
|
223
|
-
for (const [funcName, funcMeta] of Object.entries<any>(meta)) {
|
|
224
|
-
if (funcMeta.mcp) {
|
|
225
|
-
const toolName = `${namespace}:${funcName}`
|
|
226
|
-
state.mcpEndpoints.toolsMeta[toolName] = {
|
|
227
|
-
pikkuFuncId: `${namespace}:${funcName}`,
|
|
228
|
-
name: toolName,
|
|
229
|
-
description: funcMeta.description || funcMeta.title || funcName,
|
|
230
|
-
inputSchema: funcMeta.inputSchemaName ?? null,
|
|
231
|
-
outputSchema: funcMeta.outputSchemaName ?? null,
|
|
232
|
-
tags: funcMeta.tags,
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
// Load addon secrets meta
|
|
238
|
-
try {
|
|
239
|
-
const secretsMetaPath = require.resolve(
|
|
240
|
-
`${decl.package}/.pikku/secrets/pikku-secrets-meta.gen.json`
|
|
241
|
-
)
|
|
242
|
-
const secretsRaw = await readFile(secretsMetaPath, 'utf-8')
|
|
243
|
-
const secretsMeta = JSON.parse(secretsRaw)
|
|
244
|
-
for (const [key, def] of Object.entries<any>(secretsMeta)) {
|
|
245
|
-
// secretOverrides key on the SECRET ID (the string the addon passes to
|
|
246
|
-
// getSecret — its typed map is keyed by secretId, e.g.
|
|
247
|
-
// `getSecret('MAILGUN_CREDENTIALS')`), NOT the logical meta key, so the
|
|
248
|
-
// runtime aliaser (which also keys on secretId) and this merge agree.
|
|
249
|
-
// The resolved id is the real project secret; the addon's secretId is
|
|
250
|
-
// the default when no override is given. Two instances with different
|
|
251
|
-
// overrides therefore surface two distinct project secrets. `key` is
|
|
252
|
-
// the fallback for older meta that predates the secretId field.
|
|
253
|
-
const secretId = def.secretId ?? key
|
|
254
|
-
const resolvedSecretId = decl.secretOverrides?.[secretId] ?? secretId
|
|
255
|
-
const existing = state.secrets.definitions.find(
|
|
256
|
-
(d: any) => (d.secretId ?? d.name) === resolvedSecretId
|
|
257
|
-
)
|
|
258
|
-
if (!existing) {
|
|
259
|
-
state.secrets.definitions.push({
|
|
260
|
-
...def,
|
|
261
|
-
secretId: resolvedSecretId,
|
|
262
|
-
})
|
|
263
|
-
logger.debug(
|
|
264
|
-
`Loaded addon secret '${resolvedSecretId}' from ${decl.package}`
|
|
265
|
-
)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
} catch {
|
|
269
|
-
// No secrets meta — that's fine
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Load addon scopes meta. Without this an addon's own scopes never reach
|
|
273
|
-
// the host's ScopeId union or its declared set, so the pikku_scopes FK
|
|
274
|
-
// would refuse to grant one.
|
|
275
|
-
try {
|
|
276
|
-
const scopesMetaPath = require.resolve(
|
|
277
|
-
`${decl.package}/.pikku/scopes/pikku-scopes-meta.gen.json`
|
|
278
|
-
)
|
|
279
|
-
const scopesRaw = await readFile(scopesMetaPath, 'utf-8')
|
|
280
|
-
const scopesMeta = JSON.parse(scopesRaw)
|
|
281
|
-
for (const [key, def] of Object.entries<any>(scopesMeta)) {
|
|
282
|
-
const existing = state.scopes.definitions.find(
|
|
283
|
-
(d: any) => d.name === key
|
|
284
|
-
)
|
|
285
|
-
if (!existing) {
|
|
286
|
-
state.scopes.definitions.push(def)
|
|
287
|
-
logger.debug(`Loaded addon scope '${key}' from ${decl.package}`)
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} catch {
|
|
291
|
-
// No scopes meta — that's fine
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Load addon variables meta
|
|
295
|
-
try {
|
|
296
|
-
const variablesMetaPath = require.resolve(
|
|
297
|
-
`${decl.package}/.pikku/variables/pikku-variables-meta.gen.json`
|
|
298
|
-
)
|
|
299
|
-
const variablesRaw = await readFile(variablesMetaPath, 'utf-8')
|
|
300
|
-
const variablesMeta = JSON.parse(variablesRaw)
|
|
301
|
-
for (const [key, def] of Object.entries<any>(variablesMeta)) {
|
|
302
|
-
// variableOverrides key on the VARIABLE ID (the string the addon reads
|
|
303
|
-
// via its typed map, keyed by variableId), same as secretOverrides key
|
|
304
|
-
// on secretId — so the runtime aliaser and this merge agree. `key` is
|
|
305
|
-
// the fallback for older meta without a variableId field.
|
|
306
|
-
const variableId = def.variableId ?? key
|
|
307
|
-
const resolvedVariableId =
|
|
308
|
-
decl.variableOverrides?.[variableId] ?? variableId
|
|
309
|
-
const existing = state.variables.definitions.find(
|
|
310
|
-
(d: any) => (d.variableId ?? d.name) === resolvedVariableId
|
|
311
|
-
)
|
|
312
|
-
if (!existing) {
|
|
313
|
-
state.variables.definitions.push({
|
|
314
|
-
...def,
|
|
315
|
-
variableId: resolvedVariableId,
|
|
316
|
-
})
|
|
317
|
-
logger.debug(
|
|
318
|
-
`Loaded addon variable '${resolvedVariableId}' from ${decl.package}`
|
|
319
|
-
)
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
} catch {
|
|
323
|
-
// No variables meta — that's fine
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Load addon credentials meta. Without this an addon's OAuth2/wire
|
|
327
|
-
// credentials never reach the consuming app's CREDENTIAL_OAUTH2_CONFIGS,
|
|
328
|
-
// so the credential-oauth provider and BetterAuthCredentialService can't
|
|
329
|
-
// resolve them — the addon's Connect flow and status silently no-op.
|
|
330
|
-
try {
|
|
331
|
-
const credentialsMetaPath = require.resolve(
|
|
332
|
-
`${decl.package}/.pikku/credentials/pikku-credentials-meta.gen.json`
|
|
333
|
-
)
|
|
334
|
-
const credentialsRaw = await readFile(credentialsMetaPath, 'utf-8')
|
|
335
|
-
const credentialsMeta = JSON.parse(credentialsRaw)
|
|
336
|
-
for (const [key, def] of Object.entries<any>(credentialsMeta)) {
|
|
337
|
-
// Each instance's credentialOverrides remap the addon's logical
|
|
338
|
-
// credential name to a project credential — same mechanic as secrets
|
|
339
|
-
// and variables above. The credential name doubles as the better-auth
|
|
340
|
-
// providerId (accounts keyed by providerId+userId), so two instances
|
|
341
|
-
// with different overrides surface two distinct OAuth providers rather
|
|
342
|
-
// than sharing one account pool. The logical name is the default when
|
|
343
|
-
// no override is given.
|
|
344
|
-
const resolvedName = decl.credentialOverrides?.[key] ?? key
|
|
345
|
-
const existing = state.credentials.definitions.find(
|
|
346
|
-
(d: any) => d.name === resolvedName
|
|
347
|
-
)
|
|
348
|
-
if (!existing) {
|
|
349
|
-
state.credentials.definitions.push({ ...def, name: resolvedName })
|
|
350
|
-
logger.debug(
|
|
351
|
-
`Loaded addon credential '${resolvedName}' from ${decl.package}`
|
|
352
|
-
)
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
} catch {
|
|
356
|
-
// No credentials meta — that's fine
|
|
357
|
-
}
|
|
358
|
-
// Load addon serverlessIncompatible service names from pikku-addon-meta.gen.json
|
|
359
|
-
let loadedParentServices = false
|
|
360
|
-
try {
|
|
361
|
-
const addonMetaPath = require.resolve(
|
|
362
|
-
`${decl.package}/.pikku/console/pikku-addon-meta.gen.json`
|
|
363
|
-
)
|
|
364
|
-
const addonMetaRaw = await readFile(addonMetaPath, 'utf-8')
|
|
365
|
-
const addonMeta = JSON.parse(addonMetaRaw)
|
|
366
|
-
if (
|
|
367
|
-
Array.isArray(addonMeta.serverlessIncompatible) &&
|
|
368
|
-
addonMeta.serverlessIncompatible.length > 0
|
|
369
|
-
) {
|
|
370
|
-
state.addonServerlessIncompatible.set(
|
|
371
|
-
namespace,
|
|
372
|
-
addonMeta.serverlessIncompatible
|
|
373
|
-
)
|
|
374
|
-
logger.debug(
|
|
375
|
-
`Addon '${namespace}' marks [${addonMeta.serverlessIncompatible.join(', ')}] as serverless-incompatible`
|
|
376
|
-
)
|
|
377
|
-
}
|
|
378
|
-
if (
|
|
379
|
-
Array.isArray(addonMeta.requiredParentServices) &&
|
|
380
|
-
addonMeta.requiredParentServices.length > 0
|
|
381
|
-
) {
|
|
382
|
-
for (const service of addonMeta.requiredParentServices) {
|
|
383
|
-
state.addonRequiredParentServices.push(service)
|
|
384
|
-
}
|
|
385
|
-
loadedParentServices = true
|
|
386
|
-
logger.debug(
|
|
387
|
-
`Loaded ${addonMeta.requiredParentServices.length} required parent services for '${namespace}' from addon meta`
|
|
388
|
-
)
|
|
389
|
-
}
|
|
390
|
-
} catch {}
|
|
391
|
-
|
|
392
|
-
if (!loadedParentServices) {
|
|
393
|
-
try {
|
|
394
|
-
const servicesGenPath = require.resolve(
|
|
395
|
-
`${decl.package}/.pikku/pikku-services.gen.js`
|
|
396
|
-
)
|
|
397
|
-
const servicesModule = await import(servicesGenPath)
|
|
398
|
-
if (
|
|
399
|
-
servicesModule.requiredParentServices &&
|
|
400
|
-
Array.isArray(servicesModule.requiredParentServices)
|
|
401
|
-
) {
|
|
402
|
-
for (const service of servicesModule.requiredParentServices) {
|
|
403
|
-
state.addonRequiredParentServices.push(service)
|
|
404
|
-
}
|
|
405
|
-
logger.debug(
|
|
406
|
-
`Loaded ${servicesModule.requiredParentServices.length} required parent services for '${namespace}' from ${decl.package}`
|
|
407
|
-
)
|
|
408
|
-
}
|
|
409
|
-
} catch {}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
try {
|
|
413
|
-
const httpContractsPath = require.resolve(
|
|
414
|
-
`${decl.package}/.pikku/http/pikku-http-contracts-meta.gen.json`
|
|
415
|
-
)
|
|
416
|
-
const httpContractsRaw = await readFile(httpContractsPath, 'utf-8')
|
|
417
|
-
const httpContracts = JSON.parse(
|
|
418
|
-
httpContractsRaw
|
|
419
|
-
) as ExportedHTTPContractsMeta
|
|
420
|
-
applyPackageToHTTPContracts(httpContracts, decl.package, namespace)
|
|
421
|
-
state.exportedContracts.addonHttp[namespace] = httpContracts
|
|
422
|
-
} catch {
|
|
423
|
-
// No addon HTTP contracts metadata
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
try {
|
|
427
|
-
const cliContractsPath = require.resolve(
|
|
428
|
-
`${decl.package}/.pikku/cli/pikku-cli-contracts-meta.gen.json`
|
|
429
|
-
)
|
|
430
|
-
const cliContractsRaw = await readFile(cliContractsPath, 'utf-8')
|
|
431
|
-
const cliContracts = JSON.parse(
|
|
432
|
-
cliContractsRaw
|
|
433
|
-
) as ExportedCLIContractsMeta
|
|
434
|
-
applyPackageToCLIContracts(cliContracts, decl.package, namespace)
|
|
435
|
-
state.exportedContracts.addonCli[namespace] = cliContracts
|
|
436
|
-
} catch {
|
|
437
|
-
// No addon CLI contracts metadata
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
try {
|
|
441
|
-
const channelContractsPath = require.resolve(
|
|
442
|
-
`${decl.package}/.pikku/channel/pikku-channel-contracts-meta.gen.json`
|
|
443
|
-
)
|
|
444
|
-
const channelContractsRaw = await readFile(
|
|
445
|
-
channelContractsPath,
|
|
446
|
-
'utf-8'
|
|
447
|
-
)
|
|
448
|
-
const channelContracts = JSON.parse(
|
|
449
|
-
channelContractsRaw
|
|
450
|
-
) as ExportedChannelContractsMeta
|
|
451
|
-
applyPackageToChannelContracts(
|
|
452
|
-
channelContracts,
|
|
453
|
-
decl.package,
|
|
454
|
-
namespace
|
|
455
|
-
)
|
|
456
|
-
state.exportedContracts.addonChannel[namespace] = channelContracts
|
|
457
|
-
} catch {
|
|
458
|
-
// No addon channel contracts metadata
|
|
459
|
-
}
|
|
460
|
-
} catch (error: any) {
|
|
461
|
-
logger.warn(
|
|
462
|
-
`Failed to load addon function metadata for '${namespace}' (${decl.package}): ${error.message}`
|
|
463
|
-
)
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Load addon schemas into state.schemas. Called after generateAllSchemas
|
|
470
|
-
* to ensure addon schemas aren't overwritten.
|
|
471
|
-
*/
|
|
472
|
-
export async function loadAddonSchemas(
|
|
473
|
-
logger: InspectorLogger,
|
|
474
|
-
state: InspectorState
|
|
475
|
-
): Promise<void> {
|
|
476
|
-
const { wireAddonDeclarations } = state.rpc
|
|
477
|
-
if (wireAddonDeclarations.size === 0) return
|
|
478
|
-
|
|
479
|
-
const require = createRequire(join(state.rootDir, 'package.json'))
|
|
480
|
-
|
|
481
|
-
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
482
|
-
// Remote addons carry no local schemas — their funcs run on the host.
|
|
483
|
-
if (decl.remote) continue
|
|
484
|
-
try {
|
|
485
|
-
const metaPath = require.resolve(
|
|
486
|
-
`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`
|
|
487
|
-
)
|
|
488
|
-
const schemasDir = join(dirname(metaPath), '..', 'schemas', 'schemas')
|
|
489
|
-
try {
|
|
490
|
-
const schemaFiles = await readdir(schemasDir)
|
|
491
|
-
for (const file of schemaFiles) {
|
|
492
|
-
if (!file.endsWith('.schema.json')) continue
|
|
493
|
-
const schemaName = file.replace('.schema.json', '')
|
|
494
|
-
if (!state.schemas[schemaName]) {
|
|
495
|
-
const schemaRaw = await readFile(join(schemasDir, file), 'utf-8')
|
|
496
|
-
state.schemas[schemaName] = JSON.parse(schemaRaw)
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
} catch {
|
|
500
|
-
// No schemas directory — that's fine
|
|
501
|
-
}
|
|
502
|
-
} catch (error: any) {
|
|
503
|
-
logger.warn(
|
|
504
|
-
`Failed to load addon schemas for '${namespace}' (${decl.package}): ${error.message}`
|
|
505
|
-
)
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|