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.
- create_maa_project/__init__.py +1 -0
- create_maa_project/__main__.py +218 -0
- create_maa_project/release_manifest.py +2 -0
- create_maa_project-0.1.0.dist-info/METADATA +51 -0
- create_maa_project-0.1.0.dist-info/RECORD +8 -0
- create_maa_project-0.1.0.dist-info/WHEEL +4 -0
- create_maa_project-0.1.0.dist-info/entry_points.txt +2 -0
- create_maa_project-0.1.0.dist-info/licenses/LICENSE +1 -0
|
@@ -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,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 @@
|
|
|
1
|
+
AGPL-3.0-or-later
|