@biffo/cli 0.301.21 → 0.301.23

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.
@@ -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
@@ -87,21 +87,6 @@ jobs:
87
87
  - name: Build
88
88
  if: ${{ !cancelled() }}
89
89
  run: pnpm run build
90
- - name: Dependency audit
91
- if: ${{ !cancelled() }}
92
- # Fails on a real high/critical advisory, but treats a broken audit
93
- # registry as INCONCLUSIVE rather than reddening a required check for an
94
- # infrastructure hiccup (#591, #743). The raw `pnpm audit
95
- # --audit-level=high` this replaces exits non-zero identically whether it
96
- # found a vulnerability or simply could not parse the registry's
97
- # response, so any npm blip failed this job on every open PR at once.
98
- #
99
- # The relative path is deliberate. This job's working-directory is
100
- # apps/frontend, which is exactly the tree to audit, and the script
101
- # audits its own cwd — it is byte-identical to biffo-template's copy and
102
- # to every other satellite's, distributed by that repo's shared-sync.sh,
103
- # so it cannot assume a layout. Do not edit it here; edit it upstream.
104
- run: sh ../../scripts/js-dependency-audit.sh
105
90
  # The generic relocation of tabsii-platform#377 Phase 2's scale-guard --
106
91
  # the systemic half of that issue, built in biffo-template so every new
107
92
  # sibling (in any Biffo instance) is born with it. Ships as
@@ -170,6 +155,51 @@ jobs:
170
155
  - name: E2E tests
171
156
  run: pnpm run e2e
172
157
 
158
+ # Split out of the `js` job deliberately (biffo-template#1880): `pnpm
159
+ # audit`'s registry call has been chronically, intermittently unreachable
160
+ # since npm/GitHub retired the legacy audit endpoints on 2026-07-15, an
161
+ # ongoing upstream infrastructure condition unrelated to this repo's own
162
+ # code. Mirrors biffo-template's own root `ci.yml` split; see that repo's
163
+ # `js-audit` job and #1880 for the full evidence. `js` (required) no
164
+ # longer runs this step, so a registry hang can never again block this
165
+ # required check; this job runs the exact same script informationally on
166
+ # every push/PR (`continue-on-error: true`) and is deliberately NOT part
167
+ # of DEFAULT_STATUS_CHECKS/SIBLING_STATUS_CHECKS.
168
+ js-audit:
169
+ name: JS Dependency Audit (non-blocking, #1880)
170
+ runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }}
171
+ timeout-minutes: 20
172
+ continue-on-error: true
173
+ defaults:
174
+ run:
175
+ working-directory: apps/frontend
176
+ steps:
177
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
178
+ - uses: pnpm/action-setup@v4
179
+ with:
180
+ version: 9.15.9
181
+ - uses: actions/setup-node@v4
182
+ with:
183
+ node-version: ${{ env.NODE_VERSION }}
184
+ cache: pnpm
185
+ cache-dependency-path: apps/frontend/pnpm-lock.yaml
186
+ - run: pnpm install --frozen-lockfile
187
+ - name: Dependency audit
188
+ if: ${{ !cancelled() }}
189
+ # Fails on a real high/critical advisory, but treats a broken audit
190
+ # registry (non-JSON response, or the chronic #1880 timeout) as
191
+ # INCONCLUSIVE rather than blocking (#591, #743) — and this job's own
192
+ # non-required status is what stops that inconclusive result from
193
+ # blocking the merge queue in the first place.
194
+ #
195
+ # The relative path is deliberate. This job's working-directory is
196
+ # apps/frontend, which is exactly the tree to audit, and the script
197
+ # audits its own cwd — it is byte-identical to biffo-template's copy
198
+ # and to every other satellite's, distributed by that repo's
199
+ # shared-sync.sh, so it cannot assume a layout. Do not edit it here;
200
+ # edit it upstream.
201
+ run: sh ../../scripts/js-dependency-audit.sh
202
+
173
203
  python:
174
204
  name: Python (lint, types, test, security)
175
205
  runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }}
@@ -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.21",
3
+ "version": "0.301.23",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",