workon 3.2.2 → 3.2.3
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/cli.js +172 -46
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +132 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +132 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11,18 +11,99 @@ var __export = (target, all) => {
|
|
|
11
11
|
|
|
12
12
|
// src/lib/config.ts
|
|
13
13
|
import Conf from "conf";
|
|
14
|
-
|
|
14
|
+
import { openSync, closeSync, unlinkSync, existsSync, mkdirSync } from "fs";
|
|
15
|
+
import { dirname } from "path";
|
|
16
|
+
var TRANSIENT_PROPS, FileLock, Config;
|
|
15
17
|
var init_config = __esm({
|
|
16
18
|
"src/lib/config.ts"() {
|
|
17
19
|
"use strict";
|
|
18
20
|
TRANSIENT_PROPS = ["pkg", "work"];
|
|
19
|
-
|
|
21
|
+
FileLock = class _FileLock {
|
|
22
|
+
lockPath;
|
|
23
|
+
fd = null;
|
|
24
|
+
static LOCK_TIMEOUT_MS = 5e3;
|
|
25
|
+
static RETRY_INTERVAL_MS = 50;
|
|
26
|
+
constructor(configPath) {
|
|
27
|
+
this.lockPath = `${configPath}.lock`;
|
|
28
|
+
}
|
|
29
|
+
async acquire() {
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
const lockDir = dirname(this.lockPath);
|
|
32
|
+
if (!existsSync(lockDir)) {
|
|
33
|
+
mkdirSync(lockDir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
while (Date.now() - startTime < _FileLock.LOCK_TIMEOUT_MS) {
|
|
36
|
+
try {
|
|
37
|
+
this.fd = openSync(this.lockPath, "wx");
|
|
38
|
+
return;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error.code === "EEXIST") {
|
|
41
|
+
try {
|
|
42
|
+
const stat = await import("fs").then((fs) => fs.promises.stat(this.lockPath));
|
|
43
|
+
const age = Date.now() - stat.mtimeMs;
|
|
44
|
+
if (age > _FileLock.LOCK_TIMEOUT_MS) {
|
|
45
|
+
try {
|
|
46
|
+
unlinkSync(this.lockPath);
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
await new Promise((resolve2) => setTimeout(resolve2, _FileLock.RETRY_INTERVAL_MS));
|
|
53
|
+
} else {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw new Error("Failed to acquire config lock: timeout");
|
|
59
|
+
}
|
|
60
|
+
release() {
|
|
61
|
+
if (this.fd !== null) {
|
|
62
|
+
try {
|
|
63
|
+
closeSync(this.fd);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
this.fd = null;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
unlinkSync(this.lockPath);
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
Config = class _Config {
|
|
75
|
+
static _instance = null;
|
|
20
76
|
_transient = {};
|
|
77
|
+
// Using definite assignment assertion since singleton pattern may return existing instance
|
|
21
78
|
_store;
|
|
79
|
+
_lock;
|
|
22
80
|
constructor() {
|
|
81
|
+
if (_Config._instance && process.env.NODE_ENV !== "test") {
|
|
82
|
+
return _Config._instance;
|
|
83
|
+
}
|
|
23
84
|
this._store = new Conf({
|
|
24
|
-
projectName: "workon"
|
|
85
|
+
projectName: "workon",
|
|
86
|
+
...process.env.WORKON_CONFIG_DIR && { cwd: process.env.WORKON_CONFIG_DIR }
|
|
25
87
|
});
|
|
88
|
+
this._lock = new FileLock(this._store.path);
|
|
89
|
+
if (process.env.NODE_ENV !== "test") {
|
|
90
|
+
_Config._instance = this;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the singleton instance (creates one if needed)
|
|
95
|
+
*/
|
|
96
|
+
static getInstance() {
|
|
97
|
+
if (!_Config._instance) {
|
|
98
|
+
_Config._instance = new _Config();
|
|
99
|
+
}
|
|
100
|
+
return _Config._instance;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Reset the singleton instance (for testing purposes)
|
|
104
|
+
*/
|
|
105
|
+
static resetInstance() {
|
|
106
|
+
_Config._instance = null;
|
|
26
107
|
}
|
|
27
108
|
get(key, defaultValue) {
|
|
28
109
|
const rootKey = key.split(".")[0];
|
|
@@ -37,10 +118,9 @@ var init_config = __esm({
|
|
|
37
118
|
this._transient[key] = value;
|
|
38
119
|
} else {
|
|
39
120
|
if (value === void 0) {
|
|
40
|
-
|
|
41
|
-
} else {
|
|
42
|
-
this._store.set(key, value);
|
|
121
|
+
throw new Error(`Cannot set '${key}' to undefined. Use delete() to remove keys.`);
|
|
43
122
|
}
|
|
123
|
+
this._store.set(key, value);
|
|
44
124
|
}
|
|
45
125
|
}
|
|
46
126
|
has(key) {
|
|
@@ -58,6 +138,9 @@ var init_config = __esm({
|
|
|
58
138
|
this._store.delete(key);
|
|
59
139
|
}
|
|
60
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Get all projects. Returns a fresh copy from the store.
|
|
143
|
+
*/
|
|
61
144
|
getProjects() {
|
|
62
145
|
return this.get("projects") ?? {};
|
|
63
146
|
}
|
|
@@ -65,15 +148,50 @@ var init_config = __esm({
|
|
|
65
148
|
const projects = this.getProjects();
|
|
66
149
|
return projects[name];
|
|
67
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Set a project with file locking to prevent race conditions.
|
|
153
|
+
* This ensures atomic read-modify-write operations.
|
|
154
|
+
*/
|
|
155
|
+
async setProjectSafe(name, config) {
|
|
156
|
+
await this._lock.acquire();
|
|
157
|
+
try {
|
|
158
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
159
|
+
freshProjects[name] = config;
|
|
160
|
+
this._store.set("projects", freshProjects);
|
|
161
|
+
} finally {
|
|
162
|
+
this._lock.release();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Synchronous version of setProject for backwards compatibility.
|
|
167
|
+
* Note: This is less safe than setProjectSafe() in concurrent scenarios.
|
|
168
|
+
* Consider migrating to setProjectSafe() for critical operations.
|
|
169
|
+
*/
|
|
68
170
|
setProject(name, config) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
this.set("projects",
|
|
171
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
172
|
+
freshProjects[name] = config;
|
|
173
|
+
this._store.set("projects", freshProjects);
|
|
72
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Delete a project with file locking to prevent race conditions.
|
|
177
|
+
*/
|
|
178
|
+
async deleteProjectSafe(name) {
|
|
179
|
+
await this._lock.acquire();
|
|
180
|
+
try {
|
|
181
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
182
|
+
delete freshProjects[name];
|
|
183
|
+
this._store.set("projects", freshProjects);
|
|
184
|
+
} finally {
|
|
185
|
+
this._lock.release();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Synchronous version of deleteProject for backwards compatibility.
|
|
190
|
+
*/
|
|
73
191
|
deleteProject(name) {
|
|
74
|
-
const
|
|
75
|
-
delete
|
|
76
|
-
this.set("projects",
|
|
192
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
193
|
+
delete freshProjects[name];
|
|
194
|
+
this._store.set("projects", freshProjects);
|
|
77
195
|
}
|
|
78
196
|
getDefaults() {
|
|
79
197
|
return this.get("project_defaults");
|
|
@@ -280,7 +398,7 @@ var init_environment = __esm({
|
|
|
280
398
|
}
|
|
281
399
|
static ensureConfigured() {
|
|
282
400
|
if (!this.configured) {
|
|
283
|
-
this.config =
|
|
401
|
+
this.config = Config.getInstance();
|
|
284
402
|
this.log = {
|
|
285
403
|
debug: () => {
|
|
286
404
|
},
|
|
@@ -632,10 +750,10 @@ var init_claude = __esm({
|
|
|
632
750
|
}
|
|
633
751
|
static getClaudeCommand(config) {
|
|
634
752
|
if (typeof config === "boolean" || config === void 0) {
|
|
635
|
-
return "claude";
|
|
753
|
+
return "claude --dangerously-skip-permissions";
|
|
636
754
|
}
|
|
637
755
|
const flags = config.flags || [];
|
|
638
|
-
return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
|
|
756
|
+
return flags.length > 0 ? `claude --dangerously-skip-permissions ${flags.join(" ")}` : "claude --dangerously-skip-permissions";
|
|
639
757
|
}
|
|
640
758
|
static get processing() {
|
|
641
759
|
return {
|
|
@@ -1469,8 +1587,7 @@ async function initProject(defaultName, fromUser, ctx) {
|
|
|
1469
1587
|
ide,
|
|
1470
1588
|
events
|
|
1471
1589
|
};
|
|
1472
|
-
|
|
1473
|
-
config.set("projects", projects);
|
|
1590
|
+
await config.setProjectSafe(name, projectConfig);
|
|
1474
1591
|
log.info("Your project has been initialized.");
|
|
1475
1592
|
log.info(`Use 'workon ${name}' to start working!`);
|
|
1476
1593
|
}
|
|
@@ -1491,8 +1608,7 @@ async function initBranch(defaultName, ctx) {
|
|
|
1491
1608
|
branch
|
|
1492
1609
|
});
|
|
1493
1610
|
const branchConfig = mergedConfig;
|
|
1494
|
-
|
|
1495
|
-
config.set("projects", projects);
|
|
1611
|
+
await config.setProjectSafe(branchName, branchConfig);
|
|
1496
1612
|
log.info("Your branch configuration has been initialized.");
|
|
1497
1613
|
log.info(`Use 'workon ${branchName}' to start working!`);
|
|
1498
1614
|
}
|
|
@@ -1690,7 +1806,7 @@ async function createProjectManage(ctx) {
|
|
|
1690
1806
|
default: true
|
|
1691
1807
|
});
|
|
1692
1808
|
if (confirmed) {
|
|
1693
|
-
config.
|
|
1809
|
+
await config.setProjectSafe(name, projectConfig);
|
|
1694
1810
|
log.info(`Project '${name}' created successfully!`);
|
|
1695
1811
|
}
|
|
1696
1812
|
}
|
|
@@ -1768,7 +1884,7 @@ async function editProjectManage(ctx) {
|
|
|
1768
1884
|
default: true
|
|
1769
1885
|
});
|
|
1770
1886
|
if (confirmed) {
|
|
1771
|
-
config.
|
|
1887
|
+
await config.setProjectSafe(name, updatedConfig);
|
|
1772
1888
|
log.info(`Project '${name}' updated successfully!`);
|
|
1773
1889
|
}
|
|
1774
1890
|
}
|
|
@@ -1794,7 +1910,7 @@ async function deleteProjectManage(ctx) {
|
|
|
1794
1910
|
});
|
|
1795
1911
|
if (deleteAll) {
|
|
1796
1912
|
for (const branch of branches) {
|
|
1797
|
-
config.
|
|
1913
|
+
await config.deleteProjectSafe(branch);
|
|
1798
1914
|
}
|
|
1799
1915
|
}
|
|
1800
1916
|
}
|
|
@@ -1803,7 +1919,7 @@ async function deleteProjectManage(ctx) {
|
|
|
1803
1919
|
default: false
|
|
1804
1920
|
});
|
|
1805
1921
|
if (confirmed) {
|
|
1806
|
-
config.
|
|
1922
|
+
await config.deleteProjectSafe(name);
|
|
1807
1923
|
log.info(`Project '${name}' deleted.`);
|
|
1808
1924
|
}
|
|
1809
1925
|
}
|
|
@@ -1883,7 +1999,7 @@ async function editBranchManage(projectName, ctx) {
|
|
|
1883
1999
|
default: true
|
|
1884
2000
|
});
|
|
1885
2001
|
if (confirmed) {
|
|
1886
|
-
config.
|
|
2002
|
+
await config.setProjectSafe(branchName, updatedConfig);
|
|
1887
2003
|
log.info(`Branch configuration '${branchName}' updated successfully!`);
|
|
1888
2004
|
}
|
|
1889
2005
|
}
|
|
@@ -1908,7 +2024,7 @@ async function deleteBranchManage(projectName, ctx) {
|
|
|
1908
2024
|
default: false
|
|
1909
2025
|
});
|
|
1910
2026
|
if (confirmed) {
|
|
1911
|
-
config.
|
|
2027
|
+
await config.deleteProjectSafe(branchName);
|
|
1912
2028
|
log.info(`Branch configuration '${branchName}' deleted.`);
|
|
1913
2029
|
}
|
|
1914
2030
|
}
|
|
@@ -1996,9 +2112,9 @@ async function processProject(projectParam, options, ctx) {
|
|
|
1996
2112
|
const projectEnv = ProjectEnvironment.load(projectCfg, config.getDefaults());
|
|
1997
2113
|
await switchTo(projectEnv, requestedCommands, options, ctx);
|
|
1998
2114
|
} else {
|
|
1999
|
-
log.
|
|
2000
|
-
|
|
2001
|
-
|
|
2115
|
+
log.error(`Project '${projectName}' not found.`);
|
|
2116
|
+
log.info(`Run 'workon' without arguments to see available projects or create a new one.`);
|
|
2117
|
+
process.exit(1);
|
|
2002
2118
|
}
|
|
2003
2119
|
}
|
|
2004
2120
|
function validateRequestedCommands(requestedCommands, projectConfig, projectName) {
|
|
@@ -2066,7 +2182,8 @@ function resolveCommandDependencies(requestedCommands, project) {
|
|
|
2066
2182
|
}
|
|
2067
2183
|
function getClaudeArgs(project) {
|
|
2068
2184
|
const claudeConfig = project.events.claude;
|
|
2069
|
-
|
|
2185
|
+
const userFlags = typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
|
|
2186
|
+
return ["--dangerously-skip-permissions", ...userFlags];
|
|
2070
2187
|
}
|
|
2071
2188
|
async function getNpmCommand(project) {
|
|
2072
2189
|
const npmConfig = project.events.npm;
|
|
@@ -2271,8 +2388,8 @@ init_environment();
|
|
|
2271
2388
|
init_registry();
|
|
2272
2389
|
init_open();
|
|
2273
2390
|
import { Command as Command8 } from "commander";
|
|
2274
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
2275
|
-
import { join, dirname } from "path";
|
|
2391
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
2392
|
+
import { join, dirname as dirname2 } from "path";
|
|
2276
2393
|
import { fileURLToPath } from "url";
|
|
2277
2394
|
import loog from "loog";
|
|
2278
2395
|
import omelette from "omelette";
|
|
@@ -2499,7 +2616,7 @@ async function createProject(ctx) {
|
|
|
2499
2616
|
default: true
|
|
2500
2617
|
});
|
|
2501
2618
|
if (confirmed) {
|
|
2502
|
-
config.
|
|
2619
|
+
await config.setProjectSafe(name, projectConfig);
|
|
2503
2620
|
log.info(`Project '${name}' created successfully!`);
|
|
2504
2621
|
log.info(`Use 'workon ${name}' to start working!`);
|
|
2505
2622
|
} else {
|
|
@@ -2589,7 +2706,7 @@ async function editProject(ctx) {
|
|
|
2589
2706
|
default: true
|
|
2590
2707
|
});
|
|
2591
2708
|
if (confirmed) {
|
|
2592
|
-
config.
|
|
2709
|
+
await config.setProjectSafe(name, updatedConfig);
|
|
2593
2710
|
log.info(`Project '${name}' updated successfully!`);
|
|
2594
2711
|
} else {
|
|
2595
2712
|
log.info("Edit cancelled.");
|
|
@@ -2612,7 +2729,7 @@ async function deleteProject(ctx) {
|
|
|
2612
2729
|
default: false
|
|
2613
2730
|
});
|
|
2614
2731
|
if (confirmed) {
|
|
2615
|
-
config.
|
|
2732
|
+
await config.deleteProjectSafe(name);
|
|
2616
2733
|
log.info(`Project '${name}' deleted.`);
|
|
2617
2734
|
} else {
|
|
2618
2735
|
log.info("Delete cancelled.");
|
|
@@ -2638,7 +2755,7 @@ async function listProjects(ctx) {
|
|
|
2638
2755
|
|
|
2639
2756
|
// src/commands/add.ts
|
|
2640
2757
|
import { Command as Command7 } from "commander";
|
|
2641
|
-
import { existsSync, readFileSync } from "fs";
|
|
2758
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
2642
2759
|
import { basename, resolve } from "path";
|
|
2643
2760
|
import File6 from "phylo";
|
|
2644
2761
|
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
@@ -2660,7 +2777,7 @@ async function addProject(pathArg, options, ctx) {
|
|
|
2660
2777
|
const projects = config.getProjects();
|
|
2661
2778
|
const targetPath = resolve(pathArg);
|
|
2662
2779
|
log.debug(`Resolved path: ${targetPath}`);
|
|
2663
|
-
if (!
|
|
2780
|
+
if (!existsSync2(targetPath)) {
|
|
2664
2781
|
log.error(`Path does not exist: ${targetPath}`);
|
|
2665
2782
|
process.exit(1);
|
|
2666
2783
|
}
|
|
@@ -2725,7 +2842,7 @@ async function addProject(pathArg, options, ctx) {
|
|
|
2725
2842
|
if (discovery.hasClaude) {
|
|
2726
2843
|
projectConfig.events.claude = true;
|
|
2727
2844
|
}
|
|
2728
|
-
config.
|
|
2845
|
+
await config.setProjectSafe(projectName, projectConfig);
|
|
2729
2846
|
log.info(`Added project '${projectName}'`);
|
|
2730
2847
|
log.info(` Path: ${relativePath}`);
|
|
2731
2848
|
log.info(` IDE: ${ide}`);
|
|
@@ -2744,7 +2861,7 @@ function discoverProject(targetPath, log) {
|
|
|
2744
2861
|
packageJson: null
|
|
2745
2862
|
};
|
|
2746
2863
|
const packageJsonPath = resolve(targetPath, "package.json");
|
|
2747
|
-
if (
|
|
2864
|
+
if (existsSync2(packageJsonPath)) {
|
|
2748
2865
|
discovery.isNode = true;
|
|
2749
2866
|
log.debug("Detected Node project (package.json found)");
|
|
2750
2867
|
try {
|
|
@@ -2760,25 +2877,25 @@ function discoverProject(targetPath, log) {
|
|
|
2760
2877
|
}
|
|
2761
2878
|
}
|
|
2762
2879
|
const bunLockPath = resolve(targetPath, "bun.lockb");
|
|
2763
|
-
if (
|
|
2880
|
+
if (existsSync2(bunLockPath)) {
|
|
2764
2881
|
discovery.isBun = true;
|
|
2765
2882
|
log.debug("Detected Bun project (bun.lockb found)");
|
|
2766
2883
|
}
|
|
2767
2884
|
const vscodeDir = resolve(targetPath, ".vscode");
|
|
2768
2885
|
const cursorDir = resolve(targetPath, ".cursor");
|
|
2769
2886
|
const ideaDir = resolve(targetPath, ".idea");
|
|
2770
|
-
if (
|
|
2887
|
+
if (existsSync2(cursorDir)) {
|
|
2771
2888
|
discovery.detectedIde = "cursor";
|
|
2772
2889
|
log.debug("Detected Cursor (.cursor directory found)");
|
|
2773
|
-
} else if (
|
|
2890
|
+
} else if (existsSync2(vscodeDir)) {
|
|
2774
2891
|
discovery.detectedIde = "code";
|
|
2775
2892
|
log.debug("Detected VS Code (.vscode directory found)");
|
|
2776
|
-
} else if (
|
|
2893
|
+
} else if (existsSync2(ideaDir)) {
|
|
2777
2894
|
discovery.detectedIde = "idea";
|
|
2778
2895
|
log.debug("Detected IntelliJ IDEA (.idea directory found)");
|
|
2779
2896
|
}
|
|
2780
2897
|
const claudeMdPath = resolve(targetPath, "CLAUDE.md");
|
|
2781
|
-
if (
|
|
2898
|
+
if (existsSync2(claudeMdPath)) {
|
|
2782
2899
|
discovery.hasClaude = true;
|
|
2783
2900
|
log.debug("Detected Claude Code project (CLAUDE.md found)");
|
|
2784
2901
|
}
|
|
@@ -2787,7 +2904,7 @@ function discoverProject(targetPath, log) {
|
|
|
2787
2904
|
|
|
2788
2905
|
// src/commands/index.ts
|
|
2789
2906
|
var __filename = fileURLToPath(import.meta.url);
|
|
2790
|
-
var __dirname =
|
|
2907
|
+
var __dirname = dirname2(__filename);
|
|
2791
2908
|
function findPackageJson() {
|
|
2792
2909
|
const paths = [
|
|
2793
2910
|
join(__dirname, "../package.json"),
|
|
@@ -2795,7 +2912,7 @@ function findPackageJson() {
|
|
|
2795
2912
|
join(process.cwd(), "package.json")
|
|
2796
2913
|
];
|
|
2797
2914
|
for (const p of paths) {
|
|
2798
|
-
if (
|
|
2915
|
+
if (existsSync3(p)) {
|
|
2799
2916
|
return p;
|
|
2800
2917
|
}
|
|
2801
2918
|
}
|
|
@@ -2935,6 +3052,15 @@ workon() {
|
|
|
2935
3052
|
}
|
|
2936
3053
|
|
|
2937
3054
|
// src/cli.ts
|
|
3055
|
+
if (process.stdout.isTTY) {
|
|
3056
|
+
process.stdout.write("");
|
|
3057
|
+
}
|
|
2938
3058
|
var program = createCli();
|
|
2939
|
-
program.
|
|
3059
|
+
program.parseAsync().catch((error) => {
|
|
3060
|
+
if (error?.name === "ExitPromptError" || error?.message?.includes("SIGINT")) {
|
|
3061
|
+
process.exit(130);
|
|
3062
|
+
}
|
|
3063
|
+
console.error(error);
|
|
3064
|
+
process.exit(1);
|
|
3065
|
+
});
|
|
2940
3066
|
//# sourceMappingURL=cli.js.map
|