Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 49 additions & 32 deletions ee/apps/den-api/src/routes/org/plugin-system/github-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,26 @@ export type GithubConnectorAppConfig = {
type GithubFetch = typeof fetch

export type GithubManifestKind = "marketplace" | "plugin" | null
export type GithubManifestStandard = "claude" | "openai" | null

type GithubRepositorySummary = {
defaultBranch: string | null
fullName: string
hasPluginManifest?: boolean
id: number
manifestKind?: GithubManifestKind
manifestStandard?: GithubManifestStandard
marketplacePluginCount?: number | null
private: boolean
}

const SUPPORTED_GITHUB_REPOSITORY_MANIFESTS = [
{ kind: "marketplace", path: ".claude-plugin/marketplace.json", standard: "claude" },
{ kind: "marketplace", path: ".agents/plugins/marketplace.json", standard: "openai" },
{ kind: "plugin", path: ".claude-plugin/plugin.json", standard: "claude" },
{ kind: "plugin", path: ".codex-plugin/plugin.json", standard: "openai" },
] as const

export type GithubRepositoryTreeEntry = {
id: string
kind: "blob" | "tree"
Expand Down Expand Up @@ -387,6 +396,7 @@ export async function listGithubInstallationRepositories(input: { config: Github
...normalized,
hasPluginManifest: manifest.manifestKind !== null,
manifestKind: manifest.manifestKind,
manifestStandard: manifest.manifestStandard,
marketplacePluginCount: manifest.marketplacePluginCount,
})
}
Expand All @@ -396,50 +406,57 @@ export async function listGithubInstallationRepositories(input: { config: Github

async function detectRepositoryManifest(input: { fetchFn?: GithubFetch; ownerAndRepo: string; token: string }): Promise<{
manifestKind: GithubManifestKind
manifestStandard: GithubManifestStandard
marketplacePluginCount: number | null
}> {
const parts = splitRepositoryFullName(input.ownerAndRepo)
if (!parts) {
return { manifestKind: null, marketplacePluginCount: null }
return { manifestKind: null, manifestStandard: null, marketplacePluginCount: null }
}

const marketplaceResponse = await requestGithubJson<{ content?: string; encoding?: string }>({
allowStatuses: [404],
fetchFn: input.fetchFn,
headers: {
Authorization: `Bearer ${input.token}`,
},
path: `/repos/${encodeURIComponent(parts.owner)}/${encodeURIComponent(parts.repo)}/contents/.claude-plugin/marketplace.json`,
})
for (const manifest of SUPPORTED_GITHUB_REPOSITORY_MANIFESTS) {
const response = await requestGithubJson<{ content?: string; encoding?: string }>({
allowStatuses: [404],
fetchFn: input.fetchFn,
headers: {
Authorization: `Bearer ${input.token}`,
},
path: `/repos/${encodeURIComponent(parts.owner)}/${encodeURIComponent(parts.repo)}/contents/${manifest.path.split("/").map(encodeURIComponent).join("/")}`,
})

if (marketplaceResponse.ok && typeof marketplaceResponse.body?.content === "string" && marketplaceResponse.body.encoding === "base64") {
let marketplacePluginCount: number | null = null
try {
const decoded = Buffer.from(marketplaceResponse.body.content.replace(/\n/g, ""), "base64").toString("utf8")
const parsed = JSON.parse(decoded) as unknown
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && Array.isArray((parsed as Record<string, unknown>).plugins)) {
marketplacePluginCount = ((parsed as Record<string, unknown>).plugins as unknown[]).length
}
} catch {
marketplacePluginCount = null
if (!response.ok) {
continue
}
return { manifestKind: "marketplace", marketplacePluginCount }
}

const pluginResponse = await requestGithubJson<unknown>({
allowStatuses: [404],
fetchFn: input.fetchFn,
headers: {
Authorization: `Bearer ${input.token}`,
},
path: `/repos/${encodeURIComponent(parts.owner)}/${encodeURIComponent(parts.repo)}/contents/.claude-plugin/plugin.json`,
})
if (manifest.kind === "marketplace") {
let marketplacePluginCount: number | null = null
if (typeof response.body?.content === "string" && response.body.encoding === "base64") {
try {
const decoded = Buffer.from(response.body.content.replace(/\n/g, ""), "base64").toString("utf8")
const parsed = JSON.parse(decoded) as unknown
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && Array.isArray((parsed as Record<string, unknown>).plugins)) {
marketplacePluginCount = ((parsed as Record<string, unknown>).plugins as unknown[]).length
}
} catch {
marketplacePluginCount = null
}
}

if (pluginResponse.ok) {
return { manifestKind: "plugin", marketplacePluginCount: null }
return {
manifestKind: manifest.kind,
manifestStandard: manifest.standard,
marketplacePluginCount,
}
}

return {
manifestKind: manifest.kind,
manifestStandard: manifest.standard,
marketplacePluginCount: null,
}
}

return { manifestKind: null, marketplacePluginCount: null }
return { manifestKind: null, manifestStandard: null, marketplacePluginCount: null }
}

function splitRepositoryFullName(repositoryFullName: string) {
Expand Down
Loading
Loading