@b9g/shovel 0.2.0-beta.11 → 0.2.0-beta.12
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 +1 -2
- package/README.md +13 -13
- package/bin/cli.d.ts +0 -1
- package/bin/cli.js +27 -14
- package/package.json +11 -11
- package/src/_chunks/build-CZHJ4EAF.js +159 -0
- package/src/_chunks/chunk-4DKAY5MA.js +1021 -0
- package/src/_chunks/{chunk-GRAFMTEH.js → chunk-PTLNYIRW.js} +13 -5
- package/src/_chunks/develop-2R6YVDI7.js +83 -0
- package/src/_chunks/activate-TP6RQP47.js +0 -99
- package/src/_chunks/build-V3IPZGKC.js +0 -434
- package/src/_chunks/chunk-ADR5RW57.js +0 -78
- package/src/_chunks/chunk-JJFM7PO2.js +0 -468
- package/src/_chunks/develop-A7EU2ZDY.js +0 -404
package/CHANGELOG.md
CHANGED
|
@@ -124,8 +124,7 @@ router.use(cors({ origin: "https://example.com" }));
|
|
|
124
124
|
### CLI Changes
|
|
125
125
|
|
|
126
126
|
- `shovel develop` - Development server with hot reload (note: `dev` alias removed)
|
|
127
|
-
- `shovel build` - Production build
|
|
128
|
-
- `shovel activate` - Static site generation
|
|
127
|
+
- `shovel build` - Production build (use `--lifecycle` flag to run lifecycle events)
|
|
129
128
|
- Removed `--verbose` flags (use logging config instead)
|
|
130
129
|
|
|
131
130
|
### Infrastructure
|
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
Shovel is a CLI platform for developing and deploying service workers as application servers.
|
|
6
6
|
|
|
7
7
|
```javascript
|
|
8
|
+
// src/server.ts
|
|
8
9
|
import {Router} from "@b9g/router";
|
|
9
10
|
const router = new Router();
|
|
10
11
|
|
|
@@ -16,12 +17,12 @@ self.addEventListener("fetch", (ev) => {
|
|
|
16
17
|
```
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
|
-
shovel develop
|
|
20
|
+
shovel develop src/server.ts
|
|
20
21
|
```
|
|
21
22
|
## Quick Start
|
|
22
23
|
|
|
23
24
|
```javascript
|
|
24
|
-
//
|
|
25
|
+
// src/server.js
|
|
25
26
|
import {Router} from "@b9g/router";
|
|
26
27
|
|
|
27
28
|
const router = new Router();
|
|
@@ -42,12 +43,12 @@ self.addEventListener("fetch", (event) => {
|
|
|
42
43
|
npm create @b9g/shovel my-app
|
|
43
44
|
|
|
44
45
|
# Development with hot reload
|
|
45
|
-
npx @b9g/shovel develop
|
|
46
|
+
npx @b9g/shovel develop src/server.ts
|
|
46
47
|
|
|
47
48
|
# Build for production
|
|
48
|
-
npx @b9g/shovel build
|
|
49
|
-
npx @b9g/shovel build
|
|
50
|
-
npx @b9g/shovel build
|
|
49
|
+
npx @b9g/shovel build src/server.ts --platform=node
|
|
50
|
+
npx @b9g/shovel build src/server.ts --platform=bun
|
|
51
|
+
npx @b9g/shovel build src/server.ts --platform=cloudflare
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
|
|
@@ -57,7 +58,7 @@ Shovel is obsessively standards-first. All Shovel APIs use web standards, and Sh
|
|
|
57
58
|
| API | Standard | Purpose |
|
|
58
59
|
|-----|----------|--------------|
|
|
59
60
|
| `fetch()` | [Fetch](https://fetch.spec.whatwg.org) | Networking |
|
|
60
|
-
| `
|
|
61
|
+
| `install`, `activate`, `fetch` events | [Service Workers](https://w3c.github.io/ServiceWorker/) | Server lifecycle |
|
|
61
62
|
| `AsyncContext.Variable` | [TC39 Stage 2](https://github.com/tc39/proposal-async-context) | Request-scoped state |
|
|
62
63
|
| `self.caches` | [Cache API](https://w3c.github.io/ServiceWorker/#cache-interface) | Response caching |
|
|
63
64
|
| `self.directories` | [FileSystem API](https://fs.spec.whatwg.org/) | Storage (local, S3, R2) |
|
|
@@ -92,13 +93,12 @@ Each storage type is:
|
|
|
92
93
|
- **Configured uniformly** - all are configured by `shovel.json`
|
|
93
94
|
- **Platform-aware** - sensible defaults per platform, override what you need
|
|
94
95
|
|
|
95
|
-
This pattern means your app logic stays clean. Swap Redis for
|
|
96
|
-
|
|
96
|
+
This pattern means your app logic stays clean. Swap in Redis for caches, S3 for local filesystem, Postgres for SQLite - change the config, not the code.
|
|
97
97
|
|
|
98
98
|
## Platform APIs
|
|
99
99
|
|
|
100
100
|
```javascript
|
|
101
|
-
// Cache API -
|
|
101
|
+
// Cache API - Request/Response-based caching
|
|
102
102
|
const cache = await self.caches.open("my-cache");
|
|
103
103
|
await cache.put(request, response.clone());
|
|
104
104
|
const cached = await cache.match(request);
|
|
@@ -124,8 +124,8 @@ requestId.run(crypto.randomUUID(), async () => {
|
|
|
124
124
|
Import any file and get its production URL with content hashing:
|
|
125
125
|
|
|
126
126
|
```javascript
|
|
127
|
-
import styles from "./styles.css" with {
|
|
128
|
-
import logo from "./logo.png" with {
|
|
127
|
+
import styles from "./styles.css" with {assetBase: "/assets"};
|
|
128
|
+
import logo from "./logo.png" with {assetBase: "/assets"};
|
|
129
129
|
|
|
130
130
|
// styles = "/assets/styles-a1b2c3d4.css"
|
|
131
131
|
// logo = "/assets/logo-e5f6g7h8.png"
|
|
@@ -137,8 +137,8 @@ At build time, Shovel:
|
|
|
137
137
|
- Transforms imports to return the final URLs
|
|
138
138
|
|
|
139
139
|
Assets are served via the platform's best option:
|
|
140
|
-
- **Cloudflare**: Workers Assets (edge-cached, zero config)
|
|
141
140
|
- **Node/Bun**: Static file middleware or directory storage
|
|
141
|
+
- **Cloudflare**: Workers Assets (edge-cached, zero config)
|
|
142
142
|
|
|
143
143
|
## Configuration
|
|
144
144
|
|
package/bin/cli.d.ts
CHANGED
package/bin/cli.js
CHANGED
|
@@ -5,10 +5,11 @@ import {
|
|
|
5
5
|
DEFAULTS,
|
|
6
6
|
findProjectRoot,
|
|
7
7
|
loadConfig
|
|
8
|
-
} from "../src/_chunks/chunk-
|
|
8
|
+
} from "../src/_chunks/chunk-PTLNYIRW.js";
|
|
9
9
|
|
|
10
10
|
// bin/cli.ts
|
|
11
11
|
import { resolve } from "path";
|
|
12
|
+
import { spawnSync } from "child_process";
|
|
12
13
|
import { configureLogging } from "@b9g/platform/runtime";
|
|
13
14
|
import { Command } from "commander";
|
|
14
15
|
import pkg from "../package.json" with { type: "json" };
|
|
@@ -36,27 +37,39 @@ await configureLogging({
|
|
|
36
37
|
});
|
|
37
38
|
var program = new Command();
|
|
38
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"
|
|
52
|
+
});
|
|
53
|
+
process.exit(result.exitCode ?? 1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
39
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(
|
|
40
57
|
"-w, --workers <count>",
|
|
41
58
|
"Number of workers (default: CPU cores)",
|
|
42
59
|
DEFAULTS.WORKERS
|
|
43
60
|
).option("--platform <name>", "Runtime platform (node, cloudflare, bun)").action(async (entrypoint, options) => {
|
|
44
|
-
|
|
61
|
+
checkPlatformReexec(options);
|
|
62
|
+
const { developCommand } = await import("../src/_chunks/develop-2R6YVDI7.js");
|
|
45
63
|
await developCommand(entrypoint, options, config);
|
|
46
64
|
});
|
|
47
|
-
program.command("build <entrypoint>").description("Build app for production").option("
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
51
|
-
program.command("activate <entrypoint>").description(
|
|
52
|
-
"Activate ServiceWorker (for static site generation in activate event)"
|
|
53
|
-
).option("--platform <name>", "Runtime platform (node, cloudflare, bun)").option(
|
|
54
|
-
"-w, --workers <count>",
|
|
55
|
-
"Number of workers",
|
|
56
|
-
DEFAULTS.WORKERS.toString()
|
|
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)"
|
|
57
68
|
).action(async (entrypoint, options) => {
|
|
58
|
-
|
|
59
|
-
await
|
|
69
|
+
checkPlatformReexec(options);
|
|
70
|
+
const { buildCommand } = await import("../src/_chunks/build-CZHJ4EAF.js");
|
|
71
|
+
await buildCommand(entrypoint, options, config);
|
|
72
|
+
process.exit(0);
|
|
60
73
|
});
|
|
61
74
|
program.command("info").description("Display platform and runtime information").action(async () => {
|
|
62
75
|
const { infoCommand } = await import("../src/_chunks/info-TDUY3FZN.js");
|
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.12",
|
|
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": {
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"@b9g/cache": "^0.2.0-beta.0",
|
|
28
28
|
"@b9g/crank": "^0.7.2",
|
|
29
29
|
"@logtape/file": "^1.0.0",
|
|
30
|
-
"@b9g/filesystem": "^0.1.
|
|
30
|
+
"@b9g/filesystem": "^0.1.8",
|
|
31
31
|
"@b9g/http-errors": "^0.2.0-beta.0",
|
|
32
32
|
"@b9g/libuild": "^0.1.22",
|
|
33
|
-
"@b9g/platform": "^0.1.
|
|
34
|
-
"@b9g/platform-bun": "^0.1.
|
|
35
|
-
"@b9g/platform-cloudflare": "^0.1.
|
|
36
|
-
"@b9g/platform-node": "^0.1.
|
|
33
|
+
"@b9g/platform": "^0.1.13",
|
|
34
|
+
"@b9g/platform-bun": "^0.1.11",
|
|
35
|
+
"@b9g/platform-cloudflare": "^0.1.11",
|
|
36
|
+
"@b9g/platform-node": "^0.1.13",
|
|
37
37
|
"@b9g/router": "^0.2.0-beta.1",
|
|
38
38
|
"@types/bun": "^1.3.4",
|
|
39
39
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@b9g/node-webworker": "^0.2.0-beta.1",
|
|
49
|
-
"@b9g/platform": "^0.1.
|
|
50
|
-
"@b9g/platform-node": "^0.1.
|
|
51
|
-
"@b9g/platform-cloudflare": "^0.1.
|
|
52
|
-
"@b9g/platform-bun": "^0.1.
|
|
49
|
+
"@b9g/platform": "^0.1.13",
|
|
50
|
+
"@b9g/platform-node": "^0.1.13",
|
|
51
|
+
"@b9g/platform-cloudflare": "^0.1.11",
|
|
52
|
+
"@b9g/platform-bun": "^0.1.11",
|
|
53
53
|
"@b9g/cache": "^0.2.0-beta.0",
|
|
54
|
-
"@b9g/filesystem": "^0.1.
|
|
54
|
+
"@b9g/filesystem": "^0.1.8",
|
|
55
55
|
"@b9g/http-errors": "^0.2.0-beta.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ServerBundler
|
|
3
|
+
} from "./chunk-4DKAY5MA.js";
|
|
4
|
+
import {
|
|
5
|
+
findProjectRoot,
|
|
6
|
+
findWorkspaceRoot
|
|
7
|
+
} from "./chunk-PTLNYIRW.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/build.ts
|
|
10
|
+
import { resolve, join, dirname } from "path";
|
|
11
|
+
import { getLogger } from "@logtape/logtape";
|
|
12
|
+
import * as Platform from "@b9g/platform";
|
|
13
|
+
import { readFile, writeFile } from "fs/promises";
|
|
14
|
+
var logger = getLogger(["shovel"]);
|
|
15
|
+
async function buildForProduction({
|
|
16
|
+
entrypoint,
|
|
17
|
+
outDir,
|
|
18
|
+
platform = "node",
|
|
19
|
+
userBuildConfig,
|
|
20
|
+
lifecycle
|
|
21
|
+
}) {
|
|
22
|
+
const entryPath = resolve(entrypoint);
|
|
23
|
+
const outputDir = resolve(outDir);
|
|
24
|
+
const serverDir = join(outputDir, "server");
|
|
25
|
+
const projectRoot = findProjectRoot(dirname(entryPath));
|
|
26
|
+
logger.debug("Entry:", { entryPath });
|
|
27
|
+
logger.debug("Output:", { outputDir });
|
|
28
|
+
logger.debug("Target platform:", { platform });
|
|
29
|
+
logger.debug("Project root:", { projectRoot });
|
|
30
|
+
const platformInstance = await Platform.createPlatform(platform);
|
|
31
|
+
const platformESBuildConfig = platformInstance.getESBuildConfig();
|
|
32
|
+
const bundler = new ServerBundler({
|
|
33
|
+
entrypoint,
|
|
34
|
+
outDir,
|
|
35
|
+
platform: platformInstance,
|
|
36
|
+
platformESBuildConfig,
|
|
37
|
+
userBuildConfig,
|
|
38
|
+
lifecycle
|
|
39
|
+
});
|
|
40
|
+
const { success, outputs } = await bundler.build();
|
|
41
|
+
if (!success) {
|
|
42
|
+
throw new Error("Build failed");
|
|
43
|
+
}
|
|
44
|
+
await generatePackageJSON({ serverDir, platform, entryPath });
|
|
45
|
+
logger.debug("Built app to", { outputDir });
|
|
46
|
+
logger.debug("Server files", { dir: serverDir });
|
|
47
|
+
logger.debug("Public files", { dir: join(outputDir, "public") });
|
|
48
|
+
logger.info("Build complete: {path}", {
|
|
49
|
+
path: outputs.index || outputs.worker
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
platform: platformInstance,
|
|
53
|
+
workerPath: outputs.worker
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function generatePackageJSON({
|
|
57
|
+
serverDir,
|
|
58
|
+
platform,
|
|
59
|
+
entryPath
|
|
60
|
+
}) {
|
|
61
|
+
const entryDir = dirname(entryPath);
|
|
62
|
+
const sourcePackageJsonPath = resolve(entryDir, "package.json");
|
|
63
|
+
try {
|
|
64
|
+
const packageJSONContent = await readFile(sourcePackageJsonPath, "utf8");
|
|
65
|
+
try {
|
|
66
|
+
JSON.parse(packageJSONContent);
|
|
67
|
+
} catch (parseError) {
|
|
68
|
+
throw new Error(`Invalid package.json format: ${parseError}`);
|
|
69
|
+
}
|
|
70
|
+
await writeFile(
|
|
71
|
+
join(serverDir, "package.json"),
|
|
72
|
+
packageJSONContent,
|
|
73
|
+
"utf8"
|
|
74
|
+
);
|
|
75
|
+
logger.debug("Copied package.json", { serverDir });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
logger.debug("Could not copy package.json: {error}", { error });
|
|
78
|
+
try {
|
|
79
|
+
const generatedPackageJson = await generateExecutablePackageJSON(platform);
|
|
80
|
+
await writeFile(
|
|
81
|
+
join(serverDir, "package.json"),
|
|
82
|
+
JSON.stringify(generatedPackageJson, null, 2),
|
|
83
|
+
"utf8"
|
|
84
|
+
);
|
|
85
|
+
logger.debug("Generated package.json", { platform });
|
|
86
|
+
} catch (generateError) {
|
|
87
|
+
logger.debug("Could not generate package.json: {error}", {
|
|
88
|
+
error: generateError
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function generateExecutablePackageJSON(platform) {
|
|
94
|
+
const packageJSON = {
|
|
95
|
+
name: "shovel-executable",
|
|
96
|
+
version: "1.0.0",
|
|
97
|
+
type: "module",
|
|
98
|
+
private: true,
|
|
99
|
+
dependencies: {}
|
|
100
|
+
};
|
|
101
|
+
const isWorkspaceEnvironment = findWorkspaceRoot() !== null;
|
|
102
|
+
if (isWorkspaceEnvironment) {
|
|
103
|
+
packageJSON.dependencies = {};
|
|
104
|
+
} else {
|
|
105
|
+
switch (platform) {
|
|
106
|
+
case "node":
|
|
107
|
+
packageJSON.dependencies["@b9g/platform-node"] = "^0.1.0";
|
|
108
|
+
break;
|
|
109
|
+
case "bun":
|
|
110
|
+
packageJSON.dependencies["@b9g/platform-bun"] = "^0.1.0";
|
|
111
|
+
break;
|
|
112
|
+
case "cloudflare":
|
|
113
|
+
packageJSON.dependencies["@b9g/platform-cloudflare"] = "^0.1.0";
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
packageJSON.dependencies["@b9g/platform"] = "^0.1.0";
|
|
117
|
+
}
|
|
118
|
+
packageJSON.dependencies["@b9g/cache"] = "^0.1.0";
|
|
119
|
+
packageJSON.dependencies["@b9g/filesystem"] = "^0.1.0";
|
|
120
|
+
}
|
|
121
|
+
return packageJSON;
|
|
122
|
+
}
|
|
123
|
+
async function buildCommand(entrypoint, options, config) {
|
|
124
|
+
const platform = Platform.resolvePlatform({ ...options, config });
|
|
125
|
+
let lifecycleOption;
|
|
126
|
+
if (options.lifecycle) {
|
|
127
|
+
const stage = typeof options.lifecycle === "string" ? options.lifecycle : "activate";
|
|
128
|
+
if (stage !== "install" && stage !== "activate") {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Invalid lifecycle stage: ${stage}. Must be "install" or "activate".`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
lifecycleOption = { stage };
|
|
134
|
+
}
|
|
135
|
+
const { platform: platformInstance, workerPath } = await buildForProduction({
|
|
136
|
+
entrypoint,
|
|
137
|
+
outDir: "dist",
|
|
138
|
+
platform,
|
|
139
|
+
userBuildConfig: config.build,
|
|
140
|
+
lifecycle: lifecycleOption
|
|
141
|
+
});
|
|
142
|
+
if (lifecycleOption) {
|
|
143
|
+
if (!workerPath) {
|
|
144
|
+
throw new Error("No worker entry point found in build outputs");
|
|
145
|
+
}
|
|
146
|
+
logger.info("Running ServiceWorker lifecycle: {stage}", {
|
|
147
|
+
stage: lifecycleOption.stage
|
|
148
|
+
});
|
|
149
|
+
await platformInstance.serviceWorker.register(workerPath);
|
|
150
|
+
await platformInstance.serviceWorker.ready;
|
|
151
|
+
await platformInstance.serviceWorker.terminate();
|
|
152
|
+
logger.info("Lifecycle complete");
|
|
153
|
+
}
|
|
154
|
+
await platformInstance.dispose();
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
buildCommand,
|
|
158
|
+
buildForProduction
|
|
159
|
+
};
|