bedrockplus 0.1.0.dev1__py3-none-win_amd64.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.
Files changed (36) hide show
  1. bedrockplus/__init__.py +95 -0
  2. bedrockplus/__main__.py +4 -0
  3. bedrockplus/_assets/Initialize-BedrockPlusServer.ps1 +395 -0
  4. bedrockplus/_assets/README.md +85 -0
  5. bedrockplus/_assets/SHA256SUMS +20 -0
  6. bedrockplus/_assets/Start-BedrockPlus.ps1 +125 -0
  7. bedrockplus/_assets/THIRD_PARTY_NOTICES.md +26 -0
  8. bedrockplus/_assets/bedrockplus.exe +0 -0
  9. bedrockplus/_assets/bedrockplus_bootstrap.dll +0 -0
  10. bedrockplus/_assets/bedrockplus_plugin_host.dll +0 -0
  11. bedrockplus/_assets/bedrockplus_plugin_worker.dll +0 -0
  12. bedrockplus/_assets/bedrockplus_runtime.dll +0 -0
  13. bedrockplus/_assets/bin/bedrockplus.ps1 +192 -0
  14. bedrockplus/_assets/compatibility/bds/1.26.51.1/windows-x64.json +24 -0
  15. bedrockplus/_assets/compatibility/bds/supported-versions.json +14 -0
  16. bedrockplus/_assets/licenses/microsoft-detours-MIT.txt +23 -0
  17. bedrockplus/_assets/plugins/README.txt +11 -0
  18. bedrockplus/_assets/release-manifest.json +34 -0
  19. bedrockplus/_assets/server/README.txt +10 -0
  20. bedrockplus/_assets/tools/bppack.py +1865 -0
  21. bedrockplus/_assets/tools/init_python_plugin.py +140 -0
  22. bedrockplus/_assets/tools/pack_plugin.py +142 -0
  23. bedrockplus/_assets/tools/world_packs.py +530 -0
  24. bedrockplus/api.py +83 -0
  25. bedrockplus/cli.py +121 -0
  26. bedrockplus/commands.py +50 -0
  27. bedrockplus/events.py +138 -0
  28. bedrockplus/objects.py +413 -0
  29. bedrockplus/py.typed +1 -0
  30. bedrockplus-0.1.0.dev1.dist-info/METADATA +41 -0
  31. bedrockplus-0.1.0.dev1.dist-info/RECORD +36 -0
  32. bedrockplus-0.1.0.dev1.dist-info/WHEEL +4 -0
  33. bedrockplus-0.1.0.dev1.dist-info/entry_points.txt +2 -0
  34. bedrockplus-0.1.0.dev1.dist-info/licenses/THIRD_PARTY_NOTICES.md +26 -0
  35. bedrockplus-0.1.0.dev1.dist-info/licenses/microsoft-detours-MIT.txt +23 -0
  36. bedrockplus-0.1.0.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env python3
