awcs-scripts 1.0.1__tar.gz → 1.0.2__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.
@@ -0,0 +1,42 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "release",
6
+ "type": "shell",
7
+ "command": "python",
8
+ "args": ["scripts/release.py", "${input:releasePart}"],
9
+ "options": {
10
+ "cwd": "${workspaceFolder}"
11
+ },
12
+ "presentation": {
13
+ "reveal": "always",
14
+ "panel": "shared"
15
+ },
16
+ "problemMatcher": []
17
+ },
18
+ {
19
+ "label": "release (dry-run)",
20
+ "type": "shell",
21
+ "command": "python",
22
+ "args": ["scripts/release.py", "--dry-run"],
23
+ "options": {
24
+ "cwd": "${workspaceFolder}"
25
+ },
26
+ "presentation": {
27
+ "reveal": "always",
28
+ "panel": "shared"
29
+ },
30
+ "problemMatcher": []
31
+ }
32
+ ],
33
+ "inputs": [
34
+ {
35
+ "id": "releasePart",
36
+ "type": "pickString",
37
+ "description": "Which part to bump",
38
+ "options": ["patch", "minor", "major"],
39
+ "default": "patch"
40
+ }
41
+ ]
42
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: awcs-scripts
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Command line scripts for WCS maps and the ARMS API
5
5
  Project-URL: Repository, https://gitlab.sprydev.ru/automacon/agv/wcs/awcs_scripts
6
6
  Author: AWCS
@@ -17,25 +17,9 @@ Description-Content-Type: text/markdown
17
17
 
18
18
  Скрипты для карт WCS и части ARMS API. Пакет называется `awcs-scripts`, команда после установки — `awcs`.
19
19
 
20
- ## Публикация в PyPI
20
+ ## Использование
21
21
 
