@nexusbloom/cli 0.9.8 → 0.9.9

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexusbloom/cli",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "NexusBloom CLI — run tools and workflows from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
package/src/pipe.js CHANGED
@@ -295,9 +295,23 @@ function splitSteps(text) {
295
295
  return chunks;
296
296
  }
297
297
 
298
+ /**
299
+ * Is this pipe the start of a transform chain rather than the next step?
300
+ *
301
+ * The word after the pipe has to be a *whole* transform name, optionally
302
+ * followed by an argument list. Without the terminator check, a tool whose slug
303
+ * merely starts with a transform name — `base64-tool`, `split-words` — was read
304
+ * as the transform `base64` plus a stray slug, and the pipeline failed to parse
305
+ * with a confusing "Expected key=value" pointing at the pipe.
306
+ */
298
307
  function isKnownTransformWord(rest) {
299
- const word = (rest.match(/^\s*([a-zA-Z_][a-zA-Z0-9_]*)/) || [])[1];
300
- return word !== undefined && Object.hasOwn(TRANSFORMS, word);
308
+ const m = /^\s*([a-zA-Z_][a-zA-Z0-9_]*)/.exec(rest);
309
+ if (!m) return false;
310
+ if (!Object.hasOwn(TRANSFORMS, m[1])) return false;
311
+ // Whatever follows the name decides it: `len()`, `len` at the end of a step,
312
+ // then whitespace, or a pipe. A hyphen or dot means this is a slug.
313
+ const after = rest.slice(m.index + m[1].length);
314
+ return after === "" || /^\s*[(\s|]/.test(after);
301
315
  }
302
316
 
303
317
  /**
package/src/values.js CHANGED
@@ -333,6 +333,11 @@ export const TRANSFORMS = {
333
333
  (Array.isArray(v) && v.length === 0) ||
334
334
  (typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0),
335
335
 
336
+ // Builds one string out of several values. Without this a report can only ever
337
+ // be as long as whatever single tool produced it: `join` needs an array that
338
+ // already exists, and there is no way to place an upstream value inside text.
339
+ concat: (v, ...parts) => [v, ...parts].map((p) => (p === MISSING || p === undefined || p === null ? "" : String(p))).join(""),
340
+
336
341
  // — branching —
337
342
  //
338
343
  // `then(cond, whenTrue, whenFalse)` is what a linear pipeline was missing: pick
@@ -485,6 +490,11 @@ export const TRANSFORM_ARITY = {
485
490
  // understate it, so the builder asks for one and a written pipeline may list
486
491
  // as many as it likes.
487
492
  coalesce: 2,
493
+ // Arity is the *minimum* argument count the parser enforces, so a variadic
494
+ // transform declares 1 — `concat()` with nothing after it is legal and just
495
+ // stringifies the piped value. `coalesce` above keeps its 2 because a coalesce
496
+ // with no fallback does nothing.
497
+ concat: 1,
488
498
  pluck: 2, chunk: 2, without: 2, where: 4,
489
499
  truncate: 3, repeat: 2, mask: 2,
490
500
  };
@@ -565,6 +575,7 @@ export const TRANSFORM_DOCS = {
565
575
  matches: "does a string match a regular expression",
566
576
  isEmpty: "is the value empty — blank, [], or {}",
567
577
  then: "choose between two values on a condition",
578
+ concat: "combine several values into one string",
568
579
  coalesce: "first value that is not empty",
569
580
  pluck: "pull one field out of every object in an array",
570
581
  unique: "remove duplicates from an array",
@@ -621,6 +632,8 @@ export const TRANSFORM_GROUPS = [
621
632
  names: ["json", "unjson"] },
622
633
  { label: "default(v) / coalesce(a,b,…) / then(cond,a,b)", doc: "supply a value when one is missing, or choose on a condition",
623
634
  names: ["default", "coalesce", "then"] },
635
+ { label: "concat(a,b,…)", doc: "combine several values into one string",
636
+ names: ["concat"] },
624
637
  { label: "eq / ne / gt / gte / lt / lte / between(a,b)", doc: "compare — the result is true or false",
625
638
  names: ["eq", "ne", "gt", "gte", "lt", "lte", "between"] },
626
639
  { label: "includes / startsWith / endsWith / matches(re) / isEmpty", doc: "test text and arrays",