hugo-extended
Version:
✏️ Plug-and-play binary wrapper for Hugo Extended, the awesomest static-site generator.
256 lines (255 loc) • 8.84 kB
JavaScript
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 };