DEV Community

Samantha Paras
Samantha Paras

Posted on AI-assisted

Jostraca Review: A Good Merge Engine with Unsafe Defaults

Most code generators assume you never touch what they produce. That assumption rarely holds. Someone adds a prop or fixes a class name, and the next regeneration wipes it out.

Jostraca is an open-source JavaScript code generator that takes this problem head on. It says it can regenerate over hand-edited files and keep your edits. That is a big claim, so I tested it. I built a small React component generator, edited the output by hand, and changed the generator to see what survived.

Short version: the merge engine works when it has what it needs. But in my tests its failures were silent, and the most dangerous one happens on an ordinary git clone.

What I tested: jostraca@0.36.6 on Node 24, with no other dependencies, through a small JavaScript generator for text files. I didn't test the Go port, binary files, or large real-world generators, so treat this as a focused hands-on review, not a complete audit.

Setup: plain JavaScript

A Jostraca generator is a JavaScript file. You nest components (Project, Folder, File, Content) the same way as the output folders, and loops are ordinary code. Here is the core of my generator, which writes one React component per entry in a model:

import { Jostraca, Project, Folder, File, Content, each } from 'jostraca'

const model = {
  app: { name: 'demo-ui' },
  components: [
    { name: 'Button', tag: 'button', prop: 'label' },
    { name: 'Card', tag: 'section', prop: 'title' },
  ],
}

const jostraca = Jostraca({ model })

const result = await jostraca.generate({ folder: './out' }, () => {
  Project({ folder: model.app.name }, () => {
    Folder({ name: 'src/components' }, () => {
      each(model.components, (c) => {
        File({ name: c.name + '.jsx' }, () => {
          Content(
`export function ${c.name}({ ${c.prop} }) {
  return <${c.tag} className="${c.name.toLowerCase()}">{${c.prop}}</${c.tag}>
}
`)
        })
      })
    })
  })
})
Enter fullscreen mode Exit fullscreen mode

Installing it added two packages in under a second, and the first run produced the components I expected. Writing generators in plain JavaScript feels approachable, especially if you already know JS. There is no template language to learn.

There was one thing I wasn't a fan of during setup, though. Jostraca creates a hidden out/.jostraca/ folder without really drawing attention to it. It holds a copy of every generated file, which is the "baseline" that merging depends on. It also writes its own .gitignore that keeps that copy out of version control. Nothing during setup tells you that this folder is essential. That turns out to matter a lot.

Experiment 1: Keeping a manual edit

I hand-edited the generated Button.jsx to add an onClick prop, as any developer might:

export function Button({ label, onClick }) {
  return <button className="button" onClick={onClick}>{label}</button>
}
Enter fullscreen mode Exit fullscreen mode

Then I ran the unchanged generator again in each mode Jostraca offers:

Mode Result for my onClick edit
default Removed. File overwritten.
preserve: true Removed, but a backup is saved as Button.old.jsx
write: false, present: true Kept. The new version goes to Button.new.jsx
merge: true Kept, with no extra files

Merge is the mode I would choose for a real project. It kept my change without leaving .old or .new files around.

The default behavior caught me off guard. It silently overwrites a developer's work: no warning, no backup, exit code 0. The docs do say this, but a tool whose main selling point is respecting hand edits shouldn't throw them away by default.

The names are also confusing. I expected preserve to leave my file alone. Instead, it replaces my file and saves a backup. present is the mode that actually leaves my file untouched, so it behaves the way I'd expect preserve to.

Experiment 2: Changing the generator

Okay, so merge kept my onClick change. Great! But what happens when the generator itself changes? That's where things get more interesting. With that edit in place, I updated the generator in a few ways and regenerated with merge.

A change far from my edit works. I had the generator add export default Button at the end of the file. Jostraca merged both changes cleanly:

export function Button({ label, onClick }) {
  return <button className="button" onClick={onClick}>{label}</button>
}

export default Button
Enter fullscreen mode Exit fullscreen mode

This is exactly the behavior I'd hope to see from a merge engine.

