episoda 0.2.51 → 0.2.53
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 +273 -200
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +18 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1563,15 +1563,15 @@ var require_git_executor = __commonJS({
|
|
|
1563
1563
|
try {
|
|
1564
1564
|
const { stdout: gitDir } = await execAsync2("git rev-parse --git-dir", { cwd, timeout: 5e3 });
|
|
1565
1565
|
const gitDirPath = gitDir.trim();
|
|
1566
|
-
const
|
|
1566
|
+
const fs17 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1567
1567
|
const rebaseMergePath = `${gitDirPath}/rebase-merge`;
|
|
1568
1568
|
const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
|
|
1569
1569
|
try {
|
|
1570
|
-
await
|
|
1570
|
+
await fs17.access(rebaseMergePath);
|
|
1571
1571
|
inRebase = true;
|
|
1572
1572
|
} catch {
|
|
1573
1573
|
try {
|
|
1574
|
-
await
|
|
1574
|
+
await fs17.access(rebaseApplyPath);
|
|
1575
1575
|
inRebase = true;
|
|
1576
1576
|
} catch {
|
|
1577
1577
|
inRebase = false;
|
|
@@ -1625,9 +1625,9 @@ var require_git_executor = __commonJS({
|
|
|
1625
1625
|
error: validation.error || "UNKNOWN_ERROR"
|
|
1626
1626
|
};
|
|
1627
1627
|
}
|
|
1628
|
-
const
|
|
1628
|
+
const fs17 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1629
1629
|
try {
|
|
1630
|
-
await
|
|
1630
|
+
await fs17.access(command.path);
|
|
1631
1631
|
return {
|
|
1632
1632
|
success: false,
|
|
1633
1633
|
error: "WORKTREE_EXISTS",
|
|
@@ -1681,9 +1681,9 @@ var require_git_executor = __commonJS({
|
|
|
1681
1681
|
*/
|
|
1682
1682
|
async executeWorktreeRemove(command, cwd, options) {
|
|
1683
1683
|
try {
|
|
1684
|
-
const
|
|
1684
|
+
const fs17 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1685
1685
|
try {
|
|
1686
|
-
await
|
|
1686
|
+
await fs17.access(command.path);
|
|
1687
1687
|
} catch {
|
|
1688
1688
|
return {
|
|
1689
1689
|
success: false,
|
|
@@ -1718,7 +1718,7 @@ var require_git_executor = __commonJS({
|
|
|
1718
1718
|
const result = await this.runGitCommand(args, cwd, options);
|
|
1719
1719
|
if (result.success) {
|
|
1720
1720
|
try {
|
|
1721
|
-
await
|
|
1721
|
+
await fs17.rm(command.path, { recursive: true, force: true });
|
|
1722
1722
|
} catch {
|
|
1723
1723
|
}
|
|
1724
1724
|
return {
|
|
@@ -1852,10 +1852,10 @@ var require_git_executor = __commonJS({
|
|
|
1852
1852
|
*/
|
|
1853
1853
|
async executeCloneBare(command, options) {
|
|
1854
1854
|
try {
|
|
1855
|
-
const
|
|
1856
|
-
const
|
|
1855
|
+
const fs17 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1856
|
+
const path18 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1857
1857
|
try {
|
|
1858
|
-
await
|
|
1858
|
+
await fs17.access(command.path);
|
|
1859
1859
|
return {
|
|
1860
1860
|
success: false,
|
|
1861
1861
|
error: "BRANCH_ALREADY_EXISTS",
|
|
@@ -1864,9 +1864,9 @@ var require_git_executor = __commonJS({
|
|
|
1864
1864
|
};
|
|
1865
1865
|
} catch {
|
|
1866
1866
|
}
|
|
1867
|
-
const parentDir =
|
|
1867
|
+
const parentDir = path18.dirname(command.path);
|
|
1868
1868
|
try {
|
|
1869
|
-
await
|
|
1869
|
+
await fs17.mkdir(parentDir, { recursive: true });
|
|
1870
1870
|
} catch {
|
|
1871
1871
|
}
|
|
1872
1872
|
const { stdout, stderr } = await execAsync2(
|
|
@@ -1914,22 +1914,22 @@ var require_git_executor = __commonJS({
|
|
|
1914
1914
|
*/
|
|
1915
1915
|
async executeProjectInfo(cwd, options) {
|
|
1916
1916
|
try {
|
|
1917
|
-
const
|
|
1918
|
-
const
|
|
1917
|
+
const fs17 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1918
|
+
const path18 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1919
1919
|
let currentPath = cwd;
|
|
1920
1920
|
let projectPath = cwd;
|
|
1921
1921
|
let bareRepoPath;
|
|
1922
1922
|
for (let i = 0; i < 10; i++) {
|
|
1923
|
-
const bareDir =
|
|
1924
|
-
const episodaDir =
|
|
1923
|
+
const bareDir = path18.join(currentPath, ".bare");
|
|
1924
|
+
const episodaDir = path18.join(currentPath, ".episoda");
|
|
1925
1925
|
try {
|
|
1926
|
-
await
|
|
1927
|
-
await
|
|
1926
|
+
await fs17.access(bareDir);
|
|
1927
|
+
await fs17.access(episodaDir);
|
|
1928
1928
|
projectPath = currentPath;
|
|
1929
1929
|
bareRepoPath = bareDir;
|
|
1930
1930
|
break;
|
|
1931
1931
|
} catch {
|
|
1932
|
-
const parentPath =
|
|
1932
|
+
const parentPath = path18.dirname(currentPath);
|
|
1933
1933
|
if (parentPath === currentPath) {
|
|
1934
1934
|
break;
|
|
1935
1935
|
}
|
|
@@ -2538,31 +2538,31 @@ var require_auth = __commonJS({
|
|
|
2538
2538
|
exports2.loadConfig = loadConfig7;
|
|
2539
2539
|
exports2.saveConfig = saveConfig2;
|
|
2540
2540
|
exports2.validateToken = validateToken;
|
|
2541
|
-
var
|
|
2542
|
-
var
|
|
2541
|
+
var fs17 = __importStar(require("fs"));
|
|
2542
|
+
var path18 = __importStar(require("path"));
|
|
2543
2543
|
var os7 = __importStar(require("os"));
|
|
2544
2544
|
var child_process_1 = require("child_process");
|
|
2545
2545
|
var DEFAULT_CONFIG_FILE = "config.json";
|
|
2546
2546
|
function getConfigDir8() {
|
|
2547
|
-
return process.env.EPISODA_CONFIG_DIR ||
|
|
2547
|
+
return process.env.EPISODA_CONFIG_DIR || path18.join(os7.homedir(), ".episoda");
|
|
2548
2548
|
}
|
|
2549
2549
|
function getConfigPath(configPath) {
|
|
2550
2550
|
if (configPath) {
|
|
2551
2551
|
return configPath;
|
|
2552
2552
|
}
|
|
2553
|
-
return
|
|
2553
|
+
return path18.join(getConfigDir8(), DEFAULT_CONFIG_FILE);
|
|
2554
2554
|
}
|
|
2555
2555
|
function ensureConfigDir(configPath) {
|
|
2556
|
-
const dir =
|
|
2557
|
-
const isNew = !
|
|
2556
|
+
const dir = path18.dirname(configPath);
|
|
2557
|
+
const isNew = !fs17.existsSync(dir);
|
|
2558
2558
|
if (isNew) {
|
|
2559
|
-
|
|
2559
|
+
fs17.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2560
2560
|
}
|
|
2561
2561
|
if (process.platform === "darwin") {
|
|
2562
|
-
const nosyncPath =
|
|
2563
|
-
if (isNew || !
|
|
2562
|
+
const nosyncPath = path18.join(dir, ".nosync");
|
|
2563
|
+
if (isNew || !fs17.existsSync(nosyncPath)) {
|
|
2564
2564
|
try {
|
|
2565
|
-
|
|
2565
|
+
fs17.writeFileSync(nosyncPath, "", { mode: 384 });
|
|
2566
2566
|
(0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
|
|
2567
2567
|
stdio: "ignore",
|
|
2568
2568
|
timeout: 5e3
|
|
@@ -2574,9 +2574,9 @@ var require_auth = __commonJS({
|
|
|
2574
2574
|
}
|
|
2575
2575
|
async function loadConfig7(configPath) {
|
|
2576
2576
|
const fullPath = getConfigPath(configPath);
|
|
2577
|
-
if (
|
|
2577
|
+
if (fs17.existsSync(fullPath)) {
|
|
2578
2578
|
try {
|
|
2579
|
-
const content =
|
|
2579
|
+
const content = fs17.readFileSync(fullPath, "utf8");
|
|
2580
2580
|
const config = JSON.parse(content);
|
|
2581
2581
|
return config;
|
|
2582
2582
|
} catch (error) {
|
|
@@ -2601,7 +2601,7 @@ var require_auth = __commonJS({
|
|
|
2601
2601
|
ensureConfigDir(fullPath);
|
|
2602
2602
|
try {
|
|
2603
2603
|
const content = JSON.stringify(config, null, 2);
|
|
2604
|
-
|
|
2604
|
+
fs17.writeFileSync(fullPath, content, { mode: 384 });
|
|
2605
2605
|
} catch (error) {
|
|
2606
2606
|
throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
|
|
2607
2607
|
}
|
|
@@ -2718,7 +2718,7 @@ var require_package = __commonJS({
|
|
|
2718
2718
|
"package.json"(exports2, module2) {
|
|
2719
2719
|
module2.exports = {
|
|
2720
2720
|
name: "episoda",
|
|
2721
|
-
version: "0.2.
|
|
2721
|
+
version: "0.2.53",
|
|
2722
2722
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2723
2723
|
main: "dist/index.js",
|
|
2724
2724
|
types: "dist/index.d.ts",
|
|
@@ -4020,12 +4020,14 @@ var os2 = __toESM(require("os"));
|
|
|
4020
4020
|
// src/tunnel/tunnel-api.ts
|
|
4021
4021
|
var import_core7 = __toESM(require_dist());
|
|
4022
4022
|
async function provisionNamedTunnel(moduleId, port = 3e3) {
|
|
4023
|
+
console.log(`[TunnelAPI] EP1038: provisionNamedTunnel called for moduleId ${moduleId} with port ${port}`);
|
|
4023
4024
|
const config = await (0, import_core7.loadConfig)();
|
|
4024
4025
|
if (!config?.access_token) {
|
|
4025
4026
|
return { success: false, error: "Not authenticated" };
|
|
4026
4027
|
}
|
|
4027
4028
|
try {
|
|
4028
4029
|
const apiUrl = config.api_url || "https://episoda.dev";
|
|
4030
|
+
console.log(`[TunnelAPI] EP1038: POSTing to ${apiUrl}/api/tunnels with port=${port}`);
|
|
4029
4031
|
const response = await fetch(`${apiUrl}/api/tunnels`, {
|
|
4030
4032
|
method: "POST",
|
|
4031
4033
|
headers: {
|
|
@@ -4055,6 +4057,7 @@ async function provisionNamedTunnel(moduleId, port = 3e3) {
|
|
|
4055
4057
|
}
|
|
4056
4058
|
}
|
|
4057
4059
|
async function provisionNamedTunnelByUid(moduleUid, port = 3e3) {
|
|
4060
|
+
console.log(`[TunnelAPI] EP1038: provisionNamedTunnelByUid called for ${moduleUid} with port ${port}`);
|
|
4058
4061
|
const config = await (0, import_core7.loadConfig)();
|
|
4059
4062
|
if (!config?.access_token) {
|
|
4060
4063
|
return { success: false, error: "Not authenticated" };
|
|
@@ -5857,6 +5860,48 @@ function getDevServerRunner() {
|
|
|
5857
5860
|
return instance2;
|
|
5858
5861
|
}
|
|
5859
5862
|
|
|
5863
|
+
// src/utils/port-allocator.ts
|
|
5864
|
+
var PORT_RANGE_START = 3100;
|
|
5865
|
+
var PORT_RANGE_END = 3199;
|
|
5866
|
+
var PORT_WARNING_THRESHOLD = 80;
|
|
5867
|
+
var portAssignments = /* @__PURE__ */ new Map();
|
|
5868
|
+
function allocatePort(moduleUid) {
|
|
5869
|
+
const existing = portAssignments.get(moduleUid);
|
|
5870
|
+
if (existing) {
|
|
5871
|
+
return existing;
|
|
5872
|
+
}
|
|
5873
|
+
const usedPorts = new Set(portAssignments.values());
|
|
5874
|
+
if (usedPorts.size >= PORT_WARNING_THRESHOLD) {
|
|
5875
|
+
console.warn(
|
|
5876
|
+
`[PortAllocator] Warning: ${usedPorts.size}/${PORT_RANGE_END - PORT_RANGE_START + 1} ports allocated`
|
|
5877
|
+
);
|
|
5878
|
+
}
|
|
5879
|
+
for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
|
|
5880
|
+
if (!usedPorts.has(port)) {
|
|
5881
|
+
portAssignments.set(moduleUid, port);
|
|
5882
|
+
console.log(`[PortAllocator] Assigned port ${port} to ${moduleUid}`);
|
|
5883
|
+
return port;
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
throw new Error(
|
|
5887
|
+
`No available ports in range ${PORT_RANGE_START}-${PORT_RANGE_END}. ${portAssignments.size} modules are using all available ports.`
|
|
5888
|
+
);
|
|
5889
|
+
}
|
|
5890
|
+
function releasePort(moduleUid) {
|
|
5891
|
+
const port = portAssignments.get(moduleUid);
|
|
5892
|
+
if (port) {
|
|
5893
|
+
portAssignments.delete(moduleUid);
|
|
5894
|
+
console.log(`[PortAllocator] Released port ${port} from ${moduleUid}`);
|
|
5895
|
+
}
|
|
5896
|
+
}
|
|
5897
|
+
function clearAllPorts() {
|
|
5898
|
+
const count = portAssignments.size;
|
|
5899
|
+
portAssignments.clear();
|
|
5900
|
+
if (count > 0) {
|
|
5901
|
+
console.log(`[PortAllocator] Cleared ${count} port assignments`);
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
|
|
5860
5905
|
// src/preview/preview-manager.ts
|
|
5861
5906
|
var DEFAULT_PORT = 3e3;
|
|
5862
5907
|
var PreviewManager = class extends import_events3.EventEmitter {
|
|
@@ -6069,6 +6114,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
6069
6114
|
} catch (error) {
|
|
6070
6115
|
console.warn(`[PreviewManager] Error clearing tunnel URL for ${moduleUid}:`, error);
|
|
6071
6116
|
}
|
|
6117
|
+
releasePort(moduleUid);
|
|
6072
6118
|
if (state) {
|
|
6073
6119
|
state.state = "stopped";
|
|
6074
6120
|
state.tunnelUrl = void 0;
|
|
@@ -6574,80 +6620,9 @@ function getDevServerStatus() {
|
|
|
6574
6620
|
}));
|
|
6575
6621
|
}
|
|
6576
6622
|
|
|
6577
|
-
// src/
|
|
6623
|
+
// src/daemon/worktree-manager.ts
|
|
6578
6624
|
var fs13 = __toESM(require("fs"));
|
|
6579
6625
|
var path14 = __toESM(require("path"));
|
|
6580
|
-
var DEFAULT_PORT2 = 3e3;
|
|
6581
|
-
function detectDevPort(projectPath) {
|
|
6582
|
-
const envPort = getPortFromEnv(projectPath);
|
|
6583
|
-
if (envPort) {
|
|
6584
|
-
console.log(`[PortDetect] Found PORT=${envPort} in .env`);
|
|
6585
|
-
return envPort;
|
|
6586
|
-
}
|
|
6587
|
-
const scriptPort = getPortFromPackageJson(projectPath);
|
|
6588
|
-
if (scriptPort) {
|
|
6589
|
-
console.log(`[PortDetect] Found port ${scriptPort} in package.json dev script`);
|
|
6590
|
-
return scriptPort;
|
|
6591
|
-
}
|
|
6592
|
-
console.log(`[PortDetect] Using default port ${DEFAULT_PORT2}`);
|
|
6593
|
-
return DEFAULT_PORT2;
|
|
6594
|
-
}
|
|
6595
|
-
function getPortFromEnv(projectPath) {
|
|
6596
|
-
const envPaths = [
|
|
6597
|
-
path14.join(projectPath, ".env"),
|
|
6598
|
-
path14.join(projectPath, ".env.local"),
|
|
6599
|
-
path14.join(projectPath, ".env.development"),
|
|
6600
|
-
path14.join(projectPath, ".env.development.local")
|
|
6601
|
-
];
|
|
6602
|
-
for (const envPath of envPaths) {
|
|
6603
|
-
try {
|
|
6604
|
-
if (!fs13.existsSync(envPath)) continue;
|
|
6605
|
-
const content = fs13.readFileSync(envPath, "utf-8");
|
|
6606
|
-
const lines = content.split("\n");
|
|
6607
|
-
for (const line of lines) {
|
|
6608
|
-
const match = line.match(/^\s*PORT\s*=\s*["']?(\d+)["']?\s*(?:#.*)?$/);
|
|
6609
|
-
if (match) {
|
|
6610
|
-
const port = parseInt(match[1], 10);
|
|
6611
|
-
if (port > 0 && port < 65536) {
|
|
6612
|
-
return port;
|
|
6613
|
-
}
|
|
6614
|
-
}
|
|
6615
|
-
}
|
|
6616
|
-
} catch {
|
|
6617
|
-
}
|
|
6618
|
-
}
|
|
6619
|
-
return null;
|
|
6620
|
-
}
|
|
6621
|
-
function getPortFromPackageJson(projectPath) {
|
|
6622
|
-
const packageJsonPath = path14.join(projectPath, "package.json");
|
|
6623
|
-
try {
|
|
6624
|
-
if (!fs13.existsSync(packageJsonPath)) return null;
|
|
6625
|
-
const content = fs13.readFileSync(packageJsonPath, "utf-8");
|
|
6626
|
-
const pkg = JSON.parse(content);
|
|
6627
|
-
const devScript = pkg.scripts?.dev;
|
|
6628
|
-
if (!devScript) return null;
|
|
6629
|
-
const portMatch = devScript.match(/(?:--port[=\s]|-p[=\s])(\d+)/);
|
|
6630
|
-
if (portMatch) {
|
|
6631
|
-
const port = parseInt(portMatch[1], 10);
|
|
6632
|
-
if (port > 0 && port < 65536) {
|
|
6633
|
-
return port;
|
|
6634
|
-
}
|
|
6635
|
-
}
|
|
6636
|
-
const envMatch = devScript.match(/PORT[=\s](\d+)/);
|
|
6637
|
-
if (envMatch) {
|
|
6638
|
-
const port = parseInt(envMatch[1], 10);
|
|
6639
|
-
if (port > 0 && port < 65536) {
|
|
6640
|
-
return port;
|
|
6641
|
-
}
|
|
6642
|
-
}
|
|
6643
|
-
} catch {
|
|
6644
|
-
}
|
|
6645
|
-
return null;
|
|
6646
|
-
}
|
|
6647
|
-
|
|
6648
|
-
// src/daemon/worktree-manager.ts
|
|
6649
|
-
var fs14 = __toESM(require("fs"));
|
|
6650
|
-
var path15 = __toESM(require("path"));
|
|
6651
6626
|
var import_core10 = __toESM(require_dist());
|
|
6652
6627
|
function validateModuleUid(moduleUid) {
|
|
6653
6628
|
if (!moduleUid || typeof moduleUid !== "string" || !moduleUid.trim()) {
|
|
@@ -6671,8 +6646,8 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
6671
6646
|
// ============================================================
|
|
6672
6647
|
this.lockPath = "";
|
|
6673
6648
|
this.projectRoot = projectRoot;
|
|
6674
|
-
this.bareRepoPath =
|
|
6675
|
-
this.configPath =
|
|
6649
|
+
this.bareRepoPath = path14.join(projectRoot, ".bare");
|
|
6650
|
+
this.configPath = path14.join(projectRoot, ".episoda", "config.json");
|
|
6676
6651
|
this.gitExecutor = new import_core10.GitExecutor();
|
|
6677
6652
|
}
|
|
6678
6653
|
/**
|
|
@@ -6681,10 +6656,10 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
6681
6656
|
* @returns true if valid project, false otherwise
|
|
6682
6657
|
*/
|
|
6683
6658
|
async initialize() {
|
|
6684
|
-
if (!
|
|
6659
|
+
if (!fs13.existsSync(this.bareRepoPath)) {
|
|
6685
6660
|
return false;
|
|
6686
6661
|
}
|
|
6687
|
-
if (!
|
|
6662
|
+
if (!fs13.existsSync(this.configPath)) {
|
|
6688
6663
|
return false;
|
|
6689
6664
|
}
|
|
6690
6665
|
try {
|
|
@@ -6731,8 +6706,8 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
6731
6706
|
*/
|
|
6732
6707
|
static async createProject(projectRoot, repoUrl, projectId, workspaceSlug, projectSlug) {
|
|
6733
6708
|
const manager = new _WorktreeManager(projectRoot);
|
|
6734
|
-
const episodaDir =
|
|
6735
|
-
|
|
6709
|
+
const episodaDir = path14.join(projectRoot, ".episoda");
|
|
6710
|
+
fs13.mkdirSync(episodaDir, { recursive: true });
|
|
6736
6711
|
const cloneResult = await manager.gitExecutor.execute({
|
|
6737
6712
|
action: "clone_bare",
|
|
6738
6713
|
url: repoUrl,
|
|
@@ -6763,7 +6738,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
6763
6738
|
error: `Invalid module UID: "${moduleUid}" - contains disallowed characters`
|
|
6764
6739
|
};
|
|
6765
6740
|
}
|
|
6766
|
-
const worktreePath =
|
|
6741
|
+
const worktreePath = path14.join(this.projectRoot, moduleUid);
|
|
6767
6742
|
const lockAcquired = await this.acquireLock();
|
|
6768
6743
|
if (!lockAcquired) {
|
|
6769
6744
|
return {
|
|
@@ -6863,14 +6838,21 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
6863
6838
|
error: result.output || "Failed to remove worktree"
|
|
6864
6839
|
};
|
|
6865
6840
|
}
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
config
|
|
6869
|
-
|
|
6841
|
+
let configUpdated = false;
|
|
6842
|
+
try {
|
|
6843
|
+
const config = this.readConfig();
|
|
6844
|
+
if (config) {
|
|
6845
|
+
config.worktrees = config.worktrees.filter((w) => w.moduleUid !== moduleUid);
|
|
6846
|
+
this.writeConfig(config);
|
|
6847
|
+
configUpdated = true;
|
|
6848
|
+
}
|
|
6849
|
+
} catch (configError) {
|
|
6850
|
+
console.warn(`[WorktreeManager] EP1035: Config update failed after removing ${moduleUid} (non-blocking):`, configError.message);
|
|
6870
6851
|
}
|
|
6871
6852
|
return {
|
|
6872
6853
|
success: true,
|
|
6873
|
-
worktreePath: existing.worktreePath
|
|
6854
|
+
worktreePath: existing.worktreePath,
|
|
6855
|
+
configUpdated
|
|
6874
6856
|
};
|
|
6875
6857
|
} finally {
|
|
6876
6858
|
this.releaseLock();
|
|
@@ -6945,7 +6927,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
6945
6927
|
let prunedCount = 0;
|
|
6946
6928
|
await this.updateConfigSafe((config) => {
|
|
6947
6929
|
const initialCount = config.worktrees.length;
|
|
6948
|
-
config.worktrees = config.worktrees.filter((w) =>
|
|
6930
|
+
config.worktrees = config.worktrees.filter((w) => fs13.existsSync(w.worktreePath));
|
|
6949
6931
|
prunedCount = initialCount - config.worktrees.length;
|
|
6950
6932
|
return config;
|
|
6951
6933
|
});
|
|
@@ -7026,16 +7008,16 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7026
7008
|
const retryInterval = 50;
|
|
7027
7009
|
while (Date.now() - startTime < timeoutMs) {
|
|
7028
7010
|
try {
|
|
7029
|
-
|
|
7011
|
+
fs13.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
7030
7012
|
return true;
|
|
7031
7013
|
} catch (err) {
|
|
7032
7014
|
if (err.code === "EEXIST") {
|
|
7033
7015
|
try {
|
|
7034
|
-
const stats =
|
|
7016
|
+
const stats = fs13.statSync(lockPath);
|
|
7035
7017
|
const lockAge = Date.now() - stats.mtimeMs;
|
|
7036
7018
|
if (lockAge > 3e4) {
|
|
7037
7019
|
try {
|
|
7038
|
-
const lockContent =
|
|
7020
|
+
const lockContent = fs13.readFileSync(lockPath, "utf-8").trim();
|
|
7039
7021
|
const lockPid = parseInt(lockContent, 10);
|
|
7040
7022
|
if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
|
|
7041
7023
|
await new Promise((resolve3) => setTimeout(resolve3, retryInterval));
|
|
@@ -7044,7 +7026,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7044
7026
|
} catch {
|
|
7045
7027
|
}
|
|
7046
7028
|
try {
|
|
7047
|
-
|
|
7029
|
+
fs13.unlinkSync(lockPath);
|
|
7048
7030
|
} catch {
|
|
7049
7031
|
}
|
|
7050
7032
|
continue;
|
|
@@ -7065,29 +7047,34 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7065
7047
|
*/
|
|
7066
7048
|
releaseLock() {
|
|
7067
7049
|
try {
|
|
7068
|
-
|
|
7050
|
+
fs13.unlinkSync(this.getLockPath());
|
|
7069
7051
|
} catch {
|
|
7070
7052
|
}
|
|
7071
7053
|
}
|
|
7072
7054
|
readConfig() {
|
|
7073
7055
|
try {
|
|
7074
|
-
if (!
|
|
7056
|
+
if (!fs13.existsSync(this.configPath)) {
|
|
7075
7057
|
return null;
|
|
7076
7058
|
}
|
|
7077
|
-
const content =
|
|
7059
|
+
const content = fs13.readFileSync(this.configPath, "utf-8");
|
|
7078
7060
|
return JSON.parse(content);
|
|
7079
7061
|
} catch (error) {
|
|
7080
|
-
|
|
7062
|
+
if (error instanceof SyntaxError) {
|
|
7063
|
+
console.warn(`[WorktreeManager] EP1035: Config file corrupted at ${this.configPath} - JSON parse error:`, error.message);
|
|
7064
|
+
console.warn("[WorktreeManager] EP1035: Config will be recovered on next pruneStaleWorktrees call");
|
|
7065
|
+
} else {
|
|
7066
|
+
console.error("[WorktreeManager] Failed to read config:", error.message);
|
|
7067
|
+
}
|
|
7081
7068
|
return null;
|
|
7082
7069
|
}
|
|
7083
7070
|
}
|
|
7084
7071
|
writeConfig(config) {
|
|
7085
7072
|
try {
|
|
7086
|
-
const dir =
|
|
7087
|
-
if (!
|
|
7088
|
-
|
|
7073
|
+
const dir = path14.dirname(this.configPath);
|
|
7074
|
+
if (!fs13.existsSync(dir)) {
|
|
7075
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
7089
7076
|
}
|
|
7090
|
-
|
|
7077
|
+
fs13.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
7091
7078
|
} catch (error) {
|
|
7092
7079
|
console.error("[WorktreeManager] Failed to write config:", error);
|
|
7093
7080
|
throw error;
|
|
@@ -7168,14 +7155,14 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7168
7155
|
}
|
|
7169
7156
|
try {
|
|
7170
7157
|
for (const file of files) {
|
|
7171
|
-
const srcPath =
|
|
7172
|
-
const destPath =
|
|
7173
|
-
if (
|
|
7174
|
-
const destDir =
|
|
7175
|
-
if (!
|
|
7176
|
-
|
|
7177
|
-
}
|
|
7178
|
-
|
|
7158
|
+
const srcPath = path14.join(mainWorktree.worktreePath, file);
|
|
7159
|
+
const destPath = path14.join(worktree.worktreePath, file);
|
|
7160
|
+
if (fs13.existsSync(srcPath)) {
|
|
7161
|
+
const destDir = path14.dirname(destPath);
|
|
7162
|
+
if (!fs13.existsSync(destDir)) {
|
|
7163
|
+
fs13.mkdirSync(destDir, { recursive: true });
|
|
7164
|
+
}
|
|
7165
|
+
fs13.copyFileSync(srcPath, destPath);
|
|
7179
7166
|
console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
|
|
7180
7167
|
} else {
|
|
7181
7168
|
console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
|
|
@@ -7258,27 +7245,27 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7258
7245
|
}
|
|
7259
7246
|
};
|
|
7260
7247
|
function getEpisodaRoot() {
|
|
7261
|
-
return process.env.EPISODA_ROOT ||
|
|
7248
|
+
return process.env.EPISODA_ROOT || path14.join(require("os").homedir(), "episoda");
|
|
7262
7249
|
}
|
|
7263
7250
|
async function isWorktreeProject(projectRoot) {
|
|
7264
7251
|
const manager = new WorktreeManager(projectRoot);
|
|
7265
7252
|
return manager.initialize();
|
|
7266
7253
|
}
|
|
7267
7254
|
async function findProjectRoot(startPath) {
|
|
7268
|
-
let current =
|
|
7255
|
+
let current = path14.resolve(startPath);
|
|
7269
7256
|
const episodaRoot = getEpisodaRoot();
|
|
7270
7257
|
if (!current.startsWith(episodaRoot)) {
|
|
7271
7258
|
return null;
|
|
7272
7259
|
}
|
|
7273
7260
|
for (let i = 0; i < 10; i++) {
|
|
7274
|
-
const bareDir =
|
|
7275
|
-
const episodaDir =
|
|
7276
|
-
if (
|
|
7261
|
+
const bareDir = path14.join(current, ".bare");
|
|
7262
|
+
const episodaDir = path14.join(current, ".episoda");
|
|
7263
|
+
if (fs13.existsSync(bareDir) && fs13.existsSync(episodaDir)) {
|
|
7277
7264
|
if (await isWorktreeProject(current)) {
|
|
7278
7265
|
return current;
|
|
7279
7266
|
}
|
|
7280
7267
|
}
|
|
7281
|
-
const parent =
|
|
7268
|
+
const parent = path14.dirname(current);
|
|
7282
7269
|
if (parent === current) {
|
|
7283
7270
|
break;
|
|
7284
7271
|
}
|
|
@@ -7288,19 +7275,19 @@ async function findProjectRoot(startPath) {
|
|
|
7288
7275
|
}
|
|
7289
7276
|
|
|
7290
7277
|
// src/utils/worktree.ts
|
|
7291
|
-
var
|
|
7292
|
-
var
|
|
7278
|
+
var path15 = __toESM(require("path"));
|
|
7279
|
+
var fs14 = __toESM(require("fs"));
|
|
7293
7280
|
var os5 = __toESM(require("os"));
|
|
7294
7281
|
var import_core11 = __toESM(require_dist());
|
|
7295
7282
|
function getEpisodaRoot2() {
|
|
7296
|
-
return process.env.EPISODA_ROOT ||
|
|
7283
|
+
return process.env.EPISODA_ROOT || path15.join(os5.homedir(), "episoda");
|
|
7297
7284
|
}
|
|
7298
7285
|
function getWorktreeInfo(moduleUid, workspaceSlug, projectSlug) {
|
|
7299
7286
|
const root = getEpisodaRoot2();
|
|
7300
|
-
const worktreePath =
|
|
7287
|
+
const worktreePath = path15.join(root, workspaceSlug, projectSlug, moduleUid);
|
|
7301
7288
|
return {
|
|
7302
7289
|
path: worktreePath,
|
|
7303
|
-
exists:
|
|
7290
|
+
exists: fs14.existsSync(worktreePath),
|
|
7304
7291
|
moduleUid
|
|
7305
7292
|
};
|
|
7306
7293
|
}
|
|
@@ -7313,72 +7300,62 @@ async function getWorktreeInfoForModule(moduleUid) {
|
|
|
7313
7300
|
return getWorktreeInfo(moduleUid, config.workspace_slug, config.project_slug);
|
|
7314
7301
|
}
|
|
7315
7302
|
|
|
7316
|
-
// src/utils/port-allocator.ts
|
|
7317
|
-
var portAssignments = /* @__PURE__ */ new Map();
|
|
7318
|
-
function clearAllPorts() {
|
|
7319
|
-
const count = portAssignments.size;
|
|
7320
|
-
portAssignments.clear();
|
|
7321
|
-
if (count > 0) {
|
|
7322
|
-
console.log(`[PortAllocator] Cleared ${count} port assignments`);
|
|
7323
|
-
}
|
|
7324
|
-
}
|
|
7325
|
-
|
|
7326
7303
|
// src/framework-detector.ts
|
|
7327
|
-
var
|
|
7328
|
-
var
|
|
7304
|
+
var fs15 = __toESM(require("fs"));
|
|
7305
|
+
var path16 = __toESM(require("path"));
|
|
7329
7306
|
function getInstallCommand(cwd) {
|
|
7330
|
-
if (
|
|
7307
|
+
if (fs15.existsSync(path16.join(cwd, "bun.lockb"))) {
|
|
7331
7308
|
return {
|
|
7332
7309
|
command: ["bun", "install"],
|
|
7333
7310
|
description: "Installing dependencies with bun",
|
|
7334
7311
|
detectedFrom: "bun.lockb"
|
|
7335
7312
|
};
|
|
7336
7313
|
}
|
|
7337
|
-
if (
|
|
7314
|
+
if (fs15.existsSync(path16.join(cwd, "pnpm-lock.yaml"))) {
|
|
7338
7315
|
return {
|
|
7339
7316
|
command: ["pnpm", "install"],
|
|
7340
7317
|
description: "Installing dependencies with pnpm",
|
|
7341
7318
|
detectedFrom: "pnpm-lock.yaml"
|
|
7342
7319
|
};
|
|
7343
7320
|
}
|
|
7344
|
-
if (
|
|
7321
|
+
if (fs15.existsSync(path16.join(cwd, "yarn.lock"))) {
|
|
7345
7322
|
return {
|
|
7346
7323
|
command: ["yarn", "install"],
|
|
7347
7324
|
description: "Installing dependencies with yarn",
|
|
7348
7325
|
detectedFrom: "yarn.lock"
|
|
7349
7326
|
};
|
|
7350
7327
|
}
|
|
7351
|
-
if (
|
|
7328
|
+
if (fs15.existsSync(path16.join(cwd, "package-lock.json"))) {
|
|
7352
7329
|
return {
|
|
7353
7330
|
command: ["npm", "ci"],
|
|
7354
7331
|
description: "Installing dependencies with npm ci",
|
|
7355
7332
|
detectedFrom: "package-lock.json"
|
|
7356
7333
|
};
|
|
7357
7334
|
}
|
|
7358
|
-
if (
|
|
7335
|
+
if (fs15.existsSync(path16.join(cwd, "package.json"))) {
|
|
7359
7336
|
return {
|
|
7360
7337
|
command: ["npm", "install"],
|
|
7361
7338
|
description: "Installing dependencies with npm",
|
|
7362
7339
|
detectedFrom: "package.json"
|
|
7363
7340
|
};
|
|
7364
7341
|
}
|
|
7365
|
-
if (
|
|
7342
|
+
if (fs15.existsSync(path16.join(cwd, "Pipfile.lock")) || fs15.existsSync(path16.join(cwd, "Pipfile"))) {
|
|
7366
7343
|
return {
|
|
7367
7344
|
command: ["pipenv", "install"],
|
|
7368
7345
|
description: "Installing dependencies with pipenv",
|
|
7369
|
-
detectedFrom:
|
|
7346
|
+
detectedFrom: fs15.existsSync(path16.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
|
|
7370
7347
|
};
|
|
7371
7348
|
}
|
|
7372
|
-
if (
|
|
7349
|
+
if (fs15.existsSync(path16.join(cwd, "poetry.lock"))) {
|
|
7373
7350
|
return {
|
|
7374
7351
|
command: ["poetry", "install"],
|
|
7375
7352
|
description: "Installing dependencies with poetry",
|
|
7376
7353
|
detectedFrom: "poetry.lock"
|
|
7377
7354
|
};
|
|
7378
7355
|
}
|
|
7379
|
-
if (
|
|
7380
|
-
const pyprojectPath =
|
|
7381
|
-
const content =
|
|
7356
|
+
if (fs15.existsSync(path16.join(cwd, "pyproject.toml"))) {
|
|
7357
|
+
const pyprojectPath = path16.join(cwd, "pyproject.toml");
|
|
7358
|
+
const content = fs15.readFileSync(pyprojectPath, "utf-8");
|
|
7382
7359
|
if (content.includes("[tool.poetry]")) {
|
|
7383
7360
|
return {
|
|
7384
7361
|
command: ["poetry", "install"],
|
|
@@ -7387,41 +7364,41 @@ function getInstallCommand(cwd) {
|
|
|
7387
7364
|
};
|
|
7388
7365
|
}
|
|
7389
7366
|
}
|
|
7390
|
-
if (
|
|
7367
|
+
if (fs15.existsSync(path16.join(cwd, "requirements.txt"))) {
|
|
7391
7368
|
return {
|
|
7392
7369
|
command: ["pip", "install", "-r", "requirements.txt"],
|
|
7393
7370
|
description: "Installing dependencies with pip",
|
|
7394
7371
|
detectedFrom: "requirements.txt"
|
|
7395
7372
|
};
|
|
7396
7373
|
}
|
|
7397
|
-
if (
|
|
7374
|
+
if (fs15.existsSync(path16.join(cwd, "Gemfile.lock")) || fs15.existsSync(path16.join(cwd, "Gemfile"))) {
|
|
7398
7375
|
return {
|
|
7399
7376
|
command: ["bundle", "install"],
|
|
7400
7377
|
description: "Installing dependencies with bundler",
|
|
7401
|
-
detectedFrom:
|
|
7378
|
+
detectedFrom: fs15.existsSync(path16.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
|
|
7402
7379
|
};
|
|
7403
7380
|
}
|
|
7404
|
-
if (
|
|
7381
|
+
if (fs15.existsSync(path16.join(cwd, "go.sum")) || fs15.existsSync(path16.join(cwd, "go.mod"))) {
|
|
7405
7382
|
return {
|
|
7406
7383
|
command: ["go", "mod", "download"],
|
|
7407
7384
|
description: "Downloading Go modules",
|
|
7408
|
-
detectedFrom:
|
|
7385
|
+
detectedFrom: fs15.existsSync(path16.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
|
|
7409
7386
|
};
|
|
7410
7387
|
}
|
|
7411
|
-
if (
|
|
7388
|
+
if (fs15.existsSync(path16.join(cwd, "Cargo.lock")) || fs15.existsSync(path16.join(cwd, "Cargo.toml"))) {
|
|
7412
7389
|
return {
|
|
7413
7390
|
command: ["cargo", "build"],
|
|
7414
7391
|
description: "Building Rust project (downloads dependencies)",
|
|
7415
|
-
detectedFrom:
|
|
7392
|
+
detectedFrom: fs15.existsSync(path16.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
|
|
7416
7393
|
};
|
|
7417
7394
|
}
|
|
7418
7395
|
return null;
|
|
7419
7396
|
}
|
|
7420
7397
|
|
|
7421
7398
|
// src/daemon/daemon-process.ts
|
|
7422
|
-
var
|
|
7399
|
+
var fs16 = __toESM(require("fs"));
|
|
7423
7400
|
var os6 = __toESM(require("os"));
|
|
7424
|
-
var
|
|
7401
|
+
var path17 = __toESM(require("path"));
|
|
7425
7402
|
var packageJson = require_package();
|
|
7426
7403
|
async function ensureValidToken(config, bufferMs = 5 * 60 * 1e3) {
|
|
7427
7404
|
const now = Date.now();
|
|
@@ -7772,6 +7749,7 @@ var Daemon = class _Daemon {
|
|
|
7772
7749
|
}
|
|
7773
7750
|
await tunnelManager.stopTunnel(moduleUid);
|
|
7774
7751
|
await stopDevServer(moduleUid);
|
|
7752
|
+
releasePort(moduleUid);
|
|
7775
7753
|
await clearTunnelUrl(moduleUid);
|
|
7776
7754
|
this.tunnelHealthFailures.delete(moduleUid);
|
|
7777
7755
|
console.log(`[Daemon] EP823: Tunnel stopped for ${moduleUid}`);
|
|
@@ -7922,7 +7900,7 @@ var Daemon = class _Daemon {
|
|
|
7922
7900
|
client.updateActivity();
|
|
7923
7901
|
try {
|
|
7924
7902
|
const gitCmd = message.command;
|
|
7925
|
-
const bareRepoPath =
|
|
7903
|
+
const bareRepoPath = path17.join(projectPath, ".bare");
|
|
7926
7904
|
const cwd = gitCmd.worktreePath || bareRepoPath;
|
|
7927
7905
|
if (gitCmd.worktreePath) {
|
|
7928
7906
|
console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
|
|
@@ -8076,7 +8054,8 @@ var Daemon = class _Daemon {
|
|
|
8076
8054
|
return;
|
|
8077
8055
|
}
|
|
8078
8056
|
console.log(`[Daemon] EP1024: Using worktree path ${worktree.path} for ${cmd.moduleUid}`);
|
|
8079
|
-
const port = cmd.port ||
|
|
8057
|
+
const port = cmd.port || allocatePort(cmd.moduleUid);
|
|
8058
|
+
console.log(`[Daemon] EP1038: Allocated port ${port} for ${cmd.moduleUid}`);
|
|
8080
8059
|
const devConfig = await (0, import_core12.loadConfig)();
|
|
8081
8060
|
const customCommand = devConfig?.project_settings?.worktree_dev_server_script;
|
|
8082
8061
|
const startResult = await previewManager.startPreview({
|
|
@@ -8250,6 +8229,78 @@ var Daemon = class _Daemon {
|
|
|
8250
8229
|
}
|
|
8251
8230
|
}
|
|
8252
8231
|
});
|
|
8232
|
+
client.on("worktree_cleanup_command", async (message) => {
|
|
8233
|
+
if (message.type === "worktree_cleanup_command" && message.command) {
|
|
8234
|
+
const cmd = message.command;
|
|
8235
|
+
console.log(`[Daemon] EP1035: Received worktree cleanup command for ${cmd.moduleUid}`);
|
|
8236
|
+
client.updateActivity();
|
|
8237
|
+
let result;
|
|
8238
|
+
try {
|
|
8239
|
+
const projectRootPath = await findProjectRoot(cmd.worktreePath);
|
|
8240
|
+
if (!projectRootPath) {
|
|
8241
|
+
console.warn(`[Daemon] EP1035: Cannot find project root for ${cmd.worktreePath}`);
|
|
8242
|
+
result = {
|
|
8243
|
+
success: false,
|
|
8244
|
+
error: `Cannot find project root for worktree path: ${cmd.worktreePath}`
|
|
8245
|
+
};
|
|
8246
|
+
} else {
|
|
8247
|
+
const manager = new WorktreeManager(projectRootPath);
|
|
8248
|
+
const initialized = await manager.initialize();
|
|
8249
|
+
if (!initialized) {
|
|
8250
|
+
console.warn(`[Daemon] EP1035: Failed to initialize WorktreeManager for ${projectRootPath}`);
|
|
8251
|
+
result = {
|
|
8252
|
+
success: false,
|
|
8253
|
+
error: `Failed to initialize WorktreeManager for project: ${projectRootPath}`
|
|
8254
|
+
};
|
|
8255
|
+
} else {
|
|
8256
|
+
const removeResult = await manager.removeWorktree(cmd.moduleUid, cmd.force || false);
|
|
8257
|
+
if (removeResult.success) {
|
|
8258
|
+
if (removeResult.configUpdated === false) {
|
|
8259
|
+
console.warn(`[Daemon] EP1035: Worktree ${cmd.moduleUid} removed but config update failed (will sync on next prune)`);
|
|
8260
|
+
} else {
|
|
8261
|
+
console.log(`[Daemon] EP1035: Successfully cleaned up worktree for ${cmd.moduleUid}`);
|
|
8262
|
+
}
|
|
8263
|
+
result = {
|
|
8264
|
+
success: true,
|
|
8265
|
+
worktreePath: removeResult.worktreePath
|
|
8266
|
+
};
|
|
8267
|
+
} else {
|
|
8268
|
+
if (removeResult.error?.includes("not found") || removeResult.error?.includes("No worktree found")) {
|
|
8269
|
+
console.log(`[Daemon] EP1035: Worktree for ${cmd.moduleUid} already removed, syncing config`);
|
|
8270
|
+
await manager.pruneStaleWorktrees();
|
|
8271
|
+
result = {
|
|
8272
|
+
success: true,
|
|
8273
|
+
worktreePath: cmd.worktreePath
|
|
8274
|
+
};
|
|
8275
|
+
} else {
|
|
8276
|
+
console.error(`[Daemon] EP1035: Failed to remove worktree for ${cmd.moduleUid}: ${removeResult.error}`);
|
|
8277
|
+
result = {
|
|
8278
|
+
success: false,
|
|
8279
|
+
error: removeResult.error
|
|
8280
|
+
};
|
|
8281
|
+
}
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
} catch (error) {
|
|
8286
|
+
console.error(`[Daemon] EP1035: Worktree cleanup error for ${cmd.moduleUid}:`, error);
|
|
8287
|
+
result = {
|
|
8288
|
+
success: false,
|
|
8289
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8290
|
+
};
|
|
8291
|
+
}
|
|
8292
|
+
try {
|
|
8293
|
+
await client.send({
|
|
8294
|
+
type: "worktree_cleanup_result",
|
|
8295
|
+
commandId: message.id,
|
|
8296
|
+
result
|
|
8297
|
+
});
|
|
8298
|
+
} catch (sendError) {
|
|
8299
|
+
console.error(`[Daemon] EP1035: Failed to send cleanup result (WebSocket may be disconnected):`, sendError);
|
|
8300
|
+
}
|
|
8301
|
+
console.log(`[Daemon] EP1035: Worktree cleanup for ${cmd.moduleUid} completed:`, result.success ? "success" : "failed");
|
|
8302
|
+
}
|
|
8303
|
+
});
|
|
8253
8304
|
client.on("shutdown", async (message) => {
|
|
8254
8305
|
const shutdownMessage = message;
|
|
8255
8306
|
const reason = shutdownMessage.reason || "unknown";
|
|
@@ -8337,8 +8388,8 @@ var Daemon = class _Daemon {
|
|
|
8337
8388
|
let daemonPid;
|
|
8338
8389
|
try {
|
|
8339
8390
|
const pidPath = getPidFilePath();
|
|
8340
|
-
if (
|
|
8341
|
-
const pidStr =
|
|
8391
|
+
if (fs16.existsSync(pidPath)) {
|
|
8392
|
+
const pidStr = fs16.readFileSync(pidPath, "utf-8").trim();
|
|
8342
8393
|
daemonPid = parseInt(pidStr, 10);
|
|
8343
8394
|
}
|
|
8344
8395
|
} catch (pidError) {
|
|
@@ -8459,27 +8510,27 @@ var Daemon = class _Daemon {
|
|
|
8459
8510
|
*/
|
|
8460
8511
|
async installGitHooks(projectPath) {
|
|
8461
8512
|
const hooks = ["post-checkout", "pre-commit", "post-commit"];
|
|
8462
|
-
const hooksDir =
|
|
8463
|
-
if (!
|
|
8513
|
+
const hooksDir = path17.join(projectPath, ".git", "hooks");
|
|
8514
|
+
if (!fs16.existsSync(hooksDir)) {
|
|
8464
8515
|
console.warn(`[Daemon] Hooks directory not found: ${hooksDir}`);
|
|
8465
8516
|
return;
|
|
8466
8517
|
}
|
|
8467
8518
|
for (const hookName of hooks) {
|
|
8468
8519
|
try {
|
|
8469
|
-
const hookPath =
|
|
8470
|
-
const bundledHookPath =
|
|
8471
|
-
if (!
|
|
8520
|
+
const hookPath = path17.join(hooksDir, hookName);
|
|
8521
|
+
const bundledHookPath = path17.join(__dirname, "..", "hooks", hookName);
|
|
8522
|
+
if (!fs16.existsSync(bundledHookPath)) {
|
|
8472
8523
|
console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
|
|
8473
8524
|
continue;
|
|
8474
8525
|
}
|
|
8475
|
-
const hookContent =
|
|
8476
|
-
if (
|
|
8477
|
-
const existingContent =
|
|
8526
|
+
const hookContent = fs16.readFileSync(bundledHookPath, "utf-8");
|
|
8527
|
+
if (fs16.existsSync(hookPath)) {
|
|
8528
|
+
const existingContent = fs16.readFileSync(hookPath, "utf-8");
|
|
8478
8529
|
if (existingContent === hookContent) {
|
|
8479
8530
|
continue;
|
|
8480
8531
|
}
|
|
8481
8532
|
}
|
|
8482
|
-
|
|
8533
|
+
fs16.writeFileSync(hookPath, hookContent, { mode: 493 });
|
|
8483
8534
|
console.log(`[Daemon] Installed git hook: ${hookName}`);
|
|
8484
8535
|
} catch (error) {
|
|
8485
8536
|
console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
|
|
@@ -9030,14 +9081,36 @@ var Daemon = class _Daemon {
|
|
|
9030
9081
|
}
|
|
9031
9082
|
const { orphaned } = manager.auditWorktrees(activeModuleUids);
|
|
9032
9083
|
if (orphaned.length > 0) {
|
|
9033
|
-
console.log(`[Daemon]
|
|
9084
|
+
console.log(`[Daemon] EP1035: Found ${orphaned.length} orphaned worktree(s) in ${config.workspaceSlug}/${config.projectSlug}:`);
|
|
9034
9085
|
for (const w of orphaned) {
|
|
9035
9086
|
console.log(` - ${w.moduleUid} (branch: ${w.branchName})`);
|
|
9036
9087
|
}
|
|
9037
|
-
console.log(
|
|
9088
|
+
console.log("[Daemon] EP1035: Auto-cleaning orphaned worktrees...");
|
|
9089
|
+
for (const w of orphaned) {
|
|
9090
|
+
try {
|
|
9091
|
+
const removeResult = await manager.removeWorktree(w.moduleUid, true);
|
|
9092
|
+
if (removeResult.success) {
|
|
9093
|
+
console.log(`[Daemon] EP1035: Cleaned up orphaned worktree ${w.moduleUid}`);
|
|
9094
|
+
} else {
|
|
9095
|
+
if (removeResult.error?.includes("not found") || removeResult.error?.includes("No worktree found")) {
|
|
9096
|
+
console.log(`[Daemon] EP1035: Worktree ${w.moduleUid} already removed, syncing config`);
|
|
9097
|
+
await manager.pruneStaleWorktrees();
|
|
9098
|
+
} else {
|
|
9099
|
+
console.warn(`[Daemon] EP1035: Failed to clean up ${w.moduleUid}: ${removeResult.error}`);
|
|
9100
|
+
}
|
|
9101
|
+
}
|
|
9102
|
+
} catch (cleanupError) {
|
|
9103
|
+
console.warn(`[Daemon] EP1035: Error cleaning up ${w.moduleUid}:`, cleanupError.message);
|
|
9104
|
+
}
|
|
9105
|
+
}
|
|
9106
|
+
try {
|
|
9107
|
+
await manager.pruneStaleWorktrees();
|
|
9108
|
+
} catch (pruneError) {
|
|
9109
|
+
console.warn("[Daemon] EP1035: Failed to prune stale worktrees:", pruneError.message);
|
|
9110
|
+
}
|
|
9038
9111
|
}
|
|
9039
9112
|
} catch (error) {
|
|
9040
|
-
console.warn(`[Daemon]
|
|
9113
|
+
console.warn(`[Daemon] EP1035: Failed to audit ${projectPath}:`, error);
|
|
9041
9114
|
}
|
|
9042
9115
|
}
|
|
9043
9116
|
/**
|
|
@@ -9342,8 +9415,8 @@ var Daemon = class _Daemon {
|
|
|
9342
9415
|
await this.shutdown();
|
|
9343
9416
|
try {
|
|
9344
9417
|
const pidPath = getPidFilePath();
|
|
9345
|
-
if (
|
|
9346
|
-
|
|
9418
|
+
if (fs16.existsSync(pidPath)) {
|
|
9419
|
+
fs16.unlinkSync(pidPath);
|
|
9347
9420
|
console.log("[Daemon] PID file cleaned up");
|
|
9348
9421
|
}
|
|
9349
9422
|
} catch (error) {
|