423 lines
12 KiB
TypeScript
423 lines
12 KiB
TypeScript
import { Effect, Option } from "effect"
|
|
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
import type { TestActions } from "../services/TestActions.ts"
|
|
import { logAction } from "../services/TestActions.ts"
|
|
|
|
// Deeply nested admin commands
|
|
const usersList = Command.make("list", {
|
|
// Optional option with default
|
|
format: Flag.string("format").pipe(
|
|
Flag.withDescription("Output format (json, table, csv)"),
|
|
Flag.withDefault("table")
|
|
),
|
|
// Boolean flag
|
|
active: Flag.boolean("active").pipe(
|
|
Flag.withDescription("Show only active users")
|
|
),
|
|
// Option with both short and long aliases
|
|
verbose: Flag.boolean("verbose").pipe(
|
|
Flag.withAlias("v"),
|
|
Flag.withDescription("Show detailed information")
|
|
)
|
|
}, (config) =>
|
|
logAction("users list", {
|
|
format: config.format,
|
|
active: config.active,
|
|
verbose: config.verbose
|
|
})).pipe(
|
|
Command.withDescription("List all users in the system")
|
|
)
|
|
|
|
const usersCreate = Command.make("create", {
|
|
// Required positional argument
|
|
username: Argument.string("username").pipe(
|
|
Argument.withDescription("Username for the new user")
|
|
),
|
|
// Optional positional argument
|
|
email: Argument.string("email").pipe(
|
|
Argument.withDescription("Email address (optional)"),
|
|
Argument.optional
|
|
),
|
|
// Required option
|
|
role: Flag.string("role").pipe(
|
|
Flag.withDescription("User role (admin, user, guest)")
|
|
),
|
|
// Boolean with explicit value support
|
|
notify: Flag.boolean("notify").pipe(
|
|
Flag.withAlias("n"),
|
|
Flag.withDescription("Send notification email")
|
|
)
|
|
}, (config) =>
|
|
logAction("users create", {
|
|
username: config.username,
|
|
email: config.email,
|
|
role: config.role,
|
|
notify: config.notify
|
|
})).pipe(
|
|
Command.withDescription("Create a new user account")
|
|
)
|
|
|
|
const users = Command.make("users").pipe(
|
|
Command.withDescription("User management commands"),
|
|
Command.withSubcommands([usersList, usersCreate])
|
|
)
|
|
|
|
const configSet = Command.make("set", {
|
|
// Variadic positional arguments
|
|
pairs: Argument.string("key=value").pipe(
|
|
Argument.withDescription("Configuration key-value pairs"),
|
|
Argument.variadic({ min: 1 })
|
|
),
|
|
// File path option
|
|
file: Flag.file("config-file").pipe(
|
|
Flag.withAlias("f"),
|
|
Flag.withDescription("Write to specific config file"),
|
|
Flag.optional
|
|
)
|
|
}, (config) =>
|
|
logAction("config set", {
|
|
pairs: config.pairs,
|
|
file: config.file
|
|
})).pipe(
|
|
Command.withDescription("Set configuration values")
|
|
)
|
|
|
|
const configGet = Command.make("get", {
|
|
// Single required positional
|
|
key: Argument.string("key").pipe(
|
|
Argument.withDescription("Configuration key to retrieve")
|
|
),
|
|
// Options with different types
|
|
source: Flag.string("source").pipe(
|
|
Flag.withDescription("Configuration source (local, global, system)"),
|
|
Flag.withDefault("local")
|
|
)
|
|
}, (config) =>
|
|
logAction("config get", {
|
|
key: config.key,
|
|
source: config.source
|
|
})).pipe(
|
|
Command.withDescription("Get configuration value")
|
|
)
|
|
|
|
const config = Command.make("config").pipe(
|
|
Command.withSharedFlags({
|
|
// Parent command options shared with config subcommands
|
|
profile: Flag.string("profile").pipe(
|
|
Flag.withAlias("p"),
|
|
Flag.withDescription("Configuration profile to use"),
|
|
Flag.optional
|
|
)
|
|
}),
|
|
Command.withDescription("Manage application configuration"),
|
|
Command.withSubcommands([configSet, configGet])
|
|
)
|
|
|
|
const admin = Command.make("admin").pipe(
|
|
Command.withSharedFlags({
|
|
// Boolean that can be set to false explicitly
|
|
sudo: Flag.boolean("sudo").pipe(
|
|
Flag.withDescription("Run with elevated privileges")
|
|
)
|
|
}),
|
|
Command.withDescription("Administrative commands"),
|
|
Command.withSubcommands([users, config])
|
|
)
|
|
|
|
// File operations commands
|
|
const copy = Command.make("copy", {
|
|
// Multiple required positional arguments (do not require actual filesystem presence in tests)
|
|
source: Argument.file("source", { mustExist: false }).pipe(
|
|
Argument.withDescription("Source file or directory")
|
|
),
|
|
destination: Argument.file("destination", { mustExist: false }).pipe(
|
|
Argument.withDescription("Destination path")
|
|
),
|
|
// Boolean flags with short aliases
|
|
recursive: Flag.boolean("recursive").pipe(
|
|
Flag.withAlias("r"),
|
|
Flag.withDescription("Copy directories recursively")
|
|
),
|
|
force: Flag.boolean("force").pipe(
|
|
Flag.withAlias("f"),
|
|
Flag.withDescription("Overwrite existing files")
|
|
),
|
|
// Integer option
|
|
buffer: Flag.integer("buffer-size").pipe(
|
|
Flag.withDescription("Buffer size in KB"),
|
|
Flag.withDefault(64)
|
|
)
|
|
}, (config) =>
|
|
logAction("copy", {
|
|
source: config.source,
|
|
destination: config.destination,
|
|
recursive: config.recursive,
|
|
force: config.force,
|
|
bufferSize: config.buffer
|
|
})).pipe(
|
|
Command.withDescription("Copy files or directories")
|
|
)
|
|
|
|
const move = Command.make("move", {
|
|
// Variadic sources with at least 2 items
|
|
paths: Argument.string("paths").pipe(
|
|
Argument.withDescription("Source path(s) and destination"),
|
|
Argument.variadic({ min: 2 })
|
|
),
|
|
// Options
|
|
interactive: Flag.boolean("interactive").pipe(
|
|
Flag.withAlias("i"),
|
|
Flag.withDescription("Prompt before overwrite")
|
|
)
|
|
}, (config) =>
|
|
logAction("move", {
|
|
paths: config.paths,
|
|
interactive: config.interactive
|
|
})).pipe(
|
|
Command.withDescription("Move or rename files")
|
|
)
|
|
|
|
const remove = Command.make("remove", {
|
|
// Variadic with no upper limit
|
|
files: Argument.string("files").pipe(
|
|
Argument.withDescription("Files to remove"),
|
|
Argument.variadic({ min: 1 })
|
|
),
|
|
// Multiple boolean options
|
|
recursive: Flag.boolean("recursive").pipe(
|
|
Flag.withAlias("r"),
|
|
Flag.withDescription("Remove directories and contents")
|
|
),
|
|
force: Flag.boolean("force").pipe(
|
|
Flag.withAlias("f"),
|
|
Flag.withDescription("Force removal without prompts")
|
|
),
|
|
verbose: Flag.boolean("verbose").pipe(
|
|
Flag.withAlias("v"),
|
|
Flag.withDescription("Explain what is being done")
|
|
)
|
|
}, (config) =>
|
|
logAction("remove", {
|
|
files: config.files,
|
|
recursive: config.recursive,
|
|
force: config.force,
|
|
verbose: config.verbose
|
|
})).pipe(
|
|
Command.withDescription("Remove files or directories")
|
|
)
|
|
|
|
// Build command for testing option aliases
|
|
const build = Command.make("build", {
|
|
output: Flag.string("output").pipe(
|
|
Flag.withAlias("o"),
|
|
Flag.withDescription("Output directory")
|
|
),
|
|
verbose: Flag.boolean("verbose").pipe(
|
|
Flag.withAlias("v"),
|
|
Flag.withDescription("Enable verbose output")
|
|
),
|
|
configFile: Flag.string("config-file").pipe(
|
|
Flag.withAlias("f"),
|
|
Flag.withDescription("Configuration file path"),
|
|
Flag.optional
|
|
)
|
|
}, (config) =>
|
|
logAction("build", {
|
|
output: config.output,
|
|
verbose: config.verbose,
|
|
config: Option.getOrElse(config.configFile, () => "none")
|
|
})).pipe(
|
|
Command.withDescription("Build the project")
|
|
)
|
|
|
|
// Git-style commands for testing subcommands and context sharing
|
|
const gitClone = Command.make("clone", {
|
|
repository: Argument.string("repository").pipe(
|
|
Argument.withDescription("Repository URL or path")
|
|
),
|
|
branch: Flag.string("branch").pipe(
|
|
Flag.withDefault("main"),
|
|
Flag.withDescription("Branch to clone")
|
|
)
|
|
}, (config) =>
|
|
logAction("git clone", {
|
|
repository: config.repository,
|
|
branch: config.branch
|
|
})).pipe(
|
|
Command.withDescription("Clone a repository")
|
|
)
|
|
|
|
const gitAdd = Command.make("add", {
|
|
files: Argument.string("files").pipe(
|
|
Argument.withDescription("Files to add")
|
|
),
|
|
update: Flag.boolean("update").pipe(
|
|
Flag.withDescription("Update tracked files")
|
|
)
|
|
}, (config) =>
|
|
logAction("git add", {
|
|
files: config.files,
|
|
update: config.update
|
|
})).pipe(
|
|
Command.withDescription("Add files to staging")
|
|
)
|
|
|
|
const gitStatus = Command.make("status", {
|
|
short: Flag.boolean("short").pipe(
|
|
Flag.withDescription("Show short format")
|
|
)
|
|
}, (config) =>
|
|
logAction("git status", {
|
|
short: config.short
|
|
})).pipe(
|
|
Command.withDescription("Show repository status")
|
|
)
|
|
|
|
const git = Command.make("git").pipe(
|
|
Command.withSharedFlags({
|
|
verbose: Flag.boolean("verbose").pipe(
|
|
Flag.withDescription("Enable verbose output")
|
|
)
|
|
}),
|
|
Command.withHandler((config) =>
|
|
logAction("git", {
|
|
verbose: config.verbose
|
|
})
|
|
),
|
|
Command.withDescription("Git version control"),
|
|
Command.withSubcommands([gitClone, gitAdd, gitStatus])
|
|
)
|
|
|
|
// Commands for testing error handling
|
|
const testRequired = Command.make("test-required", {
|
|
required: Flag.string("required").pipe(
|
|
Flag.withDescription("A required option for testing")
|
|
)
|
|
}, (config) =>
|
|
logAction("test-required", {
|
|
required: config.required
|
|
})).pipe(
|
|
Command.withDescription("Test command with required option")
|
|
)
|
|
|
|
const testFailing: Command.Command<
|
|
"test-failing",
|
|
{ readonly input: string },
|
|
{},
|
|
string,
|
|
TestActions
|
|
> = Command.make("test-failing", {
|
|
input: Flag.string("input").pipe(
|
|
Flag.withDescription("Input that will cause handler to fail")
|
|
)
|
|
}, (config) =>
|
|
Effect.gen(function*() {
|
|
yield* logAction("test-failing", { input: config.input })
|
|
return yield* Effect.fail("Handler error")
|
|
})).pipe(
|
|
Command.withDescription("Test command that always fails")
|
|
)
|
|
|
|
// Deploy command for testing complex nested structures
|
|
const deployCommand = Command.make("deploy", {
|
|
service: Argument.string("service").pipe(
|
|
Argument.withDescription("Service to deploy")
|
|
),
|
|
environment: Argument.string("environment").pipe(
|
|
Argument.withDescription("Target environment")
|
|
),
|
|
database: {
|
|
host: Flag.string("db-host").pipe(
|
|
Flag.withDescription("Database host")
|
|
),
|
|
port: Flag.integer("db-port").pipe(
|
|
Flag.withDescription("Database port")
|
|
)
|
|
},
|
|
dryRun: Flag.boolean("dry-run").pipe(
|
|
Flag.withDescription("Perform a dry run")
|
|
)
|
|
}, (config) =>
|
|
logAction("deploy", {
|
|
service: config.service,
|
|
environment: config.environment,
|
|
database: config.database,
|
|
dryRun: config.dryRun
|
|
})).pipe(
|
|
Command.withDescription("Deploy a service")
|
|
)
|
|
|
|
const app = Command.make("app").pipe(
|
|
Command.withSharedFlags({
|
|
env: Flag.string("env").pipe(
|
|
Flag.withDescription("Environment setting"),
|
|
Flag.optional
|
|
)
|
|
}),
|
|
Command.withHandler((config) =>
|
|
logAction("app", {
|
|
env: config.env
|
|
})
|
|
),
|
|
Command.withDescription("Application management"),
|
|
Command.withSubcommands([deployCommand])
|
|
)
|
|
|
|
// Service command for nested context sharing tests
|
|
const serviceCommand = Command.make("service").pipe(
|
|
Command.withSharedFlags({
|
|
name: Flag.string("name").pipe(
|
|
Flag.withDescription("Service name")
|
|
)
|
|
}),
|
|
Command.withHandler((config) =>
|
|
logAction("service", {
|
|
name: config.name
|
|
})
|
|
),
|
|
Command.withDescription("Service management"),
|
|
Command.withSubcommands([deployCommand])
|
|
)
|
|
|
|
const appWithService = Command.make("app-nested").pipe(
|
|
Command.withSharedFlags({
|
|
env: Flag.string("env").pipe(
|
|
Flag.withDescription("Environment setting")
|
|
)
|
|
}),
|
|
Command.withHandler((config) =>
|
|
logAction("app-nested", {
|
|
env: config.env
|
|
})
|
|
),
|
|
Command.withDescription("Application with nested services"),
|
|
Command.withSubcommands([serviceCommand])
|
|
)
|
|
|
|
// Main command with global options
|
|
// Note: No handler on root command - running with no args should show help
|
|
export const ComprehensiveCli = Command.make("mycli").pipe(
|
|
Command.withSharedFlags({
|
|
// Global options available to all subcommands
|
|
debug: Flag.boolean("debug").pipe(
|
|
Flag.withAlias("d"),
|
|
Flag.withDescription("Enable debug logging")
|
|
),
|
|
config: Flag.file("config").pipe(
|
|
Flag.withAlias("c"),
|
|
Flag.withDescription("Path to configuration file"),
|
|
Flag.optional
|
|
),
|
|
quiet: Flag.boolean("quiet").pipe(
|
|
Flag.withAlias("q"),
|
|
Flag.withDescription("Suppress non-error output")
|
|
)
|
|
}),
|
|
Command.withDescription("A comprehensive CLI tool demonstrating all features"),
|
|
Command.withSubcommands([admin, copy, move, remove, build, git, testRequired, testFailing, app, appWithService])
|
|
)
|
|
|
|
export const run = Command.runWith(ComprehensiveCli, {
|
|
version: "1.0.0"
|
|
})
|