godot-cli-control 0.2.11__tar.gz → 0.2.12__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.
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/PKG-INFO +1 -1
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/README.md +2 -2
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/_version.py +2 -2
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/cli.py +10 -1
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/init_cmd.py +88 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/templates/skill/SKILL.md +1 -1
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_cli.py +1 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_init.py +183 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/.gitignore +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/LICENSE +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/CHANGELOG.md +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/LICENSE +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bin/run_cli_control.ps1 +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bin/run_cli_control.sh +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/error_codes.gd +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/game_bridge.gd +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/game_bridge.gd.uid +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/input_simulation_api.gd +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/input_simulation_api.gd.uid +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/low_level_api.gd +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/low_level_api.gd.uid +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/plugin.cfg +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/plugin.gd +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/plugin.gd.uid +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/gut/test_game_bridge.gd +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/gut/test_input_simulation_api.gd +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/gut/test_low_level_api.gd +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/run_gut.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/run_gut.sh +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/test_init_walkthrough.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/test_walkthrough.sh +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/pyproject.toml +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/README.md +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/__init__.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/__main__.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/_duration.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/bridge.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/client.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/daemon.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/pytest_plugin.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/registry.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/runner.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/skills_install.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/templates/__init__.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/templates/skill/__init__.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/__init__.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_bridge.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_cli_helpers.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_client.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_daemon.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_duration.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_e2e_input.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_e2e_screenshot_gui.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_pytest_plugin.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_registry.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_runner.py +0 -0
- {godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_skills_install.py +0 -0
|
@@ -9,13 +9,13 @@ If you have the Python CLI installed (`pipx install godot-cli-control`), the ent
|
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
cd your_godot_project
|
|
12
|
-
godot-cli-control init # copy plugin, patch project.godot, detect Godot
|
|
12
|
+
godot-cli-control init # copy plugin, patch project.godot, detect Godot, gitignore .cli_control/
|
|
13
13
|
godot-cli-control daemon start
|
|
14
14
|
godot-cli-control tree 3
|
|
15
15
|
godot-cli-control daemon stop
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`init` automates everything described in the manual setup below — copying the addon, editing `project.godot`, and detecting your Godot binary.
|
|
18
|
+
`init` automates everything described in the manual setup below — copying the addon, editing `project.godot`, and detecting your Godot binary. It also appends `.cli_control/` to the project's root `.gitignore` (the daemon's machine-local state dir: detected binary path, pid, port, log, recording intermediates), so that state never gets committed. Pass `--no-gitignore` to skip that step.
|
|
19
19
|
|
|
20
20
|
## Manual Setup (if you prefer)
|
|
21
21
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.12'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 12)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1264,6 +1264,7 @@ def cmd_init(ns: argparse.Namespace) -> int:
|
|
|
1264
1264
|
write_skills=not ns.no_skills,
|
|
1265
1265
|
skills_only=ns.skills_only,
|
|
1266
1266
|
clobber_skills=not ns.skills_no_clobber,
|
|
1267
|
+
write_gitignore=not ns.no_gitignore,
|
|
1267
1268
|
output_format=fmt,
|
|
1268
1269
|
result=result,
|
|
1269
1270
|
)
|
|
@@ -1569,7 +1570,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1569
1570
|
help="在 Godot 项目根一键接入插件",
|
|
1570
1571
|
description=(
|
|
1571
1572
|
"复制 addons/godot_cli_control 到目标项目、patch project.godot 启用插件、"
|
|
1572
|
-
"校验 GODOT_BIN
|
|
1573
|
+
"校验 GODOT_BIN、在 .gitignore 忽略 .cli_control/。"
|
|
1573
1574
|
),
|
|
1574
1575
|
epilog=(
|
|
1575
1576
|
"GODOT_BIN 查找顺序:\n"
|
|
@@ -1601,6 +1602,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1601
1602
|
"保证版本与 CLI 帮助同步)。与 --no-skills / --skills-only 都兼容。"
|
|
1602
1603
|
),
|
|
1603
1604
|
)
|
|
1605
|
+
init_p.add_argument(
|
|
1606
|
+
"--no-gitignore",
|
|
1607
|
+
action="store_true",
|
|
1608
|
+
help=(
|
|
1609
|
+
"跳过往项目根 .gitignore 追加 .cli_control/(默认会追加,"
|
|
1610
|
+
"忽略 daemon 的机器本地状态目录)。--skills-only 模式下本就跳过。"
|
|
1611
|
+
),
|
|
1612
|
+
)
|
|
1604
1613
|
skills_group = init_p.add_mutually_exclusive_group()
|
|
1605
1614
|
skills_group.add_argument(
|
|
1606
1615
|
"--no-skills",
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
绕过 Godot Editor GUI 启用步骤
|
|
9
9
|
4. 自动检测 Godot 二进制,写入 ``.cli_control/godot_bin``,daemon 启动时
|
|
10
10
|
会优先读取此文件
|
|
11
|
+
5. 在项目根 ``.gitignore`` 追加 ``.cli_control/`` —— daemon 的机器本地状态
|
|
12
|
+
目录(godot_bin 绝对路径 / pid / port / log / 录制中间产物),不应提交
|
|
11
13
|
|
|
12
14
|
完成后用户只需 ``godot-cli-control daemon start`` 即可。
|
|
13
15
|
"""
|
|
@@ -26,6 +28,12 @@ from .daemon import find_godot_binary, reimport_project
|
|
|
26
28
|
PLUGIN_DIR_NAME = "godot_cli_control"
|
|
27
29
|
ADDONS_DIRNAME = "addons"
|
|
28
30
|
|
|
31
|
+
GITIGNORE_NAME = ".gitignore"
|
|
32
|
+
# 需要 init 写进目标项目 .gitignore 的条目。用元组而非单值,给将来"增量加
|
|
33
|
+
# 忽略项"留口子(与 method_blacklist_extra 同样的增量哲学);目前只有 daemon
|
|
34
|
+
# 的机器本地状态目录 .cli_control/。
|
|
35
|
+
GITIGNORE_ENTRIES: tuple[str, ...] = (".cli_control/",)
|
|
36
|
+
|
|
29
37
|
AUTOLOAD_KEY = "GameBridgeNode"
|
|
30
38
|
AUTOLOAD_VALUE = '"*res://addons/godot_cli_control/bridge/game_bridge.gd"'
|
|
31
39
|
PLUGIN_CFG_PATH = "res://addons/godot_cli_control/plugin.cfg"
|
|
@@ -43,6 +51,7 @@ def run_init(
|
|
|
43
51
|
write_skills: bool = True,
|
|
44
52
|
skills_only: bool = False,
|
|
45
53
|
clobber_skills: bool = True,
|
|
54
|
+
write_gitignore: bool = True,
|
|
46
55
|
*,
|
|
47
56
|
output_format: str = "text",
|
|
48
57
|
result: dict[str, Any] | None = None,
|
|
@@ -56,6 +65,9 @@ def run_init(
|
|
|
56
65
|
``clobber_skills=False``:写 skill 时遇到已存在文件就跳过(用户改过
|
|
57
66
|
SKILL.md、希望保留本地版又允许 init 把缺失那条补上时用)。与
|
|
58
67
|
``write_skills=False`` / ``skills_only=True`` 都兼容。
|
|
68
|
+
``write_gitignore=False``:跳过往项目根 ``.gitignore`` 追加 ``.cli_control/``
|
|
69
|
+
(不想让 init 碰 ``.gitignore`` 的用户逃生口)。该步骤属于"项目接入"阶段,
|
|
70
|
+
``skills_only=True`` 时本就跳过。
|
|
59
71
|
|
|
60
72
|
``output_format='json'``:抑制全部人类可读 print;调用方(cli.cmd_init)
|
|
61
73
|
传入 ``result`` 字典,本函数会回填结构化字段,由 cli 侧封 JSON envelope。
|
|
@@ -93,6 +105,7 @@ def run_init(
|
|
|
93
105
|
project_godot_changes=[],
|
|
94
106
|
godot_bin=None,
|
|
95
107
|
skills_written=[],
|
|
108
|
+
gitignore_added=[],
|
|
96
109
|
)
|
|
97
110
|
|
|
98
111
|
if not (project_root / "project.godot").is_file():
|
|
@@ -157,6 +170,15 @@ def run_init(
|
|
|
157
170
|
"(跳过资源重新导入;首次 daemon start 会兜底重建 cache)"
|
|
158
171
|
)
|
|
159
172
|
|
|
173
|
+
# .gitignore:无条件确保(不依赖 godot_bin 是否检测到——daemon 早晚
|
|
174
|
+
# 会建 .cli_control/,提前忽略最稳)。属于"项目接入"阶段,故在
|
|
175
|
+
# not skills_only 块内。
|
|
176
|
+
if write_gitignore:
|
|
177
|
+
added = _ensure_gitignore_entries(project_root)
|
|
178
|
+
if added:
|
|
179
|
+
_say(f"修改 .gitignore:忽略 {', '.join(added)}")
|
|
180
|
+
_record(gitignore_added=added)
|
|
181
|
+
|
|
160
182
|
if write_skills:
|
|
161
183
|
# lazy import:保持与 cli.cmd_init 那侧 `from .init_cmd import run_init`
|
|
162
184
|
# 对称,两边都不在模块顶层 import,避免任一方将来改成顶层 import
|
|
@@ -319,3 +341,69 @@ def _ensure_in_packed_array(
|
|
|
319
341
|
body.rstrip("\n") + "\n" + f'{key}=PackedStringArray("{value}")\n'
|
|
320
342
|
)
|
|
321
343
|
return text[:start] + new_body + text[end:], True
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# ── .gitignore 维护 ──
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _gitignore_has_entry(text: str, entry: str) -> bool:
|
|
350
|
+
"""``.gitignore`` 文本里是否已忽略 ``entry``。
|
|
351
|
+
|
|
352
|
+
宽松匹配:忽略前导 ``/`` 与尾随 ``/``、行首尾空白,所以 ``.cli_control``、
|
|
353
|
+
``.cli_control/``、``/.cli_control/`` 视为同一条,不重复加。注释行(``#`` 开头)
|
|
354
|
+
不算数 —— 用户把它注释掉就是想让它失效,真条目仍要补。
|
|
355
|
+
"""
|
|
356
|
+
target = entry.strip().strip("/")
|
|
357
|
+
for line in text.splitlines():
|
|
358
|
+
stripped = line.strip()
|
|
359
|
+
if not stripped or stripped.startswith("#"):
|
|
360
|
+
continue
|
|
361
|
+
if stripped.strip("/") == target:
|
|
362
|
+
return True
|
|
363
|
+
return False
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _add_gitignore_entries(
|
|
367
|
+
text: str, entries: tuple[str, ...] | list[str]
|
|
368
|
+
) -> tuple[str, list[str]]:
|
|
369
|
+
"""纯函数:把缺失的 ``entries`` 追加到 ``.gitignore`` 文本末尾。
|
|
370
|
+
|
|
371
|
+
返回 ``(新文本, 实际新增的条目列表)``。已存在的条目跳过。追加前确保末尾
|
|
372
|
+
有换行,避免把新条目黏到原末行后面;空文本不留前导空行。行尾统一用
|
|
373
|
+
``\\n``,CRLF 回写交给 IO 包装层(与 ``_patch_project_godot`` 同构)。
|
|
374
|
+
"""
|
|
375
|
+
added: list[str] = []
|
|
376
|
+
out = text
|
|
377
|
+
for entry in entries:
|
|
378
|
+
if _gitignore_has_entry(out, entry):
|
|
379
|
+
continue
|
|
380
|
+
if out and not out.endswith("\n"):
|
|
381
|
+
out += "\n"
|
|
382
|
+
out += entry + "\n"
|
|
383
|
+
added.append(entry)
|
|
384
|
+
return out, added
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _ensure_gitignore_entries(
|
|
388
|
+
project_root: Path, entries: tuple[str, ...] = GITIGNORE_ENTRIES
|
|
389
|
+
) -> list[str]:
|
|
390
|
+
"""确保项目根 ``.gitignore`` 忽略 ``entries``;返回实际新增的条目。
|
|
391
|
+
|
|
392
|
+
文件不存在则创建(LF)。已存在则沿用 ``_patch_project_godot`` 的 bytes+CRLF
|
|
393
|
+
思路:读 bytes、探测原文是否 CRLF、解码时折成 LF 便于匹配,仅在确有新增时
|
|
394
|
+
回写并把行尾翻回原样 —— 保 Windows 上 git diff 不被整文件行尾翻转污染。
|
|
395
|
+
"""
|
|
396
|
+
path = project_root / GITIGNORE_NAME
|
|
397
|
+
if path.exists():
|
|
398
|
+
raw = path.read_bytes()
|
|
399
|
+
is_crlf = b"\r\n" in raw
|
|
400
|
+
text = raw.decode("utf-8").replace("\r\n", "\n")
|
|
401
|
+
else:
|
|
402
|
+
is_crlf = False
|
|
403
|
+
text = ""
|
|
404
|
+
|
|
405
|
+
new_text, added = _add_gitignore_entries(text, entries)
|
|
406
|
+
if added:
|
|
407
|
+
out = new_text.replace("\n", "\r\n") if is_crlf else new_text
|
|
408
|
+
path.write_bytes(out.encode("utf-8"))
|
|
409
|
+
return added
|
|
@@ -85,7 +85,7 @@ $ godot-cli-control --text exists /root/Main
|
|
|
85
85
|
true
|
|
86
86
|
|
|
87
87
|
$ godot-cli-control init
|
|
88
|
-
{"ok": true, "result": {"project_root": "/path/to/proj", "plugin_copied": true, "plugin_overwritten": false, "project_godot_changes": ["autoload/GameBridgeNode", "editor_plugins/enabled"], "godot_bin": "/usr/bin/godot", "skills_written": [".../SKILL.md", ".../SKILL.md"], "skills_only": false, "write_skills": true}}
|
|
88
|
+
{"ok": true, "result": {"project_root": "/path/to/proj", "plugin_copied": true, "plugin_overwritten": false, "project_godot_changes": ["autoload/GameBridgeNode", "editor_plugins/enabled"], "godot_bin": "/usr/bin/godot", "skills_written": [".../SKILL.md", ".../SKILL.md"], "gitignore_added": [".cli_control/"], "skills_only": false, "write_skills": true}}
|
|
89
89
|
|
|
90
90
|
$ godot-cli-control run my_script.py
|
|
91
91
|
{"ok": true, "result": {"exit_code": 0, "script": "my_script.py"}}
|
|
@@ -428,6 +428,7 @@ def test_cmd_init_emits_success_envelope_in_json_mode(
|
|
|
428
428
|
no_skills=False,
|
|
429
429
|
skills_only=False,
|
|
430
430
|
skills_no_clobber=False,
|
|
431
|
+
no_gitignore=False,
|
|
431
432
|
output_format=OUTPUT_JSON,
|
|
432
433
|
)
|
|
433
434
|
rc = cmd_init(ns)
|
|
@@ -459,6 +460,7 @@ def test_cmd_init_emits_error_envelope_on_non_godot_dir(
|
|
|
459
460
|
no_skills=False,
|
|
460
461
|
skills_only=False,
|
|
461
462
|
skills_no_clobber=False,
|
|
463
|
+
no_gitignore=False,
|
|
462
464
|
output_format=OUTPUT_JSON,
|
|
463
465
|
)
|
|
464
466
|
rc = cmd_init(ns)
|
|
@@ -469,3 +471,184 @@ def test_cmd_init_emits_error_envelope_on_non_godot_dir(
|
|
|
469
471
|
assert payload["ok"] is False
|
|
470
472
|
assert payload["error"]["code"] == -1003 # CLIENT_CODE_USAGE
|
|
471
473
|
assert "project.godot" in payload["error"]["message"]
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
# ── .gitignore 维护(确保 .cli_control/ 不被提交)──
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def test_gitignore_adds_entry_to_empty_text() -> None:
|
|
480
|
+
"""纯函数:空文本追加条目。"""
|
|
481
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
482
|
+
|
|
483
|
+
out, added = _add_gitignore_entries("", [".cli_control/"])
|
|
484
|
+
assert added == [".cli_control/"]
|
|
485
|
+
assert out == ".cli_control/\n"
|
|
486
|
+
# 不允许出现前导空行
|
|
487
|
+
assert not out.startswith("\n")
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def test_gitignore_appends_preserving_existing_entries() -> None:
|
|
491
|
+
"""已有别的忽略项时只追加、不动原内容。"""
|
|
492
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
493
|
+
|
|
494
|
+
text = "*.tmp\nbuild/\n"
|
|
495
|
+
out, added = _add_gitignore_entries(text, [".cli_control/"])
|
|
496
|
+
assert added == [".cli_control/"]
|
|
497
|
+
assert "*.tmp" in out
|
|
498
|
+
assert "build/" in out
|
|
499
|
+
assert out.endswith(".cli_control/\n")
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def test_gitignore_idempotent_when_entry_present() -> None:
|
|
503
|
+
"""已含完全相同条目 → 不重复加。"""
|
|
504
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
505
|
+
|
|
506
|
+
text = "build/\n.cli_control/\n"
|
|
507
|
+
out, added = _add_gitignore_entries(text, [".cli_control/"])
|
|
508
|
+
assert added == []
|
|
509
|
+
assert out == text
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
@pytest.mark.parametrize(
|
|
513
|
+
"existing",
|
|
514
|
+
[".cli_control\n", "/.cli_control/\n", "/.cli_control\n", ".cli_control/ \n"],
|
|
515
|
+
)
|
|
516
|
+
def test_gitignore_idempotent_on_equivalent_variants(existing: str) -> None:
|
|
517
|
+
"""带/不带斜杠、带前导 / 、带尾随空白都视为已忽略,不重复加。"""
|
|
518
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
519
|
+
|
|
520
|
+
out, added = _add_gitignore_entries(existing, [".cli_control/"])
|
|
521
|
+
assert added == []
|
|
522
|
+
assert out == existing
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def test_gitignore_commented_line_does_not_count_as_present() -> None:
|
|
526
|
+
"""被注释掉的同名行不算忽略,真条目仍要补上。"""
|
|
527
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
528
|
+
|
|
529
|
+
text = "# .cli_control/\n"
|
|
530
|
+
out, added = _add_gitignore_entries(text, [".cli_control/"])
|
|
531
|
+
assert added == [".cli_control/"]
|
|
532
|
+
assert "# .cli_control/" in out # 注释保留
|
|
533
|
+
assert out.endswith(".cli_control/\n")
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_gitignore_appends_newline_when_file_lacks_trailing_one() -> None:
|
|
537
|
+
"""原文件末行无换行符时,新条目必须独立成行、不黏在末行后面。"""
|
|
538
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
539
|
+
|
|
540
|
+
out, added = _add_gitignore_entries("build/", [".cli_control/"])
|
|
541
|
+
assert added == [".cli_control/"]
|
|
542
|
+
assert out == "build/\n.cli_control/\n"
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def test_ensure_gitignore_creates_file_when_missing(tmp_path: Path) -> None:
|
|
546
|
+
"""项目根没有 .gitignore → 创建并写入 .cli_control/。"""
|
|
547
|
+
from godot_cli_control.init_cmd import _ensure_gitignore_entries
|
|
548
|
+
|
|
549
|
+
added = _ensure_gitignore_entries(tmp_path)
|
|
550
|
+
gi = tmp_path / ".gitignore"
|
|
551
|
+
assert gi.is_file()
|
|
552
|
+
assert ".cli_control/" in gi.read_text(encoding="utf-8")
|
|
553
|
+
assert added == [".cli_control/"]
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def test_ensure_gitignore_idempotent_across_runs(tmp_path: Path) -> None:
|
|
557
|
+
"""连跑两次只写一条,不重复。"""
|
|
558
|
+
from godot_cli_control.init_cmd import _ensure_gitignore_entries
|
|
559
|
+
|
|
560
|
+
_ensure_gitignore_entries(tmp_path)
|
|
561
|
+
added2 = _ensure_gitignore_entries(tmp_path)
|
|
562
|
+
assert added2 == []
|
|
563
|
+
content = (tmp_path / ".gitignore").read_text(encoding="utf-8")
|
|
564
|
+
assert content.count(".cli_control/") == 1
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def test_ensure_gitignore_preserves_crlf(tmp_path: Path) -> None:
|
|
568
|
+
"""已有 CRLF 的 .gitignore 在追加后必须仍是 CRLF,不混入裸 LF。"""
|
|
569
|
+
from godot_cli_control.init_cmd import _ensure_gitignore_entries
|
|
570
|
+
|
|
571
|
+
gi = tmp_path / ".gitignore"
|
|
572
|
+
gi.write_bytes(b"*.tmp\r\nbuild/\r\n")
|
|
573
|
+
_ensure_gitignore_entries(tmp_path)
|
|
574
|
+
after = gi.read_bytes()
|
|
575
|
+
assert b".cli_control/\r\n" in after
|
|
576
|
+
bare_lf = after.count(b"\n") - after.count(b"\r\n")
|
|
577
|
+
assert bare_lf == 0, f"出现裸 LF:{bare_lf} 处"
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def test_run_init_adds_cli_control_to_gitignore(
|
|
581
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
582
|
+
) -> None:
|
|
583
|
+
"""端到端:run_init 默认在项目根 .gitignore 忽略 .cli_control/。"""
|
|
584
|
+
monkeypatch.delenv("GODOT_BIN", raising=False)
|
|
585
|
+
monkeypatch.setattr(
|
|
586
|
+
"godot_cli_control.init_cmd.find_godot_binary", lambda: None
|
|
587
|
+
)
|
|
588
|
+
proj = _minimal_project(tmp_path)
|
|
589
|
+
assert run_init(proj) == 0
|
|
590
|
+
gi = (proj / ".gitignore").read_text(encoding="utf-8")
|
|
591
|
+
assert ".cli_control/" in gi
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def test_run_init_no_gitignore_skips_gitignore(
|
|
595
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
596
|
+
) -> None:
|
|
597
|
+
"""write_gitignore=False → 不创建 / 不改 .gitignore。"""
|
|
598
|
+
monkeypatch.delenv("GODOT_BIN", raising=False)
|
|
599
|
+
monkeypatch.setattr(
|
|
600
|
+
"godot_cli_control.init_cmd.find_godot_binary", lambda: None
|
|
601
|
+
)
|
|
602
|
+
proj = _minimal_project(tmp_path)
|
|
603
|
+
assert run_init(proj, write_gitignore=False) == 0
|
|
604
|
+
assert not (proj / ".gitignore").exists()
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def test_run_init_skills_only_skips_gitignore(tmp_path: Path) -> None:
|
|
608
|
+
"""skills_only 属于纯刷 SKILL.md,不应碰 .gitignore。"""
|
|
609
|
+
proj = _make_min_godot_project(tmp_path)
|
|
610
|
+
assert run_init(proj, skills_only=True) == 0
|
|
611
|
+
assert not (proj / ".gitignore").exists()
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def test_run_init_json_records_gitignore_added(
|
|
615
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
616
|
+
) -> None:
|
|
617
|
+
"""json 模式 result 必须回填 gitignore_added 字段。"""
|
|
618
|
+
monkeypatch.delenv("GODOT_BIN", raising=False)
|
|
619
|
+
monkeypatch.setattr(
|
|
620
|
+
"godot_cli_control.init_cmd.find_godot_binary", lambda: None
|
|
621
|
+
)
|
|
622
|
+
proj = _make_min_godot_project(tmp_path)
|
|
623
|
+
result: dict[str, object] = {}
|
|
624
|
+
assert run_init(proj, output_format="json", result=result) == 0
|
|
625
|
+
assert result["gitignore_added"] == [".cli_control/"]
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def test_cmd_init_no_gitignore_flag_skips(
|
|
629
|
+
tmp_path: Path,
|
|
630
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
631
|
+
capsys: pytest.CaptureFixture[str],
|
|
632
|
+
) -> None:
|
|
633
|
+
"""CLI 层:--no-gitignore(ns.no_gitignore=True)不写 .gitignore。"""
|
|
634
|
+
import argparse
|
|
635
|
+
|
|
636
|
+
from godot_cli_control.cli import OUTPUT_JSON, cmd_init
|
|
637
|
+
|
|
638
|
+
monkeypatch.delenv("GODOT_BIN", raising=False)
|
|
639
|
+
monkeypatch.setattr(
|
|
640
|
+
"godot_cli_control.init_cmd.find_godot_binary", lambda: None
|
|
641
|
+
)
|
|
642
|
+
proj = _make_min_godot_project(tmp_path)
|
|
643
|
+
ns = argparse.Namespace(
|
|
644
|
+
path=str(proj),
|
|
645
|
+
force=False,
|
|
646
|
+
no_skills=False,
|
|
647
|
+
skills_only=False,
|
|
648
|
+
skills_no_clobber=False,
|
|
649
|
+
no_gitignore=True,
|
|
650
|
+
output_format=OUTPUT_JSON,
|
|
651
|
+
)
|
|
652
|
+
assert cmd_init(ns) == 0
|
|
653
|
+
capsys.readouterr()
|
|
654
|
+
assert not (proj / ".gitignore").exists()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/error_codes.gd
RENAMED
|
File without changes
|
{godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/game_bridge.gd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/plugin.gd.uid
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/run_gut.py
RENAMED
|
File without changes
|
{godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/run_gut.sh
RENAMED
|
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
|
{godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/pytest_plugin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/skills_install.py
RENAMED
|
File without changes
|
{godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/godot_cli_control/templates/__init__.py
RENAMED
|
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
|
{godot_cli_control-0.2.11 → godot_cli_control-0.2.12}/python/tests/test_e2e_screenshot_gui.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|