@shipfox/api-integration-gitea 8.0.0 → 9.0.2

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.
Files changed (60) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +45 -0
  3. package/README.md +3 -8
  4. package/dist/api/client.js +1 -1
  5. package/dist/api/client.js.map +1 -1
  6. package/dist/core/connect.d.ts +1 -1
  7. package/dist/core/connect.d.ts.map +1 -1
  8. package/dist/core/connect.js.map +1 -1
  9. package/dist/core/errors.d.ts +1 -1
  10. package/dist/core/errors.d.ts.map +1 -1
  11. package/dist/core/errors.js +1 -1
  12. package/dist/core/errors.js.map +1 -1
  13. package/dist/core/source-control.d.ts +1 -1
  14. package/dist/core/source-control.d.ts.map +1 -1
  15. package/dist/core/source-control.js +1 -1
  16. package/dist/core/source-control.js.map +1 -1
  17. package/dist/core/webhook-processor.d.ts +1 -1
  18. package/dist/core/webhook-processor.d.ts.map +1 -1
  19. package/dist/core/webhook-processor.js +1 -1
  20. package/dist/core/webhook-processor.js.map +1 -1
  21. package/dist/core/webhook.d.ts +1 -1
  22. package/dist/core/webhook.d.ts.map +1 -1
  23. package/dist/core/webhook.js +1 -1
  24. package/dist/core/webhook.js.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/presentation/dto/integrations.d.ts +3 -3
  29. package/dist/presentation/dto/integrations.d.ts.map +1 -1
  30. package/dist/presentation/dto/integrations.js +20 -7
  31. package/dist/presentation/dto/integrations.js.map +1 -1
  32. package/dist/presentation/routes/connections.d.ts +1 -1
  33. package/dist/presentation/routes/connections.d.ts.map +1 -1
  34. package/dist/presentation/routes/connections.js.map +1 -1
  35. package/dist/presentation/routes/errors.js +1 -1
  36. package/dist/presentation/routes/errors.js.map +1 -1
  37. package/dist/presentation/routes/webhooks.d.ts +1 -1
  38. package/dist/presentation/routes/webhooks.d.ts.map +1 -1
  39. package/dist/presentation/routes/webhooks.js +1 -1
  40. package/dist/presentation/routes/webhooks.js.map +1 -1
  41. package/dist/tsconfig.test.tsbuildinfo +1 -1
  42. package/package.json +20 -13
  43. package/src/api/client.ts +1 -1
  44. package/src/core/connect.test.ts +1 -1
  45. package/src/core/connect.ts +1 -1
  46. package/src/core/errors.ts +1 -1
  47. package/src/core/source-control-clone-url.test.ts +1 -1
  48. package/src/core/source-control.test.ts +1 -1
  49. package/src/core/source-control.ts +2 -2
  50. package/src/core/webhook-processor.test.ts +1 -1
  51. package/src/core/webhook-processor.ts +1 -1
  52. package/src/core/webhook.test.ts +1 -1
  53. package/src/core/webhook.ts +6 -6
  54. package/src/index.ts +2 -2
  55. package/src/presentation/dto/integrations.ts +22 -8
  56. package/src/presentation/routes/connections.test.ts +1 -1
  57. package/src/presentation/routes/connections.ts +1 -1
  58. package/src/presentation/routes/errors.ts +1 -1
  59. package/src/presentation/routes/webhooks.ts +2 -5
  60. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 18 files with swc (330.44ms)
