@shipfox/api-integration-spi 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-integration-spi",
3
3
  "license": "MIT",
4
- "version": "1.1.1",
4
+ "version": "2.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -18,7 +18,7 @@
18
18
  }
19
19
  },
20
20
  "dependencies": {
21
- "@shipfox/api-integration-core-dto": "12.2.0"
21
+ "@shipfox/api-integration-core-dto": "14.0.0"
22
22
  },
23
23
  "imports": {
24
24
  "#*": "./dist/*"
@@ -3,6 +3,7 @@ import {
3
3
  IntegrationProviderError,
4
4
  isValidGitObjectId,
5
5
  isValidGitRefName,
6
+ isValidResolvableRef,
6
7
  isValidTriggerRef,
7
8
  parseProviderRepositoryId,
8
9
  } from './contracts.js';
@@ -29,6 +30,9 @@ describe('git ref names', () => {
29
30
  'refs/heads/.foo',
30
31
  'refs/heads/foo.',
31
32
  'refs/heads/foo@{bar',
33
+ 'a'.repeat(40),
34
+ 'b'.repeat(64),
35
+ '0'.repeat(40),
32
36
  ])('rejects %s', (ref) => {
33
37
  expect(isValidGitRefName(ref)).toBe(false);
34
38
  });
@@ -38,6 +42,26 @@ describe('git ref names', () => {
38
42
  });
39
43
  });
40
44
 
45
+ describe('resolvable refs', () => {
46
+ it.each([
47
+ 'refs/heads/main',
48
+ 'refs/heads/feature/review',
49
+ 'refs/tags/v1.0.0',
50
+ ])('accepts %s', (ref) => {
51
+ expect(isValidResolvableRef(ref)).toBe(true);
52
+ });
53
+
54
+ it.each(['refs/pull/17/head'])('rejects %s', (ref) => {
55
+ expect(isValidResolvableRef(ref)).toBe(false);
56
+ });
57
+
58
+ it('rejects every name a trigger ref rejects', () => {
59
+ for (const ref of ['', 'HEAD', '-main', 'refs/heads/foo bar', 'refs/heads/foo..bar']) {
60
+ expect(isValidResolvableRef(ref)).toBe(false);
61
+ }
62
+ });
63
+ });
64
+
41
65
  describe('git object ids', () => {
42
66
  it.each(['a'.repeat(40), 'b'.repeat(64)])('accepts a full object id', (value) => {
43
67
  expect(isValidGitObjectId(value)).toBe(true);
package/src/contracts.ts CHANGED
@@ -53,6 +53,16 @@ export interface ResolveRepositoryInput<
53
53
  externalRepositoryId: string;
54
54
  }
55
55
 
56
+ export interface ResolveRefInput<Connection extends IntegrationConnection = IntegrationConnection>
57
+ extends ResolveRepositoryInput<Connection> {
58
+ ref: string;
59
+ }
60
+
61
+ export interface ResolvedRef {
62
+ ref: string;
63
+ commit: string;
64
+ }
65
+
56
66
  export interface FileSnapshot {
57
67
  path: string;
58
68
  ref: string;
@@ -129,6 +139,11 @@ export interface SourceControlProvider<
129
139
  listFiles(input: ListFilesInput<Connection>): Promise<FilePage>;
130
140
  fetchFile(input: FetchFileInput<Connection>): Promise<FileSnapshot>;
131
141
  resolveTriggerReference(payload: unknown): TriggerReference | null;
142
+ /**
143
+ * Pins a branch or tag name to the commit it currently points at.
144
+ * The snapshot can become unreachable before a caller uses it.
145
+ */
146
+ resolveRef(input: ResolveRefInput<Connection>): Promise<ResolvedRef>;
132
147
  createCheckoutSpec?(input: CreateCheckoutSpecInput<Connection>): Promise<CheckoutSpec>;
133
148
  }
134
149
 
@@ -223,6 +238,13 @@ export interface IntegrationProvider<
223
238
  > {
224
239
  provider: ProviderKind;
225
240
  displayName: string;
241
+ /**
242
+ * The event names this provider documents for its handler.
243
+ * Provider-minted names are never treated as a closed set by validation; the
244
+ * catalog is a curated diagnostic aid and may omit provider events. Providers
245
+ * without a documented catalog, including built-in trigger sources, omit it.
246
+ */
247
+ eventCatalog?: import('@shipfox/api-integration-core-dto').IntegrationEventCatalog | undefined;
226
248
  adapters?: IntegrationProviderAdapters<Connection> | undefined;
227
249
  routes?: Route[] | undefined;
228
250
  connectionExternalUrl?(connection: Connection): Promise<string | undefined>;
@@ -249,6 +271,8 @@ export type IntegrationProviderErrorReason =
249
271
  | 'repository-not-found'
250
272
  | 'installation-not-found'
251
273
  | 'file-not-found'
274
+ | 'ref-not-found'
275
+ | 'ref-invalid'
252
276
  | 'access-denied'
253
277
  | 'rate-limited'
254
278
  | 'timeout'
@@ -365,6 +389,18 @@ export function isValidTriggerRef(ref: string): boolean {
365
389
  return isValidGitRefName(ref);
366
390
  }
367
391
 
392
+ /**
393
+ * Validates a ref the platform may resolve to a commit: any safe git ref name
394
+ * except pull-request refs. Raw object ids do not satisfy the safe git ref
395
+ * name rules because valid refs must contain a slash. `refs/pull/N/head` can
396
+ * point at a fork and providers serve any commit in the repository network by
397
+ * SHA, so accepting names only keeps resolutions on refs a member pushed to
398
+ * the project repository.
399
+ */
400
+ export function isValidResolvableRef(ref: string): boolean {
401
+ return isValidTriggerRef(ref) && !ref.startsWith('refs/pull/');
402
+ }
403
+
368
404
  export function isValidGitObjectId(value: string): boolean {
369
405
  return GIT_OBJECT_ID_PATTERN.test(value) && !ZERO_GIT_OBJECT_ID_PATTERN.test(value);
370
406
  }