mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-04 07:27:20 +00:00
The live ARM64 CDN artifact was rebuilt with a symbol absent from core 0.21.1. Verify the packaged artifact through the runtime path resolver instead, retaining a mandatory native LOAD gate without a download. ARM64 emulation reproduces the CDN failure; the bundled artifact loads, builds an FTS index, and answers a query. 47 focused tests and both TypeScript checks pass. Full local suite is still running. Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
135 lines
5.6 KiB
JavaScript
135 lines
5.6 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { createRequire } from 'node:module';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
const EXTENSION_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
|
|
// Positive on-disk-corruption signatures. `FORCE INSTALL` re-downloads even when
|
|
// a file is already present; we only want that when the LOAD error proves the
|
|
// existing file is bad (truncated/wrong-platform, #2374). For everything else —
|
|
// a missing file (plain INSTALL downloads it), or a permanent non-file failure a
|
|
// re-download can never fix (missing runtime dep: "cannot open shared object") —
|
|
// plain INSTALL avoids re-downloading ~2 MB on every analyze run forever.
|
|
// Exported so a parity test keeps this byte-identical to the copy in
|
|
// src/core/lbug/extension-load-error.ts (this `.mjs` cannot import that `.ts`), #2383 F5b.
|
|
export const FILE_CORRUPTION_SIGNATURES = [
|
|
/invalid elf/i,
|
|
/file too short/i,
|
|
/not a valid/i,
|
|
/bad magic/i,
|
|
/wrong architecture/i,
|
|
/mach-o/i,
|
|
/truncat/i,
|
|
];
|
|
|
|
/**
|
|
* Decide the install verb from the LOAD error that triggered this install.
|
|
* `FORCE INSTALL` only when the error positively indicates file-level breakage;
|
|
* otherwise plain `INSTALL` (missing file, missing-dependency dlopen failure,
|
|
* or unknown/absent error).
|
|
*/
|
|
export function chooseInstallVerb(loadError) {
|
|
if (loadError && FILE_CORRUPTION_SIGNATURES.some((re) => re.test(loadError))) {
|
|
return 'FORCE INSTALL';
|
|
}
|
|
return 'INSTALL';
|
|
}
|
|
|
|
function parseLbugMaxDbSize(raw) {
|
|
const parsed = raw ? Number(raw) : NaN;
|
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
throw new Error(`Invalid LadybugDB max DB size for extension installer: ${raw ?? '<missing>'}`);
|
|
}
|
|
return Math.floor(parsed);
|
|
}
|
|
|
|
function resolveMaxDbSize() {
|
|
// argv[3] is the optional positional size; ignore it when it is actually a
|
|
// flag token (e.g. `--verify-only`) and fall back to the env default.
|
|
const sizeArg =
|
|
process.argv[3] && !process.argv[3].startsWith('--') ? process.argv[3] : undefined;
|
|
return parseLbugMaxDbSize(sizeArg ?? process.env.GITNEXUS_LBUG_MAX_DB_SIZE);
|
|
}
|
|
|
|
/** Open a scratch LadybugDB and return its connection plus a disposer. */
|
|
async function defaultConnect(lbugMaxDbSize) {
|
|
const require = createRequire(import.meta.url);
|
|
const lbugModule = require('@ladybugdb/core');
|
|
const lbug = lbugModule.default ?? lbugModule;
|
|
|
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-ext-install-'));
|
|
const dbPath = path.join(tmpDir, 'install.lbug');
|
|
const db = new lbug.Database(dbPath, 0, false, false, lbugMaxDbSize);
|
|
const conn = new lbug.Connection(db);
|
|
return {
|
|
conn,
|
|
dispose: async () => {
|
|
await conn.close().catch(() => {});
|
|
await db.close().catch(() => {});
|
|
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Install (or verify) an optional LadybugDB extension in this short-lived process.
|
|
*
|
|
* @param {string} extensionName
|
|
* @param {object} [options]
|
|
* @param {boolean} [options.verifyOnly] LOAD-only Docker build gate — no install.
|
|
* @param {boolean} [options.verifyBundled] LOAD the packaged FTS artifact without a download.
|
|
* @param {string} [options.loadError] The parent's LOAD failure; selects the verb.
|
|
* @param {(size: number) => Promise<{conn: {query: (sql: string) => Promise<unknown>}, dispose: () => Promise<void>}>} [options.connect]
|
|
* Connection factory; injectable for offline unit tests.
|
|
*/
|
|
export async function installDuckDbExtension(extensionName, options = {}) {
|
|
const { verifyOnly = false, verifyBundled = false, loadError, connect } = options;
|
|
if (!extensionName || !EXTENSION_NAME_PATTERN.test(extensionName)) {
|
|
throw new Error(`Invalid DuckDB extension name: ${extensionName ?? '<missing>'}`);
|
|
}
|
|
|
|
let loadTarget = extensionName;
|
|
if (verifyBundled) {
|
|
if (extensionName !== 'fts') throw new Error('Bundled verification only supports fts');
|
|
const { resolveVendoredFtsPath } = await import('../dist/core/lbug/vendored-extension-path.js');
|
|
const bundled = resolveVendoredFtsPath();
|
|
if (!bundled)
|
|
throw new Error(`No packaged FTS artifact for ${process.platform}-${process.arch}`);
|
|
loadTarget = `'${bundled.replace(/\\/g, '/').replace(/'/g, "''")}'`;
|
|
}
|
|
|
|
const makeConnection = connect ?? (() => defaultConnect(resolveMaxDbSize()));
|
|
const { conn, dispose } = await makeConnection();
|
|
|
|
try {
|
|
if (verifyOnly || verifyBundled) {
|
|
// LOAD must succeed without INSTALL or a network fallback, whether the
|
|
// caller verifies the packaged path or a named extension under HOME.
|
|
await conn.query(`LOAD EXTENSION ${loadTarget}`);
|
|
console.log(
|
|
`[install-ext] LOAD-only verify OK for '${extensionName}' (HOME=${process.env.HOME})`,
|
|
);
|
|
} else {
|
|
// Plain INSTALL is a no-op when the file already exists; escalate to FORCE
|
|
// only when the LOAD error proves the on-disk file is broken (#2374).
|
|
await conn.query(`${chooseInstallVerb(loadError)} ${extensionName}`);
|
|
}
|
|
} finally {
|
|
await dispose();
|
|
}
|
|
}
|
|
|
|
// Only run when executed directly — imported (e.g. by unit tests) it stays inert.
|
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
installDuckDbExtension(process.argv[2] ?? process.env.GITNEXUS_LBUG_EXTENSION_NAME, {
|
|
verifyOnly: process.argv.includes('--verify-only'),
|
|
verifyBundled: process.argv.includes('--verify-bundled'),
|
|
loadError: process.env.GITNEXUS_LBUG_EXTENSION_LOAD_ERROR,
|
|
}).catch((err) => {
|
|
console.error(err instanceof Error ? (err.stack ?? err.message) : String(err));
|
|
process.exitCode = 1;
|
|
});
|
|
}
|