This is an automated email from the ASF dual-hosted git repository. imbajin pushed a commit to branch feat/oink-core-platform in repository https://gitbox.apache.org/repos/asf/hugegraph-doc.git
commit 95718e93442e069cabd504b5cf46719dd036744e Author: dark <[email protected]> AuthorDate: Fri Sep 4 20:15:31 2026 +0800 fix(e2e): enforce archive release contracts - generate locale-aware aliases after historical builds - mark every archived page noindex,follow - validate archive robots metadata in artifacts - upgrade Playwright past the browser-download advisory --- scripts/test_versioning.py | 36 ++++++++++++++++++++++ scripts/versioning.py | 75 +++++++++++++++++++++++++++++++++++++++++++-- tests/e2e/package-lock.json | 30 +++++++++--------- tests/e2e/package.json | 2 +- 4 files changed, 124 insertions(+), 19 deletions(-) diff --git a/scripts/test_versioning.py b/scripts/test_versioning.py index 7bbf58c83..3d750f3bc 100644 --- a/scripts/test_versioning.py +++ b/scripts/test_versioning.py @@ -583,6 +583,42 @@ class VersionUrlTest(unittest.TestCase): self.assertNotIn(old, rendered) self.assertEqual(rendered.count(new), expected_count) + def test_historical_pages_are_noindex_and_aliases_are_locale_aware(self) -> None: + with tempfile.TemporaryDirectory() as temp_name: + output = Path(temp_name) + for language_prefix in ("", "cn/"): + target = ( + output + / language_prefix + / "docs/quickstart/hugegraph/hugegraph-server/index.html" + ) + target.parent.mkdir(parents=True) + target.write_text( + '<meta name="robots" content="index, follow">', + encoding="utf-8", + ) + self.assertEqual(versioning.mark_historical_pages_noindex(output), 2) + self.assertEqual( + versioning.write_historical_route_aliases( + output, ORIGIN, "versions/1.0" + ), + 2, + ) + for language_prefix in ("", "cn/"): + alias = ( + output + / language_prefix + / "docs/quickstart/hugegraph-server/index.html" + ) + body = alias.read_text(encoding="utf-8") + self.assertIn('content="noindex,follow"', body) + self.assertIn( + f"{ORIGIN}versions/1.0/{language_prefix}" + "docs/quickstart/hugegraph/hugegraph-server/", + body, + ) + self.assertFalse((output / "cn/cn").exists()) + def test_exact_legacy_content_fixes_fail_closed_on_count_drift(self) -> None: language, relative, old, _, _ = versioning.LEGACY_EXACT_CONTENT_FIXES["1.5"][0] for source in ("no expected anchor", f"{old}\n{old}"): diff --git a/scripts/versioning.py b/scripts/versioning.py index 6581879d8..8b6485a2c 100644 --- a/scripts/versioning.py +++ b/scripts/versioning.py @@ -98,6 +98,11 @@ HREFLANG_LINK_RE = re.compile( r"(?=[^>]*\bhreflang=)[^>]*>", re.IGNORECASE, ) +INDEX_FOLLOW_META_RE = re.compile( + r"<meta\b(?=[^>]*\bname=[\"']?robots(?:[\"'\s>]|$))" + r"(?=[^>]*\bcontent=[\"']?index\s*,?\s*follow(?:[\"'\s>]|$))[^>]*>", + re.IGNORECASE, +) MARKDOWN_DESTINATION_RE = re.compile( r"(?P<open>\]\(\s*<?)(?P<url>(?:https?://[^\s)>]+|/[^\s)>]+))(?P<close>>?[^)]*\))" ) @@ -692,9 +697,6 @@ def migrate_legacy_information_architecture( source = intermediate target.parent.mkdir(parents=True, exist_ok=True) source.rename(target) - old_route = "/docs/" - old_route += old_relative.removesuffix(".md").replace("/README", "") - add_frontmatter_alias(target, old_route + "/") changed += 1 summary = docs / "SUMMARY.md" text = summary.read_text(encoding="utf-8") @@ -2102,6 +2104,53 @@ def exclude_historical_sitemaps(output: pathlib.Path) -> int: return removed +def mark_historical_pages_noindex(output: pathlib.Path) -> int: + """Apply the archive indexing policy to every rendered historical page.""" + changed = 0 + for path in sorted(output.rglob("*.html")): + source = path.read_text(encoding="utf-8") + rendered, count = INDEX_FOLLOW_META_RE.subn( + '<meta name="robots" content="noindex,follow">', source + ) + if count: + path.write_text(rendered, encoding="utf-8") + changed += count + return changed + + +def write_historical_route_aliases( + output: pathlib.Path, origin: str, publish_path: str +) -> int: + """Create deterministic locale-aware redirects for migrated flat routes.""" + written = 0 + site_base = base_url(origin, publish_path) + for language_prefix in ("", "cn/"): + for old_relative, new_relative in LEGACY_IA_ROUTE_MAP.items(): + old_route = language_prefix + "docs/" + old_relative.removesuffix(".md") + old_route = old_route.replace("/README", "") + new_route = language_prefix + "docs/" + new_relative.removesuffix(".md") + target = output / new_route / "index.html" + if not target.is_file(): + continue + alias = output / old_route / "index.html" + if alias.exists(): + fail(f"historical alias collides with rendered output: {alias}") + alias.parent.mkdir(parents=True, exist_ok=True) + target_url = urllib.parse.urljoin(site_base, new_route.rstrip("/") + "/") + escaped = html.escape(target_url, quote=True) + alias.write_text( + "<!doctype html>\n" + '<html><head><meta charset="utf-8">\n' + '<meta name="robots" content="noindex,follow">\n' + f'<link rel="canonical" href="{escaped}">\n' + f'<meta http-equiv="refresh" content="0; url={escaped}">\n' + f'</head><body><a href="{escaped}">Continue</a></body></html>\n', + encoding="utf-8", + ) + written += 1 + return written + + def remove_non_equivalent_hreflang( output: pathlib.Path, origin: str, publish_path: str ) -> int: @@ -2507,6 +2556,14 @@ def validate_artifact(args: argparse.Namespace) -> None: document.feed(text) require_toc_accessible_name(document, relative) alias_target = refresh_target(document) + if entry["archived"] and relative not in {"404.html", "cn/404.html"}: + robots = [ + re.sub(r"\s+", "", item.get("content", "").lower()) + for item in document.meta + if item.get("name", "").lower() == "robots" + ] + if robots != ["noindex,follow"]: + fail(f"historical page must be noindex,follow: {relative}: {robots!r}") if alias_target: validate_url(alias_target, path) action_data = {} @@ -2932,6 +2989,16 @@ def build(args: argparse.Namespace) -> None: go_directory + os.pathsep + build_environment.get("PATH", "") ) subprocess.run(command, cwd=assembly, check=True, env=build_environment) + route_aliases = ( + write_historical_route_aliases( + output, args.site_origin, entry["publishPath"] + ) + if entry["id"] in {"1.3", "1.0"} + else 0 + ) + archived_noindex = ( + mark_historical_pages_noindex(output) if entry["archived"] else 0 + ) non_equivalent_hreflang = remove_non_equivalent_hreflang( output, args.site_origin, entry["publishPath"] ) @@ -2984,6 +3051,8 @@ def build(args: argparse.Namespace) -> None: "docsNavigation": docs_navigation, "urlScoping": url_scoping, "historicalSitemapsRemoved": historical_sitemaps, + "historicalNoindexPages": archived_noindex, + "historicalRouteAliases": route_aliases, "nonEquivalentHreflangRemoved": non_equivalent_hreflang, } ) diff --git a/tests/e2e/package-lock.json b/tests/e2e/package-lock.json index d697d2ae7..6b01049ce 100644 --- a/tests/e2e/package-lock.json +++ b/tests/e2e/package-lock.json @@ -6,26 +6,26 @@ "": { "name": "hugegraph-doc-e2e", "devDependencies": { - "@playwright/test": "1.55.0" + "@playwright/test": "1.62.1" }, "engines": { "node": ">=24" } }, "node_modules/@playwright/test": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz", - "integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.55.0" + "playwright": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/fsevents": { @@ -44,35 +44,35 @@ } }, "node_modules/playwright": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", - "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.55.0" + "playwright-core": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", - "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } } } diff --git a/tests/e2e/package.json b/tests/e2e/package.json index 31e0c64ca..e63f7d9c2 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -10,6 +10,6 @@ "test:visual": "playwright test visual.spec.js --reporter=line" }, "devDependencies": { - "@playwright/test": "1.55.0" + "@playwright/test": "1.62.1" } }
