Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/actions/generate-metadata/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,3 @@ runs:
umask 000 && chmod +w ../aiida-registry
aiida-registry test-install
shell: bash

- name: Move JSON file to the React project
run: cp plugins_metadata.json aiida-registry-app/src/
shell: bash
60 changes: 60 additions & 0 deletions .github/scripts/diff_changed_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""Detect plugins added or modified in this PR vs the base branch.

Reads `plugins.yaml` at `origin/master` and at `HEAD`, prints the list of
plugin keys that were added or whose entry differs, and writes the same list
plus a count to GITHUB_OUTPUT for downstream jobs to consume.

Plugin keys must match a conservative allow-list — anything else is dropped
with a warning so we don't pass adversarial strings into shell commands later.
"""

import os
import re
import subprocess
import sys

import yaml

SAFE_KEY = re.compile(r"^[A-Za-z0-9._-]+$")


def load_yaml_at(ref):
proc = subprocess.run(
["git", "show", f"{ref}:plugins.yaml"],
capture_output=True,
text=True,
check=False,
)
if proc.returncode != 0:
# plugins.yaml may not exist at the base ref (brand-new repo etc.).
print(f"::warning::Could not read plugins.yaml at {ref}: {proc.stderr.strip()}")
return {}
return yaml.safe_load(proc.stdout) or {}


def main():
base = load_yaml_at("origin/master")
head = load_yaml_at("HEAD")

unsafe = sorted(k for k in head if not SAFE_KEY.match(str(k)))
if unsafe:
print(f"::warning::Skipping plugin keys with unsafe characters: {unsafe}")

changed = sorted(
key
for key, value in head.items()
if SAFE_KEY.match(str(key)) and (key not in base or base[key] != value)
)

output = os.environ.get("GITHUB_OUTPUT")
if output:
with open(output, "a", encoding="utf8") as fh:
fh.write(f"changed={' '.join(changed)}\n")
fh.write(f"changed_count={len(changed)}\n")

print(f"Changed plugins ({len(changed)}): {' '.join(changed) or '(none)'}")


if __name__ == "__main__":
sys.exit(main() or 0)
135 changes: 135 additions & 0 deletions .github/scripts/extract_pr_findings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""Render a PR comment summarising warnings/errors for changed plugins.

Reads `plugins_metadata.json`, extracts the `warnings` and `errors` lists for
each plugin key passed on the command line, renders a markdown body with a
sticky-comment marker, and writes the body plus a `has_findings` flag to
GITHUB_OUTPUT. Exits non-zero when any of the listed plugins has at least one
warning or error so the workflow check goes red.

Stages:
--stage warnings Comment only reflects W001-W020 (fetch step).
--stage install Comment additionally reflects E001-E004 (test-install).
"""

import argparse
import json
import os
import re
import sys
from pathlib import Path

COMMENT_MARKER = "<!-- pr-plugin-checks -->"
README_LINK = (
"https://github.com/aiidateam/aiida-registry"
"#how-to-fix-registry-warnings-and-errors"
)


def render_message(raw):
"""Convert an HTML-tagged registry message into markdown."""
pre_block = ""
pre_match = re.match(r"^(.*?)<pre>(.*?)</pre>(.*)$", raw, re.DOTALL)
if pre_match:
raw = pre_match.group(1) + pre_match.group(3)
pre_block = pre_match.group(2).strip()

a_match = re.match(
r"^<a\s+href=['\"]([^'\"]+)['\"]>([WE]\d+)</a>:\s*(.*)$",
raw,
re.DOTALL,
)
if a_match:
head = f"`{a_match.group(2)}`: {a_match.group(3).strip()}"
else:
head = re.sub(r"<[^>]+>", "", raw).strip()

if pre_block:
# Keep error output to a reasonable size in PR comments.
if len(pre_block) > 2000:
pre_block = pre_block[:2000] + "\n... (truncated)"
head += f"\n\n```\n{pre_block}\n```"
return head


