diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d314276b892..ced20ee216e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -98,7 +98,7 @@ jobs: run: gh api ${{ env.PR_URL }} | jq -rc '${{ env.JQ_FILTER }}' >> "$GITHUB_OUTPUT" - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: filter: blob:none # we don't need all blobs sparse-checkout: ${{ env.SPARSE_CHECKOUT }} @@ -137,7 +137,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: filter: blob:none # we don't need all blobs sparse-checkout: ${{ env.SPARSE_CHECKOUT }} @@ -181,7 +181,7 @@ jobs: steps: - name: Checkout code if: needs.setup.outputs.os-matrix-is-full && runner.os != 'Linux' - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: filter: blob:none # we don't need all blobs sparse-checkout: ${{ env.SPARSE_CHECKOUT }} @@ -203,7 +203,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false @@ -239,7 +239,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false @@ -281,7 +281,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false @@ -311,7 +311,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false @@ -353,7 +353,7 @@ jobs: include: ${{ fromJSON(needs.setup.outputs.test-shard-matrix) }} steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false @@ -415,13 +415,13 @@ jobs: if: success() && github.event_name != 'merge_group' && github.event.pull_request.draft != true steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: filter: blob:none # we don't need all blobs show-progress: false - name: Download coverage reports - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: pattern: coverage-* path: coverage @@ -444,7 +444,7 @@ jobs: if: success() && github.event.pull_request.draft != true steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: filter: blob:none # we don't need all blobs sparse-checkout: ${{ env.SPARSE_CHECKOUT }} @@ -457,7 +457,7 @@ jobs: os: ${{ runner.os }} - name: Download coverage reports - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: pattern: coverage-* path: coverage @@ -535,7 +535,7 @@ jobs: if: github.event.pull_request.draft != true steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false @@ -562,12 +562,12 @@ jobs: if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci:fulltest') steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false - name: Download dist - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: renovate-dist path: dist/ @@ -601,7 +601,7 @@ jobs: if: github.event.pull_request.draft != true steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false @@ -648,7 +648,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false @@ -659,7 +659,7 @@ jobs: os: ${{ runner.os }} - name: Download dist - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: renovate-dist path: dist/ @@ -698,7 +698,7 @@ jobs: packages: write steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 # zero stands for full checkout, which is required for semantic-release filter: blob:none # we don't need all blobs, only the full tree diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f6a166c92f1..2e0ee7c175b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -31,7 +31,7 @@ jobs: security-events: write steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index d15543cea72..2325b8693a3 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 31a4893e348..bee0ba0bb55 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -18,7 +18,7 @@ jobs: if: github.event.pull_request.draft != true steps: - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false diff --git a/.github/workflows/find-issues-with-missing-labels.yml b/.github/workflows/find-issues-with-missing-labels.yml index 7645c93f0bd..f319dadee6f 100644 --- a/.github/workflows/find-issues-with-missing-labels.yml +++ b/.github/workflows/find-issues-with-missing-labels.yml @@ -18,7 +18,7 @@ jobs: GH_TOKEN: ${{ github.token }} steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Search for issues with missing labels run: bash ./tools/find-issues-with-missing-labels.sh diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a57979062cb..208a0838b05 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -20,7 +20,7 @@ jobs: steps: - name: 'Checkout code' - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false show-progress: false diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 895d802418a..75428836f28 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -21,7 +21,7 @@ jobs: - full steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false diff --git a/.github/workflows/undesirable-test-additions.yaml b/.github/workflows/undesirable-test-additions.yaml index ac58b30e9f3..7869278f1bf 100644 --- a/.github/workflows/undesirable-test-additions.yaml +++ b/.github/workflows/undesirable-test-additions.yaml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 # Fetch all history for comparison sparse-checkout: true diff --git a/.github/workflows/update-data.yml b/.github/workflows/update-data.yml index fc16fee3a4b..d143f95827e 100644 --- a/.github/workflows/update-data.yml +++ b/.github/workflows/update-data.yml @@ -16,7 +16,7 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false diff --git a/.github/workflows/ws_scan.yaml b/.github/workflows/ws_scan.yaml index 8d0e8b6816d..99d00180d58 100644 --- a/.github/workflows/ws_scan.yaml +++ b/.github/workflows/ws_scan.yaml @@ -11,7 +11,7 @@ jobs: WS_SCAN: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: show-progress: false diff --git a/lib/modules/datasource/aws-eks-addon/schema.ts b/lib/modules/datasource/aws-eks-addon/schema.ts index 1db85075e0b..4a67343b8e0 100644 --- a/lib/modules/datasource/aws-eks-addon/schema.ts +++ b/lib/modules/datasource/aws-eks-addon/schema.ts @@ -2,18 +2,22 @@ import { z } from 'zod/v4'; import { regEx } from '../../../util/regex'; import { Json } from '../../../util/schema-utils/v4'; -export const EksAddonsFilterSchema = z.object({ - addonName: z.string().nonempty().regex(regEx('^[a-z0-9][a-z0-9-]*[a-z0-9]$')), - kubernetesVersion: z - .string() - .regex(regEx('^(?\\d+)\\.(?\\d+)$')) - .optional(), - default: z - .union([z.boolean(), z.string().transform((value) => value === 'true')]) - .optional(), - region: z.string().optional(), - profile: z.string().optional(), -}); +export const EksAddonsFilter = Json.pipe( + z.object({ + addonName: z + .string() + .nonempty() + .regex(regEx('^[a-z0-9][a-z0-9-]*[a-z0-9]$')), + kubernetesVersion: z + .string() + .regex(regEx('^(?\\d+)\\.(?\\d+)$')) + .optional(), + default: z + .union([z.boolean(), z.string().transform((value) => value === 'true')]) + .optional(), + region: z.string().optional(), + profile: z.string().optional(), + }), +); -export type EksAddonsFilter = z.infer; -export const EksAddonsFilter = Json.pipe(EksAddonsFilterSchema); +export type EksAddonsFilter = z.infer; diff --git a/lib/modules/datasource/buildpacks-registry/index.ts b/lib/modules/datasource/buildpacks-registry/index.ts index bf54f00daf8..5a1aa20d262 100644 --- a/lib/modules/datasource/buildpacks-registry/index.ts +++ b/lib/modules/datasource/buildpacks-registry/index.ts @@ -6,7 +6,7 @@ import { Result } from '../../../util/result'; import { Datasource } from '../datasource'; import { ReleasesConfig } from '../schema'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; -import { BuildpacksRegistryResponseSchema } from './schema'; +import { BuildpacksRegistryResponse } from './schema'; export class BuildpacksRegistryDatasource extends Datasource { static readonly id = 'buildpacks-registry'; @@ -42,7 +42,7 @@ export class BuildpacksRegistryDatasource extends Datasource { packageName, ); - return this.http.getJsonSafe(url, BuildpacksRegistryResponseSchema); + return this.http.getJsonSafe(url, BuildpacksRegistryResponse); }) .transform(({ versions, latest }): ReleaseResult => { const releases: Release[] = versions; diff --git a/lib/modules/datasource/buildpacks-registry/schema.spec.ts b/lib/modules/datasource/buildpacks-registry/schema.spec.ts index a46bd3998fe..83e7ed6548f 100644 --- a/lib/modules/datasource/buildpacks-registry/schema.spec.ts +++ b/lib/modules/datasource/buildpacks-registry/schema.spec.ts @@ -1,4 +1,4 @@ -import { BuildpacksRegistryResponseSchema } from './schema'; +import { BuildpacksRegistryResponse } from './schema'; describe('modules/datasource/buildpacks-registry/schema', () => { it('parses buildpack-registry schema', () => { @@ -26,7 +26,7 @@ describe('modules/datasource/buildpacks-registry/schema', () => { }, ], }; - expect(BuildpacksRegistryResponseSchema.parse(response)).toMatchObject({ + expect(BuildpacksRegistryResponse.parse(response)).toMatchObject({ latest: { homepage: 'https://github.com/heroku/buildpacks-python', }, diff --git a/lib/modules/datasource/buildpacks-registry/schema.ts b/lib/modules/datasource/buildpacks-registry/schema.ts index 6bc54419638..c22c934e106 100644 --- a/lib/modules/datasource/buildpacks-registry/schema.ts +++ b/lib/modules/datasource/buildpacks-registry/schema.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; /** * Response from registry.buildpacks.io */ -export const BuildpacksRegistryResponseSchema = z.object({ +export const BuildpacksRegistryResponse = z.object({ latest: z .object({ homepage: z.string().optional(), diff --git a/lib/modules/datasource/cdnjs/index.ts b/lib/modules/datasource/cdnjs/index.ts index 8312050dbbb..e5205d53ae4 100644 --- a/lib/modules/datasource/cdnjs/index.ts +++ b/lib/modules/datasource/cdnjs/index.ts @@ -13,10 +13,7 @@ import type { Release, ReleaseResult, } from '../types'; -import { - CdnjsAPISriResponseSchema, - CdnjsAPIVersionResponseSchema, -} from './schema'; +import { CdnjsAPISriResponse, CdnjsAPIVersionResponse } from './schema'; export class CdnjsDatasource extends Datasource { static readonly id = 'cdnjs'; @@ -50,7 +47,7 @@ export class CdnjsDatasource extends Datasource { return this.http.getJsonSafe( url, { cacheProvider: memCacheProvider }, - CdnjsAPIVersionResponseSchema, + CdnjsAPIVersionResponse, ); }) .transform(({ versions, homepage, repository }): ReleaseResult => { @@ -100,7 +97,7 @@ export class CdnjsDatasource extends Datasource { .transform(({ registryUrl }) => { const url = `${registryUrl}libraries/${library}/${newValue}?fields=sri`; - return this.http.getJsonSafe(url, CdnjsAPISriResponseSchema); + return this.http.getJsonSafe(url, CdnjsAPISriResponse); }) .transform(({ sri }): string => { return sri?.[assetName]; diff --git a/lib/modules/datasource/cdnjs/schema.ts b/lib/modules/datasource/cdnjs/schema.ts index 38341de296d..6a5bd6d3376 100644 --- a/lib/modules/datasource/cdnjs/schema.ts +++ b/lib/modules/datasource/cdnjs/schema.ts @@ -19,12 +19,12 @@ export const Versions = z export const Sri = z.record(z.string()); -export const CdnjsAPIVersionResponseSchema = z.object({ +export const CdnjsAPIVersionResponse = z.object({ homepage: Homepage, repository: Repository, versions: Versions, }); -export const CdnjsAPISriResponseSchema = z.object({ +export const CdnjsAPISriResponse = z.object({ sri: Sri, }); diff --git a/lib/modules/datasource/conda/prefix-dev.ts b/lib/modules/datasource/conda/prefix-dev.ts index 28e84591b53..e60c29fc418 100644 --- a/lib/modules/datasource/conda/prefix-dev.ts +++ b/lib/modules/datasource/conda/prefix-dev.ts @@ -3,7 +3,7 @@ import { isNotNullOrUndefined } from '../../../util/array'; import type { Http } from '../../../util/http'; import { MaybeTimestamp } from '../../../util/timestamp'; import type { Release, ReleaseResult } from '../types'; -import { type File, PagedResponseSchema } from './schema/prefix-dev'; +import { type File, PagedResponse } from './schema/prefix-dev'; const MAX_PREFIX_DEV_GRAPHQL_PAGE = 100; @@ -93,7 +93,7 @@ async function getPagedResponse( }, }, }, - PagedResponseSchema, + PagedResponse, ); const currentPage = res.body.data.package?.variants; diff --git a/lib/modules/datasource/conda/schema/prefix-dev.ts b/lib/modules/datasource/conda/schema/prefix-dev.ts index 4f07efe8ac3..9a685bd1261 100644 --- a/lib/modules/datasource/conda/schema/prefix-dev.ts +++ b/lib/modules/datasource/conda/schema/prefix-dev.ts @@ -15,7 +15,7 @@ export const File = z.object({ export type File = z.infer; -export const PagedResponseSchema = z.object({ +export const PagedResponse = z.object({ data: z.object({ package: z .object({ diff --git a/lib/modules/datasource/cpan/schema.ts b/lib/modules/datasource/cpan/schema.ts index e44ce810626..d21b43bb880 100644 --- a/lib/modules/datasource/cpan/schema.ts +++ b/lib/modules/datasource/cpan/schema.ts @@ -6,7 +6,7 @@ import type { CpanRelease } from './types'; /** * https://fastapi.metacpan.org/v1/file/_mapping */ -const MetaCpanApiFileSchema = z +const MetaCpanApiFile = z .object({ module: LooseArray( z.object({ @@ -52,7 +52,7 @@ export const MetaCpanApiFileSearchResponse = z hits: z.object({ hits: LooseArray( z.object({ - _source: MetaCpanApiFileSchema, + _source: MetaCpanApiFile, }), ), }), diff --git a/lib/modules/datasource/crate/index.ts b/lib/modules/datasource/crate/index.ts index cbdea92b7cb..2adfc529402 100644 --- a/lib/modules/datasource/crate/index.ts +++ b/lib/modules/datasource/crate/index.ts @@ -20,7 +20,7 @@ import type { Release, ReleaseResult, } from '../types'; -import { ReleaseTimestampSchema } from './schema'; +import { ReleaseTimestamp } from './schema'; import type { CrateMetadata, CrateRecord, @@ -442,7 +442,7 @@ export class CrateDatasource extends Datasource { const { body: releaseTimestamp } = await this.http.getJson( url, { cacheProvider: memCacheProvider }, - ReleaseTimestampSchema, + ReleaseTimestamp, ); release.releaseTimestamp = releaseTimestamp; return release; diff --git a/lib/modules/datasource/crate/schema.ts b/lib/modules/datasource/crate/schema.ts index af908f22a93..822b14b04d2 100644 --- a/lib/modules/datasource/crate/schema.ts +++ b/lib/modules/datasource/crate/schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { MaybeTimestamp } from '../../../util/timestamp'; -export const ReleaseTimestampSchema = z +export const ReleaseTimestamp = z .object({ version: z.object({ created_at: MaybeTimestamp, diff --git a/lib/modules/datasource/custom/index.ts b/lib/modules/datasource/custom/index.ts index 9e03a3c2ddf..3d7795580a1 100644 --- a/lib/modules/datasource/custom/index.ts +++ b/lib/modules/datasource/custom/index.ts @@ -4,7 +4,7 @@ import { getExpression } from '../../../util/jsonata'; import { Datasource } from '../datasource'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; import { fetchers } from './formats'; -import { ReleaseResultZodSchema } from './schema'; +import { ReleaseResultZod } from './schema'; import { getCustomConfig } from './utils'; export class CustomDatasource extends Datasource { @@ -78,7 +78,7 @@ export class CustomDatasource extends Datasource { } try { - const parsed = ReleaseResultZodSchema.parse(data); + const parsed = ReleaseResultZod.parse(data); return structuredClone(parsed); } catch (err) { logger.debug({ err }, `Response has failed validation`); @@ -88,8 +88,8 @@ export class CustomDatasource extends Datasource { } override getDigest( - { packageName }: DigestConfig, - newValue?: string, + _cfg: DigestConfig, + _newValue?: string, ): Promise { // Return null here to support setting a digest: value can be provided digest in getReleases return Promise.resolve(null); diff --git a/lib/modules/datasource/custom/readme.md b/lib/modules/datasource/custom/readme.md index f3f051862d1..fe5f526438b 100644 --- a/lib/modules/datasource/custom/readme.md +++ b/lib/modules/datasource/custom/readme.md @@ -98,7 +98,7 @@ If you use the Mend Renovate app, use the [`logLevelRemap` config option](../../ { "logLevelRemap": [ { - "matchMessage": "/^Custom manager fetcher/", + "matchMessage": "/^Custom datasource/", "newLogLevel": "info" } ] diff --git a/lib/modules/datasource/custom/schema.ts b/lib/modules/datasource/custom/schema.ts index c45628064f4..cd196524671 100644 --- a/lib/modules/datasource/custom/schema.ts +++ b/lib/modules/datasource/custom/schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { MaybeTimestamp } from '../../../util/timestamp'; -export const ReleaseResultZodSchema = z.object({ +export const ReleaseResultZod = z.object({ releases: z.array( z .object({ diff --git a/lib/modules/datasource/forgejo-releases/index.ts b/lib/modules/datasource/forgejo-releases/index.ts index b348ea3bf08..cdab0887d9b 100644 --- a/lib/modules/datasource/forgejo-releases/index.ts +++ b/lib/modules/datasource/forgejo-releases/index.ts @@ -3,9 +3,9 @@ import type { PackageCacheNamespace } from '../../../util/cache/package/types'; import { ForgejoHttp } from '../../../util/http/forgejo'; import { Datasource } from '../datasource'; import { ForgejoTagsDatasource } from '../forgejo-tags'; -import { CommitsSchema, TagSchema } from '../forgejo-tags/schema'; +import { Commits, Tag } from '../forgejo-tags/schema'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; -import { ReleasesSchema } from './schema'; +import { Releases } from './schema'; export class ForgejoReleasesDatasource extends Datasource { static readonly id = 'forgejo-releases'; @@ -46,7 +46,7 @@ export class ForgejoReleasesDatasource extends Datasource { { paginate: true, }, - ReleasesSchema, + Releases, ) ).body; @@ -79,7 +79,7 @@ export class ForgejoReleasesDatasource extends Datasource { registryUrl, )}repos/${repo}/tags/${tag}`; - const { body } = await this.http.getJson(url, TagSchema); + const { body } = await this.http.getJson(url, Tag); return body.commit.sha; } @@ -102,7 +102,7 @@ export class ForgejoReleasesDatasource extends Datasource { const url = `${ForgejoTagsDatasource.getApiUrl( registryUrl, )}repos/${repo}/commits?stat=false&verification=false&files=false&page=1&limit=1`; - const { body } = await this.http.getJson(url, CommitsSchema); + const { body } = await this.http.getJson(url, Commits); if (body.length === 0) { return null; diff --git a/lib/modules/datasource/forgejo-releases/schema.ts b/lib/modules/datasource/forgejo-releases/schema.ts index b1482c99701..19f652aa9d0 100644 --- a/lib/modules/datasource/forgejo-releases/schema.ts +++ b/lib/modules/datasource/forgejo-releases/schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { MaybeTimestamp } from '../../../util/timestamp'; -export const ReleaseSchema = z.object({ +export const Release = z.object({ name: z.string(), tag_name: z.string(), body: z.string(), @@ -9,4 +9,4 @@ export const ReleaseSchema = z.object({ published_at: MaybeTimestamp, }); -export const ReleasesSchema = z.array(ReleaseSchema); +export const Releases = z.array(Release); diff --git a/lib/modules/datasource/forgejo-tags/index.ts b/lib/modules/datasource/forgejo-tags/index.ts index c5ab1972ab1..8b292a56e9c 100644 --- a/lib/modules/datasource/forgejo-tags/index.ts +++ b/lib/modules/datasource/forgejo-tags/index.ts @@ -5,7 +5,7 @@ import { regEx } from '../../../util/regex'; import { ensureTrailingSlash } from '../../../util/url'; import { Datasource } from '../datasource'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; -import { CommitsSchema, TagSchema, TagsSchema } from './schema'; +import { Commits, Tag, Tags } from './schema'; export class ForgejoTagsDatasource extends Datasource { static readonly id = 'forgejo-tags'; @@ -73,7 +73,7 @@ export class ForgejoTagsDatasource extends Datasource { { paginate: true, }, - TagsSchema, + Tags, ) ).body; @@ -106,7 +106,7 @@ export class ForgejoTagsDatasource extends Datasource { registryUrl, )}repos/${repo}/tags/${tag}`; - const { body } = await this.http.getJson(url, TagSchema); + const { body } = await this.http.getJson(url, Tag); return body.commit.sha; } @@ -129,7 +129,7 @@ export class ForgejoTagsDatasource extends Datasource { const url = `${ForgejoTagsDatasource.getApiUrl( registryUrl, )}repos/${repo}/commits?stat=false&verification=false&files=false&page=1&limit=1`; - const { body } = await this.http.getJson(url, CommitsSchema); + const { body } = await this.http.getJson(url, Commits); if (body.length === 0) { return null; diff --git a/lib/modules/datasource/forgejo-tags/schema.ts b/lib/modules/datasource/forgejo-tags/schema.ts index 3ba004a0bb1..5df59d7b432 100644 --- a/lib/modules/datasource/forgejo-tags/schema.ts +++ b/lib/modules/datasource/forgejo-tags/schema.ts @@ -1,19 +1,19 @@ import { z } from 'zod'; import { MaybeTimestamp } from '../../../util/timestamp'; -export const CommitSchema = z.object({ +export const Commit = z.object({ sha: z.string(), }); -export const CommitsSchema = z.array(CommitSchema); +export const Commits = z.array(Commit); -const TagCommitSchema = z.object({ +const TagCommit = z.object({ sha: z.string(), created: MaybeTimestamp, }); -export const TagSchema = z.object({ +export const Tag = z.object({ name: z.string(), - commit: TagCommitSchema, + commit: TagCommit, }); -export const TagsSchema = z.array(TagSchema); +export const Tags = z.array(Tag); diff --git a/lib/modules/datasource/gitea-releases/index.ts b/lib/modules/datasource/gitea-releases/index.ts index a2febc4d922..ad799a97a27 100644 --- a/lib/modules/datasource/gitea-releases/index.ts +++ b/lib/modules/datasource/gitea-releases/index.ts @@ -3,9 +3,9 @@ import type { PackageCacheNamespace } from '../../../util/cache/package/types'; import { GiteaHttp } from '../../../util/http/gitea'; import { Datasource } from '../datasource'; import { GiteaTagsDatasource } from '../gitea-tags'; -import { CommitsSchema, TagSchema } from '../gitea-tags/schema'; +import { Commits, Tag } from '../gitea-tags/schema'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; -import { ReleasesSchema } from './schema'; +import { Releases } from './schema'; export class GiteaReleasesDatasource extends Datasource { static readonly id = 'gitea-releases'; @@ -46,7 +46,7 @@ export class GiteaReleasesDatasource extends Datasource { { paginate: true, }, - ReleasesSchema, + Releases, ) ).body; @@ -79,7 +79,7 @@ export class GiteaReleasesDatasource extends Datasource { registryUrl, )}repos/${repo}/tags/${tag}`; - const { body } = await this.http.getJson(url, TagSchema); + const { body } = await this.http.getJson(url, Tag); return body.commit.sha; } @@ -102,7 +102,7 @@ export class GiteaReleasesDatasource extends Datasource { const url = `${GiteaTagsDatasource.getApiUrl( registryUrl, )}repos/${repo}/commits?stat=false&verification=false&files=false&page=1&limit=1`; - const { body } = await this.http.getJson(url, CommitsSchema); + const { body } = await this.http.getJson(url, Commits); if (body.length === 0) { return null; diff --git a/lib/modules/datasource/gitea-releases/schema.ts b/lib/modules/datasource/gitea-releases/schema.ts index b1482c99701..19f652aa9d0 100644 --- a/lib/modules/datasource/gitea-releases/schema.ts +++ b/lib/modules/datasource/gitea-releases/schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { MaybeTimestamp } from '../../../util/timestamp'; -export const ReleaseSchema = z.object({ +export const Release = z.object({ name: z.string(), tag_name: z.string(), body: z.string(), @@ -9,4 +9,4 @@ export const ReleaseSchema = z.object({ published_at: MaybeTimestamp, }); -export const ReleasesSchema = z.array(ReleaseSchema); +export const Releases = z.array(Release); diff --git a/lib/modules/datasource/gitea-tags/index.ts b/lib/modules/datasource/gitea-tags/index.ts index fb893b0c33c..83c5db8aae5 100644 --- a/lib/modules/datasource/gitea-tags/index.ts +++ b/lib/modules/datasource/gitea-tags/index.ts @@ -5,7 +5,7 @@ import { regEx } from '../../../util/regex'; import { ensureTrailingSlash } from '../../../util/url'; import { Datasource } from '../datasource'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; -import { CommitsSchema, TagSchema, TagsSchema } from './schema'; +import { Commits, Tag, Tags } from './schema'; export class GiteaTagsDatasource extends Datasource { static readonly id = 'gitea-tags'; @@ -73,7 +73,7 @@ export class GiteaTagsDatasource extends Datasource { { paginate: true, }, - TagsSchema, + Tags, ) ).body; @@ -106,7 +106,7 @@ export class GiteaTagsDatasource extends Datasource { registryUrl, )}repos/${repo}/tags/${tag}`; - const { body } = await this.http.getJson(url, TagSchema); + const { body } = await this.http.getJson(url, Tag); return body.commit.sha; } @@ -129,7 +129,7 @@ export class GiteaTagsDatasource extends Datasource { const url = `${GiteaTagsDatasource.getApiUrl( registryUrl, )}repos/${repo}/commits?stat=false&verification=false&files=false&page=1&limit=1`; - const { body } = await this.http.getJson(url, CommitsSchema); + const { body } = await this.http.getJson(url, Commits); if (body.length === 0) { return null; diff --git a/lib/modules/datasource/gitea-tags/schema.ts b/lib/modules/datasource/gitea-tags/schema.ts index 3ba004a0bb1..5df59d7b432 100644 --- a/lib/modules/datasource/gitea-tags/schema.ts +++ b/lib/modules/datasource/gitea-tags/schema.ts @@ -1,19 +1,19 @@ import { z } from 'zod'; import { MaybeTimestamp } from '../../../util/timestamp'; -export const CommitSchema = z.object({ +export const Commit = z.object({ sha: z.string(), }); -export const CommitsSchema = z.array(CommitSchema); +export const Commits = z.array(Commit); -const TagCommitSchema = z.object({ +const TagCommit = z.object({ sha: z.string(), created: MaybeTimestamp, }); -export const TagSchema = z.object({ +export const Tag = z.object({ name: z.string(), - commit: TagCommitSchema, + commit: TagCommit, }); -export const TagsSchema = z.array(TagSchema); +export const Tags = z.array(Tag); diff --git a/lib/modules/datasource/helm/index.ts b/lib/modules/datasource/helm/index.ts index ea344de103e..0e48b5c7c87 100644 --- a/lib/modules/datasource/helm/index.ts +++ b/lib/modules/datasource/helm/index.ts @@ -5,7 +5,7 @@ import * as helmVersioning from '../../versioning/helm'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import type { HelmRepositoryData } from './schema'; -import { HelmRepositorySchema } from './schema'; +import { HelmRepository } from './schema'; export class HelmDatasource extends Datasource { static readonly id = 'helm'; @@ -38,7 +38,7 @@ export class HelmDatasource extends Datasource { .getYamlSafe( 'index.yaml', { baseUrl: ensureTrailingSlash(helmRepository) }, - HelmRepositorySchema, + HelmRepository, ) .unwrap(); diff --git a/lib/modules/datasource/helm/schema.spec.ts b/lib/modules/datasource/helm/schema.spec.ts index 2484d1092e2..44c860a7c94 100644 --- a/lib/modules/datasource/helm/schema.spec.ts +++ b/lib/modules/datasource/helm/schema.spec.ts @@ -1,13 +1,11 @@ import { Yaml } from '../../../util/schema-utils'; -import { HelmRepositorySchema } from './schema'; +import { HelmRepository } from './schema'; import { Fixtures } from '~test/fixtures'; describe('modules/datasource/helm/schema', () => { describe('sourceUrl', () => { it('works', () => { - const repo = Yaml.pipe(HelmRepositorySchema).parse( - Fixtures.get('sample.yaml'), - ); + const repo = Yaml.pipe(HelmRepository).parse(Fixtures.get('sample.yaml')); expect(repo).toMatchObject({ airflow: { homepage: diff --git a/lib/modules/datasource/helm/schema.ts b/lib/modules/datasource/helm/schema.ts index 64465295c67..700d631fe4d 100644 --- a/lib/modules/datasource/helm/schema.ts +++ b/lib/modules/datasource/helm/schema.ts @@ -6,7 +6,7 @@ import { LooseRecord } from '../../../util/schema-utils'; import { MaybeTimestamp } from '../../../util/timestamp'; import type { Release } from '../types'; -const HelmReleaseSchema = z.object({ +const HelmRelease = z.object({ version: z.string(), created: MaybeTimestamp, digest: z.string().optional().catch(undefined), @@ -14,7 +14,7 @@ const HelmReleaseSchema = z.object({ sources: z.array(z.string()).catch([]), urls: z.array(z.string()).catch([]), }); -type HelmRelease = z.infer; +type HelmRelease = z.infer; const chartRepo = regEx(/charts?|helm|helm-charts/i); @@ -53,11 +53,11 @@ function getSourceUrl(release: HelmRelease): string | undefined { return release.sources[0]; } -export const HelmRepositorySchema = z +export const HelmRepository = z .object({ entries: LooseRecord( z.string(), - HelmReleaseSchema.array() + HelmRelease.array() .min(1) .transform((helmReleases) => { const latestRelease = helmReleases[0]; @@ -80,4 +80,4 @@ export const HelmRepositorySchema = z }) .transform(({ entries }) => entries); -export type HelmRepositoryData = z.infer; +export type HelmRepositoryData = z.infer; diff --git a/lib/modules/manager/argocd/extract.ts b/lib/modules/manager/argocd/extract.ts index a390889cf67..8402901a89f 100644 --- a/lib/modules/manager/argocd/extract.ts +++ b/lib/modules/manager/argocd/extract.ts @@ -16,7 +16,7 @@ import type { } from '../types'; import { type ApplicationDefinition, - ApplicationDefinitionSchema, + ApplicationDefinitions, type ApplicationSource, type ApplicationSpec, } from './schema'; @@ -37,7 +37,7 @@ export function extractPackageFile( return null; } - const definitions = ApplicationDefinitionSchema.catch( + const definitions = ApplicationDefinitions.catch( withDebugMessage([], `${packageFile} does not match schema`), ).parse(content); diff --git a/lib/modules/manager/argocd/schema.ts b/lib/modules/manager/argocd/schema.ts index 395f7f9a9d6..52c067c6846 100644 --- a/lib/modules/manager/argocd/schema.ts +++ b/lib/modules/manager/argocd/schema.ts @@ -39,6 +39,6 @@ export const ApplicationSet = KubernetesResource.extend({ export const ApplicationDefinition = Application.or(ApplicationSet); export type ApplicationDefinition = z.infer; -export const ApplicationDefinitionSchema = multidocYaml({ +export const ApplicationDefinitions = multidocYaml({ removeTemplates: true, }).pipe(LooseArray(ApplicationDefinition)); diff --git a/lib/modules/manager/azure-pipelines/readme.md b/lib/modules/manager/azure-pipelines/readme.md index 8face382d80..aecbd0bbbeb 100644 --- a/lib/modules/manager/azure-pipelines/readme.md +++ b/lib/modules/manager/azure-pipelines/readme.md @@ -44,7 +44,7 @@ resources: - container: linux image: ubuntu:24.04 - container: python - image: python:3.13@sha256:819daf0e0d5e5bb88927aea18a9279f870f746c234d45468fd493ab618729866 + image: python:3.13@sha256:1e4584c017d9cecf8a952cfd8f8e47ef5611b28dfc29da9e1d4b6bad98daf9b0 stages: - stage: StageOne diff --git a/lib/modules/manager/batect/extract.ts b/lib/modules/manager/batect/extract.ts index 4e5704367bb..76ad132815f 100644 --- a/lib/modules/manager/batect/extract.ts +++ b/lib/modules/manager/batect/extract.ts @@ -2,7 +2,7 @@ import upath from 'upath'; import { logger } from '../../../logger'; import { readLocalFile } from '../../../util/fs'; import type { ExtractConfig, PackageFile } from '../types'; -import { BatectConfigSchema } from './schema'; +import { BatectConfig } from './schema'; import type { ExtractionResult } from './types'; export function extractPackageFile( @@ -13,7 +13,7 @@ export function extractPackageFile( try { const { imageDependencies, bundleDependencies, fileIncludes } = - BatectConfigSchema.parse(content); + BatectConfig.parse(content); const deps = [...imageDependencies, ...bundleDependencies]; const dirName = upath.dirname(packageFile); diff --git a/lib/modules/manager/batect/schema.ts b/lib/modules/manager/batect/schema.ts index c400448c075..2dd444ba723 100644 --- a/lib/modules/manager/batect/schema.ts +++ b/lib/modules/manager/batect/schema.ts @@ -5,7 +5,7 @@ import { id as semverVersioning } from '../../versioning/semver'; import { getDep } from '../dockerfile/extract'; import type { PackageDependency } from '../types'; -export const BatectConfigSchema = Yaml.pipe( +export const BatectConfig = Yaml.pipe( z.object({ containers: LooseRecord( z.string(), @@ -56,4 +56,4 @@ export const BatectConfigSchema = Yaml.pipe( }; }); -export type BatectConfig = z.infer; +export type BatectConfig = z.infer; diff --git a/lib/modules/manager/bazel-module/extract.spec.ts b/lib/modules/manager/bazel-module/extract.spec.ts index 086e3867261..109ee78c469 100644 --- a/lib/modules/manager/bazel-module/extract.spec.ts +++ b/lib/modules/manager/bazel-module/extract.spec.ts @@ -618,121 +618,5 @@ describe('modules/manager/bazel-module/extract', () => { ], }); }); - - it('returns rules_img pull dependencies', async () => { - const input = codeBlock` - pull = use_repo_rule("@rules_img//img:pull.bzl", "pull") - - pull( - name = "ubuntu", - digest = "sha256:1e622c5f073b4f6bfad6632f2616c7f59ef256e96fe78bf6a595d1dc4376ac02", - registry = "index.docker.io", - repository = "library/ubuntu", - tag = "24.04", - ) - `; - const res = await extractPackageFile(input, 'MODULE.bazel'); - - expect(res).toEqual({ - deps: [ - { - datasource: 'docker', - depType: 'rules_img_pull', - depName: 'ubuntu', - packageName: 'index.docker.io/library/ubuntu', - currentValue: '24.04', - currentDigest: - 'sha256:1e622c5f073b4f6bfad6632f2616c7f59ef256e96fe78bf6a595d1dc4376ac02', - replaceString: expect.stringContaining('pull('), - }, - ], - }); - }); - - it('returns rules_img pull dependencies without tag', async () => { - const input = codeBlock` - pull = use_repo_rule("@rules_img//img:pull.bzl", "pull") - - pull( - name = "distroless_cc", - digest = "sha256:d1b8e4c52be1111aa108e959ef2a822299bb70fd1819dd250871a2601ca1e4b6", - registry = "gcr.io", - repository = "distroless/cc-debian12", - ) - `; - const res = await extractPackageFile(input, 'MODULE.bazel'); - - expect(res).toEqual({ - deps: [ - { - datasource: 'docker', - depType: 'rules_img_pull', - depName: 'distroless_cc', - packageName: 'gcr.io/distroless/cc-debian12', - currentDigest: - 'sha256:d1b8e4c52be1111aa108e959ef2a822299bb70fd1819dd250871a2601ca1e4b6', - replaceString: expect.stringContaining('pull('), - }, - ], - }); - }); - - it('ignores non-rules_img use_repo_rule calls', async () => { - const input = codeBlock` - other_rule = use_repo_rule("@some_other//path:rule.bzl", "other_rule") - - pull( - name = "ubuntu", - digest = "sha256:1e622c5f073b4f6bfad6632f2616c7f59ef256e96fe78bf6a595d1dc4376ac02", - registry = "index.docker.io", - repository = "library/ubuntu", - tag = "24.04", - ) - `; - const res = await extractPackageFile(input, 'MODULE.bazel'); - - expect(res).toBeNull(); - }); - - it('ignores non-rules_img use_repo_rule calls that use the name pull', async () => { - const input = codeBlock` - pull = use_repo_rule("@some_other//path:rule.bzl", "pull") - - pull( - name = "test", - value = "ignored", - ) - `; - const res = await extractPackageFile(input, 'MODULE.bazel'); - - expect(res).toBeNull(); - }); - - it('handles multiple rules_img pulls', async () => { - const input = codeBlock` - pull = use_repo_rule("@rules_img//img:pull.bzl", "pull") - - pull( - name = "ubuntu", - digest = "sha256:1e622c5f073b4f6bfad6632f2616c7f59ef256e96fe78bf6a595d1dc4376ac02", - registry = "index.docker.io", - repository = "library/ubuntu", - tag = "24.04", - ) - - pull( - name = "cuda", - digest = "sha256:f353ffca86e0cd93ab2470fe274ecf766519c24c37ed58cc2f91d915f7ebe53c", - registry = "index.docker.io", - repository = "nvidia/cuda", - tag = "12.8.1-cudnn-devel-ubuntu20.04", - ) - `; - const res = await extractPackageFile(input, 'MODULE.bazel'); - - expect(res?.deps).toHaveLength(2); - expect(res?.deps[0].depName).toBe('ubuntu'); - expect(res?.deps[1].depName).toBe('cuda'); - }); }); }); diff --git a/lib/modules/manager/bazel-module/extract.ts b/lib/modules/manager/bazel-module/extract.ts index 30655a686f8..92c74705d4b 100644 --- a/lib/modules/manager/bazel-module/extract.ts +++ b/lib/modules/manager/bazel-module/extract.ts @@ -8,7 +8,6 @@ import { parse } from './parser'; import type { ResultFragment } from './parser/fragments'; import { RuleToMavenPackageDep, fillRegistryUrls } from './parser/maven'; import { RuleToDockerPackageDep } from './parser/oci'; -import { RulesImgPullToDockerPackageDep } from './parser/rules-img'; import { GitRepositoryToPackageDep, RuleToBazelModulePackageDep, @@ -25,7 +24,6 @@ export async function extractPackageFile( const gitRepositoryDeps = extractGitRepositoryDeps(records); const mavenDeps = extractMavenDeps(records); const dockerDeps = LooseArray(RuleToDockerPackageDep).parse(records); - const rulesImgDeps = extractRulesImgDeps(records); if (gitRepositoryDeps.length) { pfc.deps.push(...gitRepositoryDeps); @@ -39,10 +37,6 @@ export async function extractPackageFile( pfc.deps.push(...dockerDeps); } - if (rulesImgDeps.length) { - pfc.deps.push(...rulesImgDeps); - } - return pfc.deps.length ? pfc : null; } catch (err) { logger.debug({ err, packageFile }, 'Failed to parse bazel module file.'); @@ -82,7 +76,3 @@ function extractMavenDeps(records: ResultFragment[]): PackageDependency[] { .transform(fillRegistryUrls) .parse(records); } - -function extractRulesImgDeps(records: ResultFragment[]): PackageDependency[] { - return LooseArray(RulesImgPullToDockerPackageDep).parse(records); -} diff --git a/lib/modules/manager/bazel-module/parser/context.ts b/lib/modules/manager/bazel-module/parser/context.ts index 5504b46d5e4..a50b5e27bcd 100644 --- a/lib/modules/manager/bazel-module/parser/context.ts +++ b/lib/modules/manager/bazel-module/parser/context.ts @@ -3,7 +3,6 @@ import type { ArrayFragment, ExtensionTagFragment, PreparedExtensionTagFragment, - RepoRuleFunctionCallFragment, ResultFragment, RuleFragment, } from './fragments'; @@ -34,22 +33,10 @@ export class Ctx implements CtxCompatible { results: ResultFragment[]; stack: AllFragments[]; - // Track repo rule assignments and function mappings - private repoRuleFunctions: Map< - string, - { loadPath: string; originalFunctionName: string } - >; - private currentRepoRuleAssignment?: { - variableName: string; - loadPath?: string; - originalFunctionName?: string; - }; - constructor(source: string) { this.source = source; this.results = []; this.stack = []; - this.repoRuleFunctions = new Map(); } private get safeCurrent(): AllFragments | undefined { @@ -80,16 +67,6 @@ export class Ctx implements CtxCompatible { throw new Error('Requested current extension tag, but does not exist.'); } - private get currentRepoRuleFunctionCall(): RepoRuleFunctionCallFragment { - const current = this.current; - if (current.type === 'repoRuleFunctionCall') { - return current; - } - throw new Error( - 'Requested current repo rule function call, but does not exist.', - ); - } - private get currentArray(): ArrayFragment { const current = this.current; if (current.type === 'array') { @@ -133,20 +110,14 @@ export class Ctx implements CtxCompatible { return true; } if ( - (parent.type === 'rule' || - parent.type === 'extensionTag' || - parent.type === 'repoRuleFunctionCall') && + (parent.type === 'rule' || parent.type === 'extensionTag') && current.type === 'attribute' && current.value !== undefined ) { parent.children[current.name] = current.value; return true; } - } else if ( - current.type === 'rule' || - current.type === 'extensionTag' || - current.type === 'repoRuleFunctionCall' - ) { + } else if (current.type === 'rule' || current.type === 'extensionTag') { this.results.push(current); return true; } @@ -232,79 +203,4 @@ export class Ctx implements CtxCompatible { array.isComplete = true; return this.processStack(); } - - // Repo rule assignment methods - startRepoRuleAssignment(variableName: string): Ctx { - this.currentRepoRuleAssignment = { variableName }; - return this; - } - - addRepoRuleLoadPath(loadPath: string): Ctx { - if (this.currentRepoRuleAssignment) { - this.currentRepoRuleAssignment.loadPath = loadPath; - } - return this; - } - - addRepoRuleFunctionName(originalFunctionName: string): Ctx { - if (this.currentRepoRuleAssignment) { - this.currentRepoRuleAssignment.originalFunctionName = - originalFunctionName; - } - return this; - } - - endRepoRuleAssignment(): Ctx { - if ( - this.currentRepoRuleAssignment?.loadPath && - this.currentRepoRuleAssignment?.originalFunctionName - ) { - // Only track rules_img repo rules - support both load paths used in tests - const rulesImgLoadPaths = [ - '@rules_img//img:pull.bzl', - '@rules_img//img/private/repository_rules:pull.bzl', - ]; - - if (rulesImgLoadPaths.includes(this.currentRepoRuleAssignment.loadPath)) { - this.repoRuleFunctions.set( - this.currentRepoRuleAssignment.variableName, - { - loadPath: this.currentRepoRuleAssignment.loadPath, - originalFunctionName: - this.currentRepoRuleAssignment.originalFunctionName, - }, - ); - } - } - this.currentRepoRuleAssignment = undefined; - return this; - } - - // Dynamic function call methods - isRulesImgFunction(functionName: string): boolean { - return this.repoRuleFunctions.has(functionName); - } - - startDynamicFunctionCall(functionName: string, offset: number): Ctx { - const repoRuleInfo = this.repoRuleFunctions.get(functionName); - if (!repoRuleInfo) { - throw new Error(`Function ${functionName} not found in repo rules`); - } - - const functionCall = fragments.repoRuleFunctionCall( - functionName, - repoRuleInfo.loadPath, - repoRuleInfo.originalFunctionName, - offset, - ); - this.stack.push(functionCall); - return this; - } - - endDynamicFunctionCall(offset: number): Ctx { - const functionCall = this.currentRepoRuleFunctionCall; - functionCall.isComplete = true; - functionCall.rawString = this.source.slice(functionCall.offset, offset); - return this.processStack(); - } } diff --git a/lib/modules/manager/bazel-module/parser/fragments.spec.ts b/lib/modules/manager/bazel-module/parser/fragments.spec.ts index 459546b6b12..ae64bc176ec 100644 --- a/lib/modules/manager/bazel-module/parser/fragments.spec.ts +++ b/lib/modules/manager/bazel-module/parser/fragments.spec.ts @@ -1,24 +1,24 @@ import { - ArrayFragmentSchema, - AttributeFragmentSchema, - BooleanFragmentSchema, - ExtensionTagFragmentSchema, - PreparedExtensionTagFragmentSchema, - RuleFragmentSchema, - StringFragmentSchema, + ArrayFragment, + AttributeFragment, + BooleanFragment, + ExtensionTagFragment, + PreparedExtensionTagFragment, + RuleFragment, + StringFragment, } from './fragments'; import * as fragments from './fragments'; describe('modules/manager/bazel-module/parser/fragments', () => { it('.string()', () => { const result = fragments.string('hello'); - expect(() => StringFragmentSchema.parse(result)).not.toThrow(); + expect(() => StringFragment.parse(result)).not.toThrow(); expect(result.value).toBe('hello'); }); it('.boolean()', () => { const result = fragments.boolean(true); - expect(() => BooleanFragmentSchema.parse(result)).not.toThrow(); + expect(() => BooleanFragment.parse(result)).not.toThrow(); expect(result.value).toBe(true); }); @@ -28,7 +28,7 @@ describe('modules/manager/bazel-module/parser/fragments', () => { { name: fragments.string('bar') }, true, ); - expect(() => RuleFragmentSchema.parse(result)).not.toThrow(); + expect(() => RuleFragment.parse(result)).not.toThrow(); expect(result.rule).toBe('foo'); expect(result.children).toEqual({ name: fragments.string('bar') }); expect(result.isComplete).toBe(true); @@ -45,7 +45,7 @@ describe('modules/manager/bazel-module/parser/fragments', () => { true, ); - expect(() => ExtensionTagFragmentSchema.parse(result)).not.toThrow(); + expect(() => ExtensionTagFragment.parse(result)).not.toThrow(); expect(result.extension).toBe('ext'); expect(result.rawExtension).toBe('ext_01'); expect(result.tag).toBe('tag'); @@ -56,9 +56,7 @@ describe('modules/manager/bazel-module/parser/fragments', () => { it('.preparedExtensionTag()', () => { const result = fragments.preparedExtensionTag('ext', 'ext_01', 0); - expect(() => - PreparedExtensionTagFragmentSchema.parse(result), - ).not.toThrow(); + expect(() => PreparedExtensionTagFragment.parse(result)).not.toThrow(); expect(result.extension).toBe('ext'); expect(result.rawExtension).toBe('ext_01'); expect(result.isComplete).toBe(false); @@ -66,7 +64,7 @@ describe('modules/manager/bazel-module/parser/fragments', () => { it('.attribute()', () => { const result = fragments.attribute('name', fragments.string('foo'), true); - expect(() => AttributeFragmentSchema.parse(result)).not.toThrow(); + expect(() => AttributeFragment.parse(result)).not.toThrow(); expect(result.name).toBe('name'); expect(result.value).toEqual(fragments.string('foo')); expect(result.isComplete).toBe(true); @@ -74,7 +72,7 @@ describe('modules/manager/bazel-module/parser/fragments', () => { it('.array()', () => { const result = fragments.array([fragments.string('foo')], true); - expect(() => ArrayFragmentSchema.parse(result)).not.toThrow(); + expect(() => ArrayFragment.parse(result)).not.toThrow(); expect(result.items).toEqual([fragments.string('foo')]); expect(result.isComplete).toBe(true); }); diff --git a/lib/modules/manager/bazel-module/parser/fragments.ts b/lib/modules/manager/bazel-module/parser/fragments.ts index ebec943904e..4cffee6dcfe 100644 --- a/lib/modules/manager/bazel-module/parser/fragments.ts +++ b/lib/modules/manager/bazel-module/parser/fragments.ts @@ -2,111 +2,91 @@ import { z } from 'zod'; import { LooseArray, LooseRecord } from '../../../../util/schema-utils'; import * as starlark from './starlark'; -export const StringFragmentSchema = z.object({ +export const StringFragment = z.object({ type: z.literal('string'), value: z.string(), isComplete: z.literal(true), }); -export const BooleanFragmentSchema = z.object({ +export const BooleanFragment = z.object({ type: z.literal('boolean'), value: z.boolean(), isComplete: z.literal(true), }); -const PrimitiveFragmentsSchema = z.discriminatedUnion('type', [ - StringFragmentSchema, - BooleanFragmentSchema, +const PrimitiveFragments = z.discriminatedUnion('type', [ + StringFragment, + BooleanFragment, ]); -export const ArrayFragmentSchema = z.object({ +export const ArrayFragment = z.object({ type: z.literal('array'), - items: LooseArray(PrimitiveFragmentsSchema), + items: LooseArray(PrimitiveFragments), isComplete: z.boolean(), }); -export const StringArrayFragmentSchema = z.object({ +export const StringArrayFragment = z.object({ type: z.literal('array'), - items: LooseArray(StringFragmentSchema), + items: LooseArray(StringFragment), isComplete: z.boolean(), }); -const ValueFragmentsSchema = z.discriminatedUnion('type', [ - StringFragmentSchema, - BooleanFragmentSchema, - ArrayFragmentSchema, +const ValueFragments = z.discriminatedUnion('type', [ + StringFragment, + BooleanFragment, + ArrayFragment, ]); -export const RuleFragmentSchema = z.object({ +export const RuleFragment = z.object({ type: z.literal('rule'), rule: z.string(), - children: LooseRecord(ValueFragmentsSchema), + children: LooseRecord(ValueFragments), isComplete: z.boolean(), }); -export const PreparedExtensionTagFragmentSchema = z.object({ +export const PreparedExtensionTagFragment = z.object({ type: z.literal('preparedExtensionTag'), - // See ExtensionTagFragmentSchema for documentation of the fields. + // See ExtensionTagFragment for documentation of the fields. extension: z.string(), rawExtension: z.string(), offset: z.number(), // start offset in the source string isComplete: z.literal(false), // never complete, parser internal type. }); -export const ExtensionTagFragmentSchema = z.object({ +export const ExtensionTagFragment = z.object({ type: z.literal('extensionTag'), // The "logical" name of the extension (e.g. `oci` or `maven`). extension: z.string(), // The "raw" name of the extension as it appears in the MODULE file (e.g. `maven_01` or `maven`) rawExtension: z.string(), tag: z.string(), - children: LooseRecord(ValueFragmentsSchema), + children: LooseRecord(ValueFragments), isComplete: z.boolean(), offset: z.number(), // start offset in the source string rawString: z.string().optional(), // raw source string }); -export const RepoRuleFunctionCallSchema = z.object({ - type: z.literal('repoRuleFunctionCall'), - // The variable name used for the function call (e.g. "pull") - functionName: z.string(), - // The load path that was imported (e.g. "@rules_img//img:pull.bzl") - loadPath: z.string(), - // The original function name from use_repo_rule (e.g. "pull") - originalFunctionName: z.string(), - children: LooseRecord(ValueFragmentsSchema), - isComplete: z.boolean(), - offset: z.number(), // start offset in the source string - rawString: z.string().optional(), // raw source string -}); -export const AttributeFragmentSchema = z.object({ +export const AttributeFragment = z.object({ type: z.literal('attribute'), name: z.string(), - value: ValueFragmentsSchema.optional(), + value: ValueFragments.optional(), isComplete: z.boolean(), }); -export const AllFragmentsSchema = z.discriminatedUnion('type', [ - ArrayFragmentSchema, - AttributeFragmentSchema, - BooleanFragmentSchema, - RuleFragmentSchema, - PreparedExtensionTagFragmentSchema, - ExtensionTagFragmentSchema, - RepoRuleFunctionCallSchema, - StringFragmentSchema, +export const AllFragments = z.discriminatedUnion('type', [ + ArrayFragment, + AttributeFragment, + BooleanFragment, + RuleFragment, + PreparedExtensionTagFragment, + ExtensionTagFragment, + StringFragment, ]); -export type AllFragments = z.infer; -export type ArrayFragment = z.infer; -export type AttributeFragment = z.infer; -export type BooleanFragment = z.infer; +export type AllFragments = z.infer; +export type ArrayFragment = z.infer; +export type AttributeFragment = z.infer; +export type BooleanFragment = z.infer; export type ChildFragments = Record; -export type PrimitiveFragments = z.infer; -export type RuleFragment = z.infer; +export type PrimitiveFragments = z.infer; +export type RuleFragment = z.infer; export type PreparedExtensionTagFragment = z.infer< - typeof PreparedExtensionTagFragmentSchema ->; -export type ExtensionTagFragment = z.infer; -export type RepoRuleFunctionCallFragment = z.infer< - typeof RepoRuleFunctionCallSchema + typeof PreparedExtensionTagFragment >; -export type StringFragment = z.infer; -export type ValueFragments = z.infer; -export type ResultFragment = - | RuleFragment - | ExtensionTagFragment - | RepoRuleFunctionCallFragment; +export type ExtensionTagFragment = z.infer; +export type StringFragment = z.infer; +export type ValueFragments = z.infer; +export type ResultFragment = RuleFragment | ExtensionTagFragment; export function string(value: string): StringFragment { return { @@ -172,27 +152,6 @@ export function extensionTag( }; } -export function repoRuleFunctionCall( - functionName: string, - loadPath: string, - originalFunctionName: string, - offset: number, - children: ChildFragments = {}, - rawString?: string, - isComplete = false, -): RepoRuleFunctionCallFragment { - return { - type: 'repoRuleFunctionCall', - functionName, - loadPath, - originalFunctionName, - offset, - rawString, - isComplete, - children, - }; -} - export function attribute( name: string, value?: ValueFragments, @@ -218,11 +177,11 @@ export function array( } export function isValue(data: unknown): data is ValueFragments { - const result = ValueFragmentsSchema.safeParse(data); + const result = ValueFragments.safeParse(data); return result.success; } export function isPrimitive(data: unknown): data is PrimitiveFragments { - const result = PrimitiveFragmentsSchema.safeParse(data); + const result = PrimitiveFragments.safeParse(data); return result.success; } diff --git a/lib/modules/manager/bazel-module/parser/index.ts b/lib/modules/manager/bazel-module/parser/index.ts index 6af7ac17109..ed4649902ce 100644 --- a/lib/modules/manager/bazel-module/parser/index.ts +++ b/lib/modules/manager/bazel-module/parser/index.ts @@ -2,15 +2,9 @@ import { lang, query as q } from 'good-enough-parser'; import { Ctx } from './context'; import { extensionTags } from './extension-tags'; import type { ResultFragment } from './fragments'; -import { dynamicFunctionCall, repoRuleAssignment } from './repo-rules'; import { rules } from './rules'; -const rule = q.alt( - rules, - extensionTags, - repoRuleAssignment, - dynamicFunctionCall, -); +const rule = q.alt(rules, extensionTags); const query = q.tree({ type: 'root-tree', diff --git a/lib/modules/manager/bazel-module/parser/maven.ts b/lib/modules/manager/bazel-module/parser/maven.ts index beee25129f0..c05d36c6d4d 100644 --- a/lib/modules/manager/bazel-module/parser/maven.ts +++ b/lib/modules/manager/bazel-module/parser/maven.ts @@ -3,9 +3,9 @@ import { MavenDatasource } from '../../../datasource/maven'; import { id as versioning } from '../../../versioning/gradle'; import type { PackageDependency } from '../../types'; import { - ExtensionTagFragmentSchema, - StringArrayFragmentSchema, - StringFragmentSchema, + ExtensionTagFragment, + StringArrayFragment, + StringFragment, } from './fragments'; const artifactTag = 'artifact'; @@ -27,13 +27,13 @@ const ArtifactSpec = z.object({ }); type ArtifactSpec = z.infer; -const MavenArtifactTarget = ExtensionTagFragmentSchema.extend({ +const MavenArtifactTarget = ExtensionTagFragment.extend({ extension: z.literal(mavenExtensionPrefix), tag: z.literal(artifactTag), children: z.object({ - artifact: StringFragmentSchema, - group: StringFragmentSchema, - version: StringFragmentSchema, + artifact: StringFragment, + group: StringFragment, + version: StringFragment, }), }).transform( ({ children: { artifact, group, version } }): PackageDependency[] => [ @@ -47,11 +47,11 @@ const MavenArtifactTarget = ExtensionTagFragmentSchema.extend({ ], ); -const MavenInstallTarget = ExtensionTagFragmentSchema.extend({ +const MavenInstallTarget = ExtensionTagFragment.extend({ extension: z.literal(mavenExtensionPrefix), tag: z.literal(installTag), children: z.object({ - artifacts: StringArrayFragmentSchema.transform((artifacts) => { + artifacts: StringArrayFragment.transform((artifacts) => { const result: ArtifactSpec[] = []; for (const { value } of artifacts.items) { const [group, artifact, version] = value.split(':'); @@ -62,7 +62,7 @@ const MavenInstallTarget = ExtensionTagFragmentSchema.extend({ return result; }), - repositories: StringArrayFragmentSchema, + repositories: StringArrayFragment, }), }).transform(({ children: { artifacts, repositories } }): PackageDependency[] => artifacts.map(({ group, artifact, version: currentValue }) => ({ diff --git a/lib/modules/manager/bazel-module/parser/oci.ts b/lib/modules/manager/bazel-module/parser/oci.ts index 24625f7b658..761262b486e 100644 --- a/lib/modules/manager/bazel-module/parser/oci.ts +++ b/lib/modules/manager/bazel-module/parser/oci.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { DockerDatasource } from '../../../datasource/docker'; import type { PackageDependency } from '../../types'; -import { ExtensionTagFragmentSchema, StringFragmentSchema } from './fragments'; +import { ExtensionTagFragment, StringFragment } from './fragments'; export const ociExtensionPrefix = 'oci'; @@ -9,14 +9,14 @@ const pullTag = 'pull'; export const ociExtensionTags = ['pull']; -export const RuleToDockerPackageDep = ExtensionTagFragmentSchema.extend({ +export const RuleToDockerPackageDep = ExtensionTagFragment.extend({ extension: z.literal(ociExtensionPrefix), tag: z.literal(pullTag), children: z.object({ - name: StringFragmentSchema, - image: StringFragmentSchema, - tag: StringFragmentSchema.optional(), - digest: StringFragmentSchema.optional(), + name: StringFragment, + image: StringFragment, + tag: StringFragment.optional(), + digest: StringFragment.optional(), }), }).transform( ({ diff --git a/lib/modules/manager/bazel-module/parser/repo-rules.ts b/lib/modules/manager/bazel-module/parser/repo-rules.ts deleted file mode 100644 index 230a8823ebf..00000000000 --- a/lib/modules/manager/bazel-module/parser/repo-rules.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { query as q } from 'good-enough-parser'; -import { kvParams } from './common'; -import type { Ctx } from './context'; - -// Support for use_repo_rule assignments and dynamic function calls -// Pattern: variable = use_repo_rule("load_path", "function_name") -// Followed by: variable(parameters) - -// Parse use_repo_rule assignments like: pull = use_repo_rule("@rules_img//img:pull.bzl", "pull") -export const repoRuleAssignment = q - .sym((ctx, token) => { - // Track the variable name being assigned (e.g., "pull") - return ctx.startRepoRuleAssignment(token.value); - }) - .op('=') - .sym('use_repo_rule') - .join( - q.tree({ - type: 'wrapped-tree', - maxDepth: 1, - search: q.many( - q.str((ctx, token) => { - // First string is load path, second is function name - if (token.value.startsWith('@')) { - return ctx.addRepoRuleLoadPath(token.value); - } - return ctx.addRepoRuleFunctionName(token.value); - }), - ), - postHandler: (ctx) => ctx.endRepoRuleAssignment(), - }), - ); - -// Parse dynamic function calls like: pull(name = "ubuntu", digest = "sha256:...") -// This needs to be dynamically matched based on tracked variables -export const dynamicFunctionCall = q - .sym((ctx, token) => { - // Check if this token matches a tracked rules_img function variable - if (ctx.isRulesImgFunction(token.value)) { - return ctx.startDynamicFunctionCall(token.value, token.offset); - } - // If not a tracked function, continue without processing - return ctx; - }) - .join( - q.tree({ - type: 'wrapped-tree', - maxDepth: 1, - search: kvParams, - postHandler: (ctx, tree) => { - if (tree.type === 'wrapped-tree') { - const { endsWith } = tree; - const endOffset = endsWith.offset + endsWith.value.length; - return ctx.endDynamicFunctionCall(endOffset); - } - throw new Error(`Unexpected tree in postHandler: ${tree.type}`); - }, - }), - ); diff --git a/lib/modules/manager/bazel-module/parser/rules-img.ts b/lib/modules/manager/bazel-module/parser/rules-img.ts deleted file mode 100644 index a566a6bf5e7..00000000000 --- a/lib/modules/manager/bazel-module/parser/rules-img.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { z } from 'zod'; -import { DockerDatasource } from '../../../datasource/docker'; -import type { PackageDependency } from '../../types'; -import { RepoRuleFunctionCallSchema, StringFragmentSchema } from './fragments'; - -export const rulesImgLoadPaths = ['@rules_img//img:pull.bzl'] as const; - -export const RulesImgPullToDockerPackageDep = RepoRuleFunctionCallSchema.extend( - { - loadPath: z.enum(rulesImgLoadPaths), - originalFunctionName: z.literal('pull'), - children: z.object({ - name: StringFragmentSchema, - digest: StringFragmentSchema, - registry: StringFragmentSchema, - repository: StringFragmentSchema, - tag: StringFragmentSchema.optional(), - }), - }, -).transform( - ({ - rawString, - functionName, - children: { name, registry, repository, digest, tag }, - }): PackageDependency => ({ - datasource: DockerDatasource.id, - depType: 'rules_img_pull', - depName: name.value, - packageName: `${registry.value}/${repository.value}`, - currentValue: tag?.value, - currentDigest: digest.value, - // Provide a replace string so the auto replacer can replace both the tag - // and digest if applicable. - replaceString: rawString, - }), -); diff --git a/lib/modules/manager/bazel-module/parser/rules.ts b/lib/modules/manager/bazel-module/parser/rules.ts index 126a3d1e3b2..a71da4f3951 100644 --- a/lib/modules/manager/bazel-module/parser/rules.ts +++ b/lib/modules/manager/bazel-module/parser/rules.ts @@ -21,7 +21,6 @@ const supportedRules = [ 'single_version_override', 'git_repository', 'new_git_repository', - 'use_repo_rule', ]; const supportedRulesRegex = regEx(`^${supportedRules.join('|')}$`); diff --git a/lib/modules/manager/bazel-module/readme.md b/lib/modules/manager/bazel-module/readme.md index 429d0cbb0f6..59a56bd8678 100644 --- a/lib/modules/manager/bazel-module/readme.md +++ b/lib/modules/manager/bazel-module/readme.md @@ -31,9 +31,9 @@ maven.artifact( ### Docker -Similarly, it updates Docker / OCI images pulled with [oci_pull](https://github.com/bazel-contrib/rules_oci/blob/main/docs/pull.md) from rules_oci, or with [pull](https://github.com/tweag/rules_img/blob/main/docs/pull.md) from rules_img. +Similarly, it updates Docker / OCI images pulled with [oci_pull](https://github.com/bazel-contrib/rules_oci/blob/main/docs/pull.md). -Note that for rules_oci, the extension must be called `oci`: +Note that the extension must be called `oci`: ``` oci = use_extension("@rules_oci//oci:extensions.bzl", "oci") @@ -46,17 +46,3 @@ oci.pull( tag = "1.27.1", ) ``` - -For rules_img, use the `pull` repo rule: - -``` -pull = use_repo_rule("@rules_img//img:pull.bzl", "pull") - -pull( - name = "nginx_image", - digest = "sha256:287ff321f9e3cde74b600cc26197424404157a72043226cbbf07ee8304a2c720", - registry = "index.docker.io", - repository = "library/nginx", - tag = "1.27.1", -) -``` diff --git a/lib/modules/manager/bazel-module/rules.ts b/lib/modules/manager/bazel-module/rules.ts index e5c159a1872..c27b6b18f5b 100644 --- a/lib/modules/manager/bazel-module/rules.ts +++ b/lib/modules/manager/bazel-module/rules.ts @@ -8,7 +8,7 @@ import { regEx } from '../../../util/regex'; import { BazelDatasource } from '../../datasource/bazel'; import { GithubTagsDatasource } from '../../datasource/github-tags'; import type { PackageDependency } from '../types'; -import { RuleFragmentSchema, StringFragmentSchema } from './parser/fragments'; +import { RuleFragment, StringFragment } from './parser/fragments'; // Rule Schemas @@ -63,11 +63,11 @@ export function bazelModulePackageDepToPackageDependency( return copy; } -const BazelDepToPackageDep = RuleFragmentSchema.extend({ +const BazelDepToPackageDep = RuleFragment.extend({ rule: z.literal('bazel_dep'), children: z.object({ - name: StringFragmentSchema, - version: StringFragmentSchema.optional(), + name: StringFragment, + version: StringFragment.optional(), }), }).transform( ({ rule, children: { name, version } }): BasePackageDep => ({ @@ -79,12 +79,12 @@ const BazelDepToPackageDep = RuleFragmentSchema.extend({ }), ); -const GitOverrideToPackageDep = RuleFragmentSchema.extend({ +const GitOverrideToPackageDep = RuleFragment.extend({ rule: z.literal('git_override'), children: z.object({ - module_name: StringFragmentSchema, - remote: StringFragmentSchema, - commit: StringFragmentSchema, + module_name: StringFragment, + remote: StringFragment, + commit: StringFragment, }), }).transform( ({ @@ -108,12 +108,12 @@ const GitOverrideToPackageDep = RuleFragmentSchema.extend({ }, ); -const SingleVersionOverrideToPackageDep = RuleFragmentSchema.extend({ +const SingleVersionOverrideToPackageDep = RuleFragment.extend({ rule: z.literal('single_version_override'), children: z.object({ - module_name: StringFragmentSchema, - version: StringFragmentSchema.optional(), - registry: StringFragmentSchema.optional(), + module_name: StringFragment, + version: StringFragment.optional(), + registry: StringFragment.optional(), }), }).transform( ({ @@ -141,10 +141,10 @@ const SingleVersionOverrideToPackageDep = RuleFragmentSchema.extend({ }, ); -const UnsupportedOverrideToPackageDep = RuleFragmentSchema.extend({ +const UnsupportedOverrideToPackageDep = RuleFragment.extend({ rule: z.enum(['archive_override', 'local_path_override']), children: z.object({ - module_name: StringFragmentSchema, + module_name: StringFragment, }), }).transform( ({ rule, children: { module_name: moduleName } }): OverridePackageDep => { @@ -238,13 +238,13 @@ export function toPackageDependencies( return collectByModule(packageDeps).map(processModulePkgDeps).flat(); } -export const GitRepositoryToPackageDep = RuleFragmentSchema.extend({ +export const GitRepositoryToPackageDep = RuleFragment.extend({ rule: z.union([z.literal('git_repository'), z.literal('new_git_repository')]), children: z.object({ - name: StringFragmentSchema, - remote: StringFragmentSchema, - commit: StringFragmentSchema.optional(), - tag: StringFragmentSchema.optional(), + name: StringFragment, + remote: StringFragment, + commit: StringFragment.optional(), + tag: StringFragment.optional(), }), }).transform( ({ rule, children: { name, remote, commit, tag } }): BasePackageDep => { diff --git a/lib/modules/manager/cargo/extract.ts b/lib/modules/manager/cargo/extract.ts index c569f222b47..e31d95a039f 100644 --- a/lib/modules/manager/cargo/extract.ts +++ b/lib/modules/manager/cargo/extract.ts @@ -10,11 +10,7 @@ import type { PackageFileContent, } from '../types'; import { extractLockFileVersions } from './locked-version'; -import { - type CargoConfig, - CargoConfigSchema, - CargoManifestSchema, -} from './schema'; +import { CargoConfig, CargoManifest } from './schema'; import type { CargoManagerData, CargoRegistries, @@ -86,7 +82,7 @@ async function readCargoConfig(): Promise { const path = `.cargo/${configName}`; const payload = await readLocalFile(path, 'utf8'); if (payload) { - const parsedCargoConfig = CargoConfigSchema.safeParse(payload); + const parsedCargoConfig = CargoConfig.safeParse(payload); if (parsedCargoConfig.success) { return parsedCargoConfig.data; } else { @@ -177,7 +173,7 @@ export async function extractPackageFile( const cargoConfig = (await readCargoConfig()) ?? {}; const cargoRegistries = extractCargoRegistries(cargoConfig); - const parsedCargoManifest = CargoManifestSchema.safeParse(content); + const parsedCargoManifest = CargoManifest.safeParse(content); if (!parsedCargoManifest.success) { logger.debug( { err: parsedCargoManifest.error, packageFile }, diff --git a/lib/modules/manager/cargo/locked-version.ts b/lib/modules/manager/cargo/locked-version.ts index bffb32ad007..7f115e9057e 100644 --- a/lib/modules/manager/cargo/locked-version.ts +++ b/lib/modules/manager/cargo/locked-version.ts @@ -1,8 +1,7 @@ import { logger } from '../../../logger'; import { coerceArray } from '../../../util/array'; import { readLocalFile } from '../../../util/fs'; -import type { CargoLockSchema } from './schema'; -import { CargoLockSchemaToml } from './schema'; +import { CargoLock } from './schema'; export async function extractLockFileVersions( lockFilePath: string, @@ -30,8 +29,8 @@ export function extractLockFileContentVersions( return versionsByPackage; } -export function parseLockFile(lockFile: string): CargoLockSchema | null { - const res = CargoLockSchemaToml.safeParse(lockFile); +export function parseLockFile(lockFile: string): CargoLock | null { + const res = CargoLock.safeParse(lockFile); if (res.success) { return res.data; } diff --git a/lib/modules/manager/cargo/schema.ts b/lib/modules/manager/cargo/schema.ts index 1a5e799e96d..ff39ada0c2a 100644 --- a/lib/modules/manager/cargo/schema.ts +++ b/lib/modules/manager/cargo/schema.ts @@ -108,7 +108,7 @@ const CargoWorkspace = z.object({ const CargoTarget = z.record(z.string(), CargoSection); -export const CargoManifestSchema = Toml.pipe( +export const CargoManifest = Toml.pipe( CargoSection.extend({ package: z .object({ @@ -131,25 +131,25 @@ const CargoConfigSource = z.object({ registry: z.string().optional(), }); -export const CargoConfigSchema = Toml.pipe( +export const CargoConfig = Toml.pipe( z.object({ registries: z.record(z.string(), CargoConfigRegistry).optional(), source: z.record(z.string(), CargoConfigSource).optional(), }), ); -export type CargoConfig = z.infer; +export type CargoConfig = z.infer; -const CargoLockPackageSchema = z.object({ +const CargoLockPackage = z.object({ name: z.string(), version: z.string(), source: z.string().optional(), }); -export const CargoLockSchema = z.object({ - package: z.array(CargoLockPackageSchema).optional(), -}); - -export type CargoLockSchema = z.infer; +export const CargoLock = Toml.pipe( + z.object({ + package: z.array(CargoLockPackage).optional(), + }), +); -export const CargoLockSchemaToml = Toml.pipe(CargoLockSchema); +export type CargoLock = z.infer; diff --git a/lib/modules/manager/crossplane/extract.ts b/lib/modules/manager/crossplane/extract.ts index 9620dd5cedd..449daff1b63 100644 --- a/lib/modules/manager/crossplane/extract.ts +++ b/lib/modules/manager/crossplane/extract.ts @@ -6,7 +6,7 @@ import type { PackageDependency, PackageFileContent, } from '../types'; -import { XPKGSchema } from './schema'; +import { XPKG } from './schema'; export function extractPackageFile( content: string, @@ -21,7 +21,7 @@ export function extractPackageFile( // not try and catching this as failureBehaviour is set to filter and therefore it will not throw const list = parseYaml(content, { - customSchema: XPKGSchema, + customSchema: XPKG, failureBehaviour: 'filter', }); diff --git a/lib/modules/manager/crossplane/schema.ts b/lib/modules/manager/crossplane/schema.ts index 9d076172dc8..eb4167fc184 100644 --- a/lib/modules/manager/crossplane/schema.ts +++ b/lib/modules/manager/crossplane/schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { regEx } from '../../../util/regex'; -export const XPKGSchema = z.object({ +export const XPKG = z.object({ apiVersion: z.string().regex(regEx(/^pkg\.crossplane\.io\//)), kind: z.enum(['Provider', 'Configuration', 'Function']), spec: z.object({ @@ -9,4 +9,4 @@ export const XPKGSchema = z.object({ }), }); -export type XPKG = z.infer; +export type XPKG = z.infer; diff --git a/lib/modules/manager/custom/jsonata/schema.ts b/lib/modules/manager/custom/jsonata/schema.ts index cf363c576b6..8a408ddfce8 100644 --- a/lib/modules/manager/custom/jsonata/schema.ts +++ b/lib/modules/manager/custom/jsonata/schema.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -const DepObjectSchema = z.object({ +const DepObject = z.object({ currentValue: z.string().optional(), datasource: z.string().optional(), depName: z.string().optional(), @@ -13,8 +13,8 @@ const DepObjectSchema = z.object({ indentation: z.string().optional(), }); -export const QueryResultZodSchema = z - .union([z.array(DepObjectSchema), DepObjectSchema]) +export const QueryResultZod = z + .union([z.array(DepObject), DepObject]) .transform((input) => { return Array.isArray(input) ? input : [input]; }); diff --git a/lib/modules/manager/custom/jsonata/utils.ts b/lib/modules/manager/custom/jsonata/utils.ts index f762dfbdbeb..c015489ae0c 100644 --- a/lib/modules/manager/custom/jsonata/utils.ts +++ b/lib/modules/manager/custom/jsonata/utils.ts @@ -7,7 +7,7 @@ import { parseUrl } from '../../../../util/url'; import type { PackageDependency } from '../../types'; import type { ValidMatchFields } from '../utils'; import { checkIsValidDependency, validMatchFields } from '../utils'; -import { QueryResultZodSchema } from './schema'; +import { QueryResultZod } from './schema'; import type { JSONataManagerTemplates, JsonataExtractConfig } from './types'; export async function handleMatching( @@ -36,7 +36,7 @@ export async function handleMatching( return []; } - const parsed = QueryResultZodSchema.safeParse(queryResult); + const parsed = QueryResultZod.safeParse(queryResult); if (parsed.success) { results = results.concat(parsed.data); } else { diff --git a/lib/modules/manager/devbox/extract.ts b/lib/modules/manager/devbox/extract.ts index 1703779ea89..63f32c53eee 100644 --- a/lib/modules/manager/devbox/extract.ts +++ b/lib/modules/manager/devbox/extract.ts @@ -1,6 +1,6 @@ import { logger, withMeta } from '../../../logger'; import type { PackageFileContent } from '../types'; -import { DevboxSchema } from './schema'; +import { Devbox } from './schema'; export function extractPackageFile( content: string, @@ -8,7 +8,7 @@ export function extractPackageFile( ): PackageFileContent | null { logger.trace('devbox.extractPackageFile()'); - const deps = withMeta({ packageFile }, () => DevboxSchema.parse(content)); + const deps = withMeta({ packageFile }, () => Devbox.parse(content)); if (!deps.length) { return null; } diff --git a/lib/modules/manager/devbox/schema.ts b/lib/modules/manager/devbox/schema.ts index 47589c6345c..4c77eb609ea 100644 --- a/lib/modules/manager/devbox/schema.ts +++ b/lib/modules/manager/devbox/schema.ts @@ -42,7 +42,7 @@ const DevboxEntry = z return dep; }); -export const DevboxSchema = Jsonc.pipe( +export const Devbox = Jsonc.pipe( z.object({ packages: z .union([ diff --git a/lib/modules/manager/github-actions/community.ts b/lib/modules/manager/github-actions/community.ts index 2559bbc3fde..76b26ccc285 100644 --- a/lib/modules/manager/github-actions/community.ts +++ b/lib/modules/manager/github-actions/community.ts @@ -16,7 +16,7 @@ function matchAction(action: string): z.Schema { .regex(regEx(`(?:https?://[^/]+/)?${escapeRegExp(action)}(?:@.+)?$`)); } -const SetupUVSchema = z +const SetupUV = z .object({ // https://github.com/astral-sh/setup-uv uses: matchAction('astral-sh/setup-uv'), @@ -43,7 +43,7 @@ const SetupUVSchema = z }; }); -const SetupPnpmSchema = z +const SetupPnpm = z .object({ uses: matchAction('pnpm/action-setup'), with: z.object({ @@ -70,7 +70,7 @@ const SetupPnpmSchema = z }; }); -const SetupPDMSchema = z +const SetupPDM = z .object({ uses: matchAction('pdm-project/setup-pdm'), with: z.object({ version: z.string().optional() }), @@ -95,7 +95,7 @@ const SetupPDMSchema = z }; }); -const InstallBinarySchema = z +const InstallBinary = z .object({ uses: z.union([ matchAction('jaxxstorm/action-install-gh-release'), @@ -113,7 +113,7 @@ const InstallBinarySchema = z }; }); -const SetupPixiSchema = z +const SetupPixi = z .object({ uses: matchAction('prefix-dev/setup-pixi'), with: z.object({ 'pixi-version': z.string() }), @@ -136,9 +136,9 @@ const SetupPixiSchema = z * each type should return `PackageDependency | undefined` */ export const CommunityActions = z.union([ - InstallBinarySchema, - SetupPDMSchema, - SetupPixiSchema, - SetupPnpmSchema, - SetupUVSchema, + InstallBinary, + SetupPDM, + SetupPixi, + SetupPnpm, + SetupUV, ]); diff --git a/lib/modules/manager/github-actions/extract.ts b/lib/modules/manager/github-actions/extract.ts index 0029283cf19..fa72131d418 100644 --- a/lib/modules/manager/github-actions/extract.ts +++ b/lib/modules/manager/github-actions/extract.ts @@ -18,7 +18,7 @@ import type { } from '../types'; import { CommunityActions } from './community'; import type { Steps } from './schema'; -import { WorkflowSchema } from './schema'; +import { Workflow } from './schema'; const dockerActionRe = regEx(/^\s+uses\s*: ['"]?docker:\/\/([^'"]+)\s*$/); const actionRe = regEx( @@ -224,7 +224,7 @@ function extractWithYAMLParser( logger.trace('github-actions.extractWithYAMLParser()'); const deps: PackageDependency[] = []; - const obj = withMeta({ packageFile }, () => WorkflowSchema.parse(content)); + const obj = withMeta({ packageFile }, () => Workflow.parse(content)); if (!obj) { return deps; diff --git a/lib/modules/manager/github-actions/schema.ts b/lib/modules/manager/github-actions/schema.ts index fbb33c0d27a..2f0d7f67988 100644 --- a/lib/modules/manager/github-actions/schema.ts +++ b/lib/modules/manager/github-actions/schema.ts @@ -6,15 +6,15 @@ import { withDebugMessage, } from '../../../util/schema-utils'; -const StepsSchema = z.object({ +const Steps = z.object({ uses: z.string(), with: LooseRecord( z.union([z.string(), z.number().transform((s) => s.toString())]), ), }); -export type Steps = z.infer; +export type Steps = z.infer; -const WorkFlowJobsSchema = z.object({ +const WorkFlowJobs = z.object({ jobs: LooseRecord( z.object({ container: z @@ -35,17 +35,17 @@ const WorkFlowJobsSchema = z.object({ 'runs-on': z .union([z.string().transform((v) => [v]), z.array(z.string())]) .catch([]), - steps: LooseArray(StepsSchema).catch([]), + steps: LooseArray(Steps).catch([]), }), ), }); -const ActionsSchema = z.object({ +const Actions = z.object({ runs: z.object({ using: z.string(), - steps: LooseArray(StepsSchema).optional().catch([]), + steps: LooseArray(Steps).optional().catch([]), }), }); -export const WorkflowSchema = Yaml.pipe( - z.union([WorkFlowJobsSchema, ActionsSchema, z.null()]), +export const Workflow = Yaml.pipe( + z.union([WorkFlowJobs, Actions, z.null()]), ).catch(withDebugMessage(null, 'Does not match schema')); diff --git a/lib/modules/manager/helmfile/extract.ts b/lib/modules/manager/helmfile/extract.ts index 1b592b97c34..0d725c5a14e 100644 --- a/lib/modules/manager/helmfile/extract.ts +++ b/lib/modules/manager/helmfile/extract.ts @@ -12,7 +12,7 @@ import type { PackageFileContent, } from '../types'; import type { Doc, HelmRepository } from './schema'; -import { Doc as documentSchema } from './schema'; +import { Doc as Document } from './schema'; import { kustomizationsKeysUsed, localChartHasKustomizationsYaml, @@ -42,7 +42,7 @@ export async function extractPackageFile( // Record kustomization usage for all deps, since updating artifacts is run on the helmfile.yaml as a whole. let needKustomize = false; const docs: Doc[] = parseYaml(content, { - customSchema: documentSchema, + customSchema: Document, failureBehaviour: 'filter', removeTemplates: true, }); diff --git a/lib/modules/manager/mise/backends.ts b/lib/modules/manager/mise/backends.ts index 209ab4fa6e7..54e8b6318f5 100644 --- a/lib/modules/manager/mise/backends.ts +++ b/lib/modules/manager/mise/backends.ts @@ -12,7 +12,7 @@ import { PypiDatasource } from '../../datasource/pypi'; import { normalizePythonDepName } from '../../datasource/pypi/common'; import { RubygemsDatasource } from '../../datasource/rubygems'; import type { PackageDependency } from '../types'; -import type { MiseToolOptionsSchema } from './schema'; +import type { MiseToolOptions } from './schema'; export type BackendToolingConfig = Omit & Required< @@ -205,7 +205,7 @@ export function createSpmToolConfig(name: string): BackendToolingConfig { export function createUbiToolConfig( name: string, version: string, - toolOptions: MiseToolOptionsSchema, + toolOptions: MiseToolOptions, ): BackendToolingConfig { let extractVersion: string | undefined = undefined; diff --git a/lib/modules/manager/mise/extract.ts b/lib/modules/manager/mise/extract.ts index bb7e1911aae..66f71c357b4 100644 --- a/lib/modules/manager/mise/extract.ts +++ b/lib/modules/manager/mise/extract.ts @@ -15,7 +15,7 @@ import { createSpmToolConfig, createUbiToolConfig, } from './backends'; -import type { MiseToolOptionsSchema, MiseToolSchema } from './schema'; +import type { MiseTool, MiseToolOptions } from './schema'; import type { ToolingDefinition } from './upgradeable-tooling'; import { asdfTooling, miseTooling } from './upgradeable-tooling'; import { parseTomlFile } from './utils'; @@ -63,7 +63,7 @@ export function extractPackageFile( return deps.length ? { deps } : null; } -function parseVersion(toolData: MiseToolSchema): string | null { +function parseVersion(toolData: MiseTool): string | null { if (is.nonEmptyString(toolData)) { // Handle the string case // e.g. 'erlang = "23.3"' @@ -84,8 +84,8 @@ function parseVersion(toolData: MiseToolSchema): string | null { function parseOptions( optionsInName: string, - toolOptions: MiseToolOptionsSchema, -): MiseToolOptionsSchema { + toolOptions: MiseToolOptions, +): MiseToolOptions { const options = is.nonEmptyString(optionsInName) ? Object.fromEntries( optionsInName.split(',').map((option) => option.split('=', 2)), @@ -102,7 +102,7 @@ function getToolConfig( backend: string, toolName: string, version: string, - toolOptions: MiseToolOptionsSchema, + toolOptions: MiseToolOptions, ): ToolingConfig | BackendToolingConfig | null { switch (backend) { case '': diff --git a/lib/modules/manager/mise/schema.ts b/lib/modules/manager/mise/schema.ts index 2e264fd9279..c23648722a3 100644 --- a/lib/modules/manager/mise/schema.ts +++ b/lib/modules/manager/mise/schema.ts @@ -1,24 +1,24 @@ import { z } from 'zod'; import { Toml } from '../../../util/schema-utils'; -const MiseToolOptionsSchema = z.object({ +const MiseToolOptions = z.object({ // ubi backend only tag_regex: z.string().optional(), }); -export type MiseToolOptionsSchema = z.infer; +export type MiseToolOptions = z.infer; -const MiseToolSchema = z.union([ +const MiseTool = z.union([ z.string(), - MiseToolOptionsSchema.extend({ + MiseToolOptions.extend({ version: z.string().optional(), }), z.array(z.string()), ]); -export type MiseToolSchema = z.infer; +export type MiseTool = z.infer; -export const MiseFileSchema = z.object({ - tools: z.record(MiseToolSchema), -}); -export type MiseFileSchema = z.infer; - -export const MiseFileSchemaToml = Toml.pipe(MiseFileSchema); +export const MiseFile = Toml.pipe( + z.object({ + tools: z.record(MiseTool), + }), +); +export type MiseFile = z.infer; diff --git a/lib/modules/manager/mise/utils.ts b/lib/modules/manager/mise/utils.ts index 75a5cbf9b9a..c3ffc52778c 100644 --- a/lib/modules/manager/mise/utils.ts +++ b/lib/modules/manager/mise/utils.ts @@ -1,12 +1,11 @@ import { logger } from '../../../logger'; -import type { MiseFileSchema } from './schema'; -import { MiseFileSchemaToml } from './schema'; +import { MiseFile } from './schema'; export function parseTomlFile( content: string, packageFile: string, -): MiseFileSchema | null { - const res = MiseFileSchemaToml.safeParse(content); +): MiseFile | null { + const res = MiseFile.safeParse(content); if (res.success) { return res.data; } else { diff --git a/lib/modules/manager/npm/extract/index.ts b/lib/modules/manager/npm/extract/index.ts index b8c5d6d1804..af385863147 100644 --- a/lib/modules/manager/npm/extract/index.ts +++ b/lib/modules/manager/npm/extract/index.ts @@ -14,7 +14,7 @@ import type { PackageFile, PackageFileContent, } from '../../types'; -import type { YarnrcConfig } from '../schema'; +import type { YarnConfig } from '../schema'; import type { NpmLockFiles, NpmManagerData } from '../types'; import { getExtractedConstraints } from './common/dependency'; import { extractPackageJson } from './common/package-file'; @@ -137,7 +137,7 @@ export async function extractPackageFile( ? await isZeroInstall(yarnrcYmlFileName) : false; - let yarnrcConfig: YarnrcConfig | null = null; + let yarnrcConfig: YarnConfig | null = null; const repoYarnrcYml = yarnrcYmlFileName ? await readLocalFile(yarnrcYmlFileName, 'utf8') : null; diff --git a/lib/modules/manager/npm/extract/pnpm.ts b/lib/modules/manager/npm/extract/pnpm.ts index 4a516878ba4..e5c18fd1333 100644 --- a/lib/modules/manager/npm/extract/pnpm.ts +++ b/lib/modules/manager/npm/extract/pnpm.ts @@ -12,12 +12,12 @@ import { } from '../../../../util/fs'; import { parseSingleYaml } from '../../../../util/yaml'; import type { PackageFile, PackageFileContent } from '../../types'; -import type { PnpmDependencySchema, PnpmLockFile } from '../post-update/types'; -import type { PnpmCatalogsSchema } from '../schema'; -import { PnpmWorkspaceFileSchema } from '../schema'; +import type { PnpmDependency, PnpmLockFile } from '../post-update/types'; +import type { PnpmCatalogs } from '../schema'; +import { PnpmWorkspaceFile } from '../schema'; import type { NpmManagerData } from '../types'; import { extractCatalogDeps } from './common/catalogs'; -import type { Catalog, LockFile, PnpmWorkspaceFile } from './types'; +import type { Catalog, LockFile } from './types'; function isPnpmLockfile(obj: any): obj is PnpmLockFile { return is.plainObject(obj) && 'lockfileVersion' in obj; @@ -220,7 +220,7 @@ function getLockedVersions( } function getLockedDependencyVersions( - obj: PnpmLockFile | Record, + obj: PnpmLockFile | Record, ): Record> { const dependencyTypes = [ 'dependencies', @@ -257,7 +257,7 @@ export function tryParsePnpmWorkspaceYaml(content: string): | { success: false; data?: never } { try { const data = parseSingleYaml(content, { - customSchema: PnpmWorkspaceFileSchema, + customSchema: PnpmWorkspaceFile, }); return { success: true, data }; } catch { @@ -265,7 +265,7 @@ export function tryParsePnpmWorkspaceYaml(content: string): } } -type PnpmCatalogs = z.TypeOf; +type PnpmCatalogs = z.TypeOf; export async function extractPnpmWorkspaceFile( catalogs: PnpmCatalogs, diff --git a/lib/modules/manager/npm/extract/yarnrc.ts b/lib/modules/manager/npm/extract/yarnrc.ts index 8e19d04483e..7b2e7d45111 100644 --- a/lib/modules/manager/npm/extract/yarnrc.ts +++ b/lib/modules/manager/npm/extract/yarnrc.ts @@ -2,8 +2,7 @@ import is from '@sindresorhus/is'; import { logger } from '../../../../logger'; import { regEx } from '../../../../util/regex'; import { Result } from '../../../../util/result'; -import { Yaml } from '../../../../util/schema-utils'; -import { type YarnrcConfig, YarnrcSchema } from '../schema'; +import { YarnConfig } from '../schema'; const registryRegEx = regEx( /^"?(@(?[^:]+):)?registry"? "?(?[^"]+)"?$/gm, @@ -11,12 +10,12 @@ const registryRegEx = regEx( export function loadConfigFromLegacyYarnrc( legacyYarnrc: string, -): YarnrcConfig | null { +): YarnConfig | null { const registryMatches = [...legacyYarnrc.matchAll(registryRegEx)] .map((m) => m.groups) .filter(is.truthy); - const yarnrcConfig: YarnrcConfig = {}; + const yarnrcConfig: YarnConfig = {}; for (const registryMatch of registryMatches) { if (registryMatch.scope) { yarnrcConfig.npmScopes ??= {}; @@ -30,10 +29,8 @@ export function loadConfigFromLegacyYarnrc( return yarnrcConfig; } -export function loadConfigFromYarnrcYml( - yarnrcYml: string, -): YarnrcConfig | null { - return Result.parse(yarnrcYml, Yaml.pipe(YarnrcSchema)) +export function loadConfigFromYarnrcYml(yarnrcYml: string): YarnConfig | null { + return Result.parse(yarnrcYml, YarnConfig) .onError((err) => { logger.warn({ yarnrcYml, err }, `Failed to load yarnrc file`); }) @@ -42,7 +39,7 @@ export function loadConfigFromYarnrcYml( export function resolveRegistryUrl( packageName: string, - yarnrcConfig: YarnrcConfig, + yarnrcConfig: YarnConfig, ): string | null { if (yarnrcConfig.npmScopes) { for (const scope in yarnrcConfig.npmScopes) { diff --git a/lib/modules/manager/npm/post-update/types.ts b/lib/modules/manager/npm/post-update/types.ts index 2ffe4d7f41b..8fea6cb3ccc 100644 --- a/lib/modules/manager/npm/post-update/types.ts +++ b/lib/modules/manager/npm/post-update/types.ts @@ -31,15 +31,15 @@ export interface GenerateLockFileResult { // the dependencies schema is different for v6 and other lockfile versions // Ref: https://github.com/pnpm/spec/issues/4#issuecomment-1524059392 -export type PnpmDependencySchema = Record; +export type PnpmDependency = Record; export interface PnpmLockFile { lockfileVersion: number | string; catalogs?: Record>; - importers?: Record>; - dependencies: PnpmDependencySchema; - devDependencies: PnpmDependencySchema; - optionalDependencies: PnpmDependencySchema; + importers?: Record>; + dependencies: PnpmDependency; + devDependencies: PnpmDependency; + optionalDependencies: PnpmDependency; } export interface YarnRcNpmRegistry { diff --git a/lib/modules/manager/npm/post-update/utils.ts b/lib/modules/manager/npm/post-update/utils.ts index e37550ca640..fbc7e71b393 100644 --- a/lib/modules/manager/npm/post-update/utils.ts +++ b/lib/modules/manager/npm/post-update/utils.ts @@ -4,12 +4,11 @@ import upath from 'upath'; import { logger } from '../../../../logger'; import { readLocalFile } from '../../../../util/fs'; import { Lazy } from '../../../../util/lazy'; -import type { PackageJsonSchema } from '../schema'; import { PackageJson } from '../schema'; export function lazyLoadPackageJson( lockFileDir: string, -): Lazy> { +): Lazy> { return new Lazy(() => loadPackageJson(lockFileDir)); } @@ -17,7 +16,7 @@ export type LazyPackageJson = ReturnType; export async function loadPackageJson( lockFileDir: string, -): Promise { +): Promise { const json = await readLocalFile( upath.join(lockFileDir, 'package.json'), 'utf8', @@ -31,7 +30,7 @@ export async function loadPackageJson( export function getPackageManagerVersion( name: string, - pkg: PackageJsonSchema, + pkg: PackageJson, ): string | null { if (pkg.volta?.[name]) { const version = pkg.volta[name]; diff --git a/lib/modules/manager/npm/schema.ts b/lib/modules/manager/npm/schema.ts index 0b89e73dd17..faa5c3efe71 100644 --- a/lib/modules/manager/npm/schema.ts +++ b/lib/modules/manager/npm/schema.ts @@ -1,31 +1,34 @@ import { z } from 'zod'; -import { Json, LooseRecord } from '../../../util/schema-utils'; +import { Json, LooseRecord, Yaml } from '../../../util/schema-utils'; -export const PnpmCatalogsSchema = z.object({ +export const PnpmCatalogs = z.object({ catalog: z.optional(z.record(z.string())), catalogs: z.optional(z.record(z.record(z.string()))), }); -export const YarnrcSchema = z.object({ - npmRegistryServer: z.string().optional(), - npmScopes: z - .record( - z.object({ - npmRegistryServer: z.string().optional(), - }), - ) - .optional(), -}); +export const YarnConfig = Yaml.pipe( + z.object({ + npmRegistryServer: z.string().optional(), + npmScopes: z + .record( + z.object({ + npmRegistryServer: z.string().optional(), + }), + ) + .optional(), + }), +); -export type YarnrcConfig = z.infer; +export type YarnConfig = z.infer; -export const PnpmWorkspaceFileSchema = z +export const PnpmWorkspaceFile = z .object({ packages: z.array(z.string()), }) - .and(PnpmCatalogsSchema); + .and(PnpmCatalogs); +export type PnpmWorkspaceFile = z.infer; -export const PackageManagerSchema = z +export const PackageManager = z .string() .transform((val) => val.split('@')) .transform(([name, ...version]) => ({ name, version: version.join('@') })); @@ -35,27 +38,27 @@ const DevEngineDependency = z.object({ version: z.string().optional(), }); -const DevEngineSchema = z.object({ +const DevEngine = z.object({ packageManager: DevEngineDependency.or( z.array(DevEngineDependency), ).optional(), }); -export const PackageJsonSchema = z.object({ - devEngines: DevEngineSchema.optional(), - engines: LooseRecord(z.string()).optional(), - dependencies: LooseRecord(z.string()).optional(), - devDependencies: LooseRecord(z.string()).optional(), - peerDependencies: LooseRecord(z.string()).optional(), - packageManager: PackageManagerSchema.optional(), - volta: LooseRecord(z.string()).optional(), -}); - -export type PackageJsonSchema = z.infer; +export const PackageJson = Json.pipe( + z.object({ + devEngines: DevEngine.optional(), + engines: LooseRecord(z.string()).optional(), + dependencies: LooseRecord(z.string()).optional(), + devDependencies: LooseRecord(z.string()).optional(), + peerDependencies: LooseRecord(z.string()).optional(), + packageManager: PackageManager.optional(), + volta: LooseRecord(z.string()).optional(), + }), +); -export const PackageJson = Json.pipe(PackageJsonSchema); +export type PackageJson = z.infer; -export const PackageLockV3Schema = z.object({ +export const PackageLockV3 = z.object({ lockfileVersion: z.literal(3), packages: LooseRecord( z @@ -66,7 +69,7 @@ export const PackageLockV3Schema = z.object({ ), }); -export const PackageLockPreV3Schema = z +export const PackageLockPreV3 = z .object({ lockfileVersion: z.union([z.literal(2), z.literal(1)]), dependencies: LooseRecord(z.object({ version: z.string() })), @@ -77,7 +80,7 @@ export const PackageLockPreV3Schema = z })); export const PackageLock = Json.pipe( - z.union([PackageLockV3Schema, PackageLockPreV3Schema]), + z.union([PackageLockV3, PackageLockPreV3]), ).transform(({ packages, lockfileVersion }) => { const lockedVersions: Record = {}; for (const [entry, val] of Object.entries(packages)) { diff --git a/lib/modules/manager/npm/update/dependency/pnpm.ts b/lib/modules/manager/npm/update/dependency/pnpm.ts index c07e6477bb4..404aa5cbc82 100644 --- a/lib/modules/manager/npm/update/dependency/pnpm.ts +++ b/lib/modules/manager/npm/update/dependency/pnpm.ts @@ -3,7 +3,7 @@ import type { Document } from 'yaml'; import { CST, isCollection, isPair, isScalar, parseDocument } from 'yaml'; import { logger } from '../../../../../logger'; import type { UpdateDependencyConfig } from '../../../types'; -import { PnpmCatalogsSchema } from '../../schema'; +import { PnpmCatalogs } from '../../schema'; import { getNewGitValue, getNewNpmAliasValue } from './common'; export function updatePnpmCatalogDependency({ @@ -42,7 +42,7 @@ export function updatePnpmCatalogDependency({ // values. Thus, we use both an annotated AST and a JS representation; the // former for manipulation, and the latter for querying/validation. document = parseDocument(fileContent, { keepSourceTokens: true }); - parsedContents = PnpmCatalogsSchema.parse(document.toJS()); + parsedContents = PnpmCatalogs.parse(document.toJS()); } catch (err) { logger.debug({ err }, 'Could not parse pnpm-workspace YAML file.'); return null; diff --git a/lib/modules/manager/nuget/schema.ts b/lib/modules/manager/nuget/schema.ts index c71dabc94a1..fc5b9dfa540 100644 --- a/lib/modules/manager/nuget/schema.ts +++ b/lib/modules/manager/nuget/schema.ts @@ -6,7 +6,7 @@ import { Jsonc } from '../../../util/schema-utils'; * * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#rollforward */ -const RollForwardSchema = z.enum([ +const RollForward = z.enum([ 'patch', 'feature', 'minor', @@ -17,47 +17,48 @@ const RollForwardSchema = z.enum([ 'latestMajor', 'disable', ]); -export type RollForward = z.infer; +export type RollForward = z.infer; /** * global.json schema * * https://learn.microsoft.com/en-us/dotnet/core/tools/global-json#allowprerelease */ -export const GlobalJsonSchema = z.object({ - /** - * Specifies information about the .NET SDK to select. - */ - sdk: z - .object({ - /** - * The version of the .NET SDK to use. - * - * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#version - */ - version: z.string().optional(), - /** - * The roll-forward policy to use when selecting an SDK version, either as a fallback when a specific SDK version is missing or as a directive to use a later version. A version must be specified with a rollForward value, unless you're setting it to latestMajor. The default roll forward behavior is determined by the matching rules. - * - * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#rollforward - */ - rollForward: RollForwardSchema.optional(), - /** - * Indicates whether the SDK resolver should consider prerelease versions when selecting the SDK version to use. - * - * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#allowprerelease - */ - allowPrerelease: z.boolean().optional(), - }) - .optional(), +export const GlobalJson = Jsonc.pipe( + z.object({ + /** + * Specifies information about the .NET SDK to select. + */ + sdk: z + .object({ + /** + * The version of the .NET SDK to use. + * + * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#version + */ + version: z.string().optional(), + /** + * The roll-forward policy to use when selecting an SDK version, either as a fallback when a specific SDK version is missing or as a directive to use a later version. A version must be specified with a rollForward value, unless you're setting it to latestMajor. The default roll forward behavior is determined by the matching rules. + * + * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#rollforward + */ + rollForward: RollForward.optional(), + /** + * Indicates whether the SDK resolver should consider prerelease versions when selecting the SDK version to use. + * + * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#allowprerelease + */ + allowPrerelease: z.boolean().optional(), + }) + .optional(), - /** - * Lets you control the project SDK version in one place rather than in each individual project. For more information, see How project SDKs are resolved. - * - * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#msbuild-sdks - */ - 'msbuild-sdks': z.record(z.string()).optional(), -}); + /** + * Lets you control the project SDK version in one place rather than in each individual project. For more information, see How project SDKs are resolved. + * + * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#msbuild-sdks + */ + 'msbuild-sdks': z.record(z.string()).optional(), + }), +); -export const GlobalJson = Jsonc.pipe(GlobalJsonSchema); export type GlobalJson = z.infer; diff --git a/lib/modules/manager/ocb/extract.ts b/lib/modules/manager/ocb/extract.ts index 83df88db24f..75238ed1489 100644 --- a/lib/modules/manager/ocb/extract.ts +++ b/lib/modules/manager/ocb/extract.ts @@ -8,7 +8,7 @@ import type { PackageDependency, PackageFileContent, } from '../types'; -import { type Module, type OCBConfig, OCBConfigSchema } from './schema'; +import { type Module, OCBConfig } from './schema'; export function extractPackageFile( content: string, @@ -18,7 +18,7 @@ export function extractPackageFile( let definition: OCBConfig | null = null; try { const yaml = parseSingleYaml(content); - const parsed = OCBConfigSchema.safeParse(yaml); + const parsed = OCBConfig.safeParse(yaml); if (!parsed.success) { logger.trace( { packageFile, error: parsed.error }, diff --git a/lib/modules/manager/ocb/schema.ts b/lib/modules/manager/ocb/schema.ts index 07119f14f7b..0f60f6b79b0 100644 --- a/lib/modules/manager/ocb/schema.ts +++ b/lib/modules/manager/ocb/schema.ts @@ -4,20 +4,20 @@ const Entry = z.object({ gomod: z.string(), }); -const ModuleSchema = z.array(Entry).optional(); -export type Module = z.infer; +const Module = z.array(Entry).optional(); +export type Module = z.infer; -export const OCBConfigSchema = z.object({ +export const OCBConfig = z.object({ dist: z.object({ otelcol_version: z.string().optional(), module: z.string().optional(), version: z.string().optional(), }), - extensions: ModuleSchema, - exporters: ModuleSchema, - receivers: ModuleSchema, - processors: ModuleSchema, - providers: ModuleSchema, - connectors: ModuleSchema, + extensions: Module, + exporters: Module, + receivers: Module, + processors: Module, + providers: Module, + connectors: Module, }); -export type OCBConfig = z.infer; +export type OCBConfig = z.infer; diff --git a/lib/modules/manager/pep621/extract.ts b/lib/modules/manager/pep621/extract.ts index 94637de9395..28c81e3e7c6 100644 --- a/lib/modules/manager/pep621/extract.ts +++ b/lib/modules/manager/pep621/extract.ts @@ -8,7 +8,7 @@ import type { PackageFileContent, } from '../types'; import { processors } from './processors'; -import { type PyProject, PyProjectSchema } from './schema'; +import { PyProject } from './schema'; export function parsePyProject( content: string, @@ -16,7 +16,7 @@ export function parsePyProject( ): PyProject | null { try { const jsonMap = parseToml(massageToml(content)); - return PyProjectSchema.parse(jsonMap); + return PyProject.parse(jsonMap); } catch (err) { logger.debug( { packageFile, err }, diff --git a/lib/modules/manager/pep621/processors/pdm.ts b/lib/modules/manager/pep621/processors/pdm.ts index 02c9a5caf90..3e1ef55a67d 100644 --- a/lib/modules/manager/pep621/processors/pdm.ts +++ b/lib/modules/manager/pep621/processors/pdm.ts @@ -13,7 +13,7 @@ import type { UpdateArtifactsResult, Upgrade, } from '../../types'; -import { PdmLockfileSchema, type PyProject } from '../schema'; +import { PdmLockfile, type PyProject } from '../schema'; import type { Pep621ManagerData } from '../types'; import { depTypes } from '../utils'; import type { PyProjectProcessor } from './types'; @@ -59,7 +59,7 @@ export class PdmProcessor implements PyProjectProcessor { if (lockFileContent) { const lockFileMapping = Result.parse( lockFileContent, - PdmLockfileSchema.transform(({ lock }) => lock), + PdmLockfile.transform(({ lock }) => lock), ).unwrapOr({}); for (const dep of deps) { diff --git a/lib/modules/manager/pep621/processors/uv.ts b/lib/modules/manager/pep621/processors/uv.ts index 6b7b9da5c0d..33e9e335704 100644 --- a/lib/modules/manager/pep621/processors/uv.ts +++ b/lib/modules/manager/pep621/processors/uv.ts @@ -19,7 +19,7 @@ import type { Upgrade, } from '../../types'; import { applyGitSource } from '../../util'; -import { type PyProject, UvLockfileSchema } from '../schema'; +import { type PyProject, UvLockfile } from '../schema'; import { depTypes } from '../utils'; import type { PyProjectProcessor } from './types'; @@ -128,7 +128,7 @@ export class UvProcessor implements PyProjectProcessor { if (lockFileContent) { const { val: lockFileMapping, err } = Result.parse( lockFileContent, - UvLockfileSchema, + UvLockfile, ).unwrap(); if (err) { diff --git a/lib/modules/manager/pep621/schema.ts b/lib/modules/manager/pep621/schema.ts index 9e01a5bbde9..a754f147355 100644 --- a/lib/modules/manager/pep621/schema.ts +++ b/lib/modules/manager/pep621/schema.ts @@ -2,15 +2,12 @@ import { z } from 'zod'; import { LooseArray, LooseRecord, Toml } from '../../../util/schema-utils'; import { PypiDatasource } from '../../datasource/pypi'; import { normalizePythonDepName } from '../../datasource/pypi/common'; -import { PixiConfigSchema } from '../pixi/schema'; import type { PackageDependency } from '../types'; import { depTypes, pep508ToPackageDependency } from './utils'; -export type PyProject = z.infer; +type Pep508Dependency = z.ZodType>>; -type Pep508DependencySchema = z.ZodType>>; - -function Pep508Dependency(depType: string): Pep508DependencySchema { +function Pep508Dependency(depType: string): Pep508Dependency { return z.string().transform((x, ctx) => { const res = pep508ToPackageDependency(depType, x); @@ -25,14 +22,12 @@ function Pep508Dependency(depType: string): Pep508DependencySchema { } return res; - }) as Pep508DependencySchema; + }) as Pep508Dependency; } -type DependencyGroupSchema = z.ZodType< - PackageDependency>[] ->; +type DependencyGroup = z.ZodType>[]>; -function DependencyGroup(depType: string): DependencyGroupSchema { +function DependencyGroup(depType: string): DependencyGroup { return LooseRecord(LooseArray(Pep508Dependency(depType))).transform( (depGroups) => { const deps: PackageDependency[] = []; @@ -47,10 +42,10 @@ function DependencyGroup(depType: string): DependencyGroupSchema { } return deps; }, - ) as unknown as DependencyGroupSchema; + ) as unknown as DependencyGroup; } -const PdmSchema = z +const PdmConfig = z .object({ 'dev-dependencies': DependencyGroup(depTypes.pdmDevDependencies).catch([]), source: LooseArray( @@ -89,7 +84,7 @@ const PdmSchema = z }), ); -const HatchSchema = z +const HatchConfig = z .object({ envs: LooseRecord( z.string(), @@ -149,7 +144,7 @@ const UvSource = z.union([ UvWorkspaceSource, ]); -const UvSchema = z.object({ +const UvConfig = z.object({ 'dev-dependencies': LooseArray( Pep508Dependency(depTypes.uvDevDependencies), ).catch([]), @@ -171,7 +166,7 @@ const UvSchema = z.object({ .optional(), }); -export const PyProjectSchema = z.object({ +export const PyProject = z.object({ project: z .object({ version: z.string().optional().catch(undefined), @@ -197,16 +192,16 @@ export const PyProjectSchema = z.object({ 'dependency-groups': DependencyGroup(depTypes.dependencyGroups).catch([]), tool: z .object({ - pixi: PixiConfigSchema.optional().catch(undefined), - pdm: PdmSchema.optional().catch(undefined), - hatch: HatchSchema.optional().catch(undefined), - uv: UvSchema.optional().catch(undefined), + pdm: PdmConfig.optional().catch(undefined), + hatch: HatchConfig.optional().catch(undefined), + uv: UvConfig.optional().catch(undefined), }) .optional() .catch(undefined), }); +export type PyProject = z.infer; -export const PdmLockfileSchema = Toml.pipe( +export const PdmLockfile = Toml.pipe( z.object({ package: LooseArray( z.object({ @@ -223,7 +218,7 @@ export const PdmLockfileSchema = Toml.pipe( ) .transform((lock) => ({ lock })); -export const UvLockfileSchema = Toml.pipe( +export const UvLockfile = Toml.pipe( z.object({ package: LooseArray( z.object({ diff --git a/lib/modules/manager/pep723/extract.ts b/lib/modules/manager/pep723/extract.ts index a98ae53cbd0..aa402429977 100644 --- a/lib/modules/manager/pep723/extract.ts +++ b/lib/modules/manager/pep723/extract.ts @@ -1,7 +1,7 @@ import { logger } from '../../../logger'; import { newlineRegex, regEx } from '../../../util/regex'; import type { PackageFileContent } from '../types'; -import { Pep723Schema } from './schema'; +import { Pep723 } from './schema'; // Adapted regex from the Python reference implementation: https://packaging.python.org/en/latest/specifications/inline-script-metadata/#reference-implementation const regex = regEx( @@ -26,7 +26,7 @@ export function extractPackageFile( .map((line) => line.substring(line.startsWith('# ') ? 2 : 1)) .join('\n'); - const { data: res, error } = Pep723Schema.safeParse(parsedToml); + const { data: res, error } = Pep723.safeParse(parsedToml); if (error) { logger.debug( diff --git a/lib/modules/manager/pep723/schema.ts b/lib/modules/manager/pep723/schema.ts index c29ccf37ec1..976c62cf0f9 100644 --- a/lib/modules/manager/pep723/schema.ts +++ b/lib/modules/manager/pep723/schema.ts @@ -8,7 +8,7 @@ const Pep723Dep = z .string() .transform((dep) => pep508ToPackageDependency(depTypes.dependencies, dep)); -export const Pep723Schema = Toml.pipe( +export const Pep723 = Toml.pipe( z .object({ 'requires-python': z.string().optional(), diff --git a/lib/modules/manager/pixi/extract.ts b/lib/modules/manager/pixi/extract.ts index 0da5c93e6a7..6b34d3d7b19 100644 --- a/lib/modules/manager/pixi/extract.ts +++ b/lib/modules/manager/pixi/extract.ts @@ -5,7 +5,6 @@ import { logger } from '../../../logger'; import { coerceArray } from '../../../util/array'; import { getSiblingFileName, localPathExists } from '../../../util/fs'; import { Result } from '../../../util/result'; -import { Toml } from '../../../util/schema-utils'; import { ensureTrailingSlash, isHttpUrl, @@ -13,17 +12,15 @@ import { } from '../../../util/url'; import type { RegistryStrategy } from '../../datasource'; import { defaultRegistryUrl as defaultCondaRegistryApi } from '../../datasource/conda/common'; -import { PyProjectSchema } from '../pep621/schema'; import type { PackageFileContent } from '../types'; import { type Channels, type PixiConfig, + PixiFile, type PixiPackageDependency, - PixiToml, + PixiPyProject, } from './schema'; -const PyProjectToml = Toml.pipe(PyProjectSchema); - export function getUserPixiConfig( content: string, packageFile: string, @@ -32,7 +29,7 @@ export function getUserPixiConfig( packageFile === 'pyproject.toml' || packageFile.endsWith('/pyproject.toml') ) { - const { val, err } = Result.parse(content, PyProjectToml).unwrap(); + const { val, err } = Result.parse(content, PixiPyProject).unwrap(); if (err) { logger.debug({ packageFile, err }, `error parsing ${packageFile}`); return null; @@ -42,7 +39,7 @@ export function getUserPixiConfig( } if (packageFile === 'pixi.toml' || packageFile.endsWith('/pixi.toml')) { - const { val, err } = Result.parse(content, PixiToml).unwrap(); + const { val, err } = Result.parse(content, PixiFile).unwrap(); if (err) { logger.debug({ packageFile, err }, `error parsing ${packageFile}`); return null; @@ -53,7 +50,7 @@ export function getUserPixiConfig( const { val, err } = Result.parse( content, - z.union([PixiToml, PyProjectToml.transform((p) => p.tool?.pixi)]), + z.union([PixiFile, PixiPyProject.transform((p) => p.tool?.pixi)]), ).unwrap(); if (err) { diff --git a/lib/modules/manager/pixi/schema.ts b/lib/modules/manager/pixi/schema.ts index 990f7a6f28e..2be50cc3c26 100644 --- a/lib/modules/manager/pixi/schema.ts +++ b/lib/modules/manager/pixi/schema.ts @@ -6,6 +6,7 @@ import { PypiDatasource } from '../../datasource/pypi'; import * as condaVersion from '../../versioning/conda/'; import { id as gitRefVersionID } from '../../versioning/git'; import { id as pep440VersionID } from '../../versioning/pep440/'; +import { PyProject } from '../pep621/schema'; import type { PackageDependency } from '../types'; export type Channels = z.infer[]; @@ -188,16 +189,25 @@ const PixiProject = z.object({ /** * `$` of `pixi.toml` or `$.tool.pixi` of `pyproject.toml` */ -export const PixiConfigSchema = z +export const PixiConfig = z .union([PixiWorkspace, PixiProject]) .and(z.object({ feature: Features.default({}) })) .and(DependenciesMixin); -export type PixiConfig = z.infer; +export type PixiConfig = z.infer; -export const PixiToml = Toml.pipe(PixiConfigSchema); +export const PixiFile = Toml.pipe(PixiConfig); -export const LockfileYaml = Yaml.pipe( +export const PixiPyProject = Toml.pipe( + PyProject.extend({ + tool: z + .object({ pixi: PixiConfig.optional().catch(undefined) }) + .optional() + .catch(undefined), + }), +); + +export const Lockfile = Yaml.pipe( z.object({ version: z.number(), }), diff --git a/lib/modules/manager/poetry/artifacts.ts b/lib/modules/manager/poetry/artifacts.ts index 296da590479..6880d229a26 100644 --- a/lib/modules/manager/poetry/artifacts.ts +++ b/lib/modules/manager/poetry/artifacts.ts @@ -21,7 +21,7 @@ import { parseUrl } from '../../../util/url'; import { PypiDatasource } from '../../datasource/pypi'; import { getGoogleAuthHostRule } from '../../datasource/util'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; -import { Lockfile, PoetrySchemaToml } from './schema'; +import { Lockfile, PoetryPyProject } from './schema'; import type { PoetryFile, PoetrySource } from './types'; export function getPythonConstraint( @@ -31,7 +31,7 @@ export function getPythonConstraint( // Read Python version from `pyproject.toml` first as it could have been updated const pyprojectPythonConstraint = Result.parse( massageToml(pyProjectContent), - PoetrySchemaToml.transform( + PoetryPyProject.transform( ({ packageFileContent }) => packageFileContent.deps.find((dep) => dep.depName === 'python') ?.currentValue, @@ -82,7 +82,7 @@ export function getPoetryRequirement( const { val: pyprojectPoetryConstraint } = Result.parse( massageToml(pyProjectContent), - PoetrySchemaToml.transform(({ poetryRequirement }) => poetryRequirement), + PoetryPyProject.transform(({ poetryRequirement }) => poetryRequirement), ).unwrap(); if (pyprojectPoetryConstraint) { logger.debug( diff --git a/lib/modules/manager/poetry/extract.ts b/lib/modules/manager/poetry/extract.ts index f13130e6102..630737dd14e 100644 --- a/lib/modules/manager/poetry/extract.ts +++ b/lib/modules/manager/poetry/extract.ts @@ -10,7 +10,7 @@ import { Result } from '../../../util/result'; import { massage as massageToml } from '../../../util/toml'; import { GithubReleasesDatasource } from '../../datasource/github-releases'; import type { PackageFileContent } from '../types'; -import { Lockfile, PoetrySchemaToml } from './schema'; +import { Lockfile, PoetryPyProject } from './schema'; export async function extractPackageFile( content: string, @@ -19,7 +19,7 @@ export async function extractPackageFile( logger.trace(`poetry.extractPackageFile(${packageFile})`); const { val: res, err } = Result.parse( massageToml(content), - PoetrySchemaToml.transform(({ packageFileContent }) => packageFileContent), + PoetryPyProject.transform(({ packageFileContent }) => packageFileContent), ).unwrap(); if (err) { logger.debug({ packageFile, err }, `Poetry: error parsing pyproject.toml`); diff --git a/lib/modules/manager/poetry/schema.spec.ts b/lib/modules/manager/poetry/schema.spec.ts index 373aa68d44c..0ae5c934d74 100644 --- a/lib/modules/manager/poetry/schema.spec.ts +++ b/lib/modules/manager/poetry/schema.spec.ts @@ -1,14 +1,13 @@ -import { PoetrySectionSchema, PoetrySources } from './schema'; +import { PoetrySection, PoetrySources } from './schema'; describe('modules/manager/poetry/schema', () => { it('parses project version', () => { - expect( - PoetrySectionSchema.parse({ version: '1.2.3' }).packageFileVersion, - ).toBe('1.2.3'); + expect(PoetrySection.parse({ version: '1.2.3' }).packageFileVersion).toBe( + '1.2.3', + ); expect( - PoetrySectionSchema.parse({ version: { some: 'value' } }) - .packageFileVersion, + PoetrySection.parse({ version: { some: 'value' } }).packageFileVersion, ).toBeUndefined(); }); diff --git a/lib/modules/manager/poetry/schema.ts b/lib/modules/manager/poetry/schema.ts index 149bb0863e7..f5035720ea7 100644 --- a/lib/modules/manager/poetry/schema.ts +++ b/lib/modules/manager/poetry/schema.ts @@ -269,7 +269,7 @@ export const PoetrySources = LooseArray(PoetrySource, { }) .catch([]); -export const PoetrySectionSchema = z +export const PoetrySection = z .object({ version: z.string().optional().catch(undefined), dependencies: withDepType( @@ -322,7 +322,7 @@ export const PoetrySectionSchema = z }, ); -export type PoetrySectionSchema = z.infer; +export type PoetrySection = z.infer; const BuildSystemRequireVal = z .string() @@ -341,40 +341,41 @@ const BuildSystemRequireVal = z return { depName, poetryRequirement }; }); -export const PoetrySchema = z - .object({ - tool: z - .object({ poetry: PoetrySectionSchema }) - .transform(({ poetry }) => poetry), - 'build-system': z - .object({ - 'build-backend': z.string().refine( - // https://python-poetry.org/docs/pyproject/#poetry-and-pep-517 - (buildBackend) => - buildBackend === 'poetry.masonry.api' || - buildBackend === 'poetry.core.masonry.api', - ), - requires: LooseArray(BuildSystemRequireVal).transform((vals) => { - const req = vals.find( - ({ depName }) => depName === 'poetry' || depName === 'poetry_core', - ); - return req?.poetryRequirement; - }), - }) - .transform(({ requires: poetryRequirement }) => poetryRequirement) - .optional() - .catch(undefined), - }) - .transform( - ({ tool: packageFileContent, 'build-system': poetryRequirement }) => ({ - packageFileContent, - poetryRequirement, - }), - ); - -export type PoetrySchema = z.infer; +export const PoetryPyProject = Toml.pipe( + z + .object({ + tool: z + .object({ poetry: PoetrySection }) + .transform(({ poetry }) => poetry), + 'build-system': z + .object({ + 'build-backend': z.string().refine( + // https://python-poetry.org/docs/pyproject/#poetry-and-pep-517 + (buildBackend) => + buildBackend === 'poetry.masonry.api' || + buildBackend === 'poetry.core.masonry.api', + ), + requires: LooseArray(BuildSystemRequireVal).transform((vals) => { + const req = vals.find( + ({ depName }) => + depName === 'poetry' || depName === 'poetry_core', + ); + return req?.poetryRequirement; + }), + }) + .transform(({ requires: poetryRequirement }) => poetryRequirement) + .optional() + .catch(undefined), + }) + .transform( + ({ tool: packageFileContent, 'build-system': poetryRequirement }) => ({ + packageFileContent, + poetryRequirement, + }), + ), +); -export const PoetrySchemaToml = Toml.pipe(PoetrySchema); +export type PoetryPyProject = z.infer; const poetryConstraint: Record = { '1.0': '<1.1.0', diff --git a/lib/modules/manager/poetry/update-locked.ts b/lib/modules/manager/poetry/update-locked.ts index 6041c4c5a6e..31bf9d21a68 100644 --- a/lib/modules/manager/poetry/update-locked.ts +++ b/lib/modules/manager/poetry/update-locked.ts @@ -12,8 +12,8 @@ export function updateLockedDependency( `poetry.updateLockedDependency: ${depName}@${currentVersion} -> ${newVersion} [${lockFile}]`, ); - const LockedVersionSchema = Lockfile.transform(({ lock }) => lock[depName]); - return Result.parse(lockFileContent, LockedVersionSchema) + const LockedVersion = Lockfile.transform(({ lock }) => lock[depName]); + return Result.parse(lockFileContent, LockedVersion) .transform( (lockedVersion): UpdateLockedResult => lockedVersion === newVersion diff --git a/lib/modules/manager/pub/extract.ts b/lib/modules/manager/pub/extract.ts index f83cbdf320c..8e77d6233b8 100644 --- a/lib/modules/manager/pub/extract.ts +++ b/lib/modules/manager/pub/extract.ts @@ -4,12 +4,12 @@ import { DartDatasource } from '../../datasource/dart'; import { DartVersionDatasource } from '../../datasource/dart-version'; import { FlutterVersionDatasource } from '../../datasource/flutter-version'; import type { PackageDependency, PackageFileContent } from '../types'; -import type { PubspecSchema } from './schema'; +import type { Pubspec } from './schema'; import { parsePubspec } from './utils'; function extractFromSection( - pubspec: PubspecSchema, - sectionKey: keyof Pick, + pubspec: Pubspec, + sectionKey: keyof Pick, ): PackageDependency[] { const sectionContent = pubspec[sectionKey]; if (!sectionContent) { @@ -67,7 +67,7 @@ function extractFromSection( return deps; } -function extractDart(pubspec: PubspecSchema): PackageDependency[] { +function extractDart(pubspec: Pubspec): PackageDependency[] { return [ { depName: 'dart', @@ -77,7 +77,7 @@ function extractDart(pubspec: PubspecSchema): PackageDependency[] { ]; } -function extractFlutter(pubspec: PubspecSchema): PackageDependency[] { +function extractFlutter(pubspec: Pubspec): PackageDependency[] { const currentValue = pubspec.environment.flutter; if (!currentValue) { return []; diff --git a/lib/modules/manager/pub/schema.ts b/lib/modules/manager/pub/schema.ts index f7c57158dc7..a66878e836c 100644 --- a/lib/modules/manager/pub/schema.ts +++ b/lib/modules/manager/pub/schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { LooseRecord, Yaml } from '../../../util/schema-utils'; -const PubspecDependencySchema = LooseRecord( +const PubspecDependency = LooseRecord( z.string(), z.union([ z.string(), @@ -16,23 +16,23 @@ const PubspecDependencySchema = LooseRecord( ]), ); -export const PubspecSchema = z.object({ - environment: z.object({ sdk: z.string(), flutter: z.string().optional() }), - dependencies: PubspecDependencySchema.optional(), - dev_dependencies: PubspecDependencySchema.optional(), -}); - -export type PubspecSchema = z.infer; +export const Pubspec = Yaml.pipe( + z.object({ + environment: z.object({ sdk: z.string(), flutter: z.string().optional() }), + dependencies: PubspecDependency.optional(), + dev_dependencies: PubspecDependency.optional(), + }), +); -export const PubspecYaml = Yaml.pipe(PubspecSchema); +export type Pubspec = z.infer; -export const PubspecLockSchema = z.object({ - sdks: z.object({ - dart: z.string(), - flutter: z.string().optional(), +export const PubspecLock = Yaml.pipe( + z.object({ + sdks: z.object({ + dart: z.string(), + flutter: z.string().optional(), + }), }), -}); - -export type PubspecLockSchema = z.infer; +); -export const PubspecLockYaml = Yaml.pipe(PubspecLockSchema); +export type PubspecLock = z.infer; diff --git a/lib/modules/manager/pub/utils.ts b/lib/modules/manager/pub/utils.ts index 4453319bd9c..fb1db6d2cb2 100644 --- a/lib/modules/manager/pub/utils.ts +++ b/lib/modules/manager/pub/utils.ts @@ -1,12 +1,11 @@ import { logger } from '../../../logger'; -import type { PubspecLockSchema, PubspecSchema } from './schema'; -import { PubspecLockYaml, PubspecYaml } from './schema'; +import { Pubspec, PubspecLock } from './schema'; export function parsePubspec( fileName: string, fileContent: string, -): PubspecSchema | null { - const res = PubspecYaml.safeParse(fileContent); +): Pubspec | null { + const res = Pubspec.safeParse(fileContent); if (res.success) { return res.data; } else { @@ -18,8 +17,8 @@ export function parsePubspec( export function parsePubspecLock( fileName: string, fileContent: string, -): PubspecLockSchema | null { - const res = PubspecLockYaml.safeParse(fileContent); +): PubspecLock | null { + const res = PubspecLock.safeParse(fileContent); if (res.success) { return res.data; } else { diff --git a/lib/modules/manager/renovate-config-presets/extract.ts b/lib/modules/manager/renovate-config-presets/extract.ts index 7a68ce888fa..d77a6267065 100644 --- a/lib/modules/manager/renovate-config-presets/extract.ts +++ b/lib/modules/manager/renovate-config-presets/extract.ts @@ -5,7 +5,7 @@ import { GiteaTagsDatasource } from '../../datasource/gitea-tags'; import { GithubTagsDatasource } from '../../datasource/github-tags'; import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; import type { PackageDependency, PackageFileContent } from '../types'; -import { RenovateJsonSchema } from './schema'; +import { RenovateJson } from './schema'; const supportedPresetSources: Record = { github: GithubTagsDatasource.id, @@ -18,7 +18,7 @@ export function extractPackageFile( packageFile: string, ): PackageFileContent | null { logger.trace(`renovate-config-presets.extractPackageFile(${packageFile})`); - const config = RenovateJsonSchema.safeParse(content); + const config = RenovateJson.safeParse(content); if (!config.success) { logger.debug({ packageFile, err: config.error }, 'Invalid Renovate Config'); return null; diff --git a/lib/modules/manager/renovate-config-presets/schema.ts b/lib/modules/manager/renovate-config-presets/schema.ts index 775050212c1..6dec17c0a36 100644 --- a/lib/modules/manager/renovate-config-presets/schema.ts +++ b/lib/modules/manager/renovate-config-presets/schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { Json5 } from '../../../util/schema-utils'; -export const RenovateJsonSchema = Json5.pipe( +export const RenovateJson = Json5.pipe( z.object({ extends: z.array(z.string()).optional(), }), diff --git a/lib/modules/platform/azure/azure-helper.ts b/lib/modules/platform/azure/azure-helper.ts index f6ac43cf670..90de424f879 100644 --- a/lib/modules/platform/azure/azure-helper.ts +++ b/lib/modules/platform/azure/azure-helper.ts @@ -8,7 +8,7 @@ import { logger } from '../../../logger'; import { streamToString } from '../../../util/streams'; import { getNewBranchName } from '../util'; import * as azureApi from './azure-got-wrapper'; -import { WrappedExceptionSchema } from './schema'; +import { WrappedException } from './schema'; import { getBranchNameWithoutRefsPrefix, getBranchNameWithoutRefsheadsPrefix, @@ -84,7 +84,7 @@ export async function getFile( if (item?.readable) { const fileContent = await streamToString(item); try { - const result = WrappedExceptionSchema.safeParse(fileContent); + const result = WrappedException.safeParse(fileContent); if (result.success) { if (result.data.typeKey === 'GitItemNotFoundException') { logger.warn({ filePath }, 'Unable to find file'); diff --git a/lib/modules/platform/azure/schema.ts b/lib/modules/platform/azure/schema.ts index 1d266ce6f29..572fe9ee2e5 100644 --- a/lib/modules/platform/azure/schema.ts +++ b/lib/modules/platform/azure/schema.ts @@ -1,14 +1,14 @@ -import type { WrappedException } from 'azure-devops-node-api/interfaces/common/VSSInterfaces'; +import type { WrappedException as _WrappedException } from 'azure-devops-node-api/interfaces/common/VSSInterfaces'; import { z } from 'zod'; import { Json } from '../../../util/schema-utils'; -const WrappedException: z.ZodSchema = z.lazy(() => +const _WrappedException: z.ZodSchema<_WrappedException> = z.lazy(() => z.object({ customProperties: z.record(z.any()).optional(), errorCode: z.number().optional(), eventId: z.number().optional(), helpLink: z.string().optional(), - innerException: WrappedException.optional(), + innerException: _WrappedException.optional(), message: z.string().optional(), stackTrace: z.string().optional(), typeKey: z.string().optional(), @@ -16,4 +16,4 @@ const WrappedException: z.ZodSchema = z.lazy(() => }), ); -export const WrappedExceptionSchema = Json.pipe(WrappedException); +export const WrappedException = Json.pipe(_WrappedException); diff --git a/lib/modules/platform/bitbucket-server/index.spec.ts b/lib/modules/platform/bitbucket-server/index.spec.ts index d64d2149c51..dae899dbd17 100644 --- a/lib/modules/platform/bitbucket-server/index.spec.ts +++ b/lib/modules/platform/bitbucket-server/index.spec.ts @@ -2739,6 +2739,546 @@ Followed by some information. await expect(bitbucket.getJsonFile('file.json')).rejects.toThrow(); }); }); + describe('modules/platform/bitbucket-server/code-owners', () => { + it('ignores comments and empty lines', () => { + const lines = ['# This is a comment', '', 'docs/** @dev@example.com']; + + const rules = bitbucket.extractRulesFromCodeOwnersLines(lines); + + expect(rules).toHaveLength(1); + expect(rules[0].pattern).toBe('docs/**'); + expect(rules[0].usernames).toEqual(['@dev@example.com']); + }); + + it('parses usernames with escaped spaces', () => { + const lines = [String.raw`src/** @Jane\\ Doe @john@example.com`]; + + const rules = bitbucket.extractRulesFromCodeOwnersLines(lines); + + expect(rules[0].usernames).toEqual([ + '@Jane Doe', + '@john@example.com', + ]); + }); + + it('parses groups with escaped spaces', () => { + const lines = [ + String.raw`src/** @reviewer-group/Jane\\ Doe @john@example.com`, + ]; + + const rules = bitbucket.extractRulesFromCodeOwnersLines(lines); + + expect(rules[0].usernames).toEqual([ + '@reviewer-group/Jane Doe', + '@john@example.com', + ]); + }); + + it('supports reviewer groups with modifiers)', () => { + const lines = [ + 'docs/** @reviewer-group/content-designers:random', + 'docs/api/** @reviewer-group/devs:random(2)', + ]; + + const rules = bitbucket.extractRulesFromCodeOwnersLines(lines); + + expect(rules[0].usernames).toEqual([ + '@reviewer-group/devs:random(2)', + ]); + expect(rules[1].usernames).toEqual([ + '@reviewer-group/content-designers:random', + ]); + }); + + it('matches paths correctly using glob patterns', () => { + const lines = ['**.css @css-owner', 'frontend/** @frontend-dev']; + + const rules = bitbucket.extractRulesFromCodeOwnersLines(lines); + + // Rules should be reversed: last line has higher precedence + expect(rules[0].pattern).toBe('frontend/**'); + expect(rules[1].pattern).toBe('**.css'); + + // Path matches + expect(rules[0].match('frontend/app/main.ts')).toBe(true); + expect(rules[1].match('styles/theme.css')).toBe(true); + + // Non-match + expect(rules[1].match('scripts/app.js')).toBe(false); + }); + + it('respects bottom-to-top rule precedence', () => { + const lines = ['** @fallback-user', 'docs/** @docs-user']; + + const rules = bitbucket.extractRulesFromCodeOwnersLines(lines); + + // Rule order should be reversed (bottom to top) + expect(rules[0].pattern).toBe('docs/**'); + expect(rules[1].pattern).toBe('**'); + }); + + it('supports rules with no owners (ownership ignored)', () => { + const lines = ['docs/images/**']; + + const rules = bitbucket.extractRulesFromCodeOwnersLines(lines); + + expect(rules[0].pattern).toBe('docs/images/**'); + expect(rules[0].usernames).toEqual([]); + expect(rules[0].match('docs/images/logo.png')).toBe(true); + }); + + it('unescapes multiple escaped spaces correctly', () => { + const lines = [String.raw`docs/** @reviewer-group/UX\\ Team\\ Lead`]; + + const rules = bitbucket.extractRulesFromCodeOwnersLines(lines); + + expect(rules[0].usernames).toEqual(['@reviewer-group/UX Team Lead']); + }); + }); + describe('expandGroupMembers()', () => { + it('returns input when it is not a group', async () => { + const users = await bitbucket.expandGroupMembers([ + '@alice', + 'user@user.com', + ]); + expect(users).toEqual(['@alice', 'user@user.com']); + }); + it('returns only active users from the matching reviewer group', async () => { + const scope = await initRepo(); + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'my-reviewer-group', + scope: { + type: 'REPOSITORY', + }, + users: [ + { + slug: 'alice', + active: true, + emailAddress: 'alice@alice.com', + displayName: 'alice', + }, + { + slug: 'bob', + active: false, + emailAddress: 'bob@bob.com', + displayName: 'bob', + }, + { + slug: 'carol', + active: true, + emailAddress: 'carol@carol.com', + displayName: 'carol', + }, + ], + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-reviewer-group', + ]); + expect(users).toEqual(['alice@alice.com', 'carol@carol.com']); + }); + it('returns empty array if group is not found', async () => { + const scope = await initRepo(); + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'other-group', + users: [ + { + slug: 'dave', + active: true, + emailAddress: 'dave@dave.com', + displayName: 'dave', + }, + ], + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/nonexistent-group', + ]); + expect(users).toEqual([]); + }); + it('returns empty array if API call fails', async () => { + const scope = await initRepo(); + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(500); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-reviewer-group', + ]); + expect(users).toEqual([]); + }); + it('returns empty array if all users in group are inactive', async () => { + const scope = await initRepo(); + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'my-reviewer-group', + users: [ + { + slug: 'user1', + active: false, + emailAddress: 'user1@user1.com', + displayName: 'user1', + }, + { + slug: 'user2', + active: false, + emailAddress: 'user2@user2.com', + displayName: 'user2', + }, + ], + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-reviewer-group', + ]); + expect(users).toEqual([]); + }); + it('prefers repository-level reviewer group over project-level group with same name', async () => { + const scope = await initRepo(); + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'my-group', + scope: { + type: 'PROJECT', + }, + users: [ + { + slug: 'jane', + active: true, + emailAddress: 'jane@project.com', + displayName: 'jane', + }, + ], + }, + { + name: 'my-group', + scope: { + type: 'REPOSITORY', + }, + users: [ + { + slug: 'zoe', + active: true, + emailAddress: 'zoe@repo.com', + displayName: 'zoe', + }, + ], + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-group', + ]); + expect(users).toEqual(['zoe@repo.com']); + }); + + it('uses project-level group when repository-level group is not available', async () => { + const scope = await initRepo(); + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'my-group', + scope: { + type: 'PROJECT', + }, + users: [ + { + slug: 'jane', + active: true, + emailAddress: 'jane@project.com', + displayName: 'jane', + }, + ], + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-group', + ]); + expect(users).toEqual(['jane@project.com']); + }); + + it('deals with not found groups correctly', async () => { + const scope = await initRepo(); + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'other-group', + scope: { + type: 'PROJECT', + }, + users: [], + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-group', + ]); + expect(users).toEqual([]); + }); + + it('handles random without number correctly', async () => { + const scope = await initRepo(); + + const userArray = [ + { + slug: 'zoe', + active: true, + emailAddress: 'zoe@zoe.com', + displayName: 'zoe', + }, + { + slug: 'user1', + active: true, + emailAddress: 'user1@user1.com', + displayName: 'user1', + }, + { + slug: 'user2', + active: true, + emailAddress: 'user2@user2.com', + displayName: 'user2', + }, + ]; + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'my-reviewer-group', + scope: { + type: 'REPOSITORY', + }, + users: userArray, + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-reviewer-group:random', + ]); + expect(users).toHaveLength(1); + expect(userArray.map((u) => u.emailAddress)).toContain(users[0]); + }); + it('handles random with number correctly', async () => { + const scope = await initRepo(); + const userArray = [ + { + slug: 'zoe', + active: true, + emailAddress: 'zoe@zoe.com', + displayName: 'zoe', + }, + { + slug: 'user1', + active: true, + emailAddress: 'user1@user1.com', + displayName: 'user1', + }, + { + slug: 'user2', + active: true, + emailAddress: 'user2@user2.com', + displayName: 'user2', + }, + ]; + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'my-reviewer-group', + scope: { + type: 'REPOSITORY', + }, + users: userArray, + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-reviewer-group:random(2)', + ]); + expect(users).toHaveLength(2); + users.forEach((user) => { + expect(userArray.map((u) => u.emailAddress)).toContain(user); + }); + }); + + it('handles non-existent modifier correctly', async () => { + const scope = await initRepo(); + const userArray = [ + { + slug: 'zoe', + active: true, + emailAddress: 'zoe@zoe.com', + displayName: 'zoe', + }, + { + slug: 'user1', + active: true, + emailAddress: 'user1@user1.com', + displayName: 'user1', + }, + { + slug: 'user2', + active: true, + emailAddress: 'user2@user2.com', + displayName: 'user2', + }, + ]; + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'my-reviewer-group', + scope: { + type: 'REPOSITORY', + }, + users: userArray, + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-reviewer-group:non-existent', + ]); + expect(users).toHaveLength(3); + users.forEach((user) => { + expect(userArray.map((u) => u.emailAddress)).toContain(user); + }); + }); + + it('handles paginated responses and finds matching group in next page', async () => { + const scope = await initRepo(); + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100`, + ) + .reply(200, { + isLastPage: false, + nextPageStart: 1, + values: [ + { + name: 'not-this-group', + scope: { + type: 'REPOSITORY', + }, + users: [ + { + slug: 'nope', + active: true, + emailAddress: 'nope@nope.com', + displayName: 'nope', + }, + ], + }, + ], + }); + + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/settings/reviewer-groups?limit=100&start=1`, + ) + .reply(200, { + isLastPage: true, + values: [ + { + name: 'my-reviewer-group', + scope: { + type: 'REPOSITORY', + }, + users: [ + { + slug: 'alice', + active: true, + emailAddress: 'alice@alice.com', + displayName: 'alice', + }, + { + slug: 'bob', + active: true, + emailAddress: 'bob@bob.com', + displayName: 'bob', + }, + ], + }, + ], + }); + + const users = await bitbucket.expandGroupMembers([ + '@reviewer-group/my-reviewer-group', + ]); + expect(users).toEqual(['alice@alice.com', 'bob@bob.com']); + }); + }); }); }); }); diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts index c1beab4dab3..daaed5120e7 100644 --- a/lib/modules/platform/bitbucket-server/index.ts +++ b/lib/modules/platform/bitbucket-server/index.ts @@ -1,4 +1,5 @@ import { setTimeout } from 'timers/promises'; +import ignore from 'ignore'; import semver from 'semver'; import type { PartialDeep } from 'type-fest'; import { @@ -22,6 +23,7 @@ import { import { memCacheProvider } from '../../../util/http/cache/memory-http-cache-provider'; import type { HttpOptions, HttpResponse } from '../../../util/http/types'; import { newlineRegex, regEx } from '../../../util/regex'; +import { sampleSize } from '../../../util/sample'; import { sanitize } from '../../../util/sanitize'; import { ensureTrailingSlash, getQueryString } from '../../../util/url'; import type { @@ -31,6 +33,7 @@ import type { EnsureCommentRemovalConfig, EnsureIssueConfig, EnsureIssueResult, + FileOwnerRule, FindPRConfig, Issue, MergePRConfig, @@ -49,7 +52,7 @@ import type { PullRequestActivity, PullRequestCommentActivity, } from './schema'; -import { UserSchema, UsersSchema, isEmail } from './schema'; +import { ReviewerGroups, User, Users, isEmail } from './schema'; import type { BbsConfig, BbsPr, @@ -59,7 +62,7 @@ import type { BbsRestUserRef, } from './types'; import * as utils from './utils'; -import { getExtraCloneOpts } from './utils'; +import { getExtraCloneOpts, parseModifier, splitEscapedSpaces } from './utils'; /* * Version: 5.3 (EOL Date: 15 Aug 2019) @@ -162,7 +165,7 @@ export async function initPlatform({ await bitbucketServerHttp.getJson( `./rest/api/1.0/users/${username}`, options, - UserSchema, + User, ) ).body; @@ -726,7 +729,7 @@ export async function getUserSlugsByEmail( const users = await bitbucketServerHttp.getJson( filterUrl, { limit: 100 }, - UsersSchema, + Users, ); if (users.body.length) { @@ -1214,6 +1217,115 @@ export async function mergePr({ return true; } +export async function expandGroupMembers( + reviewers: string[], +): Promise { + logger.debug(`expandGroupMembers(${reviewers.join(', ')})`); + const expandedUsers: string[] = []; + const reviewerGroupPrefix = '@reviewer-group/'; + + for (const reviewer of reviewers) { + const [baseEntry, modifier] = reviewer.split(':'); + + if (baseEntry.startsWith(reviewerGroupPrefix)) { + const groupName = baseEntry.replace(reviewerGroupPrefix, ''); + const groupUsers = await getUsersFromReviewerGroup(groupName); + if (!groupUsers.length) { + continue; + } + + if (modifier) { + const randomCount = parseModifier(modifier); + if (randomCount) { + expandedUsers.push(...sampleSize(groupUsers, randomCount)); + continue; + } + } + + expandedUsers.push(...groupUsers); + } else { + expandedUsers.push(baseEntry); // Add the user entry + } + } + + return [...new Set(expandedUsers)]; +} + +export function extractRulesFromCodeOwnersLines( + cleanedLines: string[], +): FileOwnerRule[] { + const results: FileOwnerRule[] = []; + + const reversedLines = cleanedLines + .filter((line) => line.trim() !== '' && !line.trim().startsWith('#')) + .reverse(); + + for (const line of reversedLines) { + const [pattern, ...entries] = splitEscapedSpaces(line); + const matcher = ignore().add(pattern); + results.push({ + pattern, + usernames: [...new Set(entries)], + score: pattern.length, + match: (path: string) => matcher.ignores(path), + }); + } + + return results; +} + +// Gets active users by name, from a reviewer group +// Returns an empty array if the group is not found or has no active users +// As there is no direct API to get group by name, we get all reviewer groups per repo and filter them +// This is not efficient, but it is the only way to get users from a group by name +// Supports both repository-scoped and project-scoped groups following the BitBucket server logic described here: +// https://confluence.atlassian.com/bitbucketserver/code-owners-1296171116.html#Codeowners-Whatifaprojectandrepositorycontainareviewergroupwiththesamename? +async function getUsersFromReviewerGroup(groupName: string): Promise { + const allGroups = []; + + try { + const reviewerGroups = await bitbucketServerHttp.getJson( + `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/settings/reviewer-groups`, + { paginate: true }, + ReviewerGroups, + ); + + allGroups.push(...reviewerGroups.body); + } catch (err) { + logger.debug({ err, groupName }, 'Failed to get reviewer groups for repo'); + return []; + } + + // First, try to find a repo-scoped group with this name + const repoGroup = allGroups.find( + (group) => group.name === groupName && group.scope?.type === 'REPOSITORY', + ); + + if (repoGroup) { + return repoGroup.users + .filter((user) => user.active) + .map((user) => user.emailAddress); + } + + // If no repo-level group, fall back to project-level group + const projectGroup = allGroups.find( + (group) => group.name === groupName && group.scope?.type === 'PROJECT', + ); + + if (projectGroup) { + return projectGroup.users + .filter((user) => user.active) + .map((user) => user.emailAddress); + } + + // Group not found at either level + logger.warn( + { groupName }, + 'Reviewer group not found at repo or project level', + ); + return []; +} + export function massageMarkdown(input: string): string { logger.debug(`massageMarkdown(${input.split(newlineRegex)[0]})`); // Remove any HTML we use diff --git a/lib/modules/platform/bitbucket-server/schema.ts b/lib/modules/platform/bitbucket-server/schema.ts index e375b381ee0..9576b0fe8f4 100644 --- a/lib/modules/platform/bitbucket-server/schema.ts +++ b/lib/modules/platform/bitbucket-server/schema.ts @@ -1,13 +1,13 @@ import { z } from 'zod'; -export const UserSchema = z.object({ +export const User = z.object({ displayName: z.string(), emailAddress: z.string(), active: z.boolean(), slug: z.string(), }); -export const UsersSchema = z.array(UserSchema); +export const Users = z.array(User); export const Files = z.array(z.string()); @@ -35,7 +35,16 @@ export const PullRequestActivity = z.union([ export type PullRequestActivity = z.infer; -const EmailSchema = z.string().email(); +export const ReviewerGroup = z.object({ + name: z.string(), + users: z.array(User), + scope: z.object({ + type: z.union([z.literal('REPOSITORY'), z.literal('PROJECT')]), + }), +}); +export const ReviewerGroups = z.array(ReviewerGroup); + +const Email = z.string().email(); export const isEmail = (value: string): boolean => - EmailSchema.safeParse(value).success; + Email.safeParse(value).success; diff --git a/lib/modules/platform/bitbucket-server/utils.ts b/lib/modules/platform/bitbucket-server/utils.ts index 6afcdb7c407..af600e798a7 100644 --- a/lib/modules/platform/bitbucket-server/utils.ts +++ b/lib/modules/platform/bitbucket-server/utils.ts @@ -6,6 +6,7 @@ import { logger } from '../../../logger'; import type { HostRule } from '../../../types'; import type { GitOptions, GitProtocol } from '../../../types/git'; import * as git from '../../../util/git'; +import { regEx } from '../../../util/regex'; import { parseUrl } from '../../../util/url'; import { getPrBodyStruct } from '../pr-body'; import type { GitUrlOption } from '../types'; @@ -148,3 +149,28 @@ export function getExtraCloneOpts(opts: HostRule): GitOptions { } return {}; } + +export function splitEscapedSpaces(str: string): string[] { + const parts = str.split(' '); + const result: string[] = []; + let last: string | undefined; + + for (const part of parts) { + if (last?.endsWith('\\\\')) { + result[result.length - 1] = last.slice(0, -2) + ' ' + part; + } else { + result.push(part); + } + last = result.at(-1); + } + + return result; +} + +export function parseModifier(value: string): number | null { + const match = regEx('^random(?:\\((\\d+)\\))?$').exec(value); + if (!match) { + return null; + } + return parseInt(match[1] ?? '1'); +} diff --git a/lib/modules/platform/bitbucket/schema.ts b/lib/modules/platform/bitbucket/schema.ts index 577a804f41a..af383e35240 100644 --- a/lib/modules/platform/bitbucket/schema.ts +++ b/lib/modules/platform/bitbucket/schema.ts @@ -2,25 +2,25 @@ import { z } from 'zod'; import { logger } from '../../../logger'; import { LooseArray } from '../../../util/schema-utils'; -const BitbucketSourceTypeSchema = z.enum(['commit_directory', 'commit_file']); +const BitbucketSourceType = z.enum(['commit_directory', 'commit_file']); -const SourceResultsSchema = z.object({ +const SourceResults = z.object({ path: z.string(), - type: BitbucketSourceTypeSchema, + type: BitbucketSourceType, commit: z.object({ hash: z.string(), }), }); -const PagedSchema = z.object({ +const Paged = z.object({ page: z.number().optional(), pagelen: z.number(), size: z.number().optional(), next: z.string().optional(), }); -export const PagedSourceResultsSchema = PagedSchema.extend({ - values: z.array(SourceResultsSchema), +export const PagedSourceResults = Paged.extend({ + values: z.array(SourceResults), }); export const RepoInfo = z diff --git a/lib/modules/platform/forgejo/schema.spec.ts b/lib/modules/platform/forgejo/schema.spec.ts index bd7ff613591..6b105f7e57b 100644 --- a/lib/modules/platform/forgejo/schema.spec.ts +++ b/lib/modules/platform/forgejo/schema.spec.ts @@ -1,7 +1,7 @@ -import { ContentsListResponseSchema } from './schema'; +import { ContentsListResponse } from './schema'; describe('modules/platform/forgejo/schema', () => { it('ContentsResponseSchema', () => { - expect(ContentsListResponseSchema.parse([])).toBeEmptyArray(); + expect(ContentsListResponse.parse([])).toBeEmptyArray(); }); }); diff --git a/lib/modules/platform/forgejo/schema.ts b/lib/modules/platform/forgejo/schema.ts index 13d1a60d4db..9582a5b7daa 100644 --- a/lib/modules/platform/forgejo/schema.ts +++ b/lib/modules/platform/forgejo/schema.ts @@ -1,12 +1,12 @@ import { z } from 'zod'; -export const ContentsResponseSchema = z.object({ +export const ContentsResponse = z.object({ name: z.string(), path: z.string(), type: z.union([z.literal('file'), z.literal('dir')]), content: z.string().nullable(), }); -export type ContentsResponse = z.infer; +export type ContentsResponse = z.infer; -export const ContentsListResponseSchema = z.array(ContentsResponseSchema); +export const ContentsListResponse = z.array(ContentsResponse); diff --git a/lib/modules/platform/gitea/schema.spec.ts b/lib/modules/platform/gitea/schema.spec.ts index 0a1b2564c4b..7dc6cd218e2 100644 --- a/lib/modules/platform/gitea/schema.spec.ts +++ b/lib/modules/platform/gitea/schema.spec.ts @@ -1,7 +1,7 @@ -import { ContentsListResponseSchema } from './schema'; +import { ContentsListResponse } from './schema'; describe('modules/platform/gitea/schema', () => { it('ContentsResponseSchema', () => { - expect(ContentsListResponseSchema.parse([])).toBeEmptyArray(); + expect(ContentsListResponse.parse([])).toBeEmptyArray(); }); }); diff --git a/lib/modules/platform/gitea/schema.ts b/lib/modules/platform/gitea/schema.ts index 13d1a60d4db..9582a5b7daa 100644 --- a/lib/modules/platform/gitea/schema.ts +++ b/lib/modules/platform/gitea/schema.ts @@ -1,12 +1,12 @@ import { z } from 'zod'; -export const ContentsResponseSchema = z.object({ +export const ContentsResponse = z.object({ name: z.string(), path: z.string(), type: z.union([z.literal('file'), z.literal('dir')]), content: z.string().nullable(), }); -export type ContentsResponse = z.infer; +export type ContentsResponse = z.infer; -export const ContentsListResponseSchema = z.array(ContentsResponseSchema); +export const ContentsListResponse = z.array(ContentsResponse); diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index b2409a77b2c..8d83f9dba77 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -78,7 +78,7 @@ import { import { GithubIssueCache, GithubIssue as Issue } from './issue'; import { massageMarkdownLinks } from './massage-markdown-links'; import { getPrCache, updatePrCache } from './pr'; -import { VulnerabilityAlertSchema } from './schema'; +import { GithubVulnerabilityAlert } from './schema'; import type { BranchProtection, CombinedBranchStatus, @@ -1977,7 +1977,7 @@ export async function getVulnerabilityAlerts(): Promise { headers: { accept: 'application/vnd.github+json' }, cacheProvider: repoCacheProvider, }, - VulnerabilityAlertSchema, + GithubVulnerabilityAlert, ) ).body; } catch (err) /* v8 ignore start */ { diff --git a/lib/modules/platform/github/schema.ts b/lib/modules/platform/github/schema.ts index 68f0ae42209..c1c8ac57e0e 100644 --- a/lib/modules/platform/github/schema.ts +++ b/lib/modules/platform/github/schema.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { logger } from '../../../logger'; import { LooseArray } from '../../../util/schema-utils'; -const PackageSchema = z.object({ +const Package = z.object({ ecosystem: z.union([ z.literal('maven'), z.literal('npm'), @@ -16,15 +16,15 @@ const PackageSchema = z.object({ name: z.string(), }); -const SecurityVulnerabilitySchema = z +const SecurityVulnerability = z .object({ first_patched_version: z.object({ identifier: z.string() }).nullish(), - package: PackageSchema, + package: Package, vulnerable_version_range: z.string(), }) .nullable(); -const SecurityAdvisorySchema = z.object({ +const SecurityAdvisory = z.object({ description: z.string(), identifiers: z.array( z.object({ @@ -35,11 +35,11 @@ const SecurityAdvisorySchema = z.object({ references: z.array(z.object({ url: z.string() })).optional(), }); -export const VulnerabilityAlertSchema = LooseArray( +export const GithubVulnerabilityAlert = LooseArray( z.object({ dismissed_reason: z.string().nullish(), - security_advisory: SecurityAdvisorySchema, - security_vulnerability: SecurityVulnerabilitySchema, + security_advisory: SecurityAdvisory, + security_vulnerability: SecurityVulnerability, dependency: z.object({ manifest_path: z.string(), }), @@ -55,6 +55,7 @@ export const VulnerabilityAlertSchema = LooseArray( /* v8 ignore stop */ }, ); +export type GithubVulnerabilityAlert = z.infer; // https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content const GithubResponseMetadata = z.object({ diff --git a/lib/util/cache/repository/http-cache.ts b/lib/util/cache/repository/http-cache.ts index 5ee43dd8ff5..bdd84514b1b 100644 --- a/lib/util/cache/repository/http-cache.ts +++ b/lib/util/cache/repository/http-cache.ts @@ -1,7 +1,7 @@ import { DateTime } from 'luxon'; import { GlobalConfig } from '../../../config/global'; import { logger } from '../../../logger'; -import { HttpCacheSchema } from '../../http/cache/schema'; +import { HttpCache } from '../../http/cache/schema'; import type { RepoCacheData } from './types'; export function cleanupHttpCache(cacheData: RepoCacheData): void { @@ -20,7 +20,7 @@ export function cleanupHttpCache(cacheData: RepoCacheData): void { const now = DateTime.now(); for (const [url, item] of Object.entries(httpCache)) { - const parsed = HttpCacheSchema.safeParse(item); + const parsed = HttpCache.safeParse(item); if (parsed.success && parsed.data) { const item = parsed.data; const expiry = DateTime.fromISO(item.timestamp).plus({ days: ttlDays }); diff --git a/lib/util/emoji.ts b/lib/util/emoji.ts index f6e23ce9b97..5be60be2005 100644 --- a/lib/util/emoji.ts +++ b/lib/util/emoji.ts @@ -20,13 +20,13 @@ let mappingsInitialized = false; const shortCodesByHex = new Map(); const hexCodesByShort = new Map(); -const EmojiShortcodesSchema = Json.pipe( +const EmojiShortcodes = Json.pipe( z.record( z.string(), z.union([z.string().transform((val) => [val]), z.array(z.string())]), ), ); -type EmojiShortcodeMapping = z.infer; +type EmojiShortcodeMapping = z.infer; const patchedEmojis: EmojiShortcodeMapping = { '26A0-FE0F': ['warning'], // Colorful warning (⚠️) instead of black and white (⚠) @@ -51,7 +51,7 @@ function lazyInitMappings(): void { 'node_modules/emojibase-data/en/shortcodes/github.json', ); - Result.parse(githubShortcodes, EmojiShortcodesSchema) + Result.parse(githubShortcodes, EmojiShortcodes) .onValue((data) => { initMapping(data); initMapping(patchedEmojis); diff --git a/lib/util/http/cache/abstract-http-cache-provider.ts b/lib/util/http/cache/abstract-http-cache-provider.ts index e35778f91d1..46550301836 100644 --- a/lib/util/http/cache/abstract-http-cache-provider.ts +++ b/lib/util/http/cache/abstract-http-cache-provider.ts @@ -2,7 +2,7 @@ import { logger } from '../../../logger'; import { HttpCacheStats } from '../../stats'; import type { GotOptions, HttpResponse } from '../types'; import { copyResponse } from '../util'; -import { type HttpCache, HttpCacheSchema } from './schema'; +import { HttpCache } from './schema'; import type { HttpCacheProvider } from './types'; export abstract class AbstractHttpCacheProvider implements HttpCacheProvider { @@ -11,7 +11,7 @@ export abstract class AbstractHttpCacheProvider implements HttpCacheProvider { async get(url: string): Promise { const cache = await this.load(url); - const httpCache = HttpCacheSchema.parse(cache); + const httpCache = HttpCache.parse(cache); if (!httpCache) { return null; } @@ -59,7 +59,7 @@ export abstract class AbstractHttpCacheProvider implements HttpCacheProvider { const httpResponse = copyResponse(resp, true); const timestamp = new Date().toISOString(); - const newHttpCache = HttpCacheSchema.parse({ + const newHttpCache = HttpCache.parse({ etag, lastModified, httpResponse, diff --git a/lib/util/http/cache/schema.ts b/lib/util/http/cache/schema.ts index fe69554ddc4..1c0df89713b 100644 --- a/lib/util/http/cache/schema.ts +++ b/lib/util/http/cache/schema.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -export const HttpCacheSchema = z +export const HttpCache = z .object({ etag: z.string().optional(), lastModified: z.string().optional(), @@ -9,4 +9,4 @@ export const HttpCacheSchema = z }) .nullable() .catch(null); -export type HttpCache = z.infer; +export type HttpCache = z.infer; diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts index c7cfcbf9d68..e1aed1c65b0 100644 --- a/lib/util/http/index.spec.ts +++ b/lib/util/http/index.spec.ts @@ -332,7 +332,7 @@ describe('util/http/index', () => { }); describe('Schema support', () => { - const SomeSchema = z + const Some = z .object({ x: z.number(), y: z.number() }) .transform(({ x, y }) => `${x} + ${y} = ${x + y}`); @@ -403,7 +403,7 @@ describe('util/http/index', () => { it('parses yaml with schema validation', async () => { httpMock.scope(baseUrl).get('/').reply(200, 'x: 2\ny: 2'); - const res = await http.getYaml('http://renovate.com', SomeSchema); + const res = await http.getYaml('http://renovate.com', Some); expect(res.body).toBe('2 + 2 = 4'); }); @@ -417,7 +417,7 @@ describe('util/http/index', () => { const res = await http.getYaml( 'http://renovate.com', { headers: { custom: 'header' } }, - SomeSchema, + Some, ); expect(res.body).toBe('2 + 2 = 4'); }); @@ -425,17 +425,17 @@ describe('util/http/index', () => { it('throws on schema validation failure', async () => { httpMock.scope(baseUrl).get('/').reply(200, 'foo: bar'); - await expect( - http.getYaml('http://renovate.com', SomeSchema), - ).rejects.toThrow(z.ZodError); + await expect(http.getYaml('http://renovate.com', Some)).rejects.toThrow( + z.ZodError, + ); }); it('throws on invalid yaml', async () => { httpMock.scope(baseUrl).get('/').reply(200, '!@#$%^'); - await expect( - http.getYaml('http://renovate.com', SomeSchema), - ).rejects.toThrow('Failed to parse YAML file'); + await expect(http.getYaml('http://renovate.com', Some)).rejects.toThrow( + 'Failed to parse YAML file', + ); }); }); @@ -444,7 +444,7 @@ describe('util/http/index', () => { httpMock.scope('http://example.com').get('/').reply(200, 'x: 2\ny: 2'); const { val, err } = await http - .getYamlSafe('http://example.com', SomeSchema) + .getYamlSafe('http://example.com', Some) .unwrap(); expect(val).toBe('2 + 2 = 4'); @@ -458,7 +458,7 @@ describe('util/http/index', () => { .reply(200, 'x: "2"\ny: "2"'); const { val, err } = await http - .getYamlSafe('http://example.com', SomeSchema) + .getYamlSafe('http://example.com', Some) .unwrap(); expect(val).toBeUndefined(); @@ -469,7 +469,7 @@ describe('util/http/index', () => { httpMock.scope('http://example.com').get('/').reply(200, '!@#$%^'); const { val, err } = await http - .getYamlSafe('http://example.com', SomeSchema) + .getYamlSafe('http://example.com', Some) .unwrap(); expect(val).toBeUndefined(); @@ -483,7 +483,7 @@ describe('util/http/index', () => { .replyWithError('network error'); const { val, err } = await http - .getYamlSafe('http://example.com', SomeSchema) + .getYamlSafe('http://example.com', Some) .unwrap(); expect(val).toBeUndefined(); @@ -501,7 +501,7 @@ describe('util/http/index', () => { .getYamlSafe( 'http://example.com', { headers: { custom: 'header' } }, - SomeSchema, + Some, ) .unwrap(); @@ -524,7 +524,7 @@ describe('util/http/index', () => { const { body }: HttpResponse = await http.getJson( 'http://renovate.com', { headers: { accept: 'application/json' } }, - SomeSchema, + Some, ); expect(body).toBe('2 + 2 = 4'); @@ -541,9 +541,9 @@ describe('util/http/index', () => { .get('/') .reply(200, JSON.stringify({ foo: 'bar' })); - await expect( - http.getJson('http://renovate.com', SomeSchema), - ).rejects.toThrow(z.ZodError); + await expect(http.getJson('http://renovate.com', Some)).rejects.toThrow( + z.ZodError, + ); }); }); @@ -555,7 +555,7 @@ describe('util/http/index', () => { .reply(200, JSON.stringify({ x: 2, y: 2 })); const { val, err } = await http - .getJsonSafe('http://example.com', SomeSchema) + .getJsonSafe('http://example.com', Some) .unwrap(); expect(val).toBe('2 + 2 = 4'); @@ -569,7 +569,7 @@ describe('util/http/index', () => { .reply(200, JSON.stringify({ x: '2', y: '2' })); const { val, err } = await http - .getJsonSafe('http://example.com', SomeSchema) + .getJsonSafe('http://example.com', Some) .unwrap(); expect(val).toBeUndefined(); @@ -580,7 +580,7 @@ describe('util/http/index', () => { httpMock.scope('http://example.com').get('/').replyWithError('unknown'); const { val, err } = await http - .getJsonSafe('http://example.com', SomeSchema) + .getJsonSafe('http://example.com', Some) .unwrap(); expect(val).toBeUndefined(); @@ -597,7 +597,7 @@ describe('util/http/index', () => { const { body }: HttpResponse = await http.postJson( 'http://renovate.com', - SomeSchema, + Some, ); expect(body).toBe('2 + 2 = 4'); @@ -611,7 +611,7 @@ describe('util/http/index', () => { .reply(200, JSON.stringify({ foo: 'bar' })); await expect( - http.postJson('http://renovate.com', SomeSchema), + http.postJson('http://renovate.com', Some), ).rejects.toThrow(z.ZodError); }); }); @@ -650,14 +650,14 @@ describe('util/http/index', () => { }); describe('getToml', () => { - const SomeSchema = z + const Some = z .object({ x: z.number(), y: z.number() }) .transform(({ x, y }) => `${x} + ${y} = ${x + y}`); it('parses toml with schema validation', async () => { httpMock.scope(baseUrl).get('/').reply(200, 'x = 2\ny = 2'); - const res = await http.getToml('http://renovate.com', SomeSchema); + const res = await http.getToml('http://renovate.com', Some); expect(res.body).toBe('2 + 2 = 4'); }); @@ -675,7 +675,7 @@ describe('util/http/index', () => { const res = await http.getToml( 'http://renovate.com', { headers: { custom: 'header' } }, - SomeSchema, + Some, ); expect(res.body).toBe('2 + 2 = 4'); }); @@ -690,9 +690,9 @@ describe('util/http/index', () => { .get('/') .reply(200, 'foo = "bar"'); - await expect( - http.getToml('http://renovate.com', SomeSchema), - ).rejects.toThrow(z.ZodError); + await expect(http.getToml('http://renovate.com', Some)).rejects.toThrow( + z.ZodError, + ); }); it('throws on invalid toml', async () => { diff --git a/lib/workers/repository/update/pr/changelog/bitbucket/index.ts b/lib/workers/repository/update/pr/changelog/bitbucket/index.ts index e9e43bc3a03..a93a95f8d73 100644 --- a/lib/workers/repository/update/pr/changelog/bitbucket/index.ts +++ b/lib/workers/repository/update/pr/changelog/bitbucket/index.ts @@ -2,7 +2,7 @@ import path from 'node:path'; import is from '@sindresorhus/is'; import changelogFilenameRegex from 'changelog-filename-regex'; import { logger } from '../../../../../../logger'; -import { PagedSourceResultsSchema } from '../../../../../../modules/platform/bitbucket/schema'; +import { PagedSourceResults } from '../../../../../../modules/platform/bitbucket/schema'; import { BitbucketHttp } from '../../../../../../util/http/bitbucket'; import { joinUrlParts } from '../../../../../../util/url'; import { compareChangelogFilePath } from '../common'; @@ -37,7 +37,7 @@ export async function getReleaseNotesMd( { paginate: true, }, - PagedSourceResultsSchema, + PagedSourceResults, ) ).body.values; diff --git a/lib/workers/repository/update/pr/changelog/forgejo/index.ts b/lib/workers/repository/update/pr/changelog/forgejo/index.ts index 10f15a103f0..83b0895b1ec 100644 --- a/lib/workers/repository/update/pr/changelog/forgejo/index.ts +++ b/lib/workers/repository/update/pr/changelog/forgejo/index.ts @@ -1,10 +1,9 @@ import changelogFilenameRegex from 'changelog-filename-regex'; import { logger } from '../../../../../../logger'; -import { ReleasesSchema } from '../../../../../../modules/datasource/forgejo-releases/schema'; -import type { ContentsResponse } from '../../../../../../modules/platform/gitea/schema'; +import { Releases } from '../../../../../../modules/datasource/forgejo-releases/schema'; import { - ContentsListResponseSchema, - ContentsResponseSchema, + ContentsListResponse, + ContentsResponse, } from '../../../../../../modules/platform/gitea/schema'; import { ForgejoHttp } from '../../../../../../util/http/forgejo'; import { fromBase64 } from '../../../../../../util/string'; @@ -34,7 +33,7 @@ export async function getReleaseNotesMd( { paginate: false, // no pagination yet }, - ContentsListResponseSchema, + ContentsListResponse, ) ).body; const allFiles = tree.filter((f) => f.type === 'file'); @@ -59,7 +58,7 @@ export async function getReleaseNotesMd( const fileRes = await http.getJson( `${apiPrefix}/${changelogFile}`, - ContentsResponseSchema, + ContentsResponse, ); // istanbul ignore if: should never happen if (!fileRes.body.content) { @@ -83,7 +82,7 @@ export async function getReleaseList( { paginate: true, }, - ReleasesSchema, + Releases, ); return res.body.map((release) => ({ url: `${project.baseUrl}${project.repository}/releases/tag/${release.tag_name}`, diff --git a/lib/workers/repository/update/pr/changelog/gitea/index.ts b/lib/workers/repository/update/pr/changelog/gitea/index.ts index 141b1872bd8..2ec5e766390 100644 --- a/lib/workers/repository/update/pr/changelog/gitea/index.ts +++ b/lib/workers/repository/update/pr/changelog/gitea/index.ts @@ -1,10 +1,9 @@ import changelogFilenameRegex from 'changelog-filename-regex'; import { logger } from '../../../../../../logger'; -import { ReleasesSchema } from '../../../../../../modules/datasource/gitea-releases/schema'; -import type { ContentsResponse } from '../../../../../../modules/platform/gitea/schema'; +import { Releases } from '../../../../../../modules/datasource/gitea-releases/schema'; import { - ContentsListResponseSchema, - ContentsResponseSchema, + ContentsListResponse, + ContentsResponse, } from '../../../../../../modules/platform/gitea/schema'; import { GiteaHttp } from '../../../../../../util/http/gitea'; import { fromBase64 } from '../../../../../../util/string'; @@ -34,7 +33,7 @@ export async function getReleaseNotesMd( { paginate: false, // no pagination yet }, - ContentsListResponseSchema, + ContentsListResponse, ) ).body; const allFiles = tree.filter((f) => f.type === 'file'); @@ -59,7 +58,7 @@ export async function getReleaseNotesMd( const fileRes = await http.getJson( `${apiPrefix}/${changelogFile}`, - ContentsResponseSchema, + ContentsResponse, ); // istanbul ignore if: should never happen if (!fileRes.body.content) { @@ -83,7 +82,7 @@ export async function getReleaseList( { paginate: true, }, - ReleasesSchema, + Releases, ); return res.body.map((release) => ({ url: `${project.baseUrl}${project.repository}/releases/tag/${release.tag_name}`, diff --git a/lib/workers/repository/update/pr/code-owners.spec.ts b/lib/workers/repository/update/pr/code-owners.spec.ts index 26a55157e13..fc19bf289ce 100644 --- a/lib/workers/repository/update/pr/code-owners.spec.ts +++ b/lib/workers/repository/update/pr/code-owners.spec.ts @@ -1,6 +1,7 @@ import { codeBlock } from 'common-tags'; import { mock } from 'vitest-mock-extended'; import type { Pr } from '../../../../modules/platform'; +import * as bitbucketserver from '../../../../modules/platform/bitbucket-server'; import * as gitlab from '../../../../modules/platform/gitlab'; import type { LongCommitSha } from '../../../../util/git/types'; import { codeOwnersForPr } from './code-owners'; @@ -285,6 +286,82 @@ describe('workers/repository/update/pr/code-owners', () => { }); }); + describe('Bitbucket Server CODEOWNERS integration', () => { + beforeAll(() => { + Object.defineProperty(platform, 'extractRulesFromCodeOwnersLines', { + value: bitbucketserver.extractRulesFromCodeOwnersLines, + writable: true, + }); + }); + + it('returns code owners for matching file using escaped spaces', async () => { + fs.readLocalFile.mockResolvedValueOnce( + String.raw`src/** @Jane\\ Doe @john@example.com`, + ); + git.getBranchFiles.mockResolvedValueOnce(['src/index.ts']); + + const codeOwners = await codeOwnersForPr(pr); + + expect(codeOwners).toEqual(['@Jane Doe', '@john@example.com']); + }); + + it('returns code owners from reviewer group with random selection', async () => { + fs.readLocalFile.mockResolvedValueOnce( + 'docs/** @reviewer-group/content-designers:random', + ); + git.getBranchFiles.mockResolvedValueOnce(['docs/readme.md']); + + const codeOwners = await codeOwnersForPr(pr); + + // Since we don't simulate the actual group resolution, this will just include the literal string + expect(codeOwners).toEqual([ + '@reviewer-group/content-designers:random', + ]); + }); + + it('does not return owners when an empty rule overrides a broader rule', async () => { + fs.readLocalFile.mockResolvedValueOnce( + [ + 'docs/images/**', // empty rule + 'docs/** @content-designer', + ].join('\n'), + ); + git.getBranchFiles.mockResolvedValueOnce(['docs/images/logo.png']); + + const codeOwners = await codeOwnersForPr(pr); + + expect(codeOwners).toEqual([]); + }); + + it('matches the most specific rule (bottom takes precedence)', async () => { + fs.readLocalFile.mockResolvedValueOnce( + [ + 'docs/** @team1', + 'docs/backend/** @team2', // higher precedence + ].join('\n'), + ); + git.getBranchFiles.mockResolvedValueOnce(['docs/backend/service.md']); + + const codeOwners = await codeOwnersForPr(pr); + + expect(codeOwners).toEqual(['@team2', '@team1']); + }); + + it('handles multiple owners with mix of usernames and groups', async () => { + fs.readLocalFile.mockResolvedValueOnce( + 'docs/** @Alice @reviewer-group/devs:random(2)', + ); + git.getBranchFiles.mockResolvedValueOnce(['docs/manual.md']); + + const codeOwners = await codeOwnersForPr(pr); + + expect(codeOwners).toEqual([ + '@Alice', + '@reviewer-group/devs:random(2)', + ]); + }); + }); + it.fails('does not parse Gitea regex as Gitlab sections', async () => { Object.defineProperty(platform, 'extractRulesFromCodeOwnersLines', { value: undefined, diff --git a/package.json b/package.json index 2b3fa6cd39f..8435ea16c70 100644 --- a/package.json +++ b/package.json @@ -347,7 +347,7 @@ "typescript": "5.8.3", "typescript-eslint": "8.39.0", "unified": "11.0.5", - "vite": "7.0.6", + "vite": "7.1.0", "vite-tsconfig-paths": "5.1.4", "vitest": "3.2.4", "vitest-mock-extended": "3.1.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b12cd1fd37..c4894918701 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -595,11 +595,11 @@ importers: specifier: 11.0.5 version: 11.0.5 vite: - specifier: 7.0.6 - version: 7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) + specifier: 7.1.0 + version: 7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) vite-tsconfig-paths: specifier: 5.1.4 - version: 5.1.4(typescript@5.8.3)(vite@7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1)) + version: 5.1.4(typescript@5.8.3)(vite@7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1)) vitest: specifier: 3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) @@ -6266,8 +6266,8 @@ packages: vite: optional: true - vite@7.0.6: - resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + vite@7.1.0: + resolution: {integrity: sha512-3jdAy3NhBJYsa/lCFcnRfbK4kNkO/bhijFCnv5ByUQk/eekYagoV2yQSISUrhpV+5JiY5hmwOh7jNnQ68dFMuQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -9096,13 +9096,13 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(vite@7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) + vite: 7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -13436,7 +13436,7 @@ snapshots: debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) + vite: 7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -13451,18 +13451,18 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1)): dependencies: debug: 4.4.1 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.8.3) optionalDependencies: - vite: 7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) + vite: 7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) transitivePeerDependencies: - supports-color - typescript - vite@7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1): + vite@7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1): dependencies: esbuild: 0.25.8 fdir: 6.4.6(picomatch@4.0.3) @@ -13486,7 +13486,7 @@ snapshots: dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -13504,7 +13504,7 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.6(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) + vite: 7.1.0(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) vite-node: 3.2.4(@types/node@22.17.0)(tsx@4.20.3)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: diff --git a/renovate.json b/renovate.json index e364cbeee33..174d8e83b8c 100644 --- a/renovate.json +++ b/renovate.json @@ -79,6 +79,12 @@ "matchPackageNames": ["*"], "matchBaseBranches": ["next"], "dependencyDashboardApproval": true + }, + { + "description": "require approval on next branch lockfile maintenance", + "matchBaseBranches": ["next"], + "matchUpdateTypes": ["lockFileMaintenance"], + "dependencyDashboardApproval": true } ], "customManagers": [ diff --git a/test/other/validate-schemas.spec.ts b/test/other/validate-schemas.spec.ts index cfe8d8c28a8..110699983a5 100644 --- a/test/other/validate-schemas.spec.ts +++ b/test/other/validate-schemas.spec.ts @@ -19,11 +19,11 @@ describe('other/validate-schemas', () => { for (const schemaFile of schemaFiles) { const correspondingDataFileName = schemaFile.replace('-schema', ''); - const schemaName = `${schemaFile + const schemaName = correspondingDataFileName .replace('.json', '') .split('-') .map(capitalize) - .join('')}` as keyof typeof Schemas; + .join('') as keyof typeof Schemas; schemasAndJsonFiles.push({ schemaName, dataFileName: correspondingDataFileName, diff --git a/tools/docs/github-query-items.ts b/tools/docs/github-query-items.ts index 4c91daab168..c3005824273 100644 --- a/tools/docs/github-query-items.ts +++ b/tools/docs/github-query-items.ts @@ -29,7 +29,7 @@ export interface Items { features: ItemsEntity[]; } -const GhOutputSchema = z.array( +const GhOutput = z.array( z.object({ url: z.string(), title: z.string(), @@ -47,7 +47,7 @@ async function getIssuesByIssueType( ): Promise { const command = `gh issue list --json "title,number,url,labels" --search "type:${issueType}" --limit 1000`; const execRes = await exec(command); - const res = GhOutputSchema.safeParse(JSON.parse(execRes.stdout)); + const res = GhOutput.safeParse(JSON.parse(execRes.stdout)); if (res.error) { throw res.error; } diff --git a/tools/schemas/schema.ts b/tools/schemas/schema.ts index 74f8010a3fe..74862cb75ce 100644 --- a/tools/schemas/schema.ts +++ b/tools/schemas/schema.ts @@ -1,17 +1,14 @@ import { z } from 'zod'; -const UrlSchema = z.record( - z.string(), - z.union([z.string(), z.array(z.string())]), -); +const Url = z.record(z.string(), z.union([z.string(), z.array(z.string())])); -export const MonorepoSchema = z.object({ - repoGroups: UrlSchema, - orgGroups: UrlSchema, - patternGroups: UrlSchema, +export const Monorepo = z.object({ + repoGroups: Url, + orgGroups: Url, + patternGroups: Url, }); -const PackageRuleSchema = z.object({ +const PackageRule = z.object({ matchCurrentVersion: z.string().optional(), matchDatasources: z.array(z.string()), matchPackageNames: z.array(z.string()), @@ -22,10 +19,10 @@ const PackageRuleSchema = z.object({ replacementVersionTemplate: z.string().optional(), }); -const RuleSetSchema = z.object({ +const RuleSet = z.object({ description: z.string(), packageRules: z - .array(PackageRuleSchema) + .array(PackageRule) .min(1) .refine( (rules) => @@ -41,32 +38,32 @@ const RuleSetSchema = z.object({ ), }); -const AllSchema = z.object({ +const All = z.object({ description: z.string(), extends: z.array(z.string()), ignoreDeps: z.array(z.string()).optional(), }); -export const ReplacementsSchema = z +export const Replacements = z .object({ $schema: z.string(), - all: AllSchema, + all: All, }) - .catchall(RuleSetSchema); + .catchall(RuleSet); -export const ChangelogUrlsSchema = z +export const ChangelogUrls = z .object({ $schema: z.string(), }) .catchall(z.record(z.string(), z.string().url())); -export const SourceUrlsSchema = z +export const SourceUrls = z .object({ $schema: z.string(), }) .catchall(z.record(z.string(), z.string().url())); -export const AbandonmentsSchema = z +export const Abandonments = z .object({ $schema: z.string(), }) diff --git a/tools/static-data/generate-lambda-node-schedule.mjs b/tools/static-data/generate-lambda-node-schedule.mjs index 0ce684830fe..7a503315d40 100644 --- a/tools/static-data/generate-lambda-node-schedule.mjs +++ b/tools/static-data/generate-lambda-node-schedule.mjs @@ -1,7 +1,7 @@ import { z } from 'zod'; import { updateJsonFile } from './utils.mjs'; -const RuntimesSchema = z.object({ +const Runtimes = z.object({ cycle: z.string().describe('The ID of the Runtime'), support: z .union([z.boolean(), z.string()]) @@ -9,7 +9,7 @@ const RuntimesSchema = z.object({ 'Either `true` if in support, or a string denoting when support for this Runtime will end. 0.10.x is a sole exception which has a value of `false` and will be filtered out', ), }); -const RuntimesArraySchema = z.array(RuntimesSchema); +const RuntimesArray = z.array(Runtimes); const lambdaDataUrl = 'https://endoflife.date/api/aws-lambda.json'; @@ -22,10 +22,10 @@ await (async () => { process.exit(1); } - return RuntimesArraySchema.parseAsync(await response.json()); + return RuntimesArray.parseAsync(await response.json()); }); - /** @type {Record>} */ + /** @type {Record>} */ const nodeRuntimes = {}; for (let lambda of lambdas) { if (!lambda.cycle.startsWith('nodejs')) {