Bridge · #429
A command that actually runs
The intended CLI is a small script that reads the same JSON endpoints the integration pages print. Because it is a script rather than a package, the honest version is the file: download it, run it with Node, and point --base at any deployment.
Commands
- node motif-cli.mjs listevery component with kind, size and both scores
- node motif-cli.mjs list --kind animatedthe same table filtered by the kind field
- node motif-cli.mjs tokenstokens as CSS custom properties
- node motif-cli.mjs tokens --format jsonthe DTCG file, verbatim
- node motif-cli.mjs badge tilt-cardMarkdown for that asset's badge
- node motif-cli.mjs --helpthe usage text below
Not on npm — say it plainly
- npx motif add tilt-card installs nothing, because there is no registry entry. The page documents what that command would do rather than printing it as if it works.
- “Add” itself needs something to add: the catalog stores metadata, not component source, so the most a real command could do today is write the token file or print a badge.
- The script needs network access to the deployment it points at, and it says which URL it read when a request fails, rather than exiting silently.
The script (3.5 KB, complete)
#!/usr/bin/env node
/*
* Motif UI CLI — served from the site, run with node.
*
* node motif-cli.mjs list [--kind animated]
* node motif-cli.mjs tokens [--format css|json]
* node motif-cli.mjs badge <slug>
* node motif-cli.mjs --help
*
* --base <url> points it at a deployment (default http://localhost:3139).
* It is not published to npm: there is no registry entry, so `npx motif`
* installs nothing. This file is the command.
*/
const args = process.argv.slice(2);
const baseIndex = args.indexOf("--base");
const base = (baseIndex >= 0 ? args[baseIndex + 1] : "http://localhost:3139").replace(/\/+$/, "");
const kindIndex = args.indexOf("--kind");
const kind = kindIndex >= 0 ? args[kindIndex + 1] : null;
const formatIndex = args.indexOf("--format");
const format = formatIndex >= 0 ? args[formatIndex + 1] : "css";
const positional = args.filter((a) => !a.startsWith("--") && a !== base && a !== kind && a !== format);
const command = positional[0] || "help";
const operand = positional[1];
async function json(path) {
const url = base + path;
let res;
try {
res = await fetch(url);
} catch (err) {
// Node reports "fetch failed" without the URL, which tells the reader
// nothing. The command pr...
throw new Error("could not reach " + url + " (" + (err && err.cause ? err.cause.code || err.cause.message : err.message) + ") — is the deployment running, and is --base right?");
}
if (!res.ok) throw new Error(url + " → HTTP " + res.status);
return res.json();
}
function help() {
console.log(`Motif UI CLI
list [--kind <kind>] components from /api/exports/catalog.json
tokens [--format css|json] design tokens from /api/exports/tokens.json
badge <slug> Markdown for that asset's score badge
--base <url> deployment to read (default ${base})
--help
Not published to npm — this file is the command.`);
}
async function list() {
const data = await json("/api/exports/catalog.json");
const rows = data.components.filter((c) => !kind || c.kind === kind);
for (const c of rows) {
console.log([c.slug.padEnd(26), c.kind.padEnd(9), (c.bundleKb + " KB").padStart(8), "a11y " + c.a11yScore, "Q " + c.qualityScore].join(" "));
}
console.log("\n" + rows.length + " of " + data.components.length + " components" + (kind ? " (kind: " + kind + ")" : ""));
}
async function tokens() {
const data = await json("/api/exports/tokens.json");
if (format === "json") { console.log(JSON.stringify(data, null, 2)); return; }
for (const [name, token] of Object.entries(data.color)) console.log(" --color-" + name + ": " + token.$value + ";");
for (const [name, token] of Object.entries(data.radius)) console.log(" --radius-" + name + ": " + token.$value + ";");
}
async function badge(slug) {
if (!slug) { console.error("badge needs a slug: node motif-cli.mjs badge tilt-card"); process.exit(1); }
const data = await json("/api/exports/catalog.json");
const asset = data.components.find((c) => c.slug === slug);
if (!asset) { console.error("no component called " + slug); process.exit(1); }
console.log("");
console.log("\n# " + asset.title + " — a11y " + asset.a11yScore + ", quality " + asset.qualityScore + ", " + asset.bundleKb + " KB, " + asset.license);
}
const run = { list, tokens, badge, help }[command] ?? help;
Promise.resolve(command === "badge" ? badge(operand) : run()).catch((err) => { console.error(err.message); process.exit(1); });
It uses fetch and process.argv only — no dependencies, so there is nothing to install before running it.