@roboticela/devkit 2.0.0 → 4.0.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 ADDED
@@ -0,0 +1,632 @@
1
+ # @roboticela/devkit
2
+
3
+ > Scaffold, extend, and theme full-stack projects with one command.
4
+
5
+ **DevKit** is the official CLI for [Roboticela](https://roboticela.com) templates. It lets you spin up a production-ready project, install full-stack feature components (auth, dashboard, billing, and more), and manage your design token system — all from the terminal.
6
+
7
+ ```bash
8
+ npx @roboticela/devkit create my-app
9
+ ```
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ - [Installation](#installation)
16
+ - [Quick Start](#quick-start)
17
+ - [Templates](#templates)
18
+ - [Commands](#commands)
19
+ - [create](#devkit-create)
20
+ - [init](#devkit-init)
21
+ - [add](#devkit-add)
22
+ - [remove](#devkit-remove)
23
+ - [list](#devkit-list)
24
+ - [info](#devkit-info)
25
+ - [update](#devkit-update)
26
+ - [upgrade](#devkit-upgrade)
27
+ - [doctor](#devkit-doctor)
28
+ - [theme](#devkit-theme)
29
+ - [install](#devkit-install)
30
+ - [eject](#devkit-eject)
31
+ - [Configuration Files](#configuration-files)
32
+ - [Design Token System](#design-token-system)
33
+ - [Environment Variables](#environment-variables)
34
+ - [Local Development](#local-development)
35
+ - [Publishing a Release](#publishing-a-release)
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ **Use without installing (recommended for project creation):**
42
+
43
+ ```bash
44
+ npx @roboticela/devkit create my-app
45
+ ```
46
+
47
+ **Install globally for repeated use:**
48
+
49
+ ```bash
50
+ npm install -g @roboticela/devkit
51
+ devkit --version
52
+ ```
53
+
54
+ **Requirements:** Node.js `>=20`
55
+
56
+ ---
57
+
58
+ ## Quick Start
59
+
60
+ ```bash
61
+ # 1. Create a new project — fully interactive
62
+ npx @roboticela/devkit create
63
+
64
+ # 2. Move into it and fill in your secrets
65
+ cd my-app
66
+ cp .env.example .env
67
+
68
+ # 3. Check everything is wired up correctly
69
+ devkit doctor
70
+
71
+ # 4. Start developing
72
+ npm run dev
73
+ ```
74
+
75
+ That's it. DevKit scaffolds the template, applies your theme, installs your chosen components, and prints exactly which environment variables you still need to fill in.
76
+
77
+ ---
78
+
79
+ ## Templates
80
+
81
+ DevKit targets two official templates. Every component declares which template(s) it supports — incompatible combinations are blocked before any files are written.
82
+
83
+ | ID | Stack | Platform |
84
+ |----|-------|----------|
85
+ | `nextjs-compact` | Next.js 16 · React 19 · TailwindCSS 4 · TypeScript | Web |
86
+ | `vite-express-tauri` | Vite 7 · React 19 · Express 5 · Tauri 2 · Prisma · TypeScript | Web + Desktop |
87
+
88
+ **Template compatibility matrix (selected components):**
89
+
90
+ ```
91
+ Component nextjs-compact vite-express-tauri
92
+ ──────────────────────────────────────────────────────
93
+ auth ✓ ✓
94
+ profile ✓ ✓
95
+ landing-page ✓ ✓
96
+ hero-section ✓ ✓
97
+ pricing ✓ ✓
98
+ dashboard ✓ ✓
99
+ blog ✓ ✓
100
+ subscriptions ✓ ✓
101
+ file-upload ✓ ✓
102
+ docs-site ✓ ✗ (Next.js MDX only)
103
+ desktop-updater ✗ ✓ (Tauri only)
104
+ desktop-tray ✗ ✓ (Tauri only)
105
+ offline-storage ✗ ✓ (Tauri only)
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Commands
111
+
112
+ ### `devkit create`
113
+
114
+ Scaffold a brand-new project. This is the recommended starting point — it fetches the template, applies your theme, installs npm dependencies, adds your chosen components, and makes the first git commit in one go.
115
+
116
+ ```bash
117
+ # Fully interactive
118
+ devkit create
119
+
120
+ # Pre-fill the project name
121
+ devkit create my-app
122
+
123
+ # Non-interactive: provide every flag
124
+ devkit create my-app \
125
+ --template=vite-express-tauri \
126
+ --preset=bold \
127
+ --add=auth,hero-section,dashboard \
128
+ --variant=hero-section:gradient-mesh \
129
+ --git --install
130
+ ```
131
+
132
+ **Flags:**
133
+
134
+ | Flag | Values | Description |
135
+ |------|--------|-------------|
136
+ | `--template` | `nextjs-compact` \| `vite-express-tauri` | Skip the template selection prompt |
137
+ | `--preset` | `default` \| `minimal` \| `bold` \| `playful` \| `corporate` | Theme preset |
138
+ | `--primary` | hex string | Override the primary brand color, e.g. `#e11d48` |
139
+ | `--git` | — | Initialize a git repository |
140
+ | `--no-git` | — | Skip git initialization |
141
+ | `--install` | — | Run `npm install` after scaffolding |
142
+ | `--no-install` | — | Skip `npm install` |
143
+ | `--add` | comma-separated | Components to add immediately, e.g. `--add=auth,pricing` |
144
+ | `--variant` | `component:variant` | Variant for a component, e.g. `--variant=hero-section:particles` |
145
+ | `-y, --yes` | — | Accept all defaults, skip all prompts |
146
+
147
+ **How it fetches the template:**
148
+
149
+ DevKit uses the GitHub Tarball API to download only the latest snapshot — no `git` binary required, and no commit history in your new project:
150
+
151
+ ```
152
+ GET https://api.github.com/repos/Roboticela/NextJS-Template-DevKit/tarball/main
153
+ ```
154
+
155
+ If the API is unreachable (rate limit or network issue), it falls back to `git clone --depth=1` automatically.
156
+
157
+ ---
158
+
159
+ ### `devkit init`
160
+
161
+ Initialize DevKit inside a project you have already cloned or set up manually. Auto-detects the template type by inspecting `package.json` and the directory structure.
162
+
163
+ > Prefer `devkit create` when starting fresh. Use `devkit init` only when adopting DevKit in an existing project.
164
+
165
+ ```bash
166
+ devkit init
167
+ ```
168
+
169
+ **What it creates:**
170
+
171
+ - `devkit.config.json` — pre-filled with your site info
172
+ - `devkit.lock.json` — empty, ready to track installed components
173
+ - `.devkit/` — directory for component manifests
174
+ - `.gitignore` entry for `.devkit/` cache files
175
+
176
+ ---
177
+
178
+ ### `devkit add`
179
+
180
+ Install one or more components into the current project.
181
+
182
+ ```bash
183
+ # Interactive — browse the registry for your template, multi-select, then pick variants when needed
184
+ devkit add
185
+
186
+ # Same as above but only print what would be installed
187
+ devkit add --dry-run
188
+
189
+ # Single component
190
+ devkit add auth
191
+
192
+ # Component with a specific variant
193
+ devkit add hero-section --variant=split-image
194
+
195
+ # Multiple components at once
196
+ devkit add auth profile pricing
197
+
198
+ # Specific version
199
+ devkit add auth@2.0.0
200
+
201
+ # Preview what would be installed without touching any files
202
+ devkit add auth --dry-run
203
+ ```
204
+
205
+ **Interactive mode** (no component names): DevKit loads components from the registry filtered by your template in `devkit.config.json`, excludes anything already in `devkit.lock.json`, and opens a terminal multiselect (space to toggle, enter to confirm). For each selected package that has variants, you get a follow-up prompt unless you passed `--variant=<id>` and that id exists for that component.
206
+
207
+ **What happens under the hood:**
208
+
209
+ 1. Resolves the component version (latest unless pinned)
210
+ 2. Checks template compatibility — exits with a clear error if incompatible
211
+ 3. Reads `devkit.config.json` — prompts for any missing required fields
212
+ 4. Downloads component files for the detected template
213
+ 5. Copies files into the project (skips user-modified files, warns you)
214
+ 6. Installs required npm packages (`npm install <packages>`)
215
+ 7. Runs database migrations if the component needs them
216
+ 8. Injects route registrations into your router file
217
+ 9. Updates `devkit.lock.json`
218
+ 10. Prints post-install instructions: which env vars to fill in and what to do next
219
+
220
+ ---
221
+
222
+ ### `devkit remove`
223
+
224
+ Uninstall a component and delete all files it owns.
225
+
226
+ ```bash
227
+ devkit remove auth
228
+
229
+ # Remove from DevKit tracking but keep the files
230
+ devkit remove auth --keep-files
231
+ ```
232
+
233
+ DevKit only deletes files it created. Files you have modified are flagged with a warning, and you decide whether to delete them.
234
+
235
+ ---
236
+
237
+ ### `devkit list`
238
+
239
+ Browse available components in the registry.
240
+
241
+ ```bash
242
+ devkit list
243
+ devkit list --template=nextjs-compact
244
+ devkit list --template=vite-express-tauri
245
+ devkit list --installed
246
+ devkit list --category=auth
247
+ devkit list --category=ui
248
+ ```
249
+
250
+ **Example output:**
251
+
252
+ ```
253
+ ┌────────────────────┬───────────────────────────────┬────────────────────────────┬──────────────┐
254
+ │ Component │ Description │ Templates │ Installed │
255
+ ├────────────────────┼───────────────────────────────┼────────────────────────────┼──────────────┤
256
+ │ auth │ Full authentication system │ nextjs-compact, vite-expr… │ v2.1.0 ✓ │
257
+ │ hero-section │ Landing hero (6 variants) │ nextjs-compact, vite-expr… │ - │
258
+ │ pricing │ Pricing table (3 variants) │ nextjs-compact, vite-expr… │ - │
259
+ │ desktop-tray │ System tray icon & menu │ vite-express-tauri only │ - │
260
+ │ docs-site │ MDX documentation site │ nextjs-compact only │ - │
261
+ └────────────────────┴───────────────────────────────┴────────────────────────────┴──────────────┘
262
+ ```
263
+
264
+ ---
265
+
266
+ ### `devkit info`
267
+
268
+ Show full details for a component: description, version, variants, required config, files created, and changelog.
269
+
270
+ ```bash
271
+ devkit info auth
272
+ devkit info hero-section
273
+ ```
274
+
275
+ ---
276
+
277
+ ### `devkit update`
278
+
279
+ Update a specific component to its latest version (or a target version).
280
+
281
+ ```bash
282
+ devkit update auth
283
+ devkit update auth@2.2.0
284
+ devkit update --all
285
+ ```
286
+
287
+ Only **managed files** (those DevKit originally created and you have not modified) are updated. User-modified files are left alone with a warning shown.
288
+
289
+ ---
290
+
291
+ ### `devkit upgrade`
292
+
293
+ Upgrade all installed components to their latest versions in one step.
294
+
295
+ ```bash
296
+ devkit upgrade
297
+
298
+ # Preview changes without applying them
299
+ devkit upgrade --dry-run
300
+ ```
301
+
302
+ ---
303
+
304
+ ### `devkit doctor`
305
+
306
+ Check the project for configuration issues, missing environment variables, and available updates. Run this whenever something doesn't work — it tells you exactly what is wrong.
307
+
308
+ ```bash
309
+ devkit doctor
310
+ ```
311
+
312
+ **Example output:**
313
+
314
+ ```
315
+ ✓ Template detected: vite-express-tauri
316
+ ✓ DevKit initialized
317
+ ✓ auth@2.1.0 — OK
318
+ ⚠ profile@1.0.0 — Update available: 1.2.0
319
+ ✗ auth — Missing: GOOGLE_CLIENT_SECRET (Google OAuth will not work)
320
+ ✗ auth — Missing: SMTP_HOST (email sending disabled)
321
+ ```
322
+
323
+ ---
324
+
325
+ ### `devkit theme`
326
+
327
+ Manage the global design token system. All DevKit components use CSS variables — change one token and every component updates instantly.
328
+
329
+ ```bash
330
+ # Regenerate globals.css from your current devkit.config.json
331
+ devkit theme apply
332
+
333
+ # Preview generated CSS without writing any files
334
+ devkit theme preview
335
+
336
+ # Switch to a named preset
337
+ devkit theme preset bold
338
+
339
+ # Set a single token
340
+ devkit theme set colors.primary "#e11d48"
341
+
342
+ # List all current theme settings
343
+ devkit theme list
344
+
345
+ # Scan component files for hardcoded colors (anti-pattern detector)
346
+ devkit theme audit
347
+ ```
348
+
349
+ **Built-in presets:**
350
+
351
+ | Preset | Primary | Secondary | Radius | Style |
352
+ |--------|---------|-----------|--------|-------|
353
+ | `default` | Indigo `#6366f1` | Amber `#f59e0b` | `md` | Modern SaaS |
354
+ | `minimal` | Slate `#475569` | Sky `#0ea5e9` | `sm` | Clean, editorial |
355
+ | `bold` | Violet `#7c3aed` | Pink `#ec4899` | `lg` | Vibrant, expressive |
356
+ | `playful` | Emerald `#10b981` | Orange `#f97316` | `full` | Friendly, rounded |
357
+ | `corporate` | Blue `#1d4ed8` | Gray `#374151` | `none` | Formal, enterprise |
358
+
359
+ ---
360
+
361
+ ### `devkit install`
362
+
363
+ Restore all components from `devkit.lock.json`. Designed for CI/CD pipelines and fresh checkouts — the equivalent of `npm ci` but for DevKit components.
364
+
365
+ ```bash
366
+ devkit install
367
+ ```
368
+
369
+ ---
370
+
371
+ ### `devkit eject`
372
+
373
+ Stop DevKit from tracking a component without deleting its files. After ejecting, the files become entirely yours — no more updates via `devkit update`.
374
+
375
+ ```bash
376
+ # Interactive — multiselect from installed components (from devkit.lock.json)
377
+ devkit eject
378
+
379
+ # Eject one component by name
380
+ devkit eject auth
381
+ ```
382
+
383
+ ---
384
+
385
+ ## Configuration Files
386
+
387
+ ### `devkit.config.json`
388
+
389
+ The single source of truth for all component configuration. Committed to git. Sensitive values (API keys, secrets) are **referenced by env var name**, not stored directly.
390
+
391
+ ```json
392
+ {
393
+ "$schema": "https://registry.devkit.roboticela.com/schemas/devkit-config.json",
394
+ "devkit": "1.0",
395
+ "template": "vite-express-tauri",
396
+ "site": {
397
+ "name": "My App",
398
+ "url": "https://myapp.com",
399
+ "description": "My awesome application"
400
+ },
401
+ "theme": {
402
+ "preset": "default",
403
+ "colors": {
404
+ "primary": "#6366f1",
405
+ "secondary": "#f59e0b"
406
+ },
407
+ "fonts": {
408
+ "sans": "Inter",
409
+ "mono": "JetBrains Mono"
410
+ },
411
+ "radius": "md",
412
+ "darkMode": true,
413
+ "darkModeStrategy": "class"
414
+ },
415
+ "auth": {
416
+ "enabled": true,
417
+ "providers": ["email", "google"],
418
+ "google": {
419
+ "clientId": "${GOOGLE_CLIENT_ID}",
420
+ "clientSecret": "${GOOGLE_CLIENT_SECRET}"
421
+ },
422
+ "smtp": {
423
+ "host": "${SMTP_HOST}",
424
+ "port": 587,
425
+ "user": "${SMTP_USER}",
426
+ "password": "${SMTP_PASSWORD}",
427
+ "from": "noreply@myapp.com"
428
+ },
429
+ "jwt": {
430
+ "accessSecret": "${JWT_ACCESS_SECRET}",
431
+ "refreshSecret": "${JWT_REFRESH_SECRET}",
432
+ "accessExpiresIn": "15m",
433
+ "refreshExpiresIn": "30d"
434
+ }
435
+ }
436
+ }
437
+ ```
438
+
439
+ ### `devkit.lock.json`
440
+
441
+ Version lock file — committed to git to ensure deterministic installs across machines and CI environments.
442
+
443
+ ```json
444
+ {
445
+ "lockVersion": 1,
446
+ "template": "vite-express-tauri",
447
+ "components": {
448
+ "auth": {
449
+ "version": "2.1.0",
450
+ "variant": null,
451
+ "resolved": "https://registry.devkit.roboticela.com/components/auth/2.1.0/vite-express-tauri.tar.gz",
452
+ "integrity": "sha512:...",
453
+ "installedAt": "2026-04-05T10:30:00Z"
454
+ },
455
+ "hero-section": {
456
+ "version": "1.3.0",
457
+ "variant": "split-image",
458
+ "resolved": "https://registry.devkit.roboticela.com/components/hero-section/1.3.0/variants/split-image/vite-express-tauri.tar.gz",
459
+ "integrity": "sha512:...",
460
+ "installedAt": "2026-04-05T11:00:00Z"
461
+ }
462
+ }
463
+ }
464
+ ```
465
+
466
+ ---
467
+
468
+ ## Design Token System
469
+
470
+ Every DevKit component uses **CSS custom properties** (variables) — no hardcoded colors, font sizes, or spacing values. This means changing your brand color in one place updates every button, form, card, and section across the entire app.
471
+
472
+ ```
473
+ devkit.config.json → devkit theme apply → globals.css → all components
474
+ ```
475
+
476
+ **Token categories:**
477
+
478
+ - **Brand colors** — primary, secondary with auto-derived hover, active, subtle, and text variants (WCAG AA contrast guaranteed)
479
+ - **Neutral / surface** — background, border, and card colors with dark mode variants
480
+ - **Semantic** — success, warning, error, info
481
+ - **Typography** — font families, sizes (`--text-xs` → `--text-6xl`), weights, line heights
482
+ - **Spacing** — `--space-1` through `--space-24`
483
+ - **Border radius** — `--radius-sm` through `--radius-full`
484
+ - **Shadows** and **transitions**
485
+
486
+ When you run `devkit theme set colors.primary "#e11d48"`, DevKit automatically computes and writes:
487
+
488
+ ```
489
+ --color-primary #e11d48 (your value)
490
+ --color-primary-hover #c01140 (10% darker)
491
+ --color-primary-active #9e0d35 (20% darker)
492
+ --color-primary-subtle #fde8ed (90% lighter tint)
493
+ --color-primary-text #ffffff (WCAG-compliant contrast)
494
+ ```
495
+
496
+ ---
497
+
498
+ ## Environment Variables
499
+
500
+ All secrets live in `.env` and are never committed. DevKit generates a `.env.example` listing everything your installed components need:
501
+
502
+ ```bash
503
+ # auth component
504
+ JWT_ACCESS_SECRET= # generate: openssl rand -hex 32
505
+ JWT_REFRESH_SECRET= # generate: openssl rand -hex 32
506
+ GOOGLE_CLIENT_ID= # optional — enables Google OAuth
507
+ GOOGLE_CLIENT_SECRET= # optional — enables Google OAuth
508
+ SMTP_HOST= # optional — enables email sending
509
+ SMTP_PORT=587
510
+ SMTP_USER=
511
+ SMTP_PASSWORD=
512
+
513
+ # subscriptions component (if installed)
514
+ PADDLE_API_KEY=
515
+ PADDLE_WEBHOOK_SECRET=
516
+ PADDLE_PRICE_BASIC=
517
+ PADDLE_PRICE_PRO=
518
+ PADDLE_PRICE_LIFETIME=
519
+
520
+ # storage component (if installed)
521
+ R2_ACCOUNT_ID=
522
+ R2_ACCESS_KEY_ID=
523
+ R2_SECRET_ACCESS_KEY=
524
+ R2_BUCKET_NAME=
525
+ R2_PUBLIC_URL=
526
+
527
+ # database (vite-express-tauri)
528
+ DATABASE_URL=
529
+ ```
530
+
531
+ Run `devkit doctor` at any time to see which variables are missing and what breaks without them.
532
+
533
+ **CLI registry override:**
534
+
535
+ ```bash
536
+ # Point the CLI at a local registry during development
537
+ DEVKIT_REGISTRY=http://localhost:4000 devkit list
538
+ DEVKIT_REGISTRY=http://localhost:4000 devkit add auth
539
+ ```
540
+
541
+ ---
542
+
543
+ ## Local Development
544
+
545
+ To work on the CLI itself:
546
+
547
+ ```bash
548
+ # Install dependencies
549
+ cd cli
550
+ npm install
551
+
552
+ # Run directly with tsx (no build step needed)
553
+ npx tsx src/index.ts --help
554
+ npx tsx src/index.ts list
555
+ npx tsx src/index.ts create my-app
556
+
557
+ # Run against a local registry
558
+ DEVKIT_REGISTRY=http://localhost:4000 npx tsx src/index.ts list
559
+
560
+ # Lint
561
+ npm run lint
562
+
563
+ # Build to dist/
564
+ npm run build
565
+
566
+ # Run the compiled output
567
+ node dist/index.js --help
568
+ ```
569
+
570
+ **Project structure:**
571
+
572
+ ```
573
+ cli/
574
+ ├── src/
575
+ │ ├── index.ts ← Entry point — registers all commands
576
+ │ ├── commands/
577
+ │ │ ├── create.ts ← devkit create
578
+ │ │ ├── init.ts ← devkit init
579
+ │ │ ├── add.ts ← devkit add
580
+ │ │ ├── remove.ts ← devkit remove
581
+ │ │ ├── list.ts ← devkit list
582
+ │ │ ├── info.ts ← devkit info
583
+ │ │ ├── update.ts ← devkit update / upgrade
584
+ │ │ ├── doctor.ts ← devkit doctor
585
+ │ │ └── theme.ts ← devkit theme *
586
+ │ └── lib/
587
+ │ ├── config.ts ← Read/write devkit.config.json & devkit.lock.json
588
+ │ ├── installer.ts ← Download + extract component tarballs
589
+ │ └── logger.ts ← Consistent terminal output helpers
590
+ ├── dist/ ← Compiled output (generated by npm run build)
591
+ ├── scripts/
592
+ │ └── post-build.js ← Makes dist/index.js executable after tsc
593
+ ├── package.json
594
+ └── tsconfig.json
595
+ ```
596
+
597
+ ---
598
+
599
+ ## Publishing a Release
600
+
601
+ The CLI is published as `@roboticela/devkit` on npm.
602
+
603
+ ```bash
604
+ cd cli
605
+
606
+ # Login once
607
+ npm login
608
+
609
+ # Publish a patch release (2.0.0 → 2.0.1)
610
+ npm run release:patch
611
+
612
+ # Publish a minor release (2.0.0 → 2.1.0)
613
+ npm run release:minor
614
+
615
+ # Publish a major release (2.0.0 → 3.0.0)
616
+ npm run release:major
617
+ ```
618
+
619
+ Each release script bumps the version in `package.json`, runs `npm run build` via `prepublishOnly`, and publishes with `--access public`.
620
+
621
+ After publishing:
622
+
623
+ ```bash
624
+ npx @roboticela/devkit create my-app # always runs the latest published version
625
+ npm install -g @roboticela/devkit # or install globally
626
+ ```
627
+
628
+ ---
629
+
630
+ ## License
631
+
632
+ MIT — [Roboticela](https://roboticela.com)
@@ -1,27 +1,126 @@
1
1
  import ora from "ora";
2
2
  import chalk from "chalk";
3
+ import { intro, outro, multiselect, select, isCancel } from "@clack/prompts";
3
4
  import { readConfig, isComponentInstalled } from "../lib/config.js";
4
5
  import { installComponent } from "../lib/installer.js";
6
+ import { listComponents } from "../lib/registry.js";
5
7
  import { log } from "../lib/logger.js";
8
+ /** `undefined` = no variant; string = chosen id; `"cancel"` = user aborted */
9
+ async function pickVariantForComponent(meta, optsVariant) {
10
+ if (!meta.hasVariants || !meta.variants?.length)
11
+ return undefined;
12
+ if (meta.variants.length === 1)
13
+ return meta.variants[0].id;
14
+ if (optsVariant) {
15
+ const match = meta.variants.find((v) => v.id === optsVariant);
16
+ if (match)
17
+ return match.id;
18
+ }
19
+ const v = await select({
20
+ message: `Variant for ${meta.name}`,
21
+ options: meta.variants.map((x) => ({
22
+ value: x.id,
23
+ label: x.label,
24
+ hint: x.description,
25
+ })),
26
+ });
27
+ if (isCancel(v))
28
+ return "cancel";
29
+ return v;
30
+ }
31
+ async function interactiveAddSelections(config, opts, cwd) {
32
+ let all;
33
+ try {
34
+ all = await listComponents(config.template);
35
+ }
36
+ catch (e) {
37
+ log.error(`Could not reach registry: ${e.message}`);
38
+ log.info("Check DEVKIT_REGISTRY or start the registry locally (see repo README).");
39
+ return null;
40
+ }
41
+ const installable = all.filter((c) => !isComponentInstalled(c.name, cwd));
42
+ if (installable.length === 0) {
43
+ log.info("Every component from the registry for your template is already installed.");
44
+ return [];
45
+ }
46
+ console.log();
47
+ intro(chalk.cyan("Add components"));
48
+ const pickedRaw = await multiselect({
49
+ message: "Select components to install (space to toggle, enter to confirm)",
50
+ options: installable.map((c) => ({
51
+ value: c.name,
52
+ label: c.name,
53
+ hint: c.description.length > 64 ? `${c.description.slice(0, 61)}…` : c.description,
54
+ })),
55
+ required: false,
56
+ });
57
+ if (isCancel(pickedRaw)) {
58
+ outro("Cancelled.");
59
+ return [];
60
+ }
61
+ const picked = pickedRaw;
62
+ if (picked.length === 0) {
63
+ outro("No components selected.");
64
+ return [];
65
+ }
66
+ const results = [];
67
+ for (const compName of picked) {
68
+ const meta = installable.find((x) => x.name === compName);
69
+ const variant = await pickVariantForComponent(meta, opts.variant);
70
+ if (variant === "cancel") {
71
+ outro("Cancelled.");
72
+ return [];
73
+ }
74
+ results.push({ name: compName, version: "latest", variant });
75
+ }
76
+ return results;
77
+ }
6
78
  export async function addCommand(names, opts, cwd = process.cwd()) {
7
79
  const config = readConfig(cwd);
8
- for (const nameAtVersion of names) {
9
- const [name, version = "latest"] = nameAtVersion.split("@");
80
+ const usedInteractive = names.length === 0;
81
+ let todo;
82
+ if (usedInteractive) {
83
+ const picked = await interactiveAddSelections(config, opts, cwd);
84
+ if (picked === null)
85
+ return;
86
+ if (picked.length === 0)
87
+ return;
88
+ todo = picked;
89
+ }
90
+ else {
91
+ todo = names.map((nameAtVersion) => {
92
+ const [name, version = "latest"] = nameAtVersion.split("@");
93
+ return { name, version, variant: opts.variant };
94
+ });
95
+ }
96
+ let anyOk = false;
97
+ for (const { name, version, variant } of todo) {
10
98
  if (isComponentInstalled(name, cwd)) {
11
99
  log.warn(`${name} is already installed. Run 'devkit update ${name}' to update.`);
12
100
  continue;
13
101
  }
14
102
  if (opts.dryRun) {
15
- log.info(`[dry-run] Would install: ${chalk.cyan(name)}@${version} (${config.template})`);
103
+ log.info(`[dry-run] Would install: ${chalk.cyan(name)}@${version} (${config.template})` +
104
+ (variant ? chalk.dim(` variant=${variant}`) : ""));
105
+ anyOk = true;
16
106
  continue;
17
107
  }
18
108
  const spinner = ora({ text: `Installing ${chalk.cyan(name)}…`, color: "cyan" }).start();
19
109
  try {
20
- await installComponent(name, config.template, version, opts.variant, cwd);
110
+ await installComponent(name, config.template, version, variant, cwd);
21
111
  spinner.succeed(chalk.green(`${name}@${version} installed`));
112
+ anyOk = true;
22
113
  }
23
114
  catch (e) {
24
115
  spinner.fail(chalk.red(`Failed to install ${name}: ${e.message}`));
25
116
  }
26
117
  }
118
+ if (usedInteractive) {
119
+ if (anyOk) {
120
+ outro(opts.dryRun ? chalk.dim("Dry run complete.") : chalk.green("Done."));
121
+ }
122
+ else if (!opts.dryRun) {
123
+ outro(chalk.yellow("Nothing was installed."));
124
+ }
125
+ }
27
126
  }
@@ -0,0 +1 @@
1
+ export declare function ejectCommand(name: string | undefined, cwd?: string): Promise<void>;
@@ -0,0 +1,60 @@
1
+ import { intro, outro, multiselect, isCancel } from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import { unlinkSync, existsSync } from "fs";
4
+ import { join } from "path";
5
+ import { readLock, writeLock } from "../lib/config.js";
6
+ import { log } from "../lib/logger.js";
7
+ export async function ejectCommand(name, cwd = process.cwd()) {
8
+ const lock = readLock(cwd);
9
+ const installed = Object.keys(lock.components);
10
+ if (installed.length === 0) {
11
+ log.info("No installed components to eject.");
12
+ return;
13
+ }
14
+ let toEject;
15
+ if (name) {
16
+ if (!(name in lock.components)) {
17
+ log.warn(`'${name}' is not an installed DevKit component.`);
18
+ return;
19
+ }
20
+ toEject = [name];
21
+ }
22
+ else {
23
+ console.log();
24
+ intro(chalk.cyan("Eject components"));
25
+ const picked = await multiselect({
26
+ message: "Select components to eject (DevKit stops tracking them; files stay on disk — space to toggle, enter to confirm)",
27
+ options: installed.map((n) => ({
28
+ value: n,
29
+ label: n,
30
+ hint: `v${lock.components[n].version}`,
31
+ })),
32
+ required: false,
33
+ });
34
+ if (isCancel(picked)) {
35
+ outro("Cancelled.");
36
+ return;
37
+ }
38
+ const list = picked;
39
+ if (list.length === 0) {
40
+ outro("No components selected.");
41
+ return;
42
+ }
43
+ toEject = list;
44
+ }
45
+ for (const n of toEject) {
46
+ if (!(n in lock.components)) {
47
+ log.warn(`Skipping '${n}' — not in lock file.`);
48
+ continue;
49
+ }
50
+ const manifestPath = join(cwd, ".devkit", `${n}.manifest.json`);
51
+ if (existsSync(manifestPath))
52
+ unlinkSync(manifestPath);
53
+ delete lock.components[n];
54
+ log.success(`${n} ejected. Files are yours — DevKit will no longer manage them.`);
55
+ }
56
+ writeLock(lock, cwd);
57
+ if (!name) {
58
+ outro(chalk.green("Done."));
59
+ }
60
+ }
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { listCommand } from "./commands/list.js";
9
9
  import { infoCommand } from "./commands/info.js";
10
10
  import { updateCommand, upgradeAllCommand } from "./commands/update.js";
11
11
  import { doctorCommand } from "./commands/doctor.js";
12
+ import { ejectCommand } from "./commands/eject.js";
12
13
  import { themeApplyCommand, themePreviewCommand, themePresetCommand, themeSetCommand, themeListCommand, themeAuditCommand, } from "./commands/theme.js";
13
14
  const program = new Command();
14
15
  program
@@ -36,11 +37,11 @@ program
36
37
  .action(() => initCommand());
37
38
  // ── devkit add ────────────────────────────────────────────────────────────────
38
39
  program
39
- .command("add <components...>")
40
- .description("Install one or more components (e.g. devkit add auth hero-section)")
41
- .option("--variant <id>", "Variant to install for components that have variants")
40
+ .command("add [components...]")
41
+ .description("Install components run with no args for an interactive picker")
42
+ .option("--variant <id>", "Variant for components with variants (non-interactive or as default when prompting)")
42
43
  .option("--dry-run", "Preview what would be installed without writing files")
43
- .action((names, opts) => addCommand(names, opts));
44
+ .action((names, opts) => addCommand(names ?? [], opts));
44
45
  // ── devkit remove ─────────────────────────────────────────────────────────────
45
46
  program
46
47
  .command("remove <name>")
@@ -124,32 +125,22 @@ program
124
125
  });
125
126
  // ── devkit eject ──────────────────────────────────────────────────────────────
126
127
  program
127
- .command("eject <name>")
128
- .description("Stop DevKit from tracking a component (files are kept, no more updates via DevKit)")
129
- .action(async (name) => {
130
- const { readLock, writeLock } = await import("./lib/config.js");
131
- const { unlinkSync, existsSync } = await import("fs");
132
- const { join } = await import("path");
133
- const { log } = await import("./lib/logger.js");
134
- const manifestPath = join(process.cwd(), ".devkit", `${name}.manifest.json`);
135
- if (existsSync(manifestPath))
136
- unlinkSync(manifestPath);
137
- const lock = readLock();
138
- delete lock.components[name];
139
- writeLock(lock);
140
- log.success(`${name} ejected. Files are yours — DevKit will no longer manage them.`);
141
- });
128
+ .command("eject [name]")
129
+ .description("Stop DevKit from tracking component(s) run with no args to pick interactively")
130
+ .action((name) => ejectCommand(name));
142
131
  // ── Help footer ───────────────────────────────────────────────────────────────
143
132
  program.addHelpText("after", `
144
133
  ${chalk.bold("Examples:")}
145
134
  ${chalk.cyan("devkit create my-app")} Interactive project creation
146
135
  ${chalk.cyan("devkit create my-app --template=nextjs-compact --yes")} Non-interactive
136
+ ${chalk.cyan("devkit add")} Interactive component picker
147
137
  ${chalk.cyan("devkit add auth")} Install auth component
148
138
  ${chalk.cyan("devkit add hero-section --variant=split-image")}
139
+ ${chalk.cyan("devkit eject")} Pick component(s) to eject
149
140
  ${chalk.cyan("devkit theme set colors.primary #e11d48")} Change primary color
150
141
  ${chalk.cyan("devkit doctor")} Check configuration
151
142
  ${chalk.cyan("devkit list")} Browse all components
152
143
 
153
- ${chalk.dim("Registry:")} ${process.env.DEVKIT_REGISTRY ?? "https://api.devkit.roboticela.com"}
144
+ ${chalk.dim("Registry:")} ${process.env.DEVKIT_REGISTRY ?? "https://registry.devkit.roboticela.com"}
154
145
  `);
155
146
  program.parse();
@@ -32,7 +32,7 @@ export function isComponentInstalled(name, cwd = process.cwd()) {
32
32
  }
33
33
  export function defaultConfig(template, siteName, siteUrl) {
34
34
  return {
35
- $schema: "https://registry.roboticela.com/schemas/devkit-config.json",
35
+ $schema: "https://registry.devkit.roboticela.com/schemas/devkit-config.json",
36
36
  devkit: "1.0",
37
37
  template,
38
38
  site: { name: siteName, url: siteUrl },
@@ -7,7 +7,7 @@ import { pipeline } from "stream/promises";
7
7
  import { getManifest } from "./registry.js";
8
8
  import { readLock, writeLock } from "./config.js";
9
9
  import { log } from "./logger.js";
10
- const REGISTRY_URL = process.env.DEVKIT_REGISTRY ?? "https://api.devkit.roboticela.com";
10
+ const REGISTRY_URL = process.env.DEVKIT_REGISTRY ?? "https://registry.devkit.roboticela.com";
11
11
  function hashFile(path) {
12
12
  const content = readFileSync(path);
13
13
  return createHash("sha256").update(content).digest("hex");
@@ -1,4 +1,4 @@
1
- const REGISTRY_URL = process.env.DEVKIT_REGISTRY ?? "https://api.devkit.roboticela.com";
1
+ const REGISTRY_URL = process.env.DEVKIT_REGISTRY ?? "https://registry.devkit.roboticela.com";
2
2
  async function get(path) {
3
3
  const res = await fetch(`${REGISTRY_URL}${path}`);
4
4
  if (!res.ok)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roboticela/devkit",
3
- "version": "2.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "Roboticela DevKit CLI — scaffold, extend, and theme full-stack projects with one command",
5
5
  "type": "module",
6
6
  "bin": {