def write_outputs(body, has_findings):
output = os.environ.get("GITHUB_OUTPUT")
if output:
with open(output, "a", encoding="utf8") as fh:
fh.write(f"has_findings={'true' if has_findings else 'false'}\n")
fh.write("body<<EOF_BODY\n")
fh.write(body)
fh.write("\nEOF_BODY\n")
print(body)


def main():
parser = argparse.ArgumentParser()
parser.add_argument("changed", nargs="*")
parser.add_argument("--json", default="plugins_metadata.json")
parser.add_argument("--stage", choices=["warnings", "install"], default="warnings")
args = parser.parse_args()

if not args.changed:
body = (
f"{COMMENT_MARKER}\n## Plugin checks\n\n"
"No plugin entries changed in this PR."
)
write_outputs(body, has_findings=False)
return 0

try:
data = json.loads(Path(args.json).read_text(encoding="utf8"))
except (OSError, json.JSONDecodeError) as exc:
body = (
f"{COMMENT_MARKER}\n## Plugin checks\n\n"
f"Internal error: could not read `{args.json}` ({exc.__class__.__name__}: {exc}). "
"The metadata fetch step likely failed; check the workflow logs."
)
write_outputs(body, has_findings=True)
return 1
plugins = data.get("plugins", {})

lines = [
COMMENT_MARKER,
"## Plugin checks",
"",
"Touched plugins: " + ", ".join(f"`{k}`" for k in args.changed),
"",
]

any_findings = False
for key in args.changed:
plugin = plugins.get(key)
if plugin is None:
lines.append(f"- ⚠️ `{key}` — not found in metadata after fetch")
any_findings = True
continue
warnings = [render_message(w) for w in (plugin.get("warnings") or [])]
errors = [render_message(e) for e in (plugin.get("errors") or [])]
if not warnings and not errors:
lines.append(f"- ✅ `{key}` — clean")
continue
any_findings = True
lines.append(f"- ❌ `{key}`")
for w in warnings:
lines.append(f" - ⚠️ {w}")
for e in errors:
lines.append(f" - 🛑 {e}")

lines.append("")
if args.stage == "warnings":
lines.append(
"Install check waits for maintainer approval after warnings pass. "
f"See [how to fix warnings and errors]({README_LINK})."
)
else:
lines.append(f"See [how to fix warnings and errors]({README_LINK}).")

body = "\n".join(lines)
write_outputs(body, has_findings=any_findings)
return 1 if any_findings else 0


if __name__ == "__main__":
sys.exit(main())
90 changes: 0 additions & 90 deletions .github/workflows/dev-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ concurrency:
cancel-in-progress: true

jobs:
get-pr:
# https://dev.to/suzukishunsuke/secure-github-actions-by-pullrequesttarget-641
outputs:
merge_commit_sha: ${{steps.pr.outputs.merge_commit_sha}}
runs-on: ubuntu-latest
steps:
- uses: suzuki-shunsuke/get-pr-action@v0.1.0
id: pr

test-utils:
runs-on: ubuntu-latest
steps:
Expand All @@ -29,84 +20,3 @@ jobs:
uses: ./.github/actions/create-dev-env
- name: Run tests
run: pytest tests/

test-webpage-build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
timeout-minutes: 30
steps:
# This is a CI job that checks if the webpage can be built
# We use the plugins metadata from caching since we don't want to
# fetch it twice and it is not essential for this job to have
# the latest generated metadata
- name: Checkout Repo ⚡️
uses: actions/checkout@v4

- name: Create dev environment
uses: ./.github/actions/create-dev-env

- name: Generate metadata
uses: ./.github/actions/generate-metadata
with:
gh_token: ${{ secrets.GITHUB_TOKEN }}
cache: true

- uses: actions/setup-node@v3
with:
node-version: '18.x'
- name: Install npm dependencies and build
run: |
npm install
npm run build
working-directory: ./aiida-registry-app

