303 lines
8.4 KiB
TypeScript
303 lines
8.4 KiB
TypeScript
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { basename, extname, resolve } from 'node:path';
|
|
|
|
import { getOctokit } from '@actions/github';
|
|
|
|
import { uploadAssets } from './upload-release-assets';
|
|
import { deleteGiteaReleaseAsset, getAssetName } from './utils';
|
|
|
|
import type { Artifact, TargetInfo } from './types';
|
|
import { createArtifact } from './utils';
|
|
|
|
type Platform = {
|
|
signature: string;
|
|
url: string;
|
|
};
|
|
|
|
type VersionContent = {
|
|
version: string;
|
|
notes: string;
|
|
pub_date: string;
|
|
platforms: {
|
|
[key: string]: Platform;
|
|
};
|
|
};
|
|
|
|
export async function uploadVersionJSON(
|
|
owner: string,
|
|
repo: string,
|
|
version: string,
|
|
notes: string,
|
|
tagName: string,
|
|
releaseId: number,
|
|
artifacts: Artifact[],
|
|
targetInfo: TargetInfo,
|
|
unzippedSig: boolean,
|
|
updaterJsonPreferNsis: boolean,
|
|
updaterJsonKeepUniversal: boolean,
|
|
retryAttempts: number,
|
|
githubBaseUrl: string,
|
|
isGitea: boolean,
|
|
assetNamePattern?: string,
|
|
) {
|
|
if (process.env.GITHUB_TOKEN === undefined) {
|
|
throw new Error('GITHUB_TOKEN is required');
|
|
}
|
|
|
|
const github = getOctokit(process.env.GITHUB_TOKEN, {
|
|
baseUrl: githubBaseUrl,
|
|
});
|
|
|
|
const versionFilename = 'latest.json';
|
|
const versionFile = resolve(process.cwd(), versionFilename);
|
|
const versionContent: VersionContent = {
|
|
version,
|
|
notes,
|
|
pub_date: new Date().toISOString(),
|
|
platforms: {},
|
|
};
|
|
|
|
const assets = await github.rest.repos.listReleaseAssets({
|
|
owner: owner,
|
|
repo: repo,
|
|
release_id: releaseId,
|
|
per_page: 50,
|
|
});
|
|
const asset = assets.data.find((e) => e.name === versionFilename);
|
|
|
|
if (asset) {
|
|
if (isGitea) {
|
|
const info = (
|
|
await github.request(
|
|
'GET /repos/{owner}/{repo}/releases/{release_id}/assets/{asset_id}',
|
|
{
|
|
owner,
|
|
repo,
|
|
release_id: releaseId,
|
|
asset_id: asset.id,
|
|
},
|
|
)
|
|
).data as { browser_download_url: string };
|
|
|
|
const data = (await github.request(`GET ${info.browser_download_url}`))
|
|
.data as string;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
versionContent.platforms = JSON.parse(
|
|
data,
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
).platforms;
|
|
} else {
|
|
const assetData = (
|
|
await github.request(
|
|
`GET /repos/{owner}/{repo}/releases/assets/{asset_id}`,
|
|
{
|
|
owner: owner,
|
|
repo: repo,
|
|
release_id: releaseId,
|
|
asset_id: asset.id,
|
|
headers: {
|
|
accept: 'application/octet-stream',
|
|
},
|
|
},
|
|
)
|
|
).data as unknown as ArrayBuffer;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
versionContent.platforms = JSON.parse(
|
|
Buffer.from(assetData).toString(),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
).platforms;
|
|
}
|
|
}
|
|
|
|
const downloadUrls = new Map<string, string>();
|
|
for (const data of assets.data) {
|
|
downloadUrls.set(data.name, data.browser_download_url);
|
|
}
|
|
|
|
// Assets matching artifacts generated by this action
|
|
const filteredAssets = [];
|
|
for (const artifact of artifacts) {
|
|
const assetName = getAssetName(artifact, assetNamePattern)
|
|
.trim()
|
|
.replace(/[ ()[\]{}]/g, '.')
|
|
.replace(/\.\./g, '.')
|
|
.normalize('NFD')
|
|
.replace(/[\u0300-\u036f]/g, '');
|
|
const downloadUrl = downloadUrls.get(assetName);
|
|
if (downloadUrl) {
|
|
filteredAssets.push({
|
|
downloadUrl,
|
|
assetName,
|
|
path: artifact.path,
|
|
arch: artifact.arch,
|
|
bundle: artifact.bundle,
|
|
});
|
|
}
|
|
}
|
|
|
|
const signatureFiles = filteredAssets.filter((asset) => {
|
|
return asset.assetName.endsWith('.sig');
|
|
});
|
|
function signaturePriority(signaturePath: string) {
|
|
if (
|
|
(unzippedSig && signaturePath.endsWith('.AppImage.sig')) ||
|
|
(!unzippedSig && signaturePath.endsWith('.AppImage.tar.gz.sig'))
|
|
) {
|
|
return 100;
|
|
}
|
|
const priorities = updaterJsonPreferNsis
|
|
? unzippedSig
|
|
? ['.exe.sig', '.msi.sig']
|
|
: ['.nsis.zip.sig', '.msi.zip.sig']
|
|
: unzippedSig
|
|
? ['.msi.sig', '.exe.sig']
|
|
: ['.msi.zip.sig', '.nsis.zip.sig'];
|
|
for (const [index, extension] of priorities.entries()) {
|
|
if (signaturePath.endsWith(extension)) {
|
|
return 100 - index;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
signatureFiles.sort((a, b) => {
|
|
return signaturePriority(b.path) - signaturePriority(a.path);
|
|
});
|
|
|
|
if (!signatureFiles[0]) {
|
|
console.warn(
|
|
'Signature not found for the updater JSON. Skipping upload...',
|
|
);
|
|
return;
|
|
}
|
|
|
|
for (const [idx, signatureFile] of signatureFiles.entries()) {
|
|
const updaterFileName = basename(
|
|
signatureFile.assetName,
|
|
extname(signatureFile.assetName),
|
|
);
|
|
let updaterFileDownloadUrl = filteredAssets.find(
|
|
(asset) => asset.assetName === updaterFileName,
|
|
)?.downloadUrl;
|
|
|
|
if (!updaterFileDownloadUrl) {
|
|
console.warn(
|
|
`Updater asset belonging to signature file "${signatureFile.assetName}" not found.`,
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// Untagged release downloads won't work after the release was published
|
|
updaterFileDownloadUrl = updaterFileDownloadUrl.replace(
|
|
/\/download\/(untagged-[^/]+)\//,
|
|
tagName
|
|
? `/download/${encodeURIComponent(tagName)}/`
|
|
: '/latest/download/',
|
|
);
|
|
|
|
let os = targetInfo.platform as string;
|
|
if (os === 'macos') {
|
|
os = 'darwin';
|
|
}
|
|
|
|
let arch = signatureFile.arch;
|
|
arch =
|
|
arch === 'amd64' || arch === 'x86_64' || arch === 'x64'
|
|
? 'x86_64'
|
|
: arch === 'x86' || arch === 'i386'
|
|
? 'i686'
|
|
: arch === 'arm'
|
|
? 'armv7'
|
|
: arch === 'arm64'
|
|
? 'aarch64'
|
|
: arch;
|
|
|
|
// This is our primary updater type we use for `{os}-{arch}`
|
|
if (idx === 0) {
|
|
if (os === 'darwin' && arch === 'universal') {
|
|
// Don't overwrite native builds
|
|
if (!versionContent.platforms['darwin-aarch64']) {
|
|
(versionContent.platforms['darwin-aarch64'] as unknown) = {
|
|
signature: readFileSync(signatureFile.path).toString(),
|
|
url: updaterFileDownloadUrl,
|
|
};
|
|
}
|
|
if (!versionContent.platforms['darwin-x86_64']) {
|
|
(versionContent.platforms['darwin-x86_64'] as unknown) = {
|
|
signature: readFileSync(signatureFile.path).toString(),
|
|
url: updaterFileDownloadUrl,
|
|
};
|
|
}
|
|
}
|
|
if (updaterJsonKeepUniversal || os !== 'darwin' || arch !== 'universal') {
|
|
(versionContent.platforms[`${os}-${arch}`] as unknown) = {
|
|
signature: readFileSync(signatureFile.path).toString(),
|
|
url: updaterFileDownloadUrl,
|
|
};
|
|
}
|
|
}
|
|
|
|
// This is for the new `{os}-{arch}-{installer}` format
|
|
if (os === 'darwin' && arch === 'universal') {
|
|
// Don't overwrite native builds
|
|
if (!versionContent.platforms['darwin-aarch64-app']) {
|
|
(versionContent.platforms['darwin-aarch64-app'] as unknown) = {
|
|
signature: readFileSync(signatureFile.path).toString(),
|
|
url: updaterFileDownloadUrl,
|
|
};
|
|
}
|
|
if (!versionContent.platforms['darwin-x86_64-app']) {
|
|
(versionContent.platforms['darwin-x86_64-app'] as unknown) = {
|
|
signature: readFileSync(signatureFile.path).toString(),
|
|
url: updaterFileDownloadUrl,
|
|
};
|
|
}
|
|
}
|
|
if (updaterJsonKeepUniversal || os !== 'darwin' || arch !== 'universal') {
|
|
(versionContent.platforms[
|
|
`${os}-${arch}-${signatureFile.bundle}`
|
|
] as unknown) = {
|
|
signature: readFileSync(signatureFile.path).toString(),
|
|
url: updaterFileDownloadUrl,
|
|
};
|
|
}
|
|
}
|
|
|
|
writeFileSync(versionFile, JSON.stringify(versionContent, null, 2));
|
|
|
|
if (asset) {
|
|
if (isGitea) {
|
|
await deleteGiteaReleaseAsset(github, owner, repo, releaseId, asset.id);
|
|
} else {
|
|
// https://docs.github.com/en/rest/releases/assets#update-a-release-asset
|
|
await github.rest.repos.deleteReleaseAsset({
|
|
owner: owner,
|
|
repo: repo,
|
|
release_id: releaseId,
|
|
asset_id: asset.id,
|
|
});
|
|
}
|
|
}
|
|
|
|
const artifact = createArtifact({
|
|
path: versionFile,
|
|
name: versionFilename,
|
|
debug: false,
|
|
platform: targetInfo.platform,
|
|
arch: '',
|
|
bundle: '',
|
|
version,
|
|
});
|
|
|
|
await uploadAssets(
|
|
owner,
|
|
repo,
|
|
releaseId,
|
|
[artifact],
|
|
retryAttempts,
|
|
githubBaseUrl,
|
|
isGitea,
|
|
);
|
|
}
|