Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates. Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
97 lines
5.1 KiB
Python
97 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Build and run an isolated local platform + fake OneOS, without external services.
|
|
|
|
Requires installed Go/Node dependencies. No install, SSH, migration, cloud CLI,
|
|
external fetch or production configuration is used. Ctrl-C stops both children.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import signal
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
import urllib.request
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--skip-web-build", action="store_true")
|
|
args = parser.parse_args()
|
|
run = Path(tempfile.mkdtemp(prefix="vehicle-oneos-local-"))
|
|
os.chmod(run, 0o700)
|
|
# Build tools need installed paths/cache, but no inherited service credentials.
|
|
clean = {key: os.environ[key] for key in ("PATH", "HOME", "TMPDIR") if key in os.environ}
|
|
clean.update({"GOPROXY": "off", "GOSUMDB": "off", "GOTOOLCHAIN": "local", "NO_UPDATE_NOTIFIER": "1"})
|
|
children, logs = [], []
|
|
try:
|
|
for name, package in (("fixture", "./cmd/oneos-local-fixture"), ("platform", "./cmd/platform-api")):
|
|
subprocess.run([shutil.which("go"), "build", "-o", str(run / name), package], cwd=ROOT / "apps/api", env=clean, check=True)
|
|
if not args.skip_web_build:
|
|
with (run / "web-build.log").open("w") as build_log:
|
|
subprocess.run([shutil.which("pnpm"), "build"], cwd=ROOT / "apps/web", env=clean, stdout=build_log, stderr=subprocess.STDOUT, check=True)
|
|
if not (ROOT / "apps/web/dist/index.html").is_file():
|
|
raise RuntimeError("local frontend build is missing")
|
|
config_file = run / "fixture.json"
|
|
fixture_log = (run / "fixture.log").open("w"); logs.append(fixture_log)
|
|
children.append(subprocess.Popen([str(run / "fixture"), "-config-file", str(config_file)], env=clean, stdout=fixture_log, stderr=subprocess.STDOUT))
|
|
deadline = time.monotonic() + 10
|
|
while not config_file.exists():
|
|
if children[-1].poll() is not None or time.monotonic() > deadline:
|
|
raise RuntimeError("local fixture failed to start; see fixture.log")
|
|
time.sleep(0.1)
|
|
fixture = json.loads(config_file.read_text())
|
|
api_env = dict(clean)
|
|
api_env.update({
|
|
"HTTP_ADDR": "127.0.0.1:20400", "DATA_MODE": "mock", "AUTH_MODE": "enforce",
|
|
"STATIC_DIR": str(ROOT / "apps/web/dist"), "EXPORT_DIR": str(run / "exports"),
|
|
"ONEOS_LOCAL_ONLY": "true", "ONEOS_AUTH_ENABLED": "true", "ONEOS_PASSWORD_ENABLED": "true",
|
|
"ONEOS_PROFILE_BASE_URL": fixture["baseURL"],
|
|
"ONEOS_PROFILE_ISSUER": fixture["issuer"], "ONEOS_PROFILE_ALLOWED_TENANT": fixture["tenantId"],
|
|
"ONEOS_PUBLIC_ORIGIN": fixture["publicOrigin"], "ONEOS_PASSWORD_CLIENT_ID": fixture["clientId"],
|
|
"ONEOS_PASSWORD_RSA_PUBLIC_KEY": fixture["publicKey"],
|
|
"ONEOS_LOGIN_TICKET_EXCHANGE_PATH": "/auth/inner/v1/sso/vehicle-platform/token-exchange",
|
|
"ONEOS_LOGIN_TICKET_SERVICE_TOKEN": fixture["serviceToken"],
|
|
"ONEOS_VEHICLE_SCOPE_PATH": "/asset/vehicle-platform/scope",
|
|
})
|
|
api_log = (run / "platform.log").open("w"); logs.append(api_log)
|
|
children.append(subprocess.Popen([str(run / "platform")], env=api_env, stdout=api_log, stderr=subprocess.STDOUT))
|
|
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
|
deadline = time.monotonic() + 10
|
|
while True:
|
|
try:
|
|
with opener.open("http://127.0.0.1:20400/api/v2/auth/providers", timeout=1) as resp:
|
|
providers = json.load(resp)["data"]
|
|
if not providers["oneos"]["passwordEnabled"] or not providers["oneos"]["ssoEnabled"]:
|
|
raise RuntimeError("local shared-token authentication not ready")
|
|
break
|
|
except (OSError, ValueError, KeyError):
|
|
if children[-1].poll() is not None or time.monotonic() > deadline:
|
|
raise RuntimeError("local platform failed to start; see platform.log")
|
|
time.sleep(0.1)
|
|
print(json.dumps({"platform": fixture["publicOrigin"], "oneosFixture": fixture["baseURL"], "runDirectory": str(run), "fixtureConfig": str(config_file), "localOnly": True}), flush=True)
|
|
while all(child.poll() is None for child in children):
|
|
time.sleep(0.5)
|
|
raise RuntimeError("one of the local child processes exited")
|
|
except KeyboardInterrupt:
|
|
pass
|
|
finally:
|
|
for child in reversed(children):
|
|
if child.poll() is None:
|
|
child.terminate()
|
|
for child in children:
|
|
try: child.wait(timeout=5)
|
|
except subprocess.TimeoutExpired: child.kill(); child.wait()
|
|
for handle in logs: handle.close()
|
|
# Keep evidence and fixtures for local inspection; directory is mode 0700.
|
|
print(f"Local processes stopped. Local evidence: {run}", flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
signal.signal(signal.SIGTERM, lambda *_: (_ for _ in ()).throw(KeyboardInterrupt()))
|
|
main()
|