You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop: Seamless-Updates: generated and uploaded latest-mac-arm64.yml to GitHub Releases (#11042)
This commit is contained in:
		| @@ -518,8 +518,10 @@ packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js | ||||
| packages/app-desktop/services/sortOrder/notesSortOrderUtils.js | ||||
| packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js | ||||
| packages/app-desktop/tools/copy7Zip.js | ||||
| packages/app-desktop/tools/generateLatestArm64Yml.js | ||||
| packages/app-desktop/tools/githubReleasesUtils.js | ||||
| packages/app-desktop/tools/modifyReleaseAssets.js | ||||
| packages/app-desktop/tools/notarizeMacApp.js | ||||
| packages/app-desktop/tools/renameReleaseAssets.js | ||||
| packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js | ||||
| packages/app-desktop/utils/7zip/pathToBundled7Zip.js | ||||
| packages/app-desktop/utils/checkForUpdatesUtils.test.js | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/build-macos-m1.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build-macos-m1.yml
									
									
									
									
										vendored
									
									
								
							| @@ -80,7 +80,7 @@ jobs: | ||||
|             echo "Building and publishing desktop application..." | ||||
|             PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn dist --mac --arm64 | ||||
|  | ||||
|             yarn renameReleaseAssets --repo="$GH_REPO" --tag="$GIT_TAG_NAME" --token="$GITHUB_TOKEN" | ||||
|             yarn modifyReleaseAssets --repo="$GH_REPO" --tag="$GIT_TAG_NAME" --token="$GITHUB_TOKEN" | ||||
|           else | ||||
|             echo "Building but *not* publishing desktop application..." | ||||
|  | ||||
|   | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -495,8 +495,10 @@ packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js | ||||
| packages/app-desktop/services/sortOrder/notesSortOrderUtils.js | ||||
| packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js | ||||
| packages/app-desktop/tools/copy7Zip.js | ||||
| packages/app-desktop/tools/generateLatestArm64Yml.js | ||||
| packages/app-desktop/tools/githubReleasesUtils.js | ||||
| packages/app-desktop/tools/modifyReleaseAssets.js | ||||
| packages/app-desktop/tools/notarizeMacApp.js | ||||
| packages/app-desktop/tools/renameReleaseAssets.js | ||||
| packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js | ||||
| packages/app-desktop/utils/7zip/pathToBundled7Zip.js | ||||
| packages/app-desktop/utils/checkForUpdatesUtils.test.js | ||||
|   | ||||
| @@ -2,7 +2,6 @@ const fs = require('fs'); | ||||
| const path = require('path'); | ||||
| const os = require('os'); | ||||
| const sha512 = require('js-sha512'); | ||||
| const crypto = require('crypto'); | ||||
| const distDirName = 'dist'; | ||||
| const distPath = path.join(__dirname, distDirName); | ||||
|  | ||||
| @@ -32,87 +31,9 @@ const generateChecksumFile = () => { | ||||
| 	return sha512FilePath; | ||||
| }; | ||||
|  | ||||
| const generateLatestArm64Yml = () => { | ||||
| 	if (os.platform() !== 'darwin' && process.arch !== 'arm64') { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const calculateHash = (filePath) => { | ||||
| 		const fileBuffer = fs.readFileSync(filePath); | ||||
| 		const hashSum = crypto.createHash('sha512'); | ||||
| 		hashSum.update(fileBuffer); | ||||
| 		return hashSum.digest('base64'); | ||||
| 	}; | ||||
|  | ||||
| 	const getFileSize = (filePath) => { | ||||
| 		return fs.statSync(filePath).size; | ||||
| 	}; | ||||
|  | ||||
| 	const extractVersion = (filePath) => { | ||||
| 		return path.basename(filePath).split('-')[1]; | ||||
| 	}; | ||||
|  | ||||
| 	const files = fs.readdirSync(distPath); | ||||
| 	let dmgPath = ''; | ||||
| 	let zipPath = ''; | ||||
| 	for (const file of files) { | ||||
| 		if (file.endsWith('arm64.dmg')) { | ||||
| 			const fileRenamed = `${file.slice(0, -4)}.DMG`; // renameReleaseAssets script will rename from .dmg to .DMG | ||||
| 			dmgPath = path.join(distPath, fileRenamed); | ||||
| 		} else if (file.endsWith('arm64.zip')) { | ||||
| 			zipPath = path.join(distPath, file); | ||||
| 		} | ||||
| 	} | ||||
| 	const versionFromFilePath = extractVersion(zipPath); | ||||
|  | ||||
| 	const info = { | ||||
| 		version: versionFromFilePath, | ||||
| 		dmgPath: dmgPath, | ||||
| 		zipPath: zipPath, | ||||
| 		releaseDate: new Date().toISOString(), | ||||
| 	}; | ||||
|  | ||||
| 	/* eslint-disable no-console */ | ||||
| 	if (!fs.existsSync(info.dmgPath) || !fs.existsSync(info.zipPath)) { | ||||
| 		console.error('One or both executable files do not exist:', info.dmgPath, info.zipPath); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	console.info('Calculating hash of files...'); | ||||
| 	const dmgHash = calculateHash(info.dmgPath); | ||||
| 	const zipHash = calculateHash(info.zipPath); | ||||
|  | ||||
| 	console.info('Calculating size of files...'); | ||||
| 	const dmgSize = getFileSize(info.dmgPath); | ||||
| 	const zipSize = getFileSize(info.zipPath); | ||||
|  | ||||
| 	console.info('Generating content of latest-mac-arm64.yml file...'); | ||||
| 	const yamlFilePath = path.join(distPath, 'latest-mac-arm64.yml'); | ||||
| 	const yamlContent = `version: ${info.version} | ||||
| files: | ||||
|   - url: ${path.basename(info.zipPath)} | ||||
|     sha512: ${zipHash} | ||||
|     size: ${zipSize} | ||||
|   - url: ${path.basename(info.dmgPath)} | ||||
|     sha512: ${dmgHash} | ||||
|     size: ${dmgSize} | ||||
| path: ${path.basename(info.zipPath)} | ||||
| sha512: ${zipHash} | ||||
| releaseDate: '${info.releaseDate}' | ||||
| `; | ||||
| 	fs.writeFileSync(yamlFilePath, yamlContent); | ||||
| 	console.log('YML file generated successfully for arm64 architecure.'); | ||||
|  | ||||
| 	const fileContent = fs.readFileSync(yamlFilePath, 'utf8'); | ||||
| 	console.log('Generated YML Content:\n', fileContent); | ||||
| 	/* eslint-enable no-console */ | ||||
| 	return yamlFilePath; | ||||
| }; | ||||
|  | ||||
| const mainHook = () => { | ||||
| 	const sha512FilePath = generateChecksumFile(); | ||||
| 	const lastestArm64YmlFilePath = generateLatestArm64Yml(); | ||||
| 	const outputFiles = [sha512FilePath, lastestArm64YmlFilePath].filter(item => item); | ||||
| 	const outputFiles = [sha512FilePath].filter(item => item); | ||||
| 	return outputFiles; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
|     "test": "jest", | ||||
|     "test-ui": "playwright test", | ||||
|     "test-ci": "yarn test && sh ./integration-tests/run-ci.sh", | ||||
|     "renameReleaseAssets": "node tools/renameReleaseAssets.js" | ||||
|     "modifyReleaseAssets": "node tools/modifyReleaseAssets.js" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|   | ||||
							
								
								
									
										69
									
								
								packages/app-desktop/tools/generateLatestArm64Yml.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								packages/app-desktop/tools/generateLatestArm64Yml.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| import * as fs from 'fs'; | ||||
| import * as path from 'path'; | ||||
| import * as crypto from 'crypto'; | ||||
|  | ||||
| export interface GenerateInfo { | ||||
| 	version: string; | ||||
| 	dmgPath: string; | ||||
| 	zipPath: string; | ||||
| 	releaseDate: string; | ||||
| } | ||||
|  | ||||
| const calculateHash = (filePath: string): string => { | ||||
| 	const fileBuffer = fs.readFileSync(filePath); | ||||
| 	const hashSum = crypto.createHash('sha512'); | ||||
| 	hashSum.update(fileBuffer); | ||||
| 	return hashSum.digest('base64'); | ||||
| }; | ||||
|  | ||||
| const getFileSize = (filePath: string): number => { | ||||
| 	return fs.statSync(filePath).size; | ||||
| }; | ||||
|  | ||||
| export const generateLatestArm64Yml = (info: GenerateInfo, destinationPath: string): string | undefined => { | ||||
| 	if (!fs.existsSync(info.dmgPath) || !fs.existsSync(info.zipPath)) { | ||||
| 		throw new Error(`One or both executable files do not exist: ${info.dmgPath}, ${info.zipPath}`); | ||||
| 	} | ||||
| 	if (!info.version) { | ||||
| 		throw new Error('Version is empty'); | ||||
| 	} | ||||
| 	if (!destinationPath) { | ||||
| 		throw new Error('Destination path is empty'); | ||||
| 	} | ||||
|  | ||||
| 	console.info('Calculating hash of files...'); | ||||
| 	const dmgHash: string = calculateHash(info.dmgPath); | ||||
| 	const zipHash: string = calculateHash(info.zipPath); | ||||
|  | ||||
| 	console.info('Calculating size of files...'); | ||||
| 	const dmgSize: number = getFileSize(info.dmgPath); | ||||
| 	const zipSize: number = getFileSize(info.zipPath); | ||||
|  | ||||
| 	console.info('Generating content of latest-mac-arm64.yml file...'); | ||||
|  | ||||
| 	if (!fs.existsSync(destinationPath)) { | ||||
| 		fs.mkdirSync(destinationPath); | ||||
| 	} | ||||
|  | ||||
| 	const yamlFilePath: string = path.join(destinationPath, 'latest-mac-arm64.yml'); | ||||
| 	const yamlContent = `version: ${info.version} | ||||
| files: | ||||
|   - url: ${path.basename(info.zipPath)} | ||||
|     sha512: ${zipHash} | ||||
|     size: ${zipSize} | ||||
|   - url: ${path.basename(info.dmgPath)} | ||||
|     sha512: ${dmgHash} | ||||
|     size: ${dmgSize} | ||||
| path: ${path.basename(info.zipPath)} | ||||
| sha512: ${zipHash} | ||||
| releaseDate: '${info.releaseDate}' | ||||
| `; | ||||
|  | ||||
| 	fs.writeFileSync(yamlFilePath, yamlContent); | ||||
| 	console.log(`YML file for version ${info.version} was generated successfully at ${destinationPath} for arm64.`); | ||||
|  | ||||
| 	const fileContent: string = fs.readFileSync(yamlFilePath, 'utf8'); | ||||
| 	console.log('Generated YML Content:\n', fileContent); | ||||
|  | ||||
| 	return yamlFilePath; | ||||
| }; | ||||
							
								
								
									
										105
									
								
								packages/app-desktop/tools/githubReleasesUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								packages/app-desktop/tools/githubReleasesUtils.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| import * as fs from 'fs'; | ||||
| import { createWriteStream } from 'fs'; | ||||
| import * as path from 'path'; | ||||
| import { promisify } from 'util'; | ||||
| import { GitHubRelease, GitHubReleaseAsset } from '../utils/checkForUpdatesUtils'; | ||||
|  | ||||
| const pipeline = promisify(require('stream').pipeline); | ||||
|  | ||||
| export interface Context { | ||||
| 	repo: string; // {owner}/{repo} | ||||
| 	githubToken: string; | ||||
| 	targetTag: 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', | ||||
| }); | ||||
|  | ||||
| export const getTargetRelease = async (context: Context, targetTag: string): Promise<GitHubRelease> => { | ||||
| 	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!`); | ||||
| }; | ||||
|  | ||||
| // Download a file from Joplin Desktop releases | ||||
| export const downloadFile = async (asset: GitHubReleaseAsset, destinationDir: string): Promise<string> => { | ||||
| 	const downloadPath = path.join(destinationDir, asset.name); | ||||
| 	if (!fs.existsSync(destinationDir)) { | ||||
| 		fs.mkdirSync(destinationDir); | ||||
| 	} | ||||
|  | ||||
| 	/* eslint-disable no-console */ | ||||
| 	console.log(`Downloading ${asset.name} to ${downloadPath}`); | ||||
| 	const response = await fetch(asset.browser_download_url); | ||||
| 	if (!response.ok) { | ||||
| 		throw new Error(`Failed to download file: Status Code ${response.status}`); | ||||
| 	} | ||||
| 	const fileStream = createWriteStream(downloadPath); | ||||
| 	await pipeline(response.body, fileStream); | ||||
| 	console.log('Download successful!'); | ||||
| 	/* eslint-enable no-console */ | ||||
| 	return downloadPath; | ||||
| }; | ||||
|  | ||||
| export 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()}`); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export const uploadReleaseAsset = async (context: Context, release: GitHubRelease, filePath: string): Promise<void> => { | ||||
| 	console.log(`Uploading file from ${filePath} to release ${release.tag_name}`); | ||||
|  | ||||
| 	const fileContent = fs.readFileSync(filePath); | ||||
| 	const fileName = path.basename(filePath); | ||||
| 	const uploadUrl = `https://uploads.github.com/repos/${context.repo}/releases/${release.id}/assets?name=${encodeURIComponent(fileName)}`; | ||||
|  | ||||
| 	const response = await fetch(uploadUrl, { | ||||
| 		method: 'POST', | ||||
| 		headers: { | ||||
| 			...defaultApiHeaders(context), | ||||
| 			'Content-Type': 'application/octet-stream', | ||||
| 		}, | ||||
| 		body: fileContent, | ||||
| 	}); | ||||
|  | ||||
| 	if (!response.ok) { | ||||
| 		throw new Error(`Failed to upload asset: ${await response.text()}`); | ||||
| 	} else { | ||||
| 		console.log(`${fileName} uploaded successfully.`); | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										93
									
								
								packages/app-desktop/tools/modifyReleaseAssets.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								packages/app-desktop/tools/modifyReleaseAssets.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
|  | ||||
| import path = require('path'); | ||||
| import { parseArgs } from 'util'; | ||||
| import { Context, downloadFile, getTargetRelease, updateReleaseAsset, uploadReleaseAsset } from './githubReleasesUtils'; | ||||
| import { GitHubRelease } from '../utils/checkForUpdatesUtils'; | ||||
| import { GenerateInfo, generateLatestArm64Yml } from './generateLatestArm64Yml'; | ||||
|  | ||||
| const basePath = path.join(__dirname, '..'); | ||||
| const downloadDir = path.join(basePath, 'downloads'); | ||||
|  | ||||
| // Renames release assets in Joplin Desktop releases | ||||
| const renameReleaseAssets = async (context: Context, release: GitHubRelease) => { | ||||
| 	// Patterns used to rename releases | ||||
| 	const renamePatterns: [RegExp, string][] = [ | ||||
| 		[/-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; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| // Creates release assets in Joplin Desktop releases | ||||
| const createReleaseAssets = async (context: Context, release: GitHubRelease) => { | ||||
| 	// Create latest-mac-arm64.yml file and publish | ||||
| 	let dmgPath; | ||||
| 	let zipPath; | ||||
| 	for (const asset of release.assets) { | ||||
| 		if (asset.name.endsWith('arm64.zip')) { | ||||
| 			zipPath = await downloadFile(asset, downloadDir); | ||||
| 		} else if (asset.name.endsWith('arm64.DMG')) { | ||||
| 			dmgPath = await downloadFile(asset, downloadDir); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	const info: GenerateInfo = { | ||||
| 		version: release.tag_name.slice(1), | ||||
| 		dmgPath: dmgPath, | ||||
| 		zipPath: zipPath, | ||||
| 		releaseDate: new Date().toISOString(), | ||||
| 	}; | ||||
|  | ||||
| 	const latestArm64FilePath = generateLatestArm64Yml(info, downloadDir); | ||||
| 	void uploadReleaseAsset(context, release, latestArm64FilePath); | ||||
| }; | ||||
|  | ||||
|  | ||||
| const modifyReleaseAssets = 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, | ||||
| 		targetTag: args.values.tag, | ||||
| 	}; | ||||
|  | ||||
| 	const release = await getTargetRelease(context, context.targetTag); | ||||
|  | ||||
| 	if (!release.assets) { | ||||
| 		console.log(release); | ||||
| 		throw new Error(`Release ${release.tag_name} missing assets!`); | ||||
| 	} | ||||
|  | ||||
| 	console.log('Renaming release assets for tag', context.targetTag, context.repo); | ||||
| 	void renameReleaseAssets(context, release); | ||||
| 	console.log('Creating latest-mac-arm64.yml asset for tag', context.targetTag, context.repo); | ||||
| 	void createReleaseAssets(context, release); | ||||
| }; | ||||
|  | ||||
| void modifyReleaseAssets(); | ||||
| @@ -1,109 +0,0 @@ | ||||
| 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(); | ||||
| @@ -7,9 +7,11 @@ export interface CheckForUpdateOptions { | ||||
| export interface GitHubReleaseAsset { | ||||
| 	name: string; | ||||
| 	browser_download_url: string; | ||||
| 	url?: string; | ||||
| } | ||||
|  | ||||
| export interface GitHubRelease { | ||||
| 	id?: string; | ||||
| 	tag_name: string; | ||||
| 	prerelease: boolean; | ||||
| 	body: string; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user