codetraxis 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/README.md +245 -0
- package/bin/cli.js +225 -0
- package/client/dist/assets/axios-vendor-B_3Om2-t.js +6 -0
- package/client/dist/assets/babel-vendor-B4E1Dfn4.js +818 -0
- package/client/dist/assets/index-BHUsNYUv.css +1 -0
- package/client/dist/assets/index-BMv7qJps.js +193 -0
- package/client/dist/assets/monaco-vendor-Bo5GWDbL.js +11 -0
- package/client/dist/assets/react-vendor-BhKDh-5n.js +49 -0
- package/client/dist/assets/redux-vendor-D-3X9xqH.js +9 -0
- package/client/dist/index.html +18 -0
- package/package.json +68 -0
- package/server/dist/index.js +62 -0
- package/server/dist/routes/agent.route.js +94 -0
- package/server/dist/routes/file.route.js +58 -0
- package/server/dist/routes/tree.route.js +13 -0
- package/server/dist/utils/agent/agentInstaller.js +1027 -0
- package/server/dist/utils/agent/debugHub.js +50 -0
- package/server/dist/utils/file/buildTree.js +79 -0
- package/server/dist/utils/file/gitignoreLoader.js +38 -0
- package/server/dist/utils/git/getGitStatus.js +58 -0
- package/server/dist/utils/watcher/gitignoreWatcher.js +22 -0
- package/server/dist/utils/watcher/watcherService.js +117 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupDebugHub = setupDebugHub;
|
|
4
|
+
const ws_1 = require("ws");
|
|
5
|
+
/**
|
|
6
|
+
* Два типа подключений:
|
|
7
|
+
* "agent" — target-проект слёт события (path: /agent)
|
|
8
|
+
* "viewer" — codetraxis client слушает (path: /debug)
|
|
9
|
+
*/
|
|
10
|
+
const viewerClients = new Set();
|
|
11
|
+
function setupDebugHub(httpServer) {
|
|
12
|
+
// ── WebSocket для агента (/agent) ─────────────────────────────────────────
|
|
13
|
+
const agentWss = new ws_1.WebSocketServer({ noServer: true });
|
|
14
|
+
agentWss.on("connection", (socket) => {
|
|
15
|
+
socket.on("message", (raw) => {
|
|
16
|
+
const data = raw.toString();
|
|
17
|
+
// Ретранслируем всем viewer-клиентам
|
|
18
|
+
for (const viewer of viewerClients) {
|
|
19
|
+
if (viewer.readyState === ws_1.WebSocket.OPEN) {
|
|
20
|
+
viewer.send(data);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
socket.on("close", () => { });
|
|
25
|
+
});
|
|
26
|
+
// ── WebSocket для viewer (/debug) ─────────────────────────────────────────
|
|
27
|
+
const viewerWss = new ws_1.WebSocketServer({ noServer: true });
|
|
28
|
+
viewerWss.on("connection", (socket) => {
|
|
29
|
+
viewerClients.add(socket);
|
|
30
|
+
socket.on("close", () => {
|
|
31
|
+
viewerClients.delete(socket);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
// ── HTTP upgrade routing ──────────────────────────────────────────────────
|
|
35
|
+
httpServer.on("upgrade", (req, socket, head) => {
|
|
36
|
+
const url = req.url ?? "";
|
|
37
|
+
if (url === "/agent") {
|
|
38
|
+
agentWss.handleUpgrade(req, socket, head, (ws) => {
|
|
39
|
+
agentWss.emit("connection", ws, req);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else if (url === "/debug") {
|
|
43
|
+
viewerWss.handleUpgrade(req, socket, head, (ws) => {
|
|
44
|
+
viewerWss.emit("connection", ws, req);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Все остальные пути (/ws и т.д.) — не трогаем,
|
|
48
|
+
// watcherService зарегистрирован с { path: "/ws" } и обработает сам.
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildTree = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const gitignoreLoader_1 = require("./gitignoreLoader");
|
|
10
|
+
const getGitStatus_1 = require("../git/getGitStatus");
|
|
11
|
+
// Агрегируем git-статус папки по дочерним элементам:
|
|
12
|
+
// если внутри есть изменения — папка тоже подсвечивается
|
|
13
|
+
const FOLDER_STATUS_PRIORITY = [
|
|
14
|
+
"conflicted",
|
|
15
|
+
"deleted",
|
|
16
|
+
"modified",
|
|
17
|
+
"added",
|
|
18
|
+
"renamed",
|
|
19
|
+
"untracked",
|
|
20
|
+
];
|
|
21
|
+
const aggregateFolderStatus = (children) => {
|
|
22
|
+
for (const status of FOLDER_STATUS_PRIORITY) {
|
|
23
|
+
if (hasStatus(children, status))
|
|
24
|
+
return status;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
};
|
|
28
|
+
const hasStatus = (nodes, status) => {
|
|
29
|
+
return nodes.some((n) => n.gitStatus === status ||
|
|
30
|
+
(n.children && hasStatus(n.children, status)));
|
|
31
|
+
};
|
|
32
|
+
const buildNodes = (dir, rootDir, ig, statusMap) => {
|
|
33
|
+
const items = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
34
|
+
return items
|
|
35
|
+
.filter((item) => {
|
|
36
|
+
const fullPath = node_path_1.default.join(dir, item.name);
|
|
37
|
+
const relativePath = node_path_1.default
|
|
38
|
+
.relative(rootDir, fullPath)
|
|
39
|
+
.replace(/\\/g, "/");
|
|
40
|
+
return !ig.ignores(relativePath);
|
|
41
|
+
})
|
|
42
|
+
.map((item) => {
|
|
43
|
+
const fullPath = node_path_1.default.join(dir, item.name);
|
|
44
|
+
const relativePath = node_path_1.default
|
|
45
|
+
.relative(rootDir, fullPath)
|
|
46
|
+
.replace(/\\/g, "/");
|
|
47
|
+
if (item.isDirectory()) {
|
|
48
|
+
const children = buildNodes(fullPath, rootDir, ig, statusMap);
|
|
49
|
+
const gitStatus = aggregateFolderStatus(children);
|
|
50
|
+
return {
|
|
51
|
+
name: item.name,
|
|
52
|
+
path: relativePath,
|
|
53
|
+
type: "folder",
|
|
54
|
+
...(gitStatus ? { gitStatus } : {}),
|
|
55
|
+
children,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const gitStatus = statusMap[relativePath];
|
|
59
|
+
return {
|
|
60
|
+
name: item.name,
|
|
61
|
+
path: relativePath,
|
|
62
|
+
type: "file",
|
|
63
|
+
...(gitStatus ? { gitStatus } : {}),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
const buildTree = (targetDir) => {
|
|
68
|
+
const workingDir = node_path_1.default.resolve(targetDir || process.cwd());
|
|
69
|
+
const ig = (0, gitignoreLoader_1.loadGitignore)(workingDir);
|
|
70
|
+
const statusMap = (0, getGitStatus_1.getGitStatusMap)(workingDir);
|
|
71
|
+
return {
|
|
72
|
+
app: {
|
|
73
|
+
name: node_path_1.default.basename(workingDir),
|
|
74
|
+
rootPath: workingDir,
|
|
75
|
+
},
|
|
76
|
+
tree: buildNodes(workingDir, workingDir, ig, statusMap),
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
exports.buildTree = buildTree;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadGitignore = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
10
|
+
// Минимальный hardcode — скрывается всегда даже без .gitignore
|
|
11
|
+
const ALWAYS_IGNORE = [
|
|
12
|
+
"node_modules",
|
|
13
|
+
".git",
|
|
14
|
+
"**/*.sock",
|
|
15
|
+
"**/*.socket",
|
|
16
|
+
"**/com.apple.launchd.*",
|
|
17
|
+
"**/.DS_Store",
|
|
18
|
+
];
|
|
19
|
+
const loadGitignore = (rootDir) => {
|
|
20
|
+
const ig = (0, ignore_1.default)();
|
|
21
|
+
// Всегда скрываем node_modules и .git
|
|
22
|
+
ig.add(ALWAYS_IGNORE);
|
|
23
|
+
// Читаем .gitignore из корня тестового проекта
|
|
24
|
+
const gitignorePath = node_path_1.default.join(rootDir, ".gitignore");
|
|
25
|
+
if (node_fs_1.default.existsSync(gitignorePath)) {
|
|
26
|
+
const raw = node_fs_1.default.readFileSync(gitignorePath, "utf-8");
|
|
27
|
+
// Нормализуем паттерны: убираем trailing slash (node_modules/ → node_modules),
|
|
28
|
+
// пропускаем комментарии и пустые строки, убираем отрицания (!) — они не нужны для скрытия
|
|
29
|
+
const lines = raw
|
|
30
|
+
.split("\n")
|
|
31
|
+
.map((line) => line.trim())
|
|
32
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
33
|
+
.map((line) => line.replace(/\/$/, "")); // убираем trailing slash
|
|
34
|
+
ig.add(lines);
|
|
35
|
+
}
|
|
36
|
+
return ig;
|
|
37
|
+
};
|
|
38
|
+
exports.loadGitignore = loadGitignore;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getGitStatusMap = void 0;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
// Разбор одной строки porcelain v1: "XY path"
|
|
6
|
+
const parseCode = (xy) => {
|
|
7
|
+
const x = xy[0]; // staged
|
|
8
|
+
const y = xy[1]; // unstaged
|
|
9
|
+
if (x === "?" && y === "?")
|
|
10
|
+
return "untracked";
|
|
11
|
+
if (x === "U" || y === "U" || (x === "A" && y === "A") || (x === "D" && y === "D"))
|
|
12
|
+
return "conflicted";
|
|
13
|
+
if (x === "R" || y === "R")
|
|
14
|
+
return "renamed";
|
|
15
|
+
if (x === "A")
|
|
16
|
+
return "added";
|
|
17
|
+
if (x === "D" || y === "D")
|
|
18
|
+
return "deleted";
|
|
19
|
+
if (x === "M" || y === "M")
|
|
20
|
+
return "modified";
|
|
21
|
+
return "clean";
|
|
22
|
+
};
|
|
23
|
+
const getGitStatusMap = (rootDir) => {
|
|
24
|
+
try {
|
|
25
|
+
// Проверяем что это git-репозиторий
|
|
26
|
+
(0, node_child_process_1.execSync)("git rev-parse --is-inside-work-tree", {
|
|
27
|
+
cwd: rootDir,
|
|
28
|
+
stdio: "ignore",
|
|
29
|
+
});
|
|
30
|
+
const output = (0, node_child_process_1.execSync)("git status --porcelain -u", {
|
|
31
|
+
cwd: rootDir,
|
|
32
|
+
encoding: "utf-8",
|
|
33
|
+
});
|
|
34
|
+
const map = {};
|
|
35
|
+
output
|
|
36
|
+
.split("\n")
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.forEach((line) => {
|
|
39
|
+
const xy = line.slice(0, 2);
|
|
40
|
+
// Порcelain: "XY path" или "XY oldpath -> newpath"
|
|
41
|
+
const rawPath = line.slice(3).trim();
|
|
42
|
+
// Обрабатываем rename: "old -> new"
|
|
43
|
+
const filePath = rawPath.includes(" -> ")
|
|
44
|
+
? rawPath.split(" -> ")[1]
|
|
45
|
+
: rawPath;
|
|
46
|
+
const status = parseCode(xy);
|
|
47
|
+
if (status !== "clean") {
|
|
48
|
+
map[filePath.replace(/\\/g, "/")] = status;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return map;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Не git-репозиторий или git не установлен — возвращаем пустой map
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
exports.getGitStatusMap = getGitStatusMap;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.makeGitignoreWatcher = makeGitignoreWatcher;
|
|
7
|
+
const gitignoreLoader_1 = require("../file/gitignoreLoader");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
/**
|
|
10
|
+
* Возвращает функцию-фильтр для chokidar, учитывающую .gitignore и ALWAYS_IGNORE.
|
|
11
|
+
* @param rootDir Корень проекта
|
|
12
|
+
*/
|
|
13
|
+
function makeGitignoreWatcher(rootDir) {
|
|
14
|
+
const ig = (0, gitignoreLoader_1.loadGitignore)(rootDir);
|
|
15
|
+
return (filePath) => {
|
|
16
|
+
// относительный путь от корня проекта
|
|
17
|
+
const relative = node_path_1.default.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
18
|
+
if (!relative)
|
|
19
|
+
return false; // Корень проекта не игнорируем!
|
|
20
|
+
return ig.ignores(relative);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startWatcher = void 0;
|
|
7
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const ws_1 = require("ws");
|
|
10
|
+
const gitignoreWatcher_1 = require("./gitignoreWatcher");
|
|
11
|
+
// ─── Debounce + batch ────────────────────────────────────────────────────────
|
|
12
|
+
const DEBOUNCE_MS = 200;
|
|
13
|
+
let debounceTimer = null;
|
|
14
|
+
let pendingTreeChanged = false;
|
|
15
|
+
const pendingFileChanges = new Set();
|
|
16
|
+
// ─── WebSocket broadcast ─────────────────────────────────────────────────────
|
|
17
|
+
let wss = null;
|
|
18
|
+
const broadcast = (event) => {
|
|
19
|
+
if (!wss)
|
|
20
|
+
return;
|
|
21
|
+
const message = JSON.stringify(event);
|
|
22
|
+
wss.clients.forEach((client) => {
|
|
23
|
+
if (client.readyState === ws_1.WebSocket.OPEN) {
|
|
24
|
+
client.send(message);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
// ─── Flush batch после debounce ──────────────────────────────────────────────
|
|
29
|
+
const scheduleBatch = () => {
|
|
30
|
+
if (debounceTimer)
|
|
31
|
+
clearTimeout(debounceTimer);
|
|
32
|
+
debounceTimer = setTimeout(() => {
|
|
33
|
+
if (pendingTreeChanged) {
|
|
34
|
+
broadcast({ type: "tree_changed" });
|
|
35
|
+
pendingTreeChanged = false;
|
|
36
|
+
}
|
|
37
|
+
pendingFileChanges.forEach((filePath) => {
|
|
38
|
+
broadcast({ type: "file_changed", path: filePath });
|
|
39
|
+
});
|
|
40
|
+
pendingFileChanges.clear();
|
|
41
|
+
debounceTimer = null;
|
|
42
|
+
}, DEBOUNCE_MS);
|
|
43
|
+
};
|
|
44
|
+
// ─── Запуск watcher ──────────────────────────────────────────────────────────
|
|
45
|
+
const startWatcher = (targetDir, httpServer) => {
|
|
46
|
+
// WebSocket сервер без привязки к серверу — routing через upgrade event
|
|
47
|
+
wss = new ws_1.WebSocketServer({ noServer: true });
|
|
48
|
+
// Регистрируем upgrade-обработчик для пути /ws
|
|
49
|
+
httpServer.on("upgrade", (req, socket, head) => {
|
|
50
|
+
if ((req.url ?? "") === "/ws") {
|
|
51
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
52
|
+
wss.emit("connection", ws, req);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
wss.on("connection", (socket) => {
|
|
57
|
+
console.log("🔌 WebSocket client connected");
|
|
58
|
+
// Сразу сообщаем клиенту что подключились
|
|
59
|
+
socket.send(JSON.stringify({ type: "connected" }));
|
|
60
|
+
socket.on("close", () => {
|
|
61
|
+
console.log("🔌 WebSocket client disconnected");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
const watcher = chokidar_1.default.watch(targetDir, {
|
|
65
|
+
ignored: (filePath) => {
|
|
66
|
+
// Всегда игнорируем Unix-сокеты и системные файлы — chokidar не умеет их watchить
|
|
67
|
+
if (filePath.endsWith(".sock") ||
|
|
68
|
+
filePath.endsWith(".socket") ||
|
|
69
|
+
/com\.apple\.launchd\./i.test(filePath) ||
|
|
70
|
+
filePath.includes("Visual Studio Code-")) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return (0, gitignoreWatcher_1.makeGitignoreWatcher)(targetDir)(filePath);
|
|
74
|
+
},
|
|
75
|
+
ignoreInitial: true,
|
|
76
|
+
persistent: true,
|
|
77
|
+
ignorePermissionErrors: true,
|
|
78
|
+
usePolling: false,
|
|
79
|
+
depth: 10,
|
|
80
|
+
awaitWriteFinish: {
|
|
81
|
+
stabilityThreshold: 150,
|
|
82
|
+
pollInterval: 50,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
// Структурные изменения → tree_changed
|
|
86
|
+
const onStructure = (filePath) => {
|
|
87
|
+
const relative = node_path_1.default.relative(targetDir, filePath).replace(/\\/g, "/");
|
|
88
|
+
console.log(`📁 Structure change: ${relative}`);
|
|
89
|
+
pendingTreeChanged = true;
|
|
90
|
+
scheduleBatch();
|
|
91
|
+
};
|
|
92
|
+
// Изменение содержимого → file_changed + tree_changed (git-статусы обновились)
|
|
93
|
+
const onChange = (filePath) => {
|
|
94
|
+
const relative = node_path_1.default.relative(targetDir, filePath).replace(/\\/g, "/");
|
|
95
|
+
console.log(`📝 File changed: ${relative}`);
|
|
96
|
+
pendingFileChanges.add(relative);
|
|
97
|
+
// git-статус файла мог измениться → нужно пересобрать дерево
|
|
98
|
+
pendingTreeChanged = true;
|
|
99
|
+
scheduleBatch();
|
|
100
|
+
};
|
|
101
|
+
watcher
|
|
102
|
+
.on("add", onStructure)
|
|
103
|
+
.on("addDir", onStructure)
|
|
104
|
+
.on("unlink", onStructure)
|
|
105
|
+
.on("unlinkDir", onStructure)
|
|
106
|
+
.on("change", onChange)
|
|
107
|
+
.on("error", (err) => {
|
|
108
|
+
// UNKNOWN — это системные сокеты / .sock файлы — chokidar не умеет их watchить, тихо игнорируем
|
|
109
|
+
const e = err;
|
|
110
|
+
if (e.code === "UNKNOWN" || e.code === "ENOENT")
|
|
111
|
+
return;
|
|
112
|
+
console.error("❌ Watcher error:", err);
|
|
113
|
+
})
|
|
114
|
+
.on("ready", () => console.log(`👀 Watching: ${targetDir}`));
|
|
115
|
+
return watcher;
|
|
116
|
+
};
|
|
117
|
+
exports.startWatcher = startWatcher;
|