2
+ """Create a minimal, immediately packageable BedrockPlus Python plugin."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ import re
11
+ import shutil
12
+ import tempfile
13
+
14
+
15
+ class InitError(RuntimeError):
16
+ pass
17
+
18
+
19
+ WINDOWS_RESERVED = {
20
+ "con", "prn", "aux", "nul", "clock$",
21
+ *(f"com{number}" for number in range(1, 10)),
22
+ *(f"lpt{number}" for number in range(1, 10)),
23
+ }
24
+
25
+
26
+ def _identity(display_name: str) -> tuple[str, str, str]:
27
+ name = display_name.strip()
28
+ if not name or len(name) > 80 or any(ord(character) < 32 for character in name):
29
+ raise InitError("plugin name must contain 1 to 80 printable characters")
30
+ words = re.findall(r"[A-Za-z0-9]+", name)
31
+ if not words:
32
+ raise InitError("plugin name must contain Latin letters or digits; for example: My Plugin")
33
+
34
+ plugin_id = "-".join(word.lower() for word in words)
35
+ if plugin_id[0].isdigit():
36
+ plugin_id = "plugin-" + plugin_id
37
+ if plugin_id.casefold() in WINDOWS_RESERVED:
38
+ plugin_id = "plugin-" + plugin_id
39
+
40
+ class_name = "".join(word[0].upper() + word[1:] for word in words)
41
+ if class_name[0].isdigit():
42
+ class_name = "Plugin" + class_name
43
+ if not class_name.casefold().endswith("plugin"):
44
+ class_name += "Plugin"
45
+ return name, plugin_id, class_name
46
+
47
+
48
+ def create_python_plugin(parent: Path, display_name: str) -> Path:
49
+ name, plugin_id, class_name = _identity(display_name)
50
+ parent = parent.resolve()
51
+ if any(part.casefold() == "referenceonly" for part in parent.parts):
52
+ raise InitError("referenceOnly is read-only; create the plugin elsewhere")
53
+ if not parent.is_dir():
54
+ raise InitError(f"destination directory does not exist: {parent}")
55
+ target = parent / plugin_id
56
+ if target.exists() or target.is_symlink():
57
+ raise InitError(f"destination already exists; no files were changed: {target}")
58
+
59
+ manifest = {
60
+ "format_version": 1,
61
+ "id": plugin_id,
62
+ "name": name,
63
+ "version": "0.1.0",
64
+ "api_version": 1,
65
+ "runtime": {
66
+ "type": "python",
67
+ "entrypoint": "python/plugin.py",
68
+ "entry_class": class_name,
69
+ "python": "3.13",
70
+ },
71
+ }
72
+ source = f'''from bedrockplus import PluginHost
73
+
74
+
75
+ class {class_name}:
76
+ id = "{plugin_id}"
77
+ name = {name!r}
78
+ version = "0.1.0"
79
+
80
+ def on_load(self, host: PluginHost) -> None:
81
+ self.host = host
82
+
83
+ def on_enable(self) -> None:
84
+ pass
85
+
86
+ def on_server_ready(self) -> None:
87
+ pass
88
+
89
+ def on_server_stopping(self) -> None:
90
+ pass
91
+
92
+ def on_disable(self) -> None:
93
+ pass
94
+
95
+ def on_unload(self) -> None:
96
+ pass
97
+ '''
98
+
99
+ staging = Path(tempfile.mkdtemp(prefix=f".bedrockplus-{plugin_id}-", dir=parent))
100
+ try:
101
+ (staging / "python").mkdir()
102
+ (staging / "manifest.json").write_text(
103
+ json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
104
+ encoding="utf-8",
105
+ newline="\n",
106
+ )
107
+ (staging / "requirements.txt").write_bytes(b"")
108
+ (staging / "python" / "plugin.py").write_text(
109
+ source,
110
+ encoding="utf-8",
111
+ newline="\n",
112
+ )
113
+ os.replace(staging, target)
114
+ except OSError as error:
115
+ raise InitError(f"could not create plugin: {error}") from error
116
+ finally:
117
+ if staging.exists():
118
+ shutil.rmtree(staging)
119
+ return target
120
+
121
+
122
+ def main() -> int:
123
+ parser = argparse.ArgumentParser(description="Create a BedrockPlus Python plugin")
124
+ parser.add_argument("name", help="display name used to derive the folder, ID, and class")
125
+ parser.add_argument("--parent", default=".", help="parent directory; defaults to the current directory")
126
+ arguments = parser.parse_args()
127
+ try:
128
+ target = create_python_plugin(Path(arguments.parent), arguments.name)
129
+ except InitError as error:
130
+ parser.exit(1, f"BedrockPlus plugin init failed: {error}\n")
131
+ print(f"Created BedrockPlus Python plugin: {target}")
132
+ print(
133
+ "Next: edit python\\plugin.py, add dependencies to requirements.txt, "
134
+ "then run bedrockplus plugin pack"
135
+ )
136
+ return 0
137
+
138
+
139
+ if __name__ == "__main__":
140
+ raise SystemExit(main())
@@ -0,0 +1,142 @@
1
+ """Prepare a plugin folder and build a self-contained .bppack.
2
+
3
+ This is the implementation behind ``bedrockplus plugin pack``. It deliberately
4
+ installs dependencies only during this explicit packaging operation, never
5
+ during server startup.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import json
12
+ from pathlib import Path
13
+ import re
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ import tempfile
18
+
19
+
20
+ ROOT = Path(__file__).resolve().parents[1]
21
+ BPPACK = ROOT / "tools" / "bppack.py"
22
+ PLUGIN_ID = re.compile(r"^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$")
23
+ SAFE_VERSION = re.compile(r"^[0-9A-Za-z.+-]+$")
24
+
25
+
26
+ class PackError(RuntimeError):
27
+ pass
28
+
29
+
30
+ def _manifest_identity(source: Path) -> tuple[str, str]:
31
+ manifest_path = source / "manifest.json"
32
+ if not manifest_path.is_file():
33
+ raise PackError(f"manifest.json was not found in {source}")
34
+ try:
35
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
36
+ except (OSError, UnicodeError, json.JSONDecodeError) as error:
37
+ raise PackError(f"manifest.json could not be read: {error}") from error
38
+ plugin_id = manifest.get("id")
39
+ version = manifest.get("version")
40
+ if not isinstance(plugin_id, str) or not plugin_id.strip():
41
+ raise PackError("manifest.json must contain a non-empty string id")
42
+ if not isinstance(version, str) or not version.strip():
43
+ raise PackError("manifest.json must contain a non-empty string version")
44
+ plugin_id = plugin_id.strip()
45
+ version = version.strip()
46
+ if not PLUGIN_ID.fullmatch(plugin_id) or not SAFE_VERSION.fullmatch(version):
47
+ raise PackError("manifest id or version cannot be used as a safe package filename")
48
+ return plugin_id, version
49
+
50
+
51
+ def _copy_ignore(_directory: str, names: list[str]) -> set[str]:
52
+ ignored = {".git", ".venv", "venv", "__pycache__", ".pytest_cache", ".mypy_cache"}
53
+ return {
54
+ name
55
+ for name in names
56
+ if name in ignored or name.endswith((".pyc", ".pyo", ".bppack"))
57
+ }
58
+
59
+
60
+ def _requirements(source: Path) -> Path | None:
61
+ locked = source / "requirements.lock"
62
+ if locked.is_file():
63
+ return locked
64
+ ordinary = source / "requirements.txt"
65
+ return ordinary if ordinary.is_file() else None
66
+
67
+
68
+ def _install_dependencies(requirements: Path, stage: Path) -> None:
69
+ vendor = stage / "python" / "vendor"
70
+ if vendor.exists():
71
+ shutil.rmtree(vendor)
72
+ vendor.mkdir(parents=True)
73
+ command = [
74
+ sys.executable,
75
+ "-I",
76
+ "-m",
77
+ "pip",
78
+ "install",
79
+ "--isolated",
80
+ "--requirement",
81
+ str(requirements),
82
+ "--target",
83
+ str(vendor),
84
+ "--prefer-binary",
85
+ "--no-compile",
86
+ "--disable-pip-version-check",
87
+ "--no-warn-script-location",
88
+ ]
89
+ print(f"Preparing dependencies declared by {requirements.name}...")
90
+ completed = subprocess.run(command, check=False, cwd=requirements.parent)
91
+ if completed.returncode != 0:
92
+ raise PackError(f"dependency installation failed with exit code {completed.returncode}")
93
+ for cache in vendor.rglob("__pycache__"):
94
+ if cache.is_dir():
95
+ shutil.rmtree(cache)
96
+
97
+
98
+ def pack(source: Path, output: Path | None) -> Path:
99
+ source = source.resolve()
100
+ if not source.is_dir():
101
+ raise PackError(f"plugin source directory does not exist: {source}")
102
+ plugin_id, version = _manifest_identity(source)
103
+ output = (output or source / f"{plugin_id}-{version}.bppack").resolve()
104
+ if output.suffix.casefold() != ".bppack":
105
+ raise PackError("output filename must use the .bppack extension")
106
+ if output.parent != source and not output.parent.exists():
107
+ output.parent.mkdir(parents=True)
108
+
109
+ with tempfile.TemporaryDirectory(prefix="bedrockplus-pack-") as temporary:
110
+ stage = Path(temporary) / "payload"
111
+ shutil.copytree(source, stage, symlinks=True, ignore=_copy_ignore)
112
+ requirements = _requirements(source)
113
+ if requirements is not None:
114
+ _install_dependencies(requirements, stage)
115
+ else:
116
+ print("No requirements.lock or requirements.txt found; packaging without external dependencies.")
117
+ completed = subprocess.run(
118
+ [sys.executable, "-I", "-B", str(BPPACK), "build", str(stage), str(output), "--force"],
119
+ check=False,
120
+ )
121
+ if completed.returncode != 0:
122
+ raise PackError(f"package validation/build failed with exit code {completed.returncode}")
123
+
124
+ print(f"BedrockPlus plugin ready: {output}")
125
+ return output
126
+
127
+
128
+ def main() -> int:
129
+ parser = argparse.ArgumentParser(description="Build a self-contained BedrockPlus plugin package")
130
+ parser.add_argument("source", nargs="?", default=".", help="plugin folder; defaults to the current directory")
131
+ parser.add_argument("--output", help="output .bppack; defaults beside manifest.json")
132
+ arguments = parser.parse_args()
133
+ try:
134
+ pack(Path(arguments.source), Path(arguments.output) if arguments.output else None)
135
+ return 0
136
+ except PackError as error:
137
+ print(f"BedrockPlus pack failed: {error}", file=sys.stderr)
138
+ return 1
139
+
140
+
141
+ if __name__ == "__main__":
142
+ raise SystemExit(main())