eidoncore 3.7.5 → 3.7.6
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/install.js +70 -28
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -110,41 +110,77 @@ async function fetchAndVerifyManifest(url) {
|
|
|
110
110
|
|
|
111
111
|
function downloadFile(url, dest) {
|
|
112
112
|
return new Promise((resolve, reject) => {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// Follow redirects BEFORE opening the write stream — GitHub releases always
|
|
114
|
+
// 302-redirect to objects.githubusercontent.com. Creating the WriteStream
|
|
115
|
+
// before resolving redirects and then calling file.destroy() on redirect
|
|
116
|
+
// causes the subsequent pipe to write to a destroyed stream (0 bytes, silent).
|
|
117
|
+
function followRedirects(href, hops) {
|
|
118
|
+
if (hops > 5) return reject(new Error('Too many redirects downloading binary'));
|
|
119
|
+
if (!href.startsWith('https://')) return reject(new Error(`Refusing non-HTTPS redirect: ${href}`));
|
|
115
120
|
const req = https.get(href, { timeout: 120000 }, res => {
|
|
116
121
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
res.resume(); // drain response body before following redirect
|
|
123
|
+
const loc = res.headers.location || '';
|
|
124
|
+
return followRedirects(loc, hops + 1);
|
|
119
125
|
}
|
|
120
126
|
if (res.statusCode !== 200) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return reject(new Error(`HTTP ${res.statusCode} downloading binary`));
|
|
127
|
+
res.resume();
|
|
128
|
+
return reject(new Error(`HTTP ${res.statusCode} downloading binary from ${href}`));
|
|
124
129
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
// Final URL resolved — now create the write stream.
|
|
131
|
+
// Do NOT use res.pipe(file) here: adding a 'data' listener puts the
|
|
132
|
+
// stream in flowing mode before pipe is registered, causing chunks to
|
|
133
|
+
// be consumed by the progress listener but not written to the file.
|
|
134
|
+
// Manual write() with backpressure is the only reliable approach on Windows.
|
|
135
|
+
const total = parseInt(res.headers['content-length'] || '0', 10);
|
|
136
|
+
let received = 0;
|
|
137
|
+
let lastPct = -1;
|
|
138
|
+
const file = fs.createWriteStream(dest);
|
|
139
|
+
|
|
140
|
+
file.on('error', err => {
|
|
141
|
+
res.destroy();
|
|
142
|
+
try { fs.unlinkSync(dest); } catch {}
|
|
143
|
+
reject(new Error(`File write error: ${err.message}`));
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
res.on('error', err => {
|
|
147
|
+
file.destroy();
|
|
148
|
+
try { fs.unlinkSync(dest); } catch {}
|
|
149
|
+
reject(new Error(`Download stream error: ${err.message}`));
|
|
150
|
+
});
|
|
151
|
+
|
|
128
152
|
res.on('data', chunk => {
|
|
129
153
|
received += chunk.length;
|
|
154
|
+
// Backpressure: pause the network stream if the disk is behind
|
|
155
|
+
const ok = file.write(chunk);
|
|
156
|
+
if (!ok) res.pause();
|
|
157
|
+
|
|
130
158
|
if (total > 0) {
|
|
131
|
-
const pct = Math.floor((received / total) * 20);
|
|
159
|
+
const pct = Math.floor((received / total) * 20);
|
|
132
160
|
if (pct !== lastPct) {
|
|
133
161
|
lastPct = pct;
|
|
134
162
|
const bar = '█'.repeat(pct) + '░'.repeat(20 - pct);
|
|
135
|
-
|
|
163
|
+
const mb = (received / 1024 / 1024).toFixed(1);
|
|
164
|
+
const tot = (total / 1024 / 1024).toFixed(1);
|
|
165
|
+
process.stdout.write(`\r [${bar}] ${mb} / ${tot} MB`);
|
|
136
166
|
}
|
|
167
|
+
} else {
|
|
168
|
+
process.stdout.write(`\r Downloading... ${(received / 1024).toFixed(0)} KB`);
|
|
137
169
|
}
|
|
138
170
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
file.on('
|
|
142
|
-
|
|
171
|
+
|
|
172
|
+
// Resume the readable when the writable drains (backpressure relief)
|
|
173
|
+
file.on('drain', () => res.resume());
|
|
174
|
+
|
|
175
|
+
res.on('end', () => {
|
|
176
|
+
process.stdout.write('\n');
|
|
177
|
+
file.end(() => resolve());
|
|
178
|
+
});
|
|
143
179
|
});
|
|
144
|
-
req.on('error', err => { fs.
|
|
180
|
+
req.on('error', err => { try { fs.unlinkSync(dest); } catch {} reject(err); });
|
|
145
181
|
req.on('timeout', () => { req.destroy(); reject(new Error('Download timed out')); });
|
|
146
182
|
}
|
|
147
|
-
|
|
183
|
+
followRedirects(url, 0);
|
|
148
184
|
});
|
|
149
185
|
}
|
|
150
186
|
|
|
@@ -161,15 +197,20 @@ function sha256File(filePath) {
|
|
|
161
197
|
|
|
162
198
|
// ── Main ─────────────────────────────────────────────────────────────────
|
|
163
199
|
async function main() {
|
|
164
|
-
//
|
|
200
|
+
// If binary already exists, check if it matches the npm package version.
|
|
201
|
+
// If versions match → skip. If outdated or broken → re-download.
|
|
165
202
|
if (fs.existsSync(BINARY_PATH)) {
|
|
166
203
|
try {
|
|
167
|
-
execFileSync(BINARY_PATH, ['--version'], {
|
|
168
|
-
|
|
169
|
-
|
|
204
|
+
const installedVersion = execFileSync(BINARY_PATH, ['--version'], { encoding: 'utf8' }).trim();
|
|
205
|
+
const pkgVersion = require('./package.json').version;
|
|
206
|
+
if (installedVersion === pkgVersion) {
|
|
207
|
+
console.log(` Eidon ${pkgVersion} already installed.`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
console.log(` Upgrading Eidon ${installedVersion} → ${pkgVersion}...`);
|
|
170
211
|
} catch {
|
|
171
212
|
// Binary exists but broken — re-download
|
|
172
|
-
fs.unlinkSync(BINARY_PATH);
|
|
213
|
+
try { fs.unlinkSync(BINARY_PATH); } catch {}
|
|
173
214
|
}
|
|
174
215
|
}
|
|
175
216
|
|
|
@@ -178,11 +219,12 @@ async function main() {
|
|
|
178
219
|
const platform = getPlatform();
|
|
179
220
|
console.log(` Platform : ${platform}`);
|
|
180
221
|
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
222
|
+
// Always download the exact version matching this npm package — no race condition
|
|
223
|
+
// with latest.json (which GitHub Pages may not have updated yet when npm was published).
|
|
224
|
+
// Allow override: EIDON_VERSION=3.2.0 npm install -g eidoncore
|
|
225
|
+
const npmVersion = require('./package.json').version;
|
|
226
|
+
const requestedVersion = process.env.EIDON_VERSION || npmVersion;
|
|
227
|
+
const manifestUrl = `${RELEASES_BASE}/${requestedVersion}.json`;
|
|
186
228
|
|
|
187
229
|
console.log(' Fetching release manifest...');
|
|
188
230
|
const manifest = await fetchAndVerifyManifest(manifestUrl);
|