tg-bot-plugin-buildkit 0.1.0__py3-none-any.whl

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,19 @@
1
+ """TG-BOT 插件 bundle 的可复用构建工具集。"""
2
+
3
+ from .buildkit import (
4
+ benchmark_bundle,
5
+ build_bundle,
6
+ inspect_bundle,
7
+ install_local_bundle,
8
+ validate_bundle,
9
+ verify_bundle,
10
+ )
11
+
12
+ __all__ = [
13
+ "benchmark_bundle",
14
+ "build_bundle",
15
+ "inspect_bundle",
16
+ "install_local_bundle",
17
+ "validate_bundle",
18
+ "verify_bundle",
19
+ ]
@@ -0,0 +1,307 @@
1
+ """构建在共享合同核心之上的高层 buildkit 操作。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import platform
7
+ import shutil
8
+ import tempfile
9
+ import time
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from .core_adapter import (
14
+ compute_artifact_digest,
15
+ compute_package_sha256,
16
+ inspect_bundle as shared_inspect_bundle,
17
+ normalize_manifest_payload,
18
+ validate_bundle_same_profile,
19
+ validate_bundle_target_profile,
20
+ verify_bundle as shared_verify_bundle,
21
+ write_reproducible_bundle,
22
+ )
23
+
24
+ MANIFEST_FILE_NAME = "manifest.json"
25
+ SUPPORTED_BUNDLE_SUFFIX = ".tgpkg"
26
+ _BUNDLE_EXCLUDED_DIRECTORY_NAMES = {
27
+ ".git",
28
+ ".hg",
29
+ ".idea",
30
+ ".mypy_cache",
31
+ ".nox",
32
+ ".pytest_cache",
33
+ ".ruff_cache",
34
+ ".svn",
35
+ ".tox",
36
+ ".venv",
37
+ ".vscode",
38
+ "__pycache__",
39
+ "node_modules",
40
+ }
41
+ _BUNDLE_EXCLUDED_TOP_LEVEL_NAMES = {
42
+ "build",
43
+ "dist",
44
+ "docs",
45
+ "scripts",
46
+ "tests",
47
+ }
48
+ _BUNDLE_EXCLUDED_TOP_LEVEL_FILE_NAMES = {
49
+ ".gitignore",
50
+ ".python-version",
51
+ "Makefile",
52
+ "README",
53
+ "README.md",
54
+ "poetry.lock",
55
+ "pyproject.toml",
56
+ "uv.lock",
57
+ }
58
+ _BUNDLE_EXCLUDED_FILE_SUFFIXES = {
59
+ ".pyc",
60
+ SUPPORTED_BUNDLE_SUFFIX,
61
+ }
62
+
63
+
64
+ def _stable_json(payload: Any) -> str:
65
+ return json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
66
+
67
+
68
+ def build_runtime_profile_id() -> str:
69
+ implementation = platform.python_implementation().lower()
70
+ version = f"{platform.python_version_tuple()[0]}.{platform.python_version_tuple()[1]}"
71
+ system_name = platform.system().lower()
72
+ machine_name = platform.machine().lower().replace("/", "_")
73
+ libc_name, _libc_version = platform.libc_ver()
74
+ abi = libc_name.lower() if libc_name else ("darwin" if system_name == "darwin" else "unknown")
75
+ return "-".join((implementation, version, system_name, machine_name, abi))
76
+
77
+
78
+ def _read_manifest_payload(source_dir: Path) -> dict[str, Any]:
79
+ manifest_path = source_dir / MANIFEST_FILE_NAME
80
+ payload = json.loads(manifest_path.read_text(encoding="utf-8"))
81
+ if not isinstance(payload, dict):
82
+ raise ValueError("manifest root must be an object")
83
+ return payload
84
+
85
+
86
+ def _write_manifest_payload(source_dir: Path, payload: dict[str, Any]) -> None:
87
+ (source_dir / MANIFEST_FILE_NAME).write_text(_stable_json(payload), encoding="utf-8")
88
+
89
+
90
+ def _output_bundle_path(output_dir: Path, *, plugin_id: str, plugin_version: str, runtime_profile_id: str) -> Path:
91
+ return output_dir / plugin_id / f"{plugin_id}-{plugin_version}-{runtime_profile_id}{SUPPORTED_BUNDLE_SUFFIX}"
92
+
93
+
94
+ def _output_roots_to_ignore(source_dir: Path, output_dir: Path) -> set[str]:
95
+ try:
96
+ relpath = output_dir.relative_to(source_dir)
97
+ except ValueError:
98
+ return set()
99
+ if not relpath.parts:
100
+ return set()
101
+ return {relpath.parts[0]}
102
+
103
+
104
+ def _should_exclude_bundle_source(path: Path, source_dir: Path, extra_top_level_names: set[str]) -> bool:
105
+ relpath = path.relative_to(source_dir)
106
+ if not relpath.parts:
107
+ return False
108
+
109
+ first_part = relpath.parts[0]
110
+ if first_part in extra_top_level_names:
111
+ return True
112
+ if first_part.startswith(".") and first_part != "META-INF":
113
+ return True
114
+ if any(part in _BUNDLE_EXCLUDED_DIRECTORY_NAMES for part in relpath.parts):
115
+ return True
116
+ if first_part in _BUNDLE_EXCLUDED_TOP_LEVEL_NAMES:
117
+ return True
118
+ if len(relpath.parts) == 1 and path.name in _BUNDLE_EXCLUDED_TOP_LEVEL_FILE_NAMES:
119
+ return True
120
+ if path.is_file() and path.suffix in _BUNDLE_EXCLUDED_FILE_SUFFIXES:
121
+ return True
122
+ return False
123
+
124
+
125
+ def _build_copy_ignore(source_dir: Path, output_dir: Path):
126
+ extra_top_level_names = _output_roots_to_ignore(source_dir, output_dir)
127
+
128
+ def _ignore(current_dir: str, names: list[str]) -> list[str]:
129
+ current_path = Path(current_dir)
130
+ ignored: list[str] = []
131
+ for name in names:
132
+ candidate = current_path / name
133
+ if _should_exclude_bundle_source(candidate, source_dir, extra_top_level_names):
134
+ ignored.append(name)
135
+ return ignored
136
+
137
+ return _ignore
138
+
139
+
140
+ def build_bundle(
141
+ *,
142
+ source_dir: str | Path,
143
+ output_dir: str | Path | None = None,
144
+ runtime_profile_id: str = "",
145
+ ) -> dict[str, Any]:
146
+ resolved_source_dir = Path(source_dir).expanduser().resolve()
147
+ resolved_output_dir = (
148
+ Path(output_dir).expanduser().resolve()
149
+ if output_dir
150
+ else (resolved_source_dir / "dist" / "plugins").resolve()
151
+ )
152
+ manifest_payload = _read_manifest_payload(resolved_source_dir)
153
+ effective_runtime_profile_id = str(runtime_profile_id or "").strip() or build_runtime_profile_id()
154
+ manifest_payload["runtime_profile_id"] = effective_runtime_profile_id
155
+ manifest_payload["artifact_digest"] = ""
156
+ manifest = normalize_manifest_payload(manifest_payload, require_artifact_digest=False)
157
+
158
+ with tempfile.TemporaryDirectory(prefix=f"tg-bot-buildkit-{manifest.plugin_id}-") as temp_dir_str:
159
+ staging_dir = Path(temp_dir_str) / manifest.plugin_id
160
+ shutil.copytree(
161
+ resolved_source_dir,
162
+ staging_dir,
163
+ ignore=_build_copy_ignore(resolved_source_dir, resolved_output_dir),
164
+ )
165
+ _write_manifest_payload(staging_dir, manifest.raw)
166
+ artifact_digest = compute_artifact_digest(staging_dir, manifest.raw)
167
+ manifest_payload = dict(manifest.raw)
168
+ manifest_payload["artifact_digest"] = artifact_digest
169
+ _write_manifest_payload(staging_dir, manifest_payload)
170
+ bundle_path = _output_bundle_path(
171
+ resolved_output_dir,
172
+ plugin_id=manifest.plugin_id,
173
+ plugin_version=manifest.plugin_version,
174
+ runtime_profile_id=effective_runtime_profile_id,
175
+ )
176
+ bundle_path.parent.mkdir(parents=True, exist_ok=True)
177
+ write_reproducible_bundle(staging_dir, bundle_path)
178
+
179
+ return {
180
+ "build_status": "ok",
181
+ "plugin_id": manifest.plugin_id,
182
+ "plugin_version": manifest.plugin_version,
183
+ "runtime_profile_id": effective_runtime_profile_id,
184
+ "artifact_digest": artifact_digest,
185
+ "package_sha256": compute_package_sha256(bundle_path),
186
+ "bundle_path": str(bundle_path),
187
+ }
188
+
189
+
190
+ def inspect_bundle(bundle_path: str | Path) -> dict[str, Any]:
191
+ inspection = shared_inspect_bundle(bundle_path)
192
+ return {
193
+ "inspect_status": "ok",
194
+ "plugin_id": inspection.plugin_id,
195
+ "plugin_version": inspection.plugin_version,
196
+ "runtime_profile_id": inspection.runtime_profile_id,
197
+ "artifact_digest": inspection.artifact_digest,
198
+ "package_sha256": inspection.package_sha256,
199
+ "entrypoint": inspection.manifest.entrypoint,
200
+ "bundle_path": str(inspection.bundle_path),
201
+ }
202
+
203
+
204
+ def verify_bundle(bundle_path: str | Path, *, allow_unsigned_dev: bool = False) -> dict[str, Any]:
205
+ verification = shared_verify_bundle(bundle_path, allow_unsigned_dev=allow_unsigned_dev)
206
+ signer_identity = ""
207
+ if verification.signer_identity is not None:
208
+ signer_identity = verification.signer_identity.persistent_value()
209
+ return {
210
+ "verify_status": "ok",
211
+ "plugin_id": verification.plugin_id,
212
+ "plugin_version": verification.plugin_version,
213
+ "runtime_profile_id": verification.runtime_profile_id,
214
+ "artifact_digest": verification.artifact_digest,
215
+ "package_sha256": verification.package_sha256,
216
+ "signature_verification_status": verification.signature_verification_status,
217
+ "signer_identity": signer_identity,
218
+ "bundle_path": str(Path(bundle_path).expanduser().resolve()),
219
+ }
220
+
221
+
222
+ def validate_bundle(
223
+ bundle_path: str | Path,
224
+ *,
225
+ mode: str,
226
+ target_runtime_profile_id: str = "",
227
+ allow_unsigned_dev: bool = False,
228
+ ) -> dict[str, Any]:
229
+ resolved_bundle_path = Path(bundle_path).expanduser().resolve()
230
+ if mode == "same-profile":
231
+ verification = validate_bundle_same_profile(
232
+ resolved_bundle_path,
233
+ runtime_profile_id=build_runtime_profile_id(),
234
+ allow_unsigned_dev=allow_unsigned_dev,
235
+ )
236
+ elif mode == "target-profile":
237
+ verification = validate_bundle_target_profile(
238
+ resolved_bundle_path,
239
+ target_runtime_profile_id=str(target_runtime_profile_id or "").strip(),
240
+ allow_unsigned_dev=allow_unsigned_dev,
241
+ )
242
+ else:
243
+ raise ValueError(f"unsupported validate mode: {mode}")
244
+ return {
245
+ "validate_status": "ok",
246
+ "mode": mode,
247
+ "plugin_id": verification.plugin_id,
248
+ "plugin_version": verification.plugin_version,
249
+ "runtime_profile_id": verification.runtime_profile_id,
250
+ "artifact_digest": verification.artifact_digest,
251
+ "package_sha256": verification.package_sha256,
252
+ "signature_verification_status": verification.signature_verification_status,
253
+ "bundle_path": str(resolved_bundle_path),
254
+ }
255
+
256
+
257
+ def install_local_bundle(
258
+ *,
259
+ bundle_path: str | Path,
260
+ plugin_root: str | Path,
261
+ dev_unsigned: bool,
262
+ ) -> dict[str, Any]:
263
+ resolved_bundle_path = Path(bundle_path).expanduser().resolve()
264
+ resolved_plugin_root = Path(plugin_root).expanduser().resolve()
265
+ verification = shared_verify_bundle(resolved_bundle_path, allow_unsigned_dev=dev_unsigned)
266
+ target_dir = resolved_plugin_root / verification.plugin_id
267
+ target_dir.mkdir(parents=True, exist_ok=True)
268
+ target_bundle_path = target_dir / f"{verification.plugin_id}{SUPPORTED_BUNDLE_SUFFIX}"
269
+ temp_bundle_path = target_bundle_path.with_suffix(f"{target_bundle_path.suffix}.tmp")
270
+ shutil.copy2(resolved_bundle_path, temp_bundle_path)
271
+ temp_bundle_path.replace(target_bundle_path)
272
+ return {
273
+ "install_status": "ok",
274
+ "plugin_id": verification.plugin_id,
275
+ "plugin_version": verification.plugin_version,
276
+ "package_sha256": verification.package_sha256,
277
+ "signature_verification_status": verification.signature_verification_status,
278
+ "installed_bundle": str(target_bundle_path),
279
+ }
280
+
281
+
282
+ def benchmark_bundle(
283
+ *,
284
+ source_dir: str | Path,
285
+ output_dir: str | Path | None = None,
286
+ runtime_profile_id: str = "",
287
+ ) -> dict[str, Any]:
288
+ started_at = time.perf_counter()
289
+ build_result = build_bundle(
290
+ source_dir=source_dir,
291
+ output_dir=output_dir,
292
+ runtime_profile_id=runtime_profile_id,
293
+ )
294
+ build_duration_ms = round((time.perf_counter() - started_at) * 1000, 3)
295
+ bundle_path = Path(build_result["bundle_path"])
296
+ inspect_result = inspect_bundle(bundle_path)
297
+ return {
298
+ "benchmark_status": "ok",
299
+ "build_duration_ms": build_duration_ms,
300
+ "bundle_size_bytes": bundle_path.stat().st_size,
301
+ "plugin_id": build_result["plugin_id"],
302
+ "plugin_version": build_result["plugin_version"],
303
+ "runtime_profile_id": build_result["runtime_profile_id"],
304
+ "artifact_digest": inspect_result["artifact_digest"],
305
+ "package_sha256": inspect_result["package_sha256"],
306
+ "bundle_path": str(bundle_path),
307
+ }
@@ -0,0 +1,104 @@
1
+ """tg-bot-plugin-buildkit 的命令行入口。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from pathlib import Path
8
+
9
+ from .buildkit import (
10
+ benchmark_bundle,
11
+ build_bundle,
12
+ inspect_bundle,
13
+ install_local_bundle,
14
+ validate_bundle,
15
+ verify_bundle,
16
+ )
17
+
18
+
19
+ def _stable_json(payload: dict[str, object]) -> str:
20
+ return json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
21
+
22
+
23
+ def _parse_args() -> argparse.Namespace:
24
+ parser = argparse.ArgumentParser(description="构建并校验 TG-BOT 插件 bundle。")
25
+ subparsers = parser.add_subparsers(dest="command", required=True)
26
+
27
+ build_parser = subparsers.add_parser("build", help="构建可复现的 .tgpkg bundle。")
28
+ build_parser.add_argument("--source-dir", required=True, help="插件源码目录。")
29
+ build_parser.add_argument("--output-dir", default="", help="生成 bundle 的输出根目录。")
30
+ build_parser.add_argument("--runtime-profile-id", default="", help="覆盖 runtime profile id。")
31
+
32
+ inspect_parser = subparsers.add_parser("inspect", help="检查 bundle 合同完整性。")
33
+ inspect_parser.add_argument("--bundle", required=True, help=".tgpkg bundle 路径。")
34
+
35
+ verify_parser = subparsers.add_parser("verify", help="校验 bundle 签名材料。")
36
+ verify_parser.add_argument("--bundle", required=True, help=".tgpkg bundle 路径。")
37
+ verify_parser.add_argument(
38
+ "--allow-unsigned-dev",
39
+ action="store_true",
40
+ help="允许未签名的开发态 bundle。",
41
+ )
42
+
43
+ validate_parser = subparsers.add_parser("validate", help="按 runtime profile 校验 bundle。")
44
+ validate_parser.add_argument("--bundle", required=True, help=".tgpkg bundle 路径。")
45
+ validate_parser.add_argument("--mode", required=True, choices=("same-profile", "target-profile"))
46
+ validate_parser.add_argument("--target-runtime-profile-id", default="", help="target-profile 模式必填。")
47
+ validate_parser.add_argument("--allow-unsigned-dev", action="store_true")
48
+
49
+ benchmark_parser = subparsers.add_parser("benchmark", help="执行一次本地构建基准测试。")
50
+ benchmark_parser.add_argument("--source-dir", required=True, help="插件源码目录。")
51
+ benchmark_parser.add_argument("--output-dir", default="", help="生成 bundle 的输出根目录。")
52
+ benchmark_parser.add_argument("--runtime-profile-id", default="", help="覆盖 runtime profile id。")
53
+
54
+ install_parser = subparsers.add_parser("install-local", help="将 bundle 复制到本地插件根目录。")
55
+ install_parser.add_argument("--bundle", required=True, help=".tgpkg bundle 路径。")
56
+ install_parser.add_argument("--plugin-root", required=True, help="目标 TG_BOT_PLUGIN_ROOT 风格目录。")
57
+ install_parser.add_argument("--dev-unsigned", action="store_true", help="允许未签名的开发态 bundle。")
58
+
59
+ return parser.parse_args()
60
+
61
+
62
+ def main() -> int:
63
+ args = _parse_args()
64
+ try:
65
+ if args.command == "build":
66
+ payload = build_bundle(
67
+ source_dir=args.source_dir,
68
+ output_dir=args.output_dir,
69
+ runtime_profile_id=args.runtime_profile_id,
70
+ )
71
+ elif args.command == "inspect":
72
+ payload = inspect_bundle(args.bundle)
73
+ elif args.command == "verify":
74
+ payload = verify_bundle(args.bundle, allow_unsigned_dev=bool(args.allow_unsigned_dev))
75
+ elif args.command == "validate":
76
+ payload = validate_bundle(
77
+ args.bundle,
78
+ mode=args.mode,
79
+ target_runtime_profile_id=args.target_runtime_profile_id,
80
+ allow_unsigned_dev=bool(args.allow_unsigned_dev),
81
+ )
82
+ elif args.command == "benchmark":
83
+ payload = benchmark_bundle(
84
+ source_dir=args.source_dir,
85
+ output_dir=args.output_dir,
86
+ runtime_profile_id=args.runtime_profile_id,
87
+ )
88
+ elif args.command == "install-local":
89
+ payload = install_local_bundle(
90
+ bundle_path=args.bundle,
91
+ plugin_root=args.plugin_root,
92
+ dev_unsigned=bool(args.dev_unsigned),
93
+ )
94
+ else:
95
+ raise ValueError(f"unsupported command: {args.command}")
96
+ except Exception as exc:
97
+ print(_stable_json({"status": "error", "message": str(exc)}), end="")
98
+ return 1
99
+ print(_stable_json({"status": "ok", **payload}), end="")
100
+ return 0
101
+
102
+
103
+ if __name__ == "__main__":
104
+ raise SystemExit(main())
@@ -0,0 +1,60 @@
1
+ """面向 tg-bot-plugin-contract-core 的本地适配层。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ from importlib import import_module
8
+ from pathlib import Path
9
+
10
+
11
+ def _candidate_src_paths() -> list[Path]:
12
+ candidates: list[Path] = []
13
+ env_path = str(os.getenv("TG_BOT_PLUGIN_CONTRACT_CORE_SRC") or "").strip()
14
+ if env_path:
15
+ candidates.append(Path(env_path).expanduser())
16
+ sibling = Path(__file__).resolve().parents[3] / "tg-bot-plugin-contract-core" / "src"
17
+ candidates.append(sibling)
18
+ deduped: list[Path] = []
19
+ for candidate in candidates:
20
+ resolved = candidate.resolve()
21
+ if resolved not in deduped:
22
+ deduped.append(resolved)
23
+ return deduped
24
+
25
+
26
+ def _load_contract_core():
27
+ try:
28
+ public_module = import_module("tg_bot_plugin_contract_core")
29
+ core_module = import_module("tg_bot_plugin_contract_core.core")
30
+ return public_module, core_module
31
+ except ImportError:
32
+ for candidate in _candidate_src_paths():
33
+ if not candidate.is_dir():
34
+ continue
35
+ candidate_str = str(candidate)
36
+ if candidate_str not in sys.path:
37
+ sys.path.insert(0, candidate_str)
38
+ try:
39
+ public_module = import_module("tg_bot_plugin_contract_core")
40
+ core_module = import_module("tg_bot_plugin_contract_core.core")
41
+ return public_module, core_module
42
+ except ImportError:
43
+ continue
44
+ raise ImportError(
45
+ "tg_bot_plugin_contract_core is not importable; install the package or set "
46
+ "TG_BOT_PLUGIN_CONTRACT_CORE_SRC to the package src directory",
47
+ )
48
+
49
+
50
+ _PUBLIC_MODULE, _CORE_MODULE = _load_contract_core()
51
+
52
+ ContractCoreError = _PUBLIC_MODULE.ContractCoreError
53
+ compute_artifact_digest = _PUBLIC_MODULE.compute_artifact_digest
54
+ compute_package_sha256 = _PUBLIC_MODULE.compute_package_sha256
55
+ inspect_bundle = _PUBLIC_MODULE.inspect_bundle
56
+ validate_bundle_same_profile = _PUBLIC_MODULE.validate_bundle_same_profile
57
+ validate_bundle_target_profile = _PUBLIC_MODULE.validate_bundle_target_profile
58
+ verify_bundle = _PUBLIC_MODULE.verify_bundle
59
+ write_reproducible_bundle = _PUBLIC_MODULE.write_reproducible_bundle
60
+ normalize_manifest_payload = _CORE_MODULE._normalize_manifest_payload
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: tg-bot-plugin-buildkit
3
+ Version: 0.1.0
4
+ Summary: 基于共享合同核心构建并校验 TG-BOT 插件 bundle。
5
+ Author: Fire Dragons
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: tg-bot-plugin-contract-core==0.1.1
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
11
+ Description-Content-Type: text/markdown
12
+
13
+ # tg-bot-plugin-buildkit
14
+
15
+ `tg-bot-plugin-buildkit` 是面向 TG-BOT 单插件仓库的可复用构建与校验 CLI。
16
+
17
+ 它封装共享 `tg-bot-plugin-contract-core` 包,并提供:
18
+
19
+ - `build`
20
+ - `inspect`
21
+ - `verify`
22
+ - `validate --mode same-profile`
23
+ - `validate --mode target-profile`
24
+ - `benchmark`
25
+ - `install-local --dev-unsigned`
26
+
27
+ ## 本地开发
28
+
29
+ 该包的正式发布形态固定依赖已发布的 `tg-bot-plugin-contract-core==0.1.1`。
30
+
31
+ 如果需要在未发布改动上进行本地多仓联调,CLI 仍可通过以下环境变量解析兄弟仓源码:
32
+
33
+ - `TG_BOT_PLUGIN_CONTRACT_CORE_SRC=/path/to/tg-bot-plugin-contract-core/src`
34
+
35
+ 这条桥接路径只作为开发态回退,不再是正式 CI / release 的 canonical 依赖方式。
36
+
37
+ ## CI / 发布
38
+
39
+ - 仓库 CI 会执行:
40
+ - `pytest`
41
+ - wheel / sdist 构建
42
+ - Docker builder 镜像构建 smoke test
43
+ - `tag push v*` 会额外执行:
44
+ - `tag == project.version` 校验
45
+ - PyPI 发布
46
+ - GHCR builder 镜像发布
47
+ - GitHub Release 附件上传
48
+ - 可复用 workflow `release-plugin.yml` 用于单插件仓库正式发版,固定执行:
49
+ - `tag == manifest.plugin_version` 校验
50
+ - `build`
51
+ - `inspect`
52
+ - `verify`
53
+ - `validate --mode same-profile`
54
+ - `validate --mode target-profile`
55
+ - 可复现性 smoke check
56
+ - bundle 产物上传 / Release asset 发布
57
+
58
+ ## 使用方式
59
+
60
+ ```bash
61
+ python -m tg_bot_plugin_buildkit.cli build --source-dir ./plugin
62
+ python -m tg_bot_plugin_buildkit.cli inspect --bundle ./dist/plugins/demo/demo-1.2.3-cpython-3.13-linux-x86_64-gnu.tgpkg
63
+ python -m tg_bot_plugin_buildkit.cli verify --bundle ./dist/plugins/demo/demo-1.2.3-cpython-3.13-linux-x86_64-gnu.tgpkg
64
+ python -m tg_bot_plugin_buildkit.cli validate --mode same-profile --bundle ./dist/plugins/demo/demo-1.2.3-cpython-3.13-linux-x86_64-gnu.tgpkg
65
+ python -m tg_bot_plugin_buildkit.cli install-local --bundle ./dist/plugins/demo/demo-1.2.3-cpython-3.13-linux-x86_64-gnu.tgpkg --plugin-root ./plugins --dev-unsigned
66
+ ```
67
+
68
+ `build` 默认会排除 Git/CI/测试/虚拟环境与 `dist` 等仓库控制或生成目录,避免把仓库元数据和旧 bundle 再次打进新的 `.tgpkg`。
@@ -0,0 +1,8 @@
1
+ tg_bot_plugin_buildkit/__init__.py,sha256=UjF4KMt6OmQQFmCGHhT4hk83GA3pCiw8CN2gqMSITH4,364
2
+ tg_bot_plugin_buildkit/buildkit.py,sha256=8rcY5RjD7IseR213PdkFQooMdh9Qn9R1yp-l0tZcbrc,11093
3
+ tg_bot_plugin_buildkit/cli.py,sha256=D6RmDJSFPE2EWiRxHn_xwSrrVZi3v4tTdvy6SNqDpDM,4562
4
+ tg_bot_plugin_buildkit/core_adapter.py,sha256=xy3kq2ANfUSQGcfgF86htciaKRyD9BUD7bx5Zx7awTo,2309
5
+ tg_bot_plugin_buildkit-0.1.0.dist-info/METADATA,sha256=IGKQy9AJkUnotLyAH2CsnfkT_Qk6ykNhBm3EtOK9j4A,2504
6
+ tg_bot_plugin_buildkit-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ tg_bot_plugin_buildkit-0.1.0.dist-info/entry_points.txt,sha256=c-OiOcCo_MX9wwmEmKjmwnDbt1HY8ovN5WLZq3ypX1Q,75
8
+ tg_bot_plugin_buildkit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tg-bot-plugin-buildkit = tg_bot_plugin_buildkit.cli:main