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
9 changes: 9 additions & 0 deletions .github/a11y-community.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"projectName": "GIT Going with GitHub",
"accessibilityLabel": "accessibility",
"installDate": "2026-10-29",
"owner": "accesswatch",
"accessibilityDoc": "https://github.com/Community-Access/git-going-with-github/blob/main/ACCESSIBILITY.md",
"responseBusinessDays": 5,
"creditReminderDays": 14
}
472 changes: 472 additions & 0 deletions .github/scripts/__tests__/a11y-community.test.mjs

Large diffs are not rendered by default.

640 changes: 640 additions & 0 deletions .github/scripts/a11y-community.mjs

Large diffs are not rendered by default.

121 changes: 121 additions & 0 deletions .github/scripts/a11y-credits.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env node
/**
* a11y-credits.mjs - print the release-notes block of accessibility credits.
*
* node .github/scripts/a11y-credits.mjs --since <tag-or-YYYY-MM-DD> [--repo owner/name]
*
* Lists items labelled "credit: yes" or "credit: anonymous" that closed since
* the tag or date, as the contributor chose to be credited, grouped as code
* (pull requests) and other contributions (reports and other issues). Each
* line describes the work by the item's title, never the person.
*
* The block goes to standard output, ready to paste. Warnings about credit
* questions unanswered for more than 14 days go to standard error.
*
* Needs a token with read access: GITHUB_TOKEN or GH_TOKEN, or a signed-in
* GitHub CLI (`gh auth token`). No dependencies.
*/

import { execFileSync } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
collectCredits, formatCredits, formatWarnings, loadConfig, makeClient, resolveSince,
} from './a11y-community.mjs';

const HERE = path.dirname(fileURLToPath(import.meta.url));

function arg(name) {
const i = process.argv.indexOf(name);
return i === -1 ? null : process.argv[i + 1];
}

function token() {
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
try {
return execFileSync('gh', ['auth', 'token'], { encoding: 'utf8' }).trim();
} catch {
return '';
}
}

function repoFromGit() {
const url = execFileSync('git', ['remote', 'get-url', 'origin'], { cwd: HERE, encoding: 'utf8' }).trim();
const m = url.match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?$/);
if (!m) throw new Error(`cannot tell the repository from ${url}; pass --repo owner/name`);
return `${m[1]}/${m[2]}`;
}

/** request and paginate over fetch, matching what makeClient expects. */
function fetchTransport(auth) {
const headers = {
accept: 'application/vnd.github+json',
'x-github-api-version': '2022-11-28',
'user-agent': 'a11y-credits',
...(auth ? { authorization: `Bearer ${auth}` } : {}),
};
const build = (route, params) => {
const [method, template] = route.split(' ');
const rest = { ...params };
const pathname = template.replace(/\{(\w+)\}/g, (_, key) => {
const value = rest[key];
delete rest[key];
return encodeURIComponent(value);
});
const url = new URL(`https://api.github.com${pathname}`);
for (const [key, value] of Object.entries(rest)) url.searchParams.set(key, value);
return { method, url };
};
const call = async (method, url) => {
const res = await fetch(url, { method, headers });
if (!res.ok) {
const error = new Error(`${method} ${url.pathname}: ${res.status} ${await res.text()}`);
error.status = res.status;
throw error;
}
return res;
};
return {
request: async (route, params) => {
const { method, url } = build(route, params);
return (await call(method, url)).json();
},
paginate: async (route, params) => {
let { method, url } = build(route, params);
const all = [];
while (url) {
const res = await call(method, url);
all.push(...(await res.json()));
const next = (res.headers.get('link') || '').match(/<([^>]+)>;\s*rel="next"/);
url = next ? new URL(next[1]) : null;
}
return all;
},
};
}

async function main() {
const sinceArg = arg('--since');
if (!sinceArg) {
console.error('Usage: node .github/scripts/a11y-credits.mjs --since <tag-or-YYYY-MM-DD> [--repo owner/name]');
process.exit(2);
}
const fullName = arg('--repo') || repoFromGit();
const [owner, repo] = fullName.split('/');
const config = loadConfig(path.join(HERE, '..', 'a11y-community.json'));
const client = makeClient(fetchTransport(token()), { owner, repo });

const since = await resolveSince(client, sinceArg);
const result = await collectCredits(client, config, since);
const block = formatCredits(result, config);
if (block) process.stdout.write(block);
else console.error(`No accessibility contributions to credit since ${since.toISOString().slice(0, 10)}.`);
const warnings = formatWarnings(result, config);
if (warnings) console.error(`\n${warnings}`);
}

