diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index faa978b13227..11bf76a7dbd6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -130,7 +130,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { default: checkCommitMessages } = await import('${{ github.workspace }}/trusted/ci/github-script/lint-commits.js') + const { default: checkCommitMessages } = await import('${{ github.workspace }}/trusted/ci/github-script/lint-commits.ts') await checkCommitMessages({ github, diff --git a/.github/workflows/merge-group.yml b/.github/workflows/merge-group.yml index e111c35ca7c8..e2ef4e77fe9c 100644 --- a/.github/workflows/merge-group.yml +++ b/.github/workflows/merge-group.yml @@ -38,8 +38,8 @@ jobs: TARGET_SHA: ${{ inputs.targetSha }} with: script: | - const { classify } = await import('${{ github.workspace }}/ci/github-script/supportedBranches.js') - const { default: supportedSystems } = await import('${{ github.workspace }}/ci/github-script/supportedSystems.js') + const { classify } = await import('${{ github.workspace }}/ci/github-script/supportedBranches.ts') + const { default: supportedSystems } = await import('${{ github.workspace }}/ci/github-script/supportedSystems.ts') const baseBranch = ( context.payload.merge_group?.base_ref ?? diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 35208ae4fab2..f5a6c1bfcec4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,8 +64,8 @@ jobs: '.github/workflows/test.yml', 'ci/github-script/package.json', 'ci/github-script/package-lock.json', - 'ci/github-script/supportedBranches.js', - 'ci/github-script/supportedSystems.js', + 'ci/github-script/supportedBranches.ts', + 'ci/github-script/supportedSystems.ts', 'ci/pinned.json', 'pkgs/top-level/release-supported-systems.json', ].includes(file))) core.setOutput('merge-group', true) @@ -82,8 +82,8 @@ jobs: 'ci/github-script/bot.js', 'ci/github-script/check-target-branch.ts', 'ci/github-script/commits.ts', - 'ci/github-script/get-pr-commit-details.js', - 'ci/github-script/lint-commits.js', + 'ci/github-script/get-pr-commit-details.ts', + 'ci/github-script/lint-commits.ts', 'ci/github-script/manual-file-edits.ts', 'ci/github-script/merge.js', 'ci/github-script/package.json', @@ -91,9 +91,9 @@ jobs: 'ci/github-script/prepare.js', 'ci/github-script/reminders.ts', 'ci/github-script/reviewers.js', - 'ci/github-script/reviews.js', - 'ci/github-script/supportedBranches.js', - 'ci/github-script/supportedSystems.js', + 'ci/github-script/reviews.ts', + 'ci/github-script/supportedBranches.ts', + 'ci/github-script/supportedSystems.ts', 'ci/github-script/withRateLimit.js', 'ci/pinned.json', 'pkgs/top-level/release-supported-systems.json', diff --git a/ci/README.md b/ci/README.md index 992ed92ed3ac..dfdf736ae711 100644 --- a/ci/README.md +++ b/ci/README.md @@ -104,7 +104,7 @@ For the purposes of CI, branches in the NixOS/nixpkgs repository are classified Some branches also have a version component, which is either `unstable` or `YY.MM`. -`ci/github-script/supportedBranches.js` is a script imported by CI to classify the base and head branches of a Pull Request. +`ci/github-script/supportedBranches.ts` is a script imported by CI to classify the base and head branches of a Pull Request. This classification will then be used to skip certain jobs. This script can also be run locally to print basic test cases. diff --git a/ci/github-script/bot.js b/ci/github-script/bot.js index aa607d0dfc1b..7000e5b79f92 100644 --- a/ci/github-script/bot.js +++ b/ci/github-script/bot.js @@ -4,7 +4,7 @@ import path from 'node:path' import { DefaultArtifactClient } from '@actions/artifact' import { handleMerge } from './merge.js' import { handleReviewers } from './reviewers.js' -import { classify } from './supportedBranches.js' +import { classify } from './supportedBranches.ts' import withRateLimit from './withRateLimit.js' export default async ({ github, context, core, dry }) => { diff --git a/ci/github-script/check-target-branch-policy.ts b/ci/github-script/check-target-branch-policy.ts index a5367ded6925..bf1dc2215e3c 100644 --- a/ci/github-script/check-target-branch-policy.ts +++ b/ci/github-script/check-target-branch-policy.ts @@ -1,4 +1,4 @@ -import { classify, split } from './supportedBranches.js' +import { classify, split } from './supportedBranches.ts' type TargetBranchPolicyFacts = { base: string diff --git a/ci/github-script/check-target-branch.ts b/ci/github-script/check-target-branch.ts index b12c2aa9d68b..709256ba6518 100644 --- a/ci/github-script/check-target-branch.ts +++ b/ci/github-script/check-target-branch.ts @@ -6,8 +6,8 @@ import { evaluateTargetBranchPolicy, getTargetBranchPolicy, } from './check-target-branch-policy.ts' -import { dismissReviews, postReview } from './reviews.js' -import { split } from './supportedBranches.js' +import { dismissReviews, postReview } from './reviews.ts' +import { split } from './supportedBranches.ts' // TODO: should this be combined with the branch checks in prepare.js? // They do seem quite similar, but this needs to run after eval, diff --git a/ci/github-script/commits.ts b/ci/github-script/commits.ts index a1fbceda6589..ef8098f95d14 100644 --- a/ci/github-script/commits.ts +++ b/ci/github-script/commits.ts @@ -2,8 +2,8 @@ import { execFileSync } from 'node:child_process' import type * as actionsCore from '@actions/core' import type { context as actionsContext } from '@actions/github' import type { GitHub } from '@actions/github/lib/utils' -import { dismissReviews, postReview } from './reviews.js' -import { classify } from './supportedBranches.js' +import { dismissReviews, postReview } from './reviews.ts' +import { classify } from './supportedBranches.ts' import withRateLimit from './withRateLimit.js' const dirname = import.meta.dirname diff --git a/ci/github-script/get-pr-commit-details.js b/ci/github-script/get-pr-commit-details.ts similarity index 72% rename from ci/github-script/get-pr-commit-details.js rename to ci/github-script/get-pr-commit-details.ts index 63a8c8a5ff6d..0ad80ebf4527 100644 --- a/ci/github-script/get-pr-commit-details.js +++ b/ci/github-script/get-pr-commit-details.ts @@ -3,26 +3,23 @@ import { promisify } from 'node:util' const execFile = promisify(nodeExecFile) -/** - * @typedef {{ - * subject: string, - * sha: string, - * author: { name: string, email: string }, - * committer: { name: string, email: string} - * changedPaths: string[], - * changedPathSegments: Set, - * }} Commit - */ +export type Commit = { + subject: string + sha: string + author: { name: string; email: string } + committer: { name: string; email: string } + changedPaths: string[] + changedPathSegments: Set +} -/** - * @param {{ - * args: string[] - * core: typeof import('@actions/core'), - * quiet?: boolean, - * repoPath?: string, - * }} RunGitProps - */ -async function runGit({ args, repoPath, core, quiet }) { +interface RunGitProps { + args: string[] + core: typeof import('@actions/core') + quiet?: boolean + repoPath?: string +} + +async function runGit({ args, repoPath, core, quiet }: RunGitProps) { if (repoPath) { args = ['-C', repoPath, ...args] } @@ -34,21 +31,29 @@ async function runGit({ args, repoPath, core, quiet }) { return await execFile('git', args) } +interface GetCommitMessagesForPRProps { + core: typeof import('@actions/core') + pr: Awaited< + ReturnType< + InstanceType< + typeof import('@actions/github/lib/utils').GitHub + >['rest']['pulls']['get'] + > + >['data'] + repoPath?: string +} + /** * Gets the SHA, subject and changed files for each commit in the given PR. * * Don't use GitHub API at all: the "list commits on PR" endpoint has a limit * of 250 commits and doesn't return the changed files. - * - * @param {{ - * core: typeof import('@actions/core'), - * pr: Awaited["rest"]["pulls"]["get"]>>["data"] - * repoPath?: string, - * }} GetCommitMessagesForPRProps - * - * @returns {Promise} */ -export async function getCommitDetailsForPR({ core, pr, repoPath }) { +export async function getCommitDetailsForPR({ + core, + pr, + repoPath, +}: GetCommitMessagesForPRProps): Promise { await runGit({ args: ['fetch', `--depth=1`, 'origin', pr.base.sha], repoPath, diff --git a/ci/github-script/lint-commits.js b/ci/github-script/lint-commits.ts similarity index 83% rename from ci/github-script/lint-commits.js rename to ci/github-script/lint-commits.ts index bbeea0990213..0c83f6636a85 100644 --- a/ci/github-script/lint-commits.js +++ b/ci/github-script/lint-commits.ts @@ -1,17 +1,23 @@ -import { getCommitDetailsForPR } from './get-pr-commit-details.js' -import { classify } from './supportedBranches.js' +import { type Commit, getCommitDetailsForPR } from './get-pr-commit-details.ts' +import { classify } from './supportedBranches.ts' -/** @typedef {import('./get-pr-commit-details.js').Commit} Commit */ +type GitHub = InstanceType +type Context = typeof import('@actions/github').context +type Core = typeof import('@actions/core') -/** - * @param {{ - * github: InstanceType, - * context: typeof import('@actions/github').context, - * core: typeof import('@actions/core'), - * repoPath?: string, - * }} LintCommitsProps - */ -export default async function lintCommits({ github, context, core, repoPath }) { +interface LintCommitsProps { + github: GitHub + context: Context + core: Core + repoPath?: string +} + +export default async function lintCommits({ + github, + context, + core, + repoPath, +}: LintCommitsProps) { // This check should only be run when we have the pull_request context. const pull_number = context.payload.pull_request?.number if (!pull_number) { @@ -53,13 +59,15 @@ export default async function lintCommits({ github, context, core, repoPath }) { await checkCommitMetadata({ commits, core }) } -/** - * @param {{ - * commits: Commit[], - * core: typeof import('@actions/core'), - * }} CheckCommitMessagesProps - */ -async function checkCommitMessages({ commits, core }) { +interface CheckCommitMessagesProps { + commits: Commit[] + core: Core +} + +async function checkCommitMessages({ + commits, + core, +}: CheckCommitMessagesProps) { const failures = new Set() const conventionalCommitTypes = [ @@ -80,10 +88,13 @@ async function checkCommitMessages({ commits, core }) { ] /** - * @param {string[]} types e.g. ["fix", "feat"] - * @param {string?} sha commit hash + * @param types e.g. ["fix", "feat"] + * @param sha commit hash */ - function makeConventionalCommitRegex(types, sha = null) { + function makeConventionalCommitRegex( + types: string[], + sha: string | null = null, + ) { core.info( `${ sha @@ -166,17 +177,15 @@ async function checkCommitMessages({ commits, core }) { } } -/** - * @param {{ - * commits: Commit[], - * core: typeof import('@actions/core'), - * }} CheckGitFieldsProps - */ -async function checkCommitMetadata({ commits, core }) { +interface CheckGitFieldsProps { + commits: Commit[] + core: Core +} + +async function checkCommitMetadata({ commits, core }: CheckGitFieldsProps) { const failures = new Set() - /** @type {(s: string) => boolean} */ - const isEmail = (s) => /^.+@.*$/.test(s) + const isEmail = (s: string) => /^.+@.*$/.test(s) for (const commit of commits) { if (!commit.author.name) { diff --git a/ci/github-script/manual-file-edits.ts b/ci/github-script/manual-file-edits.ts index f544071dbb78..3cd70e5beb6b 100644 --- a/ci/github-script/manual-file-edits.ts +++ b/ci/github-script/manual-file-edits.ts @@ -1,6 +1,6 @@ -import { getCommitDetailsForPR } from './get-pr-commit-details.js' -import { dismissReviews, postReview } from './reviews.js' -import { classify } from './supportedBranches.js' +import { getCommitDetailsForPR } from './get-pr-commit-details.ts' +import { dismissReviews, postReview } from './reviews.ts' +import { classify } from './supportedBranches.ts' export default async function checkManualFileEdits({ github, diff --git a/ci/github-script/merge.js b/ci/github-script/merge.js index 367d70c415f4..904cb3cbdd79 100644 --- a/ci/github-script/merge.js +++ b/ci/github-script/merge.js @@ -1,5 +1,5 @@ // @ts-nocheck -import { classify } from './supportedBranches.js' +import { classify } from './supportedBranches.ts' function runChecklist({ committers, diff --git a/ci/github-script/prepare.js b/ci/github-script/prepare.js index 1ba90b5fe232..7a4fcc1f1fb1 100644 --- a/ci/github-script/prepare.js +++ b/ci/github-script/prepare.js @@ -1,7 +1,7 @@ // @ts-nocheck -import { dismissReviews, postReview } from './reviews.js' -import { classify } from './supportedBranches.js' -import supportedSystems from './supportedSystems.js' +import { dismissReviews, postReview } from './reviews.ts' +import { classify } from './supportedBranches.ts' +import supportedSystems from './supportedSystems.ts' const reviewKey = 'prepare' @@ -66,7 +66,7 @@ export default async ({ github, context, core, dry }) => { // commits between that base and head is the real base. We can query for this via GitHub's // REST API. There can be multiple candidates for the real base with the same number of // commits. In this case we pick the "best" candidate by a fixed ordering of branches, - // as defined in ci/github-script/supportedBranches.js. + // as defined in ci/github-script/supportedBranches.ts. // // These requests take a while, when comparing against the wrong release - they need // to look at way more than 10k commits in that case. Thus, we try to minimize the diff --git a/ci/github-script/reminders.ts b/ci/github-script/reminders.ts index 49431c752149..98d58af95da4 100644 --- a/ci/github-script/reminders.ts +++ b/ci/github-script/reminders.ts @@ -3,9 +3,9 @@ import path from 'node:path' import type * as actionsCore from '@actions/core' import type { context as actionsContext } from '@actions/github' import type { GitHub } from '@actions/github/lib/utils' -import { getCommitDetailsForPR } from './get-pr-commit-details.js' -import { dismissReviews, postReview } from './reviews.js' -import { classify } from './supportedBranches.js' +import { getCommitDetailsForPR } from './get-pr-commit-details.ts' +import { dismissReviews, postReview } from './reviews.ts' +import { classify } from './supportedBranches.ts' /** * Reminders to post as a non-blocking review when a pull request touches diff --git a/ci/github-script/reviews.js b/ci/github-script/reviews.ts similarity index 82% rename from ci/github-script/reviews.js rename to ci/github-script/reviews.ts index e47270386f1e..be4168e0c549 100644 --- a/ci/github-script/reviews.js +++ b/ci/github-script/reviews.ts @@ -13,30 +13,28 @@ const reviewUsers = [ 'manual-edit', ] -/** - * @typedef {InstanceType} GitHub - * @typedef {typeof import('@actions/github').context} Context - * - * @typedef {Awaited>['data'][number]} Review - * @typedef {Review & { user: NonNullable }} ReviewWithNonNullUser - */ +type GitHub = InstanceType +type Context = typeof import('@actions/github').context +type Review = Awaited< + ReturnType +>['data'][number] +type ReviewWithNonNullUser = Review & { user: NonNullable } + +interface DismissReviewsProps { + github: GitHub + context: Context + core: typeof import('@actions/core') + dry: boolean + reviewKey?: string +} -/** - * @param {{ - * github: GitHub, - * context: Context, - * core: typeof import('@actions/core'), - * dry: boolean, - * reviewKey?: string, - * }} DismissReviewsProps - */ export async function dismissReviews({ github, context, core, dry, reviewKey, -}) { +}: DismissReviewsProps) { const pull_number = context.payload.pull_request?.number if (!pull_number) { core.warning('dismissReviews called outside of pull_request context') @@ -47,23 +45,29 @@ export async function dismissReviews({ return } - const allReviews = await github.paginate(github.rest.pulls.listReviews, { - ...context.repo, - pull_number, - }) + const allReviews: Review[] = await github.paginate( + github.rest.pulls.listReviews, + { + ...context.repo, + pull_number, + }, + ) - const reviews = /** @type {ReviewWithNonNullUser[]} */ ( - allReviews.filter( + const reviews = allReviews + .filter((review): review is ReviewWithNonNullUser => !!review.user) + .filter( (review) => - review.user && review.state !== 'DISMISSED' && review.user.login.endsWith('[bot]') && reviewUsers.some((substr) => review.user?.login.includes(substr)), ) - ) const reviewsByUser = reviews.reduce( (prev, curr) => { + if (!curr.user) { + return prev + } + if (!(curr.user.login in prev)) { prev[curr.user.login] = [] } @@ -72,7 +76,7 @@ export async function dismissReviews({ return prev }, - /** @type {Record } */ ({}), + {} as Record, ) const commentRegex = new RegExp( @@ -86,8 +90,8 @@ export async function dismissReviews({ ) let reviewsToMinimize = reviews - const /** @type {ReviewWithNonNullUser[]} */ reviewsToDismiss = [] - const /** @type {ReviewWithNonNullUser[]} */ reviewsToResolve = [] + const reviewsToDismiss: ReviewWithNonNullUser[] = [] + const reviewsToResolve: ReviewWithNonNullUser[] = [] if (reviewKey && reviews.every((review) => commentRegex.test(review.body))) { reviewsToMinimize = reviews.filter((review) => @@ -165,17 +169,16 @@ export async function dismissReviews({ ]) } -/** - * @param {{ - * github: GitHub, - * context: Context, - * core: typeof import('@actions/core'), - * dry: boolean, - * body: string, - * event: keyof typeof eventToState, - * reviewKey: string, - * }} PostReviewProps - */ +interface PostReviewProps { + github: GitHub + context: Context + core: typeof import('@actions/core') + dry: boolean + body: string + event: keyof typeof eventToState + reviewKey: string +} + export async function postReview({ github, context, @@ -184,7 +187,7 @@ export async function postReview({ body, event = 'REQUEST_CHANGES', reviewKey, -}) { +}: PostReviewProps) { const pull_number = context.payload.pull_request?.number if (!pull_number) { core.warning('postReview called outside of pull_request context') @@ -210,8 +213,7 @@ export async function postReview({ reviewUsers.some((substr) => review.user?.login.includes(substr)), ) - /** @type {null | Review} */ - let pendingReview + let pendingReview: null | Review const matchingReviews = reviews.filter((review) => reviewKeyRegex.test(review.body), ) diff --git a/ci/github-script/run b/ci/github-script/run index 001d3e201a2b..2920ae8b6f44 100755 --- a/ci/github-script/run +++ b/ci/github-script/run @@ -101,7 +101,7 @@ program .argument('', 'Name of the GitHub repository to run on (Example: nixpkgs)') .argument('', 'Number of the Pull Request to run on') .action(async (owner, repo, pr, options) => { - const checkCommitMessages = (await import('./lint-commits.js')).default + const checkCommitMessages = (await import('./lint-commits.ts')).default await run(checkCommitMessages, owner, repo, pr, options) }) diff --git a/ci/github-script/supportedBranches.js b/ci/github-script/supportedBranches.ts similarity index 70% rename from ci/github-script/supportedBranches.js rename to ci/github-script/supportedBranches.ts index bcb3518cde5c..cb947691ca61 100755 --- a/ci/github-script/supportedBranches.js +++ b/ci/github-script/supportedBranches.ts @@ -2,11 +2,12 @@ /* #!nix-shell -i node -p nodejs */ -// @ts-nocheck import { resolve } from 'node:path' import { fileURLToPath } from 'node:url' -const typeConfig = { +type BranchType = 'channel' | 'development' | 'primary' | 'secondary' + +const typeConfig: Record = { master: ['development', 'primary'], release: ['development', 'primary'], staging: ['development', 'secondary'], @@ -19,7 +20,7 @@ const typeConfig = { // "order" ranks the development branches by how likely they are the intended base branch // when they are an otherwise equally good fit according to ci/github-script/prepare.js. -const orderConfig = { +const orderConfig: Record = { master: 0, release: 1, staging: 2, @@ -28,15 +29,30 @@ const orderConfig = { 'staging-next': 4, } -function split(branch) { - return { - ...branch.match( - /(?.+?)(-(?\d{2}\.\d{2}|unstable)(?:-(?.*))?)?$/, - ).groups, - } +type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 +type Version = `${Digit}${Digit}.${Digit}${Digit}` | 'unstable' +interface SplitResult { + prefix: string + version: Version + suffix?: string } -function classify(branch) { +function split(branch: string) { + const groups = branch.match( + /(?.+?)(-(?\d{2}\.\d{2}|unstable)(?:-(?.*))?)?$/, + )!.groups! + return groups as unknown as SplitResult +} + +interface BranchClassification { + branch: string + order: number + stable: boolean + type: BranchType[] + version: Version +} + +function classify(branch: string): BranchClassification { const { prefix, version } = split(branch) return { branch, @@ -55,7 +71,7 @@ if ( fileURLToPath(import.meta.url) === resolve(process.argv[1]) ) { console.log('split(branch)') - function testSplit(branch) { + function testSplit(branch: string) { console.log(branch, split(branch)) } testSplit('master') @@ -72,7 +88,7 @@ if ( console.log('') console.log('classify(branch)') - function testClassify(branch) { + function testClassify(branch: string) { console.log(branch, classify(branch)) } testClassify('master') diff --git a/ci/github-script/supportedSystems.js b/ci/github-script/supportedSystems.js deleted file mode 100644 index d035c3fee230..000000000000 --- a/ci/github-script/supportedSystems.js +++ /dev/null @@ -1,11 +0,0 @@ -// @ts-nocheck -export default async ({ github, context, targetSha }) => { - const { content, encoding } = ( - await github.rest.repos.getContent({ - ...context.repo, - path: 'pkgs/top-level/release-supported-systems.json', - ref: targetSha, - }) - ).data - return JSON.parse(Buffer.from(content, encoding).toString()) -} diff --git a/ci/github-script/supportedSystems.ts b/ci/github-script/supportedSystems.ts new file mode 100644 index 000000000000..dd9e898bddd8 --- /dev/null +++ b/ci/github-script/supportedSystems.ts @@ -0,0 +1,30 @@ +interface SupportedSystemsProps { + github: InstanceType + context: typeof import('@actions/github').context + targetSha: string +} + +export default async ({ + github, + context, + targetSha, +}: SupportedSystemsProps) => { + const contentObject = ( + await github.rest.repos.getContent({ + ...context.repo, + path: 'pkgs/top-level/release-supported-systems.json', + ref: targetSha, + }) + ).data + + if ('type' in contentObject && contentObject.type === 'file') { + const { content, encoding } = contentObject + return JSON.parse( + Buffer.from(content, encoding as BufferEncoding).toString(), + ) + } else { + throw new Error( + 'Fetched pkgs/top-level/release-supported-systems.json is not a file', + ) + } +} diff --git a/pkgs/by-name/xr/xreader/package.nix b/pkgs/by-name/xr/xreader/package.nix index fdcde558a6fa..ed8c8cc3fbf1 100644 --- a/pkgs/by-name/xr/xreader/package.nix +++ b/pkgs/by-name/xr/xreader/package.nix @@ -2,6 +2,7 @@ stdenv, lib, fetchFromGitHub, + fetchpatch, glib, gobject-introspection, intltool, @@ -46,6 +47,15 @@ stdenv.mkDerivation (finalAttrs: { hash = "sha256-wycQmScxuSlo6Ln6piSBF7kmzvi6FnTm/ES/Ds+/h8I="; }; + patches = [ + # ev-poppler.cc: Only read a link destination for GOTO_DEST + # Fixes CVE-2026-19772 + (fetchpatch { + url = "https://github.com/linuxmint/xreader/commit/28ee72cc2779a3716b7d00da1aa87da992648d24.patch"; + hash = "sha256-pd0kyfwsph+H9lii7CfndETsKTYSWyYw2tb9bMOHRX8="; + }) + ]; + nativeBuildInputs = [ shared-mime-info wrapGAppsHook3