Add portable Windows desktop downloads and release uploads

This commit is contained in:
2026-10-11 11:44:27 +05:00
parent fc6da6756d
commit a54a3e7705
24 changed files with 4159 additions and 5 deletions

View File

@@ -0,0 +1,23 @@
import fs from "node:fs";
import path from "node:path";
import { createHash } from "node:crypto";
import { fileURLToPath } from "node:url";
export function assembleDesktopRelease(root) {
const directory = path.join(root, ".desktop-release");
const manifestPath = path.join(directory, "manifest.json");
if (!fs.existsSync(manifestPath)) return;
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
if (!/^MineGeo-[\d.]+-windows-x64\.exe$/.test(manifest.name)) throw new Error("Invalid desktop release name");
if (!Number.isInteger(manifest.parts) || manifest.parts < 1 || manifest.parts > 100) throw new Error("Invalid desktop release parts");
const binary = Buffer.concat(Array.from({ length: manifest.parts }, (_, i) => fs.readFileSync(path.join(directory, `part-${i}`))));
if (binary.length !== manifest.bytes || createHash("sha256").update(binary).digest("hex") !== manifest.sha256) throw new Error("Desktop release checksum mismatch");
const destination = path.join(root, "public", "desktop");
fs.mkdirSync(destination, { recursive: true });
fs.writeFileSync(path.join(destination, manifest.name), binary);
console.log(`Verified and assembled desktop release: ${manifest.name}`);
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
assembleDesktopRelease(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
}

View File

@@ -4,6 +4,7 @@ import os from "node:os";
import { fileURLToPath } from "node:url";
import { execFileSync } from "node:child_process";
import { unzipSync } from "fflate";
import { createHash } from "node:crypto";
const defaultRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
@@ -52,7 +53,7 @@ export function deploymentFiles(root = defaultRoot) {
if (relative === "package.json") {
const pkg = JSON.parse(data);
pkg.scripts = {
prebuild: "node scripts/clean-build.mjs",
prebuild: "node scripts/assemble-desktop-release.mjs && node scripts/clean-build.mjs",
build: "next build",
start: "next start",
};
@@ -89,6 +90,15 @@ export function deploymentFiles(root = defaultRoot) {
throw new Error(
`Private Git server reference found in ${relative}. Remove it before deploying.`,
);
if (relative.startsWith("public/desktop/") && relative.endsWith(".exe") && data.length > 10 * 1024 * 1024) {
const chunkSize = 6 * 1024 * 1024;
const parts = Math.ceil(data.length / chunkSize);
for (let index = 0; index < parts; index++) {
files.push({ file: `.desktop-release/part-${index}`, data: data.subarray(index * chunkSize, (index + 1) * chunkSize).toString("base64"), encoding: "base64" });
}
files.push({ file: ".desktop-release/manifest.json", data: Buffer.from(JSON.stringify({ name: path.basename(relative), parts, bytes: data.length, sha256: createHash("sha256").update(data).digest("hex") })).toString("base64"), encoding: "base64" });
return;
}
files.push({
file: relative,
data: data.toString("base64"),
@@ -124,6 +134,7 @@ export function deploymentFiles(root = defaultRoot) {
"postcss.config.mjs",
"vercel.json",
"scripts/clean-build.mjs",
"scripts/assemble-desktop-release.mjs",
]) {
if (fs.existsSync(path.join(root, file))) add(path.join(root, file), file);
}
@@ -220,7 +231,36 @@ export async function deploy(
);
return data;
}
let deployment = await api("/v13/deployments", payload);
const files = [];
for (const file of payload.files) {
const data = Buffer.from(file.data || "", "base64");
if (file.encoding !== "base64" || data.length <= 10 * 1024 * 1024) {
files.push(file);
continue;
}
const sha = createHash("sha1").update(data).digest("hex");
const response = await request(
`https://api.vercel.com/v2/files?teamId=${encodeURIComponent(team)}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/octet-stream",
"Content-Length": String(data.length),
"x-vercel-digest": sha,
},
body: data,
signal: AbortSignal.timeout(300000),
},
);
if (!response.ok)
throw new Error(
`Vercel file upload failed (${response.status}): ${file.file}`,
);
files.push({ file: file.file, sha, size: data.length });
log(`Uploaded release: ${file.file}`);
}
let deployment = await api("/v13/deployments", { ...payload, files });
if (!deployment.id) throw new Error("Vercel did not return a deployment ID.");
log(
`Uploaded ${payload.files.length} site files in one request. Waiting for Vercel…`,