Apply customizations to any project without templating placeholders
Project description
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
pip install engraft
Quick 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_name
Create a values file engraft.values.yml:
app_name: MyApp
Apply:
engraft apply --template engraft.template.yml --values engraft.values.yml
Result — 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_label
Selectors 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_description
The 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*)"(?P<value>[^"]*)"'
variable: primary_color
The selector must contain a named capture group called value. Both syntaxes are accepted:
- Python style:
(?P<value>...) - ECMAScript/JavaScript style:
(?<value>...)
Using the ECMAScript 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: logo
The variable value is a path relative to the values file directory. Useful for binary files like images.
Releasing
Versioning is automatic via hatch-vcs — the package version is derived from git tags.
To publish a new release:
- Create a GitHub Release with a tag matching
vX.Y.Z(e.g.,v0.2.0) - The release pipeline automatically runs lint, format check, and tests
- If all checks pass, the package is built and published to PyPI with Sigstore signing
Values file notes
Values are parsed as YAML 1.2. The bare words yes, no, on, off (and their capitalized variants) parse as strings, not booleans — only true and false are coerced to booleans. If you are migrating a values file from older tooling that assumed YAML 1.1 boolean semantics, quote the value explicitly (flag: "true") when a string was intended.
Non-string scalars (numbers, booleans) are coerced to strings before being substituted into target files. A key whose value is null is treated as "not provided" — the variable's default (or the noop sentinel for optional variables) is used instead.
Development
Development commands are run from inside the python/ directory:
cd python/
# Install with dev dependencies
pip install -e ".[dev]"
# Run unit tests
pytest
# Run linter
ruff check src/ tests/
The repo also has an end-to-end 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file engraft-0.2.1.tar.gz.
File metadata
- Download URL: engraft-0.2.1.tar.gz
- Upload date:
- Size: 13.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58e8880466c0314eed4bd0442de693a0b2dab22f435352ff8ab8b4b2116720bf
|
|
| MD5 |
14dea5e792c8f8ce3760f37a05dc6ea0
|
|
| BLAKE2b-256 |
af85542415a6edd4e50d7eb3e6014bab8968c2ffe5750403eb5a9c852814ac21
|
Provenance
The following attestation bundles were made for engraft-0.2.1.tar.gz:
Publisher:
release.yml on smartcompanion-app/engraft
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
engraft-0.2.1.tar.gz -
Subject digest:
58e8880466c0314eed4bd0442de693a0b2dab22f435352ff8ab8b4b2116720bf - Sigstore transparency entry: 1442081075
- Sigstore integration time:
-
Permalink:
smartcompanion-app/engraft@eeccdc27c2a7287ad4955a45ae09de65168d9e8b -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/smartcompanion-app
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eeccdc27c2a7287ad4955a45ae09de65168d9e8b -
Trigger Event:
release
-
Statement type:
File details
Details for the file engraft-0.2.1-py3-none-any.whl.
File metadata
- Download URL: engraft-0.2.1-py3-none-any.whl
- Upload date:
- Size: 11.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10c60b6381887afd3bdc39d54bf75bfe3358f2d4ed52ff19abbc34d4a1fb5cee
|
|
| MD5 |
dfb24ff51816637ba6ada6c8e086d24b
|
|
| BLAKE2b-256 |
82c867737c5375a32818c30cc83056f127c3cf64f1d4aef90092daca73ca0990
|
Provenance
The following attestation bundles were made for engraft-0.2.1-py3-none-any.whl:
Publisher:
release.yml on smartcompanion-app/engraft
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
engraft-0.2.1-py3-none-any.whl -
Subject digest:
10c60b6381887afd3bdc39d54bf75bfe3358f2d4ed52ff19abbc34d4a1fb5cee - Sigstore transparency entry: 1442081204
- Sigstore integration time:
-
Permalink:
smartcompanion-app/engraft@eeccdc27c2a7287ad4955a45ae09de65168d9e8b -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/smartcompanion-app
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eeccdc27c2a7287ad4955a45ae09de65168d9e8b -
Trigger Event:
release
-
Statement type: