@ape-egg/vibe 1.0.5 → 1.1.1

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +228 -23
  3. package/compiler/bin/vibe-compile.js +109 -0
  4. package/compiler/native/.gitkeep +0 -0
  5. package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
  6. package/compiler/src/Cargo.lock +1885 -0
  7. package/compiler/src/Cargo.toml +29 -0
  8. package/compiler/src/compiler/compile.rs +1209 -0
  9. package/compiler/src/compiler/mod.rs +5 -0
  10. package/compiler/src/config.rs +184 -0
  11. package/compiler/src/main.rs +284 -0
  12. package/compiler/src/parser/element.rs +96 -0
  13. package/compiler/src/parser/html.rs +339 -0
  14. package/compiler/src/parser/mod.rs +8 -0
  15. package/index.js +2 -248
  16. package/package.json +26 -3
  17. package/{affected.js → runtime/affected.js} +64 -4
  18. package/runtime/cleanup.js +59 -0
  19. package/runtime/component.js +116 -0
  20. package/{conditionals.js → runtime/conditionals.js} +25 -11
  21. package/{constants.js → runtime/constants.js} +23 -3
  22. package/runtime/debug.js +91 -0
  23. package/{hydrate.js → runtime/hydrate.js} +57 -7
  24. package/runtime/index.js +614 -0
  25. package/{iterate.js → runtime/iterate.js} +53 -45
  26. package/{iteration-utils.js → runtime/iteration-utils.js} +11 -1
  27. package/{parse.js → runtime/parse.js} +37 -7
  28. package/runtime/state.js +52 -0
  29. package/ROADMAP.md +0 -289
  30. package/llms.txt +0 -279
  31. package/state.js +0 -26
  32. /package/{_vibe-compiled-iteration-batch.js → runtime/_vibe-compiled-iteration-batch.js} +0 -0
  33. /package/{link.js → runtime/manifest.js} +0 -0
  34. /package/{utils.js → runtime/utils.js} +0 -0
  35. /package/{vibe.css → runtime/vibe.css} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,106 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.1] - 2026-02-01
