@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,93 @@
1
+ /**
2
+ * Tests for Detector Registry
3
+ */
4
+
5
+ const registry = require('../src/detectors/registry');
6
+
7
+ describe('Detector Registry', () => {
8
+ describe('detectProjectType', () => {
9
+ it('returns results object with expected structure', () => {
10
+ const result = registry.detectProjectType('/nonexistent/path');
11
+
12
+ expect(result).toHaveProperty('detected');
13
+ expect(result).toHaveProperty('confidence');
14
+ expect(result).toHaveProperty('metadata');
15
+ expect(result).toHaveProperty('allResults');
16
+ expect(Array.isArray(result.allResults)).toBe(true);
17
+ });
18
+
19
+ it('returns detected: null when no framework matches', () => {
20
+ const result = registry.detectProjectType('/nonexistent/path');
21
+
22
+ expect(result.detected).toBeNull();
23
+ expect(result.confidence).toBe(0);
24
+ });
25
+
26
+ it('includes all detector results in allResults', () => {
27
+ const result = registry.detectProjectType('/nonexistent/path');
28
+
29
+ expect(result.allResults.length).toBeGreaterThan(0);
30
+ expect(result.allResults[0]).toHaveProperty('detector');
31
+ expect(result.allResults[0]).toHaveProperty('priority');
32
+ });
33
+ });
34
+
35
+ describe('getDetector', () => {
36
+ it('returns nextjs detector by name', () => {
37
+ const detector = registry.getDetector('nextjs');
38
+
39
+ expect(detector).not.toBeNull();
40
+ expect(detector.name).toBe('nextjs');
41
+ });
42
+
43
+ it('returns null for unknown detector name', () => {
44
+ const detector = registry.getDetector('unknown-detector');
45
+
46
+ expect(detector).toBeNull();
47
+ });
48
+
49
+ it('returns null for empty name', () => {
50
+ const detector = registry.getDetector('');
51
+
52
+ expect(detector).toBeNull();
53
+ });
54
+ });
55
+
56
+ describe('getAllDetectors', () => {
57
+ it('returns array of detectors', () => {
58
+ const detectors = registry.getAllDetectors();
59
+
60
+ expect(Array.isArray(detectors)).toBe(true);
61
+ expect(detectors.length).toBeGreaterThan(0);
62
+ });
63
+
64
+ it('returns detectors sorted by priority (highest first)', () => {
65
+ const detectors = registry.getAllDetectors();
66
+
67
+ for (let i = 1; i < detectors.length; i++) {
68
+ expect(detectors[i - 1].priority).toBeGreaterThanOrEqual(detectors[i].priority);
69
+ }
70
+ });
71
+
72
+ it('each detector has required properties', () => {
73
+ const detectors = registry.getAllDetectors();
74
+
75
+ for (const detector of detectors) {
76
+ expect(detector).toHaveProperty('name');
77
+ expect(detector).toHaveProperty('priority');
78
+ expect(typeof detector.detect).toBe('function');
79
+ }
80
+ });
81
+ });
82
+
83
+ describe('detectors export', () => {
84
+ it('exports sorted detectors array', () => {
85
+ expect(Array.isArray(registry.detectors)).toBe(true);
86
+ expect(registry.detectors.length).toBeGreaterThan(0);
87
+ });
88
+
89
+ it('exported detectors match getAllDetectors', () => {
90
+ expect(registry.detectors).toEqual(registry.getAllDetectors());
91
+ });
92
+ });
93
+ });
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Tests for Environment Generator
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ jest.mock('fs');
9
+
10
+ const EnvGenerator = require('../src/generators/env-generator');
11
+
12
+ describe('EnvGenerator', () => {
13
+ const mockProjectPath = '/test/project';
14
+ const mockEnvPath = path.join(mockProjectPath, '.env.local');
15
+
16
+ beforeEach(() => {
17
+ jest.clearAllMocks();
18
+ fs.existsSync.mockReturnValue(false);
19
+ fs.writeFileSync.mockReturnValue(undefined);
20
+ });
21
+
22
+ describe('generate', () => {
23
+ it('creates basic env file with core config', () => {
24
+ const options = {
25
+ projectPath: mockProjectPath,
26
+ apiKey: 'test-api-key',
27
+ backendUrl: 'https://backend.test.com',
28
+ organizationId: 'org-123',
29
+ features: [],
30
+ oauthProviders: [],
31
+ };
32
+
33
+ const result = EnvGenerator.generate(options);
34
+
35
+ expect(result).toBe(mockEnvPath);
36
+ expect(fs.writeFileSync).toHaveBeenCalled();
37
+
38
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
39
+ expect(writtenContent).toContain('L4YERCAK3_API_KEY=test-api-key');
40
+ expect(writtenContent).toContain('L4YERCAK3_BACKEND_URL=https://backend.test.com');
41
+ expect(writtenContent).toContain('L4YERCAK3_ORGANIZATION_ID=org-123');
42
+ expect(writtenContent).toContain('NEXT_PUBLIC_L4YERCAK3_BACKEND_URL=https://backend.test.com');
43
+ });
44
+
45
+ it('adds Google OAuth variables when enabled', () => {
46
+ const options = {
47
+ projectPath: mockProjectPath,
48
+ apiKey: 'test-api-key',
49
+ backendUrl: 'https://backend.test.com',
50
+ organizationId: 'org-123',
51
+ features: ['oauth'],
52
+ oauthProviders: ['google'],
53
+ };
54
+
55
+ EnvGenerator.generate(options);
56
+
57
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
58
+ expect(writtenContent).toContain('GOOGLE_CLIENT_ID=');
59
+ expect(writtenContent).toContain('GOOGLE_CLIENT_SECRET=');
60
+ expect(writtenContent).toContain('NEXTAUTH_URL=');
61
+ expect(writtenContent).toContain('NEXTAUTH_SECRET=');
62
+ });
63
+
64
+ it('adds Microsoft OAuth variables when enabled', () => {
65
+ const options = {
66
+ projectPath: mockProjectPath,
67
+ apiKey: 'test-api-key',
68
+ backendUrl: 'https://backend.test.com',
69
+ organizationId: 'org-123',
70
+ features: ['oauth'],
71
+ oauthProviders: ['microsoft'],
72
+ };
73
+
74
+ EnvGenerator.generate(options);
75
+
76
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
77
+ expect(writtenContent).toContain('AZURE_CLIENT_ID=');
78
+ expect(writtenContent).toContain('AZURE_CLIENT_SECRET=');
79
+ expect(writtenContent).toContain('AZURE_TENANT_ID=');
80
+ });
81
+
82
+ it('adds GitHub OAuth variables when enabled', () => {
83
+ const options = {
84
+ projectPath: mockProjectPath,
85
+ apiKey: 'test-api-key',
86
+ backendUrl: 'https://backend.test.com',
87
+ organizationId: 'org-123',
88
+ features: ['oauth'],
89
+ oauthProviders: ['github'],
90
+ };
91
+
92
+ EnvGenerator.generate(options);
93
+
94
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
95
+ expect(writtenContent).toContain('GITHUB_CLIENT_ID=');
96
+ expect(writtenContent).toContain('GITHUB_CLIENT_SECRET=');
97
+ });
98
+
99
+ it('adds Stripe variables when enabled', () => {
100
+ const options = {
101
+ projectPath: mockProjectPath,
102
+ apiKey: 'test-api-key',
103
+ backendUrl: 'https://backend.test.com',
104
+ organizationId: 'org-123',
105
+ features: ['stripe'],
106
+ oauthProviders: [],
107
+ };
108
+
109
+ EnvGenerator.generate(options);
110
+
111
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
112
+ expect(writtenContent).toContain('NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=');
113
+ expect(writtenContent).toContain('STRIPE_SECRET_KEY=');
114
+ expect(writtenContent).toContain('STRIPE_WEBHOOK_SECRET=');
115
+ });
116
+
117
+ it('preserves existing env values', () => {
118
+ fs.existsSync.mockReturnValue(true);
119
+ fs.readFileSync.mockReturnValue(`
120
+ GOOGLE_CLIENT_ID=existing-google-id
121
+ GOOGLE_CLIENT_SECRET=existing-google-secret
122
+ CUSTOM_VAR=custom-value
123
+ `);
124
+
125
+ const options = {
126
+ projectPath: mockProjectPath,
127
+ apiKey: 'new-api-key',
128
+ backendUrl: 'https://backend.test.com',
129
+ organizationId: 'org-123',
130
+ features: ['oauth'],
131
+ oauthProviders: ['google'],
132
+ };
133
+
134
+ EnvGenerator.generate(options);
135
+
136
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
137
+ expect(writtenContent).toContain('GOOGLE_CLIENT_ID=existing-google-id');
138
+ expect(writtenContent).toContain('GOOGLE_CLIENT_SECRET=existing-google-secret');
139
+ expect(writtenContent).toContain('L4YERCAK3_API_KEY=new-api-key');
140
+ });
141
+
142
+ it('adds all OAuth providers when multiple selected', () => {
143
+ const options = {
144
+ projectPath: mockProjectPath,
145
+ apiKey: 'test-api-key',
146
+ backendUrl: 'https://backend.test.com',
147
+ organizationId: 'org-123',
148
+ features: ['oauth'],
149
+ oauthProviders: ['google', 'microsoft', 'github'],
150
+ };
151
+
152
+ EnvGenerator.generate(options);
153
+
154
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
155
+ expect(writtenContent).toContain('GOOGLE_CLIENT_ID=');
156
+ expect(writtenContent).toContain('AZURE_CLIENT_ID=');
157
+ expect(writtenContent).toContain('GITHUB_CLIENT_ID=');
158
+ });
159
+ });
160
+
161
+ describe('readExistingEnv', () => {
162
+ it('returns empty object when file does not exist', () => {
163
+ fs.existsSync.mockReturnValue(false);
164
+
165
+ const result = EnvGenerator.readExistingEnv(mockEnvPath);
166
+
167
+ expect(result).toEqual({});
168
+ });
169
+
170
+ it('parses simple key=value pairs', () => {
171
+ fs.existsSync.mockReturnValue(true);
172
+ fs.readFileSync.mockReturnValue(`
173
+ API_KEY=test123
174
+ SECRET=mysecret
175
+ `);
176
+
177
+ const result = EnvGenerator.readExistingEnv(mockEnvPath);
178
+
179
+ expect(result).toEqual({
180
+ API_KEY: 'test123',
181
+ SECRET: 'mysecret',
182
+ });
183
+ });
184
+
185
+ it('ignores comments', () => {
186
+ fs.existsSync.mockReturnValue(true);
187
+ fs.readFileSync.mockReturnValue(`
188
+ # This is a comment
189
+ API_KEY=test123
190
+ # Another comment
191
+ `);
192
+
193
+ const result = EnvGenerator.readExistingEnv(mockEnvPath);
194
+
195
+ expect(result).toEqual({
196
+ API_KEY: 'test123',
197
+ });
198
+ });
199
+
200
+ it('handles empty lines', () => {
201
+ fs.existsSync.mockReturnValue(true);
202
+ fs.readFileSync.mockReturnValue(`
203
+ API_KEY=test123
204
+
205
+ SECRET=mysecret
206
+
207
+ `);
208
+
209
+ const result = EnvGenerator.readExistingEnv(mockEnvPath);
210
+
211
+ expect(result).toEqual({
212
+ API_KEY: 'test123',
213
+ SECRET: 'mysecret',
214
+ });
215
+ });
216
+
217
+ it('handles read errors gracefully', () => {
218
+ fs.existsSync.mockReturnValue(true);
219
+ fs.readFileSync.mockImplementation(() => {
220
+ throw new Error('Read failed');
221
+ });
222
+
223
+ const result = EnvGenerator.readExistingEnv(mockEnvPath);
224
+
225
+ expect(result).toEqual({});
226
+ });
227
+ });
228
+
229
+ describe('formatEnvFile', () => {
230
+ it('includes header comment', () => {
231
+ const envVars = {
232
+ L4YERCAK3_API_KEY: 'key',
233
+ L4YERCAK3_BACKEND_URL: 'url',
234
+ L4YERCAK3_ORGANIZATION_ID: 'org',
235
+ NEXT_PUBLIC_L4YERCAK3_BACKEND_URL: 'url',
236
+ };
237
+
238
+ const result = EnvGenerator.formatEnvFile(envVars);
239
+
240
+ expect(result).toContain('# L4YERCAK3 Configuration');
241
+ expect(result).toContain('# Auto-generated by @l4yercak3/cli');
242
+ expect(result).toContain('# DO NOT commit this file to git');
243
+ });
244
+
245
+ it('groups OAuth variables under OAuth section', () => {
246
+ const envVars = {
247
+ L4YERCAK3_API_KEY: 'key',
248
+ L4YERCAK3_BACKEND_URL: 'url',
249
+ L4YERCAK3_ORGANIZATION_ID: 'org',
250
+ NEXT_PUBLIC_L4YERCAK3_BACKEND_URL: 'url',
251
+ GOOGLE_CLIENT_ID: 'google-id',
252
+ GOOGLE_CLIENT_SECRET: 'google-secret',
253
+ NEXTAUTH_URL: 'http://localhost:3000',
254
+ NEXTAUTH_SECRET: 'secret',
255
+ };
256
+
257
+ const result = EnvGenerator.formatEnvFile(envVars);
258
+
259
+ expect(result).toContain('# OAuth Configuration');
260
+ });
261
+
262
+ it('groups Stripe variables under Stripe section', () => {
263
+ const envVars = {
264
+ L4YERCAK3_API_KEY: 'key',
265
+ L4YERCAK3_BACKEND_URL: 'url',
266
+ L4YERCAK3_ORGANIZATION_ID: 'org',
267
+ NEXT_PUBLIC_L4YERCAK3_BACKEND_URL: 'url',
268
+ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: 'pk_test',
269
+ STRIPE_SECRET_KEY: 'sk_test',
270
+ STRIPE_WEBHOOK_SECRET: 'whsec_test',
271
+ };
272
+
273
+ const result = EnvGenerator.formatEnvFile(envVars);
274
+
275
+ expect(result).toContain('# Stripe Configuration');
276
+ });
277
+ });
278
+ });
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Tests for Generators Index (FileGenerator)
3
+ */
4
+
5
+ const fs = require('fs');
6
+
7
+ jest.mock('fs');
8
+
9
+ const FileGenerator = require('../src/generators/index');
10
+
11
+ describe('FileGenerator', () => {
12
+ const mockProjectPath = '/test/project';
13
+
14
+ beforeEach(() => {
15
+ jest.clearAllMocks();
16
+ fs.existsSync.mockReturnValue(false);
17
+ fs.mkdirSync.mockReturnValue(undefined);
18
+ fs.writeFileSync.mockReturnValue(undefined);
19
+ fs.readFileSync.mockReturnValue('');
20
+ });
21
+
22
+ describe('generate', () => {
23
+ it('returns results object with expected structure', async () => {
24
+ const options = {
25
+ projectPath: mockProjectPath,
26
+ apiKey: 'test-key',
27
+ backendUrl: 'https://backend.test.com',
28
+ organizationId: 'org-123',
29
+ features: [],
30
+ oauthProviders: [],
31
+ isTypeScript: false,
32
+ routerType: 'app',
33
+ };
34
+
35
+ const result = await FileGenerator.generate(options);
36
+
37
+ expect(result).toHaveProperty('apiClient');
38
+ expect(result).toHaveProperty('envFile');
39
+ expect(result).toHaveProperty('nextauth');
40
+ expect(result).toHaveProperty('oauthGuide');
41
+ expect(result).toHaveProperty('gitignore');
42
+ });
43
+
44
+ it('generates API client when features are provided', async () => {
45
+ const options = {
46
+ projectPath: mockProjectPath,
47
+ apiKey: 'test-key',
48
+ backendUrl: 'https://backend.test.com',
49
+ organizationId: 'org-123',
50
+ features: ['crm'],
51
+ oauthProviders: [],
52
+ isTypeScript: false,
53
+ };
54
+
55
+ const result = await FileGenerator.generate(options);
56
+
57
+ expect(result.apiClient).not.toBeNull();
58
+ expect(result.apiClient).toContain('api-client');
59
+ });
60
+
61
+ it('does not generate API client when no features', async () => {
62
+ const options = {
63
+ projectPath: mockProjectPath,
64
+ apiKey: 'test-key',
65
+ backendUrl: 'https://backend.test.com',
66
+ organizationId: 'org-123',
67
+ features: [],
68
+ oauthProviders: [],
69
+ isTypeScript: false,
70
+ };
71
+
72
+ const result = await FileGenerator.generate(options);
73
+
74
+ expect(result.apiClient).toBeNull();
75
+ });
76
+
77
+ it('always generates env file', async () => {
78
+ const options = {
79
+ projectPath: mockProjectPath,
80
+ apiKey: 'test-key',
81
+ backendUrl: 'https://backend.test.com',
82
+ organizationId: 'org-123',
83
+ features: [],
84
+ oauthProviders: [],
85
+ };
86
+
87
+ const result = await FileGenerator.generate(options);
88
+
89
+ expect(result.envFile).not.toBeNull();
90
+ expect(result.envFile).toContain('.env.local');
91
+ });
92
+
93
+ it('generates NextAuth config when oauth feature enabled', async () => {
94
+ const options = {
95
+ projectPath: mockProjectPath,
96
+ apiKey: 'test-key',
97
+ backendUrl: 'https://backend.test.com',
98
+ organizationId: 'org-123',
99
+ features: ['oauth'],
100
+ oauthProviders: ['google'],
101
+ isTypeScript: false,
102
+ routerType: 'app',
103
+ };
104
+
105
+ const result = await FileGenerator.generate(options);
106
+
107
+ expect(result.nextauth).not.toBeNull();
108
+ });
109
+
110
+ it('does not generate NextAuth when oauth not in features', async () => {
111
+ const options = {
112
+ projectPath: mockProjectPath,
113
+ apiKey: 'test-key',
114
+ backendUrl: 'https://backend.test.com',
115
+ organizationId: 'org-123',
116
+ features: ['crm'],
117
+ oauthProviders: ['google'],
118
+ isTypeScript: false,
119
+ routerType: 'app',
120
+ };
121
+
122
+ const result = await FileGenerator.generate(options);
123
+
124
+ expect(result.nextauth).toBeNull();
125
+ });
126
+
127
+ it('does not generate NextAuth when no oauthProviders', async () => {
128
+ const options = {
129
+ projectPath: mockProjectPath,
130
+ apiKey: 'test-key',
131
+ backendUrl: 'https://backend.test.com',
132
+ organizationId: 'org-123',
133
+ features: ['oauth'],
134
+ oauthProviders: null,
135
+ isTypeScript: false,
136
+ routerType: 'app',
137
+ };
138
+
139
+ const result = await FileGenerator.generate(options);
140
+
141
+ expect(result.nextauth).toBeNull();
142
+ });
143
+
144
+ it('generates OAuth guide when oauth feature enabled', async () => {
145
+ const options = {
146
+ projectPath: mockProjectPath,
147
+ apiKey: 'test-key',
148
+ backendUrl: 'https://backend.test.com',
149
+ organizationId: 'org-123',
150
+ features: ['oauth'],
151
+ oauthProviders: ['google'],
152
+ productionDomain: 'example.com',
153
+ appName: 'Test App',
154
+ };
155
+
156
+ const result = await FileGenerator.generate(options);
157
+
158
+ expect(result.oauthGuide).not.toBeNull();
159
+ expect(result.oauthGuide).toContain('OAUTH_SETUP_GUIDE.md');
160
+ });
161
+
162
+ it('does not generate OAuth guide when oauth not enabled', async () => {
163
+ const options = {
164
+ projectPath: mockProjectPath,
165
+ apiKey: 'test-key',
166
+ backendUrl: 'https://backend.test.com',
167
+ organizationId: 'org-123',
168
+ features: ['crm'],
169
+ oauthProviders: [],
170
+ };
171
+
172
+ const result = await FileGenerator.generate(options);
173
+
174
+ expect(result.oauthGuide).toBeNull();
175
+ });
176
+
177
+ it('always attempts to update gitignore', async () => {
178
+ const options = {
179
+ projectPath: mockProjectPath,
180
+ apiKey: 'test-key',
181
+ backendUrl: 'https://backend.test.com',
182
+ organizationId: 'org-123',
183
+ features: [],
184
+ oauthProviders: [],
185
+ };
186
+
187
+ const result = await FileGenerator.generate(options);
188
+
189
+ // gitignore generator returns path or null depending on if update needed
190
+ expect(result).toHaveProperty('gitignore');
191
+ });
192
+
193
+ it('generates all files when all features enabled', async () => {
194
+ const options = {
195
+ projectPath: mockProjectPath,
196
+ apiKey: 'test-key',
197
+ backendUrl: 'https://backend.test.com',
198
+ organizationId: 'org-123',
199
+ features: ['crm', 'oauth', 'stripe'],
200
+ oauthProviders: ['google', 'github'],
201
+ isTypeScript: true,
202
+ routerType: 'app',
203
+ productionDomain: 'example.com',
204
+ appName: 'Full App',
205
+ };
206
+
207
+ const result = await FileGenerator.generate(options);
208
+
209
+ expect(result.apiClient).not.toBeNull();
210
+ expect(result.envFile).not.toBeNull();
211
+ expect(result.nextauth).not.toBeNull();
212
+ expect(result.oauthGuide).not.toBeNull();
213
+ });
214
+ });
215
+ });