Showing the changes to packages/plugins/apps/src/source/github-source.ts (1 of 33 files). Show all
Unified · Split
| @@ -1,4 +1,4 @@ | ||
| 1 | −import { Data, Effect, Result } from "effect"; | |
| 1 | +import { Data, Effect, Predicate, Result, Schema } from "effect"; | |
| 2 | 2 | |
| 3 | 3 | import { PublishError, type FileDiagnostic } from "../pipeline/discover"; |
| 4 | 4 | import { PUBLISH_LIMITS, enforcePublishLimits } from "../pipeline/publish"; |
| @@ -106,37 +106,42 @@ const requestJson = <A>( · 101 unmodified lines | ||
| 106 | 106 | input: GitHubSourceInput, |
| 107 | 107 | path: string, |
| 108 | 108 | ): Effect.Effect<A, GitHubSourceError> => |
| 109 | − Effect.tryPromise({ | |
| 110 | − try: async () => { | |
| 111 | − const fetchImpl = input.fetch ?? globalThis.fetch; | |
| 112 | − const headers: Record<string, string> = { | |
| 113 | − accept: "application/vnd.github+json", | |
| 114 | − "user-agent": "executor-apps-github-source", | |
| 115 | − }; | |
| 116 | − if (input.token) headers.authorization = `Bearer ${input.token}`; | |
| 117 | − const response = await fetchImpl( | |
| 118 | − `${trimBaseUrl(input.baseUrl ?? "https://api.github.com")}${path}`, | |
| 119 | − { | |
| 109 | + Effect.gen(function* () { | |
| 110 | + const response = yield* Effect.tryPromise({ | |
| 111 | + try: async () => { | |
| 112 | + const fetchImpl = input.fetch ?? globalThis.fetch; | |
| 113 | + const headers: Record<string, string> = { | |
| 114 | + accept: "application/vnd.github+json", | |
| 115 | + "user-agent": "executor-apps-github-source", | |
| 116 | + }; | |
| 117 | + if (input.token) headers.authorization = `Bearer ${input.token}`; | |
| 118 | + return fetchImpl(`${trimBaseUrl(input.baseUrl ?? "https://api.github.com")}${path}`, { | |
| 120 | 119 | headers, |
| 121 | − }, | |
| 122 | − ); | |
| 123 | − if (!response.ok) { | |
| 124 | − throw new GitHubSourceError({ | |
| 125 | − message: `GitHub request failed: GET ${path} -> ${response.status}`, | |
| 126 | − status: response.status, | |
| 127 | − path, | |
| 128 | 120 | }); |
| 129 | − } | |
| 130 | − return (await response.json()) as A; | |
| 131 | − }, | |
| 132 | − catch: (cause) => | |
| 133 | − cause instanceof GitHubSourceError | |
| 134 | − ? cause | |
| 135 | − : new GitHubSourceError({ | |
| 136 | − message: `GitHub request failed: GET ${path}`, | |
| 137 | − path, | |
| 138 | − cause, | |
| 139 | − }), | |
| 121 | + }, | |
| 122 | + catch: (cause) => | |
| 123 | + new GitHubSourceError({ | |
| 124 | + message: `GitHub request failed: GET ${path}`, | |
| 125 | + path, | |
| 126 | + cause, | |
| 127 | + }), | |
| 128 | + }); | |
| 129 | + if (!response.ok) { | |
| 130 | + return yield* new GitHubSourceError({ | |
| 131 | + message: `GitHub request failed: GET ${path} -> ${response.status}`, | |
| 132 | + status: response.status, | |
| 133 | + path, | |
| 134 | + }); | |
| 135 | + } | |
| 136 | + return yield* Effect.tryPromise({ | |
| 137 | + try: () => response.json() as Promise<A>, | |
| 138 | + catch: (cause) => | |
| 139 | + new GitHubSourceError({ | |
| 140 | + message: `GitHub response was not valid JSON: GET ${path}`, | |
| 141 | + path, | |
| 142 | + cause, | |
| 143 | + }), | |
| 144 | + }); | |
| 140 | 145 | }); |
| 141 | 146 | |
| 142 | 147 | interface RepoResponse { |
| @@ -254,42 +259,39 @@ const classifyTreeEntry = ( · 111 unmodified lines | ||
| 254 | 259 | }; |
| 255 | 260 | |
| 256 | 261 | const decodeBlob = (path: string, blob: BlobResponse): Effect.Effect<string, GitHubSourceError> => |
| 257 | − Effect.try({ | |
| 258 | − try: () => { | |
| 259 | − const encoding = asString(blob.encoding); | |
| 260 | − const content = asString(blob.content); | |
| 261 | − if (!content || encoding !== "base64") { | |
| 262 | − throw new GitHubSourceError({ | |
| 263 | − message: `GitHub blob ${path} did not return base64 content`, | |
| 264 | − path, | |
| 265 | − }); | |
| 266 | − } | |
| 267 | − return Buffer.from(content.replace(/\s/g, ""), "base64").toString("utf8"); | |
| 268 | − }, | |
| 269 | − catch: (cause) => | |
| 270 | − cause instanceof GitHubSourceError | |
| 271 | − ? cause | |
| 272 | − : new GitHubSourceError({ message: `Failed to decode GitHub blob ${path}`, path, cause }), | |
| 262 | + Effect.gen(function* () { | |
| 263 | + const encoding = asString(blob.encoding); | |
| 264 | + const content = asString(blob.content); | |
| 265 | + if (!content || encoding !== "base64") { | |
| 266 | + return yield* new GitHubSourceError({ | |
| 267 | + message: `GitHub blob ${path} did not return base64 content`, | |
| 268 | + path, | |
| 269 | + }); | |
| 270 | + } | |
| 271 | + return Buffer.from(content.replace(/\s/g, ""), "base64").toString("utf8"); | |
| 273 | 272 | }); |
| 274 | 273 | |
| 274 | +const decodeExecutorJson = Schema.decodeUnknownEffect(Schema.UnknownFromJsonString); | |
| 275 | + | |
| 275 | 276 | const executorDescription = ( |
| 276 | 277 | files: FileSet, |
| 277 | 278 | ): Effect.Effect<string | undefined, GitHubSourceError> => |
| 278 | − Effect.try({ | |
| 279 | − try: () => { | |
| 280 | − const raw = files.get("executor.json"); | |
| 281 | − if (!raw) return undefined; | |
| 282 | − const parsed = JSON.parse(raw) as unknown; | |
| 283 | − if (!isRecord(parsed)) return undefined; | |
| 284 | − const description = parsed.description; | |
| 285 | − return typeof description === "string" ? description : undefined; | |
| 286 | − }, | |
| 287 | − catch: (cause) => | |
| 288 | − new GitHubSourceError({ | |
| 289 | − message: "executor.json is not valid JSON", | |
| 290 | − path: "executor.json", | |
| 291 | − cause, | |
| 292 | − }), | |
| 279 | + Effect.gen(function* () { | |
| 280 | + const raw = files.get("executor.json"); | |
| 281 | + if (!raw) return undefined; | |
| 282 | + const parsed = yield* decodeExecutorJson(raw).pipe( | |
| 283 | + Effect.mapError( | |
| 284 | + (cause) => | |
| 285 | + new GitHubSourceError({ | |
| 286 | + message: "executor.json is not valid JSON", | |
| 287 | + path: "executor.json", | |
| 288 | + cause, | |
| 289 | + }), | |
| 290 | + ), | |
| 291 | + ); | |
| 292 | + if (!isRecord(parsed)) return undefined; | |
| 293 | + const description = parsed.description; | |
| 294 | + return typeof description === "string" ? description : undefined; | |
| 293 | 295 | }); |
| 294 | 296 | |
| 295 | 297 | export const fetchGitHubSource = ( |
| @@ -336,7 +338,7 @@ export const fetchGitHubSource = ( · 40 unmodified lines | ||
| 336 | 338 | else skipped.push(classified.skipped); |
| 337 | 339 | } |
| 338 | 340 | const treeLimitError = checkTreeLimits(entries); |
| 339 | − if (treeLimitError) return yield* Effect.fail(treeLimitError); | |
| 341 | + if (treeLimitError) return yield* treeLimitError; | |
| 340 | 342 | |
| 341 | 343 | const files = new Map<string, string>(); |
| 342 | 344 | for (const entry of entries) { |
| @@ -348,7 +350,7 @@ export const fetchGitHubSource = ( · 5 unmodified lines | ||
| 348 | 350 | files.set(path, yield* decodeBlob(path, blob)); |
| 349 | 351 | } |
| 350 | 352 | const payloadLimitError = enforcePublishLimits(files); |
| 351 | − if (payloadLimitError) return yield* Effect.fail(payloadLimitError); | |
| 353 | + if (payloadLimitError) return yield* payloadLimitError; | |
| 352 | 354 | |
| 353 | 355 | return { |
| 354 | 356 | files, |
| @@ -360,17 +362,22 @@ export const fetchGitHubSource = ( · 5 unmodified lines | ||
| 360 | 362 | }; |
| 361 | 363 | }); |
| 362 | 364 | |
| 363 | −const publishErrorToSyncError = (error: PublishError): SyncErrorData => ({ | |
| 364 | − stage: error.stage, | |
| 365 | − message: error.message, | |
| 366 | − diagnostics: error.diagnostics, | |
| 367 | −}); | |
| 365 | +const publishErrorToSyncError = (publishFailure: PublishError): SyncErrorData => { | |
| 366 | + const stage = publishFailure.stage; | |
| 367 | + const message = publishFailure.message; | |
| 368 | + const diagnostics = publishFailure.diagnostics; | |
| 369 | + return { stage, message, diagnostics }; | |
| 370 | +}; | |
| 368 | 371 | |
| 369 | −const sourceErrorToSyncError = (error: GitHubSourceError): SyncErrorData => ({ | |
| 370 | − stage: "source", | |
| 371 | − message: error.message, | |
| 372 | − diagnostics: error.path ? [{ path: error.path, message: error.message }] : [], | |
| 373 | −}); | |
| 372 | +const sourceErrorToSyncError = (sourceFailure: GitHubSourceError): SyncErrorData => { | |
| 373 | + const message = sourceFailure.message; | |
| 374 | + const path = sourceFailure.path; | |
| 375 | + return { | |
| 376 | + stage: "source", | |
| 377 | + message, | |
| 378 | + diagnostics: path ? [{ path, message }] : [], | |
| 379 | + }; | |
| 380 | +}; | |
| 374 | 381 | |
| 375 | 382 | const sourceRef = (snapshot: GitHubSourceSnapshot, connection?: string): AppSourceRef => ({ |
| 376 | 383 | kind: "github", |
| @@ -391,7 +398,7 @@ export const syncGitHubSource = (input: SyncGitHubSourceInput): Effect.Effect<Gi · 14 unmodified lines | ||
| 391 | 398 | tools: [], |
| 392 | 399 | skipped: [], |
| 393 | 400 | errors: [ |
| 394 | − error instanceof PublishError | |
| 401 | + Predicate.isTagged("PublishError")(error) | |
| 395 | 402 | ? publishErrorToSyncError(error) |
| 396 | 403 | : sourceErrorToSyncError(error), |
| 397 | 404 | ], |