// The two bridge-generation namespaces of the `mtsc` CLI:
//
// mtsc bridge TypeScript -> MoonBit (was the `ts2mbt` binary)
// mtsc pkg MoonBit -> TypeScript (was the `mbt2ts` binary)
//
// They live in the root `mizchi/ts` package rather than in
// `src/cmd/mtsc/` for two reasons. The unified flows they dispatch to
// (`run_ts_to_mbt_unified_cli`, `run_mbt_to_ts_unified_cli`,
// `run_mbt_to_ts_pkg_cli`) and every `emit_*` entry point are already
// here, so a dispatcher in the cmd package would be a layer of
// indirection over its own dependency; and the help surface stays
// assertable from `src/main_wbtest.mbt`, which is what kept
// `ts2mbt_help_lines` / `mbt2ts_help_lines` public before the merge.
//
// Both entry points take `(args, start)` rather than reading
// `@env.args()` themselves. That is what makes them reachable from a
// subcommand position: `mtsc bridge decl x.d.ts` hands them `start = 2`
// where the old binaries passed `1`.
///|
/// Every verb of `mtsc bridge`, one line each, in help order.
///
/// `all` is the verb the `ts2mbt` binary spelled `bridge`. Under a
/// `bridge` namespace that spelling reads as `mtsc bridge bridge`, so the
/// verb is renamed and the old spelling kept as an accepted alias — a
/// rename that breaks a script is not a consolidation.
pub fn bridge_help_lines() -> Array[String] {
[
" --input --out [--module-spec ] [--diagnostics ] [--strict] Generate a complete build-backed MoonBit bridge scaffold from a TypeScript entrypoint",
" generate [--package-json ] [--out ] Generate bridges for every dependency / devDependency listed in package.json",
" vendor [--module-spec ] [--out ] Resolve a single from node_modules and write its bridge sub-package",
" decl [out] Emit MoonBit declare stubs from a TypeScript entrypoint",
" ffi [ffi-out] [bridge-out] Emit MoonBit JS FFI stubs and ESM bridge from a TypeScript entrypoint",
" all [decl-out] [ffi-out] [bridge-out] Emit a full MoonBit bridge bundle from a TypeScript entrypoint",
" package Emit a generated MoonBit bridge package directory",
" package-validated Emit a bridge package with opt-in structural runtime validators",
" scaffold Emit a MoonBit bridge scaffold from a TypeScript entrypoint",
]
}
///|
/// Every verb of `mtsc pkg`, one line each, in help order.
///
/// `npm` is the flow the `mbt2ts` binary selected with a bare `--pkg`
/// flag. A flag that picks a whole mode is a verb wearing a flag's
/// spelling, and under a namespace it would have read as
/// `mtsc pkg --pkg`; `--pkg` stays an accepted alias.
pub fn pkg_help_lines() -> Array[String] {
[
" npm [--out ] [--strict] [--no-facade] [--import-rewrites ] Build current MoonBit library packages and write an npm-publishable package to ./npm",
" --input --out [--diagnostics ] [--strict] [--no-facade] [--import-rewrites ] Generate a complete build-backed TypeScript package from a MoonBit pkg.generated.mbti",
" decl [out] Emit TypeScript declarations directly from a MoonBit .mbti interface",
" link-config [out] Emit MoonBit link.js.exports config from a .mbti interface",
" package [import-rewrite-json] Emit a recursive TypeScript declaration package from a root .mbti interface",
" scaffold [import-rewrite-json] Emit build-backed JS/TypeScript scaffold from a MoonBit package interface",
" facade-scaffold [import-rewrite-json] Emit build-backed JS/TypeScript scaffold plus facade wrappers for omitted local methods",
" normalize [out] Normalize MoonBit-generated TypeScript declarations into clearer .d.ts output",
]
}
///|
fn bridge_print_usage() -> Unit {
println(
"Usage: mtsc bridge [--input --out ] [ args...]",
)
println("")
println("Generate MoonBit bridge artifacts from a TypeScript entrypoint.")
println("")
println(
"Recommended: use the unified `--input/--out` flow for build-backed scaffolds.",
)
println("Verbs expose individual generation stages.")
println("")
println("Verbs:")
for line in bridge_help_lines() {
println(line)
}
}
///|
fn pkg_print_usage() -> Unit {
println(
"Usage: mtsc pkg npm [options] | [--input --out ] [ args...]",
)
println("")
println(
"Generate TypeScript declarations and JS-backed packages from a MoonBit interface.",
)
println("")
println(
"Recommended: use the unified `--input/--out` flow for build-backed scaffolds.",
)
println("Verbs expose individual generation stages.")
println("")
println("Verbs:")
for line in pkg_help_lines() {
println(line)
}
}
///|
/// Run a verb handler against the parsed positional args. Centralises the
/// per-arm `parse args -> dispatch -> propagate exit code` pipeline so
/// each arm shrinks to a single line.
///
/// `namespace_` is the `bridge` / `pkg` prefix, so the `--help` hint names
/// the command the user actually typed.
async fn bridge_cli_dispatch(
args : Array[String],
start : Int,
namespace_ : String,
verb : String,
min_args : Int,
usage : String,
print_help : () -> Unit,
handler : async (Array[String]) -> Bool,
) -> Unit {
match
cli_parse_subcommand_args(
args,
start,
min_args,
usage,
help_hint="mtsc \{namespace_} \{verb} --help",
) {
HelpRequested => print_help()
Args(positionals) => if !handler(positionals) { cli_fail("mtsc") }
}
}
///|
/// Parse `vendor`'s positional + flag arguments into
/// `(pkg_spec, module_spec_override, vendor_root_override)`.
fn bridge_parse_vendor_args(
args : Array[String],
start : Int,
) -> (String?, String?, String?) {
let mut pkg_spec : String? = None
let mut module_spec : String? = None
let mut out_dir : String? = None
let mut idx = start
while idx < args.length() {
let arg = args[idx]
if arg == "--module-spec" || arg == "--runtime-module" {
if idx + 1 >= args.length() {
return (None, None, None)
}
module_spec = Some(args[idx + 1])
idx += 2
continue
}
if arg == "--out" || arg == "-o" {
if idx + 1 >= args.length() {
return (None, None, None)
}
out_dir = Some(args[idx + 1])
idx += 2
continue
}
if arg.has_prefix("--module-spec=") {
module_spec = Some(arg["--module-spec=".length():arg.length()].to_owned())
idx += 1
continue
}
if arg.has_prefix("--out=") {
out_dir = Some(arg["--out=".length():arg.length()].to_owned())
idx += 1
continue
}
if arg.has_prefix("-") {
idx += 1
continue
}
if pkg_spec is None {
pkg_spec = Some(arg)
}
idx += 1
}
(pkg_spec, module_spec, out_dir)
}
///|
async fn bridge_run_vendor(args : Array[String], start : Int) -> Unit {
if start < args.length() && cli_subcommand_is_help(args[start]) {
bridge_print_usage()
return
}
let (pkg_spec, module_spec, out_dir) = bridge_parse_vendor_args(args, start)
let pkg = match pkg_spec {
Some(p) => p
None => {
cli_bail(
"mtsc bridge vendor: expected [--module-spec ] [--out ]",
)
return
}
}
if !ts2mbt_vendor_package(
pkg,
module_spec_override=module_spec,
vendor_root_override=out_dir,
) {
cli_fail("mtsc")
}
}
///|
/// Parse `generate`'s flag arguments into `(package_json_path?, vendor_root?)`.
fn bridge_parse_generate_args(
args : Array[String],
start : Int,
) -> (String?, String?) {
let mut pkg_json : String? = None
let mut out_dir : String? = None
let mut idx = start
while idx < args.length() {
let arg = args[idx]
if arg == "--package-json" {
if idx + 1 >= args.length() {
return (None, None)
}
pkg_json = Some(args[idx + 1])
idx += 2
continue
}
if arg == "--out" || arg == "-o" {
if idx + 1 >= args.length() {
return (None, None)
}
out_dir = Some(args[idx + 1])
idx += 2
continue
}
if arg.has_prefix("--package-json=") {
pkg_json = Some(arg["--package-json=".length():arg.length()].to_owned())
idx += 1
continue
}
if arg.has_prefix("--out=") {
out_dir = Some(arg["--out=".length():arg.length()].to_owned())
idx += 1
continue
}
idx += 1
}
(pkg_json, out_dir)
}
///|
async fn bridge_run_generate(args : Array[String], start : Int) -> Unit {
if start < args.length() && cli_subcommand_is_help(args[start]) {
bridge_print_usage()
return
}
let (pkg_json, out_dir) = bridge_parse_generate_args(args, start)
if !ts2mbt_generate_from_package_json(
package_json_path_override=pkg_json,
vendor_root_override=out_dir,
) {
cli_fail("mtsc")
}
}
///|
/// `mtsc bridge …` — the TypeScript -> MoonBit direction.
///
/// `start` is the index of the verb (or of the first `--input`-style flag
/// for the unified flow), so `mtsc bridge decl x.d.ts` passes `2`.
pub async fn run_bridge_cli(args : Array[String], start : Int) -> Unit {
if start >= args.length() {
bridge_print_usage()
return
}
if cli_token_is_version(args[start]) {
cli_print_version("mtsc")
return
}
if args[start].has_prefix("--") || args[start] == "-h" {
if args[start] == "--help" || args[start] == "-h" {
bridge_print_usage()
return
}
if !run_ts_to_mbt_unified_cli(args, start, print_help=bridge_print_usage) {
cli_fail("mtsc")
}
return
}
let verb = args[start]
let rest = start + 1
match verb {
"scaffold-from-input" =>
if !run_ts_to_mbt_unified_cli(args, rest, print_help=bridge_print_usage) {
cli_fail("mtsc")
}
"decl" =>
bridge_cli_dispatch(
args,
rest,
"bridge",
"decl",
1,
"mtsc bridge decl: expected [out]",
bridge_print_usage,
async fn(p) {
emit_moonbit_decl(
p[0],
if p.length() >= 2 {
Some(p[1])
} else {
None
},
)
},
)
"ffi" =>
bridge_cli_dispatch(
args,
rest,
"bridge",
"ffi",
2,
"mtsc bridge ffi: expected [ffi-out] [bridge-out]",
bridge_print_usage,
async fn(p) {
emit_moonbit_js_ffi(
p[0],
p[1],
if p.length() >= 3 {
Some(p[2])
} else {
None
},
if p.length() >= 4 {
Some(p[3])
} else {
None
},
)
},
)
// `bridge` is the spelling the `ts2mbt` binary used; keeping it as an
// alias makes `mtsc bridge bridge …` work, so the script rewrite is a
// prefix substitution rather than a per-call-site decision.
"all" | "bridge" =>
bridge_cli_dispatch(
args,
rest,
"bridge",
"all",
2,
"mtsc bridge all: expected [decl-out] [ffi-out] [bridge-out]",
bridge_print_usage,
async fn(p) {
emit_moonbit_bridge(
p[0],
p[1],
if p.length() >= 3 {
Some(p[2])
} else {
None
},
if p.length() >= 4 {
Some(p[3])
} else {
None
},
if p.length() >= 5 {
Some(p[4])
} else {
None
},
)
},
)
"package" =>
bridge_cli_dispatch(
args,
rest,
"bridge",
"package",
3,
"mtsc bridge package: expected ",
bridge_print_usage,
async fn(p) { emit_moonbit_bridge_package(p[0], p[1], p[2]) },
)
"package-validated" =>
bridge_cli_dispatch(
args,
rest,
"bridge",
"package-validated",
3,
"mtsc bridge package-validated: expected ",
bridge_print_usage,
async fn(p) {
emit_moonbit_bridge_package(p[0], p[1], p[2], runtime_validation=true)
},
)
"scaffold" =>
bridge_cli_dispatch(
args,
rest,
"bridge",
"scaffold",
3,
"mtsc bridge scaffold: expected ",
bridge_print_usage,
async fn(p) { emit_moonbit_scaffold_from_ts(p[0], p[1], p[2]) },
)
"vendor" => bridge_run_vendor(args, rest)
"generate" => bridge_run_generate(args, rest)
"--help" | "-h" | "help" => bridge_print_usage()
"--version" | "-V" => cli_print_version("mtsc")
_ => {
println("Unknown `mtsc bridge` verb: \{verb}")
bridge_print_usage()
cli_fail("mtsc")
}
}
}
///|
/// `mtsc pkg …` — the MoonBit -> TypeScript direction.
///
/// `start` is the index of the verb, so `mtsc pkg decl x.mbti` passes `2`.
pub async fn run_pkg_cli(args : Array[String], start : Int) -> Unit {
if start >= args.length() {
pkg_print_usage()
return
}
if cli_token_is_version(args[start]) {
cli_print_version("mtsc")
return
}
// `npm` is the verb; `--pkg` is the flag the `mbt2ts` binary used to
// select the same flow, kept so existing invocations keep working.
if args[start] == "npm" || args[start] == "--pkg" {
if !run_mbt_to_ts_pkg_cli(args, start + 1, print_help=pkg_print_usage) {
cli_fail("mtsc")
}
return
}
if args[start].has_prefix("--") || args[start] == "-h" {
if args[start] == "--help" || args[start] == "-h" {
pkg_print_usage()
return
}
if !run_mbt_to_ts_unified_cli(args, start, print_help=pkg_print_usage) {
cli_fail("mtsc")
}
return
}
let verb = args[start]
let rest = start + 1
match verb {
"generate" | "scaffold-from-input" =>
if !run_mbt_to_ts_unified_cli(args, rest, print_help=pkg_print_usage) {
cli_fail("mtsc")
}
"decl" =>
bridge_cli_dispatch(
args,
rest,
"pkg",
"decl",
1,
"mtsc pkg decl: expected [out]",
pkg_print_usage,
async fn(p) {
emit_typescript_decl_from_mbti(
p[0],
if p.length() >= 2 {
Some(p[1])
} else {
None
},
)
},
)
"link-config" =>
bridge_cli_dispatch(
args,
rest,
"pkg",
"link-config",
1,
"mtsc pkg link-config: expected [out]",
pkg_print_usage,
async fn(p) {
emit_js_link_config_from_mbti(
p[0],
if p.length() >= 2 {
Some(p[1])
} else {
None
},
)
},
)
"package" =>
bridge_cli_dispatch(
args,
rest,
"pkg",
"package",
2,
"mtsc pkg package: expected [import-rewrite-json]",
pkg_print_usage,
async fn(p) {
emit_typescript_package_from_mbti(
p[0],
p[1],
if p.length() >= 3 {
Some(p[2])
} else {
None
},
)
},
)
"scaffold" =>
bridge_cli_dispatch(
args,
rest,
"pkg",
"scaffold",
2,
"mtsc pkg scaffold: expected [import-rewrite-json]",
pkg_print_usage,
async fn(p) {
emit_typescript_scaffold_from_mbti(
p[0],
p[1],
if p.length() >= 3 {
Some(p[2])
} else {
None
},
)
},
)
"facade-scaffold" =>
bridge_cli_dispatch(
args,
rest,
"pkg",
"facade-scaffold",
2,
"mtsc pkg facade-scaffold: expected [import-rewrite-json]",
pkg_print_usage,
async fn(p) {
emit_typescript_facade_scaffold_from_mbti(
p[0],
p[1],
if p.length() >= 3 {
Some(p[2])
} else {
None
},
)
},
)
"normalize" =>
bridge_cli_dispatch(
args,
rest,
"pkg",
"normalize",
1,
"mtsc pkg normalize: expected [out]",
pkg_print_usage,
async fn(p) {
emit_typescript_decl(
p[0],
if p.length() >= 2 {
Some(p[1])
} else {
None
},
)
},
)
"--help" | "-h" | "help" => pkg_print_usage()
"--version" | "-V" => cli_print_version("mtsc")
_ => {
println("Unknown `mtsc pkg` verb: \{verb}")
pkg_print_usage()
cli_fail("mtsc")
}
}
}