@shipfox/api-integration-gitea 10.2.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 18 files with swc (327.43ms)
2
+ Successfully compiled: 18 files with swc (269.62ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @shipfox/api-integration-gitea
2
2
 
3
+ ## 12.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 54c820e: Capture the actor that caused a source-control event on the normalized trigger reference. `TriggerReference` gains a required `actor`, resolved from the webhook sender by the GitHub and Gitea providers and null for payloads that name none.
8
+
9
+ ### Patch Changes
10
+
11
+ - 24ef475: Adds provider-normalized repository, ref, and commit extraction for source-control trigger payloads.
12
+ - Updated dependencies [f78740d]
13
+ - Updated dependencies [24ef475]
14
+ - Updated dependencies [f13e8bb]
15
+ - Updated dependencies [869a792]
16
+ - Updated dependencies [54c820e]
17
+ - @shipfox/node-fastify@0.4.1
18
+ - @shipfox/api-integration-spi@1.0.0
19
+ - @shipfox/node-postgres@0.5.0
20
+ - @shipfox/api-auth-context@12.0.0
21
+ - @shipfox/api-integration-gitea-dto@12.0.0
22
+ - @shipfox/node-drizzle@0.3.5
23
+
24
+ ## 11.0.0
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies [25158c8]
29
+ - @shipfox/api-auth-context@11.0.0
30
+
3
31
  ## 10.2.0
4
32
 
5
33
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import { type CheckoutSpec, type CreateCheckoutSpecInput, type FetchFileInput, type FilePage, type FileSnapshot, type IntegrationConnection, type ListFilesInput, type ListRepositoriesInput, type RepositoryPage, type RepositorySnapshot, type ResolveRepositoryInput, type SourceControlProvider } from '@shipfox/api-integration-spi';
1
+ import { type CheckoutSpec, type CreateCheckoutSpecInput, type FetchFileInput, type FilePage, type FileSnapshot, type IntegrationConnection, type ListFilesInput, type ListRepositoriesInput, type RepositoryPage, type RepositorySnapshot, type ResolveRepositoryInput, type SourceControlProvider, type TriggerReference } from '@shipfox/api-integration-spi';
2
2
  import type { GiteaApiClient } from '#api/client.js';
3
3
  type GiteaIntegrationConnection = IntegrationConnection<'gitea'>;
4
4
  export declare class GiteaSourceControlProvider implements SourceControlProvider<GiteaIntegrationConnection> {
@@ -8,6 +8,7 @@ export declare class GiteaSourceControlProvider implements SourceControlProvider
8
8
  resolveRepository(input: ResolveRepositoryInput<GiteaIntegrationConnection>): Promise<RepositorySnapshot>;
9
9
  listFiles(input: ListFilesInput<GiteaIntegrationConnection>): Promise<FilePage>;
10
10
  fetchFile(input: FetchFileInput<GiteaIntegrationConnection>): Promise<FileSnapshot>;
11
+ resolveTriggerReference(payload: unknown): TriggerReference | null;
11
12
  createCheckoutSpec(input: CreateCheckoutSpecInput<GiteaIntegrationConnection>): Promise<CheckoutSpec>;
12
13
  }
13
14
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"source-control.d.ts","sourceRoot":"","sources":["../../src/core/source-control.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAG1B,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAEvB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAC,cAAc,EAAkB,MAAM,gBAAgB,CAAC;AAIpE,KAAK,0BAA0B,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAMjE,qBAAa,0BACX,YAAW,qBAAqB,CAAC,0BAA0B,CAAC;IAEhD,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,cAAc;IAE5C,gBAAgB,CACpB,KAAK,EAAE,qBAAqB,CAAC,0BAA0B,CAAC,GACvD,OAAO,CAAC,cAAc,CAAC;IAqCpB,iBAAiB,CACrB,KAAK,EAAE,sBAAsB,CAAC,0BAA0B,CAAC,GACxD,OAAO,CAAC,kBAAkB,CAAC;IAQxB,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,0BAA0B,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IA4B/E,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,0BAA0B,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IAwBnF,kBAAkB,CACtB,KAAK,EAAE,uBAAuB,CAAC,0BAA0B,CAAC,GACzD,OAAO,CAAC,YAAY,CAAC;CAsBzB"}
1
+ {"version":3,"file":"source-control.d.ts","sourceRoot":"","sources":["../../src/core/source-control.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAI1B,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAK1B,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAEvB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAC,cAAc,EAAkB,MAAM,gBAAgB,CAAC;AAIpE,KAAK,0BAA0B,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAMjE,qBAAa,0BACX,YAAW,qBAAqB,CAAC,0BAA0B,CAAC;IAEhD,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,cAAc;IAE5C,gBAAgB,CACpB,KAAK,EAAE,qBAAqB,CAAC,0BAA0B,CAAC,GACvD,OAAO,CAAC,cAAc,CAAC;IAqCpB,iBAAiB,CACrB,KAAK,EAAE,sBAAsB,CAAC,0BAA0B,CAAC,GACxD,OAAO,CAAC,kBAAkB,CAAC;IAQxB,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,0BAA0B,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IA4B/E,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,0BAA0B,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IAwBzF,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI;IA0C5D,kBAAkB,CACtB,KAAK,EAAE,uBAAuB,CAAC,0BAA0B,CAAC,GACzD,OAAO,CAAC,YAAY,CAAC;CAsBzB"}
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { giteaProviderKind } from '@shipfox/api-integration-gitea-dto';
3
- import { buildProviderRepositoryId, MAX_REPOSITORY_FILE_BYTES, parseProviderRepositoryId } from '@shipfox/api-integration-spi';
3
+ import { asRecord, buildProviderRepositoryId, isRecord, isValidGitObjectId, isValidTriggerRef, MAX_REPOSITORY_FILE_BYTES, nonEmptyString, parseProviderRepositoryId, positiveInteger } from '@shipfox/api-integration-spi';
4
4
  import { config, giteaCloneBaseOrigin } from '#config.js';
5
5
  import { GiteaIntegrationProviderError } from './errors.js';
6
6
  const TRAILING_SLASHES_RE = /\/+$/;
@@ -100,6 +100,45 @@ export class GiteaSourceControlProvider {
100
100
  content: file.content
101
101
  };
102
102
  }
103
+ resolveTriggerReference(payload) {
104
+ if (!isRecord(payload)) return null;
105
+ const actor = giteaEventActor(payload);
106
+ const pullRequest = asRecord(payload.pull_request);
107
+ if (pullRequest) {
108
+ const head = asRecord(pullRequest.head);
109
+ const repository = asRecord(head?.repo);
110
+ const base = asRecord(pullRequest.base);
111
+ const baseRepository = asRecord(base?.repo) ?? asRecord(payload.repository);
112
+ if (!repository || !baseRepository || !sameGiteaRepository(repository, baseRepository)) {
113
+ return null;
114
+ }
115
+ const repositoryId = giteaRepositoryId(repository);
116
+ const number = positiveInteger(pullRequest.number);
117
+ const commit = nonEmptyString(head?.sha);
118
+ const ref = number === null ? null : `refs/pull/${number}/head`;
119
+ if (!repositoryId || !ref || !commit) return null;
120
+ const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);
121
+ if (!hasValidReference) return null;
122
+ return {
123
+ externalRepositoryId: buildProviderRepositoryId(giteaProviderKind, repositoryId),
124
+ ref,
125
+ commit,
126
+ actor
127
+ };
128
+ }
129
+ const repositoryId = giteaRepositoryId(asRecord(payload.repository));
130
+ const ref = nonEmptyString(payload.ref);
131
+ const commit = nonEmptyString(payload.after);
132
+ if (!repositoryId || !ref || !commit) return null;
133
+ const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);
134
+ if (!hasValidReference) return null;
135
+ return {
136
+ externalRepositoryId: buildProviderRepositoryId(giteaProviderKind, repositoryId),
137
+ ref,
138
+ commit,
139
+ actor
140
+ };
141
+ }
103
142
  async createCheckoutSpec(input) {
104
143
  const { owner, repo } = parseGiteaRepositoryLocator(input.externalRepositoryId, input.connection.externalAccountId);
105
144
  const repository = await this.gitea.getRepository({
@@ -122,6 +161,25 @@ export class GiteaSourceControlProvider {
122
161
  };
123
162
  }
124
163
  }
164
+ function giteaRepositoryId(repository) {
165
+ const fullName = nonEmptyString(repository?.full_name);
166
+ if (!fullName) return null;
167
+ const separatorIndex = fullName.indexOf('/');
168
+ const owner = separatorIndex > 0 ? fullName.slice(0, separatorIndex) : '';
169
+ const repo = separatorIndex > 0 ? fullName.slice(separatorIndex + 1) : '';
170
+ return owner && repo && !repo.includes('/') ? `${owner}/${repo}` : null;
171
+ }
172
+ // Gitea mirrors GitHub's webhook envelope but names the handle `username` on some events
173
+ // and `login` on others, so both are read before giving up on an actor.
174
+ function giteaEventActor(payload) {
175
+ const sender = asRecord(payload.sender);
176
+ return nonEmptyString(sender?.login) ?? nonEmptyString(sender?.username);
177
+ }
178
+ function sameGiteaRepository(first, second) {
179
+ const firstId = giteaRepositoryId(first);
180
+ const secondId = giteaRepositoryId(second);
181
+ return Boolean(firstId && secondId && firstId.toLowerCase() === secondId.toLowerCase());
182
+ }
125
183
  function createCheckoutRepositoryUrl(cloneUrl) {
126
184
  if (!giteaCloneBaseOrigin) return cloneUrl;
127
185
  const repositoryUrl = new URL(cloneUrl);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/source-control.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';\nimport {\n buildProviderRepositoryId,\n type CheckoutSpec,\n type CreateCheckoutSpecInput,\n type FetchFileInput,\n type FilePage,\n type FileSnapshot,\n type IntegrationConnection,\n type ListFilesInput,\n type ListRepositoriesInput,\n MAX_REPOSITORY_FILE_BYTES,\n parseProviderRepositoryId,\n type RepositoryPage,\n type RepositorySnapshot,\n type RepositoryVisibility,\n type ResolveRepositoryInput,\n type SourceControlProvider,\n} from '@shipfox/api-integration-spi';\nimport type {GiteaApiClient, GiteaRepository} from '#api/client.js';\nimport {config, giteaCloneBaseOrigin} from '#config.js';\nimport {GiteaIntegrationProviderError} from './errors.js';\n\ntype GiteaIntegrationConnection = IntegrationConnection<'gitea'>;\n\nconst TRAILING_SLASHES_RE = /\\/+$/;\nconst SEARCH_PAGE_SIZE = 100;\nconst SEARCH_MAX_PAGES_PER_REQUEST = 5;\n\nexport class GiteaSourceControlProvider\n implements SourceControlProvider<GiteaIntegrationConnection>\n{\n constructor(private readonly gitea: GiteaApiClient) {}\n\n async listRepositories(\n input: ListRepositoriesInput<GiteaIntegrationConnection>,\n ): Promise<RepositoryPage> {\n const org = input.connection.externalAccountId;\n const needle = input.search?.trim().toLowerCase();\n\n if (!needle) {\n const page = await this.gitea.listOrgRepositories({\n org,\n limit: input.limit,\n cursor: input.cursor,\n });\n return {\n repositories: page.repositories.map(toRepositorySnapshot),\n nextCursor: page.nextCursor,\n };\n }\n\n const matches: RepositorySnapshot[] = [];\n let cursor = input.cursor;\n let pagesScanned = 0;\n while (matches.length < input.limit && pagesScanned < SEARCH_MAX_PAGES_PER_REQUEST) {\n const page = await this.gitea.listOrgRepositories({org, limit: SEARCH_PAGE_SIZE, cursor});\n pagesScanned += 1;\n for (const repo of page.repositories) {\n if (repo.fullName.toLowerCase().includes(needle)) {\n matches.push(toRepositorySnapshot(repo));\n }\n }\n cursor = page.nextCursor ?? undefined;\n if (!cursor) break;\n }\n\n return {\n repositories: matches.slice(0, input.limit),\n nextCursor: cursor ?? null,\n };\n }\n\n async resolveRepository(\n input: ResolveRepositoryInput<GiteaIntegrationConnection>,\n ): Promise<RepositorySnapshot> {\n const {owner, repo} = parseGiteaRepositoryLocator(\n input.externalRepositoryId,\n input.connection.externalAccountId,\n );\n return toRepositorySnapshot(await this.gitea.getRepository({owner, repo}));\n }\n\n async listFiles(input: ListFilesInput<GiteaIntegrationConnection>): Promise<FilePage> {\n const {owner, repo} = parseGiteaRepositoryLocator(\n input.externalRepositoryId,\n input.connection.externalAccountId,\n );\n const sha = await this.gitea.resolveRef({owner, repo, ref: input.ref});\n const tree = await this.gitea.listTree({owner, repo, sha});\n if (tree.truncated) {\n throw new GiteaIntegrationProviderError(\n 'too-many-files',\n `Gitea repository tree for ${input.externalRepositoryId} is too large to enumerate`,\n );\n }\n\n const prefix = input.prefix.replace(TRAILING_SLASHES_RE, '');\n const matched = tree.blobs\n .filter((blob) => prefixMatches(blob.path, prefix))\n .sort((a, b) => a.path.localeCompare(b.path));\n const offset = parseOffset(input.cursor);\n const page = matched.slice(offset, offset + input.limit);\n const consumed = offset + page.length;\n\n return {\n files: page.map((blob) => ({path: blob.path, type: 'file', size: blob.size})),\n nextCursor: consumed < matched.length ? String(consumed) : null,\n };\n }\n\n async fetchFile(input: FetchFileInput<GiteaIntegrationConnection>): Promise<FileSnapshot> {\n const {owner, repo} = parseGiteaRepositoryLocator(\n input.externalRepositoryId,\n input.connection.externalAccountId,\n );\n const file = await this.gitea.fetchFileContent({owner, repo, path: input.path, ref: input.ref});\n\n if (\n file.size > MAX_REPOSITORY_FILE_BYTES ||\n Buffer.byteLength(file.content, 'utf8') > MAX_REPOSITORY_FILE_BYTES\n ) {\n throw new GiteaIntegrationProviderError(\n 'content-too-large',\n 'Gitea file content is larger than the supported limit',\n );\n }\n\n return {\n path: file.path,\n ref: input.ref,\n content: file.content,\n };\n }\n\n async createCheckoutSpec(\n input: CreateCheckoutSpecInput<GiteaIntegrationConnection>,\n ): Promise<CheckoutSpec> {\n const {owner, repo} = parseGiteaRepositoryLocator(\n input.externalRepositoryId,\n input.connection.externalAccountId,\n );\n const repository = await this.gitea.getRepository({owner, repo});\n const ref = input.ref?.trim() || repository.defaultBranch;\n\n return {\n repositoryUrl: createCheckoutRepositoryUrl(repository.cloneUrl),\n ref,\n // Gitea has no per-repo, auto-expiring token like a GitHub App installation\n // token, so checkout reuses the long-lived service credential. `expiresAt`\n // is the runner's lease/refresh window, not the token's real expiry: this\n // credential does not actually expire and stays valid if it leaks.\n credentials: {\n username: config.GITEA_SERVICE_USERNAME,\n token: config.GITEA_SERVICE_TOKEN,\n expiresAt: new Date(Date.now() + config.GITEA_CHECKOUT_TTL_SECONDS * 1000),\n },\n };\n }\n}\n\nfunction createCheckoutRepositoryUrl(cloneUrl: string): string {\n if (!giteaCloneBaseOrigin) return cloneUrl;\n\n const repositoryUrl = new URL(cloneUrl);\n\n repositoryUrl.protocol = giteaCloneBaseOrigin.protocol;\n repositoryUrl.host = giteaCloneBaseOrigin.host;\n\n return repositoryUrl.toString();\n}\n\nfunction toRepositorySnapshot(repository: GiteaRepository): RepositorySnapshot {\n return {\n externalRepositoryId: buildProviderRepositoryId(\n giteaProviderKind,\n `${repository.ownerLogin}/${repository.name}`,\n ),\n owner: repository.ownerLogin,\n name: repository.name,\n fullName: repository.fullName,\n defaultBranch: repository.defaultBranch,\n visibility: toRepositoryVisibility(repository),\n cloneUrl: repository.cloneUrl,\n htmlUrl: repository.htmlUrl,\n };\n}\n\nfunction toRepositoryVisibility(repository: GiteaRepository): RepositoryVisibility {\n return repository.private ? 'private' : 'public';\n}\n\nfunction parseGiteaRepositoryLocator(\n externalRepositoryId: string,\n expectedOwner: string,\n): {owner: string; repo: string} {\n const value = parseProviderRepositoryId(externalRepositoryId, giteaProviderKind);\n const separatorIndex = value.indexOf('/');\n const owner = separatorIndex > 0 ? value.slice(0, separatorIndex) : '';\n const repo = separatorIndex > 0 ? value.slice(separatorIndex + 1) : '';\n if (!owner || !repo || repo.includes('/')) {\n throw new GiteaIntegrationProviderError(\n 'repository-not-found',\n `Gitea repository id ${externalRepositoryId} must follow the form ${giteaProviderKind}:<owner>/<repo>`,\n );\n }\n // The service token is instance-wide, so the adapter must scope every request\n // to the connection's own account itself; without this an external id naming\n // another org would read its private repos and mint checkout credentials for\n // them. Reported as not-found so it does not confirm an out-of-scope repo.\n if (owner.toLowerCase() !== expectedOwner.toLowerCase()) {\n throw new GiteaIntegrationProviderError(\n 'repository-not-found',\n `Gitea repository id ${externalRepositoryId} is not in the ${expectedOwner} account`,\n );\n }\n\n return {owner, repo};\n}\n\nfunction prefixMatches(path: string, prefix: string): boolean {\n if (!prefix) return true;\n return path === prefix || path.startsWith(`${prefix}/`);\n}\n\nfunction parseOffset(cursor: string | undefined): number {\n if (!cursor) return 0;\n const offset = Number.parseInt(cursor, 10);\n return Number.isNaN(offset) || offset < 0 ? 0 : offset;\n}\n"],"names":["Buffer","giteaProviderKind","buildProviderRepositoryId","MAX_REPOSITORY_FILE_BYTES","parseProviderRepositoryId","config","giteaCloneBaseOrigin","GiteaIntegrationProviderError","TRAILING_SLASHES_RE","SEARCH_PAGE_SIZE","SEARCH_MAX_PAGES_PER_REQUEST","GiteaSourceControlProvider","gitea","listRepositories","input","org","connection","externalAccountId","needle","search","trim","toLowerCase","page","listOrgRepositories","limit","cursor","repositories","map","toRepositorySnapshot","nextCursor","matches","pagesScanned","length","repo","fullName","includes","push","undefined","slice","resolveRepository","owner","parseGiteaRepositoryLocator","externalRepositoryId","getRepository","listFiles","sha","resolveRef","ref","tree","listTree","truncated","prefix","replace","matched","blobs","filter","blob","prefixMatches","path","sort","a","b","localeCompare","offset","parseOffset","consumed","files","type","size","String","fetchFile","file","fetchFileContent","byteLength","content","createCheckoutSpec","repository","defaultBranch","repositoryUrl","createCheckoutRepositoryUrl","cloneUrl","credentials","username","GITEA_SERVICE_USERNAME","token","GITEA_SERVICE_TOKEN","expiresAt","Date","now","GITEA_CHECKOUT_TTL_SECONDS","URL","protocol","host","toString","ownerLogin","name","visibility","toRepositoryVisibility","htmlUrl","private","expectedOwner","value","separatorIndex","indexOf","startsWith","Number","parseInt","isNaN"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/source-control.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';\nimport {\n asRecord,\n buildProviderRepositoryId,\n type CheckoutSpec,\n type CreateCheckoutSpecInput,\n type FetchFileInput,\n type FilePage,\n type FileSnapshot,\n type IntegrationConnection,\n isRecord,\n isValidGitObjectId,\n isValidTriggerRef,\n type ListFilesInput,\n type ListRepositoriesInput,\n MAX_REPOSITORY_FILE_BYTES,\n nonEmptyString,\n parseProviderRepositoryId,\n positiveInteger,\n type RepositoryPage,\n type RepositorySnapshot,\n type RepositoryVisibility,\n type ResolveRepositoryInput,\n type SourceControlProvider,\n type TriggerReference,\n} from '@shipfox/api-integration-spi';\nimport type {GiteaApiClient, GiteaRepository} from '#api/client.js';\nimport {config, giteaCloneBaseOrigin} from '#config.js';\nimport {GiteaIntegrationProviderError} from './errors.js';\n\ntype GiteaIntegrationConnection = IntegrationConnection<'gitea'>;\n\nconst TRAILING_SLASHES_RE = /\\/+$/;\nconst SEARCH_PAGE_SIZE = 100;\nconst SEARCH_MAX_PAGES_PER_REQUEST = 5;\n\nexport class GiteaSourceControlProvider\n implements SourceControlProvider<GiteaIntegrationConnection>\n{\n constructor(private readonly gitea: GiteaApiClient) {}\n\n async listRepositories(\n input: ListRepositoriesInput<GiteaIntegrationConnection>,\n ): Promise<RepositoryPage> {\n const org = input.connection.externalAccountId;\n const needle = input.search?.trim().toLowerCase();\n\n if (!needle) {\n const page = await this.gitea.listOrgRepositories({\n org,\n limit: input.limit,\n cursor: input.cursor,\n });\n return {\n repositories: page.repositories.map(toRepositorySnapshot),\n nextCursor: page.nextCursor,\n };\n }\n\n const matches: RepositorySnapshot[] = [];\n let cursor = input.cursor;\n let pagesScanned = 0;\n while (matches.length < input.limit && pagesScanned < SEARCH_MAX_PAGES_PER_REQUEST) {\n const page = await this.gitea.listOrgRepositories({org, limit: SEARCH_PAGE_SIZE, cursor});\n pagesScanned += 1;\n for (const repo of page.repositories) {\n if (repo.fullName.toLowerCase().includes(needle)) {\n matches.push(toRepositorySnapshot(repo));\n }\n }\n cursor = page.nextCursor ?? undefined;\n if (!cursor) break;\n }\n\n return {\n repositories: matches.slice(0, input.limit),\n nextCursor: cursor ?? null,\n };\n }\n\n async resolveRepository(\n input: ResolveRepositoryInput<GiteaIntegrationConnection>,\n ): Promise<RepositorySnapshot> {\n const {owner, repo} = parseGiteaRepositoryLocator(\n input.externalRepositoryId,\n input.connection.externalAccountId,\n );\n return toRepositorySnapshot(await this.gitea.getRepository({owner, repo}));\n }\n\n async listFiles(input: ListFilesInput<GiteaIntegrationConnection>): Promise<FilePage> {\n const {owner, repo} = parseGiteaRepositoryLocator(\n input.externalRepositoryId,\n input.connection.externalAccountId,\n );\n const sha = await this.gitea.resolveRef({owner, repo, ref: input.ref});\n const tree = await this.gitea.listTree({owner, repo, sha});\n if (tree.truncated) {\n throw new GiteaIntegrationProviderError(\n 'too-many-files',\n `Gitea repository tree for ${input.externalRepositoryId} is too large to enumerate`,\n );\n }\n\n const prefix = input.prefix.replace(TRAILING_SLASHES_RE, '');\n const matched = tree.blobs\n .filter((blob) => prefixMatches(blob.path, prefix))\n .sort((a, b) => a.path.localeCompare(b.path));\n const offset = parseOffset(input.cursor);\n const page = matched.slice(offset, offset + input.limit);\n const consumed = offset + page.length;\n\n return {\n files: page.map((blob) => ({path: blob.path, type: 'file', size: blob.size})),\n nextCursor: consumed < matched.length ? String(consumed) : null,\n };\n }\n\n async fetchFile(input: FetchFileInput<GiteaIntegrationConnection>): Promise<FileSnapshot> {\n const {owner, repo} = parseGiteaRepositoryLocator(\n input.externalRepositoryId,\n input.connection.externalAccountId,\n );\n const file = await this.gitea.fetchFileContent({owner, repo, path: input.path, ref: input.ref});\n\n if (\n file.size > MAX_REPOSITORY_FILE_BYTES ||\n Buffer.byteLength(file.content, 'utf8') > MAX_REPOSITORY_FILE_BYTES\n ) {\n throw new GiteaIntegrationProviderError(\n 'content-too-large',\n 'Gitea file content is larger than the supported limit',\n );\n }\n\n return {\n path: file.path,\n ref: input.ref,\n content: file.content,\n };\n }\n\n resolveTriggerReference(payload: unknown): TriggerReference | null {\n if (!isRecord(payload)) return null;\n\n const actor = giteaEventActor(payload);\n const pullRequest = asRecord(payload.pull_request);\n if (pullRequest) {\n const head = asRecord(pullRequest.head);\n const repository = asRecord(head?.repo);\n const base = asRecord(pullRequest.base);\n const baseRepository = asRecord(base?.repo) ?? asRecord(payload.repository);\n if (!repository || !baseRepository || !sameGiteaRepository(repository, baseRepository)) {\n return null;\n }\n const repositoryId = giteaRepositoryId(repository);\n const number = positiveInteger(pullRequest.number);\n const commit = nonEmptyString(head?.sha);\n const ref = number === null ? null : `refs/pull/${number}/head`;\n if (!repositoryId || !ref || !commit) return null;\n const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);\n if (!hasValidReference) return null;\n return {\n externalRepositoryId: buildProviderRepositoryId(giteaProviderKind, repositoryId),\n ref,\n commit,\n actor,\n };\n }\n\n const repositoryId = giteaRepositoryId(asRecord(payload.repository));\n const ref = nonEmptyString(payload.ref);\n const commit = nonEmptyString(payload.after);\n if (!repositoryId || !ref || !commit) return null;\n const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);\n if (!hasValidReference) return null;\n return {\n externalRepositoryId: buildProviderRepositoryId(giteaProviderKind, repositoryId),\n ref,\n commit,\n actor,\n };\n }\n\n async createCheckoutSpec(\n input: CreateCheckoutSpecInput<GiteaIntegrationConnection>,\n ): Promise<CheckoutSpec> {\n const {owner, repo} = parseGiteaRepositoryLocator(\n input.externalRepositoryId,\n input.connection.externalAccountId,\n );\n const repository = await this.gitea.getRepository({owner, repo});\n const ref = input.ref?.trim() || repository.defaultBranch;\n\n return {\n repositoryUrl: createCheckoutRepositoryUrl(repository.cloneUrl),\n ref,\n // Gitea has no per-repo, auto-expiring token like a GitHub App installation\n // token, so checkout reuses the long-lived service credential. `expiresAt`\n // is the runner's lease/refresh window, not the token's real expiry: this\n // credential does not actually expire and stays valid if it leaks.\n credentials: {\n username: config.GITEA_SERVICE_USERNAME,\n token: config.GITEA_SERVICE_TOKEN,\n expiresAt: new Date(Date.now() + config.GITEA_CHECKOUT_TTL_SECONDS * 1000),\n },\n };\n }\n}\n\nfunction giteaRepositoryId(repository: Record<string, unknown> | null): string | null {\n const fullName = nonEmptyString(repository?.full_name);\n if (!fullName) return null;\n const separatorIndex = fullName.indexOf('/');\n const owner = separatorIndex > 0 ? fullName.slice(0, separatorIndex) : '';\n const repo = separatorIndex > 0 ? fullName.slice(separatorIndex + 1) : '';\n return owner && repo && !repo.includes('/') ? `${owner}/${repo}` : null;\n}\n\n// Gitea mirrors GitHub's webhook envelope but names the handle `username` on some events\n// and `login` on others, so both are read before giving up on an actor.\nfunction giteaEventActor(payload: Record<string, unknown>): string | null {\n const sender = asRecord(payload.sender);\n return nonEmptyString(sender?.login) ?? nonEmptyString(sender?.username);\n}\n\nfunction sameGiteaRepository(\n first: Record<string, unknown>,\n second: Record<string, unknown>,\n): boolean {\n const firstId = giteaRepositoryId(first);\n const secondId = giteaRepositoryId(second);\n return Boolean(firstId && secondId && firstId.toLowerCase() === secondId.toLowerCase());\n}\n\nfunction createCheckoutRepositoryUrl(cloneUrl: string): string {\n if (!giteaCloneBaseOrigin) return cloneUrl;\n\n const repositoryUrl = new URL(cloneUrl);\n\n repositoryUrl.protocol = giteaCloneBaseOrigin.protocol;\n repositoryUrl.host = giteaCloneBaseOrigin.host;\n\n return repositoryUrl.toString();\n}\n\nfunction toRepositorySnapshot(repository: GiteaRepository): RepositorySnapshot {\n return {\n externalRepositoryId: buildProviderRepositoryId(\n giteaProviderKind,\n `${repository.ownerLogin}/${repository.name}`,\n ),\n owner: repository.ownerLogin,\n name: repository.name,\n fullName: repository.fullName,\n defaultBranch: repository.defaultBranch,\n visibility: toRepositoryVisibility(repository),\n cloneUrl: repository.cloneUrl,\n htmlUrl: repository.htmlUrl,\n };\n}\n\nfunction toRepositoryVisibility(repository: GiteaRepository): RepositoryVisibility {\n return repository.private ? 'private' : 'public';\n}\n\nfunction parseGiteaRepositoryLocator(\n externalRepositoryId: string,\n expectedOwner: string,\n): {owner: string; repo: string} {\n const value = parseProviderRepositoryId(externalRepositoryId, giteaProviderKind);\n const separatorIndex = value.indexOf('/');\n const owner = separatorIndex > 0 ? value.slice(0, separatorIndex) : '';\n const repo = separatorIndex > 0 ? value.slice(separatorIndex + 1) : '';\n if (!owner || !repo || repo.includes('/')) {\n throw new GiteaIntegrationProviderError(\n 'repository-not-found',\n `Gitea repository id ${externalRepositoryId} must follow the form ${giteaProviderKind}:<owner>/<repo>`,\n );\n }\n // The service token is instance-wide, so the adapter must scope every request\n // to the connection's own account itself; without this an external id naming\n // another org would read its private repos and mint checkout credentials for\n // them. Reported as not-found so it does not confirm an out-of-scope repo.\n if (owner.toLowerCase() !== expectedOwner.toLowerCase()) {\n throw new GiteaIntegrationProviderError(\n 'repository-not-found',\n `Gitea repository id ${externalRepositoryId} is not in the ${expectedOwner} account`,\n );\n }\n\n return {owner, repo};\n}\n\nfunction prefixMatches(path: string, prefix: string): boolean {\n if (!prefix) return true;\n return path === prefix || path.startsWith(`${prefix}/`);\n}\n\nfunction parseOffset(cursor: string | undefined): number {\n if (!cursor) return 0;\n const offset = Number.parseInt(cursor, 10);\n return Number.isNaN(offset) || offset < 0 ? 0 : offset;\n}\n"],"names":["Buffer","giteaProviderKind","asRecord","buildProviderRepositoryId","isRecord","isValidGitObjectId","isValidTriggerRef","MAX_REPOSITORY_FILE_BYTES","nonEmptyString","parseProviderRepositoryId","positiveInteger","config","giteaCloneBaseOrigin","GiteaIntegrationProviderError","TRAILING_SLASHES_RE","SEARCH_PAGE_SIZE","SEARCH_MAX_PAGES_PER_REQUEST","GiteaSourceControlProvider","gitea","listRepositories","input","org","connection","externalAccountId","needle","search","trim","toLowerCase","page","listOrgRepositories","limit","cursor","repositories","map","toRepositorySnapshot","nextCursor","matches","pagesScanned","length","repo","fullName","includes","push","undefined","slice","resolveRepository","owner","parseGiteaRepositoryLocator","externalRepositoryId","getRepository","listFiles","sha","resolveRef","ref","tree","listTree","truncated","prefix","replace","matched","blobs","filter","blob","prefixMatches","path","sort","a","b","localeCompare","offset","parseOffset","consumed","files","type","size","String","fetchFile","file","fetchFileContent","byteLength","content","resolveTriggerReference","payload","actor","giteaEventActor","pullRequest","pull_request","head","repository","base","baseRepository","sameGiteaRepository","repositoryId","giteaRepositoryId","number","commit","hasValidReference","after","createCheckoutSpec","defaultBranch","repositoryUrl","createCheckoutRepositoryUrl","cloneUrl","credentials","username","GITEA_SERVICE_USERNAME","token","GITEA_SERVICE_TOKEN","expiresAt","Date","now","GITEA_CHECKOUT_TTL_SECONDS","full_name","separatorIndex","indexOf","sender","login","first","second","firstId","secondId","Boolean","URL","protocol","host","toString","ownerLogin","name","visibility","toRepositoryVisibility","htmlUrl","private","expectedOwner","value","startsWith","Number","parseInt","isNaN"],"mappings":"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"}