diff --git a/CHANGELOG.md b/CHANGELOG.md index 2598560..34800fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,22 +2,19 @@ ## [0.1.3](https://github.com/YieldRay/nrm-lite/compare/v0.1.2...v0.1.3) (2024-03-12) - ### Bug Fixes -* fix the --version flag ([9e059ef](https://github.com/YieldRay/nrm-lite/commit/9e059efaf24198123e01307a71e59dfdad09bba2)) +- fix the --version flag ([9e059ef](https://github.com/YieldRay/nrm-lite/commit/9e059efaf24198123e01307a71e59dfdad09bba2)) ## [0.1.2](https://github.com/YieldRay/nrm-lite/compare/v0.1.1...v0.1.2) (2024-03-11) - ### Features -* support timeout for test command ([d14c87d](https://github.com/YieldRay/nrm-lite/commit/d14c87d70a5990dc60c855f5c42883d1d9e8da3e)) +- support timeout for test command ([d14c87d](https://github.com/YieldRay/nrm-lite/commit/d14c87d70a5990dc60c855f5c42883d1d9e8da3e)) ## 0.1.1 (2024-03-11) - ### Features -* add test command ([432f29d](https://github.com/YieldRay/nrm-lite/commit/432f29db4524f64951056970ab44b0917302efa2)) -* initial commit ([ff2c04b](https://github.com/YieldRay/nrm-lite/commit/ff2c04b774b81b849c60a46c18dde2555c51b61e)) +- add test command ([432f29d](https://github.com/YieldRay/nrm-lite/commit/432f29db4524f64951056970ab44b0917302efa2)) +- initial commit ([ff2c04b](https://github.com/YieldRay/nrm-lite/commit/ff2c04b774b81b849c60a46c18dde2555c51b61e)) diff --git a/README.md b/README.md index 1aa07ee..beb0951 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![node-current](https://img.shields.io/node/v/nrm-lite)](https://nodejs.dev/) [![install size](https://packagephobia.com/badge?p=nrm-lite)](https://packagephobia.com/result?p=nrm-lite) -Fast and lightweight NpmRegistryManager, for the minimalists. +Fast and lightweight Npm [Registry](https://docs.npmjs.com/cli/using-npm/registry) Manager, for the minimalists. Like [dnrm](https://github.com/markthree/dnrm), but in pure Node.js. [![asciicast](https://asciinema.org/a/646571.svg)](https://asciinema.org/a/646571) diff --git a/cli.mjs b/cli.mjs index 81da6c1..78b04fd 100644 --- a/cli.mjs +++ b/cli.mjs @@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url' import { dirname } from 'node:path' import { getRegistry, setRegistry, getConfigPath } from './config.mjs' import { REGISTRIES, speedTest } from './registry.mjs' -import c, { printRegistries } from './tty.mjs' +import c, { printRegistries } from './utils.mjs' // https://nodejs.org/api/util.html#utilparseargsconfig const { values, positionals } = parseArgs({ diff --git a/config.mjs b/config.mjs index edad94f..4f464d8 100644 --- a/config.mjs +++ b/config.mjs @@ -1,7 +1,11 @@ import * as fs from 'node:fs' import { writeFile } from 'node:fs/promises' import { homedir } from 'node:os' -import { REGISTRIES, findRegistryFromStream } from './registry.mjs' +import { + REGISTRIES, + getRegistryFromStream, + setRegistryFromStream, +} from './registry.mjs' import { isFile } from './utils.mjs' /** @@ -10,24 +14,13 @@ import { isFile } from './utils.mjs' */ export async function setRegistry(local, registryUrl) { const filePath = await getConfigPath(local) - - const fileStream = fs.createReadStream(filePath) - const { registry, lines, registryLineNumber } = - await findRegistryFromStream(fileStream) - const newRegistryLine = `registry=${registryUrl}` - - if (!registry) { - lines.push(newRegistryLine) - } else if (registry === registryUrl) { - // same, do nothing - return - } else { - // number-1 is index - lines[registryLineNumber - 1] = newRegistryLine + try { + const fileStream = fs.createReadStream(filePath) + const result = await setRegistryFromStream(fileStream, registryUrl) + return writeFile(filePath, result) + } catch { + return writeFile(filePath, `registry=${registryUrl}`) } - - const result = lines.join('\n') - return writeFile(filePath, result) } /** @@ -35,9 +28,13 @@ export async function setRegistry(local, registryUrl) { */ export async function getRegistry(local) { const filePath = await getConfigPath(local) - const fileStream = fs.createReadStream(filePath) - const { registry } = await findRegistryFromStream(fileStream) - return registry || REGISTRIES['npm'] + try { + const fileStream = fs.createReadStream(filePath) // the file may not exists + return (await getRegistryFromStream(fileStream)) || REGISTRIES['npm'] + } catch { + // when rc file not found, fallback registry to default + return REGISTRIES['npm'] + } } /** diff --git a/index.mjs b/index.mjs index d36d219..cb000f0 100644 --- a/index.mjs +++ b/index.mjs @@ -1,2 +1,2 @@ -export * from './config.mjs' -export * from './registry.mjs' +export { getRegistry, setRegistry, getConfigPath } from './config.mjs' +export { speedTest } from './registry.mjs' diff --git a/package.json b/package.json index f5a5519..bdb2352 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nrm-lite", - "version": "0.1.3", + "version": "0.2.0", "type": "module", "description": "Fast and lightweight NpmRegistryManager, for the minimalists.", "main": "index.mjs", @@ -22,8 +22,8 @@ "nrml": "cli.mjs" }, "devDependencies": { - "@types/node": "^20.11.26", - "typescript": "^5.4.2" + "@types/node": "^20.14.2", + "typescript": "^5.4.5" }, "publishConfig": { "registry": "https://registry.npmjs.org" diff --git a/registry.mjs b/registry.mjs index 217ab19..ee8ad4b 100644 --- a/registry.mjs +++ b/registry.mjs @@ -13,39 +13,49 @@ export const REGISTRIES = { } /** - * - * Note that `registryLineNumber` is index + 1 - * + * Returns undefined when line does not contain registry, or registry as string + * @param {string} line + */ +function checkLine(line) { + let currLine = line.trim() + const keyName = 'registry' + if (!currLine.startsWith(keyName)) return + currLine = currLine.slice(keyName.length).trimStart() + if (!currLine.startsWith('=')) return + return currLine.slice(1).trimStart() +} + +/** * @param {NodeJS.ReadableStream} stream - * @returns {Promise<{registry:string,lines:string[],registryLineNumber:number} - * |{registry:null,lines:string[],registryLineNumber:null}>} + * @returns {Promise} * @see https://docs.npmjs.com/cli/configuring-npm/npmrc - * - * @example - * const fileStream = fs.createReadStream(path) - * findRegistryFromStream(fileStream) */ -export async function findRegistryFromStream(stream) { +export async function getRegistryFromStream(stream) { const rl = readline.createInterface(stream) - /** - * @type {string[]} - */ - const lines = [] - - for await (const entireLine of rl) { - lines.push(entireLine) - - let currLine = entireLine.trim() - const keyName = 'registry' - if (!currLine.startsWith(keyName)) continue - currLine = currLine.slice(keyName.length).trimStart() - if (!currLine.startsWith('=')) continue - currLine = currLine.slice(1).trimStart() + for await (const line of rl) { + const r = checkLine(line) + if (r) return r + } +} - // now that current line is the registry url - return { registry: currLine, lines, registryLineNumber: lines.length } +/** + * Returns the proceed rc content + * @param {NodeJS.ReadableStream} stream + * @param {string} registryUrl + * @returns {Promise} + */ +export async function setRegistryFromStream(stream, registryUrl) { + const rl = readline.createInterface(stream) + const lines = [] + for await (const line of rl) { + const r = checkLine(line) + if (r) { + lines.push(`registry=${registryUrl}`) + } else { + lines.push(line) + } } - return { registry: null, registryLineNumber: null, lines } + return lines.join('\n') } /** diff --git a/registry.test.mjs b/registry.test.mjs index 3faf3e7..a6dc345 100644 --- a/registry.test.mjs +++ b/registry.test.mjs @@ -1,31 +1,28 @@ import { test } from 'node:test' import * as assert from 'node:assert' import { Readable } from 'node:stream' -import { findRegistryFromStream } from './registry.mjs' +import { getRegistryFromStream } from './registry.mjs' -test('test findRegistryFromStream()', async () => { - assert.deepStrictEqual(await findRegistryFromStream(Readable.from('')), { - lines: [], - registry: null, - registryLineNumber: null, - }) +test('test getRegistryFromStream()', async () => { + assert.deepStrictEqual( + await getRegistryFromStream(Readable.from('')), + undefined + ) - const text2 = `registry=https://registry.npmmirror.com/` + assert.deepStrictEqual( + await getRegistryFromStream( + Readable.from(`registry=https://registry.npmmirror.com/`) + ), + 'https://registry.npmmirror.com/' + ) - assert.deepStrictEqual(await findRegistryFromStream(Readable.from(text2)), { - registry: 'https://registry.npmmirror.com/', - registryLineNumber: 1, - lines: [text2], - }) - - const text3 = `//registry.npmjs.org/:_authToken=npm_123456 + assert.deepStrictEqual( + await getRegistryFromStream( + Readable.from(`//registry.npmjs.org/:_authToken=npm_123456 # strict-ssl=false -registry= https://registry.npmmirror.com/` - - assert.deepStrictEqual(await findRegistryFromStream(Readable.from(text3)), { - registry: 'https://registry.npmmirror.com/', - registryLineNumber: 4, - lines: text3.split(/\n/g), - }) +registry= https://registry.npmmirror.com/`) + ), + 'https://registry.npmmirror.com/' + ) }) diff --git a/tty.mjs b/tty.mjs deleted file mode 100644 index 1713243..0000000 --- a/tty.mjs +++ /dev/null @@ -1,82 +0,0 @@ -import { REGISTRIES } from './registry.mjs' - -/** - * @param {string} str - * @param {number} begin - * @param {number} end - */ -const color = (str, begin, end) => `\u001b[${begin}m${str}\u001b[${end}m` - -const c = { - /** @param {string} str*/ - blue: (str) => color(str, 34, 39), - /** @param {string} str*/ - red: (str) => color(str, 31, 39), - /** @param {string} str*/ - green: (str) => color(str, 32, 39), - /** @param {string} str*/ - yellow: (str) => color(str, 33, 39), - /** @param {string} str*/ - magenta: (str) => color(str, 35, 39), - /** @param {string} str*/ - cyan: (str) => color(str, 36, 39), - /** @param {string} str*/ - gray: (str) => color(str, 90, 39), - - /** @param {string} str*/ - bold: (str) => color(str, 1, 22), - /** @param {string} str*/ - italic: (str) => color(str, 3, 23), -} - -export default c - -/** - * @param {string} currentRegistryUrl - * @param {Array<{name:string,url:string,timeSpent?:number|null}>} registriesInfo - * @param {number=} timeoutLimit - milliseconds - */ -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 - - for (let { name, url, timeSpent } of registriesInfo) { - if (timeoutLimit) { - // lazy compute - if (!maxUrlLength) - maxUrlLength = Math.max( - ...registriesInfo.map(({ url }) => url.length) - ) - } - - let row = `${name.padEnd(maxNameLength)} → ${ - maxUrlLength ? url.padEnd(maxUrlLength) : url - }` - if (url === currentRegistryUrl) row = c.blue(row) - - if (timeoutLimit) { - if (!timeSpent) { - row += c.red(` (Error)`) - } else 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) - } -} diff --git a/utils.mjs b/utils.mjs index f402982..cc455e6 100644 --- a/utils.mjs +++ b/utils.mjs @@ -1,4 +1,6 @@ import { stat } from 'node:fs/promises' +import { REGISTRIES } from './registry.mjs' + /** * @param {string} filePath * @noexcept @@ -7,3 +9,87 @@ export const isFile = async (filePath) => await stat(filePath) .then((stat) => stat.isFile()) .catch((_) => false) + +/** + * @param {string} str + * @param {number} begin + * @param {number} end + */ +const color = (str, begin, end) => `\u001b[${begin}m${str}\u001b[${end}m` + +/** + * Check out https://github.com/YieldRay/terminal-sequences/blob/main/sgr/style.ts + */ +const c = { + /** @param {string} str*/ + blue: (str) => color(str, 34, 39), + /** @param {string} str*/ + red: (str) => color(str, 31, 39), + /** @param {string} str*/ + green: (str) => color(str, 32, 39), + /** @param {string} str*/ + yellow: (str) => color(str, 33, 39), + /** @param {string} str*/ + magenta: (str) => color(str, 35, 39), + /** @param {string} str*/ + cyan: (str) => color(str, 36, 39), + /** @param {string} str*/ + gray: (str) => color(str, 90, 39), + + /** @param {string} str*/ + bold: (str) => color(str, 1, 22), + /** @param {string} str*/ + italic: (str) => color(str, 3, 23), +} + +export default c + +/** + * @param {string} currentRegistryUrl + * @param {Array<{name:string,url:string,timeSpent?:number|null}>} registriesInfo + * @param {number=} timeoutLimit - milliseconds + */ +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 + + for (let { name, url, timeSpent } of registriesInfo) { + if (timeoutLimit) { + // lazy compute + if (!maxUrlLength) + maxUrlLength = Math.max( + ...registriesInfo.map(({ url }) => url.length) + ) + } + + let row = `${name.padEnd(maxNameLength)} → ${ + maxUrlLength ? url.padEnd(maxUrlLength) : url + }` + if (url === currentRegistryUrl) row = c.blue(row) + + if (timeoutLimit) { + if (!timeSpent) { + row += c.red(` (Error)`) + } else 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) + } +}