Greenlens/server/scripts/rebuild-from-batches.js

124 lines
3.5 KiB
JavaScript

#!/usr/bin/env node
/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');
const vm = require('vm');
require('dotenv').config();
const { closeDatabase, openDatabase } = require('../lib/sqlite');
const { ensurePlantSchema, rebuildPlantsCatalog } = require('../lib/plants');
let ts;
try {
ts = require('typescript');
} catch (error) {
console.error('The rebuild script needs the "typescript" package in node_modules.');
console.error('Install dependencies in the repository root before running this script.');
process.exit(1);
}
const ROOT_DIR = path.resolve(__dirname, '..', '..');
const BATCH_1_PATH = path.join(ROOT_DIR, 'constants', 'lexiconBatch1.ts');
const BATCH_2_PATH = path.join(ROOT_DIR, 'constants', 'lexiconBatch2.ts');
const resolveTsFilePath = (fromFile, specifier) => {
if (!specifier.startsWith('.')) return null;
const fromDirectory = path.dirname(fromFile);
const absoluteBase = path.resolve(fromDirectory, specifier);
const candidates = [
absoluteBase,
`${absoluteBase}.ts`,
`${absoluteBase}.tsx`,
path.join(absoluteBase, 'index.ts'),
];
for (const candidate of candidates) {
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
return candidate;
}
}
return null;
};
const loadTsModule = (absolutePath, cache = new Map()) => {
if (cache.has(absolutePath)) return cache.get(absolutePath);
const source = fs.readFileSync(absolutePath, 'utf8');
const transpiled = ts.transpileModule(source, {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2020,
esModuleInterop: true,
jsx: ts.JsxEmit.ReactJSX,
},
fileName: absolutePath,
reportDiagnostics: false,
}).outputText;
const module = { exports: {} };
cache.set(absolutePath, module.exports);
const localRequire = (specifier) => {
const resolvedTsPath = resolveTsFilePath(absolutePath, specifier);
if (resolvedTsPath) {
return loadTsModule(resolvedTsPath, cache);
}
return require(specifier);
};
const sandbox = {
module,
exports: module.exports,
require: localRequire,
__dirname: path.dirname(absolutePath),
__filename: absolutePath,
console,
process,
Buffer,
setTimeout,
clearTimeout,
};
vm.runInNewContext(transpiled, sandbox, { filename: absolutePath });
cache.set(absolutePath, module.exports);
return module.exports;
};
const loadBatchEntries = () => {
const batch1Module = loadTsModule(BATCH_1_PATH);
const batch2Module = loadTsModule(BATCH_2_PATH);
const batch1Entries = batch1Module.LEXICON_BATCH_1_ENTRIES;
const batch2Entries = batch2Module.LEXICON_BATCH_2_ENTRIES;
if (!Array.isArray(batch1Entries) || !Array.isArray(batch2Entries)) {
throw new Error('Could not load lexicon batch entries from TypeScript constants.');
}
return [...batch1Entries, ...batch2Entries];
};
const main = async () => {
const entries = loadBatchEntries();
const db = await openDatabase();
try {
await ensurePlantSchema(db);
const summary = await rebuildPlantsCatalog(db, entries, {
source: 'local_batch_script',
preserveExistingIds: true,
enforceUniqueImages: true,
});
console.log('Rebuild finished successfully.');
console.log(JSON.stringify(summary, null, 2));
} finally {
await closeDatabase(db);
}
};
main().catch((error) => {
console.error('Failed to rebuild plants from local batches.');
console.error(error instanceof Error ? error.stack || error.message : String(error));
process.exit(1);
});