File size: 3,322 Bytes
69f9a4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { spawn } from "node:child_process"
import { mkdir, mkdtemp, readFile, stat, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import path from "node:path"
import { FIXTURES, isolatedEnv, smoke } from "./predeploy-smoke"
import { dockerGate } from "./predeploy-docker"

async function main() {
  const logs = await mkdtemp(path.join(tmpdir(), "nodepad-predeploy-logs-"))
  const results: Record<string, unknown> = {}
  const env = isolatedEnv()
  let failed = false
  const safe = (text: string) => Object.values(FIXTURES).reduce((value, secret) => value.split(secret).join("[FIXTURE_REDACTED]"), text)
  async function run(group: string, command: string, args: string[]) {
    const child = spawn(command, args, { env, stdio: ["ignore", "pipe", "pipe"] })
    let output = ""
    child.stdout.on("data", value => { output += value }); child.stderr.on("data", value => { output += value })
    const code = await new Promise<number>((resolve, reject) => { child.once("error", reject); child.once("exit", code => resolve(code ?? 1)) })
    const file = path.join(logs, `${group}.log`)
    await writeFile(file, safe(output))
    const count = (name: string) => Number(output.match(new RegExp(`(?:ℹ |# )${name} (\\d+)`))?.[1] || 0)
    results[group] = { result: code ? "FAIL" : "PASS", passed: count("pass") || (code ? 0 : 1), failed: count("fail") || (code ? 1 : 0), skipped: count("skipped"), log: file }
    console.log(`${code ? "FAIL" : "PASS"} ${group}${count("tests") ? `: ${count("tests")} tests` : ""}`)
    if (code) { failed = true; console.error(safe(output).slice(-6000)) }
    return code === 0
  }
  try {
    await Promise.all([run("Lint", "npm", ["run", "lint"]), run("Types", "node", ["node_modules/typescript/bin/tsc", "--noEmit", "--incremental", "false"])])
    await Promise.all([run("Unit", "npm", ["run", "test:predeploy:unit"]), run("Registry", "npm", ["run", "test:registry"]), run("Fixture", "npm", ["run", "validate:fixture"])])
    if (failed) throw new Error("Code quality or unit gate failed")
    if (!await run("Build", "npm", ["run", "build"])) throw new Error("Production build failed")
    for (const file of [".next/standalone/server.js", ".next/static", "public"]) await stat(file)
    results.Artifacts = { result: "PASS", passed: 3, failed: 0, skipped: 0 }
    Object.assign(results, await smoke())
    results.Docker = await dockerGate()
  } catch (error) {
    failed = true
    console.error(safe(error instanceof Error ? error.message : "Unknown predeploy failure"))
    if (error && typeof error === "object" && "groups" in error) Object.assign(results, error.groups)
    results.GateFailure = { result: "FAIL", failed: 1 }
  }
  const verdict = failed ? "RED — DO NOT REDEPLOY" : (results.Docker as { result: string })?.result === "SKIPPED_ENV_DOCKER" ? "YELLOW — INTERNAL TESTS PASS EXCEPT EXPLICIT ENVIRONMENT-ONLY CHECKS" : "GREEN — READY TO REDEPLOY"
  const report = { generatedAt: new Date().toISOString(), verdict, results }
  await writeFile("/tmp/nodepad-predeploy-report.json", JSON.stringify(report, null, 2))
  console.log(verdict)
  console.log("Report: /tmp/nodepad-predeploy-report.json")
  if (failed) process.exitCode = 1
}
main().catch(() => { console.error("RED — DO NOT REDEPLOY: runner failed"); process.exitCode = 1 })