@magentrix-corp/magentrix-cli 1.3.16 → 1.3.17

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.
Files changed (68) hide show
  1. package/LICENSE +25 -25
  2. package/README.md +1166 -1166
  3. package/actions/autopublish.old.js +293 -293
  4. package/actions/config.js +182 -182
  5. package/actions/create.js +466 -466
  6. package/actions/help.js +164 -164
  7. package/actions/iris/buildStage.js +874 -874
  8. package/actions/iris/delete.js +256 -256
  9. package/actions/iris/dev.js +391 -391
  10. package/actions/iris/index.js +6 -6
  11. package/actions/iris/link.js +375 -375
  12. package/actions/iris/recover.js +268 -268
  13. package/actions/main.js +80 -80
  14. package/actions/publish.js +1420 -1420
  15. package/actions/pull.js +684 -684
  16. package/actions/setup.js +148 -148
  17. package/actions/status.js +17 -17
  18. package/actions/update.js +248 -248
  19. package/bin/magentrix.js +393 -393
  20. package/package.json +55 -55
  21. package/utils/assetPaths.js +158 -158
  22. package/utils/autopublishLock.js +77 -77
  23. package/utils/cacher.js +206 -206
  24. package/utils/cli/checkInstanceUrl.js +76 -74
  25. package/utils/cli/helpers/compare.js +282 -282
  26. package/utils/cli/helpers/ensureApiKey.js +63 -63
  27. package/utils/cli/helpers/ensureCredentials.js +68 -68
  28. package/utils/cli/helpers/ensureInstanceUrl.js +75 -75
  29. package/utils/cli/writeRecords.js +262 -262
  30. package/utils/compare.js +135 -135
  31. package/utils/compress.js +17 -17
  32. package/utils/config.js +527 -527
  33. package/utils/debug.js +144 -144
  34. package/utils/diagnostics/testPublishLogic.js +96 -96
  35. package/utils/diff.js +49 -49
  36. package/utils/downloadAssets.js +291 -291
  37. package/utils/filetag.js +115 -115
  38. package/utils/hash.js +14 -14
  39. package/utils/iris/backup.js +411 -411
  40. package/utils/iris/builder.js +541 -541
  41. package/utils/iris/config-reader.js +664 -664
  42. package/utils/iris/deleteHelper.js +150 -150
  43. package/utils/iris/errors.js +537 -537
  44. package/utils/iris/linker.js +601 -601
  45. package/utils/iris/lock.js +360 -360
  46. package/utils/iris/validation.js +360 -360
  47. package/utils/iris/validator.js +281 -281
  48. package/utils/iris/zipper.js +248 -248
  49. package/utils/logger.js +291 -291
  50. package/utils/magentrix/api/assets.js +220 -220
  51. package/utils/magentrix/api/auth.js +107 -107
  52. package/utils/magentrix/api/createEntity.js +61 -61
  53. package/utils/magentrix/api/deleteEntity.js +55 -55
  54. package/utils/magentrix/api/iris.js +251 -251
  55. package/utils/magentrix/api/meqlQuery.js +36 -36
  56. package/utils/magentrix/api/retrieveEntity.js +86 -86
  57. package/utils/magentrix/api/updateEntity.js +66 -66
  58. package/utils/magentrix/fetch.js +168 -168
  59. package/utils/merge.js +22 -22
  60. package/utils/permissionError.js +70 -70
  61. package/utils/preferences.js +40 -40
  62. package/utils/progress.js +469 -469
  63. package/utils/spinner.js +43 -43
  64. package/utils/template.js +52 -52
  65. package/utils/updateFileBase.js +121 -121
  66. package/utils/workspaces.js +108 -108
  67. package/vars/config.js +11 -11
  68. package/vars/global.js +50 -50
