@alpacakit/integrations 0.1.0-beta.21 → 0.1.0-beta.25

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 (53) hide show
  1. package/README.md +7 -0
  2. package/dist/catalog.d.ts +12 -0
  3. package/dist/catalog.d.ts.map +1 -0
  4. package/dist/catalog.js +82 -0
  5. package/dist/code-hosts/account.d.ts.map +1 -1
  6. package/dist/code-hosts/account.js +4 -1
  7. package/dist/code-hosts/facets.d.ts +2 -1
  8. package/dist/code-hosts/facets.d.ts.map +1 -1
  9. package/dist/code-hosts/facets.js +65 -12
  10. package/dist/code-hosts/gitea-pull-request-snapshot.d.ts +22 -0
  11. package/dist/code-hosts/gitea-pull-request-snapshot.d.ts.map +1 -0
  12. package/dist/code-hosts/gitea-pull-request-snapshot.js +226 -0
  13. package/dist/code-hosts/gitea.d.ts.map +1 -1
  14. package/dist/code-hosts/gitea.js +50 -49
  15. package/dist/code-hosts/github.d.ts.map +1 -1
  16. package/dist/code-hosts/github.js +34 -6
  17. package/dist/code-hosts/gitlab.d.ts.map +1 -1
  18. package/dist/code-hosts/gitlab.js +32 -8
  19. package/dist/code-hosts/helpers.js +4 -4
  20. package/dist/code-hosts/normalize.d.ts +1 -0
  21. package/dist/code-hosts/normalize.d.ts.map +1 -1
  22. package/dist/code-hosts/normalize.js +1 -1
  23. package/dist/code-hosts/schemas.d.ts +134 -0
  24. package/dist/code-hosts/schemas.d.ts.map +1 -1
  25. package/dist/code-hosts/schemas.js +31 -2
  26. package/dist/code-hosts/strategy.d.ts +9 -2
  27. package/dist/code-hosts/strategy.d.ts.map +1 -1
  28. package/dist/config/index.d.ts +1 -1
  29. package/dist/config/index.d.ts.map +1 -1
  30. package/dist/config/store.d.ts +2 -2
  31. package/dist/config/store.d.ts.map +1 -1
  32. package/dist/config/store.js +79 -1
  33. package/dist/definition.d.ts +34 -1
  34. package/dist/definition.d.ts.map +1 -1
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -1
  38. package/dist/keyring/index.d.ts +1 -1
  39. package/dist/keyring/index.d.ts.map +1 -1
  40. package/dist/keyring/resolver.d.ts +1 -0
  41. package/dist/keyring/resolver.d.ts.map +1 -1
  42. package/dist/keyring/resolver.js +14 -18
  43. package/dist/model.d.ts +48 -8
  44. package/dist/model.d.ts.map +1 -1
  45. package/dist/runtime.d.ts +6 -4
  46. package/dist/runtime.d.ts.map +1 -1
  47. package/dist/runtime.js +3 -0
  48. package/dist/testing/contracts.d.ts.map +1 -1
  49. package/dist/testing/contracts.js +0 -1
  50. package/dist/values.d.ts +29 -0
  51. package/dist/values.d.ts.map +1 -1
  52. package/dist/values.js +29 -0
  53. package/package.json +2 -2
package/README.md CHANGED
@@ -4,3 +4,10 @@ Provider-neutral, import-only TypeScript contracts and HTTP adapters for
4
4
  development-management services. The root export includes the runtime and the
5
5
  built-in GitHub, GitLab, and Gitea definitions. Machine-local config, native
6
6
  keyring support, and test fakes are isolated under subpath exports.
