planCli(args: string[]): Promise<CliPlan>
Works out what an invocation amounts to, without performing it.
A complete invocation becomes a run plan; anything short of one becomes a
print plan carrying the guidance for the missing word — on stderr with
exit 1, or on stdout with exit 0 under --help. Guidance that reports a
failure rather than a missing word stays on stderr either way.
An argument the parser could not use is refused before anything else is read,
--help and --version included: an unrecognised flag, a blank word, a word
past the third slot. The first two shift what the package is — the flag by
swallowing the next word, the blank by having been dropped — and the third
used to disappear without a word. Answering confidently about a package
nobody named, or about bytes that never arrived, is the defect class this
whole module is built to avoid.
A local package argument is inspected before any of that, and a directory is
refused — see directoryGuide. Everything downstream may therefore
assume the specifier names one module, which is what lets discovery and
import() be handed the same string.
Every path validates the coder through discovery first, including a complete
one: see chooseCoder for why the shortcut had to go.
An empty command line asks for a package, listing or no listing
An empty command line asks for a package, listing or no listing
import { assertEquals, assertStringIncludes } from "@std/assert"; import { stub } from "@std/testing/mock"; import { planCli } from "./cli.ts"; using _offline = stub( globalThis, "fetch", () => Promise.reject(new TypeError("offline")), ); const plan = await planCli([]); assertEquals(plan.kind, "print"); if (plan.kind === "print") { assertEquals(plan.stream, "stderr"); assertEquals(plan.code, 1); assertStringIncludes(plan.text, "NEXT <package>"); assertStringIncludes(plan.text, "TRY\n binstruct png"); }
A complete invocation resolves its shorthand and runs
A complete invocation resolves its shorthand and runs
import { assertEquals } from "@std/assert"; import { planCli } from "./cli.ts"; const plan = await planCli(["png", "pngFile", "decode"]); assertEquals(plan.kind, "run"); if (plan.kind === "run") { assertEquals(plan.specifier, "jsr:@binstruct/png"); assertEquals(plan.coder, "pngFile"); assertEquals(plan.command, "decode"); assertEquals(plan.notices, ["package: png → jsr:@binstruct/png"]); }
A local module runs under the specifier it was named by
A local module runs under the specifier it was named by
import { assertEquals } from "@std/assert"; import { planCli } from "./cli.ts"; const module = import.meta.resolve("../arp/mod.ts"); const plan = await planCli([module, "decode"]); assertEquals(plan.kind, "run"); if (plan.kind === "run") { assertEquals(plan.specifier, module); assertEquals(plan.coder, "arpData"); }
A directory is refused, and the modules in it are offered instead
A directory is refused, and the modules in it are offered instead
import { assertEquals, assertStringIncludes } from "@std/assert"; import { planCli } from "./cli.ts"; const plan = await planCli([import.meta.resolve("../arp/"), "decode"]); assertEquals(plan.kind, "print"); if (plan.kind === "print") { assertEquals(plan.code, 1); assertStringIncludes(plan.text, "names a directory"); assertStringIncludes(plan.text, "mod.ts"); }
An unknown flag is named, on stderr, and no package is guessed at
An unknown flag is named, on stderr, and no package is guessed at
import { assertEquals, assertStringIncludes } from "@std/assert"; import { planCli } from "./cli.ts"; const plan = await planCli(["--format", "json", "png"]); assertEquals(plan.kind, "print"); if (plan.kind === "print") { assertEquals(plan.stream, "stderr"); assertEquals(plan.code, 1); assertStringIncludes(plan.text, "unknown option: --format"); assertEquals(plan.text.includes("jsr:@binstruct/png"), false); }
Promise<CliPlan>
What to do