bynk/cli.rs
1//! The `bynk` driver command-line interface.
2//!
3//! Kept thin: the driver shells `bynkc` and the Node toolchain. v0.46 ships a
4//! single subcommand, `doctor`; `new` and `dev` join it in later slices.
5
6use std::path::PathBuf;
7
8use clap::{Parser, Subcommand, ValueEnum};
9
10use crate::doctor::{Capability, DoctorOptions};
11use crate::report::Format;
12
13#[derive(Parser, Debug)]
14#[command(name = "bynk", version, about = "The Bynk driver", long_about = None)]
15pub struct Cli {
16 #[command(subcommand)]
17 pub command: Command,
18}
19
20#[derive(Subcommand, Debug)]
21pub enum Command {
22 /// Check whether your machine is ready to compile, test, and deploy Bynk —
23 /// and print the exact remedy for anything missing.
24 ///
25 /// Bare `bynk doctor` is informational: it surveys every capability and
26 /// exits 0 unless `bynkc` itself is unusable. `--only <capability>` gates on
27 /// one capability (exits non-zero if its tools are missing); `--strict`
28 /// turns every warning into a failure, for CI.
29 Doctor {
30 /// Project directory to inspect (for project-local `node_modules/.bin`
31 /// resolution). Defaults to the current directory.
32 #[arg(default_value = ".")]
33 input: PathBuf,
34 /// Scope the check — and the exit code — to one capability.
35 #[arg(long, value_enum)]
36 only: Option<CapabilityArg>,
37 /// Treat every warning (optional gaps, npx provisionability, minor
38 /// version skew) as a failure. For an all-green CI gate.
39 #[arg(long)]
40 strict: bool,
41 /// Output format. `human` (default) is a grouped table; `short` and
42 /// `json` are the stable scriptable surface.
43 #[arg(long, value_enum, default_value = "human")]
44 format: FormatArg,
45 },
46 /// Build the project and serve it locally with `wrangler dev` — one step in
47 /// place of the manual compile + `cd` + `wrangler dev` recipe.
48 ///
49 /// Compiles into a managed `.bynk/dev/` build dir, picks the worker to serve
50 /// (one context → served; `--context` to choose; ambiguous → lists them),
51 /// and runs `wrangler dev` from inside it in local mode (Miniflare) — no
52 /// namespace provisioning needed. Everything after `--` is forwarded to
53 /// `wrangler dev` verbatim.
54 Dev {
55 /// Project directory to serve from (anywhere inside the project; the
56 /// root is found by walking up for `bynk.toml`). Defaults to `.`.
57 #[arg(default_value = ".")]
58 path: PathBuf,
59 /// Which context's worker to serve, for multi-context projects.
60 #[arg(long)]
61 context: Option<String>,
62 /// Serve with the V8 inspector enabled (slice 3, ADR 0104): `wrangler dev`
63 /// starts with `--inspector-port` so a JavaScript debugger can attach.
64 /// Breakpoints set in `.bynk` sources resolve through the emitted source
65 /// maps, composed into the worker bundle. Prints the inspector URL on start.
66 #[arg(long)]
67 inspect: bool,
68 /// Inspector port for `--inspect` (default 9229).
69 #[arg(long, default_value_t = 9229)]
70 inspect_port: u16,
71 /// Arguments after `--`, forwarded to `wrangler dev` (e.g. `-- --port 8788`).
72 #[arg(last = true)]
73 wrangler_args: Vec<String>,
74 },
75 /// Scaffold a new project: a complete, runnable single-context HTTP service
76 /// you can serve immediately with `bynk dev`.
77 ///
78 /// Writes a `bynk.toml`, a `.gitignore`, and `src/<name>.bynk` into a new
79 /// directory. Pure offline file-writing — it shells nothing and needs no
80 /// toolchain, so you can run it before `bynkc`, Node, or `wrangler` are
81 /// installed. The project name defaults to the target directory's final
82 /// component; `--name` overrides it and must be a legal Bynk identifier.
83 New {
84 /// Directory to create for the new project (e.g. `hello` or `./hello`).
85 path: PathBuf,
86 /// Project name / context identifier. Defaults to PATH's final
87 /// component; must be a legal Bynk identifier (a letter followed by
88 /// letters, digits, or underscores).
89 #[arg(long)]
90 name: Option<String>,
91 },
92}
93
94/// `--only` selector. Mirrors [`Capability`] minus the internal distinctions.
95#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
96pub enum CapabilityArg {
97 /// `bynkc` compile / check / fmt.
98 Compile,
99 /// `bynk test` — Node + tsc|tsx.
100 Test,
101 /// dev / deploy to Cloudflare — Node + wrangler.
102 Deploy,
103 /// Editor support — bynkc-lsp.
104 Editor,
105 /// Build Bynk from source — a Rust toolchain.
106 Build,
107}
108
109impl From<CapabilityArg> for Capability {
110 fn from(a: CapabilityArg) -> Self {
111 match a {
112 CapabilityArg::Compile => Capability::Compile,
113 CapabilityArg::Test => Capability::Test,
114 CapabilityArg::Deploy => Capability::Deploy,
115 CapabilityArg::Editor => Capability::Editor,
116 CapabilityArg::Build => Capability::BuildFromSource,
117 }
118 }
119}
120
121#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, ValueEnum)]
122pub enum FormatArg {
123 #[default]
124 Human,
125 Short,
126 Json,
127}
128
129impl From<FormatArg> for Format {
130 fn from(f: FormatArg) -> Self {
131 match f {
132 FormatArg::Human => Format::Human,
133 FormatArg::Short => Format::Short,
134 FormatArg::Json => Format::Json,
135 }
136 }
137}
138
139/// Build the [`DoctorOptions`] from the parsed flags.
140pub fn doctor_options(only: Option<CapabilityArg>, strict: bool) -> DoctorOptions {
141 DoctorOptions {
142 only: only.map(Into::into),
143 strict,
144 }
145}