UNPKG

edgejs-cli

Version:

CLI utility to generate files based on edge templates

224 lines (223 loc) 7.61 kB
import { parseArgs } from "node:util"; import DeepMerge from "@fastify/deepmerge"; import path from "node:path"; import { Edge, Template } from "edge.js"; import yaml from "js-yaml"; import { globby } from 'globby'; import pMap from "p-map"; import fs from "node:fs/promises"; import dedent from "dedent"; const args = await getArgs(); const edge = bootstrapEdge(); const dMerge = DeepMerge(); const templates = await collectTemplates(); if (!(templates === null || templates === void 0 ? void 0 : templates.length)) fail("No templates could be found"); const context = await parseContext(args.dataPath); await generateFiles(templates, context); // --- async function parseContext(ctxPath) { if (!ctxPath) return {}; const rawContext = await fs.readFile(ctxPath, "utf-8").catch(e => { fail(`Failed to read context file: ${ctxPath}`); }); if (rawContext && ctxPath.endsWith(".json")) { try { return JSON.parse(rawContext); } catch (e) { console.error(e); fail(`Failed to parse context file as json: ${ctxPath}`); } } if (rawContext && (ctxPath.endsWith(".yaml") || ctxPath.endsWith(".yml"))) { try { return yaml.load(rawContext); } catch (e) { console.error(e); fail(`Failed to parse context file as yaml: ${ctxPath}`); } } fail(`Unsupported context file extension: ${ctxPath}`); } async function collectTemplates() { var _a, _b; const inputDir = (_a = args.inputDir) !== null && _a !== void 0 ? _a : process.cwd(); if (args.inputPath) { const tmpl = getTemplate(args.inputPath); const inputStat = await fs.stat(tmpl.path); if (!inputStat.isFile()) { fail(`Expected inputPath to point to a file`); } edge.registerTemplate(tmpl.key, { template: await fs.readFile(tmpl.path, "utf-8") }); return [tmpl]; } const inputExt = (_b = args.inputExtension) !== null && _b !== void 0 ? _b : "edge"; const inputStat = await fs.stat(inputDir); if (!inputStat.isDirectory()) { fail(`Expected inputPath to point to a file`); } edge.mount(path.resolve(inputDir)); const pattern = `**/*.${inputExt}`; const templatePaths = await globby([pattern], { cwd: inputDir }); return compact(templatePaths.map(p => { if (isIgnored(p)) return null; return getTemplate(p); })); } async function generateFiles(templates, context) { const failedKeys = []; await pMap(templates, (t) => generateFile(t, context).catch(e => { console.error(`Failure generating ${t}:`, e); failedKeys.push(t.key); }), { concurrency: 5 }); if (failedKeys.length) fail(`Failed to generate: ${failedKeys.join(", ")}`); } async function generateFile(template, baseContext) { var _a, _b; const context = await getContext(template, baseContext); const content = await edge.render(template.key, context); if (template.isMulti) { return generateMulti(template, content); } const outPath = (_a = args.outputPath) !== null && _a !== void 0 ? _a : (args.skipOutputExtension ? template.key : `${template.key}.${(_b = args.outputExtension) !== null && _b !== void 0 ? _b : "html"}`); await writeFile(outPath, content, template.path); } async function generateMulti(template, content) { var _a; const xml2js = await import("xml2js"); const tree = await xml2js.parseStringPromise(`<root>${content}</root>`); if (!Array.isArray(tree.root.file)) throw new Error(`Invalid format: ${template.path} - file tag missing`); for (const f of tree.root.file) { if (args.outputPath) fail("outputPath can not be used with multi templates"); const outPath = (_a = f === null || f === void 0 ? void 0 : f.$) === null || _a === void 0 ? void 0 : _a.path; if (typeof outPath !== "string") { throw new Error(`Invalid format: ${template.path} - path attribute missing`); } let fileContent = f._ || ""; if (isAttrTrue(f.$.dedent)) fileContent = dedent(fileContent); if (isAttrTrue(f.$.trim) || isAttrTrue(f.$.strip)) fileContent = fileContent.trim(); await writeFile(outPath, fileContent, template.path); } } function isAttrTrue(val) { const lVal = val === null || val === void 0 ? void 0 : val.toLowerCase(); return lVal === "true" || lVal === "yes"; } async function writeFile(outPath, content, sourcePath) { var _a; const outDir = (_a = args.outputDir) !== null && _a !== void 0 ? _a : process.cwd(); const finalOutPath = path.resolve(outDir, outPath); console.log(`Generating file: ${sourcePath} -> ${finalOutPath}`); await fs.mkdir(path.dirname(finalOutPath), { recursive: true }); await fs.writeFile(finalOutPath, content, { encoding: "utf-8" }); } async function getContext(template, baseContext) { var _a; if (!args.relativeContextPath) return baseContext; const localContext = await parseContext(path.resolve((_a = args.inputPath) !== null && _a !== void 0 ? _a : process.cwd(), path.dirname(template.path), args.relativeContextPath)); return dMerge(baseContext, localContext); } function bootstrapEdge() { const edge = Edge.create({ cache: false }); if (args.skipEscaping) { Template.prototype.escape = (input) => input; } return edge; } async function getArgs() { const { values } = parseArgs({ options: { inputPath: { type: "string", short: "i" }, inputDir: { type: "string", short: "I" }, outputPath: { type: "string", short: "o" }, outputDir: { type: "string", short: "O" }, dataPath: { type: "string", short: "d" }, relativeContextPath: { type: "string", short: "c" }, inputExtension: { type: "string", }, outputExtension: { type: "string", }, skipOutputExtension: { type: "boolean", }, skipEscaping: { type: "boolean", }, } }); if (values.outputDir && values.outputPath) { fail(`outputDir or outputPath can not be used together`); } if (values.inputDir && values.outputPath) { fail(`outputDir must be used instead of outputPath if inputDir is passed`); } return values; } function fail(msg) { console.error(msg); process.exit(1); } function isIgnored(p) { return p.startsWith(".") || p.startsWith("_"); } function getFileNameWithoutExt(p) { return path.basename(p, path.extname(p)); } function getTemplate(subPath) { var _a; const inputDir = (_a = args.inputDir) !== null && _a !== void 0 ? _a : process.cwd(); const inputPath = path.resolve(inputDir, subPath); const baseName = getFileNameWithoutExt(subPath); const multiMatch = baseName.match(/^(.*)\.multi$/); const key = path.join(path.dirname(subPath), baseName); return { path: inputPath, key, isMulti: !!multiMatch }; } function compact(arr) { return arr.filter(Boolean); }