|
1 | | -name: Zenodo DOI Registration |
| 1 | +name: zenodo-release |
2 | 2 |
|
3 | 3 | on: |
4 | | - release: |
5 | | - types: [published] |
6 | 4 | workflow_dispatch: |
| 5 | + inputs: |
| 6 | + zenodo_doi: |
| 7 | + description: "Zenodo DOI to write into CITATION.cff" |
| 8 | + required: false |
| 9 | + type: string |
| 10 | + release_version: |
| 11 | + description: "Optional version string to write into CITATION.cff" |
| 12 | + required: false |
| 13 | + type: string |
| 14 | + |
| 15 | +permissions: |
| 16 | + contents: write |
7 | 17 |
|
8 | 18 | concurrency: |
9 | | - group: zenodo-${{ github.repository }}-${{ github.event.release.tag_name || github.run_id }} |
| 19 | + group: zenodo-${{ github.repository }} |
10 | 20 | cancel-in-progress: false |
11 | 21 |
|
12 | 22 | jobs: |
13 | | - register-doi: |
| 23 | + update-citation: |
14 | 24 | runs-on: ubuntu-latest |
15 | | - permissions: |
16 | | - contents: write |
17 | | - |
18 | 25 | steps: |
19 | 26 | - name: Checkout |
20 | 27 | uses: actions/checkout@v4 |
21 | 28 | with: |
22 | 29 | fetch-depth: 0 |
23 | 30 |
|
24 | | - - name: Wait for Zenodo webhook |
25 | | - if: ${{ github.event_name == 'release' }} |
26 | | - run: sleep 90 |
27 | | - |
28 | | - - name: Poll Zenodo for DOI |
29 | | - id: zenodo |
| 31 | + - name: Resolve metadata |
| 32 | + id: meta |
30 | 33 | shell: bash |
| 34 | + env: |
| 35 | + DOI_INPUT: ${{ inputs.zenodo_doi }} |
| 36 | + VERSION_INPUT: ${{ inputs.release_version }} |
31 | 37 | run: | |
32 | 38 | set -euo pipefail |
33 | | - REPO_URL="https://github.com/${{ github.repository }}" |
34 | | - REPO_NAME="${{ github.repository }}" |
35 | | - echo "Searching Zenodo for repository base: $REPO_URL" |
36 | | -
|
37 | | - DOI=$(python3 - <<'PY' "$REPO_NAME" |
38 | | -import sys |
39 | | - |
40 | | -repo = sys.argv[1] |
41 | | -preferred = { |
42 | | - "AIUZ-Terra-Codex-EcoSystem/AIUZ-Terra-Ecosystem": "10.5281/zenodo.18860027", |
43 | | - "AIUZ-Terra-Codex-EcoSystem/Nullo-PLT-FMP-Theory": "10.5281/zenodo.19189085", |
44 | | - "AIUZ-Terra-Codex-EcoSystem/github": "10.5281/zenodo.18860023", |
45 | | - "AIUZ-Terra-Codex-EcoSystem/terra-legal": "10.5281/zenodo.18860025", |
46 | | - "Secret-Uzbek/AIUZ": "10.5281/zenodo.18860029", |
47 | | - "Secret-Uzbek/AIUZ-Terra-codex": "10.5281/zenodo.18860223", |
48 | | - "Secret-Uzbek/AIUZ-terra-codex-FMP": "10.5281/zenodo.18860225", |
49 | | - "Secret-Uzbek/FMP-CENTRAL-REPO": "10.5281/zenodo.17731795", |
50 | | - "Secret-Uzbek/FMP-Full-Publication-Package": "10.5281/zenodo.19189042", |
51 | | - "Secret-Uzbek/FMP-monograph": "10.5281/zenodo.17598953", |
52 | | - "Secret-Uzbek/Theory-of-fractal-metascience-paradigm": "10.5281/zenodo.18860033", |
53 | | - "Secret-Uzbek/Uzbek-mining": "10.5281/zenodo.18860031", |
54 | | - "Secret-Uzbek/babur-terra-codex": "10.5281/zenodo.18780897", |
55 | | - "Secret-Uzbek/navoiy-terra-corpus": "10.5281/zenodo.18676066", |
56 | | - "Secret-Uzbek/terra-fmp-research-pipeline": "10.5281/zenodo.18061978", |
57 | | - "Secret-Uzbek/terra-translation-api": "10.5281/zenodo.18860037", |
58 | | - "Secret-Uzbek/terra-ust-autobuild": "10.5281/zenodo.18860035", |
59 | | - "Secret-Uzbek/ust-mvp": "10.5281/zenodo.18860041", |
60 | | - "Secret-Uzbek/women-made-central-asia": "10.5281/zenodo.19188951", |
61 | | - "Secret-Uzbek/women-made-central-asia-corpus": "10.5281/zenodo.19188970", |
62 | | - "Secret-Uzbek/ziyarat-terra-corpus": "10.5281/zenodo.19187958", |
63 | | -} |
64 | | -print(preferred.get(repo, "")) |
65 | | -PY |
66 | | -) |
67 | | - |
68 | | - if [ -n "$DOI" ]; then |
69 | | - echo "doi=$DOI" >> "$GITHUB_OUTPUT" |
70 | | - echo "Using preferred DOI from ORCID/DataCite mapping: $DOI" |
71 | | - exit 0 |
| 39 | +
|
| 40 | + current_doi="$(sed -n 's/^doi:[[:space:]]*//p' CITATION.cff | head -n 1 | tr -d '\"')" |
| 41 | + current_version="$(sed -n 's/^version:[[:space:]]*//p' CITATION.cff | head -n 1 | tr -d '\"')" |
| 42 | + today="$(date +%Y-%m-%d)" |
| 43 | +
|
| 44 | + doi="${DOI_INPUT:-$current_doi}" |
| 45 | + version="${VERSION_INPUT:-$current_version}" |
| 46 | +
|
| 47 | + if [ -z "$doi" ]; then |
| 48 | + echo "No DOI available. Supply workflow input zenodo_doi or add doi to CITATION.cff." >&2 |
| 49 | + exit 1 |
| 50 | + fi |
| 51 | +
|
| 52 | + if [ -z "$version" ]; then |
| 53 | + version="${GITHUB_REF_NAME:-manual}" |
72 | 54 | fi |
73 | 55 |
|
74 | | - for attempt in 1 2 3 4 5; do |
75 | | - echo "Attempt $attempt..." |
76 | | - RESPONSE=$(curl -fsSL "https://zenodo.org/api/records?q=$REPO_URL&sort=mostrecent&size=25" || echo "") |
77 | | - DOI=$(printf "%s" "$RESPONSE" | python3 - <<'PY' "$REPO_URL" |
78 | | -import json |
79 | | -import sys |
80 | | - |
81 | | -repo_url = sys.argv[1] |
82 | | -payload = sys.stdin.read().strip() |
83 | | -if not payload: |
84 | | - raise SystemExit(0) |
85 | | - |
86 | | -try: |
87 | | - data = json.loads(payload) |
88 | | -except Exception: |
89 | | - raise SystemExit(0) |
90 | | - |
91 | | -hits = data.get("hits", {}).get("hits", []) |
92 | | -for hit in hits: |
93 | | - related = hit.get("metadata", {}).get("related_identifiers", []) or [] |
94 | | - for item in related: |
95 | | - identifier = item.get("identifier", "") or "" |
96 | | - if identifier.startswith(repo_url): |
97 | | - doi = hit.get("doi") or hit.get("metadata", {}).get("doi", "") |
98 | | - if doi: |
99 | | - print(doi) |
100 | | - raise SystemExit(0) |
101 | | -PY |
102 | | -) |
103 | | - |
104 | | - if [ -n "$DOI" ]; then |
105 | | - echo "doi=$DOI" >> "$GITHUB_OUTPUT" |
106 | | - echo "Found DOI: $DOI" |
107 | | - exit 0 |
108 | | - fi |
109 | | - |
110 | | - echo "DOI not found yet, waiting 60s..." |
111 | | - sleep 60 |
112 | | - done |
113 | | - |
114 | | - echo "Zenodo DOI not found after polling window." >> "$GITHUB_STEP_SUMMARY" |
115 | | - |
116 | | - - name: Update CITATION.cff |
117 | | - if: steps.zenodo.outputs.doi != '' |
| 56 | + { |
| 57 | + echo "doi=$doi" |
| 58 | + echo "version=$version" |
| 59 | + echo "today=$today" |
| 60 | + } >> "$GITHUB_OUTPUT" |
| 61 | +
|
| 62 | + - name: Normalize CITATION.cff |
118 | 63 | shell: bash |
| 64 | + env: |
| 65 | + DOI: ${{ steps.meta.outputs.doi }} |
| 66 | + VERSION: ${{ steps.meta.outputs.version }} |
| 67 | + TODAY: ${{ steps.meta.outputs.today }} |
| 68 | + REPOSITORY_CODE: https://github.com/${{ github.repository }} |
119 | 69 | run: | |
120 | 70 | set -euo pipefail |
121 | | - DOI="${{ steps.zenodo.outputs.doi }}" |
122 | | - VERSION="${{ github.event.release.tag_name || github.ref_name }}" |
123 | | - DATE=$(date +%Y-%m-%d) |
124 | | -
|
125 | | - python3 - <<'PY' "$DOI" "$VERSION" "$DATE" "${{ github.repository }}" |
126 | | -import pathlib |
127 | | -import re |
128 | | -import sys |
129 | | - |
130 | | -doi, version, date, repo = sys.argv[1:] |
131 | | -path = pathlib.Path("CITATION.cff") |
132 | | - |
133 | | -if path.exists(): |
134 | | - content = path.read_text(encoding="utf-8") |
135 | | -else: |
136 | | - content = f'''cff-version: 1.2.0 |
137 | | -message: "If you use this software, please cite it as below." |
138 | | -type: software |
139 | | -title: "{repo.split('/')[-1]}" |
140 | | -repository-code: "https://github.com/{repo}" |
141 | | -url: "https://github.com/{repo}" |
142 | | -''' |
143 | | -
|
144 | | -updates = { |
145 | | - "doi": doi, |
146 | | - "version": version, |
147 | | - "date-released": date, |
148 | | - "url": f"https://doi.org/{doi}", |
149 | | -} |
150 | | -
|
151 | | -for key, value in updates.items(): |
152 | | - pattern = rf"^(#\s*)?{re.escape(key)}:.*$" |
153 | | - if re.search(pattern, content, flags=re.MULTILINE): |
154 | | - content = re.sub(pattern, f"{key}: {value}", content, flags=re.MULTILINE) |
155 | | - else: |
156 | | - if not content.endswith("\\n"): |
157 | | - content += "\\n" |
158 | | - content += f"{key}: {value}\\n" |
159 | | -
|
160 | | -path.write_text(content, encoding="utf-8") |
161 | | -PY |
162 | | -
|
163 | | - - name: Commit CITATION.cff |
164 | | - if: steps.zenodo.outputs.doi != '' |
| 71 | + python3 <<'PY' |
| 72 | + import os |
| 73 | + import pathlib |
| 74 | + import re |
| 75 | +
|
| 76 | + path = pathlib.Path("CITATION.cff") |
| 77 | + content = path.read_text(encoding="utf-8") |
| 78 | +
|
| 79 | + updates = { |
| 80 | + "doi": f"\"{os.environ['DOI']}\"", |
| 81 | + "url": f"\"https://doi.org/{os.environ['DOI']}\"", |
| 82 | + "version": f"\"{os.environ['VERSION']}\"", |
| 83 | + "date-released": f"\"{os.environ['TODAY']}\"", |
| 84 | + "repository-code": f"\"{os.environ['REPOSITORY_CODE']}\"", |
| 85 | + } |
| 86 | +
|
| 87 | + for key, value in updates.items(): |
| 88 | + pattern = rf"^{re.escape(key)}:.*$" |
| 89 | + if re.search(pattern, content, flags=re.MULTILINE): |
| 90 | + content = re.sub(pattern, f"{key}: {value}", content, flags=re.MULTILINE) |
| 91 | + else: |
| 92 | + if not content.endswith("\n"): |
| 93 | + content += "\n" |
| 94 | + content += f"{key}: {value}\n" |
| 95 | +
|
| 96 | + path.write_text(content, encoding="utf-8") |
| 97 | + PY |
| 98 | +
|
| 99 | + - name: Commit CITATION.cff if changed |
165 | 100 | shell: bash |
| 101 | + env: |
| 102 | + DOI: ${{ steps.meta.outputs.doi }} |
166 | 103 | run: | |
167 | 104 | set -euo pipefail |
168 | 105 | git config user.name "github-actions[bot]" |
169 | 106 | git config user.email "github-actions[bot]@users.noreply.github.com" |
170 | 107 | git add CITATION.cff |
171 | | - git diff --staged --quiet || git commit -m "chore: update CITATION.cff with DOI ${{ steps.zenodo.outputs.doi }}" |
172 | | - git push |
| 108 | + if ! git diff --staged --quiet; then |
| 109 | + git commit -m "chore: sync CITATION.cff with Zenodo DOI ${DOI}" |
| 110 | + git push |
| 111 | + fi |
| 112 | +
|
| 113 | + - name: Summary |
| 114 | + shell: bash |
| 115 | + run: | |
| 116 | + { |
| 117 | + echo "## Zenodo release sync" |
| 118 | + echo "- Repository: \`${{ github.repository }}\`" |
| 119 | + echo "- DOI: \`${{ steps.meta.outputs.doi }}\`" |
| 120 | + echo "- Version: \`${{ steps.meta.outputs.version }}\`" |
| 121 | + echo "- Date released: \`${{ steps.meta.outputs.today }}\`" |
| 122 | + } >> "$GITHUB_STEP_SUMMARY" |
0 commit comments