UNPKG

hugo-extended

Version:

✏️ Plug-and-play binary wrapper for Hugo Extended, the awesomest static-site generator.

256 lines (255 loc) 8.84 kB
import { buildArgs } from "./lib/args.mjs"; import { ENV_VAR_DOCS, getEnvConfig } from "./lib/env.mjs"; import { doesBinExist, getBinPath, logger } from "./lib/utils.mjs"; import install from "./lib/install.mjs"; import { spawn } from "node:child_process"; //#region src/hugo.ts /** * Gets the path to the Hugo binary, automatically installing it if it's missing. * * This is the main entry point for the hugo-extended package. It checks if Hugo * is already installed and available, and if not, triggers an automatic installation * before returning the binary path. * * This handles the case where Hugo may mysteriously disappear (see issue #81), * ensuring the binary is always available when this function is called. * * Environment variables that affect behavior: * - HUGO_BIN_PATH: Use a custom binary path (skips auto-install if missing) * * @returns A promise that resolves with the absolute path to the Hugo binary * @throws {Error} If installation fails, the platform is unsupported, or custom binary is missing * * @example * ```typescript * import hugo from 'hugo-extended'; * * const hugoPath = await hugo(); * console.log(hugoPath); // "/usr/local/bin/hugo" or "./bin/hugo" * ``` */ const getHugoBinary = async () => { const envConfig = getEnvConfig(); const bin = getBinPath(); if (envConfig.binPath) { if (!doesBinExist(bin)) throw new Error(`Custom Hugo binary not found at HUGO_BIN_PATH: ${bin}`); return bin; } if (!doesBinExist(bin)) { logger.warn("Hugo is missing, reinstalling now..."); await install(); } return bin; }; /** * Execute a Hugo command with type-safe options. * * This function runs Hugo with the specified command and options, inheriting stdio * so output goes directly to the console. It's perfect for interactive commands * like `hugo server` or build commands where you want to see live output. * * @param command - Hugo command to execute (e.g., "server", "build", "mod clean") * @param positionalArgsOrOptions - Either positional arguments array or options object * @param options - Type-safe options object (if first param is positional args) * @returns A promise that resolves when the command completes successfully * @throws {Error} If the command fails or Hugo is not available * * @example * ```typescript * import { exec } from 'hugo-extended'; * * // Start development server * await exec("server", { * port: 1313, * buildDrafts: true, * baseURL: "http://localhost:1313" * }); * * // Create a new project * await exec("new project", ["my-site"], { format: "yaml" }); * * // Build site for production * await exec("build", { * minify: true, * cleanDestinationDir: true * }); * ``` */ async function exec(command, positionalArgsOrOptions, options) { const bin = await getHugoBinary(); let positionalArgs; let opts; if (Array.isArray(positionalArgsOrOptions)) { positionalArgs = positionalArgsOrOptions; opts = options; } else { positionalArgs = void 0; opts = positionalArgsOrOptions; } const args = buildArgs(command, positionalArgs, opts); return new Promise((resolve, reject) => { const child = spawn(bin, args, { stdio: "inherit" }); child.on("exit", (code) => { if (code === 0 || code === null) resolve(); else reject(/* @__PURE__ */ new Error(`Hugo command failed with exit code ${code}`)); }); child.on("error", (err) => { reject(err); }); }); } /** * Execute a Hugo command and capture its output. * * This function runs Hugo with the specified command and options, capturing * stdout and stderr. It's useful for commands where you need to process the * output programmatically, like `hugo version` or `hugo list all`. * * @param command - Hugo command to execute (e.g., "version", "list all") * @param positionalArgsOrOptions - Either positional arguments array or options object * @param options - Type-safe options object (if first param is positional args) * @returns A promise that resolves with stdout and stderr strings * @throws {Error} If the command fails or Hugo is not available * * @example * ```typescript * import { execWithOutput } from 'hugo-extended'; * * // Get Hugo version * const { stdout } = await execWithOutput("version"); * console.log(stdout); // "hugo v0.154.3+extended ..." * * // List all content * const { stdout: content } = await execWithOutput("list all"); * const pages = content.split('\n'); * ``` */ async function execWithOutput(command, positionalArgsOrOptions, options) { const bin = await getHugoBinary(); let positionalArgs; let opts; if (Array.isArray(positionalArgsOrOptions)) { positionalArgs = positionalArgsOrOptions; opts = options; } else { positionalArgs = void 0; opts = positionalArgsOrOptions; } const args = buildArgs(command, positionalArgs, opts); return new Promise((resolve, reject) => { const stdoutChunks = []; const stderrChunks = []; const child = spawn(bin, args); if (child.stdout) child.stdout.on("data", (chunk) => { stdoutChunks.push(chunk); }); if (child.stderr) child.stderr.on("data", (chunk) => { stderrChunks.push(chunk); }); child.on("exit", (code) => { const stdout = Buffer.concat(stdoutChunks).toString("utf8"); const stderr = Buffer.concat(stderrChunks).toString("utf8"); if (code === 0 || code === null) resolve({ stdout, stderr }); else reject(/* @__PURE__ */ new Error(`Hugo command failed with exit code ${code}${stderr ? `\n${stderr}` : ""}`)); }); child.on("error", (err) => { reject(err); }); }); } /** * Builder-style API for executing Hugo commands. * * Provides a fluent interface where each Hugo command is a method on the * builder object. All methods are type-safe with autocomplete for options. * * @example * ```typescript * import { hugo } from 'hugo-extended'; * * // Start server * await hugo.server({ port: 1313, buildDrafts: true }); * * // Build site * await hugo.build({ minify: true }); * * // Module operations * await hugo.mod.clean({ all: true }); * await hugo.mod.get(); * ``` */ const hugo = { /** Build your site */ build: (options) => exec("build", options), /** Generate shell completion scripts */ completion: { bash: (options) => exec("completion bash", options), fish: (options) => exec("completion fish", options), powershell: (options) => exec("completion powershell", options), zsh: (options) => exec("completion zsh", options) }, /** Print Hugo configuration */ config: (options) => exec("config", options), /** Convert content to different formats */ convert: { toJSON: (options) => exec("convert toJSON", options), toTOML: (options) => exec("convert toTOML", options), toYAML: (options) => exec("convert toYAML", options) }, /** Print Hugo environment info */ env: (options) => exec("env", options), /** Generate documentation */ gen: { doc: (options) => exec("gen doc", options), man: (options) => exec("gen man", options) }, /** Import your site from others */ import: { jekyll: (options) => exec("import jekyll", options) }, /** List various types of content */ list: { all: (options) => exec("list all", options), drafts: (options) => exec("list drafts", options), expired: (options) => exec("list expired", options), future: (options) => exec("list future", options), published: (options) => exec("list published", options) }, /** Module operations */ mod: { clean: (options) => exec("mod clean", options), get: (options) => exec("mod get", options), graph: (options) => exec("mod graph", options), init: (options) => exec("mod init", options), npm: { pack: (options) => exec("mod npm pack", options) }, tidy: (options) => exec("mod tidy", options), vendor: (options) => exec("mod vendor", options), verify: (options) => exec("mod verify", options) }, /** Create new content */ new: Object.assign((pathOrOptions, options) => { if (typeof pathOrOptions === "string") return exec("new", [pathOrOptions], options); return exec("new", pathOrOptions); }, { content: (pathOrOptions, options) => { if (typeof pathOrOptions === "string") return exec("new content", [pathOrOptions], options); return exec("new content", pathOrOptions); }, project: (pathOrOptions, options) => { if (typeof pathOrOptions === "string") return exec("new project", [pathOrOptions], options); return exec("new project", pathOrOptions); }, theme: (nameOrOptions, options) => { if (typeof nameOrOptions === "string") return exec("new theme", [nameOrOptions], options); return exec("new theme", nameOrOptions); } }), /** Start the Hugo development server */ server: (options) => exec("server", options), /** Print the Hugo version */ version: (options) => exec("version", options) }; var hugo_default = Object.assign(getHugoBinary, hugo); //#endregion export { ENV_VAR_DOCS, hugo_default as default, exec, execWithOutput, getEnvConfig, getHugoBinary, hugo };