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
8 changes: 6 additions & 2 deletions .claude/skills/release-dataset/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<version>` 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.
56 changes: 56 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Release dataset

# Creates a GitHub release (tag v<version>) 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
35 changes: 35 additions & 0 deletions .zenodo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"title": "Mapping Voices: An Open Atlas of Oral-History and Voice-Testimony Collections",
"upload_type": "dataset",
"description": "<p>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.</p><p>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.</p><p>Methodology: <a href=\"https://github.com/adab-tech/mapping/blob/main/METHODOLOGY.md\">METHODOLOGY.md</a> · Data dictionary: <a href=\"https://github.com/adab-tech/mapping/blob/main/DATA_DICTIONARY.md\">DATA_DICTIONARY.md</a> · Atlas: <a href=\"https://adamu.tech/mapping/\">https://adamu.tech/mapping/</a></p>",
"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."
}
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<version>`
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
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions scripts/release-notes.mjs
Original file line number Diff line number Diff line change
@@ -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`
);
}
17 changes: 17 additions & 0 deletions tests/release-notes.test.mjs
Original file line number Diff line number Diff line change
@@ -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);
});
Loading