Files
Sebastian Poxhofer 44160755f9 feat(platform): validate gitea responses with schemas and share the implementation with forgejo (#45825)
* refactor(platform): share one implementation between gitea and forgejo

The `forgejo` platform was copied from `gitea` in #37075 and the two have
drifted since: fixes landed on one side only (zod validation #43900, empty
email #44068, null issues #44048, team reviewers #39533, merge-style fallback
#37768 vs #38040), and lint sweeps had to be applied to both copies by hand.

Both platforms now build on `lib/modules/platform/gitea-common/`, which holds
the zod schemas, API helpers, utils, PR cache and the platform object itself.
`createPlatform()` closes over its own state, so `gitea` and `forgejo` keep
separate config, caches and host types. `gitea/index.ts` and `forgejo/index.ts`
are thin wrappers that supply the platform id, default endpoint, http client
and the version gates that genuinely differ.

Reconciliations, forgejo's behaviour adopted for gitea:

- Responses are validated with zod instead of `getJsonUnchecked` casts. Gitea
  therefore tolerates empty emails, null PRs in list responses and lenient
  repo/issue payloads the same way Forgejo already did.
- `getRepoContents` returns a discriminated union, so `getRawFile` returns null
  for dir/symlink/submodule entries instead of reading a missing field. The
  gitea preset resolver now throws PRESET_INVALID for non-file entries, like
  the forgejo one.
- An unknown `default_merge_style` (e.g. `manually-merged`) is caught by the
  schema and skipped, instead of aborting the repository with
  REPOSITORY_BLOCKED from `isAllowed()`.
- `initRepo()` resolves whether the repo owner is an org and only fetches org
  labels when it is, adding one `/orgs/{owner}` request and an `orgs` entry in
  the gitea repository cache.
- Reviewers prefixed with `team:` are sent as `team_reviewers`. Gitea keeps its
  `v1.14.0` guard for requesting reviewers at all.
- `getBranchStatusCheck()` maps unknown commit status values through the
  schema's `unknown` fallback rather than warning and returning yellow.

Gitea's behaviour is kept where it is deliberately different: the Forgejo
instance detection and its deprecation notice, the `1.24.0` (or `10.0.0` for a
detected Forgejo) native automerge gate, and the reviewer version guard are
passed in as options.

Two shared log messages became static so they can be grouped in metrics:
`{platform}-native automerge: fail` is now `Platform-native automerge: fail`
and `{Platform} getRepos() error` is now `Error auto-discovering repositories`,
both with the platform id in the metadata. The dead `defaults.hostType` field
is gone.

* refactor(platform): keep the shared gitea implementation under gitea/

Drop the `gitea-common/` folder. The zod schemas, API helpers, utils, PR
cache and `createPlatform()` live in `lib/modules/platform/gitea/` under
their original file names, and `forgejo/` imports them from there.

The preset resolvers pass `giteaHttp` and `forgejoHttp` to
`getRepoContents()` directly, which removes the two bound wrappers. The
gitea changelog source uses the shared `RepoContents` type like the
forgejo one already does.

* refactor(http): derive ForgejoHttp from GiteaHttp

The two clients were identical apart from the default `hostType`.
`ForgejoHttp` now extends `GiteaHttp` and only keeps its own base url,
so the pagination logic exists once. The platform helpers accept
`GiteaHttp` directly instead of the `HttpBase` alias.

Also covers the `updatePr()` branch where the updated pull request
cannot be converted and therefore is not cached.

* refactor(platform): create the gitea and forgejo http clients in index.ts

`forgejo-helper.ts` only held the `ForgejoHttp` instance, so it is gone
and `forgejo/index.ts` creates the client next to the platform. Gitea
does the same for symmetry, which leaves `gitea-helper.ts` with only the
shared API helpers.

* refactor(platform): fetch gitea labels and issues with await

`getLabelList()` and `getIssueList()` still memoize the in-flight
promise, but build it from small async functions instead of `.then()`
and `.catch()` chains.

* refactor(platform): keep declaration order of gitea/index.ts

Order the module the way it was before `createPlatform()` so the diff
against main aligns: platform id and http client after the repo config
interface, pure helpers before the factory, and `maxBodyLength()` after
the platform object.

* refactor(platform): move gitea label fetching to labels.ts

`fetchLabelList()` takes the http client and the repository details
instead of closing over the platform state, and gets its own spec. The
factory only keeps the memoizing `getLabelList()`.

* refactor(platform): move gitea label lookup to labels.ts

* refactor(platform): create the gitea pr cache per platform

`GiteaPrCache` is now created once per platform with its http client and
platform id, and `initRepo()` records the repository. The per-repository
state moves to a private `RepoPrCache` which is bound to the repository
cache on first use and reused until the next `initRepo()`, instead of
being rebuilt on every lookup.
2026-09-14 08:45:04 +00:00

24 lines
661 B
TypeScript

import { regEx } from '../regex.ts';
import { GiteaHttp } from './gitea.ts';
import type { HttpOptions } from './types.ts';
let baseUrl: string;
export function setBaseUrl(newBaseUrl: string): void {
baseUrl = newBaseUrl.replace(regEx(/\/*$/), '/'); // TODO #12875
}
/**
* Forgejo speaks the Gitea API, so the client only differs in its default
* `hostType` and in the base url, which is kept separate from Gitea's.
*/
export class ForgejoHttp extends GiteaHttp {
protected override get baseUrl(): string | undefined {
return baseUrl;
}
constructor(hostType?: string, options?: HttpOptions) {
super(hostType ?? 'forgejo', options);
}
}