effect 4.0.0-beta.25 → 4.0.0-beta.26
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/dist/Config.js +4 -4
- package/dist/Config.js.map +1 -1
- package/dist/Effect.d.ts +20 -13
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +2 -1
- package/dist/Effect.js.map +1 -1
- package/dist/Runtime.d.ts +66 -0
- package/dist/Runtime.d.ts.map +1 -1
- package/dist/Runtime.js +72 -5
- package/dist/Runtime.js.map +1 -1
- package/dist/Schema.d.ts +15 -32
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +9 -17
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaAST.js +91 -2
- package/dist/SchemaAST.js.map +1 -1
- package/dist/SchemaGetter.d.ts +3 -3
- package/dist/SchemaRepresentation.d.ts.map +1 -1
- package/dist/SchemaRepresentation.js +12 -8
- package/dist/SchemaRepresentation.js.map +1 -1
- package/dist/Stdio.d.ts +10 -2
- package/dist/Stdio.d.ts.map +1 -1
- package/dist/Stdio.js +18 -0
- package/dist/Stdio.js.map +1 -1
- package/dist/internal/schema/representation.js +7 -3
- package/dist/internal/schema/representation.js.map +1 -1
- package/dist/internal/schema/to-codec.js +7 -10
- package/dist/internal/schema/to-codec.js.map +1 -1
- package/dist/unstable/ai/AiError.d.ts +6 -3
- package/dist/unstable/ai/AiError.d.ts.map +1 -1
- package/dist/unstable/ai/AiError.js +8 -4
- package/dist/unstable/ai/AiError.js.map +1 -1
- package/dist/unstable/ai/LanguageModel.js +7 -5
- package/dist/unstable/ai/LanguageModel.js.map +1 -1
- package/dist/unstable/cli/CliError.d.ts +19 -52
- package/dist/unstable/cli/CliError.d.ts.map +1 -1
- package/dist/unstable/cli/CliError.js +21 -53
- package/dist/unstable/cli/CliError.js.map +1 -1
- package/dist/unstable/cli/Command.d.ts +117 -62
- package/dist/unstable/cli/Command.d.ts.map +1 -1
- package/dist/unstable/cli/Command.js +95 -32
- package/dist/unstable/cli/Command.js.map +1 -1
- package/dist/unstable/cli/GlobalFlag.d.ts +1 -1
- package/dist/unstable/cli/GlobalFlag.d.ts.map +1 -1
- package/dist/unstable/cli/internal/command.d.ts +27 -10
- package/dist/unstable/cli/internal/command.d.ts.map +1 -1
- package/dist/unstable/cli/internal/command.js +40 -23
- package/dist/unstable/cli/internal/command.js.map +1 -1
- package/dist/unstable/cli/internal/config.js +42 -0
- package/dist/unstable/cli/internal/config.js.map +1 -1
- package/dist/unstable/cli/internal/help.d.ts +4 -4
- package/dist/unstable/cli/internal/help.d.ts.map +1 -1
- package/dist/unstable/cli/internal/help.js +25 -7
- package/dist/unstable/cli/internal/help.js.map +1 -1
- package/dist/unstable/cli/internal/parser.js +26 -6
- package/dist/unstable/cli/internal/parser.js.map +1 -1
- package/dist/unstable/httpapi/HttpApiBuilder.d.ts.map +1 -1
- package/dist/unstable/httpapi/HttpApiBuilder.js +1 -7
- package/dist/unstable/httpapi/HttpApiBuilder.js.map +1 -1
- package/dist/unstable/httpapi/HttpApiClient.d.ts +6 -6
- package/dist/unstable/httpapi/HttpApiClient.d.ts.map +1 -1
- package/dist/unstable/httpapi/HttpApiClient.js.map +1 -1
- package/dist/unstable/httpapi/HttpApiEndpoint.d.ts +10 -10
- package/dist/unstable/httpapi/HttpApiEndpoint.d.ts.map +1 -1
- package/dist/unstable/httpapi/HttpApiEndpoint.js +2 -2
- package/dist/unstable/httpapi/HttpApiEndpoint.js.map +1 -1
- package/dist/unstable/httpapi/HttpApiError.d.ts +6 -15
- package/dist/unstable/httpapi/HttpApiError.d.ts.map +1 -1
- package/dist/unstable/httpapi/HttpApiError.js +16 -21
- package/dist/unstable/httpapi/HttpApiError.js.map +1 -1
- package/dist/unstable/reactivity/Atom.d.ts +56 -0
- package/dist/unstable/reactivity/Atom.d.ts.map +1 -1
- package/dist/unstable/reactivity/Atom.js +66 -0
- package/dist/unstable/reactivity/Atom.js.map +1 -1
- package/dist/unstable/reactivity/AtomHttpApi.d.ts +2 -2
- package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
- package/dist/unstable/rpc/RpcServer.d.ts +3 -0
- package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
- package/dist/unstable/rpc/RpcServer.js.map +1 -1
- package/dist/unstable/sql/SqlSchema.d.ts +2 -2
- package/dist/unstable/sql/SqlSchema.d.ts.map +1 -1
- package/dist/unstable/sql/SqlSchema.js.map +1 -1
- package/package.json +1 -1
- package/src/Config.ts +4 -4
- package/src/Effect.ts +29 -9
- package/src/Runtime.ts +95 -5
- package/src/Schema.ts +20 -52
- package/src/SchemaAST.ts +127 -2
- package/src/SchemaGetter.ts +3 -3
- package/src/SchemaRepresentation.ts +12 -8
- package/src/Stdio.ts +21 -2
- package/src/internal/schema/representation.ts +8 -4
- package/src/internal/schema/to-codec.ts +7 -17
- package/src/unstable/ai/AiError.ts +8 -4
- package/src/unstable/ai/LanguageModel.ts +8 -5
- package/src/unstable/cli/CliError.ts +43 -55
- package/src/unstable/cli/Command.ts +348 -180
- package/src/unstable/cli/GlobalFlag.ts +1 -1
- package/src/unstable/cli/internal/command.ts +62 -32
- package/src/unstable/cli/internal/config.ts +49 -0
- package/src/unstable/cli/internal/help.ts +41 -16
- package/src/unstable/cli/internal/parser.ts +36 -10
- package/src/unstable/httpapi/HttpApiBuilder.ts +1 -7
- package/src/unstable/httpapi/HttpApiClient.ts +6 -6
- package/src/unstable/httpapi/HttpApiEndpoint.ts +4 -4
- package/src/unstable/httpapi/HttpApiError.ts +23 -21
- package/src/unstable/reactivity/Atom.ts +132 -0
- package/src/unstable/reactivity/AtomHttpApi.ts +2 -2
- package/src/unstable/rpc/RpcServer.ts +5 -0
- package/src/unstable/sql/SqlSchema.ts +2 -2
|
@@ -22,7 +22,7 @@ import * as Flag from "./Flag.ts"
|
|
|
22
22
|
* @category models
|
|
23
23
|
*/
|
|
24
24
|
export interface HandlerContext {
|
|
25
|
-
readonly command: Command.Command<any, unknown, unknown, unknown>
|
|
25
|
+
readonly command: Command.Command<any, unknown, any, unknown, unknown>
|
|
26
26
|
readonly commandPath: ReadonlyArray<string>
|
|
27
27
|
readonly version: string
|
|
28
28
|
}
|
|
@@ -16,7 +16,7 @@ import type * as GlobalFlag from "../GlobalFlag.ts"
|
|
|
16
16
|
import type { ArgDoc, ExampleDoc, FlagDoc, HelpDoc, SubcommandGroupDoc } from "../HelpDoc.ts"
|
|
17
17
|
import * as Param from "../Param.ts"
|
|
18
18
|
import * as Primitive from "../Primitive.ts"
|
|
19
|
-
import { type ConfigInternal, reconstructTree } from "./config.ts"
|
|
19
|
+
import { type ConfigInternal, emptyConfig, reconstructTree } from "./config.ts"
|
|
20
20
|
|
|
21
21
|
/* ========================================================================== */
|
|
22
22
|
/* Types */
|
|
@@ -26,19 +26,23 @@ import type { Command, CommandContext, Environment, ParsedTokens } from "../Comm
|
|
|
26
26
|
|
|
27
27
|
interface SubcommandGroup {
|
|
28
28
|
readonly group: string | undefined
|
|
29
|
-
readonly commands: Arr.NonEmptyReadonlyArray<Command<any, unknown, unknown, unknown>>
|
|
29
|
+
readonly commands: Arr.NonEmptyReadonlyArray<Command<any, unknown, any, unknown, unknown>>
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Internal implementation interface with all the machinery.
|
|
34
34
|
* Use toImpl() to access from internal code.
|
|
35
35
|
*/
|
|
36
|
-
export interface CommandInternal<Name extends string, Input, E, R
|
|
36
|
+
export interface CommandInternal<Name extends string, Input, E, R, ContextInput>
|
|
37
|
+
extends Command<Name, Input, ContextInput, E, R>
|
|
38
|
+
{
|
|
37
39
|
readonly config: ConfigInternal
|
|
38
|
-
readonly
|
|
40
|
+
readonly contextConfig: ConfigInternal
|
|
41
|
+
readonly service: ServiceMap.Key<CommandContext<Name>, ContextInput>
|
|
39
42
|
readonly annotations: ServiceMap.ServiceMap<never>
|
|
40
43
|
readonly globalFlags: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
|
|
41
44
|
readonly parse: (input: ParsedTokens) => Effect.Effect<Input, CliError.CliError, Environment>
|
|
45
|
+
readonly parseContext: (input: ParsedTokens) => Effect.Effect<ContextInput, CliError.CliError, Environment>
|
|
42
46
|
readonly handle: (
|
|
43
47
|
input: Input,
|
|
44
48
|
commandPath: ReadonlyArray<string>
|
|
@@ -60,9 +64,9 @@ export const TypeId = "~effect/cli/Command" as const
|
|
|
60
64
|
* Casts a Command to its internal implementation.
|
|
61
65
|
* For use by internal modules that need access to config, parse, handle, etc.
|
|
62
66
|
*/
|
|
63
|
-
export const toImpl = <Name extends string, Input, E, R>(
|
|
64
|
-
self: Command<Name, Input, E, R>
|
|
65
|
-
): CommandInternal<Name, Input, E, R> => self as CommandInternal<Name, Input, E, R>
|
|
67
|
+
export const toImpl = <Name extends string, Input, E, R, ContextInput = {}>(
|
|
68
|
+
self: Command<Name, Input, ContextInput, E, R>
|
|
69
|
+
): CommandInternal<Name, Input, E, R, ContextInput> => self as CommandInternal<Name, Input, E, R, ContextInput>
|
|
66
70
|
|
|
67
71
|
/* ========================================================================== */
|
|
68
72
|
/* Proto */
|
|
@@ -73,7 +77,7 @@ export const Proto = {
|
|
|
73
77
|
pipe() {
|
|
74
78
|
return pipeArguments(this, arguments)
|
|
75
79
|
},
|
|
76
|
-
asEffect(this: Command<any, any, any, any>) {
|
|
80
|
+
asEffect(this: Command<any, any, any, any, any>) {
|
|
77
81
|
return toImpl(this).service.asEffect()
|
|
78
82
|
}
|
|
79
83
|
}
|
|
@@ -85,10 +89,11 @@ export const Proto = {
|
|
|
85
89
|
/**
|
|
86
90
|
* Internal command constructor. Only accepts already-parsed ConfigInternal.
|
|
87
91
|
*/
|
|
88
|
-
export const makeCommand = <const Name extends string, Input, E, R>(options: {
|
|
92
|
+
export const makeCommand = <const Name extends string, Input, E, R, ContextInput = {}>(options: {
|
|
89
93
|
readonly name: Name
|
|
90
94
|
readonly config: ConfigInternal
|
|
91
|
-
readonly
|
|
95
|
+
readonly contextConfig?: ConfigInternal | undefined
|
|
96
|
+
readonly service?: ServiceMap.Key<CommandContext<Name>, ContextInput> | undefined
|
|
92
97
|
readonly annotations?: ServiceMap.ServiceMap<never> | undefined
|
|
93
98
|
readonly globalFlags?: ReadonlyArray<GlobalFlag.GlobalFlag<any>> | undefined
|
|
94
99
|
readonly description?: string | undefined
|
|
@@ -97,12 +102,16 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
|
|
|
97
102
|
readonly examples?: ReadonlyArray<Command.Example> | undefined
|
|
98
103
|
readonly subcommands?: ReadonlyArray<SubcommandGroup> | undefined
|
|
99
104
|
readonly parse?: ((input: ParsedTokens) => Effect.Effect<Input, CliError.CliError, Environment>) | undefined
|
|
105
|
+
readonly parseContext?:
|
|
106
|
+
| ((input: ParsedTokens) => Effect.Effect<ContextInput, CliError.CliError, Environment>)
|
|
107
|
+
| undefined
|
|
100
108
|
readonly handle?:
|
|
101
109
|
| ((input: Input, commandPath: ReadonlyArray<string>) => Effect.Effect<void, E, R | Environment>)
|
|
102
110
|
| undefined
|
|
103
|
-
}): Command<Name, Input, E, R> => {
|
|
104
|
-
const service = options.service ?? ServiceMap.Service<CommandContext<Name>, Input>(`${TypeId}/${options.name}`)
|
|
111
|
+
}): Command<Name, Input, ContextInput, E, R> => {
|
|
105
112
|
const config = options.config
|
|
113
|
+
const contextConfig = options.contextConfig ?? emptyConfig
|
|
114
|
+
const service = options.service ?? ServiceMap.Service<CommandContext<Name>, ContextInput>(`${TypeId}/${options.name}`)
|
|
106
115
|
const annotations = options.annotations ?? ServiceMap.empty()
|
|
107
116
|
const globalFlags = options.globalFlags ?? []
|
|
108
117
|
const subcommands = options.subcommands ?? []
|
|
@@ -113,13 +122,10 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
|
|
|
113
122
|
): Effect.Effect<void, CliError.CliError | E, R | Environment> =>
|
|
114
123
|
Predicate.isNotUndefined(options.handle)
|
|
115
124
|
? options.handle(input, commandPath)
|
|
116
|
-
: Effect.fail(new CliError.ShowHelp({ commandPath }))
|
|
125
|
+
: Effect.fail(new CliError.ShowHelp({ commandPath, errors: [] }))
|
|
117
126
|
|
|
118
|
-
const parse = options.parse ??
|
|
119
|
-
|
|
120
|
-
const values = yield* parseParams(parsedArgs, config.orderedParams)
|
|
121
|
-
return reconstructTree(config.tree, values) as Input
|
|
122
|
-
})
|
|
127
|
+
const parse = options.parse ?? makeParser(config) as any
|
|
128
|
+
const parseContext = options.parseContext ?? makeParser(contextConfig) as any
|
|
123
129
|
|
|
124
130
|
const buildHelpDoc = (commandPath: ReadonlyArray<string>): HelpDoc => {
|
|
125
131
|
const args: Array<ArgDoc> = []
|
|
@@ -152,14 +158,7 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
|
|
|
152
158
|
for (const option of config.flags) {
|
|
153
159
|
const singles = Param.extractSingleParams(option)
|
|
154
160
|
for (const single of singles) {
|
|
155
|
-
|
|
156
|
-
flags.push({
|
|
157
|
-
name: single.name,
|
|
158
|
-
aliases: formattedAliases,
|
|
159
|
-
type: single.typeName ?? Primitive.getTypeName(single.primitiveType),
|
|
160
|
-
description: single.description,
|
|
161
|
-
required: single.primitiveType._tag !== "Boolean"
|
|
162
|
-
})
|
|
161
|
+
flags.push(toFlagDoc(single))
|
|
163
162
|
}
|
|
164
163
|
}
|
|
165
164
|
|
|
@@ -198,8 +197,10 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
|
|
|
198
197
|
globalFlags,
|
|
199
198
|
subcommands,
|
|
200
199
|
config,
|
|
200
|
+
contextConfig,
|
|
201
201
|
service,
|
|
202
202
|
parse,
|
|
203
|
+
parseContext,
|
|
203
204
|
handle,
|
|
204
205
|
buildHelpDoc,
|
|
205
206
|
...(Predicate.isNotUndefined(options.description)
|
|
@@ -218,6 +219,32 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
|
|
|
218
219
|
/* Helpers */
|
|
219
220
|
/* ========================================================================== */
|
|
220
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Converts a single flag param into a FlagDoc for help display.
|
|
224
|
+
*/
|
|
225
|
+
export const toFlagDoc = (single: Param.Single<typeof Param.flagKind, unknown>): FlagDoc => {
|
|
226
|
+
const formattedAliases = single.aliases.map((alias) => alias.length === 1 ? `-${alias}` : `--${alias}`)
|
|
227
|
+
return {
|
|
228
|
+
name: single.name,
|
|
229
|
+
aliases: formattedAliases,
|
|
230
|
+
type: single.typeName ?? Primitive.getTypeName(single.primitiveType),
|
|
231
|
+
description: single.description,
|
|
232
|
+
required: single.primitiveType._tag !== "Boolean"
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Creates a parser for a given config. Used as the default for both `parse`
|
|
238
|
+
* and `parseContext`, and also by `withSharedFlags` to avoid constructing a
|
|
239
|
+
* full throwaway command.
|
|
240
|
+
*/
|
|
241
|
+
export const makeParser = (cfg: ConfigInternal) =>
|
|
242
|
+
Effect.fnUntraced(function*(input: ParsedTokens) {
|
|
243
|
+
const parsedArgs: Param.ParsedArgs = { flags: input.flags, arguments: input.arguments }
|
|
244
|
+
const values = yield* parseParams(parsedArgs, cfg.orderedParams)
|
|
245
|
+
return reconstructTree(cfg.tree, values)
|
|
246
|
+
})
|
|
247
|
+
|
|
221
248
|
/**
|
|
222
249
|
* Parses param values from parsed command arguments into their typed
|
|
223
250
|
* representations.
|
|
@@ -245,15 +272,18 @@ const parseParams: (parsedArgs: Param.ParsedArgs, params: ReadonlyArray<Param.An
|
|
|
245
272
|
/**
|
|
246
273
|
* Checks for duplicate flag names between parent and child commands.
|
|
247
274
|
*/
|
|
248
|
-
export const checkForDuplicateFlags = <Name extends string, Input>(
|
|
249
|
-
parent: Command<Name, Input, unknown, unknown>,
|
|
250
|
-
subcommands: ReadonlyArray<Command<any, unknown, unknown, unknown
|
|
275
|
+
export const checkForDuplicateFlags = <Name extends string, Input, ContextInput>(
|
|
276
|
+
parent: Command<Name, Input, ContextInput, unknown, unknown>,
|
|
277
|
+
subcommands: ReadonlyArray<Command<any, unknown, any, unknown, unknown>>,
|
|
278
|
+
options?: {
|
|
279
|
+
readonly contextConfig?: ConfigInternal | undefined
|
|
280
|
+
} | undefined
|
|
251
281
|
): void => {
|
|
252
282
|
const parentImpl = toImpl(parent)
|
|
253
283
|
const parentOptionNames = new Set<string>()
|
|
254
284
|
|
|
255
|
-
const extractNames = (
|
|
256
|
-
for (const option of
|
|
285
|
+
const extractNames = (flags: ReadonlyArray<Param.Any>): void => {
|
|
286
|
+
for (const option of flags) {
|
|
257
287
|
const singles = Param.extractSingleParams(option)
|
|
258
288
|
for (const single of singles) {
|
|
259
289
|
parentOptionNames.add(single.name)
|
|
@@ -261,7 +291,7 @@ export const checkForDuplicateFlags = <Name extends string, Input>(
|
|
|
261
291
|
}
|
|
262
292
|
}
|
|
263
293
|
|
|
264
|
-
extractNames(parentImpl.
|
|
294
|
+
extractNames((options?.contextConfig ?? parentImpl.contextConfig).flags)
|
|
265
295
|
|
|
266
296
|
for (const subcommand of subcommands) {
|
|
267
297
|
const subImpl = toImpl(subcommand)
|
|
@@ -183,6 +183,55 @@ export const parseConfig = (config: Config): ConfigInternal => {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/** @internal */
|
|
187
|
+
export const emptyConfig: ConfigInternal = parseConfig({})
|
|
188
|
+
|
|
189
|
+
const shiftNodeIndexes = (node: ConfigInternal.Node, offset: number): ConfigInternal.Node => {
|
|
190
|
+
switch (node._tag) {
|
|
191
|
+
case "Param":
|
|
192
|
+
return {
|
|
193
|
+
_tag: "Param",
|
|
194
|
+
index: node.index + offset
|
|
195
|
+
}
|
|
196
|
+
case "Array":
|
|
197
|
+
return {
|
|
198
|
+
_tag: "Array",
|
|
199
|
+
children: node.children.map((child) => shiftNodeIndexes(child, offset))
|
|
200
|
+
}
|
|
201
|
+
case "Nested":
|
|
202
|
+
return {
|
|
203
|
+
_tag: "Nested",
|
|
204
|
+
tree: shiftTreeIndexes(node.tree, offset)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const shiftTreeIndexes = (tree: ConfigInternal.Tree, offset: number): ConfigInternal.Tree => {
|
|
210
|
+
const output: ConfigInternal.Tree = {}
|
|
211
|
+
for (const key in tree) {
|
|
212
|
+
output[key] = shiftNodeIndexes(tree[key], offset)
|
|
213
|
+
}
|
|
214
|
+
return output
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** @internal */
|
|
218
|
+
export const mergeConfig = (
|
|
219
|
+
left: ConfigInternal,
|
|
220
|
+
right: ConfigInternal
|
|
221
|
+
): ConfigInternal => {
|
|
222
|
+
const offset = left.orderedParams.length
|
|
223
|
+
return {
|
|
224
|
+
[ConfigInternalTypeId]: ConfigInternalTypeId,
|
|
225
|
+
flags: [...left.flags, ...right.flags],
|
|
226
|
+
arguments: [...left.arguments, ...right.arguments],
|
|
227
|
+
orderedParams: [...left.orderedParams, ...right.orderedParams],
|
|
228
|
+
tree: {
|
|
229
|
+
...left.tree,
|
|
230
|
+
...shiftTreeIndexes(right.tree, offset)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
186
235
|
/* ========================================================================== */
|
|
187
236
|
/* Reconstruction */
|
|
188
237
|
/* ========================================================================== */
|
|
@@ -10,8 +10,7 @@ import type { Command } from "../Command.ts"
|
|
|
10
10
|
import type * as GlobalFlag from "../GlobalFlag.ts"
|
|
11
11
|
import type { FlagDoc, HelpDoc } from "../HelpDoc.ts"
|
|
12
12
|
import * as Param from "../Param.ts"
|
|
13
|
-
import
|
|
14
|
-
import { toImpl } from "./command.ts"
|
|
13
|
+
import { toFlagDoc, toImpl } from "./command.ts"
|
|
15
14
|
|
|
16
15
|
const dedupeGlobalFlags = (
|
|
17
16
|
flags: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
|
|
@@ -32,8 +31,8 @@ const dedupeGlobalFlags = (
|
|
|
32
31
|
* Returns the resolved command lineage for the provided path.
|
|
33
32
|
* Includes the root command as the first element.
|
|
34
33
|
*/
|
|
35
|
-
export const getCommandsForCommandPath = <Name extends string, Input, E, R>(
|
|
36
|
-
command: Command<Name, Input, E, R>,
|
|
34
|
+
export const getCommandsForCommandPath = <Name extends string, Input, E, R, ContextInput>(
|
|
35
|
+
command: Command<Name, Input, ContextInput, E, R>,
|
|
37
36
|
commandPath: ReadonlyArray<string>
|
|
38
37
|
): ReadonlyArray<Command.Any> => {
|
|
39
38
|
const commands: Array<Command.Any> = [command]
|
|
@@ -65,8 +64,8 @@ export const getCommandsForCommandPath = <Name extends string, Input, E, R>(
|
|
|
65
64
|
* Returns active global flags for a command path.
|
|
66
65
|
* Built-ins are prepended and declarations are collected root -> leaf.
|
|
67
66
|
*/
|
|
68
|
-
export const getGlobalFlagsForCommandPath = <Name extends string, Input, E, R>(
|
|
69
|
-
command: Command<Name, Input, E, R>,
|
|
67
|
+
export const getGlobalFlagsForCommandPath = <Name extends string, Input, E, R, ContextInput>(
|
|
68
|
+
command: Command<Name, Input, ContextInput, E, R>,
|
|
70
69
|
commandPath: ReadonlyArray<string>,
|
|
71
70
|
builtIns: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
|
|
72
71
|
): ReadonlyArray<GlobalFlag.GlobalFlag<any>> => {
|
|
@@ -97,12 +96,40 @@ const collectDeclaredGlobalFlags = (command: Command.Any): ReadonlyArray<GlobalF
|
|
|
97
96
|
return dedupeGlobalFlags(collected)
|
|
98
97
|
}
|
|
99
98
|
|
|
99
|
+
const getSharedFlagsForCommandPath = (
|
|
100
|
+
commands: ReadonlyArray<Command.Any>,
|
|
101
|
+
currentFlags: ReadonlyArray<FlagDoc>
|
|
102
|
+
): ReadonlyArray<FlagDoc> => {
|
|
103
|
+
if (commands.length <= 1) {
|
|
104
|
+
return []
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const seen = new Set(currentFlags.map((flag) => flag.name))
|
|
108
|
+
const sharedFlags: Array<FlagDoc> = []
|
|
109
|
+
|
|
110
|
+
for (const ancestor of commands.slice(0, -1)) {
|
|
111
|
+
const ancestorImpl = toImpl(ancestor)
|
|
112
|
+
for (const flag of ancestorImpl.contextConfig.flags) {
|
|
113
|
+
const singles = Param.extractSingleParams(flag)
|
|
114
|
+
for (const single of singles) {
|
|
115
|
+
if (seen.has(single.name)) {
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
seen.add(single.name)
|
|
119
|
+
sharedFlags.push(toFlagDoc(single))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return sharedFlags
|
|
125
|
+
}
|
|
126
|
+
|
|
100
127
|
/**
|
|
101
128
|
* Returns all global flags declared in a command tree.
|
|
102
129
|
* Built-ins are prepended and command declarations are deduplicated by identity.
|
|
103
130
|
*/
|
|
104
|
-
export const getGlobalFlagsForCommandTree = <Name extends string, Input, E, R>(
|
|
105
|
-
command: Command<Name, Input, E, R>,
|
|
131
|
+
export const getGlobalFlagsForCommandTree = <Name extends string, Input, E, R, ContextInput>(
|
|
132
|
+
command: Command<Name, Input, ContextInput, E, R>,
|
|
106
133
|
builtIns: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
|
|
107
134
|
): ReadonlyArray<GlobalFlag.GlobalFlag<any>> =>
|
|
108
135
|
dedupeGlobalFlags([
|
|
@@ -115,8 +142,8 @@ export const getGlobalFlagsForCommandTree = <Name extends string, Input, E, R>(
|
|
|
115
142
|
* Navigates through the command hierarchy to find the right command.
|
|
116
143
|
* Reads active global flags for the path and includes them in the help doc.
|
|
117
144
|
*/
|
|
118
|
-
export const getHelpForCommandPath = <Name extends string, Input, E, R>(
|
|
119
|
-
command: Command<Name, Input, E, R>,
|
|
145
|
+
export const getHelpForCommandPath = <Name extends string, Input, E, R, ContextInput>(
|
|
146
|
+
command: Command<Name, Input, ContextInput, E, R>,
|
|
120
147
|
commandPath: ReadonlyArray<string>,
|
|
121
148
|
builtIns: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
|
|
122
149
|
): Effect.Effect<HelpDoc, never, never> =>
|
|
@@ -126,21 +153,19 @@ export const getHelpForCommandPath = <Name extends string, Input, E, R>(
|
|
|
126
153
|
|
|
127
154
|
const baseDoc = toImpl(currentCommand).buildHelpDoc(commandPath)
|
|
128
155
|
|
|
156
|
+
const sharedFlags = getSharedFlagsForCommandPath(commands, baseDoc.flags)
|
|
157
|
+
|
|
129
158
|
const flags = getGlobalFlagsForCommandPath(command, commandPath, builtIns)
|
|
130
159
|
const globalFlagDocs: Array<FlagDoc> = []
|
|
131
160
|
for (const flag of flags) {
|
|
132
161
|
const singles = Param.extractSingleParams(flag.flag)
|
|
133
162
|
for (const single of singles) {
|
|
134
|
-
const formattedAliases = single.aliases.map((alias) => alias.length === 1 ? `-${alias}` : `--${alias}`)
|
|
135
163
|
globalFlagDocs.push({
|
|
136
|
-
|
|
137
|
-
aliases: formattedAliases,
|
|
138
|
-
type: single.typeName ?? Primitive.getTypeName(single.primitiveType),
|
|
139
|
-
description: single.description,
|
|
164
|
+
...toFlagDoc(single),
|
|
140
165
|
required: false
|
|
141
166
|
})
|
|
142
167
|
}
|
|
143
168
|
}
|
|
144
169
|
|
|
145
|
-
return { ...baseDoc, globalFlags: globalFlagDocs }
|
|
170
|
+
return { ...baseDoc, flags: [...sharedFlags, ...baseDoc.flags], globalFlags: globalFlagDocs }
|
|
146
171
|
})
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Key Behaviors
|
|
22
22
|
* -------------
|
|
23
|
-
* -
|
|
23
|
+
* - Inherited parent flags may appear before OR after the subcommand name (npm-style)
|
|
24
24
|
* - Only the first Value token can open a subcommand
|
|
25
25
|
* - Errors accumulate rather than throwing exceptions
|
|
26
26
|
*/
|
|
@@ -58,10 +58,17 @@ export const parseArgs = (
|
|
|
58
58
|
const flagParams = singles.filter(Param.isFlagParam)
|
|
59
59
|
const flagRegistry = createFlagRegistry(flagParams)
|
|
60
60
|
|
|
61
|
+
const inheritedSingles = commandImpl.contextConfig.flags.flatMap(Param.extractSingleParams)
|
|
62
|
+
const inheritedFlagParams = inheritedSingles.filter(Param.isFlagParam)
|
|
63
|
+
const inheritedFlagRegistry = createFlagRegistry(inheritedFlagParams)
|
|
64
|
+
const inheritedNames = new Set(inheritedFlagParams.map((param) => param.name))
|
|
65
|
+
|
|
61
66
|
const context: CommandContext = {
|
|
62
67
|
command,
|
|
63
68
|
commandPath: newCommandPath,
|
|
64
|
-
flagRegistry
|
|
69
|
+
flagRegistry,
|
|
70
|
+
inheritedFlagRegistry,
|
|
71
|
+
localFlagNames: flagParams.filter((param) => !inheritedNames.has(param.name)).map((param) => param.name)
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
const result = scanCommandLevel(tokens, context)
|
|
@@ -133,6 +140,8 @@ type CommandContext = {
|
|
|
133
140
|
readonly command: Command.Any
|
|
134
141
|
readonly commandPath: ReadonlyArray<string>
|
|
135
142
|
readonly flagRegistry: FlagRegistry
|
|
143
|
+
readonly inheritedFlagRegistry: FlagRegistry
|
|
144
|
+
readonly localFlagNames: ReadonlyArray<string>
|
|
136
145
|
}
|
|
137
146
|
|
|
138
147
|
/**
|
|
@@ -176,7 +185,7 @@ type LeafResult = {
|
|
|
176
185
|
type SubcommandResult = {
|
|
177
186
|
readonly _tag: "Sub"
|
|
178
187
|
readonly flags: Readonly<FlagMap>
|
|
179
|
-
readonly sub: Command<string, unknown, unknown, unknown>
|
|
188
|
+
readonly sub: Command<string, unknown, unknown, unknown, unknown>
|
|
180
189
|
readonly childTokens: ReadonlyArray<Token>
|
|
181
190
|
readonly errors: ReadonlyArray<CliError.CliError>
|
|
182
191
|
}
|
|
@@ -242,9 +251,9 @@ export const createFlagRegistry = (params: ReadonlyArray<FlagParam>): FlagRegist
|
|
|
242
251
|
|
|
243
252
|
const buildSubcommandIndex = (
|
|
244
253
|
subcommands: Command.Any["subcommands"]
|
|
245
|
-
): Map<string, Command<string, unknown, unknown, unknown>> => {
|
|
246
|
-
const index = new Map<string, Command<string, unknown, unknown, unknown>>()
|
|
247
|
-
const setKey = (key: string, command: Command<string, unknown, unknown, unknown>) => {
|
|
254
|
+
): Map<string, Command<string, unknown, unknown, unknown, unknown>> => {
|
|
255
|
+
const index = new Map<string, Command<string, unknown, unknown, unknown, unknown>>()
|
|
256
|
+
const setKey = (key: string, command: Command<string, unknown, unknown, unknown, unknown>) => {
|
|
248
257
|
const existing = index.get(key)
|
|
249
258
|
if (existing && existing !== command) {
|
|
250
259
|
throw new Error(
|
|
@@ -448,7 +457,7 @@ const toLeafResult = (state: ParseState): LeafResult => ({
|
|
|
448
457
|
* Determines how to handle the first value token.
|
|
449
458
|
*
|
|
450
459
|
* If it matches a known subcommand:
|
|
451
|
-
* - Collect
|
|
460
|
+
* - Collect inherited parent flags from remaining tokens (npm-style)
|
|
452
461
|
* - Return SubcommandResult with child tokens
|
|
453
462
|
*
|
|
454
463
|
* Otherwise:
|
|
@@ -461,13 +470,30 @@ const resolveFirstValue = (
|
|
|
461
470
|
context: CommandContext,
|
|
462
471
|
state: ParseState
|
|
463
472
|
): FirstValueResult => {
|
|
464
|
-
const { command, commandPath,
|
|
473
|
+
const { command, commandPath, inheritedFlagRegistry, localFlagNames } = context
|
|
465
474
|
const subIndex = buildSubcommandIndex(command.subcommands)
|
|
466
475
|
const sub = subIndex.get(value)
|
|
467
476
|
|
|
468
477
|
if (sub) {
|
|
469
|
-
|
|
470
|
-
|
|
478
|
+
const selectedPath = [...commandPath, sub.name]
|
|
479
|
+
|
|
480
|
+
// Local flags are not inherited by subcommands.
|
|
481
|
+
const parentFlags = state.flags.snapshot()
|
|
482
|
+
for (const localFlagName of localFlagNames) {
|
|
483
|
+
const values = parentFlags[localFlagName]
|
|
484
|
+
if (values !== undefined && values.length > 0) {
|
|
485
|
+
state.errors.push(
|
|
486
|
+
new CliError.UnrecognizedOption({
|
|
487
|
+
option: `--${localFlagName}`,
|
|
488
|
+
suggestions: [],
|
|
489
|
+
command: selectedPath
|
|
490
|
+
})
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// npm-style: inherited parent flags can appear after subcommand name
|
|
496
|
+
const tail = consumeKnownFlags(cursor.rest(), inheritedFlagRegistry)
|
|
471
497
|
state.flags.merge(tail.flagMap)
|
|
472
498
|
|
|
473
499
|
return {
|
|
@@ -36,7 +36,6 @@ import * as Multipart from "../http/Multipart.ts"
|
|
|
36
36
|
import * as UrlParams from "../http/UrlParams.ts"
|
|
37
37
|
import type * as HttpApi from "./HttpApi.ts"
|
|
38
38
|
import * as HttpApiEndpoint from "./HttpApiEndpoint.ts"
|
|
39
|
-
import { HttpApiSchemaError } from "./HttpApiError.ts"
|
|
40
39
|
import type * as HttpApiGroup from "./HttpApiGroup.ts"
|
|
41
40
|
import * as HttpApiMiddleware from "./HttpApiMiddleware.ts"
|
|
42
41
|
import * as HttpApiSchema from "./HttpApiSchema.ts"
|
|
@@ -598,12 +597,7 @@ function handlerToHttpEffect(
|
|
|
598
597
|
})
|
|
599
598
|
).pipe(
|
|
600
599
|
Effect.withErrorReporting,
|
|
601
|
-
Effect.catch((error) =>
|
|
602
|
-
if (Schema.isSchemaError(error)) {
|
|
603
|
-
error = HttpApiSchemaError.fromSchemaError(error)
|
|
604
|
-
}
|
|
605
|
-
return Effect.orDie(encodeError(error))
|
|
606
|
-
}),
|
|
600
|
+
Effect.catch((error) => Effect.orDie(encodeError(error))),
|
|
607
601
|
Effect.provideServices(services)
|
|
608
602
|
)
|
|
609
603
|
}
|
|
@@ -23,7 +23,7 @@ import * as HttpMethod from "../http/HttpMethod.ts"
|
|
|
23
23
|
import * as UrlParams from "../http/UrlParams.ts"
|
|
24
24
|
import * as HttpApi from "./HttpApi.ts"
|
|
25
25
|
import * as HttpApiEndpoint from "./HttpApiEndpoint.ts"
|
|
26
|
-
import type {
|
|
26
|
+
import type { BadRequest } from "./HttpApiError.ts"
|
|
27
27
|
import type * as HttpApiGroup from "./HttpApiGroup.ts"
|
|
28
28
|
import type * as HttpApiMiddleware from "./HttpApiMiddleware.ts"
|
|
29
29
|
import * as HttpApiSchema from "./HttpApiSchema.ts"
|
|
@@ -32,7 +32,7 @@ import * as HttpApiSchema from "./HttpApiSchema.ts"
|
|
|
32
32
|
* @since 4.0.0
|
|
33
33
|
* @category models
|
|
34
34
|
*/
|
|
35
|
-
export type Client<Groups extends HttpApiGroup.Any, E =
|
|
35
|
+
export type Client<Groups extends HttpApiGroup.Any, E = BadRequest, R = never> = Simplify<
|
|
36
36
|
& {
|
|
37
37
|
readonly [Group in Extract<Groups, { readonly topLevel: false }> as HttpApiGroup.Name<Group>]: Client.Group<
|
|
38
38
|
Group,
|
|
@@ -50,7 +50,7 @@ export type Client<Groups extends HttpApiGroup.Any, E = HttpApiSchemaError, R =
|
|
|
50
50
|
* @since 4.0.0
|
|
51
51
|
* @category models
|
|
52
52
|
*/
|
|
53
|
-
export type ForApi<Api extends HttpApi.Any, E =
|
|
53
|
+
export type ForApi<Api extends HttpApi.Any, E = BadRequest, R = never> = Api extends
|
|
54
54
|
HttpApi.HttpApi<infer _Id, infer Groups> ? Client<Groups, E, R> :
|
|
55
55
|
never
|
|
56
56
|
|
|
@@ -383,7 +383,7 @@ export const makeWith = <ApiId extends string, Groups extends HttpApiGroup.Any,
|
|
|
383
383
|
| undefined
|
|
384
384
|
readonly baseUrl?: URL | string | undefined
|
|
385
385
|
}
|
|
386
|
-
): Effect.Effect<Client<Groups,
|
|
386
|
+
): Effect.Effect<Client<Groups, BadRequest | E, R>, never, HttpApiGroup.MiddlewareClient<Groups>> => {
|
|
387
387
|
const client: Record<string, Record<string, any>> = {}
|
|
388
388
|
return makeClient(api, {
|
|
389
389
|
...options,
|
|
@@ -418,7 +418,7 @@ export const group = <
|
|
|
418
418
|
readonly baseUrl?: URL | string | undefined
|
|
419
419
|
}
|
|
420
420
|
): Effect.Effect<
|
|
421
|
-
Client.Group<Groups, GroupName,
|
|
421
|
+
Client.Group<Groups, GroupName, BadRequest | E, R>,
|
|
422
422
|
never,
|
|
423
423
|
HttpApiGroup.MiddlewareClient<HttpApiGroup.WithName<Groups, GroupName>>
|
|
424
424
|
> => {
|
|
@@ -458,7 +458,7 @@ export const endpoint = <
|
|
|
458
458
|
): Effect.Effect<
|
|
459
459
|
Client.Method<
|
|
460
460
|
HttpApiEndpoint.WithName<HttpApiGroup.Endpoints<HttpApiGroup.WithName<Groups, GroupName>>, EndpointName>,
|
|
461
|
-
|
|
461
|
+
BadRequest | E,
|
|
462
462
|
R
|
|
463
463
|
>,
|
|
464
464
|
never,
|
|
@@ -15,7 +15,7 @@ import * as HttpRouter from "../http/HttpRouter.ts"
|
|
|
15
15
|
import type { HttpServerRequest } from "../http/HttpServerRequest.ts"
|
|
16
16
|
import type { HttpServerResponse } from "../http/HttpServerResponse.ts"
|
|
17
17
|
import type * as Multipart from "../http/Multipart.ts"
|
|
18
|
-
import {
|
|
18
|
+
import { BadRequestFromSchemaError, type BadRequestNoContent } from "./HttpApiError.ts"
|
|
19
19
|
import type * as HttpApiGroup from "./HttpApiGroup.ts"
|
|
20
20
|
import type * as HttpApiMiddleware from "./HttpApiMiddleware.ts"
|
|
21
21
|
import * as HttpApiSchema from "./HttpApiSchema.ts"
|
|
@@ -53,7 +53,7 @@ export interface HttpApiEndpoint<
|
|
|
53
53
|
out Payload extends Schema.Top = never,
|
|
54
54
|
out Headers extends Schema.Top = never,
|
|
55
55
|
out Success extends Schema.Top = typeof HttpApiSchema.NoContent,
|
|
56
|
-
out Error extends Schema.Top = typeof
|
|
56
|
+
out Error extends Schema.Top = typeof BadRequestNoContent,
|
|
57
57
|
in out Middleware = never,
|
|
58
58
|
out MiddlewareR = never
|
|
59
59
|
> extends Pipeable {
|
|
@@ -179,7 +179,7 @@ export function getErrorSchemas(endpoint: AnyWithProps): [Schema.Top, ...Array<S
|
|
|
179
179
|
schemas.add(key.error)
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
-
return Arr.append(Array.from(schemas),
|
|
182
|
+
return Arr.append(Array.from(schemas), BadRequestFromSchemaError)
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/**
|
|
@@ -936,7 +936,7 @@ export const make = <Method extends HttpMethod>(method: Method) =>
|
|
|
936
936
|
: Payload,
|
|
937
937
|
Headers extends Schema.Struct.Fields ? Schema.Struct<Headers> : Headers,
|
|
938
938
|
Success extends ReadonlyArray<Schema.Top> ? Success[number] : Success,
|
|
939
|
-
(Error extends ReadonlyArray<Schema.Top> ? Error[number] : Error) | typeof
|
|
939
|
+
(Error extends ReadonlyArray<Schema.Top> ? Error[number] : Error) | typeof BadRequestNoContent
|
|
940
940
|
> => {
|
|
941
941
|
return makeProto({
|
|
942
942
|
name,
|
|
@@ -2,30 +2,11 @@
|
|
|
2
2
|
* @since 4.0.0
|
|
3
3
|
*/
|
|
4
4
|
import * as ErrorReporter from "../../ErrorReporter.ts"
|
|
5
|
+
import { identity } from "../../Function.ts"
|
|
5
6
|
import * as Schema from "../../Schema.ts"
|
|
7
|
+
import * as Transformation from "../../SchemaTransformation.ts"
|
|
6
8
|
import * as HttpApiSchema from "./HttpApiSchema.ts"
|
|
7
9
|
|
|
8
|
-
/**
|
|
9
|
-
* @category errors
|
|
10
|
-
* @since 4.0.0
|
|
11
|
-
*/
|
|
12
|
-
export class HttpApiSchemaError extends Schema.ErrorClass<HttpApiSchemaError>("effect/HttpApiSchemaError")({
|
|
13
|
-
_tag: Schema.tag("HttpApiSchemaError"),
|
|
14
|
-
message: Schema.String
|
|
15
|
-
}, {
|
|
16
|
-
httpApiStatus: 400,
|
|
17
|
-
description: "The request or response did not match the expected schema"
|
|
18
|
-
}) {
|
|
19
|
-
/**
|
|
20
|
-
* @since 4.0.0
|
|
21
|
-
*/
|
|
22
|
-
static fromSchemaError(error: Schema.SchemaError): HttpApiSchemaError {
|
|
23
|
-
return new HttpApiSchemaError({ message: error.message })
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
override readonly [ErrorReporter.ignore] = true
|
|
27
|
-
}
|
|
28
|
-
|
|
29
10
|
/**
|
|
30
11
|
* @category Built-in errors
|
|
31
12
|
* @since 4.0.0
|
|
@@ -37,6 +18,7 @@ export class BadRequest extends Schema.ErrorClass<BadRequest>("effect/HttpApiErr
|
|
|
37
18
|
httpApiStatus: 400
|
|
38
19
|
}) {
|
|
39
20
|
override readonly [ErrorReporter.ignore] = true
|
|
21
|
+
static readonly singleton = new BadRequest()
|
|
40
22
|
}
|
|
41
23
|
|
|
42
24
|
/**
|
|
@@ -47,6 +29,26 @@ export const BadRequestNoContent = BadRequest.pipe(HttpApiSchema.asNoContent({
|
|
|
47
29
|
decode: () => new BadRequest({})
|
|
48
30
|
}))
|
|
49
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @category Built-in errors
|
|
34
|
+
* @since 4.0.0
|
|
35
|
+
*/
|
|
36
|
+
export const BadRequestFromSchemaError = BadRequest.pipe(
|
|
37
|
+
Schema.decodeTo(
|
|
38
|
+
Schema.Union([Schema.declare(Schema.isSchemaError), BadRequest]),
|
|
39
|
+
Transformation.transform({
|
|
40
|
+
encode: (_) => BadRequest.singleton,
|
|
41
|
+
decode: identity
|
|
42
|
+
})
|
|
43
|
+
),
|
|
44
|
+
HttpApiSchema.asNoContent({
|
|
45
|
+
decode: () => new BadRequest({})
|
|
46
|
+
})
|
|
47
|
+
).annotate({
|
|
48
|
+
httpApiStatus: 400,
|
|
49
|
+
description: "BadRequest"
|
|
50
|
+
})
|
|
51
|
+
|
|
50
52
|
/**
|
|
51
53
|
* @category Built-in errors
|
|
52
54
|
* @since 4.0.0
|