Migrating from the previous executable API
This page lists what changes when moving from the old ExecutableApp / Parser API to the current
annotation-driven CliApp model. The rewrite ships in the same Maven artifacts (oxygen-executable,
oxygen-cli) — bump your version and expect breaking changes.
When the new API is a good fit
- New apps with subcommands declared as methods
- Apps that configure via env vars (
@envConfig) rather than-f/-ebootstrap flags - Teams that want annotation-driven CLI with less boilerplate
When you will feel pain
- Apps using pre-
--bootstrap config (-f,-j,-e,-r) - Apps with JSON-driven logger config in config files
- Apps with heavy
withCLIParserusing v1-only combinators - Shell scripts that put global flags before the subcommand name
API mapping
Entry point
=== "v1"
```scala
import oxygen.predef.executable.*
object WebServerMain extends ExecutableApp {
override val executable: Executable =
Executable
.withJsonConfig[Config]
.withEnv[Env] { (config, _) => Env.layer(config) }
.withExecute { ... }
}
```
=== "Current"
```scala
import oxygen.cli.*
import oxygen.executable.*
import zio.*
final case class WebServerMain() extends CliApp[Any, WebServerMain.Env] {
def env(@envConfig("APP_CONFIG") config: WebServerMain.Config): EnvLayer =
WebServerMain.Env.layer(config)
@execute
def run(): Effect = ...
}
object WebServerMain extends CliApp.Executable[WebServerMain](CliApp.derive)
```
Subcommands
=== "v1"
```scala
Executable.oneOf(
"client" -> clientExecutable,
"server" -> serverExecutable,
)
```
=== "Current"
```scala
final case class MyApp() extends CliApp[Any, Any] {
@command def client(...): Effect = ...
@command def server(...): Effect = ...
}
```
Each v1 branch could have its own withCLIParser and env typing. In the current API, commands share
one class, one optional def env, and per-method parameters.
Blockers
Features that do not exist in the current API today. You need a redesign or a library extension before migrating.
1. Bootstrap config sources (-f / -j / -e / -r before --)
v1 parses flags before --, loads JSON from multiple sources, merges them, and passes one Json
to withJsonConfig:
| v1 flag | Source |
|---|---|
-f / --file |
filesystem JSON |
-j / --jar |
classpath/JAR resource |
-e / --env |
env var (optional a.b.c:VAR nesting) |
-r / --raw |
inline JSON (optional nesting) |
The current API has no equivalent bootstrap phase. Config is per-parameter @envConfig("ENV_VAR")
only (ConfigLoader: raw JSON string, file path, or directory of .json files).
Impact: Apps that relied on -f / merged JSON → withJsonConfig must either:
- export merged JSON into one env var / file and use
@envConfig("APP_CONFIG"), or - add bootstrap support to the library (not implemented yet)
The in-repo WebServerMain has been migrated to @envConfig("APP_CONFIG").
2. Imperative Executable builder
withJsonConfig, withCLIParser, withLoggerFromJson, withEnv, withExecute, limitError,
Executable.oneOf — all replaced by the macro + CliApp model. Every v1 app rewrites its shape.
3. v1 Parser / Params expressiveness
Old Parser / Params features without a direct equivalent:
Params.FirstOfByArgIndex(e.g. pick logger backend by flag position)Params.ifPresent(optional alternate parsers)- Param aliases on
Params.value
Workaround: @custom + given ArgsParser[T], or restructure CLI.
4. JSON-driven logging
v1: withLoggerFromJson, additionalLoggerDecoders, logger section in app JSON.
Current API: OXYGEN_LOGGER_TYPE, OXYGEN_LOG_LEVEL, and defaultLoggerType only.
Impact: Apps that configure log targets/levels per environment in config files need a different
approach (env vars, or programmatic LogConfig inside the app after startup).
Friction (workarounds exist)
Logger CLI flags → environment variables
v1 had --log-level, --oxygen-logger, --json-logger, --zio-logger, --keep-zio-logger, etc.
The current API intentionally dropped these. Use:
Or override defaultLoggerType in your CliApp.Executable object.
Subcommand vs root-flag order
Update scripts and documentation.
@envConfig vs v1 multi-source merge
Current API: one env var per @envConfig, no a.b.c: nesting prefix, no JAR resources, no merging
multiple independent bootstrap sources.
Workaround: CI/k8s composes one JSON blob or file path into a single env var.
Error handling
v1: typed ExecuteError (parse, help, config source errors).
Current API: parse failures print help; other failures use cause.prettyPrint unless you use
ExecutableError.ExitWith.exit(code) for intentional exits.
Tab completion
Regenerate completion scripts from a current build (--: generate). Protocol is the same; parser
walking is different. Bootstrap-flag completion (-f/-e before subcommand) is not replicated.
Intentionally deferred (replaced by design)
| Previous | Current |
|---|---|
| Logger CLI pollution | Env-based OXYGEN_* logger config |
ExecutableContext |
Fixed bootstrap logging in CliAppLogging.install |
additionalLoggerParsers |
Dropped |
Manual Parsing.parse |
Macro-generated parsers |
--keep-zio-logger |
DefaultLoggerType.Zio or OXYGEN_LOGGER_TYPE=ZIO |
Parity (already covered)
ZIOAppDefaultentry (CliApp.Executable)--:completion (generate,complete, bash script)--help/--help-extrawith subcommand drill-downdef env/ ZLayer wiring- Subcommands and nested
CliApptypes @flag,@toggle,@named,@positional, optional/repeated/list/tuple paramsStrictEnum+ tab completion@customparsersExitCode/ExitWithfor clean exits- Config from env (subset of v1
-ebehavior)
Migration checklist
- Bump dependencies — same artifacts (
oxygen-executable,oxygen-cli), new major/minor version. - Rewrite app class — extend
CliApp, add@executeor@commandmethods,object extends CliApp.Executable. - Replace
withJsonConfig—@envConfig("ENV")onenvor command params; ensure deploy sets env/file. - Replace
withEnv—def env(...): EnvLayer. - Replace
withCLIParser— annotations, or@customparsers. - Update invocations — subcommand first; drop bootstrap flags or map them to env.
- Replace logger CLI —
OXYGEN_LOGGER_TYPE/OXYGEN_LOG_LEVEL. - Regenerate bash completion — see Tab completion.
- Drop
oxygen.predef.executable— importoxygen.executable.*andoxygen.cli.*.
Example: WebServerMain
The repo's web server has been migrated. See
example/apps/web-server/src/main/scala/oxygen/example/webServer/WebServerMain.scala.
Deploy with:
Or for local dev from the repo:
Until bootstrap -f/-j/merge returns, this env-or-file pattern is the supported migration path.