mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-07 03:00:29 +00:00
refactor: align cli command boundaries on CommandContext
This commit is contained in:
parent
ec18c1864b
commit
fbe0bdfbc1
57 changed files with 1445 additions and 746 deletions
|
|
@ -0,0 +1,835 @@
|
|||
---
|
||||
title: "refactor: CommandContext alignment at CLI command boundaries"
|
||||
type: refactor
|
||||
status: completed
|
||||
date: 2026-04-23
|
||||
deepened: 2026-04-23
|
||||
---
|
||||
|
||||
# CommandContext Alignment At CLI Command Boundaries
|
||||
|
||||
## Overview
|
||||
|
||||
Finish the partially landed `CommandContext` refactor in `fabro-cli` by
|
||||
making `CommandContext` the shared command-boundary abstraction for
|
||||
invocation plumbing in the CLI command families that already depend on
|
||||
it or immediately reconstruct it. The goal is to stop threading
|
||||
`&CliNamespace`, `&CliLayer`, `Printer`, and `process_local_json`
|
||||
through command entrypoints when that data is already part of the same
|
||||
invocation context.
|
||||
|
||||
This is a follow-on plan to the earlier server-access refactor. The
|
||||
current codebase already centralizes `cwd`, merged settings, and server
|
||||
access in `CommandContext`, but command entrypoints still receive raw
|
||||
plumbing and then rebuild a context locally. This plan aligns the
|
||||
surface area around the existing struct instead of introducing a second
|
||||
context or service wrapper.
|
||||
|
||||
## Problem Frame
|
||||
|
||||
`CommandContext` exists today in
|
||||
`lib/crates/fabro-cli/src/command_context.rs`, and many commands
|
||||
already depend on it for `cwd`, `machine_settings`, `user_settings`, and
|
||||
`server()` access. The refactor is only half-finished:
|
||||
|
||||
- the `printer` field and `printer()` accessor are both marked
|
||||
`#[allow(dead_code, reason = "...still being wired through")]`
|
||||
- `main.rs` still passes raw plumbing into most command families
|
||||
- many command entrypoints still accept some combination of
|
||||
`&CliNamespace`, `&CliLayer`, `Printer`, and `process_local_json`
|
||||
- those same commands often construct `CommandContext` immediately
|
||||
inside the handler
|
||||
|
||||
That leaves the CLI with two overlapping models:
|
||||
|
||||
- `CommandContext` as the intended abstraction for shared invocation
|
||||
state
|
||||
- raw parameter threading as the de facto command API
|
||||
|
||||
The result is more churn on command signatures, more repeated output
|
||||
format and JSON-gating branches, and a dead-code-marked `printer` field
|
||||
that signals the abstraction boundary is unfinished.
|
||||
|
||||
The refactor needs to finish in a way that preserves current CLI
|
||||
behavior and avoids turning `CommandContext` into a new god object.
|
||||
|
||||
## Requirements Trace
|
||||
|
||||
- **R1.** In-scope CLI command entrypoints use `CommandContext` as the
|
||||
shared invocation-plumbing abstraction instead of separately receiving
|
||||
`&CliNamespace`, `&CliLayer`, `Printer`, and `process_local_json`.
|
||||
- **R2.** `CommandContext` remains narrow: it carries invocation-scoped
|
||||
plumbing and shared derived state, not command-specific args, styles,
|
||||
or workflow-specific data.
|
||||
- **R3.** JSON output, verbosity behavior, printer routing, and global
|
||||
`--json` restrictions remain behaviorally unchanged for existing
|
||||
commands, including `auth status` remaining explicit-global-`--json`
|
||||
only rather than switching to persisted `cli.output.format = "json"`.
|
||||
- **R4.** Target-based and storage-dir-based server resolution semantics
|
||||
remain unchanged, including `CommandContext::server()` behavior and
|
||||
`ServerSummaryLookup::from_client(...)` usage.
|
||||
- **R5.** `main.rs` keeps its current pre-tracing bootstrap ordering for
|
||||
logging and upgrade checks; `CommandContext` begins after that phase.
|
||||
- **R6.** The current dead-code allowance on the `printer` field/accessor
|
||||
is removed by making printer access a real part of the abstraction, or
|
||||
by deleting redundant surface area if a narrower accessor is better.
|
||||
- **R7.** Representative unit and integration coverage exists for the
|
||||
context API, JSON/text output behavior, global `--json` gating, and
|
||||
target/connection-based command families touched by the refactor.
|
||||
|
||||
## Scope Boundaries
|
||||
|
||||
- **In scope:** command families in `fabro-cli` that already use
|
||||
`CommandContext` directly or immediately construct one from raw
|
||||
plumbing:
|
||||
`run`, `runs`, `artifact`, `store dump`, `preflight`, `validate`,
|
||||
`graph`, `model`, `secret`, `pr`, `repo`, `auth`, `provider`,
|
||||
`config`, `version`, `doctor`, `system`, and the public
|
||||
`sandbox preview` / `sandbox ssh` command boundary that currently
|
||||
forwards into `commands/run/*` leaf modules.
|
||||
- **In scope:** top-level dispatch cleanup in
|
||||
`lib/crates/fabro-cli/src/main.rs` and family dispatch modules where
|
||||
the only reason raw CLI plumbing is still passed down is command
|
||||
signature inertia.
|
||||
- **Out of scope:** `exec`, `server`, `install`, `upgrade`, `workflow`,
|
||||
`parse`, `render_graph`, hidden analytics/panic upload commands,
|
||||
hidden `run worker`, and `sandbox cp`, except for thin compile-only
|
||||
adapters if needed.
|
||||
- **Out of scope:** changing CLI text, response payloads, exit-code
|
||||
semantics, or server connection behavior beyond what is required to
|
||||
move the plumbing boundary.
|
||||
- **Out of scope:** moving `Styles` ownership into `CommandContext` or
|
||||
making low-level pure rendering helpers context-aware when explicit
|
||||
`Printer` or `Styles` parameters remain clearer.
|
||||
- **Out of scope:** redesigning `server_client.rs` connection semantics.
|
||||
This plan consumes the current `CommandContext::server()` model rather
|
||||
than reopening the earlier server-access design.
|
||||
|
||||
## Context & Research
|
||||
|
||||
### Relevant Code and Patterns
|
||||
|
||||
- `lib/crates/fabro-cli/src/command_context.rs` — current
|
||||
`CommandContext`, constructors, `user_settings()` / `machine_settings()`
|
||||
accessors, and dead-code-marked printer storage.
|
||||
- `lib/crates/fabro-cli/src/main.rs` — top-level command dispatch still
|
||||
passing raw plumbing into most families after bootstrap.
|
||||
- `lib/crates/fabro-cli/src/server_client.rs` — current target and
|
||||
storage-dir connection behavior that must stay unchanged.
|
||||
- `lib/crates/fabro-cli/src/commands/run/create.rs` — existing example
|
||||
of a helper that already takes `&CommandContext`.
|
||||
- `lib/crates/fabro-cli/src/commands/run/mod.rs` — current mixed model:
|
||||
some subcommands build a `CommandContext`, some still read raw
|
||||
`cli.output.format`, and `process_local_json` is already vestigial at
|
||||
this boundary.
|
||||
- `lib/crates/fabro-cli/src/commands/secret/mod.rs` — good family-level
|
||||
pattern where a single derived server/client can be shared across
|
||||
subcommands.
|
||||
- `lib/crates/fabro-cli/src/commands/pr/mod.rs` — representative mixed
|
||||
local/server family using both `CommandContext::base(...)` and
|
||||
`CommandContext::for_target(...)`.
|
||||
- `lib/crates/fabro-cli/src/commands/auth/mod.rs` and
|
||||
`lib/crates/fabro-cli/src/commands/provider/mod.rs` — the clearest
|
||||
examples of raw `process_local_json` still being threaded despite the
|
||||
rest of the state already belonging to the invocation.
|
||||
- `files-internal/testing-strategy.md` — CLI integration tests should
|
||||
stay command-driven and black-box, with implementation-facing behavior
|
||||
covered by unit tests near the code.
|
||||
- `lib/crates/fabro-cli/src/commands/sandbox/mod.rs` — the real public
|
||||
boundary for preview/ssh command dispatch; leaf implementation lives in
|
||||
`commands/run/preview.rs` and `commands/run/ssh.rs`, but the command
|
||||
family boundary is `sandbox`.
|
||||
- `lib/crates/fabro-cli/tests/it/cmd/sandbox_preview.rs` and
|
||||
`lib/crates/fabro-cli/tests/it/cmd/sandbox_ssh.rs` — existing
|
||||
command-owned coverage for those public entrypoints.
|
||||
|
||||
### Current-State Inventory
|
||||
|
||||
- `CommandContext::{base, for_target, for_connection}` currently has
|
||||
**42** call sites across **37** command files under
|
||||
`lib/crates/fabro-cli/src/commands`.
|
||||
- The command tree contains **one** existing helper that already accepts
|
||||
`&CommandContext` directly:
|
||||
`lib/crates/fabro-cli/src/commands/run/create.rs`.
|
||||
- The `printer()` accessor currently has no call sites, which is why the
|
||||
dead-code allowance still exists.
|
||||
- For in-scope command files, the dominant remaining use of raw
|
||||
`&CliNamespace` is reading `cli.output.format` or
|
||||
`cli.output.verbosity`; that is a signal that the data belongs on the
|
||||
shared invocation context rather than each command signature.
|
||||
- `process_local_json` is now concentrated in `auth`, `provider`,
|
||||
`graph`, `sandbox preview`, and `sandbox ssh`. That makes it a good
|
||||
candidate for a dedicated invocation-context field/helper instead of
|
||||
continued parameter threading.
|
||||
|
||||
### Related Context
|
||||
|
||||
- `docs/plans/2026-04-08-cli-services-command-context-refactor-plan.md`
|
||||
— earlier plan that introduced the current `CommandContext` and server
|
||||
access model. This plan finishes the command-boundary alignment that
|
||||
document did not fully land.
|
||||
- `docs/plans/2026-04-22-001-refactor-settings-api-entrypoints-plan.md`
|
||||
— recent owner-first context plan that reinforces the repo preference
|
||||
for dense, owner-scoped context objects instead of repeated free-form
|
||||
plumbing.
|
||||
- `git log -- lib/crates/fabro-cli/src/command_context.rs` shows recent
|
||||
follow-on commits including `simplify: drop duplicate settings plumbing
|
||||
from cli/server refactor`, which is consistent with the current goal of
|
||||
collapsing overlapping command-boundary APIs.
|
||||
|
||||
### Institutional Learnings
|
||||
|
||||
- No relevant `docs/solutions/` entries currently cover this seam.
|
||||
|
||||
### External References
|
||||
|
||||
- No external research used. The repo already has sufficient local
|
||||
context, existing partial implementation, and tests for this refactor.
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
- **Use `CommandContext` as the command-boundary API for in-scope
|
||||
commands.**
|
||||
The abstraction already owns the hard parts: working directory,
|
||||
merged settings, and server access. The remaining refactor should move
|
||||
entrypoint APIs onto that abstraction instead of continuing to thread
|
||||
raw plumbing beside it.
|
||||
|
||||
- **Keep `CommandContext` narrow and invocation-scoped, not god-shaped.**
|
||||
It should own:
|
||||
`printer`, merged CLI-derived settings (`machine_settings`,
|
||||
`user_settings`), `cwd`, config-path context, server-derivation state,
|
||||
and the invocation-only global `--json` flag.
|
||||
It should not own command args, `Styles`, render-only helpers, or
|
||||
workflow/build-specific state.
|
||||
|
||||
- **Do not store or expose the full `CliNamespace` publicly.**
|
||||
Commands in scope only need a small subset of CLI plumbing:
|
||||
output format, output verbosity, and global `--json` restrictions.
|
||||
Output format and verbosity should come from
|
||||
`ctx.user_settings().cli.output`, which already reflects CLI override
|
||||
precedence through merged settings. The only truly extra invocation
|
||||
field is the global `--json` switch, which is not part of persisted
|
||||
settings and therefore belongs on the context explicitly.
|
||||
|
||||
- **Preserve the current error-timing split.**
|
||||
`CommandContext` construction should keep doing what it does today:
|
||||
capture cwd, load local settings, and build merged invocation state.
|
||||
Server-target resolution and server-connection failures should remain
|
||||
deferred to `ctx.server().await?` or existing explicit
|
||||
`resolve_server_target(...)` calls. The refactor should not make
|
||||
malformed target/server resolution errors eager at context-construction
|
||||
time.
|
||||
|
||||
- **Keep `auth status` as an explicit-global-JSON command.**
|
||||
Most in-scope commands should read output mode from
|
||||
`ctx.user_settings().cli.output`, but `auth status` is a special case:
|
||||
it currently emits JSON only for the explicit invocation-wide global
|
||||
`--json` path, not merely because resolved CLI settings say
|
||||
`output.format = json`. That distinction should remain intact, so the
|
||||
context needs both resolved output settings and a separate helper for
|
||||
the explicit global JSON flag.
|
||||
|
||||
- **Create a base invocation context once, then derive target/connection
|
||||
variants from it.**
|
||||
`main.rs` should keep using raw settings during pre-tracing bootstrap.
|
||||
After that, it should build a base `CommandContext` once for each
|
||||
in-scope dispatch path. Command families then derive
|
||||
target-based or connection-based contexts from that base without
|
||||
re-supplying `Printer` and `CliLayer`.
|
||||
|
||||
- **Allow private derivation state inside `CommandContext` if that is the
|
||||
simplest way to avoid raw parameter threading.**
|
||||
Storing a private `CliLayer` or equivalent internal builder state is
|
||||
acceptable if it enables methods like `with_target(...)` or
|
||||
`with_connection(...)` and keeps the raw plumbing hidden behind the
|
||||
abstraction boundary.
|
||||
|
||||
- **Keep render helpers explicit.**
|
||||
Command entrypoints and family dispatchers should align on
|
||||
`CommandContext`, but low-level helpers such as table rendering,
|
||||
summary formatting, and browser-opening routines may keep explicit
|
||||
`Printer` / `Styles` / boolean parameters where that stays simpler than
|
||||
threading the full context downward.
|
||||
|
||||
- **Keep workflow/manifest layer assembly command-local.**
|
||||
Commands such as `run`, `preflight`, `validate`, and `graph` should
|
||||
continue to build their workflow/project/manifests with the existing
|
||||
command-owned helpers. `CommandContext` can provide `cwd`, merged user
|
||||
settings for output behavior, and server access, but it should not
|
||||
absorb manifest-building policy or workflow-layer composition.
|
||||
|
||||
- **Migrate family-by-family with temporary compatibility wrappers if
|
||||
needed.**
|
||||
This is a cross-cutting refactor with wide signature churn. It is
|
||||
better to allow short-lived constructor/adapter overlap during the
|
||||
migration than to force a single giant all-or-nothing patch that is
|
||||
harder to validate.
|
||||
|
||||
## Open Questions
|
||||
|
||||
### Resolved During Planning
|
||||
|
||||
- **Should this refactor introduce a second wrapper type such as
|
||||
`Services` or `InvocationContext`?**
|
||||
No. The repo already has a partially landed `CommandContext`, and the
|
||||
simplest aligned design is to finish that abstraction rather than
|
||||
splitting responsibilities across two overlapping context types.
|
||||
|
||||
- **Should `CommandContext` absorb the entire `CliNamespace`?**
|
||||
No. Public command consumers should read output mode and verbosity from
|
||||
`ctx.user_settings().cli.output`, while command-specific configuration
|
||||
stays local to the commands that own it.
|
||||
|
||||
- **Should `process_local_json` become part of `CommandContext`?**
|
||||
Yes. It is invocation-scoped, currently leaks across multiple command
|
||||
signatures, and is the one remaining piece of global command plumbing
|
||||
that is not already represented by merged settings.
|
||||
|
||||
- **Should preview/ssh be treated as `run` work or `sandbox` work in
|
||||
this plan?**
|
||||
Treat them as `sandbox` work at the public command boundary. The leaf
|
||||
implementation modules remain under `commands/run/*`, but the raw
|
||||
plumbing boundary in the current CLI is `commands/sandbox/mod.rs` and
|
||||
the plan should align to that boundary and its tests.
|
||||
|
||||
- **Should `auth status` remain “explicit global JSON only”?**
|
||||
Yes. R3 for this plan is behavioral preservation, and the current
|
||||
contract is that `auth status` emits JSON only when the explicit
|
||||
invocation-global `--json` switch is active.
|
||||
|
||||
- **Should `Styles` move into `CommandContext` in the same pass?**
|
||||
No. That would enlarge the abstraction without addressing the actual
|
||||
duplicated plumbing problem.
|
||||
|
||||
- **Should out-of-scope local commands be forced onto `CommandContext`
|
||||
just for uniformity?**
|
||||
No. This pass should target the families where `CommandContext` already
|
||||
provides real value or is already partially adopted.
|
||||
|
||||
### Deferred to Implementation
|
||||
|
||||
- **Exact API names for derived contexts.**
|
||||
The implementation may settle on `with_target(...)`,
|
||||
`for_target_from(...)`, or similar naming. The important contract is
|
||||
that callers no longer pass raw `Printer` and `CliLayer` repeatedly.
|
||||
|
||||
- **Whether static constructors remain temporarily during migration.**
|
||||
If temporary wrappers reduce compile churn while family-by-family
|
||||
patches land, they are acceptable. Final cleanup should remove the
|
||||
now-redundant raw-plumbing entrypoints from in-scope call sites.
|
||||
|
||||
- **Whether `CommandContext` should be cheaply cloneable or should build
|
||||
derived variants from private state on demand.**
|
||||
Either is acceptable if it preserves the abstraction boundary and does
|
||||
not change runtime behavior.
|
||||
|
||||
## High-Level Technical Design
|
||||
|
||||
> *This illustrates the intended approach and is directional guidance for review, not implementation specification. The implementing agent should treat it as context, not code to reproduce.*
|
||||
|
||||
| Boundary | Current shape | Target shape |
|
||||
|---|---|---|
|
||||
| `main.rs` -> command family | `dispatch(args, &cli_settings, &cli_layer, process_local_json, printer)` | `dispatch(args, &base_ctx)` |
|
||||
| family dispatch -> leaf command | raw CLI plumbing plus `CommandContext::for_target(...)` inside the leaf | derive `target_ctx` / `connection_ctx` once from `base_ctx`, then pass `&CommandContext` or already-resolved client/output helpers |
|
||||
| output mode lookup | `cli.output.format` / `cli.output.verbosity` | `ctx.user_settings().cli.output.*` |
|
||||
| global `--json` guard | separate `process_local_json` parameter | `ctx` accessor/helper |
|
||||
| printing | raw `printer` parameter | `ctx.printer()` or a narrower printer extracted from `ctx` |
|
||||
|
||||
Directional flow:
|
||||
|
||||
```text
|
||||
bootstrap raw globals/settings for tracing + upgrade check
|
||||
-> build base CommandContext once for the in-scope command dispatch
|
||||
-> family dispatch receives &base_ctx
|
||||
-> family derives:
|
||||
target_ctx from target args
|
||||
connection_ctx from storage-dir-aware args
|
||||
base/local ctx for settings-only commands
|
||||
-> leaf command reads:
|
||||
ctx.cwd()
|
||||
ctx.machine_settings()
|
||||
ctx.user_settings().cli.output.*
|
||||
ctx.require_no_json_override() or equivalent
|
||||
ctx.printer()
|
||||
ctx.server().await?
|
||||
-> render-only helpers stay explicit over Printer / Styles where simpler
|
||||
```
|
||||
|
||||
## Implementation Units
|
||||
|
||||
- [x] **Unit 1: Reframe `CommandContext` as the invocation-boundary object**
|
||||
|
||||
**Goal:** Make `CommandContext` capable of carrying the invocation
|
||||
plumbing that is still leaking through command signatures, while keeping
|
||||
the type narrowly scoped.
|
||||
|
||||
**Requirements:** R1, R2, R3, R4, R6
|
||||
|
||||
**Dependencies:** None
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-cli/src/command_context.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/main.rs`
|
||||
- Test: `lib/crates/fabro-cli/src/command_context.rs`
|
||||
|
||||
**Approach:**
|
||||
- Add the remaining invocation-only plumbing that does not already exist
|
||||
on the context, specifically the global `--json` switch used today via
|
||||
`process_local_json`.
|
||||
- Make printer access part of the live API so the dead-code allowance on
|
||||
the `printer` field/accessor can be removed.
|
||||
- Add context-derivation methods that let callers obtain target-based or
|
||||
connection-based variants from a base invocation context without
|
||||
re-supplying raw `Printer` and `CliLayer`.
|
||||
- Keep output format and verbosity sourced from
|
||||
`ctx.user_settings().cli.output` rather than storing a second public
|
||||
output-format copy on the side, while still exposing the explicit
|
||||
invocation-global JSON flag separately for commands like `auth status`
|
||||
whose contract is not identical to resolved output format.
|
||||
|
||||
**Patterns to follow:**
|
||||
- `lib/crates/fabro-cli/src/command_context.rs`
|
||||
- `docs/plans/2026-04-22-001-refactor-settings-api-entrypoints-plan.md`
|
||||
for owner-first context boundaries
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: a base context exposes the same output format and verbosity
|
||||
that commands currently read from merged CLI settings.
|
||||
- Happy path: a base context preserves both resolved output settings and
|
||||
the explicit invocation-global JSON flag so commands can distinguish
|
||||
between “resolved output format is JSON” and “user passed global
|
||||
`--json`”.
|
||||
- Happy path: deriving a target-based context preserves printer/global
|
||||
JSON state and resolves server access through the existing
|
||||
`server_client::connect_server_with_settings(...)` path.
|
||||
- Edge case: deriving a connection-based context with a storage-dir
|
||||
override changes only the storage-backed settings path and preserves
|
||||
other invocation-scoped data.
|
||||
- Error path: settings-load failures tied to context construction still
|
||||
surface during context construction, while malformed target resolution
|
||||
remains deferred until `ctx.server().await?` or explicit
|
||||
`resolve_server_target(...)` calls.
|
||||
|
||||
**Verification:**
|
||||
- The `printer` field/accessor is no longer dead code.
|
||||
- There is one clear way to obtain a base context and derive
|
||||
target/connection variants without raw plumbing at the call site.
|
||||
- The context API makes the distinction between resolved output format
|
||||
and explicit global JSON invocation state unambiguous.
|
||||
|
||||
- [x] **Unit 2: Move `main.rs` and the run/preflight/graph/sandbox boundary to context-first dispatch**
|
||||
|
||||
**Goal:** Eliminate raw plumbing from the top-level dispatch path and the
|
||||
run-oriented command family plus the public `sandbox preview` /
|
||||
`sandbox ssh` boundary that already rely heavily on `CommandContext`.
|
||||
|
||||
**Requirements:** R1, R3, R4, R5, R6, R7
|
||||
|
||||
**Dependencies:** Unit 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-cli/src/main.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/command.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/create.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/preview.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/ssh.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/sandbox/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/resume.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/rewind.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/fork.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/wait.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/diff.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/logs.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/run/cp.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/preflight.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/validate.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/graph.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/run.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/create.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/start.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/attach.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/diff.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/logs.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/preflight.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/validate.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/graph.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/sandbox_preview.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/sandbox_ssh.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/json_global.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/scenario/lifecycle.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/scenario/recovery.rs`
|
||||
|
||||
**Approach:**
|
||||
- Build a base `CommandContext` in `main.rs` only after the existing
|
||||
bootstrap phase completes.
|
||||
- Change the run-family and run-adjacent entrypoints to accept context
|
||||
instead of raw `cli` / `cli_layer` / `printer` bundles.
|
||||
- Replace direct reads of `cli.output.format` and
|
||||
`cli.output.verbosity` with `ctx.user_settings().cli.output.*`.
|
||||
- Replace direct `process_local_json` threading with a context helper
|
||||
for the small number of commands that still need it (`graph`,
|
||||
`commands/sandbox/mod.rs` for the public preview/ssh boundary).
|
||||
- Move the public `sandbox preview` / `sandbox ssh` dispatch boundary to
|
||||
the same context-first shape as the run-family boundary, while leaving
|
||||
the leaf implementation modules in `commands/run/*` if that remains
|
||||
the cleanest internal organization.
|
||||
- Keep `Styles` local to the leaf commands and keep `attach` / `start`
|
||||
client helpers narrow if broadening them adds no value.
|
||||
|
||||
**Execution note:** Start by preserving or expanding characterization
|
||||
coverage for JSON/text output and global `--json` gating before removing
|
||||
the old raw-plumbing signatures from these entrypoints.
|
||||
|
||||
**Patterns to follow:**
|
||||
- `lib/crates/fabro-cli/src/commands/run/create.rs`
|
||||
- `lib/crates/fabro-cli/src/commands/preflight.rs`
|
||||
- `lib/crates/fabro-cli/src/commands/graph.rs`
|
||||
- `lib/crates/fabro-cli/src/commands/sandbox/mod.rs`
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: `fabro run --detach` still prints a bare run ID in text
|
||||
mode and the same JSON payload in JSON mode after output format moves
|
||||
behind `CommandContext`.
|
||||
- Happy path: `fabro run` / `resume` / `attach` still inherit verbose
|
||||
rendering from CLI output verbosity and preserve idle-sleep behavior.
|
||||
- Edge case: `fabro graph --json` without an explicit output file still
|
||||
rejects the global JSON override exactly as it does today.
|
||||
- Edge case: `fabro sandbox preview --open` still opens the browser only
|
||||
when global JSON mode is not active.
|
||||
- Edge case: `fabro sandbox ssh` still allows `--print` under global
|
||||
JSON mode but continues to reject the unsupported interactive
|
||||
combination.
|
||||
- Error path: `preflight` and `validate` continue to fail on validation
|
||||
errors with the same text-vs-JSON contract and exit behavior.
|
||||
- Integration: create -> start -> attach and resume/recovery flows still
|
||||
resolve targets, stream output, and summarize results through the same
|
||||
black-box CLI contracts.
|
||||
|
||||
**Verification:**
|
||||
- `main.rs` no longer passes the raw plumbing bundle into the run /
|
||||
preflight / validate / graph / sandbox preview-ssh boundary.
|
||||
- Those command boundaries obtain shared invocation data exclusively via
|
||||
`CommandContext`.
|
||||
|
||||
- [x] **Unit 3: Migrate runs/artifact/store families to context-first family dispatch**
|
||||
|
||||
**Goal:** Remove repeated target-context reconstruction from the command
|
||||
families that already build a target-based `CommandContext` immediately
|
||||
and mostly use raw CLI state only for output mode.
|
||||
|
||||
**Requirements:** R1, R3, R4, R6, R7
|
||||
|
||||
**Dependencies:** Units 1-2
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/runs/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/runs/list.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/runs/archive.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/runs/rm.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/runs/inspect.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/artifact/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/artifact/list.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/artifact/cp.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/store/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/store/dump.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/archive.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/inspect.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/rm.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/artifact_list.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/artifact_cp.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/store_dump.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/store.rs`
|
||||
|
||||
**Approach:**
|
||||
- Have each family receive a context-first boundary and derive its
|
||||
target-based command context once, close to the namespace/selector
|
||||
boundary.
|
||||
- Switch output-mode branches to
|
||||
`ctx.user_settings().cli.output.format`.
|
||||
- Preserve existing client-sharing patterns such as
|
||||
`ServerSummaryLookup::from_client(ctx.server().await?)`.
|
||||
- Keep leaf helpers narrow where they already only need a resolved
|
||||
client, resolved run ID, or printer.
|
||||
|
||||
**Patterns to follow:**
|
||||
- `lib/crates/fabro-cli/src/commands/secret/mod.rs`
|
||||
- `lib/crates/fabro-cli/src/commands/artifact/mod.rs`
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: `runs list`, `archive`, `unarchive`, and `rm` still render
|
||||
the same JSON and text shapes after output-mode decisions move behind
|
||||
the context.
|
||||
- Happy path: `runs inspect` remains a JSON-only projection of server
|
||||
state and still resolves the selected run before fetching the state.
|
||||
- Edge case: “no runs found” and “no artifacts found” text-mode messages
|
||||
remain unchanged.
|
||||
- Error path: ambiguous or missing run selectors still fail through the
|
||||
existing server/client resolution path.
|
||||
- Integration: artifact listing/copy and store dump continue to hit the
|
||||
same server-backed data path and respect output-format selection.
|
||||
|
||||
**Verification:**
|
||||
- These family dispatchers no longer need separate `cli_layer` and
|
||||
`printer` arguments merely to reconstruct a target context.
|
||||
|
||||
- [x] **Unit 4: Migrate PR, secret, and system command families**
|
||||
|
||||
**Goal:** Align the families that mix local settings, target-based
|
||||
server access, and storage-dir-aware server access onto the same
|
||||
context-first boundary.
|
||||
|
||||
**Requirements:** R1, R3, R4, R6, R7
|
||||
|
||||
**Dependencies:** Units 1-3
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/pr/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/pr/list.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/pr/create.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/pr/view.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/pr/merge.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/pr/close.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/secret/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/secret/list.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/secret/set.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/secret/rm.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/system/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/system/info.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/system/df.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/system/events.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/system/prune.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/pr.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/pr_list.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/pr_create.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/pr_view.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/pr_merge.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/pr_close.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/secret.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/secret_list.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/secret_set.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/secret_rm.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/system.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/system_info.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/system_df.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/system_events.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/system_prune.rs`
|
||||
|
||||
**Approach:**
|
||||
- Keep `pr`’s split between base-settings work and target-based run
|
||||
lookup, but move both sides onto a common context-first boundary so
|
||||
raw `cli_layer` / `printer` threading disappears from the family API.
|
||||
- Preserve `secret`’s existing pattern of resolving the server once at
|
||||
the family boundary and passing the client into subcommands.
|
||||
- For `system`, derive connection-aware contexts from the base
|
||||
invocation context so storage-dir override behavior remains explicit
|
||||
and unchanged.
|
||||
|
||||
**Patterns to follow:**
|
||||
- `lib/crates/fabro-cli/src/commands/pr/mod.rs`
|
||||
- `lib/crates/fabro-cli/src/commands/secret/mod.rs`
|
||||
- `lib/crates/fabro-cli/src/commands/system/mod.rs`
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: PR list/view/create/merge/close continue to render the same
|
||||
JSON/text contracts and still resolve GitHub credentials from merged
|
||||
local settings.
|
||||
- Happy path: secret list/set/rm continue to resolve one server client at
|
||||
the namespace boundary and honor JSON mode.
|
||||
- Happy path: system info/df/events/prune continue to use
|
||||
storage-dir-aware connection mode where appropriate.
|
||||
- Edge case: missing secret / no matching runs-to-prune / empty PR list
|
||||
still produce the same user-visible text-mode outcomes.
|
||||
- Error path: invalid storage-dir or connection resolution still fails
|
||||
before the command attempts remote work.
|
||||
- Integration: the `system` family continues to respect explicit
|
||||
`--storage-dir` overrides and local daemon resolution semantics.
|
||||
|
||||
**Verification:**
|
||||
- These family boundaries no longer accept raw plumbing bundles when the
|
||||
only reason was to build a `CommandContext` or inspect output mode.
|
||||
|
||||
- [x] **Unit 5: Finish remaining base/target context users**
|
||||
|
||||
**Goal:** Complete the context-first migration for the remaining in-scope
|
||||
command surfaces that already use `CommandContext` but still expose raw
|
||||
plumbing at their entrypoints.
|
||||
|
||||
**Requirements:** R1, R2, R3, R5, R6, R7
|
||||
|
||||
**Dependencies:** Units 1-4
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/model.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/repo/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/repo/init.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/repo/deinit.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/auth/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/auth/login.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/auth/logout.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/auth/status.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/provider/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/provider/login.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/config/mod.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/version.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/doctor.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/model.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/model_list.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/model_test.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/repo.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/repo_init.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/repo_deinit.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/auth.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/provider.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/provider_login.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/config.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/version.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/doctor.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/json_global.rs`
|
||||
|
||||
**Approach:**
|
||||
- Move base-context-only command families (`auth`, `provider`, parts of
|
||||
`repo`) onto `CommandContext` so JSON gating and printer usage come
|
||||
from the shared invocation object.
|
||||
- Use `ctx.machine_settings()` / `ctx.user_settings()` consistently for
|
||||
local settings lookups rather than parallel raw-CLI arguments.
|
||||
- Preserve the special-case JSON contract for `auth status`: explicit
|
||||
invocation-global `--json` remains the only JSON trigger, even if
|
||||
resolved CLI settings say `output.format = json`.
|
||||
- Keep `repo deinit` and similar pure-local leaf helpers narrow if a
|
||||
derived `json_output` boolean or `Printer` extracted from the context
|
||||
keeps the internal helper clearer than passing the full context.
|
||||
|
||||
**Patterns to follow:**
|
||||
- `lib/crates/fabro-cli/src/commands/auth/login.rs`
|
||||
- `lib/crates/fabro-cli/src/commands/auth/status.rs`
|
||||
- `lib/crates/fabro-cli/src/commands/config/mod.rs`
|
||||
- `lib/crates/fabro-cli/src/commands/version.rs`
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: `auth status` and `provider login` preserve global `--json`
|
||||
restrictions and still resolve the intended server target from merged
|
||||
settings.
|
||||
- Happy path: `config`, `version`, `model`, and `doctor` continue to
|
||||
honor text-vs-JSON output without a direct `CliNamespace` parameter.
|
||||
- Edge case: persisted `cli.output.format = "json"` without explicit
|
||||
global `--json` still leaves `auth status` on its current text-mode
|
||||
path.
|
||||
- Edge case: explicit global `--json` still forces `auth status` onto
|
||||
its JSON output path even though the implementation no longer receives
|
||||
a raw `process_local_json` parameter.
|
||||
- Edge case: `repo init` non-JSON progress output and JSON result
|
||||
payloads stay unchanged.
|
||||
- Edge case: `repo deinit` still stays local and does not accidentally
|
||||
require server access just because the family boundary now uses
|
||||
`CommandContext`.
|
||||
- Error path: missing auth sessions, invalid server targets, and missing
|
||||
GitHub access continue to fail with the same user-facing contract.
|
||||
|
||||
**Verification:**
|
||||
- Remaining in-scope command entrypoints no longer expose the raw
|
||||
plumbing bundle as part of their public internal API.
|
||||
|
||||
- [x] **Unit 6: Remove migration scaffolding and prove the boundary is clean**
|
||||
|
||||
**Goal:** Delete transitional surface area and confirm the in-scope
|
||||
command tree is consistently aligned on `CommandContext`.
|
||||
|
||||
**Requirements:** R1, R2, R6, R7
|
||||
|
||||
**Dependencies:** Units 1-5
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-cli/src/command_context.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/main.rs`
|
||||
- Modify: in-scope command modules touched by transitional wrappers
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/top_level.rs`
|
||||
- Test: `lib/crates/fabro-cli/tests/it/cmd/json_global.rs`
|
||||
|
||||
**Approach:**
|
||||
- Remove temporary wrappers or compatibility constructors that were only
|
||||
present to get through the migration.
|
||||
- Delete stale comments and “still being wired through” dead-code
|
||||
annotations once the boundary is real.
|
||||
- Do a final sweep to ensure in-scope command boundaries are not still
|
||||
taking `&CliNamespace`, `&CliLayer`, `Printer`, and
|
||||
`process_local_json` together out of habit.
|
||||
|
||||
**Patterns to follow:**
|
||||
- Keep cleanup limited to true scaffolding removal; do not reopen command
|
||||
semantics or formatting behavior in the cleanup pass.
|
||||
|
||||
**Test scenarios:**
|
||||
- Test expectation: none -- cleanup-only unit. Behavioral coverage should
|
||||
already exist from Units 1-5.
|
||||
|
||||
**Verification:**
|
||||
- The command-boundary API is visibly simpler and consistent across the
|
||||
in-scope families.
|
||||
- No in-scope command still looks half-migrated.
|
||||
|
||||
## System-Wide Impact
|
||||
|
||||
- **Interaction graph:** `main.rs` bootstrap -> base `CommandContext` ->
|
||||
family dispatchers -> derived target/connection contexts ->
|
||||
`server_client.rs` / `user_config.rs`. This touches nearly every
|
||||
user-facing CLI family that already talks to settings or server
|
||||
resolution.
|
||||
- **Error propagation:** context-construction failures remain early and
|
||||
synchronous at the command boundary; server access errors still flow
|
||||
through `ctx.server().await?` and should not move deeper into render
|
||||
helpers.
|
||||
- **State lifecycle risks:** the biggest correctness risk is accidental
|
||||
reuse of the wrong derived context, especially storage-dir-aware
|
||||
contexts in the `system` family and target-based contexts in run/PR
|
||||
flows.
|
||||
- **API surface parity:** although this is an internal refactor, it
|
||||
touches external CLI contracts indirectly through JSON/text output,
|
||||
verbosity, global `--json` restrictions, and server-target resolution.
|
||||
- **Integration coverage:** black-box command tests and scenario tests
|
||||
are the main safety net. Unit tests should only cover context
|
||||
construction/derivation and not replace CLI integration coverage.
|
||||
- **Unchanged invariants:** tracing and upgrade bootstrap ordering stays
|
||||
in `main.rs`; `exec` keeps its distinct direct-provider path; server
|
||||
connection semantics stay in `server_client.rs`; `Styles` remain
|
||||
command-local.
|
||||
|
||||
## Risks & Dependencies
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Output-mode drift when replacing `cli.output.format` reads with `ctx.user_settings().cli.output.format` | Keep characterization coverage in existing `cmd/*` tests for both text and JSON paths before deleting the raw parameters |
|
||||
| Global `--json` behavior changes while moving `process_local_json` into the context | Add targeted coverage in `json_global.rs`, `auth.rs`, `provider_login.rs`, `graph.rs`, and sandbox preview/ssh command tests |
|
||||
| Storage-dir-aware system commands accidentally derive a target-mode context instead of a connection-mode context | Keep connection-specific derivation explicit and cover `system_info`, `system_df`, `system_events`, and `system_prune` with CLI integration tests |
|
||||
| `CommandContext` grows into a second god object | Keep explicit scope rules: invocation plumbing only, no command args, no `Styles`, no feature-specific render state |
|
||||
| The refactor becomes a giant compile-fix patch with poor reviewability | Land the work family-by-family with temporary compatibility wrappers where needed, and verify each family with its existing tests before cleanup |
|
||||
|
||||
## Documentation / Operational Notes
|
||||
|
||||
- No user-facing documentation changes are expected.
|
||||
- Internal comments in `command_context.rs` and nearby command modules
|
||||
should be updated to describe the final boundary, not the transitional
|
||||
“still being wired through” state.
|
||||
- The earlier April 8 plan should remain as historical context; this plan
|
||||
supersedes it for the command-boundary alignment work.
|
||||
|
||||
## Sources & References
|
||||
|
||||
- Prior plan: `docs/plans/2026-04-08-cli-services-command-context-refactor-plan.md`
|
||||
- Related plan: `docs/plans/2026-04-22-001-refactor-settings-api-entrypoints-plan.md`
|
||||
- Related code:
|
||||
`lib/crates/fabro-cli/src/command_context.rs`
|
||||
`lib/crates/fabro-cli/src/main.rs`
|
||||
`lib/crates/fabro-cli/src/server_client.rs`
|
||||
`lib/crates/fabro-cli/src/commands/run/mod.rs`
|
||||
`lib/crates/fabro-cli/src/commands/pr/mod.rs`
|
||||
`lib/crates/fabro-cli/src/commands/secret/mod.rs`
|
||||
`lib/crates/fabro-cli/src/commands/auth/mod.rs`
|
||||
`lib/crates/fabro-cli/src/commands/provider/mod.rs`
|
||||
`lib/crates/fabro-cli/src/commands/system/mod.rs`
|
||||
- Testing guidance: `files-internal/testing-strategy.md`
|
||||
- Related history:
|
||||
`93b6577cd simplify: drop duplicate settings plumbing from cli/server refactor`
|
||||
`367fd9302 refactor(cli): centralize command settings and server access`
|
||||
`4b30a5f16 refactor(cli): route command output through Printer`
|
||||
|
|
@ -26,73 +26,55 @@ pub(crate) enum ServerMode {
|
|||
}
|
||||
|
||||
pub(crate) struct CommandContext {
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "This item is kept for command plumbing that is still being wired through."
|
||||
)]
|
||||
printer: Printer,
|
||||
cwd: PathBuf,
|
||||
base_config_path: PathBuf,
|
||||
machine_settings: SettingsLayer,
|
||||
user_settings: UserSettings,
|
||||
server_mode: ServerMode,
|
||||
server: OnceCell<Arc<Client>>,
|
||||
printer: Printer,
|
||||
process_local_json: bool,
|
||||
cwd: PathBuf,
|
||||
base_config_path: PathBuf,
|
||||
cli_layer: CliLayer,
|
||||
machine_settings: SettingsLayer,
|
||||
user_settings: UserSettings,
|
||||
server_mode: ServerMode,
|
||||
server: OnceCell<Arc<Client>>,
|
||||
}
|
||||
|
||||
impl CommandContext {
|
||||
pub(crate) fn base(printer: Printer, cli_layer: &CliLayer) -> Result<Self> {
|
||||
Self::new(printer, ServerMode::None, cli_layer)
|
||||
}
|
||||
|
||||
pub(crate) fn for_target(
|
||||
args: &ServerTargetArgs,
|
||||
pub(crate) fn base(
|
||||
printer: Printer,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
) -> Result<Self> {
|
||||
Self::new(
|
||||
printer,
|
||||
ServerMode::ByTarget {
|
||||
target_override: args.server.clone(),
|
||||
},
|
||||
cli_layer,
|
||||
)
|
||||
Self::new(printer, process_local_json, ServerMode::None, cli_layer)
|
||||
}
|
||||
|
||||
pub(crate) fn for_connection(
|
||||
args: &ServerConnectionArgs,
|
||||
pub(crate) fn with_target(&self, args: &ServerTargetArgs) -> Result<Self> {
|
||||
self.with_server_mode(ServerMode::ByTarget {
|
||||
target_override: args.server.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn with_connection(&self, args: &ServerConnectionArgs) -> Result<Self> {
|
||||
self.with_server_mode(ServerMode::ByStorageDir {
|
||||
target_override: args.target.server.clone(),
|
||||
storage_dir_override: args.storage_dir.clone_path(),
|
||||
})
|
||||
}
|
||||
|
||||
fn new(
|
||||
printer: Printer,
|
||||
process_local_json: bool,
|
||||
server_mode: ServerMode,
|
||||
cli_layer: &CliLayer,
|
||||
) -> Result<Self> {
|
||||
Self::new(
|
||||
printer,
|
||||
ServerMode::ByStorageDir {
|
||||
target_override: args.target.server.clone(),
|
||||
storage_dir_override: args.storage_dir.clone_path(),
|
||||
},
|
||||
cli_layer,
|
||||
)
|
||||
}
|
||||
|
||||
fn new(printer: Printer, server_mode: ServerMode, cli_layer: &CliLayer) -> Result<Self> {
|
||||
let cwd = std::env::current_dir().context("Failed to get current directory")?;
|
||||
let base_config_path = user_config::active_settings_path(None);
|
||||
let disk_settings = match &server_mode {
|
||||
ServerMode::None | ServerMode::ByTarget { .. } => user_config::load_settings()?,
|
||||
ServerMode::ByStorageDir {
|
||||
storage_dir_override,
|
||||
..
|
||||
} => user_config::load_settings_with_storage_dir(storage_dir_override.as_deref())?,
|
||||
};
|
||||
let machine_settings = combine_files(disk_settings, SettingsLayer {
|
||||
cli: Some(cli_layer.clone()),
|
||||
..SettingsLayer::default()
|
||||
});
|
||||
let user_settings = fabro_config::UserSettings::from_layer(&machine_settings)?;
|
||||
let (machine_settings, user_settings) = load_merged_settings(cli_layer, &server_mode)?;
|
||||
|
||||
Ok(Self {
|
||||
printer,
|
||||
process_local_json,
|
||||
cwd,
|
||||
base_config_path,
|
||||
cli_layer: cli_layer.clone(),
|
||||
machine_settings,
|
||||
user_settings,
|
||||
server_mode,
|
||||
|
|
@ -100,14 +82,18 @@ impl CommandContext {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "This item is kept for command plumbing that is still being wired through."
|
||||
)]
|
||||
pub(crate) fn printer(&self) -> Printer {
|
||||
self.printer
|
||||
}
|
||||
|
||||
pub(crate) fn explicit_json_requested(&self) -> bool {
|
||||
self.process_local_json
|
||||
}
|
||||
|
||||
pub(crate) fn require_no_json_override(&self) -> Result<()> {
|
||||
crate::args::require_no_json_override(self.process_local_json)
|
||||
}
|
||||
|
||||
pub(crate) fn cwd(&self) -> &Path {
|
||||
&self.cwd
|
||||
}
|
||||
|
|
@ -149,4 +135,193 @@ impl CommandContext {
|
|||
|
||||
Ok(Arc::clone(client))
|
||||
}
|
||||
|
||||
fn with_server_mode(&self, server_mode: ServerMode) -> Result<Self> {
|
||||
let (machine_settings, user_settings) = match &server_mode {
|
||||
ServerMode::ByStorageDir { .. } => load_merged_settings(&self.cli_layer, &server_mode)?,
|
||||
ServerMode::None | ServerMode::ByTarget { .. } => {
|
||||
(self.machine_settings.clone(), self.user_settings.clone())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
printer: self.printer,
|
||||
process_local_json: self.process_local_json,
|
||||
cwd: self.cwd.clone(),
|
||||
base_config_path: self.base_config_path.clone(),
|
||||
cli_layer: self.cli_layer.clone(),
|
||||
machine_settings,
|
||||
user_settings,
|
||||
server_mode,
|
||||
server: OnceCell::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn load_merged_settings(
|
||||
cli_layer: &CliLayer,
|
||||
server_mode: &ServerMode,
|
||||
) -> Result<(SettingsLayer, UserSettings)> {
|
||||
let disk_settings = match server_mode {
|
||||
ServerMode::None | ServerMode::ByTarget { .. } => user_config::load_settings()?,
|
||||
ServerMode::ByStorageDir {
|
||||
storage_dir_override,
|
||||
..
|
||||
} => user_config::load_settings_with_storage_dir(storage_dir_override.as_deref())?,
|
||||
};
|
||||
merge_settings_layer(disk_settings, cli_layer)
|
||||
}
|
||||
|
||||
fn merge_settings_layer(
|
||||
disk_settings: SettingsLayer,
|
||||
cli_layer: &CliLayer,
|
||||
) -> Result<(SettingsLayer, UserSettings)> {
|
||||
let machine_settings = combine_files(disk_settings, SettingsLayer {
|
||||
cli: Some(cli_layer.clone()),
|
||||
..SettingsLayer::default()
|
||||
});
|
||||
let user_settings = UserSettings::from_layer(&machine_settings)?;
|
||||
Ok((machine_settings, user_settings))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use fabro_config::parse_settings_layer;
|
||||
use fabro_types::settings::cli::{CliLayer, CliOutputLayer, OutputFormat, OutputVerbosity};
|
||||
use fabro_util::printer::Printer;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use super::{CommandContext, ServerMode, merge_settings_layer};
|
||||
use crate::args::ServerTargetArgs;
|
||||
|
||||
fn cli_layer_with_json_and_verbose() -> CliLayer {
|
||||
CliLayer {
|
||||
output: Some(CliOutputLayer {
|
||||
format: Some(OutputFormat::Json),
|
||||
verbosity: Some(OutputVerbosity::Verbose),
|
||||
}),
|
||||
..CliLayer::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn synthetic_context(process_local_json: bool, printer: Printer) -> CommandContext {
|
||||
let cli_layer = cli_layer_with_json_and_verbose();
|
||||
let (machine_settings, user_settings) =
|
||||
merge_settings_layer(parse_settings_layer("_version = 1\n").unwrap(), &cli_layer)
|
||||
.expect("settings should merge");
|
||||
|
||||
CommandContext {
|
||||
printer,
|
||||
process_local_json,
|
||||
cwd: PathBuf::from("/tmp/workspace"),
|
||||
base_config_path: PathBuf::from("/tmp/settings.toml"),
|
||||
cli_layer,
|
||||
machine_settings,
|
||||
user_settings,
|
||||
server_mode: ServerMode::None,
|
||||
server: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_exposes_resolved_output_and_explicit_json_state() {
|
||||
let ctx = synthetic_context(true, Printer::Default);
|
||||
|
||||
assert_eq!(ctx.user_settings().cli.output.format, OutputFormat::Json);
|
||||
assert_eq!(
|
||||
ctx.user_settings().cli.output.verbosity,
|
||||
OutputVerbosity::Verbose
|
||||
);
|
||||
assert!(ctx.explicit_json_requested());
|
||||
assert_eq!(ctx.printer(), Printer::Default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deriving_target_context_preserves_invocation_state() {
|
||||
let base = synthetic_context(true, Printer::Verbose);
|
||||
let derived = base
|
||||
.with_target(&ServerTargetArgs {
|
||||
server: Some("https://fabro.example.com".to_string()),
|
||||
})
|
||||
.expect("target context should derive");
|
||||
|
||||
assert_eq!(derived.printer(), Printer::Verbose);
|
||||
assert!(derived.explicit_json_requested());
|
||||
assert_eq!(derived.user_settings(), base.user_settings());
|
||||
assert_eq!(derived.machine_settings(), base.machine_settings());
|
||||
assert_eq!(derived.cwd(), base.cwd());
|
||||
assert!(matches!(
|
||||
derived.server_mode,
|
||||
ServerMode::ByTarget { target_override: Some(ref target) }
|
||||
if target == "https://fabro.example.com"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_dir_override_only_changes_storage_root_in_merged_settings() {
|
||||
let cli_layer = cli_layer_with_json_and_verbose();
|
||||
let base_disk_settings = parse_settings_layer(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[server.storage]
|
||||
root = "/srv/fabro/default"
|
||||
"#,
|
||||
)
|
||||
.expect("settings fixture should parse");
|
||||
let override_disk_settings = fabro_config::user::apply_storage_dir_override(
|
||||
base_disk_settings.clone(),
|
||||
Some(std::path::Path::new("/srv/fabro/override")),
|
||||
);
|
||||
|
||||
let (base_settings, base_user_settings) =
|
||||
merge_settings_layer(base_disk_settings, &cli_layer)
|
||||
.expect("base settings should merge");
|
||||
let (connection_settings, connection_user_settings) =
|
||||
merge_settings_layer(override_disk_settings, &cli_layer)
|
||||
.expect("connection settings should merge");
|
||||
|
||||
assert_eq!(base_user_settings, connection_user_settings);
|
||||
assert_eq!(base_user_settings.cli.output.format, OutputFormat::Json);
|
||||
assert_eq!(
|
||||
base_settings
|
||||
.server
|
||||
.as_ref()
|
||||
.and_then(|server| server.storage.as_ref())
|
||||
.and_then(|storage| storage.root.as_ref())
|
||||
.map(|root| root.as_source()),
|
||||
Some("/srv/fabro/default".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
connection_settings
|
||||
.server
|
||||
.as_ref()
|
||||
.and_then(|server| server.storage.as_ref())
|
||||
.and_then(|storage| storage.root.as_ref())
|
||||
.map(|root| root.as_source()),
|
||||
Some("/srv/fabro/override".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_json_guard_uses_invocation_flag_not_resolved_output_format() {
|
||||
let json_ctx = synthetic_context(true, Printer::Default);
|
||||
let text_ctx = synthetic_context(false, Printer::Default);
|
||||
|
||||
assert!(json_ctx.require_no_json_override().is_err());
|
||||
assert!(text_ctx.require_no_json_override().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_resolution_errors_remain_deferred() {
|
||||
let ctx = synthetic_context(false, Printer::Default)
|
||||
.with_target(&ServerTargetArgs {
|
||||
server: Some("not-a-valid-target".to_string()),
|
||||
})
|
||||
.expect("target derivation should not resolve the target eagerly");
|
||||
|
||||
assert!(matches!(ctx.server_mode, ServerMode::ByTarget { .. }));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,28 +6,22 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
|
||||
use crate::args::ArtifactCpArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::server_client::Client;
|
||||
use crate::shared::{print_json_pretty, split_run_path};
|
||||
|
||||
pub(super) async fn cp_command(
|
||||
args: &ArtifactCpArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(super) async fn cp_command(args: &ArtifactCpArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let printer = base_ctx.printer();
|
||||
let (run_id_selector, asset_path) = parse_source(&args.source);
|
||||
let (run_id, client, entries) = super::resolve_artifacts(
|
||||
base_ctx,
|
||||
&args.server,
|
||||
run_id_selector,
|
||||
args.node.as_deref(),
|
||||
args.retry,
|
||||
cli_layer,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -64,7 +58,7 @@ pub(super) async fn cp_command(
|
|||
.unwrap_or_else(|| std::ffi::OsStr::new(&entry.relative_path)),
|
||||
);
|
||||
write_artifact_file(&client, &run_id, entry, &dest_file).await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({
|
||||
"copied": [{
|
||||
"relative_path": entry.relative_path,
|
||||
|
|
@ -125,7 +119,7 @@ pub(super) async fn cp_command(
|
|||
}
|
||||
}
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({ "copied": copied }))?;
|
||||
} else {
|
||||
fabro_util::printout!(
|
||||
|
|
|
|||
|
|
@ -1,30 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use cli_table::format::{Border, Justify, Separator};
|
||||
use cli_table::{Cell, CellStruct, Style, Table};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
use crate::args::ArtifactListArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
pub(super) async fn list_command(
|
||||
args: &ArtifactListArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(super) async fn list_command(args: &ArtifactListArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let printer = base_ctx.printer();
|
||||
let (_run_id, _client, entries) = super::resolve_artifacts(
|
||||
base_ctx,
|
||||
&args.server,
|
||||
&args.run_id,
|
||||
args.node.as_deref(),
|
||||
args.retry,
|
||||
cli_layer,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
fabro_util::printout!(printer, "{}", serde_json::to_string_pretty(&entries)?);
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ mod cp;
|
|||
mod list;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_types::{RunId, StageId};
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::{ArtifactCommand, ArtifactNamespace, ServerTargetArgs};
|
||||
use crate::command_context::CommandContext;
|
||||
|
|
@ -22,14 +19,13 @@ pub(super) struct ArtifactEntry {
|
|||
}
|
||||
|
||||
pub(super) async fn resolve_artifacts(
|
||||
base_ctx: &CommandContext,
|
||||
server: &ServerTargetArgs,
|
||||
run_selector: &str,
|
||||
node: Option<&str>,
|
||||
retry: Option<u32>,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<(RunId, Client, Vec<ArtifactEntry>)> {
|
||||
let ctx = CommandContext::for_target(server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(run_selector).await?.run_id;
|
||||
let mut entries = Vec::new();
|
||||
|
|
@ -62,14 +58,9 @@ pub(super) async fn resolve_artifacts(
|
|||
Ok((run_id, client.clone_for_reuse(), entries))
|
||||
}
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: ArtifactNamespace,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: ArtifactNamespace, base_ctx: &CommandContext) -> Result<()> {
|
||||
match ns.command {
|
||||
ArtifactCommand::List(args) => list::list_command(&args, cli, cli_layer, printer).await,
|
||||
ArtifactCommand::Cp(args) => cp::cp_command(&args, cli, cli_layer, printer).await,
|
||||
ArtifactCommand::List(args) => list::list_command(&args, base_ctx).await,
|
||||
ArtifactCommand::Cp(args) => cp::cp_command(&args, base_ctx).await,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ use anyhow::{Context as _, Result, bail};
|
|||
use chrono::{DateTime, Utc};
|
||||
use fabro_client::{AuthEntry, AuthStore, StoredSubject};
|
||||
use fabro_http::header::CONTENT_TYPE;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::browser;
|
||||
use fabro_util::printer::Printer;
|
||||
use serde::Deserialize;
|
||||
use tokio::time::timeout;
|
||||
|
||||
use crate::args::{AuthLoginArgs, require_no_json_override};
|
||||
use crate::args::AuthLoginArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::user_config;
|
||||
use crate::user_config::ServerTarget;
|
||||
|
|
@ -33,17 +32,13 @@ struct CliTokenSubject {
|
|||
email: String,
|
||||
}
|
||||
|
||||
pub(super) async fn login_command(
|
||||
args: AuthLoginArgs,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
require_no_json_override(process_local_json)?;
|
||||
pub(super) async fn login_command(args: AuthLoginArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
base_ctx.require_no_json_override()?;
|
||||
let printer = base_ctx.printer();
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
let _ = (args, cli_layer, printer);
|
||||
let _ = (args, printer);
|
||||
bail!(
|
||||
"CLI OAuth login is not supported on Windows in this release. Use WSL, or use a dev-token server."
|
||||
);
|
||||
|
|
@ -51,8 +46,7 @@ pub(super) async fn login_command(
|
|||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let ctx = CommandContext::base(printer, cli_layer)?;
|
||||
let target = user_config::resolve_server_target(&args.server, ctx.machine_settings())?;
|
||||
let target = user_config::resolve_server_target(&args.server, base_ctx.machine_settings())?;
|
||||
let web_url = browser_origin(&target)?;
|
||||
let pkce = fabro_oauth::generate_pkce();
|
||||
let state = fabro_oauth::generate_state();
|
||||
|
|
|
|||
|
|
@ -1,23 +1,16 @@
|
|||
use anyhow::{Result, bail};
|
||||
use fabro_client::{AuthEntry, AuthStore};
|
||||
use fabro_http::header::AUTHORIZATION;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::{AuthLogoutArgs, require_no_json_override};
|
||||
use crate::args::AuthLogoutArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::user_config;
|
||||
use crate::user_config::ServerTarget;
|
||||
|
||||
pub(super) async fn logout_command(
|
||||
args: AuthLogoutArgs,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
require_no_json_override(process_local_json)?;
|
||||
pub(super) async fn logout_command(args: AuthLogoutArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
base_ctx.require_no_json_override()?;
|
||||
let printer = base_ctx.printer();
|
||||
|
||||
let ctx = CommandContext::base(printer, cli_layer)?;
|
||||
let store = AuthStore::default();
|
||||
if args.all {
|
||||
let entries = store.list()?;
|
||||
|
|
@ -41,7 +34,7 @@ pub(super) async fn logout_command(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let target = user_config::resolve_server_target(&args.server, ctx.machine_settings())?;
|
||||
let target = user_config::resolve_server_target(&args.server, base_ctx.machine_settings())?;
|
||||
let Some(entry) = store.get(&target)? else {
|
||||
fabro_util::printerr!(printer, "Not logged in to {}.", target);
|
||||
return Ok(());
|
||||
|
|
|
|||
|
|
@ -3,26 +3,14 @@ mod logout;
|
|||
mod status;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::{AuthCommand, AuthNamespace};
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: AuthNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: AuthNamespace, base_ctx: &CommandContext) -> Result<()> {
|
||||
match ns.command {
|
||||
AuthCommand::Login(args) => {
|
||||
login::login_command(args, cli_layer, process_local_json, printer).await
|
||||
}
|
||||
AuthCommand::Logout(args) => {
|
||||
logout::logout_command(args, cli_layer, process_local_json, printer).await
|
||||
}
|
||||
AuthCommand::Status(args) => {
|
||||
status::status_command(&args, cli_layer, process_local_json, printer)
|
||||
}
|
||||
AuthCommand::Login(args) => login::login_command(args, base_ctx).await,
|
||||
AuthCommand::Logout(args) => logout::logout_command(args, base_ctx).await,
|
||||
AuthCommand::Status(args) => status::status_command(&args, base_ctx),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use chrono::{DateTime, Utc};
|
||||
use fabro_client::{AuthEntry, AuthStore};
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::dev_token::{read_dev_token_file, validate_dev_token_format};
|
||||
use fabro_util::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::args::AuthStatusArgs;
|
||||
|
|
@ -40,13 +38,8 @@ struct StatusOutput {
|
|||
dev_token: &'static str,
|
||||
}
|
||||
|
||||
pub(super) fn status_command(
|
||||
args: &AuthStatusArgs,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::base(printer, cli_layer)?;
|
||||
pub(super) fn status_command(args: &AuthStatusArgs, ctx: &CommandContext) -> Result<()> {
|
||||
let printer = ctx.printer();
|
||||
let store = AuthStore::default();
|
||||
let now = Utc::now();
|
||||
let rows = if args.server.as_deref().is_some() {
|
||||
|
|
@ -61,7 +54,7 @@ pub(super) fn status_command(
|
|||
"not_set"
|
||||
};
|
||||
|
||||
if process_local_json {
|
||||
if ctx.explicit_json_requested() {
|
||||
print_json_pretty(&StatusOutput {
|
||||
servers: rows,
|
||||
dev_token,
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ use std::io::Write;
|
|||
|
||||
use fabro_api::types::ServerSettings;
|
||||
use fabro_config::UserSettings;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::args::SettingsArgs;
|
||||
|
|
@ -28,10 +26,9 @@ struct RenderedConfig {
|
|||
|
||||
async fn rendered_config(
|
||||
args: &SettingsArgs,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&args.target)?;
|
||||
let user = fabro_config::UserSettings::resolve()?;
|
||||
let server = ctx
|
||||
.server()
|
||||
|
|
@ -41,14 +38,9 @@ async fn rendered_config(
|
|||
serde_json::to_value(RenderedConfig { user, server }).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(
|
||||
args: &SettingsArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<()> {
|
||||
let config = Box::pin(rendered_config(args, cli_layer, printer)).await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
pub(crate) async fn execute(args: &SettingsArgs, base_ctx: &CommandContext) -> anyhow::Result<()> {
|
||||
let config = Box::pin(rendered_config(args, base_ctx)).await?;
|
||||
if base_ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&config)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ use std::path::PathBuf;
|
|||
use anyhow::Result;
|
||||
use fabro_api::types as api_types;
|
||||
use fabro_config::user::active_settings_path;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::cli::{OutputFormat, OutputVerbosity};
|
||||
pub(crate) use fabro_util::check_report::{
|
||||
CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus,
|
||||
};
|
||||
|
|
@ -142,13 +141,13 @@ fn render_report(report: &CheckReport, styles: &Styles, verbose: bool, printer:
|
|||
|
||||
pub(crate) async fn run_doctor(
|
||||
args: &DoctorArgs,
|
||||
verbose: bool,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<i32, anyhow::Error> {
|
||||
let verbose =
|
||||
args.verbose || base_ctx.user_settings().cli.output.verbosity == OutputVerbosity::Verbose;
|
||||
let printer = base_ctx.printer();
|
||||
let styles = Styles::detect_stdout();
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
let json = base_ctx.user_settings().cli.output.format == OutputFormat::Json;
|
||||
let spinner = if json {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -179,7 +178,7 @@ pub(crate) async fn run_doctor(
|
|||
}],
|
||||
};
|
||||
|
||||
let ctx = match CommandContext::for_target(&args.target, printer, cli_layer) {
|
||||
let ctx = match base_ctx.with_target(&args.target) {
|
||||
Ok(ctx) => ctx,
|
||||
Err(err) => {
|
||||
report.sections.push(CheckSection {
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ use anyhow::{Context, bail};
|
|||
use fabro_api::types;
|
||||
use fabro_config::load::load_settings_user;
|
||||
use fabro_config::user::active_settings_path;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::{CliNamespace, SettingsLayer};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::SettingsLayer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::terminal::Styles;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::args::{GraphArgs, GraphDirection, require_no_json_override};
|
||||
use crate::args::{GraphArgs, GraphDirection};
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::commands::run::output::api_diagnostics_to_local;
|
||||
use crate::manifest_builder::{ManifestBuildInput, build_run_manifest};
|
||||
|
|
@ -28,16 +27,14 @@ use crate::shared::{absolute_or_current, print_diagnostics, print_json_pretty, r
|
|||
pub(crate) async fn run(
|
||||
args: &GraphArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> anyhow::Result<()> {
|
||||
if process_local_json && args.output.is_none() {
|
||||
require_no_json_override(process_local_json)?;
|
||||
if base_ctx.explicit_json_requested() && args.output.is_none() {
|
||||
base_ctx.require_no_json_override()?;
|
||||
}
|
||||
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let printer = base_ctx.printer();
|
||||
let ctx = base_ctx.with_target(&args.target)?;
|
||||
let built = build_run_manifest(ManifestBuildInput {
|
||||
workflow: args.workflow.clone(),
|
||||
cwd: ctx.cwd().to_path_buf(),
|
||||
|
|
@ -73,7 +70,7 @@ pub(crate) async fn run(
|
|||
if let Some(ref output_path) = args.output {
|
||||
std::fs::write(output_path, &rendered)
|
||||
.with_context(|| format!("writing rendered graph to {}", output_path.display()))?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({
|
||||
"path": absolute_or_current(output_path),
|
||||
"format": args.format.to_string(),
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ use crate::args::{
|
|||
DoctorArgs, InstallArgs, InstallCommand, InstallGitHubStrategyArg, InstallGithubArgs,
|
||||
InstallNonInteractiveArgs, ServerTargetArgs,
|
||||
};
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::commands::server::{start, stop};
|
||||
use crate::gh::GhCli;
|
||||
use crate::shared::provider_auth::{
|
||||
|
|
@ -1941,7 +1942,8 @@ async fn run_install_inner(
|
|||
target: ServerTargetArgs::default(),
|
||||
verbose: false,
|
||||
};
|
||||
doctor::run_doctor(&doctor_args, false, cli, cli_layer, printer).await
|
||||
let base_ctx = CommandContext::base(printer, cli_layer, false)?;
|
||||
doctor::run_doctor(&doctor_args, &base_ctx).await
|
||||
},
|
||||
)
|
||||
.await?
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ use cli_table::format::{Border, Justify, Separator};
|
|||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||
use fabro_api::types as api_types;
|
||||
use fabro_model::{Catalog, Model, Provider};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::terminal::Styles;
|
||||
use serde::Serialize;
|
||||
|
||||
|
|
@ -42,19 +40,22 @@ struct ModelTestOutput {
|
|||
|
||||
pub(crate) async fn execute(
|
||||
command: Option<ModelsCommand>,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<()> {
|
||||
let command = command.unwrap_or_default();
|
||||
let target_args = match &command {
|
||||
ModelsCommand::List(args) => &args.target,
|
||||
ModelsCommand::Test(args) => &args.target,
|
||||
};
|
||||
let ctx = CommandContext::for_target(target_args, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(target_args)?;
|
||||
let server = ctx.server().await?;
|
||||
|
||||
run_models(command, &server, cli.output.format == OutputFormat::Json).await
|
||||
run_models(
|
||||
command,
|
||||
&server,
|
||||
ctx.user_settings().cli.output.format == OutputFormat::Json,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn format_context_window(tokens: i64) -> String {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use tracing::info;
|
||||
|
||||
use crate::args::PrCloseArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::print_json_pretty;
|
||||
|
||||
pub(super) async fn close_command(
|
||||
args: PrCloseArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let (record, _run_id) =
|
||||
super::load_pr_record(&args.server, &args.run_id, cli_layer, printer).await?;
|
||||
pub(super) async fn close_command(args: PrCloseArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let (record, _run_id) = super::load_pr_record(&args.server, &args.run_id, base_ctx).await?;
|
||||
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(base_ctx)?;
|
||||
|
||||
fabro_github::close_pull_request(
|
||||
&creds,
|
||||
|
|
@ -29,7 +24,7 @@ pub(super) async fn close_command(
|
|||
.map_err(|err| anyhow::anyhow!("{err}"))?;
|
||||
|
||||
info!(number = record.number, owner = %record.owner, repo = %record.repo, "Closed pull request");
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({
|
||||
"number": record.number,
|
||||
"html_url": record.html_url,
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ use fabro_auth::configured_providers_from_process_env;
|
|||
use fabro_config::Storage;
|
||||
use fabro_model::Catalog;
|
||||
use fabro_sandbox::daytona::detect_repo_info;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_vault::Vault;
|
||||
use fabro_workflow::outcome::StageStatus;
|
||||
use fabro_workflow::pull_request::maybe_open_pull_request;
|
||||
|
|
@ -25,13 +23,9 @@ use crate::user_config;
|
|||
deprecated,
|
||||
reason = "boundary-exempt(pr-api): remove with follow-up #1 when PR ops move server-side"
|
||||
)]
|
||||
pub(super) async fn create_command(
|
||||
args: PrCreateArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
pub(super) async fn create_command(args: PrCreateArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run_id).await?.run_id;
|
||||
let events = client.list_run_events(&run_id, None, None).await?;
|
||||
|
|
@ -86,7 +80,7 @@ pub(super) async fn create_command(
|
|||
let (owner, repo) = fabro_github::parse_github_owner_repo(&https_url)
|
||||
.map_err(|err| anyhow::anyhow!("{err}"))?;
|
||||
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(base_ctx)?;
|
||||
|
||||
let branch_found = fabro_github::branch_exists(
|
||||
&creds,
|
||||
|
|
@ -136,14 +130,14 @@ pub(super) async fn create_command(
|
|||
match pull_request {
|
||||
Some(record) => {
|
||||
info!(pr_url = %record.html_url, "Pull request created");
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&record)?;
|
||||
} else {
|
||||
fabro_util::printout!(printer, "{}", record.html_url);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::Value::Null)?;
|
||||
} else {
|
||||
fabro_util::printout!(printer, "No pull request created (empty diff).");
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use cli_table::format::{Border, Separator};
|
||||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::terminal::Styles;
|
||||
use futures::future::join_all;
|
||||
use serde::Serialize;
|
||||
|
|
@ -23,13 +21,9 @@ struct PrRow {
|
|||
url: String,
|
||||
}
|
||||
|
||||
pub(super) async fn list_command(
|
||||
args: PrListArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
pub(super) async fn list_command(args: PrListArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
|
@ -42,7 +36,7 @@ pub(super) async fn list_command(
|
|||
}
|
||||
|
||||
if entries.is_empty() {
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&Vec::<PrRow>::new())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -50,7 +44,7 @@ pub(super) async fn list_command(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(base_ctx)?;
|
||||
|
||||
let futures: Vec<_> = entries
|
||||
.iter()
|
||||
|
|
@ -104,7 +98,7 @@ pub(super) async fn list_command(
|
|||
.collect()
|
||||
};
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&rows)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use tracing::info;
|
||||
|
||||
use crate::args::PrMergeArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::print_json_pretty;
|
||||
|
||||
pub(super) async fn merge_command(
|
||||
args: PrMergeArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let (record, _run_id) =
|
||||
super::load_pr_record(&args.server, &args.run_id, cli_layer, printer).await?;
|
||||
pub(super) async fn merge_command(args: PrMergeArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let (record, _run_id) = super::load_pr_record(&args.server, &args.run_id, base_ctx).await?;
|
||||
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(base_ctx)?;
|
||||
|
||||
fabro_github::merge_pull_request(
|
||||
&creds,
|
||||
|
|
@ -30,7 +25,7 @@ pub(super) async fn merge_command(
|
|||
.map_err(|err| anyhow::anyhow!("{err}"))?;
|
||||
|
||||
info!(number = record.number, owner = %record.owner, repo = %record.repo, method = %args.method, "Merged pull request");
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({
|
||||
"number": record.number,
|
||||
"html_url": record.html_url,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ use anyhow::{Context, Result, anyhow};
|
|||
use fabro_config::Storage;
|
||||
use fabro_github::GitHubCredentials;
|
||||
use fabro_types::PullRequestRecord;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_types::settings::{CliNamespace, InterpString};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::InterpString;
|
||||
|
||||
use crate::args::{PrCommand, PrNamespace, ServerTargetArgs};
|
||||
use crate::command_context::CommandContext;
|
||||
|
|
@ -20,20 +18,13 @@ use crate::user_config;
|
|||
const GITHUB_CREDENTIALS_REQUIRED: &str =
|
||||
"GitHub credentials required — run `fabro install` or set GITHUB_TOKEN";
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: PrNamespace,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: PrNamespace, base_ctx: &CommandContext) -> Result<()> {
|
||||
match ns.command {
|
||||
PrCommand::Create(args) => {
|
||||
Box::pin(create::create_command(args, cli, cli_layer, printer)).await
|
||||
}
|
||||
PrCommand::List(args) => list::list_command(args, cli, cli_layer, printer).await,
|
||||
PrCommand::View(args) => view::view_command(args, cli, cli_layer, printer).await,
|
||||
PrCommand::Merge(args) => merge::merge_command(args, cli, cli_layer, printer).await,
|
||||
PrCommand::Close(args) => close::close_command(args, cli, cli_layer, printer).await,
|
||||
PrCommand::Create(args) => Box::pin(create::create_command(args, base_ctx)).await,
|
||||
PrCommand::List(args) => list::list_command(args, base_ctx).await,
|
||||
PrCommand::View(args) => view::view_command(args, base_ctx).await,
|
||||
PrCommand::Merge(args) => merge::merge_command(args, base_ctx).await,
|
||||
PrCommand::Close(args) => close::close_command(args, base_ctx).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -41,14 +32,10 @@ pub(crate) async fn dispatch(
|
|||
deprecated,
|
||||
reason = "boundary-exempt(pr-api): remove with follow-up #1 when PR ops move server-side"
|
||||
)]
|
||||
fn load_github_credentials_required(
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<GitHubCredentials> {
|
||||
let ctx = CommandContext::base(printer, cli_layer)?;
|
||||
let server_settings = fabro_config::ServerSettings::from_layer(ctx.machine_settings())
|
||||
fn load_github_credentials_required(base_ctx: &CommandContext) -> Result<GitHubCredentials> {
|
||||
let server_settings = fabro_config::ServerSettings::from_layer(base_ctx.machine_settings())
|
||||
.map_err(anyhow::Error::from)?;
|
||||
let vault = user_config::storage_dir(ctx.machine_settings())
|
||||
let vault = user_config::storage_dir(base_ctx.machine_settings())
|
||||
.ok()
|
||||
.and_then(|dir| fabro_vault::Vault::load(Storage::new(&dir).secrets_path()).ok());
|
||||
let creds = build_github_credentials(
|
||||
|
|
@ -70,10 +57,9 @@ fn load_github_credentials_required(
|
|||
pub(crate) async fn load_pr_record(
|
||||
server: &ServerTargetArgs,
|
||||
run_id: &str,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<(PullRequestRecord, fabro_types::RunId)> {
|
||||
let ctx = CommandContext::for_target(server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(run_id).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use tracing::info;
|
||||
|
||||
use crate::args::PrViewArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::print_json_pretty;
|
||||
|
||||
pub(super) async fn view_command(
|
||||
args: PrViewArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let (record, _run_id) =
|
||||
super::load_pr_record(&args.server, &args.run_id, cli_layer, printer).await?;
|
||||
pub(super) async fn view_command(args: PrViewArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let (record, _run_id) = super::load_pr_record(&args.server, &args.run_id, base_ctx).await?;
|
||||
|
||||
let creds = super::load_github_credentials_required(cli_layer, printer)?;
|
||||
let creds = super::load_github_credentials_required(base_ctx)?;
|
||||
|
||||
let detail = fabro_github::get_pull_request(
|
||||
&creds,
|
||||
|
|
@ -30,7 +25,7 @@ pub(super) async fn view_command(
|
|||
|
||||
info!(number = detail.number, owner = %record.owner, repo = %record.repo, "Viewing pull request");
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&detail)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use anyhow::bail;
|
||||
use fabro_config::load::load_settings_user;
|
||||
use fabro_config::user::active_settings_path;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat, OutputVerbosity};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::{OutputFormat, OutputVerbosity};
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
use crate::args::PreflightArgs;
|
||||
|
|
@ -17,13 +15,13 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(crate) async fn execute(
|
||||
mut args: PreflightArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
args.verbose = args.verbose || cli.output.verbosity == OutputVerbosity::Verbose;
|
||||
let printer = base_ctx.printer();
|
||||
let ctx = base_ctx.with_target(&args.target)?;
|
||||
args.verbose =
|
||||
args.verbose || ctx.user_settings().cli.output.verbosity == OutputVerbosity::Verbose;
|
||||
|
||||
let manifest = build_run_manifest(ManifestBuildInput {
|
||||
workflow: args.workflow.clone(),
|
||||
|
|
@ -38,7 +36,7 @@ pub(crate) async fn execute(
|
|||
let response = client.run_preflight(manifest.manifest).await?;
|
||||
let diagnostics = api_diagnostics_to_local(&response.workflow.diagnostics);
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&response)?;
|
||||
} else {
|
||||
print_preflight_workflow_summary(
|
||||
|
|
|
|||
|
|
@ -1,23 +1,20 @@
|
|||
use anyhow::Result;
|
||||
use fabro_api::types;
|
||||
use fabro_auth::credential_id_for;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
use crate::args::{ProviderLoginArgs, require_no_json_override};
|
||||
use crate::args::ProviderLoginArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::provider_auth;
|
||||
|
||||
pub(super) async fn login_command(
|
||||
args: ProviderLoginArgs,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<()> {
|
||||
require_no_json_override(process_local_json)?;
|
||||
base_ctx.require_no_json_override()?;
|
||||
let printer = base_ctx.printer();
|
||||
let s = Styles::detect_stderr();
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&args.target)?;
|
||||
let server = ctx.server().await?;
|
||||
let credential = if args.api_key_stdin {
|
||||
provider_auth::authenticate_provider_with_api_key_source(
|
||||
|
|
|
|||
|
|
@ -1,20 +1,12 @@
|
|||
mod login;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::{ProviderCommand, ProviderNamespace};
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: ProviderNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: ProviderNamespace, base_ctx: &CommandContext) -> Result<()> {
|
||||
match ns.command {
|
||||
ProviderCommand::Login(args) => {
|
||||
login::login_command(args, cli_layer, process_local_json, printer).await
|
||||
}
|
||||
ProviderCommand::Login(args) => login::login_command(args, base_ctx).await,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
pub(crate) fn run_deinit(cli: &CliNamespace, printer: Printer) -> Result<Vec<String>> {
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
pub(crate) fn run_deinit(base_ctx: &CommandContext) -> Result<Vec<String>> {
|
||||
let printer = base_ctx.printer();
|
||||
let repo_root = super::init::git_repo_root()?;
|
||||
let mut removed = Vec::new();
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ pub(crate) fn run_deinit(cli: &CliNamespace, printer: Printer) -> Result<Vec<Str
|
|||
std::fs::remove_dir_all(&fabro_dir)
|
||||
.with_context(|| format!("failed to remove {}", fabro_dir.display()))?;
|
||||
removed.push(".fabro/".to_string());
|
||||
if cli.output.format != OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format != OutputFormat::Json {
|
||||
fabro_util::printerr!(
|
||||
printer,
|
||||
" {} {}",
|
||||
|
|
@ -29,7 +30,7 @@ pub(crate) fn run_deinit(cli: &CliNamespace, printer: Printer) -> Result<Vec<Str
|
|||
);
|
||||
}
|
||||
|
||||
if cli.output.format != OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format != OutputFormat::Json {
|
||||
fabro_util::printerr!(
|
||||
printer,
|
||||
"\n{}",
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use tokio::process::Command as TokioCommand;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
|
|
@ -36,10 +34,9 @@ pub(super) fn git_repo_root() -> Result<PathBuf> {
|
|||
|
||||
pub(crate) async fn run_init(
|
||||
args: &RepoInitArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<Vec<String>> {
|
||||
let printer = base_ctx.printer();
|
||||
let repo_root = spawn_blocking(git_repo_root)
|
||||
.await
|
||||
.context("git repo root task panicked")??;
|
||||
|
|
@ -79,7 +76,7 @@ draft = true
|
|||
let green = console::Style::new().green();
|
||||
let bold = console::Style::new().bold();
|
||||
let dim = console::Style::new().dim();
|
||||
if cli.output.format != OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format != OutputFormat::Json {
|
||||
fabro_util::printerr!(
|
||||
printer,
|
||||
" {} {}",
|
||||
|
|
@ -112,7 +109,7 @@ draft = true
|
|||
)
|
||||
.with_context(|| format!("failed to write {}", dot_path.display()))?;
|
||||
created.push(".fabro/workflows/hello/workflow.fabro".to_string());
|
||||
if cli.output.format != OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format != OutputFormat::Json {
|
||||
fabro_util::printerr!(
|
||||
printer,
|
||||
" {} {}",
|
||||
|
|
@ -129,7 +126,7 @@ draft = true
|
|||
)
|
||||
.with_context(|| format!("failed to write {}", toml_path.display()))?;
|
||||
created.push(".fabro/workflows/hello/workflow.toml".to_string());
|
||||
if cli.output.format != OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format != OutputFormat::Json {
|
||||
fabro_util::printerr!(
|
||||
printer,
|
||||
" {} {}",
|
||||
|
|
@ -138,7 +135,7 @@ draft = true
|
|||
);
|
||||
}
|
||||
|
||||
if cli.output.format != OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format != OutputFormat::Json {
|
||||
fabro_util::printerr!(
|
||||
printer,
|
||||
"\n{} Run a workflow with:\n\n {}",
|
||||
|
|
@ -150,18 +147,15 @@ draft = true
|
|||
);
|
||||
}
|
||||
|
||||
if cli.output.format != OutputFormat::Json {
|
||||
check_github_app_installation(&args.target, cli_layer, printer).await;
|
||||
if base_ctx.user_settings().cli.output.format != OutputFormat::Json {
|
||||
check_github_app_installation(&args.target, base_ctx).await;
|
||||
}
|
||||
|
||||
Ok(created)
|
||||
}
|
||||
|
||||
async fn check_github_app_installation(
|
||||
target: &ServerTargetArgs,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) {
|
||||
async fn check_github_app_installation(target: &ServerTargetArgs, base_ctx: &CommandContext) {
|
||||
let printer = base_ctx.printer();
|
||||
// Get the git remote origin URL
|
||||
let output = match TokioCommand::new("git")
|
||||
.args(["remote", "get-url", "origin"])
|
||||
|
|
@ -199,7 +193,7 @@ async fn check_github_app_installation(
|
|||
return; // Not a GitHub repo — skip silently
|
||||
};
|
||||
|
||||
let ctx = match CommandContext::for_target(target, printer, cli_layer) {
|
||||
let ctx = match base_ctx.with_target(target) {
|
||||
Ok(ctx) => ctx,
|
||||
Err(err) => {
|
||||
fabro_util::printerr!(
|
||||
|
|
|
|||
|
|
@ -2,30 +2,24 @@ pub(crate) mod deinit;
|
|||
pub(crate) mod init;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
|
||||
use crate::args::{RepoCommand, RepoNamespace};
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::print_json_pretty;
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: RepoNamespace,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: RepoNamespace, base_ctx: &CommandContext) -> Result<()> {
|
||||
match ns.command {
|
||||
RepoCommand::Init(args) => {
|
||||
let created = init::run_init(&args, cli, cli_layer, printer).await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
let created = init::run_init(&args, base_ctx).await?;
|
||||
if base_ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({ "created": created }))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
RepoCommand::Deinit => {
|
||||
let removed = deinit::run_deinit(cli, printer)?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
let removed = deinit::run_deinit(base_ctx)?;
|
||||
if base_ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({ "removed": removed }))?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat, OutputVerbosity};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::{OutputFormat, OutputVerbosity};
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
use crate::args::RunArgs;
|
||||
|
|
@ -9,16 +7,13 @@ use crate::command_context::CommandContext;
|
|||
use crate::shared::print_json_pretty;
|
||||
use crate::user_config::load_settings_with_storage_dir;
|
||||
|
||||
pub(crate) async fn execute(
|
||||
mut args: RunArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn execute(mut args: RunArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let printer = base_ctx.printer();
|
||||
let ctx = base_ctx.with_target(&args.target)?;
|
||||
let cli_defaults = load_settings_with_storage_dir(None)?;
|
||||
args.verbose = args.verbose || cli.output.verbosity == OutputVerbosity::Verbose;
|
||||
args.verbose =
|
||||
args.verbose || ctx.user_settings().cli.output.verbosity == OutputVerbosity::Verbose;
|
||||
|
||||
let quiet = args.detach;
|
||||
let prevent_idle_sleep = ctx.user_settings().cli.exec.prevent_idle_sleep;
|
||||
|
|
@ -50,7 +45,7 @@ pub(crate) async fn execute(
|
|||
let client = ctx.server().await?;
|
||||
super::start::start_run_with_client(&client, &created_run.run_id, false).await?;
|
||||
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
let json = ctx.user_settings().cli.output.format == OutputFormat::Json;
|
||||
if args.detach {
|
||||
if json {
|
||||
print_json_pretty(&serde_json::json!({ "run_id": created_run.run_id }))?;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use tokio::fs;
|
||||
use tracing::{debug, info};
|
||||
|
||||
|
|
@ -26,12 +24,7 @@ enum CopyDirection {
|
|||
},
|
||||
}
|
||||
|
||||
pub(crate) async fn cp_command(
|
||||
args: CpArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn cp_command(args: CpArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let direction = parse_direction(&args.src, &args.dst)?;
|
||||
|
||||
match direction {
|
||||
|
|
@ -41,7 +34,7 @@ pub(crate) async fn cp_command(
|
|||
local_path,
|
||||
} => {
|
||||
let (client, run_id) =
|
||||
resolve_client_and_run_id(&args.server, &run_prefix, cli_layer, printer).await?;
|
||||
resolve_client_and_run_id(base_ctx, &args.server, &run_prefix).await?;
|
||||
|
||||
let file_count = if args.recursive {
|
||||
Some(download_recursive(&client, &run_id, &remote_path, &local_path).await?)
|
||||
|
|
@ -51,7 +44,7 @@ pub(crate) async fn cp_command(
|
|||
None
|
||||
};
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
let mut value = serde_json::json!({
|
||||
"direction": "download",
|
||||
"recursive": args.recursive,
|
||||
|
|
@ -72,7 +65,7 @@ pub(crate) async fn cp_command(
|
|||
remote_path,
|
||||
} => {
|
||||
let (client, run_id) =
|
||||
resolve_client_and_run_id(&args.server, &run_prefix, cli_layer, printer).await?;
|
||||
resolve_client_and_run_id(base_ctx, &args.server, &run_prefix).await?;
|
||||
|
||||
let file_count = if args.recursive {
|
||||
Some(upload_recursive(&client, &run_id, &local_path, &remote_path).await?)
|
||||
|
|
@ -82,7 +75,7 @@ pub(crate) async fn cp_command(
|
|||
None
|
||||
};
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if base_ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
let mut value = serde_json::json!({
|
||||
"direction": "upload",
|
||||
"recursive": args.recursive,
|
||||
|
|
@ -124,12 +117,11 @@ fn parse_direction(src: &str, dst: &str) -> Result<CopyDirection> {
|
|||
}
|
||||
|
||||
async fn resolve_client_and_run_id(
|
||||
base_ctx: &CommandContext,
|
||||
server: &ServerTargetArgs,
|
||||
run_prefix: &str,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<(Client, fabro_types::RunId)> {
|
||||
let ctx = CommandContext::for_target(server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(run_prefix).await?.run_id;
|
||||
Ok((client.clone_for_reuse(), run_id))
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@
|
|||
use std::io::{self, IsTerminal, Write};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::args::DiffArgs;
|
||||
|
|
@ -20,21 +18,16 @@ use crate::command_context::CommandContext;
|
|||
use crate::server_client::RunProjection;
|
||||
use crate::shared::print_json_pretty;
|
||||
|
||||
pub(crate) async fn run(
|
||||
args: DiffArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn run(args: DiffArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
info!(run_id = %args.run, "Showing diff");
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
|
||||
let patch = resolve_diff(&state, &args)?;
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
let value = serde_json::json!({
|
||||
"run_id": run_id,
|
||||
"node": args.node,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use anyhow::{Context, Result};
|
||||
use fabro_checkpoint::git::Store;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::terminal::Styles;
|
||||
use fabro_workflow::operations::{ForkRunInput, RewindTarget, build_timeline_or_rebuild, fork};
|
||||
use git2::Repository;
|
||||
|
|
@ -13,15 +11,10 @@ use crate::commands::store::rebuild::rebuild_run_store;
|
|||
use crate::shared::print_json_pretty;
|
||||
use crate::shared::repo::ensure_matching_repo_origin;
|
||||
|
||||
pub(crate) async fn run(
|
||||
args: &ForkArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn run(args: &ForkArgs, styles: &Styles, base_ctx: &CommandContext) -> Result<()> {
|
||||
let repo = Repository::discover(".").context("not in a git repository")?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let printer = base_ctx.printer();
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run_id).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
|
|
@ -34,7 +27,7 @@ pub(crate) async fn run(
|
|||
let timeline = build_timeline_or_rebuild(&store, Some(&run_store), &run_id).await?;
|
||||
|
||||
if args.list {
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&super::rewind::timeline_entries_json(&timeline))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -56,7 +49,7 @@ pub(crate) async fn run(
|
|||
let run_id_string = run_id.to_string();
|
||||
let new_run_id_string = new_run_id.to_string();
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
let target = args.target.clone().unwrap_or_else(|| "latest".to_string());
|
||||
print_json_pretty(&serde_json::json!({
|
||||
"source_run_id": run_id_string,
|
||||
|
|
|
|||
|
|
@ -13,10 +13,8 @@ use std::time::Duration;
|
|||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use chrono::{DateTime, Utc};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::json::normalize_json_value;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::redact::redact_jsonl_line;
|
||||
use fabro_util::terminal::Styles;
|
||||
use tokio::time;
|
||||
|
|
@ -29,14 +27,8 @@ use crate::shared::format_usd_micros;
|
|||
|
||||
const FOLLOW_TERMINAL_GRACE: Duration = Duration::from_millis(500);
|
||||
|
||||
pub(crate) async fn run(
|
||||
args: &LogsArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
pub(crate) async fn run(args: &LogsArgs, styles: &Styles, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
info!(run_id = %run_id, "Showing logs");
|
||||
|
|
@ -60,7 +52,7 @@ pub(crate) async fn run(
|
|||
let stdout = io::stdout();
|
||||
let is_tty = stdout.is_terminal();
|
||||
let mut out = stdout.lock();
|
||||
let pretty = args.pretty && cli.output.format != OutputFormat::Json;
|
||||
let pretty = args.pretty && ctx.user_settings().cli.output.format != OutputFormat::Json;
|
||||
|
||||
for line in &filtered {
|
||||
if pretty {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat, OutputVerbosity};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::{OutputFormat, OutputVerbosity};
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
use crate::args::{AttachArgs, RunCommands, RunWorkerArgs, StartArgs};
|
||||
|
|
@ -27,19 +25,15 @@ pub(crate) mod ssh;
|
|||
pub(crate) mod start;
|
||||
pub(crate) mod wait;
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
cmd: RunCommands,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
_process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(cmd: RunCommands, base_ctx: &CommandContext) -> Result<()> {
|
||||
let printer = base_ctx.printer();
|
||||
|
||||
match cmd {
|
||||
RunCommands::Run(args) => Box::pin(command::execute(args, cli, cli_layer, printer)).await,
|
||||
RunCommands::Run(args) => Box::pin(command::execute(args, base_ctx)).await,
|
||||
RunCommands::Create(args) => {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let cli_defaults = load_settings_with_storage_dir(None)?;
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&args.target)?;
|
||||
let created_run = Box::pin(create::create_run(
|
||||
&ctx,
|
||||
&args,
|
||||
|
|
@ -49,7 +43,7 @@ pub(crate) async fn dispatch(
|
|||
printer,
|
||||
))
|
||||
.await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({ "run_id": created_run.run_id }))?;
|
||||
} else {
|
||||
fabro_util::printout!(printer, "{}", created_run.run_id);
|
||||
|
|
@ -57,26 +51,27 @@ pub(crate) async fn dispatch(
|
|||
Ok(())
|
||||
}
|
||||
RunCommands::Start(StartArgs { server, run }) => {
|
||||
let ctx = CommandContext::for_target(&server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&run).await?.run_id;
|
||||
start::start_run_with_client(client.as_ref(), &run_id, false).await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({ "run_id": run_id }))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
RunCommands::Attach(AttachArgs { server, run }) => {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let ctx = CommandContext::for_target(&server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&run).await?.run_id;
|
||||
let json = ctx.user_settings().cli.output.format == OutputFormat::Json;
|
||||
let exit_code = Box::pin(attach::attach_run_with_client(
|
||||
client.as_ref(),
|
||||
&run_id,
|
||||
false,
|
||||
styles,
|
||||
cli.output.format == OutputFormat::Json,
|
||||
json,
|
||||
ctx.user_settings().cli.output.verbosity == OutputVerbosity::Verbose,
|
||||
printer,
|
||||
))
|
||||
|
|
@ -104,34 +99,31 @@ pub(crate) async fn dispatch(
|
|||
)
|
||||
.await
|
||||
}
|
||||
RunCommands::Diff(args) => diff::run(args, cli, cli_layer, printer).await,
|
||||
RunCommands::Diff(args) => diff::run(args, base_ctx).await,
|
||||
RunCommands::Logs(args) => {
|
||||
let styles = Styles::detect_stdout();
|
||||
logs::run(&args, &styles, cli, cli_layer, printer).await
|
||||
logs::run(&args, &styles, base_ctx).await
|
||||
}
|
||||
RunCommands::Resume(args) => {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
#[cfg(feature = "sleep_inhibitor")]
|
||||
let _sleep_guard = {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
crate::sleep_inhibitor::guard(ctx.user_settings().cli.exec.prevent_idle_sleep)
|
||||
};
|
||||
Box::pin(resume::resume_command(
|
||||
args, styles, cli, cli_layer, printer,
|
||||
))
|
||||
.await
|
||||
Box::pin(resume::resume_command(args, styles, base_ctx)).await
|
||||
}
|
||||
RunCommands::Rewind(args) => {
|
||||
let styles = Styles::detect_stderr();
|
||||
Box::pin(rewind::run(&args, &styles, cli, cli_layer, printer)).await
|
||||
Box::pin(rewind::run(&args, &styles, base_ctx)).await
|
||||
}
|
||||
RunCommands::Fork(args) => {
|
||||
let styles = Styles::detect_stderr();
|
||||
Box::pin(fork::run(&args, &styles, cli, cli_layer, printer)).await
|
||||
Box::pin(fork::run(&args, &styles, base_ctx)).await
|
||||
}
|
||||
RunCommands::Wait(args) => {
|
||||
let styles = Styles::detect_stderr();
|
||||
wait::run(&args, &styles, cli, cli_layer, printer).await
|
||||
wait::run(&args, &styles, base_ctx).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
use anyhow::{Context, Result};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use tracing::info;
|
||||
|
||||
use crate::args::PreviewArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::print_json_pretty;
|
||||
|
||||
pub(crate) async fn run(
|
||||
args: PreviewArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
pub(crate) async fn run(args: PreviewArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
let expires_in_secs =
|
||||
|
|
@ -31,7 +24,7 @@ pub(crate) async fn run(
|
|||
|
||||
info!(run_id = %args.run, port = args.port, "Generating preview URL");
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
match response.token {
|
||||
Some(token) => {
|
||||
print_json_pretty(&serde_json::json!({ "url": response.url, "token": token }))?;
|
||||
|
|
@ -56,7 +49,7 @@ pub(crate) async fn run(
|
|||
}
|
||||
}
|
||||
|
||||
if args.open && !process_local_json {
|
||||
if args.open && !ctx.explicit_json_requested() {
|
||||
#[expect(
|
||||
clippy::disallowed_methods,
|
||||
reason = "Preview URL opening is a fire-and-forget OS integration, not a Tokio-managed child process."
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat, OutputVerbosity};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::{OutputFormat, OutputVerbosity};
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
use crate::args::ResumeArgs;
|
||||
|
|
@ -15,17 +13,16 @@ use crate::shared::print_json_pretty;
|
|||
pub(crate) async fn resume_command(
|
||||
args: ResumeArgs,
|
||||
styles: &'static Styles,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let printer = base_ctx.printer();
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
|
||||
super::start::start_run_with_client(client.as_ref(), &run_id, true).await?;
|
||||
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
let json = ctx.user_settings().cli.output.format == OutputFormat::Json;
|
||||
if args.detach {
|
||||
if json {
|
||||
print_json_pretty(&serde_json::json!({ "run_id": run_id }))?;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ use cli_table::format::{Border, Separator};
|
|||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||
use fabro_checkpoint::git::Store;
|
||||
use fabro_types::run_event::{CheckpointCompletedProps, RunRewoundProps, RunSubmittedProps};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_types::{EventBody, RunEvent};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -33,12 +32,11 @@ pub(crate) struct TimelineEntryJson {
|
|||
pub(crate) async fn run(
|
||||
args: &RewindArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<()> {
|
||||
let repo = Repository::discover(".").context("not in a git repository")?;
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let printer = base_ctx.printer();
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run_id).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
|
|
@ -54,7 +52,7 @@ pub(crate) async fn run(
|
|||
let timeline = build_timeline_or_rebuild(&store, Some(&run_store), &run_id).await?;
|
||||
|
||||
if args.list || args.target.is_none() {
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&timeline_entries_json(&timeline))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -79,7 +77,7 @@ pub(crate) async fn run(
|
|||
|
||||
let run_id_string = run_id.to_string();
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({
|
||||
"run_id": run_id_string,
|
||||
"target": target_arg,
|
||||
|
|
|
|||
|
|
@ -1,25 +1,18 @@
|
|||
use anyhow::{Result, bail};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use tracing::info;
|
||||
|
||||
use crate::args::{SshArgs, require_no_json_override};
|
||||
use crate::args::SshArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::print_json_pretty;
|
||||
|
||||
pub(crate) async fn run(
|
||||
args: SshArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
if process_local_json && !args.print {
|
||||
require_no_json_override(process_local_json)?;
|
||||
pub(crate) async fn run(args: SshArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
if base_ctx.explicit_json_requested() && !args.print {
|
||||
base_ctx.require_no_json_override()?;
|
||||
}
|
||||
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
let ssh = client.create_run_ssh_access(&run_id, args.ttl).await?;
|
||||
|
|
@ -27,7 +20,7 @@ pub(crate) async fn run(
|
|||
info!(run_id = %args.run, ttl_minutes = args.ttl, "Creating SSH access");
|
||||
|
||||
if args.print {
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({ "command": ssh.command }))?;
|
||||
} else {
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ use std::io::Write;
|
|||
|
||||
use anyhow::{Result, bail};
|
||||
use fabro_types::RunId;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
use fabro_workflow::records::Conclusion;
|
||||
|
|
@ -24,14 +23,9 @@ use crate::args::WaitArgs;
|
|||
use crate::command_context::CommandContext;
|
||||
use crate::shared::{format_duration_ms, format_usd_micros, run_status_kind};
|
||||
|
||||
pub(crate) async fn run(
|
||||
args: &WaitArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
pub(crate) async fn run(args: &WaitArgs, styles: &Styles, base_ctx: &CommandContext) -> Result<()> {
|
||||
let printer = base_ctx.printer();
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
info!(run_id = %run_id, "Waiting for run to complete");
|
||||
|
|
@ -63,7 +57,7 @@ pub(crate) async fn run(
|
|||
|
||||
let conclusion = client.get_run_state(&run_id).await?.conclusion;
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
let json_value = build_json_output(final_status, &run_id, conclusion.as_ref());
|
||||
let mut out = std::io::stdout().lock();
|
||||
serde_json::to_writer_pretty(&mut out, &json_value)?;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use anyhow::{Result, bail};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
|
||||
use super::short_run_id;
|
||||
use crate::args::{RunsArchiveArgs, RunsUnarchiveArgs};
|
||||
|
|
@ -11,34 +9,30 @@ use crate::shared::print_json_pretty;
|
|||
|
||||
pub(crate) async fn archive_command(
|
||||
args: &RunsArchiveArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
run_bulk(
|
||||
Action::Archive,
|
||||
&args.runs,
|
||||
ctx.server().await?.as_ref(),
|
||||
cli,
|
||||
printer,
|
||||
ctx.user_settings().cli.output.format == OutputFormat::Json,
|
||||
ctx.printer(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn unarchive_command(
|
||||
args: &RunsUnarchiveArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
run_bulk(
|
||||
Action::Unarchive,
|
||||
&args.runs,
|
||||
ctx.server().await?.as_ref(),
|
||||
cli,
|
||||
printer,
|
||||
ctx.user_settings().cli.output.format == OutputFormat::Json,
|
||||
ctx.printer(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
@ -66,10 +60,9 @@ async fn run_bulk(
|
|||
action: Action,
|
||||
identifiers: &[String],
|
||||
client: &server_client::Client,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
json: bool,
|
||||
printer: fabro_util::printer::Printer,
|
||||
) -> Result<()> {
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
let mut had_errors = false;
|
||||
let mut changed = Vec::new();
|
||||
let mut errors = Vec::new();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_workflow::run_status::RunStatus;
|
||||
use serde::Serialize;
|
||||
|
||||
|
|
@ -20,8 +18,9 @@ pub(crate) struct InspectOutput {
|
|||
pub sandbox: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
pub(crate) async fn run(args: &InspectArgs, cli_layer: &CliLayer, printer: Printer) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
pub(crate) async fn run(args: &InspectArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let client = ctx.server().await?;
|
||||
let run = ServerRunSummaryInfo::from_summary(client.resolve_run(&args.run).await?);
|
||||
let run_id = run.run_id();
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ use anyhow::Result;
|
|||
use chrono::Utc;
|
||||
use cli_table::format::{Border, Separator};
|
||||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::terminal::Styles;
|
||||
use fabro_util::text::strip_goal_decoration;
|
||||
use fabro_workflow::run_status::RunStatus;
|
||||
|
|
@ -20,11 +18,10 @@ use crate::shared::{color_if, format_duration_ms, run_status_kind, tilde_path};
|
|||
pub(crate) async fn list_command(
|
||||
args: &RunsListArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||
let label_filters = parse_label_filters(&args.filter.label);
|
||||
let filtered = filter_server_runs(
|
||||
|
|
@ -35,7 +32,7 @@ pub(crate) async fn list_command(
|
|||
!args.all,
|
||||
);
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
let json_rows: Vec<_> = filtered
|
||||
.iter()
|
||||
.map(|run| {
|
||||
|
|
|
|||
|
|
@ -1,35 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
use crate::args::RunsCommands;
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
pub(crate) mod archive;
|
||||
pub(crate) mod inspect;
|
||||
pub(crate) mod list;
|
||||
pub(crate) mod rm;
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
cmd: RunsCommands,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(cmd: RunsCommands, base_ctx: &CommandContext) -> Result<()> {
|
||||
match cmd {
|
||||
RunsCommands::Ps(args) => {
|
||||
let styles = Styles::detect_stdout();
|
||||
list::list_command(&args, &styles, cli, cli_layer, printer).await
|
||||
}
|
||||
RunsCommands::Rm(args) => rm::remove_command(&args, cli, cli_layer, printer).await,
|
||||
RunsCommands::Inspect(args) => inspect::run(&args, cli_layer, printer).await,
|
||||
RunsCommands::Archive(args) => {
|
||||
archive::archive_command(&args, cli, cli_layer, printer).await
|
||||
}
|
||||
RunsCommands::Unarchive(args) => {
|
||||
archive::unarchive_command(&args, cli, cli_layer, printer).await
|
||||
list::list_command(&args, &styles, base_ctx).await
|
||||
}
|
||||
RunsCommands::Rm(args) => rm::remove_command(&args, base_ctx).await,
|
||||
RunsCommands::Inspect(args) => inspect::run(&args, base_ctx).await,
|
||||
RunsCommands::Archive(args) => archive::archive_command(&args, base_ctx).await,
|
||||
RunsCommands::Unarchive(args) => archive::unarchive_command(&args, base_ctx).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use anyhow::{Result, bail};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
|
||||
use super::short_run_id;
|
||||
use crate::args::RunsRemoveArgs;
|
||||
|
|
@ -9,23 +7,23 @@ use crate::command_context::CommandContext;
|
|||
use crate::server_client;
|
||||
use crate::shared::print_json_pretty;
|
||||
|
||||
pub(crate) async fn remove_command(
|
||||
args: &RunsRemoveArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
remove_from(args, ctx.server().await?.as_ref(), cli, printer).await
|
||||
pub(crate) async fn remove_command(args: &RunsRemoveArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
remove_from(
|
||||
args,
|
||||
ctx.server().await?.as_ref(),
|
||||
ctx.user_settings().cli.output.format == OutputFormat::Json,
|
||||
ctx.printer(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn remove_from(
|
||||
args: &RunsRemoveArgs,
|
||||
client: &server_client::Client,
|
||||
cli: &CliNamespace,
|
||||
printer: Printer,
|
||||
json: bool,
|
||||
printer: fabro_util::printer::Printer,
|
||||
) -> Result<()> {
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
let mut had_errors = false;
|
||||
let mut removed = Vec::new();
|
||||
let mut errors = Vec::new();
|
||||
|
|
|
|||
|
|
@ -1,24 +1,12 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::SandboxCommand;
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
command: SandboxCommand,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
process_local_json: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(command: SandboxCommand, base_ctx: &CommandContext) -> Result<()> {
|
||||
match command {
|
||||
SandboxCommand::Cp(args) => super::run::cp::cp_command(args, cli, cli_layer, printer).await,
|
||||
SandboxCommand::Preview(args) => {
|
||||
super::run::preview::run(args, cli, cli_layer, process_local_json, printer).await
|
||||
}
|
||||
SandboxCommand::Ssh(args) => {
|
||||
super::run::ssh::run(args, cli, cli_layer, process_local_json, printer).await
|
||||
}
|
||||
SandboxCommand::Cp(args) => super::run::cp::cp_command(args, base_ctx).await,
|
||||
SandboxCommand::Preview(args) => super::run::preview::run(args, base_ctx).await,
|
||||
SandboxCommand::Ssh(args) => super::run::ssh::run(args, base_ctx).await,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ use anyhow::Result;
|
|||
use chrono::{DateTime, Utc};
|
||||
use cli_table::format::{Border, Separator};
|
||||
use cli_table::{Cell, CellStruct, Style, Table};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
|
|
@ -25,11 +23,11 @@ fn format_age(dt: DateTime<Utc>, now: DateTime<Utc>) -> String {
|
|||
pub(super) async fn list_command(
|
||||
client: &Client,
|
||||
_args: &SecretListArgs,
|
||||
cli: &CliNamespace,
|
||||
json_output: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let secrets = client.list_secrets().await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if json_output {
|
||||
print_json_pretty(&secrets)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,19 @@ mod rm;
|
|||
mod set;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::{SecretCommand, SecretNamespace};
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: SecretNamespace,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&ns.target, printer, cli_layer)?;
|
||||
pub(crate) async fn dispatch(ns: SecretNamespace, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&ns.target)?;
|
||||
let server = ctx.server().await?;
|
||||
let json_output =
|
||||
ctx.user_settings().cli.output.format == fabro_types::settings::cli::OutputFormat::Json;
|
||||
let printer = ctx.printer();
|
||||
match ns.command {
|
||||
SecretCommand::List(args) => list::list_command(&server, &args, cli, printer).await,
|
||||
SecretCommand::Rm(args) => rm::rm_command(&server, &args, cli, printer).await,
|
||||
SecretCommand::Set(args) => set::set_command(&server, &args, cli, printer).await,
|
||||
SecretCommand::List(args) => list::list_command(&server, &args, json_output, printer).await,
|
||||
SecretCommand::Rm(args) => rm::rm_command(&server, &args, json_output, printer).await,
|
||||
SecretCommand::Set(args) => set::set_command(&server, &args, json_output, printer).await,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::SecretRmArgs;
|
||||
|
|
@ -10,11 +8,11 @@ use crate::shared::print_json_pretty;
|
|||
pub(super) async fn rm_command(
|
||||
client: &Client,
|
||||
args: &SecretRmArgs,
|
||||
cli: &CliNamespace,
|
||||
json_output: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
client.delete_secret_by_name(&args.key).await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if json_output {
|
||||
print_json_pretty(&serde_json::json!({ "key": args.key }))?;
|
||||
} else {
|
||||
fabro_util::printerr!(printer, "Removed {}", args.key);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ use std::io::{IsTerminal, Read as _};
|
|||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use fabro_api::types;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
|
|
@ -60,7 +58,7 @@ async fn resolve_value(args: &SecretSetArgs) -> Result<String> {
|
|||
pub(super) async fn set_command(
|
||||
client: &Client,
|
||||
args: &SecretSetArgs,
|
||||
cli: &CliNamespace,
|
||||
json_output: bool,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let value = resolve_value(args).await?;
|
||||
|
|
@ -72,7 +70,7 @@ pub(super) async fn set_command(
|
|||
description: args.description.clone(),
|
||||
})
|
||||
.await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if json_output {
|
||||
print_json_pretty(&meta)?;
|
||||
} else {
|
||||
fabro_util::printerr!(printer, "Set {}", meta.name);
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ use bytes::Bytes;
|
|||
#[cfg(test)]
|
||||
use fabro_store::{ArtifactStore, RunDatabase};
|
||||
use fabro_store::{EventEnvelope, RunProjection, StageId};
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_types::{RunBlobId, RunId};
|
||||
use fabro_util::printer::Printer;
|
||||
use futures::future::BoxFuture;
|
||||
#[cfg(test)]
|
||||
use serde::de::DeserializeOwned;
|
||||
|
|
@ -26,19 +24,15 @@ use crate::command_context::CommandContext;
|
|||
use crate::server_client::Client;
|
||||
use crate::shared::{absolute_or_current, print_json_pretty};
|
||||
|
||||
pub(crate) async fn dump_command(
|
||||
args: &StoreDumpArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
|
||||
pub(crate) async fn dump_command(args: &StoreDumpArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_target(&args.server)?;
|
||||
let printer = ctx.printer();
|
||||
let client = ctx.server().await?;
|
||||
let run_id = client.resolve_run(&args.run).await?.run_id;
|
||||
let state = client.get_run_state(&run_id).await?;
|
||||
let source = ServerDumpSource::new(client.as_ref(), &run_id);
|
||||
let file_count = export_run_from_source(&source, &state, &args.output).await?;
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({
|
||||
"run_id": run_id,
|
||||
"output_dir": absolute_or_current(&args.output),
|
||||
|
|
|
|||
|
|
@ -3,19 +3,12 @@ pub(crate) mod rebuild;
|
|||
mod run_export;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
|
||||
use crate::args::{StoreCommand, StoreNamespace};
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: StoreNamespace,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: StoreNamespace, base_ctx: &CommandContext) -> Result<()> {
|
||||
match ns.command {
|
||||
StoreCommand::Dump(args) => dump::dump_command(&args, cli, cli_layer, printer).await,
|
||||
StoreCommand::Dump(args) => dump::dump_command(&args, base_ctx).await,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,23 +3,16 @@ use chrono::{DateTime, Utc};
|
|||
use cli_table::format::{Border, Justify, Separator};
|
||||
use cli_table::{Cell, CellStruct, Style, Table};
|
||||
use fabro_api::types;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
|
||||
use crate::args::DfArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::{format_size, print_json_pretty};
|
||||
|
||||
pub(super) async fn df_command(
|
||||
args: &DfArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
|
||||
pub(super) async fn df_command(args: &DfArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_connection(&args.connection)?;
|
||||
let server = ctx.server().await?;
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
let json = ctx.user_settings().cli.output.format == OutputFormat::Json;
|
||||
|
||||
let (output, storage_dir) = if json {
|
||||
(server.get_system_disk_usage(args.verbose).await?, None)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use fabro_client::sse;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use futures::StreamExt;
|
||||
|
||||
use crate::args::SystemEventsArgs;
|
||||
|
|
@ -10,16 +8,14 @@ use crate::command_context::CommandContext;
|
|||
|
||||
pub(super) async fn events_command(
|
||||
args: &SystemEventsArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
|
||||
let ctx = base_ctx.with_connection(&args.connection)?;
|
||||
let server = ctx.server().await?;
|
||||
let mut stream = server.attach_events(&args.run_ids).await?;
|
||||
let mut pending = Vec::new();
|
||||
|
||||
let json = cli.output.format == OutputFormat::Json;
|
||||
let json = ctx.user_settings().cli.output.format == OutputFormat::Json;
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk.map_err(|err| anyhow::anyhow!("{err}"))?;
|
||||
pending.extend_from_slice(&chunk);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,16 @@
|
|||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
|
||||
use crate::args::SystemInfoArgs;
|
||||
use crate::command_context::CommandContext;
|
||||
use crate::shared::print_json_pretty;
|
||||
|
||||
pub(super) async fn info_command(
|
||||
args: &SystemInfoArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
|
||||
pub(super) async fn info_command(args: &SystemInfoArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_connection(&args.connection)?;
|
||||
let server = ctx.server().await?;
|
||||
let response = server.get_system_info().await?;
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&response)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,23 +4,16 @@ mod info;
|
|||
mod prune;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::CliLayer;
|
||||
use fabro_util::printer::Printer;
|
||||
pub(crate) use prune::parse_duration;
|
||||
|
||||
use crate::args::{SystemCommand, SystemNamespace};
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
pub(crate) async fn dispatch(
|
||||
ns: SystemNamespace,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: SystemNamespace, base_ctx: &CommandContext) -> Result<()> {
|
||||
match ns.command {
|
||||
SystemCommand::Info(args) => info::info_command(&args, cli, cli_layer, printer).await,
|
||||
SystemCommand::Prune(args) => prune::prune_command(&args, cli, cli_layer, printer).await,
|
||||
SystemCommand::Df(args) => df::df_command(&args, cli, cli_layer, printer).await,
|
||||
SystemCommand::Events(args) => events::events_command(&args, cli, cli_layer, printer).await,
|
||||
SystemCommand::Info(args) => info::info_command(&args, base_ctx).await,
|
||||
SystemCommand::Prune(args) => prune::prune_command(&args, base_ctx).await,
|
||||
SystemCommand::Df(args) => df::df_command(&args, base_ctx).await,
|
||||
SystemCommand::Events(args) => events::events_command(&args, base_ctx).await,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ use std::collections::HashMap;
|
|||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use fabro_api::types;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
use tracing::{debug, info};
|
||||
|
||||
|
|
@ -11,13 +10,9 @@ use crate::args::RunsPruneArgs;
|
|||
use crate::command_context::CommandContext;
|
||||
use crate::shared::{format_size, print_json_pretty};
|
||||
|
||||
pub(super) async fn prune_command(
|
||||
args: &RunsPruneArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
|
||||
pub(super) async fn prune_command(args: &RunsPruneArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let ctx = base_ctx.with_connection(&args.connection)?;
|
||||
let printer = ctx.printer();
|
||||
let server = ctx.server().await?;
|
||||
let response = server
|
||||
.prune_runs(types::PruneRunsRequest {
|
||||
|
|
@ -29,7 +24,11 @@ pub(super) async fn prune_command(
|
|||
workflow: args.filter.workflow.clone(),
|
||||
})
|
||||
.await?;
|
||||
prune_from(&response, cli.output.format == OutputFormat::Json, printer)
|
||||
prune_from(
|
||||
&response,
|
||||
ctx.user_settings().cli.output.format == OutputFormat::Json,
|
||||
printer,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_duration(s: &str) -> Result<chrono::Duration> {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use anyhow::bail;
|
||||
use fabro_config::load::load_settings_user;
|
||||
use fabro_config::user::active_settings_path;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::{CliNamespace, SettingsLayer};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_types::settings::SettingsLayer;
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
use crate::args::ValidateArgs;
|
||||
|
|
@ -15,11 +14,10 @@ use crate::shared::{print_diagnostics, print_json_pretty, relative_path};
|
|||
pub(crate) async fn run(
|
||||
args: &ValidateArgs,
|
||||
styles: &Styles,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
base_ctx: &CommandContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let printer = base_ctx.printer();
|
||||
let ctx = base_ctx.with_target(&args.target)?;
|
||||
let built = build_run_manifest(ManifestBuildInput {
|
||||
workflow: args.workflow.clone(),
|
||||
cwd: ctx.cwd().to_path_buf(),
|
||||
|
|
@ -33,7 +31,7 @@ pub(crate) async fn run(
|
|||
let response = client.run_preflight(built.manifest).await?;
|
||||
let diagnostics = api_diagnostics_to_local(&response.workflow.diagnostics);
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&serde_json::json!({
|
||||
"workflow_name": response.workflow.name,
|
||||
"nodes": response.workflow.nodes,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@
|
|||
use std::io::IsTerminal;
|
||||
|
||||
use anyhow::Result;
|
||||
use fabro_types::settings::CliNamespace;
|
||||
use fabro_types::settings::cli::{CliLayer, OutputFormat};
|
||||
use fabro_types::settings::cli::OutputFormat;
|
||||
use fabro_util::printer::Printer;
|
||||
use serde_json::{Map, Value, json};
|
||||
|
||||
|
|
@ -16,14 +15,10 @@ use crate::command_context::CommandContext;
|
|||
use crate::shared::print_json_pretty;
|
||||
use crate::user_config::{self, ServerTarget};
|
||||
|
||||
pub(crate) async fn version_command(
|
||||
args: &VersionArgs,
|
||||
cli: &CliNamespace,
|
||||
cli_layer: &CliLayer,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
pub(crate) async fn version_command(args: &VersionArgs, base_ctx: &CommandContext) -> Result<()> {
|
||||
let client = client_info();
|
||||
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
|
||||
let printer = base_ctx.printer();
|
||||
let ctx = base_ctx.with_target(&args.target)?;
|
||||
let server_target = user_config::resolve_server_target(&args.target, ctx.machine_settings())?;
|
||||
let server_address = format_server_target(&server_target);
|
||||
let server_info = match ctx.server().await {
|
||||
|
|
@ -49,7 +44,7 @@ pub(crate) async fn version_command(
|
|||
},
|
||||
};
|
||||
|
||||
if cli.output.format == OutputFormat::Json {
|
||||
if ctx.user_settings().cli.output.format == OutputFormat::Json {
|
||||
print_json_pretty(&json_output(&client, &server_info))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ use clap::{CommandFactory, Parser};
|
|||
use fabro_config::merge::combine_files;
|
||||
use fabro_telemetry::{git, panic as tel_panic, sanitize, sender};
|
||||
use fabro_types::settings::SettingsLayer;
|
||||
use fabro_types::settings::cli::OutputVerbosity;
|
||||
use fabro_util::exit::ExitClass;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -38,6 +37,8 @@ use fabro_util::{browser, exit};
|
|||
use rustls::crypto::ring::default_provider;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::command_context::CommandContext;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "fabro", version, long_version = LONG_VERSION)]
|
||||
struct Cli {
|
||||
|
|
@ -210,51 +211,46 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
};
|
||||
|
||||
let result = Box::pin(async move {
|
||||
let build_base_ctx = || CommandContext::base(printer, &cli_layer, process_local_json);
|
||||
|
||||
match *command {
|
||||
Commands::Exec(args) => commands::exec::execute(args, &cli_settings, printer).await?,
|
||||
Commands::RunCmd(cmd) => {
|
||||
Box::pin(commands::run::dispatch(
|
||||
cmd,
|
||||
&cli_settings,
|
||||
&cli_layer,
|
||||
process_local_json,
|
||||
printer,
|
||||
))
|
||||
.await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
Box::pin(commands::run::dispatch(cmd, &base_ctx)).await?;
|
||||
}
|
||||
Commands::Preflight(args) => {
|
||||
commands::preflight::execute(args, &cli_settings, &cli_layer, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::preflight::execute(args, &base_ctx).await?;
|
||||
}
|
||||
Commands::Validate(args) => {
|
||||
let styles = Styles::detect_stderr();
|
||||
commands::validate::run(&args, &styles, &cli_settings, &cli_layer, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::validate::run(&args, &styles, &base_ctx).await?;
|
||||
}
|
||||
Commands::Graph(args) => {
|
||||
let styles = Styles::detect_stderr();
|
||||
commands::graph::run(
|
||||
&args,
|
||||
&styles,
|
||||
&cli_settings,
|
||||
&cli_layer,
|
||||
process_local_json,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::graph::run(&args, &styles, &base_ctx).await?;
|
||||
}
|
||||
Commands::Parse(args) => {
|
||||
commands::parse::run(&args, &cli_settings, printer)?;
|
||||
}
|
||||
Commands::Artifact(ns) => {
|
||||
commands::artifact::dispatch(ns, &cli_settings, &cli_layer, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::artifact::dispatch(ns, &base_ctx).await?;
|
||||
}
|
||||
Commands::Store(ns) => {
|
||||
commands::store::dispatch(ns, &cli_settings, &cli_layer, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::store::dispatch(ns, &base_ctx).await?;
|
||||
}
|
||||
Commands::RunsCmd(cmd) => {
|
||||
commands::runs::dispatch(cmd, &cli_settings, &cli_layer, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::runs::dispatch(cmd, &base_ctx).await?;
|
||||
}
|
||||
Commands::Model { command } => {
|
||||
commands::model::execute(command, &cli_settings, &cli_layer, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::model::execute(command, &base_ctx).await?;
|
||||
}
|
||||
Commands::Server(ns) => {
|
||||
Box::pin(commands::server::dispatch(
|
||||
|
|
@ -266,21 +262,13 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
.await?;
|
||||
}
|
||||
Commands::Doctor(args) => {
|
||||
let verbose =
|
||||
args.verbose || cli_settings.output.verbosity == OutputVerbosity::Verbose;
|
||||
let exit_code = Box::pin(commands::doctor::run_doctor(
|
||||
&args,
|
||||
verbose,
|
||||
&cli_settings,
|
||||
&cli_layer,
|
||||
printer,
|
||||
))
|
||||
.await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
let exit_code = Box::pin(commands::doctor::run_doctor(&args, &base_ctx)).await?;
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
Commands::Version(args) => {
|
||||
commands::version::version_command(&args, &cli_settings, &cli_layer, printer)
|
||||
.await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::version::version_command(&args, &base_ctx).await?;
|
||||
}
|
||||
Commands::Discord => {
|
||||
if process_local_json {
|
||||
|
|
@ -301,7 +289,8 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
}
|
||||
}
|
||||
Commands::Repo(ns) => {
|
||||
commands::repo::dispatch(ns, &cli_settings, &cli_layer, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::repo::dispatch(ns, &base_ctx).await?;
|
||||
}
|
||||
Commands::Install { args, command } => {
|
||||
Box::pin(commands::install::execute(
|
||||
|
|
@ -318,48 +307,36 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
commands::uninstall::run_uninstall(&args, &cli_settings, printer).await?;
|
||||
}
|
||||
Commands::Auth(ns) => {
|
||||
commands::auth::dispatch(ns, &cli_layer, process_local_json, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::auth::dispatch(ns, &base_ctx).await?;
|
||||
}
|
||||
Commands::Pr(ns) => {
|
||||
Box::pin(commands::pr::dispatch(
|
||||
ns,
|
||||
&cli_settings,
|
||||
&cli_layer,
|
||||
printer,
|
||||
))
|
||||
.await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
Box::pin(commands::pr::dispatch(ns, &base_ctx)).await?;
|
||||
}
|
||||
Commands::Secret(ns) => {
|
||||
commands::secret::dispatch(ns, &cli_settings, &cli_layer, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::secret::dispatch(ns, &base_ctx).await?;
|
||||
}
|
||||
Commands::Settings(args) => {
|
||||
Box::pin(commands::config::execute(
|
||||
&args,
|
||||
&cli_settings,
|
||||
&cli_layer,
|
||||
printer,
|
||||
))
|
||||
.await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
Box::pin(commands::config::execute(&args, &base_ctx)).await?;
|
||||
}
|
||||
Commands::Workflow(ns) => commands::workflow::dispatch(ns, &cli_settings, printer)?,
|
||||
Commands::Upgrade(args) => {
|
||||
commands::upgrade::run_upgrade(args, &cli_settings, printer).await?;
|
||||
}
|
||||
Commands::Provider(ns) => {
|
||||
commands::provider::dispatch(ns, &cli_layer, process_local_json, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::provider::dispatch(ns, &base_ctx).await?;
|
||||
}
|
||||
Commands::Sandbox { command } => {
|
||||
commands::sandbox::dispatch(
|
||||
command,
|
||||
&cli_settings,
|
||||
&cli_layer,
|
||||
process_local_json,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::sandbox::dispatch(command, &base_ctx).await?;
|
||||
}
|
||||
Commands::System(ns) => {
|
||||
commands::system::dispatch(ns, &cli_settings, &cli_layer, printer).await?;
|
||||
let base_ctx = build_base_ctx()?;
|
||||
commands::system::dispatch(ns, &base_ctx).await?;
|
||||
}
|
||||
Commands::Completion(args) => {
|
||||
require_no_json_override(process_local_json)?;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue