Skip to content

Commit

Permalink
Desktop: Seamless-Updates: generated and uploaded latest-mac-arm64.ym…
Browse files Browse the repository at this point in the history
…l to GitHub Releases (laurent22#11042)
  • Loading branch information
AliceHincu authored Sep 14, 2024
1 parent 4fa61e4 commit 5763de3
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 193 deletions.
4 changes: 3 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-macos-m1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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..."
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 1 addition & 80 deletions packages/app-desktop/afterAllArtifactBuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
};

Expand Down
2 changes: 1 addition & 1 deletion packages/app-desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
69 changes: 69 additions & 0 deletions packages/app-desktop/tools/generateLatestArm64Yml.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 105 additions & 0 deletions packages/app-desktop/tools/githubReleasesUtils.ts
Original file line number Diff line number Diff line change
@@ -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.`);
}
};
Loading

0 comments on commit 5763de3

Please sign in to comment.