wyrd/tools
esc

    ↑↓ Move ↵ Open toha results first Search by Pagefind
    Releases

    Injecting into existing files

    files and the source subdirectory write whole files. inject changes one bounded part of a file that already exists, and leaves the rest alone. Each rule names its target with into, a path relative to the target directory, and is one of two kinds: a visible region in a text file, or a typed value at a path in a JSON, JSONC, or JSON5 file.

    Toha resolves every injection in memory before it writes anything, so a failure leaves every file unchanged, and it replaces each changed file atomically. A rule that changes nothing writes nothing. A rule fails when its target is missing unless you set create: true.

    For the inverse ownership, a generated file can reserve user-editable sections.

    A managed region

    A region rule owns a block of lines, marked by comments and a checksum:

    inject:
      - into: "config/app.toml"
        region: features
        content: |
          analytics_enabled = true
          analytics_sample_rate = 0.1
        anchor: { after: "[application]", occurrence: only }

    The first apply places the block after the anchor line:

    [application]
    # >>> toha:region features >>>
    analytics_enabled = true
    analytics_sample_rate = 0.1
    # <<< toha:end features sha256:a7f3d… <<<
    name = "sample-service"

    Toha owns the marker pair and the bytes between them. On later applies it finds the markers rather than the anchor, so the block does not duplicate. If the template body is unchanged, the apply is a no-op. If you change the template body, Toha replaces only that block. If someone edits inside the block, that is drift: apply refuses it, prints config/app.toml (features), and names the same command with --force. Updates and previews protect previously edit-owned regions with the same rule, including when the template’s region body is unchanged, retracts the region, or covers it with a whole-file rule. A whole-file takeover also compares committed content outside previously owned edits with planned whole-file content outside its owned edits. These must match even without an operator change; region force does not override a conflict there. See updating projects for manual recovery. A staged update takes --force on apply PATH, not on stage. Trust and saved answers do not grant force. --force restores the owned body and never invents a missing anchor.

    The block’s body comes from an inline content template or, with source, the rendered body of a support file. The comment style is inferred from the target’s extension; set it explicitly with marker — a string is a line-comment prefix, and { open, close } is a block comment:

    inject:
      - into: "index.html"
        region: analytics
        marker: { open: "<!--", close: "-->" }
        content: "<script src=\"/analytics.js\"></script>"

    anchor.occurrence is only by default, which requires exactly one matching line; use first or last to pick among several. With no anchor, the block is appended at the end of the file.

    A typed value in a JSON-family file

    A struct rule owns one value at a path in a .json, .jsonc, or .json5 file:

    inject:
      - into: "package.json"
        struct:
          path: "scripts.build"
          value: "tsc"

    Given this file:

    {
      "name": "sample-app",
      "scripts": {
        "start": "node index.js"
      }
    }

    the apply adds the owned value and keeps everything else:

    {
      "name": "sample-app",
      "scripts": {
        "start": "node index.js",
        "build": "tsc"
      }
    }

    Toha owns the value at scripts.build. A second apply changes no bytes. If someone changes that owned value, the next apply converges it back to "tsc" — no --force needed, because a declared value is not drift. Every unrelated key, and in JSONC and JSON5 every comment, key order, quote style, and trailing comma, is preserved. Toha adds no marker comment to a JSON-family file.

    path is dot-separated object keys and bracketed array indexes, such as routes[0].name; a backslash escapes ., [, ], and \ in a key. Missing object parents are created. An array in the path must already exist and each index must be in range. value is any JSON type; its string leaves are templates, so value: "{{ name }}" renders and stays a JSON string, while value: true or value: 42 keeps its type.

    A region rule cannot target a .json, .jsonc, or .json5 file — use struct for a typed value, or a whole-file files rule. Whole-file JSON generation is unchanged.

    Previewing

    apply --dry-run reports what each rule would do without writing:

    inject package.json (scripts.build)
    update config/app.toml (features)

    inject marks a region or value it would place, update one it would change, and the owner in parentheses is the region key or the typed path.

    For commands to run after writing files, continue with Hooks and messages.