preview:
# This job is triggered by from PR from the aiida-registry repo, the developer of aiida-registry need to see the preview page of the PR
needs: [test-webpage-build, get-pr]
if: "! github.event.pull_request.head.repo.fork "
runs-on: ubuntu-latest
strategy:
fail-fast: false
timeout-minutes: 180
env:
COMMIT_AUTHOR: Deploy Action
COMMIT_AUTHOR_EMAIL: action@github.com
VITE_PR_PREVIEW_PATH: "/aiida-registry/pr-preview/pr-${{ github.event.number }}/"

steps:
- name: Checkout Repo ⚡️
uses: actions/checkout@v4
with:
ref: ${{needs.get-pr.outputs.merge_commit_sha}}
- name: Create dev environment
uses: ./.github/actions/create-dev-env

- name: Generate metadata
uses: ./.github/actions/generate-metadata
with:
gh_token: ${{ secrets.GITHUB_TOKEN }}
cache: false

- uses: actions/setup-node@v3
with:
node-version: '18.x'
- name: Install npm dependencies and build
run: |
echo $VITE_PR_PREVIEW_PATH
npm install
npm run build
working-directory: ./aiida-registry-app

- name: Add plugins file to the build folder
run: cp plugins_metadata.json aiida-registry-app/dist/

- name: Deploy preview
uses: rossjrw/pr-preview-action@v1
with:
source-dir: ./aiida-registry-app/dist
preview-branch: gh-pages
umbrella-dir: pr-preview
action: auto
custom-url:
token: ${{ secrets.BOT_COMMENT_TOKEN }} # use aiida-bot token to deploy the preview
84 changes: 0 additions & 84 deletions .github/workflows/plugin-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,87 +47,3 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pytest tests/

test-webpage-build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
timeout-minutes: 30
needs:
- get-pr
steps:
# This is a CI job that checks if the webpage can be built
# We use the plugins metadata from caching since we don't want to
# fetch it twice and it is not essential for this job to have
# the latest generated metadata
- name: Checkout Repo ⚡️
uses: actions/checkout@v4
with:
ref: ${{needs.get-pr.outputs.merge_commit_sha}}

- name: Create dev environment
uses: ./.github/actions/create-dev-env

- name: Generate metadata
uses: ./.github/actions/generate-metadata
with:
gh_token: ${{ secrets.GITHUB_TOKEN }}
cache: true

- uses: actions/setup-node@v3
with:
node-version: '18.x'
- name: Install npm dependencies and build
run: |
npm install
npm run build
working-directory: ./aiida-registry-app

preview:
# This job is triggered by (only) a PR.
needs: [test-webpage-build, get-pr]
runs-on: ubuntu-latest
strategy:
fail-fast: false
timeout-minutes: 180
env:
COMMIT_AUTHOR: Deploy Action
COMMIT_AUTHOR_EMAIL: action@github.com
VITE_PR_PREVIEW_PATH: "/aiida-registry/pr-preview/pr-${{ github.event.number }}/"

steps:
- name: Checkout Repo ⚡️
uses: actions/checkout@v4
with:
ref: ${{needs.get-pr.outputs.merge_commit_sha}}
- name: Create dev environment
uses: ./.github/actions/create-dev-env

- name: Generate metadata
uses: ./.github/actions/generate-metadata
with:
gh_token: ${{ secrets.GITHUB_TOKEN }}
cache: false

- uses: actions/setup-node@v3
with:
node-version: '18.x'
- name: Install npm dependencies and build
run: |
echo $VITE_PR_PREVIEW_PATH
npm install
npm run build
working-directory: ./aiida-registry-app

- name: Add plugins file to the build folder
run: cp plugins_metadata.json aiida-registry-app/dist/

- name: Deploy preview
uses: rossjrw/pr-preview-action@v1
with:
source-dir: ./aiida-registry-app/dist
preview-branch: gh-pages
umbrella-dir: pr-preview
action: auto
custom-url:
token: ${{ secrets.BOT_COMMENT_TOKEN }} # use aiida-bot token to deploy the preview
Loading
Loading