main().catch((error) => {
console.error(error.message);
process.exit(1);
});
65 changes: 65 additions & 0 deletions .github/workflows/accessibility-credits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Accessibility credits

# Rule 2 of this project's accessibility practice (ACCESSIBILITY.md,
# recognition; Open Source Accessibility Framework, Community action 4).
#
# - A merged pull request with the accessibility label, or one that closes an
# accessibility issue, from someone who is not a maintainer or a bot; and an
# accessibility report closed as completed with a documented outcome: one
# question asking the contributor whether and how to be credited, label
# "credit: asked". Never asked twice on the same item.
# - The contributor, and only the contributor, replies "credit: @handle",
# "credit: name <Name>", "credit: anonymous" or "credit: no": the label
# becomes "credit: yes", "credit: anonymous" or "credit: no", with a short
# confirmation. A chosen name is stored only in that confirmation.
# - At release time, node .github/scripts/a11y-credits.mjs --since <tag-or-date>
# prints the credits block for the release notes.
#
# pull_request_target is used so the question can be posted on pull requests
# from forks. It checks out only the base branch's .github folder and never
# runs code from the pull request.

on:
pull_request_target:
types: [closed]
issues:
types: [closed]
issue_comment:
types: [created]

# pull-requests: write is needed only because GitHub requires it to comment on,
# and label, a pull request; issues: write alone is enough for issues.
permissions:
contents: read
issues: write
pull-requests: write

concurrency:
group: accessibility-credits-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: false

jobs:
credit:
# The label name repeats accessibilityLabel in .github/a11y-community.json.
if: >-
github.repository_owner == 'Community-Access' && (
(github.event_name == 'pull_request_target' && github.event.pull_request.merged) ||
(github.event_name == 'issues' && github.event.sender.type != 'Bot' &&
contains(github.event.issue.labels.*.name, 'accessibility')) ||
(github.event_name == 'issue_comment' && github.event.sender.type != 'Bot' &&
contains(join(github.event.issue.labels.*.name, ','), 'credit: ')))
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out the rule
uses: actions/checkout@v4
with:
persist-credentials: false
sparse-checkout: .github