4
+
5
+ ### Added
6
+
7
+ - **Compiler CLI**: Modern command structure with subcommand support
8
+ - New bin command: `vibe`
9
+ - Subcommand syntax: `bunx vibe compile [options]` or shorthand `bunx vibe c [options]`
10
+ - Accessible directly via `bunx` without manual npm scripts
11
+ - Automatic detection of unknown subcommands with helpful error messages
12
+ - Backward compatibility: works with or without `compile` subcommand
13
+ - **Validation time tracking**: Compiler now reports component validation phase timing separately
14
+ - Shows time spent fetching external components
15
+ - Helps explain total compilation time vs individual phase times
16
+ - Displayed as: `* Validated components in XXms`
17
+ - **Component tree output**: Improved verbose logging for component compilation
18
+ - Tree structure with nested dependencies (e.g., nav.html → top-of-nav.html, menu.html)
19
+ - Usage counts for each unique component
20
+ - Color-coded: internal (green) vs external (magenta)
21
+ - Summary format: `Compiled components (X internal, Y external)`
22
+ - Clean hierarchical display with `└─` for children
23
+ - **Compiler documentation**: Updated READMEs with new CLI commands
24
+ - `/vibe/README.md` and `/vibe/compiler/README.md` now show `bunx vibe compile` syntax
25
+ - Added npm scripts examples for optional shortcuts
26
+ - Removed outdated `bun vibe:compile` references
27
+
28
+ ### Changed
29
+
30
+ - **Compiler output formatting**: Consistent spacing between major sections (Vibe Compiler, Compiler config, Compiled components, etc.)
31
+ - **Verbose logging**: Removed redundant component loading list, streamlined validation output
32
+
33
+ ---
34
+
35
+ ## [1.0.7] - 2026-01-30
36
+
37
+ ### Added
38
+
39
+ - **`<component>` element**: Runtime implementation of reusable HTML components
40
+ - Fetch HTML from local or remote sources: `<component src="/components/card.html">`
41
+ - Props system: Pass reactive bindings `headline="@[title]"` or static values `theme="dark"`
42
+ - Children via `<slot>`: Content between tags replaces `<slot></slot>` in fetched HTML
43
+ - Recursive resolution: Fetched HTML can contain more `<component>` elements
44
+ - Runtime-first with future compiler optimization planned (Phase 5)
45
+ - **Deep reactivity**: Nested property mutations now automatically trigger updates
46
+ - Recursive proxy implementation wraps all nested objects and arrays
47
+ - `$.obj.nested.prop = x` now triggers reactivity (previously required `$.obj = { ...$.obj, nested: { ...$.obj.nested, prop: x } }`)
48
+ - WeakMap cache prevents double-wrapping of proxies
49
+ - Tracks root property for each mutation to trigger correct affected element checks
50
+ - Same approach as Vue 3 - tiny performance cost for massive DX improvement
51
+ - **Scoped state for iterations**: Iteration instances now preserve local context for reactive updates
52
+ - Each `<!-- each items as item -->` instance stores its own `scopedState` (Proxy wrapper with local variables)
53
+ - When global state changes (e.g., `tutorialProgress`), affected elements inside iterations can access both local (`item`) and global variables
54
+ - Enables reactive attributes like `completed="@[tutorialProgress[item.id]]"` to update correctly without full re-render
55
+ - Scoped state flows through `affected.js` → `hydrate.js` pipeline for proper evaluation context
56
+ - **Compiler (first pass)**: Rust-based optional build tool
57
+ - Asset handling with whitelist approach (fonts, images, media, docs automatically copied)
58
+ - HTML/CSS/JS processing
59
+ - Configuration via `package.json` (`vibe-compiler` section)
60
+ - Node.js wrapper for cross-platform execution
61
+ - Foundation for future optimizations (component inlining, iteration optimization, two-way binding sugar)
62
+ - **Unit tests**: Comprehensive unit test suite (212 tests) for runtime modules using Bun's test framework - tests pure function layer (utils, state, iteration-utils, conditionals, affected, manifest) with aggressive edge case coverage
63
+ - **NPM scripts**: Added `bun unit` and `bun unit:watch` commands for running unit tests
64
+ - **Name bindings**: Bindings in attribute name position now supported
65
+ - Syntax: `<icon @[section.icon]></icon>` evaluates expression and sets it as attribute name
66
+ - HTML lowercases attribute names, so case-insensitive property lookup is used for simple properties
67
+ - Old attributes are automatically cleaned up when the bound value changes
68
+ - Useful for dynamic attribute names like icon libraries, custom elements, or conditional attributes
69
+ - Works in all contexts: root level, iterations, conditionals, and inside `<component>` elements
70
+
71
+ ### Fixed
72
+
73
+ - **Replace abort**: `<component>` elements now properly abort pending fetches (via `AbortController`) when removed from DOM before fetch completes, preventing "no parent node" errors and saving bandwidth
74
+ - **Nested iterations and conditionals**: Fixed issue where nested `<!-- each -->` and `<!-- if -->` blocks inside `<component>` elements would render then immediately disappear
75
+ - Root cause: MutationObserver re-parsing created fresh runtime state, causing iterations to think they needed re-rendering
76
+ - Solution: Added `__vibeRendered` marker on comment nodes that persists across re-parses
77
+ - Now properly handles complex nested structures like menu systems with dynamic sections and items
78
+ - **Conditionals wrapping iteration content**: Fixed conditionals in attribute name position (e.g., `<!-- if condition -->` wrapping `<nav-section>`) not being detected inside iteration templates
79
+ - Root cause: `initializeBlock()` only parsed the first element node, missing comment nodes like `<!-- if -->`
80
+ - Solution: Parse all template nodes in a container to capture comment nodes, ensuring tree element references match actual DOM nodes
81
+ - Enables patterns like conditional menu items: `<!-- each items as item --><!-- if condition --><item>...</item><!-- /if --><!-- /each -->`
82
+ - **StructuredClone with proxies**: Replaced `structuredClone()` with custom `deepClone()` that properly unwraps proxy objects for state snapshots in hooks
83
+
84
+ ---
85
+
86
+ ## [1.0.6] - 2026-01-29
87
+
88
+ ### Added
89
+
90
+ - **Debug mode**: New optional third parameter to enable colored console logging of Vibe's lifecycle phases (Attach, Parse, Manifest, Hydrate, Iterate, Condition, Observe, Fetch, Complete, Update, Mutate)
91
+ - **Debug helper**: Centralized `debugLog()` function with phase-specific color coding and tree-style indentation for nested operations
92
+
93
+ ### Fixed
94
+
95
+ - **Nested brackets**: `BINDING_REGEX` and `PURE_BINDING_REGEX` now correctly parse bindings with one level of nested brackets (e.g., `@[items[0]]`, `@[obj[key]]`)
96
+
97
+ ### Changed
98
+
99
+ - **Internal naming**: Renamed `link.js` → `manifest.js` and `linkList` → `manifest` throughout codebase to better reflect its purpose as a DOM element registry/manifest
100
+ - **Phase constants**: Added `PHASE_*` constants for all lifecycle phases to ensure consistent naming across debug logs
101
+
102
+ ---
103
+
3
104
  ## [1.0.5] - 2026-01-25
