aios-core 3.1.0 → 3.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.
@@ -0,0 +1,137 @@
1
+ /**
2
+ * File Hasher Utility
3
+ * Cross-platform file hashing with line ending normalization
4
+ *
5
+ * @module src/installer/file-hasher
6
+ * @story 6.18 - Dynamic Manifest & Brownfield Upgrade System
7
+ */
8
+
9
+ const crypto = require('crypto');
10
+ const fs = require('fs-extra');
11
+ const path = require('path');
12
+
13
+ /**
14
+ * List of file extensions that should be treated as binary (not normalized)
15
+ */
16
+ const BINARY_EXTENSIONS = [
17
+ '.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp', '.svg',
18
+ '.pdf', '.zip', '.tar', '.gz', '.7z',
19
+ '.woff', '.woff2', '.ttf', '.eot',
20
+ '.mp3', '.mp4', '.wav', '.avi',
21
+ '.exe', '.dll', '.so', '.dylib',
22
+ ];
23
+
24
+ /**
25
+ * Check if a file should be treated as binary based on extension
26
+ * @param {string} filePath - Path to the file
27
+ * @returns {boolean} - True if file is binary
28
+ */
29
+ function isBinaryFile(filePath) {
30
+ const ext = path.extname(filePath).toLowerCase();
31
+ return BINARY_EXTENSIONS.includes(ext);
32
+ }
33
+
34
+ /**
35
+ * Normalize line endings from CRLF to LF for consistent cross-platform hashing
36
+ * @param {string} content - File content as string
37
+ * @returns {string} - Normalized content with LF line endings
38
+ */
39
+ function normalizeLineEndings(content) {
40
+ return content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
41
+ }
42
+
43
+ /**
44
+ * Remove UTF-8 BOM if present
45
+ * @param {string} content - File content
46
+ * @returns {string} - Content without BOM
47
+ */
48
+ function removeBOM(content) {
49
+ if (content.charCodeAt(0) === 0xFEFF) {
50
+ return content.slice(1);
51
+ }
52
+ return content;
53
+ }
54
+
55
+ /**
56
+ * Compute SHA256 hash of a file with cross-platform normalization
57
+ * Text files have line endings normalized for consistent hashes across OS
58
+ * Binary files are hashed as-is
59
+ *
60
+ * @param {string} filePath - Absolute path to the file
61
+ * @returns {string} - SHA256 hash as hex string
62
+ * @throws {Error} - If file cannot be read
63
+ */
64
+ function hashFile(filePath) {
65
+ if (!fs.existsSync(filePath)) {
66
+ throw new Error(`File not found: ${filePath}`);
67
+ }
68
+
69
+ const stats = fs.statSync(filePath);
70
+ if (stats.isDirectory()) {
71
+ throw new Error(`Cannot hash directory: ${filePath}`);
72
+ }
73
+
74
+ let content;
75
+
76
+ if (isBinaryFile(filePath)) {
77
+ // Binary files: hash raw bytes
78
+ content = fs.readFileSync(filePath);
79
+ } else {
80
+ // Text files: normalize line endings and remove BOM
81
+ const rawContent = fs.readFileSync(filePath, 'utf8');
82
+ const withoutBOM = removeBOM(rawContent);
83
+ const normalized = normalizeLineEndings(withoutBOM);
84
+ content = Buffer.from(normalized, 'utf8');
85
+ }
86
+
87
+ return crypto.createHash('sha256').update(content).digest('hex');
88
+ }
89
+
90
+ /**
91
+ * Compute SHA256 hash of a string (for manifest integrity)
92
+ * @param {string} content - String content to hash
93
+ * @returns {string} - SHA256 hash as hex string
94
+ */
95
+ function hashString(content) {
96
+ return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
97
+ }
98
+
99
+ /**
100
+ * Compare two hashes for equality
101
+ * @param {string} hash1 - First hash
102
+ * @param {string} hash2 - Second hash
103
+ * @returns {boolean} - True if hashes match
104
+ */
105
+ function hashesMatch(hash1, hash2) {
106
+ if (!hash1 || !hash2) return false;
107
+ return hash1.toLowerCase() === hash2.toLowerCase();
108
+ }
109
+
110
+ /**
111
+ * Get file metadata including hash
112
+ * @param {string} filePath - Absolute path to the file
113
+ * @param {string} basePath - Base path for relative path calculation
114
+ * @returns {Object} - File metadata object
115
+ */
116
+ function getFileMetadata(filePath, basePath) {
117
+ const stats = fs.statSync(filePath);
118
+ const relativePath = path.relative(basePath, filePath).replace(/\\/g, '/');
119
+
120
+ return {
121
+ path: relativePath,
122
+ hash: `sha256:${hashFile(filePath)}`,
123
+ size: stats.size,
124
+ isBinary: isBinaryFile(filePath),
125
+ };
126
+ }
127
+
128
+ module.exports = {
129
+ hashFile,
130
+ hashString,
131
+ hashesMatch,
132
+ getFileMetadata,
133
+ isBinaryFile,
134
+ normalizeLineEndings,
135
+ removeBOM,
136
+ BINARY_EXTENSIONS,
137
+ };