@b9g/shovel 0.2.6 → 0.2.7

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/create.js +124 -63
  3. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to Shovel will be documented in this file.
4
4
 
5
+ ## [0.2.7] - 2026-02-06
6
+
7
+ ### Features
8
+
9
+ - **Request logger middleware** - New `logger()` middleware in `@b9g/router/middleware` logs requests and responses with timing via LogTape (default category: `["app", "router"]`)
10
+ - **CLI flags for create-shovel** - `--template`, `--typescript`/`--no-typescript`, `--platform` flags to bypass interactive prompts. `--template crank` is shorthand for static-site + Crank.js.
11
+ - **Logger in generated templates** - All Router-based templates (api, full-stack) now include `router.use(logger())` out of the box
12
+
13
+ ### Dependencies
14
+
15
+ - **`@b9g/router`** `0.2.2` - Added `@logtape/logtape` as explicit dependency (was previously resolved via workspace only)
16
+
5
17
  ## [0.2.6] - 2026-02-06
6
18
 
7
19
  ### Features
package/bin/create.js CHANGED
@@ -20,10 +20,26 @@ function validateProjectName(name) {
20
20
  return "Use lowercase letters, numbers, and hyphens only";
21
21
  return void 0;
22
22
  }
23
+ function parseFlags(args) {
24
+ const flags = {};
25
+ for (let i = 0; i < args.length; i++) {
26
+ const arg = args[i];
27
+ if (arg === "--template" && args[i + 1])
28
+ flags.template = args[++i];
29
+ else if (arg === "--typescript")
30
+ flags.typescript = true;
31
+ else if (arg === "--no-typescript")
32
+ flags.typescript = false;
33
+ else if (arg === "--platform" && args[i + 1])
34
+ flags.platform = args[++i];
35
+ }
36
+ return flags;
37
+ }
23
38
  async function main() {
24
39
  console.info("");
25
40
  intro("Create Shovel App");
26
- let projectName = process.argv[2];
41
+ const flags = parseFlags(process.argv.slice(2));
42
+ let projectName = process.argv[2]?.startsWith("-") ? void 0 : process.argv[2];
27
43
  if (projectName) {
28
44
  const validationError = validateProjectName(projectName);
29
45
  if (validationError) {
@@ -52,37 +68,53 @@ async function main() {
52
68
  process.exit(0);
53
69
  }
54
70
  }
55
- const template = await select({
56
- message: "Choose a starter template:",
57
- options: [
58
- {
59
- value: "hello-world",
60
- label: "Hello World",
61
- hint: "Minimal fetch handler to get started"
62
- },
63
- {
64
- value: "api",
65
- label: "API",
66
- hint: "REST endpoints with JSON responses"
67
- },
68
- {
69
- value: "static-site",
70
- label: "Static Site",
71
- hint: "Server-rendered HTML pages"
72
- },
73
- {
74
- value: "full-stack",
75
- label: "Full Stack",
76
- hint: "HTML pages + API routes"
77
- }
78
- ]
79
- });
80
- if (typeof template === "symbol") {
81
- outro("Project creation cancelled");
82
- process.exit(0);
83
- }
71
+ let template;
84
72
  let uiFramework = "vanilla";
85
- if (template === "static-site" || template === "full-stack") {
73
+ if (flags.template === "crank") {
74
+ template = "static-site";
75
+ uiFramework = "crank";
76
+ } else if (flags.template) {
77
+ const valid = ["hello-world", "api", "static-site", "full-stack"];
78
+ if (!valid.includes(flags.template)) {
79
+ console.error(
80
+ `Error: Unknown template "${flags.template}". Valid options: ${valid.join(", ")}, crank`
81
+ );
82
+ process.exit(1);
83
+ }
84
+ template = flags.template;
85
+ } else {
86
+ const templateResult = await select({
87
+ message: "Choose a starter template:",
88
+ options: [
89
+ {
90
+ value: "hello-world",
91
+ label: "Hello World",
92
+ hint: "Minimal fetch handler to get started"
93
+ },
94
+ {
95
+ value: "api",
96
+ label: "API",
97
+ hint: "REST endpoints with JSON responses"
98
+ },
99
+ {
100
+ value: "static-site",
101
+ label: "Static Site",
102
+ hint: "Server-rendered HTML pages"
103
+ },
104
+ {
105
+ value: "full-stack",
106
+ label: "Full Stack",
107
+ hint: "HTML pages + API routes"
108
+ }
109
+ ]
110
+ });
111
+ if (typeof templateResult === "symbol") {
112
+ outro("Project creation cancelled");
113
+ process.exit(0);
114
+ }
115
+ template = templateResult;
116
+ }
117
+ if (uiFramework === "vanilla" && (template === "static-site" || template === "full-stack")) {
86
118
  const framework = await select({
87
119
  message: "UI framework:",
88
120
  initialValue: "crank",
@@ -115,39 +147,58 @@ async function main() {
115
147
  }
116
148
  uiFramework = framework;
117
149
  }
118
- const typescript = await confirm({
119
- message: "Use TypeScript?",
120
- initialValue: true
121
- });
122
- if (typeof typescript === "symbol") {
123
- outro("Project creation cancelled");
124
- process.exit(0);
150
+ let typescript;
151
+ if (flags.typescript !== void 0) {
152
+ typescript = flags.typescript;
153
+ } else {
154
+ const tsResult = await confirm({
155
+ message: "Use TypeScript?",
156
+ initialValue: true
157
+ });
158
+ if (typeof tsResult === "symbol") {
159
+ outro("Project creation cancelled");
160
+ process.exit(0);
161
+ }
162
+ typescript = tsResult;
125
163
  }
126
- const detectedPlatform = detectPlatform();
127
- const platform = await select({
128
- message: "Which platform?",
129
- initialValue: detectedPlatform,
130
- options: [
131
- {
132
- value: "node",
133
- label: "Node.js",
134
- hint: detectedPlatform === "node" ? "detected" : void 0
135
- },
136
- {
137
- value: "bun",
138
- label: "Bun",
139
- hint: detectedPlatform === "bun" ? "detected" : void 0
140
- },
141
- {
142
- value: "cloudflare",
143
- label: "Cloudflare Workers",
144
- hint: "Edge runtime"
145
- }
146
- ]
147
- });
148
- if (typeof platform === "symbol") {
149
- outro("Project creation cancelled");
150
- process.exit(0);
164
+ let platform;
165
+ if (flags.platform) {
166
+ const valid = ["node", "bun", "cloudflare"];
167
+ if (!valid.includes(flags.platform)) {
168
+ console.error(
169
+ `Error: Unknown platform "${flags.platform}". Valid options: ${valid.join(", ")}`
170
+ );
171
+ process.exit(1);
172
+ }
173
+ platform = flags.platform;
174
+ } else {
175
+ const detectedPlatform = detectPlatform();
176
+ const platformResult = await select({
177
+ message: "Which platform?",
178
+ initialValue: detectedPlatform,
179
+ options: [
180
+ {
181
+ value: "node",
182
+ label: "Node.js",
183
+ hint: detectedPlatform === "node" ? "detected" : void 0
184
+ },
185
+ {
186
+ value: "bun",
187
+ label: "Bun",
188
+ hint: detectedPlatform === "bun" ? "detected" : void 0
189
+ },
190
+ {
191
+ value: "cloudflare",
192
+ label: "Cloudflare Workers",
193
+ hint: "Edge runtime"
194
+ }
195
+ ]
196
+ });
197
+ if (typeof platformResult === "symbol") {
198
+ outro("Project creation cancelled");
199
+ process.exit(0);
200
+ }
201
+ platform = platformResult;
151
202
  }
152
203
  const config = {
153
204
  name: projectName,
@@ -287,8 +338,10 @@ self.addEventListener("fetch", (event) => {
287
338
  }
288
339
  function generateApi(config) {
289
340
  return `import { Router } from "@b9g/router";
341
+ import { logger } from "@b9g/router/middleware";
290
342
 
291
343
  const router = new Router();
344
+ router.use(logger());
292
345
 
293
346
  // In-memory data store
294
347
  const users = [
@@ -622,8 +675,10 @@ function generateFullStackVanilla(config) {
622
675
  const ext = config.typescript ? "ts" : "js";
623
676
  const t = config.typescript;
624
677
  return `import { Router } from "@b9g/router";
678
+ import { logger } from "@b9g/router/middleware";
625
679
 
626
680
  const router = new Router();
681
+ router.use(logger());
627
682
 
628
683
  // API routes
629
684
  router.route("/api/hello").get(() => {
@@ -688,8 +743,10 @@ function generateFullStackHtmx(config) {
688
743
  const ext = config.typescript ? "ts" : "js";
689
744
  const t = config.typescript;
690
745
  return `import { Router } from "@b9g/router";
746
+ import { logger } from "@b9g/router/middleware";
691
747
 
692
748
  const router = new Router();
749
+ router.use(logger());
693
750
 
694
751
  // API routes \u2014 return HTML fragments when HTMX requests, JSON otherwise
695
752
  router.route("/api/hello").get((req) => {
@@ -764,8 +821,10 @@ function generateFullStackAlpine(config) {
764
821
  const ext = config.typescript ? "ts" : "js";
765
822
  const t = config.typescript;
766
823
  return `import { Router } from "@b9g/router";
824
+ import { logger } from "@b9g/router/middleware";
767
825
 
768
826
  const router = new Router();
827
+ router.use(logger());
769
828
 
770
829
  // API routes
771
830
  router.route("/api/hello").get(() => {
@@ -837,9 +896,11 @@ self.addEventListener("fetch", (event) => {
837
896
  function generateFullStackCrank(config) {
838
897
  const t = config.typescript;
839
898
  return `import { Router } from "@b9g/router";
899
+ import { logger } from "@b9g/router/middleware";
840
900
  import {renderer} from "@b9g/crank/html";
841
901
 
842
902
  const router = new Router();
903
+ router.use(logger());
843
904
 
844
905
  const css = \`
845
906
  ${css}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/shovel",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
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
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "@b9g/assets": "^0.2.1",
37
37
  "@b9g/crank": "^0.7.2",
38
38
  "@b9g/libuild": "^0.1.22",
39
- "@b9g/router": "^0.2.1",
39
+ "@b9g/router": "^0.2.2",
40
40
  "@logtape/file": "^1.0.0",
41
41
  "@types/bun": "^1.3.4",
42
42
  "@typescript-eslint/eslint-plugin": "^8.0.0",