function planCli
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.

Examples

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

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

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

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

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);
}

Parameters

args: string[]

Command line arguments

Return Type

Promise<CliPlan>

What to do