UNPKG

@fdm-monster/server

Version:

FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.

128 lines (127 loc) 6.77 kB
import { ExternalServiceError, InternalServerException, NotFoundException } from "../../exceptions/runtime.exceptions.js"; import { AppConstants } from "../../server.constants.js"; import { ensureDirExists, getMediaPath } from "../../utils/fs.utils.js"; import { errorSummary } from "../../utils/error.utils.js"; import { checkVersionSatisfiesMinimum, getMaximumOfVersionsSafe } from "../../utils/semver.utils.js"; import { RequestError } from "octokit"; import { join } from "node:path"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { compare } from "semver"; import { readdir, rm } from "node:fs/promises"; import AdmZip from "adm-zip"; //#region src/services/core/client-bundle.service.ts var ClientBundleService = class ClientBundleService { logger; githubOwner = AppConstants.orgName; githubRepo = AppConstants.clientRepoName; minVersion = AppConstants.defaultClientMinimum; constructor(loggerFactory, githubService) { this.githubService = githubService; this.logger = loggerFactory(ClientBundleService.name); } get clientPackageJsonPath() { return join(getMediaPath(), AppConstants.defaultClientBundleStorage, "package.json"); } get clientIndexHtmlPath() { return join(getMediaPath(), AppConstants.defaultClientBundleStorage, "dist/index.html"); } async getReleases() { try { const [releases, latestRelease] = await Promise.all([this.githubService.getReleases(this.githubOwner, this.githubRepo), this.githubService.getLatestRelease(this.githubOwner, this.githubRepo)]); return { minimum: { tag_name: this.minVersion }, current: { tag_name: this.getClientVersion() }, latest: latestRelease.data, releases: releases.data }; } catch (e) { if (e instanceof RequestError) { this.logger.error(`Github OctoKit error ${errorSummary(e)}`); throw new ExternalServiceError({ error: "Github OctoKit error: " + e.message }, "GitHub"); } throw new InternalServerException("Something went wrong with the request to Github"); } } async shouldUpdateWithReason(overrideAutoUpdate, minimumVersion, requestedVersion, allowDowngrade) { const clientAutoUpdate = AppConstants.enableClientDistAutoUpdateKey; const existingVersion = this.getClientVersion(); minimumVersion ??= this.minVersion; if (!clientAutoUpdate && !overrideAutoUpdate) return this.createResponse(false, existingVersion, minimumVersion, requestedVersion, null, "Client auto-update disabled, skipping"); if (!existingVersion || !this.doesClientIndexHtmlExist()) { const reason = existingVersion ? "Client index.html could not be found" : "Client package.json does not exist"; return this.createResponse(true, existingVersion, minimumVersion, requestedVersion, getMaximumOfVersionsSafe(minimumVersion, requestedVersion), `${reason}, downloading new release`); } if (!checkVersionSatisfiesMinimum(existingVersion, minimumVersion)) return this.createResponse(true, existingVersion, minimumVersion, requestedVersion, getMaximumOfVersionsSafe(minimumVersion, requestedVersion), `Client version ${existingVersion} below minimum ${minimumVersion}, downloading new release`); if (requestedVersion) return this.evaluateRequestedVersion(existingVersion, minimumVersion, requestedVersion, allowDowngrade); return this.createResponse(false, existingVersion, minimumVersion, requestedVersion, getMaximumOfVersionsSafe(minimumVersion, requestedVersion), `Client already satisfies minimum version ${minimumVersion}`); } async downloadClientUpdate(releaseTag) { const release = (await this.githubService.getReleaseByTag(this.githubOwner, this.githubRepo, releaseTag)).data; const assetName = `dist-client-${release.tag_name}.zip`; const asset = release.assets.find((a) => a.name === assetName); if (!asset) throw new NotFoundException(`Asset ${assetName} not found in release ${release.tag_name}`); const zipPath = await this.downloadZip(asset.id, asset.name); await this.extractZip(zipPath); return release.tag_name; } evaluateRequestedVersion(currentVersion, minimumVersion, requestedVersion, allowDowngrade) { if (compare(requestedVersion, minimumVersion) === -1) return this.createResponse(false, currentVersion, minimumVersion, requestedVersion, requestedVersion, `Requested version ${requestedVersion} below minimum ${minimumVersion}, skipping`); const versionComparison = compare(requestedVersion, currentVersion); if (versionComparison === 0) return this.createResponse(false, currentVersion, minimumVersion, requestedVersion, requestedVersion, `Requested version ${requestedVersion} same as current, skipping`); else if (versionComparison === -1) return this.createResponse(!!allowDowngrade, currentVersion, minimumVersion, requestedVersion, requestedVersion, allowDowngrade ? `Downgrading to ${requestedVersion} (above minimum ${minimumVersion})` : `Downgrade not allowed, skipping`); else return this.createResponse(true, currentVersion, minimumVersion, requestedVersion, requestedVersion, `Upgrading from ${currentVersion} to ${requestedVersion}`); } createResponse(shouldUpdate, currentVersion, minimumVersion, requestedVersion, targetVersion, reason) { return { shouldUpdate, requestedVersion, currentVersion, minimumVersion, targetVersion, reason }; } async downloadZip(assetId, assetName) { const assetResult = await this.githubService.requestAsset(this.githubOwner, this.githubRepo, assetId); const distZipPath = join(getMediaPath(), AppConstants.defaultClientBundleZipsStorage); ensureDirExists(distZipPath); const path = join(distZipPath, assetName); writeFileSync(path, Buffer.from(assetResult.data)); this.logger.log(`Downloaded client ZIP to ${distZipPath}`); return path; } async extractZip(zipPath) { const distPath = join(getMediaPath(), AppConstants.defaultClientBundleStorage); ensureDirExists(distPath); this.logger.debug(`Clearing contents of ${distPath}`); for (const item of await readdir(distPath)) { const itemPath = join(distPath, item); await rm(itemPath, { force: true, recursive: true }).catch((e) => this.logger.error(`Failed to remove ${itemPath}: ${e.message}`)); } try { new AdmZip(zipPath).extractAllTo(distPath); this.logger.log(`Successfully extracted client to ${distPath}`); } catch (e) { this.logger.error(`Extraction failed: ${e.message}`); throw e; } } doesClientIndexHtmlExist() { return existsSync(this.clientIndexHtmlPath); } getClientVersion() { const path = this.clientPackageJsonPath; if (!existsSync(path)) return null; try { return JSON.parse(readFileSync(path, "utf-8"))?.version ?? null; } catch { return null; } } }; //#endregion export { ClientBundleService }; //# sourceMappingURL=client-bundle.service.js.map