chore: import upstream snapshot with attribution
Auto Update PR / update-prs (push) Has been cancelled
CI / format-check (push) Has been cancelled
CI / test (3.10) (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / live-api-tests (push) Has been cancelled
CI / plugin-integration-test (push) Has been cancelled
CI / ollama-integration-test (push) Has been cancelled
CI / test-fork-pr (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:37:14 +08:00
commit 76d991c447
147 changed files with 43242 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# Copyright 2025 Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Script to add new required status checks to an existing branch protection rule.
# This preserves all your current settings and just adds the new checks
echo "Adding new PR validation checks to existing branch protection..."
# Add the new checks to existing ones
echo "Adding new checks: enforce, size, and protect-infrastructure..."
gh api repos/:owner/:repo/branches/main/protection/required_status_checks/contexts \
--method POST \
--input - <<< '["enforce", "size", "protect-infrastructure"]'
echo ""
echo "✓ New checks added!"
echo ""
echo "Updated required status checks will include:"
echo "- test (3.10) [existing]"
echo "- test (3.11) [existing]"
echo "- test (3.12) [existing]"
echo "- Validate PR Template [existing]"
echo "- live-api-tests [existing]"
echo "- ollama-integration-test [existing]"
echo "- enforce [NEW - linked issue validation]"
echo "- size [NEW - PR size limit]"
echo "- protect-infrastructure [NEW - infrastructure file protection]"
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
# Copyright 2025 Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Add size labels to PRs based on their change count
echo "Adding size labels to PRs..."
# Get all open PRs with their additions and deletions
gh pr list --limit 50 --json number,additions,deletions --jq '.[]' | while read -r pr_data; do
pr_number=$(echo "$pr_data" | jq -r '.number')
additions=$(echo "$pr_data" | jq -r '.additions')
deletions=$(echo "$pr_data" | jq -r '.deletions')
total_changes=$((additions + deletions))
# Determine size label
if [ $total_changes -lt 50 ]; then
size_label="size/XS"
elif [ $total_changes -lt 150 ]; then
size_label="size/S"
elif [ $total_changes -lt 600 ]; then
size_label="size/M"
elif [ $total_changes -lt 1000 ]; then
size_label="size/L"
else
size_label="size/XL"
fi
echo "PR #$pr_number: $total_changes lines -> $size_label"
# Remove any existing size labels first
existing_labels=$(gh pr view $pr_number --json labels --jq '.labels[].name' | grep "^size/" || true)
if [ ! -z "$existing_labels" ]; then
echo " Removing existing label: $existing_labels"
gh pr edit $pr_number --remove-label "$existing_labels"
fi
# Add the new size label
gh pr edit $pr_number --add-label "$size_label"
sleep 1 # Avoid rate limiting
done
echo "Done adding size labels!"
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
# Copyright 2025 Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Revalidate all open PRs
echo "Fetching all open PRs..."
PR_NUMBERS=$(gh pr list --limit 50 --json number --jq '.[].number')
TOTAL=$(echo "$PR_NUMBERS" | wc -w | tr -d ' ')
echo "Found $TOTAL open PRs"
echo "Starting revalidation..."
echo ""
COUNT=0
for pr in $PR_NUMBERS; do
COUNT=$((COUNT + 1))
echo "[$COUNT/$TOTAL] Triggering revalidation for PR #$pr..."
gh workflow run revalidate-pr.yml -f pr_number=$pr
# Small delay to avoid rate limiting
sleep 2
done
echo ""
echo "All workflows triggered!"
echo ""
echo "To monitor progress:"
echo " gh run list --workflow=revalidate-pr.yml --limit=$TOTAL"
echo ""
echo "To see results, check comments on each PR"
+217
View File
@@ -0,0 +1,217 @@
#!/usr/bin/env python3
# Copyright 2025 Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Publish a new version to Zenodo via the InvenioRDM Records API.
Zenodo migrated to InvenioRDM in late 2023. The legacy /api/deposit
endpoints still exist but /actions/newversion now rejects records with
files attached ("Please remove all files first" on field files.enabled).
This script uses the modern /api/records flow instead.
Reads project name from pyproject.toml. ZENODO_RECORD_ID may be either
the concept ID or any version's record ID — Zenodo resolves to the
latest version.
"""
import glob
import os
import re
import sys
import tomllib
import urllib.request
import requests
API = "https://zenodo.org/api"
TOKEN = os.environ["ZENODO_TOKEN"]
RECORD_ID = os.environ["ZENODO_RECORD_ID"]
VERSION = os.environ["RELEASE_TAG"].lstrip("v")
REPO = os.environ["GITHUB_REPOSITORY"]
SERVER = os.environ.get("GITHUB_SERVER_URL", "https://github.com")
AUTH = {"Authorization": f"Bearer {TOKEN}"}
ACCEPT_HEADERS = {**AUTH, "Accept": "application/vnd.inveniordm.v1+json"}
JSON_HEADERS = {**ACCEPT_HEADERS, "Content-Type": "application/json"}
try:
with open("pyproject.toml", "rb") as f:
pyproject = tomllib.load(f)
PROJECT_META = pyproject["project"]
PROJECT = PROJECT_META["name"]
except (KeyError, FileNotFoundError) as e:
print(f"❌ Error loading project metadata: {e}", file=sys.stderr)
sys.exit(1)
def _check(r: requests.Response, op: str) -> None:
"""raise_for_status with the response body included for debugging."""
if not r.ok:
body = r.text[:2000] if r.text else "<empty>"
print(f"❌ Zenodo {op} failed: {r.status_code} {r.reason}", file=sys.stderr)
print(f" URL: {r.request.url}", file=sys.stderr)
print(f" Response body: {body}", file=sys.stderr)
r.raise_for_status()
def new_version_draft(record_id: str) -> dict:
"""Create a new version draft via the InvenioRDM Records API.
If a draft already exists for the record, the API returns it instead
of creating a duplicate.
"""
r = requests.post(
f"{API}/records/{record_id}/versions",
headers=JSON_HEADERS,
timeout=30,
)
_check(r, "create new version")
return r.json()
def published_metadata(record_id: str) -> dict:
"""Read required metadata from the latest published version."""
r = requests.get(
f"{API}/records/{record_id}",
headers=ACCEPT_HEADERS,
timeout=30,
)
_check(r, "GET published record")
return r.json().get("metadata", {})
def release_date() -> str | None:
"""Return the release date from CITATION.cff, if present."""
try:
with open("CITATION.cff", encoding="utf-8") as f:
match = re.search(
r"^date-released:\s*([0-9]{4}-[0-9]{2}-[0-9]{2})$",
f.read(),
re.MULTILINE,
)
except FileNotFoundError:
return None
return match.group(1) if match else None
def upload_file(draft_id: str, path: str, dest_name: str = None) -> None:
"""Register, upload, and commit a file on a draft (3-step RDM flow)."""
dest = dest_name or os.path.basename(path)
files_url = f"{API}/records/{draft_id}/draft/files"
init = requests.post(
files_url, headers=JSON_HEADERS, json=[{"key": dest}], timeout=30
)
if init.status_code == 400 and "already exists" in init.text.lower():
# Re-running over an existing draft; remove the stale entry first.
del_r = requests.delete(f"{files_url}/{dest}", headers=AUTH, timeout=30)
_check(del_r, f"delete stale file {dest}")
init = requests.post(
files_url, headers=JSON_HEADERS, json=[{"key": dest}], timeout=30
)
_check(init, f"register file {dest}")
with open(path, "rb") as fp:
up = requests.put(
f"{files_url}/{dest}/content",
data=fp,
headers={**AUTH, "Content-Type": "application/octet-stream"},
timeout=300,
)
_check(up, f"upload {dest}")
commit = requests.post(f"{files_url}/{dest}/commit", headers=AUTH, timeout=30)
_check(commit, f"commit {dest}")
def update_metadata(draft_id: str) -> None:
"""Patch version-specific fields while preserving required metadata."""
r = requests.get(
f"{API}/records/{draft_id}/draft",
headers=ACCEPT_HEADERS,
timeout=30,
)
_check(r, "GET draft")
draft_metadata = r.json().get("metadata", {})
source_metadata = published_metadata(RECORD_ID)
metadata = dict(source_metadata)
metadata.update(draft_metadata)
for field in ("creators", "description", "publication_date"):
if not metadata.get(field) and source_metadata.get(field):
metadata[field] = source_metadata[field]
metadata["title"] = f"{PROJECT.replace('-', ' ').title()} v{VERSION}"
metadata["version"] = VERSION
if released := release_date():
metadata["publication_date"] = released
# InvenioRDM resource type schema differs from the legacy upload_type
# enum; "software" is the canonical id.
metadata["resource_type"] = {"id": "software"}
r = requests.put(
f"{API}/records/{draft_id}/draft",
headers=JSON_HEADERS,
json={"metadata": metadata},
timeout=30,
)
_check(r, "PUT metadata")
def publish_draft(draft_id: str) -> dict:
r = requests.post(
f"{API}/records/{draft_id}/draft/actions/publish",
headers=JSON_HEADERS,
timeout=60,
)
_check(r, "publish")
return r.json()
def main() -> int:
try:
draft = new_version_draft(RECORD_ID)
draft_id = draft["id"]
print(f"️ Working on draft id={draft_id}", file=sys.stderr)
tarball = f"/tmp/{PROJECT}-v{VERSION}.tar.gz"
src_url = f"{SERVER}/{REPO}/archive/refs/tags/v{VERSION}.tar.gz"
urllib.request.urlretrieve(src_url, tarball)
upload_file(draft_id, tarball, f"{PROJECT}-{VERSION}.tar.gz")
for path in glob.glob("dist/*"):
upload_file(draft_id, path)
update_metadata(draft_id)
record = publish_draft(draft_id)
doi = record.get("doi") or record.get("pids", {}).get("doi", {}).get(
"identifier"
)
record_id = record.get("id")
print(f"✅ Published to Zenodo: https://doi.org/{doi}")
if "GITHUB_OUTPUT" in os.environ:
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
f.write(f"doi={doi}\n")
f.write(f"record_id={record_id}\n")
f.write(f"zenodo_url=https://zenodo.org/records/{record_id}\n")
return 0
except Exception as e:
print(f"❌ Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())