mirror of
https://github.com/Darkatse/TauriTavern.git
synced 2026-10-19 15:45:12 +00:00
196 lines
5.5 KiB
JavaScript
196 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { spawnSync } from "node:child_process";
|
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
|
|
const PRODUCT_NAME = "TauriTavern";
|
|
const BINARY_CANDIDATES = ["tauritavern", "TauriTavern"];
|
|
|
|
function printHelp() {
|
|
console.log(`Usage: node scripts/build-portable.mjs [options] [-- <extra tauri args>]
|
|
|
|
Options:
|
|
--target <triple> Rust target triple, e.g. x86_64-pc-windows-msvc
|
|
--output-dir <path> Portable output directory (default: release)
|
|
--skip-web-build Skip frontend bundle build
|
|
--help Show this help message
|
|
`);
|
|
}
|
|
|
|
function quoteShellArgument(value) {
|
|
if (value.length === 0) {
|
|
return "\"\"";
|
|
}
|
|
if (!/[\s"]/u.test(value)) {
|
|
return value;
|
|
}
|
|
return `"${value.replace(/"/gu, '\\"')}"`;
|
|
}
|
|
|
|
function run(command, args, cwd, env = process.env) {
|
|
const commandLine = [command, ...args.map(quoteShellArgument)].join(" ");
|
|
const result = spawnSync(commandLine, { cwd, env, stdio: "inherit", shell: true });
|
|
if (result.error) {
|
|
throw result.error;
|
|
}
|
|
if (result.status !== 0) {
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const options = {
|
|
target: null,
|
|
outputDir: "release",
|
|
skipWebBuild: false,
|
|
extraTauriArgs: [],
|
|
};
|
|
|
|
let passThrough = false;
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const value = argv[index];
|
|
|
|
if (passThrough) {
|
|
options.extraTauriArgs.push(value);
|
|
continue;
|
|
}
|
|
|
|
if (value === "--") {
|
|
passThrough = true;
|
|
continue;
|
|
}
|
|
|
|
if (value === "--help" || value === "-h") {
|
|
printHelp();
|
|
process.exit(0);
|
|
}
|
|
|
|
if (value === "--skip-web-build") {
|
|
options.skipWebBuild = true;
|
|
continue;
|
|
}
|
|
|
|
if (value === "--target") {
|
|
const target = argv[index + 1];
|
|
if (!target) {
|
|
throw new Error("Missing value for --target");
|
|
}
|
|
options.target = target;
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (value === "--output-dir") {
|
|
const outputDir = argv[index + 1];
|
|
if (!outputDir) {
|
|
throw new Error("Missing value for --output-dir");
|
|
}
|
|
options.outputDir = outputDir;
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
options.extraTauriArgs.push(value);
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
function resolveReleaseDirectory(repoRoot, target) {
|
|
const releaseRoot = target
|
|
? path.join(repoRoot, "src-tauri", "target", target, "release")
|
|
: path.join(repoRoot, "src-tauri", "target", "release");
|
|
return releaseRoot;
|
|
}
|
|
|
|
function resolvePortableBinary(releaseDirectory) {
|
|
const executableSuffix = process.platform === "win32" ? ".exe" : "";
|
|
|
|
for (const candidate of BINARY_CANDIDATES) {
|
|
const binaryPath = path.join(releaseDirectory, `${candidate}${executableSuffix}`);
|
|
if (existsSync(binaryPath) && statSync(binaryPath).isFile()) {
|
|
return binaryPath;
|
|
}
|
|
}
|
|
|
|
const fallback = readdirSync(releaseDirectory, { withFileTypes: true })
|
|
.filter((entry) => entry.isFile())
|
|
.map((entry) => entry.name)
|
|
.find((name) => {
|
|
if (process.platform === "win32") {
|
|
return name.toLowerCase().endsWith(".exe");
|
|
}
|
|
return !name.includes(".");
|
|
});
|
|
|
|
if (fallback) {
|
|
return path.join(releaseDirectory, fallback);
|
|
}
|
|
|
|
throw new Error(`Unable to locate built portable binary in ${releaseDirectory}`);
|
|
}
|
|
|
|
function normalizePlatform(platform) {
|
|
if (platform === "win32") {
|
|
return "windows";
|
|
}
|
|
if (platform === "darwin") {
|
|
return "macos";
|
|
}
|
|
return platform;
|
|
}
|
|
|
|
function normalizeArch(arch) {
|
|
if (arch === "x64") {
|
|
return "x64";
|
|
}
|
|
if (arch === "ia32") {
|
|
return "x86";
|
|
}
|
|
return arch;
|
|
}
|
|
|
|
function buildPlatformTag(target) {
|
|
if (target) {
|
|
return target.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
}
|
|
return `${normalizePlatform(process.platform)}-${normalizeArch(process.arch)}`;
|
|
}
|
|
|
|
function main() {
|
|
const repoRoot = process.cwd();
|
|
const options = parseArgs(process.argv.slice(2));
|
|
|
|
const tauriArgs = ["scripts/tauri-app.mjs", "build", "--no-bundle", "--features", "portable"];
|
|
if (options.target) {
|
|
tauriArgs.push("--target", options.target);
|
|
}
|
|
if (options.extraTauriArgs.length > 0) {
|
|
tauriArgs.push(...options.extraTauriArgs);
|
|
}
|
|
const tauriEnv = options.skipWebBuild
|
|
? { ...process.env, TAURITAVERN_SKIP_WEB_BUILD: "1" }
|
|
: process.env;
|
|
run("node", tauriArgs, repoRoot, tauriEnv);
|
|
|
|
const releaseDirectory = resolveReleaseDirectory(repoRoot, options.target);
|
|
const binaryPath = resolvePortableBinary(releaseDirectory);
|
|
const outputDirectory = path.resolve(repoRoot, options.outputDir);
|
|
mkdirSync(outputDirectory, { recursive: true });
|
|
|
|
const extension = process.platform === "win32" ? ".exe" : "";
|
|
const artifactName = `${PRODUCT_NAME}-${buildPlatformTag(options.target)}-portable${extension}`;
|
|
const artifactPath = path.join(outputDirectory, artifactName);
|
|
copyFileSync(binaryPath, artifactPath);
|
|
|
|
console.log(`Portable binary copied to ${artifactPath}`);
|
|
}
|
|
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
}
|