From f35352c7870f33200f404a37e048fe26a1c86f80 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 11:51:39 +0000 Subject: [PATCH] Automate GitHub releases for each dataset version; add Zenodo metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .github/workflows/release.yml: when data/dataset-meta.json's version changes on main (or on manual dispatch), validate, then create release v with notes from the CHANGELOG section if it doesn't exist. - scripts/release-notes.mjs extracts that section (tested in tests/release-notes.test.mjs). - .zenodo.json: Zenodo record metadata (dataset, CC BY 4.0, creator, keywords, scope statement, links), so each release's DOI record is described correctly once the Zenodo–GitHub integration is enabled. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0134nrWUakNL58qJMzCVDcn6 --- .claude/skills/release-dataset/SKILL.md | 8 +++- .github/workflows/release.yml | 56 +++++++++++++++++++++++++ .zenodo.json | 35 ++++++++++++++++ CHANGELOG.md | 9 ++++ README.md | 4 ++ scripts/release-notes.mjs | 35 ++++++++++++++++ tests/release-notes.test.mjs | 17 ++++++++ 7 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .zenodo.json create mode 100644 scripts/release-notes.mjs create mode 100644 tests/release-notes.test.mjs diff --git a/.claude/skills/release-dataset/SKILL.md b/.claude/skills/release-dataset/SKILL.md index 757dedf..765b52a 100644 --- a/.claude/skills/release-dataset/SKILL.md +++ b/.claude/skills/release-dataset/SKILL.md @@ -26,5 +26,9 @@ description: Cut a new Mapping Voices dataset version — bump the version every && node scripts/build-data.mjs --check && node scripts/validate-locales.mjs ``` 5. Run the `site-audit` skill if UI changed. Commit "Release dataset vx.y.z". -6. After merge: suggest a GitHub release/tag (Zenodo mints a DOI per release - if the integration is enabled). +6. After merge, `.github/workflows/release.yml` creates the GitHub release + `v` automatically (notes from the CHANGELOG section, via + `scripts/release-notes.mjs`). With the Zenodo–GitHub integration on, + Zenodo archives it and mints a DOI (metadata in `.zenodo.json`). Once the + user reports the DOI, add it to CITATION.cff (`doi:` / `identifiers`), + the README citation, and the About page citation. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a81ef89 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,56 @@ +name: Release dataset + +# Creates a GitHub release (tag v) whenever the dataset version in +# data/dataset-meta.json changes on main and no release exists for it yet. +# With the Zenodo–GitHub integration enabled for this repository, Zenodo +# archives each release and mints a DOI for it (metadata: .zenodo.json). + +on: + push: + branches: [main] + paths: [data/dataset-meta.json] + workflow_dispatch: + +concurrency: + group: release + cancel-in-progress: false + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: "24" + + - name: Validate before releasing + run: | + node scripts/validate-data.mjs + node --test tests/*.test.mjs + node scripts/build-data.mjs --check + + - name: Read version + id: meta + run: echo "version=$(node -p 'require("./data/dataset-meta.json").version')" >> "$GITHUB_OUTPUT" + + - name: Create release if missing + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ steps.meta.outputs.version }} + run: | + if gh release view "v$VERSION" >/dev/null 2>&1; then + echo "Release v$VERSION already exists — nothing to do." + exit 0 + fi + node scripts/release-notes.mjs "$VERSION" > notes.md + gh release create "v$VERSION" \ + --target "$GITHUB_SHA" \ + --title "Mapping Voices dataset v$VERSION" \ + --notes-file notes.md diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 0000000..69c9646 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,35 @@ +{ + "title": "Mapping Voices: An Open Atlas of Oral-History and Voice-Testimony Collections", + "upload_type": "dataset", + "description": "

A curated, openly licensed metadata dataset of real, publicly documented oral-history and voice-testimony collections worldwide, with normalized geographic, linguistic, thematic, and temporal metadata and a link to each holding institution's own page. The dataset describes and links to collections; it does not contain or redistribute their content.

Counts and statistics describe the collections currently indexed in Mapping Voices, not the global distribution of oral history. Absence of an indexed archive for a place or language is not evidence that no oral-history tradition or collection exists.

Methodology: METHODOLOGY.md · Data dictionary: DATA_DICTIONARY.md · Atlas: https://adamu.tech/mapping/

