create-solostack 1.2.2 ā 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generators/base.js +6 -5
- package/src/generators/pro/admin.js +344 -0
- package/src/generators/pro/api-keys.js +464 -0
- package/src/generators/pro/database-full.js +233 -0
- package/src/generators/pro/emails.js +248 -0
- package/src/generators/pro/oauth.js +217 -0
- package/src/generators/pro/stripe-advanced.js +521 -0
- package/src/generators/setup.js +38 -21
- package/src/index.js +112 -4
- package/src/utils/license.js +83 -0
- package/src/utils/logger.js +33 -0
- package/src/utils/packages.js +14 -0
package/src/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import chalk from 'chalk';
|
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { validateProjectName } from './utils/validate.js';
|
|
7
|
-
import { printSuccess, printError } from './utils/logger.js';
|
|
7
|
+
import { printSuccess, printError, printProSuccess } from './utils/logger.js';
|
|
8
8
|
import { ensureDir, writeFile } from './utils/files.js';
|
|
9
|
-
import { installPackages } from './utils/packages.js';
|
|
9
|
+
import { installPackages, installProPackages } from './utils/packages.js';
|
|
10
10
|
import { initGit } from './utils/git.js';
|
|
11
11
|
import { generateBase } from './generators/base.js';
|
|
12
12
|
import { generateDatabase } from './generators/database.js';
|
|
@@ -15,6 +15,14 @@ import { generatePayments } from './generators/payments.js';
|
|
|
15
15
|
import { generateEmails } from './generators/emails.js';
|
|
16
16
|
import { generateSetup } from './generators/setup.js';
|
|
17
17
|
import { generateUI } from './generators/ui.js';
|
|
18
|
+
import { validateLicense, isLicenseKeyFormat } from './utils/license.js';
|
|
19
|
+
// Pro generators
|
|
20
|
+
import { generateOAuth } from './generators/pro/oauth.js';
|
|
21
|
+
import { generateFullDatabase } from './generators/pro/database-full.js';
|
|
22
|
+
import { generateAdvancedStripe } from './generators/pro/stripe-advanced.js';
|
|
23
|
+
import { generateAdvancedEmails } from './generators/pro/emails.js';
|
|
24
|
+
import { generateAdmin } from './generators/pro/admin.js';
|
|
25
|
+
import { generateApiKeys } from './generators/pro/api-keys.js';
|
|
18
26
|
import {
|
|
19
27
|
AUTH_PROVIDERS,
|
|
20
28
|
DATABASES,
|
|
@@ -112,6 +120,68 @@ export async function main() {
|
|
|
112
120
|
|
|
113
121
|
const projectPath = path.join(process.cwd(), projectName);
|
|
114
122
|
|
|
123
|
+
// Pro upgrade prompt
|
|
124
|
+
let hasProLicense = false;
|
|
125
|
+
let licenseData = null;
|
|
126
|
+
|
|
127
|
+
const proAnswer = await inquirer.prompt([
|
|
128
|
+
{
|
|
129
|
+
type: 'confirm',
|
|
130
|
+
name: 'wantsPro',
|
|
131
|
+
message: chalk.cyan('š Upgrade to Pro? (Admin panel, OAuth, API keys, advanced features)'),
|
|
132
|
+
default: false,
|
|
133
|
+
}
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
if (proAnswer.wantsPro) {
|
|
137
|
+
let retryLicense = true;
|
|
138
|
+
|
|
139
|
+
while (retryLicense && !hasProLicense) {
|
|
140
|
+
const licenseAnswer = await inquirer.prompt([
|
|
141
|
+
{
|
|
142
|
+
type: 'input',
|
|
143
|
+
name: 'licenseKey',
|
|
144
|
+
message: 'Enter your Pro license key (get one at https://solostack.dev/pro):',
|
|
145
|
+
validate: (input) => {
|
|
146
|
+
if (!input) {
|
|
147
|
+
return 'License key required. Visit https://solostack.dev/pro to purchase.';
|
|
148
|
+
}
|
|
149
|
+
if (!isLicenseKeyFormat(input)) {
|
|
150
|
+
return 'Invalid license key format. Expected: sk_live_XXXXX...';
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
let spinner = ora('Validating Pro license...').start();
|
|
158
|
+
licenseData = await validateLicense(licenseAnswer.licenseKey);
|
|
159
|
+
|
|
160
|
+
if (licenseData.valid) {
|
|
161
|
+
hasProLicense = true;
|
|
162
|
+
spinner.succeed(chalk.green(`Pro license validated! Welcome ${licenseData.email} š`));
|
|
163
|
+
} else {
|
|
164
|
+
spinner.fail(chalk.red('Invalid license key'));
|
|
165
|
+
|
|
166
|
+
const retryAnswer = await inquirer.prompt([
|
|
167
|
+
{
|
|
168
|
+
type: 'confirm',
|
|
169
|
+
name: 'retry',
|
|
170
|
+
message: 'Would you like to try a different license key?',
|
|
171
|
+
default: true,
|
|
172
|
+
}
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
retryLicense = retryAnswer.retry;
|
|
176
|
+
|
|
177
|
+
if (!retryLicense) {
|
|
178
|
+
console.log(chalk.yellow('\nš” Generating free version instead.'));
|
|
179
|
+
console.log(chalk.yellow(' Get Pro at: https://solostack.dev/pro\n'));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
115
185
|
try {
|
|
116
186
|
// Start generation
|
|
117
187
|
console.log(chalk.cyan('\\nāļø Creating your SaaS boilerplate...\\n'));
|
|
@@ -153,9 +223,43 @@ export async function main() {
|
|
|
153
223
|
|
|
154
224
|
// Generate setup & diagnostics
|
|
155
225
|
spinner = ora('Adding diagnostics page').start();
|
|
156
|
-
await generateSetup(projectPath);
|
|
226
|
+
await generateSetup(projectPath, config);
|
|
157
227
|
spinner.succeed('Added diagnostics page (/setup)');
|
|
158
228
|
|
|
229
|
+
// Pro Features Generation
|
|
230
|
+
if (hasProLicense) {
|
|
231
|
+
console.log(chalk.cyan('\nš Adding Pro features...\n'));
|
|
232
|
+
|
|
233
|
+
spinner = ora('Adding OAuth providers (Google, GitHub)').start();
|
|
234
|
+
await generateOAuth(projectPath);
|
|
235
|
+
spinner.succeed('OAuth providers configured');
|
|
236
|
+
|
|
237
|
+
spinner = ora('Upgrading database schema').start();
|
|
238
|
+
await generateFullDatabase(projectPath);
|
|
239
|
+
spinner.succeed('Full database schema created');
|
|
240
|
+
|
|
241
|
+
spinner = ora('Setting up advanced Stripe integration').start();
|
|
242
|
+
await generateAdvancedStripe(projectPath);
|
|
243
|
+
spinner.succeed('Stripe subscriptions and webhooks configured');
|
|
244
|
+
|
|
245
|
+
spinner = ora('Adding email templates').start();
|
|
246
|
+
await generateAdvancedEmails(projectPath);
|
|
247
|
+
spinner.succeed('Email templates created');
|
|
248
|
+
|
|
249
|
+
spinner = ora('Creating admin panel').start();
|
|
250
|
+
await generateAdmin(projectPath);
|
|
251
|
+
spinner.succeed('Admin panel created');
|
|
252
|
+
|
|
253
|
+
spinner = ora('Setting up API key system').start();
|
|
254
|
+
await generateApiKeys(projectPath);
|
|
255
|
+
spinner.succeed('API key system configured');
|
|
256
|
+
|
|
257
|
+
// Install Pro-specific packages
|
|
258
|
+
spinner = ora('Installing Pro packages...').start();
|
|
259
|
+
await installProPackages(projectPath);
|
|
260
|
+
spinner.succeed('Pro packages installed');
|
|
261
|
+
}
|
|
262
|
+
|
|
159
263
|
// Install dependencies
|
|
160
264
|
spinner = ora('Installing dependencies (this may take a minute...)').start();
|
|
161
265
|
await installPackages(projectPath);
|
|
@@ -169,7 +273,11 @@ export async function main() {
|
|
|
169
273
|
}
|
|
170
274
|
|
|
171
275
|
// Success message
|
|
172
|
-
|
|
276
|
+
if (hasProLicense) {
|
|
277
|
+
printProSuccess(projectName, projectPath);
|
|
278
|
+
} else {
|
|
279
|
+
printSuccess(projectName, projectPath);
|
|
280
|
+
}
|
|
173
281
|
|
|
174
282
|
// Optional setup wizard
|
|
175
283
|
console.log(); // Empty line for spacing
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License validation utility for SoloStack Pro
|
|
3
|
+
* Validates license keys against the SoloStack API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// License API endpoint - Deployed Cloudflare Worker
|
|
7
|
+
const LICENSE_API = 'https://solostack-license-api.solo-stack.workers.dev/validate';
|
|
8
|
+
|
|
9
|
+
// Mock mode for local testing (set SOLOSTACK_MOCK_LICENSE=true)
|
|
10
|
+
const MOCK_MODE = process.env.SOLOSTACK_MOCK_LICENSE === 'true';
|
|
11
|
+
|
|
12
|
+
// Test license key for development (exactly 32 chars after sk_live_)
|
|
13
|
+
const TEST_LICENSE = 'sk_live_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates a license key against the SoloStack API
|
|
17
|
+
* @param {string} licenseKey - The license key to validate
|
|
18
|
+
* @returns {Promise<{valid: boolean, email?: string, features?: string[], error?: string}>}
|
|
19
|
+
*/
|
|
20
|
+
export async function validateLicense(licenseKey) {
|
|
21
|
+
// Mock mode for local testing
|
|
22
|
+
if (MOCK_MODE) {
|
|
23
|
+
if (licenseKey === TEST_LICENSE) {
|
|
24
|
+
return {
|
|
25
|
+
valid: true,
|
|
26
|
+
email: 'test@example.com',
|
|
27
|
+
features: ['all'],
|
|
28
|
+
purchaseDate: '2025-01-08',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { valid: false, error: 'Invalid test license key' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(LICENSE_API, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
body: JSON.stringify({ license: licenseKey }),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
return { valid: false, error: `API error: ${response.status}` };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const data = await response.json();
|
|
46
|
+
return {
|
|
47
|
+
valid: data.valid === true,
|
|
48
|
+
email: data.email,
|
|
49
|
+
features: data.features || [],
|
|
50
|
+
purchaseDate: data.purchaseDate,
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// Network error - allow offline mode with cached license
|
|
54
|
+
console.error('License validation error:', error.message);
|
|
55
|
+
return {
|
|
56
|
+
valid: false,
|
|
57
|
+
error: 'Could not connect to license server. Check your internet connection.',
|
|
58
|
+
offline: true
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Checks if a string matches the license key format
|
|
65
|
+
* Format: sk_live_XXXXXXXXXXXXXXXXXXXXXXXXXX (sk_live_ + 32+ alphanumeric chars)
|
|
66
|
+
* @param {string} key - The key to check
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
export function isLicenseKeyFormat(key) {
|
|
70
|
+
if (!key || typeof key !== 'string') return false;
|
|
71
|
+
return /^sk_live_[A-Za-z0-9]{32,}$/.test(key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns a masked version of the license key for display
|
|
76
|
+
* e.g., sk_live_abc123... -> sk_live_abc1****
|
|
77
|
+
* @param {string} key - The license key
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export function maskLicenseKey(key) {
|
|
81
|
+
if (!key || key.length < 12) return '****';
|
|
82
|
+
return key.substring(0, 12) + '****';
|
|
83
|
+
}
|
package/src/utils/logger.js
CHANGED
|
@@ -60,3 +60,36 @@ export function printWarning(message) {
|
|
|
60
60
|
export function printInfo(message) {
|
|
61
61
|
console.log(chalk.blue(`\nā¹ļø ${message}\n`));
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Prints a Pro success message with Pro-specific next steps
|
|
66
|
+
* @param {string} projectName - Name of the generated project
|
|
67
|
+
* @param {string} projectPath - Absolute path to the project
|
|
68
|
+
*/
|
|
69
|
+
export function printProSuccess(projectName, projectPath) {
|
|
70
|
+
console.log(chalk.green.bold('\nš Your Pro SaaS is ready!\n'));
|
|
71
|
+
|
|
72
|
+
console.log(chalk.cyan('š Pro Features Included:'));
|
|
73
|
+
console.log(chalk.white(' ā OAuth (Google & GitHub)'));
|
|
74
|
+
console.log(chalk.white(' ā Advanced Stripe (subscriptions, webhooks)'));
|
|
75
|
+
console.log(chalk.white(' ā Admin panel'));
|
|
76
|
+
console.log(chalk.white(' ā API key system'));
|
|
77
|
+
console.log(chalk.white(' ā Email templates (verification, reset, etc.)'));
|
|
78
|
+
console.log(chalk.white(' ā Full database schema\n'));
|
|
79
|
+
|
|
80
|
+
console.log(chalk.yellow('š Next steps:\n'));
|
|
81
|
+
console.log(chalk.white(` 1. ${chalk.cyan(`cd ${projectName}`)}`));
|
|
82
|
+
console.log(chalk.white(` 2. ${chalk.cyan('cp .env.example .env')}`));
|
|
83
|
+
console.log(chalk.white(' 3. Add your API keys to .env'));
|
|
84
|
+
console.log(chalk.white(` 4. ${chalk.cyan('npx prisma db push')}`));
|
|
85
|
+
console.log(chalk.white(` 5. ${chalk.cyan('npm run dev')}\n`));
|
|
86
|
+
|
|
87
|
+
console.log(chalk.cyan('š§ Setup verification:'));
|
|
88
|
+
console.log(chalk.white(' Visit http://localhost:3000/setup to verify all integrations\n'));
|
|
89
|
+
|
|
90
|
+
console.log(chalk.cyan('š¤ Admin access:'));
|
|
91
|
+
console.log(chalk.white(' Visit http://localhost:3000/admin (requires admin role)\n'));
|
|
92
|
+
|
|
93
|
+
console.log(chalk.gray('Need help? Join our Discord: https://discord.gg/solostack'));
|
|
94
|
+
console.log(chalk.gray('Documentation: https://solostack.dev/docs\n'));
|
|
95
|
+
}
|
package/src/utils/packages.js
CHANGED
|
@@ -73,3 +73,17 @@ export async function addPackage(projectPath, packages, options = {}) {
|
|
|
73
73
|
stdio: 'inherit',
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Installs Pro-specific packages
|
|
79
|
+
* @param {string} projectPath - Path to the project directory
|
|
80
|
+
*/
|
|
81
|
+
export async function installProPackages(projectPath) {
|
|
82
|
+
const proPackages = [
|
|
83
|
+
'@auth/prisma-adapter',
|
|
84
|
+
'date-fns',
|
|
85
|
+
'bcryptjs',
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
await addPackage(projectPath, proPackages);
|
|
89
|
+
}
|