2
+ Successfully compiled: 18 files with swc (411.9ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # @shipfox/api-integration-gitea
2
2
 
3
+ ## 9.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 4b85404: Adds versioned architecture identity to participating package artifacts during publication.
8
+ - Updated dependencies [4b85404]
9
+ - @shipfox/api-auth-context@9.0.2
10
+ - @shipfox/api-integration-gitea-dto@9.0.2
11
+ - @shipfox/api-integration-spi@0.2.2
12
+ - @shipfox/config@1.2.4
13
+ - @shipfox/node-drizzle@0.3.4
14
+ - @shipfox/node-fastify@0.3.2
15
+ - @shipfox/node-opentelemetry@0.6.2
16
+ - @shipfox/node-postgres@0.4.4
17
+
18
+ ## 9.0.1
19
+
20
+ ### Patch Changes
21
+
22
+ - 475ce59: Republishes all public packages after restoring release authorization.
23
+ - Updated dependencies [475ce59]
24
+ - @shipfox/api-auth-context@9.0.1
25
+ - @shipfox/api-integration-gitea-dto@9.0.1
26
+ - @shipfox/api-integration-spi@0.2.1
27
+ - @shipfox/config@1.2.3
28
+ - @shipfox/node-drizzle@0.3.3
29
+ - @shipfox/node-fastify@0.3.1
30
+ - @shipfox/node-opentelemetry@0.6.1
31
+ - @shipfox/node-postgres@0.4.3
32
+
33
+ ## 9.0.0
34
+
35
+ ### Patch Changes
36
+
37
+ - 4a6d124: Separates Integrations provider SPI contracts from the public DTO surface.
38
+ - Updated dependencies [4a6d124]
39
+ - @shipfox/api-integration-spi@0.2.0
40
+ - @shipfox/api-auth-context@9.0.0
41
+ - @shipfox/api-integration-gitea-dto@9.0.0
42
+ - @shipfox/config@1.2.2
43
+ - @shipfox/node-drizzle@0.3.2
44
+ - @shipfox/node-fastify@0.3.0
45
+ - @shipfox/node-opentelemetry@0.6.0
46
+ - @shipfox/node-postgres@0.4.2
47
+
3
48
  ## 8.0.0
4
49
 
5
50
  ### Patch Changes
package/README.md CHANGED
@@ -51,11 +51,6 @@ turbo type --filter=@shipfox/api-integration-gitea
51
51
  turbo test --filter=@shipfox/api-integration-gitea
52
52
  ```
53
53
 
54
- Tests use Vitest and a real PostgreSQL database. Start local services before
55
- running the test suite:
56
-
57
- ```sh
58
- docker compose up -d
59
- ```
60
-
61
- The test environment uses the `api_test` database, set in `test/env.ts`.
54
+ For repository test conventions, read the
55
+ [testing guide](../../../../docs/guides/testing.md). This package uses the
56
+ `api_test` database, set in `test/env.ts`.
@@ -1,5 +1,5 @@
1
1
  import { Buffer } from 'node:buffer';
2
- import { MAX_REPOSITORY_FILE_BYTES } from '@shipfox/api-integration-core-dto';
2
+ import { MAX_REPOSITORY_FILE_BYTES } from '@shipfox/api-integration-spi';
3
3
  import { config } from '#config.js';
4
4
  import { GiteaIntegrationProviderError } from '#core/errors.js';
5
5
  const TRAILING_SLASHES_RE = /\/+$/;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {MAX_REPOSITORY_FILE_BYTES} from '@shipfox/api-integration-core-dto';\nimport {config} from '#config.js';\nimport {GiteaIntegrationProviderError} from '#core/errors.js';\n\nconst TRAILING_SLASHES_RE = /\\/+$/;\nconst NEXT_PAGE_RE = /[?&]page=(\\d+)/;\n\n// Recursive tree listing is a single call; a tree larger than this is reported as\n// truncated by Gitea and surfaced to the adapter as `too-many-files`.\nconst TREE_PAGE_SIZE = 1000;\n\nexport interface GiteaRepository {\n ownerLogin: string;\n name: string;\n fullName: string;\n defaultBranch: string;\n private: boolean;\n cloneUrl: string;\n htmlUrl: string;\n}\n\nexport interface GiteaRepositoryPage {\n repositories: GiteaRepository[];\n nextCursor: string | null;\n}\n\nexport interface GiteaTreeBlob {\n path: string;\n size: number | null;\n}\n\nexport interface GiteaTree {\n blobs: GiteaTreeBlob[];\n truncated: boolean;\n}\n\nexport interface GiteaFileContent {\n path: string;\n content: string;\n size: number;\n}\n\nexport interface GiteaApiClient {\n listOrgRepositories(input: {\n org: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GiteaRepositoryPage>;\n getRepository(input: {owner: string; repo: string}): Promise<GiteaRepository>;\n resolveRef(input: {owner: string; repo: string; ref: string}): Promise<string>;\n listTree(input: {owner: string; repo: string; sha: string}): Promise<GiteaTree>;\n fetchFileContent(input: {\n owner: string;\n repo: string;\n path: string;\n ref: string;\n }): Promise<GiteaFileContent>;\n organizationExists(input: {org: string}): Promise<boolean>;\n}\n\nexport function createGiteaApiClient(): GiteaApiClient {\n return new HttpGiteaApiClient();\n}\n\nclass HttpGiteaApiClient implements GiteaApiClient {\n private cachedBaseApiUrl: string | undefined;\n private cachedAuthHeader: string | undefined;\n\n async listOrgRepositories(input: {\n org: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GiteaRepositoryPage> {\n const response = await this.request(`orgs/${encodeURIComponent(input.org)}/repos`, {\n page: String(cursorToPage(input.cursor)),\n limit: String(input.limit),\n });\n const data = await response.json();\n if (!Array.isArray(data)) {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea repository list response was not an array',\n );\n }\n\n return {\n repositories: data.map(toGiteaRepository),\n nextCursor: nextCursorFromLink(response.headers.get('link')),\n };\n }\n\n async getRepository(input: {owner: string; repo: string}): Promise<GiteaRepository> {\n const response = await this.request(\n `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}`,\n );\n return toGiteaRepository(await response.json());\n }\n\n async resolveRef(input: {owner: string; repo: string; ref: string}): Promise<string> {\n const response = await this.request(\n `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/commits`,\n {sha: input.ref, limit: '1', stat: 'false', verification: 'false', files: 'false'},\n );\n const data = await response.json();\n const head = Array.isArray(data) ? data[0] : undefined;\n if (!isRecord(head) || typeof head.sha !== 'string') {\n throw new GiteaIntegrationProviderError(\n 'repository-not-found',\n `Gitea ref ${input.ref} did not resolve to a commit`,\n );\n }\n return head.sha;\n }\n\n async listTree(input: {owner: string; repo: string; sha: string}): Promise<GiteaTree> {\n const response = await this.request(\n `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/git/trees/${encodeURIComponent(input.sha)}`,\n {recursive: 'true', per_page: String(TREE_PAGE_SIZE)},\n );\n const data = await response.json();\n if (!isRecord(data)) {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea tree response was not an object',\n );\n }\n\n const entries = Array.isArray(data.tree) ? data.tree : [];\n const blobs: GiteaTreeBlob[] = [];\n for (const entry of entries) {\n if (isRecord(entry) && entry.type === 'blob' && typeof entry.path === 'string') {\n blobs.push({path: entry.path, size: typeof entry.size === 'number' ? entry.size : null});\n }\n }\n\n return {blobs, truncated: data.truncated === true};\n }\n\n async fetchFileContent(input: {\n owner: string;\n repo: string;\n path: string;\n ref: string;\n }): Promise<GiteaFileContent> {\n const response = await this.request(\n `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/contents/${encodePath(input.path)}`,\n {ref: input.ref},\n {notFoundReason: 'file-not-found'},\n );\n const data = await response.json();\n\n // Gitea returns an array for a directory path and an object for a single file;\n // a non-file response means the requested path is not a readable file.\n if (!isRecord(data) || Array.isArray(data) || data.type !== 'file') {\n throw new GiteaIntegrationProviderError(\n 'file-not-found',\n `Gitea path ${input.path} is not a file`,\n );\n }\n\n // A well-formed Gitea file response always carries a numeric size; coercing a\n // missing size to 0 would skip the limit guard below and decode unbounded content.\n if (typeof data.size !== 'number') {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea file response did not include a numeric size',\n );\n }\n const size = data.size;\n if (size > MAX_REPOSITORY_FILE_BYTES) {\n throw new GiteaIntegrationProviderError(\n 'content-too-large',\n 'Gitea file content is larger than the supported limit',\n );\n }\n if (typeof data.content !== 'string' || data.encoding !== 'base64') {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea file response did not include base64 content',\n );\n }\n\n return {\n path: typeof data.path === 'string' ? data.path : input.path,\n size,\n content: Buffer.from(data.content, 'base64').toString('utf8'),\n };\n }\n\n async organizationExists(input: {org: string}): Promise<boolean> {\n const response = await this.requestRaw(`orgs/${encodeURIComponent(input.org)}`);\n if (response.ok) return true;\n if (response.status === 404) return false;\n throw giteaHttpError(response);\n }\n\n private async request(\n path: string,\n searchParams: Record<string, string> = {},\n options: {notFoundReason?: NotFoundReason} = {},\n ): Promise<Response> {\n const response = await this.requestRaw(path, {searchParams});\n if (!response.ok)\n throw giteaHttpError(response, options.notFoundReason ?? 'repository-not-found');\n return response;\n }\n\n private async requestRaw(\n path: string,\n options: {method?: string; searchParams?: Record<string, string>; body?: unknown} = {},\n ): Promise<Response> {\n const url = new URL(`${this.baseApiUrl()}/${path}`);\n for (const [key, value] of Object.entries(options.searchParams ?? {})) {\n url.searchParams.set(key, value);\n }\n\n const headers: Record<string, string> = {\n authorization: this.authHeader(),\n accept: 'application/json',\n };\n const init: RequestInit = {\n method: options.method ?? 'GET',\n headers,\n signal: AbortSignal.timeout(config.GITEA_REQUEST_TIMEOUT_MS),\n };\n if (options.body !== undefined) {\n headers['content-type'] = 'application/json';\n init.body = JSON.stringify(options.body);\n }\n\n try {\n return await fetch(url, init);\n } catch (error) {\n // A bare `fetch` has no request timeout, so an unresponsive Gitea would\n // otherwise hold the worker open indefinitely; the AbortSignal.timeout\n // above surfaces as a fast `timeout` (503) instead of stalling.\n if (\n error instanceof Error &&\n (error.name === 'TimeoutError' || error.name === 'AbortError')\n ) {\n throw new GiteaIntegrationProviderError('timeout', 'Gitea request timed out');\n }\n throw new GiteaIntegrationProviderError(\n 'provider-unavailable',\n `Gitea request failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n private baseApiUrl(): string {\n if (!this.cachedBaseApiUrl) {\n this.cachedBaseApiUrl = `${config.GITEA_BASE_URL.replace(TRAILING_SLASHES_RE, '')}/api/v1`;\n }\n return this.cachedBaseApiUrl;\n }\n\n private authHeader(): string {\n if (!this.cachedAuthHeader) {\n const credentials = `${config.GITEA_SERVICE_USERNAME}:${config.GITEA_SERVICE_TOKEN}`;\n this.cachedAuthHeader = `Basic ${Buffer.from(credentials).toString('base64')}`;\n }\n return this.cachedAuthHeader;\n }\n}\n\ntype NotFoundReason = 'repository-not-found' | 'file-not-found';\n\nfunction giteaHttpError(\n response: Response,\n notFoundReason: NotFoundReason = 'repository-not-found',\n): GiteaIntegrationProviderError {\n const status = response.status;\n if (status === 404) {\n return new GiteaIntegrationProviderError(notFoundReason, `Gitea responded ${status}`);\n }\n if (isRateLimited(response)) {\n return new GiteaIntegrationProviderError(\n 'rate-limited',\n `Gitea responded ${status}`,\n retryAfterSeconds(response),\n );\n }\n if (status === 401 || status === 403) {\n return new GiteaIntegrationProviderError('access-denied', `Gitea responded ${status}`);\n }\n // Server errors and any other unexpected status mean the provider could not\n // serve the request; surface it as unavailable rather than leaking a raw error.\n return new GiteaIntegrationProviderError('provider-unavailable', `Gitea responded ${status}`);\n}\n\nfunction isRateLimited(response: Response): boolean {\n if (response.status === 429) return true;\n return response.status === 403 && response.headers.get('x-ratelimit-remaining') === '0';\n}\n\nfunction retryAfterSeconds(response: Response): number | undefined {\n const retryAfter = response.headers.get('retry-after');\n if (!retryAfter) return undefined;\n const parsed = Number.parseInt(retryAfter, 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction cursorToPage(cursor: string | undefined): number {\n if (!cursor) return 1;\n const page = Number.parseInt(cursor, 10);\n return Number.isNaN(page) || page < 1 ? 1 : page;\n}\n\nfunction nextCursorFromLink(link: string | null): string | null {\n if (!link) return null;\n const next = link.split(',').find((part) => part.includes('rel=\"next\"'));\n if (!next) return null;\n const match = next.match(NEXT_PAGE_RE);\n return match?.[1] ?? null;\n}\n\nfunction encodePath(path: string): string {\n const segments = path.split('/');\n // `..`/`.`/empty segments survive encodeURIComponent and would let `new URL`\n // normalize the request path out of `/contents/` and hit other authenticated\n // Gitea endpoints, so reject them before building the URL.\n for (const segment of segments) {\n if (segment === '' || segment === '.' || segment === '..') {\n throw new GiteaIntegrationProviderError(\n 'file-not-found',\n `Gitea path contains an invalid segment: ${path}`,\n );\n }\n }\n return segments.map(encodeURIComponent).join('/');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction toGiteaRepository(raw: unknown): GiteaRepository {\n if (\n !isRecord(raw) ||\n !isRecord(raw.owner) ||\n typeof raw.owner.login !== 'string' ||\n typeof raw.name !== 'string' ||\n typeof raw.full_name !== 'string' ||\n typeof raw.default_branch !== 'string' ||\n typeof raw.clone_url !== 'string' ||\n typeof raw.html_url !== 'string'\n ) {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea repository response is missing required fields',\n );\n }\n\n return {\n ownerLogin: raw.owner.login,\n name: raw.name,\n fullName: raw.full_name,\n defaultBranch: raw.default_branch,\n private: raw.private === true,\n cloneUrl: raw.clone_url,\n htmlUrl: raw.html_url,\n };\n}\n"],"names":["Buffer","MAX_REPOSITORY_FILE_BYTES","config","GiteaIntegrationProviderError","TRAILING_SLASHES_RE","NEXT_PAGE_RE","TREE_PAGE_SIZE","createGiteaApiClient","HttpGiteaApiClient","listOrgRepositories","input","response","request","encodeURIComponent","org","page","String","cursorToPage","cursor","limit","data","json","Array","isArray","repositories","map","toGiteaRepository","nextCursor","nextCursorFromLink","headers","get","getRepository","owner","repo","resolveRef","sha","ref","stat","verification","files","head","undefined","isRecord","listTree","recursive","per_page","entries","tree","blobs","entry","type","path","push","size","truncated","fetchFileContent","encodePath","notFoundReason","content","encoding","from","toString","organizationExists","requestRaw","ok","status","giteaHttpError","searchParams","options","url","URL","baseApiUrl","key","value","Object","set","authorization","authHeader","accept","init","method","signal","AbortSignal","timeout","GITEA_REQUEST_TIMEOUT_MS","body","JSON","stringify","fetch","error","Error","name","message","cachedBaseApiUrl","GITEA_BASE_URL","replace","cachedAuthHeader","credentials","GITEA_SERVICE_USERNAME","GITEA_SERVICE_TOKEN","isRateLimited","retryAfterSeconds","retryAfter","parsed","Number","parseInt","isNaN","link","next","split","find","part","includes","match","segments","segment","join","raw","login","full_name","default_branch","clone_url","html_url","ownerLogin","fullName","defaultBranch","private","cloneUrl","htmlUrl"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {MAX_REPOSITORY_FILE_BYTES} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport {GiteaIntegrationProviderError} from '#core/errors.js';\n\nconst TRAILING_SLASHES_RE = /\\/+$/;\nconst NEXT_PAGE_RE = /[?&]page=(\\d+)/;\n\n// Recursive tree listing is a single call; a tree larger than this is reported as\n// truncated by Gitea and surfaced to the adapter as `too-many-files`.\nconst TREE_PAGE_SIZE = 1000;\n\nexport interface GiteaRepository {\n ownerLogin: string;\n name: string;\n fullName: string;\n defaultBranch: string;\n private: boolean;\n cloneUrl: string;\n htmlUrl: string;\n}\n\nexport interface GiteaRepositoryPage {\n repositories: GiteaRepository[];\n nextCursor: string | null;\n}\n\nexport interface GiteaTreeBlob {\n path: string;\n size: number | null;\n}\n\nexport interface GiteaTree {\n blobs: GiteaTreeBlob[];\n truncated: boolean;\n}\n\nexport interface GiteaFileContent {\n path: string;\n content: string;\n size: number;\n}\n\nexport interface GiteaApiClient {\n listOrgRepositories(input: {\n org: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GiteaRepositoryPage>;\n getRepository(input: {owner: string; repo: string}): Promise<GiteaRepository>;\n resolveRef(input: {owner: string; repo: string; ref: string}): Promise<string>;\n listTree(input: {owner: string; repo: string; sha: string}): Promise<GiteaTree>;\n fetchFileContent(input: {\n owner: string;\n repo: string;\n path: string;\n ref: string;\n }): Promise<GiteaFileContent>;\n organizationExists(input: {org: string}): Promise<boolean>;\n}\n\nexport function createGiteaApiClient(): GiteaApiClient {\n return new HttpGiteaApiClient();\n}\n\nclass HttpGiteaApiClient implements GiteaApiClient {\n private cachedBaseApiUrl: string | undefined;\n private cachedAuthHeader: string | undefined;\n\n async listOrgRepositories(input: {\n org: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GiteaRepositoryPage> {\n const response = await this.request(`orgs/${encodeURIComponent(input.org)}/repos`, {\n page: String(cursorToPage(input.cursor)),\n limit: String(input.limit),\n });\n const data = await response.json();\n if (!Array.isArray(data)) {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea repository list response was not an array',\n );\n }\n\n return {\n repositories: data.map(toGiteaRepository),\n nextCursor: nextCursorFromLink(response.headers.get('link')),\n };\n }\n\n async getRepository(input: {owner: string; repo: string}): Promise<GiteaRepository> {\n const response = await this.request(\n `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}`,\n );\n return toGiteaRepository(await response.json());\n }\n\n async resolveRef(input: {owner: string; repo: string; ref: string}): Promise<string> {\n const response = await this.request(\n `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/commits`,\n {sha: input.ref, limit: '1', stat: 'false', verification: 'false', files: 'false'},\n );\n const data = await response.json();\n const head = Array.isArray(data) ? data[0] : undefined;\n if (!isRecord(head) || typeof head.sha !== 'string') {\n throw new GiteaIntegrationProviderError(\n 'repository-not-found',\n `Gitea ref ${input.ref} did not resolve to a commit`,\n );\n }\n return head.sha;\n }\n\n async listTree(input: {owner: string; repo: string; sha: string}): Promise<GiteaTree> {\n const response = await this.request(\n `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/git/trees/${encodeURIComponent(input.sha)}`,\n {recursive: 'true', per_page: String(TREE_PAGE_SIZE)},\n );\n const data = await response.json();\n if (!isRecord(data)) {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea tree response was not an object',\n );\n }\n\n const entries = Array.isArray(data.tree) ? data.tree : [];\n const blobs: GiteaTreeBlob[] = [];\n for (const entry of entries) {\n if (isRecord(entry) && entry.type === 'blob' && typeof entry.path === 'string') {\n blobs.push({path: entry.path, size: typeof entry.size === 'number' ? entry.size : null});\n }\n }\n\n return {blobs, truncated: data.truncated === true};\n }\n\n async fetchFileContent(input: {\n owner: string;\n repo: string;\n path: string;\n ref: string;\n }): Promise<GiteaFileContent> {\n const response = await this.request(\n `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/contents/${encodePath(input.path)}`,\n {ref: input.ref},\n {notFoundReason: 'file-not-found'},\n );\n const data = await response.json();\n\n // Gitea returns an array for a directory path and an object for a single file;\n // a non-file response means the requested path is not a readable file.\n if (!isRecord(data) || Array.isArray(data) || data.type !== 'file') {\n throw new GiteaIntegrationProviderError(\n 'file-not-found',\n `Gitea path ${input.path} is not a file`,\n );\n }\n\n // A well-formed Gitea file response always carries a numeric size; coercing a\n // missing size to 0 would skip the limit guard below and decode unbounded content.\n if (typeof data.size !== 'number') {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea file response did not include a numeric size',\n );\n }\n const size = data.size;\n if (size > MAX_REPOSITORY_FILE_BYTES) {\n throw new GiteaIntegrationProviderError(\n 'content-too-large',\n 'Gitea file content is larger than the supported limit',\n );\n }\n if (typeof data.content !== 'string' || data.encoding !== 'base64') {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea file response did not include base64 content',\n );\n }\n\n return {\n path: typeof data.path === 'string' ? data.path : input.path,\n size,\n content: Buffer.from(data.content, 'base64').toString('utf8'),\n };\n }\n\n async organizationExists(input: {org: string}): Promise<boolean> {\n const response = await this.requestRaw(`orgs/${encodeURIComponent(input.org)}`);\n if (response.ok) return true;\n if (response.status === 404) return false;\n throw giteaHttpError(response);\n }\n\n private async request(\n path: string,\n searchParams: Record<string, string> = {},\n options: {notFoundReason?: NotFoundReason} = {},\n ): Promise<Response> {\n const response = await this.requestRaw(path, {searchParams});\n if (!response.ok)\n throw giteaHttpError(response, options.notFoundReason ?? 'repository-not-found');\n return response;\n }\n\n private async requestRaw(\n path: string,\n options: {method?: string; searchParams?: Record<string, string>; body?: unknown} = {},\n ): Promise<Response> {\n const url = new URL(`${this.baseApiUrl()}/${path}`);\n for (const [key, value] of Object.entries(options.searchParams ?? {})) {\n url.searchParams.set(key, value);\n }\n\n const headers: Record<string, string> = {\n authorization: this.authHeader(),\n accept: 'application/json',\n };\n const init: RequestInit = {\n method: options.method ?? 'GET',\n headers,\n signal: AbortSignal.timeout(config.GITEA_REQUEST_TIMEOUT_MS),\n };\n if (options.body !== undefined) {\n headers['content-type'] = 'application/json';\n init.body = JSON.stringify(options.body);\n }\n\n try {\n return await fetch(url, init);\n } catch (error) {\n // A bare `fetch` has no request timeout, so an unresponsive Gitea would\n // otherwise hold the worker open indefinitely; the AbortSignal.timeout\n // above surfaces as a fast `timeout` (503) instead of stalling.\n if (\n error instanceof Error &&\n (error.name === 'TimeoutError' || error.name === 'AbortError')\n ) {\n throw new GiteaIntegrationProviderError('timeout', 'Gitea request timed out');\n }\n throw new GiteaIntegrationProviderError(\n 'provider-unavailable',\n `Gitea request failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n private baseApiUrl(): string {\n if (!this.cachedBaseApiUrl) {\n this.cachedBaseApiUrl = `${config.GITEA_BASE_URL.replace(TRAILING_SLASHES_RE, '')}/api/v1`;\n }\n return this.cachedBaseApiUrl;\n }\n\n private authHeader(): string {\n if (!this.cachedAuthHeader) {\n const credentials = `${config.GITEA_SERVICE_USERNAME}:${config.GITEA_SERVICE_TOKEN}`;\n this.cachedAuthHeader = `Basic ${Buffer.from(credentials).toString('base64')}`;\n }\n return this.cachedAuthHeader;\n }\n}\n\ntype NotFoundReason = 'repository-not-found' | 'file-not-found';\n\nfunction giteaHttpError(\n response: Response,\n notFoundReason: NotFoundReason = 'repository-not-found',\n): GiteaIntegrationProviderError {\n const status = response.status;\n if (status === 404) {\n return new GiteaIntegrationProviderError(notFoundReason, `Gitea responded ${status}`);\n }\n if (isRateLimited(response)) {\n return new GiteaIntegrationProviderError(\n 'rate-limited',\n `Gitea responded ${status}`,\n retryAfterSeconds(response),\n );\n }\n if (status === 401 || status === 403) {\n return new GiteaIntegrationProviderError('access-denied', `Gitea responded ${status}`);\n }\n // Server errors and any other unexpected status mean the provider could not\n // serve the request; surface it as unavailable rather than leaking a raw error.\n return new GiteaIntegrationProviderError('provider-unavailable', `Gitea responded ${status}`);\n}\n\nfunction isRateLimited(response: Response): boolean {\n if (response.status === 429) return true;\n return response.status === 403 && response.headers.get('x-ratelimit-remaining') === '0';\n}\n\nfunction retryAfterSeconds(response: Response): number | undefined {\n const retryAfter = response.headers.get('retry-after');\n if (!retryAfter) return undefined;\n const parsed = Number.parseInt(retryAfter, 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction cursorToPage(cursor: string | undefined): number {\n if (!cursor) return 1;\n const page = Number.parseInt(cursor, 10);\n return Number.isNaN(page) || page < 1 ? 1 : page;\n}\n\nfunction nextCursorFromLink(link: string | null): string | null {\n if (!link) return null;\n const next = link.split(',').find((part) => part.includes('rel=\"next\"'));\n if (!next) return null;\n const match = next.match(NEXT_PAGE_RE);\n return match?.[1] ?? null;\n}\n\nfunction encodePath(path: string): string {\n const segments = path.split('/');\n // `..`/`.`/empty segments survive encodeURIComponent and would let `new URL`\n // normalize the request path out of `/contents/` and hit other authenticated\n // Gitea endpoints, so reject them before building the URL.\n for (const segment of segments) {\n if (segment === '' || segment === '.' || segment === '..') {\n throw new GiteaIntegrationProviderError(\n 'file-not-found',\n `Gitea path contains an invalid segment: ${path}`,\n );\n }\n }\n return segments.map(encodeURIComponent).join('/');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction toGiteaRepository(raw: unknown): GiteaRepository {\n if (\n !isRecord(raw) ||\n !isRecord(raw.owner) ||\n typeof raw.owner.login !== 'string' ||\n typeof raw.name !== 'string' ||\n typeof raw.full_name !== 'string' ||\n typeof raw.default_branch !== 'string' ||\n typeof raw.clone_url !== 'string' ||\n typeof raw.html_url !== 'string'\n ) {\n throw new GiteaIntegrationProviderError(\n 'malformed-provider-response',\n 'Gitea repository response is missing required fields',\n );\n }\n\n return {\n ownerLogin: raw.owner.login,\n name: raw.name,\n fullName: raw.full_name,\n defaultBranch: raw.default_branch,\n private: raw.private === true,\n cloneUrl: raw.clone_url,\n htmlUrl: raw.html_url,\n };\n}\n"],"names":["Buffer","MAX_REPOSITORY_FILE_BYTES","config","GiteaIntegrationProviderError","TRAILING_SLASHES_RE","NEXT_PAGE_RE","TREE_PAGE_SIZE","createGiteaApiClient","HttpGiteaApiClient","listOrgRepositories","input","response","request","encodeURIComponent","org","page","String","cursorToPage","cursor","limit","data","json","Array","isArray","repositories","map","toGiteaRepository","nextCursor","nextCursorFromLink","headers","get","getRepository","owner","repo","resolveRef","sha","ref","stat","verification","files","head","undefined","isRecord","listTree","recursive","per_page","entries","tree","blobs","entry","type","path","push","size","truncated","fetchFileContent","encodePath","notFoundReason","content","encoding","from","toString","organizationExists","requestRaw","ok","status","giteaHttpError","searchParams","options","url","URL","baseApiUrl","key","value","Object","set","authorization","authHeader","accept","init","method","signal","AbortSignal","timeout","GITEA_REQUEST_TIMEOUT_MS","body","JSON","stringify","fetch","error","Error","name","message","cachedBaseApiUrl","GITEA_BASE_URL","replace","cachedAuthHeader","credentials","GITEA_SERVICE_USERNAME","GITEA_SERVICE_TOKEN","isRateLimited","retryAfterSeconds","retryAfter","parsed","Number","parseInt","isNaN","link","next","split","find","part","includes","match","segments","segment","join","raw","login","full_name","default_branch","clone_url","html_url","ownerLogin","fullName","defaultBranch","private","cloneUrl","htmlUrl"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { IntegrationConnection } from '@shipfox/api-integration-core-dto';
1
+ import type { IntegrationConnection } from '@shipfox/api-integration-spi';
2
2
  import type { GiteaApiClient } from '#api/client.js';
3
3
  export interface ConnectGiteaConnectionInput {
4
4
  workspaceId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/core/connect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,mCAAmC,CAAC;AAC7E,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAC;AAGnD,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,cAAc,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,sBAAsB,EAAE,CACtB,KAAK,EAAE,2BAA2B,KAC/B,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;CAC9C;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CA2BzC"}
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/core/connect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,8BAA8B,CAAC;AACxE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAC;AAGnD,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,cAAc,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,sBAAsB,EAAE,CACtB,KAAK,EAAE,2BAA2B,KAC/B,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;CAC9C;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CA2BzC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/connect.ts"],"sourcesContent":["import type {IntegrationConnection} from '@shipfox/api-integration-core-dto';\nimport type {GiteaApiClient} from '#api/client.js';\nimport {GiteaOrgAlreadyLinkedError, GiteaOrganizationNotFoundError} from './errors.js';\n\nexport interface ConnectGiteaConnectionInput {\n workspaceId: string;\n org: string;\n displayName: string;\n}\n\nexport interface HandleGiteaConnectParams {\n gitea: GiteaApiClient;\n workspaceId: string;\n org: string;\n getExistingGiteaConnection: (input: {\n org: string;\n }) => Promise<IntegrationConnection<'gitea'> | undefined>;\n connectGiteaConnection: (\n input: ConnectGiteaConnectionInput,\n ) => Promise<IntegrationConnection<'gitea'>>;\n}\n\nexport async function handleGiteaConnect(\n params: HandleGiteaConnectParams,\n): Promise<IntegrationConnection<'gitea'>> {\n // Gitea resolves org names case-insensitively, but the ownership lookup and the\n // unique indexes that back the cross-tenant guard compare the stored `org`\n // case-sensitively. Canonicalize once here so a case variant (\"Acme\" vs \"acme\")\n // cannot slip past getExistingGiteaConnection and link an org another workspace\n // already owns. The source-control scope guard already lowercases both sides.\n const org = params.org.toLowerCase();\n\n if (!(await params.gitea.organizationExists({org}))) {\n throw new GiteaOrganizationNotFoundError(org);\n }\n\n const existing = await params.getExistingGiteaConnection({org});\n if (existing && existing.workspaceId !== params.workspaceId) {\n throw new GiteaOrgAlreadyLinkedError(org);\n }\n if (existing && existing.lifecycleStatus === 'active') {\n return existing;\n }\n\n // The read-only service account cannot manage org hooks; the instance admin\n // provisions push delivery out of band.\n return await params.connectGiteaConnection({\n workspaceId: params.workspaceId,\n org,\n displayName: `Gitea ${org}`,\n });\n}\n"],"names":["GiteaOrgAlreadyLinkedError","GiteaOrganizationNotFoundError","handleGiteaConnect","params","org","toLowerCase","gitea","organizationExists","existing","getExistingGiteaConnection","workspaceId","lifecycleStatus","connectGiteaConnection","displayName"],"mappings":"AAEA,SAAQA,0BAA0B,EAAEC,8BAA8B,QAAO,cAAc;AAoBvF,OAAO,eAAeC,mBACpBC,MAAgC;IAEhC,gFAAgF;IAChF,2EAA2E;IAC3E,gFAAgF;IAChF,gFAAgF;IAChF,8EAA8E;IAC9E,MAAMC,MAAMD,OAAOC,GAAG,CAACC,WAAW;IAElC,IAAI,CAAE,MAAMF,OAAOG,KAAK,CAACC,kBAAkB,CAAC;QAACH;IAAG,IAAK;QACnD,MAAM,IAAIH,+BAA+BG;IAC3C;IAEA,MAAMI,WAAW,MAAML,OAAOM,0BAA0B,CAAC;QAACL;IAAG;IAC7D,IAAII,YAAYA,SAASE,WAAW,KAAKP,OAAOO,WAAW,EAAE;QAC3D,MAAM,IAAIV,2BAA2BI;IACvC;IACA,IAAII,YAAYA,SAASG,eAAe,KAAK,UAAU;QACrD,OAAOH;IACT;IAEA,4EAA4E;IAC5E,wCAAwC;IACxC,OAAO,MAAML,OAAOS,sBAAsB,CAAC;QACzCF,aAAaP,OAAOO,WAAW;QAC/BN;QACAS,aAAa,CAAC,MAAM,EAAET,KAAK;IAC7B;AACF"}
1
+ {"version":3,"sources":["../../src/core/connect.ts"],"sourcesContent":["import type {IntegrationConnection} from '@shipfox/api-integration-spi';\nimport type {GiteaApiClient} from '#api/client.js';\nimport {GiteaOrgAlreadyLinkedError, GiteaOrganizationNotFoundError} from './errors.js';\n\nexport interface ConnectGiteaConnectionInput {\n workspaceId: string;\n org: string;\n displayName: string;\n}\n\nexport interface HandleGiteaConnectParams {\n gitea: GiteaApiClient;\n workspaceId: string;\n org: string;\n getExistingGiteaConnection: (input: {\n org: string;\n }) => Promise<IntegrationConnection<'gitea'> | undefined>;\n connectGiteaConnection: (\n input: ConnectGiteaConnectionInput,\n ) => Promise<IntegrationConnection<'gitea'>>;\n}\n\nexport async function handleGiteaConnect(\n params: HandleGiteaConnectParams,\n): Promise<IntegrationConnection<'gitea'>> {\n // Gitea resolves org names case-insensitively, but the ownership lookup and the\n // unique indexes that back the cross-tenant guard compare the stored `org`\n // case-sensitively. Canonicalize once here so a case variant (\"Acme\" vs \"acme\")\n // cannot slip past getExistingGiteaConnection and link an org another workspace\n // already owns. The source-control scope guard already lowercases both sides.\n const org = params.org.toLowerCase();\n\n if (!(await params.gitea.organizationExists({org}))) {\n throw new GiteaOrganizationNotFoundError(org);\n }\n\n const existing = await params.getExistingGiteaConnection({org});\n if (existing && existing.workspaceId !== params.workspaceId) {\n throw new GiteaOrgAlreadyLinkedError(org);\n }\n if (existing && existing.lifecycleStatus === 'active') {\n return existing;\n }\n\n // The read-only service account cannot manage org hooks; the instance admin\n // provisions push delivery out of band.\n return await params.connectGiteaConnection({\n workspaceId: params.workspaceId,\n org,\n displayName: `Gitea ${org}`,\n });\n}\n"],"names":["GiteaOrgAlreadyLinkedError","GiteaOrganizationNotFoundError","handleGiteaConnect","params","org","toLowerCase","gitea","organizationExists","existing","getExistingGiteaConnection","workspaceId","lifecycleStatus","connectGiteaConnection","displayName"],"mappings":"AAEA,SAAQA,0BAA0B,EAAEC,8BAA8B,QAAO,cAAc;AAoBvF,OAAO,eAAeC,mBACpBC,MAAgC;IAEhC,gFAAgF;IAChF,2EAA2E;IAC3E,gFAAgF;IAChF,gFAAgF;IAChF,8EAA8E;IAC9E,MAAMC,MAAMD,OAAOC,GAAG,CAACC,WAAW;IAElC,IAAI,CAAE,MAAMF,OAAOG,KAAK,CAACC,kBAAkB,CAAC;QAACH;IAAG,IAAK;QACnD,MAAM,IAAIH,+BAA+BG;IAC3C;IAEA,MAAMI,WAAW,MAAML,OAAOM,0BAA0B,CAAC;QAACL;IAAG;IAC7D,IAAII,YAAYA,SAASE,WAAW,KAAKP,OAAOO,WAAW,EAAE;QAC3D,MAAM,IAAIV,2BAA2BI;IACvC;IACA,IAAII,YAAYA,SAASG,eAAe,KAAK,UAAU;QACrD,OAAOH;IACT;IAEA,4EAA4E;IAC5E,wCAAwC;IACxC,OAAO,MAAML,OAAOS,sBAAsB,CAAC;QACzCF,aAAaP,OAAOO,WAAW;QAC/BN;QACAS,aAAa,CAAC,MAAM,EAAET,KAAK;IAC7B;AACF"}
@@ -1,4 +1,4 @@
1
- import { IntegrationProviderError } from '@shipfox/api-integration-core-dto';
1
+ import { IntegrationProviderError } from '@shipfox/api-integration-spi';
2
2
  export declare class GiteaIntegrationProviderError extends IntegrationProviderError {
3
3
  }
4
4
  export declare class GiteaOrganizationNotFoundError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,mCAAmC,CAAC;AAE3E,qBAAa,6BAA8B,SAAQ,wBAAwB;CAAG;AAE9E,qBAAa,8BAA+B,SAAQ,KAAK;gBAC3C,GAAG,EAAE,MAAM;CAIxB;AAED,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,GAAG,EAAE,MAAM;CAIxB"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,8BAA8B,CAAC;AAEtE,qBAAa,6BAA8B,SAAQ,wBAAwB;CAAG;AAE9E,qBAAa,8BAA+B,SAAQ,KAAK;gBAC3C,GAAG,EAAE,MAAM;CAIxB;AAED,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,GAAG,EAAE,MAAM;CAIxB"}
@@ -1,4 +1,4 @@
1
- import { IntegrationProviderError } from '@shipfox/api-integration-core-dto';
1
+ import { IntegrationProviderError } from '@shipfox/api-integration-spi';
2
2
  export class GiteaIntegrationProviderError extends IntegrationProviderError {
3
3
  }
4
4
  export class GiteaOrganizationNotFoundError extends Error {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/errors.ts"],"sourcesContent":["import {IntegrationProviderError} from '@shipfox/api-integration-core-dto';\n\nexport class GiteaIntegrationProviderError extends IntegrationProviderError {}\n\nexport class GiteaOrganizationNotFoundError extends Error {\n constructor(org: string) {\n super(`Gitea organization not found: ${org}`);\n this.name = 'GiteaOrganizationNotFoundError';\n }\n}\n\nexport class GiteaOrgAlreadyLinkedError extends Error {\n constructor(org: string) {\n super(`Gitea organization is already linked to another Shipfox workspace: ${org}`);\n this.name = 'GiteaOrgAlreadyLinkedError';\n }\n}\n"],"names":["IntegrationProviderError","GiteaIntegrationProviderError","GiteaOrganizationNotFoundError","Error","org","name","GiteaOrgAlreadyLinkedError"],"mappings":"AAAA,SAAQA,wBAAwB,QAAO,oCAAoC;AAE3E,OAAO,MAAMC,sCAAsCD;AAA0B;AAE7E,OAAO,MAAME,uCAAuCC;IAClD,YAAYC,GAAW,CAAE;QACvB,KAAK,CAAC,CAAC,8BAA8B,EAAEA,KAAK;QAC5C,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMC,mCAAmCH;IAC9C,YAAYC,GAAW,CAAE;QACvB,KAAK,CAAC,CAAC,mEAAmE,EAAEA,KAAK;QACjF,IAAI,CAACC,IAAI,GAAG;IACd;AACF"}
1
+ {"version":3,"sources":["../../src/core/errors.ts"],"sourcesContent":["import {IntegrationProviderError} from '@shipfox/api-integration-spi';\n\nexport class GiteaIntegrationProviderError extends IntegrationProviderError {}\n\nexport class GiteaOrganizationNotFoundError extends Error {\n constructor(org: string) {\n super(`Gitea organization not found: ${org}`);\n this.name = 'GiteaOrganizationNotFoundError';\n }\n}\n\nexport class GiteaOrgAlreadyLinkedError extends Error {\n constructor(org: string) {\n super(`Gitea organization is already linked to another Shipfox workspace: ${org}`);\n this.name = 'GiteaOrgAlreadyLinkedError';\n }\n}\n"],"names":["IntegrationProviderError","GiteaIntegrationProviderError","GiteaOrganizationNotFoundError","Error","org","name","GiteaOrgAlreadyLinkedError"],"mappings":"AAAA,SAAQA,wBAAwB,QAAO,+BAA+B;AAEtE,OAAO,MAAMC,sCAAsCD;AAA0B;AAE7E,OAAO,MAAME,uCAAuCC;IAClD,YAAYC,GAAW,CAAE;QACvB,KAAK,CAAC,CAAC,8BAA8B,EAAEA,KAAK;QAC5C,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMC,mCAAmCH;IAC9C,YAAYC,GAAW,CAAE;QACvB,KAAK,CAAC,CAAC,mEAAmE,EAAEA,KAAK;QACjF,IAAI,CAACC,IAAI,GAAG;IACd;AACF"}
@@ -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-core-dto';
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';
2
2
  import type { GiteaApiClient } from '#api/client.js';
3
3
  type GiteaIntegrationConnection = IntegrationConnection<'gitea'>;
4
4
  export declare class GiteaSourceControlProvider implements SourceControlProvider<GiteaIntegrationConnection> {
@@ -1 +1 @@
1
- {"version":3,"file":"source-control.d.ts","sourceRoot":"","sources":["../../src/core/source-control.ts"],"names":[],"mappings":"AACA,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,mCAAmC,CAAC;AAE3C,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,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,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
- import { buildProviderRepositoryId, MAX_REPOSITORY_FILE_BYTES, parseProviderRepositoryId } from '@shipfox/api-integration-core-dto';
3
2
  import { giteaProviderKind } from '@shipfox/api-integration-gitea-dto';
3
+ import { buildProviderRepositoryId, MAX_REPOSITORY_FILE_BYTES, parseProviderRepositoryId } 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 = /\/+$/;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/source-control.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\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-core-dto';\nimport {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';\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","buildProviderRepositoryId","MAX_REPOSITORY_FILE_BYTES","parseProviderRepositoryId","giteaProviderKind","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 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,4 +1,4 @@
1
- import { type GetIntegrationConnectionByIdFn, type PublishSourcePushFn, type RecordDeliveryOnlyFn, type StoredWebhookRequest, type WebhookProcessingResult } from '@shipfox/api-integration-core-dto';
1
+ import { type GetIntegrationConnectionByIdFn, type PublishSourcePushFn, type RecordDeliveryOnlyFn, type StoredWebhookRequest, type WebhookProcessingResult } from '@shipfox/api-integration-spi';
2
2
  import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
3
3
  export interface CreateGiteaWebhookProcessorOptions {
4
4
  coreDb: () => NodePgDatabase<Record<string, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-processor.d.ts","sourceRoot":"","sources":["../../src/core/webhook-processor.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC7B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAY9D,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC1E;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,qBAAqB,CAEvB"}
1
+ {"version":3,"file":"webhook-processor.d.ts","sourceRoot":"","sources":["../../src/core/webhook-processor.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC7B,MAAM,8BAA8B,CAAC;AAGtC,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAY9D,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC1E;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,qBAAqB,CAEvB"}
@@ -1,5 +1,5 @@
1
1
  import { Buffer } from 'node:buffer';
2
- import { decodeWebhookBody } from '@shipfox/api-integration-core-dto';
2
+ import { decodeWebhookBody } from '@shipfox/api-integration-spi';
3
3
  import { verifyHexHmacSignature } from '@shipfox/node-fastify';
4
4
  import { logger } from '@shipfox/node-opentelemetry';
5
5
  import { config } from '#config.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/webhook-processor.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {\n decodeWebhookBody,\n type GetIntegrationConnectionByIdFn,\n type PublishSourcePushFn,\n type RecordDeliveryOnlyFn,\n type StoredWebhookRequest,\n type WebhookProcessingResult,\n} from '@shipfox/api-integration-core-dto';\nimport {verifyHexHmacSignature} from '@shipfox/node-fastify';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {\n GiteaWebhookMalformedJsonError,\n GiteaWebhookMalformedPushPayloadError,\n handleGiteaWebhook,\n} from '#core/webhook.js';\n\nconst DELIVERY_HEADER = 'x-gitea-delivery';\nconst EVENT_HEADER = 'x-gitea-event';\nconst SIGNATURE_HEADER = 'x-gitea-signature';\n\nexport interface CreateGiteaWebhookProcessorOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport interface GiteaWebhookProcessor {\n process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;\n}\n\nexport function createGiteaWebhookProcessor(\n options: CreateGiteaWebhookProcessorOptions,\n): GiteaWebhookProcessor {\n return {process: (request) => processGiteaWebhookRequest(options, request)};\n}\n\nasync function processGiteaWebhookRequest(\n options: CreateGiteaWebhookProcessorOptions,\n request: StoredWebhookRequest,\n): Promise<WebhookProcessingResult> {\n if (request.route_id !== 'gitea') {\n throw new Error(`Gitea processor cannot process ${request.route_id} requests`);\n }\n\n const deliveryId = request.headers[DELIVERY_HEADER];\n const event = request.headers[EVENT_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n if (!deliveryId || !event || !signature) {\n return {outcome: 'discarded', reason: 'missing_required_input'};\n }\n\n const rawBody = Buffer.from(decodeWebhookBody(request.body));\n if (!verifyHexHmacSignature({rawBody, signature, secret: config.GITEA_WEBHOOK_SECRET})) {\n return {outcome: 'discarded', reason: 'invalid_signature', deliveryId};\n }\n\n try {\n const result = await options.coreDb().transaction(async (tx) =>\n handleGiteaWebhook({\n tx,\n deliveryId,\n event,\n rawBody: rawBody.toString('utf8'),\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n return result.outcome === 'duplicate'\n ? {outcome: 'duplicate', deliveryId}\n : {outcome: 'processed', deliveryId};\n } catch (error) {\n if (error instanceof GiteaWebhookMalformedJsonError) {\n logger().warn({deliveryId, err: error}, 'gitea webhook payload JSON parse failed');\n return {outcome: 'discarded', reason: 'malformed_payload', deliveryId};\n }\n if (error instanceof GiteaWebhookMalformedPushPayloadError) {\n logger().warn(\n {deliveryId, issues: error.issues},\n 'gitea webhook push payload failed schema validation',\n );\n return {outcome: 'discarded', reason: 'unsupported_event', deliveryId};\n }\n throw error;\n }\n}\n"],"names":["Buffer","decodeWebhookBody","verifyHexHmacSignature","logger","config","GiteaWebhookMalformedJsonError","GiteaWebhookMalformedPushPayloadError","handleGiteaWebhook","DELIVERY_HEADER","EVENT_HEADER","SIGNATURE_HEADER","createGiteaWebhookProcessor","options","process","request","processGiteaWebhookRequest","route_id","Error","deliveryId","headers","event","signature","outcome","reason","rawBody","from","body","secret","GITEA_WEBHOOK_SECRET","result","coreDb","transaction","tx","toString","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","error","warn","err","issues"],"mappings":"AAAA,SAAQA,MAAM,QAAO,cAAc;AACnC,SACEC,iBAAiB,QAMZ,oCAAoC;AAC3C,SAAQC,sBAAsB,QAAO,wBAAwB;AAC7D,SAAQC,MAAM,QAAO,8BAA8B;AAEnD,SAAQC,MAAM,QAAO,aAAa;AAClC,SACEC,8BAA8B,EAC9BC,qCAAqC,EACrCC,kBAAkB,QACb,mBAAmB;AAE1B,MAAMC,kBAAkB;AACxB,MAAMC,eAAe;AACrB,MAAMC,mBAAmB;AAazB,OAAO,SAASC,4BACdC,OAA2C;IAE3C,OAAO;QAACC,SAAS,CAACC,UAAYC,2BAA2BH,SAASE;IAAQ;AAC5E;AAEA,eAAeC,2BACbH,OAA2C,EAC3CE,OAA6B;IAE7B,IAAIA,QAAQE,QAAQ,KAAK,SAAS;QAChC,MAAM,IAAIC,MAAM,CAAC,+BAA+B,EAAEH,QAAQE,QAAQ,CAAC,SAAS,CAAC;IAC/E;IAEA,MAAME,aAAaJ,QAAQK,OAAO,CAACX,gBAAgB;IACnD,MAAMY,QAAQN,QAAQK,OAAO,CAACV,aAAa;IAC3C,MAAMY,YAAYP,QAAQK,OAAO,CAACT,iBAAiB;IACnD,IAAI,CAACQ,cAAc,CAACE,SAAS,CAACC,WAAW;QACvC,OAAO;YAACC,SAAS;YAAaC,QAAQ;QAAwB;IAChE;IAEA,MAAMC,UAAUxB,OAAOyB,IAAI,CAACxB,kBAAkBa,QAAQY,IAAI;IAC1D,IAAI,CAACxB,uBAAuB;QAACsB;QAASH;QAAWM,QAAQvB,OAAOwB,oBAAoB;IAAA,IAAI;QACtF,OAAO;YAACN,SAAS;YAAaC,QAAQ;YAAqBL;QAAU;IACvE;IAEA,IAAI;QACF,MAAMW,SAAS,MAAMjB,QAAQkB,MAAM,GAAGC,WAAW,CAAC,OAAOC,KACvDzB,mBAAmB;gBACjByB;gBACAd;gBACAE;gBACAI,SAASA,QAAQS,QAAQ,CAAC;gBAC1BC,mBAAmBtB,QAAQsB,iBAAiB;gBAC5CC,oBAAoBvB,QAAQuB,kBAAkB;gBAC9CC,8BAA8BxB,QAAQwB,4BAA4B;YACpE;QAEF,OAAOP,OAAOP,OAAO,KAAK,cACtB;YAACA,SAAS;YAAaJ;QAAU,IACjC;YAACI,SAAS;YAAaJ;QAAU;IACvC,EAAE,OAAOmB,OAAO;QACd,IAAIA,iBAAiBhC,gCAAgC;YACnDF,SAASmC,IAAI,CAAC;gBAACpB;gBAAYqB,KAAKF;YAAK,GAAG;YACxC,OAAO;gBAACf,SAAS;gBAAaC,QAAQ;gBAAqBL;YAAU;QACvE;QACA,IAAImB,iBAAiB/B,uCAAuC;YAC1DH,SAASmC,IAAI,CACX;gBAACpB;gBAAYsB,QAAQH,MAAMG,MAAM;YAAA,GACjC;YAEF,OAAO;gBAAClB,SAAS;gBAAaC,QAAQ;gBAAqBL;YAAU;QACvE;QACA,MAAMmB;IACR;AACF"}
1
+ {"version":3,"sources":["../../src/core/webhook-processor.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {\n decodeWebhookBody,\n type GetIntegrationConnectionByIdFn,\n type PublishSourcePushFn,\n type RecordDeliveryOnlyFn,\n type StoredWebhookRequest,\n type WebhookProcessingResult,\n} from '@shipfox/api-integration-spi';\nimport {verifyHexHmacSignature} from '@shipfox/node-fastify';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {\n GiteaWebhookMalformedJsonError,\n GiteaWebhookMalformedPushPayloadError,\n handleGiteaWebhook,\n} from '#core/webhook.js';\n\nconst DELIVERY_HEADER = 'x-gitea-delivery';\nconst EVENT_HEADER = 'x-gitea-event';\nconst SIGNATURE_HEADER = 'x-gitea-signature';\n\nexport interface CreateGiteaWebhookProcessorOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport interface GiteaWebhookProcessor {\n process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;\n}\n\nexport function createGiteaWebhookProcessor(\n options: CreateGiteaWebhookProcessorOptions,\n): GiteaWebhookProcessor {\n return {process: (request) => processGiteaWebhookRequest(options, request)};\n}\n\nasync function processGiteaWebhookRequest(\n options: CreateGiteaWebhookProcessorOptions,\n request: StoredWebhookRequest,\n): Promise<WebhookProcessingResult> {\n if (request.route_id !== 'gitea') {\n throw new Error(`Gitea processor cannot process ${request.route_id} requests`);\n }\n\n const deliveryId = request.headers[DELIVERY_HEADER];\n const event = request.headers[EVENT_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n if (!deliveryId || !event || !signature) {\n return {outcome: 'discarded', reason: 'missing_required_input'};\n }\n\n const rawBody = Buffer.from(decodeWebhookBody(request.body));\n if (!verifyHexHmacSignature({rawBody, signature, secret: config.GITEA_WEBHOOK_SECRET})) {\n return {outcome: 'discarded', reason: 'invalid_signature', deliveryId};\n }\n\n try {\n const result = await options.coreDb().transaction(async (tx) =>\n handleGiteaWebhook({\n tx,\n deliveryId,\n event,\n rawBody: rawBody.toString('utf8'),\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n return result.outcome === 'duplicate'\n ? {outcome: 'duplicate', deliveryId}\n : {outcome: 'processed', deliveryId};\n } catch (error) {\n if (error instanceof GiteaWebhookMalformedJsonError) {\n logger().warn({deliveryId, err: error}, 'gitea webhook payload JSON parse failed');\n return {outcome: 'discarded', reason: 'malformed_payload', deliveryId};\n }\n if (error instanceof GiteaWebhookMalformedPushPayloadError) {\n logger().warn(\n {deliveryId, issues: error.issues},\n 'gitea webhook push payload failed schema validation',\n );\n return {outcome: 'discarded', reason: 'unsupported_event', deliveryId};\n }\n throw error;\n }\n}\n"],"names":["Buffer","decodeWebhookBody","verifyHexHmacSignature","logger","config","GiteaWebhookMalformedJsonError","GiteaWebhookMalformedPushPayloadError","handleGiteaWebhook","DELIVERY_HEADER","EVENT_HEADER","SIGNATURE_HEADER","createGiteaWebhookProcessor","options","process","request","processGiteaWebhookRequest","route_id","Error","deliveryId","headers","event","signature","outcome","reason","rawBody","from","body","secret","GITEA_WEBHOOK_SECRET","result","coreDb","transaction","tx","toString","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","error","warn","err","issues"],"mappings":"AAAA,SAAQA,MAAM,QAAO,cAAc;AACnC,SACEC,iBAAiB,QAMZ,+BAA+B;AACtC,SAAQC,sBAAsB,QAAO,wBAAwB;AAC7D,SAAQC,MAAM,QAAO,8BAA8B;AAEnD,SAAQC,MAAM,QAAO,aAAa;AAClC,SACEC,8BAA8B,EAC9BC,qCAAqC,EACrCC,kBAAkB,QACb,mBAAmB;AAE1B,MAAMC,kBAAkB;AACxB,MAAMC,eAAe;AACrB,MAAMC,mBAAmB;AAazB,OAAO,SAASC,4BACdC,OAA2C;IAE3C,OAAO;QAACC,SAAS,CAACC,UAAYC,2BAA2BH,SAASE;IAAQ;AAC5E;AAEA,eAAeC,2BACbH,OAA2C,EAC3CE,OAA6B;IAE7B,IAAIA,QAAQE,QAAQ,KAAK,SAAS;QAChC,MAAM,IAAIC,MAAM,CAAC,+BAA+B,EAAEH,QAAQE,QAAQ,CAAC,SAAS,CAAC;IAC/E;IAEA,MAAME,aAAaJ,QAAQK,OAAO,CAACX,gBAAgB;IACnD,MAAMY,QAAQN,QAAQK,OAAO,CAACV,aAAa;IAC3C,MAAMY,YAAYP,QAAQK,OAAO,CAACT,iBAAiB;IACnD,IAAI,CAACQ,cAAc,CAACE,SAAS,CAACC,WAAW;QACvC,OAAO;YAACC,SAAS;YAAaC,QAAQ;QAAwB;IAChE;IAEA,MAAMC,UAAUxB,OAAOyB,IAAI,CAACxB,kBAAkBa,QAAQY,IAAI;IAC1D,IAAI,CAACxB,uBAAuB;QAACsB;QAASH;QAAWM,QAAQvB,OAAOwB,oBAAoB;IAAA,IAAI;QACtF,OAAO;YAACN,SAAS;YAAaC,QAAQ;YAAqBL;QAAU;IACvE;IAEA,IAAI;QACF,MAAMW,SAAS,MAAMjB,QAAQkB,MAAM,GAAGC,WAAW,CAAC,OAAOC,KACvDzB,mBAAmB;gBACjByB;gBACAd;gBACAE;gBACAI,SAASA,QAAQS,QAAQ,CAAC;gBAC1BC,mBAAmBtB,QAAQsB,iBAAiB;gBAC5CC,oBAAoBvB,QAAQuB,kBAAkB;gBAC9CC,8BAA8BxB,QAAQwB,4BAA4B;YACpE;QAEF,OAAOP,OAAOP,OAAO,KAAK,cACtB;YAACA,SAAS;YAAaJ;QAAU,IACjC;YAACI,SAAS;YAAaJ;QAAU;IACvC,EAAE,OAAOmB,OAAO;QACd,IAAIA,iBAAiBhC,gCAAgC;YACnDF,SAASmC,IAAI,CAAC;gBAACpB;gBAAYqB,KAAKF;YAAK,GAAG;YACxC,OAAO;gBAACf,SAAS;gBAAaC,QAAQ;gBAAqBL;YAAU;QACvE;QACA,IAAImB,iBAAiB/B,uCAAuC;YAC1DH,SAASmC,IAAI,CACX;gBAACpB;gBAAYsB,QAAQH,MAAMG,MAAM;YAAA,GACjC;YAEF,OAAO;gBAAClB,SAAS;gBAAaC,QAAQ;gBAAqBL;YAAU;QACvE;QACA,MAAMmB;IACR;AACF"}
@@ -1,5 +1,5 @@
1
- import { type GetIntegrationConnectionByIdFn, type IntegrationTx, type PublishSourcePushFn, type RecordDeliveryOnlyFn } from '@shipfox/api-integration-core-dto';
2
1
  import { type GiteaPushPayloadDto } from '@shipfox/api-integration-gitea-dto';
2
+ import { type GetIntegrationConnectionByIdFn, type IntegrationTx, type PublishSourcePushFn, type RecordDeliveryOnlyFn } from '@shipfox/api-integration-spi';
3
3
  export declare class GiteaWebhookMalformedJsonError extends Error {
4
4
  constructor(options: {
5
5
  cause: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/core/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAE1B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,KAAK,mBAAmB,EAGzB,MAAM,oCAAoC,CAAC;AAQ5C,qBAAa,8BAA+B,SAAQ,KAAK;gBAC3C,OAAO,EAAE;QAAC,KAAK,EAAE,OAAO,CAAA;KAAC;CAItC;AAED,qBAAa,qCAAsC,SAAQ,KAAK;aAClC,MAAM,EAAE,OAAO;gBAAf,MAAM,EAAE,OAAO;CAI5C;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,MAAM,sBAAsB,GAC9B,WAAW,GACX,WAAW,GACX,SAAS,GACT,aAAa,GACb,qBAAqB,CAAC;AAC1B,MAAM,MAAM,yBAAyB,GAAG,sBAAsB,GAAG,eAAe,CAAC;AAMjF,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC;IAAC,OAAO,EAAE,yBAAyB,CAAA;CAAC,CAAC,CAoB/C;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC;IAAC,OAAO,EAAE,sBAAsB,CAAA;CAAC,CAAC,CAuF5C"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/core/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,mBAAmB,EAGzB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAE1B,MAAM,8BAA8B,CAAC;AAQtC,qBAAa,8BAA+B,SAAQ,KAAK;gBAC3C,OAAO,EAAE;QAAC,KAAK,EAAE,OAAO,CAAA;KAAC;CAItC;AAED,qBAAa,qCAAsC,SAAQ,KAAK;aAClC,MAAM,EAAE,OAAO;gBAAf,MAAM,EAAE,OAAO;CAI5C;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,MAAM,sBAAsB,GAC9B,WAAW,GACX,WAAW,GACX,SAAS,GACT,aAAa,GACb,qBAAqB,CAAC;AAC1B,MAAM,MAAM,yBAAyB,GAAG,sBAAsB,GAAG,eAAe,CAAC;AAMjF,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC;IAAC,OAAO,EAAE,yBAAyB,CAAA;CAAC,CAAC,CAoB/C;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC;IAAC,OAAO,EAAE,sBAAsB,CAAA;CAAC,CAAC,CAuF5C"}
@@ -1,5 +1,5 @@
1
- import { buildProviderRepositoryId } from '@shipfox/api-integration-core-dto';
2
1
  import { giteaProviderKind, giteaPushPayloadSchema } from '@shipfox/api-integration-gitea-dto';
2
+ import { buildProviderRepositoryId } from '@shipfox/api-integration-spi';
3
3
  import { logger } from '@shipfox/node-opentelemetry';
4
4
  import { getGiteaConnectionByOrg } from '#db/connections.js';
5
5
  const REFS_HEADS_PREFIX = 'refs/heads/';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import {\n buildProviderRepositoryId,\n type GetIntegrationConnectionByIdFn,\n type IntegrationTx,\n type PublishSourcePushFn,\n type RecordDeliveryOnlyFn,\n type SourcePushPayload,\n} from '@shipfox/api-integration-core-dto';\nimport {\n type GiteaPushPayloadDto,\n giteaProviderKind,\n giteaPushPayloadSchema,\n} from '@shipfox/api-integration-gitea-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {getGiteaConnectionByOrg} from '#db/connections.js';\n\nconst REFS_HEADS_PREFIX = 'refs/heads/';\n// Gitea sends a `push` webhook for a branch deletion with `after` set to this all-zero SHA.\nconst DELETED_BRANCH_SHA = '0'.repeat(40);\n\nexport class GiteaWebhookMalformedJsonError extends Error {\n constructor(options: {cause: unknown}) {\n super('Gitea webhook payload is not valid JSON', {cause: options.cause});\n this.name = 'GiteaWebhookMalformedJsonError';\n }\n}\n\nexport class GiteaWebhookMalformedPushPayloadError extends Error {\n constructor(public readonly issues: unknown) {\n super('Gitea webhook push payload failed schema validation');\n this.name = 'GiteaWebhookMalformedPushPayloadError';\n }\n}\n\nexport interface HandleGiteaWebhookParams {\n tx: IntegrationTx;\n deliveryId: string;\n event: string;\n rawBody: string;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport interface HandleGiteaPushParams {\n tx: IntegrationTx;\n deliveryId: string;\n payload: GiteaPushPayloadDto;\n rawPayload: unknown;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport type HandleGiteaPushOutcome =\n | 'published'\n | 'duplicate'\n | 'deleted'\n | 'unknown-org'\n | 'inactive-connection';\nexport type HandleGiteaWebhookOutcome = HandleGiteaPushOutcome | 'recorded-only';\n\nfunction isBranchDeletion(after: string): boolean {\n return after === DELETED_BRANCH_SHA;\n}\n\nexport async function handleGiteaWebhook(\n params: HandleGiteaWebhookParams,\n): Promise<{outcome: HandleGiteaWebhookOutcome}> {\n if (params.event !== 'push') {\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: giteaProviderKind,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'recorded-only'};\n }\n\n const {payload, rawPayload} = parseGiteaPushPayload(params.rawBody);\n return handleGiteaPush({\n tx: params.tx,\n deliveryId: params.deliveryId,\n payload,\n rawPayload,\n publishSourcePush: params.publishSourcePush,\n recordDeliveryOnly: params.recordDeliveryOnly,\n getIntegrationConnectionById: params.getIntegrationConnectionById,\n });\n}\n\nexport async function handleGiteaPush(\n params: HandleGiteaPushParams,\n): Promise<{outcome: HandleGiteaPushOutcome}> {\n // Dropping deletes before `publishSourcePush` avoids project and trigger fan-out.\n if (isBranchDeletion(params.payload.after)) {\n return {outcome: 'deleted'};\n }\n\n const owner = params.payload.repository.owner.username;\n // Gitea org routes are case-insensitive, and the connect flow stores them lower-cased.\n const giteaConnection = await getGiteaConnectionByOrg(owner.toLowerCase(), {tx: params.tx});\n if (!giteaConnection) {\n logger().warn(\n {deliveryId: params.deliveryId, org: owner},\n 'gitea webhook: unknown org, dropping',\n );\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: giteaProviderKind,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'unknown-org'};\n }\n\n const connection = await params.getIntegrationConnectionById(giteaConnection.connectionId, {\n tx: params.tx,\n });\n if (!connection) {\n logger().warn(\n {deliveryId: params.deliveryId, org: owner, connectionId: giteaConnection.connectionId},\n 'gitea webhook: org has no connection, dropping',\n );\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: giteaProviderKind,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'unknown-org'};\n }\n\n if (connection.lifecycleStatus !== 'active') {\n const logContext = {\n deliveryId: params.deliveryId,\n org: owner,\n connectionId: connection.id,\n workspaceId: connection.workspaceId,\n lifecycleStatus: connection.lifecycleStatus,\n };\n // `disabled` is an expected steady state; only `error` is anomalous.\n if (connection.lifecycleStatus === 'error') {\n logger().warn(logContext, 'gitea webhook: connection in error state, dropping');\n } else {\n logger().info(logContext, 'gitea webhook: connection disabled, dropping');\n }\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: giteaProviderKind,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'inactive-connection'};\n }\n\n const ref = stripRefsHeads(params.payload.ref);\n const defaultBranch = params.payload.repository.default_branch;\n const push: SourcePushPayload = {\n // Match the source-control adapter's owner/name repository id.\n externalRepositoryId: buildProviderRepositoryId(\n giteaProviderKind,\n `${owner}/${params.payload.repository.name}`,\n ),\n ref,\n headCommitSha: params.payload.after,\n defaultBranch,\n isDefaultBranch: ref === defaultBranch,\n };\n const result = await params.publishSourcePush({\n tx: params.tx,\n provider: giteaProviderKind,\n source: connection.slug,\n workspaceId: connection.workspaceId,\n connectionId: connection.id,\n connectionName: connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n rawPayload: params.rawPayload,\n push,\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nfunction stripRefsHeads(ref: string): string {\n return ref.startsWith(REFS_HEADS_PREFIX) ? ref.slice(REFS_HEADS_PREFIX.length) : ref;\n}\n\nfunction parseGiteaPushPayload(rawBody: string): {\n payload: GiteaPushPayloadDto;\n rawPayload: unknown;\n} {\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(rawBody);\n } catch (error) {\n throw new GiteaWebhookMalformedJsonError({cause: error});\n }\n\n const validated = giteaPushPayloadSchema.safeParse(parsedJson);\n if (!validated.success) {\n throw new GiteaWebhookMalformedPushPayloadError(validated.error.issues);\n }\n\n return {payload: validated.data, rawPayload: parsedJson};\n}\n"],"names":["buildProviderRepositoryId","giteaProviderKind","giteaPushPayloadSchema","logger","getGiteaConnectionByOrg","REFS_HEADS_PREFIX","DELETED_BRANCH_SHA","repeat","GiteaWebhookMalformedJsonError","Error","options","cause","name","GiteaWebhookMalformedPushPayloadError","issues","isBranchDeletion","after","handleGiteaWebhook","params","event","recordDeliveryOnly","tx","provider","deliveryId","outcome","payload","rawPayload","parseGiteaPushPayload","rawBody","handleGiteaPush","publishSourcePush","getIntegrationConnectionById","owner","repository","username","giteaConnection","toLowerCase","warn","org","connection","connectionId","lifecycleStatus","logContext","id","workspaceId","info","ref","stripRefsHeads","defaultBranch","default_branch","push","externalRepositoryId","headCommitSha","isDefaultBranch","result","source","slug","connectionName","displayName","receivedAt","Date","toISOString","published","startsWith","slice","length","parsedJson","JSON","parse","error","validated","safeParse","success","data"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import {\n type GiteaPushPayloadDto,\n giteaProviderKind,\n giteaPushPayloadSchema,\n} from '@shipfox/api-integration-gitea-dto';\nimport {\n buildProviderRepositoryId,\n type GetIntegrationConnectionByIdFn,\n type IntegrationTx,\n type PublishSourcePushFn,\n type RecordDeliveryOnlyFn,\n type SourcePushPayload,\n} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {getGiteaConnectionByOrg} from '#db/connections.js';\n\nconst REFS_HEADS_PREFIX = 'refs/heads/';\n// Gitea sends a `push` webhook for a branch deletion with `after` set to this all-zero SHA.\nconst DELETED_BRANCH_SHA = '0'.repeat(40);\n\nexport class GiteaWebhookMalformedJsonError extends Error {\n constructor(options: {cause: unknown}) {\n super('Gitea webhook payload is not valid JSON', {cause: options.cause});\n this.name = 'GiteaWebhookMalformedJsonError';\n }\n}\n\nexport class GiteaWebhookMalformedPushPayloadError extends Error {\n constructor(public readonly issues: unknown) {\n super('Gitea webhook push payload failed schema validation');\n this.name = 'GiteaWebhookMalformedPushPayloadError';\n }\n}\n\nexport interface HandleGiteaWebhookParams {\n tx: IntegrationTx;\n deliveryId: string;\n event: string;\n rawBody: string;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport interface HandleGiteaPushParams {\n tx: IntegrationTx;\n deliveryId: string;\n payload: GiteaPushPayloadDto;\n rawPayload: unknown;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport type HandleGiteaPushOutcome =\n | 'published'\n | 'duplicate'\n | 'deleted'\n | 'unknown-org'\n | 'inactive-connection';\nexport type HandleGiteaWebhookOutcome = HandleGiteaPushOutcome | 'recorded-only';\n\nfunction isBranchDeletion(after: string): boolean {\n return after === DELETED_BRANCH_SHA;\n}\n\nexport async function handleGiteaWebhook(\n params: HandleGiteaWebhookParams,\n): Promise<{outcome: HandleGiteaWebhookOutcome}> {\n if (params.event !== 'push') {\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: giteaProviderKind,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'recorded-only'};\n }\n\n const {payload, rawPayload} = parseGiteaPushPayload(params.rawBody);\n return handleGiteaPush({\n tx: params.tx,\n deliveryId: params.deliveryId,\n payload,\n rawPayload,\n publishSourcePush: params.publishSourcePush,\n recordDeliveryOnly: params.recordDeliveryOnly,\n getIntegrationConnectionById: params.getIntegrationConnectionById,\n });\n}\n\nexport async function handleGiteaPush(\n params: HandleGiteaPushParams,\n): Promise<{outcome: HandleGiteaPushOutcome}> {\n // Dropping deletes before `publishSourcePush` avoids project and trigger fan-out.\n if (isBranchDeletion(params.payload.after)) {\n return {outcome: 'deleted'};\n }\n\n const owner = params.payload.repository.owner.username;\n // Gitea org routes are case-insensitive, and the connect flow stores them lower-cased.\n const giteaConnection = await getGiteaConnectionByOrg(owner.toLowerCase(), {tx: params.tx});\n if (!giteaConnection) {\n logger().warn(\n {deliveryId: params.deliveryId, org: owner},\n 'gitea webhook: unknown org, dropping',\n );\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: giteaProviderKind,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'unknown-org'};\n }\n\n const connection = await params.getIntegrationConnectionById(giteaConnection.connectionId, {\n tx: params.tx,\n });\n if (!connection) {\n logger().warn(\n {deliveryId: params.deliveryId, org: owner, connectionId: giteaConnection.connectionId},\n 'gitea webhook: org has no connection, dropping',\n );\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: giteaProviderKind,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'unknown-org'};\n }\n\n if (connection.lifecycleStatus !== 'active') {\n const logContext = {\n deliveryId: params.deliveryId,\n org: owner,\n connectionId: connection.id,\n workspaceId: connection.workspaceId,\n lifecycleStatus: connection.lifecycleStatus,\n };\n // `disabled` is an expected steady state; only `error` is anomalous.\n if (connection.lifecycleStatus === 'error') {\n logger().warn(logContext, 'gitea webhook: connection in error state, dropping');\n } else {\n logger().info(logContext, 'gitea webhook: connection disabled, dropping');\n }\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: giteaProviderKind,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'inactive-connection'};\n }\n\n const ref = stripRefsHeads(params.payload.ref);\n const defaultBranch = params.payload.repository.default_branch;\n const push: SourcePushPayload = {\n // Match the source-control adapter's owner/name repository id.\n externalRepositoryId: buildProviderRepositoryId(\n giteaProviderKind,\n `${owner}/${params.payload.repository.name}`,\n ),\n ref,\n headCommitSha: params.payload.after,\n defaultBranch,\n isDefaultBranch: ref === defaultBranch,\n };\n const result = await params.publishSourcePush({\n tx: params.tx,\n provider: giteaProviderKind,\n source: connection.slug,\n workspaceId: connection.workspaceId,\n connectionId: connection.id,\n connectionName: connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n rawPayload: params.rawPayload,\n push,\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nfunction stripRefsHeads(ref: string): string {\n return ref.startsWith(REFS_HEADS_PREFIX) ? ref.slice(REFS_HEADS_PREFIX.length) : ref;\n}\n\nfunction parseGiteaPushPayload(rawBody: string): {\n payload: GiteaPushPayloadDto;\n rawPayload: unknown;\n} {\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(rawBody);\n } catch (error) {\n throw new GiteaWebhookMalformedJsonError({cause: error});\n }\n\n const validated = giteaPushPayloadSchema.safeParse(parsedJson);\n if (!validated.success) {\n throw new GiteaWebhookMalformedPushPayloadError(validated.error.issues);\n }\n\n return {payload: validated.data, rawPayload: parsedJson};\n}\n"],"names":["giteaProviderKind","giteaPushPayloadSchema","buildProviderRepositoryId","logger","getGiteaConnectionByOrg","REFS_HEADS_PREFIX","DELETED_BRANCH_SHA","repeat","GiteaWebhookMalformedJsonError","Error","options","cause","name","GiteaWebhookMalformedPushPayloadError","issues","isBranchDeletion","after","handleGiteaWebhook","params","event","recordDeliveryOnly","tx","provider","deliveryId","outcome","payload","rawPayload","parseGiteaPushPayload","rawBody","handleGiteaPush","publishSourcePush","getIntegrationConnectionById","owner","repository","username","giteaConnection","toLowerCase","warn","org","connection","connectionId","lifecycleStatus","logContext","id","workspaceId","info","ref","stripRefsHeads","defaultBranch","default_branch","push","externalRepositoryId","headCommitSha","isDefaultBranch","result","source","slug","connectionName","displayName","receivedAt","Date","toISOString","published","startsWith","slice","length","parsedJson","JSON","parse","error","validated","safeParse","success","data"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GetIntegrationConnectionByIdFn, IntegrationConnection, PublishSourcePushFn, RecordDeliveryOnlyFn } from '@shipfox/api-integration-core-dto';
1
+ import type { GetIntegrationConnectionByIdFn, IntegrationConnection, PublishSourcePushFn, RecordDeliveryOnlyFn } from '@shipfox/api-integration-spi';
2
2
  import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
3
3
  import { type GiteaApiClient } from '#api/client.js';
4
4
  import type { ConnectGiteaConnectionInput } from '#core/connect.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,8BAA8B,EAC9B,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAuB,KAAK,cAAc,EAAC,MAAM,gBAAgB,CAAC;AACzE,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,kBAAkB,CAAC;AAElE,OAAO,EAAC,0BAA0B,EAAC,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAC,OAAO,EAAE,EAAE,EAAC,MAAM,WAAW,CAAC;AACtC,OAAO,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAC;AAIjD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,oBAAoB,EAAC,MAAM,gBAAgB,CAAC;AACpD,YAAY,EAAC,2BAA2B,EAAC,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAC,0BAA0B,EAAC,MAAM,yBAAyB,CAAC;AACnE,YAAY,EACV,kCAAkC,EAClC,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAC,2BAA2B,EAAC,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAC,eAAe,EAAE,2BAA2B,EAAC,MAAM,oBAAoB,CAAC;AACrF,OAAO,EACL,gCAAgC,EAChC,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,OAAO,EAAE,EAAE,EAAE,cAAc,EAAC,CAAC;AAErC,MAAM,WAAW,qCAAqC;IACpD,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACnC,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,sBAAsB,EAAE,CACtB,KAAK,EAAE,2BAA2B,KAC/B,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,qCAAqC;;;;;;sCAUvD;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;EAmB9F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,8BAA8B,EAC9B,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAuB,KAAK,cAAc,EAAC,MAAM,gBAAgB,CAAC;AACzE,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,kBAAkB,CAAC;AAElE,OAAO,EAAC,0BAA0B,EAAC,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAC,OAAO,EAAE,EAAE,EAAC,MAAM,WAAW,CAAC;AACtC,OAAO,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAC;AAIjD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,oBAAoB,EAAC,MAAM,gBAAgB,CAAC;AACpD,YAAY,EAAC,2BAA2B,EAAC,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAC,0BAA0B,EAAC,MAAM,yBAAyB,CAAC;AACnE,YAAY,EACV,kCAAkC,EAClC,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAC,2BAA2B,EAAC,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAC,eAAe,EAAE,2BAA2B,EAAC,MAAM,oBAAoB,CAAC;AACrF,OAAO,EACL,gCAAgC,EAChC,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,OAAO,EAAE,EAAE,EAAE,cAAc,EAAC,CAAC;AAErC,MAAM,WAAW,qCAAqC;IACpD,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACnC,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,sBAAsB,EAAE,CACtB,KAAK,EAAE,2BAA2B,KAC/B,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,qCAAqC;;;;;;sCAUvD;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;EAmB9F"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n GetIntegrationConnectionByIdFn,\n IntegrationConnection,\n PublishSourcePushFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-core-dto';\nimport {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {createGiteaApiClient, type GiteaApiClient} from '#api/client.js';\nimport type {ConnectGiteaConnectionInput} from '#core/connect.js';\nimport {giteaConnectionExternalUrl} from '#core/connection-url.js';\nimport {GiteaSourceControlProvider} from '#core/source-control.js';\nimport {createGiteaWebhookProcessor} from '#core/webhook-processor.js';\nimport {closeDb, db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {createGiteaConnectionRoutes} from '#presentation/routes/connections.js';\nimport {createGiteaWebhookRoutes} from '#presentation/routes/webhooks.js';\n\nexport type {\n GiteaApiClient,\n GiteaFileContent,\n GiteaRepository,\n GiteaRepositoryPage,\n GiteaTree,\n GiteaTreeBlob,\n} from '#api/client.js';\nexport {createGiteaApiClient} from '#api/client.js';\nexport type {ConnectGiteaConnectionInput} from '#core/connect.js';\nexport {handleGiteaConnect} from '#core/connect.js';\nexport {\n GiteaIntegrationProviderError,\n GiteaOrgAlreadyLinkedError,\n GiteaOrganizationNotFoundError,\n} from '#core/errors.js';\nexport {GiteaSourceControlProvider} from '#core/source-control.js';\nexport type {\n CreateGiteaWebhookProcessorOptions,\n GiteaWebhookProcessor,\n} from '#core/webhook-processor.js';\nexport {createGiteaWebhookProcessor} from '#core/webhook-processor.js';\nexport type {GiteaConnection, UpsertGiteaConnectionParams} from '#db/connections.js';\nexport {\n getGiteaConnectionByConnectionId,\n getGiteaConnectionByOrg,\n upsertGiteaConnection,\n} from '#db/connections.js';\nexport {closeDb, db, migrationsPath};\n\nexport interface CreateGiteaIntegrationProviderOptions {\n gitea?: GiteaApiClient | undefined;\n getExistingGiteaConnection: (input: {\n org: string;\n }) => Promise<IntegrationConnection<'gitea'> | undefined>;\n connectGiteaConnection: (\n input: ConnectGiteaConnectionInput,\n ) => Promise<IntegrationConnection<'gitea'>>;\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport function createGiteaIntegrationProvider(options: CreateGiteaIntegrationProviderOptions) {\n const gitea = options.gitea ?? createGiteaApiClient();\n const webhookProcessor = createGiteaWebhookProcessor(options);\n\n return {\n provider: giteaProviderKind,\n displayName: 'Gitea',\n adapters: {\n source_control: new GiteaSourceControlProvider(gitea),\n },\n connectionExternalUrl(connection: {externalAccountId: string}): Promise<string | undefined> {\n return Promise.resolve(giteaConnectionExternalUrl(connection.externalAccountId));\n },\n routes: [\n createGiteaConnectionRoutes({\n gitea,\n getExistingGiteaConnection: options.getExistingGiteaConnection,\n connectGiteaConnection: options.connectGiteaConnection,\n }),\n createGiteaWebhookRoutes({\n coreDb: options.coreDb,\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n processor: webhookProcessor,\n }),\n ],\n webhookProcessors: [{routeIds: ['gitea'] as const, processor: webhookProcessor}],\n };\n}\n"],"names":["giteaProviderKind","createGiteaApiClient","giteaConnectionExternalUrl","GiteaSourceControlProvider","createGiteaWebhookProcessor","closeDb","db","migrationsPath","createGiteaConnectionRoutes","createGiteaWebhookRoutes","handleGiteaConnect","GiteaIntegrationProviderError","GiteaOrgAlreadyLinkedError","GiteaOrganizationNotFoundError","getGiteaConnectionByConnectionId","getGiteaConnectionByOrg","upsertGiteaConnection","createGiteaIntegrationProvider","options","gitea","webhookProcessor","provider","displayName","adapters","source_control","connectionExternalUrl","connection","Promise","resolve","externalAccountId","routes","getExistingGiteaConnection","connectGiteaConnection","coreDb","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","processor","webhookProcessors","routeIds"],"mappings":"AAMA,SAAQA,iBAAiB,QAAO,qCAAqC;AAErE,SAAQC,oBAAoB,QAA4B,iBAAiB;AAEzE,SAAQC,0BAA0B,QAAO,0BAA0B;AACnE,SAAQC,0BAA0B,QAAO,0BAA0B;AACnE,SAAQC,2BAA2B,QAAO,6BAA6B;AACvE,SAAQC,OAAO,EAAEC,EAAE,QAAO,YAAY;AACtC,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,2BAA2B,QAAO,sCAAsC;AAChF,SAAQC,wBAAwB,QAAO,mCAAmC;AAU1E,SAAQR,oBAAoB,QAAO,iBAAiB;AAEpD,SAAQS,kBAAkB,QAAO,mBAAmB;AACpD,SACEC,6BAA6B,EAC7BC,0BAA0B,EAC1BC,8BAA8B,QACzB,kBAAkB;AACzB,SAAQV,0BAA0B,QAAO,0BAA0B;AAKnE,SAAQC,2BAA2B,QAAO,6BAA6B;AAEvE,SACEU,gCAAgC,EAChCC,uBAAuB,EACvBC,qBAAqB,QAChB,qBAAqB;AAC5B,SAAQX,OAAO,EAAEC,EAAE,EAAEC,cAAc,GAAE;AAgBrC,OAAO,SAASU,+BAA+BC,OAA8C;IAC3F,MAAMC,QAAQD,QAAQC,KAAK,IAAIlB;IAC/B,MAAMmB,mBAAmBhB,4BAA4Bc;IAErD,OAAO;QACLG,UAAUrB;QACVsB,aAAa;QACbC,UAAU;YACRC,gBAAgB,IAAIrB,2BAA2BgB;QACjD;QACAM,uBAAsBC,UAAuC;YAC3D,OAAOC,QAAQC,OAAO,CAAC1B,2BAA2BwB,WAAWG,iBAAiB;QAChF;QACAC,QAAQ;YACNtB,4BAA4B;gBAC1BW;gBACAY,4BAA4Bb,QAAQa,0BAA0B;gBAC9DC,wBAAwBd,QAAQc,sBAAsB;YACxD;YACAvB,yBAAyB;gBACvBwB,QAAQf,QAAQe,MAAM;gBACtBC,mBAAmBhB,QAAQgB,iBAAiB;gBAC5CC,oBAAoBjB,QAAQiB,kBAAkB;gBAC9CC,8BAA8BlB,QAAQkB,4BAA4B;gBAClEC,WAAWjB;YACb;SACD;QACDkB,mBAAmB;YAAC;gBAACC,UAAU;oBAAC;iBAAQ;gBAAWF,WAAWjB;YAAgB;SAAE;IAClF;AACF"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';\nimport type {\n GetIntegrationConnectionByIdFn,\n IntegrationConnection,\n PublishSourcePushFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-spi';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {createGiteaApiClient, type GiteaApiClient} from '#api/client.js';\nimport type {ConnectGiteaConnectionInput} from '#core/connect.js';\nimport {giteaConnectionExternalUrl} from '#core/connection-url.js';\nimport {GiteaSourceControlProvider} from '#core/source-control.js';\nimport {createGiteaWebhookProcessor} from '#core/webhook-processor.js';\nimport {closeDb, db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {createGiteaConnectionRoutes} from '#presentation/routes/connections.js';\nimport {createGiteaWebhookRoutes} from '#presentation/routes/webhooks.js';\n\nexport type {\n GiteaApiClient,\n GiteaFileContent,\n GiteaRepository,\n GiteaRepositoryPage,\n GiteaTree,\n GiteaTreeBlob,\n} from '#api/client.js';\nexport {createGiteaApiClient} from '#api/client.js';\nexport type {ConnectGiteaConnectionInput} from '#core/connect.js';\nexport {handleGiteaConnect} from '#core/connect.js';\nexport {\n GiteaIntegrationProviderError,\n GiteaOrgAlreadyLinkedError,\n GiteaOrganizationNotFoundError,\n} from '#core/errors.js';\nexport {GiteaSourceControlProvider} from '#core/source-control.js';\nexport type {\n CreateGiteaWebhookProcessorOptions,\n GiteaWebhookProcessor,\n} from '#core/webhook-processor.js';\nexport {createGiteaWebhookProcessor} from '#core/webhook-processor.js';\nexport type {GiteaConnection, UpsertGiteaConnectionParams} from '#db/connections.js';\nexport {\n getGiteaConnectionByConnectionId,\n getGiteaConnectionByOrg,\n upsertGiteaConnection,\n} from '#db/connections.js';\nexport {closeDb, db, migrationsPath};\n\nexport interface CreateGiteaIntegrationProviderOptions {\n gitea?: GiteaApiClient | undefined;\n getExistingGiteaConnection: (input: {\n org: string;\n }) => Promise<IntegrationConnection<'gitea'> | undefined>;\n connectGiteaConnection: (\n input: ConnectGiteaConnectionInput,\n ) => Promise<IntegrationConnection<'gitea'>>;\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport function createGiteaIntegrationProvider(options: CreateGiteaIntegrationProviderOptions) {\n const gitea = options.gitea ?? createGiteaApiClient();\n const webhookProcessor = createGiteaWebhookProcessor(options);\n\n return {\n provider: giteaProviderKind,\n displayName: 'Gitea',\n adapters: {\n source_control: new GiteaSourceControlProvider(gitea),\n },\n connectionExternalUrl(connection: {externalAccountId: string}): Promise<string | undefined> {\n return Promise.resolve(giteaConnectionExternalUrl(connection.externalAccountId));\n },\n routes: [\n createGiteaConnectionRoutes({\n gitea,\n getExistingGiteaConnection: options.getExistingGiteaConnection,\n connectGiteaConnection: options.connectGiteaConnection,\n }),\n createGiteaWebhookRoutes({\n coreDb: options.coreDb,\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n processor: webhookProcessor,\n }),\n ],\n webhookProcessors: [{routeIds: ['gitea'] as const, processor: webhookProcessor}],\n };\n}\n"],"names":["giteaProviderKind","createGiteaApiClient","giteaConnectionExternalUrl","GiteaSourceControlProvider","createGiteaWebhookProcessor","closeDb","db","migrationsPath","createGiteaConnectionRoutes","createGiteaWebhookRoutes","handleGiteaConnect","GiteaIntegrationProviderError","GiteaOrgAlreadyLinkedError","GiteaOrganizationNotFoundError","getGiteaConnectionByConnectionId","getGiteaConnectionByOrg","upsertGiteaConnection","createGiteaIntegrationProvider","options","gitea","webhookProcessor","provider","displayName","adapters","source_control","connectionExternalUrl","connection","Promise","resolve","externalAccountId","routes","getExistingGiteaConnection","connectGiteaConnection","coreDb","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","processor","webhookProcessors","routeIds"],"mappings":"AAAA,SAAQA,iBAAiB,QAAO,qCAAqC;AAQrE,SAAQC,oBAAoB,QAA4B,iBAAiB;AAEzE,SAAQC,0BAA0B,QAAO,0BAA0B;AACnE,SAAQC,0BAA0B,QAAO,0BAA0B;AACnE,SAAQC,2BAA2B,QAAO,6BAA6B;AACvE,SAAQC,OAAO,EAAEC,EAAE,QAAO,YAAY;AACtC,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,2BAA2B,QAAO,sCAAsC;AAChF,SAAQC,wBAAwB,QAAO,mCAAmC;AAU1E,SAAQR,oBAAoB,QAAO,iBAAiB;AAEpD,SAAQS,kBAAkB,QAAO,mBAAmB;AACpD,SACEC,6BAA6B,EAC7BC,0BAA0B,EAC1BC,8BAA8B,QACzB,kBAAkB;AACzB,SAAQV,0BAA0B,QAAO,0BAA0B;AAKnE,SAAQC,2BAA2B,QAAO,6BAA6B;AAEvE,SACEU,gCAAgC,EAChCC,uBAAuB,EACvBC,qBAAqB,QAChB,qBAAqB;AAC5B,SAAQX,OAAO,EAAEC,EAAE,EAAEC,cAAc,GAAE;AAgBrC,OAAO,SAASU,+BAA+BC,OAA8C;IAC3F,MAAMC,QAAQD,QAAQC,KAAK,IAAIlB;IAC/B,MAAMmB,mBAAmBhB,4BAA4Bc;IAErD,OAAO;QACLG,UAAUrB;QACVsB,aAAa;QACbC,UAAU;YACRC,gBAAgB,IAAIrB,2BAA2BgB;QACjD;QACAM,uBAAsBC,UAAuC;YAC3D,OAAOC,QAAQC,OAAO,CAAC1B,2BAA2BwB,WAAWG,iBAAiB;QAChF;QACAC,QAAQ;YACNtB,4BAA4B;gBAC1BW;gBACAY,4BAA4Bb,QAAQa,0BAA0B;gBAC9DC,wBAAwBd,QAAQc,sBAAsB;YACxD;YACAvB,yBAAyB;gBACvBwB,QAAQf,QAAQe,MAAM;gBACtBC,mBAAmBhB,QAAQgB,iBAAiB;gBAC5CC,oBAAoBjB,QAAQiB,kBAAkB;gBAC9CC,8BAA8BlB,QAAQkB,4BAA4B;gBAClEC,WAAWjB;YACb;SACD;QACDkB,mBAAmB;YAAC;gBAACC,UAAU;oBAAC;iBAAQ;gBAAWF,WAAWjB;YAAgB;SAAE;IAClF;AACF"}
@@ -1,4 +1,4 @@
1
- import { type IntegrationConnection } from '@shipfox/api-integration-core-dto';
1
+ import type { IntegrationConnection } from '@shipfox/api-integration-spi';
2
2
  export declare function toIntegrationConnectionDto(connection: IntegrationConnection<'gitea'>, options?: {
3
3
  externalUrl?: string | undefined;
4
4
  }): {
@@ -11,7 +11,7 @@ export declare function toIntegrationConnectionDto(connection: IntegrationConnec
11
11
  external_account_id: string;
12
12
  slug: string;
13
13
  display_name: string;
14
- lifecycle_status: import("@shipfox/api-integration-core-dto").IntegrationConnectionLifecycleStatus;
15
- capabilities: import("@shipfox/api-integration-core-dto").IntegrationCapability[];
14
+ lifecycle_status: import("@shipfox/api-integration-spi").IntegrationConnectionLifecycleStatus;
15
+ capabilities: string[];
16
16
  };
17
17
  //# sourceMappingURL=integrations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,mCAAmC,CAAC;AAE3C,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,qBAAqB,CAAC,OAAO,CAAC,EAC1C,OAAO,GAAE;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAM;;;;;;;;;;;;EAMjD"}
1
+ {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,8BAA8B,CAAC;AAExE,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,qBAAqB,CAAC,OAAO,CAAC,EAC1C,OAAO,GAAE;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAM;;;;;;;;;;;;EAGjD"}