@timeax/scaffold 0.0.2 → 0.0.4

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 CHANGED
@@ -1,4 +1,4 @@
1
- # Scaffold - `@timeax/scaffold`
1
+ # Scaffold `@timeax/scaffold`
2
2
 
3
3
  A tiny, opinionated scaffolding tool that keeps your project structure in sync with a **declarative tree** (like `structure.txt`) – Prisma‑style.
4
4
 
@@ -8,26 +8,30 @@ A tiny, opinionated scaffolding tool that keeps your project structure in sync w
8
8
  * Reverse‑engineer existing projects into `*.txt` structures.
9
9
  * Watch for changes and re‑apply automatically.
10
10
 
11
+ > **Supported structure files:** `.tss`, `.stx`, `structure.txt` (and any `.txt` inside `.scaffold/`).
12
+
11
13
  ---
12
14
 
13
15
  ## Features
14
16
 
15
- * **Prisma‑style scaffold directory**: all config and structure lives under `scaffold/` by default.
17
+ * **Prisma‑style scaffold directory**: all config and structures live under a hidden root, **`.scaffold/`**, by default.
16
18
  * **Config‑driven groups**: declare multiple roots (e.g. `app`, `frontend`) with their own structure files.
17
19
  * **Plain‑text structure files**: strict, easy‑to‑read tree syntax with indentation and annotations.
18
20
  * **Safe apply**:
19
21
 
20
- * Creates missing files/directories.
21
- * Tracks what it created in a cache.
22
- * Only auto‑deletes files it previously created.
23
- * Interactive delete for “large” files.
22
+ * Creates missing files/directories.
23
+ * Tracks what it created in a cache.
24
+ * Only auto‑deletes files it previously created.
25
+ * Interactive delete for “large” files.
24
26
  * **Hooks**:
25
27
 
26
- * Regular hooks around file create/delete.
27
- * Stub hooks around content generation.
28
+ * Regular hooks around file create/delete.
29
+ * Stub hooks around content generation.
28
30
  * **Stubs**: programmatic content generators for files (e.g. React pages, controllers, etc.).
29
- * **Watch mode**: watch `scaffold/` for changes and re‑run automatically.
30
- * **Scanner**: generate `structure.txt` (or per‑group `*.txt`) from an existing codebase.
31
+ * **Watch mode**: watch `.scaffold/` for changes and re‑run automatically.
32
+ * **Scanner**: generate `*.tss`/`structure.txt` from an existing codebase.
33
+ * **AST + formatter**: loose/strict parser with diagnostics, plus a smart formatter that can *fix* simple indentation mistakes.
34
+ * **VS Code extension**: syntax highlighting, formatting, diagnostics, folding, hover info, “go to file”, and code actions.
31
35
 
32
36
  ---
33
37
 
@@ -57,7 +61,7 @@ pnpm scaffold init
57
61
  This will create:
58
62
 
59
63
  ```txt
60
- scaffold/
64
+ .scaffold/
61
65
  config.ts # main ScaffoldConfig
62
66
  structure.txt # example structure (single-root mode)
63
67
  ```
@@ -65,7 +69,7 @@ scaffold/
65
69
  If you want a different directory name:
66
70
 
67
71
  ```bash
68
- scaffold init --dir tools/scaffold
72
+ scaffold init --dir tools/.scaffold
69
73
  ```
70
74
 
71
75
  > Use `--force` to overwrite existing config/structure files.
@@ -74,7 +78,7 @@ scaffold init --dir tools/scaffold
74
78
 
75
79
  ### 2. Define your structure
76
80
 
77
- By default, `scaffold/structure.txt` is used in single‑root mode.
81
+ By default, `.scaffold/structure.txt` is used in single‑root mode.
78
82
 
79
83
  Example:
80
84
 
@@ -98,12 +102,13 @@ README.md
98
102
 
99
103
  **Rules:**
100
104
 
101
- * Indent with **2 spaces per level** (strict).
105
+ * Indent with **2 spaces per level** by default (configurable via `indentStep`).
102
106
  * Directories **must** end with `/`.
103
107
  * Files **must not** end with `/`.
