@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 +632 -0
- package/dist/commands/add.js +103 -4
- package/dist/commands/eject.d.ts +1 -0
- package/dist/commands/eject.js +60 -0
- package/dist/index.js +11 -20
- package/dist/lib/config.js +1 -1
- package/dist/lib/installer.js +1 -1
- package/dist/lib/registry.js +1 -1
- package/package.json +1 -1
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)
|
package/dist/commands/add.js
CHANGED
|
@@ -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
|
-
|
|
9
|
-
|
|
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,
|
|
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
|
|
40
|
-
.description("Install
|
|
41
|
-
.option("--variant <id>", "Variant
|
|
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
|
|
128
|
-
.description("Stop DevKit from tracking
|
|
129
|
-
.action(
|
|
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://
|
|
144
|
+
${chalk.dim("Registry:")} ${process.env.DEVKIT_REGISTRY ?? "https://registry.devkit.roboticela.com"}
|
|
154
145
|
`);
|
|
155
146
|
program.parse();
|
package/dist/lib/config.js
CHANGED
|
@@ -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 },
|
package/dist/lib/installer.js
CHANGED
|
@@ -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://
|
|
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");
|
package/dist/lib/registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const REGISTRY_URL = process.env.DEVKIT_REGISTRY ?? "https://
|
|
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)
|