nrm-lite
Version:
Fast and lightweight NpmRegistryManager, for the minimalists.
260 lines (239 loc) • 6.42 kB
JavaScript
import process from 'node:process'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import { platform } from 'node:os'
import {
getRegistry,
setRegistry,
getConfigPath,
getAllRegistries,
} from './config.mjs'
import { speedTest } from './registry.mjs'
import {
parseArgs,
styleText,
execFileAsync,
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
*/
async function help(v) {
let pkg = { name: 'nrm-lite', version: '.unknown' }
try {
const require = createRequire(import.meta.url)
const pkgPath = join(
dirname(fileURLToPath(import.meta.url)),
'package.json',
)
pkg = require(pkgPath)
} catch {}
if (v) {
console.log('v' + pkg.version)
process.exit(1)
}
console.log(`${styleText('green', pkg.name)} v${pkg.version}
${styleText('bold', 'Usage:')}
nrml ls List registries
nrml use ${styleText('grey', '<name>')} Use registry
nrml test ${styleText(
'grey',
'[<timeout>]',
)} Test registry speed, optional timeout in second (default: 2)
nrml add ${styleText('grey', '<name>')} ${styleText(
'grey',
'<url>',
)} Add custom registry
nrml del ${styleText('grey', '<name>')} Delete custom registry
nrml rc Open .npmrc file
nrml help Show this help
${styleText('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 ${styleText('grey', `[${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 ${styleText('magenta', name)} already exists!`,
)
process.exit(-1)
} else {
await appendNrmrc(name, url)
console.log(
`Registry ${styleText(
'magenta',
name,
)} has been added, run ${styleText(
'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()
nrmrc.delete(name)
await writeNrmrc(nrmrc)
console.log(`Registry ${styleText('magenta', name)} has been deleted.`)
}
async function rc() {
const filePath = await getConfigPath(local)
try {
// try vscode first
await execFileAsync('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) await execFileAsync(cmd, [filePath])
else shouldRunEditor = true
} catch {
shouldRunEditor = true
}
try {
if (shouldRunEditor) await execFileAsync('editor', [filePath])
} catch {
console.log(
`Failed to open file, please open ${styleText(
'gray',
filePath,
)} manually.`,
)
process.exit(-1)
}
}
}