scripts/ had ~80 flat files. Entry points (ci_*.sh, codegen.sh, run_tests.sh, format_native.sh, setup_hooks.sh, upload-symbols.*) and the shared pubspec_version.py stay at the root; checkers, generators, maestro tooling and release tooling move into subdirectories with their tests. Updated every reference: workflow steps, guard-test glob, Docker COPY paths and .dockerignore whitelist, website audit path, dart test imports, and regenerated the five outputs whose headers embed generator paths.
152 lines
4.8 KiB
Python
Executable File
152 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate Dart and Go relay protocol constants from relay_protocol.json."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SPEC_PATH = ROOT / "relay_protocol.json"
|
|
DART_PATH = ROOT / "lib/watch_together/services/relay_protocol.g.dart"
|
|
GO_PATH = ROOT / "server/relay_protocol_gen.go"
|
|
|
|
SUPPORTED_ID_PATTERN = r"^[A-Za-z0-9_-]+$"
|
|
|
|
|
|
def camel_to_pascal(value: str) -> str:
|
|
return value[:1].upper() + value[1:]
|
|
|
|
|
|
def validated_id_pattern(spec: dict) -> str:
|
|
try:
|
|
pattern = spec["idPattern"]
|
|
except KeyError:
|
|
raise ValueError("idPattern is required") from None
|
|
if not isinstance(pattern, str):
|
|
raise ValueError("idPattern must be a string")
|
|
if pattern != SUPPORTED_ID_PATTERN:
|
|
raise ValueError(
|
|
f"unsupported idPattern {pattern!r}; expected {SUPPORTED_ID_PATTERN!r}"
|
|
)
|
|
return pattern
|
|
|
|
|
|
def dart_source(spec: dict) -> str:
|
|
id_pattern = validated_id_pattern(spec)
|
|
lines = [
|
|
"// Generated by scripts/codegen/generate_relay_protocol.py. Do not edit.",
|
|
"",
|
|
"abstract final class RelayProtocol {",
|
|
]
|
|
lines.extend(
|
|
[
|
|
f" static const int protocolVersion = {spec['protocolVersion']};",
|
|
f" static const int legacyProtocolVersion = {spec['legacyProtocolVersion']};",
|
|
"",
|
|
]
|
|
)
|
|
for group in ("clientMessageTypes", "serverMessageTypes"):
|
|
for name, value in spec[group].items():
|
|
lines.append(f" static const String {name} = {value!r};")
|
|
for name, value in spec["errorCodes"].items():
|
|
lines.append(f" static const String {name}Code = {value!r};")
|
|
lines.append("")
|
|
for name, value in spec["limits"].items():
|
|
lines.append(f" static const int {name} = {value};")
|
|
lines.extend(
|
|
[
|
|
"",
|
|
f" static final RegExp _idPattern = RegExp(r{id_pattern!r});",
|
|
"",
|
|
" static bool isValidSessionId(String value) =>",
|
|
" value.isNotEmpty && value.length <= maxSessionIdLength && _idPattern.hasMatch(value);",
|
|
"",
|
|
" static bool isValidPeerId(String value) =>",
|
|
" value.isNotEmpty && value.length <= maxPeerIdLength && _idPattern.hasMatch(value);",
|
|
"}",
|
|
"",
|
|
]
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def go_source(spec: dict) -> str:
|
|
validated_id_pattern(spec)
|
|
lines = [
|
|
"// Code generated by scripts/codegen/generate_relay_protocol.py. DO NOT EDIT.",
|
|
"",
|
|
"package main",
|
|
"",
|
|
"const (",
|
|
]
|
|
lines.extend(
|
|
[
|
|
f"\trelayProtocolVersion = {spec['protocolVersion']}",
|
|
f"\tlegacyRelayProtocolVersion = {spec['legacyProtocolVersion']}",
|
|
"",
|
|
]
|
|
)
|
|
protocol_constants = []
|
|
for group in ("clientMessageTypes", "serverMessageTypes"):
|
|
protocol_constants.extend(
|
|
(f"relayType{camel_to_pascal(name)}", f'"{value}"')
|
|
for name, value in spec[group].items()
|
|
)
|
|
protocol_constants.extend(
|
|
(f"relayError{camel_to_pascal(name)}", f'"{value}"')
|
|
for name, value in spec["errorCodes"].items()
|
|
)
|
|
protocol_name_width = max(len(name) for name, _ in protocol_constants)
|
|
lines.extend(
|
|
f"\t{name:<{protocol_name_width}} = {value}"
|
|
for name, value in protocol_constants
|
|
)
|
|
lines.append("")
|
|
go_limit_names = {
|
|
"maxRoomSize": "maxRoomSize",
|
|
"maxMessageSize": "maxMessageSize",
|
|
"maxSessionIdLength": "maxSessionIDLength",
|
|
"maxPeerIdLength": "maxPeerIDLength",
|
|
}
|
|
limit_constants = [
|
|
(go_limit_names[name], str(value)) for name, value in spec["limits"].items()
|
|
]
|
|
limit_name_width = max(len(name) for name, _ in limit_constants)
|
|
lines.extend(
|
|
f"\t{name:<{limit_name_width}} = {value}"
|
|
for name, value in limit_constants
|
|
)
|
|
lines.extend(
|
|
[
|
|
")",
|
|
"",
|
|
"func validRelayID(value string, maxLength int) bool {",
|
|
"\tif len(value) == 0 || len(value) > maxLength {",
|
|
"\t\treturn false",
|
|
"\t}",
|
|
"\tfor _, ch := range value {",
|
|
"\t\tif (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') &&",
|
|
"\t\t\t(ch < '0' || ch > '9') && ch != '_' && ch != '-' {",
|
|
"\t\t\treturn false",
|
|
"\t\t}",
|
|
"\t}",
|
|
"\treturn true",
|
|
"}",
|
|
"",
|
|
]
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> None:
|
|
spec = json.loads(SPEC_PATH.read_text(encoding="utf-8"))
|
|
dart_output = dart_source(spec)
|
|
go_output = go_source(spec)
|
|
DART_PATH.write_text(dart_output, encoding="utf-8", newline="\n")
|
|
GO_PATH.write_text(go_output, encoding="utf-8")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|