@jam-overture/ loom/ cli
Scaffolding and inspection from a terminal.
Everything below is exported from that import. The names, the signatures and the sentences are read from the package itself rather than written here, so this page says what the copy of Loom in your node_modules says — and it changes in the same pull request the code does.
25 exports, in 5 modules. Generated from ./dist/cli/index.d.ts, which is the declaration file this package publishes for @jam-overture/loom/cli.
Nothing to install first. Everything this import loads arrives with @jam-overture/loom itself.
No import here has everything behind it. @jam-overture/loom/cli publishes 25 of the 1,298 names this package publishes. The other 1,273 are behind one of the 16 other imports, and not one of those imports publishes a single name this one does. The imports do not nest: a name that is not on this page is not a name that does not exist, and the search at the top of the page says which import it comes from.
Reading the arguments
cli/args
Argument parsing, by hand rather than by dependency.
DEFAULT_DIRECTORYvalue
const DEFAULT_DIRECTORY = "loom"
parseArgumentsfunction
const parseArguments: (argv: readonly string[]) => Result<CliCommand, CliError>
Filesystem
cli/filesystem
The CLI's only impure seam.
FileSysteminterface
interface FileSystem {
readonly list: (directory: string) => Promise<Result<readonly string[], string>>;
readonly write: (path: string, contents: string) => Promise<Result<void, string>>;
}nodeFileSystemvalue
const nodeFileSystem: FileSystem
What a command will do
cli/plan
What a command would write, decided before anything is written.
CliCommandtype
type CliCommand = {
readonly kind: "init";
readonly directory: string;
} | {
readonly kind: "add-primitive";
readonly directory: string;
readonly type: string;
} | {
readonly kind: "help";
};CliErrortype
type CliError = {
readonly code: "unknown-command";
readonly given: string;
} | {
readonly code: "missing-argument";
readonly argument: string;
} | {
readonly code: "unexpected-argument";
readonly given: string;
} | {
readonly code: "invalid-primitive-type";
readonly type: string;
} | {
readonly code: "reserved-primitive-type";
readonly type: string;
} | {
readonly code: "framework-namespace";
readonly type: string;
} | {
readonly code: "already-registered";
readonly type: string;
} | {
readonly code: "file-exists";
readonly path: string;
} | {
readonly code: "filesystem-failed";
readonly path: string;
readonly detail: string;
};CliErrorCodetype
type CliErrorCode = CliError["code"];
CLI_ERROR_CODESvalue
Every way a command can refuse, in the order a user meets them.
const CLI_ERROR_CODES: readonly CliErrorCode[]
PlannedFiletype
type PlannedFile = {
readonly path: string;
readonly contents: string;
};WritePlantype
type WritePlan = {
readonly files: readonly PlannedFile[];
/** Lines for the caller to print — what happened, and what to do next. */
readonly notes: readonly string[];
};PRIMITIVES_DIRECTORYvalue
const PRIMITIVES_DIRECTORY = "primitives"
existingPrimitivesfunction
The primitive modules already in the directory, recovered from their names.
const existingPrimitives: (paths: readonly string[]) => readonly PrimitiveNames[]
planCommandfunction
const planCommand: (command: CliCommand, existing: readonly string[]) => Result<WritePlan, CliError>
Running a command
cli/run
Parse, plan, write — in that order, and only writing once the whole plan is known to be safe. A command that would clash with an existing file writes nothing at all rather than leaving a directory half-scaffolded.
CLI_USAGEvalue
const CLI_USAGE = "loom \u2014 scaffolding for the Loom primitive registry\n\nUsage:\n loom init [--dir <directory>]\n loom add primitive <type> [--dir <directory>]\n loom --help\n\nOptions:\n --dir <directory> Where the Loom source lives (default: loom)\n\ninit writes a starter primitive, a generated registry, and a conformance test\ninto <directory>/primitives. add primitive declares one more and\nregenerates the registry from the directory's contents.\n\nA type is dot-namespaced kebab-case: app.card, commerce.product-card.\nloom.* is the framework's own namespace and is refused \u2014 those\nnames belong to the primitives @jam-overture/loom already registers."
CliReporttype
type CliReport = {
readonly written: readonly string[];
readonly notes: readonly string[];
readonly usage: boolean;
};describeCliErrorfunction
const describeCliError: (error: CliError) => string
runClifunction
const runCli: (argv: readonly string[], filesystem: FileSystem) => Promise<Result<CliReport, CliError>>
Templates
cli/templates
What the CLI writes, as pure functions of a name.
PrimitiveNamestype
type PrimitiveNames = {
/** The primitive type, exactly as a tree will address it. */
readonly type: string;
/**
* The module basename, which is the type verbatim: `commerce.product-card.ts`.
*
* Dots survive where a slug would not. Folding `.` to `-` is lossy — nothing
* can tell whether `commerce-product-card` was `commerce.product-card` or
* `commerce-product.card` — and the registry is regenerated from the directory
* listing, so that round trip has to be exact.
*/
readonly module: string;
/** The exported binding: `commerce.product-card` → `commerceProductCard`. */
readonly exportName: string;
};namesForfunction
const namesFor: (type: string) => PrimitiveNames
primitiveModulefunction
const primitiveModule: (names: PrimitiveNames) => string
REGISTRY_MODULEvalue
const REGISTRY_MODULE = "registry.ts"
CONFORMANCE_TEST_MODULEvalue
const CONFORMANCE_TEST_MODULE = "registry.test.ts"
RESERVED_PRIMITIVE_TYPESvalue
Reserved because the registry and its test share the primitives directory, and registry and registry.test are both valid primitive types — so a primitive named either one would be written over the file that registers it.
const RESERVED_PRIMITIVE_TYPES: readonly string[]
registryModulefunction
const registryModule: (primitives: readonly PrimitiveNames[]) => string
conformanceTestfunction
The generated test is the point of the generated scaffold. auditRegistry reports and never enforces, so a deployment only benefits from it if something actually runs it — and a host is far more likely to keep a test that was there from the first commit than to add one later.
const conformanceTest: () => string