nukejs 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +529 -0
  3. package/bin/index.mjs +126 -0
  4. package/dist/app.d.ts +18 -0
  5. package/dist/app.js +124 -0
  6. package/dist/app.js.map +7 -0
  7. package/dist/as-is/Link.d.ts +6 -0
  8. package/dist/as-is/Link.tsx +20 -0
  9. package/dist/as-is/useRouter.d.ts +7 -0
  10. package/dist/as-is/useRouter.ts +33 -0
  11. package/dist/build-common.d.ts +192 -0
  12. package/dist/build-common.js +737 -0
  13. package/dist/build-common.js.map +7 -0
  14. package/dist/build-node.d.ts +1 -0
  15. package/dist/build-node.js +170 -0
  16. package/dist/build-node.js.map +7 -0
  17. package/dist/build-vercel.d.ts +1 -0
  18. package/dist/build-vercel.js +65 -0
  19. package/dist/build-vercel.js.map +7 -0
  20. package/dist/builder.d.ts +1 -0
  21. package/dist/builder.js +97 -0
  22. package/dist/builder.js.map +7 -0
  23. package/dist/bundle.d.ts +68 -0
  24. package/dist/bundle.js +166 -0
  25. package/dist/bundle.js.map +7 -0
  26. package/dist/bundler.d.ts +58 -0
  27. package/dist/bundler.js +98 -0
  28. package/dist/bundler.js.map +7 -0
  29. package/dist/component-analyzer.d.ts +72 -0
  30. package/dist/component-analyzer.js +102 -0
  31. package/dist/component-analyzer.js.map +7 -0
  32. package/dist/config.d.ts +35 -0
  33. package/dist/config.js +30 -0
  34. package/dist/config.js.map +7 -0
  35. package/dist/hmr-bundle.d.ts +25 -0
  36. package/dist/hmr-bundle.js +76 -0
  37. package/dist/hmr-bundle.js.map +7 -0
  38. package/dist/hmr.d.ts +55 -0
  39. package/dist/hmr.js +62 -0
  40. package/dist/hmr.js.map +7 -0
  41. package/dist/html-store.d.ts +121 -0
  42. package/dist/html-store.js +42 -0
  43. package/dist/html-store.js.map +7 -0
  44. package/dist/http-server.d.ts +99 -0
  45. package/dist/http-server.js +166 -0
  46. package/dist/http-server.js.map +7 -0
  47. package/dist/index.d.ts +9 -0
  48. package/dist/index.js +20 -0
  49. package/dist/index.js.map +7 -0
  50. package/dist/logger.d.ts +58 -0
  51. package/dist/logger.js +53 -0
  52. package/dist/logger.js.map +7 -0
  53. package/dist/metadata.d.ts +50 -0
  54. package/dist/metadata.js +43 -0
  55. package/dist/metadata.js.map +7 -0
  56. package/dist/middleware-loader.d.ts +50 -0
  57. package/dist/middleware-loader.js +50 -0
  58. package/dist/middleware-loader.js.map +7 -0
  59. package/dist/middleware.d.ts +22 -0
  60. package/dist/middleware.example.d.ts +8 -0
  61. package/dist/middleware.example.js +58 -0
  62. package/dist/middleware.example.js.map +7 -0
  63. package/dist/middleware.js +59 -0
  64. package/dist/middleware.js.map +7 -0
  65. package/dist/renderer.d.ts +44 -0
  66. package/dist/renderer.js +130 -0
  67. package/dist/renderer.js.map +7 -0
  68. package/dist/router.d.ts +84 -0
  69. package/dist/router.js +104 -0
  70. package/dist/router.js.map +7 -0
  71. package/dist/ssr.d.ts +39 -0
  72. package/dist/ssr.js +168 -0
  73. package/dist/ssr.js.map +7 -0
  74. package/dist/use-html.d.ts +64 -0
  75. package/dist/use-html.js +125 -0
  76. package/dist/use-html.js.map +7 -0
  77. package/dist/utils.d.ts +26 -0
  78. package/dist/utils.js +62 -0
  79. package/dist/utils.js.map +7 -0
  80. package/package.json +64 -12
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NukeJS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,529 @@
1
+ # ☢️ NukeJS
2
+
3
+ A **minimal**, opinionated full-stack React framework on Node.js that server-renders everything and hydrates only interactive parts.
4
+
5
+ ```
6
+ npm create nuke
7
+ ```
8
+
9
+ ## Table of Contents
10
+
11
+ - [Overview](#overview)
12
+ - [Getting Started](#getting-started)
13
+ - [Project Structure](#project-structure)
14
+ - [Pages & Routing](#pages--routing)
15
+ - [Layouts](#layouts)
16
+ - [Client Components](#client-components)
17
+ - [API Routes](#api-routes)
18
+ - [Middleware](#middleware)
19
+ - [Static Files](#static-files)
20
+ - [useHtml() — Head Management](#usehtml--head-management)
21
+ - [Configuration](#configuration)
22
+ - [Building & Deploying](#building--deploying)
23
+
24
+ ## Overview
25
+
26
+ NukeJS gives you:
27
+
28
+ | Feature | Description |
29
+ |---|---|
30
+ | **File-based routing** | Pages in `app/pages/`, API in `server/` |
31
+ | **Server-side rendering** | All pages rendered to HTML on the server |
32
+ | **Partial hydration** | Only `"use client"` components download JS |
33
+ | **SPA navigation** | Client-side page transitions after first load |
34
+ | **Hot module replacement** | Instant page updates during development |
35
+ | **Zero config** | Works out of the box; `nuke.config.ts` for overrides |
36
+ | **Deploy anywhere** | Node.js or Vercel serverless |
37
+
38
+ ### The core idea
39
+
40
+ Most pages don't need JavaScript. NukeJS renders your entire React tree to HTML on the server, and only ships JavaScript for components explicitly marked `"use client"`. Everything else stays server-only — no hydration cost, no JS bundle for static content.
41
+
42
+ ```tsx
43
+ // app/pages/index.tsx — Server component (zero JS sent to browser)
44
+ export default async function Home() {
45
+ const posts = await db.getPosts(); // runs on server only
46
+ return (
47
+ <main>
48
+ <h1>Blog</h1>
49
+ {posts.map(p => <PostCard key={p.id} post={p} />)}
50
+ <LikeButton postId={posts[0].id} /> {/* ← this one is interactive */}
51
+ </main>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ```tsx
57
+ // app/components/LikeButton.tsx — Client component (JS downloaded)
58
+ "use client";
59
+ import { useState } from 'react';
60
+
61
+ export default function LikeButton({ postId }: { postId: string }) {
62
+ const [liked, setLiked] = useState(false);
63
+ return <button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>;
64
+ }
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Getting Started
70
+
71
+ ### Prerequisites
72
+
73
+ - Node.js 20+
74
+ - React 19+
75
+ - esbuild (peer dependency)
76
+
77
+ ### Installation
78
+
79
+ ```bash
80
+ npm create nuke
81
+ ```
82
+
83
+ ### Running the dev server
84
+
85
+ ```bash
86
+ npm run dev
87
+ ```
88
+
89
+ The server starts on port 3000 by default (auto-increments if in use).
90
+
91
+ ---
92
+
93
+ ## Project Structure
94
+
95
+ ```
96
+ my-app/
97
+ ├── app/
98
+ │ ├── pages/ # Page components (file-based routing)
99
+ │ │ ├── layout.tsx # Root layout (wraps every page)
100
+ │ │ ├── index.tsx # → /
101
+ │ │ ├── about.tsx # → /about
102
+ │ │ └── blog/
103
+ │ │ ├── layout.tsx # Blog section layout
104
+ │ │ ├── index.tsx # → /blog
105
+ │ │ └── [slug].tsx # → /blog/:slug
106
+ │ ├── components/ # Shared components (not routed)
107
+ │ └── public/ # Static files served at root (e.g. /favicon.ico)
108
+ ├── server/ # API route handlers
109
+ │ ├── users/
110
+ │ │ ├── index.ts # → GET/POST /users
111
+ │ │ └── [id].ts # → GET/PUT/DELETE /users/:id
112
+ │ └── auth.ts # → /auth
113
+ ├── middleware.ts # (optional) global request middleware
114
+ ├── nuke.config.ts # (optional) configuration
115
+ └── package.json
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Pages & Routing
121
+
122
+ ### Basic pages
123
+
124
+ Each `.tsx` file in `app/pages/` maps to a URL route:
125
+
126
+ | File | URL |
127
+ |---|---|
128
+ | `index.tsx` | `/` |
129
+ | `about.tsx` | `/about` |
130
+ | `blog/index.tsx` | `/blog` |
131
+ | `blog/[slug].tsx` | `/blog/:slug` |
132
+ | `docs/[...path].tsx` | `/docs/*` (catch-all) |
133
+ | `files/[[...path]].tsx` | `/files` or `/files/*` (optional) |
134
+
135
+ ### Page component
136
+
137
+ A page exports a default React component. It may be async (runs on the server).
138
+
139
+ ```tsx
140
+ // app/pages/blog/[slug].tsx
141
+ export default async function BlogPost({ slug }: { slug: string }) {
142
+ const post = await fetchPost(slug);
143
+ return (
144
+ <article>
145
+ <h1>{post.title}</h1>
146
+ <p>{post.content}</p>
147
+ </article>
148
+ );
149
+ }
150
+ ```
151
+
152
+ Route params are passed as props to the component.
153
+
154
+ ### Catch-all routes
155
+
156
+ ```tsx
157
+ // app/pages/docs/[...path].tsx
158
+ export default function Docs({ path }: { path: string[] }) {
159
+ // path = ['getting-started', 'installation'] for /docs/getting-started/installation
160
+ return <DocViewer segments={path} />;
161
+ }
162
+ ```
163
+
164
+ ### Route specificity
165
+
166
+ When multiple routes could match a URL, the most specific one wins:
167
+
168
+ ```
169
+ /users/profile → users/profile.tsx (static, wins)
170
+ /users/42 → users/[id].tsx (dynamic)
171
+ /users/a/b/c → users/[...rest].tsx (catch-all)
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Layouts
177
+
178
+ Place a `layout.tsx` alongside your pages to wrap a group of routes.
179
+
180
+ ```tsx
181
+ // app/pages/layout.tsx — Wraps every page
182
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
183
+ return (
184
+ <div>
185
+ <Nav />
186
+ <main>{children}</main>
187
+ <Footer />
188
+ </div>
189
+ );
190
+ }
191
+ ```
192
+
193
+ Layouts nest automatically. A page at `blog/[slug].tsx` gets wrapped by both `layout.tsx` (root) and `blog/layout.tsx` (blog section).
194
+
195
+ ### Title templates in layouts
196
+
197
+ ```tsx
198
+ // app/pages/layout.tsx
199
+ import { useHtml } from 'nukejs';
200
+
201
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
202
+ useHtml({ title: (prev) => `${prev} | Acme Corp` });
203
+ return <>{children}</>;
204
+ }
205
+
206
+ // app/pages/about.tsx
207
+ export default function About() {
208
+ useHtml({ title: 'About Us' });
209
+ // Final title: "About Us | Acme Corp"
210
+ return <h1>About</h1>;
211
+ }
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Client Components
217
+
218
+ Add `"use client"` as the very first line of any component file to make it a **client component**. NukeJS will:
219
+
220
+ 1. Bundle that file separately and serve it as `/__client-component/<id>.js`
221
+ 2. Render a `<span data-hydrate-id="…">` placeholder in the server HTML
222
+ 3. Hydrate the placeholder with React in the browser
223
+
224
+ ```tsx
225
+ "use client";
226
+ import { useState, useEffect } from 'react';
227
+
228
+ export default function Counter({ initial = 0 }: { initial?: number }) {
229
+ const [count, setCount] = useState(initial);
230
+ return (
231
+ <div>
232
+ <p>Count: {count}</p>
233
+ <button onClick={() => setCount(c => c + 1)}>+</button>
234
+ </div>
235
+ );
236
+ }
237
+ ```
238
+
239
+ ### Rules for client components
240
+
241
+ - The `"use client"` directive must be the **first non-comment line**
242
+ - The component must have a **named default export** (NukeJS uses the function name to match props during hydration)
243
+ - Props must be **JSON-serializable** (no functions, no class instances)
244
+ - React elements passed as props are supported (serialized and reconstructed)
245
+
246
+ ### Passing children to client components
247
+
248
+ Children and other React elements can be passed as props — NukeJS serializes them at render time:
249
+
250
+ ```tsx
251
+ // Server component
252
+ <Modal>
253
+ <p>This content is from the server</p>
254
+ </Modal>
255
+
256
+ // Modal is a "use client" component; its children are serialized as
257
+ // { __re: 'html', tag: 'p', props: { children: 'This content...' } }
258
+ // and reconstructed in the browser before mounting.
259
+ ```
260
+
261
+ ---
262
+
263
+ ## API Routes
264
+
265
+ Export named HTTP method handlers from `.ts` files in your `server/` directory.
266
+
267
+ ```ts
268
+ // server/users/index.ts
269
+ import type { ApiRequest, ApiResponse } from 'nukejs';
270
+
271
+ export async function GET(req: ApiRequest, res: ApiResponse) {
272
+ const users = await db.getUsers();
273
+ res.json(users);
274
+ }
275
+
276
+ export async function POST(req: ApiRequest, res: ApiResponse) {
277
+ const user = await db.createUser(req.body);
278
+ res.json(user, 201);
279
+ }
280
+ ```
281
+
282
+ ```ts
283
+ // server/users/[id].ts
284
+ export async function GET(req: ApiRequest, res: ApiResponse) {
285
+ const { id } = req.params as { id: string };
286
+ const user = await db.getUser(id);
287
+ if (!user) { res.json({ error: 'Not found' }, 404); return; }
288
+ res.json(user);
289
+ }
290
+
291
+ export async function DELETE(req: ApiRequest, res: ApiResponse) {
292
+ await db.deleteUser(req.params!.id as string);
293
+ res.status(204).end();
294
+ }
295
+ ```
296
+
297
+ ### Request object
298
+
299
+ | Property | Type | Description |
300
+ |---|---|---|
301
+ | `req.body` | `any` | Parsed JSON body (or raw string), up to 10 MB |
302
+ | `req.params` | `Record<string, string \| string[]>` | Dynamic route segments |
303
+ | `req.query` | `Record<string, string>` | URL search params |
304
+ | `req.method` | `string` | HTTP method |
305
+ | `req.headers` | `IncomingHttpHeaders` | Request headers |
306
+
307
+ ### Response object
308
+
309
+ | Method | Description |
310
+ |---|---|
311
+ | `res.json(data, status?)` | Send a JSON response (default status 200) |
312
+ | `res.status(code)` | Set status code and return `res` for chaining |
313
+ | `res.setHeader(name, value)` | Set a response header |
314
+ | `res.end(body?)` | Send raw response |
315
+
316
+ ---
317
+
318
+ ## Middleware
319
+
320
+ Create `middleware.ts` in your project root to intercept every request before routing:
321
+
322
+ ```ts
323
+ // middleware.ts
324
+ import type { IncomingMessage, ServerResponse } from 'http';
325
+
326
+ export default async function middleware(
327
+ req: IncomingMessage,
328
+ res: ServerResponse,
329
+ ): Promise<void> {
330
+ // Logging
331
+ console.log(`${req.method} ${req.url}`);
332
+
333
+ // Auth guard
334
+ if (req.url?.startsWith('/admin') && !isAuthenticated(req)) {
335
+ res.statusCode = 401;
336
+ res.end('Unauthorized');
337
+ return; // End response to halt further processing
338
+ }
339
+
340
+ // Header injection (let request continue without ending it)
341
+ res.setHeader('X-Powered-By', 'nukejs');
342
+ }
343
+ ```
344
+
345
+ If `res.end()` (or `res.json()`) is called, NukeJS stops processing and does not handle the request through routing. If middleware returns without ending the response, the request continues to API routes or SSR.
346
+
347
+ ---
348
+
349
+ ## Static Files
350
+
351
+ Place any file in `app/public/` and it will be served directly at its path relative to that directory — no route file needed.
352
+
353
+ ```
354
+ app/public/
355
+ ├── favicon.ico → GET /favicon.ico
356
+ ├── robots.txt → GET /robots.txt
357
+ ├── logo.png → GET /logo.png
358
+ └── fonts/
359
+ └── inter.woff2 → GET /fonts/inter.woff2
360
+ ```
361
+
362
+ Every file type is served with the correct `Content-Type` automatically (images, fonts, CSS, video, audio, JSON, WASM, etc.).
363
+
364
+ Reference public files directly in your components:
365
+
366
+ ```tsx
367
+ export default function Layout({ children }: { children: React.ReactNode }) {
368
+ return (
369
+ <>
370
+ <link rel="icon" href="/favicon.ico" />
371
+ <img src="/logo.png" alt="Logo" />
372
+ {children}
373
+ </>
374
+ );
375
+ }
376
+ ```
377
+
378
+ ### Deployment behaviour
379
+
380
+ | Environment | How public files are served |
381
+ |---|---|
382
+ | `nuke dev` | Served by the built-in middleware before any API or SSR routing |
383
+ | `nuke build` (Node) | Copied to `dist/static/` and served by the production HTTP server |
384
+ | `nuke build` (Vercel) | Copied to `.vercel/output/static/` — served by Vercel's CDN, no function invocation |
385
+
386
+ On Vercel, public files receive the same zero-latency CDN treatment as `__react.js` and `__n.js`.
387
+
388
+ ---
389
+
390
+ ## useHtml() — Head Management
391
+
392
+ The `useHtml()` hook works in both server components and client components to control the document head.
393
+
394
+ ```tsx
395
+ import { useHtml } from 'nukejs';
396
+
397
+ export default function Page() {
398
+ useHtml({
399
+ title: 'My Page',
400
+
401
+ meta: [
402
+ { name: 'description', content: 'Page description' },
403
+ { property: 'og:title', content: 'My Page' },
404
+ ],
405
+
406
+ link: [
407
+ { rel: 'canonical', href: 'https://example.com/page' },
408
+ { rel: 'stylesheet', href: '/styles.css' },
409
+ ],
410
+
411
+ htmlAttrs: { lang: 'en', class: 'dark' },
412
+ bodyAttrs: { class: 'page-home' },
413
+ });
414
+
415
+ return <main>...</main>;
416
+ }
417
+ ```
418
+
419
+ ### Title resolution order
420
+
421
+ When both a layout and a page call `useHtml({ title })`, they are resolved in this order:
422
+
423
+ ```
424
+ Layout: useHtml({ title: (prev) => `${prev} | Site` })
425
+ Page: useHtml({ title: 'Home' })
426
+ Result: "Home | Site"
427
+ ```
428
+
429
+ The page title always serves as the base value; layout functions wrap it outward.
430
+
431
+ ---
432
+
433
+ ## Configuration
434
+
435
+ Create `nuke.config.ts` in your project root:
436
+
437
+ ```ts
438
+ // nuke.config.ts
439
+ export default {
440
+ // Directory containing API route files (default: './server')
441
+ serverDir: './server',
442
+
443
+ // Port for the dev server (default: 3000, auto-increments if in use)
444
+ port: 3000,
445
+
446
+ // Logging verbosity
447
+ // false — silent (default)
448
+ // 'error' — errors only
449
+ // 'info' — startup messages + errors
450
+ // true — verbose (all debug output)
451
+ debug: false,
452
+ };
453
+ ```
454
+
455
+ ---
456
+
457
+ ## Link Component & Navigation
458
+
459
+ Use the built-in `<Link>` component for client-side navigation (no full page reload):
460
+
461
+ ```tsx
462
+ import { Link } from 'nukejs';
463
+
464
+ export default function Nav() {
465
+ return (
466
+ <nav>
467
+ <Link href="/">Home</Link>
468
+ <Link href="/about">About</Link>
469
+ <Link href="/blog">Blog</Link>
470
+ </nav>
471
+ );
472
+ }
473
+ ```
474
+
475
+ ### useRouter
476
+
477
+ ```tsx
478
+ "use client";
479
+ import { useRouter } from 'nukejs';
480
+
481
+ export default function SearchForm() {
482
+ const router = useRouter();
483
+ return (
484
+ <button onClick={() => router.push('/results?q=nuke')}>
485
+ Search
486
+ </button>
487
+ );
488
+ }
489
+ ```
490
+
491
+ ---
492
+
493
+ ## Building & Deploying
494
+
495
+ ### Node.js server
496
+
497
+ ```bash
498
+ npm run build # builds to dist/
499
+ node dist/index.mjs # starts the production server
500
+ ```
501
+
502
+ The build output:
503
+
504
+ ```
505
+ dist/
506
+ ├── api/ # Bundled API route handlers (.mjs)
507
+ ├── pages/ # Bundled page handlers (.mjs)
508
+ ├── static/
509
+ │ ├── __react.js # Bundled React runtime
510
+ │ ├── __n.js # NukeJS client runtime
511
+ │ ├── __client-component/ # Bundled "use client" component files
512
+ │ └── <app/public files> # Copied from app/public/ at build time
513
+ ├── manifest.json # Route dispatch table
514
+ └── index.mjs # HTTP server entry point
515
+ ```
516
+
517
+ ### Vercel
518
+ Just import the code from GitHub.
519
+
520
+ ### Environment variables
521
+
522
+ | Variable | Description |
523
+ |---|---|
524
+ | `ENVIRONMENT=production` | Disables HMR and file watching |
525
+ | `PORT` | Port for the production server |
526
+
527
+ ## License
528
+
529
+ MIT
package/bin/index.mjs ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'child_process';
4
+ import { fileURLToPath } from 'url';
5
+ import path from 'path';
6
+ import fs from 'fs';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const distDir = path.join(__dirname, '../dist');
10
+ const srcDir = path.join(__dirname, '../src');
11
+
12
+ const arg = process.argv[2];
13
+
14
+ // ── helpers ───────────────────────────────────────────────────────────────────
15
+
16
+ function spawnWith(bin, args, extraEnv = {}) {
17
+ const child = spawn(bin, args, {
18
+ stdio: 'inherit',
19
+ cwd: process.cwd(),
20
+ env: { ...process.env, ...extraEnv },
21
+ });
22
+ child.on('exit', (code) => process.exit(code ?? 0));
23
+ }
24
+
25
+ const isWindows = process.platform === 'win32';
26
+
27
+ function resolveBin(name) {
28
+ // On Windows, .bin/ entries are .cmd wrappers — must include the extension
29
+ const candidates = isWindows ? [name + '.cmd', name + '.ps1', name] : [name];
30
+
31
+ const searchDirs = [
32
+ path.join(process.cwd(), 'node_modules', '.bin'), // user's project
33
+ path.join(__dirname, '..', 'node_modules', '.bin'), // nukejs's own deps
34
+ ];
35
+
36
+ for (const dir of searchDirs) {
37
+ for (const candidate of candidates) {
38
+ const full = path.join(dir, candidate);
39
+ if (fs.existsSync(full)) return full;
40
+ }
41
+ }
42
+
43
+ // last resort: rely on PATH (works if tsx is installed globally)
44
+ return isWindows ? name + '.cmd' : name;
45
+ }
46
+
47
+ function runWithNode(scriptPath, extraEnv = {}) {
48
+ if (!fs.existsSync(scriptPath)) {
49
+ console.error(`\n ✖ Cannot find ${path.relative(process.cwd(), scriptPath)}`);
50
+ console.error(` Run "nuke build" first.\n`);
51
+ process.exit(1);
52
+ }
53
+ spawnWith(process.execPath, [scriptPath], extraEnv);
54
+ }
55
+
56
+ const RESTART_CODE = 75;
57
+
58
+ function runWithTsx(scriptPath, extraEnv = {}) {
59
+ if (!fs.existsSync(scriptPath)) {
60
+ console.error(`\n ✖ Cannot find ${path.relative(process.cwd(), scriptPath)}`);
61
+ console.error(` Is the nukejs package intact?\n`);
62
+ process.exit(1);
63
+ }
64
+
65
+ const tsx = resolveBin('tsx');
66
+
67
+ function launch() {
68
+ // On Windows, .bin/ entries are .cmd wrappers which cannot be spawned
69
+ // directly — they require the shell to interpret them. Rather than
70
+ // setting shell:true (which triggers DEP0190 and passes args through an
71
+ // unescaped shell string), we invoke cmd.exe explicitly with /c so the
72
+ // arguments remain as a proper array and are never concatenated by Node.
73
+ const [bin, args] = isWindows && tsx.endsWith('.cmd')
74
+ ? ['cmd.exe', ['/c', tsx, scriptPath]]
75
+ : [tsx, [scriptPath]];
76
+
77
+ const child = spawn(bin, args, {
78
+ stdio: 'inherit',
79
+ cwd: process.cwd(),
80
+ env: { ...process.env, ...extraEnv },
81
+ // shell is always false — cmd.exe /c handles .cmd dispatch on Windows,
82
+ // and Unix never needed it.
83
+ shell: false,
84
+ });
85
+ child.on('exit', (code) => {
86
+ if (code === RESTART_CODE) {
87
+ console.log('\n ↺ Restarting server...\n');
88
+ launch();
89
+ } else {
90
+ process.exit(code ?? 0);
91
+ }
92
+ });
93
+ }
94
+
95
+ launch();
96
+ }
97
+
98
+ // ── commands ──────────────────────────────────────────────────────────────────
99
+
100
+ if (!arg || arg === 'dev') {
101
+ // nuke | nuke dev → prefer src/app.ts (monorepo / local dev),
102
+ // fall back to dist/app.js (installed package)
103
+ const srcEntry = path.join(srcDir, 'app.ts');
104
+ const distEntry = path.join(distDir, 'app.js');
105
+ const devScript = fs.existsSync(srcEntry) ? srcEntry : distEntry;
106
+ runWithTsx(devScript, { ENVIRONMENT: 'development' });
107
+
108
+ } else if (arg === 'build') {
109
+ // nuke build → run compiled dist via plain node
110
+ const isVercel = !!(
111
+ process.env.VERCEL ||
112
+ process.env.VERCEL_ENV ||
113
+ process.env.NOW_BUILDER
114
+ );
115
+
116
+ if (isVercel) {
117
+ runWithNode(path.join(distDir, 'build-vercel.js'));
118
+ } else {
119
+ runWithNode(path.join(distDir, 'build-node.js'));
120
+ }
121
+
122
+ } else {
123
+ console.error(`\n ✖ Unknown command: "${arg}"`);
124
+ console.error(` Usage: nuke [dev|build]\n`);
125
+ process.exit(1);
126
+ }
package/dist/app.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * app.ts — NukeJS Dev Server Entry Point
3
+ *
4
+ * This is the runtime that powers `nuke dev`. It:
5
+ * 1. Loads your nuke.config.ts (or uses sensible defaults)
6
+ * 2. Discovers API route prefixes from your server directory
7
+ * 3. Starts an HTTP server that handles:
8
+ * /__hmr_ping — heartbeat for HMR reconnect polling
9
+ * /__react.js — bundled React + ReactDOM (resolved via importmap)
10
+ * /__n.js — NukeJS client runtime bundle
11
+ * /__client-component/* — on-demand "use client" component bundles
12
+ * /api/** — API route handlers from serverDir
13
+ * /** — SSR pages from app/pages
14
+ * 4. Watches for file changes and broadcasts HMR events to connected browsers
15
+ *
16
+ * In production (ENVIRONMENT=production), HMR and all file watching are skipped.
17
+ */
18
+ export {};