@timeax/scaffold 0.0.1 → 0.0.3
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/dist/cli.cjs +115 -14
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +115 -14
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +91 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -1
- package/dist/index.d.ts +84 -1
- package/dist/index.mjs +88 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/readme.md +184 -46
- package/scripts/postpublish.mjs +72 -0
- package/scripts/prepublish.mjs +95 -0
- package/src/cli/main.ts +38 -0
- package/src/core/config-loader.ts +4 -3
- package/src/core/init-scaffold.ts +8 -3
- package/src/core/scan-structure.ts +71 -0
- package/src/core/structure-txt.ts +88 -15
- package/src/index.ts +3 -2
- package/src/schema/config.ts +10 -1
- package/src/schema/index.ts +1 -0
package/readme.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @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,11 +8,13 @@ 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` files inside `.scaffold/`.
|
|
12
|
+
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
## Features
|
|
14
16
|
|
|
15
|
-
* **Prisma‑style scaffold directory**: all config and structure lives under
|
|
17
|
+
* **Prisma‑style scaffold directory**: all config and structure lives under `.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**:
|
|
@@ -26,8 +28,14 @@ A tiny, opinionated scaffolding tool that keeps your project structure in sync w
|
|
|
26
28
|
* Regular hooks around file create/delete.
|
|
27
29
|
* Stub hooks around content generation.
|
|
28
30
|
* **Stubs**: programmatic content generators for files (e.g. React pages, controllers, etc.).
|
|
29
|
-
* **Watch mode**: watch
|
|
31
|
+
* **Watch mode**: watch `.scaffold/` for changes and re‑run automatically.
|
|
30
32
|
* **Scanner**: generate `structure.txt` (or per‑group `*.txt`) from an existing codebase.
|
|
33
|
+
* **VS Code integration** (via a companion extension):
|
|
34
|
+
|
|
35
|
+
* Syntax highlighting for `.tss`, `.stx`, `structure.txt`, and `.scaffold/**/*.txt`.
|
|
36
|
+
* Inline diagnostics using the same parser as the CLI.
|
|
37
|
+
* “Go to file” from a structure line.
|
|
38
|
+
* Simple formatting and sorting commands.
|
|
31
39
|
|
|
32
40
|
---
|
|
33
41
|
|
|
@@ -57,7 +65,7 @@ pnpm scaffold init
|
|
|
57
65
|
This will create:
|
|
58
66
|
|
|
59
67
|
```txt
|
|
60
|
-
scaffold/
|
|
68
|
+
.scaffold/
|
|
61
69
|
config.ts # main ScaffoldConfig
|
|
62
70
|
structure.txt # example structure (single-root mode)
|
|
63
71
|
```
|
|
@@ -74,7 +82,7 @@ scaffold init --dir tools/scaffold
|
|
|
74
82
|
|
|
75
83
|
### 2. Define your structure
|
|
76
84
|
|
|
77
|
-
By default,
|
|
85
|
+
By default, `.scaffold/structure.txt` is used in single‑root mode.
|
|
78
86
|
|
|
79
87
|
Example:
|
|
80
88
|
|
|
@@ -98,12 +106,16 @@ README.md
|
|
|
98
106
|
|
|
99
107
|
**Rules:**
|
|
100
108
|
|
|
101
|
-
* Indent with **2 spaces per level** (strict).
|
|
109
|
+
* Indent with **2 spaces per level** (strict by default; configurable).
|
|
102
110
|
* Directories **must** end with `/`.
|
|
103
111
|
* Files **must not** end with `/`.
|
|
104
112
|
* You **cannot indent under a file** (files cannot have children).
|
|
105
113
|
* You can’t “skip” levels (no jumping from depth 0 to depth 2 in one go).
|
|
106
|
-
* Lines starting with `#` are comments.
|
|
114
|
+
* Lines starting with `#` or `//` (after indentation) are comments.
|
|
115
|
+
* Inline comments are supported:
|
|
116
|
+
|
|
117
|
+
* `index.ts # comment`
|
|
118
|
+
* `index.ts // comment`
|
|
107
119
|
|
|
108
120
|
#### Annotations
|
|
109
121
|
|
|
@@ -124,20 +136,22 @@ Supported inline annotations:
|
|
|
124
136
|
|
|
125
137
|
These map onto the `StructureEntry` fields in TypeScript.
|
|
126
138
|
|
|
139
|
+
> `:` is reserved for annotations (e.g. `@stub:page`). Paths themselves must **not** contain `:`.
|
|
140
|
+
|
|
127
141
|
---
|
|
128
142
|
|
|
129
143
|
### 3. Configure groups (optional but recommended)
|
|
130
144
|
|
|
131
|
-
In
|
|
145
|
+
In `.scaffold/config.ts` you can enable grouped mode:
|
|
132
146
|
|
|
133
147
|
```ts
|
|
134
148
|
import type { ScaffoldConfig } from '@timeax/scaffold';
|
|
135
149
|
|
|
136
150
|
const config: ScaffoldConfig = {
|
|
137
|
-
|
|
151
|
+
base: '.', // project root (optional, defaults to cwd)
|
|
138
152
|
|
|
139
153
|
groups: [
|
|
140
|
-
{ name: 'app',
|
|
154
|
+
{ name: 'app', root: 'app', structureFile: 'app.txt' },
|
|
141
155
|
{ name: 'frontend', root: 'resources/js', structureFile: 'frontend.txt' },
|
|
142
156
|
],
|
|
143
157
|
|
|
@@ -148,21 +162,21 @@ const config: ScaffoldConfig = {
|
|
|
148
162
|
export default config;
|
|
149
163
|
```
|
|
150
164
|
|
|
151
|
-
Then create per‑group structure files in
|
|
165
|
+
Then create per‑group structure files in `.scaffold/`:
|
|
152
166
|
|
|
153
167
|
```txt
|
|
154
|
-
# scaffold/app.txt
|
|
168
|
+
# .scaffold/app.txt
|
|
155
169
|
App/Services/
|
|
156
170
|
UserService.php
|
|
157
171
|
|
|
158
|
-
# scaffold/frontend.txt
|
|
172
|
+
# .scaffold/frontend.txt
|
|
159
173
|
src/
|
|
160
174
|
index.tsx
|
|
161
175
|
pages/
|
|
162
176
|
home.tsx
|
|
163
177
|
```
|
|
164
178
|
|
|
165
|
-
> When `groups` is defined and non‑empty, single‑root `structure
|
|
179
|
+
> When `groups` is defined and non‑empty, single‑root `structure` / `structureFile` is ignored.
|
|
166
180
|
|
|
167
181
|
---
|
|
168
182
|
|
|
@@ -173,16 +187,16 @@ src/
|
|
|
173
187
|
scaffold
|
|
174
188
|
|
|
175
189
|
# or with explicit scaffold dir / config
|
|
176
|
-
scaffold --dir scaffold --config scaffold/config.ts
|
|
190
|
+
scaffold --dir .scaffold --config .scaffold/config.ts
|
|
177
191
|
```
|
|
178
192
|
|
|
179
193
|
What happens:
|
|
180
194
|
|
|
181
|
-
* Config is loaded from
|
|
195
|
+
* Config is loaded from `.scaffold/config.*` (Prisma‑style resolution).
|
|
182
196
|
* Structure(s) are resolved (grouped or single‑root).
|
|
183
197
|
* Files/directories missing on disk are created.
|
|
184
198
|
* 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
|
|
199
|
+
* Any previously created files that are no longer in the structure are candidates for deletion:
|
|
186
200
|
|
|
187
201
|
* Small files are deleted automatically.
|
|
188
202
|
* Large files (configurable threshold) trigger an interactive prompt.
|
|
@@ -195,8 +209,8 @@ scaffold --watch
|
|
|
195
209
|
|
|
196
210
|
* Watches:
|
|
197
211
|
|
|
198
|
-
*
|
|
199
|
-
*
|
|
212
|
+
* `.scaffold/config.*`
|
|
213
|
+
* `.scaffold/*.txt`
|
|
200
214
|
* Debounces rapid edits.
|
|
201
215
|
* Prevents overlapping runs.
|
|
202
216
|
|
|
@@ -213,11 +227,13 @@ scaffold [options]
|
|
|
213
227
|
Options:
|
|
214
228
|
|
|
215
229
|
* `-c, --config <path>` – override config file path.
|
|
216
|
-
* `-d, --dir <path>` – override scaffold directory (default:
|
|
230
|
+
* `-d, --dir <path>` – override scaffold directory (default: `./.scaffold`).
|
|
217
231
|
* `-w, --watch` – watch scaffold directory for changes.
|
|
218
232
|
* `--quiet` – silence logs.
|
|
219
233
|
* `--debug` – verbose debug logs.
|
|
220
234
|
|
|
235
|
+
---
|
|
236
|
+
|
|
221
237
|
### `scaffold init`
|
|
222
238
|
|
|
223
239
|
Initialize the scaffold directory + config + structure.
|
|
@@ -228,42 +244,85 @@ scaffold init [options]
|
|
|
228
244
|
|
|
229
245
|
Options:
|
|
230
246
|
|
|
231
|
-
* `-d, --dir <path>` – scaffold directory (default:
|
|
247
|
+
* `-d, --dir <path>` – scaffold directory (default: `./.scaffold`, inherited from root options).
|
|
232
248
|
* `--force` – overwrite existing `config.ts` / `structure.txt`.
|
|
233
249
|
|
|
250
|
+
---
|
|
251
|
+
|
|
234
252
|
### `scaffold scan`
|
|
235
253
|
|
|
236
254
|
Generate `structure.txt`‑style definitions from an existing project.
|
|
237
255
|
|
|
238
256
|
Two modes:
|
|
239
257
|
|
|
240
|
-
1.
|
|
258
|
+
#### 1. Config‑aware mode (default if no `--root` / `--out` given)
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
scaffold scan
|
|
262
|
+
scaffold scan --from-config
|
|
263
|
+
scaffold scan --from-config --groups app frontend
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
* Loads `.scaffold/config.ts`.
|
|
267
|
+
* For each `group` in config:
|
|
268
|
+
|
|
269
|
+
* Scans `group.root` on disk.
|
|
270
|
+
* Writes to `.scaffold/<group.structureFile || group.name + '.txt'>`.
|
|
271
|
+
* `--groups` filters which groups to scan.
|
|
272
|
+
|
|
273
|
+
#### 2. Manual mode (single root)
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
scaffold scan -r src
|
|
277
|
+
scaffold scan -r src -o .scaffold/src.txt
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Options:
|
|
241
281
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
scaffold scan --from-config --groups app frontend
|
|
246
|
-
```
|
|
282
|
+
* `-r, --root <path>` – directory to scan.
|
|
283
|
+
* `-o, --out <path>` – output file (otherwise prints to stdout).
|
|
284
|
+
* `--ignore <patterns...>` – extra globs to ignore (in addition to defaults like `node_modules/**`, `.git/**`, etc.).
|
|
247
285
|
|
|
248
|
-
|
|
249
|
-
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
### `scaffold structures`
|
|
250
289
|
|
|
251
|
-
|
|
252
|
-
* Writes to `scaffold/<group.structureFile || group.name + '.txt'>`.
|
|
253
|
-
* `--groups` filters which groups to scan.
|
|
290
|
+
Ensure that all structure files declared in your config exist.
|
|
254
291
|
|
|
255
|
-
|
|
292
|
+
```bash
|
|
293
|
+
scaffold structures
|
|
294
|
+
```
|
|
256
295
|
|
|
257
|
-
|
|
258
|
-
scaffold scan -r src
|
|
259
|
-
scaffold scan -r src -o scaffold/src.txt
|
|
260
|
-
```
|
|
296
|
+
What it does:
|
|
261
297
|
|
|
262
|
-
|
|
298
|
+
* Loads `.scaffold/config.*`.
|
|
299
|
+
* Determines which structure files are expected:
|
|
263
300
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
301
|
+
* **Grouped mode** (`config.groups` defined): each group gets `group.structureFile || `${group.name}.txt``.
|
|
302
|
+
* **Single-root mode** (no groups): uses `config.structureFile || 'structure.txt'`.
|
|
303
|
+
* For each expected structure file:
|
|
304
|
+
|
|
305
|
+
* If it **already exists** → it is left untouched.
|
|
306
|
+
* If it is **missing** → it is created with a small header comment.
|
|
307
|
+
|
|
308
|
+
Examples:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# With grouped config:
|
|
312
|
+
# groups: [
|
|
313
|
+
# { name: 'app', root: 'app', structureFile: 'app.txt' },
|
|
314
|
+
# { name: 'frontend', root: 'resources/js', structureFile: 'frontend.txt' },
|
|
315
|
+
# ]
|
|
316
|
+
scaffold structures
|
|
317
|
+
# => ensures .scaffold/app.txt and .scaffold/frontend.txt exist
|
|
318
|
+
|
|
319
|
+
# With single-root config:
|
|
320
|
+
# structureFile: 'structure.txt'
|
|
321
|
+
scaffold structures
|
|
322
|
+
# => ensures .scaffold/structure.txt exists
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
This is useful right after setting up or editing `.scaffold/config.ts` so that all declared structure files are present and ready to edit.
|
|
267
326
|
|
|
268
327
|
---
|
|
269
328
|
|
|
@@ -276,8 +335,8 @@ import { runOnce } from '@timeax/scaffold';
|
|
|
276
335
|
|
|
277
336
|
await runOnce(process.cwd(), {
|
|
278
337
|
// optional overrides
|
|
279
|
-
configPath: 'scaffold/config.ts',
|
|
280
|
-
scaffoldDir: 'scaffold',
|
|
338
|
+
configPath: '.scaffold/config.ts',
|
|
339
|
+
scaffoldDir: '.scaffold',
|
|
281
340
|
});
|
|
282
341
|
```
|
|
283
342
|
|
|
@@ -298,7 +357,7 @@ const results = await scanProjectFromConfig(process.cwd(), {
|
|
|
298
357
|
groups: ['app', 'frontend'],
|
|
299
358
|
});
|
|
300
359
|
|
|
301
|
-
// write group structure files to scaffold/
|
|
360
|
+
// write group structure files to .scaffold/
|
|
302
361
|
await writeScannedStructuresFromConfig(process.cwd(), {
|
|
303
362
|
groups: ['app'],
|
|
304
363
|
});
|
|
@@ -362,7 +421,9 @@ const config: ScaffoldConfig = {
|
|
|
362
421
|
name: 'page',
|
|
363
422
|
async getContent(ctx) {
|
|
364
423
|
const name = ctx.targetPath.split('/').pop();
|
|
365
|
-
return `export default function ${name}() {
|
|
424
|
+
return `export default function ${name}() {
|
|
425
|
+
return <div>${name}</div>;
|
|
426
|
+
}`;
|
|
366
427
|
},
|
|
367
428
|
hooks: {
|
|
368
429
|
preStub: [
|
|
@@ -379,7 +440,7 @@ const config: ScaffoldConfig = {
|
|
|
379
440
|
};
|
|
380
441
|
```
|
|
381
442
|
|
|
382
|
-
In
|
|
443
|
+
In a structure file:
|
|
383
444
|
|
|
384
445
|
```txt
|
|
385
446
|
src/
|
|
@@ -420,5 +481,82 @@ Some things this package is intentionally designed to grow into:
|
|
|
420
481
|
* Stub groups (one logical stub creating multiple files).
|
|
421
482
|
* Built‑in templates for common stacks (Laravel + Inertia, Next.js, etc.).
|
|
422
483
|
* Better diff/dry‑run UX (show what will change without touching disk).
|
|
484
|
+
* Deeper VS Code integration:
|
|
485
|
+
|
|
486
|
+
* Tree-aware sorting.
|
|
487
|
+
* Visual tree editor.
|
|
488
|
+
* Code actions / quick fixes for common mistakes.
|
|
423
489
|
|
|
424
490
|
PRs and ideas are welcome ✨
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## VS Code extension
|
|
495
|
+
|
|
496
|
+
There is an official VS Code companion extension for `@timeax/scaffold` that makes working with your structure files much nicer.
|
|
497
|
+
|
|
498
|
+
### Language support
|
|
499
|
+
|
|
500
|
+
The extension adds a custom language **Scaffold Structure** and:
|
|
501
|
+
|
|
502
|
+
* Highlights:
|
|
503
|
+
|
|
504
|
+
* Directories (lines ending with `/`)
|
|
505
|
+
* Files
|
|
506
|
+
* Inline annotations like `@stub:name`, `@include:pattern`, `@exclude:pattern`
|
|
507
|
+
* Comments using `#` or `//` (full-line and inline)
|
|
508
|
+
* Treats the following files as scaffold structures:
|
|
509
|
+
|
|
510
|
+
* `*.tss`
|
|
511
|
+
* `*.stx`
|
|
512
|
+
* `structure.txt`
|
|
513
|
+
* Any `.txt` file inside your `.scaffold/` directory
|
|
514
|
+
|
|
515
|
+
The syntax rules match the CLI parser:
|
|
516
|
+
|
|
517
|
+
* Indent is in fixed steps (configurable via `indentStep`).
|
|
518
|
+
* Only directories can have children.
|
|
519
|
+
* `:` is reserved for annotations and not allowed inside path names.
|
|
520
|
+
|
|
521
|
+
### Editor commands
|
|
522
|
+
|
|
523
|
+
The extension contributes several commands (available via the Command Palette and context menus when editing a scaffold structure file):
|
|
524
|
+
|
|
525
|
+
* **Scaffold: Go to file**
|
|
526
|
+
|
|
527
|
+
* Reads the path on the current line and opens the corresponding file in your project.
|
|
528
|
+
* Respects `.scaffold/config.*`:
|
|
529
|
+
|
|
530
|
+
* Uses `base`/`root` to resolve paths.
|
|
531
|
+
* If the current structure file belongs to a `group`, it resolves relative to that group’s `root`.
|
|
532
|
+
* If the file doesn’t exist, it can prompt to create it and open it immediately.
|
|
533
|
+
|
|
534
|
+
* **Scaffold: Format structure file**
|
|
535
|
+
|
|
536
|
+
* Normalizes line endings and trims trailing whitespace.
|
|
537
|
+
* Designed to be safe even on partially-invalid files.
|
|
538
|
+
* Future versions may use the full AST from `@timeax/scaffold` to enforce indentation and ordering.
|
|
539
|
+
|
|
540
|
+
* **Scaffold: Sort entries**
|
|
541
|
+
|
|
542
|
+
* Naive helper that sorts non-comment lines lexicographically while keeping comment/blank lines in place.
|
|
543
|
+
* Useful for quick cleanups of small structure files.
|
|
544
|
+
|
|
545
|
+
* **Scaffold: Open config**
|
|
546
|
+
|
|
547
|
+
* Opens `.scaffold/config.*` for the current workspace (searching common extensions like `config.ts`, `config.mts`, etc.).
|
|
548
|
+
|
|
549
|
+
* **Scaffold: Open .scaffold folder**
|
|
550
|
+
|
|
551
|
+
* Reveals the `.scaffold/` directory in the VS Code Explorer.
|
|
552
|
+
|
|
553
|
+
### Live validation (diagnostics)
|
|
554
|
+
|
|
555
|
+
Whenever you open or edit a scaffold structure file:
|
|
556
|
+
|
|
557
|
+
* The extension calls `parseStructureText` from `@timeax/scaffold` under the hood.
|
|
558
|
+
* If parsing fails, the error message (e.g. invalid indentation, children under a file, bad path, etc.) is shown as an editor diagnostic (squiggly underline) on the relevant line.
|
|
559
|
+
|
|
560
|
+
This means your editor and the CLI always agree on what is valid, since they share the same parser and rules.
|
|
561
|
+
|
|
562
|
+
> The extension is optional, but highly recommended if you edit `*.tss` / `*.stx` or `structure.txt` files frequently.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/postpublish.mjs
|
|
3
|
+
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
function incrementPatch(version) {
|
|
8
|
+
const parts = version.split(".");
|
|
9
|
+
|
|
10
|
+
if (parts.length !== 3) {
|
|
11
|
+
throw new Error(`[postpublish] Unsupported version format: "${version}"`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const [majorRaw, minorRaw, patchRaw] = parts;
|
|
15
|
+
const major = Number(majorRaw);
|
|
16
|
+
const minor = Number(minorRaw);
|
|
17
|
+
const patch = Number(patchRaw);
|
|
18
|
+
|
|
19
|
+
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`[postpublish] Version contains non-numeric parts: "${version}"`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const nextPatch = patch + 1;
|
|
26
|
+
return `${major}.${minor}.${nextPatch}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function main() {
|
|
30
|
+
const pkgPath = path.resolve(process.cwd(), "package.json");
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(pkgPath)) {
|
|
33
|
+
console.error("[postpublish] Could not find package.json at", pkgPath);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const raw = fs.readFileSync(pkgPath, "utf8");
|
|
38
|
+
let pkg;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
pkg = JSON.parse(raw);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.error("[postpublish] Failed to parse package.json:", e);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const currentVersion = pkg.version;
|
|
48
|
+
if (typeof currentVersion !== "string") {
|
|
49
|
+
console.error(
|
|
50
|
+
'[postpublish] package.json does not have a string "version" field.'
|
|
51
|
+
);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let nextVersion;
|
|
56
|
+
try {
|
|
57
|
+
nextVersion = incrementPatch(currentVersion);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error(String(e));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pkg.version = nextVersion;
|
|
64
|
+
|
|
65
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
66
|
+
|
|
67
|
+
console.log(
|
|
68
|
+
`[postpublish] Bumped version in package.json: ${currentVersion} → ${nextVersion}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
main();
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/prepublish.mjs
|
|
3
|
+
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import readline from "node:readline/promises";
|
|
6
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
7
|
+
|
|
8
|
+
function run(command, args, options = {}) {
|
|
9
|
+
const result = spawnSync(command, args, {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
...options,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (result.error) {
|
|
15
|
+
console.error(`[prepublish] Failed to run ${command}:`, result.error);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
20
|
+
process.exit(result.status);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runCapture(command, args, options = {}) {
|
|
27
|
+
const result = spawnSync(command, args, {
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
30
|
+
...options,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (result.error) {
|
|
34
|
+
throw result.error;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
// Check if we're in a git repo
|
|
42
|
+
try {
|
|
43
|
+
const revParse = runCapture("git", [
|
|
44
|
+
"rev-parse",
|
|
45
|
+
"--is-inside-work-tree",
|
|
46
|
+
]);
|
|
47
|
+
if (revParse.status !== 0) {
|
|
48
|
+
console.log("[prepublish] Not a git repository; skipping git checks.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
console.log("[prepublish] Not a git repository; skipping git checks.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check for uncommitted changes
|
|
57
|
+
const status = runCapture("git", ["status", "--porcelain"]);
|
|
58
|
+
const outputText = status.stdout.trim();
|
|
59
|
+
|
|
60
|
+
if (!outputText) {
|
|
61
|
+
console.log("[prepublish] Working tree clean. Nothing to commit.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log("[prepublish] You have uncommitted changes:\n");
|
|
66
|
+
console.log(outputText + "\n");
|
|
67
|
+
|
|
68
|
+
const rl = readline.createInterface({ input, output });
|
|
69
|
+
const message = (
|
|
70
|
+
await rl.question(
|
|
71
|
+
"[prepublish] Commit message (leave blank to abort publish): "
|
|
72
|
+
)
|
|
73
|
+
).trim();
|
|
74
|
+
await rl.close();
|
|
75
|
+
|
|
76
|
+
if (!message) {
|
|
77
|
+
console.error(
|
|
78
|
+
"[prepublish] No commit message provided. Aborting publish."
|
|
79
|
+
);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log("[prepublish] Staging changes...");
|
|
84
|
+
run("git", ["add", "."]);
|
|
85
|
+
|
|
86
|
+
console.log("[prepublish] Committing...");
|
|
87
|
+
run("git", ["commit", "-m", message]);
|
|
88
|
+
|
|
89
|
+
console.log("[prepublish] Pushing to default remote...");
|
|
90
|
+
run("git", ["push"]);
|
|
91
|
+
|
|
92
|
+
console.log("[prepublish] Git push completed. Proceeding with publish.");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await main();
|
package/src/cli/main.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Command } from 'commander';
|
|
|
5
5
|
import { runOnce, RunOptions } from '../core/runner';
|
|
6
6
|
import { watchScaffold } from '../core/watcher';
|
|
7
7
|
import {
|
|
8
|
+
ensureStructureFilesFromConfig,
|
|
8
9
|
scanDirectoryToStructureText,
|
|
9
10
|
writeScannedStructuresFromConfig,
|
|
10
11
|
} from '../core/scan-structure';
|
|
@@ -170,6 +171,30 @@ async function handleInitCommand(
|
|
|
170
171
|
);
|
|
171
172
|
}
|
|
172
173
|
|
|
174
|
+
|
|
175
|
+
async function handleStructuresCommand(
|
|
176
|
+
cwd: string,
|
|
177
|
+
baseOpts: BaseCliOptions,
|
|
178
|
+
) {
|
|
179
|
+
const logger = createCliLogger(baseOpts);
|
|
180
|
+
|
|
181
|
+
logger.info('Ensuring structure files declared in config exist...');
|
|
182
|
+
|
|
183
|
+
const { created, existing } = await ensureStructureFilesFromConfig(cwd, {
|
|
184
|
+
scaffoldDirOverride: baseOpts.dir,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (created.length === 0) {
|
|
188
|
+
logger.info('All structure files already exist. Nothing to do.');
|
|
189
|
+
} else {
|
|
190
|
+
for (const filePath of created) {
|
|
191
|
+
logger.info(`Created structure file: ${filePath}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
existing.forEach((p) => logger.debug(`Structure file already exists: ${p}`));
|
|
196
|
+
}
|
|
197
|
+
|
|
173
198
|
async function main() {
|
|
174
199
|
const cwd = process.cwd();
|
|
175
200
|
|
|
@@ -234,9 +259,22 @@ async function main() {
|
|
|
234
259
|
await handleRunCommand(cwd, opts);
|
|
235
260
|
});
|
|
236
261
|
|
|
262
|
+
interface StructuresCliOptions { }
|
|
263
|
+
|
|
264
|
+
program
|
|
265
|
+
.command('structures')
|
|
266
|
+
.description(
|
|
267
|
+
'Create missing structure files specified in the config (does not overwrite existing files)',
|
|
268
|
+
)
|
|
269
|
+
.action(async (_opts: StructuresCliOptions, cmd: Command) => {
|
|
270
|
+
const baseOpts = cmd.parent?.opts<BaseCliOptions>() ?? {};
|
|
271
|
+
await handleStructuresCommand(cwd, baseOpts);
|
|
272
|
+
});
|
|
273
|
+
|
|
237
274
|
await program.parseAsync(process.argv);
|
|
238
275
|
}
|
|
239
276
|
|
|
277
|
+
|
|
240
278
|
// Run and handle errors
|
|
241
279
|
main().catch((err) => {
|
|
242
280
|
defaultLogger.error(err);
|
|
@@ -7,12 +7,13 @@ import crypto from 'crypto';
|
|
|
7
7
|
import { pathToFileURL } from 'url';
|
|
8
8
|
import { transform } from 'esbuild';
|
|
9
9
|
|
|
10
|
-
import type
|
|
10
|
+
import { SCAFFOLD_ROOT_DIR, type ScaffoldConfig } from '../schema';
|
|
11
11
|
import { defaultLogger } from '../util/logger';
|
|
12
12
|
import { ensureDirSync } from '../util/fs-utils';
|
|
13
13
|
|
|
14
14
|
const logger = defaultLogger.child('[config]');
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
export interface LoadScaffoldConfigOptions {
|
|
17
18
|
/**
|
|
18
19
|
* Optional explicit scaffold directory path (absolute or relative to cwd).
|
|
@@ -68,7 +69,7 @@ export async function loadScaffoldConfig(
|
|
|
68
69
|
// First pass: figure out an initial scaffold dir just to locate config.*
|
|
69
70
|
const initialScaffoldDir = options.scaffoldDir
|
|
70
71
|
? path.resolve(absCwd, options.scaffoldDir)
|
|
71
|
-
: path.join(absCwd,
|
|
72
|
+
: path.join(absCwd, SCAFFOLD_ROOT_DIR);
|
|
72
73
|
|
|
73
74
|
const configPath =
|
|
74
75
|
options.configPath ?? resolveConfigPath(initialScaffoldDir);
|
|
@@ -85,7 +86,7 @@ export async function loadScaffoldConfig(
|
|
|
85
86
|
// Final scaffoldDir (can still be overridden by CLI)
|
|
86
87
|
const scaffoldDir = options.scaffoldDir
|
|
87
88
|
? path.resolve(absCwd, options.scaffoldDir)
|
|
88
|
-
: path.join(configRoot,
|
|
89
|
+
: path.join(configRoot, SCAFFOLD_ROOT_DIR);
|
|
89
90
|
|
|
90
91
|
// projectRoot (base) is relative to configRoot
|
|
91
92
|
const baseRoot = config.base
|
|
@@ -4,6 +4,7 @@ import fs from 'fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { ensureDirSync } from '../util/fs-utils';
|
|
6
6
|
import { defaultLogger } from '../util/logger';
|
|
7
|
+
import { SCAFFOLD_ROOT_DIR } from '../schema';
|
|
7
8
|
|
|
8
9
|
const logger = defaultLogger.child('[init]');
|
|
9
10
|
|
|
@@ -52,7 +53,10 @@ const config: ScaffoldConfig = {
|
|
|
52
53
|
// base: 'src', // apply to <root>/src
|
|
53
54
|
// base: '..', // apply to parent of <root>
|
|
54
55
|
// base: '.',
|
|
55
|
-
|
|
56
|
+
|
|
57
|
+
// Number of spaces per indent level in structure files (default: 2).
|
|
58
|
+
// indentStep: 2,
|
|
59
|
+
|
|
56
60
|
// Cache file path, relative to base.
|
|
57
61
|
// cacheFile: '.scaffold-cache.json',
|
|
58
62
|
|
|
@@ -85,7 +89,8 @@ const config: ScaffoldConfig = {
|
|
|
85
89
|
export default config;
|
|
86
90
|
`;
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
|
|
93
|
+
const DEFAULT_STRUCTURE_TXT = `# ${SCAFFOLD_ROOT_DIR}/structure.txt
|
|
89
94
|
# Example structure definition.
|
|
90
95
|
# - Indent with 2 spaces per level
|
|
91
96
|
# - Directories must end with "/"
|
|
@@ -113,7 +118,7 @@ export async function initScaffold(
|
|
|
113
118
|
structurePath: string;
|
|
114
119
|
created: { config: boolean; structure: boolean };
|
|
115
120
|
}> {
|
|
116
|
-
const scaffoldDirRel = options.scaffoldDir ??
|
|
121
|
+
const scaffoldDirRel = options.scaffoldDir ?? SCAFFOLD_ROOT_DIR;
|
|
117
122
|
const scaffoldDirAbs = path.resolve(cwd, scaffoldDirRel);
|
|
118
123
|
const configFileName = options.configFileName ?? 'config.ts';
|
|
119
124
|
const structureFileName = options.structureFileName ?? 'structure.txt';
|