4
105
 
5
106
  ### Fixed
package/README.md CHANGED
@@ -4,34 +4,71 @@ A runtime-first reactive library.
4
4
 
5
5
  No virtual DOM. No build step. Just modern JavaScript.
6
6
 
7
- ## Install
8
-
9
7
  ```bash
10
8
  npm install @ape-egg/vibe
11
9
  ```
12
10
 
13
- ## Quick Start
11
+ ---
12
+
13
+ ## Vibe Runtime
14
+
15
+ The core reactive runtime. Works directly in the browser without any build tools.
16
+
17
+ ### Quick Start
14
18
 
15
19
  ```html
16
- <script type="module">
17
- import state from "@ape-egg/vibe";
18
- window.$ = state({ name: "World", count: 0 });
19
- </script>
20
+ <html>
21
+ <head>
22
+ <link rel="stylesheet" href="@ape-egg/vibe/vibe.css">
23
+ <script type="module">
24
+ import state from "@ape-egg/vibe";
25
+ // Attaches to element with attribute "vibe" by default
26
+ window.$ = state({ name: "World", count: 0 });
27
+ </script>
28
+ </head>
29
+ <body vibe>
20
30
 
21
31
  <h1>Hello, @[name]!</h1>
22
32
  <button onclick="$.count++">Clicked @[count] times</button>
33
+
34
+ </body>
35
+ </html>
23
36
  ```
24
37
 
25
- ## Features
38
+ ### Prevent FOUC
39
+
40
+ To prevent a flash of unstyled content while Vibe hydrates:
41
+
42
+ 1. Include `vibe.css` in your HTML
43
+ 2. Add the `vibe` attribute to an element (typically `<body>`) to mark the hydration boundary
44
+
45
+ The CSS targets `[vibe]` and hides it until hydration completes. Once Vibe finishes processing, it removes the attribute, revealing your content.
26
46
 
27
47
  ### Reactive Bindings
28
48
 
29
- Use `@[property]` anywhere in HTML or CSS:
49
+ Vibe supports bindings in three positions:
30
50
 
51
+ **Text content** — Inside element tags:
31
52
  ```html
32
53
  <div>@[firstName] @[lastName]</div>
33
- <div>@[firstName + ' ' + lastName]</div>
54
+ <h1>Hello, @[name]!</h1>
55
+ ```
56
+
57
+ **Attribute values** — In attribute value position:
58
+ ```html
59
+ <input value="@[username]" />
60
+ <div class="@[theme]" style="color: @[color]"></div>
61
+ <a href="@[url]">Link</a>
62
+ ```
34
63
 
64
+ **Attribute names** — In attribute name position (useful for dynamic attributes):
65
+ ```html
66
+ <icon @[iconName]></icon>
67
+ <button @[state]>Click me</button>
68
+ ```
69
+
70
+ **CSS** — Bindings also work in style tags:
71
+ ```html
35
72
  <style>
36
73
  .box { background: @[themeColor]; }
37
74
  </style>
@@ -65,6 +102,16 @@ Nested iteration with dot paths:
65
102
  <!-- /if -->
66
103
  ```
