@episoda/cli 0.2.171 → 0.2.173
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 +854 -420
- 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.173",
|
|
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,21 @@ 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) {
|
|
10931
|
+
const wsHealthy = await this.checkWsHealth(wsPort);
|
|
10932
|
+
if (!wsHealthy) {
|
|
10933
|
+
console.warn(
|
|
10934
|
+
`[DevServerRunner] EP1412: Existing server for ${moduleUid} has unhealthy ws-server on ${wsPort}; preserving preview because app port ${port} is healthy`
|
|
10935
|
+
);
|
|
10936
|
+
}
|
|
10741
10937
|
console.log(`[DevServerRunner] EP1042: Correct server already running on port ${port} for ${moduleUid}`);
|
|
10742
10938
|
return { success: true, alreadyRunning: true };
|
|
10743
10939
|
}
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10940
|
+
if (existingEntry.worktreePath !== projectPath || existingEntry.moduleUid !== moduleUid) {
|
|
10941
|
+
console.log(`[DevServerRunner] EP1042: Port ${port} owned by ${existingEntry.moduleUid} (${existingEntry.worktreePath}), killing...`);
|
|
10942
|
+
await this.killProcessOnPort(port);
|
|
10943
|
+
registry.unregister(existingEntry.moduleUid);
|
|
10944
|
+
this.servers.delete(existingEntry.moduleUid);
|
|
10945
|
+
}
|
|
10748
10946
|
} else {
|
|
10749
10947
|
console.log(`[DevServerRunner] EP1042: Unknown process on port ${port}, killing...`);
|
|
10750
10948
|
await this.killProcessOnPort(port);
|
|
@@ -10754,21 +10952,32 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10754
10952
|
return { success: false, error: `Port ${port} still in use after cleanup` };
|
|
10755
10953
|
}
|
|
10756
10954
|
}
|
|
10955
|
+
const wsPortAvailability = await this.ensureWsPortAvailable(projectPath, wsPort);
|
|
10956
|
+
if (wsPortAvailability) {
|
|
10957
|
+
return wsPortAvailability;
|
|
10958
|
+
}
|
|
10757
10959
|
const existing = this.servers.get(moduleUid);
|
|
10758
10960
|
if (existing && !existing.process.killed) {
|
|
10961
|
+
const wsHealthy = await this.checkWsHealth(existing.wsPort);
|
|
10962
|
+
if (!wsHealthy) {
|
|
10963
|
+
console.warn(
|
|
10964
|
+
`[DevServerRunner] EP1412: In-memory server exists for ${moduleUid} with unhealthy ws-server (${existing.wsPort}); preserving running app process`
|
|
10965
|
+
);
|
|
10966
|
+
}
|
|
10759
10967
|
console.log(`[DevServerRunner] Process already exists for ${moduleUid}`);
|
|
10760
10968
|
return { success: true, alreadyRunning: true };
|
|
10761
10969
|
}
|
|
10762
|
-
console.log(`[DevServerRunner] Starting dev server for ${moduleUid} on port ${port}...`);
|
|
10970
|
+
console.log(`[DevServerRunner] Starting dev server for ${moduleUid} on port ${port} (ws:${wsPort})...`);
|
|
10763
10971
|
const injectedEnvVars = await this.fetchEnvVars(projectPath);
|
|
10764
10972
|
try {
|
|
10765
10973
|
const logPath = this.getLogFilePath(moduleUid);
|
|
10766
|
-
const process2 = this.spawnProcess(projectPath, port, moduleUid, logPath, customCommand, injectedEnvVars);
|
|
10974
|
+
const process2 = this.spawnProcess(projectPath, port, wsPort, moduleUid, logPath, customCommand, injectedEnvVars);
|
|
10767
10975
|
const state = {
|
|
10768
10976
|
process: process2,
|
|
10769
10977
|
moduleUid,
|
|
10770
10978
|
projectPath,
|
|
10771
10979
|
port,
|
|
10980
|
+
wsPort,
|
|
10772
10981
|
startedAt: /* @__PURE__ */ new Date(),
|
|
10773
10982
|
restartCount: 0,
|
|
10774
10983
|
lastRestartAt: null,
|
|
@@ -10788,16 +10997,26 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10788
10997
|
this.writeToLog(logPath, "Failed to start within timeout", true);
|
|
10789
10998
|
return { success: false, error: "Dev server failed to start within timeout" };
|
|
10790
10999
|
}
|
|
11000
|
+
console.log(`[DevServerRunner] Waiting for ws-server on port ${wsPort}...`);
|
|
11001
|
+
const wsReady = await this.waitForWsPort(wsPort, DEV_SERVER_CONSTANTS.STARTUP_TIMEOUT_MS);
|
|
11002
|
+
if (!wsReady) {
|
|
11003
|
+
const wsStartError = this.detectWsStartupError(logPath, wsPort);
|
|
11004
|
+
this.writeToLog(logPath, `WARN: ${wsStartError}`, true);
|
|
11005
|
+
console.warn(
|
|
11006
|
+
`[DevServerRunner] EP1412: ws-server did not become healthy on ${wsPort}; continuing because preview liveness is app/tunnel based`
|
|
11007
|
+
);
|
|
11008
|
+
}
|
|
10791
11009
|
if (process2.pid) {
|
|
10792
11010
|
registry.register({
|
|
10793
11011
|
pid: process2.pid,
|
|
10794
11012
|
port,
|
|
11013
|
+
wsPort,
|
|
10795
11014
|
worktreePath: projectPath,
|
|
10796
11015
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10797
11016
|
moduleUid
|
|
10798
11017
|
});
|
|
10799
11018
|
}
|
|
10800
|
-
console.log(`[DevServerRunner] Server started successfully on
|
|
11019
|
+
console.log(`[DevServerRunner] Server started successfully on ports app:${port} ws:${wsPort}`);
|
|
10801
11020
|
this.writeToLog(logPath, "Server started successfully");
|
|
10802
11021
|
this.emit("started", moduleUid, port);
|
|
10803
11022
|
return { success: true };
|
|
@@ -10830,6 +11049,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10830
11049
|
state.process.kill("SIGKILL");
|
|
10831
11050
|
}
|
|
10832
11051
|
}
|
|
11052
|
+
await this.cleanupWsServerOnPort(state.wsPort, state.projectPath);
|
|
10833
11053
|
} else if (registryEntry) {
|
|
10834
11054
|
console.log(`[DevServerRunner] EP1042: Stopping orphaned server for ${moduleUid} (PID ${registryEntry.pid})`);
|
|
10835
11055
|
if (registry.isProcessRunning(registryEntry.pid)) {
|
|
@@ -10839,6 +11059,9 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10839
11059
|
registry.killProcess(registryEntry.pid, "SIGKILL");
|
|
10840
11060
|
}
|
|
10841
11061
|
}
|
|
11062
|
+
if (registryEntry.wsPort) {
|
|
11063
|
+
await this.cleanupWsServerOnPort(registryEntry.wsPort, registryEntry.worktreePath);
|
|
11064
|
+
}
|
|
10842
11065
|
}
|
|
10843
11066
|
this.servers.delete(moduleUid);
|
|
10844
11067
|
registry.unregister(moduleUid);
|
|
@@ -10852,7 +11075,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10852
11075
|
if (!state) {
|
|
10853
11076
|
return { success: false, error: `No dev server found for ${moduleUid}` };
|
|
10854
11077
|
}
|
|
10855
|
-
const { projectPath, port, autoRestartEnabled, customCommand, logFile } = state;
|
|
11078
|
+
const { projectPath, port, wsPort, autoRestartEnabled, customCommand, logFile } = state;
|
|
10856
11079
|
console.log(`[DevServerRunner] Restarting server for ${moduleUid}...`);
|
|
10857
11080
|
this.writeToLog(logFile, "Manual restart requested");
|
|
10858
11081
|
await this.stop(moduleUid);
|
|
@@ -10863,6 +11086,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10863
11086
|
return this.start({
|
|
10864
11087
|
projectPath,
|
|
10865
11088
|
port,
|
|
11089
|
+
wsPort,
|
|
10866
11090
|
moduleUid,
|
|
10867
11091
|
customCommand,
|
|
10868
11092
|
autoRestart: autoRestartEnabled
|
|
@@ -10878,6 +11102,16 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10878
11102
|
}
|
|
10879
11103
|
return this.checkHealth(state.port);
|
|
10880
11104
|
}
|
|
11105
|
+
/**
|
|
11106
|
+
* Check if ws-server for a module is healthy
|
|
11107
|
+
*/
|
|
11108
|
+
async isWsHealthy(moduleUid) {
|
|
11109
|
+
const state = this.servers.get(moduleUid);
|
|
11110
|
+
if (!state) {
|
|
11111
|
+
return false;
|
|
11112
|
+
}
|
|
11113
|
+
return this.checkWsHealth(state.wsPort);
|
|
11114
|
+
}
|
|
10881
11115
|
/**
|
|
10882
11116
|
* Check if a dev server is running
|
|
10883
11117
|
*/
|
|
@@ -10949,6 +11183,125 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10949
11183
|
return false;
|
|
10950
11184
|
}
|
|
10951
11185
|
}
|
|
11186
|
+
async ensureWsPortAvailable(projectPath, wsPort) {
|
|
11187
|
+
if (!await isPortInUse(wsPort)) {
|
|
11188
|
+
return null;
|
|
11189
|
+
}
|
|
11190
|
+
const pids = this.getPidsOnPort(wsPort);
|
|
11191
|
+
if (pids.length === 0) {
|
|
11192
|
+
return { success: false, error: `WS_BIND_CONFLICT: WS port ${wsPort} is already in use.` };
|
|
11193
|
+
}
|
|
11194
|
+
const registry = getDevServerRegistry();
|
|
11195
|
+
const expectedPath = path25.resolve(projectPath);
|
|
11196
|
+
const unsafe = [];
|
|
11197
|
+
const killable = [];
|
|
11198
|
+
for (const pid of pids) {
|
|
11199
|
+
const command = this.getProcessCommand(pid);
|
|
11200
|
+
const cwd = registry.getProcessCwd(pid);
|
|
11201
|
+
const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
|
|
11202
|
+
if (this.isLikelyWsServerProcess(command) && cwdMatches) {
|
|
11203
|
+
killable.push(pid);
|
|
11204
|
+
} else {
|
|
11205
|
+
unsafe.push({ pid, command });
|
|
11206
|
+
}
|
|
11207
|
+
}
|
|
11208
|
+
if (unsafe.length > 0) {
|
|
11209
|
+
const details = unsafe.map((item) => `PID ${item.pid} (${item.command})`).join(", ");
|
|
11210
|
+
return {
|
|
11211
|
+
success: false,
|
|
11212
|
+
error: `WS_BIND_CONFLICT: WS port ${wsPort} is in use by another process: ${details}`
|
|
11213
|
+
};
|
|
11214
|
+
}
|
|
11215
|
+
for (const pid of killable) {
|
|
11216
|
+
try {
|
|
11217
|
+
process.kill(pid, "SIGTERM");
|
|
11218
|
+
} catch {
|
|
11219
|
+
}
|
|
11220
|
+
}
|
|
11221
|
+
await this.wait(800);
|
|
11222
|
+
for (const pid of killable) {
|
|
11223
|
+
try {
|
|
11224
|
+
process.kill(pid, 0);
|
|
11225
|
+
process.kill(pid, "SIGKILL");
|
|
11226
|
+
} catch {
|
|
11227
|
+
}
|
|
11228
|
+
}
|
|
11229
|
+
await this.wait(400);
|
|
11230
|
+
if (await isPortInUse(wsPort)) {
|
|
11231
|
+
return {
|
|
11232
|
+
success: false,
|
|
11233
|
+
error: `WS_BIND_CONFLICT: WS port ${wsPort} remained in use after stale ws-server cleanup`
|
|
11234
|
+
};
|
|
11235
|
+
}
|
|
11236
|
+
return null;
|
|
11237
|
+
}
|
|
11238
|
+
async cleanupWsServerOnPort(wsPort, worktreePath) {
|
|
11239
|
+
if (!wsPort || !await isPortInUse(wsPort)) return;
|
|
11240
|
+
const registry = getDevServerRegistry();
|
|
11241
|
+
const expectedPath = path25.resolve(worktreePath);
|
|
11242
|
+
const pids = this.getPidsOnPort(wsPort);
|
|
11243
|
+
for (const pid of pids) {
|
|
11244
|
+
const command = this.getProcessCommand(pid);
|
|
11245
|
+
const cwd = registry.getProcessCwd(pid);
|
|
11246
|
+
const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
|
|
11247
|
+
if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
|
|
11248
|
+
continue;
|
|
11249
|
+
}
|
|
11250
|
+
try {
|
|
11251
|
+
process.kill(pid, "SIGTERM");
|
|
11252
|
+
} catch {
|
|
11253
|
+
}
|
|
11254
|
+
}
|
|
11255
|
+
await this.wait(500);
|
|
11256
|
+
for (const pid of pids) {
|
|
11257
|
+
const command = this.getProcessCommand(pid);
|
|
11258
|
+
const cwd = registry.getProcessCwd(pid);
|
|
11259
|
+
const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
|
|
11260
|
+
if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
|
|
11261
|
+
continue;
|
|
11262
|
+
}
|
|
11263
|
+
try {
|
|
11264
|
+
process.kill(pid, 0);
|
|
11265
|
+
process.kill(pid, "SIGKILL");
|
|
11266
|
+
} catch {
|
|
11267
|
+
}
|
|
11268
|
+
}
|
|
11269
|
+
}
|
|
11270
|
+
getPidsOnPort(port) {
|
|
11271
|
+
try {
|
|
11272
|
+
const output = (0, import_child_process13.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
|
|
11273
|
+
if (!output) return [];
|
|
11274
|
+
return output.split("\n").map((value) => parseInt(value, 10)).filter((value) => !isNaN(value) && value > 0);
|
|
11275
|
+
} catch {
|
|
11276
|
+
return [];
|
|
11277
|
+
}
|
|
11278
|
+
}
|
|
11279
|
+
getProcessCommand(pid) {
|
|
11280
|
+
try {
|
|
11281
|
+
return (0, import_child_process13.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8" }).trim() || "unknown";
|
|
11282
|
+
} catch {
|
|
11283
|
+
return "unknown";
|
|
11284
|
+
}
|
|
11285
|
+
}
|
|
11286
|
+
isLikelyWsServerProcess(command) {
|
|
11287
|
+
return command.includes("ws-server.js") || command.includes("build:ws-server");
|
|
11288
|
+
}
|
|
11289
|
+
detectWsStartupError(logPath, wsPort) {
|
|
11290
|
+
const fallback = `WS_BIND_CONFLICT: ws-server failed to become healthy on WS port ${wsPort}`;
|
|
11291
|
+
if (!logPath) return fallback;
|
|
11292
|
+
try {
|
|
11293
|
+
if (!fs25.existsSync(logPath)) return fallback;
|
|
11294
|
+
const content = fs25.readFileSync(logPath, "utf8");
|
|
11295
|
+
if (content.includes("EADDRINUSE") && content.includes(`${wsPort}`)) {
|
|
11296
|
+
return `WS_BIND_CONFLICT: ws-server could not bind to WS port ${wsPort} (EADDRINUSE)`;
|
|
11297
|
+
}
|
|
11298
|
+
if (content.includes("Failed to initialize")) {
|
|
11299
|
+
return `WS_SERVER_START_FAILED: ws-server failed during startup on WS port ${wsPort}`;
|
|
11300
|
+
}
|
|
11301
|
+
} catch {
|
|
11302
|
+
}
|
|
11303
|
+
return fallback;
|
|
11304
|
+
}
|
|
10952
11305
|
// ============ Private Methods ============
|
|
10953
11306
|
async fetchEnvVars(projectPath) {
|
|
10954
11307
|
try {
|
|
@@ -10962,8 +11315,8 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10962
11315
|
cacheTtl: 300
|
|
10963
11316
|
});
|
|
10964
11317
|
console.log(`[DevServerRunner] Loaded ${Object.keys(result.envVars).length} env vars`);
|
|
10965
|
-
const envFilePath =
|
|
10966
|
-
if (!
|
|
11318
|
+
const envFilePath = path25.join(projectPath, ".env");
|
|
11319
|
+
if (!fs25.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
|
|
10967
11320
|
console.log(`[DevServerRunner] Writing .env file`);
|
|
10968
11321
|
writeEnvFile(projectPath, result.envVars);
|
|
10969
11322
|
}
|
|
@@ -10973,7 +11326,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10973
11326
|
return {};
|
|
10974
11327
|
}
|
|
10975
11328
|
}
|
|
10976
|
-
spawnProcess(projectPath, port, moduleUid, logPath, customCommand, envVars) {
|
|
11329
|
+
spawnProcess(projectPath, port, wsPort, moduleUid, logPath, customCommand, envVars) {
|
|
10977
11330
|
this.rotateLogIfNeeded(logPath);
|
|
10978
11331
|
const nodeOptions = process.env.NODE_OPTIONS || "";
|
|
10979
11332
|
const memoryFlag = `--max-old-space-size=${DEV_SERVER_CONSTANTS.NODE_MEMORY_LIMIT_MB}`;
|
|
@@ -10985,6 +11338,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
10985
11338
|
...process.env,
|
|
10986
11339
|
...envVars,
|
|
10987
11340
|
PORT: String(port),
|
|
11341
|
+
WS_PORT: String(wsPort),
|
|
10988
11342
|
NODE_OPTIONS: enhancedNodeOptions
|
|
10989
11343
|
};
|
|
10990
11344
|
const proc = (0, import_child_process13.spawn)(cmd, args, {
|
|
@@ -11048,6 +11402,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11048
11402
|
const newProcess = this.spawnProcess(
|
|
11049
11403
|
state.projectPath,
|
|
11050
11404
|
state.port,
|
|
11405
|
+
state.wsPort,
|
|
11051
11406
|
moduleUid,
|
|
11052
11407
|
logPath,
|
|
11053
11408
|
state.customCommand,
|
|
@@ -11078,7 +11433,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11078
11433
|
return Math.min(delay, DEV_SERVER_CONSTANTS.MAX_RESTART_DELAY_MS);
|
|
11079
11434
|
}
|
|
11080
11435
|
async checkHealth(port) {
|
|
11081
|
-
return new Promise((
|
|
11436
|
+
return new Promise((resolve6) => {
|
|
11082
11437
|
const req = http.request(
|
|
11083
11438
|
{
|
|
11084
11439
|
hostname: "localhost",
|
|
@@ -11087,12 +11442,35 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11087
11442
|
method: "HEAD",
|
|
11088
11443
|
timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
|
|
11089
11444
|
},
|
|
11090
|
-
() =>
|
|
11445
|
+
() => resolve6(true)
|
|
11446
|
+
);
|
|
11447
|
+
req.on("error", () => resolve6(false));
|
|
11448
|
+
req.on("timeout", () => {
|
|
11449
|
+
req.destroy();
|
|
11450
|
+
resolve6(false);
|
|
11451
|
+
});
|
|
11452
|
+
req.end();
|
|
11453
|
+
});
|
|
11454
|
+
}
|
|
11455
|
+
async checkWsHealth(wsPort) {
|
|
11456
|
+
return new Promise((resolve6) => {
|
|
11457
|
+
const req = http.request(
|
|
11458
|
+
{
|
|
11459
|
+
hostname: "127.0.0.1",
|
|
11460
|
+
port: wsPort,
|
|
11461
|
+
path: "/health",
|
|
11462
|
+
method: "GET",
|
|
11463
|
+
timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
|
|
11464
|
+
},
|
|
11465
|
+
(res) => {
|
|
11466
|
+
resolve6(res.statusCode === 200);
|
|
11467
|
+
res.resume();
|
|
11468
|
+
}
|
|
11091
11469
|
);
|
|
11092
|
-
req.on("error", () =>
|
|
11470
|
+
req.on("error", () => resolve6(false));
|
|
11093
11471
|
req.on("timeout", () => {
|
|
11094
11472
|
req.destroy();
|
|
11095
|
-
|
|
11473
|
+
resolve6(false);
|
|
11096
11474
|
});
|
|
11097
11475
|
req.end();
|
|
11098
11476
|
});
|
|
@@ -11108,29 +11486,40 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11108
11486
|
}
|
|
11109
11487
|
return false;
|
|
11110
11488
|
}
|
|
11489
|
+
async waitForWsPort(wsPort, timeoutMs) {
|
|
11490
|
+
const startTime = Date.now();
|
|
11491
|
+
const checkInterval = 500;
|
|
11492
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
11493
|
+
if (await this.checkWsHealth(wsPort)) {
|
|
11494
|
+
return true;
|
|
11495
|
+
}
|
|
11496
|
+
await this.wait(checkInterval);
|
|
11497
|
+
}
|
|
11498
|
+
return false;
|
|
11499
|
+
}
|
|
11111
11500
|
wait(ms) {
|
|
11112
|
-
return new Promise((
|
|
11501
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
11113
11502
|
}
|
|
11114
11503
|
getLogsDir() {
|
|
11115
|
-
const logsDir =
|
|
11116
|
-
if (!
|
|
11117
|
-
|
|
11504
|
+
const logsDir = path25.join((0, import_core12.getConfigDir)(), "logs");
|
|
11505
|
+
if (!fs25.existsSync(logsDir)) {
|
|
11506
|
+
fs25.mkdirSync(logsDir, { recursive: true });
|
|
11118
11507
|
}
|
|
11119
11508
|
return logsDir;
|
|
11120
11509
|
}
|
|
11121
11510
|
getLogFilePath(moduleUid) {
|
|
11122
|
-
return
|
|
11511
|
+
return path25.join(this.getLogsDir(), `dev-${moduleUid}.log`);
|
|
11123
11512
|
}
|
|
11124
11513
|
rotateLogIfNeeded(logPath) {
|
|
11125
11514
|
try {
|
|
11126
|
-
if (
|
|
11127
|
-
const stats =
|
|
11515
|
+
if (fs25.existsSync(logPath)) {
|
|
11516
|
+
const stats = fs25.statSync(logPath);
|
|
11128
11517
|
if (stats.size > DEV_SERVER_CONSTANTS.MAX_LOG_SIZE_BYTES) {
|
|
11129
11518
|
const backupPath = `${logPath}.1`;
|
|
11130
|
-
if (
|
|
11131
|
-
|
|
11519
|
+
if (fs25.existsSync(backupPath)) {
|
|
11520
|
+
fs25.unlinkSync(backupPath);
|
|
11132
11521
|
}
|
|
11133
|
-
|
|
11522
|
+
fs25.renameSync(logPath, backupPath);
|
|
11134
11523
|
}
|
|
11135
11524
|
}
|
|
11136
11525
|
} catch {
|
|
@@ -11141,7 +11530,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11141
11530
|
try {
|
|
11142
11531
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11143
11532
|
const prefix = isError ? "ERR" : "OUT";
|
|
11144
|
-
|
|
11533
|
+
fs25.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
|
|
11145
11534
|
`);
|
|
11146
11535
|
} catch {
|
|
11147
11536
|
}
|
|
@@ -11150,6 +11539,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
|
|
|
11150
11539
|
return {
|
|
11151
11540
|
moduleUid: state.moduleUid,
|
|
11152
11541
|
port: state.port,
|
|
11542
|
+
wsPort: state.wsPort,
|
|
11153
11543
|
pid: state.process.pid,
|
|
11154
11544
|
startedAt: state.startedAt,
|
|
11155
11545
|
uptime: Math.floor((Date.now() - state.startedAt.getTime()) / 1e3),
|
|
@@ -11210,7 +11600,8 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11210
11600
|
* @returns Result with success status and preview URL
|
|
11211
11601
|
*/
|
|
11212
11602
|
async startPreview(config) {
|
|
11213
|
-
const { moduleUid, worktreePath, port = DEFAULT_PORT, customCommand } = config;
|
|
11603
|
+
const { moduleUid, worktreePath, port = DEFAULT_PORT, wsPort: requestedWsPort, customCommand } = config;
|
|
11604
|
+
let wsPort = requestedWsPort;
|
|
11214
11605
|
if (!worktreePath) {
|
|
11215
11606
|
return { success: false, error: "Worktree path is required" };
|
|
11216
11607
|
}
|
|
@@ -11237,29 +11628,50 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11237
11628
|
}
|
|
11238
11629
|
const existing = this.previews.get(moduleUid);
|
|
11239
11630
|
if (existing && (existing.state === "live" || existing.state === "running")) {
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11631
|
+
const appHealthy = await this.devServer.isHealthy(moduleUid);
|
|
11632
|
+
const wsHealthy = await this.devServer.isWsHealthy(moduleUid);
|
|
11633
|
+
if (appHealthy) {
|
|
11634
|
+
if (!wsHealthy) {
|
|
11635
|
+
console.warn(
|
|
11636
|
+
`[PreviewManager] EP1412: Preview app is healthy for ${moduleUid}; ws-server unhealthy (${existing.wsPort}) is diagnostics-only for preview liveness`
|
|
11637
|
+
);
|
|
11638
|
+
}
|
|
11639
|
+
console.log(`[PreviewManager] Preview already running for ${moduleUid}`);
|
|
11640
|
+
return {
|
|
11641
|
+
success: true,
|
|
11642
|
+
previewUrl: existing.tunnelUrl,
|
|
11643
|
+
alreadyRunning: true
|
|
11644
|
+
};
|
|
11645
|
+
}
|
|
11646
|
+
console.warn(
|
|
11647
|
+
`[PreviewManager] EP1412: Existing preview for ${moduleUid} is unhealthy (app=${appHealthy}, ws=${wsHealthy}); restarting due to app health failure`
|
|
11648
|
+
);
|
|
11649
|
+
await this.stopPreview(moduleUid);
|
|
11246
11650
|
}
|
|
11247
11651
|
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");
|
|
11652
|
+
let state = null;
|
|
11258
11653
|
try {
|
|
11654
|
+
if (!wsPort) {
|
|
11655
|
+
wsPort = allocateWsPort(moduleUid);
|
|
11656
|
+
}
|
|
11657
|
+
wsPort = assignWsPort(moduleUid, wsPort);
|
|
11658
|
+
console.log(`[PreviewManager] Starting preview for ${moduleUid} at ${worktreePath}:${port} (ws:${wsPort})`);
|
|
11659
|
+
state = {
|
|
11660
|
+
moduleUid,
|
|
11661
|
+
worktreePath,
|
|
11662
|
+
port,
|
|
11663
|
+
wsPort,
|
|
11664
|
+
state: "starting",
|
|
11665
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
11666
|
+
};
|
|
11667
|
+
const activeState = state;
|
|
11668
|
+
this.previews.set(moduleUid, state);
|
|
11669
|
+
this.emitStateChange(moduleUid, "starting");
|
|
11259
11670
|
console.log(`[PreviewManager] Starting dev server for ${moduleUid}...`);
|
|
11260
11671
|
const devResult = await this.devServer.start({
|
|
11261
11672
|
projectPath: worktreePath,
|
|
11262
11673
|
port,
|
|
11674
|
+
wsPort,
|
|
11263
11675
|
moduleUid,
|
|
11264
11676
|
customCommand,
|
|
11265
11677
|
autoRestart: true
|
|
@@ -11267,6 +11679,8 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11267
11679
|
if (!devResult.success) {
|
|
11268
11680
|
state.state = "error";
|
|
11269
11681
|
state.error = devResult.error || "Failed to start dev server";
|
|
11682
|
+
releasePort(moduleUid);
|
|
11683
|
+
releaseWsPort(moduleUid);
|
|
11270
11684
|
this.emitStateChange(moduleUid, "error");
|
|
11271
11685
|
this.emit("error", moduleUid, new Error(state.error));
|
|
11272
11686
|
return { success: false, error: state.error };
|
|
@@ -11275,7 +11689,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11275
11689
|
this.emitStateChange(moduleUid, "running");
|
|
11276
11690
|
console.log(`[PreviewManager] Dev server running on port ${port}`);
|
|
11277
11691
|
console.log(`[PreviewManager] Starting Named Tunnel for ${moduleUid}...`);
|
|
11278
|
-
|
|
11692
|
+
activeState.state = "tunneling";
|
|
11279
11693
|
this.emitStateChange(moduleUid, "tunneling");
|
|
11280
11694
|
const MAX_TUNNEL_RETRIES = 2;
|
|
11281
11695
|
let tunnelResult = { success: false };
|
|
@@ -11283,7 +11697,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11283
11697
|
for (let attempt = 1; attempt <= MAX_TUNNEL_RETRIES; attempt++) {
|
|
11284
11698
|
if (attempt > 1) {
|
|
11285
11699
|
console.log(`[PreviewManager] Retrying tunnel for ${moduleUid} (attempt ${attempt}/${MAX_TUNNEL_RETRIES})...`);
|
|
11286
|
-
await new Promise((
|
|
11700
|
+
await new Promise((resolve6) => setTimeout(resolve6, 2e3));
|
|
11287
11701
|
}
|
|
11288
11702
|
tunnelResult = await this.tunnel.startTunnel({
|
|
11289
11703
|
moduleUid,
|
|
@@ -11293,20 +11707,20 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11293
11707
|
onStatusChange: (status, error) => {
|
|
11294
11708
|
console.log(`[PreviewManager] Tunnel status for ${moduleUid}: ${status}${error ? ` - ${error}` : ""}`);
|
|
11295
11709
|
if (status === "error") {
|
|
11296
|
-
|
|
11297
|
-
|
|
11710
|
+
activeState.state = "error";
|
|
11711
|
+
activeState.error = error || "Tunnel error";
|
|
11298
11712
|
this.emitStateChange(moduleUid, "error");
|
|
11299
11713
|
} else if (status === "disconnected") {
|
|
11300
|
-
|
|
11301
|
-
|
|
11714
|
+
activeState.state = "running";
|
|
11715
|
+
activeState.tunnelUrl = void 0;
|
|
11302
11716
|
this.emitStateChange(moduleUid, "running");
|
|
11303
11717
|
} else if (status === "reconnecting") {
|
|
11304
|
-
|
|
11718
|
+
activeState.state = "tunneling";
|
|
11305
11719
|
this.emitStateChange(moduleUid, "tunneling");
|
|
11306
11720
|
}
|
|
11307
11721
|
},
|
|
11308
11722
|
onUrl: (url) => {
|
|
11309
|
-
|
|
11723
|
+
activeState.tunnelUrl = url;
|
|
11310
11724
|
}
|
|
11311
11725
|
});
|
|
11312
11726
|
if (tunnelResult.success) {
|
|
@@ -11322,8 +11736,10 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11322
11736
|
} catch (cleanupError) {
|
|
11323
11737
|
console.warn(`[PreviewManager] Error cleaning up dev server after tunnel failure:`, cleanupError);
|
|
11324
11738
|
}
|
|
11325
|
-
|
|
11326
|
-
|
|
11739
|
+
releasePort(moduleUid);
|
|
11740
|
+
releaseWsPort(moduleUid);
|
|
11741
|
+
activeState.state = "error";
|
|
11742
|
+
activeState.error = lastError;
|
|
11327
11743
|
this.previews.delete(moduleUid);
|
|
11328
11744
|
this.emitStateChange(moduleUid, "error");
|
|
11329
11745
|
return {
|
|
@@ -11331,9 +11747,9 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11331
11747
|
error: `Tunnel failed after ${MAX_TUNNEL_RETRIES} attempts: ${lastError}`
|
|
11332
11748
|
};
|
|
11333
11749
|
}
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11750
|
+
activeState.state = "live";
|
|
11751
|
+
activeState.tunnelUrl = tunnelResult.url;
|
|
11752
|
+
activeState.error = void 0;
|
|
11337
11753
|
this.emitStateChange(moduleUid, "live");
|
|
11338
11754
|
this.emit("live", moduleUid, tunnelResult.url);
|
|
11339
11755
|
console.log(`[PreviewManager] Preview live for ${moduleUid}: ${tunnelResult.url}`);
|
|
@@ -11344,9 +11760,17 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11344
11760
|
} catch (error) {
|
|
11345
11761
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
11346
11762
|
console.error(`[PreviewManager] Error starting preview for ${moduleUid}:`, error);
|
|
11347
|
-
state
|
|
11348
|
-
|
|
11349
|
-
|
|
11763
|
+
if (state) {
|
|
11764
|
+
state.state = "error";
|
|
11765
|
+
state.error = errorMsg;
|
|
11766
|
+
} else {
|
|
11767
|
+
this.previews.delete(moduleUid);
|
|
11768
|
+
}
|
|
11769
|
+
releasePort(moduleUid);
|
|
11770
|
+
releaseWsPort(moduleUid);
|
|
11771
|
+
if (state) {
|
|
11772
|
+
this.emitStateChange(moduleUid, "error");
|
|
11773
|
+
}
|
|
11350
11774
|
this.emit("error", moduleUid, error instanceof Error ? error : new Error(errorMsg));
|
|
11351
11775
|
return { success: false, error: errorMsg };
|
|
11352
11776
|
} finally {
|
|
@@ -11382,6 +11806,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11382
11806
|
console.warn(`[PreviewManager] Error clearing tunnel URL for ${moduleUid}:`, error);
|
|
11383
11807
|
}
|
|
11384
11808
|
releasePort(moduleUid);
|
|
11809
|
+
releaseWsPort(moduleUid);
|
|
11385
11810
|
if (state) {
|
|
11386
11811
|
state.state = "stopped";
|
|
11387
11812
|
state.tunnelUrl = void 0;
|
|
@@ -11404,11 +11829,12 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11404
11829
|
}
|
|
11405
11830
|
console.log(`[PreviewManager] Restarting preview for ${moduleUid}`);
|
|
11406
11831
|
await this.stopPreview(moduleUid);
|
|
11407
|
-
await new Promise((
|
|
11832
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
11408
11833
|
return this.startPreview({
|
|
11409
11834
|
moduleUid,
|
|
11410
11835
|
worktreePath: state.worktreePath,
|
|
11411
|
-
port: state.port
|
|
11836
|
+
port: state.port,
|
|
11837
|
+
wsPort: state.wsPort
|
|
11412
11838
|
});
|
|
11413
11839
|
}
|
|
11414
11840
|
/**
|
|
@@ -11431,6 +11857,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
11431
11857
|
tunnelUrl: state.tunnelUrl,
|
|
11432
11858
|
tunnelState: tunnelInfo?.status === "connected" ? "connected" : tunnelInfo?.status === "starting" ? "starting" : tunnelInfo?.status === "error" ? "error" : tunnelInfo?.status === "disconnected" ? "disconnected" : void 0,
|
|
11433
11859
|
port: state.port,
|
|
11860
|
+
wsPort: state.wsPort,
|
|
11434
11861
|
error: state.error,
|
|
11435
11862
|
startedAt: state.startedAt
|
|
11436
11863
|
};
|
|
@@ -11595,8 +12022,8 @@ async function deleteWorktree(config, moduleUid) {
|
|
|
11595
12022
|
}
|
|
11596
12023
|
|
|
11597
12024
|
// src/daemon/package-manager.ts
|
|
11598
|
-
var
|
|
11599
|
-
var
|
|
12025
|
+
var fs26 = __toESM(require("fs"));
|
|
12026
|
+
var path26 = __toESM(require("path"));
|
|
11600
12027
|
function pnpmCommand(args) {
|
|
11601
12028
|
return [
|
|
11602
12029
|
"if command -v pnpm >/dev/null 2>&1; then",
|
|
@@ -11614,13 +12041,13 @@ var PACKAGE_MANAGERS = {
|
|
|
11614
12041
|
{
|
|
11615
12042
|
name: "pnpm",
|
|
11616
12043
|
detector: (p) => {
|
|
11617
|
-
if (
|
|
12044
|
+
if (fs26.existsSync(path26.join(p, "pnpm-lock.yaml"))) {
|
|
11618
12045
|
return "pnpm-lock.yaml";
|
|
11619
12046
|
}
|
|
11620
|
-
const pkgJsonPath =
|
|
11621
|
-
if (
|
|
12047
|
+
const pkgJsonPath = path26.join(p, "package.json");
|
|
12048
|
+
if (fs26.existsSync(pkgJsonPath)) {
|
|
11622
12049
|
try {
|
|
11623
|
-
const pkg = JSON.parse(
|
|
12050
|
+
const pkg = JSON.parse(fs26.readFileSync(pkgJsonPath, "utf-8"));
|
|
11624
12051
|
if (pkg.packageManager?.startsWith("pnpm")) {
|
|
11625
12052
|
return "package.json (packageManager field)";
|
|
11626
12053
|
}
|
|
@@ -11636,7 +12063,7 @@ var PACKAGE_MANAGERS = {
|
|
|
11636
12063
|
},
|
|
11637
12064
|
{
|
|
11638
12065
|
name: "yarn",
|
|
11639
|
-
detector: (p) =>
|
|
12066
|
+
detector: (p) => fs26.existsSync(path26.join(p, "yarn.lock")) ? "yarn.lock" : null,
|
|
11640
12067
|
config: {
|
|
11641
12068
|
installCmd: "yarn install --frozen-lockfile",
|
|
11642
12069
|
cacheEnvVar: "YARN_CACHE_FOLDER"
|
|
@@ -11644,14 +12071,14 @@ var PACKAGE_MANAGERS = {
|
|
|
11644
12071
|
},
|
|
11645
12072
|
{
|
|
11646
12073
|
name: "bun",
|
|
11647
|
-
detector: (p) =>
|
|
12074
|
+
detector: (p) => fs26.existsSync(path26.join(p, "bun.lockb")) ? "bun.lockb" : null,
|
|
11648
12075
|
config: {
|
|
11649
12076
|
installCmd: "bun install --frozen-lockfile"
|
|
11650
12077
|
}
|
|
11651
12078
|
},
|
|
11652
12079
|
{
|
|
11653
12080
|
name: "npm",
|
|
11654
|
-
detector: (p) =>
|
|
12081
|
+
detector: (p) => fs26.existsSync(path26.join(p, "package-lock.json")) ? "package-lock.json" : null,
|
|
11655
12082
|
config: {
|
|
11656
12083
|
installCmd: "npm ci"
|
|
11657
12084
|
}
|
|
@@ -11660,7 +12087,7 @@ var PACKAGE_MANAGERS = {
|
|
|
11660
12087
|
// EP1222: Default to pnpm for new projects without lockfile
|
|
11661
12088
|
// This encourages standardization on pnpm across Episoda projects
|
|
11662
12089
|
name: "pnpm",
|
|
11663
|
-
detector: (p) =>
|
|
12090
|
+
detector: (p) => fs26.existsSync(path26.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
|
|
11664
12091
|
config: {
|
|
11665
12092
|
installCmd: pnpmCommand("install"),
|
|
11666
12093
|
cacheEnvVar: "PNPM_HOME"
|
|
@@ -11671,13 +12098,13 @@ var PACKAGE_MANAGERS = {
|
|
|
11671
12098
|
{
|
|
11672
12099
|
name: "uv",
|
|
11673
12100
|
detector: (p) => {
|
|
11674
|
-
const pyprojectPath =
|
|
11675
|
-
if (
|
|
12101
|
+
const pyprojectPath = path26.join(p, "pyproject.toml");
|
|
12102
|
+
if (fs26.existsSync(path26.join(p, "uv.lock"))) {
|
|
11676
12103
|
return "uv.lock";
|
|
11677
12104
|
}
|
|
11678
|
-
if (
|
|
12105
|
+
if (fs26.existsSync(pyprojectPath)) {
|
|
11679
12106
|
try {
|
|
11680
|
-
const content =
|
|
12107
|
+
const content = fs26.readFileSync(pyprojectPath, "utf-8");
|
|
11681
12108
|
if (content.includes("[tool.uv]") || content.includes("[project]")) {
|
|
11682
12109
|
return "pyproject.toml";
|
|
11683
12110
|
}
|
|
@@ -11693,7 +12120,7 @@ var PACKAGE_MANAGERS = {
|
|
|
11693
12120
|
},
|
|
11694
12121
|
{
|
|
11695
12122
|
name: "pip",
|
|
11696
|
-
detector: (p) =>
|
|
12123
|
+
detector: (p) => fs26.existsSync(path26.join(p, "requirements.txt")) ? "requirements.txt" : null,
|
|
11697
12124
|
config: {
|
|
11698
12125
|
installCmd: "pip install -r requirements.txt"
|
|
11699
12126
|
}
|
|
@@ -11745,15 +12172,15 @@ function detectPackageManager(worktreePath, preferredLanguages) {
|
|
|
11745
12172
|
}
|
|
11746
12173
|
|
|
11747
12174
|
// src/daemon/build-packages.ts
|
|
11748
|
-
var
|
|
11749
|
-
var
|
|
12175
|
+
var fs27 = __toESM(require("fs"));
|
|
12176
|
+
var path27 = __toESM(require("path"));
|
|
11750
12177
|
function hasPackageScript(worktreePath, scriptName) {
|
|
11751
12178
|
try {
|
|
11752
|
-
const packageJsonPath =
|
|
11753
|
-
if (!
|
|
12179
|
+
const packageJsonPath = path27.join(worktreePath, "package.json");
|
|
12180
|
+
if (!fs27.existsSync(packageJsonPath)) {
|
|
11754
12181
|
return false;
|
|
11755
12182
|
}
|
|
11756
|
-
const packageJson2 = JSON.parse(
|
|
12183
|
+
const packageJson2 = JSON.parse(fs27.readFileSync(packageJsonPath, "utf-8"));
|
|
11757
12184
|
return typeof packageJson2.scripts?.[scriptName] === "string";
|
|
11758
12185
|
} catch {
|
|
11759
12186
|
return false;
|
|
@@ -11811,19 +12238,19 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
|
|
|
11811
12238
|
if (process.env.EPISODA_MODE !== "cloud") {
|
|
11812
12239
|
return;
|
|
11813
12240
|
}
|
|
11814
|
-
const homeDir = process.env.HOME ||
|
|
11815
|
-
const workspaceConfigPath =
|
|
12241
|
+
const homeDir = process.env.HOME || os13.homedir();
|
|
12242
|
+
const workspaceConfigPath = path28.join(
|
|
11816
12243
|
homeDir,
|
|
11817
12244
|
"episoda",
|
|
11818
12245
|
workspaceSlug,
|
|
11819
12246
|
".episoda",
|
|
11820
12247
|
"config.json"
|
|
11821
12248
|
);
|
|
11822
|
-
if (!
|
|
12249
|
+
if (!fs28.existsSync(workspaceConfigPath)) {
|
|
11823
12250
|
return;
|
|
11824
12251
|
}
|
|
11825
12252
|
try {
|
|
11826
|
-
const content =
|
|
12253
|
+
const content = fs28.readFileSync(workspaceConfigPath, "utf8");
|
|
11827
12254
|
const workspaceConfig = JSON.parse(content);
|
|
11828
12255
|
let changed = false;
|
|
11829
12256
|
if (projectId && workspaceConfig.projectId !== projectId && workspaceConfig.project_id !== projectId) {
|
|
@@ -11835,7 +12262,7 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
|
|
|
11835
12262
|
changed = true;
|
|
11836
12263
|
}
|
|
11837
12264
|
if (changed) {
|
|
11838
|
-
|
|
12265
|
+
fs28.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
|
|
11839
12266
|
console.log("[Worktree] Updated workspace config with project context");
|
|
11840
12267
|
}
|
|
11841
12268
|
} catch (error) {
|
|
@@ -11882,19 +12309,19 @@ async function handleWorktreeCreate(request2) {
|
|
|
11882
12309
|
}
|
|
11883
12310
|
try {
|
|
11884
12311
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
11885
|
-
const bareRepoPath =
|
|
12312
|
+
const bareRepoPath = path28.join(projectPath, ".bare");
|
|
11886
12313
|
const gitEnv = projectId ? { ...process.env, EPISODA_PROJECT_ID: projectId } : process.env;
|
|
11887
|
-
if (!
|
|
12314
|
+
if (!fs28.existsSync(bareRepoPath)) {
|
|
11888
12315
|
console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
|
|
11889
12316
|
console.log(`[Worktree] Repo URL: ${repoUrl.replace(/\/\/[^@]*@/, "//***@")}`);
|
|
11890
|
-
const episodaDir =
|
|
11891
|
-
|
|
12317
|
+
const episodaDir = path28.join(projectPath, ".episoda");
|
|
12318
|
+
fs28.mkdirSync(episodaDir, { recursive: true });
|
|
11892
12319
|
try {
|
|
11893
12320
|
console.log(`[Worktree] K1273: Starting git clone...`);
|
|
11894
12321
|
await execAsync2(`git clone --bare "${repoUrl}" "${bareRepoPath}"`, { env: gitEnv });
|
|
11895
12322
|
console.log(`[Worktree] K1273: Clone successful`);
|
|
11896
12323
|
await execAsync2(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { env: gitEnv });
|
|
11897
|
-
const configPath =
|
|
12324
|
+
const configPath = path28.join(episodaDir, "config.json");
|
|
11898
12325
|
const config = {
|
|
11899
12326
|
projectId,
|
|
11900
12327
|
workspaceSlug,
|
|
@@ -11903,7 +12330,7 @@ async function handleWorktreeCreate(request2) {
|
|
|
11903
12330
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11904
12331
|
worktrees: []
|
|
11905
12332
|
};
|
|
11906
|
-
|
|
12333
|
+
fs28.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
11907
12334
|
console.log(`[Worktree] K1273: Project initialized at ${projectPath}`);
|
|
11908
12335
|
} catch (cloneError) {
|
|
11909
12336
|
console.error(`[Worktree] K1273: Git clone failed: ${cloneError.message}`);
|
|
@@ -11916,8 +12343,8 @@ async function handleWorktreeCreate(request2) {
|
|
|
11916
12343
|
console.log(`[Worktree] EP1373: Project exists, skipping handler-level fetch (manager handles narrow fetch)`);
|
|
11917
12344
|
}
|
|
11918
12345
|
const manager = new WorktreeManager(projectPath);
|
|
11919
|
-
const
|
|
11920
|
-
if (!
|
|
12346
|
+
const initialized3 = await manager.initialize();
|
|
12347
|
+
if (!initialized3) {
|
|
11921
12348
|
return {
|
|
11922
12349
|
success: false,
|
|
11923
12350
|
error: `Failed to initialize WorktreeManager at ${projectPath}`
|
|
@@ -11938,8 +12365,8 @@ async function handleWorktreeCreate(request2) {
|
|
|
11938
12365
|
let finalError;
|
|
11939
12366
|
if (envVars && Object.keys(envVars).length > 0) {
|
|
11940
12367
|
const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
11941
|
-
const envPath =
|
|
11942
|
-
|
|
12368
|
+
const envPath = path28.join(worktreePath, ".env");
|
|
12369
|
+
fs28.writeFileSync(envPath, envContent + "\n", "utf-8");
|
|
11943
12370
|
console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
|
|
11944
12371
|
}
|
|
11945
12372
|
const isCloud = process.env.EPISODA_MODE === "cloud";
|
|
@@ -12011,8 +12438,8 @@ async function handleWorktreeRelease(request2) {
|
|
|
12011
12438
|
try {
|
|
12012
12439
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
12013
12440
|
const manager = new WorktreeManager(projectPath);
|
|
12014
|
-
const
|
|
12015
|
-
if (!
|
|
12441
|
+
const initialized3 = await manager.initialize();
|
|
12442
|
+
if (!initialized3) {
|
|
12016
12443
|
console.log(`[Worktree] EP1143: Project not initialized, nothing to release`);
|
|
12017
12444
|
return { success: true };
|
|
12018
12445
|
}
|
|
@@ -12065,8 +12492,8 @@ async function handleWorktreeList(workspaceSlug, projectSlug) {
|
|
|
12065
12492
|
try {
|
|
12066
12493
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
12067
12494
|
const manager = new WorktreeManager(projectPath);
|
|
12068
|
-
const
|
|
12069
|
-
if (!
|
|
12495
|
+
const initialized3 = await manager.initialize();
|
|
12496
|
+
if (!initialized3) {
|
|
12070
12497
|
return { success: true, worktrees: [] };
|
|
12071
12498
|
}
|
|
12072
12499
|
const worktrees = manager.listWorktrees();
|
|
@@ -12080,18 +12507,18 @@ async function handleProjectEject(request2) {
|
|
|
12080
12507
|
console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
|
|
12081
12508
|
try {
|
|
12082
12509
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
12083
|
-
const bareRepoPath =
|
|
12084
|
-
if (!
|
|
12510
|
+
const bareRepoPath = path28.join(projectPath, ".bare");
|
|
12511
|
+
if (!fs28.existsSync(projectPath)) {
|
|
12085
12512
|
console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
|
|
12086
12513
|
return { success: true };
|
|
12087
12514
|
}
|
|
12088
|
-
if (!
|
|
12515
|
+
if (!fs28.existsSync(bareRepoPath)) {
|
|
12089
12516
|
console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
|
|
12090
12517
|
return { success: true };
|
|
12091
12518
|
}
|
|
12092
12519
|
const manager = new WorktreeManager(projectPath);
|
|
12093
|
-
const
|
|
12094
|
-
if (
|
|
12520
|
+
const initialized3 = await manager.initialize();
|
|
12521
|
+
if (initialized3) {
|
|
12095
12522
|
const worktrees = manager.listWorktrees();
|
|
12096
12523
|
if (worktrees.length > 0) {
|
|
12097
12524
|
return {
|
|
@@ -12100,12 +12527,12 @@ async function handleProjectEject(request2) {
|
|
|
12100
12527
|
};
|
|
12101
12528
|
}
|
|
12102
12529
|
}
|
|
12103
|
-
const artifactsPath =
|
|
12104
|
-
if (
|
|
12530
|
+
const artifactsPath = path28.join(projectPath, "artifacts");
|
|
12531
|
+
if (fs28.existsSync(artifactsPath)) {
|
|
12105
12532
|
await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
|
|
12106
12533
|
}
|
|
12107
12534
|
console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
|
|
12108
|
-
await
|
|
12535
|
+
await fs28.promises.rm(projectPath, { recursive: true, force: true });
|
|
12109
12536
|
console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
|
|
12110
12537
|
return { success: true };
|
|
12111
12538
|
} catch (error) {
|
|
@@ -12119,7 +12546,7 @@ async function handleProjectEject(request2) {
|
|
|
12119
12546
|
async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
|
|
12120
12547
|
const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
|
|
12121
12548
|
try {
|
|
12122
|
-
const files = await
|
|
12549
|
+
const files = await fs28.promises.readdir(artifactsPath);
|
|
12123
12550
|
if (files.length === 0) {
|
|
12124
12551
|
return;
|
|
12125
12552
|
}
|
|
@@ -12133,8 +12560,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
|
|
|
12133
12560
|
}
|
|
12134
12561
|
const artifacts = [];
|
|
12135
12562
|
for (const fileName of files) {
|
|
12136
|
-
const filePath =
|
|
12137
|
-
const stat = await
|
|
12563
|
+
const filePath = path28.join(artifactsPath, fileName);
|
|
12564
|
+
const stat = await fs28.promises.stat(filePath);
|
|
12138
12565
|
if (stat.isDirectory()) {
|
|
12139
12566
|
continue;
|
|
12140
12567
|
}
|
|
@@ -12143,9 +12570,9 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
|
|
|
12143
12570
|
continue;
|
|
12144
12571
|
}
|
|
12145
12572
|
try {
|
|
12146
|
-
const content = await
|
|
12573
|
+
const content = await fs28.promises.readFile(filePath);
|
|
12147
12574
|
const base64Content = content.toString("base64");
|
|
12148
|
-
const ext =
|
|
12575
|
+
const ext = path28.extname(fileName).toLowerCase();
|
|
12149
12576
|
const mimeTypes = {
|
|
12150
12577
|
".json": "application/json",
|
|
12151
12578
|
".txt": "text/plain",
|
|
@@ -12201,8 +12628,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
|
|
|
12201
12628
|
}
|
|
12202
12629
|
|
|
12203
12630
|
// src/daemon/handlers/project-handlers.ts
|
|
12204
|
-
var
|
|
12205
|
-
var
|
|
12631
|
+
var path29 = __toESM(require("path"));
|
|
12632
|
+
var fs29 = __toESM(require("fs"));
|
|
12206
12633
|
function validateSlug(slug, fieldName) {
|
|
12207
12634
|
if (!slug || typeof slug !== "string") {
|
|
12208
12635
|
return `${fieldName} is required`;
|
|
@@ -12242,14 +12669,14 @@ async function handleProjectSetup(params) {
|
|
|
12242
12669
|
console.log(`[ProjectSetup] EP1199: Setting up project ${workspaceSlug}/${projectSlug}`);
|
|
12243
12670
|
try {
|
|
12244
12671
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
12245
|
-
const artifactsPath =
|
|
12246
|
-
const configDir =
|
|
12247
|
-
const configPath =
|
|
12248
|
-
await
|
|
12249
|
-
await
|
|
12672
|
+
const artifactsPath = path29.join(projectPath, "artifacts");
|
|
12673
|
+
const configDir = path29.join(projectPath, ".episoda");
|
|
12674
|
+
const configPath = path29.join(configDir, "config.json");
|
|
12675
|
+
await fs29.promises.mkdir(artifactsPath, { recursive: true });
|
|
12676
|
+
await fs29.promises.mkdir(configDir, { recursive: true });
|
|
12250
12677
|
let existingConfig = {};
|
|
12251
12678
|
try {
|
|
12252
|
-
const existing = await
|
|
12679
|
+
const existing = await fs29.promises.readFile(configPath, "utf-8");
|
|
12253
12680
|
existingConfig = JSON.parse(existing);
|
|
12254
12681
|
} catch {
|
|
12255
12682
|
}
|
|
@@ -12262,7 +12689,7 @@ async function handleProjectSetup(params) {
|
|
|
12262
12689
|
// Only set created_at if not already present
|
|
12263
12690
|
created_at: existingConfig.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
12264
12691
|
};
|
|
12265
|
-
await
|
|
12692
|
+
await fs29.promises.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
12266
12693
|
console.log(`[ProjectSetup] EP1199: Project setup complete at ${projectPath}`);
|
|
12267
12694
|
return {
|
|
12268
12695
|
success: true,
|
|
@@ -12282,8 +12709,8 @@ async function handleProjectSetup(params) {
|
|
|
12282
12709
|
// src/utils/dev-server.ts
|
|
12283
12710
|
var import_child_process15 = require("child_process");
|
|
12284
12711
|
var import_core14 = __toESM(require_dist());
|
|
12285
|
-
var
|
|
12286
|
-
var
|
|
12712
|
+
var fs30 = __toESM(require("fs"));
|
|
12713
|
+
var path30 = __toESM(require("path"));
|
|
12287
12714
|
var MAX_RESTART_ATTEMPTS = 5;
|
|
12288
12715
|
var INITIAL_RESTART_DELAY_MS = 2e3;
|
|
12289
12716
|
var MAX_RESTART_DELAY_MS = 3e4;
|
|
@@ -12291,26 +12718,26 @@ var MAX_LOG_SIZE_BYTES2 = 5 * 1024 * 1024;
|
|
|
12291
12718
|
var NODE_MEMORY_LIMIT_MB = 2048;
|
|
12292
12719
|
var activeServers = /* @__PURE__ */ new Map();
|
|
12293
12720
|
function getLogsDir() {
|
|
12294
|
-
const logsDir =
|
|
12295
|
-
if (!
|
|
12296
|
-
|
|
12721
|
+
const logsDir = path30.join((0, import_core14.getConfigDir)(), "logs");
|
|
12722
|
+
if (!fs30.existsSync(logsDir)) {
|
|
12723
|
+
fs30.mkdirSync(logsDir, { recursive: true });
|
|
12297
12724
|
}
|
|
12298
12725
|
return logsDir;
|
|
12299
12726
|
}
|
|
12300
12727
|
function getLogFilePath(moduleUid) {
|
|
12301
|
-
return
|
|
12728
|
+
return path30.join(getLogsDir(), `dev-${moduleUid}.log`);
|
|
12302
12729
|
}
|
|
12303
12730
|
function rotateLogIfNeeded(logPath) {
|
|
12304
12731
|
try {
|
|
12305
|
-
if (
|
|
12306
|
-
const stats =
|
|
12732
|
+
if (fs30.existsSync(logPath)) {
|
|
12733
|
+
const stats = fs30.statSync(logPath);
|
|
12307
12734
|
if (stats.size > MAX_LOG_SIZE_BYTES2) {
|
|
12308
12735
|
const backupPath = `${logPath}.1`;
|
|
12309
|
-
if (
|
|
12310
|
-
|
|
12736
|
+
if (fs30.existsSync(backupPath)) {
|
|
12737
|
+
fs30.unlinkSync(backupPath);
|
|
12311
12738
|
}
|
|
12312
|
-
|
|
12313
|
-
console.log(`[DevServer] EP932: Rotated log file for ${
|
|
12739
|
+
fs30.renameSync(logPath, backupPath);
|
|
12740
|
+
console.log(`[DevServer] EP932: Rotated log file for ${path30.basename(logPath)}`);
|
|
12314
12741
|
}
|
|
12315
12742
|
}
|
|
12316
12743
|
} catch (error) {
|
|
@@ -12323,7 +12750,7 @@ function writeToLog(logPath, line, isError = false) {
|
|
|
12323
12750
|
const prefix = isError ? "ERR" : "OUT";
|
|
12324
12751
|
const logLine = `[${timestamp}] [${prefix}] ${line}
|
|
12325
12752
|
`;
|
|
12326
|
-
|
|
12753
|
+
fs30.appendFileSync(logPath, logLine);
|
|
12327
12754
|
} catch {
|
|
12328
12755
|
}
|
|
12329
12756
|
}
|
|
@@ -12343,7 +12770,7 @@ async function killProcessOnPort(port) {
|
|
|
12343
12770
|
} catch {
|
|
12344
12771
|
}
|
|
12345
12772
|
}
|
|
12346
|
-
await new Promise((
|
|
12773
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
12347
12774
|
for (const pid of pids) {
|
|
12348
12775
|
try {
|
|
12349
12776
|
(0, import_child_process15.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
|
|
@@ -12352,7 +12779,7 @@ async function killProcessOnPort(port) {
|
|
|
12352
12779
|
} catch {
|
|
12353
12780
|
}
|
|
12354
12781
|
}
|
|
12355
|
-
await new Promise((
|
|
12782
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
12356
12783
|
const stillInUse = await isPortInUse(port);
|
|
12357
12784
|
if (stillInUse) {
|
|
12358
12785
|
console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`);
|
|
@@ -12372,7 +12799,7 @@ async function waitForPort(port, timeoutMs = 3e4) {
|
|
|
12372
12799
|
if (await isPortInUse(port)) {
|
|
12373
12800
|
return true;
|
|
12374
12801
|
}
|
|
12375
|
-
await new Promise((
|
|
12802
|
+
await new Promise((resolve6) => setTimeout(resolve6, checkInterval));
|
|
12376
12803
|
}
|
|
12377
12804
|
return false;
|
|
12378
12805
|
}
|
|
@@ -12444,7 +12871,7 @@ async function handleProcessExit(moduleUid, code, signal) {
|
|
|
12444
12871
|
const delay = calculateRestartDelay(serverInfo.restartCount);
|
|
12445
12872
|
console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`);
|
|
12446
12873
|
writeToLog(serverInfo.logFile || "", `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false);
|
|
12447
|
-
await new Promise((
|
|
12874
|
+
await new Promise((resolve6) => setTimeout(resolve6, delay));
|
|
12448
12875
|
if (!activeServers.has(moduleUid)) {
|
|
12449
12876
|
console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`);
|
|
12450
12877
|
return;
|
|
@@ -12502,8 +12929,8 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
|
|
|
12502
12929
|
});
|
|
12503
12930
|
injectedEnvVars = result.envVars;
|
|
12504
12931
|
console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? "cache" : "server"})`);
|
|
12505
|
-
const envFilePath =
|
|
12506
|
-
if (!
|
|
12932
|
+
const envFilePath = path30.join(projectPath, ".env");
|
|
12933
|
+
if (!fs30.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
|
|
12507
12934
|
console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
|
|
12508
12935
|
writeEnvFile(projectPath, injectedEnvVars);
|
|
12509
12936
|
}
|
|
@@ -12569,7 +12996,7 @@ async function stopDevServer(moduleUid) {
|
|
|
12569
12996
|
writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false);
|
|
12570
12997
|
}
|
|
12571
12998
|
serverInfo.process.kill("SIGTERM");
|
|
12572
|
-
await new Promise((
|
|
12999
|
+
await new Promise((resolve6) => setTimeout(resolve6, 2e3));
|
|
12573
13000
|
if (!serverInfo.process.killed) {
|
|
12574
13001
|
serverInfo.process.kill("SIGKILL");
|
|
12575
13002
|
}
|
|
@@ -12587,7 +13014,7 @@ async function restartDevServer(moduleUid) {
|
|
|
12587
13014
|
writeToLog(logFile, `Manual restart requested`, false);
|
|
12588
13015
|
}
|
|
12589
13016
|
await stopDevServer(moduleUid);
|
|
12590
|
-
await new Promise((
|
|
13017
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
12591
13018
|
if (await isPortInUse(port)) {
|
|
12592
13019
|
await killProcessOnPort(port);
|
|
12593
13020
|
}
|
|
@@ -12641,9 +13068,9 @@ var IPCRouter = class {
|
|
|
12641
13068
|
// EP726: Kept for backward compatibility
|
|
12642
13069
|
machineName: this.host.deviceName,
|
|
12643
13070
|
// EP1186: User-friendly machine name from server
|
|
12644
|
-
hostname:
|
|
12645
|
-
platform:
|
|
12646
|
-
arch:
|
|
13071
|
+
hostname: os14.hostname(),
|
|
13072
|
+
platform: os14.platform(),
|
|
13073
|
+
arch: os14.arch(),
|
|
12647
13074
|
projects
|
|
12648
13075
|
};
|
|
12649
13076
|
});
|
|
@@ -12669,7 +13096,7 @@ var IPCRouter = class {
|
|
|
12669
13096
|
if (attempt < MAX_RETRIES) {
|
|
12670
13097
|
const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
|
|
12671
13098
|
console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
|
|
12672
|
-
await new Promise((
|
|
13099
|
+
await new Promise((resolve6) => setTimeout(resolve6, delay));
|
|
12673
13100
|
await this.host.disconnectProject(projectPath);
|
|
12674
13101
|
}
|
|
12675
13102
|
}
|
|
@@ -12807,6 +13234,7 @@ var IPCRouter = class {
|
|
|
12807
13234
|
await tunnelManager.stopTunnel(moduleUid);
|
|
12808
13235
|
await stopDevServer(moduleUid);
|
|
12809
13236
|
releasePort(moduleUid);
|
|
13237
|
+
releaseWsPort(moduleUid);
|
|
12810
13238
|
await clearTunnelUrl(moduleUid);
|
|
12811
13239
|
this.host.deleteTunnelHealthFailure(moduleUid);
|
|
12812
13240
|
console.log(`[Daemon] EP823: Tunnel stopped for ${moduleUid}`);
|
|
@@ -12844,8 +13272,8 @@ var IPCRouter = class {
|
|
|
12844
13272
|
};
|
|
12845
13273
|
|
|
12846
13274
|
// src/daemon/update-manager.ts
|
|
12847
|
-
var
|
|
12848
|
-
var
|
|
13275
|
+
var fs31 = __toESM(require("fs"));
|
|
13276
|
+
var path31 = __toESM(require("path"));
|
|
12849
13277
|
var import_child_process17 = require("child_process");
|
|
12850
13278
|
var import_core17 = __toESM(require_dist());
|
|
12851
13279
|
var semver2 = __toESM(require("semver"));
|
|
@@ -13103,8 +13531,8 @@ var UpdateManager = class _UpdateManager {
|
|
|
13103
13531
|
console.log(`[Daemon] EP1324: Update to ${targetVersion} installed, restarting daemon...`);
|
|
13104
13532
|
await this.host.shutdown();
|
|
13105
13533
|
const configDir = (0, import_core17.getConfigDir)();
|
|
13106
|
-
const logPath =
|
|
13107
|
-
const logFd =
|
|
13534
|
+
const logPath = path31.join(configDir, "daemon.log");
|
|
13535
|
+
const logFd = fs31.openSync(logPath, "a");
|
|
13108
13536
|
const child = (0, import_child_process17.spawn)("node", [this.daemonEntryFile], {
|
|
13109
13537
|
detached: true,
|
|
13110
13538
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -13112,7 +13540,7 @@ var UpdateManager = class _UpdateManager {
|
|
|
13112
13540
|
});
|
|
13113
13541
|
if (!child.pid) {
|
|
13114
13542
|
try {
|
|
13115
|
-
|
|
13543
|
+
fs31.closeSync(logFd);
|
|
13116
13544
|
} catch {
|
|
13117
13545
|
}
|
|
13118
13546
|
this.recordUpdateFailure(targetVersion, "Failed to spawn replacement daemon (missing pid)");
|
|
@@ -13120,7 +13548,7 @@ var UpdateManager = class _UpdateManager {
|
|
|
13120
13548
|
}
|
|
13121
13549
|
child.unref();
|
|
13122
13550
|
const pidPath = getPidFilePath();
|
|
13123
|
-
|
|
13551
|
+
fs31.writeFileSync(pidPath, child.pid.toString(), "utf-8");
|
|
13124
13552
|
console.log(`[Daemon] EP1324: New daemon spawned (PID: ${child.pid}), exiting old process`);
|
|
13125
13553
|
process.exit(0);
|
|
13126
13554
|
} catch (error) {
|
|
@@ -13171,8 +13599,8 @@ var UpdateManager = class _UpdateManager {
|
|
|
13171
13599
|
};
|
|
13172
13600
|
|
|
13173
13601
|
// src/daemon/health-orchestrator.ts
|
|
13174
|
-
var
|
|
13175
|
-
var
|
|
13602
|
+
var fs32 = __toESM(require("fs"));
|
|
13603
|
+
var path32 = __toESM(require("path"));
|
|
13176
13604
|
var import_core18 = __toESM(require_dist());
|
|
13177
13605
|
var HealthOrchestrator = class _HealthOrchestrator {
|
|
13178
13606
|
constructor(host) {
|
|
@@ -13376,10 +13804,15 @@ var HealthOrchestrator = class _HealthOrchestrator {
|
|
|
13376
13804
|
console.log(`[Daemon] EP1042: Killed ${cleanup.cleaned} orphaned dev server(s)`);
|
|
13377
13805
|
}
|
|
13378
13806
|
const registryPorts = /* @__PURE__ */ new Map();
|
|
13807
|
+
const registryWsPorts = /* @__PURE__ */ new Map();
|
|
13379
13808
|
for (const entry of registry.getAll()) {
|
|
13380
13809
|
registryPorts.set(entry.moduleUid, entry.port);
|
|
13810
|
+
if (entry.wsPort) {
|
|
13811
|
+
registryWsPorts.set(entry.moduleUid, entry.wsPort);
|
|
13812
|
+
}
|
|
13381
13813
|
}
|
|
13382
13814
|
reconcileWithRegistry(registryPorts);
|
|
13815
|
+
reconcileWsPortsWithRegistry(registryWsPorts);
|
|
13383
13816
|
console.log("[Daemon] EP1042: Orphaned dev server cleanup complete");
|
|
13384
13817
|
} catch (error) {
|
|
13385
13818
|
console.error("[Daemon] EP1042: Failed to clean up orphaned dev servers:", error);
|
|
@@ -13421,26 +13854,26 @@ var HealthOrchestrator = class _HealthOrchestrator {
|
|
|
13421
13854
|
checkAndRotateLog() {
|
|
13422
13855
|
try {
|
|
13423
13856
|
const configDir = (0, import_core18.getConfigDir)();
|
|
13424
|
-
const logPath =
|
|
13425
|
-
if (!
|
|
13426
|
-
const stats =
|
|
13857
|
+
const logPath = path32.join(configDir, "daemon.log");
|
|
13858
|
+
if (!fs32.existsSync(logPath)) return;
|
|
13859
|
+
const stats = fs32.statSync(logPath);
|
|
13427
13860
|
if (stats.size < _HealthOrchestrator.MAX_LOG_SIZE_BYTES) return;
|
|
13428
13861
|
console.log(`[Daemon] EP1351: daemon.log is ${Math.round(stats.size / 1024 / 1024)}MB, rotating...`);
|
|
13429
13862
|
for (let i = _HealthOrchestrator.MAX_LOG_FILES - 1; i >= 1; i--) {
|
|
13430
13863
|
const src = `${logPath}.${i}`;
|
|
13431
13864
|
const dst = `${logPath}.${i + 1}`;
|
|
13432
|
-
if (
|
|
13865
|
+
if (fs32.existsSync(src)) {
|
|
13433
13866
|
try {
|
|
13434
|
-
|
|
13867
|
+
fs32.renameSync(src, dst);
|
|
13435
13868
|
} catch {
|
|
13436
13869
|
}
|
|
13437
13870
|
}
|
|
13438
13871
|
}
|
|
13439
13872
|
try {
|
|
13440
|
-
|
|
13873
|
+
fs32.copyFileSync(logPath, `${logPath}.1`);
|
|
13441
13874
|
} catch {
|
|
13442
13875
|
}
|
|
13443
|
-
|
|
13876
|
+
fs32.truncateSync(logPath, 0);
|
|
13444
13877
|
console.log("[Daemon] EP1351: Log rotated successfully");
|
|
13445
13878
|
} catch (error) {
|
|
13446
13879
|
console.warn("[Daemon] EP1351: Log rotation failed:", error instanceof Error ? error.message : error);
|
|
@@ -13489,9 +13922,9 @@ var HealthOrchestrator = class _HealthOrchestrator {
|
|
|
13489
13922
|
if (Number.isFinite(lastAccessedMs)) {
|
|
13490
13923
|
referenceTimestampMs = lastAccessedMs;
|
|
13491
13924
|
fallbackAgeSource.push(`${w.moduleUid}(lastAccessed)`);
|
|
13492
|
-
} else if (w.worktreePath &&
|
|
13925
|
+
} else if (w.worktreePath && fs32.existsSync(w.worktreePath)) {
|
|
13493
13926
|
try {
|
|
13494
|
-
const stats =
|
|
13927
|
+
const stats = fs32.statSync(w.worktreePath);
|
|
13495
13928
|
referenceTimestampMs = stats.mtimeMs;
|
|
13496
13929
|
fallbackAgeSource.push(`${w.moduleUid}(fs.mtime)`);
|
|
13497
13930
|
} catch {
|
|
@@ -13857,7 +14290,7 @@ var ConnectionManager = class {
|
|
|
13857
14290
|
* handlers are removed deterministically on every exit path.
|
|
13858
14291
|
*/
|
|
13859
14292
|
async waitForAuthentication(client, timeoutMs = 3e4) {
|
|
13860
|
-
await new Promise((
|
|
14293
|
+
await new Promise((resolve6, reject) => {
|
|
13861
14294
|
let settled = false;
|
|
13862
14295
|
const cleanup = () => {
|
|
13863
14296
|
clearTimeout(timeout);
|
|
@@ -13874,7 +14307,7 @@ var ConnectionManager = class {
|
|
|
13874
14307
|
if (settled) return;
|
|
13875
14308
|
settled = true;
|
|
13876
14309
|
cleanup();
|
|
13877
|
-
|
|
14310
|
+
resolve6();
|
|
13878
14311
|
};
|
|
13879
14312
|
const errorHandler = (message) => {
|
|
13880
14313
|
if (settled) return;
|
|
@@ -13937,7 +14370,7 @@ function resolveDaemonWsEndpoint(config, env = process.env) {
|
|
|
13937
14370
|
|
|
13938
14371
|
// src/daemon/project-message-router.ts
|
|
13939
14372
|
var import_core19 = __toESM(require_dist());
|
|
13940
|
-
var
|
|
14373
|
+
var path33 = __toESM(require("path"));
|
|
13941
14374
|
var ProjectMessageRouter = class {
|
|
13942
14375
|
constructor(host) {
|
|
13943
14376
|
this.host = host;
|
|
@@ -13954,7 +14387,7 @@ var ProjectMessageRouter = class {
|
|
|
13954
14387
|
client.updateActivity();
|
|
13955
14388
|
try {
|
|
13956
14389
|
const gitCmd = message.command;
|
|
13957
|
-
const bareRepoPath =
|
|
14390
|
+
const bareRepoPath = path33.join(projectPath, ".bare");
|
|
13958
14391
|
const cwd = gitCmd.worktreePath || bareRepoPath;
|
|
13959
14392
|
if (gitCmd.worktreePath) {
|
|
13960
14393
|
console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
|
|
@@ -14294,32 +14727,32 @@ async function fetchEnvVars2(projectId) {
|
|
|
14294
14727
|
}
|
|
14295
14728
|
|
|
14296
14729
|
// src/daemon/project-git-config.ts
|
|
14297
|
-
var
|
|
14298
|
-
var
|
|
14730
|
+
var fs33 = __toESM(require("fs"));
|
|
14731
|
+
var path34 = __toESM(require("path"));
|
|
14299
14732
|
var import_core21 = __toESM(require_dist());
|
|
14300
14733
|
function getGitDirs(projectPath) {
|
|
14301
|
-
const bareDir =
|
|
14302
|
-
const gitPath =
|
|
14303
|
-
if (
|
|
14734
|
+
const bareDir = path34.join(projectPath, ".bare");
|
|
14735
|
+
const gitPath = path34.join(projectPath, ".git");
|
|
14736
|
+
if (fs33.existsSync(bareDir) && fs33.statSync(bareDir).isDirectory()) {
|
|
14304
14737
|
return { gitDir: bareDir, workDir: projectPath };
|
|
14305
14738
|
}
|
|
14306
|
-
if (
|
|
14739
|
+
if (fs33.existsSync(gitPath) && fs33.statSync(gitPath).isDirectory()) {
|
|
14307
14740
|
return { gitDir: null, workDir: projectPath };
|
|
14308
14741
|
}
|
|
14309
|
-
if (
|
|
14742
|
+
if (fs33.existsSync(gitPath) && fs33.statSync(gitPath).isFile()) {
|
|
14310
14743
|
return { gitDir: null, workDir: projectPath };
|
|
14311
14744
|
}
|
|
14312
|
-
const entries =
|
|
14745
|
+
const entries = fs33.readdirSync(projectPath, { withFileTypes: true });
|
|
14313
14746
|
for (const entry of entries) {
|
|
14314
14747
|
if (entry.isDirectory() && entry.name.startsWith("EP")) {
|
|
14315
|
-
const worktreePath =
|
|
14316
|
-
const worktreeGit =
|
|
14317
|
-
if (
|
|
14748
|
+
const worktreePath = path34.join(projectPath, entry.name);
|
|
14749
|
+
const worktreeGit = path34.join(worktreePath, ".git");
|
|
14750
|
+
if (fs33.existsSync(worktreeGit)) {
|
|
14318
14751
|
return { gitDir: null, workDir: worktreePath };
|
|
14319
14752
|
}
|
|
14320
14753
|
}
|
|
14321
14754
|
}
|
|
14322
|
-
if (
|
|
14755
|
+
if (fs33.existsSync(bareDir)) {
|
|
14323
14756
|
return { gitDir: bareDir, workDir: projectPath };
|
|
14324
14757
|
}
|
|
14325
14758
|
return { gitDir: null, workDir: projectPath };
|
|
@@ -14364,24 +14797,24 @@ async function configureGitUser(projectPath, userId, workspaceId, machineId, pro
|
|
|
14364
14797
|
async function installGitHooks(projectPath) {
|
|
14365
14798
|
const hooks = ["post-checkout", "pre-commit", "post-commit"];
|
|
14366
14799
|
let hooksDir;
|
|
14367
|
-
const bareHooksDir =
|
|
14368
|
-
const gitHooksDir =
|
|
14369
|
-
if (
|
|
14800
|
+
const bareHooksDir = path34.join(projectPath, ".bare", "hooks");
|
|
14801
|
+
const gitHooksDir = path34.join(projectPath, ".git", "hooks");
|
|
14802
|
+
if (fs33.existsSync(bareHooksDir)) {
|
|
14370
14803
|
hooksDir = bareHooksDir;
|
|
14371
|
-
} else if (
|
|
14804
|
+
} else if (fs33.existsSync(gitHooksDir) && fs33.statSync(path34.join(projectPath, ".git")).isDirectory()) {
|
|
14372
14805
|
hooksDir = gitHooksDir;
|
|
14373
14806
|
} else {
|
|
14374
|
-
const parentBareHooks =
|
|
14375
|
-
if (
|
|
14807
|
+
const parentBareHooks = path34.join(projectPath, "..", ".bare", "hooks");
|
|
14808
|
+
if (fs33.existsSync(parentBareHooks)) {
|
|
14376
14809
|
hooksDir = parentBareHooks;
|
|
14377
14810
|
} else {
|
|
14378
14811
|
console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
|
|
14379
14812
|
return;
|
|
14380
14813
|
}
|
|
14381
14814
|
}
|
|
14382
|
-
if (!
|
|
14815
|
+
if (!fs33.existsSync(hooksDir)) {
|
|
14383
14816
|
try {
|
|
14384
|
-
|
|
14817
|
+
fs33.mkdirSync(hooksDir, { recursive: true });
|
|
14385
14818
|
} catch {
|
|
14386
14819
|
console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
|
|
14387
14820
|
return;
|
|
@@ -14389,20 +14822,20 @@ async function installGitHooks(projectPath) {
|
|
|
14389
14822
|
}
|
|
14390
14823
|
for (const hookName of hooks) {
|
|
14391
14824
|
try {
|
|
14392
|
-
const hookPath =
|
|
14393
|
-
const bundledHookPath =
|
|
14394
|
-
if (!
|
|
14825
|
+
const hookPath = path34.join(hooksDir, hookName);
|
|
14826
|
+
const bundledHookPath = path34.join(__dirname, "..", "hooks", hookName);
|
|
14827
|
+
if (!fs33.existsSync(bundledHookPath)) {
|
|
14395
14828
|
console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
|
|
14396
14829
|
continue;
|
|
14397
14830
|
}
|
|
14398
|
-
const hookContent =
|
|
14399
|
-
if (
|
|
14400
|
-
const existingContent =
|
|
14831
|
+
const hookContent = fs33.readFileSync(bundledHookPath, "utf-8");
|
|
14832
|
+
if (fs33.existsSync(hookPath)) {
|
|
14833
|
+
const existingContent = fs33.readFileSync(hookPath, "utf-8");
|
|
14401
14834
|
if (existingContent === hookContent) {
|
|
14402
14835
|
continue;
|
|
14403
14836
|
}
|
|
14404
14837
|
}
|
|
14405
|
-
|
|
14838
|
+
fs33.writeFileSync(hookPath, hookContent, { mode: 493 });
|
|
14406
14839
|
console.log(`[Daemon] Installed git hook: ${hookName}`);
|
|
14407
14840
|
} catch (error) {
|
|
14408
14841
|
console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
|
|
@@ -14545,9 +14978,9 @@ var Daemon = class _Daemon {
|
|
|
14545
14978
|
this.healthServer = http2.createServer((req, res) => {
|
|
14546
14979
|
if (req.url === "/health" || req.url === "/") {
|
|
14547
14980
|
const isConnected = this.connectionManager.liveConnectionCount() > 0;
|
|
14548
|
-
const projects = Array.from(this.connectionManager.entries()).map(([
|
|
14549
|
-
path:
|
|
14550
|
-
connected: this.connectionManager.hasLiveConnection(
|
|
14981
|
+
const projects = Array.from(this.connectionManager.entries()).map(([path35, conn]) => ({
|
|
14982
|
+
path: path35,
|
|
14983
|
+
connected: this.connectionManager.hasLiveConnection(path35)
|
|
14551
14984
|
}));
|
|
14552
14985
|
const status = {
|
|
14553
14986
|
status: isConnected ? "healthy" : "degraded",
|
|
@@ -14620,8 +15053,8 @@ var Daemon = class _Daemon {
|
|
|
14620
15053
|
await this.shutdown();
|
|
14621
15054
|
try {
|
|
14622
15055
|
const pidPath = getPidFilePath();
|
|
14623
|
-
if (
|
|
14624
|
-
|
|
15056
|
+
if (fs34.existsSync(pidPath)) {
|
|
15057
|
+
fs34.unlinkSync(pidPath);
|
|
14625
15058
|
console.log("[Daemon] PID file cleaned up (watchdog)");
|
|
14626
15059
|
}
|
|
14627
15060
|
} catch (error) {
|
|
@@ -14684,8 +15117,8 @@ var Daemon = class _Daemon {
|
|
|
14684
15117
|
}
|
|
14685
15118
|
}
|
|
14686
15119
|
let releaseLock;
|
|
14687
|
-
const lockPromise = new Promise((
|
|
14688
|
-
releaseLock =
|
|
15120
|
+
const lockPromise = new Promise((resolve6) => {
|
|
15121
|
+
releaseLock = resolve6;
|
|
14689
15122
|
});
|
|
14690
15123
|
this.tunnelOperationLocks.set(moduleUid, lockPromise);
|
|
14691
15124
|
try {
|
|
@@ -14712,7 +15145,7 @@ var Daemon = class _Daemon {
|
|
|
14712
15145
|
const maxWait = 35e3;
|
|
14713
15146
|
const startTime = Date.now();
|
|
14714
15147
|
while (this.connectionManager.hasPendingConnection(projectPath) && Date.now() - startTime < maxWait) {
|
|
14715
|
-
await new Promise((
|
|
15148
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
14716
15149
|
}
|
|
14717
15150
|
if (this.connectionManager.hasLiveConnection(projectPath)) {
|
|
14718
15151
|
console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
|
|
@@ -15011,8 +15444,8 @@ var Daemon = class _Daemon {
|
|
|
15011
15444
|
};
|
|
15012
15445
|
} else {
|
|
15013
15446
|
const manager = new WorktreeManager(projectRootPath);
|
|
15014
|
-
const
|
|
15015
|
-
if (!
|
|
15447
|
+
const initialized3 = await manager.initialize();
|
|
15448
|
+
if (!initialized3) {
|
|
15016
15449
|
console.warn(`[Daemon] EP1035: Failed to initialize WorktreeManager for ${projectRootPath}`);
|
|
15017
15450
|
result = {
|
|
15018
15451
|
success: false,
|
|
@@ -15210,8 +15643,8 @@ var Daemon = class _Daemon {
|
|
|
15210
15643
|
let daemonPid;
|
|
15211
15644
|
try {
|
|
15212
15645
|
const pidPath = getPidFilePath();
|
|
15213
|
-
if (
|
|
15214
|
-
const pidStr =
|
|
15646
|
+
if (fs34.existsSync(pidPath)) {
|
|
15647
|
+
const pidStr = fs34.readFileSync(pidPath, "utf-8").trim();
|
|
15215
15648
|
daemonPid = parseInt(pidStr, 10);
|
|
15216
15649
|
}
|
|
15217
15650
|
} catch (pidError) {
|
|
@@ -15222,9 +15655,9 @@ var Daemon = class _Daemon {
|
|
|
15222
15655
|
const containerId = process.env.EPISODA_CONTAINER_ID;
|
|
15223
15656
|
const capabilities = ["worktree_create_v1"];
|
|
15224
15657
|
await client.connect(wsEndpoint.wsUrl, config.access_token, this.machineId, {
|
|
15225
|
-
hostname:
|
|
15226
|
-
osPlatform:
|
|
15227
|
-
osArch:
|
|
15658
|
+
hostname: os15.hostname(),
|
|
15659
|
+
osPlatform: os15.platform(),
|
|
15660
|
+
osArch: os15.arch(),
|
|
15228
15661
|
daemonPid,
|
|
15229
15662
|
cliVersion: this.cliRuntimeVersion,
|
|
15230
15663
|
cliPackageName: packageJson.name,
|
|
@@ -15763,6 +16196,7 @@ var Daemon = class _Daemon {
|
|
|
15763
16196
|
console.error("[Daemon] Failed to stop tunnels:", error);
|
|
15764
16197
|
}
|
|
15765
16198
|
clearAllPorts();
|
|
16199
|
+
clearAllWsPorts();
|
|
15766
16200
|
if (this.disconnectWatchdogTimer) {
|
|
15767
16201
|
clearInterval(this.disconnectWatchdogTimer);
|
|
15768
16202
|
this.disconnectWatchdogTimer = null;
|
|
@@ -15785,8 +16219,8 @@ var Daemon = class _Daemon {
|
|
|
15785
16219
|
await this.shutdown();
|
|
15786
16220
|
try {
|
|
15787
16221
|
const pidPath = getPidFilePath();
|
|
15788
|
-
if (
|
|
15789
|
-
|
|
16222
|
+
if (fs34.existsSync(pidPath)) {
|
|
16223
|
+
fs34.unlinkSync(pidPath);
|
|
15790
16224
|
console.log("[Daemon] PID file cleaned up");
|
|
15791
16225
|
}
|
|
15792
16226
|
} catch (error) {
|