skip to the page

@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