| 453 | } |
| 454 | |
| 455 | function writeReleaseNotes(version: string, releaseNotes: string, force: boolean) { |
| 456 | const releasesDir = join(ROOT, "releases"); |
| 457 | const releasePath = join(releasesDir, `v${version}.md`); |
| 458 | mkdirSync(releasesDir, { recursive: true }); |
| 459 | |
| 460 | // Use an exclusive-write flag rather than a separate existsSync check so the |
| 461 | // "already exists" guard is atomic with the write (no TOCTOU race). |
| 462 | // EEXIST is only reachable when force is false (the "wx" flag); with force the |
| 463 | // "w" flag overwrites and never throws it, so no separate force check is needed. |
| 464 | try { |
| 465 | writeFileSync(releasePath, `${releaseNotes}\n`, { flag: force ? "w" : "wx" }); |
| 466 | } catch (error) { |
| 467 | if ((error as NodeJS.ErrnoException).code === "EEXIST") { |
| 468 | console.log( |
| 469 | `${releasePath} already exists; leaving it unchanged. Pass --force to overwrite it.`, |
| 470 | ); |
| 471 | return; |
| 472 | } |
| 473 | throw error; |
| 474 | } |
| 475 | console.log(`Wrote ${releasePath}`); |
| 476 | } |
| 477 | |
| 478 | function prependDocsUpdate(version: string, docsUpdate: string) { |
| 479 | const changelogPath = join(ROOT, "docs", "changelog.mdx"); |