shipcli 0.0.1__tar.gz
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.
- shipcli-0.0.1/LICENSE +21 -0
- shipcli-0.0.1/PKG-INFO +182 -0
- shipcli-0.0.1/README.md +155 -0
- shipcli-0.0.1/pyproject.toml +48 -0
- shipcli-0.0.1/setup.cfg +4 -0
- shipcli-0.0.1/shipcli/__init__.py +5 -0
- shipcli-0.0.1/shipcli/__main__.py +11 -0
- shipcli-0.0.1/shipcli/builder.py +220 -0
- shipcli-0.0.1/shipcli/cli.py +185 -0
- shipcli-0.0.1/shipcli/commands/__init__.py +17 -0
- shipcli-0.0.1/shipcli/commands/build.py +30 -0
- shipcli-0.0.1/shipcli/commands/init.py +30 -0
- shipcli-0.0.1/shipcli/commands/install.py +22 -0
- shipcli-0.0.1/shipcli/commands/publish.py +83 -0
- shipcli-0.0.1/shipcli/commands/uninstall.py +47 -0
- shipcli-0.0.1/shipcli/commands/upgrade.py +83 -0
- shipcli-0.0.1/shipcli/config.py +35 -0
- shipcli-0.0.1/shipcli/console.py +33 -0
- shipcli-0.0.1/shipcli/installer.py +209 -0
- shipcli-0.0.1/shipcli/publisher.py +432 -0
- shipcli-0.0.1/shipcli/scaffold.py +411 -0
- shipcli-0.0.1/shipcli/version.py +95 -0
- shipcli-0.0.1/shipcli.egg-info/PKG-INFO +182 -0
- shipcli-0.0.1/shipcli.egg-info/SOURCES.txt +30 -0
- shipcli-0.0.1/shipcli.egg-info/dependency_links.txt +1 -0
- shipcli-0.0.1/shipcli.egg-info/entry_points.txt +2 -0
- shipcli-0.0.1/shipcli.egg-info/requires.txt +10 -0
- shipcli-0.0.1/shipcli.egg-info/top_level.txt +1 -0
- shipcli-0.0.1/tests/test_build_install.py +219 -0
- shipcli-0.0.1/tests/test_cli.py +121 -0
- shipcli-0.0.1/tests/test_init.py +92 -0
- shipcli-0.0.1/tests/test_publish.py +341 -0
shipcli-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CaffeineOddity
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
shipcli-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shipcli
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Pure Python CLI scaffold and delivery tool.
|
|
5
|
+
Author: CaffeineOddity
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: cli,scaffold,pyinstaller,release,tooling
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: MacOS
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
15
|
+
Classifier: Topic :: Utilities
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Provides-Extra: build
|
|
20
|
+
Requires-Dist: pyinstaller>=6.0; extra == "build"
|
|
21
|
+
Provides-Extra: publish
|
|
22
|
+
Requires-Dist: twine>=5.1.0; extra == "publish"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build>=1.2.2; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest>=9.0.0; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# shipcli
|
|
29
|
+
|
|
30
|
+
`shipcli` 是一个纯 Python 的 CLI 脚手架与交付工具,用来初始化、构建、发布、安装和升级命令行项目。
|
|
31
|
+
|
|
32
|
+
## 特性
|
|
33
|
+
|
|
34
|
+
- 用 `shipcli init <path>` 快速生成新的 Python CLI 项目
|
|
35
|
+
- 用 `shipcli build` / `shipcli build --release` 生成 dev 或 release 版的 wheel/sdist 分发文件
|
|
36
|
+
- 用 `shipcli publish` 把 release 版本发布到 GitHub Release 或 PyPI
|
|
37
|
+
- `shipcli upgrade` / `uninstall` 只管 shipcli 自身;项目 CLI(如 demo-cli)用其自带命令管理
|
|
38
|
+
- 默认以当前目录作为项目目录,也支持 `--project <path>` 跨目录操作
|
|
39
|
+
- 初始化后的项目默认包含 `help`、`version`、`upgrade`、`uninstall` 命令示例
|
|
40
|
+
|
|
41
|
+
## 命令作用范围
|
|
42
|
+
|
|
43
|
+
| 命令 | 作用对象 |
|
|
44
|
+
|------|---------|
|
|
45
|
+
| `init` / `build` / `install` / `publish` | 目标项目(当前目录或 `--project`,如 demo-cli) |
|
|
46
|
+
| `upgrade` / `uninstall` | shipcli 自身 |
|
|
47
|
+
|
|
48
|
+
若要升级或卸载某个项目 CLI(如 demo-cli),应先 `install` 安装它,再用其自带命令:`demo-cli upgrade` / `demo-cli uninstall`。
|
|
49
|
+
|
|
50
|
+
## 安装
|
|
51
|
+
|
|
52
|
+
### 首次安装(开发)
|
|
53
|
+
|
|
54
|
+
shipcli 未发布到 PyPI 前,从本地仓库安装:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone <your-repo-url>
|
|
58
|
+
cd shipcli
|
|
59
|
+
python3 -m pip install -e . # editable 安装,改代码即生效
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
安装完成后:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
shipcli --help
|
|
66
|
+
shipcli --version # 形如:shipcli 0.0.1 (editable)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`--version` / `--help` 会标注当前安装形态:`editable`(editable 安装)、`installed`(pip 正式安装)、`binary`(PyInstaller 二进制)。
|
|
70
|
+
|
|
71
|
+
### 安装 build 产物
|
|
72
|
+
|
|
73
|
+
`shipcli build` 产出 wheel/sdist 后,可直接装本地 wheel(模拟用户从 PyPI 安装):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
shipcli build --release
|
|
77
|
+
python3 -m pip install .build/dist/0.0.1/dist/*.whl
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 快速开始
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
shipcli init demo-cli
|
|
84
|
+
|
|
85
|
+
cd demo-cli
|
|
86
|
+
shipcli build # dev 构建,产出 <version>.devN
|
|
87
|
+
shipcli build --release # release 构建,产出发布版 wheel/sdist
|
|
88
|
+
shipcli publish --github --pypi # 发布到 GitHub Release + PyPI
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
也支持不切目录,直接指定目标项目:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
shipcli build --project ./demo-cli
|
|
95
|
+
shipcli publish --project ./demo-cli --github --github-repo owner/demo-cli
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 构建
|
|
99
|
+
|
|
100
|
+
`shipcli build` 产出标准的 Python 分发文件(wheel/sdist),不再依赖 PyInstaller:
|
|
101
|
+
|
|
102
|
+
- **dev 构建**(`shipcli build`):版本号 `<base>.devN`(如 `0.0.1.dev1`),build 号自动 +1。用于本地验证。
|
|
103
|
+
- **release 构建**(`shipcli build --release`):版本号取配置中的 `version`(如 `0.0.1`)。
|
|
104
|
+
- **版本递增**(`--increase <major|minor|patch>`):构建前把版本号指定位 +1(低位归零)并重置 build,可与 `--release` 同用。
|
|
105
|
+
|
|
106
|
+
版本号唯一真源是 `build.config.json` 的 `version`;release 构建会自动同步到 `pyproject.toml`,构建时会写入包 `__init__.py` 的 `__version__`。
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
shipcli build # dev 构建
|
|
110
|
+
shipcli build --release # release 构建
|
|
111
|
+
shipcli build --increase patch # patch+1 后 dev 构建
|
|
112
|
+
shipcli build --increase minor --release # minor+1 后 release 构建
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
产物目录结构:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
.build/dist/<version>/dist/
|
|
119
|
+
├── <name>-<version>-py3-none-any.whl
|
|
120
|
+
└── <name>-<version>.tar.gz
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 升级 shipcli 自身
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
shipcli upgrade # 从 PyPI 升级到最新发布版
|
|
127
|
+
shipcli upgrade --version 0.0.1 # 从 PyPI 升级到指定发布版
|
|
128
|
+
shipcli upgrade --local . # 从本地项目 build 产物升级(取最新版本)
|
|
129
|
+
shipcli upgrade --local . --version 0.0.1.dev1 # 指定本地 build 产物版本
|
|
130
|
+
shipcli uninstall # 卸载 shipcli(pip 卸载 + 清理本地痕迹)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
dev 版不上 PyPI,仅本地安装/升级:先 `shipcli build` 产出 dev wheel,再用 `shipcli upgrade --local .` 装上。
|
|
134
|
+
|
|
135
|
+
## 发布
|
|
136
|
+
|
|
137
|
+
- `shipcli publish` 只做发布,不负责构建
|
|
138
|
+
- 发布前先执行 `shipcli build --release`
|
|
139
|
+
- 发布到 GitHub Release:上传 wheel/sdist 及各自的 `.sha256` 校验文件
|
|
140
|
+
- 发布到 PyPI:上传 wheel/sdist
|
|
141
|
+
- GitHub 凭证只从环境变量读取:`SHIPCLI_GITHUB_TOKEN` 或 `GITHUB_TOKEN`
|
|
142
|
+
- GitHub 仓库可通过 `--github-repo` 传入,或设置 `SHIPCLI_GITHUB_REPO` / `GITHUB_REPOSITORY`
|
|
143
|
+
- PyPI 凭证只从环境变量读取:`SHIPCLI_PYPI_TOKEN`,也兼容现有 `TWINE_PASSWORD`
|
|
144
|
+
- `publish` 只支持 release 版本,不接受 dev 版本
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
cd demo-cli
|
|
148
|
+
|
|
149
|
+
# 发布本地已构建的 release 分发文件到 GitHub Release + PyPI
|
|
150
|
+
shipcli build --release
|
|
151
|
+
SHIPCLI_GITHUB_TOKEN=... SHIPCLI_PYPI_TOKEN=... \
|
|
152
|
+
shipcli publish --github --pypi --github-repo owner/demo-cli
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 初始化后的目录结构
|
|
156
|
+
|
|
157
|
+
执行 `shipcli init demo-cli` 后,默认会生成:
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
demo-cli/
|
|
161
|
+
├── build.config.json
|
|
162
|
+
├── README.md
|
|
163
|
+
├── demo_cli/
|
|
164
|
+
│ ├── __init__.py
|
|
165
|
+
│ ├── __main__.py
|
|
166
|
+
│ ├── cli.py
|
|
167
|
+
│ └── commands/
|
|
168
|
+
│ ├── __init__.py
|
|
169
|
+
│ ├── help.py
|
|
170
|
+
│ ├── version.py
|
|
171
|
+
│ ├── upgrade.py
|
|
172
|
+
│ └── uninstall.py
|
|
173
|
+
└── tests/
|
|
174
|
+
└── test_cli.py
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 开发验证
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
python3 -m pytest
|
|
181
|
+
shipcli build --release
|
|
182
|
+
```
|
shipcli-0.0.1/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# shipcli
|
|
2
|
+
|
|
3
|
+
`shipcli` 是一个纯 Python 的 CLI 脚手架与交付工具,用来初始化、构建、发布、安装和升级命令行项目。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 用 `shipcli init <path>` 快速生成新的 Python CLI 项目
|
|
8
|
+
- 用 `shipcli build` / `shipcli build --release` 生成 dev 或 release 版的 wheel/sdist 分发文件
|
|
9
|
+
- 用 `shipcli publish` 把 release 版本发布到 GitHub Release 或 PyPI
|
|
10
|
+
- `shipcli upgrade` / `uninstall` 只管 shipcli 自身;项目 CLI(如 demo-cli)用其自带命令管理
|
|
11
|
+
- 默认以当前目录作为项目目录,也支持 `--project <path>` 跨目录操作
|
|
12
|
+
- 初始化后的项目默认包含 `help`、`version`、`upgrade`、`uninstall` 命令示例
|
|
13
|
+
|
|
14
|
+
## 命令作用范围
|
|
15
|
+
|
|
16
|
+
| 命令 | 作用对象 |
|
|
17
|
+
|------|---------|
|
|
18
|
+
| `init` / `build` / `install` / `publish` | 目标项目(当前目录或 `--project`,如 demo-cli) |
|
|
19
|
+
| `upgrade` / `uninstall` | shipcli 自身 |
|
|
20
|
+
|
|
21
|
+
若要升级或卸载某个项目 CLI(如 demo-cli),应先 `install` 安装它,再用其自带命令:`demo-cli upgrade` / `demo-cli uninstall`。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
### 首次安装(开发)
|
|
26
|
+
|
|
27
|
+
shipcli 未发布到 PyPI 前,从本地仓库安装:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone <your-repo-url>
|
|
31
|
+
cd shipcli
|
|
32
|
+
python3 -m pip install -e . # editable 安装,改代码即生效
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
安装完成后:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
shipcli --help
|
|
39
|
+
shipcli --version # 形如:shipcli 0.0.1 (editable)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`--version` / `--help` 会标注当前安装形态:`editable`(editable 安装)、`installed`(pip 正式安装)、`binary`(PyInstaller 二进制)。
|
|
43
|
+
|
|
44
|
+
### 安装 build 产物
|
|
45
|
+
|
|
46
|
+
`shipcli build` 产出 wheel/sdist 后,可直接装本地 wheel(模拟用户从 PyPI 安装):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
shipcli build --release
|
|
50
|
+
python3 -m pip install .build/dist/0.0.1/dist/*.whl
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 快速开始
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
shipcli init demo-cli
|
|
57
|
+
|
|
58
|
+
cd demo-cli
|
|
59
|
+
shipcli build # dev 构建,产出 <version>.devN
|
|
60
|
+
shipcli build --release # release 构建,产出发布版 wheel/sdist
|
|
61
|
+
shipcli publish --github --pypi # 发布到 GitHub Release + PyPI
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
也支持不切目录,直接指定目标项目:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
shipcli build --project ./demo-cli
|
|
68
|
+
shipcli publish --project ./demo-cli --github --github-repo owner/demo-cli
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 构建
|
|
72
|
+
|
|
73
|
+
`shipcli build` 产出标准的 Python 分发文件(wheel/sdist),不再依赖 PyInstaller:
|
|
74
|
+
|
|
75
|
+
- **dev 构建**(`shipcli build`):版本号 `<base>.devN`(如 `0.0.1.dev1`),build 号自动 +1。用于本地验证。
|
|
76
|
+
- **release 构建**(`shipcli build --release`):版本号取配置中的 `version`(如 `0.0.1`)。
|
|
77
|
+
- **版本递增**(`--increase <major|minor|patch>`):构建前把版本号指定位 +1(低位归零)并重置 build,可与 `--release` 同用。
|
|
78
|
+
|
|
79
|
+
版本号唯一真源是 `build.config.json` 的 `version`;release 构建会自动同步到 `pyproject.toml`,构建时会写入包 `__init__.py` 的 `__version__`。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
shipcli build # dev 构建
|
|
83
|
+
shipcli build --release # release 构建
|
|
84
|
+
shipcli build --increase patch # patch+1 后 dev 构建
|
|
85
|
+
shipcli build --increase minor --release # minor+1 后 release 构建
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
产物目录结构:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
.build/dist/<version>/dist/
|
|
92
|
+
├── <name>-<version>-py3-none-any.whl
|
|
93
|
+
└── <name>-<version>.tar.gz
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 升级 shipcli 自身
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
shipcli upgrade # 从 PyPI 升级到最新发布版
|
|
100
|
+
shipcli upgrade --version 0.0.1 # 从 PyPI 升级到指定发布版
|
|
101
|
+
shipcli upgrade --local . # 从本地项目 build 产物升级(取最新版本)
|
|
102
|
+
shipcli upgrade --local . --version 0.0.1.dev1 # 指定本地 build 产物版本
|
|
103
|
+
shipcli uninstall # 卸载 shipcli(pip 卸载 + 清理本地痕迹)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
dev 版不上 PyPI,仅本地安装/升级:先 `shipcli build` 产出 dev wheel,再用 `shipcli upgrade --local .` 装上。
|
|
107
|
+
|
|
108
|
+
## 发布
|
|
109
|
+
|
|
110
|
+
- `shipcli publish` 只做发布,不负责构建
|
|
111
|
+
- 发布前先执行 `shipcli build --release`
|
|
112
|
+
- 发布到 GitHub Release:上传 wheel/sdist 及各自的 `.sha256` 校验文件
|
|
113
|
+
- 发布到 PyPI:上传 wheel/sdist
|
|
114
|
+
- GitHub 凭证只从环境变量读取:`SHIPCLI_GITHUB_TOKEN` 或 `GITHUB_TOKEN`
|
|
115
|
+
- GitHub 仓库可通过 `--github-repo` 传入,或设置 `SHIPCLI_GITHUB_REPO` / `GITHUB_REPOSITORY`
|
|
116
|
+
- PyPI 凭证只从环境变量读取:`SHIPCLI_PYPI_TOKEN`,也兼容现有 `TWINE_PASSWORD`
|
|
117
|
+
- `publish` 只支持 release 版本,不接受 dev 版本
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cd demo-cli
|
|
121
|
+
|
|
122
|
+
# 发布本地已构建的 release 分发文件到 GitHub Release + PyPI
|
|
123
|
+
shipcli build --release
|
|
124
|
+
SHIPCLI_GITHUB_TOKEN=... SHIPCLI_PYPI_TOKEN=... \
|
|
125
|
+
shipcli publish --github --pypi --github-repo owner/demo-cli
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 初始化后的目录结构
|
|
129
|
+
|
|
130
|
+
执行 `shipcli init demo-cli` 后,默认会生成:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
demo-cli/
|
|
134
|
+
├── build.config.json
|
|
135
|
+
├── README.md
|
|
136
|
+
├── demo_cli/
|
|
137
|
+
│ ├── __init__.py
|
|
138
|
+
│ ├── __main__.py
|
|
139
|
+
│ ├── cli.py
|
|
140
|
+
│ └── commands/
|
|
141
|
+
│ ├── __init__.py
|
|
142
|
+
│ ├── help.py
|
|
143
|
+
│ ├── version.py
|
|
144
|
+
│ ├── upgrade.py
|
|
145
|
+
│ └── uninstall.py
|
|
146
|
+
└── tests/
|
|
147
|
+
└── test_cli.py
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 开发验证
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
python3 -m pytest
|
|
154
|
+
shipcli build --release
|
|
155
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "shipcli"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Pure Python CLI scaffold and delivery tool."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "CaffeineOddity" }
|
|
15
|
+
]
|
|
16
|
+
keywords = ["cli", "scaffold", "pyinstaller", "release", "tooling"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Operating System :: MacOS",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Topic :: Software Development :: Build Tools",
|
|
25
|
+
"Topic :: Utilities"
|
|
26
|
+
]
|
|
27
|
+
dependencies = []
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
build = [
|
|
31
|
+
"pyinstaller>=6.0"
|
|
32
|
+
]
|
|
33
|
+
publish = [
|
|
34
|
+
"twine>=5.1.0"
|
|
35
|
+
]
|
|
36
|
+
dev = [
|
|
37
|
+
"build>=1.2.2",
|
|
38
|
+
"pytest>=9.0.0"
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
shipcli = "shipcli.cli:main"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
include = ["shipcli*"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
testpaths = ["tests"]
|
shipcli-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""shipcli 纯 Python 构建流程。
|
|
2
|
+
|
|
3
|
+
方向 A:build 产出标准的 wheel/sdist(去掉 PyInstaller 二进制)。
|
|
4
|
+
- debug 构建(`shipcli build`)产 dev 版 wheel,版本号 `<base>.devN`。
|
|
5
|
+
- release 构建(`shipcli build --release`)产 release 版 wheel/sdist。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import importlib.util
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from shipcli.config import BuildConfig, config_path, load_config
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class BuildPlan:
|
|
23
|
+
"""描述一次构建的关键路径与版本信息。"""
|
|
24
|
+
|
|
25
|
+
app_name: str
|
|
26
|
+
build_root: Path
|
|
27
|
+
dist_root: Path
|
|
28
|
+
product_dir: Path
|
|
29
|
+
dist_dir: Path
|
|
30
|
+
product_version: str
|
|
31
|
+
base_version: str
|
|
32
|
+
build_number: int
|
|
33
|
+
release: bool
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def bump_version(version: str, part: str) -> str:
|
|
37
|
+
"""把语义版本号的指定位 +1,低位归零。"""
|
|
38
|
+
if part not in {"major", "minor", "patch"}:
|
|
39
|
+
raise ValueError("版本位仅支持 major / minor / patch")
|
|
40
|
+
segments = version.split(".")
|
|
41
|
+
if len(segments) != 3 or not all(seg.isdigit() for seg in segments):
|
|
42
|
+
raise ValueError(f"版本号不是标准的三段语义版本:{version}")
|
|
43
|
+
major, minor, patch = (int(seg) for seg in segments)
|
|
44
|
+
if part == "major":
|
|
45
|
+
major, minor, patch = major + 1, 0, 0
|
|
46
|
+
elif part == "minor":
|
|
47
|
+
minor, patch = minor + 1, 0
|
|
48
|
+
else:
|
|
49
|
+
patch += 1
|
|
50
|
+
return f"{major}.{minor}.{patch}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def resolve_build_plan(
|
|
54
|
+
project_root: Path,
|
|
55
|
+
config: BuildConfig,
|
|
56
|
+
release: bool = False,
|
|
57
|
+
increase: str | None = None,
|
|
58
|
+
) -> BuildPlan:
|
|
59
|
+
"""根据配置和参数生成本次构建计划。"""
|
|
60
|
+
if increase is not None:
|
|
61
|
+
base_version = bump_version(config["version"], increase)
|
|
62
|
+
else:
|
|
63
|
+
base_version = config["version"]
|
|
64
|
+
|
|
65
|
+
build_root = project_root / config["build_root"]
|
|
66
|
+
dist_root = build_root / "dist"
|
|
67
|
+
if release:
|
|
68
|
+
build_number = config["build"]
|
|
69
|
+
product_version = base_version
|
|
70
|
+
else:
|
|
71
|
+
build_number = config["build"] + 1
|
|
72
|
+
# PEP 440 规范的 dev 版本号:`<base>.devN`(可被 pip/PyPI 接受)。
|
|
73
|
+
product_version = f"{base_version}.dev{build_number}"
|
|
74
|
+
|
|
75
|
+
product_dir = dist_root / product_version
|
|
76
|
+
dist_dir = product_dir / "dist"
|
|
77
|
+
return BuildPlan(
|
|
78
|
+
app_name=config["app_name"],
|
|
79
|
+
build_root=build_root,
|
|
80
|
+
dist_root=dist_root,
|
|
81
|
+
product_dir=product_dir,
|
|
82
|
+
dist_dir=dist_dir,
|
|
83
|
+
product_version=product_version,
|
|
84
|
+
base_version=base_version,
|
|
85
|
+
build_number=build_number,
|
|
86
|
+
release=release,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def run_build(
|
|
91
|
+
project_root: Path,
|
|
92
|
+
release: bool = False,
|
|
93
|
+
increase: str | None = None,
|
|
94
|
+
) -> BuildPlan:
|
|
95
|
+
"""生成本次构建的 wheel/sdist,并在 debug 构建成功后回写 build。"""
|
|
96
|
+
config = load_config(project_root)
|
|
97
|
+
if not (project_root / "pyproject.toml").is_file():
|
|
98
|
+
raise FileNotFoundError("未找到 pyproject.toml,无法构建分发文件")
|
|
99
|
+
if shutil_is_missing_python():
|
|
100
|
+
raise RuntimeError("未找到可用的 Python 解释器")
|
|
101
|
+
|
|
102
|
+
# --increase 先把 bump 后的版本与重置的 build=0 回写配置,再据此生成计划,
|
|
103
|
+
# 保证后续 dev 号、产物目录都基于新版本与新 build 号。
|
|
104
|
+
if increase is not None:
|
|
105
|
+
config["version"] = bump_version(config["version"], increase)
|
|
106
|
+
config["build"] = 0
|
|
107
|
+
_write_version_and_build(project_root, config["version"], config["build"])
|
|
108
|
+
|
|
109
|
+
plan = resolve_build_plan(project_root, config, release=release, increase=None)
|
|
110
|
+
|
|
111
|
+
plan.build_root.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
if plan.product_dir.exists():
|
|
113
|
+
import shutil
|
|
114
|
+
|
|
115
|
+
shutil.rmtree(plan.product_dir)
|
|
116
|
+
plan.product_dir.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
|
|
118
|
+
# release 构建需把版本同步到 pyproject.toml,再产 wheel/sdist;
|
|
119
|
+
# dev 构建也要把 dev 版号同步进 pyproject.toml,否则 wheel 版本号不对。
|
|
120
|
+
target_version = plan.base_version if plan.release else plan.product_version
|
|
121
|
+
_sync_pyproject_version(project_root, target_version)
|
|
122
|
+
_write_package_version(project_root, config, plan.product_version)
|
|
123
|
+
_build_distributions(project_root, plan)
|
|
124
|
+
|
|
125
|
+
if not plan.release:
|
|
126
|
+
_write_build_number(project_root, plan.build_number)
|
|
127
|
+
else:
|
|
128
|
+
# release 构建会改写版本相关文件,git 仓库下自动提交,
|
|
129
|
+
# 保证后续 publish 打的 tag 落在「版本号已更新」的完整 commit 上。
|
|
130
|
+
_maybe_commit_release(project_root, plan.base_version)
|
|
131
|
+
return plan
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _maybe_commit_release(project_root: Path, version: str) -> None:
|
|
135
|
+
"""release 构建后,若为 git 仓库且有改动,自动提交版本发布点。"""
|
|
136
|
+
from shipcli.publisher import git_commit_all, git_working_tree_clean, is_git_repo
|
|
137
|
+
|
|
138
|
+
if not is_git_repo(project_root):
|
|
139
|
+
return
|
|
140
|
+
if git_working_tree_clean(project_root):
|
|
141
|
+
return
|
|
142
|
+
git_commit_all(project_root, f"release v{version}")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def shutil_is_missing_python() -> bool:
|
|
146
|
+
"""检测当前解释器是否可用(保留可测试的薄封装)。"""
|
|
147
|
+
import shutil
|
|
148
|
+
|
|
149
|
+
return shutil.which(sys.executable) is None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _build_distributions(project_root: Path, plan: BuildPlan) -> None:
|
|
153
|
+
"""调用 `python -m build` 生成 wheel/sdist 到 plan.dist_dir。"""
|
|
154
|
+
if importlib.util.find_spec("build") is None:
|
|
155
|
+
raise RuntimeError("未安装 build,请先执行 `python3 -m pip install build`")
|
|
156
|
+
|
|
157
|
+
plan.dist_dir.mkdir(parents=True, exist_ok=True)
|
|
158
|
+
command = [
|
|
159
|
+
sys.executable,
|
|
160
|
+
"-m",
|
|
161
|
+
"build",
|
|
162
|
+
"--outdir",
|
|
163
|
+
str(plan.dist_dir),
|
|
164
|
+
str(project_root),
|
|
165
|
+
]
|
|
166
|
+
subprocess.run(command, check=True, cwd=project_root)
|
|
167
|
+
if not any(plan.dist_dir.glob("*.whl")):
|
|
168
|
+
raise RuntimeError(f"分发文件构建失败:{plan.dist_dir}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _package_init_path(project_root: Path, config: BuildConfig) -> Path:
|
|
172
|
+
"""根据构建入口推导包根目录下的 __init__.py 路径。"""
|
|
173
|
+
entry = config["entry"]
|
|
174
|
+
package = entry.split("/__main__.py", 1)[0] if entry.endswith("/__main__.py") else entry.rsplit("/", 1)[0]
|
|
175
|
+
return project_root / package / "__init__.py"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _write_package_version(project_root: Path, config: BuildConfig, version: str) -> None:
|
|
179
|
+
"""把本次构建版本写入包 __init__.py 的 __version__,供打包后运行时读取。"""
|
|
180
|
+
path = _package_init_path(project_root, config)
|
|
181
|
+
if not path.is_file():
|
|
182
|
+
raise FileNotFoundError(f"包入口文件不存在:{path}")
|
|
183
|
+
text = path.read_text(encoding="utf-8")
|
|
184
|
+
pattern = re.compile(r'(?m)^(__version__\s*=\s*")([^"]*)("\s*)$')
|
|
185
|
+
if not pattern.search(text):
|
|
186
|
+
raise RuntimeError(f"{path} 缺少 __version__ 定义,无法写入版本号")
|
|
187
|
+
synced = pattern.sub(lambda m: f'{m.group(1)}{version}{m.group(3)}', text)
|
|
188
|
+
if synced != text:
|
|
189
|
+
path.write_text(synced, encoding="utf-8")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _sync_pyproject_version(project_root: Path, version: str) -> None:
|
|
193
|
+
"""把目标版本同步到 pyproject.toml,保证 wheel 版本号正确。"""
|
|
194
|
+
path = project_root / "pyproject.toml"
|
|
195
|
+
if not path.is_file():
|
|
196
|
+
return
|
|
197
|
+
text = path.read_text(encoding="utf-8")
|
|
198
|
+
pattern = re.compile(r'(?m)^(\s*version\s*=\s*")([^"]*)("\s*)$')
|
|
199
|
+
if not pattern.search(text):
|
|
200
|
+
return # 静态版本号缺失时跳过(可能用了动态版本,不强行改写)
|
|
201
|
+
synced = pattern.sub(lambda m: f'{m.group(1)}{version}{m.group(3)}', text)
|
|
202
|
+
if synced != text:
|
|
203
|
+
path.write_text(synced, encoding="utf-8")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _write_version_and_build(project_root: Path, version: str, build: int) -> None:
|
|
207
|
+
"""更新 build.config.json 中的 version 与 build 字段。"""
|
|
208
|
+
path = config_path(project_root)
|
|
209
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
210
|
+
payload["version"] = version
|
|
211
|
+
payload["build"] = build
|
|
212
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _write_build_number(project_root: Path, build_number: int) -> None:
|
|
216
|
+
"""只更新 build.config.json 中的 build 字段。"""
|
|
217
|
+
path = config_path(project_root)
|
|
218
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
219
|
+
payload["build"] = build_number
|
|
220
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|