@metalbolicx/blueprint
v0.1.0
Published
Transactional code generator with Hygen-compatible templates
Readme
Blueprint
A fast, transactional template generator — a modern replacement for Hygen.
Generate code, config files, or any text from templates with atomic commits, declarative prompts, and no runtime dependencies. Language-agnostic — the same engine drives TypeScript, Go, Python, Rust, SQL, YAML, or any text output.
npx blueprint generate react-component --name User --path src/componentsInstallation
npm install -g blueprintFor non-installed use:
npx blueprint <command>Onboarding
Scaffold a runnable hello-world template in your project:
blueprint init
blueprint generate hello-world myfirstProduces hello-myfirst.md in the current directory.
For global installation (templates stored in ~/.config/blueprint/):
blueprint init --global
blueprint generate hello-world myfirstBoth commands are idempotent — re-running is safe and produces no changes.
Quick start
- Init —
blueprint initscaffolds.blueprint.yamland a hello-world example in_templates/hello-world/ - Author — create a manifest + template files:
_templates/
└── component/
└── new/
├── manifest.yaml
└── files/
└── Component.tsx.ejs.t# manifest.yaml
name: component
classification: component
prompts:
- name: name
type: input
description: "Component name (PascalCase)"
default: MyComponent
- name: path
type: input
description: "Output path"
default: src/components# files/Component.tsx.ejs.t
---
to: <%= path %>/<%= name %>.tsx
---
import React from 'react'
interface <%= name %>Props {
children?: React.ReactNode
}
export const <%= name %>: React.FC<<%= name %>Props> = ({ children }) => {
return <div className="<%= h.kebabCase(name) %>"><%= children %></div>
}- Generate —
blueprint generate component --name Button --path src/ui
CLI usage
# Show help
blueprint --help
# Scaffold .blueprint.yaml
blueprint init
# Generate from a template classification
blueprint generate <classification> [options]
# Options:
# --name <name> Component name (PascalCase)
# -n, --name <name> Short form
# --force Skip prompts, overwrite existing files
# -f, --force Short form
# --output <dir> Output directory
# -o, --output <dir> Short form
# --<key> <value> Arbitrary attributes passed to templates
# Template registry management
# blueprint template copy <classification> Copy a project generator into global registry
# blueprint template list List registry entries (name + source path)
# blueprint template remove <classification> Remove a global template and its registry entryExamples
# Basic generation with prompts
blueprint generate react-component
# With explicit name (skips name prompt)
blueprint generate react-component --name Button
# Force overwrite existing files
blueprint generate react-component --name Button --force
# Custom output directory
blueprint generate react-component --name Button --output src/ui
# Pass custom attributes
blueprint generate react-component --name Button --path src/components --framework react18
# Use short flags
blueprint generate react-component -n Button -f -o src/uiTemplate format
manifest.yaml
Declares metadata, classification, and interactive prompts:
name: component
classification: component
prompts:
- name: package
type: input
description: "Package or module name"
default: main
- name: framework
type: select
description: "Framework"
options:
- react
- vue
- svelte
- name: includeTests
type: confirm
description: "Include test file?"
default: trueTemplate files (*.ejs.t)
Frontmatter defines the operation; body is the template. Uses EJS syntax.
---
to: src/<%= name %>.tsx
inject: React.FC<<%= name %>Props>
---
import React from 'react'
interface <%= name %>Props {
children?: React.ReactNode
}
export const <%= name %>: React.FC<<%= name %>Props> = ({ children }) => {
return <div className="<%= h.kebabCase(name) %>"><%= children %></div>
}Frontmatter directives
| Directive | Type | Description |
|-----------|------|-------------|
| to | string | Target file path |
| inject | string | Regex pattern to match for replacement |
| after | string | Regex — insert content after this pattern |
| before | string | Regex — insert content before this pattern |
| prepend | bool | Prepend content to existing file |
| append | bool | Append content to existing file |
| force | bool | Overwrite existing file |
| sh | string | Shell command to execute after render |
Directive examples
to — Write to a specific path:
---
to: src/<%= name %>.tsx
---inject — Replace content matching a regex:
---
inject: const \w+ = new
---
const newInstance = new Constructor()after — Insert after a regex match:
---
after: class \w+
---
// Added after class definitionbefore — Insert before a regex match:
---
before: export default
---
// Header commentprepend — Add to the beginning of a file:
---
prepend: true
---
// This goes at the topappend — Add to the end of a file:
---
append: true
---
// This goes at the bottomforce — Overwrite without prompting:
---
to: src/<%= name %>.tsx
force: true
---script — Run a configured script after render:
---
to: src/<%= name %>.tsx
script: setup
---Context variables
Available inside every template via EJS:
| Variable | Description |
|----------|-------------|
| <%= name %> | Component name (lowercase) |
| <%= Name %> | Component name (PascalCase) |
| <%= names %> | Pluralized lowercase |
| <%= Names %> | Pluralized PascalCase |
| <%= path %> | User-provided path attribute |
| <%= package %> | User-provided package attribute |
| Any prompt answer | Available by its name |
FuncMaps (template helpers)
Available as h.* in templates:
| Function | Example | Result |
|----------|---------|--------|
| h.pascalCase(str) | <%= h.pascalCase("hello_world") %> | HelloWorld |
| h.camelCase(str) | <%= h.camelCase("hello_world") %> | helloWorld |
| h.kebabCase(str) | <%= h.kebabCase("HelloWorld") %> | hello-world |
| h.snakeCase(str) | <%= h.snakeCase("HelloWorld") %> | hello_world |
| h.upper(str) | <%= h.upper("hello") %> | HELLO |
| h.lower(str) | <%= h.lower("HELLO") %> | hello |
| h.trim(str) | <%= h.trim(" hello ") %> | hello |
| h.title(str) | <%= h.title("hello world") %> | Hello World |
Hooks
Lifecycle hooks in .blueprint.yaml:
hooks:
pre_generate: echo "Starting generation..."
post_generate: prettier --write generated/
timeout: 30sSupported interpreters: bash, sh, node, python3, pwsh.
Safety
- Transactional: renders to temp staging dir, commits atomically — no partial writes
- Rollback: on any failure (render error, shell error), staged files are cleaned up
- Conflict resolution: bulk prompt —
[y]es to all, [n]o to all, [s]elect individually, [a]bort
Global template registry
- Template registry entries are stored in
~/.config/blueprint/config.yamlunder theregistryarray. Each entry recordsname,source(absolute path to the original project generator), andpath(the installed~/.config/blueprint/templates/<name>/location). blueprint template copy <classification>copies the entire source generator directory (manifest + actions) into the registry folder and persists the entry.blueprint template listshows installed templates and their originating paths.blueprint template remove <classification>deletes the registry directory and removes the associated config entry.- Discovery automatically appends registry paths after the project’s
_templates/templates/generatorsstack, so local generators still win when a name conflicts.
Local release
Publish from a clean worktree to avoid shipping unintended files:
git stash # or git checkout -- .
pnpm build # ReScript compile + rolldown bundle
pnpm res:test # full test suite
npm pack --dry-run # inspect the exact file list
npm publish --access public
git tag v<version>
git push --tagsPublishing
The package is published as @metalbolicx/blueprint (scoped).
Pre-publish checklist
git stash # or git checkout -- .
pnpm build # ReScript compile + rolldown bundle
pnpm res:test # full test suite — must be 675/675 green
npm pack --dry-run # verify clean tarball (only LICENSE, README.md, dist/main.mjs, package.json)
npm publish --dry-run --access publicRun pnpm release:smoke for a single-command readiness check (build → pack → install → invoke → cleanup).
After publish
git tag v<version>
git push --tagsNote
The bin name stays "blueprint" even though the package is scoped — users run npx blueprint, not npx @metalbolicx/blueprint.
Docs
Full documentation at /docs, including architecture, API reference, and tutorials.
