@biffo/cli 0.301.20 → 0.301.22

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.
@@ -424,8 +424,16 @@ export function deliberateClosingReferences(text, { code = true } = {}) {
424
424
  * carried only `Refs #133`. The issue closed on merge anyway. The lesson
425
425
  * recorded then: *keeping a denial out of the commit is not sufficient —
426
426
  * GitHub's linker reads the PR description text on its own.*
427
- * - #1238 / #1021 (2026-08-03) — `- **Does not close #1021.**` in the body,
428
- * `Refs #1021` in the commit, #1021 closed by the squash-merge.
427
+ * - biffo-template#1238 / biffo-template#1021 (2026-08-03) — this repo's own
428
+ * issues, not an external one: `- **Does not close #1021.**` in the body,
429
+ * `Refs #1021` in the commit, #1021 closed by the squash-merge. (#1872
430
+ * re-checked this entry against the wrong repo — tabsii-platform, where no
431
+ * such pair exists at that date — and, not finding it there, filed a
432
+ * doubt. Re-verified directly against biffo-template's own #1021/#1238:
433
+ * the pairing, the quoted lines and the date all check out exactly as
434
+ * recorded. The two bare `#N`s were ambiguous next to the other entries'
435
+ * explicit `tabsii-platform#76`/`tabsii-crm#133` qualifiers, which is the
436
+ * actual defect #1872 found — now spelled out with the repo name.)
429
437
  *
430
438
  * The recorded fix each time was a *practice*: "never write a closing keyword
431
439
  * in prose". Three occurrences produced a rule and no mechanism, and the
@@ -661,9 +669,10 @@ function formatNegatedFailure({ negated }) {
661
669
  ...negated.map((n) => ` ${n.source}, line ${n.lineNumber}: ${n.line}`),
662
670
  '',
663
671
  'This has now happened four times (tabsii-platform#76, tabsii-crm#133,',
664
- '#1021 via #1238 — see #1245). Keeping the denial out of the commit',
665
- 'message is not enough: GitHub reads the PR description on its own — and',
666
- '(#1334) a commit message on its own, independent of the body.',
672
+ 'biffo-template#1021 via biffo-template#1238 — see #1245). Keeping the',
673
+ 'denial out of the commit message is not enough: GitHub reads the PR',
674
+ 'description on its own — and (#1334) a commit message on its own,',
675
+ 'independent of the body.',
667
676
  '',
668
677
  'Rewrite the line so no closing keyword sits in front of the reference:',
669
678
  ...refs.map((r) => ` - \`Refs ${r}\`, or "leaves ${r} open"`),
@@ -80,12 +80,29 @@ attempts=3
80
80
  inconclusive=0
81
81
  failed=0
82
82
 
83
- # Audit one directory. Returns 0 if clean or inconclusive, 1 on a real finding.
84
- # `$1` is the directory, `$2` a human label, `$3` extra pnpm flags.
83
+ # Audit one directory. Returns 0 if clean or inconclusive, 1 on a real finding,
84
+ # AND writes a one-word verdict (`ok` / `fail` / `inconclusive`) to `$4`.
85
+ #
86
+ # The verdict file exists because this function is invoked backgrounded (`&`,
87
+ # see the audit loop below) so the four (or however many) trees run in
88
+ # parallel rather than paying their registry round-trip one after another
89
+ # (#1874: 4 trees serially cost 15m21s in a real CI run and blew the job's
90
+ # 20-minute cap). A backgrounded call is a forked subshell — every variable it
91
+ # touches, including `inconclusive`/`failed` below, is a copy in that child
92
+ # process and vanishes when it exits. The return code has the same problem: a
93
+ # background job's exit status is only visible via `wait "$pid"`, one pid at a
94
+ # time, which is no simpler than a file and less robust (a killed/never-run
95
+ # job leaves nothing to wait on). So each invocation reports for itself, in
96
+ # writing, and the parent tallies the results after `wait` once every
97
+ # invocation has finished.
98
+ #
99
+ # `$1` is the directory, `$2` a human label, `$3` extra pnpm flags, `$4` the
100
+ # result file this invocation must write its verdict to.
85
101
  audit_dir() {
86
102
  dir="$1"
87
103
  label="$2"
88
104
  extra="$3"
105
+ resultfile="$4"
89
106
 
90
107
  for attempt in $(seq 1 "$attempts"); do
91
108
  # printf, never echo: the CI step runs `sh scripts/...` i.e. dash, whose
@@ -122,12 +139,14 @@ audit_dir() {
122
139
  if [ "$((high + crit))" -gt 0 ]; then
123
140
  echo "::error::${label}: ${crit} critical + ${high} high advisory(ies) across ${total} package(s); registry answered ${seen_at}."
124
141
  printf '%s' "$out" | jq '.advisories // .metadata.vulnerabilities' 2>/dev/null | head -c 4000
142
+ echo "fail" >"$resultfile"
125
143
  return 1
126
144
  fi
127
145
  # A bare "no advisories" is not falsifiable. State the population, the
128
146
  # severities that did NOT block, and when the registry was asked, so a
129
147
  # reader can tell a clean tree from a tree nobody looked at properly.
130
148
  echo "${label}: 0 critical, 0 high across ${total} package(s) (${mod} moderate, ${low} low — reported, not blocking); registry answered ${seen_at}."
149
+ echo "ok" >"$resultfile"
131
150
  return 0
132
151
  fi
133
152
 
@@ -137,7 +156,7 @@ audit_dir() {
137
156
  done
138
157
 
139
158
  echo "::error::${label}: audit could not run after ${attempts} attempts (the registry returned a non-JSON/error response). Advisory scanning was NOT performed for this tree, so this is INCONCLUSIVE and BLOCKS — a gate that cannot see its input must not report clean (#1269, #591)."
140
- inconclusive=$((inconclusive + 1))
159
+ echo "inconclusive" >"$resultfile"
141
160
  return 0
142
161
  }
143
162
 
@@ -197,11 +216,31 @@ for lock in $ALL_LOCKS; do
197
216
  fi
198
217
  done
199
218
 
200
- # Audit each discovered tree. The workspace's own lockfile is audited WITHOUT
201
- # --ignore-workspace, so pnpm resolves it normally; every other discovered
202
- # lockfile is a separate, vendored project and needs the flag, or pnpm walks
203
- # up, finds the workspace, and silently audits THAT instead — reporting clean
204
- # for a tree it never looked at, which is this exact defect one level down.
219
+ # Audit each discovered tree IN PARALLEL (#1874). Each `audit_dir` call is a
220
+ # real network round-trip, with its own retry/backoff, to registry.npmjs.org
221
+ # — run one after another they cost roughly N x the slowest single tree (a
222
+ # real CI run measured 4 trees / 15m21s, blowing the job's 20-minute cap
223
+ # mid-way through ~9 other required guard steps). Backgrounding them lets the
224
+ # round-trips overlap instead.
225
+ #
226
+ # A one-word-per-tree result file (see `audit_dir` above) is how the parent
227
+ # shell learns each backgrounded verdict back: `$TMP_DIR` is created via
228
+ # `mktemp -d` (collision-safe by construction — no need to hand-roll a `$$`
229
+ # suffix on top of it) and torn down by the EXIT/HUP/INT/TERM trap below
230
+ # whichever way this script leaves.
231
+ #
232
+ # The workspace's own lockfile is audited WITHOUT --ignore-workspace, so pnpm
233
+ # resolves it normally; every other discovered lockfile is a separate,
234
+ # vendored project and needs the flag, or pnpm walks up, finds the workspace,
235
+ # and silently audits THAT instead — reporting clean for a tree it never
236
+ # looked at, which is this exact defect one level down.
237
+ TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/js-dependency-audit.XXXXXX") || {
238
+ echo "::error::js-dependency-audit: could not create a temp directory for parallel results (mktemp failed)." >&2
239
+ exit 2
240
+ }
241
+ trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
242
+
243
+ i=0
205
244
  for lock in $ALL_LOCKS; do
206
245
  dir=$(dirname "$lock")
207
246
  dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
@@ -210,13 +249,41 @@ for lock in $ALL_LOCKS; do
210
249
  "$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
211
250
  *) rel="$dir_abs" ;;
212
251
  esac
252
+ i=$((i + 1))
253
+ resultfile="$TMP_DIR/result.$i"
213
254
  if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
214
- audit_dir "$dir" "pnpm audit (workspace: ${rel})" "" || failed=1
255
+ audit_dir "$dir" "pnpm audit (workspace: ${rel})" "" "$resultfile" &
215
256
  else
216
- audit_dir "$dir" "pnpm audit (${rel})" "--ignore-workspace" || failed=1
257
+ audit_dir "$dir" "pnpm audit (${rel})" "--ignore-workspace" "$resultfile" &
217
258
  fi
218
259
  done
219
260
 
261
+ # Wait for every backgrounded audit_dir before reading any result file back —
262
+ # reading early would race a tree that is still auditing.
263
+ wait
264
+
265
+ # Tally the verdicts the backgrounded invocations wrote for themselves. A
266
+ # result file that is missing or unreadable (the subshell was killed before
267
+ # it could write, or never started) fails CLOSED as inconclusive rather than
268
+ # being silently skipped — the same posture as the empty-discovery check
269
+ # above: a tree this run cannot account for is not a clean tree.
270
+ i=0
271
+ for lock in $ALL_LOCKS; do
272
+ i=$((i + 1))
273
+ resultfile="$TMP_DIR/result.$i"
274
+ verdict=$(cat "$resultfile" 2>/dev/null)
275
+ case "$verdict" in
276
+ ok) ;;
277
+ fail) failed=1 ;;
278
+ inconclusive) inconclusive=$((inconclusive + 1)) ;;
279
+ *)
280
+ lock_dir=$(dirname "$lock")
281
+ echo "::error::js-dependency-audit: no verdict recorded for ${lock_dir} (expected ok/fail/inconclusive in ${resultfile}). Treating as inconclusive."
282
+ inconclusive=$((inconclusive + 1))
283
+ ;;
284
+ esac
285
+ done
286
+
220
287
  if [ "$failed" -ne 0 ]; then
