( releasePlan: ReleasePlan, packages: Map<string, WorkspacePackage>, rootDir: string, config: BumpyConfig, )
| 15 | |
| 16 | /** Apply the release plan: bump versions, update changelogs, delete bump files */ |
| 17 | export async function applyReleasePlan( |
| 18 | releasePlan: ReleasePlan, |
| 19 | packages: Map<string, WorkspacePackage>, |
| 20 | rootDir: string, |
| 21 | config: BumpyConfig, |
| 22 | ): Promise<void> { |
| 23 | const releaseMap = new Map(releasePlan.releases.map((r) => [r.name, r])); |
| 24 | const formatter = config.changelog !== false ? await loadFormatter(config.changelog, rootDir) : null; |
| 25 | |
| 26 | // 1. Update package.json versions and internal dependency ranges (preserving formatting) |
| 27 | for (const release of releasePlan.releases) { |
| 28 | const pkg = packages.get(release.name)!; |
| 29 | const pkgJsonPath = resolve(pkg.dir, 'package.json'); |
| 30 | const pkgJson = await readJson<Record<string, unknown>>(pkgJsonPath); |
| 31 | |
| 32 | // Bump the version (in-place string replacement) |
| 33 | await updateJsonFields(pkgJsonPath, { version: release.newVersion }); |
| 34 | |
| 35 | // Update internal dependency ranges (in-place string replacement) |
| 36 | for (const depField of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const) { |
| 37 | const deps = pkgJson[depField] as Record<string, string> | undefined; |
| 38 | if (!deps) continue; |
| 39 | for (const [depName, range] of Object.entries(deps)) { |
| 40 | const depRelease = releaseMap.get(depName); |
| 41 | if (!depRelease) continue; |
| 42 | const newRange = updateRange(range, depRelease.newVersion); |
| 43 | await updateJsonNestedField(pkgJsonPath, depField, depName, newRange); |
| 44 | } |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | // 2. Update changelogs |
| 49 | if (formatter) { |
| 50 | for (const release of releasePlan.releases) { |
| 51 | const pkg = packages.get(release.name)!; |
| 52 | const changelogPath = resolve(pkg.dir, 'CHANGELOG.md'); |
| 53 | |
| 54 | const entry = await generateChangelogEntry(release, releasePlan.bumpFiles, formatter); |
| 55 | let existingContent = ''; |
| 56 | if (await exists(changelogPath)) { |
| 57 | existingContent = await readText(changelogPath); |
| 58 | } |
| 59 | const newContent = prependToChangelog(existingContent, entry); |
| 60 | await writeText(changelogPath, newContent); |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | // 3. Delete all bump files (including empty ones that aren't in the release plan). |
| 65 | // Channel subdirs (`.bumpy/<channel>/`) hold bump files that shipped as prereleases; |
| 66 | // a stable release consumes those too (promotion), then removes the empty dirs. |
| 67 | const bumpyDir = getBumpyDir(rootDir); |
| 68 | const allBumpFiles = await listFiles(bumpyDir, '.md'); |
| 69 | for (const file of allBumpFiles) { |
| 70 | if (file === 'README.md') continue; |
| 71 | await removeFile(resolve(bumpyDir, file)); |
| 72 | } |
| 73 | const { rmdir } = await import('node:fs/promises'); |
| 74 | for (const channel of Object.keys(config.channels || {})) { |
no test coverage detected
searching dependent graphs…