@b9g/shovel 0.2.0-beta.2 → 0.2.0-beta.20

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 CHANGED
@@ -1,802 +1,78 @@
1
1
  #!/usr/bin/env sh
2
2
  ':' //; exec "$([ "${npm_config_user_agent#bun/}" != "$npm_config_user_agent" ] && command -v bun || command -v node)" "$0" "$@"
3
3
  /// <reference types="./cli.d.ts" />
4
+ import {
5
+ DEFAULTS,
6
+ findProjectRoot,
7
+ loadConfig
8
+ } from "../src/_chunks/chunk-PTLNYIRW.js";
4
9
 
5
10
  // bin/cli.ts
11
+ import { resolve } from "path";
12
+ import { spawnSync } from "child_process";
13
+ import { configureLogging } from "@b9g/platform/runtime";
6
14
  import { Command } from "commander";
7
15
  import pkg from "../package.json" with { type: "json" };
8
-
9
- // src/esbuild/config.ts
10
- var DEFAULTS = {
11
- SERVER: {
12
- PORT: 7777,
13
- HOST: "localhost"
14
- },
15
- WORKERS: 1
16
- // Single worker for development - user can override with --workers flag
17
- };
18
-
19
- // src/commands/develop.ts
20
- import { configure, getConsoleSink, getLogger as getLogger2 } from "@logtape/logtape";
21
- import { AsyncContext } from "@b9g/async-context";
22
- import * as Platform from "@b9g/platform";
23
-
24
- // src/esbuild/watcher.ts
25
- import * as ESBuild from "esbuild";
26
- import { watch } from "fs";
27
- import { resolve, dirname as dirname2, join } from "path";
28
- import { readFileSync } from "fs";
29
- import { mkdir } from "fs/promises";
30
- import { assetsPlugin } from "@b9g/assets/plugin";
31
-
32
- // src/esbuild/import-meta-plugin.ts
33
- import { readFile } from "fs/promises";
34
- import { dirname } from "path";
35
- import { pathToFileURL } from "url";
36
- function importMetaPlugin() {
37
- return {
38
- name: "import-meta-transform",
39
- setup(build3) {
40
- build3.onLoad({ filter: /\.[jt]sx?$/, namespace: "file" }, async (args) => {
41
- if (args.path.includes("node_modules")) {
42
- return null;
43
- }
44
- const contents = await readFile(args.path, "utf8");
45
- if (!contents.includes("import.meta.url") && !contents.includes("import.meta.dirname") && !contents.includes("import.meta.filename")) {
46
- return null;
47
- }
48
- const fileUrl = pathToFileURL(args.path).href;
49
- const fileDirname = dirname(args.path);
50
- const fileFilename = args.path;
51
- let transformed = contents;
52
- transformed = transformed.replace(
53
- /\bimport\.meta\.url\b/g,
54
- JSON.stringify(fileUrl)
55
- );
56
- transformed = transformed.replace(
57
- /\bimport\.meta\.dirname\b/g,
58
- JSON.stringify(fileDirname)
59
- );
60
- transformed = transformed.replace(
61
- /\bimport\.meta\.filename\b/g,
62
- JSON.stringify(fileFilename)
63
- );
64
- const ext = args.path.split(".").pop();
65
- let loader = "js";
66
- if (ext === "ts")
67
- loader = "ts";
68
- else if (ext === "tsx")
69
- loader = "tsx";
70
- else if (ext === "jsx")
71
- loader = "jsx";
72
- return {
73
- contents: transformed,
74
- loader
75
- };
76
- });
77
- }
78
- };
79
- }
80
-
81
- // src/esbuild/watcher.ts
82
- import { getLogger } from "@logtape/logtape";
83
- var logger = getLogger(["watcher"]);
84
- var Watcher = class {
85
- #watcher;
86
- #building;
87
- #options;
88
- constructor(options) {
89
- this.#building = false;
90
- this.#options = options;
91
- }
92
- /**
93
- * Start watching and building
94
- */
95
- async start() {
96
- const entryPath = resolve(this.#options.entrypoint);
97
- await this.#build();
98
- const watchDir = dirname2(entryPath);
99
- logger.info("Watching for changes", { watchDir });
100
- this.#watcher = watch(
101
- watchDir,
102
- { recursive: true },
103
- (_eventType, filename) => {
104
- if (filename && (filename.endsWith(".js") || filename.endsWith(".ts") || filename.endsWith(".tsx"))) {
105
- const outDir = this.#options.outDir || "dist";
106
- if (filename.startsWith(outDir + "/") || filename.startsWith(outDir + "\\")) {
107
- return;
108
- }
109
- this.#debouncedBuild();
110
- }
111
- }
112
- );
113
- }
114
- /**
115
- * Stop watching
116
- */
117
- async stop() {
118
- if (this.#watcher) {
119
- this.#watcher.close();
120
- this.#watcher = void 0;
121
- }
122
- }
123
- #timeout;
124
- #debouncedBuild() {
125
- if (this.#timeout) {
126
- clearTimeout(this.#timeout);
127
- }
128
- this.#timeout = setTimeout(() => {
129
- this.#build();
130
- }, 100);
131
- }
132
- async #build() {
133
- if (this.#building)
134
- return;
135
- this.#building = true;
136
- try {
137
- const entryPath = resolve(this.#options.entrypoint);
138
- const outputDir = resolve(this.#options.outDir);
139
- const version = Date.now();
140
- const initialCwd = process.cwd();
141
- let workspaceRoot = initialCwd;
142
- while (workspaceRoot !== dirname2(workspaceRoot)) {
143
- try {
144
- const packageJSON = JSON.parse(
145
- readFileSync(resolve(workspaceRoot, "package.json"), "utf8")
146
- );
147
- if (packageJSON.workspaces) {
148
- break;
149
- }
150
- } catch {
151
- }
152
- workspaceRoot = dirname2(workspaceRoot);
153
- }
154
- if (workspaceRoot === dirname2(workspaceRoot)) {
155
- workspaceRoot = initialCwd;
156
- }
157
- logger.info("Building", { entryPath });
158
- logger.info("Workspace root", { workspaceRoot });
159
- await mkdir(join(outputDir, "server"), { recursive: true });
160
- await mkdir(join(outputDir, "assets"), { recursive: true });
161
- const result = await ESBuild.build({
162
- entryPoints: [entryPath],
163
- bundle: true,
164
- format: "esm",
165
- target: "es2022",
166
- platform: "node",
167
- outfile: `${outputDir}/server/app.js`,
168
- packages: "external",
169
- absWorkingDir: workspaceRoot,
170
- plugins: [
171
- importMetaPlugin(),
172
- assetsPlugin({
173
- outputDir: `${outputDir}/assets`,
174
- manifest: `${outputDir}/server/asset-manifest.json`
175
- })
176
- ],
177
- sourcemap: "inline",
178
- minify: false,
179
- treeShaking: true
180
- });
181
- if (result.errors.length > 0) {
182
- logger.error("Build errors", { errors: result.errors });
183
- this.#options.onBuild?.(false, version);
184
- } else {
185
- logger.info("Build complete", { version });
186
- this.#options.onBuild?.(true, version);
187
- }
188
- } catch (error) {
189
- logger.error("Build failed", { error });
190
- this.#options.onBuild?.(false, Date.now());
191
- } finally {
192
- this.#building = false;
193
- }
194
- }
195
- };
196
-
197
- // src/commands/develop.ts
198
- var logger2 = getLogger2(["cli"]);
199
- await configure({
200
- contextLocalStorage: new AsyncContext.Variable(),
201
- sinks: {
202
- console: getConsoleSink()
203
- },
204
- loggers: [
205
- { category: ["logtape", "meta"], sinks: [] },
206
- { category: ["platform-node"], level: "debug", sinks: ["console"] },
207
- { category: ["platform-bun"], level: "debug", sinks: ["console"] },
208
- { category: ["platform-cloudflare"], level: "debug", sinks: ["console"] },
209
- { category: ["cache"], level: "debug", sinks: ["console"] },
210
- { category: ["router"], level: "debug", sinks: ["console"] },
211
- { category: ["assets"], level: "debug", sinks: ["console"] },
212
- { category: ["cli"], level: "debug", sinks: ["console"] },
213
- { category: ["watcher"], level: "debug", sinks: ["console"] },
214
- { category: ["worker"], level: "debug", sinks: ["console"] }
215
- ]
16
+ var projectRoot = findProjectRoot();
17
+ var config = loadConfig(projectRoot);
18
+ async function reifySinks(sinks, baseDir) {
19
+ const reified = {};
20
+ for (const [name, sinkConfig] of Object.entries(sinks ?? {})) {
21
+ const { module: modulePath, export: exportName, ...rest } = sinkConfig;
22
+ if (modulePath) {
23
+ const resolvedPath = modulePath.startsWith("./") || modulePath.startsWith("../") ? resolve(baseDir, modulePath) : modulePath;
24
+ const mod = await import(resolvedPath);
25
+ const impl = exportName ? mod[exportName] : mod.default;
26
+ reified[name] = { ...rest, impl };
27
+ } else if (sinkConfig.impl) {
28
+ reified[name] = sinkConfig;
29
+ }
30
+ }
31
+ return reified;
32
+ }
33
+ var reifiedSinks = await reifySinks(config.logging?.sinks, projectRoot);
34
+ await configureLogging({
35
+ sinks: reifiedSinks,
36
+ loggers: config.logging?.loggers
216
37
  });
217
- async function developCommand(entrypoint, options) {
218
- try {
219
- const platformName = Platform.resolvePlatform(options);
220
- const workerCount = getWorkerCount(options);
221
- if (options.verbose) {
222
- Platform.displayPlatformInfo(platformName);
223
- logger2.info("Worker configuration", { workerCount });
224
- }
225
- const platformConfig = {
226
- hotReload: true,
227
- port: parseInt(options.port) || DEFAULTS.SERVER.PORT,
228
- host: options.host || DEFAULTS.SERVER.HOST
229
- };
230
- const platformInstance = await Platform.createPlatform(
231
- platformName,
232
- platformConfig
233
- );
234
- logger2.info("Starting development server", {});
235
- logger2.info("Workers", { workerCount });
236
- let serviceWorker;
237
- const outDir = "dist";
238
- const watcher = new Watcher({
239
- entrypoint,
240
- outDir,
241
- onBuild: async (success, version) => {
242
- if (success && serviceWorker) {
243
- logger2.info("Reloading Workers", { version });
244
- if (platformInstance && typeof platformInstance.reloadWorkers === "function") {
245
- await platformInstance.reloadWorkers(version);
246
- }
247
- logger2.info("Workers reloaded", {});
248
- }
249
- }
250
- });
251
- logger2.info("Building", { entrypoint });
252
- await watcher.start();
253
- logger2.info("Build complete, watching for changes", {});
254
- const builtEntrypoint = `${outDir}/server/app.js`;
255
- serviceWorker = await platformInstance.loadServiceWorker(builtEntrypoint, {
256
- hotReload: true,
257
- workerCount
258
- });
259
- const server = platformInstance.createServer(serviceWorker.handleRequest, {
260
- port: parseInt(options.port) || DEFAULTS.SERVER.PORT,
261
- host: options.host || DEFAULTS.SERVER.HOST
262
- });
263
- await server.listen();
264
- logger2.info("Server running", {
265
- url: `http://${options.host}:${options.port}`
266
- });
267
- logger2.info("Serving", { entrypoint });
268
- const shutdown = async (signal) => {
269
- logger2.info("Shutting down gracefully", { signal });
270
- await watcher.stop();
271
- await serviceWorker.dispose();
272
- await platformInstance.dispose();
273
- await server.close();
274
- logger2.info("Shutdown complete", {});
275
- process.exit(0);
276
- };
277
- process.on("SIGINT", () => shutdown("SIGINT"));
278
- process.on("SIGTERM", () => shutdown("SIGTERM"));
279
- } catch (error) {
280
- logger2.error("Failed to start development server", {
281
- error: error.message,
282
- stack: error.stack
283
- });
284
- process.exit(1);
285
- }
286
- }
287
- function getWorkerCount(options) {
288
- if (options.workers) {
289
- return parseInt(options.workers);
290
- }
291
- if (process.env.WORKER_COUNT) {
292
- return parseInt(process.env.WORKER_COUNT);
293
- }
294
- return DEFAULTS.WORKERS;
295
- }
296
-
297
- // src/commands/activate.ts
298
- import { getLogger as getLogger3 } from "@logtape/logtape";
299
- import * as Platform2 from "@b9g/platform";
300
- var logger3 = getLogger3(["cli"]);
301
- async function activateCommand(entrypoint, options) {
302
- try {
303
- const platformName = Platform2.resolvePlatform(options);
304
- const workerCount = getWorkerCount2(options);
305
- if (options.verbose) {
306
- Platform2.displayPlatformInfo(platformName);
307
- logger3.info("Worker configuration", { workerCount });
308
- }
309
- const platformConfig = {
310
- hotReload: false
311
- };
312
- const platformInstance = await Platform2.createPlatform(
313
- platformName,
314
- platformConfig
315
- );
316
- logger3.info("Activating ServiceWorker", {});
317
- const serviceWorker = await platformInstance.loadServiceWorker(entrypoint, {
318
- hotReload: false,
319
- workerCount
38
+ var program = new Command();
39
+ program.name("shovel").description("Shovel CLI").version(pkg.version);
40
+ function checkPlatformReexec(options) {
41
+ const platform = options.platform ?? config.platform;
42
+ const isBun = typeof globalThis.Bun !== "undefined";
43
+ if (platform === "bun" && !isBun) {
44
+ const result = spawnSync("bun", process.argv.slice(1), { stdio: "inherit" });
45
+ process.exit(result.status ?? 1);
46
+ }
47
+ if (platform === "node" && isBun) {
48
+ const result = Bun.spawnSync(["node", ...process.argv.slice(1)], {
49
+ stdout: "inherit",
50
+ stderr: "inherit",
51
+ stdin: "inherit"
320
52
  });
321
- logger3.info(
322
- "ServiceWorker activated - check dist/ for generated content",
323
- {}
324
- );
325
- await serviceWorker.dispose();
326
- await platformInstance.dispose();
327
- } catch (error) {
328
- logger3.error("ServiceWorker activation failed", { error: error.message });
329
- if (options.verbose) {
330
- logger3.error("Stack trace", { stack: error.stack });
331
- }
332
- process.exit(1);
333
- }
334
- }
335
- function getWorkerCount2(options) {
336
- if (options.workers) {
337
- return parseInt(options.workers);
338
- }
339
- if (process.env.WORKER_COUNT) {
340
- return parseInt(process.env.WORKER_COUNT);
53
+ process.exit(result.exitCode ?? 1);
341
54
  }
342
- return DEFAULTS.WORKERS;
343
55
  }
344
-
345
- // src/commands/info.ts
346
- import { getLogger as getLogger4 } from "@logtape/logtape";
347
- import { detectRuntime, detectDevelopmentPlatform } from "@b9g/platform";
348
- var logger4 = getLogger4(["cli"]);
349
- async function infoCommand() {
350
- logger4.info("Shovel Platform Information", {});
351
- logger4.info("---", {});
352
- logger4.info("Current Runtime", { runtime: detectRuntime() });
353
- logger4.info("Default Platform", { platform: detectDevelopmentPlatform() });
354
- }
355
-
356
- // src/commands/build.ts
357
- import * as ESBuild2 from "esbuild";
358
- import { resolve as resolve2, join as join2, dirname as dirname3 } from "path";
359
- import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
360
- import { fileURLToPath } from "url";
361
- import { assetsPlugin as assetsPlugin2 } from "@b9g/assets/plugin";
362
- import { configure as configure2, getConsoleSink as getConsoleSink2, getLogger as getLogger5 } from "@logtape/logtape";
363
- import { AsyncContext as AsyncContext2 } from "@b9g/async-context";
364
- await configure2({
365
- reset: true,
366
- // Allow reconfiguration if already configured
367
- contextLocalStorage: new AsyncContext2.Variable(),
368
- sinks: {
369
- console: getConsoleSink2()
370
- },
371
- loggers: [
372
- { category: ["logtape", "meta"], sinks: [] },
373
- { category: ["cli"], level: "info", sinks: ["console"] },
374
- { category: ["assets"], level: "info", sinks: ["console"] }
375
- ]
376
- });
377
- var logger5 = getLogger5(["cli"]);
378
- var BUILD_DEFAULTS = {
379
- format: "esm",
380
- target: "es2022",
381
- outputFile: "index.js",
382
- sourcemap: false,
383
- minify: false,
384
- treeShaking: true
385
- };
386
- var BUILD_STRUCTURE = {
387
- serverDir: "server",
388
- staticDir: "static",
389
- assetsDir: "static/assets"
390
- };
391
- async function buildForProduction({
392
- entrypoint,
393
- outDir,
394
- verbose,
395
- platform = "node",
396
- workerCount = 1
397
- }) {
398
- const buildContext = await initializeBuild({
399
- entrypoint,
400
- outDir,
401
- verbose,
402
- platform,
403
- workerCount
404
- });
405
- const buildConfig = await createBuildConfig(buildContext);
406
- const result = await ESBuild2.build(buildConfig);
407
- if (verbose && result.metafile) {
408
- await logBundleAnalysis(result.metafile);
409
- }
410
- await generatePackageJSON({
411
- ...buildContext,
412
- entryPath: buildContext.entryPath
413
- });
414
- if (verbose) {
415
- logger5.info("Built app to", { outputDir: buildContext.outputDir });
416
- logger5.info("Server files", { dir: buildContext.serverDir });
417
- logger5.info("Asset files", { dir: buildContext.assetsDir });
418
- }
419
- }
420
- async function initializeBuild({
421
- entrypoint,
422
- outDir,
423
- verbose,
424
- platform,
425
- workerCount = 1
426
- }) {
427
- if (!entrypoint) {
428
- throw new Error("Entry point is required");
429
- }
430
- if (!outDir) {
431
- throw new Error("Output directory is required");
432
- }
433
- if (verbose) {
434
- logger5.info("Entry:", { path: entrypoint });
435
- logger5.info("Output:", { dir: outDir });
436
- logger5.info("Target platform:", { platform });
437
- }
438
- const entryPath = resolve2(entrypoint);
439
- const outputDir = resolve2(outDir);
440
- try {
441
- const stats = await readFile2(entryPath, "utf8");
442
- if (stats.length === 0) {
443
- logger5.warn("Entry point is empty", { entryPath });
444
- }
445
- } catch (error) {
446
- throw new Error(`Entry point not found or not accessible: ${entryPath}`);
447
- }
448
- const validPlatforms = ["node", "bun", "cloudflare", "cloudflare-workers"];
449
- if (!validPlatforms.includes(platform)) {
450
- throw new Error(
451
- `Invalid platform: ${platform}. Valid platforms: ${validPlatforms.join(", ")}`
452
- );
453
- }
454
- const workspaceRoot = await findWorkspaceRoot();
455
- if (verbose) {
456
- logger5.info("Entry:", { entryPath });
457
- logger5.info("Output:", { outputDir });
458
- logger5.info("Target platform:", { platform });
459
- logger5.info("Workspace root:", { workspaceRoot });
460
- }
461
- try {
462
- await mkdir2(outputDir, { recursive: true });
463
- await mkdir2(join2(outputDir, BUILD_STRUCTURE.serverDir), { recursive: true });
464
- await mkdir2(join2(outputDir, BUILD_STRUCTURE.staticDir), { recursive: true });
465
- await mkdir2(join2(outputDir, BUILD_STRUCTURE.assetsDir), { recursive: true });
466
- } catch (error) {
467
- throw new Error(
468
- `Failed to create output directory structure: ${error.message}`
469
- );
470
- }
471
- return {
472
- entryPath,
473
- outputDir,
474
- serverDir: join2(outputDir, BUILD_STRUCTURE.serverDir),
475
- assetsDir: join2(outputDir, BUILD_STRUCTURE.assetsDir),
476
- workspaceRoot,
477
- platform,
478
- verbose,
479
- workerCount
480
- };
481
- }
482
- async function findWorkspaceRoot() {
483
- let workspaceRoot = process.cwd();
484
- while (workspaceRoot !== dirname3(workspaceRoot)) {
485
- try {
486
- const packageJSON = JSON.parse(
487
- await readFile2(resolve2(workspaceRoot, "package.json"), "utf8")
488
- );
489
- if (packageJSON.workspaces) {
490
- return workspaceRoot;
491
- }
492
- } catch {
493
- }
494
- workspaceRoot = dirname3(workspaceRoot);
495
- }
496
- return workspaceRoot;
497
- }
498
- async function findShovelPackageRoot() {
499
- let currentDir = dirname3(fileURLToPath(import.meta.url));
500
- let packageRoot = currentDir;
501
- while (packageRoot !== dirname3(packageRoot)) {
502
- try {
503
- const packageJSONPath = join2(packageRoot, "package.json");
504
- const content = await readFile2(packageJSONPath, "utf8");
505
- const pkg2 = JSON.parse(content);
506
- if (pkg2.name === "@b9g/shovel" || pkg2.name === "shovel") {
507
- if (packageRoot.endsWith("/dist") || packageRoot.endsWith("\\dist")) {
508
- return dirname3(packageRoot);
509
- }
510
- return packageRoot;
511
- }
512
- } catch {
513
- }
514
- packageRoot = dirname3(packageRoot);
515
- }
516
- return currentDir;
517
- }
518
- async function createBuildConfig({
519
- entryPath,
520
- serverDir,
521
- assetsDir,
522
- workspaceRoot,
523
- platform,
524
- workerCount
525
- }) {
526
- const isCloudflare = platform === "cloudflare" || platform === "cloudflare-workers";
527
- try {
528
- const virtualEntry = await createVirtualEntry(
529
- entryPath,
530
- platform,
531
- workerCount
532
- );
533
- const external = ["node:*"];
534
- const shovelRoot = await findShovelPackageRoot();
535
- if (!isCloudflare) {
536
- const userBuildConfig = {
537
- entryPoints: [entryPath],
538
- bundle: true,
539
- format: BUILD_DEFAULTS.format,
540
- target: BUILD_DEFAULTS.target,
541
- platform: "node",
542
- outfile: join2(serverDir, "server.js"),
543
- absWorkingDir: workspaceRoot || dirname3(entryPath),
544
- mainFields: ["module", "main"],
545
- conditions: ["import", "module"],
546
- plugins: [
547
- importMetaPlugin(),
548
- assetsPlugin2({
549
- outputDir: assetsDir,
550
- manifest: join2(serverDir, "asset-manifest.json")
551
- })
552
- ],
553
- metafile: true,
554
- sourcemap: BUILD_DEFAULTS.sourcemap,
555
- minify: BUILD_DEFAULTS.minify,
556
- treeShaking: BUILD_DEFAULTS.treeShaking,
557
- // Node.js doesn't support import.meta.env, so alias it to process.env
558
- // Bun supports it natively, so don't replace
559
- define: platform === "node" ? { "import.meta.env": "process.env" } : {},
560
- external
561
- };
562
- await ESBuild2.build(userBuildConfig);
563
- const runtimeSourcePath = join2(
564
- shovelRoot,
565
- "packages/platform/dist/src/runtime.js"
566
- );
567
- const workerDestPath = join2(serverDir, "worker.js");
568
- try {
569
- await ESBuild2.build({
570
- entryPoints: [runtimeSourcePath],
571
- bundle: true,
572
- format: "esm",
573
- target: "es2022",
574
- platform: "node",
575
- outfile: workerDestPath,
576
- external: ["node:*"]
577
- });
578
- } catch (error) {
579
- const installedRuntimePath = join2(
580
- shovelRoot,
581
- "node_modules/@b9g/platform/dist/src/runtime.js"
582
- );
583
- await ESBuild2.build({
584
- entryPoints: [installedRuntimePath],
585
- bundle: true,
586
- format: "esm",
587
- target: "es2022",
588
- platform: "node",
589
- outfile: workerDestPath,
590
- external: ["node:*"]
591
- });
592
- }
593
- }
594
- const buildConfig = {
595
- stdin: {
596
- contents: virtualEntry,
597
- resolveDir: shovelRoot,
598
- // Use Shovel root to resolve @b9g packages
599
- sourcefile: "virtual-entry.js"
600
- },
601
- bundle: true,
602
- format: BUILD_DEFAULTS.format,
603
- target: BUILD_DEFAULTS.target,
604
- platform: isCloudflare ? "browser" : "node",
605
- // Cloudflare: single-file architecture (server.js contains everything)
606
- // Node/Bun: multi-file architecture (index.js is entry, server.js is user code)
607
- outfile: join2(
608
- serverDir,
609
- isCloudflare ? "server.js" : BUILD_DEFAULTS.outputFile
610
- ),
611
- absWorkingDir: workspaceRoot || dirname3(entryPath),
612
- mainFields: ["module", "main"],
613
- conditions: ["import", "module"],
614
- plugins: isCloudflare ? [
615
- importMetaPlugin(),
616
- assetsPlugin2({
617
- outputDir: assetsDir,
618
- manifest: join2(serverDir, "asset-manifest.json")
619
- })
620
- ] : [],
621
- // Assets already handled in user code build
622
- metafile: true,
623
- sourcemap: BUILD_DEFAULTS.sourcemap,
624
- minify: BUILD_DEFAULTS.minify,
625
- treeShaking: BUILD_DEFAULTS.treeShaking,
626
- // Node.js doesn't support import.meta.env, so alias it to process.env
627
- // Bun and Cloudflare support it natively, so don't replace
628
- define: platform === "node" ? { "import.meta.env": "process.env" } : {},
629
- external
630
- };
631
- if (isCloudflare) {
632
- await configureCloudflareTarget(buildConfig);
633
- }
634
- return buildConfig;
635
- } catch (error) {
636
- throw new Error(`Failed to create build configuration: ${error.message}`);
637
- }
638
- }
639
- async function configureCloudflareTarget(buildConfig) {
640
- const { cloudflareWorkerBanner, cloudflareWorkerFooter } = await import("@b9g/platform-cloudflare");
641
- buildConfig.platform = "browser";
642
- buildConfig.conditions = ["worker", "browser"];
643
- buildConfig.banner = { js: cloudflareWorkerBanner };
644
- buildConfig.footer = { js: cloudflareWorkerFooter };
645
- }
646
- async function createVirtualEntry(userEntryPath, platform, workerCount = 1) {
647
- const isCloudflare = platform === "cloudflare" || platform === "cloudflare-workers";
648
- if (isCloudflare) {
649
- return `
650
- // Import user's ServiceWorker code
651
- import "${userEntryPath}";
652
- `;
653
- }
654
- return await createWorkerEntry(userEntryPath, workerCount, platform);
655
- }
656
- async function createWorkerEntry(userEntryPath, workerCount, platform) {
657
- let currentDir = dirname3(fileURLToPath(import.meta.url));
658
- let packageRoot = currentDir;
659
- while (packageRoot !== dirname3(packageRoot)) {
660
- try {
661
- const packageJSONPath = join2(packageRoot, "package.json");
662
- await readFile2(packageJSONPath, "utf8");
663
- break;
664
- } catch {
665
- packageRoot = dirname3(packageRoot);
666
- }
667
- }
668
- let templatePath = join2(packageRoot, "src/worker-entry.ts");
669
- try {
670
- await readFile2(templatePath, "utf8");
671
- } catch {
672
- templatePath = join2(packageRoot, "src/worker-entry.js");
673
- }
674
- const transpileResult = await ESBuild2.build({
675
- entryPoints: [templatePath],
676
- bundle: false,
677
- // Just transpile - bundling happens in final build
678
- format: "esm",
679
- target: "es2022",
680
- platform: "node",
681
- write: false,
682
- define: {
683
- WORKER_COUNT: JSON.stringify(workerCount),
684
- PLATFORM: JSON.stringify(platform)
685
- }
686
- });
687
- return transpileResult.outputFiles[0].text;
688
- }
689
- async function logBundleAnalysis(metafile) {
690
- try {
691
- logger5.info("Bundle analysis:", {});
692
- const analysis = await ESBuild2.analyzeMetafile(metafile);
693
- logger5.info(analysis, {});
694
- } catch (error) {
695
- logger5.warn("Failed to analyze bundle", { error: error.message });
696
- }
697
- }
698
- async function generatePackageJSON({ serverDir, platform, verbose, entryPath }) {
699
- const entryDir = dirname3(entryPath);
700
- const sourcePackageJsonPath = resolve2(entryDir, "package.json");
701
- try {
702
- const packageJSONContent = await readFile2(sourcePackageJsonPath, "utf8");
703
- try {
704
- JSON.parse(packageJSONContent);
705
- } catch (parseError) {
706
- throw new Error(`Invalid package.json format: ${parseError.message}`);
707
- }
708
- await writeFile(
709
- join2(serverDir, "package.json"),
710
- packageJSONContent,
711
- "utf8"
712
- );
713
- if (verbose) {
714
- logger5.info("Copied package.json", { serverDir });
715
- }
716
- } catch (error) {
717
- if (verbose) {
718
- logger5.warn("Could not copy package.json", { error: error.message });
719
- }
720
- try {
721
- const generatedPackageJson = await generateExecutablePackageJSON(platform);
722
- await writeFile(
723
- join2(serverDir, "package.json"),
724
- JSON.stringify(generatedPackageJson, null, 2),
725
- "utf8"
726
- );
727
- if (verbose) {
728
- logger5.info("Generated package.json", { platform });
729
- logger5.info("Package.json contents", {
730
- contents: JSON.stringify(generatedPackageJson, null, 2)
731
- });
732
- }
733
- } catch (generateError) {
734
- if (verbose) {
735
- logger5.warn("Could not generate package.json", {
736
- error: generateError.message
737
- });
738
- logger5.warn("Generation error details", { error: generateError });
739
- }
740
- }
741
- }
742
- }
743
- async function generateExecutablePackageJSON(platform) {
744
- const packageJSON = {
745
- name: "shovel-executable",
746
- version: "1.0.0",
747
- type: "module",
748
- private: true,
749
- dependencies: {}
750
- };
751
- const workspaceRoot = await findWorkspaceRoot();
752
- const isWorkspaceEnvironment = workspaceRoot !== null;
753
- if (isWorkspaceEnvironment) {
754
- packageJSON.dependencies = {};
755
- } else {
756
- switch (platform) {
757
- case "node":
758
- packageJSON.dependencies["@b9g/platform-node"] = "^0.1.0";
759
- break;
760
- case "bun":
761
- packageJSON.dependencies["@b9g/platform-bun"] = "^0.1.0";
762
- break;
763
- case "cloudflare":
764
- packageJSON.dependencies["@b9g/platform-cloudflare"] = "^0.1.0";
765
- break;
766
- default:
767
- packageJSON.dependencies["@b9g/platform"] = "^0.1.0";
768
- }
769
- packageJSON.dependencies["@b9g/cache"] = "^0.1.0";
770
- packageJSON.dependencies["@b9g/filesystem"] = "^0.1.0";
771
- }
772
- return packageJSON;
773
- }
774
- async function buildCommand(entrypoint, options) {
775
- await buildForProduction({
776
- entrypoint,
777
- outDir: "dist",
778
- verbose: options.verbose || false,
779
- platform: options.platform || "node",
780
- workerCount: options.workers ? parseInt(options.workers, 10) : 1
781
- });
782
- process.exit(0);
783
- }
784
-
785
- // bin/cli.ts
786
- var program = new Command();
787
- program.name("shovel").description("Shovel CLI").version(pkg.version);
788
56
  program.command("develop <entrypoint>").description("Start development server with hot reload").option("-p, --port <port>", "Port to listen on", DEFAULTS.SERVER.PORT).option("-h, --host <host>", "Host to bind to", DEFAULTS.SERVER.HOST).option(
789
57
  "-w, --workers <count>",
790
58
  "Number of workers (default: CPU cores)",
791
59
  DEFAULTS.WORKERS
792
- ).option("-v, --verbose", "Verbose logging", false).option("--platform <name>", "Runtime platform (node, cloudflare, bun)").action(developCommand);
793
- program.command("build <entrypoint>").description("Build app for production").option("-w, --workers <count>", "Worker count (defaults to 1)", void 0).option("--platform <name>", "Runtime platform (node, cloudflare, bun)").action(buildCommand);
794
- program.command("activate <entrypoint>").description(
795
- "Activate ServiceWorker (for static site generation in activate event)"
796
- ).option("-v, --verbose", "Verbose logging", false).option("--platform <name>", "Runtime platform (node, cloudflare, bun)").option(
797
- "-w, --workers <count>",
798
- "Number of workers",
799
- DEFAULTS.WORKERS.toString()
800
- ).action(activateCommand);
801
- program.command("info").description("Display platform and runtime information").action(infoCommand);
60
+ ).option("--platform <name>", "Runtime platform (node, cloudflare, bun)").action(async (entrypoint, options) => {
61
+ checkPlatformReexec(options);
62
+ const { developCommand } = await import("../src/_chunks/develop-VHR5FLGQ.js");
63
+ await developCommand(entrypoint, options, config);
64
+ });
65
+ program.command("build <entrypoint>").description("Build app for production").option("--platform <name>", "Runtime platform (node, cloudflare, bun)").option(
66
+ "--lifecycle [stage]",
67
+ "Run ServiceWorker lifecycle after build (install or activate, default: activate)"
68
+ ).action(async (entrypoint, options) => {
69
+ checkPlatformReexec(options);
70
+ const { buildCommand } = await import("../src/_chunks/build-IWPEM2EW.js");
71
+ await buildCommand(entrypoint, options, config);
72
+ process.exit(0);
73
+ });
74
+ program.command("info").description("Display platform and runtime information").action(async () => {
75
+ const { infoCommand } = await import("../src/_chunks/info-TDUY3FZN.js");
76
+ await infoCommand();
77
+ });
802
78
  program.parse();