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>
This commit is contained in:
@@ -463,6 +463,11 @@ async function writeMetadata() {
|
||||
path.join(root, 'scripts', 'goosed-canary.compose.yml'),
|
||||
path.join(runtimeScriptsDir, 'goosed-canary.compose.yml'),
|
||||
);
|
||||
await fs.copyFile(
|
||||
path.join(root, 'scripts', 'goosed-harness-mysql2-cli.mjs'),
|
||||
path.join(runtimeScriptsDir, 'goosed-harness-mysql2-cli.mjs'),
|
||||
);
|
||||
await fs.chmod(path.join(runtimeScriptsDir, 'goosed-harness-mysql2-cli.mjs'), 0o755);
|
||||
|
||||
await fs.copyFile(
|
||||
path.join(root, 'scripts', 'load-env.mjs'),
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/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);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'node:test';
|
||||
import {
|
||||
findLastRowSet,
|
||||
formatRows,
|
||||
parseArgs,
|
||||
resolveMysql2Module,
|
||||
} from './goosed-harness-mysql2-cli.mjs';
|
||||
|
||||
test('parseArgs reads the mysql CLI subset harness uses', () => {
|
||||
assert.deepEqual(
|
||||
parseArgs(['-h', 'db.local', '-u', 'boot', '--default-character-set=utf8mb4', '--batch', '-N', 'codex_harness', '-e', 'SELECT 1']),
|
||||
{ host: 'db.local', user: 'boot', database: 'codex_harness', sql: 'SELECT 1', noHeaders: true },
|
||||
);
|
||||
});
|
||||
|
||||
test('resolveMysql2Module resolves from the Memind node_modules, not /opt/portal', () => {
|
||||
const resolved = resolveMysql2Module({});
|
||||
assert.match(resolved, /node_modules[\\/]mysql2[\\/]promise\.js$/);
|
||||
assert.doesNotMatch(resolved, /^\/opt\/portal/);
|
||||
});
|
||||
|
||||
test('resolveMysql2Module honours MEMIND_MYSQL2_MODULE override', () => {
|
||||
assert.equal(resolveMysql2Module({ MEMIND_MYSQL2_MODULE: '/x/promise.js' }), '/x/promise.js');
|
||||
});
|
||||
|
||||
test('findLastRowSet returns a single SELECT row set as-is', () => {
|
||||
const rows = [['1', 'a'], ['2', null]];
|
||||
assert.equal(findLastRowSet(rows), rows);
|
||||
assert.equal(formatRows(findLastRowSet(rows)), '1\ta\n2\tNULL\n');
|
||||
});
|
||||
|
||||
test('findLastRowSet skips ResultSetHeader in multi-statement results', () => {
|
||||
const header = { affectedRows: 1, insertId: 7 };
|
||||
const rows = [['7']];
|
||||
assert.equal(findLastRowSet([header, rows]), rows);
|
||||
assert.equal(formatRows(findLastRowSet([header, rows])), '7\n');
|
||||
});
|
||||
|
||||
test('findLastRowSet returns null for DDL-only results', () => {
|
||||
assert.equal(findLastRowSet({ affectedRows: 0 }), null);
|
||||
assert.equal(formatRows(findLastRowSet({ affectedRows: 0 })), '');
|
||||
});
|
||||
Reference in New Issue
Block a user