@ndp-software/lit-md 0.3.0

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.
@@ -0,0 +1,381 @@
1
+ # How --wait Mode Works
2
+
3
+ A detailed walkthrough of the `--wait` mode implementation in lit-md, explaining every piece of the code.
4
+
5
+ ## 1. Startup Phase
6
+
7
+ **File: `cli.ts` lines 38-43**
8
+
9
+ ```typescript
10
+ const wait = extractFlag('--wait')
11
+ ```
12
+
13
+ When a user runs:
14
+
15
+ ```bash
16
+ lit-md --wait --test file.lit-md.ts
17
+ ```
18
+
19
+ The `extractFlag()` function checks if `--wait` is in the argument list. If found, it removes it from `args` and returns `true`. The `wait` variable is then used throughout the program to change behavior.
20
+
21
+ ## 2. Entry Point - Main Async Function
22
+
23
+ **File: `cli.ts` lines 302-313**
24
+
25
+ ```typescript
26
+ ;(async () => {
27
+ await executeTasks() // ← Run ONCE on startup
28
+
29
+ if (wait && process.stdin.isTTY) { // ← If --wait AND interactive terminal
30
+ while (true) { // ← INFINITE LOOP
31
+ const trigger = await watchFilesAndWait(inputPaths)
32
+ await executeTasks() // ← Run again on change/spacebar
33
+ }
34
+ }
35
+ })()
36
+ ```
37
+
38
+ This is where the main logic happens:
39
+
40
+ 1. **First run**: `executeTasks()` is called once immediately
41
+ 2. **Check conditions**:
42
+ - `wait` = user passed `--wait` flag
43
+ - `process.stdin.isTTY` = terminal is interactive (not piped/redirected)
44
+ 3. **If both true**: Enter infinite loop that:
45
+ - Waits for file changes or spacebar
46
+ - Runs `executeTasks()` again
47
+ - Loops back to wait
48
+
49
+ If either condition is false, the program ends after the first run.
50
+
51
+ ## 3. First Run: executeTasks()
52
+
53
+ **File: `cli.ts` lines 249-300**
54
+
55
+ This function runs on startup AND every time through the watch loop. It performs three tasks in order:
56
+
57
+ ### 3a) Typecheck (if `--typecheck` flag used)
58
+
59
+ **Lines 250-259**
60
+
61
+ ```typescript
62
+ if (runTypecheck) {
63
+ const result = typecheck(inputPaths.map(p => resolve(p)))
64
+ if (!result.ok) {
65
+ for (const msg of result.messages) console.error(msg)
66
+ // In wait mode, report error but continue; in normal mode, exit
67
+ if (!wait) process.exit(1)
68
+ // Continue to markdown generation even if typecheck failed
69
+ }
70
+ }
71
+ ```
72
+
73
+ **Key difference in wait mode**: If typecheck fails, the program prints the error but **doesn't exit**. It continues to the next step. In normal mode, it would call `process.exit(1)` and stop.
74
+
75
+ ### 3b) Run Tests (if `--test` flag used)
76
+
77
+ **Lines 262-296**
78
+
79
+ This is where the most significant difference between wait and normal mode occurs:
80
+
81
+ #### Normal Mode Output
82
+
83
+ ```typescript
84
+ const spawnOptions = { stdio: 'inherit', env: process.env }
85
+ ```
86
+
87
+ - `stdio: 'inherit'` means test output goes **directly to the console** in real-time
88
+ - User sees every test name, timing, and full output
89
+ - This is useful for initial verification
90
+
91
+ #### Wait Mode Output
92
+
93
+ ```typescript
94
+ const spawnOptions = wait ? { encoding: 'utf-8' as const } : { stdio: 'inherit' as const, env: process.env }
95
+ ```
96
+
97
+ - `encoding: 'utf-8'` captures **all output into memory** in `result.stdout`
98
+ - We can then process and summarize it before displaying
99
+
100
+ ```typescript
101
+ if (wait && result.stdout) {
102
+ const output = result.stdout.toString()
103
+ const stats = parseTestSummary(output) // Extract pass/fail counts
104
+
105
+ if (stats.hasFailed) {
106
+ // Extract and show failures section
107
+ const failureStart = output.indexOf('✖ failing tests')
108
+ if (failureStart !== -1) {
109
+ const failureSection = output.substring(failureStart)
110
+ console.error(failureSection) // ← Show ONLY failures
111
+ }
112
+ console.error(`\n❌ Tests failed: ${stats.failed}/${stats.total} failed`)
113
+ } else {
114
+ // All passed: show one-line summary
115
+ console.log(`✅ Tests passed: ${stats.passed} passed`)
116
+ }
117
+ }
118
+ ```
119
+
120
+ **In wait mode:**
121
+ - If all tests pass: `✅ Tests passed: 5 passed` (one line)
122
+ - If tests fail: Show failures section + count
123
+
124
+ **Error handling:**
125
+ - Tests failed in wait mode? Continue to markdown generation anyway
126
+ - Tests failed in normal mode? Call `process.exit()` and stop
127
+
128
+ ### 3c) Generate Markdown
129
+
130
+ **Line 299**
131
+
132
+ ```typescript
133
+ await generateMarkdown()
134
+ ```
135
+
136
+ This always happens, even if typecheck or tests failed. This is crucial for wait mode - we want to regenerate the documentation on every change, even if there are errors.
137
+
138
+ ## 4. Watch Loop: watchFilesAndWait()
139
+
140
+ **File: `shell.ts` lines 275-353**
141
+
142
+ This is the heart of the watch mechanism. It waits for either:
143
+ 1. A file to change, or
144
+ 2. The user to press spacebar
145
+
146
+ And returns a Promise that resolves when one of those happens.
147
+
148
+ ### 4a) File Watching - Dependency Collection
149
+
150
+ **Lines 289-300**
151
+
152
+ ```typescript
153
+ const filesToWatch = collectAllDependencies(inputPaths)
154
+
155
+ const watchers = Array.from(filesToWatch).map(filePath => {
156
+ return watch(filePath, (eventType) => {
157
+ const now = Date.now()
158
+ // Debounce: only consider changes if enough time has passed
159
+ if (now - lastChangeTime >= DEBOUNCE_MS) {
160
+ lastChangeTime = now
161
+ changeDetected = true
162
+ }
163
+ })
164
+ })
165
+ ```
166
+
167
+ **Key features:**
168
+
169
+ - **Collects all dependencies**: Not just the input file, but also all files it imports. For example:
170
+ - Input: `file.lit-md.ts`
171
+ - Imports: `./utils.ts`
172
+ - `utils.ts` imports: `./parser.ts`
173
+ - Result: Watch all three files
174
+
175
+ - **Debouncing**: If a file changes rapidly (like during a save operation that triggers multiple write events), debounce for 300ms to avoid redundant runs
176
+
177
+ - **Sets flag**: When a change is detected, set `changeDetected = true`
178
+
179
+ ### 4b) Keyboard Listening - Raw Mode
180
+
181
+ **Lines 316-317**
182
+
183
+ ```typescript
184
+ console.error('Press space to regenerate, Ctrl+C to exit...')
185
+
186
+ process.stdin.setRawMode(true) // ← Detect individual keypress
187
+ process.stdin.resume()
188
+ ```
189
+
190
+ - `setRawMode(true)` puts the terminal in "raw mode", allowing detection of individual key presses (not line-buffered)
191
+ - This lets us respond immediately to spacebar
192
+
193
+ ### 4c) Return a Promise
194
+
195
+ **Lines 319-352**
196
+
197
+ The function returns a Promise that resolves when one of two things happens:
198
+
199
+ #### Handler 1: Keyboard Input
200
+
201
+ ```typescript
202
+ const onData = (data: Buffer) => {
203
+ const char = data[0]
204
+
205
+ if (char === 0x20) { // Spacebar (ASCII 0x20)
206
+ cleanup()
207
+ resolve('spacebar')
208
+ } else if (char === 0x03) { // Ctrl+C (ASCII 0x03)
209
+ cleanup()
210
+ process.exit(0)
211
+ }
212
+ }
213
+
214
+ process.stdin.on('data', onData)
215
+ ```
216
+
217
+ - **Spacebar (0x20)**: Clean up and return `'spacebar'`
218
+ - **Ctrl+C (0x03)**: Close watchers and exit the process
219
+
220
+ #### Handler 2: File Changes
221
+
222
+ ```typescript
223
+ const checkForChanges = setInterval(() => {
224
+ if (changeDetected) {
225
+ cleanup()
226
+ console.error('Files changed, regenerating...')
227
+ resolve('filechange')
228
+ }
229
+ }, 50)
230
+ ```
231
+
232
+ - Checks every 50ms if `changeDetected` flag is set
233
+ - If yes, clean up and return `'filechange'`
234
+
235
+ #### Cleanup Function
236
+
237
+ ```typescript
238
+ const cleanup = () => {
239
+ process.stdin.off('data', onData)
240
+ clearInterval(checkForChanges)
241
+ process.stdin.setRawMode(false) // ← Restore normal terminal
242
+ process.stdin.pause()
243
+ process.removeListener('SIGINT', exitHandler)
244
+ process.removeListener('SIGTERM', exitHandler)
245
+ watchers.forEach(w => w.close()) // ← Close file watchers
246
+ }
247
+ ```
248
+
249
+ Called by either handler to properly clean up before resolving the Promise.
250
+
251
+ ## 5. The Loop Continues
252
+
253
+ **Back in `cli.ts` lines 307-311**
254
+
255
+ ```typescript
256
+ while (true) {
257
+ const trigger = await watchFilesAndWait(inputPaths) // ← Waits here
258
+ // trigger = 'spacebar' OR 'filechange'
259
+
260
+ await executeTasks() // ← Run again!
261
+ // Loop back to watchFilesAndWait()
262
+ }
263
+ ```
264
+
265
+ After `watchFilesAndWait()` returns, `executeTasks()` is called again, which:
266
+ 1. Runs typecheck (if enabled)
267
+ 2. Runs tests with **condensed output** (in wait mode)
268
+ 3. Generates markdown
269
+
270
+ Then the loop goes back to waiting for the next change or spacebar press.
271
+
272
+ ## 6. Key Differences: Wait Mode vs Normal Mode
273
+
274
+ | Aspect | Normal Mode | Wait Mode |
275
+ |--------|------------|-----------|
276
+ | **Execution** | Run once and exit | Run, then enter watch loop |
277
+ | **Test Output** | Show full output | Show condensed summary |
278
+ | **On Typecheck Fail** | Exit with error | Print error, continue |
279
+ | **On Tests Fail** | Exit with error | Show summary, continue |
280
+ | **Target Environment** | CI/CD, one-time runs | Development (iterative) |
281
+
282
+ ## 7. Error Handling in Wait Mode
283
+
284
+ Unlike normal mode, **errors don't stop the process**:
285
+
286
+ - **Typecheck fails**: Print error → continue to tests → generate markdown
287
+ - **Tests fail**: Print summary + failures → generate markdown
288
+
289
+ This is by design. In development, you want to keep the process running so you can make edits and regenerate without restarting. The errors are reported, but they don't halt the workflow.
290
+
291
+ ## 8. Test Output Parsing
292
+
293
+ **`cli.ts` lines 49-70**
294
+
295
+ ```typescript
296
+ function parseTestSummary(output: string): { passed: number; failed: number; total: number; hasFailed: boolean } {
297
+ const lines = output.split('\n')
298
+ let stats = { passed: 0, failed: 0, total: 0, hasFailed: false }
299
+
300
+ for (const line of lines) {
301
+ if (line.includes('ℹ pass')) {
302
+ const match = line.match(/pass\s+(\d+)/)
303
+ if (match) stats.passed = parseInt(match[1])
304
+ }
305
+ if (line.includes('ℹ fail')) {
306
+ const match = line.match(/fail\s+(\d+)/)
307
+ if (match) stats.failed = parseInt(match[1])
308
+ }
309
+ if (line.includes('ℹ tests')) {
310
+ const match = line.match(/tests\s+(\d+)/)
311
+ if (match) stats.total = parseInt(match[1])
312
+ }
313
+ }
314
+
315
+ stats.hasFailed = stats.failed > 0
316
+ return stats
317
+ }
318
+ ```
319
+
320
+ This function looks for summary lines in the test output like:
321
+
322
+ ```
323
+ ℹ tests 12
324
+ ℹ pass 12
325
+ ℹ fail 0
326
+ ```
327
+
328
+ It extracts the numbers and returns an object with:
329
+ - `passed`: Number of passing tests
330
+ - `failed`: Number of failing tests
331
+ - `total`: Total test count
332
+ - `hasFailed`: Boolean to quickly check if there were failures
333
+
334
+ This is used to decide whether to show a simple summary or the failures section.
335
+
336
+ ## 9. Execution Timeline
337
+
338
+ ```
339
+ Start: lit-md --wait --test file.lit-md.ts
340
+
341
+ ├─ Extract --wait flag → wait = true
342
+
343
+ ├─ First executeTasks() call
344
+ │ ├─ Run tests → Show FULL output (stdio: 'inherit')
345
+ │ └─ Generate markdown
346
+
347
+ └─ Check: wait && process.stdin.isTTY? → YES
348
+
349
+ └─ Enter watch loop (infinite)
350
+
351
+ ├─ await watchFilesAndWait() ← Waits here
352
+ │ │
353
+ │ ├─ (User presses spacebar)
354
+ │ │ └─ resolve('spacebar')
355
+ │ │
356
+ │ └─ (Or: User edits file.lit-md.ts)
357
+ │ └─ Detect change → resolve('filechange')
358
+ │ Show: "Files changed, regenerating..."
359
+
360
+ ├─ Second executeTasks() call
361
+ │ ├─ Run tests → Show CONDENSED output (capture and parse)
362
+ │ │ If all passed: ✅ Tests passed: 5 passed
363
+ │ │ If failed: ❌ Tests failed: 1/5 failed + failures section
364
+ │ └─ Generate markdown
365
+
366
+ └─ Loop back to watchFilesAndWait()
367
+ (repeat forever until Ctrl+C)
368
+ ```
369
+
370
+ ## Summary
371
+
372
+ The `--wait` mode creates a development-friendly loop:
373
+
374
+ 1. **Initial run**: Execute all tasks with full output for debugging
375
+ 2. **Watch and wait**: Monitor input files and their dependencies
376
+ 3. **Responsive to input**: Either file changes (auto-regenerate) or spacebar (manual trigger)
377
+ 4. **Condensed feedback**: Show test summaries instead of full output during iterations
378
+ 5. **Resilient to errors**: Keep running even if tests or typecheck fail
379
+ 6. **Clean exit**: Ctrl+C properly closes watchers and restores terminal
380
+
381
+ This makes `--wait` ideal for iterative development where you're editing, testing, and regenerating documentation repeatedly.
@@ -0,0 +1,254 @@
1
+ ## shellExample
2
+
3
+ `shellExample` provides a more structured way to include shell commands,
4
+ with support for
5
+ - input file generation and
6
+ - output file assertions, and
7
+ - more detailed stdout assertions.
8
+
9
+ ```ts
10
+ shellExample('echo "hello world"', { stdout: {display: true}})
11
+ ```
12
+ becomes
13
+ ```sh
14
+ $ echo "hello world"
15
+ hello world
16
+ ```
17
+
18
+ Can contain assertions on stdout, which appear as comments in the emitted markdown.
19
+ Assertions can use `contains` or `matches`, with either strings or regex patterns.
20
+ ```ts
21
+ shellExample('echo "ok"', {stdout: {contains: 'ok'}})
22
+ ```
23
+
24
+ stdout.matches provides an alternative assertion method:
25
+ ```ts
26
+ shellExample('echo "version 1.0.0"', {stdout: {matches: /version \d+\.\d+\.\d+/}})
27
+ ```
28
+
29
+ Can provide input files that are created before the command runs,
30
+ and output file assertions that check for files created by the command and their contents.
31
+ ```ts
32
+ shellExample('cp input.txt output.txt', {
33
+ inputFiles: [{path: 'input.txt', content: 'hello world'}],
34
+ outputFiles: [{path: 'output.txt', contains: 'hello world'}]
35
+ })
36
+ ```
37
+
38
+ Output file assertions can check contents with `contains` (substring or regex) or `matches` (regex or string).
39
+ Both properties are optional — you can specify just one, or display file contents without assertions.
40
+ ```ts
41
+ shellExample('cp input.txt output.txt', {
42
+ inputFiles: [{path: 'input.txt', content: 'first line\nsecond line'}],
43
+ outputFiles: [{path: 'output.txt', matches: /^first/}]
44
+ })
45
+ ```
46
+
47
+ File assertions can also use regex in contains or strings in matches:
48
+ ```ts
49
+ shellExample('cp input.txt output.txt', {
50
+ inputFiles: [{path: 'input.txt', content: 'data.json'}],
51
+ outputFiles: [{path: 'output.txt', contains: /\.json/}]
52
+ })
53
+ ```
54
+
55
+ You can even output the output file contents, or the stdout:
56
+ ```ts
57
+ shellExample('echo "Hello, World!" | tee greeting.txt', {
58
+ stdout: {
59
+ contains: "Hello",
60
+ display: true /* outputs standard out after the command */
61
+ },
62
+ outputFiles: [{
63
+ contains: 'Hello',
64
+ path: 'greeting.txt',
65
+ // display: true, /* by default display, but suppress with `display: false` */
66
+ summary: true
67
+ }]
68
+ })
69
+ ```
70
+
71
+ ```sh
72
+ $ echo "Hello, World!" | tee greeting.txt
73
+ Hello, World!
74
+ ```
75
+
76
+ Output file `greeting.txt` contains `Hello`:
77
+ ```
78
+ Hello, World!
79
+ ```
80
+
81
+ You can also display the `shellExample` call itself in the output using the `meta` option:
82
+ ```ts
83
+ shellExample('echo "Hello, World!"', {
84
+ meta: true,
85
+ stdout: {}
86
+ })
87
+ ```
88
+
89
+ ```ts
90
+ shellExample('echo "Hello, World!"', {
91
+ stdout: {}
92
+ })
93
+ ```
94
+ becomes
95
+ ```sh
96
+ $ echo "Hello, World!"
97
+ ```
98
+
99
+ Input and output files can display their contents:
100
+ ```ts
101
+ shellExample('cat input.txt', {
102
+ inputFiles: [{
103
+ path: 'input.txt',
104
+ content: 'File contents to display',
105
+ display: true, // Show file contents in output
106
+ displayPath: true,
107
+ summary: true
108
+ }],
109
+ stdout: {display: true}
110
+ })
111
+ ```
112
+
113
+ ### Advanced Usage
114
+
115
+ shellExample provides more control and structured options for shell command examples. Use when you need to:
116
+
117
+ - Capture and display stdout dynamically
118
+ - Create input files before running
119
+ - Assert output files match patterns
120
+ - Hide/customize what's displayed
121
+ - Show the function call itself with `meta: true`
122
+ ```ts
123
+ shellExample('echo "hello world"')
124
+ ```
125
+
126
+ With Assertions
127
+ ```ts
128
+ shellExample('echo "ok"', {
129
+ stdout: {contains: 'ok'}
130
+ })
131
+ ```
132
+
133
+ Exit Code Assertions
134
+ ```ts
135
+ shellExample('echo "success"', {
136
+ exitCode: 0,
137
+ stdout: {display: true}
138
+ })
139
+
140
+ shellExample('false', {
141
+ exitCode: 1
142
+ })
143
+
144
+ shellExample('true', {
145
+ exitCode: 0,
146
+ stdout: {display: true}
147
+ })
148
+ ```
149
+
150
+ Input and Output Files
151
+ ```ts
152
+ shellExample('cat input.txt > output.txt', {
153
+ inputFiles: [
154
+ {path: 'input.txt', content: 'Hello'}
155
+ ],
156
+ outputFiles: [
157
+ {path: 'output.txt', matches: /Hello/}
158
+ ]
159
+ })
160
+ ```
161
+
162
+ Timeout Configuration
163
+ ```ts
164
+ shellExample('echo "quick"', {
165
+ timeout: 3000,
166
+ stdout: {display: true}
167
+ })
168
+
169
+ shellExample('echo "instant"', {
170
+ timeout: 500,
171
+ stdout: {display: true}
172
+ })
173
+ ```
174
+
175
+ Options Reference
176
+
177
+ #### displayCommand
178
+
179
+ - Type: `boolean` | `'hidden'`
180
+ - When 'hidden' or false, command is executed but not shown in output
181
+ - Default: true (command is shown)
182
+
183
+ #### stdout
184
+
185
+ - Type: { contains?: string | RegExp; matches?: string | RegExp; display?: boolean }
186
+ - `contains`: Assert output contains this string or matches regex pattern (optional)
187
+ - `matches`: Assert output matches this string (substring) or regex pattern (optional)
188
+ - `display`: When true, dynamically execute and show actual stdout (default: false)
189
+ - At least one of contains/matches is typically specified, but both are optional
190
+
191
+ #### outputFiles
192
+
193
+ Array of output file assertions:
194
+
195
+ - `path`: File path (relative to temp directory)
196
+ - `contains`: String or regex to check if file contains this value (optional)
197
+ - `matches`: String or regex to check if file matches this value (optional)
198
+ - `displayPath`: Show the filename (default: true)
199
+ - `summary`: Show summary line before contents (default: true)
200
+
201
+ #### inputFiles
202
+
203
+ Array of input files to create:
204
+
205
+ - `path`: File path
206
+ - `content`: File contents
207
+ - `displayPath`: Show the filename (default: true)
208
+ - `display`: Show the file contents (default: true)
209
+ - `summary`: Show summary line (default: true)
210
+
211
+ #### exitCode
212
+
213
+ - Type: `number`
214
+ - Asserts the command exits with this specific code
215
+ - When not specified, expects exit code 0 (success)
216
+ - Useful for testing expected failures (e.g., exitCode: 1)
217
+ - If actual exit code doesn't match, command fails with detailed error message
218
+
219
+ #### timeout
220
+
221
+ - Type: `number`
222
+ - Command timeout in milliseconds
223
+ - Default: 3000 (3 seconds)
224
+ - If command runs longer than timeout, throws ETIMEDOUT error
225
+ - Useful for preventing infinite loops or very long-running commands
226
+
227
+ #### meta
228
+
229
+ - Type: `boolean`
230
+ - When true, outputs a fenced code block showing the `shellExample` call itself before the command output
231
+ - The `meta: true` option is removed from the reconstructed call for cleaner documentation
232
+ - Default: false (only shows the command and its output)
233
+ - Useful for showing both the code and its result in documentation
234
+
235
+ #### Example with All Options
236
+ ```ts
237
+ shellExample(
238
+ 'cat input.txt && echo "Done" | tee result.log', {
239
+ displayCommand: true,
240
+ inputFiles: [
241
+ {path: 'input.txt', content: 'Config data', displayPath: true, display: true, summary: true}
242
+ ],
243
+ stdout: {
244
+ contains: 'Done',
245
+ display: true, // Show actual output
246
+ matches: /Done/ // Both contains and matches are optional
247
+ },
248
+ outputFiles: [
249
+ {path: 'result.log', matches: /Done/, displayPath: true, summary: true}
250
+ ],
251
+ exitCode: 0, // Assert successful exit
252
+ timeout: 5000 // Set 5 second timeout
253
+ })
254
+ ```
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@ndp-software/lit-md",
3
+ "version": "0.3.0",
4
+ "description": "Literate test files that generate README.md",
5
+ "license": "UNLICENSED -- All rights reserved. See LICENSE file.",
6
+ "author": "Andy Peterson <andy@ndpsoftware.com>",
7
+ "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": ""
11
+ },
12
+ "keywords": [
13
+ "literate programming",
14
+ "markdown",
15
+ "testing",
16
+ "readme",
17
+ "documentation",
18
+ "typescript"
19
+ ],
20
+ "engines": {
21
+ "node": ">=22"
22
+ },
23
+ "sideEffects": false,
24
+ "bin": {
25
+ "lit-md": "dist/cli.js"
26
+ },
27
+ "files": [
28
+ "README.md",
29
+ "dist/**/*",
30
+ "docs/**/*",
31
+ "src/**/*"
32
+ ],
33
+ "exports": {
34
+ "main": "./dist/index.js",
35
+ "types": "./dist/index.d.ts"
36
+ },
37
+ "scripts": {
38
+ "prepublishOnly": "npm run verify && npm run build",
39
+ "readme": "node ./src/cli.ts --test --typecheck --out ./README.md src/docs/README.lit-md.ts",
40
+ "test": "node --test './test/**/*.test.ts'",
41
+ "test:watch": "node --test --watch './test/**/*.test.ts'",
42
+ "test:acceptance": "node --test test/acceptance.ts",
43
+ "test:update": "node src/cli.ts -u test/acceptance/*.ts test/acceptance/*.js",
44
+ "typecheck": "tsc -p ./tsconfig.check.json",
45
+ "verify": "npm run typecheck && npm run test && node ./src/cli.ts --test --typecheck --dryrun src/docs/README.lit-md.ts",
46
+ "build": "npm run build:types && npm run build:js",
47
+ "build:all": "npm run clean && npm run readme && npm run build",
48
+ "build:types": "tsc -p tsconfig.build.json",
49
+ "build:js": "esbuild ./src/cli.ts --bundle --format=esm --platform=node --target=es2022 --sourcemap --external:typescript --outfile=./dist/cli.js",
50
+ "build:docs": "node ./src/cli.ts --test --typecheck --out ./docs/cli.md ./src/docs/cli.lit-md.ts && node ./src/cli.ts --test --typecheck --out ./docs/shell-examples.md ./src/docs/shell-examples.lit-md.ts",
51
+ "clean": "rm -rf dist"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^25",
55
+ "esbuild": "^0.27.3",
56
+ "typescript": "^5"
57
+ }
58
+ }