@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,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Next.js Detector
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
jest.mock('fs');
|
|
9
|
+
|
|
10
|
+
const NextJsDetector = require('../src/detectors/nextjs-detector');
|
|
11
|
+
|
|
12
|
+
describe('NextJsDetector', () => {
|
|
13
|
+
const mockProjectPath = '/test/project';
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('properties', () => {
|
|
20
|
+
it('has correct name', () => {
|
|
21
|
+
expect(NextJsDetector.name).toBe('nextjs');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('has high priority', () => {
|
|
25
|
+
expect(NextJsDetector.priority).toBe(100);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('detect', () => {
|
|
30
|
+
it('returns not detected when no package.json exists', () => {
|
|
31
|
+
fs.existsSync.mockReturnValue(false);
|
|
32
|
+
|
|
33
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
34
|
+
|
|
35
|
+
// When package.json doesn't exist, returns raw results object
|
|
36
|
+
expect(result.isNextJs).toBe(false);
|
|
37
|
+
expect(result.version).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('returns not detected when Next.js is not in dependencies', () => {
|
|
41
|
+
fs.existsSync.mockReturnValue(true);
|
|
42
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
43
|
+
dependencies: {
|
|
44
|
+
react: '^18.0.0',
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
49
|
+
|
|
50
|
+
expect(result).toEqual({
|
|
51
|
+
detected: false,
|
|
52
|
+
confidence: 0,
|
|
53
|
+
metadata: {},
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('detects Next.js project with version', () => {
|
|
58
|
+
fs.existsSync.mockImplementation((p) => {
|
|
59
|
+
if (p === path.join(mockProjectPath, 'package.json')) return true;
|
|
60
|
+
return false;
|
|
61
|
+
});
|
|
62
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
63
|
+
dependencies: {
|
|
64
|
+
next: '^14.0.0',
|
|
65
|
+
react: '^18.0.0',
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
70
|
+
|
|
71
|
+
expect(result.detected).toBe(true);
|
|
72
|
+
expect(result.confidence).toBe(0.95);
|
|
73
|
+
expect(result.metadata.version).toBe('^14.0.0');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('detects TypeScript from dependencies', () => {
|
|
77
|
+
fs.existsSync.mockImplementation((p) => {
|
|
78
|
+
if (p === path.join(mockProjectPath, 'package.json')) return true;
|
|
79
|
+
return false;
|
|
80
|
+
});
|
|
81
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
82
|
+
dependencies: {
|
|
83
|
+
next: '^14.0.0',
|
|
84
|
+
},
|
|
85
|
+
devDependencies: {
|
|
86
|
+
typescript: '^5.0.0',
|
|
87
|
+
},
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
91
|
+
|
|
92
|
+
expect(result.metadata.hasTypeScript).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('detects TypeScript from tsconfig.json', () => {
|
|
96
|
+
fs.existsSync.mockImplementation((p) => {
|
|
97
|
+
if (p === path.join(mockProjectPath, 'package.json')) return true;
|
|
98
|
+
if (p === path.join(mockProjectPath, 'tsconfig.json')) return true;
|
|
99
|
+
return false;
|
|
100
|
+
});
|
|
101
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
102
|
+
dependencies: {
|
|
103
|
+
next: '^14.0.0',
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
108
|
+
|
|
109
|
+
expect(result.metadata.hasTypeScript).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('detects App Router from app directory', () => {
|
|
113
|
+
fs.existsSync.mockImplementation((p) => {
|
|
114
|
+
if (p === path.join(mockProjectPath, 'package.json')) return true;
|
|
115
|
+
if (p === path.join(mockProjectPath, 'app')) return true;
|
|
116
|
+
return false;
|
|
117
|
+
});
|
|
118
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
119
|
+
dependencies: {
|
|
120
|
+
next: '^14.0.0',
|
|
121
|
+
},
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
125
|
+
|
|
126
|
+
expect(result.metadata.routerType).toBe('app');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('detects App Router from src/app directory', () => {
|
|
130
|
+
fs.existsSync.mockImplementation((p) => {
|
|
131
|
+
if (p === path.join(mockProjectPath, 'package.json')) return true;
|
|
132
|
+
if (p === path.join(mockProjectPath, 'src', 'app')) return true;
|
|
133
|
+
return false;
|
|
134
|
+
});
|
|
135
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
136
|
+
dependencies: {
|
|
137
|
+
next: '^14.0.0',
|
|
138
|
+
},
|
|
139
|
+
}));
|
|
140
|
+
|
|
141
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
142
|
+
|
|
143
|
+
expect(result.metadata.routerType).toBe('app');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('detects Pages Router from pages directory', () => {
|
|
147
|
+
fs.existsSync.mockImplementation((p) => {
|
|
148
|
+
if (p === path.join(mockProjectPath, 'package.json')) return true;
|
|
149
|
+
if (p === path.join(mockProjectPath, 'pages')) return true;
|
|
150
|
+
return false;
|
|
151
|
+
});
|
|
152
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
153
|
+
dependencies: {
|
|
154
|
+
next: '^14.0.0',
|
|
155
|
+
},
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
159
|
+
|
|
160
|
+
expect(result.metadata.routerType).toBe('pages');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('detects next.config.js', () => {
|
|
164
|
+
fs.existsSync.mockImplementation((p) => {
|
|
165
|
+
if (p === path.join(mockProjectPath, 'package.json')) return true;
|
|
166
|
+
if (p === path.join(mockProjectPath, 'next.config.js')) return true;
|
|
167
|
+
return false;
|
|
168
|
+
});
|
|
169
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
170
|
+
dependencies: {
|
|
171
|
+
next: '^14.0.0',
|
|
172
|
+
},
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
176
|
+
|
|
177
|
+
expect(result.metadata.config).toBe('next.config.js');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('detects next.config.mjs', () => {
|
|
181
|
+
fs.existsSync.mockImplementation((p) => {
|
|
182
|
+
if (p === path.join(mockProjectPath, 'package.json')) return true;
|
|
183
|
+
if (p === path.join(mockProjectPath, 'next.config.mjs')) return true;
|
|
184
|
+
return false;
|
|
185
|
+
});
|
|
186
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
187
|
+
dependencies: {
|
|
188
|
+
next: '^14.0.0',
|
|
189
|
+
},
|
|
190
|
+
}));
|
|
191
|
+
|
|
192
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
193
|
+
|
|
194
|
+
expect(result.metadata.config).toBe('next.config.mjs');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('handles JSON parse errors gracefully', () => {
|
|
198
|
+
fs.existsSync.mockReturnValue(true);
|
|
199
|
+
fs.readFileSync.mockReturnValue('invalid json');
|
|
200
|
+
|
|
201
|
+
const result = NextJsDetector.detect(mockProjectPath);
|
|
202
|
+
|
|
203
|
+
expect(result).toEqual({
|
|
204
|
+
detected: false,
|
|
205
|
+
confidence: 0,
|
|
206
|
+
metadata: {},
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('getSupportedFeatures', () => {
|
|
212
|
+
it('returns all features as supported', () => {
|
|
213
|
+
const features = NextJsDetector.getSupportedFeatures();
|
|
214
|
+
|
|
215
|
+
expect(features).toEqual({
|
|
216
|
+
oauth: true,
|
|
217
|
+
stripe: true,
|
|
218
|
+
crm: true,
|
|
219
|
+
projects: true,
|
|
220
|
+
invoices: true,
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('getAvailableGenerators', () => {
|
|
226
|
+
it('returns list of available generators', () => {
|
|
227
|
+
const generators = NextJsDetector.getAvailableGenerators();
|
|
228
|
+
|
|
229
|
+
expect(generators).toContain('api-client');
|
|
230
|
+
expect(generators).toContain('env');
|
|
231
|
+
expect(generators).toContain('nextauth');
|
|
232
|
+
expect(generators).toContain('oauth-guide');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for OAuth Detector
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
jest.mock('fs');
|
|
9
|
+
|
|
10
|
+
const OAuthDetector = require('../src/detectors/oauth-detector');
|
|
11
|
+
|
|
12
|
+
describe('OAuthDetector', () => {
|
|
13
|
+
const mockProjectPath = '/test/project';
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
fs.existsSync.mockReturnValue(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('detect', () => {
|
|
21
|
+
it('returns hasOAuth false when no OAuth setup exists', () => {
|
|
22
|
+
fs.existsSync.mockReturnValue(false);
|
|
23
|
+
|
|
24
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
25
|
+
|
|
26
|
+
expect(result.hasOAuth).toBe(false);
|
|
27
|
+
expect(result.oauthType).toBeNull();
|
|
28
|
+
expect(result.configPath).toBeNull();
|
|
29
|
+
expect(result.providers).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('NextAuth.js detection', () => {
|
|
33
|
+
it('detects App Router NextAuth config (TypeScript)', () => {
|
|
34
|
+
fs.existsSync.mockImplementation((p) =>
|
|
35
|
+
p === path.join(mockProjectPath, 'app/api/auth/[...nextauth]/route.ts')
|
|
36
|
+
);
|
|
37
|
+
fs.readFileSync.mockReturnValue('GoogleProvider({})');
|
|
38
|
+
|
|
39
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
40
|
+
|
|
41
|
+
expect(result.hasOAuth).toBe(true);
|
|
42
|
+
expect(result.oauthType).toBe('nextauth');
|
|
43
|
+
expect(result.configPath).toBe('app/api/auth/[...nextauth]/route.ts');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('detects App Router NextAuth config (JavaScript)', () => {
|
|
47
|
+
fs.existsSync.mockImplementation((p) =>
|
|
48
|
+
p === path.join(mockProjectPath, 'app/api/auth/[...nextauth]/route.js')
|
|
49
|
+
);
|
|
50
|
+
fs.readFileSync.mockReturnValue('export default');
|
|
51
|
+
|
|
52
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
53
|
+
|
|
54
|
+
expect(result.hasOAuth).toBe(true);
|
|
55
|
+
expect(result.oauthType).toBe('nextauth');
|
|
56
|
+
expect(result.configPath).toBe('app/api/auth/[...nextauth]/route.js');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('detects Pages Router NextAuth config', () => {
|
|
60
|
+
fs.existsSync.mockImplementation((p) =>
|
|
61
|
+
p === path.join(mockProjectPath, 'pages/api/auth/[...nextauth].ts')
|
|
62
|
+
);
|
|
63
|
+
fs.readFileSync.mockReturnValue('export default NextAuth');
|
|
64
|
+
|
|
65
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
66
|
+
|
|
67
|
+
expect(result.hasOAuth).toBe(true);
|
|
68
|
+
expect(result.oauthType).toBe('nextauth');
|
|
69
|
+
expect(result.configPath).toBe('pages/api/auth/[...nextauth].ts');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('detects src/app NextAuth config', () => {
|
|
73
|
+
fs.existsSync.mockImplementation((p) =>
|
|
74
|
+
p === path.join(mockProjectPath, 'src/app/api/auth/[...nextauth]/route.ts')
|
|
75
|
+
);
|
|
76
|
+
fs.readFileSync.mockReturnValue('NextAuth');
|
|
77
|
+
|
|
78
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
79
|
+
|
|
80
|
+
expect(result.hasOAuth).toBe(true);
|
|
81
|
+
expect(result.configPath).toBe('src/app/api/auth/[...nextauth]/route.ts');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('provider detection', () => {
|
|
86
|
+
it('detects Google provider', () => {
|
|
87
|
+
fs.existsSync.mockImplementation((p) =>
|
|
88
|
+
p === path.join(mockProjectPath, 'app/api/auth/[...nextauth]/route.ts')
|
|
89
|
+
);
|
|
90
|
+
fs.readFileSync.mockReturnValue('GoogleProvider({ clientId: "" })');
|
|
91
|
+
|
|
92
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
93
|
+
|
|
94
|
+
expect(result.providers).toContain('google');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('detects Microsoft/Azure provider', () => {
|
|
98
|
+
fs.existsSync.mockImplementation((p) =>
|
|
99
|
+
p === path.join(mockProjectPath, 'app/api/auth/[...nextauth]/route.ts')
|
|
100
|
+
);
|
|
101
|
+
fs.readFileSync.mockReturnValue('AzureADProvider({ clientId: "" })');
|
|
102
|
+
|
|
103
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
104
|
+
|
|
105
|
+
expect(result.providers).toContain('microsoft');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('detects GitHub provider', () => {
|
|
109
|
+
fs.existsSync.mockImplementation((p) =>
|
|
110
|
+
p === path.join(mockProjectPath, 'app/api/auth/[...nextauth]/route.ts')
|
|
111
|
+
);
|
|
112
|
+
fs.readFileSync.mockReturnValue('GitHubProvider({ clientId: "" })');
|
|
113
|
+
|
|
114
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
115
|
+
|
|
116
|
+
expect(result.providers).toContain('github');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('detects multiple providers', () => {
|
|
120
|
+
fs.existsSync.mockImplementation((p) =>
|
|
121
|
+
p === path.join(mockProjectPath, 'app/api/auth/[...nextauth]/route.ts')
|
|
122
|
+
);
|
|
123
|
+
fs.readFileSync.mockReturnValue(`
|
|
124
|
+
GoogleProvider({}),
|
|
125
|
+
AzureADProvider({}),
|
|
126
|
+
GitHubProvider({})
|
|
127
|
+
`);
|
|
128
|
+
|
|
129
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
130
|
+
|
|
131
|
+
expect(result.providers).toContain('google');
|
|
132
|
+
expect(result.providers).toContain('microsoft');
|
|
133
|
+
expect(result.providers).toContain('github');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('detects provider by keyword (lowercase)', () => {
|
|
137
|
+
fs.existsSync.mockImplementation((p) =>
|
|
138
|
+
p === path.join(mockProjectPath, 'app/api/auth/[...nextauth]/route.ts')
|
|
139
|
+
);
|
|
140
|
+
fs.readFileSync.mockReturnValue('providers: [google, azure, github]');
|
|
141
|
+
|
|
142
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
143
|
+
|
|
144
|
+
expect(result.providers).toContain('google');
|
|
145
|
+
expect(result.providers).toContain('microsoft');
|
|
146
|
+
expect(result.providers).toContain('github');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('environment variable detection', () => {
|
|
151
|
+
it('detects OAuth env vars in .env.local', () => {
|
|
152
|
+
fs.existsSync.mockImplementation((p) =>
|
|
153
|
+
p === path.join(mockProjectPath, '.env.local')
|
|
154
|
+
);
|
|
155
|
+
fs.readFileSync.mockReturnValue('NEXTAUTH_SECRET=secret123');
|
|
156
|
+
|
|
157
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
158
|
+
|
|
159
|
+
expect(result.hasEnvVars).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('detects OAuth env vars in .env', () => {
|
|
163
|
+
fs.existsSync.mockImplementation((p) =>
|
|
164
|
+
p === path.join(mockProjectPath, '.env')
|
|
165
|
+
);
|
|
166
|
+
fs.readFileSync.mockReturnValue('GOOGLE_CLIENT_ID=abc123');
|
|
167
|
+
|
|
168
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
169
|
+
|
|
170
|
+
expect(result.hasEnvVars).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('detects various OAuth env var names', () => {
|
|
174
|
+
const oauthVars = [
|
|
175
|
+
'NEXTAUTH_URL',
|
|
176
|
+
'NEXTAUTH_SECRET',
|
|
177
|
+
'GOOGLE_CLIENT_ID',
|
|
178
|
+
'GOOGLE_CLIENT_SECRET',
|
|
179
|
+
'MICROSOFT_CLIENT_ID',
|
|
180
|
+
'GITHUB_CLIENT_ID',
|
|
181
|
+
'GOOGLE_OAUTH_CLIENT_ID',
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
for (const varName of oauthVars) {
|
|
185
|
+
fs.existsSync.mockImplementation((p) =>
|
|
186
|
+
p === path.join(mockProjectPath, '.env.local')
|
|
187
|
+
);
|
|
188
|
+
fs.readFileSync.mockReturnValue(`${varName}=value`);
|
|
189
|
+
|
|
190
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
191
|
+
|
|
192
|
+
expect(result.hasEnvVars).toBe(true);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('package.json detection', () => {
|
|
198
|
+
it('detects next-auth in dependencies', () => {
|
|
199
|
+
fs.existsSync.mockImplementation((p) =>
|
|
200
|
+
p === path.join(mockProjectPath, 'package.json')
|
|
201
|
+
);
|
|
202
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
203
|
+
dependencies: { 'next-auth': '^4.0.0' },
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
207
|
+
|
|
208
|
+
expect(result.hasOAuth).toBe(true);
|
|
209
|
+
expect(result.oauthType).toBe('nextauth');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('detects next-auth in devDependencies', () => {
|
|
213
|
+
fs.existsSync.mockImplementation((p) =>
|
|
214
|
+
p === path.join(mockProjectPath, 'package.json')
|
|
215
|
+
);
|
|
216
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({
|
|
217
|
+
devDependencies: { 'next-auth': '^4.0.0' },
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
221
|
+
|
|
222
|
+
expect(result.hasOAuth).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('handles package.json read errors', () => {
|
|
226
|
+
fs.existsSync.mockImplementation((p) =>
|
|
227
|
+
p === path.join(mockProjectPath, 'package.json')
|
|
228
|
+
);
|
|
229
|
+
fs.readFileSync.mockImplementation(() => {
|
|
230
|
+
throw new Error('Read failed');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
234
|
+
|
|
235
|
+
expect(result.hasOAuth).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('handles invalid JSON in package.json', () => {
|
|
239
|
+
fs.existsSync.mockImplementation((p) =>
|
|
240
|
+
p === path.join(mockProjectPath, 'package.json')
|
|
241
|
+
);
|
|
242
|
+
fs.readFileSync.mockReturnValue('invalid json');
|
|
243
|
+
|
|
244
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
245
|
+
|
|
246
|
+
expect(result.hasOAuth).toBe(false);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('handles config file read errors gracefully', () => {
|
|
251
|
+
fs.existsSync.mockImplementation((p) =>
|
|
252
|
+
p === path.join(mockProjectPath, 'app/api/auth/[...nextauth]/route.ts')
|
|
253
|
+
);
|
|
254
|
+
fs.readFileSync.mockImplementation(() => {
|
|
255
|
+
throw new Error('Read failed');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const result = OAuthDetector.detect(mockProjectPath);
|
|
259
|
+
|
|
260
|
+
expect(result.hasOAuth).toBe(true);
|
|
261
|
+
expect(result.providers).toEqual([]);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|