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/index.d.cts CHANGED
@@ -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?: unknown): void;
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
@@ -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?: unknown): void;
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 Config = class {
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
- this._store.set(key, value);
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 projects = this.getProjects();
55
- projects[name] = config;
56
- this.set("projects", projects);
156
+ const freshProjects = this._store.get("projects") ?? {};
157
+ freshProjects[name] = config;
158
+ this._store.set("projects", freshProjects);
57
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
+ }
172
+ }
173
+ /**
174
+ * Synchronous version of deleteProject for backwards compatibility.
175
+ */
58
176
  deleteProject(name) {
59
- const projects = this.getProjects();
60
- delete projects[name];
61
- this.set("projects", 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 = new Config();
562
+ this.config = Config.getInstance();
445
563
  this.log = {
446
564
  debug: () => {
447
565
  },
@@ -769,10 +887,10 @@ var ClaudeEvent = class _ClaudeEvent {
769
887
  }
770
888
  static getClaudeCommand(config) {
771
889
  if (typeof config === "boolean" || config === void 0) {
772
- return "claude";
890
+ return "claude --dangerously-skip-permissions";
773
891
  }
774
892
  const flags = config.flags || [];
775
- return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
893
+ return flags.length > 0 ? `claude --dangerously-skip-permissions ${flags.join(" ")}` : "claude --dangerously-skip-permissions";
776
894
  }
777
895
  static get processing() {
778
896
  return {