episoda 0.2.21 → 0.2.23
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 +403 -29
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/hooks/post-checkout +3 -2
- package/dist/hooks/pre-commit +6 -5
- package/dist/index.js +459 -568
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -295,7 +295,7 @@ var require_git_executor = __commonJS({
|
|
|
295
295
|
var git_validator_1 = require_git_validator();
|
|
296
296
|
var git_parser_1 = require_git_parser();
|
|
297
297
|
var execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
298
|
-
var
|
|
298
|
+
var GitExecutor4 = class {
|
|
299
299
|
/**
|
|
300
300
|
* Execute a git command
|
|
301
301
|
* @param command - The git command to execute
|
|
@@ -530,6 +530,24 @@ var require_git_executor = __commonJS({
|
|
|
530
530
|
* Execute status command
|
|
531
531
|
*/
|
|
532
532
|
async executeStatus(cwd, options) {
|
|
533
|
+
try {
|
|
534
|
+
const isBareResult = await execAsync("git rev-parse --is-bare-repository", { cwd, timeout: 5e3 });
|
|
535
|
+
if (isBareResult.stdout.trim() === "true") {
|
|
536
|
+
const headResult = await execAsync("git symbolic-ref --short HEAD", { cwd, timeout: 5e3 });
|
|
537
|
+
const branchName = headResult.stdout.trim();
|
|
538
|
+
return {
|
|
539
|
+
success: true,
|
|
540
|
+
output: `## ${branchName}`,
|
|
541
|
+
details: {
|
|
542
|
+
uncommittedFiles: [],
|
|
543
|
+
// No working tree in bare repo
|
|
544
|
+
branchName,
|
|
545
|
+
currentBranch: branchName
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
533
551
|
const result = await this.runGitCommand(["status", "--porcelain", "-b"], cwd, options);
|
|
534
552
|
if (result.success && result.output) {
|
|
535
553
|
const statusInfo = (0, git_parser_1.parseGitStatus)(result.output);
|
|
@@ -1531,15 +1549,15 @@ var require_git_executor = __commonJS({
|
|
|
1531
1549
|
try {
|
|
1532
1550
|
const { stdout: gitDir } = await execAsync("git rev-parse --git-dir", { cwd, timeout: 5e3 });
|
|
1533
1551
|
const gitDirPath = gitDir.trim();
|
|
1534
|
-
const
|
|
1552
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1535
1553
|
const rebaseMergePath = `${gitDirPath}/rebase-merge`;
|
|
1536
1554
|
const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
|
|
1537
1555
|
try {
|
|
1538
|
-
await
|
|
1556
|
+
await fs11.access(rebaseMergePath);
|
|
1539
1557
|
inRebase = true;
|
|
1540
1558
|
} catch {
|
|
1541
1559
|
try {
|
|
1542
|
-
await
|
|
1560
|
+
await fs11.access(rebaseApplyPath);
|
|
1543
1561
|
inRebase = true;
|
|
1544
1562
|
} catch {
|
|
1545
1563
|
inRebase = false;
|
|
@@ -1593,9 +1611,9 @@ var require_git_executor = __commonJS({
|
|
|
1593
1611
|
error: validation.error || "UNKNOWN_ERROR"
|
|
1594
1612
|
};
|
|
1595
1613
|
}
|
|
1596
|
-
const
|
|
1614
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1597
1615
|
try {
|
|
1598
|
-
await
|
|
1616
|
+
await fs11.access(command.path);
|
|
1599
1617
|
return {
|
|
1600
1618
|
success: false,
|
|
1601
1619
|
error: "WORKTREE_EXISTS",
|
|
@@ -1646,9 +1664,9 @@ var require_git_executor = __commonJS({
|
|
|
1646
1664
|
*/
|
|
1647
1665
|
async executeWorktreeRemove(command, cwd, options) {
|
|
1648
1666
|
try {
|
|
1649
|
-
const
|
|
1667
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1650
1668
|
try {
|
|
1651
|
-
await
|
|
1669
|
+
await fs11.access(command.path);
|
|
1652
1670
|
} catch {
|
|
1653
1671
|
return {
|
|
1654
1672
|
success: false,
|
|
@@ -1801,10 +1819,10 @@ var require_git_executor = __commonJS({
|
|
|
1801
1819
|
*/
|
|
1802
1820
|
async executeCloneBare(command, options) {
|
|
1803
1821
|
try {
|
|
1804
|
-
const
|
|
1805
|
-
const
|
|
1822
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1823
|
+
const path13 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1806
1824
|
try {
|
|
1807
|
-
await
|
|
1825
|
+
await fs11.access(command.path);
|
|
1808
1826
|
return {
|
|
1809
1827
|
success: false,
|
|
1810
1828
|
error: "BRANCH_ALREADY_EXISTS",
|
|
@@ -1813,9 +1831,9 @@ var require_git_executor = __commonJS({
|
|
|
1813
1831
|
};
|
|
1814
1832
|
} catch {
|
|
1815
1833
|
}
|
|
1816
|
-
const parentDir =
|
|
1834
|
+
const parentDir = path13.dirname(command.path);
|
|
1817
1835
|
try {
|
|
1818
|
-
await
|
|
1836
|
+
await fs11.mkdir(parentDir, { recursive: true });
|
|
1819
1837
|
} catch {
|
|
1820
1838
|
}
|
|
1821
1839
|
const { stdout, stderr } = await execAsync(
|
|
@@ -1858,24 +1876,22 @@ var require_git_executor = __commonJS({
|
|
|
1858
1876
|
*/
|
|
1859
1877
|
async executeProjectInfo(cwd, options) {
|
|
1860
1878
|
try {
|
|
1861
|
-
const
|
|
1862
|
-
const
|
|
1879
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1880
|
+
const path13 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1863
1881
|
let currentPath = cwd;
|
|
1864
|
-
let worktreeMode = false;
|
|
1865
1882
|
let projectPath = cwd;
|
|
1866
1883
|
let bareRepoPath;
|
|
1867
1884
|
for (let i = 0; i < 10; i++) {
|
|
1868
|
-
const bareDir =
|
|
1869
|
-
const episodaDir =
|
|
1885
|
+
const bareDir = path13.join(currentPath, ".bare");
|
|
1886
|
+
const episodaDir = path13.join(currentPath, ".episoda");
|
|
1870
1887
|
try {
|
|
1871
|
-
await
|
|
1872
|
-
await
|
|
1873
|
-
worktreeMode = true;
|
|
1888
|
+
await fs11.access(bareDir);
|
|
1889
|
+
await fs11.access(episodaDir);
|
|
1874
1890
|
projectPath = currentPath;
|
|
1875
1891
|
bareRepoPath = bareDir;
|
|
1876
1892
|
break;
|
|
1877
1893
|
} catch {
|
|
1878
|
-
const parentPath =
|
|
1894
|
+
const parentPath = path13.dirname(currentPath);
|
|
1879
1895
|
if (parentPath === currentPath) {
|
|
1880
1896
|
break;
|
|
1881
1897
|
}
|
|
@@ -1884,9 +1900,8 @@ var require_git_executor = __commonJS({
|
|
|
1884
1900
|
}
|
|
1885
1901
|
return {
|
|
1886
1902
|
success: true,
|
|
1887
|
-
output:
|
|
1903
|
+
output: bareRepoPath ? "Episoda project" : "Git repository",
|
|
1888
1904
|
details: {
|
|
1889
|
-
worktreeMode,
|
|
1890
1905
|
projectPath,
|
|
1891
1906
|
bareRepoPath
|
|
1892
1907
|
}
|
|
@@ -1998,7 +2013,7 @@ var require_git_executor = __commonJS({
|
|
|
1998
2013
|
}
|
|
1999
2014
|
}
|
|
2000
2015
|
};
|
|
2001
|
-
exports2.GitExecutor =
|
|
2016
|
+
exports2.GitExecutor = GitExecutor4;
|
|
2002
2017
|
}
|
|
2003
2018
|
});
|
|
2004
2019
|
|
|
@@ -2467,34 +2482,34 @@ var require_auth = __commonJS({
|
|
|
2467
2482
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
2468
2483
|
exports2.getConfigDir = getConfigDir5;
|
|
2469
2484
|
exports2.getConfigPath = getConfigPath4;
|
|
2470
|
-
exports2.loadConfig =
|
|
2471
|
-
exports2.saveConfig =
|
|
2485
|
+
exports2.loadConfig = loadConfig6;
|
|
2486
|
+
exports2.saveConfig = saveConfig3;
|
|
2472
2487
|
exports2.validateToken = validateToken;
|
|
2473
|
-
var
|
|
2474
|
-
var
|
|
2488
|
+
var fs11 = __importStar(require("fs"));
|
|
2489
|
+
var path13 = __importStar(require("path"));
|
|
2475
2490
|
var os3 = __importStar(require("os"));
|
|
2476
2491
|
var child_process_1 = require("child_process");
|
|
2477
2492
|
var DEFAULT_CONFIG_FILE = "config.json";
|
|
2478
2493
|
function getConfigDir5() {
|
|
2479
|
-
return process.env.EPISODA_CONFIG_DIR ||
|
|
2494
|
+
return process.env.EPISODA_CONFIG_DIR || path13.join(os3.homedir(), ".episoda");
|
|
2480
2495
|
}
|
|
2481
2496
|
function getConfigPath4(configPath) {
|
|
2482
2497
|
if (configPath) {
|
|
2483
2498
|
return configPath;
|
|
2484
2499
|
}
|
|
2485
|
-
return
|
|
2500
|
+
return path13.join(getConfigDir5(), DEFAULT_CONFIG_FILE);
|
|
2486
2501
|
}
|
|
2487
2502
|
function ensureConfigDir(configPath) {
|
|
2488
|
-
const dir =
|
|
2489
|
-
const isNew = !
|
|
2503
|
+
const dir = path13.dirname(configPath);
|
|
2504
|
+
const isNew = !fs11.existsSync(dir);
|
|
2490
2505
|
if (isNew) {
|
|
2491
|
-
|
|
2506
|
+
fs11.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2492
2507
|
}
|
|
2493
2508
|
if (process.platform === "darwin") {
|
|
2494
|
-
const nosyncPath =
|
|
2495
|
-
if (isNew || !
|
|
2509
|
+
const nosyncPath = path13.join(dir, ".nosync");
|
|
2510
|
+
if (isNew || !fs11.existsSync(nosyncPath)) {
|
|
2496
2511
|
try {
|
|
2497
|
-
|
|
2512
|
+
fs11.writeFileSync(nosyncPath, "", { mode: 384 });
|
|
2498
2513
|
(0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
|
|
2499
2514
|
stdio: "ignore",
|
|
2500
2515
|
timeout: 5e3
|
|
@@ -2504,13 +2519,13 @@ var require_auth = __commonJS({
|
|
|
2504
2519
|
}
|
|
2505
2520
|
}
|
|
2506
2521
|
}
|
|
2507
|
-
async function
|
|
2522
|
+
async function loadConfig6(configPath) {
|
|
2508
2523
|
const fullPath = getConfigPath4(configPath);
|
|
2509
|
-
if (!
|
|
2524
|
+
if (!fs11.existsSync(fullPath)) {
|
|
2510
2525
|
return null;
|
|
2511
2526
|
}
|
|
2512
2527
|
try {
|
|
2513
|
-
const content =
|
|
2528
|
+
const content = fs11.readFileSync(fullPath, "utf8");
|
|
2514
2529
|
const config = JSON.parse(content);
|
|
2515
2530
|
return config;
|
|
2516
2531
|
} catch (error) {
|
|
@@ -2518,12 +2533,12 @@ var require_auth = __commonJS({
|
|
|
2518
2533
|
return null;
|
|
2519
2534
|
}
|
|
2520
2535
|
}
|
|
2521
|
-
async function
|
|
2536
|
+
async function saveConfig3(config, configPath) {
|
|
2522
2537
|
const fullPath = getConfigPath4(configPath);
|
|
2523
2538
|
ensureConfigDir(fullPath);
|
|
2524
2539
|
try {
|
|
2525
2540
|
const content = JSON.stringify(config, null, 2);
|
|
2526
|
-
|
|
2541
|
+
fs11.writeFileSync(fullPath, content, { mode: 384 });
|
|
2527
2542
|
} catch (error) {
|
|
2528
2543
|
throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
|
|
2529
2544
|
}
|
|
@@ -2634,10 +2649,10 @@ var require_dist = __commonJS({
|
|
|
2634
2649
|
|
|
2635
2650
|
// src/index.ts
|
|
2636
2651
|
var import_commander = require("commander");
|
|
2637
|
-
var
|
|
2652
|
+
var import_core14 = __toESM(require_dist());
|
|
2638
2653
|
|
|
2639
2654
|
// src/commands/dev.ts
|
|
2640
|
-
var
|
|
2655
|
+
var import_core4 = __toESM(require_dist());
|
|
2641
2656
|
|
|
2642
2657
|
// src/framework-detector.ts
|
|
2643
2658
|
var fs = __toESM(require("fs"));
|
|
@@ -3110,9 +3125,9 @@ async function getDevServerStatus() {
|
|
|
3110
3125
|
}
|
|
3111
3126
|
|
|
3112
3127
|
// src/commands/dev.ts
|
|
3113
|
-
var
|
|
3114
|
-
var
|
|
3115
|
-
var
|
|
3128
|
+
var import_child_process2 = require("child_process");
|
|
3129
|
+
var path5 = __toESM(require("path"));
|
|
3130
|
+
var fs4 = __toESM(require("fs"));
|
|
3116
3131
|
|
|
3117
3132
|
// src/utils/port-check.ts
|
|
3118
3133
|
var net2 = __toESM(require("net"));
|
|
@@ -3172,7 +3187,8 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3172
3187
|
}
|
|
3173
3188
|
/**
|
|
3174
3189
|
* Initialize worktree manager from existing project root
|
|
3175
|
-
*
|
|
3190
|
+
* EP971: All projects use worktree architecture
|
|
3191
|
+
* @returns true if valid project, false otherwise
|
|
3176
3192
|
*/
|
|
3177
3193
|
async initialize() {
|
|
3178
3194
|
if (!fs3.existsSync(this.bareRepoPath)) {
|
|
@@ -3183,7 +3199,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3183
3199
|
}
|
|
3184
3200
|
try {
|
|
3185
3201
|
const config = this.readConfig();
|
|
3186
|
-
return config
|
|
3202
|
+
return config !== null;
|
|
3187
3203
|
} catch {
|
|
3188
3204
|
return false;
|
|
3189
3205
|
}
|
|
@@ -3208,7 +3224,6 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3208
3224
|
workspaceSlug,
|
|
3209
3225
|
projectSlug,
|
|
3210
3226
|
bareRepoPath: manager.bareRepoPath,
|
|
3211
|
-
worktreeMode: true,
|
|
3212
3227
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3213
3228
|
worktrees: []
|
|
3214
3229
|
};
|
|
@@ -3564,6 +3579,148 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3564
3579
|
this.releaseLock();
|
|
3565
3580
|
}
|
|
3566
3581
|
}
|
|
3582
|
+
// EP959-11: Worktree setup methods
|
|
3583
|
+
/**
|
|
3584
|
+
* Update the setup status of a worktree
|
|
3585
|
+
*/
|
|
3586
|
+
async updateWorktreeStatus(moduleUid, status2, error) {
|
|
3587
|
+
return this.updateConfigSafe((config) => {
|
|
3588
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
3589
|
+
if (worktree) {
|
|
3590
|
+
worktree.setupStatus = status2;
|
|
3591
|
+
if (status2 === "running") {
|
|
3592
|
+
worktree.setupStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3593
|
+
} else if (status2 === "ready" || status2 === "error") {
|
|
3594
|
+
worktree.setupCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3595
|
+
}
|
|
3596
|
+
if (error) {
|
|
3597
|
+
worktree.setupError = error;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
return config;
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Get worktree info including setup status
|
|
3605
|
+
*/
|
|
3606
|
+
getWorktreeStatus(moduleUid) {
|
|
3607
|
+
const config = this.readConfig();
|
|
3608
|
+
if (!config) return null;
|
|
3609
|
+
return config.worktrees.find((w) => w.moduleUid === moduleUid) || null;
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Copy files from main worktree to module worktree
|
|
3613
|
+
*
|
|
3614
|
+
* @deprecated EP964: This function is deprecated. Use worktree_env_vars for
|
|
3615
|
+
* environment variables or worktree_setup_script for other file operations.
|
|
3616
|
+
* This function now returns success (no-op) when main/ worktree doesn't exist.
|
|
3617
|
+
*/
|
|
3618
|
+
async copyFilesFromMain(moduleUid, files) {
|
|
3619
|
+
console.warn(`[WorktreeManager] EP964: copyFilesFromMain is DEPRECATED.`);
|
|
3620
|
+
console.warn(`[WorktreeManager] EP964: Use worktree_env_vars for .env or worktree_setup_script for other files.`);
|
|
3621
|
+
const config = this.readConfig();
|
|
3622
|
+
if (!config) {
|
|
3623
|
+
return { success: false, error: "Config not found" };
|
|
3624
|
+
}
|
|
3625
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
3626
|
+
if (!worktree) {
|
|
3627
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
3628
|
+
}
|
|
3629
|
+
const mainWorktree = config.worktrees.find((w) => w.moduleUid === "main");
|
|
3630
|
+
if (!mainWorktree) {
|
|
3631
|
+
console.warn(`[WorktreeManager] EP964: No 'main' worktree - skipping file copy (this is expected).`);
|
|
3632
|
+
return { success: true };
|
|
3633
|
+
}
|
|
3634
|
+
try {
|
|
3635
|
+
for (const file of files) {
|
|
3636
|
+
const srcPath = path4.join(mainWorktree.worktreePath, file);
|
|
3637
|
+
const destPath = path4.join(worktree.worktreePath, file);
|
|
3638
|
+
if (fs3.existsSync(srcPath)) {
|
|
3639
|
+
const destDir = path4.dirname(destPath);
|
|
3640
|
+
if (!fs3.existsSync(destDir)) {
|
|
3641
|
+
fs3.mkdirSync(destDir, { recursive: true });
|
|
3642
|
+
}
|
|
3643
|
+
fs3.copyFileSync(srcPath, destPath);
|
|
3644
|
+
console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
|
|
3645
|
+
} else {
|
|
3646
|
+
console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
return { success: true };
|
|
3650
|
+
} catch (error) {
|
|
3651
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
/**
|
|
3655
|
+
* Run worktree setup script
|
|
3656
|
+
* EP959-M1: Enhanced logging with working directory, timeout info, and script preview
|
|
3657
|
+
*/
|
|
3658
|
+
async runSetupScript(moduleUid, script) {
|
|
3659
|
+
const config = this.readConfig();
|
|
3660
|
+
if (!config) {
|
|
3661
|
+
return { success: false, error: "Config not found" };
|
|
3662
|
+
}
|
|
3663
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
3664
|
+
if (!worktree) {
|
|
3665
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
3666
|
+
}
|
|
3667
|
+
const TIMEOUT_MINUTES = 10;
|
|
3668
|
+
const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
|
|
3669
|
+
console.log(`[WorktreeManager] EP959: Running setup script for ${moduleUid}`);
|
|
3670
|
+
console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
|
|
3671
|
+
console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
|
|
3672
|
+
console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
|
|
3673
|
+
try {
|
|
3674
|
+
const { execSync: execSync7 } = require("child_process");
|
|
3675
|
+
execSync7(script, {
|
|
3676
|
+
cwd: worktree.worktreePath,
|
|
3677
|
+
stdio: "inherit",
|
|
3678
|
+
timeout: TIMEOUT_MINUTES * 60 * 1e3,
|
|
3679
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
3680
|
+
});
|
|
3681
|
+
console.log(`[WorktreeManager] EP959: Setup script completed successfully for ${moduleUid}`);
|
|
3682
|
+
return { success: true };
|
|
3683
|
+
} catch (error) {
|
|
3684
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3685
|
+
console.error(`[WorktreeManager] EP959: Setup script failed for ${moduleUid}:`, errorMessage);
|
|
3686
|
+
return { success: false, error: errorMessage };
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
/**
|
|
3690
|
+
* Run worktree cleanup script before removal
|
|
3691
|
+
* EP959-m3: Execute cleanup script when worktree is being released
|
|
3692
|
+
*/
|
|
3693
|
+
async runCleanupScript(moduleUid, script) {
|
|
3694
|
+
const config = this.readConfig();
|
|
3695
|
+
if (!config) {
|
|
3696
|
+
return { success: false, error: "Config not found" };
|
|
3697
|
+
}
|
|
3698
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
3699
|
+
if (!worktree) {
|
|
3700
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
3701
|
+
}
|
|
3702
|
+
const TIMEOUT_MINUTES = 5;
|
|
3703
|
+
const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
|
|
3704
|
+
console.log(`[WorktreeManager] EP959: Running cleanup script for ${moduleUid}`);
|
|
3705
|
+
console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
|
|
3706
|
+
console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
|
|
3707
|
+
console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
|
|
3708
|
+
try {
|
|
3709
|
+
const { execSync: execSync7 } = require("child_process");
|
|
3710
|
+
execSync7(script, {
|
|
3711
|
+
cwd: worktree.worktreePath,
|
|
3712
|
+
stdio: "inherit",
|
|
3713
|
+
timeout: TIMEOUT_MINUTES * 60 * 1e3,
|
|
3714
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
3715
|
+
});
|
|
3716
|
+
console.log(`[WorktreeManager] EP959: Cleanup script completed successfully for ${moduleUid}`);
|
|
3717
|
+
return { success: true };
|
|
3718
|
+
} catch (error) {
|
|
3719
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3720
|
+
console.warn(`[WorktreeManager] EP959: Cleanup script failed for ${moduleUid} (non-blocking):`, errorMessage);
|
|
3721
|
+
return { success: false, error: errorMessage };
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3567
3724
|
};
|
|
3568
3725
|
function getEpisodaRoot() {
|
|
3569
3726
|
return process.env.EPISODA_ROOT || path4.join(require("os").homedir(), "episoda");
|
|
@@ -3598,401 +3755,65 @@ async function findProjectRoot(startPath) {
|
|
|
3598
3755
|
return null;
|
|
3599
3756
|
}
|
|
3600
3757
|
|
|
3601
|
-
// src/
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
var import_core5 = __toESM(require_dist());
|
|
3606
|
-
|
|
3607
|
-
// src/daemon/project-tracker.ts
|
|
3608
|
-
var fs4 = __toESM(require("fs"));
|
|
3609
|
-
var path5 = __toESM(require("path"));
|
|
3610
|
-
var import_core4 = __toESM(require_dist());
|
|
3611
|
-
function getProjectsFilePath() {
|
|
3612
|
-
return path5.join((0, import_core4.getConfigDir)(), "projects.json");
|
|
3613
|
-
}
|
|
3614
|
-
function readProjects() {
|
|
3615
|
-
const projectsPath = getProjectsFilePath();
|
|
3616
|
-
try {
|
|
3617
|
-
if (!fs4.existsSync(projectsPath)) {
|
|
3618
|
-
return { projects: [] };
|
|
3619
|
-
}
|
|
3620
|
-
const content = fs4.readFileSync(projectsPath, "utf-8");
|
|
3621
|
-
const data = JSON.parse(content);
|
|
3622
|
-
if (!data.projects || !Array.isArray(data.projects)) {
|
|
3623
|
-
console.warn("Invalid projects.json structure, resetting");
|
|
3624
|
-
return { projects: [] };
|
|
3625
|
-
}
|
|
3626
|
-
return data;
|
|
3627
|
-
} catch (error) {
|
|
3628
|
-
console.error("Error reading projects.json:", error);
|
|
3629
|
-
return { projects: [] };
|
|
3630
|
-
}
|
|
3631
|
-
}
|
|
3632
|
-
function writeProjects(data) {
|
|
3633
|
-
const projectsPath = getProjectsFilePath();
|
|
3634
|
-
try {
|
|
3635
|
-
const dir = path5.dirname(projectsPath);
|
|
3636
|
-
if (!fs4.existsSync(dir)) {
|
|
3637
|
-
fs4.mkdirSync(dir, { recursive: true });
|
|
3638
|
-
}
|
|
3639
|
-
fs4.writeFileSync(projectsPath, JSON.stringify(data, null, 2), "utf-8");
|
|
3640
|
-
} catch (error) {
|
|
3641
|
-
throw new Error(`Failed to write projects.json: ${error}`);
|
|
3642
|
-
}
|
|
3643
|
-
}
|
|
3644
|
-
function addProject2(projectId, projectPath, options) {
|
|
3645
|
-
const data = readProjects();
|
|
3646
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3647
|
-
const existingByPath = data.projects.find((p) => p.path === projectPath);
|
|
3648
|
-
if (existingByPath) {
|
|
3649
|
-
existingByPath.id = projectId;
|
|
3650
|
-
existingByPath.last_active = now;
|
|
3651
|
-
if (options?.worktreeMode !== void 0) {
|
|
3652
|
-
existingByPath.worktreeMode = options.worktreeMode;
|
|
3653
|
-
}
|
|
3654
|
-
if (options?.bareRepoPath) {
|
|
3655
|
-
existingByPath.bareRepoPath = options.bareRepoPath;
|
|
3656
|
-
}
|
|
3657
|
-
writeProjects(data);
|
|
3658
|
-
return existingByPath;
|
|
3659
|
-
}
|
|
3660
|
-
const existingByIdIndex = data.projects.findIndex((p) => p.id === projectId);
|
|
3661
|
-
if (existingByIdIndex !== -1) {
|
|
3662
|
-
const existingById = data.projects[existingByIdIndex];
|
|
3663
|
-
console.log(`[ProjectTracker] Replacing project entry: ${existingById.path} -> ${projectPath}`);
|
|
3664
|
-
data.projects.splice(existingByIdIndex, 1);
|
|
3665
|
-
}
|
|
3666
|
-
const projectName = path5.basename(projectPath);
|
|
3667
|
-
const newProject = {
|
|
3668
|
-
id: projectId,
|
|
3669
|
-
path: projectPath,
|
|
3670
|
-
name: projectName,
|
|
3671
|
-
added_at: now,
|
|
3672
|
-
last_active: now,
|
|
3673
|
-
// EP944: Worktree mode fields
|
|
3674
|
-
worktreeMode: options?.worktreeMode,
|
|
3675
|
-
bareRepoPath: options?.bareRepoPath
|
|
3676
|
-
};
|
|
3677
|
-
data.projects.push(newProject);
|
|
3678
|
-
writeProjects(data);
|
|
3679
|
-
return newProject;
|
|
3680
|
-
}
|
|
3681
|
-
function getProject(projectPath) {
|
|
3682
|
-
const data = readProjects();
|
|
3683
|
-
return data.projects.find((p) => p.path === projectPath) || null;
|
|
3684
|
-
}
|
|
3685
|
-
|
|
3686
|
-
// src/commands/migrate.ts
|
|
3687
|
-
async function migrateCommand(options = {}) {
|
|
3688
|
-
const cwd = options.cwd || process.cwd();
|
|
3689
|
-
status.info("Checking current project...");
|
|
3690
|
-
status.info("");
|
|
3691
|
-
if (!fs5.existsSync(path6.join(cwd, ".git"))) {
|
|
3692
|
-
throw new Error(
|
|
3693
|
-
"Not a git repository.\nRun this command from the root of an existing git project."
|
|
3694
|
-
);
|
|
3695
|
-
}
|
|
3696
|
-
const bareDir = path6.join(cwd, ".bare");
|
|
3697
|
-
if (fs5.existsSync(bareDir)) {
|
|
3698
|
-
throw new Error(
|
|
3699
|
-
"This project is already in worktree mode.\nUse `episoda checkout {module}` to create worktrees."
|
|
3700
|
-
);
|
|
3701
|
-
}
|
|
3702
|
-
const config = await (0, import_core5.loadConfig)();
|
|
3703
|
-
if (!config || !config.access_token) {
|
|
3704
|
-
throw new Error(
|
|
3705
|
-
"Not authenticated. Please run `episoda auth` first."
|
|
3706
|
-
);
|
|
3707
|
-
}
|
|
3708
|
-
const episodaConfigPath = path6.join(cwd, ".episoda", "config.json");
|
|
3709
|
-
if (!fs5.existsSync(episodaConfigPath)) {
|
|
3710
|
-
throw new Error(
|
|
3711
|
-
"No .episoda/config.json found.\nThis project is not connected to episoda.dev.\nUse `episoda clone {workspace}/{project}` to clone a fresh copy."
|
|
3712
|
-
);
|
|
3713
|
-
}
|
|
3714
|
-
let existingConfig;
|
|
3715
|
-
try {
|
|
3716
|
-
existingConfig = JSON.parse(fs5.readFileSync(episodaConfigPath, "utf-8"));
|
|
3717
|
-
} catch (error) {
|
|
3718
|
-
throw new Error("Failed to read .episoda/config.json");
|
|
3719
|
-
}
|
|
3720
|
-
const trackedProject = getProject(cwd);
|
|
3721
|
-
const projectId = trackedProject?.id || existingConfig.project_id;
|
|
3722
|
-
if (!projectId) {
|
|
3723
|
-
throw new Error(
|
|
3724
|
-
"Cannot determine project ID.\nUse `episoda clone {workspace}/{project}` for a fresh clone."
|
|
3725
|
-
);
|
|
3726
|
-
}
|
|
3727
|
-
const gitExecutor = new import_core5.GitExecutor();
|
|
3728
|
-
let currentBranch;
|
|
3729
|
-
try {
|
|
3730
|
-
currentBranch = (0, import_child_process2.execSync)("git branch --show-current", {
|
|
3731
|
-
cwd,
|
|
3732
|
-
encoding: "utf-8"
|
|
3733
|
-
}).trim();
|
|
3734
|
-
} catch {
|
|
3735
|
-
throw new Error("Failed to determine current branch");
|
|
3736
|
-
}
|
|
3737
|
-
const statusResult = await gitExecutor.execute({ action: "status" }, { cwd });
|
|
3738
|
-
const hasUncommitted = (statusResult.details?.uncommittedFiles?.length || 0) > 0;
|
|
3739
|
-
if (hasUncommitted && !options.force) {
|
|
3740
|
-
status.warning("You have uncommitted changes:");
|
|
3741
|
-
for (const file of statusResult.details?.uncommittedFiles || []) {
|
|
3742
|
-
status.info(` - ${file}`);
|
|
3743
|
-
}
|
|
3744
|
-
status.info("");
|
|
3745
|
-
throw new Error(
|
|
3746
|
-
"Please commit or stash your changes first.\nOr use --force to stash changes automatically."
|
|
3747
|
-
);
|
|
3748
|
-
}
|
|
3749
|
-
let remoteUrl;
|
|
3750
|
-
try {
|
|
3751
|
-
remoteUrl = (0, import_child_process2.execSync)("git remote get-url origin", {
|
|
3752
|
-
cwd,
|
|
3753
|
-
encoding: "utf-8"
|
|
3754
|
-
}).trim();
|
|
3755
|
-
} catch {
|
|
3756
|
-
throw new Error(
|
|
3757
|
-
'No git remote "origin" found.\nPlease configure a remote before migrating.'
|
|
3758
|
-
);
|
|
3758
|
+
// src/api/machine-settings.ts
|
|
3759
|
+
async function fetchProjectPath(config, projectId) {
|
|
3760
|
+
if (!config.device_id || !config.access_token) {
|
|
3761
|
+
return null;
|
|
3759
3762
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3763
|
+
const serverUrl = config.api_url || "https://episoda.dev";
|
|
3764
|
+
const url = `${serverUrl}/api/dev/machines/${config.device_id}/project-path/${projectId}`;
|
|
3762
3765
|
try {
|
|
3763
|
-
const
|
|
3764
|
-
|
|
3766
|
+
const response = await fetch(url, {
|
|
3767
|
+
method: "GET",
|
|
3765
3768
|
headers: {
|
|
3766
3769
|
"Authorization": `Bearer ${config.access_token}`,
|
|
3767
3770
|
"Content-Type": "application/json"
|
|
3768
3771
|
}
|
|
3769
3772
|
});
|
|
3770
|
-
if (response.ok) {
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
if (projectData) {
|
|
3774
|
-
workspaceSlug = projectData.workspace_slug || workspaceSlug;
|
|
3775
|
-
projectSlug = projectData.slug || projectSlug;
|
|
3773
|
+
if (!response.ok) {
|
|
3774
|
+
if (response.status === 404) {
|
|
3775
|
+
return null;
|
|
3776
3776
|
}
|
|
3777
|
+
console.debug(`[MachineSettings] fetchProjectPath failed: ${response.status} ${response.statusText}`);
|
|
3778
|
+
return null;
|
|
3777
3779
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
status.info(` Target path: ${targetPath}`);
|
|
3784
|
-
status.info(` Remote URL: ${remoteUrl}`);
|
|
3785
|
-
status.info(` Current branch: ${currentBranch}`);
|
|
3786
|
-
if (hasUncommitted) {
|
|
3787
|
-
status.warning(` Uncommitted changes will be stashed`);
|
|
3788
|
-
}
|
|
3789
|
-
status.info("");
|
|
3790
|
-
if (options.dryRun) {
|
|
3791
|
-
status.info("Dry run complete. No changes made.");
|
|
3792
|
-
status.info("Remove --dry-run to perform the migration.");
|
|
3793
|
-
return;
|
|
3780
|
+
const data = await response.json();
|
|
3781
|
+
return data.data?.path || null;
|
|
3782
|
+
} catch (error) {
|
|
3783
|
+
console.debug(`[MachineSettings] fetchProjectPath network error:`, error);
|
|
3784
|
+
return null;
|
|
3794
3785
|
}
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3786
|
+
}
|
|
3787
|
+
async function syncProjectPath(config, projectId, path13) {
|
|
3788
|
+
if (!config.device_id || !config.access_token) {
|
|
3789
|
+
return false;
|
|
3798
3790
|
}
|
|
3799
|
-
const
|
|
3791
|
+
const serverUrl = config.api_url || "https://episoda.dev";
|
|
3792
|
+
const url = `${serverUrl}/api/dev/machines/${config.device_id}/project-path/${projectId}`;
|
|
3800
3793
|
try {
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
url: remoteUrl,
|
|
3809
|
-
path: bareRepoPath
|
|
3810
|
-
});
|
|
3811
|
-
if (!cloneResult.success) {
|
|
3812
|
-
throw new Error(`Failed to clone: ${cloneResult.output}`);
|
|
3813
|
-
}
|
|
3814
|
-
status.success("\u2713 Bare repository cloned");
|
|
3815
|
-
status.info("Creating worktree configuration...");
|
|
3816
|
-
const episodaDir = path6.join(targetPath, ".episoda");
|
|
3817
|
-
fs5.mkdirSync(episodaDir, { recursive: true });
|
|
3818
|
-
const worktreeConfig = {
|
|
3819
|
-
projectId,
|
|
3820
|
-
workspaceSlug,
|
|
3821
|
-
projectSlug,
|
|
3822
|
-
bareRepoPath,
|
|
3823
|
-
worktreeMode: true,
|
|
3824
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3825
|
-
worktrees: []
|
|
3826
|
-
};
|
|
3827
|
-
fs5.writeFileSync(
|
|
3828
|
-
path6.join(episodaDir, "config.json"),
|
|
3829
|
-
JSON.stringify(worktreeConfig, null, 2),
|
|
3830
|
-
"utf-8"
|
|
3831
|
-
);
|
|
3832
|
-
status.info(`Creating worktree for branch "${currentBranch}"...`);
|
|
3833
|
-
const moduleUid = extractModuleUid(currentBranch) || "main";
|
|
3834
|
-
const worktreePath = path6.join(targetPath, moduleUid);
|
|
3835
|
-
const worktreeResult = await gitExecutor.execute({
|
|
3836
|
-
action: "worktree_add",
|
|
3837
|
-
path: worktreePath,
|
|
3838
|
-
branch: currentBranch,
|
|
3839
|
-
create: false
|
|
3840
|
-
}, { cwd: bareRepoPath });
|
|
3841
|
-
if (!worktreeResult.success) {
|
|
3842
|
-
throw new Error(`Failed to create worktree: ${worktreeResult.output}`);
|
|
3843
|
-
}
|
|
3844
|
-
status.success(`\u2713 Worktree created at ${worktreePath}`);
|
|
3845
|
-
worktreeConfig.worktrees.push({
|
|
3846
|
-
moduleUid,
|
|
3847
|
-
branchName: currentBranch,
|
|
3848
|
-
worktreePath,
|
|
3849
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3850
|
-
lastAccessed: (/* @__PURE__ */ new Date()).toISOString()
|
|
3851
|
-
});
|
|
3852
|
-
fs5.writeFileSync(
|
|
3853
|
-
path6.join(episodaDir, "config.json"),
|
|
3854
|
-
JSON.stringify(worktreeConfig, null, 2),
|
|
3855
|
-
"utf-8"
|
|
3856
|
-
);
|
|
3857
|
-
if (hasUncommitted && uncommittedFiles.length > 0) {
|
|
3858
|
-
status.info("Copying uncommitted changes to new worktree...");
|
|
3859
|
-
let copiedCount = 0;
|
|
3860
|
-
let failedCount = 0;
|
|
3861
|
-
for (const file of uncommittedFiles) {
|
|
3862
|
-
const sourcePath = path6.join(cwd, file);
|
|
3863
|
-
const destPath = path6.join(worktreePath, file);
|
|
3864
|
-
try {
|
|
3865
|
-
if (fs5.existsSync(sourcePath)) {
|
|
3866
|
-
const destDir = path6.dirname(destPath);
|
|
3867
|
-
if (!fs5.existsSync(destDir)) {
|
|
3868
|
-
fs5.mkdirSync(destDir, { recursive: true });
|
|
3869
|
-
}
|
|
3870
|
-
fs5.copyFileSync(sourcePath, destPath);
|
|
3871
|
-
copiedCount++;
|
|
3872
|
-
}
|
|
3873
|
-
} catch (copyError) {
|
|
3874
|
-
failedCount++;
|
|
3875
|
-
status.warning(`Could not copy: ${file}`);
|
|
3876
|
-
}
|
|
3877
|
-
}
|
|
3878
|
-
if (copiedCount > 0) {
|
|
3879
|
-
status.success(`\u2713 Copied ${copiedCount} uncommitted file(s)`);
|
|
3880
|
-
}
|
|
3881
|
-
if (failedCount > 0) {
|
|
3882
|
-
status.warning(`${failedCount} file(s) could not be copied`);
|
|
3883
|
-
status.info("You may need to copy some files manually from the original directory");
|
|
3884
|
-
}
|
|
3885
|
-
}
|
|
3886
|
-
addProject2(projectId, targetPath, {
|
|
3887
|
-
worktreeMode: true,
|
|
3888
|
-
bareRepoPath
|
|
3794
|
+
const response = await fetch(url, {
|
|
3795
|
+
method: "PUT",
|
|
3796
|
+
headers: {
|
|
3797
|
+
"Authorization": `Bearer ${config.access_token}`,
|
|
3798
|
+
"Content-Type": "application/json"
|
|
3799
|
+
},
|
|
3800
|
+
body: JSON.stringify({ path: path13 })
|
|
3889
3801
|
});
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
const apiUrl = config.api_url || "https://episoda.dev";
|
|
3893
|
-
const settingsResponse = await fetch(`${apiUrl}/api/projects/${projectId}/settings`, {
|
|
3894
|
-
method: "PATCH",
|
|
3895
|
-
headers: {
|
|
3896
|
-
"Authorization": `Bearer ${config.access_token}`,
|
|
3897
|
-
"Content-Type": "application/json"
|
|
3898
|
-
},
|
|
3899
|
-
body: JSON.stringify({
|
|
3900
|
-
local_project_path: worktreePath
|
|
3901
|
-
})
|
|
3902
|
-
});
|
|
3903
|
-
if (settingsResponse.ok) {
|
|
3904
|
-
status.success("\u2713 Project settings updated");
|
|
3905
|
-
} else {
|
|
3906
|
-
status.warning("Could not update project settings in database");
|
|
3907
|
-
}
|
|
3908
|
-
} catch {
|
|
3909
|
-
status.warning("Could not update project settings in database");
|
|
3802
|
+
if (!response.ok) {
|
|
3803
|
+
console.debug(`[MachineSettings] syncProjectPath failed: ${response.status} ${response.statusText}`);
|
|
3910
3804
|
}
|
|
3911
|
-
|
|
3912
|
-
const updatedConfig = {
|
|
3913
|
-
...config,
|
|
3914
|
-
project_settings: {
|
|
3915
|
-
...config.project_settings,
|
|
3916
|
-
local_project_path: worktreePath,
|
|
3917
|
-
cached_at: Date.now()
|
|
3918
|
-
}
|
|
3919
|
-
};
|
|
3920
|
-
await (0, import_core5.saveConfig)(updatedConfig);
|
|
3921
|
-
status.success("\u2713 Local config updated");
|
|
3922
|
-
} catch {
|
|
3923
|
-
status.warning("Could not update local config cache");
|
|
3924
|
-
}
|
|
3925
|
-
status.info("");
|
|
3926
|
-
status.success("Migration complete!");
|
|
3927
|
-
status.info("");
|
|
3928
|
-
status.info("New project structure:");
|
|
3929
|
-
status.info(` ${targetPath}/`);
|
|
3930
|
-
status.info(" \u251C\u2500\u2500 .bare/ # Git repository");
|
|
3931
|
-
status.info(" \u251C\u2500\u2500 .episoda/");
|
|
3932
|
-
status.info(" \u2502 \u2514\u2500\u2500 config.json");
|
|
3933
|
-
status.info(` \u2514\u2500\u2500 ${moduleUid}/ # Your current worktree`);
|
|
3934
|
-
status.info("");
|
|
3935
|
-
status.info("Next steps:");
|
|
3936
|
-
status.info(` 1. cd ${worktreePath}`);
|
|
3937
|
-
status.info(" 2. Continue working on your current branch");
|
|
3938
|
-
status.info(" 3. Use `episoda checkout EP###` for other modules");
|
|
3939
|
-
status.info("");
|
|
3940
|
-
status.info("Your original repository at:");
|
|
3941
|
-
status.info(` ${cwd}`);
|
|
3942
|
-
status.info("can be safely removed after verifying the migration.");
|
|
3943
|
-
status.info("");
|
|
3805
|
+
return response.ok;
|
|
3944
3806
|
} catch (error) {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
try {
|
|
3948
|
-
fs5.rmSync(targetPath, { recursive: true, force: true });
|
|
3949
|
-
status.info(`Removed ${targetPath}`);
|
|
3950
|
-
} catch {
|
|
3951
|
-
status.warning(`Could not remove ${targetPath}`);
|
|
3952
|
-
}
|
|
3953
|
-
} else if (targetExistedBefore) {
|
|
3954
|
-
status.info("Target directory existed before migration, only removing migration artifacts...");
|
|
3955
|
-
const bareRepoPath = path6.join(targetPath, ".bare");
|
|
3956
|
-
const episodaDir = path6.join(targetPath, ".episoda");
|
|
3957
|
-
if (fs5.existsSync(bareRepoPath)) {
|
|
3958
|
-
try {
|
|
3959
|
-
fs5.rmSync(bareRepoPath, { recursive: true, force: true });
|
|
3960
|
-
status.info("Removed .bare directory");
|
|
3961
|
-
} catch {
|
|
3962
|
-
status.warning("Could not remove .bare directory");
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
|
-
if (fs5.existsSync(episodaDir)) {
|
|
3966
|
-
try {
|
|
3967
|
-
const configPath = path6.join(episodaDir, "config.json");
|
|
3968
|
-
if (fs5.existsSync(configPath)) {
|
|
3969
|
-
const config2 = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
|
|
3970
|
-
if (config2.worktreeMode) {
|
|
3971
|
-
fs5.rmSync(episodaDir, { recursive: true, force: true });
|
|
3972
|
-
status.info("Removed .episoda directory");
|
|
3973
|
-
}
|
|
3974
|
-
}
|
|
3975
|
-
} catch {
|
|
3976
|
-
status.warning("Could not remove .episoda directory");
|
|
3977
|
-
}
|
|
3978
|
-
}
|
|
3979
|
-
}
|
|
3980
|
-
throw error;
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
function extractModuleUid(branchName) {
|
|
3984
|
-
const match = branchName.match(/ep(\d+)/i);
|
|
3985
|
-
if (match) {
|
|
3986
|
-
return `EP${match[1]}`;
|
|
3807
|
+
console.debug(`[MachineSettings] syncProjectPath network error:`, error);
|
|
3808
|
+
return false;
|
|
3987
3809
|
}
|
|
3988
|
-
return null;
|
|
3989
3810
|
}
|
|
3990
3811
|
|
|
3991
3812
|
// src/commands/dev.ts
|
|
3992
3813
|
var CONNECTION_MAX_RETRIES = 3;
|
|
3993
3814
|
function findGitRoot(startDir) {
|
|
3994
3815
|
try {
|
|
3995
|
-
const result = (0,
|
|
3816
|
+
const result = (0, import_child_process2.execSync)("git rev-parse --show-toplevel", {
|
|
3996
3817
|
cwd: startDir,
|
|
3997
3818
|
encoding: "utf-8",
|
|
3998
3819
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4004,7 +3825,7 @@ function findGitRoot(startDir) {
|
|
|
4004
3825
|
}
|
|
4005
3826
|
async function devCommand(options = {}) {
|
|
4006
3827
|
try {
|
|
4007
|
-
const config = await (0,
|
|
3828
|
+
const config = await (0, import_core4.loadConfig)();
|
|
4008
3829
|
if (!config || !config.access_token || !config.project_id) {
|
|
4009
3830
|
status.error("No authentication found. Please run:");
|
|
4010
3831
|
status.info("");
|
|
@@ -4046,59 +3867,33 @@ async function devCommand(options = {}) {
|
|
|
4046
3867
|
await new Promise((resolve4) => setTimeout(resolve4, 2e3));
|
|
4047
3868
|
}
|
|
4048
3869
|
}
|
|
4049
|
-
const serverUrl = config.api_url || process.env.EPISODA_API_URL || "https://episoda.dev";
|
|
4050
3870
|
let projectPath;
|
|
4051
|
-
const
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
projectPath = cachedSettings.local_project_path;
|
|
4056
|
-
status.debug(`Using cached project path: ${projectPath}`);
|
|
3871
|
+
const serverPath = await fetchProjectPath(config, config.project_id);
|
|
3872
|
+
if (serverPath && fs4.existsSync(serverPath)) {
|
|
3873
|
+
projectPath = serverPath;
|
|
3874
|
+
status.debug(`Using server-synced project path: ${projectPath}`);
|
|
4057
3875
|
} else {
|
|
4058
3876
|
const detectedRoot = findGitRoot(options.cwd || process.cwd());
|
|
4059
|
-
projectPath = detectedRoot ||
|
|
3877
|
+
projectPath = detectedRoot || path5.resolve(options.cwd || process.cwd());
|
|
4060
3878
|
if (detectedRoot) {
|
|
4061
3879
|
status.debug(`Detected project root: ${projectPath}`);
|
|
4062
3880
|
} else {
|
|
4063
3881
|
status.warning(`Could not detect git root, using: ${projectPath}`);
|
|
4064
3882
|
}
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
...cachedSettings,
|
|
4069
|
-
local_project_path: projectPath,
|
|
4070
|
-
cached_at: Date.now()
|
|
3883
|
+
syncProjectPath(config, config.project_id, projectPath).then((synced) => {
|
|
3884
|
+
if (synced) {
|
|
3885
|
+
status.debug("Project path synced to server");
|
|
4071
3886
|
}
|
|
4072
|
-
};
|
|
4073
|
-
await (0, import_core6.saveConfig)(updatedConfig);
|
|
4074
|
-
status.debug("Cached project settings locally");
|
|
4075
|
-
const settingsUrl = `${serverUrl}/api/projects/${config.project_id}/settings`;
|
|
4076
|
-
fetch(settingsUrl, {
|
|
4077
|
-
method: "PATCH",
|
|
4078
|
-
headers: {
|
|
4079
|
-
"Authorization": `Bearer ${config.access_token}`,
|
|
4080
|
-
"Content-Type": "application/json"
|
|
4081
|
-
},
|
|
4082
|
-
body: JSON.stringify({ local_project_path: projectPath })
|
|
4083
3887
|
}).catch(() => {
|
|
4084
3888
|
});
|
|
4085
3889
|
}
|
|
4086
|
-
const hasGitDir = fs6.existsSync(path7.join(projectPath, ".git"));
|
|
4087
3890
|
const isWorktree = await isWorktreeProject(projectPath);
|
|
4088
|
-
if (
|
|
3891
|
+
if (!isWorktree) {
|
|
3892
|
+
status.error("Not an Episoda project.");
|
|
4089
3893
|
status.info("");
|
|
4090
|
-
status.info("
|
|
4091
|
-
status.info("This is a one-time operation that enables multi-module development.");
|
|
3894
|
+
status.info("Use `episoda clone {workspace}/{project}` to set up a project.");
|
|
4092
3895
|
status.info("");
|
|
4093
|
-
|
|
4094
|
-
await migrateCommand({ cwd: projectPath, silent: false });
|
|
4095
|
-
status.success("Project migrated to worktree mode!");
|
|
4096
|
-
status.info("");
|
|
4097
|
-
} catch (error) {
|
|
4098
|
-
status.warning(`Migration skipped: ${error.message}`);
|
|
4099
|
-
status.info("You can run `episoda migrate` manually later.");
|
|
4100
|
-
status.info("");
|
|
4101
|
-
}
|
|
3896
|
+
process.exit(1);
|
|
4102
3897
|
}
|
|
4103
3898
|
let daemonPid = isDaemonRunning();
|
|
4104
3899
|
if (!daemonPid) {
|
|
@@ -4192,7 +3987,7 @@ async function runDevServer(command, cwd, autoRestart) {
|
|
|
4192
3987
|
let shuttingDown = false;
|
|
4193
3988
|
const startServer = () => {
|
|
4194
3989
|
status.info(`Starting dev server: ${command.join(" ")}`);
|
|
4195
|
-
devProcess = (0,
|
|
3990
|
+
devProcess = (0, import_child_process2.spawn)(command[0], command.slice(1), {
|
|
4196
3991
|
cwd,
|
|
4197
3992
|
stdio: ["inherit", "inherit", "inherit"],
|
|
4198
3993
|
shell: true
|
|
@@ -4245,33 +4040,33 @@ Received ${signal}, shutting down...`);
|
|
|
4245
4040
|
|
|
4246
4041
|
// src/commands/auth.ts
|
|
4247
4042
|
var os = __toESM(require("os"));
|
|
4248
|
-
var
|
|
4249
|
-
var
|
|
4250
|
-
var
|
|
4251
|
-
var
|
|
4043
|
+
var fs6 = __toESM(require("fs"));
|
|
4044
|
+
var path7 = __toESM(require("path"));
|
|
4045
|
+
var import_child_process4 = require("child_process");
|
|
4046
|
+
var import_core6 = __toESM(require_dist());
|
|
4252
4047
|
|
|
4253
4048
|
// src/daemon/machine-id.ts
|
|
4254
|
-
var
|
|
4255
|
-
var
|
|
4049
|
+
var fs5 = __toESM(require("fs"));
|
|
4050
|
+
var path6 = __toESM(require("path"));
|
|
4256
4051
|
var crypto2 = __toESM(require("crypto"));
|
|
4257
|
-
var
|
|
4258
|
-
var
|
|
4052
|
+
var import_child_process3 = require("child_process");
|
|
4053
|
+
var import_core5 = __toESM(require_dist());
|
|
4259
4054
|
function isValidUUID(str) {
|
|
4260
4055
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4261
4056
|
return uuidRegex.test(str);
|
|
4262
4057
|
}
|
|
4263
4058
|
async function getMachineId() {
|
|
4264
|
-
const machineIdPath =
|
|
4059
|
+
const machineIdPath = path6.join((0, import_core5.getConfigDir)(), "machine-id");
|
|
4265
4060
|
try {
|
|
4266
|
-
if (
|
|
4267
|
-
const existingId =
|
|
4061
|
+
if (fs5.existsSync(machineIdPath)) {
|
|
4062
|
+
const existingId = fs5.readFileSync(machineIdPath, "utf-8").trim();
|
|
4268
4063
|
if (existingId) {
|
|
4269
4064
|
if (isValidUUID(existingId)) {
|
|
4270
4065
|
return existingId;
|
|
4271
4066
|
}
|
|
4272
4067
|
console.log("[MachineId] Migrating legacy machine ID to UUID format...");
|
|
4273
4068
|
const newUUID = generateMachineId();
|
|
4274
|
-
|
|
4069
|
+
fs5.writeFileSync(machineIdPath, newUUID, "utf-8");
|
|
4275
4070
|
console.log(`[MachineId] Migrated: ${existingId} \u2192 ${newUUID}`);
|
|
4276
4071
|
return newUUID;
|
|
4277
4072
|
}
|
|
@@ -4280,11 +4075,11 @@ async function getMachineId() {
|
|
|
4280
4075
|
}
|
|
4281
4076
|
const machineId = generateMachineId();
|
|
4282
4077
|
try {
|
|
4283
|
-
const dir =
|
|
4284
|
-
if (!
|
|
4285
|
-
|
|
4078
|
+
const dir = path6.dirname(machineIdPath);
|
|
4079
|
+
if (!fs5.existsSync(dir)) {
|
|
4080
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
4286
4081
|
}
|
|
4287
|
-
|
|
4082
|
+
fs5.writeFileSync(machineIdPath, machineId, "utf-8");
|
|
4288
4083
|
} catch (error) {
|
|
4289
4084
|
console.error("Warning: Could not save machine ID to disk:", error);
|
|
4290
4085
|
}
|
|
@@ -4293,7 +4088,7 @@ async function getMachineId() {
|
|
|
4293
4088
|
function getHardwareUUID() {
|
|
4294
4089
|
try {
|
|
4295
4090
|
if (process.platform === "darwin") {
|
|
4296
|
-
const output = (0,
|
|
4091
|
+
const output = (0, import_child_process3.execSync)(
|
|
4297
4092
|
`ioreg -d2 -c IOPlatformExpertDevice | awk -F\\" '/IOPlatformUUID/{print $(NF-1)}'`,
|
|
4298
4093
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
4299
4094
|
).trim();
|
|
@@ -4301,20 +4096,20 @@ function getHardwareUUID() {
|
|
|
4301
4096
|
return output;
|
|
4302
4097
|
}
|
|
4303
4098
|
} else if (process.platform === "linux") {
|
|
4304
|
-
if (
|
|
4305
|
-
const machineId =
|
|
4099
|
+
if (fs5.existsSync("/etc/machine-id")) {
|
|
4100
|
+
const machineId = fs5.readFileSync("/etc/machine-id", "utf-8").trim();
|
|
4306
4101
|
if (machineId && machineId.length > 0) {
|
|
4307
4102
|
return machineId;
|
|
4308
4103
|
}
|
|
4309
4104
|
}
|
|
4310
|
-
if (
|
|
4311
|
-
const dbusId =
|
|
4105
|
+
if (fs5.existsSync("/var/lib/dbus/machine-id")) {
|
|
4106
|
+
const dbusId = fs5.readFileSync("/var/lib/dbus/machine-id", "utf-8").trim();
|
|
4312
4107
|
if (dbusId && dbusId.length > 0) {
|
|
4313
4108
|
return dbusId;
|
|
4314
4109
|
}
|
|
4315
4110
|
}
|
|
4316
4111
|
} else if (process.platform === "win32") {
|
|
4317
|
-
const output = (0,
|
|
4112
|
+
const output = (0, import_child_process3.execSync)("wmic csproduct get uuid", {
|
|
4318
4113
|
encoding: "utf-8",
|
|
4319
4114
|
timeout: 5e3
|
|
4320
4115
|
});
|
|
@@ -4695,7 +4490,7 @@ async function authCommand(options = {}) {
|
|
|
4695
4490
|
status.info(` Project: ${tokenResponse.project_uid}`);
|
|
4696
4491
|
status.info(` Workspace: ${tokenResponse.workspace_uid}`);
|
|
4697
4492
|
status.info("");
|
|
4698
|
-
await (0,
|
|
4493
|
+
await (0, import_core6.saveConfig)({
|
|
4699
4494
|
project_id: tokenResponse.project_id,
|
|
4700
4495
|
user_id: tokenResponse.user_id,
|
|
4701
4496
|
workspace_id: tokenResponse.workspace_id,
|
|
@@ -4709,7 +4504,7 @@ async function authCommand(options = {}) {
|
|
|
4709
4504
|
// Convert to Unix timestamp
|
|
4710
4505
|
api_url: apiUrl
|
|
4711
4506
|
});
|
|
4712
|
-
status.success(`\u2713 Configuration saved to ${(0,
|
|
4507
|
+
status.success(`\u2713 Configuration saved to ${(0, import_core6.getConfigPath)()}`);
|
|
4713
4508
|
status.info("");
|
|
4714
4509
|
status.info("Installing git credential helper...");
|
|
4715
4510
|
const credentialHelperInstalled = await installGitCredentialHelper(apiUrl);
|
|
@@ -4753,7 +4548,7 @@ async function monitorAuthorization(apiUrl, deviceCode, expiresIn) {
|
|
|
4753
4548
|
resolve4(false);
|
|
4754
4549
|
}, expiresIn * 1e3);
|
|
4755
4550
|
const url = `${apiUrl}/api/oauth/authorize-stream?device_code=${deviceCode}`;
|
|
4756
|
-
const curlProcess = (0,
|
|
4551
|
+
const curlProcess = (0, import_child_process4.spawn)("curl", ["-N", url]);
|
|
4757
4552
|
let buffer = "";
|
|
4758
4553
|
curlProcess.stdout.on("data", (chunk) => {
|
|
4759
4554
|
buffer += chunk.toString();
|
|
@@ -4857,7 +4652,7 @@ function openBrowser(url) {
|
|
|
4857
4652
|
break;
|
|
4858
4653
|
}
|
|
4859
4654
|
try {
|
|
4860
|
-
(0,
|
|
4655
|
+
(0, import_child_process4.spawn)(command, args, {
|
|
4861
4656
|
detached: true,
|
|
4862
4657
|
stdio: "ignore"
|
|
4863
4658
|
}).unref();
|
|
@@ -4868,17 +4663,17 @@ function openBrowser(url) {
|
|
|
4868
4663
|
async function installGitCredentialHelper(apiUrl) {
|
|
4869
4664
|
try {
|
|
4870
4665
|
const homeDir = os.homedir();
|
|
4871
|
-
const episodaBinDir =
|
|
4872
|
-
const helperPath =
|
|
4873
|
-
|
|
4666
|
+
const episodaBinDir = path7.join(homeDir, ".episoda", "bin");
|
|
4667
|
+
const helperPath = path7.join(episodaBinDir, "git-credential-episoda");
|
|
4668
|
+
fs6.mkdirSync(episodaBinDir, { recursive: true });
|
|
4874
4669
|
const scriptContent = generateCredentialHelperScript(apiUrl);
|
|
4875
|
-
|
|
4670
|
+
fs6.writeFileSync(helperPath, scriptContent, { mode: 493 });
|
|
4876
4671
|
try {
|
|
4877
|
-
|
|
4672
|
+
fs6.accessSync(helperPath, fs6.constants.X_OK);
|
|
4878
4673
|
} catch {
|
|
4879
4674
|
}
|
|
4880
4675
|
try {
|
|
4881
|
-
const allHelpers = (0,
|
|
4676
|
+
const allHelpers = (0, import_child_process4.execSync)("git config --global --get-all credential.helper", {
|
|
4882
4677
|
encoding: "utf8",
|
|
4883
4678
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4884
4679
|
}).trim().split("\n");
|
|
@@ -4887,7 +4682,7 @@ async function installGitCredentialHelper(apiUrl) {
|
|
|
4887
4682
|
}
|
|
4888
4683
|
} catch {
|
|
4889
4684
|
}
|
|
4890
|
-
(0,
|
|
4685
|
+
(0, import_child_process4.execSync)(`git config --global --add credential.helper "${helperPath}"`, {
|
|
4891
4686
|
encoding: "utf8",
|
|
4892
4687
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4893
4688
|
});
|
|
@@ -4905,19 +4700,19 @@ function updateShellProfile(binDir) {
|
|
|
4905
4700
|
}
|
|
4906
4701
|
const homeDir = os.homedir();
|
|
4907
4702
|
const profiles = [
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4703
|
+
path7.join(homeDir, ".bashrc"),
|
|
4704
|
+
path7.join(homeDir, ".zshrc"),
|
|
4705
|
+
path7.join(homeDir, ".profile")
|
|
4911
4706
|
];
|
|
4912
4707
|
const exportLine = `export PATH="${binDir}:$PATH" # Added by episoda auth`;
|
|
4913
4708
|
for (const profile of profiles) {
|
|
4914
4709
|
try {
|
|
4915
|
-
if (
|
|
4916
|
-
const content =
|
|
4710
|
+
if (fs6.existsSync(profile)) {
|
|
4711
|
+
const content = fs6.readFileSync(profile, "utf8");
|
|
4917
4712
|
if (content.includes(".episoda/bin")) {
|
|
4918
4713
|
continue;
|
|
4919
4714
|
}
|
|
4920
|
-
|
|
4715
|
+
fs6.appendFileSync(profile, `
|
|
4921
4716
|
# Episoda CLI
|
|
4922
4717
|
${exportLine}
|
|
4923
4718
|
`);
|
|
@@ -4929,10 +4724,10 @@ ${exportLine}
|
|
|
4929
4724
|
|
|
4930
4725
|
// src/commands/connect.ts
|
|
4931
4726
|
var os2 = __toESM(require("os"));
|
|
4932
|
-
var
|
|
4933
|
-
var
|
|
4934
|
-
var
|
|
4935
|
-
var
|
|
4727
|
+
var fs7 = __toESM(require("fs"));
|
|
4728
|
+
var path8 = __toESM(require("path"));
|
|
4729
|
+
var import_child_process5 = require("child_process");
|
|
4730
|
+
var import_core7 = __toESM(require_dist());
|
|
4936
4731
|
async function connectCommand(options) {
|
|
4937
4732
|
const { code } = options;
|
|
4938
4733
|
const apiUrl = options.apiUrl || process.env.EPISODA_API_URL || "https://episoda.dev";
|
|
@@ -4948,14 +4743,14 @@ async function connectCommand(options) {
|
|
|
4948
4743
|
status.info(` Project: ${tokenResponse.project_uid}`);
|
|
4949
4744
|
status.info(` Workspace: ${tokenResponse.workspace_uid}`);
|
|
4950
4745
|
status.info("");
|
|
4951
|
-
await (0,
|
|
4746
|
+
await (0, import_core7.saveConfig)({
|
|
4952
4747
|
project_id: tokenResponse.project_id,
|
|
4953
4748
|
user_id: tokenResponse.user_id,
|
|
4954
4749
|
workspace_id: tokenResponse.workspace_id,
|
|
4955
4750
|
access_token: tokenResponse.access_token,
|
|
4956
4751
|
api_url: apiUrl
|
|
4957
4752
|
});
|
|
4958
|
-
status.success(`\u2713 Configuration saved to ${(0,
|
|
4753
|
+
status.success(`\u2713 Configuration saved to ${(0, import_core7.getConfigPath)()}`);
|
|
4959
4754
|
status.info("");
|
|
4960
4755
|
status.info("Installing git credential helper...");
|
|
4961
4756
|
const credentialHelperInstalled = await installGitCredentialHelper2(apiUrl);
|
|
@@ -5005,13 +4800,13 @@ async function exchangeUserCode(apiUrl, userCode, machineId) {
|
|
|
5005
4800
|
async function installGitCredentialHelper2(apiUrl) {
|
|
5006
4801
|
try {
|
|
5007
4802
|
const homeDir = os2.homedir();
|
|
5008
|
-
const episodaBinDir =
|
|
5009
|
-
const helperPath =
|
|
5010
|
-
|
|
4803
|
+
const episodaBinDir = path8.join(homeDir, ".episoda", "bin");
|
|
4804
|
+
const helperPath = path8.join(episodaBinDir, "git-credential-episoda");
|
|
4805
|
+
fs7.mkdirSync(episodaBinDir, { recursive: true });
|
|
5011
4806
|
const scriptContent = generateCredentialHelperScript(apiUrl);
|
|
5012
|
-
|
|
4807
|
+
fs7.writeFileSync(helperPath, scriptContent, { mode: 493 });
|
|
5013
4808
|
try {
|
|
5014
|
-
const allHelpers = (0,
|
|
4809
|
+
const allHelpers = (0, import_child_process5.execSync)("git config --global --get-all credential.helper", {
|
|
5015
4810
|
encoding: "utf8",
|
|
5016
4811
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5017
4812
|
}).trim().split("\n");
|
|
@@ -5020,7 +4815,7 @@ async function installGitCredentialHelper2(apiUrl) {
|
|
|
5020
4815
|
}
|
|
5021
4816
|
} catch {
|
|
5022
4817
|
}
|
|
5023
|
-
(0,
|
|
4818
|
+
(0, import_child_process5.execSync)(`git config --global --add credential.helper "${helperPath}"`, {
|
|
5024
4819
|
encoding: "utf8",
|
|
5025
4820
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5026
4821
|
});
|
|
@@ -5038,19 +4833,19 @@ function updateShellProfile2(binDir) {
|
|
|
5038
4833
|
}
|
|
5039
4834
|
const homeDir = os2.homedir();
|
|
5040
4835
|
const profiles = [
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
4836
|
+
path8.join(homeDir, ".bashrc"),
|
|
4837
|
+
path8.join(homeDir, ".zshrc"),
|
|
4838
|
+
path8.join(homeDir, ".profile")
|
|
5044
4839
|
];
|
|
5045
4840
|
const exportLine = `export PATH="${binDir}:$PATH" # Added by episoda`;
|
|
5046
4841
|
for (const profile of profiles) {
|
|
5047
4842
|
try {
|
|
5048
|
-
if (
|
|
5049
|
-
const content =
|
|
4843
|
+
if (fs7.existsSync(profile)) {
|
|
4844
|
+
const content = fs7.readFileSync(profile, "utf8");
|
|
5050
4845
|
if (content.includes(".episoda/bin")) {
|
|
5051
4846
|
continue;
|
|
5052
4847
|
}
|
|
5053
|
-
|
|
4848
|
+
fs7.appendFileSync(profile, `
|
|
5054
4849
|
# Episoda CLI
|
|
5055
4850
|
${exportLine}
|
|
5056
4851
|
`);
|
|
@@ -5061,11 +4856,11 @@ ${exportLine}
|
|
|
5061
4856
|
}
|
|
5062
4857
|
|
|
5063
4858
|
// src/commands/status.ts
|
|
5064
|
-
var
|
|
4859
|
+
var import_core8 = __toESM(require_dist());
|
|
5065
4860
|
async function statusCommand(options = {}) {
|
|
5066
4861
|
status.info("Checking CLI status...");
|
|
5067
4862
|
status.info("");
|
|
5068
|
-
const config = await (0,
|
|
4863
|
+
const config = await (0, import_core8.loadConfig)();
|
|
5069
4864
|
if (!config) {
|
|
5070
4865
|
status.error("\u2717 CLI not initialized");
|
|
5071
4866
|
status.info("");
|
|
@@ -5080,8 +4875,8 @@ async function statusCommand(options = {}) {
|
|
|
5080
4875
|
status.info("Configuration:");
|
|
5081
4876
|
status.info(` Project ID: ${config.project_id}`);
|
|
5082
4877
|
status.info(` API URL: ${config.api_url}`);
|
|
5083
|
-
status.info(` CLI Version: ${
|
|
5084
|
-
status.info(` Config file: ${(0,
|
|
4878
|
+
status.info(` CLI Version: ${import_core8.VERSION}`);
|
|
4879
|
+
status.info(` Config file: ${(0, import_core8.getConfigPath)()}`);
|
|
5085
4880
|
status.info("");
|
|
5086
4881
|
if (!config.access_token || config.access_token === "") {
|
|
5087
4882
|
status.warning("\u26A0 Not authenticated");
|
|
@@ -5241,9 +5036,83 @@ async function stopCommand(options = {}) {
|
|
|
5241
5036
|
}
|
|
5242
5037
|
|
|
5243
5038
|
// src/commands/clone.ts
|
|
5244
|
-
var
|
|
5245
|
-
var
|
|
5246
|
-
var
|
|
5039
|
+
var fs9 = __toESM(require("fs"));
|
|
5040
|
+
var path10 = __toESM(require("path"));
|
|
5041
|
+
var import_child_process6 = require("child_process");
|
|
5042
|
+
var import_core10 = __toESM(require_dist());
|
|
5043
|
+
|
|
5044
|
+
// src/daemon/project-tracker.ts
|
|
5045
|
+
var fs8 = __toESM(require("fs"));
|
|
5046
|
+
var path9 = __toESM(require("path"));
|
|
5047
|
+
var import_core9 = __toESM(require_dist());
|
|
5048
|
+
function getProjectsFilePath() {
|
|
5049
|
+
return path9.join((0, import_core9.getConfigDir)(), "projects.json");
|
|
5050
|
+
}
|
|
5051
|
+
function readProjects() {
|
|
5052
|
+
const projectsPath = getProjectsFilePath();
|
|
5053
|
+
try {
|
|
5054
|
+
if (!fs8.existsSync(projectsPath)) {
|
|
5055
|
+
return { projects: [] };
|
|
5056
|
+
}
|
|
5057
|
+
const content = fs8.readFileSync(projectsPath, "utf-8");
|
|
5058
|
+
const data = JSON.parse(content);
|
|
5059
|
+
if (!data.projects || !Array.isArray(data.projects)) {
|
|
5060
|
+
console.warn("Invalid projects.json structure, resetting");
|
|
5061
|
+
return { projects: [] };
|
|
5062
|
+
}
|
|
5063
|
+
return data;
|
|
5064
|
+
} catch (error) {
|
|
5065
|
+
console.error("Error reading projects.json:", error);
|
|
5066
|
+
return { projects: [] };
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
function writeProjects(data) {
|
|
5070
|
+
const projectsPath = getProjectsFilePath();
|
|
5071
|
+
try {
|
|
5072
|
+
const dir = path9.dirname(projectsPath);
|
|
5073
|
+
if (!fs8.existsSync(dir)) {
|
|
5074
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
5075
|
+
}
|
|
5076
|
+
fs8.writeFileSync(projectsPath, JSON.stringify(data, null, 2), "utf-8");
|
|
5077
|
+
} catch (error) {
|
|
5078
|
+
throw new Error(`Failed to write projects.json: ${error}`);
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
function addProject2(projectId, projectPath, options) {
|
|
5082
|
+
const data = readProjects();
|
|
5083
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5084
|
+
const existingByPath = data.projects.find((p) => p.path === projectPath);
|
|
5085
|
+
if (existingByPath) {
|
|
5086
|
+
existingByPath.id = projectId;
|
|
5087
|
+
existingByPath.last_active = now;
|
|
5088
|
+
if (options?.bareRepoPath) {
|
|
5089
|
+
existingByPath.bareRepoPath = options.bareRepoPath;
|
|
5090
|
+
}
|
|
5091
|
+
writeProjects(data);
|
|
5092
|
+
return existingByPath;
|
|
5093
|
+
}
|
|
5094
|
+
const existingByIdIndex = data.projects.findIndex((p) => p.id === projectId);
|
|
5095
|
+
if (existingByIdIndex !== -1) {
|
|
5096
|
+
const existingById = data.projects[existingByIdIndex];
|
|
5097
|
+
console.log(`[ProjectTracker] Replacing project entry: ${existingById.path} -> ${projectPath}`);
|
|
5098
|
+
data.projects.splice(existingByIdIndex, 1);
|
|
5099
|
+
}
|
|
5100
|
+
const projectName = path9.basename(projectPath);
|
|
5101
|
+
const newProject = {
|
|
5102
|
+
id: projectId,
|
|
5103
|
+
path: projectPath,
|
|
5104
|
+
name: projectName,
|
|
5105
|
+
added_at: now,
|
|
5106
|
+
last_active: now,
|
|
5107
|
+
// EP971: Bare repo path for worktree architecture
|
|
5108
|
+
bareRepoPath: options?.bareRepoPath
|
|
5109
|
+
};
|
|
5110
|
+
data.projects.push(newProject);
|
|
5111
|
+
writeProjects(data);
|
|
5112
|
+
return newProject;
|
|
5113
|
+
}
|
|
5114
|
+
|
|
5115
|
+
// src/commands/clone.ts
|
|
5247
5116
|
async function cloneCommand(slugArg, options = {}) {
|
|
5248
5117
|
const slugParts = slugArg.split("/");
|
|
5249
5118
|
if (slugParts.length !== 2 || !slugParts[0] || !slugParts[1]) {
|
|
@@ -5254,7 +5123,7 @@ async function cloneCommand(slugArg, options = {}) {
|
|
|
5254
5123
|
const [workspaceSlug, projectSlug] = slugParts;
|
|
5255
5124
|
status.info(`Cloning ${workspaceSlug}/${projectSlug}...`);
|
|
5256
5125
|
status.info("");
|
|
5257
|
-
const config = await (0,
|
|
5126
|
+
const config = await (0, import_core10.loadConfig)();
|
|
5258
5127
|
if (!config || !config.access_token) {
|
|
5259
5128
|
throw new Error(
|
|
5260
5129
|
"Not authenticated. Please run `episoda auth` first."
|
|
@@ -5262,9 +5131,9 @@ async function cloneCommand(slugArg, options = {}) {
|
|
|
5262
5131
|
}
|
|
5263
5132
|
const apiUrl = options.apiUrl || config.api_url || "https://episoda.dev";
|
|
5264
5133
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
5265
|
-
if (
|
|
5266
|
-
const bareRepoPath =
|
|
5267
|
-
if (
|
|
5134
|
+
if (fs9.existsSync(projectPath)) {
|
|
5135
|
+
const bareRepoPath = path10.join(projectPath, ".bare");
|
|
5136
|
+
if (fs9.existsSync(bareRepoPath)) {
|
|
5268
5137
|
status.warning(`Project already cloned at ${projectPath}`);
|
|
5269
5138
|
status.info("");
|
|
5270
5139
|
status.info("Next steps:");
|
|
@@ -5273,7 +5142,7 @@ async function cloneCommand(slugArg, options = {}) {
|
|
|
5273
5142
|
return;
|
|
5274
5143
|
}
|
|
5275
5144
|
throw new Error(
|
|
5276
|
-
`Directory exists but is not
|
|
5145
|
+
`Directory exists but is not an Episoda project: ${projectPath}
|
|
5277
5146
|
Please remove it manually or use a different location.`
|
|
5278
5147
|
);
|
|
5279
5148
|
}
|
|
@@ -5296,7 +5165,7 @@ Please configure a repository in the project settings on episoda.dev.`
|
|
|
5296
5165
|
status.info("");
|
|
5297
5166
|
status.info("Creating project directory...");
|
|
5298
5167
|
const episodaRoot = getEpisodaRoot();
|
|
5299
|
-
|
|
5168
|
+
fs9.mkdirSync(projectPath, { recursive: true });
|
|
5300
5169
|
status.success(`\u2713 Created ${projectPath}`);
|
|
5301
5170
|
status.info("Cloning repository (bare)...");
|
|
5302
5171
|
try {
|
|
@@ -5310,8 +5179,10 @@ Please configure a repository in the project settings on episoda.dev.`
|
|
|
5310
5179
|
status.success("\u2713 Repository cloned");
|
|
5311
5180
|
status.info("");
|
|
5312
5181
|
const bareRepoPath = worktreeManager.getBareRepoPath();
|
|
5182
|
+
await extractBootstrapScripts(bareRepoPath, projectPath);
|
|
5183
|
+
status.success("\u2713 Bootstrap scripts extracted");
|
|
5184
|
+
status.info("");
|
|
5313
5185
|
addProject2(projectDetails.id, projectPath, {
|
|
5314
|
-
worktreeMode: true,
|
|
5315
5186
|
bareRepoPath
|
|
5316
5187
|
});
|
|
5317
5188
|
status.success("\u2713 Project registered with daemon");
|
|
@@ -5320,18 +5191,21 @@ Please configure a repository in the project settings on episoda.dev.`
|
|
|
5320
5191
|
status.info("");
|
|
5321
5192
|
status.info("Project structure:");
|
|
5322
5193
|
status.info(` ${projectPath}/`);
|
|
5323
|
-
status.info(" \u251C\u2500\u2500 .bare/
|
|
5194
|
+
status.info(" \u251C\u2500\u2500 .bare/ # Git repository");
|
|
5324
5195
|
status.info(" \u2514\u2500\u2500 .episoda/");
|
|
5325
|
-
status.info(" \
|
|
5196
|
+
status.info(" \u251C\u2500\u2500 config.json # Project config");
|
|
5197
|
+
status.info(" \u2514\u2500\u2500 scripts/");
|
|
5198
|
+
status.info(" \u2514\u2500\u2500 api-helper.sh # API authentication");
|
|
5326
5199
|
status.info("");
|
|
5327
5200
|
status.info("Next steps:");
|
|
5328
5201
|
status.info(` 1. cd ${projectPath}`);
|
|
5329
|
-
status.info(" 2. episoda
|
|
5202
|
+
status.info(" 2. source .episoda/scripts/api-helper.sh # Load API helpers");
|
|
5203
|
+
status.info(" 3. episoda checkout {moduleUid} # e.g., episoda checkout EP100");
|
|
5330
5204
|
status.info("");
|
|
5331
5205
|
} catch (error) {
|
|
5332
|
-
if (
|
|
5206
|
+
if (fs9.existsSync(projectPath)) {
|
|
5333
5207
|
try {
|
|
5334
|
-
|
|
5208
|
+
fs9.rmSync(projectPath, { recursive: true, force: true });
|
|
5335
5209
|
} catch {
|
|
5336
5210
|
}
|
|
5337
5211
|
}
|
|
@@ -5404,9 +5278,23 @@ ${errorBody}`
|
|
|
5404
5278
|
}
|
|
5405
5279
|
return data;
|
|
5406
5280
|
}
|
|
5281
|
+
async function extractBootstrapScripts(bareRepoPath, projectPath) {
|
|
5282
|
+
const scriptsDir = path10.join(projectPath, ".episoda", "scripts");
|
|
5283
|
+
fs9.mkdirSync(scriptsDir, { recursive: true });
|
|
5284
|
+
try {
|
|
5285
|
+
const scriptContent = (0, import_child_process6.execSync)(
|
|
5286
|
+
`git --git-dir="${bareRepoPath}" show main:scripts/api-helper.sh`,
|
|
5287
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5288
|
+
);
|
|
5289
|
+
const scriptPath = path10.join(scriptsDir, "api-helper.sh");
|
|
5290
|
+
fs9.writeFileSync(scriptPath, scriptContent, { mode: 493 });
|
|
5291
|
+
} catch (error) {
|
|
5292
|
+
console.log("[clone] Could not extract api-helper.sh:", error);
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5407
5295
|
|
|
5408
5296
|
// src/commands/checkout.ts
|
|
5409
|
-
var
|
|
5297
|
+
var import_core11 = __toESM(require_dist());
|
|
5410
5298
|
|
|
5411
5299
|
// src/utils/http.ts
|
|
5412
5300
|
async function fetchWithRetry(url, options, maxRetries = 3) {
|
|
@@ -5446,7 +5334,7 @@ async function checkoutCommand(moduleUid, options = {}) {
|
|
|
5446
5334
|
"Not in a worktree project.\nRun this command from within a project cloned with `episoda clone`.\nOr cd to ~/episoda/{workspace}/{project}/"
|
|
5447
5335
|
);
|
|
5448
5336
|
}
|
|
5449
|
-
const config = await (0,
|
|
5337
|
+
const config = await (0, import_core11.loadConfig)();
|
|
5450
5338
|
if (!config || !config.access_token) {
|
|
5451
5339
|
throw new Error(
|
|
5452
5340
|
"Not authenticated. Please run `episoda auth` first."
|
|
@@ -5485,8 +5373,8 @@ The .bare directory or .episoda/config.json may be missing.`
|
|
|
5485
5373
|
const branchName = moduleDetails.branch_name || `feature/${moduleUid.toLowerCase()}`;
|
|
5486
5374
|
let createBranch = !moduleDetails.branch_name || options.create;
|
|
5487
5375
|
if (createBranch && !moduleDetails.branch_name) {
|
|
5488
|
-
const { GitExecutor:
|
|
5489
|
-
const gitExecutor = new
|
|
5376
|
+
const { GitExecutor: GitExecutor4 } = await Promise.resolve().then(() => __toESM(require_dist()));
|
|
5377
|
+
const gitExecutor = new GitExecutor4();
|
|
5490
5378
|
const branchCheck = await gitExecutor.execute(
|
|
5491
5379
|
{ action: "branch_exists", branch: branchName },
|
|
5492
5380
|
{ cwd: worktreeManager.getBareRepoPath() }
|
|
@@ -5583,8 +5471,8 @@ async function updateModuleCheckout(apiUrl, moduleId, accessToken, branchName) {
|
|
|
5583
5471
|
}
|
|
5584
5472
|
|
|
5585
5473
|
// src/commands/release.ts
|
|
5586
|
-
var
|
|
5587
|
-
var
|
|
5474
|
+
var path11 = __toESM(require("path"));
|
|
5475
|
+
var import_core12 = __toESM(require_dist());
|
|
5588
5476
|
async function releaseCommand(moduleUid, options = {}) {
|
|
5589
5477
|
if (!moduleUid || !moduleUid.match(/^EP\d+$/)) {
|
|
5590
5478
|
throw new Error(
|
|
@@ -5630,12 +5518,24 @@ Commit or stash your changes first, or use --force to discard them.`
|
|
|
5630
5518
|
);
|
|
5631
5519
|
}
|
|
5632
5520
|
}
|
|
5633
|
-
const currentPath =
|
|
5521
|
+
const currentPath = path11.resolve(process.cwd());
|
|
5634
5522
|
if (currentPath.startsWith(existing.worktreePath)) {
|
|
5635
5523
|
status.warning("You are inside the worktree being released.");
|
|
5636
5524
|
status.info(`Please cd to ${projectRoot} first.`);
|
|
5637
5525
|
throw new Error("Cannot release worktree while inside it");
|
|
5638
5526
|
}
|
|
5527
|
+
const config = await (0, import_core12.loadConfig)();
|
|
5528
|
+
const cleanupScript = config?.project_settings?.worktree_cleanup_script;
|
|
5529
|
+
if (cleanupScript) {
|
|
5530
|
+
status.info("Running cleanup script...");
|
|
5531
|
+
const cleanupResult = await worktreeManager.runCleanupScript(moduleUid, cleanupScript);
|
|
5532
|
+
if (!cleanupResult.success) {
|
|
5533
|
+
status.warning(`Cleanup script failed: ${cleanupResult.error}`);
|
|
5534
|
+
status.info("Continuing with worktree removal...");
|
|
5535
|
+
} else {
|
|
5536
|
+
status.success("\u2713 Cleanup script completed");
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5639
5539
|
status.info("Removing worktree...");
|
|
5640
5540
|
const result = await worktreeManager.removeWorktree(moduleUid, options.force);
|
|
5641
5541
|
if (!result.success) {
|
|
@@ -5644,7 +5544,6 @@ Commit or stash your changes first, or use --force to discard them.`
|
|
|
5644
5544
|
status.success("\u2713 Worktree removed");
|
|
5645
5545
|
status.info("");
|
|
5646
5546
|
try {
|
|
5647
|
-
const config = await (0, import_core13.loadConfig)();
|
|
5648
5547
|
if (config?.access_token) {
|
|
5649
5548
|
const worktreeConfig = worktreeManager.getConfig();
|
|
5650
5549
|
if (worktreeConfig) {
|
|
@@ -5668,7 +5567,7 @@ Commit or stash your changes first, or use --force to discard them.`
|
|
|
5668
5567
|
status.info("");
|
|
5669
5568
|
}
|
|
5670
5569
|
async function checkUncommittedChanges(worktreePath) {
|
|
5671
|
-
const gitExecutor = new
|
|
5570
|
+
const gitExecutor = new import_core12.GitExecutor();
|
|
5672
5571
|
const result = await gitExecutor.execute(
|
|
5673
5572
|
{ action: "status" },
|
|
5674
5573
|
{ cwd: worktreePath }
|
|
@@ -5693,10 +5592,10 @@ async function updateModuleRelease(apiUrl, projectId, workspaceSlug, moduleUid,
|
|
|
5693
5592
|
}
|
|
5694
5593
|
|
|
5695
5594
|
// src/commands/list.ts
|
|
5696
|
-
var
|
|
5697
|
-
var
|
|
5595
|
+
var fs10 = __toESM(require("fs"));
|
|
5596
|
+
var path12 = __toESM(require("path"));
|
|
5698
5597
|
var import_chalk2 = __toESM(require("chalk"));
|
|
5699
|
-
var
|
|
5598
|
+
var import_core13 = __toESM(require_dist());
|
|
5700
5599
|
async function listCommand(subcommand, options = {}) {
|
|
5701
5600
|
if (subcommand === "worktrees" || subcommand === "wt") {
|
|
5702
5601
|
await listWorktrees(options);
|
|
@@ -5706,7 +5605,7 @@ async function listCommand(subcommand, options = {}) {
|
|
|
5706
5605
|
}
|
|
5707
5606
|
async function listProjects(options) {
|
|
5708
5607
|
const episodaRoot = getEpisodaRoot();
|
|
5709
|
-
if (!
|
|
5608
|
+
if (!fs10.existsSync(episodaRoot)) {
|
|
5710
5609
|
status.info("No projects cloned yet.");
|
|
5711
5610
|
status.info("");
|
|
5712
5611
|
status.info("Clone a project with:");
|
|
@@ -5714,12 +5613,12 @@ async function listProjects(options) {
|
|
|
5714
5613
|
return;
|
|
5715
5614
|
}
|
|
5716
5615
|
const projects = [];
|
|
5717
|
-
const workspaces =
|
|
5616
|
+
const workspaces = fs10.readdirSync(episodaRoot, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
5718
5617
|
for (const workspace of workspaces) {
|
|
5719
|
-
const workspacePath =
|
|
5720
|
-
const projectDirs =
|
|
5618
|
+
const workspacePath = path12.join(episodaRoot, workspace.name);
|
|
5619
|
+
const projectDirs = fs10.readdirSync(workspacePath, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
5721
5620
|
for (const projectDir of projectDirs) {
|
|
5722
|
-
const projectPath =
|
|
5621
|
+
const projectPath = path12.join(workspacePath, projectDir.name);
|
|
5723
5622
|
if (await isWorktreeProject(projectPath)) {
|
|
5724
5623
|
const manager = new WorktreeManager(projectPath);
|
|
5725
5624
|
await manager.initialize();
|
|
@@ -5784,7 +5683,7 @@ async function listWorktrees(options) {
|
|
|
5784
5683
|
status.info(" episoda checkout {moduleUid}");
|
|
5785
5684
|
return;
|
|
5786
5685
|
}
|
|
5787
|
-
const gitExecutor = new
|
|
5686
|
+
const gitExecutor = new import_core13.GitExecutor();
|
|
5788
5687
|
for (const wt of worktrees) {
|
|
5789
5688
|
console.log("");
|
|
5790
5689
|
let statusIndicator = import_chalk2.default.green("\u25CF");
|
|
@@ -5821,7 +5720,7 @@ async function listWorktrees(options) {
|
|
|
5821
5720
|
}
|
|
5822
5721
|
|
|
5823
5722
|
// src/index.ts
|
|
5824
|
-
import_commander.program.name("episoda").description("Episoda local development
|
|
5723
|
+
import_commander.program.name("episoda").description("Episoda CLI - local development with git worktree isolation").version(import_core14.VERSION);
|
|
5825
5724
|
import_commander.program.command("auth").description("Authenticate to Episoda via OAuth and configure CLI").option("--api-url <url>", "API URL (default: https://episoda.dev)").action(async (options) => {
|
|
5826
5725
|
try {
|
|
5827
5726
|
await authCommand(options);
|
|
@@ -5838,7 +5737,7 @@ import_commander.program.command("connect").description("Connect using a pre-aut
|
|
|
5838
5737
|
process.exit(1);
|
|
5839
5738
|
}
|
|
5840
5739
|
});
|
|
5841
|
-
import_commander.program.command("dev").description("
|
|
5740
|
+
import_commander.program.command("dev").description("Connect to Episoda and start dev server").option("--auto-restart", "Auto-restart dev server if it crashes").option("--connection-only", "Connection-only mode (don't start dev server)").allowUnknownOption().action(async (options, cmd) => {
|
|
5842
5741
|
try {
|
|
5843
5742
|
const args = process.argv.slice(process.argv.indexOf("dev") + 1);
|
|
5844
5743
|
const separatorIndex = args.indexOf("--");
|
|
@@ -5877,7 +5776,7 @@ import_commander.program.command("disconnect").description("Disconnect from epis
|
|
|
5877
5776
|
process.exit(1);
|
|
5878
5777
|
}
|
|
5879
5778
|
});
|
|
5880
|
-
import_commander.program.command("clone").description("Clone a project
|
|
5779
|
+
import_commander.program.command("clone").description("Clone a project for multi-module development").argument("<workspace/project>", "Workspace and project slug (e.g., my-team/my-project)").option("--api-url <url>", "API URL (default: https://episoda.dev)").action(async (slug, options) => {
|
|
5881
5780
|
try {
|
|
5882
5781
|
await cloneCommand(slug, options);
|
|
5883
5782
|
} catch (error) {
|
|
@@ -5909,14 +5808,6 @@ import_commander.program.command("list").description("List cloned projects and w
|
|
|
5909
5808
|
process.exit(1);
|
|
5910
5809
|
}
|
|
5911
5810
|
});
|
|
5912
|
-
import_commander.program.command("migrate").description("Convert existing project to worktree mode").option("--force", "Force migration even with uncommitted changes (auto-stash)").option("--dry-run", "Show what would be done without making changes").action(async (options) => {
|
|
5913
|
-
try {
|
|
5914
|
-
await migrateCommand(options);
|
|
5915
|
-
} catch (error) {
|
|
5916
|
-
status.error(`Migration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5917
|
-
process.exit(1);
|
|
5918
|
-
}
|
|
5919
|
-
});
|
|
5920
5811
|
import_commander.program.command("dev:restart").description("Restart the dev server for a module").argument("[moduleUid]", "Module UID (required)").action(async (moduleUid) => {
|
|
5921
5812
|
try {
|
|
5922
5813
|
if (!moduleUid) {
|