A change on the same line produces a conflict. When the generator renamed the CSS class on the line I had edited, Jostraca wrote git-style conflict markers:

<<<<<<< GENERATED: 2026-10-26T01:10:52.279Z/merge
export function Button({ label }) {
  return <button className="ui-button">{label}</button>
=======
export function Button({ label, onClick }) {
  return <button className="button" onClick={onClick}>{label}</button>
>>>>>>> EXISTING: 2026-10-26T01:10:52.208Z/merge
}
Enter fullscreen mode Exit fullscreen mode

To be fair, this conflict is expected behavior, not a flaw in Jostraca. The generator and I changed the same line, and no merge tool can decide which version is right. Git would conflict here too. (Git also conflicts on changes to neighbouring lines, and so did Jostraca when I added a header comment just above my edit.) My issue isn't that conflicts happen. It's how they're communicated and handled. Jostraca printed nothing and exited with code 0, reporting success even though the file now contains broken JSX. A later build or lint check might catch the broken file, but the generator itself doesn't flag it as a failure. The only sign is a conflicted list in the return value, which you have to know to check. I'd expect a conflict to produce a warning or a nonzero exit code, not let broken code through silently.

The fresh-clone problem

This is where I started getting a little concerned.

Merge needs the .jostraca/generated baseline to know what changed. That folder is excluded from git by default, so anyone who clones the repository won't have it. I simulated a fresh clone by deleting the folder, with my onClick edit in place, then regenerated with merge after a generator change:

// Generated by demo-ui. Do not edit.
export function Button({ label }) {
  return <button className="button">{label}</button>
}
Enter fullscreen mode Exit fullscreen mode

My edit was gone, with no conflict, no warning, and exit code 0. Jostraca's report listed the file as written, as if this were a normal first run.

This defeats the purpose of using merge in the first place. You turn on merge so hand edits are safe. Then a teammate clones the repo, runs the generator, and loses those edits with no indication that anything went wrong. The documentation does mention that a missing baseline means the file is "silently overwritten", but that is easy to miss, and nothing at runtime tells you.

What I'd want before trusting it

Until Jostraca changes its defaults, I would add my own guard around it:

import { existsSync } from 'node:fs'

if (existsSync('./out') && !existsSync('./out/.jostraca/generated')) {
  console.error('No merge baseline found: refusing to regenerate over existing files.')
  process.exit(1)
}

// ... jostraca.generate(...) with existing: { txt: { write: true, merge: true } }

if (result.files.conflicted.length > 0) {
  console.error('Merge conflicts in:', result.files.conflicted.join(', '))
  process.exit(1)
}
Enter fullscreen mode Exit fullscreen mode

I tested this safeguard against the conflict and missing-baseline cases above. It protected my manual edit in those scenarios. However, it's a conservative check designed for this project setup, not a universal solution. Most developers wouldn't know to add a safeguard like this in the first place. Here's what I'd like to see from the project itself:

  • A nonzero exit code, or at least a loud warning, on conflicts, so CI catches broken output.
  • A warning when merge is enabled but the baseline is missing, instead of falling back to an overwrite.
  • Making the baseline's importance clear during setup, including whether it should be committed.
  • Clearer mode names, so preserve does what the word suggests.

Verdict

Jostraca has a good core idea, and a merge engine that does what it promises when it has what it needs. Plain JavaScript generators are approachable, and the non-conflicting merge in my tests worked well.

But in my tests, the failures were silent. The default overwrites edits, conflicts exit with success, and a missing baseline quietly turns merge back into overwrite. I see real potential in Jostraca, but I wouldn't trust it with important project files without safeguards in place.

With safer defaults, clearer documentation, and better visibility into failures, Jostraca could be a tool I'd feel comfortable using in a real project. For a tool designed to protect manual edits, knowing when those edits aren't protected is just as important as the merge itself.

Have you used Jostraca or another generator that supports merging? I'm curious how other developers handle manual edits without risking them during regeneration.

Top comments (0)