@magentrix-corp/magentrix-cli 1.3.11 → 1.3.13
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/actions/iris/buildStage.js +22 -3
- package/actions/publish.js +43 -21
- package/actions/pull.js +31 -22
- package/bin/magentrix.js +8 -0
- package/package.json +1 -1
- package/utils/cacher.js +28 -6
- package/utils/cli/writeRecords.js +1 -1
- package/utils/iris/zipper.js +10 -1
- package/utils/magentrix/api/iris.js +7 -5
- package/vars/global.js +16 -0
|
@@ -435,11 +435,23 @@ async function buildFromVueProject(options) {
|
|
|
435
435
|
// This ensures pull doesn't overwrite our staged files
|
|
436
436
|
console.log();
|
|
437
437
|
console.log(chalk.gray('Checking workspace sync status...'));
|
|
438
|
+
|
|
439
|
+
// Check for a previous incomplete pull first — `magentrix status` only checks
|
|
440
|
+
// code entities, so a partial pull that synced code but not assets would falsely
|
|
441
|
+
// report "in sync". The marker file catches this case.
|
|
442
|
+
const workspaceConfig = new Config({ projectDir: workspacePath });
|
|
443
|
+
const hadIncompletePull = workspaceConfig.read('pullIncomplete', { global: false, filename: 'config.json' });
|
|
444
|
+
|
|
438
445
|
const syncStatus = await checkWorkspaceSyncStatus(workspacePath);
|
|
446
|
+
const needsPull = syncStatus.needsPull || !!hadIncompletePull;
|
|
439
447
|
|
|
440
|
-
if (
|
|
448
|
+
if (needsPull) {
|
|
441
449
|
console.log();
|
|
442
|
-
|
|
450
|
+
if (hadIncompletePull && !syncStatus.needsPull) {
|
|
451
|
+
console.log(chalk.yellow('⚠ A previous pull did not complete. Your workspace may be out of sync.'));
|
|
452
|
+
} else {
|
|
453
|
+
console.log(chalk.yellow('⚠ Your workspace may be out of sync with the server.'));
|
|
454
|
+
}
|
|
443
455
|
|
|
444
456
|
const shouldPull = await confirm({
|
|
445
457
|
message: 'Would you like to pull latest changes first?',
|
|
@@ -451,9 +463,16 @@ async function buildFromVueProject(options) {
|
|
|
451
463
|
console.log(chalk.blue('Running pull from workspace...'));
|
|
452
464
|
console.log();
|
|
453
465
|
|
|
466
|
+
// Mark pull as in-progress before starting
|
|
467
|
+
workspaceConfig.save('pullIncomplete', true, { global: false, filename: 'config.json' });
|
|
468
|
+
|
|
454
469
|
const pullSuccess = await runCommandFromWorkspace(workspacePath, 'pull');
|
|
455
470
|
|
|
456
|
-
if (
|
|
471
|
+
if (pullSuccess) {
|
|
472
|
+
// Pull completed successfully — clear the marker
|
|
473
|
+
workspaceConfig.removeKey('pullIncomplete', { filename: 'config.json' });
|
|
474
|
+
} else {
|
|
475
|
+
// Pull failed or was cancelled — marker stays for next run
|
|
457
476
|
console.log();
|
|
458
477
|
console.log(chalk.yellow('Pull encountered issues. You may want to resolve them manually.'));
|
|
459
478
|
|
package/actions/publish.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
EXPORT_ROOT,
|
|
13
13
|
TYPE_DIR_MAP,
|
|
14
14
|
IRIS_APPS_DIR,
|
|
15
|
+
ALLOWED_SRC_DIRS,
|
|
15
16
|
} from "../vars/global.js";
|
|
16
17
|
import { getFileTag, setFileTag } from "../utils/filetag.js";
|
|
17
18
|
import { sha256 } from "../utils/hash.js";
|
|
@@ -198,9 +199,14 @@ const handleDeleteStaticAssetAction = async (instanceUrl, apiKey, action) => {
|
|
|
198
199
|
|
|
199
200
|
if (isNotFound) {
|
|
200
201
|
// Clean up base.json since file doesn't exist on server
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
removeFromBase(
|
|
202
|
+
// Use the original base.json key if available (avoids path format mismatches)
|
|
203
|
+
if (action.baseKey) {
|
|
204
|
+
removeFromBase(action.baseKey);
|
|
205
|
+
} else {
|
|
206
|
+
for (const name of action.names) {
|
|
207
|
+
const filePath = action.filePath || `${EXPORT_ROOT}/${action.folder}/${name}`;
|
|
208
|
+
removeFromBase(filePath);
|
|
209
|
+
}
|
|
204
210
|
}
|
|
205
211
|
return { cleanedFromCache: true };
|
|
206
212
|
}
|
|
@@ -252,7 +258,8 @@ const handleDeleteFolderAction = async (instanceUrl, apiKey, action) => {
|
|
|
252
258
|
|
|
253
259
|
if (isNotFound) {
|
|
254
260
|
// Clean up base.json since folder doesn't exist on server
|
|
255
|
-
|
|
261
|
+
// Use original base.json key if available (avoids path format mismatches)
|
|
262
|
+
removeFromBase(action.baseKey || action.folderPath);
|
|
256
263
|
|
|
257
264
|
// Also remove all files and subfolders inside this folder from base
|
|
258
265
|
const hits = await config.searchObject({}, { filename: "base.json", global: false });
|
|
@@ -288,14 +295,19 @@ const handlePublishIrisAppAction = async (instanceUrl, apiKey, action) => {
|
|
|
288
295
|
// Create zip from the app folder
|
|
289
296
|
const zipBuffer = await createIrisZip(action.appPath, action.slug);
|
|
290
297
|
|
|
291
|
-
//
|
|
298
|
+
// For updates, don't send app-name/description/icon to avoid triggering
|
|
299
|
+
// metadata modification that may require permissions the user doesn't have.
|
|
300
|
+
// The server already has the correct metadata from the original create.
|
|
301
|
+
// For creates, send all metadata so the app is properly registered.
|
|
302
|
+
const isUpdate = action.action === 'update_iris_app';
|
|
303
|
+
|
|
292
304
|
const response = await publishApp(
|
|
293
305
|
instanceUrl,
|
|
294
306
|
apiKey,
|
|
295
307
|
zipBuffer,
|
|
296
308
|
`${action.slug}.zip`,
|
|
297
|
-
action.appName,
|
|
298
|
-
{
|
|
309
|
+
isUpdate ? null : action.appName,
|
|
310
|
+
isUpdate ? {} : {
|
|
299
311
|
appDescription: action.appDescription,
|
|
300
312
|
appIconId: action.appIconId
|
|
301
313
|
}
|
|
@@ -424,9 +436,14 @@ const updateCacheAfterSuccess = async (action, operationResult) => {
|
|
|
424
436
|
case "delete_static_asset":
|
|
425
437
|
// Skip if already cleaned from cache during 404 handling
|
|
426
438
|
if (!operationResult?.cleanedFromCache) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
removeFromBase(
|
|
439
|
+
// Use the original base.json key if available (avoids path format mismatches)
|
|
440
|
+
if (action.baseKey) {
|
|
441
|
+
removeFromBase(action.baseKey);
|
|
442
|
+
} else {
|
|
443
|
+
for (const name of action.names) {
|
|
444
|
+
const filePath = action.filePath || `${EXPORT_ROOT}/${action.folder}/${name}`;
|
|
445
|
+
removeFromBase(filePath);
|
|
446
|
+
}
|
|
430
447
|
}
|
|
431
448
|
}
|
|
432
449
|
break;
|
|
@@ -442,8 +459,8 @@ const updateCacheAfterSuccess = async (action, operationResult) => {
|
|
|
442
459
|
case "delete_folder": {
|
|
443
460
|
// Skip if already cleaned from cache during 404 handling
|
|
444
461
|
if (!operationResult?.cleanedFromCache) {
|
|
445
|
-
// Remove the folder itself from base using
|
|
446
|
-
removeFromBase(action.folderPath);
|
|
462
|
+
// Remove the folder itself from base using the original base.json key
|
|
463
|
+
removeFromBase(action.baseKey || action.folderPath);
|
|
447
464
|
|
|
448
465
|
// Also remove all files and subfolders inside this folder from base
|
|
449
466
|
const hits = await config.searchObject({}, { filename: "base.json", global: false });
|
|
@@ -870,16 +887,19 @@ export const runPublish = async (options = {}) => {
|
|
|
870
887
|
progress.completeStep('load', `✓ Loaded ${cachedFiles.length} entries (${loadTime}ms load, ${mapTime}ms map)`);
|
|
871
888
|
}
|
|
872
889
|
|
|
873
|
-
// Step 3: Scan local workspace (
|
|
890
|
+
// Step 3: Scan local workspace (only whitelisted code entity directories)
|
|
874
891
|
if (progress) progress.startStep('scan');
|
|
875
892
|
|
|
876
893
|
const walkStart = Date.now();
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
894
|
+
// Only scan whitelisted directories for code entities (exclude Assets and iris-apps, handled separately)
|
|
895
|
+
const codeEntityDirs = ALLOWED_SRC_DIRS.filter(dir => dir !== 'Assets' && dir !== IRIS_APPS_DIR);
|
|
896
|
+
const localPathArrays = await Promise.all(
|
|
897
|
+
codeEntityDirs.map(dir => {
|
|
898
|
+
const dirPath = path.join(EXPORT_ROOT, dir);
|
|
899
|
+
return fs.existsSync(dirPath) ? walkFiles(dirPath) : Promise.resolve([]);
|
|
900
|
+
})
|
|
901
|
+
);
|
|
902
|
+
const localPaths = localPathArrays.flat();
|
|
883
903
|
const walkTime = Date.now() - walkStart;
|
|
884
904
|
|
|
885
905
|
const tagStart = Date.now();
|
|
@@ -1025,7 +1045,8 @@ export const runPublish = async (options = {}) => {
|
|
|
1025
1045
|
action: "delete_folder",
|
|
1026
1046
|
folderPath: cachedPath,
|
|
1027
1047
|
parentPath: toApiFolderPath(parentDir),
|
|
1028
|
-
folderName: path.basename(cachedPath)
|
|
1048
|
+
folderName: path.basename(cachedPath),
|
|
1049
|
+
baseKey: cachedFolder.tag // The original base.json key for correct cache cleanup
|
|
1029
1050
|
});
|
|
1030
1051
|
}
|
|
1031
1052
|
}
|
|
@@ -1063,7 +1084,8 @@ export const runPublish = async (options = {}) => {
|
|
|
1063
1084
|
action: 'delete_static_asset',
|
|
1064
1085
|
folder: toApiPath(actualPath),
|
|
1065
1086
|
names: [path.basename(actualPath)],
|
|
1066
|
-
filePath: actualPath // Store actual file path for filtering
|
|
1087
|
+
filePath: actualPath, // Store actual file path for filtering
|
|
1088
|
+
baseKey: id // The original base.json key for correct cache cleanup
|
|
1067
1089
|
});
|
|
1068
1090
|
continue;
|
|
1069
1091
|
}
|
package/actions/pull.js
CHANGED
|
@@ -128,9 +128,12 @@ export const pull = async () => {
|
|
|
128
128
|
progress.startStep('load');
|
|
129
129
|
const hits = await config.searchObject({}, { filename: "base.json", global: false });
|
|
130
130
|
const cachedResults = hits?.[0]?.value || {};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
// Include the base.json key (_baseKey) alongside each entry so we can
|
|
132
|
+
// use the correct key for removeFromBaseBulk later. The keys may differ
|
|
133
|
+
// from lastKnownActualPath/filePath due to path format inconsistencies.
|
|
134
|
+
const cachedAssets = Object.entries(cachedResults)
|
|
135
|
+
.filter(([, c]) => c.type === 'File' || c.type === 'Folder')
|
|
136
|
+
.map(([key, c]) => ({ ...c, _baseKey: key }));
|
|
134
137
|
progress.completeStep('load', `✓ Loaded ${Object.keys(cachedResults).length} cached entries`);
|
|
135
138
|
|
|
136
139
|
// Step 4a: Download code entities (ActiveClass and ActivePage)
|
|
@@ -338,7 +341,7 @@ export const pull = async () => {
|
|
|
338
341
|
|
|
339
342
|
// If this asset was in base.json but not returned from server, it was deleted
|
|
340
343
|
if (!serverAssetPaths.has(cachedPath)) {
|
|
341
|
-
assetsToDelete.push(cachedPath);
|
|
344
|
+
assetsToDelete.push({ path: cachedPath, baseKey: cached._baseKey });
|
|
342
345
|
}
|
|
343
346
|
|
|
344
347
|
processedCount++;
|
|
@@ -402,33 +405,35 @@ export const pull = async () => {
|
|
|
402
405
|
// Delete local files/folders that were deleted on server
|
|
403
406
|
logger.info(`Starting deletion of ${assetsToDelete.length} assets`);
|
|
404
407
|
const deletionLogs = [];
|
|
405
|
-
for
|
|
408
|
+
// Track base.json keys for entries that need removal
|
|
409
|
+
const assetBaseKeysToRemove = [];
|
|
410
|
+
for (const asset of assetsToDelete) {
|
|
406
411
|
try {
|
|
407
|
-
if (fs.existsSync(
|
|
408
|
-
const stats = fs.statSync(
|
|
412
|
+
if (fs.existsSync(asset.path)) {
|
|
413
|
+
const stats = fs.statSync(asset.path);
|
|
409
414
|
if (stats.isDirectory()) {
|
|
410
|
-
fs.rmSync(
|
|
411
|
-
deletionLogs.push({ type: 'folder', path:
|
|
412
|
-
logger.info('Deleted folder', { path:
|
|
415
|
+
fs.rmSync(asset.path, { recursive: true, force: true });
|
|
416
|
+
deletionLogs.push({ type: 'folder', path: asset.path });
|
|
417
|
+
logger.info('Deleted folder', { path: asset.path });
|
|
413
418
|
} else {
|
|
414
|
-
fs.unlinkSync(
|
|
415
|
-
deletionLogs.push({ type: 'file', path:
|
|
416
|
-
logger.info('Deleted file', { path:
|
|
419
|
+
fs.unlinkSync(asset.path);
|
|
420
|
+
deletionLogs.push({ type: 'file', path: asset.path });
|
|
421
|
+
logger.info('Deleted file', { path: asset.path });
|
|
417
422
|
}
|
|
418
423
|
}
|
|
424
|
+
// Always remove from base.json regardless of whether the local file
|
|
425
|
+
// existed — the server confirmed deletion, so the cache entry is stale.
|
|
426
|
+
assetBaseKeysToRemove.push(asset.baseKey);
|
|
419
427
|
} catch (err) {
|
|
420
|
-
deletionLogs.push({ type: 'error', path:
|
|
421
|
-
logger.error(`Failed to delete asset: ${
|
|
428
|
+
deletionLogs.push({ type: 'error', path: asset.path, error: err.message });
|
|
429
|
+
logger.error(`Failed to delete asset: ${asset.path}`, err);
|
|
422
430
|
}
|
|
423
431
|
}
|
|
424
432
|
|
|
425
|
-
// Bulk remove from base.json
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (assetPathsToRemove.length > 0) {
|
|
431
|
-
removeFromBaseBulk(assetPathsToRemove);
|
|
433
|
+
// Bulk remove from base.json using the original base.json keys
|
|
434
|
+
// (not the normalized paths, which may have a different format)
|
|
435
|
+
if (assetBaseKeysToRemove.length > 0) {
|
|
436
|
+
removeFromBaseBulk(assetBaseKeysToRemove);
|
|
432
437
|
}
|
|
433
438
|
|
|
434
439
|
// Delete local code entity files that were deleted on server
|
|
@@ -639,6 +644,10 @@ export const pull = async () => {
|
|
|
639
644
|
logger.close();
|
|
640
645
|
progress.finish('Pull completed successfully!');
|
|
641
646
|
|
|
647
|
+
// Clear any incomplete-pull marker (set by vue-run-build when a pull is
|
|
648
|
+
// started but cancelled/fails). A successful pull means we're fully synced.
|
|
649
|
+
config.removeKey('pullIncomplete', { filename: 'config.json' });
|
|
650
|
+
|
|
642
651
|
// Summary
|
|
643
652
|
console.log(chalk.bold(`Summary:`));
|
|
644
653
|
console.log(` • ${activeClassResult.Records.length} ActiveClass records`);
|
package/bin/magentrix.js
CHANGED
|
@@ -20,6 +20,7 @@ import { configWizard } from '../actions/config.js';
|
|
|
20
20
|
import { irisLink, irisDev, irisDelete, irisRecover, vueBuildStage } from '../actions/iris/index.js';
|
|
21
21
|
import Config from '../utils/config.js';
|
|
22
22
|
import { registerWorkspace, getRegisteredWorkspaces } from '../utils/workspaces.js';
|
|
23
|
+
import { ensureVSCodeFileAssociation } from '../utils/preferences.js';
|
|
23
24
|
|
|
24
25
|
const config = new Config();
|
|
25
26
|
|
|
@@ -102,6 +103,13 @@ function ensureWorkspaceRegistered() {
|
|
|
102
103
|
|
|
103
104
|
async function preMiddleware() {
|
|
104
105
|
ensureWorkspaceRegistered();
|
|
106
|
+
|
|
107
|
+
// Ensure .vscode folder exists in project root (not in src/) for Magentrix projects
|
|
108
|
+
const magentrixDir = join(CWD, '.magentrix');
|
|
109
|
+
if (existsSync(magentrixDir)) {
|
|
110
|
+
await ensureVSCodeFileAssociation(CWD);
|
|
111
|
+
}
|
|
112
|
+
|
|
105
113
|
await recacheFileIdIndex(EXPORT_ROOT);
|
|
106
114
|
await cacheDir(EXPORT_ROOT);
|
|
107
115
|
}
|
package/package.json
CHANGED
package/utils/cacher.js
CHANGED
|
@@ -4,10 +4,13 @@ import Config from './config.js';
|
|
|
4
4
|
import { findFileByTag, getFileTag, isPathLinkedToTagByLastKnownPath, setFileTag } from './filetag.js';
|
|
5
5
|
import { compressString } from './compress.js';
|
|
6
6
|
import { sha256 } from './hash.js';
|
|
7
|
-
import { EXPORT_ROOT } from '../vars/global.js';
|
|
7
|
+
import { EXPORT_ROOT, ALLOWED_SRC_DIRS, IRIS_APPS_DIR } from '../vars/global.js';
|
|
8
8
|
|
|
9
9
|
const config = new Config();
|
|
10
10
|
|
|
11
|
+
// System/hidden files that should never be synced
|
|
12
|
+
const IGNORED_FILES = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
15
|
* Recursively caches all files in a directory with tagging and content snapshotting
|
|
13
16
|
* OPTIMIZED: Parallel processing + mtime checks for maximum speed
|
|
@@ -19,8 +22,15 @@ export const cacheDir = async (dir) => {
|
|
|
19
22
|
|
|
20
23
|
const absDir = path.resolve(dir);
|
|
21
24
|
|
|
22
|
-
//
|
|
23
|
-
const
|
|
25
|
+
// Only walk whitelisted code entity directories (exclude Assets and iris-apps, handled separately)
|
|
26
|
+
const codeEntityDirs = ALLOWED_SRC_DIRS.filter(d => d !== 'Assets' && d !== IRIS_APPS_DIR);
|
|
27
|
+
const fileArrays = await Promise.all(
|
|
28
|
+
codeEntityDirs.map(d => {
|
|
29
|
+
const dirPath = path.join(absDir, d);
|
|
30
|
+
return fs.existsSync(dirPath) ? walkFiles(dirPath) : Promise.resolve([]);
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
const files = fileArrays.flat();
|
|
24
34
|
|
|
25
35
|
const cache = config.read('cachedFiles', { global: false, filename: 'fileCache.json' }) || {};
|
|
26
36
|
|
|
@@ -108,10 +118,17 @@ export const cacheDir = async (dir) => {
|
|
|
108
118
|
* @returns {Promise<void>}
|
|
109
119
|
*/
|
|
110
120
|
export const recacheFileIdIndex = async (dir) => {
|
|
111
|
-
// Exclude Assets folder - they don't use file tags, tracked in base.json instead
|
|
112
121
|
const absDir = path.resolve(dir);
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
|
|
123
|
+
// Only walk whitelisted code entity directories (exclude Assets and iris-apps, handled separately)
|
|
124
|
+
const codeEntityDirs = ALLOWED_SRC_DIRS.filter(d => d !== 'Assets' && d !== IRIS_APPS_DIR);
|
|
125
|
+
const fileArrays = await Promise.all(
|
|
126
|
+
codeEntityDirs.map(d => {
|
|
127
|
+
const dirPath = path.join(absDir, d);
|
|
128
|
+
return fs.existsSync(dirPath) ? walkFiles(dirPath) : Promise.resolve([]);
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
const files = fileArrays.flat();
|
|
115
132
|
if (!files || files?.length < 1) return;
|
|
116
133
|
|
|
117
134
|
// Process files in parallel batches of 50 for speed
|
|
@@ -165,6 +182,11 @@ export async function walkFiles(dir, settings) {
|
|
|
165
182
|
for (const entry of entries) {
|
|
166
183
|
const fullPath = path.join(dir, entry.name);
|
|
167
184
|
|
|
185
|
+
// Skip OS-generated system files (.DS_Store, Thumbs.db, etc.)
|
|
186
|
+
if (IGNORED_FILES.has(entry.name)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
168
190
|
// Check if this path should be ignored
|
|
169
191
|
if (ignore.find(p => fullPath.startsWith(p) || fullPath === p)) {
|
|
170
192
|
continue;
|
|
@@ -245,7 +245,7 @@ export const writeRecords = async (records, resolutionMethod, progress = null, l
|
|
|
245
245
|
updateBase(filePath, record, cachedFilePath);
|
|
246
246
|
}
|
|
247
247
|
} catch (err) {
|
|
248
|
-
const msg = `Failed to write file ${
|
|
248
|
+
const msg = `Failed to write file ${filePath}: ${err.message}`;
|
|
249
249
|
if (logger) {
|
|
250
250
|
logger.error(msg, err);
|
|
251
251
|
} else {
|
package/utils/iris/zipper.js
CHANGED
|
@@ -5,6 +5,9 @@ import { randomUUID, createHash } from 'node:crypto';
|
|
|
5
5
|
import archiver from 'archiver';
|
|
6
6
|
import extractZip from 'extract-zip';
|
|
7
7
|
|
|
8
|
+
// System/hidden files that should never be included in IRIS app zips or hashes
|
|
9
|
+
const IGNORED_FILES = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* Recursively fix permissions on extracted files.
|
|
10
13
|
* Sets directories to 0o755 and files to 0o644.
|
|
@@ -61,7 +64,10 @@ export async function createIrisZip(distPath, appSlug) {
|
|
|
61
64
|
|
|
62
65
|
// Add the dist directory contents under the app slug folder
|
|
63
66
|
// This creates the structure: appSlug/remoteEntry.js, appSlug/assets/...
|
|
64
|
-
|
|
67
|
+
// Filter out OS-generated system files (.DS_Store, Thumbs.db, etc.)
|
|
68
|
+
archive.directory(distPath, appSlug, (entry) =>
|
|
69
|
+
IGNORED_FILES.has(basename(entry.name)) ? false : entry
|
|
70
|
+
);
|
|
65
71
|
|
|
66
72
|
archive.finalize();
|
|
67
73
|
});
|
|
@@ -195,6 +201,9 @@ export function getFilesRecursive(dir, basePath = dir) {
|
|
|
195
201
|
function walk(currentDir) {
|
|
196
202
|
const entries = readdirSync(currentDir);
|
|
197
203
|
for (const entry of entries) {
|
|
204
|
+
// Skip OS-generated system files
|
|
205
|
+
if (IGNORED_FILES.has(entry)) continue;
|
|
206
|
+
|
|
198
207
|
const fullPath = join(currentDir, entry);
|
|
199
208
|
const stat = statSync(fullPath);
|
|
200
209
|
if (stat.isDirectory()) {
|
|
@@ -48,10 +48,6 @@ export const publishApp = async (instanceUrl, token, zipBuffer, filename, appNam
|
|
|
48
48
|
throw new Error('filename is required');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
if (!appName) {
|
|
52
|
-
throw new Error('appName is required for navigation menu updates');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
51
|
// Create a File object from the buffer for FormData
|
|
56
52
|
const file = new File([zipBuffer], filename, { type: 'application/zip' });
|
|
57
53
|
|
|
@@ -59,8 +55,14 @@ export const publishApp = async (instanceUrl, token, zipBuffer, filename, appNam
|
|
|
59
55
|
formData.append('file', file);
|
|
60
56
|
|
|
61
57
|
// Build query parameters
|
|
58
|
+
// Note: app-name is only sent when provided (required for creates, optional for updates)
|
|
59
|
+
// Sending app-name on updates can trigger a rename attempt that fails if the user
|
|
60
|
+
// lacks metadata-edit permissions on the server
|
|
62
61
|
const params = new URLSearchParams();
|
|
63
|
-
|
|
62
|
+
|
|
63
|
+
if (appName) {
|
|
64
|
+
params.append('app-name', appName);
|
|
65
|
+
}
|
|
64
66
|
|
|
65
67
|
if (options.appDescription) {
|
|
66
68
|
params.append('app-description', options.appDescription);
|
package/vars/global.js
CHANGED
|
@@ -6,6 +6,22 @@ export const EXPORT_ROOT = "src";
|
|
|
6
6
|
export const ASSETS_DIR = "Assets"; // Local directory name for static assets (API uses /contents/assets)
|
|
7
7
|
export const IRIS_APPS_DIR = "iris-apps"; // Local directory for Iris Vue.js apps
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Whitelist of allowed directories inside EXPORT_ROOT.
|
|
11
|
+
* Only these folders will be scanned during publish/cache operations.
|
|
12
|
+
* This prevents accidental processing of hidden folders (.magentrix, .vscode, etc.)
|
|
13
|
+
* or any other unexpected directories users might create.
|
|
14
|
+
*/
|
|
15
|
+
export const ALLOWED_SRC_DIRS = [
|
|
16
|
+
"Assets",
|
|
17
|
+
"Classes",
|
|
18
|
+
"Controllers",
|
|
19
|
+
"iris-apps",
|
|
20
|
+
"Pages",
|
|
21
|
+
"Templates",
|
|
22
|
+
"Triggers"
|
|
23
|
+
];
|
|
24
|
+
|
|
9
25
|
/**
|
|
10
26
|
* Maps Magentrix Type fields to local folder names and extensions.
|
|
11
27
|
* Extensions chosen to avoid collisions and clearly indicate type.
|