Tofu/scripts/import-recipes/importRecipes.js
2026-09-18 01:41:02 +02:00

79 lines
3.2 KiB
JavaScript

#!/usr/bin/env node
import { join } from 'node:path'
import { USAGE } from '#import-recipes/cli/USAGE.js'
import { buildRecipeIndex } from '#import-recipes/recipes/buildRecipeIndex.js'
import { createAnthropicClient } from '#import-recipes/extraction/createAnthropicClient.js'
import { formatImportSummary } from '#import-recipes/cli/formatImportSummary.js'
import { importOdtFiles } from '#import-recipes/cli/importOdtFiles.js'
import { listOdtFileNames } from '#import-recipes/cli/listOdtFileNames.js'
import { parseCliArguments } from '#import-recipes/cli/parseCliArguments.js'
import { previewOdtFiles } from '#import-recipes/cli/previewOdtFiles.js'
import { verifyApiAccess } from '#import-recipes/extraction/verifyApiAccess.js'
import { writeJsonFile } from '#import-recipes/cli/writeJsonFile.js'
// Bootstrap tool, run once by hand: a folder of .odt recipes in, one JSON file
// per recipe plus the index the app reads out. Returns the exit code rather than
// calling process.exit, so nothing is cut off mid-write.
async function main() {
const { source, out, dryRun, limit, problems } = parseCliArguments(process.argv.slice(2))
if (problems.length > 0 || source === null) {
for (const problem of problems) console.error(problem)
if (source === null) console.error('--source is required')
console.error(`\n${USAGE}`)
return 1
}
let fileNames = []
try {
fileNames = listOdtFileNames(source)
} catch (error) {
console.error(`Cannot read ${source}: ${error.message}`)
return 1
}
if (fileNames.length === 0) {
console.error(`No .odt file in ${source}.`)
return 1
}
const selected = limit === null ? fileNames : fileNames.slice(0, limit)
console.log(`${selected.length} of ${fileNames.length} .odt files in ${source}\n`)
if (dryRun) {
const { failures } = previewOdtFiles({ sourceDir: source, fileNames: selected })
// A dry run imports nothing, so it gets its own wording rather than the
// summary of a real run.
console.log(`\n${selected.length - failures.length} of ${selected.length} files readable, ${failures.length} not.`)
for (const { fileName, reason } of failures) console.log(` ${fileName} - ${reason}`)
console.log('\nDry run: the model was not called, nothing was written.')
return failures.length === 0 ? 0 : 1
}
const creation = createAnthropicClient()
if (!creation.ok) {
console.error(creation.message)
return 1
}
const access = await verifyApiAccess(creation.client)
if (!access.ok) {
console.error(access.message)
return 1
}
const { recipes, failures, usage } = await importOdtFiles({
client: creation.client,
sourceDir: source,
outDir: out,
fileNames: selected
})
const indexPath = join(out, 'index.json')
try {
writeJsonFile(indexPath, buildRecipeIndex({ recipes, generatedAt: new Date().toISOString() }))
} catch (error) {
console.error(`Could not write ${indexPath}: ${error.message}`)
return 1
}
console.log(`\n${formatImportSummary({ importedCount: recipes.length, failures })}`)
console.log(`\nWritten: ${join(out, 'recipes')}/ and ${indexPath}`)
console.log(`Tokens: ${usage.input} in, ${usage.output} out.`)
return failures.length === 0 ? 0 : 1
}
process.exitCode = await main()