create-propelkit 1.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 +131 -0
- package/bin/cli.js +6 -0
- package/package.json +31 -0
- package/src/cli-detector.js +104 -0
- package/src/config.js +25 -0
- package/src/design-flow.js +442 -0
- package/src/index.js +126 -0
- package/src/launcher.js +42 -0
- package/src/license-validator.js +85 -0
- package/src/messages.js +119 -0
- package/src/orchestrators/existing-designs.ts +176 -0
- package/src/page-mapper.js +247 -0
- package/src/prompt-generator.js +429 -0
- package/src/scenarios.js +217 -0
- package/src/setup-wizard.js +315 -0
- package/src/validators.js +153 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lovable Prompt Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates comprehensive Lovable prompts from design system and page list.
|
|
5
|
+
* Uses array-based markdown generation pattern (PropelKit convention).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Typography configurations for each style reference.
|
|
10
|
+
* @param {string} styleReference - One of: linear, notion, stripe, vercel, airbnb
|
|
11
|
+
* @returns {{ headings: string, body: string, characteristics: string }}
|
|
12
|
+
*/
|
|
13
|
+
function getTypography(styleReference) {
|
|
14
|
+
const typographyMap = {
|
|
15
|
+
linear: {
|
|
16
|
+
headings: 'Inter, system-ui, sans-serif',
|
|
17
|
+
body: 'Inter, system-ui, sans-serif',
|
|
18
|
+
characteristics: 'Clean, technical, precise'
|
|
19
|
+
},
|
|
20
|
+
notion: {
|
|
21
|
+
headings: 'Inter, ui-sans-serif, sans-serif',
|
|
22
|
+
body: 'Inter, ui-sans-serif, sans-serif',
|
|
23
|
+
characteristics: 'Friendly, approachable, readable'
|
|
24
|
+
},
|
|
25
|
+
stripe: {
|
|
26
|
+
headings: 'Sohne, system-ui, sans-serif',
|
|
27
|
+
body: 'Sohne, system-ui, sans-serif',
|
|
28
|
+
characteristics: 'Professional, elegant, refined'
|
|
29
|
+
},
|
|
30
|
+
vercel: {
|
|
31
|
+
headings: 'Geist Sans, system-ui, sans-serif',
|
|
32
|
+
body: 'Geist Sans, system-ui, sans-serif',
|
|
33
|
+
characteristics: 'Modern, geometric, minimal'
|
|
34
|
+
},
|
|
35
|
+
airbnb: {
|
|
36
|
+
headings: 'Cereal, system-ui, sans-serif',
|
|
37
|
+
body: 'Cereal, system-ui, sans-serif',
|
|
38
|
+
characteristics: 'Warm, inviting, human'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const key = styleReference.toLowerCase();
|
|
43
|
+
return typographyMap[key] || typographyMap.linear;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get human-readable style description.
|
|
48
|
+
* @param {string} styleReference - One of: linear, notion, stripe, vercel, airbnb
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
function getStyleDescription(styleReference) {
|
|
52
|
+
const descriptions = {
|
|
53
|
+
linear: 'Linear (minimal, dark mode friendly, clean typography)',
|
|
54
|
+
notion: 'Notion (friendly, content-focused, approachable)',
|
|
55
|
+
stripe: 'Stripe (professional, elegant, trustworthy)',
|
|
56
|
+
vercel: 'Vercel (modern, geometric, developer-focused)',
|
|
57
|
+
airbnb: 'Airbnb (warm, inviting, human-centered)'
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const key = styleReference.toLowerCase();
|
|
61
|
+
return descriptions[key] || descriptions.linear;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Page detail templates for each known page type.
|
|
66
|
+
* @param {string} pageName - Name of the page
|
|
67
|
+
* @returns {{ purpose: string, elements: string[], components: string, userFlow: string }}
|
|
68
|
+
*/
|
|
69
|
+
function getPageDetails(pageName) {
|
|
70
|
+
const pageTemplates = {
|
|
71
|
+
// Core pages
|
|
72
|
+
'Home': {
|
|
73
|
+
purpose: 'Landing page that introduces the product and drives conversions',
|
|
74
|
+
elements: [
|
|
75
|
+
'Hero section with value proposition',
|
|
76
|
+
'Feature highlights (3-4 key benefits)',
|
|
77
|
+
'Social proof (testimonials or logos)',
|
|
78
|
+
'Pricing preview or CTA section',
|
|
79
|
+
'Footer with navigation links'
|
|
80
|
+
],
|
|
81
|
+
components: 'Hero, FeatureCard, Testimonial, PricingPreview, Footer',
|
|
82
|
+
userFlow: 'User lands, scans value prop, reviews features, clicks CTA to sign up'
|
|
83
|
+
},
|
|
84
|
+
'Dashboard': {
|
|
85
|
+
purpose: 'Central hub showing key metrics and quick actions',
|
|
86
|
+
elements: [
|
|
87
|
+
'Welcome message with user name',
|
|
88
|
+
'Metrics cards (4-6 key numbers)',
|
|
89
|
+
'Recent activity feed',
|
|
90
|
+
'Quick action buttons',
|
|
91
|
+
'Status indicators or alerts'
|
|
92
|
+
],
|
|
93
|
+
components: 'MetricCard, ActivityFeed, Button, Card, Badge',
|
|
94
|
+
userFlow: 'User logs in, sees overview, takes quick action or dives deeper'
|
|
95
|
+
},
|
|
96
|
+
'Settings': {
|
|
97
|
+
purpose: 'User preferences and account management',
|
|
98
|
+
elements: [
|
|
99
|
+
'Profile section (name, email, avatar)',
|
|
100
|
+
'Notification preferences',
|
|
101
|
+
'Security settings (password, 2FA)',
|
|
102
|
+
'Connected accounts',
|
|
103
|
+
'Danger zone (delete account)'
|
|
104
|
+
],
|
|
105
|
+
components: 'Form, Input, Switch, Avatar, Tabs, AlertDialog',
|
|
106
|
+
userFlow: 'User navigates to settings, updates preferences, saves changes'
|
|
107
|
+
},
|
|
108
|
+
'404': {
|
|
109
|
+
purpose: 'Friendly error page for missing routes',
|
|
110
|
+
elements: [
|
|
111
|
+
'Clear error message',
|
|
112
|
+
'Illustration or icon',
|
|
113
|
+
'Link to home page',
|
|
114
|
+
'Search suggestion',
|
|
115
|
+
'Help contact option'
|
|
116
|
+
],
|
|
117
|
+
components: 'Card, Button, Input',
|
|
118
|
+
userFlow: 'User hits broken link, sees error, clicks to return home'
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Auth pages
|
|
122
|
+
'Login': {
|
|
123
|
+
purpose: 'Authenticate returning users',
|
|
124
|
+
elements: [
|
|
125
|
+
'Email input field',
|
|
126
|
+
'Password input with visibility toggle',
|
|
127
|
+
'Remember me checkbox',
|
|
128
|
+
'Login button',
|
|
129
|
+
'Forgot password link',
|
|
130
|
+
'Sign up redirect link'
|
|
131
|
+
],
|
|
132
|
+
components: 'Form, Input, Button, Checkbox, Card',
|
|
133
|
+
userFlow: 'User enters credentials, clicks login, redirected to dashboard'
|
|
134
|
+
},
|
|
135
|
+
'Signup': {
|
|
136
|
+
purpose: 'Register new users',
|
|
137
|
+
elements: [
|
|
138
|
+
'Name input field',
|
|
139
|
+
'Email input field',
|
|
140
|
+
'Password input with strength indicator',
|
|
141
|
+
'Terms and conditions checkbox',
|
|
142
|
+
'Sign up button',
|
|
143
|
+
'Login redirect link'
|
|
144
|
+
],
|
|
145
|
+
components: 'Form, Input, Button, Checkbox, Card, PasswordStrength',
|
|
146
|
+
userFlow: 'User fills form, accepts terms, clicks signup, receives welcome email'
|
|
147
|
+
},
|
|
148
|
+
'Forgot Password': {
|
|
149
|
+
purpose: 'Initiate password reset flow',
|
|
150
|
+
elements: [
|
|
151
|
+
'Email input field',
|
|
152
|
+
'Submit button',
|
|
153
|
+
'Success message area',
|
|
154
|
+
'Back to login link'
|
|
155
|
+
],
|
|
156
|
+
components: 'Form, Input, Button, Card, Alert',
|
|
157
|
+
userFlow: 'User enters email, clicks submit, checks inbox for reset link'
|
|
158
|
+
},
|
|
159
|
+
'Reset Password': {
|
|
160
|
+
purpose: 'Set new password after reset request',
|
|
161
|
+
elements: [
|
|
162
|
+
'New password input',
|
|
163
|
+
'Confirm password input',
|
|
164
|
+
'Password requirements list',
|
|
165
|
+
'Submit button',
|
|
166
|
+
'Success redirect'
|
|
167
|
+
],
|
|
168
|
+
components: 'Form, Input, Button, Card, Alert',
|
|
169
|
+
userFlow: 'User sets new password, confirms it, clicks submit, redirected to login'
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
// Payment pages
|
|
173
|
+
'Pricing': {
|
|
174
|
+
purpose: 'Display plans and drive subscription decisions',
|
|
175
|
+
elements: [
|
|
176
|
+
'Plan cards (3 tiers recommended)',
|
|
177
|
+
'Feature comparison table',
|
|
178
|
+
'Toggle for monthly/annual pricing',
|
|
179
|
+
'CTA buttons per plan',
|
|
180
|
+
'FAQ section',
|
|
181
|
+
'Money-back guarantee badge'
|
|
182
|
+
],
|
|
183
|
+
components: 'PricingCard, Table, Toggle, Button, Accordion, Badge',
|
|
184
|
+
userFlow: 'User compares plans, toggles billing cycle, selects plan, proceeds to checkout'
|
|
185
|
+
},
|
|
186
|
+
'Checkout': {
|
|
187
|
+
purpose: 'Complete payment transaction',
|
|
188
|
+
elements: [
|
|
189
|
+
'Order summary card',
|
|
190
|
+
'Payment method selection',
|
|
191
|
+
'Payment form (card details)',
|
|
192
|
+
'Billing address form',
|
|
193
|
+
'Apply coupon input',
|
|
194
|
+
'Secure payment badge',
|
|
195
|
+
'Pay button with amount'
|
|
196
|
+
],
|
|
197
|
+
components: 'Card, Form, Input, Button, RadioGroup, Badge',
|
|
198
|
+
userFlow: 'User reviews order, enters payment details, clicks pay, sees confirmation'
|
|
199
|
+
},
|
|
200
|
+
'Billing History': {
|
|
201
|
+
purpose: 'View past invoices and payment methods',
|
|
202
|
+
elements: [
|
|
203
|
+
'Invoice table with download links',
|
|
204
|
+
'Current subscription status',
|
|
205
|
+
'Payment methods list',
|
|
206
|
+
'Add payment method button',
|
|
207
|
+
'Cancel subscription option'
|
|
208
|
+
],
|
|
209
|
+
components: 'Table, Card, Button, Badge, Dialog',
|
|
210
|
+
userFlow: 'User views invoices, downloads receipts, manages payment methods'
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// Multi-tenancy pages
|
|
214
|
+
'Team Settings': {
|
|
215
|
+
purpose: 'Manage team configuration and branding',
|
|
216
|
+
elements: [
|
|
217
|
+
'Team name input',
|
|
218
|
+
'Team logo upload',
|
|
219
|
+
'Team URL/slug setting',
|
|
220
|
+
'Member list with roles',
|
|
221
|
+
'Remove member actions',
|
|
222
|
+
'Team deletion option'
|
|
223
|
+
],
|
|
224
|
+
components: 'Form, Input, Avatar, Table, Button, AlertDialog',
|
|
225
|
+
userFlow: 'Admin updates team info, manages members, saves changes'
|
|
226
|
+
},
|
|
227
|
+
'Invite Members': {
|
|
228
|
+
purpose: 'Add new team members via email invitation',
|
|
229
|
+
elements: [
|
|
230
|
+
'Email input field',
|
|
231
|
+
'Role selector dropdown',
|
|
232
|
+
'Send invite button',
|
|
233
|
+
'Pending invitations list',
|
|
234
|
+
'Resend/cancel invite actions'
|
|
235
|
+
],
|
|
236
|
+
components: 'Form, Input, Select, Button, Table, Badge',
|
|
237
|
+
userFlow: 'Admin enters email, selects role, sends invite, tracks pending invites'
|
|
238
|
+
},
|
|
239
|
+
'Role Management': {
|
|
240
|
+
purpose: 'Configure team roles and permissions',
|
|
241
|
+
elements: [
|
|
242
|
+
'Roles table',
|
|
243
|
+
'Permission matrix (role x permission)',
|
|
244
|
+
'Create role button',
|
|
245
|
+
'Edit role dialog',
|
|
246
|
+
'Default role indicator'
|
|
247
|
+
],
|
|
248
|
+
components: 'Table, Checkbox, Button, Dialog, Badge',
|
|
249
|
+
userFlow: 'Admin views roles, edits permissions, creates custom roles'
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
// Credits pages
|
|
253
|
+
'Usage Dashboard': {
|
|
254
|
+
purpose: 'Monitor credit consumption and balance',
|
|
255
|
+
elements: [
|
|
256
|
+
'Credit balance card',
|
|
257
|
+
'Usage chart (time series)',
|
|
258
|
+
'Usage breakdown by feature',
|
|
259
|
+
'Low credit warning',
|
|
260
|
+
'Top up CTA button'
|
|
261
|
+
],
|
|
262
|
+
components: 'Card, Chart, Progress, Alert, Button',
|
|
263
|
+
userFlow: 'User checks balance, reviews usage patterns, tops up if needed'
|
|
264
|
+
},
|
|
265
|
+
'Purchase Credits': {
|
|
266
|
+
purpose: 'Buy additional credits',
|
|
267
|
+
elements: [
|
|
268
|
+
'Credit package options',
|
|
269
|
+
'Quantity selector',
|
|
270
|
+
'Bulk discount indicator',
|
|
271
|
+
'Price summary',
|
|
272
|
+
'Checkout button'
|
|
273
|
+
],
|
|
274
|
+
components: 'Card, RadioGroup, Input, Button, Badge',
|
|
275
|
+
userFlow: 'User selects package, adjusts quantity, proceeds to payment'
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
// Admin pages
|
|
279
|
+
'Admin Panel': {
|
|
280
|
+
purpose: 'System administration and monitoring',
|
|
281
|
+
elements: [
|
|
282
|
+
'User statistics card',
|
|
283
|
+
'Revenue metrics',
|
|
284
|
+
'System health indicators',
|
|
285
|
+
'Recent signups list',
|
|
286
|
+
'Quick admin actions'
|
|
287
|
+
],
|
|
288
|
+
components: 'Card, Chart, Table, Badge, Button',
|
|
289
|
+
userFlow: 'Admin reviews metrics, monitors health, takes administrative actions'
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
// Profile page
|
|
293
|
+
'Profile': {
|
|
294
|
+
purpose: 'View and edit user profile information',
|
|
295
|
+
elements: [
|
|
296
|
+
'Avatar with upload option',
|
|
297
|
+
'Display name field',
|
|
298
|
+
'Email field (read-only)',
|
|
299
|
+
'Bio/about text area',
|
|
300
|
+
'Save button'
|
|
301
|
+
],
|
|
302
|
+
components: 'Form, Input, Textarea, Avatar, Button, Card',
|
|
303
|
+
userFlow: 'User views profile, updates information, saves changes'
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// Reports page
|
|
307
|
+
'Reports': {
|
|
308
|
+
purpose: 'View detailed analytics and generate reports',
|
|
309
|
+
elements: [
|
|
310
|
+
'Date range selector',
|
|
311
|
+
'Report type selector',
|
|
312
|
+
'Data visualization charts',
|
|
313
|
+
'Export options (CSV, PDF)',
|
|
314
|
+
'Saved reports list'
|
|
315
|
+
],
|
|
316
|
+
components: 'DatePicker, Select, Chart, Button, Table',
|
|
317
|
+
userFlow: 'User selects date range, chooses report type, views data, exports'
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Return template or fallback for unknown pages
|
|
322
|
+
return pageTemplates[pageName] || {
|
|
323
|
+
purpose: `${pageName} functionality`,
|
|
324
|
+
elements: [
|
|
325
|
+
'Main content area',
|
|
326
|
+
'Navigation elements',
|
|
327
|
+
'Action buttons',
|
|
328
|
+
'Status indicators'
|
|
329
|
+
],
|
|
330
|
+
components: 'Card, Button, Form',
|
|
331
|
+
userFlow: 'User interacts with page content and takes relevant actions'
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Generate comprehensive Lovable prompt from design system and pages.
|
|
337
|
+
* @param {{ primaryColor: string, accentColor: string, styleReference: string, brandVoice: string }} designSystem
|
|
338
|
+
* @param {string[]} pages - Array of page names
|
|
339
|
+
* @param {string} projectName - Name of the project
|
|
340
|
+
* @returns {string} - Complete markdown prompt for Lovable
|
|
341
|
+
*/
|
|
342
|
+
function generateLovablePrompt(designSystem, pages, projectName) {
|
|
343
|
+
const lines = [];
|
|
344
|
+
const { primaryColor, accentColor, styleReference, brandVoice } = designSystem;
|
|
345
|
+
const typography = getTypography(styleReference);
|
|
346
|
+
const styleDescription = getStyleDescription(styleReference);
|
|
347
|
+
|
|
348
|
+
// Header
|
|
349
|
+
lines.push(`# ${projectName} - Complete UI Design`);
|
|
350
|
+
lines.push('');
|
|
351
|
+
|
|
352
|
+
// Design System section
|
|
353
|
+
lines.push('## Design System');
|
|
354
|
+
lines.push('');
|
|
355
|
+
lines.push(`**Style Reference:** ${styleDescription}`);
|
|
356
|
+
lines.push(`**Primary Color:** ${primaryColor}`);
|
|
357
|
+
lines.push(`**Accent Color:** ${accentColor}`);
|
|
358
|
+
lines.push(`**Brand Voice:** ${brandVoice}`);
|
|
359
|
+
lines.push('');
|
|
360
|
+
lines.push('**Typography:**');
|
|
361
|
+
lines.push(`- Headings: ${typography.headings}`);
|
|
362
|
+
lines.push(`- Body: ${typography.body}`);
|
|
363
|
+
lines.push(`- Characteristics: ${typography.characteristics}`);
|
|
364
|
+
lines.push('');
|
|
365
|
+
|
|
366
|
+
// Anti-Patterns section (CRITICAL for 2026)
|
|
367
|
+
lines.push('## Anti-Patterns to Avoid');
|
|
368
|
+
lines.push('');
|
|
369
|
+
lines.push('Create distinctive, human-centered design. DO NOT use:');
|
|
370
|
+
lines.push('');
|
|
371
|
+
lines.push('- Generic purple-to-blue gradients (the "AI aesthetic")');
|
|
372
|
+
lines.push('- Stock photo placeholder images with perfect diversity casting');
|
|
373
|
+
lines.push('- Overly rounded everything (8px border-radius on all elements)');
|
|
374
|
+
lines.push('- Generic "Get Started" hero sections with floating mockups');
|
|
375
|
+
lines.push('- Emoji-fueled faux-minimalism that feels hollow');
|
|
376
|
+
lines.push('- Same-y corporate filler aesthetics with meaningless taglines');
|
|
377
|
+
lines.push('');
|
|
378
|
+
lines.push('**Instead:** Add texture, intentional friction, human imperfection, and genuine brand personality. Make it feel crafted, not generated.');
|
|
379
|
+
lines.push('');
|
|
380
|
+
|
|
381
|
+
// Pages section
|
|
382
|
+
lines.push('## Pages');
|
|
383
|
+
lines.push('');
|
|
384
|
+
|
|
385
|
+
for (let i = 0; i < pages.length; i++) {
|
|
386
|
+
const pageName = pages[i];
|
|
387
|
+
const details = getPageDetails(pageName);
|
|
388
|
+
|
|
389
|
+
lines.push(`### ${i + 1}. ${pageName}`);
|
|
390
|
+
lines.push('');
|
|
391
|
+
lines.push(`**Purpose:** ${details.purpose}`);
|
|
392
|
+
lines.push('');
|
|
393
|
+
lines.push('**Key Elements:**');
|
|
394
|
+
for (const element of details.elements) {
|
|
395
|
+
lines.push(`- ${element}`);
|
|
396
|
+
}
|
|
397
|
+
lines.push('');
|
|
398
|
+
lines.push(`**Components:** ${details.components}`);
|
|
399
|
+
lines.push('');
|
|
400
|
+
lines.push(`**User Flow:** ${details.userFlow}`);
|
|
401
|
+
lines.push('');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Global Notes section
|
|
405
|
+
lines.push('## Global Notes');
|
|
406
|
+
lines.push('');
|
|
407
|
+
lines.push('- Use Tailwind CSS with shadcn/ui components');
|
|
408
|
+
lines.push('- Follow responsive design principles (mobile-first)');
|
|
409
|
+
lines.push('- Ensure accessibility compliance (WCAG 2.1 AA)');
|
|
410
|
+
lines.push('- Include loading states for async operations');
|
|
411
|
+
lines.push('- Include empty states for lists and tables');
|
|
412
|
+
lines.push('- Add subtle hover effects and micro-interactions');
|
|
413
|
+
lines.push('- Use consistent spacing based on 4px/8px grid');
|
|
414
|
+
lines.push('- Implement smooth transitions (150-300ms duration)');
|
|
415
|
+
lines.push('');
|
|
416
|
+
|
|
417
|
+
// Footer
|
|
418
|
+
lines.push('---');
|
|
419
|
+
lines.push('*Generated by PropelKit AI PM*');
|
|
420
|
+
|
|
421
|
+
return lines.join('\n');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
module.exports = {
|
|
425
|
+
generateLovablePrompt,
|
|
426
|
+
getTypography,
|
|
427
|
+
getStyleDescription,
|
|
428
|
+
getPageDetails
|
|
429
|
+
};
|
package/src/scenarios.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const _messages = require('./messages'); // Reserved for future use
|
|
7
|
+
const { REPOS } = require('./config');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Ask user which scenario applies
|
|
11
|
+
* @returns {Promise<'fresh'|'existing'>}
|
|
12
|
+
*/
|
|
13
|
+
async function askScenario() {
|
|
14
|
+
const { scenario } = await inquirer.prompt([
|
|
15
|
+
{
|
|
16
|
+
type: 'list',
|
|
17
|
+
name: 'scenario',
|
|
18
|
+
message: 'What would you like to do?',
|
|
19
|
+
choices: [
|
|
20
|
+
{
|
|
21
|
+
name: 'Start fresh - Clone PropelKit and create new project',
|
|
22
|
+
value: 'fresh'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'Configure existing - I already cloned PropelKit',
|
|
26
|
+
value: 'existing'
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]);
|
|
31
|
+
return scenario;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Handle fresh start scenario - clone repo
|
|
36
|
+
* @param {string} tier - License tier ('starter' or 'pro')
|
|
37
|
+
* @param {object} clis - CLI detection results
|
|
38
|
+
* @param {boolean} useGitHub - Whether user wants GitHub integration (from CLI-03 prompt)
|
|
39
|
+
* @returns {Promise<string>} - Path to project directory
|
|
40
|
+
*/
|
|
41
|
+
async function handleFreshStart(tier, clis, useGitHub) {
|
|
42
|
+
// Ask for project name/directory
|
|
43
|
+
const { projectName } = await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'input',
|
|
46
|
+
name: 'projectName',
|
|
47
|
+
message: 'Project name (will create directory):',
|
|
48
|
+
default: 'my-saas',
|
|
49
|
+
validate: (input) => {
|
|
50
|
+
if (!input.trim()) return 'Project name is required';
|
|
51
|
+
if (fs.existsSync(input)) return `Directory "${input}" already exists`;
|
|
52
|
+
if (!/^[a-z0-9-_]+$/i.test(input)) return 'Use letters, numbers, dashes, underscores only';
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
59
|
+
const repoUrl = REPOS[tier]; // 'starter' or 'pro'
|
|
60
|
+
const tierLabel = tier.charAt(0).toUpperCase() + tier.slice(1);
|
|
61
|
+
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log(chalk.cyan(`Cloning PropelKit ${tierLabel}...`));
|
|
64
|
+
console.log(chalk.dim(` From: ${repoUrl}`));
|
|
65
|
+
console.log(chalk.dim(` To: ${targetDir}`));
|
|
66
|
+
console.log('');
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Clone the repo
|
|
70
|
+
execSync(`git clone ${repoUrl} "${targetDir}"`, {
|
|
71
|
+
stdio: 'inherit'
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Remove .git to start fresh
|
|
75
|
+
const gitDir = path.join(targetDir, '.git');
|
|
76
|
+
if (fs.existsSync(gitDir)) {
|
|
77
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Initialize fresh git repo
|
|
81
|
+
execSync('git init', { cwd: targetDir, stdio: 'pipe' });
|
|
82
|
+
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(chalk.green(`PropelKit ${tierLabel} cloned successfully!`));
|
|
85
|
+
console.log('');
|
|
86
|
+
|
|
87
|
+
// Write initial config with CLI availability, useGitHub preference, and tier
|
|
88
|
+
await writeInitialConfig(targetDir, clis, useGitHub, tier);
|
|
89
|
+
|
|
90
|
+
return targetDir;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new Error(`Failed to clone PropelKit: ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Handle existing clone scenario
|
|
98
|
+
* @param {string} tier - License tier ('starter' or 'pro')
|
|
99
|
+
* @param {object} clis - CLI detection results
|
|
100
|
+
* @param {boolean} useGitHub - Whether user wants GitHub integration (from CLI-03 prompt)
|
|
101
|
+
* @returns {Promise<string>} - Path to project directory
|
|
102
|
+
*/
|
|
103
|
+
async function handleExistingClone(tier, clis, useGitHub) {
|
|
104
|
+
const currentDir = process.cwd();
|
|
105
|
+
|
|
106
|
+
// Check if this looks like a PropelKit project
|
|
107
|
+
const hasPackageJson = fs.existsSync(path.join(currentDir, 'package.json'));
|
|
108
|
+
const hasPropelkitCommands = fs.existsSync(path.join(currentDir, '.claude', 'commands', 'propelkit'));
|
|
109
|
+
|
|
110
|
+
if (!hasPackageJson) {
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(chalk.yellow('Warning: No package.json found in current directory.'));
|
|
113
|
+
console.log(chalk.dim('Make sure you\'re in the PropelKit project root.'));
|
|
114
|
+
console.log('');
|
|
115
|
+
|
|
116
|
+
const { proceed } = await inquirer.prompt([
|
|
117
|
+
{
|
|
118
|
+
type: 'confirm',
|
|
119
|
+
name: 'proceed',
|
|
120
|
+
message: 'Continue anyway?',
|
|
121
|
+
default: false
|
|
122
|
+
}
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
if (!proceed) {
|
|
126
|
+
throw new Error('Aborted by user');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!hasPropelkitCommands) {
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log(chalk.yellow('Warning: PropelKit commands not found at .claude/commands/propelkit/'));
|
|
133
|
+
console.log(chalk.dim('This may not be a PropelKit project or it may be outdated.'));
|
|
134
|
+
console.log('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Write config with CLI availability, useGitHub preference, and tier
|
|
138
|
+
await writeInitialConfig(currentDir, clis, useGitHub, tier);
|
|
139
|
+
|
|
140
|
+
return currentDir;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Write initial .planning/config.json with CLI availability, GitHub preference, and tier
|
|
145
|
+
* @param {string} projectDir - Project directory
|
|
146
|
+
* @param {object[]} clis - CLI detection results
|
|
147
|
+
* @param {boolean} useGitHub - Whether user wants GitHub integration (CLI-03)
|
|
148
|
+
* @param {string} tier - License tier ('starter' or 'pro')
|
|
149
|
+
*/
|
|
150
|
+
async function writeInitialConfig(projectDir, clis, useGitHub, tier) {
|
|
151
|
+
const planningDir = path.join(projectDir, '.planning');
|
|
152
|
+
|
|
153
|
+
// Create .planning directory if needed
|
|
154
|
+
if (!fs.existsSync(planningDir)) {
|
|
155
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Build cliAvailable object from detection results
|
|
159
|
+
const cliAvailable = {};
|
|
160
|
+
for (const cli of clis) {
|
|
161
|
+
cliAvailable[cli.name] = cli.installed;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check if config already exists
|
|
165
|
+
const configPath = path.join(planningDir, 'config.json');
|
|
166
|
+
let config = {};
|
|
167
|
+
|
|
168
|
+
if (fs.existsSync(configPath)) {
|
|
169
|
+
try {
|
|
170
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
171
|
+
} catch (_e) {
|
|
172
|
+
// Invalid JSON, start fresh
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Merge CLI availability into config
|
|
177
|
+
config.cliAvailable = cliAvailable;
|
|
178
|
+
|
|
179
|
+
// Store GitHub integration preference (CLI-03 requirement)
|
|
180
|
+
config.useGitHub = useGitHub;
|
|
181
|
+
|
|
182
|
+
// Store tier for later use
|
|
183
|
+
config.tier = tier;
|
|
184
|
+
|
|
185
|
+
// Write config
|
|
186
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
187
|
+
|
|
188
|
+
console.log(chalk.dim(`CLI availability saved to .planning/config.json`));
|
|
189
|
+
console.log(chalk.dim(`Tier: ${tier}`));
|
|
190
|
+
if (useGitHub) {
|
|
191
|
+
console.log(chalk.dim(`GitHub integration: enabled`));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Main scenario handler
|
|
197
|
+
* @param {string} tier - License tier ('starter' or 'pro')
|
|
198
|
+
* @param {object[]} clis - CLI detection results
|
|
199
|
+
* @param {boolean} useGitHub - Whether user wants GitHub integration (from CLI-03 prompt)
|
|
200
|
+
* @returns {Promise<string>} - Path to project directory
|
|
201
|
+
*/
|
|
202
|
+
async function handleScenario(tier, clis, useGitHub) {
|
|
203
|
+
const scenario = await askScenario();
|
|
204
|
+
|
|
205
|
+
if (scenario === 'fresh') {
|
|
206
|
+
return handleFreshStart(tier, clis, useGitHub);
|
|
207
|
+
} else {
|
|
208
|
+
return handleExistingClone(tier, clis, useGitHub);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
askScenario,
|
|
214
|
+
handleFreshStart,
|
|
215
|
+
handleExistingClone,
|
|
216
|
+
handleScenario
|
|
217
|
+
};
|