spb/spbgit Public MIT
SPB Git — the platform hosting itself
JavaScript 73.9%
CSS 11.7%
Nunjucks 11.6%
Shell 2.7%
1/**2 * ─────────────────────────────────────────────3 * SPB Git — Personal Git Platform4 * ─────────────────────────────────────────────5 * Author : Simon-Pierre Boucher6 * Contact : contact@spboucher.ai7 * File : cli/lib/ui.mjs8 * Purpose : CLI output helpers — aligned tables, prompts, concurrency9 * License : MIT © Simon-Pierre Boucher10 * ─────────────────────────────────────────────11 */1213import { createInterface } from 'node:readline/promises';14import pc from 'picocolors';1516/**17 * Print an aligned table.18 * @param {string[][]} rows already-colored cells allowed (width uses raw length)19 * @param {{pad?: number}} [opts]20 */21export function printTable(rows, opts = {}) {22 if (rows.length === 0) return;23 const pad = opts.pad ?? 2;24 // eslint-disable-next-line no-control-regex25 const visible = (s) => String(s).replace(/\u001b\[[0-9;]*m/g, '');26 const widths = [];27 for (const row of rows) {28 row.forEach((cell, i) => {29 widths[i] = Math.max(widths[i] ?? 0, visible(cell).length);30 });31 }32 for (const row of rows) {33 const line = row34 .map((cell, i) => cell + ' '.repeat(widths[i] - visible(cell).length + (i < row.length - 1 ? pad : 0)))35 .join('');36 console.log(line.trimEnd());37 }38}3940/**41 * Ask one interactive question.42 * @param {string} question43 * @param {string} [fallback]44 * @returns {Promise<string>}45 */46export async function prompt(question, fallback = '') {47 const rl = createInterface({ input: process.stdin, output: process.stdout });48 const suffix = fallback ? pc.dim(` (${fallback})`) : '';49 const answer = (await rl.question(`${question}${suffix}: `)).trim();50 rl.close();51 return answer || fallback;52}5354/**55 * Run at most `limit` async jobs concurrently, preserving order.56 * @template T,R57 * @param {T[]} items58 * @param {number} limit59 * @param {(item: T, index: number) => Promise<R>} worker60 * @returns {Promise<R[]>}61 */62export async function mapLimit(items, limit, worker) {63 const results = new Array(items.length);64 let next = 0;65 const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {66 while (next < items.length) {67 const index = next;68 next += 1;69 results[index] = await worker(items[index], index);70 }71 });72 await Promise.all(runners);73 return results;74}7576/** Symbols shared across commands. */77export const SYM = Object.freeze({78 ok: pc.green('✓'),79 push: pc.cyan('↑'),80 pull: pc.yellow('↓'),81 fail: pc.red('✗'),82 skip: pc.dim('·'),83});84