@@ -1,360 +1,360 @@
1
- /**
2
- * Operation locking system for Iris Vue integration.
3
- * Prevents concurrent operations that could cause data corruption.
4
- */
5
-
6
- import { existsSync, writeFileSync, readFileSync, unlinkSync, mkdirSync } from 'node:fs';
7
- import { join, dirname } from 'node:path';
8
- import { formatConcurrentOperationError } from './errors.js';
9
-
10
- /**
11
- * Lock directory relative to workspace.
12
- */
13
- const LOCK_DIR = '.magentrix/locks';
14
-
15
- /**
16
- * Lock types for different operations.
17
- */
18
- export const LockTypes = {
19
- BUILD: 'build',
20
- STAGE: 'stage',
21
- PUBLISH: 'publish',
22
- DELETE: 'delete',
23
- RECOVER: 'recover',
24
- DEV_SERVER: 'dev-server'
25
- };
26
-
27
- /**
28
- * Default lock timeout in milliseconds (5 minutes).
29
- * Locks older than this are considered stale and can be overwritten.
30
- */
31
- const DEFAULT_LOCK_TIMEOUT = 5 * 60 * 1000;
32
-
33
- /**
34
- * Get the lock file path for a given lock type and context.
35
- *
36
- * @param {string} lockType - One of LockTypes
37
- * @param {string} context - Context identifier (e.g., workspace path, project slug)
38
- * @param {string} basePath - Base path for lock files (defaults to cwd)
39
- * @returns {string} - Path to the lock file
40
- */
41
- function getLockPath(lockType, context = 'global', basePath = process.cwd()) {
42
- const lockDir = join(basePath, LOCK_DIR);
43
- const safeContext = context.replace(/[^a-zA-Z0-9-_]/g, '_');
44
- return join(lockDir, `${lockType}-${safeContext}.lock`);
45
- }
46
-
47
- /**
48
- * Create lock file data.
49
- *
50
- * @param {string} operation - Description of the operation
51
- * @returns {Object} - Lock data
52
- */
53
- function createLockData(operation) {
54
- return {
55
- pid: process.pid,
56
- operation,
57
- startedAt: new Date().toISOString(),
58
- hostname: process.env.HOSTNAME || process.env.COMPUTERNAME || 'unknown'
59
- };
60
- }
61
-
62
- /**
63
- * Check if a lock is stale (older than timeout).
64
- *
65
- * @param {Object} lockData - The lock file data
66
- * @param {number} timeout - Timeout in milliseconds
67
- * @returns {boolean} - True if lock is stale
68
- */
69
- function isLockStale(lockData, timeout = DEFAULT_LOCK_TIMEOUT) {
70
- if (!lockData.startedAt) {
71
- return true;
72
- }
73
-
74
- const startTime = new Date(lockData.startedAt).getTime();
75
- const now = Date.now();
76
-
77
- return (now - startTime) > timeout;
78
- }
79
-
80
- /**
81
- * Check if the process that created the lock is still running.
82
- *
83
- * @param {number} pid - Process ID to check
84
- * @returns {boolean} - True if process is running
85
- */
86
- function isProcessRunning(pid) {
87
- try {
88
- // Sending signal 0 checks if process exists without killing it
89
- process.kill(pid, 0);
90
- return true;
91
- } catch (err) {
92
- // ESRCH means process doesn't exist
93
- // EPERM means process exists but we don't have permission
94
- return err.code === 'EPERM';
95
- }
96
- }
97
-
98
- /**
99
- * Acquire a lock for an operation.
100
- *
101
- * @param {string} lockType - One of LockTypes
102
- * @param {Object} options - Lock options
103
- * @param {string} options.context - Context identifier
104
- * @param {string} options.operation - Description of the operation
105
- * @param {string} options.basePath - Base path for lock files
106
- * @param {number} options.timeout - Lock timeout in milliseconds
107
- * @param {boolean} options.force - Force acquire even if locked
108
- * @returns {{
109
- * acquired: boolean,
110
- * lockPath: string,
111
- * error: string | null,
112
- * existingLock: Object | null
113
- * }}
114
- */
115
- export function acquireLock(lockType, options = {}) {
116
- const {
117
- context = 'global',
118
- operation = lockType,
119
- basePath = process.cwd(),
120
- timeout = DEFAULT_LOCK_TIMEOUT,
121
- force = false
122
- } = options;
123
-
124
- const lockPath = getLockPath(lockType, context, basePath);
125
- const lockDir = dirname(lockPath);
126
-
127
- const result = {
128
- acquired: false,
129
- lockPath,
130
- error: null,
131
- existingLock: null
132
- };
133
-
134
- // Ensure lock directory exists
135
- try {
136
- if (!existsSync(lockDir)) {
137
- mkdirSync(lockDir, { recursive: true });
138
- }
139
- } catch (err) {
140
- result.error = `Failed to create lock directory: ${err.message}`;
141
- return result;
142
- }
143
-
144
- // Check for existing lock
145
- if (existsSync(lockPath)) {
146
- try {
147
- const existingData = JSON.parse(readFileSync(lockPath, 'utf-8'));
148
- result.existingLock = existingData;
149
-
150
- // Check if lock is stale or process is dead
151
- const stale = isLockStale(existingData, timeout);
152
- const processGone = existingData.pid && !isProcessRunning(existingData.pid);
153
-
154
- if (!force && !stale && !processGone) {
155
- // Lock is valid and held by another process
156
- result.error = formatConcurrentOperationError({
157
- operation,
158
- lockHolder: `${existingData.operation} (PID: ${existingData.pid})`,
159
- lockFile: lockPath
160
- });
161
- return result;
162
- }
163
-
164
- // Lock is stale or orphaned, we can take over
165
- console.log(`Note: Removed stale lock from ${existingData.operation}`);
166
- } catch (err) {
167
- // Invalid lock file, we can overwrite it
168
- console.log(`Note: Removed invalid lock file`);
169
- }
170
- }
171
-
172
- // Create new lock
173
- try {
174
- const lockData = createLockData(operation);
175
- writeFileSync(lockPath, JSON.stringify(lockData, null, 2), 'utf-8');
176
- result.acquired = true;
177
- } catch (err) {
178
- result.error = `Failed to create lock: ${err.message}`;
179
- return result;
180
- }
181
-
182
- return result;
183
- }
184
-
185
- /**
186
- * Release a lock.
187
- *
188
- * @param {string} lockType - One of LockTypes
189
- * @param {Object} options - Options
190
- * @param {string} options.context - Context identifier
191
- * @param {string} options.basePath - Base path for lock files
192
- * @returns {boolean} - True if lock was released
193
- */
194
- export function releaseLock(lockType, options = {}) {
195
- const {
196
- context = 'global',
197
- basePath = process.cwd()
198
- } = options;
199
-
200
- const lockPath = getLockPath(lockType, context, basePath);
201
-
202
- try {
203
- if (existsSync(lockPath)) {
204
- // Verify this is our lock before deleting
205
- try {
206
- const lockData = JSON.parse(readFileSync(lockPath, 'utf-8'));
207
- if (lockData.pid !== process.pid) {
208
- // Not our lock, don't delete
209
- return false;
210
- }
211
- } catch {
212
- // Can't read lock, assume it's okay to delete
213
- }
214
-
215
- unlinkSync(lockPath);
216
- }
217
- return true;
218
- } catch (err) {
219
- // Failed to release, but don't crash
220
- console.log(`Warning: Could not release lock: ${err.message}`);
221
- return false;
222
- }
223
- }
224
-
225
- /**
226
- * Check if an operation is locked.
227
- *
228
- * @param {string} lockType - One of LockTypes
229
- * @param {Object} options - Options
230
- * @param {string} options.context - Context identifier
231
- * @param {string} options.basePath - Base path for lock files
232
- * @param {number} options.timeout - Lock timeout in milliseconds
233
- * @returns {{
234
- * locked: boolean,
235
- * lockData: Object | null,
236
- * stale: boolean
237
- * }}
238
- */
239
- export function checkLock(lockType, options = {}) {
240
- const {
241
- context = 'global',
242
- basePath = process.cwd(),
243
- timeout = DEFAULT_LOCK_TIMEOUT
244
- } = options;
245
-
246
- const lockPath = getLockPath(lockType, context, basePath);
247
-
248
- const result = {
249
- locked: false,
250
- lockData: null,
251
- stale: false
252
- };
253
-
254
- if (!existsSync(lockPath)) {
255
- return result;
256
- }
257
-
258
- try {
259
- const lockData = JSON.parse(readFileSync(lockPath, 'utf-8'));
260
- result.lockData = lockData;
261
-
262
- const stale = isLockStale(lockData, timeout);
263
- const processGone = lockData.pid && !isProcessRunning(lockData.pid);
264
-
265
- result.stale = stale || processGone;
266
- result.locked = !result.stale;
267
- } catch {
268
- // Invalid lock file
269
- result.stale = true;
270
- }
271
-
272
- return result;
273
- }
274
-
275
- /**
276
- * Execute a function with a lock.
277
- * Automatically acquires lock before execution and releases after.
278
- *
279
- * @param {string} lockType - One of LockTypes
280
- * @param {Function} fn - Async function to execute
281
- * @param {Object} options - Lock options
282
- * @returns {Promise<*>} - Result of the function
283
- */
284
- export async function withLock(lockType, fn, options = {}) {
285
- const lockResult = acquireLock(lockType, options);
286
-
287
- if (!lockResult.acquired) {
288
- throw new Error(lockResult.error || 'Failed to acquire lock');
289
- }
290
-
291
- try {
292
- return await fn();
293
- } finally {
294
- releaseLock(lockType, options);
295
- }
296
- }
297
-
298
- /**
299
- * Clean up all stale locks in a directory.
300
- *
301
- * @param {string} basePath - Base path for lock files
302
- * @param {number} timeout - Lock timeout in milliseconds
303
- * @returns {number} - Number of locks cleaned up
304
- */
305
- export function cleanupStaleLocks(basePath = process.cwd(), timeout = DEFAULT_LOCK_TIMEOUT) {
306
- const lockDir = join(basePath, LOCK_DIR);
307
- let cleaned = 0;
308
-
309
- if (!existsSync(lockDir)) {
310
- return 0;
311
- }
312
-
313
- try {
314
- const { readdirSync } = require('node:fs');
315
- const files = readdirSync(lockDir);
316
-
317
- for (const file of files) {
318
- if (!file.endsWith('.lock')) continue;
319
-
320
- const lockPath = join(lockDir, file);
321
- try {
322
- const lockData = JSON.parse(readFileSync(lockPath, 'utf-8'));
323
-
324
- if (isLockStale(lockData, timeout) || !isProcessRunning(lockData.pid)) {
325
- unlinkSync(lockPath);
326
- cleaned++;
327
- }
328
- } catch {
329
- // Invalid lock file, remove it
330
- try {
331
- unlinkSync(lockPath);
332
- cleaned++;
333
- } catch {
334
- // Couldn't delete, skip
335
- }
336
- }
337
- }
338
- } catch {
339
- // Can't read directory, skip cleanup
340
- }
341
-
342
- return cleaned;
343
- }
344
-
345
- /**
346
- * Create a project-specific lock context from a path.
347
- *
348
- * @param {string} projectPath - Path to the project
349
- * @returns {string} - Lock context identifier
350
- */
351
- export function createProjectContext(projectPath) {
352
- // Create a short hash-like identifier from the path
353
- let hash = 0;
354
- for (let i = 0; i < projectPath.length; i++) {
355
- const char = projectPath.charCodeAt(i);
356
- hash = ((hash << 5) - hash) + char;
357
- hash = hash & hash; // Convert to 32bit integer
358
- }
359
- return `project-${Math.abs(hash).toString(36)}`;
360
- }
1
+ /**
2
+ * Operation locking system for Iris Vue integration.
3
+ * Prevents concurrent operations that could cause data corruption.
4
+ */
5
+
6
+ import { existsSync, writeFileSync, readFileSync, unlinkSync, mkdirSync } from 'node:fs';
7
+ import { join, dirname } from 'node:path';
8
+ import { formatConcurrentOperationError } from './errors.js';
9
+
10
+ /**
11
+ * Lock directory relative to workspace.
12
+ */
13
+ const LOCK_DIR = '.magentrix/locks';
14
+
15
+ /**
16
+ * Lock types for different operations.
17
+ */
18
+ export const LockTypes = {
19
+ BUILD: 'build',
20
+ STAGE: 'stage',
21
+ PUBLISH: 'publish',
22
+ DELETE: 'delete',
23
+ RECOVER: 'recover',
24
+ DEV_SERVER: 'dev-server'
25
+ };
26
+
27
+ /**
28
+ * Default lock timeout in milliseconds (5 minutes).
29
+ * Locks older than this are considered stale and can be overwritten.
30
+ */
31
+ const DEFAULT_LOCK_TIMEOUT = 5 * 60 * 1000;
32
+
33
+ /**
34
+ * Get the lock file path for a given lock type and context.
35
+ *
36
+ * @param {string} lockType - One of LockTypes
37
+ * @param {string} context - Context identifier (e.g., workspace path, project slug)
38
+ * @param {string} basePath - Base path for lock files (defaults to cwd)
39
+ * @returns {string} - Path to the lock file
40
+ */
41
+ function getLockPath(lockType, context = 'global', basePath = process.cwd()) {
42
+ const lockDir = join(basePath, LOCK_DIR);
43
+ const safeContext = context.replace(/[^a-zA-Z0-9-_]/g, '_');
44
+ return join(lockDir, `${lockType}-${safeContext}.lock`);
45
+ }
46
+
47
+ /**
48
+ * Create lock file data.
49
+ *
50
+ * @param {string} operation - Description of the operation
51
+ * @returns {Object} - Lock data
52
+ */
53
+ function createLockData(operation) {
54
+ return {
55
+ pid: process.pid,
56
+ operation,
57
+ startedAt: new Date().toISOString(),
58
+ hostname: process.env.HOSTNAME || process.env.COMPUTERNAME || 'unknown'
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Check if a lock is stale (older than timeout).
64
+ *
65
+ * @param {Object} lockData - The lock file data
66
+ * @param {number} timeout - Timeout in milliseconds
67
+ * @returns {boolean} - True if lock is stale
68
+ */
69
+ function isLockStale(lockData, timeout = DEFAULT_LOCK_TIMEOUT) {
70
+ if (!lockData.startedAt) {
71
+ return true;
72
+ }
73
+
74
+ const startTime = new Date(lockData.startedAt).getTime();
75
+ const now = Date.now();
76
+
77
+ return (now - startTime) > timeout;
78
+ }
79
+
80
+ /**
81
+ * Check if the process that created the lock is still running.
82
+ *
83
+ * @param {number} pid - Process ID to check
84
+ * @returns {boolean} - True if process is running
85
+ */
86
+ function isProcessRunning(pid) {
87
+ try {
88
+ // Sending signal 0 checks if process exists without killing it
89
+ process.kill(pid, 0);
90
+ return true;
91
+ } catch (err) {
92
+ // ESRCH means process doesn't exist
93
+ // EPERM means process exists but we don't have permission
94
+ return err.code === 'EPERM';
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Acquire a lock for an operation.
100
+ *
101
+ * @param {string} lockType - One of LockTypes
102
+ * @param {Object} options - Lock options
103
+ * @param {string} options.context - Context identifier
104
+ * @param {string} options.operation - Description of the operation
105
+ * @param {string} options.basePath - Base path for lock files
106
+ * @param {number} options.timeout - Lock timeout in milliseconds
107
+ * @param {boolean} options.force - Force acquire even if locked
108
+ * @returns {{
109
+ * acquired: boolean,
110
+ * lockPath: string,
111
+ * error: string | null,
112
+ * existingLock: Object | null
113
+ * }}
114
+ */
115
+ export function acquireLock(lockType, options = {}) {
116
+ const {
117
+ context = 'global',
118
+ operation = lockType,
119
+ basePath = process.cwd(),
120
+ timeout = DEFAULT_LOCK_TIMEOUT,
121
+ force = false
122
+ } = options;
123
+
124
+ const lockPath = getLockPath(lockType, context, basePath);
125
+ const lockDir = dirname(lockPath);
126
+
127
+ const result = {
128
+ acquired: false,
129
+ lockPath,
130
+ error: null,
131
+ existingLock: null
132
+ };
133
+
134
+ // Ensure lock directory exists
135
+ try {
136
+ if (!existsSync(lockDir)) {
137
+ mkdirSync(lockDir, { recursive: true });
138
+ }
139
+ } catch (err) {
140
+ result.error = `Failed to create lock directory: ${err.message}`;
141
+ return result;
142
+ }
143
+
144
+ // Check for existing lock
145
+ if (existsSync(lockPath)) {
146
+ try {
147
+ const existingData = JSON.parse(readFileSync(lockPath, 'utf-8'));
148
+ result.existingLock = existingData;
149
+
150
+ // Check if lock is stale or process is dead
151
+ const stale = isLockStale(existingData, timeout);
152
+ const processGone = existingData.pid && !isProcessRunning(existingData.pid);
153
+
154
+ if (!force && !stale && !processGone) {
155
+ // Lock is valid and held by another process
156
+ result.error = formatConcurrentOperationError({
157
+ operation,
158
+ lockHolder: `${existingData.operation} (PID: ${existingData.pid})`,
159
+ lockFile: lockPath
160
+ });
161
+ return result;
162
+ }
163
+
164
+ // Lock is stale or orphaned, we can take over
165
+ console.log(`Note: Removed stale lock from ${existingData.operation}`);
166
+ } catch (err) {
167
+ // Invalid lock file, we can overwrite it
168
+ console.log(`Note: Removed invalid lock file`);
169
+ }
170
+ }
171
+
172
+ // Create new lock
173
+ try {
174
+ const lockData = createLockData(operation);
175
+ writeFileSync(lockPath, JSON.stringify(lockData, null, 2), 'utf-8');
176
+ result.acquired = true;
177
+ } catch (err) {
178
+ result.error = `Failed to create lock: ${err.message}`;
179
+ return result;
180
+ }
181
+
182
+ return result;
183
+ }
184
+
185
+ /**
186
+ * Release a lock.
187
+ *
188
+ * @param {string} lockType - One of LockTypes
189
+ * @param {Object} options - Options
190
+ * @param {string} options.context - Context identifier
191
+ * @param {string} options.basePath - Base path for lock files
192
+ * @returns {boolean} - True if lock was released
193
+ */
194
+ export function releaseLock(lockType, options = {}) {
195
+ const {
196
+ context = 'global',
197
+ basePath = process.cwd()
198
+ } = options;
199
+
200
+ const lockPath = getLockPath(lockType, context, basePath);
201
+
202
+ try {
203
+ if (existsSync(lockPath)) {
204
+ // Verify this is our lock before deleting
205
+ try {
206
+ const lockData = JSON.parse(readFileSync(lockPath, 'utf-8'));
207
+ if (lockData.pid !== process.pid) {
208
+ // Not our lock, don't delete
209
+ return false;
210
+ }
211
+ } catch {
212
+ // Can't read lock, assume it's okay to delete
213
+ }
214
+
215
+ unlinkSync(lockPath);
216
+ }
217
+ return true;
218
+ } catch (err) {
219
+ // Failed to release, but don't crash
220
+ console.log(`Warning: Could not release lock: ${err.message}`);
221
+ return false;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Check if an operation is locked.
227
+ *
228
+ * @param {string} lockType - One of LockTypes
229
+ * @param {Object} options - Options
230
+ * @param {string} options.context - Context identifier
231
+ * @param {string} options.basePath - Base path for lock files
232
+ * @param {number} options.timeout - Lock timeout in milliseconds
233
+ * @returns {{
234
+ * locked: boolean,
235
+ * lockData: Object | null,
236
+ * stale: boolean
237
+ * }}
238
+ */
239
+ export function checkLock(lockType, options = {}) {
240
+ const {
241
+ context = 'global',
242
+ basePath = process.cwd(),
243
+ timeout = DEFAULT_LOCK_TIMEOUT
244
+ } = options;
245
+
246
+ const lockPath = getLockPath(lockType, context, basePath);
247
+
248
+ const result = {
249
+ locked: false,
250
+ lockData: null,
251
+ stale: false
252
+ };
253
+
254
+ if (!existsSync(lockPath)) {
255
+ return result;
256
+ }
257
+
258
+ try {
259
+ const lockData = JSON.parse(readFileSync(lockPath, 'utf-8'));
260
+ result.lockData = lockData;
261
+
262
+ const stale = isLockStale(lockData, timeout);
263
+ const processGone = lockData.pid && !isProcessRunning(lockData.pid);
264
+
265
+ result.stale = stale || processGone;
266
+ result.locked = !result.stale;
267
+ } catch {
268
+ // Invalid lock file
269
+ result.stale = true;
270
+ }
271
+
272
+ return result;
273
+ }
274
+
275
+ /**
276
+ * Execute a function with a lock.
277
+ * Automatically acquires lock before execution and releases after.
278
+ *
279
+ * @param {string} lockType - One of LockTypes
280
+ * @param {Function} fn - Async function to execute
281
+ * @param {Object} options - Lock options
282
+ * @returns {Promise<*>} - Result of the function
283
+ */
284
+ export async function withLock(lockType, fn, options = {}) {
285
+ const lockResult = acquireLock(lockType, options);
286
+
287
+ if (!lockResult.acquired) {
288
+ throw new Error(lockResult.error || 'Failed to acquire lock');
289
+ }
290
+
291
+ try {
292
+ return await fn();
293
+ } finally {
294
+ releaseLock(lockType, options);
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Clean up all stale locks in a directory.
300
+ *
301
+ * @param {string} basePath - Base path for lock files
302
+ * @param {number} timeout - Lock timeout in milliseconds
303
+ * @returns {number} - Number of locks cleaned up
304
+ */
305
+ export function cleanupStaleLocks(basePath = process.cwd(), timeout = DEFAULT_LOCK_TIMEOUT) {
306
+ const lockDir = join(basePath, LOCK_DIR);
307
+ let cleaned = 0;
308
+
309
+ if (!existsSync(lockDir)) {
310
+ return 0;
311
+ }
312
+
313
+ try {
314
+ const { readdirSync } = require('node:fs');
315
+ const files = readdirSync(lockDir);
316
+
317
+ for (const file of files) {
318
+ if (!file.endsWith('.lock')) continue;
319
+
320
+ const lockPath = join(lockDir, file);
321
+ try {
322
+ const lockData = JSON.parse(readFileSync(lockPath, 'utf-8'));
323
+
324
+ if (isLockStale(lockData, timeout) || !isProcessRunning(lockData.pid)) {
325
+ unlinkSync(lockPath);
326
+ cleaned++;
327
+ }
328
+ } catch {
329
+ // Invalid lock file, remove it
330
+ try {
331
+ unlinkSync(lockPath);
332
+ cleaned++;
333
+ } catch {
334
+ // Couldn't delete, skip
335
+ }
336
+ }
337
+ }
338
+ } catch {
339
+ // Can't read directory, skip cleanup
340
+ }
341
+
342
+ return cleaned;
343
+ }
344
+
345
+ /**
346
+ * Create a project-specific lock context from a path.
347
+ *
348
+ * @param {string} projectPath - Path to the project
349
+ * @returns {string} - Lock context identifier
350
+ */
351
+ export function createProjectContext(projectPath) {
352
+ // Create a short hash-like identifier from the path
353
+ let hash = 0;
354
+ for (let i = 0; i < projectPath.length; i++) {
355
+ const char = projectPath.charCodeAt(i);
356
+ hash = ((hash << 5) - hash) + char;
357
+ hash = hash & hash; // Convert to 32bit integer
358
+ }
359
+ return `project-${Math.abs(hash).toString(36)}`;
360
+ }