#!/usr/bin/env node /** * Build one Node zip per Lambda key. Used by deploy.yaml. * Each zip is functions//.zip on S3. GIT_SHA is written to * src/buildInfo.js inside the zip so a deploy is identifiable without a * Terraform-owned env var. */ import { spawn, spawnSync } from "node:child_process"; import { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const ROOT = fileURLToPath(new URL("..", import.meta.url)); const FUNCTIONS = [ "process_csv", "slack_app_home", "fetch_boa", "expense_receiver", "expense_processor", ]; function parseArgs(argv) { const out = { gitSha: "", outDir: join(ROOT, "build", "packages"), only: [] }; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; if (arg === "--git-sha") { out.gitSha = argv[++i]; } else if (arg === "--out-dir") { out.outDir = resolve(argv[++i]); } else if (arg === "--only") { out.only.push(argv[++i]); } else { throw new Error(`unknown argument: ${arg}`); } } if (!out.gitSha) { throw new Error("--git-sha is required"); } return out; } function zipDir(srcDir, zipPath) { return new Promise((resolve, reject) => { const child = spawn("zip", ["-qr", zipPath, "."], { cwd: srcDir, stdio: "inherit" }); child.on("exit", (code) => { if (code === 0) resolve(); else reject(new Error(`zip exited ${code}`)); }); }); } async function build(name, gitSha, outDir) { const dest = mkdtempSync(join(tmpdir(), `payments-${name}-`)); try { cpSync(join(ROOT, "src"), join(dest, "src"), { recursive: true }); cpSync(join(ROOT, "package.json"), join(dest, "package.json")); if (existsSync(join(ROOT, "package-lock.json"))) { cpSync(join(ROOT, "package-lock.json"), join(dest, "package-lock.json")); } writeFileSync( join(dest, "src", "buildInfo.js"), `export const GIT_SHA = ${JSON.stringify(gitSha)};\n`, "utf8", ); const npm = spawnSync("npm", ["ci", "--omit=dev"], { cwd: dest, stdio: "inherit" }); if (npm.status !== 0) { throw new Error("npm ci --omit=dev failed"); } mkdirSync(outDir, { recursive: true }); const zipPath = resolve(outDir, `${name}.zip`); rmSync(zipPath, { force: true }); await zipDir(dest, zipPath); return zipPath; } finally { rmSync(dest, { recursive: true, force: true }); } } const args = parseArgs(process.argv.slice(2)); const selected = args.only.length ? args.only : FUNCTIONS; const unknown = selected.filter((name) => !FUNCTIONS.includes(name)); if (unknown.length) { console.error(`unknown function keys: ${unknown.join(", ")}`); process.exit(2); } for (const name of selected) { const path = await build(name, args.gitSha, args.outDir); console.log(path); }