Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add JSDocs to improve maintainability and tooling support #129

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/update-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"semver": "^7.3.2"
},
"devDependencies": {
"@types/node": "^18.15.10",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provides editor support for Typescript-aware IDEs.

"@types/request-ip": "^0.0.37",
"cross-env": "^7.0.2",
"nock": "^13.0.5",
"nodemon": "^2.0.6",
Expand Down
1 change: 1 addition & 0 deletions src/asset-platform.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -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);

Expand Down
23 changes: 23 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -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<void>;
}

export interface ServerCache {
get<T = unknown>(key: string): Promise<T | undefined>;
set(key: string, value: any): Promise<void>;
lock(resource: unknown): Promise<Lock>;
}

export type Latest = {
[P in Platform | "darwin" | "win32"]?: {
name: string;
version: string;
url: string;
notes: string;
RELEASES?: string;
};
};
57 changes: 52 additions & 5 deletions src/updates.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand All @@ -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");
Expand Down Expand Up @@ -155,6 +170,11 @@ class Updates {
return latest && latest[platform];
}

/**
* @param {string} account
* @param {string} repository
* @returns {Promise<import("./types.js").Latest|undefined|null>}
*/
async getLatest(account, repository) {
account = encodeURIComponent(account);
repository = encodeURIComponent(repository);
Expand All @@ -176,6 +196,7 @@ class Updates {
return;
}

/** @type {import("./types.js").Latest} */
const latest = {};

const releases = await res.json();
Expand Down Expand Up @@ -213,28 +234,31 @@ 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];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block contains the only functional changes I made. Their only purpose is to get better type support, since latest[key] shows up as Latest|undefined even inside the if(latest[key]){} block.

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);
}
}
}

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] &&
Expand All @@ -245,6 +269,7 @@ const hasAllAssets = (latest) => {
);
};

/** @param {import("./types.js").Latest} latest */
const hasAnyAsset = (latest) => {
return !!(
latest[PLATFORM_ARCH.DARWIN_X64] ||
Expand All @@ -255,32 +280,54 @@ 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);
res.end(url);
};

module.exports = Updates;

/**
* @typedef {http.IncomingMessage} Req
* @typedef {http.ServerResponse} Res
*/