From 231cd0386a0bce115446d2832f477ceee09bcc33 Mon Sep 17 00:00:00 2001 From: Adam Coster Date: Mon, 27 Mar 2023 18:34:30 -0500 Subject: [PATCH] chore: Added JSDocs to improve maintainability and tooling support --- bin/update-server.js | 1 + package.json | 2 ++ src/asset-platform.js | 1 + src/constants.js | 8 +++--- src/types.ts | 23 +++++++++++++++++ src/updates.js | 57 +++++++++++++++++++++++++++++++++++++++---- 6 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 src/types.ts diff --git a/bin/update-server.js b/bin/update-server.js index efa6e4f2..90741813 100755 --- a/bin/update-server.js +++ b/bin/update-server.js @@ -36,6 +36,7 @@ const redlock = new Redlock([client], { retryDelay: ms("10s"), }); +/** @type {import("../src/types.js").ServerCache} */ const cache = { async get(key) { const json = await get(key); diff --git a/package.json b/package.json index 774917c5..55dcc1e3 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "semver": "^7.3.2" }, "devDependencies": { + "@types/node": "^18.15.10", + "@types/request-ip": "^0.0.37", "cross-env": "^7.0.2", "nock": "^13.0.5", "nodemon": "^2.0.6", diff --git a/src/asset-platform.js b/src/asset-platform.js index cc671bbe..2e01c13a 100644 --- a/src/asset-platform.js +++ b/src/asset-platform.js @@ -1,5 +1,6 @@ const { PLATFORM_ARCH } = require("./constants"); +/** @param {string} fileName */ const assetPlatform = (fileName) => { if (/.*(mac|darwin|osx).*(-arm).*\.zip/i.test(fileName)) { return PLATFORM_ARCH.DARWIN_ARM64; diff --git a/src/constants.js b/src/constants.js index f2577100..a1b16520 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,15 +1,15 @@ -const PLATFORM = { +const PLATFORM = /** @type {const} */ ({ WIN32: "win32", DARWIN: "darwin", -}; +}); -const PLATFORM_ARCH = { +const PLATFORM_ARCH = /** @type {const} */ ({ DARWIN_X64: "darwin-x64", DARWIN_ARM64: "darwin-arm64", WIN_X64: "win32-x64", WIN_IA32: "win32-ia32", WIN_ARM64: "win32-arm64", -}; +}); const PLATFORM_ARCHS = Object.values(PLATFORM_ARCH); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..a58a7158 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,23 @@ +import type { PLATFORM_ARCH } from "./constants.js"; + +export type Platform = typeof PLATFORM_ARCH[keyof typeof PLATFORM_ARCH]; + +export interface Lock { + unlock(): Promise; +} + +export interface ServerCache { + get(key: string): Promise; + set(key: string, value: any): Promise; + lock(resource: unknown): Promise; +} + +export type Latest = { + [P in Platform | "darwin" | "win32"]?: { + name: string; + version: string; + url: string; + notes: string; + RELEASES?: string; + }; +}; diff --git a/src/updates.js b/src/updates.js index 445b7b68..64166447 100644 --- a/src/updates.js +++ b/src/updates.js @@ -16,12 +16,20 @@ const { NODE_ENV: env } = process.env; if (env === "test") log.level = "error"; class Updates { + /** @type {string} */ token; + /** @type {import("./types.js").ServerCache} */ cache; + + /** @param {{token: string, cache: import("./types.js").ServerCache}} options */ constructor({ token, cache }) { assert(cache, ".cache required"); this.token = token; this.cache = cache; } + /** + * @param {string|number|undefined} port + * @param {()=>void} [cb] + */ listen(port, cb) { if (typeof port === "function") { [port, cb] = [undefined, port]; @@ -113,8 +121,14 @@ class Updates { } } + /** + * @param {string} account + * @param {string} repository + * @param {import("./types.js").Platform} platform + */ async cachedGetLatest(account, repository, platform) { const key = `${account}/${repository}`; + /** @type {import("./types.js").Latest|undefined|null} */ let latest = await this.cache.get(key); if (latest) { @@ -126,6 +140,7 @@ class Updates { return latest[platform] || null; } + /** @type {import("./types.js").Lock|undefined} */ let lock; if (this.cache.lock) { log.debug({ key }, "lock acquiring"); @@ -155,6 +170,11 @@ class Updates { return latest && latest[platform]; } + /** + * @param {string} account + * @param {string} repository + * @returns {Promise} + */ async getLatest(account, repository) { account = encodeURIComponent(account); repository = encodeURIComponent(repository); @@ -176,6 +196,7 @@ class Updates { return; } + /** @type {import("./types.js").Latest} */ const latest = {}; const releases = await res.json(); @@ -213,15 +234,16 @@ class Updates { PLATFORM_ARCH.WIN_IA32, PLATFORM_ARCH.WIN_ARM64, ]) { - if (latest[key]) { - const rurl = `https://github.com/${account}/${repository}/releases/download/${latest[key].version}/RELEASES`; + const the_latest = latest[key]; + if (the_latest) { + const rurl = `https://github.com/${account}/${repository}/releases/download/${the_latest.version}/RELEASES`; const rres = await fetch(rurl); if (rres.status < 400) { const body = await rres.text(); const matches = body.match(/[^ ]*\.nupkg/gim); assert(matches); const nuPKG = rurl.replace("RELEASES", matches[0]); - latest[key].RELEASES = body.replace(matches[0], nuPKG); + the_latest.RELEASES = body.replace(matches[0], nuPKG); } } } @@ -229,12 +251,14 @@ class Updates { return hasAnyAsset(latest) ? latest : null; } + /** @param {string|null} ip */ hashIp(ip) { if (!ip) return; return crypto.createHash("sha256").update(ip).digest("hex"); } } +/** @param {import("./types.js").Latest} latest */ const hasAllAssets = (latest) => { return !!( latest[PLATFORM_ARCH.DARWIN_X64] && @@ -245,6 +269,7 @@ const hasAllAssets = (latest) => { ); }; +/** @param {import("./types.js").Latest} latest */ const hasAnyAsset = (latest) => { return !!( latest[PLATFORM_ARCH.DARWIN_X64] || @@ -255,28 +280,45 @@ const hasAnyAsset = (latest) => { ); }; +/** + * @param {Res} res + * @param {string} message + */ const notFound = (res, message = "Not found") => { res.statusCode = 404; res.end(message); }; +/** + * @param {Res} res + * @param {string} message + */ const badRequest = (res, message) => { res.statusCode = 400; res.end(message); }; +/** @param {Res} res */ const noContent = (res) => { res.statusCode = 204; res.end(); }; +/** + * @param {Res} res + * @param {any} obj + */ const json = (res, obj) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(obj)); }; -// DO NOT PASS USER-SUPPLIED CONTENT TO THIS FUNCTION -// AS IT WILL REDIRECT A USER ANYWHERE +/** + * DO NOT PASS USER-SUPPLIED CONTENT TO THIS FUNCTION + * AS IT WILL REDIRECT A USER ANYWHERE + * @param {Res} res + * @param {string} url + */ const redirect = (res, url) => { res.statusCode = 302; res.setHeader("Location", url); @@ -284,3 +326,8 @@ const redirect = (res, url) => { }; module.exports = Updates; + +/** + * @typedef {http.IncomingMessage} Req + * @typedef {http.ServerResponse} Res + */