@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.
- package/LICENSE +25 -25
- package/README.md +1166 -1166
- package/actions/autopublish.old.js +293 -293
- package/actions/config.js +182 -182
- package/actions/create.js +466 -466
- package/actions/help.js +164 -164
- package/actions/iris/buildStage.js +874 -874
- package/actions/iris/delete.js +256 -256
- package/actions/iris/dev.js +391 -391
- package/actions/iris/index.js +6 -6
- package/actions/iris/link.js +375 -375
- package/actions/iris/recover.js +268 -268
- package/actions/main.js +80 -80
- package/actions/publish.js +1420 -1420
- package/actions/pull.js +684 -684
- package/actions/setup.js +148 -148
- package/actions/status.js +17 -17
- package/actions/update.js +248 -248
- package/bin/magentrix.js +393 -393
- package/package.json +55 -55
- package/utils/assetPaths.js +158 -158
- package/utils/autopublishLock.js +77 -77
- package/utils/cacher.js +206 -206
- package/utils/cli/checkInstanceUrl.js +76 -74
- package/utils/cli/helpers/compare.js +282 -282
- package/utils/cli/helpers/ensureApiKey.js +63 -63
- package/utils/cli/helpers/ensureCredentials.js +68 -68
- package/utils/cli/helpers/ensureInstanceUrl.js +75 -75
- package/utils/cli/writeRecords.js +262 -262
- package/utils/compare.js +135 -135
- package/utils/compress.js +17 -17
- package/utils/config.js +527 -527
- package/utils/debug.js +144 -144
- package/utils/diagnostics/testPublishLogic.js +96 -96
- package/utils/diff.js +49 -49
- package/utils/downloadAssets.js +291 -291
- package/utils/filetag.js +115 -115
- package/utils/hash.js +14 -14
- package/utils/iris/backup.js +411 -411
- package/utils/iris/builder.js +541 -541
- package/utils/iris/config-reader.js +664 -664
- package/utils/iris/deleteHelper.js +150 -150
- package/utils/iris/errors.js +537 -537
- package/utils/iris/linker.js +601 -601
- package/utils/iris/lock.js +360 -360
- package/utils/iris/validation.js +360 -360
- package/utils/iris/validator.js +281 -281
- package/utils/iris/zipper.js +248 -248
- package/utils/logger.js +291 -291
- package/utils/magentrix/api/assets.js +220 -220
- package/utils/magentrix/api/auth.js +107 -107
- package/utils/magentrix/api/createEntity.js +61 -61
- package/utils/magentrix/api/deleteEntity.js +55 -55
- package/utils/magentrix/api/iris.js +251 -251
- package/utils/magentrix/api/meqlQuery.js +36 -36
- package/utils/magentrix/api/retrieveEntity.js +86 -86
- package/utils/magentrix/api/updateEntity.js +66 -66
- package/utils/magentrix/fetch.js +168 -168
- package/utils/merge.js +22 -22
- package/utils/permissionError.js +70 -70
- package/utils/preferences.js +40 -40
- package/utils/progress.js +469 -469
- package/utils/spinner.js +43 -43
- package/utils/template.js +52 -52
- package/utils/updateFileBase.js +121 -121
- package/utils/workspaces.js +108 -108
- package/vars/config.js +11 -11
- package/vars/global.js +50 -50
package/utils/spinner.js
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
export async function withSpinner(message, fn, config = { showCompletion: true }) {
|
|
2
|
-
const spinnerChars = ['|', '/', '-', '\\'];
|
|
3
|
-
let i = 0;
|
|
4
|
-
let spinnerActive = true;
|
|
5
|
-
let spinner;
|
|
6
|
-
|
|
7
|
-
// Patch both log and error for full coverage
|
|
8
|
-
const originalLog = console.log;
|
|
9
|
-
const originalError = console.error;
|
|
10
|
-
function clearSpinnerLine() {
|
|
11
|
-
process.stdout.write('\r' + ' '.repeat(message.length + 8) + '\r');
|
|
12
|
-
if (spinnerActive) {
|
|
13
|
-
clearInterval(spinner);
|
|
14
|
-
spinnerActive = false;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
console.log = (...args) => { clearSpinnerLine(); originalLog(...args); };
|
|
18
|
-
console.error = (...args) => { clearSpinnerLine(); originalError(...args); };
|
|
19
|
-
|
|
20
|
-
process.stdout.write(`${message} `);
|
|
21
|
-
|
|
22
|
-
spinner = setInterval(() => {
|
|
23
|
-
process.stdout.write(`\r${message} ${spinnerChars[i++ % spinnerChars.length]}`);
|
|
24
|
-
}, 80);
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const result = await fn();
|
|
28
|
-
const treatAsError = result?.hasErrors;
|
|
29
|
-
spinnerActive = false;
|
|
30
|
-
clearInterval(spinner);
|
|
31
|
-
if (config?.showCompletion) process.stdout.write(`\r${message} ${treatAsError ? '❌' : '✅'}\n`);
|
|
32
|
-
return result;
|
|
33
|
-
} catch (err) {
|
|
34
|
-
spinnerActive = false;
|
|
35
|
-
clearInterval(spinner);
|
|
36
|
-
if (config?.showCompletion) process.stdout.write(`\r${message} ❌\n`);
|
|
37
|
-
throw err;
|
|
38
|
-
} finally {
|
|
39
|
-
// Restore logs always
|
|
40
|
-
console.log = originalLog;
|
|
41
|
-
console.error = originalError;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
export async function withSpinner(message, fn, config = { showCompletion: true }) {
|
|
2
|
+
const spinnerChars = ['|', '/', '-', '\\'];
|
|
3
|
+
let i = 0;
|
|
4
|
+
let spinnerActive = true;
|
|
5
|
+
let spinner;
|
|
6
|
+
|
|
7
|
+
// Patch both log and error for full coverage
|
|
8
|
+
const originalLog = console.log;
|
|
9
|
+
const originalError = console.error;
|
|
10
|
+
function clearSpinnerLine() {
|
|
11
|
+
process.stdout.write('\r' + ' '.repeat(message.length + 8) + '\r');
|
|
12
|
+
if (spinnerActive) {
|
|
13
|
+
clearInterval(spinner);
|
|
14
|
+
spinnerActive = false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
console.log = (...args) => { clearSpinnerLine(); originalLog(...args); };
|
|
18
|
+
console.error = (...args) => { clearSpinnerLine(); originalError(...args); };
|
|
19
|
+
|
|
20
|
+
process.stdout.write(`${message} `);
|
|
21
|
+
|
|
22
|
+
spinner = setInterval(() => {
|
|
23
|
+
process.stdout.write(`\r${message} ${spinnerChars[i++ % spinnerChars.length]}`);
|
|
24
|
+
}, 80);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const result = await fn();
|
|
28
|
+
const treatAsError = result?.hasErrors;
|
|
29
|
+
spinnerActive = false;
|
|
30
|
+
clearInterval(spinner);
|
|
31
|
+
if (config?.showCompletion) process.stdout.write(`\r${message} ${treatAsError ? '❌' : '✅'}\n`);
|
|
32
|
+
return result;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
spinnerActive = false;
|
|
35
|
+
clearInterval(spinner);
|
|
36
|
+
if (config?.showCompletion) process.stdout.write(`\r${message} ❌\n`);
|
|
37
|
+
throw err;
|
|
38
|
+
} finally {
|
|
39
|
+
// Restore logs always
|
|
40
|
+
console.log = originalLog;
|
|
41
|
+
console.error = originalError;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/utils/template.js
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns the default template for an ActiveClass Controller.
|
|
3
|
-
* @param {string} className
|
|
4
|
-
* @returns {string}
|
|
5
|
-
*/
|
|
6
|
-
export const getControllerTemplate = (className) => `
|
|
7
|
-
public class ${className} : AspxController {
|
|
8
|
-
public override ActionResponse Index()
|
|
9
|
-
{
|
|
10
|
-
return View();
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
`.trim();
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Returns the default template for an ActiveClass Trigger.
|
|
17
|
-
* @param {string} className - For instance ContactTrigger
|
|
18
|
-
* @param {string} entityName - For instance Force__Contact
|
|
19
|
-
* @returns {string}
|
|
20
|
-
*/
|
|
21
|
-
export const getTriggerTemplate = (className, entityName = '<ENTITYNAME>') => `
|
|
22
|
-
public class ${className} : ActiveTrigger<${entityName}>
|
|
23
|
-
{
|
|
24
|
-
public override void Execute(TransactionContext<${entityName}> trigger) {
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
`.trim();
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Returns the default template for a general ActiveClass.
|
|
32
|
-
* @param {string} className
|
|
33
|
-
* @returns {string}
|
|
34
|
-
*/
|
|
35
|
-
export const getClassTemplate = (className) => `
|
|
36
|
-
public class ${className} {
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
`.trim();
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Returns the default template for an Active Page.
|
|
43
|
-
* @param {string} pageName
|
|
44
|
-
* @param {string} pageLabel
|
|
45
|
-
* @returns {string}
|
|
46
|
-
*/
|
|
47
|
-
export const getPageTemplate = (pageName, pageLabel) => `
|
|
48
|
-
<aspx:AspxPage runat="server" Id="${pageName}" title="${pageLabel}">
|
|
49
|
-
<body>
|
|
50
|
-
</body>
|
|
51
|
-
</aspx:AspxPage>
|
|
52
|
-
`.trim();
|
|
1
|
+
/**
|
|
2
|
+
* Returns the default template for an ActiveClass Controller.
|
|
3
|
+
* @param {string} className
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export const getControllerTemplate = (className) => `
|
|
7
|
+
public class ${className} : AspxController {
|
|
8
|
+
public override ActionResponse Index()
|
|
9
|
+
{
|
|
10
|
+
return View();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
`.trim();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns the default template for an ActiveClass Trigger.
|
|
17
|
+
* @param {string} className - For instance ContactTrigger
|
|
18
|
+
* @param {string} entityName - For instance Force__Contact
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
export const getTriggerTemplate = (className, entityName = '<ENTITYNAME>') => `
|
|
22
|
+
public class ${className} : ActiveTrigger<${entityName}>
|
|
23
|
+
{
|
|
24
|
+
public override void Execute(TransactionContext<${entityName}> trigger) {
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`.trim();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the default template for a general ActiveClass.
|
|
32
|
+
* @param {string} className
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
export const getClassTemplate = (className) => `
|
|
36
|
+
public class ${className} {
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
`.trim();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns the default template for an Active Page.
|
|
43
|
+
* @param {string} pageName
|
|
44
|
+
* @param {string} pageLabel
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
export const getPageTemplate = (pageName, pageLabel) => `
|
|
48
|
+
<aspx:AspxPage runat="server" Id="${pageName}" title="${pageLabel}">
|
|
49
|
+
<body>
|
|
50
|
+
</body>
|
|
51
|
+
</aspx:AspxPage>
|
|
52
|
+
`.trim();
|
package/utils/updateFileBase.js
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
import { EXPORT_ROOT } from "../vars/global.js";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { sha256 } from "./hash.js";
|
|
5
|
-
import Config from "./config.js";
|
|
6
|
-
import { compressString } from "./compress.js";
|
|
7
|
-
|
|
8
|
-
const config = new Config();
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Recursively collects all file paths under a directory, returning relative paths from base.
|
|
12
|
-
*
|
|
13
|
-
* @param {string} dirPath - Absolute or relative path to the root directory to scan.
|
|
14
|
-
* @param {string} [basePath=''] - Internal use. The base path for recursion, relative to dirPath.
|
|
15
|
-
* @returns {string[]} Array of relative file paths found under dirPath.
|
|
16
|
-
*/
|
|
17
|
-
export function getAllFiles(dirPath, basePath = "") {
|
|
18
|
-
let results = [];
|
|
19
|
-
const fullDirPath = path.join(dirPath, basePath);
|
|
20
|
-
const entries = fs.readdirSync(fullDirPath, { withFileTypes: true });
|
|
21
|
-
|
|
22
|
-
for (const entry of entries) {
|
|
23
|
-
const relPath = path.join(basePath, entry.name);
|
|
24
|
-
const entryPath = path.join(dirPath, relPath);
|
|
25
|
-
|
|
26
|
-
if (entry.isDirectory()) {
|
|
27
|
-
// Recurse into subdirectory
|
|
28
|
-
results = results.concat(getAllFiles(dirPath, relPath));
|
|
29
|
-
} else if (entry.isFile()) {
|
|
30
|
-
// Add file's relative path to results
|
|
31
|
-
results.push(relPath);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return results;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Updates (or creates) the base sync state for a specific file.
|
|
39
|
-
*
|
|
40
|
-
* Saves the current file's lastModified time, content hash, and contents
|
|
41
|
-
* to base.json using config.save(), under the key of the file's relative path.
|
|
42
|
-
*
|
|
43
|
-
* This should be called only when the local and remote file are confirmed to be in sync
|
|
44
|
-
* (i.e., after a successful pull, push, or conflict resolution).
|
|
45
|
-
*
|
|
46
|
-
* @param {string} filePath - The expected path to the file (relative to project root or EXPORT_ROOT).
|
|
47
|
-
* @param {object} record - The Magentrix record
|
|
48
|
-
* @param {string} actualPath - Should only be provided if the file has been renamed and the base has not been updated
|
|
49
|
-
* @param {object} contentSnapshot - Optional { content, hash } snapshot of what was actually published (prevents race conditions)
|
|
50
|
-
*/
|
|
51
|
-
export const updateBase = (filePath, record, actualPath = '', contentSnapshot = null) => {
|
|
52
|
-
// This is the true location of the file
|
|
53
|
-
const fileSystemLocation = actualPath || path.resolve(filePath);
|
|
54
|
-
|
|
55
|
-
if (!fs.existsSync(fileSystemLocation)) {
|
|
56
|
-
// Silently skip files that don't exist - they may have failed to download
|
|
57
|
-
// This is expected behavior for files that returned 404 during download
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Get file stats for mtime
|
|
62
|
-
const fileStats = fs.statSync(fileSystemLocation);
|
|
63
|
-
const isDirectory = fileStats.isDirectory();
|
|
64
|
-
|
|
65
|
-
// Use snapshot if provided (to avoid race conditions), otherwise read from disk
|
|
66
|
-
let fileContent, contentHash;
|
|
67
|
-
if (isDirectory) {
|
|
68
|
-
// Folders don't have content - use provided contentHash if any (e.g., for Iris apps)
|
|
69
|
-
fileContent = '';
|
|
70
|
-
contentHash = record.contentHash || '';
|
|
71
|
-
} else if (contentSnapshot && contentSnapshot.content) {
|
|
72
|
-
// Use the snapshot of what was actually published
|
|
73
|
-
fileContent = contentSnapshot.content;
|
|
74
|
-
contentHash = contentSnapshot.hash;
|
|
75
|
-
} else {
|
|
76
|
-
// Read from disk (normal behavior)
|
|
77
|
-
fileContent = fs.readFileSync(fileSystemLocation, "utf-8");
|
|
78
|
-
contentHash = sha256(fileContent);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Save sync metadata and content to base.json via config manager.
|
|
82
|
-
// - key: the relative path to the file
|
|
83
|
-
// - value: lastModified, contentHash, and full content
|
|
84
|
-
// - options: ensure writing to base.json
|
|
85
|
-
const saveData = {
|
|
86
|
-
lastModified: fileStats.mtimeMs,
|
|
87
|
-
contentHash,
|
|
88
|
-
compressedContent: isDirectory ? '' : compressString(fileContent),
|
|
89
|
-
recordId: record.Id,
|
|
90
|
-
type: record.Type,
|
|
91
|
-
filePath,
|
|
92
|
-
lastKnownActualPath: fileSystemLocation,
|
|
93
|
-
lastKnownPath: path.resolve(filePath)
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Preserve custom fields from record (e.g., for Iris apps: folderName, appName, modifiedOn)
|
|
97
|
-
const customFields = ['folderName', 'appName', 'modifiedOn', 'uploadedOn', 'size'];
|
|
98
|
-
for (const field of customFields) {
|
|
99
|
-
if (record[field] !== undefined) {
|
|
100
|
-
saveData[field] = record[field];
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (saveData.type === 'File' || saveData.type === 'Folder') delete saveData.compressedContent;
|
|
105
|
-
|
|
106
|
-
config.save(
|
|
107
|
-
record.Id,
|
|
108
|
-
saveData,
|
|
109
|
-
{
|
|
110
|
-
filename: "base.json"
|
|
111
|
-
}
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
export const removeFromBase = (recordId) => {
|
|
116
|
-
config.removeKey(recordId, { filename: "base.json" });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export const removeFromBaseBulk = (recordIds) => {
|
|
120
|
-
config.removeKeys(recordIds, { filename: "base.json" });
|
|
121
|
-
}
|
|
1
|
+
import { EXPORT_ROOT } from "../vars/global.js";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { sha256 } from "./hash.js";
|
|
5
|
+
import Config from "./config.js";
|
|
6
|
+
import { compressString } from "./compress.js";
|
|
7
|
+
|
|
8
|
+
const config = new Config();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Recursively collects all file paths under a directory, returning relative paths from base.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} dirPath - Absolute or relative path to the root directory to scan.
|
|
14
|
+
* @param {string} [basePath=''] - Internal use. The base path for recursion, relative to dirPath.
|
|
15
|
+
* @returns {string[]} Array of relative file paths found under dirPath.
|
|
16
|
+
*/
|
|
17
|
+
export function getAllFiles(dirPath, basePath = "") {
|
|
18
|
+
let results = [];
|
|
19
|
+
const fullDirPath = path.join(dirPath, basePath);
|
|
20
|
+
const entries = fs.readdirSync(fullDirPath, { withFileTypes: true });
|
|
21
|
+
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const relPath = path.join(basePath, entry.name);
|
|
24
|
+
const entryPath = path.join(dirPath, relPath);
|
|
25
|
+
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
// Recurse into subdirectory
|
|
28
|
+
results = results.concat(getAllFiles(dirPath, relPath));
|
|
29
|
+
} else if (entry.isFile()) {
|
|
30
|
+
// Add file's relative path to results
|
|
31
|
+
results.push(relPath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Updates (or creates) the base sync state for a specific file.
|
|
39
|
+
*
|
|
40
|
+
* Saves the current file's lastModified time, content hash, and contents
|
|
41
|
+
* to base.json using config.save(), under the key of the file's relative path.
|
|
42
|
+
*
|
|
43
|
+
* This should be called only when the local and remote file are confirmed to be in sync
|
|
44
|
+
* (i.e., after a successful pull, push, or conflict resolution).
|
|
45
|
+
*
|
|
46
|
+
* @param {string} filePath - The expected path to the file (relative to project root or EXPORT_ROOT).
|
|
47
|
+
* @param {object} record - The Magentrix record
|
|
48
|
+
* @param {string} actualPath - Should only be provided if the file has been renamed and the base has not been updated
|
|
49
|
+
* @param {object} contentSnapshot - Optional { content, hash } snapshot of what was actually published (prevents race conditions)
|
|
50
|
+
*/
|
|
51
|
+
export const updateBase = (filePath, record, actualPath = '', contentSnapshot = null) => {
|
|
52
|
+
// This is the true location of the file
|
|
53
|
+
const fileSystemLocation = actualPath || path.resolve(filePath);
|
|
54
|
+
|
|
55
|
+
if (!fs.existsSync(fileSystemLocation)) {
|
|
56
|
+
// Silently skip files that don't exist - they may have failed to download
|
|
57
|
+
// This is expected behavior for files that returned 404 during download
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Get file stats for mtime
|
|
62
|
+
const fileStats = fs.statSync(fileSystemLocation);
|
|
63
|
+
const isDirectory = fileStats.isDirectory();
|
|
64
|
+
|
|
65
|
+
// Use snapshot if provided (to avoid race conditions), otherwise read from disk
|
|
66
|
+
let fileContent, contentHash;
|
|
67
|
+
if (isDirectory) {
|
|
68
|
+
// Folders don't have content - use provided contentHash if any (e.g., for Iris apps)
|
|
69
|
+
fileContent = '';
|
|
70
|
+
contentHash = record.contentHash || '';
|
|
71
|
+
} else if (contentSnapshot && contentSnapshot.content) {
|
|
72
|
+
// Use the snapshot of what was actually published
|
|
73
|
+
fileContent = contentSnapshot.content;
|
|
74
|
+
contentHash = contentSnapshot.hash;
|
|
75
|
+
} else {
|
|
76
|
+
// Read from disk (normal behavior)
|
|
77
|
+
fileContent = fs.readFileSync(fileSystemLocation, "utf-8");
|
|
78
|
+
contentHash = sha256(fileContent);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Save sync metadata and content to base.json via config manager.
|
|
82
|
+
// - key: the relative path to the file
|
|
83
|
+
// - value: lastModified, contentHash, and full content
|
|
84
|
+
// - options: ensure writing to base.json
|
|
85
|
+
const saveData = {
|
|
86
|
+
lastModified: fileStats.mtimeMs,
|
|
87
|
+
contentHash,
|
|
88
|
+
compressedContent: isDirectory ? '' : compressString(fileContent),
|
|
89
|
+
recordId: record.Id,
|
|
90
|
+
type: record.Type,
|
|
91
|
+
filePath,
|
|
92
|
+
lastKnownActualPath: fileSystemLocation,
|
|
93
|
+
lastKnownPath: path.resolve(filePath)
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Preserve custom fields from record (e.g., for Iris apps: folderName, appName, modifiedOn)
|
|
97
|
+
const customFields = ['folderName', 'appName', 'modifiedOn', 'uploadedOn', 'size'];
|
|
98
|
+
for (const field of customFields) {
|
|
99
|
+
if (record[field] !== undefined) {
|
|
100
|
+
saveData[field] = record[field];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (saveData.type === 'File' || saveData.type === 'Folder') delete saveData.compressedContent;
|
|
105
|
+
|
|
106
|
+
config.save(
|
|
107
|
+
record.Id,
|
|
108
|
+
saveData,
|
|
109
|
+
{
|
|
110
|
+
filename: "base.json"
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const removeFromBase = (recordId) => {
|
|
116
|
+
config.removeKey(recordId, { filename: "base.json" });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const removeFromBaseBulk = (recordIds) => {
|
|
120
|
+
config.removeKeys(recordIds, { filename: "base.json" });
|
|
121
|
+
}
|
package/utils/workspaces.js
CHANGED
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import Config from './config.js';
|
|
4
|
-
|
|
5
|
-
const config = new Config();
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Register a workspace in the global registry.
|
|
9
|
-
* This stores the workspace path so it can be discovered later.
|
|
10
|
-
*
|
|
11
|
-
* @param {string} workspacePath - Full path to the workspace
|
|
12
|
-
* @param {string} instanceUrl - The Magentrix instance URL for this workspace
|
|
13
|
-
*/
|
|
14
|
-
export function registerWorkspace(workspacePath, instanceUrl) {
|
|
15
|
-
const workspaces = config.read('workspaces', { global: true }) || [];
|
|
16
|
-
|
|
17
|
-
// Check if already registered
|
|
18
|
-
const existingIndex = workspaces.findIndex(w => w.path === workspacePath);
|
|
19
|
-
|
|
20
|
-
if (existingIndex >= 0) {
|
|
21
|
-
// Update existing entry
|
|
22
|
-
workspaces[existingIndex] = {
|
|
23
|
-
path: workspacePath,
|
|
24
|
-
instanceUrl,
|
|
25
|
-
lastUsed: new Date().toISOString()
|
|
26
|
-
};
|
|
27
|
-
} else {
|
|
28
|
-
// Add new entry
|
|
29
|
-
workspaces.push({
|
|
30
|
-
path: workspacePath,
|
|
31
|
-
instanceUrl,
|
|
32
|
-
lastUsed: new Date().toISOString()
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
config.save('workspaces', workspaces, { global: true });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get all registered workspaces from the global registry.
|
|
41
|
-
* Validates that each workspace still exists on disk.
|
|
42
|
-
*
|
|
43
|
-
* @returns {Array<{path: string, instanceUrl: string, lastUsed: string, valid: boolean}>}
|
|
44
|
-
*/
|
|
45
|
-
export function getRegisteredWorkspaces() {
|
|
46
|
-
const workspaces = config.read('workspaces', { global: true }) || [];
|
|
47
|
-
|
|
48
|
-
return workspaces.map(workspace => {
|
|
49
|
-
// Check if the workspace still exists and is valid
|
|
50
|
-
const magentrixFolder = join(workspace.path, '.magentrix');
|
|
51
|
-
const srcFolder = join(workspace.path, 'src');
|
|
52
|
-
|
|
53
|
-
const valid = existsSync(magentrixFolder) && existsSync(srcFolder);
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
...workspace,
|
|
57
|
-
valid
|
|
58
|
-
};
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Get all valid registered workspaces (those that still exist on disk).
|
|
64
|
-
*
|
|
65
|
-
* @returns {Array<{path: string, instanceUrl: string, lastUsed: string, valid: boolean}>}
|
|
66
|
-
*/
|
|
67
|
-
export function getValidWorkspaces() {
|
|
68
|
-
return getRegisteredWorkspaces().filter(w => w.valid);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Remove a workspace from the global registry.
|
|
73
|
-
*
|
|
74
|
-
* @param {string} workspacePath - Path to the workspace to remove
|
|
75
|
-
* @returns {boolean} True if removed, false if not found
|
|
76
|
-
*/
|
|
77
|
-
export function unregisterWorkspace(workspacePath) {
|
|
78
|
-
const workspaces = config.read('workspaces', { global: true }) || [];
|
|
79
|
-
const initialLength = workspaces.length;
|
|
80
|
-
|
|
81
|
-
const filtered = workspaces.filter(w => w.path !== workspacePath);
|
|
82
|
-
|
|
83
|
-
if (filtered.length < initialLength) {
|
|
84
|
-
config.save('workspaces', filtered, { global: true });
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Clean up invalid workspaces (those that no longer exist on disk).
|
|
93
|
-
*
|
|
94
|
-
* @returns {number} Number of workspaces removed
|
|
95
|
-
*/
|
|
96
|
-
export function cleanupInvalidWorkspaces() {
|
|
97
|
-
const workspaces = getRegisteredWorkspaces();
|
|
98
|
-
const validWorkspaces = workspaces.filter(w => w.valid);
|
|
99
|
-
const removedCount = workspaces.length - validWorkspaces.length;
|
|
100
|
-
|
|
101
|
-
if (removedCount > 0) {
|
|
102
|
-
// Remove the 'valid' property before saving
|
|
103
|
-
const toSave = validWorkspaces.map(({ valid, ...rest }) => rest);
|
|
104
|
-
config.save('workspaces', toSave, { global: true });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return removedCount;
|
|
108
|
-
}
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import Config from './config.js';
|
|
4
|
+
|
|
5
|
+
const config = new Config();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register a workspace in the global registry.
|
|
9
|
+
* This stores the workspace path so it can be discovered later.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} workspacePath - Full path to the workspace
|
|
12
|
+
* @param {string} instanceUrl - The Magentrix instance URL for this workspace
|
|
13
|
+
*/
|
|
14
|
+
export function registerWorkspace(workspacePath, instanceUrl) {
|
|
15
|
+
const workspaces = config.read('workspaces', { global: true }) || [];
|
|
16
|
+
|
|
17
|
+
// Check if already registered
|
|
18
|
+
const existingIndex = workspaces.findIndex(w => w.path === workspacePath);
|
|
19
|
+
|
|
20
|
+
if (existingIndex >= 0) {
|
|
21
|
+
// Update existing entry
|
|
22
|
+
workspaces[existingIndex] = {
|
|
23
|
+
path: workspacePath,
|
|
24
|
+
instanceUrl,
|
|
25
|
+
lastUsed: new Date().toISOString()
|
|
26
|
+
};
|
|
27
|
+
} else {
|
|
28
|
+
// Add new entry
|
|
29
|
+
workspaces.push({
|
|
30
|
+
path: workspacePath,
|
|
31
|
+
instanceUrl,
|
|
32
|
+
lastUsed: new Date().toISOString()
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
config.save('workspaces', workspaces, { global: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get all registered workspaces from the global registry.
|
|
41
|
+
* Validates that each workspace still exists on disk.
|
|
42
|
+
*
|
|
43
|
+
* @returns {Array<{path: string, instanceUrl: string, lastUsed: string, valid: boolean}>}
|
|
44
|
+
*/
|
|
45
|
+
export function getRegisteredWorkspaces() {
|
|
46
|
+
const workspaces = config.read('workspaces', { global: true }) || [];
|
|
47
|
+
|
|
48
|
+
return workspaces.map(workspace => {
|
|
49
|
+
// Check if the workspace still exists and is valid
|
|
50
|
+
const magentrixFolder = join(workspace.path, '.magentrix');
|
|
51
|
+
const srcFolder = join(workspace.path, 'src');
|
|
52
|
+
|
|
53
|
+
const valid = existsSync(magentrixFolder) && existsSync(srcFolder);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...workspace,
|
|
57
|
+
valid
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all valid registered workspaces (those that still exist on disk).
|
|
64
|
+
*
|
|
65
|
+
* @returns {Array<{path: string, instanceUrl: string, lastUsed: string, valid: boolean}>}
|
|
66
|
+
*/
|
|
67
|
+
export function getValidWorkspaces() {
|
|
68
|
+
return getRegisteredWorkspaces().filter(w => w.valid);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Remove a workspace from the global registry.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} workspacePath - Path to the workspace to remove
|
|
75
|
+
* @returns {boolean} True if removed, false if not found
|
|
76
|
+
*/
|
|
77
|
+
export function unregisterWorkspace(workspacePath) {
|
|
78
|
+
const workspaces = config.read('workspaces', { global: true }) || [];
|
|
79
|
+
const initialLength = workspaces.length;
|
|
80
|
+
|
|
81
|
+
const filtered = workspaces.filter(w => w.path !== workspacePath);
|
|
82
|
+
|
|
83
|
+
if (filtered.length < initialLength) {
|
|
84
|
+
config.save('workspaces', filtered, { global: true });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clean up invalid workspaces (those that no longer exist on disk).
|
|
93
|
+
*
|
|
94
|
+
* @returns {number} Number of workspaces removed
|
|
95
|
+
*/
|
|
96
|
+
export function cleanupInvalidWorkspaces() {
|
|
97
|
+
const workspaces = getRegisteredWorkspaces();
|
|
98
|
+
const validWorkspaces = workspaces.filter(w => w.valid);
|
|
99
|
+
const removedCount = workspaces.length - validWorkspaces.length;
|
|
100
|
+
|
|
101
|
+
if (removedCount > 0) {
|
|
102
|
+
// Remove the 'valid' property before saving
|
|
103
|
+
const toSave = validWorkspaces.map(({ valid, ...rest }) => rest);
|
|
104
|
+
config.save('workspaces', toSave, { global: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return removedCount;
|
|
108
|
+
}
|