@visualautomate/cli 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +81 -37
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -59,6 +59,10 @@ state {"runs":1}
59
59
  A run passes when it succeeds and, if the test file names an `expectOutput`,
60
60
  leaves by that port — the same verdict as the GitHub check.
61
61
 
62
+ The verdict is green when it passed and red when it did not, in the sandbox as
63
+ well as out here. `NO_COLOR` turns that off, and output that is not a terminal —
64
+ a pipe, a file, a CI log — is plain anyway; `FORCE_COLOR` turns it back on.
65
+
62
66
  ### In a GitHub repository
63
67
 
64
68
  `test` works wherever the platform tests your code on a push:
package/dist/index.js CHANGED
@@ -1823,12 +1823,12 @@ function entryIn(dir) {
1823
1823
  return ENTRIES.map((file2) => join(dir, file2)).find((file2) => existsSync(file2)) ?? null;
1824
1824
  }
1825
1825
  function matchFolders(folders, name) {
1826
- const wanted = name.trim().toLowerCase();
1827
- const exact = folders.filter((folder) => folder.toLowerCase() === wanted);
1826
+ const wanted2 = name.trim().toLowerCase();
1827
+ const exact = folders.filter((folder) => folder.toLowerCase() === wanted2);
1828
1828
  if (exact.length > 0) return exact;
1829
- const prefix = folders.filter((folder) => folder.toLowerCase().startsWith(wanted));
1829
+ const prefix = folders.filter((folder) => folder.toLowerCase().startsWith(wanted2));
1830
1830
  if (prefix.length > 0) return prefix;
1831
- return folders.filter((folder) => folder.toLowerCase().includes(wanted));
1831
+ return folders.filter((folder) => folder.toLowerCase().includes(wanted2));
1832
1832
  }
