SPB Git

spb/spbgit Public MIT

SPB Git — the platform hosting itself

JavaScript 73.9% CSS 11.7% Nunjucks 11.6% Shell 2.7%
2.7 KB · 84 lines javascript
Raw Blame History
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