@boses/github-clone 1.1.6 → 1.3.0

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,268 @@
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`、`gitclone.com/github.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
+ ```
44
+
45
+ ## CLI 命令
46
+
47
+ ### `g clone` — 克隆仓库
48
+
49
+ ```sh
50
+ g clone <url> [dir] [options]
51
+ ```
26
52
 
27
- ### API
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 参数 |
28
62
 
29
- #### Clone
63
+ **支持的 URL 格式:**
30
64
 
31
- **clone <path> [dir] <--branch [branchName]>**
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` |
32
70
 
33
- - `<path>`
71
+ **示例:**
34
72
 
35
- - type:`stirng`
36
- - require:`true`
73
+ ```sh
74
+ # 配置镜像后克隆
75
+ g mirror set kgithub
76
+ g clone https://github.com/bosens-China/github-clone
37
77
 
38
- 拉取 GitHub 仓库 对应的地址,可以拉取以下三种类型地址
78
+ # 指定目录与分支
79
+ g clone https://github.com/bosens-China/github-clone.git my-dir -b dev
39
80
 
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 的地址 |
81
+ # 最小体积:只要 main 分支最近一次提交
82
+ g clone https://github.com/bosens-China/github-clone -b main --single-branch --depth 1
45
83
 
46
- - `[dir]`
84
+ # 已配置镜像但本次强制直连
85
+ g clone https://github.com/bosens-China/github-clone --no-mirror --verbose
47
86
 
48
- - type:`stirng`
49
- - require:`false`
87
+ # SSH 地址同样支持
88
+ g clone git@github.com:bosens-China/github-clone.git
89
+ ```
50
90
 
51
- clone 到本地的目录名称
91
+ **默认输出示例:**
52
92
 
53
- - `--branch [branchName]`
93
+ ```text
94
+ → 使用镜像 kgithub.com
95
+ ... git clone 进度 ...
96
+ ✓ 克隆完成:repo
97
+ 已恢复 origin 为 GitHub 官方地址
98
+ ```
54
99
 
55
- - type:`stirng`
56
- - require:`false`
100
+ **`--verbose` 输出示例:**
57
101
 
58
- 指定拉取的分支名称,可以以`--branch`长形式使用也可以以`-b`的短形式使用,例如:
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
59
109
 
60
- ```sh
61
- g clone https://github.com/bosens-China/github-clone.git -b dev
62
- ```
110
+ ... git clone 进度 ...
111
+ 克隆完成:repo,分支 dev
112
+ ```
63
113
 
64
- #### set [url]
114
+ ### `g mirror` — 镜像管理
65
115
 
66
- - `[url]`
116
+ | 命令 | 说明 |
117
+ |------|------|
118
+ | `g mirror list`(`ls`) | 列出内置镜像预设,标注当前使用的镜像 |
119
+ | `g mirror set <address\|preset>` | 设置镜像地址或预设名(`kgithub` / `moeyy` / `gitclone`) |
120
+ | `g mirror get` | 查看 `~/.g.config` 中的自定义镜像 |
121
+ | `g mirror test [address]` | 对镜像发起 HTTP HEAD 探测是否可达(超时 10 秒) |
122
+ | `g mirror unset` | 删除 `~/.g.config` |
67
123
 
68
- - type:`stirng`
69
- - require:`true`
124
+ **内置预设:**
70
125
 
71
- 用于配置镜像网站
126
+ | 预设名 | 地址 | 说明 |
127
+ |--------|------|------|
128
+ | `kgithub` | `kgithub.com` | KGitHub 镜像(域名替换) |
129
+ | `moeyy` | `github.moeyy.xyz` | Moeyy 镜像(域名替换) |
130
+ | `gitclone` | `gitclone.com/github.com` | GitClone 缓存(路径前缀) |
72
131
 
73
- #### get
132
+ 例如,`g mirror set gitclone` 会将 `https://github.com/owner/repo.git` 转换为 `https://gitclone.com/github.com/owner/repo.git`。
74
133
 
75
- 返回用户配置的`set [url]`地址,默认为`github.com.cnpmjs.org`
134
+ **镜像与克隆的关系:**
76
135
 
77
- ## Node
136
+ | 场景 | `g mirror get` | `g clone` 行为 |
137
+ |------|----------------|----------------|
138
+ | 未配置镜像 | 提示未配置 | 直连 GitHub |
139
+ | 已 `mirror set` | 显示配置的地址 | 使用配置的镜像 |
140
+ | 已 `mirror unset` | 提示未配置 | 直连 GitHub |
141
+ | 已配置镜像 + `--no-mirror` | — | 强制直连 GitHub |
78
142
 
79
- ### 安装
143
+ ## 编程式 API
80
144
 
