effect 4.0.0-beta.14 → 4.0.0-beta.15

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.
Files changed (60) hide show
  1. package/dist/Channel.d.ts +6 -6
  2. package/dist/Channel.d.ts.map +1 -1
  3. package/dist/Channel.js +4 -4
  4. package/dist/Channel.js.map +1 -1
  5. package/dist/Effect.d.ts +7 -7
  6. package/dist/Effect.d.ts.map +1 -1
  7. package/dist/Effect.js.map +1 -1
  8. package/dist/Stream.d.ts +7 -7
  9. package/dist/Stream.d.ts.map +1 -1
  10. package/dist/Stream.js +8 -6
  11. package/dist/Stream.js.map +1 -1
  12. package/dist/Types.d.ts +70 -0
  13. package/dist/Types.d.ts.map +1 -1
  14. package/dist/internal/effect.js +4 -4
  15. package/dist/internal/effect.js.map +1 -1
  16. package/dist/unstable/ai/LanguageModel.d.ts +2 -0
  17. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  18. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  19. package/dist/unstable/cli/Command.d.ts +34 -4
  20. package/dist/unstable/cli/Command.d.ts.map +1 -1
  21. package/dist/unstable/cli/Command.js +73 -24
  22. package/dist/unstable/cli/Command.js.map +1 -1
  23. package/dist/unstable/cli/GlobalFlag.d.ts +21 -56
  24. package/dist/unstable/cli/GlobalFlag.d.ts.map +1 -1
  25. package/dist/unstable/cli/GlobalFlag.js +9 -48
  26. package/dist/unstable/cli/GlobalFlag.js.map +1 -1
  27. package/dist/unstable/cli/internal/command.d.ts +3 -0
  28. package/dist/unstable/cli/internal/command.d.ts.map +1 -1
  29. package/dist/unstable/cli/internal/command.js +2 -0
  30. package/dist/unstable/cli/internal/command.js.map +1 -1
  31. package/dist/unstable/cli/internal/help.d.ts +18 -4
  32. package/dist/unstable/cli/internal/help.d.ts.map +1 -1
  33. package/dist/unstable/cli/internal/help.js +61 -7
  34. package/dist/unstable/cli/internal/help.js.map +1 -1
  35. package/dist/unstable/httpapi/HttpApiBuilder.d.ts +10 -4
  36. package/dist/unstable/httpapi/HttpApiBuilder.d.ts.map +1 -1
  37. package/dist/unstable/httpapi/HttpApiBuilder.js +17 -6
  38. package/dist/unstable/httpapi/HttpApiBuilder.js.map +1 -1
  39. package/dist/unstable/httpapi/HttpApiEndpoint.d.ts +7 -2
  40. package/dist/unstable/httpapi/HttpApiEndpoint.d.ts.map +1 -1
  41. package/dist/unstable/httpapi/HttpApiEndpoint.js.map +1 -1
  42. package/dist/unstable/reactivity/AtomHttpApi.d.ts +2 -2
  43. package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
  44. package/dist/unstable/reactivity/AtomRegistry.js +2 -6
  45. package/dist/unstable/reactivity/AtomRegistry.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/Channel.ts +24 -14
  48. package/src/Effect.ts +30 -8
  49. package/src/Stream.ts +46 -22
  50. package/src/Types.ts +66 -0
  51. package/src/internal/effect.ts +41 -14
  52. package/src/unstable/ai/LanguageModel.ts +9 -6
  53. package/src/unstable/cli/Command.ts +119 -31
  54. package/src/unstable/cli/GlobalFlag.ts +36 -114
  55. package/src/unstable/cli/internal/command.ts +5 -0
  56. package/src/unstable/cli/internal/help.ts +103 -22
  57. package/src/unstable/httpapi/HttpApiBuilder.ts +68 -13
  58. package/src/unstable/httpapi/HttpApiEndpoint.ts +13 -4
  59. package/src/unstable/reactivity/AtomHttpApi.ts +2 -2
  60. package/src/unstable/reactivity/AtomRegistry.ts +2 -6
@@ -6,7 +6,7 @@ import * as Console from "../../Console.ts"
6
6
  import * as Effect from "../../Effect.ts"
7
7
  import type * as FileSystem from "../../FileSystem.ts"
8
8
  import { dual } from "../../Function.ts"
9
- import * as Layer from "../../Layer.ts"
9
+ import type * as Layer from "../../Layer.ts"
10
10
  import * as Option from "../../Option.ts"
11
11
  import type * as Path from "../../Path.ts"
