/* oxlint-disable executor/no-try-catch-or-throw -- boundary: plugin source config validation is converted into the extension Effect failure channel */import { Data, Effect, Predicate, Redacted, Result } from "effect";import { AuthTemplateSlug, ConnectionName, IntegrationSlug, ProviderItemId, ProviderKey, ToolName, ToolResult, definePlugin, IntegrationAlreadyExistsError, type PluginCtx, type ToolDef,} from "@executor-js/sdk"; import { makeInProcessAppToolExecutor, type AppToolExecutor } from "../executor/app-tool-executor";import type { BundleBackend } from "../pipeline/bundle";import { PUBLISH_LIMITS, publish } from "../pipeline/publish";import { buildBridge, resolveIntegrationBindings } from "./bindings";import { makePluginCtxAppsResolver } from "./resolver";import { descriptorCollection, makeAppsStore, sourceCollection, toolCollection, type AppSourceConfig, type AppSourceRecord, type AppsStore,} from "./store";import { publishErrorToDiagnostic, sourceErrorToDiagnostic, type SyncDiagnostic,} from "../source/app-source";import { checkGitAppSourceRefs, fetchGitAppSource, parseGitSourceUrl } from "../source/git-source";import { fetchLocalDirectoryAppSource, listLocalDirectoryDirs,} from "../source/local-directory-source";import { AppSourceError, type AppSourceSnapshot } from "../source/app-source";import type { PublishError } from "../pipeline/publish";import { DRIVER_VERSION } from "../executor/dynamic-worker-app-tool-executor"; const APPS_CONNECTION = ConnectionName.make("published");const APPS_NO_AUTH = AuthTemplateSlug.make("none"); class AppPluginError extends Data.TaggedError("AppPluginError")<{ readonly message: string;}> {} class AppSlugConflictError extends Data.TaggedError("AppSlugConflictError")<{ readonly app: string;}> {} interface ProjectedToolSchema { readonly inputSchema?: unknown; readonly outputSchema?: unknown;} const innerToolError = ( cause: unknown,): { readonly address: string; readonly innerMessage: string; readonly code?: string } | null => { const direct = Predicate.isTagged("AppInnerToolError")(cause) && typeof cause === "object" && cause !== null ? (cause as { readonly address?: unknown; readonly innerMessage?: unknown; readonly code?: unknown; }) : null; const nested = direct === null && cause !== null && typeof cause === "object" && "cause" in cause && Predicate.isTagged("AppInnerToolError")(cause.cause) ? (cause.cause as { readonly address?: unknown; readonly innerMessage?: unknown; readonly code?: unknown; }) : direct; if ( nested === null || typeof nested.address !== "string" || typeof nested.innerMessage !== "string" ) { return null; } return { address: nested.address, innerMessage: nested.innerMessage, ...(typeof nested.code === "string" ? { code: nested.code } : {}), };}; const slugify = (value: string): string => value .trim() .toLowerCase() .replace(/[^a-z0-9-]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 80); export interface CreateAppSourceInput { readonly slug?: string; readonly app?: string; readonly kind: AppSourceConfig["kind"]; readonly url?: string; readonly ref?: string; readonly token?: string; readonly path?: string;} export type SyncAppSourceResult = | { readonly status: "published"; readonly sourceRef: string; readonly tools: readonly string[]; readonly errors?: undefined; } | { readonly status: "up-to-date"; readonly sourceRef: string; readonly tools: readonly string[]; readonly errors?: undefined; } | { readonly status: "failed"; readonly sourceRef?: string; readonly tools: readonly string[]; readonly errors: readonly SyncDiagnostic[]; }; const sourceConfig = (input: CreateAppSourceInput): AppSourceConfig => { if (input.kind === "git") { if (!input.url) throw new AppPluginError({ message: "git source url is required" }); return { kind: "git", url: input.url, ...(input.ref ? { ref: input.ref } : {}), }; } if (!input.path) throw new AppPluginError({ message: "local-directory source path is required" }); return { kind: "local-directory", path: input.path };}; const appDescription = (record: AppSourceRecord, description?: string): string => { if (description) return description; if (record.description) return record.description; if (record.config.kind === "git") return `Custom tools from ${record.config.url}`; return `Custom tools from ${record.config.path}`;}; const ensureAppIntegration = ( ctx: PluginCtx<AppsStore>, record: AppSourceRecord, description?: string,): Effect.Effect<void, unknown> => Effect.gen(function* () { const appSlug = IntegrationSlug.make(record.app); const existing = yield* ctx.core.integrations.get(appSlug); if (existing && existing.kind !== "apps") return; yield* ctx.core.integrations.register({ slug: appSlug, name: record.app, description: appDescription(record, description), config: {}, }); }); const ensureAppConnection = ( ctx: PluginCtx<AppsStore>, record: AppSourceRecord, description?: string,): Effect.Effect<void, unknown> => Effect.gen(function* () { const appSlug = IntegrationSlug.make(record.app); yield* ensureAppIntegration(ctx, record, description); const existing = yield* ctx.connections.get({ owner: "org", integration: appSlug, name: APPS_CONNECTION, }); if (existing) { yield* ctx.connections.refresh({ owner: "org", integration: appSlug, name: APPS_CONNECTION, }); return; } yield* ctx.connections.create({ owner: "org", integration: appSlug, name: APPS_CONNECTION, template: APPS_NO_AUTH, values: {}, }); }); const guardAppIntegration = ( ctx: PluginCtx<AppsStore>, record: AppSourceRecord,): Effect.Effect<void, unknown> => Effect.gen(function* () { const appSlug = IntegrationSlug.make(record.app); const existing = yield* ctx.core.integrations.get(appSlug); if (existing && existing.kind !== "apps") { return yield* new IntegrationAlreadyExistsError({ slug: appSlug }); } const sources = yield* ctx.storage.listSources(); const duplicate = sources.find( (source) => source.slug !== record.slug && source.app === record.app, ); if (duplicate) { return yield* new AppSlugConflictError({ app: record.app }); } }); const appRegistrationErrorToDiagnostic = (error: unknown): SyncDiagnostic => { if (Predicate.isTagged("IntegrationAlreadyExistsError")(error)) { const slug = String((error as IntegrationAlreadyExistsError).slug); return { stage: "project", message: `Integration slug collides with an existing integration: ${slug}`, diagnostics: [{ path: slug, message: `Integration slug already exists: ${slug}` }], }; } if (Predicate.isTagged("AppSlugConflictError")(error)) { const app = (error as AppSlugConflictError).app; const message = `app source already exists for slug: ${app}`; return { stage: "project", message, diagnostics: [{ path: app, message }], }; } return { stage: "project", message: "App publish failed", };}; const tokenItemId = (slug: string): ProviderItemId => ProviderItemId.make(`apps/source-tokens/${slug}`); // A source authenticates only with its own explicitly provided token. No// implicit credential sharing: a stored GitHub connection must never leak// into git fetches the user did not tie to it.// Unwrapped here rather than at the fetch sites because the git source API// takes a bare bearer token; keeping it wrapped any longer would only move the// unwrap, not remove it.const gitTokenFor = ( ctx: PluginCtx<AppsStore>, config: Extract<AppSourceConfig, { readonly kind: "git" }>,): Effect.Effect<string | null, unknown> => Effect.gen(function* () { if (config.tokenProvider && config.tokenItemId) { const token = yield* ctx.providers.get( ProviderKey.make(config.tokenProvider), ProviderItemId.make(config.tokenItemId), ); // oxlint-disable-next-line executor/no-redacted-unwrap -- boundary: the git source API takes a bare bearer token return token === null ? null : Redacted.value(token); } return null; }); const activeToolNamesFor = ( ctx: PluginCtx<AppsStore>, app: string,): Effect.Effect<readonly string[], unknown> => ctx.storage .listActiveTools() .pipe(Effect.map((tools) => tools.filter((tool) => tool.app === app).map((tool) => tool.name))); const makeAppsExtension = ( ctx: PluginCtx<AppsStore>, options?: Pick< AppsPluginOptions, "executor" | "bundler" | "sourceKinds" | "allowPrivateGitHosts" >,) => { const executor = options?.executor; const activeExecutor = executor ?? makeInProcessAppToolExecutor(); const activeBundler = options?.bundler; const sourceKinds = options?.sourceKinds ?? ["git", "local-directory"]; const now = () => Date.now(); return { publish: (input: Parameters<typeof publish>[1]) => publish({ store: ctx.storage, executor: activeExecutor, bundler: activeBundler }, input), listSources: () => ctx.storage.listSources(), getSource: (slug: string) => ctx.storage.getSource(slug), listDirs: (input: { readonly path?: string; readonly includeHidden?: boolean }) => Effect.gen(function* () { if (!sourceKinds.includes("local-directory")) { return yield* new AppPluginError({ message: "app source kind is not enabled: local-directory", }); } return yield* listLocalDirectoryDirs(input); }), createSource: (input: CreateAppSourceInput) => Effect.gen(function* () { const config = sourceConfig(input); if (!sourceKinds.includes(config.kind)) { return yield* new AppPluginError({ message: `app source kind is not enabled: ${config.kind}`, }); } if (config.kind === "git") { yield* parseGitSourceUrl(config.url, { allowPrivateHosts: options?.allowPrivateGitHosts === true, }).pipe( Effect.mapError( () => new AppPluginError({ message: "git source URL is not valid", }), ), ); } const slug = slugify( input.slug ?? input.app ?? (config.kind === "git" ? config.url : config.path), ); if (!slug) return yield* new AppPluginError({ message: "source slug is required" }); const app = slugify(input.app ?? slug); const storedConfig = config.kind === "git" && input.token ? { ...config, tokenProvider: String( yield* ctx.providers.setDefault(tokenItemId(slug), input.token), ), tokenItemId: String(tokenItemId(slug)), } : config; const record: AppSourceRecord = { slug, app, kind: storedConfig.kind, config: storedConfig, status: { type: "pending" }, updatedAt: now(), }; yield* ctx.storage.putSource(record, "org"); yield* ensureAppIntegration(ctx, record); return record; }), deleteSource: (slug: string) => Effect.gen(function* () { const record = yield* ctx.storage.getSource(slug); if (record) { yield* ctx.storage.removePublished(record.app, "org"); yield* ctx.core.integrations .remove(IntegrationSlug.make(record.app)) .pipe(Effect.catchTag("IntegrationRemovalNotAllowedError", () => Effect.void)); } if ( record?.config.kind === "git" && record.config.tokenProvider && record.config.tokenItemId ) { yield* ctx.providers.remove( ProviderKey.make(record.config.tokenProvider), ProviderItemId.make(record.config.tokenItemId), ); } yield* ctx.storage.removeSource(slug, "org"); return { removed: true }; }), syncSource: (slug: string): Effect.Effect<SyncAppSourceResult, unknown> => Effect.gen(function* () { const record = yield* ctx.storage.getSource(slug); if (!record) return yield* new AppPluginError({ message: `app source not found: ${slug}` }); if (!sourceKinds.includes(record.config.kind)) { return yield* new AppPluginError({ message: `app source kind is not enabled: ${record.config.kind}`, }); } const gitToken = record.config.kind === "git" ? yield* gitTokenFor(ctx, record.config) : null; if (record.config.kind === "git") { const checked = yield* checkGitAppSourceRefs({ url: record.config.url, ...(record.config.ref ? { ref: record.config.ref } : {}), ...(gitToken ? { token: gitToken } : {}), allowPrivateHosts: options?.allowPrivateGitHosts === true, }).pipe(Effect.result); if (Result.isFailure(checked)) { const diagnostic = sourceErrorToDiagnostic(checked.failure); const failed: AppSourceRecord = { ...record, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }; yield* ctx.storage.putSource(failed, "org"); return { status: "failed", tools: [], errors: [diagnostic] }; } if (record.sourceRef === checked.success.sourceRef) { const guarded = yield* guardAppIntegration(ctx, record).pipe(Effect.result); if (Result.isFailure(guarded)) { const diagnostic = appRegistrationErrorToDiagnostic(guarded.failure); const failed: AppSourceRecord = { ...record, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }; yield* ctx.storage.putSource(failed, "org"); return { status: "failed", tools: [], errors: [diagnostic] }; } const tools = yield* activeToolNamesFor(ctx, record.app); const updated: AppSourceRecord = { ...record, status: { type: "up-to-date", at: now(), tools }, updatedAt: now(), }; yield* ctx.storage.putSource(updated, "org"); yield* ensureAppConnection(ctx, updated); return { status: "up-to-date", sourceRef: checked.success.sourceRef, tools }; } } let fetched: Result.Result<AppSourceSnapshot, AppSourceError | PublishError>; if (record.config.kind === "git") { fetched = yield* fetchGitAppSource({ url: record.config.url, ...(record.config.ref ? { ref: record.config.ref } : {}), ...(gitToken ? { token: gitToken } : {}), maxBytes: PUBLISH_LIMITS.maxTotalBytes, allowPrivateHosts: options?.allowPrivateGitHosts === true, }).pipe(Effect.result); } else { fetched = yield* fetchLocalDirectoryAppSource(record.config).pipe(Effect.result); } if (Result.isFailure(fetched)) { const error = fetched.failure; const diagnostic = Predicate.isTagged("PublishError")(error) ? publishErrorToDiagnostic(error as PublishError) : sourceErrorToDiagnostic(error as AppSourceError); const failed: AppSourceRecord = { ...record, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }; yield* ctx.storage.putSource(failed, "org"); return { status: "failed", tools: [], errors: [diagnostic] }; } const snapshot: AppSourceSnapshot = fetched.success; if (record.sourceRef === snapshot.sourceRef) { const guarded = yield* guardAppIntegration(ctx, record).pipe(Effect.result); if (Result.isFailure(guarded)) { const diagnostic = appRegistrationErrorToDiagnostic(guarded.failure); const failed: AppSourceRecord = { ...record, description: snapshot.description, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }; yield* ctx.storage.putSource(failed, "org"); return { status: "failed", tools: [], errors: [diagnostic] }; } const tools = yield* activeToolNamesFor(ctx, record.app); const updated: AppSourceRecord = { ...record, description: snapshot.description, status: { type: "up-to-date", at: now(), tools }, updatedAt: now(), }; yield* ctx.storage.putSource(updated, "org"); yield* ensureAppConnection(ctx, updated, snapshot.description); return { status: "up-to-date", sourceRef: snapshot.sourceRef, tools }; } const guarded = yield* guardAppIntegration(ctx, record).pipe(Effect.result); if (Result.isFailure(guarded)) { const diagnostic = appRegistrationErrorToDiagnostic(guarded.failure); yield* ctx.storage.putSource( { ...record, sourceRef: snapshot.sourceRef, description: snapshot.description, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }, "org", ); return { status: "failed", sourceRef: snapshot.sourceRef, tools: [], errors: [diagnostic], }; } const published = yield* publish( { store: ctx.storage, executor: activeExecutor, bundler: activeBundler }, { app: record.app, files: snapshot.files, sourceRef: snapshot.sourceRef,Lines 1–500 of 714. Load more
/* oxlint-disable executor/no-try-catch-or-throw -- boundary: plugin source config validation is converted into the extension Effect failure channel */ import { Data, Effect, Predicate, Redacted, Result } from "effect"; import { AuthTemplateSlug, ConnectionName, IntegrationSlug, ProviderItemId, ProviderKey, ToolName, ToolResult, definePlugin, IntegrationAlreadyExistsError, type PluginCtx, type ToolDef, } from "@executor-js/sdk"; import { makeInProcessAppToolExecutor, type AppToolExecutor } from "../executor/app-tool-executor"; import type { BundleBackend } from "../pipeline/bundle"; import { PUBLISH_LIMITS, publish } from "../pipeline/publish"; import { buildBridge, resolveIntegrationBindings } from "./bindings"; import { makePluginCtxAppsResolver } from "./resolver"; import { descriptorCollection, makeAppsStore, sourceCollection, toolCollection, type AppSourceConfig, type AppSourceRecord, type AppsStore, } from "./store"; import { publishErrorToDiagnostic, sourceErrorToDiagnostic, type SyncDiagnostic, } from "../source/app-source"; import { checkGitAppSourceRefs, fetchGitAppSource, parseGitSourceUrl } from "../source/git-source"; import { fetchLocalDirectoryAppSource, listLocalDirectoryDirs, } from "../source/local-directory-source"; import { AppSourceError, type AppSourceSnapshot } from "../source/app-source"; import type { PublishError } from "../pipeline/publish"; import { DRIVER_VERSION } from "../executor/dynamic-worker-app-tool-executor"; const APPS_CONNECTION = ConnectionName.make("published"); const APPS_NO_AUTH = AuthTemplateSlug.make("none"); class AppPluginError extends Data.TaggedError("AppPluginError")<{ readonly message: string; }> {} class AppSlugConflictError extends Data.TaggedError("AppSlugConflictError")<{ readonly app: string; }> {} interface ProjectedToolSchema { readonly inputSchema?: unknown; readonly outputSchema?: unknown; } const innerToolError = ( cause: unknown, ): { readonly address: string; readonly innerMessage: string; readonly code?: string } | null => { const direct = Predicate.isTagged("AppInnerToolError")(cause) && typeof cause === "object" && cause !== null ? (cause as { readonly address?: unknown; readonly innerMessage?: unknown; readonly code?: unknown; }) : null; const nested = direct === null && cause !== null && typeof cause === "object" && "cause" in cause && Predicate.isTagged("AppInnerToolError")(cause.cause) ? (cause.cause as { readonly address?: unknown; readonly innerMessage?: unknown; readonly code?: unknown; }) : direct; if ( nested === null || typeof nested.address !== "string" || typeof nested.innerMessage !== "string" ) { return null; } return { address: nested.address, innerMessage: nested.innerMessage, ...(typeof nested.code === "string" ? { code: nested.code } : {}), }; }; const slugify = (value: string): string => value .trim() .toLowerCase() .replace(/[^a-z0-9-]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 80); export interface CreateAppSourceInput { readonly slug?: string; readonly app?: string; readonly kind: AppSourceConfig["kind"]; readonly url?: string; readonly ref?: string; readonly token?: string; readonly path?: string; } export type SyncAppSourceResult = | { readonly status: "published"; readonly sourceRef: string; readonly tools: readonly string[]; readonly errors?: undefined; } | { readonly status: "up-to-date"; readonly sourceRef: string; readonly tools: readonly string[]; readonly errors?: undefined; } | { readonly status: "failed"; readonly sourceRef?: string; readonly tools: readonly string[]; readonly errors: readonly SyncDiagnostic[]; }; const sourceConfig = (input: CreateAppSourceInput): AppSourceConfig => { if (input.kind === "git") { if (!input.url) throw new AppPluginError({ message: "git source url is required" }); return { kind: "git", url: input.url, ...(input.ref ? { ref: input.ref } : {}), }; } if (!input.path) throw new AppPluginError({ message: "local-directory source path is required" }); return { kind: "local-directory", path: input.path }; }; const appDescription = (record: AppSourceRecord, description?: string): string => { if (description) return description; if (record.description) return record.description; if (record.config.kind === "git") return `Custom tools from ${record.config.url}`; return `Custom tools from ${record.config.path}`; }; const ensureAppIntegration = ( ctx: PluginCtx<AppsStore>, record: AppSourceRecord, description?: string, ): Effect.Effect<void, unknown> => Effect.gen(function* () { const appSlug = IntegrationSlug.make(record.app); const existing = yield* ctx.core.integrations.get(appSlug); if (existing && existing.kind !== "apps") return; yield* ctx.core.integrations.register({ slug: appSlug, name: record.app, description: appDescription(record, description), config: {}, }); }); const ensureAppConnection = ( ctx: PluginCtx<AppsStore>, record: AppSourceRecord, description?: string, ): Effect.Effect<void, unknown> => Effect.gen(function* () { const appSlug = IntegrationSlug.make(record.app); yield* ensureAppIntegration(ctx, record, description); const existing = yield* ctx.connections.get({ owner: "org", integration: appSlug, name: APPS_CONNECTION, }); if (existing) { yield* ctx.connections.refresh({ owner: "org", integration: appSlug, name: APPS_CONNECTION, }); return; } yield* ctx.connections.create({ owner: "org", integration: appSlug, name: APPS_CONNECTION, template: APPS_NO_AUTH, values: {}, }); }); const guardAppIntegration = ( ctx: PluginCtx<AppsStore>, record: AppSourceRecord, ): Effect.Effect<void, unknown> => Effect.gen(function* () { const appSlug = IntegrationSlug.make(record.app); const existing = yield* ctx.core.integrations.get(appSlug); if (existing && existing.kind !== "apps") { return yield* new IntegrationAlreadyExistsError({ slug: appSlug }); } const sources = yield* ctx.storage.listSources(); const duplicate = sources.find( (source) => source.slug !== record.slug && source.app === record.app, ); if (duplicate) { return yield* new AppSlugConflictError({ app: record.app }); } }); const appRegistrationErrorToDiagnostic = (error: unknown): SyncDiagnostic => { if (Predicate.isTagged("IntegrationAlreadyExistsError")(error)) { const slug = String((error as IntegrationAlreadyExistsError).slug); return { stage: "project", message: `Integration slug collides with an existing integration: ${slug}`, diagnostics: [{ path: slug, message: `Integration slug already exists: ${slug}` }], }; } if (Predicate.isTagged("AppSlugConflictError")(error)) { const app = (error as AppSlugConflictError).app; const message = `app source already exists for slug: ${app}`; return { stage: "project", message, diagnostics: [{ path: app, message }], }; } return { stage: "project", message: "App publish failed", }; }; const tokenItemId = (slug: string): ProviderItemId => ProviderItemId.make(`apps/source-tokens/${slug}`); // A source authenticates only with its own explicitly provided token. No // implicit credential sharing: a stored GitHub connection must never leak // into git fetches the user did not tie to it. // Unwrapped here rather than at the fetch sites because the git source API // takes a bare bearer token; keeping it wrapped any longer would only move the // unwrap, not remove it. const gitTokenFor = ( ctx: PluginCtx<AppsStore>, config: Extract<AppSourceConfig, { readonly kind: "git" }>, ): Effect.Effect<string | null, unknown> => Effect.gen(function* () { if (config.tokenProvider && config.tokenItemId) { const token = yield* ctx.providers.get( ProviderKey.make(config.tokenProvider), ProviderItemId.make(config.tokenItemId), ); // oxlint-disable-next-line executor/no-redacted-unwrap -- boundary: the git source API takes a bare bearer token return token === null ? null : Redacted.value(token); } return null; }); const activeToolNamesFor = ( ctx: PluginCtx<AppsStore>, app: string, ): Effect.Effect<readonly string[], unknown> => ctx.storage .listActiveTools() .pipe(Effect.map((tools) => tools.filter((tool) => tool.app === app).map((tool) => tool.name))); const makeAppsExtension = ( ctx: PluginCtx<AppsStore>, options?: Pick< AppsPluginOptions, "executor" | "bundler" | "sourceKinds" | "allowPrivateGitHosts" >, ) => { const executor = options?.executor; const activeExecutor = executor ?? makeInProcessAppToolExecutor(); const activeBundler = options?.bundler; const sourceKinds = options?.sourceKinds ?? ["git", "local-directory"]; const now = () => Date.now(); return { publish: (input: Parameters<typeof publish>[1]) => publish({ store: ctx.storage, executor: activeExecutor, bundler: activeBundler }, input), listSources: () => ctx.storage.listSources(), getSource: (slug: string) => ctx.storage.getSource(slug), listDirs: (input: { readonly path?: string; readonly includeHidden?: boolean }) => Effect.gen(function* () { if (!sourceKinds.includes("local-directory")) { return yield* new AppPluginError({ message: "app source kind is not enabled: local-directory", }); } return yield* listLocalDirectoryDirs(input); }), createSource: (input: CreateAppSourceInput) => Effect.gen(function* () { const config = sourceConfig(input); if (!sourceKinds.includes(config.kind)) { return yield* new AppPluginError({ message: `app source kind is not enabled: ${config.kind}`, }); } if (config.kind === "git") { yield* parseGitSourceUrl(config.url, { allowPrivateHosts: options?.allowPrivateGitHosts === true, }).pipe( Effect.mapError( () => new AppPluginError({ message: "git source URL is not valid", }), ), ); } const slug = slugify( input.slug ?? input.app ?? (config.kind === "git" ? config.url : config.path), ); if (!slug) return yield* new AppPluginError({ message: "source slug is required" }); const app = slugify(input.app ?? slug); const storedConfig = config.kind === "git" && input.token ? { ...config, tokenProvider: String( yield* ctx.providers.setDefault(tokenItemId(slug), input.token), ), tokenItemId: String(tokenItemId(slug)), } : config; const record: AppSourceRecord = { slug, app, kind: storedConfig.kind, config: storedConfig, status: { type: "pending" }, updatedAt: now(), }; yield* ctx.storage.putSource(record, "org"); yield* ensureAppIntegration(ctx, record); return record; }), deleteSource: (slug: string) => Effect.gen(function* () { const record = yield* ctx.storage.getSource(slug); if (record) { yield* ctx.storage.removePublished(record.app, "org"); yield* ctx.core.integrations .remove(IntegrationSlug.make(record.app)) .pipe(Effect.catchTag("IntegrationRemovalNotAllowedError", () => Effect.void)); } if ( record?.config.kind === "git" && record.config.tokenProvider && record.config.tokenItemId ) { yield* ctx.providers.remove( ProviderKey.make(record.config.tokenProvider), ProviderItemId.make(record.config.tokenItemId), ); } yield* ctx.storage.removeSource(slug, "org"); return { removed: true }; }), syncSource: (slug: string): Effect.Effect<SyncAppSourceResult, unknown> => Effect.gen(function* () { const record = yield* ctx.storage.getSource(slug); if (!record) return yield* new AppPluginError({ message: `app source not found: ${slug}` }); if (!sourceKinds.includes(record.config.kind)) { return yield* new AppPluginError({ message: `app source kind is not enabled: ${record.config.kind}`, }); } const gitToken = record.config.kind === "git" ? yield* gitTokenFor(ctx, record.config) : null; if (record.config.kind === "git") { const checked = yield* checkGitAppSourceRefs({ url: record.config.url, ...(record.config.ref ? { ref: record.config.ref } : {}), ...(gitToken ? { token: gitToken } : {}), allowPrivateHosts: options?.allowPrivateGitHosts === true, }).pipe(Effect.result); if (Result.isFailure(checked)) { const diagnostic = sourceErrorToDiagnostic(checked.failure); const failed: AppSourceRecord = { ...record, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }; yield* ctx.storage.putSource(failed, "org"); return { status: "failed", tools: [], errors: [diagnostic] }; } if (record.sourceRef === checked.success.sourceRef) { const guarded = yield* guardAppIntegration(ctx, record).pipe(Effect.result); if (Result.isFailure(guarded)) { const diagnostic = appRegistrationErrorToDiagnostic(guarded.failure); const failed: AppSourceRecord = { ...record, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }; yield* ctx.storage.putSource(failed, "org"); return { status: "failed", tools: [], errors: [diagnostic] }; } const tools = yield* activeToolNamesFor(ctx, record.app); const updated: AppSourceRecord = { ...record, status: { type: "up-to-date", at: now(), tools }, updatedAt: now(), }; yield* ctx.storage.putSource(updated, "org"); yield* ensureAppConnection(ctx, updated); return { status: "up-to-date", sourceRef: checked.success.sourceRef, tools }; } } let fetched: Result.Result<AppSourceSnapshot, AppSourceError | PublishError>; if (record.config.kind === "git") { fetched = yield* fetchGitAppSource({ url: record.config.url, ...(record.config.ref ? { ref: record.config.ref } : {}), ...(gitToken ? { token: gitToken } : {}), maxBytes: PUBLISH_LIMITS.maxTotalBytes, allowPrivateHosts: options?.allowPrivateGitHosts === true, }).pipe(Effect.result); } else { fetched = yield* fetchLocalDirectoryAppSource(record.config).pipe(Effect.result); } if (Result.isFailure(fetched)) { const error = fetched.failure; const diagnostic = Predicate.isTagged("PublishError")(error) ? publishErrorToDiagnostic(error as PublishError) : sourceErrorToDiagnostic(error as AppSourceError); const failed: AppSourceRecord = { ...record, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }; yield* ctx.storage.putSource(failed, "org"); return { status: "failed", tools: [], errors: [diagnostic] }; } const snapshot: AppSourceSnapshot = fetched.success; if (record.sourceRef === snapshot.sourceRef) { const guarded = yield* guardAppIntegration(ctx, record).pipe(Effect.result); if (Result.isFailure(guarded)) { const diagnostic = appRegistrationErrorToDiagnostic(guarded.failure); const failed: AppSourceRecord = { ...record, description: snapshot.description, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }; yield* ctx.storage.putSource(failed, "org"); return { status: "failed", tools: [], errors: [diagnostic] }; } const tools = yield* activeToolNamesFor(ctx, record.app); const updated: AppSourceRecord = { ...record, description: snapshot.description, status: { type: "up-to-date", at: now(), tools }, updatedAt: now(), }; yield* ctx.storage.putSource(updated, "org"); yield* ensureAppConnection(ctx, updated, snapshot.description); return { status: "up-to-date", sourceRef: snapshot.sourceRef, tools }; } const guarded = yield* guardAppIntegration(ctx, record).pipe(Effect.result); if (Result.isFailure(guarded)) { const diagnostic = appRegistrationErrorToDiagnostic(guarded.failure); yield* ctx.storage.putSource( { ...record, sourceRef: snapshot.sourceRef, description: snapshot.description, status: { type: "failed", at: now(), errors: [diagnostic] }, updatedAt: now(), }, "org", ); return { status: "failed", sourceRef: snapshot.sourceRef, tools: [], errors: [diagnostic], }; } const published = yield* publish( { store: ctx.storage, executor: activeExecutor, bundler: activeBundler }, { app: record.app, files: snapshot.files, sourceRef: snapshot.sourceRef,