@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,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login Command
|
|
3
|
+
* Handles CLI authentication via browser OAuth flow
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { default: open } = require('open');
|
|
7
|
+
const configManager = require('../config/config-manager');
|
|
8
|
+
const backendClient = require('../api/backend-client');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Start local server to receive OAuth callback
|
|
13
|
+
*/
|
|
14
|
+
function startCallbackServer() {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const http = require('http');
|
|
17
|
+
|
|
18
|
+
const server = http.createServer((req, res) => {
|
|
19
|
+
const url = new URL(req.url, 'http://localhost:3001');
|
|
20
|
+
|
|
21
|
+
if (url.pathname === '/callback') {
|
|
22
|
+
const token = url.searchParams.get('token');
|
|
23
|
+
|
|
24
|
+
if (token) {
|
|
25
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
26
|
+
res.end(`
|
|
27
|
+
<html>
|
|
28
|
+
<head><title>CLI Login Success</title></head>
|
|
29
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
30
|
+
<h1 style="color: #9F7AEA;">✅ Successfully logged in!</h1>
|
|
31
|
+
<p>You can close this window and return to your terminal.</p>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
`);
|
|
35
|
+
|
|
36
|
+
server.close();
|
|
37
|
+
resolve(token);
|
|
38
|
+
} else {
|
|
39
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
40
|
+
res.end(`
|
|
41
|
+
<html>
|
|
42
|
+
<head><title>CLI Login Error</title></head>
|
|
43
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
44
|
+
<h1 style="color: #EF4444;">❌ Login failed</h1>
|
|
45
|
+
<p>No token received. Please try again.</p>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
48
|
+
`);
|
|
49
|
+
|
|
50
|
+
server.close();
|
|
51
|
+
reject(new Error('No token received'));
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
res.writeHead(404);
|
|
55
|
+
res.end('Not found');
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
server.listen(3001, 'localhost', () => {
|
|
60
|
+
console.log(chalk.gray(' Waiting for authentication...'));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Timeout after 5 minutes
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
server.close();
|
|
66
|
+
reject(new Error('Login timeout - please try again'));
|
|
67
|
+
}, 5 * 60 * 1000);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Login command handler
|
|
73
|
+
*/
|
|
74
|
+
async function handleLogin() {
|
|
75
|
+
try {
|
|
76
|
+
// Check if already logged in
|
|
77
|
+
if (configManager.isLoggedIn()) {
|
|
78
|
+
const session = configManager.getSession();
|
|
79
|
+
console.log(chalk.yellow(' ⚠️ You are already logged in'));
|
|
80
|
+
console.log(chalk.gray(` Email: ${session.email}`));
|
|
81
|
+
console.log(chalk.gray(` Session expires: ${new Date(session.expiresAt).toLocaleString()}`));
|
|
82
|
+
console.log(chalk.gray('\n Run "l4yercak3 logout" to log out first\n'));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(chalk.cyan(' 🔐 Opening browser for authentication...\n'));
|
|
87
|
+
|
|
88
|
+
// Start callback server
|
|
89
|
+
const callbackPromise = startCallbackServer();
|
|
90
|
+
|
|
91
|
+
// Open browser
|
|
92
|
+
const loginUrl = backendClient.getLoginUrl();
|
|
93
|
+
console.log(chalk.gray(` Login URL: ${loginUrl}\n`));
|
|
94
|
+
|
|
95
|
+
await open(loginUrl);
|
|
96
|
+
|
|
97
|
+
// Wait for callback
|
|
98
|
+
const token = await callbackPromise;
|
|
99
|
+
|
|
100
|
+
// Validate token and get user info
|
|
101
|
+
console.log(chalk.gray(' Validating session...'));
|
|
102
|
+
|
|
103
|
+
// Save session
|
|
104
|
+
// Note: In real implementation, backend would return full session data
|
|
105
|
+
const session = {
|
|
106
|
+
token,
|
|
107
|
+
expiresAt: Date.now() + (30 * 24 * 60 * 60 * 1000), // 30 days
|
|
108
|
+
// Backend should return: userId, email, etc.
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
configManager.saveSession(session);
|
|
112
|
+
|
|
113
|
+
// Validate session with backend
|
|
114
|
+
try {
|
|
115
|
+
const userInfo = await backendClient.validateSession();
|
|
116
|
+
if (userInfo) {
|
|
117
|
+
configManager.saveSession({
|
|
118
|
+
...session,
|
|
119
|
+
userId: userInfo.userId,
|
|
120
|
+
email: userInfo.email,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.log(chalk.yellow(` ⚠️ Warning: Could not validate session: ${error.message}`));
|
|
125
|
+
console.log(chalk.gray(' Session saved, but validation failed. You may need to log in again.'));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log(chalk.green('\n ✅ Successfully logged in!\n'));
|
|
129
|
+
|
|
130
|
+
const finalSession = configManager.getSession();
|
|
131
|
+
if (finalSession && finalSession.email) {
|
|
132
|
+
console.log(chalk.gray(` Logged in as: ${finalSession.email}`));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(chalk.red(`\n ❌ Login failed: ${error.message}\n`));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
command: 'login',
|
|
143
|
+
description: 'Authenticate with L4YERCAK3 platform',
|
|
144
|
+
handler: handleLogin,
|
|
145
|
+
};
|
|
146
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logout Command
|
|
3
|
+
* Clears CLI session
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const configManager = require('../config/config-manager');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
async function handleLogout() {
|
|
10
|
+
if (!configManager.isLoggedIn()) {
|
|
11
|
+
console.log(chalk.yellow(' ⚠️ You are not logged in\n'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
configManager.clearSession();
|
|
16
|
+
console.log(chalk.green(' ✅ Successfully logged out\n'));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
command: 'logout',
|
|
21
|
+
description: 'Log out from L4YERCAK3 platform',
|
|
22
|
+
handler: handleLogout,
|
|
23
|
+
};
|
|
24
|
+
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spread Command
|
|
3
|
+
* Main command for setting up boilerplate integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const configManager = require('../config/config-manager');
|
|
7
|
+
const backendClient = require('../api/backend-client');
|
|
8
|
+
const projectDetector = require('../detectors');
|
|
9
|
+
const fileGenerator = require('../generators');
|
|
10
|
+
const inquirer = require('inquirer');
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Helper function to create an organization
|
|
16
|
+
*/
|
|
17
|
+
async function createOrganization(orgName) {
|
|
18
|
+
console.log(chalk.gray(` Creating organization "${orgName}"...`));
|
|
19
|
+
const newOrg = await backendClient.createOrganization(orgName);
|
|
20
|
+
// Handle different response formats
|
|
21
|
+
const organizationId = newOrg.organizationId || newOrg.id || newOrg.data?.organizationId || newOrg.data?.id;
|
|
22
|
+
const organizationName = newOrg.name || orgName;
|
|
23
|
+
|
|
24
|
+
if (!organizationId) {
|
|
25
|
+
throw new Error('Organization ID not found in response. Please check backend API endpoint.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(chalk.green(` ✅ Organization created: ${organizationName}\n`));
|
|
29
|
+
return { organizationId, organizationName };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function handleSpread() {
|
|
33
|
+
// Check if logged in
|
|
34
|
+
if (!configManager.isLoggedIn()) {
|
|
35
|
+
console.log(chalk.yellow(' ⚠️ You must be logged in first'));
|
|
36
|
+
console.log(chalk.gray('\n Run "l4yercak3 login" to authenticate\n'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(chalk.cyan(' 🍰 Setting up your Layer Cake integration...\n'));
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Step 1: Detect project
|
|
44
|
+
console.log(chalk.gray(' 🔍 Detecting project...\n'));
|
|
45
|
+
const detection = projectDetector.detect();
|
|
46
|
+
|
|
47
|
+
// Display framework detection results
|
|
48
|
+
if (detection.framework.type) {
|
|
49
|
+
const frameworkName = detection.framework.type === 'nextjs' ? 'Next.js' : detection.framework.type;
|
|
50
|
+
console.log(chalk.green(` ✅ Detected ${frameworkName} project`));
|
|
51
|
+
|
|
52
|
+
if (detection.framework.type === 'nextjs') {
|
|
53
|
+
const meta = detection.framework.metadata;
|
|
54
|
+
if (meta.version) {
|
|
55
|
+
console.log(chalk.gray(` Version: ${meta.version}`));
|
|
56
|
+
}
|
|
57
|
+
if (meta.routerType) {
|
|
58
|
+
console.log(chalk.gray(` Router: ${meta.routerType === 'app' ? 'App Router' : 'Pages Router'}`));
|
|
59
|
+
}
|
|
60
|
+
if (meta.hasTypeScript) {
|
|
61
|
+
console.log(chalk.gray(' TypeScript: Yes'));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Show supported features
|
|
66
|
+
const features = detection.framework.supportedFeatures;
|
|
67
|
+
const supportedFeatures = Object.entries(features)
|
|
68
|
+
.filter(([_, supported]) => supported === true || supported === 'manual')
|
|
69
|
+
.map(([name]) => name);
|
|
70
|
+
|
|
71
|
+
if (supportedFeatures.length > 0) {
|
|
72
|
+
console.log(chalk.gray(` Supported features: ${supportedFeatures.join(', ')}`));
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
console.log(chalk.yellow(' ⚠️ Could not detect project type'));
|
|
76
|
+
const { continueAnyway } = await inquirer.prompt([
|
|
77
|
+
{
|
|
78
|
+
type: 'confirm',
|
|
79
|
+
name: 'continueAnyway',
|
|
80
|
+
message: 'Continue with basic setup anyway?',
|
|
81
|
+
default: false,
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
if (!continueAnyway) {
|
|
86
|
+
console.log(chalk.gray('\n Setup cancelled.\n'));
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Display GitHub detection
|
|
92
|
+
if (detection.github.isGitHub) {
|
|
93
|
+
console.log(chalk.green(` ✅ Detected GitHub repository: ${detection.github.owner}/${detection.github.repo}`));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Display API client detection
|
|
97
|
+
if (detection.apiClient.hasApiClient) {
|
|
98
|
+
console.log(chalk.yellow(` ⚠️ Existing API client found: ${detection.apiClient.clientPath}`));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Display OAuth detection
|
|
102
|
+
if (detection.oauth.hasOAuth) {
|
|
103
|
+
console.log(chalk.yellow(` ⚠️ Existing OAuth setup detected: ${detection.oauth.oauthType}`));
|
|
104
|
+
if (detection.oauth.providers.length > 0) {
|
|
105
|
+
console.log(chalk.gray(` Providers: ${detection.oauth.providers.join(', ')}`));
|
|
106
|
+
}
|
|
107
|
+
if (detection.oauth.configPath) {
|
|
108
|
+
console.log(chalk.gray(` Config: ${detection.oauth.configPath}`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log('');
|
|
113
|
+
|
|
114
|
+
// Step 2: Organization selection
|
|
115
|
+
console.log(chalk.cyan(' 📦 Organization Setup\n'));
|
|
116
|
+
let organizationId;
|
|
117
|
+
let organizationName;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const orgsResponse = await backendClient.getOrganizations();
|
|
121
|
+
// Handle different response formats
|
|
122
|
+
const organizations = Array.isArray(orgsResponse)
|
|
123
|
+
? orgsResponse
|
|
124
|
+
: orgsResponse.organizations || orgsResponse.data || [];
|
|
125
|
+
|
|
126
|
+
if (organizations.length === 0) {
|
|
127
|
+
// No organizations, create one
|
|
128
|
+
const { orgName } = await inquirer.prompt([
|
|
129
|
+
{
|
|
130
|
+
type: 'input',
|
|
131
|
+
name: 'orgName',
|
|
132
|
+
message: 'Organization name:',
|
|
133
|
+
default: detection.github.repo || 'My Organization',
|
|
134
|
+
validate: (input) => input.trim().length > 0 || 'Organization name is required',
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const result = await createOrganization(orgName);
|
|
139
|
+
organizationId = result.organizationId;
|
|
140
|
+
organizationName = result.organizationName;
|
|
141
|
+
} else {
|
|
142
|
+
// Select or create organization
|
|
143
|
+
const { orgChoice } = await inquirer.prompt([
|
|
144
|
+
{
|
|
145
|
+
type: 'list',
|
|
146
|
+
name: 'orgChoice',
|
|
147
|
+
message: 'Select organization:',
|
|
148
|
+
choices: [
|
|
149
|
+
...organizations.map(org => ({
|
|
150
|
+
name: `${org.name} (${org.id})`,
|
|
151
|
+
value: org.id,
|
|
152
|
+
})),
|
|
153
|
+
{ name: '➕ Create new organization', value: '__create__' },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
if (orgChoice === '__create__') {
|
|
159
|
+
const { orgName } = await inquirer.prompt([
|
|
160
|
+
{
|
|
161
|
+
type: 'input',
|
|
162
|
+
name: 'orgName',
|
|
163
|
+
message: 'Organization name:',
|
|
164
|
+
default: detection.github.repo || 'My Organization',
|
|
165
|
+
validate: (input) => input.trim().length > 0 || 'Organization name is required',
|
|
166
|
+
},
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
const result = await createOrganization(orgName);
|
|
170
|
+
organizationId = result.organizationId;
|
|
171
|
+
organizationName = result.organizationName;
|
|
172
|
+
} else {
|
|
173
|
+
const selectedOrg = organizations.find(org => org.id === orgChoice);
|
|
174
|
+
organizationId = orgChoice;
|
|
175
|
+
organizationName = selectedOrg.name;
|
|
176
|
+
console.log(chalk.green(` ✅ Selected organization: ${organizationName}\n`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(chalk.red(` ❌ Error managing organizations: ${error.message}\n`));
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Step 3: Generate API key
|
|
185
|
+
console.log(chalk.cyan(' 🔑 API Key Setup\n'));
|
|
186
|
+
let apiKey;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
console.log(chalk.gray(' Generating API key...'));
|
|
190
|
+
const apiKeyResponse = await backendClient.generateApiKey(
|
|
191
|
+
organizationId,
|
|
192
|
+
'CLI Generated Key',
|
|
193
|
+
['*']
|
|
194
|
+
);
|
|
195
|
+
// Handle different response formats
|
|
196
|
+
apiKey = apiKeyResponse.key || apiKeyResponse.apiKey || apiKeyResponse.data?.key || apiKeyResponse.data?.apiKey;
|
|
197
|
+
|
|
198
|
+
if (!apiKey) {
|
|
199
|
+
throw new Error('API key not found in response. Please check backend API endpoint.');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(chalk.green(` ✅ API key generated\n`));
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(chalk.red(` ❌ Error generating API key: ${error.message}\n`));
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Step 4: Feature selection
|
|
209
|
+
console.log(chalk.cyan(' ⚙️ Feature Selection\n'));
|
|
210
|
+
const { features } = await inquirer.prompt([
|
|
211
|
+
{
|
|
212
|
+
type: 'checkbox',
|
|
213
|
+
name: 'features',
|
|
214
|
+
message: 'Select features to enable:',
|
|
215
|
+
choices: [
|
|
216
|
+
{ name: 'CRM (Contacts)', value: 'crm', checked: true },
|
|
217
|
+
{ name: 'Projects', value: 'projects', checked: true },
|
|
218
|
+
{ name: 'Invoices', value: 'invoices', checked: true },
|
|
219
|
+
{ name: 'OAuth Authentication', value: 'oauth', checked: false },
|
|
220
|
+
{ name: 'Stripe Integration', value: 'stripe', checked: false },
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
// Step 5: OAuth provider selection (if OAuth enabled)
|
|
226
|
+
let oauthProviders = [];
|
|
227
|
+
if (features.includes('oauth')) {
|
|
228
|
+
const { providers } = await inquirer.prompt([
|
|
229
|
+
{
|
|
230
|
+
type: 'checkbox',
|
|
231
|
+
name: 'providers',
|
|
232
|
+
message: 'Select OAuth providers:',
|
|
233
|
+
choices: [
|
|
234
|
+
{ name: 'Google', value: 'google', checked: true },
|
|
235
|
+
{ name: 'Microsoft', value: 'microsoft', checked: true },
|
|
236
|
+
{ name: 'GitHub', value: 'github', checked: false },
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
]);
|
|
240
|
+
oauthProviders = providers;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Step 6: Backend URL
|
|
244
|
+
const defaultBackendUrl = configManager.getBackendUrl();
|
|
245
|
+
const { backendUrl } = await inquirer.prompt([
|
|
246
|
+
{
|
|
247
|
+
type: 'input',
|
|
248
|
+
name: 'backendUrl',
|
|
249
|
+
message: 'Backend API URL:',
|
|
250
|
+
default: defaultBackendUrl,
|
|
251
|
+
validate: (input) => {
|
|
252
|
+
try {
|
|
253
|
+
new URL(input);
|
|
254
|
+
return true;
|
|
255
|
+
} catch {
|
|
256
|
+
return 'Please enter a valid URL';
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
// Step 7: Production domain (for OAuth redirect URIs)
|
|
263
|
+
let productionDomain = 'your-domain.com';
|
|
264
|
+
if (features.includes('oauth')) {
|
|
265
|
+
const { domain } = await inquirer.prompt([
|
|
266
|
+
{
|
|
267
|
+
type: 'input',
|
|
268
|
+
name: 'domain',
|
|
269
|
+
message: 'Production domain (for OAuth redirect URIs):',
|
|
270
|
+
default: detection.github.repo ? `${detection.github.repo}.vercel.app` : 'your-domain.com',
|
|
271
|
+
},
|
|
272
|
+
]);
|
|
273
|
+
productionDomain = domain;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Step 8: Generate files
|
|
277
|
+
console.log(chalk.cyan('\n 📝 Generating files...\n'));
|
|
278
|
+
|
|
279
|
+
// Extract framework metadata for generation
|
|
280
|
+
const frameworkMeta = detection.framework.metadata || {};
|
|
281
|
+
const isTypeScript = frameworkMeta.hasTypeScript || false;
|
|
282
|
+
const routerType = frameworkMeta.routerType || 'pages';
|
|
283
|
+
|
|
284
|
+
const generationOptions = {
|
|
285
|
+
projectPath: detection.projectPath,
|
|
286
|
+
apiKey,
|
|
287
|
+
backendUrl,
|
|
288
|
+
organizationId,
|
|
289
|
+
organizationName,
|
|
290
|
+
features,
|
|
291
|
+
oauthProviders,
|
|
292
|
+
productionDomain,
|
|
293
|
+
appName: detection.github.repo || organizationName,
|
|
294
|
+
isTypeScript,
|
|
295
|
+
routerType,
|
|
296
|
+
frameworkType: detection.framework.type || 'unknown',
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const generatedFiles = await fileGenerator.generate(generationOptions);
|
|
300
|
+
|
|
301
|
+
// Display results
|
|
302
|
+
console.log(chalk.green(' ✅ Files generated:\n'));
|
|
303
|
+
if (generatedFiles.apiClient) {
|
|
304
|
+
console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.apiClient)}`));
|
|
305
|
+
}
|
|
306
|
+
if (generatedFiles.envFile) {
|
|
307
|
+
console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.envFile)}`));
|
|
308
|
+
}
|
|
309
|
+
if (generatedFiles.nextauth) {
|
|
310
|
+
console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.nextauth)}`));
|
|
311
|
+
}
|
|
312
|
+
if (generatedFiles.oauthGuide) {
|
|
313
|
+
console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.oauthGuide)}`));
|
|
314
|
+
}
|
|
315
|
+
if (generatedFiles.gitignore) {
|
|
316
|
+
console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.gitignore)} (updated)`));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Save project configuration
|
|
320
|
+
const projectConfig = {
|
|
321
|
+
organizationId,
|
|
322
|
+
organizationName,
|
|
323
|
+
apiKey: `${apiKey.substring(0, 10)}...`, // Store partial key for reference only
|
|
324
|
+
backendUrl,
|
|
325
|
+
features,
|
|
326
|
+
oauthProviders,
|
|
327
|
+
productionDomain,
|
|
328
|
+
frameworkType: detection.framework.type,
|
|
329
|
+
createdAt: Date.now(),
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
configManager.saveProjectConfig(detection.projectPath, projectConfig);
|
|
333
|
+
console.log(chalk.gray(` 📝 Configuration saved to ~/.l4yercak3/config.json\n`));
|
|
334
|
+
|
|
335
|
+
console.log(chalk.cyan('\n 🎉 Setup complete!\n'));
|
|
336
|
+
|
|
337
|
+
if (features.includes('oauth')) {
|
|
338
|
+
console.log(chalk.yellow(' 📋 Next steps:\n'));
|
|
339
|
+
console.log(chalk.gray(' 1. Follow the OAuth setup guide (OAUTH_SETUP_GUIDE.md)'));
|
|
340
|
+
console.log(chalk.gray(' 2. Add OAuth credentials to .env.local'));
|
|
341
|
+
console.log(chalk.gray(' 3. Install NextAuth.js: npm install next-auth'));
|
|
342
|
+
if (oauthProviders.includes('microsoft')) {
|
|
343
|
+
console.log(chalk.gray(' 4. Install Azure AD provider: npm install next-auth/providers/azure-ad'));
|
|
344
|
+
}
|
|
345
|
+
console.log('');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
console.log(chalk.gray(' Your project is now connected to L4YERCAK3! 🍰\n'));
|
|
349
|
+
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error(chalk.red(`\n ❌ Error: ${error.message}\n`));
|
|
352
|
+
if (error.stack) {
|
|
353
|
+
console.error(chalk.gray(error.stack));
|
|
354
|
+
}
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
module.exports = {
|
|
360
|
+
command: 'spread',
|
|
361
|
+
description: 'Initialize a new project integration',
|
|
362
|
+
handler: handleSpread,
|
|
363
|
+
};
|
|
364
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Command
|
|
3
|
+
* Shows authentication status and user info
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const configManager = require('../config/config-manager');
|
|
7
|
+
const backendClient = require('../api/backend-client');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
async function handleStatus() {
|
|
11
|
+
const isLoggedIn = configManager.isLoggedIn();
|
|
12
|
+
const session = configManager.getSession();
|
|
13
|
+
|
|
14
|
+
console.log(chalk.bold(' Authentication Status\n'));
|
|
15
|
+
|
|
16
|
+
if (!isLoggedIn) {
|
|
17
|
+
console.log(chalk.red(' ❌ Not logged in'));
|
|
18
|
+
console.log(chalk.gray('\n Run "l4yercak3 login" to authenticate\n'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(chalk.green(' ✅ Logged in\n'));
|
|
23
|
+
|
|
24
|
+
if (session) {
|
|
25
|
+
if (session.email) {
|
|
26
|
+
console.log(chalk.gray(` Email: ${session.email}`));
|
|
27
|
+
}
|
|
28
|
+
if (session.expiresAt) {
|
|
29
|
+
const expiresDate = new Date(session.expiresAt);
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const daysLeft = Math.floor((session.expiresAt - now) / (24 * 60 * 60 * 1000));
|
|
32
|
+
|
|
33
|
+
if (daysLeft > 0) {
|
|
34
|
+
console.log(chalk.gray(` Session expires: ${expiresDate.toLocaleString()} (${daysLeft} days)`));
|
|
35
|
+
} else {
|
|
36
|
+
console.log(chalk.yellow(` ⚠️ Session expired: ${expiresDate.toLocaleString()}`));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Try to validate session with backend
|
|
42
|
+
try {
|
|
43
|
+
const userInfo = await backendClient.validateSession();
|
|
44
|
+
if (userInfo) {
|
|
45
|
+
console.log(chalk.gray(`\n Backend URL: ${configManager.getBackendUrl()}`));
|
|
46
|
+
if (userInfo.organizations && userInfo.organizations.length > 0) {
|
|
47
|
+
console.log(chalk.gray(` Organizations: ${userInfo.organizations.length}`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.log(chalk.yellow(`\n ⚠️ Could not validate session: ${error.message}`));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log('');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
command: 'status',
|
|
59
|
+
description: 'Show authentication status',
|
|
60
|
+
handler: handleStatus,
|
|
61
|
+
};
|
|
62
|
+
|