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.
- bedrockplus/__init__.py +95 -0
- bedrockplus/__main__.py +4 -0
- bedrockplus/_assets/Initialize-BedrockPlusServer.ps1 +395 -0
- bedrockplus/_assets/README.md +85 -0
- bedrockplus/_assets/SHA256SUMS +20 -0
- bedrockplus/_assets/Start-BedrockPlus.ps1 +125 -0
- bedrockplus/_assets/THIRD_PARTY_NOTICES.md +26 -0
- bedrockplus/_assets/bedrockplus.exe +0 -0
- bedrockplus/_assets/bedrockplus_bootstrap.dll +0 -0
- bedrockplus/_assets/bedrockplus_plugin_host.dll +0 -0
- bedrockplus/_assets/bedrockplus_plugin_worker.dll +0 -0
- bedrockplus/_assets/bedrockplus_runtime.dll +0 -0
- bedrockplus/_assets/bin/bedrockplus.ps1 +192 -0
- bedrockplus/_assets/compatibility/bds/1.26.51.1/windows-x64.json +24 -0
- bedrockplus/_assets/compatibility/bds/supported-versions.json +14 -0
- bedrockplus/_assets/licenses/microsoft-detours-MIT.txt +23 -0
- bedrockplus/_assets/plugins/README.txt +11 -0
- bedrockplus/_assets/release-manifest.json +34 -0
- bedrockplus/_assets/server/README.txt +10 -0
- bedrockplus/_assets/tools/bppack.py +1865 -0
- bedrockplus/_assets/tools/init_python_plugin.py +140 -0
- bedrockplus/_assets/tools/pack_plugin.py +142 -0
- bedrockplus/_assets/tools/world_packs.py +530 -0
- bedrockplus/api.py +83 -0
- bedrockplus/cli.py +121 -0
- bedrockplus/commands.py +50 -0
- bedrockplus/events.py +138 -0
- bedrockplus/objects.py +413 -0
- bedrockplus/py.typed +1 -0
- bedrockplus-0.1.0.dev1.dist-info/METADATA +41 -0
- bedrockplus-0.1.0.dev1.dist-info/RECORD +36 -0
- bedrockplus-0.1.0.dev1.dist-info/WHEEL +4 -0
- bedrockplus-0.1.0.dev1.dist-info/entry_points.txt +2 -0
- bedrockplus-0.1.0.dev1.dist-info/licenses/THIRD_PARTY_NOTICES.md +26 -0
- bedrockplus-0.1.0.dev1.dist-info/licenses/microsoft-detours-MIT.txt +23 -0
- 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())
|