UNPKG

hfs

Version:
200 lines (199 loc) 9.89 kB
"use strict"; // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.autoCheckUpdateResult = void 0; exports.getVersions = getVersions; exports.getUpdates = getUpdates; exports.localUpdateAvailable = localUpdateAvailable; exports.previousAvailable = previousAvailable; exports.updateSupported = updateSupported; exports.update = update; const github_1 = require("./github"); const const_1 = require("./const"); const path_1 = require("path"); const child_process_1 = require("child_process"); const misc_1 = require("./misc"); const fs_1 = require("fs"); const plugins_1 = require("./plugins"); const promises_1 = require("fs/promises"); const open_1 = __importDefault(require("open")); const config_1 = require("./config"); const util_os_1 = require("./util-os"); const first_1 = require("./first"); const persistence_1 = require("./persistence"); const lodash_1 = __importDefault(require("lodash")); const argv_1 = require("./argv"); const updateToBeta = (0, config_1.defineConfig)('update_to_beta', false); const autoCheckUpdate = (0, config_1.defineConfig)('auto_check_update', true); const lastCheckUpdate = persistence_1.storedMap.singleSync('lastCheckUpdate', 0); const AUTO_CHECK_EVERY = misc_1.DAY; exports.autoCheckUpdateResult = persistence_1.storedMap.singleSync('autoCheckUpdateResult', undefined); exports.autoCheckUpdateResult.ready().then(() => { exports.autoCheckUpdateResult.set(v => { if (!v) return; // refresh isNewer, as currentVersion may have changed v.isNewer = config_1.currentVersion.olderThan(v.tag_name); return v; }); }); setInterval((0, misc_1.debounceAsync)(async () => { if (!autoCheckUpdate.get()) return; if (Date.now() < lastCheckUpdate.get() + AUTO_CHECK_EVERY) return; console.log("checking for updates"); try { const u = (await getUpdates(true))[0]; if (u) console.log("new version available", u.name); exports.autoCheckUpdateResult.set(u); lastCheckUpdate.set(Date.now()); } catch (_a) { } }), misc_1.HOUR); const ReleaseKeys = ['prerelease', 'tag_name', 'name', 'body', 'assets', 'isNewer', 'versionScalar']; const ReleaseAssetKeys = ['name', 'browser_download_url']; const curV = config_1.currentVersion.scalar; function prepareRelease(r) { const v = (0, config_1.versionToScalar)(r.name); return Object.assign(lodash_1.default.pick(r, ReleaseKeys), { versionScalar: v, isNewer: v > curV, // make easy to know what's newer assets: r.assets.map((a) => lodash_1.default.pick(a, ReleaseAssetKeys)) }); } async function getVersions(interrupt) { if (!updateToBeta.get() && !const_1.RUNNING_BETA) return []; const ret = []; for await (const x of (0, github_1.apiGithubPaginated)(`repos/${const_1.HFS_REPO}/releases`)) { if (x.name.endsWith('-ignore')) continue; const rel = prepareRelease(x); if (rel.versionScalar === curV) continue; if (interrupt === null || interrupt === void 0 ? void 0 : interrupt(rel)) break; // avoid fetching too much data ret.push(rel); } return lodash_1.default.sortBy(ret, x => -x.versionScalar); } async function getUpdates(strict = false) { (0, github_1.getProjectInfo)(); // check for alerts const stable = prepareRelease(await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases/latest')); const res = await getVersions(r => r.versionScalar < stable.versionScalar); // we don't consider betas before stable const ret = res.filter(x => x.prerelease && (strict ? x.isNewer : (x.versionScalar !== curV))); if (stable.isNewer || const_1.RUNNING_BETA && !strict) ret.push(stable); return ret; } const LOCAL_UPDATE = 'hfs-update.zip'; // update from file takes precedence over net const INSTALLED_FN = 'hfs-installed.zip'; const PREVIOUS_FN = 'hfs-previous.zip'; function localUpdateAvailable() { return (0, misc_1.exists)(LOCAL_UPDATE); } function previousAvailable() { return (0, misc_1.exists)(PREVIOUS_FN); } async function updateSupported() { return process.env.DISABLE_UPDATE ? false : (argv_1.argv.forceupdate || const_1.IS_BINARY && !await util_os_1.RUNNING_AS_SERVICE); } async function update(tagOrUrl = '') { if (!await updateSupported()) throw "only binary versions supports automatic update for now"; let url = tagOrUrl.includes('://') && tagOrUrl; if (tagOrUrl === const_1.PREVIOUS_TAG) await (0, promises_1.rename)(PREVIOUS_FN, LOCAL_UPDATE); else if (tagOrUrl ? !url : !await localUpdateAvailable()) { if (/^\d/.test(tagOrUrl)) // work even if the tag is passed without the initial 'v' (useful for console commands) tagOrUrl = 'v' + tagOrUrl; const update = !tagOrUrl ? (await getUpdates(true))[0] : await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases/tags/' + tagOrUrl).catch(e => { if (e.message === '404') console.error("version not found"); else throw e; }); if (!update) throw "update not found"; const plat = '-' + (0, misc_1.xlate)(process.platform, { win32: 'windows', darwin: 'mac' }); const assetSearch = `${plat}-${process.arch}`; const legacyAssetSearch = `${plat}${(0, misc_1.prefix)('-', (0, misc_1.xlate)(process.arch, { x64: '', arm64: 'arm' }))}.zip`; // legacy pre-0.53.0-rc16 const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip')) || update.assets.find((x) => x.name.endsWith(legacyAssetSearch)); if (!asset) throw `asset not found: ${assetSearch}`; url = asset.browser_download_url; } if (url) { console.log("downloading", url); const { body } = await (0, misc_1.httpWithBody)(url); if (!body) throw "download failed for " + url; await (0, promises_1.writeFile)(LOCAL_UPDATE, body); } const bin = process.execPath; const binPath = (0, path_1.dirname)(bin); const binFile = 'hfs' + (const_1.IS_WINDOWS ? '.exe' : ''); // currently running bin could have been renamed let newBinFile = binFile; do { newBinFile = 'new-' + newBinFile; } while ((0, fs_1.existsSync)((0, path_1.join)(binPath, newBinFile))); plugins_1.pluginsWatcher.pause(); try { await (0, misc_1.unzip)((0, fs_1.createReadStream)(LOCAL_UPDATE), path => (0, path_1.join)(binPath, path === binFile ? newBinFile : path)); const newBin = (0, path_1.join)(binPath, newBinFile); if (!const_1.IS_WINDOWS) { const { mode } = await (0, promises_1.stat)(bin); await (0, promises_1.chmod)(newBin, mode).catch(console.error); } await (0, promises_1.rename)(INSTALLED_FN, PREVIOUS_FN).catch(console.warn); await (0, promises_1.rename)(LOCAL_UPDATE, INSTALLED_FN).catch(console.warn); (0, first_1.onProcessExit)(() => { const oldBinFile = 'old-' + binFile; const oldBin = (0, path_1.join)(binPath, oldBinFile); try { (0, fs_1.unlinkSync)(oldBin); } catch (_a) { } (0, fs_1.renameSync)(bin, oldBin); console.log("launching new version in background", newBinFile); launch(newBin, ['--updating', binFile, '--cwd .'], { sync: true }); // sync necessary to work on Mac by double-click }); console.log("quitting"); setTimeout(() => process.exit()); // give time to return (and caller to complete, eg: rest api to reply) } catch (e) { plugins_1.pluginsWatcher.unpause(); throw (e === null || e === void 0 ? void 0 : e.message) || String(e); } } function launch(cmd, pars = [], options) { return ((options === null || options === void 0 ? void 0 : options.sync) ? child_process_1.spawnSync : child_process_1.spawn)((0, util_os_1.cmdEscape)(cmd), pars, { detached: true, shell: true, stdio: [0, 1, 2], ...options }); } if (argv_1.argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references const bin = process.execPath; const dest = (0, path_1.join)((0, path_1.dirname)(bin), argv_1.argv.updating); (0, fs_1.renameSync)(bin, dest); // have to relaunch with new name, or otherwise next update will fail with EBUSY on hfs.exe console.log("renamed binary file to", argv_1.argv.updating, "and restarting"); // be sure to test launching both double-clicking and in a terminal if (const_1.IS_WINDOWS) // this method on Mac works only once, and without console (0, first_1.onProcessExit)(() => launch(dest, ['--updated', '--cwd .'])); // launch+sync here would cause old process to stay open, locking ports else { /* open() is the only consistent way that i could find working on Mac that preserved console input/output over relaunching, * but I couldn't find a way to pass parameters, at least on Linux. The workaround I'm using is to write them to a temp file, that's read and deleted at restart. * For the record, on mac you can: write "./hfs arg1 arg2" to /tmp/tmp.sh with 0o700, and then spawn "open -a Terminal /tmp/tmp.sh" */ try { (0, fs_1.writeFileSync)(const_1.ARGS_FILE, JSON.stringify(['--updated', '--cwd', process.cwd().replaceAll(' ', '\\ ')])); } catch (_a) { } void (0, open_1.default)(dest); } process.exit(); }