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 CHANGED
@@ -11,18 +11,99 @@ var __export = (target, all) => {
11
11
 
12
12
  // src/lib/config.ts
13
13
  import Conf from "conf";
14
- var TRANSIENT_PROPS, Config;
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
- Config = class {
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
- this._store.set(key, value);
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 projects = this.getProjects();
70
- projects[name] = config;
71
- this.set("projects", 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 projects = this.getProjects();
75
- delete projects[name];
76
- this.set("projects", 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 = new 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
- projects[name] = projectConfig;
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
- projects[branchName] = branchConfig;
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.setProject(name, projectConfig);
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.setProject(name, updatedConfig);
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.deleteProject(branch);
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.deleteProject(name);
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.setProject(branchName, updatedConfig);
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.deleteProject(branchName);
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.debug(`Project '${projectName}' not found, starting interactive mode`);
2000
- const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
2001
- await runInteractive2({ config, log, environment, suggestedName: projectName });
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
- return typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
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 existsSync2 } from "fs";
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.setProject(name, projectConfig);
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.setProject(name, updatedConfig);
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.deleteProject(name);
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 (!existsSync(targetPath)) {
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.setProject(projectName, projectConfig);
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 (existsSync(packageJsonPath)) {
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 (existsSync(bunLockPath)) {
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 (existsSync(cursorDir)) {
2887
+ if (existsSync2(cursorDir)) {
2771
2888
  discovery.detectedIde = "cursor";
2772
2889
  log.debug("Detected Cursor (.cursor directory found)");
2773
- } else if (existsSync(vscodeDir)) {
2890
+ } else if (existsSync2(vscodeDir)) {
2774
2891
  discovery.detectedIde = "code";
2775
2892
  log.debug("Detected VS Code (.vscode directory found)");
2776
- } else if (existsSync(ideaDir)) {
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 (existsSync(claudeMdPath)) {
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 = dirname(__filename);
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 (existsSync2(p)) {
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.parse();
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