12
12
  import type { Pipeable } from "../../Pipeable.ts"
@@ -22,7 +22,7 @@ import * as CliOutput from "./CliOutput.ts"
22
22
  import * as GlobalFlag from "./GlobalFlag.ts"
23
23
  import { checkForDuplicateFlags, makeCommand, toImpl, TypeId } from "./internal/command.ts"
24
24
  import { parseConfig } from "./internal/config.ts"
25
- import { getHelpForCommandPath } from "./internal/help.ts"
25
+ import { getGlobalFlagsForCommandPath, getGlobalFlagsForCommandTree, getHelpForCommandPath } from "./internal/help.ts"
26
26
  import * as Lexer from "./internal/lexer.ts"
27
27
  import * as Parser from "./internal/parser.ts"
28
28
  import * as Param from "./Param.ts"
@@ -588,7 +588,7 @@ export const make: {
588
588
  name: Name,
589
589
  config: Config,
590
590
  handler: (config: Command.Config.Infer<Config>) => Effect.Effect<void, E, R>
591
- ): Command<Name, Command.Config.Infer<Config>, E, R>
591
+ ): Command<Name, Command.Config.Infer<Config>, E, Exclude<R, GlobalFlag.BuiltInSettingContext>>
592
592
  } = ((
593
593
  name: string,
594
594
  config?: Command.Config,
@@ -661,7 +661,7 @@ export const withHandler: {
661
661
  */
662
662
  <A, R, E>(handler: (value: A) => Effect.Effect<void, E, R>): <Name extends string, XR, XE>(
663
663
  self: Command<Name, A, XE, XR>
664
- ) => Command<Name, A, E, R>
664
+ ) => Command<Name, A, E, Exclude<R, GlobalFlag.BuiltInSettingContext>>
665
665
  /* ========================================================================== */
666
666
  /* Combinators */
667
667
  /* ========================================================================== */
@@ -693,11 +693,12 @@ export const withHandler: {
693
693
  <Name extends string, A, XR, XE, R, E>(
694
694
  self: Command<Name, A, XE, XR>,
695
695
  handler: (value: A) => Effect.Effect<void, E, R>
696
- ): Command<Name, A, E, R>
696
+ ): Command<Name, A, E, Exclude<R, GlobalFlag.BuiltInSettingContext>>
697
697
  } = dual(2, <Name extends string, A, XR, XE, R, E>(
698
698
  self: Command<Name, A, XE, XR>,
699
699
  handler: (value: A) => Effect.Effect<void, E, R>
700
- ): Command<Name, A, E, R> => makeCommand({ ...toImpl(self), handle: handler }))
700
+ ): Command<Name, A, E, Exclude<R, GlobalFlag.BuiltInSettingContext>> =>
701
+ makeCommand({ ...toImpl(self), handle: handler }))
701
702
 
702
703
  interface SubcommandGroupInternal {
703
704
  readonly group: string | undefined
@@ -942,6 +943,7 @@ export const withSubcommands: {
942
943
  shortDescription: impl.shortDescription,
943
944
  alias: impl.alias,
944
945
  annotations: impl.annotations,
946
+ globalFlags: impl.globalFlags,
945
947
  examples: impl.examples,
946
948
  service: impl.service,
947
949
  subcommands: normalized.groups,
@@ -950,7 +952,50 @@ export const withSubcommands: {
950
952
  })
951
953
  })
952
954
 
955
+ /**
956
+ * Declares global flags for a command scope.
957
+ *
958
+ * Declared global flags apply to the command and all of its descendants.
959
+ *
960
+ * @since 4.0.0
961
+ * @category combinators
962
+ */
963
+ export const withGlobalFlags: {
964
+ /**
965
+ * Declares global flags for a command scope.
966
+ *
967
+ * Declared global flags apply to the command and all of its descendants.
968
+ *
969
+ * @since 4.0.0
970
+ * @category combinators
971
+ */
972
+ <const GlobalFlags extends ReadonlyArray<GlobalFlag.GlobalFlag<any>>>(globalFlags: GlobalFlags): <Name extends string, Input, E, R>(
973
+ self: Command<Name, Input, E, R>
974
+ ) => Command<Name, Input, E, Exclude<R, ExtractGlobalFlagContext<GlobalFlags>>>
975
+ /**
976
+ * Declares global flags for a command scope.
977
+ *
978
+ * Declared global flags apply to the command and all of its descendants.
979
+ *
980
+ * @since 4.0.0
981
+ * @category combinators
982
+ */
983
+ <Name extends string, Input, E, R, const GlobalFlags extends ReadonlyArray<GlobalFlag.GlobalFlag<any>>>(self: Command<Name, Input, E, R>, globalFlags: GlobalFlags): Command<Name, Input, E, Exclude<R, ExtractGlobalFlagContext<GlobalFlags>>>
984
+ } = dual(
985
+ 2,
986
+ <Name extends string, Input, E, R, const GlobalFlags extends ReadonlyArray<GlobalFlag.GlobalFlag<any>>>(
987
+ self: Command<Name, Input, E, R>,
988
+ globalFlags: GlobalFlags
989
+ ): Command<Name, Input, E, Exclude<R, ExtractGlobalFlagContext<GlobalFlags>>> => {
990
+ const impl = toImpl(self)
991
+ const next = Array.from(new Set([...impl.globalFlags, ...globalFlags]))
992
+ return makeCommand({ ...impl, globalFlags: next })
993
+ }
994
+ )
995
+
953
996
  // Type extractors for subcommand arrays - T[number] gives union of all elements
