fast-dev-cli 0.16.2__tar.gz → 0.17.0__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.
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/PKG-INFO +1 -1
- fast_dev_cli-0.17.0/fast_dev_cli/__init__.py +1 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/fast_dev_cli/cli.py +117 -36
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/pyproject.toml +19 -23
- fast_dev_cli-0.17.0/scripts/deps.py +156 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_bump.py +32 -48
- fast_dev_cli-0.17.0/tests/test_exec.py +41 -0
- fast_dev_cli-0.16.2/fast_dev_cli/__init__.py +0 -1
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/LICENSE +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/README.md +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/fast_dev_cli/__main__.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/fast_dev_cli/py.typed +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/pdm_build.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/scripts/check.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/scripts/format.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/scripts/test.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/__init__.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/conftest.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_fast_test.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_functions.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_lint.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_poetry_version_plugin.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_runserver.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_sync.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_tag.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_upgrade.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_upload.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/test_version.py +0 -0
- {fast_dev_cli-0.16.2 → fast_dev_cli-0.17.0}/tests/utils.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.17.0"
|
|
@@ -16,6 +16,7 @@ from typing import (
|
|
|
16
16
|
Optional,
|
|
17
17
|
cast,
|
|
18
18
|
get_args,
|
|
19
|
+
overload,
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
import typer
|
|
@@ -64,7 +65,10 @@ ToolOption = Option(
|
|
|
64
65
|
)
|
|
65
66
|
|
|
66
67
|
|
|
67
|
-
class
|
|
68
|
+
class FastDevCliError(Exception): ...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ShellCommandError(FastDevCliError): ...
|
|
68
72
|
|
|
69
73
|
|
|
70
74
|
def poetry_module_name(name: str) -> str:
|
|
@@ -111,7 +115,10 @@ def run_and_echo(
|
|
|
111
115
|
echo(f"--> {cmd}")
|
|
112
116
|
if dry:
|
|
113
117
|
return 0
|
|
114
|
-
|
|
118
|
+
command: list[str] | str = cmd
|
|
119
|
+
if "shell" not in kw and not (set(cmd) & {"|", ">"}):
|
|
120
|
+
command = shlex.split(cmd)
|
|
121
|
+
return _run_shell(command, **kw).returncode
|
|
115
122
|
|
|
116
123
|
|
|
117
124
|
def check_call(cmd: str) -> bool:
|
|
@@ -177,33 +184,61 @@ def read_version_from_file(
|
|
|
177
184
|
return "0.0.0"
|
|
178
185
|
|
|
179
186
|
|
|
187
|
+
@overload
|
|
180
188
|
def get_current_version(
|
|
181
189
|
verbose: bool = False,
|
|
182
190
|
is_poetry: bool | None = None,
|
|
183
191
|
package_name: str | None = None,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if not is_poetry:
|
|
188
|
-
work_dir = None
|
|
189
|
-
if package_name is None:
|
|
190
|
-
work_dir = Project.get_work_dir()
|
|
191
|
-
package_name = re.sub(r"[- ]", "_", work_dir.name)
|
|
192
|
-
try:
|
|
193
|
-
installed_version = importlib_metadata.version(package_name)
|
|
194
|
-
except importlib_metadata.PackageNotFoundError:
|
|
195
|
-
...
|
|
196
|
-
else:
|
|
197
|
-
if installed_version != "0.0.0":
|
|
198
|
-
return installed_version
|
|
199
|
-
return read_version_from_file(package_name, work_dir)
|
|
192
|
+
*,
|
|
193
|
+
check_version: Literal[False] = False,
|
|
194
|
+
) -> str: ...
|
|
200
195
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
196
|
+
|
|
197
|
+
@overload
|
|
198
|
+
def get_current_version(
|
|
199
|
+
verbose: bool = False,
|
|
200
|
+
is_poetry: bool | None = None,
|
|
201
|
+
package_name: str | None = None,
|
|
202
|
+
*,
|
|
203
|
+
check_version: Literal[True] = True,
|
|
204
|
+
) -> tuple[bool, str]: ...
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def get_current_version(
|
|
208
|
+
verbose: bool = False,
|
|
209
|
+
is_poetry: bool | None = None,
|
|
210
|
+
package_name: str | None = None,
|
|
211
|
+
*,
|
|
212
|
+
check_version: bool = False,
|
|
213
|
+
) -> str | tuple[bool, str]:
|
|
214
|
+
if is_poetry is True or Project.manage_by_poetry():
|
|
215
|
+
cmd = ["poetry", "version", "-s"]
|
|
216
|
+
if verbose:
|
|
217
|
+
echo(f"--> {' '.join(cmd)}")
|
|
218
|
+
if out := capture_cmd_output(cmd, raises=True):
|
|
219
|
+
out = out.splitlines()[-1].strip().split()[-1]
|
|
220
|
+
if check_version:
|
|
221
|
+
return True, out
|
|
222
|
+
return out
|
|
223
|
+
toml_text = work_dir = None
|
|
224
|
+
if package_name is None:
|
|
225
|
+
work_dir = Project.get_work_dir()
|
|
226
|
+
toml_text = Project.load_toml_text()
|
|
227
|
+
doc = tomllib.loads(toml_text)
|
|
228
|
+
project_name = doc.get("project", {}).get("name", work_dir.name)
|
|
229
|
+
package_name = re.sub(r"[- ]", "_", project_name)
|
|
230
|
+
local_version = read_version_from_file(package_name, work_dir, toml_text)
|
|
231
|
+
try:
|
|
232
|
+
installed_version = importlib_metadata.version(package_name)
|
|
233
|
+
except importlib_metadata.PackageNotFoundError:
|
|
234
|
+
installed_version = ""
|
|
235
|
+
current_version = local_version or installed_version
|
|
236
|
+
if not current_version:
|
|
237
|
+
raise FastDevCliError(f"Failed to get current version of {package_name!r}")
|
|
238
|
+
if check_version:
|
|
239
|
+
is_conflict = bool(local_version) and local_version != installed_version
|
|
240
|
+
return is_conflict, current_version
|
|
241
|
+
return current_version
|
|
207
242
|
|
|
208
243
|
|
|
209
244
|
def _ensure_bool(value: bool | OptionInfo) -> bool:
|
|
@@ -391,7 +426,9 @@ class BumpUp(DryRun):
|
|
|
391
426
|
raise Exit(1) from e
|
|
392
427
|
|
|
393
428
|
def gen(self: Self) -> str:
|
|
394
|
-
_version = get_current_version()
|
|
429
|
+
should_sync, _version = get_current_version(check_version=True)
|
|
430
|
+
if should_sync:
|
|
431
|
+
Project.sync_dependencies()
|
|
395
432
|
filename = self.filename
|
|
396
433
|
echo(f"Current version(@{filename}): {_version}")
|
|
397
434
|
if self.part:
|
|
@@ -413,8 +450,6 @@ class BumpUp(DryRun):
|
|
|
413
450
|
cmd += " && git push && git push --tags && git log -1"
|
|
414
451
|
else:
|
|
415
452
|
cmd += " --allow-dirty"
|
|
416
|
-
if Project.is_pdm_project():
|
|
417
|
-
cmd = "pdm sync --prod && " + cmd
|
|
418
453
|
return cmd
|
|
419
454
|
|
|
420
455
|
def run(self: Self) -> None:
|
|
@@ -462,6 +497,7 @@ class EnvError(Exception):
|
|
|
462
497
|
|
|
463
498
|
class Project:
|
|
464
499
|
path_depth = 5
|
|
500
|
+
_tool: ToolName | None = None
|
|
465
501
|
|
|
466
502
|
@staticmethod
|
|
467
503
|
def is_poetry_v2(text: str) -> bool:
|
|
@@ -492,7 +528,7 @@ class Project:
|
|
|
492
528
|
return d
|
|
493
529
|
if allow_cwd:
|
|
494
530
|
return cls.get_root_dir(cwd)
|
|
495
|
-
raise EnvError(f"{name} not found! Make sure this is a
|
|
531
|
+
raise EnvError(f"{name} not found! Make sure this is a python project.")
|
|
496
532
|
|
|
497
533
|
@classmethod
|
|
498
534
|
def load_toml_text(cls: type[Self], name: str = TOML_FILE) -> str:
|
|
@@ -500,22 +536,40 @@ class Project:
|
|
|
500
536
|
return toml_file.read_text("utf8")
|
|
501
537
|
|
|
502
538
|
@classmethod
|
|
503
|
-
def manage_by_poetry(cls: type[Self]) -> bool:
|
|
504
|
-
return cls.get_manage_tool() == "poetry"
|
|
539
|
+
def manage_by_poetry(cls: type[Self], cache: bool = False) -> bool:
|
|
540
|
+
return cls.get_manage_tool(cache=cache) == "poetry"
|
|
505
541
|
|
|
506
542
|
@classmethod
|
|
507
|
-
def get_manage_tool(cls: type[Self]) -> ToolName | None:
|
|
543
|
+
def get_manage_tool(cls: type[Self], cache: bool = False) -> ToolName | None:
|
|
544
|
+
if cache and cls._tool:
|
|
545
|
+
return cls._tool
|
|
508
546
|
try:
|
|
509
547
|
text = cls.load_toml_text()
|
|
510
548
|
except EnvError:
|
|
511
549
|
pass
|
|
512
550
|
else:
|
|
551
|
+
with contextlib.suppress(KeyError, tomllib.TOMLDecodeError):
|
|
552
|
+
doc = tomllib.loads(text)
|
|
553
|
+
backend = doc["build-system"]["build-backend"]
|
|
554
|
+
if "poetry" in backend:
|
|
555
|
+
cls._tool = "poetry"
|
|
556
|
+
return cls._tool
|
|
557
|
+
elif "pdm" in backend:
|
|
558
|
+
cls._tool = "pdm"
|
|
559
|
+
work_dir = cls.get_work_dir(allow_cwd=True)
|
|
560
|
+
if not Path(work_dir, "pdm.lock").exists() and (
|
|
561
|
+
"[tool.uv]" in text or Path(work_dir, "uv.lock").exists()
|
|
562
|
+
):
|
|
563
|
+
cls._tool = "uv"
|
|
564
|
+
return cls._tool
|
|
513
565
|
for name in get_args(ToolName):
|
|
514
566
|
if f"[tool.{name}]" in text:
|
|
515
|
-
|
|
567
|
+
cls._tool = cast(ToolName, name)
|
|
568
|
+
return cls._tool
|
|
516
569
|
# Poetry 2.0 default to not include the '[tool.poetry]' section
|
|
517
570
|
if cls.is_poetry_v2(text):
|
|
518
|
-
|
|
571
|
+
cls._tool = "poetry"
|
|
572
|
+
return cls._tool
|
|
519
573
|
return None
|
|
520
574
|
|
|
521
575
|
@staticmethod
|
|
@@ -531,14 +585,24 @@ class Project:
|
|
|
531
585
|
return root
|
|
532
586
|
|
|
533
587
|
@classmethod
|
|
534
|
-
def is_pdm_project(cls, strict: bool = True) -> bool:
|
|
535
|
-
if cls.get_manage_tool() != "pdm":
|
|
588
|
+
def is_pdm_project(cls, strict: bool = True, cache: bool = False) -> bool:
|
|
589
|
+
if cls.get_manage_tool(cache=cache) != "pdm":
|
|
536
590
|
return False
|
|
537
591
|
if strict:
|
|
538
592
|
lock_file = cls.get_work_dir() / "pdm.lock"
|
|
539
593
|
return lock_file.exists()
|
|
540
594
|
return True
|
|
541
595
|
|
|
596
|
+
@classmethod
|
|
597
|
+
def sync_dependencies(cls) -> None:
|
|
598
|
+
if cls.is_pdm_project():
|
|
599
|
+
cmd = "pdm sync --prod"
|
|
600
|
+
run_and_echo(cmd)
|
|
601
|
+
elif cls.manage_by_poetry(cache=True):
|
|
602
|
+
run_and_echo("poetry install --only=main")
|
|
603
|
+
elif cls.get_manage_tool(cache=True) == "uv":
|
|
604
|
+
run_and_echo("uv sync")
|
|
605
|
+
|
|
542
606
|
|
|
543
607
|
class ParseError(Exception):
|
|
544
608
|
"""Raise this if parse dependence line error"""
|
|
@@ -749,7 +813,9 @@ class GitTag(DryRun):
|
|
|
749
813
|
return "git push" in self.git_status
|
|
750
814
|
|
|
751
815
|
def gen(self: Self) -> str:
|
|
752
|
-
_version = get_current_version(verbose=False)
|
|
816
|
+
should_sync, _version = get_current_version(verbose=False, check_version=True)
|
|
817
|
+
if should_sync:
|
|
818
|
+
Project.sync_dependencies()
|
|
753
819
|
if self.has_v_prefix():
|
|
754
820
|
# Add `v` at prefix to compare with bumpversion tool
|
|
755
821
|
_version = "v" + _version
|
|
@@ -1124,6 +1190,21 @@ def runserver(
|
|
|
1124
1190
|
dev(port, host, dry=dry)
|
|
1125
1191
|
|
|
1126
1192
|
|
|
1193
|
+
@cli.command(name="exec")
|
|
1194
|
+
def run_by_subprocess(cmd: str, dry: bool = DryOption) -> None:
|
|
1195
|
+
"""Run cmd by subprocess, auto set shell=True when cmd contains '|'"""
|
|
1196
|
+
try:
|
|
1197
|
+
rc = run_and_echo(cmd, verbose=True, dry=_ensure_bool(dry))
|
|
1198
|
+
except FileNotFoundError as e:
|
|
1199
|
+
if e.filename == cmd.split()[0]:
|
|
1200
|
+
echo(f"Command not found: {e.filename}")
|
|
1201
|
+
raise Exit(1) from None
|
|
1202
|
+
raise e
|
|
1203
|
+
else:
|
|
1204
|
+
if rc:
|
|
1205
|
+
raise Exit(rc)
|
|
1206
|
+
|
|
1207
|
+
|
|
1127
1208
|
def main() -> None:
|
|
1128
1209
|
cli()
|
|
1129
1210
|
|
|
@@ -42,7 +42,7 @@ dependencies = [
|
|
|
42
42
|
"bumpversion2 >=1.4.3,<2",
|
|
43
43
|
"pytest >=8.2.0,<9",
|
|
44
44
|
]
|
|
45
|
-
version = "0.
|
|
45
|
+
version = "0.17.0"
|
|
46
46
|
|
|
47
47
|
[project.urls]
|
|
48
48
|
Homepage = "https://github.com/waketzheng/fast-dev-cli"
|
|
@@ -78,30 +78,27 @@ dev = [
|
|
|
78
78
|
]
|
|
79
79
|
|
|
80
80
|
[tool.pdm.scripts]
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
up = "pdm update -G :all"
|
|
82
|
+
tree = "pdm list --tree {args}"
|
|
83
|
+
deps = "pdm run python scripts/deps.py {args}"
|
|
84
|
+
prod = "pdm install --prod --frozen {args}"
|
|
85
|
+
test = "pdm run fast test {args}"
|
|
83
86
|
lint = "pdm run fast lint {args}"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
update_self_version = "pdm sync"
|
|
88
|
-
|
|
89
|
-
[tool.pdm.scripts.style]
|
|
90
|
-
composite = [
|
|
91
|
-
"ruff format",
|
|
92
|
-
"ruff check --fix {args}",
|
|
93
|
-
]
|
|
87
|
+
check = "pdm run fast check {args}"
|
|
88
|
+
tag = "pdm run fast tag {args}"
|
|
89
|
+
bump = "pdm run fast bump patch --commit {args}"
|
|
94
90
|
|
|
95
|
-
[tool.pdm.scripts.
|
|
91
|
+
[tool.pdm.scripts.fresh]
|
|
96
92
|
composite = [
|
|
97
|
-
"
|
|
98
|
-
"
|
|
93
|
+
"up",
|
|
94
|
+
"deps",
|
|
99
95
|
]
|
|
100
96
|
|
|
101
|
-
[tool.pdm.scripts.
|
|
97
|
+
[tool.pdm.scripts.ci]
|
|
102
98
|
composite = [
|
|
103
|
-
"
|
|
104
|
-
"
|
|
99
|
+
"deps",
|
|
100
|
+
"check",
|
|
101
|
+
"test",
|
|
105
102
|
]
|
|
106
103
|
|
|
107
104
|
[tool.pdm.scripts.start]
|
|
@@ -110,11 +107,10 @@ composite = [
|
|
|
110
107
|
"deps",
|
|
111
108
|
]
|
|
112
109
|
|
|
113
|
-
[tool.pdm.scripts.
|
|
110
|
+
[tool.pdm.scripts.style]
|
|
114
111
|
composite = [
|
|
115
|
-
"
|
|
116
|
-
"check",
|
|
117
|
-
"test",
|
|
112
|
+
"ruff format",
|
|
113
|
+
"ruff check --fix --unsafe-fixes {args}",
|
|
118
114
|
]
|
|
119
115
|
|
|
120
116
|
[tool.waketzheng._internal-slim-build.packages.fastdevcli-slim.project]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Install deps by `pdm install -G :all --frozen`
|
|
4
|
+
if value of `pdm config use_uv` is True,
|
|
5
|
+
otherwise by the following shells:
|
|
6
|
+
|
|
7
|
+
pdm run python -m ensurepip
|
|
8
|
+
pdm run python -m pip install --upgrade pip
|
|
9
|
+
pdm run python -m pip install --group dev -e .
|
|
10
|
+
|
|
11
|
+
Usage::
|
|
12
|
+
pdm run python scripts/deps.py
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import platform
|
|
19
|
+
import shlex
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
__version__ = "0.1.3"
|
|
25
|
+
__updated_at__ = "2025.07.21"
|
|
26
|
+
SHELL = """
|
|
27
|
+
pdm run python -m ensurepip
|
|
28
|
+
pdm run python -m pip install --upgrade pip
|
|
29
|
+
pdm run python -m pip install --group dev -e .
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def run_and_echo(
|
|
34
|
+
cmd: str,
|
|
35
|
+
env: dict[str, str] | None = None,
|
|
36
|
+
dry: bool = False,
|
|
37
|
+
verbose: bool = True,
|
|
38
|
+
**kw,
|
|
39
|
+
) -> int:
|
|
40
|
+
if verbose:
|
|
41
|
+
print("-->", cmd)
|
|
42
|
+
if dry:
|
|
43
|
+
return 0
|
|
44
|
+
if env is not None:
|
|
45
|
+
env = dict(os.environ, **env)
|
|
46
|
+
return subprocess.run(shlex.split(cmd), env=env, **kw).returncode
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def capture_output(cmd: str) -> str:
|
|
50
|
+
r = subprocess.run(
|
|
51
|
+
shlex.split(cmd), capture_output=True, text=True, encoding="utf-8"
|
|
52
|
+
)
|
|
53
|
+
return r.stdout.strip()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_using_uv() -> bool:
|
|
57
|
+
return capture_output("pdm config use_uv").lower() == "true"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def pop_if_contains(args: list[str], flag: str) -> bool:
|
|
61
|
+
try:
|
|
62
|
+
index = args.index(flag)
|
|
63
|
+
except ValueError:
|
|
64
|
+
return False
|
|
65
|
+
else:
|
|
66
|
+
args.pop(index)
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def prefer_pdm(args: list[str]) -> bool:
|
|
71
|
+
if pop_if_contains(args, "--pip"):
|
|
72
|
+
return False
|
|
73
|
+
return platform.system() != "Windows" or is_using_uv()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def not_distribution() -> bool:
|
|
77
|
+
toml = Path(__file__).parent / "pyproject.toml"
|
|
78
|
+
if not toml.exists():
|
|
79
|
+
toml = toml.parent.parent / toml.name
|
|
80
|
+
if not toml.exists():
|
|
81
|
+
return False
|
|
82
|
+
if sys.version_info >= (3, 11):
|
|
83
|
+
import tomllib
|
|
84
|
+
else:
|
|
85
|
+
try:
|
|
86
|
+
import tomli as tomllib
|
|
87
|
+
except ImportError:
|
|
88
|
+
return False
|
|
89
|
+
doc = tomllib.loads(toml.read_text("utf8"))
|
|
90
|
+
try:
|
|
91
|
+
return not doc["tool"]["pdm"]["distribution"]
|
|
92
|
+
except KeyError:
|
|
93
|
+
...
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def run_shell(command: str, args: list[str], dry: bool) -> int | None:
|
|
98
|
+
if args:
|
|
99
|
+
command += " " + " ".join(i if i.startswith("-") else repr(i) for i in args)
|
|
100
|
+
if run_and_echo(command, dry=dry) == 0:
|
|
101
|
+
return None
|
|
102
|
+
return 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def pip_available() -> bool:
|
|
106
|
+
rc = run_and_echo(
|
|
107
|
+
"pdm run python -m pip --version", capture_output=True, verbose=False
|
|
108
|
+
)
|
|
109
|
+
return rc == 0
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main() -> int | None:
|
|
113
|
+
args = sys.argv[1:]
|
|
114
|
+
if len(args) == 1:
|
|
115
|
+
a1 = args[0]
|
|
116
|
+
if a1 == "-h" or "--help" in args:
|
|
117
|
+
print(__doc__)
|
|
118
|
+
return None
|
|
119
|
+
elif a1 in ("-v", "--version"):
|
|
120
|
+
print(__version__)
|
|
121
|
+
return None
|
|
122
|
+
dry = pop_if_contains(args, "--dry") or pop_if_contains(args, "--dry-run")
|
|
123
|
+
if pop_if_contains(args, "--uv"):
|
|
124
|
+
if os.path.exists("uv.lock"):
|
|
125
|
+
return run_shell("uv sync --all-extras --all-groups", args, dry)
|
|
126
|
+
command = (
|
|
127
|
+
"uv install --all-extras -r pyproject.toml --group dev --no-verify-hashes"
|
|
128
|
+
)
|
|
129
|
+
return run_shell(command, args, dry)
|
|
130
|
+
if prefer_pdm(args):
|
|
131
|
+
command = "pdm install --frozen -G :all"
|
|
132
|
+
return run_shell(command, args, dry)
|
|
133
|
+
shell = SHELL.strip()
|
|
134
|
+
if pop_if_contains(args, "--no-dev"):
|
|
135
|
+
shell = shell.replace(" --group dev", "")
|
|
136
|
+
if args:
|
|
137
|
+
extras = " ".join(i if i.startswith("-") else repr(i) for i in args)
|
|
138
|
+
if extras.startswith("-"):
|
|
139
|
+
shell += " " + extras
|
|
140
|
+
else:
|
|
141
|
+
shell += extras # e.g.: '[xls,fastapi]'
|
|
142
|
+
elif not_distribution():
|
|
143
|
+
shell = shell.replace(" -e .", "")
|
|
144
|
+
cmds = shell.splitlines()
|
|
145
|
+
if pip_available(): # If pip exists, skip installing it by ensurepip
|
|
146
|
+
cmds = cmds[1:]
|
|
147
|
+
for cmd in cmds:
|
|
148
|
+
if run_and_echo(cmd, dry=dry) != 0:
|
|
149
|
+
return 1
|
|
150
|
+
if not dry:
|
|
151
|
+
print("Done.")
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
if __name__ == "__main__":
|
|
156
|
+
sys.exit(main())
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
import shutil
|
|
3
4
|
import subprocess
|
|
5
|
+
import sys
|
|
4
6
|
from contextlib import redirect_stdout
|
|
5
7
|
from io import StringIO
|
|
6
8
|
from pathlib import Path
|
|
@@ -39,7 +41,7 @@ def test_enum():
|
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
def _bump_commands(
|
|
42
|
-
version: str, filename=TOML_FILE, emoji=False, add_sync=
|
|
44
|
+
version: str, filename=TOML_FILE, emoji=False, add_sync=False
|
|
43
45
|
) -> tuple[str, str, str]:
|
|
44
46
|
cmd = rf'bumpversion --parse "(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)" --current-version="{version}"'
|
|
45
47
|
suffix = " --commit && git push && git push --tags && git log -1"
|
|
@@ -243,23 +245,41 @@ path = "app/__init__.py"
|
|
|
243
245
|
"""
|
|
244
246
|
|
|
245
247
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
@pytest.fixture
|
|
249
|
+
def dynamic_pdm_project(tmp_work_dir):
|
|
250
|
+
py = "{}.{}".format(*sys.version_info)
|
|
251
|
+
project = "my-project"
|
|
252
|
+
capture_cmd_output(f"pdm new {project} --non-interactive --python={py} --no-git")
|
|
253
|
+
with chdir(project):
|
|
249
254
|
toml_file = Path("pyproject.toml")
|
|
250
255
|
content = toml_file.read_text().replace(
|
|
251
256
|
'version = "0.1.0"', 'dynamic = ["version"]'
|
|
252
257
|
)
|
|
253
|
-
toml_file.write_text(content)
|
|
254
|
-
with toml_file.open("a+") as f:
|
|
255
|
-
f.write(PDM_DYNAMIC_VERSION)
|
|
258
|
+
toml_file.write_text(content + PDM_DYNAMIC_VERSION)
|
|
256
259
|
app = Path("app")
|
|
257
260
|
app.mkdir()
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
yield app
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_pdm_project(dynamic_pdm_project):
|
|
265
|
+
init_file = dynamic_pdm_project / "__init__.py"
|
|
266
|
+
init_file.write_text('__version__ = "0.2.0"')
|
|
267
|
+
out = capture_cmd_output("fast bump patch")
|
|
268
|
+
assert str(init_file) in out
|
|
269
|
+
assert "0.2.1" in init_file.read_text()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def test_installed_version_is_0_0_0(dynamic_pdm_project):
|
|
273
|
+
version_file = dynamic_pdm_project / "__init__.py"
|
|
274
|
+
version_file.write_text('__version__ = "0.0.0"')
|
|
275
|
+
toml_file = Path("pyproject.toml")
|
|
276
|
+
text = toml_file.read_text()
|
|
277
|
+
toml_file.write_text(re.sub(r"(distribution = )false", r"\1true", text))
|
|
278
|
+
capture_cmd_output("pdm install")
|
|
279
|
+
version_file.write_text('__version__ = "0.3.0"')
|
|
280
|
+
out = capture_cmd_output("fast bump patch")
|
|
281
|
+
assert str(version_file) in out
|
|
282
|
+
assert "0.3.1" in version_file.read_text()
|
|
263
283
|
|
|
264
284
|
|
|
265
285
|
def test_hatch_project(tmp_work_dir):
|
|
@@ -369,39 +389,3 @@ build-backend = "pdm.backend"
|
|
|
369
389
|
out = capture_cmd_output("fast bump patch")
|
|
370
390
|
assert str(version_file) in out
|
|
371
391
|
assert "0.3.1" in version_file.read_text()
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
def test_installed_version_is_0_0_0(tmp_work_dir):
|
|
375
|
-
project_name = "my-project"
|
|
376
|
-
package_name = "app"
|
|
377
|
-
Path(project_name).mkdir()
|
|
378
|
-
with chdir(project_name):
|
|
379
|
-
Path(package_name).mkdir()
|
|
380
|
-
version_file = Path(package_name, "__init__.py")
|
|
381
|
-
version_file.write_text('__version__ = "0.3.0"')
|
|
382
|
-
toml_file = Path("pyproject.toml")
|
|
383
|
-
toml_file.write_text("""
|
|
384
|
-
[project]
|
|
385
|
-
name = "my-project"
|
|
386
|
-
description = ""
|
|
387
|
-
authors = [{name="Waket Zheng", email="waketzheng@gmail.com"}]
|
|
388
|
-
readme = "README.md"
|
|
389
|
-
dynamic = ["version"]
|
|
390
|
-
keywords = []
|
|
391
|
-
requires-python = ">=3.9"
|
|
392
|
-
dependencies = []
|
|
393
|
-
|
|
394
|
-
[tool.pdm]
|
|
395
|
-
distribution = false
|
|
396
|
-
|
|
397
|
-
[tool.pdm.version]
|
|
398
|
-
source = "file"
|
|
399
|
-
path = "app/__init__.py"
|
|
400
|
-
|
|
401
|
-
[build-system]
|
|
402
|
-
requires = ["pdm-backend"]
|
|
403
|
-
build-backend = "pdm.backend"
|
|
404
|
-
""")
|
|
405
|
-
out = capture_cmd_output("fast bump patch")
|
|
406
|
-
assert str(version_file) in out
|
|
407
|
-
assert "0.3.1" in version_file.read_text()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from fast_dev_cli.cli import Exit, capture_cmd_output, run_by_subprocess
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_exec_dry():
|
|
7
|
+
out = capture_cmd_output('fast exec "echo hello" --dry')
|
|
8
|
+
assert "--> echo hello" in out
|
|
9
|
+
assert out.count("hello") == 1
|
|
10
|
+
out = capture_cmd_output('fast exec "echo hello|grep h" --dry')
|
|
11
|
+
assert "--> echo hello|grep h" in out
|
|
12
|
+
assert out.count("hello") == 1
|
|
13
|
+
out = capture_cmd_output(
|
|
14
|
+
'fast exec "invalid command" --dry && echo success || echo failed', shell=True
|
|
15
|
+
)
|
|
16
|
+
assert "success" in out
|
|
17
|
+
assert "failed" not in out
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_exec():
|
|
21
|
+
out = capture_cmd_output('fast exec "echo hello"')
|
|
22
|
+
assert "--> echo hello" in out
|
|
23
|
+
assert out.count("hello") == 2
|
|
24
|
+
out = capture_cmd_output('fast exec "echo hello|grep h"')
|
|
25
|
+
assert "--> echo hello|grep h" in out
|
|
26
|
+
assert out.count("hello") == 2
|
|
27
|
+
out = capture_cmd_output(
|
|
28
|
+
'fast exec "invalid command" && echo success || echo failed', shell=True
|
|
29
|
+
)
|
|
30
|
+
assert "failed" in out
|
|
31
|
+
assert "success" not in out
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_run_by_subprocess(capsys):
|
|
35
|
+
with pytest.raises(Exit):
|
|
36
|
+
run_by_subprocess("python -c 'import sys;sys.exit(1)'")
|
|
37
|
+
with pytest.raises(Exit, match="1"):
|
|
38
|
+
run_by_subprocess("not-exit-command")
|
|
39
|
+
out = capsys.readouterr().out
|
|
40
|
+
assert "Command not found: not-exit-command" in out
|
|
41
|
+
assert not run_by_subprocess(f"cat {__file__}|grep xxx")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.16.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|