import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises' import path from 'node:path' import AdmZip from 'adm-zip' import { downloadFile, readJson, sha256File, writeJson } from './lib.js' const manifestPath = process.argv[2] if (!manifestPath) { console.error('Usage: node scripts/import-strongs.js sources/.json') process.exit(1) } const root = process.cwd() const manifest = await readJson(path.resolve(root, manifestPath)) const archivePath = path.join(root, 'cache', manifest.id, path.basename(manifest.source.download_url)) const extractDir = path.join(root, 'cache', manifest.id, 'source') const packageDir = path.join(root, 'packages', 'json', manifest.id) await downloadFile(manifest.source.download_url, archivePath) const sourceSha = await sha256File(archivePath) if (sourceSha !== manifest.checks.expected_sha256.toLowerCase()) { throw new Error(`${manifest.id}: source checksum mismatch. Expected ${manifest.checks.expected_sha256}, got ${sourceSha}`) } await rm(extractDir, { force: true, recursive: true }) await mkdir(extractDir, { recursive: true }) new AdmZip(archivePath).extractAllTo(extractDir, true) const sourceRoot = await findExtractedRoot(extractDir) const greek = parseDictionary( await readFile(path.join(sourceRoot, 'greek', 'strongs-greek-dictionary.js'), 'utf8'), 'strongsGreekDictionary', 'greek', ) const hebrew = parseDictionary( await readFile(path.join(sourceRoot, 'hebrew', 'strongs-hebrew-dictionary.js'), 'utf8'), 'strongsHebrewDictionary', 'hebrew', ) const entries = [...hebrew, ...greek].sort((a, b) => { if (a.system !== b.system) return a.system.localeCompare(b.system) return a.number_value - b.number_value }) await mkdir(packageDir, { recursive: true }) await writeJsonl(path.join(packageDir, 'hebrew.jsonl'), hebrew) await writeJsonl(path.join(packageDir, 'greek.jsonl'), greek) await writeJsonl(path.join(packageDir, 'entries.jsonl'), entries) const files = { entries_jsonl: await fileInfo(path.join(packageDir, 'entries.jsonl')), hebrew_jsonl: await fileInfo(path.join(packageDir, 'hebrew.jsonl')), greek_jsonl: await fileInfo(path.join(packageDir, 'greek.jsonl')), } await writeJson(path.join(packageDir, 'catalog.json'), { schema_version: 'librebible.resource-catalog.v1', project: manifest.project_name ?? 'LibreBible', id: manifest.id, resource_type: manifest.resource_type, title: manifest.title, short_title: manifest.short_title, abbreviation: manifest.abbreviation, alternate_ids: manifest.alternate_ids ?? [], language: manifest.language, script: manifest.script, canon: manifest.canon, contributors: manifest.contributors ?? [], features: manifest.features ?? [], attachments: manifest.attachments ?? {}, source: manifest.source, license: manifest.license, catalog_display: manifest.catalog_display, importer: manifest.importer, source_manifest_file: path.basename(manifestPath), generated_at: new Date().toISOString(), source_sha256: sourceSha, checks: manifest.checks, counts: { entries: entries.length, dictionary_entries: entries.length, hebrew_entries: hebrew.length, greek_entries: greek.length, }, files, }) console.log(`${manifest.id}: imported ${entries.length} Strong's dictionary entries`) async function findExtractedRoot(dir) { const entries = await readdir(dir, { withFileTypes: true }) const dirs = entries.filter((entry) => entry.isDirectory()) if (dirs.length !== 1) return dir return path.join(dir, dirs[0].name) } function parseDictionary(source, variableName, language) { const match = new RegExp(`var\\s+${variableName}\\s*=\\s*(\\{[\\s\\S]*?\\})\\s*;\\s*(?:module\\.exports\\s*=\\s*${variableName}\\s*;)?\\s*$`).exec(source) if (!match) throw new Error(`Could not find ${variableName}`) const data = JSON.parse(match[1]) return Object.entries(data).map(([number, entry]) => ({ id: number, strongs_number: number, system: number.startsWith('H') ? 'hebrew' : 'greek', language, number_value: Number(number.slice(1)), lemma: entry.lemma ?? null, transliteration: entry.translit ?? entry.xlit ?? null, pronunciation: entry.pron ?? null, derivation: cleanText(entry.derivation), strongs_definition: cleanText(entry.strongs_def), kjv_definition: cleanText(entry.kjv_def), })) } function cleanText(value) { if (value == null) return null return String(value).replace(/\s+/g, ' ').trim() } async function writeJsonl(filePath, rows) { await writeFile(filePath, `${rows.map((row) => JSON.stringify(row)).join('\n')}\n`, 'utf8') } async function fileInfo(filePath) { const data = await readFile(filePath) return { path: path.basename(filePath), bytes: data.length, sha256: await sha256File(filePath), } }