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

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/README.md +1 -1
  2. package/bin/cli.js +205 -65
  3. package/package.json +19 -19
package/README.md CHANGED
@@ -29,7 +29,7 @@ Shovel implements web platform APIs that server-side JavaScript is missing:
29
29
  |-----|----------|--------------|
30
30
  | `fetch` event | [Service Workers](https://w3c.github.io/ServiceWorker/) | Request handling |
31
31
  | `self.caches` | [Cache API](https://w3c.github.io/ServiceWorker/#cache-interface) | Response caching |
32
- | `self.buckets` | [FileSystemDirectoryHandle](https://fs.spec.whatwg.org/#api-filesystemdirectoryhandle) | Storage (local, S3, R2) |
32
+ | `self.buckets` | [FileSystem API](https://fs.spec.whatwg.org/) | Storage (local, S3, R2) |
33
33
  | `self.cookieStore` | [Cookie Store API](https://wicg.github.io/cookie-store/) | Cookie management |
34
34
  | `URLPattern` | [URLPattern](https://urlpattern.spec.whatwg.org/) | Route matching (100% WPT) |
35
35
  | `AsyncContext.Variable` | [TC39 Stage 2](https://github.com/tc39/proposal-async-context) | Request-scoped state |
package/bin/cli.js CHANGED
@@ -20,11 +20,12 @@ var DEFAULTS = {
20
20
  import { configure, getConsoleSink, getLogger as getLogger2 } from "@logtape/logtape";
21
21
  import { AsyncContext } from "@b9g/async-context";
22
22
  import * as Platform from "@b9g/platform";
23
+ import { loadConfig } from "@b9g/platform/config";
23
24
 
24
25
  // src/esbuild/watcher.ts
25
26
  import * as ESBuild from "esbuild";
26
- import { existsSync } from "fs";
27
- import { resolve, join, dirname as dirname2 } from "path";
27
+ import { existsSync as existsSync2 } from "fs";
28
+ import { resolve, join as join2, dirname as dirname3 } from "path";
28
29
  import { mkdir } from "fs/promises";
29
30
  import { assetsPlugin } from "@b9g/assets/plugin";
30
31
 
@@ -77,16 +78,129 @@ function importMetaPlugin() {
77
78
  };
78
79
  }
79
80
 
81
+ // src/esbuild/jsx-config.ts
82
+ import { readFile as readFile2 } from "fs/promises";
83
+ import { join, dirname as dirname2 } from "path";
84
+ import { existsSync } from "fs";
85
+ var CRANK_JSX_DEFAULTS = {
86
+ jsx: "automatic",
87
+ jsxImportSource: "@b9g/crank"
88
+ };
89
+ async function findTsConfig(startDir) {
90
+ let dir = startDir;
91
+ while (dir !== dirname2(dir)) {
92
+ const tsconfigPath = join(dir, "tsconfig.json");
93
+ if (existsSync(tsconfigPath)) {
94
+ return tsconfigPath;
95
+ }
96
+ dir = dirname2(dir);
97
+ }
98
+ return null;
99
+ }
100
+ async function parseTsConfig(tsconfigPath) {
101
+ const content = await readFile2(tsconfigPath, "utf8");
102
+ const stripped = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
103
+ const config = JSON.parse(stripped);
104
+ if (config.extends) {
105
+ const baseDir = dirname2(tsconfigPath);
106
+ let extendsPath = config.extends;
107
+ if (extendsPath.startsWith(".")) {
108
+ extendsPath = join(baseDir, extendsPath);
109
+ } else {
110
+ extendsPath = join(baseDir, "node_modules", extendsPath);
111
+ }
112
+ if (!extendsPath.endsWith(".json")) {
113
+ extendsPath += ".json";
114
+ }
115
+ if (existsSync(extendsPath)) {
116
+ const baseConfig = await parseTsConfig(extendsPath);
117
+ return {
118
+ ...baseConfig,
119
+ ...config,
120
+ compilerOptions: {
121
+ ...baseConfig.compilerOptions,
122
+ ...config.compilerOptions
123
+ }
124
+ };
125
+ }
126
+ }
127
+ return config;
128
+ }
129
+ function mapTsConfigToEsbuild(compilerOptions) {
130
+ const options = {};
131
+ if (compilerOptions.jsx) {
132
+ switch (compilerOptions.jsx) {
133
+ case "react":
134
+ case "react-native":
135
+ options.jsx = "transform";
136
+ break;
137
+ case "react-jsx":
138
+ case "react-jsxdev":
139
+ options.jsx = "automatic";
140
+ break;
141
+ case "preserve":
142
+ options.jsx = "preserve";
143
+ break;
144
+ }
145
+ }
146
+ if (compilerOptions.jsxFactory) {
147
+ options.jsxFactory = compilerOptions.jsxFactory;
148
+ }
149
+ if (compilerOptions.jsxFragmentFactory) {
150
+ options.jsxFragment = compilerOptions.jsxFragmentFactory;
151
+ }
152
+ if (compilerOptions.jsxImportSource) {
153
+ options.jsxImportSource = compilerOptions.jsxImportSource;
154
+ }
155
+ return options;
156
+ }
157
+ async function loadJSXConfig(projectRoot) {
158
+ const tsconfigPath = await findTsConfig(projectRoot);
159
+ if (tsconfigPath) {
160
+ try {
161
+ const config = await parseTsConfig(tsconfigPath);
162
+ const compilerOptions = config.compilerOptions || {};
163
+ const hasJsxConfig = compilerOptions.jsx || compilerOptions.jsxFactory || compilerOptions.jsxFragmentFactory || compilerOptions.jsxImportSource;
164
+ if (hasJsxConfig) {
165
+ const tsOptions = mapTsConfigToEsbuild(compilerOptions);
166
+ return {
167
+ ...CRANK_JSX_DEFAULTS,
168
+ ...tsOptions
169
+ };
170
+ }
171
+ } catch {
172
+ }
173
+ }
174
+ return { ...CRANK_JSX_DEFAULTS };
175
+ }
176
+ function applyJSXOptions(buildOptions, jsxOptions) {
177
+ if (jsxOptions.jsx) {
178
+ buildOptions.jsx = jsxOptions.jsx;
179
+ }
180
+ if (jsxOptions.jsxFactory) {
181
+ buildOptions.jsxFactory = jsxOptions.jsxFactory;
182
+ }
183
+ if (jsxOptions.jsxFragment) {
184
+ buildOptions.jsxFragment = jsxOptions.jsxFragment;
185
+ }
186
+ if (jsxOptions.jsxImportSource) {
187
+ buildOptions.jsxImportSource = jsxOptions.jsxImportSource;
188
+ }
189
+ if (jsxOptions.jsxSideEffects !== void 0) {
190
+ buildOptions.jsxSideEffects = jsxOptions.jsxSideEffects;
191
+ }
192
+ }
193
+
80
194
  // src/esbuild/watcher.ts
81
195
  import { getLogger } from "@logtape/logtape";
82
196
  var logger = getLogger(["watcher"]);
83
197
  function findProjectRoot() {
84
198
  let dir = process.cwd();
85
- while (dir !== dirname2(dir)) {
86
- if (existsSync(join(dir, "package.json"))) {
199
+ while (dir !== dirname3(dir)) {
200
+ if (existsSync2(join2(dir, "package.json"))) {
87
201
  return dir;
88
202
  }
89
- dir = dirname2(dir);
203
+ dir = dirname3(dir);
90
204
  }
91
205
  return process.cwd();
92
206
  }
@@ -110,12 +224,13 @@ var Watcher = class {
110
224
  async start() {
111
225
  const entryPath = resolve(this.#projectRoot, this.#options.entrypoint);
112
226
  const outputDir = resolve(this.#projectRoot, this.#options.outDir);
113
- await mkdir(join(outputDir, "server"), { recursive: true });
114
- await mkdir(join(outputDir, "static"), { recursive: true });
227
+ await mkdir(join2(outputDir, "server"), { recursive: true });
228
+ await mkdir(join2(outputDir, "static"), { recursive: true });
229
+ const jsxOptions = await loadJSXConfig(this.#projectRoot);
115
230
  const initialBuildPromise = new Promise((resolve3) => {
116
231
  this.#initialBuildResolve = resolve3;
117
232
  });
118
- this.#ctx = await ESBuild.context({
233
+ const buildOptions = {
119
234
  entryPoints: [entryPath],
120
235
  bundle: true,
121
236
  format: "esm",
@@ -128,7 +243,13 @@ var Watcher = class {
128
243
  plugins: [
129
244
  importMetaPlugin(),
130
245
  assetsPlugin({
131
- outDir: outputDir
246
+ outDir: outputDir,
247
+ clientBuild: {
248
+ jsx: jsxOptions.jsx,
249
+ jsxFactory: jsxOptions.jsxFactory,
250
+ jsxFragment: jsxOptions.jsxFragment,
251
+ jsxImportSource: jsxOptions.jsxImportSource
252
+ }
132
253
  }),
133
254
  // Plugin to detect build completion (works with watch mode)
134
255
  {
@@ -152,7 +273,7 @@ var Watcher = class {
152
273
  if (success) {
153
274
  logger.info("Build complete", { entrypoint: outputPath });
154
275
  } else {
155
- logger.error("Build errors", { errors: result.errors });
276
+ logger.error("Build errors: {errors}", { errors: result.errors });
156
277
  }
157
278
  this.#currentEntrypoint = outputPath;
158
279
  if (!this.#initialBuildComplete) {
@@ -168,7 +289,9 @@ var Watcher = class {
168
289
  sourcemap: "inline",
169
290
  minify: false,
170
291
  treeShaking: true
171
- });
292
+ };
293
+ applyJSXOptions(buildOptions, jsxOptions);
294
+ this.#ctx = await ESBuild.context(buildOptions);
172
295
  logger.info("Starting esbuild watch mode");
173
296
  await this.#ctx.watch();
174
297
  return initialBuildPromise;
@@ -206,8 +329,9 @@ await configure({
206
329
  var logger2 = getLogger2(["cli"]);
207
330
  async function developCommand(entrypoint, options) {
208
331
  try {
209
- const platformName = Platform.resolvePlatform(options);
210
- const workerCount = getWorkerCount(options);
332
+ const config = loadConfig(process.cwd());
333
+ const platformName = Platform.resolvePlatform({ ...options, config });
334
+ const workerCount = getWorkerCount(options, config);
211
335
  if (options.verbose) {
212
336
  Platform.displayPlatformInfo(platformName);
213
337
  logger2.info("Worker configuration", { workerCount });
@@ -273,13 +397,16 @@ async function developCommand(entrypoint, options) {
273
397
  process.exit(1);
274
398
  }
275
399
  }
276
- function getWorkerCount(options) {
400
+ function getWorkerCount(options, config) {
277
401
  if (options.workers) {
278
402
  return parseInt(options.workers);
279
403
  }
280
404
  if (process.env.WORKER_COUNT) {
281
405
  return parseInt(process.env.WORKER_COUNT);
282
406
  }
407
+ if (config?.workers) {
408
+ return config.workers;
409
+ }
283
410
  return DEFAULTS.WORKERS;
284
411
  }
285
412
 
@@ -314,10 +441,7 @@ async function activateCommand(entrypoint, options) {
314
441
  await serviceWorker.dispose();
315
442
  await platformInstance.dispose();
316
443
  } catch (error) {
317
- logger3.error("ServiceWorker activation failed", { error: error.message });
318
- if (options.verbose) {
319
- logger3.error("Stack trace", { stack: error.stack });
320
- }
444
+ logger3.error("ServiceWorker activation failed: {error}", { error });
321
445
  process.exit(1);
322
446
  }
323
447
  }
@@ -344,12 +468,14 @@ async function infoCommand() {
344
468
 
345
469
  // src/commands/build.ts
346
470
  import * as ESBuild2 from "esbuild";
347
- import { resolve as resolve2, join as join2, dirname as dirname3 } from "path";
348
- import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
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";
349
473
  import { fileURLToPath } from "url";
350
474
  import { assetsPlugin as assetsPlugin2 } from "@b9g/assets/plugin";
351
475
  import { configure as configure2, getConsoleSink as getConsoleSink2, getLogger as getLogger5 } from "@logtape/logtape";
352
476
  import { AsyncContext as AsyncContext2 } from "@b9g/async-context";
477
+ import * as Platform3 from "@b9g/platform";
478
+ import { loadConfig as loadConfig2 } from "@b9g/platform/config";
353
479
  await configure2({
354
480
  reset: true,
355
481
  contextLocalStorage: new AsyncContext2.Variable(),
@@ -401,7 +527,7 @@ async function buildForProduction({
401
527
  if (verbose) {
402
528
  logger5.info("Built app to", { outputDir: buildContext.outputDir });
403
529
  logger5.info("Server files", { dir: buildContext.serverDir });
404
- logger5.info("Static files", { dir: join2(buildContext.outputDir, "static") });
530
+ logger5.info("Static files", { dir: join3(buildContext.outputDir, "static") });
405
531
  }
406
532
  }
407
533
  async function initializeBuild({
@@ -425,7 +551,7 @@ async function initializeBuild({
425
551
  const entryPath = resolve2(entrypoint);
426
552
  const outputDir = resolve2(outDir);
427
553
  try {
428
- const stats = await readFile2(entryPath, "utf8");
554
+ const stats = await readFile3(entryPath, "utf8");
429
555
  if (stats.length === 0) {
430
556
  logger5.warn("Entry point is empty", { entryPath });
431
557
  }
@@ -447,8 +573,8 @@ async function initializeBuild({
447
573
  }
448
574
  try {
449
575
  await mkdir2(outputDir, { recursive: true });
450
- await mkdir2(join2(outputDir, BUILD_STRUCTURE.serverDir), { recursive: true });
451
- await mkdir2(join2(outputDir, BUILD_STRUCTURE.staticDir), { recursive: true });
576
+ await mkdir2(join3(outputDir, BUILD_STRUCTURE.serverDir), { recursive: true });
577
+ await mkdir2(join3(outputDir, BUILD_STRUCTURE.staticDir), { recursive: true });
452
578
  } catch (error) {
453
579
  throw new Error(
454
580
  `Failed to create output directory structure: ${error.message}`
@@ -457,7 +583,7 @@ async function initializeBuild({
457
583
  return {
458
584
  entryPath,
459
585
  outputDir,
460
- serverDir: join2(outputDir, BUILD_STRUCTURE.serverDir),
586
+ serverDir: join3(outputDir, BUILD_STRUCTURE.serverDir),
461
587
  workspaceRoot,
462
588
  platform,
463
589
  verbose,
@@ -466,37 +592,37 @@ async function initializeBuild({
466
592
  }
467
593
  async function findWorkspaceRoot() {
468
594
  let workspaceRoot = process.cwd();
469
- while (workspaceRoot !== dirname3(workspaceRoot)) {
595
+ while (workspaceRoot !== dirname4(workspaceRoot)) {
470
596
  try {
471
597
  const packageJSON = JSON.parse(
472
- await readFile2(resolve2(workspaceRoot, "package.json"), "utf8")
598
+ await readFile3(resolve2(workspaceRoot, "package.json"), "utf8")
473
599
  );
474
600
  if (packageJSON.workspaces) {
475
601
  return workspaceRoot;
476
602
  }
477
603
  } catch {
478
604
  }
479
- workspaceRoot = dirname3(workspaceRoot);
605
+ workspaceRoot = dirname4(workspaceRoot);
480
606
  }
481
607
  return workspaceRoot;
482
608
  }
483
609
  async function findShovelPackageRoot() {
484
- let currentDir = dirname3(fileURLToPath(import.meta.url));
610
+ let currentDir = dirname4(fileURLToPath(import.meta.url));
485
611
  let packageRoot = currentDir;
486
- while (packageRoot !== dirname3(packageRoot)) {
612
+ while (packageRoot !== dirname4(packageRoot)) {
487
613
  try {
488
- const packageJSONPath = join2(packageRoot, "package.json");
489
- const content = await readFile2(packageJSONPath, "utf8");
614
+ const packageJSONPath = join3(packageRoot, "package.json");
615
+ const content = await readFile3(packageJSONPath, "utf8");
490
616
  const pkg2 = JSON.parse(content);
491
617
  if (pkg2.name === "@b9g/shovel" || pkg2.name === "shovel") {
492
618
  if (packageRoot.endsWith("/dist") || packageRoot.endsWith("\\dist")) {
493
- return dirname3(packageRoot);
619
+ return dirname4(packageRoot);
494
620
  }
495
621
  return packageRoot;
496
622
  }
497
623
  } catch {
498
624
  }
499
- packageRoot = dirname3(packageRoot);
625
+ packageRoot = dirname4(packageRoot);
500
626
  }
501
627
  return currentDir;
502
628
  }
@@ -509,6 +635,7 @@ async function createBuildConfig({
509
635
  workerCount
510
636
  }) {
511
637
  const isCloudflare = platform === "cloudflare" || platform === "cloudflare-workers";
638
+ const jsxOptions = await loadJSXConfig(workspaceRoot || dirname4(entryPath));
512
639
  try {
513
640
  const virtualEntry = await createVirtualEntry(
514
641
  entryPath,
@@ -524,19 +651,25 @@ async function createBuildConfig({
524
651
  format: BUILD_DEFAULTS.format,
525
652
  target: BUILD_DEFAULTS.target,
526
653
  platform: "node",
527
- outfile: join2(serverDir, "server.js"),
528
- absWorkingDir: workspaceRoot || dirname3(entryPath),
654
+ outfile: join3(serverDir, "server.js"),
655
+ absWorkingDir: workspaceRoot || dirname4(entryPath),
529
656
  mainFields: ["module", "main"],
530
657
  conditions: ["import", "module"],
531
658
  // Allow user code to import @b9g packages from shovel's packages directory
532
659
  nodePaths: [
533
- join2(shovelRoot, "packages"),
534
- join2(shovelRoot, "node_modules")
660
+ join3(shovelRoot, "packages"),
661
+ join3(shovelRoot, "node_modules")
535
662
  ],
536
663
  plugins: [
537
664
  importMetaPlugin(),
538
665
  assetsPlugin2({
539
- outDir: outputDir
666
+ outDir: outputDir,
667
+ clientBuild: {
668
+ jsx: jsxOptions.jsx,
669
+ jsxFactory: jsxOptions.jsxFactory,
670
+ jsxFragment: jsxOptions.jsxFragment,
671
+ jsxImportSource: jsxOptions.jsxImportSource
672
+ }
540
673
  })
541
674
  ],
542
675
  metafile: true,
@@ -548,12 +681,13 @@ async function createBuildConfig({
548
681
  define: platform === "node" ? { "import.meta.env": "process.env" } : {},
549
682
  external
550
683
  };
684
+ applyJSXOptions(userBuildConfig, jsxOptions);
551
685
  await ESBuild2.build(userBuildConfig);
552
- const runtimeSourcePath = join2(
686
+ const runtimeSourcePath = join3(
553
687
  shovelRoot,
554
688
  "packages/platform/dist/src/runtime.js"
555
689
  );
556
- const workerDestPath = join2(serverDir, "worker.js");
690
+ const workerDestPath = join3(serverDir, "worker.js");
557
691
  try {
558
692
  await ESBuild2.build({
559
693
  entryPoints: [runtimeSourcePath],
@@ -565,7 +699,7 @@ async function createBuildConfig({
565
699
  external: ["node:*"]
566
700
  });
567
701
  } catch (error) {
568
- const installedRuntimePath = join2(
702
+ const installedRuntimePath = join3(
569
703
  shovelRoot,
570
704
  "node_modules/@b9g/platform/dist/src/runtime.js"
571
705
  );
@@ -593,17 +727,23 @@ async function createBuildConfig({
593
727
  platform: isCloudflare ? "browser" : "node",
594
728
  // Cloudflare: single-file architecture (server.js contains everything)
595
729
  // Node/Bun: multi-file architecture (index.js is entry, server.js is user code)
596
- outfile: join2(
730
+ outfile: join3(
597
731
  serverDir,
598
732
  isCloudflare ? "server.js" : BUILD_DEFAULTS.outputFile
599
733
  ),
600
- absWorkingDir: workspaceRoot || dirname3(entryPath),
734
+ absWorkingDir: workspaceRoot || dirname4(entryPath),
601
735
  mainFields: ["module", "main"],
602
736
  conditions: ["import", "module"],
603
737
  plugins: isCloudflare ? [
604
738
  importMetaPlugin(),
605
739
  assetsPlugin2({
606
- outDir: outputDir
740
+ outDir: outputDir,
741
+ clientBuild: {
742
+ jsx: jsxOptions.jsx,
743
+ jsxFactory: jsxOptions.jsxFactory,
744
+ jsxFragment: jsxOptions.jsxFragment,
745
+ jsxImportSource: jsxOptions.jsxImportSource
746
+ }
607
747
  })
608
748
  ] : [],
609
749
  // Assets already handled in user code build
@@ -617,6 +757,7 @@ async function createBuildConfig({
617
757
  external
618
758
  };
619
759
  if (isCloudflare) {
760
+ applyJSXOptions(buildConfig, jsxOptions);
620
761
  await configureCloudflareTarget(buildConfig);
621
762
  }
622
763
  return buildConfig;
@@ -642,22 +783,22 @@ import "${userEntryPath}";
642
783
  return await createWorkerEntry(userEntryPath, workerCount, platform);
643
784
  }
644
785
  async function createWorkerEntry(userEntryPath, workerCount, platform) {
645
- let currentDir = dirname3(fileURLToPath(import.meta.url));
786
+ let currentDir = dirname4(fileURLToPath(import.meta.url));
646
787
  let packageRoot = currentDir;
647
- while (packageRoot !== dirname3(packageRoot)) {
788
+ while (packageRoot !== dirname4(packageRoot)) {
648
789
  try {
649
- const packageJSONPath = join2(packageRoot, "package.json");
650
- await readFile2(packageJSONPath, "utf8");
790
+ const packageJSONPath = join3(packageRoot, "package.json");
791
+ await readFile3(packageJSONPath, "utf8");
651
792
  break;
652
793
  } catch {
653
- packageRoot = dirname3(packageRoot);
794
+ packageRoot = dirname4(packageRoot);
654
795
  }
655
796
  }
656
- let templatePath = join2(packageRoot, "src/worker-entry.ts");
797
+ let templatePath = join3(packageRoot, "src/worker-entry.ts");
657
798
  try {
658
- await readFile2(templatePath, "utf8");
799
+ await readFile3(templatePath, "utf8");
659
800
  } catch {
660
- templatePath = join2(packageRoot, "src/worker-entry.js");
801
+ templatePath = join3(packageRoot, "src/worker-entry.js");
661
802
  }
662
803
  const transpileResult = await ESBuild2.build({
663
804
  entryPoints: [templatePath],
@@ -680,21 +821,21 @@ async function logBundleAnalysis(metafile) {
680
821
  const analysis = await ESBuild2.analyzeMetafile(metafile);
681
822
  logger5.info(analysis, {});
682
823
  } catch (error) {
683
- logger5.warn("Failed to analyze bundle", { error: error.message });
824
+ logger5.warn("Failed to analyze bundle: {error}", { error });
684
825
  }
685
826
  }
686
827
  async function generatePackageJSON({ serverDir, platform, verbose, entryPath }) {
687
- const entryDir = dirname3(entryPath);
828
+ const entryDir = dirname4(entryPath);
688
829
  const sourcePackageJsonPath = resolve2(entryDir, "package.json");
689
830
  try {
690
- const packageJSONContent = await readFile2(sourcePackageJsonPath, "utf8");
831
+ const packageJSONContent = await readFile3(sourcePackageJsonPath, "utf8");
691
832
  try {
692
833
  JSON.parse(packageJSONContent);
693
834
  } catch (parseError) {
694
835
  throw new Error(`Invalid package.json format: ${parseError.message}`);
695
836
  }
696
837
  await writeFile(
697
- join2(serverDir, "package.json"),
838
+ join3(serverDir, "package.json"),
698
839
  packageJSONContent,
699
840
  "utf8"
700
841
  );
@@ -703,12 +844,12 @@ async function generatePackageJSON({ serverDir, platform, verbose, entryPath })
703
844
  }
704
845
  } catch (error) {
705
846
  if (verbose) {
706
- logger5.warn("Could not copy package.json", { error: error.message });
847
+ logger5.warn("Could not copy package.json: {error}", { error });
707
848
  }
708
849
  try {
709
850
  const generatedPackageJson = await generateExecutablePackageJSON(platform);
710
851
  await writeFile(
711
- join2(serverDir, "package.json"),
852
+ join3(serverDir, "package.json"),
712
853
  JSON.stringify(generatedPackageJson, null, 2),
713
854
  "utf8"
714
855
  );
@@ -720,10 +861,7 @@ async function generatePackageJSON({ serverDir, platform, verbose, entryPath })
720
861
  }
721
862
  } catch (generateError) {
722
863
  if (verbose) {
723
- logger5.warn("Could not generate package.json", {
724
- error: generateError.message
725
- });
726
- logger5.warn("Generation error details", { error: generateError });
864
+ logger5.warn("Could not generate package.json: {error}", { error: generateError });
727
865
  }
728
866
  }
729
867
  }
@@ -760,12 +898,14 @@ async function generateExecutablePackageJSON(platform) {
760
898
  return packageJSON;
761
899
  }
762
900
  async function buildCommand(entrypoint, options) {
901
+ const config = loadConfig2(process.cwd());
902
+ const platform = Platform3.resolvePlatform({ ...options, config });
763
903
  await buildForProduction({
764
904
  entrypoint,
765
905
  outDir: "dist",
766
906
  verbose: options.verbose || false,
767
- platform: options.platform || "node",
768
- workerCount: options.workers ? parseInt(options.workers, 10) : 1
907
+ platform,
908
+ workerCount: options.workers ? parseInt(options.workers, 10) : config.workers
769
909
  });
770
910
  process.exit(0);
771
911
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/shovel",
3
- "version": "0.2.0-beta.7",
3
+ "version": "0.2.0-beta.8",
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.1",
13
+ "@b9g/async-context": "^0.1.2",
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.13",
25
- "@b9g/cache": "^0.1.4",
24
+ "@b9g/assets": "^0.1.14",
25
+ "@b9g/cache": "^0.1.5",
26
26
  "@b9g/crank": "^0.7.2",
27
- "@b9g/filesystem": "^0.1.6",
28
- "@b9g/http-errors": "^0.1.5",
27
+ "@b9g/filesystem": "^0.1.7",
28
+ "@b9g/http-errors": "^0.1.6",
29
29
  "@b9g/libuild": "^0.1.17",
30
- "@b9g/platform": "^0.1.10",
31
- "@b9g/platform-bun": "^0.1.8",
32
- "@b9g/platform-cloudflare": "^0.1.7",
33
- "@b9g/platform-node": "^0.1.10",
34
- "@b9g/router": "^0.1.8",
30
+ "@b9g/platform": "^0.1.11",
31
+ "@b9g/platform-bun": "^0.1.9",
32
+ "@b9g/platform-cloudflare": "^0.1.8",
33
+ "@b9g/platform-node": "^0.1.11",
34
+ "@b9g/router": "^0.1.9",
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.3",
41
- "@b9g/platform": "^0.1.10",
42
- "@b9g/platform-node": "^0.1.10",
43
- "@b9g/platform-cloudflare": "^0.1.7",
44
- "@b9g/platform-bun": "^0.1.8",
45
- "@b9g/cache": "^0.1.4",
46
- "@b9g/filesystem": "^0.1.6",
47
- "@b9g/http-errors": "^0.1.5"
40
+ "@b9g/node-webworker": "^0.1.4",
41
+ "@b9g/platform": "^0.1.11",
42
+ "@b9g/platform-node": "^0.1.11",
43
+ "@b9g/platform-cloudflare": "^0.1.8",
44
+ "@b9g/platform-bun": "^0.1.9",
45
+ "@b9g/cache": "^0.1.5",
46
+ "@b9g/filesystem": "^0.1.7",
47
+ "@b9g/http-errors": "^0.1.6"
48
48
  },
49
49
  "peerDependenciesMeta": {
50
50
  "@b9g/platform": {