@episoda/cli 0.2.171 → 0.2.172
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/daemon/daemon-process.js +859 -424
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +34 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1640,15 +1640,15 @@ var require_git_executor = __commonJS({
|
|
|
1640
1640
|
try {
|
|
1641
1641
|
const { stdout: gitDir } = await execAsync3("git rev-parse --git-dir", { cwd, timeout: 5e3 });
|
|
1642
1642
|
const gitDirPath = gitDir.trim();
|
|
1643
|
-
const
|
|
1643
|
+
const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1644
1644
|
const rebaseMergePath = `${gitDirPath}/rebase-merge`;
|
|
1645
1645
|
const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
|
|
1646
1646
|
try {
|
|
1647
|
-
await
|
|
1647
|
+
await fs35.access(rebaseMergePath);
|
|
1648
1648
|
inRebase = true;
|
|
1649
1649
|
} catch {
|
|
1650
1650
|
try {
|
|
1651
|
-
await
|
|
1651
|
+
await fs35.access(rebaseApplyPath);
|
|
1652
1652
|
inRebase = true;
|
|
1653
1653
|
} catch {
|
|
1654
1654
|
inRebase = false;
|
|
@@ -1704,9 +1704,9 @@ var require_git_executor = __commonJS({
|
|
|
1704
1704
|
};
|
|
1705
1705
|
}
|
|
1706
1706
|
}
|
|
1707
|
-
const
|
|
1707
|
+
const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1708
1708
|
try {
|
|
1709
|
-
await
|
|
1709
|
+
await fs35.access(command.path);
|
|
1710
1710
|
return {
|
|
1711
1711
|
success: false,
|
|
1712
1712
|
error: "WORKTREE_EXISTS",
|
|
@@ -1765,9 +1765,9 @@ var require_git_executor = __commonJS({
|
|
|
1765
1765
|
*/
|
|
1766
1766
|
async executeWorktreeRemove(command, cwd, options) {
|
|
1767
1767
|
try {
|
|
1768
|
-
const
|
|
1768
|
+
const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1769
1769
|
try {
|
|
1770
|
-
await
|
|
1770
|
+
await fs35.access(command.path);
|
|
1771
1771
|
} catch {
|
|
1772
1772
|
return {
|
|
1773
1773
|
success: false,
|
|
@@ -1802,7 +1802,7 @@ var require_git_executor = __commonJS({
|
|
|
1802
1802
|
const result = await this.runGitCommand(args, cwd, options);
|
|
1803
1803
|
if (result.success) {
|
|
1804
1804
|
try {
|
|
1805
|
-
await
|
|
1805
|
+
await fs35.rm(command.path, { recursive: true, force: true });
|
|
1806
1806
|
} catch {
|
|
1807
1807
|
}
|
|
1808
1808
|
return {
|
|
@@ -1936,10 +1936,10 @@ var require_git_executor = __commonJS({
|
|
|
1936
1936
|
*/
|
|
1937
1937
|
async executeCloneBare(command, options) {
|
|
1938
1938
|
try {
|
|
1939
|
-
const
|
|
1940
|
-
const
|
|
1939
|
+
const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1940
|
+
const path35 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1941
1941
|
try {
|
|
1942
|
-
await
|
|
1942
|
+
await fs35.access(command.path);
|
|
1943
1943
|
return {
|
|
1944
1944
|
success: false,
|
|
1945
1945
|
error: "BRANCH_ALREADY_EXISTS",
|
|
@@ -1948,9 +1948,9 @@ var require_git_executor = __commonJS({
|
|
|
1948
1948
|
};
|
|
1949
1949
|
} catch {
|
|
1950
1950
|
}
|
|
1951
|
-
const parentDir =
|
|
1951
|
+
const parentDir = path35.dirname(command.path);
|
|
1952
1952
|
try {
|
|
1953
|
-
await
|
|
1953
|
+
await fs35.mkdir(parentDir, { recursive: true });
|
|
1954
1954
|
} catch {
|
|
1955
1955
|
}
|
|
1956
1956
|
const { stdout, stderr } = await execAsync3(
|
|
@@ -1998,22 +1998,22 @@ var require_git_executor = __commonJS({
|
|
|
1998
1998
|
*/
|
|
1999
1999
|
async executeProjectInfo(cwd, options) {
|
|
2000
2000
|
try {
|
|
2001
|
-
const
|
|
2002
|
-
const
|
|
2001
|
+
const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
2002
|
+
const path35 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
2003
2003
|
let currentPath = cwd;
|
|
2004
2004
|
let projectPath = cwd;
|
|
2005
2005
|
let bareRepoPath;
|
|
2006
2006
|
for (let i = 0; i < 10; i++) {
|
|
2007
|
-
const bareDir =
|
|
2008
|
-
const episodaDir =
|
|
2007
|
+
const bareDir = path35.join(currentPath, ".bare");
|
|
2008
|
+
const episodaDir = path35.join(currentPath, ".episoda");
|
|
2009
2009
|
try {
|
|
2010
|
-
await
|
|
2011
|
-
await
|
|
2010
|
+
await fs35.access(bareDir);
|
|
2011
|
+
await fs35.access(episodaDir);
|
|
2012
2012
|
projectPath = currentPath;
|
|
2013
2013
|
bareRepoPath = bareDir;
|
|
2014
2014
|
break;
|
|
2015
2015
|
} catch {
|
|
2016
|
-
const parentPath =
|
|
2016
|
+
const parentPath = path35.dirname(currentPath);
|
|
2017
2017
|
if (parentPath === currentPath) {
|
|
2018
2018
|
break;
|
|
2019
2019
|
}
|
|
@@ -2235,7 +2235,7 @@ var require_websocket_client = __commonJS({
|
|
|
2235
2235
|
clearTimeout(this.reconnectTimeout);
|
|
2236
2236
|
this.reconnectTimeout = void 0;
|
|
2237
2237
|
}
|
|
2238
|
-
return new Promise((
|
|
2238
|
+
return new Promise((resolve6, reject) => {
|
|
2239
2239
|
const connectionTimeout = setTimeout(() => {
|
|
2240
2240
|
if (this.ws) {
|
|
2241
2241
|
this.ws.terminate();
|
|
@@ -2266,7 +2266,7 @@ var require_websocket_client = __commonJS({
|
|
|
2266
2266
|
daemonPid: this.daemonPid
|
|
2267
2267
|
});
|
|
2268
2268
|
this.startHeartbeat();
|
|
2269
|
-
|
|
2269
|
+
resolve6();
|
|
2270
2270
|
});
|
|
2271
2271
|
this.ws.on("pong", () => {
|
|
2272
2272
|
if (this.heartbeatTimeoutTimer) {
|
|
@@ -2397,13 +2397,13 @@ var require_websocket_client = __commonJS({
|
|
|
2397
2397
|
console.warn("[EpisodaClient] Cannot send - WebSocket not connected");
|
|
2398
2398
|
return false;
|
|
2399
2399
|
}
|
|
2400
|
-
return new Promise((
|
|
2400
|
+
return new Promise((resolve6) => {
|
|
2401
2401
|
this.ws.send(JSON.stringify(message), (error) => {
|
|
2402
2402
|
if (error) {
|
|
2403
2403
|
console.error("[EpisodaClient] Failed to send message:", error);
|
|
2404
|
-
|
|
2404
|
+
resolve6(false);
|
|
2405
2405
|
} else {
|
|
2406
|
-
|
|
2406
|
+
resolve6(true);
|
|
2407
2407
|
}
|
|
2408
2408
|
});
|
|
2409
2409
|
});
|
|
@@ -2674,33 +2674,33 @@ var require_auth = __commonJS({
|
|
|
2674
2674
|
exports2.loadConfig = loadConfig16;
|
|
2675
2675
|
exports2.saveConfig = saveConfig4;
|
|
2676
2676
|
exports2.validateToken = validateToken;
|
|
2677
|
-
var
|
|
2678
|
-
var
|
|
2679
|
-
var
|
|
2677
|
+
var fs35 = __importStar(require("fs"));
|
|
2678
|
+
var path35 = __importStar(require("path"));
|
|
2679
|
+
var os16 = __importStar(require("os"));
|
|
2680
2680
|
var child_process_1 = require("child_process");
|
|
2681
2681
|
var DEFAULT_CONFIG_FILE = "config.json";
|
|
2682
2682
|
var hasWarnedMissingProjectId = false;
|
|
2683
2683
|
var hasWarnedMissingRequiredFields = false;
|
|
2684
2684
|
function getConfigDir10() {
|
|
2685
|
-
return process.env.EPISODA_CONFIG_DIR ||
|
|
2685
|
+
return process.env.EPISODA_CONFIG_DIR || path35.join(os16.homedir(), ".episoda");
|
|
2686
2686
|
}
|
|
2687
2687
|
function getConfigPath(configPath) {
|
|
2688
2688
|
if (configPath) {
|
|
2689
2689
|
return configPath;
|
|
2690
2690
|
}
|
|
2691
|
-
return
|
|
2691
|
+
return path35.join(getConfigDir10(), DEFAULT_CONFIG_FILE);
|
|
2692
2692
|
}
|
|
2693
2693
|
function ensureConfigDir(configPath) {
|
|
2694
|
-
const dir =
|
|
2695
|
-
const isNew = !
|
|
2694
|
+
const dir = path35.dirname(configPath);
|
|
2695
|
+
const isNew = !fs35.existsSync(dir);
|
|
2696
2696
|
if (isNew) {
|
|
2697
|
-
|
|
2697
|
+
fs35.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2698
2698
|
}
|
|
2699
2699
|
if (process.platform === "darwin") {
|
|
2700
|
-
const nosyncPath =
|
|
2701
|
-
if (isNew || !
|
|
2700
|
+
const nosyncPath = path35.join(dir, ".nosync");
|
|
2701
|
+
if (isNew || !fs35.existsSync(nosyncPath)) {
|
|
2702
2702
|
try {
|
|
2703
|
-
|
|
2703
|
+
fs35.writeFileSync(nosyncPath, "", { mode: 384 });
|
|
2704
2704
|
(0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
|
|
2705
2705
|
stdio: "ignore",
|
|
2706
2706
|
timeout: 5e3
|
|
@@ -2714,11 +2714,11 @@ var require_auth = __commonJS({
|
|
|
2714
2714
|
const fullPath = getConfigPath(configPath);
|
|
2715
2715
|
const isCloudMode = process.env.EPISODA_MODE === "cloud";
|
|
2716
2716
|
const readConfigFile = (pathToFile) => {
|
|
2717
|
-
if (!
|
|
2717
|
+
if (!fs35.existsSync(pathToFile)) {
|
|
2718
2718
|
return null;
|
|
2719
2719
|
}
|
|
2720
2720
|
try {
|
|
2721
|
-
const content =
|
|
2721
|
+
const content = fs35.readFileSync(pathToFile, "utf8");
|
|
2722
2722
|
return JSON.parse(content);
|
|
2723
2723
|
} catch (error) {
|
|
2724
2724
|
console.error("Error loading config:", error);
|
|
@@ -2757,11 +2757,11 @@ var require_auth = __commonJS({
|
|
|
2757
2757
|
}
|
|
2758
2758
|
const homeDir = process.env.HOME || require("os").homedir();
|
|
2759
2759
|
const workspaceConfigPath = require("path").join(homeDir, "episoda", process.env.EPISODA_WORKSPACE, ".episoda", "config.json");
|
|
2760
|
-
if (!
|
|
2760
|
+
if (!fs35.existsSync(workspaceConfigPath)) {
|
|
2761
2761
|
return null;
|
|
2762
2762
|
}
|
|
2763
2763
|
try {
|
|
2764
|
-
const content =
|
|
2764
|
+
const content = fs35.readFileSync(workspaceConfigPath, "utf8");
|
|
2765
2765
|
const workspaceConfig2 = JSON.parse(content);
|
|
2766
2766
|
const expiresAtEnv = envValue(process.env.EPISODA_ACCESS_TOKEN_EXPIRES_AT);
|
|
2767
2767
|
return {
|
|
@@ -2789,11 +2789,11 @@ var require_auth = __commonJS({
|
|
|
2789
2789
|
}
|
|
2790
2790
|
const homeDir = process.env.HOME || require("os").homedir();
|
|
2791
2791
|
const projectConfigPath = require("path").join(homeDir, "episoda", workspaceSlug, projectSlug, ".episoda", "config.json");
|
|
2792
|
-
if (!
|
|
2792
|
+
if (!fs35.existsSync(projectConfigPath)) {
|
|
2793
2793
|
return null;
|
|
2794
2794
|
}
|
|
2795
2795
|
try {
|
|
2796
|
-
const content =
|
|
2796
|
+
const content = fs35.readFileSync(projectConfigPath, "utf8");
|
|
2797
2797
|
const projectConfig2 = JSON.parse(content);
|
|
2798
2798
|
return {
|
|
2799
2799
|
project_id: projectConfig2.projectId || projectConfig2.project_id,
|
|
@@ -2861,7 +2861,7 @@ var require_auth = __commonJS({
|
|
|
2861
2861
|
ensureConfigDir(fullPath);
|
|
2862
2862
|
try {
|
|
2863
2863
|
const content = JSON.stringify(config, null, 2);
|
|
2864
|
-
|
|
2864
|
+
fs35.writeFileSync(fullPath, content, { mode: 384 });
|
|
2865
2865
|
} catch (error) {
|
|
2866
2866
|
throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
|
|
2867
2867
|
}
|
|
@@ -2931,6 +2931,23 @@ Branch: ${details.branchName}`;
|
|
|
2931
2931
|
}
|
|
2932
2932
|
});
|
|
2933
2933
|
|
|
2934
|
+
// ../core/dist/internal-env-keys.js
|
|
2935
|
+
var require_internal_env_keys = __commonJS({
|
|
2936
|
+
"../core/dist/internal-env-keys.js"(exports2) {
|
|
2937
|
+
"use strict";
|
|
2938
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
2939
|
+
exports2.INTERNAL_ONLY_ENV_KEYS = void 0;
|
|
2940
|
+
exports2.isInternalOnlyEnvKey = isInternalOnlyEnvKey;
|
|
2941
|
+
exports2.INTERNAL_ONLY_ENV_KEYS = [
|
|
2942
|
+
"AI_CREDENTIALS_ENCRYPTION_KEY"
|
|
2943
|
+
];
|
|
2944
|
+
var INTERNAL_ONLY_ENV_KEY_SET = new Set(exports2.INTERNAL_ONLY_ENV_KEYS);
|
|
2945
|
+
function isInternalOnlyEnvKey(key) {
|
|
2946
|
+
return INTERNAL_ONLY_ENV_KEY_SET.has(key.toUpperCase());
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
});
|
|
2950
|
+
|
|
2934
2951
|
// ../core/dist/index.js
|
|
2935
2952
|
var require_dist = __commonJS({
|
|
2936
2953
|
"../core/dist/index.js"(exports2) {
|
|
@@ -2966,6 +2983,7 @@ var require_dist = __commonJS({
|
|
|
2966
2983
|
__exportStar(require_errors(), exports2);
|
|
2967
2984
|
__exportStar(require_git_validator(), exports2);
|
|
2968
2985
|
__exportStar(require_git_parser(), exports2);
|
|
2986
|
+
__exportStar(require_internal_env_keys(), exports2);
|
|
2969
2987
|
var version_1 = require_version();
|
|
2970
2988
|
Object.defineProperty(exports2, "VERSION", { enumerable: true, get: function() {
|
|
2971
2989
|
return version_1.VERSION;
|
|
@@ -2978,7 +2996,7 @@ var require_package = __commonJS({
|
|
|
2978
2996
|
"package.json"(exports2, module2) {
|
|
2979
2997
|
module2.exports = {
|
|
2980
2998
|
name: "@episoda/cli",
|
|
2981
|
-
version: "0.2.
|
|
2999
|
+
version: "0.2.172",
|
|
2982
3000
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2983
3001
|
main: "dist/index.js",
|
|
2984
3002
|
types: "dist/index.d.ts",
|
|
@@ -3283,10 +3301,10 @@ var IPCServer = class {
|
|
|
3283
3301
|
this.server = net.createServer((socket) => {
|
|
3284
3302
|
this.handleConnection(socket);
|
|
3285
3303
|
});
|
|
3286
|
-
return new Promise((
|
|
3304
|
+
return new Promise((resolve6, reject) => {
|
|
3287
3305
|
this.server.listen(socketPath, () => {
|
|
3288
3306
|
fs3.chmodSync(socketPath, 384);
|
|
3289
|
-
|
|
3307
|
+
resolve6();
|
|
3290
3308
|
});
|
|
3291
3309
|
this.server.on("error", reject);
|
|
3292
3310
|
});
|
|
@@ -3297,12 +3315,12 @@ var IPCServer = class {
|
|
|
3297
3315
|
async stop() {
|
|
3298
3316
|
if (!this.server) return;
|
|
3299
3317
|
const socketPath = getSocketPath();
|
|
3300
|
-
return new Promise((
|
|
3318
|
+
return new Promise((resolve6) => {
|
|
3301
3319
|
this.server.close(() => {
|
|
3302
3320
|
if (fs3.existsSync(socketPath)) {
|
|
3303
3321
|
fs3.unlinkSync(socketPath);
|
|
3304
3322
|
}
|
|
3305
|
-
|
|
3323
|
+
resolve6();
|
|
3306
3324
|
});
|
|
3307
3325
|
});
|
|
3308
3326
|
}
|
|
@@ -3528,7 +3546,7 @@ function getDownloadUrl() {
|
|
|
3528
3546
|
return platformUrls[arch4] || null;
|
|
3529
3547
|
}
|
|
3530
3548
|
async function downloadFile(url, destPath) {
|
|
3531
|
-
return new Promise((
|
|
3549
|
+
return new Promise((resolve6, reject) => {
|
|
3532
3550
|
const followRedirect = (currentUrl, redirectCount = 0) => {
|
|
3533
3551
|
if (redirectCount > 5) {
|
|
3534
3552
|
reject(new Error("Too many redirects"));
|
|
@@ -3558,7 +3576,7 @@ async function downloadFile(url, destPath) {
|
|
|
3558
3576
|
response.pipe(file);
|
|
3559
3577
|
file.on("finish", () => {
|
|
3560
3578
|
file.close();
|
|
3561
|
-
|
|
3579
|
+
resolve6();
|
|
3562
3580
|
});
|
|
3563
3581
|
file.on("error", (err) => {
|
|
3564
3582
|
fs4.unlinkSync(destPath);
|
|
@@ -3972,10 +3990,10 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
3972
3990
|
const isTracked = Array.from(this.tunnelStates.values()).some((s) => s.info.pid === pid);
|
|
3973
3991
|
console.log(`[Tunnel] EP904: Found cloudflared PID ${pid} on port ${port} (tracked: ${isTracked})`);
|
|
3974
3992
|
this.killByPid(pid, "SIGTERM");
|
|
3975
|
-
await new Promise((
|
|
3993
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
3976
3994
|
if (this.isProcessRunning(pid)) {
|
|
3977
3995
|
this.killByPid(pid, "SIGKILL");
|
|
3978
|
-
await new Promise((
|
|
3996
|
+
await new Promise((resolve6) => setTimeout(resolve6, 200));
|
|
3979
3997
|
}
|
|
3980
3998
|
killed.push(pid);
|
|
3981
3999
|
}
|
|
@@ -4009,7 +4027,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4009
4027
|
if (!this.tunnelStates.has(moduleUid)) {
|
|
4010
4028
|
console.log(`[Tunnel] EP877: Found orphaned process PID ${pid} for ${moduleUid}, killing...`);
|
|
4011
4029
|
this.killByPid(pid, "SIGTERM");
|
|
4012
|
-
await new Promise((
|
|
4030
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
4013
4031
|
if (this.isProcessRunning(pid)) {
|
|
4014
4032
|
this.killByPid(pid, "SIGKILL");
|
|
4015
4033
|
}
|
|
@@ -4024,7 +4042,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4024
4042
|
if (!trackedPids.includes(pid) && !cleaned.includes(pid)) {
|
|
4025
4043
|
console.log(`[Tunnel] EP877: Found untracked cloudflared process PID ${pid}, killing...`);
|
|
4026
4044
|
this.killByPid(pid, "SIGTERM");
|
|
4027
|
-
await new Promise((
|
|
4045
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
4028
4046
|
if (this.isProcessRunning(pid)) {
|
|
4029
4047
|
this.killByPid(pid, "SIGKILL");
|
|
4030
4048
|
}
|
|
@@ -4114,7 +4132,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4114
4132
|
return { success: false, error: `Failed to get cloudflared: ${errorMessage}` };
|
|
4115
4133
|
}
|
|
4116
4134
|
}
|
|
4117
|
-
return new Promise((
|
|
4135
|
+
return new Promise((resolve6) => {
|
|
4118
4136
|
const tunnelInfo = {
|
|
4119
4137
|
moduleUid,
|
|
4120
4138
|
url: previewUrl || "",
|
|
@@ -4180,7 +4198,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4180
4198
|
moduleUid,
|
|
4181
4199
|
url: tunnelInfo.url
|
|
4182
4200
|
});
|
|
4183
|
-
|
|
4201
|
+
resolve6({ success: true, url: tunnelInfo.url });
|
|
4184
4202
|
}
|
|
4185
4203
|
};
|
|
4186
4204
|
process2.stderr?.on("data", (data) => {
|
|
@@ -4207,7 +4225,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4207
4225
|
onStatusChange?.("error", errorMsg);
|
|
4208
4226
|
this.emitEvent({ type: "error", moduleUid, error: errorMsg });
|
|
4209
4227
|
}
|
|
4210
|
-
|
|
4228
|
+
resolve6({ success: false, error: errorMsg });
|
|
4211
4229
|
} else if (wasConnected) {
|
|
4212
4230
|
if (currentState && !currentState.intentionallyStopped) {
|
|
4213
4231
|
console.log(`[Tunnel] EP948: Named tunnel ${moduleUid} crashed unexpectedly, attempting reconnect...`);
|
|
@@ -4238,7 +4256,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4238
4256
|
this.emitEvent({ type: "error", moduleUid, error: error.message });
|
|
4239
4257
|
}
|
|
4240
4258
|
if (!connected) {
|
|
4241
|
-
|
|
4259
|
+
resolve6({ success: false, error: error.message });
|
|
4242
4260
|
}
|
|
4243
4261
|
});
|
|
4244
4262
|
setTimeout(() => {
|
|
@@ -4261,7 +4279,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4261
4279
|
onStatusChange?.("error", errorMsg);
|
|
4262
4280
|
this.emitEvent({ type: "error", moduleUid, error: errorMsg });
|
|
4263
4281
|
}
|
|
4264
|
-
|
|
4282
|
+
resolve6({ success: false, error: errorMsg });
|
|
4265
4283
|
}
|
|
4266
4284
|
}, TUNNEL_TIMEOUTS.NAMED_TUNNEL_CONNECT);
|
|
4267
4285
|
});
|
|
@@ -4322,7 +4340,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4322
4340
|
if (orphanPid && this.isProcessRunning(orphanPid)) {
|
|
4323
4341
|
console.log(`[Tunnel] EP877: Killing orphaned process ${orphanPid} for ${moduleUid} before starting new tunnel`);
|
|
4324
4342
|
this.killByPid(orphanPid, "SIGTERM");
|
|
4325
|
-
await new Promise((
|
|
4343
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
4326
4344
|
if (this.isProcessRunning(orphanPid)) {
|
|
4327
4345
|
this.killByPid(orphanPid, "SIGKILL");
|
|
4328
4346
|
}
|
|
@@ -4331,7 +4349,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4331
4349
|
const killedOnPort = await this.killCloudflaredOnPort(port);
|
|
4332
4350
|
if (killedOnPort.length > 0) {
|
|
4333
4351
|
console.log(`[Tunnel] EP904: Pre-start port cleanup killed ${killedOnPort.length} process(es) on port ${port}`);
|
|
4334
|
-
await new Promise((
|
|
4352
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
4335
4353
|
}
|
|
4336
4354
|
const cleanup = await this.cleanupOrphanedProcesses();
|
|
4337
4355
|
if (cleanup.cleaned > 0) {
|
|
@@ -4371,7 +4389,7 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4371
4389
|
if (orphanPid && this.isProcessRunning(orphanPid)) {
|
|
4372
4390
|
console.log(`[Tunnel] EP877: Stopping orphaned process ${orphanPid} for ${moduleUid} via PID file`);
|
|
4373
4391
|
this.killByPid(orphanPid, "SIGTERM");
|
|
4374
|
-
await new Promise((
|
|
4392
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
4375
4393
|
if (this.isProcessRunning(orphanPid)) {
|
|
4376
4394
|
this.killByPid(orphanPid, "SIGKILL");
|
|
4377
4395
|
}
|
|
@@ -4387,16 +4405,16 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4387
4405
|
const tunnel = state.info;
|
|
4388
4406
|
if (tunnel.process && !tunnel.process.killed) {
|
|
4389
4407
|
tunnel.process.kill("SIGTERM");
|
|
4390
|
-
await new Promise((
|
|
4408
|
+
await new Promise((resolve6) => {
|
|
4391
4409
|
const timeout = setTimeout(() => {
|
|
4392
4410
|
if (tunnel.process && !tunnel.process.killed) {
|
|
4393
4411
|
tunnel.process.kill("SIGKILL");
|
|
4394
4412
|
}
|
|
4395
|
-
|
|
4413
|
+
resolve6();
|
|
4396
4414
|
}, 3e3);
|
|
4397
4415
|
tunnel.process.once("exit", () => {
|
|
4398
4416
|
clearTimeout(timeout);
|
|
4399
|
-
|
|
4417
|
+
resolve6();
|
|
4400
4418
|
});
|
|
4401
4419
|
});
|
|
4402
4420
|
}
|
|
@@ -4683,15 +4701,15 @@ async function writeToStdinWithBackpressure(process2, data, drainTimeoutMs, labe
|
|
|
4683
4701
|
if (!stdin || stdin.destroyed) {
|
|
4684
4702
|
throw new Error(`[${label}] stdin not available. session=${sessionId}`);
|
|
4685
4703
|
}
|
|
4686
|
-
await new Promise((
|
|
4704
|
+
await new Promise((resolve6, reject) => {
|
|
4687
4705
|
const ok = stdin.write(data);
|
|
4688
4706
|
if (ok) {
|
|
4689
|
-
|
|
4707
|
+
resolve6();
|
|
4690
4708
|
return;
|
|
4691
4709
|
}
|
|
4692
4710
|
const onDrain = () => {
|
|
4693
4711
|
cleanup();
|
|
4694
|
-
|
|
4712
|
+
resolve6();
|
|
4695
4713
|
};
|
|
4696
4714
|
const onError = (err) => {
|
|
4697
4715
|
cleanup();
|
|
@@ -4712,18 +4730,18 @@ async function writeToStdinWithBackpressure(process2, data, drainTimeoutMs, labe
|
|
|
4712
4730
|
});
|
|
4713
4731
|
}
|
|
4714
4732
|
function waitForProcessExit(process2, alive, timeoutMs) {
|
|
4715
|
-
return new Promise((
|
|
4733
|
+
return new Promise((resolve6) => {
|
|
4716
4734
|
if (!process2 || !alive) {
|
|
4717
|
-
|
|
4735
|
+
resolve6(true);
|
|
4718
4736
|
return;
|
|
4719
4737
|
}
|
|
4720
4738
|
const onExit = () => {
|
|
4721
4739
|
clearTimeout(timer);
|
|
4722
|
-
|
|
4740
|
+
resolve6(true);
|
|
4723
4741
|
};
|
|
4724
4742
|
const timer = setTimeout(() => {
|
|
4725
4743
|
process2.removeListener("exit", onExit);
|
|
4726
|
-
|
|
4744
|
+
resolve6(false);
|
|
4727
4745
|
}, timeoutMs);
|
|
4728
4746
|
process2.once("exit", onExit);
|
|
4729
4747
|
});
|
|
@@ -5719,10 +5737,10 @@ var CodexPersistentRuntime = class {
|
|
|
5719
5737
|
}
|
|
5720
5738
|
waitForThreadId(timeoutMs) {
|
|
5721
5739
|
if (this._agentSessionId) return Promise.resolve(this._agentSessionId);
|
|
5722
|
-
return new Promise((
|
|
5740
|
+
return new Promise((resolve6, reject) => {
|
|
5723
5741
|
const onId = (id) => {
|
|
5724
5742
|
clearTimeout(timer);
|
|
5725
|
-
|
|
5743
|
+
resolve6(id);
|
|
5726
5744
|
};
|
|
5727
5745
|
const timer = setTimeout(() => {
|
|
5728
5746
|
this.threadIdWaiters = this.threadIdWaiters.filter((w) => w !== onId);
|
|
@@ -5753,12 +5771,12 @@ var CodexPersistentRuntime = class {
|
|
|
5753
5771
|
sendRequest(method, params, timeoutMs = INIT_TIMEOUT_MS) {
|
|
5754
5772
|
const id = this.nextId++;
|
|
5755
5773
|
const msg = { jsonrpc: "2.0", id, method, params };
|
|
5756
|
-
return new Promise(async (
|
|
5774
|
+
return new Promise(async (resolve6, reject) => {
|
|
5757
5775
|
const timeout = setTimeout(() => {
|
|
5758
5776
|
this.pending.delete(id);
|
|
5759
5777
|
reject(new Error(`JSON-RPC timeout: ${method}`));
|
|
5760
5778
|
}, timeoutMs);
|
|
5761
|
-
this.pending.set(id, { resolve:
|
|
5779
|
+
this.pending.set(id, { resolve: resolve6, reject, timeout });
|
|
5762
5780
|
try {
|
|
5763
5781
|
await this.writeToStdin(JSON.stringify(msg) + "\n");
|
|
5764
5782
|
} catch (err) {
|
|
@@ -5908,11 +5926,11 @@ var UnifiedAgentRuntime = class {
|
|
|
5908
5926
|
if (!this.currentTurn && this.impl.turnState === "idle") {
|
|
5909
5927
|
return this.dispatchTurn(message, callbacks);
|
|
5910
5928
|
}
|
|
5911
|
-
return new Promise((
|
|
5929
|
+
return new Promise((resolve6, reject) => {
|
|
5912
5930
|
this.queuedTurns.push({
|
|
5913
5931
|
message,
|
|
5914
5932
|
callbacks,
|
|
5915
|
-
resolve:
|
|
5933
|
+
resolve: resolve6,
|
|
5916
5934
|
reject
|
|
5917
5935
|
});
|
|
5918
5936
|
});
|
|
@@ -7425,13 +7443,13 @@ async function getChildProcessRssMb(pid) {
|
|
|
7425
7443
|
if (!pid || pid <= 0) return null;
|
|
7426
7444
|
try {
|
|
7427
7445
|
if (typeof import_child_process9.execFile !== "function") return null;
|
|
7428
|
-
const stdout = await new Promise((
|
|
7446
|
+
const stdout = await new Promise((resolve6, reject) => {
|
|
7429
7447
|
(0, import_child_process9.execFile)("ps", ["-o", "rss=", "-p", String(pid)], { timeout: 1e3 }, (err, out) => {
|
|
7430
7448
|
if (err) {
|
|
7431
7449
|
reject(err);
|
|
7432
7450
|
return;
|
|
7433
7451
|
}
|
|
7434
|
-
|
|
7452
|
+
resolve6(String(out));
|
|
7435
7453
|
});
|
|
7436
7454
|
});
|
|
7437
7455
|
const kb = Number(String(stdout).trim());
|
|
@@ -7534,8 +7552,8 @@ var AgentControlPlane = class {
|
|
|
7534
7552
|
async withConfigLock(fn) {
|
|
7535
7553
|
const previousLock = this.configWriteLock;
|
|
7536
7554
|
let releaseLock;
|
|
7537
|
-
this.configWriteLock = new Promise((
|
|
7538
|
-
releaseLock =
|
|
7555
|
+
this.configWriteLock = new Promise((resolve6) => {
|
|
7556
|
+
releaseLock = resolve6;
|
|
7539
7557
|
});
|
|
7540
7558
|
try {
|
|
7541
7559
|
await previousLock;
|
|
@@ -8778,7 +8796,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
8778
8796
|
const lockContent = fs14.readFileSync(lockPath, "utf-8").trim();
|
|
8779
8797
|
const lockPid = parseInt(lockContent, 10);
|
|
8780
8798
|
if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
|
|
8781
|
-
await new Promise((
|
|
8799
|
+
await new Promise((resolve6) => setTimeout(resolve6, retryInterval));
|
|
8782
8800
|
continue;
|
|
8783
8801
|
}
|
|
8784
8802
|
} catch {
|
|
@@ -8792,7 +8810,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
8792
8810
|
} catch {
|
|
8793
8811
|
continue;
|
|
8794
8812
|
}
|
|
8795
|
-
await new Promise((
|
|
8813
|
+
await new Promise((resolve6) => setTimeout(resolve6, retryInterval));
|
|
8796
8814
|
continue;
|
|
8797
8815
|
}
|
|
8798
8816
|
throw err;
|
|
@@ -9355,62 +9373,190 @@ function reconcileWithRegistry(activeModules) {
|
|
|
9355
9373
|
}
|
|
9356
9374
|
}
|
|
9357
9375
|
|
|
9358
|
-
// src/
|
|
9376
|
+
// src/utils/ws-port-allocator.ts
|
|
9359
9377
|
var fs19 = __toESM(require("fs"));
|
|
9360
9378
|
var path19 = __toESM(require("path"));
|
|
9379
|
+
var os10 = __toESM(require("os"));
|
|
9380
|
+
var WS_PORT_RANGE_START = 3200;
|
|
9381
|
+
var WS_PORT_RANGE_END = 3299;
|
|
9382
|
+
var WS_PORT_WARNING_THRESHOLD = 80;
|
|
9383
|
+
var WS_PORTS_FILE = path19.join(os10.homedir(), ".episoda", "ws-ports.json");
|
|
9384
|
+
var wsPortAssignments = /* @__PURE__ */ new Map();
|
|
9385
|
+
var initialized2 = false;
|
|
9386
|
+
function loadFromDisk2() {
|
|
9387
|
+
if (initialized2) return;
|
|
9388
|
+
initialized2 = true;
|
|
9389
|
+
try {
|
|
9390
|
+
if (!fs19.existsSync(WS_PORTS_FILE)) return;
|
|
9391
|
+
const content = fs19.readFileSync(WS_PORTS_FILE, "utf8");
|
|
9392
|
+
const data = JSON.parse(content);
|
|
9393
|
+
for (const [moduleUid, port] of Object.entries(data)) {
|
|
9394
|
+
if (typeof port === "number" && port >= WS_PORT_RANGE_START && port <= WS_PORT_RANGE_END) {
|
|
9395
|
+
wsPortAssignments.set(moduleUid, port);
|
|
9396
|
+
}
|
|
9397
|
+
}
|
|
9398
|
+
if (wsPortAssignments.size > 0) {
|
|
9399
|
+
console.log(`[WsPortAllocator] EP1406: Loaded ${wsPortAssignments.size} WS port assignments from disk`);
|
|
9400
|
+
}
|
|
9401
|
+
} catch (error) {
|
|
9402
|
+
console.warn("[WsPortAllocator] EP1406: Failed to load ws-ports.json:", error);
|
|
9403
|
+
}
|
|
9404
|
+
}
|
|
9405
|
+
function saveToDisk2() {
|
|
9406
|
+
try {
|
|
9407
|
+
const dir = path19.dirname(WS_PORTS_FILE);
|
|
9408
|
+
if (!fs19.existsSync(dir)) {
|
|
9409
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
9410
|
+
}
|
|
9411
|
+
const data = {};
|
|
9412
|
+
for (const [moduleUid, port] of wsPortAssignments) {
|
|
9413
|
+
data[moduleUid] = port;
|
|
9414
|
+
}
|
|
9415
|
+
fs19.writeFileSync(WS_PORTS_FILE, JSON.stringify(data, null, 2), "utf8");
|
|
9416
|
+
} catch (error) {
|
|
9417
|
+
console.warn("[WsPortAllocator] EP1406: Failed to save ws-ports.json:", error);
|
|
9418
|
+
}
|
|
9419
|
+
}
|
|
9420
|
+
function allocateWsPort(moduleUid) {
|
|
9421
|
+
loadFromDisk2();
|
|
9422
|
+
const existing = wsPortAssignments.get(moduleUid);
|
|
9423
|
+
if (existing) {
|
|
9424
|
+
console.log(`[WsPortAllocator] EP1406: Returning existing WS port ${existing} for ${moduleUid}`);
|
|
9425
|
+
return existing;
|
|
9426
|
+
}
|
|
9427
|
+
const usedPorts = new Set(wsPortAssignments.values());
|
|
9428
|
+
if (usedPorts.size >= WS_PORT_WARNING_THRESHOLD) {
|
|
9429
|
+
console.warn(
|
|
9430
|
+
`[WsPortAllocator] EP1406: Warning ${usedPorts.size}/${WS_PORT_RANGE_END - WS_PORT_RANGE_START + 1} WS ports allocated`
|
|
9431
|
+
);
|
|
9432
|
+
}
|
|
9433
|
+
for (let port = WS_PORT_RANGE_START; port <= WS_PORT_RANGE_END; port++) {
|
|
9434
|
+
if (!usedPorts.has(port)) {
|
|
9435
|
+
wsPortAssignments.set(moduleUid, port);
|
|
9436
|
+
saveToDisk2();
|
|
9437
|
+
console.log(`[WsPortAllocator] EP1406: Allocated WS port ${port} to ${moduleUid}`);
|
|
9438
|
+
return port;
|
|
9439
|
+
}
|
|
9440
|
+
}
|
|
9441
|
+
throw new Error(
|
|
9442
|
+
`No available WS ports in range ${WS_PORT_RANGE_START}-${WS_PORT_RANGE_END}. ${wsPortAssignments.size} modules are using all available WS ports.`
|
|
9443
|
+
);
|
|
9444
|
+
}
|
|
9445
|
+
function assignWsPort(moduleUid, wsPort) {
|
|
9446
|
+
loadFromDisk2();
|
|
9447
|
+
if (!Number.isInteger(wsPort) || wsPort < WS_PORT_RANGE_START || wsPort > WS_PORT_RANGE_END) {
|
|
9448
|
+
throw new Error(
|
|
9449
|
+
`WS port ${wsPort} is outside allowed range ${WS_PORT_RANGE_START}-${WS_PORT_RANGE_END}`
|
|
9450
|
+
);
|
|
9451
|
+
}
|
|
9452
|
+
for (const [ownerModuleUid, ownerPort] of wsPortAssignments.entries()) {
|
|
9453
|
+
if (ownerPort === wsPort && ownerModuleUid !== moduleUid) {
|
|
9454
|
+
throw new Error(`WS port ${wsPort} is already assigned to ${ownerModuleUid}`);
|
|
9455
|
+
}
|
|
9456
|
+
}
|
|
9457
|
+
const existing = wsPortAssignments.get(moduleUid);
|
|
9458
|
+
if (existing === wsPort) {
|
|
9459
|
+
return wsPort;
|
|
9460
|
+
}
|
|
9461
|
+
wsPortAssignments.set(moduleUid, wsPort);
|
|
9462
|
+
saveToDisk2();
|
|
9463
|
+
console.log(`[WsPortAllocator] EP1406: Assigned WS port ${wsPort} to ${moduleUid}`);
|
|
9464
|
+
return wsPort;
|
|
9465
|
+
}
|
|
9466
|
+
function releaseWsPort(moduleUid) {
|
|
9467
|
+
loadFromDisk2();
|
|
9468
|
+
const port = wsPortAssignments.get(moduleUid);
|
|
9469
|
+
if (!port) return;
|
|
9470
|
+
wsPortAssignments.delete(moduleUid);
|
|
9471
|
+
saveToDisk2();
|
|
9472
|
+
console.log(`[WsPortAllocator] EP1406: Released WS port ${port} from ${moduleUid}`);
|
|
9473
|
+
}
|
|
9474
|
+
function reconcileWsPortsWithRegistry(activeModules) {
|
|
9475
|
+
loadFromDisk2();
|
|
9476
|
+
let changed = false;
|
|
9477
|
+
for (const [moduleUid, port] of wsPortAssignments) {
|
|
9478
|
+
if (!activeModules.has(moduleUid)) {
|
|
9479
|
+
console.log(`[WsPortAllocator] EP1406: Removing stale WS allocation ${moduleUid} -> ${port}`);
|
|
9480
|
+
wsPortAssignments.delete(moduleUid);
|
|
9481
|
+
changed = true;
|
|
9482
|
+
}
|
|
9483
|
+
}
|
|
9484
|
+
for (const [moduleUid, port] of activeModules) {
|
|
9485
|
+
if (!wsPortAssignments.has(moduleUid)) {
|
|
9486
|
+
console.log(`[WsPortAllocator] EP1406: Adding WS allocation from registry ${moduleUid} -> ${port}`);
|
|
9487
|
+
wsPortAssignments.set(moduleUid, port);
|
|
9488
|
+
changed = true;
|
|
9489
|
+
}
|
|
9490
|
+
}
|
|
9491
|
+
if (changed) {
|
|
9492
|
+
saveToDisk2();
|
|
9493
|
+
}
|
|
9494
|
+
}
|
|
9495
|
+
function clearAllWsPorts() {
|
|
9496
|
+
const count = wsPortAssignments.size;
|
|
9497
|
+
wsPortAssignments.clear();
|
|
9498
|
+
saveToDisk2();
|
|
9499
|
+
if (count > 0) {
|
|
9500
|
+
console.log(`[WsPortAllocator] EP1406: Cleared ${count} WS port assignments`);
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
9503
|
+
|
|
9504
|
+
// src/framework-detector.ts
|
|
9505
|
+
var fs20 = __toESM(require("fs"));
|
|
9506
|
+
var path20 = __toESM(require("path"));
|
|
9361
9507
|
function getInstallCommand(cwd) {
|
|
9362
|
-
if (
|
|
9508
|
+
if (fs20.existsSync(path20.join(cwd, "bun.lockb"))) {
|
|
9363
9509
|
return {
|
|
9364
9510
|
command: ["bun", "install"],
|
|
9365
9511
|
description: "Installing dependencies with bun",
|
|
9366
9512
|
detectedFrom: "bun.lockb"
|
|
9367
9513
|
};
|
|
9368
9514
|
}
|
|
9369
|
-
if (
|
|
9515
|
+
if (fs20.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) {
|
|
9370
9516
|
return {
|
|
9371
9517
|
command: ["pnpm", "install"],
|
|
9372
9518
|
description: "Installing dependencies with pnpm",
|
|
9373
9519
|
detectedFrom: "pnpm-lock.yaml"
|
|
9374
9520
|
};
|
|
9375
9521
|
}
|
|
9376
|
-
if (
|
|
9522
|
+
if (fs20.existsSync(path20.join(cwd, "yarn.lock"))) {
|
|
9377
9523
|
return {
|
|
9378
9524
|
command: ["yarn", "install"],
|
|
9379
9525
|
description: "Installing dependencies with yarn",
|
|
9380
9526
|
detectedFrom: "yarn.lock"
|
|
9381
9527
|
};
|
|
9382
9528
|
}
|
|
9383
|
-
if (
|
|
9529
|
+
if (fs20.existsSync(path20.join(cwd, "package-lock.json"))) {
|
|
9384
9530
|
return {
|
|
9385
9531
|
command: ["npm", "ci"],
|
|
9386
9532
|
description: "Installing dependencies with npm ci",
|
|
9387
9533
|
detectedFrom: "package-lock.json"
|
|
9388
9534
|
};
|
|
9389
9535
|
}
|
|
9390
|
-
if (
|
|
9536
|
+
if (fs20.existsSync(path20.join(cwd, "package.json"))) {
|
|
9391
9537
|
return {
|
|
9392
9538
|
command: ["npm", "install"],
|
|
9393
9539
|
description: "Installing dependencies with npm",
|
|
9394
9540
|
detectedFrom: "package.json"
|
|
9395
9541
|
};
|
|
9396
9542
|
}
|
|
9397
|
-
if (
|
|
9543
|
+
if (fs20.existsSync(path20.join(cwd, "Pipfile.lock")) || fs20.existsSync(path20.join(cwd, "Pipfile"))) {
|
|
9398
9544
|
return {
|
|
9399
9545
|
command: ["pipenv", "install"],
|
|
9400
9546
|
description: "Installing dependencies with pipenv",
|
|
9401
|
-
detectedFrom:
|
|
9547
|
+
detectedFrom: fs20.existsSync(path20.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
|
|
9402
9548
|
};
|
|
9403
9549
|
}
|
|
9404
|
-
if (
|
|
9550
|
+
if (fs20.existsSync(path20.join(cwd, "poetry.lock"))) {
|
|
9405
9551
|
return {
|
|
9406
9552
|
command: ["poetry", "install"],
|
|
9407
9553
|
description: "Installing dependencies with poetry",
|
|
9408
9554
|
detectedFrom: "poetry.lock"
|
|
9409
9555
|
};
|
|
9410
9556
|
}
|
|
9411
|
-
if (
|
|
9412
|
-
const pyprojectPath =
|
|
9413
|
-
const content =
|
|
9557
|
+
if (fs20.existsSync(path20.join(cwd, "pyproject.toml"))) {
|
|
9558
|
+
const pyprojectPath = path20.join(cwd, "pyproject.toml");
|
|
9559
|
+
const content = fs20.readFileSync(pyprojectPath, "utf-8");
|
|
9414
9560
|
if (content.includes("[tool.poetry]")) {
|
|
9415
9561
|
return {
|
|
9416
9562
|
command: ["poetry", "install"],
|
|
@@ -9419,68 +9565,68 @@ function getInstallCommand(cwd) {
|
|
|
9419
9565
|
};
|
|
9420
9566
|
}
|
|
9421
9567
|
}
|
|
9422
|
-
if (
|
|
9568
|
+
if (fs20.existsSync(path20.join(cwd, "requirements.txt"))) {
|
|
9423
9569
|
return {
|
|
9424
9570
|
command: ["pip", "install", "-r", "requirements.txt"],
|
|
9425
9571
|
description: "Installing dependencies with pip",
|
|
9426
9572
|
detectedFrom: "requirements.txt"
|
|
9427
9573
|
};
|
|
9428
9574
|
}
|
|
9429
|
-
if (
|
|
9575
|
+
if (fs20.existsSync(path20.join(cwd, "Gemfile.lock")) || fs20.existsSync(path20.join(cwd, "Gemfile"))) {
|
|
9430
9576
|
return {
|
|
9431
9577
|
command: ["bundle", "install"],
|
|
9432
9578
|
description: "Installing dependencies with bundler",
|
|
9433
|
-
detectedFrom:
|
|
9579
|
+
detectedFrom: fs20.existsSync(path20.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
|
|
9434
9580
|
};
|
|
9435
9581
|
}
|
|
9436
|
-
if (
|
|
9582
|
+
if (fs20.existsSync(path20.join(cwd, "go.sum")) || fs20.existsSync(path20.join(cwd, "go.mod"))) {
|
|
9437
9583
|
return {
|
|
9438
9584
|
command: ["go", "mod", "download"],
|
|
9439
9585
|
description: "Downloading Go modules",
|
|
9440
|
-
detectedFrom:
|
|
9586
|
+
detectedFrom: fs20.existsSync(path20.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
|
|
9441
9587
|
};
|
|
9442
9588
|
}
|
|
9443
|
-
if (
|
|
9589
|
+
if (fs20.existsSync(path20.join(cwd, "Cargo.lock")) || fs20.existsSync(path20.join(cwd, "Cargo.toml"))) {
|
|
9444
9590
|
return {
|
|
9445
9591
|
command: ["cargo", "build"],
|
|
9446
9592
|
description: "Building Rust project (downloads dependencies)",
|
|
9447
|
-
detectedFrom:
|
|
9593
|
+
detectedFrom: fs20.existsSync(path20.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
|
|
9448
9594
|
};
|
|
9449
9595
|
}
|
|
9450
9596
|
return null;
|
|
9451
9597
|
}
|
|
9452
9598
|
|
|
9453
9599
|
// src/daemon/daemon-process.ts
|
|
9454
|
-
var
|
|
9600
|
+
var fs34 = __toESM(require("fs"));
|
|
9455
9601
|
var http2 = __toESM(require("http"));
|
|
9456
|
-
var
|
|
9602
|
+
var os15 = __toESM(require("os"));
|
|
9457
9603
|
|
|
9458
9604
|
// src/daemon/ipc-router.ts
|
|
9459
|
-
var
|
|
9605
|
+
var os14 = __toESM(require("os"));
|
|
9460
9606
|
var import_core15 = __toESM(require_dist());
|
|
9461
9607
|
|
|
9462
9608
|
// src/daemon/handlers/file-handlers.ts
|
|
9463
|
-
var
|
|
9464
|
-
var
|
|
9609
|
+
var fs22 = __toESM(require("fs"));
|
|
9610
|
+
var path22 = __toESM(require("path"));
|
|
9465
9611
|
var readline = __toESM(require("readline"));
|
|
9466
9612
|
|
|
9467
9613
|
// src/daemon/permissions/path-classifier.ts
|
|
9468
|
-
var
|
|
9469
|
-
var
|
|
9614
|
+
var path21 = __toESM(require("path"));
|
|
9615
|
+
var fs21 = __toESM(require("fs"));
|
|
9470
9616
|
function classifyPath(targetPath, options) {
|
|
9471
9617
|
const { workspaceRoot, projectRoot } = options;
|
|
9472
|
-
const normalizedWorkspace =
|
|
9473
|
-
const normalizedProject =
|
|
9474
|
-
const resolvedPath =
|
|
9475
|
-
const artifactsDir =
|
|
9476
|
-
if (resolvedPath.startsWith(artifactsDir +
|
|
9618
|
+
const normalizedWorkspace = path21.resolve(workspaceRoot);
|
|
9619
|
+
const normalizedProject = path21.resolve(projectRoot);
|
|
9620
|
+
const resolvedPath = path21.isAbsolute(targetPath) ? path21.resolve(targetPath) : path21.resolve(projectRoot, targetPath);
|
|
9621
|
+
const artifactsDir = path21.join(normalizedWorkspace, "artifacts");
|
|
9622
|
+
if (resolvedPath.startsWith(artifactsDir + path21.sep) || resolvedPath === artifactsDir) {
|
|
9477
9623
|
return {
|
|
9478
9624
|
classification: "artifacts",
|
|
9479
9625
|
resolvedPath
|
|
9480
9626
|
};
|
|
9481
9627
|
}
|
|
9482
|
-
if (!resolvedPath.startsWith(normalizedProject +
|
|
9483
|
-
if (resolvedPath.startsWith(normalizedWorkspace +
|
|
9628
|
+
if (!resolvedPath.startsWith(normalizedProject + path21.sep) && resolvedPath !== normalizedProject) {
|
|
9629
|
+
if (resolvedPath.startsWith(normalizedWorkspace + path21.sep)) {
|
|
9484
9630
|
return {
|
|
9485
9631
|
classification: "unknown",
|
|
9486
9632
|
resolvedPath
|
|
@@ -9491,8 +9637,8 @@ function classifyPath(targetPath, options) {
|
|
|
9491
9637
|
resolvedPath
|
|
9492
9638
|
};
|
|
9493
9639
|
}
|
|
9494
|
-
const relativePath =
|
|
9495
|
-
const pathParts = relativePath.split(
|
|
9640
|
+
const relativePath = path21.relative(normalizedProject, resolvedPath);
|
|
9641
|
+
const pathParts = relativePath.split(path21.sep);
|
|
9496
9642
|
if (pathParts[0] === ".bare") {
|
|
9497
9643
|
return {
|
|
9498
9644
|
classification: "bare_repo",
|
|
@@ -9507,9 +9653,9 @@ function classifyPath(targetPath, options) {
|
|
|
9507
9653
|
}
|
|
9508
9654
|
const firstPart = pathParts[0];
|
|
9509
9655
|
if (firstPart && isModuleUidPattern(firstPart)) {
|
|
9510
|
-
const worktreeDir =
|
|
9511
|
-
const gitFile =
|
|
9512
|
-
const worktreeExists =
|
|
9656
|
+
const worktreeDir = path21.join(normalizedProject, firstPart);
|
|
9657
|
+
const gitFile = path21.join(worktreeDir, ".git");
|
|
9658
|
+
const worktreeExists = fs21.existsSync(gitFile) && fs21.statSync(gitFile).isFile();
|
|
9513
9659
|
return {
|
|
9514
9660
|
classification: "worktree",
|
|
9515
9661
|
moduleUid: firstPart,
|
|
@@ -9526,19 +9672,19 @@ function isModuleUidPattern(str) {
|
|
|
9526
9672
|
return /^EP\d+$/.test(str);
|
|
9527
9673
|
}
|
|
9528
9674
|
function deriveWorkspaceRoot(projectPath) {
|
|
9529
|
-
return
|
|
9675
|
+
return path21.dirname(path21.resolve(projectPath));
|
|
9530
9676
|
}
|
|
9531
9677
|
function validateWorkspacePath(filePath, projectPath) {
|
|
9532
|
-
const normalizedProjectPath =
|
|
9678
|
+
const normalizedProjectPath = path21.resolve(projectPath);
|
|
9533
9679
|
const workspaceRoot = deriveWorkspaceRoot(projectPath);
|
|
9534
|
-
const normalizedWorkspace =
|
|
9535
|
-
const artifactsDir =
|
|
9536
|
-
const absolutePath =
|
|
9537
|
-
const normalizedPath =
|
|
9538
|
-
if (normalizedPath.startsWith(normalizedProjectPath +
|
|
9680
|
+
const normalizedWorkspace = path21.resolve(workspaceRoot);
|
|
9681
|
+
const artifactsDir = path21.join(normalizedWorkspace, "artifacts");
|
|
9682
|
+
const absolutePath = path21.isAbsolute(filePath) ? path21.resolve(filePath) : path21.resolve(projectPath, filePath);
|
|
9683
|
+
const normalizedPath = path21.normalize(absolutePath);
|
|
9684
|
+
if (normalizedPath.startsWith(normalizedProjectPath + path21.sep) || normalizedPath === normalizedProjectPath) {
|
|
9539
9685
|
return normalizedPath;
|
|
9540
9686
|
}
|
|
9541
|
-
if (normalizedPath.startsWith(artifactsDir +
|
|
9687
|
+
if (normalizedPath.startsWith(artifactsDir + path21.sep) || normalizedPath === artifactsDir) {
|
|
9542
9688
|
return normalizedPath;
|
|
9543
9689
|
}
|
|
9544
9690
|
return null;
|
|
@@ -9636,13 +9782,13 @@ async function handleFileRead(command, projectPath) {
|
|
|
9636
9782
|
};
|
|
9637
9783
|
}
|
|
9638
9784
|
try {
|
|
9639
|
-
if (!
|
|
9785
|
+
if (!fs22.existsSync(validPath)) {
|
|
9640
9786
|
return {
|
|
9641
9787
|
success: false,
|
|
9642
9788
|
error: "File not found"
|
|
9643
9789
|
};
|
|
9644
9790
|
}
|
|
9645
|
-
const stats =
|
|
9791
|
+
const stats = fs22.statSync(validPath);
|
|
9646
9792
|
if (stats.isDirectory()) {
|
|
9647
9793
|
return {
|
|
9648
9794
|
success: false,
|
|
@@ -9655,7 +9801,7 @@ async function handleFileRead(command, projectPath) {
|
|
|
9655
9801
|
error: `File too large: ${stats.size} bytes exceeds limit of ${maxSize} bytes`
|
|
9656
9802
|
};
|
|
9657
9803
|
}
|
|
9658
|
-
const buffer =
|
|
9804
|
+
const buffer = fs22.readFileSync(validPath);
|
|
9659
9805
|
let content;
|
|
9660
9806
|
if (encoding === "base64") {
|
|
9661
9807
|
content = buffer.toString("base64");
|
|
@@ -9695,9 +9841,9 @@ async function handleFileWrite(command, projectPath) {
|
|
|
9695
9841
|
}
|
|
9696
9842
|
try {
|
|
9697
9843
|
if (createDirs) {
|
|
9698
|
-
const dirPath =
|
|
9699
|
-
if (!
|
|
9700
|
-
|
|
9844
|
+
const dirPath = path22.dirname(validPath);
|
|
9845
|
+
if (!fs22.existsSync(dirPath)) {
|
|
9846
|
+
fs22.mkdirSync(dirPath, { recursive: true });
|
|
9701
9847
|
}
|
|
9702
9848
|
}
|
|
9703
9849
|
let buffer;
|
|
@@ -9707,8 +9853,8 @@ async function handleFileWrite(command, projectPath) {
|
|
|
9707
9853
|
buffer = Buffer.from(content, "utf8");
|
|
9708
9854
|
}
|
|
9709
9855
|
const tempPath = `${validPath}.tmp.${Date.now()}`;
|
|
9710
|
-
|
|
9711
|
-
|
|
9856
|
+
fs22.writeFileSync(tempPath, buffer);
|
|
9857
|
+
fs22.renameSync(tempPath, validPath);
|
|
9712
9858
|
return {
|
|
9713
9859
|
success: true,
|
|
9714
9860
|
bytesWritten: buffer.length
|
|
@@ -9733,13 +9879,13 @@ async function handleFileList(command, projectPath) {
|
|
|
9733
9879
|
};
|
|
9734
9880
|
}
|
|
9735
9881
|
try {
|
|
9736
|
-
if (!
|
|
9882
|
+
if (!fs22.existsSync(validPath)) {
|
|
9737
9883
|
return {
|
|
9738
9884
|
success: false,
|
|
9739
9885
|
error: "Directory not found"
|
|
9740
9886
|
};
|
|
9741
9887
|
}
|
|
9742
|
-
const stats =
|
|
9888
|
+
const stats = fs22.statSync(validPath);
|
|
9743
9889
|
if (!stats.isDirectory()) {
|
|
9744
9890
|
return {
|
|
9745
9891
|
success: false,
|
|
@@ -9750,11 +9896,11 @@ async function handleFileList(command, projectPath) {
|
|
|
9750
9896
|
if (recursive) {
|
|
9751
9897
|
await listDirectoryRecursive(validPath, validPath, entries, includeHidden);
|
|
9752
9898
|
} else {
|
|
9753
|
-
const dirEntries = await
|
|
9899
|
+
const dirEntries = await fs22.promises.readdir(validPath, { withFileTypes: true });
|
|
9754
9900
|
for (const entry of dirEntries) {
|
|
9755
9901
|
if (!includeHidden && entry.name.startsWith(".")) continue;
|
|
9756
|
-
const entryPath =
|
|
9757
|
-
const entryStats = await
|
|
9902
|
+
const entryPath = path22.join(validPath, entry.name);
|
|
9903
|
+
const entryStats = await fs22.promises.stat(entryPath);
|
|
9758
9904
|
entries.push({
|
|
9759
9905
|
name: entry.name,
|
|
9760
9906
|
type: entry.isDirectory() ? "directory" : "file",
|
|
@@ -9776,13 +9922,13 @@ async function handleFileList(command, projectPath) {
|
|
|
9776
9922
|
}
|
|
9777
9923
|
}
|
|
9778
9924
|
async function listDirectoryRecursive(basePath, currentPath, entries, includeHidden) {
|
|
9779
|
-
const dirEntries = await
|
|
9925
|
+
const dirEntries = await fs22.promises.readdir(currentPath, { withFileTypes: true });
|
|
9780
9926
|
for (const entry of dirEntries) {
|
|
9781
9927
|
if (!includeHidden && entry.name.startsWith(".")) continue;
|
|
9782
|
-
const entryPath =
|
|
9783
|
-
const relativePath =
|
|
9928
|
+
const entryPath = path22.join(currentPath, entry.name);
|
|
9929
|
+
const relativePath = path22.relative(basePath, entryPath);
|
|
9784
9930
|
try {
|
|
9785
|
-
const entryStats = await
|
|
9931
|
+
const entryStats = await fs22.promises.stat(entryPath);
|
|
9786
9932
|
entries.push({
|
|
9787
9933
|
name: relativePath,
|
|
9788
9934
|
type: entry.isDirectory() ? "directory" : "file",
|
|
@@ -9806,7 +9952,7 @@ async function handleFileSearch(command, projectPath) {
|
|
|
9806
9952
|
};
|
|
9807
9953
|
}
|
|
9808
9954
|
try {
|
|
9809
|
-
if (!
|
|
9955
|
+
if (!fs22.existsSync(validPath)) {
|
|
9810
9956
|
return {
|
|
9811
9957
|
success: false,
|
|
9812
9958
|
error: "Directory not found"
|
|
@@ -9896,12 +10042,12 @@ function findClosingBracket(pattern, start) {
|
|
|
9896
10042
|
}
|
|
9897
10043
|
async function searchFilesRecursive(basePath, currentPath, pattern, files, maxResults) {
|
|
9898
10044
|
if (files.length >= maxResults) return;
|
|
9899
|
-
const entries = await
|
|
10045
|
+
const entries = await fs22.promises.readdir(currentPath, { withFileTypes: true });
|
|
9900
10046
|
for (const entry of entries) {
|
|
9901
10047
|
if (files.length >= maxResults) return;
|
|
9902
10048
|
if (entry.name.startsWith(".")) continue;
|
|
9903
|
-
const entryPath =
|
|
9904
|
-
const relativePath =
|
|
10049
|
+
const entryPath = path22.join(currentPath, entry.name);
|
|
10050
|
+
const relativePath = path22.relative(basePath, entryPath);
|
|
9905
10051
|
try {
|
|
9906
10052
|
if (entry.isDirectory()) {
|
|
9907
10053
|
await searchFilesRecursive(basePath, entryPath, pattern, files, maxResults);
|
|
@@ -9929,7 +10075,7 @@ async function handleFileGrep(command, projectPath) {
|
|
|
9929
10075
|
};
|
|
9930
10076
|
}
|
|
9931
10077
|
try {
|
|
9932
|
-
if (!
|
|
10078
|
+
if (!fs22.existsSync(validPath)) {
|
|
9933
10079
|
return {
|
|
9934
10080
|
success: false,
|
|
9935
10081
|
error: "Path not found"
|
|
@@ -9938,7 +10084,7 @@ async function handleFileGrep(command, projectPath) {
|
|
|
9938
10084
|
const matches = [];
|
|
9939
10085
|
const searchRegex = new RegExp(pattern, caseSensitive ? "" : "i");
|
|
9940
10086
|
const fileGlobRegex = globToRegex(filePattern);
|
|
9941
|
-
const stats =
|
|
10087
|
+
const stats = fs22.statSync(validPath);
|
|
9942
10088
|
if (stats.isFile()) {
|
|
9943
10089
|
await grepFile(validPath, validPath, searchRegex, matches, effectiveMaxResults);
|
|
9944
10090
|
} else {
|
|
@@ -9959,8 +10105,8 @@ async function handleFileGrep(command, projectPath) {
|
|
|
9959
10105
|
}
|
|
9960
10106
|
async function grepFile(basePath, filePath, pattern, matches, maxResults) {
|
|
9961
10107
|
if (matches.length >= maxResults) return;
|
|
9962
|
-
const relativePath =
|
|
9963
|
-
const fileStream =
|
|
10108
|
+
const relativePath = path22.relative(basePath, filePath);
|
|
10109
|
+
const fileStream = fs22.createReadStream(filePath, { encoding: "utf8" });
|
|
9964
10110
|
const rl = readline.createInterface({
|
|
9965
10111
|
input: fileStream,
|
|
9966
10112
|
crlfDelay: Infinity
|
|
@@ -9974,7 +10120,7 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
|
|
|
9974
10120
|
}
|
|
9975
10121
|
if (pattern.test(line)) {
|
|
9976
10122
|
matches.push({
|
|
9977
|
-
file: relativePath ||
|
|
10123
|
+
file: relativePath || path22.basename(filePath),
|
|
9978
10124
|
line: lineNumber,
|
|
9979
10125
|
content: line.slice(0, 500)
|
|
9980
10126
|
// Truncate long lines
|
|
@@ -9984,11 +10130,11 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
|
|
|
9984
10130
|
}
|
|
9985
10131
|
async function grepDirectoryRecursive(basePath, currentPath, searchPattern, filePattern, matches, maxResults) {
|
|
9986
10132
|
if (matches.length >= maxResults) return;
|
|
9987
|
-
const entries = await
|
|
10133
|
+
const entries = await fs22.promises.readdir(currentPath, { withFileTypes: true });
|
|
9988
10134
|
for (const entry of entries) {
|
|
9989
10135
|
if (matches.length >= maxResults) return;
|
|
9990
10136
|
if (entry.name.startsWith(".")) continue;
|
|
9991
|
-
const entryPath =
|
|
10137
|
+
const entryPath = path22.join(currentPath, entry.name);
|
|
9992
10138
|
try {
|
|
9993
10139
|
if (entry.isDirectory()) {
|
|
9994
10140
|
await grepDirectoryRecursive(basePath, entryPath, searchPattern, filePattern, matches, maxResults);
|
|
@@ -10016,13 +10162,13 @@ async function handleFileEdit(command, projectPath) {
|
|
|
10016
10162
|
};
|
|
10017
10163
|
}
|
|
10018
10164
|
try {
|
|
10019
|
-
if (!
|
|
10165
|
+
if (!fs22.existsSync(validPath)) {
|
|
10020
10166
|
return {
|
|
10021
10167
|
success: false,
|
|
10022
10168
|
error: "File not found"
|
|
10023
10169
|
};
|
|
10024
10170
|
}
|
|
10025
|
-
const stats =
|
|
10171
|
+
const stats = fs22.statSync(validPath);
|
|
10026
10172
|
if (stats.isDirectory()) {
|
|
10027
10173
|
return {
|
|
10028
10174
|
success: false,
|
|
@@ -10036,7 +10182,7 @@ async function handleFileEdit(command, projectPath) {
|
|
|
10036
10182
|
error: `File too large for edit operation: ${stats.size} bytes exceeds limit of ${MAX_EDIT_SIZE} bytes (10MB). Use write-file for large files.`
|
|
10037
10183
|
};
|
|
10038
10184
|
}
|
|
10039
|
-
const content =
|
|
10185
|
+
const content = fs22.readFileSync(validPath, "utf8");
|
|
10040
10186
|
const occurrences = content.split(oldString).length - 1;
|
|
10041
10187
|
if (occurrences === 0) {
|
|
10042
10188
|
return {
|
|
@@ -10053,8 +10199,8 @@ async function handleFileEdit(command, projectPath) {
|
|
|
10053
10199
|
const newContent = replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);
|
|
10054
10200
|
const replacements = replaceAll ? occurrences : 1;
|
|
10055
10201
|
const tempPath = `${validPath}.tmp.${Date.now()}`;
|
|
10056
|
-
|
|
10057
|
-
|
|
10202
|
+
fs22.writeFileSync(tempPath, newContent, "utf8");
|
|
10203
|
+
fs22.renameSync(tempPath, validPath);
|
|
10058
10204
|
const newSize = Buffer.byteLength(newContent, "utf8");
|
|
10059
10205
|
return {
|
|
10060
10206
|
success: true,
|
|
@@ -10079,7 +10225,7 @@ async function handleFileDelete(command, projectPath) {
|
|
|
10079
10225
|
error: "Invalid path: directory traversal not allowed"
|
|
10080
10226
|
};
|
|
10081
10227
|
}
|
|
10082
|
-
const normalizedProjectPath =
|
|
10228
|
+
const normalizedProjectPath = path22.resolve(projectPath);
|
|
10083
10229
|
if (validPath === normalizedProjectPath) {
|
|
10084
10230
|
return {
|
|
10085
10231
|
success: false,
|
|
@@ -10094,13 +10240,13 @@ async function handleFileDelete(command, projectPath) {
|
|
|
10094
10240
|
};
|
|
10095
10241
|
}
|
|
10096
10242
|
try {
|
|
10097
|
-
if (!
|
|
10243
|
+
if (!fs22.existsSync(validPath)) {
|
|
10098
10244
|
return {
|
|
10099
10245
|
success: false,
|
|
10100
10246
|
error: "Path not found"
|
|
10101
10247
|
};
|
|
10102
10248
|
}
|
|
10103
|
-
const stats =
|
|
10249
|
+
const stats = fs22.statSync(validPath);
|
|
10104
10250
|
const isDirectory = stats.isDirectory();
|
|
10105
10251
|
if (isDirectory && !recursive) {
|
|
10106
10252
|
return {
|
|
@@ -10109,9 +10255,9 @@ async function handleFileDelete(command, projectPath) {
|
|
|
10109
10255
|
};
|
|
10110
10256
|
}
|
|
10111
10257
|
if (isDirectory) {
|
|
10112
|
-
|
|
10258
|
+
fs22.rmSync(validPath, { recursive: true, force: true });
|
|
10113
10259
|
} else {
|
|
10114
|
-
|
|
10260
|
+
fs22.unlinkSync(validPath);
|
|
10115
10261
|
}
|
|
10116
10262
|
return {
|
|
10117
10263
|
success: true,
|
|
@@ -10144,8 +10290,8 @@ async function handleFileMkdir(command, projectPath) {
|
|
|
10144
10290
|
};
|
|
10145
10291
|
}
|
|
10146
10292
|
try {
|
|
10147
|
-
if (
|
|
10148
|
-
const stats =
|
|
10293
|
+
if (fs22.existsSync(validPath)) {
|
|
10294
|
+
const stats = fs22.statSync(validPath);
|
|
10149
10295
|
if (stats.isDirectory()) {
|
|
10150
10296
|
return {
|
|
10151
10297
|
success: true,
|
|
@@ -10160,7 +10306,7 @@ async function handleFileMkdir(command, projectPath) {
|
|
|
10160
10306
|
}
|
|
10161
10307
|
}
|
|
10162
10308
|
const modeNum = parseInt(mode, 8);
|
|
10163
|
-
|
|
10309
|
+
fs22.mkdirSync(validPath, { recursive: true, mode: modeNum });
|
|
10164
10310
|
return {
|
|
10165
10311
|
success: true,
|
|
10166
10312
|
created: true
|
|
@@ -10187,7 +10333,7 @@ async function handleExec(command, projectPath) {
|
|
|
10187
10333
|
env = {}
|
|
10188
10334
|
} = command;
|
|
10189
10335
|
const effectiveTimeout = Math.min(Math.max(timeout, 1e3), MAX_TIMEOUT);
|
|
10190
|
-
return new Promise((
|
|
10336
|
+
return new Promise((resolve6) => {
|
|
10191
10337
|
let stdout = "";
|
|
10192
10338
|
let stderr = "";
|
|
10193
10339
|
let timedOut = false;
|
|
@@ -10195,7 +10341,7 @@ async function handleExec(command, projectPath) {
|
|
|
10195
10341
|
const done = (result) => {
|
|
10196
10342
|
if (resolved) return;
|
|
10197
10343
|
resolved = true;
|
|
10198
|
-
|
|
10344
|
+
resolve6(result);
|
|
10199
10345
|
};
|
|
10200
10346
|
try {
|
|
10201
10347
|
const proc = (0, import_child_process11.spawn)(cmd, {
|
|
@@ -10259,9 +10405,9 @@ async function handleExec(command, projectPath) {
|
|
|
10259
10405
|
}
|
|
10260
10406
|
|
|
10261
10407
|
// src/daemon/handlers/worktree-handlers.ts
|
|
10262
|
-
var
|
|
10263
|
-
var
|
|
10264
|
-
var
|
|
10408
|
+
var path28 = __toESM(require("path"));
|
|
10409
|
+
var fs28 = __toESM(require("fs"));
|
|
10410
|
+
var os13 = __toESM(require("os"));
|
|
10265
10411
|
var import_child_process14 = require("child_process");
|
|
10266
10412
|
var import_util2 = require("util");
|
|
10267
10413
|
|
|
@@ -10291,52 +10437,52 @@ var import_fs = require("fs");
|
|
|
10291
10437
|
// src/preview/dev-server-runner.ts
|
|
10292
10438
|
var import_child_process13 = require("child_process");
|
|
10293
10439
|
var http = __toESM(require("http"));
|
|
10294
|
-
var
|
|
10295
|
-
var
|
|
10440
|
+
var fs25 = __toESM(require("fs"));
|
|
10441
|
+
var path25 = __toESM(require("path"));
|
|
10296
10442
|
var import_events2 = require("events");
|
|
10297
10443
|
var import_core12 = __toESM(require_dist());
|
|
10298
10444
|
|
|
10299
10445
|
// src/utils/port-check.ts
|
|
10300
10446
|
var net2 = __toESM(require("net"));
|
|
10301
10447
|
async function isPortInUse(port) {
|
|
10302
|
-
return new Promise((
|
|
10448
|
+
return new Promise((resolve6) => {
|
|
10303
10449
|
const server = net2.createServer();
|
|
10304
10450
|
server.once("error", (err) => {
|
|
10305
10451
|
if (err.code === "EADDRINUSE") {
|
|
10306
|
-
|
|
10452
|
+
resolve6(true);
|
|
10307
10453
|
} else {
|
|
10308
|
-
|
|
10454
|
+
resolve6(false);
|
|
10309
10455
|
}
|
|
10310
10456
|
});
|
|
10311
10457
|
server.once("listening", () => {
|
|
10312
10458
|
server.close();
|
|
10313
|
-
|
|
10459
|
+
resolve6(false);
|
|
10314
10460
|
});
|
|
10315
10461
|
server.listen(port);
|
|
10316
10462
|
});
|
|
10317
10463
|
}
|
|
10318
10464
|
|
|
10319
10465
|
// src/utils/env-cache.ts
|
|
10320
|
-
var
|
|
10321
|
-
var
|
|
10322
|
-
var
|
|
10466
|
+
var fs23 = __toESM(require("fs"));
|
|
10467
|
+
var path23 = __toESM(require("path"));
|
|
10468
|
+
var os11 = __toESM(require("os"));
|
|
10323
10469
|
var DEFAULT_CACHE_TTL = 60;
|
|
10324
|
-
var CACHE_DIR =
|
|
10470
|
+
var CACHE_DIR = path23.join(os11.homedir(), ".episoda", "cache");
|
|
10325
10471
|
function getCacheFilePath(projectId) {
|
|
10326
|
-
return
|
|
10472
|
+
return path23.join(CACHE_DIR, `env-vars-${projectId}.json`);
|
|
10327
10473
|
}
|
|
10328
10474
|
function ensureCacheDir() {
|
|
10329
|
-
if (!
|
|
10330
|
-
|
|
10475
|
+
if (!fs23.existsSync(CACHE_DIR)) {
|
|
10476
|
+
fs23.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
|
|
10331
10477
|
}
|
|
10332
10478
|
}
|
|
10333
10479
|
function readCache(projectId) {
|
|
10334
10480
|
try {
|
|
10335
10481
|
const cacheFile = getCacheFilePath(projectId);
|
|
10336
|
-
if (!
|
|
10482
|
+
if (!fs23.existsSync(cacheFile)) {
|
|
10337
10483
|
return null;
|
|
10338
10484
|
}
|
|
10339
|
-
const content =
|
|
10485
|
+
const content = fs23.readFileSync(cacheFile, "utf-8");
|
|
10340
10486
|
const data = JSON.parse(content);
|
|
10341
10487
|
if (!data.vars || typeof data.vars !== "object" || !data.fetchedAt) {
|
|
10342
10488
|
return null;
|
|
@@ -10355,7 +10501,7 @@ function writeCache(projectId, vars) {
|
|
|
10355
10501
|
fetchedAt: Date.now(),
|
|
10356
10502
|
projectId
|
|
10357
10503
|
};
|
|
10358
|
-
|
|
10504
|
+
fs23.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
|
|
10359
10505
|
} catch (error) {
|
|
10360
10506
|
console.warn("[env-cache] Failed to write cache:", error instanceof Error ? error.message : error);
|
|
10361
10507
|
}
|
|
@@ -10424,11 +10570,11 @@ No cached values available as fallback.`
|
|
|
10424
10570
|
}
|
|
10425
10571
|
|
|
10426
10572
|
// src/preview/dev-server-registry.ts
|
|
10427
|
-
var
|
|
10428
|
-
var
|
|
10429
|
-
var
|
|
10573
|
+
var fs24 = __toESM(require("fs"));
|
|
10574
|
+
var path24 = __toESM(require("path"));
|
|
10575
|
+
var os12 = __toESM(require("os"));
|
|
10430
10576
|
var import_child_process12 = require("child_process");
|
|
10431
|
-
var DEV_SERVER_REGISTRY_DIR =
|
|
10577
|
+
var DEV_SERVER_REGISTRY_DIR = path24.join(os12.homedir(), ".episoda", "dev-servers");
|
|
10432
10578
|
var DevServerRegistry = class {
|
|
10433
10579
|
constructor() {
|
|
10434
10580
|
this.ensureRegistryDir();
|
|
@@ -10438,9 +10584,9 @@ var DevServerRegistry = class {
|
|
|
10438
10584
|
*/
|
|
10439
10585
|
ensureRegistryDir() {
|
|
10440
10586
|
try {
|
|
10441
|
-
if (!
|
|
10587
|
+
if (!fs24.existsSync(DEV_SERVER_REGISTRY_DIR)) {
|
|
10442
10588
|
console.log(`[DevServerRegistry] EP1042: Creating registry directory: ${DEV_SERVER_REGISTRY_DIR}`);
|
|
10443
|
-
|
|
10589
|
+
fs24.mkdirSync(DEV_SERVER_REGISTRY_DIR, { recursive: true });
|
|
10444
10590
|
}
|
|
10445
10591
|
} catch (error) {
|
|
10446
10592
|
console.error(`[DevServerRegistry] EP1042: Failed to create registry directory:`, error);
|
|
@@ -10451,7 +10597,7 @@ var DevServerRegistry = class {
|
|
|
10451
10597
|
* Get the registry file path for a module
|
|
10452
10598
|
*/
|
|
10453
10599
|
getEntryPath(moduleUid) {
|
|
10454
|
-
return
|
|
10600
|
+
return path24.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
|
|
10455
10601
|
}
|
|
10456
10602
|
/**
|
|
10457
10603
|
* Register a dev server
|
|
@@ -10462,8 +10608,10 @@ var DevServerRegistry = class {
|
|
|
10462
10608
|
try {
|
|
10463
10609
|
this.ensureRegistryDir();
|
|
10464
10610
|
const entryPath = this.getEntryPath(entry.moduleUid);
|
|
10465
|
-
|
|
10466
|
-
console.log(
|
|
10611
|
+
fs24.writeFileSync(entryPath, JSON.stringify(entry, null, 2), "utf8");
|
|
10612
|
+
console.log(
|
|
10613
|
+
`[DevServerRegistry] EP1042: Registered ${entry.moduleUid} (PID ${entry.pid}, port ${entry.port}${entry.wsPort ? `, wsPort ${entry.wsPort}` : ""})`
|
|
10614
|
+
);
|
|
10467
10615
|
} catch (error) {
|
|
10468
10616
|
console.error(`[DevServerRegistry] EP1042: Failed to register ${entry.moduleUid}:`, error);
|
|
10469
10617
|
}
|
|
@@ -10476,8 +10624,8 @@ var DevServerRegistry = class {
|
|
|
10476
10624
|
unregister(moduleUid) {
|
|
10477
10625
|
try {
|
|
10478
10626
|
const entryPath = this.getEntryPath(moduleUid);
|
|
10479
|
-
if (
|
|
10480
|
-
|
|
10627
|
+
if (fs24.existsSync(entryPath)) {
|
|
10628
|
+
fs24.unlinkSync(entryPath);
|
|
10481
10629
|
console.log(`[DevServerRegistry] EP1042: Unregistered ${moduleUid}`);
|
|
10482
10630
|
}
|
|
10483
10631
|
} catch (error) {
|
|
@@ -10493,16 +10641,21 @@ var DevServerRegistry = class {
|
|
|
10493
10641
|
getByModule(moduleUid) {
|
|
10494
10642
|
try {
|
|
10495
10643
|
const entryPath = this.getEntryPath(moduleUid);
|
|
10496
|
-
if (!
|
|
10644
|
+
if (!fs24.existsSync(entryPath)) {
|
|
10497
10645
|
return null;
|
|
10498
10646
|
}
|
|
10499
|
-
const content =
|
|
10647
|
+
const content = fs24.readFileSync(entryPath, "utf8");
|
|
10500
10648
|
const entry = JSON.parse(content);
|
|
10501
10649
|
if (!entry.pid || !entry.port || !entry.worktreePath) {
|
|
10502
10650
|
console.warn(`[DevServerRegistry] EP1042: Invalid entry for ${moduleUid}, removing`);
|
|
10503
10651
|
this.unregister(moduleUid);
|
|
10504
10652
|
return null;
|
|
10505
10653
|
}
|
|
10654
|
+
if (entry.wsPort !== void 0 && (typeof entry.wsPort !== "number" || entry.wsPort <= 0)) {
|
|
10655
|
+
console.warn(`[DevServerRegistry] EP1042: Invalid wsPort for ${moduleUid}, removing`);
|
|
10656
|
+
this.unregister(moduleUid);
|
|
10657
|
+
return null;
|
|
10658
|
+
}
|
|
10506
10659
|
if (!this.isProcessRunning(entry.pid)) {
|
|
10507
10660
|
console.log(`[DevServerRegistry] EP1042: PID ${entry.pid} for ${moduleUid} is not running, removing stale entry`);
|
|
10508
10661
|
this.unregister(moduleUid);
|
|
@@ -10534,7 +10687,7 @@ var DevServerRegistry = class {
|
|
|
10534
10687
|
const entries = [];
|
|
10535
10688
|
try {
|
|
10536
10689
|
this.ensureRegistryDir();
|
|
10537
|
-
const files =
|
|
10690
|
+
const files = fs24.readdirSync(DEV_SERVER_REGISTRY_DIR).filter((f) => f.endsWith(".json"));
|
|
10538
10691
|
for (const file of files) {
|
|
10539
10692
|
const moduleUid = file.replace(".json", "");
|
|
10540
10693
|
const entry = this.getByModule(moduleUid);
|
|
@@ -10592,7 +10745,7 @@ var DevServerRegistry = class {
|
|
|
10592
10745
|
return null;
|
|
10593
10746
|
} catch {
|
|
10594
10747
|
try {
|
|
10595
|
-
return
|
|
10748
|
+
return fs24.readlinkSync(`/proc/${pid}/cwd`);
|
|
10596
10749
|
} catch {
|
|
10597
10750
|
return null;
|
|
10598
10751
|
}
|
|
@@ -10649,6 +10802,10 @@ var DevServerRegistry = class {
|
|
|
10649
10802
|
}
|
|
10650
10803
|
cleaned.push(entry.pid);
|
|
10651
10804
|
}
|
|
10805
|
+
if (entry.wsPort) {
|
|
10806
|
+
const wsPids = this.killWsServerOnPort(entry.wsPort, entry.worktreePath);
|
|
10807
|
+
cleaned.push(...wsPids);
|
|
10808
|
+
}
|
|
10652
10809
|
this.unregister(entry.moduleUid);
|
|
10653
10810
|
}
|
|
10654
10811
|
}
|
|
@@ -10702,7 +10859,39 @@ var DevServerRegistry = class {
|
|
|
10702
10859
|
return killed;
|
|
10703
10860
|
}
|
|
10704
10861
|
wait(ms) {
|
|
10705
|
-
return new Promise((
|
|
10862
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
10863
|
+
}
|
|
10864
|
+
killWsServerOnPort(wsPort, worktreePath) {
|
|
10865
|
+
const killed = [];
|
|
10866
|
+
try {
|
|
10867
|
+
const output = (0, import_child_process12.execSync)(`lsof -ti:${wsPort} 2>/dev/null || true`, {
|
|
10868
|
+
encoding: "utf8",
|
|
10869
|
+
timeout: 5e3
|
|
10870
|
+
}).trim();
|
|
10871
|
+
if (!output) {
|
|
10872
|
+
return killed;
|
|
10873
|
+
}
|
|
10874
|
+
const expectedPath = path24.resolve(worktreePath);
|
|
10875
|
+
const pids = output.split("\n").map((line) => parseInt(line, 10)).filter((pid) => !isNaN(pid) && pid > 0);
|
|
10876
|
+
for (const pid of pids) {
|
|
10877
|
+
const command = (0, import_child_process12.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8", timeout: 5e3 }).trim();
|
|
10878
|
+
const cwd = this.getProcessCwd(pid);
|
|
10879
|
+
const cwdMatches = cwd ? path24.resolve(cwd) === expectedPath : false;
|
|
10880
|
+
const isWsServer = command.includes("ws-server.js") || command.includes("build:ws-server");
|
|
10881
|
+
if (!isWsServer || !cwdMatches) {
|
|
10882
|
+
continue;
|
|
10883
|
+
}
|
|
10884
|
+
this.killProcess(pid, "SIGTERM");
|
|
10885
|
+
killed.push(pid);
|
|
10886
|
+
}
|
|
10887
|
+
for (const pid of killed) {
|
|
10888
|
+
if (this.isProcessRunning(pid)) {
|
|
10889
|
+
this.killProcess(pid, "SIGKILL");
|
|
10890
|
+
}
|
|
10891
|
+
}
|
|
10892
|
+
} catch {
|
|
10893
|
+
}
|
|
10894
|
+
return killed;
|
|
10706
10895
|
}
|
|
10707
10896
|
};
|
|
10708
10897
|
var registryInstance = null;
|
|
@@ -10729,6 +10918,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10729
10918
|
const {
|
|
10730
10919
|
projectPath,
|
|
10731
10920
|
port,
|
|
10921
|
+
wsPort,
|
|
10732
10922
|
moduleUid,
|
|
10733
10923
|
customCommand,
|
|
10734
10924
|
autoRestart = true
|
|
@@ -10738,13 +10928,25 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10738
10928
|
const existingEntry = registry.getByPort(port);
|
|
10739
10929
|
if (existingEntry) {
|
|
10740
10930
|
if (existingEntry.worktreePath === projectPath && existingEntry.moduleUid === moduleUid) {
|
|
10741
|
-
|
|
10742
|
-
|
|
10931
|
+
const wsHealthy = await this.checkWsHealth(wsPort);
|
|
10932
|
+
if (wsHealthy) {
|
|
10933
|
+
console.log(`[DevServerRunner] EP1042: Correct server already running on port ${port} for ${moduleUid}`);
|
|
10934
|
+
return { success: true, alreadyRunning: true };
|
|
10935
|
+
}
|
|
10936
|
+
console.warn(
|
|
10937
|
+
`[DevServerRunner] EP1406: Existing dev server for ${moduleUid} has unhealthy ws-server on port ${wsPort}, restarting`
|
|
10938
|
+
);
|
|
10939
|
+
await this.killProcessOnPort(port);
|
|
10940
|
+
await this.cleanupWsServerOnPort(wsPort, projectPath);
|
|
10941
|
+
registry.unregister(existingEntry.moduleUid);
|
|
10942
|
+
this.servers.delete(existingEntry.moduleUid);
|
|
10943
|
+
}
|
|
10944
|
+
if (existingEntry.worktreePath !== projectPath || existingEntry.moduleUid !== moduleUid) {
|
|
10945
|
+
console.log(`[DevServerRunner] EP1042: Port ${port} owned by ${existingEntry.moduleUid} (${existingEntry.worktreePath}), killing...`);
|
|
10946
|
+
await this.killProcessOnPort(port);
|
|
10947
|
+
registry.unregister(existingEntry.moduleUid);
|
|
10948
|
+
this.servers.delete(existingEntry.moduleUid);
|
|
10743
10949
|
}
|
|
10744
|
-
console.log(`[DevServerRunner] EP1042: Port ${port} owned by ${existingEntry.moduleUid} (${existingEntry.worktreePath}), killing...`);
|
|
10745
|
-
await this.killProcessOnPort(port);
|
|
10746
|
-
registry.unregister(existingEntry.moduleUid);
|
|
10747
|
-
this.servers.delete(existingEntry.moduleUid);
|
|
10748
10950
|
} else {
|
|
10749
10951
|
console.log(`[DevServerRunner] EP1042: Unknown process on port ${port}, killing...`);
|
|
10750
10952
|
await this.killProcessOnPort(port);
|
|
@@ -10754,21 +10956,34 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10754
10956
|
return { success: false, error: `Port ${port} still in use after cleanup` };
|
|
10755
10957
|
}
|
|
10756
10958
|
}
|
|
10959
|
+
const wsPortAvailability = await this.ensureWsPortAvailable(projectPath, wsPort);
|
|
10960
|
+
if (wsPortAvailability) {
|
|
10961
|
+
return wsPortAvailability;
|
|
10962
|
+
}
|
|
10757
10963
|
const existing = this.servers.get(moduleUid);
|
|
10758
10964
|
if (existing && !existing.process.killed) {
|
|
10759
|
-
|
|
10760
|
-
|
|
10965
|
+
const wsHealthy = await this.checkWsHealth(existing.wsPort);
|
|
10966
|
+
if (wsHealthy) {
|
|
10967
|
+
console.log(`[DevServerRunner] Process already exists for ${moduleUid}`);
|
|
10968
|
+
return { success: true, alreadyRunning: true };
|
|
10969
|
+
}
|
|
10970
|
+
console.warn(
|
|
10971
|
+
`[DevServerRunner] EP1406: In-memory process exists for ${moduleUid} but ws-server is unhealthy on ${existing.wsPort}, restarting`
|
|
10972
|
+
);
|
|
10973
|
+
await this.stop(moduleUid);
|
|
10974
|
+
await this.wait(500);
|
|
10761
10975
|
}
|
|
10762
|
-
console.log(`[DevServerRunner] Starting dev server for ${moduleUid} on port ${port}...`);
|
|
10976
|
+
console.log(`[DevServerRunner] Starting dev server for ${moduleUid} on port ${port} (ws:${wsPort})...`);
|
|
10763
10977
|
const injectedEnvVars = await this.fetchEnvVars(projectPath);
|
|
10764
10978
|
try {
|
|
10765
10979
|
const logPath = this.getLogFilePath(moduleUid);
|
|
10766
|
-
const process2 = this.spawnProcess(projectPath, port, moduleUid, logPath, customCommand, injectedEnvVars);
|
|
10980
|
+
const process2 = this.spawnProcess(projectPath, port, wsPort, moduleUid, logPath, customCommand, injectedEnvVars);
|
|
10767
10981
|
const state = {
|
|
10768
10982
|
process: process2,
|
|
10769
10983
|
moduleUid,
|
|
10770
10984
|
projectPath,
|
|
10771
10985
|
port,
|
|
10986
|
+
wsPort,
|
|
10772
10987
|
startedAt: /* @__PURE__ */ new Date(),
|
|
10773
10988
|
restartCount: 0,
|
|
10774
10989
|
lastRestartAt: null,
|
|
@@ -10788,16 +11003,26 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10788
11003
|
this.writeToLog(logPath, "Failed to start within timeout", true);
|
|
10789
11004
|
return { success: false, error: "Dev server failed to start within timeout" };
|
|
10790
11005
|
}
|
|
11006
|
+
console.log(`[DevServerRunner] Waiting for ws-server on port ${wsPort}...`);
|
|
11007
|
+
const wsReady = await this.waitForWsPort(wsPort, DEV_SERVER_CONSTANTS.STARTUP_TIMEOUT_MS);
|
|
11008
|
+
if (!wsReady) {
|
|
11009
|
+
process2.kill();
|
|
11010
|
+
this.servers.delete(moduleUid);
|
|
11011
|
+
const wsStartError = this.detectWsStartupError(logPath, wsPort);
|
|
11012
|
+
this.writeToLog(logPath, wsStartError, true);
|
|
11013
|
+
return { success: false, error: wsStartError };
|
|
11014
|
+
}
|
|
10791
11015
|
if (process2.pid) {
|
|
10792
11016
|
registry.register({
|
|
10793
11017
|
pid: process2.pid,
|
|
10794
11018
|
port,
|
|
11019
|
+
wsPort,
|
|
10795
11020
|
worktreePath: projectPath,
|
|
10796
11021
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10797
11022
|
moduleUid
|
|
10798
11023
|
});
|
|
10799
11024
|
}
|
|
10800
|
-
console.log(`[DevServerRunner] Server started successfully on
|
|
11025
|
+
console.log(`[DevServerRunner] Server started successfully on ports app:${port} ws:${wsPort}`);
|
|
10801
11026
|
this.writeToLog(logPath, "Server started successfully");
|
|
10802
11027
|
this.emit("started", moduleUid, port);
|
|
10803
11028
|
return { success: true };
|
|
@@ -10830,6 +11055,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10830
11055
|
state.process.kill("SIGKILL");
|
|
10831
11056
|
}
|
|
10832
11057
|
}
|
|
11058
|
+
await this.cleanupWsServerOnPort(state.wsPort, state.projectPath);
|
|
10833
11059
|
} else if (registryEntry) {
|
|
10834
11060
|
console.log(`[DevServerRunner] EP1042: Stopping orphaned server for ${moduleUid} (PID ${registryEntry.pid})`);
|
|
10835
11061
|
if (registry.isProcessRunning(registryEntry.pid)) {
|
|
@@ -10839,6 +11065,9 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10839
11065
|
registry.killProcess(registryEntry.pid, "SIGKILL");
|
|
10840
11066
|
}
|
|
10841
11067
|
}
|
|
11068
|
+
if (registryEntry.wsPort) {
|
|
11069
|
+
await this.cleanupWsServerOnPort(registryEntry.wsPort, registryEntry.worktreePath);
|
|
11070
|
+
}
|
|
10842
11071
|
}
|
|
10843
11072
|
this.servers.delete(moduleUid);
|
|
10844
11073
|
registry.unregister(moduleUid);
|
|
@@ -10852,7 +11081,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10852
11081
|
if (!state) {
|
|
10853
11082
|
return { success: false, error: `No dev server found for ${moduleUid}` };
|
|
10854
11083
|
}
|
|
10855
|
-
const { projectPath, port, autoRestartEnabled, customCommand, logFile } = state;
|
|
11084
|
+
const { projectPath, port, wsPort, autoRestartEnabled, customCommand, logFile } = state;
|
|
10856
11085
|
console.log(`[DevServerRunner] Restarting server for ${moduleUid}...`);
|
|
10857
11086
|
this.writeToLog(logFile, "Manual restart requested");
|
|
10858
11087
|
await this.stop(moduleUid);
|
|
@@ -10863,6 +11092,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10863
11092
|
return this.start({
|
|
10864
11093
|
projectPath,
|
|
10865
11094
|
port,
|
|
11095
|
+
wsPort,
|
|
10866
11096
|
moduleUid,
|
|
10867
11097
|
customCommand,
|
|
10868
11098
|
autoRestart: autoRestartEnabled
|
|
@@ -10878,6 +11108,16 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10878
11108
|
}
|
|
10879
11109
|
return this.checkHealth(state.port);
|
|
10880
11110
|
}
|
|
11111
|
+
/**
|
|
11112
|
+
* Check if ws-server for a module is healthy
|
|
11113
|
+
*/
|
|
11114
|
+
async isWsHealthy(moduleUid) {
|
|
11115
|
+
const state = this.servers.get(moduleUid);
|
|
11116
|
+
if (!state) {
|
|
11117
|
+
return false;
|
|
11118
|
+
}
|
|
11119
|
+
return this.checkWsHealth(state.wsPort);
|
|
11120
|
+
}
|
|
10881
11121
|
/**
|
|
10882
11122
|
* Check if a dev server is running
|
|
10883
11123
|
*/
|
|
@@ -10949,6 +11189,125 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10949
11189
|
return false;
|
|
10950
11190
|
}
|
|
10951
11191
|
}
|
|
11192
|
+
async ensureWsPortAvailable(projectPath, wsPort) {
|
|
11193
|
+
if (!await isPortInUse(wsPort)) {
|
|
11194
|
+
return null;
|
|
11195
|
+
}
|
|
11196
|
+
const pids = this.getPidsOnPort(wsPort);
|
|
11197
|
+
if (pids.length === 0) {
|
|
11198
|
+
return { success: false, error: `WS_BIND_CONFLICT: WS port ${wsPort} is already in use.` };
|
|
11199
|
+
}
|
|
11200
|
+
const registry = getDevServerRegistry();
|
|
11201
|
+
const expectedPath = path25.resolve(projectPath);
|
|
11202
|
+
const unsafe = [];
|
|
11203
|
+
const killable = [];
|
|
11204
|
+
for (const pid of pids) {
|
|
11205
|
+
const command = this.getProcessCommand(pid);
|
|
11206
|
+
const cwd = registry.getProcessCwd(pid);
|
|
11207
|
+
const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
|
|
11208
|
+
if (this.isLikelyWsServerProcess(command) && cwdMatches) {
|
|
11209
|
+
killable.push(pid);
|
|
11210
|
+
} else {
|
|
11211
|
+
unsafe.push({ pid, command });
|
|
11212
|
+
}
|
|
11213
|
+
}
|
|
11214
|
+
if (unsafe.length > 0) {
|
|
11215
|
+
const details = unsafe.map((item) => `PID ${item.pid} (${item.command})`).join(", ");
|
|
11216
|
+
return {
|
|
11217
|
+
success: false,
|
|
11218
|
+
error: `WS_BIND_CONFLICT: WS port ${wsPort} is in use by another process: ${details}`
|
|
11219
|
+
};
|
|
11220
|
+
}
|
|
11221
|
+
for (const pid of killable) {
|
|
11222
|
+
try {
|
|
11223
|
+
process.kill(pid, "SIGTERM");
|
|
11224
|
+
} catch {
|
|
11225
|
+
}
|
|
11226
|
+
}
|
|
11227
|
+
await this.wait(800);
|
|
11228
|
+
for (const pid of killable) {
|
|
11229
|
+
try {
|
|
11230
|
+
process.kill(pid, 0);
|
|
11231
|
+
process.kill(pid, "SIGKILL");
|
|
11232
|
+
} catch {
|
|
11233
|
+
}
|
|
11234
|
+
}
|
|
11235
|
+
await this.wait(400);
|
|
11236
|
+
if (await isPortInUse(wsPort)) {
|
|
11237
|
+
return {
|
|
11238
|
+
success: false,
|
|
11239
|
+
error: `WS_BIND_CONFLICT: WS port ${wsPort} remained in use after stale ws-server cleanup`
|
|
11240
|
+
};
|
|
11241
|
+
}
|
|
11242
|
+
return null;
|
|
11243
|
+
}
|
|
11244
|
+
async cleanupWsServerOnPort(wsPort, worktreePath) {
|
|
11245
|
+
if (!wsPort || !await isPortInUse(wsPort)) return;
|
|
11246
|
+
const registry = getDevServerRegistry();
|
|
11247
|
+
const expectedPath = path25.resolve(worktreePath);
|
|
11248
|
+
const pids = this.getPidsOnPort(wsPort);
|
|
11249
|
+
for (const pid of pids) {
|
|
11250
|
+
const command = this.getProcessCommand(pid);
|
|
11251
|
+
const cwd = registry.getProcessCwd(pid);
|
|
11252
|
+
const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
|
|
11253
|
+
if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
|
|
11254
|
+
continue;
|
|
11255
|
+
}
|
|
11256
|
+
try {
|
|
11257
|
+
process.kill(pid, "SIGTERM");
|
|
11258
|
+
} catch {
|
|
11259
|
+
}
|
|
11260
|
+
}
|
|
11261
|
+
await this.wait(500);
|
|
11262
|
+
for (const pid of pids) {
|
|
11263
|
+
const command = this.getProcessCommand(pid);
|
|
11264
|
+
const cwd = registry.getProcessCwd(pid);
|
|
11265
|
+
const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
|
|
11266
|
+
if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
|
|
11267
|
+
continue;
|
|
11268
|
+
}
|
|
11269
|
+
try {
|
|
11270
|
+
process.kill(pid, 0);
|
|
11271
|
+
process.kill(pid, "SIGKILL");
|
|
11272
|
+
} catch {
|
|
11273
|
+
}
|
|
11274
|
+
}
|
|
11275
|
+
}
|
|
11276
|
+
getPidsOnPort(port) {
|
|
11277
|
+
try {
|
|
11278
|
+
const output = (0, import_child_process13.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
|
|
11279
|
+
if (!output) return [];
|
|
11280
|
+
return output.split("\n").map((value) => parseInt(value, 10)).filter((value) => !isNaN(value) && value > 0);
|
|
11281
|
+
} catch {
|
|
11282
|
+
return [];
|
|
11283
|
+
}
|
|
11284
|
+
}
|
|
11285
|
+
getProcessCommand(pid) {
|
|
11286
|
+
try {
|
|
11287
|
+
return (0, import_child_process13.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8" }).trim() || "unknown";
|
|
11288
|
+
} catch {
|
|
11289
|
+
return "unknown";
|
|
11290
|
+
}
|
|
11291
|
+
}
|
|
11292
|
+
isLikelyWsServerProcess(command) {
|
|
11293
|
+
return command.includes("ws-server.js") || command.includes("build:ws-server");
|
|
11294
|
+
}
|
|
11295
|
+
detectWsStartupError(logPath, wsPort) {
|
|
11296
|
+
const fallback = `WS_BIND_CONFLICT: ws-server failed to become healthy on WS port ${wsPort}`;
|
|
11297
|
+
if (!logPath) return fallback;
|
|
11298
|
+
try {
|
|
11299
|
+
if (!fs25.existsSync(logPath)) return fallback;
|
|
11300
|
+
const content = fs25.readFileSync(logPath, "utf8");
|
|
11301
|
+
if (content.includes("EADDRINUSE") && content.includes(`${wsPort}`)) {
|
|
11302
|
+
return `WS_BIND_CONFLICT: ws-server could not bind to WS port ${wsPort} (EADDRINUSE)`;
|
|
11303
|
+
}
|
|
11304
|
+
if (content.includes("Failed to initialize")) {
|
|
11305
|
+
return `WS_SERVER_START_FAILED: ws-server failed during startup on WS port ${wsPort}`;
|
|
11306
|
+
}
|
|
11307
|
+
} catch {
|
|
11308
|
+
}
|
|
11309
|
+
return fallback;
|
|
11310
|
+
}
|
|
10952
11311
|
// ============ Private Methods ============
|
|
10953
11312
|
async fetchEnvVars(projectPath) {
|
|
10954
11313
|
try {
|
|
@@ -10962,8 +11321,8 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10962
11321
|
cacheTtl: 300
|
|
10963
11322
|
});
|
|
10964
11323
|
console.log(`[DevServerRunner] Loaded ${Object.keys(result.envVars).length} env vars`);
|
|
10965
|
-
const envFilePath =
|
|
10966
|
-
if (!
|
|
11324
|
+
const envFilePath = path25.join(projectPath, ".env");
|
|
11325
|
+
if (!fs25.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
|
|
10967
11326
|
console.log(`[DevServerRunner] Writing .env file`);
|
|
10968
11327
|
writeEnvFile(projectPath, result.envVars);
|
|
10969
11328
|
}
|
|
@@ -10973,7 +11332,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10973
11332
|
return {};
|
|
10974
11333
|
}
|
|
10975
11334
|
}
|
|
10976
|
-
spawnProcess(projectPath, port, moduleUid, logPath, customCommand, envVars) {
|
|
11335
|
+
spawnProcess(projectPath, port, wsPort, moduleUid, logPath, customCommand, envVars) {
|
|
10977
11336
|
this.rotateLogIfNeeded(logPath);
|
|
10978
11337
|
const nodeOptions = process.env.NODE_OPTIONS || "";
|
|
10979
11338
|
const memoryFlag = `--max-old-space-size=${DEV_SERVER_CONSTANTS.NODE_MEMORY_LIMIT_MB}`;
|
|
@@ -10985,6 +11344,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10985
11344
|
...process.env,
|
|
10986
11345
|
...envVars,
|
|
10987
11346
|
PORT: String(port),
|
|
11347
|
+
WS_PORT: String(wsPort),
|
|
10988
11348
|
NODE_OPTIONS: enhancedNodeOptions
|
|
10989
11349
|
};
|
|
10990
11350
|
const proc = (0, import_child_process13.spawn)(cmd, args, {
|
|
@@ -11048,6 +11408,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11048
11408
|
const newProcess = this.spawnProcess(
|
|
11049
11409
|
state.projectPath,
|
|
11050
11410
|
state.port,
|
|
11411
|
+
state.wsPort,
|
|
11051
11412
|
moduleUid,
|
|
11052
11413
|
logPath,
|
|
11053
11414
|
state.customCommand,
|
|
@@ -11078,7 +11439,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11078
11439
|
return Math.min(delay, DEV_SERVER_CONSTANTS.MAX_RESTART_DELAY_MS);
|
|
11079
11440
|
}
|
|
11080
11441
|
async checkHealth(port) {
|
|
11081
|
-
return new Promise((
|
|
11442
|
+
return new Promise((resolve6) => {
|
|
11082
11443
|
const req = http.request(
|
|
11083
11444
|
{
|
|
11084
11445
|
hostname: "localhost",
|
|
@@ -11087,12 +11448,35 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11087
11448
|
method: "HEAD",
|
|
11088
11449
|
timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
|
|
11089
11450
|
},
|
|
11090
|
-
() =>
|
|
11451
|
+
() => resolve6(true)
|
|
11452
|
+
);
|
|
11453
|
+
req.on("error", () => resolve6(false));
|
|
11454
|
+
req.on("timeout", () => {
|
|
11455
|
+
req.destroy();
|
|
11456
|
+
resolve6(false);
|
|
11457
|
+
});
|
|
11458
|
+
req.end();
|
|
11459
|
+
});
|
|
11460
|
+
}
|
|
11461
|
+
async checkWsHealth(wsPort) {
|
|
11462
|
+
return new Promise((resolve6) => {
|
|
11463
|
+
const req = http.request(
|
|
11464
|
+
{
|
|
11465
|
+
hostname: "127.0.0.1",
|
|
11466
|
+
port: wsPort,
|
|
11467
|
+
path: "/health",
|
|
11468
|
+
method: "GET",
|
|
11469
|
+
timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
|
|
11470
|
+
},
|
|
11471
|
+
(res) => {
|
|
11472
|
+
resolve6(res.statusCode === 200);
|
|
11473
|
+
res.resume();
|
|
11474
|
+
}
|
|
11091
11475
|
);
|
|
11092
|
-
req.on("error", () =>
|
|
11476
|
+
req.on("error", () => resolve6(false));
|
|
11093
11477
|
req.on("timeout", () => {
|
|
11094
11478
|
req.destroy();
|
|
11095
|
-
|
|
11479
|
+
resolve6(false);
|
|
11096
11480
|
});
|
|
11097
11481
|
req.end();
|
|
11098
11482
|
});
|
|
@@ -11108,29 +11492,40 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11108
11492
|
}
|
|
11109
11493
|
return false;
|
|
11110
11494
|
}
|
|
11495
|
+
async waitForWsPort(wsPort, timeoutMs) {
|
|
11496
|
+
const startTime = Date.now();
|
|
11497
|
+
const checkInterval = 500;
|
|
11498
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
11499
|
+
if (await this.checkWsHealth(wsPort)) {
|
|
11500
|
+
return true;
|
|
11501
|
+
}
|
|
11502
|
+
await this.wait(checkInterval);
|
|
11503
|
+
}
|
|
11504
|
+
return false;
|
|
11505
|
+
}
|
|
11111
11506
|
wait(ms) {
|
|
11112
|
-
return new Promise((
|
|
11507
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
11113
11508
|
}
|
|
11114
11509
|
getLogsDir() {
|
|
11115
|
-
const logsDir =
|
|
11116
|
-
if (!
|
|
11117
|
-
|
|
11510
|
+
const logsDir = path25.join((0, import_core12.getConfigDir)(), "logs");
|
|
11511
|
+
if (!fs25.existsSync(logsDir)) {
|
|
11512
|
+
fs25.mkdirSync(logsDir, { recursive: true });
|
|
11118
11513
|
}
|
|
11119
11514
|
return logsDir;
|
|
11120
11515
|
}
|
|
11121
11516
|
getLogFilePath(moduleUid) {
|
|
11122
|
-
return
|
|
11517
|
+
return path25.join(this.getLogsDir(), `dev-${moduleUid}.log`);
|
|
11123
11518
|
}
|
|
11124
11519
|
rotateLogIfNeeded(logPath) {
|
|
11125
11520
|
try {
|
|
11126
|
-
if (
|
|
11127
|
-
const stats =
|
|
11521
|
+
if (fs25.existsSync(logPath)) {
|
|
11522
|
+
const stats = fs25.statSync(logPath);
|
|
11128
11523
|
if (stats.size > DEV_SERVER_CONSTANTS.MAX_LOG_SIZE_BYTES) {
|
|
11129
11524
|
const backupPath = `${logPath}.1`;
|
|
11130
|
-
if (
|
|
11131
|
-
|
|
11525
|
+
if (fs25.existsSync(backupPath)) {
|
|
11526
|
+
fs25.unlinkSync(backupPath);
|
|
11132
11527
|
}
|
|
11133
|
-
|
|
11528
|
+
fs25.renameSync(logPath, backupPath);
|
|
11134
11529
|
}
|
|
11135
11530
|
}
|
|
11136
11531
|
} catch {
|
|
@@ -11141,7 +11536,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11141
11536
|
try {
|
|
11142
11537
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11143
11538
|
const prefix = isError ? "ERR" : "OUT";
|
|
11144
|
-
|
|
11539
|
+
fs25.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
|
|
11145
11540
|
`);
|
|
11146
11541
|
} catch {
|
|
11147
11542
|
}
|
|
@@ -11150,6 +11545,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11150
11545
|
return {
|
|
11151
11546
|
moduleUid: state.moduleUid,
|
|
11152
11547
|
port: state.port,
|
|
11548
|
+
wsPort: state.wsPort,
|
|
11153
11549
|
pid: state.process.pid,
|
|
11154
11550
|
startedAt: state.startedAt,
|
|
11155
11551
|
uptime: Math.floor((Date.now() - state.startedAt.getTime()) / 1e3),
|
|
@@ -11210,7 +11606,8 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11210
11606
|
* @returns Result with success status and preview URL
|
|
11211
11607
|
*/
|
|
11212
11608
|
async startPreview(config) {
|
|
11213
|
-
const { moduleUid, worktreePath, port = DEFAULT_PORT, customCommand } = config;
|
|
11609
|
+
const { moduleUid, worktreePath, port = DEFAULT_PORT, wsPort: requestedWsPort, customCommand } = config;
|
|
11610
|
+
let wsPort = requestedWsPort;
|
|
11214
11611
|
if (!worktreePath) {
|
|
11215
11612
|
return { success: false, error: "Worktree path is required" };
|
|
11216
11613
|
}
|
|
@@ -11237,29 +11634,45 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11237
11634
|
}
|
|
11238
11635
|
const existing = this.previews.get(moduleUid);
|
|
11239
11636
|
if (existing && (existing.state === "live" || existing.state === "running")) {
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11637
|
+
const appHealthy = await this.devServer.isHealthy(moduleUid);
|
|
11638
|
+
const wsHealthy = await this.devServer.isWsHealthy(moduleUid);
|
|
11639
|
+
if (appHealthy && wsHealthy) {
|
|
11640
|
+
console.log(`[PreviewManager] Preview already running for ${moduleUid}`);
|
|
11641
|
+
return {
|
|
11642
|
+
success: true,
|
|
11643
|
+
previewUrl: existing.tunnelUrl,
|
|
11644
|
+
alreadyRunning: true
|
|
11645
|
+
};
|
|
11646
|
+
}
|
|
11647
|
+
console.warn(
|
|
11648
|
+
`[PreviewManager] EP1406: Existing preview for ${moduleUid} is unhealthy (app=${appHealthy}, ws=${wsHealthy}), restarting`
|
|
11649
|
+
);
|
|
11650
|
+
await this.stopPreview(moduleUid);
|
|
11246
11651
|
}
|
|
11247
11652
|
this.startingModules.add(moduleUid);
|
|
11248
|
-
|
|
11249
|
-
const state = {
|
|
11250
|
-
moduleUid,
|
|
11251
|
-
worktreePath,
|
|
11252
|
-
port,
|
|
11253
|
-
state: "starting",
|
|
11254
|
-
startedAt: /* @__PURE__ */ new Date()
|
|
11255
|
-
};
|
|
11256
|
-
this.previews.set(moduleUid, state);
|
|
11257
|
-
this.emitStateChange(moduleUid, "starting");
|
|
11653
|
+
let state = null;
|
|
11258
11654
|
try {
|
|
11655
|
+
if (!wsPort) {
|
|
11656
|
+
wsPort = allocateWsPort(moduleUid);
|
|
11657
|
+
}
|
|
11658
|
+
wsPort = assignWsPort(moduleUid, wsPort);
|
|
11659
|
+
console.log(`[PreviewManager] Starting preview for ${moduleUid} at ${worktreePath}:${port} (ws:${wsPort})`);
|
|
11660
|
+
state = {
|
|
11661
|
+
moduleUid,
|
|
11662
|
+
worktreePath,
|
|
11663
|
+
port,
|
|
11664
|
+
wsPort,
|
|
11665
|
+
state: "starting",
|
|
11666
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
11667
|
+
};
|
|
11668
|
+
const activeState = state;
|
|
11669
|
+
this.previews.set(moduleUid, state);
|
|
11670
|
+
this.emitStateChange(moduleUid, "starting");
|
|
11259
11671
|
console.log(`[PreviewManager] Starting dev server for ${moduleUid}...`);
|
|
11260
11672
|
const devResult = await this.devServer.start({
|
|
11261
11673
|
projectPath: worktreePath,
|
|
11262
11674
|
port,
|
|
11675
|
+
wsPort,
|
|
11263
11676
|
moduleUid,
|
|
11264
11677
|
customCommand,
|
|
11265
11678
|
autoRestart: true
|
|
@@ -11267,6 +11680,8 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11267
11680
|
if (!devResult.success) {
|
|
11268
11681
|
state.state = "error";
|
|
11269
11682
|
state.error = devResult.error || "Failed to start dev server";
|
|
11683
|
+
releasePort(moduleUid);
|
|
11684
|
+
releaseWsPort(moduleUid);
|
|
11270
11685
|
this.emitStateChange(moduleUid, "error");
|
|
11271
11686
|
this.emit("error", moduleUid, new Error(state.error));
|
|
11272
11687
|
return { success: false, error: state.error };
|
|
@@ -11275,7 +11690,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11275
11690
|
this.emitStateChange(moduleUid, "running");
|
|
11276
11691
|
console.log(`[PreviewManager] Dev server running on port ${port}`);
|
|
11277
11692
|
console.log(`[PreviewManager] Starting Named Tunnel for ${moduleUid}...`);
|
|
11278
|
-
|
|
11693
|
+
activeState.state = "tunneling";
|
|
11279
11694
|
this.emitStateChange(moduleUid, "tunneling");
|
|
11280
11695
|
const MAX_TUNNEL_RETRIES = 2;
|
|
11281
11696
|
let tunnelResult = { success: false };
|
|
@@ -11283,7 +11698,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11283
11698
|
for (let attempt = 1; attempt <= MAX_TUNNEL_RETRIES; attempt++) {
|
|
11284
11699
|
if (attempt > 1) {
|
|
11285
11700
|
console.log(`[PreviewManager] Retrying tunnel for ${moduleUid} (attempt ${attempt}/${MAX_TUNNEL_RETRIES})...`);
|
|
11286
|
-
await new Promise((
|
|
11701
|
+
await new Promise((resolve6) => setTimeout(resolve6, 2e3));
|
|
11287
11702
|
}
|
|
11288
11703
|
tunnelResult = await this.tunnel.startTunnel({
|
|
11289
11704
|
moduleUid,
|
|
@@ -11293,20 +11708,20 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11293
11708
|
onStatusChange: (status, error) => {
|
|
11294
11709
|
console.log(`[PreviewManager] Tunnel status for ${moduleUid}: ${status}${error ? ` - ${error}` : ""}`);
|
|
11295
11710
|
if (status === "error") {
|
|
11296
|
-
|
|
11297
|
-
|
|
11711
|
+
activeState.state = "error";
|
|
11712
|
+
activeState.error = error || "Tunnel error";
|
|
11298
11713
|
this.emitStateChange(moduleUid, "error");
|
|
11299
11714
|
} else if (status === "disconnected") {
|
|
11300
|
-
|
|
11301
|
-
|
|
11715
|
+
activeState.state = "running";
|
|
11716
|
+
activeState.tunnelUrl = void 0;
|
|
11302
11717
|
this.emitStateChange(moduleUid, "running");
|
|
11303
11718
|
} else if (status === "reconnecting") {
|
|
11304
|
-
|
|
11719
|
+
activeState.state = "tunneling";
|
|
11305
11720
|
this.emitStateChange(moduleUid, "tunneling");
|
|
11306
11721
|
}
|
|
11307
11722
|
},
|
|
11308
11723
|
onUrl: (url) => {
|
|
11309
|
-
|
|
11724
|
+
activeState.tunnelUrl = url;
|
|
11310
11725
|
}
|
|
11311
11726
|
});
|
|
11312
11727
|
if (tunnelResult.success) {
|
|
@@ -11322,8 +11737,10 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11322
11737
|
} catch (cleanupError) {
|
|
11323
11738
|
console.warn(`[PreviewManager] Error cleaning up dev server after tunnel failure:`, cleanupError);
|
|
11324
11739
|
}
|
|
11325
|
-
|
|
11326
|
-
|
|
11740
|
+
releasePort(moduleUid);
|
|
11741
|
+
releaseWsPort(moduleUid);
|
|
11742
|
+
activeState.state = "error";
|
|
11743
|
+
activeState.error = lastError;
|
|
11327
11744
|
this.previews.delete(moduleUid);
|
|
11328
11745
|
this.emitStateChange(moduleUid, "error");
|
|
11329
11746
|
return {
|
|
@@ -11331,9 +11748,9 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11331
11748
|
error: `Tunnel failed after ${MAX_TUNNEL_RETRIES} attempts: ${lastError}`
|
|
11332
11749
|
};
|
|
11333
11750
|
}
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11751
|
+
activeState.state = "live";
|
|
11752
|
+
activeState.tunnelUrl = tunnelResult.url;
|
|
11753
|
+
activeState.error = void 0;
|
|
11337
11754
|
this.emitStateChange(moduleUid, "live");
|
|
11338
11755
|
this.emit("live", moduleUid, tunnelResult.url);
|
|
11339
11756
|
console.log(`[PreviewManager] Preview live for ${moduleUid}: ${tunnelResult.url}`);
|
|
@@ -11344,9 +11761,17 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11344
11761
|
} catch (error) {
|
|
11345
11762
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
11346
11763
|
console.error(`[PreviewManager] Error starting preview for ${moduleUid}:`, error);
|
|
11347
|
-
state
|
|
11348
|
-
|
|
11349
|
-
|
|
11764
|
+
if (state) {
|
|
11765
|
+
state.state = "error";
|
|
11766
|
+
state.error = errorMsg;
|
|
11767
|
+
} else {
|
|
11768
|
+
this.previews.delete(moduleUid);
|
|
11769
|
+
}
|
|
11770
|
+
releasePort(moduleUid);
|
|
11771
|
+
releaseWsPort(moduleUid);
|
|
11772
|
+
if (state) {
|
|
11773
|
+
this.emitStateChange(moduleUid, "error");
|
|
11774
|
+
}
|
|
11350
11775
|
this.emit("error", moduleUid, error instanceof Error ? error : new Error(errorMsg));
|
|
11351
11776
|
return { success: false, error: errorMsg };
|
|
11352
11777
|
} finally {
|
|
@@ -11382,6 +11807,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11382
11807
|
console.warn(`[PreviewManager] Error clearing tunnel URL for ${moduleUid}:`, error);
|
|
11383
11808
|
}
|
|
11384
11809
|
releasePort(moduleUid);
|
|
11810
|
+
releaseWsPort(moduleUid);
|
|
11385
11811
|
if (state) {
|
|
11386
11812
|
state.state = "stopped";
|
|
11387
11813
|
state.tunnelUrl = void 0;
|
|
@@ -11404,11 +11830,12 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11404
11830
|
}
|
|
11405
11831
|
console.log(`[PreviewManager] Restarting preview for ${moduleUid}`);
|
|
11406
11832
|
await this.stopPreview(moduleUid);
|
|
11407
|
-
await new Promise((
|
|
11833
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
11408
11834
|
return this.startPreview({
|
|
11409
11835
|
moduleUid,
|
|
11410
11836
|
worktreePath: state.worktreePath,
|
|
11411
|
-
port: state.port
|
|
11837
|
+
port: state.port,
|
|
11838
|
+
wsPort: state.wsPort
|
|
11412
11839
|
});
|
|
11413
11840
|
}
|
|
11414
11841
|
/**
|
|
@@ -11431,6 +11858,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11431
11858
|
tunnelUrl: state.tunnelUrl,
|
|
11432
11859
|
tunnelState: tunnelInfo?.status === "connected" ? "connected" : tunnelInfo?.status === "starting" ? "starting" : tunnelInfo?.status === "error" ? "error" : tunnelInfo?.status === "disconnected" ? "disconnected" : void 0,
|
|
11433
11860
|
port: state.port,
|
|
11861
|
+
wsPort: state.wsPort,
|
|
11434
11862
|
error: state.error,
|
|
11435
11863
|
startedAt: state.startedAt
|
|
11436
11864
|
};
|
|
@@ -11595,8 +12023,8 @@ async function deleteWorktree(config, moduleUid) {
|
|
|
11595
12023
|
}
|
|
11596
12024
|
|
|
11597
12025
|
// src/daemon/package-manager.ts
|
|
11598
|
-
var
|
|
11599
|
-
var
|
|
12026
|
+
var fs26 = __toESM(require("fs"));
|
|
12027
|
+
var path26 = __toESM(require("path"));
|
|
11600
12028
|
function pnpmCommand(args) {
|
|
11601
12029
|
return [
|
|
11602
12030
|
"if command -v pnpm >/dev/null 2>&1; then",
|
|
@@ -11614,13 +12042,13 @@ var PACKAGE_MANAGERS = {
|
|
|
11614
12042
|
{
|
|
11615
12043
|
name: "pnpm",
|
|
11616
12044
|
detector: (p) => {
|
|
11617
|
-
if (
|
|
12045
|
+
if (fs26.existsSync(path26.join(p, "pnpm-lock.yaml"))) {
|
|
11618
12046
|
return "pnpm-lock.yaml";
|
|
11619
12047
|
}
|
|
11620
|
-
const pkgJsonPath =
|
|
11621
|
-
if (
|
|
12048
|
+
const pkgJsonPath = path26.join(p, "package.json");
|
|
12049
|
+
if (fs26.existsSync(pkgJsonPath)) {
|
|
11622
12050
|
try {
|
|
11623
|
-
const pkg = JSON.parse(
|
|
12051
|
+
const pkg = JSON.parse(fs26.readFileSync(pkgJsonPath, "utf-8"));
|
|
11624
12052
|
if (pkg.packageManager?.startsWith("pnpm")) {
|
|
11625
12053
|
return "package.json (packageManager field)";
|
|
11626
12054
|
}
|
|
@@ -11636,7 +12064,7 @@ var PACKAGE_MANAGERS = {
|
|
|
11636
12064
|
},
|
|
11637
12065
|
{
|
|
11638
12066
|
name: "yarn",
|
|
11639
|
-
detector: (p) =>
|
|
12067
|
+
detector: (p) => fs26.existsSync(path26.join(p, "yarn.lock")) ? "yarn.lock" : null,
|
|
11640
12068
|
config: {
|
|
11641
12069
|
installCmd: "yarn install --frozen-lockfile",
|
|
11642
12070
|
cacheEnvVar: "YARN_CACHE_FOLDER"
|
|
@@ -11644,14 +12072,14 @@ var PACKAGE_MANAGERS = {
|
|
|
11644
12072
|
},
|
|
11645
12073
|
{
|
|
11646
12074
|
name: "bun",
|
|
11647
|
-
detector: (p) =>
|
|
12075
|
+
detector: (p) => fs26.existsSync(path26.join(p, "bun.lockb")) ? "bun.lockb" : null,
|
|
11648
12076
|
config: {
|
|
11649
12077
|
installCmd: "bun install --frozen-lockfile"
|
|
11650
12078
|
}
|
|
11651
12079
|
},
|
|
11652
12080
|
{
|
|
11653
12081
|
name: "npm",
|
|
11654
|
-
detector: (p) =>
|
|
12082
|
+
detector: (p) => fs26.existsSync(path26.join(p, "package-lock.json")) ? "package-lock.json" : null,
|
|
11655
12083
|
config: {
|
|
11656
12084
|
installCmd: "npm ci"
|
|
11657
12085
|
}
|
|
@@ -11660,7 +12088,7 @@ var PACKAGE_MANAGERS = {
|
|
|
11660
12088
|
// EP1222: Default to pnpm for new projects without lockfile
|
|
11661
12089
|
// This encourages standardization on pnpm across Episoda projects
|
|
11662
12090
|
name: "pnpm",
|
|
11663
|
-
detector: (p) =>
|
|
12091
|
+
detector: (p) => fs26.existsSync(path26.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
|
|
11664
12092
|
config: {
|
|
11665
12093
|
installCmd: pnpmCommand("install"),
|
|
11666
12094
|
cacheEnvVar: "PNPM_HOME"
|
|
@@ -11671,13 +12099,13 @@ var PACKAGE_MANAGERS = {
|
|
|
11671
12099
|
{
|
|
11672
12100
|
name: "uv",
|
|
11673
12101
|
detector: (p) => {
|
|
11674
|
-
const pyprojectPath =
|
|
11675
|
-
if (
|
|
12102
|
+
const pyprojectPath = path26.join(p, "pyproject.toml");
|
|
12103
|
+
if (fs26.existsSync(path26.join(p, "uv.lock"))) {
|
|
11676
12104
|
return "uv.lock";
|
|
11677
12105
|
}
|
|
11678
|
-
if (
|
|
12106
|
+
if (fs26.existsSync(pyprojectPath)) {
|
|
11679
12107
|
try {
|
|
11680
|
-
const content =
|
|
12108
|
+
const content = fs26.readFileSync(pyprojectPath, "utf-8");
|
|
11681
12109
|
if (content.includes("[tool.uv]") || content.includes("[project]")) {
|
|
11682
12110
|
return "pyproject.toml";
|
|
11683
12111
|
}
|
|
@@ -11693,7 +12121,7 @@ var PACKAGE_MANAGERS = {
|
|
|
11693
12121
|
},
|
|
11694
12122
|
{
|
|
11695
12123
|
name: "pip",
|
|
11696
|
-
detector: (p) =>
|
|
12124
|
+
detector: (p) => fs26.existsSync(path26.join(p, "requirements.txt")) ? "requirements.txt" : null,
|
|
11697
12125
|
config: {
|
|
11698
12126
|
installCmd: "pip install -r requirements.txt"
|
|
11699
12127
|
}
|
|
@@ -11745,15 +12173,15 @@ function detectPackageManager(worktreePath, preferredLanguages) {
|
|
|
11745
12173
|
}
|
|
11746
12174
|
|
|
11747
12175
|
// src/daemon/build-packages.ts
|
|
11748
|
-
var
|
|
11749
|
-
var
|
|
12176
|
+
var fs27 = __toESM(require("fs"));
|
|
12177
|
+
var path27 = __toESM(require("path"));
|
|
11750
12178
|
function hasPackageScript(worktreePath, scriptName) {
|
|
11751
12179
|
try {
|
|
11752
|
-
const packageJsonPath =
|
|
11753
|
-
if (!
|
|
12180
|
+
const packageJsonPath = path27.join(worktreePath, "package.json");
|
|
12181
|
+
if (!fs27.existsSync(packageJsonPath)) {
|
|
11754
12182
|
return false;
|
|
11755
12183
|
}
|
|
11756
|
-
const packageJson2 = JSON.parse(
|
|
12184
|
+
const packageJson2 = JSON.parse(fs27.readFileSync(packageJsonPath, "utf-8"));
|
|
11757
12185
|
return typeof packageJson2.scripts?.[scriptName] === "string";
|
|
11758
12186
|
} catch {
|
|
11759
12187
|
return false;
|
|
@@ -11811,19 +12239,19 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
|
|
|
11811
12239
|
if (process.env.EPISODA_MODE !== "cloud") {
|
|
11812
12240
|
return;
|
|
11813
12241
|
}
|
|
11814
|
-
const homeDir = process.env.HOME ||
|
|
11815
|
-
const workspaceConfigPath =
|
|
12242
|
+
const homeDir = process.env.HOME || os13.homedir();
|
|
12243
|
+
const workspaceConfigPath = path28.join(
|
|
11816
12244
|
homeDir,
|
|
11817
12245
|
"episoda",
|
|
11818
12246
|
workspaceSlug,
|
|
11819
12247
|
".episoda",
|
|
11820
12248
|
"config.json"
|
|
11821
12249
|
);
|
|
11822
|
-
if (!
|
|
12250
|
+
if (!fs28.existsSync(workspaceConfigPath)) {
|
|
11823
12251
|
return;
|
|
11824
12252
|
}
|
|
11825
12253
|
try {
|
|
11826
|
-
const content =
|
|
12254
|
+
const content = fs28.readFileSync(workspaceConfigPath, "utf8");
|
|
11827
12255
|
const workspaceConfig = JSON.parse(content);
|
|
11828
12256
|
let changed = false;
|
|
11829
12257
|
if (projectId && workspaceConfig.projectId !== projectId && workspaceConfig.project_id !== projectId) {
|
|
@@ -11835,7 +12263,7 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
|
|
|
11835
12263
|
changed = true;
|
|
11836
12264
|
}
|
|
11837
12265
|
if (changed) {
|
|
11838
|
-
|
|
12266
|
+
fs28.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
|
|
11839
12267
|
console.log("[Worktree] Updated workspace config with project context");
|
|
11840
12268
|
}
|
|
11841
12269
|
} catch (error) {
|
|
@@ -11882,19 +12310,19 @@ async function handleWorktreeCreate(request2) {
|
|
|
11882
12310
|
}
|
|
11883
12311
|
try {
|
|
11884
12312
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
11885
|
-
const bareRepoPath =
|
|
12313
|
+
const bareRepoPath = path28.join(projectPath, ".bare");
|
|
11886
12314
|
const gitEnv = projectId ? { ...process.env, EPISODA_PROJECT_ID: projectId } : process.env;
|
|
11887
|
-
if (!
|
|
12315
|
+
if (!fs28.existsSync(bareRepoPath)) {
|
|
11888
12316
|
console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
|
|
11889
12317
|
console.log(`[Worktree] Repo URL: ${repoUrl.replace(/\/\/[^@]*@/, "//***@")}`);
|
|
11890
|
-
const episodaDir =
|
|
11891
|
-
|
|
12318
|
+
const episodaDir = path28.join(projectPath, ".episoda");
|
|
12319
|
+
fs28.mkdirSync(episodaDir, { recursive: true });
|
|
11892
12320
|
try {
|
|
11893
12321
|
console.log(`[Worktree] K1273: Starting git clone...`);
|
|
11894
12322
|
await execAsync2(`git clone --bare "${repoUrl}" "${bareRepoPath}"`, { env: gitEnv });
|
|
11895
12323
|
console.log(`[Worktree] K1273: Clone successful`);
|
|
11896
12324
|
await execAsync2(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { env: gitEnv });
|
|
11897
|
-
const configPath =
|
|
12325
|
+
const configPath = path28.join(episodaDir, "config.json");
|
|
11898
12326
|
const config = {
|
|
11899
12327
|
projectId,
|
|
11900
12328
|
workspaceSlug,
|
|
@@ -11903,7 +12331,7 @@ async function handleWorktreeCreate(request2) {
|
|
|
11903
12331
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11904
12332
|
worktrees: []
|
|
11905
12333
|
};
|
|
11906
|
-
|
|
12334
|
+
fs28.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
11907
12335
|
console.log(`[Worktree] K1273: Project initialized at ${projectPath}`);
|
|
11908
12336
|
} catch (cloneError) {
|
|
11909
12337
|
console.error(`[Worktree] K1273: Git clone failed: ${cloneError.message}`);
|
|
@@ -11916,8 +12344,8 @@ async function handleWorktreeCreate(request2) {
|
|
|
11916
12344
|
console.log(`[Worktree] EP1373: Project exists, skipping handler-level fetch (manager handles narrow fetch)`);
|
|
11917
12345
|
}
|
|
11918
12346
|
const manager = new WorktreeManager(projectPath);
|
|
11919
|
-
const
|
|
11920
|
-
if (!
|
|
12347
|
+
const initialized3 = await manager.initialize();
|
|
12348
|
+
if (!initialized3) {
|
|
11921
12349
|
return {
|
|
11922
12350
|
success: false,
|
|
11923
12351
|
error: `Failed to initialize WorktreeManager at ${projectPath}`
|
|
@@ -11938,8 +12366,8 @@ async function handleWorktreeCreate(request2) {
|
|
|
11938
12366
|
let finalError;
|
|
11939
12367
|
if (envVars && Object.keys(envVars).length > 0) {
|
|
11940
12368
|
const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
11941
|
-
const envPath =
|
|
11942
|
-
|
|
12369
|
+
const envPath = path28.join(worktreePath, ".env");
|
|
12370
|
+
fs28.writeFileSync(envPath, envContent + "\n", "utf-8");
|
|
11943
12371
|
console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
|
|
11944
12372
|
}
|
|
11945
12373
|
const isCloud = process.env.EPISODA_MODE === "cloud";
|
|
@@ -12011,8 +12439,8 @@ async function handleWorktreeRelease(request2) {
|
|
|
12011
12439
|
try {
|
|
12012
12440
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
12013
12441
|
const manager = new WorktreeManager(projectPath);
|
|
12014
|
-
const
|
|
12015
|
-
if (!
|
|
12442
|
+
const initialized3 = await manager.initialize();
|
|
12443
|
+
if (!initialized3) {
|
|
12016
12444
|
console.log(`[Worktree] EP1143: Project not initialized, nothing to release`);
|
|
12017
12445
|
return { success: true };
|
|
12018
12446
|
}
|
|
@@ -12065,8 +12493,8 @@ async function handleWorktreeList(workspaceSlug, projectSlug) {
|
|
|
12065
12493
|
try {
|
|
12066
12494
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
12067
12495
|
const manager = new WorktreeManager(projectPath);
|
|
12068
|
-
const
|
|
12069
|
-
if (!
|
|
12496
|
+
const initialized3 = await manager.initialize();
|
|
12497
|
+
if (!initialized3) {
|
|
12070
12498
|
return { success: true, worktrees: [] };
|
|
12071
12499
|
}
|
|
12072
12500
|
const worktrees = manager.listWorktrees();
|
|
@@ -12080,18 +12508,18 @@ async function handleProjectEject(request2) {
|
|
|
12080
12508
|
console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
|
|
12081
12509
|
try {
|
|
12082
12510
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
12083
|
-
const bareRepoPath =
|
|
12084
|
-
if (!
|
|
12511
|
+
const bareRepoPath = path28.join(projectPath, ".bare");
|
|
12512
|
+
if (!fs28.existsSync(projectPath)) {
|
|
12085
12513
|
console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
|
|
12086
12514
|
return { success: true };
|
|
12087
12515
|
}
|
|
12088
|
-
if (!
|
|
12516
|
+
if (!fs28.existsSync(bareRepoPath)) {
|
|
12089
12517
|
console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
|
|
12090
12518
|
return { success: true };
|
|
12091
12519
|
}
|
|
12092
12520
|
const manager = new WorktreeManager(projectPath);
|
|
12093
|
-
const
|
|
12094
|
-
if (
|
|
12521
|
+
const initialized3 = await manager.initialize();
|
|
12522
|
+
if (initialized3) {
|
|
12095
12523
|
const worktrees = manager.listWorktrees();
|
|
12096
12524
|
if (worktrees.length > 0) {
|
|
12097
12525
|
return {
|
|
@@ -12100,12 +12528,12 @@ async function handleProjectEject(request2) {
|
|
|
12100
12528
|
};
|
|
12101
12529
|
}
|
|
12102
12530
|
}
|
|
12103
|
-
const artifactsPath =
|
|
12104
|
-
if (
|
|
12531
|
+
const artifactsPath = path28.join(projectPath, "artifacts");
|
|
12532
|
+
if (fs28.existsSync(artifactsPath)) {
|
|
12105
12533
|
await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
|
|
12106
12534
|
}
|
|
12107
12535
|
console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
|
|
12108
|
-
await
|
|
12536
|
+
await fs28.promises.rm(projectPath, { recursive: true, force: true });
|
|
12109
12537
|
console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
|
|
12110
12538
|
return { success: true };
|
|
12111
12539
|
} catch (error) {
|
|
@@ -12119,7 +12547,7 @@ async function handleProjectEject(request2) {
|
|
|
12119
12547
|
async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
|
|
12120
12548
|
const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
|
|
12121
12549
|
try {
|
|
12122
|
-
const files = await
|
|
12550
|
+
const files = await fs28.promises.readdir(artifactsPath);
|
|
12123
12551
|
if (files.length === 0) {
|
|
12124
12552
|
return;
|
|
12125
12553
|
}
|
|
@@ -12133,8 +12561,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
|
|
|
12133
12561
|
}
|
|
12134
12562
|
const artifacts = [];
|
|
12135
12563
|
for (const fileName of files) {
|
|
12136
|
-
const filePath =
|
|
12137
|
-
const stat = await
|
|
12564
|
+
const filePath = path28.join(artifactsPath, fileName);
|
|
12565
|
+
const stat = await fs28.promises.stat(filePath);
|
|
12138
12566
|
if (stat.isDirectory()) {
|
|
12139
12567
|
continue;
|
|
12140
12568
|
}
|
|
@@ -12143,9 +12571,9 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
|
|
|
12143
12571
|
continue;
|
|
12144
12572
|
}
|
|
12145
12573
|
try {
|
|
12146
|
-
const content = await
|
|
12574
|
+
const content = await fs28.promises.readFile(filePath);
|
|
12147
12575
|
const base64Content = content.toString("base64");
|
|
12148
|
-
const ext =
|
|
12576
|
+
const ext = path28.extname(fileName).toLowerCase();
|
|
12149
12577
|
const mimeTypes = {
|
|
12150
12578
|
".json": "application/json",
|
|
12151
12579
|
".txt": "text/plain",
|
|
@@ -12201,8 +12629,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
|
|
|
12201
12629
|
}
|
|
12202
12630
|
|
|
12203
12631
|
// src/daemon/handlers/project-handlers.ts
|
|
12204
|
-
var
|
|
12205
|
-
var
|
|
12632
|
+
var path29 = __toESM(require("path"));
|
|
12633
|
+
var fs29 = __toESM(require("fs"));
|
|
12206
12634
|
function validateSlug(slug, fieldName) {
|
|
12207
12635
|
if (!slug || typeof slug !== "string") {
|
|
12208
12636
|
return `${fieldName} is required`;
|
|
@@ -12242,14 +12670,14 @@ async function handleProjectSetup(params) {
|
|
|
12242
12670
|
console.log(`[ProjectSetup] EP1199: Setting up project ${workspaceSlug}/${projectSlug}`);
|
|
12243
12671
|
try {
|
|
12244
12672
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
12245
|
-
const artifactsPath =
|
|
12246
|
-
const configDir =
|
|
12247
|
-
const configPath =
|
|
12248
|
-
await
|
|
12249
|
-
await
|
|
12673
|
+
const artifactsPath = path29.join(projectPath, "artifacts");
|
|
12674
|
+
const configDir = path29.join(projectPath, ".episoda");
|
|
12675
|
+
const configPath = path29.join(configDir, "config.json");
|
|
12676
|
+
await fs29.promises.mkdir(artifactsPath, { recursive: true });
|
|
12677
|
+
await fs29.promises.mkdir(configDir, { recursive: true });
|
|
12250
12678
|
let existingConfig = {};
|
|
12251
12679
|
try {
|
|
12252
|
-
const existing = await
|
|
12680
|
+
const existing = await fs29.promises.readFile(configPath, "utf-8");
|
|
12253
12681
|
existingConfig = JSON.parse(existing);
|
|
12254
12682
|
} catch {
|
|
12255
12683
|
}
|
|
@@ -12262,7 +12690,7 @@ async function handleProjectSetup(params) {
|
|
|
12262
12690
|
// Only set created_at if not already present
|
|
12263
12691
|
created_at: existingConfig.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
12264
12692
|
};
|
|
12265
|
-
await
|
|
12693
|
+
await fs29.promises.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
12266
12694
|
console.log(`[ProjectSetup] EP1199: Project setup complete at ${projectPath}`);
|
|
12267
12695
|
return {
|
|
12268
12696
|
success: true,
|
|
@@ -12282,8 +12710,8 @@ async function handleProjectSetup(params) {
|
|
|
12282
12710
|
// src/utils/dev-server.ts
|
|
12283
12711
|
var import_child_process15 = require("child_process");
|
|
12284
12712
|
var import_core14 = __toESM(require_dist());
|
|
12285
|
-
var
|
|
12286
|
-
var
|
|
12713
|
+
var fs30 = __toESM(require("fs"));
|
|
12714
|
+
var path30 = __toESM(require("path"));
|
|
12287
12715
|
var MAX_RESTART_ATTEMPTS = 5;
|
|
12288
12716
|
var INITIAL_RESTART_DELAY_MS = 2e3;
|
|
12289
12717
|
var MAX_RESTART_DELAY_MS = 3e4;
|
|
@@ -12291,26 +12719,26 @@ var MAX_LOG_SIZE_BYTES2 = 5 * 1024 * 1024;
|
|
|
12291
12719
|
var NODE_MEMORY_LIMIT_MB = 2048;
|
|
12292
12720
|
var activeServers = /* @__PURE__ */ new Map();
|
|
12293
12721
|
function getLogsDir() {
|
|
12294
|
-
const logsDir =
|
|
12295
|
-
if (!
|
|
12296
|
-
|
|
12722
|
+
const logsDir = path30.join((0, import_core14.getConfigDir)(), "logs");
|
|
12723
|
+
if (!fs30.existsSync(logsDir)) {
|
|
12724
|
+
fs30.mkdirSync(logsDir, { recursive: true });
|
|
12297
12725
|
}
|
|
12298
12726
|
return logsDir;
|
|
12299
12727
|
}
|
|
12300
12728
|
function getLogFilePath(moduleUid) {
|
|
12301
|
-
return
|
|
12729
|
+
return path30.join(getLogsDir(), `dev-${moduleUid}.log`);
|
|
12302
12730
|
}
|
|
12303
12731
|
function rotateLogIfNeeded(logPath) {
|
|
12304
12732
|
try {
|
|
12305
|
-
if (
|
|
12306
|
-
const stats =
|
|
12733
|
+
if (fs30.existsSync(logPath)) {
|
|
12734
|
+
const stats = fs30.statSync(logPath);
|
|
12307
12735
|
if (stats.size > MAX_LOG_SIZE_BYTES2) {
|
|
12308
12736
|
const backupPath = `${logPath}.1`;
|
|
12309
|
-
if (
|
|
12310
|
-
|
|
12737
|
+
if (fs30.existsSync(backupPath)) {
|
|
12738
|
+
fs30.unlinkSync(backupPath);
|
|
12311
12739
|
}
|
|
12312
|
-
|
|
12313
|
-
console.log(`[DevServer] EP932: Rotated log file for ${
|
|
12740
|
+
fs30.renameSync(logPath, backupPath);
|
|
12741
|
+
console.log(`[DevServer] EP932: Rotated log file for ${path30.basename(logPath)}`);
|
|
12314
12742
|
}
|
|
12315
12743
|
}
|
|
12316
12744
|
} catch (error) {
|
|
@@ -12323,7 +12751,7 @@ function writeToLog(logPath, line, isError = false) {
|
|
|
12323
12751
|
const prefix = isError ? "ERR" : "OUT";
|
|
12324
12752
|
const logLine = `[${timestamp}] [${prefix}] ${line}
|
|
12325
12753
|
`;
|
|
12326
|
-
|
|
12754
|
+
fs30.appendFileSync(logPath, logLine);
|
|
12327
12755
|
} catch {
|
|
12328
12756
|
}
|
|
12329
12757
|
}
|
|
@@ -12343,7 +12771,7 @@ async function killProcessOnPort(port) {
|
|
|
12343
12771
|
} catch {
|
|
12344
12772
|
}
|
|
12345
12773
|
}
|
|
12346
|
-
await new Promise((
|
|
12774
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
12347
12775
|
for (const pid of pids) {
|
|
12348
12776
|
try {
|
|
12349
12777
|
(0, import_child_process15.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
|
|
@@ -12352,7 +12780,7 @@ async function killProcessOnPort(port) {
|
|
|
12352
12780
|
} catch {
|
|
12353
12781
|
}
|
|
12354
12782
|
}
|
|
12355
|
-
await new Promise((
|
|
12783
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
12356
12784
|
const stillInUse = await isPortInUse(port);
|
|
12357
12785
|
if (stillInUse) {
|
|
12358
12786
|
console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`);
|
|
@@ -12372,7 +12800,7 @@ async function waitForPort(port, timeoutMs = 3e4) {
|
|
|
12372
12800
|
if (await isPortInUse(port)) {
|
|
12373
12801
|
return true;
|
|
12374
12802
|
}
|
|
12375
|
-
await new Promise((
|
|
12803
|
+
await new Promise((resolve6) => setTimeout(resolve6, checkInterval));
|
|
12376
12804
|
}
|
|
12377
12805
|
return false;
|
|
12378
12806
|
}
|
|
@@ -12444,7 +12872,7 @@ async function handleProcessExit(moduleUid, code, signal) {
|
|
|
12444
12872
|
const delay = calculateRestartDelay(serverInfo.restartCount);
|
|
12445
12873
|
console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`);
|
|
12446
12874
|
writeToLog(serverInfo.logFile || "", `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false);
|
|
12447
|
-
await new Promise((
|
|
12875
|
+
await new Promise((resolve6) => setTimeout(resolve6, delay));
|
|
12448
12876
|
if (!activeServers.has(moduleUid)) {
|
|
12449
12877
|
console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`);
|
|
12450
12878
|
return;
|
|
@@ -12502,8 +12930,8 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
|
|
|
12502
12930
|
});
|
|
12503
12931
|
injectedEnvVars = result.envVars;
|
|
12504
12932
|
console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? "cache" : "server"})`);
|
|
12505
|
-
const envFilePath =
|
|
12506
|
-
if (!
|
|
12933
|
+
const envFilePath = path30.join(projectPath, ".env");
|
|
12934
|
+
if (!fs30.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
|
|
12507
12935
|
console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
|
|
12508
12936
|
writeEnvFile(projectPath, injectedEnvVars);
|
|
12509
12937
|
}
|
|
@@ -12569,7 +12997,7 @@ async function stopDevServer(moduleUid) {
|
|
|
12569
12997
|
writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false);
|
|
12570
12998
|
}
|
|
12571
12999
|
serverInfo.process.kill("SIGTERM");
|
|
12572
|
-
await new Promise((
|
|
13000
|
+
await new Promise((resolve6) => setTimeout(resolve6, 2e3));
|
|
12573
13001
|
if (!serverInfo.process.killed) {
|
|
12574
13002
|
serverInfo.process.kill("SIGKILL");
|
|
12575
13003
|
}
|
|
@@ -12587,7 +13015,7 @@ async function restartDevServer(moduleUid) {
|
|
|
12587
13015
|
writeToLog(logFile, `Manual restart requested`, false);
|
|
12588
13016
|
}
|
|
12589
13017
|
await stopDevServer(moduleUid);
|
|
12590
|
-
await new Promise((
|
|
13018
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
12591
13019
|
if (await isPortInUse(port)) {
|
|
12592
13020
|
await killProcessOnPort(port);
|
|
12593
13021
|
}
|
|
@@ -12641,9 +13069,9 @@ var IPCRouter = class {
|
|
|
12641
13069
|
// EP726: Kept for backward compatibility
|
|
12642
13070
|
machineName: this.host.deviceName,
|
|
12643
13071
|
// EP1186: User-friendly machine name from server
|
|
12644
|
-
hostname:
|
|
12645
|
-
platform:
|
|
12646
|
-
arch:
|
|
13072
|
+
hostname: os14.hostname(),
|
|
13073
|
+
platform: os14.platform(),
|
|
13074
|
+
arch: os14.arch(),
|
|
12647
13075
|
projects
|
|
12648
13076
|
};
|
|
12649
13077
|
});
|
|
@@ -12669,7 +13097,7 @@ var IPCRouter = class {
|
|
|
12669
13097
|
if (attempt < MAX_RETRIES) {
|
|
12670
13098
|
const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
|
|
12671
13099
|
console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
|
|
12672
|
-
await new Promise((
|
|
13100
|
+
await new Promise((resolve6) => setTimeout(resolve6, delay));
|
|
12673
13101
|
await this.host.disconnectProject(projectPath);
|
|
12674
13102
|
}
|
|
12675
13103
|
}
|
|
@@ -12807,6 +13235,7 @@ var IPCRouter = class {
|
|
|
12807
13235
|
await tunnelManager.stopTunnel(moduleUid);
|
|
12808
13236
|
await stopDevServer(moduleUid);
|
|
12809
13237
|
releasePort(moduleUid);
|
|
13238
|
+
releaseWsPort(moduleUid);
|
|
12810
13239
|
await clearTunnelUrl(moduleUid);
|
|
12811
13240
|
this.host.deleteTunnelHealthFailure(moduleUid);
|
|
12812
13241
|
console.log(`[Daemon] EP823: Tunnel stopped for ${moduleUid}`);
|
|
@@ -12844,8 +13273,8 @@ var IPCRouter = class {
|
|
|
12844
13273
|
};
|
|
12845
13274
|
|
|
12846
13275
|
// src/daemon/update-manager.ts
|
|
12847
|
-
var
|
|
12848
|
-
var
|
|
13276
|
+
var fs31 = __toESM(require("fs"));
|
|
13277
|
+
var path31 = __toESM(require("path"));
|
|
12849
13278
|
var import_child_process17 = require("child_process");
|
|
12850
13279
|
var import_core17 = __toESM(require_dist());
|
|
12851
13280
|
var semver2 = __toESM(require("semver"));
|
|
@@ -13103,8 +13532,8 @@ var UpdateManager = class _UpdateManager {
|
|
|
13103
13532
|
console.log(`[Daemon] EP1324: Update to ${targetVersion} installed, restarting daemon...`);
|
|
13104
13533
|
await this.host.shutdown();
|
|
13105
13534
|
const configDir = (0, import_core17.getConfigDir)();
|
|
13106
|
-
const logPath =
|
|
13107
|
-
const logFd =
|
|
13535
|
+
const logPath = path31.join(configDir, "daemon.log");
|
|
13536
|
+
const logFd = fs31.openSync(logPath, "a");
|
|
13108
13537
|
const child = (0, import_child_process17.spawn)("node", [this.daemonEntryFile], {
|
|
13109
13538
|
detached: true,
|
|
13110
13539
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -13112,7 +13541,7 @@ var UpdateManager = class _UpdateManager {
|
|
|
13112
13541
|
});
|
|
13113
13542
|
if (!child.pid) {
|
|
13114
13543
|
try {
|
|
13115
|
-
|
|
13544
|
+
fs31.closeSync(logFd);
|
|
13116
13545
|
} catch {
|
|
13117
13546
|
}
|
|
13118
13547
|
this.recordUpdateFailure(targetVersion, "Failed to spawn replacement daemon (missing pid)");
|
|
@@ -13120,7 +13549,7 @@ var UpdateManager = class _UpdateManager {
|
|
|
13120
13549
|
}
|
|
13121
13550
|
child.unref();
|
|
13122
13551
|
const pidPath = getPidFilePath();
|
|
13123
|
-
|
|
13552
|
+
fs31.writeFileSync(pidPath, child.pid.toString(), "utf-8");
|
|
13124
13553
|
console.log(`[Daemon] EP1324: New daemon spawned (PID: ${child.pid}), exiting old process`);
|
|
13125
13554
|
process.exit(0);
|
|
13126
13555
|
} catch (error) {
|
|
@@ -13171,8 +13600,8 @@ var UpdateManager = class _UpdateManager {
|
|
|
13171
13600
|
};
|
|
13172
13601
|
|
|
13173
13602
|
// src/daemon/health-orchestrator.ts
|
|
13174
|
-
var
|
|
13175
|
-
var
|
|
13603
|
+
var fs32 = __toESM(require("fs"));
|
|
13604
|
+
var path32 = __toESM(require("path"));
|
|
13176
13605
|
var import_core18 = __toESM(require_dist());
|
|
13177
13606
|
var HealthOrchestrator = class _HealthOrchestrator {
|
|
13178
13607
|
constructor(host) {
|
|
@@ -13376,10 +13805,15 @@ var HealthOrchestrator = class _HealthOrchestrator {
|
|
|
13376
13805
|
console.log(`[Daemon] EP1042: Killed ${cleanup.cleaned} orphaned dev server(s)`);
|
|
13377
13806
|
}
|
|
13378
13807
|
const registryPorts = /* @__PURE__ */ new Map();
|
|
13808
|
+
const registryWsPorts = /* @__PURE__ */ new Map();
|
|
13379
13809
|
for (const entry of registry.getAll()) {
|
|
13380
13810
|
registryPorts.set(entry.moduleUid, entry.port);
|
|
13811
|
+
if (entry.wsPort) {
|
|
13812
|
+
registryWsPorts.set(entry.moduleUid, entry.wsPort);
|
|
13813
|
+
}
|
|
13381
13814
|
}
|
|
13382
13815
|
reconcileWithRegistry(registryPorts);
|
|
13816
|
+
reconcileWsPortsWithRegistry(registryWsPorts);
|
|
13383
13817
|
console.log("[Daemon] EP1042: Orphaned dev server cleanup complete");
|
|
13384
13818
|
} catch (error) {
|
|
13385
13819
|
console.error("[Daemon] EP1042: Failed to clean up orphaned dev servers:", error);
|
|
@@ -13421,26 +13855,26 @@ var HealthOrchestrator = class _HealthOrchestrator {
|
|
|
13421
13855
|
checkAndRotateLog() {
|
|
13422
13856
|
try {
|
|
13423
13857
|
const configDir = (0, import_core18.getConfigDir)();
|
|
13424
|
-
const logPath =
|
|
13425
|
-
if (!
|
|
13426
|
-
const stats =
|
|
13858
|
+
const logPath = path32.join(configDir, "daemon.log");
|
|
13859
|
+
if (!fs32.existsSync(logPath)) return;
|
|
13860
|
+
const stats = fs32.statSync(logPath);
|
|
13427
13861
|
if (stats.size < _HealthOrchestrator.MAX_LOG_SIZE_BYTES) return;
|
|
13428
13862
|
console.log(`[Daemon] EP1351: daemon.log is ${Math.round(stats.size / 1024 / 1024)}MB, rotating...`);
|
|
13429
13863
|
for (let i = _HealthOrchestrator.MAX_LOG_FILES - 1; i >= 1; i--) {
|
|
13430
13864
|
const src = `${logPath}.${i}`;
|
|
13431
13865
|
const dst = `${logPath}.${i + 1}`;
|
|
13432
|
-
if (
|
|
13866
|
+
if (fs32.existsSync(src)) {
|
|
13433
13867
|
try {
|
|
13434
|
-
|
|
13868
|
+
fs32.renameSync(src, dst);
|
|
13435
13869
|
} catch {
|
|
13436
13870
|
}
|
|
13437
13871
|
}
|
|
13438
13872
|
}
|
|
13439
13873
|
try {
|
|
13440
|
-
|
|
13874
|
+
fs32.copyFileSync(logPath, `${logPath}.1`);
|
|
13441
13875
|
} catch {
|
|
13442
13876
|
}
|
|
13443
|
-
|
|
13877
|
+
fs32.truncateSync(logPath, 0);
|
|
13444
13878
|
console.log("[Daemon] EP1351: Log rotated successfully");
|
|
13445
13879
|
} catch (error) {
|
|
13446
13880
|
console.warn("[Daemon] EP1351: Log rotation failed:", error instanceof Error ? error.message : error);
|
|
@@ -13489,9 +13923,9 @@ var HealthOrchestrator = class _HealthOrchestrator {
|
|
|
13489
13923
|
if (Number.isFinite(lastAccessedMs)) {
|
|
13490
13924
|
referenceTimestampMs = lastAccessedMs;
|
|
13491
13925
|
fallbackAgeSource.push(`${w.moduleUid}(lastAccessed)`);
|
|
13492
|
-
} else if (w.worktreePath &&
|
|
13926
|
+
} else if (w.worktreePath && fs32.existsSync(w.worktreePath)) {
|
|
13493
13927
|
try {
|
|
13494
|
-
const stats =
|
|
13928
|
+
const stats = fs32.statSync(w.worktreePath);
|
|
13495
13929
|
referenceTimestampMs = stats.mtimeMs;
|
|
13496
13930
|
fallbackAgeSource.push(`${w.moduleUid}(fs.mtime)`);
|
|
13497
13931
|
} catch {
|
|
@@ -13857,7 +14291,7 @@ var ConnectionManager = class {
|
|
|
13857
14291
|
* handlers are removed deterministically on every exit path.
|
|
13858
14292
|
*/
|
|
13859
14293
|
async waitForAuthentication(client, timeoutMs = 3e4) {
|
|
13860
|
-
await new Promise((
|
|
14294
|
+
await new Promise((resolve6, reject) => {
|
|
13861
14295
|
let settled = false;
|
|
13862
14296
|
const cleanup = () => {
|
|
13863
14297
|
clearTimeout(timeout);
|
|
@@ -13874,7 +14308,7 @@ var ConnectionManager = class {
|
|
|
13874
14308
|
if (settled) return;
|
|
13875
14309
|
settled = true;
|
|
13876
14310
|
cleanup();
|
|
13877
|
-
|
|
14311
|
+
resolve6();
|
|
13878
14312
|
};
|
|
13879
14313
|
const errorHandler = (message) => {
|
|
13880
14314
|
if (settled) return;
|
|
@@ -13937,7 +14371,7 @@ function resolveDaemonWsEndpoint(config, env = process.env) {
|
|
|
13937
14371
|
|
|
13938
14372
|
// src/daemon/project-message-router.ts
|
|
13939
14373
|
var import_core19 = __toESM(require_dist());
|
|
13940
|
-
var
|
|
14374
|
+
var path33 = __toESM(require("path"));
|
|
13941
14375
|
var ProjectMessageRouter = class {
|
|
13942
14376
|
constructor(host) {
|
|
13943
14377
|
this.host = host;
|
|
@@ -13954,7 +14388,7 @@ var ProjectMessageRouter = class {
|
|
|
13954
14388
|
client.updateActivity();
|
|
13955
14389
|
try {
|
|
13956
14390
|
const gitCmd = message.command;
|
|
13957
|
-
const bareRepoPath =
|
|
14391
|
+
const bareRepoPath = path33.join(projectPath, ".bare");
|
|
13958
14392
|
const cwd = gitCmd.worktreePath || bareRepoPath;
|
|
13959
14393
|
if (gitCmd.worktreePath) {
|
|
13960
14394
|
console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
|
|
@@ -14294,32 +14728,32 @@ async function fetchEnvVars2(projectId) {
|
|
|
14294
14728
|
}
|
|
14295
14729
|
|
|
14296
14730
|
// src/daemon/project-git-config.ts
|
|
14297
|
-
var
|
|
14298
|
-
var
|
|
14731
|
+
var fs33 = __toESM(require("fs"));
|
|
14732
|
+
var path34 = __toESM(require("path"));
|
|
14299
14733
|
var import_core21 = __toESM(require_dist());
|
|
14300
14734
|
function getGitDirs(projectPath) {
|
|
14301
|
-
const bareDir =
|
|
14302
|
-
const gitPath =
|
|
14303
|
-
if (
|
|
14735
|
+
const bareDir = path34.join(projectPath, ".bare");
|
|
14736
|
+
const gitPath = path34.join(projectPath, ".git");
|
|
14737
|
+
if (fs33.existsSync(bareDir) && fs33.statSync(bareDir).isDirectory()) {
|
|
14304
14738
|
return { gitDir: bareDir, workDir: projectPath };
|
|
14305
14739
|
}
|
|
14306
|
-
if (
|
|
14740
|
+
if (fs33.existsSync(gitPath) && fs33.statSync(gitPath).isDirectory()) {
|
|
14307
14741
|
return { gitDir: null, workDir: projectPath };
|
|
14308
14742
|
}
|
|
14309
|
-
if (
|
|
14743
|
+
if (fs33.existsSync(gitPath) && fs33.statSync(gitPath).isFile()) {
|
|
14310
14744
|
return { gitDir: null, workDir: projectPath };
|
|
14311
14745
|
}
|
|
14312
|
-
const entries =
|
|
14746
|
+
const entries = fs33.readdirSync(projectPath, { withFileTypes: true });
|
|
14313
14747
|
for (const entry of entries) {
|
|
14314
14748
|
if (entry.isDirectory() && entry.name.startsWith("EP")) {
|
|
14315
|
-
const worktreePath =
|
|
14316
|
-
const worktreeGit =
|
|
14317
|
-
if (
|
|
14749
|
+
const worktreePath = path34.join(projectPath, entry.name);
|
|
14750
|
+
const worktreeGit = path34.join(worktreePath, ".git");
|
|
14751
|
+
if (fs33.existsSync(worktreeGit)) {
|
|
14318
14752
|
return { gitDir: null, workDir: worktreePath };
|
|
14319
14753
|
}
|
|
14320
14754
|
}
|
|
14321
14755
|
}
|
|
14322
|
-
if (
|
|
14756
|
+
if (fs33.existsSync(bareDir)) {
|
|
14323
14757
|
return { gitDir: bareDir, workDir: projectPath };
|
|
14324
14758
|
}
|
|
14325
14759
|
return { gitDir: null, workDir: projectPath };
|
|
@@ -14364,24 +14798,24 @@ async function configureGitUser(projectPath, userId, workspaceId, machineId, pro
|
|
|
14364
14798
|
async function installGitHooks(projectPath) {
|
|
14365
14799
|
const hooks = ["post-checkout", "pre-commit", "post-commit"];
|
|
14366
14800
|
let hooksDir;
|
|
14367
|
-
const bareHooksDir =
|
|
14368
|
-
const gitHooksDir =
|
|
14369
|
-
if (
|
|
14801
|
+
const bareHooksDir = path34.join(projectPath, ".bare", "hooks");
|
|
14802
|
+
const gitHooksDir = path34.join(projectPath, ".git", "hooks");
|
|
14803
|
+
if (fs33.existsSync(bareHooksDir)) {
|
|
14370
14804
|
hooksDir = bareHooksDir;
|
|
14371
|
-
} else if (
|
|
14805
|
+
} else if (fs33.existsSync(gitHooksDir) && fs33.statSync(path34.join(projectPath, ".git")).isDirectory()) {
|
|
14372
14806
|
hooksDir = gitHooksDir;
|
|
14373
14807
|
} else {
|
|
14374
|
-
const parentBareHooks =
|
|
14375
|
-
if (
|
|
14808
|
+
const parentBareHooks = path34.join(projectPath, "..", ".bare", "hooks");
|
|
14809
|
+
if (fs33.existsSync(parentBareHooks)) {
|
|
14376
14810
|
hooksDir = parentBareHooks;
|
|
14377
14811
|
} else {
|
|
14378
14812
|
console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
|
|
14379
14813
|
return;
|
|
14380
14814
|
}
|
|
14381
14815
|
}
|
|
14382
|
-
if (!
|
|
14816
|
+
if (!fs33.existsSync(hooksDir)) {
|
|
14383
14817
|
try {
|
|
14384
|
-
|
|
14818
|
+
fs33.mkdirSync(hooksDir, { recursive: true });
|
|
14385
14819
|
} catch {
|
|
14386
14820
|
console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
|
|
14387
14821
|
return;
|
|
@@ -14389,20 +14823,20 @@ async function installGitHooks(projectPath) {
|
|
|
14389
14823
|
}
|
|
14390
14824
|
for (const hookName of hooks) {
|
|
14391
14825
|
try {
|
|
14392
|
-
const hookPath =
|
|
14393
|
-
const bundledHookPath =
|
|
14394
|
-
if (!
|
|
14826
|
+
const hookPath = path34.join(hooksDir, hookName);
|
|
14827
|
+
const bundledHookPath = path34.join(__dirname, "..", "hooks", hookName);
|
|
14828
|
+
if (!fs33.existsSync(bundledHookPath)) {
|
|
14395
14829
|
console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
|
|
14396
14830
|
continue;
|
|
14397
14831
|
}
|
|
14398
|
-
const hookContent =
|
|
14399
|
-
if (
|
|
14400
|
-
const existingContent =
|
|
14832
|
+
const hookContent = fs33.readFileSync(bundledHookPath, "utf-8");
|
|
14833
|
+
if (fs33.existsSync(hookPath)) {
|
|
14834
|
+
const existingContent = fs33.readFileSync(hookPath, "utf-8");
|
|
14401
14835
|
if (existingContent === hookContent) {
|
|
14402
14836
|
continue;
|
|
14403
14837
|
}
|
|
14404
14838
|
}
|
|
14405
|
-
|
|
14839
|
+
fs33.writeFileSync(hookPath, hookContent, { mode: 493 });
|
|
14406
14840
|
console.log(`[Daemon] Installed git hook: ${hookName}`);
|
|
14407
14841
|
} catch (error) {
|
|
14408
14842
|
console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
|
|
@@ -14545,9 +14979,9 @@ var Daemon = class _Daemon {
|
|
|
14545
14979
|
this.healthServer = http2.createServer((req, res) => {
|
|
14546
14980
|
if (req.url === "/health" || req.url === "/") {
|
|
14547
14981
|
const isConnected = this.connectionManager.liveConnectionCount() > 0;
|
|
14548
|
-
const projects = Array.from(this.connectionManager.entries()).map(([
|
|
14549
|
-
path:
|
|
14550
|
-
connected: this.connectionManager.hasLiveConnection(
|
|
14982
|
+
const projects = Array.from(this.connectionManager.entries()).map(([path35, conn]) => ({
|
|
14983
|
+
path: path35,
|
|
14984
|
+
connected: this.connectionManager.hasLiveConnection(path35)
|
|
14551
14985
|
}));
|
|
14552
14986
|
const status = {
|
|
14553
14987
|
status: isConnected ? "healthy" : "degraded",
|
|
@@ -14620,8 +15054,8 @@ var Daemon = class _Daemon {
|
|
|
14620
15054
|
await this.shutdown();
|
|
14621
15055
|
try {
|
|
14622
15056
|
const pidPath = getPidFilePath();
|
|
14623
|
-
if (
|
|
14624
|
-
|
|
15057
|
+
if (fs34.existsSync(pidPath)) {
|
|
15058
|
+
fs34.unlinkSync(pidPath);
|
|
14625
15059
|
console.log("[Daemon] PID file cleaned up (watchdog)");
|
|
14626
15060
|
}
|
|
14627
15061
|
} catch (error) {
|
|
@@ -14684,8 +15118,8 @@ var Daemon = class _Daemon {
|
|
|
14684
15118
|
}
|
|
14685
15119
|
}
|
|
14686
15120
|
let releaseLock;
|
|
14687
|
-
const lockPromise = new Promise((
|
|
14688
|
-
releaseLock =
|
|
15121
|
+
const lockPromise = new Promise((resolve6) => {
|
|
15122
|
+
releaseLock = resolve6;
|
|
14689
15123
|
});
|
|
14690
15124
|
this.tunnelOperationLocks.set(moduleUid, lockPromise);
|
|
14691
15125
|
try {
|
|
@@ -14712,7 +15146,7 @@ var Daemon = class _Daemon {
|
|
|
14712
15146
|
const maxWait = 35e3;
|
|
14713
15147
|
const startTime = Date.now();
|
|
14714
15148
|
while (this.connectionManager.hasPendingConnection(projectPath) && Date.now() - startTime < maxWait) {
|
|
14715
|
-
await new Promise((
|
|
15149
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
14716
15150
|
}
|
|
14717
15151
|
if (this.connectionManager.hasLiveConnection(projectPath)) {
|
|
14718
15152
|
console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
|
|
@@ -15011,8 +15445,8 @@ var Daemon = class _Daemon {
|
|
|
15011
15445
|
};
|
|
15012
15446
|
} else {
|
|
15013
15447
|
const manager = new WorktreeManager(projectRootPath);
|
|
15014
|
-
const
|
|
15015
|
-
if (!
|
|
15448
|
+
const initialized3 = await manager.initialize();
|
|
15449
|
+
if (!initialized3) {
|
|
15016
15450
|
console.warn(`[Daemon] EP1035: Failed to initialize WorktreeManager for ${projectRootPath}`);
|
|
15017
15451
|
result = {
|
|
15018
15452
|
success: false,
|
|
@@ -15210,8 +15644,8 @@ var Daemon = class _Daemon {
|
|
|
15210
15644
|
let daemonPid;
|
|
15211
15645
|
try {
|
|
15212
15646
|
const pidPath = getPidFilePath();
|
|
15213
|
-
if (
|
|
15214
|
-
const pidStr =
|
|
15647
|
+
if (fs34.existsSync(pidPath)) {
|
|
15648
|
+
const pidStr = fs34.readFileSync(pidPath, "utf-8").trim();
|
|
15215
15649
|
daemonPid = parseInt(pidStr, 10);
|
|
15216
15650
|
}
|
|
15217
15651
|
} catch (pidError) {
|
|
@@ -15222,9 +15656,9 @@ var Daemon = class _Daemon {
|
|
|
15222
15656
|
const containerId = process.env.EPISODA_CONTAINER_ID;
|
|
15223
15657
|
const capabilities = ["worktree_create_v1"];
|
|
15224
15658
|
await client.connect(wsEndpoint.wsUrl, config.access_token, this.machineId, {
|
|
15225
|
-
hostname:
|
|
15226
|
-
osPlatform:
|
|
15227
|
-
osArch:
|
|
15659
|
+
hostname: os15.hostname(),
|
|
15660
|
+
osPlatform: os15.platform(),
|
|
15661
|
+
osArch: os15.arch(),
|
|
15228
15662
|
daemonPid,
|
|
15229
15663
|
cliVersion: this.cliRuntimeVersion,
|
|
15230
15664
|
cliPackageName: packageJson.name,
|
|
@@ -15763,6 +16197,7 @@ var Daemon = class _Daemon {
|
|
|
15763
16197
|
console.error("[Daemon] Failed to stop tunnels:", error);
|
|
15764
16198
|
}
|
|
15765
16199
|
clearAllPorts();
|
|
16200
|
+
clearAllWsPorts();
|
|
15766
16201
|
if (this.disconnectWatchdogTimer) {
|
|
15767
16202
|
clearInterval(this.disconnectWatchdogTimer);
|
|
15768
16203
|
this.disconnectWatchdogTimer = null;
|
|
@@ -15785,8 +16220,8 @@ var Daemon = class _Daemon {
|
|
|
15785
16220
|
await this.shutdown();
|
|
15786
16221
|
try {
|
|
15787
16222
|
const pidPath = getPidFilePath();
|
|
15788
|
-
if (
|
|
15789
|
-
|
|
16223
|
+
if (fs34.existsSync(pidPath)) {
|
|
16224
|
+
fs34.unlinkSync(pidPath);
|
|
15790
16225
|
console.log("[Daemon] PID file cleaned up");
|
|
15791
16226
|
}
|
|
15792
16227
|
} catch (error) {
|