create-bw-app 0.3.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.
Files changed (41) hide show
  1. package/README.md +34 -0
  2. package/bin/create-bw-app.mjs +9 -0
  3. package/package.json +27 -0
  4. package/src/cli.mjs +78 -0
  5. package/src/constants.mjs +114 -0
  6. package/src/generator.mjs +821 -0
  7. package/template/base/app/bootstrap/page.tsx +75 -0
  8. package/template/base/app/globals.css +545 -0
  9. package/template/base/app/layout.tsx +16 -0
  10. package/template/base/app/page.tsx +144 -0
  11. package/template/base/app/playground/auth/auth-playground.tsx +112 -0
  12. package/template/base/app/playground/auth/page.tsx +5 -0
  13. package/template/base/app/playground/layout.tsx +41 -0
  14. package/template/base/app/preview/app-shell/page.tsx +11 -0
  15. package/template/base/app/preview/app-shell-preview.tsx +185 -0
  16. package/template/base/config/bootstrap.ts +125 -0
  17. package/template/base/config/brand.ts +21 -0
  18. package/template/base/config/client.ts +18 -0
  19. package/template/base/config/env.ts +64 -0
  20. package/template/base/config/modules.ts +62 -0
  21. package/template/base/next-env.d.ts +6 -0
  22. package/template/base/public/brand/logo-dark.svg +7 -0
  23. package/template/base/public/brand/logo-light.svg +7 -0
  24. package/template/base/public/brand/logo-mark.svg +5 -0
  25. package/template/base/tsconfig.json +36 -0
  26. package/template/modules/admin/app/api/admin/users/roles/route.ts +6 -0
  27. package/template/modules/admin/app/api/admin/users/route.ts +6 -0
  28. package/template/modules/admin/app/playground/admin/page.tsx +102 -0
  29. package/template/modules/crm/app/playground/crm/page.tsx +103 -0
  30. package/template/modules/projects/app/playground/projects/page.tsx +93 -0
  31. package/template/site/base/app/globals.css +68 -0
  32. package/template/site/base/app/layout.tsx +17 -0
  33. package/template/site/base/app/page.tsx +165 -0
  34. package/template/site/base/components/ui/badge.tsx +28 -0
  35. package/template/site/base/components/ui/button.tsx +52 -0
  36. package/template/site/base/components/ui/card.tsx +28 -0
  37. package/template/site/base/components.json +17 -0
  38. package/template/site/base/lib/utils.ts +6 -0
  39. package/template/site/base/next-env.d.ts +6 -0
  40. package/template/site/base/postcss.config.mjs +7 -0
  41. package/template/site/base/tsconfig.json +23 -0
