import { parseArgs } from 'util'; interface Context { repo: string; // {owner}/{repo} githubToken: string; } const apiBaseUrl = 'https://api.github.com/repos/'; const defaultApiHeaders = (context: Context) => ({ 'Authorization': `token ${context.githubToken}`, 'X-GitHub-Api-Version': '2022-11-28', 'Accept': 'application/vnd.github+json', }); const getTargetRelease = async (context: Context, targetTag: string) => { console.log('Fetching releases...'); // Note: We need to fetch all releases, not just /releases/tag/tag-name-here. // The latter doesn't include draft releases. const result = await fetch(`${apiBaseUrl}${context.repo}/releases`, { method: 'GET', headers: defaultApiHeaders(context), }); const releases = await result.json(); if (!result.ok) { throw new Error(`Error fetching release: ${JSON.stringify(releases)}`); } for (const release of releases) { if (release.tag_name === targetTag) { return release; } } throw new Error(`No release with tag ${targetTag} found!`); }; const updateReleaseAsset = async (context: Context, assetUrl: string, newName: string) => { console.log('Updating asset with URL', assetUrl, 'to have name, ', newName); // See https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#update-a-release-asset const result = await fetch(assetUrl, { method: 'PATCH', headers: defaultApiHeaders(context), body: JSON.stringify({ name: newName, }), }); if (!result.ok) { throw new Error(`Unable to update release asset: ${await result.text()}`); } }; // Renames release assets in Joplin Desktop releases const renameReleaseAssets = async () => { const args = parseArgs({ options: { tag: { type: 'string' }, token: { type: 'string' }, repo: { type: 'string' }, }, }); if (!args.values.tag || !args.values.token || !args.values.repo) { throw new Error([ 'Required arguments: --tag, --token, --repo', ' --tag should be a git tag with an associated release (e.g. v12.12.12)', ' --token should be a GitHub API token', ' --repo should be a string in the form user/reponame (e.g. laurent22/joplin)', ].join('\n')); } const context: Context = { repo: args.values.repo, githubToken: args.values.token, }; console.log('Renaming release assets for tag', args.values.tag, context.repo); const release = await getTargetRelease(context, args.values.tag); if (!release.assets) { console.log(release); throw new Error(`Release ${release.name} missing assets!`); } // Patterns used to rename releases const renamePatterns = [ [/-arm64\.dmg$/, '-arm64.DMG'], ]; for (const asset of release.assets) { for (const [pattern, replacement] of renamePatterns) { if (asset.name.match(pattern)) { const newName = asset.name.replace(pattern, replacement); await updateReleaseAsset(context, asset.url, newName); // Only rename a release once. break; } } } }; void renameReleaseAssets();