125 lines
3.7 KiB
TypeScript
125 lines
3.7 KiB
TypeScript
import {
|
|
copyFileSync,
|
|
createReadStream,
|
|
existsSync,
|
|
mkdirSync,
|
|
readdirSync,
|
|
renameSync,
|
|
rmSync,
|
|
statSync,
|
|
} from "node:fs";
|
|
import { createHash } from "node:crypto";
|
|
import { basename, join } from "node:path";
|
|
import packageJson from "../package.json" with { type: "json" };
|
|
|
|
const { version } = packageJson;
|
|
const releaseVersion = process.env.TENSAMIN_RELEASE_VERSION || version;
|
|
const releaseTag = process.env.TENSAMIN_RELEASE_TAG || releaseVersion;
|
|
const releasesDir = join("releases");
|
|
const releaseAssetBaseUrl = process.env.FORGEJO_RELEASE_ASSET_BASE_URL;
|
|
|
|
function sha256(filePath: string) {
|
|
const hash = createHash("sha256");
|
|
const stream = createReadStream(filePath);
|
|
|
|
return new Promise<string>((resolve, reject) => {
|
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
stream.on("error", reject);
|
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
});
|
|
}
|
|
|
|
function platformFor(file: string) {
|
|
if (/\.apk$/i.test(file)) return "android";
|
|
if (/win|nsis|portable|\.exe$/i.test(file)) return "windows";
|
|
if (/mac|darwin|\.dmg$/i.test(file)) return "macos";
|
|
return "linux";
|
|
}
|
|
|
|
function archFor(file: string) {
|
|
if (/arm64|aarch64/i.test(file)) return "arm64";
|
|
if (/\.apk$/i.test(file)) return "universal";
|
|
return "x64";
|
|
}
|
|
|
|
// directory
|
|
rmSync(releasesDir, { recursive: true, force: true });
|
|
mkdirSync(releasesDir, { recursive: true });
|
|
|
|
// apk
|
|
const apkSrc =
|
|
"apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk";
|
|
if (existsSync(apkSrc)) {
|
|
renameSync(apkSrc, join(releasesDir, `Tensamin-${version}.apk`));
|
|
} else {
|
|
console.warn(`Skipping .apk: ${apkSrc} does not exist.`);
|
|
}
|
|
|
|
// Electron desktop artifacts
|
|
const electronReleaseDir = "apps/electron/release";
|
|
const electronArtifactPattern = /\.(AppImage|deb|rpm|exe|dmg|zip)$/i;
|
|
|
|
function readFilesRecursive(dir: string): string[] {
|
|
return readdirSync(dir).flatMap((file) => {
|
|
const filePath = join(dir, file);
|
|
const stat = statSync(filePath);
|
|
|
|
if (stat.isDirectory()) return readFilesRecursive(filePath);
|
|
if (stat.isFile()) return [filePath];
|
|
return [];
|
|
});
|
|
}
|
|
|
|
const copyElectronArtifacts = () => {
|
|
if (!existsSync(electronReleaseDir)) {
|
|
console.warn(
|
|
`Skipping Electron artifacts: ${electronReleaseDir} does not exist.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
for (const source of readFilesRecursive(electronReleaseDir)) {
|
|
const file = basename(source);
|
|
if (!electronArtifactPattern.test(file)) continue;
|
|
|
|
copyFileSync(source, join(releasesDir, file));
|
|
}
|
|
};
|
|
|
|
copyElectronArtifacts();
|
|
|
|
const artifacts = await Promise.all(
|
|
readdirSync(releasesDir)
|
|
.map((file) => join(releasesDir, file))
|
|
.filter((file) => statSync(file).isFile())
|
|
.filter(
|
|
(file) =>
|
|
!file.endsWith("electron-release-metadata.json") &&
|
|
!file.endsWith("SHA256SUMS"),
|
|
)
|
|
.map(async (filePath) => {
|
|
const name = filePath.split(/[\\/]/).at(-1)!;
|
|
|
|
return {
|
|
name,
|
|
platform: platformFor(name),
|
|
arch: archFor(name),
|
|
url: releaseAssetBaseUrl
|
|
? `${releaseAssetBaseUrl.replace(/\/$/, "")}/${encodeURIComponent(name)}`
|
|
: `__FORGEJO_RELEASE_ASSET_URL__/${encodeURIComponent(name)}`,
|
|
sha256: await sha256(filePath),
|
|
size: statSync(filePath).size,
|
|
};
|
|
}),
|
|
);
|
|
|
|
await Bun.write(
|
|
join(releasesDir, "electron-release-metadata.json"),
|
|
`${JSON.stringify({ version: releaseVersion, tag: releaseTag, publishedAt: new Date().toISOString(), artifacts: artifacts.filter((artifact) => artifact.platform !== "android") }, null, 2)}\n`,
|
|
);
|
|
await Bun.write(
|
|
join(releasesDir, "SHA256SUMS"),
|
|
`${artifacts.map((artifact) => `${artifact.sha256} ${artifact.name}`).join("\n")}\n`,
|
|
);
|
|
|
|
console.log("Releases copied successfully.");
|