@smartcompanion/engraft
v0.2.2
Published
Apply customizations to any project without templating placeholders
Readme
engraft
Apply customizations to any project without templating placeholders.
The Problem
Customizing a project (e.g., white-label products) forces a choice between bad options:
- Templating tools (Cookiecutter, Copier, Yeoman) require
{{ placeholders }}in source code — the repo is no longer a working app - Forking leads to diverging codebases that are painful to sync with upstream
- Manual editing is error-prone, undocumented, and impossible to reproduce
engraft solves this by keeping the source repo clean and runnable while providing a declarative, reproducible customization layer on top.
How It Works
engraft uses a two-file model:
- Template file — defines what can be customized and how (maintained by the repo author)
- Values file — contains the consumer's customization values
The original project stays untouched. Run engraft apply and the customizations are applied in place.
Installation
Requires Node.js 20 or newer.
npm install -g @smartcompanion/engraftQuick Start
Given a project with a config.json:
{
"name": "DefaultApp",
"version": "1.0.0"
}Create a template file engraft.template.yml:
variables:
app_name:
description: Application name
default: DefaultApp
customizations:
- action: json_replace
file: config.json
replace:
- selector: $.name
variable: app_nameCreate a values file engraft.values.yml:
app_name: MyAppApply:
engraft apply --template engraft.template.yml --values engraft.values.ymlResult — config.json now contains:
{
"name": "MyApp",
"version": "1.0.0"
}Action Reference
json_replace
Replace values in JSON files using JSONPath-like selectors.
- action: json_replace
file: app.json
replace:
- selector: $.expo.name
variable: app_name
- selector: $.expo.extra.items[0].label
variable: item_labelSelectors use dot notation with optional array indices: $.path.to.key or $.array[0].field.
html_replace
Replace values in HTML files using XPath selectors. Supports both element text and attribute values.
- action: html_replace
file: index.html
replace:
- selector: //title
variable: page_title
- selector: //meta[@name='description']/@content
variable: page_descriptionThe selector must match exactly one element or attribute. Matching zero or more than one is an error.
regex_replace
Replace values in any text file using regex with a named capture group.
- action: regex_replace
file: src/theme.ts
replace:
- selector: '(PRIMARY_COLOR\s*=\s*)"(?<value>[^"]*)"'
variable: primary_colorThe selector must contain a named capture group called value. Both syntaxes are accepted:
- ECMAScript/JavaScript style:
(?<value>...) - Python style:
(?P<value>...)
Using either form keeps templates portable between the Python and TypeScript implementations. Only the captured group is replaced; the surrounding match is preserved.
file_replace
Replace an entire file with a source file referenced by a variable.
- action: file_replace
file: assets/logo.png
variable: logoThe variable value is a path relative to the values file directory. Useful for binary files like images.
Values file notes
Values are parsed as YAML 1.2 (js-yaml's default). The bare words yes, no, on, off parse as strings. Non-string scalars (numbers, booleans) are coerced to strings. A key whose value is null is treated as "not provided".
Development
cd typescript/
# Install dependencies
npm install
# Build the CLI bundle (outputs dist/cli.js)
npm run build
# Run tests
npm test
# Type-check only
npm run lintThe repo also has an end-to-end pytest harness under e2e/ (run from the repo root) that runs the same fixture scenarios against both the Python and TypeScript CLIs and asserts identical output.
