next-issue 2.4.0 → 2.4.2

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,93 +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
- export async function openIssues(repo, limit) {
49
+ export async function openIssues(repo, limit, labels) {
50
+ const filter = labels.length === 0 ? '' : ` label:${labels.map((label) => `"${label}"`).join(',')}`;
48
51
  const raw = await gh(repo, [
49
- "issue",
50
- "list",
51
- "--state",
52
- "open",
53
- "--search",
54
- "sort:created-asc",
55
- "--limit",
52
+ 'issue',
53
+ 'list',
54
+ '--state',
55
+ 'open',
56
+ '--search',
57
+ `sort:created-asc${filter}`,
58
+ '--limit',
56
59
  String(limit),
57
- "--json",
60
+ '--json',
58
61
  ISSUE_FIELDS,
59
62
  ]);
60
63
  const parsed = JSON.parse(raw);
61
64
  return parsed.map(toIssue).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
62
65
  }
63
66
  export async function viewIssue(repo, issue) {
64
- const raw = await gh(repo, ["issue", "view", String(issue), "--json", ISSUE_FIELDS]);
65
- return toIssue(JSON.parse(raw));
67
+ const raw = await gh(repo, ['issue', 'view', String(issue), '--json', `${ISSUE_FIELDS},state`]);
68
+ const parsed = JSON.parse(raw);
69
+ return { ...toIssue(parsed), open: parsed.state === 'OPEN' };
66
70
  }
67
71
  export async function issueComments(repo, issue) {
68
- const raw = await gh(repo, ["issue", "view", String(issue), "--json", "comments"]);
72
+ const raw = await gh(repo, ['issue', 'view', String(issue), '--json', 'comments']);
69
73
  const parsed = JSON.parse(raw);
70
74
  return parsed.comments.map((comment) => `@${comment.author.login}: ${comment.body}`);
71
75
  }
72
76
  export async function openBlockers(repo, issue) {
73
77
  const raw = await repeatable([
74
- "api",
75
- "--paginate",
78
+ 'api',
79
+ '--paginate',
76
80
  `repos/${slug(repo)}/issues/${issue}/dependencies/blocked_by`,
77
- "--jq",
81
+ '--jq',
78
82
  '.[] | select(.state == "open") | (.repository_url | split("/") | .[-2:] | join("/")) + "#" + (.number | tostring)',
79
83
  ], { cwd: repo.root });
80
84
  return raw
81
- .split("\n")
85
+ .split('\n')
82
86
  .filter((line) => line.length > 0)
83
87
  .map((ref) => (ref.startsWith(`${slug(repo)}#`) ? ref.slice(slug(repo).length) : ref));
84
88
  }
85
89
  async function labelsOf(repo, kind, number) {
86
- const raw = await gh(repo, [kind, "view", String(number), "--json", "labels"]);
90
+ const raw = await gh(repo, [kind, 'view', String(number), '--json', 'labels']);
87
91
  const parsed = JSON.parse(raw);
88
92
  return parsed.labels.map((label) => label.name);
89
93
  }
90
94
  export function labelArgs(add, remove, current) {
91
- const args = ["--add-label", add];
95
+ const args = ['--add-label', add];
92
96
  for (const name of remove.filter((item) => item !== add && current.includes(item))) {
93
- args.push("--remove-label", name);
97
+ args.push('--remove-label', name);
94
98
  }
95
99
  return args;
96
100
  }
97
101
  export async function setLabel(repo, kind, number, add, remove) {
98
102
  await ensureLabel(repo, add);
99
103
  const current = await labelsOf(repo, kind, number);
100
- await gh(repo, [kind, "edit", String(number), ...labelArgs(add, remove, current)]);
104
+ await gh(repo, [kind, 'edit', String(number), ...labelArgs(add, remove, current)]);
101
105
  }
102
106
  async function ensureLabel(repo, label) {
103
107
  let known = KNOWN_LABELS.get(slug(repo));
104
108
  if (known === undefined) {
105
- const raw = await gh(repo, ["label", "list", "--limit", "1000", "--json", "name"]);
109
+ const raw = await gh(repo, ['label', 'list', '--limit', '1000', '--json', 'name']);
106
110
  known = new Set(JSON.parse(raw).map((item) => item.name));
107
111
  KNOWN_LABELS.set(slug(repo), known);
108
112
  }
109
113
  if (known.has(label)) {
110
114
  return;
111
115
  }
112
- const result = await run("gh", ["label", "create", label, "--repo", slug(repo)], {
116
+ const result = await run('gh', ['label', 'create', label, '--repo', slug(repo)], {
113
117
  cwd: repo.root,
114
118
  });
115
119
  if (result.code === 0) {
@@ -117,23 +121,32 @@ async function ensureLabel(repo, label) {
117
121
  }
118
122
  }
119
123
  export async function findPr(repo, branch) {
120
- 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
+ ]);
121
134
  const parsed = JSON.parse(raw);
122
135
  return parsed[0]?.number;
123
136
  }
124
137
  export async function createPr(repo, branch, base, title, body, draft) {
125
138
  const created = await ghOnce(repo, [
126
- "pr",
127
- "create",
128
- "--head",
139
+ 'pr',
140
+ 'create',
141
+ '--head',
129
142
  branch,
130
- "--base",
143
+ '--base',
131
144
  base,
132
- "--title",
145
+ '--title',
133
146
  title,
134
- "--body",
147
+ '--body',
135
148
  body,
136
- ...(draft ? ["--draft"] : []),
149
+ ...(draft ? ['--draft'] : []),
137
150
  ]);
138
151
  const number = prNumber(created) ?? (await findPr(repo, branch));
139
152
  if (number === undefined) {
@@ -146,34 +159,44 @@ export function prNumber(output) {
146
159
  return match === null ? undefined : Number(match[1]);
147
160
  }
148
161
  async function prHead(repo, pr) {
149
- return gh(repo, ["pr", "view", String(pr), "--json", "headRefOid", "--jq", ".headRefOid"]);
162
+ return gh(repo, ['pr', 'view', String(pr), '--json', 'headRefOid', '--jq', '.headRefOid']);
150
163
  }
151
164
  async function readChecks(repo, pr) {
152
- 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 });
153
166
  const output = result.stdout.trim();
154
167
  if (output.length === 0) {
155
168
  if (NO_CHECKS.test(result.stderr)) {
156
- return "none";
169
+ return 'none';
157
170
  }
158
171
  throw new Error(`gh pr checks ${pr} failed with code ${result.code}: ${result.stderr.trim()}`);
159
172
  }
160
173
  const checks = JSON.parse(output);
161
- return checks.length === 0 ? "none" : checks;
174
+ return checks.length === 0 ? 'none' : checks;
162
175
  }
163
176
  async function watchOnce(repo, pr, intervalSeconds, timeoutMs, show) {
164
- 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 });
165
188
  if (watch.timedOut) {
166
- return "timeout";
189
+ return 'timeout';
167
190
  }
168
191
  if (watch.code === 0) {
169
- return "pass";
192
+ return 'pass';
170
193
  }
171
194
  if (watch.code === CHECK_PENDING_CODE) {
172
- return "pending";
195
+ return 'pending';
173
196
  }
174
197
  const checks = await readChecks(repo, pr);
175
- if (checks !== "none" && checks.some((check) => check.bucket === "fail")) {
176
- return "fail";
198
+ if (checks !== 'none' && checks.some((check) => check.bucket === 'fail')) {
199
+ return 'fail';
177
200
  }
178
201
  throw new Error(`gh pr checks ${pr} --watch failed with code ${watch.code} and no check failed: ${watch.stderr.trim()}`);
179
202
  }
@@ -188,7 +211,7 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
188
211
  let failures = 0;
189
212
  for (;;) {
190
213
  if (rest() === 0) {
191
- return "timeout";
214
+ return 'timeout';
192
215
  }
193
216
  const state = await read(probe, options.head, rest(), () => (shown ??= clock.now()));
194
217
  if (state instanceof Error) {
@@ -199,10 +222,10 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
199
222
  }
200
223
  else {
201
224
  failures = 0;
202
- if (state === "none" && clock.now() - (shown ?? 0) >= options.graceMs) {
203
- return "none";
225
+ if (state === 'none' && clock.now() - (shown ?? 0) >= options.graceMs) {
226
+ return 'none';
204
227
  }
205
- if (state !== "none" && state !== "behind" && state !== "pending") {
228
+ if (state !== 'none' && state !== 'behind' && state !== 'pending') {
206
229
  return state;
207
230
  }
208
231
  }
@@ -212,11 +235,11 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
212
235
  async function read(probe, head, timeoutMs, shown) {
213
236
  try {
214
237
  if (head !== undefined && (await probe.head()) !== head) {
215
- return "behind";
238
+ return 'behind';
216
239
  }
217
240
  shown();
218
- if ((await probe.list()) === "none") {
219
- return "none";
241
+ if ((await probe.list()) === 'none') {
242
+ return 'none';
220
243
  }
221
244
  return await probe.watch(timeoutMs);
222
245
  }
@@ -227,17 +250,17 @@ async function read(probe, head, timeoutMs, shown) {
227
250
  export function waitForChecks(repo, pr, options) {
228
251
  return pollChecks({
229
252
  head: () => prHead(repo, pr),
230
- list: async () => ((await readChecks(repo, pr)) === "none" ? "none" : "some"),
253
+ list: async () => ((await readChecks(repo, pr)) === 'none' ? 'none' : 'some'),
231
254
  watch: (timeoutMs) => watchOnce(repo, pr, options.intervalSeconds, timeoutMs, options.show),
232
255
  }, options);
233
256
  }
234
257
  export async function failedCheckLogs(repo, pr, maxChars) {
235
258
  const checks = await readChecks(repo, pr);
236
- if (checks === "none") {
237
- 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.';
238
261
  }
239
262
  const failed = checks
240
- .filter((check) => check.bucket === "fail")
263
+ .filter((check) => check.bucket === 'fail')
241
264
  .map((check) => ({
242
265
  heading: `## ${check.name}\n${check.description}`,
243
266
  runId: /\/actions\/runs\/(\d+)/.exec(check.link)?.[1],
@@ -245,25 +268,32 @@ export async function failedCheckLogs(repo, pr, maxChars) {
245
268
  return runLogs(repo, failed, maxChars);
246
269
  }
247
270
  async function baseRuns(repo, sha) {
248
- 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
+ ]);
249
279
  return JSON.parse(raw);
250
280
  }
251
281
  function baseState(runs) {
252
- if (runs.some((entry) => entry.status === "completed" && FAILED_CONCLUSIONS.has(entry.conclusion))) {
253
- return "fail";
282
+ if (runs.some((entry) => entry.status === 'completed' && FAILED_CONCLUSIONS.has(entry.conclusion))) {
283
+ return 'fail';
254
284
  }
255
- return runs.every((entry) => entry.status === "completed") ? "pass" : "pending";
285
+ return runs.every((entry) => entry.status === 'completed') ? 'pass' : 'pending';
256
286
  }
257
287
  export function waitForBaseChecks(repo, sha, options) {
258
288
  return pollChecks({
259
289
  head: async () => sha,
260
- list: async () => ((await baseRuns(repo, sha)).length === 0 ? "none" : "some"),
290
+ list: async () => ((await baseRuns(repo, sha)).length === 0 ? 'none' : 'some'),
261
291
  watch: async () => baseState(await baseRuns(repo, sha)),
262
292
  }, options);
263
293
  }
264
294
  export async function failedBaseLogs(repo, sha, maxChars) {
265
295
  const failed = (await baseRuns(repo, sha))
266
- .filter((entry) => entry.status === "completed" && FAILED_CONCLUSIONS.has(entry.conclusion))
296
+ .filter((entry) => entry.status === 'completed' && FAILED_CONCLUSIONS.has(entry.conclusion))
267
297
  .map((entry) => ({ heading: `## ${entry.name}`, runId: String(entry.databaseId) }));
268
298
  return runLogs(repo, failed, maxChars);
269
299
  }
@@ -275,7 +305,7 @@ async function runLogs(repo, failed, maxChars) {
275
305
  if (runId === undefined) {
276
306
  continue;
277
307
  }
278
- 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)], {
279
309
  cwd: repo.root,
280
310
  tailChars: share,
281
311
  });
@@ -284,43 +314,44 @@ async function runLogs(repo, failed, maxChars) {
284
314
  continue;
285
315
  }
286
316
  const reason = [`The log read failed with code ${logs.code}.`, logs.stderr.trim()]
287
- .filter((line) => line !== "")
288
- .join(" ");
289
- 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'));
290
320
  }
291
- return parts.join("\n\n");
321
+ return parts.join('\n\n');
292
322
  }
293
323
  export async function markPrReady(repo, pr) {
294
- 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)], {
295
325
  cwd: repo.root,
296
326
  });
297
327
  return result.code === 0;
298
328
  }
299
329
  export async function mergePr(repo, pr) {
300
- 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 });
301
331
  return result.code === 0;
302
332
  }
303
333
  export async function prMerged(repo, pr) {
304
- 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');
305
336
  }
306
337
  export async function mergeCommit(repo, pr, options, clock = REAL_CLOCK) {
307
338
  const deadline = clock.now() + options.timeoutMs;
308
339
  for (;;) {
309
- 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']);
310
341
  const view = JSON.parse(raw);
311
- if (view.state === "MERGED" && view.mergeCommit?.oid) {
342
+ if (view.state === 'MERGED' && view.mergeCommit?.oid) {
312
343
  return view.mergeCommit.oid;
313
344
  }
314
345
  const rest = deadline - clock.now();
315
- if (view.state === "CLOSED" || rest <= 0) {
346
+ if (view.state === 'CLOSED' || rest <= 0) {
316
347
  return undefined;
317
348
  }
318
349
  await clock.sleep(Math.min(options.intervalSeconds * 1000, rest));
319
350
  }
320
351
  }
321
352
  export async function reopenIssue(repo, issue) {
322
- await gh(repo, ["issue", "reopen", String(issue)]);
353
+ await gh(repo, ['issue', 'reopen', String(issue)]);
323
354
  }
324
355
  export async function commentOnPr(repo, pr, body) {
325
- await ghOnce(repo, ["pr", "comment", String(pr), "--body-file", "-"], body);
356
+ await ghOnce(repo, ['pr', 'comment', String(pr), '--body-file', '-'], body);
326
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
@@ -20,7 +20,7 @@ export async function runIssues(args, loop) {
20
20
  if (loop.stop() || report.halt === true) {
21
21
  return { reports, stoppedAt: issue.number };
22
22
  }
23
- if (args.once && report.outcome !== "skipped") {
23
+ if (args.once && report.outcome !== 'skipped') {
24
24
  break;
25
25
  }
26
26
  }
@@ -51,8 +51,10 @@ export async function watchIssues(args, watch) {
51
51
  }
52
52
  }
53
53
  export function exitCode(reports) {
54
- return reports.some((report) => report.outcome === "needs-human" || report.outcome === "error") ? 1 : 0;
54
+ return reports.some((report) => report.outcome === 'needs-human' || report.outcome === 'error')
55
+ ? 1
56
+ : 0;
55
57
  }
56
58
  function handled(reports) {
57
- return reports.filter((report) => report.outcome !== "skipped").length;
59
+ return reports.filter((report) => report.outcome !== 'skipped').length;
58
60
  }
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
  }