@nmakarov/cli-toolkit 0.55.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/deploy/service.js","../src/deploy/paths.js","../src/deploy/run.js","../src/deploy/log.js","../src/deploy/git.js","../src/deploy/release.js","../src/deploy/activate.js","../src/deploy/deps.js","../src/deploy/test.js","../src/deploy/prune.js","../src/deploy/pm2.js","../src/deploy/nginx.js","../src/deploy/sync-env.js","../src/deploy/build-info.js","../src/deploy/init-structure.js","../src/deploy/bootstrap-host.js","../src/deploy/deploy-service.js","../src/deploy/provision-service.js","../src/deploy/rollback-service.js","../src/deploy/ssh-remote.js","../src/deploy/manifests.js"],"sourcesContent":["/**\n * A \"service\" is a plain manifest object describing how to deploy one app onto a\n * host. Projects define these; the engine is otherwise project-agnostic.\n *\n * Required:\n * name unique id, also the default pm2 app + nginx site name\n * appsRoot base dir on the host, e.g. /apps/<name>\n * repoUrl git url to clone (ssh git@… or https://…)\n * pm2.script entry script (relative to the release/cwd)\n *\n * Optional (sensible defaults applied by defineService):\n * repoDirName default: repo name derived from repoUrl\n * repoSubdir default: \"\" (release = repo root). Set \"v2\" for a monorepo subdir.\n * keepReleases default: 3\n * testCommand default: null (shell run in the release before activating; null = skip)\n * envScrubPatterns default: [] (regex source strings; matching .env lines are dropped)\n * legacyRepoEnv default: null (fallback .env path on a pre-migration host)\n * buildInfoPath default: \"build-info.json\" (written into each release, relative to release root)\n * deployKey default: null (path to a git deploy key; only needed for ssh repoUrl)\n * requireEnv default: false (true = fail if no .env found; false = continue with empty)\n * pm2: {\n * appName=name, script, args=\"\", port,\n * stopAllowance=60, // seconds → injected as --stopAllowance=N on the process\n * killTimeout=65000, // ms; pm2 kill_timeout (default: stopAllowance*1000+5000)\n * }\n * nginx: { siteName=name, fqdn } (omit nginx entirely to skip the nginx step)\n */\nexport function deriveRepoDirName(repoUrl) {\n const tail = String(repoUrl ?? \"\").split(\"/\").pop() ?? \"repo\";\n return tail.replace(/\\.git$/, \"\") || \"repo\";\n}\n\nexport function defineService(service) {\n if (!service?.name) throw new Error(\"service manifest needs a `name`\");\n if (!service.appsRoot) throw new Error(`service \"${service.name}\" needs an appsRoot`);\n if (!service.repoUrl) throw new Error(`service \"${service.name}\" needs a repoUrl`);\n if (!service.pm2?.script) throw new Error(`service \"${service.name}\" needs pm2.script`);\n\n const pm2 = {\n appName: service.name,\n args: \"\",\n stopAllowance: 60,\n ...service.pm2,\n };\n if (pm2.killTimeout == null) {\n const stopSec = Number(pm2.stopAllowance);\n pm2.killTimeout = Number.isFinite(stopSec) && stopSec > 0\n ? Math.floor(stopSec * 1000) + 5000\n : 65_000;\n }\n const nginx = service.nginx\n ? { siteName: service.name, ...service.nginx }\n : null;\n\n return {\n repoDirName: deriveRepoDirName(service.repoUrl),\n repoSubdir: \"\",\n keepReleases: 3,\n testCommand: null,\n envScrubPatterns: [],\n legacyRepoEnv: null,\n buildInfoPath: \"build-info.json\",\n deployKey: null,\n requireEnv: false,\n ...service,\n pm2,\n nginx,\n };\n}\n","import { join } from \"node:path\";\n\n/**\n * Resolve on-disk paths for a service under <appsRoot>/.\n *\n * <appsRoot>/\n * <repoDirName>/[<repoSubdir>/] git checkout (build source only)\n * releases/<stamp>/ one release = contents of the run dir\n * shared/.env, ecosystem.config.cjs\n * logs/deploy.log\n * current -> releases/<stamp>\n */\nexport function servicePaths(service) {\n const root = service.appsRoot;\n const repoDirName = service.repoDirName ?? \"repo\";\n const repoSubdir = service.repoSubdir ?? \"\";\n\n const repo = join(root, repoDirName);\n const repoRun = repoSubdir ? join(repo, repoSubdir) : repo;\n\n return {\n root,\n repo,\n repoRun,\n repoEnv: join(repoRun, \".env\"),\n releases: join(root, \"releases\"),\n shared: join(root, \"shared\"),\n logs: join(root, \"logs\"),\n current: join(root, \"current\"),\n sharedEnv: join(root, \"shared\", \".env\"),\n ecosystem: join(root, \"shared\", \"ecosystem.config.cjs\"),\n deployLog: join(root, \"logs\", \"deploy.log\"),\n lockHashFile: join(root, \"shared\", \".package-lock.sha256\"),\n };\n}\n\n/** ISO-8601 release stamp with `:` (safe on Linux ext4). */\nexport function releaseStamp(date = new Date()) {\n return date.toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n}\n\nexport function releaseDir(releasesRoot, stamp) {\n return join(releasesRoot, stamp);\n}\n","import { spawn } from \"node:child_process\";\n\n/** npm omits devDependencies when NODE_ENV=production — unset it for ci + tests. */\nexport function npmEnv(baseEnv = process.env) {\n const env = { ...baseEnv };\n delete env.NODE_ENV;\n return env;\n}\n\n/** Env for npm ci/install so devDependencies are always installed. */\nexport function npmInstallEnv(baseEnv = process.env) {\n return {\n ...npmEnv(baseEnv),\n NPM_CONFIG_PRODUCTION: \"false\",\n npm_config_production: \"false\",\n };\n}\n\n/** Run a command with inherited stdio. Rejects on non-zero exit. */\nexport function run(cmd, args, options = {}) {\n const { cwd, env, logger } = options;\n\n return new Promise((resolve, reject) => {\n logger?.info?.(`$ ${cmd} ${args.join(\" \")}${cwd ? ` (cwd=${cwd})` : \"\"}`);\n const child = spawn(cmd, args, {\n cwd,\n env: env ?? process.env,\n stdio: \"inherit\",\n });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`${cmd} exited with code ${code}`));\n });\n });\n}\n\n/** Run a shell one-liner via bash -lc. */\nexport function runShell(command, options = {}) {\n return run(\"bash\", [\"-lc\", command], options);\n}\n\n/**\n * Run a command capturing stdout/stderr (for `pm2 jlist`, etc.).\n * Rejects on non-zero exit unless `allowFail` is set.\n *\n * @returns {Promise<{ stdout: string, stderr: string, code: number|null }>}\n */\nexport function runCapture(cmd, args, options = {}) {\n const { cwd, env, logger, allowFail = false } = options;\n\n return new Promise((resolve, reject) => {\n logger?.info?.(`$ ${cmd} ${args.join(\" \")}${cwd ? ` (cwd=${cwd})` : \"\"}`);\n const child = spawn(cmd, args, {\n cwd,\n env: env ?? process.env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => {\n if (code === 0 || allowFail) resolve({ stdout, stderr, code });\n else reject(new Error(`${cmd} exited with code ${code}${stderr ? `: ${stderr.trim()}` : \"\"}`));\n });\n });\n}\n","import { appendFile, mkdir } from \"node:fs/promises\";\n\nexport async function appendDeployLog(deployLogPath, message) {\n await mkdir(deployLogPath.replace(/\\/[^/]+$/, \"\"), { recursive: true });\n const line = `[${new Date().toISOString()}] ${message}\\n`;\n await appendFile(deployLogPath, line);\n}\n","import { access } from \"node:fs/promises\";\n\nimport { servicePaths } from \"./paths.js\";\nimport { run } from \"./run.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function cloneRepo(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n\n if (await pathExists(paths.repo)) {\n throw new Error(`Repo already exists at ${paths.repo} — use git pull instead`);\n }\n if (dryRun) {\n logger.info(`[dryRun] would git clone ${service.repoUrl} ${paths.repo}`);\n return;\n }\n\n await run(\"git\", [\"clone\", service.repoUrl, paths.repo], { logger });\n logger.info(`cloned ${service.repoUrl} → ${paths.repo}`);\n}\n\nexport async function pullRepo(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n\n if (!(await pathExists(paths.repo))) {\n throw new Error(`Repo missing at ${paths.repo} — run provision first`);\n }\n if (dryRun) {\n logger.info(`[dryRun] would git -C ${paths.repo} pull --ff-only`);\n return;\n }\n\n await run(\"git\", [\"-C\", paths.repo, \"pull\", \"--ff-only\"], { logger });\n logger.info(`pulled ${paths.repo}`);\n}\n","import { cp, mkdir, readlink, readdir, stat } from \"node:fs/promises\";\nimport { join, dirname as pathDirname, sep } from \"node:path\";\n\nimport { releaseDir, releaseStamp, servicePaths } from \"./paths.js\";\n\nconst SKIP_TOP = new Set([\"node_modules\", \".git\"]);\n\nfunction shouldCopyEntry(srcPath) {\n const parts = srcPath.split(sep);\n if (parts.some((p) => p === \"node_modules\" || p === \".git\")) return false;\n const top = parts.at(-1);\n if (parts.length === 1 && SKIP_TOP.has(top)) return false;\n return true;\n}\n\n/** Copy the run dir into releases/<stamp>/ (excluding node_modules and .git). */\nexport async function createRelease(service, options = {}) {\n const { stamp = releaseStamp(), dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n const dest = releaseDir(paths.releases, stamp);\n\n if (dryRun) {\n logger.info(`[dryRun] would copy ${paths.repoRun} → ${dest}`);\n return { stamp, path: dest };\n }\n\n await mkdir(paths.releases, { recursive: true });\n await cp(paths.repoRun, dest, {\n recursive: true,\n filter: (src) => shouldCopyEntry(src),\n });\n\n logger.info(`release ${stamp} created at ${dest}`);\n return { stamp, path: dest };\n}\n\n/** Resolve current release path if symlink exists. */\nexport async function readCurrentRelease(paths) {\n try {\n const target = await readlink(paths.current);\n return target.startsWith(\"/\") ? target : join(pathDirname(paths.current), target);\n } catch {\n return null;\n }\n}\n\nexport async function listReleases(paths) {\n let names;\n try {\n names = await readdir(paths.releases);\n } catch {\n return [];\n }\n\n const entries = [];\n for (const name of names) {\n const full = releaseDir(paths.releases, name);\n const s = await stat(full);\n if (s.isDirectory()) entries.push({ name, path: full, mtime: s.mtime });\n }\n\n entries.sort((a, b) => b.name.localeCompare(a.name));\n return entries;\n}\n","import { symlink, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/** Point current → release and link shared/.env inside the release. */\nexport async function activateRelease(releasePath, paths, options = {}) {\n const { dryRun = false, logger = console } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would activate ${releasePath} → ${paths.current}`);\n return;\n }\n\n try {\n await unlink(paths.current);\n } catch {\n // first deploy\n }\n await symlink(releasePath, paths.current);\n\n const envLink = join(releasePath, \".env\");\n try {\n await unlink(envLink);\n } catch {\n // ok\n }\n await symlink(paths.sharedEnv, envLink);\n\n logger.info(`active release: ${releasePath}`);\n}\n","import { createHash } from \"node:crypto\";\nimport { access, cp, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { readCurrentRelease } from \"./release.js\";\nimport { npmInstallEnv, run } from \"./run.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function lockfileHash(releasePath) {\n const lockPath = join(releasePath, \"package-lock.json\");\n try {\n const buf = await readFile(lockPath);\n return createHash(\"sha256\").update(buf).digest(\"hex\");\n } catch {\n return null; // no lockfile → can't fingerprint; always (re)install\n }\n}\n\nasync function readHashFile(path) {\n try {\n return (await readFile(path, \"utf8\")).trim();\n } catch {\n return null;\n }\n}\n\nasync function npmInstall(releasePath, hasLock, logger) {\n await rm(join(releasePath, \"node_modules\"), { recursive: true, force: true });\n const cmd = hasLock ? [\"ci\", \"--include=dev\"] : [\"install\", \"--include=dev\", \"--no-audit\", \"--no-fund\"];\n logger.info(`running npm ${cmd.join(\" \")} in ${releasePath}`);\n await run(\"npm\", cmd, { cwd: releasePath, logger, env: npmInstallEnv() });\n}\n\n/**\n * Install deps in the release. If the lockfile is unchanged from the active\n * release and its node_modules exists, copy it (fast path); otherwise npm ci.\n */\nexport async function installDeps(service, releasePath, paths, options = {}) {\n const { dryRun = false, logger = console } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would install deps in ${releasePath}`);\n return { hash: null, copied: false };\n }\n\n const hash = await lockfileHash(releasePath);\n const hasLock = hash !== null;\n const hashMarker = join(releasePath, \".deploy-package-lock.sha256\");\n\n const currentPath = await readCurrentRelease(paths);\n let copied = false;\n\n if (hasLock && currentPath && (await pathExists(join(currentPath, \"node_modules\")))) {\n const currentHash = await readHashFile(join(currentPath, \".deploy-package-lock.sha256\"));\n if (currentHash === hash) {\n logger.info(\"lockfile unchanged — copying node_modules from current release\");\n await cp(join(currentPath, \"node_modules\"), join(releasePath, \"node_modules\"), {\n recursive: true,\n force: true,\n });\n copied = true;\n }\n }\n\n if (!copied) {\n await npmInstall(releasePath, hasLock, logger);\n }\n\n if (hasLock) {\n await writeFile(hashMarker, `${hash}\\n`, \"utf8\");\n await mkdir(paths.shared, { recursive: true });\n await writeFile(paths.lockHashFile, `${hash}\\n`, \"utf8\");\n }\n\n return { hash, copied };\n}\n","import { symlink, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { npmInstallEnv, runShell } from \"./run.js\";\n\n/**\n * Symlink shared/.env into the release and run the service's testCommand (a shell\n * one-liner, e.g. \"npm run test:ci\"). No-op when the service defines no command.\n */\nexport async function runReleaseTests(service, releasePath, paths, options = {}) {\n const { dryRun = false, logger = console } = options;\n\n if (!service.testCommand) {\n logger.info(\"no testCommand configured — skipping tests\");\n return;\n }\n\n if (dryRun) {\n logger.info(`[dryRun] would run \"${service.testCommand}\" in ${releasePath}`);\n return;\n }\n\n const envLink = join(releasePath, \".env\");\n try {\n await unlink(envLink);\n } catch {\n // no existing link\n }\n await symlink(paths.sharedEnv, envLink);\n\n logger.info(`running \"${service.testCommand}\" in ${releasePath}`);\n await runShell(service.testCommand, { cwd: releasePath, logger, env: npmInstallEnv() });\n}\n","import { rm, readlink } from \"node:fs/promises\";\n\nimport { listReleases } from \"./release.js\";\n\n/**\n * Remove old releases, keeping the newest N (always keeps the active one).\n *\n * @param {object} service\n * @param {object} paths\n * @param {{ dryRun?: boolean, logger?: object, protect?: string[] }} [options]\n * `protect` — release stamp names that must not be deleted (e.g. still-running\n * previous release during a rolling deploy).\n */\nexport async function pruneReleases(service, paths, options = {}) {\n const { dryRun = false, logger = console, protect = [] } = options;\n const keep = service.keepReleases ?? 3;\n const releases = await listReleases(paths);\n\n let activeName = null;\n try {\n const target = await readlink(paths.current);\n activeName = target.split(\"/\").pop();\n } catch {\n // no current\n }\n\n const keepSet = new Set();\n for (const rel of releases) {\n if (keepSet.size < keep) keepSet.add(rel.name);\n }\n if (activeName) keepSet.add(activeName);\n for (const name of protect) {\n if (name) keepSet.add(name);\n }\n\n const toRemove = releases.filter((rel) => !keepSet.has(rel.name));\n for (const rel of toRemove) {\n if (dryRun) {\n logger.info(`[dryRun] would rm -rf ${rel.path}`);\n } else {\n await rm(rel.path, { recursive: true, force: true });\n logger.info(`pruned ${rel.path}`);\n }\n }\n\n return { removed: toRemove.map((r) => r.name) };\n}\n","import { runShell, runCapture } from \"./run.js\";\n\nfunction sleep(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** True if `pid` still exists (same host). */\nexport function isPidAlive(pid) {\n const n = Number(pid);\n if (!Number.isFinite(n) || n <= 0) return false;\n try {\n process.kill(n, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Parse `pm2 jlist` and return the process entry for `appName`, or null.\n *\n * @param {string} appName\n * @param {{ logger?: { info?: Function, warn?: Function } }} [options]\n * @returns {Promise<object|null>}\n */\nexport async function getPm2Process(appName, options = {}) {\n const { logger } = options;\n const { stdout } = await runCapture(\"bash\", [\"-lc\", \"pm2 jlist\"], { logger });\n let list;\n try {\n list = JSON.parse(stdout || \"[]\");\n } catch (err) {\n throw new Error(`pm2 jlist: invalid JSON (${err?.message ?? err})`);\n }\n if (!Array.isArray(list)) return null;\n return list.find((p) => p?.name === appName) ?? null;\n}\n\n/**\n * Poll pm2 until `predicate(proc)` is true (proc may be null if missing), or timeout.\n *\n * @param {string} appName\n * @param {(proc: object|null) => boolean} predicate\n * @param {{ timeoutMs?: number, pollMs?: number, dryRun?: boolean, logger?: object, label?: string }} [options]\n */\nexport async function waitPm2(appName, predicate, options = {}) {\n const {\n timeoutMs = 65_000,\n pollMs = 500,\n dryRun = false,\n logger = console,\n label = \"condition\",\n } = options;\n\n if (dryRun) {\n logger.info?.(`[dryRun] would wait pm2 ${appName} for ${label}`);\n return null;\n }\n\n const deadline = Date.now() + timeoutMs;\n let lastStatus = \"(unknown)\";\n while (Date.now() < deadline) {\n const proc = await getPm2Process(appName, { logger });\n lastStatus = proc?.pm2_env?.status ?? \"(missing)\";\n if (predicate(proc)) return proc;\n await sleep(pollMs);\n }\n throw new Error(\n `pm2 wait timed out after ${timeoutMs}ms for ${appName} (${label}); last status=${lastStatus}`\n );\n}\n\n/**\n * True when pm2 reports the app online on a *new* pid (old process fully gone).\n * Avoids treating a not-yet-dead pre-reload process as a successful restart.\n */\nexport function isFreshOnline(proc, oldPid) {\n if (proc?.pm2_env?.status !== \"online\") return false;\n const pid = Number(proc.pid);\n if (!Number.isFinite(pid) || pid <= 0) return false;\n if (oldPid != null) {\n if (pid === oldPid) return false;\n if (isPidAlive(oldPid)) return false;\n }\n return true;\n}\n\n/** `pm2 startOrReload <ecosystem> --update-env` — rolling restart (default deploy path). */\nexport async function reloadPm2(paths, options = {}) {\n const { dryRun = false, logger = console, appName = null, waitTimeoutMs = 65_000 } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would pm2 startOrReload ${paths.ecosystem} --update-env`);\n return;\n }\n\n let oldPid = null;\n if (appName) {\n const before = await getPm2Process(appName, { logger });\n const n = Number(before?.pid);\n if (Number.isFinite(n) && n > 0 && before?.pm2_env?.status === \"online\") {\n oldPid = n;\n logger.info(`pm2 ${appName}: reloading (old pid=${oldPid})`);\n }\n }\n\n await runShell(`pm2 startOrReload \"${paths.ecosystem}\" --update-env`, { logger });\n logger.info(\"pm2 reloaded\");\n\n if (appName) {\n const proc = await waitPm2(\n appName,\n (p) => isFreshOnline(p, oldPid),\n {\n timeoutMs: waitTimeoutMs,\n logger,\n label: oldPid\n ? `online on new pid (old ${oldPid} gone)`\n : \"online after reload\",\n }\n );\n logger.info(`pm2 ${appName} online pid=${proc?.pid ?? \"?\"}`);\n }\n}\n\n/** `pm2 stop <app>` and wait until status is stopped (or process missing). */\nexport async function stopPm2(appName, options = {}) {\n const { dryRun = false, logger = console, waitTimeoutMs = 65_000 } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would pm2 stop ${appName}`);\n return;\n }\n\n const before = await getPm2Process(appName, { logger });\n if (!before) {\n logger.info(`pm2 ${appName}: not present (already stopped)`);\n return;\n }\n if (before.pm2_env?.status === \"stopped\") {\n logger.info(`pm2 ${appName}: already stopped`);\n return;\n }\n\n const oldPid = Number(before.pid);\n await runShell(`pm2 stop \"${appName}\"`, { logger });\n await waitPm2(\n appName,\n (proc) => {\n if (proc && proc.pm2_env?.status !== \"stopped\") return false;\n if (Number.isFinite(oldPid) && oldPid > 0 && isPidAlive(oldPid)) return false;\n return true;\n },\n { timeoutMs: waitTimeoutMs, logger, label: \"stopped\" }\n );\n logger.info(`pm2 ${appName} stopped`);\n}\n\n/** `pm2 start <ecosystem> --update-env` and wait until online. */\nexport async function startPm2(paths, options = {}) {\n const { dryRun = false, logger = console, appName = null, waitTimeoutMs = 65_000 } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would pm2 start ${paths.ecosystem} --update-env`);\n return;\n }\n\n await runShell(`pm2 start \"${paths.ecosystem}\" --update-env`, { logger });\n logger.info(\"pm2 started\");\n\n if (appName) {\n const proc = await waitPm2(\n appName,\n (p) => isFreshOnline(p, null),\n { timeoutMs: waitTimeoutMs, logger, label: \"online after start\" }\n );\n logger.info(`pm2 ${appName} online pid=${proc?.pid ?? \"?\"}`);\n }\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { runShell } from \"./run.js\";\n\nasync function hasTlsCert(certPath) {\n try {\n await runShell(`sudo test -f '${certPath}'`, {});\n return true;\n } catch {\n return false;\n }\n}\n\nfunction proxyBlock(port) {\n return ` location / {\n proxy_pass http://127.0.0.1:${port};\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_read_timeout 120s;\n }`;\n}\n\nfunction buildNginxConfig(service) {\n const { nginx, pm2 } = service;\n const certDir = `/etc/letsencrypt/live/${nginx.fqdn}`;\n\n return `# ${nginx.siteName} — managed by cli-toolkit deploy (proxy mode)\n# ${nginx.fqdn} → 127.0.0.1:${pm2.port}\n\nserver {\n listen 80;\n listen [::]:80;\n server_name ${nginx.fqdn};\n location / { return 301 https://$host$request_uri; }\n}\n\nserver {\n listen 443 ssl;\n listen [::]:443 ssl;\n server_name ${nginx.fqdn};\n\n ssl_certificate ${certDir}/fullchain.pem;\n ssl_certificate_key ${certDir}/privkey.pem;\n include /etc/letsencrypt/options-ssl-nginx.conf;\n ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;\n\n client_max_body_size 25m;\n\n${proxyBlock(pm2.port)}\n}\n`;\n}\n\nfunction buildNginxConfigHttpOnly(service) {\n const { nginx, pm2 } = service;\n return `# ${nginx.siteName} — managed by cli-toolkit deploy (HTTP proxy, no TLS cert yet)\n\nserver {\n listen 80;\n listen [::]:80;\n server_name ${nginx.fqdn};\n\n client_max_body_size 25m;\n\n${proxyBlock(pm2.port)}\n}\n`;\n}\n\n/** Replace the stub nginx site with a reverse-proxy to the app. */\nexport async function enableNginxUpstream(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const { nginx } = service;\n if (!nginx) {\n logger.info(\"no nginx config on service — skipping nginx step\");\n return { skipped: true };\n }\n\n const siteAvailable = `/etc/nginx/sites-available/${nginx.siteName}`;\n const siteEnabled = `/etc/nginx/sites-enabled/${nginx.siteName}`;\n const cert = `/etc/letsencrypt/live/${nginx.fqdn}/fullchain.pem`;\n\n if (dryRun) {\n logger.info(`[dryRun] would write ${siteAvailable} (proxy → 127.0.0.1:${service.pm2.port}) and reload nginx`);\n return { hasCert: null };\n }\n\n const hasCert = await hasTlsCert(cert);\n const config = hasCert ? buildNginxConfig(service) : buildNginxConfigHttpOnly(service);\n\n const tmp = join(tmpdir(), `${service.name}-nginx.conf`);\n await writeFile(tmp, config);\n await runShell(\n `sudo cp '${tmp}' '${siteAvailable}' && sudo ln -sf '${siteAvailable}' '${siteEnabled}' && sudo nginx -t && sudo systemctl reload nginx`,\n { logger },\n );\n\n logger.info(`nginx upstream enabled for ${nginx.fqdn} → 127.0.0.1:${service.pm2.port} (tls=${hasCert})`);\n return { hasCert };\n}\n","import { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\n\nimport { servicePaths } from \"./paths.js\";\n\n/**\n * Drop .env lines matching any of the service's envScrubPatterns (regex source\n * strings). Useful to strip empty keys that would crash strict param validation.\n */\nexport function scrubEnvContent(content, patterns = []) {\n const regexes = patterns.map((p) => (p instanceof RegExp ? p : new RegExp(p)));\n if (regexes.length === 0) return content;\n return content\n .split(\"\\n\")\n .filter((line) => !regexes.some((re) => re.test(line.trim())))\n .join(\"\\n\");\n}\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Copy <repoRun>/.env → shared/.env (with optional legacy fallback + scrubbing). */\nexport async function syncEnv(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n const patterns = service.envScrubPatterns ?? [];\n\n if (dryRun) {\n logger.info(`[dryRun] would sync ${paths.repoEnv} → ${paths.sharedEnv}`);\n return { source: paths.repoEnv, dest: paths.sharedEnv };\n }\n\n let source = paths.repoEnv;\n if (!(await pathExists(source)) && service.legacyRepoEnv && (await pathExists(service.legacyRepoEnv))) {\n logger.info(`using legacy env: ${service.legacyRepoEnv}`);\n source = service.legacyRepoEnv;\n }\n\n await mkdir(paths.shared, { recursive: true });\n\n if (!(await pathExists(source))) {\n if (service.requireEnv) {\n throw new Error(\n `No .env found at ${paths.repoEnv}` +\n (service.legacyRepoEnv ? ` or ${service.legacyRepoEnv}` : \"\") +\n \" — place .env on the host or run the remote deploy from a laptop with a local .env (auto-scp)\",\n );\n }\n // Optional env: ensure shared/.env exists so the per-release symlink is valid.\n if (!(await pathExists(paths.sharedEnv))) {\n await writeFile(paths.sharedEnv, \"\", { mode: 0o600 });\n }\n logger.warn(`no .env found (source ${source}) — using empty ${paths.sharedEnv}`);\n return { source: null, dest: paths.sharedEnv };\n }\n\n const raw = await readFile(source, \"utf8\");\n await writeFile(paths.sharedEnv, scrubEnvContent(raw, patterns), { mode: 0o600 });\n logger.info(`synced ${source} → ${paths.sharedEnv}`);\n return { source, dest: paths.sharedEnv };\n}\n","import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nimport { readCurrentRelease } from \"./release.js\";\nimport { servicePaths } from \"./paths.js\";\n\nconst execFileAsync = promisify(execFile);\n\n/** Bump semver patch (0.1.0 → 0.1.1). */\nexport function bumpPatchVersion(version) {\n const parts = String(version).trim().split(\".\");\n const major = Number.parseInt(parts[0], 10) || 0;\n const minor = Number.parseInt(parts[1], 10) || 0;\n const patch = Number.parseInt(parts[2], 10) || 0;\n return `${major}.${minor}.${patch + 1}`;\n}\n\nasync function readJson(path) {\n try {\n return JSON.parse(await readFile(path, \"utf8\"));\n } catch {\n return null;\n }\n}\n\nasync function gitShortCommit(repoPath) {\n try {\n const { stdout } = await execFileAsync(\"git\", [\"-C\", repoPath, \"rev-parse\", \"--short\", \"HEAD\"], {\n encoding: \"utf8\",\n });\n return stdout.trim() || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Next semver: bump patch from the **currently active** release's build-info,\n * else seed from package.json. Each release carries its own build-info.json so a\n * rollback restores that release's label.\n */\nexport async function resolveNextVersion(service, paths, pkgPath) {\n const pkg = (await readJson(pkgPath)) ?? {};\n const baseVersion = pkg.version || \"0.1.0\";\n\n const currentPath = await readCurrentRelease(paths);\n if (currentPath) {\n const active = await readJson(join(currentPath, service.buildInfoPath));\n if (active?.version) return bumpPatchVersion(active.version);\n }\n return baseVersion;\n}\n\n/** Read build-info from a release directory (for rollback logs / status). */\nexport async function readReleaseBuildInfo(service, releasePath) {\n return readJson(join(releasePath, service.buildInfoPath));\n}\n\n/** Write build-info.json into the new release directory. */\nexport async function writeReleaseBuildInfo(service, releasePath, { stamp, dryRun = false, logger = console }) {\n const paths = servicePaths(service);\n const pkgPath = join(paths.repoRun, \"package.json\");\n\n const version = await resolveNextVersion(service, paths, pkgPath);\n const gitCommit = await gitShortCommit(paths.repo);\n\n const buildInfo = {\n version,\n release: stamp,\n deployedAt: new Date().toISOString(),\n gitCommit,\n service: service.name,\n };\n\n const dest = join(releasePath, service.buildInfoPath);\n\n if (dryRun) {\n logger.info(`[dryRun] would write ${dest} (${JSON.stringify(buildInfo)})`);\n return buildInfo;\n }\n\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, `${JSON.stringify(buildInfo, null, 2)}\\n`, { mode: 0o644 });\n\n const gitSuffix = gitCommit ? ` git=${gitCommit}` : \"\";\n logger.info(`build info: v${version} release=${stamp}${gitSuffix}`);\n return buildInfo;\n}\n","import { access, appendFile, mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { servicePaths } from \"./paths.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Fail fast with a useful message when not on a provisioned host. */\nasync function requireDeployRoot(parentDir, serviceRoot) {\n if (await pathExists(parentDir)) return;\n throw new Error(\n `Cannot create ${serviceRoot}: parent directory ${parentDir} does not exist.\\n` +\n `This is meant to run on the target host (where ${parentDir} exists). ` +\n `For a local dry run use --appsRoot=/tmp/<service>.`,\n );\n}\n\nfunction resolveKillTimeoutMs(pm2) {\n const explicit = Number(pm2?.killTimeout);\n if (Number.isFinite(explicit) && explicit > 0) return Math.floor(explicit);\n const stopSec = Number(pm2?.stopAllowance);\n if (Number.isFinite(stopSec) && stopSec > 0) return Math.floor(stopSec * 1000) + 5000;\n return 65_000;\n}\n\n/** Append `--stopAllowance=<sec>` to pm2 args when manifest sets pm2.stopAllowance. */\nfunction resolvePm2Args(pm2) {\n const base = String(pm2?.args ?? \"\").trim();\n const stopSec = Number(pm2?.stopAllowance);\n if (!Number.isFinite(stopSec) || stopSec <= 0) return base;\n if (/(?:^|\\s)--stopAllowance=/.test(base)) return base;\n return `${base}${base ? \" \" : \"\"}--stopAllowance=${Math.floor(stopSec)}`.trim();\n}\n\nfunction buildEcosystemConfig(service, paths) {\n const { pm2 } = service;\n const outLog = join(paths.logs, `${pm2.appName}.out.log`);\n const errLog = join(paths.logs, `${pm2.appName}.err.log`);\n const killTimeoutMs = resolveKillTimeoutMs(pm2);\n const args = resolvePm2Args(pm2);\n\n return `/**\n * pm2 ecosystem for ${service.name} — written by cli-toolkit deploy from the\n * service manifest (init/deploy). Do not hand-edit; change pm2.* in the\n * manifest and redeploy. cwd → \\`current\\` symlink.\n */\nmodule.exports = {\n apps: [\n {\n name: \"${pm2.appName}\",\n script: \"${pm2.script}\",\n cwd: \"${paths.current}\",\n args: ${JSON.stringify(args)},\n instances: 1,\n exec_mode: \"fork\",\n autorestart: true,\n min_uptime: \"10s\",\n max_restarts: 10,\n restart_delay: 2000,\n max_memory_restart: \"1500M\",\n // Grace window after SIGINT/SIGTERM before SIGKILL (ms). Align with --stopAllowance.\n kill_timeout: ${killTimeoutMs},\n out_file: \"${outLog}\",\n error_file: \"${errLog}\",\n merge_logs: true,\n time: true,\n env: {\n // cli-toolkit Args: ENV selects env-suffixed defaults (DB_NAME_PRODUCTION, BUCKET_PRODUCTION, …)\n ENV: \"production\",\n NODE_ENV: \"production\",\n },\n },\n ],\n};\n`;\n}\n\n/** Create <appsRoot>/{releases,shared,logs} and sync shared/ecosystem.config.cjs from the manifest. */\nexport async function initServiceStructure(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n const dirs = [paths.releases, paths.shared, paths.logs];\n const created = [];\n const skipped = [];\n\n if (!dryRun) {\n await requireDeployRoot(dirname(paths.root), paths.root);\n }\n\n for (const dir of dirs) {\n if (await pathExists(dir)) {\n skipped.push(dir);\n continue;\n }\n if (!dryRun) await mkdir(dir, { recursive: true });\n created.push(dir);\n }\n\n const ecosystemExisted = await pathExists(paths.ecosystem);\n if (!dryRun) {\n await writeFile(paths.ecosystem, buildEcosystemConfig(service, paths), { mode: 0o644 });\n }\n if (ecosystemExisted) {\n logger.info(`synced ${paths.ecosystem} from manifest`);\n } else {\n created.push(paths.ecosystem);\n logger.info(`seeded ${paths.ecosystem}`);\n }\n\n const line = `[${new Date().toISOString()}] init-structure service=${service.name} dryRun=${dryRun} created=${created.length} skipped=${skipped.length} ecosystem=synced\\n`;\n if (!dryRun) {\n await mkdir(paths.logs, { recursive: true });\n await appendFile(paths.deployLog, line);\n }\n\n logger.info(`service=${service.name} appsRoot=${paths.root}`);\n logger.info(`created: ${created.length ? created.join(\", \") : \"(none)\"}`);\n logger.info(`already present: ${skipped.length ? skipped.join(\", \") : \"(none)\"}`);\n\n return { paths, created, skipped, ecosystemCreated: !ecosystemExisted, ecosystemSynced: true };\n}\n","import { execSync } from \"node:child_process\";\nimport { access, chmod, copyFile, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, join } from \"node:path\";\n\nimport { runShell } from \"./run.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction expandHome(path) {\n return path.startsWith(\"~/\") ? join(homedir(), path.slice(2)) : path;\n}\n\nasync function ensurePm2Startup(options = {}) {\n const { user = \"ubuntu\", dryRun = false, logger = console } = options;\n\n if (dryRun) {\n logger.info(\"[dryRun] would configure pm2 startup systemd\");\n return;\n }\n\n try {\n execSync(\"pm2 ping\", { stdio: \"ignore\" });\n } catch {\n // pm2 daemon may not be up yet\n }\n\n try {\n const out = execSync(`pm2 startup systemd -u ${user} --hp /home/${user}`, { encoding: \"utf8\" });\n const sudoLine = out.split(\"\\n\").find((l) => l.trim().startsWith(\"sudo\"));\n if (sudoLine) {\n execSync(sudoLine.trim(), { stdio: \"inherit\" });\n logger.info(\"pm2 startup systemd configured\");\n }\n } catch (err) {\n logger.warn(`pm2 startup skipped or already configured: ${err.message}`);\n }\n}\n\nasync function installDeployKey(deployKeyPath, options = {}) {\n const { dryRun = false, logger = console } = options;\n const keyPath = expandHome(deployKeyPath);\n\n if (!(await pathExists(keyPath))) {\n logger.warn(`deploy key not found at ${keyPath} — skipping git ssh setup`);\n return;\n }\n\n const keyBase = basename(keyPath);\n const sshDir = join(homedir(), \".ssh\");\n const destKey = join(sshDir, keyBase);\n const configPath = join(sshDir, \"config\");\n const block = `\nHost github.com\n HostName github.com\n User git\n IdentityFile ${destKey}\n IdentitiesOnly yes\n`;\n\n if (dryRun) {\n logger.info(`[dryRun] would install deploy key ${keyPath} → ${destKey}`);\n return;\n }\n\n await mkdir(sshDir, { recursive: true, mode: 0o700 });\n await copyFile(keyPath, destKey);\n await chmod(destKey, 0o600);\n\n let config = \"\";\n if (await pathExists(configPath)) config = await readFile(configPath, \"utf8\");\n if (!config.includes(\"Host github.com\")) {\n await writeFile(configPath, `${config.trimEnd()}\\n${block}\\n`, { mode: 0o600 });\n logger.info(\"updated ~/.ssh/config for github.com\");\n }\n logger.info(`deploy key installed at ${destKey}`);\n}\n\nasync function installLogrotate(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const conf = `/etc/logrotate.d/${service.name}`;\n const body = `${service.appsRoot}/logs/*.log {\n daily\n rotate 14\n compress\n delaycompress\n missingok\n notifempty\n copytruncate\n}\n`;\n\n if (dryRun) {\n logger.info(`[dryRun] would write ${conf}`);\n return;\n }\n\n const tmp = join(\"/tmp\", `${service.name}-logrotate.conf`);\n await writeFile(tmp, body);\n await runShell(`sudo cp '${tmp}' '${conf}'`, { logger });\n logger.info(`logrotate config written: ${conf}`);\n}\n\nconst OPERATOR_SHELL_BEGIN = \"# >>> mlsfarm-operator >>>\";\nconst OPERATOR_SHELL_END = \"# <<< mlsfarm-operator <<<\";\n\n/**\n * Idempotent ~/.bashrc block: ENV=production + cd into /apps/<service>/current\n * on interactive login. Safe to re-run; replaces any prior managed block.\n */\nexport async function installOperatorShell(service, options = {}) {\n const { user = \"ubuntu\", dryRun = false, logger = console } = options;\n const appsRoot = service.appsRoot;\n if (!appsRoot) {\n logger.warn(\"installOperatorShell: service.appsRoot missing — skipped\");\n return;\n }\n\n const home = user === \"ubuntu\" || user === process.env.USER\n ? join(homedir(), \".bashrc\")\n : `/home/${user}/.bashrc`;\n const currentDir = join(appsRoot, \"current\");\n const block = [\n OPERATOR_SHELL_BEGIN,\n \"# Managed by cli-toolkit deploy bootstrap — do not edit by hand.\",\n 'export ENV=production',\n `if [ -d \"${currentDir}\" ]; then`,\n ` cd \"${currentDir}\" || true`,\n \"fi\",\n OPERATOR_SHELL_END,\n \"\",\n ].join(\"\\n\");\n\n if (dryRun) {\n logger.info(`[dryRun] would update ${home} (ENV=production, cd ${currentDir})`);\n return;\n }\n\n let existing = \"\";\n if (await pathExists(home)) {\n existing = await readFile(home, \"utf8\");\n }\n\n const begin = existing.indexOf(OPERATOR_SHELL_BEGIN);\n const end = existing.indexOf(OPERATOR_SHELL_END);\n let next;\n if (begin !== -1 && end !== -1 && end > begin) {\n const afterEnd = end + OPERATOR_SHELL_END.length;\n const rest = existing.slice(afterEnd).replace(/^\\n/, \"\");\n next = `${existing.slice(0, begin).replace(/\\s*$/, \"\\n\")}${block}${rest}`;\n } else {\n next = `${existing.replace(/\\s*$/, \"\\n\")}\\n${block}`;\n }\n\n await writeFile(home, next, { mode: 0o644 });\n logger.info(`operator shell: ${home} → ENV=production, cd ${currentDir}`);\n}\n\n/** One-time host prep: pm2 systemd, optional git deploy key, logrotate, operator shell. */\nexport async function bootstrapHost(service, options = {}) {\n const { deployKey = service.deployKey, user = \"ubuntu\", dryRun = false, logger = console } = options;\n\n await ensurePm2Startup({ user, dryRun, logger });\n if (deployKey) await installDeployKey(deployKey, { dryRun, logger });\n await installLogrotate(service, { dryRun, logger });\n await installOperatorShell(service, { user, dryRun, logger });\n logger.info(\"bootstrap-host complete\");\n}\n","import { readlink } from \"node:fs/promises\";\n\nimport { initServiceStructure } from \"./init-structure.js\";\nimport { appendDeployLog } from \"./log.js\";\nimport { pullRepo } from \"./git.js\";\nimport { createRelease } from \"./release.js\";\nimport { installDeps } from \"./deps.js\";\nimport { runReleaseTests } from \"./test.js\";\nimport { activateRelease } from \"./activate.js\";\nimport { pruneReleases } from \"./prune.js\";\nimport { reloadPm2, stopPm2, startPm2 } from \"./pm2.js\";\nimport { enableNginxUpstream } from \"./nginx.js\";\nimport { syncEnv } from \"./sync-env.js\";\nimport { writeReleaseBuildInfo } from \"./build-info.js\";\nimport { servicePaths } from \"./paths.js\";\n\n/**\n * Resolve pm2 kill_timeout (ms). Prefer manifest `pm2.killTimeout`, else\n * stopAllowance seconds × 1000 + 5s buffer, else 65s.\n */\nfunction resolveKillTimeoutMs(service) {\n const explicit = Number(service.pm2?.killTimeout);\n if (Number.isFinite(explicit) && explicit > 0) return Math.floor(explicit);\n const stopSec = Number(service.pm2?.stopAllowance);\n if (Number.isFinite(stopSec) && stopSec > 0) return Math.floor(stopSec * 1000) + 5000;\n return 65_000;\n}\n\n/**\n * Full deploy: pull → env → release → deps → test → activate → pm2 → prune → nginx.\n *\n * Default (**rolling**): keep the process running while the new release is built and\n * `current` is flipped; then `pm2 startOrReload` (SIGTERM + kill_timeout) so the\n * runner drains and pm2 brings it back on the new code. Previous release is not\n * pruned until after that restart.\n *\n * **`--stopFirst`**: `pm2 stop` + wait → activate/prune → `pm2 start`. Use when a\n * runner is wedged and will not honor graceful stop.\n */\nexport async function deployService(service, options = {}) {\n const {\n dryRun = false,\n skipPull = false,\n skipTests = false,\n skipNginx = false,\n stopFirst = false,\n logger = console,\n } = options;\n\n const paths = servicePaths(service);\n const appName = service.pm2.appName;\n const killTimeoutMs = resolveKillTimeoutMs(service);\n const waitTimeoutMs = killTimeoutMs + 10_000;\n\n await initServiceStructure(service, { dryRun, logger });\n\n if (!skipPull) await pullRepo(service, { dryRun, logger });\n\n await syncEnv(service, { dryRun, logger });\n\n const { stamp, path: releasePath } = await createRelease(service, { dryRun, logger });\n await writeReleaseBuildInfo(service, releasePath, { stamp, dryRun, logger });\n await installDeps(service, releasePath, paths, { dryRun, logger });\n\n if (!skipTests) await runReleaseTests(service, releasePath, paths, { dryRun, logger });\n\n let previousReleaseName = null;\n try {\n const prev = await readlink(paths.current);\n previousReleaseName = prev.split(\"/\").pop() || null;\n } catch {\n // first deploy\n }\n\n if (stopFirst) {\n logger.info(`deploy mode=stopFirst app=${appName} killTimeoutMs=${killTimeoutMs}`);\n await stopPm2(appName, { dryRun, logger, waitTimeoutMs });\n await activateRelease(releasePath, paths, { dryRun, logger });\n await pruneReleases(service, paths, { dryRun, logger });\n await startPm2(paths, { dryRun, logger, appName, waitTimeoutMs });\n } else {\n logger.info(\n `deploy mode=rolling app=${appName} killTimeoutMs=${killTimeoutMs}` +\n (previousReleaseName ? ` previous=${previousReleaseName}` : \"\")\n );\n await activateRelease(releasePath, paths, { dryRun, logger });\n // Restart into new `current` before pruning the previous tree (still in use by the old process).\n await reloadPm2(paths, { dryRun, logger, appName, waitTimeoutMs });\n await pruneReleases(service, paths, { dryRun, logger });\n }\n\n if (!skipNginx) await enableNginxUpstream(service, { dryRun, logger });\n\n const summary = `deploy complete stamp=${stamp} mode=${stopFirst ? \"stopFirst\" : \"rolling\"} dryRun=${dryRun}`;\n logger.info(summary);\n if (!dryRun) await appendDeployLog(paths.deployLog, summary);\n\n return { stamp, releasePath, mode: stopFirst ? \"stopFirst\" : \"rolling\" };\n}\n","import { access } from \"node:fs/promises\";\n\nimport { bootstrapHost } from \"./bootstrap-host.js\";\nimport { initServiceStructure } from \"./init-structure.js\";\nimport { cloneRepo, pullRepo } from \"./git.js\";\nimport { deployService } from \"./deploy-service.js\";\nimport { syncEnv } from \"./sync-env.js\";\nimport { servicePaths } from \"./paths.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/** First-time setup: optional bootstrap, init dirs, clone/pull, sync .env, first deploy. */\nexport async function provisionService(service, options = {}) {\n const {\n dryRun = false,\n deploy = true,\n skipBootstrap = true,\n deployKey,\n logger = console,\n } = options;\n\n if (!skipBootstrap) {\n await bootstrapHost(service, { deployKey, dryRun, logger });\n }\n\n await initServiceStructure(service, { dryRun, logger });\n const paths = servicePaths(service);\n\n if (await pathExists(paths.repo)) {\n logger.info(`repo exists at ${paths.repo} — pulling`);\n await pullRepo(service, { dryRun, logger });\n } else {\n await cloneRepo(service, { dryRun, logger });\n }\n\n await syncEnv(service, { dryRun, logger });\n\n if (deploy) {\n await deployService(service, { dryRun, logger, skipPull: true });\n } else {\n logger.info(\"provision complete (deploy skipped)\");\n }\n}\n","import { readlink } from \"node:fs/promises\";\n\nimport { activateRelease } from \"./activate.js\";\nimport { readReleaseBuildInfo } from \"./build-info.js\";\nimport { appendDeployLog } from \"./log.js\";\nimport { listReleases } from \"./release.js\";\nimport { reloadPm2 } from \"./pm2.js\";\nimport { servicePaths } from \"./paths.js\";\n\n/** Flip current to a named release or the previous one. */\nexport async function rollbackService(service, options = {}) {\n const { release: targetName, dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n const releases = await listReleases(paths);\n\n if (releases.length === 0) throw new Error(\"No releases to roll back to\");\n\n let activeName = null;\n try {\n const target = await readlink(paths.current);\n activeName = target.split(\"/\").pop();\n } catch {\n throw new Error(\"No active release (current symlink missing)\");\n }\n\n let rollbackTarget;\n if (targetName) {\n rollbackTarget = releases.find((r) => r.name === targetName);\n if (!rollbackTarget) throw new Error(`Release not found: ${targetName}`);\n } else {\n rollbackTarget = releases.find((r) => r.name !== activeName);\n if (!rollbackTarget) throw new Error(\"No previous release to roll back to\");\n }\n\n if (rollbackTarget.name === activeName) throw new Error(`Already on release ${activeName}`);\n\n logger.info(`rollback ${activeName} → ${rollbackTarget.name}`);\n const buildInfo = await readReleaseBuildInfo(service, rollbackTarget.path);\n if (buildInfo?.version) {\n logger.info(`rollback target: v${buildInfo.version} release=${buildInfo.release ?? rollbackTarget.name}`);\n }\n await activateRelease(rollbackTarget.path, paths, { dryRun, logger });\n await reloadPm2(paths, { dryRun, logger });\n\n const summary = `rollback ${activeName} → ${rollbackTarget.name} dryRun=${dryRun}`;\n if (!dryRun) await appendDeployLog(paths.deployLog, summary);\n\n return { from: activeName, to: rollbackTarget.name, path: rollbackTarget.path };\n}\n","import { access, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport { spawn } from \"node:child_process\";\n\nimport { scrubEnvContent } from \"./sync-env.js\";\nimport { servicePaths } from \"./paths.js\";\n\n/** Path (relative to the run dir on the server) of the toolkit deploy CLI. */\nexport const REMOTE_CLI_REL = \"node_modules/@nmakarov/cli-toolkit/scripts/deploy/cli.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction expandHome(path) {\n return path.startsWith(\"~/\") ? join(homedir(), path.slice(2)) : path;\n}\n\nfunction resolveLocalEnvPath(envFile) {\n if (envFile) {\n const expanded = expandHome(envFile);\n return expanded.startsWith(\"/\") ? expanded : join(process.cwd(), expanded);\n }\n return join(process.cwd(), \".env\");\n}\n\n/** Parse the ssh host out of a git url (git@host:..., ssh://git@host/...). null for https. */\nexport function parseGitHost(repoUrl) {\n const u = String(repoUrl ?? \"\");\n let m = u.match(/^[^@]+@([^:]+):/); // git@github.com:org/repo.git\n if (m) return m[1];\n m = u.match(/^ssh:\\/\\/[^@]+@([^/:]+)/); // ssh://git@github.com/org/repo.git\n if (m) return m[1];\n return null;\n}\n\nexport function shellQuote(value) {\n return `'${String(value).replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport function sshRun(host, remoteCommand, options = {}) {\n const { logger } = options;\n return new Promise((resolve, reject) => {\n logger?.info?.(`ssh ${host} ${remoteCommand.slice(0, 120)}${remoteCommand.length > 120 ? \"…\" : \"\"}`);\n const child = spawn(\"ssh\", [host, remoteCommand], { stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => (code === 0 ? resolve() : reject(new Error(`ssh ${host} exited with code ${code}`))));\n });\n}\n\nfunction scp(localPath, remoteSpec) {\n return new Promise((resolve, reject) => {\n const child = spawn(\"scp\", [localPath, remoteSpec], { stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => (code === 0 ? resolve() : reject(new Error(`scp exited with code ${code}`))));\n });\n}\n\n/** Copy a git deploy key from the laptop to the server (no-op if not present locally). */\nexport async function ensureDeployKeyOnRemote(host, deployKeyPath, options = {}) {\n const { logger = console } = options;\n if (!deployKeyPath) return false;\n const localPath = expandHome(deployKeyPath);\n if (!(await pathExists(localPath))) return false;\n\n const keyBase = basename(localPath);\n logger.info(`copying deploy key ${localPath} → ${host}:~/.ssh/${keyBase}`);\n await sshRun(host, \"mkdir -p ~/.ssh && chmod 700 ~/.ssh\", { logger });\n await scp(localPath, `${host}:.ssh/${keyBase}`, { logger });\n await sshRun(host, `chmod 600 ~/.ssh/${keyBase}`, { logger });\n return true;\n}\n\n/** known_hosts for the git host + (optional) ~/.ssh/config IdentityFile for the deploy key. */\nexport async function prepareGitHost(host, options = {}) {\n const { logger = console, gitHost, keyBasename } = options;\n if (!gitHost) return;\n\n const configBlock = keyBasename\n ? `if ! grep -q 'Host ${gitHost}' ~/.ssh/config 2>/dev/null; then\n printf '%s\\\\n' '' 'Host ${gitHost}' ' HostName ${gitHost}' ' User git' ' IdentityFile ~/.ssh/${keyBasename}' ' IdentitiesOnly yes' >> ~/.ssh/config\n chmod 600 ~/.ssh/config\n echo \"configured ~/.ssh/config for ${gitHost}\"\nfi`\n : `:`;\n\n const script = `\nset -euo pipefail\nmkdir -p ~/.ssh\nchmod 700 ~/.ssh\nif ! grep -q '^${gitHost}' ~/.ssh/known_hosts 2>/dev/null; then\n ssh-keyscan -t ed25519,rsa ${gitHost} >> ~/.ssh/known_hosts 2>/dev/null\n echo \"added ${gitHost} to known_hosts\"\nfi\n${configBlock}\n`.trim();\n\n await sshRun(host, script, { logger });\n}\n\n/** Install the run-dir workspace deps so the toolkit CLI is available on the server. */\nexport async function ensureRepoDependencies(host, service, options = {}) {\n const { logger = console } = options;\n const paths = servicePaths(service);\n const run = shellQuote(paths.repoRun);\n await sshRun(\n host,\n `cd ${run} && if [ ! -d node_modules/@nmakarov/cli-toolkit ] || [ package-lock.json -nt node_modules/.package-lock.json ]; then npm ci; fi`,\n { logger },\n );\n}\n\n/** Copy laptop .env → server run-dir .env (scrubbed). sync-env then copies it to shared/. */\nexport async function ensureEnvOnRemote(host, service, options = {}) {\n const { logger = console, envFile } = options;\n const paths = servicePaths(service);\n const localPath = resolveLocalEnvPath(envFile);\n if (!(await pathExists(localPath))) return false;\n\n logger.info(`copying .env ${localPath} → ${host}:${paths.repoEnv}`);\n await sshRun(host, `mkdir -p ${shellQuote(dirname(paths.repoEnv))}`, { logger });\n\n const scrubbed = scrubEnvContent(await readFile(localPath, \"utf8\"), service.envScrubPatterns ?? []);\n const tmp = join(tmpdir(), `deploy-env-${Date.now()}`);\n await writeFile(tmp, scrubbed, { mode: 0o600 });\n await scp(tmp, `${host}:${paths.repoEnv}`, { logger });\n await sshRun(host, `chmod 600 ${shellQuote(paths.repoEnv)}`, { logger });\n return true;\n}\n\n/** Ensure a checkout exists on the server, then pull latest + deps + env. */\nexport async function ensureRemoteRepo(host, service, options = {}) {\n const { logger = console, deployKey = service.deployKey, envFile } = options;\n const paths = servicePaths(service);\n const repo = shellQuote(paths.repo);\n const repoUrl = shellQuote(service.repoUrl);\n const root = shellQuote(paths.root);\n\n const localKeyBase = deployKey ? basename(expandHome(deployKey)) : null;\n await ensureDeployKeyOnRemote(host, deployKey, { logger });\n await prepareGitHost(host, { logger, gitHost: parseGitHost(service.repoUrl), keyBasename: localKeyBase });\n\n await sshRun(\n host,\n `mkdir -p ${root} && if [ -d ${repo}/.git ]; then git -C ${repo} pull --ff-only; else git clone ${repoUrl} ${repo}; fi`,\n { logger },\n );\n\n await ensureRepoDependencies(host, service, { logger });\n await ensureEnvOnRemote(host, service, { logger, envFile });\n}\n\n/**\n * Invoke the toolkit deploy CLI on the server in local mode (no --host), passing\n * the same --service/--manifests + extra flags. Run after ensureRemoteRepo.\n */\nexport async function runRemoteCli(host, service, command, args = [], options = {}) {\n const { logger = console, manifests, skipPull = false, deployKey = service.deployKey, envFile } = options;\n const paths = servicePaths(service);\n const run = shellQuote(paths.repoRun);\n const cli = shellQuote(REMOTE_CLI_REL);\n\n if (!skipPull) {\n await ensureRemoteRepo(host, service, { logger, deployKey, envFile });\n } else {\n await ensureRepoDependencies(host, service, { logger });\n await ensureEnvOnRemote(host, service, { logger, envFile });\n }\n\n const passthrough = [\n `--service=${service.name}`,\n ...(manifests ? [`--manifests=${manifests}`] : []),\n ...args,\n ].map(shellQuote).join(\" \");\n\n await sshRun(host, `cd ${run} && node ${cli} ${shellQuote(command)} ${passthrough}`.trim(), { logger });\n}\n\nexport async function runRemoteStatus(host, service, options = {}) {\n const { logger = console } = options;\n const paths = servicePaths(service);\n const port = service.pm2.port;\n const app = service.pm2.appName;\n\n const script = `\necho \"=== apps root ===\"\nls -la ${shellQuote(paths.root)} 2>/dev/null || echo \"(missing)\"\necho \"\"\necho \"=== current ===\"\nreadlink ${shellQuote(paths.current)} 2>/dev/null || echo \"(not set)\"\necho \"\"\necho \"=== releases ===\"\nls -1 ${shellQuote(paths.releases)} 2>/dev/null || echo \"(none)\"\necho \"\"\necho \"=== pm2 ===\"\npm2 describe ${app} 2>/dev/null | head -20 || pm2 status ${app} 2>/dev/null || echo \"(not running)\"\n${port ? `echo \"\"\necho \"=== app health (localhost) ===\"\ncurl -sf http://127.0.0.1:${port}/healthz 2>/dev/null || echo \"(no /healthz on :${port})\"` : \"\"}\n`.trim();\n\n await sshRun(host, script, { logger });\n}\n","import { isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { defineService } from \"./service.js\";\n\n/**\n * Load a project's service manifests module. The module may export:\n * export const services = { \"my-svc\": { … }, … } // map\n * export default { \"my-svc\": { … } } // map as default\n * export const services = [ { name: \"my-svc\", … } ] // array\n * Every entry is normalized through defineService().\n *\n * @param {{ manifests?: string, cwd?: string }} opts manifests = path to the module\n * @returns {Promise<Record<string, object>>} name → normalized service\n */\nexport async function loadServices({ manifests = \"deploy/services.js\", cwd = process.cwd() } = {}) {\n const abs = isAbsolute(manifests) ? manifests : resolve(cwd, manifests);\n let mod;\n try {\n mod = await import(pathToFileURL(abs).href);\n } catch (err) {\n throw new Error(`Could not load deploy manifests from ${abs}: ${err.message}`);\n }\n\n const raw = mod.services ?? mod.default;\n if (!raw) {\n throw new Error(`Manifests module ${abs} must export \\`services\\` (or default): a map or array of service manifests`);\n }\n\n const list = Array.isArray(raw) ? raw : Object.values(raw);\n const out = {};\n for (const entry of list) {\n const svc = defineService(entry);\n out[svc.name] = svc;\n }\n return out;\n}\n\n/** Resolve one normalized service by name (optionally overriding appsRoot). */\nexport function resolveServiceFrom(serviceMap, name, { appsRoot } = {}) {\n const svc = serviceMap[name];\n if (!svc) {\n throw new Error(`Unknown service \"${name}\". Known: ${Object.keys(serviceMap).join(\", \") || \"(none)\"}`);\n }\n return appsRoot ? { ...svc, appsRoot } : svc;\n}\n"],"mappings":"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","names":["resolve","mkdir","join","mkdir","join","join","access","cp","mkdir","join","pathExists","access","join","cp","mkdir","symlink","unlink","join","join","unlink","symlink","rm","readlink","readlink","rm","resolve","writeFile","join","join","writeFile","access","mkdir","readFile","writeFile","pathExists","access","mkdir","writeFile","readFile","readFile","writeFile","mkdir","join","readFile","join","mkdir","writeFile","access","appendFile","mkdir","writeFile","dirname","join","pathExists","access","join","dirname","mkdir","writeFile","appendFile","access","mkdir","readFile","writeFile","join","pathExists","access","join","mkdir","readFile","writeFile","readlink","resolveKillTimeoutMs","readlink","access","pathExists","access","readlink","readlink","access","readFile","writeFile","homedir","tmpdir","basename","dirname","join","spawn","pathExists","access","expandHome","join","homedir","resolve","spawn","basename","run","dirname","readFile","tmpdir","writeFile"]}
1
+ {"version":3,"sources":["../src/deploy/service.js","../src/deploy/paths.js","../src/deploy/run.js","../src/deploy/log.js","../src/deploy/git.js","../src/deploy/release.js","../src/deploy/activate.js","../src/deploy/deps.js","../src/deploy/test.js","../src/deploy/prune.js","../src/deploy/pm2.js","../src/deploy/nginx.js","../src/deploy/sync-env.js","../src/deploy/build-info.js","../src/deploy/init-structure.js","../src/deploy/bootstrap-host.js","../src/deploy/deploy-service.js","../src/deploy/provision-service.js","../src/deploy/rollback-service.js","../src/deploy/ssh-remote.js","../src/deploy/manifests.js"],"sourcesContent":["/**\n * A \"service\" is a plain manifest object describing how to deploy one app onto a\n * host. Projects define these; the engine is otherwise project-agnostic.\n *\n * Required:\n * name unique id, also the default pm2 app + nginx site name\n * appsRoot base dir on the host, e.g. /apps/<name>\n * repoUrl git url to clone (ssh git@… or https://…)\n * pm2.script entry script (relative to the release/cwd)\n *\n * Optional (sensible defaults applied by defineService):\n * repoDirName default: repo name derived from repoUrl\n * repoSubdir default: \"\" (release = repo root). Set \"v2\" for a monorepo subdir.\n * keepReleases default: 3\n * testCommand default: null (shell run in the release before activating; null = skip)\n * envScrubPatterns default: [] (regex source strings; matching .env lines are dropped)\n * legacyRepoEnv default: null (fallback .env path on a pre-migration host)\n * buildInfoPath default: \"build-info.json\" (written into each release, relative to release root)\n * deployKey default: null (path to a git deploy key; only needed for ssh repoUrl)\n * requireEnv default: false (true = fail if no .env found; false = continue with empty)\n * pm2: {\n * appName=name, script, args=\"\", port,\n * stopAllowance=60, // seconds → injected as --stopAllowance=N on the process\n * killTimeout=65000, // ms; pm2 kill_timeout (default: stopAllowance*1000+5000)\n * }\n * nginx: { siteName=name, fqdn } (omit nginx entirely to skip the nginx step)\n */\nexport function deriveRepoDirName(repoUrl) {\n const tail = String(repoUrl ?? \"\").split(\"/\").pop() ?? \"repo\";\n return tail.replace(/\\.git$/, \"\") || \"repo\";\n}\n\nexport function defineService(service) {\n if (!service?.name) throw new Error(\"service manifest needs a `name`\");\n if (!service.appsRoot) throw new Error(`service \"${service.name}\" needs an appsRoot`);\n if (!service.repoUrl) throw new Error(`service \"${service.name}\" needs a repoUrl`);\n if (!service.pm2?.script) throw new Error(`service \"${service.name}\" needs pm2.script`);\n\n const pm2 = {\n appName: service.name,\n args: \"\",\n stopAllowance: 60,\n ...service.pm2,\n };\n if (pm2.killTimeout == null) {\n const stopSec = Number(pm2.stopAllowance);\n pm2.killTimeout = Number.isFinite(stopSec) && stopSec > 0\n ? Math.floor(stopSec * 1000) + 5000\n : 65_000;\n }\n const nginx = service.nginx\n ? { siteName: service.name, ...service.nginx }\n : null;\n\n return {\n repoDirName: deriveRepoDirName(service.repoUrl),\n repoSubdir: \"\",\n keepReleases: 3,\n testCommand: null,\n envScrubPatterns: [],\n legacyRepoEnv: null,\n buildInfoPath: \"build-info.json\",\n deployKey: null,\n requireEnv: false,\n ...service,\n pm2,\n nginx,\n };\n}\n","import { join } from \"node:path\";\n\n/**\n * Resolve on-disk paths for a service under <appsRoot>/.\n *\n * <appsRoot>/\n * <repoDirName>/[<repoSubdir>/] git checkout (build source only)\n * releases/<stamp>/ one release = contents of the run dir\n * shared/.env, ecosystem.config.cjs\n * logs/deploy.log\n * current -> releases/<stamp>\n */\nexport function servicePaths(service) {\n const root = service.appsRoot;\n const repoDirName = service.repoDirName ?? \"repo\";\n const repoSubdir = service.repoSubdir ?? \"\";\n\n const repo = join(root, repoDirName);\n const repoRun = repoSubdir ? join(repo, repoSubdir) : repo;\n\n return {\n root,\n repo,\n repoRun,\n repoEnv: join(repoRun, \".env\"),\n releases: join(root, \"releases\"),\n shared: join(root, \"shared\"),\n logs: join(root, \"logs\"),\n current: join(root, \"current\"),\n sharedEnv: join(root, \"shared\", \".env\"),\n ecosystem: join(root, \"shared\", \"ecosystem.config.cjs\"),\n /** Preloaded by pm2 `node_args --require` so ENV survives flaky restarts. */\n ensureEnv: join(root, \"shared\", \"ensure-env.cjs\"),\n deployLog: join(root, \"logs\", \"deploy.log\"),\n lockHashFile: join(root, \"shared\", \".package-lock.sha256\"),\n };\n}\n\n/** ISO-8601 release stamp with `:` (safe on Linux ext4). */\nexport function releaseStamp(date = new Date()) {\n return date.toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n}\n\nexport function releaseDir(releasesRoot, stamp) {\n return join(releasesRoot, stamp);\n}\n","import { spawn } from \"node:child_process\";\n\n/** npm omits devDependencies when NODE_ENV=production — unset it for ci + tests. */\nexport function npmEnv(baseEnv = process.env) {\n const env = { ...baseEnv };\n delete env.NODE_ENV;\n return env;\n}\n\n/** Env for npm ci/install so devDependencies are always installed. */\nexport function npmInstallEnv(baseEnv = process.env) {\n return {\n ...npmEnv(baseEnv),\n NPM_CONFIG_PRODUCTION: \"false\",\n npm_config_production: \"false\",\n };\n}\n\n/** Run a command with inherited stdio. Rejects on non-zero exit. */\nexport function run(cmd, args, options = {}) {\n const { cwd, env, logger } = options;\n\n return new Promise((resolve, reject) => {\n logger?.info?.(`$ ${cmd} ${args.join(\" \")}${cwd ? ` (cwd=${cwd})` : \"\"}`);\n const child = spawn(cmd, args, {\n cwd,\n env: env ?? process.env,\n stdio: \"inherit\",\n });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`${cmd} exited with code ${code}`));\n });\n });\n}\n\n/** Run a shell one-liner via bash -lc. */\nexport function runShell(command, options = {}) {\n return run(\"bash\", [\"-lc\", command], options);\n}\n\n/**\n * Run a command capturing stdout/stderr (for `pm2 jlist`, etc.).\n * Rejects on non-zero exit unless `allowFail` is set.\n *\n * @returns {Promise<{ stdout: string, stderr: string, code: number|null }>}\n */\nexport function runCapture(cmd, args, options = {}) {\n const { cwd, env, logger, allowFail = false } = options;\n\n return new Promise((resolve, reject) => {\n logger?.info?.(`$ ${cmd} ${args.join(\" \")}${cwd ? ` (cwd=${cwd})` : \"\"}`);\n const child = spawn(cmd, args, {\n cwd,\n env: env ?? process.env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => {\n if (code === 0 || allowFail) resolve({ stdout, stderr, code });\n else reject(new Error(`${cmd} exited with code ${code}${stderr ? `: ${stderr.trim()}` : \"\"}`));\n });\n });\n}\n","import { appendFile, mkdir } from \"node:fs/promises\";\n\nexport async function appendDeployLog(deployLogPath, message) {\n await mkdir(deployLogPath.replace(/\\/[^/]+$/, \"\"), { recursive: true });\n const line = `[${new Date().toISOString()}] ${message}\\n`;\n await appendFile(deployLogPath, line);\n}\n","import { access } from \"node:fs/promises\";\n\nimport { servicePaths } from \"./paths.js\";\nimport { run } from \"./run.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function cloneRepo(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n\n if (await pathExists(paths.repo)) {\n throw new Error(`Repo already exists at ${paths.repo} — use git pull instead`);\n }\n if (dryRun) {\n logger.info(`[dryRun] would git clone ${service.repoUrl} ${paths.repo}`);\n return;\n }\n\n await run(\"git\", [\"clone\", service.repoUrl, paths.repo], { logger });\n logger.info(`cloned ${service.repoUrl} → ${paths.repo}`);\n}\n\nexport async function pullRepo(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n\n if (!(await pathExists(paths.repo))) {\n throw new Error(`Repo missing at ${paths.repo} — run provision first`);\n }\n if (dryRun) {\n logger.info(`[dryRun] would git -C ${paths.repo} pull --ff-only`);\n return;\n }\n\n await run(\"git\", [\"-C\", paths.repo, \"pull\", \"--ff-only\"], { logger });\n logger.info(`pulled ${paths.repo}`);\n}\n","import { cp, mkdir, readlink, readdir, stat } from \"node:fs/promises\";\nimport { join, dirname as pathDirname, sep } from \"node:path\";\n\nimport { releaseDir, releaseStamp, servicePaths } from \"./paths.js\";\n\nconst SKIP_TOP = new Set([\"node_modules\", \".git\"]);\n\nfunction shouldCopyEntry(srcPath) {\n const parts = srcPath.split(sep);\n if (parts.some((p) => p === \"node_modules\" || p === \".git\")) return false;\n const top = parts.at(-1);\n if (parts.length === 1 && SKIP_TOP.has(top)) return false;\n return true;\n}\n\n/** Copy the run dir into releases/<stamp>/ (excluding node_modules and .git). */\nexport async function createRelease(service, options = {}) {\n const { stamp = releaseStamp(), dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n const dest = releaseDir(paths.releases, stamp);\n\n if (dryRun) {\n logger.info(`[dryRun] would copy ${paths.repoRun} → ${dest}`);\n return { stamp, path: dest };\n }\n\n await mkdir(paths.releases, { recursive: true });\n await cp(paths.repoRun, dest, {\n recursive: true,\n filter: (src) => shouldCopyEntry(src),\n });\n\n logger.info(`release ${stamp} created at ${dest}`);\n return { stamp, path: dest };\n}\n\n/** Resolve current release path if symlink exists. */\nexport async function readCurrentRelease(paths) {\n try {\n const target = await readlink(paths.current);\n return target.startsWith(\"/\") ? target : join(pathDirname(paths.current), target);\n } catch {\n return null;\n }\n}\n\nexport async function listReleases(paths) {\n let names;\n try {\n names = await readdir(paths.releases);\n } catch {\n return [];\n }\n\n const entries = [];\n for (const name of names) {\n const full = releaseDir(paths.releases, name);\n const s = await stat(full);\n if (s.isDirectory()) entries.push({ name, path: full, mtime: s.mtime });\n }\n\n entries.sort((a, b) => b.name.localeCompare(a.name));\n return entries;\n}\n","import { symlink, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/** Point current → release and link shared/.env inside the release. */\nexport async function activateRelease(releasePath, paths, options = {}) {\n const { dryRun = false, logger = console } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would activate ${releasePath} → ${paths.current}`);\n return;\n }\n\n try {\n await unlink(paths.current);\n } catch {\n // first deploy\n }\n await symlink(releasePath, paths.current);\n\n const envLink = join(releasePath, \".env\");\n try {\n await unlink(envLink);\n } catch {\n // ok\n }\n await symlink(paths.sharedEnv, envLink);\n\n logger.info(`active release: ${releasePath}`);\n}\n","import { createHash } from \"node:crypto\";\nimport { access, cp, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { readCurrentRelease } from \"./release.js\";\nimport { npmInstallEnv, run } from \"./run.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function lockfileHash(releasePath) {\n const lockPath = join(releasePath, \"package-lock.json\");\n try {\n const buf = await readFile(lockPath);\n return createHash(\"sha256\").update(buf).digest(\"hex\");\n } catch {\n return null; // no lockfile → can't fingerprint; always (re)install\n }\n}\n\nasync function readHashFile(path) {\n try {\n return (await readFile(path, \"utf8\")).trim();\n } catch {\n return null;\n }\n}\n\nasync function npmInstall(releasePath, hasLock, logger) {\n await rm(join(releasePath, \"node_modules\"), { recursive: true, force: true });\n const cmd = hasLock ? [\"ci\", \"--include=dev\"] : [\"install\", \"--include=dev\", \"--no-audit\", \"--no-fund\"];\n logger.info(`running npm ${cmd.join(\" \")} in ${releasePath}`);\n await run(\"npm\", cmd, { cwd: releasePath, logger, env: npmInstallEnv() });\n}\n\n/**\n * Install deps in the release. If the lockfile is unchanged from the active\n * release and its node_modules exists, copy it (fast path); otherwise npm ci.\n */\nexport async function installDeps(service, releasePath, paths, options = {}) {\n const { dryRun = false, logger = console } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would install deps in ${releasePath}`);\n return { hash: null, copied: false };\n }\n\n const hash = await lockfileHash(releasePath);\n const hasLock = hash !== null;\n const hashMarker = join(releasePath, \".deploy-package-lock.sha256\");\n\n const currentPath = await readCurrentRelease(paths);\n let copied = false;\n\n if (hasLock && currentPath && (await pathExists(join(currentPath, \"node_modules\")))) {\n const currentHash = await readHashFile(join(currentPath, \".deploy-package-lock.sha256\"));\n if (currentHash === hash) {\n logger.info(\"lockfile unchanged — copying node_modules from current release\");\n await cp(join(currentPath, \"node_modules\"), join(releasePath, \"node_modules\"), {\n recursive: true,\n force: true,\n });\n copied = true;\n }\n }\n\n if (!copied) {\n await npmInstall(releasePath, hasLock, logger);\n }\n\n if (hasLock) {\n await writeFile(hashMarker, `${hash}\\n`, \"utf8\");\n await mkdir(paths.shared, { recursive: true });\n await writeFile(paths.lockHashFile, `${hash}\\n`, \"utf8\");\n }\n\n return { hash, copied };\n}\n","import { symlink, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { npmInstallEnv, runShell } from \"./run.js\";\n\n/**\n * Symlink shared/.env into the release and run the service's testCommand (a shell\n * one-liner, e.g. \"npm run test:ci\"). No-op when the service defines no command.\n */\nexport async function runReleaseTests(service, releasePath, paths, options = {}) {\n const { dryRun = false, logger = console } = options;\n\n if (!service.testCommand) {\n logger.info(\"no testCommand configured — skipping tests\");\n return;\n }\n\n if (dryRun) {\n logger.info(`[dryRun] would run \"${service.testCommand}\" in ${releasePath}`);\n return;\n }\n\n const envLink = join(releasePath, \".env\");\n try {\n await unlink(envLink);\n } catch {\n // no existing link\n }\n await symlink(paths.sharedEnv, envLink);\n\n logger.info(`running \"${service.testCommand}\" in ${releasePath}`);\n await runShell(service.testCommand, { cwd: releasePath, logger, env: npmInstallEnv() });\n}\n","import { rm, readlink } from \"node:fs/promises\";\n\nimport { listReleases } from \"./release.js\";\n\n/**\n * Remove old releases, keeping the newest N (always keeps the active one).\n *\n * @param {object} service\n * @param {object} paths\n * @param {{ dryRun?: boolean, logger?: object, protect?: string[] }} [options]\n * `protect` — release stamp names that must not be deleted (e.g. still-running\n * previous release during a rolling deploy).\n */\nexport async function pruneReleases(service, paths, options = {}) {\n const { dryRun = false, logger = console, protect = [] } = options;\n const keep = service.keepReleases ?? 3;\n const releases = await listReleases(paths);\n\n let activeName = null;\n try {\n const target = await readlink(paths.current);\n activeName = target.split(\"/\").pop();\n } catch {\n // no current\n }\n\n const keepSet = new Set();\n for (const rel of releases) {\n if (keepSet.size < keep) keepSet.add(rel.name);\n }\n if (activeName) keepSet.add(activeName);\n for (const name of protect) {\n if (name) keepSet.add(name);\n }\n\n const toRemove = releases.filter((rel) => !keepSet.has(rel.name));\n for (const rel of toRemove) {\n if (dryRun) {\n logger.info(`[dryRun] would rm -rf ${rel.path}`);\n } else {\n await rm(rel.path, { recursive: true, force: true });\n logger.info(`pruned ${rel.path}`);\n }\n }\n\n return { removed: toRemove.map((r) => r.name) };\n}\n","import { runShell, runCapture } from \"./run.js\";\n\nfunction sleep(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** True if `pid` still exists (same host). */\nexport function isPidAlive(pid) {\n const n = Number(pid);\n if (!Number.isFinite(n) || n <= 0) return false;\n try {\n process.kill(n, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Parse `pm2 jlist` and return the process entry for `appName`, or null.\n *\n * @param {string} appName\n * @param {{ logger?: { info?: Function, warn?: Function } }} [options]\n * @returns {Promise<object|null>}\n */\nexport async function getPm2Process(appName, options = {}) {\n const { logger } = options;\n const { stdout } = await runCapture(\"bash\", [\"-lc\", \"pm2 jlist\"], { logger });\n let list;\n try {\n list = JSON.parse(stdout || \"[]\");\n } catch (err) {\n throw new Error(`pm2 jlist: invalid JSON (${err?.message ?? err})`);\n }\n if (!Array.isArray(list)) return null;\n return list.find((p) => p?.name === appName) ?? null;\n}\n\n/**\n * Poll pm2 until `predicate(proc)` is true (proc may be null if missing), or timeout.\n *\n * @param {string} appName\n * @param {(proc: object|null) => boolean} predicate\n * @param {{ timeoutMs?: number, pollMs?: number, dryRun?: boolean, logger?: object, label?: string }} [options]\n */\nexport async function waitPm2(appName, predicate, options = {}) {\n const {\n timeoutMs = 65_000,\n pollMs = 500,\n dryRun = false,\n logger = console,\n label = \"condition\",\n } = options;\n\n if (dryRun) {\n logger.info?.(`[dryRun] would wait pm2 ${appName} for ${label}`);\n return null;\n }\n\n const deadline = Date.now() + timeoutMs;\n let lastStatus = \"(unknown)\";\n while (Date.now() < deadline) {\n const proc = await getPm2Process(appName, { logger });\n lastStatus = proc?.pm2_env?.status ?? \"(missing)\";\n if (predicate(proc)) return proc;\n await sleep(pollMs);\n }\n throw new Error(\n `pm2 wait timed out after ${timeoutMs}ms for ${appName} (${label}); last status=${lastStatus}`\n );\n}\n\n/**\n * True when pm2 reports the app online on a *new* pid (old process fully gone).\n * Avoids treating a not-yet-dead pre-reload process as a successful restart.\n */\nexport function isFreshOnline(proc, oldPid) {\n if (proc?.pm2_env?.status !== \"online\") return false;\n const pid = Number(proc.pid);\n if (!Number.isFinite(pid) || pid <= 0) return false;\n if (oldPid != null) {\n if (pid === oldPid) return false;\n if (isPidAlive(oldPid)) return false;\n }\n return true;\n}\n\n/** `pm2 startOrReload <ecosystem> --update-env` — rolling restart (default deploy path). */\nexport async function reloadPm2(paths, options = {}) {\n const { dryRun = false, logger = console, appName = null, waitTimeoutMs = 65_000 } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would pm2 startOrReload ${paths.ecosystem} --update-env`);\n return;\n }\n\n let oldPid = null;\n if (appName) {\n const before = await getPm2Process(appName, { logger });\n const n = Number(before?.pid);\n if (Number.isFinite(n) && n > 0 && before?.pm2_env?.status === \"online\") {\n oldPid = n;\n logger.info(`pm2 ${appName}: reloading (old pid=${oldPid})`);\n }\n }\n\n // Prefix ENV on the pm2 CLI itself: --update-env copies the invoking shell's\n // environment into the app, which otherwise may omit / stale-out ecosystem env.\n await runShell(\n `ENV=production NODE_ENV=production pm2 startOrReload \"${paths.ecosystem}\" --update-env`,\n { logger }\n );\n logger.info(\"pm2 reloaded\");\n\n if (appName) {\n const proc = await waitPm2(\n appName,\n (p) => isFreshOnline(p, oldPid),\n {\n timeoutMs: waitTimeoutMs,\n logger,\n label: oldPid\n ? `online on new pid (old ${oldPid} gone)`\n : \"online after reload\",\n }\n );\n logger.info(`pm2 ${appName} online pid=${proc?.pid ?? \"?\"}`);\n }\n}\n\n/** `pm2 stop <app>` and wait until status is stopped (or process missing). */\nexport async function stopPm2(appName, options = {}) {\n const { dryRun = false, logger = console, waitTimeoutMs = 65_000 } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would pm2 stop ${appName}`);\n return;\n }\n\n const before = await getPm2Process(appName, { logger });\n if (!before) {\n logger.info(`pm2 ${appName}: not present (already stopped)`);\n return;\n }\n if (before.pm2_env?.status === \"stopped\") {\n logger.info(`pm2 ${appName}: already stopped`);\n return;\n }\n\n const oldPid = Number(before.pid);\n await runShell(`pm2 stop \"${appName}\"`, { logger });\n await waitPm2(\n appName,\n (proc) => {\n if (proc && proc.pm2_env?.status !== \"stopped\") return false;\n if (Number.isFinite(oldPid) && oldPid > 0 && isPidAlive(oldPid)) return false;\n return true;\n },\n { timeoutMs: waitTimeoutMs, logger, label: \"stopped\" }\n );\n logger.info(`pm2 ${appName} stopped`);\n}\n\n/** `pm2 start <ecosystem> --update-env` and wait until online. */\nexport async function startPm2(paths, options = {}) {\n const { dryRun = false, logger = console, appName = null, waitTimeoutMs = 65_000 } = options;\n\n if (dryRun) {\n logger.info(`[dryRun] would pm2 start ${paths.ecosystem} --update-env`);\n return;\n }\n\n await runShell(\n `ENV=production NODE_ENV=production pm2 start \"${paths.ecosystem}\" --update-env`,\n { logger }\n );\n logger.info(\"pm2 started\");\n\n if (appName) {\n const proc = await waitPm2(\n appName,\n (p) => isFreshOnline(p, null),\n { timeoutMs: waitTimeoutMs, logger, label: \"online after start\" }\n );\n logger.info(`pm2 ${appName} online pid=${proc?.pid ?? \"?\"}`);\n }\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { runShell } from \"./run.js\";\n\nasync function hasTlsCert(certPath) {\n try {\n await runShell(`sudo test -f '${certPath}'`, {});\n return true;\n } catch {\n return false;\n }\n}\n\nfunction proxyBlock(port) {\n return ` location / {\n proxy_pass http://127.0.0.1:${port};\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_read_timeout 120s;\n }`;\n}\n\nfunction buildNginxConfig(service) {\n const { nginx, pm2 } = service;\n const certDir = `/etc/letsencrypt/live/${nginx.fqdn}`;\n\n return `# ${nginx.siteName} — managed by cli-toolkit deploy (proxy mode)\n# ${nginx.fqdn} → 127.0.0.1:${pm2.port}\n\nserver {\n listen 80;\n listen [::]:80;\n server_name ${nginx.fqdn};\n location / { return 301 https://$host$request_uri; }\n}\n\nserver {\n listen 443 ssl;\n listen [::]:443 ssl;\n server_name ${nginx.fqdn};\n\n ssl_certificate ${certDir}/fullchain.pem;\n ssl_certificate_key ${certDir}/privkey.pem;\n include /etc/letsencrypt/options-ssl-nginx.conf;\n ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;\n\n client_max_body_size 25m;\n\n${proxyBlock(pm2.port)}\n}\n`;\n}\n\nfunction buildNginxConfigHttpOnly(service) {\n const { nginx, pm2 } = service;\n return `# ${nginx.siteName} — managed by cli-toolkit deploy (HTTP proxy, no TLS cert yet)\n\nserver {\n listen 80;\n listen [::]:80;\n server_name ${nginx.fqdn};\n\n client_max_body_size 25m;\n\n${proxyBlock(pm2.port)}\n}\n`;\n}\n\n/** Replace the stub nginx site with a reverse-proxy to the app. */\nexport async function enableNginxUpstream(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const { nginx } = service;\n if (!nginx) {\n logger.info(\"no nginx config on service — skipping nginx step\");\n return { skipped: true };\n }\n\n const siteAvailable = `/etc/nginx/sites-available/${nginx.siteName}`;\n const siteEnabled = `/etc/nginx/sites-enabled/${nginx.siteName}`;\n const cert = `/etc/letsencrypt/live/${nginx.fqdn}/fullchain.pem`;\n\n if (dryRun) {\n logger.info(`[dryRun] would write ${siteAvailable} (proxy → 127.0.0.1:${service.pm2.port}) and reload nginx`);\n return { hasCert: null };\n }\n\n const hasCert = await hasTlsCert(cert);\n const config = hasCert ? buildNginxConfig(service) : buildNginxConfigHttpOnly(service);\n\n const tmp = join(tmpdir(), `${service.name}-nginx.conf`);\n await writeFile(tmp, config);\n await runShell(\n `sudo cp '${tmp}' '${siteAvailable}' && sudo ln -sf '${siteAvailable}' '${siteEnabled}' && sudo nginx -t && sudo systemctl reload nginx`,\n { logger },\n );\n\n logger.info(`nginx upstream enabled for ${nginx.fqdn} → 127.0.0.1:${service.pm2.port} (tls=${hasCert})`);\n return { hasCert };\n}\n","import { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\n\nimport { servicePaths } from \"./paths.js\";\n\n/**\n * Drop .env lines matching any of the service's envScrubPatterns (regex source\n * strings). Useful to strip empty keys that would crash strict param validation.\n */\nexport function scrubEnvContent(content, patterns = []) {\n const regexes = patterns.map((p) => (p instanceof RegExp ? p : new RegExp(p)));\n if (regexes.length === 0) return content;\n return content\n .split(\"\\n\")\n .filter((line) => !regexes.some((re) => re.test(line.trim())))\n .join(\"\\n\");\n}\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Copy <repoRun>/.env → shared/.env (with optional legacy fallback + scrubbing). */\nexport async function syncEnv(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n const patterns = service.envScrubPatterns ?? [];\n\n if (dryRun) {\n logger.info(`[dryRun] would sync ${paths.repoEnv} → ${paths.sharedEnv}`);\n return { source: paths.repoEnv, dest: paths.sharedEnv };\n }\n\n let source = paths.repoEnv;\n if (!(await pathExists(source)) && service.legacyRepoEnv && (await pathExists(service.legacyRepoEnv))) {\n logger.info(`using legacy env: ${service.legacyRepoEnv}`);\n source = service.legacyRepoEnv;\n }\n\n await mkdir(paths.shared, { recursive: true });\n\n if (!(await pathExists(source))) {\n if (service.requireEnv) {\n throw new Error(\n `No .env found at ${paths.repoEnv}` +\n (service.legacyRepoEnv ? ` or ${service.legacyRepoEnv}` : \"\") +\n \" — place .env on the host or run the remote deploy from a laptop with a local .env (auto-scp)\",\n );\n }\n // Optional env: ensure shared/.env exists so the per-release symlink is valid.\n if (!(await pathExists(paths.sharedEnv))) {\n await writeFile(paths.sharedEnv, \"\", { mode: 0o600 });\n }\n logger.warn(`no .env found (source ${source}) — using empty ${paths.sharedEnv}`);\n return { source: null, dest: paths.sharedEnv };\n }\n\n const raw = await readFile(source, \"utf8\");\n await writeFile(paths.sharedEnv, scrubEnvContent(raw, patterns), { mode: 0o600 });\n logger.info(`synced ${source} → ${paths.sharedEnv}`);\n return { source, dest: paths.sharedEnv };\n}\n","import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nimport { readCurrentRelease } from \"./release.js\";\nimport { servicePaths } from \"./paths.js\";\n\nconst execFileAsync = promisify(execFile);\n\n/** Bump semver patch (0.1.0 → 0.1.1). */\nexport function bumpPatchVersion(version) {\n const parts = String(version).trim().split(\".\");\n const major = Number.parseInt(parts[0], 10) || 0;\n const minor = Number.parseInt(parts[1], 10) || 0;\n const patch = Number.parseInt(parts[2], 10) || 0;\n return `${major}.${minor}.${patch + 1}`;\n}\n\nasync function readJson(path) {\n try {\n return JSON.parse(await readFile(path, \"utf8\"));\n } catch {\n return null;\n }\n}\n\nasync function gitShortCommit(repoPath) {\n try {\n const { stdout } = await execFileAsync(\"git\", [\"-C\", repoPath, \"rev-parse\", \"--short\", \"HEAD\"], {\n encoding: \"utf8\",\n });\n return stdout.trim() || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Next semver: bump patch from the **currently active** release's build-info,\n * else seed from package.json. Each release carries its own build-info.json so a\n * rollback restores that release's label.\n */\nexport async function resolveNextVersion(service, paths, pkgPath) {\n const pkg = (await readJson(pkgPath)) ?? {};\n const baseVersion = pkg.version || \"0.1.0\";\n\n const currentPath = await readCurrentRelease(paths);\n if (currentPath) {\n const active = await readJson(join(currentPath, service.buildInfoPath));\n if (active?.version) return bumpPatchVersion(active.version);\n }\n return baseVersion;\n}\n\n/** Read build-info from a release directory (for rollback logs / status). */\nexport async function readReleaseBuildInfo(service, releasePath) {\n return readJson(join(releasePath, service.buildInfoPath));\n}\n\n/** Write build-info.json into the new release directory. */\nexport async function writeReleaseBuildInfo(service, releasePath, { stamp, dryRun = false, logger = console }) {\n const paths = servicePaths(service);\n const pkgPath = join(paths.repoRun, \"package.json\");\n\n const version = await resolveNextVersion(service, paths, pkgPath);\n const gitCommit = await gitShortCommit(paths.repo);\n\n const buildInfo = {\n version,\n release: stamp,\n deployedAt: new Date().toISOString(),\n gitCommit,\n service: service.name,\n };\n\n const dest = join(releasePath, service.buildInfoPath);\n\n if (dryRun) {\n logger.info(`[dryRun] would write ${dest} (${JSON.stringify(buildInfo)})`);\n return buildInfo;\n }\n\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, `${JSON.stringify(buildInfo, null, 2)}\\n`, { mode: 0o644 });\n\n const gitSuffix = gitCommit ? ` git=${gitCommit}` : \"\";\n logger.info(`build info: v${version} release=${stamp}${gitSuffix}`);\n return buildInfo;\n}\n","import { access, appendFile, mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { servicePaths } from \"./paths.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Fail fast with a useful message when not on a provisioned host. */\nasync function requireDeployRoot(parentDir, serviceRoot) {\n if (await pathExists(parentDir)) return;\n throw new Error(\n `Cannot create ${serviceRoot}: parent directory ${parentDir} does not exist.\\n` +\n `This is meant to run on the target host (where ${parentDir} exists). ` +\n `For a local dry run use --appsRoot=/tmp/<service>.`,\n );\n}\n\nfunction resolveKillTimeoutMs(pm2) {\n const explicit = Number(pm2?.killTimeout);\n if (Number.isFinite(explicit) && explicit > 0) return Math.floor(explicit);\n const stopSec = Number(pm2?.stopAllowance);\n if (Number.isFinite(stopSec) && stopSec > 0) return Math.floor(stopSec * 1000) + 5000;\n return 65_000;\n}\n\n/** Append `--stopAllowance=<sec>` to pm2 args when manifest sets pm2.stopAllowance. */\nfunction resolvePm2Args(pm2) {\n const base = String(pm2?.args ?? \"\").trim();\n const stopSec = Number(pm2?.stopAllowance);\n if (!Number.isFinite(stopSec) || stopSec <= 0) return base;\n if (/(?:^|\\s)--stopAllowance=/.test(base)) return base;\n return `${base}${base ? \" \" : \"\"}--stopAllowance=${Math.floor(stopSec)}`.trim();\n}\n\n/** CJS snippet required before the app — always force production on deployed hosts. */\nfunction buildEnsureEnvScript() {\n return `/**\n * Written by cli-toolkit deploy (init-structure). Do not hand-edit.\n * Preloaded via pm2 node_args --require. Always set (do not only fill when unset):\n * \\`pm2 startOrReload --update-env\\` can inject a non-production ENV from the\n * deploy shell / prior dump, and a conditional assign would leave it in place.\n */\nprocess.env.ENV = \"production\";\nprocess.env.NODE_ENV = \"production\";\n`;\n}\n\nfunction buildEcosystemConfig(service, paths) {\n const { pm2 } = service;\n const outLog = join(paths.logs, `${pm2.appName}.out.log`);\n const errLog = join(paths.logs, `${pm2.appName}.err.log`);\n const killTimeoutMs = resolveKillTimeoutMs(pm2);\n const args = resolvePm2Args(pm2);\n const ensureEnv = paths.ensureEnv;\n\n return `/**\n * pm2 ecosystem for ${service.name} — written by cli-toolkit deploy from the\n * service manifest (init/deploy). Do not hand-edit; change pm2.* in the\n * manifest and redeploy. cwd → \\`current\\` symlink.\n */\nmodule.exports = {\n apps: [\n {\n name: \"${pm2.appName}\",\n script: \"${pm2.script}\",\n cwd: \"${paths.current}\",\n args: ${JSON.stringify(args)},\n // Absolute --require so ENV is set before Args constructs (see shared/ensure-env.cjs).\n node_args: ${JSON.stringify(`--require ${ensureEnv}`)},\n instances: 1,\n exec_mode: \"fork\",\n autorestart: true,\n min_uptime: \"10s\",\n max_restarts: 10,\n restart_delay: 2000,\n max_memory_restart: \"1500M\",\n // Grace window after SIGINT/SIGTERM before SIGKILL (ms). Align with --stopAllowance.\n kill_timeout: ${killTimeoutMs},\n out_file: \"${outLog}\",\n error_file: \"${errLog}\",\n merge_logs: true,\n time: true,\n env: {\n // cli-toolkit Args: ENV selects env-suffixed defaults (DB_NAME_PRODUCTION, BUCKET_PRODUCTION, …)\n ENV: \"production\",\n NODE_ENV: \"production\",\n },\n },\n ],\n};\n`;\n}\n\n/** Create <appsRoot>/{releases,shared,logs} and sync shared/ecosystem.config.cjs from the manifest. */\nexport async function initServiceStructure(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n const dirs = [paths.releases, paths.shared, paths.logs];\n const created = [];\n const skipped = [];\n\n if (!dryRun) {\n await requireDeployRoot(dirname(paths.root), paths.root);\n }\n\n for (const dir of dirs) {\n if (await pathExists(dir)) {\n skipped.push(dir);\n continue;\n }\n if (!dryRun) await mkdir(dir, { recursive: true });\n created.push(dir);\n }\n\n const ecosystemExisted = await pathExists(paths.ecosystem);\n const ensureEnvExisted = await pathExists(paths.ensureEnv);\n if (!dryRun) {\n await writeFile(paths.ensureEnv, buildEnsureEnvScript(), { mode: 0o644 });\n await writeFile(paths.ecosystem, buildEcosystemConfig(service, paths), { mode: 0o644 });\n }\n if (ensureEnvExisted) {\n logger.info(`synced ${paths.ensureEnv}`);\n } else {\n created.push(paths.ensureEnv);\n logger.info(`seeded ${paths.ensureEnv}`);\n }\n if (ecosystemExisted) {\n logger.info(`synced ${paths.ecosystem} from manifest`);\n } else {\n created.push(paths.ecosystem);\n logger.info(`seeded ${paths.ecosystem}`);\n }\n\n const line = `[${new Date().toISOString()}] init-structure service=${service.name} dryRun=${dryRun} created=${created.length} skipped=${skipped.length} ecosystem=synced\\n`;\n if (!dryRun) {\n await mkdir(paths.logs, { recursive: true });\n await appendFile(paths.deployLog, line);\n }\n\n logger.info(`service=${service.name} appsRoot=${paths.root}`);\n logger.info(`created: ${created.length ? created.join(\", \") : \"(none)\"}`);\n logger.info(`already present: ${skipped.length ? skipped.join(\", \") : \"(none)\"}`);\n\n return { paths, created, skipped, ecosystemCreated: !ecosystemExisted, ecosystemSynced: true };\n}\n","import { execSync } from \"node:child_process\";\nimport { access, chmod, copyFile, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, join } from \"node:path\";\n\nimport { runShell } from \"./run.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction expandHome(path) {\n return path.startsWith(\"~/\") ? join(homedir(), path.slice(2)) : path;\n}\n\nasync function ensurePm2Startup(options = {}) {\n const { user = \"ubuntu\", dryRun = false, logger = console } = options;\n\n if (dryRun) {\n logger.info(\"[dryRun] would configure pm2 startup systemd\");\n return;\n }\n\n try {\n execSync(\"pm2 ping\", { stdio: \"ignore\" });\n } catch {\n // pm2 daemon may not be up yet\n }\n\n try {\n const out = execSync(`pm2 startup systemd -u ${user} --hp /home/${user}`, { encoding: \"utf8\" });\n const sudoLine = out.split(\"\\n\").find((l) => l.trim().startsWith(\"sudo\"));\n if (sudoLine) {\n execSync(sudoLine.trim(), { stdio: \"inherit\" });\n logger.info(\"pm2 startup systemd configured\");\n }\n } catch (err) {\n logger.warn(`pm2 startup skipped or already configured: ${err.message}`);\n }\n}\n\nasync function installDeployKey(deployKeyPath, options = {}) {\n const { dryRun = false, logger = console } = options;\n const keyPath = expandHome(deployKeyPath);\n\n if (!(await pathExists(keyPath))) {\n logger.warn(`deploy key not found at ${keyPath} — skipping git ssh setup`);\n return;\n }\n\n const keyBase = basename(keyPath);\n const sshDir = join(homedir(), \".ssh\");\n const destKey = join(sshDir, keyBase);\n const configPath = join(sshDir, \"config\");\n const block = `\nHost github.com\n HostName github.com\n User git\n IdentityFile ${destKey}\n IdentitiesOnly yes\n`;\n\n if (dryRun) {\n logger.info(`[dryRun] would install deploy key ${keyPath} → ${destKey}`);\n return;\n }\n\n await mkdir(sshDir, { recursive: true, mode: 0o700 });\n await copyFile(keyPath, destKey);\n await chmod(destKey, 0o600);\n\n let config = \"\";\n if (await pathExists(configPath)) config = await readFile(configPath, \"utf8\");\n if (!config.includes(\"Host github.com\")) {\n await writeFile(configPath, `${config.trimEnd()}\\n${block}\\n`, { mode: 0o600 });\n logger.info(\"updated ~/.ssh/config for github.com\");\n }\n logger.info(`deploy key installed at ${destKey}`);\n}\n\nasync function installLogrotate(service, options = {}) {\n const { dryRun = false, logger = console } = options;\n const conf = `/etc/logrotate.d/${service.name}`;\n const body = `${service.appsRoot}/logs/*.log {\n daily\n rotate 14\n compress\n delaycompress\n missingok\n notifempty\n copytruncate\n}\n`;\n\n if (dryRun) {\n logger.info(`[dryRun] would write ${conf}`);\n return;\n }\n\n const tmp = join(\"/tmp\", `${service.name}-logrotate.conf`);\n await writeFile(tmp, body);\n await runShell(`sudo cp '${tmp}' '${conf}'`, { logger });\n logger.info(`logrotate config written: ${conf}`);\n}\n\nconst OPERATOR_SHELL_BEGIN = \"# >>> mlsfarm-operator >>>\";\nconst OPERATOR_SHELL_END = \"# <<< mlsfarm-operator <<<\";\n\n/**\n * Idempotent ~/.bashrc block: ENV=production + cd into /apps/<service>/current\n * on interactive login. Safe to re-run; replaces any prior managed block.\n */\nexport async function installOperatorShell(service, options = {}) {\n const { user = \"ubuntu\", dryRun = false, logger = console } = options;\n const appsRoot = service.appsRoot;\n if (!appsRoot) {\n logger.warn(\"installOperatorShell: service.appsRoot missing — skipped\");\n return;\n }\n\n const home = user === \"ubuntu\" || user === process.env.USER\n ? join(homedir(), \".bashrc\")\n : `/home/${user}/.bashrc`;\n const currentDir = join(appsRoot, \"current\");\n const block = [\n OPERATOR_SHELL_BEGIN,\n \"# Managed by cli-toolkit deploy bootstrap — do not edit by hand.\",\n 'export ENV=production',\n `if [ -d \"${currentDir}\" ]; then`,\n ` cd \"${currentDir}\" || true`,\n \"fi\",\n OPERATOR_SHELL_END,\n \"\",\n ].join(\"\\n\");\n\n if (dryRun) {\n logger.info(`[dryRun] would update ${home} (ENV=production, cd ${currentDir})`);\n return;\n }\n\n let existing = \"\";\n if (await pathExists(home)) {\n existing = await readFile(home, \"utf8\");\n }\n\n const begin = existing.indexOf(OPERATOR_SHELL_BEGIN);\n const end = existing.indexOf(OPERATOR_SHELL_END);\n let next;\n if (begin !== -1 && end !== -1 && end > begin) {\n const afterEnd = end + OPERATOR_SHELL_END.length;\n const rest = existing.slice(afterEnd).replace(/^\\n/, \"\");\n next = `${existing.slice(0, begin).replace(/\\s*$/, \"\\n\")}${block}${rest}`;\n } else {\n next = `${existing.replace(/\\s*$/, \"\\n\")}\\n${block}`;\n }\n\n await writeFile(home, next, { mode: 0o644 });\n logger.info(`operator shell: ${home} → ENV=production, cd ${currentDir}`);\n}\n\n/** One-time host prep: pm2 systemd, optional git deploy key, logrotate, operator shell. */\nexport async function bootstrapHost(service, options = {}) {\n const { deployKey = service.deployKey, user = \"ubuntu\", dryRun = false, logger = console } = options;\n\n await ensurePm2Startup({ user, dryRun, logger });\n if (deployKey) await installDeployKey(deployKey, { dryRun, logger });\n await installLogrotate(service, { dryRun, logger });\n await installOperatorShell(service, { user, dryRun, logger });\n logger.info(\"bootstrap-host complete\");\n}\n","import { readlink } from \"node:fs/promises\";\n\nimport { initServiceStructure } from \"./init-structure.js\";\nimport { appendDeployLog } from \"./log.js\";\nimport { pullRepo } from \"./git.js\";\nimport { createRelease } from \"./release.js\";\nimport { installDeps } from \"./deps.js\";\nimport { runReleaseTests } from \"./test.js\";\nimport { activateRelease } from \"./activate.js\";\nimport { pruneReleases } from \"./prune.js\";\nimport { reloadPm2, stopPm2, startPm2 } from \"./pm2.js\";\nimport { enableNginxUpstream } from \"./nginx.js\";\nimport { syncEnv } from \"./sync-env.js\";\nimport { writeReleaseBuildInfo } from \"./build-info.js\";\nimport { servicePaths } from \"./paths.js\";\n\n/**\n * Resolve pm2 kill_timeout (ms). Prefer manifest `pm2.killTimeout`, else\n * stopAllowance seconds × 1000 + 5s buffer, else 65s.\n */\nfunction resolveKillTimeoutMs(service) {\n const explicit = Number(service.pm2?.killTimeout);\n if (Number.isFinite(explicit) && explicit > 0) return Math.floor(explicit);\n const stopSec = Number(service.pm2?.stopAllowance);\n if (Number.isFinite(stopSec) && stopSec > 0) return Math.floor(stopSec * 1000) + 5000;\n return 65_000;\n}\n\n/**\n * Full deploy: pull → env → release → deps → test → activate → pm2 → prune → nginx.\n *\n * Default (**rolling**): keep the process running while the new release is built and\n * `current` is flipped; then `pm2 startOrReload` (SIGTERM + kill_timeout) so the\n * runner drains and pm2 brings it back on the new code. Previous release is not\n * pruned until after that restart.\n *\n * **`--stopFirst`**: `pm2 stop` + wait → activate/prune → `pm2 start`. Use when a\n * runner is wedged and will not honor graceful stop.\n */\nexport async function deployService(service, options = {}) {\n const {\n dryRun = false,\n skipPull = false,\n skipTests = false,\n skipNginx = false,\n stopFirst = false,\n logger = console,\n } = options;\n\n const paths = servicePaths(service);\n const appName = service.pm2.appName;\n const killTimeoutMs = resolveKillTimeoutMs(service);\n const waitTimeoutMs = killTimeoutMs + 10_000;\n\n await initServiceStructure(service, { dryRun, logger });\n\n if (!skipPull) await pullRepo(service, { dryRun, logger });\n\n await syncEnv(service, { dryRun, logger });\n\n const { stamp, path: releasePath } = await createRelease(service, { dryRun, logger });\n await writeReleaseBuildInfo(service, releasePath, { stamp, dryRun, logger });\n await installDeps(service, releasePath, paths, { dryRun, logger });\n\n if (!skipTests) await runReleaseTests(service, releasePath, paths, { dryRun, logger });\n\n let previousReleaseName = null;\n try {\n const prev = await readlink(paths.current);\n previousReleaseName = prev.split(\"/\").pop() || null;\n } catch {\n // first deploy\n }\n\n if (stopFirst) {\n logger.info(`deploy mode=stopFirst app=${appName} killTimeoutMs=${killTimeoutMs}`);\n await stopPm2(appName, { dryRun, logger, waitTimeoutMs });\n await activateRelease(releasePath, paths, { dryRun, logger });\n await pruneReleases(service, paths, { dryRun, logger });\n await startPm2(paths, { dryRun, logger, appName, waitTimeoutMs });\n } else {\n logger.info(\n `deploy mode=rolling app=${appName} killTimeoutMs=${killTimeoutMs}` +\n (previousReleaseName ? ` previous=${previousReleaseName}` : \"\")\n );\n await activateRelease(releasePath, paths, { dryRun, logger });\n // Restart into new `current` before pruning the previous tree (still in use by the old process).\n await reloadPm2(paths, { dryRun, logger, appName, waitTimeoutMs });\n await pruneReleases(service, paths, { dryRun, logger });\n }\n\n if (!skipNginx) await enableNginxUpstream(service, { dryRun, logger });\n\n const summary = `deploy complete stamp=${stamp} mode=${stopFirst ? \"stopFirst\" : \"rolling\"} dryRun=${dryRun}`;\n logger.info(summary);\n if (!dryRun) await appendDeployLog(paths.deployLog, summary);\n\n return { stamp, releasePath, mode: stopFirst ? \"stopFirst\" : \"rolling\" };\n}\n","import { access } from \"node:fs/promises\";\n\nimport { bootstrapHost } from \"./bootstrap-host.js\";\nimport { initServiceStructure } from \"./init-structure.js\";\nimport { cloneRepo, pullRepo } from \"./git.js\";\nimport { deployService } from \"./deploy-service.js\";\nimport { syncEnv } from \"./sync-env.js\";\nimport { servicePaths } from \"./paths.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/** First-time setup: optional bootstrap, init dirs, clone/pull, sync .env, first deploy. */\nexport async function provisionService(service, options = {}) {\n const {\n dryRun = false,\n deploy = true,\n skipBootstrap = true,\n deployKey,\n logger = console,\n } = options;\n\n if (!skipBootstrap) {\n await bootstrapHost(service, { deployKey, dryRun, logger });\n }\n\n await initServiceStructure(service, { dryRun, logger });\n const paths = servicePaths(service);\n\n if (await pathExists(paths.repo)) {\n logger.info(`repo exists at ${paths.repo} — pulling`);\n await pullRepo(service, { dryRun, logger });\n } else {\n await cloneRepo(service, { dryRun, logger });\n }\n\n await syncEnv(service, { dryRun, logger });\n\n if (deploy) {\n await deployService(service, { dryRun, logger, skipPull: true });\n } else {\n logger.info(\"provision complete (deploy skipped)\");\n }\n}\n","import { readlink } from \"node:fs/promises\";\n\nimport { activateRelease } from \"./activate.js\";\nimport { readReleaseBuildInfo } from \"./build-info.js\";\nimport { appendDeployLog } from \"./log.js\";\nimport { listReleases } from \"./release.js\";\nimport { reloadPm2 } from \"./pm2.js\";\nimport { servicePaths } from \"./paths.js\";\n\n/** Flip current to a named release or the previous one. */\nexport async function rollbackService(service, options = {}) {\n const { release: targetName, dryRun = false, logger = console } = options;\n const paths = servicePaths(service);\n const releases = await listReleases(paths);\n\n if (releases.length === 0) throw new Error(\"No releases to roll back to\");\n\n let activeName = null;\n try {\n const target = await readlink(paths.current);\n activeName = target.split(\"/\").pop();\n } catch {\n throw new Error(\"No active release (current symlink missing)\");\n }\n\n let rollbackTarget;\n if (targetName) {\n rollbackTarget = releases.find((r) => r.name === targetName);\n if (!rollbackTarget) throw new Error(`Release not found: ${targetName}`);\n } else {\n rollbackTarget = releases.find((r) => r.name !== activeName);\n if (!rollbackTarget) throw new Error(\"No previous release to roll back to\");\n }\n\n if (rollbackTarget.name === activeName) throw new Error(`Already on release ${activeName}`);\n\n logger.info(`rollback ${activeName} → ${rollbackTarget.name}`);\n const buildInfo = await readReleaseBuildInfo(service, rollbackTarget.path);\n if (buildInfo?.version) {\n logger.info(`rollback target: v${buildInfo.version} release=${buildInfo.release ?? rollbackTarget.name}`);\n }\n await activateRelease(rollbackTarget.path, paths, { dryRun, logger });\n await reloadPm2(paths, { dryRun, logger });\n\n const summary = `rollback ${activeName} → ${rollbackTarget.name} dryRun=${dryRun}`;\n if (!dryRun) await appendDeployLog(paths.deployLog, summary);\n\n return { from: activeName, to: rollbackTarget.name, path: rollbackTarget.path };\n}\n","import { access, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport { spawn } from \"node:child_process\";\n\nimport { scrubEnvContent } from \"./sync-env.js\";\nimport { servicePaths } from \"./paths.js\";\n\n/** Path (relative to the run dir on the server) of the toolkit deploy CLI. */\nexport const REMOTE_CLI_REL = \"node_modules/@nmakarov/cli-toolkit/scripts/deploy/cli.js\";\n\nasync function pathExists(path) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction expandHome(path) {\n return path.startsWith(\"~/\") ? join(homedir(), path.slice(2)) : path;\n}\n\nfunction resolveLocalEnvPath(envFile) {\n if (envFile) {\n const expanded = expandHome(envFile);\n return expanded.startsWith(\"/\") ? expanded : join(process.cwd(), expanded);\n }\n return join(process.cwd(), \".env\");\n}\n\n/** Parse the ssh host out of a git url (git@host:..., ssh://git@host/...). null for https. */\nexport function parseGitHost(repoUrl) {\n const u = String(repoUrl ?? \"\");\n let m = u.match(/^[^@]+@([^:]+):/); // git@github.com:org/repo.git\n if (m) return m[1];\n m = u.match(/^ssh:\\/\\/[^@]+@([^/:]+)/); // ssh://git@github.com/org/repo.git\n if (m) return m[1];\n return null;\n}\n\nexport function shellQuote(value) {\n return `'${String(value).replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport function sshRun(host, remoteCommand, options = {}) {\n const { logger } = options;\n return new Promise((resolve, reject) => {\n logger?.info?.(`ssh ${host} ${remoteCommand.slice(0, 120)}${remoteCommand.length > 120 ? \"…\" : \"\"}`);\n const child = spawn(\"ssh\", [host, remoteCommand], { stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => (code === 0 ? resolve() : reject(new Error(`ssh ${host} exited with code ${code}`))));\n });\n}\n\nfunction scp(localPath, remoteSpec) {\n return new Promise((resolve, reject) => {\n const child = spawn(\"scp\", [localPath, remoteSpec], { stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => (code === 0 ? resolve() : reject(new Error(`scp exited with code ${code}`))));\n });\n}\n\n/** Copy a git deploy key from the laptop to the server (no-op if not present locally). */\nexport async function ensureDeployKeyOnRemote(host, deployKeyPath, options = {}) {\n const { logger = console } = options;\n if (!deployKeyPath) return false;\n const localPath = expandHome(deployKeyPath);\n if (!(await pathExists(localPath))) return false;\n\n const keyBase = basename(localPath);\n logger.info(`copying deploy key ${localPath} → ${host}:~/.ssh/${keyBase}`);\n await sshRun(host, \"mkdir -p ~/.ssh && chmod 700 ~/.ssh\", { logger });\n await scp(localPath, `${host}:.ssh/${keyBase}`, { logger });\n await sshRun(host, `chmod 600 ~/.ssh/${keyBase}`, { logger });\n return true;\n}\n\n/** known_hosts for the git host + (optional) ~/.ssh/config IdentityFile for the deploy key. */\nexport async function prepareGitHost(host, options = {}) {\n const { logger = console, gitHost, keyBasename } = options;\n if (!gitHost) return;\n\n const configBlock = keyBasename\n ? `if ! grep -q 'Host ${gitHost}' ~/.ssh/config 2>/dev/null; then\n printf '%s\\\\n' '' 'Host ${gitHost}' ' HostName ${gitHost}' ' User git' ' IdentityFile ~/.ssh/${keyBasename}' ' IdentitiesOnly yes' >> ~/.ssh/config\n chmod 600 ~/.ssh/config\n echo \"configured ~/.ssh/config for ${gitHost}\"\nfi`\n : `:`;\n\n const script = `\nset -euo pipefail\nmkdir -p ~/.ssh\nchmod 700 ~/.ssh\nif ! grep -q '^${gitHost}' ~/.ssh/known_hosts 2>/dev/null; then\n ssh-keyscan -t ed25519,rsa ${gitHost} >> ~/.ssh/known_hosts 2>/dev/null\n echo \"added ${gitHost} to known_hosts\"\nfi\n${configBlock}\n`.trim();\n\n await sshRun(host, script, { logger });\n}\n\n/** Install the run-dir workspace deps so the toolkit CLI is available on the server. */\nexport async function ensureRepoDependencies(host, service, options = {}) {\n const { logger = console } = options;\n const paths = servicePaths(service);\n const run = shellQuote(paths.repoRun);\n await sshRun(\n host,\n `cd ${run} && if [ ! -d node_modules/@nmakarov/cli-toolkit ] || [ package-lock.json -nt node_modules/.package-lock.json ]; then npm ci; fi`,\n { logger },\n );\n}\n\n/** Copy laptop .env → server run-dir .env (scrubbed). sync-env then copies it to shared/. */\nexport async function ensureEnvOnRemote(host, service, options = {}) {\n const { logger = console, envFile } = options;\n const paths = servicePaths(service);\n const localPath = resolveLocalEnvPath(envFile);\n if (!(await pathExists(localPath))) return false;\n\n logger.info(`copying .env ${localPath} → ${host}:${paths.repoEnv}`);\n await sshRun(host, `mkdir -p ${shellQuote(dirname(paths.repoEnv))}`, { logger });\n\n const scrubbed = scrubEnvContent(await readFile(localPath, \"utf8\"), service.envScrubPatterns ?? []);\n const tmp = join(tmpdir(), `deploy-env-${Date.now()}`);\n await writeFile(tmp, scrubbed, { mode: 0o600 });\n await scp(tmp, `${host}:${paths.repoEnv}`, { logger });\n await sshRun(host, `chmod 600 ${shellQuote(paths.repoEnv)}`, { logger });\n return true;\n}\n\n/** Ensure a checkout exists on the server, then pull latest + deps + env. */\nexport async function ensureRemoteRepo(host, service, options = {}) {\n const { logger = console, deployKey = service.deployKey, envFile } = options;\n const paths = servicePaths(service);\n const repo = shellQuote(paths.repo);\n const repoUrl = shellQuote(service.repoUrl);\n const root = shellQuote(paths.root);\n\n const localKeyBase = deployKey ? basename(expandHome(deployKey)) : null;\n await ensureDeployKeyOnRemote(host, deployKey, { logger });\n await prepareGitHost(host, { logger, gitHost: parseGitHost(service.repoUrl), keyBasename: localKeyBase });\n\n await sshRun(\n host,\n `mkdir -p ${root} && if [ -d ${repo}/.git ]; then git -C ${repo} pull --ff-only; else git clone ${repoUrl} ${repo}; fi`,\n { logger },\n );\n\n await ensureRepoDependencies(host, service, { logger });\n await ensureEnvOnRemote(host, service, { logger, envFile });\n}\n\n/**\n * Invoke the toolkit deploy CLI on the server in local mode (no --host), passing\n * the same --service/--manifests + extra flags. Run after ensureRemoteRepo.\n */\nexport async function runRemoteCli(host, service, command, args = [], options = {}) {\n const { logger = console, manifests, skipPull = false, deployKey = service.deployKey, envFile } = options;\n const paths = servicePaths(service);\n const run = shellQuote(paths.repoRun);\n const cli = shellQuote(REMOTE_CLI_REL);\n\n if (!skipPull) {\n await ensureRemoteRepo(host, service, { logger, deployKey, envFile });\n } else {\n await ensureRepoDependencies(host, service, { logger });\n await ensureEnvOnRemote(host, service, { logger, envFile });\n }\n\n const passthrough = [\n `--service=${service.name}`,\n ...(manifests ? [`--manifests=${manifests}`] : []),\n ...args,\n ].map(shellQuote).join(\" \");\n\n await sshRun(host, `cd ${run} && node ${cli} ${shellQuote(command)} ${passthrough}`.trim(), { logger });\n}\n\nexport async function runRemoteStatus(host, service, options = {}) {\n const { logger = console } = options;\n const paths = servicePaths(service);\n const port = service.pm2.port;\n const app = service.pm2.appName;\n\n const script = `\necho \"=== apps root ===\"\nls -la ${shellQuote(paths.root)} 2>/dev/null || echo \"(missing)\"\necho \"\"\necho \"=== current ===\"\nreadlink ${shellQuote(paths.current)} 2>/dev/null || echo \"(not set)\"\necho \"\"\necho \"=== releases ===\"\nls -1 ${shellQuote(paths.releases)} 2>/dev/null || echo \"(none)\"\necho \"\"\necho \"=== pm2 ===\"\npm2 describe ${app} 2>/dev/null | head -20 || pm2 status ${app} 2>/dev/null || echo \"(not running)\"\n${port ? `echo \"\"\necho \"=== app health (localhost) ===\"\ncurl -sf http://127.0.0.1:${port}/healthz 2>/dev/null || echo \"(no /healthz on :${port})\"` : \"\"}\n`.trim();\n\n await sshRun(host, script, { logger });\n}\n","import { isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { defineService } from \"./service.js\";\n\n/**\n * Load a project's service manifests module. The module may export:\n * export const services = { \"my-svc\": { … }, … } // map\n * export default { \"my-svc\": { … } } // map as default\n * export const services = [ { name: \"my-svc\", … } ] // array\n * Every entry is normalized through defineService().\n *\n * @param {{ manifests?: string, cwd?: string }} opts manifests = path to the module\n * @returns {Promise<Record<string, object>>} name → normalized service\n */\nexport async function loadServices({ manifests = \"deploy/services.js\", cwd = process.cwd() } = {}) {\n const abs = isAbsolute(manifests) ? manifests : resolve(cwd, manifests);\n let mod;\n try {\n mod = await import(pathToFileURL(abs).href);\n } catch (err) {\n throw new Error(`Could not load deploy manifests from ${abs}: ${err.message}`);\n }\n\n const raw = mod.services ?? mod.default;\n if (!raw) {\n throw new Error(`Manifests module ${abs} must export \\`services\\` (or default): a map or array of service manifests`);\n }\n\n const list = Array.isArray(raw) ? raw : Object.values(raw);\n const out = {};\n for (const entry of list) {\n const svc = defineService(entry);\n out[svc.name] = svc;\n }\n return out;\n}\n\n/** Resolve one normalized service by name (optionally overriding appsRoot). */\nexport function resolveServiceFrom(serviceMap, name, { appsRoot } = {}) {\n const svc = serviceMap[name];\n if (!svc) {\n throw new Error(`Unknown service \"${name}\". Known: ${Object.keys(serviceMap).join(\", \") || \"(none)\"}`);\n }\n return appsRoot ? { ...svc, appsRoot } : svc;\n}\n"],"mappings":"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","names":["resolve","mkdir","join","mkdir","join","join","access","cp","mkdir","join","pathExists","access","join","cp","mkdir","symlink","unlink","join","join","unlink","symlink","rm","readlink","readlink","rm","resolve","writeFile","join","join","writeFile","access","mkdir","readFile","writeFile","pathExists","access","mkdir","writeFile","readFile","readFile","writeFile","mkdir","join","readFile","join","mkdir","writeFile","access","appendFile","mkdir","writeFile","dirname","join","pathExists","access","join","dirname","mkdir","writeFile","appendFile","access","mkdir","readFile","writeFile","join","pathExists","access","join","mkdir","readFile","writeFile","readlink","resolveKillTimeoutMs","readlink","access","pathExists","access","readlink","readlink","access","readFile","writeFile","homedir","tmpdir","basename","dirname","join","spawn","pathExists","access","expandHome","join","homedir","resolve","spawn","basename","run","dirname","readFile","tmpdir","writeFile"]}
package/dist/index.cjs CHANGED
@@ -1271,6 +1271,10 @@ var Args = class _Args {
1271
1271
  this.parseArgs(args);
1272
1272
  this.env = this.get("env")?.toLowerCase() || "local";
1273
1273
  this.loadDotEnv();
1274
+ const envAfterDotEnv = this.get("env")?.toLowerCase();
1275
+ if (envAfterDotEnv) {
1276
+ this.env = envAfterDotEnv;
1277
+ }
1274
1278
  this.loadConfigFiles();
1275
1279
  this.checkConflicts();
1276
1280
  if (context && typeof context.registerCleanup === "function") {
@@ -3731,6 +3735,12 @@ var Db = class _Db {
3731
3735
  }
3732
3736
  if (!dbConnectionString) {
3733
3737
  if (!dbName) {
3738
+ const env = context?.args?.env;
3739
+ if (env && String(env).toLowerCase() !== "local") {
3740
+ throw new ParamError(
3741
+ `Db: dbName not set for env="${env}" (set --dbName or DB_NAME_${String(env).toUpperCase()})`
3742
+ );
3743
+ }
3734
3744
  dbName = "local";
3735
3745
  }
3736
3746
  const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;
@@ -5433,6 +5443,8 @@ function servicePaths(service) {
5433
5443
  current: (0, import_node_path.join)(root, "current"),
5434
5444
  sharedEnv: (0, import_node_path.join)(root, "shared", ".env"),
5435
5445
  ecosystem: (0, import_node_path.join)(root, "shared", "ecosystem.config.cjs"),
5446
+ /** Preloaded by pm2 `node_args --require` so ENV survives flaky restarts. */
5447
+ ensureEnv: (0, import_node_path.join)(root, "shared", "ensure-env.cjs"),
5436
5448
  deployLog: (0, import_node_path.join)(root, "logs", "deploy.log"),
5437
5449
  lockHashFile: (0, import_node_path.join)(root, "shared", ".package-lock.sha256")
5438
5450
  };
@@ -5822,7 +5834,10 @@ async function reloadPm2(paths, options = {}) {
5822
5834
  logger.info(`pm2 ${appName}: reloading (old pid=${oldPid})`);
5823
5835
  }
5824
5836
  }
5825
- await runShell(`pm2 startOrReload "${paths.ecosystem}" --update-env`, { logger });
5837
+ await runShell(
5838
+ `ENV=production NODE_ENV=production pm2 startOrReload "${paths.ecosystem}" --update-env`,
5839
+ { logger }
5840
+ );
5826
5841
  logger.info("pm2 reloaded");
5827
5842
  if (appName) {
5828
5843
  const proc = await waitPm2(
@@ -5871,7 +5886,10 @@ async function startPm2(paths, options = {}) {
5871
5886
  logger.info(`[dryRun] would pm2 start ${paths.ecosystem} --update-env`);
5872
5887
  return;
5873
5888
  }
5874
- await runShell(`pm2 start "${paths.ecosystem}" --update-env`, { logger });
5889
+ await runShell(
5890
+ `ENV=production NODE_ENV=production pm2 start "${paths.ecosystem}" --update-env`,
5891
+ { logger }
5892
+ );
5875
5893
  logger.info("pm2 started");
5876
5894
  if (appName) {
5877
5895
  const proc = await waitPm2(
@@ -6123,12 +6141,24 @@ function resolvePm2Args(pm2) {
6123
6141
  if (/(?:^|\s)--stopAllowance=/.test(base)) return base;
6124
6142
  return `${base}${base ? " " : ""}--stopAllowance=${Math.floor(stopSec)}`.trim();
6125
6143
  }
6144
+ function buildEnsureEnvScript() {
6145
+ return `/**
6146
+ * Written by cli-toolkit deploy (init-structure). Do not hand-edit.
6147
+ * Preloaded via pm2 node_args --require. Always set (do not only fill when unset):
6148
+ * \`pm2 startOrReload --update-env\` can inject a non-production ENV from the
6149
+ * deploy shell / prior dump, and a conditional assign would leave it in place.
6150
+ */
6151
+ process.env.ENV = "production";
6152
+ process.env.NODE_ENV = "production";
6153
+ `;
6154
+ }
6126
6155
  function buildEcosystemConfig(service, paths) {
6127
6156
  const { pm2 } = service;
6128
6157
  const outLog = (0, import_node_path8.join)(paths.logs, `${pm2.appName}.out.log`);
6129
6158
  const errLog = (0, import_node_path8.join)(paths.logs, `${pm2.appName}.err.log`);
6130
6159
  const killTimeoutMs = resolveKillTimeoutMs(pm2);
6131
6160
  const args = resolvePm2Args(pm2);
6161
+ const ensureEnv = paths.ensureEnv;
6132
6162
  return `/**
6133
6163
  * pm2 ecosystem for ${service.name} \u2014 written by cli-toolkit deploy from the
6134
6164
  * service manifest (init/deploy). Do not hand-edit; change pm2.* in the
@@ -6141,6 +6171,8 @@ module.exports = {
6141
6171
  script: "${pm2.script}",
6142
6172
  cwd: "${paths.current}",
6143
6173
  args: ${JSON.stringify(args)},
6174
+ // Absolute --require so ENV is set before Args constructs (see shared/ensure-env.cjs).
6175
+ node_args: ${JSON.stringify(`--require ${ensureEnv}`)},
6144
6176
  instances: 1,
6145
6177
  exec_mode: "fork",
6146
6178
  autorestart: true,
@@ -6182,9 +6214,17 @@ async function initServiceStructure(service, options = {}) {
6182
6214
  created.push(dir);
6183
6215
  }
6184
6216
  const ecosystemExisted = await pathExists4(paths.ecosystem);
6217
+ const ensureEnvExisted = await pathExists4(paths.ensureEnv);
6185
6218
  if (!dryRun) {
6219
+ await (0, import_promises11.writeFile)(paths.ensureEnv, buildEnsureEnvScript(), { mode: 420 });
6186
6220
  await (0, import_promises11.writeFile)(paths.ecosystem, buildEcosystemConfig(service, paths), { mode: 420 });
6187
6221
  }
6222
+ if (ensureEnvExisted) {
6223
+ logger.info(`synced ${paths.ensureEnv}`);
6224
+ } else {
6225
+ created.push(paths.ensureEnv);
6226
+ logger.info(`seeded ${paths.ensureEnv}`);
6227
+ }
6188
6228
  if (ecosystemExisted) {
6189
6229
  logger.info(`synced ${paths.ecosystem} from manifest`);
6190
6230
  } else {