221
288
  exit 1
222
289
  fi
@@ -424,8 +424,16 @@ export function deliberateClosingReferences(text, { code = true } = {}) {
424
424
  * carried only `Refs #133`. The issue closed on merge anyway. The lesson
425
425
  * recorded then: *keeping a denial out of the commit is not sufficient —
426
426
  * GitHub's linker reads the PR description text on its own.*
427
- * - #1238 / #1021 (2026-08-03) — `- **Does not close #1021.**` in the body,
428
- * `Refs #1021` in the commit, #1021 closed by the squash-merge.
427
+ * - biffo-template#1238 / biffo-template#1021 (2026-08-03) — this repo's own
428
+ * issues, not an external one: `- **Does not close #1021.**` in the body,
429
+ * `Refs #1021` in the commit, #1021 closed by the squash-merge. (#1872
430
+ * re-checked this entry against the wrong repo — tabsii-platform, where no
431
+ * such pair exists at that date — and, not finding it there, filed a
432
+ * doubt. Re-verified directly against biffo-template's own #1021/#1238:
433
+ * the pairing, the quoted lines and the date all check out exactly as
434
+ * recorded. The two bare `#N`s were ambiguous next to the other entries'
435
+ * explicit `tabsii-platform#76`/`tabsii-crm#133` qualifiers, which is the
436
+ * actual defect #1872 found — now spelled out with the repo name.)
429
437
  *
430
438
  * The recorded fix each time was a *practice*: "never write a closing keyword
431
439
  * in prose". Three occurrences produced a rule and no mechanism, and the
@@ -661,9 +669,10 @@ function formatNegatedFailure({ negated }) {
661
669
  ...negated.map((n) => ` ${n.source}, line ${n.lineNumber}: ${n.line}`),
662
670
  '',
663
671
  'This has now happened four times (tabsii-platform#76, tabsii-crm#133,',
664
- '#1021 via #1238 — see #1245). Keeping the denial out of the commit',
665
- 'message is not enough: GitHub reads the PR description on its own — and',
666
- '(#1334) a commit message on its own, independent of the body.',
672
+ 'biffo-template#1021 via biffo-template#1238 — see #1245). Keeping the',
673
+ 'denial out of the commit message is not enough: GitHub reads the PR',
674
+ 'description on its own — and (#1334) a commit message on its own,',
675
+ 'independent of the body.',
667
676
  '',
668
677
  'Rewrite the line so no closing keyword sits in front of the reference:',
669
678
  ...refs.map((r) => ` - \`Refs ${r}\`, or "leaves ${r} open"`),
@@ -80,12 +80,29 @@ attempts=3
80
80
  inconclusive=0
81
81
  failed=0
82
82
 
83
- # Audit one directory. Returns 0 if clean or inconclusive, 1 on a real finding.
84
- # `$1` is the directory, `$2` a human label, `$3` extra pnpm flags.
83
+ # Audit one directory. Returns 0 if clean or inconclusive, 1 on a real finding,
84
+ # AND writes a one-word verdict (`ok` / `fail` / `inconclusive`) to `$4`.
85
+ #
86
+ # The verdict file exists because this function is invoked backgrounded (`&`,
87
+ # see the audit loop below) so the four (or however many) trees run in
88
+ # parallel rather than paying their registry round-trip one after another
89
+ # (#1874: 4 trees serially cost 15m21s in a real CI run and blew the job's
90
+ # 20-minute cap). A backgrounded call is a forked subshell — every variable it
91
+ # touches, including `inconclusive`/`failed` below, is a copy in that child
92
+ # process and vanishes when it exits. The return code has the same problem: a
93
+ # background job's exit status is only visible via `wait "$pid"`, one pid at a
94
+ # time, which is no simpler than a file and less robust (a killed/never-run
95
+ # job leaves nothing to wait on). So each invocation reports for itself, in
96
+ # writing, and the parent tallies the results after `wait` once every
97
+ # invocation has finished.
98
+ #
99
+ # `$1` is the directory, `$2` a human label, `$3` extra pnpm flags, `$4` the
100
+ # result file this invocation must write its verdict to.
85
101
  audit_dir() {
86
102
  dir="$1"
87
103
  label="$2"
88
104
  extra="$3"
105
+ resultfile="$4"
89
106
 
90
107
  for attempt in $(seq 1 "$attempts"); do
91
108
  # printf, never echo: the CI step runs `sh scripts/...` i.e. dash, whose
@@ -122,12 +139,14 @@ audit_dir() {
122
139
  if [ "$((high + crit))" -gt 0 ]; then
123
140
  echo "::error::${label}: ${crit} critical + ${high} high advisory(ies) across ${total} package(s); registry answered ${seen_at}."
124
141
  printf '%s' "$out" | jq '.advisories // .metadata.vulnerabilities' 2>/dev/null | head -c 4000
142
+ echo "fail" >"$resultfile"
125
143
  return 1
126
144
  fi
127
145
  # A bare "no advisories" is not falsifiable. State the population, the
128
146
  # severities that did NOT block, and when the registry was asked, so a
129
147
  # reader can tell a clean tree from a tree nobody looked at properly.
130
148
  echo "${label}: 0 critical, 0 high across ${total} package(s) (${mod} moderate, ${low} low — reported, not blocking); registry answered ${seen_at}."
149
+ echo "ok" >"$resultfile"
131
150
  return 0
132
151
  fi
133
152
 
@@ -137,7 +156,7 @@ audit_dir() {
137
156
  done
138
157
 
139
158
  echo "::error::${label}: audit could not run after ${attempts} attempts (the registry returned a non-JSON/error response). Advisory scanning was NOT performed for this tree, so this is INCONCLUSIVE and BLOCKS — a gate that cannot see its input must not report clean (#1269, #591)."
140
- inconclusive=$((inconclusive + 1))
159
+ echo "inconclusive" >"$resultfile"
141
160
  return 0
142
161
  }
143
162
 
@@ -197,11 +216,31 @@ for lock in $ALL_LOCKS; do
197
216
  fi
198
217
  done
199
218
 
200
- # Audit each discovered tree. The workspace's own lockfile is audited WITHOUT
201
- # --ignore-workspace, so pnpm resolves it normally; every other discovered
202
- # lockfile is a separate, vendored project and needs the flag, or pnpm walks
203
- # up, finds the workspace, and silently audits THAT instead — reporting clean
204
- # for a tree it never looked at, which is this exact defect one level down.
219
+ # Audit each discovered tree IN PARALLEL (#1874). Each `audit_dir` call is a
220
+ # real network round-trip, with its own retry/backoff, to registry.npmjs.org
221
+ # — run one after another they cost roughly N x the slowest single tree (a
222
+ # real CI run measured 4 trees / 15m21s, blowing the job's 20-minute cap
223
+ # mid-way through ~9 other required guard steps). Backgrounding them lets the
224
+ # round-trips overlap instead.
225
+ #
226
+ # A one-word-per-tree result file (see `audit_dir` above) is how the parent
227
+ # shell learns each backgrounded verdict back: `$TMP_DIR` is created via
228
+ # `mktemp -d` (collision-safe by construction — no need to hand-roll a `$$`
229
+ # suffix on top of it) and torn down by the EXIT/HUP/INT/TERM trap below
230
+ # whichever way this script leaves.
231
+ #
232
+ # The workspace's own lockfile is audited WITHOUT --ignore-workspace, so pnpm
233
+ # resolves it normally; every other discovered lockfile is a separate,
234
+ # vendored project and needs the flag, or pnpm walks up, finds the workspace,
235
+ # and silently audits THAT instead — reporting clean for a tree it never
236
+ # looked at, which is this exact defect one level down.
237
+ TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/js-dependency-audit.XXXXXX") || {
238
+ echo "::error::js-dependency-audit: could not create a temp directory for parallel results (mktemp failed)." >&2
239
+ exit 2
240
+ }
241
+ trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
242
+
243
+ i=0
205
244
  for lock in $ALL_LOCKS; do
206
245
  dir=$(dirname "$lock")
207
246
  dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
@@ -210,13 +249,41 @@ for lock in $ALL_LOCKS; do
210
249
  "$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
211
250
  *) rel="$dir_abs" ;;
212
251
  esac
252
+ i=$((i + 1))
253
+ resultfile="$TMP_DIR/result.$i"
213
254
  if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
214
- audit_dir "$dir" "pnpm audit (workspace: ${rel})" "" || failed=1
255
+ audit_dir "$dir" "pnpm audit (workspace: ${rel})" "" "$resultfile" &
215
256
  else
216
- audit_dir "$dir" "pnpm audit (${rel})" "--ignore-workspace" || failed=1
257
+ audit_dir "$dir" "pnpm audit (${rel})" "--ignore-workspace" "$resultfile" &
217
258
  fi
218
259
  done
219
260
 
261
+ # Wait for every backgrounded audit_dir before reading any result file back —
262
+ # reading early would race a tree that is still auditing.
263
+ wait
264
+
265
+ # Tally the verdicts the backgrounded invocations wrote for themselves. A
266
+ # result file that is missing or unreadable (the subshell was killed before
267
+ # it could write, or never started) fails CLOSED as inconclusive rather than
268
+ # being silently skipped — the same posture as the empty-discovery check
269
+ # above: a tree this run cannot account for is not a clean tree.
270
+ i=0
271
+ for lock in $ALL_LOCKS; do
272
+ i=$((i + 1))
273
+ resultfile="$TMP_DIR/result.$i"
274
+ verdict=$(cat "$resultfile" 2>/dev/null)
275
+ case "$verdict" in
276
+ ok) ;;
277
+ fail) failed=1 ;;
278
+ inconclusive) inconclusive=$((inconclusive + 1)) ;;
279
+ *)
280
+ lock_dir=$(dirname "$lock")
281
+ echo "::error::js-dependency-audit: no verdict recorded for ${lock_dir} (expected ok/fail/inconclusive in ${resultfile}). Treating as inconclusive."
282
+ inconclusive=$((inconclusive + 1))
283
+ ;;
284
+ esac
285
+ done
286
+
220
287
  if [ "$failed" -ne 0 ]; then
221
288
  exit 1
222
289
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.301.20",
3
+ "version": "0.301.22",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",