@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.
- package/bin/cli.js +215 -156
- package/bin/create.js +55 -13
- 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 {
|
|
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(
|
|
40
|
-
|
|
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/
|
|
195
|
-
import {
|
|
196
|
-
|
|
197
|
-
function findProjectRoot() {
|
|
198
|
-
let dir =
|
|
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
|
|
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(
|
|
228
|
-
await mkdir(
|
|
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((
|
|
231
|
-
this.#initialBuildResolve =
|
|
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: (
|
|
258
|
-
|
|
285
|
+
setup: (build3) => {
|
|
286
|
+
build3.onStart(() => {
|
|
259
287
|
logger.info("Building", {
|
|
260
288
|
entrypoint: this.#options.entrypoint
|
|
261
289
|
});
|
|
262
290
|
});
|
|
263
|
-
|
|
264
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const
|
|
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(
|
|
434
|
-
|
|
435
|
-
|
|
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
|
|
471
|
-
import { resolve as
|
|
472
|
-
import { mkdir as
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
552
|
-
const outputDir =
|
|
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
|
|
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("
|
|
677
|
+
logger5.info("Project root:", { projectRoot });
|
|
573
678
|
}
|
|
574
679
|
try {
|
|
575
|
-
await
|
|
576
|
-
await
|
|
577
|
-
await
|
|
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:
|
|
587
|
-
|
|
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
|
-
|
|
702
|
+
projectRoot,
|
|
634
703
|
platform,
|
|
635
704
|
workerCount
|
|
636
705
|
}) {
|
|
637
706
|
const isCloudflare = platform === "cloudflare" || platform === "cloudflare-workers";
|
|
638
|
-
const jsxOptions = await loadJSXConfig(
|
|
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:
|
|
655
|
-
absWorkingDir:
|
|
722
|
+
outfile: join5(serverDir, "server.js"),
|
|
723
|
+
absWorkingDir: projectRoot,
|
|
656
724
|
mainFields: ["module", "main"],
|
|
657
725
|
conditions: ["import", "module"],
|
|
658
|
-
//
|
|
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
|
-
|
|
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
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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 =
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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:
|
|
721
|
-
//
|
|
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:
|
|
780
|
+
outfile: join5(
|
|
731
781
|
serverDir,
|
|
732
782
|
isCloudflare ? "server.js" : BUILD_DEFAULTS.outputFile
|
|
733
783
|
),
|
|
734
|
-
absWorkingDir:
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
851
|
+
let templatePath = join5(packageRoot, "src/worker-entry.ts");
|
|
798
852
|
try {
|
|
799
853
|
await readFile3(templatePath, "utf8");
|
|
800
|
-
} catch {
|
|
801
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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}", {
|
|
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
|
|
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
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
32
|
+
"@b9g/platform-cloudflare": "^0.1.9",
|
|
33
33
|
"@b9g/platform-node": "^0.1.11",
|
|
34
|
-
"@b9g/router": "^0.1.
|
|
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.
|
|
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.
|
|
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.
|
|
47
|
+
"@b9g/http-errors": "^0.1.5"
|
|
48
48
|
},
|
|
49
49
|
"peerDependenciesMeta": {
|
|
50
50
|
"@b9g/platform": {
|