create-maa-project 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 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,218 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import platform
7
+ import subprocess
8
+ import sys
9
+ import urllib.request
10
+ from urllib.parse import urlparse
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ from . import __version__
15
+ from .release_manifest import RELEASE_MANIFEST_SHA256
16
+
17
+ RELEASE_REPOSITORY = "Windsland52/create-maa-project"
18
+
19
+
20
+ def main() -> None:
21
+ try:
22
+ binary = ensure_binary()
23
+ except RuntimeError as error:
24
+ raise SystemExit(fallback_message(error))
25
+
26
+ try:
27
+ raise SystemExit(subprocess.call([str(binary), *sys.argv[1:]]))
28
+ except OSError as error:
29
+ raise SystemExit(execution_failure_message(binary, error))
30
+
31
+
32
+ def fallback_message(error: RuntimeError) -> str:
33
+ return (
34
+ f"{error}\n"
35
+ "Install Node.js and run `npx create-maa-project`, or run the TypeScript CLI from source. "
36
+ "The PyPI wrapper does not install Node.js or invoke npx automatically."
37
+ )
38
+
39
+
40
+ def execution_failure_message(
41
+ binary: Path,
42
+ error: OSError,
43
+ system_name: Optional[str] = None,
44
+ ) -> str:
45
+ message = [
46
+ f"Unable to run downloaded create-maa-project binary at {binary}: {error}",
47
+ "Check that the cached binary is executable and has not been blocked by the operating system.",
48
+ ]
49
+ if (system_name or platform.system()) == "Windows":
50
+ message.extend(
51
+ [
52
+ "On Windows, check the PowerShell execution policy used by your shell, "
53
+ "confirm uvx or pipx is available on PATH if you launched through it, "
54
+ "and unblock the downloaded .exe if Windows marked it as downloaded from the Internet.",
55
+ "The PyPI wrapper does not modify PowerShell execution policy.",
56
+ ]
57
+ )
58
+ message.append("You can also install Node.js and run `npx create-maa-project`, or run the TypeScript CLI from source.")
59
+ return "\n".join(message)
60
+
61
+
62
+ def ensure_binary() -> Path:
63
+ cache_dir = Path(os.getenv("CREATE_MAA_PROJECT_CACHE", Path.home() / ".cache" / "create-maa-project"))
64
+ target = cache_dir / __version__ / binary_name()
65
+ if cached_binary_is_valid(target):
66
+ return target
67
+ if not RELEASE_MANIFEST_SHA256:
68
+ raise RuntimeError(
69
+ "PyPI wrapper is missing the trusted CLI release manifest digest. "
70
+ "Install Node.js and run `npx create-maa-project`, or use an official release wheel."
71
+ )
72
+
73
+ manifest_url = (
74
+ f"https://github.com/{RELEASE_REPOSITORY}/releases/download/v{__version__}/"
75
+ "create-maa-project-manifest.json"
76
+ )
77
+ try:
78
+ manifest_bytes = download(manifest_url)
79
+ except OSError as error:
80
+ raise RuntimeError(
81
+ "Unable to download create-maa-project binary. Install Node.js and run "
82
+ "`npx create-maa-project`, or retry with network access."
83
+ ) from error
84
+
85
+ digest = hashlib.sha256(manifest_bytes).hexdigest()
86
+ if digest != RELEASE_MANIFEST_SHA256:
87
+ raise RuntimeError("Downloaded CLI release manifest failed sha256 verification.")
88
+
89
+ manifest = parse_manifest(manifest_bytes)
90
+ validate_manifest_version(manifest)
91
+ asset = select_asset(manifest)
92
+ try:
93
+ binary_bytes = download(asset["url"])
94
+ except OSError as error:
95
+ raise RuntimeError(
96
+ "Unable to download create-maa-project binary asset. Retry with network access."
97
+ ) from error
98
+ binary_digest = hashlib.sha256(binary_bytes).hexdigest()
99
+ if binary_digest != asset["sha256"]:
100
+ raise RuntimeError("Downloaded CLI binary failed sha256 verification.")
101
+
102
+ target.parent.mkdir(parents=True, exist_ok=True)
103
+ target.write_bytes(binary_bytes)
104
+ binary_digest_path(target).write_text(f"{binary_digest}\n", encoding="utf8")
105
+ target.chmod(0o755)
106
+ return target
107
+
108
+
109
+ def cached_binary_is_valid(target: Path) -> bool:
110
+ if not target.exists():
111
+ return False
112
+ digest_path = binary_digest_path(target)
113
+ if not digest_path.exists():
114
+ return False
115
+ try:
116
+ expected_digest = digest_path.read_text(encoding="utf8").strip()
117
+ actual_digest = hashlib.sha256(target.read_bytes()).hexdigest()
118
+ except OSError:
119
+ return False
120
+ try:
121
+ validate_sha256(expected_digest)
122
+ except RuntimeError:
123
+ return False
124
+ return actual_digest == expected_digest
125
+
126
+
127
+ def binary_digest_path(target: Path) -> Path:
128
+ return target.with_name(f"{target.name}.sha256")
129
+
130
+
131
+ def binary_name() -> str:
132
+ suffix = ".exe" if platform.system() == "Windows" else ""
133
+ return f"create-maa-project{suffix}"
134
+
135
+
136
+ def parse_manifest(manifest_bytes: bytes) -> dict[str, object]:
137
+ try:
138
+ manifest = json.loads(manifest_bytes)
139
+ except json.JSONDecodeError as error:
140
+ raise RuntimeError("Downloaded CLI release manifest is not valid JSON.") from error
141
+ if not isinstance(manifest, dict):
142
+ raise RuntimeError("Downloaded CLI release manifest must be a JSON object.")
143
+ return manifest
144
+
145
+
146
+ def validate_manifest_version(manifest: dict[str, object]) -> None:
147
+ version = manifest_version(manifest)
148
+ if version != __version__:
149
+ raise RuntimeError(
150
+ f"CLI release manifest version {version or '<missing>'} does not match "
151
+ f"PyPI wrapper version {__version__}."
152
+ )
153
+
154
+
155
+ def select_asset(
156
+ manifest: dict[str, object],
157
+ system_name: Optional[str] = None,
158
+ machine_name: Optional[str] = None,
159
+ ) -> dict[str, str]:
160
+ validate_manifest_version(manifest)
161
+ system = {"Windows": "win", "Linux": "linux", "Darwin": "macos"}.get(system_name or platform.system())
162
+ machine = (machine_name or platform.machine()).lower()
163
+ arch = "aarch64" if machine in {"arm64", "aarch64"} else "x86_64"
164
+ for asset in manifest.get("assets", []):
165
+ if not isinstance(asset, dict):
166
+ continue
167
+ if asset.get("os") == system and asset.get("arch") == arch and asset.get("kind") == "sea":
168
+ asset_version = manifest_version(asset)
169
+ if asset_version != __version__:
170
+ raise RuntimeError(
171
+ f"CLI binary asset version {asset_version or '<missing>'} does not match "
172
+ f"PyPI wrapper version {__version__}."
173
+ )
174
+ url = asset_string(asset, "url")
175
+ sha256 = asset_string(asset, "sha256")
176
+ validate_asset_url(url)
177
+ validate_sha256(sha256)
178
+ return {
179
+ "url": url,
180
+ "sha256": sha256,
181
+ }
182
+ raise RuntimeError(f"No CLI binary is available for {system}/{arch}.")
183
+
184
+
185
+ def manifest_version(manifest: dict[str, object]) -> str:
186
+ for key in ("version", "tag"):
187
+ value = manifest.get(key)
188
+ if isinstance(value, str) and value:
189
+ return value[1:] if value.startswith("v") else value
190
+ return ""
191
+
192
+
193
+ def asset_string(asset: dict[str, object], key: str) -> str:
194
+ value = asset.get(key)
195
+ if not isinstance(value, str) or not value:
196
+ raise RuntimeError(f"CLI binary asset must include a non-empty {key}.")
197
+ return value
198
+
199
+
200
+ def validate_asset_url(url: str) -> None:
201
+ parsed = urlparse(url)
202
+ if parsed.scheme != "https" or not parsed.netloc:
203
+ raise RuntimeError("CLI binary asset must include an https URL.")
204
+
205
+
206
+ def validate_sha256(value: str) -> None:
207
+ if len(value) != 64 or any(char not in "0123456789abcdefABCDEF" for char in value):
208
+ raise RuntimeError("CLI binary asset must include a 64-character sha256 digest.")
209
+
210
+
211
+ def download(url: str) -> bytes:
212
+ request = urllib.request.Request(url, headers={"User-Agent": f"create-maa-project/{__version__}"})
213
+ with urllib.request.urlopen(request, timeout=30) as response:
214
+ return response.read()
215
+
216
+
217
+ if __name__ == "__main__":
218
+ main()
@@ -0,0 +1,2 @@
1
+ # Filled by the release workflow when publishing the PyPI wrapper.
2
+ RELEASE_MANIFEST_SHA256 = "62b98efc13c404ef204457e802c42317efecaca9a7fe6a5cd63a10611f1874a6"
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: create-maa-project
3
+ Version: 0.1.0
4
+ Summary: PyPI wrapper for create-maa-project
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+
9
+ # create-maa-project
10
+
11
+ MaaFW pure pipeline project scaffolding CLI.
12
+
13
+ ```bash
14
+ npx create-maa-project my-project
15
+ ```
16
+
17
+ Current implementation covers the local pipeline scaffold core:
18
+
19
+ - interactive create flow without LLM
20
+ - default `resource/base` project shape
21
+ - committed `maa-project.json` and `maa-project.lock.json`
22
+ - project-owned files such as `interface.json`, `package.json`, `tasks/`, `resource/`, editor ignores/settings, and OCR model files are created once instead of managed as template baselines
23
+ - metadata sync for interface, package, controller, license, network mode, display name, and validated GitHub repository URLs
24
+ - managed file hash checks through `--doctor` and `--diff`
25
+ - `--accept-changes [path...]` to accept selected or all changed managed files
26
+ - accepted local baseline reporting in `--doctor` and generated `tools/check-project.mjs`
27
+ - `--update node-deps` dependency refresh with successful pending-action cleanup
28
+ - `--update schema` refreshes the generated project from the CLI's embedded schema baseline
29
+ - runtime updates resolve MaaFramework and MFAAvalonia assets from GitHub Releases, verify GitHub-provided sha256 digests, cache MFAAvalonia GUI files for release staging, and extract MaaFramework into the generated runtime layout
30
+ - `pnpm sync:runtime` in generated projects calls `create-maa-project --update maafw --update runtime:mfa`; set `CREATE_MAA_PROJECT_RUNTIME_PLATFORM=all` to sync every desktop platform instead of the current platform
31
+ - default asset downloads retry transient network failures; set `CREATE_MAA_PROJECT_DOWNLOAD_ATTEMPTS=<n>` to override the default
32
+ - OCR downloads can be seeded for local/offline verification with `CREATE_MAA_PROJECT_OCR_ZIP_PATH`
33
+ - OCR model updates use a verified manifest from `CREATE_MAA_PROJECT_OCR_MANIFEST_URL` when configured, with the existing OCR zip as fallback
34
+ - explicit schema baseline sync through `pnpm sync:schema`, plus generated daily schema-sync workflow
35
+ - CLI project creation attempts OCR model download and `pnpm install` by default, keeping actionable pending items if either fails
36
+ - conservative `--update template` with `--update template --diff` preview and `--force` overwrite
37
+ - generated project lint and release dry-run smoke checks, including pending-action, pnpm lockfile, VS Code settings, and interface schema guards
38
+ - release staging through generated `tools/build-release.mjs`, with MFAAvalonia GUI files laid down first, MaaFramework runtime overlaid after it, package-only `interface.json` rewriting, tag-based version injection, dev-file exclusion, package smoke checks, and Unix tar executable metadata smoke in the release workflow
39
+ - default release workflow packages the 3 OS x 2 arch desktop artifact matrix using M9A-style GUI suffixes such as `-MFAA`: Windows zip artifacts, plus Linux/macOS tar.gz artifacts; MFAAvalonia runtime sync uses its upstream `win/linux/osx` and `x64/arm64` asset matrix separately
40
+ - explicit `--git`/`--no-git` creation flow with parent-repository and pending-commit guards
41
+ - write lock, explicit stale-lock cleanup, local backups for file overwrites and non-empty target directories, cache cleanup, and backup restore
42
+
43
+ Schema syncing is explicit and workflow-based rather than part of build.
44
+
45
+ ## Release
46
+
47
+ Pushing a `v*` tag runs `.github/workflows/release.yml`. The workflow checks the
48
+ repo, builds the npm package, builds SEA binaries for Windows/Linux/macOS on
49
+ `x86_64` and `aarch64`, writes `create-maa-project-manifest.json`, publishes the
50
+ GitHub Release assets, publishes npm, then builds the PyPI wrapper with the
51
+ release manifest digest embedded and publishes it through trusted publishing.
@@ -0,0 +1,8 @@
1
+ create_maa_project/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ create_maa_project/__main__.py,sha256=DQsHMyZ8gPPsRvhW4LtJVNRvj7jOEo3MvO0jwJht3fo,7754
3
+ create_maa_project/release_manifest.py,sha256=JgTv0l2RA5aTjalwYwLycu7GSSsMsSLZa7tShi92eyE,160
4
+ create_maa_project-0.1.0.dist-info/METADATA,sha256=gvN8d3hVkfCyOkQiqRzOERqVs5nzxlE150WvlVXa1V8,3847
5
+ create_maa_project-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
6
+ create_maa_project-0.1.0.dist-info/entry_points.txt,sha256=Ixyerv6d0PGlLSIDuSOanwYXWQAQk-FjfhufgYbhocw,72
7
+ create_maa_project-0.1.0.dist-info/licenses/LICENSE,sha256=jUvKLUlcNGeNxq6tetag2sJFRnMroZ05HuigHZMXOQI,18
8
+ create_maa_project-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ create-maa-project = create_maa_project.__main__:main
@@ -0,0 +1 @@
1
+ AGPL-3.0-or-later