episoda 0.2.33 → 0.2.35
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 +337 -175
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +660 -65
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1549,15 +1549,15 @@ var require_git_executor = __commonJS({
|
|
|
1549
1549
|
try {
|
|
1550
1550
|
const { stdout: gitDir } = await execAsync("git rev-parse --git-dir", { cwd, timeout: 5e3 });
|
|
1551
1551
|
const gitDirPath = gitDir.trim();
|
|
1552
|
-
const
|
|
1552
|
+
const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1553
1553
|
const rebaseMergePath = `${gitDirPath}/rebase-merge`;
|
|
1554
1554
|
const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
|
|
1555
1555
|
try {
|
|
1556
|
-
await
|
|
1556
|
+
await fs15.access(rebaseMergePath);
|
|
1557
1557
|
inRebase = true;
|
|
1558
1558
|
} catch {
|
|
1559
1559
|
try {
|
|
1560
|
-
await
|
|
1560
|
+
await fs15.access(rebaseApplyPath);
|
|
1561
1561
|
inRebase = true;
|
|
1562
1562
|
} catch {
|
|
1563
1563
|
inRebase = false;
|
|
@@ -1611,9 +1611,9 @@ var require_git_executor = __commonJS({
|
|
|
1611
1611
|
error: validation.error || "UNKNOWN_ERROR"
|
|
1612
1612
|
};
|
|
1613
1613
|
}
|
|
1614
|
-
const
|
|
1614
|
+
const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1615
1615
|
try {
|
|
1616
|
-
await
|
|
1616
|
+
await fs15.access(command.path);
|
|
1617
1617
|
return {
|
|
1618
1618
|
success: false,
|
|
1619
1619
|
error: "WORKTREE_EXISTS",
|
|
@@ -1667,9 +1667,9 @@ var require_git_executor = __commonJS({
|
|
|
1667
1667
|
*/
|
|
1668
1668
|
async executeWorktreeRemove(command, cwd, options) {
|
|
1669
1669
|
try {
|
|
1670
|
-
const
|
|
1670
|
+
const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1671
1671
|
try {
|
|
1672
|
-
await
|
|
1672
|
+
await fs15.access(command.path);
|
|
1673
1673
|
} catch {
|
|
1674
1674
|
return {
|
|
1675
1675
|
success: false,
|
|
@@ -1822,10 +1822,10 @@ var require_git_executor = __commonJS({
|
|
|
1822
1822
|
*/
|
|
1823
1823
|
async executeCloneBare(command, options) {
|
|
1824
1824
|
try {
|
|
1825
|
-
const
|
|
1826
|
-
const
|
|
1825
|
+
const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1826
|
+
const path17 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1827
1827
|
try {
|
|
1828
|
-
await
|
|
1828
|
+
await fs15.access(command.path);
|
|
1829
1829
|
return {
|
|
1830
1830
|
success: false,
|
|
1831
1831
|
error: "BRANCH_ALREADY_EXISTS",
|
|
@@ -1834,9 +1834,9 @@ var require_git_executor = __commonJS({
|
|
|
1834
1834
|
};
|
|
1835
1835
|
} catch {
|
|
1836
1836
|
}
|
|
1837
|
-
const parentDir =
|
|
1837
|
+
const parentDir = path17.dirname(command.path);
|
|
1838
1838
|
try {
|
|
1839
|
-
await
|
|
1839
|
+
await fs15.mkdir(parentDir, { recursive: true });
|
|
1840
1840
|
} catch {
|
|
1841
1841
|
}
|
|
1842
1842
|
const { stdout, stderr } = await execAsync(
|
|
@@ -1879,22 +1879,22 @@ var require_git_executor = __commonJS({
|
|
|
1879
1879
|
*/
|
|
1880
1880
|
async executeProjectInfo(cwd, options) {
|
|
1881
1881
|
try {
|
|
1882
|
-
const
|
|
1883
|
-
const
|
|
1882
|
+
const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1883
|
+
const path17 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1884
1884
|
let currentPath = cwd;
|
|
1885
1885
|
let projectPath = cwd;
|
|
1886
1886
|
let bareRepoPath;
|
|
1887
1887
|
for (let i = 0; i < 10; i++) {
|
|
1888
|
-
const bareDir =
|
|
1889
|
-
const episodaDir =
|
|
1888
|
+
const bareDir = path17.join(currentPath, ".bare");
|
|
1889
|
+
const episodaDir = path17.join(currentPath, ".episoda");
|
|
1890
1890
|
try {
|
|
1891
|
-
await
|
|
1892
|
-
await
|
|
1891
|
+
await fs15.access(bareDir);
|
|
1892
|
+
await fs15.access(episodaDir);
|
|
1893
1893
|
projectPath = currentPath;
|
|
1894
1894
|
bareRepoPath = bareDir;
|
|
1895
1895
|
break;
|
|
1896
1896
|
} catch {
|
|
1897
|
-
const parentPath =
|
|
1897
|
+
const parentPath = path17.dirname(currentPath);
|
|
1898
1898
|
if (parentPath === currentPath) {
|
|
1899
1899
|
break;
|
|
1900
1900
|
}
|
|
@@ -2107,7 +2107,7 @@ var require_websocket_client = __commonJS({
|
|
|
2107
2107
|
clearTimeout(this.reconnectTimeout);
|
|
2108
2108
|
this.reconnectTimeout = void 0;
|
|
2109
2109
|
}
|
|
2110
|
-
return new Promise((
|
|
2110
|
+
return new Promise((resolve5, reject) => {
|
|
2111
2111
|
const connectionTimeout = setTimeout(() => {
|
|
2112
2112
|
if (this.ws) {
|
|
2113
2113
|
this.ws.terminate();
|
|
@@ -2134,7 +2134,7 @@ var require_websocket_client = __commonJS({
|
|
|
2134
2134
|
daemonPid: this.daemonPid
|
|
2135
2135
|
});
|
|
2136
2136
|
this.startHeartbeat();
|
|
2137
|
-
|
|
2137
|
+
resolve5();
|
|
2138
2138
|
});
|
|
2139
2139
|
this.ws.on("pong", () => {
|
|
2140
2140
|
if (this.heartbeatTimeoutTimer) {
|
|
@@ -2250,13 +2250,13 @@ var require_websocket_client = __commonJS({
|
|
|
2250
2250
|
if (!this.ws || !this.isConnected) {
|
|
2251
2251
|
throw new Error("WebSocket not connected");
|
|
2252
2252
|
}
|
|
2253
|
-
return new Promise((
|
|
2253
|
+
return new Promise((resolve5, reject) => {
|
|
2254
2254
|
this.ws.send(JSON.stringify(message), (error) => {
|
|
2255
2255
|
if (error) {
|
|
2256
2256
|
console.error("[EpisodaClient] Failed to send message:", error);
|
|
2257
2257
|
reject(error);
|
|
2258
2258
|
} else {
|
|
2259
|
-
|
|
2259
|
+
resolve5();
|
|
2260
2260
|
}
|
|
2261
2261
|
});
|
|
2262
2262
|
});
|
|
@@ -2485,34 +2485,34 @@ var require_auth = __commonJS({
|
|
|
2485
2485
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
2486
2486
|
exports2.getConfigDir = getConfigDir5;
|
|
2487
2487
|
exports2.getConfigPath = getConfigPath4;
|
|
2488
|
-
exports2.loadConfig =
|
|
2488
|
+
exports2.loadConfig = loadConfig9;
|
|
2489
2489
|
exports2.saveConfig = saveConfig3;
|
|
2490
2490
|
exports2.validateToken = validateToken;
|
|
2491
|
-
var
|
|
2492
|
-
var
|
|
2493
|
-
var
|
|
2491
|
+
var fs15 = __importStar(require("fs"));
|
|
2492
|
+
var path17 = __importStar(require("path"));
|
|
2493
|
+
var os4 = __importStar(require("os"));
|
|
2494
2494
|
var child_process_1 = require("child_process");
|
|
2495
2495
|
var DEFAULT_CONFIG_FILE = "config.json";
|
|
2496
2496
|
function getConfigDir5() {
|
|
2497
|
-
return process.env.EPISODA_CONFIG_DIR ||
|
|
2497
|
+
return process.env.EPISODA_CONFIG_DIR || path17.join(os4.homedir(), ".episoda");
|
|
2498
2498
|
}
|
|
2499
2499
|
function getConfigPath4(configPath) {
|
|
2500
2500
|
if (configPath) {
|
|
2501
2501
|
return configPath;
|
|
2502
2502
|
}
|
|
2503
|
-
return
|
|
2503
|
+
return path17.join(getConfigDir5(), DEFAULT_CONFIG_FILE);
|
|
2504
2504
|
}
|
|
2505
2505
|
function ensureConfigDir(configPath) {
|
|
2506
|
-
const dir =
|
|
2507
|
-
const isNew = !
|
|
2506
|
+
const dir = path17.dirname(configPath);
|
|
2507
|
+
const isNew = !fs15.existsSync(dir);
|
|
2508
2508
|
if (isNew) {
|
|
2509
|
-
|
|
2509
|
+
fs15.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2510
2510
|
}
|
|
2511
2511
|
if (process.platform === "darwin") {
|
|
2512
|
-
const nosyncPath =
|
|
2513
|
-
if (isNew || !
|
|
2512
|
+
const nosyncPath = path17.join(dir, ".nosync");
|
|
2513
|
+
if (isNew || !fs15.existsSync(nosyncPath)) {
|
|
2514
2514
|
try {
|
|
2515
|
-
|
|
2515
|
+
fs15.writeFileSync(nosyncPath, "", { mode: 384 });
|
|
2516
2516
|
(0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
|
|
2517
2517
|
stdio: "ignore",
|
|
2518
2518
|
timeout: 5e3
|
|
@@ -2522,13 +2522,13 @@ var require_auth = __commonJS({
|
|
|
2522
2522
|
}
|
|
2523
2523
|
}
|
|
2524
2524
|
}
|
|
2525
|
-
async function
|
|
2525
|
+
async function loadConfig9(configPath) {
|
|
2526
2526
|
const fullPath = getConfigPath4(configPath);
|
|
2527
|
-
if (!
|
|
2527
|
+
if (!fs15.existsSync(fullPath)) {
|
|
2528
2528
|
return null;
|
|
2529
2529
|
}
|
|
2530
2530
|
try {
|
|
2531
|
-
const content =
|
|
2531
|
+
const content = fs15.readFileSync(fullPath, "utf8");
|
|
2532
2532
|
const config = JSON.parse(content);
|
|
2533
2533
|
return config;
|
|
2534
2534
|
} catch (error) {
|
|
@@ -2541,7 +2541,7 @@ var require_auth = __commonJS({
|
|
|
2541
2541
|
ensureConfigDir(fullPath);
|
|
2542
2542
|
try {
|
|
2543
2543
|
const content = JSON.stringify(config, null, 2);
|
|
2544
|
-
|
|
2544
|
+
fs15.writeFileSync(fullPath, content, { mode: 384 });
|
|
2545
2545
|
} catch (error) {
|
|
2546
2546
|
throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
|
|
2547
2547
|
}
|
|
@@ -2652,7 +2652,7 @@ var require_dist = __commonJS({
|
|
|
2652
2652
|
|
|
2653
2653
|
// src/index.ts
|
|
2654
2654
|
var import_commander = require("commander");
|
|
2655
|
-
var
|
|
2655
|
+
var import_core18 = __toESM(require_dist());
|
|
2656
2656
|
|
|
2657
2657
|
// src/commands/dev.ts
|
|
2658
2658
|
var import_core4 = __toESM(require_dist());
|
|
@@ -2989,7 +2989,7 @@ async function startDaemon() {
|
|
|
2989
2989
|
const pid = child.pid;
|
|
2990
2990
|
const pidPath = getPidFilePath();
|
|
2991
2991
|
fs2.writeFileSync(pidPath, pid.toString(), "utf-8");
|
|
2992
|
-
await new Promise((
|
|
2992
|
+
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
2993
2993
|
const runningPid = isDaemonRunning();
|
|
2994
2994
|
if (!runningPid) {
|
|
2995
2995
|
throw new Error("Daemon failed to start");
|
|
@@ -3011,7 +3011,7 @@ async function stopDaemon(timeout = 5e3) {
|
|
|
3011
3011
|
while (Date.now() - startTime < timeout) {
|
|
3012
3012
|
try {
|
|
3013
3013
|
process.kill(pid, 0);
|
|
3014
|
-
await new Promise((
|
|
3014
|
+
await new Promise((resolve5) => setTimeout(resolve5, 100));
|
|
3015
3015
|
} catch (error) {
|
|
3016
3016
|
const pidPath2 = getPidFilePath();
|
|
3017
3017
|
if (fs2.existsSync(pidPath2)) {
|
|
@@ -3041,7 +3041,7 @@ var import_core2 = __toESM(require_dist());
|
|
|
3041
3041
|
var getSocketPath = () => path3.join((0, import_core2.getConfigDir)(), "daemon.sock");
|
|
3042
3042
|
var DEFAULT_TIMEOUT = 15e3;
|
|
3043
3043
|
async function sendCommand(command, params, timeout = DEFAULT_TIMEOUT) {
|
|
3044
|
-
return new Promise((
|
|
3044
|
+
return new Promise((resolve5, reject) => {
|
|
3045
3045
|
const socket = net.createConnection(getSocketPath());
|
|
3046
3046
|
const requestId = crypto.randomUUID();
|
|
3047
3047
|
let buffer = "";
|
|
@@ -3072,7 +3072,7 @@ async function sendCommand(command, params, timeout = DEFAULT_TIMEOUT) {
|
|
|
3072
3072
|
clearTimeout(timeoutHandle);
|
|
3073
3073
|
socket.end();
|
|
3074
3074
|
if (response.success) {
|
|
3075
|
-
|
|
3075
|
+
resolve5(response.data);
|
|
3076
3076
|
} else {
|
|
3077
3077
|
reject(new Error(response.error || "Command failed"));
|
|
3078
3078
|
}
|
|
@@ -3135,18 +3135,18 @@ var fs5 = __toESM(require("fs"));
|
|
|
3135
3135
|
// src/utils/port-check.ts
|
|
3136
3136
|
var net2 = __toESM(require("net"));
|
|
3137
3137
|
async function isPortInUse(port) {
|
|
3138
|
-
return new Promise((
|
|
3138
|
+
return new Promise((resolve5) => {
|
|
3139
3139
|
const server = net2.createServer();
|
|
3140
3140
|
server.once("error", (err) => {
|
|
3141
3141
|
if (err.code === "EADDRINUSE") {
|
|
3142
|
-
|
|
3142
|
+
resolve5(true);
|
|
3143
3143
|
} else {
|
|
3144
|
-
|
|
3144
|
+
resolve5(false);
|
|
3145
3145
|
}
|
|
3146
3146
|
});
|
|
3147
3147
|
server.once("listening", () => {
|
|
3148
3148
|
server.close();
|
|
3149
|
-
|
|
3149
|
+
resolve5(false);
|
|
3150
3150
|
});
|
|
3151
3151
|
server.listen(port);
|
|
3152
3152
|
});
|
|
@@ -3518,7 +3518,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3518
3518
|
const lockContent = fs3.readFileSync(lockPath, "utf-8").trim();
|
|
3519
3519
|
const lockPid = parseInt(lockContent, 10);
|
|
3520
3520
|
if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
|
|
3521
|
-
await new Promise((
|
|
3521
|
+
await new Promise((resolve5) => setTimeout(resolve5, retryInterval));
|
|
3522
3522
|
continue;
|
|
3523
3523
|
}
|
|
3524
3524
|
} catch {
|
|
@@ -3532,7 +3532,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3532
3532
|
} catch {
|
|
3533
3533
|
continue;
|
|
3534
3534
|
}
|
|
3535
|
-
await new Promise((
|
|
3535
|
+
await new Promise((resolve5) => setTimeout(resolve5, retryInterval));
|
|
3536
3536
|
continue;
|
|
3537
3537
|
}
|
|
3538
3538
|
throw err;
|
|
@@ -3799,7 +3799,7 @@ async function fetchProjectPath(config, projectId) {
|
|
|
3799
3799
|
return null;
|
|
3800
3800
|
}
|
|
3801
3801
|
}
|
|
3802
|
-
async function syncProjectPath(config, projectId,
|
|
3802
|
+
async function syncProjectPath(config, projectId, path17) {
|
|
3803
3803
|
if (!config.device_id || !config.access_token) {
|
|
3804
3804
|
return false;
|
|
3805
3805
|
}
|
|
@@ -3812,7 +3812,7 @@ async function syncProjectPath(config, projectId, path15) {
|
|
|
3812
3812
|
"Authorization": `Bearer ${config.access_token}`,
|
|
3813
3813
|
"Content-Type": "application/json"
|
|
3814
3814
|
},
|
|
3815
|
-
body: JSON.stringify({ path:
|
|
3815
|
+
body: JSON.stringify({ path: path17 })
|
|
3816
3816
|
});
|
|
3817
3817
|
if (!response.ok) {
|
|
3818
3818
|
console.debug(`[MachineSettings] syncProjectPath failed: ${response.status} ${response.statusText}`);
|
|
@@ -3903,7 +3903,7 @@ async function devCommand(options = {}) {
|
|
|
3903
3903
|
const killedCount = killAllEpisodaProcesses();
|
|
3904
3904
|
if (killedCount > 0) {
|
|
3905
3905
|
status.info(`Cleaned up ${killedCount} stale process${killedCount > 1 ? "es" : ""}`);
|
|
3906
|
-
await new Promise((
|
|
3906
|
+
await new Promise((resolve5) => setTimeout(resolve5, 2e3));
|
|
3907
3907
|
}
|
|
3908
3908
|
}
|
|
3909
3909
|
let projectPath;
|
|
@@ -3963,7 +3963,7 @@ async function devCommand(options = {}) {
|
|
|
3963
3963
|
for (let retry = 0; retry < CONNECTION_MAX_RETRIES && !connected; retry++) {
|
|
3964
3964
|
if (retry > 0) {
|
|
3965
3965
|
status.info(`Retrying connection (attempt ${retry + 1}/${CONNECTION_MAX_RETRIES})...`);
|
|
3966
|
-
await new Promise((
|
|
3966
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
3967
3967
|
}
|
|
3968
3968
|
try {
|
|
3969
3969
|
const result = await addProject(config.project_id, projectPath);
|
|
@@ -4586,10 +4586,10 @@ async function initiateDeviceFlow(apiUrl, machineId) {
|
|
|
4586
4586
|
return await response.json();
|
|
4587
4587
|
}
|
|
4588
4588
|
async function monitorAuthorization(apiUrl, deviceCode, expiresIn) {
|
|
4589
|
-
return new Promise((
|
|
4589
|
+
return new Promise((resolve5) => {
|
|
4590
4590
|
const timeout = setTimeout(() => {
|
|
4591
4591
|
status.error("Authorization timed out");
|
|
4592
|
-
|
|
4592
|
+
resolve5(false);
|
|
4593
4593
|
}, expiresIn * 1e3);
|
|
4594
4594
|
const url = `${apiUrl}/api/oauth/authorize-stream?device_code=${deviceCode}`;
|
|
4595
4595
|
const curlProcess = (0, import_child_process5.spawn)("curl", ["-N", url]);
|
|
@@ -4615,26 +4615,26 @@ async function monitorAuthorization(apiUrl, deviceCode, expiresIn) {
|
|
|
4615
4615
|
if (eventType === "authorized") {
|
|
4616
4616
|
clearTimeout(timeout);
|
|
4617
4617
|
curlProcess.kill();
|
|
4618
|
-
|
|
4618
|
+
resolve5(true);
|
|
4619
4619
|
return;
|
|
4620
4620
|
} else if (eventType === "denied") {
|
|
4621
4621
|
clearTimeout(timeout);
|
|
4622
4622
|
curlProcess.kill();
|
|
4623
4623
|
status.error("Authorization denied by user");
|
|
4624
|
-
|
|
4624
|
+
resolve5(false);
|
|
4625
4625
|
return;
|
|
4626
4626
|
} else if (eventType === "expired") {
|
|
4627
4627
|
clearTimeout(timeout);
|
|
4628
4628
|
curlProcess.kill();
|
|
4629
4629
|
status.error("Authorization code expired");
|
|
4630
|
-
|
|
4630
|
+
resolve5(false);
|
|
4631
4631
|
return;
|
|
4632
4632
|
} else if (eventType === "error") {
|
|
4633
4633
|
const errorData = JSON.parse(data);
|
|
4634
4634
|
clearTimeout(timeout);
|
|
4635
4635
|
curlProcess.kill();
|
|
4636
4636
|
status.error(`Authorization error: ${errorData.error_description || errorData.error || "Unknown error"}`);
|
|
4637
|
-
|
|
4637
|
+
resolve5(false);
|
|
4638
4638
|
return;
|
|
4639
4639
|
}
|
|
4640
4640
|
} catch (error) {
|
|
@@ -4644,13 +4644,13 @@ async function monitorAuthorization(apiUrl, deviceCode, expiresIn) {
|
|
|
4644
4644
|
curlProcess.on("error", (error) => {
|
|
4645
4645
|
clearTimeout(timeout);
|
|
4646
4646
|
status.error(`Failed to monitor authorization: ${error.message}`);
|
|
4647
|
-
|
|
4647
|
+
resolve5(false);
|
|
4648
4648
|
});
|
|
4649
4649
|
curlProcess.on("close", (code) => {
|
|
4650
4650
|
clearTimeout(timeout);
|
|
4651
4651
|
if (code !== 0 && code !== null) {
|
|
4652
4652
|
status.error(`Authorization monitoring failed with code ${code}`);
|
|
4653
|
-
|
|
4653
|
+
resolve5(false);
|
|
4654
4654
|
}
|
|
4655
4655
|
});
|
|
4656
4656
|
});
|
|
@@ -5406,7 +5406,7 @@ async function fetchWithRetry(url, options, maxRetries = 3) {
|
|
|
5406
5406
|
lastError = error instanceof Error ? error : new Error("Network error");
|
|
5407
5407
|
}
|
|
5408
5408
|
if (attempt < maxRetries - 1) {
|
|
5409
|
-
await new Promise((
|
|
5409
|
+
await new Promise((resolve5) => setTimeout(resolve5, Math.pow(2, attempt) * 1e3));
|
|
5410
5410
|
}
|
|
5411
5411
|
}
|
|
5412
5412
|
throw lastError || new Error("Request failed after retries");
|
|
@@ -5989,7 +5989,7 @@ async function updateCommand(options = {}) {
|
|
|
5989
5989
|
}
|
|
5990
5990
|
if (options.restart && daemonWasRunning) {
|
|
5991
5991
|
status.info("Restarting daemon...");
|
|
5992
|
-
await new Promise((
|
|
5992
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
5993
5993
|
if (startDaemon2()) {
|
|
5994
5994
|
status.success("\u2713 Daemon restarted");
|
|
5995
5995
|
status.info("");
|
|
@@ -6010,8 +6010,534 @@ async function updateCommand(options = {}) {
|
|
|
6010
6010
|
}
|
|
6011
6011
|
}
|
|
6012
6012
|
|
|
6013
|
+
// src/commands/run.ts
|
|
6014
|
+
var import_child_process9 = require("child_process");
|
|
6015
|
+
var import_core15 = __toESM(require_dist());
|
|
6016
|
+
|
|
6017
|
+
// src/utils/env-cache.ts
|
|
6018
|
+
var fs13 = __toESM(require("fs"));
|
|
6019
|
+
var path15 = __toESM(require("path"));
|
|
6020
|
+
var os3 = __toESM(require("os"));
|
|
6021
|
+
var DEFAULT_CACHE_TTL = 60;
|
|
6022
|
+
var CACHE_DIR = path15.join(os3.homedir(), ".episoda", "cache");
|
|
6023
|
+
function getCacheFilePath(projectId) {
|
|
6024
|
+
return path15.join(CACHE_DIR, `env-vars-${projectId}.json`);
|
|
6025
|
+
}
|
|
6026
|
+
function ensureCacheDir() {
|
|
6027
|
+
if (!fs13.existsSync(CACHE_DIR)) {
|
|
6028
|
+
fs13.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
function readCache(projectId) {
|
|
6032
|
+
try {
|
|
6033
|
+
const cacheFile = getCacheFilePath(projectId);
|
|
6034
|
+
if (!fs13.existsSync(cacheFile)) {
|
|
6035
|
+
return null;
|
|
6036
|
+
}
|
|
6037
|
+
const content = fs13.readFileSync(cacheFile, "utf-8");
|
|
6038
|
+
const data = JSON.parse(content);
|
|
6039
|
+
if (!data.vars || typeof data.vars !== "object" || !data.fetchedAt) {
|
|
6040
|
+
return null;
|
|
6041
|
+
}
|
|
6042
|
+
return data;
|
|
6043
|
+
} catch {
|
|
6044
|
+
return null;
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
6047
|
+
function writeCache(projectId, vars) {
|
|
6048
|
+
try {
|
|
6049
|
+
ensureCacheDir();
|
|
6050
|
+
const cacheFile = getCacheFilePath(projectId);
|
|
6051
|
+
const data = {
|
|
6052
|
+
vars,
|
|
6053
|
+
fetchedAt: Date.now(),
|
|
6054
|
+
projectId
|
|
6055
|
+
};
|
|
6056
|
+
fs13.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
|
|
6057
|
+
} catch (error) {
|
|
6058
|
+
console.warn("[env-cache] Failed to write cache:", error instanceof Error ? error.message : error);
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
function isCacheValid(cache, ttlSeconds) {
|
|
6062
|
+
const ageMs = Date.now() - cache.fetchedAt;
|
|
6063
|
+
return ageMs < ttlSeconds * 1e3;
|
|
6064
|
+
}
|
|
6065
|
+
async function fetchEnvVarsWithCache(apiUrl, accessToken, options = {}) {
|
|
6066
|
+
const {
|
|
6067
|
+
noCache = false,
|
|
6068
|
+
cacheTtl = DEFAULT_CACHE_TTL,
|
|
6069
|
+
offline = false,
|
|
6070
|
+
projectId = "default"
|
|
6071
|
+
} = options;
|
|
6072
|
+
if (offline) {
|
|
6073
|
+
const cache = readCache(projectId);
|
|
6074
|
+
if (cache && Object.keys(cache.vars).length > 0) {
|
|
6075
|
+
return {
|
|
6076
|
+
envVars: cache.vars,
|
|
6077
|
+
fromCache: true,
|
|
6078
|
+
cacheAge: Date.now() - cache.fetchedAt
|
|
6079
|
+
};
|
|
6080
|
+
}
|
|
6081
|
+
throw new Error(
|
|
6082
|
+
"Offline mode requires cached env vars, but no cache found.\nRun without --offline first to populate the cache."
|
|
6083
|
+
);
|
|
6084
|
+
}
|
|
6085
|
+
if (!noCache) {
|
|
6086
|
+
const cache = readCache(projectId);
|
|
6087
|
+
if (cache && isCacheValid(cache, cacheTtl)) {
|
|
6088
|
+
return {
|
|
6089
|
+
envVars: cache.vars,
|
|
6090
|
+
fromCache: true,
|
|
6091
|
+
cacheAge: Date.now() - cache.fetchedAt
|
|
6092
|
+
};
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
try {
|
|
6096
|
+
const envVars = await fetchEnvVars(apiUrl, accessToken);
|
|
6097
|
+
if (Object.keys(envVars).length > 0) {
|
|
6098
|
+
writeCache(projectId, envVars);
|
|
6099
|
+
}
|
|
6100
|
+
return {
|
|
6101
|
+
envVars,
|
|
6102
|
+
fromCache: false
|
|
6103
|
+
};
|
|
6104
|
+
} catch (error) {
|
|
6105
|
+
const cache = readCache(projectId);
|
|
6106
|
+
if (cache && Object.keys(cache.vars).length > 0) {
|
|
6107
|
+
const cacheAge = Date.now() - cache.fetchedAt;
|
|
6108
|
+
console.warn(
|
|
6109
|
+
`[env-cache] Failed to fetch env vars, using stale cache (${Math.round(cacheAge / 1e3)}s old)`
|
|
6110
|
+
);
|
|
6111
|
+
return {
|
|
6112
|
+
envVars: cache.vars,
|
|
6113
|
+
fromCache: true,
|
|
6114
|
+
cacheAge
|
|
6115
|
+
};
|
|
6116
|
+
}
|
|
6117
|
+
throw new Error(
|
|
6118
|
+
`Failed to fetch environment variables: ${error instanceof Error ? error.message : error}
|
|
6119
|
+
No cached values available as fallback.`
|
|
6120
|
+
);
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
|
|
6124
|
+
// src/commands/run.ts
|
|
6125
|
+
async function runCommand(args, options = {}) {
|
|
6126
|
+
if (args.length === 0) {
|
|
6127
|
+
throw new Error(
|
|
6128
|
+
"No command specified.\n\nUsage:\n episoda run <command> [args...]\n episoda run -- <command> [args...]\n\nExamples:\n episoda run npm run dev\n episoda run -- npm run dev --port 3001\n episoda run python manage.py runserver"
|
|
6129
|
+
);
|
|
6130
|
+
}
|
|
6131
|
+
const config = await (0, import_core15.loadConfig)();
|
|
6132
|
+
if (!config || !config.access_token) {
|
|
6133
|
+
throw new Error(
|
|
6134
|
+
"Not authenticated. Please run `episoda auth` first."
|
|
6135
|
+
);
|
|
6136
|
+
}
|
|
6137
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
6138
|
+
const cacheOptions = {
|
|
6139
|
+
noCache: options.noCache,
|
|
6140
|
+
cacheTtl: options.cacheTtl,
|
|
6141
|
+
offline: options.offline,
|
|
6142
|
+
projectId: config.project_id
|
|
6143
|
+
};
|
|
6144
|
+
if (options.verbose) {
|
|
6145
|
+
status.info("Fetching environment variables...");
|
|
6146
|
+
}
|
|
6147
|
+
const { envVars, fromCache, cacheAge } = await fetchEnvVarsWithCache(
|
|
6148
|
+
apiUrl,
|
|
6149
|
+
config.access_token,
|
|
6150
|
+
cacheOptions
|
|
6151
|
+
);
|
|
6152
|
+
const varCount = Object.keys(envVars).length;
|
|
6153
|
+
if (varCount === 0) {
|
|
6154
|
+
status.warning("No environment variables found. Command will run with system env only.");
|
|
6155
|
+
} else if (options.verbose) {
|
|
6156
|
+
if (fromCache) {
|
|
6157
|
+
status.info(`Using ${varCount} cached env vars (${Math.round(cacheAge / 1e3)}s old)`);
|
|
6158
|
+
} else {
|
|
6159
|
+
status.info(`Fetched ${varCount} env vars from server`);
|
|
6160
|
+
}
|
|
6161
|
+
status.info(`Variables: ${Object.keys(envVars).join(", ")}`);
|
|
6162
|
+
}
|
|
6163
|
+
const mergedEnv = {
|
|
6164
|
+
...process.env,
|
|
6165
|
+
...envVars
|
|
6166
|
+
};
|
|
6167
|
+
const [command, ...commandArgs] = args;
|
|
6168
|
+
if (options.verbose) {
|
|
6169
|
+
status.info(`Running: ${command} ${commandArgs.join(" ")}`);
|
|
6170
|
+
status.info("");
|
|
6171
|
+
}
|
|
6172
|
+
const child = (0, import_child_process9.spawn)(command, commandArgs, {
|
|
6173
|
+
stdio: "inherit",
|
|
6174
|
+
env: mergedEnv,
|
|
6175
|
+
shell: process.platform === "win32"
|
|
6176
|
+
// Use shell on Windows for .cmd/.bat files
|
|
6177
|
+
});
|
|
6178
|
+
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
6179
|
+
const signalHandlers = /* @__PURE__ */ new Map();
|
|
6180
|
+
for (const signal of signals) {
|
|
6181
|
+
const handler = () => {
|
|
6182
|
+
if (child.pid) {
|
|
6183
|
+
child.kill(signal);
|
|
6184
|
+
}
|
|
6185
|
+
};
|
|
6186
|
+
signalHandlers.set(signal, handler);
|
|
6187
|
+
process.on(signal, handler);
|
|
6188
|
+
}
|
|
6189
|
+
child.on("error", (error) => {
|
|
6190
|
+
for (const [signal, handler] of signalHandlers) {
|
|
6191
|
+
process.removeListener(signal, handler);
|
|
6192
|
+
}
|
|
6193
|
+
if (error.code === "ENOENT") {
|
|
6194
|
+
status.error(`Command not found: ${command}`);
|
|
6195
|
+
process.exit(127);
|
|
6196
|
+
} else {
|
|
6197
|
+
status.error(`Failed to start command: ${error.message}`);
|
|
6198
|
+
process.exit(1);
|
|
6199
|
+
}
|
|
6200
|
+
});
|
|
6201
|
+
child.on("exit", (code, signal) => {
|
|
6202
|
+
for (const [sig, handler] of signalHandlers) {
|
|
6203
|
+
process.removeListener(sig, handler);
|
|
6204
|
+
}
|
|
6205
|
+
if (signal) {
|
|
6206
|
+
process.exit(128 + (signalToNumber(signal) || 0));
|
|
6207
|
+
} else {
|
|
6208
|
+
process.exit(code ?? 1);
|
|
6209
|
+
}
|
|
6210
|
+
});
|
|
6211
|
+
}
|
|
6212
|
+
function signalToNumber(signal) {
|
|
6213
|
+
const signalMap = {
|
|
6214
|
+
SIGHUP: 1,
|
|
6215
|
+
SIGINT: 2,
|
|
6216
|
+
SIGQUIT: 3,
|
|
6217
|
+
SIGTERM: 15
|
|
6218
|
+
};
|
|
6219
|
+
return signalMap[signal];
|
|
6220
|
+
}
|
|
6221
|
+
|
|
6222
|
+
// src/commands/shell.ts
|
|
6223
|
+
var import_child_process10 = require("child_process");
|
|
6224
|
+
var import_core16 = __toESM(require_dist());
|
|
6225
|
+
async function shellCommand(shell, options = {}) {
|
|
6226
|
+
const config = await (0, import_core16.loadConfig)();
|
|
6227
|
+
if (!config || !config.access_token) {
|
|
6228
|
+
throw new Error(
|
|
6229
|
+
"Not authenticated. Please run `episoda auth` first."
|
|
6230
|
+
);
|
|
6231
|
+
}
|
|
6232
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
6233
|
+
const cacheOptions = {
|
|
6234
|
+
noCache: options.noCache,
|
|
6235
|
+
cacheTtl: options.cacheTtl,
|
|
6236
|
+
offline: options.offline,
|
|
6237
|
+
projectId: config.project_id
|
|
6238
|
+
};
|
|
6239
|
+
status.info("Fetching environment variables...");
|
|
6240
|
+
const { envVars, fromCache, cacheAge } = await fetchEnvVarsWithCache(
|
|
6241
|
+
apiUrl,
|
|
6242
|
+
config.access_token,
|
|
6243
|
+
cacheOptions
|
|
6244
|
+
);
|
|
6245
|
+
const varCount = Object.keys(envVars).length;
|
|
6246
|
+
if (varCount === 0) {
|
|
6247
|
+
status.warning("No environment variables found.");
|
|
6248
|
+
} else if (fromCache) {
|
|
6249
|
+
status.info(`Using ${varCount} cached env vars (${Math.round(cacheAge / 1e3)}s old)`);
|
|
6250
|
+
} else {
|
|
6251
|
+
status.info(`Fetched ${varCount} env vars from server`);
|
|
6252
|
+
}
|
|
6253
|
+
if (options.print) {
|
|
6254
|
+
printExports(envVars);
|
|
6255
|
+
return;
|
|
6256
|
+
}
|
|
6257
|
+
const targetShell = shell || process.env.SHELL || "/bin/bash";
|
|
6258
|
+
status.info(`Opening ${targetShell} with ${varCount} environment variables...`);
|
|
6259
|
+
status.info('Type "exit" to return to your normal shell.');
|
|
6260
|
+
status.info("");
|
|
6261
|
+
const mergedEnv = {
|
|
6262
|
+
...process.env,
|
|
6263
|
+
...envVars,
|
|
6264
|
+
// Set a marker so users know they're in an episoda shell
|
|
6265
|
+
EPISODA_SHELL: "1"
|
|
6266
|
+
};
|
|
6267
|
+
const child = (0, import_child_process10.spawn)(targetShell, [], {
|
|
6268
|
+
stdio: "inherit",
|
|
6269
|
+
env: mergedEnv
|
|
6270
|
+
});
|
|
6271
|
+
child.on("error", (error) => {
|
|
6272
|
+
if (error.code === "ENOENT") {
|
|
6273
|
+
status.error(`Shell not found: ${targetShell}`);
|
|
6274
|
+
process.exit(127);
|
|
6275
|
+
} else {
|
|
6276
|
+
status.error(`Failed to start shell: ${error.message}`);
|
|
6277
|
+
process.exit(1);
|
|
6278
|
+
}
|
|
6279
|
+
});
|
|
6280
|
+
child.on("exit", (code) => {
|
|
6281
|
+
process.exit(code ?? 0);
|
|
6282
|
+
});
|
|
6283
|
+
}
|
|
6284
|
+
function printExports(envVars) {
|
|
6285
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
6286
|
+
const escapedValue = value.replace(/'/g, "'\\''");
|
|
6287
|
+
console.log(`export ${key}='${escapedValue}'`);
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
|
|
6291
|
+
// src/commands/env.ts
|
|
6292
|
+
var fs14 = __toESM(require("fs"));
|
|
6293
|
+
var path16 = __toESM(require("path"));
|
|
6294
|
+
var readline = __toESM(require("readline"));
|
|
6295
|
+
var import_core17 = __toESM(require_dist());
|
|
6296
|
+
async function envListCommand(options = {}) {
|
|
6297
|
+
const config = await (0, import_core17.loadConfig)();
|
|
6298
|
+
if (!config || !config.access_token) {
|
|
6299
|
+
throw new Error("Not authenticated. Please run `episoda auth` first.");
|
|
6300
|
+
}
|
|
6301
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
6302
|
+
const url = `${apiUrl}/api/projects/${config.project_id}/env-vars`;
|
|
6303
|
+
const response = await fetch(url, {
|
|
6304
|
+
headers: {
|
|
6305
|
+
"Authorization": `Bearer ${config.access_token}`,
|
|
6306
|
+
"Content-Type": "application/json"
|
|
6307
|
+
}
|
|
6308
|
+
});
|
|
6309
|
+
if (!response.ok) {
|
|
6310
|
+
throw new Error(`Failed to list env vars: ${response.status} ${response.statusText}`);
|
|
6311
|
+
}
|
|
6312
|
+
const data = await response.json();
|
|
6313
|
+
if (!data.success || !data.env_vars) {
|
|
6314
|
+
throw new Error("Failed to parse env vars response");
|
|
6315
|
+
}
|
|
6316
|
+
if (data.env_vars.length === 0) {
|
|
6317
|
+
status.info("No environment variables configured.");
|
|
6318
|
+
status.info("Add one with: episoda env set KEY=value");
|
|
6319
|
+
return;
|
|
6320
|
+
}
|
|
6321
|
+
console.log("\nEnvironment Variables:");
|
|
6322
|
+
console.log("\u2500".repeat(60));
|
|
6323
|
+
for (const envVar of data.env_vars) {
|
|
6324
|
+
const envBadge = envVar.environment === "all" ? "" : ` [${envVar.environment}]`;
|
|
6325
|
+
const sealed = envVar.is_sealed ? " (sealed)" : "";
|
|
6326
|
+
if (options.showValues) {
|
|
6327
|
+
const preview = envVar.value_preview || "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
6328
|
+
console.log(` ${envVar.key}${envBadge}${sealed} = ${preview}`);
|
|
6329
|
+
} else {
|
|
6330
|
+
console.log(` ${envVar.key}${envBadge}${sealed}`);
|
|
6331
|
+
}
|
|
6332
|
+
}
|
|
6333
|
+
console.log("");
|
|
6334
|
+
console.log(`Total: ${data.env_vars.length} variable(s)`);
|
|
6335
|
+
if (!options.showValues) {
|
|
6336
|
+
console.log("\nTip: Use --show-values to see value previews");
|
|
6337
|
+
}
|
|
6338
|
+
}
|
|
6339
|
+
async function envSetCommand(keyValue, options = {}) {
|
|
6340
|
+
const config = await (0, import_core17.loadConfig)();
|
|
6341
|
+
if (!config || !config.access_token) {
|
|
6342
|
+
throw new Error("Not authenticated. Please run `episoda auth` first.");
|
|
6343
|
+
}
|
|
6344
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
6345
|
+
let key;
|
|
6346
|
+
let value;
|
|
6347
|
+
const eqIndex = keyValue.indexOf("=");
|
|
6348
|
+
if (eqIndex === -1) {
|
|
6349
|
+
key = keyValue;
|
|
6350
|
+
value = await promptForValue(key);
|
|
6351
|
+
} else {
|
|
6352
|
+
key = keyValue.slice(0, eqIndex);
|
|
6353
|
+
value = keyValue.slice(eqIndex + 1);
|
|
6354
|
+
}
|
|
6355
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
|
|
6356
|
+
throw new Error(
|
|
6357
|
+
`Invalid key format: ${key}
|
|
6358
|
+
Keys must be uppercase with underscores (e.g., API_KEY, DATABASE_URL)`
|
|
6359
|
+
);
|
|
6360
|
+
}
|
|
6361
|
+
const url = `${apiUrl}/api/projects/${config.project_id}/env-vars`;
|
|
6362
|
+
const response = await fetch(url, {
|
|
6363
|
+
method: "POST",
|
|
6364
|
+
headers: {
|
|
6365
|
+
"Authorization": `Bearer ${config.access_token}`,
|
|
6366
|
+
"Content-Type": "application/json"
|
|
6367
|
+
},
|
|
6368
|
+
body: JSON.stringify({
|
|
6369
|
+
key,
|
|
6370
|
+
value,
|
|
6371
|
+
environment: options.environment || "dev"
|
|
6372
|
+
})
|
|
6373
|
+
});
|
|
6374
|
+
const data = await response.json();
|
|
6375
|
+
if (!response.ok) {
|
|
6376
|
+
if (response.status === 409) {
|
|
6377
|
+
status.info(`${key} already exists, updating...`);
|
|
6378
|
+
await updateEnvVar(apiUrl, config.access_token, config.project_id, key, value);
|
|
6379
|
+
return;
|
|
6380
|
+
}
|
|
6381
|
+
throw new Error(data.error?.message || `Failed to set env var: ${response.status}`);
|
|
6382
|
+
}
|
|
6383
|
+
status.success(`Set ${key} successfully`);
|
|
6384
|
+
}
|
|
6385
|
+
async function updateEnvVar(apiUrl, accessToken, projectId, key, value) {
|
|
6386
|
+
const listUrl = `${apiUrl}/api/projects/${projectId}/env-vars`;
|
|
6387
|
+
const listResponse = await fetch(listUrl, {
|
|
6388
|
+
headers: {
|
|
6389
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
6390
|
+
"Content-Type": "application/json"
|
|
6391
|
+
}
|
|
6392
|
+
});
|
|
6393
|
+
if (!listResponse.ok) {
|
|
6394
|
+
throw new Error("Failed to find existing env var for update");
|
|
6395
|
+
}
|
|
6396
|
+
const listData = await listResponse.json();
|
|
6397
|
+
const existingVar = listData.env_vars?.find((v) => v.key === key);
|
|
6398
|
+
if (!existingVar) {
|
|
6399
|
+
throw new Error(`Env var ${key} not found for update`);
|
|
6400
|
+
}
|
|
6401
|
+
const updateUrl = `${apiUrl}/api/projects/${projectId}/env-vars/${existingVar.id}`;
|
|
6402
|
+
const updateResponse = await fetch(updateUrl, {
|
|
6403
|
+
method: "PATCH",
|
|
6404
|
+
headers: {
|
|
6405
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
6406
|
+
"Content-Type": "application/json"
|
|
6407
|
+
},
|
|
6408
|
+
body: JSON.stringify({ value })
|
|
6409
|
+
});
|
|
6410
|
+
if (!updateResponse.ok) {
|
|
6411
|
+
throw new Error(`Failed to update env var: ${updateResponse.status}`);
|
|
6412
|
+
}
|
|
6413
|
+
status.success(`Updated ${key} successfully`);
|
|
6414
|
+
}
|
|
6415
|
+
async function envRemoveCommand(key) {
|
|
6416
|
+
const config = await (0, import_core17.loadConfig)();
|
|
6417
|
+
if (!config || !config.access_token) {
|
|
6418
|
+
throw new Error("Not authenticated. Please run `episoda auth` first.");
|
|
6419
|
+
}
|
|
6420
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
6421
|
+
const listUrl = `${apiUrl}/api/projects/${config.project_id}/env-vars`;
|
|
6422
|
+
const listResponse = await fetch(listUrl, {
|
|
6423
|
+
headers: {
|
|
6424
|
+
"Authorization": `Bearer ${config.access_token}`,
|
|
6425
|
+
"Content-Type": "application/json"
|
|
6426
|
+
}
|
|
6427
|
+
});
|
|
6428
|
+
if (!listResponse.ok) {
|
|
6429
|
+
throw new Error("Failed to list env vars");
|
|
6430
|
+
}
|
|
6431
|
+
const listData = await listResponse.json();
|
|
6432
|
+
const existingVar = listData.env_vars?.find((v) => v.key === key);
|
|
6433
|
+
if (!existingVar) {
|
|
6434
|
+
throw new Error(`Env var ${key} not found`);
|
|
6435
|
+
}
|
|
6436
|
+
const deleteUrl = `${apiUrl}/api/projects/${config.project_id}/env-vars/${existingVar.id}`;
|
|
6437
|
+
const deleteResponse = await fetch(deleteUrl, {
|
|
6438
|
+
method: "DELETE",
|
|
6439
|
+
headers: {
|
|
6440
|
+
"Authorization": `Bearer ${config.access_token}`
|
|
6441
|
+
}
|
|
6442
|
+
});
|
|
6443
|
+
if (!deleteResponse.ok) {
|
|
6444
|
+
throw new Error(`Failed to remove env var: ${deleteResponse.status}`);
|
|
6445
|
+
}
|
|
6446
|
+
status.success(`Removed ${key}`);
|
|
6447
|
+
}
|
|
6448
|
+
async function envPullCommand(options = {}) {
|
|
6449
|
+
const config = await (0, import_core17.loadConfig)();
|
|
6450
|
+
if (!config || !config.access_token) {
|
|
6451
|
+
throw new Error("Not authenticated. Please run `episoda auth` first.");
|
|
6452
|
+
}
|
|
6453
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
6454
|
+
status.info("Fetching environment variables...");
|
|
6455
|
+
const envVars = await fetchEnvVars(apiUrl, config.access_token);
|
|
6456
|
+
if (Object.keys(envVars).length === 0) {
|
|
6457
|
+
status.warning("No environment variables found.");
|
|
6458
|
+
return;
|
|
6459
|
+
}
|
|
6460
|
+
const header = [
|
|
6461
|
+
"# AUTO-GENERATED by episoda env pull",
|
|
6462
|
+
"# Do not edit - changes will be overwritten",
|
|
6463
|
+
`# Source of truth: ${apiUrl}/settings/env-vars`,
|
|
6464
|
+
`# Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
6465
|
+
"#",
|
|
6466
|
+
"# To update: episoda env set KEY=value",
|
|
6467
|
+
"# To refresh: episoda env pull",
|
|
6468
|
+
""
|
|
6469
|
+
].join("\n");
|
|
6470
|
+
const envContent = Object.entries(envVars).map(([key, value]) => {
|
|
6471
|
+
if (/[\s'"#$`\\]/.test(value) || value.includes("\n")) {
|
|
6472
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
6473
|
+
return `${key}="${escaped}"`;
|
|
6474
|
+
}
|
|
6475
|
+
return `${key}=${value}`;
|
|
6476
|
+
}).join("\n") + "\n";
|
|
6477
|
+
const fullContent = header + envContent;
|
|
6478
|
+
if (options.stdout) {
|
|
6479
|
+
console.log(fullContent);
|
|
6480
|
+
return;
|
|
6481
|
+
}
|
|
6482
|
+
const filename = options.file || ".env";
|
|
6483
|
+
const filepath = path16.resolve(process.cwd(), filename);
|
|
6484
|
+
fs14.writeFileSync(filepath, fullContent, { mode: 384 });
|
|
6485
|
+
status.success(`Wrote ${Object.keys(envVars).length} env vars to ${filename}`);
|
|
6486
|
+
}
|
|
6487
|
+
async function promptForValue(key) {
|
|
6488
|
+
if (!process.stdin.isTTY) {
|
|
6489
|
+
return new Promise((resolve5, reject) => {
|
|
6490
|
+
const rl = readline.createInterface({
|
|
6491
|
+
input: process.stdin,
|
|
6492
|
+
output: process.stdout
|
|
6493
|
+
});
|
|
6494
|
+
rl.question(`Enter value for ${key}: `, (answer) => {
|
|
6495
|
+
rl.close();
|
|
6496
|
+
resolve5(answer);
|
|
6497
|
+
});
|
|
6498
|
+
rl.on("close", () => {
|
|
6499
|
+
reject(new Error("Input closed"));
|
|
6500
|
+
});
|
|
6501
|
+
});
|
|
6502
|
+
}
|
|
6503
|
+
return new Promise((resolve5, reject) => {
|
|
6504
|
+
const rl = readline.createInterface({
|
|
6505
|
+
input: process.stdin,
|
|
6506
|
+
output: process.stdout
|
|
6507
|
+
});
|
|
6508
|
+
let value = "";
|
|
6509
|
+
let resolved = false;
|
|
6510
|
+
const cleanup = () => {
|
|
6511
|
+
if (process.stdin.isTTY) {
|
|
6512
|
+
process.stdin.setRawMode(false);
|
|
6513
|
+
}
|
|
6514
|
+
process.stdin.removeListener("data", dataHandler);
|
|
6515
|
+
rl.close();
|
|
6516
|
+
};
|
|
6517
|
+
const dataHandler = (char) => {
|
|
6518
|
+
const str = char.toString();
|
|
6519
|
+
if (str === "\n" || str === "\r" || str === "\r\n") {
|
|
6520
|
+
resolved = true;
|
|
6521
|
+
cleanup();
|
|
6522
|
+
console.log("");
|
|
6523
|
+
resolve5(value);
|
|
6524
|
+
} else if (str === "") {
|
|
6525
|
+
cleanup();
|
|
6526
|
+
reject(new Error("Cancelled"));
|
|
6527
|
+
} else if (str === "\x7F" || str === "\b") {
|
|
6528
|
+
value = value.slice(0, -1);
|
|
6529
|
+
} else {
|
|
6530
|
+
value += str;
|
|
6531
|
+
}
|
|
6532
|
+
};
|
|
6533
|
+
process.stdin.setRawMode(true);
|
|
6534
|
+
process.stdout.write(`Enter value for ${key}: `);
|
|
6535
|
+
process.stdin.on("data", dataHandler);
|
|
6536
|
+
});
|
|
6537
|
+
}
|
|
6538
|
+
|
|
6013
6539
|
// src/index.ts
|
|
6014
|
-
import_commander.program.name("episoda").description("Episoda CLI - local development with git worktree isolation").version(
|
|
6540
|
+
import_commander.program.name("episoda").description("Episoda CLI - local development with git worktree isolation").version(import_core18.VERSION);
|
|
6015
6541
|
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) => {
|
|
6016
6542
|
try {
|
|
6017
6543
|
await authCommand(options);
|
|
@@ -6167,5 +6693,74 @@ function formatUptime(seconds) {
|
|
|
6167
6693
|
const mins = Math.floor(seconds % 3600 / 60);
|
|
6168
6694
|
return `${hours}h ${mins}m`;
|
|
6169
6695
|
}
|
|
6696
|
+
import_commander.program.command("run").description("Run a command with environment variables injected from the database").argument("<command...>", "Command to run with injected env vars").option("--no-cache", "Force fresh fetch from server").option("--cache-ttl <seconds>", "Cache TTL in seconds (default: 60)", parseInt).option("--offline", "Use cached values only, no server call").option("--verbose", "Show fetched variable names").allowUnknownOption().action(async (commandArgs, options) => {
|
|
6697
|
+
try {
|
|
6698
|
+
const args = process.argv.slice(process.argv.indexOf("run") + 1);
|
|
6699
|
+
const separatorIndex = args.indexOf("--");
|
|
6700
|
+
let finalArgs;
|
|
6701
|
+
if (separatorIndex >= 0) {
|
|
6702
|
+
finalArgs = args.slice(separatorIndex + 1);
|
|
6703
|
+
} else {
|
|
6704
|
+
finalArgs = commandArgs.filter(
|
|
6705
|
+
(arg) => !arg.startsWith("--no-cache") && !arg.startsWith("--cache-ttl") && !arg.startsWith("--offline") && !arg.startsWith("--verbose")
|
|
6706
|
+
);
|
|
6707
|
+
}
|
|
6708
|
+
await runCommand(finalArgs, {
|
|
6709
|
+
noCache: options.cache === false,
|
|
6710
|
+
cacheTtl: options.cacheTtl,
|
|
6711
|
+
offline: options.offline,
|
|
6712
|
+
verbose: options.verbose
|
|
6713
|
+
});
|
|
6714
|
+
} catch (error) {
|
|
6715
|
+
status.error(`Run failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6716
|
+
process.exit(1);
|
|
6717
|
+
}
|
|
6718
|
+
});
|
|
6719
|
+
import_commander.program.command("shell").description("Open an interactive shell with environment variables injected").argument("[shell]", "Shell to use (default: $SHELL)").option("--print", "Print export statements instead of opening shell").option("--no-cache", "Force fresh fetch from server").option("--cache-ttl <seconds>", "Cache TTL in seconds (default: 60)", parseInt).option("--offline", "Use cached values only").action(async (shell, options) => {
|
|
6720
|
+
try {
|
|
6721
|
+
await shellCommand(shell, {
|
|
6722
|
+
print: options.print,
|
|
6723
|
+
noCache: options.cache === false,
|
|
6724
|
+
cacheTtl: options.cacheTtl,
|
|
6725
|
+
offline: options.offline
|
|
6726
|
+
});
|
|
6727
|
+
} catch (error) {
|
|
6728
|
+
status.error(`Shell failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6729
|
+
process.exit(1);
|
|
6730
|
+
}
|
|
6731
|
+
});
|
|
6732
|
+
var envCmd = import_commander.program.command("env").description("Manage environment variables");
|
|
6733
|
+
envCmd.command("list").description("List environment variables").option("--show-values", "Show value previews (masked)").action(async (options) => {
|
|
6734
|
+
try {
|
|
6735
|
+
await envListCommand({ showValues: options.showValues });
|
|
6736
|
+
} catch (error) {
|
|
6737
|
+
status.error(`Env list failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6738
|
+
process.exit(1);
|
|
6739
|
+
}
|
|
6740
|
+
});
|
|
6741
|
+
envCmd.command("set").description("Set an environment variable").argument("<key=value>", "KEY=value pair (omit value to prompt)").option("-e, --environment <env>", "Environment: all, dev, preview, prod (default: dev)").action(async (keyValue, options) => {
|
|
6742
|
+
try {
|
|
6743
|
+
await envSetCommand(keyValue, { environment: options.environment });
|
|
6744
|
+
} catch (error) {
|
|
6745
|
+
status.error(`Env set failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6746
|
+
process.exit(1);
|
|
6747
|
+
}
|
|
6748
|
+
});
|
|
6749
|
+
envCmd.command("remove").description("Remove an environment variable").argument("<key>", "Variable key to remove").action(async (key) => {
|
|
6750
|
+
try {
|
|
6751
|
+
await envRemoveCommand(key);
|
|
6752
|
+
} catch (error) {
|
|
6753
|
+
status.error(`Env remove failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6754
|
+
process.exit(1);
|
|
6755
|
+
}
|
|
6756
|
+
});
|
|
6757
|
+
envCmd.command("pull").description("Generate .env file from database (explicit file generation)").option("-f, --file <filename>", "Output filename (default: .env)").option("--stdout", "Print to stdout instead of file").action(async (options) => {
|
|
6758
|
+
try {
|
|
6759
|
+
await envPullCommand({ file: options.file, stdout: options.stdout });
|
|
6760
|
+
} catch (error) {
|
|
6761
|
+
status.error(`Env pull failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6762
|
+
process.exit(1);
|
|
6763
|
+
}
|
|
6764
|
+
});
|
|
6170
6765
|
import_commander.program.parse();
|
|
6171
6766
|
//# sourceMappingURL=index.js.map
|