997
+ type ExtractGlobalFlagContext<T extends ReadonlyArray<GlobalFlag.GlobalFlag<any>>> = T[number] extends
998
+ GlobalFlag.Setting<infer Id, any> ? GlobalFlag.Setting.Identifier<Id> : never
954
999
  type ExtractSubcommand<T> = T extends Command<any, any, any, any> ? T
955
1000
  : T extends Command.SubcommandGroup<infer Commands> ? Commands[number]
956
1001
  : never
@@ -1534,6 +1579,45 @@ export const provideEffectDiscard: {
1534
1579
  /* Execution */
1535
1580
  /* ========================================================================== */
1536
1581
 
1582
+ const getOutOfScopeGlobalFlagErrors = (
1583
+ allFlags: ReadonlyArray<GlobalFlag.GlobalFlag<any>>,
1584
+ activeFlags: ReadonlyArray<GlobalFlag.GlobalFlag<any>>,
1585
+ flagMap: Record<string, ReadonlyArray<string>>,
1586
+ commandPath: ReadonlyArray<string>
1587
+ ): ReadonlyArray<CliError.CliError> => {
1588
+ const activeSet = new Set(activeFlags)
1589
+ const errors: Array<CliError.CliError> = []
1590
+ const seen = new Set<string>()
1591
+
1592
+ for (const flag of allFlags) {
1593
+ if (activeSet.has(flag)) {
1594
+ continue
1595
+ }
1596
+
1597
+ const singles = Param.extractSingleParams(flag.flag)
1598
+ for (const single of singles) {
1599
+ const entries = flagMap[single.name]
1600
+ if (!entries || entries.length === 0) {
1601
+ continue
1602
+ }
1603
+ const option = `--${single.name}`
1604
+ if (seen.has(option)) {
1605
+ continue
1606
+ }
1607
+ seen.add(option)
1608
+ errors.push(
1609
+ new CliError.UnrecognizedOption({
1610
+ option,
1611
+ suggestions: [],
1612
+ command: commandPath
1613
+ })
1614
+ )
1615
+ }
1616
+ }
1617
+
1618
+ return errors
1619
+ }
1620
+
1537
1621
  const showHelp = <Name extends string, Input, E, R>(
1538
1622
  command: Command<Name, Input, E, R>,
1539
1623
  commandPath: ReadonlyArray<string>,
@@ -1541,7 +1625,7 @@ const showHelp = <Name extends string, Input, E, R>(
1541
1625
  ): Effect.Effect<void, never, Environment> =>
1542
1626
  Effect.gen(function*() {
1543
1627
  const formatter = yield* CliOutput.Formatter
1544
- const helpDoc = yield* getHelpForCommandPath(command, commandPath, GlobalFlag.Registry)
1628
+ const helpDoc = yield* getHelpForCommandPath(command, commandPath, GlobalFlag.BuiltIns)
1545
1629
  yield* Console.log(formatter.formatHelpDoc(helpDoc))
1546
1630
  if (errors && errors.length > 0) {
1547
1631
  yield* Console.error(formatter.formatErrors(errors))
@@ -1698,11 +1782,11 @@ export const runWith = <const Name extends string, Input, E, R>(
1698
1782
  function*(args: ReadonlyArray<string>) {
1699
1783
  const { tokens, trailingOperands } = Lexer.lex(args)
1700
1784
 
1701
- // 1. Read global flags from registry
1702
- const flags = Array.from(yield* GlobalFlag.Registry)
1785
+ // 1. Collect known global flags from the command tree
1786
+ const allFlags = getGlobalFlagsForCommandTree(command, GlobalFlag.BuiltIns)
1703
1787
 
1704
1788
  // 2. Extract global flag tokens
1705
- const allFlagParams = flags.flatMap((f) => Param.extractSingleParams(f.flag))
1789
+ const allFlagParams = allFlags.flatMap((f) => Param.extractSingleParams(f.flag))
1706
1790
  const globalRegistry = Parser.createFlagRegistry(allFlagParams.filter(Param.isFlagParam))
1707
1791
  const { flagMap, remainder } = Parser.consumeKnownFlags(tokens, globalRegistry)
1708
1792
  const emptyArgs: Param.ParsedArgs = { flags: flagMap, arguments: [] }
@@ -1711,9 +1795,17 @@ export const runWith = <const Name extends string, Input, E, R>(
1711
1795
  const parsedArgs = yield* Parser.parseArgs({ tokens: remainder, trailingOperands }, command)
1712
1796
  const commandPath = [command.name, ...Parser.getCommandPath(parsedArgs)] as const
1713
1797
  const handlerCtx: GlobalFlag.HandlerContext = { command, commandPath, version: config.version }
1798
+ const activeFlags = getGlobalFlagsForCommandPath(command, commandPath, GlobalFlag.BuiltIns)
1714
1799
 
1715
- // 4. Process action flags first present action wins, then exit
1716
- for (const flag of flags) {
1800
+ // 4. Reject globals that were passed outside the active command scope
1801
+ const outOfScopeErrors = getOutOfScopeGlobalFlagErrors(allFlags, activeFlags, flagMap, commandPath)
1802
+ if (outOfScopeErrors.length > 0) {
1803
+ const parseErrors = parsedArgs.errors ?? []
1804
+ return yield* showHelp(command, commandPath, [...outOfScopeErrors, ...parseErrors])
1805
+ }
1806
+
1807
+ // 5. Process action flags — first present action wins, then exit
1808
+ for (const flag of activeFlags) {
1717
1809
  if (flag._tag !== "Action") continue
1718
1810
  const singles = Param.extractSingleParams(flag.flag)
1719
1811
  const hasEntry = singles.some((s) => {
@@ -1726,7 +1818,7 @@ export const runWith = <const Name extends string, Input, E, R>(
1726
1818
  return
1727
1819
  }
1728
1820
 
1729
- // 5. Handle parsing errors
1821
+ // 6. Handle parsing errors
1730
1822
  if (parsedArgs.errors && parsedArgs.errors.length > 0) {
1731
1823
  return yield* showHelp(command, commandPath, parsedArgs.errors)
1732
1824
  }
@@ -1735,29 +1827,25 @@ export const runWith = <const Name extends string, Input, E, R>(
1735
1827
  return yield* showHelp(command, commandPath, [parseResult.failure])
1736
1828
  }
1737
1829
 
1738
- // 6. Provide setting values
1739
- let contextLayer: Layer.Layer<never> = Layer.empty
1740
- for (const flag of flags) {
1830
+ // 7. Provide setting values
1831
+ let program = commandImpl.handle(parseResult.success, [command.name])
1832
+ for (const flag of activeFlags) {
1741
1833
  if (flag._tag !== "Setting") continue
1742
1834
  const [, value] = yield* flag.flag.parse(emptyArgs)
1743
- contextLayer = Layer.merge(contextLayer, Layer.succeed(flag, value))
1835
+ program = Effect.provideService(program, flag, value)
1744
1836
  }
1745
1837
 
1746
- // 7. Apply built-in setting behavior
1747
- if (flags.includes(GlobalFlag.LogLevel)) {
1748
- const [, logLevel] = yield* GlobalFlag.LogLevel.flag.parse(emptyArgs)
1749
- contextLayer = Layer.merge(
1750
- contextLayer,
1751
- Option.match(logLevel, {
1752
- onNone: () => Layer.empty,
1753
- onSome: (level) => Layer.succeed(References.MinimumLogLevel, level)
1754
- })
1755
- )
1756
- }
1838
+ const [, logLevel] = yield* GlobalFlag.LogLevel.flag.parse(emptyArgs)
1839
+ program = Effect.provideService(program, GlobalFlag.LogLevel, logLevel)
1840
+
1841
+ // 8. Apply built-in setting behavior
1842
+ const services = Option.match(logLevel, {
1843
+ onNone: () => ServiceMap.empty(),
1844
+ onSome: (level) => ServiceMap.make(References.MinimumLogLevel, level)
1845
+ })
1757
1846
 
1758
- // 8. Run command handler with composed context
1759
- const program = commandImpl.handle(parseResult.success, [command.name])
1760
- yield* Effect.provide(program, contextLayer)
1847
+ // 9. Run command handler with composed context
1848
+ yield* Effect.provideServices(program, services)
1761
1849
  },
1762
1850
  Effect.catchIf(
1763
1851
  ((error: any) =>
@@ -4,7 +4,6 @@
4
4
 
5
5
  import * as Console from "../../Console.ts"
6
6
  import * as Effect from "../../Effect.ts"
7
- import { dual } from "../../Function.ts"
8
7
  import type { LogLevel as LogLevelType } from "../../LogLevel.ts"
9
8
  import * as Option from "../../Option.ts"
10
9
  import * as ServiceMap from "../../ServiceMap.ts"
@@ -49,18 +48,30 @@ export interface Action<A> {
49
48
  * @since 4.0.0
50
49
  * @category models
51
50
  */
52
- export interface Setting<A> extends ServiceMap.Reference<A> {
51
+ export interface Setting<Id extends string, A> extends ServiceMap.Service<Setting.Identifier<Id>, A> {
53
52
  readonly _tag: "Setting"
53
+ readonly id: Id
54
54
  readonly flag: Flag.Flag<A>
55
55
  }
56
56
 
57
+ /**
58
+ * @since 4.0.0
59
+ */
60
+ export declare namespace Setting {
61
+ /**
62
+ * @since 4.0.0
63
+ * @category models
64
+ */
65
+ export type Identifier<Id extends string> = `effect/unstable/cli/GlobalFlag/${Id}`
66
+ }
67
+
57
68
  /**
58
69
  * Global flag discriminated union.
59
70
  *
60
71
  * @since 4.0.0
61
72
  * @category models
62
73
  */
63
- export type GlobalFlag<A> = Action<A> | Setting<A>
74
+ export type GlobalFlag<A> = Action<A> | Setting<any, A>
64
75
 
65
76
  /* ========================================================================== */
66
77
  /* Constructors */
@@ -90,17 +101,19 @@ export const action = <A>(options: {
90
101
  * @since 4.0.0
91
102
  * @category constructors
92
103
  */
93
- export const setting = <A>(options: {
104
+ export const setting = <const Id extends string>(
105
+ id: Id
106
+ ) =>
107
+ <A>(options: {
94
108
  readonly flag: Flag.Flag<A>
95
- readonly defaultValue: () => A
96
- }): Setting<A> => {
109
+ }): Setting<Id, A> => {
97
110
  settingIdCounter += 1
98
- const ref = ServiceMap.Reference<A>(
99
- `effect/cli/GlobalFlag/Setting/${settingIdCounter}`,
100
- { defaultValue: options.defaultValue }
111
+ const ref = ServiceMap.Service<Setting.Identifier<Id>, A>(
112
+ `effect/unstable/cli/GlobalFlag/${id}/${settingIdCounter}`
101
113
  )
102
114
  return Object.assign(ref, {
103
115
  _tag: "Setting" as const,
116
+ id,
104
117
  flag: options.flag
105
118
  })
106
119
  }
@@ -130,7 +143,7 @@ export const Help: Action<boolean> = action({
130
143
  run: (_, { command, commandPath }) =>
131
144
  Effect.gen(function*() {
132
145
  const formatter = yield* CliOutput.Formatter
133
- const helpDoc = yield* HelpInternal.getHelpForCommandPath(command, commandPath, Registry)
146
+ const helpDoc = yield* HelpInternal.getHelpForCommandPath(command, commandPath, BuiltIns)
134
147
  yield* Console.log(formatter.formatHelpDoc(helpDoc))
135
148
  })
136
149
  })
@@ -184,7 +197,7 @@ export const Completions: Action<Option.Option<"bash" | "zsh" | "fish">> = actio
184
197
  * @since 4.0.0
185
198
  * @category references
186
199
  */
187
- export const LogLevel: Setting<Option.Option<LogLevelType>> = setting({
200
+ export const LogLevel: Setting<"log-level", Option.Option<LogLevelType>> = setting("log-level")({
188
201
  flag: Flag.choiceWithValue(
189
202
  "log-level",
190
203
  [
@@ -201,121 +214,30 @@ export const LogLevel: Setting<Option.Option<LogLevelType>> = setting({
201
214
  ).pipe(
202
215
  Flag.optional,
203
216
  Flag.withDescription("Sets the minimum log level")
204
- ),
205
- defaultValue: () => Option.none()
217
+ )
206
218
  })
207
219
 
208
220
  /* ========================================================================== */
209
- /* Registry */
221
+ /* References */
210
222
  /* ========================================================================== */
211
223
 
212
224
  /**
213
- * The ordered set of global flag references.
214
- * The parser iterates this set to know which flags to extract.
225
+ * Built-in global flags in default precedence order.
215
226
  *
216
227
  * @since 4.0.0
217
228
  * @category references
218
229
  */
219
- export const Registry: ServiceMap.Reference<
220
- Set<GlobalFlag<any>>
221
- > = ServiceMap.Reference("effect/cli/GlobalFlag/Registry", {
222
- defaultValue: () =>
223
- new Set<GlobalFlag<any>>([
224
- Help,
225
- Version,
226
- Completions,
227
- LogLevel
228
- ])
229
- })
230
-
231
- /* ========================================================================== */
232
- /* Public API */
233
- /* ========================================================================== */
230
+ export const BuiltIns: ReadonlyArray<GlobalFlag<any>> = [
231
+ Help,
232
+ Version,
233
+ Completions,
234
+ LogLevel
235
+ ]
234
236
 
235
237
  /**
236
- * Adds a global flag to the registry.
238
+ * Built-in setting context identifiers.
237
239
  *
238
240
  * @since 4.0.0
239
- * @category modifiers
240
- */
241
- export const add: {
242
- /* ========================================================================== */
243
- /* Public API */
244
- /* ========================================================================== */
245
-
246
- /**
247
- * Adds a global flag to the registry.
248
- *
249
- * @since 4.0.0
250
- * @category modifiers
251
- */
252
- <A>(flag: GlobalFlag<A>): <B, E, R>(
253
- self: Effect.Effect<B, E, R>
254
- ) => Effect.Effect<B, E, R>
255
- /* ========================================================================== */
256
- /* Public API */
257
- /* ========================================================================== */
258
-
259
- /**
260
- * Adds a global flag to the registry.
261
- *
262
- * @since 4.0.0
263
- * @category modifiers
264
- */
265
- <B, E, R, A>(self: Effect.Effect<B, E, R>, flag: GlobalFlag<A>): Effect.Effect<B, E, R>
266
- } = dual(
267
- 2,
268
- Effect.fnUntraced(function*<B, E, R, A>(
269
- self: Effect.Effect<B, E, R>,
270
- flag: GlobalFlag<A>
271
- ) {
272
- const currentRegistry = yield* Registry
273
- const nextRegistry = new Set([...currentRegistry, flag])
274
- return yield* Effect.provideService(self, Registry, nextRegistry)
275
- })
276
- )
277
-
278
- /**
279
- * Removes a global flag by its reference.
280
- *
281
- * @since 4.0.0
282
- * @category modifiers
283
- */
284
- export const remove: {
285
- /**
286
- * Removes a global flag by its reference.
287
- *
288
- * @since 4.0.0
289
- * @category modifiers
290
- */
291
- <A>(flag: GlobalFlag<A>): <B, E, R>(
292
- self: Effect.Effect<B, E, R>
293
- ) => Effect.Effect<B, E, R>
294
- /**
295
- * Removes a global flag by its reference.
296
- *
297
- * @since 4.0.0
298
- * @category modifiers
299
- */
300
- <B, E, R, A>(self: Effect.Effect<B, E, R>, flag: GlobalFlag<A>): Effect.Effect<B, E, R>
301
- } = dual(
302
- 2,
303
- Effect.fnUntraced(function*<B, E, R, A>(
304
- self: Effect.Effect<B, E, R>,
305
- flag: GlobalFlag<A>
306
- ) {
307
- const currentRegistry = yield* Registry
308
- const nextRegistry = new Set(currentRegistry)
309
- nextRegistry.delete(flag)
310
- return yield* Effect.provideService(self, Registry, nextRegistry)
311
- })
312
- )
313
-
314
- /**
315
- * Removes all global flags (built-in and user-defined).
316
- *
317
- * @since 4.0.0
318
- * @category modifiers
241
+ * @category models
319
242
  */
320
- export const clear = <B, E, R>(self: Effect.Effect<B, E, R>): Effect.Effect<B, E, R> =>
321
- Effect.provideService(self, Registry, new Set())
243
+ export type BuiltInSettingContext = Setting.Identifier<"log-level">
@@ -12,6 +12,7 @@ import { pipeArguments } from "../../../Pipeable.ts"
12
12
  import * as Predicate from "../../../Predicate.ts"
13
13
  import * as ServiceMap from "../../../ServiceMap.ts"
14
14
  import * as CliError from "../CliError.ts"
15
+ import type * as GlobalFlag from "../GlobalFlag.ts"
15
16
  import type { ArgDoc, ExampleDoc, FlagDoc, HelpDoc, SubcommandGroupDoc } from "../HelpDoc.ts"
16
17
  import * as Param from "../Param.ts"
17
18
  import * as Primitive from "../Primitive.ts"
@@ -36,6 +37,7 @@ export interface CommandInternal<Name extends string, Input, E, R> extends Comma
36
37
  readonly config: ConfigInternal
37
38
  readonly service: ServiceMap.Service<CommandContext<Name>, Input>
38
39
  readonly annotations: ServiceMap.ServiceMap<never>
40
+ readonly globalFlags: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
39
41
  readonly parse: (input: ParsedTokens) => Effect.Effect<Input, CliError.CliError, Environment>
40
42
  readonly handle: (
41
43
  input: Input,
@@ -88,6 +90,7 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
88
90
  readonly config: ConfigInternal
89
91
  readonly service?: ServiceMap.Service<CommandContext<Name>, Input> | undefined
90
92
  readonly annotations?: ServiceMap.ServiceMap<never> | undefined
93
+ readonly globalFlags?: ReadonlyArray<GlobalFlag.GlobalFlag<any>> | undefined
91
94
  readonly description?: string | undefined
92
95
  readonly shortDescription?: string | undefined
93
96
  readonly alias?: string | undefined
@@ -101,6 +104,7 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
101
104
  const service = options.service ?? ServiceMap.Service<CommandContext<Name>, Input>(`${TypeId}/${options.name}`)
102
105
  const config = options.config
103
106
  const annotations = options.annotations ?? ServiceMap.empty()
107
+ const globalFlags = options.globalFlags ?? []
104
108
  const subcommands = options.subcommands ?? []
105
109
 
106
110
  const handle = (
@@ -191,6 +195,7 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
191
195
  name: options.name,
192
196
  examples: options.examples ?? [],
193
197
  annotations,
198
+ globalFlags,
194
199
  subcommands,
195
200
  config,
196
201
  service,
@@ -6,46 +6,127 @@
6
6
  * Extracted from command.ts to avoid circular dependencies.
7
7
  */
8
8
  import * as Effect from "../../../Effect.ts"
9
- import type * as ServiceMap from "../../../ServiceMap.ts"
10
9
  import type { Command } from "../Command.ts"
11
- import type { GlobalFlag } from "../GlobalFlag.ts"
10
+ import type * as GlobalFlag from "../GlobalFlag.ts"
12
11
  import type { FlagDoc, HelpDoc } from "../HelpDoc.ts"
13
12
  import * as Param from "../Param.ts"
14
13
  import * as Primitive from "../Primitive.ts"
15
14
  import { toImpl } from "./command.ts"
16
15
 
16
+ const dedupeGlobalFlags = (
17
+ flags: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
18
+ ): ReadonlyArray<GlobalFlag.GlobalFlag<any>> => {
19
+ const seen = new Set<GlobalFlag.GlobalFlag<any>>()
20
+ const deduped: Array<GlobalFlag.GlobalFlag<any>> = []
21
+ for (const flag of flags) {
22
+ if (seen.has(flag)) {
23
+ continue
24
+ }
25
+ seen.add(flag)
26
+ deduped.push(flag)
27
+ }
28
+ return deduped
29
+ }
30
+
17
31
  /**
18
- * Helper function to get help documentation for a specific command path.
19
- * Navigates through the command hierarchy to find the right command.
20
- * Reads global flags from the registry and includes them in the help doc.
32
+ * Returns the resolved command lineage for the provided path.
33
+ * Includes the root command as the first element.
21
34
  */
22
- export const getHelpForCommandPath = <Name extends string, Input, E, R>(
35
+ export const getCommandsForCommandPath = <Name extends string, Input, E, R>(
23
36
  command: Command<Name, Input, E, R>,
24
- commandPath: ReadonlyArray<string>,
25
- registry: ServiceMap.Reference<Set<GlobalFlag<unknown>>>
26
- ): Effect.Effect<HelpDoc, never, never> =>
27
- Effect.gen(function*() {
28
- let currentCommand: Command.Any = command
37
+ commandPath: ReadonlyArray<string>
38
+ ): ReadonlyArray<Command.Any> => {
39
+ const commands: Array<Command.Any> = [command]
40
+ let currentCommand: Command.Any = command
29
41
 
30
- for (let i = 1; i < commandPath.length; i++) {
31
- const subcommandName = commandPath[i]
32
- let subcommand: Command.Any | undefined = undefined
42
+ for (let i = 1; i < commandPath.length; i++) {
43
+ const subcommandName = commandPath[i]
44
+ let subcommand: Command.Any | undefined = undefined
33
45
 
34
- for (const group of currentCommand.subcommands) {
35
- subcommand = group.commands.find((sub) => sub.name === subcommandName)
36
- if (subcommand) {
37
- break
38
- }
46
+ for (const group of currentCommand.subcommands) {
47
+ subcommand = group.commands.find((sub) => sub.name === subcommandName)
48
+ if (subcommand) {
49
+ break
39
50
  }
51
+ }
40
52
 
41
- if (subcommand) {
42
- currentCommand = subcommand
53
+ if (!subcommand) {
54
+ break
55
+ }
56
+
57
+ commands.push(subcommand)
58
+ currentCommand = subcommand
59
+ }
60
+
61
+ return commands
62
+ }
63
+
64
+ /**
65
+ * Returns active global flags for a command path.
66
+ * Built-ins are prepended and declarations are collected root -> leaf.
67
+ */
68
+ export const getGlobalFlagsForCommandPath = <Name extends string, Input, E, R>(
69
+ command: Command<Name, Input, E, R>,
70
+ commandPath: ReadonlyArray<string>,
71
+ builtIns: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
72
+ ): ReadonlyArray<GlobalFlag.GlobalFlag<any>> => {
73
+ const commands = getCommandsForCommandPath(command, commandPath)
74
+ const declared = commands.flatMap((current) => toImpl(current).globalFlags)
75
+ return dedupeGlobalFlags([
76
+ ...builtIns,
77
+ ...declared
78
+ ])
79
+ }
80
+
81
+ const collectDeclaredGlobalFlags = (command: Command.Any): ReadonlyArray<GlobalFlag.GlobalFlag<any>> => {
82
+ const collected: Array<GlobalFlag.GlobalFlag<any>> = []
83
+
84
+ const visit = (current: Command.Any): void => {
85
+ const impl = toImpl(current)
86
+ for (const flag of impl.globalFlags) {
87
+ collected.push(flag)
88
+ }
89
+ for (const group of current.subcommands) {
90
+ for (const subcommand of group.commands) {
91
+ visit(subcommand)
43
92
  }
44
93
  }
94
+ }
95
+
96
+ visit(command)
97
+ return dedupeGlobalFlags(collected)
98
+ }
99
+
100
+ /**
101
+ * Returns all global flags declared in a command tree.
102
+ * Built-ins are prepended and command declarations are deduplicated by identity.
103
+ */
104
+ export const getGlobalFlagsForCommandTree = <Name extends string, Input, E, R>(
105
+ command: Command<Name, Input, E, R>,
106
+ builtIns: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
107
+ ): ReadonlyArray<GlobalFlag.GlobalFlag<any>> =>
108
+ dedupeGlobalFlags([
109
+ ...builtIns,
110
+ ...collectDeclaredGlobalFlags(command)
111
+ ])
112
+
113
+ /**
114
+ * Helper function to get help documentation for a specific command path.
115
+ * Navigates through the command hierarchy to find the right command.
116
+ * Reads active global flags for the path and includes them in the help doc.
117
+ */
118
+ export const getHelpForCommandPath = <Name extends string, Input, E, R>(
119
+ command: Command<Name, Input, E, R>,
120
+ commandPath: ReadonlyArray<string>,
121
+ builtIns: ReadonlyArray<GlobalFlag.GlobalFlag<any>>
122
+ ): Effect.Effect<HelpDoc, never, never> =>
123
+ Effect.gen(function*() {
124
+ const commands = getCommandsForCommandPath(command, commandPath)
125
+ const currentCommand = commands.length > 0 ? commands[commands.length - 1] : command
45
126
 
46
127
  const baseDoc = toImpl(currentCommand).buildHelpDoc(commandPath)
47
128
 
48
- const flags = yield* registry
129
+ const flags = getGlobalFlagsForCommandPath(command, commandPath, builtIns)
49
130
  const globalFlagDocs: Array<FlagDoc> = []
50
131
  for (const flag of flags) {
51
132
  const singles = Param.extractSingleParams(flag.flag)