episoda 0.2.51 → 0.2.52
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.
|
@@ -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.52",
|
|
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 {
|
|
@@ -6945,7 +6920,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
6945
6920
|
let prunedCount = 0;
|
|
6946
6921
|
await this.updateConfigSafe((config) => {
|
|
6947
6922
|
const initialCount = config.worktrees.length;
|
|
6948
|
-
config.worktrees = config.worktrees.filter((w) =>
|
|
6923
|
+
config.worktrees = config.worktrees.filter((w) => fs13.existsSync(w.worktreePath));
|
|
6949
6924
|
prunedCount = initialCount - config.worktrees.length;
|
|
6950
6925
|
return config;
|
|
6951
6926
|
});
|
|
@@ -7026,16 +7001,16 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7026
7001
|
const retryInterval = 50;
|
|
7027
7002
|
while (Date.now() - startTime < timeoutMs) {
|
|
7028
7003
|
try {
|
|
7029
|
-
|
|
7004
|
+
fs13.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
7030
7005
|
return true;
|
|
7031
7006
|
} catch (err) {
|
|
7032
7007
|
if (err.code === "EEXIST") {
|
|
7033
7008
|
try {
|
|
7034
|
-
const stats =
|
|
7009
|
+
const stats = fs13.statSync(lockPath);
|
|
7035
7010
|
const lockAge = Date.now() - stats.mtimeMs;
|
|
7036
7011
|
if (lockAge > 3e4) {
|
|
7037
7012
|
try {
|
|
7038
|
-
const lockContent =
|
|
7013
|
+
const lockContent = fs13.readFileSync(lockPath, "utf-8").trim();
|
|
7039
7014
|
const lockPid = parseInt(lockContent, 10);
|
|
7040
7015
|
if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
|
|
7041
7016
|
await new Promise((resolve3) => setTimeout(resolve3, retryInterval));
|
|
@@ -7044,7 +7019,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7044
7019
|
} catch {
|
|
7045
7020
|
}
|
|
7046
7021
|
try {
|
|
7047
|
-
|
|
7022
|
+
fs13.unlinkSync(lockPath);
|
|
7048
7023
|
} catch {
|
|
7049
7024
|
}
|
|
7050
7025
|
continue;
|
|
@@ -7065,16 +7040,16 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7065
7040
|
*/
|
|
7066
7041
|
releaseLock() {
|
|
7067
7042
|
try {
|
|
7068
|
-
|
|
7043
|
+
fs13.unlinkSync(this.getLockPath());
|
|
7069
7044
|
} catch {
|
|
7070
7045
|
}
|
|
7071
7046
|
}
|
|
7072
7047
|
readConfig() {
|
|
7073
7048
|
try {
|
|
7074
|
-
if (!
|
|
7049
|
+
if (!fs13.existsSync(this.configPath)) {
|
|
7075
7050
|
return null;
|
|
7076
7051
|
}
|
|
7077
|
-
const content =
|
|
7052
|
+
const content = fs13.readFileSync(this.configPath, "utf-8");
|
|
7078
7053
|
return JSON.parse(content);
|
|
7079
7054
|
} catch (error) {
|
|
7080
7055
|
console.error("[WorktreeManager] Failed to read config:", error);
|
|
@@ -7083,11 +7058,11 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7083
7058
|
}
|
|
7084
7059
|
writeConfig(config) {
|
|
7085
7060
|
try {
|
|
7086
|
-
const dir =
|
|
7087
|
-
if (!
|
|
7088
|
-
|
|
7061
|
+
const dir = path14.dirname(this.configPath);
|
|
7062
|
+
if (!fs13.existsSync(dir)) {
|
|
7063
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
7089
7064
|
}
|
|
7090
|
-
|
|
7065
|
+
fs13.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
7091
7066
|
} catch (error) {
|
|
7092
7067
|
console.error("[WorktreeManager] Failed to write config:", error);
|
|
7093
7068
|
throw error;
|
|
@@ -7168,14 +7143,14 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7168
7143
|
}
|
|
7169
7144
|
try {
|
|
7170
7145
|
for (const file of files) {
|
|
7171
|
-
const srcPath =
|
|
7172
|
-
const destPath =
|
|
7173
|
-
if (
|
|
7174
|
-
const destDir =
|
|
7175
|
-
if (!
|
|
7176
|
-
|
|
7177
|
-
}
|
|
7178
|
-
|
|
7146
|
+
const srcPath = path14.join(mainWorktree.worktreePath, file);
|
|
7147
|
+
const destPath = path14.join(worktree.worktreePath, file);
|
|
7148
|
+
if (fs13.existsSync(srcPath)) {
|
|
7149
|
+
const destDir = path14.dirname(destPath);
|
|
7150
|
+
if (!fs13.existsSync(destDir)) {
|
|
7151
|
+
fs13.mkdirSync(destDir, { recursive: true });
|
|
7152
|
+
}
|
|
7153
|
+
fs13.copyFileSync(srcPath, destPath);
|
|
7179
7154
|
console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
|
|
7180
7155
|
} else {
|
|
7181
7156
|
console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
|
|
@@ -7258,27 +7233,27 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
7258
7233
|
}
|
|
7259
7234
|
};
|
|
7260
7235
|
function getEpisodaRoot() {
|
|
7261
|
-
return process.env.EPISODA_ROOT ||
|
|
7236
|
+
return process.env.EPISODA_ROOT || path14.join(require("os").homedir(), "episoda");
|
|
7262
7237
|
}
|
|
7263
7238
|
async function isWorktreeProject(projectRoot) {
|
|
7264
7239
|
const manager = new WorktreeManager(projectRoot);
|
|
7265
7240
|
return manager.initialize();
|
|
7266
7241
|
}
|
|
7267
7242
|
async function findProjectRoot(startPath) {
|
|
7268
|
-
let current =
|
|
7243
|
+
let current = path14.resolve(startPath);
|
|
7269
7244
|
const episodaRoot = getEpisodaRoot();
|
|
7270
7245
|
if (!current.startsWith(episodaRoot)) {
|
|
7271
7246
|
return null;
|
|
7272
7247
|
}
|
|
7273
7248
|
for (let i = 0; i < 10; i++) {
|
|
7274
|
-
const bareDir =
|
|
7275
|
-
const episodaDir =
|
|
7276
|
-
if (
|
|
7249
|
+
const bareDir = path14.join(current, ".bare");
|
|
7250
|
+
const episodaDir = path14.join(current, ".episoda");
|
|
7251
|
+
if (fs13.existsSync(bareDir) && fs13.existsSync(episodaDir)) {
|
|
7277
7252
|
if (await isWorktreeProject(current)) {
|
|
7278
7253
|
return current;
|
|
7279
7254
|
}
|
|
7280
7255
|
}
|
|
7281
|
-
const parent =
|
|
7256
|
+
const parent = path14.dirname(current);
|
|
7282
7257
|
if (parent === current) {
|
|
7283
7258
|
break;
|
|
7284
7259
|
}
|
|
@@ -7288,19 +7263,19 @@ async function findProjectRoot(startPath) {
|
|
|
7288
7263
|
}
|
|
7289
7264
|
|
|
7290
7265
|
// src/utils/worktree.ts
|
|
7291
|
-
var
|
|
7292
|
-
var
|
|
7266
|
+
var path15 = __toESM(require("path"));
|
|
7267
|
+
var fs14 = __toESM(require("fs"));
|
|
7293
7268
|
var os5 = __toESM(require("os"));
|
|
7294
7269
|
var import_core11 = __toESM(require_dist());
|
|
7295
7270
|
function getEpisodaRoot2() {
|
|
7296
|
-
return process.env.EPISODA_ROOT ||
|
|
7271
|
+
return process.env.EPISODA_ROOT || path15.join(os5.homedir(), "episoda");
|
|
7297
7272
|
}
|
|
7298
7273
|
function getWorktreeInfo(moduleUid, workspaceSlug, projectSlug) {
|
|
7299
7274
|
const root = getEpisodaRoot2();
|
|
7300
|
-
const worktreePath =
|
|
7275
|
+
const worktreePath = path15.join(root, workspaceSlug, projectSlug, moduleUid);
|
|
7301
7276
|
return {
|
|
7302
7277
|
path: worktreePath,
|
|
7303
|
-
exists:
|
|
7278
|
+
exists: fs14.existsSync(worktreePath),
|
|
7304
7279
|
moduleUid
|
|
7305
7280
|
};
|
|
7306
7281
|
}
|
|
@@ -7313,72 +7288,62 @@ async function getWorktreeInfoForModule(moduleUid) {
|
|
|
7313
7288
|
return getWorktreeInfo(moduleUid, config.workspace_slug, config.project_slug);
|
|
7314
7289
|
}
|
|
7315
7290
|
|
|
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
7291
|
// src/framework-detector.ts
|
|
7327
|
-
var
|
|
7328
|
-
var
|
|
7292
|
+
var fs15 = __toESM(require("fs"));
|
|
7293
|
+
var path16 = __toESM(require("path"));
|
|
7329
7294
|
function getInstallCommand(cwd) {
|
|
7330
|
-
if (
|
|
7295
|
+
if (fs15.existsSync(path16.join(cwd, "bun.lockb"))) {
|
|
7331
7296
|
return {
|
|
7332
7297
|
command: ["bun", "install"],
|
|
7333
7298
|
description: "Installing dependencies with bun",
|
|
7334
7299
|
detectedFrom: "bun.lockb"
|
|
7335
7300
|
};
|
|
7336
7301
|
}
|
|
7337
|
-
if (
|
|
7302
|
+
if (fs15.existsSync(path16.join(cwd, "pnpm-lock.yaml"))) {
|
|
7338
7303
|
return {
|
|
7339
7304
|
command: ["pnpm", "install"],
|
|
7340
7305
|
description: "Installing dependencies with pnpm",
|
|
7341
7306
|
detectedFrom: "pnpm-lock.yaml"
|
|
7342
7307
|
};
|
|
7343
7308
|
}
|
|
7344
|
-
if (
|
|
7309
|
+
if (fs15.existsSync(path16.join(cwd, "yarn.lock"))) {
|
|
7345
7310
|
return {
|
|
7346
7311
|
command: ["yarn", "install"],
|
|
7347
7312
|
description: "Installing dependencies with yarn",
|
|
7348
7313
|
detectedFrom: "yarn.lock"
|
|
7349
7314
|
};
|
|
7350
7315
|
}
|
|
7351
|
-
if (
|
|
7316
|
+
if (fs15.existsSync(path16.join(cwd, "package-lock.json"))) {
|
|
7352
7317
|
return {
|
|
7353
7318
|
command: ["npm", "ci"],
|
|
7354
7319
|
description: "Installing dependencies with npm ci",
|
|
7355
7320
|
detectedFrom: "package-lock.json"
|
|
7356
7321
|
};
|
|
7357
7322
|
}
|
|
7358
|
-
if (
|
|
7323
|
+
if (fs15.existsSync(path16.join(cwd, "package.json"))) {
|
|
7359
7324
|
return {
|
|
7360
7325
|
command: ["npm", "install"],
|
|
7361
7326
|
description: "Installing dependencies with npm",
|
|
7362
7327
|
detectedFrom: "package.json"
|
|
7363
7328
|
};
|
|
7364
7329
|
}
|
|
7365
|
-
if (
|
|
7330
|
+
if (fs15.existsSync(path16.join(cwd, "Pipfile.lock")) || fs15.existsSync(path16.join(cwd, "Pipfile"))) {
|
|
7366
7331
|
return {
|
|
7367
7332
|
command: ["pipenv", "install"],
|
|
7368
7333
|
description: "Installing dependencies with pipenv",
|
|
7369
|
-
detectedFrom:
|
|
7334
|
+
detectedFrom: fs15.existsSync(path16.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
|
|
7370
7335
|
};
|
|
7371
7336
|
}
|
|
7372
|
-
if (
|
|
7337
|
+
if (fs15.existsSync(path16.join(cwd, "poetry.lock"))) {
|
|
7373
7338
|
return {
|
|
7374
7339
|
command: ["poetry", "install"],
|
|
7375
7340
|
description: "Installing dependencies with poetry",
|
|
7376
7341
|
detectedFrom: "poetry.lock"
|
|
7377
7342
|
};
|
|
7378
7343
|
}
|
|
7379
|
-
if (
|
|
7380
|
-
const pyprojectPath =
|
|
7381
|
-
const content =
|
|
7344
|
+
if (fs15.existsSync(path16.join(cwd, "pyproject.toml"))) {
|
|
7345
|
+
const pyprojectPath = path16.join(cwd, "pyproject.toml");
|
|
7346
|
+
const content = fs15.readFileSync(pyprojectPath, "utf-8");
|
|
7382
7347
|
if (content.includes("[tool.poetry]")) {
|
|
7383
7348
|
return {
|
|
7384
7349
|
command: ["poetry", "install"],
|
|
@@ -7387,41 +7352,41 @@ function getInstallCommand(cwd) {
|
|
|
7387
7352
|
};
|
|
7388
7353
|
}
|
|
7389
7354
|
}
|
|
7390
|
-
if (
|
|
7355
|
+
if (fs15.existsSync(path16.join(cwd, "requirements.txt"))) {
|
|
7391
7356
|
return {
|
|
7392
7357
|
command: ["pip", "install", "-r", "requirements.txt"],
|
|
7393
7358
|
description: "Installing dependencies with pip",
|
|
7394
7359
|
detectedFrom: "requirements.txt"
|
|
7395
7360
|
};
|
|
7396
7361
|
}
|
|
7397
|
-
if (
|
|
7362
|
+
if (fs15.existsSync(path16.join(cwd, "Gemfile.lock")) || fs15.existsSync(path16.join(cwd, "Gemfile"))) {
|
|
7398
7363
|
return {
|
|
7399
7364
|
command: ["bundle", "install"],
|
|
7400
7365
|
description: "Installing dependencies with bundler",
|
|
7401
|
-
detectedFrom:
|
|
7366
|
+
detectedFrom: fs15.existsSync(path16.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
|
|
7402
7367
|
};
|
|
7403
7368
|
}
|
|
7404
|
-
if (
|
|
7369
|
+
if (fs15.existsSync(path16.join(cwd, "go.sum")) || fs15.existsSync(path16.join(cwd, "go.mod"))) {
|
|
7405
7370
|
return {
|
|
7406
7371
|
command: ["go", "mod", "download"],
|
|
7407
7372
|
description: "Downloading Go modules",
|
|
7408
|
-
detectedFrom:
|
|
7373
|
+
detectedFrom: fs15.existsSync(path16.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
|
|
7409
7374
|
};
|
|
7410
7375
|
}
|
|
7411
|
-
if (
|
|
7376
|
+
if (fs15.existsSync(path16.join(cwd, "Cargo.lock")) || fs15.existsSync(path16.join(cwd, "Cargo.toml"))) {
|
|
7412
7377
|
return {
|
|
7413
7378
|
command: ["cargo", "build"],
|
|
7414
7379
|
description: "Building Rust project (downloads dependencies)",
|
|
7415
|
-
detectedFrom:
|
|
7380
|
+
detectedFrom: fs15.existsSync(path16.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
|
|
7416
7381
|
};
|
|
7417
7382
|
}
|
|
7418
7383
|
return null;
|
|
7419
7384
|
}
|
|
7420
7385
|
|
|
7421
7386
|
// src/daemon/daemon-process.ts
|
|
7422
|
-
var
|
|
7387
|
+
var fs16 = __toESM(require("fs"));
|
|
7423
7388
|
var os6 = __toESM(require("os"));
|
|
7424
|
-
var
|
|
7389
|
+
var path17 = __toESM(require("path"));
|
|
7425
7390
|
var packageJson = require_package();
|
|
7426
7391
|
async function ensureValidToken(config, bufferMs = 5 * 60 * 1e3) {
|
|
7427
7392
|
const now = Date.now();
|
|
@@ -7772,6 +7737,7 @@ var Daemon = class _Daemon {
|
|
|
7772
7737
|
}
|
|
7773
7738
|
await tunnelManager.stopTunnel(moduleUid);
|
|
7774
7739
|
await stopDevServer(moduleUid);
|
|
7740
|
+
releasePort(moduleUid);
|
|
7775
7741
|
await clearTunnelUrl(moduleUid);
|
|
7776
7742
|
this.tunnelHealthFailures.delete(moduleUid);
|
|
7777
7743
|
console.log(`[Daemon] EP823: Tunnel stopped for ${moduleUid}`);
|
|
@@ -7922,7 +7888,7 @@ var Daemon = class _Daemon {
|
|
|
7922
7888
|
client.updateActivity();
|
|
7923
7889
|
try {
|
|
7924
7890
|
const gitCmd = message.command;
|
|
7925
|
-
const bareRepoPath =
|
|
7891
|
+
const bareRepoPath = path17.join(projectPath, ".bare");
|
|
7926
7892
|
const cwd = gitCmd.worktreePath || bareRepoPath;
|
|
7927
7893
|
if (gitCmd.worktreePath) {
|
|
7928
7894
|
console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
|
|
@@ -8076,7 +8042,8 @@ var Daemon = class _Daemon {
|
|
|
8076
8042
|
return;
|
|
8077
8043
|
}
|
|
8078
8044
|
console.log(`[Daemon] EP1024: Using worktree path ${worktree.path} for ${cmd.moduleUid}`);
|
|
8079
|
-
const port = cmd.port ||
|
|
8045
|
+
const port = cmd.port || allocatePort(cmd.moduleUid);
|
|
8046
|
+
console.log(`[Daemon] EP1038: Allocated port ${port} for ${cmd.moduleUid}`);
|
|
8080
8047
|
const devConfig = await (0, import_core12.loadConfig)();
|
|
8081
8048
|
const customCommand = devConfig?.project_settings?.worktree_dev_server_script;
|
|
8082
8049
|
const startResult = await previewManager.startPreview({
|
|
@@ -8337,8 +8304,8 @@ var Daemon = class _Daemon {
|
|
|
8337
8304
|
let daemonPid;
|
|
8338
8305
|
try {
|
|
8339
8306
|
const pidPath = getPidFilePath();
|
|
8340
|
-
if (
|
|
8341
|
-
const pidStr =
|
|
8307
|
+
if (fs16.existsSync(pidPath)) {
|
|
8308
|
+
const pidStr = fs16.readFileSync(pidPath, "utf-8").trim();
|
|
8342
8309
|
daemonPid = parseInt(pidStr, 10);
|
|
8343
8310
|
}
|
|
8344
8311
|
} catch (pidError) {
|
|
@@ -8459,27 +8426,27 @@ var Daemon = class _Daemon {
|
|
|
8459
8426
|
*/
|
|
8460
8427
|
async installGitHooks(projectPath) {
|
|
8461
8428
|
const hooks = ["post-checkout", "pre-commit", "post-commit"];
|
|
8462
|
-
const hooksDir =
|
|
8463
|
-
if (!
|
|
8429
|
+
const hooksDir = path17.join(projectPath, ".git", "hooks");
|
|
8430
|
+
if (!fs16.existsSync(hooksDir)) {
|
|
8464
8431
|
console.warn(`[Daemon] Hooks directory not found: ${hooksDir}`);
|
|
8465
8432
|
return;
|
|
8466
8433
|
}
|
|
8467
8434
|
for (const hookName of hooks) {
|
|
8468
8435
|
try {
|
|
8469
|
-
const hookPath =
|
|
8470
|
-
const bundledHookPath =
|
|
8471
|
-
if (!
|
|
8436
|
+
const hookPath = path17.join(hooksDir, hookName);
|
|
8437
|
+
const bundledHookPath = path17.join(__dirname, "..", "hooks", hookName);
|
|
8438
|
+
if (!fs16.existsSync(bundledHookPath)) {
|
|
8472
8439
|
console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
|
|
8473
8440
|
continue;
|
|
8474
8441
|
}
|
|
8475
|
-
const hookContent =
|
|
8476
|
-
if (
|
|
8477
|
-
const existingContent =
|
|
8442
|
+
const hookContent = fs16.readFileSync(bundledHookPath, "utf-8");
|
|
8443
|
+
if (fs16.existsSync(hookPath)) {
|
|
8444
|
+
const existingContent = fs16.readFileSync(hookPath, "utf-8");
|
|
8478
8445
|
if (existingContent === hookContent) {
|
|
8479
8446
|
continue;
|
|
8480
8447
|
}
|
|
8481
8448
|
}
|
|
8482
|
-
|
|
8449
|
+
fs16.writeFileSync(hookPath, hookContent, { mode: 493 });
|
|
8483
8450
|
console.log(`[Daemon] Installed git hook: ${hookName}`);
|
|
8484
8451
|
} catch (error) {
|
|
8485
8452
|
console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
|
|
@@ -9342,8 +9309,8 @@ var Daemon = class _Daemon {
|
|
|
9342
9309
|
await this.shutdown();
|
|
9343
9310
|
try {
|
|
9344
9311
|
const pidPath = getPidFilePath();
|
|
9345
|
-
if (
|
|
9346
|
-
|
|
9312
|
+
if (fs16.existsSync(pidPath)) {
|
|
9313
|
+
fs16.unlinkSync(pidPath);
|
|
9347
9314
|
console.log("[Daemon] PID file cleaned up");
|
|
9348
9315
|
}
|
|
9349
9316
|
} catch (error) {
|