@fabrika/provider-zerops 0.2.0 → 0.3.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 (83) hide show
  1. package/package.json +18 -5
  2. package/src/api/client.ts +522 -0
  3. package/src/api/errors.ts +50 -0
  4. package/src/api/polling.ts +59 -0
  5. package/src/api/readers.ts +185 -0
  6. package/src/api/service-env-response.ts +34 -0
  7. package/src/api/types.ts +602 -0
  8. package/src/api.ts +29 -1378
  9. package/src/authoring.ts +7 -10
  10. package/src/cli-args.ts +2 -6
  11. package/src/cli.ts +3 -28
  12. package/src/collaborators.ts +2 -4
  13. package/src/compile.ts +3 -1
  14. package/src/control.ts +203 -450
  15. package/src/descriptor.ts +179 -0
  16. package/src/index.ts +23 -127
  17. package/src/installation/admin-options.ts +100 -0
  18. package/src/installation/admin.ts +123 -0
  19. package/src/installation/deploy-options.ts +127 -0
  20. package/src/installation/deploy.ts +668 -0
  21. package/src/installation/generated/platform-console-schema.ts +41 -0
  22. package/src/installation/generated/platform-proxy-manifest.ts +72 -0
  23. package/src/installation/hosts.ts +117 -0
  24. package/src/installation/index.ts +390 -0
  25. package/src/installation/init-options.ts +148 -0
  26. package/src/installation/init.ts +394 -0
  27. package/src/installation/install-collaborators.ts +27 -0
  28. package/src/installation/install-environment.ts +193 -0
  29. package/src/installation/install-options.ts +156 -0
  30. package/src/installation/install-project.ts +201 -0
  31. package/src/installation/install-provisioning.ts +185 -0
  32. package/src/installation/install.ts +262 -0
  33. package/src/installation/installation-source.ts +42 -0
  34. package/src/installation/log.ts +77 -0
  35. package/src/installation/machine-key.ts +65 -0
  36. package/src/installation/manifest.ts +95 -0
  37. package/src/installation/placement-options.ts +33 -0
  38. package/src/installation/platform-source.ts +106 -0
  39. package/src/installation/platform-upload.ts +34 -0
  40. package/src/installation/proxy-manifest.ts +113 -0
  41. package/src/installation/secrets.ts +79 -0
  42. package/src/installation/sidecar.ts +125 -0
  43. package/src/installation/source-options.ts +12 -0
  44. package/src/installation/templates/README.md +116 -0
  45. package/src/installation/templates/fabrika.ref +1 -0
  46. package/src/installation/templates/gitignore +3 -0
  47. package/src/installation/templates/platform.yml +144 -0
  48. package/src/installation/topology/artifacts.ts +141 -0
  49. package/src/installation/topology/index.ts +13 -0
  50. package/src/installation/topology/installation-schemas.ts +225 -0
  51. package/src/installation/topology/invariants.ts +86 -0
  52. package/src/installation/topology/setups.ts +442 -0
  53. package/src/installation/topology/topology.ts +501 -0
  54. package/src/installation/topology/validate.ts +26 -0
  55. package/src/installation/upgrade-options.ts +99 -0
  56. package/src/installation/upgrade-output.ts +31 -0
  57. package/src/installation/upgrade.ts +373 -0
  58. package/src/managed-env.ts +113 -0
  59. package/src/manifest.ts +194 -344
  60. package/src/namespace/claims.ts +77 -0
  61. package/src/namespace/codec.ts +179 -0
  62. package/src/namespace/constants.ts +30 -0
  63. package/src/namespace/errors.ts +60 -0
  64. package/src/namespace/manifest.ts +28 -0
  65. package/src/namespace/operator.ts +151 -0
  66. package/src/namespace/provisioning.ts +482 -0
  67. package/src/namespace/proxy-attempt.ts +220 -0
  68. package/src/namespace/proxy-source.ts +22 -0
  69. package/src/namespace/topology.ts +115 -0
  70. package/src/namespace/types.ts +114 -0
  71. package/src/namespace.ts +34 -1271
  72. package/src/plan.ts +21 -23
  73. package/src/provider.ts +266 -241
  74. package/src/source/constants.ts +11 -0
  75. package/src/source/port.ts +49 -0
  76. package/src/source/types.ts +4 -0
  77. package/src/source/validation.ts +72 -0
  78. package/src/source.ts +2 -1220
  79. package/src/types.ts +8 -7
  80. package/src/version-attempt.ts +18 -0
  81. package/src/codec.ts +0 -108
  82. package/src/namespace-command.ts +0 -209
  83. package/src/source-connection.ts +0 -344