104
- * You **cannot indent under a file** (files cannot have children).
105
- * You can’t “skip” levels (no jumping from depth 0 to depth 2 in one go).
106
- * Lines starting with `#` are comments.
108
+ * You **cannot indent under a file** (files cannot have children) – in strict mode this is an error, in loose mode you get a diagnostic.
109
+ * You can’t “skip” levels (no jumping from depth 0 straight to depth 2 in one go).
110
+ * Lines starting with `#` or `//` are treated as comments.
111
+ * Inline comments are supported: `index.ts # comment`, `index.ts // comment`.
107
112
 
108
113
  #### Annotations
109
114
 
@@ -126,18 +131,23 @@ These map onto the `StructureEntry` fields in TypeScript.
126
131
 
127
132
  ---
128
133
 
129
- ### 3. Configure groups (optional but recommended)
134
+ ## Config: groups, base, and indent
130
135
 
131
- In `scaffold/config.ts` you can enable grouped mode:
136
+ In `.scaffold/config.ts` you can enable grouped mode and control the base root + indent step:
132
137
 
133
138
  ```ts
134
139
  import type { ScaffoldConfig } from '@timeax/scaffold';
135
140
 
136
141
  const config: ScaffoldConfig = {
137
- root: '.', // project root (optional, defaults to cwd)
142
+ // Project root (defaults to cwd if omitted)
143
+ base: '.',
144
+
145
+ // Indent step in spaces (must match your `.tss`/`structure.txt`)
146
+ indentStep: 2,
138
147
 
148
+ // Optional: grouped mode
139
149
  groups: [
140
- { name: 'app', root: 'app', structureFile: 'app.txt' },
150
+ { name: 'app', root: 'app', structureFile: 'app.txt' },
141
151
  { name: 'frontend', root: 'resources/js', structureFile: 'frontend.txt' },
142
152
  ],
143
153
 
@@ -148,14 +158,14 @@ const config: ScaffoldConfig = {
148
158
  export default config;
149
159
  ```
150
160
 
151
- Then create per‑group structure files in `scaffold/`:
161
+ Then create per‑group structure files in `.scaffold/`:
152
162
 
153
163
  ```txt
154
- # scaffold/app.txt
164
+ # .scaffold/app.txt
155
165
  App/Services/
156
166
  UserService.php
157
167
 
158
- # scaffold/frontend.txt
168
+ # .scaffold/frontend.txt
159
169
  src/
160
170
  index.tsx
161
171
  pages/
@@ -166,26 +176,26 @@ src/
166
176
 
167
177
  ---
168
178
 
169
- ### 4. Run scaffold
179
+ ## Running scaffold
170
180
 
171
181
  ```bash
172
182
  # single run
173
183
  scaffold
174
184
 
175
185
  # or with explicit scaffold dir / config
