From 8cd56db7ce3b60c2e40056b10be65b04a19a8020 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sat, 13 Jan 2024 11:41:22 +0100 Subject: [PATCH 1/3] refactor: extract table formatting function Extract logic for formatting packument for viewing into separate function. --- src/cmd-search.ts | 75 +++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/src/cmd-search.ts b/src/cmd-search.ts index b45b1137..a2feac45 100644 --- a/src/cmd-search.ts +++ b/src/cmd-search.ts @@ -12,10 +12,6 @@ import { SemanticVersion } from "./types/semantic-version"; import { CmdOptions } from "./types/options"; import { Registry } from "./registry-client"; -type DateString = string; - -type TableRow = [DomainName, SemanticVersion, DateString, ""]; - type SearchResultCode = 0 | 1; export type SearchOptions = CmdOptions; @@ -45,14 +41,14 @@ const getNpmFetchOptions = function (registry: Registry): Options { const searchEndpoint = async function ( registry: Registry, keyword: string -): Promise { +): Promise { try { // NOTE: The results of the search will be Packument objects so we can change the type const results = ( await npmSearch(keyword, getNpmFetchOptions(registry)) ); log.verbose("npmsearch", results.join(os.EOL)); - return results.map(getTableRow); + return results; } catch (err) { if (isHttpError(err) && !is404Error(err)) { log.error("", err.message); @@ -64,32 +60,28 @@ const searchEndpoint = async function ( const searchOld = async function ( registry: Registry, keyword: string -): Promise { +): Promise { // all endpoint try { const results = ( await npmFetch.json("/-/all", getNpmFetchOptions(registry)) ); - let objects: SearchedPackument[] = []; + let packuments: SearchedPackument[] = []; if (results) { if (Array.isArray(results)) { // results is an array of objects - objects = results; + packuments = results; } else { // results is an object if ("_updated" in results) delete results["_updated"]; - objects = Object.values(results); + packuments = Object.values(results); } } - log.verbose("endpoint.all", objects.join(os.EOL)); - // prepare rows - const rows = objects.map((packument) => { - return getTableRow(packument); - }); + log.verbose("endpoint.all", packuments.join(os.EOL)); // filter keyword const klc = keyword.toLowerCase(); - return rows.filter( - (row) => row.filter((x) => x.toLowerCase().includes(klc)).length > 0 + return packuments.filter((packument) => + packument.name.toLowerCase().includes(klc) ); } catch (err) { if (isHttpError(err) && !is404Error(err)) { @@ -99,25 +91,32 @@ const searchOld = async function ( } }; -const getTable = function () { - return new Table({ - head: ["Name", "Version", "Date"], - colWidths: [42, 20, 12], - }); -}; +function formatResults(searchResults: SearchedPackument[]): string { + function getTable(): Table<[string, string, string]> { + return new Table({ + head: ["Name", "Version", "Date"], + colWidths: [42, 20, 12], + }); + } -const getTableRow = function (packument: SearchedPackument): TableRow { - const name = packument.name; - const version = tryGetLatestVersion(packument); - let date = ""; - if (packument.time && packument.time.modified) - date = packument.time.modified.split("T")[0]!; - if (packument.date) { - date = packument.date.toISOString().slice(0, 10); + function getTableRow(packument: SearchedPackument): [string, string, string] { + const name = packument.name; + const version = tryGetLatestVersion(packument); + let date = ""; + if (packument.time && packument.time.modified) + date = packument.time.modified.split("T")[0]!; + if (packument.date) { + date = packument.date.toISOString().slice(0, 10); + } + assert(version !== undefined); + return [name, version, date]; } - assert(version !== undefined); - return [name, version, date, ""]; -}; + + const rows = searchResults.map(getTableRow); + const table = getTable(); + rows.forEach((row) => table.push(row)); + return table.toString(); +} export async function search( keyword: string, @@ -127,17 +126,15 @@ export async function search( const env = await parseEnv(options, false); if (env === null) return 1; - const table = getTable(); // search endpoint let results = await searchEndpoint(env.registry, keyword); // search old search if (results === undefined) { - results = (await searchOld(env.registry, keyword)) || []; + results = await searchOld(env.registry, keyword); } // search upstream - if (results && results.length) { - results.forEach((x) => table.push(x.slice(0, -1))); - console.log(table.toString()); + if (results !== undefined && results.length > 0) { + console.log(formatResults(results)); } else log.notice("", `No matches found for "${keyword}"`); return 0; } From f5dc272c79f9d1c7a5786deee08130b1df9fc3c1 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sat, 13 Jan 2024 11:52:13 +0100 Subject: [PATCH 2/3] refactor: extract helper type Extract type that represents the version-part of a packument. Shorter than always rewriting the properties manually. --- src/types/packument.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/types/packument.ts b/src/types/packument.ts index 3c270df4..ad060f61 100644 --- a/src/types/packument.ts +++ b/src/types/packument.ts @@ -72,9 +72,15 @@ export type UnityPackument = { users?: Record; }; -const hasLatestDistTag = ( - packument: Partial -): packument is Partial & { +/** + * The minimum properties a Packument must have in order for it's version + * to be determined. + */ +export type VersionedPackument = Pick; + +const hasLatestDistTag = ( + packument: T +): packument is T & { "dist-tags": { latest: SemanticVersion }; } => { return packument["dist-tags"]?.["latest"] !== undefined; @@ -84,10 +90,9 @@ const hasLatestDistTag = ( * Attempt to get the latest version from a package * @param packument The package. All properties are assumed to be potentially missing */ -export const tryGetLatestVersion = function (packument: { - "dist-tags"?: { latest?: SemanticVersion }; - version?: SemanticVersion; -}): SemanticVersion | undefined { +export const tryGetLatestVersion = function ( + packument: VersionedPackument +): SemanticVersion | undefined { if (hasLatestDistTag(packument)) return packument["dist-tags"].latest; else if (packument.version) return packument.version; }; From c3fc2ce9d07abd060f89b325dd88cf0dc777844f Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sat, 13 Jan 2024 11:57:27 +0100 Subject: [PATCH 3/3] refactor: extract module Extract table formatting logic to own module. This way other output formatting functions can be grouped together an reused. --- src/cmd-search.ts | 34 +++--------------------------- src/output-formatting.ts | 45 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 src/output-formatting.ts diff --git a/src/cmd-search.ts b/src/cmd-search.ts index a2feac45..bc51b124 100644 --- a/src/cmd-search.ts +++ b/src/cmd-search.ts @@ -1,16 +1,15 @@ import npmSearch, { Options } from "libnpmsearch"; import npmFetch from "npm-registry-fetch"; -import Table from "cli-table"; import log from "./logger"; import { is404Error, isHttpError } from "./utils/error-type-guards"; import * as os from "os"; -import assert from "assert"; -import { tryGetLatestVersion, UnityPackument } from "./types/packument"; +import { UnityPackument } from "./types/packument"; import { parseEnv } from "./utils/env"; import { DomainName } from "./types/domain-name"; import { SemanticVersion } from "./types/semantic-version"; import { CmdOptions } from "./types/options"; import { Registry } from "./registry-client"; +import { formatAsTable } from "./output-formatting"; type SearchResultCode = 0 | 1; @@ -91,33 +90,6 @@ const searchOld = async function ( } }; -function formatResults(searchResults: SearchedPackument[]): string { - function getTable(): Table<[string, string, string]> { - return new Table({ - head: ["Name", "Version", "Date"], - colWidths: [42, 20, 12], - }); - } - - function getTableRow(packument: SearchedPackument): [string, string, string] { - const name = packument.name; - const version = tryGetLatestVersion(packument); - let date = ""; - if (packument.time && packument.time.modified) - date = packument.time.modified.split("T")[0]!; - if (packument.date) { - date = packument.date.toISOString().slice(0, 10); - } - assert(version !== undefined); - return [name, version, date]; - } - - const rows = searchResults.map(getTableRow); - const table = getTable(); - rows.forEach((row) => table.push(row)); - return table.toString(); -} - export async function search( keyword: string, options: SearchOptions @@ -134,7 +106,7 @@ export async function search( } // search upstream if (results !== undefined && results.length > 0) { - console.log(formatResults(results)); + console.log(formatAsTable(results)); } else log.notice("", `No matches found for "${keyword}"`); return 0; } diff --git a/src/output-formatting.ts b/src/output-formatting.ts new file mode 100644 index 00000000..62939478 --- /dev/null +++ b/src/output-formatting.ts @@ -0,0 +1,45 @@ +import Table from "cli-table"; +import { + tryGetLatestVersion, + UnityPackument, + VersionedPackument, +} from "./types/packument"; +import assert from "assert"; + +/** + * A type describing the minimum required properties of a packument + * for being able to be formatted as a table. + */ +type TableablePackument = Pick & + VersionedPackument; + +/** + * Formats an array of packuments as a table. The table is returned + * as a single string (Includes line breaks). + * @param packuments The packuments + */ +export function formatAsTable(packuments: TableablePackument[]): string { + const table = new Table({ + head: ["Name", "Version", "Date"], + colWidths: [42, 20, 12], + }); + + function getTableRow( + packument: TableablePackument + ): [string, string, string] { + const name = packument.name; + const version = tryGetLatestVersion(packument); + let date = ""; + if (packument.time && packument.time.modified) + date = packument.time.modified.split("T")[0]!; + if (packument.date) { + date = packument.date.toISOString().slice(0, 10); + } + assert(version !== undefined); + return [name, version, date]; + } + + const rows = packuments.map(getTableRow); + rows.forEach((row) => table.push(row)); + return table.toString(); +}