A small, dependency-light module for writing generated content to files — handles directory creation, UTF-8 defaults, stable/unique filenames, and typed errors, so consuming projects don't have to hand-roll mkdirSync/writeFileSync logic.
Written on TypeScript, can generate a uniquie filenames.
Arthur calling it WRITE because one of the main goals for this project was to write a file on a disk
npm install markup-generatorRequires Node 20+. Ships as a dual CJS/ESM build with bundled TypeScript types.
npm install markup-generator is the only install path. Version 4.x ships as a self-contained package: the monorepo's internal @llazyemail/* modules are built and bundled into this package's dist/. They are not published separately (and the @llazyemail npm scope does not need to exist).
| Import from | Contents |
|---|---|
markup-generator |
Full API: fs / write / read / email / template / cli helpers |
markup-generator/browser |
Browser-safe generateFileName only |
markup-generator bin |
`npx markup-generator name |
Internal layout (contributors only): packages/ holds @llazyemail/{fs,write,read,email,template,cli,core}.
Upgrading from 3.x? See MIGRATION.md. Monorepo layout: docs/v4-monorepo.md.
Several sibling projects independently reinvented the same file-writing boilerplate — manual directory creation, raw fs calls, and ad-hoc error handling. This module centralizes that logic so every consumer gets the same guarantees: missing directories are created automatically, content is validated before writing, and failures come back as a single typed error instead of inconsistent thrown exceptions.
Writes content to dir/fileName, creating dir if it doesn't exist. Defaults to UTF-8 encoding.
const { writeGeneratedFile } = require('markup-generator');
const outPath = await writeGeneratedFile({
content: '<html>...</html>',
fileName: 'newsletter.html',
dir: 'generated',
});Resolves with the absolute path written to. Throws MarkupGeneratorError on failure (see Error handling).
A higher-level wrapper around writeGeneratedFile, purpose-built for email-generation scripts, with graceful failure handling. Silent by default since 4.0.0 — pass logger: console to get the success / failure lines.
const { writeGeneratedEmail } = require('markup-generator');
await writeGeneratedEmail({
content: renderedHtml,
fileName: 'hackernoon-email.html',
label: 'Hackernoon',
logger: console, // optional; omit for silent
});- On success: writes the file and (with a logger) logs a confirmation.
- On
MarkupGeneratorError: logs (with a logger)❌ Failed to write <file> [<code>]: <message>, setsprocess.exitCode = 1, and resolvesundefined— does not throw. - Any other error type still throws, so unexpected failures aren't silently swallowed.
Generates a unique filename by appending a UUID to prefix.
const { generateFileName } = require('markup-generator');
generateFileName('hackernoon-email');
// → 'hackernoon-email-3f9c2a10-....html' (illustrative — check exact format in source)
⚠️ Because this appends a UUID, it produces a different filename on every call. Don't use it where a caller expects a stable, predictable filename — passfileNameexplicitly towriteGeneratedFile/writeGeneratedEmailinstead in that case.
Typed JSON read/write helpers (included in markup-generator).
const { readJson, writeJson } = require('markup-generator');
const config = await readJson('config.json');
await writeJson('output.json', { updated: true });Throws MarkupGeneratorError with code JSON_READ (file couldn't be read) or JSON_PARSE (invalid JSON) on failure.
Loads a data module — .json files are parsed via readJson; .js/.mjs/.cjs files are loaded via dynamic import (CJS-safe, so it works correctly even when bundled/transpiled to CommonJS). Resolved relative to process.cwd() unless the path is already absolute.
const { loadData } = require('markup-generator');
const data = await loadData('content/data.js');Reads a plain content file (HTML, text, markdown) as a UTF-8 string. Resolved relative to process.cwd() unless already absolute.
const { loadContent } = require('markup-generator');
const html = loadContent('templates/fallback.html');Resolves a path relative to process.cwd(), unless it's already absolute. Used internally by the read/write helpers above; exported for consumers who want the same resolution behavior in their own scripts.
const { resolveFromCwd } = require('markup-generator');
resolveFromCwd('output/file.html'); // → absolute pathAn end-to-end CLI helper for "render a template, write the result" scripts — parses CLI arguments, loads data/content, calls your render function, and writes the output, all in one call.
const { generateTemplate } = require('markup-generator');
const { renderTemplate } = require('./dist/index.cjs.js');
generateTemplate({
render: renderTemplate,
argv: process.argv,
defaultDataPath: 'content/content2.js',
}).catch((err) => {
console.error(err.message);
process.exit(1);
});This bundles CLI parsing, data loading, and rendering-orchestration into one call. If your script's argument format or fallback-content logic diverges from the built-in defaults, use the lower-level
parseArgv/loadData/loadContent/writeGeneratedFilepieces directly instead.
writeHTML(fileName, data, dir)— deprecated, usewriteGeneratedFile({ fileName, content, dir }). Removed in v5.isFolderExists(dir)— deprecated alias ofensureDir(dir). Removed in v5.writingFile(content, prefix)— removed, usewriteGeneratedFile({ content, prefix }).
Every failure from this module is a MarkupGeneratorError — a single error class with a code field, so callers can branch on failure type without parsing message strings.
import { MarkupGeneratorError } from 'markup-generator';
try {
await writeGeneratedFile({ content: '', fileName: 'out.html', dir: 'generated' });
} catch (error) {
if (error instanceof MarkupGeneratorError) {
console.error(`[${error.code}] ${error.message}`);
} else {
throw error;
}
}| Code | Meaning |
|---|---|
EMPTY_CONTENT |
content was an empty string |
NOT_A_STRING |
content was not a string |
FILE_EXISTS |
Target file already exists and wasn't expected to |
WRITE_FAILED |
The underlying file write failed |
JSON_READ |
A JSON file couldn't be read from disk |
JSON_PARSE |
A file's contents were not valid JSON |
pnpm install
pnpm exec turbo run build test lint # all workspace packages
pnpm exec tsc --noEmit # root typecheck
pnpm run publish:dry # pnpm -r publish --dry-run (never publishes)Test runner: Vitest (migrated from Jest). Build tool: tsup.
See MIGRATION.md for breaking-change notes between major versions.
See LICENSE.