Metadata-Version: 2.4
Name: mirr
Version: 0.2.0
Summary: An nrm-shaped mirror configuration tool for uv, pip, npm, and conda
Requires-Python: >=3.9
Requires-Dist: click>=8.1.8
Requires-Dist: tomlkit>=0.13.2
Description-Content-Type: text/markdown

# `mirr`or

简体中文 | [English](README.en.md)

`mirr` 是一个仿照 nrm 风格设计的常用包管理器镜像配置工具,目前支持
[uv](https://docs.astral.sh/uv/)、pip、npm 和 conda(只读)。
它保留了熟悉的命令形式,同时尊重每个工具在用户级、项目级、环境变量及多索引配置上的语义。

## 安装

mirr 需要 Python 3.9 及以上版本。无需安装,直接用 [`uvx`](https://docs.astral.sh/uv/guides/tools/) 运行:

```console
uvx mirr --version
```

或者安装为常驻命令:

```console
uv tool install mirr
mirr --version
```

从源码安装(用于开发):

```console
uv tool install .
mirr --version
```

## 从 nrm 迁移

核心操作使用相同的命令名:

| nrm | mirr | 作用 |
| --- | --- | --- |
| `nrm ls` | `mirr ls` | 列出所有索引,并标记当前生效的索引 |
| `nrm current -u` | `mirr current -u` | 显示当前生效的索引 URL |
| `nrm use <name>` | `mirr use <name>` | 修改用户级默认索引 |
| `nrm use <name> --local` | `mirr use <name> --local` | 修改项目级默认索引 |
| `nrm add <name> <url> [home]` | `mirr add <name> <url> [home]` | 添加自定义索引 |
| `nrm del <name>` | `mirr del <name>` | 删除自定义索引 |
| `nrm rename <name> <new-name>` | `mirr rename <name> <new-name>` | 重命名自定义索引 |
| `nrm home <name> [browser]` | `mirr home <name> [browser]` | 打开索引主页 |
| `nrm test [name]` | `mirr test [name]` | 测量端点延迟 |

认证、发布、npm scope,以及针对特定包的 uv source 绑定,目前初始版本的 mirr 尚未涉及。

## 多工具支持

除了不带前缀的历史命令(始终等价于 `mirr uv <verb>`),mirr 也支持显式的
`mirr <tool> <verb>` 形式:

```console
mirr uv use tsinghua      # 等价于 mirr use tsinghua
mirr pip use tsinghua
mirr pip use tsinghua --local   # 写入当前激活 virtualenv 的 pip.conf
mirr npm use npmmirror
mirr npm use npmmirror --local  # 写入当前目录的 .npmrc
mirr conda ls
mirr conda test
```

| 工具 | 支持的命令 | `--local` 作用域 | 说明 |
| --- | --- | --- | --- |
| `uv` | 全部 | 项目 `uv.toml`/`pyproject.toml` | 与历史行为完全一致 |
| `pip` | 全部 | 当前激活的 virtualenv | pip 没有项目级配置;未激活 venv 时 `--local` 会报错 |
| `npm` | 全部 | 当前目录的 `.npmrc` | scope 覆盖条目(如 `@corp:registry=`)始终保留 |
| `conda` | 仅 `ls`、`test` | 不适用 | channel 是有序列表而非单一默认值,写入语义留待后续版本;`use`/`add`/`del`/`rename`/`current` 会明确报告暂不支持 |

`mirr <tool> --help` 只会列出该工具当前实际支持的命令。

## 快速开始

```console
$ mirr test
[uv] * pypi -------- 187 ms
[uv]   tsinghua ---- 43 ms
[uv]   aliyun ------ 96 ms
[uv]   tencent ----- 121 ms
[uv]   huawei ------ 88 ms
[uv]   ustc -------- 104 ms

$ mirr use tsinghua
[uv] SUCCESS The index has been changed to 'tsinghua'.

$ mirr current
[uv] You are using tsinghua index.
```

每条输出都带着 `[tool]` 前缀,标明这次改动的是哪个工具的镜像配置——
无前缀命令和 `mirr uv <verb>` 一样,都会显示 `[uv]`。

在交互式终端中不带名称执行 `mirr use` 可以从目录列表中选择;
在脚本中请始终显式指定名称。

## 命令

以下命令以不带前缀的历史形式(操作 uv)描述;pip 和 npm 的行为、参数与输出格式相同,
只需把命令换成 `mirr pip <verb>` 或 `mirr npm <verb>`,唯一的区别是各自的配置文件位置、
环境变量名称,以及 `--local` 的作用域(见上表)。

### `mirr ls`

列出内置和自定义的索引条目。`*` 标记的是当前目录下实际生效的索引,而不仅仅是上一次用户级的选择。

### `mirr current`

沿用 nrm 熟悉的语句式输出风格。已知有效 URL 对应的目录名称时会直接显示该名称,
`--show-url` 会改为显示 URL,若当前 URL 不在目录中,则会附带提示所需的 `mirr add` 命令。

```console
mirr current --show-url
mirr current -u
mirr current --verbose
```

详细模式(verbose)会附加显示来源以及相应的配置文件路径(如适用)。

### `mirr use [name]`

修改 uv 的用户级默认索引(写入结构化的 `[[index]] default = true` 条目),同时保留其他
不相关的设置及已命名的附加索引。

```console
mirr use pypi
mirr use tsinghua
```

如果项目级配置或 `UV_DEFAULT_INDEX` 环境变量仍然覆盖了新的用户级设置,
mirr 依然会写入所请求的用户配置,并打印警告说明当前生效的覆盖来源。

### `mirr use [name] --local`

仅修改当前项目:

1. 优先使用最近一层已存在的 `uv.toml`。
2. 否则更新最近一层 `pyproject.toml` 中的 `[tool.uv]`。
3. 如果都不存在,则在用户确认后在当前目录创建 `uv.toml`。

在非交互式脚本中允许创建新的本地 `uv.toml` 时,使用 `--yes`:

```console
mirr use aliyun --local --yes
```

当 `pyproject.toml` 存在时,mirr 不会额外创建同级的 `uv.toml`,
因为这样会导致 uv 忽略该文件中 `[tool.uv]` 的设置。

### 自定义目录条目

```console
mirr add company https://packages.example.com/simple https://packages.example.com
mirr rename company internal
mirr del internal
```

内置条目无法被重命名或删除。删除一个正在生效的自定义条目前,必须先切换到其他条目。

### 主页与可达性

```console
mirr home pypi
mirr home pypi firefox
mirr test pypi
mirr test
mirr test --timeout 10
```

`mirr test` 会对每个 Simple Repository 端点下的 `pip/` 项目页面发起一次轻量级、
经过 TLS 校验的 `HEAD` 请求,绝不会下载根索引。如果服务器不支持 `HEAD`,
mirr 会改为发起请求并只读取最多一个字节。批量测试所有条目时采用有限并发,
并按目录顺序打印结果。与 nrm 一样,`*` 标记当前生效的索引,各列对齐显示,
最快的成功结果会被高亮。这是一次可达性与延迟检测,而非包下载吞吐量基准测试。

## 配置优先级

`mirr current` 按以下顺序评估持久化配置与环境变量:

1. `UV_DEFAULT_INDEX`,以及仍受支持的旧版别名 `UV_INDEX_URL`
2. 项目级 `uv.toml` 或 `pyproject.toml` 中的 `[tool.uv]`
3. 用户级 `uv.toml`
4. 系统级 `uv.toml`
5. uv 隐式的默认值 `https://pypi.org/simple`

传递给后续 uv 调用的命令行选项是不可预测的,因此不会被 `mirr current` 纳入考虑。

用户级 uv 配置遵循 uv 的平台约定,包括 Linux 和 macOS 上支持 XDG 的
`~/.config/uv/uv.toml`,以及 Windows 上的 `%APPDATA%\uv\uv.toml`。
mirr 的自定义条目则使用对应平台上 mirr 自身的用户配置目录。

`mirr pip current` 和 `mirr npm current` 遵循同样的"环境变量 > 就近作用域 > 用户 >
系统/全局"结构,只是变量名称和文件不同:

| | 环境变量 | 就近作用域 | 用户级文件 |
| --- | --- | --- | --- |
| pip | `PIP_INDEX_URL` | 当前激活的 virtualenv (`$VIRTUAL_ENV/pip.conf`) | `pip.conf`(平台相关路径) |
| npm | `npm_config_registry` | 当前目录的 `.npmrc` | `~/.npmrc` |

每个工具的自定义目录各自独立存放(`~/.config/mirr/{uv,pip,npm,conda}.toml`,历史的
`config.toml` 原样保留作为 uv 的目录文件,无需迁移)。

## 冲突恢复

mirr 写入的是 uv 推荐的结构化形式(`--default-index`/`[[index]] default = true`),
而不是被 uv 官方文档标记为 legacy 的标量 `index-url`(`default-index` 本身则从来都
不是合法的配置文件字段,只是 `--default-index` 命令行参数与 `UV_DEFAULT_INDEX`
环境变量的名称,写入它会导致 uv 解析配置时报错)。若切换前已经存在遗留的 `index-url`
标量,会在写入新的结构化默认值时一并删除,避免同一份配置里出现两个相互冲突的默认索引。

在用户级 `uv.toml` 中,mirr 会给它写入的条目带上目录名称(如 `name = "tsinghua"`),
下次 `mirr use <name>` 时按名原地更新,不会不断追加新条目:

```toml
[[index]]
name = "tsinghua"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true
```

`pyproject.toml` 通常由团队共享、经过审查,因此 mirr 在其中写入的默认值**不带名字**
(`[[tool.uv.index]] url = "..." default = true`),并且从不自动修改或重命名一个已经
带 `name` 的默认值——那属于冲突,需要人工处理。无论哪种文件,只要现有的默认值除了
`name`、`url`、`default` 之外还带有其他字段(`explicit`、`authenticate` 等未知语义)、
或者存在多个 `default = true` 条目、或者切换后会与另一个已命名条目重名,mirr 都会拒绝
自动处理,并保持原文件不变,把冲突留给人工解决。当解析、校验或原子替换失败时,
mirr 也会拒绝处理格式错误的 TOML,并保持原文件不变。

## 安全边界

- 拒绝包含内嵌用户名、密码或令牌的目录 URL。
- mirr 不维护凭证存储,也不会在错误信息中打印 URL 中的凭证。
- 私有索引的凭证请使用 uv 自身支持的认证机制配置。
- `mirr test` 期间始终保持 TLS 校验开启。
- 传入浏览器参数时,直接以参数向量启动进程,mirr 不会构造 shell 命令。

## 开发

```console
uv sync --locked
uv run pytest
uv run ruff check .
uv build
```
