@b9g/shovel 0.2.12 → 0.2.14
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/CHANGELOG.md +8 -0
- package/bin/create.js +259 -143
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Shovel will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.13] - 2026-02-24
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **`@b9g/shovel`** - Fixed missing `esbuild-plugins-node-modules-polyfill` dependency causing "Cannot find package" errors at runtime
|
|
10
|
+
|
|
5
11
|
## [0.2.12] - 2026-02-24
|
|
6
12
|
|
|
7
13
|
### Bug Fixes
|
|
8
14
|
|
|
9
15
|
- **`@b9g/shovel`** - Plain `.js` and `.mjs` client assets are now bundled for the browser instead of copied raw. Previously only `.ts`/`.tsx`/`.jsx` were transpiled, so non-TypeScript Crank projects using `@b9g/crank/standalone` in client code failed with "Module name does not resolve to a valid URL" in the browser
|
|
10
16
|
- **`@b9g/shovel`** - Removed `.cts` from asset transpilation list (no CJS in client assets)
|
|
17
|
+
- **`@b9g/shovel`** - Replaced deprecated `@esbuild-plugins/node-modules-polyfill` and `@esbuild-plugins/node-globals-polyfill` with `esbuild-plugins-node-modules-polyfill`, eliminating deprecation warnings and audit vulnerabilities
|
|
11
18
|
- **`@b9g/router`** - Simplified logger middleware to a single response line (`200 GET / (3ms)`) instead of logging both request and response with arrow prefixes
|
|
12
19
|
|
|
13
20
|
### Improvements
|
|
@@ -17,6 +24,7 @@ All notable changes to Shovel will be documented in this file.
|
|
|
17
24
|
- **`create-shovel`** - Added API call demo to full-stack vanilla template
|
|
18
25
|
- **`create-shovel`** - Crank templates now include client-side hydration with `@b9g/assets`
|
|
19
26
|
- **`create-shovel`** - Initial project version is now `0.1.0` instead of `0.0.1`
|
|
27
|
+
- **`create-shovel`** - Bumped generated eslint to `^10.0.0` (fixes audit vulnerabilities in generated projects)
|
|
20
28
|
|
|
21
29
|
### Dependencies
|
|
22
30
|
|
package/bin/create.js
CHANGED
|
@@ -265,31 +265,43 @@ async function createProject(config, projectPath) {
|
|
|
265
265
|
await mkdir(join(projectPath, "src"), { recursive: true });
|
|
266
266
|
const ext = config.uiFramework === "crank" && config.useJSX ? config.typescript ? "tsx" : "jsx" : config.typescript ? "ts" : "js";
|
|
267
267
|
const isCrank = config.uiFramework === "crank";
|
|
268
|
-
const
|
|
268
|
+
const hasClientBundle = config.template === "static-site" || config.template === "full-stack";
|
|
269
|
+
const entryFile = hasClientBundle ? `src/server.${ext}` : `src/app.${ext}`;
|
|
269
270
|
const startCmd = config.platform === "bun" ? "bun dist/server/supervisor.js" : "node dist/server/supervisor.js";
|
|
270
271
|
const dependencies = {
|
|
271
272
|
"@b9g/router": "^0.2.0",
|
|
272
273
|
"@b9g/shovel": "^0.2.0"
|
|
273
274
|
};
|
|
275
|
+
if (hasClientBundle) {
|
|
276
|
+
dependencies["@b9g/assets"] = "^0.2.0";
|
|
277
|
+
}
|
|
274
278
|
if (isCrank) {
|
|
275
279
|
dependencies["@b9g/crank"] = "^0.7.2";
|
|
276
|
-
|
|
280
|
+
}
|
|
281
|
+
if (config.uiFramework === "htmx") {
|
|
282
|
+
dependencies["htmx.org"] = "^2.0.0";
|
|
283
|
+
}
|
|
284
|
+
if (config.uiFramework === "alpine") {
|
|
285
|
+
dependencies["alpinejs"] = "^3.14.0";
|
|
277
286
|
}
|
|
278
287
|
const devDependencies = {};
|
|
279
288
|
if (config.typescript) {
|
|
280
289
|
devDependencies["@types/node"] = "^18.0.0";
|
|
281
290
|
devDependencies["typescript"] = "^5.0.0";
|
|
282
291
|
}
|
|
283
|
-
if (
|
|
292
|
+
if (hasClientBundle) {
|
|
284
293
|
devDependencies["eslint"] = "^10.0.0";
|
|
285
294
|
devDependencies["@eslint/js"] = "^10.0.0";
|
|
295
|
+
if (config.typescript) {
|
|
296
|
+
devDependencies["typescript-eslint"] = "^8.0.0";
|
|
297
|
+
}
|
|
286
298
|
}
|
|
287
299
|
const scripts = {
|
|
288
300
|
develop: `shovel develop ${entryFile} --platform ${config.platform}`,
|
|
289
301
|
build: `shovel build ${entryFile} --platform ${config.platform}`,
|
|
290
302
|
start: startCmd
|
|
291
303
|
};
|
|
292
|
-
if (
|
|
304
|
+
if (hasClientBundle) {
|
|
293
305
|
scripts.lint = "eslint src/";
|
|
294
306
|
}
|
|
295
307
|
const packageJSON = {
|
|
@@ -322,34 +334,50 @@ async function createProject(config, projectPath) {
|
|
|
322
334
|
esModuleInterop: true,
|
|
323
335
|
strict: true,
|
|
324
336
|
skipLibCheck: true,
|
|
337
|
+
noEmit: true,
|
|
338
|
+
allowImportingTsExtensions: true,
|
|
325
339
|
forceConsistentCasingInFileNames: true,
|
|
326
|
-
lib: ["ES2022", "WebWorker"]
|
|
340
|
+
lib: ["ES2022", "DOM", "DOM.Iterable", "WebWorker"]
|
|
327
341
|
};
|
|
328
342
|
if (config.uiFramework === "crank" && config.useJSX) {
|
|
329
343
|
compilerOptions.jsx = "react-jsx";
|
|
330
344
|
compilerOptions.jsxImportSource = "@b9g/crank";
|
|
331
345
|
}
|
|
332
346
|
const tsConfig = {
|
|
333
|
-
compilerOptions
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
347
|
+
compilerOptions,
|
|
348
|
+
include: [
|
|
349
|
+
"src/**/*",
|
|
350
|
+
"node_modules/@b9g/platform/src/globals.d.ts",
|
|
351
|
+
"dist/server/shovel.d.ts"
|
|
352
|
+
],
|
|
353
|
+
exclude: []
|
|
339
354
|
};
|
|
340
355
|
await writeFile(
|
|
341
356
|
join(projectPath, "tsconfig.json"),
|
|
342
357
|
JSON.stringify(tsConfig, null, 2)
|
|
343
358
|
);
|
|
344
359
|
}
|
|
345
|
-
if (
|
|
346
|
-
|
|
360
|
+
if (hasClientBundle) {
|
|
361
|
+
let eslintConfig;
|
|
362
|
+
if (config.typescript) {
|
|
363
|
+
eslintConfig = `import js from "@eslint/js";
|
|
364
|
+
import tseslint from "typescript-eslint";
|
|
365
|
+
|
|
366
|
+
export default tseslint.config(
|
|
367
|
+
js.configs.recommended,
|
|
368
|
+
tseslint.configs.recommended,
|
|
369
|
+
{ ignores: ["dist/"] },
|
|
370
|
+
);
|
|
371
|
+
`;
|
|
372
|
+
} else {
|
|
373
|
+
eslintConfig = `import js from "@eslint/js";
|
|
347
374
|
|
|
348
375
|
export default [
|
|
349
376
|
js.configs.recommended,
|
|
350
377
|
{ ignores: ["dist/"] },
|
|
351
378
|
];
|
|
352
379
|
`;
|
|
380
|
+
}
|
|
353
381
|
await writeFile(join(projectPath, "eslint.config.js"), eslintConfig);
|
|
354
382
|
}
|
|
355
383
|
const readme = generateReadme(config);
|
|
@@ -477,44 +505,35 @@ function generateStaticSite(config) {
|
|
|
477
505
|
function generateStaticSiteVanilla(config) {
|
|
478
506
|
const ext = config.typescript ? "ts" : "js";
|
|
479
507
|
const t = config.typescript;
|
|
480
|
-
return
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
async function handleRequest(request${t ? ": Request" : ""})${t ? ": Promise<Response>" : ""} {
|
|
488
|
-
const url = new URL(request.url);
|
|
508
|
+
return {
|
|
509
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
510
|
+
import {assets} from "@b9g/assets/middleware";
|
|
511
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
512
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
489
513
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
<h1>Welcome to ${config.name}</h1>
|
|
493
|
-
<p>Edit <code>src/app.${ext}</code> to get started.</p>
|
|
494
|
-
<button id="counter">Clicked: 0</button>
|
|
495
|
-
<script>
|
|
496
|
-
let count = 0;
|
|
497
|
-
const btn = document.getElementById("counter");
|
|
498
|
-
btn.addEventListener("click", () => btn.textContent = "Clicked: " + ++count);
|
|
499
|
-
</script>
|
|
500
|
-
<p><a href="/about">About</a></p>
|
|
501
|
-
\`), {
|
|
502
|
-
headers: { "Content-Type": "text/html" },
|
|
503
|
-
});
|
|
504
|
-
}
|
|
514
|
+
const router = new Router();
|
|
515
|
+
router.use(assets());
|
|
505
516
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
}
|
|
517
|
+
router.route("/").get(() => {
|
|
518
|
+
return new Response(renderPage("Home", \`
|
|
519
|
+
<h1>Welcome to ${config.name}</h1>
|
|
520
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
521
|
+
<button id="counter">Clicked: 0</button>
|
|
522
|
+
<p><a href="/about">About</a></p>
|
|
523
|
+
\`), {
|
|
524
|
+
headers: { "Content-Type": "text/html" },
|
|
525
|
+
});
|
|
526
|
+
});
|
|
515
527
|
|
|
516
|
-
|
|
517
|
-
|
|
528
|
+
router.route("/about").get(() => {
|
|
529
|
+
return new Response(renderPage("About", \`
|
|
530
|
+
<h1>About</h1>
|
|
531
|
+
<p>This is a static site built with <strong>Shovel</strong>.</p>
|
|
532
|
+
<p><a href="/">Home</a></p>
|
|
533
|
+
\`), {
|
|
534
|
+
headers: { "Content-Type": "text/html" },
|
|
535
|
+
});
|
|
536
|
+
});
|
|
518
537
|
|
|
519
538
|
function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})${t ? ": string" : ""} {
|
|
520
539
|
return \`<!DOCTYPE html>
|
|
@@ -529,56 +548,64 @@ ${css}
|
|
|
529
548
|
</head>
|
|
530
549
|
<body>
|
|
531
550
|
<main>\${content}</main>
|
|
551
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
532
552
|
</body>
|
|
533
553
|
</html>\`;
|
|
534
554
|
}
|
|
535
|
-
|
|
555
|
+
|
|
556
|
+
self.addEventListener("fetch", (event) => {
|
|
557
|
+
event.respondWith(router.handle(event.request));
|
|
558
|
+
});
|
|
559
|
+
`,
|
|
560
|
+
[`client.${ext}`]: `const btn = document.getElementById("counter");
|
|
561
|
+
if (btn) {
|
|
562
|
+
let count = 0;
|
|
563
|
+
btn.addEventListener("click", () => btn.textContent = "Clicked: " + ++count);
|
|
564
|
+
}
|
|
565
|
+
`
|
|
566
|
+
};
|
|
536
567
|
}
|
|
537
568
|
function generateStaticSiteHtmx(config) {
|
|
538
569
|
const ext = config.typescript ? "ts" : "js";
|
|
539
570
|
const t = config.typescript;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
async function handleRequest(request${t ? ": Request" : ""})${t ? ": Promise<Response>" : ""} {
|
|
548
|
-
const url = new URL(request.url);
|
|
571
|
+
const files = {
|
|
572
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
573
|
+
import {assets} from "@b9g/assets/middleware";
|
|
574
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
575
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
549
576
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
<h1>Welcome to ${config.name}</h1>
|
|
553
|
-
<p>Edit <code>src/app.${ext}</code> to get started.</p>
|
|
554
|
-
<button hx-get="/greeting" hx-target="#result" hx-swap="innerHTML">Get Greeting</button>
|
|
555
|
-
<div id="result"></div>
|
|
556
|
-
<p><a href="/about">About</a></p>
|
|
557
|
-
\`), {
|
|
558
|
-
headers: { "Content-Type": "text/html" },
|
|
559
|
-
});
|
|
560
|
-
}
|
|
577
|
+
const router = new Router();
|
|
578
|
+
router.use(assets());
|
|
561
579
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
580
|
+
router.route("/").get(() => {
|
|
581
|
+
return new Response(renderPage("Home", \`
|
|
582
|
+
<h1>Welcome to ${config.name}</h1>
|
|
583
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
584
|
+
<button hx-get="/greeting" hx-target="#result" hx-swap="innerHTML">Get Greeting</button>
|
|
585
|
+
<div id="result"></div>
|
|
586
|
+
<p><a href="/about">About</a></p>
|
|
587
|
+
\`), {
|
|
588
|
+
headers: { "Content-Type": "text/html" },
|
|
589
|
+
});
|
|
590
|
+
});
|
|
569
591
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
});
|
|
578
|
-
}
|
|
592
|
+
router.route("/greeting").get(() => {
|
|
593
|
+
const hour = new Date().getHours();
|
|
594
|
+
const greeting = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening";
|
|
595
|
+
return new Response(\`<p>\${greeting}! The time is \${new Date().toLocaleTimeString()}.</p>\`, {
|
|
596
|
+
headers: { "Content-Type": "text/html" },
|
|
597
|
+
});
|
|
598
|
+
});
|
|
579
599
|
|
|
580
|
-
|
|
581
|
-
|
|
600
|
+
router.route("/about").get(() => {
|
|
601
|
+
return new Response(renderPage("About", \`
|
|
602
|
+
<h1>About</h1>
|
|
603
|
+
<p>This is a static site built with <strong>Shovel</strong> and <strong>HTMX</strong>.</p>
|
|
604
|
+
<p><a href="/">Home</a></p>
|
|
605
|
+
\`), {
|
|
606
|
+
headers: { "Content-Type": "text/html" },
|
|
607
|
+
});
|
|
608
|
+
});
|
|
582
609
|
|
|
583
610
|
function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})${t ? ": string" : ""} {
|
|
584
611
|
return \`<!DOCTYPE html>
|
|
@@ -587,56 +614,64 @@ function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})$
|
|
|
587
614
|
<meta charset="UTF-8">
|
|
588
615
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
589
616
|
<title>\${title} - ${config.name}</title>
|
|
590
|
-
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
|
591
617
|
<style>
|
|
592
618
|
${css}
|
|
593
619
|
</style>
|
|
594
620
|
</head>
|
|
595
621
|
<body>
|
|
596
622
|
<main>\${content}</main>
|
|
623
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
597
624
|
</body>
|
|
598
625
|
</html>\`;
|
|
599
626
|
}
|
|
627
|
+
|
|
628
|
+
self.addEventListener("fetch", (event) => {
|
|
629
|
+
event.respondWith(router.handle(event.request));
|
|
630
|
+
});
|
|
631
|
+
`,
|
|
632
|
+
[`client.${ext}`]: `import "htmx.org";
|
|
633
|
+
`
|
|
634
|
+
};
|
|
635
|
+
if (t) {
|
|
636
|
+
files[`env.d.ts`] = `declare module "htmx.org";
|
|
600
637
|
`;
|
|
638
|
+
}
|
|
639
|
+
return files;
|
|
601
640
|
}
|
|
602
641
|
function generateStaticSiteAlpine(config) {
|
|
603
642
|
const ext = config.typescript ? "ts" : "js";
|
|
604
643
|
const t = config.typescript;
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
async function handleRequest(request${t ? ": Request" : ""})${t ? ": Promise<Response>" : ""} {
|
|
613
|
-
const url = new URL(request.url);
|
|
644
|
+
const files = {
|
|
645
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
646
|
+
import {assets} from "@b9g/assets/middleware";
|
|
647
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
648
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
614
649
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
<h1>Welcome to ${config.name}</h1>
|
|
618
|
-
<p>Edit <code>src/app.${ext}</code> to get started.</p>
|
|
619
|
-
<div x-data="{ count: 0 }">
|
|
620
|
-
<button @click="count++">Clicked: <span x-text="count"></span></button>
|
|
621
|
-
</div>
|
|
622
|
-
<p><a href="/about">About</a></p>
|
|
623
|
-
\`), {
|
|
624
|
-
headers: { "Content-Type": "text/html" },
|
|
625
|
-
});
|
|
626
|
-
}
|
|
650
|
+
const router = new Router();
|
|
651
|
+
router.use(assets());
|
|
627
652
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
653
|
+
router.route("/").get(() => {
|
|
654
|
+
return new Response(renderPage("Home", \`
|
|
655
|
+
<h1>Welcome to ${config.name}</h1>
|
|
656
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
657
|
+
<div x-data="{ count: 0 }">
|
|
658
|
+
<button @click="count++">Clicked: <span x-text="count"></span></button>
|
|
659
|
+
</div>
|
|
660
|
+
<p><a href="/about">About</a></p>
|
|
661
|
+
\`), {
|
|
662
|
+
headers: { "Content-Type": "text/html" },
|
|
663
|
+
});
|
|
664
|
+
});
|
|
637
665
|
|
|
638
|
-
|
|
639
|
-
|
|
666
|
+
router.route("/about").get(() => {
|
|
667
|
+
return new Response(renderPage("About", \`
|
|
668
|
+
<h1>About</h1>
|
|
669
|
+
<p>This is a static site built with <strong>Shovel</strong> and <strong>Alpine.js</strong>.</p>
|
|
670
|
+
<p><a href="/">Home</a></p>
|
|
671
|
+
\`), {
|
|
672
|
+
headers: { "Content-Type": "text/html" },
|
|
673
|
+
});
|
|
674
|
+
});
|
|
640
675
|
|
|
641
676
|
function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})${t ? ": string" : ""} {
|
|
642
677
|
return \`<!DOCTYPE html>
|
|
@@ -645,17 +680,38 @@ function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})$
|
|
|
645
680
|
<meta charset="UTF-8">
|
|
646
681
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
647
682
|
<title>\${title} - ${config.name}</title>
|
|
648
|
-
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js"></script>
|
|
649
683
|
<style>
|
|
650
684
|
${css}
|
|
651
685
|
</style>
|
|
652
686
|
</head>
|
|
653
687
|
<body>
|
|
654
688
|
<main>\${content}</main>
|
|
689
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
655
690
|
</body>
|
|
656
691
|
</html>\`;
|
|
657
692
|
}
|
|
693
|
+
|
|
694
|
+
self.addEventListener("fetch", (event) => {
|
|
695
|
+
event.respondWith(router.handle(event.request));
|
|
696
|
+
});
|
|
697
|
+
`,
|
|
698
|
+
[`client.${ext}`]: `import Alpine from "alpinejs";
|
|
699
|
+
Alpine.start();
|
|
700
|
+
`
|
|
701
|
+
};
|
|
702
|
+
if (t) {
|
|
703
|
+
files[`env.d.ts`] = `declare module "alpinejs" {
|
|
704
|
+
interface Alpine {
|
|
705
|
+
start(): void;
|
|
706
|
+
plugin(plugin: unknown): void;
|
|
707
|
+
[key: string]: unknown;
|
|
708
|
+
}
|
|
709
|
+
const alpine: Alpine;
|
|
710
|
+
export default alpine;
|
|
711
|
+
}
|
|
658
712
|
`;
|
|
713
|
+
}
|
|
714
|
+
return files;
|
|
659
715
|
}
|
|
660
716
|
function generateStaticSiteCrank(config) {
|
|
661
717
|
const t = config.typescript;
|
|
@@ -666,7 +722,8 @@ function generateStaticSiteCrank(config) {
|
|
|
666
722
|
import {Router} from "@b9g/router";
|
|
667
723
|
import {assets} from "@b9g/assets/middleware";
|
|
668
724
|
import {Page, Counter} from "./components";
|
|
669
|
-
|
|
725
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
726
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
670
727
|
|
|
671
728
|
const router = new Router();
|
|
672
729
|
router.use(assets());
|
|
@@ -747,7 +804,8 @@ import {renderer} from "@b9g/crank/html";
|
|
|
747
804
|
import {Router} from "@b9g/router";
|
|
748
805
|
import {assets} from "@b9g/assets/middleware";
|
|
749
806
|
import {Page, Counter} from "./components";
|
|
750
|
-
|
|
807
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
808
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
751
809
|
|
|
752
810
|
const router = new Router();
|
|
753
811
|
router.use(assets());
|
|
@@ -838,11 +896,16 @@ function generateFullStack(config) {
|
|
|
838
896
|
function generateFullStackVanilla(config) {
|
|
839
897
|
const ext = config.typescript ? "ts" : "js";
|
|
840
898
|
const t = config.typescript;
|
|
841
|
-
return
|
|
899
|
+
return {
|
|
900
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
842
901
|
import {logger} from "@b9g/router/middleware";
|
|
902
|
+
import {assets} from "@b9g/assets/middleware";
|
|
903
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
904
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
843
905
|
|
|
844
906
|
const router = new Router();
|
|
845
907
|
router.use(logger());
|
|
908
|
+
router.use(assets());
|
|
846
909
|
|
|
847
910
|
// API routes
|
|
848
911
|
router.route("/api/hello").get(() => {
|
|
@@ -861,17 +924,9 @@ router.route("/api/echo").post(async (req) => {
|
|
|
861
924
|
router.route("/").get(() => {
|
|
862
925
|
return new Response(renderPage("Home", \`
|
|
863
926
|
<h1>Welcome to ${config.name}</h1>
|
|
864
|
-
<p>Edit <code>src/
|
|
927
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
865
928
|
<button id="call-api">Call API</button>
|
|
866
929
|
<div id="result"></div>
|
|
867
|
-
<script>
|
|
868
|
-
document.getElementById("call-api").addEventListener("click", async () => {
|
|
869
|
-
const res = await fetch("/api/hello");
|
|
870
|
-
const data = await res.json();
|
|
871
|
-
document.getElementById("result").innerHTML =
|
|
872
|
-
"<p>" + data.message + "</p><p><small>" + data.timestamp + "</small></p>";
|
|
873
|
-
});
|
|
874
|
-
</script>
|
|
875
930
|
<ul>
|
|
876
931
|
<li><a href="/about">About</a></li>
|
|
877
932
|
<li><a href="/api/hello">API: /api/hello</a></li>
|
|
@@ -904,6 +959,7 @@ ${css}
|
|
|
904
959
|
</head>
|
|
905
960
|
<body>
|
|
906
961
|
<main>\${content}</main>
|
|
962
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
907
963
|
</body>
|
|
908
964
|
</html>\`;
|
|
909
965
|
}
|
|
@@ -911,16 +967,35 @@ ${css}
|
|
|
911
967
|
self.addEventListener("fetch", (event) => {
|
|
912
968
|
event.respondWith(router.handle(event.request));
|
|
913
969
|
});
|
|
914
|
-
|
|
970
|
+
`,
|
|
971
|
+
[`client.${ext}`]: `const callBtn = document.getElementById("call-api");
|
|
972
|
+
if (callBtn) {
|
|
973
|
+
callBtn.addEventListener("click", async () => {
|
|
974
|
+
const res = await fetch("/api/hello");
|
|
975
|
+
const data = await res.json();
|
|
976
|
+
const result = document.getElementById("result");
|
|
977
|
+
if (result) {
|
|
978
|
+
result.innerHTML =
|
|
979
|
+
\`<p>\${data.message}</p><p><small>\${data.timestamp}</small></p>\`;
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
`
|
|
984
|
+
};
|
|
915
985
|
}
|
|
916
986
|
function generateFullStackHtmx(config) {
|
|
917
987
|
const ext = config.typescript ? "ts" : "js";
|
|
918
988
|
const t = config.typescript;
|
|
919
|
-
|
|
989
|
+
const files = {
|
|
990
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
920
991
|
import {logger} from "@b9g/router/middleware";
|
|
992
|
+
import {assets} from "@b9g/assets/middleware";
|
|
993
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
994
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
921
995
|
|
|
922
996
|
const router = new Router();
|
|
923
997
|
router.use(logger());
|
|
998
|
+
router.use(assets());
|
|
924
999
|
|
|
925
1000
|
// API routes \u2014 return HTML fragments when HTMX requests, JSON otherwise
|
|
926
1001
|
router.route("/api/hello").get((req) => {
|
|
@@ -946,7 +1021,7 @@ router.route("/api/echo").post(async (req) => {
|
|
|
946
1021
|
router.route("/").get(() => {
|
|
947
1022
|
return new Response(renderPage("Home", \`
|
|
948
1023
|
<h1>Welcome to ${config.name}</h1>
|
|
949
|
-
<p>Edit <code>src/
|
|
1024
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
950
1025
|
<button hx-get="/api/hello" hx-target="#result" hx-swap="innerHTML">Call API</button>
|
|
951
1026
|
<div id="result"></div>
|
|
952
1027
|
<ul>
|
|
@@ -975,13 +1050,13 @@ function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})$
|
|
|
975
1050
|
<meta charset="UTF-8">
|
|
976
1051
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
977
1052
|
<title>\${title} - ${config.name}</title>
|
|
978
|
-
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
|
979
1053
|
<style>
|
|
980
1054
|
${css}
|
|
981
1055
|
</style>
|
|
982
1056
|
</head>
|
|
983
1057
|
<body>
|
|
984
1058
|
<main>\${content}</main>
|
|
1059
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
985
1060
|
</body>
|
|
986
1061
|
</html>\`;
|
|
987
1062
|
}
|
|
@@ -989,16 +1064,29 @@ ${css}
|
|
|
989
1064
|
self.addEventListener("fetch", (event) => {
|
|
990
1065
|
event.respondWith(router.handle(event.request));
|
|
991
1066
|
});
|
|
1067
|
+
`,
|
|
1068
|
+
[`client.${ext}`]: `import "htmx.org";
|
|
1069
|
+
`
|
|
1070
|
+
};
|
|
1071
|
+
if (t) {
|
|
1072
|
+
files[`env.d.ts`] = `declare module "htmx.org";
|
|
992
1073
|
`;
|
|
1074
|
+
}
|
|
1075
|
+
return files;
|
|
993
1076
|
}
|
|
994
1077
|
function generateFullStackAlpine(config) {
|
|
995
1078
|
const ext = config.typescript ? "ts" : "js";
|
|
996
1079
|
const t = config.typescript;
|
|
997
|
-
|
|
1080
|
+
const files = {
|
|
1081
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
998
1082
|
import {logger} from "@b9g/router/middleware";
|
|
1083
|
+
import {assets} from "@b9g/assets/middleware";
|
|
1084
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
1085
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
999
1086
|
|
|
1000
1087
|
const router = new Router();
|
|
1001
1088
|
router.use(logger());
|
|
1089
|
+
router.use(assets());
|
|
1002
1090
|
|
|
1003
1091
|
// API routes
|
|
1004
1092
|
router.route("/api/hello").get(() => {
|
|
@@ -1017,7 +1105,7 @@ router.route("/api/echo").post(async (req) => {
|
|
|
1017
1105
|
router.route("/").get(() => {
|
|
1018
1106
|
return new Response(renderPage("Home", \`
|
|
1019
1107
|
<h1>Welcome to ${config.name}</h1>
|
|
1020
|
-
<p>Edit <code>src/
|
|
1108
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
1021
1109
|
<div x-data="{ result: null }">
|
|
1022
1110
|
<button @click="fetch('/api/hello').then(r => r.json()).then(d => result = d)">Call API</button>
|
|
1023
1111
|
<div id="result" x-show="result">
|
|
@@ -1051,13 +1139,13 @@ function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})$
|
|
|
1051
1139
|
<meta charset="UTF-8">
|
|
1052
1140
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1053
1141
|
<title>\${title} - ${config.name}</title>
|
|
1054
|
-
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js"></script>
|
|
1055
1142
|
<style>
|
|
1056
1143
|
${css}
|
|
1057
1144
|
</style>
|
|
1058
1145
|
</head>
|
|
1059
1146
|
<body>
|
|
1060
1147
|
<main>\${content}</main>
|
|
1148
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
1061
1149
|
</body>
|
|
1062
1150
|
</html>\`;
|
|
1063
1151
|
}
|
|
@@ -1065,7 +1153,24 @@ ${css}
|
|
|
1065
1153
|
self.addEventListener("fetch", (event) => {
|
|
1066
1154
|
event.respondWith(router.handle(event.request));
|
|
1067
1155
|
});
|
|
1156
|
+
`,
|
|
1157
|
+
[`client.${ext}`]: `import Alpine from "alpinejs";
|
|
1158
|
+
Alpine.start();
|
|
1159
|
+
`
|
|
1160
|
+
};
|
|
1161
|
+
if (t) {
|
|
1162
|
+
files[`env.d.ts`] = `declare module "alpinejs" {
|
|
1163
|
+
interface Alpine {
|
|
1164
|
+
start(): void;
|
|
1165
|
+
plugin(plugin: unknown): void;
|
|
1166
|
+
[key: string]: unknown;
|
|
1167
|
+
}
|
|
1168
|
+
const alpine: Alpine;
|
|
1169
|
+
export default alpine;
|
|
1170
|
+
}
|
|
1068
1171
|
`;
|
|
1172
|
+
}
|
|
1173
|
+
return files;
|
|
1069
1174
|
}
|
|
1070
1175
|
function generateFullStackCrank(config) {
|
|
1071
1176
|
const t = config.typescript;
|
|
@@ -1077,7 +1182,8 @@ import {Router} from "@b9g/router";
|
|
|
1077
1182
|
import {logger} from "@b9g/router/middleware";
|
|
1078
1183
|
import {assets} from "@b9g/assets/middleware";
|
|
1079
1184
|
import {Page, Counter} from "./components";
|
|
1080
|
-
|
|
1185
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
1186
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
1081
1187
|
|
|
1082
1188
|
const router = new Router();
|
|
1083
1189
|
router.use(logger());
|
|
@@ -1177,7 +1283,8 @@ import {Router} from "@b9g/router";
|
|
|
1177
1283
|
import {logger} from "@b9g/router/middleware";
|
|
1178
1284
|
import {assets} from "@b9g/assets/middleware";
|
|
1179
1285
|
import {Page, Counter} from "./components";
|
|
1180
|
-
|
|
1286
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
1287
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
1181
1288
|
|
|
1182
1289
|
const router = new Router();
|
|
1183
1290
|
router.use(logger());
|
|
@@ -1286,6 +1393,7 @@ function generateReadme(config) {
|
|
|
1286
1393
|
};
|
|
1287
1394
|
const ext = config.uiFramework === "crank" && config.useJSX ? config.typescript ? "tsx" : "jsx" : config.typescript ? "ts" : "js";
|
|
1288
1395
|
const isCrank = config.uiFramework === "crank";
|
|
1396
|
+
const hasClientBundle = config.template === "static-site" || config.template === "full-stack";
|
|
1289
1397
|
let projectTree;
|
|
1290
1398
|
if (isCrank) {
|
|
1291
1399
|
projectTree = `${config.name}/
|
|
@@ -1295,6 +1403,14 @@ function generateReadme(config) {
|
|
|
1295
1403
|
\u2502 \u2514\u2500\u2500 client.${ext} # Client-side hydration
|
|
1296
1404
|
\u251C\u2500\u2500 eslint.config.js
|
|
1297
1405
|
\u251C\u2500\u2500 package.json
|
|
1406
|
+
${config.typescript ? "\u251C\u2500\u2500 tsconfig.json\n" : ""}\u2514\u2500\u2500 README.md`;
|
|
1407
|
+
} else if (hasClientBundle) {
|
|
1408
|
+
projectTree = `${config.name}/
|
|
1409
|
+
\u251C\u2500\u2500 src/
|
|
1410
|
+
\u2502 \u251C\u2500\u2500 server.${ext} # Application entry point
|
|
1411
|
+
\u2502 \u2514\u2500\u2500 client.${ext} # Client-side code
|
|
1412
|
+
\u251C\u2500\u2500 eslint.config.js
|
|
1413
|
+
\u251C\u2500\u2500 package.json
|
|
1298
1414
|
${config.typescript ? "\u251C\u2500\u2500 tsconfig.json\n" : ""}\u2514\u2500\u2500 README.md`;
|
|
1299
1415
|
} else {
|
|
1300
1416
|
projectTree = `${config.name}/
|
|
@@ -1320,7 +1436,7 @@ Open http://localhost:7777
|
|
|
1320
1436
|
|
|
1321
1437
|
- \`npm run develop\` - Start development server
|
|
1322
1438
|
- \`npm run build\` - Build for production
|
|
1323
|
-
- \`npm start\` - Run production build${
|
|
1439
|
+
- \`npm start\` - Run production build${hasClientBundle ? "\n- `npm run lint` - Lint source files" : ""}
|
|
1324
1440
|
|
|
1325
1441
|
## Project Structure
|
|
1326
1442
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/shovel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "ServiceWorker-first universal deployment platform. Write ServiceWorker apps once, deploy anywhere (Node/Bun/Cloudflare). Registry-based multi-app orchestration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"@b9g/filesystem": "^0.2.0",
|
|
20
20
|
"@b9g/http-errors": "^0.2.1",
|
|
21
21
|
"@b9g/node-webworker": "^0.2.1",
|
|
22
|
-
"@b9g/platform": "^0.1.
|
|
22
|
+
"@b9g/platform": "^0.1.18",
|
|
23
23
|
"@b9g/platform-bun": "^0.1.16",
|
|
24
24
|
"@b9g/platform-cloudflare": "^0.1.15",
|
|
25
25
|
"@b9g/platform-node": "^0.1.17",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"@logtape/logtape": "^2.0.0",
|
|
28
28
|
"commander": "^13.1.0",
|
|
29
29
|
"esbuild": "^0.27.2",
|
|
30
|
+
"esbuild-plugins-node-modules-polyfill": "^1.8.1",
|
|
30
31
|
"mime": "^4.0.4",
|
|
31
32
|
"zod": "^3.23.0"
|
|
32
33
|
},
|
|
@@ -40,7 +41,6 @@
|
|
|
40
41
|
"@types/bun": "^1.3.4",
|
|
41
42
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
42
43
|
"@typescript-eslint/parser": "^8.0.0",
|
|
43
|
-
"esbuild-plugins-node-modules-polyfill": "^1.8.1",
|
|
44
44
|
"eslint": "^9.0.0",
|
|
45
45
|
"eslint-config-prettier": "^10.0.0",
|
|
46
46
|
"eslint-plugin-prettier": "^5.0.0",
|