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
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:
Executable
+39
@@ -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]"
|
||||
Executable
+55
@@ -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!"
|
||||
Executable
+42
@@ -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"
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user