jostraca 0.31.1 → 0.31.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -0
- package/package.json +2 -1
package/README.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# jostraca
|
|
2
|
+
|
|
3
|
+
A code and project generator that uses React-style components to define
|
|
4
|
+
files, folders, and content declaratively. This is the canonical
|
|
5
|
+
TypeScript implementation, published to npm as
|
|
6
|
+
[`jostraca`](https://www.npmjs.com/package/jostraca). (A feature-parity Go
|
|
7
|
+
port lives at [`github.com/jostraca/jostraca/go`](https://pkg.go.dev/github.com/jostraca/jostraca/go).)
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/jostraca)
|
|
10
|
+
[](https://github.com/jostraca/jostraca/blob/master/LICENSE)
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install jostraca
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Peer dependencies (install the ones your usage needs):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install memfs shape
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`memfs` backs in-memory generation; `shape` backs option validation. Both
|
|
23
|
+
are loose ranges (`memfs >=4`, `shape >=10`).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
This README is organised along the four [Diátaxis](https://diataxis.fr)
|
|
28
|
+
documentation modes. Jump to the one that fits what you need right now:
|
|
29
|
+
|
|
30
|
+
- **[Tutorial](#tutorial)** — learning-oriented. Build your first
|
|
31
|
+
generator from scratch.
|
|
32
|
+
- **[How-to guides](#how-to-guides)** — task-oriented. Recipes for
|
|
33
|
+
specific jobs.
|
|
34
|
+
- **[Reference](#reference)** — information-oriented. Every component,
|
|
35
|
+
option, and utility.
|
|
36
|
+
- **[Explanation](#explanation)** — understanding-oriented. How and why
|
|
37
|
+
Jostraca works the way it does.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Tutorial
|
|
42
|
+
|
|
43
|
+
*A short lesson. Follow it top to bottom and you'll have a working
|
|
44
|
+
generator.*
|
|
45
|
+
|
|
46
|
+
### 1. A first file tree
|
|
47
|
+
|
|
48
|
+
Create a generator, then describe a tree of components. Nesting the
|
|
49
|
+
components mirrors the folders and files you want on disk.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { Jostraca, Project, Folder, File, Content } from 'jostraca'
|
|
53
|
+
|
|
54
|
+
const jostraca = Jostraca()
|
|
55
|
+
|
|
56
|
+
await jostraca.generate({ folder: './out' }, () => {
|
|
57
|
+
Project({ folder: 'my-app' }, () => {
|
|
58
|
+
|
|
59
|
+
Folder({ name: 'src' }, () => {
|
|
60
|
+
File({ name: 'index.js' }, () => {
|
|
61
|
+
Content('console.log("hello world")\n')
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
File({ name: 'package.json' }, () => {
|
|
66
|
+
Content('{ "name": "my-app" }\n')
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Run it, and Jostraca writes:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
out/
|
|
76
|
+
my-app/
|
|
77
|
+
src/
|
|
78
|
+
index.js -> console.log("hello world")
|
|
79
|
+
package.json -> { "name": "my-app" }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
You declared a tree; Jostraca built it. That is the whole model.
|
|
83
|
+
|
|
84
|
+
### 2. Insert values with a template
|
|
85
|
+
|
|
86
|
+
Real generators produce files that vary with input data. Pass a `model`
|
|
87
|
+
to `Jostraca()` and reference it in content with `$$path$$`:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const jostraca = Jostraca({
|
|
91
|
+
model: { app: { name: 'Acme', version: '1.0.0' } }
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
await jostraca.generate({ folder: './out' }, () => {
|
|
95
|
+
Project({}, () => {
|
|
96
|
+
File({ name: 'config.txt' }, () => {
|
|
97
|
+
Content('App: $$app.name$$ v$$app.version$$\n')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
// out/config.txt -> App: Acme v1.0.0
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3. Make a reusable component
|
|
105
|
+
|
|
106
|
+
When a shape repeats, capture it with `cmp()` — a plain function that
|
|
107
|
+
emits content. This is the "React-style" part: components compose.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { cmp, Content, each } from 'jostraca'
|
|
111
|
+
|
|
112
|
+
const FunctionDef = cmp(function FunctionDef(props: any) {
|
|
113
|
+
Content(`function ${props.name}(`)
|
|
114
|
+
Content(props.params.join(', '))
|
|
115
|
+
Content(') {\n')
|
|
116
|
+
each(props.ctx$.model.body, (line) => Content(` ${line}\n`))
|
|
117
|
+
Content('}\n')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
File({ name: 'utils.js' }, () => {
|
|
121
|
+
FunctionDef({ name: 'greet', params: ['name'] })
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
From here, the **How-to guides** below cover each capability in turn, and
|
|
126
|
+
the **Reference** documents every option.
|
|
127
|
+
|
|
128
|
+
## How-to guides
|
|
129
|
+
|
|
130
|
+
*Practical recipes. Each one is self-contained — read only the one you
|
|
131
|
+
need.*
|
|
132
|
+
|
|
133
|
+
### Read a template file and fill its slots
|
|
134
|
+
|
|
135
|
+
Use `Fragment` to read an external template, and `Slot` to replace marked
|
|
136
|
+
regions inside it.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// template.html contains:
|
|
140
|
+
// <html>
|
|
141
|
+
// <!-- <[SLOT:head]> -->
|
|
142
|
+
// <body>
|
|
143
|
+
// <!-- <[SLOT:body]> -->
|
|
144
|
+
// </body>
|
|
145
|
+
// </html>
|
|
146
|
+
|
|
147
|
+
File({ name: 'index.html' }, () => {
|
|
148
|
+
Fragment({ from: '/templates/template.html' }, () => {
|
|
149
|
+
Slot({ name: 'head' }, () => {
|
|
150
|
+
Content('<title>My Page</title>')
|
|
151
|
+
})
|
|
152
|
+
Slot({ name: 'body' }, () => {
|
|
153
|
+
Content('<h1>Hello</h1>')
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
An unnamed `<[SLOT]>` marker receives all non-`Slot` children of the
|
|
160
|
+
Fragment. Giving a Fragment non-`Slot` children when its source has no
|
|
161
|
+
unnamed `<[SLOT]>` marker is an error — there is nowhere for that content
|
|
162
|
+
to go, and it would otherwise be discarded silently.
|
|
163
|
+
|
|
164
|
+
### Copy files and directories
|
|
165
|
+
|
|
166
|
+
`Copy` brings in existing files or whole directory trees, applying
|
|
167
|
+
template substitution to text files (binaries pass through untouched):
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const jostraca = Jostraca({ model: { title: 'My App' } })
|
|
171
|
+
|
|
172
|
+
await jostraca.generate({ folder: './out' }, () => {
|
|
173
|
+
Project({ folder: 'app' }, () => {
|
|
174
|
+
Folder({ name: 'static' }, () => {
|
|
175
|
+
Copy({ from: '/templates/assets' })
|
|
176
|
+
Copy({ from: '/templates/readme.txt', to: 'README.txt' })
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Edit an existing file in place
|
|
183
|
+
|
|
184
|
+
`Inject` replaces the content between two markers in a file that already
|
|
185
|
+
exists, leaving the rest untouched:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// existing foo.txt:
|
|
189
|
+
// HEADER
|
|
190
|
+
// #--START--#
|
|
191
|
+
// old content
|
|
192
|
+
// #--END--#
|
|
193
|
+
// FOOTER
|
|
194
|
+
|
|
195
|
+
Project({}, () => {
|
|
196
|
+
Inject({ name: 'foo.txt' }, () => {
|
|
197
|
+
Content('new content')
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
// Result: HEADER\n#--START--#\nnew content\n#--END--#\nFOOTER
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Regenerate without clobbering hand edits
|
|
204
|
+
|
|
205
|
+
By default Jostraca overwrites. When users edit generated files, choose a
|
|
206
|
+
gentler mode per file extension:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
await jostraca.generate({
|
|
210
|
+
folder: './out',
|
|
211
|
+
existing: {
|
|
212
|
+
txt: {
|
|
213
|
+
write: true, // overwrite existing files (default)
|
|
214
|
+
preserve: true, // keep a .old. backup of what was overwritten
|
|
215
|
+
present: false, // write to .new. instead of overwriting
|
|
216
|
+
diff: false, // write an annotated 2-way diff
|
|
217
|
+
merge: false, // 3-way merge with conflict markers
|
|
218
|
+
},
|
|
219
|
+
bin: {
|
|
220
|
+
write: true,
|
|
221
|
+
preserve: false,
|
|
222
|
+
present: false,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}, root)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
For files a user should own outright, add the line `# JOSTRACA_PROTECT`
|
|
229
|
+
anywhere in the generated file — Jostraca will never overwrite it on
|
|
230
|
+
subsequent runs.
|
|
231
|
+
|
|
232
|
+
### Generate in memory (for tests or virtual FS)
|
|
233
|
+
|
|
234
|
+
Set `mem: true` and provide any template inputs via `vol`. Nothing touches
|
|
235
|
+
disk; read the result back from the returned volume:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const jostraca = Jostraca({
|
|
239
|
+
mem: true,
|
|
240
|
+
vol: { '/templates/header.txt': 'HEADER\n' }
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
const result = await jostraca.generate({ folder: '/' }, root)
|
|
244
|
+
|
|
245
|
+
const files = result.vol().toJSON()
|
|
246
|
+
// { '/output.txt': '...' }
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Reference
|
|
250
|
+
|
|
251
|
+
*Information-oriented. Look things up here; don't read it front to back.*
|
|
252
|
+
|
|
253
|
+
The complete, authoritative reference for every component, prop, option,
|
|
254
|
+
and utility is **[REFERENCE.md](./REFERENCE.md)**.
|
|
255
|
+
|
|
256
|
+
### Components
|
|
257
|
+
|
|
258
|
+
`Project`, `Folder`, `File`, `Content`, `Fragment`, `Slot`, `Inject`,
|
|
259
|
+
`Copy`, `Line`, `List` — plus custom components via `cmp()`.
|
|
260
|
+
|
|
261
|
+
### `generate()` result
|
|
262
|
+
|
|
263
|
+
`generate()` returns a `JostracaResult`:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
{
|
|
267
|
+
when: number, // timestamp of generation
|
|
268
|
+
files: {
|
|
269
|
+
written: string[], // files written to disk
|
|
270
|
+
preserved: string[], // backup copies created
|
|
271
|
+
presented: string[], // .new. files created
|
|
272
|
+
diffed: string[], // diff files created
|
|
273
|
+
merged: string[], // merged files created
|
|
274
|
+
conflicted: string[], // files with merge conflicts
|
|
275
|
+
unchanged: string[], // files left unchanged
|
|
276
|
+
},
|
|
277
|
+
audit: () => Audit[], // audit trail of operations
|
|
278
|
+
vol?: () => any, // virtual volume (mem mode)
|
|
279
|
+
fs?: () => FST, // file system (mem mode)
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Utility exports
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import {
|
|
287
|
+
each, // iterate arrays/objects with marking and sorting
|
|
288
|
+
get, // simple dot-path property access
|
|
289
|
+
getx, // advanced path access with operators
|
|
290
|
+
camelify, // 'foo_bar' -> 'FooBar'
|
|
291
|
+
snakify, // 'FooBar' -> 'foo_bar'
|
|
292
|
+
kebabify, // 'FooBar' -> 'foo-bar'
|
|
293
|
+
names, // generate all case variants of a name
|
|
294
|
+
template, // process template strings with model data
|
|
295
|
+
indent, // indent text content
|
|
296
|
+
cmp, // create custom components
|
|
297
|
+
deep, // deep merge objects
|
|
298
|
+
omap, // map over object entries (sorted key order)
|
|
299
|
+
} from 'jostraca'
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
See [REFERENCE.md](./REFERENCE.md) for signatures and edge-case behaviour.
|
|
303
|
+
|
|
304
|
+
## Explanation
|
|
305
|
+
|
|
306
|
+
*Understanding-oriented. Background and design — read this to build a
|
|
307
|
+
mental model, not to accomplish a specific task.*
|
|
308
|
+
|
|
309
|
+
### Two phases: define, then build
|
|
310
|
+
|
|
311
|
+
A `generate()` call runs in two distinct phases. First, the **define
|
|
312
|
+
phase** executes your callback: every component call (`Project`, `File`,
|
|
313
|
+
`Content`, …) records a node in an in-memory tree — nothing is written
|
|
314
|
+
yet. Then the **build phase** walks that tree and performs the real file
|
|
315
|
+
operations. Separating the two means the whole intended output is known
|
|
316
|
+
before a single byte is written, which is what makes existing-file
|
|
317
|
+
handling, protection, and merging possible.
|
|
318
|
+
|
|
319
|
+
### Why React-style components
|
|
320
|
+
|
|
321
|
+
Components nest to mirror the filesystem, and they compose: a component is
|
|
322
|
+
just a function that emits more components. That gives you ordinary
|
|
323
|
+
language tools — loops, conditionals, parameters, reuse via `cmp()` — for
|
|
324
|
+
describing structure, instead of a bespoke templating dialect. The nesting
|
|
325
|
+
is kept noise-free by an `AsyncLocalStorage` context, so child components
|
|
326
|
+
know their parent without you threading it through by hand.
|
|
327
|
+
|
|
328
|
+
### The philosophy of existing files
|
|
329
|
+
|
|
330
|
+
Generators are run repeatedly against code that humans also edit. Jostraca
|
|
331
|
+
treats the already-on-disk file as a first-class input, not an obstacle:
|
|
332
|
+
the `write` / `preserve` / `present` / `diff` / `merge` modes and
|
|
333
|
+
`# JOSTRACA_PROTECT` exist so that regeneration is safe by policy rather
|
|
334
|
+
than by luck. The three-way merge in particular keeps a baseline of what
|
|
335
|
+
was last generated, so it can distinguish your edits from generator
|
|
336
|
+
changes.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Build and test (contributors)
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
cd ts
|
|
344
|
+
npm install # also pulls peer deps: memfs, shape
|
|
345
|
+
npm run build # tsc --build src test
|
|
346
|
+
npm test # node --test dist-test/**/*.test.js
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
From the repo root, `make all` builds and tests both the TS and Go stacks.
|
|
350
|
+
See the top-level [`CLAUDE.md`](../CLAUDE.md) for the full contributor
|
|
351
|
+
guide, and [`../go/README.md`](../go/README.md) for the Go port.
|
|
352
|
+
|
|
353
|
+
## License
|
|
354
|
+
|
|
355
|
+
MIT. Copyright (c) Richard Rodger. See [LICENSE](./LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jostraca",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.2",
|
|
4
4
|
"main": "dist/jostraca.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"types": "dist/jostraca.d.ts",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"src",
|
|
44
44
|
"dist",
|
|
45
45
|
"gen",
|
|
46
|
+
"README.md",
|
|
46
47
|
"LICENSE"
|
|
47
48
|
],
|
|
48
49
|
"devDependencies": {
|