@biffo/cli 0.244.2 → 0.244.4
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/dist/index.js +8 -4
- package/package.json +1 -1
- package/scripts/practices-corpus.mjs +15 -3
- package/scripts/wait-for-checks.sh +46 -1
package/dist/index.js
CHANGED
|
@@ -2709,11 +2709,15 @@ function applyMigrationCarry(instanceDir, plan) {
|
|
|
2709
2709
|
const written = [];
|
|
2710
2710
|
for (const e of plan.entries) {
|
|
2711
2711
|
const abs = join7(instanceDir, e.path);
|
|
2712
|
-
if (existsSync7(abs)) {
|
|
2713
|
-
throw new Error(`Refusing to overwrite an existing migration: ${e.path}`);
|
|
2714
|
-
}
|
|
2715
2712
|
mkdirSync2(dirname4(abs), { recursive: true });
|
|
2716
|
-
|
|
2713
|
+
try {
|
|
2714
|
+
writeFileSync3(abs, e.content, { flag: "wx" });
|
|
2715
|
+
} catch (err) {
|
|
2716
|
+
if (err.code === "EEXIST") {
|
|
2717
|
+
throw new Error(`Refusing to overwrite an existing migration: ${e.path}`);
|
|
2718
|
+
}
|
|
2719
|
+
throw err;
|
|
2720
|
+
}
|
|
2717
2721
|
written.push(e.path);
|
|
2718
2722
|
}
|
|
2719
2723
|
return written;
|
package/package.json
CHANGED
|
@@ -147,6 +147,14 @@ export function slugify(text) {
|
|
|
147
147
|
* Refuses to overwrite an existing file: a collision means the slug needs to
|
|
148
148
|
* be more specific, not that the earlier entry should be silently replaced.
|
|
149
149
|
*
|
|
150
|
+
* The refusal is an atomic `wx` create, not an `existsSync` check followed by
|
|
151
|
+
* a write (#1222). This corpus has concurrent writers BY DESIGN — several
|
|
152
|
+
* agent sessions run against this estate at once — so the window between a
|
|
153
|
+
* check and a write is not theoretical here: two sessions writing the same
|
|
154
|
+
* `date-slug` is the exact case the guard exists for, and the check-then-write
|
|
155
|
+
* form lost the earlier entry rather than refusing. `EEXIST` is translated
|
|
156
|
+
* back into the same message, so nothing else changes.
|
|
157
|
+
*
|
|
150
158
|
* @param {Record<string, any>} row
|
|
151
159
|
* @param {{dir?: string, date?: string, slug?: string}} [opts]
|
|
152
160
|
* @returns {string} the path written, relative to `opts.dir`'s base
|
|
@@ -158,10 +166,14 @@ export function writeEvidenceEntry(row, opts = {}) {
|
|
|
158
166
|
mkdirSync(dir, { recursive: true })
|
|
159
167
|
const file = `${date}-${slug || 'entry'}.json`
|
|
160
168
|
const path = join(dir, file)
|
|
161
|
-
|
|
162
|
-
|
|
169
|
+
try {
|
|
170
|
+
writeFileSync(path, `${JSON.stringify({ ...row, date }, null, 2)}\n`, { flag: 'wx' })
|
|
171
|
+
} catch (err) {
|
|
172
|
+
if (err && err.code === 'EEXIST') {
|
|
173
|
+
throw new Error(`${path} already exists — choose a more specific slug or date`)
|
|
174
|
+
}
|
|
175
|
+
throw err
|
|
163
176
|
}
|
|
164
|
-
writeFileSync(path, `${JSON.stringify({ ...row, date }, null, 2)}\n`)
|
|
165
177
|
return path
|
|
166
178
|
}
|
|
167
179
|
|
|
@@ -54,6 +54,29 @@
|
|
|
54
54
|
# something must be re-run — but the message says which, so nobody debugs a
|
|
55
55
|
# phantom.
|
|
56
56
|
#
|
|
57
|
+
# ## A conflicting PR has no checks, and never will
|
|
58
|
+
#
|
|
59
|
+
# GitHub cannot compute a merge commit for a branch that conflicts with its
|
|
60
|
+
# base, and it creates **no check runs at all** for such a PR — `gh pr checks`
|
|
61
|
+
# reports "no checks reported" and the rollup stays empty until the branch is
|
|
62
|
+
# rebased. From the checks alone that is indistinguishable from "CI has not
|
|
63
|
+
# started yet", so this script would wait out its entire timeout on a PR whose
|
|
64
|
+
# checks can never arrive. On 2026-08-03 PR #1243 spent over ten minutes
|
|
65
|
+
# printing "Waiting on 5 required check(s)" while one API field said why
|
|
66
|
+
# (#1246).
|
|
67
|
+
#
|
|
68
|
+
# So each poll also reads `mergeable`:
|
|
69
|
+
#
|
|
70
|
+
# CONFLICTING exit 2 immediately, naming the cause and the remedy. Still
|
|
71
|
+
# "cannot tell", never a pass — the change is failing *fast*,
|
|
72
|
+
# not failing open.
|
|
73
|
+
# UNKNOWN keep waiting. GitHub returns it transiently while it computes
|
|
74
|
+
# mergeability, which it always does for a few seconds after a
|
|
75
|
+
# push, so treating it as a conflict would be a fresh fail-fast
|
|
76
|
+
# bug of exactly the shape this script exists to prevent.
|
|
77
|
+
# anything keep waiting on the checks as before. An unreadable field
|
|
78
|
+
# else (old gh, missing scope) must never become a verdict.
|
|
79
|
+
#
|
|
57
80
|
# ## Usage
|
|
58
81
|
#
|
|
59
82
|
# sh scripts/wait-for-checks.sh <pr-number> [-R owner/repo]
|
|
@@ -69,7 +92,10 @@ TIMEOUT="${WAIT_FOR_CHECKS_TIMEOUT:-1800}"
|
|
|
69
92
|
INTERVAL="${WAIT_FOR_CHECKS_INTERVAL:-30}"
|
|
70
93
|
|
|
71
94
|
usage() {
|
|
72
|
-
|
|
95
|
+
# Print the whole header block, however long it grows: from line 2 up to the
|
|
96
|
+
# first line that is not a comment. A hard-coded end line silently truncates
|
|
97
|
+
# the help the moment anyone documents something new above it.
|
|
98
|
+
sed -n '2,/^[^#]/p' "$0" | sed -n 's/^# \{0,1\}//p'
|
|
73
99
|
exit 2
|
|
74
100
|
}
|
|
75
101
|
|
|
@@ -156,6 +182,21 @@ prev_count=-1
|
|
|
156
182
|
rollup=""
|
|
157
183
|
|
|
158
184
|
while :; do
|
|
185
|
+
# Read this every poll, not once up front: a PR is frequently UNKNOWN for the
|
|
186
|
+
# first few seconds after a push and only then resolves, and a PR that starts
|
|
187
|
+
# clean can be made CONFLICTING at any moment by a merge into the base branch.
|
|
188
|
+
mergeable=$(gh_pr view "$PR" --json mergeable --jq '.mergeable' 2>/dev/null) || mergeable=""
|
|
189
|
+
|
|
190
|
+
if [ "$mergeable" = "CONFLICTING" ]; then
|
|
191
|
+
echo "${RED}wait-for-checks: PR $PR conflicts with $base.${OFF}" >&2
|
|
192
|
+
echo "GitHub creates no check runs for a PR whose merge commit it cannot" >&2
|
|
193
|
+
echo "compute, so the checks this is waiting for will never appear. Rebase" >&2
|
|
194
|
+
echo "the branch on $base (or merge $base into it) and push — CI starts as" >&2
|
|
195
|
+
echo "soon as the conflict clears." >&2
|
|
196
|
+
echo "Not a failure and not a pass: this is 'cannot tell'." >&2
|
|
197
|
+
exit 2
|
|
198
|
+
fi
|
|
199
|
+
|
|
159
200
|
rollup=$(gh_pr view "$PR" --json statusCheckRollup --jq '
|
|
160
201
|
[ .statusCheckRollup[]?
|
|
161
202
|
| { name: (.name // .context),
|
|
@@ -203,6 +244,10 @@ EOF
|
|
|
203
244
|
if [ "$count" = "0" ]; then
|
|
204
245
|
# The exact case the naive loop gets wrong, so name it explicitly.
|
|
205
246
|
echo "No checks ever appeared on PR $PR. That is 'cannot tell', not 'green'." >&2
|
|
247
|
+
if [ "$mergeable" = "UNKNOWN" ]; then
|
|
248
|
+
echo "GitHub never resolved this PR's mergeability (mergeable=UNKNOWN), which" >&2
|
|
249
|
+
echo "is often how a conflict looks before it settles. Check it by hand." >&2
|
|
250
|
+
fi
|
|
206
251
|
else
|
|
207
252
|
echo "Still unfinished:" >&2
|
|
208
253
|
printf '%s\n' "$rollup" | awk -F'\t' 'NF && ($2 == "" || $2 == "PENDING" || $2 == "IN_PROGRESS" || $2 == "QUEUED" || $2 == "WAITING") { print " " $1 }' >&2
|