Conform v0.95.5
Conform is for anyone maintaining multiple similar projects — from solo developers to teams — who want consistent repo scaffolding without ceremony. It plans changes, shows readable diffs, then safely applies only what’s needed with backups and a manifest so you can roll back with confidence. Everything runs locally and offline — no telemetry, no SaaS.
Who It’s For
- Teams maintaining multiple similar Node/Next.js repositories
- Internal platform teams enforcing a standard project template
- Agencies standardizing client repos while preserving local tweaks
- Solo developers with multiple projects wanting consistent setup
- CI pipelines that want to gate or report on config drift
At a Glance
- Safe, minimal changes with review-first workflow (plan → diff → apply)
- Destination-aware planning for Next.js, Tailwind, and Git integrations
- Deep JSON merges (ESLint/Stylelint/JSConfig), additive package.json merges
- Yarn policy enforced; removes conflicting lockfiles for hygiene
- Offline-first; no telemetry; notification-only update checks
- Backups + manifest for reliable rollback (
restoreby timestamp)
Key Features
Drive projects back to a clean, predictable state. Conform walks a source tree (a template or an existing project), proposes the minimum set of changes, deep‑merges JSON configs, adds missing package.json scripts and metadata, and unions ignore files by unique lines. Preview everything with conform plan --diff, then apply changes knowing backups and a manifest are created only when content actually changes. It also enforces Yarn during package.json merges and removes conflicting lockfiles to avoid tool drift.
Supported Platform
Conform is distributed as a single macOS binary (Apple Silicon) via direct download from the website. Additional platforms may follow in future releases.
Note: Conform manages Node.js repositories but does not require Node.js on the host to run. It is a standalone terminal (CLI) binary.
Installation
You can install Conform system‑wide for all users, or locally for just your current user. Both methods start from the downloaded release zip from the website (macOS, Apple Silicon).
System‑wide install (all users)
- Download and unzip the release archive
- What it does: extracts the
conformbinary from the downloaded zip.
unzip conform.zip
- Optional: verify the published checksum before installing
- What it does: computes the SHA‑256 of the downloaded archive and lets you compare it with the value published on the website.
shasum -a 256 conform.zip
# or
openssl dgst -sha256 conform.zip
- Create the system binary directory if needed
- What it does: ensures
/usr/local/binexists, a common system‑wide location on macOS that is already onPATH.
sudo mkdir -p /usr/local/bin
- Install the binary for all users
- What it does: copies
conforminto/usr/local/binso every user can run it.
sudo cp ./conform /usr/local/bin/
- Ensure the binary is executable
- What it does: sets execute permissions so the system can run the binary.
sudo chmod +x /usr/local/bin/conform
- macOS only: clear the quarantine attribute (if present)
- What it does: removes Gatekeeper’s quarantine flag that can block first‑run of manually downloaded binaries. No output if the attribute is absent.
xattr -d com.apple.quarantine /usr/local/bin/conform 2>/dev/null || true
- Verify the installation
- What it does: confirms Conform is on your
PATHand prints version/help.
conform --version
conform --help
Local install (single current user)
This path installs to ~/.local/bin without sudo, using the provided installer script. It also adds ~/.local/bin to your PATH in ~/.zshrc if it’s missing.
- Download and unzip the release archive
- What it does: extracts the
conformbinary from the downloaded zip.
unzip conform.zip
- Optional: verify the published checksum before installing
- What it does: computes the SHA‑256 of the downloaded archive and lets you compare it with the value published on the website.
shasum -a 256 conform.zip
# or
openssl dgst -sha256 conform.zip
- Install to
~/.local/binusing the installer script
- What it does: copies the binary to
~/.local/bin/conform, adds~/.local/binto yourPATHin~/.zshrcif needed, and on macOS removes the quarantine attribute.
bash sh/install.sh --bin ./conform
- Refresh your shell session (if
conformis not yet found)
- What it does: reloads your shell so updated
PATHtakes effect immediately.
exec zsh
- Verify the installation
- What it does: confirms Conform is on your
PATHand prints version/help.
conform --version
conform --help
Quick Start
Initialize in a repository:
conform init --source ../my-template
Preview the plan (and diffs):
conform plan --config .conform.toml --dest . --diff
Apply safely:
conform apply --config .conform.toml --dest .
# or preview writes without changing files
conform apply --config .conform.toml --dest . --dry-run
Housekeeping:
conform status --config .conform.toml --dest .
conform restore --stamp 20250909-121530 --dest .
conform clean-backups --yes
Examples
Example excerpts below illustrate typical behavior. Outputs are abbreviated for clarity.
Plan diff (excerpt)
conform plan --config .conform.toml --dest . --diff
# shows a readable diff of what would change; no files are written
--- a/.editorconfig
+++ b/.editorconfig
@@
indent_style = space
indent_size = 2
--- a/package.json
+++ b/package.json
@@ scripts
"build": "next build",
+ "lint": "next lint"
@@ packageManager
+ "packageManager": "yarn@<version>"
package.json merge (additive)
Before (destination):
{
"name": "my-app",
"scripts": { "build": "next build" }
}
Template (source):
{
"scripts": { "build": "next build", "lint": "next lint" },
"packageManager": "yarn@x.y.z",
"engines": { "node": ">=20" }
}
After:
{
"name": "my-app",
"scripts": { "build": "next build", "lint": "next lint" },
"packageManager": "yarn@x.y.z",
"engines": { "node": ">=20" }
}
Notes: scripts are added but not overwritten; packageManager is enforced to Yarn; missing engines.node can be set when enabled in config.
Ignore files (union by unique line)
Destination .gitignore:
.env
.next
Template .gitignore:
.env
node_modules
Result:
.env
.next
node_modules
Manual review items
Some files (e.g., next.config.mjs) are flagged for manual review to avoid risky automatic changes. They appear in the plan as ManualReview items so you can copy/adapt changes by hand.
Core Concepts
A template — or any existing project you choose to serve as the “template” — is your source of truth. Conform walks that tree, compares it to your destination repo, and proposes a minimal, reviewable plan of changes. The .conform.toml configuration tells Conform where that source lives, which files to consider (and exclude), and how to merge specific formats.
Example .conform.toml:
[source]
path = "../my-template"
[include]
globs = [
".editorconfig",
".prettierignore",
"prettier.config.js",
".eslintignore",
".eslintrc.json",
".stylelintrc.json",
".nvmrc",
"postcss.config.js",
"tailwind.config.js",
".husky/**",
".gitignore",
"jsconfig.json",
"commitlint.config.js",
]
[exclude]
globs = [
".git/**",
"node_modules/**",
".next/**",
"__dist/**",
".idea/**",
".yarn-cache/**",
"out/**",
"public/**",
]
[merge]
package_json = true
eslintrc_json = true
jsconfig_json = true
stylelintrc_json = true
union_ignore_files = true
set_package_manager = "auto"
update_engines = true
Configuration Reference
Top‑level keys in .conform.toml:
source.path(string)- Absolute or relative path to the template directory.
include.globs(array[string])- Glob patterns relative to
source.pathspecifying which files to consider.
- Glob patterns relative to
exclude.globs(array[string])- Glob patterns to exclude (e.g.,
node_modules/**, build outputs).
- Glob patterns to exclude (e.g.,
merge.package_json(bool)- Enable
package.jsonmerge behaviors (additive scripts/metadata, Yarn policy).
- Enable
merge.eslintrc_json,merge.jsconfig_json,merge.stylelintrc_json(bool)- Enable deep JSON merges for these config types.
merge.union_ignore_files(bool)- Union unique lines across ignore files like
.gitignore,.eslintignore,.prettierignore.
- Union unique lines across ignore files like
merge.set_package_manager("auto"|"yarn"|"none")autoenforces Yarn based on template policy;yarnalways;nonedisables.
merge.update_engines(bool)- If
true, setsengines.nodefrom the template when missing in destination.
- If
Detection gates (destination‑aware behavior):
- Next.js
- Enabled when a
nextdependency or anynext.config.*exists.
- Enabled when a
- Tailwind
- Enabled when a
tailwindcssdependency ortailwind.config.*/postcss.config.*exists.
- Enabled when a
- Git integrations
- Enabled when a
.git/directory exists (e.g., to include.husky/**,commitlint.config.js).
- Enabled when a
How It Works
The planner walks source.path, applies include/exclude globs, compares with your destination, and proposes a minimal set of operations. Destination‑aware detection gates include Next.js, Tailwind, and Git; they ensure only relevant files are considered. The executor computes merged results first and only writes when content actually changes; any overwrite creates a sibling backup and records details in a manifest. Dry‑run flags are available to simulate both apply and cleanup.
Conform performs a small set of predictable operations: add new files, safely overwrite with backups, deep‑merge JSON, union ignore files by unique lines, flag risky files (like next.config.mjs) for manual review, and remove conflicting lockfiles when enforcing Yarn.
JSON merges are tailored to keep intent clear. Generic config files like .eslintrc.json, jsconfig.json, and .stylelintrc.json are merged deeply; arrays are unioned by value without disturbing existing order; scalars are set only when the destination is null. For package.json, scripts are added but never clobbered, essential metadata can be filled in when missing, and the package manager is enforced to Yarn to avoid tool conflicts.
Safety & Rollback
Overwrites create backups named filename-conform-<YYYYMMDD-HHMMSS>.backup next to the original file, and every run writes a .conform-manifest-<stamp>.json capturing counts, operations, and backup locations. Restoring from a given stamp reverts changes reliably.
Example manifest (truncated):
{
"stamp": "20250909-121530",
"applied": 12,
"skipped": 3,
"backed_up": 5,
"applied_ops": [
{ "kind": "CopyNew", "src": "template/.editorconfig", "dest": ".editorconfig" },
{ "kind": "MergeJson::PackageJsonScripts", "src": "template/package.json", "dest": "package.json" }
],
"backups": [
{ "original": "package.json", "backup": "package.json-conform-20250909-121530.backup" }
]
}
Rollback & clean:
conform restore --stamp 20250909-121530
conform clean-backups --yes
Examples
Example excerpts below illustrate typical behavior. Outputs are abbreviated for clarity.
Plan diff (excerpt)
conform plan --config .conform.toml --dest . --diff
# shows a readable diff of what would change; no files are written
--- a/.editorconfig
+++ b/.editorconfig
@@
indent_style = space
indent_size = 2
--- a/package.json
+++ b/package.json
@@ scripts
"build": "next build",
+ "lint": "next lint"
@@ packageManager
+ "packageManager": "yarn@<version>"
package.json merge (additive)
Before (destination):
{
"name": "my-app",
"scripts": { "build": "next build" }
}
Template (source):
{
"scripts": { "build": "next build", "lint": "next lint" },
"packageManager": "yarn@x.y.z",
"engines": { "node": ">=20" }
}
After:
{
"name": "my-app",
"scripts": { "build": "next build", "lint": "next lint" },
"packageManager": "yarn@x.y.z",
"engines": { "node": ">=20" }
}
Notes: scripts are added but not overwritten; packageManager is enforced to Yarn; missing engines.node can be set when enabled in config.
Ignore files (union by unique line)
Destination .gitignore:
.env
.next
Template .gitignore:
.env
node_modules
Result:
.env
.next
node_modules
Manual review items
Some files (e.g., next.config.mjs) are flagged for manual review to avoid risky automatic changes. They appear in the plan as ManualReview items so you can copy/adapt changes by hand.
Safety & Rollback
Overwrites create backups named filename-conform-<YYYYMMDD-HHMMSS>.backup next to the original file, and every run writes a .conform-manifest-<stamp>.json capturing counts, operations, and backup locations. Restoring from a given stamp reverts changes reliably.
Example manifest (truncated):
{
"stamp": "20250909-121530",
"applied": 12,
"skipped": 3,
"backed_up": 5,
"applied_ops": [
{ "kind": "CopyNew", "src": "template/.editorconfig", "dest": ".editorconfig" },
{ "kind": "MergeJson::PackageJsonScripts", "src": "template/package.json", "dest": "package.json" }
],
"backups": [
{ "original": "package.json", "backup": "package.json-conform-20250909-121530.backup" }
]
}
Rollback & clean:
conform restore --stamp 20250909-121530
conform clean-backups --yes
Safety & Privacy
Review changes first with conform plan --diff. Every overwrite is backed up, and each run records a manifest so you can see exactly what changed and roll back with confidence. Conform is local‑first and has no telemetry — we do not collect or transmit any usage or personal information.
Update Checks
Conform performs a tiny, notification‑only update check at most once every 4 hours by fetching a small JSON file from the website.
- Cadence and scope
- Checks at most once every 4 hours; fails fast and silently when offline.
- Purely informational: does not download or install updates automatically.
- When and where it prints
- Prints a one‑line notice to stderr after a command finishes if a newer version exists.
- Once a newer version is detected, the notice appears on every run until you upgrade.
- Force a check now
conform --check-updates- Prints either “Up to date (vX.Y.Z)” or an “Update available” line. Exit code remains 0.
- Privacy
- No telemetry. The check is a single anonymous HTTPS GET to a static file; nothing is uploaded.
CI
- Platform: macOS runners only for now (binary distribution is macOS Apple Silicon). For other platforms, contact us via the website.
- Non-blocking review flow:
conform plan --config .conform.toml --dest . --diff > conform-plan.diff # attach conform-plan.diff as a CI artifact or post-process into PR comments - Blocking flow (fail when changes would be applied):
out=$(conform plan --config .conform.toml --dest . --diff) echo "$out" > conform-plan.diff # naive check: fail when the diff contains additions/removals if echo "$out" | rg -q "^\+\+\+|^--- |^@@ "; then echo "Conform found differences. See conform-plan.diff" >&2 exit 1 fi - Exit codes: non-zero indicates an error. Conform does not currently use a special exit code to indicate “changes planned”, so use output inspection when you want to block on differences.
CLI Reference
These commands and flags match conform --help output.
-
Global flags
--check-updates- Force an immediate update check (notification‑only; exit code unaffected).
-
conform init --source <dir> [--force]- Initialize a
.conform.tomlin the current directory - Flags:
--source <dir>(required),--force
- Initialize a
-
conform plan [--config <file>] [--dest <path>] [--diff]- Compute and print the plan of operations
- Flags:
--config,--dest,--diff
-
conform status [--config <file>] [--dest <path>]- Show a concise summary of what would change
- Flags:
--config,--dest
-
conform apply [--config <file>] [--dest <path>] [--dry-run]- Apply the plan of operations
- Flags:
--config,--dest,--dry-run
-
conform restore --stamp <yyyymmdd-hhmmss> [--dest <path>]- Restore files from backups with a given timestamp
- Flags:
--stamp(required),--dest
-
conform clean-backups [--dest <path>] [--dry-run] [--yes]- Remove all conform backup files under the working directory
- Flags:
--dest,--dry-run,--yes
FAQ
Will Conform overwrite my existing package.json scripts?
No — it adds missing scripts from your template and leaves existing ones intact.
Does it require Yarn?
Yes — during package.json merges Conform enforces packageManager: "yarn" and removes incompatible lockfiles to avoid tool conflicts.
How do I preview changes safely?
Use conform plan --diff or run apply with --dry-run.
Can I limit what files are considered?
Yes — tune include.globs and exclude.globs in .conform.toml.
Does it work without internet? Yes — it runs fully locally with no telemetry.
Which files require manual review?
Certain files (like next.config.mjs) are flagged intentionally.
Will my .gitignore be replaced?
No — ignore files are merged by unique lines.
Can I roll back? Yes — every overwrite is backed up and the run is recorded in a manifest.
Support & Docs
- Docs and downloads: https://conform.theodd0ne.com
- Changelog: see the homepage for the current version and release notes.
Troubleshooting
- “source.path is not a directory” → ensure
.conform.tomlpoints to the correct template root. - Plan shows no changes but files differ → check
include.globs/exclude.globsand detection gates. - No backups were written → backups are created only when content actually changes and is written.
- Package manager changed → current behavior sets
packageManager: "yarn"duringpackage.jsonmerges. - Diffs show unexpected changes → confirm your template files and globs include/exclude rules.
ManualReviewitems keep appearing → these files require a hand pass; copy over changes selectively.- CI artifacts are empty → ensure
--diffis passed toplanand that the job runs in the destination repo root.
Additional Configuration Notes
- Sensible defaults exist for Node/Next/Tailwind projects via
include.globs/exclude.globs. merge.set_package_managerandmerge.update_enginescontrol Yarn enforcement and Node engine updates.- Future: preset recipes may be introduced to simplify common setups.
Limitations
- Node.js‑first focus for now (package.json, ESLint/Prettier/Stylelint configs, ignore files).
- JSON only; no YAML/TOML merges beyond
.conform.tomlconfiguration. - No 3‑way merges; merges are additive/deep from template to destination.
- Manual review for risky files (e.g.,
next.config.mjs).
Upgrade
Upgrade by replacing the installed binary with a newer release and re‑verifying.
- System‑wide install
unzip conform.zip sudo cp ./conform /usr/local/bin/ sudo chmod +x /usr/local/bin/conform xattr -d com.apple.quarantine /usr/local/bin/conform 2>/dev/null || true conform --version - Local install (single user)
unzip conform.zip bash sh/install.sh --bin ./conform exec zsh # if PATH was updated conform --version
Uninstall
Remove the installed binary from your PATH:
- System‑wide install
sudo rm -f /usr/local/bin/conform - Local install (single user)
rm -f "$HOME/.local/bin/conform"
Optional cleanup:
- If the local installer added
export PATH="$HOME/.local/bin:$PATH"to~/.zshrc, remove that line. - Remove any created backups and manifests in your projects when you no longer need them:
find . -name '*-conform-*.backup' -delete rm -f ./.conform-manifest-*.json
Copyright
Copyright (c) 2025 Igor Maric. All rights reserved.