aios-core 3.1.0 → 3.2.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/.aios-core/install-manifest.yaml +2233 -349
- package/bin/aios-init.js +126 -0
- package/package.json +4 -1
- package/scripts/generate-install-manifest.js +337 -0
- package/scripts/validate-manifest.js +265 -0
- package/src/installer/brownfield-upgrader.js +438 -0
- package/src/installer/file-hasher.js +137 -0
|
@@ -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
|
+
};
|