afeafa1d57
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>
85 lines
3.1 KiB
JavaScript
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);
|
|
});
|
|
}
|