@magentrix-corp/magentrix-cli 1.2.1 → 1.3.0
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/README.md +282 -2
- package/actions/autopublish.js +9 -48
- package/actions/iris/buildStage.js +330 -0
- package/actions/iris/delete.js +211 -0
- package/actions/iris/dev.js +338 -0
- package/actions/iris/index.js +6 -0
- package/actions/iris/link.js +377 -0
- package/actions/iris/recover.js +228 -0
- package/actions/publish.js +183 -9
- package/actions/pull.js +107 -4
- package/bin/magentrix.js +43 -1
- package/package.json +2 -1
- package/utils/autopublishLock.js +77 -0
- package/utils/cli/helpers/compare.js +4 -5
- package/utils/iris/backup.js +201 -0
- package/utils/iris/builder.js +304 -0
- package/utils/iris/config-reader.js +296 -0
- package/utils/iris/deleteHelper.js +102 -0
- package/utils/iris/linker.js +490 -0
- package/utils/iris/validator.js +281 -0
- package/utils/iris/zipper.js +239 -0
- package/utils/logger.js +13 -5
- package/utils/magentrix/api/iris.js +235 -0
- package/utils/permissionError.js +70 -0
- package/utils/progress.js +87 -1
- package/utils/updateFileBase.js +10 -2
- package/vars/global.js +1 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { fetchMagentrix } from "../fetch.js";
|
|
2
|
+
import { File } from "node:buffer";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* List all deployed Iris applications.
|
|
6
|
+
* @param {string} instanceUrl - Magentrix instance base URL
|
|
7
|
+
* @param {string} token - OAuth2 bearer token
|
|
8
|
+
* @returns {Promise<{success: boolean, apps: Array<{folderName: string, uploadedOn: string, modifiedOn: string, size: number}>}>}
|
|
9
|
+
*/
|
|
10
|
+
export const listApps = async (instanceUrl, token) => {
|
|
11
|
+
if (!instanceUrl || !token) {
|
|
12
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const response = await fetchMagentrix({
|
|
16
|
+
instanceUrl,
|
|
17
|
+
token,
|
|
18
|
+
path: '/iris/listapps',
|
|
19
|
+
method: 'GET',
|
|
20
|
+
returnErrorObject: true
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return response;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Publish (upload) an Iris Vue.js application.
|
|
28
|
+
* @param {string} instanceUrl - Magentrix instance base URL
|
|
29
|
+
* @param {string} token - OAuth2 bearer token
|
|
30
|
+
* @param {Buffer} zipBuffer - The zip file as a Buffer
|
|
31
|
+
* @param {string} filename - The filename for the zip (e.g., "my-app.zip")
|
|
32
|
+
* @param {string} appName - The user-friendly display name (required for navigation)
|
|
33
|
+
* @returns {Promise<{success: boolean, message: string, folderName: string}>}
|
|
34
|
+
*/
|
|
35
|
+
export const publishApp = async (instanceUrl, token, zipBuffer, filename, appName) => {
|
|
36
|
+
if (!instanceUrl || !token) {
|
|
37
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!zipBuffer || !Buffer.isBuffer(zipBuffer)) {
|
|
41
|
+
throw new Error('zipBuffer must be a valid Buffer');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!filename) {
|
|
45
|
+
throw new Error('filename is required');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!appName) {
|
|
49
|
+
throw new Error('appName is required for navigation menu updates');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Create a File object from the buffer for FormData
|
|
53
|
+
const file = new File([zipBuffer], filename, { type: 'application/zip' });
|
|
54
|
+
|
|
55
|
+
const formData = new FormData();
|
|
56
|
+
formData.append('file', file);
|
|
57
|
+
|
|
58
|
+
// app-name is passed as URL parameter (required for navigation)
|
|
59
|
+
const response = await fetchMagentrix({
|
|
60
|
+
instanceUrl,
|
|
61
|
+
token,
|
|
62
|
+
path: `/iris/publishapp?app-name=${encodeURIComponent(appName)}`,
|
|
63
|
+
method: 'POST',
|
|
64
|
+
body: formData,
|
|
65
|
+
ignoreContentType: true,
|
|
66
|
+
returnErrorObject: true
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return response;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Delete an Iris application.
|
|
74
|
+
* @param {string} instanceUrl - Magentrix instance base URL
|
|
75
|
+
* @param {string} token - OAuth2 bearer token
|
|
76
|
+
* @param {string} folderName - The folder name of the app to delete
|
|
77
|
+
* @returns {Promise<{success: boolean, message: string}>}
|
|
78
|
+
*/
|
|
79
|
+
export const deleteApp = async (instanceUrl, token, folderName) => {
|
|
80
|
+
if (!instanceUrl || !token) {
|
|
81
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!folderName) {
|
|
85
|
+
throw new Error('folderName is required');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const response = await fetchMagentrix({
|
|
89
|
+
instanceUrl,
|
|
90
|
+
token,
|
|
91
|
+
path: `/iris/deleteapp?folderName=${encodeURIComponent(folderName)}`,
|
|
92
|
+
method: 'POST',
|
|
93
|
+
returnErrorObject: true
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return response;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Download a single Iris application as a buffer.
|
|
101
|
+
* @param {string} instanceUrl - Magentrix instance base URL
|
|
102
|
+
* @param {string} token - OAuth2 bearer token
|
|
103
|
+
* @param {string} folderName - The folder name of the app to download
|
|
104
|
+
* @returns {Promise<{buffer: Buffer, filename: string}>}
|
|
105
|
+
*/
|
|
106
|
+
export const downloadApp = async (instanceUrl, token, folderName) => {
|
|
107
|
+
if (!instanceUrl || !token) {
|
|
108
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!folderName) {
|
|
112
|
+
throw new Error('folderName is required');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const url = `${instanceUrl.replace(/\/$/, '')}/iris/downloadapp?folderName=${encodeURIComponent(folderName)}`;
|
|
116
|
+
|
|
117
|
+
const response = await fetch(url, {
|
|
118
|
+
method: 'GET',
|
|
119
|
+
headers: {
|
|
120
|
+
'Authorization': `Bearer ${token}`
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
let errorMessage = `Download failed (${response.status})`;
|
|
126
|
+
try {
|
|
127
|
+
const errorData = await response.json();
|
|
128
|
+
if (errorData.message) {
|
|
129
|
+
errorMessage = errorData.message;
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
errorMessage = `Download failed: ${response.statusText}`;
|
|
133
|
+
}
|
|
134
|
+
throw new Error(errorMessage);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Get filename from Content-Disposition header
|
|
138
|
+
const contentDisposition = response.headers.get('content-disposition') || '';
|
|
139
|
+
const filenameMatch = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(contentDisposition);
|
|
140
|
+
const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : `${folderName}.zip`;
|
|
141
|
+
|
|
142
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
143
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
144
|
+
|
|
145
|
+
return { buffer, filename };
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Download all Iris applications as a single zip buffer.
|
|
150
|
+
* @param {string} instanceUrl - Magentrix instance base URL
|
|
151
|
+
* @param {string} token - OAuth2 bearer token
|
|
152
|
+
* @returns {Promise<{buffer: Buffer, filename: string}>}
|
|
153
|
+
*/
|
|
154
|
+
export const downloadAllApps = async (instanceUrl, token) => {
|
|
155
|
+
if (!instanceUrl || !token) {
|
|
156
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const url = `${instanceUrl.replace(/\/$/, '')}/iris/downloadallapps`;
|
|
160
|
+
|
|
161
|
+
const response = await fetch(url, {
|
|
162
|
+
method: 'GET',
|
|
163
|
+
headers: {
|
|
164
|
+
'Authorization': `Bearer ${token}`
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
let errorMessage = `Download failed (${response.status})`;
|
|
170
|
+
try {
|
|
171
|
+
const errorData = await response.json();
|
|
172
|
+
if (errorData.message) {
|
|
173
|
+
errorMessage = errorData.message;
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
errorMessage = `Download failed: ${response.statusText}`;
|
|
177
|
+
}
|
|
178
|
+
throw new Error(errorMessage);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Get filename from Content-Disposition header
|
|
182
|
+
const contentDisposition = response.headers.get('content-disposition') || '';
|
|
183
|
+
const filenameMatch = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(contentDisposition);
|
|
184
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
185
|
+
const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : `iris-apps_${timestamp}.zip`;
|
|
186
|
+
|
|
187
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
188
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
189
|
+
|
|
190
|
+
return { buffer, filename };
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get platform assets for local development (CSS, fonts, etc.).
|
|
195
|
+
* @param {string} siteUrl - Magentrix instance base URL (from Vue config.ts)
|
|
196
|
+
* @param {string} token - OAuth2 bearer token
|
|
197
|
+
* @returns {Promise<{success: boolean, assets: string[]}>}
|
|
198
|
+
*/
|
|
199
|
+
export const getIrisAssets = async (siteUrl, token) => {
|
|
200
|
+
if (!siteUrl) {
|
|
201
|
+
throw new Error('Missing required siteUrl');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const url = `${siteUrl.replace(/\/$/, '')}/iris/getirisassets`;
|
|
205
|
+
|
|
206
|
+
const headers = {
|
|
207
|
+
'Accept': 'application/json'
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Token is optional for this endpoint (might be public)
|
|
211
|
+
if (token) {
|
|
212
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const response = await fetch(url, {
|
|
216
|
+
method: 'GET',
|
|
217
|
+
headers
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (!response.ok) {
|
|
221
|
+
let errorMessage = `Failed to fetch Iris assets (${response.status})`;
|
|
222
|
+
try {
|
|
223
|
+
const errorData = await response.json();
|
|
224
|
+
if (errorData.message) {
|
|
225
|
+
errorMessage = errorData.message;
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
errorMessage = `Failed to fetch Iris assets: ${response.statusText}`;
|
|
229
|
+
}
|
|
230
|
+
throw new Error(errorMessage);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const data = await response.json();
|
|
234
|
+
return data;
|
|
235
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Display a user-friendly permission error message with platform-specific guidance.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} options - Options for the error message
|
|
8
|
+
* @param {string} options.operation - What operation failed ('delete' or 'restore')
|
|
9
|
+
* @param {string} options.targetPath - The path that couldn't be accessed
|
|
10
|
+
* @param {string} [options.backupPath] - Optional backup path (for restore operations)
|
|
11
|
+
* @param {string} [options.slug] - Optional app slug (for restore operations)
|
|
12
|
+
*/
|
|
13
|
+
export function showPermissionError({ operation, targetPath, backupPath, slug }) {
|
|
14
|
+
const platform = os.platform();
|
|
15
|
+
const username = os.userInfo().username;
|
|
16
|
+
const group = platform === 'darwin' ? 'staff' : username;
|
|
17
|
+
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(chalk.bgYellow.bold.black(' ⚠ Permission Denied '));
|
|
20
|
+
console.log(chalk.yellow('─'.repeat(48)));
|
|
21
|
+
console.log(chalk.white(`Cannot ${operation} files due to permission issues.`));
|
|
22
|
+
console.log();
|
|
23
|
+
console.log(chalk.white('This may happen if:'));
|
|
24
|
+
console.log(chalk.gray(' • A process has the folder locked'));
|
|
25
|
+
console.log(chalk.gray(' • The folder is owned by another user (e.g., root)'));
|
|
26
|
+
if (platform === 'darwin') {
|
|
27
|
+
console.log(chalk.gray(' • macOS Gatekeeper is blocking the operation'));
|
|
28
|
+
}
|
|
29
|
+
console.log();
|
|
30
|
+
console.log(chalk.white('To fix this, try:'));
|
|
31
|
+
console.log(chalk.cyan(' 1. Close any editors that have the folder open'));
|
|
32
|
+
console.log(chalk.cyan(' 2. Run the command again'));
|
|
33
|
+
console.log();
|
|
34
|
+
|
|
35
|
+
// Platform-specific ownership fix
|
|
36
|
+
if (platform === 'win32') {
|
|
37
|
+
console.log(chalk.white('If the folder has wrong ownership, run as Administrator:'));
|
|
38
|
+
console.log(chalk.cyan(` icacls "${targetPath}" /grant ${username}:F /T`));
|
|
39
|
+
} else {
|
|
40
|
+
console.log(chalk.white('If the folder has wrong ownership, run:'));
|
|
41
|
+
console.log(chalk.cyan(` sudo chown -R ${username}:${group} "${targetPath}"`));
|
|
42
|
+
}
|
|
43
|
+
console.log();
|
|
44
|
+
|
|
45
|
+
// Operation-specific manual commands
|
|
46
|
+
if (operation === 'delete') {
|
|
47
|
+
console.log(chalk.white('Or delete manually:'));
|
|
48
|
+
if (platform === 'win32') {
|
|
49
|
+
console.log(chalk.cyan(` rmdir /s /q "${targetPath}"`));
|
|
50
|
+
} else {
|
|
51
|
+
console.log(chalk.cyan(` rm -rf "${targetPath}"`));
|
|
52
|
+
}
|
|
53
|
+
} else if (operation === 'restore' && backupPath && slug) {
|
|
54
|
+
console.log(chalk.white('Or manually restore:'));
|
|
55
|
+
const zipPath = `${backupPath}/${slug}.zip`;
|
|
56
|
+
const destDir = targetPath.replace(/[/\\][^/\\]+$/, ''); // Parent directory
|
|
57
|
+
if (platform === 'win32') {
|
|
58
|
+
console.log(chalk.cyan(` tar -xf "${zipPath}" -C "${destDir}"`));
|
|
59
|
+
} else {
|
|
60
|
+
console.log(chalk.cyan(` unzip "${zipPath}" -d "${destDir}"`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(chalk.yellow('─'.repeat(48)));
|
|
65
|
+
|
|
66
|
+
if (backupPath) {
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(chalk.gray(`Backup preserved at: ${backupPath}`));
|
|
69
|
+
}
|
|
70
|
+
}
|
package/utils/progress.js
CHANGED
|
@@ -40,6 +40,7 @@ export class ProgressTracker {
|
|
|
40
40
|
this.interval = null;
|
|
41
41
|
this.lastRenderLength = 0;
|
|
42
42
|
this.isFinished = false;
|
|
43
|
+
this.issues = []; // Collected issues/warnings to display at end
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
@@ -147,6 +148,36 @@ export class ProgressTracker {
|
|
|
147
148
|
this.render();
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Add an issue/warning to be displayed at the end
|
|
153
|
+
* @param {string} type - 'error' | 'warning' | 'info'
|
|
154
|
+
* @param {string} message - The issue message
|
|
155
|
+
* @param {string} [hint] - Optional hint for resolution
|
|
156
|
+
*/
|
|
157
|
+
addIssue(type, message, hint = null) {
|
|
158
|
+
this.issues.push({ type, message, hint });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check if there are any issues collected
|
|
163
|
+
* @returns {boolean}
|
|
164
|
+
*/
|
|
165
|
+
hasIssues() {
|
|
166
|
+
return this.issues.length > 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get issues by type
|
|
171
|
+
* @param {string} type - 'error' | 'warning' | 'info'
|
|
172
|
+
* @returns {Array}
|
|
173
|
+
*/
|
|
174
|
+
getIssues(type = null) {
|
|
175
|
+
if (type) {
|
|
176
|
+
return this.issues.filter(i => i.type === type);
|
|
177
|
+
}
|
|
178
|
+
return this.issues;
|
|
179
|
+
}
|
|
180
|
+
|
|
150
181
|
/**
|
|
151
182
|
* Update the message for the current step
|
|
152
183
|
* @param {string} message - New message
|
|
@@ -289,8 +320,12 @@ export class ProgressTracker {
|
|
|
289
320
|
/**
|
|
290
321
|
* Finish the progress tracker
|
|
291
322
|
* @param {string} message - Optional final message
|
|
323
|
+
* @param {object} options - Optional configuration
|
|
324
|
+
* @param {boolean} options.showIssues - Whether to show collected issues (default: false)
|
|
292
325
|
*/
|
|
293
|
-
finish(message = '') {
|
|
326
|
+
finish(message = '', options = {}) {
|
|
327
|
+
const { showIssues = false } = options;
|
|
328
|
+
|
|
294
329
|
this.stopSpinner();
|
|
295
330
|
this.isFinished = true;
|
|
296
331
|
|
|
@@ -318,6 +353,57 @@ export class ProgressTracker {
|
|
|
318
353
|
console.log(chalk.green(`✓ All ${completed} step(s) completed successfully`));
|
|
319
354
|
}
|
|
320
355
|
console.log('');
|
|
356
|
+
|
|
357
|
+
// Only display collected issues if explicitly requested
|
|
358
|
+
if (showIssues && this.issues.length > 0) {
|
|
359
|
+
this.displayIssues();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Display collected issues
|
|
365
|
+
* Can be called manually after finish() if needed
|
|
366
|
+
*/
|
|
367
|
+
displayIssues() {
|
|
368
|
+
if (this.issues.length === 0) return;
|
|
369
|
+
|
|
370
|
+
const errors = this.issues.filter(i => i.type === 'error');
|
|
371
|
+
const warnings = this.issues.filter(i => i.type === 'warning');
|
|
372
|
+
const infos = this.issues.filter(i => i.type === 'info');
|
|
373
|
+
|
|
374
|
+
console.log(chalk.bold('Issues Summary:'));
|
|
375
|
+
|
|
376
|
+
if (errors.length > 0) {
|
|
377
|
+
console.log(chalk.red(` ✗ ${errors.length} error(s)`));
|
|
378
|
+
errors.forEach((issue, i) => {
|
|
379
|
+
console.log(chalk.red(` ${i + 1}. ${issue.message}`));
|
|
380
|
+
if (issue.hint) {
|
|
381
|
+
console.log(chalk.yellow(` ${issue.hint}`));
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (warnings.length > 0) {
|
|
387
|
+
console.log(chalk.yellow(` ⚠ ${warnings.length} warning(s)`));
|
|
388
|
+
warnings.forEach((issue, i) => {
|
|
389
|
+
console.log(chalk.yellow(` ${i + 1}. ${issue.message}`));
|
|
390
|
+
if (issue.hint) {
|
|
391
|
+
console.log(chalk.gray(` ${issue.hint}`));
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (infos.length > 0) {
|
|
397
|
+
console.log(chalk.cyan(` ℹ ${infos.length} info(s)`));
|
|
398
|
+
infos.forEach((issue, i) => {
|
|
399
|
+
console.log(chalk.cyan(` ${i + 1}. ${issue.message}`));
|
|
400
|
+
if (issue.hint) {
|
|
401
|
+
console.log(chalk.gray(` ${issue.hint}`));
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
console.log('');
|
|
321
407
|
}
|
|
322
408
|
|
|
323
409
|
/**
|
package/utils/updateFileBase.js
CHANGED
|
@@ -65,9 +65,9 @@ export const updateBase = (filePath, record, actualPath = '', contentSnapshot =
|
|
|
65
65
|
// Use snapshot if provided (to avoid race conditions), otherwise read from disk
|
|
66
66
|
let fileContent, contentHash;
|
|
67
67
|
if (isDirectory) {
|
|
68
|
-
// Folders don't have content
|
|
68
|
+
// Folders don't have content - use provided contentHash if any (e.g., for Iris apps)
|
|
69
69
|
fileContent = '';
|
|
70
|
-
contentHash = '';
|
|
70
|
+
contentHash = record.contentHash || '';
|
|
71
71
|
} else if (contentSnapshot && contentSnapshot.content) {
|
|
72
72
|
// Use the snapshot of what was actually published
|
|
73
73
|
fileContent = contentSnapshot.content;
|
|
@@ -91,6 +91,14 @@ export const updateBase = (filePath, record, actualPath = '', contentSnapshot =
|
|
|
91
91
|
filePath,
|
|
92
92
|
lastKnownActualPath: fileSystemLocation,
|
|
93
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
|
+
}
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
if (saveData.type === 'File' || saveData.type === 'Folder') delete saveData.compressedContent;
|
package/vars/global.js
CHANGED
|
@@ -4,6 +4,7 @@ export const CWD = process.cwd();
|
|
|
4
4
|
export const HASHED_CWD = sha256(CWD);
|
|
5
5
|
export const EXPORT_ROOT = "src";
|
|
6
6
|
export const ASSETS_DIR = "Assets"; // Local directory name for static assets (API uses /contents/assets)
|
|
7
|
+
export const IRIS_APPS_DIR = "iris-apps"; // Local directory for Iris Vue.js apps
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Maps Magentrix Type fields to local folder names and extensions.
|