create-propelkit 1.0.5 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-propelkit",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Initialize a PropelKit SaaS project with AI PM",
5
5
  "bin": "./bin/cli.js",
6
6
  "main": "./src/index.js",
@@ -0,0 +1,68 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Generates vercel.json configuration file for Vercel deployment
6
+ * @param {string} projectDir - Absolute path to project directory
7
+ * @param {object} chalk - Chalk instance for colored output
8
+ */
9
+ function generateVercelJson(projectDir, chalk) {
10
+ const vercelJsonPath = path.join(projectDir, 'vercel.json');
11
+
12
+ // Check if vercel.json already exists
13
+ if (fs.existsSync(vercelJsonPath)) {
14
+ console.log(chalk.dim(' vercel.json exists, skipping'));
15
+ return;
16
+ }
17
+
18
+ const config = {
19
+ "$schema": "https://openapi.vercel.sh/vercel.json",
20
+ "framework": "nextjs",
21
+ "buildCommand": "npm run build",
22
+ "installCommand": "npm ci"
23
+ };
24
+
25
+ fs.writeFileSync(vercelJsonPath, JSON.stringify(config, null, 2) + '\n');
26
+ console.log(chalk.dim(' Created vercel.json'));
27
+ }
28
+
29
+ /**
30
+ * Generates railway.toml configuration file for Railway deployment
31
+ * @param {string} projectDir - Absolute path to project directory
32
+ * @param {object} chalk - Chalk instance for colored output
33
+ */
34
+ function generateRailwayToml(projectDir, chalk) {
35
+ const railwayTomlPath = path.join(projectDir, 'railway.toml');
36
+
37
+ // Check if railway.toml already exists
38
+ if (fs.existsSync(railwayTomlPath)) {
39
+ console.log(chalk.dim(' railway.toml exists, skipping'));
40
+ return;
41
+ }
42
+
43
+ const config = `[build]
44
+ builder = "NIXPACKS"
45
+
46
+ [build.nixpacksPlan.phases.setup]
47
+ nixPkgs = ["nodejs_20"]
48
+
49
+ [build.nixpacksPlan.phases.install]
50
+ cmds = ["npm ci"]
51
+
52
+ [build.nixpacksPlan.phases.build]
53
+ cmds = ["npm run build"]
54
+
55
+ [deploy]
56
+ startCommand = "npm run start"
57
+ restartPolicyType = "on_failure"
58
+ restartPolicyMaxRetries = 3
59
+ `;
60
+
61
+ fs.writeFileSync(railwayTomlPath, config);
62
+ console.log(chalk.dim(' Created railway.toml'));
63
+ }
64
+
65
+ module.exports = {
66
+ generateVercelJson,
67
+ generateRailwayToml
68
+ };
package/src/config.js CHANGED
@@ -9,8 +9,8 @@ const API_ENDPOINTS = {
9
9
  };
10
10
 
11
11
  const LICENSE_PATTERNS = {
12
- // PK-STARTER-2026-XXXXXX or PK-PRO-2026-XXXXXX
13
- format: /^PK-(STARTER|PRO|AGENCY)-\d{4}-[A-Z0-9]{6}$/
12
+ // PK-STARTER-2026-XXXXXX (6 chars) or PK-PRO-2026-75AA835B178345DD (16 chars)
13
+ format: /^PK-(STARTER|PRO|AGENCY)-\d{4}-[A-Z0-9]{6,16}$/
14
14
  };
15
15
 
