const { execSync } = require("child_process"); const fs = require("fs"); const isWin = process.platform === "win32"; const isMac = process.platform === "darwin"; const src = "dist"; const dstWin = "Z:\\docker\\webpage\\frontend\\"; const dstMac = "/Volumes/gahusb.synology.me/docker/webpage/frontend/"; const dst = isWin ? dstWin : dstMac; if (!fs.existsSync(src)) { console.error("dist not found. Run build first."); process.exit(1); } if (!fs.existsSync(dst)) { console.error("NAS path not found. Check mount: " + dst); process.exit(1); } if (isWin) { const cmd = 'powershell -NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference=\\"Stop\\"; $src=\\"dist\\"; $dst=\\"Z:\\\\docker\\\\webpage\\\\frontend\\\\\\"; if(!(Test-Path $src)){ throw \\"dist not found. Run build first.\\" }; if(!(Test-Path $dst)){ throw \\"NAS drive not found. Check Z: mapping.\\" }; $log = Join-Path (Get-Location) \\"robocopy.log\\"; robocopy $src $dst /MIR /R:1 /W:1 /E /NFL /NDL /NP /V /TEE /LOG:$log; $rc = $LASTEXITCODE; if($rc -ge 8){ Write-Host \\"robocopy failed with code $rc. See $log\\"; exit $rc } else { exit 0 }"'; execSync(cmd, { stdio: "inherit" }); } else if (isMac) { const sshTarget = process.env.NAS_SSH_TARGET; const sshPath = process.env.NAS_SSH_PATH || "/volume1/docker/webpage/frontend/"; const sshPort = process.env.NAS_SSH_PORT; if (sshTarget) { const sshCmd = sshPort ? `ssh -p ${sshPort}` : "ssh"; execSync( `rsync -r --delete --delete-delay -e \"${sshCmd}\" ${src}/ ${sshTarget}:${sshPath}`, { stdio: "inherit" } ); process.exit(0); } // rsync on macOS + SMB/NAS can be flaky; use ditto after a safe clean. if (!dst.includes("docker/webpage/frontend")) { console.error("Safety check failed: unexpected dst path: " + dst); process.exit(1); } try { const testPath = `${dst}.deploy-write-test`; fs.writeFileSync(testPath, "ok"); fs.unlinkSync(testPath); } catch (err) { console.error( "NAS write test failed. Files may be locked or permissions are read-only." ); console.error( "Try stopping services using the folder, remounting the share with write access,", "or set NAS_SSH_TARGET to deploy over SSH instead." ); throw err; } const sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); const retry = (fn, attempts = 6) => { for (let i = 0; i < attempts; i += 1) { try { fn(); return; } catch (err) { if (i === attempts - 1) throw err; sleep(150); } } }; const removeTree = (target) => { const stat = fs.lstatSync(target); if (stat.isDirectory()) { retry(() => { const entries = fs.readdirSync(target); for (const entry of entries) { removeTree(`${target}/${entry}`); } }); retry(() => fs.rmdirSync(target)); } else { retry(() => fs.unlinkSync(target)); } }; if (process.env.NAS_CLEAN === "1") { try { const entries = fs.readdirSync(dst); for (const entry of entries) { removeTree(`${dst}${entry}`); } } catch (err) { console.warn("Clean skipped due to NAS lock:", err?.message ?? err); } } execSync(`ditto ${src} ${dst}`, { stdio: "inherit" }); } else { const baseArgs = ["rsync", "-r", "--delete", "--delete-delay", "-t"]; const cmd = `${baseArgs.join(" ")} ${src}/ ${dst}`; execSync(cmd, { stdio: "inherit" }); }