67
104
 
105
+ ### Components
106
+
107
+ Runtime component loading with props and slots:
108
+
109
+ ```html
110
+ <component src="/components/card.html" title="@[pageTitle]" theme="dark">
111
+ <p>Content passed as slot</p>
112
+ </component>
113
+ ```
114
+
68
115
  ### Dehydrate
69
116
 
70
117
  Skip reactive processing for an element:
@@ -73,32 +120,190 @@ Skip reactive processing for an element:
73
120
  <code dehydrate>@[this] displays literally</code>
74
121
  ```
75
122
 
76
- ### Dynamic Elements
123
+ ### Deep Reactivity
77
124
 
78
- Elements added via JavaScript are automatically hydrated through MutationObserver.
125
+ Vibe uses recursive proxies to detect changes at any nesting level:
79
126
 
80
- ## How It Works
127
+ ```javascript
128
+ // All of these trigger reactive updates:
129
+ $.user.name = "Alice";
130
+ $.todos[2].completed = true;
131
+ $.config.theme.colors.primary = "#007bff";
132
+ ```
133
+
134
+ No need for immutable update patterns or spread operators. Just mutate and Vibe handles the rest.
135
+
136
+ ### How It Works
81
137
 
82
138
  1. **Proxy-based state** — `window.$` intercepts property changes
83
- 2. **DOM parsing** — Finds all `@[...]` bindings on load
84
- 3. **Surgical updates** — Only affected elements re-render
85
- 4. **MutationObserver** — Tracks dynamically added elements
139
+ 2. **Deep reactivity** — Nested mutations trigger updates automatically (`$.obj.nested.prop = x`)
140
+ 3. **DOM parsing** — Finds all `@[...]` bindings on load
141
+ 4. **Surgical updates** — Only affected elements re-render
142
+ 5. **MutationObserver** — Tracks dynamically added elements
86
143
 
87
- ## Prevent FOUC
144
+ ---
88
145
 
89
- Hide content until hydration completes:
146
+ ## Vibe Compiler
90
147
 
91
- ```html
92
- <body style="visibility: hidden;">
148
+ Optional build step for production optimization. The compiler processes HTML files, inlines components, and handles assets while preserving directory structure.
149
+
150
+ ### Installation
151
+
152
+ The compiler requires Rust for non-macOS ARM64 platforms:
153
+
154
+ ```bash
155
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
156
+ ```
157
+
158
+ **Note:** A prebuilt binary for macOS ARM64 is included. Other platforms will compile from source automatically.
159
+
160
+ ### Usage
161
+
162
+ ```bash
163
+ # Initialize config in package.json
164
+ bunx vibe compile --init
165
+
166
+ # Compile
167
+ bunx vibe compile
168
+
169
+ # With options
170
+ bunx vibe compile --verbose # Step-by-step logging
171
+ bunx vibe compile --minify # Minify output
172
+ bunx vibe compile --accessibility # Transform custom tags to divs
173
+ bunx vibe compile --validate # Validate HTML syntax
174
+ bunx vibe compile --create-manifest # Generate hydration manifest
175
+ bunx vibe compile --source-maps # Generate source maps
176
+ bunx vibe compile --node-modules-as-is # Copy node_modules as-is
177
+ bunx vibe compile --watch # Watch mode (future)
93
178
  ```
94
179
 
