@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,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager
|
|
3
|
+
* Handles storing and retrieving CLI configuration and session data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
class ConfigManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.configDir = path.join(os.homedir(), '.l4yercak3');
|
|
13
|
+
this.configFile = path.join(this.configDir, 'config.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Ensure config directory exists
|
|
18
|
+
*/
|
|
19
|
+
ensureConfigDir() {
|
|
20
|
+
if (!fs.existsSync(this.configDir)) {
|
|
21
|
+
fs.mkdirSync(this.configDir, { recursive: true, mode: 0o700 });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get full config object
|
|
27
|
+
*/
|
|
28
|
+
getConfig() {
|
|
29
|
+
this.ensureConfigDir();
|
|
30
|
+
|
|
31
|
+
if (!fs.existsSync(this.configFile)) {
|
|
32
|
+
return {
|
|
33
|
+
session: null,
|
|
34
|
+
organizations: [],
|
|
35
|
+
settings: {
|
|
36
|
+
backendUrl: process.env.L4YERCAK3_BACKEND_URL || 'https://backend.l4yercak3.com',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const data = fs.readFileSync(this.configFile, 'utf8');
|
|
43
|
+
return JSON.parse(data);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Error reading config file:', error.message);
|
|
46
|
+
return {
|
|
47
|
+
session: null,
|
|
48
|
+
organizations: [],
|
|
49
|
+
settings: {},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Save config object
|
|
56
|
+
*/
|
|
57
|
+
saveConfig(config) {
|
|
58
|
+
this.ensureConfigDir();
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
fs.writeFileSync(
|
|
62
|
+
this.configFile,
|
|
63
|
+
JSON.stringify(config, null, 2),
|
|
64
|
+
{ mode: 0o600 } // Read/write for owner only
|
|
65
|
+
);
|
|
66
|
+
return true;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('Error saving config file:', error.message);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get current session
|
|
75
|
+
*/
|
|
76
|
+
getSession() {
|
|
77
|
+
const config = this.getConfig();
|
|
78
|
+
return config.session;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Save session
|
|
83
|
+
*/
|
|
84
|
+
saveSession(session) {
|
|
85
|
+
const config = this.getConfig();
|
|
86
|
+
config.session = session;
|
|
87
|
+
return this.saveConfig(config);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Clear session (logout)
|
|
92
|
+
*/
|
|
93
|
+
clearSession() {
|
|
94
|
+
const config = this.getConfig();
|
|
95
|
+
config.session = null;
|
|
96
|
+
return this.saveConfig(config);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if user is logged in
|
|
101
|
+
*/
|
|
102
|
+
isLoggedIn() {
|
|
103
|
+
const session = this.getSession();
|
|
104
|
+
if (!session || !session.token) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check expiration
|
|
109
|
+
if (session.expiresAt && session.expiresAt < Date.now()) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get backend URL from config or env
|
|
118
|
+
*/
|
|
119
|
+
getBackendUrl() {
|
|
120
|
+
const config = this.getConfig();
|
|
121
|
+
return config.settings?.backendUrl || process.env.L4YERCAK3_BACKEND_URL || 'https://backend.l4yercak3.com';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Set backend URL
|
|
126
|
+
*/
|
|
127
|
+
setBackendUrl(url) {
|
|
128
|
+
const config = this.getConfig();
|
|
129
|
+
if (!config.settings) {
|
|
130
|
+
config.settings = {};
|
|
131
|
+
}
|
|
132
|
+
config.settings.backendUrl = url;
|
|
133
|
+
return this.saveConfig(config);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Add organization to config
|
|
138
|
+
*/
|
|
139
|
+
addOrganization(org) {
|
|
140
|
+
const config = this.getConfig();
|
|
141
|
+
if (!config.organizations) {
|
|
142
|
+
config.organizations = [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Remove if exists
|
|
146
|
+
config.organizations = config.organizations.filter(o => o.id !== org.id);
|
|
147
|
+
|
|
148
|
+
// Add new
|
|
149
|
+
config.organizations.push(org);
|
|
150
|
+
|
|
151
|
+
return this.saveConfig(config);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get organizations
|
|
156
|
+
*/
|
|
157
|
+
getOrganizations() {
|
|
158
|
+
const config = this.getConfig();
|
|
159
|
+
return config.organizations || [];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Save project configuration
|
|
164
|
+
* Stores configuration for a specific project (by project path)
|
|
165
|
+
*/
|
|
166
|
+
saveProjectConfig(projectPath, projectConfig) {
|
|
167
|
+
const config = this.getConfig();
|
|
168
|
+
if (!config.projects) {
|
|
169
|
+
config.projects = {};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Normalize project path (use absolute path)
|
|
173
|
+
const normalizedPath = path.resolve(projectPath);
|
|
174
|
+
config.projects[normalizedPath] = {
|
|
175
|
+
...projectConfig,
|
|
176
|
+
updatedAt: Date.now(),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return this.saveConfig(config);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get project configuration
|
|
184
|
+
*/
|
|
185
|
+
getProjectConfig(projectPath) {
|
|
186
|
+
const config = this.getConfig();
|
|
187
|
+
if (!config.projects) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const normalizedPath = path.resolve(projectPath);
|
|
192
|
+
return config.projects[normalizedPath] || null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get all project configurations
|
|
197
|
+
*/
|
|
198
|
+
getAllProjectConfigs() {
|
|
199
|
+
const config = this.getConfig();
|
|
200
|
+
return config.projects || {};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = new ConfigManager();
|
|
205
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client Pattern Detector
|
|
3
|
+
* Detects existing API client implementations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
class ApiClientDetector {
|
|
10
|
+
/**
|
|
11
|
+
* Detect existing API client patterns
|
|
12
|
+
*/
|
|
13
|
+
detect(projectPath = process.cwd()) {
|
|
14
|
+
const results = {
|
|
15
|
+
hasApiClient: false,
|
|
16
|
+
clientPath: null,
|
|
17
|
+
clientType: null, // 'fetch', 'axios', 'custom'
|
|
18
|
+
hasEnvFile: false,
|
|
19
|
+
envFilePath: null,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Common API client locations
|
|
23
|
+
const clientPaths = [
|
|
24
|
+
'lib/api-client.ts',
|
|
25
|
+
'lib/api-client.js',
|
|
26
|
+
'lib/api.ts',
|
|
27
|
+
'lib/api.js',
|
|
28
|
+
'src/lib/api-client.ts',
|
|
29
|
+
'src/lib/api-client.js',
|
|
30
|
+
'src/lib/api.ts',
|
|
31
|
+
'src/lib/api.js',
|
|
32
|
+
'utils/api-client.ts',
|
|
33
|
+
'utils/api-client.js',
|
|
34
|
+
'utils/api.ts',
|
|
35
|
+
'utils/api.js',
|
|
36
|
+
'src/utils/api-client.ts',
|
|
37
|
+
'src/utils/api-client.js',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Check for existing API client
|
|
41
|
+
for (const clientPath of clientPaths) {
|
|
42
|
+
const fullPath = path.join(projectPath, clientPath);
|
|
43
|
+
if (fs.existsSync(fullPath)) {
|
|
44
|
+
results.hasApiClient = true;
|
|
45
|
+
results.clientPath = clientPath;
|
|
46
|
+
|
|
47
|
+
// Try to detect client type
|
|
48
|
+
try {
|
|
49
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
50
|
+
if (content.includes('axios')) {
|
|
51
|
+
results.clientType = 'axios';
|
|
52
|
+
} else if (content.includes('fetch')) {
|
|
53
|
+
results.clientType = 'fetch';
|
|
54
|
+
} else {
|
|
55
|
+
results.clientType = 'custom';
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
results.clientType = 'unknown';
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check for environment files
|
|
65
|
+
const envFiles = [
|
|
66
|
+
'.env.local',
|
|
67
|
+
'.env',
|
|
68
|
+
'.env.development',
|
|
69
|
+
'.env.production',
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const envFile of envFiles) {
|
|
73
|
+
const fullPath = path.join(projectPath, envFile);
|
|
74
|
+
if (fs.existsSync(fullPath)) {
|
|
75
|
+
results.hasEnvFile = true;
|
|
76
|
+
results.envFilePath = envFile;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = new ApiClientDetector();
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Detector Class
|
|
3
|
+
*
|
|
4
|
+
* All project type detectors should extend this class or implement its interface.
|
|
5
|
+
* This provides a consistent API for detection across all project types.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class BaseDetector {
|
|
9
|
+
/**
|
|
10
|
+
* Unique identifier for this project type
|
|
11
|
+
* @returns {string} Detector name (e.g., 'nextjs', 'react', 'vue')
|
|
12
|
+
*/
|
|
13
|
+
get name() {
|
|
14
|
+
throw new Error('Detector must implement name getter');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Detection priority (higher = checked first)
|
|
19
|
+
* Range: 0-100
|
|
20
|
+
*
|
|
21
|
+
* Priority guidelines:
|
|
22
|
+
* - 100: Very specific frameworks (Next.js, Nuxt, SvelteKit)
|
|
23
|
+
* - 75: Framework with build tool (Vite + React, Webpack + Vue)
|
|
24
|
+
* - 50: Pure frameworks (React, Vue, Svelte)
|
|
25
|
+
* - 25: Generic JavaScript/TypeScript projects
|
|
26
|
+
*
|
|
27
|
+
* @returns {number} Priority value
|
|
28
|
+
*/
|
|
29
|
+
get priority() {
|
|
30
|
+
return 50; // Default priority
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detect if project matches this type
|
|
35
|
+
*
|
|
36
|
+
* @param {string} projectPath - Path to project directory (defaults to process.cwd())
|
|
37
|
+
* @returns {object} Detection result with:
|
|
38
|
+
* - detected: {boolean} Is this the project type?
|
|
39
|
+
* - confidence: {number} 0-1, how sure are we?
|
|
40
|
+
* - metadata: {object} Type-specific information
|
|
41
|
+
*/
|
|
42
|
+
detect(_projectPath = process.cwd()) {
|
|
43
|
+
throw new Error('Detector must implement detect() method');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get supported features for this project type
|
|
48
|
+
*
|
|
49
|
+
* @returns {object} Feature support matrix:
|
|
50
|
+
* - oauth: {boolean|'manual'} true = full support, 'manual' = guide only, false = not supported
|
|
51
|
+
* - stripe: {boolean} Stripe integration support
|
|
52
|
+
* - crm: {boolean} CRM features support
|
|
53
|
+
* - projects: {boolean} Projects feature support
|
|
54
|
+
* - invoices: {boolean} Invoices feature support
|
|
55
|
+
*/
|
|
56
|
+
getSupportedFeatures() {
|
|
57
|
+
return {
|
|
58
|
+
oauth: false,
|
|
59
|
+
stripe: false,
|
|
60
|
+
crm: false,
|
|
61
|
+
projects: false,
|
|
62
|
+
invoices: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get available generators for this project type
|
|
68
|
+
*
|
|
69
|
+
* @returns {string[]} List of generator names that work with this project type
|
|
70
|
+
*/
|
|
71
|
+
getAvailableGenerators() {
|
|
72
|
+
return ['api-client', 'env']; // Default: shared generators only
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = BaseDetector;
|
|
77
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Repository Detector
|
|
3
|
+
* Detects GitHub repository information from git remotes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
class GitHubDetector {
|
|
10
|
+
/**
|
|
11
|
+
* Detect GitHub repository information
|
|
12
|
+
*/
|
|
13
|
+
detect(projectPath = process.cwd()) {
|
|
14
|
+
const results = {
|
|
15
|
+
hasGit: false,
|
|
16
|
+
isGitHub: false,
|
|
17
|
+
owner: null,
|
|
18
|
+
repo: null,
|
|
19
|
+
url: null,
|
|
20
|
+
branch: null,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Check if .git directory exists
|
|
24
|
+
const gitDir = path.join(projectPath, '.git');
|
|
25
|
+
if (!require('fs').existsSync(gitDir)) {
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
results.hasGit = true;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Get git remote URL
|
|
33
|
+
const remoteUrl = execSync('git config --get remote.origin.url', {
|
|
34
|
+
cwd: projectPath,
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
37
|
+
}).trim();
|
|
38
|
+
|
|
39
|
+
if (!remoteUrl) {
|
|
40
|
+
return results;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Parse GitHub URL (supports both HTTPS and SSH formats)
|
|
44
|
+
// https://github.com/owner/repo.git
|
|
45
|
+
// git@github.com:owner/repo.git
|
|
46
|
+
const githubMatch = remoteUrl.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
47
|
+
|
|
48
|
+
if (githubMatch) {
|
|
49
|
+
results.isGitHub = true;
|
|
50
|
+
results.owner = githubMatch[1];
|
|
51
|
+
results.repo = githubMatch[2].replace(/\.git$/, '');
|
|
52
|
+
results.url = `https://github.com/${results.owner}/${results.repo}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Get current branch
|
|
56
|
+
try {
|
|
57
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
58
|
+
cwd: projectPath,
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
61
|
+
}).trim();
|
|
62
|
+
results.branch = branch;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
// Couldn't get branch
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Error reading git config
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = new GitHubDetector();
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Detectors
|
|
3
|
+
* Main entry point for all project detection
|
|
4
|
+
*
|
|
5
|
+
* This module orchestrates both framework detection (Next.js, React, etc.)
|
|
6
|
+
* and project metadata detection (GitHub, API clients, etc.)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const registry = require('./registry');
|
|
10
|
+
const githubDetector = require('./github-detector');
|
|
11
|
+
const apiClientDetector = require('./api-client-detector');
|
|
12
|
+
const oauthDetector = require('./oauth-detector');
|
|
13
|
+
|
|
14
|
+
class ProjectDetector {
|
|
15
|
+
/**
|
|
16
|
+
* Run all detectors and return combined results
|
|
17
|
+
*
|
|
18
|
+
* @param {string} projectPath - Path to project directory
|
|
19
|
+
* @returns {object} Combined detection results
|
|
20
|
+
*/
|
|
21
|
+
detect(projectPath = process.cwd()) {
|
|
22
|
+
// Detect framework/project type
|
|
23
|
+
const frameworkDetection = registry.detectProjectType(projectPath);
|
|
24
|
+
|
|
25
|
+
// Detect project metadata (framework-agnostic)
|
|
26
|
+
const githubInfo = githubDetector.detect(projectPath);
|
|
27
|
+
const apiClientInfo = apiClientDetector.detect(projectPath);
|
|
28
|
+
const oauthInfo = oauthDetector.detect(projectPath);
|
|
29
|
+
|
|
30
|
+
// Get detector instance if we have a match
|
|
31
|
+
const detector = frameworkDetection.detected
|
|
32
|
+
? registry.getDetector(frameworkDetection.detected)
|
|
33
|
+
: null;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
// Framework detection
|
|
37
|
+
framework: {
|
|
38
|
+
type: frameworkDetection.detected,
|
|
39
|
+
confidence: frameworkDetection.confidence,
|
|
40
|
+
metadata: frameworkDetection.metadata,
|
|
41
|
+
supportedFeatures: detector?.getSupportedFeatures() || {},
|
|
42
|
+
availableGenerators: detector?.getAvailableGenerators() || [],
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// Project metadata (framework-agnostic)
|
|
46
|
+
github: githubInfo,
|
|
47
|
+
apiClient: apiClientInfo,
|
|
48
|
+
oauth: oauthInfo,
|
|
49
|
+
|
|
50
|
+
// Raw detection results (for debugging)
|
|
51
|
+
_raw: {
|
|
52
|
+
frameworkResults: frameworkDetection.allResults,
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Project path
|
|
56
|
+
projectPath,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get detector for a specific project type
|
|
62
|
+
*
|
|
63
|
+
* @param {string} type - Project type name (e.g., 'nextjs')
|
|
64
|
+
* @returns {object|null} Detector instance or null
|
|
65
|
+
*/
|
|
66
|
+
getDetector(type) {
|
|
67
|
+
return registry.getDetector(type);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get all available project types
|
|
72
|
+
*
|
|
73
|
+
* @returns {array} Array of detector names
|
|
74
|
+
*/
|
|
75
|
+
getAvailableTypes() {
|
|
76
|
+
return registry.getAllDetectors().map(d => d.name);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = new ProjectDetector();
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js Project Detector
|
|
3
|
+
* Detects Next.js projects and their configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const BaseDetector = require('./base-detector');
|
|
9
|
+
|
|
10
|
+
class NextJsDetector extends BaseDetector {
|
|
11
|
+
get name() {
|
|
12
|
+
return 'nextjs';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get priority() {
|
|
16
|
+
return 100; // High priority - very specific framework
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detect if current directory is a Next.js project
|
|
21
|
+
*/
|
|
22
|
+
detect(projectPath = process.cwd()) {
|
|
23
|
+
const results = {
|
|
24
|
+
isNextJs: false,
|
|
25
|
+
version: null,
|
|
26
|
+
routerType: null, // 'app' or 'pages'
|
|
27
|
+
hasTypeScript: false,
|
|
28
|
+
config: null,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Check for package.json
|
|
32
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
33
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
39
|
+
const dependencies = {
|
|
40
|
+
...packageJson.dependencies,
|
|
41
|
+
...packageJson.devDependencies,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Check if Next.js is installed
|
|
45
|
+
if (dependencies.next) {
|
|
46
|
+
results.isNextJs = true;
|
|
47
|
+
results.version = dependencies.next;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check for TypeScript
|
|
51
|
+
if (dependencies.typescript || fs.existsSync(path.join(projectPath, 'tsconfig.json'))) {
|
|
52
|
+
results.hasTypeScript = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Detect router type (App Router vs Pages Router)
|
|
56
|
+
const appDir = path.join(projectPath, 'app');
|
|
57
|
+
const pagesDir = path.join(projectPath, 'pages');
|
|
58
|
+
const srcAppDir = path.join(projectPath, 'src', 'app');
|
|
59
|
+
const srcPagesDir = path.join(projectPath, 'src', 'pages');
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(appDir) || fs.existsSync(srcAppDir)) {
|
|
62
|
+
results.routerType = 'app';
|
|
63
|
+
} else if (fs.existsSync(pagesDir) || fs.existsSync(srcPagesDir)) {
|
|
64
|
+
results.routerType = 'pages';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Read Next.js config if exists
|
|
68
|
+
const configFiles = [
|
|
69
|
+
'next.config.js',
|
|
70
|
+
'next.config.mjs',
|
|
71
|
+
'next.config.ts',
|
|
72
|
+
'next.config.cjs',
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
for (const configFile of configFiles) {
|
|
76
|
+
const configPath = path.join(projectPath, configFile);
|
|
77
|
+
if (fs.existsSync(configPath)) {
|
|
78
|
+
try {
|
|
79
|
+
// For .js/.mjs/.cjs files, we'd need to require them
|
|
80
|
+
// For now, just note that config exists
|
|
81
|
+
results.config = configFile;
|
|
82
|
+
break;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Config file exists but couldn't be read
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// Error reading package.json
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Return in BaseDetector format
|
|
93
|
+
if (!results.isNextJs) {
|
|
94
|
+
return {
|
|
95
|
+
detected: false,
|
|
96
|
+
confidence: 0,
|
|
97
|
+
metadata: {},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
detected: true,
|
|
103
|
+
confidence: 0.95, // High confidence for Next.js detection
|
|
104
|
+
metadata: {
|
|
105
|
+
version: results.version,
|
|
106
|
+
routerType: results.routerType,
|
|
107
|
+
hasTypeScript: results.hasTypeScript,
|
|
108
|
+
config: results.config,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get supported features for Next.js
|
|
115
|
+
*/
|
|
116
|
+
getSupportedFeatures() {
|
|
117
|
+
return {
|
|
118
|
+
oauth: true, // Full NextAuth.js support
|
|
119
|
+
stripe: true, // Full Stripe support
|
|
120
|
+
crm: true, // Full CRM support
|
|
121
|
+
projects: true, // Full Projects support
|
|
122
|
+
invoices: true, // Full Invoices support
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get available generators for Next.js
|
|
128
|
+
*/
|
|
129
|
+
getAvailableGenerators() {
|
|
130
|
+
return [
|
|
131
|
+
'api-client',
|
|
132
|
+
'env',
|
|
133
|
+
'nextauth', // Next.js-specific
|
|
134
|
+
'oauth-guide',
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = new NextJsDetector();
|