22
- Выкладка происходит только по тегу `vX.Y.Z`. Обычный push и merge request пакет не публикуют: CI прогоняет тесты и собирает wheel. Версию берёт джоба `publish` из тега (`v0.1.0` → `0.1.0`), поле `version` в `pyproject.toml` на публикацию не влияет.
23
-
24
- Токен в репозиторий не входит. Его нужно завести один раз:
25
-
26
- 1. На [pypi.org](https://pypi.org) создать API-токен с правом загрузки. Для первой выкладки имя `awcs-scripts` должно быть свободно: проект создаётся при первой успешной загрузке. До этого нужен токен уровня аккаунта, после первой публикации его можно ограничить этим проектом.
27
- 2. В GitLab, в CI/CD-переменных этого репозитория, добавить `UV_PUBLISH_TOKEN` со значением токена и включить **Masked**. **Protected** включать только если теги `v*` тоже protected: иначе на теге переменная в джобу не попадёт и `uv publish` упадёт без токена.
28
-
29
- Тег и отправка:
30
-
31
- ```
32
- git tag v0.1.0
33
- git push origin v0.1.0
34
- ```
35
-
36
- Репозиторий закрытый, а `uv publish` пишет на публичный PyPI. После зелёного пайплайна wheel доступен по имени пакета.
37
-
38
- ## Установка
22
+ ### Установка
39
23
 
40
24
  ```
41
25
  uv tool install awcs-scripts
@@ -53,30 +37,37 @@ uvx --from git+https://gitlab.sprydev.ru/automacon/agv/wcs/awcs_scripts.git awcs
53
37
  uv tool install .
54
38
  ```
55
39
 
56
- После этого команда `awcs` доступна в системе. Для разработки в том же клоне:
40
+ После этого команда `awcs` доступна в системе.
41
+
42
+ Разовый запуск после публикации:
57
43
 
58
44
  ```
59
- uv sync
60
- uv run awcs help
61
- uv build
45
+ uvx --from awcs-scripts awcs help
46
+ uvx --from awcs-scripts awcs mrmap-to-raster path/to/map.mrmap --resolution 0.05 --out-dir path/to/out
47
+ uvx --from awcs-scripts awcs users me
62
48
  ```
63
49
 
64
- ## Удаление
50
+ ### Команды
65
51
 
66
- Снимает команду `awcs`, поставленную через `uv tool install` из клона или с PyPI:
52
+ #### Основное
67
53
 
68
54
  ```
69
- uv tool uninstall awcs-scripts
55
+ awcs help
56
+ awcs version
70
57
  ```
71
58
 
72
- Профиль с адресом и ключом эта команда не трогает. Его файл лежит по `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Чтобы убрать и ключ, удалите этот файл.
73
-
74
- ## Команды
59
+ #### Карты
75
60
 
76
61
  ```
77
- awcs help
78
62
  awcs mrmap-to-raster path/to/map.mrmap --out-dir path/to/out
79
63
  awcs mrmap-to-bg path/to/map.mrmap path/to/layout.lif --layout_id layoutId --resolution 0.05 --output path/to/layout.out.lif
64
+ ```
65
+
66
+ `awcs mrmap-to-raster` пишет PNG, GeoTIFF и JSON. `--resolution` задаёт метры на пиксель, по умолчанию берётся `resolution_2d` из карты. `awcs mrmap-to-bg` подставляет этот растр в `backgroundImage` одного layout. Без `--output` исходный `.lif` перезаписывается.
67
+
68
+ #### API
69
+
70
+ ```
80
71
  awcs login --url https://dev.wcs.automacon.ru --apikey <apikey>
81
72
  awcs users list
82
73
  awcs users me
@@ -90,20 +81,65 @@ awcs projects create --lif_json path/to/layout.lif -m MAIRUI -v 1.0.0 --desc "li
90
81
  awcs projects activate 12
91
82
  ```
92
83
 
93
- Разовый запуск после публикации:
84
+ `awcs login` проверяет ключ через `GET /users/me` и только после ответа 200 сохраняет origin и ключ. Профиль лежит вне репозитория: путь из `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Ключ в вывод не печатается. Клиент сам дописывает `/api/v1` и шлёт заголовок `X-Api-Key`.
85
+
86
+ У списков есть `--page`, `--per_page`, `--filter`, `--sort_by`. `awcs missions create` принимает `--template_id` или `--template_code` и необязательный `--params`. `awcs mission_templates execute` отправляет `--params` плоским JSON-объектом. `awcs projects create` загружает LIF как `multipart/form-data`: `-m` это `--manufacturer`, `-v` и `--version` это `--project_version`, `--desc` это `--description`, `--sync` это `--synchronous`.
87
+
88
+ ### Удаление
89
+
90
+ Снимает команду `awcs`, поставленную через `uv tool install` из клона или с PyPI:
94
91
 
95
92
  ```
96
- uvx --from awcs-scripts awcs help
97
- uvx --from awcs-scripts awcs mrmap-to-raster path/to/map.mrmap --resolution 0.05 --out-dir path/to/out
98
- uvx --from awcs-scripts awcs users me
93
+ uv tool uninstall awcs-scripts
99
94
  ```
100
95
 
101
- `awcs mrmap-to-raster` пишет PNG, GeoTIFF и JSON. `--resolution` задаёт метры на пиксель, по умолчанию берётся `resolution_2d` из карты. `awcs mrmap-to-bg` подставляет этот растр в `backgroundImage` одного layout. Без `--output` исходный `.lif` перезаписывается.
96
+ Профиль с адресом и ключом эта команда не трогает. Его файл лежит по `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Чтобы убрать и ключ, удалите этот файл.
102
97
 
103
- `awcs login` проверяет ключ через `GET /users/me` и только после ответа 200 сохраняет origin и ключ. Профиль лежит вне репозитория: путь из `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Ключ в вывод не печатается. Клиент сам дописывает `/api/v1` и шлёт заголовок `X-Api-Key`.
98
+ ## Разработка
104
99
 
105
- У списков есть `--page`, `--per_page`, `--filter`, `--sort_by`. `awcs missions create` принимает `--template_id` или `--template_code` и необязательный `--params`. `awcs mission_templates execute` отправляет `--params` плоским JSON-объектом. `awcs projects create` загружает LIF как `multipart/form-data`: `-m` это `--manufacturer`, `-v` и `--version` это `--project_version`, `--desc` это `--description`, `--sync` это `--synchronous`.
100
+ ### Публикация в PyPI
106
101
 
107
- ## Разработка
102
+ Выкладка происходит только по тегу `vX.Y.Z`. Обычный push и merge request пакет не публикуют: CI прогоняет тесты и собирает wheel. Как сменить номер версии — в разделе ниже.
103
+
104
+ Токен в репозиторий не входит. Его нужно завести один раз:
105
+
106
+ 1. На [pypi.org](https://pypi.org) создать API-токен с правом загрузки. Для первой выкладки имя `awcs-scripts` должно быть свободно: проект создаётся при первой успешной загрузке. До этого нужен токен уровня аккаунта, после первой публикации его можно ограничить этим проектом.
107
+ 2. В GitLab, в CI/CD-переменных этого репозитория, добавить `UV_PUBLISH_TOKEN` со значением токена и включить **Masked**. **Protected** включать только если теги `v*` тоже protected: иначе на теге переменная в джобу не попадёт и `uv publish` упадёт без токена.
108
+
109
+ Репозиторий закрытый, а `uv publish` пишет на публичный PyPI. После зелёного пайплайна wheel доступен по имени пакета.
110
+
111
+ ### Обновление версии
112
+
113
+ Выпуск — одна команда из корня репозитория. По умолчанию она поднимает patch:
114
+
115
+ ```
116
+ python scripts/release.py
117
+ ```
118
+
119
+ `minor` и `major` передаются аргументом: `python scripts/release.py minor`. Команда берёт последний тег `vX.Y.Z`, при другом номере в `pyproject.toml` сначала записывает туда версию тега, затем выполняет `uv version --bump`. Тот же номер попадает в `__version__` в `src/awcs_scripts/__init__.py` и, если uv его перепишет, в `uv.lock`. Дальше идут коммит `Release X.Y.Z.`, тег `vX.Y.Z`, `git push origin HEAD` и `git push origin vX.Y.Z`.
120
+
121
+ Посмотреть следующий номер:
122
+
123
+ ```
124
+ python scripts/release.py --dry-run
125
+ ```
126
+
127
+ В Cursor это задачи `release` и `release (dry-run)`: Command Palette → Tasks: Run Task. Команда выпускает из чистого дерева на ветке. Тег должен совпадать с `^v\d+\.\d+\.\d+$`: по нему джоба `publish` подставляет номер командой `uv version` и собирает wheel. PyPI не принимает повторную загрузку той же версии.
128
+
129
+ Уже установленную команду обновляют после зелёного пайплайна тега:
130
+
131
+ ```
132
+ uv tool upgrade awcs-scripts
133
+ ```
134
+
135
+ ### В клоне
136
+
137
+ Для разработки в том же клоне:
138
+
139
+ ```
140
+ uv sync
141
+ uv run awcs help
142
+ uv build
143
+ ```
108
144
 
109
145
  Карта живёт в `src/awcs_scripts/mrmap/`. Имена файлов совпадают с подкомандами: `mrmap-to-raster.py` и `mrmap-to-bg.py`. Будущие shell-скрипты кладутся в `bash/` и тоже вызываются только через `awcs`.
@@ -2,25 +2,9 @@
2
2
 
3
3
  Скрипты для карт WCS и части ARMS API. Пакет называется `awcs-scripts`, команда после установки — `awcs`.
4
4
 
5
- ## Публикация в PyPI
5
+ ## Использование
6
6
 
7
- Выкладка происходит только по тегу `vX.Y.Z`. Обычный push и merge request пакет не публикуют: CI прогоняет тесты и собирает wheel. Версию берёт джоба `publish` из тега (`v0.1.0` → `0.1.0`), поле `version` в `pyproject.toml` на публикацию не влияет.
8
-
9
- Токен в репозиторий не входит. Его нужно завести один раз:
10
-
11
- 1. На [pypi.org](https://pypi.org) создать API-токен с правом загрузки. Для первой выкладки имя `awcs-scripts` должно быть свободно: проект создаётся при первой успешной загрузке. До этого нужен токен уровня аккаунта, после первой публикации его можно ограничить этим проектом.
12
- 2. В GitLab, в CI/CD-переменных этого репозитория, добавить `UV_PUBLISH_TOKEN` со значением токена и включить **Masked**. **Protected** включать только если теги `v*` тоже protected: иначе на теге переменная в джобу не попадёт и `uv publish` упадёт без токена.
13
-
14
- Тег и отправка:
15
-
16
- ```
17
- git tag v0.1.0
18
- git push origin v0.1.0
19
- ```
20
-
21
- Репозиторий закрытый, а `uv publish` пишет на публичный PyPI. После зелёного пайплайна wheel доступен по имени пакета.
22
-
23
- ## Установка
7
+ ### Установка
24
8
 
25
9
  ```
26
10
  uv tool install awcs-scripts
@@ -38,30 +22,37 @@ uvx --from git+https://gitlab.sprydev.ru/automacon/agv/wcs/awcs_scripts.git awcs
38
22
  uv tool install .
39
23
  ```
40
24
 
41
- После этого команда `awcs` доступна в системе. Для разработки в том же клоне:
25
+ После этого команда `awcs` доступна в системе.
26
+
27
+ Разовый запуск после публикации:
42
28
 
43
29
  ```
44
- uv sync
45
- uv run awcs help
46
- uv build
30
+ uvx --from awcs-scripts awcs help
31
+ uvx --from awcs-scripts awcs mrmap-to-raster path/to/map.mrmap --resolution 0.05 --out-dir path/to/out
32
+ uvx --from awcs-scripts awcs users me
47
33
  ```
48
34
 
49
- ## Удаление
35
+ ### Команды
50
36
 
51
- Снимает команду `awcs`, поставленную через `uv tool install` из клона или с PyPI:
37
+ #### Основное
52
38
 
53
39
  ```
54
- uv tool uninstall awcs-scripts
40
+ awcs help
41
+ awcs version
55
42
  ```
56
43
 
57
- Профиль с адресом и ключом эта команда не трогает. Его файл лежит по `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Чтобы убрать и ключ, удалите этот файл.
58
-
59
- ## Команды
44
+ #### Карты
60
45
 
61
46
  ```
62
- awcs help
63
47
  awcs mrmap-to-raster path/to/map.mrmap --out-dir path/to/out
64
48
  awcs mrmap-to-bg path/to/map.mrmap path/to/layout.lif --layout_id layoutId --resolution 0.05 --output path/to/layout.out.lif
49
+ ```
50
+
51
+ `awcs mrmap-to-raster` пишет PNG, GeoTIFF и JSON. `--resolution` задаёт метры на пиксель, по умолчанию берётся `resolution_2d` из карты. `awcs mrmap-to-bg` подставляет этот растр в `backgroundImage` одного layout. Без `--output` исходный `.lif` перезаписывается.
52
+
53
+ #### API
54
+
55
+ ```
65
56
  awcs login --url https://dev.wcs.automacon.ru --apikey <apikey>
66
57
  awcs users list
67
58
  awcs users me
@@ -75,20 +66,65 @@ awcs projects create --lif_json path/to/layout.lif -m MAIRUI -v 1.0.0 --desc "li
75
66
  awcs projects activate 12
76
67
  ```
77
68
 
78
- Разовый запуск после публикации:
69
+ `awcs login` проверяет ключ через `GET /users/me` и только после ответа 200 сохраняет origin и ключ. Профиль лежит вне репозитория: путь из `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Ключ в вывод не печатается. Клиент сам дописывает `/api/v1` и шлёт заголовок `X-Api-Key`.
70
+
71
+ У списков есть `--page`, `--per_page`, `--filter`, `--sort_by`. `awcs missions create` принимает `--template_id` или `--template_code` и необязательный `--params`. `awcs mission_templates execute` отправляет `--params` плоским JSON-объектом. `awcs projects create` загружает LIF как `multipart/form-data`: `-m` это `--manufacturer`, `-v` и `--version` это `--project_version`, `--desc` это `--description`, `--sync` это `--synchronous`.
72
+
73
+ ### Удаление
74
+
75
+ Снимает команду `awcs`, поставленную через `uv tool install` из клона или с PyPI:
79
76
 
80
77
  ```
81
- uvx --from awcs-scripts awcs help
82
- uvx --from awcs-scripts awcs mrmap-to-raster path/to/map.mrmap --resolution 0.05 --out-dir path/to/out
83
- uvx --from awcs-scripts awcs users me
78
+ uv tool uninstall awcs-scripts
84
79
  ```
85
80
 
86
- `awcs mrmap-to-raster` пишет PNG, GeoTIFF и JSON. `--resolution` задаёт метры на пиксель, по умолчанию берётся `resolution_2d` из карты. `awcs mrmap-to-bg` подставляет этот растр в `backgroundImage` одного layout. Без `--output` исходный `.lif` перезаписывается.
81
+ Профиль с адресом и ключом эта команда не трогает. Его файл лежит по `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Чтобы убрать и ключ, удалите этот файл.
87
82
 
88
- `awcs login` проверяет ключ через `GET /users/me` и только после ответа 200 сохраняет origin и ключ. Профиль лежит вне репозитория: путь из `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Ключ в вывод не печатается. Клиент сам дописывает `/api/v1` и шлёт заголовок `X-Api-Key`.
83
+ ## Разработка
89
84
 
90
- У списков есть `--page`, `--per_page`, `--filter`, `--sort_by`. `awcs missions create` принимает `--template_id` или `--template_code` и необязательный `--params`. `awcs mission_templates execute` отправляет `--params` плоским JSON-объектом. `awcs projects create` загружает LIF как `multipart/form-data`: `-m` это `--manufacturer`, `-v` и `--version` это `--project_version`, `--desc` это `--description`, `--sync` это `--synchronous`.
85
+ ### Публикация в PyPI
91
86
 
92
- ## Разработка
87
+ Выкладка происходит только по тегу `vX.Y.Z`. Обычный push и merge request пакет не публикуют: CI прогоняет тесты и собирает wheel. Как сменить номер версии — в разделе ниже.
88
+
89
+ Токен в репозиторий не входит. Его нужно завести один раз:
90
+
91
+ 1. На [pypi.org](https://pypi.org) создать API-токен с правом загрузки. Для первой выкладки имя `awcs-scripts` должно быть свободно: проект создаётся при первой успешной загрузке. До этого нужен токен уровня аккаунта, после первой публикации его можно ограничить этим проектом.
92
+ 2. В GitLab, в CI/CD-переменных этого репозитория, добавить `UV_PUBLISH_TOKEN` со значением токена и включить **Masked**. **Protected** включать только если теги `v*` тоже protected: иначе на теге переменная в джобу не попадёт и `uv publish` упадёт без токена.
93
+
94
+ Репозиторий закрытый, а `uv publish` пишет на публичный PyPI. После зелёного пайплайна wheel доступен по имени пакета.
95
+
96
+ ### Обновление версии
97
+
98
+ Выпуск — одна команда из корня репозитория. По умолчанию она поднимает patch:
99
+
100
+ ```
101
+ python scripts/release.py
102
+ ```
103
+
104
+ `minor` и `major` передаются аргументом: `python scripts/release.py minor`. Команда берёт последний тег `vX.Y.Z`, при другом номере в `pyproject.toml` сначала записывает туда версию тега, затем выполняет `uv version --bump`. Тот же номер попадает в `__version__` в `src/awcs_scripts/__init__.py` и, если uv его перепишет, в `uv.lock`. Дальше идут коммит `Release X.Y.Z.`, тег `vX.Y.Z`, `git push origin HEAD` и `git push origin vX.Y.Z`.
105
+
106
+ Посмотреть следующий номер:
107
+
108
+ ```
109
+ python scripts/release.py --dry-run
110
+ ```
111
+
112
+ В Cursor это задачи `release` и `release (dry-run)`: Command Palette → Tasks: Run Task. Команда выпускает из чистого дерева на ветке. Тег должен совпадать с `^v\d+\.\d+\.\d+$`: по нему джоба `publish` подставляет номер командой `uv version` и собирает wheel. PyPI не принимает повторную загрузку той же версии.
113
+
114
+ Уже установленную команду обновляют после зелёного пайплайна тега:
115
+
116
+ ```
117
+ uv tool upgrade awcs-scripts
118
+ ```
119
+
120
+ ### В клоне
121
+
122
+ Для разработки в том же клоне:
123
+
124
+ ```
125
+ uv sync
126
+ uv run awcs help
127
+ uv build
128
+ ```
93
129
 
94
130
  Карта живёт в `src/awcs_scripts/mrmap/`. Имена файлов совпадают с подкомандами: `mrmap-to-raster.py` и `mrmap-to-bg.py`. Будущие shell-скрипты кладутся в `bash/` и тоже вызываются только через `awcs`.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "awcs-scripts"
3
- version = "1.0.1"
3
+ version = "1.0.2"
4
4
  description = "Command line scripts for WCS maps and the ARMS API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -0,0 +1,141 @@
1
+ """Bump the package version, commit it, tag vX.Y.Z, and push the branch and tag."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import re
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ ROOT = Path(__file__).resolve().parent.parent
12
+ PYPROJECT = ROOT / "pyproject.toml"
13
+ INIT = ROOT / "src" / "awcs_scripts" / "__init__.py"
14
+ VERSION_FILES = (
15
+ "pyproject.toml",
16
+ "src/awcs_scripts/__init__.py",
17
+ "uv.lock",
18
+ )
19
+ TAG = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$")
20
+ PARTS = ("major", "minor", "patch")
21
+
22
+
23
+ def _fail(message: str) -> None:
24
+ print(message, file=sys.stderr)
25
+ raise SystemExit(1)
26
+
27
+
28
+ def _run(cmd: list[str]) -> subprocess.CompletedProcess[str]:
29
+ result = subprocess.run(cmd, cwd=ROOT, text=True, capture_output=True, check=False)
30
+ if result.returncode != 0:
31
+ detail = (result.stderr or result.stdout).strip()
32
+ _fail(detail or f"command failed: {' '.join(cmd)}")
33
+ return result
34
+
35
+
36
+ def _git(*args: str) -> str:
37
+ return _run(["git", *args]).stdout.strip()
38
+
39
+
40
+ def _pyproject_version() -> str:
41
+ text = PYPROJECT.read_text(encoding="utf-8")
42
+ match = re.search(r'(?m)^version = "(\d+\.\d+\.\d+)"\s*$', text)
43
+ if match is None:
44
+ _fail("version in pyproject.toml is not X.Y.Z")
45
+ return match.group(1)
46
+
47
+
48
+ def _latest_tag_version() -> str | None:
49
+ for line in _git("tag", "-l", "--sort=-v:refname").splitlines():
50
+ if TAG.fullmatch(line.strip()):
51
+ return line.strip()[1:]
52
+ return None
53
+
54
+
55
+ def _bump(version: str, part: str) -> str:
56
+ major, minor, patch = (int(piece) for piece in version.split("."))
57
+ if part == "major":
58
+ return f"{major + 1}.0.0"
59
+ if part == "minor":
60
+ return f"{major}.{minor + 1}.0"
61
+ return f"{major}.{minor}.{patch + 1}"
62
+
63
+
64
+ def _status_paths() -> list[str]:
65
+ # Keep the leading space in porcelain lines. strip() would turn
66
+ # " M pyproject.toml" into "M pyproject.toml" and drop the first letter.
67
+ paths: list[str] = []
68
+ for line in _run(["git", "status", "--porcelain"]).stdout.splitlines():
69
+ path = line[3:]
70
+ if " -> " in path:
71
+ path = path.split(" -> ", 1)[1]
72
+ paths.append(path.strip().strip('"'))
73
+ return paths
74
+
75
+
76
+ def _require_branch() -> None:
77
+ if _git("rev-parse", "--abbrev-ref", "HEAD") == "HEAD":
78
+ _fail("HEAD is detached; checkout a branch before releasing")
79
+
80
+
81
+ def _require_clean() -> None:
82
+ if _status_paths():
83
+ _fail("working tree is not clean; commit other changes before releasing")
84
+
85
+
86
+ def _write_version(version: str) -> None:
87
+ with INIT.open(encoding="utf-8", newline="") as handle:
88
+ text = handle.read()
89
+ updated, count = re.subn(
90
+ r'^__version__ = "[^"]*"',
91
+ f'__version__ = "{version}"',
92
+ text,
93
+ count=1,
94
+ flags=re.M,
95
+ )
96
+ if count != 1:
97
+ _fail("__version__ not found in src/awcs_scripts/__init__.py")
98
+ with INIT.open("w", encoding="utf-8", newline="") as handle:
99
+ handle.write(updated)
100
+
101
+
102
+ def main(argv: list[str] | None = None) -> None:
103
+ parser = argparse.ArgumentParser(description="Bump, tag, and push a release.")
104
+ parser.add_argument("part", nargs="?", default="patch", choices=PARTS)
105
+ parser.add_argument("--dry-run", action="store_true", help="Print the next version and write nothing")
106
+ args = parser.parse_args(argv)
107
+
108
+ base = _latest_tag_version() or _pyproject_version()
109
+ version = _bump(base, args.part)
110
+ if args.dry_run:
111
+ print(version)
112
+ return
113
+
114
+ _require_branch()
115
+ _require_clean()
116
+ if _git("tag", "-l", f"v{version}"):
117
+ _fail(f"tag v{version} already exists")
118
+
119
+ if _pyproject_version() != base:
120
+ _run(["uv", "version", base, "--no-sync"])
121
+ bumped = _run(["uv", "version", "--bump", args.part, "--short", "--no-sync"]).stdout.strip()
122
+ if bumped != version:
123
+ _fail(f"uv version returned {bumped}, expected {version}")
124
+ _write_version(version)
125
+
126
+ unexpected = [path for path in _status_paths() if path not in VERSION_FILES]
127
+ if unexpected:
128
+ _fail("unexpected changes: " + ", ".join(unexpected))
129
+ if not _status_paths():
130
+ _fail("version did not change")
131
+
132
+ _run(["git", "add", "--", *VERSION_FILES])
133
+ _run(["git", "commit", "-m", f"Release {version}."])
134
+ _run(["git", "tag", f"v{version}"])
135
+ _run(["git", "push", "origin", "HEAD"])
136
+ _run(["git", "push", "origin", f"v{version}"])
137
+ print(f"v{version}")
138
+
139
+
140
+ if __name__ == "__main__":
141
+ main()
@@ -1,3 +1,3 @@
1
1
  """Scripts for WCS maps and the ARMS API."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "1.0.2"
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import importlib.metadata
6
7
  import importlib.util
7
8
  import json
8
9
  import sys
@@ -28,6 +29,10 @@ def _load_tool(filename: str):
28
29
  return module
29
30
 
30
31
 
32
+ def _cmd_version(_args: argparse.Namespace) -> None:
33
+ print(importlib.metadata.version("awcs-scripts"))
34
+
35
+
31
36
  def _fail(message: str) -> None:
32
37
  print(message, file=sys.stderr)
33
38
  raise SystemExit(1)
@@ -172,6 +177,9 @@ def build_parser() -> argparse.ArgumentParser:
172
177
  help_parser = sub.add_parser("help", help="Show this help")
173
178
  help_parser.set_defaults(func=lambda _args: parser.print_help())
174
179
 
180
+ version_parser = sub.add_parser("version", help="Print the package version")
181
+ version_parser.set_defaults(func=_cmd_version)
182
+
175
183
  _add_tool(sub, "mrmap-to-raster", "Rasterize a .mrmap to PNG, GeoTIFF, and JSON")
176
184
  _add_tool(sub, "mrmap-to-bg", "Set a .mrmap raster as a LIF layout background")
177
185
 
@@ -1,3 +1,5 @@
1
+ import importlib.metadata
2
+
1
3
  import pytest
2
4
 
3
5
  from awcs_scripts.cli import main
@@ -10,6 +12,13 @@ def test_help_lists_commands(capsys):
10
12
  assert "mrmap-to-bg" in text
11
13
  assert "projects" in text
12
14
  assert "login" in text
15
+ assert "version" in text
16
+
17
+
18
+ def test_version(capsys):
19
+ main(["version"])
20
+ text = capsys.readouterr().out.strip()
21
+ assert text == importlib.metadata.version("awcs-scripts")
13
22
 
14
23
 
15
24
  def test_help_command(capsys):
@@ -17,6 +26,7 @@ def test_help_command(capsys):
17
26
  text = capsys.readouterr().out
18
27
  assert "mission_templates" in text
19
28
  assert "users" in text
29
+ assert "version" in text
20
30
 
21
31
 
22
32
  def test_raster_help():
@@ -23,7 +23,7 @@ wheels = [
23
23
 
24
24
  [[package]]
25
25
  name = "awcs-scripts"
26
- version = "1.0.1"
26
+ version = "1.0.2"
27
27
  source = { editable = "." }
28
28
  dependencies = [
29
29
  { name = "httpx" },
File without changes
File without changes