", + "creators": [ + { + "name": "Abubakar, Adamu" + } + ], + "license": "cc-by-4.0", + "access_right": "open", + "keywords": [ + "oral history", + "voice testimony", + "digital humanities", + "archives", + "cultural heritage", + "language resources", + "open data", + "linked metadata" + ], + "related_identifiers": [ + { + "identifier": "https://adamu.tech/mapping/", + "relation": "isDocumentedBy", + "resource_type": "other" + }, + { + "identifier": "https://github.com/adab-tech/mapping", + "relation": "isSupplementTo", + "resource_type": "software" + } + ], + "notes": "Data licensed CC BY 4.0; application code MIT (see LICENSE). The dataset describes and links to collections held by other institutions; it does not contain or redistribute their recordings." +} diff --git a/CHANGELOG.md b/CHANGELOG.md index bebb272..ff4d23e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to the Mapping Voices dataset and atlas. Dataset changes are recorded as additions, removals, corrections, metadata changes, broken-source changes, and taxonomy changes (METHODOLOGY §10, §12). +## [Unreleased] + +### Tooling +- `.github/workflows/release.yml` creates a GitHub release `v` + whenever the dataset version changes on `main` (or on demand), with + release notes taken from this changelog by `scripts/release-notes.mjs` + (tested). With the Zenodo–GitHub integration enabled, each release is + archived with a DOI; `.zenodo.json` describes it as a CC BY 4.0 dataset. + ## [0.4.0] — 2026-09-27 — Access, institution type, period discussed; Theme Explorer; Coverage Gaps ### Dataset diff --git a/README.md b/README.md index af68bd0..514b352 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,10 @@ by their holding institutions. > Voice-Testimony Collections*. Dataset, version 0.4.0, 2026-09-27. CC BY 4.0. > https://adab-tech.github.io/mapping/ +Every dataset version is published as a GitHub release (created +automatically when the version changes; see `.github/workflows/release.yml`) +and archived with a DOI on Zenodo once the integration is enabled. + To cite one collection, cite its holding institution and give the Mapping Voices identifier (e.g. `MV-000023`) as the finding aid. Machine-readable metadata is in [`CITATION.cff`](CITATION.cff) (GitHub shows it as "Cite diff --git a/scripts/release-notes.mjs b/scripts/release-notes.mjs new file mode 100644 index 0000000..7a1e1e0 --- /dev/null +++ b/scripts/release-notes.mjs @@ -0,0 +1,35 @@ +#!/usr/bin/env node +// Prints the CHANGELOG.md section for one dataset version, for use as +// GitHub release notes (see .github/workflows/release.yml). +// +// Usage: node scripts/release-notes.mjs [version] (default: data/dataset-meta.json) +// Exits non-zero if the version has no CHANGELOG section. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); + +export function releaseNotes(changelog, version) { + const lines = changelog.split(/\r?\n/); + const start = lines.findIndex((l) => l.startsWith(`## [${version}]`)); + if (start === -1) return null; + let end = lines.findIndex((l, i) => i > start && /^## \[/.test(l)); + if (end === -1) end = lines.length; + return lines.slice(start + 1, end).join("\n").trim() + "\n"; +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) { + const version = + process.argv[2] || JSON.parse(readFileSync(path.join(ROOT, "data", "dataset-meta.json"), "utf8")).version; + const notes = releaseNotes(readFileSync(path.join(ROOT, "CHANGELOG.md"), "utf8"), version); + if (!notes) { + console.error(`CHANGELOG.md has no "## [${version}]" section.`); + process.exit(1); + } + const meta = JSON.parse(readFileSync(path.join(ROOT, "data", "dataset-meta.json"), "utf8")); + process.stdout.write( + `${notes}\n---\n\n${meta.scope_statement}\n\nData: CC BY 4.0 · Code: MIT · Methodology: ${meta.methodology}\n` + ); +} diff --git a/tests/release-notes.test.mjs b/tests/release-notes.test.mjs new file mode 100644 index 0000000..8c5f7eb --- /dev/null +++ b/tests/release-notes.test.mjs @@ -0,0 +1,17 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { releaseNotes } from "../scripts/release-notes.mjs"; + +const changelog = readFileSync(new URL("../CHANGELOG.md", import.meta.url), "utf8"); +const meta = JSON.parse(readFileSync(new URL("../data/dataset-meta.json", import.meta.url), "utf8")); + +test("the current dataset version has a CHANGELOG section (a release can be cut)", () => { + const notes = releaseNotes(changelog, meta.version); + assert.ok(notes && notes.length > 50, `no CHANGELOG section for ${meta.version}`); + assert.ok(!notes.includes("## ["), "section must stop before the next version"); +}); + +test("unknown versions return null", () => { + assert.equal(releaseNotes(changelog, "9.9.9"), null); +});