bitflicker64 commented on code in PR #496:
URL: https://github.com/apache/hugegraph-doc/pull/496#discussion_r4078693305


##########
scripts/update_oink.py:
##########
@@ -0,0 +1,141 @@
+#!/usr/bin/env python3
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0.
+
+"""Explicit, review-gated OINK upgrades using the existing site validators."""
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import subprocess
+import sys
+import tempfile
+
+from oink_module import MODULE, VERSION, command, download_locked, 
locked_version
+
+ROOT = Path(__file__).resolve().parents[1]
+BASELINE = ROOT / "scripts/oink-overrides.json"
+WATCH = ("assets/js/sidebar-state.js", "assets/js/sidebar-nav.js",
+         "assets/js/docs-shell.js", "assets/js/search.js", 
"assets/js/search-engine.js",
+         "assets/js/surface-coordinator.js", "assets/js/command-palette.js")
+
+
+def run(args, *, cwd=ROOT, env=None):
+    print("+ " + " ".join(map(str, args)), flush=True)
+    subprocess.run(list(map(str, args)), cwd=cwd, env=env, check=True)
+
+
+def snapshot(root, upstream):
+    paths = set(WATCH)
+    # Project SCSS depends on upstream selectors even without a same-name 
override.
+    paths.update(str(p.relative_to(upstream)) for p in (upstream / 
"assets/scss").rglob("*.scss"))
+    for folder in ("layouts", "assets", "i18n"):
+        def key(relative):
+            name = relative.as_posix()
+            # Locale filenames are case-insensitive; record the upstream 
spelling
+            # rather than letting the host filesystem decide which file 
matches.
+            return name.casefold().replace("_", "-") if folder == "i18n" else 
name
+
+        upstream_paths = {key(p.relative_to(upstream)): 
p.relative_to(upstream).as_posix()
+                          for p in (upstream / folder).rglob("*") if 
p.is_file()}
+        paths.update(upstream_paths[key(p.relative_to(root))]
+                     for p in (root / folder).rglob("*")
+                     if p.is_file() and key(p.relative_to(root)) in 
upstream_paths)
+    return {p: hashlib.sha256((upstream / p).read_bytes()).hexdigest()
+            if (upstream / p).is_file() else None for p in sorted(paths)}
+
+
+def changes(previous, current):
+    return [p for p in sorted(previous.keys() | current.keys()) if 
previous.get(p) != current.get(p)]
+
+
+def preflight(root, target, resume):
+    if not VERSION.fullmatch(target):
+        raise ValueError("use an explicit release such as v1.1.0; branches and 
latest are not accepted")
+    if resume:
+        if locked_version(root) != target:
+            raise ValueError("--resume only validates the already-pinned 
target; it cannot switch versions")
+    elif subprocess.check_output(["git", "status", "--porcelain"], cwd=root, 
text=True).strip():
+        raise ValueError("working tree is not clean; commit/stash your work 
first, or use --resume for this pinned target")
+
+
+def validate(work):
+    py = sys.executable
+    cli = [py, "scripts/versioning.py"]
+    origin = "https://hugegraph.apache.org/";
+    historical = "https://hugegraph.apache.org/";
+    run(["bash", "dist/validate-links.sh"])
+    run([py, "-m", "unittest", "discover", "-s", "scripts", "-p", "test_*.py"])
+    run(["scripts/hugo.sh", "build", "--destination", work / "strict"])
+    resolved = work / "resolved.json"
+    run([*cli, "prepare", "--output", resolved])
+    manifest = json.loads(resolved.read_text())
+    for entry in manifest["versions"]:
+        artifact = work / "artifacts" / entry["id"]
+        run([*cli, "build", "--version", entry["id"], "--sha", entry["sha"],
+             "--site-origin", origin, "--historical-origin", historical, 
"--output", artifact])
+    # aggregate validates every version artifact before merging it.
+    run([*cli, "aggregate", "--resolved-manifest", resolved,
+         "--artifacts", work / "artifacts", "--site-origin", origin,
+         "--historical-origin", historical, "--output", work / "site"])
+    run([py, "-m", "unittest", 
"scripts.test_download_data.DownloadDataTest.test_rendered_download_pages_have_verified_rows",
 "-v"],
+        env=dict(os.environ, DOWNLOAD_PUBLIC_DIR=str(work / "site")))
+    run([*cli, "config", "--version", "latest", "--site-origin", 
"http://127.0.0.1:4174/";,
+         "--historical-origin", historical, "--output", work / "ai.json"])
+    run([os.environ.get("HUGO_BIN", "hugo"), "--config",
+         f"hugo.yaml,{work / 'ai.json'},tests/e2e/ai-enabled.yaml",
+         "--destination", work / "ai", "--cleanDestinationDir", "--gc", 
"--minify",
+         "--environment", "production", "--panicOnWarning"])
+    env = dict(os.environ, SITE_ROOT=str(work / "site"), AI_SITE_ROOT=str(work 
/ "ai"))
+    run(["npm", "ci"], cwd=ROOT / "tests/e2e")
+    run(["npx", "playwright", "install", "chromium"], cwd=ROOT / "tests/e2e")
+    run(["npm", "run", "test:ci"], cwd=ROOT / "tests/e2e", env=env)
+    run(["npm", "run", "test:visual"], cwd=ROOT / "tests/e2e", env=env)
+
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("version", nargs="?")
+    parser.add_argument("--check-baseline", action="store_true", 
help="read-only CI check of reviewed upstream files")
+    parser.add_argument("--resume", action="store_true", help="validate/adapt 
the already-pinned target in a dirty worktree")
+    parser.add_argument("--accept-reviewed", action="store_true", help="record 
human review of reported upstream changes; still run every validator")
+    args = parser.parse_args()
+    if args.check_baseline:
+        module = download_locked(ROOT)
+        baseline = json.loads(BASELINE.read_text())
+        if (baseline["version"] != module["Version"]
+                or changes(baseline["files"], snapshot(ROOT, 
Path(module["Dir"])))):
+            raise ValueError("OINK upstream files differ from reviewed 
baseline; follow scripts/oink-upgrade.md")

Review Comment:
   🧹 This check fails for local changes too, but the message says upstream 
files differ and does not name any file. `snapshot()` builds its inventory from 
the site's own `layouts/`, `assets/` and `i18n/`, so adding or removing a 
same-name override changes the key set while OINK stays at v1.1.0.
   
   Reproduced at this head: `--check-baseline` prints `Reviewed OINK upstream 
baseline matches`. After copying upstream `layouts/_partials/breadcrumb.html` 
into the site byte for byte, it exits 1 with `OINK upstream files differ from 
reviewed baseline; follow scripts/oink-upgrade.md. Changes and artifacts are 
retained.` A contributor gets this from all five `Build <version>` jobs, and 
the SOP only describes the version-upgrade flow.
   
   Please include `changes(...)` in the error and say whether a path was added, 
removed or changed. The SOP or contribution.md should also say that adding or 
removing an override needs `scripts/update-oink.sh <pinned> --resume 
--accept-reviewed`. The trailing "Changes and artifacts are retained" from 
`__main__` does not apply to this read-only mode.



##########
scripts/update_oink.py:
##########
@@ -0,0 +1,141 @@
+#!/usr/bin/env python3
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0.
+
+"""Explicit, review-gated OINK upgrades using the existing site validators."""
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import subprocess
+import sys
+import tempfile
+
+from oink_module import MODULE, VERSION, command, download_locked, 
locked_version
+
+ROOT = Path(__file__).resolve().parents[1]
+BASELINE = ROOT / "scripts/oink-overrides.json"
+WATCH = ("assets/js/sidebar-state.js", "assets/js/sidebar-nav.js",
+         "assets/js/docs-shell.js", "assets/js/search.js", 
"assets/js/search-engine.js",
+         "assets/js/surface-coordinator.js", "assets/js/command-palette.js")
+
+
+def run(args, *, cwd=ROOT, env=None):
+    print("+ " + " ".join(map(str, args)), flush=True)
+    subprocess.run(list(map(str, args)), cwd=cwd, env=env, check=True)
+
+
+def snapshot(root, upstream):
+    paths = set(WATCH)
+    # Project SCSS depends on upstream selectors even without a same-name 
override.
+    paths.update(str(p.relative_to(upstream)) for p in (upstream / 
"assets/scss").rglob("*.scss"))
+    for folder in ("layouts", "assets", "i18n"):
+        def key(relative):
+            name = relative.as_posix()
+            # Locale filenames are case-insensitive; record the upstream 
spelling
+            # rather than letting the host filesystem decide which file 
matches.
+            return name.casefold().replace("_", "-") if folder == "i18n" else 
name
+
+        upstream_paths = {key(p.relative_to(upstream)): 
p.relative_to(upstream).as_posix()
+                          for p in (upstream / folder).rglob("*") if 
p.is_file()}
+        paths.update(upstream_paths[key(p.relative_to(root))]
+                     for p in (root / folder).rglob("*")
+                     if p.is_file() and key(p.relative_to(root)) in 
upstream_paths)
+    return {p: hashlib.sha256((upstream / p).read_bytes()).hexdigest()
+            if (upstream / p).is_file() else None for p in sorted(paths)}
+
+
+def changes(previous, current):
+    return [p for p in sorted(previous.keys() | current.keys()) if 
previous.get(p) != current.get(p)]
+
+
+def preflight(root, target, resume):
+    if not VERSION.fullmatch(target):
+        raise ValueError("use an explicit release such as v1.1.0; branches and 
latest are not accepted")
+    if resume:
+        if locked_version(root) != target:
+            raise ValueError("--resume only validates the already-pinned 
target; it cannot switch versions")
+    elif subprocess.check_output(["git", "status", "--porcelain"], cwd=root, 
text=True).strip():
+        raise ValueError("working tree is not clean; commit/stash your work 
first, or use --resume for this pinned target")
+
+
+def validate(work):
+    py = sys.executable
+    cli = [py, "scripts/versioning.py"]
+    origin = "https://hugegraph.apache.org/";
+    historical = "https://hugegraph.apache.org/";
+    run(["bash", "dist/validate-links.sh"])
+    run([py, "-m", "unittest", "discover", "-s", "scripts", "-p", "test_*.py"])
+    run(["scripts/hugo.sh", "build", "--destination", work / "strict"])
+    resolved = work / "resolved.json"
+    run([*cli, "prepare", "--output", resolved])
+    manifest = json.loads(resolved.read_text())
+    for entry in manifest["versions"]:
+        artifact = work / "artifacts" / entry["id"]
+        run([*cli, "build", "--version", entry["id"], "--sha", entry["sha"],
+             "--site-origin", origin, "--historical-origin", historical, 
"--output", artifact])
+    # aggregate validates every version artifact before merging it.
+    run([*cli, "aggregate", "--resolved-manifest", resolved,
+         "--artifacts", work / "artifacts", "--site-origin", origin,
+         "--historical-origin", historical, "--output", work / "site"])
+    run([py, "-m", "unittest", 
"scripts.test_download_data.DownloadDataTest.test_rendered_download_pages_have_verified_rows",
 "-v"],
+        env=dict(os.environ, DOWNLOAD_PUBLIC_DIR=str(work / "site")))
+    run([*cli, "config", "--version", "latest", "--site-origin", 
"http://127.0.0.1:4174/";,
+         "--historical-origin", historical, "--output", work / "ai.json"])
+    run([os.environ.get("HUGO_BIN", "hugo"), "--config",
+         f"hugo.yaml,{work / 'ai.json'},tests/e2e/ai-enabled.yaml",
+         "--destination", work / "ai", "--cleanDestinationDir", "--gc", 
"--minify",
+         "--environment", "production", "--panicOnWarning"])
+    env = dict(os.environ, SITE_ROOT=str(work / "site"), AI_SITE_ROOT=str(work 
/ "ai"))
+    run(["npm", "ci"], cwd=ROOT / "tests/e2e")
+    run(["npx", "playwright", "install", "chromium"], cwd=ROOT / "tests/e2e")
+    run(["npm", "run", "test:ci"], cwd=ROOT / "tests/e2e", env=env)
+    run(["npm", "run", "test:visual"], cwd=ROOT / "tests/e2e", env=env)
+
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("version", nargs="?")
+    parser.add_argument("--check-baseline", action="store_true", 
help="read-only CI check of reviewed upstream files")
+    parser.add_argument("--resume", action="store_true", help="validate/adapt 
the already-pinned target in a dirty worktree")
+    parser.add_argument("--accept-reviewed", action="store_true", help="record 
human review of reported upstream changes; still run every validator")
+    args = parser.parse_args()
+    if args.check_baseline:
+        module = download_locked(ROOT)
+        baseline = json.loads(BASELINE.read_text())
+        if (baseline["version"] != module["Version"]
+                or changes(baseline["files"], snapshot(ROOT, 
Path(module["Dir"])))):
+            raise ValueError("OINK upstream files differ from reviewed 
baseline; follow scripts/oink-upgrade.md")
+        print("Reviewed OINK upstream baseline matches")
+        return
+    if args.version is None:
+        parser.error("version is required for upgrades")
+    preflight(ROOT, args.version, args.resume)
+    previous = json.loads(BASELINE.read_text())
+    work = Path(tempfile.mkdtemp(prefix="hugegraph-oink-"))
+    print(f"Upgrade artifacts and review report: {work}", flush=True)
+    if not args.resume and locked_version(ROOT) != args.version:
+        command(["get", MODULE + "@" + args.version], ROOT)

Review Comment:
   🧹 `go get` keeps the old version's checksums, so the documented upgrade 
leaves four lines in go.sum. The SOP says "The theme version and its two 
checksums live only in go.mod / go.sum", and it forbids `go mod tidy`, which 
would drop the `require` here because nothing imports the module.
   
   Reproduced on a copy of this head's go.mod/go.sum: `go get 
github.com/pgsty/[email protected]` rewrites the `require` but keeps both `v1.1.0` 
lines next to the new `v1.0.0` lines. `download_locked()` still passes, so 
nothing catches the leftovers, and they build up with each upgrade. This PR's 
go.sum has two lines, so it was cleaned by hand.
   
   Please rewrite go.sum after `go get` to only the `MODULE version` and 
`MODULE version/go.mod` lines, or make `download_locked()` reject extra entries 
so the command and the SOP agree.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to