@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/iris/zipper.js
CHANGED
|
@@ -1,248 +1,248 @@
|
|
|
1
|
-
import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync, readdirSync, statSync, chmodSync } from 'node:fs';
|
|
2
|
-
import { tmpdir } from 'node:os';
|
|
3
|
-
import { join, basename } from 'node:path';
|
|
4
|
-
import { randomUUID, createHash } from 'node:crypto';
|
|
5
|
-
import archiver from 'archiver';
|
|
6
|
-
import extractZip from 'extract-zip';
|
|
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
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Recursively fix permissions on extracted files.
|
|
13
|
-
* Sets directories to 0o755 and files to 0o644.
|
|
14
|
-
* This ensures the current user can read/write/delete the files.
|
|
15
|
-
*
|
|
16
|
-
* @param {string} dir - Directory to fix permissions for
|
|
17
|
-
*/
|
|
18
|
-
function fixPermissions(dir) {
|
|
19
|
-
if (!existsSync(dir)) return;
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const stat = statSync(dir);
|
|
23
|
-
if (stat.isDirectory()) {
|
|
24
|
-
chmodSync(dir, 0o755);
|
|
25
|
-
const entries = readdirSync(dir);
|
|
26
|
-
for (const entry of entries) {
|
|
27
|
-
fixPermissions(join(dir, entry));
|
|
28
|
-
}
|
|
29
|
-
} else {
|
|
30
|
-
chmodSync(dir, 0o644);
|
|
31
|
-
}
|
|
32
|
-
} catch {
|
|
33
|
-
// Ignore permission errors - best effort
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Create a zip file from a build directory with proper Iris structure.
|
|
39
|
-
* The zip will contain a single root folder with the app slug.
|
|
40
|
-
*
|
|
41
|
-
* @param {string} distPath - Path to the build output directory (e.g., ./dist)
|
|
42
|
-
* @param {string} appSlug - Slug for the app (becomes the root folder in zip)
|
|
43
|
-
* @returns {Promise<Buffer>} - The zip file as a Buffer
|
|
44
|
-
*/
|
|
45
|
-
export async function createIrisZip(distPath, appSlug) {
|
|
46
|
-
return new Promise((resolve, reject) => {
|
|
47
|
-
const chunks = [];
|
|
48
|
-
|
|
49
|
-
const archive = archiver('zip', {
|
|
50
|
-
zlib: { level: 9 } // Maximum compression
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
archive.on('data', (chunk) => {
|
|
54
|
-
chunks.push(chunk);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
archive.on('error', (err) => {
|
|
58
|
-
reject(err);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
archive.on('end', () => {
|
|
62
|
-
resolve(Buffer.concat(chunks));
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// Add the dist directory contents under the app slug folder
|
|
66
|
-
// This creates the structure: appSlug/remoteEntry.js, appSlug/assets/...
|
|
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
|
-
);
|
|
71
|
-
|
|
72
|
-
archive.finalize();
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Extract a zip buffer to a directory.
|
|
78
|
-
*
|
|
79
|
-
* @param {Buffer} zipBuffer - The zip file as a Buffer
|
|
80
|
-
* @param {string} outputDir - Directory to extract to
|
|
81
|
-
* @returns {Promise<string>} - Path to the extracted directory
|
|
82
|
-
*/
|
|
83
|
-
export async function extractIrisZip(zipBuffer, outputDir) {
|
|
84
|
-
// Create a temporary file to hold the zip
|
|
85
|
-
const tempDir = join(tmpdir(), `iris-extract-${randomUUID()}`);
|
|
86
|
-
const tempZipPath = join(tempDir, 'temp.zip');
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
// Create temp directory
|
|
90
|
-
mkdirSync(tempDir, { recursive: true });
|
|
91
|
-
|
|
92
|
-
// Write buffer to temp file
|
|
93
|
-
writeFileSync(tempZipPath, zipBuffer);
|
|
94
|
-
|
|
95
|
-
// Ensure output directory exists
|
|
96
|
-
mkdirSync(outputDir, { recursive: true });
|
|
97
|
-
|
|
98
|
-
// Extract
|
|
99
|
-
await extractZip(tempZipPath, { dir: outputDir });
|
|
100
|
-
|
|
101
|
-
// Fix permissions so files can be deleted/modified later
|
|
102
|
-
fixPermissions(outputDir);
|
|
103
|
-
|
|
104
|
-
return outputDir;
|
|
105
|
-
} finally {
|
|
106
|
-
// Cleanup temp directory
|
|
107
|
-
try {
|
|
108
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
109
|
-
} catch {
|
|
110
|
-
// Ignore cleanup errors
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Get the size of a buffer in human-readable format.
|
|
117
|
-
*
|
|
118
|
-
* @param {number} bytes - Size in bytes
|
|
119
|
-
* @returns {string} - Human-readable size (e.g., "1.2 MB")
|
|
120
|
-
*/
|
|
121
|
-
export function formatFileSize(bytes) {
|
|
122
|
-
if (bytes === 0) return '0 B';
|
|
123
|
-
|
|
124
|
-
const units = ['B', 'KB', 'MB', 'GB'];
|
|
125
|
-
const k = 1024;
|
|
126
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
127
|
-
|
|
128
|
-
if (i === 0) return `${bytes} ${units[0]}`;
|
|
129
|
-
|
|
130
|
-
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${units[i]}`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Count files in a directory recursively.
|
|
135
|
-
*
|
|
136
|
-
* @param {string} dir - Directory path
|
|
137
|
-
* @returns {number} - Number of files
|
|
138
|
-
*/
|
|
139
|
-
export function countFiles(dir) {
|
|
140
|
-
if (!existsSync(dir)) return 0;
|
|
141
|
-
|
|
142
|
-
let count = 0;
|
|
143
|
-
|
|
144
|
-
function walk(currentDir) {
|
|
145
|
-
const entries = readdirSync(currentDir);
|
|
146
|
-
for (const entry of entries) {
|
|
147
|
-
const fullPath = join(currentDir, entry);
|
|
148
|
-
const stat = statSync(fullPath);
|
|
149
|
-
if (stat.isDirectory()) {
|
|
150
|
-
walk(fullPath);
|
|
151
|
-
} else {
|
|
152
|
-
count++;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
walk(dir);
|
|
158
|
-
return count;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Calculate total size of files in a directory recursively.
|
|
163
|
-
*
|
|
164
|
-
* @param {string} dir - Directory path
|
|
165
|
-
* @returns {number} - Total size in bytes
|
|
166
|
-
*/
|
|
167
|
-
export function calculateDirSize(dir) {
|
|
168
|
-
if (!existsSync(dir)) return 0;
|
|
169
|
-
|
|
170
|
-
let totalSize = 0;
|
|
171
|
-
|
|
172
|
-
function walk(currentDir) {
|
|
173
|
-
const entries = readdirSync(currentDir);
|
|
174
|
-
for (const entry of entries) {
|
|
175
|
-
const fullPath = join(currentDir, entry);
|
|
176
|
-
const stat = statSync(fullPath);
|
|
177
|
-
if (stat.isDirectory()) {
|
|
178
|
-
walk(fullPath);
|
|
179
|
-
} else {
|
|
180
|
-
totalSize += stat.size;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
walk(dir);
|
|
186
|
-
return totalSize;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Get list of all files in a directory recursively with relative paths.
|
|
191
|
-
*
|
|
192
|
-
* @param {string} dir - Directory path
|
|
193
|
-
* @param {string} basePath - Base path for relative path calculation
|
|
194
|
-
* @returns {string[]} - Array of relative file paths
|
|
195
|
-
*/
|
|
196
|
-
export function getFilesRecursive(dir, basePath = dir) {
|
|
197
|
-
if (!existsSync(dir)) return [];
|
|
198
|
-
|
|
199
|
-
const files = [];
|
|
200
|
-
|
|
201
|
-
function walk(currentDir) {
|
|
202
|
-
const entries = readdirSync(currentDir);
|
|
203
|
-
for (const entry of entries) {
|
|
204
|
-
// Skip OS-generated system files
|
|
205
|
-
if (IGNORED_FILES.has(entry)) continue;
|
|
206
|
-
|
|
207
|
-
const fullPath = join(currentDir, entry);
|
|
208
|
-
const stat = statSync(fullPath);
|
|
209
|
-
if (stat.isDirectory()) {
|
|
210
|
-
walk(fullPath);
|
|
211
|
-
} else {
|
|
212
|
-
const relativePath = fullPath.slice(basePath.length + 1);
|
|
213
|
-
files.push(relativePath);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
walk(dir);
|
|
219
|
-
return files;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Calculate a content hash for an entire Iris app folder.
|
|
224
|
-
* Hashes all file contents and their relative paths to detect any changes.
|
|
225
|
-
*
|
|
226
|
-
* @param {string} dir - Directory path to the Iris app
|
|
227
|
-
* @returns {string} - SHA256 hash of all files
|
|
228
|
-
*/
|
|
229
|
-
export function hashIrisAppFolder(dir) {
|
|
230
|
-
if (!existsSync(dir)) return '';
|
|
231
|
-
|
|
232
|
-
const hash = createHash('sha256');
|
|
233
|
-
const files = getFilesRecursive(dir).sort(); // Sort for consistent ordering
|
|
234
|
-
|
|
235
|
-
for (const relativePath of files) {
|
|
236
|
-
const fullPath = join(dir, relativePath);
|
|
237
|
-
try {
|
|
238
|
-
const content = readFileSync(fullPath);
|
|
239
|
-
// Include the relative path in the hash so renames are detected
|
|
240
|
-
hash.update(relativePath);
|
|
241
|
-
hash.update(content);
|
|
242
|
-
} catch {
|
|
243
|
-
// Skip files we can't read
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return hash.digest('hex');
|
|
248
|
-
}
|
|
1
|
+
import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync, readdirSync, statSync, chmodSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join, basename } from 'node:path';
|
|
4
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
5
|
+
import archiver from 'archiver';
|
|
6
|
+
import extractZip from 'extract-zip';
|
|
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
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Recursively fix permissions on extracted files.
|
|
13
|
+
* Sets directories to 0o755 and files to 0o644.
|
|
14
|
+
* This ensures the current user can read/write/delete the files.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} dir - Directory to fix permissions for
|
|
17
|
+
*/
|
|
18
|
+
function fixPermissions(dir) {
|
|
19
|
+
if (!existsSync(dir)) return;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const stat = statSync(dir);
|
|
23
|
+
if (stat.isDirectory()) {
|
|
24
|
+
chmodSync(dir, 0o755);
|
|
25
|
+
const entries = readdirSync(dir);
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
fixPermissions(join(dir, entry));
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
chmodSync(dir, 0o644);
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// Ignore permission errors - best effort
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a zip file from a build directory with proper Iris structure.
|
|
39
|
+
* The zip will contain a single root folder with the app slug.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} distPath - Path to the build output directory (e.g., ./dist)
|
|
42
|
+
* @param {string} appSlug - Slug for the app (becomes the root folder in zip)
|
|
43
|
+
* @returns {Promise<Buffer>} - The zip file as a Buffer
|
|
44
|
+
*/
|
|
45
|
+
export async function createIrisZip(distPath, appSlug) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const chunks = [];
|
|
48
|
+
|
|
49
|
+
const archive = archiver('zip', {
|
|
50
|
+
zlib: { level: 9 } // Maximum compression
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
archive.on('data', (chunk) => {
|
|
54
|
+
chunks.push(chunk);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
archive.on('error', (err) => {
|
|
58
|
+
reject(err);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
archive.on('end', () => {
|
|
62
|
+
resolve(Buffer.concat(chunks));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Add the dist directory contents under the app slug folder
|
|
66
|
+
// This creates the structure: appSlug/remoteEntry.js, appSlug/assets/...
|
|
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
|
+
);
|
|
71
|
+
|
|
72
|
+
archive.finalize();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extract a zip buffer to a directory.
|
|
78
|
+
*
|
|
79
|
+
* @param {Buffer} zipBuffer - The zip file as a Buffer
|
|
80
|
+
* @param {string} outputDir - Directory to extract to
|
|
81
|
+
* @returns {Promise<string>} - Path to the extracted directory
|
|
82
|
+
*/
|
|
83
|
+
export async function extractIrisZip(zipBuffer, outputDir) {
|
|
84
|
+
// Create a temporary file to hold the zip
|
|
85
|
+
const tempDir = join(tmpdir(), `iris-extract-${randomUUID()}`);
|
|
86
|
+
const tempZipPath = join(tempDir, 'temp.zip');
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Create temp directory
|
|
90
|
+
mkdirSync(tempDir, { recursive: true });
|
|
91
|
+
|
|
92
|
+
// Write buffer to temp file
|
|
93
|
+
writeFileSync(tempZipPath, zipBuffer);
|
|
94
|
+
|
|
95
|
+
// Ensure output directory exists
|
|
96
|
+
mkdirSync(outputDir, { recursive: true });
|
|
97
|
+
|
|
98
|
+
// Extract
|
|
99
|
+
await extractZip(tempZipPath, { dir: outputDir });
|
|
100
|
+
|
|
101
|
+
// Fix permissions so files can be deleted/modified later
|
|
102
|
+
fixPermissions(outputDir);
|
|
103
|
+
|
|
104
|
+
return outputDir;
|
|
105
|
+
} finally {
|
|
106
|
+
// Cleanup temp directory
|
|
107
|
+
try {
|
|
108
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
109
|
+
} catch {
|
|
110
|
+
// Ignore cleanup errors
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get the size of a buffer in human-readable format.
|
|
117
|
+
*
|
|
118
|
+
* @param {number} bytes - Size in bytes
|
|
119
|
+
* @returns {string} - Human-readable size (e.g., "1.2 MB")
|
|
120
|
+
*/
|
|
121
|
+
export function formatFileSize(bytes) {
|
|
122
|
+
if (bytes === 0) return '0 B';
|
|
123
|
+
|
|
124
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
125
|
+
const k = 1024;
|
|
126
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
127
|
+
|
|
128
|
+
if (i === 0) return `${bytes} ${units[0]}`;
|
|
129
|
+
|
|
130
|
+
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${units[i]}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Count files in a directory recursively.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} dir - Directory path
|
|
137
|
+
* @returns {number} - Number of files
|
|
138
|
+
*/
|
|
139
|
+
export function countFiles(dir) {
|
|
140
|
+
if (!existsSync(dir)) return 0;
|
|
141
|
+
|
|
142
|
+
let count = 0;
|
|
143
|
+
|
|
144
|
+
function walk(currentDir) {
|
|
145
|
+
const entries = readdirSync(currentDir);
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
const fullPath = join(currentDir, entry);
|
|
148
|
+
const stat = statSync(fullPath);
|
|
149
|
+
if (stat.isDirectory()) {
|
|
150
|
+
walk(fullPath);
|
|
151
|
+
} else {
|
|
152
|
+
count++;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
walk(dir);
|
|
158
|
+
return count;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Calculate total size of files in a directory recursively.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} dir - Directory path
|
|
165
|
+
* @returns {number} - Total size in bytes
|
|
166
|
+
*/
|
|
167
|
+
export function calculateDirSize(dir) {
|
|
168
|
+
if (!existsSync(dir)) return 0;
|
|
169
|
+
|
|
170
|
+
let totalSize = 0;
|
|
171
|
+
|
|
172
|
+
function walk(currentDir) {
|
|
173
|
+
const entries = readdirSync(currentDir);
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
const fullPath = join(currentDir, entry);
|
|
176
|
+
const stat = statSync(fullPath);
|
|
177
|
+
if (stat.isDirectory()) {
|
|
178
|
+
walk(fullPath);
|
|
179
|
+
} else {
|
|
180
|
+
totalSize += stat.size;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
walk(dir);
|
|
186
|
+
return totalSize;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get list of all files in a directory recursively with relative paths.
|
|
191
|
+
*
|
|
192
|
+
* @param {string} dir - Directory path
|
|
193
|
+
* @param {string} basePath - Base path for relative path calculation
|
|
194
|
+
* @returns {string[]} - Array of relative file paths
|
|
195
|
+
*/
|
|
196
|
+
export function getFilesRecursive(dir, basePath = dir) {
|
|
197
|
+
if (!existsSync(dir)) return [];
|
|
198
|
+
|
|
199
|
+
const files = [];
|
|
200
|
+
|
|
201
|
+
function walk(currentDir) {
|
|
202
|
+
const entries = readdirSync(currentDir);
|
|
203
|
+
for (const entry of entries) {
|
|
204
|
+
// Skip OS-generated system files
|
|
205
|
+
if (IGNORED_FILES.has(entry)) continue;
|
|
206
|
+
|
|
207
|
+
const fullPath = join(currentDir, entry);
|
|
208
|
+
const stat = statSync(fullPath);
|
|
209
|
+
if (stat.isDirectory()) {
|
|
210
|
+
walk(fullPath);
|
|
211
|
+
} else {
|
|
212
|
+
const relativePath = fullPath.slice(basePath.length + 1);
|
|
213
|
+
files.push(relativePath);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
walk(dir);
|
|
219
|
+
return files;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Calculate a content hash for an entire Iris app folder.
|
|
224
|
+
* Hashes all file contents and their relative paths to detect any changes.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} dir - Directory path to the Iris app
|
|
227
|
+
* @returns {string} - SHA256 hash of all files
|
|
228
|
+
*/
|
|
229
|
+
export function hashIrisAppFolder(dir) {
|
|
230
|
+
if (!existsSync(dir)) return '';
|
|
231
|
+
|
|
232
|
+
const hash = createHash('sha256');
|
|
233
|
+
const files = getFilesRecursive(dir).sort(); // Sort for consistent ordering
|
|
234
|
+
|
|
235
|
+
for (const relativePath of files) {
|
|
236
|
+
const fullPath = join(dir, relativePath);
|
|
237
|
+
try {
|
|
238
|
+
const content = readFileSync(fullPath);
|
|
239
|
+
// Include the relative path in the hash so renames are detected
|
|
240
|
+
hash.update(relativePath);
|
|
241
|
+
hash.update(content);
|
|
242
|
+
} catch {
|
|
243
|
+
// Skip files we can't read
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return hash.digest('hex');
|
|
248
|
+
}
|