@b9g/shovel 0.2.0-beta.2 → 0.2.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +184 -172
- package/README.md +313 -54
- package/bin/cli.d.ts +0 -1
- package/bin/cli.js +62 -786
- package/bin/create.js +79 -37
- package/package.json +33 -37
- package/src/_chunks/build-IWPEM2EW.js +160 -0
- package/src/_chunks/chunk-PTLNYIRW.js +1158 -0
- package/src/_chunks/chunk-VWH6D26D.js +1062 -0
- package/src/_chunks/develop-VHR5FLGQ.js +85 -0
- package/src/_chunks/info-TDUY3FZN.js +13 -0
- package/src/worker-entry.d.ts +0 -7
- package/src/worker-entry.js +0 -37
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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("
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
)
|
|
797
|
-
|
|
798
|
-
"
|
|
799
|
-
|
|
800
|
-
).action(
|
|
801
|
-
|
|
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();
|