Compare commits

..
Author SHA1 Message Date
Aidan Timson 2cae28ddbc Type check workflow scripts in lint:types 2026-09-30 16:06:38 +01:00
Aidan Timson 4331f9d206 Convert workflow scripts to TypeScript 2026-09-30 16:06:33 +01:00
10 changed files with 105 additions and 17 deletions
@@ -4,10 +4,18 @@
// .github/workflows/blocking-labels.yaml via actions/github-script:
//
// const { default: checkBlockingLabels } =
// await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-blocking-labels.mjs`);
// await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-blocking-labels.mts`);
// await checkBlockingLabels({ github, context, core });
export default async function checkBlockingLabels({ context, core }) {
import type {
GitHubScriptArgs,
PullRequestPayload,
} from "./github-script.d.ts";
export default async function checkBlockingLabels({
context,
core,
}: GitHubScriptArgs<PullRequestPayload>) {
const blockingLabels = [
"wait for backend",
"Needs UX",
@@ -6,14 +6,19 @@
// via actions/github-script:
//
// const { default: checkStandards } =
// await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-pull-request-standards.mjs`);
// await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-pull-request-standards.mts`);
// await checkStandards({ github, context, core });
import type {
GitHubScriptArgs,
PullRequestPayload,
} from "./github-script.d.ts";
export default async function checkPullRequestStandards({
github,
context,
core,
}) {
}: GitHubScriptArgs<PullRequestPayload>) {
const pr = context.payload.pull_request;
// Exempt bots (Copilot agent, dependabot), drafts, and maintainers.
@@ -52,11 +57,16 @@ export default async function checkPullRequestStandards({
const normalized = body.toLowerCase();
// Ignore 404s from mutations that race manual edits or cancelled runs.
const ignoreMissing = async (fn) => {
const ignoreMissing = async <T,>(fn: () => Promise<T>) => {
try {
await fn();
} catch (error) {
if (error.status === 404) {
if (
typeof error === "object" &&
error !== null &&
"status" in error &&
error.status === 404
) {
core.info("Target already removed, nothing to do");
return;
}
@@ -65,7 +75,7 @@ export default async function checkPullRequestStandards({
};
// Hide/restore our comment via GraphQL (REST cannot minimize).
const setMinimized = async (subjectId, minimized) => {
const setMinimized = async (subjectId: string, minimized: boolean) => {
const mutation = minimized
? `mutation($id: ID!) {
minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) {
@@ -81,13 +91,13 @@ export default async function checkPullRequestStandards({
await github.graphql(mutation, { id: subjectId });
} catch (error) {
core.info(
`Could not ${minimized ? "minimize" : "restore"} comment: ${error.message}`
`Could not ${minimized ? "minimize" : "restore"} comment: ${error instanceof Error ? error.message : error}`
);
}
};
// Content of a "## <name>" section, or null when the heading is absent.
const section = (name) => {
const section = (name: string) => {
const match = body.match(
new RegExp(`##\\s${name}([\\s\\S]*?)(?=\\n##\\s|$)`, "i")
);
@@ -128,7 +138,8 @@ export default async function checkPullRequestStandards({
issue_number,
per_page: 100,
});
const existing = comments.find((c) => c.body.includes(marker));
const existing = comments.find((c) => c.body?.includes(marker));
const hasLabel = pr.labels.some((l) => l.name === label);
if (isValid) {
@@ -5,14 +5,16 @@
// actions/github-script:
//
// const { default: checkTaskAuthorization } =
// await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-task-authorization.mjs`);
// await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-task-authorization.mts`);
// await checkTaskAuthorization({ github, context, core });
import type { GitHubScriptArgs, IssuePayload } from "./github-script.d.ts";
export default async function checkTaskAuthorization({
github,
context,
core,
}) {
}: GitHubScriptArgs<IssuePayload>) {
const issueAuthor = context.payload.issue.user.login;
// Check if user is an organization member
+48
View File
@@ -0,0 +1,48 @@
// The arguments actions/github-script passes to the scripts in this directory,
// limited to the parts they use.
import type { Octokit } from "@octokit/rest";
export interface Summary {
addHeading(text: string, level?: number): Summary;
addRaw(text: string): Summary;
write(): Promise<Summary>;
}
export interface Core {
info(message: string): void;
setFailed(message: string): void;
summary: Summary;
}
export interface User {
login: string;
type: string;
}
export interface PullRequestPayload {
pull_request: {
number: number;
body: string | null;
draft: boolean;
user: User;
labels: { name: string }[];
};
}
export interface IssuePayload {
issue: {
user: User;
};
}
export interface Context<Payload> {
repo: { owner: string; repo: string };
issue: { owner: string; repo: string; number: number };
payload: Payload;
}
export interface GitHubScriptArgs<Payload> {
github: Octokit;
context: Context<Payload>;
core: Core;
}
+19
View File
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2024",
"lib": ["ES2024"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
// actions/github-script imports these directly, relying on Node's type stripping
"erasableSyntaxOnly": true,
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true,
"types": []
},
"include": ["*.mts", "*.d.ts"]
}
+1 -1
View File
@@ -30,6 +30,6 @@ jobs:
with:
script: |
const { default: checkBlockingLabels } = await import(
`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-blocking-labels.mjs`
`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-blocking-labels.mts`
);
await checkBlockingLabels({ github, context, core });
@@ -33,6 +33,6 @@ jobs:
with:
script: |
const { default: checkStandards } = await import(
`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-pull-request-standards.mjs`
`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-pull-request-standards.mts`
);
await checkStandards({ github, context, core });
@@ -51,6 +51,6 @@ jobs:
with:
script: |
const { default: checkTaskAuthorization } = await import(
`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-task-authorization.mjs`
`${process.env.GITHUB_WORKSPACE}/.github/scripts/check-task-authorization.mts`
);
await checkTaskAuthorization({ github, context, core });
+1 -1
View File
@@ -290,7 +290,7 @@ export default tseslint.config(
},
},
{
files: [".github/scripts/*.mjs"],
files: [".github/scripts/*.mts"],
languageOptions: {
globals: globals.node,
},
+1 -1
View File
@@ -12,7 +12,7 @@
"format:eslint": "eslint \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --fix",
"lint:prettier": "prettier . --cache --check",
"format:prettier": "prettier . --cache --write",
"lint:types": "node ./node_modules/@typescript/native/bin/tsc",
"lint:types": "node ./node_modules/@typescript/native/bin/tsc && node ./node_modules/@typescript/native/bin/tsc -p .github/scripts",
"lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"",
"lint:licenses": "node --no-deprecation script/check-licenses",
"lint": "pnpm run lint:eslint && pnpm run lint:prettier && pnpm run lint:types && pnpm run lint:lit",