start-vibing-stacks 1.8.1 โ†’ 1.9.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/dist/index.js CHANGED
@@ -45,11 +45,9 @@ if (FLAGS.help) {
45
45
  --version, -v Show version
46
46
 
47
47
  ${chalk.bold('Supported Stacks:')}
48
- ๐Ÿ˜ PHP 8.3+ Laravel, Symfony, CodeIgniter, Vanilla
48
+ ๐Ÿ˜ PHP 8.3+ Laravel 12 + Octane + Inertia.js (React)
49
49
  ๐Ÿ“ฆ Node.js/TS Next.js, Nuxt, Express, Fastify
50
- ๐Ÿ Python Django, FastAPI, Flask (coming soon)
51
- ๐Ÿฆ€ Rust (coming soon)
52
- ๐Ÿน Go (coming soon)
50
+ ๐Ÿ Python 3.12+ FastAPI, Django, Flask
53
51
  `);
54
52
  process.exit(0);
55
53
  }
package/dist/ui.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Start Vibing Stacks โ€” Terminal UI
3
3
  */
4
4
  import chalk from 'chalk';
5
- const VERSION = '1.8.1';
5
+ const VERSION = '1.9.0';
6
6
  const gradient = (text) => {
7
7
  const colors = [chalk.hex('#FF6B6B'), chalk.hex('#FF8E53'), chalk.hex('#FFBD2E'), chalk.hex('#48BB78'), chalk.hex('#4299E1'), chalk.hex('#9F7AEA')];
8
8
  return text.split('').map((c, i) => colors[i % colors.length](c)).join('');
@@ -12,7 +12,7 @@ ${chalk.hex('#9F7AEA')(' โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
12
12
  ${chalk.hex('#9F7AEA')(' โ•‘')} ${chalk.hex('#9F7AEA')('โ•‘')}
13
13
  ${chalk.hex('#9F7AEA')(' โ•‘')} ${chalk.hex('#FF6B6B').bold('โšก')} ${chalk.bold.white('S T A R T')} ${chalk.bold.white('V I B I N G')} ${chalk.bold.white('S T A C K S')} ${chalk.hex('#FF6B6B').bold('โšก')} ${chalk.hex('#9F7AEA')('โ•‘')}
14
14
  ${chalk.hex('#9F7AEA')(' โ•‘')} ${chalk.hex('#9F7AEA')('โ•‘')}
15
- ${chalk.hex('#9F7AEA')(' โ•‘')} ${chalk.hex('#FF8E53')('๐Ÿ˜ PHP')} ${chalk.hex('#48BB78')('๐Ÿ“ฆ Node')} ${chalk.hex('#4299E1')('๐Ÿ Python')} ${chalk.hex('#FF6B6B')('๐Ÿฆ€ Rust')} ${chalk.hex('#FFBD2E')('๐Ÿน Go')} ${chalk.hex('#9F7AEA')('โ•‘')}
15
+ ${chalk.hex('#9F7AEA')(' โ•‘')} ${chalk.hex('#FF8E53')('๐Ÿ˜ PHP')} ${chalk.hex('#48BB78')('๐Ÿ“ฆ Node.js')} ${chalk.hex('#4299E1')('๐Ÿ Python')} ${chalk.hex('#9F7AEA')('โ•‘')}
16
16
  ${chalk.hex('#9F7AEA')(' โ•‘')} ${chalk.hex('#9F7AEA')('โ•‘')}
17
17
  ${chalk.hex('#9F7AEA')(' โ•‘')} ${chalk.dim('AI-powered dev workflow ยท 33 skills ยท v' + VERSION)} ${chalk.hex('#9F7AEA')('โ•‘')}
18
18
  ${chalk.hex('#9F7AEA')(' โ•‘')} ${chalk.hex('#9F7AEA')('โ•‘')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing-stacks",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "AI-powered multi-stack dev workflow for Claude Code. Supports PHP, Node.js, Python and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,198 @@
1
+ # Inertia.js + React โ€” Laravel Frontend
2
+
3
+ **ALWAYS invoke when writing Inertia.js pages, components, or shared data.**
4
+
5
+ ## How Inertia Works
6
+
7
+ ```
8
+ Browser โ†โ†’ Inertia.js โ†โ†’ Laravel Controller
9
+ (no API needed)
10
+
11
+ - First request: full HTML (SSR or SPA)
12
+ - Subsequent: XHR with JSON props โ†’ React re-renders
13
+ - No API routes needed โ€” controllers return Inertia::render()
14
+ ```
15
+
16
+ ## Controller Pattern
17
+
18
+ ```php
19
+ use Inertia\Inertia;
20
+
21
+ class UserController extends Controller
22
+ {
23
+ public function index(): \Inertia\Response
24
+ {
25
+ return Inertia::render('Users/Index', [
26
+ 'users' => User::query()
27
+ ->select('id', 'name', 'email', 'created_at')
28
+ ->orderByDesc('created_at')
29
+ ->paginate(20),
30
+ 'filters' => request()->only(['search', 'role']),
31
+ ]);
32
+ }
33
+
34
+ public function store(StoreUserRequest $request): \Illuminate\Http\RedirectResponse
35
+ {
36
+ User::create($request->validated());
37
+ return redirect()->route('users.index')
38
+ ->with('success', 'User created.');
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## React Page Component
44
+
45
+ ```tsx
46
+ // resources/js/Pages/Users/Index.tsx
47
+ import { Head, Link, router } from '@inertiajs/react';
48
+ import { PageProps, User, PaginatedData } from '@/types';
49
+
50
+ interface Props extends PageProps {
51
+ users: PaginatedData<User>;
52
+ filters: { search?: string; role?: string };
53
+ }
54
+
55
+ export default function UsersIndex({ users, filters }: Props) {
56
+ return (
57
+ <>
58
+ <Head title="Users" />
59
+
60
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
61
+ {/* Search */}
62
+ <input
63
+ defaultValue={filters.search}
64
+ onChange={(e) => router.get('/users', { search: e.target.value }, {
65
+ preserveState: true,
66
+ replace: true,
67
+ })}
68
+ placeholder="Search..."
69
+ className="border rounded-lg px-4 py-2"
70
+ />
71
+
72
+ {/* List */}
73
+ {users.data.map((user) => (
74
+ <div key={user.id} className="p-4 border-b">
75
+ <Link href={`/users/${user.id}`} className="text-blue-600 hover:underline">
76
+ {user.name}
77
+ </Link>
78
+ </div>
79
+ ))}
80
+
81
+ {/* Pagination */}
82
+ {users.links.map((link, i) => (
83
+ <Link key={i} href={link.url ?? ''} className={link.active ? 'font-bold' : ''}>
84
+ <span dangerouslySetInnerHTML={{ __html: link.label }} />
85
+ </Link>
86
+ ))}
87
+ </div>
88
+ </>
89
+ );
90
+ }
91
+ ```
92
+
93
+ ## Shared Data (Layout Props)
94
+
95
+ ```php
96
+ // app/Http/Middleware/HandleInertiaRequests.php
97
+ public function share(Request $request): array
98
+ {
99
+ return [
100
+ ...parent::share($request),
101
+ 'auth' => [
102
+ 'user' => $request->user()?->only('id', 'name', 'email', 'avatar'),
103
+ ],
104
+ 'flash' => [
105
+ 'success' => session('success'),
106
+ 'error' => session('error'),
107
+ ],
108
+ ];
109
+ }
110
+ ```
111
+
112
+ ```tsx
113
+ // Access in any component
114
+ import { usePage } from '@inertiajs/react';
115
+
116
+ const { auth, flash } = usePage().props;
117
+ ```
118
+
119
+ ## Forms (useForm hook)
120
+
121
+ ```tsx
122
+ import { useForm } from '@inertiajs/react';
123
+
124
+ export default function CreateUser() {
125
+ const { data, setData, post, processing, errors } = useForm({
126
+ name: '',
127
+ email: '',
128
+ password: '',
129
+ });
130
+
131
+ const submit = (e: React.FormEvent) => {
132
+ e.preventDefault();
133
+ post('/users');
134
+ };
135
+
136
+ return (
137
+ <form onSubmit={submit}>
138
+ <input value={data.name} onChange={e => setData('name', e.target.value)} />
139
+ {errors.name && <span className="text-red-500 text-sm">{errors.name}</span>}
140
+
141
+ <input value={data.email} onChange={e => setData('email', e.target.value)} />
142
+ {errors.email && <span className="text-red-500 text-sm">{errors.email}</span>}
143
+
144
+ <button type="submit" disabled={processing} className="bg-blue-600 text-white px-4 py-2 rounded-lg disabled:opacity-50">
145
+ {processing ? 'Saving...' : 'Create'}
146
+ </button>
147
+ </form>
148
+ );
149
+ }
150
+ ```
151
+
152
+ ## TypeScript Types
153
+
154
+ ```tsx
155
+ // resources/js/types/index.d.ts
156
+ export interface PageProps {
157
+ auth: { user: User | null };
158
+ flash: { success?: string; error?: string };
159
+ }
160
+
161
+ export interface User {
162
+ id: string;
163
+ name: string;
164
+ email: string;
165
+ avatar?: string;
166
+ created_at: string;
167
+ }
168
+
169
+ export interface PaginatedData<T> {
170
+ data: T[];
171
+ links: { url: string | null; label: string; active: boolean }[];
172
+ current_page: number;
173
+ last_page: number;
174
+ per_page: number;
175
+ total: number;
176
+ }
177
+ ```
178
+
179
+ ## TailwindCSS 4 Setup
180
+
181
+ ```css
182
+ /* resources/css/app.css */
183
+ @import "tailwindcss";
184
+
185
+ @theme {
186
+ --color-primary: #3b82f6;
187
+ --color-primary-foreground: #ffffff;
188
+ --font-sans: 'Inter', sans-serif;
189
+ }
190
+ ```
191
+
192
+ ## FORBIDDEN
193
+
194
+ 1. **API routes for Inertia pages** โ€” use `Inertia::render()` in controllers
195
+ 2. **`window.location` for navigation** โ€” use `router.visit()` or `<Link>`
196
+ 3. **Fetching data in useEffect** โ€” pass as props from controller
197
+ 4. **Duplicating validation** โ€” validate in FormRequest, show errors from `useForm`
198
+ 5. **`any` types for page props** โ€” always type with `interface Props extends PageProps`
@@ -1,159 +1,66 @@
1
1
  {
2
2
  "id": "php",
3
3
  "name": "PHP",
4
- "icon": "\ud83d\udc18",
4
+ "icon": "๐Ÿ˜",
5
5
  "runtime": "PHP 8.3+",
6
6
  "minVersion": "8.3.0",
7
7
  "packageManager": "composer",
8
- "extensions": [
9
- ".php",
10
- ".blade.php",
11
- ".twig",
12
- ".phtml"
13
- ],
14
- "testExtensions": [
15
- "*Test.php",
16
- "*_test.php"
17
- ],
18
- "detectFiles": [
19
- "composer.json",
20
- "index.php",
21
- "artisan",
22
- "public/index.php"
23
- ],
8
+ "extensions": [".php", ".blade.php"],
9
+ "testExtensions": ["*Test.php", "*_test.php"],
10
+ "detectFiles": ["composer.json", "artisan", "public/index.php"],
24
11
  "commands": {
25
12
  "test": "vendor/bin/phpunit",
26
13
  "lint": "vendor/bin/phpstan analyse --level=6",
27
14
  "format": "vendor/bin/php-cs-fixer fix",
28
- "serve": "php -S localhost:8000 -t public/",
29
- "build": null
15
+ "serve": "php artisan octane:start --watch",
16
+ "build": "npm run build"
30
17
  },
31
18
  "qualityGates": [
32
- {
33
- "name": "PHPStan",
34
- "command": "vendor/bin/phpstan analyse --level=6",
35
- "required": true,
36
- "order": 1
37
- },
38
- {
39
- "name": "PHPUnit",
40
- "command": "vendor/bin/phpunit",
41
- "required": true,
42
- "order": 2
43
- },
44
- {
45
- "name": "CS Fixer",
46
- "command": "vendor/bin/php-cs-fixer fix --dry-run --diff",
47
- "required": false,
48
- "order": 3
49
- }
19
+ { "name": "PHPStan", "command": "vendor/bin/phpstan analyse --level=6", "required": true, "order": 1 },
20
+ { "name": "PHPUnit", "command": "vendor/bin/phpunit", "required": true, "order": 2 },
21
+ { "name": "TypeCheck", "command": "npx tsc --noEmit", "required": true, "order": 3 },
22
+ { "name": "Lint", "command": "npx eslint resources/js/", "required": false, "order": 4 }
50
23
  ],
51
24
  "frameworks": [
52
- {
53
- "id": "laravel",
54
- "name": "Laravel",
55
- "icon": "\ud83c\udfd7\ufe0f",
56
- "detectFiles": [
57
- "artisan",
58
- "bootstrap/app.php"
59
- ]
60
- },
61
25
  {
62
26
  "id": "laravel-octane",
63
- "name": "Laravel + Octane (RoadRunner)",
64
- "icon": "\ud83d\ude80",
65
- "detectFiles": [
66
- "artisan",
67
- "rr.yaml"
68
- ]
69
- },
70
- {
71
- "id": "symfony",
72
- "name": "Symfony",
73
- "icon": "\ud83c\udfb5",
74
- "detectFiles": [
75
- "symfony.lock"
76
- ]
27
+ "name": "Laravel 12 + Octane (RoadRunner) + Inertia.js",
28
+ "icon": "๐Ÿš€",
29
+ "detectFiles": ["artisan", "rr.yaml"],
30
+ "default": true
77
31
  },
78
32
  {
79
- "id": "codeigniter",
80
- "name": "CodeIgniter",
81
- "icon": "\ud83e\udde9",
82
- "detectFiles": [
83
- "spark"
84
- ]
85
- },
86
- {
87
- "id": "vanilla",
88
- "name": "Vanilla PHP",
89
- "icon": "\ud83d\udcc4"
33
+ "id": "laravel",
34
+ "name": "Laravel 12 (standard)",
35
+ "icon": "๐Ÿ—๏ธ",
36
+ "detectFiles": ["artisan", "bootstrap/app.php"]
90
37
  }
91
38
  ],
92
39
  "databases": [
93
- {
94
- "id": "mysql",
95
- "name": "MySQL / MariaDB",
96
- "icon": "\ud83d\udc2c"
97
- },
98
- {
99
- "id": "postgresql",
100
- "name": "PostgreSQL",
101
- "icon": "\ud83d\udc18"
102
- },
103
- {
104
- "id": "mongodb",
105
- "name": "MongoDB",
106
- "icon": "\ud83c\udf43"
107
- },
108
- {
109
- "id": "sqlite",
110
- "name": "SQLite",
111
- "icon": "\ud83d\udcc1"
112
- },
113
- {
114
- "id": "none",
115
- "name": "None",
116
- "icon": "\u274c"
117
- }
40
+ { "id": "mysql", "name": "MySQL / MariaDB", "icon": "๐Ÿฌ" },
41
+ { "id": "postgresql", "name": "PostgreSQL", "icon": "๐Ÿ˜" },
42
+ { "id": "sqlite", "name": "SQLite", "icon": "๐Ÿ“" }
118
43
  ],
119
44
  "frontendOptions": [
120
45
  {
121
- "id": "react-tailwind",
122
- "name": "ReactJS 19+ / TailwindCSS 4+",
123
- "icon": "\u269b\ufe0f"
124
- },
125
- {
126
- "id": "tailwind-vanilla",
127
- "name": "TailwindCSS 4+ / Vanilla JS",
128
- "icon": "\ud83c\udfa8"
46
+ "id": "react-inertia",
47
+ "name": "ReactJS 19 + Inertia.js + TailwindCSS 4",
48
+ "icon": "โš›๏ธ",
49
+ "default": true
129
50
  },
130
51
  {
131
52
  "id": "blade",
132
- "name": "Blade Templates (Laravel)",
133
- "icon": "\ud83d\uddbc\ufe0f"
134
- },
135
- {
136
- "id": "livewire",
137
- "name": "Livewire + Alpine.js",
138
- "icon": "\u26a1"
139
- },
140
- {
141
- "id": "twig",
142
- "name": "Twig Templates (Symfony)",
143
- "icon": "\ud83c\udf3f"
53
+ "name": "Blade + TailwindCSS 4",
54
+ "icon": "๐Ÿ–ผ๏ธ"
144
55
  },
145
56
  {
146
57
  "id": "none",
147
- "name": "API only \u2014 no frontend",
148
- "icon": "\u274c"
58
+ "name": "API only โ€” no frontend",
59
+ "icon": "๐Ÿ”Œ"
149
60
  }
150
61
  ],
151
62
  "deployTargets": [
152
- {
153
- "id": "github",
154
- "name": "GitHub (git push)",
155
- "icon": "\ud83d\udc19"
156
- }
63
+ { "id": "github", "name": "GitHub (git push)", "icon": "๐Ÿ™" }
157
64
  ],
158
65
  "skills": [
159
66
  "php-patterns",
@@ -192,10 +99,10 @@
192
99
  "name": "Node.js",
193
100
  "command": "node",
194
101
  "versionFlag": "--version",
195
- "minVersion": "18.0.0",
102
+ "minVersion": "20.0.0",
196
103
  "installCommand": {
197
104
  "macos": "brew install node",
198
- "linux": "curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt install -y nodejs"
105
+ "linux": "curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs"
199
106
  },
200
107
  "versionRegex": "v?(\\d+\\.\\d+\\.\\d+)"
201
108
  }