@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,145 @@
1
+ /**
2
+ * Tests for GitHub Detector
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const { execSync } = require('child_process');
7
+
8
+ jest.mock('fs');
9
+ jest.mock('child_process');
10
+
11
+ const GitHubDetector = require('../src/detectors/github-detector');
12
+
13
+ describe('GitHubDetector', () => {
14
+ const mockProjectPath = '/test/project';
15
+
16
+ beforeEach(() => {
17
+ jest.clearAllMocks();
18
+ });
19
+
20
+ describe('detect', () => {
21
+ it('returns hasGit false when no .git directory exists', () => {
22
+ fs.existsSync.mockReturnValue(false);
23
+
24
+ const result = GitHubDetector.detect(mockProjectPath);
25
+
26
+ expect(result.hasGit).toBe(false);
27
+ expect(result.isGitHub).toBe(false);
28
+ expect(result.owner).toBeNull();
29
+ expect(result.repo).toBeNull();
30
+ });
31
+
32
+ it('returns hasGit true when .git directory exists', () => {
33
+ fs.existsSync.mockReturnValue(true);
34
+ execSync.mockReturnValue('https://github.com/owner/repo.git\n');
35
+
36
+ const result = GitHubDetector.detect(mockProjectPath);
37
+
38
+ expect(result.hasGit).toBe(true);
39
+ });
40
+
41
+ it('parses HTTPS GitHub URL correctly', () => {
42
+ fs.existsSync.mockReturnValue(true);
43
+ execSync
44
+ .mockReturnValueOnce('https://github.com/myorg/myrepo.git\n')
45
+ .mockReturnValueOnce('main\n');
46
+
47
+ const result = GitHubDetector.detect(mockProjectPath);
48
+
49
+ expect(result.isGitHub).toBe(true);
50
+ expect(result.owner).toBe('myorg');
51
+ expect(result.repo).toBe('myrepo');
52
+ expect(result.url).toBe('https://github.com/myorg/myrepo');
53
+ expect(result.branch).toBe('main');
54
+ });
55
+
56
+ it('parses SSH GitHub URL correctly', () => {
57
+ fs.existsSync.mockReturnValue(true);
58
+ execSync
59
+ .mockReturnValueOnce('git@github.com:myorg/myrepo.git\n')
60
+ .mockReturnValueOnce('develop\n');
61
+
62
+ const result = GitHubDetector.detect(mockProjectPath);
63
+
64
+ expect(result.isGitHub).toBe(true);
65
+ expect(result.owner).toBe('myorg');
66
+ expect(result.repo).toBe('myrepo');
67
+ expect(result.url).toBe('https://github.com/myorg/myrepo');
68
+ expect(result.branch).toBe('develop');
69
+ });
70
+
71
+ it('parses GitHub URL without .git extension', () => {
72
+ fs.existsSync.mockReturnValue(true);
73
+ execSync
74
+ .mockReturnValueOnce('https://github.com/owner/repo\n')
75
+ .mockReturnValueOnce('main\n');
76
+
77
+ const result = GitHubDetector.detect(mockProjectPath);
78
+
79
+ expect(result.isGitHub).toBe(true);
80
+ expect(result.owner).toBe('owner');
81
+ expect(result.repo).toBe('repo');
82
+ });
83
+
84
+ it('returns isGitHub false for non-GitHub remote', () => {
85
+ fs.existsSync.mockReturnValue(true);
86
+ execSync.mockReturnValue('https://gitlab.com/owner/repo.git\n');
87
+
88
+ const result = GitHubDetector.detect(mockProjectPath);
89
+
90
+ expect(result.hasGit).toBe(true);
91
+ expect(result.isGitHub).toBe(false);
92
+ expect(result.owner).toBeNull();
93
+ expect(result.repo).toBeNull();
94
+ });
95
+
96
+ it('handles empty remote URL', () => {
97
+ fs.existsSync.mockReturnValue(true);
98
+ execSync.mockReturnValue('\n');
99
+
100
+ const result = GitHubDetector.detect(mockProjectPath);
101
+
102
+ expect(result.hasGit).toBe(true);
103
+ expect(result.isGitHub).toBe(false);
104
+ });
105
+
106
+ it('handles git command failure gracefully', () => {
107
+ fs.existsSync.mockReturnValue(true);
108
+ execSync.mockImplementation(() => {
109
+ throw new Error('git command failed');
110
+ });
111
+
112
+ const result = GitHubDetector.detect(mockProjectPath);
113
+
114
+ expect(result.hasGit).toBe(true);
115
+ expect(result.isGitHub).toBe(false);
116
+ });
117
+
118
+ it('handles branch detection failure gracefully', () => {
119
+ fs.existsSync.mockReturnValue(true);
120
+ execSync
121
+ .mockReturnValueOnce('https://github.com/owner/repo.git\n')
122
+ .mockImplementationOnce(() => {
123
+ throw new Error('branch command failed');
124
+ });
125
+
126
+ const result = GitHubDetector.detect(mockProjectPath);
127
+
128
+ expect(result.isGitHub).toBe(true);
129
+ expect(result.owner).toBe('owner');
130
+ expect(result.branch).toBeNull();
131
+ });
132
+
133
+ it('uses correct working directory for git commands', () => {
134
+ fs.existsSync.mockReturnValue(true);
135
+ execSync.mockReturnValue('https://github.com/owner/repo.git\n');
136
+
137
+ GitHubDetector.detect(mockProjectPath);
138
+
139
+ expect(execSync).toHaveBeenCalledWith(
140
+ 'git config --get remote.origin.url',
141
+ expect.objectContaining({ cwd: mockProjectPath })
142
+ );
143
+ });
144
+ });
145
+ });
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Tests for Gitignore Generator
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ jest.mock('fs');
9
+
10
+ const GitignoreGenerator = require('../src/generators/gitignore-generator');
11
+
12
+ describe('GitignoreGenerator', () => {
13
+ const mockProjectPath = '/test/project';
14
+ const mockGitignorePath = path.join(mockProjectPath, '.gitignore');
15
+
16
+ beforeEach(() => {
17
+ jest.clearAllMocks();
18
+ fs.existsSync.mockReturnValue(false);
19
+ fs.writeFileSync.mockReturnValue(undefined);
20
+ });
21
+
22
+ describe('generate', () => {
23
+ it('creates new .gitignore with L4YERCAK3 entries', () => {
24
+ fs.existsSync.mockReturnValue(false);
25
+
26
+ const result = GitignoreGenerator.generate({ projectPath: mockProjectPath });
27
+
28
+ expect(result).toBe(mockGitignorePath);
29
+ expect(fs.writeFileSync).toHaveBeenCalled();
30
+
31
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
32
+ expect(writtenContent).toContain('# L4YERCAK3 Configuration');
33
+ expect(writtenContent).toContain('.env.local');
34
+ expect(writtenContent).toContain('.env*.local');
35
+ expect(writtenContent).toContain('.l4yercak3/');
36
+ });
37
+
38
+ it('appends to existing .gitignore', () => {
39
+ fs.existsSync.mockReturnValue(true);
40
+ fs.readFileSync.mockReturnValue(`# Existing gitignore
41
+ node_modules/
42
+ .DS_Store
43
+ `);
44
+
45
+ const result = GitignoreGenerator.generate({ projectPath: mockProjectPath });
46
+
47
+ expect(result).toBe(mockGitignorePath);
48
+
49
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
50
+ expect(writtenContent).toContain('node_modules/');
51
+ expect(writtenContent).toContain('.DS_Store');
52
+ expect(writtenContent).toContain('# L4YERCAK3 Configuration');
53
+ expect(writtenContent).toContain('.env.local');
54
+ });
55
+
56
+ it('returns null when all entries already exist', () => {
57
+ fs.existsSync.mockReturnValue(true);
58
+ fs.readFileSync.mockReturnValue(`# L4YERCAK3 Configuration
59
+ # Auto-generated by @l4yercak3/cli
60
+ .env.local
61
+ .env*.local
62
+ .l4yercak3/
63
+ `);
64
+
65
+ const result = GitignoreGenerator.generate({ projectPath: mockProjectPath });
66
+
67
+ expect(result).toBeNull();
68
+ expect(fs.writeFileSync).not.toHaveBeenCalled();
69
+ });
70
+
71
+ it('adds missing entries when some already exist', () => {
72
+ fs.existsSync.mockReturnValue(true);
73
+ fs.readFileSync.mockReturnValue(`# L4YERCAK3 Configuration
74
+ .env.local
75
+ `);
76
+
77
+ GitignoreGenerator.generate({ projectPath: mockProjectPath });
78
+
79
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
80
+ expect(writtenContent).toContain('.env*.local');
81
+ expect(writtenContent).toContain('.l4yercak3/');
82
+ });
83
+
84
+ it('adds newline before section if file does not end with newline', () => {
85
+ fs.existsSync.mockReturnValue(true);
86
+ fs.readFileSync.mockReturnValue('node_modules/');
87
+
88
+ GitignoreGenerator.generate({ projectPath: mockProjectPath });
89
+
90
+ const writtenContent = fs.writeFileSync.mock.calls[0][1];
91
+ // Should have newline between existing content and new section
92
+ expect(writtenContent).toMatch(/node_modules\/\n\n# L4YERCAK3 Configuration/);
93
+ });
94
+
95
+ it('handles whitespace variations in existing entries', () => {
96
+ fs.existsSync.mockReturnValue(true);
97
+ fs.readFileSync.mockReturnValue(`# Some config
98
+ .env.local
99
+ .env*.local
100
+ .l4yercak3/
101
+ # L4YERCAK3 Configuration
102
+ `);
103
+
104
+ const result = GitignoreGenerator.generate({ projectPath: mockProjectPath });
105
+
106
+ expect(result).toBeNull();
107
+ });
108
+ });
109
+ });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Tests for Logo Module
3
+ */
4
+
5
+ const figlet = require('figlet');
6
+
7
+ jest.mock('chalk', () => {
8
+ const mockChalk = (text) => text;
9
+ mockChalk.hex = () => mockChalk;
10
+ return mockChalk;
11
+ });
12
+
13
+ jest.mock('figlet', () => ({
14
+ textSync: jest.fn().mockReturnValue('MOCK LOGO\nLINE 2\nLINE 3'),
15
+ }));
16
+
17
+ // Spy on console.log
18
+ const originalConsoleLog = console.log;
19
+ let consoleOutput = [];
20
+
21
+ beforeEach(() => {
22
+ consoleOutput = [];
23
+ console.log = jest.fn((...args) => {
24
+ consoleOutput.push(args.join(' '));
25
+ });
26
+ });
27
+
28
+ afterEach(() => {
29
+ console.log = originalConsoleLog;
30
+ });
31
+
32
+ const { showLogo, rainbow } = require('../src/logo');
33
+
34
+ describe('Logo Module', () => {
35
+ describe('rainbow export', () => {
36
+ it('exports rainbow color array', () => {
37
+ expect(Array.isArray(rainbow)).toBe(true);
38
+ expect(rainbow.length).toBeGreaterThan(0);
39
+ });
40
+
41
+ it('contains valid hex colors', () => {
42
+ rainbow.forEach((color) => {
43
+ expect(color).toMatch(/^#[0-9A-Fa-f]{6}$/);
44
+ });
45
+ });
46
+ });
47
+
48
+ describe('showLogo', () => {
49
+ it('calls figlet.textSync with L4YERCAK3', () => {
50
+ showLogo(false);
51
+
52
+ expect(figlet.textSync).toHaveBeenCalledWith(
53
+ 'L4YERCAK3',
54
+ expect.objectContaining({
55
+ font: '3D-ASCII',
56
+ })
57
+ );
58
+ });
59
+
60
+ it('prints logo lines to console', () => {
61
+ showLogo(false);
62
+
63
+ // Should have printed the mocked logo lines
64
+ expect(consoleOutput.some((line) => line.includes('MOCK LOGO'))).toBe(true);
65
+ });
66
+
67
+ it('shows building metaphor by default', () => {
68
+ showLogo();
69
+
70
+ // Should print building metaphor lines
71
+ const output = consoleOutput.join('\n');
72
+ expect(output.length).toBeGreaterThan(100);
73
+ });
74
+
75
+ it('hides building metaphor when showBuilding is false', () => {
76
+ showLogo(false);
77
+
78
+ // Output should be shorter without building
79
+ const outputWithoutBuilding = consoleOutput.length;
80
+
81
+ consoleOutput = [];
82
+ showLogo(true);
83
+
84
+ const outputWithBuilding = consoleOutput.length;
85
+
86
+ expect(outputWithBuilding).toBeGreaterThan(outputWithoutBuilding);
87
+ });
88
+
89
+ it('prints spacing after logo', () => {
90
+ showLogo(false);
91
+
92
+ // Last output should be empty (spacing)
93
+ expect(consoleOutput[consoleOutput.length - 1]).toBe('');
94
+ });
95
+ });
96
+ });
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Tests for NextAuth Generator
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ jest.mock('fs');
9
+
10
+ const NextAuthGenerator = require('../src/generators/nextauth-generator');
11
+
12
+ describe('NextAuthGenerator', () => {
13
+ const mockProjectPath = '/test/project';
14
+
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ fs.existsSync.mockReturnValue(false);
18
+ fs.mkdirSync.mockReturnValue(undefined);
19
+ fs.writeFileSync.mockReturnValue(undefined);
20
+ });
21
+
22
+ describe('generate', () => {
23
+ it('creates route.js in app/api/auth/[...nextauth] for App Router', () => {
24
+ const options = {
25
+ projectPath: mockProjectPath,
26
+ backendUrl: 'https://backend.test.com',
27
+ oauthProviders: ['google'],
28
+ routerType: 'app',
29
+ isTypeScript: false,
30
+ };
31
+
32
+ const result = NextAuthGenerator.generate(options);
33
+
34
+ expect(result).toBe(
35
+ path.join(mockProjectPath, 'app', 'api', 'auth', '[...nextauth]', 'route.js')
36
+ );
37
+ expect(fs.mkdirSync).toHaveBeenCalledWith(
38
+ path.join(mockProjectPath, 'app', 'api', 'auth'),
39
+ { recursive: true }
40
+ );
41
+ expect(fs.mkdirSync).toHaveBeenCalledWith(
42
+ path.join(mockProjectPath, 'app', 'api', 'auth', '[...nextauth]'),
43
+ { recursive: true }
44
+ );
45
+ });
46
+
47
+ it('creates [...nextauth].ts in pages/api/auth for Pages Router', () => {
48
+ const options = {
49
+ projectPath: mockProjectPath,
50
+ backendUrl: 'https://backend.test.com',
51
+ oauthProviders: ['google'],
52
+ routerType: 'pages',
53
+ isTypeScript: true,
54
+ };
55
+
56
+ const result = NextAuthGenerator.generate(options);
57
+
58
+ expect(result).toBe(
59
+ path.join(mockProjectPath, 'pages', 'api', 'auth', '[...nextauth].ts')
60
+ );
61
+ });
62
+
63
+ it('does not create [...nextauth] dir for Pages Router', () => {
64
+ const options = {
65
+ projectPath: mockProjectPath,
66
+ backendUrl: 'https://backend.test.com',
67
+ oauthProviders: ['google'],
68
+ routerType: 'pages',
69
+ isTypeScript: false,
70
+ };
71
+
72
+ NextAuthGenerator.generate(options);
73
+
74
+ // Should only create pages/api/auth, not [...nextauth] dir
75
+ const mkdirCalls = fs.mkdirSync.mock.calls.map((c) => c[0]);
76
+ expect(mkdirCalls).not.toContain(
77
+ path.join(mockProjectPath, 'pages', 'api', 'auth', '[...nextauth]')
78
+ );
79
+ });
80
+ });
81
+
82
+ describe('generateCode', () => {
83
+ describe('Google provider', () => {
84
+ it('includes Google provider import and config', () => {
85
+ const code = NextAuthGenerator.generateCode({
86
+ oauthProviders: ['google'],
87
+ routerType: 'app',
88
+ isTypeScript: false,
89
+ });
90
+
91
+ expect(code).toContain("import GoogleProvider from 'next-auth/providers/google'");
92
+ expect(code).toContain('GoogleProvider({');
93
+ expect(code).toContain('process.env.GOOGLE_CLIENT_ID');
94
+ expect(code).toContain('process.env.GOOGLE_CLIENT_SECRET');
95
+ });
96
+ });
97
+
98
+ describe('Microsoft provider', () => {
99
+ it('includes Azure AD provider import and config', () => {
100
+ const code = NextAuthGenerator.generateCode({
101
+ oauthProviders: ['microsoft'],
102
+ routerType: 'app',
103
+ isTypeScript: false,
104
+ });
105
+
106
+ expect(code).toContain("import AzureADProvider from 'next-auth/providers/azure-ad'");
107
+ expect(code).toContain('AzureADProvider({');
108
+ expect(code).toContain('process.env.AZURE_CLIENT_ID');
109
+ expect(code).toContain('process.env.AZURE_CLIENT_SECRET');
110
+ expect(code).toContain('process.env.AZURE_TENANT_ID');
111
+ });
112
+ });
113
+
114
+ describe('GitHub provider', () => {
115
+ it('includes GitHub provider import and config', () => {
116
+ const code = NextAuthGenerator.generateCode({
117
+ oauthProviders: ['github'],
118
+ routerType: 'app',
119
+ isTypeScript: false,
120
+ });
121
+
122
+ expect(code).toContain("import GitHubProvider from 'next-auth/providers/github'");
123
+ expect(code).toContain('GitHubProvider({');
124
+ expect(code).toContain('process.env.GITHUB_CLIENT_ID');
125
+ expect(code).toContain('process.env.GITHUB_CLIENT_SECRET');
126
+ });
127
+ });
128
+
129
+ describe('multiple providers', () => {
130
+ it('includes all selected providers', () => {
131
+ const code = NextAuthGenerator.generateCode({
132
+ oauthProviders: ['google', 'microsoft', 'github'],
133
+ routerType: 'app',
134
+ isTypeScript: false,
135
+ });
136
+
137
+ expect(code).toContain('GoogleProvider');
138
+ expect(code).toContain('AzureADProvider');
139
+ expect(code).toContain('GitHubProvider');
140
+ });
141
+ });
142
+
143
+ describe('App Router format', () => {
144
+ it('uses route handler exports', () => {
145
+ const code = NextAuthGenerator.generateCode({
146
+ oauthProviders: ['google'],
147
+ routerType: 'app',
148
+ isTypeScript: false,
149
+ });
150
+
151
+ expect(code).toContain('export { handler as GET, handler as POST }');
152
+ expect(code).toContain('const handler = NextAuth(authOptions)');
153
+ expect(code).toContain("import type { NextAuthOptions } from 'next-auth'");
154
+ });
155
+ });
156
+
157
+ describe('Pages Router format', () => {
158
+ it('uses default export', () => {
159
+ const code = NextAuthGenerator.generateCode({
160
+ oauthProviders: ['google'],
161
+ routerType: 'pages',
162
+ isTypeScript: false,
163
+ });
164
+
165
+ expect(code).toContain('export default NextAuth({');
166
+ expect(code).not.toContain('export { handler as GET');
167
+ });
168
+ });
169
+
170
+ describe('TypeScript support', () => {
171
+ it('adds non-null assertions for TypeScript', () => {
172
+ const code = NextAuthGenerator.generateCode({
173
+ oauthProviders: ['google'],
174
+ routerType: 'app',
175
+ isTypeScript: true,
176
+ });
177
+
178
+ expect(code).toContain('process.env.GOOGLE_CLIENT_ID!');
179
+ expect(code).toContain('process.env.GOOGLE_CLIENT_SECRET!');
180
+ });
181
+
182
+ it('omits non-null assertions for JavaScript', () => {
183
+ const code = NextAuthGenerator.generateCode({
184
+ oauthProviders: ['google'],
185
+ routerType: 'app',
186
+ isTypeScript: false,
187
+ });
188
+
189
+ expect(code).not.toContain('GOOGLE_CLIENT_ID!');
190
+ expect(code).not.toContain('GOOGLE_CLIENT_SECRET!');
191
+ });
192
+ });
193
+
194
+ describe('callbacks', () => {
195
+ it('includes signIn callback with backend sync', () => {
196
+ const code = NextAuthGenerator.generateCode({
197
+ oauthProviders: ['google'],
198
+ routerType: 'app',
199
+ isTypeScript: false,
200
+ });
201
+
202
+ expect(code).toContain('async signIn({ user, account, profile })');
203
+ expect(code).toContain('/api/v1/auth/sync-user');
204
+ expect(code).toContain('NEXT_PUBLIC_L4YERCAK3_BACKEND_URL');
205
+ expect(code).toContain('L4YERCAK3_API_KEY');
206
+ });
207
+
208
+ it('includes session callback', () => {
209
+ const code = NextAuthGenerator.generateCode({
210
+ oauthProviders: ['google'],
211
+ routerType: 'app',
212
+ isTypeScript: false,
213
+ });
214
+
215
+ expect(code).toContain('async session({ session, user');
216
+ expect(code).toContain('session.user.id');
217
+ expect(code).toContain('session.user.organizationId');
218
+ });
219
+ });
220
+
221
+ it('includes custom signin page', () => {
222
+ const code = NextAuthGenerator.generateCode({
223
+ oauthProviders: ['google'],
224
+ routerType: 'app',
225
+ isTypeScript: false,
226
+ });
227
+
228
+ expect(code).toContain("signIn: '/auth/signin'");
229
+ });
230
+ });
231
+ });