@henryqw/pi-pr 6.0.3 → 6.0.5

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.
@@ -12,6 +12,7 @@ import {
12
12
  extensionExecApi,
13
13
  inspectWorktree,
14
14
  isAncestor,
15
+ parseSingleOutputLine,
15
16
  readHead,
16
17
  readRemoteOid,
17
18
  requiredOid,
@@ -177,13 +178,6 @@ function parseJson(output: string, label: string): unknown {
177
178
  }
178
179
  }
179
180
 
180
- function outputLine(output: string, label: string): string {
181
- const normalized = output.replace(/\r\n/g, "\n");
182
- const lines = (normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized).split("\n");
183
- if (lines.length !== 1 || !lines[0]) throw new Error(`${label} returned invalid output`);
184
- return requiredText(lines[0], label);
185
- }
186
-
187
181
  function actionsUrl(authority: CurrentPullRequest, suffix: string): string {
188
182
  const url = new URL(authority.url.origin);
189
183
  url.pathname = `/${authority.base.repository}/actions/${suffix}`;
@@ -312,9 +306,7 @@ export class PullRequestCiFixer {
312
306
  private collectedFingerprint?: string;
313
307
 
314
308
  constructor(options: PullRequestCiFixOptions) {
315
- if (!options.authority || options.authority.lifecycle !== "open" ||
316
- options.authority.target.provenance !== "configured" || options.authority.conditions.ci !== "failure" ||
317
- options.authority.local.worktree !== "clean" || options.authority.local.head !== "equal") {
309
+ if (!options.authority || options.authority.target.provenance !== "configured") {
318
310
  throw new TypeError("CI repair requires a configured failed pull request with a clean equal local HEAD");
319
311
  }
320
312
  const head = requiredOid(options.authority.head.oid, "pull request head OID");
@@ -357,10 +349,10 @@ export class PullRequestCiFixer {
357
349
  }
358
350
 
359
351
  private async requireOriginalLocal(): Promise<void> {
360
- const branch = outputLine(
352
+ const branch = requiredText(parseSingleOutputLine(
361
353
  (await runChecked(this.exec, "git", ["branch", "--show-current"], this.options())).stdout,
362
354
  "current branch",
363
- );
355
+ ), "current branch");
364
356
  if (branch !== this.authority.target.branch) throw new Error("CI repair cancelled: current branch changed");
365
357
  if (await inspectWorktree(this.exec, this.options()) !== "clean") {
366
358
  throw new Error("CI repair requires a clean worktree with no Git operation in progress");
@@ -695,10 +687,10 @@ export class PullRequestCiFixer {
695
687
  await this.requireCollectedEvidence();
696
688
  const original = this.authority.head.oid;
697
689
  await this.requireSavedDestination(original);
698
- const branch = outputLine(
690
+ const branch = requiredText(parseSingleOutputLine(
699
691
  (await runChecked(this.exec, "git", ["branch", "--show-current"], this.options())).stdout,
700
692
  "current branch",
701
- );
693
+ ), "current branch");
702
694
  if (branch !== this.authority.target.branch) throw new Error("CI repair publish cancelled: current branch changed");
703
695
  if (await inspectWorktree(this.exec, this.options()) !== "clean") {
704
696
  throw new Error("CI repair publish requires a clean worktree with no Git operation in progress");
@@ -288,9 +288,12 @@ export function createPrCommandHandler(
288
288
  const discovery = await load(pi, ctx, undefined, undefined, base);
289
289
  commandInvocation?.assertCurrent();
290
290
  const decision = deriveRouteDecision(discovery, intent);
291
+ if (decision.kind === "feedback-blocked") {
292
+ onRouteResolved?.("blocked");
293
+ throw new Error(feedbackBlockerMessage(decision.blocker));
294
+ }
291
295
  const nextStep = decision.nextStep;
292
296
  onRouteResolved?.(nextStep);
293
- if (decision.kind === "feedback-blocked") throw new Error(feedbackBlockerMessage(decision.blocker));
294
297
  if (base !== undefined && nextStep !== "create") {
295
298
  throw new Error("/pr --base is accepted only for pull request creation");
296
299
  }
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { realpath, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
+ import { isDeepStrictEqual } from "node:util";
4
5
  import { spawnBounded, type Exec, type ExecOptions } from "@henryqw/pi-process";
5
6
  import {
6
7
  extensionConfigDir,
@@ -32,6 +33,7 @@ import {
32
33
  isAncestor,
33
34
  isRecord,
34
35
  parseNulPaths,
36
+ parseSingleOutputLine,
35
37
  parseStatusSnapshot,
36
38
  readHead,
37
39
  readRemoteOid,
@@ -342,16 +344,12 @@ function parseProjection(value: unknown): SweepFinalProjection {
342
344
  };
343
345
  }
344
346
 
345
- function sameProjection(left: SweepFinalProjection, right: SweepFinalProjection): boolean {
346
- return JSON.stringify(left) === JSON.stringify(right);
347
- }
348
-
349
347
  function projectionMatches(snapshot: FeedbackSnapshot, projection: SweepFinalProjection): boolean {
350
348
  if (feedbackContentFingerprint(snapshot) !== projection.contentFingerprint) return false;
351
349
  const items = feedbackEntries(snapshot).map(({ id, kind }) => ({ id, kind })).sort((left, right) =>
352
350
  left.id.localeCompare(right.id) || left.kind.localeCompare(right.kind));
353
351
  const threads = snapshot.reviewThreads.map(({ id, isResolved }) => ({ id, isResolved })).sort((left, right) => left.id.localeCompare(right.id));
354
- return JSON.stringify(items) === JSON.stringify(projection.items) && JSON.stringify(threads) === JSON.stringify(projection.threads);
352
+ return isDeepStrictEqual(items, projection.items) && isDeepStrictEqual(threads, projection.threads);
355
353
  }
356
354
 
357
355
  function resolutionAttempt(value: unknown, index: number): ResolutionAttempt {
@@ -463,7 +461,7 @@ function parseState(value: unknown, expectedRoot: string, expectedId: string): S
463
461
  throw new Error("push attempt head does not match publication head");
464
462
  }
465
463
  if (hasFinalProjection !== (projection !== null)) throw new Error("sweep phase and final projection are inconsistent");
466
- if (projection && (!ledger || !sameProjection(projection, buildProjection(feedback.generation, snapshot, ledger)))) {
464
+ if (projection && (!ledger || !isDeepStrictEqual(projection, buildProjection(feedback.generation, snapshot, ledger)))) {
467
465
  throw new Error("final projection does not match feedback and ledger coverage");
468
466
  }
469
467
  return state;
@@ -505,13 +503,6 @@ function status(state: SweepState): SweepStatus {
505
503
  };
506
504
  }
507
505
 
508
- function oneLine(output: string, label: string): string {
509
- const normalized = output.replace(/\r\n/g, "\n");
510
- const lines = normalized.endsWith("\n") ? normalized.slice(0, -1).split("\n") : normalized.split("\n");
511
- if (lines.length !== 1 || !lines[0]) throw new Error(`${label} returned invalid output`);
512
- return lines[0];
513
- }
514
-
515
506
  function onlyResolutionChanged(before: FeedbackSnapshot, after: FeedbackSnapshot, threadId: string): boolean {
516
507
  if (feedbackContentFingerprint(before) !== feedbackContentFingerprint(after)) return false;
517
508
  const beforeStates = before.reviewThreads.map(({ id, isResolved }) => ({ id, isResolved })).sort((left, right) => left.id.localeCompare(right.id));
@@ -564,7 +555,7 @@ export class PullRequestCommentSweep {
564
555
  }
565
556
 
566
557
  private async location(): Promise<{ root: string; id: string; path: string }> {
567
- const top = oneLine((await runChecked(this.exec, "git", ["rev-parse", "--show-toplevel"], this.options())).stdout, "Git worktree root");
558
+ const top = parseSingleOutputLine((await runChecked(this.exec, "git", ["rev-parse", "--show-toplevel"], this.options())).stdout, "Git worktree root");
568
559
  const root = await realpath(top);
569
560
  const id = createHash("sha256").update(root).digest("hex");
570
561
  return { root, id, path: join(extensionConfigDir("pi-pr", this.agentDir), "sweep", id, STATE_FILE) };
@@ -1016,7 +1007,7 @@ export class PullRequestCommentSweep {
1016
1007
  throw new Error("Comment sweep is not ready to finalize");
1017
1008
  }
1018
1009
  const projection = parseProjection(projectionInput);
1019
- if (!sameProjection(projection, state.projection)) throw new Error("Final projection does not match the declared refreshed projection");
1010
+ if (!isDeepStrictEqual(projection, state.projection)) throw new Error("Final projection does not match the declared refreshed projection");
1020
1011
  const checks = parseChecks(checksInput);
1021
1012
  if (state.attempts.resolutions.some(({ state: attempt }) => attempt !== "applied")) {
1022
1013
  throw new Error("Comment sweep has unresolved or unknown mutation attempts");
@@ -1026,7 +1017,7 @@ export class PullRequestCommentSweep {
1026
1017
  }
1027
1018
  await this.freshProjection(state);
1028
1019
  const alreadyChecked = state.attempts.finalize.state === "applied" &&
1029
- JSON.stringify(state.attempts.finalize.checks) === JSON.stringify(checks);
1020
+ isDeepStrictEqual(state.attempts.finalize.checks, checks);
1030
1021
  if (!alreadyChecked) {
1031
1022
  state.attempts.finalize = { state: "attempting", checks };
1032
1023
  await this.save(location, state);
@@ -173,9 +173,7 @@ export class PullRequestCreator {
173
173
  private explicitBase?: string;
174
174
 
175
175
  constructor(options: CreatePullRequestOptions) {
176
- if (!options.target || options.target.remoteOid !== null && !requiredOid(options.target.remoteOid, "remote OID")) {
177
- throw new TypeError("PR creation requires a validated creation target");
178
- }
176
+ if (options.target.remoteOid !== null) requiredOid(options.target.remoteOid, "remote OID");
179
177
  this.cwd = options.cwd;
180
178
  this.target = { ...options.target };
181
179
  this.noTarget = options.target.provenance === "inferred" && options.target.remoteOid === null;
@@ -115,10 +115,8 @@ export async function withWorktreeLock<T>(
115
115
  cwd: requiredText(cwd, "cwd"),
116
116
  signal: options.signal,
117
117
  });
118
- const normalizedRoot = rootResult.stdout.replace(/\r\n/g, "\n");
119
- const rootLines = (normalizedRoot.endsWith("\n") ? normalizedRoot.slice(0, -1) : normalizedRoot).split("\n");
120
- if (rootLines.length !== 1 || !rootLines[0]) throw new Error("Git worktree root resolution returned invalid output");
121
- const canonical = requiredText(await realpath(rootLines[0]), "canonical Git worktree root");
118
+ const root = parseSingleOutputLine(rootResult.stdout, "Git worktree root resolution");
119
+ const canonical = requiredText(await realpath(root), "canonical Git worktree root");
122
120
  const lockNamespace = resolve(extensionConfigDir("pi-pr", options.agentDir));
123
121
  const lockDirectory = join(lockNamespace, "worktree-locks");
124
122
  const identity = createHash("sha256").update(canonical).digest("hex");
@@ -197,8 +197,6 @@ type PullRequestCandidate = {
197
197
  headOid: string;
198
198
  };
199
199
 
200
- type SearchPullRequest = PullRequestCandidate;
201
-
202
200
  export type PullRequestPublication = {
203
201
  number: number;
204
202
  url: URL;
@@ -224,7 +222,7 @@ export type BranchUpstreamTarget = {
224
222
  };
225
223
 
226
224
  type SearchSelection =
227
- | { kind: "candidate"; candidate: SearchPullRequest; pullRequest: ListedPullRequest | null }
225
+ | { kind: "candidate"; candidate: PullRequestCandidate; pullRequest: ListedPullRequest | null }
228
226
  | { kind: "none" }
229
227
  | { kind: "ambiguous"; urls: URL[] }
230
228
  | { kind: "oid-mismatch"; urls: URL[] }
@@ -232,7 +230,7 @@ type SearchSelection =
232
230
 
233
231
  type SearchPage = {
234
232
  totalCount: number;
235
- candidates: SearchPullRequest[];
233
+ candidates: PullRequestCandidate[];
236
234
  cursors: string[];
237
235
  hasNextPage: boolean;
238
236
  endCursor: string | null;
@@ -676,7 +674,7 @@ function listedPullRequest(value: unknown): ListedPullRequest | null {
676
674
  };
677
675
  }
678
676
 
679
- function searchPullRequest(value: unknown, host: string): SearchPullRequest {
677
+ function searchPullRequest(value: unknown, host: string): PullRequestCandidate {
680
678
  if (!isRecord(value) || value.__typename !== "PullRequest") {
681
679
  fail("Find pull requests", "invalid GitHub CLI output");
682
680
  }
@@ -742,7 +740,7 @@ function parseSearchPage(output: string, pushTarget: Pick<PushTarget, "repositor
742
740
  !Array.isArray(search.edges) || search.edges.length > PR_DISCOVERY_PAGE_SIZE ||
743
741
  !isRecord(search.pageInfo)
744
742
  ) fail("Find pull requests", "invalid GitHub CLI output");
745
- const candidates: SearchPullRequest[] = [];
743
+ const candidates: PullRequestCandidate[] = [];
746
744
  const cursors: string[] = [];
747
745
  for (const edge of search.edges) {
748
746
  if (!isRecord(edge)) fail("Find pull requests", "invalid GitHub CLI output");
@@ -766,7 +764,7 @@ function parseSearchPage(output: string, pushTarget: Pick<PushTarget, "repositor
766
764
  };
767
765
  }
768
766
 
769
- function matchingSearchPullRequests(candidates: SearchPullRequest[], pushTarget: PushTarget): SearchPullRequest[] {
767
+ function matchingSearchPullRequests(candidates: PullRequestCandidate[], pushTarget: PushTarget): PullRequestCandidate[] {
770
768
  return candidates.filter((candidate) =>
771
769
  candidate.url.hostname.toLowerCase() === pushTarget.repository.host &&
772
770
  candidate.headRepository !== null &&
@@ -775,7 +773,7 @@ function matchingSearchPullRequests(candidates: SearchPullRequest[], pushTarget:
775
773
  );
776
774
  }
777
775
 
778
- function selectSearchPullRequest(candidates: SearchPullRequest[], pushTarget: PushTarget): SearchSelection {
776
+ function selectSearchPullRequest(candidates: PullRequestCandidate[], pushTarget: PushTarget): SearchSelection {
779
777
  const matching = matchingSearchPullRequests(candidates, pushTarget);
780
778
  const open = matching.filter((candidate) => candidate.lifecycle === "open");
781
779
  if (open.length > 1) return { kind: "ambiguous", urls: open.map(({ url }) => url) };
@@ -1681,9 +1679,9 @@ async function enumerateSearchPullRequests(
1681
1679
  pi: Pick<ExtensionAPI, "exec">,
1682
1680
  context: PullRequestLoadContext,
1683
1681
  target: Pick<PushTarget, "repository" | "ref">,
1684
- ): Promise<SearchPullRequest[] | null> {
1682
+ ): Promise<PullRequestCandidate[] | null> {
1685
1683
  const [owner, name] = target.repository.nameWithOwner.split("/");
1686
- const candidates: SearchPullRequest[] = [];
1684
+ const candidates: PullRequestCandidate[] = [];
1687
1685
  const cursors = new Set<string>();
1688
1686
  let totalCount: number | null = null;
1689
1687
  let endCursor: string | null = null;
@@ -71,7 +71,7 @@ export type FeedbackRouteBlocker =
71
71
  | { kind: "head-not-equal"; relation: Exclude<LocalHeadRelation, "equal"> };
72
72
  type RouteDecision =
73
73
  | { kind: "selected"; nextStep: NextStep }
74
- | { kind: "feedback-blocked"; nextStep: "blocked"; blocker: FeedbackRouteBlocker };
74
+ | { kind: "feedback-blocked"; blocker: FeedbackRouteBlocker };
75
75
 
76
76
  function localMutationSafe(local: LocalMergeSafety): boolean {
77
77
  return local.worktree === "clean" && local.head === "equal";
@@ -122,25 +122,24 @@ export function deriveRouteDecision(
122
122
  ): RouteDecision {
123
123
  if (intent === "automatic") return { kind: "selected", nextStep: deriveAutomaticNextStep(discovery) };
124
124
  if (discovery.kind === "blocked") {
125
- return { kind: "feedback-blocked", nextStep: "blocked", blocker: { kind: "discovery-blocked" } };
125
+ return { kind: "feedback-blocked", blocker: { kind: "discovery-blocked" } };
126
126
  }
127
127
  if (discovery.kind !== "current") {
128
- return { kind: "feedback-blocked", nextStep: "blocked", blocker: { kind: "pull-request-unavailable" } };
128
+ return { kind: "feedback-blocked", blocker: { kind: "pull-request-unavailable" } };
129
129
  }
130
130
  const pullRequest = discovery.pullRequest;
131
131
  if (pullRequest.lifecycle !== "open") {
132
- return { kind: "feedback-blocked", nextStep: "blocked", blocker: { kind: "pull-request-not-open" } };
132
+ return { kind: "feedback-blocked", blocker: { kind: "pull-request-not-open" } };
133
133
  }
134
134
  if (pullRequest.target.provenance !== "configured") {
135
- return { kind: "feedback-blocked", nextStep: "blocked", blocker: { kind: "target-not-configured" } };
135
+ return { kind: "feedback-blocked", blocker: { kind: "target-not-configured" } };
136
136
  }
137
137
  if (pullRequest.local.worktree !== "clean") {
138
- return { kind: "feedback-blocked", nextStep: "blocked", blocker: { kind: "worktree-dirty" } };
138
+ return { kind: "feedback-blocked", blocker: { kind: "worktree-dirty" } };
139
139
  }
140
140
  if (pullRequest.local.head !== "equal") {
141
141
  return {
142
142
  kind: "feedback-blocked",
143
- nextStep: "blocked",
144
143
  blocker: { kind: "head-not-equal", relation: pullRequest.local.head },
145
144
  };
146
145
  }
@@ -148,5 +147,5 @@ export function deriveRouteDecision(
148
147
  }
149
148
 
150
149
  export function deriveNextStep(discovery: PullRequestDiscovery<PullRequest & { target: PullRequestTarget }>): NextStep {
151
- return deriveRouteDecision(discovery).nextStep;
150
+ return deriveAutomaticNextStep(discovery);
152
151
  }
@@ -11,7 +11,7 @@ import {
11
11
 
12
12
  export type PrDisplayInput = PullRequest & {
13
13
  number: number;
14
- url: string | URL;
14
+ url: URL;
15
15
  approved: boolean;
16
16
  target: PullRequestTarget;
17
17
  };
@@ -140,7 +140,7 @@ export function projectPrDisplay(
140
140
  nextStep,
141
141
  footer: {
142
142
  number: input.number,
143
- url: typeof input.url === "string" ? input.url : input.url.href,
143
+ url: input.url.href,
144
144
  ...footerStatus(input),
145
145
  },
146
146
  widget: widgetText(input, nextStep),
@@ -62,12 +62,9 @@ export class PullRequestBranchUpdater {
62
62
  private readonly load: Load;
63
63
 
64
64
  constructor(options: UpdateBranchOptions) {
65
- if (!options.authority || options.authority.lifecycle !== "open" || options.authority.target.provenance !== "configured") {
65
+ if (!options.authority || options.authority.target.provenance !== "configured") {
66
66
  throw new TypeError("Branch update requires a configured open pull request");
67
67
  }
68
- if (!options.authority.conditions.baseUpdateRequired && !options.authority.conditions.conflict) {
69
- throw new TypeError("Pull request does not require a branch update");
70
- }
71
68
  this.cwd = options.cwd;
72
69
  this.authority = cloneCurrentPullRequest(options.authority);
73
70
  this.signal = options.signal;
package/extensions/pr.ts CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  samePullRequestObservation,
27
27
  type PullRequestObservation,
28
28
  } from "./pr-github.ts";
29
- import { isRecord, runChecked } from "./pr-execution.ts";
29
+ import { isRecord, parseSingleOutputLine, runChecked } from "./pr-execution.ts";
30
30
  import {
31
31
  discoveryIssueKey,
32
32
  discoveryIssueMessage,
@@ -48,7 +48,7 @@ const GH_PR_CREATE = /(?:^|[;&|]\s*|\n\s*)gh\s+pr\s+create(?=\s|$|[;&|])/;
48
48
  const GIT_COMMIT = /(?:^|[;&|]\s*|\n\s*)git\s+commit(?=\s|$|[;&|])/;
49
49
  const GIT_PUSH = /(?:^|[;&|]\s*|\n\s*)git\s+push(?=\s|$|[;&|])/;
50
50
  const WORKFLOW_ROUTES = new Set(["create", "update-branch", "sweep", "fix-ci"]);
51
- const DELEGATED_TOOLS = new Set(["delegate_task", "delegate_flow", "delegate_flow_continue"]);
51
+ const DELEGATED_TOOLS = new Set(["delegate_task"]);
52
52
  const CLOSED = { additionalProperties: false } as const;
53
53
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
54
54
 
@@ -171,10 +171,7 @@ type PullRequestExtensionDependencies = {
171
171
 
172
172
  async function canonicalWorktree(cwd: string, signal?: AbortSignal): Promise<string> {
173
173
  const result = await runChecked(spawnBounded, "git", ["rev-parse", "--show-toplevel"], { cwd, signal });
174
- const normalized = result.stdout.replace(/\r\n/g, "\n");
175
- const lines = (normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized).split("\n");
176
- if (lines.length !== 1 || !lines[0]) throw new Error("Git worktree root resolution returned invalid output");
177
- return await realpath(lines[0]);
174
+ return await realpath(parseSingleOutputLine(result.stdout, "Git worktree root resolution"));
178
175
  }
179
176
 
180
177
  function toolResult(value: unknown) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-pr",
3
- "version": "6.0.3",
3
+ "version": "6.0.5",
4
4
  "description": "Run /pr to safely discover or link the current pull request, then create, update, address feedback, fix CI, or merge when ready.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -27,8 +27,8 @@
27
27
  "pack:check": "npm pack --dry-run"
28
28
  },
29
29
  "peerDependencies": {
30
- "@earendil-works/pi-coding-agent": "^0.86.0",
31
- "@earendil-works/pi-tui": "^0.86.0",
30
+ "@earendil-works/pi-coding-agent": ">=0.86.0 <0.88.0",
31
+ "@earendil-works/pi-tui": ">=0.86.0 <0.88.0",
32
32
  "typebox": "^1.3.15"
33
33
  },
34
34
  "dependencies": {
@@ -38,8 +38,8 @@
38
38
  "proper-lockfile": "^4.1.2"
39
39
  },
40
40
  "devDependencies": {
41
- "@earendil-works/pi-coding-agent": "0.86.0",
42
- "@earendil-works/pi-tui": "0.86.0",
41
+ "@earendil-works/pi-coding-agent": "0.87.0",
42
+ "@earendil-works/pi-tui": "0.87.0",
43
43
  "@types/proper-lockfile": "^4.1.4"
44
44
  },
45
45
  "repository": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import assert from "node:assert/strict";
4
4
  import { randomUUID } from "node:crypto";
5
- import { chmod, mkdtemp, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
5
+ import { chmod, mkdtemp, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
6
6
  import { tmpdir } from "node:os";
7
7
  import { join, resolve } from "node:path";
8
8
  import { readTextFileBounded } from "@henryqw/pi-config-store";
@@ -118,28 +118,13 @@ async function requireCleanHead(cwd, signal, pullRequest, expectedHead = pullReq
118
118
  async function writeSnapshotAtomically(path, contents, signal) {
119
119
  signal.throwIfAborted();
120
120
  const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
121
- let file;
122
- let created = false;
123
121
  try {
124
- file = await open(temporaryPath, "wx", 0o600);
125
- created = true;
126
- signal.throwIfAborted();
127
- if (process.platform !== "win32") await file.chmod(0o600);
128
- signal.throwIfAborted();
129
- await file.writeFile(contents, { encoding: "utf8", signal });
130
- signal.throwIfAborted();
131
- await file.sync();
132
- signal.throwIfAborted();
133
- await file.close();
134
- file = undefined;
122
+ await writeFile(temporaryPath, contents, { encoding: "utf8", flag: "wx", mode: 0o600, flush: true, signal });
123
+ if (process.platform !== "win32") await chmod(temporaryPath, 0o600);
135
124
  signal.throwIfAborted();
136
125
  await rename(temporaryPath, path);
137
126
  } catch (error) {
138
- try {
139
- await file?.close();
140
- } finally {
141
- if (created) await rm(temporaryPath, { force: true });
142
- }
127
+ await rm(temporaryPath, { force: true });
143
128
  throw error;
144
129
  }
145
130
  }