amical/apps/desktop/scripts/download-node-binaries.ts
haritabh-z01 f92d47df30 Squashed commit of the following:
commit 9cf278791848e0a618ebabca97d9fc7405047cde
Author: haritabh-z01 <haritabh.z01+github@gmail.com>
Date:   Fri Sep 12 00:15:52 2025 +0530

    fix: native dep coppying on windows

commit 4cdebf31659753f8f9bf0a01299474dc7a1a99a1
Author: haritabh-z01 <haritabh.z01+github@gmail.com>
Date:   Thu Sep 11 23:21:06 2025 +0530

    chore: set diff default shortcuts for windows

commit 9caf66fa7b5188cf445ace530ccf35033853909d
Author: haritabh-z01 <haritabh.z01+github@gmail.com>
Date:   Thu Sep 11 23:10:16 2025 +0530

    fix: windows shortcuts compatibility
2025-09-12 00:29:02 +05:30

300 lines
8.4 KiB
TypeScript

#!/usr/bin/env tsx
import * as https from "node:https";
import * as fs from "node:fs";
import * as path from "node:path";
import { execSync } from "node:child_process";
import { pipeline } from "node:stream/promises";
import { createWriteStream, mkdirSync, chmodSync } from "node:fs";
// Node.js version to download
const NODE_VERSION = "22.17.0";
// Platform/arch types
type Platform = "darwin" | "win32" | "linux";
type Architecture = "arm64" | "x64";
interface PlatformConfig {
platform: Platform;
arch: Architecture;
url: string;
binary: string;
}
// Platform configurations
const PLATFORMS: PlatformConfig[] = [
{
platform: "darwin",
arch: "arm64",
url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-arm64.tar.gz`,
binary: "bin/node",
},
{
platform: "darwin",
arch: "x64",
url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-x64.tar.gz`,
binary: "bin/node",
},
{
platform: "win32",
arch: "x64",
url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-win-x64.zip`,
binary: "node.exe",
},
{
platform: "linux",
arch: "x64",
url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz`,
binary: "bin/node",
},
];
const RESOURCES_DIR = path.join(__dirname, "..", "node-binaries");
// Parse command line arguments
const args = process.argv.slice(2);
const downloadAll = args.includes("--all");
async function downloadFile(url: string, dest: string): Promise<void> {
return new Promise((resolve, reject) => {
const file = createWriteStream(dest);
https
.get(url, (response) => {
if (response.statusCode === 302 || response.statusCode === 301) {
// Handle redirect
const redirectUrl = response.headers.location;
if (!redirectUrl) {
reject(new Error("Redirect without location header"));
return;
}
https
.get(redirectUrl, async (redirectResponse) => {
if (redirectResponse.statusCode !== 200) {
reject(
new Error(
`Failed to download: ${redirectResponse.statusCode}`,
),
);
return;
}
// Show download progress
const totalSize = parseInt(
redirectResponse.headers["content-length"] || "0",
10,
);
let downloadedSize = 0;
redirectResponse.on("data", (chunk) => {
downloadedSize += chunk.length;
if (totalSize > 0) {
const percent = Math.round(
(downloadedSize / totalSize) * 100,
);
process.stdout.write(`\r Downloading: ${percent}%`);
}
});
await pipeline(redirectResponse, file);
process.stdout.write("\n");
resolve();
})
.on("error", reject);
} else if (response.statusCode === 200) {
// Direct download
const totalSize = parseInt(
response.headers["content-length"] || "0",
10,
);
let downloadedSize = 0;
response.on("data", (chunk) => {
downloadedSize += chunk.length;
if (totalSize > 0) {
const percent = Math.round((downloadedSize / totalSize) * 100);
process.stdout.write(`\r Downloading: ${percent}%`);
}
});
pipeline(response, file)
.then(() => {
process.stdout.write("\n");
resolve();
})
.catch(reject);
} else {
reject(new Error(`Failed to download: ${response.statusCode}`));
}
})
.on("error", reject);
});
}
async function extractArchive(
archivePath: string,
platform: Platform,
): Promise<string> {
const tempDir = path.join(path.dirname(archivePath), "temp");
mkdirSync(tempDir, { recursive: true });
console.log(" Extracting archive...");
if (platform === "win32") {
// Use unzip command (available on macOS) to extract zip files
execSync(
`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${tempDir}' -Force"`,
{ stdio: "inherit" },
);
} else {
// Use tar for Unix-like systems
execSync(`tar -xzf "${archivePath}" -C "${tempDir}"`, { stdio: "inherit" });
}
return tempDir;
}
async function downloadNodeBinary(config: PlatformConfig): Promise<void> {
const { platform, arch, url, binary } = config;
const platformDir = path.join(RESOURCES_DIR, `${platform}-${arch}`);
const binaryPath = path.join(
platformDir,
platform === "win32" ? "node.exe" : "node",
);
// Skip if already exists
if (fs.existsSync(binaryPath)) {
console.log(`${platform}-${arch} binary already exists`);
return;
}
console.log(`\nDownloading Node.js for ${platform}-${arch}...`);
// Create directory
mkdirSync(platformDir, { recursive: true });
// Download archive
const archiveExt = platform === "win32" ? ".zip" : ".tar.gz";
const archivePath = path.join(
platformDir,
`node-v${NODE_VERSION}${archiveExt}`,
);
try {
await downloadFile(url, archivePath);
console.log(" Download complete");
// Extract archive
const tempDir = await extractArchive(archivePath, platform);
// Find the node binary in extracted files
// Windows uses different directory naming convention (win instead of win32)
const extractedDirName =
platform === "win32"
? `node-v${NODE_VERSION}-win-${arch}`
: `node-v${NODE_VERSION}-${platform}-${arch}`;
const extractedBinaryPath = path.join(tempDir, extractedDirName, binary);
// Verify binary exists
if (!fs.existsSync(extractedBinaryPath)) {
throw new Error(
`Binary not found at expected path: ${extractedBinaryPath}`,
);
}
// Copy binary to final location
console.log(" Installing binary...");
fs.copyFileSync(extractedBinaryPath, binaryPath);
// Make executable on Unix-like systems
if (platform !== "win32") {
chmodSync(binaryPath, "755");
}
// Clean up
fs.rmSync(tempDir, { recursive: true, force: true });
fs.unlinkSync(archivePath);
console.log(`✓ Successfully installed ${platform}-${arch} binary`);
} catch (error) {
console.error(
`✗ Failed to download ${platform}-${arch}:`,
error instanceof Error ? error.message : error,
);
// Clean up on failure
if (fs.existsSync(archivePath)) {
fs.unlinkSync(archivePath);
}
throw error;
}
}
function getCurrentPlatform(): PlatformConfig | undefined {
const currentPlatform = process.platform as string;
const currentArch = process.arch as string;
return PLATFORMS.find(
(p) => p.platform === currentPlatform && p.arch === currentArch,
);
}
async function main() {
console.log(`Node.js Binary Downloader v${NODE_VERSION}`);
console.log("=====================================\n");
// Create base directory
mkdirSync(RESOURCES_DIR, { recursive: true });
if (downloadAll) {
console.log("Mode: Download all platforms\n");
// Download binaries for all platforms
let success = 0;
let failed = 0;
for (const platform of PLATFORMS) {
try {
await downloadNodeBinary(platform);
success++;
} catch (error) {
failed++;
}
}
console.log(`\nSummary: ${success} succeeded, ${failed} failed`);
if (failed > 0) {
process.exit(1);
}
} else {
console.log("Mode: Download current platform only\n");
// Download only for current platform
const currentPlatform = getCurrentPlatform();
if (!currentPlatform) {
console.error(
`✗ Unsupported platform: ${process.platform}-${process.arch}`,
);
console.error(" Supported platforms:");
PLATFORMS.forEach((p) => {
console.error(` - ${p.platform}-${p.arch}`);
});
process.exit(1);
}
await downloadNodeBinary(currentPlatform);
}
console.log("\nDone! Node.js binaries available at:", RESOURCES_DIR);
}
// Run if called directly
if (require.main === module) {
main().catch((error) => {
console.error("\nFatal error:", error);
process.exit(1);
});
}
// Export for potential programmatic use
export { downloadNodeBinary, PLATFORMS, NODE_VERSION, getCurrentPlatform };