New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@claudiu-ceia/spatch

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@claudiu-ceia/spatch

Structural patcher for TS/JS using a patch-document template format.

Source
npmnpm
Version
0.2.0
Version published
Weekly downloads
6
20%
Maintainers
1
Weekly downloads
 
Created
Source

spatch

spatch applies structural rewrites using a patch document.

A patch document is a text block where:

  • - lines define what to match
  • + lines define what to insert
  • other lines are context shared by both sides

You can pass the patch document either:

  • inline as a string
  • as a file path

CLI

spatch <patch-input> [scope] [--cwd <path>] [--dry-run] [--json] [--no-color] [--interactive]
# or:
astkit patch <patch-input> [scope] [--cwd <path>] [--dry-run] [--json] [--no-color] [--interactive]

Examples:

# patch document from file
spatch rules/const-to-let.spatch src --cwd /repo

# inline patch document
spatch $'-const :[name] = :[value];\n+let :[name] = :[value];' src

# preview only
spatch rules/const-to-let.spatch src --dry-run

# structured JSON output
spatch rules/const-to-let.spatch src --json

# interactive apply mode
spatch rules/const-to-let.spatch src --interactive

API

import { patchProject } from "@claudiu-ceia/spatch";

await patchProject(patchInput, {
  cwd: "/repo",       // optional, default process.cwd()
  scope: "src",       // file or directory, default "."
  dryRun: false,       // default false
  encoding: "utf8",   // default utf8
});

patchInput can be:

  • a patch document string
  • a path to a patch file (resolved from cwd)

Patch Document Grammar

Line kinds

  • -...: deletion line (belongs to match pattern only)
  • +...: addition line (belongs to replacement only)
  • ...: context line (belongs to both pattern and replacement)
  • \-... and \+...: escaped marker lines, treated as literal context starting with - or +

Minimum change rule

A patch document must contain at least one - line or one + line.

Newline behavior

If the patch document ends with a trailing newline, both generated pattern and replacement preserve it.

Metavariables

Inside pattern/replacement text, holes use this syntax:

  • :[name]
  • :[_] (anonymous hole, not captured)
  • :[name~regex] (capture must satisfy regex)
  • ... (variadic wildcard; captured and reusable in replacement)

Examples:

-const :[name] = :[value];
+let :[name] = :[value];
-const :[name~[a-z]+] = :[value~\d+];
+let :[name] = Number(:[value]);

Repeated holes enforce equality:

-:[x] + :[x];
+double(:[x]);

foo + foo matches, foo + bar does not.

Variadic example:

-foo(:[x], ...);
+bar(:[x], ...);

Rewrites the callee and preserves remaining arguments.

How It Works

1) Parse phase

patchProject resolves patchInput into a patch document and parses it into:

  • pattern
  • replacement

2) Rewrite phase

For each scoped file:

  • compile template tokens from pattern
  • find all structural matches
  • render replacement with captures
  • apply replacements
  • optionally write file (skipped in dryRun)

3) Output phase

Return an aggregate result:

  • files scanned/matched/changed
  • match and replacement counts
  • elapsed time
  • per-file occurrences with spans and captures

Structural Balancing

Hole captures are checked for structural balance to avoid malformed partial captures.

Balanced constructs supported in capture chunks:

  • parentheses (...)
  • brackets [...]
  • braces {...}
  • single/double/template strings
  • line and block comments

End-to-End Example

Patch document:

function wrap() {
-  const value = :[value];
+  let value = :[value];
   return value;
}

Call:

await patchProject("rules/wrap.spatch", { cwd: "/repo", scope: "src" });

Flow Diagram

patchProject(patchInput, options)
  -> parsePatchInvocation
      -> resolve patch text (inline or file)
      -> parsePatchDocument (+/-/context)
  -> rewriteProject
      -> compileTemplate(pattern)
      -> collect files
      -> for each file: match -> render -> apply -> (write)
  -> buildSpatchResult

FAQs

Package last updated on 11 Feb 2026

Related posts