release: public alpha 0.6.0 baseline

This commit is contained in:
JWeBot Contributors
2026-10-15 03:58:31 +08:00
commit d4242ae07c
238 changed files with 37724 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "jwebot-config"
version = "0.6.0a0+1015"
description = "Profile-based configuration compiler for JWeBot"
requires-python = ">=3.9"
dependencies = [
"tomli>=2; python_version < '3.11'",
]
[project.scripts]
jwebot-config = "jwebot_config.__main__:main"
[tool.setuptools.packages.find]
where = ["src"]
@@ -0,0 +1,39 @@
"""Profile-based configuration generation and activation for JWeBot."""
from .profile import (
CONFIG_FORMAT_VERSION,
DEFAULT_PROFILE_FILES,
ConfigError,
ConfigLayout,
activate_profile,
archive_legacy_material,
compile_profile,
export_profile,
import_profile,
load_active_environment,
load_private_context,
migrate_legacy_context,
migrate_legacy_environment,
validate_profile,
write_default_profile,
)
__all__ = [
"CONFIG_FORMAT_VERSION",
"DEFAULT_PROFILE_FILES",
"ConfigError",
"ConfigLayout",
"activate_profile",
"archive_legacy_material",
"compile_profile",
"export_profile",
"import_profile",
"load_active_environment",
"load_private_context",
"migrate_legacy_context",
"migrate_legacy_environment",
"validate_profile",
"write_default_profile",
]
__version__ = "0.6.0a0+1015"
@@ -0,0 +1,242 @@
"""Command-line interface for JWeBot configuration profiles."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from .profile import (
ConfigError,
ConfigLayout,
activate_profile,
archive_legacy_material,
compile_profile,
export_profile,
import_profile,
migrate_legacy_context,
migrate_legacy_environment,
validate_profile,
write_default_profile,
)
def progress(step: int, total: int, label: str) -> None:
width = 20
filled = round(width * step / total)
bar = "#" * filled + "-" * (width - filled)
percent = round(100 * step / total)
print(f"[{bar}] {percent:3d}% {label}")
def layout_from_args(args: argparse.Namespace) -> ConfigLayout:
return ConfigLayout.from_environment(
config_root=args.config_root,
state_root=args.state_root,
)
def add_layout_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument("--config-root", type=Path)
parser.add_argument("--state-root", type=Path)
def command_init(args: argparse.Namespace) -> int:
layout = layout_from_args(args)
progress(1, 3, "create profile")
profile = write_default_profile(args.profile, layout, force=args.force)
progress(2, 3, "compile profile")
compile_profile(profile, layout)
if args.activate:
activate_profile(args.profile, layout)
progress(3, 3, "ready")
print(f"profile={args.profile}")
return 0
def command_validate(args: argparse.Namespace) -> int:
layout = layout_from_args(args)
path = args.path or layout.profile(args.profile)
profile_id = validate_profile(path)
print(f"valid={profile_id}")
return 0
def command_compile(args: argparse.Namespace) -> int:
layout = layout_from_args(args)
progress(1, 2, "validate profile")
profile = layout.profile(args.profile)
validate_profile(profile)
progress(2, 2, "compile generated files")
generated = compile_profile(profile, layout)
print(f"generated={generated}")
return 0
def command_activate(args: argparse.Namespace) -> int:
layout = layout_from_args(args)
progress(1, 3, "validate profile")
validate_profile(layout.profile(args.profile))
progress(2, 3, "compile atomically")
activate_profile(args.profile, layout)
progress(3, 3, "activate profile")
print(f"active={args.profile}")
return 0
def command_import(args: argparse.Namespace) -> int:
layout = layout_from_args(args)
progress(1, 3, "inspect package")
profile_id = import_profile(args.source, layout, replace=args.replace)
progress(2, 3, "compile package")
compile_profile(layout.profile(profile_id), layout)
if args.activate:
activate_profile(profile_id, layout)
progress(3, 3, "import ready")
print(f"imported={profile_id}")
return 0
def command_export(args: argparse.Namespace) -> int:
layout = layout_from_args(args)
progress(1, 2, "validate profile")
validate_profile(layout.profile(args.profile))
progress(2, 2, "build private export" if args.include_private else "build redacted export")
output = export_profile(
args.profile,
args.output,
layout,
include_private=args.include_private,
)
print(f"export={output}")
return 0
def command_migrate_legacy(args: argparse.Namespace) -> int:
layout = layout_from_args(args)
profile = layout.profile(args.profile)
if not profile.exists():
progress(1, 6, "create profile")
write_default_profile(args.profile, layout)
else:
progress(1, 6, "reuse profile")
progress(2, 6, "archive incidents and debug material")
archive = archive_legacy_material(args.repository, args.profile, layout)
progress(3, 6, "import private context")
imported_context = migrate_legacy_context(
args.repository,
args.profile,
layout,
environment=args.environment,
)
progress(4, 6, "import legacy environment")
imported_environment = migrate_legacy_environment(
args.environment,
args.profile,
layout,
)
progress(5, 6, "compile profile")
compile_profile(profile, layout)
if args.activate:
activate_profile(args.profile, layout)
progress(6, 6, "migration ready")
print(f"archive={archive}")
print(f"context_imported={str(imported_context).lower()}")
print(f"environment_imported={str(imported_environment).lower()}")
return 0
def command_status(args: argparse.Namespace) -> int:
layout = layout_from_args(args)
active = layout.active_profile()
profiles = sorted(
path.name
for path in layout.profiles_root.glob("*")
if path.is_dir() and not path.name.startswith(".")
)
value = {
"config_root": str(layout.config_root),
"state_root": str(layout.state_root),
"active": active.name if active else None,
"profiles": profiles,
}
print(json.dumps(value, indent=2, sort_keys=True))
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Generate, import, validate, and activate JWeBot config profiles"
)
subparsers = parser.add_subparsers(dest="command", required=True)
init = subparsers.add_parser("init")
init.add_argument("profile")
init.add_argument("--activate", action="store_true")
init.add_argument("--force", action="store_true")
add_layout_arguments(init)
init.set_defaults(handler=command_init)
validate = subparsers.add_parser("validate")
validate.add_argument("profile", nargs="?", default="default")
validate.add_argument("--path", type=Path)
add_layout_arguments(validate)
validate.set_defaults(handler=command_validate)
compile_parser = subparsers.add_parser("compile")
compile_parser.add_argument("profile")
add_layout_arguments(compile_parser)
compile_parser.set_defaults(handler=command_compile)
activate = subparsers.add_parser("activate")
activate.add_argument("profile")
add_layout_arguments(activate)
activate.set_defaults(handler=command_activate)
import_parser = subparsers.add_parser("import")
import_parser.add_argument("source", type=Path)
import_parser.add_argument("--replace", action="store_true")
import_parser.add_argument("--activate", action="store_true")
add_layout_arguments(import_parser)
import_parser.set_defaults(handler=command_import)
export = subparsers.add_parser("export")
export.add_argument("profile")
export.add_argument("--output", type=Path, required=True)
export.add_argument("--include-private", action="store_true")
add_layout_arguments(export)
export.set_defaults(handler=command_export)
migrate = subparsers.add_parser("migrate-legacy")
migrate.add_argument("profile")
migrate.add_argument("--repository", type=Path, default=Path.cwd())
migrate.add_argument(
"--environment",
type=Path,
default=Path.home() / ".config" / "wechat-health-watch" / "environment",
)
migrate.add_argument("--activate", action="store_true")
add_layout_arguments(migrate)
migrate.set_defaults(handler=command_migrate_legacy)
status = subparsers.add_parser("status")
add_layout_arguments(status)
status.set_defaults(handler=command_status)
return parser
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
try:
return int(args.handler(args))
except ConfigError as error:
print(f"jwebot-config: {error}", file=sys.stderr)
return 2
except KeyboardInterrupt:
return 130
if __name__ == "__main__":
raise SystemExit(main())
File diff suppressed because it is too large Load Diff