Skip to content

Recipes

Themed deploy script

Spinner while uploading, progress bar per file, final success/failure box.

import { spinner, progress, box, log, colors } from "@mongez/copper";
async function deploy(files: string[]) {
const auth = spinner({ text: "Authenticating…" }).start();
try {
await login();
auth.succeed("Authenticated");
} catch (err) {
auth.fail("Auth failed");
log.error(err);
return;
}
const bar = progress({ total: files.length, color: "lime" });
for (const file of files) {
try {
await upload(file);
} catch (err) {
bar.stop();
log.error(`Failed on ${file}:`, err);
console.log(box(`Deploy aborted at ${file}`, { borderColor: "red" }));
return;
}
bar.tick();
}
bar.done();
console.log(box(`${files.length} files deployed`, {
borderStyle: "round",
borderColor: "green",
padding: 1,
}));
}

Refusing to color piped output

Most CLIs should respect NO_COLOR automatically — @mongez/copper already does. If you need to enforce uncolored output (e.g. when emitting structured logs to a file), build a forced-off instance:

import { createColors } from "@mongez/copper";
const plain = createColors(false);
fs.appendFileSync("deploy.log", plain.gray("[" + new Date().toISOString() + "] ") + "done\n");

Honoring a --quiet flag

import { createLogger } from "@mongez/copper";
const quiet = process.argv.includes("--quiet");
const log = createLogger({
level: quiet ? "warn" : "debug",
stream: process.stderr,
});
log.info("verbose status"); // hidden in --quiet
log.warn("worth showing"); // always visible

Capturing CLI output for tests

stripAnsi removes color/cursor codes; pair with a custom stream:

import { createLogger, stripAnsi } from "@mongez/copper";
function captureLogs(run: (log: ReturnType<typeof createLogger>) => void) {
const lines: string[] = [];
const stream = {
write(c: string) { lines.push(c); return true; },
} as unknown as NodeJS.WritableStream;
run(createLogger({ stream }));
return lines.map(stripAnsi);
}
const out = captureLogs(log => {
log.info("started");
log.success("done");
});
// ["ℹ started\n", "✔ done\n"]

Surface a docs URL the user can click to get unstuck:

import { colors, link, symbols } from "@mongez/copper";
function reportError(code: string, message: string) {
const url = `https://errors.example.com/${code}`;
console.error(`${colors.red.bold(symbols.cross)} ${message}`);
console.error(` ${colors.gray("See:")} ${link(code, url)}`);
}

In terminals that don’t support OSC-8 the user still sees code (https://errors.example.com/...).

import { box, colors, link } from "@mongez/copper";
console.log(box([
colors.cyan.bold("my-cli") + " " + colors.gray("v" + pkg.version),
"",
`Docs: ${link("read here", "https://example.com/docs")}`,
].join("\n"), {
borderStyle: "double",
borderColor: "cyan",
padding: 1,
align: "center",
}));

Walking a directory with progress

Pair with @mongez/fs:

import { listAll } from "@mongez/fs";
import { spinner, progress, log } from "@mongez/copper";
const sp = spinner({ text: "Scanning…" }).start();
const files = await listAll("./src");
sp.succeed(`Found ${files.length} files`);
const bar = progress({ total: files.length });
for (const f of files) {
await processFile(f);
bar.tick();
}
bar.done();
log.success("Done");

Error-only stderr, info to stdout

import { createLogger } from "@mongez/copper";
const out = createLogger({ stream: process.stdout, level: "info" });
const err = createLogger({ stream: process.stderr, level: "warn" });
out.info("Building…");
err.warn("Deprecated flag --legacy");
err.error("Build failed");

Pipe to grep or jq: cli 2>/dev/null | head keeps the warnings off the success pipeline.