@l4yercak3/cli 1.0.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/.claude/settings.local.json +18 -0
- package/.cursor/rules.md +203 -0
- package/.eslintrc.js +31 -0
- package/README.md +227 -0
- package/bin/cli.js +61 -0
- package/docs/ADDING_NEW_PROJECT_TYPE.md +156 -0
- package/docs/ARCHITECTURE_RELATIONSHIPS.md +411 -0
- package/docs/CLI_AUTHENTICATION.md +214 -0
- package/docs/DETECTOR_ARCHITECTURE.md +326 -0
- package/docs/DEVELOPMENT.md +194 -0
- package/docs/IMPLEMENTATION_PHASES.md +468 -0
- package/docs/OAUTH_CLARIFICATION.md +258 -0
- package/docs/OAUTH_SETUP_GUIDE_TEMPLATE.md +211 -0
- package/docs/PHASE_0_PROGRESS.md +120 -0
- package/docs/PHASE_1_COMPLETE.md +366 -0
- package/docs/PHASE_SUMMARY.md +149 -0
- package/docs/PLAN.md +511 -0
- package/docs/README.md +56 -0
- package/docs/STRIPE_INTEGRATION.md +447 -0
- package/docs/SUMMARY.md +230 -0
- package/docs/UPDATED_PLAN.md +447 -0
- package/package.json +53 -0
- package/src/api/backend-client.js +148 -0
- package/src/commands/login.js +146 -0
- package/src/commands/logout.js +24 -0
- package/src/commands/spread.js +364 -0
- package/src/commands/status.js +62 -0
- package/src/config/config-manager.js +205 -0
- package/src/detectors/api-client-detector.js +85 -0
- package/src/detectors/base-detector.js +77 -0
- package/src/detectors/github-detector.js +74 -0
- package/src/detectors/index.js +80 -0
- package/src/detectors/nextjs-detector.js +139 -0
- package/src/detectors/oauth-detector.js +122 -0
- package/src/detectors/registry.js +97 -0
- package/src/generators/api-client-generator.js +197 -0
- package/src/generators/env-generator.js +162 -0
- package/src/generators/gitignore-generator.js +92 -0
- package/src/generators/index.js +50 -0
- package/src/generators/nextauth-generator.js +242 -0
- package/src/generators/oauth-guide-generator.js +277 -0
- package/src/logo.js +116 -0
- package/tests/api-client-detector.test.js +214 -0
- package/tests/api-client-generator.test.js +169 -0
- package/tests/backend-client.test.js +361 -0
- package/tests/base-detector.test.js +101 -0
- package/tests/commands/login.test.js +98 -0
- package/tests/commands/logout.test.js +70 -0
- package/tests/commands/status.test.js +167 -0
- package/tests/config-manager.test.js +313 -0
- package/tests/detector-index.test.js +209 -0
- package/tests/detector-registry.test.js +93 -0
- package/tests/env-generator.test.js +278 -0
- package/tests/generators-index.test.js +215 -0
- package/tests/github-detector.test.js +145 -0
- package/tests/gitignore-generator.test.js +109 -0
- package/tests/logo.test.js +96 -0
- package/tests/nextauth-generator.test.js +231 -0
- package/tests/nextjs-detector.test.js +235 -0
- package/tests/oauth-detector.test.js +264 -0
- package/tests/oauth-guide-generator.test.js +273 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Setup Detector
|
|
3
|
+
* Detects existing OAuth/NextAuth.js configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
class OAuthDetector {
|
|
10
|
+
/**
|
|
11
|
+
* Detect existing OAuth setup
|
|
12
|
+
*/
|
|
13
|
+
detect(projectPath = process.cwd()) {
|
|
14
|
+
const results = {
|
|
15
|
+
hasOAuth: false,
|
|
16
|
+
oauthType: null, // 'nextauth', 'custom', 'unknown'
|
|
17
|
+
configPath: null,
|
|
18
|
+
providers: [], // ['google', 'microsoft', 'github']
|
|
19
|
+
hasEnvVars: false,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Check for NextAuth.js configuration
|
|
23
|
+
const nextAuthPaths = [
|
|
24
|
+
// App Router
|
|
25
|
+
'app/api/auth/[...nextauth]/route.ts',
|
|
26
|
+
'app/api/auth/[...nextauth]/route.js',
|
|
27
|
+
'src/app/api/auth/[...nextauth]/route.ts',
|
|
28
|
+
'src/app/api/auth/[...nextauth]/route.js',
|
|
29
|
+
// Pages Router
|
|
30
|
+
'pages/api/auth/[...nextauth].ts',
|
|
31
|
+
'pages/api/auth/[...nextauth].js',
|
|
32
|
+
'src/pages/api/auth/[...nextauth].ts',
|
|
33
|
+
'src/pages/api/auth/[...nextauth].js',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
for (const configPath of nextAuthPaths) {
|
|
37
|
+
const fullPath = path.join(projectPath, configPath);
|
|
38
|
+
if (fs.existsSync(fullPath)) {
|
|
39
|
+
results.hasOAuth = true;
|
|
40
|
+
results.oauthType = 'nextauth';
|
|
41
|
+
results.configPath = configPath;
|
|
42
|
+
|
|
43
|
+
// Try to detect providers from config file
|
|
44
|
+
try {
|
|
45
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
46
|
+
const detectedProviders = [];
|
|
47
|
+
|
|
48
|
+
if (content.includes('GoogleProvider') || content.includes('google')) {
|
|
49
|
+
detectedProviders.push('google');
|
|
50
|
+
}
|
|
51
|
+
if (content.includes('AzureADProvider') || content.includes('microsoft') || content.includes('azure')) {
|
|
52
|
+
detectedProviders.push('microsoft');
|
|
53
|
+
}
|
|
54
|
+
if (content.includes('GitHubProvider') || content.includes('github')) {
|
|
55
|
+
detectedProviders.push('github');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
results.providers = detectedProviders;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// Couldn't read config file
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check for OAuth-related environment variables
|
|
67
|
+
const envFiles = ['.env.local', '.env', '.env.development'];
|
|
68
|
+
for (const envFile of envFiles) {
|
|
69
|
+
const envPath = path.join(projectPath, envFile);
|
|
70
|
+
if (fs.existsSync(envPath)) {
|
|
71
|
+
try {
|
|
72
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
73
|
+
const oauthVars = [
|
|
74
|
+
'NEXTAUTH_URL',
|
|
75
|
+
'NEXTAUTH_SECRET',
|
|
76
|
+
'GOOGLE_CLIENT_ID',
|
|
77
|
+
'GOOGLE_CLIENT_SECRET',
|
|
78
|
+
'MICROSOFT_CLIENT_ID',
|
|
79
|
+
'MICROSOFT_CLIENT_SECRET',
|
|
80
|
+
'GITHUB_CLIENT_ID',
|
|
81
|
+
'GITHUB_CLIENT_SECRET',
|
|
82
|
+
'GOOGLE_OAUTH_CLIENT_ID',
|
|
83
|
+
'GOOGLE_OAUTH_CLIENT_SECRET',
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const hasOAuthVars = oauthVars.some(varName => content.includes(varName));
|
|
87
|
+
if (hasOAuthVars) {
|
|
88
|
+
results.hasEnvVars = true;
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
// Couldn't read env file
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check package.json for NextAuth.js dependency
|
|
97
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
98
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
99
|
+
try {
|
|
100
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
101
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
102
|
+
|
|
103
|
+
if (deps['next-auth'] || deps.nextauth) {
|
|
104
|
+
results.hasOAuth = true;
|
|
105
|
+
if (!results.oauthType) {
|
|
106
|
+
results.oauthType = 'nextauth';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
// Couldn't read package.json
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = new OAuthDetector();
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detector Registry
|
|
3
|
+
*
|
|
4
|
+
* Central registry for all project type detectors.
|
|
5
|
+
* Detectors are automatically sorted by priority (highest first).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const nextJsDetector = require('./nextjs-detector');
|
|
9
|
+
// Future detectors will be added here:
|
|
10
|
+
// const reactDetector = require('./react-detector');
|
|
11
|
+
// const vueDetector = require('./vue-detector');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* All registered detectors
|
|
15
|
+
* Add new detectors here as they're implemented
|
|
16
|
+
*/
|
|
17
|
+
const detectors = [
|
|
18
|
+
nextJsDetector,
|
|
19
|
+
// Future: reactDetector, vueDetector, etc.
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sort detectors by priority (highest first)
|
|
24
|
+
*/
|
|
25
|
+
const sortedDetectors = [...detectors].sort((a, b) => b.priority - a.priority);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Detect project type
|
|
29
|
+
*
|
|
30
|
+
* Runs all detectors in priority order and returns the first match
|
|
31
|
+
* with confidence > 0.8, or all results if no high-confidence match.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} projectPath - Path to project directory
|
|
34
|
+
* @returns {object} Detection results
|
|
35
|
+
*/
|
|
36
|
+
function detectProjectType(projectPath = process.cwd()) {
|
|
37
|
+
const results = {
|
|
38
|
+
detected: null, // Best match detector
|
|
39
|
+
confidence: 0, // Confidence of best match
|
|
40
|
+
metadata: {}, // Metadata from best match
|
|
41
|
+
allResults: [], // All detector results (for debugging)
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Run all detectors
|
|
45
|
+
for (const detector of sortedDetectors) {
|
|
46
|
+
try {
|
|
47
|
+
const result = detector.detect(projectPath);
|
|
48
|
+
|
|
49
|
+
results.allResults.push({
|
|
50
|
+
detector: detector.name,
|
|
51
|
+
priority: detector.priority,
|
|
52
|
+
...result,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// If this detector found a match with high confidence, use it
|
|
56
|
+
if (result.detected && result.confidence > 0.8) {
|
|
57
|
+
if (result.confidence > results.confidence) {
|
|
58
|
+
results.detected = detector.name;
|
|
59
|
+
results.confidence = result.confidence;
|
|
60
|
+
results.metadata = result.metadata;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(`Error in detector ${detector.name}:`, error);
|
|
65
|
+
// Continue with other detectors
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get detector by name
|
|
74
|
+
*
|
|
75
|
+
* @param {string} name - Detector name
|
|
76
|
+
* @returns {object|null} Detector instance or null
|
|
77
|
+
*/
|
|
78
|
+
function getDetector(name) {
|
|
79
|
+
return detectors.find(d => d.name === name) || null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get all registered detectors
|
|
84
|
+
*
|
|
85
|
+
* @returns {array} Array of detector instances
|
|
86
|
+
*/
|
|
87
|
+
function getAllDetectors() {
|
|
88
|
+
return sortedDetectors;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
detectProjectType,
|
|
93
|
+
getDetector,
|
|
94
|
+
getAllDetectors,
|
|
95
|
+
detectors: sortedDetectors, // Expose sorted list
|
|
96
|
+
};
|
|
97
|
+
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client Generator
|
|
3
|
+
* Generates API client code for the project
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
class ApiClientGenerator {
|
|
10
|
+
/**
|
|
11
|
+
* Generate API client file
|
|
12
|
+
*/
|
|
13
|
+
generate(options) {
|
|
14
|
+
const {
|
|
15
|
+
projectPath,
|
|
16
|
+
apiKey,
|
|
17
|
+
backendUrl,
|
|
18
|
+
organizationId,
|
|
19
|
+
isTypeScript,
|
|
20
|
+
} = options;
|
|
21
|
+
|
|
22
|
+
// Determine output path based on project structure
|
|
23
|
+
const libDir = fs.existsSync(path.join(projectPath, 'src'))
|
|
24
|
+
? path.join(projectPath, 'src', 'lib')
|
|
25
|
+
: path.join(projectPath, 'lib');
|
|
26
|
+
|
|
27
|
+
// Ensure lib directory exists
|
|
28
|
+
if (!fs.existsSync(libDir)) {
|
|
29
|
+
fs.mkdirSync(libDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const extension = isTypeScript ? 'ts' : 'js';
|
|
33
|
+
const outputPath = path.join(libDir, `api-client.${extension}`);
|
|
34
|
+
|
|
35
|
+
// Generate API client code
|
|
36
|
+
const code = this.generateCode({
|
|
37
|
+
apiKey,
|
|
38
|
+
backendUrl,
|
|
39
|
+
organizationId,
|
|
40
|
+
isTypeScript,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
fs.writeFileSync(outputPath, code, 'utf8');
|
|
44
|
+
return outputPath;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate API client code
|
|
49
|
+
*/
|
|
50
|
+
generateCode({ apiKey, backendUrl, organizationId, isTypeScript }) {
|
|
51
|
+
const returnType = isTypeScript ? ': Promise<any>' : '';
|
|
52
|
+
|
|
53
|
+
return `/**
|
|
54
|
+
* L4YERCAK3 API Client
|
|
55
|
+
* Auto-generated by @l4yercak3/cli
|
|
56
|
+
*
|
|
57
|
+
* This client handles authenticated requests to the L4YERCAK3 backend API.
|
|
58
|
+
* Organization ID: ${organizationId}
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
// Using native fetch (available in Next.js 13+)
|
|
62
|
+
|
|
63
|
+
class L4YERCAK3Client {
|
|
64
|
+
constructor(apiKey${isTypeScript ? ': string' : ''} = '${apiKey}', baseUrl${isTypeScript ? ': string' : ''} = '${backendUrl}') {
|
|
65
|
+
this.apiKey = apiKey;
|
|
66
|
+
this.baseUrl = baseUrl;
|
|
67
|
+
this.organizationId = '${organizationId}';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Make authenticated API request
|
|
72
|
+
*/
|
|
73
|
+
async request(endpoint${isTypeScript ? ': string' : ''}, options${isTypeScript ? ': RequestInit' : ''} = {})${returnType} {
|
|
74
|
+
const url = \`\${this.baseUrl}\${endpoint}\`;
|
|
75
|
+
const headers = {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
'Authorization': \`Bearer \${this.apiKey}\`,
|
|
78
|
+
'X-Organization-Id': this.organizationId,
|
|
79
|
+
...options.headers,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const response = await fetch(url, {
|
|
83
|
+
...options,
|
|
84
|
+
headers,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const error = await response.json().catch(() => ({ message: 'Request failed' }));
|
|
89
|
+
throw new Error(error.message || \`API request failed: \${response.status}\`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return response.json();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* CRM Methods
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
async getContacts()${returnType} {
|
|
100
|
+
return this.request('/api/v1/crm/contacts');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getContact(contactId${isTypeScript ? ': string' : ''})${returnType} {
|
|
104
|
+
return this.request(\`/api/v1/crm/contacts/\${contactId}\`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async createContact(data${isTypeScript ? ': any' : ''})${returnType} {
|
|
108
|
+
return this.request('/api/v1/crm/contacts', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
body: JSON.stringify(data),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async updateContact(contactId${isTypeScript ? ': string' : ''}, data${isTypeScript ? ': any' : ''})${returnType} {
|
|
115
|
+
return this.request(\`/api/v1/crm/contacts/\${contactId}\`, {
|
|
116
|
+
method: 'PATCH',
|
|
117
|
+
body: JSON.stringify(data),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async deleteContact(contactId${isTypeScript ? ': string' : ''})${returnType} {
|
|
122
|
+
return this.request(\`/api/v1/crm/contacts/\${contactId}\`, {
|
|
123
|
+
method: 'DELETE',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Projects Methods
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
async getProjects()${returnType} {
|
|
132
|
+
return this.request('/api/v1/projects');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async getProject(projectId${isTypeScript ? ': string' : ''})${returnType} {
|
|
136
|
+
return this.request(\`/api/v1/projects/\${projectId}\`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async createProject(data${isTypeScript ? ': any' : ''})${returnType} {
|
|
140
|
+
return this.request('/api/v1/projects', {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
body: JSON.stringify(data),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async updateProject(projectId${isTypeScript ? ': string' : ''}, data${isTypeScript ? ': any' : ''})${returnType} {
|
|
147
|
+
return this.request(\`/api/v1/projects/\${projectId}\`, {
|
|
148
|
+
method: 'PATCH',
|
|
149
|
+
body: JSON.stringify(data),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async deleteProject(projectId${isTypeScript ? ': string' : ''})${returnType} {
|
|
154
|
+
return this.request(\`/api/v1/projects/\${projectId}\`, {
|
|
155
|
+
method: 'DELETE',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Invoices Methods
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
async getInvoices()${returnType} {
|
|
164
|
+
return this.request('/api/v1/invoices');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getInvoice(invoiceId${isTypeScript ? ': string' : ''})${returnType} {
|
|
168
|
+
return this.request(\`/api/v1/invoices/\${invoiceId}\`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async createInvoice(data${isTypeScript ? ': any' : ''})${returnType} {
|
|
172
|
+
return this.request('/api/v1/invoices', {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
body: JSON.stringify(data),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async updateInvoice(invoiceId${isTypeScript ? ': string' : ''}, data${isTypeScript ? ': any' : ''})${returnType} {
|
|
179
|
+
return this.request(\`/api/v1/invoices/\${invoiceId}\`, {
|
|
180
|
+
method: 'PATCH',
|
|
181
|
+
body: JSON.stringify(data),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async deleteInvoice(invoiceId${isTypeScript ? ': string' : ''})${returnType} {
|
|
186
|
+
return this.request(\`/api/v1/invoices/\${invoiceId}\`, {
|
|
187
|
+
method: 'DELETE',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
${isTypeScript ? 'export default L4YERCAK3Client;' : 'module.exports = L4YERCAK3Client;'}
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = new ApiClientGenerator();
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment File Generator
|
|
3
|
+
* Generates .env.local file with configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
class EnvGenerator {
|
|
10
|
+
/**
|
|
11
|
+
* Generate or update .env.local file
|
|
12
|
+
*/
|
|
13
|
+
generate(options) {
|
|
14
|
+
const {
|
|
15
|
+
projectPath,
|
|
16
|
+
apiKey,
|
|
17
|
+
backendUrl,
|
|
18
|
+
organizationId,
|
|
19
|
+
features,
|
|
20
|
+
oauthProviders,
|
|
21
|
+
} = options;
|
|
22
|
+
|
|
23
|
+
const envPath = path.join(projectPath, '.env.local');
|
|
24
|
+
const existingEnv = this.readExistingEnv(envPath);
|
|
25
|
+
|
|
26
|
+
// Merge with new values
|
|
27
|
+
const envVars = {
|
|
28
|
+
...existingEnv,
|
|
29
|
+
// Core API configuration
|
|
30
|
+
L4YERCAK3_API_KEY: apiKey,
|
|
31
|
+
L4YERCAK3_BACKEND_URL: backendUrl,
|
|
32
|
+
L4YERCAK3_ORGANIZATION_ID: organizationId,
|
|
33
|
+
NEXT_PUBLIC_L4YERCAK3_BACKEND_URL: backendUrl,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Add OAuth variables if OAuth is enabled
|
|
37
|
+
if (features.includes('oauth') && oauthProviders) {
|
|
38
|
+
if (oauthProviders.includes('google')) {
|
|
39
|
+
envVars.GOOGLE_CLIENT_ID = existingEnv.GOOGLE_CLIENT_ID || 'your_google_client_id_here';
|
|
40
|
+
envVars.GOOGLE_CLIENT_SECRET = existingEnv.GOOGLE_CLIENT_SECRET || 'your_google_client_secret_here';
|
|
41
|
+
}
|
|
42
|
+
if (oauthProviders.includes('microsoft')) {
|
|
43
|
+
envVars.AZURE_CLIENT_ID = existingEnv.AZURE_CLIENT_ID || 'your_azure_client_id_here';
|
|
44
|
+
envVars.AZURE_CLIENT_SECRET = existingEnv.AZURE_CLIENT_SECRET || 'your_azure_client_secret_here';
|
|
45
|
+
envVars.AZURE_TENANT_ID = existingEnv.AZURE_TENANT_ID || 'your_azure_tenant_id_here';
|
|
46
|
+
}
|
|
47
|
+
if (oauthProviders.includes('github')) {
|
|
48
|
+
envVars.GITHUB_CLIENT_ID = existingEnv.GITHUB_CLIENT_ID || 'your_github_client_id_here';
|
|
49
|
+
envVars.GITHUB_CLIENT_SECRET = existingEnv.GITHUB_CLIENT_SECRET || 'your_github_client_secret_here';
|
|
50
|
+
}
|
|
51
|
+
envVars.NEXTAUTH_URL = existingEnv.NEXTAUTH_URL || 'http://localhost:3000';
|
|
52
|
+
envVars.NEXTAUTH_SECRET = existingEnv.NEXTAUTH_SECRET || 'generate_with_openssl_rand_base64_32';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Add Stripe variables if Stripe is enabled
|
|
56
|
+
if (features.includes('stripe')) {
|
|
57
|
+
envVars.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = existingEnv.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || 'your_stripe_publishable_key_here';
|
|
58
|
+
envVars.STRIPE_SECRET_KEY = existingEnv.STRIPE_SECRET_KEY || 'your_stripe_secret_key_here';
|
|
59
|
+
envVars.STRIPE_WEBHOOK_SECRET = existingEnv.STRIPE_WEBHOOK_SECRET || 'your_stripe_webhook_secret_here';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Write env file
|
|
63
|
+
const envContent = this.formatEnvFile(envVars);
|
|
64
|
+
fs.writeFileSync(envPath, envContent, 'utf8');
|
|
65
|
+
|
|
66
|
+
return envPath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Read existing .env.local file
|
|
71
|
+
*/
|
|
72
|
+
readExistingEnv(envPath) {
|
|
73
|
+
const envVars = {};
|
|
74
|
+
|
|
75
|
+
if (!fs.existsSync(envPath)) {
|
|
76
|
+
return envVars;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
81
|
+
const lines = content.split('\n');
|
|
82
|
+
|
|
83
|
+
for (const line of lines) {
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
86
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
87
|
+
if (match) {
|
|
88
|
+
const key = match[1].trim();
|
|
89
|
+
const value = match[2].trim();
|
|
90
|
+
envVars[key] = value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// Error reading file, return empty object
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return envVars;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Format environment variables as .env file
|
|
103
|
+
*/
|
|
104
|
+
formatEnvFile(envVars) {
|
|
105
|
+
let content = `# L4YERCAK3 Configuration
|
|
106
|
+
# Auto-generated by @l4yercak3/cli
|
|
107
|
+
# DO NOT commit this file to git - it contains sensitive credentials
|
|
108
|
+
|
|
109
|
+
# Core API Configuration
|
|
110
|
+
L4YERCAK3_API_KEY=${envVars.L4YERCAK3_API_KEY}
|
|
111
|
+
L4YERCAK3_BACKEND_URL=${envVars.L4YERCAK3_BACKEND_URL}
|
|
112
|
+
L4YERCAK3_ORGANIZATION_ID=${envVars.L4YERCAK3_ORGANIZATION_ID}
|
|
113
|
+
NEXT_PUBLIC_L4YERCAK3_BACKEND_URL=${envVars.NEXT_PUBLIC_L4YERCAK3_BACKEND_URL}
|
|
114
|
+
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
// Add OAuth section if present
|
|
118
|
+
if (envVars.GOOGLE_CLIENT_ID || envVars.AZURE_CLIENT_ID || envVars.GITHUB_CLIENT_ID) {
|
|
119
|
+
content += `# OAuth Configuration
|
|
120
|
+
`;
|
|
121
|
+
if (envVars.GOOGLE_CLIENT_ID) {
|
|
122
|
+
content += `GOOGLE_CLIENT_ID=${envVars.GOOGLE_CLIENT_ID}
|
|
123
|
+
GOOGLE_CLIENT_SECRET=${envVars.GOOGLE_CLIENT_SECRET}
|
|
124
|
+
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
if (envVars.AZURE_CLIENT_ID) {
|
|
128
|
+
content += `AZURE_CLIENT_ID=${envVars.AZURE_CLIENT_ID}
|
|
129
|
+
AZURE_CLIENT_SECRET=${envVars.AZURE_CLIENT_SECRET}
|
|
130
|
+
AZURE_TENANT_ID=${envVars.AZURE_TENANT_ID}
|
|
131
|
+
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
if (envVars.GITHUB_CLIENT_ID) {
|
|
135
|
+
content += `GITHUB_CLIENT_ID=${envVars.GITHUB_CLIENT_ID}
|
|
136
|
+
GITHUB_CLIENT_SECRET=${envVars.GITHUB_CLIENT_SECRET}
|
|
137
|
+
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
if (envVars.NEXTAUTH_URL) {
|
|
141
|
+
content += `NEXTAUTH_URL=${envVars.NEXTAUTH_URL}
|
|
142
|
+
NEXTAUTH_SECRET=${envVars.NEXTAUTH_SECRET}
|
|
143
|
+
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Add Stripe section if present
|
|
149
|
+
if (envVars.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY) {
|
|
150
|
+
content += `# Stripe Configuration
|
|
151
|
+
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${envVars.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}
|
|
152
|
+
STRIPE_SECRET_KEY=${envVars.STRIPE_SECRET_KEY}
|
|
153
|
+
STRIPE_WEBHOOK_SECRET=${envVars.STRIPE_WEBHOOK_SECRET}
|
|
154
|
+
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return content;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = new EnvGenerator();
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gitignore Generator
|
|
3
|
+
* Updates .gitignore to ensure sensitive files are not committed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
class GitignoreGenerator {
|
|
10
|
+
/**
|
|
11
|
+
* Update .gitignore file
|
|
12
|
+
*/
|
|
13
|
+
generate(options) {
|
|
14
|
+
const { projectPath } = options;
|
|
15
|
+
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
16
|
+
|
|
17
|
+
// Required entries to ensure are present
|
|
18
|
+
const requiredEntries = [
|
|
19
|
+
'# L4YERCAK3 Configuration',
|
|
20
|
+
'.env.local',
|
|
21
|
+
'.env*.local',
|
|
22
|
+
'.l4yercak3/',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
let gitignoreContent = '';
|
|
26
|
+
let needsUpdate = false;
|
|
27
|
+
|
|
28
|
+
// Read existing .gitignore if it exists
|
|
29
|
+
if (fs.existsSync(gitignorePath)) {
|
|
30
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check if required entries are missing
|
|
34
|
+
for (const entry of requiredEntries) {
|
|
35
|
+
// Skip comment lines for checking
|
|
36
|
+
if (entry.startsWith('#')) {
|
|
37
|
+
if (!gitignoreContent.includes(entry)) {
|
|
38
|
+
needsUpdate = true;
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// Check for the actual entry (with or without leading/trailing whitespace)
|
|
42
|
+
const entryRegex = new RegExp(`^\\s*${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'm');
|
|
43
|
+
if (!entryRegex.test(gitignoreContent)) {
|
|
44
|
+
needsUpdate = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If no update needed, return null
|
|
50
|
+
if (!needsUpdate && gitignoreContent) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Append required entries if they're missing
|
|
55
|
+
if (needsUpdate) {
|
|
56
|
+
if (gitignoreContent && !gitignoreContent.endsWith('\n')) {
|
|
57
|
+
gitignoreContent += '\n';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add section header if not present
|
|
61
|
+
if (!gitignoreContent.includes('# L4YERCAK3 Configuration')) {
|
|
62
|
+
gitignoreContent += '\n# L4YERCAK3 Configuration\n';
|
|
63
|
+
gitignoreContent += '# Auto-generated by @l4yercak3/cli\n';
|
|
64
|
+
gitignoreContent += '# DO NOT commit sensitive configuration files\n\n';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add entries if missing
|
|
68
|
+
const entriesToAdd = [
|
|
69
|
+
'.env.local',
|
|
70
|
+
'.env*.local',
|
|
71
|
+
'.l4yercak3/',
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
for (const entry of entriesToAdd) {
|
|
75
|
+
const entryRegex = new RegExp(`^\\s*${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'm');
|
|
76
|
+
if (!entryRegex.test(gitignoreContent)) {
|
|
77
|
+
gitignoreContent += `${entry}\n`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Write updated .gitignore
|
|
83
|
+
fs.writeFileSync(gitignorePath, gitignoreContent, 'utf8');
|
|
84
|
+
return gitignorePath;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = new GitignoreGenerator();
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|