Skip to content
Closed
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
35 changes: 27 additions & 8 deletions .github/scripts/sync-docs-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,24 @@ function stripMarkdown(value) {
return trimmed.replace(/^`|`$/g, "").replace(/^<|>$/g, "").trim();
}

function readBodyValue(body, label) {
function readBodyField(body, label) {
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const match = String(body || "").match(
new RegExp(`^[ \\t]*(?:-[ \\t]*)?${escaped}:[ \\t]*(.*)$`, "im"),
);
return match ? stripMarkdown(match[1]) : "";
return match ? stripMarkdown(match[1]) : undefined;
}

function readBodyValue(body, label) {
return readBodyField(body, label) ?? "";
}

function readFirstBodyValue(body, labels) {
for (const label of labels) {
const value = readBodyField(body, label);
if (value !== undefined) return value;
}
return undefined;
}

function labelSuffix(labels, prefix) {
Expand All @@ -45,10 +57,10 @@ function parseIssueMetadata(issue) {
const labels = issue.labels || [];
const pagePath = readBodyValue(issue.body, "Pfad");
const gitBookUrl = readBodyValue(issue.body, "GitBook URL");
const sprachreviewCr = readBodyValue(issue.body, "DE Sprache");
const optimierungsCr = readBodyValue(issue.body, "Optimierung");
const fachcheckCr = readBodyValue(issue.body, "Fachcheck");
const enSyncCr = readBodyValue(issue.body, "EN Sync");
const sprachreviewCr = readFirstBodyValue(issue.body, ["DE Sprache", "DE Sprachreview"]);
const optimierungsCr = readFirstBodyValue(issue.body, ["Optimierung", "DE Optimierung"]);
const fachcheckCr = readFirstBodyValue(issue.body, ["Fachcheck", "DE Fachcheck"]);
const enSyncCr = readFirstBodyValue(issue.body, ["EN Sync"]);

if (!pagePath) throw new Error("Issue body is missing 'Pfad'.");
if (!gitBookUrl) throw new Error("Issue body is missing 'GitBook URL'.");
Expand Down Expand Up @@ -201,6 +213,11 @@ function buildFieldUpdates(project, metadata) {
return { singleSelect, text };
}

function getTextFieldAction(value, required) {
if (value === undefined || (required && !value)) return "skip";
return value ? "set" : "clear";
}

async function run({ github, context, core, issueNumber, organization, projectNumber }) {
if (!Number.isInteger(issueNumber) || issueNumber < 1) {
throw new Error("issue_number must be a positive integer.");
Expand Down Expand Up @@ -232,9 +249,10 @@ async function run({ github, context, core, issueNumber, organization, projectNu
}

for (const { field, value, required } of updates.text) {
if (value) {
const action = getTextFieldAction(value, required);
if (action === "set") {
await setFieldValue(github, project.id, itemId, field.id, { text: value });
} else if (!required) {
} else if (action === "clear") {
await clearFieldValue(github, project.id, itemId, field.id);
}
}
Expand Down Expand Up @@ -264,6 +282,7 @@ module.exports = {
buildFieldUpdates,
findField,
findOption,
getTextFieldAction,
normalize,
parseIssueMetadata,
readBodyValue,
Expand Down
30 changes: 30 additions & 0 deletions .github/scripts/sync-docs-project.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const test = require("node:test");
const {
buildFieldUpdates,
findOption,
getTextFieldAction,
normalize,
parseIssueMetadata,
readBodyValue,
Expand Down Expand Up @@ -70,6 +71,35 @@ test("reads markdown links and backtick values", () => {
);
});

test("accepts existing issue CR headings used by editors", () => {
const result = parseIssueMetadata({
body: `Pfad: example
GitBook URL: https://example.com
- DE Sprachreview: https://example.com/sprache
- DE Optimierung: https://example.com/optimierung
- DE Fachcheck: Nicht erforderlich – Fachcheck ohne Änderungsbedarf abgeschlossen.`,
labels: ["stage:en-sync", "lang:de", "area:loslegen"],
});

assert.equal(result.sprachreviewCr, "https://example.com/sprache");
assert.equal(result.optimierungsCr, "https://example.com/optimierung");
assert.equal(result.fachcheckCr, "Nicht erforderlich – Fachcheck ohne Änderungsbedarf abgeschlossen.");
});

test("omitted CR headings preserve project values while blank headings clear them", () => {
const labels = ["stage:en-sync", "lang:de", "area:loslegen"];
const base = "Pfad: example\nGitBook URL: https://example.com";

const omitted = parseIssueMetadata({ body: base, labels });
const blank = parseIssueMetadata({ body: base + "\n- Fachcheck:", labels });

assert.equal(omitted.fachcheckCr, undefined);
assert.equal(blank.fachcheckCr, "");
assert.equal(getTextFieldAction(omitted.fachcheckCr, false), "skip");
assert.equal(getTextFieldAction(blank.fachcheckCr, false), "clear");
assert.equal(getTextFieldAction("https://example.com/cr", false), "set");
});

test("matches labels to project options independent of punctuation and case", () => {
assert.equal(normalize("DE Sprachreview"), normalize("de-sprachreview"));
assert.equal(
Expand Down
18 changes: 16 additions & 2 deletions .github/workflows/sync-docs-project.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
name: Sync docs issue to project

on:
pull_request:
paths:
- ".github/scripts/sync-docs-project.js"
- ".github/scripts/sync-docs-project.test.js"
- ".github/workflows/sync-docs-project.yml"
issues:
types: [opened, edited, labeled, unlabeled, reopened]
workflow_dispatch:
Expand All @@ -15,12 +20,21 @@ permissions:
issues: read

concurrency:
group: docs-project-sync-${{ github.event.issue.number || inputs.issue_number }}
group: docs-project-sync-${{ github.event.issue.number || inputs.issue_number || github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
test:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Test metadata parser
run: node --test .github/scripts/sync-docs-project.test.js

sync:
if: github.event_name == 'workflow_dispatch' || contains(github.event.issue.labels.*.name, 'docs-overhaul')
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'issues' && contains(github.event.issue.labels.*.name, 'docs-overhaul'))
runs-on: ubuntu-latest
env:
ISSUE_NUMBER: ${{ inputs.issue_number || github.event.issue.number }}
Expand Down
Loading