7
+
8
+ Repository setup is split into two provider-owned contracts:
9
+ `RepositoryScopeAdapter` applies provider-neutral repository updates to typed
10
+ connection settings and projects them back to a repository reference, while
11
+ the connection-level `RepositoryHost` finds or creates repositories.
12
+ Repository-scoped issue, pull-request, and file operations remain on
13
+ `Repository`.
@@ -0,0 +1,12 @@
1
+ import { type JsonObject } from "@alpacakit/core";
2
+ import type { IntegrationDefinition, IntegrationSettingsInput } from "./definition.js";
3
+ import type { IntegrationId } from "./ids.js";
4
+ import type { RepositoryRef } from "./model.js";
5
+ type AnyDefinition = IntegrationDefinition<IntegrationId, any, any>;
6
+ export interface IntegrationDefinitionCatalog {
7
+ resolveSettings(integration: IntegrationId, current: JsonObject | null, input: IntegrationSettingsInput): JsonObject;
8
+ projectRepository(integration: IntegrationId, settings: JsonObject): RepositoryRef;
9
+ }
10
+ export declare function createIntegrationDefinitionCatalog(definitions: readonly AnyDefinition[]): IntegrationDefinitionCatalog;
11
+ export {};
12
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,KAAK,EACV,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAOhD,KAAK,aAAa,GAAG,qBAAqB,CAAC,aAAa,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEpE,MAAM,WAAW,4BAA4B;IAC3C,eAAe,CACb,WAAW,EAAE,aAAa,EAC1B,OAAO,EAAE,UAAU,GAAG,IAAI,EAC1B,KAAK,EAAE,wBAAwB,GAC9B,UAAU,CAAC;IACd,iBAAiB,CACf,WAAW,EAAE,aAAa,EAC1B,QAAQ,EAAE,UAAU,GACnB,aAAa,CAAC;CAClB;AAED,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,SAAS,aAAa,EAAE,GACpC,4BAA4B,CAE9B"}
@@ -0,0 +1,82 @@
1
+ import { CODEC_DECODE_RESULT_KIND } from "@alpacakit/core";
2
+ import { INTEGRATION_ERROR_CODE, IntegrationError } from "./error.js";
3
+ import { INTEGRATION_SETTINGS_INPUT_KIND, REPOSITORY_SETTINGS_UPDATE_KIND, } from "./values.js";
4
+ export function createIntegrationDefinitionCatalog(definitions) {
5
+ return new DefaultIntegrationDefinitionCatalog(definitions);
6
+ }
7
+ class DefaultIntegrationDefinitionCatalog {
8
+ definitions;
9
+ constructor(definitions) {
10
+ const indexed = new Map();
11
+ for (const definition of definitions) {
12
+ if (indexed.has(definition.id)) {
13
+ throw invalidInput(`Duplicate integration id "${definition.id}"`, `integration id "${definition.id}" registered twice`, definition.id);
14
+ }
15
+ indexed.set(definition.id, definition);
16
+ }
17
+ this.definitions = indexed;
18
+ }
19
+ resolveSettings(id, current, input) {
20
+ const definition = this.requireDefinition(id);
21
+ if (input.kind === INTEGRATION_SETTINGS_INPUT_KIND.raw) {
22
+ this.decodeSettings(definition, input.settings);
23
+ return input.settings;
24
+ }
25
+ const adapter = definition.repositoryScope;
26
+ if (adapter === undefined) {
27
+ throw unsupported(definition.id, "repository settings");
28
+ }
29
+ const currentSettings = current === null ? null : this.decodeSettings(definition, current);
30
+ if (input.update.kind === REPOSITORY_SETTINGS_UPDATE_KIND.baseUrl) {
31
+ if (currentSettings === null) {
32
+ throw invalidInput(`Repository settings are required for integration "${definition.id}"`, "a base URL cannot identify a repository by itself", definition.id);
33
+ }
34
+ }
35
+ const settings = adapter.applySettings({
36
+ current: currentSettings,
37
+ ...input.update,
38
+ });
39
+ this.decodeSettings(definition, settings);
40
+ return settings;
41
+ }
42
+ projectRepository(id, settings) {
43
+ const definition = this.requireDefinition(id);
44
+ const adapter = definition.repositoryScope;
45
+ if (adapter === undefined) {
46
+ throw unsupported(definition.id, "repository settings");
47
+ }
48
+ return adapter.projectRepository(this.decodeSettings(definition, settings));
49
+ }
50
+ requireDefinition(id) {
51
+ const definition = this.definitions.get(id);
52
+ if (definition === undefined) {
53
+ throw invalidInput(`Unknown integration id "${id}"`, `no integration registered with id "${id}"`, id);
54
+ }
55
+ return definition;
56
+ }
57
+ decodeSettings(definition, settings) {
58
+ const decoded = definition.settingsCodec.decode(settings);
59
+ if (decoded.kind === CODEC_DECODE_RESULT_KIND.invalid) {
60
+ throw invalidInput(`Invalid settings for integration "${definition.id}"`, decoded.issues.join("; "), definition.id);
61
+ }
62
+ return decoded.value;
63
+ }
64
+ }
65
+ function invalidInput(message, reason, integrationId) {
66
+ return new IntegrationError({
67
+ code: INTEGRATION_ERROR_CODE.invalidInput,
68
+ message,
69
+ data: { reason },
70
+ integrationId,
71
+ connectionId: null,
72
+ });
73
+ }
74
+ function unsupported(integrationId, operation) {
75
+ return new IntegrationError({
76
+ code: INTEGRATION_ERROR_CODE.unsupported,
77
+ message: `Integration "${integrationId}" does not support ${operation}`,
78
+ data: { operation },
79
+ integrationId,
80
+ connectionId: null,
81
+ });
82
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/code-hosts/account.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EAEP,cAAc,EAEf,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,cAAc,CAAC;AAStB,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAEvE;AAsGD,wBAAgB,WAAW,CACzB,UAAU,EAAE,aAAa,EACzB,QAAQ,EAAE,cAAc,CAAC,WAAW,CAAC,GACpC,cAAc,CAAC,WAAW,CAAC,CAO7B"}
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/code-hosts/account.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EAEP,cAAc,EAEf,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,cAAc,CAAC;AAStB,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAEvE;AAyGD,wBAAgB,WAAW,CACzB,UAAU,EAAE,aAAa,EACzB,QAAQ,EAAE,cAAc,CAAC,WAAW,CAAC,GACpC,cAAc,CAAC,WAAW,CAAC,CAO7B"}
@@ -67,7 +67,10 @@ class CodeHostAccount {
67
67
  if (this.repositoryRequest !== null) {
68
68
  return this.repositoryRequest;
69
69
  }
70
- const pending = this.session.transport.request(this.session.strategy.paths.repository(), rawRepositoryCodec);
70
+ const pending = this.session.transport.request(this.session.strategy.paths.repository({
71
+ owner: this.session.strategy.scope.owner,
72
+ name: this.session.strategy.scope.repository,
73
+ }), rawRepositoryCodec);
71
74
  this.repositoryRequest = pending;
72
75
  void pending.catch(() => {
73
76
  if (this.repositoryRequest === pending) {
@@ -1,5 +1,5 @@
1
1
  import type { DoctorCheck } from "../login.js";
2
- import type { Repository, WorkItems } from "../model.js";
2
+ import type { Repository, RepositoryHost, WorkItems } from "../model.js";
3
3
  import type { HttpTransport } from "../transport/http-transport.js";
4
4
  import { type CodeHostStrategy } from "./strategy.js";
5
5
  export { createCodeHostAccount } from "./account.js";
@@ -9,5 +9,6 @@ export type CodeHostSession = {
9
9
  };
10
10
  export declare function createCodeHostWorkItems(session: CodeHostSession): WorkItems;
11
11
  export declare function createCodeHostRepository(session: CodeHostSession): Repository;
12
+ export declare function createCodeHostRepositoryHost(session: CodeHostSession): RepositoryHost;
12
13
  export declare function probeCodeHostDoctor(session: CodeHostSession): Promise<DoctorCheck[]>;
13
14
  //# sourceMappingURL=facets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"facets.d.ts","sourceRoot":"","sources":["../../src/code-hosts/facets.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EASV,UAAU,EAKV,SAAS,EACV,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAoCpE,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,eAAe,CAAC;AAavB,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAqBrD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;CACrC,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CA0I3E;AAiKD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,eAAe,GAAG,UAAU,CAqF7E;AAsGD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,WAAW,EAAE,CAAC,CA8DxB"}
1
+ {"version":3,"file":"facets.d.ts","sourceRoot":"","sources":["../../src/code-hosts/facets.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAQV,UAAU,EACV,cAAc,EAKd,SAAS,EACV,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAuCpE,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,eAAe,CAAC;AAavB,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAqBrD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;CACrC,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CA0I3E;AAiKD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,eAAe,GAAG,UAAU,CAoE7E;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,eAAe,GACvB,cAAc,CAwChB;AA0ID,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,WAAW,EAAE,CAAC,CAgExB"}
@@ -1,7 +1,7 @@
1
1
  import { INTEGRATION_ERROR_CODE, IntegrationError, isIntegrationError, withIntegrationIdentity, } from "../error.js";
2
2
  import { commentReaction, } from "../ids.js";
3
3
  import { collectPaginated } from "../transport/collect.js";
4
- import { DOCTOR_CHECK_STATUS, WORK_ITEM_KIND, WORK_ITEM_QUERY_STATE, WORK_ITEM_STATE, } from "../values.js";
4
+ import { DOCTOR_CHECK_STATUS, ENSURE_REPOSITORY_RESULT_KIND, PULL_REQUEST_REVIEW_CONTEXT_AVAILABILITY, WORK_ITEM_KIND, WORK_ITEM_QUERY_STATE, WORK_ITEM_STATE, } from "../values.js";
5
5
  import { ownerKindOf } from "./account.js";
6
6
  import { commentIdentity } from "./helpers.js";
7
7
  import { normalizeComment, normalizeCommit, normalizePullRequest, normalizeWorkItem, } from "./normalize.js";
@@ -220,7 +220,7 @@ export function createCodeHostRepository(session) {
220
220
  const ensureLinkedBranch = strategy.ensureLinkedBranch;
221
221
  return {
222
222
  info: async () => {
223
- const raw = await transport.request(strategy.paths.repository(), rawRepositoryCodec);
223
+ const raw = await transport.request(strategy.paths.repository(repository), rawRepositoryCodec);
224
224
  return {
225
225
  ref: repository,
226
226
  defaultBranch: raw.defaultBranch,
@@ -258,17 +258,37 @@ export function createCodeHostRepository(session) {
258
258
  : {
259
259
  ensure: async (workItem, branch) => await ensureLinkedBranch(transport, refNumber(workItem), branch),
260
260
  },
261
- provisioning: {
262
- ensureRepository: async (input) => {
263
- const raw = await transport.request(await strategy.provisionRequest(transport, input), rawRepositoryCodec);
261
+ };
262
+ }
263
+ export function createCodeHostRepositoryHost(session) {
264
+ const { transport, strategy } = session;
265
+ const normalizeRepository = (raw, ref) => ({
266
+ ref,
267
+ defaultBranch: raw.defaultBranch,
268
+ url: raw.url,
269
+ isPrivate: raw.isPrivate,
270
+ ownerKind: ownerKindOf(raw, strategy.ownerKind),
271
+ });
272
+ const find = async (ref) => {
273
+ const raw = await transport.requestOptional(strategy.paths.repository(ref), rawRepositoryCodec);
274
+ return raw === null ? null : normalizeRepository(raw, ref);
275
+ };
276
+ return {
277
+ find,
278
+ ensure: async (input) => {
279
+ const { ref } = input;
280
+ const existing = await find(ref);
281
+ if (existing !== null) {
264
282
  return {
265
- ref: { owner: input.owner, name: input.name },
266
- defaultBranch: raw.defaultBranch,
267
- url: raw.url,
268
- isPrivate: raw.isPrivate,
269
- ownerKind: ownerKindOf(raw, strategy.ownerKind),
283
+ kind: ENSURE_REPOSITORY_RESULT_KIND.existing,
284
+ repository: existing,
270
285
  };
271
- },
286
+ }
287
+ const raw = await transport.request(await strategy.provisionRequest(transport, input), rawRepositoryCodec);
288
+ return {
289
+ kind: ENSURE_REPOSITORY_RESULT_KIND.created,
290
+ repository: normalizeRepository(raw, ref),
291
+ };
272
292
  },
273
293
  };
274
294
  }
@@ -291,6 +311,39 @@ function createPullRequests(session, normalizePr, getByRef) {
291
311
  changedFiles: (ref) => collectPaginated(transport, strategy.paths.pullRequests.changedFiles(refNumber(ref)), rawChangedFileListCodec, strategy.pager),
292
312
  commits: async (ref) => (await collectPaginated(transport, strategy.paths.pullRequests.commits(refNumber(ref)), rawCommitListCodec, strategy.pager)).map(normalizeCommit),
293
313
  status: async (ref) => await strategy.pullRequestStatus(transport, refNumber(ref)),
314
+ snapshot: async (ref) => {
315
+ const read = strategy.pullRequestSnapshot;
316
+ if (read !== undefined) {
317
+ const snapshot = await read(transport, refNumber(ref));
318
+ return snapshot === null
319
+ ? null
320
+ : {
321
+ pullRequest: normalizePr(snapshot.pullRequest),
322
+ status: snapshot.status,
323
+ reviewContext: snapshot.reviewContext,
324
+ };
325
+ }
326
+ const raw = await getByRef(ref);
327
+ if (raw === null) {
328
+ return null;
329
+ }
330
+ const status = await strategy.pullRequestStatus(transport, refNumber(ref));
331
+ const pullRequest = normalizePr(raw);
332
+ return {
333
+ pullRequest,
334
+ status: status ?? {
335
+ reviewDecision: null,
336
+ mergeability: null,
337
+ mergeStateStatus: null,
338
+ isDraft: pullRequest.isDraft,
339
+ headSha: pullRequest.headSha,
340
+ latestApprovedReviewSha: null,
341
+ },
342
+ reviewContext: {
343
+ availability: PULL_REQUEST_REVIEW_CONTEXT_AVAILABILITY.unsupported,
344
+ },
345
+ };
346
+ },
294
347
  headIncludedInBase: async (ref) => {
295
348
  const pr = await getByRef(ref);
296
349
  if (pr === null) {
@@ -331,7 +384,7 @@ function createPullRequests(session, normalizePr, getByRef) {
331
384
  export async function probeCodeHostDoctor(session) {
332
385
  const checks = [];
333
386
  try {
334
- await session.transport.request(session.strategy.paths.repository(), rawRepositoryCodec);
387
+ await session.transport.request(session.strategy.paths.repository(normalizationRepository(session.strategy)), rawRepositoryCodec);
335
388
  checks.push({
336
389
  id: DOCTOR_CHECK_ID.reachable,
337
390
  status: DOCTOR_CHECK_STATUS.ok,
@@ -0,0 +1,22 @@
1
+ import type { PullRequestSnapshot, PullRequestStatus } from "../model.js";
2
+ import type { HttpTransport } from "../transport/http-transport.js";
3
+ import { type RawGiteaReview, type RawPullRequest } from "./schemas.js";
4
+ export declare function readGiteaPullRequestSnapshot(input: {
5
+ readonly transport: HttpTransport;
6
+ readonly repositoryPath: string;
7
+ readonly number: string;
8
+ readonly pager: import("../transport/types.js").Pager;
9
+ }): Promise<{
10
+ readonly pullRequest: RawPullRequest;
11
+ readonly status: PullRequestStatus;
12
+ readonly reviewContext: PullRequestSnapshot["reviewContext"];
13
+ } | null>;
14
+ export declare function readGiteaPullRequest(transport: HttpTransport, repositoryPath: string, number: string): Promise<RawPullRequest | null>;
15
+ export declare function readGiteaPullRequestReviews(input: {
16
+ readonly transport: HttpTransport;
17
+ readonly repositoryPath: string;
18
+ readonly number: string;
19
+ readonly pager: import("../transport/types.js").Pager;
20
+ }): Promise<RawGiteaReview[]>;
21
+ export declare function projectGiteaPullRequestStatus(pullRequest: RawPullRequest, reviews: readonly RawGiteaReview[]): PullRequestStatus;
22
+ //# sourceMappingURL=gitea-pull-request-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitea-pull-request-snapshot.d.ts","sourceRoot":"","sources":["../../src/code-hosts/gitea-pull-request-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAapE,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,cAAc,EAKpB,MAAM,cAAc,CAAC;AAatB,wBAAsB,4BAA4B,CAAC,KAAK,EAAE;IACxD,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,uBAAuB,EAAE,KAAK,CAAC;CACvD,GAAG,OAAO,CAAC;IACV,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;CAC9D,GAAG,IAAI,CAAC,CAsCR;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,aAAa,EACxB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAShC;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,uBAAuB,EAAE,KAAK,CAAC;CACvD,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAY5B;AAED,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,cAAc,EAC3B,OAAO,EAAE,SAAS,cAAc,EAAE,GACjC,iBAAiB,CAmCnB"}
@@ -0,0 +1,226 @@
1
+ import { collectPaginated } from "../transport/collect.js";
2
+ import { COMMENT_SIDE, HTTP_METHOD, MERGE_STATE_STATUS, MERGEABILITY, PULL_REQUEST_FEEDBACK_KIND, PULL_REQUEST_REVIEW_CONTEXT_AVAILABILITY, PULL_REQUEST_REVIEW_STATE, PULL_REQUEST_STATE, REVIEW_DECISION, } from "../values.js";
3
+ import { isBot, normalizeUser } from "./normalize.js";
4
+ import { rawCommentListCodec, rawGiteaReviewCommentListCodec, rawGiteaReviewListCodec, rawPullRequestCodec, } from "./schemas.js";
5
+ import { CODE_HOST_RESOURCE } from "./values.js";
6
+ const MAX_PAGE_SIZE = 50;
7
+ const INLINE_REVIEW_READ_CONCURRENCY = 4;
8
+ const GITEA_REVIEW_STATE = {
9
+ approved: "APPROVED",
10
+ changesRequested: "REQUEST_CHANGES",
11
+ commented: "COMMENT",
12
+ pending: "PENDING",
13
+ requestReview: "REQUEST_REVIEW",
14
+ };
15
+ export async function readGiteaPullRequestSnapshot(input) {
16
+ const pullRequest = await readGiteaPullRequest(input.transport, input.repositoryPath, input.number);
17
+ if (pullRequest === null) {
18
+ return null;
19
+ }
20
+ if (pullRequest.state.toLowerCase() !== PULL_REQUEST_STATE.open) {
21
+ return {
22
+ pullRequest,
23
+ status: projectGiteaPullRequestStatusWithoutReviewHistory(pullRequest),
24
+ reviewContext: {
25
+ availability: PULL_REQUEST_REVIEW_CONTEXT_AVAILABILITY.unavailable,
26
+ },
27
+ };
28
+ }
29
+ const [reviews, conversation] = await Promise.all([
30
+ readGiteaPullRequestReviews(input),
31
+ readGiteaPullRequestConversation(input),
32
+ ]);
33
+ const inline = await readGiteaPullRequestInlineComments({
34
+ ...input,
35
+ reviews,
36
+ });
37
+ return {
38
+ pullRequest,
39
+ status: projectGiteaPullRequestStatus(pullRequest, reviews),
40
+ reviewContext: {
41
+ availability: PULL_REQUEST_REVIEW_CONTEXT_AVAILABILITY.available,
42
+ feedback: projectGiteaPullRequestFeedback({
43
+ conversation,
44
+ reviews,
45
+ inline,
46
+ }),
47
+ },
48
+ };
49
+ }
50
+ export function readGiteaPullRequest(transport, repositoryPath, number) {
51
+ return transport.requestOptional({
52
+ method: HTTP_METHOD.get,
53
+ path: `${repositoryPath}/pulls/${number}`,
54
+ resource: CODE_HOST_RESOURCE.pullRequest,
55
+ }, rawPullRequestCodec);
56
+ }
57
+ export function readGiteaPullRequestReviews(input) {
58
+ return collectPaginated(input.transport, {
59
+ method: HTTP_METHOD.get,
60
+ path: `${input.repositoryPath}/pulls/${input.number}/reviews`,
61
+ query: { limit: MAX_PAGE_SIZE },
62
+ resource: CODE_HOST_RESOURCE.pullRequestReviews,
63
+ }, rawGiteaReviewListCodec, input.pager);
64
+ }
65
+ export function projectGiteaPullRequestStatus(pullRequest, reviews) {
66
+ const recentFirst = reviews
67
+ .filter((review) => review.dismissed !== true && review.stale !== true)
68
+ .reverse();
69
+ const latestDecision = recentFirst.find((review) => review.state === GITEA_REVIEW_STATE.changesRequested ||
70
+ review.state === GITEA_REVIEW_STATE.approved);
71
+ const approved = recentFirst.find((review) => review.state === GITEA_REVIEW_STATE.approved);
72
+ return {
73
+ reviewDecision: latestDecision?.state === GITEA_REVIEW_STATE.approved
74
+ ? REVIEW_DECISION.approved
75
+ : latestDecision?.state === GITEA_REVIEW_STATE.changesRequested
76
+ ? REVIEW_DECISION.changesRequested
77
+ : REVIEW_DECISION.reviewRequired,
78
+ mergeability: pullRequest.mergeable === true
79
+ ? MERGEABILITY.mergeable
80
+ : pullRequest.mergeable === false
81
+ ? MERGEABILITY.conflicting
82
+ : MERGEABILITY.unknown,
83
+ mergeStateStatus: pullRequest.mergeable === true
84
+ ? MERGE_STATE_STATUS.clean
85
+ : pullRequest.mergeable === false
86
+ ? MERGE_STATE_STATUS.dirty
87
+ : MERGE_STATE_STATUS.unknown,
88
+ isDraft: pullRequest.isDraft,
89
+ headSha: pullRequest.headSha,
90
+ latestApprovedReviewSha: approved?.commit_id ?? null,
91
+ };
92
+ }
93
+ function projectGiteaPullRequestStatusWithoutReviewHistory(pullRequest) {
94
+ return {
95
+ ...projectGiteaPullRequestStatus(pullRequest, []),
96
+ reviewDecision: null,
97
+ latestApprovedReviewSha: null,
98
+ };
99
+ }
100
+ function readGiteaPullRequestConversation(input) {
101
+ return collectPaginated(input.transport, {
102
+ method: HTTP_METHOD.get,
103
+ path: `${input.repositoryPath}/issues/${input.number}/comments`,
104
+ query: { limit: MAX_PAGE_SIZE },
105
+ resource: CODE_HOST_RESOURCE.comments,
106
+ }, rawCommentListCodec, input.pager);
107
+ }
108
+ async function readGiteaPullRequestInlineComments(input) {
109
+ const reviews = input.reviews.filter((review) => isSubmittedGiteaReview(review) && (review.comments_count ?? 0) > 0);
110
+ const comments = [];
111
+ for (let index = 0; index < reviews.length; index += INLINE_REVIEW_READ_CONCURRENCY) {
112
+ comments.push(...(await Promise.all(reviews
113
+ .slice(index, index + INLINE_REVIEW_READ_CONCURRENCY)
114
+ .map((review) => collectPaginated(input.transport, {
115
+ method: HTTP_METHOD.get,
116
+ path: `${input.repositoryPath}/pulls/${input.number}/reviews/${review.id}/comments`,
117
+ query: { limit: MAX_PAGE_SIZE },
118
+ resource: CODE_HOST_RESOURCE.pullRequestReviewComments,
119
+ }, rawGiteaReviewCommentListCodec, input.pager)))));
120
+ }
121
+ return comments.flatMap((page, index) => {
122
+ const review = reviews[index];
123
+ return review === undefined
124
+ ? []
125
+ : page.map((comment) => ({ review, comment }));
126
+ });
127
+ }
128
+ function projectGiteaPullRequestFeedback(input) {
129
+ return [
130
+ ...input.conversation.map(projectGiteaConversationFeedback),
131
+ ...input.reviews
132
+ .filter(isSubmittedGiteaReview)
133
+ .filter((review) => (review.body ?? "").trim().length > 0)
134
+ .map(projectGiteaReviewFeedback),
135
+ ...input.inline.map(projectGiteaInlineFeedback),
136
+ ];
137
+ }
138
+ function projectGiteaConversationFeedback(comment) {
139
+ return {
140
+ id: `${PULL_REQUEST_FEEDBACK_KIND.conversation}:${comment.id}`,
141
+ kind: PULL_REQUEST_FEEDBACK_KIND.conversation,
142
+ body: comment.body,
143
+ url: comment.url,
144
+ author: normalizeUser(comment.author),
145
+ isBot: isBot(comment.author),
146
+ createdAt: comment.createdAt,
147
+ updatedAt: comment.updatedAt,
148
+ reviewState: null,
149
+ commitSha: null,
150
+ stale: false,
151
+ dismissed: false,
152
+ resolved: false,
153
+ path: null,
154
+ line: null,
155
+ side: null,
156
+ inReplyTo: null,
157
+ };
158
+ }
159
+ function projectGiteaReviewFeedback(review) {
160
+ return {
161
+ id: `${PULL_REQUEST_FEEDBACK_KIND.review}:${review.id}`,
162
+ kind: PULL_REQUEST_FEEDBACK_KIND.review,
163
+ body: review.body ?? "",
164
+ url: review.html_url ?? null,
165
+ author: review.user == null ? null : normalizeUser(review.user),
166
+ isBot: review.user == null ? false : isBot(review.user),
167
+ createdAt: review.submitted_at ?? null,
168
+ updatedAt: review.updated_at ?? null,
169
+ reviewState: normalizeGiteaReviewState(review),
170
+ commitSha: review.commit_id ?? null,
171
+ stale: review.stale ?? false,
172
+ dismissed: review.dismissed ?? false,
173
+ resolved: false,
174
+ path: null,
175
+ line: null,
176
+ side: null,
177
+ inReplyTo: null,
178
+ };
179
+ }
180
+ function projectGiteaInlineFeedback(input) {
181
+ const position = input.comment.position ?? 0;
182
+ const originalPosition = input.comment.original_position ?? 0;
183
+ return {
184
+ id: `${PULL_REQUEST_FEEDBACK_KIND.inline}:${input.comment.id}`,
185
+ kind: PULL_REQUEST_FEEDBACK_KIND.inline,
186
+ body: input.comment.body ?? "",
187
+ url: input.comment.html_url ?? null,
188
+ author: input.comment.user == null ? null : normalizeUser(input.comment.user),
189
+ isBot: input.comment.user == null ? false : isBot(input.comment.user),
190
+ createdAt: input.comment.created_at ?? null,
191
+ updatedAt: input.comment.updated_at ?? null,
192
+ reviewState: normalizeGiteaReviewState(input.review),
193
+ commitSha: input.comment.commit_id ?? input.review.commit_id ?? null,
194
+ stale: input.review.stale ?? false,
195
+ dismissed: input.review.dismissed ?? false,
196
+ resolved: input.comment.resolver != null,
197
+ path: input.comment.path ?? null,
198
+ line: position > 0 ? position : originalPosition > 0 ? originalPosition : null,
199
+ side: position > 0
200
+ ? COMMENT_SIDE.right
201
+ : originalPosition > 0
202
+ ? COMMENT_SIDE.left
203
+ : null,
204
+ inReplyTo: input.comment.in_reply_to_id == null
205
+ ? null
206
+ : `${PULL_REQUEST_FEEDBACK_KIND.inline}:${input.comment.in_reply_to_id}`,
207
+ };
208
+ }
209
+ function isSubmittedGiteaReview(review) {
210
+ return (review.state !== GITEA_REVIEW_STATE.pending &&
211
+ review.state !== GITEA_REVIEW_STATE.requestReview);
212
+ }
213
+ function normalizeGiteaReviewState(review) {
214
+ if (review.dismissed === true) {
215
+ return PULL_REQUEST_REVIEW_STATE.dismissed;
216
+ }
217
+ if (review.state === GITEA_REVIEW_STATE.approved) {
218
+ return PULL_REQUEST_REVIEW_STATE.approved;
219
+ }
220
+ if (review.state === GITEA_REVIEW_STATE.changesRequested) {
221
+ return PULL_REQUEST_REVIEW_STATE.changesRequested;
222
+ }
223
+ return review.state === GITEA_REVIEW_STATE.commented
224
+ ? PULL_REQUEST_REVIEW_STATE.commented
225
+ : PULL_REQUEST_REVIEW_STATE.unknown;
226
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"gitea.d.ts","sourceRoot":"","sources":["../../src/code-hosts/gitea.ts"],"names":[],"mappings":"AAmCA,OAAO,EACL,KAAK,eAAe,EAKrB,MAAM,aAAa,CAAC;AAuBrB,eAAO,MAAM,oBAAoB,2DAAyB,CAAC;AAW3D,eAAO,MAAM,oBAAoB,6DAGvB,CAAC;AACX,eAAO,MAAM,2BAA2B;;;CAGA,CAAC;AAEzC,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAkBF,wBAAgB,KAAK,gJAiCpB"}
1
+ {"version":3,"file":"gitea.d.ts","sourceRoot":"","sources":["../../src/code-hosts/gitea.ts"],"names":[],"mappings":"AAiCA,OAAO,EACL,KAAK,eAAe,EAMrB,MAAM,aAAa,CAAC;AA2BrB,eAAO,MAAM,oBAAoB,2DAAyB,CAAC;AAO3D,eAAO,MAAM,oBAAoB,6DAGvB,CAAC;AACX,eAAO,MAAM,2BAA2B;;;CAGA,CAAC;AAEzC,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAwBF,wBAAgB,KAAK,gJAuDpB"}