@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,121 +0,0 @@
|
|
|
1
|
-
import { strict as assert } from 'node: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 { ErrorCode } from '../error-codes.js'
|
|
8
|
-
import type { InspectorLogger } from '../types.js'
|
|
9
|
-
|
|
10
|
-
const recordingLogger = () => {
|
|
11
|
-
const criticals: Array<{ code: ErrorCode; message: string }> = []
|
|
12
|
-
const logger: InspectorLogger = {
|
|
13
|
-
debug: () => {},
|
|
14
|
-
info: () => {},
|
|
15
|
-
warn: () => {},
|
|
16
|
-
error: () => {},
|
|
17
|
-
critical: (code, message) => {
|
|
18
|
-
criticals.push({ code, message })
|
|
19
|
-
},
|
|
20
|
-
hasCriticalErrors: () => criticals.length > 0,
|
|
21
|
-
}
|
|
22
|
-
return { logger, criticals }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const withTempApp = async (
|
|
26
|
-
source: string,
|
|
27
|
-
run: (file: string, rootDir: string) => Promise<void>
|
|
28
|
-
) => {
|
|
29
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-addon-bans-'))
|
|
30
|
-
const file = join(rootDir, 'app.ts')
|
|
31
|
-
await writeFile(
|
|
32
|
-
join(rootDir, 'package.json'),
|
|
33
|
-
JSON.stringify({ name: 'test-addon', type: 'module' }, null, 2)
|
|
34
|
-
)
|
|
35
|
-
await writeFile(file, source)
|
|
36
|
-
try {
|
|
37
|
-
await run(file, rootDir)
|
|
38
|
-
} finally {
|
|
39
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe('addon authoring bans', () => {
|
|
44
|
-
const wireHTTPSource = [
|
|
45
|
-
"import { wireHTTP } from '@pikku/core/http'",
|
|
46
|
-
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
47
|
-
'const f = pikkuSessionlessFunc({ func: async () => ({ ok: true }) })',
|
|
48
|
-
"wireHTTP({ method: 'get', route: '/a', func: f })",
|
|
49
|
-
].join('\n')
|
|
50
|
-
|
|
51
|
-
const defineWithMiddlewareSource = [
|
|
52
|
-
"import { defineHTTPRoutes } from '@pikku/core/http'",
|
|
53
|
-
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
54
|
-
'const f = pikkuSessionlessFunc({ func: async () => ({ ok: true }) })',
|
|
55
|
-
'const mw = (async (_s: any, _w: any, next: any) => next()) as any',
|
|
56
|
-
"export const routes = defineHTTPRoutes({ basePath: '/x', routes: { a: { method: 'get', route: '/a', func: f, middleware: [mw] } } })",
|
|
57
|
-
].join('\n')
|
|
58
|
-
|
|
59
|
-
test('wire* inside an addon is a critical error', async () => {
|
|
60
|
-
await withTempApp(wireHTTPSource, async (file, rootDir) => {
|
|
61
|
-
const { logger, criticals } = recordingLogger()
|
|
62
|
-
await inspect(logger, [file], { rootDir, isAddon: true })
|
|
63
|
-
assert.ok(
|
|
64
|
-
criticals.some((c) => c.code === ErrorCode.ADDON_WIRING_NOT_ALLOWED),
|
|
65
|
-
`expected ADDON_WIRING_NOT_ALLOWED, got ${JSON.stringify(criticals)}`
|
|
66
|
-
)
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test('wire* outside an addon is allowed', async () => {
|
|
71
|
-
await withTempApp(wireHTTPSource, async (file, rootDir) => {
|
|
72
|
-
const { logger, criticals } = recordingLogger()
|
|
73
|
-
await inspect(logger, [file], { rootDir })
|
|
74
|
-
assert.ok(
|
|
75
|
-
!criticals.some((c) => c.code === ErrorCode.ADDON_WIRING_NOT_ALLOWED),
|
|
76
|
-
`expected no addon-wiring ban, got ${JSON.stringify(criticals)}`
|
|
77
|
-
)
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
test('define* carrying middleware inside an addon is a critical error', async () => {
|
|
82
|
-
await withTempApp(defineWithMiddlewareSource, async (file, rootDir) => {
|
|
83
|
-
const { logger, criticals } = recordingLogger()
|
|
84
|
-
await inspect(logger, [file], { rootDir, isAddon: true })
|
|
85
|
-
assert.ok(
|
|
86
|
-
criticals.some(
|
|
87
|
-
(c) => c.code === ErrorCode.ADDON_CONTRACT_HANDLERS_NOT_ALLOWED
|
|
88
|
-
),
|
|
89
|
-
`expected ADDON_CONTRACT_HANDLERS_NOT_ALLOWED, got ${JSON.stringify(criticals)}`
|
|
90
|
-
)
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
test('define* carrying middleware outside an addon is allowed', async () => {
|
|
95
|
-
await withTempApp(defineWithMiddlewareSource, async (file, rootDir) => {
|
|
96
|
-
const { logger, criticals } = recordingLogger()
|
|
97
|
-
await inspect(logger, [file], { rootDir })
|
|
98
|
-
assert.ok(
|
|
99
|
-
!criticals.some(
|
|
100
|
-
(c) => c.code === ErrorCode.ADDON_CONTRACT_HANDLERS_NOT_ALLOWED
|
|
101
|
-
),
|
|
102
|
-
`expected no contract-handlers ban, got ${JSON.stringify(criticals)}`
|
|
103
|
-
)
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
test('defineSecret inside an addon is allowed', async () => {
|
|
108
|
-
const source = [
|
|
109
|
-
"import { defineSecret } from '@pikku/core'",
|
|
110
|
-
"defineSecret({ name: 'example', secretId: 'EXAMPLE' } as any)",
|
|
111
|
-
].join('\n')
|
|
112
|
-
await withTempApp(source, async (file, rootDir) => {
|
|
113
|
-
const { logger, criticals } = recordingLogger()
|
|
114
|
-
await inspect(logger, [file], { rootDir, isAddon: true })
|
|
115
|
-
assert.ok(
|
|
116
|
-
!criticals.some((c) => c.code === ErrorCode.ADDON_WIRING_NOT_ALLOWED),
|
|
117
|
-
`expected defineSecret to be allowed, got ${JSON.stringify(criticals)}`
|
|
118
|
-
)
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
})
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { strict as assert } from 'node:assert'
|
|
2
|
-
import { describe, test } from 'node:test'
|
|
3
|
-
import { mkdir, 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
|
-
|
|
9
|
-
const logger: InspectorLogger = {
|
|
10
|
-
debug: () => {},
|
|
11
|
-
info: () => {},
|
|
12
|
-
warn: () => {},
|
|
13
|
-
error: () => {},
|
|
14
|
-
critical: () => {},
|
|
15
|
-
hasCriticalErrors: () => false,
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe('addon contract metadata', () => {
|
|
19
|
-
test('collects exported local define contracts', async () => {
|
|
20
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-exported-contracts-'))
|
|
21
|
-
const file = join(rootDir, 'contracts.ts')
|
|
22
|
-
|
|
23
|
-
await writeFile(
|
|
24
|
-
join(rootDir, 'package.json'),
|
|
25
|
-
JSON.stringify({ name: 'test-app', type: 'module' }, null, 2)
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
await writeFile(
|
|
29
|
-
file,
|
|
30
|
-
[
|
|
31
|
-
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
32
|
-
"import { defineHTTPRoutes } from '@pikku/core/http'",
|
|
33
|
-
"import { defineChannelRoutes } from '@pikku/core/channel'",
|
|
34
|
-
"import { defineCLICommands } from '@pikku/core/cli'",
|
|
35
|
-
'export const listTodos = pikkuSessionlessFunc({ func: async () => ({ ok: true }) })',
|
|
36
|
-
'export const subscribeTodo = pikkuSessionlessFunc({ func: async () => ({ ok: true }) })',
|
|
37
|
-
'export const runSync = pikkuSessionlessFunc({ func: async () => ({ ok: true }) })',
|
|
38
|
-
"export const httpRoutes = defineHTTPRoutes({ basePath: '/todos', routes: { list: { method: 'get', route: '/', func: listTodos } } })",
|
|
39
|
-
'export const channelRoutes = defineChannelRoutes({ subscribe: { func: subscribeTodo } })',
|
|
40
|
-
'export const cliCommands = defineCLICommands({ sync: { func: runSync, options: {} } })',
|
|
41
|
-
].join('\n')
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const state = await inspect(logger, [file], { rootDir })
|
|
46
|
-
assert.equal(
|
|
47
|
-
state.exportedContracts.http.httpRoutes?.routes.list?.func.pikkuFuncId,
|
|
48
|
-
'listTodos'
|
|
49
|
-
)
|
|
50
|
-
assert.equal(
|
|
51
|
-
state.exportedContracts.channel.channelRoutes?.subscribe?.pikkuFuncId,
|
|
52
|
-
'subscribeTodo'
|
|
53
|
-
)
|
|
54
|
-
assert.equal(
|
|
55
|
-
state.exportedContracts.cli.cliCommands?.sync?.pikkuFuncId,
|
|
56
|
-
'runSync'
|
|
57
|
-
)
|
|
58
|
-
} finally {
|
|
59
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
test('expands addon contracts referenced via refHTTP/refChannel/refCLI', async () => {
|
|
64
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-addon-refs-app-'))
|
|
65
|
-
const nodeModulesDir = join(rootDir, 'node_modules', '@test', 'addon')
|
|
66
|
-
const appFile = join(rootDir, 'app.ts')
|
|
67
|
-
|
|
68
|
-
await mkdir(join(nodeModulesDir, '.pikku', 'function'), { recursive: true })
|
|
69
|
-
await mkdir(join(nodeModulesDir, '.pikku', 'http'), { recursive: true })
|
|
70
|
-
await mkdir(join(nodeModulesDir, '.pikku', 'cli'), { recursive: true })
|
|
71
|
-
await mkdir(join(nodeModulesDir, '.pikku', 'channel'), { recursive: true })
|
|
72
|
-
|
|
73
|
-
await writeFile(
|
|
74
|
-
join(rootDir, 'package.json'),
|
|
75
|
-
JSON.stringify({ name: 'test-app', type: 'module' }, null, 2)
|
|
76
|
-
)
|
|
77
|
-
await writeFile(
|
|
78
|
-
join(nodeModulesDir, 'package.json'),
|
|
79
|
-
JSON.stringify({ name: '@test/addon', type: 'module' }, null, 2)
|
|
80
|
-
)
|
|
81
|
-
await writeFile(
|
|
82
|
-
join(
|
|
83
|
-
nodeModulesDir,
|
|
84
|
-
'.pikku',
|
|
85
|
-
'function',
|
|
86
|
-
'pikku-functions-meta.gen.json'
|
|
87
|
-
),
|
|
88
|
-
JSON.stringify(
|
|
89
|
-
{
|
|
90
|
-
listTodos: {
|
|
91
|
-
pikkuFuncId: 'listTodos',
|
|
92
|
-
inputSchemaName: null,
|
|
93
|
-
outputSchemaName: null,
|
|
94
|
-
sessionless: true,
|
|
95
|
-
},
|
|
96
|
-
runSync: {
|
|
97
|
-
pikkuFuncId: 'runSync',
|
|
98
|
-
inputSchemaName: null,
|
|
99
|
-
outputSchemaName: null,
|
|
100
|
-
sessionless: true,
|
|
101
|
-
},
|
|
102
|
-
subscribeTodo: {
|
|
103
|
-
pikkuFuncId: 'subscribeTodo',
|
|
104
|
-
inputSchemaName: null,
|
|
105
|
-
outputSchemaName: null,
|
|
106
|
-
sessionless: true,
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
null,
|
|
110
|
-
2
|
|
111
|
-
)
|
|
112
|
-
)
|
|
113
|
-
await writeFile(
|
|
114
|
-
join(
|
|
115
|
-
nodeModulesDir,
|
|
116
|
-
'.pikku',
|
|
117
|
-
'http',
|
|
118
|
-
'pikku-http-contracts-meta.gen.json'
|
|
119
|
-
),
|
|
120
|
-
JSON.stringify(
|
|
121
|
-
{
|
|
122
|
-
httpRoutes: {
|
|
123
|
-
basePath: '/addon',
|
|
124
|
-
routes: {
|
|
125
|
-
list: {
|
|
126
|
-
method: 'get',
|
|
127
|
-
route: '/todos',
|
|
128
|
-
func: { pikkuFuncId: 'listTodos' },
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
null,
|
|
134
|
-
2
|
|
135
|
-
)
|
|
136
|
-
)
|
|
137
|
-
await writeFile(
|
|
138
|
-
join(
|
|
139
|
-
nodeModulesDir,
|
|
140
|
-
'.pikku',
|
|
141
|
-
'cli',
|
|
142
|
-
'pikku-cli-contracts-meta.gen.json'
|
|
143
|
-
),
|
|
144
|
-
JSON.stringify(
|
|
145
|
-
{
|
|
146
|
-
cliCommands: {
|
|
147
|
-
sync: {
|
|
148
|
-
pikkuFuncId: 'runSync',
|
|
149
|
-
positionals: [],
|
|
150
|
-
options: {},
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
null,
|
|
155
|
-
2
|
|
156
|
-
)
|
|
157
|
-
)
|
|
158
|
-
await writeFile(
|
|
159
|
-
join(
|
|
160
|
-
nodeModulesDir,
|
|
161
|
-
'.pikku',
|
|
162
|
-
'channel',
|
|
163
|
-
'pikku-channel-contracts-meta.gen.json'
|
|
164
|
-
),
|
|
165
|
-
JSON.stringify(
|
|
166
|
-
{
|
|
167
|
-
channelRoutes: {
|
|
168
|
-
subscribe: {
|
|
169
|
-
pikkuFuncId: 'subscribeTodo',
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
null,
|
|
174
|
-
2
|
|
175
|
-
)
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
await writeFile(
|
|
179
|
-
appFile,
|
|
180
|
-
[
|
|
181
|
-
"import { wireAddon } from '@pikku/core/addon'",
|
|
182
|
-
"import { wireHTTPRoutes } from '@pikku/core/http'",
|
|
183
|
-
"import { wireChannel } from '@pikku/core/channel'",
|
|
184
|
-
"import { wireCLI } from '@pikku/core/cli'",
|
|
185
|
-
'declare const refHTTP: (name: string) => any',
|
|
186
|
-
'declare const refChannel: (name: string) => any',
|
|
187
|
-
'declare const refCLI: (name: string) => any',
|
|
188
|
-
"wireAddon({ name: 'addon', package: '@test/addon' })",
|
|
189
|
-
"wireHTTPRoutes({ basePath: '/api', routes: { keep: refHTTP('addon:httpRoutes'), moved: refHTTP('addon:httpRoutes', { basePath: '/ext' }) } })",
|
|
190
|
-
"wireChannel({ name: 'live', route: '/live', auth: false, onMessageWiring: { action: refChannel('addon:channelRoutes') } })",
|
|
191
|
-
"wireCLI({ program: 'app', commands: { ...refCLI('addon:cliCommands') } })",
|
|
192
|
-
].join('\n')
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
const state = await inspect(logger, [appFile], { rootDir })
|
|
197
|
-
|
|
198
|
-
// Default: the addon contract's own basePath ('/addon') is preserved —
|
|
199
|
-
// the cosmetic slot key ('keep') does not affect the path.
|
|
200
|
-
const httpEntry = state.http.meta.get['/api/addon/todos']
|
|
201
|
-
assert.equal(httpEntry?.packageName, '@test/addon')
|
|
202
|
-
assert.equal(httpEntry?.pikkuFuncId, 'addon:listTodos')
|
|
203
|
-
|
|
204
|
-
// Override: the second-arg basePath ('/ext') replaces the addon's own.
|
|
205
|
-
const overridden = state.http.meta.get['/api/ext/todos']
|
|
206
|
-
assert.equal(overridden?.packageName, '@test/addon')
|
|
207
|
-
assert.equal(overridden?.pikkuFuncId, 'addon:listTodos')
|
|
208
|
-
|
|
209
|
-
const channelEntry =
|
|
210
|
-
state.channels.meta.live?.messageWirings.action?.subscribe
|
|
211
|
-
assert.equal(channelEntry?.packageName, '@test/addon')
|
|
212
|
-
assert.equal(channelEntry?.pikkuFuncId, 'addon:subscribeTodo')
|
|
213
|
-
|
|
214
|
-
const cliEntry = state.cli.meta.programs.app?.commands.sync
|
|
215
|
-
assert.equal(cliEntry?.packageName, '@test/addon')
|
|
216
|
-
assert.equal(cliEntry?.pikkuFuncId, 'addon:runSync')
|
|
217
|
-
} finally {
|
|
218
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
219
|
-
}
|
|
220
|
-
})
|
|
221
|
-
})
|
|
@@ -1,144 +0,0 @@
|
|
|
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 { ErrorCode } from '../error-codes.js'
|
|
8
|
-
import type { InspectorLogger, InspectorOptions } from '../types.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: ({ code, message }: any) => criticals.push({ code, message }),
|
|
19
|
-
critical: (code: any, message: string) => criticals.push({ code, message }),
|
|
20
|
-
hasCriticalErrors: () => criticals.length > 0,
|
|
21
|
-
} as InspectorLogger
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function run(source: string, allow: InspectorOptions['allow'] = {}) {
|
|
25
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-allow-'))
|
|
26
|
-
const file = join(rootDir, 'subject.ts')
|
|
27
|
-
await writeFile(file, source)
|
|
28
|
-
const criticals: Array<{ code: string; message: string }> = []
|
|
29
|
-
const state = await inspect(makeLogger(criticals), [file], {
|
|
30
|
-
rootDir,
|
|
31
|
-
allow,
|
|
32
|
-
})
|
|
33
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
34
|
-
return { state, criticals }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const has = (criticals: Array<{ code: string }>, code: ErrorCode) =>
|
|
38
|
-
criticals.some((c) => c.code === code)
|
|
39
|
-
|
|
40
|
-
const PERMISSIONS_IN_BODY = [
|
|
41
|
-
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
42
|
-
'export const stripeWebhook = pikkuSessionlessFunc({',
|
|
43
|
-
' expose: true,',
|
|
44
|
-
' permissionsInBody: true,',
|
|
45
|
-
' func: async () => ({ ok: true }),',
|
|
46
|
-
'})',
|
|
47
|
-
].join('\n')
|
|
48
|
-
|
|
49
|
-
const COMPLEX_WORKFLOW = [
|
|
50
|
-
"import { pikkuWorkflowComplexFunc } from '@pikku/core/workflow'",
|
|
51
|
-
'export const cleanup = pikkuWorkflowComplexFunc(',
|
|
52
|
-
' async (_services, _input, { workflow }: any) => {',
|
|
53
|
-
' for (const id of [1, 2, 3]) {',
|
|
54
|
-
" await workflow.step('touch', { id })",
|
|
55
|
-
' }',
|
|
56
|
-
' return { ok: true }',
|
|
57
|
-
' }',
|
|
58
|
-
')',
|
|
59
|
-
].join('\n')
|
|
60
|
-
|
|
61
|
-
describe('allow: escape hatches are refused unless opted into', () => {
|
|
62
|
-
test('permissionsInBody is a critical when not allowed', async () => {
|
|
63
|
-
const { criticals } = await run(PERMISSIONS_IN_BODY)
|
|
64
|
-
assert.ok(
|
|
65
|
-
has(criticals, ErrorCode.PERMISSIONS_IN_BODY_NOT_ALLOWED),
|
|
66
|
-
`expected PKU576, got ${JSON.stringify(criticals)}`
|
|
67
|
-
)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test('the message names the flag that would permit it', async () => {
|
|
71
|
-
const { criticals } = await run(PERMISSIONS_IN_BODY)
|
|
72
|
-
const found = criticals.find(
|
|
73
|
-
(c) => c.code === ErrorCode.PERMISSIONS_IN_BODY_NOT_ALLOWED
|
|
74
|
-
)
|
|
75
|
-
assert.match(found!.message, /allow: \{ permissionsInBody: true \}/)
|
|
76
|
-
assert.match(found!.message, /stripeWebhook/)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test('permissionsInBody is accepted once allowed', async () => {
|
|
80
|
-
const { criticals, state } = await run(PERMISSIONS_IN_BODY, {
|
|
81
|
-
permissionsInBody: true,
|
|
82
|
-
})
|
|
83
|
-
assert.equal(
|
|
84
|
-
has(criticals, ErrorCode.PERMISSIONS_IN_BODY_NOT_ALLOWED),
|
|
85
|
-
false
|
|
86
|
-
)
|
|
87
|
-
const meta = Object.values(state.functions.meta) as any[]
|
|
88
|
-
assert.equal(
|
|
89
|
-
meta.find((m) => m.name === 'stripeWebhook')?.permissionsInBody,
|
|
90
|
-
true
|
|
91
|
-
)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
test('a refused function is not registered at all', async () => {
|
|
95
|
-
const { state } = await run(PERMISSIONS_IN_BODY)
|
|
96
|
-
const meta = Object.values(state.functions.meta) as any[]
|
|
97
|
-
assert.equal(
|
|
98
|
-
meta.some((m) => m.name === 'stripeWebhook'),
|
|
99
|
-
false,
|
|
100
|
-
'a refused function must not reach the meta, or the build would emit it anyway'
|
|
101
|
-
)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
test('complexWorkflows is a critical when not allowed', async () => {
|
|
105
|
-
const { criticals } = await run(COMPLEX_WORKFLOW)
|
|
106
|
-
assert.ok(
|
|
107
|
-
has(criticals, ErrorCode.COMPLEX_WORKFLOW_NOT_ALLOWED),
|
|
108
|
-
`expected PKU643, got ${JSON.stringify(criticals)}`
|
|
109
|
-
)
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
test('the complex-workflow message points at the DSL', async () => {
|
|
113
|
-
const { criticals } = await run(COMPLEX_WORKFLOW)
|
|
114
|
-
const found = criticals.find(
|
|
115
|
-
(c) => c.code === ErrorCode.COMPLEX_WORKFLOW_NOT_ALLOWED
|
|
116
|
-
)
|
|
117
|
-
assert.match(found!.message, /pikkuWorkflowFunc/)
|
|
118
|
-
assert.match(found!.message, /allow: \{ complexWorkflows: true \}/)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
test('complexWorkflows is accepted once allowed', async () => {
|
|
122
|
-
const { criticals } = await run(COMPLEX_WORKFLOW, {
|
|
123
|
-
complexWorkflows: true,
|
|
124
|
-
})
|
|
125
|
-
assert.equal(has(criticals, ErrorCode.COMPLEX_WORKFLOW_NOT_ALLOWED), false)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
test('an ordinary function and DSL workflow are unaffected', async () => {
|
|
129
|
-
const { criticals } = await run(
|
|
130
|
-
[
|
|
131
|
-
"import { pikkuFunc } from '@pikku/core'",
|
|
132
|
-
'export const ordinary = pikkuFunc({',
|
|
133
|
-
' expose: true,',
|
|
134
|
-
' func: async () => ({ ok: true }),',
|
|
135
|
-
'})',
|
|
136
|
-
].join('\n')
|
|
137
|
-
)
|
|
138
|
-
assert.equal(
|
|
139
|
-
has(criticals, ErrorCode.PERMISSIONS_IN_BODY_NOT_ALLOWED),
|
|
140
|
-
false
|
|
141
|
-
)
|
|
142
|
-
assert.equal(has(criticals, ErrorCode.COMPLEX_WORKFLOW_NOT_ALLOWED), false)
|
|
143
|
-
})
|
|
144
|
-
})
|
|
@@ -1,75 +0,0 @@
|
|
|
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
|
-
|
|
9
|
-
const silentLogger = (): InspectorLogger =>
|
|
10
|
-
({
|
|
11
|
-
debug: () => {},
|
|
12
|
-
info: () => {},
|
|
13
|
-
warn: () => {},
|
|
14
|
-
error: () => {},
|
|
15
|
-
diagnostic: () => {},
|
|
16
|
-
critical: () => {},
|
|
17
|
-
hasCriticalErrors: () => false,
|
|
18
|
-
}) as InspectorLogger
|
|
19
|
-
|
|
20
|
-
async function metaFor(source: string) {
|
|
21
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-audit-'))
|
|
22
|
-
const file = join(rootDir, 'subject.ts')
|
|
23
|
-
await writeFile(file, source)
|
|
24
|
-
const state = await inspect(silentLogger(), [file], { rootDir })
|
|
25
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
26
|
-
return Object.values(state.functions.meta) as any[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const func = (name: string, auditLine: string | null) =>
|
|
30
|
-
[
|
|
31
|
-
"import { pikkuFunc } from '@pikku/core'",
|
|
32
|
-
`export const ${name} = pikkuFunc({`,
|
|
33
|
-
...(auditLine ? [` ${auditLine}`] : []),
|
|
34
|
-
' func: async () => ({ ok: true }),',
|
|
35
|
-
'})',
|
|
36
|
-
].join('\n')
|
|
37
|
-
|
|
38
|
-
describe('the audit flag reaches the function meta', () => {
|
|
39
|
-
test('`audit: true` resolves to best-effort', async () => {
|
|
40
|
-
const meta = await metaFor(func('cancelInvoice', 'audit: true,'))
|
|
41
|
-
assert.deepEqual(meta.find((m) => m.name === 'cancelInvoice')?.audit, {
|
|
42
|
-
durability: 'best-effort',
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
test('an explicit durability is carried through', async () => {
|
|
47
|
-
const meta = await metaFor(
|
|
48
|
-
func('chargeCard', "audit: { durability: 'transactional' },")
|
|
49
|
-
)
|
|
50
|
-
assert.deepEqual(meta.find((m) => m.name === 'chargeCard')?.audit, {
|
|
51
|
-
durability: 'transactional',
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test('an object without a durability defaults to best-effort', async () => {
|
|
56
|
-
const meta = await metaFor(func('refund', 'audit: {},'))
|
|
57
|
-
assert.deepEqual(meta.find((m) => m.name === 'refund')?.audit, {
|
|
58
|
-
durability: 'best-effort',
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
// Absent rather than `false`: an unmarked function drops `auditLog.write()`
|
|
63
|
-
// with a warning, so "no audit key" has to be the honest reading of it.
|
|
64
|
-
test('a function with no audit config carries no audit key', async () => {
|
|
65
|
-
const meta = await metaFor(func('listInvoices', null))
|
|
66
|
-
const found = meta.find((m) => m.name === 'listInvoices')
|
|
67
|
-
assert.ok(found, 'the function should still be registered')
|
|
68
|
-
assert.equal(found.audit, undefined)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('`audit: false` is the same as not declaring it', async () => {
|
|
72
|
-
const meta = await metaFor(func('healthCheck', 'audit: false,'))
|
|
73
|
-
assert.equal(meta.find((m) => m.name === 'healthCheck')?.audit, undefined)
|
|
74
|
-
})
|
|
75
|
-
})
|