diff --git a/README.md b/README.md index 3d90866..ac4b00a 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,12 @@ nrml --help ## Usage ```sh -nrm-lite v0.1.0 +nrm-lite Usage: nrml ls List registry nrml use [name] Use registry + nrml test Test registry speed nrml rc Open .npmrc file nrml help Show this help Global Options: diff --git a/cli.mjs b/cli.mjs index 8186497..ca9c78c 100644 --- a/cli.mjs +++ b/cli.mjs @@ -6,7 +6,7 @@ import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname } from 'node:path' import { getRegistry, setRegistry, getConfigPath } from './config.mjs' -import { REGISTRIES } from './registry.mjs' +import { REGISTRIES, speedTest } from './registry.mjs' import c, { printRegistries } from './tty.mjs' // https://nodejs.org/api/util.html#utilparseargsconfig @@ -39,6 +39,10 @@ if (values.help) { const command = positionals[0] || 'ls' const { local } = values +/** + * @type {string} + */ +let name switch (command) { case 'h': @@ -48,15 +52,19 @@ switch (command) { case 'ls': ls() break + case 'test': + name = positionals[1] + test(name) + break case 'rc': rc() break case 'use': - const name = positionals[1] + name = positionals[1] use(name) break default: - console.error(`Unknown command ${command}\n`) + console.error(`Unknown command '${command}'\n`) process.exit(1) } @@ -71,6 +79,7 @@ function help() { ${c.bold('Usage:')} nrml ls List registry nrml use ${c.gray('[name]')} Use registry + nrml test Test registry speed nrml rc Open .npmrc file nrml help Show this help ${c.bold('Global Options:')} @@ -103,6 +112,22 @@ async function use(name) { printRegistries(registryUrl) } +/** + * @param {string} name + */ +async function test(name) { + const info = await Promise.all( + Object.entries(REGISTRIES).map(async ([name, url]) => ({ + name, + url, + timeSpent: await speedTest(url), + })) + ) + + const currentRegistry = await getRegistry(local) + printRegistries(currentRegistry, info, 2000) +} + async function rc() { const filePath = await getConfigPath(local) try { diff --git a/config.mjs b/config.mjs index aa5e695..edad94f 100644 --- a/config.mjs +++ b/config.mjs @@ -31,7 +31,7 @@ export async function setRegistry(local, registryUrl) { } /** - * @param {boolean|undefined} local + * @param {boolean=} local */ export async function getRegistry(local) { const filePath = await getConfigPath(local) @@ -42,7 +42,7 @@ export async function getRegistry(local) { /** * If `local` is not provided, check if local npmrc file exists - * @param {boolean|undefined} local + * @param {boolean=} local * @noexcept */ export async function getConfigPath(local) { diff --git a/package.json b/package.json index 45c6862..e5ac91f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nrm-lite", - "version": "0.1.0", + "version": "0.1.1", "type": "module", "description": "simple and lightweight replacement for nrm", "main": "index.mjs", diff --git a/registry.mjs b/registry.mjs index 3ae1011..beaaf11 100644 --- a/registry.mjs +++ b/registry.mjs @@ -47,3 +47,20 @@ export async function findRegistryFromStream(stream) { } return { registry: null, registryLineNumber: null, lines } } + +/** + * @param {string} url + */ +export async function speedTest(url, milliseconds = 2000) { + try { + const beginTime = Date.now() + await fetch(url, { + method: 'HEAD', + signal: AbortSignal.timeout(milliseconds), + }) + const timeSpent = Date.now() - beginTime + return timeSpent + } catch { + return null + } +} diff --git a/tty.mjs b/tty.mjs index 19e3b6e..1d0dcc9 100644 --- a/tty.mjs +++ b/tty.mjs @@ -1,4 +1,4 @@ -import { REGISTRIES } from './registry.mjs' +import { REGISTRIES, speedTest } from './registry.mjs' /** * @param {string} str @@ -32,18 +32,51 @@ const c = { export default c /** - * @param {string} registryUrl + * @param {string} currentRegistryUrl + * @param {Array<{name:string,url:string,timeSpent?:number|null}>} registriesInfo + * @param {number=} timeoutLimit - milliseconds */ -export function printRegistries(registryUrl) { - let maxNameLength = 0 +export function printRegistries( + currentRegistryUrl, + registriesInfo = Object.entries(REGISTRIES).map(([name, url]) => ({ + name, + url, + })), + timeoutLimit +) { + const maxNameLength = Math.max( + ...registriesInfo.map(({ name }) => name.length) + ) + /** + * @type {number=} + */ + let maxUrlLength = undefined - const registries = Object.entries(REGISTRIES).map(([name, url]) => { - maxNameLength = Math.max(maxNameLength, name.length) - return { name, url, highlight: url === registryUrl } - }) + for (let { name, url, timeSpent } of registriesInfo) { + if (timeoutLimit) { + // lazy compute + if (!maxUrlLength) + maxUrlLength = Math.max( + ...registriesInfo.map(({ url }) => url.length) + ) + } - for (const { name, url, highlight } of registries) { - const row = `${name.padEnd(maxNameLength)} → ${url}` - console.log(highlight ? c.blue(row) : row) + let row = `${name.padEnd(maxNameLength)} → ${ + maxUrlLength ? url.padEnd(maxUrlLength) : url + }` + if (url === currentRegistryUrl) row = c.blue(row) + + if (timeoutLimit) { + timeSpent ??= Infinity + + if (timeSpent >= timeoutLimit) { + row += c.red(` (>${(timeoutLimit / 1000).toFixed(1)}s)`) + } else if (timeSpent >= timeoutLimit / 2) { + row += c.yellow(` (${(timeSpent / 1000).toFixed(2)}s)`) + } else { + row += c.green(` (${(timeSpent / 1000).toFixed(2)}s)`) + } + } + console.log(row) } }