176
- scaffold --dir scaffold --config scaffold/config.ts
186
+ scaffold --dir .scaffold --config .scaffold/config.ts
177
187
  ```
178
188
 
179
189
  What happens:
180
190
 
181
- * Config is loaded from `scaffold/config.*` (Prisma‑style resolution).
191
+ * Config is loaded from `.scaffold/config.*` (Prisma‑style resolution).
182
192
  * Structure(s) are resolved (grouped or single‑root).
183
193
  * Files/directories missing on disk are created.
184
- * New files are registered in `.scaffold-cache.json` (under project root by default).
185
- * Any previously created files that are no longer in the structure are candidates for deletion.
194
+ * New files are registered in a cache file (default: `.scaffold-cache.json` under project root).
195
+ * Any previously created files that are no longer in the structure are candidates for deletion:
186
196
 
187
- * Small files are deleted automatically.
188
- * Large files (configurable threshold) trigger an interactive prompt.
197
+ * Small files are deleted automatically.
198
+ * Large files (configurable threshold) trigger an interactive prompt.
189
199
 
190
200
  ### Watch mode
191
201
 
@@ -195,8 +205,8 @@ scaffold --watch
195
205
 
196
206
  * Watches:
197
207
 
198
- * `scaffold/config.*`
199
- * `scaffold/*.txt`
208
+ * `.scaffold/config.*`
209
+ * `.scaffold/*.txt` / `.scaffold/*.tss` / `.scaffold/*.stx`
200
210
  * Debounces rapid edits.
201
211
  * Prevents overlapping runs.
202
212
 
@@ -213,11 +223,13 @@ scaffold [options]
213
223
  Options:
214
224
 
215
225
  * `-c, --config <path>` – override config file path.
216
- * `-d, --dir <path>` – override scaffold directory (default: `./scaffold`).
226
+ * `-d, --dir <path>` – override scaffold directory (default: `./.scaffold`).
217
227
  * `-w, --watch` – watch scaffold directory for changes.
218
228
  * `--quiet` – silence logs.
219
229
  * `--debug` – verbose debug logs.
220
230
 
231
+ ---
232
+
221
233
  ### `scaffold init`
222
234
 
223
235
  Initialize the scaffold directory + config + structure.
@@ -228,42 +240,44 @@ scaffold init [options]
228
240
 
229
241
  Options:
230
242
 
231
- * `-d, --dir <path>` – scaffold directory (default: `./scaffold`, inherited from root options).
243
+ * `-d, --dir <path>` – scaffold directory (default: `./.scaffold`, inherited from root options).
232
244
  * `--force` – overwrite existing `config.ts` / `structure.txt`.
233
245
 
246
+ ---
247
+
234
248
  ### `scaffold scan`
235
249
 
236
- Generate `structure.txt`‑style definitions from an existing project.
250
+ Generate `*.tss`/`structure.txt`‑style definitions from an existing project.
237
251
 
238
252
  Two modes:
239
253
 
240
- 1. **Config‑aware mode** (default if no `--root` / `--out` given):
254
+ #### 1. Config‑aware mode (default if no `--root` / `--out`)
241
255
 
242
- ```bash
243
- scaffold scan
244
- scaffold scan --from-config
245
- scaffold scan --from-config --groups app frontend
246
- ```
256
+ ```bash
257
+ scaffold scan
258
+ scaffold scan --from-config
259
+ scaffold scan --from-config --groups app frontend
260
+ ```
247
261
 
248
- * Loads `scaffold/config.ts`.
249
- * For each `group` in config:
262
+ * Loads `.scaffold/config.ts`.
263
+ * For each `group` in config:
250
264
 
251
- * Scans `group.root` on disk.
252
- * Writes to `scaffold/<group.structureFile || group.name + '.txt'>`.
253
- * `--groups` filters which groups to scan.
265
+ * Scans `group.root` on disk.
266
+ * Writes to `.scaffold/<group.structureFile || group.name + '.txt'>`.
267
+ * `--groups` filters which groups to scan.
254
268
 
255
- 2. **Manual mode** (single root):
269
+ #### 2. Manual mode (single root)
256
270
 
257
- ```bash
258
- scaffold scan -r src
259
- scaffold scan -r src -o scaffold/src.txt
260
- ```
271
+ ```bash
272
+ scaffold scan -r src
273
+ scaffold scan -r src -o .scaffold/src.txt
274
+ ```
261
275
 
262
- Options:
276
+ Options:
263
277
 
264
- * `-r, --root <path>` – directory to scan.
265
- * `-o, --out <path>` – output file (otherwise prints to stdout).
266
- * `--ignore <patterns...>` – extra globs to ignore (in addition to defaults like `node_modules/**`, `.git/**`, etc.).
278
+ * `-r, --root <path>` – directory to scan.
279
+ * `-o, --out <path>` – output file (otherwise prints to stdout).
280
+ * `--ignore <patterns...>` – extra globs to ignore (in addition to defaults like `node_modules/**`, `.git/**`, etc.).
267
281
 
268
282
  ---
269
283
 
@@ -277,15 +291,15 @@ scaffold structures
277
291
 
278
292
  What it does:
279
293
 
280
- * Loads `scaffold/config.*`.
294
+ * Loads `.scaffold/config.*`.
281
295
  * Determines which structure files are expected:
282
296
 
283
- * **Grouped mode** (`config.groups` defined): each group gets `group.structureFile || \`${group.name}.txt``.
284
- * **Single-root mode** (no groups): uses `config.structureFile || 'structure.txt'`.
297
+ * **Grouped mode** (`config.groups` defined): each group gets `group.structureFile || \`${group.name}.txt``.
298
+ * **Single-root mode** (no groups): uses `config.structureFile || 'structure.txt'`.
285
299
  * For each expected structure file:
286
300
 
287
- * If it **already exists** → it is left untouched.
288
- * If it is **missing** → it is created with a small header comment.
301
+ * If it **already exists** → it is left untouched.
302
+ * If it is **missing** → it is created with a small header comment.
289
303
 
290
304
  Examples:
291
305
 
@@ -296,15 +310,17 @@ Examples:
296
310
  # { name: 'frontend', root: 'resources/js', structureFile: 'frontend.txt' },
297
311
  # ]
298
312
  scaffold structures
299
- # => ensures scaffold/app.txt and scaffold/frontend.txt exist
313
+ # => ensures .scaffold/app.txt and .scaffold/frontend.txt exist
300
314
 
301
315
  # With single-root config:
302
316
  # structureFile: 'structure.txt'
303
317
  scaffold structures
304
- # => ensures scaffold/structure.txt exists
318
+ # => ensures .scaffold/structure.txt exists
305
319
  ```
306
320
 
307
- This is useful right after setting up or editing `scaffold/config.ts` so that all declared structure files are present and ready to edit.
321
+ This is useful right after setting up or editing `.scaffold/config.ts` so that all declared structure files are present and ready to edit.
322
+
323
+ ---
308
324
 
309
325
  ## TypeScript API
310
326
 
@@ -315,8 +331,8 @@ import { runOnce } from '@timeax/scaffold';
315
331
 
316
332
  await runOnce(process.cwd(), {
317
333
  // optional overrides
318
- configPath: 'scaffold/config.ts',
319
- scaffoldDir: 'scaffold',
334
+ configPath: '.scaffold/config.ts',
335
+ scaffoldDir: '.scaffold',
320
336
  });
321
337
  ```
322
338
 
@@ -337,7 +353,7 @@ const results = await scanProjectFromConfig(process.cwd(), {
337
353
  groups: ['app', 'frontend'],
338
354
  });
339
355
 
340
- // write group structure files to scaffold/
356
+ // write group structure files to .scaffold/
341
357
  await writeScannedStructuresFromConfig(process.cwd(), {
342
358
  groups: ['app'],
343
359
  });
@@ -345,7 +361,100 @@ await writeScannedStructuresFromConfig(process.cwd(), {
345
361
 
346
362
  ---
347
363
 
348
- ## Hooks & stubs (high‑level overview)
364
+ ## AST & Formatter
365
+
366
+ `@timeax/scaffold` exposes an AST parser and formatter from a dedicated subpath:
367
+
368
+ ```ts
369
+ import { parseStructureAst, formatStructureText } from '@timeax/scaffold/ast';
370
+ ```
371
+
372
+ ### `parseStructureAst(text, options)`
373
+
374
+ Parses a structure file into a loose or strict AST with diagnostics:
375
+
376
+ ```ts
377
+ const ast = parseStructureAst(text, {
378
+ indentStep: 2, // must match your structure files
379
+ mode: 'loose', // 'loose' | 'strict'
380
+ });
381
+ ```
382
+
383
+ Return shape (simplified):
384
+
385
+ ```ts
386
+ interface StructureAstNode {
387
+ type: 'dir' | 'file';
388
+ name: string;
389
+ path?: string; // normalized POSIX path (no trailing slash for files)
390
+ line?: number; // 1-based source line
391
+ indentLevel?: number; // 0,1,2,...
392
+ stub?: string;
393
+ include?: string[];
394
+ exclude?: string[];
395
+ children?: StructureAstNode[];
396
+ }
397
+
398
+ interface StructureDiagnostic {
399
+ code:
400
+ | 'indent-misaligned'
401
+ | 'indent-skip-level'
402
+ | 'child-of-file-loose'
403
+ | 'path-colon'
404
+ | 'unknown';
405
+ message: string;
406
+ line: number; // 1-based
407
+ severity: 'info' | 'warning' | 'error';
408
+ }
409
+
410
+ interface StructureAst {
411
+ rootNodes: StructureAstNode[];
412
+ indentStep: number;
413
+ mode: 'loose' | 'strict';
414
+ diagnostics: StructureDiagnostic[];
415
+ }
416
+ ```
417
+
418
+ **Loose mode** tries to recover from small mistakes (over‑indent, under‑indent) and reports them as diagnostics instead of throwing.
419
+
420
+ **Strict mode** is closer to the CLI parser and may reject invalid indentation entirely.
421
+
422
+ Typical diagnostics:
423
+
424
+ * `indent-misaligned` – indent is not a multiple of `indentStep`.
425
+ * `indent-skip-level` – you jumped more than one level at once.
426
+ * `child-of-file-loose` – a line is indented under a file.
427
+ * `path-colon` – path token contains a colon (`:`), which is reserved for annotations.
428
+
429
+ ### `formatStructureText(text, options)`
430
+
431
+ Smart formatter that:
432
+
433
+ * Normalizes line endings and trailing whitespace.
434
+ * Re‑indents entries to canonical multiples of `indentStep`.
435
+ * Fixes common over‑indent/under‑indent issues in **loose mode**.
436
+ * Preserves:
437
+
438
+ * Blank lines.
439
+ * Full‑line comments (`#`, `//`).
440
+ * Inline comments and annotations (keeps them attached to their entries).
441
+
442
+ ```ts
443
+ const { text: formatted, ast, diagnostics } = formatStructureText(input, {
444
+ indentStep: 2,
445
+ mode: 'loose', // 'loose' is recommended for editor integrations
446
+ });
447
+ ```
448
+
449
+ `formatStructureText` reuses the same AST model and diagnostics, so you can:
450
+
451
+ * Run it in an editor (e.g. VS Code) for formatting.
452
+ * Show the diagnostics in a side panel or gutter.
453
+ * Still feed the formatted text back into the strict CLI parser later.
454
+
455
+ ---
456
+
457
+ ## Hooks & Stubs (high‑level overview)
349
458
 
350
459
  ### Regular hooks
351
460
 
@@ -386,6 +495,7 @@ Each receives a `HookContext` with fields like:
386
495
  * `absolutePath`
387
496
  * `isDirectory`
388
497
  * `stubName?`
498
+ * `group?`
389
499
 
390
500
  ### Stubs
391
501
 
@@ -401,7 +511,9 @@ const config: ScaffoldConfig = {
401
511
  name: 'page',
402
512
  async getContent(ctx) {
403
513
  const name = ctx.targetPath.split('/').pop();
404
- return `export default function ${name}() {\n return <div>${name}</div>;\n}`;
514
+ return `export default function ${name}() {
515
+ return <div>${name}</div>;
516
+ }`;
405
517
  },
406
518
  hooks: {
407
519
  preStub: [
@@ -412,13 +524,14 @@ const config: ScaffoldConfig = {
412
524
  },
413
525
  },
414
526
  ],
527
+ postStub: [],
415
528
  },
416
529
  },
417
530
  },
418
531
  };
419
532
  ```
420
533
 
421
- In `structure.txt`:
534
+ In a structure file:
422
535
 
423
536
  ```txt
424
537
  src/
@@ -431,33 +544,124 @@ Any file in `pages/` without an explicit stub inherits `@stub:page` from the par
431
544
 
432
545
  ---
433
546
 
434
- ## Cache & safety
547
+ ## Cache & Safety
435
548
 
436
549
  * Cache file (default): `.scaffold-cache.json` under project root (configurable via `cacheFile`).
437
550
  * Every file created by scaffold is recorded with:
438
551
 
439
- * project‑relative path
440
- * created time
441
- * size at creation
442
- * stub name (if any)
443
- * group metadata
552
+ * project‑relative path
553
+ * created time
554
+ * size at creation
555
+ * stub name (if any)
556
+ * group metadata
444
557
  * On each run, scaffold compares the **desired structure** vs. **cached entries**:
445
558
 
446
- * If a cached file is no longer in the structure and still exists → deletion candidate.
447
- * If its size exceeds `sizePromptThreshold` (configurable) and the CLI is interactive → prompt the user.
448
- * If the user chooses “keep”, the file is left on disk and removed from the cache (user now owns it).
559
+ * If a cached file is no longer in the structure and still exists → deletion candidate.
560
+ * If its size exceeds `sizePromptThreshold` (configurable) and the CLI is interactive → prompt the user.
561
+ * If the user chooses “keep”, the file is left on disk and removed from the cache (user now owns it).
449
562
 
450
563
  This keeps scaffolding **idempotent** and avoids reckless deletes.
451
564
 
452
565
  ---
453
566
 
567
+ ## VS Code Extension
568
+
569
+ There is a companion VS Code extension that makes editing scaffold files much nicer.
570
+
571
+ ### Language & files
572
+
573
+ * Registers a `scaffold-structure` language.
574
+ * Treats as scaffold structure files:
575
+
576
+ * `.tss`
577
+ * `.stx`
578
+ * `structure.txt`
579
+ * Any `.txt` inside `.scaffold/` (configurable in the extension’s `package.json`).
580
+
581
+ ### Syntax highlighting
582
+
583
+ * Tree‑like syntax with clear highlighting for:
584
+
585
+ * Directories vs files.
586
+ * Annotations (`@stub:`, `@include:`, `@exclude:`).
587
+ * Comments and inline comments.
588
+
589
+ ### Formatting
590
+
591
+ * Command: **“Scaffold: Format structure”** (`timeax-scaffold.formatStructure`).
592
+ * Uses `formatStructureText` from `@timeax/scaffold/ast`:
593
+
594
+ * Normalizes indentation to `indentStep`.
595
+ * Fixes simple over/under‑indents in loose mode.
596
+ * Preserves blank lines and comments.
597
+
598
+ You can wire this as the default formatter for `scaffold-structure` files via your VS Code settings.
599
+
600
+ ### Sorting
601
+
602
+ * Command: **“Scaffold: Sort entries”** (`timeax-scaffold.sortEntries`).
603
+ * Sorts non‑comment lines lexicographically while preserving comment/blank line positions.
604
+
605
+ ### Diagnostics
606
+
607
+ * Live diagnostics (squiggles) using `parseStructureAst`:
608
+
609
+ * `indent-misaligned`, `indent-skip-level`, `child-of-file-loose`, `path-colon`, etc.
610
+ * Diagnostics update on open and on change.
611
+
612
+ ### Folding
613
+
614
+ * Folding regions for directories based on AST:
615
+
616
+ * Collapse an entire subtree under a dir.
617
+
618
+ ### Hover
619
+
620
+ * Hovering an entry shows:
621
+
622
+ * Kind (dir/file).
623
+ * Effective `path`.
624
+ * Stub / include / exclude.
625
+ * Resolved absolute path based on `base` + group root.
626
+
627
+ ### “Go to file”
628
+
629
+ * Command: **“Scaffold: Go to file”** (`timeax-scaffold.openTargetFile`).
630
+ * On a file line:
631
+
632
+ * Resolves the project base (from `.scaffold/config.*`, `base`, and group `root`).
633
+ * Opens the target file if it exists.
634
+ * If it doesn’t exist, you can create it on the spot.
635
+
636
+ ### Code actions
637
+
638
+ Source actions exposed in the light‑bulb menu for structure files:
639
+
640
+ * **“Scaffold: Ensure structure files exist (scaffold structures)”**
641
+
642
+ * Runs `npx scaffold structures` in a workspace terminal.
643
+ * **“Scaffold: Apply structure to project (scaffold)”**
644
+
645
+ * Runs `npx scaffold` in a workspace terminal.
646
+
647
+ ### Status bar integration
648
+
649
+ * Status bar item (left side) shows current scaffold context:
650
+
651
+ * `Scaffold: frontend (resources/js)` when editing `.scaffold/frontend.txt`.
652
+ * `Scaffold: single root` when in single‑root mode.
653
+ * Tooltip shows the resolved base root path.
654
+
655
+ ---
656
+
454
657
  ## Roadmap / Ideas
455
658
 
456
659
  Some things this package is intentionally designed to grow into:
457
660
 
458
- * Richer annotations in `*.txt` (e.g. per‑entry hooks, metadata aliases).
459
- * Stub groups (one logical stub creating multiple files).
661
+ * Richer annotations in `*.tss` (e.g. per‑entry hooks, metadata aliases).
662
+ * Stub groups (one logical stub creating multiple files at once).
460
663
  * Built‑in templates for common stacks (Laravel + Inertia, Next.js, etc.).
461
664
  * Better diff/dry‑run UX (show what will change without touching disk).
665
+ * Deeper editor integrations (per‑group commands, quick‑fixes for diagnostics, etc.).
462
666
 
463
667
  PRs and ideas are welcome ✨