95
- Or use the included CSS:
180
+ Or via npm scripts:
181
+
182
+ ```json
183
+ {
184
+ "scripts": {
185
+ "vibe:compile": "vibe compile",
186
+ "vibe:compile:init": "vibe compile --init"
187
+ }
188
+ }
189
+ ```
190
+
191
+ Then run with `bun vibe:compile` or `npm run vibe:compile`.
192
+
193
+ ### Configuration
194
+
195
+ Add to your `package.json`:
196
+
197
+ ```json
198
+ {
199
+ "vibe-compiler": {
200
+ "source": "./",
201
+ "output": "./compiled",
202
+ "components": "components",
203
+ "pages": "pages",
204
+ "assets": "assets",
205
+ "minify": false,
206
+ "accessibility": false,
207
+ "excludeTags": [],
208
+ "nodeModulesAsIs": false
209
+ }
210
+ }
211
+ ```
212
+
213
+ **Defaults** (when no config):
214
+ - `source`: `./`
215
+ - `output`: `./compiled`
216
+ - `components`: `<source>/components`
217
+ - `pages`: `<source>/pages`
218
+ - `assets`: `<source>/assets`
219
+
220
+ ### Asset Handling
221
+
222
+ The compiler uses a whitelist approach. These file types are automatically copied:
223
+
224
+ **Fonts:** `.ttf`, `.otf`, `.woff`, `.woff2`, `.eot`
225
+ **Images:** `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`, `.avif`, `.ico`
226
+ **Media:** `.mp4`, `.webm`, `.ogg`, `.mp3`, `.wav`, `.flac`, `.aac`
227
+ **Documents:** `.pdf`
228
+ **Data:** `.json`, `.xml`, `.csv`
229
+
230
+ HTML, CSS, and JavaScript files are processed by the compiler.
231
+
232
+ ### Node Modules (Self-Contained Output)
233
+
234
+ By default, the compiler creates deployable output by installing production dependencies directly into the output directory:
235
+
236
+ 1. Copies `package.json` and lockfile to output
237
+ 2. Runs `<package-manager> install --production` in output directory
238
+ 3. Removes `package.json` and lockfile from output (cleanup)
239
+
240
+ This ensures:
241
+ - Compiled output only includes runtime dependencies (from `dependencies`, not `devDependencies`)
242
+ - Local `node_modules` is never modified
243
+ - Faster than copying (no intermediate copy step)
244
+
245
+ **Package Manager Detection:**
246
+ Auto-detects based on lockfiles: `bun.lockb`, `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
247
+
248
+ **Opt-out:**
249
+ Set `nodeModulesAsIs: true` in config or use `--node-modules-as-is` flag to copy node_modules as-is.
250
+
251
+ ### Output Structure
252
+
253
+ Mirrors source structure:
254
+
255
+ ```
256
+ src/ compiled/
257
+ ├── pages/ ├── pages/
258
+ │ └── index.html │ └── index.html
259
+ ├── components/ │ (components inlined)
260
+ │ └── counter.html
261
+ ├── assets/ ├── assets/
262
+ │ └── image.png │ └── image.png
263
+ └── node_modules/ └── node_modules/
264
+ └── @ape-egg/ └── @ape-egg/
265
+ └── vibe/ └── vibe/
266
+ ```
267
+
268
+ ### Accessibility Mode
269
+
270
+ `--accessibility` transforms custom HTML elements to divs with classes:
96
271
 
97
272
  ```html
