@b9g/shovel 0.2.0-beta.8 → 0.2.0-beta.9

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 (3) hide show
  1. package/bin/cli.js +215 -156
  2. package/bin/create.js +55 -13
  3. package/package.json +9 -9
package/bin/cli.js CHANGED
@@ -24,8 +24,7 @@ import { loadConfig } from "@b9g/platform/config";
24
24
 
25
25
  // src/esbuild/watcher.ts
26
26
  import * as ESBuild from "esbuild";
27
- import { existsSync as existsSync2 } from "fs";
28
- import { resolve, join as join2, dirname as dirname3 } from "path";
27
+ import { resolve, join as join3 } from "path";
29
28
  import { mkdir } from "fs/promises";
30
29
  import { assetsPlugin } from "@b9g/assets/plugin";
31
30
 
@@ -36,8 +35,8 @@ import { pathToFileURL } from "url";
36
35
  function importMetaPlugin() {
37
36
  return {
38
37
  name: "import-meta-transform",
39
- setup(build2) {
40
- build2.onLoad({ filter: /\.[jt]sx?$/, namespace: "file" }, async (args) => {
38
+ setup(build3) {
39
+ build3.onLoad({ filter: /\.[jt]sx?$/, namespace: "file" }, async (args) => {
41
40
  if (args.path.includes("node_modules")) {
42
41
  return null;
43
42
  }
@@ -168,7 +167,12 @@ async function loadJSXConfig(projectRoot) {
168
167
  ...tsOptions
169
168
  };
170
169
  }
171
- } catch {
170
+ } catch (err) {
171
+ if (!(err instanceof SyntaxError) || !/^(Unexpected token|Expected|JSON)/i.test(
172
+ String(err.message)
173
+ )) {
174
+ throw err;
175
+ }
172
176
  }
173
177
  }
174
178
  return { ...CRANK_JSX_DEFAULTS };
@@ -191,19 +195,43 @@ function applyJSXOptions(buildOptions, jsxOptions) {
191
195
  }
192
196
  }
193
197
 
194
- // src/esbuild/watcher.ts
195
- import { getLogger } from "@logtape/logtape";
196
- var logger = getLogger(["watcher"]);
197
- function findProjectRoot() {
198
- let dir = process.cwd();
198
+ // src/utils/project.ts
199
+ import { existsSync as existsSync2, readFileSync } from "fs";
200
+ import { dirname as dirname3, join as join2 } from "path";
201
+ function findProjectRoot(startDir = process.cwd()) {
202
+ let dir = startDir;
199
203
  while (dir !== dirname3(dir)) {
200
204
  if (existsSync2(join2(dir, "package.json"))) {
201
205
  return dir;
202
206
  }
203
207
  dir = dirname3(dir);
204
208
  }
205
- return process.cwd();
209
+ return startDir;
210
+ }
211
+ function findWorkspaceRoot(startDir = process.cwd()) {
212
+ let dir = startDir;
213
+ while (dir !== dirname3(dir)) {
214
+ const packageJsonPath = join2(dir, "package.json");
215
+ if (existsSync2(packageJsonPath)) {
216
+ try {
217
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
218
+ if (packageJson.workspaces) {
219
+ return dir;
220
+ }
221
+ } catch {
222
+ }
223
+ }
224
+ dir = dirname3(dir);
225
+ }
226
+ return null;
206
227
  }
228
+ function getNodeModulesPath(startDir) {
229
+ return join2(findProjectRoot(startDir), "node_modules");
230
+ }
231
+
232
+ // src/esbuild/watcher.ts
233
+ import { getLogger } from "@logtape/logtape";
234
+ var logger = getLogger(["watcher"]);
207
235
  var Watcher = class {
208
236
  #options;
209
237
  #ctx;
@@ -224,11 +252,11 @@ var Watcher = class {
224
252
  async start() {
225
253
  const entryPath = resolve(this.#projectRoot, this.#options.entrypoint);
226
254
  const outputDir = resolve(this.#projectRoot, this.#options.outDir);
227
- await mkdir(join2(outputDir, "server"), { recursive: true });
228
- await mkdir(join2(outputDir, "static"), { recursive: true });
255
+ await mkdir(join3(outputDir, "server"), { recursive: true });
256
+ await mkdir(join3(outputDir, "static"), { recursive: true });
229
257
  const jsxOptions = await loadJSXConfig(this.#projectRoot);
230
- const initialBuildPromise = new Promise((resolve3) => {
231
- this.#initialBuildResolve = resolve3;
258
+ const initialBuildPromise = new Promise((resolve4) => {
259
+ this.#initialBuildResolve = resolve4;
232
260
  });
233
261
  const buildOptions = {
234
262
  entryPoints: [entryPath],
@@ -254,14 +282,32 @@ var Watcher = class {
254
282
  // Plugin to detect build completion (works with watch mode)
255
283
  {
256
284
  name: "build-notify",
257
- setup: (build2) => {
258
- build2.onStart(() => {
285
+ setup: (build3) => {
286
+ build3.onStart(() => {
259
287
  logger.info("Building", {
260
288
  entrypoint: this.#options.entrypoint
261
289
  });
262
290
  });
263
- build2.onEnd(async (result) => {
264
- const success = result.errors.length === 0;
291
+ build3.onEnd(async (result) => {
292
+ let success = result.errors.length === 0;
293
+ const dynamicImportWarnings = (result.warnings || []).filter(
294
+ (w) => w.text.includes("cannot be bundled") || w.text.includes("import() call") || w.text.includes("dynamic import")
295
+ );
296
+ if (dynamicImportWarnings.length > 0) {
297
+ success = false;
298
+ for (const warning of dynamicImportWarnings) {
299
+ const loc = warning.location;
300
+ const file = loc?.file || "unknown";
301
+ const line = loc?.line || "?";
302
+ logger.error(
303
+ "Non-analyzable dynamic import at {file}:{line}: {text}",
304
+ { file, line, text: warning.text }
305
+ );
306
+ }
307
+ logger.error(
308
+ "Dynamic imports must use literal strings, not variables. For config-driven providers, ensure they are registered in shovel.json."
309
+ );
310
+ }
265
311
  let outputPath = "";
266
312
  if (result.metafile) {
267
313
  const outputs = Object.keys(result.metafile.outputs);
@@ -329,22 +375,18 @@ await configure({
329
375
  var logger2 = getLogger2(["cli"]);
330
376
  async function developCommand(entrypoint, options) {
331
377
  try {
332
- const config = loadConfig(process.cwd());
378
+ const projectRoot = findProjectRoot();
379
+ const config = loadConfig(projectRoot);
333
380
  const platformName = Platform.resolvePlatform({ ...options, config });
334
381
  const workerCount = getWorkerCount(options, config);
335
382
  if (options.verbose) {
336
383
  Platform.displayPlatformInfo(platformName);
337
384
  logger2.info("Worker configuration", { workerCount });
338
385
  }
339
- const platformConfig = {
340
- hotReload: true,
386
+ const platformInstance = await Platform.createPlatform(platformName, {
341
387
  port: parseInt(options.port) || DEFAULTS.SERVER.PORT,
342
388
  host: options.host || DEFAULTS.SERVER.HOST
343
- };
344
- const platformInstance = await Platform.createPlatform(
345
- platformName,
346
- platformConfig
347
- );
389
+ });
348
390
  logger2.info("Starting development server", {});
349
391
  logger2.info("Workers", { workerCount });
350
392
  let serviceWorker;
@@ -352,6 +394,7 @@ async function developCommand(entrypoint, options) {
352
394
  const watcher = new Watcher({
353
395
  entrypoint,
354
396
  outDir,
397
+ config,
355
398
  onBuild: async (success, builtEntrypoint2) => {
356
399
  if (success && serviceWorker) {
357
400
  logger2.info("Reloading Workers", { entrypoint: builtEntrypoint2 });
@@ -413,27 +456,28 @@ function getWorkerCount(options, config) {
413
456
  // src/commands/activate.ts
414
457
  import { getLogger as getLogger3 } from "@logtape/logtape";
415
458
  import * as Platform2 from "@b9g/platform";
459
+ import * as ESBuild2 from "esbuild";
460
+ import { resolve as resolve2, join as join4 } from "path";
461
+ import { mkdir as mkdir2 } from "fs/promises";
462
+ import { assetsPlugin as assetsPlugin2 } from "@b9g/assets/plugin";
416
463
  var logger3 = getLogger3(["cli"]);
417
464
  async function activateCommand(entrypoint, options) {
418
465
  try {
419
466
  const platformName = Platform2.resolvePlatform(options);
420
467
  const workerCount = getWorkerCount2(options);
421
- if (options.verbose) {
422
- Platform2.displayPlatformInfo(platformName);
423
- logger3.info("Worker configuration", { workerCount });
424
- }
425
- const platformConfig = {
426
- hotReload: false
427
- };
428
- const platformInstance = await Platform2.createPlatform(
429
- platformName,
430
- platformConfig
431
- );
468
+ logger3.debug("Platform: {platform}", { platform: platformName });
469
+ logger3.debug("Worker count: {workerCount}", { workerCount });
470
+ logger3.info("Building ServiceWorker for activation");
471
+ const builtEntrypoint = await buildForActivate(entrypoint);
472
+ const platformInstance = await Platform2.createPlatform(platformName);
432
473
  logger3.info("Activating ServiceWorker", {});
433
- const serviceWorker = await platformInstance.loadServiceWorker(entrypoint, {
434
- hotReload: false,
435
- workerCount
436
- });
474
+ const serviceWorker = await platformInstance.loadServiceWorker(
475
+ builtEntrypoint,
476
+ {
477
+ hotReload: false,
478
+ workerCount
479
+ }
480
+ );
437
481
  logger3.info(
438
482
  "ServiceWorker activated - check dist/ for generated content",
439
483
  {}
@@ -445,6 +489,46 @@ async function activateCommand(entrypoint, options) {
445
489
  process.exit(1);
446
490
  }
447
491
  }
492
+ async function buildForActivate(entrypoint) {
493
+ const entryPath = resolve2(entrypoint);
494
+ const outputDir = resolve2("dist");
495
+ const serverDir = join4(outputDir, "server");
496
+ await mkdir2(serverDir, { recursive: true });
497
+ await mkdir2(join4(outputDir, "static"), { recursive: true });
498
+ const projectRoot = findProjectRoot();
499
+ const jsxOptions = await loadJSXConfig(projectRoot);
500
+ const outfile = join4(serverDir, "server.js");
501
+ const buildConfig = {
502
+ entryPoints: [entryPath],
503
+ bundle: true,
504
+ format: "esm",
505
+ target: "es2022",
506
+ platform: "node",
507
+ outfile,
508
+ absWorkingDir: projectRoot,
509
+ mainFields: ["module", "main"],
510
+ conditions: ["import", "module"],
511
+ nodePaths: [getNodeModulesPath()],
512
+ plugins: [
513
+ importMetaPlugin(),
514
+ assetsPlugin2({
515
+ outDir: outputDir,
516
+ clientBuild: {
517
+ jsx: jsxOptions.jsx,
518
+ jsxFactory: jsxOptions.jsxFactory,
519
+ jsxFragment: jsxOptions.jsxFragment,
520
+ jsxImportSource: jsxOptions.jsxImportSource
521
+ }
522
+ })
523
+ ],
524
+ external: ["node:*"]
525
+ };
526
+ applyJSXOptions(buildConfig, jsxOptions);
527
+ logger3.debug("Building entrypoint: {entryPath}", { entryPath, outfile });
528
+ await ESBuild2.build(buildConfig);
529
+ logger3.debug("Build complete: {outfile}", { outfile });
530
+ return outfile;
531
+ }
448
532
  function getWorkerCount2(options) {
449
533
  if (options.workers) {
450
534
  return parseInt(options.workers);
@@ -467,11 +551,11 @@ async function infoCommand() {
467
551
  }
468
552
 
469
553
  // src/commands/build.ts
470
- import * as ESBuild2 from "esbuild";
471
- import { resolve as resolve2, join as join3, dirname as dirname4 } from "path";
472
- import { mkdir as mkdir2, readFile as readFile3, writeFile } from "fs/promises";
554
+ import * as ESBuild3 from "esbuild";
555
+ import { resolve as resolve3, join as join5, dirname as dirname4 } from "path";
556
+ import { mkdir as mkdir3, readFile as readFile3, writeFile } from "fs/promises";
473
557
  import { fileURLToPath } from "url";
474
- import { assetsPlugin as assetsPlugin2 } from "@b9g/assets/plugin";
558
+ import { assetsPlugin as assetsPlugin3 } from "@b9g/assets/plugin";
475
559
  import { configure as configure2, getConsoleSink as getConsoleSink2, getLogger as getLogger5 } from "@logtape/logtape";
476
560
  import { AsyncContext as AsyncContext2 } from "@b9g/async-context";
477
561
  import * as Platform3 from "@b9g/platform";
@@ -489,6 +573,26 @@ await configure2({
489
573
  ]
490
574
  });
491
575
  var logger5 = getLogger5(["cli"]);
576
+ function validateDynamicImports(result, context2) {
577
+ const dynamicImportWarnings = (result.warnings || []).filter(
578
+ (w) => w.text.includes("cannot be bundled") || w.text.includes("import() call") || w.text.includes("dynamic import")
579
+ );
580
+ if (dynamicImportWarnings.length > 0) {
581
+ const locations = dynamicImportWarnings.map((w) => {
582
+ const loc = w.location;
583
+ const file = loc?.file || "unknown";
584
+ const line = loc?.line || "?";
585
+ return ` ${file}:${line} - ${w.text}`;
586
+ }).join("\n");
587
+ throw new Error(
588
+ `Build failed (${context2}): Non-analyzable dynamic imports found:
589
+ ${locations}
590
+
591
+ Dynamic imports must use literal strings, not variables.
592
+ For config-driven providers, ensure they are registered in shovel.json.`
593
+ );
594
+ }
595
+ }
492
596
  var BUILD_DEFAULTS = {
493
597
  format: "esm",
494
598
  target: "es2022",
@@ -516,7 +620,8 @@ async function buildForProduction({
516
620
  workerCount
517
621
  });
518
622
  const buildConfig = await createBuildConfig(buildContext);
519
- const result = await ESBuild2.build(buildConfig);
623
+ const result = await ESBuild3.build(buildConfig);
624
+ validateDynamicImports(result, "main bundle");
520
625
  if (verbose && result.metafile) {
521
626
  await logBundleAnalysis(result.metafile);
522
627
  }
@@ -527,7 +632,7 @@ async function buildForProduction({
527
632
  if (verbose) {
528
633
  logger5.info("Built app to", { outputDir: buildContext.outputDir });
529
634
  logger5.info("Server files", { dir: buildContext.serverDir });
530
- logger5.info("Static files", { dir: join3(buildContext.outputDir, "static") });
635
+ logger5.info("Static files", { dir: join5(buildContext.outputDir, "static") });
531
636
  }
532
637
  }
533
638
  async function initializeBuild({
@@ -548,8 +653,8 @@ async function initializeBuild({
548
653
  logger5.info("Output:", { dir: outDir });
549
654
  logger5.info("Target platform:", { platform });
550
655
  }
551
- const entryPath = resolve2(entrypoint);
552
- const outputDir = resolve2(outDir);
656
+ const entryPath = resolve3(entrypoint);
657
+ const outputDir = resolve3(outDir);
553
658
  try {
554
659
  const stats = await readFile3(entryPath, "utf8");
555
660
  if (stats.length === 0) {
@@ -564,17 +669,17 @@ async function initializeBuild({
564
669
  `Invalid platform: ${platform}. Valid platforms: ${validPlatforms.join(", ")}`
565
670
  );
566
671
  }
567
- const workspaceRoot = await findWorkspaceRoot();
672
+ const projectRoot = findProjectRoot();
568
673
  if (verbose) {
569
674
  logger5.info("Entry:", { entryPath });
570
675
  logger5.info("Output:", { outputDir });
571
676
  logger5.info("Target platform:", { platform });
572
- logger5.info("Workspace root:", { workspaceRoot });
677
+ logger5.info("Project root:", { projectRoot });
573
678
  }
574
679
  try {
575
- await mkdir2(outputDir, { recursive: true });
576
- await mkdir2(join3(outputDir, BUILD_STRUCTURE.serverDir), { recursive: true });
577
- await mkdir2(join3(outputDir, BUILD_STRUCTURE.staticDir), { recursive: true });
680
+ await mkdir3(outputDir, { recursive: true });
681
+ await mkdir3(join5(outputDir, BUILD_STRUCTURE.serverDir), { recursive: true });
682
+ await mkdir3(join5(outputDir, BUILD_STRUCTURE.staticDir), { recursive: true });
578
683
  } catch (error) {
579
684
  throw new Error(
580
685
  `Failed to create output directory structure: ${error.message}`
@@ -583,59 +688,23 @@ async function initializeBuild({
583
688
  return {
584
689
  entryPath,
585
690
  outputDir,
586
- serverDir: join3(outputDir, BUILD_STRUCTURE.serverDir),
587
- workspaceRoot,
691
+ serverDir: join5(outputDir, BUILD_STRUCTURE.serverDir),
692
+ projectRoot,
588
693
  platform,
589
694
  verbose,
590
695
  workerCount
591
696
  };
592
697
  }
593
- async function findWorkspaceRoot() {
594
- let workspaceRoot = process.cwd();
595
- while (workspaceRoot !== dirname4(workspaceRoot)) {
596
- try {
597
- const packageJSON = JSON.parse(
598
- await readFile3(resolve2(workspaceRoot, "package.json"), "utf8")
599
- );
600
- if (packageJSON.workspaces) {
601
- return workspaceRoot;
602
- }
603
- } catch {
604
- }
605
- workspaceRoot = dirname4(workspaceRoot);
606
- }
607
- return workspaceRoot;
608
- }
609
- async function findShovelPackageRoot() {
610
- let currentDir = dirname4(fileURLToPath(import.meta.url));
611
- let packageRoot = currentDir;
612
- while (packageRoot !== dirname4(packageRoot)) {
613
- try {
614
- const packageJSONPath = join3(packageRoot, "package.json");
615
- const content = await readFile3(packageJSONPath, "utf8");
616
- const pkg2 = JSON.parse(content);
617
- if (pkg2.name === "@b9g/shovel" || pkg2.name === "shovel") {
618
- if (packageRoot.endsWith("/dist") || packageRoot.endsWith("\\dist")) {
619
- return dirname4(packageRoot);
620
- }
621
- return packageRoot;
622
- }
623
- } catch {
624
- }
625
- packageRoot = dirname4(packageRoot);
626
- }
627
- return currentDir;
628
- }
629
698
  async function createBuildConfig({
630
699
  entryPath,
631
700
  outputDir,
632
701
  serverDir,
633
- workspaceRoot,
702
+ projectRoot,
634
703
  platform,
635
704
  workerCount
636
705
  }) {
637
706
  const isCloudflare = platform === "cloudflare" || platform === "cloudflare-workers";
638
- const jsxOptions = await loadJSXConfig(workspaceRoot || dirname4(entryPath));
707
+ const jsxOptions = await loadJSXConfig(projectRoot || dirname4(entryPath));
639
708
  try {
640
709
  const virtualEntry = await createVirtualEntry(
641
710
  entryPath,
@@ -643,7 +712,6 @@ async function createBuildConfig({
643
712
  workerCount
644
713
  );
645
714
  const external = ["node:*"];
646
- const shovelRoot = await findShovelPackageRoot();
647
715
  if (!isCloudflare) {
648
716
  const userBuildConfig = {
649
717
  entryPoints: [entryPath],
@@ -651,18 +719,15 @@ async function createBuildConfig({
651
719
  format: BUILD_DEFAULTS.format,
652
720
  target: BUILD_DEFAULTS.target,
653
721
  platform: "node",
654
- outfile: join3(serverDir, "server.js"),
655
- absWorkingDir: workspaceRoot || dirname4(entryPath),
722
+ outfile: join5(serverDir, "server.js"),
723
+ absWorkingDir: projectRoot,
656
724
  mainFields: ["module", "main"],
657
725
  conditions: ["import", "module"],
658
- // Allow user code to import @b9g packages from shovel's packages directory
659
- nodePaths: [
660
- join3(shovelRoot, "packages"),
661
- join3(shovelRoot, "node_modules")
662
- ],
726
+ // Resolve packages from the user's project node_modules
727
+ nodePaths: [getNodeModulesPath()],
663
728
  plugins: [
664
729
  importMetaPlugin(),
665
- assetsPlugin2({
730
+ assetsPlugin3({
666
731
  outDir: outputDir,
667
732
  clientBuild: {
668
733
  jsx: jsxOptions.jsx,
@@ -682,43 +747,28 @@ async function createBuildConfig({
682
747
  external
683
748
  };
684
749
  applyJSXOptions(userBuildConfig, jsxOptions);
685
- await ESBuild2.build(userBuildConfig);
686
- const runtimeSourcePath = join3(
687
- shovelRoot,
688
- "packages/platform/dist/src/runtime.js"
750
+ const userBuildResult = await ESBuild3.build(userBuildConfig);
751
+ validateDynamicImports(userBuildResult, "user code");
752
+ const runtimeSourcePath = join5(
753
+ getNodeModulesPath(),
754
+ "@b9g/platform/dist/src/runtime.js"
689
755
  );
690
- const workerDestPath = join3(serverDir, "worker.js");
691
- try {
692
- await ESBuild2.build({
693
- entryPoints: [runtimeSourcePath],
694
- bundle: true,
695
- format: "esm",
696
- target: "es2022",
697
- platform: "node",
698
- outfile: workerDestPath,
699
- external: ["node:*"]
700
- });
701
- } catch (error) {
702
- const installedRuntimePath = join3(
703
- shovelRoot,
704
- "node_modules/@b9g/platform/dist/src/runtime.js"
705
- );
706
- await ESBuild2.build({
707
- entryPoints: [installedRuntimePath],
708
- bundle: true,
709
- format: "esm",
710
- target: "es2022",
711
- platform: "node",
712
- outfile: workerDestPath,
713
- external: ["node:*"]
714
- });
715
- }
756
+ const workerDestPath = join5(serverDir, "worker.js");
757
+ await ESBuild3.build({
758
+ entryPoints: [runtimeSourcePath],
759
+ bundle: true,
760
+ format: "esm",
761
+ target: "es2022",
762
+ platform: "node",
763
+ outfile: workerDestPath,
764
+ external: ["node:*"]
765
+ });
716
766
  }
717
767
  const buildConfig = {
718
768
  stdin: {
719
769
  contents: virtualEntry,
720
- resolveDir: shovelRoot,
721
- // Use Shovel root to resolve @b9g packages
770
+ resolveDir: projectRoot,
771
+ // Resolve packages from user's project
722
772
  sourcefile: "virtual-entry.js"
723
773
  },
724
774
  bundle: true,
@@ -727,16 +777,18 @@ async function createBuildConfig({
727
777
  platform: isCloudflare ? "browser" : "node",
728
778
  // Cloudflare: single-file architecture (server.js contains everything)
729
779
  // Node/Bun: multi-file architecture (index.js is entry, server.js is user code)
730
- outfile: join3(
780
+ outfile: join5(
731
781
  serverDir,
732
782
  isCloudflare ? "server.js" : BUILD_DEFAULTS.outputFile
733
783
  ),
734
- absWorkingDir: workspaceRoot || dirname4(entryPath),
784
+ absWorkingDir: projectRoot,
735
785
  mainFields: ["module", "main"],
736
786
  conditions: ["import", "module"],
787
+ // Resolve packages from the user's project node_modules
788
+ nodePaths: [getNodeModulesPath()],
737
789
  plugins: isCloudflare ? [
738
790
  importMetaPlugin(),
739
- assetsPlugin2({
791
+ assetsPlugin3({
740
792
  outDir: outputDir,
741
793
  clientBuild: {
742
794
  jsx: jsxOptions.jsx,
@@ -775,32 +827,37 @@ async function configureCloudflareTarget(buildConfig) {
775
827
  async function createVirtualEntry(userEntryPath, platform, workerCount = 1) {
776
828
  const isCloudflare = platform === "cloudflare" || platform === "cloudflare-workers";
777
829
  if (isCloudflare) {
778
- return `
779
- // Import user's ServiceWorker code
830
+ return `// Import user's ServiceWorker code
780
831
  import "${userEntryPath}";
781
832
  `;
782
833
  }
783
- return await createWorkerEntry(userEntryPath, workerCount, platform);
834
+ return createWorkerEntry(userEntryPath, workerCount, platform);
784
835
  }
785
836
  async function createWorkerEntry(userEntryPath, workerCount, platform) {
786
837
  let currentDir = dirname4(fileURLToPath(import.meta.url));
787
838
  let packageRoot = currentDir;
788
839
  while (packageRoot !== dirname4(packageRoot)) {
789
840
  try {
790
- const packageJSONPath = join3(packageRoot, "package.json");
841
+ const packageJSONPath = join5(packageRoot, "package.json");
791
842
  await readFile3(packageJSONPath, "utf8");
792
843
  break;
793
- } catch {
844
+ } catch (err) {
845
+ if (err.code !== "ENOENT") {
846
+ throw err;
847
+ }
794
848
  packageRoot = dirname4(packageRoot);
795
849
  }
796
850
  }
797
- let templatePath = join3(packageRoot, "src/worker-entry.ts");
851
+ let templatePath = join5(packageRoot, "src/worker-entry.ts");
798
852
  try {
799
853
  await readFile3(templatePath, "utf8");
800
- } catch {
801
- templatePath = join3(packageRoot, "src/worker-entry.js");
854
+ } catch (err) {
855
+ if (err.code !== "ENOENT") {
856
+ throw err;
857
+ }
858
+ templatePath = join5(packageRoot, "src/worker-entry.js");
802
859
  }
803
- const transpileResult = await ESBuild2.build({
860
+ const transpileResult = await ESBuild3.build({
804
861
  entryPoints: [templatePath],
805
862
  bundle: false,
806
863
  // Just transpile - bundling happens in final build
@@ -818,7 +875,7 @@ async function createWorkerEntry(userEntryPath, workerCount, platform) {
818
875
  async function logBundleAnalysis(metafile) {
819
876
  try {
820
877
  logger5.info("Bundle analysis:", {});
821
- const analysis = await ESBuild2.analyzeMetafile(metafile);
878
+ const analysis = await ESBuild3.analyzeMetafile(metafile);
822
879
  logger5.info(analysis, {});
823
880
  } catch (error) {
824
881
  logger5.warn("Failed to analyze bundle: {error}", { error });
@@ -826,7 +883,7 @@ async function logBundleAnalysis(metafile) {
826
883
  }
827
884
  async function generatePackageJSON({ serverDir, platform, verbose, entryPath }) {
828
885
  const entryDir = dirname4(entryPath);
829
- const sourcePackageJsonPath = resolve2(entryDir, "package.json");
886
+ const sourcePackageJsonPath = resolve3(entryDir, "package.json");
830
887
  try {
831
888
  const packageJSONContent = await readFile3(sourcePackageJsonPath, "utf8");
832
889
  try {
@@ -835,7 +892,7 @@ async function generatePackageJSON({ serverDir, platform, verbose, entryPath })
835
892
  throw new Error(`Invalid package.json format: ${parseError.message}`);
836
893
  }
837
894
  await writeFile(
838
- join3(serverDir, "package.json"),
895
+ join5(serverDir, "package.json"),
839
896
  packageJSONContent,
840
897
  "utf8"
841
898
  );
@@ -849,7 +906,7 @@ async function generatePackageJSON({ serverDir, platform, verbose, entryPath })
849
906
  try {
850
907
  const generatedPackageJson = await generateExecutablePackageJSON(platform);
851
908
  await writeFile(
852
- join3(serverDir, "package.json"),
909
+ join5(serverDir, "package.json"),
853
910
  JSON.stringify(generatedPackageJson, null, 2),
854
911
  "utf8"
855
912
  );
@@ -861,7 +918,9 @@ async function generatePackageJSON({ serverDir, platform, verbose, entryPath })
861
918
  }
862
919
  } catch (generateError) {
863
920
  if (verbose) {
864
- logger5.warn("Could not generate package.json: {error}", { error: generateError });
921
+ logger5.warn("Could not generate package.json: {error}", {
922
+ error: generateError
923
+ });
865
924
  }
866
925
  }
867
926
  }
@@ -874,8 +933,7 @@ async function generateExecutablePackageJSON(platform) {
874
933
  private: true,
875
934
  dependencies: {}
876
935
  };
877
- const workspaceRoot = await findWorkspaceRoot();
878
- const isWorkspaceEnvironment = workspaceRoot !== null;
936
+ const isWorkspaceEnvironment = findWorkspaceRoot() !== null;
879
937
  if (isWorkspaceEnvironment) {
880
938
  packageJSON.dependencies = {};
881
939
  } else {
@@ -898,7 +956,8 @@ async function generateExecutablePackageJSON(platform) {
898
956
  return packageJSON;
899
957
  }
900
958
  async function buildCommand(entrypoint, options) {
901
- const config = loadConfig2(process.cwd());
959
+ const projectRoot = findProjectRoot();
960
+ const config = loadConfig2(projectRoot);
902
961
  const platform = Platform3.resolvePlatform({ ...options, config });
903
962
  await buildForProduction({
904
963
  entrypoint,
package/bin/create.js CHANGED
@@ -8,7 +8,7 @@ import picocolors from "picocolors";
8
8
  import { mkdir, writeFile } from "fs/promises";
9
9
  import { join, resolve } from "path";
10
10
  import { existsSync } from "fs";
11
- var { cyan, green, yellow: _yellow, red, dim, bold } = picocolors;
11
+ var { cyan, green, red, dim, bold } = picocolors;
12
12
  async function main() {
13
13
  console.info("");
14
14
  intro(cyan("\u{1F680} Create Shovel App"));
@@ -200,28 +200,49 @@ function getRequestInfo(request: Request) {
200
200
 
201
201
  async function parseBody(request: Request) {
202
202
  const contentType = request.headers.get('content-type') || '';
203
-
203
+
204
204
  if (contentType.includes('application/json')) {
205
205
  try {
206
206
  return await request.json();
207
- } catch {
207
+ } catch (err) {
208
+ // Only ignore JSON parse errors, rethrow others
209
+ if (
210
+ !(err instanceof SyntaxError) ||
211
+ !/^(Unexpected token|Expected|JSON)/i.test(String(err.message))
212
+ ) {
213
+ throw err;
214
+ }
208
215
  return null;
209
216
  }
210
217
  }
211
-
218
+
212
219
  if (contentType.includes('application/x-www-form-urlencoded')) {
213
220
  try {
214
221
  const formData = await request.formData();
215
222
  return Object.fromEntries(formData.entries());
216
- } catch {
223
+ } catch (err) {
224
+ // Only ignore form data parse errors, rethrow others
225
+ if (
226
+ !(err instanceof TypeError) ||
227
+ !String(err.message).includes('FormData')
228
+ ) {
229
+ throw err;
230
+ }
217
231
  return null;
218
232
  }
219
233
  }
220
-
234
+
221
235
  try {
222
236
  const text = await request.text();
223
237
  return text || null;
224
- } catch {
238
+ } catch (err) {
239
+ // Only ignore body already consumed errors, rethrow others
240
+ if (
241
+ !(err instanceof TypeError) ||
242
+ !String(err.message).includes('body')
243
+ ) {
244
+ throw err;
245
+ }
225
246
  return null;
226
247
  }
227
248
  }
@@ -237,28 +258,49 @@ function getRequestInfo(request) {
237
258
 
238
259
  async function parseBody(request) {
239
260
  const contentType = request.headers.get('content-type') || '';
240
-
261
+
241
262
  if (contentType.includes('application/json')) {
242
263
  try {
243
264
  return await request.json();
244
- } catch {
265
+ } catch (err) {
266
+ // Only ignore JSON parse errors, rethrow others
267
+ if (
268
+ !(err instanceof SyntaxError) ||
269
+ !/^(Unexpected token|Expected|JSON)/i.test(String(err.message))
270
+ ) {
271
+ throw err;
272
+ }
245
273
  return null;
246
274
  }
247
275
  }
248
-
276
+
249
277
  if (contentType.includes('application/x-www-form-urlencoded')) {
250
278
  try {
251
279
  const formData = await request.formData();
252
280
  return Object.fromEntries(formData.entries());
253
- } catch {
281
+ } catch (err) {
282
+ // Only ignore form data parse errors, rethrow others
283
+ if (
284
+ !(err instanceof TypeError) ||
285
+ !String(err.message).includes('FormData')
286
+ ) {
287
+ throw err;
288
+ }
254
289
  return null;
255
290
  }
256
291
  }
257
-
292
+
258
293
  try {
259
294
  const text = await request.text();
260
295
  return text || null;
261
- } catch {
296
+ } catch (err) {
297
+ // Only ignore body already consumed errors, rethrow others
298
+ if (
299
+ !(err instanceof TypeError) ||
300
+ !String(err.message).includes('body')
301
+ ) {
302
+ throw err;
303
+ }
262
304
  return null;
263
305
  }
264
306
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/shovel",
3
- "version": "0.2.0-beta.8",
3
+ "version": "0.2.0-beta.9",
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
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "create": "bin/create.js"
11
11
  },
12
12
  "dependencies": {
13
- "@b9g/async-context": "^0.1.2",
13
+ "@b9g/async-context": "^0.1.4",
14
14
  "@clack/prompts": "^0.7.0",
15
15
  "@logtape/logtape": "^1.2.0",
16
16
  "commander": "^13.1.0",
@@ -21,30 +21,30 @@
21
21
  "source-map": "^0.7.4"
22
22
  },
23
23
  "devDependencies": {
24
- "@b9g/assets": "^0.1.14",
24
+ "@b9g/assets": "^0.1.15",
25
25
  "@b9g/cache": "^0.1.5",
26
26
  "@b9g/crank": "^0.7.2",
27
27
  "@b9g/filesystem": "^0.1.7",
28
- "@b9g/http-errors": "^0.1.6",
28
+ "@b9g/http-errors": "^0.1.5",
29
29
  "@b9g/libuild": "^0.1.17",
30
30
  "@b9g/platform": "^0.1.11",
31
31
  "@b9g/platform-bun": "^0.1.9",
32
- "@b9g/platform-cloudflare": "^0.1.8",
32
+ "@b9g/platform-cloudflare": "^0.1.9",
33
33
  "@b9g/platform-node": "^0.1.11",
34
- "@b9g/router": "^0.1.9",
34
+ "@b9g/router": "^0.1.10",
35
35
  "@types/bun": "^1.2.2",
36
36
  "mitata": "^1.0.34",
37
37
  "typescript": "^5.7.3"
38
38
  },
39
39
  "peerDependencies": {
40
- "@b9g/node-webworker": "^0.1.4",
40
+ "@b9g/node-webworker": "^0.1.3",
41
41
  "@b9g/platform": "^0.1.11",
42
42
  "@b9g/platform-node": "^0.1.11",
43
- "@b9g/platform-cloudflare": "^0.1.8",
43
+ "@b9g/platform-cloudflare": "^0.1.9",
44
44
  "@b9g/platform-bun": "^0.1.9",
45
45
  "@b9g/cache": "^0.1.5",
46
46
  "@b9g/filesystem": "^0.1.7",
47
- "@b9g/http-errors": "^0.1.6"
47
+ "@b9g/http-errors": "^0.1.5"
48
48
  },
49
49
  "peerDependenciesMeta": {
50
50
  "@b9g/platform": {