package/src/source.ts CHANGED
@@ -1,1220 +1,2 @@
1
- export const ZEROPS_SOURCE_PROTOCOL_VERSION = 1
2
- export const ZEROPS_SOURCE_RESOLVE_PATH = '/v1/source/resolve'
3
- export const ZEROPS_SOURCE_UPLOAD_PATH = '/v1/source/upload'
4
- export const ZEROPS_SOURCE_CANCEL_PATH = '/v1/source/cancel'
5
- export const ZEROPS_SOURCE_WEBHOOK_CONFIGURE_PATH = '/v1/source/github/webhook/configure'
6
- export const ZEROPS_SOURCE_INSTALLATIONS_VERIFY_PATH = '/v1/source/github/installations/verify'
7
- export const ZEROPS_SOURCE_PROTOCOL_VERSION_V2 = 2
8
- export const ZEROPS_SOURCE_RESOLVE_PATH_V2 = '/v2/source/resolve'
9
- export const ZEROPS_SOURCE_UPLOAD_PATH_V2 = '/v2/source/upload'
10
- export const ZEROPS_SOURCE_CREDENTIAL_ACTIVATE_PATH_V2 = '/v2/source/credentials/activate'
11
- export const ZEROPS_SOURCE_CREDENTIAL_STATUS_PATH_V2 = '/v2/source/credentials/status'
12
- export const ZEROPS_SOURCE_CREDENTIAL_BUNDLE_VERSION_V2 = 2
13
- export const ZEROPS_SOURCE_CREDENTIAL_ENV_V2_PREFIX = 'GITHUB_APP_CREDENTIALS_V2_'
14
-
15
- const MAX_ID_LENGTH = 128
16
- const MAX_REF_LENGTH = 255
17
- const MAX_UPLOAD_URL_LENGTH = 4096
18
- const MAX_SOURCE_CREDENTIAL_BUNDLE_BYTES = 72 * 1024
19
- const MAX_GITHUB_APP_PRIVATE_KEY_LENGTH = 64 * 1024
20
- const MAX_WEBHOOK_URL_LENGTH = 2048
21
- const MAX_WEBHOOK_SECRET_LENGTH = 4096
22
- const MAX_INSTALLATION_REPOSITORIES = 100
23
- const SHA256_PATTERN = /^[a-f0-9]{64}$/
24
- const COMMIT_SHA_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/
25
- const ID_PATTERN = /^[A-Za-z0-9._:-]+$/
26
- const GITHUB_OWNER_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?$/
27
- const GITHUB_REPOSITORY_PATTERN = /^[a-z0-9._-]{1,100}$/
28
- const GITHUB_APP_ID_PATTERN = /^[1-9][0-9]{0,31}$/
29
- const GITHUB_APP_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,98}[a-z0-9])?$/
30
- const GITHUB_LOGIN_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,98}[A-Za-z0-9])?$/
31
-
32
- export interface ZeropsSourceCredentialBundleV2 {
33
- readonly version: typeof ZEROPS_SOURCE_CREDENTIAL_BUNDLE_VERSION_V2
34
- readonly connectionId: string
35
- readonly githubAppId: string
36
- readonly privateKeyPem: string
37
- }
38
-
39
- export interface ZeropsSourceGitHubAppIdentityV1 {
40
- readonly id: number
41
- readonly slug: string
42
- readonly htmlUrl: string
43
- readonly public: boolean
44
- readonly owner: {
45
- readonly login: string
46
- readonly type: 'Organization'
47
- }
48
- readonly permissions: {
49
- readonly contents: 'read'
50
- }
51
- readonly events: readonly ['push']
52
- }
53
-
54
- export interface ZeropsSourceCredentialActivateRequestV2 {
55
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION_V2
56
- readonly connectionId: string
57
- /** Canonical JSON that is byte-identical to the durable source environment value. */
58
- readonly credentialBundle: string
59
- readonly credentialSha256: string
60
- }
61
-
62
- export interface ZeropsSourceCredentialActivateResponseV2 {
63
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION_V2
64
- readonly connectionId: string
65
- readonly credentialVersion: typeof ZEROPS_SOURCE_CREDENTIAL_BUNDLE_VERSION_V2
66
- readonly credentialSha256: string
67
- readonly githubApp: ZeropsSourceGitHubAppIdentityV1
68
- }
69
-
70
- export interface ZeropsSourceCredentialStatusRequestV2 {
71
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION_V2
72
- readonly connectionId: string
73
- }
74
-
75
- export interface ZeropsSourceCredentialAnonymousStatusResponseV2 {
76
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION_V2
77
- readonly connectionId: string
78
- readonly state: 'anonymous'
79
- }
80
-
81
- export interface ZeropsSourceCredentialActiveStatusResponseV2 extends ZeropsSourceCredentialActivateResponseV2 {
82
- readonly state: 'active'
83
- }
84
-
85
- export type ZeropsSourceCredentialStatusResponseV2 =
86
- | ZeropsSourceCredentialAnonymousStatusResponseV2
87
- | ZeropsSourceCredentialActiveStatusResponseV2
88
-
89
- export interface ZeropsSourceWebhookConfigureInput {
90
- readonly connectionId: string
91
- readonly credentialSha256: string
92
- readonly url: string
93
- readonly secret: string
94
- readonly signal: AbortSignal
95
- }
96
-
97
- export interface ZeropsSourceWebhookConfigureRequestV1 {
98
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
99
- readonly connectionId: string
100
- readonly credentialSha256: string
101
- readonly url: string
102
- readonly secret: string
103
- }
104
-
105
- export interface ZeropsSourceWebhookConfigureResponseV1 {
106
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
107
- readonly connectionId: string
108
- readonly credentialSha256: string
109
- readonly webhook: { readonly url: string; readonly contentType: 'json'; readonly insecureSsl: '0' }
110
- }
111
-
112
- export type ZeropsSourceInstallationScopeV1 =
113
- | { readonly kind: 'organization'; readonly organization: string }
114
- | { readonly kind: 'repositories'; readonly repositories: readonly ZeropsSourceRepository[] }
115
-
116
- export interface ZeropsSourceInstallationsVerifyInput {
117
- readonly connectionId: string
118
- readonly credentialSha256: string
119
- readonly scope: ZeropsSourceInstallationScopeV1
120
- readonly signal: AbortSignal
121
- }
122
-
123
- export interface ZeropsSourceInstallationsVerifyRequestV1 {
124
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
125
- readonly connectionId: string
126
- readonly credentialSha256: string
127
- readonly scope: ZeropsSourceInstallationScopeV1
128
- }
129
-
130
- export type ZeropsSourceInstallationVerificationV1 =
131
- | { readonly status: 'missing' }
132
- | {
133
- readonly status: 'installed'
134
- readonly installationId: number
135
- readonly accountLogin: string
136
- readonly repositorySelection: 'all' | 'selected'
137
- }
138
-
139
- export interface ZeropsSourceInstallationsVerifyResponseV1 {
140
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
141
- readonly connectionId: string
142
- readonly credentialSha256: string
143
- readonly installation: ZeropsSourceInstallationVerificationV1
144
- }
145
-
146
- export interface ZeropsSourceRepository {
147
- owner: string
148
- name: string
149
- }
150
-
151
- export interface ZeropsSourceDescriptor {
152
- path: 'zerops.yaml'
153
- sha256: string
154
- }
155
-
156
- export interface ZeropsSourceResolveRequestV1 {
157
- protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
158
- runId: string
159
- repository: ZeropsSourceRepository
160
- requestedRef: string
161
- expectedCommitSha?: string
162
- githubInstallationId?: number
163
- descriptorSha256: string
164
- }
165
-
166
- export interface ZeropsSourceResolveResponseV1 {
167
- protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
168
- runId: string
169
- commitSha: string
170
- descriptorSha256: string
171
- }
172
-
173
- export interface ZeropsSourceUploadRequestV1 {
174
- protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
175
- runId: string
176
- appVersionId: string
177
- repository: ZeropsSourceRepository
178
- commitSha: string
179
- githubInstallationId?: number
180
- uploadUrl: string
181
- descriptor: ZeropsSourceDescriptor
182
- }
183
-
184
- export interface ZeropsSourceUploadResponseV1 {
185
- protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
186
- runId: string
187
- appVersionId: string
188
- commitSha: string
189
- descriptorSha256: string
190
- }
191
-
192
- export interface ZeropsSourcePrivateBindingV2 {
193
- readonly connectionId: string
194
- readonly installationId: number
195
- }
196
-
197
- export interface ZeropsSourceResolveRequestV2 {
198
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION_V2
199
- readonly runId: string
200
- readonly repository: ZeropsSourceRepository
201
- readonly requestedRef: string
202
- readonly expectedCommitSha?: string
203
- readonly privateBinding?: ZeropsSourcePrivateBindingV2
204
- readonly descriptorSha256: string
205
- }
206
-
207
- export interface ZeropsSourceResolveResponseV2 {
208
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION_V2
209
- readonly runId: string
210
- readonly commitSha: string
211
- readonly descriptorSha256: string
212
- }
213
-
214
- export interface ZeropsSourceUploadRequestV2 {
215
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION_V2
216
- readonly runId: string
217
- readonly appVersionId: string
218
- readonly repository: ZeropsSourceRepository
219
- readonly commitSha: string
220
- readonly privateBinding?: ZeropsSourcePrivateBindingV2
221
- readonly uploadUrl: string
222
- readonly descriptor: ZeropsSourceDescriptor
223
- }
224
-
225
- export interface ZeropsSourceUploadResponseV2 {
226
- readonly protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION_V2
227
- readonly runId: string
228
- readonly appVersionId: string
229
- readonly commitSha: string
230
- readonly descriptorSha256: string
231
- }
232
-
233
- export interface ZeropsSourceCancelRequestV1 {
234
- protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
235
- runId: string
236
- appVersionId: string
237
- }
238
-
239
- export interface ZeropsSourceCancelResponseV1 {
240
- protocolVersion: typeof ZEROPS_SOURCE_PROTOCOL_VERSION
241
- runId: string
242
- appVersionId: string
243
- }
244
-
245
- export interface ZeropsSourceCancelResult {
246
- runId: string
247
- appVersionId: string
248
- }
249
-
250
- export type ZeropsSourceErrorCode =
251
- | 'invalid_request'
252
- | 'unauthorized'
253
- | 'repository_not_found'
254
- | 'installation_not_found'
255
- | 'ref_not_found'
256
- | 'commit_mismatch'
257
- | 'descriptor_missing'
258
- | 'descriptor_mismatch'
259
- | 'archive_rejected'
260
- | 'upload_rejected'
261
- | 'upload_failed'
262
- | 'credentials_invalid'
263
- | 'credentials_conflict'
264
- | 'cancelled'
265
- | 'internal'
266
-
267
- export const ZEROPS_SOURCE_ERROR_CODES: readonly ZeropsSourceErrorCode[] = [
268
- 'invalid_request',
269
- 'unauthorized',
270
- 'repository_not_found',
271
- 'installation_not_found',
272
- 'ref_not_found',
273
- 'commit_mismatch',
274
- 'descriptor_missing',
275
- 'descriptor_mismatch',
276
- 'archive_rejected',
277
- 'upload_rejected',
278
- 'upload_failed',
279
- 'credentials_invalid',
280
- 'credentials_conflict',
281
- 'cancelled',
282
- 'internal',
283
- ]
284
-
285
- export type ZeropsSourceErrorStage =
286
- | 'authenticate'
287
- | 'validate'
288
- | 'resolve'
289
- | 'archive'
290
- | 'upload'
291
- | 'cancel'
292
- | 'credentials'
293
-
294
- export const ZEROPS_SOURCE_ERROR_STAGES: readonly ZeropsSourceErrorStage[] = [
295
- 'authenticate',
296
- 'validate',
297
- 'resolve',
298
- 'archive',
299
- 'upload',
300
- 'cancel',
301
- 'credentials',
302
- ]
303
-
304
- export interface ZeropsSourceErrorEnvelope {
305
- error: {
306
- code: ZeropsSourceErrorCode
307
- stage: ZeropsSourceErrorStage
308
- retryable: boolean
309
- }
310
- }
311
-
312
- export interface ZeropsSourceResolveInput {
313
- runId: string
314
- repository: ZeropsSourceRepository
315
- requestedRef: string
316
- expectedCommitSha?: string
317
- githubInstallationId?: number
318
- descriptorSha256: string
319
- signal: AbortSignal
320
- }
321
-
322
- export interface ZeropsSourceResolveResult {
323
- runId: string
324
- commitSha: string
325
- descriptorSha256: string
326
- }
327
-
328
- export interface ZeropsSourceUploadInput {
329
- runId: string
330
- appVersionId: string
331
- repository: ZeropsSourceRepository
332
- commitSha: string
333
- githubInstallationId?: number
334
- uploadUrl: string
335
- descriptor: ZeropsSourceDescriptor
336
- signal: AbortSignal
337
- }
338
-
339
- export interface ZeropsSourceResolveInputV2 {
340
- readonly runId: string
341
- readonly repository: ZeropsSourceRepository
342
- readonly requestedRef: string
343
- readonly expectedCommitSha?: string
344
- readonly privateBinding?: ZeropsSourcePrivateBindingV2
345
- readonly descriptorSha256: string
346
- readonly signal: AbortSignal
347
- }
348
-
349
- export interface ZeropsSourceUploadInputV2 {
350
- readonly runId: string
351
- readonly appVersionId: string
352
- readonly repository: ZeropsSourceRepository
353
- readonly commitSha: string
354
- readonly privateBinding?: ZeropsSourcePrivateBindingV2
355
- readonly uploadUrl: string
356
- readonly descriptor: ZeropsSourceDescriptor
357
- readonly signal: AbortSignal
358
- }
359
-
360
- export interface ZeropsSourceUploadResult {
361
- runId: string
362
- appVersionId: string
363
- commitSha: string
364
- descriptorSha256: string
365
- }
366
-
367
- export interface ZeropsSourceCancelInput {
368
- runId: string
369
- appVersionId: string
370
- signal: AbortSignal
371
- }
372
-
373
- export interface ZeropsSourceClient {
374
- resolve(input: ZeropsSourceResolveInput): Promise<ZeropsSourceResolveResult>
375
- upload(input: ZeropsSourceUploadInput): Promise<ZeropsSourceUploadResult>
376
- cancel(input: ZeropsSourceCancelInput): Promise<void>
377
- }
378
-
379
- /** Keyed source operations; the unkeyed v1 wire remains the anonymous public-repository path. */
380
- export interface ZeropsSourceClientV2 {
381
- resolveV2(input: ZeropsSourceResolveInputV2): Promise<ZeropsSourceResolveResult>
382
- uploadV2(input: ZeropsSourceUploadInputV2): Promise<ZeropsSourceUploadResult>
383
- }
384
-
385
- export interface ZeropsSourceCredentialActivateInput {
386
- readonly connectionId: string
387
- readonly credentialBundle: string
388
- readonly credentialSha256: string
389
- readonly signal: AbortSignal
390
- }
391
-
392
- export interface ZeropsSourceCredentialStatusInput {
393
- readonly connectionId: string
394
- readonly signal: AbortSignal
395
- }
396
-
397
- /** Credential management is separate from the deploy source client and is composed only in the authenticated admin flow. */
398
- export interface ZeropsSourceCredentialManager {
399
- configureWebhook(input: ZeropsSourceWebhookConfigureInput): Promise<ZeropsSourceWebhookConfigureResponseV1>
400
- verifyInstallations(input: ZeropsSourceInstallationsVerifyInput): Promise<ZeropsSourceInstallationsVerifyResponseV1>
401
- }
402
-
403
- /** Keyed credential operations — the only way a private credential reaches `source`. */
404
- export interface ZeropsSourceCredentialManagerV2 {
405
- activateV2(input: ZeropsSourceCredentialActivateInput): Promise<ZeropsSourceCredentialActivateResponseV2>
406
- statusV2(input: ZeropsSourceCredentialStatusInput): Promise<ZeropsSourceCredentialStatusResponseV2>
407
- }
408
-
409
- const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value)
410
-
411
- const record = (value: unknown, label: string): Record<string, unknown> => {
412
- if (!isRecord(value)) throw new Error(`${label} must be an object`)
413
- return value
414
- }
415
-
416
- const exactKeys = (value: Record<string, unknown>, allowed: readonly string[], label: string): void => {
417
- const allowedKeys = new Set(allowed)
418
- const unknownKey = Object.keys(value).find((key) => !allowedKeys.has(key))
419
- if (unknownKey !== undefined) throw new Error(`${label} contains an unknown field`)
420
- const missingKey = allowed.find((key) => !(key in value))
421
- if (missingKey !== undefined) throw new Error(`${label} is missing field ${missingKey}`)
422
- }
423
-
424
- const protocolVersion = (value: Record<string, unknown>, label: string): typeof ZEROPS_SOURCE_PROTOCOL_VERSION => {
425
- if (value['protocolVersion'] !== ZEROPS_SOURCE_PROTOCOL_VERSION) throw new Error(`${label} has an unsupported protocolVersion`)
426
- return ZEROPS_SOURCE_PROTOCOL_VERSION
427
- }
428
-
429
- const protocolVersionV2 = (value: Record<string, unknown>, label: string): typeof ZEROPS_SOURCE_PROTOCOL_VERSION_V2 => {
430
- if (value['protocolVersion'] !== ZEROPS_SOURCE_PROTOCOL_VERSION_V2) throw new Error(`${label} has an unsupported protocolVersion`)
431
- return ZEROPS_SOURCE_PROTOCOL_VERSION_V2
432
- }
433
-
434
- const boundedString = (value: unknown, label: string, maximum: number): string => {
435
- if (typeof value !== 'string' || value.length === 0 || value.length > maximum) {
436
- throw new Error(`${label} must be a non-empty string of at most ${maximum} characters`)
437
- }
438
- return value
439
- }
440
-
441
- const identifier = (value: unknown, label: string): string => {
442
- const parsed = boundedString(value, label, MAX_ID_LENGTH)
443
- if (!ID_PATTERN.test(parsed)) throw new Error(`${label} contains invalid characters`)
444
- return parsed
445
- }
446
-
447
- const ref = (value: unknown, label: string): string => {
448
- const parsed = boundedString(value, label, MAX_REF_LENGTH)
449
- if (COMMIT_SHA_PATTERN.test(parsed)) return parsed
450
- const hasForbiddenCharacter = [...parsed].some((character) => {
451
- const code = character.charCodeAt(0)
452
- return code <= 0x20 || code === 0x7f || '~^:?*[\\'.includes(character)
453
- })
454
- const components = parsed.split('/')
455
- if (
456
- parsed.startsWith('-')
457
- || parsed.trim() !== parsed
458
- || parsed === '@'
459
- || parsed.includes('..')
460
- || parsed.includes('@{')
461
- || parsed.includes('//')
462
- || parsed.endsWith('/')
463
- || parsed.endsWith('.')
464
- || hasForbiddenCharacter
465
- || components.some((component) => component === '' || component.startsWith('.') || component.endsWith('.lock'))
466
- ) {
467
- throw new Error(`${label} must be a safe Git ref or lowercase commit digest`)
468
- }
469
- return parsed
470
- }
471
-
472
- const commitSha = (value: unknown, label: string): string => {
473
- if (typeof value !== 'string' || !COMMIT_SHA_PATTERN.test(value)) throw new Error(`${label} must be a lowercase 40- or 64-character hex digest`)
474
- return value
475
- }
476
-
477
- const sha256 = (value: unknown, label: string): string => {
478
- if (typeof value !== 'string' || !SHA256_PATTERN.test(value)) throw new Error(`${label} must be a lowercase 64-character hex digest`)
479
- return value
480
- }
481
-
482
- const sourceCredentialBundleValueV2 = (value: unknown): ZeropsSourceCredentialBundleV2 => {
483
- const parsed = record(value, 'source credential bundle v2')
484
- exactKeys(parsed, ['version', 'connectionId', 'githubAppId', 'privateKeyPem'], 'source credential bundle v2')
485
- if (parsed['version'] !== ZEROPS_SOURCE_CREDENTIAL_BUNDLE_VERSION_V2) {
486
- throw new Error('source credential bundle v2 has an unsupported version')
487
- }
488
- const connectionId = identifier(parsed['connectionId'], 'source credential bundle v2.connectionId')
489
- const githubAppId = boundedString(parsed['githubAppId'], 'source credential bundle v2.githubAppId', 32)
490
- if (!GITHUB_APP_ID_PATTERN.test(githubAppId)) throw new Error('source credential bundle v2.githubAppId is invalid')
491
- const privateKeyPem = boundedString(
492
- parsed['privateKeyPem'],
493
- 'source credential bundle v2.privateKeyPem',
494
- MAX_GITHUB_APP_PRIVATE_KEY_LENGTH,
495
- )
496
- if (!isCanonicalPrivateKeyPem(privateKeyPem)) throw new Error('source credential bundle v2.privateKeyPem is invalid')
497
- return { version: ZEROPS_SOURCE_CREDENTIAL_BUNDLE_VERSION_V2, connectionId, githubAppId, privateKeyPem }
498
- }
499
-
500
- const isCanonicalPrivateKeyPem = (value: string): boolean => {
501
- const match = /^-----BEGIN (RSA PRIVATE KEY|PRIVATE KEY)-----\n([A-Za-z0-9+/=\n]+)\n-----END (RSA PRIVATE KEY|PRIVATE KEY)-----\n?$/.exec(value)
502
- if (match === null || match[1] !== match[3]) return false
503
- const body = match[2]
504
- if (body === undefined) return false
505
- const lines = body.split('\n')
506
- if (lines.some((line) => line.length === 0 || line.length > 64)) return false
507
- if (lines.slice(0, -1).some((line) => line.includes('='))) return false
508
- const encoded = lines.join('')
509
- if (encoded.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) return false
510
- try {
511
- const decoded = atob(encoded)
512
- return decoded.length > 0 && decoded.charCodeAt(0) === 0x30
513
- } catch {
514
- return false
515
- }
516
- }
517
-
518
- export function buildZeropsSourceCredentialBundleV2(
519
- input: Omit<ZeropsSourceCredentialBundleV2, 'version'>,
520
- ): ZeropsSourceCredentialBundleV2 {
521
- return sourceCredentialBundleValueV2({ version: ZEROPS_SOURCE_CREDENTIAL_BUNDLE_VERSION_V2, ...input })
522
- }
523
-
524
- export function serializeZeropsSourceCredentialBundleV2(bundle: ZeropsSourceCredentialBundleV2): string {
525
- const parsed = sourceCredentialBundleValueV2(bundle)
526
- return JSON.stringify({
527
- version: parsed.version,
528
- connectionId: parsed.connectionId,
529
- githubAppId: parsed.githubAppId,
530
- privateKeyPem: parsed.privateKeyPem,
531
- })
532
- }
533
-
534
- export function decodeZeropsSourceCredentialBundleV2(value: unknown): ZeropsSourceCredentialBundleV2 {
535
- if (typeof value !== 'string' || new TextEncoder().encode(value).byteLength > MAX_SOURCE_CREDENTIAL_BUNDLE_BYTES) {
536
- throw new Error('source credential bundle v2 must be bounded canonical JSON')
537
- }
538
- let decoded: unknown
539
- try {
540
- decoded = JSON.parse(value)
541
- } catch {
542
- throw new Error('source credential bundle v2 must be bounded canonical JSON')
543
- }
544
- const bundle = sourceCredentialBundleValueV2(decoded)
545
- if (serializeZeropsSourceCredentialBundleV2(bundle) !== value) {
546
- throw new Error('source credential bundle v2 must be bounded canonical JSON')
547
- }
548
- return bundle
549
- }
550
-
551
- export async function sha256ZeropsSourceCredentialBundleV2(value: string): Promise<string> {
552
- decodeZeropsSourceCredentialBundleV2(value)
553
- const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value)))
554
- return [...digest].map((byte) => byte.toString(16).padStart(2, '0')).join('')
555
- }
556
-
557
- export async function zeropsSourceCredentialEnvV2(connectionId: string): Promise<string> {
558
- const canonicalConnectionId = identifier(connectionId, 'source credential connectionId')
559
- const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonicalConnectionId)))
560
- return `${ZEROPS_SOURCE_CREDENTIAL_ENV_V2_PREFIX}${[...digest].map((byte) => byte.toString(16).padStart(2, '0')).join('')}`
561
- }
562
-
563
- const installationId = (value: unknown, label: string): number => {
564
- if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`)
565
- return value
566
- }
567
-
568
- const optionalCommitSha = (value: unknown, label: string): string | undefined => value === undefined ? undefined : commitSha(value, label)
569
-
570
- const optionalInstallationId = (value: unknown, label: string): number | undefined => value === undefined ? undefined : installationId(value, label)
571
-
572
- const privateBindingV2 = (value: unknown, label: string): ZeropsSourcePrivateBindingV2 => {
573
- const parsed = record(value, label)
574
- exactKeys(parsed, ['connectionId', 'installationId'], label)
575
- return {
576
- connectionId: identifier(parsed['connectionId'], `${label}.connectionId`),
577
- installationId: installationId(parsed['installationId'], `${label}.installationId`),
578
- }
579
- }
580
-
581
- const optionalPrivateBindingV2 = (value: unknown, label: string): ZeropsSourcePrivateBindingV2 | undefined =>
582
- value === undefined ? undefined : privateBindingV2(value, label)
583
-
584
- const repository = (value: unknown, label: string): ZeropsSourceRepository => {
585
- const parsed = record(value, label)
586
- exactKeys(parsed, ['owner', 'name'], label)
587
- const owner = boundedString(parsed['owner'], `${label}.owner`, 39)
588
- const name = boundedString(parsed['name'], `${label}.name`, 100)
589
- if (!GITHUB_OWNER_PATTERN.test(owner)) throw new Error(`${label}.owner must be a normalized lowercase GitHub owner`)
590
- if (!GITHUB_REPOSITORY_PATTERN.test(name) || name === '.' || name === '..') {
591
- throw new Error(`${label}.name must be a normalized lowercase GitHub repository name`)
592
- }
593
- return { owner, name }
594
- }
595
-
596
- const descriptor = (value: unknown, label: string): ZeropsSourceDescriptor => {
597
- const parsed = record(value, label)
598
- exactKeys(parsed, ['path', 'sha256'], label)
599
- if (parsed['path'] !== 'zerops.yaml') throw new Error(`${label}.path must be zerops.yaml`)
600
- return { path: 'zerops.yaml', sha256: sha256(parsed['sha256'], `${label}.sha256`) }
601
- }
602
-
603
- const githubAppIdentity = (value: unknown, label: string): ZeropsSourceGitHubAppIdentityV1 => {
604
- const parsed = record(value, label)
605
- exactKeys(parsed, ['id', 'slug', 'htmlUrl', 'public', 'owner', 'permissions', 'events'], label)
606
- const id = installationId(parsed['id'], `${label}.id`)
607
- const slug = boundedString(parsed['slug'], `${label}.slug`, 100)
608
- if (!GITHUB_APP_SLUG_PATTERN.test(slug)) throw new Error(`${label}.slug is invalid`)
609
- const htmlUrl = boundedString(parsed['htmlUrl'], `${label}.htmlUrl`, 2048)
610
- let parsedHtmlUrl: URL
611
- try {
612
- parsedHtmlUrl = new URL(htmlUrl)
613
- } catch {
614
- throw new Error(`${label}.htmlUrl is invalid`)
615
- }
616
- if (
617
- parsedHtmlUrl.protocol !== 'https:' || parsedHtmlUrl.hostname !== 'github.com' || parsedHtmlUrl.port !== ''
618
- || parsedHtmlUrl.username !== '' || parsedHtmlUrl.password !== '' || parsedHtmlUrl.search !== '' || parsedHtmlUrl.hash !== ''
619
- || parsedHtmlUrl.pathname !== `/apps/${slug}`
620
- ) {
621
- throw new Error(`${label}.htmlUrl is invalid`)
622
- }
623
- if (typeof parsed['public'] !== 'boolean') throw new Error(`${label}.public must be boolean`)
624
- const owner = record(parsed['owner'], `${label}.owner`)
625
- exactKeys(owner, ['login', 'type'], `${label}.owner`)
626
- const login = boundedString(owner['login'], `${label}.owner.login`, 100)
627
- if (!GITHUB_LOGIN_PATTERN.test(login) || owner['type'] !== 'Organization') throw new Error(`${label}.owner is invalid`)
628
- const permissions = record(parsed['permissions'], `${label}.permissions`)
629
- exactKeys(permissions, ['contents'], `${label}.permissions`)
630
- if (permissions['contents'] !== 'read') throw new Error(`${label}.permissions is invalid`)
631
- if (!Array.isArray(parsed['events']) || parsed['events'].length !== 1 || parsed['events'][0] !== 'push') {
632
- throw new Error(`${label}.events is invalid`)
633
- }
634
- return {
635
- id,
636
- slug,
637
- htmlUrl,
638
- public: parsed['public'],
639
- owner: { login, type: 'Organization' },
640
- permissions: { contents: 'read' },
641
- events: ['push'],
642
- }
643
- }
644
-
645
- const optionalFields = (value: Record<string, unknown>, required: readonly string[], optional: readonly string[], label: string): void => {
646
- const allowed = [...required, ...optional]
647
- const unknownKey = Object.keys(value).find((key) => !allowed.includes(key))
648
- if (unknownKey !== undefined) throw new Error(`${label} contains an unknown field`)
649
- const missingKey = required.find((key) => !(key in value))
650
- if (missingKey !== undefined) throw new Error(`${label} is missing field ${missingKey}`)
651
- for (const key of optional) {
652
- if (key in value && value[key] === undefined) throw new Error(`${label}.${key} must be omitted instead of undefined`)
653
- }
654
- }
655
-
656
- /** Validate only credential-safe URL structure; the source runtime owns the exact Zerops destination allowlist. */
657
- const uploadUrl = (value: unknown, label: string): string => {
658
- const candidate = boundedString(value, label, MAX_UPLOAD_URL_LENGTH)
659
- if (/\s/u.test(candidate) || candidate.includes('#')) throw new Error(`${label} must be a signed HTTPS URL`)
660
- let parsed: URL
661
- try {
662
- parsed = new URL(candidate)
663
- } catch {
664
- throw new Error(`${label} must be a signed HTTPS URL`)
665
- }
666
- if (
667
- parsed.protocol !== 'https:'
668
- || parsed.username !== ''
669
- || parsed.password !== ''
670
- || parsed.hash !== ''
671
- || parsed.search === ''
672
- ) {
673
- throw new Error(`${label} must be a signed HTTPS URL without userinfo or fragment`)
674
- }
675
- return candidate
676
- }
677
-
678
- export function normalizeZeropsSourceRepository(repoUrl: string): ZeropsSourceRepository {
679
- if (/\s/u.test(repoUrl) || /\p{Cc}/u.test(repoUrl)) {
680
- throw new Error('source repository URL must be a canonical GitHub repository URL')
681
- }
682
- const candidate = repoUrl.startsWith('github.com/') ? `https://${repoUrl}` : repoUrl
683
- let parsed: URL
684
- try {
685
- parsed = new URL(candidate)
686
- } catch {
687
- throw new Error('source repository URL must be a canonical GitHub repository URL')
688
- }
689
- if (
690
- parsed.protocol !== 'https:' || parsed.hostname !== 'github.com' || parsed.port !== '' || parsed.username !== '' || parsed.password !== ''
691
- || parsed.search !== '' || parsed.hash !== ''
692
- ) {
693
- throw new Error('source repository URL must be an HTTPS GitHub URL without credentials, port, query, or fragment')
694
- }
695
- const segments = parsed.pathname.split('/')
696
- if (segments.length !== 3 || segments[0] !== '') throw new Error('source repository URL must identify one GitHub repository')
697
- const owner = segments[1]
698
- const rawName = segments[2]
699
- if (owner === undefined || rawName === undefined) throw new Error('source repository URL must identify one GitHub repository')
700
- const name = rawName.endsWith('.git') ? rawName.slice(0, -4) : rawName
701
- return repository({ owner: owner.toLowerCase(), name: name.toLowerCase() }, 'source repository')
702
- }
703
-
704
- export function decodeZeropsSourceCredentialActivateRequestV2(value: unknown): ZeropsSourceCredentialActivateRequestV2 {
705
- const parsed = record(value, 'source credential activate request v2')
706
- exactKeys(
707
- parsed,
708
- ['protocolVersion', 'connectionId', 'credentialBundle', 'credentialSha256'],
709
- 'source credential activate request v2',
710
- )
711
- const connectionId = identifier(parsed['connectionId'], 'source credential activate request v2.connectionId')
712
- const credentialBundle = boundedString(
713
- parsed['credentialBundle'],
714
- 'source credential activate request v2.credentialBundle',
715
- MAX_SOURCE_CREDENTIAL_BUNDLE_BYTES,
716
- )
717
- const bundle = decodeZeropsSourceCredentialBundleV2(credentialBundle)
718
- if (bundle.connectionId !== connectionId) throw new Error('source credential activate request v2 connectionId does not match its bundle')
719
- return {
720
- protocolVersion: protocolVersionV2(parsed, 'source credential activate request v2'),
721
- connectionId,
722
- credentialBundle,
723
- credentialSha256: sha256(parsed['credentialSha256'], 'source credential activate request v2.credentialSha256'),
724
- }
725
- }
726
-
727
- export function buildZeropsSourceCredentialActivateRequestV2(
728
- input: ZeropsSourceCredentialActivateInput,
729
- ): ZeropsSourceCredentialActivateRequestV2 {
730
- return decodeZeropsSourceCredentialActivateRequestV2({
731
- protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION_V2,
732
- connectionId: input.connectionId,
733
- credentialBundle: input.credentialBundle,
734
- credentialSha256: input.credentialSha256,
735
- })
736
- }
737
-
738
- export function decodeZeropsSourceCredentialActivateResponseV2(value: unknown): ZeropsSourceCredentialActivateResponseV2 {
739
- const parsed = record(value, 'source credential activate response v2')
740
- exactKeys(
741
- parsed,
742
- ['protocolVersion', 'connectionId', 'credentialVersion', 'credentialSha256', 'githubApp'],
743
- 'source credential activate response v2',
744
- )
745
- if (parsed['credentialVersion'] !== ZEROPS_SOURCE_CREDENTIAL_BUNDLE_VERSION_V2) {
746
- throw new Error('source credential activate response v2 has an unsupported credentialVersion')
747
- }
748
- return {
749
- protocolVersion: protocolVersionV2(parsed, 'source credential activate response v2'),
750
- connectionId: identifier(parsed['connectionId'], 'source credential activate response v2.connectionId'),
751
- credentialVersion: ZEROPS_SOURCE_CREDENTIAL_BUNDLE_VERSION_V2,
752
- credentialSha256: sha256(parsed['credentialSha256'], 'source credential activate response v2.credentialSha256'),
753
- githubApp: githubAppIdentity(parsed['githubApp'], 'source credential activate response v2.githubApp'),
754
- }
755
- }
756
-
757
- export function buildZeropsSourceCredentialActivateResponseV2(
758
- input: Omit<ZeropsSourceCredentialActivateResponseV2, 'protocolVersion'>,
759
- ): ZeropsSourceCredentialActivateResponseV2 {
760
- return decodeZeropsSourceCredentialActivateResponseV2({ protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION_V2, ...input })
761
- }
762
-
763
- export function decodeZeropsSourceCredentialStatusRequestV2(value: unknown): ZeropsSourceCredentialStatusRequestV2 {
764
- const parsed = record(value, 'source credential status request v2')
765
- exactKeys(parsed, ['protocolVersion', 'connectionId'], 'source credential status request v2')
766
- return {
767
- protocolVersion: protocolVersionV2(parsed, 'source credential status request v2'),
768
- connectionId: identifier(parsed['connectionId'], 'source credential status request v2.connectionId'),
769
- }
770
- }
771
-
772
- export function buildZeropsSourceCredentialStatusRequestV2(
773
- input: ZeropsSourceCredentialStatusInput,
774
- ): ZeropsSourceCredentialStatusRequestV2 {
775
- return decodeZeropsSourceCredentialStatusRequestV2({
776
- protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION_V2,
777
- connectionId: input.connectionId,
778
- })
779
- }
780
-
781
- export function decodeZeropsSourceCredentialStatusResponseV2(value: unknown): ZeropsSourceCredentialStatusResponseV2 {
782
- const parsed = record(value, 'source credential status response v2')
783
- if (parsed['state'] === 'anonymous') {
784
- exactKeys(parsed, ['protocolVersion', 'connectionId', 'state'], 'source credential status response v2')
785
- return {
786
- protocolVersion: protocolVersionV2(parsed, 'source credential status response v2'),
787
- connectionId: identifier(parsed['connectionId'], 'source credential status response v2.connectionId'),
788
- state: 'anonymous',
789
- }
790
- }
791
- if (parsed['state'] !== 'active') throw new Error('source credential status response v2.state is invalid')
792
- exactKeys(
793
- parsed,
794
- ['protocolVersion', 'connectionId', 'state', 'credentialVersion', 'credentialSha256', 'githubApp'],
795
- 'source credential status response v2',
796
- )
797
- const active = decodeZeropsSourceCredentialActivateResponseV2({
798
- protocolVersion: parsed['protocolVersion'],
799
- connectionId: parsed['connectionId'],
800
- credentialVersion: parsed['credentialVersion'],
801
- credentialSha256: parsed['credentialSha256'],
802
- githubApp: parsed['githubApp'],
803
- })
804
- return { ...active, state: 'active' }
805
- }
806
-
807
- export function buildZeropsSourceCredentialStatusResponseV2(
808
- input:
809
- | { readonly connectionId: string; readonly state: 'anonymous' }
810
- | Omit<ZeropsSourceCredentialActiveStatusResponseV2, 'protocolVersion'>,
811
- ): ZeropsSourceCredentialStatusResponseV2 {
812
- return decodeZeropsSourceCredentialStatusResponseV2({ protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION_V2, ...input })
813
- }
814
-
815
- const WEBHOOK_PATH = '/webhooks/github'
816
-
817
- /**
818
- * Only the Zerops `source` configure wire reaches this, and every Zerops connection appends its own id
819
- * so one App's deliveries cannot be replayed against another. The bare path is a Cloudflare URL and is
820
- * refused here (ADR-0039).
821
- */
822
- const isWebhookPath = (pathname: string): boolean => {
823
- if (!pathname.startsWith(`${WEBHOOK_PATH}/`)) return false
824
- const connectionId = pathname.slice(WEBHOOK_PATH.length + 1)
825
- // `ID_PATTERN` has no `%`, so a percent-escape in the path fails here rather than round-tripping.
826
- return connectionId.length > 0 && connectionId.length <= MAX_ID_LENGTH && ID_PATTERN.test(connectionId)
827
- }
828
-
829
- const webhookUrl = (value: unknown, label: string): string => {
830
- const candidate = boundedString(value, label, MAX_WEBHOOK_URL_LENGTH)
831
- let parsed: URL
832
- try {
833
- parsed = new URL(candidate)
834
- } catch {
835
- throw new Error(`${label} must be a secure URL`)
836
- }
837
- if (
838
- parsed.protocol !== 'https:' || parsed.port !== '' || parsed.username !== '' || parsed.password !== '' || parsed.search !== '' || parsed.hash !== ''
839
- || !isWebhookPath(parsed.pathname) || parsed.hostname === '' || candidate !== parsed.href
840
- ) throw new Error(`${label} must be an exact secure GitHub webhook URL`)
841
- return candidate
842
- }
843
-
844
- const webhookSecret = (value: unknown, label: string): string => {
845
- const secret = boundedString(value, label, MAX_WEBHOOK_SECRET_LENGTH)
846
- if ([...secret].some((character) => character.charCodeAt(0) < 0x20 || character.charCodeAt(0) === 0x7f)) {
847
- throw new Error(`${label} is invalid`)
848
- }
849
- return secret
850
- }
851
-
852
- export function decodeZeropsSourceWebhookConfigureRequest(value: unknown): ZeropsSourceWebhookConfigureRequestV1 {
853
- const parsed = record(value, 'source webhook configure request')
854
- exactKeys(parsed, ['protocolVersion', 'connectionId', 'credentialSha256', 'url', 'secret'], 'source webhook configure request')
855
- return {
856
- protocolVersion: protocolVersion(parsed, 'source webhook configure request'),
857
- connectionId: identifier(parsed['connectionId'], 'source webhook configure request.connectionId'),
858
- credentialSha256: sha256(parsed['credentialSha256'], 'source webhook configure request.credentialSha256'),
859
- url: webhookUrl(parsed['url'], 'source webhook configure request.url'),
860
- secret: webhookSecret(parsed['secret'], 'source webhook configure request.secret'),
861
- }
862
- }
863
-
864
- export function buildZeropsSourceWebhookConfigureRequest(input: ZeropsSourceWebhookConfigureInput): ZeropsSourceWebhookConfigureRequestV1 {
865
- return decodeZeropsSourceWebhookConfigureRequest({
866
- protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION,
867
- connectionId: input.connectionId,
868
- credentialSha256: input.credentialSha256,
869
- url: input.url,
870
- secret: input.secret,
871
- })
872
- }
873
-
874
- export function decodeZeropsSourceWebhookConfigureResponse(value: unknown): ZeropsSourceWebhookConfigureResponseV1 {
875
- const parsed = record(value, 'source webhook configure response')
876
- exactKeys(parsed, ['protocolVersion', 'connectionId', 'credentialSha256', 'webhook'], 'source webhook configure response')
877
- const webhook = record(parsed['webhook'], 'source webhook configure response.webhook')
878
- exactKeys(webhook, ['url', 'contentType', 'insecureSsl'], 'source webhook configure response.webhook')
879
- if (webhook['contentType'] !== 'json' || webhook['insecureSsl'] !== '0') throw new Error('source webhook configure response is insecure')
880
- return {
881
- protocolVersion: protocolVersion(parsed, 'source webhook configure response'),
882
- connectionId: identifier(parsed['connectionId'], 'source webhook configure response.connectionId'),
883
- credentialSha256: sha256(parsed['credentialSha256'], 'source webhook configure response.credentialSha256'),
884
- webhook: { url: webhookUrl(webhook['url'], 'source webhook configure response.webhook.url'), contentType: 'json', insecureSsl: '0' },
885
- }
886
- }
887
-
888
- export function buildZeropsSourceWebhookConfigureResponse(
889
- input: Omit<ZeropsSourceWebhookConfigureResponseV1, 'protocolVersion'>,
890
- ): ZeropsSourceWebhookConfigureResponseV1 {
891
- return decodeZeropsSourceWebhookConfigureResponse({ protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION, ...input })
892
- }
893
-
894
- const installationScope = (value: unknown, label: string): ZeropsSourceInstallationScopeV1 => {
895
- const parsed = record(value, label)
896
- if (parsed['kind'] === 'organization') {
897
- exactKeys(parsed, ['kind', 'organization'], label)
898
- const organization = boundedString(parsed['organization'], `${label}.organization`, 39).toLowerCase()
899
- if (!GITHUB_OWNER_PATTERN.test(organization)) throw new Error(`${label}.organization is invalid`)
900
- return { kind: 'organization', organization }
901
- }
902
- if (parsed['kind'] !== 'repositories') throw new Error(`${label}.kind is invalid`)
903
- exactKeys(parsed, ['kind', 'repositories'], label)
904
- if (!Array.isArray(parsed['repositories']) || parsed['repositories'].length === 0 || parsed['repositories'].length > MAX_INSTALLATION_REPOSITORIES) {
905
- throw new Error(`${label}.repositories is invalid`)
906
- }
907
- const repositories = parsed['repositories'].map((entry) => repository(entry, `${label}.repositories entry`))
908
- const keys = repositories.map((entry) => `${entry.owner}/${entry.name}`)
909
- if (new Set(keys).size !== keys.length) throw new Error(`${label}.repositories contains a duplicate`)
910
- return { kind: 'repositories', repositories }
911
- }
912
-
913
- export function decodeZeropsSourceInstallationsVerifyRequest(value: unknown): ZeropsSourceInstallationsVerifyRequestV1 {
914
- const parsed = record(value, 'source installations verify request')
915
- exactKeys(parsed, ['protocolVersion', 'connectionId', 'credentialSha256', 'scope'], 'source installations verify request')
916
- return {
917
- protocolVersion: protocolVersion(parsed, 'source installations verify request'),
918
- connectionId: identifier(parsed['connectionId'], 'source installations verify request.connectionId'),
919
- credentialSha256: sha256(parsed['credentialSha256'], 'source installations verify request.credentialSha256'),
920
- scope: installationScope(parsed['scope'], 'source installations verify request.scope'),
921
- }
922
- }
923
-
924
- export function buildZeropsSourceInstallationsVerifyRequest(
925
- input: ZeropsSourceInstallationsVerifyInput,
926
- ): ZeropsSourceInstallationsVerifyRequestV1 {
927
- return decodeZeropsSourceInstallationsVerifyRequest({
928
- protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION,
929
- connectionId: input.connectionId,
930
- credentialSha256: input.credentialSha256,
931
- scope: input.scope,
932
- })
933
- }
934
-
935
- const installationVerification = (value: unknown, label: string): ZeropsSourceInstallationVerificationV1 => {
936
- const parsed = record(value, label)
937
- if (parsed['status'] === 'missing') {
938
- exactKeys(parsed, ['status'], label)
939
- return { status: 'missing' }
940
- }
941
- if (parsed['status'] !== 'installed') throw new Error(`${label}.status is invalid`)
942
- exactKeys(parsed, ['status', 'installationId', 'accountLogin', 'repositorySelection'], label)
943
- const accountLogin = boundedString(parsed['accountLogin'], `${label}.accountLogin`, 100)
944
- if (!GITHUB_LOGIN_PATTERN.test(accountLogin)) throw new Error(`${label}.accountLogin is invalid`)
945
- const repositorySelection = parsed['repositorySelection']
946
- if (repositorySelection !== 'all' && repositorySelection !== 'selected') throw new Error(`${label}.repositorySelection is invalid`)
947
- return {
948
- status: 'installed',
949
- installationId: installationId(parsed['installationId'], `${label}.installationId`),
950
- accountLogin,
951
- repositorySelection,
952
- }
953
- }
954
-
955
- export function decodeZeropsSourceInstallationsVerifyResponse(value: unknown): ZeropsSourceInstallationsVerifyResponseV1 {
956
- const parsed = record(value, 'source installations verify response')
957
- exactKeys(parsed, ['protocolVersion', 'connectionId', 'credentialSha256', 'installation'], 'source installations verify response')
958
- return {
959
- protocolVersion: protocolVersion(parsed, 'source installations verify response'),
960
- connectionId: identifier(parsed['connectionId'], 'source installations verify response.connectionId'),
961
- credentialSha256: sha256(parsed['credentialSha256'], 'source installations verify response.credentialSha256'),
962
- installation: installationVerification(parsed['installation'], 'source installations verify response.installation'),
963
- }
964
- }
965
-
966
- export function buildZeropsSourceInstallationsVerifyResponse(
967
- input: Omit<ZeropsSourceInstallationsVerifyResponseV1, 'protocolVersion'>,
968
- ): ZeropsSourceInstallationsVerifyResponseV1 {
969
- return decodeZeropsSourceInstallationsVerifyResponse({ protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION, ...input })
970
- }
971
-
972
- export function decodeZeropsSourceResolveRequest(value: unknown): ZeropsSourceResolveRequestV1 {
973
- const parsed = record(value, 'source resolve request')
974
- optionalFields(
975
- parsed,
976
- ['protocolVersion', 'runId', 'repository', 'requestedRef', 'descriptorSha256'],
977
- ['expectedCommitSha', 'githubInstallationId'],
978
- 'source resolve request',
979
- )
980
- const expected = optionalCommitSha(parsed['expectedCommitSha'], 'source resolve request.expectedCommitSha')
981
- const installation = optionalInstallationId(parsed['githubInstallationId'], 'source resolve request.githubInstallationId')
982
- return {
983
- protocolVersion: protocolVersion(parsed, 'source resolve request'),
984
- runId: identifier(parsed['runId'], 'source resolve request.runId'),
985
- repository: repository(parsed['repository'], 'source resolve request.repository'),
986
- requestedRef: ref(parsed['requestedRef'], 'source resolve request.requestedRef'),
987
- ...(expected !== undefined ? { expectedCommitSha: expected } : {}),
988
- ...(installation !== undefined ? { githubInstallationId: installation } : {}),
989
- descriptorSha256: sha256(parsed['descriptorSha256'], 'source resolve request.descriptorSha256'),
990
- }
991
- }
992
-
993
- export function buildZeropsSourceResolveRequest(input: ZeropsSourceResolveInput): ZeropsSourceResolveRequestV1 {
994
- return decodeZeropsSourceResolveRequest({
995
- protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION,
996
- runId: input.runId,
997
- repository: input.repository,
998
- requestedRef: input.requestedRef,
999
- ...(input.expectedCommitSha !== undefined ? { expectedCommitSha: input.expectedCommitSha } : {}),
1000
- ...(input.githubInstallationId !== undefined ? { githubInstallationId: input.githubInstallationId } : {}),
1001
- descriptorSha256: input.descriptorSha256,
1002
- })
1003
- }
1004
-
1005
- export function decodeZeropsSourceResolveResponse(value: unknown): ZeropsSourceResolveResponseV1 {
1006
- const parsed = record(value, 'source resolve response')
1007
- exactKeys(parsed, ['protocolVersion', 'runId', 'commitSha', 'descriptorSha256'], 'source resolve response')
1008
- return {
1009
- protocolVersion: protocolVersion(parsed, 'source resolve response'),
1010
- runId: identifier(parsed['runId'], 'source resolve response.runId'),
1011
- commitSha: commitSha(parsed['commitSha'], 'source resolve response.commitSha'),
1012
- descriptorSha256: sha256(parsed['descriptorSha256'], 'source resolve response.descriptorSha256'),
1013
- }
1014
- }
1015
-
1016
- export function buildZeropsSourceResolveResponse(result: ZeropsSourceResolveResult): ZeropsSourceResolveResponseV1 {
1017
- return decodeZeropsSourceResolveResponse({ protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION, ...result })
1018
- }
1019
-
1020
- export function decodeZeropsSourceUploadRequest(value: unknown): ZeropsSourceUploadRequestV1 {
1021
- const parsed = record(value, 'source upload request')
1022
- optionalFields(
1023
- parsed,
1024
- ['protocolVersion', 'runId', 'appVersionId', 'repository', 'commitSha', 'uploadUrl', 'descriptor'],
1025
- ['githubInstallationId'],
1026
- 'source upload request',
1027
- )
1028
- const installation = optionalInstallationId(parsed['githubInstallationId'], 'source upload request.githubInstallationId')
1029
- return {
1030
- protocolVersion: protocolVersion(parsed, 'source upload request'),
1031
- runId: identifier(parsed['runId'], 'source upload request.runId'),
1032
- appVersionId: identifier(parsed['appVersionId'], 'source upload request.appVersionId'),
1033
- repository: repository(parsed['repository'], 'source upload request.repository'),
1034
- commitSha: commitSha(parsed['commitSha'], 'source upload request.commitSha'),
1035
- ...(installation !== undefined ? { githubInstallationId: installation } : {}),
1036
- uploadUrl: uploadUrl(parsed['uploadUrl'], 'source upload request.uploadUrl'),
1037
- descriptor: descriptor(parsed['descriptor'], 'source upload request.descriptor'),
1038
- }
1039
- }
1040
-
1041
- export function buildZeropsSourceUploadRequest(input: ZeropsSourceUploadInput): ZeropsSourceUploadRequestV1 {
1042
- return decodeZeropsSourceUploadRequest({
1043
- protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION,
1044
- runId: input.runId,
1045
- appVersionId: input.appVersionId,
1046
- repository: input.repository,
1047
- commitSha: input.commitSha,
1048
- ...(input.githubInstallationId !== undefined ? { githubInstallationId: input.githubInstallationId } : {}),
1049
- uploadUrl: input.uploadUrl,
1050
- descriptor: input.descriptor,
1051
- })
1052
- }
1053
-
1054
- export function decodeZeropsSourceUploadResponse(value: unknown): ZeropsSourceUploadResponseV1 {
1055
- const parsed = record(value, 'source upload response')
1056
- exactKeys(parsed, ['protocolVersion', 'runId', 'appVersionId', 'commitSha', 'descriptorSha256'], 'source upload response')
1057
- return {
1058
- protocolVersion: protocolVersion(parsed, 'source upload response'),
1059
- runId: identifier(parsed['runId'], 'source upload response.runId'),
1060
- appVersionId: identifier(parsed['appVersionId'], 'source upload response.appVersionId'),
1061
- commitSha: commitSha(parsed['commitSha'], 'source upload response.commitSha'),
1062
- descriptorSha256: sha256(parsed['descriptorSha256'], 'source upload response.descriptorSha256'),
1063
- }
1064
- }
1065
-
1066
- export function buildZeropsSourceUploadResponse(result: ZeropsSourceUploadResult): ZeropsSourceUploadResponseV1 {
1067
- return decodeZeropsSourceUploadResponse({ protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION, ...result })
1068
- }
1069
-
1070
- export function decodeZeropsSourceResolveRequestV2(value: unknown): ZeropsSourceResolveRequestV2 {
1071
- const parsed = record(value, 'source resolve request v2')
1072
- optionalFields(
1073
- parsed,
1074
- ['protocolVersion', 'runId', 'repository', 'requestedRef', 'descriptorSha256'],
1075
- ['expectedCommitSha', 'privateBinding'],
1076
- 'source resolve request v2',
1077
- )
1078
- const expected = optionalCommitSha(parsed['expectedCommitSha'], 'source resolve request v2.expectedCommitSha')
1079
- const binding = optionalPrivateBindingV2(parsed['privateBinding'], 'source resolve request v2.privateBinding')
1080
- return {
1081
- protocolVersion: protocolVersionV2(parsed, 'source resolve request v2'),
1082
- runId: identifier(parsed['runId'], 'source resolve request v2.runId'),
1083
- repository: repository(parsed['repository'], 'source resolve request v2.repository'),
1084
- requestedRef: ref(parsed['requestedRef'], 'source resolve request v2.requestedRef'),
1085
- ...(expected !== undefined ? { expectedCommitSha: expected } : {}),
1086
- ...(binding !== undefined ? { privateBinding: binding } : {}),
1087
- descriptorSha256: sha256(parsed['descriptorSha256'], 'source resolve request v2.descriptorSha256'),
1088
- }
1089
- }
1090
-
1091
- export function buildZeropsSourceResolveRequestV2(input: ZeropsSourceResolveInputV2): ZeropsSourceResolveRequestV2 {
1092
- return decodeZeropsSourceResolveRequestV2({
1093
- protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION_V2,
1094
- runId: input.runId,
1095
- repository: input.repository,
1096
- requestedRef: input.requestedRef,
1097
- ...(input.expectedCommitSha !== undefined ? { expectedCommitSha: input.expectedCommitSha } : {}),
1098
- ...(input.privateBinding !== undefined ? { privateBinding: input.privateBinding } : {}),
1099
- descriptorSha256: input.descriptorSha256,
1100
- })
1101
- }
1102
-
1103
- export function decodeZeropsSourceResolveResponseV2(value: unknown): ZeropsSourceResolveResponseV2 {
1104
- const parsed = record(value, 'source resolve response v2')
1105
- exactKeys(parsed, ['protocolVersion', 'runId', 'commitSha', 'descriptorSha256'], 'source resolve response v2')
1106
- return {
1107
- protocolVersion: protocolVersionV2(parsed, 'source resolve response v2'),
1108
- runId: identifier(parsed['runId'], 'source resolve response v2.runId'),
1109
- commitSha: commitSha(parsed['commitSha'], 'source resolve response v2.commitSha'),
1110
- descriptorSha256: sha256(parsed['descriptorSha256'], 'source resolve response v2.descriptorSha256'),
1111
- }
1112
- }
1113
-
1114
- export function buildZeropsSourceResolveResponseV2(result: ZeropsSourceResolveResult): ZeropsSourceResolveResponseV2 {
1115
- return decodeZeropsSourceResolveResponseV2({ protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION_V2, ...result })
1116
- }
1117
-
1118
- export function decodeZeropsSourceUploadRequestV2(value: unknown): ZeropsSourceUploadRequestV2 {
1119
- const parsed = record(value, 'source upload request v2')
1120
- optionalFields(
1121
- parsed,
1122
- ['protocolVersion', 'runId', 'appVersionId', 'repository', 'commitSha', 'uploadUrl', 'descriptor'],
1123
- ['privateBinding'],
1124
- 'source upload request v2',
1125
- )
1126
- const binding = optionalPrivateBindingV2(parsed['privateBinding'], 'source upload request v2.privateBinding')
1127
- return {
1128
- protocolVersion: protocolVersionV2(parsed, 'source upload request v2'),
1129
- runId: identifier(parsed['runId'], 'source upload request v2.runId'),
1130
- appVersionId: identifier(parsed['appVersionId'], 'source upload request v2.appVersionId'),
1131
- repository: repository(parsed['repository'], 'source upload request v2.repository'),
1132
- commitSha: commitSha(parsed['commitSha'], 'source upload request v2.commitSha'),
1133
- ...(binding !== undefined ? { privateBinding: binding } : {}),
1134
- uploadUrl: uploadUrl(parsed['uploadUrl'], 'source upload request v2.uploadUrl'),
1135
- descriptor: descriptor(parsed['descriptor'], 'source upload request v2.descriptor'),
1136
- }
1137
- }
1138
-
1139
- export function buildZeropsSourceUploadRequestV2(input: ZeropsSourceUploadInputV2): ZeropsSourceUploadRequestV2 {
1140
- return decodeZeropsSourceUploadRequestV2({
1141
- protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION_V2,
1142
- runId: input.runId,
1143
- appVersionId: input.appVersionId,
1144
- repository: input.repository,
1145
- commitSha: input.commitSha,
1146
- ...(input.privateBinding !== undefined ? { privateBinding: input.privateBinding } : {}),
1147
- uploadUrl: input.uploadUrl,
1148
- descriptor: input.descriptor,
1149
- })
1150
- }
1151
-
1152
- export function decodeZeropsSourceUploadResponseV2(value: unknown): ZeropsSourceUploadResponseV2 {
1153
- const parsed = record(value, 'source upload response v2')
1154
- exactKeys(parsed, ['protocolVersion', 'runId', 'appVersionId', 'commitSha', 'descriptorSha256'], 'source upload response v2')
1155
- return {
1156
- protocolVersion: protocolVersionV2(parsed, 'source upload response v2'),
1157
- runId: identifier(parsed['runId'], 'source upload response v2.runId'),
1158
- appVersionId: identifier(parsed['appVersionId'], 'source upload response v2.appVersionId'),
1159
- commitSha: commitSha(parsed['commitSha'], 'source upload response v2.commitSha'),
1160
- descriptorSha256: sha256(parsed['descriptorSha256'], 'source upload response v2.descriptorSha256'),
1161
- }
1162
- }
1163
-
1164
- export function buildZeropsSourceUploadResponseV2(result: ZeropsSourceUploadResult): ZeropsSourceUploadResponseV2 {
1165
- return decodeZeropsSourceUploadResponseV2({ protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION_V2, ...result })
1166
- }
1167
-
1168
- export function decodeZeropsSourceCancelRequest(value: unknown): ZeropsSourceCancelRequestV1 {
1169
- const parsed = record(value, 'source cancel request')
1170
- exactKeys(parsed, ['protocolVersion', 'runId', 'appVersionId'], 'source cancel request')
1171
- return {
1172
- protocolVersion: protocolVersion(parsed, 'source cancel request'),
1173
- runId: identifier(parsed['runId'], 'source cancel request.runId'),
1174
- appVersionId: identifier(parsed['appVersionId'], 'source cancel request.appVersionId'),
1175
- }
1176
- }
1177
-
1178
- export function buildZeropsSourceCancelRequest(input: ZeropsSourceCancelInput): ZeropsSourceCancelRequestV1 {
1179
- return decodeZeropsSourceCancelRequest({
1180
- protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION,
1181
- runId: input.runId,
1182
- appVersionId: input.appVersionId,
1183
- })
1184
- }
1185
-
1186
- export function decodeZeropsSourceCancelResponse(value: unknown): ZeropsSourceCancelResponseV1 {
1187
- const parsed = record(value, 'source cancel response')
1188
- exactKeys(parsed, ['protocolVersion', 'runId', 'appVersionId'], 'source cancel response')
1189
- return {
1190
- protocolVersion: protocolVersion(parsed, 'source cancel response'),
1191
- runId: identifier(parsed['runId'], 'source cancel response.runId'),
1192
- appVersionId: identifier(parsed['appVersionId'], 'source cancel response.appVersionId'),
1193
- }
1194
- }
1195
-
1196
- export function buildZeropsSourceCancelResponse(result: ZeropsSourceCancelResult): ZeropsSourceCancelResponseV1 {
1197
- return decodeZeropsSourceCancelResponse({ protocolVersion: ZEROPS_SOURCE_PROTOCOL_VERSION, ...result })
1198
- }
1199
-
1200
- const includesString = <T extends string>(values: readonly T[], value: unknown): value is T =>
1201
- typeof value === 'string' && values.some((entry) => entry === value)
1202
-
1203
- export function decodeZeropsSourceErrorEnvelope(value: unknown): ZeropsSourceErrorEnvelope {
1204
- const parsed = record(value, 'source error response')
1205
- exactKeys(parsed, ['error'], 'source error response')
1206
- const error = record(parsed['error'], 'source error response.error')
1207
- exactKeys(error, ['code', 'stage', 'retryable'], 'source error response.error')
1208
- if (!includesString(ZEROPS_SOURCE_ERROR_CODES, error['code'])) throw new Error('source error response.error.code is invalid')
1209
- if (!includesString(ZEROPS_SOURCE_ERROR_STAGES, error['stage'])) throw new Error('source error response.error.stage is invalid')
1210
- if (typeof error['retryable'] !== 'boolean') throw new Error('source error response.error.retryable must be boolean')
1211
- return { error: { code: error['code'], stage: error['stage'], retryable: error['retryable'] } }
1212
- }
1213
-
1214
- export function buildZeropsSourceErrorEnvelope(
1215
- code: ZeropsSourceErrorCode,
1216
- stage: ZeropsSourceErrorStage,
1217
- retryable: boolean,
1218
- ): ZeropsSourceErrorEnvelope {
1219
- return decodeZeropsSourceErrorEnvelope({ error: { code, stage, retryable } })
1220
- }
1
+ export type { ZeropsSourceRepository } from './source/types'
2
+ export { isZeropsUploadDestination, normalizeZeropsSourceRepository } from './source/validation'