@b9g/shovel 0.2.1 → 0.2.3

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 CHANGED
@@ -2,209 +2,51 @@
2
2
 
3
3
  All notable changes to Shovel will be documented in this file.
4
4
 
5
- ## [0.2.0-beta.12] - 2026-01-14
5
+ ## [0.2.3] - 2026-02-02
6
6
 
7
- ### Breaking Changes
8
-
9
- - **`shovel activate` command removed** - Use `shovel build --lifecycle` instead
10
- - **`loadServiceWorker()` removed** - Use `platform.serviceWorker.register()` instead
11
- - **`getEntryWrapper()` removed** - Use `getProductionEntryPoints()` instead
12
-
13
- ### Build System Unification
14
-
15
- Major refactor of the build system to be platform-driven and unified across all commands.
7
+ ### Features
16
8
 
17
- - **New `ServerBundler` class** - Unified bundler replacing separate build/watch logic
18
- - **Platform-driven entry points** - Platforms define their output structure via `getProductionEntryPoints()`:
19
- - Node/Bun: `{ index: "<supervisor>", worker: "<worker>" }` - two files
20
- - Cloudflare: `{ worker: "<code>" }` - single file
21
- - **Deleted `activate` command** - Replaced with `shovel build --lifecycle` flag
22
- - `--lifecycle` - runs activate stage (default)
23
- - `--lifecycle install` - runs install stage only
9
+ - **Enable code splitting for client-side bundles** - Dynamic imports in client scripts now create separate chunks instead of being inlined, allowing heavy dependencies to be lazy-loaded. ([#39](https://github.com/bikeshaving/shovel/pull/39), fixes [#38](https://github.com/bikeshaving/shovel/issues/38))
24
10
 
25
- ### API Changes
11
+ ### Bug Fixes
26
12
 
27
- - **New `platform.serviceWorker.register()` API** - Mirrors browser's `navigator.serviceWorker.register()`
28
- - **Deleted `loadServiceWorker()`** - Use `serviceWorker.register()` instead
29
- - **New `platform.listen()` / `close()`** - Server lifecycle management
30
- - **New `runLifecycle()` and `dispatchRequest()`** - Public runtime utilities
13
+ - **Use unique manifest keys for chunks** - Chunk files are now keyed by URL to avoid collisions when the same chunk appears in different assetBase directories.
31
14
 
32
- ### Code Quality
15
+ ## [0.2.2] - 2026-01-30
33
16
 
34
- - Extracted `mergeConfigWithDefaults()` helper to reduce duplication
35
- - Added JSDoc to platform `create*` methods documenting defaults
36
- - Standardized import organization (node builtins → external → @b9g/* → relative)
37
- - Renamed `isDynamicCode` → `containsRuntimeExpressions` for clarity
17
+ ### Bug Fixes
38
18
 
39
- ### Package Updates
19
+ - **Fix asset manifest invalidation in dev mode** - Assets with new content hashes after client bundle changes no longer return 404. The root cause was build-time manifest resolution reading stale data from disk during rebuilds. ([#36](https://github.com/bikeshaving/shovel/pull/36), fixes [#35](https://github.com/bikeshaving/shovel/issues/35))
40
20
 
41
- - `@b9g/platform` → 0.1.14-beta.0
42
- - `@b9g/platform-node` → 0.1.14-beta.0
43
- - `@b9g/platform-bun` → 0.1.12-beta.0
44
- - `@b9g/platform-cloudflare` → 0.1.12-beta.0
21
+ ## [0.2.1] - 2026-01-28
45
22
 
46
- ### Deleted Files
23
+ ### Improvements
47
24
 
48
- - `src/commands/activate.ts` - Replaced by `build --lifecycle`
49
- - `src/utils/watcher.ts` - Merged into `ServerBundler`
50
- - `src/plugins/shovel.ts` - Split into `config.ts` + `entry.ts`
51
- - `packages/platform/test/single-threaded.test.ts`
52
- - `SingleThreadedRuntime` class - All platforms now use `ServiceWorkerPool`
25
+ - **Improved create-shovel UX** - Better templates and user experience for project scaffolding
53
26
 
54
- ---
27
+ ## [0.2.0] - 2026-01-28
55
28
 
56
- ## [0.2.0-beta.11] - 2026-01-10
29
+ This is a major release that establishes Shovel as a complete ServiceWorker-based meta-framework with locked-down APIs for core packages, a unified configuration system, and comprehensive platform support.
57
30
 
58
- ### Changes since beta.10
59
- - **ESBuild configuration support (#18)** - Custom esbuild options in shovel.json
60
- - **Config expression syntax** - `$PORT || 3000`, `$DATABASE_URL ?? null`
61
- - **Null fallback fix** - Allow intentional null fallbacks in config expressions
62
- - **DatabaseStorage API redesign** - New open/get pattern with IndexedDB-style migrations
63
- - **Migrated from Drizzle to @b9g/zen** - Simpler, more portable database layer
64
- - **Logging DX improvements** - Better defaults, consolidated categories
65
- - **`impl` key unification** - Simplified resource configuration
66
- - **CI/lint enforcement** - ESLint and Prettier standardized
67
- - **Documentation** - Comprehensive docs for all APIs
31
+ ### Highlights
68
32
 
69
- ### Package Updates
70
- - `@b9g/router` 0.2.0-beta.1 (CORS middleware, trailingSlash moved)
71
- - `@b9g/node-webworker` 0.2.0-beta.1 (CloseEvent, onclose, env option)
72
- - `@b9g/cache-redis` 0.2.0-beta.1 (logger category fix)
73
- - `@b9g/assets` 0.2.0-beta.0
74
- - `@b9g/async-context` → 0.2.0-beta.0
75
- - `@b9g/cache` 0.2.0-beta.0
76
- - `@b9g/http-errors` → 0.2.0-beta.0
77
- - `@b9g/match-pattern` → 0.2.0-beta.0
33
+ - **Consistent Worker Execution Model** - ServiceWorker code always runs in a worker thread
34
+ - **ESBuild Configuration Support** - Custom esbuild options in shovel.json
35
+ - **Config Expression Syntax** - `$PORT || 3000`, `$DATABASE_URL ?? null`
36
+ - **Comprehensive Logging System** - Built-in LogTape integration
37
+ - **Database Storage API** - IndexedDB-style migrations with `self.databases`
38
+ - **CORS Middleware** - New middleware in `@b9g/router/middleware`
39
+ - **Unified Build System** - New `ServerBundler` class, platform-driven entry points
78
40
 
79
- ---
80
-
81
- ## [0.2.0-beta.10] - Previous Beta
41
+ ### Breaking Changes
82
42
 
83
- This is a major release that establishes Shovel as a complete ServiceWorker-based meta-framework. The 0.2.0 beta introduces locked-down APIs for core packages, a unified configuration system, and comprehensive platform support.
43
+ - `self.buckets` renamed to `self.directories`
44
+ - `shovel activate` command removed - Use `shovel build --lifecycle` instead
45
+ - `loadServiceWorker()` removed - Use `platform.serviceWorker.register()` instead
46
+ - `@b9g/router` trailingSlash middleware moved to `@b9g/router/middleware`
47
+ - `self.loggers.get()` now takes an array: `self.loggers.get(["app", "db"])`
84
48
 
85
- ### Breaking Changes
49
+ ### CLI
86
50
 
87
- - **`self.buckets` renamed to `self.directories`** - The file system API now uses `directories` to align with web standards terminology
88
- - **`@b9g/router` middleware moved** - `trailingSlash` middleware moved from main export to `@b9g/router/middleware`
89
- - **`self.loggers.get()` signature changed** - Now takes an array: `self.loggers.get(["app", "db"])` instead of dot notation
90
- - **Config `module`/`export` unified to `impl`** - Resource configurations now use a single `impl` key for reified implementations
91
-
92
- ### New Features
93
-
94
- #### Consistent Worker Execution Model (#17)
95
- ServiceWorker code now ALWAYS runs in a worker thread, never the main thread. This ensures:
96
- - Same globals/environment in dev and prod (eliminates mode-only bugs)
97
- - Worker isolation preserved
98
- - Simplified mental model
99
-
100
- #### ESBuild Configuration Support (#18)
101
- Custom ESBuild options can now be specified in `shovel.json`:
102
- ```json
103
- {
104
- "esbuild": {
105
- "external": ["lightningcss"],
106
- "define": { "DEBUG": "true" }
107
- }
108
- }
109
- ```
110
-
111
- #### Config Expression Syntax
112
- Environment variables and expressions in `shovel.json`:
113
- ```json
114
- {
115
- "port": "$PORT || 3000",
116
- "databases": {
117
- "main": {
118
- "url": "$DATABASE_URL"
119
- }
120
- }
121
- }
122
- ```
123
-
124
- #### Comprehensive Logging System
125
- - Built-in LogTape integration with console sink by default
126
- - Configurable sinks (file, OpenTelemetry, Sentry, etc.)
127
- - Category-based log levels
128
- - `shovel` category logs at `info` level by default
129
-
130
- #### Database Storage API
131
- New `self.databases` API with IndexedDB-style migrations:
132
- ```typescript
133
- self.addEventListener("activate", (event) => {
134
- event.waitUntil(
135
- databases.open("main", 2, (e) => {
136
- e.waitUntil(e.db.exec`CREATE TABLE users (...)`);
137
- })
138
- );
139
- });
140
-
141
- // In fetch handlers
142
- const db = databases.get("main");
143
- const users = await db.all`SELECT * FROM users`;
144
- ```
145
-
146
- #### CORS Middleware
147
- New CORS middleware in `@b9g/router/middleware`:
148
- ```typescript
149
- import { cors } from "@b9g/router/middleware";
150
- router.use(cors({ origin: "https://example.com" }));
151
- ```
152
-
153
- ### Package Updates
154
-
155
- #### Locked at 0.2.0-beta.0 (API stable)
156
- - `@b9g/async-context` - AsyncContext polyfill
157
- - `@b9g/match-pattern` - URLPattern implementation
158
- - `@b9g/assets` - Static asset pipeline with content hashing
159
- - `@b9g/cache` - Cache API implementation (memory, postmessage)
160
- - `@b9g/http-errors` - HTTP error classes
161
-
162
- #### Updated to 0.2.0-beta.1
163
- - `@b9g/router` - Added CORS middleware, moved trailingSlash to /middleware
164
- - `@b9g/node-webworker` - Added CloseEvent, onclose handler, env option
165
- - `@b9g/cache-redis` - Logger category updates
166
-
167
- #### Still Evolving (0.1.x)
168
- - `@b9g/platform` - Core runtime and platform abstraction
169
- - `@b9g/platform-bun` - Bun platform adapter
170
- - `@b9g/platform-node` - Node.js platform adapter
171
- - `@b9g/platform-cloudflare` - Cloudflare Workers adapter
172
- - `@b9g/filesystem` - File System Access API implementation
173
- - `@b9g/filesystem-s3` - S3 filesystem adapter
174
-
175
- ### CLI Changes
176
-
177
- - `shovel develop` - Development server with hot reload (note: `dev` alias removed)
178
- - `shovel build` - Production build (use `--lifecycle` flag to run lifecycle events)
179
- - Removed `--verbose` flags (use logging config instead)
180
-
181
- ### Infrastructure
182
-
183
- - Migrated from Drizzle ORM to `@b9g/zen` for database operations
184
- - Added GitHub Actions CI with parallel test execution
185
- - ESLint and Prettier configuration standardized
186
- - Comprehensive test suites with `bun:test`
187
-
188
- ### Documentation
189
-
190
- New documentation pages:
191
- - Getting Started guide
192
- - CLI reference
193
- - Configuration (shovel.json) reference
194
- - Deployment guide
195
- - ServiceWorker lifecycle
196
- - Routing and middleware
197
- - Storage APIs (databases, caches, directories)
198
- - Cookies and AsyncContext
199
-
200
- ---
201
-
202
- ## [0.1.x] - Previous Releases
203
-
204
- Initial development releases establishing core architecture:
205
- - ServiceWorker-based request handling
206
- - Platform abstraction layer
207
- - Router with generator-based middleware
208
- - Cache API implementations
209
- - File System Access API
210
- - Hot reload in development
51
+ - `shovel develop` - Development server with hot reload
52
+ - `shovel build` - Production build with `--lifecycle` flag for lifecycle events
package/README.md CHANGED
@@ -69,7 +69,9 @@ Your code uses standards. Shovel makes them work everywhere.
69
69
 
70
70
  ## Meta-Framework
71
71
 
72
- Shovel is a meta-framework: it provides and implements primitives rather than opinions. Instead of dictating how you build, it gives you portable building blocks that work everywhere.
72
+ Shovel is a meta-framework: it generates bundles and compiles your code with ESBuild.
73
+ You write code, and it runs in development and production with the exact same APIs.
74
+ Shovel takes care of single file bundle requirements, and transpiling JSX/TypeScript.
73
75
 
74
76
  ## True Portability
75
77
 
package/bin/cli.js CHANGED
@@ -75,7 +75,7 @@ program.command("develop <entrypoint>").description("Start development server wi
75
75
  DEFAULTS.WORKERS
76
76
  ).option("--platform <name>", "Runtime platform (node, cloudflare, bun)").action(async (entrypoint, options) => {
77
77
  checkPlatformReexec(options);
78
- const { developCommand } = await import("../src/_chunks/develop-UJ7PMMOM.js");
78
+ const { developCommand } = await import("../src/_chunks/develop-6DPQE5H4.js");
79
79
  await developCommand(entrypoint, options, config);
80
80
  });
81
81
  program.command("build <entrypoint>").description("Build app for production").option("--platform <name>", "Runtime platform (node, cloudflare, bun)").option(
@@ -83,7 +83,7 @@ program.command("build <entrypoint>").description("Build app for production").op
83
83
  "Run ServiceWorker lifecycle after build (install or activate, default: activate)"
84
84
  ).action(async (entrypoint, options) => {
85
85
  checkPlatformReexec(options);
86
- const { buildCommand } = await import("../src/_chunks/build-QZE6GNRD.js");
86
+ const { buildCommand } = await import("../src/_chunks/build-BM4A74RI.js");
87
87
  await buildCommand(entrypoint, options, config);
88
88
  process.exit(0);
89
89
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/shovel",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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": {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ServerBundler,
3
3
  loadPlatformModule
4
- } from "./chunk-LVKYX67O.js";
4
+ } from "./chunk-WVHECOTO.js";
5
5
  import {
6
6
  findProjectRoot,
7
7
  findWorkspaceRoot
@@ -98,6 +98,7 @@ function assetsPlugin(options = {}) {
98
98
  let content;
99
99
  let outputExt = ext;
100
100
  let mimeType;
101
+ let chunkFiles = [];
101
102
  if (needsTranspilation) {
102
103
  const defaultPlugins = [
103
104
  NodeModulesPolyfillPlugin(),
@@ -111,11 +112,12 @@ function assetsPlugin(options = {}) {
111
112
  entryPoints: [args.path],
112
113
  bundle: true,
113
114
  format: "esm",
115
+ splitting: true,
114
116
  target: ["es2022", "chrome90"],
115
117
  platform: "browser",
116
118
  write: false,
117
119
  minify: true,
118
- // outdir is required for esbuild to know where to put extracted CSS
120
+ // outdir is required for esbuild to know where to put extracted CSS and chunks
119
121
  outdir: outDir,
120
122
  // Apply polyfills and user-provided build options
121
123
  plugins,
@@ -152,10 +154,10 @@ function assetsPlugin(options = {}) {
152
154
  outputExt = ".css";
153
155
  mimeType = "text/css";
154
156
  } else {
155
- const jsOutput = result.outputFiles.find(
157
+ const jsOutputFiles = result.outputFiles.filter(
156
158
  (f) => f.path.endsWith(".js")
157
159
  );
158
- if (!jsOutput) {
160
+ if (jsOutputFiles.length === 0) {
159
161
  return {
160
162
  errors: [
161
163
  {
@@ -164,9 +166,30 @@ function assetsPlugin(options = {}) {
164
166
  ]
165
167
  };
166
168
  }
167
- content = Buffer.from(jsOutput.text);
169
+ const entryBaseName = name + ".js";
170
+ const entryFile = jsOutputFiles.find(
171
+ (f) => basename(f.path) === entryBaseName
172
+ );
173
+ if (!entryFile) {
174
+ return {
175
+ errors: [
176
+ {
177
+ text: `Could not find entry file ${entryBaseName} in output`
178
+ }
179
+ ]
180
+ };
181
+ }
182
+ content = Buffer.from(entryFile.text);
168
183
  outputExt = ".js";
169
184
  mimeType = "application/javascript";
185
+ for (const file of jsOutputFiles) {
186
+ if (file === entryFile)
187
+ continue;
188
+ chunkFiles.push({
189
+ filename: basename(file.path),
190
+ content: Buffer.from(file.text)
191
+ });
192
+ }
170
193
  }
171
194
  } else if (needsCSSBundling) {
172
195
  const entryPath = args.path;
@@ -274,6 +297,23 @@ function assetsPlugin(options = {}) {
274
297
  }
275
298
  const outputPath = join(outputDir, filename);
276
299
  writeFileSync(outputPath, content);
300
+ for (const chunk of chunkFiles) {
301
+ const chunkPath = join(outputDir, chunk.filename);
302
+ writeFileSync(chunkPath, chunk.content);
303
+ const chunkUrl = `${basePath}${chunk.filename}`;
304
+ const chunkHash = createHash("sha256").update(chunk.content).digest("hex").slice(0, HASH_LENGTH);
305
+ manifest.assets[chunkUrl] = {
306
+ source: chunk.filename,
307
+ output: chunk.filename,
308
+ url: chunkUrl,
309
+ hash: chunkHash,
310
+ size: chunk.content.length,
311
+ type: "application/javascript"
312
+ };
313
+ if (sharedManifest) {
314
+ sharedManifest.assets[chunkUrl] = manifest.assets[chunkUrl];
315
+ }
316
+ }
277
317
  const sourcePath = relative(process.cwd(), args.path);
278
318
  const manifestEntry = {
279
319
  source: sourcePath,
@@ -475,6 +515,12 @@ function createAssetsManifestPlugin(projectRoot, outDir = "dist", sharedManifest
475
515
  namespace: "shovel-assets"
476
516
  }));
477
517
  build2.onLoad({ filter: /.*/, namespace: "shovel-assets" }, () => {
518
+ if (sharedManifest) {
519
+ return {
520
+ contents: `export default ${MANIFEST_PLACEHOLDER};`,
521
+ loader: "js"
522
+ };
523
+ }
478
524
  if (existsSync2(manifestPath)) {
479
525
  try {
480
526
  const manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ServerBundler,
3
3
  loadPlatformModule
4
- } from "./chunk-LVKYX67O.js";
4
+ } from "./chunk-WVHECOTO.js";
5
5
  import {
6
6
  DEFAULTS
7
7
  } from "./chunk-NZVIBZYG.js";