diff --git a/.changeset/many-badgers-obey.md b/.changeset/many-badgers-obey.md
new file mode 100644
index 0000000000..6ec45f57f8
--- /dev/null
+++ b/.changeset/many-badgers-obey.md
@@ -0,0 +1,5 @@
+---
+"effect": patch
+---
+
+Allow schemas provided to CLI flags / arguments to utilize the environment required by the CLI
diff --git a/packages/effect/src/unstable/cli/Argument.ts b/packages/effect/src/unstable/cli/Argument.ts
index ca0fe5ddaf..950ead512a 100644
--- a/packages/effect/src/unstable/cli/Argument.ts
+++ b/packages/effect/src/unstable/cli/Argument.ts
@@ -17,6 +17,7 @@ import type * as Redacted from "../../Redacted.ts"
import type * as Result from "../../Result.ts"
import type * as Schema from "../../Schema.ts"
import type * as CliError from "./CliError.ts"
+import type { Environment } from "./Command.ts"
import * as Param from "./Param.ts"
import type * as Primitive from "./Primitive.ts"
@@ -261,7 +262,7 @@ export const fileParse = (
*/
export const fileSchema = (
name: string,
- schema: Schema.Decoder,
+ schema: Schema.Decoder,
options?: Primitive.FileSchemaOptions | undefined
): Argument => Param.fileSchema(Param.argumentKind, name, schema, options)
@@ -339,11 +340,11 @@ export const withDescription: {
*/
export const withDefault: {
(
- defaultValue: B | Effect.Effect
+ defaultValue: B | Effect.Effect
): (self: Argument) => Argument
(
self: Argument,
- defaultValue: B | Effect.Effect
+ defaultValue: B | Effect.Effect
): Argument
} = Param.withDefault
@@ -471,15 +472,15 @@ export const map: {
*/
export const mapEffect: {
(
- f: (a: A) => Effect.Effect
+ f: (a: A) => Effect.Effect
): (self: Argument) => Argument
(
self: Argument,
- f: (a: A) => Effect.Effect
+ f: (a: A) => Effect.Effect
): Argument
} = dual(2, (
self: Argument,
- f: (a: A) => Effect.Effect
+ f: (a: A) => Effect.Effect
) => Param.mapEffect(self, f))
/**
@@ -589,8 +590,8 @@ export const between: {
* @since 4.0.0
*/
export const withSchema: {
- (schema: Schema.Codec): (self: Argument) => Argument
- (self: Argument, schema: Schema.Codec): Argument
+ (schema: Schema.Codec): (self: Argument) => Argument
+ (self: Argument, schema: Schema.Codec): Argument
} = dual(2, (self: Argument, schema: Schema.Codec) => Param.withSchema(self, schema))
/**
diff --git a/packages/effect/src/unstable/cli/Flag.ts b/packages/effect/src/unstable/cli/Flag.ts
index 4ab5ee058f..4029a6b1b7 100644
--- a/packages/effect/src/unstable/cli/Flag.ts
+++ b/packages/effect/src/unstable/cli/Flag.ts
@@ -18,6 +18,7 @@ import type * as Redacted from "../../Redacted.ts"
import type * as Result from "../../Result.ts"
import type * as Schema from "../../Schema.ts"
import type * as CliError from "./CliError.ts"
+import type { Environment } from "./Command.ts"
import * as Param from "./Param.ts"
import type * as Primitive from "./Primitive.ts"
@@ -355,7 +356,7 @@ export const fileParse = (
*/
export const fileSchema = (
name: string,
- schema: Schema.Decoder,
+ schema: Schema.Decoder,
options?: Primitive.FileSchemaOptions | undefined
): Flag => Param.fileSchema(Param.flagKind, name, schema, options)
@@ -579,8 +580,8 @@ export const optional = (param: Flag): Flag> => Param.opt
* @since 4.0.0
*/
export const withDefault: {
- (defaultValue: B | Effect.Effect): (self: Flag) => Flag
- (self: Flag, defaultValue: B | Effect.Effect): Flag
+ (defaultValue: B | Effect.Effect): (self: Flag) => Flag
+ (self: Flag, defaultValue: B | Effect.Effect): Flag
} = Param.withDefault
/**
@@ -677,15 +678,15 @@ export const map: {
*/
export const mapEffect: {
(
- f: (a: A) => Effect.Effect
+ f: (a: A) => Effect.Effect
): (self: Flag) => Flag
(
self: Flag,
- f: (a: A) => Effect.Effect
+ f: (a: A) => Effect.Effect
): Flag
} = dual(2, (
self: Flag,
- f: (a: A) => Effect.Effect
+ f: (a: A) => Effect.Effect
) => Param.mapEffect(self, f))
/**
@@ -981,6 +982,6 @@ export const orElseResult: {
* @since 4.0.0
*/
export const withSchema: {
- (schema: Schema.Codec): (self: Flag) => Flag
- (self: Flag, schema: Schema.Codec): Flag
+ (schema: Schema.Codec): (self: Flag) => Flag
+ (self: Flag, schema: Schema.Codec): Flag
} = dual(2, (self: Flag, schema: Schema.Codec) => Param.withSchema(self, schema))
diff --git a/packages/effect/src/unstable/cli/Param.ts b/packages/effect/src/unstable/cli/Param.ts
index 734ecbc9a9..689a3fc08b 100644
--- a/packages/effect/src/unstable/cli/Param.ts
+++ b/packages/effect/src/unstable/cli/Param.ts
@@ -12,19 +12,16 @@
*/
import * as Config from "../../Config.ts"
import * as Effect from "../../Effect.ts"
-import type * as FileSystem from "../../FileSystem.ts"
import { dual, identity } from "../../Function.ts"
import * as Option from "../../Option.ts"
-import type * as Path from "../../Path.ts"
import { type Pipeable, pipeArguments } from "../../Pipeable.ts"
import * as Predicate from "../../Predicate.ts"
import type * as Redacted from "../../Redacted.ts"
import * as Result from "../../Result.ts"
import * as Schema from "../../Schema.ts"
-import type * as Terminal from "../../Terminal.ts"
import type { Covariant } from "../../Types.ts"
-import type { ChildProcessSpawner } from "../process/ChildProcessSpawner.ts"
import * as CliError from "./CliError.ts"
+import type { Environment } from "./Command.ts"
import * as Primitive from "./Primitive.ts"
import * as Prompt from "./Prompt.ts"
@@ -56,15 +53,6 @@ export interface Param extends Param.Variance
*/
export type ParamKind = "argument" | "flag"
-/**
- * Services that parameter parsing can require, such as filesystem, path,
- * terminal, and child-process support.
- *
- * @category models
- * @since 4.0.0
- */
-export type Environment = FileSystem.FileSystem | Path.Path | Terminal.Terminal | ChildProcessSpawner
-
/**
* Defines the kind discriminator for positional argument parameters.
*
@@ -859,7 +847,7 @@ export const fileParse = (
export const fileSchema = (
kind: Kind,
name: string,
- schema: Schema.Decoder,
+ schema: Schema.Decoder,
options?: Primitive.FileSchemaOptions | undefined
): Param =>
makeSingle({
@@ -1799,8 +1787,15 @@ export const withMetavar: {
* @since 4.0.0
*/
export const withSchema: {
- (schema: Schema.Codec): (self: Param) => Param
- (self: Param, schema: Schema.Codec): Param
+ (
+ schema: Schema.Codec
+ ): (
+ self: Param
+ ) => Param
+ (
+ self: Param,
+ schema: Schema.Codec
+ ): Param
} = dual(2, (
self: Param,
schema: Schema.Codec
diff --git a/packages/effect/src/unstable/cli/Primitive.ts b/packages/effect/src/unstable/cli/Primitive.ts
index 53c04b44d2..dd340e80b0 100644
--- a/packages/effect/src/unstable/cli/Primitive.ts
+++ b/packages/effect/src/unstable/cli/Primitive.ts
@@ -24,6 +24,7 @@ import * as Schema from "../../Schema.ts"
import type { Formatter } from "../../SchemaIssue.ts"
import type * as Struct from "../../Struct.ts"
import type { Covariant } from "../../Types.ts"
+import type { Environment } from "./Command.ts"
const TypeId = "~effect/cli/Primitive"
@@ -58,7 +59,7 @@ const TypeId = "~effect/cli/Primitive"
*/
export interface Primitive extends Primitive.Variance {
readonly _tag: string
- readonly parse: (value: string) => Effect.Effect
+ readonly parse: (value: string) => Effect.Effect
}
/**
@@ -97,9 +98,7 @@ export const isBoolean = (p: Primitive): p is Primitive => p._
const makePrimitive = (
tag: string,
- parse: (
- value: string
- ) => Effect.Effect
+ parse: (value: string) => Effect.Effect
): Primitive =>
Object.assign(Object.create(Proto), {
_tag: tag,
@@ -108,7 +107,7 @@ const makePrimitive = (
const makeSchemaPrimitive = (
tag: string,
- schema: Schema.Codec
+ schema: Schema.Codec
): Primitive => {
const toCodecStringTree = Schema.toCodecStringTree(schema)
const decode = Schema.decodeUnknownEffect(toCodecStringTree)
@@ -614,7 +613,7 @@ export type FileSchemaOptions = Struct.Simplify<
* @since 4.0.0
*/
export const fileSchema = (
- schema: Schema.Decoder,
+ schema: Schema.Decoder,
options?: FileSchemaOptions | undefined
): Primitive => {
const decode = Schema.decodeUnknownEffect(schema)
diff --git a/packages/effect/src/unstable/cli/internal/parser.ts b/packages/effect/src/unstable/cli/internal/parser.ts
index 40abd776e8..18fb1e3cc1 100644
--- a/packages/effect/src/unstable/cli/internal/parser.ts
+++ b/packages/effect/src/unstable/cli/internal/parser.ts
@@ -27,7 +27,7 @@
import * as Effect from "../../../Effect.ts"
import * as Option from "../../../Option.ts"
import * as CliError from "../CliError.ts"
-import type { Command, ParsedTokens } from "../Command.ts"
+import type { Command, Environment, ParsedTokens } from "../Command.ts"
import * as Param from "../Param.ts"
import * as Primitive from "../Primitive.ts"
import { suggest } from "./auto-suggest.ts"
@@ -50,7 +50,7 @@ export const parseArgs = (
lexResult: LexResult,
command: Command.Any,
commandPath: ReadonlyArray = []
-): Effect.Effect =>
+): Effect.Effect =>
Effect.gen(function*() {
const { tokens, trailingOperands: afterEndOfOptions } = lexResult
const newCommandPath = [...commandPath, command.name]
diff --git a/packages/effect/test/unstable/cli/Arguments.test.ts b/packages/effect/test/unstable/cli/Arguments.test.ts
index 29b16436bf..7314bf3755 100644
--- a/packages/effect/test/unstable/cli/Arguments.test.ts
+++ b/packages/effect/test/unstable/cli/Arguments.test.ts
@@ -1,5 +1,5 @@
import { assert, describe, expect, it } from "@effect/vitest"
-import { Effect, FileSystem, Layer, Option, Path, PlatformError, Ref, Result } from "effect"
+import { Effect, FileSystem, Layer, Option, Path, PlatformError, Ref, Result, Stdio } from "effect"
import { TestConsole } from "effect/testing"
import { Argument, CliOutput, Command, Flag } from "effect/unstable/cli"
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
@@ -32,6 +32,11 @@ const CliOutputLayer = CliOutput.layer(
colors: false
})
)
+const StdioLayer = Stdio.layerTest({})
+const ChildProcessSpawnerLayer = Layer.succeed(
+ ChildProcessSpawner.ChildProcessSpawner,
+ ChildProcessSpawner.make(() => Effect.die("Not implemented"))
+)
const TestLayer = Layer.mergeAll(
ConsoleLayer,
@@ -39,7 +44,8 @@ const TestLayer = Layer.mergeAll(
PathLayer,
TerminalLayer,
CliOutputLayer,
- Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("Not implemented")))
+ StdioLayer,
+ ChildProcessSpawnerLayer
)
describe("Command arguments", () => {
diff --git a/packages/effect/test/unstable/cli/Param.test.ts b/packages/effect/test/unstable/cli/Param.test.ts
index 42f38f3b70..9e69900743 100644
--- a/packages/effect/test/unstable/cli/Param.test.ts
+++ b/packages/effect/test/unstable/cli/Param.test.ts
@@ -1,5 +1,5 @@
import { assert, describe, it } from "@effect/vitest"
-import { Config, ConfigProvider, Effect, FileSystem, Layer, Option, Path, Ref } from "effect"
+import { Config, ConfigProvider, Effect, FileSystem, Layer, Option, Path, Ref, Stdio } from "effect"
import { TestConsole } from "effect/testing"
import { Argument, CliError, Flag, Prompt } from "effect/unstable/cli"
import { ChildProcessSpawner } from "effect/unstable/process"
@@ -9,13 +9,19 @@ const ConsoleLayer = TestConsole.layer
const FileSystemLayer = FileSystem.layerNoop({})
const PathLayer = Path.layer
const TerminalLayer = MockTerminal.layer
+const StdioLayer = Stdio.layerTest({})
+const ChildProcessSpawnerLayer = Layer.succeed(
+ ChildProcessSpawner.ChildProcessSpawner,
+ ChildProcessSpawner.make(() => Effect.die("Not implemented"))
+)
const TestLayer = Layer.mergeAll(
ConsoleLayer,
FileSystemLayer,
PathLayer,
TerminalLayer,
- Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("Not implemented")))
+ StdioLayer,
+ ChildProcessSpawnerLayer
)
describe("Param", () => {
diff --git a/packages/effect/test/unstable/cli/Primitive.test.ts b/packages/effect/test/unstable/cli/Primitive.test.ts
index ddbc8e9ebf..0b83b1ee63 100644
--- a/packages/effect/test/unstable/cli/Primitive.test.ts
+++ b/packages/effect/test/unstable/cli/Primitive.test.ts
@@ -1,13 +1,27 @@
import { assert, describe, it } from "@effect/vitest"
-import { Effect, FileSystem, Layer, Path, PlatformError, Redacted } from "effect"
+import { Effect, FileSystem, Layer, Path, PlatformError, Redacted, Stdio } from "effect"
+import { TestConsole } from "effect/testing/index"
import { Primitive } from "effect/unstable/cli"
+import { ChildProcessSpawner } from "effect/unstable/process"
+import * as MockTerminal from "./services/MockTerminal.ts"
+const ConsoleLayer = TestConsole.layer
const FileSystemLayer = FileSystem.layerNoop({})
const PathLayer = Path.layer
+const TerminalLayer = MockTerminal.layer
+const StdioLayer = Stdio.layerTest({})
+const ChildProcessSpawnerLayer = Layer.succeed(
+ ChildProcessSpawner.ChildProcessSpawner,
+ ChildProcessSpawner.make(() => Effect.die("Not implemented"))
+)
const TestLayer = Layer.mergeAll(
+ ConsoleLayer,
FileSystemLayer,
- PathLayer
+ PathLayer,
+ TerminalLayer,
+ StdioLayer,
+ ChildProcessSpawnerLayer
)
// Helper functions to reduce repetition