@@ -0,0 +1,75 @@
1
+ import Link from "next/link";
2
+ import { getStarterBootstrapChecklist } from "../../config/bootstrap";
3
+
4
+ export default function BootstrapPage() {
5
+ const checklist = getStarterBootstrapChecklist();
6
+
7
+ return (
8
+ <main className="shell">
9
+ <div className="frame">
10
+ <section className="hero">
11
+ <span className="eyebrow">Client Bootstrap</span>
12
+ <h1 className="title">Launch checklist for {checklist.client.brand.companyName}</h1>
13
+ <p className="lead">
14
+ This page turns the starter config into an operational checklist for provisioning a new client instance.
15
+ </p>
16
+ <div className="actions">
17
+ <Link href="/" className="action">Back to overview</Link>
18
+ <Link href="/playground/auth" className="action secondary">Open playgrounds</Link>
19
+ </div>
20
+ </section>
21
+
22
+ <section className="panel">
23
+ <div className="panel-inner">
24
+ <h2>Client summary</h2>
25
+ <div className="grid">
26
+ <article className="panel" style={{ background: "rgba(255,255,255,0.72)" }}>
27
+ <div className="panel-inner">
28
+ <p className="status ok">{checklist.client.brand.slug}</p>
29
+ <h3>{checklist.client.brand.productName}</h3>
30
+ <p className="muted">{checklist.client.brand.tagline}</p>
31
+ </div>
32
+ </article>
33
+ <article className="panel" style={{ background: "rgba(255,255,255,0.72)" }}>
34
+ <div className="panel-inner">
35
+ <p className={`status ${checklist.client.envReadiness.allReady ? "ok" : "warn"}`}>
36
+ {checklist.client.envReadiness.allReady ? "Ready" : "Blocked"}
37
+ </p>
38
+ <h3>Environment status</h3>
39
+ <p className="muted">
40
+ {checklist.client.envReadiness.allReady
41
+ ? "All required environment keys are configured."
42
+ : `${checklist.client.envReadiness.missing.length} required key(s) still missing.`}
43
+ </p>
44
+ </div>
45
+ </article>
46
+ </div>
47
+ </div>
48
+ </section>
49
+
50
+ <section className="grid" style={{ marginTop: 18 }}>
51
+ {checklist.sections.map((section) => (
52
+ <article key={section.key} className="panel">
53
+ <div className="panel-inner">
54
+ <h2>{section.title}</h2>
55
+ <ul className="list">
56
+ {section.items.map((item) => (
57
+ <li key={item.label}>
58
+ <div className="check-row">
59
+ <span className={`check-dot ${item.done ? "done" : "pending"}`} aria-hidden="true" />
60
+ <div>
61
+ <strong>{item.label}</strong>
62
+ {item.detail ? <p className="muted inline-detail">{item.detail}</p> : null}
63
+ </div>
64
+ </div>
65
+ </li>
66
+ ))}
67
+ </ul>
68
+ </div>
69
+ </article>
70
+ ))}
71
+ </section>
72
+ </div>
73
+ </main>
74
+ );
75
+ }
@@ -0,0 +1,545 @@
1
+ :root {
2
+ color-scheme: light;
3
+ --bg: #f3efe5;
4
+ --panel: rgba(255, 255, 255, 0.82);
5
+ --panel-strong: #ffffff;
6
+ --text: #18241d;
7
+ --muted: #5a665c;
8
+ --line: rgba(24, 36, 29, 0.12);
9
+ --accent: #266946;
10
+ --accent-soft: rgba(38, 105, 70, 0.12);
11
+ --danger: #b43f3f;
12
+ --ink-soft: #eef1e8;
13
+ }
14
+
15
+ * {
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ html,
20
+ body {
21
+ margin: 0;
22
+ padding: 0;
23
+ background:
24
+ radial-gradient(circle at 15% 10%, rgba(126, 170, 112, 0.18), transparent 22%),
25
+ radial-gradient(circle at 85% 12%, rgba(189, 140, 89, 0.12), transparent 18%),
26
+ linear-gradient(180deg, #faf8f1 0%, var(--bg) 48%, #e8e0cf 100%);
27
+ color: var(--text);
28
+ font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
29
+ }
30
+
31
+ a {
32
+ color: inherit;
33
+ text-decoration: none;
34
+ }
35
+
36
+ button,
37
+ input {
38
+ font: inherit;
39
+ }
40
+
41
+ .shell {
42
+ min-height: 100vh;
43
+ padding: 32px 20px 56px;
44
+ }
45
+
46
+ .frame {
47
+ margin: 0 auto;
48
+ max-width: 1120px;
49
+ }
50
+
51
+ .starter-home .frame {
52
+ position: relative;
53
+ }
54
+
55
+ .hero {
56
+ display: grid;
57
+ gap: 16px;
58
+ margin-bottom: 28px;
59
+ }
60
+
61
+ .starter-hero {
62
+ display: grid;
63
+ grid-template-columns: minmax(0, 1.25fr) minmax(280px, 0.75fr);
64
+ gap: 20px;
65
+ align-items: stretch;
66
+ margin-bottom: 28px;
67
+ }
68
+
69
+ .starter-hero-copy {
70
+ position: relative;
71
+ display: grid;
72
+ gap: 16px;
73
+ padding: 30px 0 8px;
74
+ }
75
+
76
+ .starter-hero-copy::before {
77
+ content: "";
78
+ position: absolute;
79
+ inset: 0 0 auto 0;
80
+ height: 1px;
81
+ background: linear-gradient(90deg, rgba(24, 36, 29, 0.35), transparent);
82
+ }
83
+
84
+ .starter-hero-card {
85
+ position: relative;
86
+ overflow: hidden;
87
+ background:
88
+ linear-gradient(180deg, rgba(255, 255, 255, 0.86), rgba(247, 244, 235, 0.84)),
89
+ radial-gradient(circle at top right, rgba(38, 105, 70, 0.18), transparent 45%);
90
+ }
91
+
92
+ .starter-hero-card::after {
93
+ content: "";
94
+ position: absolute;
95
+ inset: auto -36px -36px auto;
96
+ width: 150px;
97
+ height: 150px;
98
+ border-radius: 999px;
99
+ background: rgba(38, 105, 70, 0.08);
100
+ }
101
+
102
+ .starter-stat-grid {
103
+ display: grid;
104
+ grid-template-columns: repeat(2, minmax(0, 1fr));
105
+ gap: 16px;
106
+ margin-top: 18px;
107
+ }
108
+
109
+ .stat-number {
110
+ display: block;
111
+ font-family: Georgia, "Times New Roman", serif;
112
+ font-size: clamp(2rem, 4vw, 3rem);
113
+ line-height: 1;
114
+ letter-spacing: -0.04em;
115
+ }
116
+
117
+ .starter-hero-note {
118
+ margin-top: 24px;
119
+ padding-top: 16px;
120
+ border-top: 1px solid var(--line);
121
+ }
122
+
123
+ .eyebrow {
124
+ display: inline-flex;
125
+ width: fit-content;
126
+ padding: 6px 10px;
127
+ border-radius: 999px;
128
+ background: var(--accent-soft);
129
+ color: var(--accent);
130
+ font-size: 12px;
131
+ font-weight: 700;
132
+ letter-spacing: 0.04em;
133
+ text-transform: uppercase;
134
+ }
135
+
136
+ .title {
137
+ margin: 0;
138
+ font-family: Georgia, "Times New Roman", serif;
139
+ font-size: clamp(2.4rem, 5.5vw, 4.8rem);
140
+ line-height: 0.92;
141
+ letter-spacing: -0.04em;
142
+ }
143
+
144
+ .lead {
145
+ max-width: 760px;
146
+ margin: 0;
147
+ color: var(--muted);
148
+ font-size: 18px;
149
+ line-height: 1.6;
150
+ }
151
+
152
+ .grid {
153
+ display: grid;
154
+ gap: 18px;
155
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
156
+ }
157
+
158
+ .starter-signal-grid {
159
+ grid-template-columns: repeat(2, minmax(0, 1fr));
160
+ }
161
+
162
+ .panel {
163
+ border: 1px solid var(--line);
164
+ border-radius: 24px;
165
+ background: var(--panel);
166
+ backdrop-filter: blur(10px);
167
+ box-shadow: 0 18px 50px rgba(23, 34, 22, 0.08);
168
+ }
169
+
170
+ .panel-inner {
171
+ padding: 22px;
172
+ }
173
+
174
+ .preview-glass-card {
175
+ background:
176
+ linear-gradient(180deg, rgba(255, 255, 255, 0.84), rgba(246, 244, 236, 0.76)),
177
+ radial-gradient(circle at top right, rgba(38, 105, 70, 0.08), transparent 45%);
178
+ }
179
+
180
+ .panel h2,
181
+ .panel h3,
182
+ .panel p {
183
+ margin-top: 0;
184
+ }
185
+
186
+ .panel p:last-child {
187
+ margin-bottom: 0;
188
+ }
189
+
190
+ .muted {
191
+ color: var(--muted);
192
+ }
193
+
194
+ .list {
195
+ display: grid;
196
+ gap: 10px;
197
+ margin: 0;
198
+ padding: 0;
199
+ list-style: none;
200
+ }
201
+
202
+ .list li {
203
+ padding: 12px 14px;
204
+ border-radius: 16px;
205
+ background: rgba(255, 255, 255, 0.78);
206
+ border: 1px solid rgba(23, 34, 22, 0.08);
207
+ }
208
+
209
+ .actions {
210
+ display: flex;
211
+ flex-wrap: wrap;
212
+ gap: 12px;
213
+ margin-top: 10px;
214
+ }
215
+
216
+ .action {
217
+ display: inline-flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ padding: 12px 16px;
221
+ border-radius: 14px;
222
+ background: var(--text);
223
+ color: white;
224
+ font-weight: 600;
225
+ }
226
+
227
+ .action.secondary {
228
+ background: white;
229
+ color: var(--text);
230
+ border: 1px solid var(--line);
231
+ }
232
+
233
+ .inline-link {
234
+ display: inline-flex;
235
+ margin-top: 12px;
236
+ color: var(--accent);
237
+ font-weight: 700;
238
+ }
239
+
240
+ .package-name {
241
+ margin-top: 12px;
242
+ color: var(--muted);
243
+ font-size: 13px;
244
+ font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
245
+ }
246
+
247
+ .check-row {
248
+ display: flex;
249
+ gap: 12px;
250
+ align-items: flex-start;
251
+ }
252
+
253
+ .check-dot {
254
+ width: 12px;
255
+ height: 12px;
256
+ margin-top: 5px;
257
+ border-radius: 999px;
258
+ flex: 0 0 auto;
259
+ }
260
+
261
+ .check-dot.done {
262
+ background: var(--accent);
263
+ }
264
+
265
+ .check-dot.pending {
266
+ background: #c99925;
267
+ }
268
+
269
+ .inline-detail {
270
+ margin-top: 6px;
271
+ margin-bottom: 0;
272
+ }
273
+
274
+ .playground-layout {
275
+ display: grid;
276
+ gap: 18px;
277
+ grid-template-columns: 280px 1fr;
278
+ align-items: start;
279
+ }
280
+
281
+ .shell-preview-page {
282
+ padding-top: 22px;
283
+ }
284
+
285
+ .app-preview-shell {
286
+ display: grid;
287
+ grid-template-columns: 320px minmax(0, 1fr);
288
+ gap: 20px;
289
+ align-items: start;
290
+ }
291
+
292
+ .app-preview-sidebar {
293
+ position: sticky;
294
+ top: 24px;
295
+ min-height: calc(100vh - 72px);
296
+ border: 1px solid var(--line);
297
+ border-radius: 28px;
298
+ background:
299
+ linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(241, 237, 226, 0.84)),
300
+ radial-gradient(circle at top, rgba(38, 105, 70, 0.12), transparent 34%);
301
+ backdrop-filter: blur(16px);
302
+ box-shadow: 0 22px 60px rgba(24, 36, 29, 0.1);
303
+ }
304
+
305
+ .app-preview-main {
306
+ display: grid;
307
+ gap: 18px;
308
+ }
309
+
310
+ .app-preview-header {
311
+ display: flex;
312
+ justify-content: space-between;
313
+ gap: 20px;
314
+ align-items: flex-start;
315
+ padding: 24px 26px;
316
+ border: 1px solid var(--line);
317
+ border-radius: 28px;
318
+ background:
319
+ linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(246, 243, 235, 0.86)),
320
+ radial-gradient(circle at right top, rgba(38, 105, 70, 0.14), transparent 35%);
321
+ backdrop-filter: blur(16px);
322
+ }
323
+
324
+ .app-preview-header-copy h1 {
325
+ margin-bottom: 8px;
326
+ font-family: Georgia, "Times New Roman", serif;
327
+ font-size: clamp(2rem, 4vw, 3rem);
328
+ line-height: 0.96;
329
+ letter-spacing: -0.04em;
330
+ }
331
+
332
+ .app-preview-header-actions {
333
+ display: flex;
334
+ gap: 10px;
335
+ align-items: center;
336
+ }
337
+
338
+ .app-preview-mobile-nav {
339
+ display: none;
340
+ }
341
+
342
+ .app-preview-content {
343
+ display: grid;
344
+ }
345
+
346
+ .app-preview-stage {
347
+ display: grid;
348
+ gap: 18px;
349
+ }
350
+
351
+ .preview-card-icon {
352
+ display: inline-flex;
353
+ margin-bottom: 16px;
354
+ width: 38px;
355
+ height: 38px;
356
+ align-items: center;
357
+ justify-content: center;
358
+ border-radius: 14px;
359
+ background: var(--ink-soft);
360
+ color: var(--accent);
361
+ }
362
+
363
+ .preview-stage-panel {
364
+ background:
365
+ linear-gradient(180deg, rgba(23, 37, 29, 0.96), rgba(18, 30, 24, 0.95)),
366
+ radial-gradient(circle at top left, rgba(113, 196, 139, 0.15), transparent 34%);
367
+ color: #f4f1e8;
368
+ border-color: rgba(255, 255, 255, 0.1);
369
+ }
370
+
371
+ .preview-stage-panel .muted {
372
+ color: rgba(244, 241, 232, 0.72);
373
+ }
374
+
375
+ .preview-stage-head {
376
+ display: flex;
377
+ justify-content: space-between;
378
+ gap: 16px;
379
+ align-items: flex-start;
380
+ margin-bottom: 24px;
381
+ }
382
+
383
+ .preview-stage-head h2 {
384
+ margin-bottom: 0;
385
+ font-size: 1.75rem;
386
+ }
387
+
388
+ .nav-chip {
389
+ border: 1px solid rgba(255, 255, 255, 0.12);
390
+ background: rgba(255, 255, 255, 0.04);
391
+ color: #f4f1e8;
392
+ border-radius: 999px;
393
+ padding: 10px 14px;
394
+ font-size: 12px;
395
+ font-weight: 700;
396
+ transition: 160ms ease;
397
+ }
398
+
399
+ .nav-chip.active,
400
+ .nav-chip:hover {
401
+ border-color: rgba(113, 196, 139, 0.5);
402
+ background: rgba(113, 196, 139, 0.14);
403
+ }
404
+
405
+ .preview-surface-grid {
406
+ display: grid;
407
+ gap: 14px;
408
+ grid-template-columns: repeat(3, minmax(0, 1fr));
409
+ }
410
+
411
+ .preview-surface-card {
412
+ display: grid;
413
+ gap: 6px;
414
+ padding: 16px;
415
+ border-radius: 20px;
416
+ background: rgba(255, 255, 255, 0.06);
417
+ border: 1px solid rgba(255, 255, 255, 0.08);
418
+ }
419
+
420
+ .preview-label {
421
+ margin: 0;
422
+ color: rgba(244, 241, 232, 0.62);
423
+ font-size: 11px;
424
+ font-weight: 700;
425
+ letter-spacing: 0.08em;
426
+ text-transform: uppercase;
427
+ }
428
+
429
+ .sidebar {
430
+ position: sticky;
431
+ top: 20px;
432
+ }
433
+
434
+ .nav-list {
435
+ display: grid;
436
+ gap: 8px;
437
+ }
438
+
439
+ .nav-item {
440
+ display: block;
441
+ padding: 12px 14px;
442
+ border-radius: 14px;
443
+ border: 1px solid transparent;
444
+ color: var(--muted);
445
+ }
446
+
447
+ .nav-item.active,
448
+ .nav-item:hover {
449
+ background: white;
450
+ border-color: var(--line);
451
+ color: var(--text);
452
+ }
453
+
454
+ .stack {
455
+ display: grid;
456
+ gap: 18px;
457
+ }
458
+
459
+ .form-grid {
460
+ display: grid;
461
+ gap: 14px;
462
+ }
463
+
464
+ .field {
465
+ display: grid;
466
+ gap: 8px;
467
+ }
468
+
469
+ .field label {
470
+ font-size: 14px;
471
+ font-weight: 600;
472
+ }
473
+
474
+ .field input {
475
+ width: 100%;
476
+ padding: 13px 14px;
477
+ border-radius: 14px;
478
+ border: 1px solid var(--line);
479
+ background: white;
480
+ }
481
+
482
+ .result-box,
483
+ .code-box {
484
+ padding: 16px;
485
+ border-radius: 16px;
486
+ background: #fff;
487
+ border: 1px solid var(--line);
488
+ overflow: auto;
489
+ }
490
+
491
+ .status {
492
+ display: inline-flex;
493
+ align-items: center;
494
+ gap: 8px;
495
+ padding: 8px 12px;
496
+ border-radius: 999px;
497
+ font-size: 13px;
498
+ font-weight: 700;
499
+ }
500
+
501
+ .status.ok {
502
+ background: rgba(31, 122, 69, 0.14);
503
+ color: var(--accent);
504
+ }
505
+
506
+ .status.warn {
507
+ background: rgba(180, 127, 34, 0.14);
508
+ color: #8d5f00;
509
+ }
510
+
511
+ .status.error {
512
+ background: rgba(180, 63, 63, 0.12);
513
+ color: var(--danger);
514
+ }
515
+
516
+ @media (max-width: 900px) {
517
+ .starter-hero,
518
+ .starter-signal-grid,
519
+ .app-preview-shell,
520
+ .preview-surface-grid {
521
+ grid-template-columns: 1fr;
522
+ }
523
+
524
+ .playground-layout {
525
+ grid-template-columns: 1fr;
526
+ }
527
+
528
+ .sidebar {
529
+ position: static;
530
+ }
531
+
532
+ .app-preview-sidebar {
533
+ display: none;
534
+ }
535
+
536
+ .app-preview-mobile-nav {
537
+ display: block;
538
+ }
539
+
540
+ .app-preview-header,
541
+ .preview-stage-head {
542
+ grid-template-columns: 1fr;
543
+ display: grid;
544
+ }
545
+ }
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from "react";
2
+ import { starterBrandConfig } from "../config/brand";
3
+ import "./globals.css";
4
+
5
+ export const metadata = {
6
+ title: `${starterBrandConfig.companyName} Starter`,
7
+ description: starterBrandConfig.tagline,
8
+ };
9
+
10
+ export default function RootLayout({ children }: { children: ReactNode }) {
11
+ return (
12
+ <html lang="en">
13
+ <body>{children}</body>
14
+ </html>
15
+ );
16
+ }