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 +31 -31
- package/src/blueprint-manager.js +144 -0
- package/src/cli-detector.js +80 -0
- package/src/index.js +133 -136
- package/src/launcher.js +43 -43
- package/src/messages.js +143 -119
- package/src/payment-config.js +119 -0
- package/src/scenarios.js +15 -5
package/package.json
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-propelkit",
|
|
3
|
-
"version": "1.0.
|
|
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
|
+
};
|
package/src/cli-detector.js
CHANGED
|
@@ -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,
|
|
6
|
-
const { handleScenario } = require('./scenarios');
|
|
7
|
-
const {
|
|
8
|
-
const {
|
|
9
|
-
const {
|
|
10
|
-
const {
|
|
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
|
|
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:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
|