81
145
  ```sh
82
- yarn add @boses/github-clone
146
+ pnpm add @boses/github-clone
147
+ ```
148
+
149
+ ```ts
150
+ import clone from '@boses/github-clone';
151
+
152
+ try {
153
+ clone('https://github.com/bosens-China/github-clone', {
154
+ mirrorHost: 'kgithub.com',
155
+ branch: 'main',
156
+ depth: 1,
157
+ singleBranch: true,
158
+ silence: true,
159
+ });
160
+ } catch (error) {
161
+ console.error(error);
162
+ }
163
+ ```
164
+
165
+ ### `clone(url, options?)`
166
+
167
+ 同步 API,请用 `try/catch` 处理错误。不会读取 `~/.g.config`,镜像需通过 `mirrorHost` 显式传入。
168
+
169
+ | 选项 | 类型 | 说明 |
170
+ |------|------|------|
171
+ | `dirName` | `string` | 本地目录名 |
172
+ | `branch` | `string` | 分支名 |
173
+ | `mirrorHost` | `string` | 镜像地址,支持域名或域名/路径(API 不会读取 `~/.g.config`,需显式传入) |
174
+ | `cwd` | `string` | 执行目录,默认 `process.cwd()` |
175
+ | `silence` | `boolean` | 静默模式,不继承 Git 输出 |
176
+ | `depth` | `number` | 浅克隆深度 |
177
+ | `singleBranch` | `boolean` | 仅克隆指定分支 |
178
+
179
+ ## 项目结构
180
+
181
+ ```text
182
+ src/
183
+ ├── index.ts # 库 API 入口
184
+ ├── constants.ts
185
+ ├── types.ts
186
+ ├── cli/
187
+ │ ├── index.ts # CLI 入口
188
+ │ └── commands/
189
+ │ ├── clone-command.ts
190
+ │ └── mirror-command.ts
191
+ ├── config/
192
+ │ └── mirror-store.ts
193
+ └── core/
194
+ ├── clone-repo.ts
195
+ ├── git-client.ts
196
+ └── github-url.ts
197
+
198
+ test/ # 测试目录(与 src 目录结构对应)
83
199
  ```
84
200
 
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');
201
+ 命名约定:多词文件名使用 **kebab-case**;`index.ts` 作为模块入口。
202
+
203
+ ## 开发
204
+
205
+ 要求:Node ≥ 22,pnpm,TypeScript strict。
206
+
207
+ ```sh
208
+ pnpm install
209
+ pnpm dev # 监听模式运行 CLI(src/cli/index.ts)
210
+ pnpm test # 运行测试
211
+ pnpm test:coverage # 测试 + 覆盖率
212
+ pnpm type-check # TypeScript 类型检查
213
+ pnpm lint # ESLint 检查
214
+ pnpm format # ESLint 自动修复(与 pre-commit 钩子一致)
215
+ pnpm build # Rolldown 打包 + 生成 .d.ts
90
216
  ```
91
217
 
92
- > clone 会以同步的形式运行,记得使用 `try` 包裹住可能的错误
218
+ `pnpm install` 会通过 `prepare` 脚本自动安装 Husky 钩子。提交时 `pre-commit` 会对暂存的 `*.{ts,mjs}` 文件执行 `eslint --fix`。
219
+
220
+ ## 发布流程
93
221
 
