@evomap/evolver 1.89.14 → 1.89.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/index.js +219 -29
- package/package.json +1 -1
- package/src/adapters/claudeCode.js +2 -2
- package/src/adapters/codex.js +2 -2
- package/src/adapters/hookAdapter.js +14 -2
- package/src/adapters/scripts/evolver-session-start.js +182 -2
- package/src/config.js +11 -3
- package/src/evolve/guards.js +1 -1
- package/src/evolve/pipeline/collect.js +1 -1
- package/src/evolve/pipeline/dispatch.js +1 -1
- package/src/evolve/pipeline/enrich.js +1 -1
- package/src/evolve/pipeline/hub.js +1 -1
- package/src/evolve/pipeline/select.js +1 -1
- package/src/evolve/pipeline/signals.js +1 -1
- package/src/evolve/utils.js +1 -1
- package/src/evolve.js +1 -1
- package/src/forceUpdate.js +499 -119
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/antiAbuseTelemetry.js +1 -1
- package/src/gep/autoDistillConv.js +1 -1
- package/src/gep/autoDistillLlm.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/cliContracts.js +1154 -0
- package/src/gep/contentHash.js +1 -1
- package/src/gep/conversationDistiller.js +1 -1
- package/src/gep/conversationSniffer.js +1 -1
- package/src/gep/crypto.js +1 -1
- package/src/gep/curriculum.js +1 -1
- package/src/gep/deviceId.js +1 -1
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/epigenetics.js +1 -1
- package/src/gep/execBridge.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/hash.js +1 -1
- package/src/gep/hostErrorClassifier.js +34 -0
- package/src/gep/hubFetch.js +1 -1
- package/src/gep/hubReview.js +1 -1
- package/src/gep/hubSearch.js +1 -1
- package/src/gep/hubVerify.js +1 -1
- package/src/gep/issueReporter.js +87 -0
- package/src/gep/learningSignals.js +1 -1
- package/src/gep/memoryGraph.js +1 -1
- package/src/gep/memoryGraphAdapter.js +1 -1
- package/src/gep/mutation.js +1 -1
- package/src/gep/narrativeMemory.js +1 -1
- package/src/gep/openPRRegistry.js +1 -1
- package/src/gep/paths.js +20 -0
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/prompt.js +1 -1
- package/src/gep/recallInject.js +1 -1
- package/src/gep/recallVerifier.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/sanitize.js +20 -4
- package/src/gep/savingsCore.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/signals.js +70 -24
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/tokenSavings.js +1 -1
- package/src/gep/workspaceKeychain.js +1 -1
- package/src/ops/lifecycle.js +501 -31
- package/src/proxy/extensions/traceControl.js +1 -1
- package/src/proxy/index.js +4 -4
- package/src/proxy/inject.js +1 -1
- package/src/proxy/lifecycle/manager.js +233 -33
- package/src/proxy/sync/inbound.js +5 -4
- package/src/proxy/sync/outbound.js +3 -2
- package/src/proxy/trace/extractor.js +1 -1
- package/src/proxy/trace/usage.js +1 -1
package/src/forceUpdate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const zlib = require('zlib');
|
|
4
5
|
const { execFileSync } = require('child_process');
|
|
5
6
|
const { getEvolverInstallRoot } = require('./gep/paths');
|
|
6
7
|
|
|
@@ -57,12 +58,41 @@ const FORCE_UPDATE_FAIL_CODES = Object.freeze({
|
|
|
57
58
|
DEGIT_TIMEOUT: 'degit_timeout',
|
|
58
59
|
DEGIT_FAILED: 'degit_failed',
|
|
59
60
|
DOWNLOAD_INCOMPLETE: 'download_incomplete',
|
|
61
|
+
DOWNLOADED_PACKAGE_NAME_MISMATCH: 'downloaded_package_name_mismatch',
|
|
60
62
|
DOWNLOADED_VERSION_MISMATCH: 'downloaded_version_mismatch',
|
|
61
63
|
DELETE_FAILED: 'delete_failed',
|
|
62
64
|
COPY_FAILED: 'copy_failed',
|
|
65
|
+
FALLBACK_DOWNLOAD_INCOMPLETE: 'fallback_download_incomplete',
|
|
66
|
+
FALLBACK_DELETE_FAILED: 'fallback_delete_failed',
|
|
67
|
+
FALLBACK_COPY_FAILED: 'fallback_copy_failed',
|
|
68
|
+
FALLBACK_DOWNLOADED_PACKAGE_NAME_MISMATCH: 'fallback_downloaded_package_name_mismatch',
|
|
69
|
+
FALLBACK_DOWNLOADED_VERSION_MISMATCH: 'fallback_downloaded_version_mismatch',
|
|
63
70
|
ALL_CHANNELS_EXHAUSTED: 'all_channels_exhausted',
|
|
64
71
|
});
|
|
65
72
|
|
|
73
|
+
const EVOLVER_INSTALL_MARKERS = Object.freeze([
|
|
74
|
+
Object.freeze({
|
|
75
|
+
relPath: path.join('src', 'forceUpdate.js'),
|
|
76
|
+
required: true,
|
|
77
|
+
tokens: Object.freeze(['executeForceUpdate', 'FORCE_UPDATE_FAIL_CODES']),
|
|
78
|
+
}),
|
|
79
|
+
Object.freeze({
|
|
80
|
+
relPath: path.join('src', 'gep', 'paths.js'),
|
|
81
|
+
tokens: Object.freeze(['getRepoRoot', 'getEvolverInstallRoot']),
|
|
82
|
+
}),
|
|
83
|
+
Object.freeze({
|
|
84
|
+
relPath: path.join('src', 'gep', 'a2aProtocol.js'),
|
|
85
|
+
tokens: Object.freeze(['GEP A2A Protocol', 'reportForceUpdateOutcome']),
|
|
86
|
+
}),
|
|
87
|
+
Object.freeze({
|
|
88
|
+
relPath: 'index.js',
|
|
89
|
+
tokens: Object.freeze(['proxy-token', './src/evolve']),
|
|
90
|
+
}),
|
|
91
|
+
]);
|
|
92
|
+
const MAX_INSTALL_MARKER_BYTES = 1024 * 1024;
|
|
93
|
+
const FORCE_UPDATE_BACKUP_PREFIX = '.evolver-force-update-backup-';
|
|
94
|
+
const FORCE_UPDATE_JOURNAL_FILE = '.evolver-force-update-journal.json';
|
|
95
|
+
|
|
66
96
|
// Build the structured failure result that replaces a bare `return false`.
|
|
67
97
|
// Shape: { ok:false, code, detail }. Distinct from `true`, FORCE_UPDATE_NOOP
|
|
68
98
|
// and FORCE_UPDATE_BUSY, so the three call sites' `result === true` /
|
|
@@ -86,6 +116,38 @@ function _errStr(e) {
|
|
|
86
116
|
return code + (e.message != null ? String(e.message) : String(e));
|
|
87
117
|
}
|
|
88
118
|
|
|
119
|
+
function _isEvolverPackageName(name) {
|
|
120
|
+
return name === '@evomap/evolver' || name === 'evolver';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function _fileMatchesInstallMarker(root, marker) {
|
|
124
|
+
try {
|
|
125
|
+
var markerPath = path.join(root, marker.relPath);
|
|
126
|
+
var st = fs.statSync(markerPath);
|
|
127
|
+
if (!st.isFile()) return false;
|
|
128
|
+
if (st.size > MAX_INSTALL_MARKER_BYTES) return false;
|
|
129
|
+
var content = fs.readFileSync(markerPath, 'utf8');
|
|
130
|
+
for (var i = 0; i < marker.tokens.length; i++) {
|
|
131
|
+
if (!content.includes(marker.tokens[i])) return false;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
} catch (_) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function _hasStrongEvolverInstallMarkers(root) {
|
|
140
|
+
var matched = 0;
|
|
141
|
+
var requiredMatched = false;
|
|
142
|
+
for (var i = 0; i < EVOLVER_INSTALL_MARKERS.length; i++) {
|
|
143
|
+
var marker = EVOLVER_INSTALL_MARKERS[i];
|
|
144
|
+
if (!_fileMatchesInstallMarker(root, marker)) continue;
|
|
145
|
+
matched++;
|
|
146
|
+
if (marker.required) requiredMatched = true;
|
|
147
|
+
}
|
|
148
|
+
return requiredMatched && matched >= 3;
|
|
149
|
+
}
|
|
150
|
+
|
|
89
151
|
// Map a Channel 1 (GitHub Release / degit) throw to a structured failure.
|
|
90
152
|
// `phase` records how far the try block got before throwing, so a readFileSync
|
|
91
153
|
// ENOENT (truncated download) is not misread as an npx ENOENT (npx missing):
|
|
@@ -139,6 +201,18 @@ function _classifyChannel1Error(e, phase) {
|
|
|
139
201
|
return _fail(FORCE_UPDATE_FAIL_CODES.DEGIT_FAILED, detail);
|
|
140
202
|
}
|
|
141
203
|
|
|
204
|
+
function _withFallbackFailure(primaryFailure, fallbackFailure) {
|
|
205
|
+
if (!primaryFailure) return fallbackFailure;
|
|
206
|
+
if (!fallbackFailure) return primaryFailure;
|
|
207
|
+
var primaryCode = String(primaryFailure.code || FORCE_UPDATE_FAIL_CODES.ALL_CHANNELS_EXHAUSTED);
|
|
208
|
+
var fallbackCode = String(fallbackFailure.code || FORCE_UPDATE_FAIL_CODES.ALL_CHANNELS_EXHAUSTED);
|
|
209
|
+
var terminalCode = 'fallback_' + fallbackCode.replace(/[^a-z0-9_]/gi, '_').toLowerCase();
|
|
210
|
+
var detail = 'primary_failed=' + primaryCode + ' | fallback_failed=' + fallbackCode;
|
|
211
|
+
if (fallbackFailure.detail) detail += ': ' + fallbackFailure.detail;
|
|
212
|
+
if (primaryFailure.detail) detail += ' | primary_detail=' + primaryFailure.detail;
|
|
213
|
+
return _fail(terminalCode, detail);
|
|
214
|
+
}
|
|
215
|
+
|
|
142
216
|
function _isRetryableFsLockError(e) {
|
|
143
217
|
var code = e && e.code;
|
|
144
218
|
return code === 'EPERM' || code === 'EBUSY' || code === 'EACCES' ||
|
|
@@ -164,6 +238,144 @@ function _retryFsLockOperation(fn) {
|
|
|
164
238
|
throw err;
|
|
165
239
|
}
|
|
166
240
|
|
|
241
|
+
function _downloadUrlWithNode(url, destPath) {
|
|
242
|
+
var script = [
|
|
243
|
+
"const fs=require('fs');",
|
|
244
|
+
"const https=require('https');",
|
|
245
|
+
"const url=process.argv[1];",
|
|
246
|
+
"const dest=process.argv[2];",
|
|
247
|
+
"function fail(msg){try{fs.rmSync(dest,{force:true});}catch(_){};console.error(msg);process.exit(1);}",
|
|
248
|
+
"function get(u, redirects){",
|
|
249
|
+
" const parsed=new URL(u);",
|
|
250
|
+
" if(parsed.protocol!=='https:') fail('refusing non-https download URL');",
|
|
251
|
+
" const req=https.get(parsed,{headers:{'User-Agent':'evomap-evolver-force-update'}},(res)=>{",
|
|
252
|
+
" if(res.statusCode>=300&&res.statusCode<400&&res.headers.location){",
|
|
253
|
+
" if(redirects<=0) fail('too many redirects');",
|
|
254
|
+
" res.resume();",
|
|
255
|
+
" return get(new URL(res.headers.location, parsed).toString(), redirects-1);",
|
|
256
|
+
" }",
|
|
257
|
+
" if(res.statusCode!==200) fail('download status '+res.statusCode);",
|
|
258
|
+
" const out=fs.createWriteStream(dest,{flags:'wx',mode:0o600});",
|
|
259
|
+
" res.pipe(out);",
|
|
260
|
+
" out.on('finish',()=>out.close(()=>process.exit(0)));",
|
|
261
|
+
" out.on('error',(e)=>fail(e&&e.message||e));",
|
|
262
|
+
" });",
|
|
263
|
+
" req.setTimeout(60000,()=>req.destroy(new Error('download timeout')));",
|
|
264
|
+
" req.on('error',(e)=>fail(e&&e.message||e));",
|
|
265
|
+
"}",
|
|
266
|
+
"get(url,5);",
|
|
267
|
+
].join('');
|
|
268
|
+
execFileSync(process.execPath, ['-e', script, url, destPath], {
|
|
269
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
270
|
+
timeout: 70000, windowsHide: true, maxBuffer: MAX_EXEC_BUFFER,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function _readTarString(block, start, length) {
|
|
275
|
+
var end = start;
|
|
276
|
+
var max = start + length;
|
|
277
|
+
while (end < max && block[end] !== 0) end++;
|
|
278
|
+
return block.toString('utf8', start, end);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function _readTarOctal(block, start, length) {
|
|
282
|
+
var raw = _readTarString(block, start, length).trim();
|
|
283
|
+
if (!raw) return 0;
|
|
284
|
+
var parsed = parseInt(raw, 8);
|
|
285
|
+
if (!Number.isFinite(parsed) || parsed < 0) throw new Error('invalid tar numeric field');
|
|
286
|
+
return parsed;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function _isZeroTarBlock(block) {
|
|
290
|
+
for (var i = 0; i < block.length; i++) {
|
|
291
|
+
if (block[i] !== 0) return false;
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function _stripTarPathComponents(entryName, count) {
|
|
297
|
+
var normalized = String(entryName || '').replace(/\\/g, '/');
|
|
298
|
+
if (!normalized || normalized.startsWith('/')) return '';
|
|
299
|
+
var parts = normalized.split('/').filter(function (part) { return part !== '' && part !== '.'; });
|
|
300
|
+
if (parts.length <= count) return '';
|
|
301
|
+
return parts.slice(count).join('/');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function _safeTarOutputPath(root, relativeName) {
|
|
305
|
+
var parts = String(relativeName || '').split('/');
|
|
306
|
+
for (var i = 0; i < parts.length; i++) {
|
|
307
|
+
if (!parts[i] || parts[i] === '.' || parts[i] === '..') return '';
|
|
308
|
+
}
|
|
309
|
+
var rootPath = path.resolve(root);
|
|
310
|
+
var outPath = path.resolve(rootPath, relativeName);
|
|
311
|
+
if (outPath !== rootPath && outPath.startsWith(rootPath + path.sep)) return outPath;
|
|
312
|
+
return '';
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function _extractTarGzWithNode(archivePath, tempTarget) {
|
|
316
|
+
var archive = zlib.gunzipSync(fs.readFileSync(archivePath));
|
|
317
|
+
var offset = 0;
|
|
318
|
+
while (offset + 512 <= archive.length) {
|
|
319
|
+
var header = archive.subarray(offset, offset + 512);
|
|
320
|
+
offset += 512;
|
|
321
|
+
if (_isZeroTarBlock(header)) break;
|
|
322
|
+
|
|
323
|
+
var name = _readTarString(header, 0, 100);
|
|
324
|
+
var prefix = _readTarString(header, 345, 155);
|
|
325
|
+
if (prefix) name = prefix + '/' + name;
|
|
326
|
+
var size = _readTarOctal(header, 124, 12);
|
|
327
|
+
var typeFlag = _readTarString(header, 156, 1) || '0';
|
|
328
|
+
if (offset + size > archive.length) throw new Error('truncated tar entry: ' + name);
|
|
329
|
+
var mode = 0;
|
|
330
|
+
try { mode = _readTarOctal(header, 100, 8) & 0o777; } catch (_) { mode = 0; }
|
|
331
|
+
|
|
332
|
+
var stripped = _stripTarPathComponents(name, 1);
|
|
333
|
+
var outPath = _safeTarOutputPath(tempTarget, stripped);
|
|
334
|
+
if (outPath) {
|
|
335
|
+
if (typeFlag === '5') {
|
|
336
|
+
fs.mkdirSync(outPath, { recursive: true });
|
|
337
|
+
if (mode && process.platform !== 'win32') {
|
|
338
|
+
try { fs.chmodSync(outPath, mode); } catch (_) {}
|
|
339
|
+
}
|
|
340
|
+
} else if (typeFlag === '0' || typeFlag === '\0') {
|
|
341
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
342
|
+
fs.writeFileSync(outPath, archive.subarray(offset, offset + size));
|
|
343
|
+
if (mode && process.platform !== 'win32') {
|
|
344
|
+
try { fs.chmodSync(outPath, mode); } catch (_) {}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
offset += Math.ceil(size / 512) * 512;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function _clearTempTarget(tempTarget) {
|
|
354
|
+
fs.rmSync(tempTarget, { recursive: true, force: true });
|
|
355
|
+
fs.mkdirSync(tempTarget, { recursive: true });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function _tryDownloadReleaseTarball(requiredVersion, tempTarget) {
|
|
359
|
+
var archiveDir = null;
|
|
360
|
+
var archivePath = null;
|
|
361
|
+
var url = 'https://codeload.github.com/EvoMap/evolver/tar.gz/refs/tags/v' + requiredVersion;
|
|
362
|
+
try {
|
|
363
|
+
archiveDir = fs.mkdtempSync(path.join(os.tmpdir(), '.evolver-update-archive-'));
|
|
364
|
+
archivePath = path.join(archiveDir, 'archive.tar.gz');
|
|
365
|
+
_clearTempTarget(tempTarget);
|
|
366
|
+
console.log('[ForceUpdate] Channel 1b: GitHub tarball fallback (v' + requiredVersion + ')...');
|
|
367
|
+
_downloadUrlWithNode(url, archivePath);
|
|
368
|
+
_extractTarGzWithNode(archivePath, tempTarget);
|
|
369
|
+
return null;
|
|
370
|
+
} catch (e) {
|
|
371
|
+
return _fail(FORCE_UPDATE_FAIL_CODES.DOWNLOAD_INCOMPLETE, _errStr(e));
|
|
372
|
+
} finally {
|
|
373
|
+
if (archiveDir) {
|
|
374
|
+
try { fs.rmSync(archiveDir, { recursive: true, force: true }); } catch (_) {}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
167
379
|
function _recoverPackageCommitMarkerIfMissing(installRoot) {
|
|
168
380
|
var pkgDst = path.join(installRoot, 'package.json');
|
|
169
381
|
if (fs.existsSync(pkgDst)) return false;
|
|
@@ -186,23 +398,256 @@ function _recoverPackageCommitMarkerIfMissing(installRoot) {
|
|
|
186
398
|
return false;
|
|
187
399
|
}
|
|
188
400
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
401
|
+
function _isForceUpdateKeepEntry(name) {
|
|
402
|
+
return name === 'node_modules' || name === 'memory' || name === '.git' || name === 'MEMORY.md' ||
|
|
403
|
+
name === '.env' || name === '.env.local' || name === 'USER.md' || name === '.evolver' ||
|
|
404
|
+
name === 'logs';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function _isForceUpdateInternalEntry(name) {
|
|
408
|
+
return name === FORCE_UPDATE_JOURNAL_FILE || String(name || '').startsWith(FORCE_UPDATE_BACKUP_PREFIX);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function _isForceUpdateBootstrapEntry(name) {
|
|
412
|
+
return name === 'index.js';
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function _readInstallPackageVersion(installRoot) {
|
|
195
416
|
try {
|
|
196
|
-
fs.
|
|
197
|
-
return
|
|
417
|
+
var pkg = JSON.parse(fs.readFileSync(path.join(installRoot, 'package.json'), 'utf8'));
|
|
418
|
+
return pkg && pkg.version ? String(pkg.version) : '';
|
|
198
419
|
} catch (_) {
|
|
199
|
-
return
|
|
420
|
+
return '';
|
|
200
421
|
}
|
|
201
422
|
}
|
|
202
423
|
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
|
|
424
|
+
function _writeForceUpdateRecoveryJournal(backupRoot, requiredVersion, previousVersion) {
|
|
425
|
+
var journal = {
|
|
426
|
+
state: 'precommit',
|
|
427
|
+
requiredVersion: String(requiredVersion || ''),
|
|
428
|
+
previousVersion: String(previousVersion || ''),
|
|
429
|
+
createdAt: Date.now(),
|
|
430
|
+
};
|
|
431
|
+
fs.writeFileSync(
|
|
432
|
+
path.join(backupRoot, FORCE_UPDATE_JOURNAL_FILE),
|
|
433
|
+
JSON.stringify(journal),
|
|
434
|
+
{ encoding: 'utf8', mode: 0o600 },
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function _removeInstallEntryIfPresent(entryPath) {
|
|
439
|
+
_retryFsLockOperation(function () {
|
|
440
|
+
fs.rmSync(entryPath, {
|
|
441
|
+
recursive: true, force: true, maxRetries: 3, retryDelay: 200,
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function _copyFileIfPresent(src, dst) {
|
|
447
|
+
if (!fs.existsSync(src)) return false;
|
|
448
|
+
fs.copyFileSync(src, dst);
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function _restoreFileBackupIfPresent(backupPath, dstPath) {
|
|
453
|
+
if (!backupPath || !fs.existsSync(backupPath)) return false;
|
|
454
|
+
try { fs.rmSync(dstPath, { recursive: true, force: true }); } catch (_) {}
|
|
455
|
+
fs.copyFileSync(backupPath, dstPath);
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function _commitAtomicFileReplacement(srcPath, dstPath, tmpPath, backupPath) {
|
|
460
|
+
try { fs.rmSync(tmpPath, { force: true }); } catch (_) {}
|
|
461
|
+
try { fs.rmSync(backupPath, { force: true }); } catch (_) {}
|
|
462
|
+
fs.copyFileSync(srcPath, tmpPath);
|
|
463
|
+
_copyFileIfPresent(dstPath, backupPath);
|
|
464
|
+
_retryFsLockOperation(function () {
|
|
465
|
+
fs.renameSync(tmpPath, dstPath);
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function _restoreMovedInstallEntries(installRoot, movedEntries, copiedEntryNames) {
|
|
470
|
+
var ok = true;
|
|
471
|
+
var seenCopied = Object.create(null);
|
|
472
|
+
for (var ci = copiedEntryNames.length - 1; ci >= 0; ci--) {
|
|
473
|
+
var copiedName = copiedEntryNames[ci];
|
|
474
|
+
if (seenCopied[copiedName]) continue;
|
|
475
|
+
seenCopied[copiedName] = true;
|
|
476
|
+
try {
|
|
477
|
+
_removeInstallEntryIfPresent(path.join(installRoot, copiedName));
|
|
478
|
+
} catch (copyCleanupErr) {
|
|
479
|
+
ok = false;
|
|
480
|
+
console.warn('[ForceUpdate] rollback cleanup failed for ' + copiedName + ': ' +
|
|
481
|
+
(copyCleanupErr.message || copyCleanupErr));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
for (var mi = movedEntries.length - 1; mi >= 0; mi--) {
|
|
486
|
+
var moved = movedEntries[mi];
|
|
487
|
+
try {
|
|
488
|
+
if (fs.existsSync(moved.livePath)) _removeInstallEntryIfPresent(moved.livePath);
|
|
489
|
+
fs.renameSync(moved.backupPath, moved.livePath);
|
|
490
|
+
} catch (restoreErr) {
|
|
491
|
+
ok = false;
|
|
492
|
+
console.warn('[ForceUpdate] rollback restore failed for ' + moved.name + ': ' +
|
|
493
|
+
(restoreErr.message || restoreErr));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return ok;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function _installDownloadedTree(installRoot, tempTarget, requiredVersion, successLabel) {
|
|
500
|
+
var phase = 'parse';
|
|
501
|
+
var backupRoot = null;
|
|
502
|
+
var movedEntries = [];
|
|
503
|
+
var copiedEntryNames = [];
|
|
504
|
+
var committedIndex = false;
|
|
505
|
+
var indexBackup = null;
|
|
506
|
+
var packageBackup = null;
|
|
507
|
+
try {
|
|
508
|
+
var tmpPkg = JSON.parse(fs.readFileSync(path.join(tempTarget, 'package.json'), 'utf8'));
|
|
509
|
+
if (!_isEvolverPackageName(tmpPkg && tmpPkg.name)) {
|
|
510
|
+
return _fail(FORCE_UPDATE_FAIL_CODES.DOWNLOADED_PACKAGE_NAME_MISMATCH,
|
|
511
|
+
'downloaded package.json name="' + (tmpPkg && tmpPkg.name) + '", expected "@evomap/evolver"');
|
|
512
|
+
}
|
|
513
|
+
if (!tmpPkg.version) {
|
|
514
|
+
return _fail(FORCE_UPDATE_FAIL_CODES.DOWNLOAD_INCOMPLETE,
|
|
515
|
+
'downloaded package.json has no version field');
|
|
516
|
+
}
|
|
517
|
+
if (tmpPkg.version !== requiredVersion) {
|
|
518
|
+
return _fail(FORCE_UPDATE_FAIL_CODES.DOWNLOADED_VERSION_MISMATCH,
|
|
519
|
+
'downloaded version=' + JSON.stringify(tmpPkg.version) + ', expected ' + requiredVersion);
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
if (!fs.statSync(path.join(tempTarget, 'index.js')).isFile()) {
|
|
523
|
+
return _fail(FORCE_UPDATE_FAIL_CODES.DOWNLOAD_INCOMPLETE,
|
|
524
|
+
'downloaded index.js is not a file');
|
|
525
|
+
}
|
|
526
|
+
} catch (indexReadErr) {
|
|
527
|
+
return _fail(FORCE_UPDATE_FAIL_CODES.DOWNLOAD_INCOMPLETE,
|
|
528
|
+
'missing/unreadable index.js in downloaded tree: ' + _errStr(indexReadErr));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
phase = 'delete';
|
|
532
|
+
var entries = fs.readdirSync(installRoot, { withFileTypes: true });
|
|
533
|
+
backupRoot = fs.mkdtempSync(path.join(installRoot, FORCE_UPDATE_BACKUP_PREFIX));
|
|
534
|
+
_writeForceUpdateRecoveryJournal(backupRoot, requiredVersion, _readInstallPackageVersion(installRoot));
|
|
535
|
+
var indexSrc = path.join(tempTarget, 'index.js');
|
|
536
|
+
var indexDst = path.join(installRoot, 'index.js');
|
|
537
|
+
var indexTmp = indexDst + '.' + process.pid + '.evolver-tmp';
|
|
538
|
+
indexBackup = path.join(backupRoot, 'index.js');
|
|
539
|
+
try {
|
|
540
|
+
phase = 'copy';
|
|
541
|
+
_commitAtomicFileReplacement(indexSrc, indexDst, indexTmp, indexBackup);
|
|
542
|
+
committedIndex = true;
|
|
543
|
+
} catch (indexErr) {
|
|
544
|
+
_restoreFileBackupIfPresent(indexBackup, indexDst);
|
|
545
|
+
try { fs.rmSync(indexTmp, { force: true }); } catch (_) {}
|
|
546
|
+
console.warn('[ForceUpdate] index.js commit (atomic replace) failed: ' + (indexErr.message || indexErr));
|
|
547
|
+
try { indexErr._evolverEntry = 'index.js commit'; } catch (_) {}
|
|
548
|
+
throw indexErr;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
for (var ei = 0; ei < entries.length; ei++) {
|
|
552
|
+
var eName = entries[ei].name;
|
|
553
|
+
// package.json is the install's commit marker: keep the OLD one in
|
|
554
|
+
// place through the entire delete+copy below and swap in the new one
|
|
555
|
+
// atomically at the very end. index.js was already atomically replaced
|
|
556
|
+
// after the recovery journal was written, before moving payload entries,
|
|
557
|
+
// so an interrupted update restarts through the recovery-capable
|
|
558
|
+
// bootstrap. Keep-list entries are local state and must not be deleted or
|
|
559
|
+
// overwritten by a downloaded release.
|
|
560
|
+
if (_isForceUpdateKeepEntry(eName) || _isForceUpdateBootstrapEntry(eName) ||
|
|
561
|
+
_isForceUpdateInternalEntry(eName) || eName === 'package.json') continue;
|
|
562
|
+
try {
|
|
563
|
+
(function (entryName) {
|
|
564
|
+
phase = 'delete';
|
|
565
|
+
var livePath = path.join(installRoot, entryName);
|
|
566
|
+
var backupPath = path.join(backupRoot, entryName);
|
|
567
|
+
_retryFsLockOperation(function () {
|
|
568
|
+
fs.renameSync(livePath, backupPath);
|
|
569
|
+
});
|
|
570
|
+
movedEntries.push({ name: entryName, livePath: livePath, backupPath: backupPath });
|
|
571
|
+
})(eName);
|
|
572
|
+
} catch (rmErr) {
|
|
573
|
+
console.warn('[ForceUpdate] backup move failed for ' + eName + ': ' + (rmErr.message || rmErr));
|
|
574
|
+
try { rmErr._evolverEntry = eName; } catch (_) {}
|
|
575
|
+
throw rmErr;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
phase = 'copy';
|
|
580
|
+
var newEntries = fs.readdirSync(tempTarget, { withFileTypes: true });
|
|
581
|
+
for (var ni = 0; ni < newEntries.length; ni++) {
|
|
582
|
+
if (newEntries[ni].name === 'package.json' || _isForceUpdateKeepEntry(newEntries[ni].name) ||
|
|
583
|
+
_isForceUpdateBootstrapEntry(newEntries[ni].name) || _isForceUpdateInternalEntry(newEntries[ni].name)) continue;
|
|
584
|
+
var src = path.join(tempTarget, newEntries[ni].name);
|
|
585
|
+
var dst = path.join(installRoot, newEntries[ni].name);
|
|
586
|
+
try {
|
|
587
|
+
(function (copySrc, copyDst) {
|
|
588
|
+
copiedEntryNames.push(path.basename(copyDst));
|
|
589
|
+
_retryFsLockOperation(function () {
|
|
590
|
+
fs.cpSync(copySrc, copyDst, { recursive: true });
|
|
591
|
+
});
|
|
592
|
+
})(src, dst);
|
|
593
|
+
} catch (copyErr) {
|
|
594
|
+
console.warn('[ForceUpdate] cpSync failed for ' + newEntries[ni].name + ': ' +
|
|
595
|
+
(copyErr.message || copyErr));
|
|
596
|
+
try { copyErr._evolverEntry = newEntries[ni].name; } catch (_) {}
|
|
597
|
+
throw copyErr;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
var pkgSrc = path.join(tempTarget, 'package.json');
|
|
602
|
+
var pkgDst = path.join(installRoot, 'package.json');
|
|
603
|
+
var pkgTmp = pkgDst + '.' + process.pid + '.evolver-tmp';
|
|
604
|
+
packageBackup = path.join(backupRoot, 'package.json');
|
|
605
|
+
|
|
606
|
+
try {
|
|
607
|
+
_commitAtomicFileReplacement(pkgSrc, pkgDst, pkgTmp, packageBackup);
|
|
608
|
+
} catch (pkgErr) {
|
|
609
|
+
_restoreFileBackupIfPresent(packageBackup, pkgDst);
|
|
610
|
+
if (committedIndex) _restoreFileBackupIfPresent(indexBackup, indexDst);
|
|
611
|
+
try { fs.rmSync(pkgTmp, { force: true }); } catch (_) {}
|
|
612
|
+
console.warn('[ForceUpdate] package.json commit (atomic replace) failed: ' + (pkgErr.message || pkgErr));
|
|
613
|
+
try { pkgErr._evolverEntry = 'package.json commit'; } catch (_) {}
|
|
614
|
+
throw pkgErr;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
try { fs.rmSync(tempTarget, { recursive: true, force: true }); } catch (_) {}
|
|
618
|
+
if (backupRoot) {
|
|
619
|
+
try { fs.rmSync(backupRoot, { recursive: true, force: true }); } catch (backupCleanupErr) {
|
|
620
|
+
console.warn('[ForceUpdate] backup cleanup failed: ' + (backupCleanupErr.message || backupCleanupErr));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
console.log('[ForceUpdate] ' + successLabel + ': ' + tmpPkg.version);
|
|
624
|
+
return { ok: true, version: tmpPkg.version };
|
|
625
|
+
} catch (e) {
|
|
626
|
+
if (backupRoot) {
|
|
627
|
+
var rollbackOk = true;
|
|
628
|
+
if (committedIndex) {
|
|
629
|
+
try {
|
|
630
|
+
if (!_restoreFileBackupIfPresent(indexBackup, path.join(installRoot, 'index.js'))) rollbackOk = false;
|
|
631
|
+
} catch (indexRestoreErr) {
|
|
632
|
+
rollbackOk = false;
|
|
633
|
+
console.warn('[ForceUpdate] rollback restore failed for index.js: ' +
|
|
634
|
+
(indexRestoreErr.message || indexRestoreErr));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
_restoreFileBackupIfPresent(packageBackup, path.join(installRoot, 'package.json'));
|
|
639
|
+
} catch (packageRestoreErr) {
|
|
640
|
+
rollbackOk = false;
|
|
641
|
+
console.warn('[ForceUpdate] rollback restore failed for package.json: ' +
|
|
642
|
+
(packageRestoreErr.message || packageRestoreErr));
|
|
643
|
+
}
|
|
644
|
+
var restored = _restoreMovedInstallEntries(installRoot, movedEntries, copiedEntryNames);
|
|
645
|
+
if (rollbackOk && restored) {
|
|
646
|
+
try { fs.rmSync(backupRoot, { recursive: true, force: true }); } catch (_) {}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return _classifyChannel1Error(e, phase);
|
|
650
|
+
}
|
|
206
651
|
}
|
|
207
652
|
|
|
208
653
|
// Module-level mutex: shared by every caller that requires('../forceUpdate'),
|
|
@@ -326,7 +771,7 @@ function _executeForceUpdateInner(forceUpdate) {
|
|
|
326
771
|
// the deletion loop and the user's data.
|
|
327
772
|
try {
|
|
328
773
|
const pkg = JSON.parse(fs.readFileSync(path.join(INSTALL_ROOT, 'package.json'), 'utf8'));
|
|
329
|
-
if (!pkg || (pkg.name
|
|
774
|
+
if (!pkg || !_isEvolverPackageName(pkg.name)) {
|
|
330
775
|
console.warn('[ForceUpdate] Refusing — ' + INSTALL_ROOT +
|
|
331
776
|
'/package.json has name="' + (pkg && pkg.name) +
|
|
332
777
|
'", expected "@evomap/evolver". Aborting to avoid data loss.');
|
|
@@ -337,7 +782,7 @@ function _executeForceUpdateInner(forceUpdate) {
|
|
|
337
782
|
if (_recoverPackageCommitMarkerIfMissing(INSTALL_ROOT)) {
|
|
338
783
|
try {
|
|
339
784
|
const recoveredPkg = JSON.parse(fs.readFileSync(path.join(INSTALL_ROOT, 'package.json'), 'utf8'));
|
|
340
|
-
if (!recoveredPkg || (recoveredPkg.name
|
|
785
|
+
if (!recoveredPkg || !_isEvolverPackageName(recoveredPkg.name)) {
|
|
341
786
|
console.warn('[ForceUpdate] Refusing — recovered ' + INSTALL_ROOT +
|
|
342
787
|
'/package.json has name="' + (recoveredPkg && recoveredPkg.name) +
|
|
343
788
|
'", expected "@evomap/evolver". Aborting to avoid data loss.');
|
|
@@ -351,6 +796,8 @@ function _executeForceUpdateInner(forceUpdate) {
|
|
|
351
796
|
return _fail(FORCE_UPDATE_FAIL_CODES.INSTALL_GUARD_UNREADABLE,
|
|
352
797
|
'cannot read recovered install root package.json: ' + _errStr(recoverReadErr));
|
|
353
798
|
}
|
|
799
|
+
} else if (_hasStrongEvolverInstallMarkers(INSTALL_ROOT)) {
|
|
800
|
+
console.warn('[ForceUpdate] install package.json is unreadable, but strong evolver install markers are present; bootstrap recovery allowed');
|
|
354
801
|
} else {
|
|
355
802
|
console.warn('[ForceUpdate] Refusing — cannot read ' + INSTALL_ROOT +
|
|
356
803
|
'/package.json: ' + (e && e.message || e));
|
|
@@ -438,118 +885,51 @@ function _executeForceUpdateInner(forceUpdate) {
|
|
|
438
885
|
encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
439
886
|
timeout: 60000, windowsHide: true, maxBuffer: MAX_EXEC_BUFFER,
|
|
440
887
|
});
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (tmpPkg.version && tmpPkg.version === requiredVersion) {
|
|
446
|
-
var entries = fs.readdirSync(INSTALL_ROOT, { withFileTypes: true });
|
|
447
|
-
for (var ei = 0; ei < entries.length; ei++) {
|
|
448
|
-
var eName = entries[ei].name;
|
|
449
|
-
// package.json is the install's commit marker: keep the OLD one in
|
|
450
|
-
// place through the entire delete+copy below and swap in the new one
|
|
451
|
-
// atomically at the very end (see "commit marker" block). If it were
|
|
452
|
-
// deleted here and any later cpSync threw (ENOSPC, a Windows lock that
|
|
453
|
-
// outlasts the retries, a kill), the install root would be left with
|
|
454
|
-
// no package.json — and the install-guard at the top of this function
|
|
455
|
-
// refuses on an unreadable package.json, wedging the node in
|
|
456
|
-
// install_guard_unreadable on every subsequent attempt with no path
|
|
457
|
-
// that ever re-copies it. Deferring it keeps the install self-healing.
|
|
458
|
-
if (_isForceUpdateKeepEntry(eName) || eName === 'package.json') continue;
|
|
459
|
-
try {
|
|
460
|
-
(function (entryName) {
|
|
461
|
-
phase = 'delete';
|
|
462
|
-
_retryFsLockOperation(function () {
|
|
463
|
-
fs.rmSync(path.join(INSTALL_ROOT, entryName), {
|
|
464
|
-
recursive: true, force: true, maxRetries: 3, retryDelay: 200,
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
})(eName);
|
|
468
|
-
} catch (rmErr) {
|
|
469
|
-
console.warn('[ForceUpdate] rmSync failed for ' + eName + ': ' + (rmErr.message || rmErr));
|
|
470
|
-
try { rmErr._evolverEntry = eName; } catch (_) {}
|
|
471
|
-
throw rmErr;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
phase = 'copy';
|
|
475
|
-
var newEntries = fs.readdirSync(TMP_TARGET, { withFileTypes: true });
|
|
476
|
-
for (var ni = 0; ni < newEntries.length; ni++) {
|
|
477
|
-
// Deferred: package.json is the commit marker, written last after every
|
|
478
|
-
// other entry has copied successfully (see below). Keep-list entries are
|
|
479
|
-
// local state and must not be overwritten by the downloaded release.
|
|
480
|
-
if (newEntries[ni].name === 'package.json' || _isForceUpdateKeepEntry(newEntries[ni].name)) continue;
|
|
481
|
-
var src = path.join(TMP_TARGET, newEntries[ni].name);
|
|
482
|
-
var dst = path.join(INSTALL_ROOT, newEntries[ni].name);
|
|
483
|
-
try {
|
|
484
|
-
(function (copySrc, copyDst) {
|
|
485
|
-
_retryFsLockOperation(function () {
|
|
486
|
-
fs.cpSync(copySrc, copyDst, { recursive: true });
|
|
487
|
-
});
|
|
488
|
-
})(src, dst);
|
|
489
|
-
} catch (copyErr) {
|
|
490
|
-
console.warn('[ForceUpdate] cpSync failed for ' + newEntries[ni].name + ': ' + (copyErr.message || copyErr));
|
|
491
|
-
// Tag the failing entry so _classifyChannel1Error can name it in the
|
|
492
|
-
// copy_failed detail. phase is already 'copy' here.
|
|
493
|
-
try { copyErr._evolverEntry = newEntries[ni].name; } catch (_) {}
|
|
494
|
-
throw copyErr;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
// Commit marker: every other entry copied successfully, so swap in the
|
|
498
|
-
// new package.json LAST. POSIX gets an atomic rename-over-existing. Windows
|
|
499
|
-
// cannot rename over an existing destination, so it first renames the old
|
|
500
|
-
// package.json to a recoverable same-directory backup; the install guard
|
|
501
|
-
// restores that backup on the next tick if the process dies mid-commit.
|
|
502
|
-
var pkgSrc = path.join(TMP_TARGET, 'package.json');
|
|
503
|
-
var pkgDst = path.join(INSTALL_ROOT, 'package.json');
|
|
504
|
-
var pkgTmp = pkgDst + '.' + process.pid + '.evolver-tmp';
|
|
505
|
-
var pkgBackup = pkgDst + '.' + process.pid + '.evolver-old';
|
|
506
|
-
try {
|
|
507
|
-
_retryFsLockOperation(function () {
|
|
508
|
-
try { fs.rmSync(pkgTmp, { force: true }); } catch (_) {}
|
|
509
|
-
fs.cpSync(pkgSrc, pkgTmp);
|
|
510
|
-
if (process.platform === 'win32') {
|
|
511
|
-
if (!fs.existsSync(pkgDst)) _recoverPackageCommitMarkerIfMissing(INSTALL_ROOT);
|
|
512
|
-
try { fs.rmSync(pkgBackup, { force: true }); } catch (_) {}
|
|
513
|
-
fs.renameSync(pkgDst, pkgBackup);
|
|
514
|
-
try {
|
|
515
|
-
fs.renameSync(pkgTmp, pkgDst);
|
|
516
|
-
} catch (commitErr) {
|
|
517
|
-
_restorePackageBackup(pkgBackup, pkgDst);
|
|
518
|
-
throw commitErr;
|
|
519
|
-
}
|
|
520
|
-
try { fs.rmSync(pkgBackup, { force: true }); } catch (_) {}
|
|
521
|
-
} else {
|
|
522
|
-
fs.renameSync(pkgTmp, pkgDst);
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
} catch (pkgErr) {
|
|
526
|
-
_restorePackageBackup(pkgBackup, pkgDst);
|
|
527
|
-
try { fs.rmSync(pkgTmp, { force: true }); } catch (_) {}
|
|
528
|
-
console.warn('[ForceUpdate] package.json commit (atomic replace) failed: ' + (pkgErr.message || pkgErr));
|
|
529
|
-
try { pkgErr._evolverEntry = 'package.json commit'; } catch (_) {}
|
|
530
|
-
throw pkgErr;
|
|
531
|
-
}
|
|
532
|
-
try { fs.rmSync(TMP_TARGET, { recursive: true, force: true }); } catch (_) {}
|
|
533
|
-
console.log('[ForceUpdate] GitHub Release update successful: ' + tmpPkg.version);
|
|
888
|
+
var installResult = _installDownloadedTree(
|
|
889
|
+
INSTALL_ROOT, TMP_TARGET, requiredVersion, 'GitHub Release update successful',
|
|
890
|
+
);
|
|
891
|
+
if (installResult.ok === true) {
|
|
534
892
|
return true;
|
|
535
893
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
894
|
+
channel1Failure = installResult;
|
|
895
|
+
if (
|
|
896
|
+
channel1Failure.code === FORCE_UPDATE_FAIL_CODES.DOWNLOAD_INCOMPLETE ||
|
|
897
|
+
channel1Failure.code === FORCE_UPDATE_FAIL_CODES.DOWNLOADED_PACKAGE_NAME_MISMATCH ||
|
|
898
|
+
channel1Failure.code === FORCE_UPDATE_FAIL_CODES.DOWNLOADED_VERSION_MISMATCH
|
|
899
|
+
) {
|
|
900
|
+
var postDownloadFallbackFailure = _tryDownloadReleaseTarball(requiredVersion, TMP_TARGET);
|
|
901
|
+
if (!postDownloadFallbackFailure) {
|
|
902
|
+
var postDownloadFallbackResult = _installDownloadedTree(
|
|
903
|
+
INSTALL_ROOT, TMP_TARGET, requiredVersion, 'GitHub tarball fallback update successful',
|
|
904
|
+
);
|
|
905
|
+
if (postDownloadFallbackResult.ok === true) {
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
postDownloadFallbackFailure = postDownloadFallbackResult;
|
|
909
|
+
}
|
|
910
|
+
console.warn('[ForceUpdate] GitHub tarball fallback failed (' + postDownloadFallbackFailure.code + '): ' +
|
|
911
|
+
postDownloadFallbackFailure.detail);
|
|
912
|
+
channel1Failure = _withFallbackFailure(channel1Failure, postDownloadFallbackFailure);
|
|
548
913
|
}
|
|
549
914
|
try { fs.rmSync(TMP_TARGET, { recursive: true, force: true }); } catch (_) {}
|
|
550
915
|
} catch (e) {
|
|
551
916
|
channel1Failure = _classifyChannel1Error(e, phase);
|
|
552
917
|
console.warn('[ForceUpdate] GitHub Release failed (' + channel1Failure.code + '):', e && e.message || e);
|
|
918
|
+
var fallbackFailure = _tryDownloadReleaseTarball(requiredVersion, TMP_TARGET);
|
|
919
|
+
if (!fallbackFailure) {
|
|
920
|
+
var fallbackInstallResult = _installDownloadedTree(
|
|
921
|
+
INSTALL_ROOT, TMP_TARGET, requiredVersion, 'GitHub tarball fallback update successful',
|
|
922
|
+
);
|
|
923
|
+
if (fallbackInstallResult.ok === true) {
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
fallbackFailure = fallbackInstallResult;
|
|
927
|
+
}
|
|
928
|
+
if (fallbackFailure) {
|
|
929
|
+
console.warn('[ForceUpdate] GitHub tarball fallback failed (' + fallbackFailure.code + '): ' +
|
|
930
|
+
fallbackFailure.detail);
|
|
931
|
+
channel1Failure = _withFallbackFailure(channel1Failure, fallbackFailure);
|
|
932
|
+
}
|
|
553
933
|
try { fs.rmSync(TMP_TARGET, { recursive: true, force: true }); } catch (_) {}
|
|
554
934
|
// Fall through to Channel 2 (manual download URL hint) instead of
|
|
555
935
|
// returning. A Channel 1 error (degit missing, network down, tag not
|