Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export const MIGRATE_STATUS_COMMAND = Object.freeze({
|
||||
command: "pnpm",
|
||||
args: ["--filter", "@toolctrl/db", "prisma:migrate:status"],
|
||||
});
|
||||
|
||||
const MAX_EXCERPT_LENGTH = 1200;
|
||||
const ALLOW_UNREACHABLE_ENV = "DB_MIGRATE_STATUS_ALLOW_UNREACHABLE";
|
||||
const DATABASE_URL_ASSIGNMENT_PATTERN = /\bDATABASE_URL\s*=\s*([^\s'\"]+|'[^']*'|\"[^\"]*\")/g;
|
||||
const DATABASE_URL_JSON_PATTERN = /("DATABASE_URL"\s*:\s*")([^"]+)(")/g;
|
||||
const DATABASE_URL_TEXT_PATTERN = /(DATABASE_URL(?:\s+(?:is|was|value|set to|configured as))?[:=]?\s+)([^\s]+)/gi;
|
||||
const URL_WITH_CREDENTIALS_PATTERN = /\b([a-z][a-z0-9+.-]*:\/\/)([^\s/@:]+)(?::([^\s/@]*))?@/gi;
|
||||
const UNREACHABLE_DATABASE_PATTERN = /\bP1001\b|Can't reach database server/i;
|
||||
|
||||
export function buildMigrateStatusCommand() {
|
||||
return {
|
||||
command: MIGRATE_STATUS_COMMAND.command,
|
||||
args: [...MIGRATE_STATUS_COMMAND.args],
|
||||
display: [MIGRATE_STATUS_COMMAND.command, ...MIGRATE_STATUS_COMMAND.args].join(" "),
|
||||
};
|
||||
}
|
||||
|
||||
function redactUrlCredentials(value) {
|
||||
return value.replace(URL_WITH_CREDENTIALS_PATTERN, (_match, protocol) => `${protocol}<redacted>@`);
|
||||
}
|
||||
|
||||
export function redactSecrets(value, env = {}) {
|
||||
let redacted = String(value ?? "");
|
||||
const databaseUrl = env.DATABASE_URL;
|
||||
|
||||
if (databaseUrl) {
|
||||
redacted = redacted.split(databaseUrl).join("<redacted:DATABASE_URL>");
|
||||
}
|
||||
|
||||
redacted = redacted
|
||||
.replace(DATABASE_URL_JSON_PATTERN, `$1<redacted:DATABASE_URL>$3`)
|
||||
.replace(DATABASE_URL_ASSIGNMENT_PATTERN, "DATABASE_URL=<redacted:DATABASE_URL>")
|
||||
.replace(DATABASE_URL_TEXT_PATTERN, (_match, prefix) => `${prefix}<redacted:DATABASE_URL>`);
|
||||
|
||||
return redactUrlCredentials(redacted);
|
||||
}
|
||||
|
||||
export function createOutputExcerpt(value, env = {}, maxLength = MAX_EXCERPT_LENGTH) {
|
||||
const sanitized = redactSecrets(value, env).trim();
|
||||
|
||||
if (!sanitized) {
|
||||
return "<empty>";
|
||||
}
|
||||
|
||||
if (sanitized.length <= maxLength) {
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
return `${sanitized.slice(0, maxLength)}… <truncated ${sanitized.length - maxLength} chars>`;
|
||||
}
|
||||
|
||||
export function formatMigrateStatusResult(result) {
|
||||
const status = result.ok ? "PASS" : "FAIL";
|
||||
const lines = [
|
||||
`${status} database migrate status verification`,
|
||||
`phase: ${result.phase}`,
|
||||
`command: ${result.commandDisplay}`,
|
||||
`exitCode: ${result.exitCode ?? "n/a"}`,
|
||||
`status: ${result.status}`,
|
||||
`stdout: ${result.stdoutExcerpt}`,
|
||||
`stderr: ${result.stderrExcerpt}`,
|
||||
];
|
||||
|
||||
if (result.errorMessage) {
|
||||
lines.push(`error: ${result.errorMessage}`);
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function allowsUnreachableDatabase(env = {}) {
|
||||
return env[ALLOW_UNREACHABLE_ENV] === "1" || env[ALLOW_UNREACHABLE_ENV] === "true";
|
||||
}
|
||||
|
||||
function isUnreachableDatabaseResult({ stdout = "", stderr = "" }) {
|
||||
return UNREACHABLE_DATABASE_PATTERN.test(`${stdout}\n${stderr}`);
|
||||
}
|
||||
|
||||
function runSpawn({ command, args, env, cwd, spawnImpl }) {
|
||||
return new Promise((resolve) => {
|
||||
let child;
|
||||
|
||||
try {
|
||||
child = spawnImpl(command, args, {
|
||||
cwd,
|
||||
env: { ...env, CI: "1" },
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
} catch (error) {
|
||||
resolve({ exitCode: null, stdout: "", stderr: "", error });
|
||||
return;
|
||||
}
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
let settled = false;
|
||||
|
||||
const settle = (payload) => {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
resolve(payload);
|
||||
}
|
||||
};
|
||||
|
||||
child.stdout?.on?.("data", (chunk) => {
|
||||
stdout += chunk.toString();
|
||||
});
|
||||
child.stderr?.on?.("data", (chunk) => {
|
||||
stderr += chunk.toString();
|
||||
});
|
||||
child.on?.("error", (error) => {
|
||||
settle({ exitCode: null, stdout, stderr, error });
|
||||
});
|
||||
child.on?.("close", (exitCode) => {
|
||||
settle({ exitCode, stdout, stderr, error: null });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyDbMigrateStatus({ env = process.env, spawnImpl = spawn, cwd = process.cwd() } = {}) {
|
||||
const command = buildMigrateStatusCommand();
|
||||
|
||||
if (!env.DATABASE_URL) {
|
||||
return {
|
||||
ok: false,
|
||||
phase: "preflight",
|
||||
commandDisplay: command.display,
|
||||
exitCode: null,
|
||||
status: "missing DATABASE_URL",
|
||||
stdoutExcerpt: "<empty>",
|
||||
stderrExcerpt: "DATABASE_URL is required for Prisma migrate status and was not provided.",
|
||||
};
|
||||
}
|
||||
|
||||
const spawnResult = await runSpawn({
|
||||
command: command.command,
|
||||
args: command.args,
|
||||
env,
|
||||
cwd,
|
||||
spawnImpl,
|
||||
});
|
||||
|
||||
if (spawnResult.error) {
|
||||
return {
|
||||
ok: false,
|
||||
phase: "spawn",
|
||||
commandDisplay: command.display,
|
||||
exitCode: spawnResult.exitCode,
|
||||
status: "spawn error",
|
||||
stdoutExcerpt: createOutputExcerpt(spawnResult.stdout, env),
|
||||
stderrExcerpt: createOutputExcerpt(spawnResult.stderr, env),
|
||||
errorMessage: createOutputExcerpt(spawnResult.error.message, env),
|
||||
};
|
||||
}
|
||||
|
||||
const unreachableDatabaseAllowed =
|
||||
spawnResult.exitCode !== 0 && allowsUnreachableDatabase(env) && isUnreachableDatabaseResult(spawnResult);
|
||||
const ok = spawnResult.exitCode === 0 || unreachableDatabaseAllowed;
|
||||
return {
|
||||
ok,
|
||||
phase: "prisma:migrate:status",
|
||||
commandDisplay: command.display,
|
||||
exitCode: spawnResult.exitCode,
|
||||
status: unreachableDatabaseAllowed
|
||||
? `database unreachable; skipped by ${ALLOW_UNREACHABLE_ENV}`
|
||||
: ok
|
||||
? "migrations status clean"
|
||||
: "prisma migrate status failed",
|
||||
stdoutExcerpt: createOutputExcerpt(spawnResult.stdout, env),
|
||||
stderrExcerpt: createOutputExcerpt(spawnResult.stderr, env),
|
||||
};
|
||||
}
|
||||
|
||||
const isCli = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
|
||||
|
||||
if (isCli) {
|
||||
const result = await verifyDbMigrateStatus();
|
||||
const output = formatMigrateStatusResult(result);
|
||||
|
||||
if (result.ok) {
|
||||
process.stdout.write(output);
|
||||
} else {
|
||||
process.stderr.write(output);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user