Files
tauri-action/src/upload-version-json.ts
T
2025-11-04 13:28:13 +01:00

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,
);
}