next-issue 2.4.1 → 2.4.3

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/github.mjs CHANGED
@@ -1,21 +1,23 @@
1
- import { must, run } from "./exec.mjs";
1
+ import { must, run } from './exec.mjs';
2
2
  const CHECK_PENDING_CODE = 8;
3
3
  const READ_TRIES = 3;
4
4
  const NETWORK_TRIES = 3;
5
5
  const NETWORK_FAULT = /unexpected EOF|connection reset|connection refused|i\/o timeout|TLS handshake timeout|HTTP 50[234]/i;
6
6
  const KNOWN_LABELS = new Map();
7
7
  const NO_CHECKS = /no checks reported/i;
8
- const FAILED_CONCLUSIONS = new Set(["failure", "timed_out", "startup_failure"]);
8
+ const FAILED_CONCLUSIONS = new Set(['failure', 'timed_out', 'startup_failure']);
9
9
  function slug(repo) {
10
10
  return `${repo.owner}/${repo.name}`;
11
11
  }
12
12
  async function repeatable(args, options) {
13
13
  for (let attempt = 1;; attempt += 1) {
14
14
  try {
15
- return await must("gh", args, options);
15
+ return await must('gh', args, options);
16
16
  }
17
17
  catch (error) {
18
- if (attempt >= NETWORK_TRIES || !(error instanceof Error) || !NETWORK_FAULT.test(error.message)) {
18
+ if (attempt >= NETWORK_TRIES ||
19
+ !(error instanceof Error) ||
20
+ !NETWORK_FAULT.test(error.message)) {
19
21
  throw error;
20
22
  }
21
23
  await new Promise((resolve) => setTimeout(resolve, attempt * 1000));
@@ -23,95 +25,95 @@ async function repeatable(args, options) {
23
25
  }
24
26
  }
25
27
  async function gh(repo, args) {
26
- return repeatable([...args, "--repo", slug(repo)], { cwd: repo.root });
28
+ return repeatable([...args, '--repo', slug(repo)], { cwd: repo.root });
27
29
  }
28
30
  async function ghOnce(repo, args, input) {
29
- return must("gh", [...args, "--repo", slug(repo)], { cwd: repo.root, input });
31
+ return must('gh', [...args, '--repo', slug(repo)], { cwd: repo.root, input });
30
32
  }
31
33
  export async function defaultBranch(repo) {
32
- const raw = await repeatable(["repo", "view", slug(repo), "--json", "defaultBranchRef"], {
34
+ const raw = await repeatable(['repo', 'view', slug(repo), '--json', 'defaultBranchRef'], {
33
35
  cwd: repo.root,
34
36
  });
35
37
  return JSON.parse(raw).defaultBranchRef.name;
36
38
  }
37
- const ISSUE_FIELDS = "number,title,body,createdAt,labels";
39
+ const ISSUE_FIELDS = 'number,title,body,createdAt,labels';
38
40
  function toIssue(item) {
39
41
  return {
40
42
  number: item.number,
41
43
  title: item.title,
42
- body: item.body ?? "",
44
+ body: item.body ?? '',
43
45
  createdAt: item.createdAt,
44
46
  labels: item.labels.map((label) => label.name),
45
47
  };
46
48
  }
47
49
  export async function openIssues(repo, limit, labels) {
48
- const filter = labels.length === 0 ? "" : ` label:${labels.map((label) => `"${label}"`).join(",")}`;
50
+ const filter = labels.length === 0 ? '' : ` label:${labels.map((label) => `"${label}"`).join(',')}`;
49
51
  const raw = await gh(repo, [
50
- "issue",
51
- "list",
52
- "--state",
53
- "open",
54
- "--search",
52
+ 'issue',
53
+ 'list',
54
+ '--state',
55
+ 'open',
56
+ '--search',
55
57
  `sort:created-asc${filter}`,
56
- "--limit",
58
+ '--limit',
57
59
  String(limit),
58
- "--json",
60
+ '--json',
59
61
  ISSUE_FIELDS,
60
62
  ]);
61
63
  const parsed = JSON.parse(raw);
62
64
  return parsed.map(toIssue).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
63
65
  }
64
66
  export async function viewIssue(repo, issue) {
65
- const raw = await gh(repo, ["issue", "view", String(issue), "--json", `${ISSUE_FIELDS},state`]);
67
+ const raw = await gh(repo, ['issue', 'view', String(issue), '--json', `${ISSUE_FIELDS},state`]);
66
68
  const parsed = JSON.parse(raw);
67
- return { ...toIssue(parsed), open: parsed.state === "OPEN" };
69
+ return { ...toIssue(parsed), open: parsed.state === 'OPEN' };
68
70
  }
69
71
  export async function issueComments(repo, issue) {
70
- const raw = await gh(repo, ["issue", "view", String(issue), "--json", "comments"]);
72
+ const raw = await gh(repo, ['issue', 'view', String(issue), '--json', 'comments']);
71
73
  const parsed = JSON.parse(raw);
72
74
  return parsed.comments.map((comment) => `@${comment.author.login}: ${comment.body}`);
73
75
  }
74
76
  export async function openBlockers(repo, issue) {
75
77
  const raw = await repeatable([
76
- "api",
77
- "--paginate",
78
+ 'api',
79
+ '--paginate',
78
80
  `repos/${slug(repo)}/issues/${issue}/dependencies/blocked_by`,
79
- "--jq",
81
+ '--jq',
80
82
  '.[] | select(.state == "open") | (.repository_url | split("/") | .[-2:] | join("/")) + "#" + (.number | tostring)',
81
83
  ], { cwd: repo.root });
82
84
  return raw
83
- .split("\n")
85
+ .split('\n')
84
86
  .filter((line) => line.length > 0)
85
87
  .map((ref) => (ref.startsWith(`${slug(repo)}#`) ? ref.slice(slug(repo).length) : ref));
86
88
  }
87
89
  async function labelsOf(repo, kind, number) {
88
- const raw = await gh(repo, [kind, "view", String(number), "--json", "labels"]);
90
+ const raw = await gh(repo, [kind, 'view', String(number), '--json', 'labels']);
89
91
  const parsed = JSON.parse(raw);
90
92
  return parsed.labels.map((label) => label.name);
91
93
  }
92
94
  export function labelArgs(add, remove, current) {
93
- const args = ["--add-label", add];
95
+ const args = ['--add-label', add];
94
96
  for (const name of remove.filter((item) => item !== add && current.includes(item))) {
95
- args.push("--remove-label", name);
97
+ args.push('--remove-label', name);
96
98
  }
97
99
  return args;
98
100
  }
99
101
  export async function setLabel(repo, kind, number, add, remove) {
100
102
  await ensureLabel(repo, add);
101
103
  const current = await labelsOf(repo, kind, number);
102
- await gh(repo, [kind, "edit", String(number), ...labelArgs(add, remove, current)]);
104
+ await gh(repo, [kind, 'edit', String(number), ...labelArgs(add, remove, current)]);
103
105
  }
104
106
  async function ensureLabel(repo, label) {
105
107
  let known = KNOWN_LABELS.get(slug(repo));
106
108
  if (known === undefined) {
107
- const raw = await gh(repo, ["label", "list", "--limit", "1000", "--json", "name"]);
109
+ const raw = await gh(repo, ['label', 'list', '--limit', '1000', '--json', 'name']);
108
110
  known = new Set(JSON.parse(raw).map((item) => item.name));
109
111
  KNOWN_LABELS.set(slug(repo), known);
110
112
  }
111
113
  if (known.has(label)) {
112
114
  return;
113
115
  }
114
- const result = await run("gh", ["label", "create", label, "--repo", slug(repo)], {
116
+ const result = await run('gh', ['label', 'create', label, '--repo', slug(repo)], {
115
117
  cwd: repo.root,
116
118
  });
117
119
  if (result.code === 0) {
@@ -119,23 +121,32 @@ async function ensureLabel(repo, label) {
119
121
  }
120
122
  }
121
123
  export async function findPr(repo, branch) {
122
- const raw = await gh(repo, ["pr", "list", "--head", branch, "--state", "open", "--json", "number"]);
124
+ const raw = await gh(repo, [
125
+ 'pr',
126
+ 'list',
127
+ '--head',
128
+ branch,
129
+ '--state',
130
+ 'open',
131
+ '--json',
132
+ 'number',
133
+ ]);
123
134
  const parsed = JSON.parse(raw);
124
135
  return parsed[0]?.number;
125
136
  }
126
137
  export async function createPr(repo, branch, base, title, body, draft) {
127
138
  const created = await ghOnce(repo, [
128
- "pr",
129
- "create",
130
- "--head",
139
+ 'pr',
140
+ 'create',
141
+ '--head',
131
142
  branch,
132
- "--base",
143
+ '--base',
133
144
  base,
134
- "--title",
145
+ '--title',
135
146
  title,
136
- "--body",
147
+ '--body',
137
148
  body,
138
- ...(draft ? ["--draft"] : []),
149
+ ...(draft ? ['--draft'] : []),
139
150
  ]);
140
151
  const number = prNumber(created) ?? (await findPr(repo, branch));
141
152
  if (number === undefined) {
@@ -148,34 +159,44 @@ export function prNumber(output) {
148
159
  return match === null ? undefined : Number(match[1]);
149
160
  }
150
161
  async function prHead(repo, pr) {
151
- return gh(repo, ["pr", "view", String(pr), "--json", "headRefOid", "--jq", ".headRefOid"]);
162
+ return gh(repo, ['pr', 'view', String(pr), '--json', 'headRefOid', '--jq', '.headRefOid']);
152
163
  }
153
164
  async function readChecks(repo, pr) {
154
- const result = await run("gh", ["pr", "checks", String(pr), "--json", "name,bucket,link,description", "--repo", slug(repo)], { cwd: repo.root });
165
+ const result = await run('gh', ['pr', 'checks', String(pr), '--json', 'name,bucket,link,description', '--repo', slug(repo)], { cwd: repo.root });
155
166
  const output = result.stdout.trim();
156
167
  if (output.length === 0) {
157
168
  if (NO_CHECKS.test(result.stderr)) {
158
- return "none";
169
+ return 'none';
159
170
  }
160
171
  throw new Error(`gh pr checks ${pr} failed with code ${result.code}: ${result.stderr.trim()}`);
161
172
  }
162
173
  const checks = JSON.parse(output);
163
- return checks.length === 0 ? "none" : checks;
174
+ return checks.length === 0 ? 'none' : checks;
164
175
  }
165
176
  async function watchOnce(repo, pr, intervalSeconds, timeoutMs, show) {
166
- const watch = await run("gh", ["pr", "checks", String(pr), "--watch", "--fail-fast", "--interval", String(intervalSeconds), "--repo", slug(repo)], { cwd: repo.root, show, timeoutMs });
177
+ const watch = await run('gh', [
178
+ 'pr',
179
+ 'checks',
180
+ String(pr),
181
+ '--watch',
182
+ '--fail-fast',
183
+ '--interval',
184
+ String(intervalSeconds),
185
+ '--repo',
186
+ slug(repo),
187
+ ], { cwd: repo.root, show, timeoutMs });
167
188
  if (watch.timedOut) {
168
- return "timeout";
189
+ return 'timeout';
169
190
  }
170
191
  if (watch.code === 0) {
171
- return "pass";
192
+ return 'pass';
172
193
  }
173
194
  if (watch.code === CHECK_PENDING_CODE) {
174
- return "pending";
195
+ return 'pending';
175
196
  }
176
197
  const checks = await readChecks(repo, pr);
177
- if (checks !== "none" && checks.some((check) => check.bucket === "fail")) {
178
- return "fail";
198
+ if (checks !== 'none' && checks.some((check) => check.bucket === 'fail')) {
199
+ return 'fail';
179
200
  }
180
201
  throw new Error(`gh pr checks ${pr} --watch failed with code ${watch.code} and no check failed: ${watch.stderr.trim()}`);
181
202
  }
@@ -190,7 +211,7 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
190
211
  let failures = 0;
191
212
  for (;;) {
192
213
  if (rest() === 0) {
193
- return "timeout";
214
+ return 'timeout';
194
215
  }
195
216
  const state = await read(probe, options.head, rest(), () => (shown ??= clock.now()));
196
217
  if (state instanceof Error) {
@@ -201,10 +222,10 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
201
222
  }
202
223
  else {
203
224
  failures = 0;
204
- if (state === "none" && clock.now() - (shown ?? 0) >= options.graceMs) {
205
- return "none";
225
+ if (state === 'none' && clock.now() - (shown ?? 0) >= options.graceMs) {
226
+ return 'none';
206
227
  }
207
- if (state !== "none" && state !== "behind" && state !== "pending") {
228
+ if (state !== 'none' && state !== 'behind' && state !== 'pending') {
208
229
  return state;
209
230
  }
210
231
  }
@@ -214,11 +235,11 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
214
235
  async function read(probe, head, timeoutMs, shown) {
215
236
  try {
216
237
  if (head !== undefined && (await probe.head()) !== head) {
217
- return "behind";
238
+ return 'behind';
218
239
  }
219
240
  shown();
220
- if ((await probe.list()) === "none") {
221
- return "none";
241
+ if ((await probe.list()) === 'none') {
242
+ return 'none';
222
243
  }
223
244
  return await probe.watch(timeoutMs);
224
245
  }
@@ -229,17 +250,17 @@ async function read(probe, head, timeoutMs, shown) {
229
250
  export function waitForChecks(repo, pr, options) {
230
251
  return pollChecks({
231
252
  head: () => prHead(repo, pr),
232
- list: async () => ((await readChecks(repo, pr)) === "none" ? "none" : "some"),
253
+ list: async () => ((await readChecks(repo, pr)) === 'none' ? 'none' : 'some'),
233
254
  watch: (timeoutMs) => watchOnce(repo, pr, options.intervalSeconds, timeoutMs, options.show),
234
255
  }, options);
235
256
  }
236
257
  export async function failedCheckLogs(repo, pr, maxChars) {
237
258
  const checks = await readChecks(repo, pr);
238
- if (checks === "none") {
239
- return "The checks failed, but gh reports no check for the pull request.";
259
+ if (checks === 'none') {
260
+ return 'The checks failed, but gh reports no check for the pull request.';
240
261
  }
241
262
  const failed = checks
242
- .filter((check) => check.bucket === "fail")
263
+ .filter((check) => check.bucket === 'fail')
243
264
  .map((check) => ({
244
265
  heading: `## ${check.name}\n${check.description}`,
245
266
  runId: /\/actions\/runs\/(\d+)/.exec(check.link)?.[1],
@@ -247,25 +268,32 @@ export async function failedCheckLogs(repo, pr, maxChars) {
247
268
  return runLogs(repo, failed, maxChars);
248
269
  }
249
270
  async function baseRuns(repo, sha) {
250
- const raw = await gh(repo, ["run", "list", "--commit", sha, "--json", "databaseId,name,status,conclusion"]);
271
+ const raw = await gh(repo, [
272
+ 'run',
273
+ 'list',
274
+ '--commit',
275
+ sha,
276
+ '--json',
277
+ 'databaseId,name,status,conclusion',
278
+ ]);
251
279
  return JSON.parse(raw);
252
280
  }
253
281
  function baseState(runs) {
254
- if (runs.some((entry) => entry.status === "completed" && FAILED_CONCLUSIONS.has(entry.conclusion))) {
255
- return "fail";
282
+ if (runs.some((entry) => entry.status === 'completed' && FAILED_CONCLUSIONS.has(entry.conclusion))) {
283
+ return 'fail';
256
284
  }
257
- return runs.every((entry) => entry.status === "completed") ? "pass" : "pending";
285
+ return runs.every((entry) => entry.status === 'completed') ? 'pass' : 'pending';
258
286
  }
259
287
  export function waitForBaseChecks(repo, sha, options) {
260
288
  return pollChecks({
261
289
  head: async () => sha,
262
- list: async () => ((await baseRuns(repo, sha)).length === 0 ? "none" : "some"),
290
+ list: async () => ((await baseRuns(repo, sha)).length === 0 ? 'none' : 'some'),
263
291
  watch: async () => baseState(await baseRuns(repo, sha)),
264
292
  }, options);
265
293
  }
266
294
  export async function failedBaseLogs(repo, sha, maxChars) {
267
295
  const failed = (await baseRuns(repo, sha))
268
- .filter((entry) => entry.status === "completed" && FAILED_CONCLUSIONS.has(entry.conclusion))
296
+ .filter((entry) => entry.status === 'completed' && FAILED_CONCLUSIONS.has(entry.conclusion))
269
297
  .map((entry) => ({ heading: `## ${entry.name}`, runId: String(entry.databaseId) }));
270
298
  return runLogs(repo, failed, maxChars);
271
299
  }
@@ -277,7 +305,7 @@ async function runLogs(repo, failed, maxChars) {
277
305
  if (runId === undefined) {
278
306
  continue;
279
307
  }
280
- const logs = await run("gh", ["run", "view", runId, "--log-failed", "--repo", slug(repo)], {
308
+ const logs = await run('gh', ['run', 'view', runId, '--log-failed', '--repo', slug(repo)], {
281
309
  cwd: repo.root,
282
310
  tailChars: share,
283
311
  });
@@ -286,43 +314,44 @@ async function runLogs(repo, failed, maxChars) {
286
314
  continue;
287
315
  }
288
316
  const reason = [`The log read failed with code ${logs.code}.`, logs.stderr.trim()]
289
- .filter((line) => line !== "")
290
- .join(" ");
291
- parts.push([logs.stdout.trim(), reason].filter((line) => line !== "").join("\n"));
317
+ .filter((line) => line !== '')
318
+ .join(' ');
319
+ parts.push([logs.stdout.trim(), reason].filter((line) => line !== '').join('\n'));
292
320
  }
293
- return parts.join("\n\n");
321
+ return parts.join('\n\n');
294
322
  }
295
323
  export async function markPrReady(repo, pr) {
296
- const result = await run("gh", ["pr", "ready", String(pr), "--repo", slug(repo)], {
324
+ const result = await run('gh', ['pr', 'ready', String(pr), '--repo', slug(repo)], {
297
325
  cwd: repo.root,
298
326
  });
299
327
  return result.code === 0;
300
328
  }
301
329
  export async function mergePr(repo, pr) {
302
- const result = await run("gh", ["pr", "merge", String(pr), "--squash", "--delete-branch", "--repo", slug(repo)], { cwd: repo.root });
330
+ const result = await run('gh', ['pr', 'merge', String(pr), '--squash', '--delete-branch', '--repo', slug(repo)], { cwd: repo.root });
303
331
  return result.code === 0;
304
332
  }
305
333
  export async function prMerged(repo, pr) {
306
- return (await gh(repo, ["pr", "view", String(pr), "--json", "state", "--jq", ".state"])).trim() === "MERGED";
334
+ return ((await gh(repo, ['pr', 'view', String(pr), '--json', 'state', '--jq', '.state'])).trim() ===
335
+ 'MERGED');
307
336
  }
308
337
  export async function mergeCommit(repo, pr, options, clock = REAL_CLOCK) {
309
338
  const deadline = clock.now() + options.timeoutMs;
310
339
  for (;;) {
311
- const raw = await gh(repo, ["pr", "view", String(pr), "--json", "state,mergeCommit"]);
340
+ const raw = await gh(repo, ['pr', 'view', String(pr), '--json', 'state,mergeCommit']);
312
341
  const view = JSON.parse(raw);
313
- if (view.state === "MERGED" && view.mergeCommit?.oid) {
342
+ if (view.state === 'MERGED' && view.mergeCommit?.oid) {
314
343
  return view.mergeCommit.oid;
315
344
  }
316
345
  const rest = deadline - clock.now();
317
- if (view.state === "CLOSED" || rest <= 0) {
346
+ if (view.state === 'CLOSED' || rest <= 0) {
318
347
  return undefined;
319
348
  }
320
349
  await clock.sleep(Math.min(options.intervalSeconds * 1000, rest));
321
350
  }
322
351
  }
323
352
  export async function reopenIssue(repo, issue) {
324
- await gh(repo, ["issue", "reopen", String(issue)]);
353
+ await gh(repo, ['issue', 'reopen', String(issue)]);
325
354
  }
326
355
  export async function commentOnPr(repo, pr, body) {
327
- await ghOnce(repo, ["pr", "comment", String(pr), "--body-file", "-"], body);
356
+ await ghOnce(repo, ['pr', 'comment', String(pr), '--body-file', '-'], body);
328
357
  }
@@ -1,25 +1,25 @@
1
- import { stopChildren } from "./exec.mjs";
1
+ import { stopChildren } from './exec.mjs';
2
2
  export function listenForInterrupts(say) {
3
3
  let presses = 0;
4
4
  let wake;
5
5
  const press = () => {
6
6
  presses += 1;
7
7
  if (presses === 1 && wake !== undefined) {
8
- say("Stopping now.");
8
+ say('Stopping now.');
9
9
  wake();
10
10
  }
11
11
  else if (presses === 1) {
12
- say("Stopping after this issue. Press Ctrl-C again to stop after this round.");
12
+ say('Stopping after this issue. Press Ctrl-C again to stop after this round.');
13
13
  }
14
14
  else if (presses === 2) {
15
- say("Stopping after this round. Press Ctrl-C again to stop now.");
15
+ say('Stopping after this round. Press Ctrl-C again to stop now.');
16
16
  }
17
17
  else {
18
- process.off("SIGINT", press);
19
- stopChildren("SIGINT");
18
+ process.off('SIGINT', press);
19
+ stopChildren('SIGINT');
20
20
  }
21
21
  };
22
- process.on("SIGINT", press);
22
+ process.on('SIGINT', press);
23
23
  return {
24
24
  afterIssue: () => presses >= 1,
25
25
  afterRound: () => presses >= 2,
package/dist/loop.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  export const BACK_OFF_MINUTES = [1, 2, 3, 5, 10];
2
- export async function runIssues(args, loop) {
2
+ export async function runIssues(args, loop, seen = new Set()) {
3
3
  const reports = [];
4
- const seen = new Set();
5
4
  while (args.max === undefined || handled(reports) < args.max) {
6
5
  let issues;
7
6
  try {
@@ -20,7 +19,7 @@ export async function runIssues(args, loop) {
20
19
  if (loop.stop() || report.halt === true) {
21
20
  return { reports, stoppedAt: issue.number };
22
21
  }
23
- if (args.once && report.outcome !== "skipped") {
22
+ if (args.once && report.outcome !== 'skipped') {
24
23
  break;
25
24
  }
26
25
  }
@@ -29,10 +28,16 @@ export async function runIssues(args, loop) {
29
28
  export async function watchIssues(args, watch) {
30
29
  const limit = args.once ? 1 : args.max;
31
30
  const reports = [];
31
+ const failed = new Set();
32
32
  let step = 0;
33
33
  for (;;) {
34
- const pass = await runIssues({ once: false, max: limit === undefined ? undefined : limit - handled(reports) }, watch);
34
+ const pass = await runIssues({ once: false, max: limit === undefined ? undefined : limit - handled(reports) }, watch, new Set(failed));
35
35
  reports.push(...pass.reports);
36
+ for (const report of pass.reports) {
37
+ if (report.outcome === 'error') {
38
+ failed.add(report.issue);
39
+ }
40
+ }
36
41
  if (handled(pass.reports) > 0) {
37
42
  await watch.passEnd(pass.reports);
38
43
  step = 0;
@@ -51,8 +56,10 @@ export async function watchIssues(args, watch) {
51
56
  }
52
57
  }
53
58
  export function exitCode(reports) {
54
- return reports.some((report) => report.outcome === "needs-human" || report.outcome === "error") ? 1 : 0;
59
+ return reports.some((report) => report.outcome === 'needs-human' || report.outcome === 'error')
60
+ ? 1
61
+ : 0;
55
62
  }
56
63
  function handled(reports) {
57
- return reports.filter((report) => report.outcome !== "skipped").length;
64
+ return reports.filter((report) => report.outcome !== 'skipped').length;
58
65
  }
package/dist/observe.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { createWriteStream } from "node:fs";
2
- import { mkdir, readdir, rm, stat } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { STATE_DIR } from "./state.mjs";
1
+ import { createWriteStream } from 'node:fs';
2
+ import { mkdir, readdir, rm, stat } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { STATE_DIR } from './state.mjs';
5
5
  const VISIBILITY = { quiet: 0, normal: 1, verbose: 2 };
6
6
  export function emptyUsage() {
7
7
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
@@ -63,11 +63,11 @@ export class Recorder {
63
63
  this.runId = runId;
64
64
  }
65
65
  static async create(root, level, listener) {
66
- const runId = new Date().toISOString().replace(/[:.]/g, "-");
67
- const dir = join(root, STATE_DIR, "runs");
66
+ const runId = new Date().toISOString().replace(/[:.]/g, '-');
67
+ const dir = join(root, STATE_DIR, 'runs');
68
68
  await mkdir(dir, { recursive: true });
69
69
  const file = join(dir, `${runId}.jsonl`);
70
- const sink = new Sink(createWriteStream(file, { flags: "a" }), level, file, listener);
70
+ const sink = new Sink(createWriteStream(file, { flags: 'a' }), level, file, listener);
71
71
  return new Recorder(sink, {}, runId);
72
72
  }
73
73
  get logFile() {
@@ -79,7 +79,7 @@ export class Recorder {
79
79
  scope(fields) {
80
80
  return new Recorder(this.sink, { ...this.base, ...fields }, this.runId);
81
81
  }
82
- event(kind, fields = {}, visibility = "normal") {
82
+ event(kind, fields = {}, visibility = 'normal') {
83
83
  this.sink.count(kind);
84
84
  const record = {
85
85
  ts: new Date().toISOString(),
@@ -95,18 +95,18 @@ export class Recorder {
95
95
  }
96
96
  usage(role, usage, ms, fields = {}) {
97
97
  this.sink.add(role, usage, ms);
98
- this.event("usage", { role, ms, ...usage, ...fields }, "normal");
98
+ this.event('usage', { role, ms, ...usage, ...fields }, 'normal');
99
99
  }
100
100
  async step(kind, fields, run) {
101
101
  const started = Date.now();
102
- this.event(`${kind}.start`, fields, "verbose");
102
+ this.event(`${kind}.start`, fields, 'verbose');
103
103
  try {
104
104
  const result = await run();
105
105
  this.event(`${kind}.end`, { ...fields, ms: Date.now() - started });
106
106
  return result;
107
107
  }
108
108
  catch (error) {
109
- this.event(`${kind}.error`, { ...fields, ms: Date.now() - started, error: message(error) }, "quiet");
109
+ this.event(`${kind}.error`, { ...fields, ms: Date.now() - started, error: message(error) }, 'quiet');
110
110
  throw error;
111
111
  }
112
112
  }
@@ -140,7 +140,7 @@ export async function pruneRuns(root, keepDays) {
140
140
  if (keepDays <= 0) {
141
141
  return 0;
142
142
  }
143
- const dir = join(root, STATE_DIR, "runs");
143
+ const dir = join(root, STATE_DIR, 'runs');
144
144
  let names;
145
145
  try {
146
146
  names = await readdir(dir);
@@ -170,20 +170,20 @@ export function message(error) {
170
170
  }
171
171
  function render(record) {
172
172
  const time = new Date(String(record.ts)).toTimeString().slice(0, 8);
173
- const issue = record.issue === undefined ? "" : ` #${record.issue}`;
173
+ const issue = record.issue === undefined ? '' : ` #${record.issue}`;
174
174
  const extras = Object.entries(record)
175
- .filter(([key]) => !["ts", "runId", "kind", "issue"].includes(key))
175
+ .filter(([key]) => !['ts', 'runId', 'kind', 'issue'].includes(key))
176
176
  .map(([key, value]) => `${key}=${short(value)}`)
177
- .join(" ");
178
- return `${time}${issue} ${String(record.kind)}${extras.length > 0 ? ` ${extras}` : ""}\n`;
177
+ .join(' ');
178
+ return `${time}${issue} ${String(record.kind)}${extras.length > 0 ? ` ${extras}` : ''}\n`;
179
179
  }
180
180
  function short(value) {
181
181
  if (value === undefined || value === null) {
182
- return "-";
182
+ return '-';
183
183
  }
184
- if (typeof value === "number") {
184
+ if (typeof value === 'number') {
185
185
  return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(6)));
186
186
  }
187
- const text = String(value).replace(/\s+/g, " ");
187
+ const text = String(value).replace(/\s+/g, ' ');
188
188
  return text.length > 120 ? `${text.slice(0, 117)}...` : text;
189
189
  }