- name: Apply the rule
uses: actions/github-script@v8
with:
script: |
const { runFromAction } = await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/a11y-community.mjs`);
await runFromAction({ github, context, core }, 'credits');
63 changes: 63 additions & 0 deletions .github/workflows/accessibility-reports.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Accessibility reports

# Rule 1 of this project's accessibility practice (ACCESSIBILITY.md, "How we
# respond"; Open Source Accessibility Framework, Community action 2). An
# accessibility report is an issue with the accessibility label, opened by
# someone who is not a maintainer or a bot, on or after the install date in
# .github/a11y-community.json.
#
# - Opened or first labelled: one acknowledgement, label "a11y: awaiting response".
# - A maintainer comments: that label and "a11y: response overdue" come off.
# - Daily: still awaiting after 5 business days gets "a11y: response overdue"
# and one reminder to the accessibility owner.
# - Closed: needs a merged pull request or commit, or a maintainer comment with
# an "Outcome:" line after the reporter's last comment; otherwise it is
# reopened. Closed as completed invites the reporter to confirm the fix.
#
# The logic, and its tests, are in .github/scripts/a11y-community.mjs. Events
# from bots are skipped, so the workflow's own reopen and comments never
# trigger it again.

on:
issues:
types: [opened, labeled, closed]
issue_comment:
types: [created]
schedule:
# Daily at 13:41 UTC: 06:41 in Arizona, off the top of the hour.
- cron: "41 13 * * *"

permissions:
contents: read
issues: write
pull-requests: read

concurrency:
group: accessibility-reports-${{ github.event.issue.number || 'overdue-sweep' }}
cancel-in-progress: false

jobs:
respond:
# The label name repeats accessibilityLabel in .github/a11y-community.json,
# so a comment on an unrelated issue does not start a job.
if: >-
github.repository_owner == 'Community-Access' && (
github.event_name == 'schedule' ||
(github.event.sender.type != 'Bot' &&
github.event.issue.pull_request == null &&
contains(github.event.issue.labels.*.name, 'accessibility')))
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out the rule
uses: actions/checkout@v4
with:
persist-credentials: false
sparse-checkout: .github

- name: Apply the rule
uses: actions/github-script@v8
with:
script: |
const { runFromAction } = await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/a11y-community.mjs`);
await runFromAction({ github, context, core }, 'reports');
2 changes: 1 addition & 1 deletion .github/workflows/accessibility-review-reminder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
'- [ ] Check that open reports have an acknowledgement, a severity, a status and a next step.',
'- [ ] Keep a few `good first issue` and `help wanted` accessibility issues open, with context and acceptance criteria.',
'- [ ] Review the dependency list in ACCESSIBILITY.md and any upstream blockers.',
'- [ ] Credit accessibility contributors in the update, only in the form each person agreed to.',
'- [ ] Credit accessibility contributors in the update, only in the form each person agreed to: run `node .github/scripts/a11y-credits.mjs --since ' + sinceDate + '` and paste its block; leave out anyone it lists as unanswered after 14 days, or ask them again.',
'- [ ] Check the matching actions in the public Open Source Accessibility Framework tracking issue.',
'',
'Close this issue with a link to the pull request that adds the update.'
Expand Down
8 changes: 8 additions & 0 deletions ACCESSIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ We use four levels, based on how much the barrier stops a person completing a ta
- Before we close a report, we write down the outcome on the issue, and we ask the reporter, or another person affected, to confirm the fix works for them when they are willing.
- We credit reporters and testers only in the way they choose (see [Recognizing contributions](#recognizing-contributions)).

Two workflows hold us to this for reports opened from 29 October 2026 on. Their logic and its tests are in [.github/scripts/a11y-community.mjs](https://github.com/Community-Access/git-going-with-github/blob/main/.github/scripts/a11y-community.mjs).

- **When you report a barrier,** a bot replies once: it thanks you, says a maintainer will reply with a status and a next step within 5 business days, points you to [Known limitations](#known-limitations), and invites, but never requires, details of your setup. The issue is labeled `a11y: awaiting response` until a maintainer comments.
- **If no maintainer has replied after 5 business days** (Monday to Friday), the issue is labeled `a11y: response overdue` and the accessibility owner is reminded on the issue.
- **A report cannot close without an outcome.** Maintainers close it from the pull request or commit that fixes it, or write a comment with a line that starts `Outcome:`, after the reporter's last comment, saying what changed or why it cannot. A report closed without either is reopened automatically, with a note saying how to add one. When a report closes as fixed, you are invited to confirm that the fix works for you, and the issue is labeled `a11y: validation requested`.

If we cannot meet a date we gave, we say why on the issue, share any workaround, and give a new date.

## Taking part
Expand Down Expand Up @@ -193,6 +199,8 @@ We publish accessibility progress here, every quarter and after each cohort, and

Accessibility work, including reports, testing, reviews and documentation, is project work, and we say so publicly. Before we name anyone in a progress update, release notes or a community post, we ask how they would like to be credited: by GitHub handle, by name, or not at all, and we record the answer on the issue or pull request. We describe the work, never the person's disability, diagnosis or assistive technology, even if they mentioned it in a report, and we never present one person as speaking for a disability community.

A workflow does the asking. When an accessibility pull request from outside the maintainers is merged, or your report closes as fixed, a bot asks you once how you would like to be credited, and you reply with one line: `credit: @handle`, `credit: name <Name>`, `credit: anonymous` or `credit: no`. Only your own reply counts, you can change it later, and nothing about you beyond the credit you choose is published. The answer is recorded as a `credit:` label. Before each progress update, the accessibility owner runs `node .github/scripts/a11y-credits.mjs --since <date of the last update>` and pastes the block it prints; it lists items labeled `credit: yes` or `credit: anonymous` closed since then, as code and other contributions, and warns about `credit: asked` items with no answer after 14 days, whose contributors are left out or asked again.

## Reusing our accessibility materials

Other projects are welcome to reuse what we built for this workshop, under the repository's [CC BY 4.0 license](https://github.com/Community-Access/git-going-with-github/blob/main/LICENSE), with attribution. Each item below says what problem it solves and where it falls short.
Expand Down
7 changes: 7 additions & 0 deletions html/ACCESSIBILITY.html
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,12 @@ <h3 id="how-we-respond">How we respond</h3>
<li>Before we close a report, we write down the outcome on the issue, and we ask the reporter, or another person affected, to confirm the fix works for them when they are willing.</li>
<li>We credit reporters and testers only in the way they choose (see <a href="#recognizing-contributions">Recognizing contributions</a>).</li>
</ul>
<p>Two workflows hold us to this for reports opened from 29 October 2026 on. Their logic and its tests are in <a href="https://github.com/Community-Access/git-going-with-github/blob/main/.github/scripts/a11y-community.mjs">.github/scripts/a11y-community.mjs</a>.</p>
<ul>
<li><strong>When you report a barrier,</strong> a bot replies once: it thanks you, says a maintainer will reply with a status and a next step within 5 business days, points you to <a href="#known-limitations">Known limitations</a>, and invites, but never requires, details of your setup. The issue is labeled <code>a11y: awaiting response</code> until a maintainer comments.</li>
<li><strong>If no maintainer has replied after 5 business days</strong> (Monday to Friday), the issue is labeled <code>a11y: response overdue</code> and the accessibility owner is reminded on the issue.</li>
<li><strong>A report cannot close without an outcome.</strong> Maintainers close it from the pull request or commit that fixes it, or write a comment with a line that starts <code>Outcome:</code>, after the reporter&#39;s last comment, saying what changed or why it cannot. A report closed without either is reopened automatically, with a note saying how to add one. When a report closes as fixed, you are invited to confirm that the fix works for you, and the issue is labeled <code>a11y: validation requested</code>.</li>
</ul>
<p>If we cannot meet a date we gave, we say why on the issue, share any workaround, and give a new date.</p>
<h2 id="taking-part">Taking part</h2>
<p>There are several ways to take part, and no single channel is required:</p>
Expand Down Expand Up @@ -400,6 +406,7 @@ <h3 id="october-2026-adopting-the-framework">October 2026: adopting the framewor
</ul>
<h2 id="recognizing-contributions">Recognizing contributions</h2>
<p>Accessibility work, including reports, testing, reviews and documentation, is project work, and we say so publicly. Before we name anyone in a progress update, release notes or a community post, we ask how they would like to be credited: by GitHub handle, by name, or not at all, and we record the answer on the issue or pull request. We describe the work, never the person&#39;s disability, diagnosis or assistive technology, even if they mentioned it in a report, and we never present one person as speaking for a disability community.</p>
<p>A workflow does the asking. When an accessibility pull request from outside the maintainers is merged, or your report closes as fixed, a bot asks you once how you would like to be credited, and you reply with one line: <code>credit: @handle</code>, <code>credit: name &lt;Name&gt;</code>, <code>credit: anonymous</code> or <code>credit: no</code>. Only your own reply counts, you can change it later, and nothing about you beyond the credit you choose is published. The answer is recorded as a <code>credit:</code> label. Before each progress update, the accessibility owner runs <code>node .github/scripts/a11y-credits.mjs --since &lt;date of the last update&gt;</code> and pastes the block it prints; it lists items labeled <code>credit: yes</code> or <code>credit: anonymous</code> closed since then, as code and other contributions, and warns about <code>credit: asked</code> items with no answer after 14 days, whose contributors are left out or asked again.</p>
<h2 id="reusing-our-accessibility-materials">Reusing our accessibility materials</h2>
<p>Other projects are welcome to reuse what we built for this workshop, under the repository&#39;s <a href="https://github.com/Community-Access/git-going-with-github/blob/main/LICENSE">CC BY 4.0 license</a>, with attribution. Each item below says what problem it solves and where it falls short.</p>
<ul>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"build": "npm run build:podcasts && npm run build:html",
"watch:html": "node scripts/build-html.js --watch",
"clean": "node -e \"require('fs').rmSync('html', {recursive: true, force: true})\"",
"test:automation": "node --test .github/scripts/__tests__/*.test.js",
"test:automation": "node --test .github/scripts/__tests__/*.test.js .github/scripts/__tests__/*.test.mjs",
"test:provisioning": "node --test .github/scripts/provisioning/__tests__/*.test.js"
},
"keywords": [
Expand Down
Loading