nrm-lite
Version:
Fast and lightweight NpmRegistryManager, for the minimalists.
238 lines (217 loc) • 6.11 kB
JavaScript
import process from 'node:process'
import { parseArgs } from 'node:util'
import { execSync } from 'node:child_process'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'
import { platform } from 'node:os'
import {
getRegistry,
setRegistry,
getConfigPath,
getAllRegistries,
} from './config.mjs'
import { speedTest } from './registry.mjs'
import c, { printRegistries } from './utils.mjs'
import { appendNrmrc, readNrmrc, writeNrmrc } from './nrmrc.mjs'
// https://nodejs.org/api/util.html#utilparseargsconfig
const { values, positionals } = parseArgs({
options: {
help: {
type: 'boolean',
multiple: false,
short: 'h',
default: false,
},
local: {
type: 'boolean',
short: 'l',
default: false,
},
version: {
type: 'boolean',
short: 'v',
default: false,
},
},
strict: true,
allowPositionals: true,
})
if (values.help) {
help()
}
const command = positionals[0] || ''
const { local } = values
switch (command) {
case 'h':
case 'help':
help()
break
// @ts-ignore intentional fallthrough here
case '':
if (values.version) help(true)
case 'ls':
ls()
break
case 'test': {
const timeout = positionals[1] || '2'
test(Number.parseFloat(timeout) * 1000)
break
}
case 'add': {
const name = positionals[1]
const url = positionals[2]
add(name, url)
break
}
case 'del': {
const name = positionals[1]
del(name)
break
}
case 'rc':
rc()
break
case 'use': {
const name = positionals[1]
use(name)
break
}
default:
console.log(`Unknown command '${command}'\n`)
process.exit(1)
}
/**
* @param {boolean=} v - only print version number
*/
function help(v) {
// assert json will cause ExperimentalWarning. so we use this instead
// import pkg from './package.json' assert { type: 'json' }
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const pkg = JSON.parse(readFileSync(`${__dirname}/package.json`, 'utf-8'))
if (v) {
console.log('v' + pkg.version)
process.exit(1)
}
console.log(`${c.green(pkg.name)} v${pkg.version}
${c.bold('Usage:')}
nrml ls List registries
nrml use ${c.gray('<name>')} Use registry
nrml test ${c.gray(
'[<timeout>]',
)} Test registry speed, optional timeout in second (default: 2)
nrml add ${c.gray('<name>')} ${c.gray('<url>')} Add custom registry
nrml del ${c.gray('<name>')} Delete custom registry
nrml rc Open .npmrc file
nrml help Show this help
${c.bold('Global Options:')}
--local Use local .npmrc file, rather than the global one (default: false)`)
process.exit(1)
}
async function ls() {
const currentRegistry = await getRegistry(local)
await printRegistries(currentRegistry)
}
/**
* @param {string} name
*/
async function use(name) {
if (!name) {
console.log(`Please provide a name!`)
process.exit(-1)
}
const registries = await getAllRegistries()
const names = Array.from(registries.keys())
if (!names.includes(name)) {
console.log(`'${name}' is not in ${c.gray(`[${names.join('|')}]`)}`)
process.exit(-1)
}
/** @type {*} */
const registryUrl = registries.get(name)
await setRegistry(local, registryUrl)
await printRegistries(registryUrl)
}
/**
* @param {number} timeoutLimit
*/
async function test(timeoutLimit) {
const registries = await getAllRegistries()
const info = await Promise.all(
Array.from(registries.entries()).map(async ([name, url]) => ({
name,
url,
timeSpent: await speedTest(url, timeoutLimit),
})),
)
const currentRegistry = await getRegistry(local)
await printRegistries(currentRegistry, info, timeoutLimit)
process.exit(0)
}
/**
* @param {string} name
* @param {string} url
*/
async function add(name, url) {
if (!name) {
console.log(`Please provide a name!`)
process.exit(-1)
}
if (!url) {
console.log(`Please provide an url!`)
process.exit(-1)
}
const registries = await getAllRegistries()
const names = Array.from(registries.keys())
if (names.includes(name)) {
console.log(`Registry name ${c.magenta(name)} already exists!`)
process.exit(-1)
} else {
await appendNrmrc(name, url)
console.log(
`Registry ${c.magenta(name)} has been added, run ${c.green(
`nrml use ${name}`,
)} to use.`,
)
}
}
/**
* @param {string} name
*/
async function del(name) {
if (!name) {
console.log(`Please provide a name!`)
process.exit(-1)
}
const nrmrc = await readNrmrc().catch(() => new Map())
nrmrc.delete(name)
await writeNrmrc(nrmrc)
console.log(`Registry ${c.magenta(name)} has been deleted.`)
}
async function rc() {
const filePath = await getConfigPath(local)
try {
// try vscode first
execSync(`code ${filePath}`)
} catch {
/** @type {Record<string, string>} */
const map = { win32: 'start', darwin: 'open', linux: 'xdg-open' }
const cmd = map[platform()]
let shouldRunEditor = false
try {
if (cmd) execSync(`${cmd} "${filePath}"`)
else shouldRunEditor = true
} catch {
shouldRunEditor = true
}
try {
if (shouldRunEditor) execSync(`editor "${filePath}"`)
} catch {
console.log(
`Failed to open file, please open ${c.gray(filePath)} manually.`,
)
process.exit(-1)
}
}
}