import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, } from "node:fs"; import path from "node:path"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import { getReleaseArch, getReleaseArtifactName, getReleaseVersion, } from "./release-artifact-utils.mjs"; const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const packageJson = JSON.parse( readFileSync(path.join(rootDir, "package.json"), "utf8"), ); const buildEnv = process.env.ELECTROBUN_BUILD_ENV || "stable"; const targetOs = process.env.ELECTROBUN_OS || (process.platform === "darwin" ? "macos" : process.platform === "win32" ? "win" : process.platform === "linux" ? "linux" : null); const buildArch = getReleaseArch( process.env.ELECTROBUN_ARCH || process.arch, ); const version = process.env.ELECTROBUN_APP_VERSION || getReleaseVersion(packageJson); const artifactDir = process.env.ELECTROBUN_ARTIFACT_DIR || path.join(rootDir, "app_dist"); const identifier = process.env.ELECTROBUN_APP_IDENTIFIER || "com.tombutcher.farmcontrol"; const artifactPrefix = `farmcontrol-${version}-`; function getBuildRoot() { const electrobunBuildDir = process.env.ELECTROBUN_BUILD_DIR; if (!electrobunBuildDir) { return path.join(rootDir, "build"); } const baseName = path.basename(electrobunBuildDir); if (baseName.startsWith("stable-")) { return path.dirname(electrobunBuildDir); } return electrobunBuildDir; } function walkFiles(dir) { const files = []; if (!existsSync(dir)) { return files; } for (const entry of readdirSync(dir)) { const fullPath = path.join(dir, entry); const stats = statSync(fullPath); if (stats.isDirectory()) { files.push(...walkFiles(fullPath)); } else { files.push(fullPath); } } return files; } function findByExtension(root, extension) { return walkFiles(root).find((filePath) => filePath.toLowerCase().endsWith(extension.toLowerCase()), ); } function findMacAppBundle(arch) { const platformDir = path.join(getBuildRoot(), `stable-macos-${arch}`); if (!existsSync(platformDir)) { return null; } for (const child of readdirSync(platformDir)) { if (child.endsWith(".app")) { return path.join(platformDir, child); } } return null; } function findMacDmgSource(arch) { const prefixedArtifact = walkFiles(artifactDir).find( (filePath) => filePath.includes(`stable-macos-${arch}`) && filePath.toLowerCase().endsWith(".dmg"), ); if (prefixedArtifact) { return prefixedArtifact; } const buildDmg = findByExtension( path.join(getBuildRoot(), `stable-macos-${arch}`), ".dmg", ); if (buildDmg) { return buildDmg; } return findByExtension(artifactDir, ".dmg"); } function publishArtifact(sourcePath, arch, ext) { if (!sourcePath || !existsSync(sourcePath)) { throw new Error( `Missing source artifact for ${arch}.${ext}: ${sourcePath ?? "not found"}`, ); } mkdirSync(artifactDir, { recursive: true }); const destination = path.join( artifactDir, getReleaseArtifactName(version, arch, ext), ); cpSync(sourcePath, destination); console.log(`Published ${destination}`); return destination; } function buildMacPkg(appBundlePath, arch) { const pkgPath = path.join( artifactDir, getReleaseArtifactName(version, arch, "pkg"), ); const result = spawnSync( "pkgbuild", [ "--component", appBundlePath, "--install-location", "/Applications", "--identifier", identifier, "--version", version, pkgPath, ], { stdio: "inherit" }, ); if (result.status !== 0) { throw new Error(`pkgbuild failed with exit code ${result.status ?? 1}`); } console.log(`Published ${pkgPath}`); return pkgPath; } if (buildEnv === "dev") { console.log("finalize-desktop-artifacts: skipping dev build"); process.exit(0); } if (targetOs !== "macos") { console.log( "finalize-desktop-artifacts: no desktop packaging configured for", targetOs ?? "unknown target", ); process.exit(0); } const dmgSource = findMacDmgSource(buildArch); const appBundle = findMacAppBundle(buildArch); if (!dmgSource || !appBundle) { console.log( `finalize-desktop-artifacts: no macOS ${buildArch} release artifacts found, skipping`, ); process.exit(0); } const published = [ publishArtifact(dmgSource, buildArch, "dmg"), buildMacPkg(appBundle, buildArch), ]; for (const filePath of published) { const basename = path.basename(filePath); for (const entry of readdirSync(artifactDir)) { if (entry === basename || entry.startsWith(artifactPrefix)) { continue; } if ( entry.includes("stable-macos-") || entry.endsWith("-update.json") || entry.endsWith(".tar.gz") ) { rmSync(path.join(artifactDir, entry), { recursive: true, force: true }); } } }