@pikku/core 0.12.63 → 0.12.66
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 +368 -0
- package/dist/dev/hot-reload.js +13 -55
- package/dist/dev/module-runner.d.ts +14 -0
- package/dist/dev/module-runner.js +59 -0
- package/dist/dev/reload-meta.d.ts +13 -0
- package/dist/dev/reload-meta.js +22 -0
- package/dist/errors/errors.d.ts +12 -0
- package/dist/errors/errors.js +19 -0
- package/dist/function/function-runner.d.ts +5 -5
- package/dist/function/function-runner.js +39 -111
- package/dist/function/functions.types.d.ts +17 -7
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/middleware/auth-apikey.d.ts +1 -0
- package/dist/middleware/auth-bearer.d.ts +1 -0
- package/dist/middleware/auth-cookie.d.ts +1 -0
- package/dist/middleware/cors.d.ts +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.d.ts +1 -0
- package/dist/middleware/telemetry.d.ts +2 -0
- package/dist/middleware/timeout.d.ts +1 -0
- package/dist/permissions.d.ts +42 -27
- package/dist/permissions.js +101 -203
- package/dist/pikku-state.js +0 -2
- package/dist/scopes.d.ts +17 -0
- package/dist/scopes.js +59 -0
- package/dist/services/ai-embedding-service.d.ts +31 -0
- package/dist/services/ai-embedding-service.js +1 -0
- package/dist/services/credential-wire-service.d.ts +3 -1
- package/dist/services/credential-wire-service.js +10 -4
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.js +2 -0
- package/dist/services/meta-service.d.ts +15 -2
- package/dist/services/meta-service.js +51 -1
- package/dist/services/queue-webhook-service.d.ts +55 -0
- package/dist/services/queue-webhook-service.js +136 -0
- package/dist/services/scope-service.d.ts +67 -0
- package/dist/services/scope-service.js +1 -0
- package/dist/services/typed-secret-service.d.ts +8 -0
- package/dist/services/typed-secret-service.js +39 -4
- package/dist/services/webhook-service.d.ts +140 -0
- package/dist/services/webhook-service.js +44 -0
- package/dist/services/workflow-service.d.ts +7 -0
- package/dist/testing/service-tests.js +37 -0
- package/dist/types/core.types.d.ts +34 -6
- package/dist/types/state.types.d.ts +20 -2
- package/dist/utils/hmac.d.ts +16 -0
- package/dist/utils/hmac.js +26 -0
- package/dist/utils/safe-fetch.d.ts +51 -0
- package/dist/utils/safe-fetch.js +192 -0
- package/dist/wirings/ai-agent/ai-agent-agui.d.ts +84 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +305 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +105 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +249 -11
- package/dist/wirings/ai-agent/ai-agent-runner.js +10 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +88 -16
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +56 -2
- package/dist/wirings/ai-agent/index.d.ts +2 -1
- package/dist/wirings/ai-agent/index.js +2 -1
- package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
- package/dist/wirings/ai-agent/voice-input.js +5 -41
- package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
- package/dist/wirings/channel/channel-common.js +0 -1
- package/dist/wirings/channel/channel-handler.js +1 -4
- package/dist/wirings/channel/channel-middleware-runner.d.ts +12 -0
- package/dist/wirings/channel/channel-middleware-runner.js +34 -19
- package/dist/wirings/channel/channel.types.d.ts +2 -6
- package/dist/wirings/cli/cli-runner.js +3 -2
- package/dist/wirings/cli/cli.types.d.ts +1 -2
- package/dist/wirings/cli/command-parser.d.ts +2 -0
- package/dist/wirings/cli/command-parser.js +59 -2
- package/dist/wirings/credential/credential.types.d.ts +14 -0
- package/dist/wirings/credential/validate-credential-definitions.js +1 -0
- package/dist/wirings/gateway/gateway-runner.js +100 -50
- package/dist/wirings/gateway/gateway.types.d.ts +8 -5
- package/dist/wirings/http/http-routes.js +0 -3
- package/dist/wirings/http/http-runner.d.ts +9 -29
- package/dist/wirings/http/http-runner.js +9 -34
- package/dist/wirings/http/http.types.d.ts +4 -12
- package/dist/wirings/mcp/mcp-runner.js +0 -2
- package/dist/wirings/mcp/mcp.types.d.ts +5 -11
- package/dist/wirings/oauth2/index.d.ts +0 -3
- package/dist/wirings/oauth2/index.js +1 -2
- package/dist/wirings/rpc/addon-runner.d.ts +28 -0
- package/dist/wirings/rpc/addon-runner.js +173 -0
- package/dist/wirings/rpc/index.d.ts +5 -1
- package/dist/wirings/rpc/index.js +3 -1
- package/dist/wirings/rpc/remote-addon-auth.d.ts +26 -0
- package/dist/wirings/rpc/remote-addon-auth.js +43 -0
- package/dist/wirings/rpc/rpc-runner.d.ts +19 -0
- package/dist/wirings/rpc/rpc-runner.js +109 -3
- package/dist/wirings/rpc/rpc-types.d.ts +15 -1
- package/dist/wirings/rpc/wire-addon.js +9 -0
- package/dist/wirings/rpc/wire-remote-addon.d.ts +47 -0
- package/dist/wirings/rpc/wire-remote-addon.js +19 -0
- package/dist/wirings/scope/index.d.ts +3 -0
- package/dist/wirings/scope/index.js +2 -0
- package/dist/wirings/scope/scope.types.d.ts +39 -0
- package/dist/wirings/scope/scope.types.js +1 -0
- package/dist/wirings/scope/validate-scope-definitions.d.ts +16 -0
- package/dist/wirings/scope/validate-scope-definitions.js +76 -0
- package/dist/wirings/scope/wire-scope.d.ts +33 -0
- package/dist/wirings/scope/wire-scope.js +32 -0
- package/dist/wirings/secret/secret.types.d.ts +14 -0
- package/dist/wirings/secret/validate-secret-definitions.js +2 -0
- package/dist/wirings/variable/validate-variable-definitions.js +2 -0
- package/dist/wirings/variable/variable.types.d.ts +14 -0
- package/dist/wirings/workflow/dsl/index.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +89 -7
- package/dist/wirings/workflow/graph/graph-node.d.ts +2 -0
- package/dist/wirings/workflow/graph/graph-runner.js +3 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +6 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.js +1 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +5 -0
- package/dist/wirings/workflow/index.d.ts +2 -2
- package/dist/wirings/workflow/index.js +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +59 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +187 -0
- package/dist/wirings/workflow/workflow.types.d.ts +1 -3
- package/package.json +6 -1
- package/run-tests.sh +1 -0
- package/src/dev/hot-reload.ts +13 -68
- package/src/dev/module-runner.test.ts +169 -0
- package/src/dev/module-runner.ts +103 -0
- package/src/dev/reload-meta.test.ts +31 -2
- package/src/dev/reload-meta.ts +26 -0
- package/src/errors/errors.ts +24 -0
- package/src/function/function-runner.test.ts +276 -142
- package/src/function/function-runner.ts +52 -145
- package/src/function/functions.types.ts +28 -38
- package/src/index.ts +8 -6
- package/src/middleware/index.ts +1 -5
- package/src/permissions.test.ts +160 -373
- package/src/permissions.ts +137 -279
- package/src/pikku-state.ts +0 -2
- package/src/scopes.test.ts +167 -0
- package/src/scopes.ts +69 -0
- package/src/services/ai-embedding-service.ts +31 -0
- package/src/services/credential-wire-service.ts +10 -4
- package/src/services/index.ts +20 -0
- package/src/services/meta-service.ts +68 -3
- package/src/services/queue-webhook-service.test.ts +408 -0
- package/src/services/queue-webhook-service.ts +182 -0
- package/src/services/scope-service.ts +75 -0
- package/src/services/typed-secret-service.test.ts +35 -0
- package/src/services/typed-secret-service.ts +39 -4
- package/src/services/webhook-service.ts +180 -0
- package/src/services/workflow-service.ts +7 -0
- package/src/testing/service-tests.ts +49 -0
- package/src/types/core.types.ts +42 -14
- package/src/types/state.types.ts +23 -3
- package/src/utils/hmac.ts +27 -0
- package/src/utils/safe-fetch.test.ts +373 -0
- package/src/utils/safe-fetch.ts +213 -0
- package/src/wirings/ai-agent/ai-agent-agui.test.ts +1127 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +386 -0
- package/src/wirings/ai-agent/ai-agent-authorization.test.ts +204 -0
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +483 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +324 -13
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +207 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
- package/src/wirings/ai-agent/ai-agent-runner.ts +28 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +301 -10
- package/src/wirings/ai-agent/ai-agent-stream.ts +122 -16
- package/src/wirings/ai-agent/ai-agent.types.ts +57 -0
- package/src/wirings/ai-agent/index.ts +5 -0
- package/src/wirings/ai-agent/voice-input.test.ts +90 -0
- package/src/wirings/ai-agent/voice-input.ts +9 -40
- package/src/wirings/channel/channel-common.ts +0 -1
- package/src/wirings/channel/channel-handler.ts +1 -9
- package/src/wirings/channel/channel-middleware-runner.test.ts +28 -2
- package/src/wirings/channel/channel-middleware-runner.ts +39 -27
- package/src/wirings/channel/channel.types.ts +0 -6
- package/src/wirings/cli/cli-runner.ts +4 -2
- package/src/wirings/cli/cli.types.ts +0 -2
- package/src/wirings/cli/command-parser.test.ts +130 -0
- package/src/wirings/cli/command-parser.ts +80 -2
- package/src/wirings/credential/credential.types.ts +14 -0
- package/src/wirings/credential/validate-credential-definitions.ts +1 -0
- package/src/wirings/gateway/gateway-authorization.test.ts +444 -0
- package/src/wirings/gateway/gateway-runner.ts +114 -68
- package/src/wirings/gateway/gateway.types.ts +7 -9
- package/src/wirings/http/http-routes.ts +0 -3
- package/src/wirings/http/http-runner.test.ts +1 -11
- package/src/wirings/http/http-runner.ts +16 -39
- package/src/wirings/http/http.types.ts +7 -14
- package/src/wirings/mcp/mcp-runner.ts +0 -2
- package/src/wirings/mcp/mcp.types.ts +3 -11
- package/src/wirings/oauth2/index.ts +0 -3
- package/src/wirings/rpc/addon-runner.ts +233 -0
- package/src/wirings/rpc/index.ts +17 -1
- package/src/wirings/rpc/remote-addon-auth.ts +69 -0
- package/src/wirings/rpc/rpc-runner.test.ts +319 -16
- package/src/wirings/rpc/rpc-runner.ts +155 -3
- package/src/wirings/rpc/rpc-types.ts +16 -5
- package/src/wirings/rpc/wire-addon.test.ts +6 -3
- package/src/wirings/rpc/wire-addon.ts +9 -0
- package/src/wirings/rpc/wire-remote-addon.ts +57 -0
- package/src/wirings/scope/index.ts +14 -0
- package/src/wirings/scope/scope.test.ts +135 -0
- package/src/wirings/scope/scope.types.ts +44 -0
- package/src/wirings/scope/validate-scope-definitions.ts +110 -0
- package/src/wirings/scope/wire-scope.ts +34 -0
- package/src/wirings/secret/secret.types.ts +14 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -0
- package/src/wirings/variable/validate-variable-definitions.ts +2 -0
- package/src/wirings/variable/variable.types.ts +14 -0
- package/src/wirings/workflow/dsl/index.ts +4 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +97 -6
- package/src/wirings/workflow/graph/graph-node.ts +2 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +58 -0
- package/src/wirings/workflow/graph/graph-runner.ts +3 -0
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +5 -0
- package/src/wirings/workflow/index.ts +5 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +341 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +272 -0
- package/src/wirings/workflow/workflow-on-error.test.ts +154 -0
- package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
- package/src/wirings/workflow/workflow.types.ts +4 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/src/wirings/oauth2/oauth2-client.test.ts +0 -930
- package/src/wirings/oauth2/oauth2-client.ts +0 -331
- package/src/wirings/oauth2/oauth2-routes.ts +0 -234
|
@@ -36,6 +36,18 @@ export const clearChannelMiddlewareCache = () => {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Combine the inherited (tag/named) channel middleware with any per-run
|
|
41
|
+
* middleware for a wiring.
|
|
42
|
+
*
|
|
43
|
+
* Only the statically-resolved inherited middleware is deterministic per `uid`
|
|
44
|
+
* and safe to cache. `wireChannelMiddleware` is a per-run set of closures (e.g.
|
|
45
|
+
* an AI agent's per-invocation stream middleware holding that run's
|
|
46
|
+
* thread/session state) and MUST NOT be cached — caching it lets a later run of
|
|
47
|
+
* the same `uid` reuse an earlier run's closures, leaking that run's state (and
|
|
48
|
+
* growing memory) across invocations. It is therefore appended fresh on every
|
|
49
|
+
* call after the cached inherited slice.
|
|
50
|
+
*/
|
|
39
51
|
export const combineChannelMiddleware = (
|
|
40
52
|
wireType: string,
|
|
41
53
|
uid: string,
|
|
@@ -50,40 +62,40 @@ export const combineChannelMiddleware = (
|
|
|
50
62
|
} = {}
|
|
51
63
|
): readonly CorePikkuChannelMiddleware[] => {
|
|
52
64
|
const cacheKey = `${wireType}:${uid}`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (middleware) {
|
|
72
|
-
resolved.push(middleware)
|
|
65
|
+
let inherited = channelMiddlewareCache[cacheKey]
|
|
66
|
+
if (!inherited) {
|
|
67
|
+
const resolved: CorePikkuChannelMiddleware[] = []
|
|
68
|
+
if (wireInheritedChannelMiddleware) {
|
|
69
|
+
for (const meta of wireInheritedChannelMiddleware) {
|
|
70
|
+
if (meta.type === 'tag') {
|
|
71
|
+
const groups = getTagGroups(
|
|
72
|
+
pikkuState(packageName, 'channelMiddleware', 'tagGroup'),
|
|
73
|
+
meta.tag
|
|
74
|
+
)
|
|
75
|
+
for (const group of groups) {
|
|
76
|
+
resolved.push(...group)
|
|
77
|
+
}
|
|
78
|
+
} else if (meta.type === 'wire') {
|
|
79
|
+
const middleware = getChannelMiddlewareByName(meta.name)
|
|
80
|
+
if (middleware) {
|
|
81
|
+
resolved.push(middleware)
|
|
82
|
+
}
|
|
73
83
|
}
|
|
74
84
|
}
|
|
75
85
|
}
|
|
86
|
+
inherited = channelMiddlewareCache[cacheKey] = freezeDedupe(
|
|
87
|
+
resolved
|
|
88
|
+
) as readonly CorePikkuChannelMiddleware[]
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
if (wireChannelMiddleware) {
|
|
79
|
-
|
|
91
|
+
if (!wireChannelMiddleware?.length) {
|
|
92
|
+
return inherited
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return channelMiddlewareCache[cacheKey]
|
|
95
|
+
return freezeDedupe([
|
|
96
|
+
...inherited,
|
|
97
|
+
...wireChannelMiddleware,
|
|
98
|
+
]) as readonly CorePikkuChannelMiddleware[]
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
export function wrapChannelWithMiddleware<Out>(
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
import type {
|
|
7
7
|
CorePikkuMiddleware,
|
|
8
8
|
MiddlewareMetadata,
|
|
9
|
-
PermissionMetadata,
|
|
10
9
|
} from '../../types/core.types.js'
|
|
11
10
|
|
|
12
11
|
export type BinaryData = ArrayBuffer | Uint8Array
|
|
@@ -23,7 +22,6 @@ export type CorePikkuChannelMiddlewareFactory<
|
|
|
23
22
|
Event = unknown,
|
|
24
23
|
> = (input: In) => CorePikkuChannelMiddleware<Services, Event>
|
|
25
24
|
import type {
|
|
26
|
-
CorePermissionGroup,
|
|
27
25
|
CorePikkuFunction,
|
|
28
26
|
CorePikkuFunctionConfig,
|
|
29
27
|
CorePikkuFunctionSessionless,
|
|
@@ -52,7 +50,6 @@ export interface ChannelMessageMeta {
|
|
|
52
50
|
errors?: string[]
|
|
53
51
|
tags?: string[]
|
|
54
52
|
middleware?: MiddlewareMetadata[]
|
|
55
|
-
permissions?: PermissionMetadata[]
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
export interface ChannelMeta {
|
|
@@ -72,7 +69,6 @@ export interface ChannelMeta {
|
|
|
72
69
|
errors?: string[]
|
|
73
70
|
tags?: string[]
|
|
74
71
|
middleware?: MiddlewareMetadata[] // Pre-resolved middleware chain (tag + explicit)
|
|
75
|
-
permissions?: PermissionMetadata[] // Pre-resolved permission chain (tag + explicit)
|
|
76
72
|
channelMiddleware?: MiddlewareMetadata[]
|
|
77
73
|
}
|
|
78
74
|
|
|
@@ -123,7 +119,6 @@ export type CoreChannel<
|
|
|
123
119
|
| ChannelFunctionMessage
|
|
124
120
|
| {
|
|
125
121
|
func: ChannelFunctionMessage
|
|
126
|
-
permissions?: CorePermissionGroup<PikkuPermission>
|
|
127
122
|
auth?: boolean
|
|
128
123
|
middleware?: PikkuMiddleware[]
|
|
129
124
|
}
|
|
@@ -133,7 +128,6 @@ export type CoreChannel<
|
|
|
133
128
|
channelMiddleware?: Array<
|
|
134
129
|
CorePikkuChannelMiddleware | CorePikkuChannelMiddlewareFactory
|
|
135
130
|
>
|
|
136
|
-
permissions?: CorePermissionGroup<PikkuPermission>
|
|
137
131
|
auth?: boolean
|
|
138
132
|
binary?: boolean | null
|
|
139
133
|
onBinaryMessage?: (
|
|
@@ -374,8 +374,6 @@ export async function runCLICommand({
|
|
|
374
374
|
auth: false,
|
|
375
375
|
inheritedMiddleware: currentCommand.middleware,
|
|
376
376
|
wireMiddleware: allWireMiddleware,
|
|
377
|
-
inheritedPermissions: currentCommand.permissions,
|
|
378
|
-
wirePermissions: undefined,
|
|
379
377
|
coerceDataFromSchema: true,
|
|
380
378
|
tags: programData?.tags,
|
|
381
379
|
wire,
|
|
@@ -492,6 +490,10 @@ export async function executeCLI({
|
|
|
492
490
|
return
|
|
493
491
|
}
|
|
494
492
|
|
|
493
|
+
// Non-fatal diagnostics (unknown options are still accepted) go to stderr
|
|
494
|
+
// so they never pollute a command's machine-readable stdout.
|
|
495
|
+
parsed.warnings.forEach((warning) => console.error(`Warning: ${warning}`))
|
|
496
|
+
|
|
495
497
|
if (parsed.errors.length > 0) {
|
|
496
498
|
// Check if any error is about an unknown command
|
|
497
499
|
const hasUnknownCommand = parsed.errors.some(
|
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
CoreUserSession,
|
|
5
5
|
CoreServices,
|
|
6
6
|
MiddlewareMetadata,
|
|
7
|
-
PermissionMetadata,
|
|
8
7
|
} from '../../types/core.types.js'
|
|
9
8
|
import type {
|
|
10
9
|
CorePikkuFunctionConfig,
|
|
@@ -80,7 +79,6 @@ export interface CLICommandMeta {
|
|
|
80
79
|
tags?: string[]
|
|
81
80
|
subcommands?: Record<string, CLICommandMeta>
|
|
82
81
|
middleware?: MiddlewareMetadata[] // Pre-resolved middleware chain (tag + explicit)
|
|
83
|
-
permissions?: PermissionMetadata[] // Pre-resolved permission chain (tag + explicit)
|
|
84
82
|
isDefault?: boolean
|
|
85
83
|
}
|
|
86
84
|
|
|
@@ -270,6 +270,7 @@ describe('Command Parser', () => {
|
|
|
270
270
|
// Unknown options are allowed (for forward compatibility)
|
|
271
271
|
// They just won't have defaults or validation
|
|
272
272
|
assert.strictEqual(result.options.unknown, true)
|
|
273
|
+
assert.strictEqual(result.errors.length, 0)
|
|
273
274
|
})
|
|
274
275
|
|
|
275
276
|
test('should report error for unknown short flag', () => {
|
|
@@ -525,4 +526,133 @@ describe('Command Parser', () => {
|
|
|
525
526
|
)
|
|
526
527
|
})
|
|
527
528
|
})
|
|
529
|
+
|
|
530
|
+
describe('unknown long options', () => {
|
|
531
|
+
const listMeta: CLIMeta = {
|
|
532
|
+
programs: {
|
|
533
|
+
'test-cli': {
|
|
534
|
+
program: 'test-cli',
|
|
535
|
+
options: {},
|
|
536
|
+
commands: {
|
|
537
|
+
list: {
|
|
538
|
+
pikkuFuncId: 'listFunc',
|
|
539
|
+
positionals: [],
|
|
540
|
+
options: {
|
|
541
|
+
section: { description: 'Section to list', default: 'all' },
|
|
542
|
+
autoApply: {
|
|
543
|
+
description: 'Apply automatically',
|
|
544
|
+
default: false,
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
renderers: {},
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
test('warns (but does not error) on an unknown --opt value option', () => {
|
|
555
|
+
const result = parseCLIArguments(
|
|
556
|
+
['list', '--sektion', 'functions'],
|
|
557
|
+
'test-cli',
|
|
558
|
+
listMeta
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
assert.strictEqual(result.errors.length, 0, 'should stay non-fatal')
|
|
562
|
+
assert.strictEqual(result.warnings.length, 1)
|
|
563
|
+
assert.ok(
|
|
564
|
+
result.warnings[0].startsWith('Unknown option: --sektion (ignored)'),
|
|
565
|
+
`unexpected warning: ${result.warnings[0]}`
|
|
566
|
+
)
|
|
567
|
+
// Forward compatibility: the value is still parsed through
|
|
568
|
+
assert.strictEqual(result.options.sektion, 'functions')
|
|
569
|
+
// ...and the real option keeps its default
|
|
570
|
+
assert.strictEqual(result.options.section, 'all')
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
test('warns on an unknown --opt=value option', () => {
|
|
574
|
+
const result = parseCLIArguments(
|
|
575
|
+
['list', '--sektion=functions'],
|
|
576
|
+
'test-cli',
|
|
577
|
+
listMeta
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
assert.strictEqual(result.errors.length, 0)
|
|
581
|
+
assert.strictEqual(result.warnings.length, 1)
|
|
582
|
+
assert.ok(
|
|
583
|
+
result.warnings[0].startsWith('Unknown option: --sektion (ignored)'),
|
|
584
|
+
`unexpected warning: ${result.warnings[0]}`
|
|
585
|
+
)
|
|
586
|
+
assert.strictEqual(result.options.sektion, 'functions')
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
test('suggests a near-miss option name', () => {
|
|
590
|
+
const result = parseCLIArguments(
|
|
591
|
+
['list', '--sektion'],
|
|
592
|
+
'test-cli',
|
|
593
|
+
listMeta
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
assert.ok(
|
|
597
|
+
result.warnings[0].includes('Did you mean --section?'),
|
|
598
|
+
`expected a suggestion, got: ${result.warnings[0]}`
|
|
599
|
+
)
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
test('suggests the kebab-case rendering of a camelCase option', () => {
|
|
603
|
+
const result = parseCLIArguments(
|
|
604
|
+
['list', '--auto-aply'],
|
|
605
|
+
'test-cli',
|
|
606
|
+
listMeta
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
assert.ok(
|
|
610
|
+
result.warnings[0].includes('Did you mean --auto-apply?'),
|
|
611
|
+
`expected a suggestion, got: ${result.warnings[0]}`
|
|
612
|
+
)
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
test('omits the suggestion when nothing is close', () => {
|
|
616
|
+
const result = parseCLIArguments(
|
|
617
|
+
['list', '--completely-different'],
|
|
618
|
+
'test-cli',
|
|
619
|
+
listMeta
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
assert.strictEqual(
|
|
623
|
+
result.warnings[0],
|
|
624
|
+
'Unknown option: --completely-different (ignored)'
|
|
625
|
+
)
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
test('does not warn for a known option (either casing)', () => {
|
|
629
|
+
const kebab = parseCLIArguments(
|
|
630
|
+
['list', '--section', 'functions', '--auto-apply'],
|
|
631
|
+
'test-cli',
|
|
632
|
+
listMeta
|
|
633
|
+
)
|
|
634
|
+
assert.deepStrictEqual(kebab.warnings, [])
|
|
635
|
+
assert.strictEqual(kebab.options.section, 'functions')
|
|
636
|
+
|
|
637
|
+
const camel = parseCLIArguments(
|
|
638
|
+
['list', '--autoApply'],
|
|
639
|
+
'test-cli',
|
|
640
|
+
listMeta
|
|
641
|
+
)
|
|
642
|
+
assert.deepStrictEqual(camel.warnings, [])
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
test('does not warn for --help', () => {
|
|
646
|
+
const result = parseCLIArguments(['list', '--help'], 'test-cli', listMeta)
|
|
647
|
+
|
|
648
|
+
assert.deepStrictEqual(result.warnings, [])
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
test('unknown short flags still error, not warn', () => {
|
|
652
|
+
const result = parseCLIArguments(['list', '-x'], 'test-cli', listMeta)
|
|
653
|
+
|
|
654
|
+
assert.ok(result.errors.some((e) => e.includes('Unknown option: -x')))
|
|
655
|
+
assert.deepStrictEqual(result.warnings, [])
|
|
656
|
+
})
|
|
657
|
+
})
|
|
528
658
|
})
|
|
@@ -18,6 +18,71 @@ function toKebabCase(str: string): string {
|
|
|
18
18
|
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Options the runner handles itself — never reported as unknown. */
|
|
22
|
+
const RESERVED_OPTIONS = new Set(['help'])
|
|
23
|
+
|
|
24
|
+
/** Levenshtein distance, capped-free and dependency-free. Used only to suggest
|
|
25
|
+
* a near-miss option name, so the naive O(n*m) implementation is fine. */
|
|
26
|
+
function levenshtein(a: string, b: string): number {
|
|
27
|
+
if (a === b) return 0
|
|
28
|
+
if (a.length === 0) return b.length
|
|
29
|
+
if (b.length === 0) return a.length
|
|
30
|
+
|
|
31
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i)
|
|
32
|
+
for (let i = 1; i <= a.length; i++) {
|
|
33
|
+
const row = [i]
|
|
34
|
+
for (let j = 1; j <= b.length; j++) {
|
|
35
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1
|
|
36
|
+
row[j] = Math.min(row[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost)
|
|
37
|
+
}
|
|
38
|
+
prev = row
|
|
39
|
+
}
|
|
40
|
+
return prev[b.length]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Finds the closest declared option (distance <= 2) to what the user typed.
|
|
44
|
+
* Compares against the kebab-case rendering, since that is what is typed. */
|
|
45
|
+
function suggestOption(
|
|
46
|
+
typed: string,
|
|
47
|
+
availableOptions: Record<string, CLIOption>
|
|
48
|
+
): string | null {
|
|
49
|
+
let best: string | null = null
|
|
50
|
+
let bestDistance = 3
|
|
51
|
+
|
|
52
|
+
for (const name of Object.keys(availableOptions)) {
|
|
53
|
+
const kebab = toKebabCase(name)
|
|
54
|
+
const distance = Math.min(
|
|
55
|
+
levenshtein(typed, kebab),
|
|
56
|
+
levenshtein(typed, name)
|
|
57
|
+
)
|
|
58
|
+
if (distance < bestDistance) {
|
|
59
|
+
bestDistance = distance
|
|
60
|
+
best = kebab
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return best
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Records a warning that an unknown long option was accepted but ignored.
|
|
68
|
+
* Unknown options stay non-fatal for forward compatibility (a newer command
|
|
69
|
+
* version may understand them) — they are just no longer silent. */
|
|
70
|
+
function warnUnknownOption(
|
|
71
|
+
typed: string,
|
|
72
|
+
availableOptions: Record<string, CLIOption>,
|
|
73
|
+
result: ParsedCommand
|
|
74
|
+
) {
|
|
75
|
+
if (RESERVED_OPTIONS.has(toCamelCase(typed))) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const suggestion = suggestOption(typed, availableOptions)
|
|
80
|
+
result.warnings.push(
|
|
81
|
+
`Unknown option: --${typed} (ignored)` +
|
|
82
|
+
(suggestion ? ` Did you mean --${suggestion}?` : '')
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
21
86
|
/**
|
|
22
87
|
* Result of parsing CLI arguments
|
|
23
88
|
*/
|
|
@@ -27,6 +92,8 @@ export interface ParsedCommand {
|
|
|
27
92
|
positionals: Record<string, any>
|
|
28
93
|
options: Record<string, any>
|
|
29
94
|
errors: string[]
|
|
95
|
+
/** Non-fatal diagnostics (e.g. unknown options that were accepted+ignored) */
|
|
96
|
+
warnings: string[]
|
|
30
97
|
}
|
|
31
98
|
|
|
32
99
|
/**
|
|
@@ -43,6 +110,7 @@ export function parseCLIArguments(
|
|
|
43
110
|
positionals: {},
|
|
44
111
|
options: {},
|
|
45
112
|
errors: [],
|
|
113
|
+
warnings: [],
|
|
46
114
|
}
|
|
47
115
|
|
|
48
116
|
const meta = allMeta.programs[programName]
|
|
@@ -148,7 +216,12 @@ export function parseCLIArguments(
|
|
|
148
216
|
const key = toCamelCase(arg.slice(2, equalIndex))
|
|
149
217
|
const optionDef = availableOptions[key]
|
|
150
218
|
|
|
151
|
-
// Unknown options are allowed for forward compatibility
|
|
219
|
+
// Unknown options are allowed for forward compatibility, but warned
|
|
220
|
+
// about so they are not silently dropped by the input schema.
|
|
221
|
+
if (!optionDef) {
|
|
222
|
+
warnUnknownOption(arg.slice(2, equalIndex), availableOptions, result)
|
|
223
|
+
}
|
|
224
|
+
|
|
152
225
|
const value = arg.slice(equalIndex + 1)
|
|
153
226
|
optionArgs[key] = parseOptionValue(value, optionDef)
|
|
154
227
|
} else {
|
|
@@ -156,7 +229,12 @@ export function parseCLIArguments(
|
|
|
156
229
|
const key = toCamelCase(arg.slice(2))
|
|
157
230
|
const optionDef = availableOptions[key]
|
|
158
231
|
|
|
159
|
-
// Unknown options are allowed for forward compatibility
|
|
232
|
+
// Unknown options are allowed for forward compatibility, but warned
|
|
233
|
+
// about so they are not silently dropped by the input schema.
|
|
234
|
+
if (!optionDef) {
|
|
235
|
+
warnUnknownOption(arg.slice(2), availableOptions, result)
|
|
236
|
+
}
|
|
237
|
+
|
|
160
238
|
if (optionDef && optionDef.array) {
|
|
161
239
|
// Array option - collect all following non-flag values
|
|
162
240
|
currentIndex++
|
|
@@ -6,6 +6,13 @@ export type CoreCredential<T = unknown> = {
|
|
|
6
6
|
description?: string
|
|
7
7
|
type: 'singleton' | 'wire'
|
|
8
8
|
schema: T
|
|
9
|
+
/**
|
|
10
|
+
* Link to documentation explaining how to obtain this value — a provider's
|
|
11
|
+
* API-key page, a setup guide, an internal runbook. Surfaced by consoles and
|
|
12
|
+
* deploy UIs so a user facing a missing value has somewhere to go instead of
|
|
13
|
+
* an opaque identifier.
|
|
14
|
+
*/
|
|
15
|
+
docsUrl?: string
|
|
9
16
|
oauth2?: OAuth2CredentialConfig & {
|
|
10
17
|
appCredentialSecretId: string
|
|
11
18
|
}
|
|
@@ -17,6 +24,13 @@ export type CredentialDefinitionMeta = {
|
|
|
17
24
|
description?: string
|
|
18
25
|
type: 'singleton' | 'wire'
|
|
19
26
|
schema?: Record<string, unknown> | string
|
|
27
|
+
/**
|
|
28
|
+
* Link to documentation explaining how to obtain this value — a provider's
|
|
29
|
+
* API-key page, a setup guide, an internal runbook. Surfaced by consoles and
|
|
30
|
+
* deploy UIs so a user facing a missing value has somewhere to go instead of
|
|
31
|
+
* an opaque identifier.
|
|
32
|
+
*/
|
|
33
|
+
docsUrl?: string
|
|
20
34
|
oauth2?: OAuth2CredentialConfig & {
|
|
21
35
|
appCredentialSecretId: string
|
|
22
36
|
}
|