create-propelkit 1.0.2 → 1.0.4

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,31 +1,31 @@
1
- {
2
- "name": "create-propelkit",
3
- "version": "1.0.2",
4
- "description": "Initialize a PropelKit SaaS project with AI PM",
5
- "bin": "./bin/cli.js",
6
- "main": "./src/index.js",
7
- "type": "commonjs",
8
- "files": [
9
- "bin",
10
- "src"
11
- ],
12
- "keywords": [
13
- "propelkit",
14
- "saas",
15
- "boilerplate",
16
- "india",
17
- "razorpay",
18
- "supabase"
19
- ],
20
- "author": "PropelKit",
21
- "license": "MIT",
22
- "engines": {
23
- "node": ">=18.0.0"
24
- },
25
- "dependencies": {
26
- "chalk": "^4.1.2",
27
- "command-exists": "^1.2.9",
28
- "inquirer": "^8.2.6",
29
- "zod": "^4.3.6"
30
- }
31
- }
1
+ {
2
+ "name": "create-propelkit",
3
+ "version": "1.0.4",
4
+ "description": "Initialize a PropelKit SaaS project with AI PM",
5
+ "bin": "./bin/cli.js",
6
+ "main": "./src/index.js",
7
+ "type": "commonjs",
8
+ "files": [
9
+ "bin",
10
+ "src"
11
+ ],
12
+ "keywords": [
13
+ "propelkit",
14
+ "saas",
15
+ "boilerplate",
16
+ "india",
17
+ "razorpay",
18
+ "supabase"
19
+ ],
20
+ "author": "PropelKit",
21
+ "license": "MIT",
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "dependencies": {
26
+ "chalk": "^4.1.2",
27
+ "command-exists": "^1.2.9",
28
+ "inquirer": "^8.2.6",
29
+ "zod": "^4.3.6"
30
+ }
31
+ }
@@ -0,0 +1,144 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * Blueprint schema (for reference):
7
+ * {
8
+ * name: string,
9
+ * createdAt: string (ISO),
10
+ * setupVersion: string,
11
+ * tier: 'starter' | 'pro',
12
+ * paymentProcessor: 'stripe' | 'razorpay' | 'both',
13
+ * envVarsReady: boolean,
14
+ * stripeCLIInstalled: boolean,
15
+ * // ... other fields (colorScheme, visualStyle, etc.)
16
+ * }
17
+ */
18
+
19
+ /**
20
+ * Read blueprint.json from project directory
21
+ * @param {string} projectDir - Project directory path
22
+ * @returns {object|null} Blueprint object or null if not found
23
+ */
24
+ function readBlueprint(projectDir) {
25
+ const blueprintPath = path.join(projectDir, 'blueprint.json');
26
+ if (!fs.existsSync(blueprintPath)) {
27
+ return null;
28
+ }
29
+ try {
30
+ return JSON.parse(fs.readFileSync(blueprintPath, 'utf-8'));
31
+ } catch (err) {
32
+ console.log(chalk.yellow('Warning: Could not parse blueprint.json'));
33
+ return null;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Write blueprint.json to project directory
39
+ * @param {string} projectDir - Project directory path
40
+ * @param {object} blueprint - Blueprint object to write
41
+ */
42
+ function writeBlueprint(projectDir, blueprint) {
43
+ const blueprintPath = path.join(projectDir, 'blueprint.json');
44
+ fs.writeFileSync(blueprintPath, JSON.stringify(blueprint, null, 2) + '\n');
45
+ console.log(chalk.dim('Updated blueprint.json'));
46
+ }
47
+
48
+ /**
49
+ * Update blueprint.json with new fields (merge, not replace)
50
+ * @param {string} projectDir - Project directory path
51
+ * @param {object} updates - Fields to update/add
52
+ * @returns {object} Updated blueprint
53
+ */
54
+ function updateBlueprint(projectDir, updates) {
55
+ const existing = readBlueprint(projectDir) || {};
56
+ const updated = { ...existing, ...updates };
57
+ writeBlueprint(projectDir, updated);
58
+ return updated;
59
+ }
60
+
61
+ /**
62
+ * Write payment API keys to .env.local
63
+ * Only writes keys that were provided (processor-specific)
64
+ * @param {string} projectDir - Project directory path
65
+ * @param {object} paymentConfig - Payment configuration with keys
66
+ */
67
+ function writePaymentEnvVars(projectDir, paymentConfig) {
68
+ if (!paymentConfig || !paymentConfig.hasKeys || !paymentConfig.keys) {
69
+ return; // No keys to write
70
+ }
71
+
72
+ const envPath = path.join(projectDir, '.env.local');
73
+ let envContent = '';
74
+
75
+ // Read existing .env.local if it exists
76
+ if (fs.existsSync(envPath)) {
77
+ envContent = fs.readFileSync(envPath, 'utf-8');
78
+ if (!envContent.endsWith('\n')) {
79
+ envContent += '\n';
80
+ }
81
+ }
82
+
83
+ const keys = paymentConfig.keys;
84
+ const processor = paymentConfig.processor;
85
+
86
+ // Add Stripe keys if provided
87
+ if ((processor === 'stripe' || processor === 'both') && keys.stripeSecretKey) {
88
+ envContent += `\n# Stripe (added by setup wizard)\n`;
89
+ envContent += `STRIPE_SECRET_KEY=${keys.stripeSecretKey}\n`;
90
+ if (keys.stripePublishableKey) {
91
+ envContent += `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${keys.stripePublishableKey}\n`;
92
+ }
93
+ if (keys.stripeWebhookSecret) {
94
+ envContent += `STRIPE_WEBHOOK_SECRET=${keys.stripeWebhookSecret}\n`;
95
+ }
96
+ }
97
+
98
+ // Add Razorpay keys if provided
99
+ if ((processor === 'razorpay' || processor === 'both') && keys.razorpayKeyId) {
100
+ envContent += `\n# Razorpay (added by setup wizard)\n`;
101
+ envContent += `NEXT_PUBLIC_RAZORPAY_KEY_ID=${keys.razorpayKeyId}\n`;
102
+ if (keys.razorpayKeySecret) {
103
+ envContent += `RAZORPAY_KEY_SECRET=${keys.razorpayKeySecret}\n`;
104
+ }
105
+ if (keys.razorpayWebhookSecret) {
106
+ envContent += `RAZORPAY_WEBHOOK_SECRET=${keys.razorpayWebhookSecret}\n`;
107
+ }
108
+ }
109
+
110
+ fs.writeFileSync(envPath, envContent);
111
+ console.log(chalk.dim('Updated .env.local with payment keys'));
112
+ }
113
+
114
+ /**
115
+ * Ensure blueprint has required payment fields
116
+ * @param {string} projectDir - Project directory path
117
+ * @param {object} paymentConfig - Payment configuration from wizard
118
+ * @param {string} tier - License tier (starter/pro)
119
+ */
120
+ function persistPaymentConfig(projectDir, paymentConfig, tier) {
121
+ if (!paymentConfig) {
122
+ // Starter tier or no payment config - just set tier
123
+ updateBlueprint(projectDir, { tier });
124
+ return;
125
+ }
126
+
127
+ updateBlueprint(projectDir, {
128
+ tier,
129
+ paymentProcessor: paymentConfig.processor,
130
+ envVarsReady: paymentConfig.hasKeys,
131
+ stripeCLIInstalled: paymentConfig.stripeCLIInstalled || false
132
+ });
133
+
134
+ // Write env vars if keys provided
135
+ writePaymentEnvVars(projectDir, paymentConfig);
136
+ }
137
+
138
+ module.exports = {
139
+ readBlueprint,
140
+ writeBlueprint,
141
+ updateBlueprint,
142
+ persistPaymentConfig,
143
+ writePaymentEnvVars
144
+ };
@@ -1,5 +1,6 @@
1
1
  const commandExists = require('command-exists');
2
2
  const inquirer = require('inquirer');
3
+ const os = require('os');
3
4
 
4
5
  const CLI_DEFINITIONS = [
5
6
  {
@@ -22,6 +23,18 @@ const CLI_DEFINITIONS = [
22
23
  installUrl: 'https://cli.github.com/',
23
24
  required: false,
24
25
  benefit: 'Enables automatic repo creation and commits'
26
+ },
27
+ {
28
+ name: 'stripe',
29
+ label: 'Stripe CLI',
30
+ installUrl: {
31
+ win32: 'https://docs.stripe.com/stripe-cli#install',
32
+ darwin: 'brew install stripe/stripe-cli/stripe',
33
+ linux: 'https://docs.stripe.com/stripe-cli#install'
34
+ },
35
+ required: false,
36
+ benefit: 'Enables AI PM to auto-create Stripe products and prices',
37
+ displayInInitial: false // Not shown in initial CLI detection
25
38
  }
26
39
  ];
27
40
 
@@ -95,10 +108,77 @@ function getCLIByName(clis, name) {
95
108
  return clis.find(cli => cli.name === name);
96
109
  }
97
110
 
111
+ /**
112
+ * Detect Stripe CLI on-demand after payment processor selection
113
+ * @param {string} paymentProcessor - 'stripe', 'razorpay', or 'both'
114
+ * @returns {Promise<{required: boolean, installed: boolean}>}
115
+ */
116
+ async function detectStripeCLI(paymentProcessor) {
117
+ // Stripe CLI not required for Razorpay-only projects
118
+ if (paymentProcessor === 'razorpay') {
119
+ return { required: false, installed: true };
120
+ }
121
+
122
+ // Stripe or both - detect Stripe CLI
123
+ const installed = await commandExists('stripe')
124
+ .then(() => true)
125
+ .catch(() => false);
126
+
127
+ return { required: true, installed };
128
+ }
129
+
130
+ /**
131
+ * Display Stripe CLI installation guidance with platform-specific instructions
132
+ * @param {object} chalk - Chalk instance for styling
133
+ * @returns {Promise<boolean>} - Whether user wants to continue without Stripe CLI
134
+ */
135
+ async function displayStripeGuidance(chalk) {
136
+ const platform = os.platform();
137
+
138
+ console.log('');
139
+ console.log(chalk.yellow.bold('⚠ Stripe CLI Not Found'));
140
+ console.log(chalk.dim('-'.repeat(50)));
141
+ console.log('');
142
+ console.log('The AI PM can automatically create Stripe products and prices');
143
+ console.log('if the Stripe CLI is installed and authenticated.');
144
+ console.log('');
145
+ console.log(chalk.cyan.bold('Installation:'));
146
+
147
+ if (platform === 'win32') {
148
+ console.log(chalk.white(' Windows (via Scoop):'));
149
+ console.log(chalk.dim(' scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git'));
150
+ console.log(chalk.dim(' scoop install stripe'));
151
+ } else if (platform === 'darwin') {
152
+ console.log(chalk.white(' macOS (via Homebrew):'));
153
+ console.log(chalk.dim(' brew install stripe/stripe-cli/stripe'));
154
+ } else {
155
+ console.log(chalk.white(' Linux:'));
156
+ console.log(chalk.dim(' See: https://docs.stripe.com/stripe-cli#install'));
157
+ }
158
+
159
+ console.log('');
160
+ console.log(chalk.cyan.bold('After installation:'));
161
+ console.log(chalk.dim(' stripe login'));
162
+ console.log('');
163
+
164
+ const { continueWithout } = await inquirer.prompt([
165
+ {
166
+ type: 'confirm',
167
+ name: 'continueWithout',
168
+ message: 'Continue without Stripe CLI? (You can configure manually later)',
169
+ default: true
170
+ }
171
+ ]);
172
+
173
+ return continueWithout;
174
+ }
175
+
98
176
  module.exports = {
99
177
  detectCLIs,
100
178
  displayCLIStatus,
101
179
  askGitHubConnection,
102
180
  getCLIByName,
181
+ detectStripeCLI,
182
+ displayStripeGuidance,
103
183
  CLI_DEFINITIONS
104
184
  };
package/src/index.js CHANGED
@@ -1,136 +1,133 @@
1
- const chalk = require('chalk');
2
- const path = require('path');
3
- const fs = require('fs');
4
- const inquirer = require('inquirer');
5
- const { detectCLIs, displayCLIStatus, getCLIByName, askGitHubConnection } = require('./cli-detector');
6
- const { handleScenario } = require('./scenarios');
7
- const { runDesignFlow } = require('./design-flow');
8
- const { runSetupWizard } = require('./setup-wizard');
9
- const { launchClaude } = require('./launcher');
10
- const { validateLicense } = require('./license-validator');
11
- const messages = require('./messages');
12
-
13
- async function main() {
14
- // Step 1: Welcome
15
- messages.welcome();
16
-
17
- // Step 2: LICENSE VALIDATION (first thing after welcome)
18
- console.log('');
19
- const { licenseKey } = await inquirer.prompt([
20
- {
21
- type: 'input',
22
- name: 'licenseKey',
23
- message: 'License key:',
24
- validate: input => input.trim() ? true : 'License key is required'
25
- }
26
- ]);
27
-
28
- console.log(chalk.dim('Validating license...'));
29
- const validation = await validateLicense(licenseKey.trim());
30
-
31
- if (!validation.valid) {
32
- messages.licenseInvalid(validation.message);
33
- process.exit(1);
34
- }
35
-
36
- const tier = validation.tier; // 'starter' or 'pro'
37
-
38
- if (tier === 'starter') {
39
- messages.starterLicenseValid();
40
- } else {
41
- messages.proLicenseValid();
42
- }
43
-
44
- // Step 3: Detect CLIs
45
- messages.detectingCLIs();
46
- const clis = await detectCLIs();
47
- displayCLIStatus(clis, chalk);
48
-
49
- // Step 4: Check for Claude Code (required for PRO only)
50
- const claudeCli = getCLIByName(clis, 'claude');
51
- if (tier === 'pro' && !claudeCli.installed) {
52
- messages.claudeRequired(claudeCli.installUrl);
53
- process.exit(1);
54
- }
55
-
56
- // Step 5: Show benefits for missing optional CLIs
57
- const supabaseCli = getCLIByName(clis, 'supabase');
58
- const ghCli = getCLIByName(clis, 'gh');
59
-
60
- if (!supabaseCli.installed) {
61
- messages.supabaseBenefits();
62
- }
63
-
64
- if (!ghCli.installed) {
65
- messages.githubBenefits();
66
- }
67
-
68
- // Step 6: GitHub connection (for Pro tier, or if user wants it for Starter)
69
- const useGitHub = tier === 'pro' ? await askGitHubConnection(ghCli, chalk) : false;
70
-
71
- // Step 7: Clone scenario (pass tier first)
72
- let projectDir;
73
- try {
74
- projectDir = await handleScenario(tier, clis, useGitHub);
75
- } catch (error) {
76
- if (error.message === 'Aborted by user') {
77
- console.log('');
78
- console.log(chalk.dim('Goodbye!'));
79
- process.exit(0);
80
- }
81
- throw error;
82
- }
83
-
84
- // Step 8: TIER-SPECIFIC POST-CLONE FLOW
85
- if (tier === 'starter') {
86
- // Starter: Run setup wizard, then done
87
- await runSetupWizard(projectDir);
88
- messages.starterWizardComplete();
89
- } else {
90
- // Pro: Run design flow, then launch Claude Code
91
- console.log('');
92
- const designResult = await runDesignFlow(
93
- path.basename(projectDir),
94
- projectDir
95
- );
96
-
97
- // Store design result in config.json
98
- const configPath = path.join(projectDir, '.planning', 'config.json');
99
- let config = {};
100
- if (fs.existsSync(configPath)) {
101
- config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
102
- }
103
- config.designFlow = designResult;
104
-
105
- const planningDir = path.dirname(configPath);
106
- if (!fs.existsSync(planningDir)) {
107
- fs.mkdirSync(planningDir, { recursive: true });
108
- }
109
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
110
-
111
- // Launch Claude Code
112
- messages.proLaunchingAIPM(projectDir);
113
-
114
- // Give user time to read the instructions
115
- await inquirer.prompt([
116
- {
117
- type: 'input',
118
- name: 'continue',
119
- message: chalk.dim('Press Enter to launch Claude Code...'),
120
- }
121
- ]);
122
-
123
- try {
124
- await launchClaude(projectDir);
125
- } catch (error) {
126
- if (error.message === 'CLAUDE_NOT_FOUND') {
127
- const claudeInfo = getCLIByName(clis, 'claude');
128
- messages.claudeRequired(claudeInfo.installUrl);
129
- process.exit(1);
130
- }
131
- throw error;
132
- }
133
- }
134
- }
135
-
136
- module.exports = { main };
1
+ const chalk = require('chalk');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const inquirer = require('inquirer');
5
+ const { detectCLIs, displayCLIStatus, getCLIByName, detectStripeCLI, displayStripeGuidance } = require('./cli-detector');
6
+ const { handleScenario } = require('./scenarios');
7
+ const { runSetupWizard } = require('./setup-wizard');
8
+ const { launchClaude } = require('./launcher');
9
+ const { validateLicense } = require('./license-validator');
10
+ const { askPaymentProcessor, askPaymentKeys } = require('./payment-config');
11
+ const messages = require('./messages');
12
+
13
+ async function main() {
14
+ // Step 1: Welcome
15
+ messages.welcome();
16
+
17
+ // Step 2: LICENSE VALIDATION
18
+ console.log('');
19
+ const { licenseKey } = await inquirer.prompt([
20
+ {
21
+ type: 'input',
22
+ name: 'licenseKey',
23
+ message: 'License key:',
24
+ validate: input => input.trim() ? true : 'License key is required'
25
+ }
26
+ ]);
27
+
28
+ console.log(chalk.dim('Validating license...'));
29
+ const validation = await validateLicense(licenseKey.trim());
30
+
31
+ if (!validation.valid) {
32
+ messages.licenseInvalid(validation.message);
33
+ process.exit(1);
34
+ }
35
+
36
+ const tier = validation.tier; // 'starter' or 'pro'
37
+
38
+ if (tier === 'starter') {
39
+ messages.starterLicenseValid();
40
+ } else {
41
+ messages.proLicenseValid();
42
+ }
43
+
44
+ // Step 3: Detect CLIs (quick check)
45
+ messages.detectingCLIs();
46
+ const clis = await detectCLIs();
47
+ displayCLIStatus(clis, chalk);
48
+
49
+ // Step 4: Check for Claude Code (required for PRO only)
50
+ const claudeCli = getCLIByName(clis, 'claude');
51
+ if (tier === 'pro' && !claudeCli.installed) {
52
+ messages.claudeRequired(claudeCli.installUrl);
53
+ process.exit(1);
54
+ }
55
+
56
+ // Step 4.5: Payment configuration (PRO only)
57
+ let paymentConfig = null;
58
+ if (tier === 'pro') {
59
+ messages.paymentProcessorPrompt();
60
+ const processor = await askPaymentProcessor();
61
+
62
+ // Detect Stripe CLI if Stripe is selected
63
+ const stripeCLI = await detectStripeCLI(processor);
64
+ if (stripeCLI.required && !stripeCLI.installed) {
65
+ const continueWithout = await displayStripeGuidance(chalk);
66
+ if (!continueWithout) {
67
+ console.log('');
68
+ console.log(chalk.dim('Setup cancelled. Install Stripe CLI and try again.'));
69
+ process.exit(0);
70
+ }
71
+ }
72
+
73
+ const keysResult = await askPaymentKeys(processor);
74
+
75
+ paymentConfig = {
76
+ processor,
77
+ hasKeys: keysResult.hasKeys,
78
+ keys: keysResult.keys,
79
+ stripeCLIInstalled: stripeCLI.installed
80
+ };
81
+
82
+ if (keysResult.hasKeys) {
83
+ messages.paymentKeysReceived(processor);
84
+ } else {
85
+ messages.paymentKeysSkipped();
86
+ }
87
+ }
88
+
89
+ // Step 5: Clone scenario
90
+ let projectDir;
91
+ try {
92
+ projectDir = await handleScenario(tier, clis, false, paymentConfig); // useGitHub = false, AI PM will handle
93
+ } catch (error) {
94
+ if (error.message === 'Aborted by user') {
95
+ console.log('');
96
+ console.log(chalk.dim('Goodbye!'));
97
+ process.exit(0);
98
+ }
99
+ throw error;
100
+ }
101
+
102
+ // Step 6: TIER-SPECIFIC POST-CLONE FLOW
103
+ if (tier === 'starter') {
104
+ // Starter: Run setup wizard, then done
105
+ await runSetupWizard(projectDir);
106
+ messages.starterWizardComplete();
107
+ } else {
108
+ // Pro: Launch Claude Code directly - AI PM handles everything
109
+ messages.proLaunchingAIPM(projectDir);
110
+
111
+ // Give user time to read the instructions
112
+ await inquirer.prompt([
113
+ {
114
+ type: 'input',
115
+ name: 'continue',
116
+ message: chalk.dim('Press Enter to launch Claude Code...'),
117
+ }
118
+ ]);
119
+
120
+ try {
121
+ await launchClaude(projectDir);
122
+ } catch (error) {
123
+ if (error.message === 'CLAUDE_NOT_FOUND') {
124
+ const claudeInfo = getCLIByName(clis, 'claude');
125
+ messages.claudeRequired(claudeInfo.installUrl);
126
+ process.exit(1);
127
+ }
128
+ throw error;
129
+ }
130
+ }
131
+ }
132
+
133
+ module.exports = { main };
package/src/launcher.js CHANGED
@@ -1,43 +1,43 @@
1
- const { spawn } = require('child_process');
2
- const path = require('path');
3
- const messages = require('./messages');
4
-
5
- /**
6
- * Launch Claude Code in the specified directory
7
- * @param {string} projectDir - Absolute path to project directory
8
- * @param {object} options - Launch options
9
- * @returns {Promise<void>}
10
- */
11
- function launchClaude(projectDir, _options = {}) {
12
- return new Promise((resolve, reject) => {
13
- const resolvedDir = path.resolve(projectDir);
14
-
15
- messages.launchingClaude(resolvedDir);
16
-
17
- // Launch with the /propelkit:new-project command pre-filled
18
- const claude = spawn('claude', ['/propelkit:new-project'], {
19
- cwd: resolvedDir,
20
- stdio: 'inherit', // User sees Claude's I/O directly
21
- shell: true // Required for Windows compatibility
22
- });
23
-
24
- claude.on('error', (err) => {
25
- if (err.code === 'ENOENT') {
26
- reject(new Error('CLAUDE_NOT_FOUND'));
27
- } else {
28
- reject(new Error(`Failed to launch Claude Code: ${err.message}`));
29
- }
30
- });
31
-
32
- claude.on('exit', (code) => {
33
- if (code === 0) {
34
- resolve();
35
- } else {
36
- // Non-zero exit is fine - user may have quit normally
37
- resolve();
38
- }
39
- });
40
- });
41
- }
42
-
43
- module.exports = { launchClaude };
1
+ const { spawn } = require('child_process');
2
+ const path = require('path');
3
+ const messages = require('./messages');
4
+
5
+ /**
6
+ * Launch Claude Code in the specified directory
7
+ * @param {string} projectDir - Absolute path to project directory
8
+ * @param {object} options - Launch options
9
+ * @returns {Promise<void>}
10
+ */
11
+ function launchClaude(projectDir, _options = {}) {
12
+ return new Promise((resolve, reject) => {
13
+ const resolvedDir = path.resolve(projectDir);
14
+
15
+ messages.launchingClaude(resolvedDir);
16
+
17
+ // Launch with the /propelkit:new-project command pre-filled
18
+ const claude = spawn('claude', ['/propelkit:new-project'], {
19
+ cwd: resolvedDir,
20
+ stdio: 'inherit', // User sees Claude's I/O directly
21
+ shell: true // Required for Windows compatibility
22
+ });
23
+
24
+ claude.on('error', (err) => {
25
+ if (err.code === 'ENOENT') {
26
+ reject(new Error('CLAUDE_NOT_FOUND'));
27
+ } else {
28
+ reject(new Error(`Failed to launch Claude Code: ${err.message}`));
29
+ }
30
+ });
31
+
32
+ claude.on('exit', (code) => {
33
+ if (code === 0) {
34
+ resolve();
35
+ } else {
36
+ // Non-zero exit is fine - user may have quit normally
37
+ resolve();
38
+ }
39
+ });
40
+ });
41
+ }
42
+
43
+ module.exports = { launchClaude };
package/src/messages.js CHANGED
@@ -1,119 +1,143 @@
1
- const chalk = require('chalk');
2
-
3
- const messages = {
4
- welcome: () => {
5
- console.log('');
6
- console.log(chalk.cyan.bold('PropelKit'));
7
- console.log(chalk.dim('AI-powered SaaS boilerplate for India'));
8
- console.log('');
9
- },
10
-
11
- detectingCLIs: () => {
12
- console.log(chalk.dim('Detecting installed tools...'));
13
- },
14
-
15
- claudeRequired: (installUrl) => {
16
- console.log('');
17
- console.log(chalk.red.bold('Claude Code is required to use PropelKit'));
18
- console.log('');
19
- console.log('PropelKit uses Claude Code as the AI PM to build your project.');
20
- console.log('');
21
- console.log(chalk.cyan('Install Claude Code:'));
22
- console.log(chalk.dim(` ${installUrl}`));
23
- console.log('');
24
- console.log('After installing, run this command again.');
25
- console.log('');
26
- },
27
-
28
- supabaseBenefits: () => {
29
- console.log(chalk.cyan('Supabase CLI Benefits:'));
30
- console.log(chalk.dim(' - Automatic database migrations after each phase'));
31
- console.log(chalk.dim(' - No need to manually copy SQL to Supabase dashboard'));
32
- console.log(chalk.dim(' - Local development with supabase start'));
33
- console.log('');
34
- console.log(chalk.dim('Without Supabase CLI:'));
35
- console.log(chalk.dim(' - SQL will be shown inline for you to paste manually'));
36
- console.log(chalk.dim(' - Migration files saved to supabase/migrations/'));
37
- console.log('');
38
- },
39
-
40
- githubBenefits: () => {
41
- console.log(chalk.cyan('GitHub CLI Benefits:'));
42
- console.log(chalk.dim(' - Automatic repo creation'));
43
- console.log(chalk.dim(' - Automatic push after each phase'));
44
- console.log(chalk.dim(' - Branch management'));
45
- console.log('');
46
- console.log(chalk.dim('Without GitHub CLI:'));
47
- console.log(chalk.dim(' - Local git commits still happen'));
48
- console.log(chalk.dim(' - You push to remote manually'));
49
- console.log('');
50
- },
51
-
52
- launchingClaude: (projectDir) => {
53
- console.log('');
54
- console.log(chalk.cyan.bold('Launching Claude Code...'));
55
- console.log(chalk.dim(`Directory: ${projectDir}`));
56
- console.log('');
57
- console.log(chalk.yellow('When Claude Code opens, type:'));
58
- console.log(chalk.white.bold(' /propelkit:new-project'));
59
- console.log('');
60
- console.log(chalk.dim('This starts the AI PM conversation to build your project.'));
61
- console.log('');
62
- },
63
-
64
- cloneInstructions: (repoUrl, targetDir) => {
65
- console.log('');
66
- console.log(chalk.cyan('Cloning PropelKit...'));
67
- console.log(chalk.dim(` From: ${repoUrl}`));
68
- console.log(chalk.dim(` To: ${targetDir}`));
69
- console.log('');
70
- },
71
-
72
- resumeInstructions: (command) => {
73
- console.log('');
74
- console.log(chalk.yellow('To resume, run:'));
75
- console.log(chalk.white.bold(` ${command}`));
76
- console.log('');
77
- },
78
-
79
- starterLicenseValid: () => {
80
- console.log(chalk.green('License validated: STARTER'));
81
- console.log('');
82
- },
83
-
84
- proLicenseValid: () => {
85
- console.log(chalk.green('License validated: PRO'));
86
- console.log('');
87
- },
88
-
89
- licenseInvalid: (message) => {
90
- console.log('');
91
- console.log(chalk.red('License validation failed'));
92
- console.log(chalk.dim(message || 'Invalid license key'));
93
- console.log('');
94
- console.log(`Purchase a license at: ${chalk.cyan('https://propelkit.dev')}`);
95
- console.log('');
96
- },
97
-
98
- starterWizardComplete: () => {
99
- console.log('');
100
- console.log(chalk.green.bold('PropelKit Starter is ready!'));
101
- console.log('');
102
- console.log(chalk.dim('Run these commands to start:'));
103
- console.log(chalk.cyan(' npm install'));
104
- console.log(chalk.cyan(' npm run dev'));
105
- console.log('');
106
- },
107
-
108
- proLaunchingAIPM: (projectDir) => {
109
- console.log('');
110
- console.log(chalk.cyan.bold('Launching AI PM in Claude Code...'));
111
- console.log(chalk.dim(`Project: ${projectDir}`));
112
- console.log('');
113
- console.log(chalk.dim('Claude will start with /propelkit:new-project'));
114
- console.log(chalk.dim('Just press Enter to begin the AI PM conversation.'));
115
- console.log('');
116
- }
117
- };
118
-
119
- module.exports = messages;
1
+ const chalk = require('chalk');
2
+
3
+ const messages = {
4
+ welcome: () => {
5
+ console.log('');
6
+ console.log(chalk.cyan.bold('PropelKit'));
7
+ console.log(chalk.dim('AI-powered SaaS boilerplate for India'));
8
+ console.log('');
9
+ },
10
+
11
+ detectingCLIs: () => {
12
+ console.log(chalk.dim('Detecting installed tools...'));
13
+ },
14
+
15
+ claudeRequired: (installUrl) => {
16
+ console.log('');
17
+ console.log(chalk.red.bold('Claude Code is required to use PropelKit'));
18
+ console.log('');
19
+ console.log('PropelKit uses Claude Code as the AI PM to build your project.');
20
+ console.log('');
21
+ console.log(chalk.cyan('Install Claude Code:'));
22
+ console.log(chalk.dim(` ${installUrl}`));
23
+ console.log('');
24
+ console.log('After installing, run this command again.');
25
+ console.log('');
26
+ },
27
+
28
+ supabaseBenefits: () => {
29
+ console.log(chalk.cyan('Supabase CLI Benefits:'));
30
+ console.log(chalk.dim(' - Automatic database migrations after each phase'));
31
+ console.log(chalk.dim(' - No need to manually copy SQL to Supabase dashboard'));
32
+ console.log(chalk.dim(' - Local development with supabase start'));
33
+ console.log('');
34
+ console.log(chalk.dim('Without Supabase CLI:'));
35
+ console.log(chalk.dim(' - SQL will be shown inline for you to paste manually'));
36
+ console.log(chalk.dim(' - Migration files saved to supabase/migrations/'));
37
+ console.log('');
38
+ },
39
+
40
+ githubBenefits: () => {
41
+ console.log(chalk.cyan('GitHub CLI Benefits:'));
42
+ console.log(chalk.dim(' - Automatic repo creation'));
43
+ console.log(chalk.dim(' - Automatic push after each phase'));
44
+ console.log(chalk.dim(' - Branch management'));
45
+ console.log('');
46
+ console.log(chalk.dim('Without GitHub CLI:'));
47
+ console.log(chalk.dim(' - Local git commits still happen'));
48
+ console.log(chalk.dim(' - You push to remote manually'));
49
+ console.log('');
50
+ },
51
+
52
+ launchingClaude: (projectDir) => {
53
+ console.log('');
54
+ console.log(chalk.cyan.bold('Launching Claude Code...'));
55
+ console.log(chalk.dim(`Directory: ${projectDir}`));
56
+ console.log('');
57
+ console.log(chalk.yellow('When Claude Code opens, type:'));
58
+ console.log(chalk.white.bold(' /propelkit:new-project'));
59
+ console.log('');
60
+ console.log(chalk.dim('This starts the AI PM conversation to build your project.'));
61
+ console.log('');
62
+ },
63
+
64
+ cloneInstructions: (repoUrl, targetDir) => {
65
+ console.log('');
66
+ console.log(chalk.cyan('Cloning PropelKit...'));
67
+ console.log(chalk.dim(` From: ${repoUrl}`));
68
+ console.log(chalk.dim(` To: ${targetDir}`));
69
+ console.log('');
70
+ },
71
+
72
+ resumeInstructions: (command) => {
73
+ console.log('');
74
+ console.log(chalk.yellow('To resume, run:'));
75
+ console.log(chalk.white.bold(` ${command}`));
76
+ console.log('');
77
+ },
78
+
79
+ starterLicenseValid: () => {
80
+ console.log(chalk.green('License validated: STARTER'));
81
+ console.log('');
82
+ },
83
+
84
+ proLicenseValid: () => {
85
+ console.log(chalk.green('License validated: PRO'));
86
+ console.log('');
87
+ },
88
+
89
+ licenseInvalid: (message) => {
90
+ console.log('');
91
+ console.log(chalk.red('License validation failed'));
92
+ console.log(chalk.dim(message || 'Invalid license key'));
93
+ console.log('');
94
+ console.log(`Purchase a license at: ${chalk.cyan('https://propelkit.dev')}`);
95
+ console.log('');
96
+ },
97
+
98
+ starterWizardComplete: () => {
99
+ console.log('');
100
+ console.log(chalk.green.bold('PropelKit Starter is ready!'));
101
+ console.log('');
102
+ console.log(chalk.dim('Run these commands to start:'));
103
+ console.log(chalk.cyan(' npm install'));
104
+ console.log(chalk.cyan(' npm run dev'));
105
+ console.log('');
106
+ },
107
+
108
+ proLaunchingAIPM: (projectDir) => {
109
+ console.log('');
110
+ console.log(chalk.cyan.bold('Launching AI PM in Claude Code...'));
111
+ console.log(chalk.dim(`Project: ${projectDir}`));
112
+ console.log('');
113
+ console.log(chalk.dim('Claude will start with /propelkit:new-project'));
114
+ console.log(chalk.dim('Just press Enter to begin the AI PM conversation.'));
115
+ console.log('');
116
+ },
117
+
118
+ paymentProcessorPrompt: () => {
119
+ console.log('');
120
+ console.log(chalk.cyan.bold('Payment Configuration'));
121
+ console.log(chalk.dim('-'.repeat(50)));
122
+ console.log('');
123
+ console.log(chalk.dim('PropelKit supports multiple payment processors:'));
124
+ console.log(chalk.dim(' - Stripe for international payments'));
125
+ console.log(chalk.dim(' - Razorpay for India (INR + UPI)'));
126
+ console.log(chalk.dim(' - Both for global SaaS with auto-routing'));
127
+ console.log('');
128
+ },
129
+
130
+ paymentKeysSkipped: () => {
131
+ console.log('');
132
+ console.log(chalk.dim('API keys skipped. AI PM will generate .env template.'));
133
+ console.log('');
134
+ },
135
+
136
+ paymentKeysReceived: (processor) => {
137
+ console.log('');
138
+ console.log(chalk.green(`${processor} keys saved.`));
139
+ console.log('');
140
+ }
141
+ };
142
+
143
+ module.exports = messages;
@@ -0,0 +1,119 @@
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+
4
+ /**
5
+ * Ask user to select payment processor
6
+ * @returns {Promise<'stripe'|'razorpay'|'both'>}
7
+ */
8
+ async function askPaymentProcessor() {
9
+ const { processor } = await inquirer.prompt([
10
+ {
11
+ type: 'list',
12
+ name: 'processor',
13
+ message: 'Which payment processor?',
14
+ choices: [
15
+ {
16
+ name: `${chalk.cyan('Stripe')} - International (USD, EUR, GBP)`,
17
+ value: 'stripe'
18
+ },
19
+ {
20
+ name: `${chalk.cyan('Razorpay')} - India (INR, UPI)`,
21
+ value: 'razorpay'
22
+ },
23
+ {
24
+ name: `${chalk.cyan('Both')} - Global with auto-routing`,
25
+ value: 'both'
26
+ }
27
+ ]
28
+ }
29
+ ]);
30
+
31
+ return processor;
32
+ }
33
+
34
+ /**
35
+ * Ask user for payment processor API keys
36
+ * @param {string} processor - 'stripe' | 'razorpay' | 'both'
37
+ * @returns {Promise<{hasKeys: boolean, keys: object|null}>}
38
+ */
39
+ async function askPaymentKeys(processor) {
40
+ // First, ask if user has keys ready
41
+ const { hasKeys } = await inquirer.prompt([
42
+ {
43
+ type: 'confirm',
44
+ name: 'hasKeys',
45
+ message: `Do you have ${processor === 'both' ? 'your' : processor} API keys ready?`,
46
+ default: false
47
+ }
48
+ ]);
49
+
50
+ if (!hasKeys) {
51
+ return { hasKeys: false, keys: null };
52
+ }
53
+
54
+ // User has keys - prompt based on processor selection
55
+ const questions = [];
56
+
57
+ // Stripe keys (if needed)
58
+ if (processor === 'stripe' || processor === 'both') {
59
+ questions.push(
60
+ {
61
+ type: 'input',
62
+ name: 'STRIPE_SECRET_KEY',
63
+ message: 'Stripe Secret Key:',
64
+ when: () => processor === 'stripe' || processor === 'both',
65
+ validate: input => input.trim() ? true : 'Stripe Secret Key is required'
66
+ },
67
+ {
68
+ type: 'input',
69
+ name: 'STRIPE_PUBLISHABLE_KEY',
70
+ message: 'Stripe Publishable Key:',
71
+ when: () => processor === 'stripe' || processor === 'both',
72
+ validate: input => input.trim() ? true : 'Stripe Publishable Key is required'
73
+ },
74
+ {
75
+ type: 'input',
76
+ name: 'STRIPE_WEBHOOK_SECRET',
77
+ message: 'Stripe Webhook Secret (optional, can set up later):',
78
+ when: () => processor === 'stripe' || processor === 'both',
79
+ default: ''
80
+ }
81
+ );
82
+ }
83
+
84
+ // Razorpay keys (if needed)
85
+ if (processor === 'razorpay' || processor === 'both') {
86
+ questions.push(
87
+ {
88
+ type: 'input',
89
+ name: 'RAZORPAY_KEY_ID',
90
+ message: 'Razorpay Key ID:',
91
+ when: () => processor === 'razorpay' || processor === 'both',
92
+ validate: input => input.trim() ? true : 'Razorpay Key ID is required'
93
+ },
94
+ {
95
+ type: 'input',
96
+ name: 'RAZORPAY_KEY_SECRET',
97
+ message: 'Razorpay Key Secret:',
98
+ when: () => processor === 'razorpay' || processor === 'both',
99
+ validate: input => input.trim() ? true : 'Razorpay Key Secret is required'
100
+ },
101
+ {
102
+ type: 'input',
103
+ name: 'RAZORPAY_WEBHOOK_SECRET',
104
+ message: 'Razorpay Webhook Secret (optional, can set up later):',
105
+ when: () => processor === 'razorpay' || processor === 'both',
106
+ default: ''
107
+ }
108
+ );
109
+ }
110
+
111
+ const keys = await inquirer.prompt(questions);
112
+
113
+ return { hasKeys: true, keys };
114
+ }
115
+
116
+ module.exports = {
117
+ askPaymentProcessor,
118
+ askPaymentKeys
119
+ };
package/src/scenarios.js CHANGED
@@ -5,6 +5,7 @@ const inquirer = require('inquirer');
5
5
  const chalk = require('chalk');
6
6
  const _messages = require('./messages'); // Reserved for future use
7
7
  const { REPOS } = require('./config');
8
+ const { persistPaymentConfig } = require('./blueprint-manager');
8
9
 
9
10
  /**
10
11
  * Ask user which scenario applies
@@ -36,9 +37,10 @@ async function askScenario() {
36
37
  * @param {string} tier - License tier ('starter' or 'pro')
37
38
  * @param {object} clis - CLI detection results
38
39
  * @param {boolean} useGitHub - Whether user wants GitHub integration (from CLI-03 prompt)
40
+ * @param {object} paymentConfig - Payment configuration from wizard (optional)
39
41
  * @returns {Promise<string>} - Path to project directory
40
42
  */
41
- async function handleFreshStart(tier, clis, useGitHub) {
43
+ async function handleFreshStart(tier, clis, useGitHub, paymentConfig = null) {
42
44
  // Ask for project name/directory
43
45
  const { projectName } = await inquirer.prompt([
44
46
  {
@@ -87,6 +89,9 @@ async function handleFreshStart(tier, clis, useGitHub) {
87
89
  // Write initial config with CLI availability, useGitHub preference, and tier
88
90
  await writeInitialConfig(targetDir, clis, useGitHub, tier);
89
91
 
92
+ // Persist payment configuration to blueprint.json
93
+ persistPaymentConfig(targetDir, paymentConfig, tier);
94
+
90
95
  return targetDir;
91
96
  } catch (error) {
92
97
  throw new Error(`Failed to clone PropelKit: ${error.message}`);
@@ -98,9 +103,10 @@ async function handleFreshStart(tier, clis, useGitHub) {
98
103
  * @param {string} tier - License tier ('starter' or 'pro')
99
104
  * @param {object} clis - CLI detection results
100
105
  * @param {boolean} useGitHub - Whether user wants GitHub integration (from CLI-03 prompt)
106
+ * @param {object} paymentConfig - Payment configuration from wizard (optional)
101
107
  * @returns {Promise<string>} - Path to project directory
102
108
  */
103
- async function handleExistingClone(tier, clis, useGitHub) {
109
+ async function handleExistingClone(tier, clis, useGitHub, paymentConfig = null) {
104
110
  const currentDir = process.cwd();
105
111
 
106
112
  // Check if this looks like a PropelKit project
@@ -137,6 +143,9 @@ async function handleExistingClone(tier, clis, useGitHub) {
137
143
  // Write config with CLI availability, useGitHub preference, and tier
138
144
  await writeInitialConfig(currentDir, clis, useGitHub, tier);
139
145
 
146
+ // Persist payment configuration to blueprint.json
147
+ persistPaymentConfig(currentDir, paymentConfig, tier);
148
+
140
149
  return currentDir;
141
150
  }
142
151
 
@@ -197,15 +206,16 @@ async function writeInitialConfig(projectDir, clis, useGitHub, tier) {
197
206
  * @param {string} tier - License tier ('starter' or 'pro')
198
207
  * @param {object[]} clis - CLI detection results
199
208
  * @param {boolean} useGitHub - Whether user wants GitHub integration (from CLI-03 prompt)
209
+ * @param {object} paymentConfig - Payment configuration from wizard (optional)
200
210
  * @returns {Promise<string>} - Path to project directory
201
211
  */
202
- async function handleScenario(tier, clis, useGitHub) {
212
+ async function handleScenario(tier, clis, useGitHub, paymentConfig = null) {
203
213
  const scenario = await askScenario();
204
214
 
205
215
  if (scenario === 'fresh') {
206
- return handleFreshStart(tier, clis, useGitHub);
216
+ return handleFreshStart(tier, clis, useGitHub, paymentConfig);
207
217
  } else {
208
- return handleExistingClone(tier, clis, useGitHub);
218
+ return handleExistingClone(tier, clis, useGitHub, paymentConfig);
209
219
  }
210
220
  }
211
221