98
- <link rel="stylesheet" href="@ape-egg/vibe/vibe.css">
99
- <body vibe>
273
+ <!-- input -->
274
+ <counter-header>Count</counter-header>
275
+
276
+ <!-- output -->
277
+ <div class="counter-header">Count</div>
100
278
  ```
101
279
 
280
+ Exclude specific tags (e.g., web components) via `excludeTags` config.
281
+
282
+ ### Building Native Binaries
283
+
284
+ For distribution without Rust:
285
+
286
+ ```bash
287
+ cd node_modules/@ape-egg/vibe/compiler/src
288
+
289
+ # Build for current platform
290
+ cargo build --release
291
+
292
+ # Copy binary to native directory
293
+ cp target/release/vibe-compiler ../native/vibe-compiler-darwin-arm64
294
+ ```
295
+
296
+ Supported platforms:
297
+ - `vibe-compiler-darwin-arm64` (macOS Apple Silicon) ✅ Included
298
+ - `vibe-compiler-darwin-x64` (macOS Intel)
299
+ - `vibe-compiler-linux-x64`
300
+ - `vibe-compiler-linux-arm64`
301
+ - `vibe-compiler-win32-x64.exe`
302
+
303
+ The CLI wrapper automatically falls back to `cargo run` when native binaries aren't present.
304
+
305
+ ---
306
+
102
307
  ## Browser Support
103
308
 
104
309
  Modern browsers with Proxy and MutationObserver support.
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join } from 'node:path';
6
+ import { existsSync } from 'node:fs';
7
+ import { platform, arch } from 'node:os';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const nativeDir = join(__dirname, '..', 'native');
11
+ const originalCwd = process.cwd();
12
+
13
+ // Map Node.js platform/arch to binary names
14
+ const getPlatformBinary = () => {
15
+ const os = platform();
16
+ const cpu = arch();
17
+
18
+ const platformMap = {
19
+ 'darwin-arm64': 'vibe-compiler-darwin-arm64',
20
+ 'darwin-x64': 'vibe-compiler-darwin-x64',
21
+ 'linux-x64': 'vibe-compiler-linux-x64',
22
+ 'linux-arm64': 'vibe-compiler-linux-arm64',
23
+ 'win32-x64': 'vibe-compiler-win32-x64.exe',
24
+ };
25
+
26
+ const key = `${os}-${cpu}`;
27
+ const binary = platformMap[key];
28
+
29
+ if (!binary) {
30
+ console.error(`Unsupported platform: ${os}-${cpu}`);
31
+ console.error('Supported platforms: darwin-arm64, darwin-x64, linux-x64, linux-arm64, win32-x64');
32
+ process.exit(1);
33
+ }
34
+
35
+ return binary;
36
+ };
37
+
38
+ const run = () => {
39
+ const binary = getPlatformBinary();
40
+ const binaryPath = join(nativeDir, binary);
41
+ let userArgs = process.argv.slice(2);
42
+
43
+ // Handle subcommand: vibe compile [args]
44
+ // If first arg is "compile" or "c", remove it (the binary doesn't need it)
45
+ if (userArgs[0] === 'compile' || userArgs[0] === 'c') {
46
+ userArgs = userArgs.slice(1);
47
+ } else if (userArgs[0] && !userArgs[0].startsWith('-')) {
48
+ // Unknown subcommand
49
+ console.error(`Unknown subcommand: ${userArgs[0]}`);
50
+ console.error('Available subcommands: compile (or c)');
51
+ console.error('Usage: vibe compile [options]');
52
+ process.exit(1);
53
+ }
54
+ // If no subcommand provided, default to compile behavior (pass args as-is)
55
+
56
+ // Check if native binary exists
57
+ if (!existsSync(binaryPath)) {
58
+ // Fall back to cargo run for development
59
+ console.info('[vibe-compile] Native binary not found, building with cargo...');
60
+
61
+ const srcDir = join(__dirname, '..', 'src');
62
+
63
+ if (!existsSync(join(srcDir, 'Cargo.toml'))) {
64
+ console.error('Neither native binary nor Rust source found.');
65
+ console.error(`Expected binary at: ${binaryPath}`);
66
+ console.error(`Or Cargo.toml at: ${srcDir}`);
67
+ process.exit(1);
68
+ }
69
+
70
+ // Pass --cwd to tell the Rust binary the original working directory
71
+ const cargoArgs = ['run', '--release', '--', '--cwd', originalCwd, ...userArgs];
72
+
73
+ const cargo = spawn('cargo', cargoArgs, {
74
+ cwd: srcDir,
75
+ stdio: 'inherit',
76
+ });
77
+
78
+ cargo.on('error', (err) => {
79
+ if (err.code === 'ENOENT') {
80
+ console.error('Cargo not found. Please install Rust: https://rustup.rs');
81
+ } else {
82
+ console.error('Failed to run cargo:', err.message);
83
+ }
84
+ process.exit(1);
85
+ });
86
+
87
+ cargo.on('close', (code) => {
88
+ process.exit(code ?? 0);
89
+ });
90
+
91
+ return;
92
+ }
93
+
94
+ // Run the native binary (it runs in the user's cwd, so no --cwd needed)
95
+ const child = spawn(binaryPath, userArgs, {
96
+ stdio: 'inherit',
97
+ });
98
+
99
+ child.on('error', (err) => {
100
+ console.error('Failed to run vibe-compiler:', err.message);
101
+ process.exit(1);
102
+ });
103
+
104
+ child.on('close', (code) => {
105
+ process.exit(code ?? 0);
106
+ });
107
+ };
108
+
109
+ run();
File without changes