94
- ### API
222
+ 本仓库通过 GitHub Actions + [release-please](https://github.com/googleapis/release-please) + npm Trusted Publishing 自动化发布,无需手动维护版本号、CHANGELOG,也无需 `NPM_TOKEN`。
95
223
 
96
- `clone: (url: string, options: Partial<Options>) => void`
224
+ **两条 workflow:**
97
225
 
98
- #### url
226
+ | 文件 | 触发 | 作用 |
227
+ |------|------|------|
228
+ | `.github/workflows/ci.yml` | `pull_request` / `push` 到 `main` | lint + type-check + 测试 + 构建校验 |
229
+ | `.github/workflows/release.yml` | `push` 到 `main` | release-please 维护 release PR;该 PR 合并后自动 `npm publish` |
99
230
 
100
- - type:`string`
101
- - require: `true`
231
+ **贡献提交规范(Conventional Commits):**
102
232
 
103
- 拉取的 GitHub 仓库地址
233
+ ```text
234
+ feat: ... # 新功能(minor)
235
+ fix: ... # bug 修复(patch)
236
+ perf: ... # 性能优化(patch)
237
+ refactor: ... # 重构(patch)
238
+ docs: ... # 文档
239
+ test: ... # 测试
240
+ chore: ... # 杂项(不进 CHANGELOG)
104
241
 
105
- #### options
242
+ # 破坏性变更(major)
243
+ feat!: ...
244
+ 或在 body 中写 BREAKING CHANGE: ...
245
+ ```
106
246
 
107
- | 名称 | 类型 | 是否必填 | 描述 |
108
- | ------------- | --------- | -------- | ------------------------------------------ |
109
- | dirName | `string` | `false` | 拉取的目录名称 |
110
- | branch | `string` | `false` | 拉取的分支名称 |
111
- | mirrorAddress | `string` | `false` | 镜像网站,如果你需要使用镜像可以填写此网站 |
112
- | silence | `boolean` | `false` | 是否静默模式执行 clone |
113
- | cwd | `string` | `false` | 执行 clone 所执行的目录路径 |
247
+ **发布步骤:**
114
248
 
115
- ## 其他
249
+ 1. 在 PR 中按上述格式写 commit
250
+ 2. PR 合并到 `main`
251
+ 3. release-please 自动在仓库里维护一个 `chore(main): release x.y.z` PR,里面包含 CHANGELOG 与版本号更新
252
+ 4. Review 并合并该 PR → 自动打 tag → 触发 `npm publish`(OIDC,无 token)
253
+ 5. 在 npmjs.com 用 2FA approve staged package → 上线
116
254
 
117
- 目前版本更新导致对`1.0.9`之前的`g get`不支持,请重新执行`g set [url]`的操作
255
+ ## 限制
118
256
 
119
- 如果发现错误或者需要有更好的建议欢迎在 [issues](https://github.com/bosens-China/github-clone) 中提出
257
+ - 仅支持 GitHub(`github.com`),不支持 GitLab / Gitee / GitHub Enterprise
258
+ - 支持域名替换型及 `gitclone.com/github.com/...` 路径前缀型镜像,不支持 `ghproxy.com/https://github.com/...` 这类嵌套完整 URL
259
+ - 不支持 `git pull` / `git fetch` 加速
260
+ - `mirror test` 仅验证 HTTP 可达,不保证 `git clone` 一定成功
120
261
 
121
- ## 参考
262
+ ## 反馈
122
263
 
123
- - [知乎 git clone 一个 github 上的仓库,太慢,经常连接失败...](https://www.zhihu.com/question/27159393/answer/1117219745)
264
+ 如有问题或建议,欢迎在 [Issues](https://github.com/bosens-China/github-clone) 反馈。
124
265
 
125
266
  ## 协议
126
267
 
127
- [MIT License](/License)
268
+ [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":"AAgBA,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,4CAA4C;AAC5C,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,EAgBxC,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{dirName:s,branch:c,cwd:n=i.cwd(),mirrorAddress:h,silence:g}=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||""}`,c?`--branch ${c}`:""].filter((t=>t)).join(" "),{cwd:n,stdio:g?"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 dirName: string;\n branch: string;\n mirrorAddress: string;\n cwd: 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 { dirName, branch, cwd = process.cwd(), mirrorAddress, silence } = options || {};\n // 如果存在镜像网站就替换一下格式\n const template = mirrorAddress ? replaceMirror(url, mirrorAddress) : url;\n const shellArr = ['git', 'clone', template, `${dirName || ''}`, branch ? `--branch ${branch}` : ''];\n const shell = shellArr.filter((f) => f).join(' ');\n pull(shell, { cwd, stdio: silence ? 'pipe' : 'inherit' });\n};\n\nexport default clone;\n"],"names":["clone","url","options","execSync","gitExist","Error","exec","isGithubLink","dirName","branch","cwd","process","mirrorAddress","silence","shellStr","option","args","split","name","shift","info","spawnSync","error","pull","currentWebsite","replaceWebsite","s","startsWith","warehouse","match","endsWith","replace","replaceMirror","filter","f","join","stdio"],"mappings":"4FA8BO,MCGDA,EAAQ,CAACC,EAAaC,KAC1B,IA3Be,MACf,IAEE,OADAC,EAAS,kBACF,EACP,SACA,OAAO,IAsBJC,GACH,MAAM,IAAIC,MAAM,uBAElB,IDP0B,CAACJ,KACf,uEACCK,KAAKL,GCKbM,CAAaN,GAChB,MAAM,IAAII,MAAM,mBAAmBJ,4CAErC,MAAMO,QAAEA,EAAOC,OAAEA,EAAMC,IAAEA,EAAMC,EAAQD,MAAKE,cAAEA,EAAaC,QAAEA,GAAYX,GAAW,GAxBzE,EAACY,EAAkBC,KAC9B,MAAMC,EAAOF,EAASG,MAAM,KACtBC,EAAOF,EAAKG,SAAW,GACvBC,EAAOC,EAAUH,EAAMF,EAAMD,GACnC,GAAIK,EAAKE,MACP,MAAMF,EAAKE,OAwBbC,CAFiB,CAAC,MAAO,QADRX,EDDU,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,GCZ0BM,CAAc/B,EAAKW,GAAiBX,EACzB,GAAGO,GAAW,KAAMC,EAAS,YAAYA,IAAW,IACzEwB,QAAQC,GAAMA,IAAGC,KAAK,KACjC,CAAEzB,IAAAA,EAAK0B,MAAOvB,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"}