create-solostack 1.2.3 → 1.3.1
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/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/index.js +95 -3
- package/src/utils/license.js +83 -0
- package/src/utils/logger.js +33 -0
- package/src/utils/packages.js +14 -0
- package/src/utils/pro-api.js +71 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pro API Client
|
|
3
|
+
* Fetches Pro files from the server-side generation API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { writeFile, ensureDir } from './files.js';
|
|
8
|
+
|
|
9
|
+
// Pro generation API endpoint
|
|
10
|
+
const PRO_API = 'https://solostack-license-api.solo-stack.workers.dev/generate-pro';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Fetch Pro files from the server
|
|
14
|
+
* @param {string} licenseKey - Valid Pro license key
|
|
15
|
+
* @param {object} config - Project configuration
|
|
16
|
+
* @returns {Promise<object>} - Object with file paths and contents
|
|
17
|
+
*/
|
|
18
|
+
export async function fetchProFiles(licenseKey, config = {}) {
|
|
19
|
+
const response = await fetch(PRO_API, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify({
|
|
25
|
+
license: licenseKey,
|
|
26
|
+
config: config,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const error = await response.json().catch(() => ({ error: 'Server error' }));
|
|
32
|
+
throw new Error(error.error || 'Failed to generate Pro files');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
|
|
37
|
+
if (!data.success) {
|
|
38
|
+
throw new Error(data.error || 'Pro generation failed');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return data.files;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Write Pro files to the project directory
|
|
46
|
+
* @param {string} projectPath - Path to the project
|
|
47
|
+
* @param {object} files - Object with { "path/to/file": "content" }
|
|
48
|
+
*/
|
|
49
|
+
export async function writeProFiles(projectPath, files) {
|
|
50
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
51
|
+
const fullPath = path.join(projectPath, filePath);
|
|
52
|
+
|
|
53
|
+
// Ensure directory exists
|
|
54
|
+
await ensureDir(path.dirname(fullPath));
|
|
55
|
+
|
|
56
|
+
// Write file
|
|
57
|
+
await writeFile(fullPath, content);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate Pro features for a project
|
|
63
|
+
* @param {string} projectPath - Path to the project
|
|
64
|
+
* @param {string} licenseKey - Valid Pro license key
|
|
65
|
+
* @param {object} config - Project configuration
|
|
66
|
+
*/
|
|
67
|
+
export async function generateProFromServer(projectPath, licenseKey, config = {}) {
|
|
68
|
+
const files = await fetchProFiles(licenseKey, config);
|
|
69
|
+
await writeProFiles(projectPath, files);
|
|
70
|
+
return Object.keys(files).length;
|
|
71
|
+
}
|