fix: pin NIXPACKS_NODE_VERSION from the repo's .nvmrc/engines.node
Nixpacks silently falls back to its own default Node version when a repo doesn't match what it expects, instead of failing the build — confirmed in practice as a green build that crashed at runtime on a missing global. detectNodeVersion() reads the repo's own .nvmrc or package.json engines.node and passes it through as NIXPACKS_NODE_VERSION so the build actually uses what the repo declares. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,41 @@ export interface BuildResult {
|
||||
logPath: string;
|
||||
}
|
||||
|
||||
function parseMajorVersion(raw: string): string | undefined {
|
||||
const match = raw.match(/(\d+)/);
|
||||
return match ? match[1] : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nixpacks doesn't fail when a repo's declared runtime requirement isn't met — it silently
|
||||
* falls back to its own default version (seen in practice: Node 18 picked for a repo requiring
|
||||
* >=20, build green, container crashed at runtime on a missing global). Pinning
|
||||
* NIXPACKS_NODE_VERSION from the repo's own .nvmrc/package.json closes that gap for Node repos.
|
||||
*/
|
||||
export function detectNodeVersion(repoDir: string): string | undefined {
|
||||
const nvmrcPath = path.join(repoDir, ".nvmrc");
|
||||
if (fs.existsSync(nvmrcPath)) {
|
||||
const version = parseMajorVersion(fs.readFileSync(nvmrcPath, "utf8"));
|
||||
if (version) return version;
|
||||
}
|
||||
|
||||
const pkgPath = path.join(repoDir, "package.json");
|
||||
if (fs.existsSync(pkgPath)) {
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")) as { engines?: { node?: string } };
|
||||
const engineNode = pkg.engines?.node;
|
||||
if (typeof engineNode === "string") {
|
||||
const version = parseMajorVersion(engineNode);
|
||||
if (version) return version;
|
||||
}
|
||||
} catch {
|
||||
// malformed package.json — nothing to pin, let nixpacks use its default
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function runNixpacksBuild(slug: string, repoDir: string, imageTag: string): Promise<BuildResult> {
|
||||
return new Promise((resolve) => {
|
||||
const buildId = Date.now().toString();
|
||||
@@ -19,7 +54,16 @@ export function runNixpacksBuild(slug: string, repoDir: string, imageTag: string
|
||||
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
||||
const logStream = fs.createWriteStream(logPath, { flags: "a" });
|
||||
|
||||
const child = spawn("nixpacks", ["build", repoDir, "--name", imageTag]);
|
||||
const env = { ...process.env };
|
||||
const nodeVersion = detectNodeVersion(repoDir);
|
||||
if (nodeVersion) {
|
||||
env.NIXPACKS_NODE_VERSION = nodeVersion;
|
||||
logStream.write(
|
||||
`[remote-mcp-manager] pinning NIXPACKS_NODE_VERSION=${nodeVersion} (detected from .nvmrc/package.json engines.node)\n`,
|
||||
);
|
||||
}
|
||||
|
||||
const child = spawn("nixpacks", ["build", repoDir, "--name", imageTag], { env });
|
||||
child.stdout.pipe(logStream, { end: false });
|
||||
child.stderr.pipe(logStream, { end: false });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user