@axhub/genie 0.2.1 → 0.2.3
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/dist/assets/App-Ddoy7BIl.js +484 -0
- package/dist/assets/{ReviewApp-DIT2yWk-.js → ReviewApp-DOAHmxVe.js} +1 -1
- package/dist/assets/{_basePickBy-Dz3NcIVK.js → _basePickBy-CAs6HGVw.js} +1 -1
- package/dist/assets/{_baseUniq-DON_Sg7x.js → _baseUniq-qvm36g_v.js} +1 -1
- package/dist/assets/{arc-Y4G80q-l.js → arc-60yKSsVt.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-D_qR4657.js → architectureDiagram-2XIMDMQ5-C5Ik86xM.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-NsmAlV5_.js → blockDiagram-WCTKOSBZ-CGqfB5z7.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-cbOJM4yr.js → c4Diagram-IC4MRINW-C02h4qfL.js} +1 -1
- package/dist/assets/channel-B3JUshOm.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-bLBhl74J.js → chunk-4BX2VUAB-BVVwu3tU.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-D8kNkDUO.js → chunk-55IACEB6-Dyehrd1R.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BjR6UbXB.js → chunk-FMBD7UC4-ByEz0wOl.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-luNqWn64.js → chunk-JSJVCQXG-BXHZvm_e.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-CNnKm6dK.js → chunk-KX2RTZJC-DJPATr22.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-Cp9gb43u.js → chunk-NQ4KR5QH-RgZbucc4.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-HlVYo2Oq.js → chunk-QZHKN3VN-DMIXCLGh.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-CjSZoOGO.js → chunk-WL4C6EOR-Bg4bzqqP.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-DZEYhtwP.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-DZEYhtwP.js +1 -0
- package/dist/assets/clone-B2sPtF_O.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-DZWRjeEd.js → cose-bilkent-S5V4N54A-BZeDiX7Y.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-yAzUmqI7.js → dagre-KLK3FWXG-BrwI_1JI.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-CvzlIvDJ.js → diagram-E7M64L7V-CxAlKNFG.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-DFMIJpuM.js → diagram-IFDJBPK2-VClYIYo3.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-KL-J3gyb.js → diagram-P4PSJMXO-CYRL57cP.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-BXszHbTM.js → erDiagram-INFDFZHY-ZySJDqNG.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-Ba43NVp6.js → flowDiagram-PKNHOUZH-Ct9-q2zS.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-uLHfhCrg.js → ganttDiagram-A5KZAMGK-Qn4VUvVR.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BTEuFaiL.js → gitGraphDiagram-K3NZZRJ6-CaveIlg3.js} +1 -1
- package/dist/assets/{graph-h2nuWjx4.js → graph-B1tvcjpq.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-C6BY7XZJ.js → highlighted-body-TPN3WLV5-Cx4aCKGT.js} +1 -1
- package/dist/assets/index-BFX9lxRB.css +1 -0
- package/dist/assets/index-Q-dfaLIv.js +2 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-BOLfvCIq.js → infoDiagram-LFFYTUFH-FNMZEktA.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-BRzQ1ee5.js → ishikawaDiagram-PHBUUO56-D9db2Pov.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-DXm_VcMy.js → journeyDiagram-4ABVD52K-BzYKMYOM.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-D_oyzopl.js → kanban-definition-K7BYSVSG-qC9Xmf0j.js} +1 -1
- package/dist/assets/{layout-Q8YoR_E1.js → layout-dbzqH_4Q.js} +1 -1
- package/dist/assets/{linear-B3qNg7di.js → linear-HOAktq-3.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-BVZ_4MKo.js → mermaid-O7DHMXV3-CazKksAH.js} +5 -5
- package/dist/assets/{mindmap-definition-YRQLILUH-CjulgYdi.js → mindmap-definition-YRQLILUH-DlfKip6Z.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-8VzrefxA.js → pieDiagram-SKSYHLDU-BYv9DU8d.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-CFh-ijm2.js → quadrantDiagram-337W2JSQ-l21TFCGi.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-BNPlTs5Q.js → requirementDiagram-Z7DCOOCP-DwM1YiVc.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-C5l_hYst.js → sankeyDiagram-WA2Y5GQK-DaSSXYkb.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-B4a_rQw8.js → sequenceDiagram-2WXFIKYE-BPBhAefs.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-Bt4mMmKB.js → stateDiagram-RAJIS63D-Cs4xILlc.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-DeUj7MLk.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-CLYvSw_R.js → timeline-definition-YZTLITO2-BPnQPJ2R.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-ksND0hZK.js → treemap-KZPCXAKY-BRWkr5mn.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-CaQg4oZK.js → vennDiagram-LZ73GAT5-DioBg_XD.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-C8dCbTeM.js → xychartDiagram-JWTSCODW-Bh2R2Uzh.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/server/cli.js +1 -1
- package/server/database/db.js +0 -45
- package/server/external-agent/service.js +39 -490
- package/server/external-agent/service.test.js +12 -0
- package/server/index.js +21 -43
- package/server/middleware/auth.js +15 -1
- package/server/routes/agent.js +15 -1136
- package/server/routes/cli-auth.js +60 -52
- package/server/routes/projects.js +10 -304
- package/server/routes/settings.js +4 -0
- package/server/routes/user.js +0 -102
- package/dist/assets/App-BfaNALgf.js +0 -504
- package/dist/assets/channel-C6KNnXlA.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-BQlzzlH7.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-BQlzzlH7.js +0 -1
- package/dist/assets/clone-DMxS3qWP.js +0 -1
- package/dist/assets/index-2198VgsK.css +0 -1
- package/dist/assets/index-C6Bb2jGF.js +0 -2
- package/dist/assets/stateDiagram-v2-FVOUBMTO-6NYMazfq.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import {
|
|
2
|
+
import { constants as fsConstants } from 'fs';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import os from 'os';
|
|
@@ -26,64 +26,72 @@ function getLocatorCommand() {
|
|
|
26
26
|
return process.platform === 'win32' ? 'where' : 'which';
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
childProcess = spawn(getLocatorCommand(), [command], {
|
|
37
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
38
|
-
});
|
|
39
|
-
} catch (error) {
|
|
40
|
-
resolve({
|
|
41
|
-
found: false,
|
|
42
|
-
resolvedPath: null,
|
|
43
|
-
reason: `Failed to run command locator: ${error.message}`
|
|
44
|
-
});
|
|
45
|
-
return;
|
|
29
|
+
async function isRunnableCommand(candidatePath) {
|
|
30
|
+
try {
|
|
31
|
+
if (process.platform === 'win32') {
|
|
32
|
+
await fs.access(candidatePath, fsConstants.F_OK);
|
|
33
|
+
return true;
|
|
46
34
|
}
|
|
47
35
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
await fs.access(candidatePath, fsConstants.X_OK);
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
async function resolveCommandPath(command) {
|
|
44
|
+
const normalizedCommand = String(command || '').trim();
|
|
45
|
+
if (!normalizedCommand) {
|
|
46
|
+
return {
|
|
47
|
+
found: false,
|
|
48
|
+
resolvedPath: null,
|
|
49
|
+
reason: 'Command name is empty'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
53
|
+
const isDirectPath = normalizedCommand.includes(path.sep) || (process.platform === 'win32' && normalizedCommand.includes('/'));
|
|
54
|
+
const pathEntries = isDirectPath
|
|
55
|
+
? ['']
|
|
56
|
+
: String(process.env.PATH || '')
|
|
57
|
+
.split(path.delimiter)
|
|
58
|
+
.map((entry) => entry.trim())
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
|
|
61
|
+
const windowsExtensions = process.platform === 'win32'
|
|
62
|
+
? String(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
|
|
63
|
+
.split(';')
|
|
64
|
+
.map((ext) => ext.trim())
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
: [''];
|
|
67
|
+
|
|
68
|
+
const candidatePaths = [];
|
|
69
|
+
for (const baseDir of pathEntries) {
|
|
70
|
+
const baseCandidate = isDirectPath ? normalizedCommand : path.join(baseDir, normalizedCommand);
|
|
71
|
+
candidatePaths.push(baseCandidate);
|
|
72
|
+
|
|
73
|
+
if (process.platform === 'win32' && !path.extname(baseCandidate)) {
|
|
74
|
+
for (const ext of windowsExtensions) {
|
|
75
|
+
candidatePaths.push(`${baseCandidate}${ext}`);
|
|
70
76
|
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
for (const candidatePath of candidatePaths) {
|
|
81
|
+
if (await isRunnableCommand(candidatePath)) {
|
|
82
|
+
return {
|
|
83
|
+
found: true,
|
|
84
|
+
resolvedPath: candidatePath,
|
|
85
|
+
reason: null
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
});
|
|
90
|
+
return {
|
|
91
|
+
found: false,
|
|
92
|
+
resolvedPath: null,
|
|
93
|
+
reason: `${normalizedCommand} not found in PATH`
|
|
94
|
+
};
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
function buildInstallationStatus(provider, command, installed, resolvedPath, reason, cacheHit = false) {
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { spawn } from 'child_process';
|
|
5
4
|
import os from 'os';
|
|
6
5
|
import { addProjectManually } from '../projects.js';
|
|
7
|
-
import { githubTokensDb } from '../database/db.js';
|
|
8
6
|
|
|
9
7
|
const router = express.Router();
|
|
10
8
|
|
|
11
|
-
function sanitizeGitError(message, token) {
|
|
12
|
-
if (!message || !token) return message;
|
|
13
|
-
return message.replace(new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '***');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
9
|
// Configure allowed workspace root (defaults to user's home directory)
|
|
17
10
|
export const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
|
|
18
11
|
|
|
@@ -169,9 +162,6 @@ export async function validateWorkspacePath(requestedPath) {
|
|
|
169
162
|
* Body:
|
|
170
163
|
* - workspaceType: 'existing' | 'new'
|
|
171
164
|
* - path: string (workspace path)
|
|
172
|
-
* - githubUrl?: string (optional, for new workspaces)
|
|
173
|
-
* - githubTokenId?: number (optional, ID of stored token)
|
|
174
|
-
* - newGithubToken?: string (optional, one-time token)
|
|
175
165
|
*/
|
|
176
166
|
router.post('/create-workspace', async (req, res) => {
|
|
177
167
|
try {
|
|
@@ -182,6 +172,16 @@ router.post('/create-workspace', async (req, res) => {
|
|
|
182
172
|
return res.status(400).json({ error: 'workspaceType and path are required' });
|
|
183
173
|
}
|
|
184
174
|
|
|
175
|
+
if (
|
|
176
|
+
githubUrl !== undefined
|
|
177
|
+
|| githubTokenId !== undefined
|
|
178
|
+
|| newGithubToken !== undefined
|
|
179
|
+
) {
|
|
180
|
+
return res.status(400).json({
|
|
181
|
+
error: 'GitHub-based workspace creation has been removed. Create an empty workspace or add an existing local workspace instead.'
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
185
|
if (!['existing', 'new'].includes(workspaceType)) {
|
|
186
186
|
return res.status(400).json({ error: 'workspaceType must be "existing" or "new"' });
|
|
187
187
|
}
|
|
@@ -229,66 +229,6 @@ router.post('/create-workspace', async (req, res) => {
|
|
|
229
229
|
// Create the directory if it doesn't exist
|
|
230
230
|
await fs.mkdir(absolutePath, { recursive: true });
|
|
231
231
|
|
|
232
|
-
// If GitHub URL is provided, clone the repository
|
|
233
|
-
if (githubUrl) {
|
|
234
|
-
let githubToken = null;
|
|
235
|
-
|
|
236
|
-
// Get GitHub token if needed
|
|
237
|
-
if (githubTokenId) {
|
|
238
|
-
// Fetch token from database
|
|
239
|
-
const token = await getGithubTokenById(githubTokenId, req.user.id);
|
|
240
|
-
if (!token) {
|
|
241
|
-
// Clean up created directory
|
|
242
|
-
await fs.rm(absolutePath, { recursive: true, force: true });
|
|
243
|
-
return res.status(404).json({ error: 'GitHub token not found' });
|
|
244
|
-
}
|
|
245
|
-
githubToken = token.github_token;
|
|
246
|
-
} else if (newGithubToken) {
|
|
247
|
-
githubToken = newGithubToken;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Extract repo name from URL for the clone destination
|
|
251
|
-
const normalizedUrl = githubUrl.replace(/\/+$/, '').replace(/\.git$/, '');
|
|
252
|
-
const repoName = normalizedUrl.split('/').pop() || 'repository';
|
|
253
|
-
const clonePath = path.join(absolutePath, repoName);
|
|
254
|
-
|
|
255
|
-
// Check if clone destination already exists to prevent data loss
|
|
256
|
-
try {
|
|
257
|
-
await fs.access(clonePath);
|
|
258
|
-
return res.status(409).json({
|
|
259
|
-
error: 'Directory already exists',
|
|
260
|
-
details: `The destination path "${clonePath}" already exists. Please choose a different location or remove the existing directory.`
|
|
261
|
-
});
|
|
262
|
-
} catch (err) {
|
|
263
|
-
// Directory doesn't exist, which is what we want
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Clone the repository into a subfolder
|
|
267
|
-
try {
|
|
268
|
-
await cloneGitHubRepository(githubUrl, clonePath, githubToken);
|
|
269
|
-
} catch (error) {
|
|
270
|
-
// Only clean up if clone created partial data (check if dir exists and is empty or partial)
|
|
271
|
-
try {
|
|
272
|
-
const stats = await fs.stat(clonePath);
|
|
273
|
-
if (stats.isDirectory()) {
|
|
274
|
-
await fs.rm(clonePath, { recursive: true, force: true });
|
|
275
|
-
}
|
|
276
|
-
} catch (cleanupError) {
|
|
277
|
-
// Directory doesn't exist or cleanup failed - ignore
|
|
278
|
-
}
|
|
279
|
-
throw new Error(`Failed to clone repository: ${error.message}`);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Add the cloned repo path to the project list
|
|
283
|
-
const project = await addProjectManually(clonePath);
|
|
284
|
-
|
|
285
|
-
return res.json({
|
|
286
|
-
success: true,
|
|
287
|
-
project,
|
|
288
|
-
message: 'New workspace created and repository cloned successfully'
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
|
|
292
232
|
// Add the new workspace to the project list (no clone)
|
|
293
233
|
const project = await addProjectManually(absolutePath);
|
|
294
234
|
|
|
@@ -308,238 +248,4 @@ router.post('/create-workspace', async (req, res) => {
|
|
|
308
248
|
}
|
|
309
249
|
});
|
|
310
250
|
|
|
311
|
-
/**
|
|
312
|
-
* Helper function to get GitHub token from database
|
|
313
|
-
*/
|
|
314
|
-
async function getGithubTokenById(tokenId, userId) {
|
|
315
|
-
const credential = githubTokensDb.getGithubTokenById(userId, tokenId);
|
|
316
|
-
|
|
317
|
-
// Return in the expected format (github_token field for compatibility)
|
|
318
|
-
if (credential) {
|
|
319
|
-
return {
|
|
320
|
-
...credential,
|
|
321
|
-
github_token: credential.credential_value
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Clone repository with progress streaming (SSE)
|
|
330
|
-
* GET /api/projects/clone-progress
|
|
331
|
-
*/
|
|
332
|
-
router.get('/clone-progress', async (req, res) => {
|
|
333
|
-
const { path: workspacePath, githubUrl, githubTokenId, newGithubToken } = req.query;
|
|
334
|
-
|
|
335
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
336
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
337
|
-
res.setHeader('Connection', 'keep-alive');
|
|
338
|
-
res.flushHeaders();
|
|
339
|
-
|
|
340
|
-
const sendEvent = (type, data) => {
|
|
341
|
-
res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`);
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
try {
|
|
345
|
-
if (!workspacePath || !githubUrl) {
|
|
346
|
-
sendEvent('error', { message: 'workspacePath and githubUrl are required' });
|
|
347
|
-
res.end();
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const validation = await validateWorkspacePath(workspacePath);
|
|
352
|
-
if (!validation.valid) {
|
|
353
|
-
sendEvent('error', { message: validation.error });
|
|
354
|
-
res.end();
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const absolutePath = validation.resolvedPath;
|
|
359
|
-
|
|
360
|
-
await fs.mkdir(absolutePath, { recursive: true });
|
|
361
|
-
|
|
362
|
-
let githubToken = null;
|
|
363
|
-
if (githubTokenId) {
|
|
364
|
-
const token = await getGithubTokenById(parseInt(githubTokenId), req.user.id);
|
|
365
|
-
if (!token) {
|
|
366
|
-
await fs.rm(absolutePath, { recursive: true, force: true });
|
|
367
|
-
sendEvent('error', { message: 'GitHub token not found' });
|
|
368
|
-
res.end();
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
githubToken = token.github_token;
|
|
372
|
-
} else if (newGithubToken) {
|
|
373
|
-
githubToken = newGithubToken;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const normalizedUrl = githubUrl.replace(/\/+$/, '').replace(/\.git$/, '');
|
|
377
|
-
const repoName = normalizedUrl.split('/').pop() || 'repository';
|
|
378
|
-
const clonePath = path.join(absolutePath, repoName);
|
|
379
|
-
|
|
380
|
-
// Check if clone destination already exists to prevent data loss
|
|
381
|
-
try {
|
|
382
|
-
await fs.access(clonePath);
|
|
383
|
-
sendEvent('error', { message: `Directory "${repoName}" already exists. Please choose a different location or remove the existing directory.` });
|
|
384
|
-
res.end();
|
|
385
|
-
return;
|
|
386
|
-
} catch (err) {
|
|
387
|
-
// Directory doesn't exist, which is what we want
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
let cloneUrl = githubUrl;
|
|
391
|
-
if (githubToken) {
|
|
392
|
-
try {
|
|
393
|
-
const url = new URL(githubUrl);
|
|
394
|
-
url.username = githubToken;
|
|
395
|
-
url.password = '';
|
|
396
|
-
cloneUrl = url.toString();
|
|
397
|
-
} catch (error) {
|
|
398
|
-
// SSH URL or invalid - use as-is
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
sendEvent('progress', { message: `Cloning into '${repoName}'...` });
|
|
403
|
-
|
|
404
|
-
const gitProcess = spawn('git', ['clone', '--progress', cloneUrl, clonePath], {
|
|
405
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
406
|
-
env: {
|
|
407
|
-
...process.env,
|
|
408
|
-
GIT_TERMINAL_PROMPT: '0'
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
let lastError = '';
|
|
413
|
-
|
|
414
|
-
gitProcess.stdout.on('data', (data) => {
|
|
415
|
-
const message = data.toString().trim();
|
|
416
|
-
if (message) {
|
|
417
|
-
sendEvent('progress', { message });
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
gitProcess.stderr.on('data', (data) => {
|
|
422
|
-
const message = data.toString().trim();
|
|
423
|
-
lastError = message;
|
|
424
|
-
if (message) {
|
|
425
|
-
sendEvent('progress', { message });
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
gitProcess.on('close', async (code) => {
|
|
430
|
-
if (code === 0) {
|
|
431
|
-
try {
|
|
432
|
-
const project = await addProjectManually(clonePath);
|
|
433
|
-
sendEvent('complete', { project, message: 'Repository cloned successfully' });
|
|
434
|
-
} catch (error) {
|
|
435
|
-
sendEvent('error', { message: `Clone succeeded but failed to add project: ${error.message}` });
|
|
436
|
-
}
|
|
437
|
-
} else {
|
|
438
|
-
const sanitizedError = sanitizeGitError(lastError, githubToken);
|
|
439
|
-
let errorMessage = 'Git clone failed';
|
|
440
|
-
if (lastError.includes('Authentication failed') || lastError.includes('could not read Username')) {
|
|
441
|
-
errorMessage = 'Authentication failed. Please check your credentials.';
|
|
442
|
-
} else if (lastError.includes('Repository not found')) {
|
|
443
|
-
errorMessage = 'Repository not found. Please check the URL and ensure you have access.';
|
|
444
|
-
} else if (lastError.includes('already exists')) {
|
|
445
|
-
errorMessage = 'Directory already exists';
|
|
446
|
-
} else if (sanitizedError) {
|
|
447
|
-
errorMessage = sanitizedError;
|
|
448
|
-
}
|
|
449
|
-
try {
|
|
450
|
-
await fs.rm(clonePath, { recursive: true, force: true });
|
|
451
|
-
} catch (cleanupError) {
|
|
452
|
-
console.error('Failed to clean up after clone failure:', sanitizeGitError(cleanupError.message, githubToken));
|
|
453
|
-
}
|
|
454
|
-
sendEvent('error', { message: errorMessage });
|
|
455
|
-
}
|
|
456
|
-
res.end();
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
gitProcess.on('error', (error) => {
|
|
460
|
-
if (error.code === 'ENOENT') {
|
|
461
|
-
sendEvent('error', { message: 'Git is not installed or not in PATH' });
|
|
462
|
-
} else {
|
|
463
|
-
sendEvent('error', { message: error.message });
|
|
464
|
-
}
|
|
465
|
-
res.end();
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
req.on('close', () => {
|
|
469
|
-
gitProcess.kill();
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
} catch (error) {
|
|
473
|
-
sendEvent('error', { message: error.message });
|
|
474
|
-
res.end();
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Helper function to clone a GitHub repository
|
|
480
|
-
*/
|
|
481
|
-
function cloneGitHubRepository(githubUrl, destinationPath, githubToken = null) {
|
|
482
|
-
return new Promise((resolve, reject) => {
|
|
483
|
-
let cloneUrl = githubUrl;
|
|
484
|
-
|
|
485
|
-
if (githubToken) {
|
|
486
|
-
try {
|
|
487
|
-
const url = new URL(githubUrl);
|
|
488
|
-
url.username = githubToken;
|
|
489
|
-
url.password = '';
|
|
490
|
-
cloneUrl = url.toString();
|
|
491
|
-
} catch (error) {
|
|
492
|
-
// SSH URL - use as-is
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const gitProcess = spawn('git', ['clone', '--progress', cloneUrl, destinationPath], {
|
|
497
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
498
|
-
env: {
|
|
499
|
-
...process.env,
|
|
500
|
-
GIT_TERMINAL_PROMPT: '0'
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
let stdout = '';
|
|
505
|
-
let stderr = '';
|
|
506
|
-
|
|
507
|
-
gitProcess.stdout.on('data', (data) => {
|
|
508
|
-
stdout += data.toString();
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
gitProcess.stderr.on('data', (data) => {
|
|
512
|
-
stderr += data.toString();
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
gitProcess.on('close', (code) => {
|
|
516
|
-
if (code === 0) {
|
|
517
|
-
resolve({ stdout, stderr });
|
|
518
|
-
} else {
|
|
519
|
-
let errorMessage = 'Git clone failed';
|
|
520
|
-
|
|
521
|
-
if (stderr.includes('Authentication failed') || stderr.includes('could not read Username')) {
|
|
522
|
-
errorMessage = 'Authentication failed. Please check your GitHub token.';
|
|
523
|
-
} else if (stderr.includes('Repository not found')) {
|
|
524
|
-
errorMessage = 'Repository not found. Please check the URL and ensure you have access.';
|
|
525
|
-
} else if (stderr.includes('already exists')) {
|
|
526
|
-
errorMessage = 'Directory already exists';
|
|
527
|
-
} else if (stderr) {
|
|
528
|
-
errorMessage = stderr;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
reject(new Error(errorMessage));
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
gitProcess.on('error', (error) => {
|
|
536
|
-
if (error.code === 'ENOENT') {
|
|
537
|
-
reject(new Error('Git is not installed or not in PATH'));
|
|
538
|
-
} else {
|
|
539
|
-
reject(error);
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
|
|
545
251
|
export default router;
|
|
@@ -117,6 +117,10 @@ router.post('/credentials', async (req, res) => {
|
|
|
117
117
|
return res.status(400).json({ error: 'Credential value is required' });
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
if (credentialType.trim() === 'github_token') {
|
|
121
|
+
return res.status(400).json({ error: 'GitHub tokens are no longer supported.' });
|
|
122
|
+
}
|
|
123
|
+
|
|
120
124
|
const result = credentialsDb.createCredential(
|
|
121
125
|
req.user.id,
|
|
122
126
|
credentialName.trim(),
|
package/server/routes/user.js
CHANGED
|
@@ -1,106 +1,4 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import { userDb } from '../database/db.js';
|
|
3
|
-
import { authenticateToken } from '../middleware/auth.js';
|
|
4
|
-
import { getSystemGitConfig } from '../utils/gitConfig.js';
|
|
5
|
-
import { exec } from 'child_process';
|
|
6
|
-
import { promisify } from 'util';
|
|
7
|
-
|
|
8
|
-
const execAsync = promisify(exec);
|
|
9
2
|
const router = express.Router();
|
|
10
3
|
|
|
11
|
-
router.get('/git-config', authenticateToken, async (req, res) => {
|
|
12
|
-
try {
|
|
13
|
-
const userId = req.user.id;
|
|
14
|
-
let gitConfig = userDb.getGitConfig(userId);
|
|
15
|
-
|
|
16
|
-
// If database is empty, try to get from system git config
|
|
17
|
-
if (!gitConfig || (!gitConfig.git_name && !gitConfig.git_email)) {
|
|
18
|
-
const systemConfig = await getSystemGitConfig();
|
|
19
|
-
|
|
20
|
-
// If system has values, save them to database for this user
|
|
21
|
-
if (systemConfig.git_name || systemConfig.git_email) {
|
|
22
|
-
userDb.updateGitConfig(userId, systemConfig.git_name, systemConfig.git_email);
|
|
23
|
-
gitConfig = systemConfig;
|
|
24
|
-
console.log(`Auto-populated git config from system for user ${userId}: ${systemConfig.git_name} <${systemConfig.git_email}>`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
res.json({
|
|
29
|
-
success: true,
|
|
30
|
-
gitName: gitConfig?.git_name || null,
|
|
31
|
-
gitEmail: gitConfig?.git_email || null
|
|
32
|
-
});
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error('Error getting git config:', error);
|
|
35
|
-
res.status(500).json({ error: 'Failed to get git configuration' });
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Apply git config globally via git config --global
|
|
40
|
-
router.post('/git-config', authenticateToken, async (req, res) => {
|
|
41
|
-
try {
|
|
42
|
-
const userId = req.user.id;
|
|
43
|
-
const { gitName, gitEmail } = req.body;
|
|
44
|
-
|
|
45
|
-
if (!gitName || !gitEmail) {
|
|
46
|
-
return res.status(400).json({ error: 'Git name and email are required' });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Validate email format
|
|
50
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
51
|
-
if (!emailRegex.test(gitEmail)) {
|
|
52
|
-
return res.status(400).json({ error: 'Invalid email format' });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
userDb.updateGitConfig(userId, gitName, gitEmail);
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
await execAsync(`git config --global user.name "${gitName.replace(/"/g, '\\"')}"`);
|
|
59
|
-
await execAsync(`git config --global user.email "${gitEmail.replace(/"/g, '\\"')}"`);
|
|
60
|
-
console.log(`Applied git config globally: ${gitName} <${gitEmail}>`);
|
|
61
|
-
} catch (gitError) {
|
|
62
|
-
console.error('Error applying git config:', gitError);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
res.json({
|
|
66
|
-
success: true,
|
|
67
|
-
gitName,
|
|
68
|
-
gitEmail
|
|
69
|
-
});
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.error('Error updating git config:', error);
|
|
72
|
-
res.status(500).json({ error: 'Failed to update git configuration' });
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
router.post('/complete-onboarding', authenticateToken, async (req, res) => {
|
|
77
|
-
try {
|
|
78
|
-
const userId = req.user.id;
|
|
79
|
-
userDb.completeOnboarding(userId);
|
|
80
|
-
|
|
81
|
-
res.json({
|
|
82
|
-
success: true,
|
|
83
|
-
message: 'Onboarding completed successfully'
|
|
84
|
-
});
|
|
85
|
-
} catch (error) {
|
|
86
|
-
console.error('Error completing onboarding:', error);
|
|
87
|
-
res.status(500).json({ error: 'Failed to complete onboarding' });
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
router.get('/onboarding-status', authenticateToken, async (req, res) => {
|
|
92
|
-
try {
|
|
93
|
-
const userId = req.user.id;
|
|
94
|
-
const hasCompleted = userDb.hasCompletedOnboarding(userId);
|
|
95
|
-
|
|
96
|
-
res.json({
|
|
97
|
-
success: true,
|
|
98
|
-
hasCompletedOnboarding: hasCompleted
|
|
99
|
-
});
|
|
100
|
-
} catch (error) {
|
|
101
|
-
console.error('Error checking onboarding status:', error);
|
|
102
|
-
res.status(500).json({ error: 'Failed to check onboarding status' });
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
4
|
export default router;
|