workon 3.2.1 → 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 +456 -269
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +141 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -2
- package/dist/index.d.ts +36 -2
- package/dist/index.js +141 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import File from 'phylo';
|
|
2
2
|
|
|
3
|
-
type IdeType = 'vscode' | 'idea' | 'atom' | 'code' | 'subl' | 'vim' | 'emacs';
|
|
3
|
+
type IdeType = 'vscode' | 'idea' | 'atom' | 'code' | 'subl' | 'vim' | 'emacs' | 'cursor';
|
|
4
4
|
interface ClaudeConfig {
|
|
5
5
|
flags?: string[];
|
|
6
6
|
split_terminal?: boolean;
|
|
@@ -135,17 +135,51 @@ interface OpenOptions extends GlobalOptions {
|
|
|
135
135
|
dryRun?: boolean;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Config class with singleton pattern and file locking to prevent
|
|
140
|
+
* race conditions that could clear the config.
|
|
141
|
+
*/
|
|
138
142
|
declare class Config {
|
|
143
|
+
private static _instance;
|
|
139
144
|
private _transient;
|
|
140
145
|
private _store;
|
|
146
|
+
private _lock;
|
|
141
147
|
constructor();
|
|
148
|
+
/**
|
|
149
|
+
* Get the singleton instance (creates one if needed)
|
|
150
|
+
*/
|
|
151
|
+
static getInstance(): Config;
|
|
152
|
+
/**
|
|
153
|
+
* Reset the singleton instance (for testing purposes)
|
|
154
|
+
*/
|
|
155
|
+
static resetInstance(): void;
|
|
142
156
|
get<T = unknown>(key: string, defaultValue?: T): T | undefined;
|
|
143
|
-
set(key: string, value
|
|
157
|
+
set(key: string, value: unknown): void;
|
|
144
158
|
has(key: string): boolean;
|
|
145
159
|
delete(key: string): void;
|
|
160
|
+
/**
|
|
161
|
+
* Get all projects. Returns a fresh copy from the store.
|
|
162
|
+
*/
|
|
146
163
|
getProjects(): Record<string, ProjectConfig>;
|
|
147
164
|
getProject(name: string): ProjectConfig | undefined;
|
|
165
|
+
/**
|
|
166
|
+
* Set a project with file locking to prevent race conditions.
|
|
167
|
+
* This ensures atomic read-modify-write operations.
|
|
168
|
+
*/
|
|
169
|
+
setProjectSafe(name: string, config: ProjectConfig): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* Synchronous version of setProject for backwards compatibility.
|
|
172
|
+
* Note: This is less safe than setProjectSafe() in concurrent scenarios.
|
|
173
|
+
* Consider migrating to setProjectSafe() for critical operations.
|
|
174
|
+
*/
|
|
148
175
|
setProject(name: string, config: ProjectConfig): void;
|
|
176
|
+
/**
|
|
177
|
+
* Delete a project with file locking to prevent race conditions.
|
|
178
|
+
*/
|
|
179
|
+
deleteProjectSafe(name: string): Promise<void>;
|
|
180
|
+
/**
|
|
181
|
+
* Synchronous version of deleteProject for backwards compatibility.
|
|
182
|
+
*/
|
|
149
183
|
deleteProject(name: string): void;
|
|
150
184
|
getDefaults(): ProjectDefaults | undefined;
|
|
151
185
|
setDefaults(defaults: ProjectDefaults): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import File from 'phylo';
|
|
2
2
|
|
|
3
|
-
type IdeType = 'vscode' | 'idea' | 'atom' | 'code' | 'subl' | 'vim' | 'emacs';
|
|
3
|
+
type IdeType = 'vscode' | 'idea' | 'atom' | 'code' | 'subl' | 'vim' | 'emacs' | 'cursor';
|
|
4
4
|
interface ClaudeConfig {
|
|
5
5
|
flags?: string[];
|
|
6
6
|
split_terminal?: boolean;
|
|
@@ -135,17 +135,51 @@ interface OpenOptions extends GlobalOptions {
|
|
|
135
135
|
dryRun?: boolean;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Config class with singleton pattern and file locking to prevent
|
|
140
|
+
* race conditions that could clear the config.
|
|
141
|
+
*/
|
|
138
142
|
declare class Config {
|
|
143
|
+
private static _instance;
|
|
139
144
|
private _transient;
|
|
140
145
|
private _store;
|
|
146
|
+
private _lock;
|
|
141
147
|
constructor();
|
|
148
|
+
/**
|
|
149
|
+
* Get the singleton instance (creates one if needed)
|
|
150
|
+
*/
|
|
151
|
+
static getInstance(): Config;
|
|
152
|
+
/**
|
|
153
|
+
* Reset the singleton instance (for testing purposes)
|
|
154
|
+
*/
|
|
155
|
+
static resetInstance(): void;
|
|
142
156
|
get<T = unknown>(key: string, defaultValue?: T): T | undefined;
|
|
143
|
-
set(key: string, value
|
|
157
|
+
set(key: string, value: unknown): void;
|
|
144
158
|
has(key: string): boolean;
|
|
145
159
|
delete(key: string): void;
|
|
160
|
+
/**
|
|
161
|
+
* Get all projects. Returns a fresh copy from the store.
|
|
162
|
+
*/
|
|
146
163
|
getProjects(): Record<string, ProjectConfig>;
|
|
147
164
|
getProject(name: string): ProjectConfig | undefined;
|
|
165
|
+
/**
|
|
166
|
+
* Set a project with file locking to prevent race conditions.
|
|
167
|
+
* This ensures atomic read-modify-write operations.
|
|
168
|
+
*/
|
|
169
|
+
setProjectSafe(name: string, config: ProjectConfig): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* Synchronous version of setProject for backwards compatibility.
|
|
172
|
+
* Note: This is less safe than setProjectSafe() in concurrent scenarios.
|
|
173
|
+
* Consider migrating to setProjectSafe() for critical operations.
|
|
174
|
+
*/
|
|
148
175
|
setProject(name: string, config: ProjectConfig): void;
|
|
176
|
+
/**
|
|
177
|
+
* Delete a project with file locking to prevent race conditions.
|
|
178
|
+
*/
|
|
179
|
+
deleteProjectSafe(name: string): Promise<void>;
|
|
180
|
+
/**
|
|
181
|
+
* Synchronous version of deleteProject for backwards compatibility.
|
|
182
|
+
*/
|
|
149
183
|
deleteProject(name: string): void;
|
|
150
184
|
getDefaults(): ProjectDefaults | undefined;
|
|
151
185
|
setDefaults(defaults: ProjectDefaults): void;
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,94 @@
|
|
|
1
1
|
// src/lib/config.ts
|
|
2
2
|
import Conf from "conf";
|
|
3
|
+
import { openSync, closeSync, unlinkSync, existsSync, mkdirSync } from "fs";
|
|
4
|
+
import { dirname } from "path";
|
|
3
5
|
var TRANSIENT_PROPS = ["pkg", "work"];
|
|
4
|
-
var
|
|
6
|
+
var FileLock = class _FileLock {
|
|
7
|
+
lockPath;
|
|
8
|
+
fd = null;
|
|
9
|
+
static LOCK_TIMEOUT_MS = 5e3;
|
|
10
|
+
static RETRY_INTERVAL_MS = 50;
|
|
11
|
+
constructor(configPath) {
|
|
12
|
+
this.lockPath = `${configPath}.lock`;
|
|
13
|
+
}
|
|
14
|
+
async acquire() {
|
|
15
|
+
const startTime = Date.now();
|
|
16
|
+
const lockDir = dirname(this.lockPath);
|
|
17
|
+
if (!existsSync(lockDir)) {
|
|
18
|
+
mkdirSync(lockDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
while (Date.now() - startTime < _FileLock.LOCK_TIMEOUT_MS) {
|
|
21
|
+
try {
|
|
22
|
+
this.fd = openSync(this.lockPath, "wx");
|
|
23
|
+
return;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error.code === "EEXIST") {
|
|
26
|
+
try {
|
|
27
|
+
const stat = await import("fs").then((fs) => fs.promises.stat(this.lockPath));
|
|
28
|
+
const age = Date.now() - stat.mtimeMs;
|
|
29
|
+
if (age > _FileLock.LOCK_TIMEOUT_MS) {
|
|
30
|
+
try {
|
|
31
|
+
unlinkSync(this.lockPath);
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, _FileLock.RETRY_INTERVAL_MS));
|
|
38
|
+
} else {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw new Error("Failed to acquire config lock: timeout");
|
|
44
|
+
}
|
|
45
|
+
release() {
|
|
46
|
+
if (this.fd !== null) {
|
|
47
|
+
try {
|
|
48
|
+
closeSync(this.fd);
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
this.fd = null;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
unlinkSync(this.lockPath);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var Config = class _Config {
|
|
60
|
+
static _instance = null;
|
|
5
61
|
_transient = {};
|
|
62
|
+
// Using definite assignment assertion since singleton pattern may return existing instance
|
|
6
63
|
_store;
|
|
64
|
+
_lock;
|
|
7
65
|
constructor() {
|
|
66
|
+
if (_Config._instance && process.env.NODE_ENV !== "test") {
|
|
67
|
+
return _Config._instance;
|
|
68
|
+
}
|
|
8
69
|
this._store = new Conf({
|
|
9
|
-
projectName: "workon"
|
|
70
|
+
projectName: "workon",
|
|
71
|
+
...process.env.WORKON_CONFIG_DIR && { cwd: process.env.WORKON_CONFIG_DIR }
|
|
10
72
|
});
|
|
73
|
+
this._lock = new FileLock(this._store.path);
|
|
74
|
+
if (process.env.NODE_ENV !== "test") {
|
|
75
|
+
_Config._instance = this;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get the singleton instance (creates one if needed)
|
|
80
|
+
*/
|
|
81
|
+
static getInstance() {
|
|
82
|
+
if (!_Config._instance) {
|
|
83
|
+
_Config._instance = new _Config();
|
|
84
|
+
}
|
|
85
|
+
return _Config._instance;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Reset the singleton instance (for testing purposes)
|
|
89
|
+
*/
|
|
90
|
+
static resetInstance() {
|
|
91
|
+
_Config._instance = null;
|
|
11
92
|
}
|
|
12
93
|
get(key, defaultValue) {
|
|
13
94
|
const rootKey = key.split(".")[0];
|
|
@@ -22,10 +103,9 @@ var Config = class {
|
|
|
22
103
|
this._transient[key] = value;
|
|
23
104
|
} else {
|
|
24
105
|
if (value === void 0) {
|
|
25
|
-
|
|
26
|
-
} else {
|
|
27
|
-
this._store.set(key, value);
|
|
106
|
+
throw new Error(`Cannot set '${key}' to undefined. Use delete() to remove keys.`);
|
|
28
107
|
}
|
|
108
|
+
this._store.set(key, value);
|
|
29
109
|
}
|
|
30
110
|
}
|
|
31
111
|
has(key) {
|
|
@@ -43,6 +123,9 @@ var Config = class {
|
|
|
43
123
|
this._store.delete(key);
|
|
44
124
|
}
|
|
45
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Get all projects. Returns a fresh copy from the store.
|
|
128
|
+
*/
|
|
46
129
|
getProjects() {
|
|
47
130
|
return this.get("projects") ?? {};
|
|
48
131
|
}
|
|
@@ -50,15 +133,50 @@ var Config = class {
|
|
|
50
133
|
const projects = this.getProjects();
|
|
51
134
|
return projects[name];
|
|
52
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Set a project with file locking to prevent race conditions.
|
|
138
|
+
* This ensures atomic read-modify-write operations.
|
|
139
|
+
*/
|
|
140
|
+
async setProjectSafe(name, config) {
|
|
141
|
+
await this._lock.acquire();
|
|
142
|
+
try {
|
|
143
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
144
|
+
freshProjects[name] = config;
|
|
145
|
+
this._store.set("projects", freshProjects);
|
|
146
|
+
} finally {
|
|
147
|
+
this._lock.release();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Synchronous version of setProject for backwards compatibility.
|
|
152
|
+
* Note: This is less safe than setProjectSafe() in concurrent scenarios.
|
|
153
|
+
* Consider migrating to setProjectSafe() for critical operations.
|
|
154
|
+
*/
|
|
53
155
|
setProject(name, config) {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
this.set("projects",
|
|
156
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
157
|
+
freshProjects[name] = config;
|
|
158
|
+
this._store.set("projects", freshProjects);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Delete a project with file locking to prevent race conditions.
|
|
162
|
+
*/
|
|
163
|
+
async deleteProjectSafe(name) {
|
|
164
|
+
await this._lock.acquire();
|
|
165
|
+
try {
|
|
166
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
167
|
+
delete freshProjects[name];
|
|
168
|
+
this._store.set("projects", freshProjects);
|
|
169
|
+
} finally {
|
|
170
|
+
this._lock.release();
|
|
171
|
+
}
|
|
57
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Synchronous version of deleteProject for backwards compatibility.
|
|
175
|
+
*/
|
|
58
176
|
deleteProject(name) {
|
|
59
|
-
const
|
|
60
|
-
delete
|
|
61
|
-
this.set("projects",
|
|
177
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
178
|
+
delete freshProjects[name];
|
|
179
|
+
this._store.set("projects", freshProjects);
|
|
62
180
|
}
|
|
63
181
|
getDefaults() {
|
|
64
182
|
return this.get("project_defaults");
|
|
@@ -441,7 +559,7 @@ var EnvironmentRecognizer = class {
|
|
|
441
559
|
}
|
|
442
560
|
static ensureConfigured() {
|
|
443
561
|
if (!this.configured) {
|
|
444
|
-
this.config =
|
|
562
|
+
this.config = Config.getInstance();
|
|
445
563
|
this.log = {
|
|
446
564
|
debug: () => {
|
|
447
565
|
},
|
|
@@ -500,18 +618,22 @@ var CwdEvent = class {
|
|
|
500
618
|
const { project, isShellMode, shellCommands } = context;
|
|
501
619
|
const projectPath = project.path.path;
|
|
502
620
|
if (isShellMode) {
|
|
503
|
-
shellCommands.push(`
|
|
621
|
+
shellCommands.push(`pushd "${projectPath}" > /dev/null`);
|
|
504
622
|
} else {
|
|
505
623
|
const shell = process.env.SHELL || "/bin/bash";
|
|
506
|
-
spawn2(shell, [], {
|
|
624
|
+
const child = spawn2(shell, ["-i"], {
|
|
507
625
|
cwd: projectPath,
|
|
508
626
|
stdio: "inherit"
|
|
509
627
|
});
|
|
628
|
+
await new Promise((resolve, reject) => {
|
|
629
|
+
child.on("close", () => resolve());
|
|
630
|
+
child.on("error", (err) => reject(err));
|
|
631
|
+
});
|
|
510
632
|
}
|
|
511
633
|
},
|
|
512
634
|
generateShellCommand(context) {
|
|
513
635
|
const projectPath = context.project.path.path;
|
|
514
|
-
return [`
|
|
636
|
+
return [`pushd "${projectPath}" > /dev/null`];
|
|
515
637
|
}
|
|
516
638
|
};
|
|
517
639
|
}
|
|
@@ -570,7 +692,7 @@ var IdeEvent = class {
|
|
|
570
692
|
const projectPath = project.path.path;
|
|
571
693
|
const ide = project.ide || "code";
|
|
572
694
|
if (isShellMode) {
|
|
573
|
-
shellCommands.push(
|
|
695
|
+
shellCommands.push(`set +m; ${ide} "${projectPath}" &>/dev/null &`);
|
|
574
696
|
} else {
|
|
575
697
|
spawn3(ide, [projectPath], {
|
|
576
698
|
detached: true,
|
|
@@ -581,7 +703,7 @@ var IdeEvent = class {
|
|
|
581
703
|
generateShellCommand(context) {
|
|
582
704
|
const projectPath = context.project.path.path;
|
|
583
705
|
const ide = context.project.ide || "code";
|
|
584
|
-
return [
|
|
706
|
+
return [`set +m; ${ide} "${projectPath}" &>/dev/null &`];
|
|
585
707
|
}
|
|
586
708
|
};
|
|
587
709
|
}
|
|
@@ -765,10 +887,10 @@ var ClaudeEvent = class _ClaudeEvent {
|
|
|
765
887
|
}
|
|
766
888
|
static getClaudeCommand(config) {
|
|
767
889
|
if (typeof config === "boolean" || config === void 0) {
|
|
768
|
-
return "claude";
|
|
890
|
+
return "claude --dangerously-skip-permissions";
|
|
769
891
|
}
|
|
770
892
|
const flags = config.flags || [];
|
|
771
|
-
return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
|
|
893
|
+
return flags.length > 0 ? `claude --dangerously-skip-permissions ${flags.join(" ")}` : "claude --dangerously-skip-permissions";
|
|
772
894
|
}
|
|
773
895
|
static get processing() {
|
|
774
896
|
return {
|