prm-cli
Version:
Registry Manager for Package Management Tools
277 lines (242 loc) • 8.63 kB
text/typescript
const chalk = require('chalk');
const request = require('request');
const fs = require('fs');
const path = require('path');
const registryList = require('./registryList').default;
// const spawn = require('child_process').spawn;
const spawn = require('cross-spawn'); // 兼容windows
// 类型定义
interface RegistryItem {
name: string;
registry: string;
home: string;
}
// 获取配置文件路径
function getConfigPath(): string {
const userHome = require('os').homedir();
return path.join(userHome, '.prm-config.json');
}
// 加载用户自定义配置
function loadUserConfig(): RegistryItem[] {
const configPath = getConfigPath();
try {
if (fs.existsSync(configPath)) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
return config.customRegistries || [];
}
} catch (error) {
console.warn(chalk.yellow('读取用户配置失败,将使用默认配置'));
}
return [];
}
// 保存用户自定义配置
function saveUserConfig(customRegistries: RegistryItem[]): void {
const configPath = getConfigPath();
const config = { customRegistries };
try {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
} catch (error) {
console.error(chalk.red('保存配置失败:'), error);
}
}
// 获取完整的注册源列表(默认 + 自定义)
function getAllRegistries(): RegistryItem[] {
const userRegistries = loadUserConfig();
return [...registryList, ...userRegistries];
}
function getRegistry(name: string): RegistryItem | undefined {
const allRegistries = getAllRegistries();
return allRegistries.find((item: RegistryItem) => item.name === name);
}
export function onTest(name: string) {
const registry = getRegistry(name);
// 需要测试的 registry 列表
const registries = registry ? [registry] : getAllRegistries();
// 遍历 registryList 测试每个 registry 的响应时间
registries.forEach((item: RegistryItem) => {
const { registry, home, name } = item;
const start = Date.now();
request(
{
url: registry,
timeout: 5000,
},
(error: Error, response: any, body: any) => {
const end = Date.now();
const success = !error && response.statusCode === 200;
const time = end - start;
const color = success ? 'green' : 'red';
const symbol = success ? '🟢' : '💩';
console.log(
chalk[color](
`${symbol} ${name.padEnd(12, ' ')} (${(time + 'ms').padEnd(6, ' ')}) - ${error || response.statusCode}`
)
);
}
);
});
}
// 切换源
export function onUse(name: string) {
const registry = getRegistry(name);
if (registry === undefined) {
return console.error(chalk.red(`${name} does not exist!`));
} else {
// 更改npm的源
const npm = spawn('npm', ['config', 'set', 'registry', registry.registry, '--no-workspaces']);
// 失败
npm.stderr.on('data', (data: Buffer) => {
console.error(chalk.red(data.toString()));
});
// 成功
npm.on('close', (code: number) => {
if (code === 0) {
const npm = spawn('npm', ['config', 'get', 'registry', '--no-workspaces']);
npm.stdout.on('data', (data: string) => {
const current = data.toString().trim();
console.log(chalk.green(`\n🎉🎉🎉 Succeed!`), chalk.blue(`\n------ ${registry.name} (${current}) \n`));
});
} else {
console.log(chalk.red('npm config set 命令执行失败'));
}
});
}
}
// 获取列表
export function onList() {
const npm = spawn('npm', ['config', 'get', 'registry', '--no-workspaces']);
npm.stdout.on('data', (data: string) => {
console.log('\n');
const current = data.toString().trim();
const allRegistries = getAllRegistries();
// 显示默认源
console.log(chalk.gray('📦 默认镜像源列表:'));
registryList.forEach((item: RegistryItem) => {
const str = `${(item.name + ' ').padEnd(14, ' ')} ${item.home.padEnd(32, ' ')} ${item.registry}`;
console.log(item.registry === current ? chalk.blue(`🚀 ${str}`) : ` ${str}`);
});
// 显示自定义源
const userRegistries = loadUserConfig();
if (userRegistries.length > 0) {
console.log(chalk.gray('\n🔧 自定义镜像源列表:'));
userRegistries.forEach((item: RegistryItem) => {
const str = `${(item.name + ' ').padEnd(14, ' ')} ${item.home.padEnd(32, ' ')} ${item.registry}`;
console.log(item.registry === current ? chalk.blue(`🚀 ${str}`) : ` ${str}`);
});
}
// 如果当前源不在源列表中,则显示当前源
if (!allRegistries.some((item: RegistryItem) => item.registry === current)) {
console.log(chalk.gray('\n💡 当前源:'));
const str = `${('custom' + ' ').padEnd(14, ' ')} ${current}`;
console.log(chalk.blue(`🚀 ${str} (in your .npmrc)`));
}
console.log('\n');
});
}
// 添加源
export function onAdd(name?: string, registry?: string, home?: string) {
const readline = require('readline');
if (name && registry) {
// 直接添加模式(命令行参数)
addRegistry(name, registry, home || '');
return;
}
// 交互式添加模式
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log(chalk.blue('\n📝 添加自定义镜像源\n'));
rl.question(chalk.green('请输入源名称: '), (inputName: string) => {
if (!inputName.trim()) {
console.log(chalk.red('❌ 源名称不能为空'));
rl.close();
return;
}
// 检查是否已存在
const existingRegistry = getRegistry(inputName.trim());
if (existingRegistry) {
console.log(chalk.red(`❌ 源名称 "${inputName.trim()}" 已存在`));
rl.close();
return;
}
rl.question(chalk.green('请输入镜像源地址: '), (inputRegistry: string) => {
if (!inputRegistry.trim()) {
console.log(chalk.red('❌ 镜像源地址不能为空'));
rl.close();
return;
}
// 验证 URL 格式
try {
new URL(inputRegistry.trim());
} catch (error) {
console.log(chalk.red('❌ 镜像源地址格式不正确'));
rl.close();
return;
}
rl.question(chalk.green('请输入主页地址 (可选): '), (inputHome: string) => {
addRegistry(inputName.trim(), inputRegistry.trim(), inputHome.trim() || inputRegistry.trim());
rl.close();
});
});
});
}
// 添加注册源的核心逻辑
function addRegistry(name: string, registry: string, home: string): void {
const userRegistries = loadUserConfig();
// 检查是否已存在
const existingRegistry = getRegistry(name);
if (existingRegistry) {
console.log(chalk.red(`❌ 源名称 "${name}" 已存在`));
return;
}
// 检查 registry URL 是否已存在
const allRegistries = getAllRegistries();
if (allRegistries.some(item => item.registry === registry)) {
console.log(chalk.red(`❌ 镜像源地址 "${registry}" 已存在`));
return;
}
const newRegistry: RegistryItem = {
name,
registry: registry.endsWith('/') ? registry : registry + '/',
home: home || registry
};
userRegistries.push(newRegistry);
saveUserConfig(userRegistries);
console.log(chalk.green('\n✅ 镜像源添加成功!'));
console.log(chalk.blue(` 名称: ${name}`));
console.log(chalk.blue(` 地址: ${newRegistry.registry}`));
console.log(chalk.blue(` 主页: ${newRegistry.home}`));
console.log(chalk.gray(`\n💡 使用 'prm use ${name}' 切换到新添加的源\n`));
}
// 删除自定义源
export function onDelete(name: string) {
if (!name) {
console.log(chalk.red('❌ 请指定要删除的源名称'));
return;
}
const userRegistries = loadUserConfig();
const index = userRegistries.findIndex(item => item.name === name);
if (index === -1) {
// 检查是否是默认源
const isDefaultRegistry = registryList.some((item: RegistryItem) => item.name === name);
if (isDefaultRegistry) {
console.log(chalk.red(`❌ 不能删除默认源 "${name}"`));
} else {
console.log(chalk.red(`❌ 源 "${name}" 不存在`));
}
return;
}
const deletedRegistry = userRegistries[index];
userRegistries.splice(index, 1);
saveUserConfig(userRegistries);
console.log(chalk.green(`✅ 成功删除源 "${deletedRegistry.name}"`));
console.log(chalk.gray(` 地址: ${deletedRegistry.registry}\n`));
}
exports.default = {
onUse,
onList,
onAdd,
onTest,
onDelete,
};