feat: complete production workflow migration

This commit is contained in:
2026-08-06 14:29:57 +08:00
parent 7f215e79c4
commit 8df5266abb
448 changed files with 56937 additions and 14619 deletions
+22 -28
View File
@@ -8,8 +8,8 @@ from pathlib import Path, PurePosixPath
from gyxx_flow.security.scanner import scan_repository
PROJECT_ROOT = Path(__file__).parents[1]
RUNTIME_ROOT = (
PROJECT_ROOT / "src" / "gyxx_flow" / "modules" / "content_marketing" / "runtime"
MODULE_ROOT = (
PROJECT_ROOT / "src" / "gyxx_flow" / "modules" / "content_marketing"
)
MANIFEST_PATH = (
PROJECT_ROOT / "config" / "source-manifests" / "content_marketing.json"
@@ -32,10 +32,8 @@ FIXED_USER_PATHS = (
EXPECTED_CATEGORIES = {
"config",
"dependency",
"launcher",
"source",
"sql",
"test",
"tool",
}
REQUIRED_SOURCES = {
@@ -44,14 +42,13 @@ REQUIRED_SOURCES = {
"pgy_xhs_scraper_v2.py",
"xingtu_scraper_v2.py",
"chanmama_scraper.py",
"daily_creator_exposure_scope.py",
"daily_marketing_report.py",
"data/tools/daily_run.bat",
"data/tools/friday_relogin_parallel.py",
"data/tools/schema_gyxx_super_data.sql",
"data/tools/migrations/005_style_product_profile.sql",
"data/config/db.env.example",
"data/config/款式_多维表格_对照.json",
"tests/test_collection_completeness.py",
}
@@ -74,7 +71,7 @@ def test_content_source_snapshot_manifest_is_complete_and_well_formed() -> None:
assert manifest["module"] == "content_marketing"
files = manifest["files"]
assert isinstance(files, list)
assert len(files) == 90
assert len(files) == 65
sources = [entry["source_relative_path"] for entry in files]
targets = [entry["target_relative_path"] for entry in files]
@@ -88,13 +85,13 @@ def test_content_source_snapshot_manifest_is_complete_and_well_formed() -> None:
relative = PurePosixPath(entry["target_relative_path"])
assert not relative.is_absolute()
assert ".." not in relative.parts
assert relative.parts[:5] == (
assert relative.parts[:4] == (
"src",
"gyxx_flow",
"modules",
"content_marketing",
"runtime",
)
assert relative.parts[4] != "runtime"
assert SHA256.fullmatch(entry["source_sha256"])
assert SHA256.fullmatch(entry["target_sha256"])
assert entry["transformed"] is (
@@ -110,7 +107,7 @@ def test_content_source_snapshot_targets_exist_and_match_manifest_hashes() -> No
generated = _manifest()["generated_files"]
assert [entry["target_relative_path"] for entry in generated] == [
"src/gyxx_flow/modules/content_marketing/runtime/runtime_paths.py"
"src/gyxx_flow/modules/content_marketing/runtime_paths.py"
]
for entry in generated:
target = PROJECT_ROOT.joinpath(*PurePosixPath(entry["target_relative_path"]).parts)
@@ -133,6 +130,9 @@ def test_content_source_snapshot_exclusions_cover_runtime_and_sensitive_state()
"data/notes/**",
"data/reports/**",
"data/tmp/**",
"tests/**",
"**/*.bat",
"**/*.ps1",
} <= excluded
@@ -140,57 +140,51 @@ def test_content_source_snapshot_has_no_legacy_roots_or_plaintext_credentials()
manifest_text = MANIFEST_PATH.read_text(encoding="utf-8").casefold()
assert not any(root in manifest_text for root in OLD_PROJECT_ROOTS)
for path in RUNTIME_ROOT.rglob("*"):
for path in MODULE_ROOT.rglob("*"):
if not path.is_file() or b"\x00" in path.read_bytes():
continue
text = path.read_text(encoding="utf-8-sig", errors="strict").casefold()
assert not any(root in text for root in OLD_PROJECT_ROOTS), path
assert not any(root in text for root in FIXED_USER_PATHS), path
assert scan_repository(RUNTIME_ROOT) == []
assert scan_repository(MODULE_ROOT) == []
def test_chanmama_credentials_are_externalized_in_the_copied_source() -> None:
source = (RUNTIME_ROOT / "chanmama_scraper.py").read_text(encoding="utf-8-sig")
source = (MODULE_ROOT / "chanmama_scraper.py").read_text(encoding="utf-8-sig")
assert 'os.getenv("CHANMAMA_ACCOUNT", "")' in source
assert 'os.getenv("CHANMAMA_PASSWORD", "")' in source
assert not re.search(r'^PASSWORD\s*=\s*["\'][^"\']+["\']', source, re.MULTILINE)
def test_content_runtime_has_one_portable_path_boundary() -> None:
python_sources = list(RUNTIME_ROOT.rglob("*.py"))
def test_content_module_has_one_portable_path_boundary() -> None:
python_sources = list(MODULE_ROOT.rglob("*.py"))
path_mutations: list[Path] = []
forbidden_cross_module_imports: list[Path] = []
for path in python_sources:
source = path.read_text(encoding="utf-8-sig")
if re.search(r"sys\.path\.(?:insert|append)\(", source):
path_mutations.append(path.relative_to(RUNTIME_ROOT))
path_mutations.append(path.relative_to(MODULE_ROOT))
if "lark_cli_runtime" in source:
forbidden_cross_module_imports.append(path.relative_to(RUNTIME_ROOT))
forbidden_cross_module_imports.append(path.relative_to(MODULE_ROOT))
assert path_mutations == []
assert forbidden_cross_module_imports == []
runtime_paths = (
RUNTIME_ROOT / "runtime_paths.py"
MODULE_ROOT / "runtime_paths.py"
).read_text(encoding="utf-8-sig")
assert "GYXX_DATA_ROOT" in runtime_paths
assert "GYXX_MODULE_ROOT" in runtime_paths
def test_content_runtime_uses_portable_python_launchers_and_data_paths() -> None:
batch_sources = list(RUNTIME_ROOT.rglob("*.bat"))
assert batch_sources
def test_content_module_uses_python_commands_and_portable_data_paths() -> None:
assert list(MODULE_ROOT.rglob("*.bat")) == []
assert list(MODULE_ROOT.rglob("*.ps1")) == []
for path in batch_sources:
source = path.read_text(encoding="utf-8-sig").casefold()
assert "%gyxx_python%" in source, path
assert "hermes-agent" not in source, path
assert "appdata" not in source, path
path_constants = (RUNTIME_ROOT / "runtime_paths.py").read_text(
path_constants = (MODULE_ROOT / "runtime_paths.py").read_text(
encoding="utf-8-sig"
)
assert 'DataLayout(data_root).for_module("content_marketing")' in path_constants