Conform

Node.js gone rogue? Conform will sort it out.

Conform eliminates config drift across Node.js repositories. Point it at your template (package.json, ESLint, Prettier, Husky, Tailwind/PostCSS, ignore files, and more) and it will plan, diff, merge, back up, and apply only what’s needed — safely and predictably.

Copyright (c) 2025. Igor Maric. All rights reserved.

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 (restore by 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)

  1. Download and unzip the release archive
  • What it does: extracts the conform binary from the downloaded zip.
unzip conform.zip
  1. 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
  1. Create the system binary directory if needed
  • What it does: ensures /usr/local/bin exists, a common system‑wide location on macOS that is already on PATH.
sudo mkdir -p /usr/local/bin
  1. Install the binary for all users
  • What it does: copies conform into /usr/local/bin so every user can run it.
sudo cp ./conform /usr/local/bin/
  1. 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
  1. 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
  1. Verify the installation
  • What it does: confirms Conform is on your PATH and 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.

  1. Download and unzip the release archive
  • What it does: extracts the conform binary from the downloaded zip.
unzip conform.zip
  1. 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
  1. Install to ~/.local/bin using the installer script
  • What it does: copies the binary to ~/.local/bin/conform, adds ~/.local/bin to your PATH in ~/.zshrc if needed, and on macOS removes the quarantine attribute.
bash sh/install.sh --bin ./conform
  1. Refresh your shell session (if conform is not yet found)
  • What it does: reloads your shell so updated PATH takes effect immediately.
exec zsh
  1. Verify the installation
  • What it does: confirms Conform is on your PATH and 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.path specifying which files to consider.
  • exclude.globs (array[string])
    • Glob patterns to exclude (e.g., node_modules/**, build outputs).
  • merge.package_json (bool)
    • Enable package.json merge behaviors (additive scripts/metadata, Yarn policy).
  • 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.
  • merge.set_package_manager ("auto"|"yarn"|"none")
    • auto enforces Yarn based on template policy; yarn always; none disables.
  • merge.update_engines (bool)
    • If true, sets engines.node from the template when missing in destination.

Detection gates (destination‑aware behavior):

  • Next.js
    • Enabled when a next dependency or any next.config.* exists.
  • Tailwind
    • Enabled when a tailwindcss dependency or tailwind.config.*/postcss.config.* exists.
  • Git integrations
    • Enabled when a .git/ directory exists (e.g., to include .husky/**, commitlint.config.js).

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.toml in the current directory
    • Flags: --source <dir> (required), --force
  • 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

Troubleshooting

  • “source.path is not a directory” → ensure .conform.toml points to the correct template root.
  • Plan shows no changes but files differ → check include.globs / exclude.globs and 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" during package.json merges.
  • Diffs show unexpected changes → confirm your template files and globs include/exclude rules.
  • ManualReview items keep appearing → these files require a hand pass; copy over changes selectively.
  • CI artifacts are empty → ensure --diff is passed to plan and 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_manager and merge.update_engines control 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.toml configuration.
  • 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 (c) 2025 Igor Maric. All rights reserved.