@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
package/package.json CHANGED
@@ -1,55 +1,55 @@
1
- {
2
- "name": "@magentrix-corp/magentrix-cli",
3
- "version": "1.3.16",
4
- "description": "CLI tool for synchronizing local files with Magentrix cloud platform",
5
- "main": "index.js",
6
- "type": "module",
7
- "bin": {
8
- "magentrix": "./bin/magentrix.js"
9
- },
10
- "scripts": {
11
- "test": "node tests/all.js"
12
- },
13
- "keywords": [
14
- "magentrix",
15
- "cli",
16
- "sync",
17
- "cloud",
18
- "automation",
19
- "development-tools"
20
- ],
21
- "author": "Magentrix Corporation",
22
- "license": "UNLICENSED",
23
- "engines": {
24
- "node": ">=20.0.0"
25
- },
26
- "publishConfig": {
27
- "access": "public"
28
- },
29
- "files": [
30
- "bin/",
31
- "actions/",
32
- "utils/",
33
- "vars/",
34
- "README.md",
35
- "LICENSE"
36
- ],
37
- "dependencies": {
38
- "@inquirer/prompts": "^7.6.0",
39
- "archiver": "^7.0.1",
40
- "chalk": "^5.4.1",
41
- "chokidar": "^4.0.3",
42
- "commander": "^14.0.0",
43
- "diff": "^8.0.2",
44
- "dotenv": "^17.2.3",
45
- "extract-zip": "^2.0.1",
46
- "fuzzy": "^0.1.3",
47
- "inquirer": "^12.7.0",
48
- "node-diff3": "^3.1.2",
49
- "ora": "^8.2.0",
50
- "pako": "^2.1.0",
51
- "prompts": "^2.4.2",
52
- "readline-sync": "^1.4.10",
53
- "uuid": "^11.1.0"
54
- }
55
- }
1
+ {
2
+ "name": "@magentrix-corp/magentrix-cli",
3
+ "version": "1.3.17",
4
+ "description": "CLI tool for synchronizing local files with Magentrix cloud platform",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "magentrix": "./bin/magentrix.js"
9
+ },
10
+ "scripts": {
11
+ "test": "node tests/all.js"
12
+ },
13
+ "keywords": [
14
+ "magentrix",
15
+ "cli",
16
+ "sync",
17
+ "cloud",
18
+ "automation",
19
+ "development-tools"
20
+ ],
21
+ "author": "Magentrix Corporation",
22
+ "license": "UNLICENSED",
23
+ "engines": {
24
+ "node": ">=20.0.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "files": [
30
+ "bin/",
31
+ "actions/",
32
+ "utils/",
33
+ "vars/",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "dependencies": {
38
+ "@inquirer/prompts": "^7.6.0",
39
+ "archiver": "^7.0.1",
40
+ "chalk": "^5.4.1",
41
+ "chokidar": "^4.0.3",
42
+ "commander": "^14.0.0",
43
+ "diff": "^8.0.2",
44
+ "dotenv": "^17.2.3",
45
+ "extract-zip": "^2.0.1",
46
+ "fuzzy": "^0.1.3",
47
+ "inquirer": "^12.7.0",
48
+ "node-diff3": "^3.1.2",
49
+ "ora": "^8.2.0",
50
+ "pako": "^2.1.0",
51
+ "prompts": "^2.4.2",
52
+ "readline-sync": "^1.4.10",
53
+ "uuid": "^11.1.0"
54
+ }
55
+ }
@@ -1,158 +1,158 @@
1
- import path from 'path';
2
- import { EXPORT_ROOT } from '../vars/global.js';
3
-
4
- /**
5
- * Asset path utilities for handling the difference between local and API paths.
6
- *
7
- * Local structure: src/Assets/...
8
- * API structure: /contents/assets/...
9
- *
10
- * These helpers abstract away the "Contents" prefix requirement for the Magentrix API.
11
- */
12
-
13
- /**
14
- * Convert a local asset path to an API path by adding the 'contents' prefix
15
- * and normalizing with forward slashes (preserves original casing).
16
- *
17
- * @param {string} localPath - Local file path (e.g., "src/Assets/Images/Logo.png")
18
- * @returns {string} API path (e.g., "/contents/assets/Images")
19
- *
20
- * @example
21
- * toApiPath("src/Assets/Images/Logo.png") // "/contents/assets/Images"
22
- * toApiPath("src/Assets") // "/contents/assets"
23
- */
24
- export const toApiPath = (localPath) => {
25
- // Handle undefined or empty paths
26
- if (!localPath) {
27
- return '/contents/assets';
28
- }
29
-
30
- // Normalize the path
31
- const normalized = path.normalize(localPath);
32
-
33
- // Remove EXPORT_ROOT from path (handle both absolute and relative paths)
34
- let relative;
35
- if (path.isAbsolute(normalized)) {
36
- // For absolute paths, find and remove everything up to and including EXPORT_ROOT
37
- const exportRootPattern = new RegExp(`.*[\\\\/]${EXPORT_ROOT}[\\\\/]`);
38
- relative = normalized.replace(exportRootPattern, '');
39
- } else {
40
- // For relative paths, just remove EXPORT_ROOT prefix
41
- relative = normalized.replace(new RegExp(`^${EXPORT_ROOT}[\\\\/]?`), '');
42
- }
43
-
44
- // Replace 'Assets' with 'contents/assets' (case insensitive search, but replace with lowercase)
45
- const apiPath = relative.replace(/^Assets/i, 'contents/assets');
46
-
47
- // Normalize to forward slashes (preserve casing!), remove filename
48
- let dirPath = path.dirname(apiPath).replace(/\\/g, '/');
49
-
50
- // Handle edge case where dirname returns '.' for root
51
- if (dirPath === '.') {
52
- dirPath = 'contents/assets';
53
- }
54
-
55
- // Ensure it starts with /
56
- return dirPath.startsWith('/') ? dirPath : `/${dirPath}`;
57
- };
58
-
59
- /**
60
- * Convert an API path to a local path by removing the 'contents' prefix.
61
- * Returns a relative path (without EXPORT_ROOT) that can be joined with EXPORT_ROOT.
62
- *
63
- * @param {string} apiPath - API path (e.g., "/contents/assets/images/logo.png")
64
- * @returns {string} Relative local path (e.g., "Assets/images/logo.png")
65
- *
66
- * @example
67
- * toLocalPath("/contents/assets/images") // "Assets/images"
68
- * toLocalPath("/contents/assets") // "Assets"
69
- */
70
- export const toLocalPath = (apiPath) => {
71
- // Remove leading slash and 'contents/' prefix
72
- const cleaned = apiPath.replace(/^\/+/, '').replace(/^contents\//i, '');
73
-
74
- // Capitalize 'assets' to 'Assets'
75
- const withCapitalAssets = cleaned.replace(/^assets/i, 'Assets');
76
-
77
- return withCapitalAssets;
78
- };
79
-
80
- /**
81
- * Convert a local folder path to an API path (keeps the folder, doesn't extract parent).
82
- * Similar to toApiPath but for folders (preserves original casing).
83
- *
84
- * @param {string} localFolderPath - Local folder path (e.g., "src/Assets/Images")
85
- * @returns {string} API path (e.g., "/contents/assets/Images")
86
- *
87
- * @example
88
- * toApiFolderPath("src/Assets/Images") // "/contents/assets/Images"
89
- * toApiFolderPath("src/Assets") // "/contents/assets"
90
- */
91
- export const toApiFolderPath = (localFolderPath) => {
92
- // Handle undefined or empty paths
93
- if (!localFolderPath) {
94
- return '/contents/assets';
95
- }
96
-
97
- // Normalize the path
98
- const normalized = path.normalize(localFolderPath);
99
-
100
- // Remove EXPORT_ROOT from path (handle both absolute and relative paths)
101
- let relative;
102
- if (path.isAbsolute(normalized)) {
103
- // For absolute paths, find and remove everything up to and including EXPORT_ROOT
104
- const exportRootPattern = new RegExp(`.*[\\\\/]${EXPORT_ROOT}[\\\\/]`);
105
- relative = normalized.replace(exportRootPattern, '');
106
- } else {
107
- // For relative paths, just remove EXPORT_ROOT prefix
108
- relative = normalized.replace(new RegExp(`^${EXPORT_ROOT}[\\\\/]?`), '');
109
- }
110
-
111
- // Replace 'Assets' with 'contents/assets' (case insensitive search, but replace with lowercase)
112
- let apiPath = relative.replace(/^Assets/i, 'contents/assets');
113
-
114
- // Normalize to forward slashes (preserve casing!)
115
- apiPath = apiPath.replace(/\\/g, '/');
116
-
117
- // Handle edge case where path is just 'Assets'
118
- if (apiPath === 'contents/assets' || apiPath === '') {
119
- return '/contents/assets';
120
- }
121
-
122
- // Ensure it starts with /
123
- return apiPath.startsWith('/') ? apiPath : `/${apiPath}`;
124
- };
125
-
126
- /**
127
- * Check if a local path is within the Assets directory.
128
- *
129
- * @param {string} localPath - Local file path
130
- * @returns {boolean} True if path is within Assets directory
131
- *
132
- * @example
133
- * isAssetPath("src/Assets/images/logo.png") // true
134
- * isAssetPath("src/Classes/MyClass.ac") // false
135
- */
136
- export const isAssetPath = (localPath) => {
137
- const normalized = path.normalize(localPath);
138
- const assetsDir = path.join(EXPORT_ROOT, 'Assets');
139
- return normalized.startsWith(assetsDir);
140
- };
141
-
142
- /**
143
- * Extract the folder path from a full file path (removes filename).
144
- * Returns path relative to Assets root.
145
- *
146
- * @param {string} filePath - Full file path
147
- * @returns {string} Folder path relative to EXPORT_ROOT
148
- *
149
- * @example
150
- * getAssetFolder("src/Assets/images/logo.png") // "Assets/images"
151
- * getAssetFolder("src/Assets/logo.png") // "Assets"
152
- */
153
- export const getAssetFolder = (filePath) => {
154
- const normalized = path.normalize(filePath);
155
- const relative = normalized.replace(new RegExp(`^${EXPORT_ROOT}[\\\\/]?`), '');
156
- const dirPath = path.dirname(relative);
157
- return dirPath === '.' ? 'Assets' : dirPath;
158
- };
1
+ import path from 'path';
2
+ import { EXPORT_ROOT } from '../vars/global.js';
3
+
4
+ /**
5
+ * Asset path utilities for handling the difference between local and API paths.
6
+ *
7
+ * Local structure: src/Assets/...
8
+ * API structure: /contents/assets/...
9
+ *
10
+ * These helpers abstract away the "Contents" prefix requirement for the Magentrix API.
11
+ */
12
+
13
+ /**
14
+ * Convert a local asset path to an API path by adding the 'contents' prefix
15
+ * and normalizing with forward slashes (preserves original casing).
16
+ *
17
+ * @param {string} localPath - Local file path (e.g., "src/Assets/Images/Logo.png")
18
+ * @returns {string} API path (e.g., "/contents/assets/Images")
19
+ *
20
+ * @example
21
+ * toApiPath("src/Assets/Images/Logo.png") // "/contents/assets/Images"
22
+ * toApiPath("src/Assets") // "/contents/assets"
23
+ */
24
+ export const toApiPath = (localPath) => {
25
+ // Handle undefined or empty paths
26
+ if (!localPath) {
27
+ return '/contents/assets';
28
+ }
29
+
30
+ // Normalize the path
31
+ const normalized = path.normalize(localPath);
32
+
33
+ // Remove EXPORT_ROOT from path (handle both absolute and relative paths)
34
+ let relative;
35
+ if (path.isAbsolute(normalized)) {
36
+ // For absolute paths, find and remove everything up to and including EXPORT_ROOT
37
+ const exportRootPattern = new RegExp(`.*[\\\\/]${EXPORT_ROOT}[\\\\/]`);
38
+ relative = normalized.replace(exportRootPattern, '');
39
+ } else {
40
+ // For relative paths, just remove EXPORT_ROOT prefix
41
+ relative = normalized.replace(new RegExp(`^${EXPORT_ROOT}[\\\\/]?`), '');
42
+ }
43
+
44
+ // Replace 'Assets' with 'contents/assets' (case insensitive search, but replace with lowercase)
45
+ const apiPath = relative.replace(/^Assets/i, 'contents/assets');
46
+
47
+ // Normalize to forward slashes (preserve casing!), remove filename
48
+ let dirPath = path.dirname(apiPath).replace(/\\/g, '/');
49
+
50
+ // Handle edge case where dirname returns '.' for root
51
+ if (dirPath === '.') {
52
+ dirPath = 'contents/assets';
53
+ }
54
+
55
+ // Ensure it starts with /
56
+ return dirPath.startsWith('/') ? dirPath : `/${dirPath}`;
57
+ };
58
+
59
+ /**
60
+ * Convert an API path to a local path by removing the 'contents' prefix.
61
+ * Returns a relative path (without EXPORT_ROOT) that can be joined with EXPORT_ROOT.
62
+ *
63
+ * @param {string} apiPath - API path (e.g., "/contents/assets/images/logo.png")
64
+ * @returns {string} Relative local path (e.g., "Assets/images/logo.png")
65
+ *
66
+ * @example
67
+ * toLocalPath("/contents/assets/images") // "Assets/images"
68
+ * toLocalPath("/contents/assets") // "Assets"
69
+ */
70
+ export const toLocalPath = (apiPath) => {
71
+ // Remove leading slash and 'contents/' prefix
72
+ const cleaned = apiPath.replace(/^\/+/, '').replace(/^contents\//i, '');
73
+
74
+ // Capitalize 'assets' to 'Assets'
75
+ const withCapitalAssets = cleaned.replace(/^assets/i, 'Assets');
76
+
77
+ return withCapitalAssets;
78
+ };
79
+
80
+ /**
81
+ * Convert a local folder path to an API path (keeps the folder, doesn't extract parent).
82
+ * Similar to toApiPath but for folders (preserves original casing).
83
+ *
84
+ * @param {string} localFolderPath - Local folder path (e.g., "src/Assets/Images")
85
+ * @returns {string} API path (e.g., "/contents/assets/Images")
86
+ *
87
+ * @example
88
+ * toApiFolderPath("src/Assets/Images") // "/contents/assets/Images"
89
+ * toApiFolderPath("src/Assets") // "/contents/assets"
90
+ */
91
+ export const toApiFolderPath = (localFolderPath) => {
92
+ // Handle undefined or empty paths
93
+ if (!localFolderPath) {
94
+ return '/contents/assets';
95
+ }
96
+
97
+ // Normalize the path
98
+ const normalized = path.normalize(localFolderPath);
99
+
100
+ // Remove EXPORT_ROOT from path (handle both absolute and relative paths)
101
+ let relative;
102
+ if (path.isAbsolute(normalized)) {
103
+ // For absolute paths, find and remove everything up to and including EXPORT_ROOT
104
+ const exportRootPattern = new RegExp(`.*[\\\\/]${EXPORT_ROOT}[\\\\/]`);
105
+ relative = normalized.replace(exportRootPattern, '');
106
+ } else {
107
+ // For relative paths, just remove EXPORT_ROOT prefix
108
+ relative = normalized.replace(new RegExp(`^${EXPORT_ROOT}[\\\\/]?`), '');
109
+ }
110
+
111
+ // Replace 'Assets' with 'contents/assets' (case insensitive search, but replace with lowercase)
112
+ let apiPath = relative.replace(/^Assets/i, 'contents/assets');
113
+
114
+ // Normalize to forward slashes (preserve casing!)
115
+ apiPath = apiPath.replace(/\\/g, '/');
116
+
117
+ // Handle edge case where path is just 'Assets'
118
+ if (apiPath === 'contents/assets' || apiPath === '') {
119
+ return '/contents/assets';
120
+ }
121
+
122
+ // Ensure it starts with /
123
+ return apiPath.startsWith('/') ? apiPath : `/${apiPath}`;
124
+ };
125
+
126
+ /**
127
+ * Check if a local path is within the Assets directory.
128
+ *
129
+ * @param {string} localPath - Local file path
130
+ * @returns {boolean} True if path is within Assets directory
131
+ *
132
+ * @example
133
+ * isAssetPath("src/Assets/images/logo.png") // true
134
+ * isAssetPath("src/Classes/MyClass.ac") // false
135
+ */
136
+ export const isAssetPath = (localPath) => {
137
+ const normalized = path.normalize(localPath);
138
+ const assetsDir = path.join(EXPORT_ROOT, 'Assets');
139
+ return normalized.startsWith(assetsDir);
140
+ };
141
+
142
+ /**
143
+ * Extract the folder path from a full file path (removes filename).
144
+ * Returns path relative to Assets root.
145
+ *
146
+ * @param {string} filePath - Full file path
147
+ * @returns {string} Folder path relative to EXPORT_ROOT
148
+ *
149
+ * @example
150
+ * getAssetFolder("src/Assets/images/logo.png") // "Assets/images"
151
+ * getAssetFolder("src/Assets/logo.png") // "Assets"
152
+ */
153
+ export const getAssetFolder = (filePath) => {
154
+ const normalized = path.normalize(filePath);
155
+ const relative = normalized.replace(new RegExp(`^${EXPORT_ROOT}[\\\\/]?`), '');
156
+ const dirPath = path.dirname(relative);
157
+ return dirPath === '.' ? 'Assets' : dirPath;
158
+ };
@@ -1,77 +1,77 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- const LOCK_FILE_NAME = 'autopublish.lock';
5
- const LOCK_EXPIRY_MS = 3600000; // 1 hour
6
-
7
- /**
8
- * Get the path to the autopublish lock file.
9
- * @returns {string} - Path to lock file
10
- */
11
- export function getLockFilePath() {
12
- return path.join(process.cwd(), '.magentrix', LOCK_FILE_NAME);
13
- }
14
-
15
- /**
16
- * Check if autopublish is currently running.
17
- * @returns {boolean} - True if autopublish is running (lock file exists and is not stale)
18
- */
19
- export function isAutopublishRunning() {
20
- const lockFile = getLockFilePath();
21
-
22
- if (!fs.existsSync(lockFile)) return false;
23
-
24
- try {
25
- const lockData = JSON.parse(fs.readFileSync(lockFile, 'utf-8'));
26
- const lockAge = Date.now() - lockData.timestamp;
27
-
28
- // If lock is older than 1 hour, consider it stale
29
- return lockAge < LOCK_EXPIRY_MS;
30
- } catch {
31
- // If we can't read the lock file, assume it's not running
32
- return false;
33
- }
34
- }
35
-
36
- /**
37
- * Create a lock file to prevent multiple autopublish instances.
38
- * @returns {boolean} - True if lock was acquired, false if already locked
39
- */
40
- export function acquireAutopublishLock() {
41
- const lockFile = getLockFilePath();
42
-
43
- try {
44
- if (fs.existsSync(lockFile)) {
45
- // Check if the lock is stale (process might have crashed)
46
- if (isAutopublishRunning()) {
47
- return false; // Lock is active
48
- }
49
- }
50
-
51
- // Create lock file
52
- fs.mkdirSync(path.dirname(lockFile), { recursive: true });
53
- fs.writeFileSync(lockFile, JSON.stringify({
54
- pid: process.pid,
55
- timestamp: Date.now()
56
- }));
57
-
58
- return true;
59
- } catch (err) {
60
- return false;
61
- }
62
- }
63
-
64
- /**
65
- * Release the autopublish lock file.
66
- */
67
- export function releaseAutopublishLock() {
68
- const lockFile = getLockFilePath();
69
-
70
- try {
71
- if (fs.existsSync(lockFile)) {
72
- fs.unlinkSync(lockFile);
73
- }
74
- } catch {
75
- // Ignore errors during cleanup
76
- }
77
- }
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ const LOCK_FILE_NAME = 'autopublish.lock';
5
+ const LOCK_EXPIRY_MS = 3600000; // 1 hour
6
+
7
+ /**
8
+ * Get the path to the autopublish lock file.
9
+ * @returns {string} - Path to lock file
10
+ */
11
+ export function getLockFilePath() {
12
+ return path.join(process.cwd(), '.magentrix', LOCK_FILE_NAME);
13
+ }
14
+
15
+ /**
16
+ * Check if autopublish is currently running.
17
+ * @returns {boolean} - True if autopublish is running (lock file exists and is not stale)
18
+ */
19
+ export function isAutopublishRunning() {
20
+ const lockFile = getLockFilePath();
21
+
22
+ if (!fs.existsSync(lockFile)) return false;
23
+
24
+ try {
25
+ const lockData = JSON.parse(fs.readFileSync(lockFile, 'utf-8'));
26
+ const lockAge = Date.now() - lockData.timestamp;
27
+
28
+ // If lock is older than 1 hour, consider it stale
29
+ return lockAge < LOCK_EXPIRY_MS;
30
+ } catch {
31
+ // If we can't read the lock file, assume it's not running
32
+ return false;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Create a lock file to prevent multiple autopublish instances.
38
+ * @returns {boolean} - True if lock was acquired, false if already locked
39
+ */
40
+ export function acquireAutopublishLock() {
41
+ const lockFile = getLockFilePath();
42
+
43
+ try {
44
+ if (fs.existsSync(lockFile)) {
45
+ // Check if the lock is stale (process might have crashed)
46
+ if (isAutopublishRunning()) {
47
+ return false; // Lock is active
48
+ }
49
+ }
50
+
51
+ // Create lock file
52
+ fs.mkdirSync(path.dirname(lockFile), { recursive: true });
53
+ fs.writeFileSync(lockFile, JSON.stringify({
54
+ pid: process.pid,
55
+ timestamp: Date.now()
56
+ }));
57
+
58
+ return true;
59
+ } catch (err) {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Release the autopublish lock file.
66
+ */
67
+ export function releaseAutopublishLock() {
68
+ const lockFile = getLockFilePath();
69
+
70
+ try {
71
+ if (fs.existsSync(lockFile)) {
72
+ fs.unlinkSync(lockFile);
73
+ }
74
+ } catch {
75
+ // Ignore errors during cleanup
76
+ }
77
+ }