semantiq-mcp 0.8.0 → 0.9.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/package.json +1 -1
- package/scripts/install.js +98 -1
package/package.json
CHANGED
package/scripts/install.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const https = require('https');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const crypto = require('crypto');
|
|
6
7
|
const { execSync } = require('child_process');
|
|
7
8
|
|
|
8
9
|
const VERSION = require('../package.json').version;
|
|
@@ -36,6 +37,11 @@ function downloadFile(url, dest) {
|
|
|
36
37
|
return new Promise((resolve, reject) => {
|
|
37
38
|
const file = fs.createWriteStream(dest);
|
|
38
39
|
|
|
40
|
+
const cleanup = () => {
|
|
41
|
+
file.close();
|
|
42
|
+
fs.unlink(dest, () => {});
|
|
43
|
+
};
|
|
44
|
+
|
|
39
45
|
const request = (url) => {
|
|
40
46
|
https.get(url, (response) => {
|
|
41
47
|
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
@@ -44,6 +50,7 @@ function downloadFile(url, dest) {
|
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
if (response.statusCode !== 200) {
|
|
53
|
+
cleanup();
|
|
47
54
|
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
48
55
|
return;
|
|
49
56
|
}
|
|
@@ -53,6 +60,43 @@ function downloadFile(url, dest) {
|
|
|
53
60
|
file.close();
|
|
54
61
|
resolve();
|
|
55
62
|
});
|
|
63
|
+
response.on('error', (err) => {
|
|
64
|
+
cleanup();
|
|
65
|
+
reject(err);
|
|
66
|
+
});
|
|
67
|
+
}).on('error', (err) => {
|
|
68
|
+
cleanup();
|
|
69
|
+
reject(err);
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
request(url);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Download a small text resource (the .sha256 file) directly into memory,
|
|
78
|
+
// following redirects. Rejects on any non-200 final status.
|
|
79
|
+
function downloadText(url) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const request = (url) => {
|
|
82
|
+
https.get(url, (response) => {
|
|
83
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
84
|
+
request(response.headers.location);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (response.statusCode !== 200) {
|
|
89
|
+
reject(new Error(`Failed to download checksum: ${response.statusCode}`));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let data = '';
|
|
94
|
+
response.setEncoding('utf8');
|
|
95
|
+
response.on('data', (chunk) => {
|
|
96
|
+
data += chunk;
|
|
97
|
+
});
|
|
98
|
+
response.on('end', () => resolve(data));
|
|
99
|
+
response.on('error', reject);
|
|
56
100
|
}).on('error', reject);
|
|
57
101
|
};
|
|
58
102
|
|
|
@@ -60,22 +104,74 @@ function downloadFile(url, dest) {
|
|
|
60
104
|
});
|
|
61
105
|
}
|
|
62
106
|
|
|
107
|
+
function sha256File(filePath) {
|
|
108
|
+
const hash = crypto.createHash('sha256');
|
|
109
|
+
hash.update(fs.readFileSync(filePath));
|
|
110
|
+
return hash.digest('hex');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The published .sha256 may be either a bare hex digest or the standard
|
|
114
|
+
// `sha256sum` format: "<hex> <filename>". Extract the leading hex token.
|
|
115
|
+
function parseExpectedSha256(raw, archiveName) {
|
|
116
|
+
const tokens = raw.trim().split(/\s+/);
|
|
117
|
+
const hex = (tokens[0] || '').toLowerCase();
|
|
118
|
+
if (!/^[0-9a-f]{64}$/.test(hex)) {
|
|
119
|
+
throw new Error(`Malformed checksum file for ${archiveName}`);
|
|
120
|
+
}
|
|
121
|
+
return hex;
|
|
122
|
+
}
|
|
123
|
+
|
|
63
124
|
async function install() {
|
|
64
125
|
const { target, isWindows } = getPlatform();
|
|
65
126
|
const binName = isWindows ? 'semantiq.exe' : 'semantiq';
|
|
66
127
|
const archiveName = `semantiq-v${VERSION}-${target}.tar.gz`;
|
|
67
128
|
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
|
|
129
|
+
const checksumUrl = `${url}.sha256`;
|
|
68
130
|
|
|
69
131
|
const binDir = path.join(__dirname, '..', 'bin');
|
|
70
132
|
const binPath = path.join(binDir, binName);
|
|
71
133
|
const archivePath = path.join(binDir, archiveName);
|
|
72
134
|
|
|
135
|
+
// Best-effort removal of partial/untrusted artifacts.
|
|
136
|
+
const removeArtifacts = () => {
|
|
137
|
+
for (const p of [archivePath, binPath]) {
|
|
138
|
+
try {
|
|
139
|
+
if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
140
|
+
} catch (_) {
|
|
141
|
+
// ignore
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
73
146
|
console.log(`Downloading Semantiq v${VERSION} for ${target}...`);
|
|
74
147
|
|
|
75
148
|
try {
|
|
76
149
|
await downloadFile(url, archivePath);
|
|
77
150
|
|
|
78
|
-
//
|
|
151
|
+
// Integrity verification: never extract/execute an unverified binary.
|
|
152
|
+
// The release CI publishes "<archive>.sha256" next to each artifact.
|
|
153
|
+
let expectedSha;
|
|
154
|
+
try {
|
|
155
|
+
const checksumRaw = await downloadText(checksumUrl);
|
|
156
|
+
expectedSha = parseExpectedSha256(checksumRaw, archiveName);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
removeArtifacts();
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Could not verify integrity (missing or unreadable ${archiveName}.sha256): ${err.message}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const actualSha = sha256File(archivePath);
|
|
165
|
+
if (actualSha !== expectedSha) {
|
|
166
|
+
removeArtifacts();
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Checksum mismatch for ${archiveName}\n expected: ${expectedSha}\n actual: ${actualSha}`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log('Checksum verified.');
|
|
173
|
+
|
|
174
|
+
// Extract (verified archive only)
|
|
79
175
|
if (isWindows) {
|
|
80
176
|
execSync(`tar -xzf "${archivePath}" -C "${binDir}"`, { stdio: 'inherit' });
|
|
81
177
|
} else {
|
|
@@ -88,6 +184,7 @@ async function install() {
|
|
|
88
184
|
|
|
89
185
|
console.log('Semantiq installed successfully!');
|
|
90
186
|
} catch (error) {
|
|
187
|
+
removeArtifacts();
|
|
91
188
|
console.error('Failed to install Semantiq:', error.message);
|
|
92
189
|
console.error('');
|
|
93
190
|
console.error('Alternative installation methods:');
|