1833
1833
  function findTargets(cwd, only) {
1834
1834
  const dir = resolve(cwd);
@@ -2087,6 +2087,38 @@ async function savePersisted(dir, files, state) {
2087
2087
 
2088
2088
  // src/docker.ts
2089
2089
  import { spawnSync } from "node:child_process";
2090
+
2091
+ // src/colour.ts
2092
+ var CODES = {
2093
+ reset: "\x1B[0m",
2094
+ bold: "\x1B[1m",
2095
+ dim: "\x1B[2m",
2096
+ red: "\x1B[31m",
2097
+ green: "\x1B[32m",
2098
+ yellow: "\x1B[33m",
2099
+ blue: "\x1B[34m"
2100
+ };
2101
+ function wanted() {
2102
+ const env = process.env;
2103
+ if (env.NO_COLOR !== void 0 && env.NO_COLOR !== "") return false;
2104
+ if (env.FORCE_COLOR !== void 0 && env.FORCE_COLOR !== "" && env.FORCE_COLOR !== "0") return true;
2105
+ return Boolean(process.stdout.isTTY);
2106
+ }
2107
+ var ON = wanted();
2108
+ function paint(code) {
2109
+ return (text) => ON ? `${code}${text}${CODES.reset}` : text;
2110
+ }
2111
+ var red = paint(CODES.red);
2112
+ var green = paint(CODES.green);
2113
+ var yellow = paint(CODES.yellow);
2114
+ var blue = paint(CODES.blue);
2115
+ var dim = paint(CODES.dim);
2116
+ var bold = paint(CODES.bold);
2117
+ function colourEnv() {
2118
+ return ON ? ["--env", "FORCE_COLOR=1"] : ["--env", "NO_COLOR=1"];
2119
+ }
2120
+
2121
+ // src/docker.ts
2090
2122
  var IMAGE = "ghcr.io/visualautomate/plugin-sandbox";
2091
2123
  var TIERS = {
2092
2124
  standard: { cpus: "0.25", memory: "1g" },
@@ -2119,6 +2151,9 @@ function dockerArgs(run) {
2119
2151
  "ALL",
2120
2152
  "--security-opt",
2121
2153
  "no-new-privileges",
2154
+ // So a run inside the container is as readable as one out here: its
2155
+ // stdout is a pipe to Docker, which would otherwise turn colour off.
2156
+ ...colourEnv(),
2122
2157
  ...run.user ? ["--user", `${run.user.uid}:${run.user.gid}`] : [],
2123
2158
  run.image,
2124
2159
  ...run.args
@@ -2142,16 +2177,18 @@ function dockerProblem() {
2142
2177
  function explainExit(code, tier) {
2143
2178
  if (code === 137) {
2144
2179
  const next = Object.keys(TIERS)[Object.keys(TIERS).indexOf(tier) + 1];
2145
- return `\u2717 stopped the step used more than the ${TIERS[tier].memory.replace("g", " GB")} of memory the ${tier} tier has, and the container was killed. On the platform it would stop the same way.` + (next ? ` To check it fits a bigger machine: --tier ${next}` : "");
2180
+ return red(
2181
+ `\u2717 stopped the step used more than the ${TIERS[tier].memory.replace("g", " GB")} of memory the ${tier} tier has, and the container was killed. On the platform it would stop the same way.` + (next ? ` To check it fits a bigger machine: --tier ${next}` : "")
2182
+ );
2146
2183
  }
2147
- if (code === 125) return "\u2717 Docker could not start the container. Is the image available? Try: docker pull " + IMAGE;
2184
+ if (code === 125) return red("\u2717 Docker could not start the container. Is the image available? Try: docker pull " + IMAGE);
2148
2185
  return null;
2149
2186
  }
2150
2187
  function ensureDocker(say, waitMs = 12e4) {
2151
2188
  const problem = dockerProblem();
2152
2189
  if (!problem) return null;
2153
2190
  if (problem.includes("not installed")) return problem;
2154
- say("Docker is not running. Starting Docker Desktop\u2026");
2191
+ say(yellow("Docker is not running. Starting Docker Desktop\u2026"));
2155
2192
  const started = spawnSync("docker", ["desktop", "start"], { encoding: "utf8" });
2156
2193
  if (started.status !== 0) {
2157
2194
  say("Could not start it from here \u2014 start Docker Desktop yourself; this will carry on when it is up.");
@@ -2185,7 +2222,7 @@ function runInDocker(run) {
2185
2222
  }
2186
2223
 
2187
2224
  // src/index.ts
2188
- var VERSION = true ? "0.5.1" : "0.0.0-dev";
2225
+ var VERSION = true ? "0.5.2" : "0.0.0-dev";
2189
2226
  var DEFAULT_API_URL = "https://visualautomate.com";
2190
2227
  function configPath() {
2191
2228
  return join3(homedir(), ".visualautomate", "config.json");
@@ -2212,7 +2249,7 @@ async function tokenFor(flags) {
2212
2249
  return { token, apiUrl };
2213
2250
  }
2214
2251
  function fail(message) {
2215
- console.error(message);
2252
+ console.error(red(message));
2216
2253
  throw new SilentExit();
2217
2254
  }
2218
2255
  var SilentExit = class extends Error {
@@ -2289,8 +2326,8 @@ async function cmdLogin(flags) {
2289
2326
  });
2290
2327
  const who = await whoami(apiUrl, token);
2291
2328
  await writeConfig({ token, apiUrl, email: who.email });
2292
- ok(`
2293
- \u2713 Signed in as ${who.email}`);
2329
+ ok(green(`
2330
+ \u2713 Signed in as ${who.email}`));
2294
2331
  ok(` Token stored in ${configPath()}`);
2295
2332
  }
2296
2333
  async function whoami(apiUrl, token) {
@@ -2376,9 +2413,9 @@ visualautomate push # publish it to your account
2376
2413
  \`test.json\` is the input, config and state \`test\` runs with.
2377
2414
  `
2378
2415
  );
2379
- ok(`
2416
+ ok(green(`
2380
2417
  \u2713 Created ${name} from the "${templateName}" template
2381
- `);
2418
+ `));
2382
2419
  ok(" cd " + name);
2383
2420
  ok(" visualautomate test");
2384
2421
  ok(" visualautomate push\n");
@@ -2412,12 +2449,12 @@ function line(indent, text) {
2412
2449
  async function runTarget(target, flags, several) {
2413
2450
  const say = (text) => line(several, text);
2414
2451
  if (several) ok(`
2415
- \u25B8 ${target.name}`);
2452
+ ${bold(`\u25B8 ${target.name}`)}`);
2416
2453
  try {
2417
2454
  const code = await codeFor(target);
2418
2455
  const check = validatePluginCode(code);
2419
2456
  if (!check.valid) {
2420
- say(`\u2717 refused ${check.error}`);
2457
+ say(`${red("\u2717 refused")} ${check.error}`);
2421
2458
  return false;
2422
2459
  }
2423
2460
  const settings = await readTestFile(target.dir, flags.input);
@@ -2431,7 +2468,7 @@ async function runTarget(target, flags, several) {
2431
2468
  const manifest = parseJsonc(await readFile3(manifestPath, "utf8")).value;
2432
2469
  outputs = manifest.outputs ?? [];
2433
2470
  } catch (error) {
2434
- say(`\u2717 manifest.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
2471
+ say(red(`\u2717 manifest.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`));
2435
2472
  return false;
2436
2473
  }
2437
2474
  }
@@ -2457,33 +2494,37 @@ async function runTarget(target, flags, several) {
2457
2494
  const port = result.output?.output ?? null;
2458
2495
  const expected = settings.expectOutput;
2459
2496
  const passed = result.status === "success" && (!expected || port === expected);
2460
- say(`${passed ? "\u2713 passed" : "\u2717 failed"} ${result.status}, ${result.durationMs}ms`);
2461
- if (result.error) say(`error ${result.error}`);
2497
+ const verdict = passed ? green(bold("\u2713 passed")) : red(bold("\u2717 failed"));
2498
+ say(`${verdict} ${dim(`${result.status}, ${result.durationMs}ms`)}`);
2499
+ if (result.error) say(`${dim("error")} ${red(result.error)}`);
2462
2500
  if (result.output) {
2463
- say(`output ${port}${expected ? ` (expected ${expected})` : ""}`);
2464
- say(`data ${JSON.stringify(result.output.data, null, 2).replace(/\n/g, "\n ")}`);
2501
+ const port_ = passed || !expected ? port : red(String(port));
2502
+ say(`${dim("output")} ${port_}${expected ? dim(` (expected ${expected})`) : ""}`);
2503
+ say(`${dim("data")} ${JSON.stringify(result.output.data, null, 2).replace(/\n/g, "\n ")}`);
2465
2504
  }
2466
- if (result.files.size > 0) say(`files ${[...result.files.keys()].join(", ")}`);
2467
- if (result.state.size > 0) say(`state ${JSON.stringify(Object.fromEntries(result.state))}`);
2468
- for (const entry of result.logs ?? []) say(`log ${entry}`);
2505
+ if (result.files.size > 0) say(`${dim("files")} ${[...result.files.keys()].join(", ")}`);
2506
+ if (result.state.size > 0) say(`${dim("state")} ${JSON.stringify(Object.fromEntries(result.state))}`);
2507
+ for (const entry of result.logs ?? []) say(`${dim("log")} ${entry}`);
2469
2508
  for (const [host, enforced] of denied) {
2470
2509
  say(
2471
- enforced ? `egress blocked ${host} \u2014 not in allowedDomains` : `! egress ${host} is not allowed on the platform unless the plugin declares it. Add "allowedDomains": ["${host}"] to the test file to check with the same rule.`
2510
+ enforced ? `${dim("egress")} ${red(`blocked ${host}`)} \u2014 not in allowedDomains` : yellow(`! egress ${host} is not allowed on the platform unless the plugin declares it. `) + `Add "allowedDomains": ["${host}"] to the test file to check with the same rule.`
2472
2511
  );
2473
2512
  }
2474
2513
  if (persist) {
2475
2514
  const refused = await savePersisted(target.dir, result.files, result.state);
2476
- say(`kept storage and state in ${relative3(process.cwd(), join3(target.dir, ".visualautomate")) || ".visualautomate"}`);
2477
- for (const path of refused) say(`! storage path "${path}" points outside the storage folder and was not written`);
2515
+ say(`${dim("kept")} ${dim(`storage and state in ${relative3(process.cwd(), join3(target.dir, ".visualautomate")) || ".visualautomate"}`)}`);
2516
+ for (const path of refused) say(yellow(`! storage path "${path}" points outside the storage folder and was not written`));
2478
2517
  }
2479
2518
  if (port && outputs.length > 0 && !outputs.includes(port)) {
2480
2519
  say(
2481
- `! This returned output "${port}", which manifest.json does not list (it has ${outputs.map((o) => `"${o}"`).join(", ")}). On a canvas nothing would follow it.`
2520
+ yellow(
2521
+ `! This returned output "${port}", which manifest.json does not list (it has ${outputs.map((o) => `"${o}"`).join(", ")}). On a canvas nothing would follow it.`
2522
+ )
2482
2523
  );
2483
2524
  }
2484
2525
  return passed;
2485
2526
  } catch (error) {
2486
- say(`\u2717 ${error instanceof Error ? error.message : String(error)}`);
2527
+ say(red(`\u2717 ${error instanceof Error ? error.message : String(error)}`));
2487
2528
  return false;
2488
2529
  }
2489
2530
  }
@@ -2492,8 +2533,11 @@ async function runTargets(targets, flags) {
2492
2533
  for (const target of targets) {
2493
2534
  if (await runTarget(target, flags, targets.length > 1)) passed++;
2494
2535
  }
2495
- if (targets.length > 1) ok(`
2496
- ${passed} of ${targets.length} passed`);
2536
+ if (targets.length > 1) {
2537
+ const tally = `${passed} of ${targets.length} passed`;
2538
+ ok(`
2539
+ ${passed === targets.length ? green(bold(tally)) : red(bold(tally))}`);
2540
+ }
2497
2541
  return passed === targets.length;
2498
2542
  }
2499
2543
  function packageLoader(dir) {
@@ -2559,7 +2603,7 @@ function sayWhatRuns(targets, picked) {
2559
2603
  return;
2560
2604
  }
2561
2605
  ok(`Running ${targets.length} modules: ${targets.map((t) => t.name.replace(/^modules\//, "")).join(", ")}`);
2562
- if (!picked) ok(`One of them: vsa dev ${targets[0].name.replace(/^modules\//, "")}`);
2606
+ if (!picked) ok(dim(`One of them: vsa dev ${targets[0].name.replace(/^modules\//, "")}`));
2563
2607
  }
2564
2608
  async function cmdTest(args, flags) {
2565
2609
  const targets = targetsOrFail(args, flags);
@@ -2575,11 +2619,11 @@ async function cmdDev(args, flags) {
2575
2619
  if (inSandbox) prepareSandbox(flags);
2576
2620
  const header = () => {
2577
2621
  process.stdout.write("\x1Bc");
2578
- ok(`vsa dev \u2014 ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \u2014 Ctrl+C to stop`);
2622
+ ok(`${bold("vsa dev")} ${dim(`\u2014 ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \u2014 Ctrl+C to stop`)}`);
2579
2623
  };
2580
2624
  const run = async (chosen) => inSandbox ? runTargetsInDocker(chosen, targets, flags) : runTargets(chosen, flags);
2581
2625
  header();
2582
- ok(inSandbox ? `in ${imageFor(flags)}, tier ${flags.tier || "standard"}` : "on this machine, not in the sandbox");
2626
+ ok(dim(inSandbox ? `in ${imageFor(flags)}, tier ${flags.tier || "standard"}` : "on this machine, not in the sandbox"));
2583
2627
  sayWhatRuns(targets, Boolean(picked));
2584
2628
  await run(targets);
2585
2629
  const ignored = [`${sep2}node_modules${sep2}`, `${sep2}.git${sep2}`, `${sep2}dist${sep2}`, `${sep2}.visualautomate${sep2}`];
@@ -2598,14 +2642,14 @@ async function cmdDev(args, flags) {
2598
2642
  targets = findTargets(root, picked);
2599
2643
  } catch (error) {
2600
2644
  header();
2601
- ok(`\u2717 ${error instanceof Error ? error.message : String(error)}`);
2645
+ ok(red(`\u2717 ${error instanceof Error ? error.message : String(error)}`));
2602
2646
  running = false;
2603
2647
  return;
2604
2648
  }
2605
2649
  const hit = changed.map((path) => targetForPath(targets, path));
2606
2650
  const chosen = hit.some((t) => t === null) ? targets : [...new Set(hit)];
2607
2651
  header();
2608
- ok(`changed: ${changed.map((p) => relative3(root, p)).join(", ")}`);
2652
+ ok(dim(`changed: ${changed.map((p) => relative3(root, p)).join(", ")}`));
2609
2653
  await run(chosen);
2610
2654
  running = false;
2611
2655
  };
@@ -2707,8 +2751,8 @@ ${text.slice(0, 500)}`);
2707
2751
  if (!res.ok || !data.success) {
2708
2752
  fail(`Push refused: ${data.error ?? `status ${res.status}`}`);
2709
2753
  }
2710
- ok(`
2711
- \u2713 Published (id: ${data.pluginId})`);
2754
+ ok(green(`
2755
+ \u2713 Published (id: ${data.pluginId})`));
2712
2756
  ok(` A version snapshot was saved, so you can roll back from the dashboard.`);
2713
2757
  }
2714
2758
  function printHelp() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visualautomate/cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Build and publish VisualAutomate plugins.",
5
5
  "license": "MIT",
6
6
  "type": "module",