16
16
  const TIMEOUTS = {
@@ -0,0 +1,220 @@
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const { detectPlatformCLI } = require('./platform-detector');
6
+ const { readEnvFile, getRequiredEnvVars, promptMissingEnvVars } = require('./env-mapper');
7
+ const { generateVercelJson, generateRailwayToml } = require('./config-generator');
8
+ const { checkVercelAuth, deployToVercel } = require('./vercel-deployer');
9
+ const { deployToRailway } = require('./railway-deployer');
10
+ const { displayWebhookGuidance } = require('./webhook-guidance');
11
+
12
+ /**
13
+ * Main deployment command orchestrator
14
+ * Handles platform selection, CLI detection, env vars, and deployment
15
+ */
16
+ async function deployCommand() {
17
+ // Step 1: Determine project directory
18
+ const projectDir = process.cwd();
19
+
20
+ // Step 2: Verify PropelKit project
21
+ const featuresPath = path.join(projectDir, 'src', 'config', 'features.ts');
22
+ if (!fs.existsSync(featuresPath)) {
23
+ throw new Error('Not a PropelKit project (src/config/features.ts not found)');
24
+ }
25
+
26
+ // Step 3: Read payment processor from features.ts
27
+ const featuresContent = fs.readFileSync(featuresPath, 'utf-8');
28
+ const processorMatch = featuresContent.match(/paymentProcessor:\s*['"](\w+)['"]/);
29
+ const paymentProcessor = processorMatch ? processorMatch[1] : 'razorpay';
30
+
31
+ // Step 4: Display header
32
+ console.log('');
33
+ console.log(chalk.cyan.bold('🚀 PropelKit Deployment'));
34
+ console.log(chalk.dim('Deploy your SaaS to the cloud'));
35
+ console.log('');
36
+
37
+ // Step 5: Platform selection prompt
38
+ const { platform } = await inquirer.prompt([
39
+ {
40
+ type: 'list',
41
+ name: 'platform',
42
+ message: 'Select deployment platform:',
43
+ choices: [
44
+ { name: 'Vercel (Recommended)', value: 'vercel' },
45
+ { name: 'Railway', value: 'railway' },
46
+ { name: 'Manual (config files only)', value: 'manual' }
47
+ ]
48
+ }
49
+ ]);
50
+
51
+ // Step 6: Handle manual deployment
52
+ if (platform === 'manual') {
53
+ await handleManualDeploy(projectDir, paymentProcessor, chalk);
54
+ return;
55
+ }
56
+
57
+ // Step 7: CLI detection
58
+ const cliDetection = await detectPlatformCLI(platform);
59
+ if (!cliDetection.installed) {
60
+ console.log('');
61
+ console.log(chalk.red(`${cliDetection.config.label} is not installed.`));
62
+ console.log('');
63
+ console.log(chalk.yellow('Installation options:'));
64
+ Object.entries(cliDetection.config.installCommand).forEach(([method, command]) => {
65
+ console.log(chalk.dim(` ${method}: ${command}`));
66
+ });
67
+ console.log('');
68
+ console.log(chalk.dim(`Documentation: ${cliDetection.config.docs}`));
69
+ console.log('');
70
+ process.exit(1);
71
+ }
72
+
73
+ // Step 8: Vercel authentication check
74
+ if (platform === 'vercel') {
75
+ const authCheck = await checkVercelAuth();
76
+ if (!authCheck.authenticated) {
77
+ console.log('');
78
+ console.log(chalk.red('Not authenticated with Vercel CLI.'));
79
+ console.log('');
80
+ console.log(chalk.yellow('Please run:'));
81
+ console.log(chalk.bold(' vercel login'));
82
+ console.log('');
83
+ process.exit(1);
84
+ }
85
+ console.log('');
86
+ console.log(chalk.green(`✓ Authenticated as ${authCheck.user}`));
87
+ console.log('');
88
+ }
89
+
90
+ // Step 9: Environment variables
91
+ const existingEnvVars = readEnvFile(projectDir) || {};
92
+ const requiredEnvVars = getRequiredEnvVars(paymentProcessor);
93
+ const envVars = await promptMissingEnvVars(existingEnvVars, requiredEnvVars, chalk);
94
+
95
+ // Step 10: Production confirmation prompt
96
+ let isProduction = false;
97
+ if (platform === 'vercel') {
98
+ const { production } = await inquirer.prompt([
99
+ {
100
+ type: 'confirm',
101
+ name: 'production',
102
+ message: 'Deploy to production? (No = preview/staging)',
103
+ default: false
104
+ }
105
+ ]);
106
+ isProduction = production;
107
+ }
108
+
109
+ // Step 11: Execute deployment
110
+ let deploymentResult;
111
+ try {
112
+ if (platform === 'vercel') {
113
+ deploymentResult = await deployToVercel(projectDir, envVars, isProduction, chalk);
114
+ } else if (platform === 'railway') {
115
+ deploymentResult = await deployToRailway(projectDir, envVars, chalk);
116
+ }
117
+ } catch (error) {
118
+ console.log('');
119
+ console.log(chalk.red('Deployment failed:'));
120
+ console.log(chalk.red(error.message));
121
+ console.log('');
122
+ process.exit(1);
123
+ }
124
+
125
+ // Step 12: Prompt for deployed URL
126
+ console.log('');
127
+ const { deployedUrl } = await inquirer.prompt([
128
+ {
129
+ type: 'input',
130
+ name: 'deployedUrl',
131
+ message: 'Enter your deployment URL:',
132
+ default: 'https://your-app.vercel.app',
133
+ validate: input => {
134
+ if (!input.trim()) return 'URL is required';
135
+ if (!input.startsWith('http')) return 'URL must start with http:// or https://';
136
+ return true;
137
+ }
138
+ }
139
+ ]);
140
+
141
+ // Step 13: Display webhook guidance
142
+ displayWebhookGuidance(
143
+ deployedUrl.trim(),
144
+ paymentProcessor,
145
+ platform,
146
+ Object.keys(envVars),
147
+ chalk
148
+ );
149
+ }
150
+
151
+ /**
152
+ * Handle manual deployment (config file generation only)
153
+ * @param {string} projectDir - Absolute path to project directory
154
+ * @param {string} paymentProcessor - Payment processor from features.ts
155
+ * @param {object} chalk - Chalk instance for colored output
156
+ */
157
+ async function handleManualDeploy(projectDir, paymentProcessor, chalk) {
158
+ console.log('');
159
+ console.log(chalk.cyan('Manual Deployment Setup'));
160
+ console.log('');
161
+
162
+ // Checkbox prompt for config files
163
+ const { platforms } = await inquirer.prompt([
164
+ {
165
+ type: 'checkbox',
166
+ name: 'platforms',
167
+ message: 'Generate config files for:',
168
+ choices: [
169
+ { name: 'Vercel', value: 'vercel', checked: true },
170
+ { name: 'Railway', value: 'railway', checked: true }
171
+ ],
172
+ validate: input => input.length > 0 ? true : 'Select at least one platform'
173
+ }
174
+ ]);
175
+
176
+ // Generate selected config files
177
+ console.log('');
178
+ console.log(chalk.cyan('Generating configuration files...'));
179
+ console.log('');
180
+
181
+ if (platforms.includes('vercel')) {
182
+ generateVercelJson(projectDir, chalk);
183
+ }
184
+
185
+ if (platforms.includes('railway')) {
186
+ generateRailwayToml(projectDir, chalk);
187
+ }
188
+
189
+ // Display manual deployment instructions
190
+ console.log('');
191
+ console.log(chalk.yellow.bold('📋 Manual Deployment Steps:'));
192
+ console.log('');
193
+ console.log(chalk.white('1. Push your code to GitHub:'));
194
+ console.log(chalk.dim(' git add .'));
195
+ console.log(chalk.dim(' git commit -m "Add deployment config"'));
196
+ console.log(chalk.dim(' git push'));
197
+ console.log('');
198
+ console.log(chalk.white('2. Import project in platform dashboard:'));
199
+ if (platforms.includes('vercel')) {
200
+ console.log(chalk.dim(' Vercel: https://vercel.com/new'));
201
+ }
202
+ if (platforms.includes('railway')) {
203
+ console.log(chalk.dim(' Railway: https://railway.app/new'));
204
+ }
205
+ console.log('');
206
+ console.log(chalk.white('3. Set environment variables in dashboard:'));
207
+ const requiredEnvVars = getRequiredEnvVars(paymentProcessor);
208
+ requiredEnvVars.forEach(key => {
209
+ console.log(chalk.dim(` • ${key}`));
210
+ });
211
+ console.log('');
212
+ console.log(chalk.white('4. Deploy from platform dashboard'));
213
+ console.log('');
214
+ console.log(chalk.green('✓ Config files generated successfully!'));
215
+ console.log('');
216
+ }
217
+
218
+ module.exports = {
219
+ deployCommand
220
+ };
@@ -0,0 +1,140 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const inquirer = require('inquirer');
4
+
5
+ /**
6
+ * Reads environment variables from .env.local or .env
7
+ * @param {string} projectDir - Absolute path to project directory
8
+ * @returns {object|null} Object with env vars, or null if no file exists
9
+ */
10
+ function readEnvFile(projectDir) {
11
+ const envLocalPath = path.join(projectDir, '.env.local');
12
+ const envPath = path.join(projectDir, '.env');
13
+
14
+ let envFilePath = null;
15
+ if (fs.existsSync(envLocalPath)) {
16
+ envFilePath = envLocalPath;
17
+ } else if (fs.existsSync(envPath)) {
18
+ envFilePath = envPath;
19
+ } else {
20
+ return null;
21
+ }
22
+
23
+ const content = fs.readFileSync(envFilePath, 'utf-8');
24
+ const envVars = {};
25
+
26
+ // Simple parser: KEY=VALUE lines
27
+ content.split('\n').forEach(line => {
28
+ const trimmed = line.trim();
29
+
30
+ // Skip comments and empty lines
31
+ if (!trimmed || trimmed.startsWith('#')) {
32
+ return;
33
+ }
34
+
35
+ const equalsIndex = trimmed.indexOf('=');
36
+ if (equalsIndex === -1) {
37
+ return;
38
+ }
39
+
40
+ const key = trimmed.substring(0, equalsIndex).trim();
41
+ const value = trimmed.substring(equalsIndex + 1).trim();
42
+
43
+ if (key) {
44
+ envVars[key] = value;
45
+ }
46
+ });
47
+
48
+ return envVars;
49
+ }
50
+
51
+ /**
52
+ * Gets list of required environment variables based on payment processor
53
+ * @param {string} paymentProcessor - 'stripe', 'razorpay', or 'both'
54
+ * @returns {string[]} Array of required env var names
55
+ */
56
+ function getRequiredEnvVars(paymentProcessor) {
57
+ const baseVars = [
58
+ 'NEXT_PUBLIC_APP_URL',
59
+ 'NEXT_PUBLIC_SUPABASE_URL',
60
+ 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
61
+ 'SUPABASE_SERVICE_ROLE_KEY',
62
+ 'RESEND_API_KEY',
63
+ 'INNGEST_EVENT_KEY',
64
+ 'INNGEST_SIGNING_KEY'
65
+ ];
66
+
67
+ const stripeVars = [
68
+ 'STRIPE_SECRET_KEY',
69
+ 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY',
70
+ 'STRIPE_WEBHOOK_SECRET'
71
+ ];
72
+
73
+ const razorpayVars = [
74
+ 'NEXT_PUBLIC_RAZORPAY_KEY_ID',
75
+ 'RAZORPAY_KEY_SECRET',
76
+ 'RAZORPAY_WEBHOOK_SECRET'
77
+ ];
78
+
79
+ let required = [...baseVars];
80
+
81
+ if (paymentProcessor === 'stripe' || paymentProcessor === 'both') {
82
+ required = required.concat(stripeVars);
83
+ }
84
+
85
+ if (paymentProcessor === 'razorpay' || paymentProcessor === 'both') {
86
+ required = required.concat(razorpayVars);
87
+ }
88
+
89
+ return required;
90
+ }
91
+
92
+ /**
93
+ * Prompts user for missing environment variables
94
+ * @param {object} existing - Existing env vars from file
95
+ * @param {string[]} required - Array of required env var names
96
+ * @param {object} chalk - Chalk instance for colored output
97
+ * @returns {Promise<object>} Merged object with existing and new env vars
98
+ */
99
+ async function promptMissingEnvVars(existing, required, chalk) {
100
+ // Filter for missing vars or vars with placeholder values
101
+ const missing = required.filter(key => {
102
+ if (!existing[key]) return true;
103
+ const value = existing[key];
104
+ // Check for placeholder values starting with 'your-'
105
+ if (value.startsWith('your-')) return true;
106
+ return false;
107
+ });
108
+
109
+ // No missing vars
110
+ if (missing.length === 0) {
111
+ return existing;
112
+ }
113
+
114
+ // Display missing vars
115
+ console.log('');
116
+ console.log(chalk.yellow('Missing required environment variables:'));
117
+ missing.forEach(key => {
118
+ console.log(chalk.yellow(` - ${key}`));
119
+ });
120
+ console.log('');
121
+
122
+ // Prompt for each missing var
123
+ const questions = missing.map(key => ({
124
+ type: 'input',
125
+ name: key,
126
+ message: `${key}:`,
127
+ validate: input => input.trim() ? true : `${key} is required`
128
+ }));
129
+
130
+ const answers = await inquirer.prompt(questions);
131
+
132
+ // Merge existing with new answers
133
+ return { ...existing, ...answers };
134
+ }
135
+
136
+ module.exports = {
137
+ readEnvFile,
138
+ getRequiredEnvVars,
139
+ promptMissingEnvVars
140
+ };
package/src/index.js CHANGED
@@ -9,6 +9,13 @@ const { launchClaude } = require('./launcher');
9
9
  const { validateLicense } = require('./license-validator');
10
10
  const { askPaymentProcessor, askPaymentKeys } = require('./payment-config');
11
11
  const { runLovableFlow } = require('./lovable-flow');
12
+ const { generateVercelJson, generateRailwayToml } = require('./config-generator');
13
+ const { readEnvFile, getRequiredEnvVars, promptMissingEnvVars } = require('./env-mapper');
14
+ const { detectPlatformCLI, PLATFORM_CLIS } = require('./platform-detector');
15
+ const { checkVercelAuth, deployToVercel } = require('./vercel-deployer');
16
+ const { deployToRailway } = require('./railway-deployer');
17
+ const { deployCommand } = require('./deploy-command');
18
+ const { displayWebhookGuidance } = require('./webhook-guidance');
12
19
  const messages = require('./messages');
13
20
 
14
21
  async function main() {
@@ -134,4 +141,18 @@ async function main() {
134
141
  }
135
142
  }
136
143
 
137
- module.exports = { main };
144
+ module.exports = {
145
+ main,
146
+ generateVercelJson,
147
+ generateRailwayToml,
148
+ readEnvFile,
149
+ getRequiredEnvVars,
150
+ promptMissingEnvVars,
151
+ detectPlatformCLI,
152
+ PLATFORM_CLIS,
153
+ checkVercelAuth,
154
+ deployToVercel,
155
+ deployToRailway,
156
+ deployCommand,
157
+ displayWebhookGuidance
158
+ };
@@ -3,6 +3,10 @@
3
3
  *
4
4
  * Handles Lovable UI design approach prompts for CLI wizard.
5
5
  * Called during PRO tier setup, before launching AI PM.
6
+ *
7
+ * The CLI only asks a simple question here. The intelligent Lovable prompt
8
+ * generation happens AFTER the AI PM understands the full project scope
9
+ * (post-roadmap), inside new-project.md Phase 10.5.
6
10
  */
7
11
 
8
12
  const inquirer = require('inquirer');
@@ -17,26 +21,26 @@ const { updateBlueprint } = require('./blueprint-manager');
17
21
  */
18
22
  async function askLovableChoice() {
19
23
  console.log('');
20
- console.log(chalk.cyan.bold('UI Design Approach'));
21
- console.log(chalk.dim('Choose how you want to create your user interface'));
24
+ console.log(chalk.cyan.bold('UI Design'));
25
+ console.log(chalk.dim('PropelKit can generate a tailored Lovable prompt for your project'));
22
26
  console.log('');
23
27
 
24
28
  const { choice } = await inquirer.prompt([
25
29
  {
26
30
  type: 'list',
27
31
  name: 'choice',
28
- message: 'How would you like to handle UI design?',
32
+ message: 'How would you like to handle UI?',
29
33
  choices: [
30
34
  {
31
- name: 'Make new UI with Lovable - AI will help you design beautiful interfaces',
35
+ name: 'Use Lovable - AI PM will craft a perfect prompt after understanding your project',
32
36
  value: 'make-new'
33
37
  },
34
38
  {
35
- name: 'Have existing designs - Paste your Lovable code into lovable-import/',
39
+ name: 'I already have Lovable designs - Paste into lovable-designs/ folder',
36
40
  value: 'have-existing'
37
41
  },
38
42
  {
39
- name: 'Skip for now - Focus on backend first, design later',
43
+ name: 'Skip for now - Decide later (you can always add UI anytime)',
40
44
  value: 'skip'
41
45
  }
42
46
  ]
@@ -54,80 +58,81 @@ function displayLovableGuidance(choice) {
54
58
  console.log('');
55
59
 
56
60
  if (choice === 'make-new') {
57
- console.log(chalk.cyan('The AI PM will help you design your UI.'));
61
+ console.log(chalk.cyan('Great choice! Here\'s what will happen:'));
58
62
  console.log('');
59
- console.log(chalk.dim('Tips for great Lovable prompts:'));
60
- console.log(chalk.dim(' - Look at competitors for inspiration (what works? what doesn\'t?)'));
61
- console.log(chalk.dim(' - Be specific about your brand colors and typography'));
62
- console.log(chalk.dim(' - Describe the vibe: professional, playful, minimal, bold'));
63
- console.log(chalk.dim(' - Mention specific UI patterns you like (sidebars, cards, modals)'));
63
+ console.log(chalk.dim(' 1. AI PM will deeply understand your project first'));
64
+ console.log(chalk.dim(' 2. After planning all features, it generates an intelligent Lovable prompt'));
65
+ console.log(chalk.dim(' 3. The prompt includes every page, feature, and style tailored to your project'));
66
+ console.log(chalk.dim(' 4. You paste it into Lovable, generate UI, then paste output back'));
67
+ console.log(chalk.dim(' 5. AI PM auto-wires everything into your Next.js app'));
64
68
  console.log('');
65
- console.log(chalk.dim('The AI PM will guide you through extensive design questions.'));
69
+ console.log(chalk.dim('Pro tip: Have inspiration screenshots ready - the AI PM will ask for them!'));
66
70
  } else if (choice === 'have-existing') {
67
- console.log(chalk.cyan('Paste your Lovable code into lovable-import/ when ready.'));
71
+ console.log(chalk.cyan('Paste your Lovable output into lovable-designs/ when ready.'));
68
72
  console.log('');
69
- console.log(chalk.dim('The AI PM will detect and integrate your designs automatically.'));
70
- console.log(chalk.dim('Integration includes:'));
71
- console.log(chalk.dim(' - Translation from React/Vite to Next.js App Router'));
72
- console.log(chalk.dim(' - Backend wiring analysis'));
73
- console.log(chalk.dim(' - Component organization'));
73
+ console.log(chalk.dim('The AI PM will auto-wire your designs:'));
74
+ console.log(chalk.dim(' - Translate React Router to Next.js App Router'));
75
+ console.log(chalk.dim(' - Connect to Supabase auth and database'));
76
+ console.log(chalk.dim(' - Wire up payment and email flows'));
77
+ console.log(chalk.dim(' - Apply your brand config'));
74
78
  console.log('');
75
79
  console.log(chalk.dim('You can paste code before or after AI PM starts.'));
76
80
  } else {
77
- console.log(chalk.yellow('Design skipped for now.'));
81
+ console.log(chalk.yellow('No problem! You can add UI anytime.'));
78
82
  console.log('');
79
- console.log(chalk.dim('You can add UI later using:'));
80
- console.log(chalk.dim(' - AI PM design flow (during conversation)'));
81
- console.log(chalk.dim(' - Paste code into lovable-import/ anytime'));
83
+ console.log(chalk.dim('Options when you\'re ready:'));
84
+ console.log(chalk.dim(' - Run /propelkit:wire-ui to import and wire Lovable designs'));
85
+ console.log(chalk.dim(' - AI PM can generate a Lovable prompt during any session'));
82
86
  console.log('');
83
87
  }
84
88
  }
85
89
 
86
90
  /**
87
- * Create lovable-import directory in project
91
+ * Create lovable-designs directory in project
88
92
  * @param {string} projectDir - Project directory path
89
93
  */
90
- function createLovableImportDir(projectDir) {
91
- const lovableDir = path.join(projectDir, 'lovable-import');
94
+ function createLovableDesignsDir(projectDir) {
95
+ const lovableDir = path.join(projectDir, 'lovable-designs');
92
96
  if (!fs.existsSync(lovableDir)) {
93
97
  fs.mkdirSync(lovableDir, { recursive: true });
94
98
 
95
99
  // Add README to explain the folder
96
100
  const readmePath = path.join(lovableDir, 'README.md');
97
- const readmeContent = `# Lovable Import Directory
101
+ const readmeContent = `# Lovable Designs
102
+
103
+ Paste your Lovable-generated UI code here.
98
104
 
99
- Paste your Lovable-generated code here.
105
+ ## How it works
100
106
 
101
- ## What to paste
107
+ 1. AI PM generates a tailored prompt for your project (after understanding it fully)
108
+ 2. You paste the prompt into lovable.dev and generate your UI
109
+ 3. Export/download from Lovable and paste the output here
110
+ 4. Run \`/propelkit:wire-ui\` to auto-wire everything into your Next.js app
102
111
 
103
- Export your project from Lovable and paste all React components and files here.
104
- The AI PM will automatically:
112
+ ## What the auto-wiring does
105
113
 
106
- 1. **Detect** the code when you're ready
107
- 2. **Translate** from React/Vite to Next.js App Router
108
- 3. **Integrate** with PropelKit backend APIs
109
- 4. **Generate** an integration report
114
+ - Translates React Router to Next.js App Router
115
+ - Connects components to Supabase auth and database
116
+ - Wires up payment flows (Stripe/Razorpay)
117
+ - Applies your brand config from \`src/config/brand.ts\`
118
+ - Generates an integration report
110
119
 
111
120
  ## Folder structure
112
121
 
113
122
  Paste your Lovable export maintaining the original structure:
114
123
  \`\`\`
115
- lovable-import/
116
- ├── src/
117
- ├── components/
118
- ├── pages/
119
- └── ...
120
- ├── package.json
121
- └── ...
124
+ lovable-designs/
125
+ +-- src/
126
+ | +-- components/
127
+ | +-- pages/
128
+ | +-- ...
129
+ +-- package.json
130
+ +-- ...
122
131
  \`\`\`
123
-
124
- ## Need help?
125
-
126
- The AI PM will guide you through the integration process.
127
132
  `;
128
133
  fs.writeFileSync(readmePath, readmeContent);
129
134
 
130
- console.log(chalk.dim(`Created lovable-import/ directory`));
135
+ console.log(chalk.dim(`Created lovable-designs/ directory`));
131
136
  }
132
137
  }
133
138
 
@@ -148,7 +153,7 @@ function persistLovableChoice(projectDir, choice) {
148
153
  async function runLovableFlow(projectDir) {
149
154
  const choice = await askLovableChoice();
150
155
  displayLovableGuidance(choice);
151
- createLovableImportDir(projectDir);
156
+ createLovableDesignsDir(projectDir);
152
157
  persistLovableChoice(projectDir, choice);
153
158
  return choice;
154
159
  }
@@ -156,7 +161,7 @@ async function runLovableFlow(projectDir) {
156
161
  module.exports = {
157
162
  askLovableChoice,
158
163
  displayLovableGuidance,
159
- createLovableImportDir,
164
+ createLovableDesignsDir,
160
165
  persistLovableChoice,
161
166
  runLovableFlow
162
167
  };
package/src/messages.js CHANGED
@@ -4,7 +4,7 @@ const messages = {
4
4
  welcome: () => {
5
5
  console.log('');
6
6
  console.log(chalk.cyan.bold('PropelKit'));
7
- console.log(chalk.dim('AI-powered SaaS boilerplate for India'));
7
+ console.log(chalk.dim('AI-powered SaaS boilerplate'));
8
8
  console.log('');
9
9
  },
10
10
 
@@ -122,7 +122,7 @@ const messages = {
122
122
  console.log('');
123
123
  console.log(chalk.dim('PropelKit supports multiple payment processors:'));
124
124
  console.log(chalk.dim(' - Stripe for international payments'));
125
- console.log(chalk.dim(' - Razorpay for India (INR + UPI)'));
125
+ console.log(chalk.dim(' - Razorpay (INR + UPI)'));
126
126
  console.log(chalk.dim(' - Both for global SaaS with auto-routing'));
127
127
  console.log('');
128
128
  },
@@ -23,7 +23,7 @@ const PAGE_TEMPLATES = {
23
23
  // Credits pages (optional - based on features.credits)
24
24
  credits: ['Usage Dashboard', 'Purchase Credits'],
25
25
 
26
- // GST pages (always included for India)
26
+ // GST pages (optional - based on features.gst)
27
27
  gst: ['Invoice Settings'],
28
28
 
29
29
  // Admin pages (always included)
@@ -143,7 +143,7 @@ const PAGE_DETAILS = {
143
143
 
144
144
  // GST pages
145
145
  'Invoice Settings': {
146
- purpose: 'Configure GST and invoice details for Indian billing',
146
+ purpose: 'Configure GST and invoice details for tax billing',
147
147
  elements: ['GST number input', 'Business name', 'Business address', 'State selector', 'Invoice preferences'],
148
148
  components: ['Form', 'Input', 'Select', 'Button', 'Alert'],
149
149
  userFlow: 'User enters GST details -> Saves settings -> Future invoices use these details'
@@ -175,7 +175,7 @@ function derivePages(featuresConfig) {
175
175
  // Payment pages (always included)
176
176
  PAGE_TEMPLATES.payments.forEach(page => pages.add(page));
177
177
 
178
- // GST pages (always included for India-first SaaS)
178
+ // GST pages (included when GST feature is enabled)
179
179
  PAGE_TEMPLATES.gst.forEach(page => pages.add(page));
180
180
 
181
181
  // Admin pages (always included)
@@ -17,7 +17,7 @@ async function askPaymentProcessor() {
17
17
  value: 'stripe'
18
18
  },
19
19
  {
20
- name: `${chalk.cyan('Razorpay')} - India (INR, UPI)`,
20
+ name: `${chalk.cyan('Razorpay')} - (INR, UPI)`,
21
21
  value: 'razorpay'
22
22
  },
23
23
  {
@@ -0,0 +1,51 @@
1
+ const commandExists = require('command-exists');
2
+
3
+ const PLATFORM_CLIS = {
4
+ vercel: {
5
+ name: 'vercel',
6
+ label: 'Vercel CLI',
7
+ installCommand: {
8
+ global: 'npm install -g vercel',
9
+ npx: 'npx vercel'
10
+ },
11
+ docs: 'https://vercel.com/docs/cli'
12
+ },
13
+ railway: {
14
+ name: 'railway',
15
+ label: 'Railway CLI',
16
+ installCommand: {
17
+ global: 'npm install -g @railway/cli',
18
+ macos: 'brew install railway',
19
+ windows: 'scoop install railway'
20
+ },
21
+ docs: 'https://docs.railway.app/reference/cli-api'
22
+ }
23
+ };
24
+
25
+ /**
26
+ * Detect if a platform's CLI is installed
27
+ * @param {string} platform - 'vercel' or 'railway'
28
+ * @returns {Promise<{required: boolean, installed: boolean, config: object}>}
29
+ */
30
+ async function detectPlatformCLI(platform) {
31
+ const config = PLATFORM_CLIS[platform];
32
+
33
+ if (!config) {
34
+ throw new Error(`Unknown platform: ${platform}`);
35
+ }
36
+
37
+ const installed = await commandExists(config.name)
38
+ .then(() => true)
39
+ .catch(() => false);
40
+
41
+ return {
42
+ required: true,
43
+ installed,
44
+ config
45
+ };
46
+ }
47
+
48
+ module.exports = {
49
+ detectPlatformCLI,
50
+ PLATFORM_CLIS
51
+ };
@@ -0,0 +1,65 @@
1
+ const { spawn } = require('child_process');
2
+ const path = require('path');
3
+ const { generateRailwayToml } = require('./config-generator');
4
+
5
+ /**
6
+ * Deploys project to Railway using Railway CLI
7
+ * @param {string} projectDir - Absolute path to project directory
8
+ * @param {object} envVars - Environment variables (keys returned for guidance, not passed to CLI)
9
+ * @param {object} chalk - Chalk instance for colored output
10
+ * @returns {Promise<{success: boolean, envVars: string[]}>}
11
+ */
12
+ async function deployToRailway(projectDir, envVars, chalk) {
13
+ return new Promise((resolve, reject) => {
14
+ // Step 1: Generate railway.toml before deployment
15
+ generateRailwayToml(projectDir, chalk);
16
+
17
+ console.log('');
18
+ console.log(chalk.cyan('🚀 Deploying to Railway...'));
19
+ console.log('');
20
+ console.log(chalk.dim('Note: Set environment variables in Railway dashboard after deployment'));
21
+ console.log('');
22
+
23
+ // Step 2: Build args for railway CLI
24
+ const args = ['up', '--detach'];
25
+
26
+ // Step 3: Spawn railway command
27
+ const railwayProcess = spawn('railway', args, {
28
+ cwd: projectDir,
29
+ stdio: 'inherit',
30
+ shell: true // Required for Windows cross-platform compatibility
31
+ });
32
+
33
+ // Step 4: Set 5-minute timeout
34
+ const timeout = setTimeout(() => {
35
+ railwayProcess.kill();
36
+ reject(new Error('Railway deployment timed out after 5 minutes'));
37
+ }, 300000); // 300000ms = 5 minutes
38
+
39
+ // Step 5: Handle process completion
40
+ railwayProcess.on('error', (error) => {
41
+ clearTimeout(timeout);
42
+ if (error.code === 'ENOENT') {
43
+ reject(new Error('Railway CLI not found. Install it from: https://docs.railway.app/develop/cli'));
44
+ } else {
45
+ reject(new Error(`Railway deployment failed: ${error.message}`));
46
+ }
47
+ });
48
+
49
+ railwayProcess.on('exit', (code) => {
50
+ clearTimeout(timeout);
51
+ if (code === 0) {
52
+ resolve({
53
+ success: true,
54
+ envVars: Object.keys(envVars) // Return env var keys for post-deploy guidance
55
+ });
56
+ } else {
57
+ reject(new Error(`Railway deployment failed with exit code ${code}`));
58
+ }
59
+ });
60
+ });
61
+ }
62
+
63
+ module.exports = {
64
+ deployToRailway
65
+ };
@@ -0,0 +1,115 @@
1
+ const { spawn } = require('child_process');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Check if user is authenticated with Vercel CLI
6
+ * @returns {Promise<{authenticated: boolean, user?: string}>}
7
+ */
8
+ async function checkVercelAuth() {
9
+ return new Promise((resolve) => {
10
+ const vercel = spawn('vercel', ['whoami'], {
11
+ stdio: 'pipe',
12
+ shell: true
13
+ });
14
+
15
+ let stdout = '';
16
+ let stderr = '';
17
+
18
+ vercel.stdout.on('data', (data) => {
19
+ stdout += data.toString();
20
+ });
21
+
22
+ vercel.stderr.on('data', (data) => {
23
+ stderr += data.toString();
24
+ });
25
+
26
+ vercel.on('close', (code) => {
27
+ if (code === 0 && stdout.trim()) {
28
+ resolve({ authenticated: true, user: stdout.trim() });
29
+ } else {
30
+ resolve({ authenticated: false });
31
+ }
32
+ });
33
+
34
+ vercel.on('error', (error) => {
35
+ resolve({ authenticated: false });
36
+ });
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Deploy project to Vercel with environment variables
42
+ * @param {string} projectDir - Absolute path to project directory
43
+ * @param {object} envVars - Environment variables object
44
+ * @param {boolean} isProduction - Whether to deploy to production (--prod flag)
45
+ * @param {object} chalk - Chalk instance for colored output
46
+ * @returns {Promise<{success: boolean}>}
47
+ */
48
+ async function deployToVercel(projectDir, envVars, isProduction, chalk) {
49
+ return new Promise((resolve, reject) => {
50
+ // Build args array
51
+ const args = ['--yes'];
52
+
53
+ if (isProduction) {
54
+ args.push('--prod');
55
+ }
56
+
57
+ // Allowed secret env vars (never exposed to browser)
58
+ const allowedSecrets = [
59
+ 'STRIPE_SECRET_KEY',
60
+ 'STRIPE_WEBHOOK_SECRET',
61
+ 'RAZORPAY_KEY_SECRET',
62
+ 'RAZORPAY_WEBHOOK_SECRET',
63
+ 'SUPABASE_SERVICE_ROLE_KEY',
64
+ 'RESEND_API_KEY',
65
+ 'INNGEST_EVENT_KEY',
66
+ 'INNGEST_SIGNING_KEY'
67
+ ];
68
+
69
+ // Filter and add env vars
70
+ Object.entries(envVars).forEach(([key, value]) => {
71
+ // Include if starts with NEXT_PUBLIC_ OR is in allowed secrets list
72
+ if (key.startsWith('NEXT_PUBLIC_') || allowedSecrets.includes(key)) {
73
+ args.push('--env', `${key}=${value}`);
74
+ }
75
+ });
76
+
77
+ console.log('');
78
+ console.log(chalk.cyan(`Deploying to Vercel ${isProduction ? '(production)' : '(preview)'}...`));
79
+ console.log(chalk.dim(`Environment variables: ${Object.keys(envVars).filter(key => key.startsWith('NEXT_PUBLIC_') || allowedSecrets.includes(key)).length} passed`));
80
+ console.log('');
81
+
82
+ const vercel = spawn('vercel', args, {
83
+ cwd: projectDir,
84
+ stdio: 'inherit', // Show real-time logs
85
+ shell: true
86
+ });
87
+
88
+ // 5-minute timeout
89
+ const timeout = setTimeout(() => {
90
+ vercel.kill();
91
+ reject(new Error('Deployment timed out after 5 minutes'));
92
+ }, 300000);
93
+
94
+ vercel.on('close', (code) => {
95
+ clearTimeout(timeout);
96
+ if (code === 0) {
97
+ console.log('');
98
+ console.log(chalk.green('Deployment successful!'));
99
+ resolve({ success: true });
100
+ } else {
101
+ reject(new Error(`Deployment failed with exit code ${code}`));
102
+ }
103
+ });
104
+
105
+ vercel.on('error', (error) => {
106
+ clearTimeout(timeout);
107
+ reject(new Error(`Deployment error: ${error.message}`));
108
+ });
109
+ });
110
+ }
111
+
112
+ module.exports = {
113
+ checkVercelAuth,
114
+ deployToVercel
115
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Display post-deployment webhook configuration guidance
3
+ * @param {string} deployedUrl - The deployed application URL
4
+ * @param {string} paymentProcessor - 'stripe', 'razorpay', or 'both'
5
+ * @param {string} platform - 'vercel' or 'railway'
6
+ * @param {string[]} envVarKeys - Array of environment variable keys
7
+ * @param {object} chalk - Chalk instance for colored output
8
+ */
9
+ function displayWebhookGuidance(deployedUrl, paymentProcessor, platform, envVarKeys, chalk) {
10
+ console.log('');
11
+ console.log(chalk.green.bold('✓ Deployment successful!'));
12
+ console.log('');
13
+ console.log(chalk.cyan('Your app is deployed at:'));
14
+ console.log(chalk.bold(deployedUrl));
15
+ console.log('');
16
+
17
+ // Section 2: Configure webhook URLs based on payment processor
18
+ console.log(chalk.yellow.bold('📡 Next Steps: Configure Webhooks'));
19
+ console.log('');
20
+
21
+ if (paymentProcessor === 'razorpay' || paymentProcessor === 'both') {
22
+ console.log(chalk.cyan('Razorpay Webhooks:'));
23
+ console.log(chalk.dim(' Dashboard: https://dashboard.razorpay.com/app/webhooks'));
24
+ console.log('');
25
+ console.log(chalk.white(' Webhook URL:'));
26
+ console.log(chalk.bold(` ${deployedUrl}/api/payments/webhooks/razorpay`));
27
+ console.log('');
28
+ console.log(chalk.dim(' Events to subscribe:'));
29
+ console.log(chalk.dim(' • payment.captured'));
30
+ console.log(chalk.dim(' • subscription.activated'));
31
+ console.log(chalk.dim(' • subscription.charged'));
32
+ console.log('');
33
+ }
34
+
35
+ if (paymentProcessor === 'stripe' || paymentProcessor === 'both') {
36
+ console.log(chalk.cyan('Stripe Webhooks:'));
37
+ console.log(chalk.dim(' Dashboard: https://dashboard.stripe.com/webhooks'));
38
+ console.log('');
39
+ console.log(chalk.white(' Webhook URL:'));
40
+ console.log(chalk.bold(` ${deployedUrl}/api/payments/webhooks/stripe`));
41
+ console.log('');
42
+ console.log(chalk.dim(' Events to subscribe:'));
43
+ console.log(chalk.dim(' • checkout.session.completed'));
44
+ console.log(chalk.dim(' • invoice.paid'));
45
+ console.log(chalk.dim(' • invoice.payment_failed'));
46
+ console.log(chalk.dim(' • customer.subscription.updated'));
47
+ console.log(chalk.dim(' • customer.subscription.deleted'));
48
+ console.log('');
49
+ }
50
+
51
+ // Section 3: Railway-specific env var setup
52
+ if (platform === 'railway') {
53
+ console.log(chalk.yellow.bold('⚙️ Railway Environment Variables'));
54
+ console.log('');
55
+ console.log(chalk.white('Set these in your Railway dashboard:'));
56
+ console.log(chalk.dim(' Dashboard: https://railway.app/dashboard'));
57
+ console.log('');
58
+ envVarKeys.forEach(key => {
59
+ console.log(chalk.dim(` • ${key}`));
60
+ });
61
+ console.log('');
62
+ }
63
+
64
+ // Section 4: Test your deployment checklist
65
+ console.log(chalk.yellow.bold('✅ Testing Checklist'));
66
+ console.log('');
67
+ console.log(chalk.dim(' 1. Visit your app and sign up'));
68
+ console.log(chalk.dim(' 2. Test authentication flow'));
69
+ console.log(chalk.dim(' 3. Verify payment webhook (test mode)'));
70
+ console.log('');
71
+
72
+ // Section 5: Production warning
73
+ console.log(chalk.red.bold('⚠️ Important: Production Setup'));
74
+ console.log('');
75
+ console.log(chalk.yellow('For production deployments:'));
76
+ console.log(chalk.dim(' • Generate NEW webhook secrets (never reuse test mode secrets)'));
77
+ console.log(chalk.dim(' • Update webhook secrets in your deployment platform'));
78
+ console.log(chalk.dim(' • Test webhooks in production before launching'));
79
+ console.log('');
80
+ }
81
+
82
+ module.exports = {
83
+ displayWebhookGuidance
84
+ };