import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { promisify } from "node:util"; const execFileAsync = promisify(execFile); async function writeExecutable(filePath: string, contents: string) { await writeFile(filePath, contents, "utf8"); await chmod(filePath, 0o755); } test("deploy-server merges post-rsync remote work into a single ssh session", async () => { const tempRoot = await mkdtemp(path.join(os.tmpdir(), "boss-deploy-script-")); const binDir = path.join(tempRoot, "bin"); const logDir = path.join(tempRoot, "logs"); const repoRoot = "/Users/kris/code/boss"; await mkdir(binDir, { recursive: true }); await mkdir(logDir, { recursive: true }); await writeExecutable( path.join(binDir, "security"), `#!/bin/sh printf 'Asd123456.' `, ); await writeExecutable( path.join(binDir, "npm"), `#!/bin/sh printf '%s\\0' "$*" >> "${path.join(logDir, "npm.log")}" exit 0 `, ); await writeExecutable( path.join(binDir, "rsync"), `#!/bin/sh printf '%s\\0' "$*" >> "${path.join(logDir, "rsync.log")}" exit 0 `, ); await writeExecutable( path.join(binDir, "ssh"), `#!/bin/sh printf '%s\\0' "$*" >> "${path.join(logDir, "ssh.log")}" exit 0 `, ); await writeExecutable( path.join(binDir, "sshpass"), `#!/bin/sh if [ "$1" = "-e" ]; then shift fi exec "$@" `, ); try { await execFileAsync("zsh", [path.join(repoRoot, "scripts/deploy-server.sh")], { cwd: repoRoot, env: { ...process.env, PATH: `${binDir}:${process.env.PATH ?? ""}`, }, }); const sshLog = await readFile(path.join(logDir, "ssh.log"), "utf8"); const sshCalls = sshLog .split("\0") .map((line) => line.trim()) .filter(Boolean); const rsyncLog = await readFile(path.join(logDir, "rsync.log"), "utf8"); const rsyncArgs = rsyncLog .split("\0") .map((line) => line.trim()) .filter(Boolean) .join(" "); assert.equal(sshCalls.length, 2); assert.match(sshCalls[0] ?? "", /sudo mkdir -p \/opt\/boss/); assert.match(rsyncArgs, /--rsync-path=sudo rsync/); assert.match(rsyncArgs, /--exclude \.project/); assert.match(rsyncArgs, /--exclude \.classpath/); assert.match(rsyncArgs, /--exclude \.settings/); assert.match(sshCalls[1] ?? "", /bootstrap-server\.sh/); assert.match(sshCalls[1] ?? "", /sudo chown -R ubuntu:ubuntu \/opt\/boss\/data \/opt\/boss\/public\/downloads/); assert.match(sshCalls[1] ?? "", /npm install --omit=dev/); assert.match(sshCalls[1] ?? "", /systemctl restart boss-web/); assert.match(sshCalls[1] ?? "", /curl -fsS http:\/\/127\.0\.0\.1:3000\/api\/health/); } finally { await rm(tempRoot, { recursive: true, force: true }); } }); test("deploy-server falls back to remote build when local build hits ENOSPC", async () => { const tempRoot = await mkdtemp(path.join(os.tmpdir(), "boss-deploy-script-remote-build-")); const binDir = path.join(tempRoot, "bin"); const logDir = path.join(tempRoot, "logs"); const repoRoot = "/Users/kris/code/boss"; await mkdir(binDir, { recursive: true }); await mkdir(logDir, { recursive: true }); await writeExecutable( path.join(binDir, "security"), `#!/bin/sh printf 'Asd123456.' `, ); await writeExecutable( path.join(binDir, "npm"), `#!/bin/sh COUNT_FILE="${path.join(logDir, "npm-count")}" count=0 if [ -f "$COUNT_FILE" ]; then count="$(cat "$COUNT_FILE")" fi count=$((count + 1)) printf '%s' "$count" > "$COUNT_FILE" printf '%s\\0' "$*" >> "${path.join(logDir, "npm.log")}" if [ "$count" -eq 1 ]; then printf 'Error: ENOSPC: no space left on device\\n' >&2 exit 1 fi exit 0 `, ); await writeExecutable( path.join(binDir, "rsync"), `#!/bin/sh printf '%s\\0' "$*" >> "${path.join(logDir, "rsync.log")}" exit 0 `, ); await writeExecutable( path.join(binDir, "ssh"), `#!/bin/sh printf '%s\\0' "$*" >> "${path.join(logDir, "ssh.log")}" exit 0 `, ); await writeExecutable( path.join(binDir, "sshpass"), `#!/bin/sh if [ "$1" = "-e" ]; then shift fi exec "$@" `, ); try { await execFileAsync("zsh", [path.join(repoRoot, "scripts/deploy-server.sh")], { cwd: repoRoot, env: { ...process.env, PATH: `${binDir}:${process.env.PATH ?? ""}`, }, }); const rsyncLog = await readFile(path.join(logDir, "rsync.log"), "utf8"); const rsyncArgs = rsyncLog .split("\0") .map((line) => line.trim()) .filter(Boolean) .join(" "); assert.match(rsyncArgs, /--exclude \.next/); assert.match(rsyncArgs, /--rsync-path=sudo rsync/); const sshLog = await readFile(path.join(logDir, "ssh.log"), "utf8"); const sshCalls = sshLog .split("\0") .map((line) => line.trim()) .filter(Boolean); assert.equal(sshCalls.length, 2); assert.match(sshCalls[1] ?? "", /sudo chown -R ubuntu:ubuntu \/opt\/boss\/data \/opt\/boss\/public\/downloads/); assert.match(sshCalls[1] ?? "", /npm install && BOSS_RUNTIME_ROOT=\/opt\/boss BOSS_STATE_FILE=\/opt\/boss\/data\/boss-state\.json npm run build/); assert.match(sshCalls[1] ?? "", /npm prune --omit=dev/); assert.doesNotMatch(sshCalls[1] ?? "", /npm install --omit=dev/); } finally { await rm(tempRoot, { recursive: true, force: true }); } });