Skip to content

ci: check issues’ states #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 22, 2025
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
17 changes: 16 additions & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,22 @@ jobs:
with:
name: dist
path: "./dist"
- run: pnpm check
check-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- uses: typst-community/setup-typst@v4
- run: |
pnpm check-issues >> "$GITHUB_STEP_SUMMARY"
env:
GH_TOKEN: ${{ github.token }}

fmt:
runs-on: ubuntu-latest
Expand Down
3 changes: 1 addition & 2 deletions main.typ
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@

#level.advanced
#issue("typst#5319")
#issue("typst#5785")
#issue("typst#5785", closed: true) // duplicate but cleaner

#babel(
en: [#link("https://www.unicode.org/ivd/")[Ideographic Variation Sequence (IVS)] is a mechanism for plain text #link("https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-23/#G19053")[specified by Unicode] to change the glyph to be used to display a character. For more information, please refer to #link("https://unicode.org/faq/vs.html")[the FAQ on Unicode.org].],
Expand Down Expand Up @@ -1421,7 +1421,6 @@ $ integral f dif x $

- GitHub

- Check/display issue status
- Watch
#link("https://github.com/typst/typst/issues?q=%20is%3Aopen%20label%3Acjk%20sort%3Areactions-desc")[label: cjk · Issues · typst/typst]

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"build": "tsc && vite build && node --experimental-strip-types scripts/build.ts",
"dev": "node --experimental-strip-types scripts/dev.ts",
"preview": "vite preview --base=/clreq/",
"check": "node --experimental-strip-types scripts/check_uniq.ts"
"check-issues": "node --experimental-strip-types scripts/check_issues.ts"
},
"devDependencies": {
"@types/glob-watcher": "^5.0.5",
Expand Down
201 changes: 201 additions & 0 deletions scripts/check_issues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/**
* Check issues.
*/

import assert from "node:assert";
import { execFile as _execFile } from "node:child_process";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";

import { extraArgs } from "./config.ts";
import { typst } from "./typst.ts";

const execFile = promisify(_execFile);

interface IssueMeta {
repo: string;
num: string;
note: string;
closed: boolean;
}

type IssueState =
& { title: string }
& ({
state: "OPEN";
stateReason: string | null;
closed: false;
closedAt: null;
} | {
state: "CLOSED";
stateReason: string;
closed: true;
closedAt: string;
});

async function queryIssues(): Promise<IssueMeta[]> {
return JSON.parse(
await typst([
"query",
"main.typ",
"<issue>",
"--field=value",
...extraArgs.pre,
]),
);
}

/**
* Checks if there are any duplicate issues.
*
* @throws Exits the process with status code 1 if duplicates are found
*
* Most issues should be linked only once.
* If an issue truly needs to be linked multiple times (e.g., it involves multiple problems),
* add a note to clarify. For example:
*
* ```typst
* #issue("hayagriva#189", note: [mentioned])
* ```
*/
function assertUniq(issues: IssueMeta[]): void {
const suspiciousDest = issues.filter(({ note }) => note === "auto").map((
{ repo, num },
) => `${repo}#${num}`);
const duplicates = suspiciousDest.filter((item, index) =>
suspiciousDest.indexOf(item) !== index
);

if (duplicates.length > 0) {
console.error(
"%cDuplicated issues found:",
"color: red;",
new Set(duplicates),
);
process.exit(1);
} else {
console.log("All issues are unique.");
}
}

/***
* Fetch states of issues.
*
* Duplicate issues will be collected into a single entry.
*/
async function fetchStates(
issues: IssueMeta[],
): Promise<[IssueMeta[], IssueState][]> {
const repos = new Set(issues.map((i) => i.repo));

/** repo ⇒ [issue number, IssueMeta[]][] */
const grouped: Map<string, [string, IssueMeta[]][]> = new Map(
Array.from(repos.values()).map(
(repo) => {
const issuesConcerned = issues.filter((i) => repo === i.repo);
const numUniq = Array.from(
(new Set(issuesConcerned.map(({ num }) => num))).values(),
);
return [
repo,
numUniq.map((n) => [
n,
issuesConcerned.filter(({ num }) => n === num),
]),
];
},
),
);

const query = [
"query {",
...Array.from(grouped.entries()).map(([repo, issues]) => {
const [owner, name] = repo.split("/", 2);
const repoSanitized = repo.replaceAll(/[\/-]/g, "_");
return [
` ${repoSanitized}: repository(owner: "${owner}", name: "${name}") {`,
...issues.map(([num, _meta]) =>
` issue_${num}: issue(number: ${num}) { title state stateReason closed closedAt }`
),
" }",
];
}),
"}",
].flat().join("\n");

const { stdout } = await execFile("gh", [
"api",
"graphql",
"--raw-field",
`query=${query}`,
]);
const data = JSON.parse(stdout).data as Record<
string,
Record<string, IssueState>
>;

const stateFlat = Object.values(data).map((issues_of_repo) =>
Object.values(issues_of_repo)
).flat();
const metaFlat = Array.from(grouped.values()).flat();

assert.strictEqual(stateFlat.length, metaFlat.length);

return metaFlat.map(([_num, meta], index) => [meta, stateFlat[index]]);
}

/**
* Checks if all issues’ states are up to date.
*
* @throws Exits the process with status code 1 if outdated issues are found
*
* Most issues should be open.
* If a closed issue is truly needed (e.g., duplicated but cleaner),
* add a note to clarify. For example:
*
* ```typst
* #issue("hayagriva#189", closed: true)
* ```
*/
function assertStateUpdated(states: [IssueMeta[], IssueState][]): void {
const outdated = states.filter(([meta, state]) =>
meta.some((m) => m.closed !== state.closed)
);

if (outdated.length > 0) {
console.error(
"%cOutdated issues found:",
"color: red;",
);
console.error(
outdated.map(([meta, state]) => {
const stateHuman = state.closed
? `closed at ${state.closedAt} for ${state.stateReason}`
: `open${state.stateReason ? ` for ${state.stateReason}` : ""}`;

const metaHuman = meta.map((m) =>
`(note: ${m.note}, closed: ${m.closed})`
).join(" ");

return [
`- ${meta[0].repo}#${meta[0].num} ${state.title} (${stateHuman})`,
` ${metaHuman}`,
];
}).flat().join(
"\n",
),
);
process.exit(1);
} else {
console.log("All issues’ states are up to date.");
}
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
const issues: IssueMeta[] = await queryIssues();

assertUniq(issues);

const states = await fetchStates(issues);
assertStateUpdated(states);
}
42 changes: 0 additions & 42 deletions scripts/check_uniq.ts

This file was deleted.

1 change: 1 addition & 0 deletions typ/templates/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
rel="stylesheet"
/>
<title>clreq-gap for typst</title>
<meta name="description" content="Chinese Layout Gap Analysis for Typst. 分析 Typst 与中文排版的差距。" />
</head>
<body>
<style>
Expand Down
5 changes: 3 additions & 2 deletions typ/util.typ
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@
/// - repo-num (str): Repo and issue number, e.g., `"typst#193"`, `"hayagriva#189"`
/// - anchor (str): Anchor in the page, e.g., `"#issuecomment-1855739446"`
/// - note (content): Annotation
/// - closed (bool): State of the issue
/// -> content
#let issue(repo-num, anchor: "", note: auto) = {
#let issue(repo-num, anchor: "", note: auto, closed: false) = {
let (repo, num) = repo-num.split("#")
if not repo.contains("/") {
repo = "typst/" + repo
Expand Down Expand Up @@ -66,7 +67,7 @@
} else { [~(#note)] }
},
)
[#metadata((repo-num: repo-num, note: repr(note))) <issue>]
[#metadata((repo: repo, num: num, note: repr(note), closed: closed)) <issue>]
}

/// Link to a workaround
Expand Down