@boses/github-clone 1.1.5 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,127 +1,265 @@
1
1
  # GitHub-clone
2
2
 
3
- ![mlt](https://img.shields.io/badge/License-MIT-brightgreen) ![mlt](https://img.shields.io/badge/npm-1.1.1-brightgreen)
3
+ ![License](https://img.shields.io/badge/License-MIT-brightgreen)
4
4
 
5
- 为了解决国内 GitHub clone 速度慢较慢的工具
5
+ 解决国内 GitHub `clone` 速度慢、连接不稳定问题的 CLI 工具。
6
6
 
7
- > 注意以下文档书写形式遵循下述规范:
8
- >
9
- > `[]`代表这个字段必填,`<>`则为选填,而`name?:string`,表示这个参数为非必填为`string`类型。
7
+ 安装后使用 `g` 命令。配置镜像后,工具会**通过镜像加速下载**,克隆完成后**自动将 `origin` 恢复为 GitHub 官方地址**,便于后续 `push`。
10
8
 
11
- ## 执行流程
9
+ ## 快速开始
12
10
 
13
- - 首先将输入的 `github.com`替换成设置的镜像网站地址,默认为(github.com.cnpmjs.org)
14
- - 执行 `git clone [url]` 的操作
15
- - 拉取镜像完成之后,重写 git 的远程仓库推送地址,将镜像推送地址重置为 github 的镜像地址
11
+ ```sh
12
+ g mirror set kgithub # 配置镜像(首次使用建议执行)
13
+ g mirror test # 可选:探测镜像是否可用
14
+ g clone https://github.com/owner/repo
15
+ ```
16
+
17
+ 未配置镜像时,`g clone` 将直连 `github.com`。
18
+
19
+ ## 工作原理
20
+
21
+ ```text
22
+ 输入 URL →(若已配置镜像)替换为镜像域名 → git clone → git remote set-url origin(恢复官方地址)
23
+ ```
24
+
25
+ 1. 若已通过 `g mirror set` 配置镜像,将 URL 中的 `github.com` 替换为镜像域名
26
+ 2. 执行 `git clone`
27
+ 3. 使用镜像时,克隆完成后将 `origin` 改回 `https://github.com/<owner>/<repo>.git`
16
28
 
17
- ## CLI 使用方式
29
+ 镜像配置保存在 `~/.g.config`(纯文本,内容为镜像域名,如 `kgithub.com`)。
18
30
 
19
- ### 安装
31
+ ## 安装
20
32
 
21
33
  ```sh
22
- yarn global add @boses/github-clone
34
+ pnpm add -g @boses/github-clone
35
+ # 或
36
+ npm i -g @boses/github-clone
23
37
  ```
24
38
 
25
- 之后通过`g clone <path>`形式来使用,更多`API`和`clone`的使用方法可以调用`g ---help`查看。
39
+ ```sh
40
+ g mirror set kgithub
41
+ g clone https://github.com/bosens-China/github-clone
42
+ g --help
43
+ ```
26
44
 
27
- ### API
45
+ ## CLI 命令
28
46
 
29
- #### Clone
47
+ ### `g clone` — 克隆仓库
30
48
 
31
- **clone <path> [dir] <--branch [branchName]>**
49
+ ```sh
50
+ g clone <url> [dir] [options]
51
+ ```
32
52
 
33
- - `<path>`
53
+ | 参数 / 选项 | 说明 |
54
+ |-------------|------|
55
+ | `url` | GitHub 仓库地址(必填) |
56
+ | `dir` | 本地目录名;省略则使用仓库名 |
57
+ | `-b, --branch <name>` | 只检出指定分支(等价于 `git clone --branch`) |
58
+ | `--depth <n>` | 浅克隆:只拉取最近 `n` 次提交。`--depth 1` 表示只要最新版本,更快更省空间,但本地没有完整 Git 历史 |
59
+ | `--single-branch` | 只克隆单个分支,不下载其他远程分支。常与 `-b` 联用;单独使用时默认只拉远程默认分支(通常是 `main`) |
60
+ | `--no-mirror` | 忽略已配置的镜像,强制直连 `github.com` |
61
+ | `--verbose` | 克隆前输出详细计划:镜像/直连模式、实际克隆地址、origin 恢复地址、本地目录、Git 参数 |
34
62
 
35
- - type:`stirng`
36
- - require:`true`
63
+ **支持的 URL 格式:**
37
64
 
38
- 拉取 GitHub 仓库 对应的地址,可以拉取以下三种类型地址
65
+ | 类型 | 示例 |
66
+ |------|------|
67
+ | HTTPS | `https://github.com/owner/repo` |
68
+ | HTTPS + `.git` | `https://github.com/owner/repo.git` |
69
+ | SSH | `git@github.com:owner/repo` 或 `git@github.com:owner/repo.git` |
39
70
 
40
- | 类型 | 说明 |
41
- | ------------------------------------------------ | ----------------------------- |
42
- | https://github.com/bosens-China/github-clone | 默认浏览器导航栏的地址 |
43
- | https://github.com/bosens-China/github-clone.git | Github 右侧 Code HTTPS 的地址 |
44
- | git@github.com:bosens-China/breeze-clone.git | Github 右侧 Code SSH 的地址 |
71
+ **示例:**
45
72
 
46
- - `[dir]`
73
+ ```sh
74
+ # 配置镜像后克隆
75
+ g mirror set kgithub
76
+ g clone https://github.com/bosens-China/github-clone
47
77
 
48
- - type:`stirng`
49
- - require:`false`
78
+ # 指定目录与分支
79
+ g clone https://github.com/bosens-China/github-clone.git my-dir -b dev
50
80
 
51
- clone 到本地的目录名称
81
+ # 最小体积:只要 main 分支最近一次提交
82
+ g clone https://github.com/bosens-China/github-clone -b main --single-branch --depth 1
52
83
 
53
- - `--branch [branchName]`
84
+ # 已配置镜像但本次强制直连
85
+ g clone https://github.com/bosens-China/github-clone --no-mirror --verbose
54
86
 
55
- - type:`stirng`
56
- - require:`false`
87
+ # SSH 地址同样支持
88
+ g clone git@github.com:bosens-China/github-clone.git
89
+ ```
57
90
 
58
- 指定拉取的分支名称,可以以`--branch`长形式使用也可以以`-b`的短形式使用,例如:
91
+ **默认输出示例:**
59
92
 
60
- ```sh
61
- g clone https://github.com/bosens-China/github-clone.git -b dev
62
- ```
93
+ ```text
94
+ 使用镜像 kgithub.com
95
+ ... git clone 进度 ...
96
+ ✓ 克隆完成:repo
97
+ 已恢复 origin 为 GitHub 官方地址
98
+ ```
63
99
 
64
- #### set [url]
100
+ **`--verbose` 输出示例:**
65
101
 
66
- - `[url]`
102
+ ```text
103
+ 克隆计划
104
+ 模式 : 镜像(kgithub.com)
105
+ 克隆地址 : https://kgithub.com/owner/repo.git
106
+ 恢复 origin: https://github.com/owner/repo.git
107
+ 本地目录 : repo
108
+ Git 参数 : --branch dev --depth 1 --single-branch
67
109
 
68
- - type:`stirng`
69
- - require:`true`
110
+ ... git clone 进度 ...
111
+ 克隆完成:repo,分支 dev
112
+ ```
113
+
114
+ ### `g mirror` — 镜像管理
70
115
 
71
- 用于配置镜像网站
116
+ | 命令 | 说明 |
117
+ |------|------|
118
+ | `g mirror list`(`ls`) | 列出内置镜像预设,标注当前使用的镜像 |
119
+ | `g mirror set <host\|preset>` | 设置镜像域名或预设名(`kgithub` / `moeyy`) |
120
+ | `g mirror get` | 查看 `~/.g.config` 中的自定义镜像 |
121
+ | `g mirror test [host]` | 对镜像发起 HTTP HEAD 探测是否可达(超时 10 秒) |
122
+ | `g mirror unset` | 删除 `~/.g.config` |
72
123
 
73
- #### get
124
+ **内置预设:**
74
125
 
75
- 返回用户配置的`set [url]`地址,默认为`github.com.cnpmjs.org`
126
+ | 预设名 | 域名 | 说明 |
127
+ |--------|------|------|
128
+ | `kgithub` | `kgithub.com` | KGitHub 镜像(域名替换) |
129
+ | `moeyy` | `github.moeyy.xyz` | Moeyy 镜像(域名替换) |
76
130
 
77
- ## Node
131
+ **镜像与克隆的关系:**
78
132
 
79
- ### 安装
133
+ | 场景 | `g mirror get` | `g clone` 行为 |
134
+ |------|----------------|----------------|
135
+ | 未配置镜像 | 提示未配置 | 直连 GitHub |
136
+ | 已 `mirror set` | 显示配置的域名 | 使用配置的镜像 |
137
+ | 已 `mirror unset` | 提示未配置 | 直连 GitHub |
138
+ | 已配置镜像 + `--no-mirror` | — | 强制直连 GitHub |
139
+
140
+ ## 编程式 API
80
141
 
81
142
  ```sh
82
- yarn add @boses/github-clone
143
+ pnpm add @boses/github-clone
83
144
  ```
84
145
 
85
- ```js
86
- const clone = require('@boses/github-clone');
87
- // 也可以通过es模块引用
88
- // import clone from '@boses/github-clone/gitClone.esm'
89
- clone('https://github.com/SunshowerC/blog');
146
+ ```ts
147
+ import clone from '@boses/github-clone';
148
+
149
+ try {
150
+ clone('https://github.com/bosens-China/github-clone', {
151
+ mirrorHost: 'kgithub.com',
152
+ branch: 'main',
153
+ depth: 1,
154
+ singleBranch: true,
155
+ silence: true,
156
+ });
157
+ } catch (error) {
158
+ console.error(error);
159
+ }
90
160
  ```
91
161
 
92
- > clone 会以同步的形式运行,记得使用 `try` 包裹住可能的错误
162
+ ### `clone(url, options?)`
163
+
164
+ 同步 API,请用 `try/catch` 处理错误。不会读取 `~/.g.config`,镜像需通过 `mirrorHost` 显式传入。
165
+
166
+ | 选项 | 类型 | 说明 |
167
+ |------|------|------|
168
+ | `dirName` | `string` | 本地目录名 |
169
+ | `branch` | `string` | 分支名 |
170
+ | `mirrorHost` | `string` | 镜像域名(API 不会读取 `~/.g.config`,需显式传入) |
171
+ | `cwd` | `string` | 执行目录,默认 `process.cwd()` |
172
+ | `silence` | `boolean` | 静默模式,不继承 Git 输出 |
173
+ | `depth` | `number` | 浅克隆深度 |
174
+ | `singleBranch` | `boolean` | 仅克隆指定分支 |
175
+
176
+ ## 项目结构
177
+
178
+ ```text
179
+ src/
180
+ ├── index.ts # 库 API 入口
181
+ ├── constants.ts
182
+ ├── types.ts
183
+ ├── cli/
184
+ │ ├── index.ts # CLI 入口
185
+ │ └── commands/
186
+ │ ├── clone-command.ts
187
+ │ └── mirror-command.ts
188
+ ├── config/
189
+ │ └── mirror-store.ts
190
+ └── core/
191
+ ├── clone-repo.ts
192
+ ├── git-client.ts
193
+ └── github-url.ts
194
+
195
+ test/ # 测试目录(与 src 目录结构对应)
196
+ ```
93
197
 
94
- ### API
198
+ 命名约定:多词文件名使用 **kebab-case**;`index.ts` 作为模块入口。
95
199
 
96
- `clone: (url: string, options: Partial<Options>) => void`
200
+ ## 开发
97
201
 
98
- #### url
202
+ 要求:Node ≥ 22,pnpm,TypeScript strict。
203
+
204
+ ```sh
205
+ pnpm install
206
+ pnpm dev # 监听模式运行 CLI(src/cli/index.ts)
207
+ pnpm test # 运行测试
208
+ pnpm test:coverage # 测试 + 覆盖率
209
+ pnpm type-check # TypeScript 类型检查
210
+ pnpm lint # ESLint 检查
211
+ pnpm format # ESLint 自动修复(与 pre-commit 钩子一致)
212
+ pnpm build # Rolldown 打包 + 生成 .d.ts
213
+ ```
99
214
 
100
- - type:`string`
101
- - require: `true`
215
+ `pnpm install` 会通过 `prepare` 脚本自动安装 Husky 钩子。提交时 `pre-commit` 会对暂存的 `*.{ts,mjs}` 文件执行 `eslint --fix`。
102
216
 
103
- 拉取的 GitHub 仓库地址
217
+ ## 发布流程
104
218
 
105
- #### options
219
+ 本仓库通过 GitHub Actions + [release-please](https://github.com/googleapis/release-please) + npm Trusted Publishing 自动化发布,无需手动维护版本号、CHANGELOG,也无需 `NPM_TOKEN`。
220
+
221
+ **两条 workflow:**
222
+
223
+ | 文件 | 触发 | 作用 |
224
+ |------|------|------|
225
+ | `.github/workflows/ci.yml` | `pull_request` / `push` 到 `main` | lint + type-check + 测试 + 构建校验 |
226
+ | `.github/workflows/release.yml` | `push` 到 `main` | release-please 维护 release PR;该 PR 合并后自动 `npm publish` |
227
+
228
+ **贡献提交规范(Conventional Commits):**
229
+
230
+ ```text
231
+ feat: ... # 新功能(minor)
232
+ fix: ... # bug 修复(patch)
233
+ perf: ... # 性能优化(patch)
234
+ refactor: ... # 重构(patch)
235
+ docs: ... # 文档
236
+ test: ... # 测试
237
+ chore: ... # 杂项(不进 CHANGELOG)
238
+
239
+ # 破坏性变更(major)
240
+ feat!: ...
241
+ 或在 body 中写 BREAKING CHANGE: ...
242
+ ```
106
243
 
107
- | 名称 | 类型 | 是否必填 | 描述 |
108
- | ------------- | --------- | -------- | ------------------------------------------ |
109
- | dirName | `string` | `false` | 拉取的目录名称 |
110
- | branch | `string` | `false` | 拉取的分支名称 |
111
- | mirrorAddress | `string` | `false` | 镜像网站,如果你需要使用镜像可以填写此网站 |
112
- | silence | `boolean` | `false` | 是否静默模式执行 clone |
113
- | cwd | `string` | `false` | 执行 clone 所执行的目录路径 |
244
+ **发布步骤:**
114
245
 
115
- ## 其他
246
+ 1. 在 PR 中按上述格式写 commit
247
+ 2. PR 合并到 `main`
248
+ 3. release-please 自动在仓库里维护一个 `chore(main): release x.y.z` PR,里面包含 CHANGELOG 与版本号更新
249
+ 4. Review 并合并该 PR → 自动打 tag → 触发 `npm publish`(OIDC,无 token)
250
+ 5. 在 npmjs.com 用 2FA approve staged package → 上线
116
251
 
117
- 目前版本更新导致对`1.0.9`之前的`g get`不支持,请重新执行`g set [url]`的操作
252
+ ## 限制
118
253
 
119
- 如果发现错误或者需要有更好的建议欢迎在 [issues](https://github.com/bosens-China/github-clone) 中提出
254
+ - 仅支持 GitHub(`github.com`),不支持 GitLab / Gitee / GitHub Enterprise
255
+ - 镜像为**域名替换型**,不支持 `ghproxy.com/https://github.com/...` 前缀代理型
256
+ - 不支持 `git pull` / `git fetch` 加速
257
+ - `mirror test` 仅验证 HTTP 可达,不保证 `git clone` 一定成功
120
258
 
121
- ## 参考
259
+ ## 反馈
122
260
 
123
- - [知乎 git clone 一个 github 上的仓库,太慢,经常连接失败...](https://www.zhihu.com/question/27159393/answer/1117219745)
261
+ 如有问题或建议,欢迎在 [Issues](https://github.com/bosens-China/github-clone) 反馈。
124
262
 
125
263
  ## 协议
126
264
 
127
- [MIT License](/License)
265
+ [MIT License](/LICENSE)
@@ -0,0 +1,10 @@
1
+ export declare class ConfigStore {
2
+ private readonly configPath;
3
+ constructor(configPath?: string);
4
+ getMirrorHost(): string | undefined;
5
+ setMirrorHost(host: string): void;
6
+ unsetMirrorHost(): void;
7
+ getConfigPath(): string;
8
+ }
9
+ export declare const defaultConfigStore: ConfigStore;
10
+ //# sourceMappingURL=mirror-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror-store.d.ts","sourceRoot":"","sources":["../../src/config/mirror-store.ts"],"names":[],"mappings":"AAaA,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,SAAoB;IAE3D,aAAa,IAAI,MAAM,GAAG,SAAS;IAQnC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKjC,eAAe,IAAI,IAAI;IAMvB,aAAa,IAAI,MAAM;CAGxB;AAED,eAAO,MAAM,kBAAkB,aAAoB,CAAC"}
@@ -0,0 +1,10 @@
1
+ /** 域名替换型镜像:将 URL 中的 github.com 替换为镜像主机 */
2
+ export interface MirrorPreset {
3
+ name: string;
4
+ host: string;
5
+ description: string;
6
+ }
7
+ export declare const MIRROR_PRESETS: readonly MirrorPreset[];
8
+ /** 用于 mirror test 的探测仓库路径(GitHub 官方示例仓库,长期稳定) */
9
+ export declare const MIRROR_PROBE_REPO = "octocat/Hello-World";
10
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAWxC,CAAC;AAEX,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,wBAAwB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { CloneOptions } from '../types';
2
+ export declare function cloneGithubRepo(url: string, options?: CloneOptions): void;
3
+ export default cloneGithubRepo;
4
+ //# sourceMappingURL=clone-repo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clone-repo.d.ts","sourceRoot":"","sources":["../../src/core/clone-repo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAI7C,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAkCzE;AAED,eAAe,eAAe,CAAC"}
@@ -0,0 +1,13 @@
1
+ export interface GitCloneParams {
2
+ url: string;
3
+ dirName?: string;
4
+ branch?: string;
5
+ depth?: number;
6
+ singleBranch?: boolean;
7
+ cwd?: string;
8
+ silence?: boolean;
9
+ }
10
+ export declare function gitExists(): boolean;
11
+ export declare function runGitClone(params: GitCloneParams): void;
12
+ export declare function setRemoteOrigin(dir: string, url: string, cwd: string): void;
13
+ //# sourceMappingURL=git-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-client.d.ts","sourceRoot":"","sources":["../../src/core/git-client.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA8BD,wBAAgB,SAAS,IAAI,OAAO,CAOnC;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAOxD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAQ3E"}
@@ -0,0 +1,11 @@
1
+ export interface ParsedGithubUrl {
2
+ owner: string;
3
+ repo: string;
4
+ normalizedHttps: string;
5
+ }
6
+ export declare function isGithubLink(url: string): boolean;
7
+ export declare function parseGithubUrl(url: string): ParsedGithubUrl;
8
+ export declare function replaceMirrorHost(url: string, mirrorHost: string): string;
9
+ export declare function getRepoDirName(url: string): string;
10
+ export declare function buildMirrorProbeUrl(mirrorHost: string, repoPath: string): string;
11
+ //# sourceMappingURL=github-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-url.d.ts","sourceRoot":"","sources":["../../src/core/github-url.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGjD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAuB3D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEhF"}
@@ -1,2 +1,108 @@
1
- import{execSync as t,spawnSync as r}from"child_process";import i from"process";import"path";const o=(o,e)=>{if(!(()=>{try{return t("git --version"),!0}catch(t){return!1}})())throw new Error("Git does not exist!");if(!(t=>!!/^(https:\/\/github\.com\/|git@github\.com:)[\s\S]+\/[\s\S]+(\.git)?$/.exec(t))(o))throw new Error(`The current URL ${o} is not in a valid GitHub clone format!`);const{dir:s,branch:c,mirrorAddress:h,silence:n}=e||{};((t,i)=>{const o=t.split(" "),e=o.shift()||"",s=r(e,o,i);if(s.error)throw s.error})(`git clone ${h?((t,r)=>{let i=t;if(i.startsWith("git@")){const[,t,r]=i.match(/^git@github\.com:([\s\S]+)\/([\s\S]+)\.git$/)||[];i=`https://github.com/${t}/${r}.git`}return i.endsWith(".git")||(i+=".git"),i=i.replace("github.com",r),i})(o,h):o}${s?` ${s}`:""}${c?` --branch ${c}`:""}`,{cwd:i.cwd(),stdio:n?"pipe":"inherit"})};export{o as default};
2
- //# sourceMappingURL=gitClone.esm.js.map
1
+ import process from "node:process";
2
+ import path from "node:path";
3
+ import { execaSync } from "execa";
4
+ //#region src/core/git-client.ts
5
+ function toExecaStdio(silence) {
6
+ return silence ? "pipe" : "inherit";
7
+ }
8
+ function buildCloneArgs(params) {
9
+ const args = ["clone", params.url];
10
+ if (params.branch) args.push("--branch", params.branch);
11
+ if (params.depth !== void 0) args.push("--depth", String(params.depth));
12
+ if (params.singleBranch) args.push("--single-branch");
13
+ if (params.dirName) args.push(params.dirName);
14
+ return args;
15
+ }
16
+ function assertGitSuccess(result) {
17
+ if (result.failed || result.exitCode !== void 0 && result.exitCode !== 0) {
18
+ const stderr = result.stderr?.trim();
19
+ throw new Error(stderr || `Git 命令执行失败,退出码 ${result.exitCode ?? "未知"}`);
20
+ }
21
+ }
22
+ function gitExists() {
23
+ try {
24
+ execaSync("git", ["--version"]);
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+ function runGitClone(params) {
31
+ assertGitSuccess(execaSync("git", buildCloneArgs(params), {
32
+ cwd: params.cwd,
33
+ stdio: toExecaStdio(params.silence),
34
+ reject: false
35
+ }));
36
+ }
37
+ function setRemoteOrigin(dir, url, cwd) {
38
+ const dirPath = path.join(cwd, dir);
39
+ assertGitSuccess(execaSync("git", [
40
+ "remote",
41
+ "set-url",
42
+ "origin",
43
+ url
44
+ ], {
45
+ cwd: dirPath,
46
+ stdio: "pipe",
47
+ reject: false
48
+ }));
49
+ }
50
+ //#endregion
51
+ //#region src/core/github-url.ts
52
+ const GITHUB_HTTPS_RE = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/;
53
+ const GITHUB_SSH_RE = /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/;
54
+ function parseGithubUrl(url) {
55
+ const trimmed = url.trim();
56
+ const httpsMatch = GITHUB_HTTPS_RE.exec(trimmed);
57
+ if (httpsMatch) {
58
+ const [, owner, repo] = httpsMatch;
59
+ return {
60
+ owner,
61
+ repo,
62
+ normalizedHttps: `https://github.com/${owner}/${repo}.git`
63
+ };
64
+ }
65
+ const sshMatch = GITHUB_SSH_RE.exec(trimmed);
66
+ if (sshMatch) {
67
+ const [, owner, repo] = sshMatch;
68
+ return {
69
+ owner,
70
+ repo,
71
+ normalizedHttps: `https://github.com/${owner}/${repo}.git`
72
+ };
73
+ }
74
+ throw new Error(`无效的 GitHub 仓库地址:${url}`);
75
+ }
76
+ function replaceMirrorHost(url, mirrorHost) {
77
+ const { normalizedHttps } = parseGithubUrl(url);
78
+ return normalizedHttps.replace("github.com", mirrorHost);
79
+ }
80
+ function getRepoDirName(url) {
81
+ const { repo } = parseGithubUrl(url);
82
+ return repo;
83
+ }
84
+ //#endregion
85
+ //#region src/core/clone-repo.ts
86
+ function cloneGithubRepo(url, options) {
87
+ if (!gitExists()) throw new Error("未检测到 Git,请先安装 Git,并确保其在 PATH 中可用");
88
+ const { normalizedHttps } = parseGithubUrl(url);
89
+ const mirrorHost = options?.mirrorHost;
90
+ const cloneUrl = mirrorHost ? replaceMirrorHost(url, mirrorHost) : normalizedHttps;
91
+ const { dirName, branch, cwd = process.cwd(), silence, depth, singleBranch } = options ?? {};
92
+ const requestedDir = dirName?.trim() ? dirName.trim() : void 0;
93
+ const targetDir = requestedDir ?? getRepoDirName(url);
94
+ runGitClone({
95
+ url: cloneUrl,
96
+ dirName: requestedDir,
97
+ branch,
98
+ depth,
99
+ singleBranch,
100
+ cwd,
101
+ silence
102
+ });
103
+ if (mirrorHost) setRemoteOrigin(targetDir, normalizedHttps, cwd);
104
+ }
105
+ //#endregion
106
+ export { cloneGithubRepo as default };
107
+
108
+ //# sourceMappingURL=gitClone.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gitClone.esm.js","sources":["../src/utils.ts","../src/gitClone.ts"],"sourcesContent":["import path from 'path';\nimport { execSync } from 'child_process';\n\n/**\n * 替换git的远程推送源地址,这里暂定origin名称为固定的\n */\nexport const setGitSource = (dir: string, url: string) => {\n // 仓库所在文件地址\n const dirPath = path.join(process.cwd(), dir);\n // 先删除在执行\n const shellStr = `git remote set-url origin ${url}`;\n execSync(shellStr, { cwd: dirPath });\n};\n\n/**\n * 根据github的url返回对应的文件夹名称\n * https://github.com.cnpmjs.org/bosens-China/github-clone会返回github-clone\n *\n */\nexport const getDir = (url: string) => {\n const str = url;\n const dir = str.split('/').pop() || '';\n return dir.includes('.git') ? dir.slice(0, dir.indexOf('.git')) : dir;\n};\n\n/* 是否为支持的git clone拉取格式\n * https://github.com/bosens-China/breeze-cli\n * https://github.com/bosens-China/breeze-cli.git\n * git@github.com:bosens-China/breeze-cli.git\n */\nexport const isGithubLink = (url: string) => {\n const reg = /^(https:\\/\\/github\\.com\\/|git@github\\.com:)[\\s\\S]+\\/[\\s\\S]+(\\.git)?$/;\n return !!reg.exec(url);\n};\n\n/**\n * 将当前的网站替换成镜像网站,例如\n * https://github.com/bosens-China/github-clone会被替换成\n * https://github.com.cnpmjs.org/bosens-China/github-clone\n */\n\nexport const replaceMirror = (currentWebsite: string, replaceWebsite: string) => {\n let s = currentWebsite;\n // 这里处理一下以git@开头的情况,如果git@开头,把用户名和仓库提取出来,用https的形式拉取\n // https://github.com/bosens-China/breeze-cli.git\n // git@github.com:bosens-China/breeze-cli.git\n if (s.startsWith('git@')) {\n const [, name, warehouse] = s.match(/^git@github\\.com:([\\s\\S]+)\\/([\\s\\S]+)\\.git$/) || [];\n s = `https://github.com/${name}/${warehouse}.git`;\n }\n if (!s.endsWith('.git')) {\n s += '.git';\n }\n s = s.replace('github.com', replaceWebsite);\n return s;\n};\n","import { execSync, spawnSync, SpawnSyncOptionsWithBufferEncoding } from 'child_process';\nimport process from 'process';\nimport { isGithubLink, replaceMirror } from './utils';\n\n/**\n * 判断git是否存在\n */\nconst gitExist = () => {\n try {\n execSync(`git --version`);\n return true;\n } catch {\n return false;\n }\n};\n\nconst pull = (shellStr: string, option?: SpawnSyncOptionsWithBufferEncoding) => {\n const args = shellStr.split(' ');\n const name = args.shift() || '';\n const info = spawnSync(name, args, option);\n if (info.error) {\n throw info.error;\n }\n return info;\n};\ninterface Options {\n dir: string;\n branch: string;\n mirrorAddress: string;\n silence: boolean;\n}\n\nconst clone = (url: string, options?: Partial<Options>) => {\n if (!gitExist()) {\n throw new Error(`Git does not exist!`);\n }\n if (!isGithubLink(url)) {\n throw new Error(`The current URL ${url} is not in a valid GitHub clone format!`);\n }\n const { dir, branch, mirrorAddress, silence } = options || {};\n // 如果存在镜像网站就替换一下格式\n const template = mirrorAddress ? replaceMirror(url, mirrorAddress) : url;\n const shell = `git clone ${template}${dir ? ` ${dir}` : ''}${branch ? ` --branch ${branch}` : ''}`;\n pull(shell, { cwd: process.cwd(), stdio: silence ? 'pipe' : 'inherit' });\n};\n\nexport default clone;\n"],"names":["clone","url","options","execSync","gitExist","Error","exec","isGithubLink","dir","branch","mirrorAddress","silence","shellStr","option","args","split","name","shift","info","spawnSync","error","pull","currentWebsite","replaceWebsite","s","startsWith","warehouse","match","endsWith","replace","replaceMirror","cwd","process","stdio"],"mappings":"4FA8BO,MCEDA,EAAQ,CAACC,EAAaC,KAC1B,IA1Be,MACf,IAEE,OADAC,EAAS,kBACF,EACP,SACA,OAAO,IAqBJC,GACH,MAAM,IAAIC,MAAM,uBAElB,IDN0B,CAACJ,KACf,uEACCK,KAAKL,GCIbM,CAAaN,GAChB,MAAM,IAAII,MAAM,mBAAmBJ,4CAErC,MAAMO,IAAEA,EAAGC,OAAEA,EAAMC,cAAEA,EAAaC,QAAEA,GAAYT,GAAW,GAvBhD,EAACU,EAAkBC,KAC9B,MAAMC,EAAOF,EAASG,MAAM,KACtBC,EAAOF,EAAKG,SAAW,GACvBC,EAAOC,EAAUH,EAAMF,EAAMD,GACnC,GAAIK,EAAKE,MACP,MAAMF,EAAKE,OAsBbC,CADc,aADGX,EDAU,EAACY,EAAwBC,KACpD,IAAIC,EAAIF,EAIR,GAAIE,EAAEC,WAAW,QAAS,CACxB,OAAST,EAAMU,GAAaF,EAAEG,MAAM,gDAAkD,GACtFH,EAAI,sBAAsBR,KAAQU,QAMpC,OAJKF,EAAEI,SAAS,UACdJ,GAAK,QAEPA,EAAIA,EAAEK,QAAQ,aAAcN,GACrBC,GCb0BM,CAAc7B,EAAKS,GAAiBT,IAC/BO,EAAM,IAAIA,IAAQ,KAAKC,EAAS,aAAaA,IAAW,KAClF,CAAEsB,IAAKC,EAAQD,MAAOE,MAAOtB,EAAU,OAAS"}
1
+ {"version":3,"file":"gitClone.esm.js","names":[],"sources":["../src/core/git-client.ts","../src/core/github-url.ts","../src/core/clone-repo.ts"],"sourcesContent":["import path from 'node:path';\nimport { execaSync } from 'execa';\n\nexport interface GitCloneParams {\n url: string;\n dirName?: string;\n branch?: string;\n depth?: number;\n singleBranch?: boolean;\n cwd?: string;\n silence?: boolean;\n}\n\nfunction toExecaStdio(silence?: boolean): 'pipe' | 'inherit' {\n return silence ? 'pipe' : 'inherit';\n}\n\nfunction buildCloneArgs(params: GitCloneParams): string[] {\n const args = ['clone', params.url];\n if (params.branch) {\n args.push('--branch', params.branch);\n }\n if (params.depth !== undefined) {\n args.push('--depth', String(params.depth));\n }\n if (params.singleBranch) {\n args.push('--single-branch');\n }\n if (params.dirName) {\n args.push(params.dirName);\n }\n return args;\n}\n\nfunction assertGitSuccess(result: { failed: boolean; exitCode?: number; stderr?: string }): void {\n if (result.failed || (result.exitCode !== undefined && result.exitCode !== 0)) {\n const stderr = result.stderr?.trim();\n throw new Error(stderr || `Git 命令执行失败,退出码 ${result.exitCode ?? '未知'}`);\n }\n}\n\nexport function gitExists(): boolean {\n try {\n execaSync('git', ['--version']);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function runGitClone(params: GitCloneParams): void {\n const result = execaSync('git', buildCloneArgs(params), {\n cwd: params.cwd,\n stdio: toExecaStdio(params.silence),\n reject: false,\n });\n assertGitSuccess(result);\n}\n\nexport function setRemoteOrigin(dir: string, url: string, cwd: string): void {\n const dirPath = path.join(cwd, dir);\n const result = execaSync('git', ['remote', 'set-url', 'origin', url], {\n cwd: dirPath,\n stdio: 'pipe',\n reject: false,\n });\n assertGitSuccess(result);\n}\n","const GITHUB_HTTPS_RE = /^https:\\/\\/github\\.com\\/([^/]+)\\/([^/]+?)(?:\\.git)?(?:\\/.*)?$/;\nconst GITHUB_SSH_RE = /^git@github\\.com:([^/]+)\\/([^/]+?)(?:\\.git)?$/;\n\nexport interface ParsedGithubUrl {\n owner: string;\n repo: string;\n normalizedHttps: string;\n}\n\nexport function isGithubLink(url: string): boolean {\n const trimmed = url.trim();\n return GITHUB_HTTPS_RE.test(trimmed) || GITHUB_SSH_RE.test(trimmed);\n}\n\nexport function parseGithubUrl(url: string): ParsedGithubUrl {\n const trimmed = url.trim();\n const httpsMatch = GITHUB_HTTPS_RE.exec(trimmed);\n if (httpsMatch) {\n const [, owner, repo] = httpsMatch as RegExpExecArray & [string, string, string];\n return {\n owner,\n repo,\n normalizedHttps: `https://github.com/${owner}/${repo}.git`,\n };\n }\n\n const sshMatch = GITHUB_SSH_RE.exec(trimmed);\n if (sshMatch) {\n const [, owner, repo] = sshMatch as RegExpExecArray & [string, string, string];\n return {\n owner,\n repo,\n normalizedHttps: `https://github.com/${owner}/${repo}.git`,\n };\n }\n\n throw new Error(`无效的 GitHub 仓库地址:${url}`);\n}\n\nexport function replaceMirrorHost(url: string, mirrorHost: string): string {\n const { normalizedHttps } = parseGithubUrl(url);\n return normalizedHttps.replace('github.com', mirrorHost);\n}\n\nexport function getRepoDirName(url: string): string {\n const { repo } = parseGithubUrl(url);\n return repo;\n}\n\nexport function buildMirrorProbeUrl(mirrorHost: string, repoPath: string): string {\n return `https://${mirrorHost}/${repoPath}`;\n}\n","import process from 'node:process';\nimport type { CloneOptions } from '../types';\nimport { gitExists, runGitClone, setRemoteOrigin } from './git-client';\nimport { getRepoDirName, parseGithubUrl, replaceMirrorHost } from './github-url';\n\nexport function cloneGithubRepo(url: string, options?: CloneOptions): void {\n if (!gitExists()) {\n throw new Error('未检测到 Git,请先安装 Git,并确保其在 PATH 中可用');\n }\n\n const { normalizedHttps } = parseGithubUrl(url);\n const mirrorHost = options?.mirrorHost;\n const cloneUrl = mirrorHost ? replaceMirrorHost(url, mirrorHost) : normalizedHttps;\n\n const {\n dirName,\n branch,\n cwd = process.cwd(),\n silence,\n depth,\n singleBranch,\n } = options ?? {};\n\n const requestedDir = dirName?.trim() ? dirName.trim() : undefined;\n const targetDir = requestedDir ?? getRepoDirName(url);\n\n runGitClone({\n url: cloneUrl,\n dirName: requestedDir,\n branch,\n depth,\n singleBranch,\n cwd,\n silence,\n });\n\n if (mirrorHost) {\n setRemoteOrigin(targetDir, normalizedHttps, cwd);\n }\n}\n\nexport default cloneGithubRepo;\n"],"mappings":";;;;AAaA,SAAS,aAAa,SAAuC;CAC3D,OAAO,UAAU,SAAS;AAC5B;AAEA,SAAS,eAAe,QAAkC;CACxD,MAAM,OAAO,CAAC,SAAS,OAAO,GAAG;CACjC,IAAI,OAAO,QACT,KAAK,KAAK,YAAY,OAAO,MAAM;CAErC,IAAI,OAAO,UAAU,KAAA,GACnB,KAAK,KAAK,WAAW,OAAO,OAAO,KAAK,CAAC;CAE3C,IAAI,OAAO,cACT,KAAK,KAAK,iBAAiB;CAE7B,IAAI,OAAO,SACT,KAAK,KAAK,OAAO,OAAO;CAE1B,OAAO;AACT;AAEA,SAAS,iBAAiB,QAAuE;CAC/F,IAAI,OAAO,UAAW,OAAO,aAAa,KAAA,KAAa,OAAO,aAAa,GAAI;EAC7E,MAAM,SAAS,OAAO,QAAQ,KAAK;EACnC,MAAM,IAAI,MAAM,UAAU,kBAAkB,OAAO,YAAY,MAAM;CACvE;AACF;AAEA,SAAgB,YAAqB;CACnC,IAAI;EACF,UAAU,OAAO,CAAC,WAAW,CAAC;EAC9B,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,YAAY,QAA8B;CAMxD,iBALe,UAAU,OAAO,eAAe,MAAM,GAAG;EACtD,KAAK,OAAO;EACZ,OAAO,aAAa,OAAO,OAAO;EAClC,QAAQ;CACV,CACsB,CAAC;AACzB;AAEA,SAAgB,gBAAgB,KAAa,KAAa,KAAmB;CAC3E,MAAM,UAAU,KAAK,KAAK,KAAK,GAAG;CAMlC,iBALe,UAAU,OAAO;EAAC;EAAU;EAAW;EAAU;CAAG,GAAG;EACpE,KAAK;EACL,OAAO;EACP,QAAQ;CACV,CACsB,CAAC;AACzB;;;ACnEA,MAAM,kBAAkB;AACxB,MAAM,gBAAgB;AAatB,SAAgB,eAAe,KAA8B;CAC3D,MAAM,UAAU,IAAI,KAAK;CACzB,MAAM,aAAa,gBAAgB,KAAK,OAAO;CAC/C,IAAI,YAAY;EACd,MAAM,GAAG,OAAO,QAAQ;EACxB,OAAO;GACL;GACA;GACA,iBAAiB,sBAAsB,MAAM,GAAG,KAAK;EACvD;CACF;CAEA,MAAM,WAAW,cAAc,KAAK,OAAO;CAC3C,IAAI,UAAU;EACZ,MAAM,GAAG,OAAO,QAAQ;EACxB,OAAO;GACL;GACA;GACA,iBAAiB,sBAAsB,MAAM,GAAG,KAAK;EACvD;CACF;CAEA,MAAM,IAAI,MAAM,mBAAmB,KAAK;AAC1C;AAEA,SAAgB,kBAAkB,KAAa,YAA4B;CACzE,MAAM,EAAE,oBAAoB,eAAe,GAAG;CAC9C,OAAO,gBAAgB,QAAQ,cAAc,UAAU;AACzD;AAEA,SAAgB,eAAe,KAAqB;CAClD,MAAM,EAAE,SAAS,eAAe,GAAG;CACnC,OAAO;AACT;;;AC1CA,SAAgB,gBAAgB,KAAa,SAA8B;CACzE,IAAI,CAAC,UAAU,GACb,MAAM,IAAI,MAAM,kCAAkC;CAGpD,MAAM,EAAE,oBAAoB,eAAe,GAAG;CAC9C,MAAM,aAAa,SAAS;CAC5B,MAAM,WAAW,aAAa,kBAAkB,KAAK,UAAU,IAAI;CAEnE,MAAM,EACJ,SACA,QACA,MAAM,QAAQ,IAAI,GAClB,SACA,OACA,iBACE,WAAW,CAAC;CAEhB,MAAM,eAAe,SAAS,KAAK,IAAI,QAAQ,KAAK,IAAI,KAAA;CACxD,MAAM,YAAY,gBAAgB,eAAe,GAAG;CAEpD,YAAY;EACV,KAAK;EACL,SAAS;EACT;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,IAAI,YACF,gBAAgB,WAAW,iBAAiB,GAAG;AAEnD"}