@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.
Files changed (61) hide show
  1. package/.claude/settings.local.json +18 -0
  2. package/.cursor/rules.md +203 -0
  3. package/.eslintrc.js +31 -0
  4. package/README.md +227 -0
  5. package/bin/cli.js +61 -0
  6. package/docs/ADDING_NEW_PROJECT_TYPE.md +156 -0
  7. package/docs/ARCHITECTURE_RELATIONSHIPS.md +411 -0
  8. package/docs/CLI_AUTHENTICATION.md +214 -0
  9. package/docs/DETECTOR_ARCHITECTURE.md +326 -0
  10. package/docs/DEVELOPMENT.md +194 -0
  11. package/docs/IMPLEMENTATION_PHASES.md +468 -0
  12. package/docs/OAUTH_CLARIFICATION.md +258 -0
  13. package/docs/OAUTH_SETUP_GUIDE_TEMPLATE.md +211 -0
  14. package/docs/PHASE_0_PROGRESS.md +120 -0
  15. package/docs/PHASE_1_COMPLETE.md +366 -0
  16. package/docs/PHASE_SUMMARY.md +149 -0
  17. package/docs/PLAN.md +511 -0
  18. package/docs/README.md +56 -0
  19. package/docs/STRIPE_INTEGRATION.md +447 -0
  20. package/docs/SUMMARY.md +230 -0
  21. package/docs/UPDATED_PLAN.md +447 -0
  22. package/package.json +53 -0
  23. package/src/api/backend-client.js +148 -0
  24. package/src/commands/login.js +146 -0
  25. package/src/commands/logout.js +24 -0
  26. package/src/commands/spread.js +364 -0
  27. package/src/commands/status.js +62 -0
  28. package/src/config/config-manager.js +205 -0
  29. package/src/detectors/api-client-detector.js +85 -0
  30. package/src/detectors/base-detector.js +77 -0
  31. package/src/detectors/github-detector.js +74 -0
  32. package/src/detectors/index.js +80 -0
  33. package/src/detectors/nextjs-detector.js +139 -0
  34. package/src/detectors/oauth-detector.js +122 -0
  35. package/src/detectors/registry.js +97 -0
  36. package/src/generators/api-client-generator.js +197 -0
  37. package/src/generators/env-generator.js +162 -0
  38. package/src/generators/gitignore-generator.js +92 -0
  39. package/src/generators/index.js +50 -0
  40. package/src/generators/nextauth-generator.js +242 -0
  41. package/src/generators/oauth-guide-generator.js +277 -0
  42. package/src/logo.js +116 -0
  43. package/tests/api-client-detector.test.js +214 -0
  44. package/tests/api-client-generator.test.js +169 -0
  45. package/tests/backend-client.test.js +361 -0
  46. package/tests/base-detector.test.js +101 -0
  47. package/tests/commands/login.test.js +98 -0
  48. package/tests/commands/logout.test.js +70 -0
  49. package/tests/commands/status.test.js +167 -0
  50. package/tests/config-manager.test.js +313 -0
  51. package/tests/detector-index.test.js +209 -0
  52. package/tests/detector-registry.test.js +93 -0
  53. package/tests/env-generator.test.js +278 -0
  54. package/tests/generators-index.test.js +215 -0
  55. package/tests/github-detector.test.js +145 -0
  56. package/tests/gitignore-generator.test.js +109 -0
  57. package/tests/logo.test.js +96 -0
  58. package/tests/nextauth-generator.test.js +231 -0
  59. package/tests/nextjs-detector.test.js +235 -0
  60. package/tests/oauth-detector.test.js +264 -0
  61. 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
+