Jostraca code generation, made repeatable

Generate in memory

Run a generator on a virtual filesystem with mem, and read the result back from the volume.

Rendered from docs/how-to/generate-in-memory.md in the generator repository — where a correction belongs, and where the test suite runs every example on this page or states why it does not.

mem: true runs the whole generate on an in-memory filesystem. Nothing touches disk, and the result carries vol() so you can read back what was written.

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

const jostraca = Jostraca({ mem: true })

const res = await jostraca.generate({ folder: '/out' }, () => {
  Project({ folder: 'app' }, () => {
    File({ name: 'index.js' }, () => Content('// entry\n'))
    Folder({ name: 'src' }, () => {
      File({ name: 'lib.js' }, () => Content('// lib\n'))
    })
  })
})

const vol = res.vol().toJSON()
const paths = Object.keys(vol).filter((p) => !p.includes('/.jostraca/'))
console.log(JSON.stringify(paths.sort()))
console.log(JSON.stringify(vol['/out/app/index.js']))
["/out/app/index.js","/out/app/src/lib.js"]
"// entry\n"

Use an absolute output folder in memory mode. A relative one resolves against the process working directory, so the volume keys come back prefixed with wherever the process happened to be.

Seed input files with vol, which is how a fragment or a copy source gets there without a temp directory:

import { Jostraca, Project, File, Fragment } from 'jostraca'

const jostraca = Jostraca({
  mem: true,
  vol: { '/tpl/header.txt': 'HEADER\n' },
})

const res = await jostraca.generate({ folder: '/out' }, () => {
  Project({}, () => {
    File({ name: 'a.txt' }, () => Fragment({ from: '/tpl/header.txt' }))
  })
})

console.log(JSON.stringify(res.vol().toJSON()['/out/a.txt']))
"HEADER\n"

Three rules about the pairing, because they are not symmetrical:

  • vol without mem does nothing. No virtual filesystem is built and the run goes to the real one. This is the mistake to check first when a memory-mode test writes real files.
  • A global mem: true with no per-call vol shares one volume for the life of the instance, so state accumulates across generate() calls. That is useful for a two-run test and surprising otherwise.
  • A per-call vol forks: it seeds a fresh volume from the global seed merged with yours, and that call’s writes never reach the shared one.

The in-memory filesystem is part of jostraca. Nothing is installed for it, and it is not a filesystem you can pass around outside a run.

The Go port takes the same pair: WithMem() switches the in-memory filesystem on and WithVol() seeds it, and the result carries Vol and FS. WithFS(NewMemFS()) is still available when you want to seed the provider by writing into it. The Go reference shows both, along with the three rules they share with TypeScript.

Before v0.35.0 both Go options were inert—a generator configured with them wrote to the real filesystem and returned nil for Vol and FS, with no error.

See also#