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 if (changes.totalCount < 1) { 82 return false; 83 } 84 85 delete changes.versions[UNPUBLISHED_VERSION_NAME]; 86 let updated = false; 87 for (const version of Object.keys(changes.versions).sort((v1, v2) => 88 semver.gt(v1, v2) ? 1 : -1 89 )) { 90 const groupData = changes.versions[version]; 91 if (Object.keys(groupData).length === 0) { 92 const result = await targetChangelog.insertEmptyPublishedVersionAsync( 93 version, 94 changes.versionDateMap[version] 95 ); 96 updated ||= result; 97 continue; 98 } 99 100 for (const [changeType, entries] of Object.entries(groupData)) { 101 for (const entry of entries) { 102 const result = await targetChangelog.moveEntryBetweenVersionsAsync( 103 entry, 104 changeType, 105 UNPUBLISHED_VERSION_NAME, 106 version, 107 changes.versionDateMap[version] 108 ); 109 updated ||= result; 110 } 111 } 112 } 113 if (updated) { 114 await targetChangelog.saveAsync(); 115 } 116 117 return true; 118} 119