import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { get } from "node:https"; import { dirname, resolve } from "node:path"; import { spawn, spawnSync } from "node:child_process"; const adminerFile = resolve("tools/adminer/adminer.php"); const localPhp = resolve("tools/php/php.exe"); const host = process.env.ADMINER_HOST || "127.0.0.1"; const port = process.env.ADMINER_PORT || "8080"; const adminerDownloadUrl = process.env.ADMINER_DOWNLOAD_URL || "https://www.adminer.org/latest.php"; const phpCommand = existsSync(localPhp) ? localPhp : "php"; const usesLocalPhp = existsSync(localPhp); if (!commandExists(phpCommand)) { console.error("Adminer needs PHP CLI. Install PHP first, then run npm run adminer again."); process.exit(1); } if (usesLocalPhp) { ensureLocalPhpMysqlExtensions(); } if (!existsSync(adminerFile)) { mkdirSync(dirname(adminerFile), { recursive: true }); await downloadFile(adminerDownloadUrl, adminerFile); } const child = spawn(phpCommand, ["-S", `${host}:${port}`, adminerFile], { stdio: "inherit", shell: true, }); child.on("exit", (code, signal) => { if (signal) { process.kill(process.pid, signal); return; } process.exit(code ?? 0); }); function commandExists(command: string): boolean { const result = spawnSync(command, ["-v"], { stdio: "ignore", shell: true }); return result.status === 0; } function ensureLocalPhpMysqlExtensions(): void { const phpDir = dirname(localPhp); const phpIni = resolve(phpDir, "php.ini"); const template = resolve(phpDir, "php.ini-production"); const source = existsSync(phpIni) ? phpIni : template; if (!existsSync(source)) { console.warn("Local PHP is missing php.ini-production; Adminer may not have MariaDB extensions enabled."); return; } let contents = readFileSync(source, "utf8"); contents = ensureIniLine(contents, /^;?\s*extension_dir\s*=.*$/m, 'extension_dir = "ext"'); contents = ensureIniLine(contents, /^;?\s*extension\s*=\s*mysqli\s*$/m, "extension=mysqli"); contents = ensureIniLine(contents, /^;?\s*extension\s*=\s*pdo_mysql\s*$/m, "extension=pdo_mysql"); writeFileSync(phpIni, contents); } function ensureIniLine(contents: string, pattern: RegExp, line: string): string { if (pattern.test(contents)) return contents.replace(pattern, line); return `${contents.trimEnd()}\n${line}\n`; } function downloadFile(url: string, target: string): Promise { return new Promise((resolveDownload, rejectDownload) => { const request = get(url, (response) => { if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) { downloadFile(response.headers.location, target).then(resolveDownload, rejectDownload); return; } if (response.statusCode !== 200) { rejectDownload(new Error(`Adminer download failed with HTTP ${response.statusCode}`)); return; } const file = createWriteStream(target); response.pipe(file); file.on("finish", () => { file.close(); resolveDownload(); }); file.on("error", rejectDownload); }); request.on("error", rejectDownload); }); }