1import { Command } from '@expo/commander'; 2import chalk from 'chalk'; 3import semver from 'semver'; 4 5import { Changelog, MemChangelog, UNPUBLISHED_VERSION_NAME } from '../Changelogs'; 6import Git from '../Git'; 7import { Package, getListOfPackagesAsync } from '../Packages'; 8import { filterAsync } from '../Utils'; 9 10type CommandOptions = { 11 branch: string; 12}; 13 14export default (program: Command) => { 15 program 16 .command('sync-sdk-branch-changelogs') 17 .alias('ssbc') 18 .description('Sync packages changelogs from sdk branch to current branch.') 19 .option('-b, --branch <name>', 'Source branch name') 20 .asyncAction(async (options: CommandOptions) => { 21 if (!options.branch) { 22 throw new Error( 23 'Missing branch name. Run with `--branch <name>` to specify source branch.' 24 ); 25 } 26 27 console.log('\u203A Fetching git changes'); 28 await Git.fetchAsync(); 29 console.log(''); 30 31 const packages = await getPackagesWithChangelogAsync(); 32 33 for (const pkg of packages) { 34 let changed = false; 35 try { 36 changed = await syncChangelogAsync(pkg, options.branch); 37 } catch (e) { 38 const errorMessage = e.message ?? String(e); 39 console.error(`❌ ${chalk.red(pkg.packageName)}: ${errorMessage}`); 40 } 41 if (changed) { 42 console.log(`✅ ${pkg.packageName}`); 43 } 44 } 45 }); 46}; 47 48/** 49 * Gets packages with a changelog file. 50 */ 51async function getPackagesWithChangelogAsync(): Promise<Package[]> { 52 const packages = await getListOfPackagesAsync(); 53 return filterAsync(packages, (pkg) => pkg.hasChangelogAsync()); 54} 55 56/** 57 * Syncs changelog of a package from `sourceBranch` to current branch 58 */ 59async function syncChangelogAsync(pkg: Package, sourceBranch: string) { 60 const sourceChangelog = new MemChangelog( 61 await Git.readFileAsync(`origin/${sourceBranch}`, pkg.changelogPath) 62 ); 63 const targetChangelog = new Changelog(pkg.changelogPath); 64 65 const sourceLastVersion = await sourceChangelog.getLastPublishedVersionAsync(); 66 const targetLastVersion = await targetChangelog.getLastPublishedVersionAsync(); 67 if (!sourceLastVersion || !targetLastVersion) { 68 throw new Error('Cannot determine latest published version'); 69 } 70 71 if (semver.gt(targetLastVersion, sourceLastVersion)) { 72 throw new Error( 73 'Current version is newer than source branch and there might be some inconsistency in between, e.g. canary version published. Please update manually.' 74 ); 75 } else if (semver.eq(sourceLastVersion, targetLastVersion)) { 76 return false; 77 } 78 79 const changes = await sourceChangelog.getChangesAsync(targetLastVersion, sourceLastVersion); 80 81 delete changes.versions[UNPUBLISHED_VERSION_NAME]; 82 let updated = false; 83 for (const version of Object.keys(changes.versions).sort((v1, v2) => 84 semver.gt(v1, v2) ? 1 : -1 85 )) { 86 const groupData = changes.versions[version]; 87 if (Object.keys(groupData).length === 0) { 88 const result = await targetChangelog.insertEmptyPublishedVersionAsync( 89 version, 90 changes.versionDateMap[version] 91 ); 92 updated ||= result; 93 continue; 94 } 95 96 for (const [changeType, entries] of Object.entries(groupData)) { 97 for (const entry of entries) { 98 const result = await targetChangelog.moveEntryBetweenVersionsAsync( 99 entry, 100 changeType, 101 UNPUBLISHED_VERSION_NAME, 102 version, 103 changes.versionDateMap[version] 104 ); 105 updated ||= result; 106 } 107 } 108 } 109 if (updated) { 110 await targetChangelog.saveAsync(); 111 } 112 113 return true; 114} 115