hugo-extended
Version:
✏️ Plug-and-play binary wrapper for Hugo Extended, the awesomest static-site generator.
126 lines (124 loc) • 4.35 kB
JavaScript
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
//#region src/lib/args.ts
const __dirname = path.dirname(fileURLToPath(import.meta.url));
let cachedSpec = null;
/**
* Load the Hugo spec from the generated json file (cached after first load).
*
* @returns The parsed Hugo spec containing global flags and command-specific flags.
*/
function loadSpec() {
if (cachedSpec) return cachedSpec;
const specPath = path.join(__dirname, "..", "generated", "flags.json");
try {
const specText = fs.readFileSync(specPath, "utf8");
cachedSpec = JSON.parse(specText);
} catch (error) {
throw new Error(`Failed to load Hugo spec from ${specPath}. Ensure the project is built (npm run build) before use.`, { cause: error });
}
return cachedSpec;
}
/**
* Convert a camelCase property name to kebab-case flag name.
*
* @param name - Property name in camelCase (e.g., "buildDrafts").
* @returns Kebab-case flag name (e.g., "build-drafts").
*
* @example
* camelToKebab("baseURL") // "base-u-r-l"
* camelToKebab("buildDrafts") // "build-drafts"
*/
function camelToKebab(name) {
return name.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
}
/**
* Find a flag spec by its camelCase property name.
*
* @param flags - Array of flag specs to search.
* @param propName - Property name in camelCase.
* @returns The matching flag spec, or undefined if not found.
*/
function findFlag(flags, propName) {
const kebab = camelToKebab(propName);
return flags.find((f) => {
const flagName = f.long.startsWith("--") ? f.long.slice(2) : f.long;
return flagName === kebab || flagName === propName;
});
}
/**
* Build command-line arguments from a command and options object.
*
* This function:
* 1. Loads the Hugo spec to understand flag types
* 2. Converts camelCase property names to kebab-case flags
* 3. Formats values according to their types (boolean, string, number, arrays)
* 4. Returns an argv array ready to pass to child_process
*
* @param command - Hugo command string (e.g., "server", "build", "mod clean").
* @param positionalArgs - Optional array of positional arguments (e.g., paths, names).
* @param options - Options object with camelCase property names.
* @returns Array of command-line arguments.
*
* @example
* buildArgs("server", undefined, { port: 1313, buildDrafts: true })
* // Returns: ["server", "--port", "1313", "--build-drafts"]
*
* @example
* buildArgs("new site", ["my-site"], { format: "yaml" })
* // Returns: ["new", "site", "my-site", "--format", "yaml"]
*
* @example
* buildArgs("build", undefined, { theme: ["a", "b"], minify: true })
* // Returns: ["build", "--theme", "a", "--theme", "b", "--minify"]
*/
function buildArgs(command, positionalArgs, options) {
const spec = loadSpec();
const args = [];
args.push(...command.split(" "));
if (positionalArgs && positionalArgs.length > 0) args.push(...positionalArgs);
if (!options || Object.keys(options).length === 0) return args;
const cmdSpec = spec.commands.find((c) => c.command === command);
const allFlags = [...spec.globalFlags, ...cmdSpec?.flags ?? []];
for (const [key, value] of Object.entries(options)) {
if (value === void 0 || value === null) continue;
const flagSpec = findFlag(allFlags, key);
const flagName = flagSpec ? flagSpec.long.startsWith("--") ? flagSpec.long : `--${flagSpec.long}` : `--${camelToKebab(key)}`;
switch (flagSpec?.kind ?? inferKind(value)) {
case "boolean":
if (value === true) args.push(flagName);
break;
case "string":
args.push(flagName, String(value));
break;
case "number":
args.push(flagName, String(value));
break;
case "string[]":
if (Array.isArray(value)) for (const item of value) args.push(flagName, String(item));
break;
case "number[]":
if (Array.isArray(value)) for (const item of value) args.push(flagName, String(item));
break;
}
}
return args;
}
/**
* Infer the kind of a value when we don't have spec information.
*
* @param value - The value to inspect.
* @returns The inferred flag kind.
*/
function inferKind(value) {
if (typeof value === "boolean") return "boolean";
if (typeof value === "number") return "number";
if (Array.isArray(value)) {
if (value.length > 0 && typeof value[0] === "number") return "number[]";
return "string[]";
}
return "string";
}
//#endregion
export { buildArgs };