Files
memind/scripts/goosed-harness-mysql2-cli.mjs
T
john afeafa1d57 fix(goosed-ops): ship mysql2 CLI shim for harness memory on native pool
The 103 harness shim imported mysql2 from a Docker-only path, so every
harness_remember failed on the native pool. The shim resolves mysql2 from
the Portal runtime (or MEMIND_MYSQL2_MODULE) and ships in the runtime.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 14:25:24 +08:00

85 lines
3.1 KiB
JavaScript

#!/usr/bin/env node
// mysql-CLI-compatible shim for harness (CODEX_HARNESS_MYSQL) on hosts without a mysql client.
// Supports the subset harness uses: -h HOST -u USER [-N] [--flags] [DATABASE] [-e SQL | stdin].
import fs from 'node:fs';
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
export function parseArgs(argv) {
const opts = { host: '', user: '', database: '', sql: '', noHeaders: false };
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === '-h' && i + 1 < argv.length) {
opts.host = argv[++i];
} else if (arg === '-u' && i + 1 < argv.length) {
opts.user = argv[++i];
} else if (arg === '-N') {
opts.noHeaders = true;
} else if (arg === '-e' && i + 1 < argv.length) {
opts.sql = argv[++i];
} else if (!arg.startsWith('-') && !opts.database) {
opts.database = arg;
}
}
return opts;
}
export function resolveMysql2Module(env = process.env, requireFrom = import.meta.url) {
const explicit = String(env.MEMIND_MYSQL2_MODULE || '').trim();
if (explicit) return explicit;
return createRequire(requireFrom).resolve('mysql2/promise');
}
// With rowsAsArray, a row set is an array of rows and each row is an array of scalar cells.
// Multi-statement results are an array of row sets and ResultSetHeader objects.
const isRow = (value) => Array.isArray(value) && value.every((cell) => !Array.isArray(cell));
const isRowSet = (value) => Array.isArray(value) && value.every(isRow);
export function findLastRowSet(result) {
if (isRowSet(result)) return result;
if (!Array.isArray(result)) return null;
for (let i = result.length - 1; i >= 0; i -= 1) {
if (isRowSet(result[i])) return result[i];
}
return null;
}
export function formatRows(rows) {
if (!rows || rows.length === 0) return '';
const lines = rows.map((row) => {
const values = Array.isArray(row) ? row : Object.values(row);
return values.map((value) => (value == null ? 'NULL' : String(value))).join('\t');
});
return `${lines.join('\n')}\n`;
}
async function main() {
const opts = parseArgs(process.argv.slice(2));
const sql = opts.sql || fs.readFileSync(0, 'utf8');
const modulePath = resolveMysql2Module();
const mysqlModule = await import(pathToFileURL(modulePath).href);
const mysql = mysqlModule.default ?? mysqlModule;
const connection = await mysql.createConnection({
host: opts.host || process.env.CODEX_HARNESS_DB_HOST || '127.0.0.1',
user: opts.user || process.env.CODEX_HARNESS_DB_USER || 'boot',
password: process.env.MYSQL_PWD || process.env.CODEX_HARNESS_DB_PASSWORD || '',
database: opts.database || undefined,
charset: 'utf8mb4',
multipleStatements: true,
rowsAsArray: true,
});
try {
const [result] = await connection.query({ sql, rowsAsArray: true });
process.stdout.write(formatRows(findLastRowSet(result)));
} finally {
await connection.end();
}
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
main().catch((error) => {
process.stderr.write(`${error?.code ? `${error.code}: ` : ''}${error?.message ?? error}\n`);
process.exit(1);
});
}