@jlmx/starter 1.1.2 → 1.2.0
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/README.md +39 -11
- package/dist/index.js +162 -85
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,19 +1,47 @@
|
|
|
1
1
|
# @jlmx/starter
|
|
2
2
|
|
|
3
|
-
CLI to scaffold new projects with my preferred stack.
|
|
3
|
+
CLI to scaffold new projects with my preferred stack, optimized for Cloudflare deployment.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
|
+
Interactive mode — prompts for all options:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bunx @jlmx/starter
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Pass a project name to skip the name prompt:
|
|
14
|
+
|
|
7
15
|
```bash
|
|
8
16
|
bunx @jlmx/starter my-app
|
|
9
17
|
```
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
One-liner with flags — fully non-interactive:
|
|
12
20
|
|
|
13
21
|
```bash
|
|
14
|
-
bunx @jlmx/starter
|
|
22
|
+
bunx @jlmx/starter my-app --template nextjs --features tailwind,fonts,cloudflare --bindings d1,r2 --ai claude
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Accept all defaults with no prompts:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bunx @jlmx/starter my-app --yes
|
|
15
29
|
```
|
|
16
30
|
|
|
31
|
+
## Options
|
|
32
|
+
|
|
33
|
+
| Flag | Values | Description |
|
|
34
|
+
|------|--------|-------------|
|
|
35
|
+
| `--template` | `tanstack-start`, `nextjs` | Framework template |
|
|
36
|
+
| `--features` | `tailwind,fonts,auth,cloudflare` | Comma-separated feature list |
|
|
37
|
+
| `--bindings` | `d1,r2,kv,ai,queues` | Cloudflare bindings (requires `cloudflare` feature) |
|
|
38
|
+
| `--ai` | `none`, `agents`, `claude`, `both` | AI coding assistant instructions |
|
|
39
|
+
| `-y, --yes` | — | Accept all defaults, skip all prompts |
|
|
40
|
+
| `-h, --help` | — | Show help |
|
|
41
|
+
| `-v, --version` | — | Show version |
|
|
42
|
+
|
|
43
|
+
Defaults (used with `--yes`): `tanstack-start`, features `tailwind,fonts,cloudflare`, no bindings, no AI instructions.
|
|
44
|
+
|
|
17
45
|
## Templates
|
|
18
46
|
|
|
19
47
|
- **TanStack Start** - Full-stack React with TanStack Router
|
|
@@ -22,28 +50,28 @@ bunx @jlmx/starter
|
|
|
22
50
|
## Features
|
|
23
51
|
|
|
24
52
|
All projects include:
|
|
25
|
-
- **Tailwind CSS** - Custom design system with `tailwindcss-animate`
|
|
26
|
-
- **Custom Fonts** - Inter + JetBrains Mono via Fontsource
|
|
27
53
|
- **Biome** - Fast linting and formatting (replaces ESLint + Prettier)
|
|
28
54
|
- **Logger** - Structured logging utility
|
|
29
55
|
- **Result Type** - Explicit error handling (neverthrow pattern)
|
|
30
56
|
|
|
31
57
|
Optional:
|
|
58
|
+
- **Tailwind CSS** - Custom design system with `tailwindcss-animate`
|
|
59
|
+
- **Custom Fonts** - Inter + JetBrains Mono via Fontsource
|
|
32
60
|
- **Authentication** - Better Auth + Drizzle ORM
|
|
33
61
|
- **Cloudflare Workers** - Edge deployment with D1, R2, KV, AI, Queues
|
|
34
62
|
- **AI Instructions** - AGENTS.md and/or CLAUDE.md for coding assistants
|
|
35
63
|
|
|
36
64
|
## Cloudflare Bindings
|
|
37
65
|
|
|
38
|
-
When
|
|
66
|
+
When `cloudflare` is selected, you can enable:
|
|
39
67
|
|
|
40
68
|
| Binding | Description |
|
|
41
69
|
|---------|-------------|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
70
|
+
| `d1` | SQLite database at the edge |
|
|
71
|
+
| `r2` | S3-compatible object storage |
|
|
72
|
+
| `kv` | Key-value storage |
|
|
73
|
+
| `ai` | Workers AI models |
|
|
74
|
+
| `queues` | Message queues |
|
|
47
75
|
|
|
48
76
|
## Development
|
|
49
77
|
|
package/dist/index.js
CHANGED
|
@@ -181,12 +181,18 @@ Usage:
|
|
|
181
181
|
bunx @jlmx/starter [project-name] [options]
|
|
182
182
|
|
|
183
183
|
Options:
|
|
184
|
-
-h, --help
|
|
185
|
-
-v, --version
|
|
184
|
+
-h, --help Show this help message
|
|
185
|
+
-v, --version Show version number
|
|
186
|
+
-y, --yes Accept defaults (no prompts)
|
|
187
|
+
--template <name> tanstack-start | nextjs
|
|
188
|
+
--features <list> Comma-separated: tailwind,fonts,auth,cloudflare
|
|
189
|
+
--bindings <list> Comma-separated: d1,r2,kv,ai,queues
|
|
190
|
+
--ai <value> none | agents | claude | both
|
|
186
191
|
|
|
187
192
|
Examples:
|
|
188
193
|
bunx @jlmx/starter my-app
|
|
189
|
-
bunx @jlmx/starter
|
|
194
|
+
bunx @jlmx/starter my-app --template nextjs --features tailwind,fonts,cloudflare --bindings d1,r2 --ai claude
|
|
195
|
+
bunx @jlmx/starter my-app --yes
|
|
190
196
|
|
|
191
197
|
Templates:
|
|
192
198
|
tanstack-start Full-stack React with TanStack Router
|
|
@@ -215,10 +221,19 @@ async function main() {
|
|
|
215
221
|
console.info(VERSION);
|
|
216
222
|
process.exit(0);
|
|
217
223
|
}
|
|
224
|
+
const useDefaults = args.includes("-y") || args.includes("--yes");
|
|
225
|
+
const DEFAULT_TEMPLATE = "tanstack-start";
|
|
226
|
+
const DEFAULT_FEATURES = ["tailwind", "fonts", "cloudflare"];
|
|
227
|
+
const DEFAULT_BINDINGS = [];
|
|
228
|
+
const DEFAULT_AI = "none";
|
|
218
229
|
console.clear();
|
|
219
230
|
p.intro("@jlmx/starter");
|
|
220
231
|
let projectName = args.find((arg) => !arg.startsWith("-"));
|
|
221
232
|
if (!projectName) {
|
|
233
|
+
if (useDefaults) {
|
|
234
|
+
p.log.error("--yes requires a project name as the first argument");
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
222
237
|
const nameResult = await p.text({
|
|
223
238
|
message: "Project name:",
|
|
224
239
|
placeholder: "my-app",
|
|
@@ -235,40 +250,69 @@ async function main() {
|
|
|
235
250
|
}
|
|
236
251
|
projectName = nameResult;
|
|
237
252
|
}
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
253
|
+
const templateFlag = getFlagValue(args, "--template");
|
|
254
|
+
const validTemplates = ["tanstack-start", "nextjs"];
|
|
255
|
+
let selectedTemplate;
|
|
256
|
+
if (templateFlag) {
|
|
257
|
+
if (!validTemplates.includes(templateFlag)) {
|
|
258
|
+
p.log.error(`Invalid template "${templateFlag}". Choose: ${validTemplates.join(", ")}`);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
selectedTemplate = templateFlag;
|
|
262
|
+
} else if (useDefaults) {
|
|
263
|
+
selectedTemplate = DEFAULT_TEMPLATE;
|
|
264
|
+
} else {
|
|
265
|
+
const template = await p.select({
|
|
266
|
+
message: "Select a template:",
|
|
267
|
+
options: [
|
|
268
|
+
{
|
|
269
|
+
value: "tanstack-start",
|
|
270
|
+
label: "TanStack Start",
|
|
271
|
+
hint: "Full-stack React with TanStack Router"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
value: "nextjs",
|
|
275
|
+
label: "Next.js",
|
|
276
|
+
hint: "Full-stack React with App Router"
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
});
|
|
280
|
+
if (p.isCancel(template)) {
|
|
281
|
+
p.cancel("Cancelled");
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}
|
|
284
|
+
selectedTemplate = template;
|
|
256
285
|
}
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
286
|
+
const featuresFlag = getFlagValue(args, "--features");
|
|
287
|
+
const validFeatures = ["tailwind", "fonts", "auth", "cloudflare"];
|
|
288
|
+
let selectedFeatures;
|
|
289
|
+
if (featuresFlag) {
|
|
290
|
+
const parsed = featuresFlag.split(",").map((f) => f.trim());
|
|
291
|
+
const invalid = parsed.filter((f) => !validFeatures.includes(f));
|
|
292
|
+
if (invalid.length > 0) {
|
|
293
|
+
p.log.error(`Invalid features: ${invalid.join(", ")}. Choose from: ${validFeatures.join(", ")}`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
selectedFeatures = parsed;
|
|
297
|
+
} else if (useDefaults) {
|
|
298
|
+
selectedFeatures = [...DEFAULT_FEATURES];
|
|
299
|
+
} else {
|
|
300
|
+
const features = await p.multiselect({
|
|
301
|
+
message: "Select features:",
|
|
302
|
+
options: [
|
|
303
|
+
{ value: "tailwind", label: "Tailwind CSS", hint: "design system + tailwindcss-animate" },
|
|
304
|
+
{ value: "fonts", label: "Custom Fonts", hint: "Inter + JetBrains Mono" },
|
|
305
|
+
{ value: "auth", label: "Authentication", hint: "Better Auth + D1" },
|
|
306
|
+
{ value: "cloudflare", label: "Cloudflare Workers", hint: "deployment config" }
|
|
307
|
+
],
|
|
308
|
+
initialValues: ["tailwind", "fonts", "cloudflare"]
|
|
309
|
+
});
|
|
310
|
+
if (p.isCancel(features)) {
|
|
311
|
+
p.cancel("Cancelled");
|
|
312
|
+
process.exit(0);
|
|
313
|
+
}
|
|
314
|
+
selectedFeatures = features;
|
|
270
315
|
}
|
|
271
|
-
const selectedFeatures = features;
|
|
272
316
|
let cfBindings = [];
|
|
273
317
|
if (selectedFeatures.includes("auth")) {
|
|
274
318
|
if (!selectedFeatures.includes("cloudflare")) {
|
|
@@ -277,65 +321,91 @@ async function main() {
|
|
|
277
321
|
}
|
|
278
322
|
}
|
|
279
323
|
if (selectedFeatures.includes("cloudflare")) {
|
|
324
|
+
const bindingsFlag = getFlagValue(args, "--bindings");
|
|
325
|
+
const validBindings = ["d1", "r2", "kv", "ai", "queues"];
|
|
280
326
|
const needsD1 = selectedFeatures.includes("auth");
|
|
281
|
-
|
|
282
|
-
|
|
327
|
+
if (bindingsFlag !== undefined) {
|
|
328
|
+
const parsed = bindingsFlag === "" ? [] : bindingsFlag.split(",").map((b) => b.trim());
|
|
329
|
+
const invalid = parsed.filter((b) => !validBindings.includes(b));
|
|
330
|
+
if (invalid.length > 0) {
|
|
331
|
+
p.log.error(`Invalid bindings: ${invalid.join(", ")}. Choose from: ${validBindings.join(", ")}`);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
cfBindings = parsed;
|
|
335
|
+
} else if (useDefaults) {
|
|
336
|
+
cfBindings = [...DEFAULT_BINDINGS];
|
|
337
|
+
} else {
|
|
338
|
+
const bindings = await p.multiselect({
|
|
339
|
+
message: "Cloudflare platform features:",
|
|
340
|
+
options: [
|
|
341
|
+
{
|
|
342
|
+
value: "d1",
|
|
343
|
+
label: "D1 Database",
|
|
344
|
+
hint: needsD1 ? "required for auth" : "SQLite at the edge"
|
|
345
|
+
},
|
|
346
|
+
{ value: "r2", label: "R2 Storage", hint: "S3-compatible object storage" },
|
|
347
|
+
{ value: "kv", label: "KV Store", hint: "key-value storage" },
|
|
348
|
+
{ value: "ai", label: "Workers AI", hint: "run AI models" },
|
|
349
|
+
{ value: "queues", label: "Queues", hint: "message queues" }
|
|
350
|
+
],
|
|
351
|
+
initialValues: needsD1 ? ["d1"] : [],
|
|
352
|
+
required: false
|
|
353
|
+
});
|
|
354
|
+
if (p.isCancel(bindings)) {
|
|
355
|
+
p.cancel("Cancelled");
|
|
356
|
+
process.exit(0);
|
|
357
|
+
}
|
|
358
|
+
cfBindings = bindings;
|
|
359
|
+
}
|
|
360
|
+
if (needsD1 && !cfBindings.includes("d1")) {
|
|
361
|
+
cfBindings.push("d1");
|
|
362
|
+
p.log.info("D1 Database enabled (required for auth)");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const aiFlag = getFlagValue(args, "--ai");
|
|
366
|
+
const validAi = ["none", "agents", "claude", "both"];
|
|
367
|
+
let selectedAiInstructions;
|
|
368
|
+
if (aiFlag) {
|
|
369
|
+
if (!validAi.includes(aiFlag)) {
|
|
370
|
+
p.log.error(`Invalid --ai value "${aiFlag}". Choose: ${validAi.join(", ")}`);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
selectedAiInstructions = aiFlag;
|
|
374
|
+
} else if (useDefaults) {
|
|
375
|
+
selectedAiInstructions = DEFAULT_AI;
|
|
376
|
+
} else {
|
|
377
|
+
const aiInstructions = await p.select({
|
|
378
|
+
message: "AI coding assistant instructions:",
|
|
283
379
|
options: [
|
|
284
380
|
{
|
|
285
|
-
value: "
|
|
286
|
-
label: "
|
|
287
|
-
hint:
|
|
381
|
+
value: "none",
|
|
382
|
+
label: "None",
|
|
383
|
+
hint: "skip AI instructions"
|
|
288
384
|
},
|
|
289
|
-
{
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
385
|
+
{
|
|
386
|
+
value: "agents",
|
|
387
|
+
label: "AGENTS.md",
|
|
388
|
+
hint: "generic (Codex, Cursor, etc.)"
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
value: "claude",
|
|
392
|
+
label: "CLAUDE.md",
|
|
393
|
+
hint: "Claude Code specific"
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
value: "both",
|
|
397
|
+
label: "Both",
|
|
398
|
+
hint: "AGENTS.md + CLAUDE.md symlink"
|
|
399
|
+
}
|
|
400
|
+
]
|
|
296
401
|
});
|
|
297
|
-
if (p.isCancel(
|
|
402
|
+
if (p.isCancel(aiInstructions)) {
|
|
298
403
|
p.cancel("Cancelled");
|
|
299
404
|
process.exit(0);
|
|
300
405
|
}
|
|
301
|
-
|
|
302
|
-
if (needsD1 && !cfBindings.includes("d1")) {
|
|
303
|
-
cfBindings.push("d1");
|
|
304
|
-
p.log.info("D1 Database enabled (required for auth)");
|
|
305
|
-
}
|
|
406
|
+
selectedAiInstructions = aiInstructions;
|
|
306
407
|
}
|
|
307
|
-
const aiInstructions = await p.select({
|
|
308
|
-
message: "AI coding assistant instructions:",
|
|
309
|
-
options: [
|
|
310
|
-
{
|
|
311
|
-
value: "none",
|
|
312
|
-
label: "None",
|
|
313
|
-
hint: "skip AI instructions"
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
value: "agents",
|
|
317
|
-
label: "AGENTS.md",
|
|
318
|
-
hint: "generic (Codex, Cursor, etc.)"
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
value: "claude",
|
|
322
|
-
label: "CLAUDE.md",
|
|
323
|
-
hint: "Claude Code specific"
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
value: "both",
|
|
327
|
-
label: "Both",
|
|
328
|
-
hint: "AGENTS.md + CLAUDE.md symlink"
|
|
329
|
-
}
|
|
330
|
-
]
|
|
331
|
-
});
|
|
332
|
-
if (p.isCancel(aiInstructions)) {
|
|
333
|
-
p.cancel("Cancelled");
|
|
334
|
-
process.exit(0);
|
|
335
|
-
}
|
|
336
|
-
const selectedAiInstructions = aiInstructions;
|
|
337
408
|
const projectPath = resolve(process.cwd(), projectName);
|
|
338
|
-
const selectedTemplate = template;
|
|
339
409
|
const s = p.spinner();
|
|
340
410
|
if (selectedTemplate === "tanstack-start") {
|
|
341
411
|
s.start("Creating TanStack Start project...");
|
|
@@ -617,4 +687,11 @@ ${envVarPrefix}BETTER_AUTH_URL=http://localhost:3000
|
|
|
617
687
|
|
|
618
688
|
${nextSteps}`);
|
|
619
689
|
}
|
|
620
|
-
main().catch(
|
|
690
|
+
main().catch((err) => {
|
|
691
|
+
if (err instanceof Error) {
|
|
692
|
+
console.error(`Error: ${err.message}`);
|
|
693
|
+
} else {
|
|
694
|
+
console.error(err);
|
|
695
|
+
}
|
|
696
|
+
process.exit(1);
|
|
697
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jlmx/starter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Scaffold new projects with jlmx's preferred stack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "bun build ./src/index.ts --outdir ./dist --target bun --packages external",
|
|
16
16
|
"dev": "bun run ./src/index.ts",
|
|
17
|
+
"test": "bun test",
|
|
17
18
|
"lint": "biome check .",
|
|
18
19
|
"lint:fix": "biome check --write .",
|
|
19
20
|
"format": "biome format --write .",
|