( input: InsertPluginInput, options: InsertPluginOptions, )
| 65 | } |
| 66 | |
| 67 | export async function insertPlugin( |
| 68 | input: InsertPluginInput, |
| 69 | options: InsertPluginOptions, |
| 70 | ): Promise<{ id: string; slug: string }> { |
| 71 | const supabase = await createClient(); |
| 72 | |
| 73 | const skipScan = options.skipScan === true; |
| 74 | |
| 75 | const { data: plugin, error: pluginError } = await supabase |
| 76 | .from("plugins") |
| 77 | .insert({ |
| 78 | name: input.name, |
| 79 | description: input.description, |
| 80 | logo: input.logo || null, |
| 81 | repository: input.repository || null, |
| 82 | homepage: input.homepage || null, |
| 83 | license: input.license || null, |
| 84 | keywords: input.keywords || [], |
| 85 | author_name: input.author_name || null, |
| 86 | author_url: input.author_url || null, |
| 87 | author_avatar: input.author_avatar || null, |
| 88 | owner_id: options.ownerId, |
| 89 | active: skipScan, |
| 90 | plan: "standard", |
| 91 | scan_status: skipScan ? "unscanned" : "pending", |
| 92 | discovery_source: options.source, |
| 93 | github_repo_id: options.githubRepoId ?? null, |
| 94 | }) |
| 95 | .select("id, slug") |
| 96 | .single(); |
| 97 | |
| 98 | if (pluginError) { |
| 99 | if (pluginError.code === "23505") { |
| 100 | // Distinguish repo-id collision (idempotent re-run) from name collision |
| 101 | // so callers can decide whether it's an error or expected. |
| 102 | const detail = pluginError.message?.toLowerCase() ?? ""; |
| 103 | if (detail.includes("github_repo_id")) { |
| 104 | throw new InsertPluginError( |
| 105 | "A plugin with this GitHub repository already exists.", |
| 106 | "duplicate_repo", |
| 107 | ); |
| 108 | } |
| 109 | throw new InsertPluginError( |
| 110 | "A plugin with this name already exists.", |
| 111 | "duplicate_name", |
| 112 | ); |
| 113 | } |
| 114 | throw new InsertPluginError( |
| 115 | `Failed to create plugin: ${pluginError.message}`, |
| 116 | "insert_failed", |
| 117 | ); |
| 118 | } |
| 119 | |
| 120 | const componentRows = input.components.map((comp, i) => ({ |
| 121 | plugin_id: plugin.id, |
| 122 | type: comp.type, |
| 123 | name: comp.name, |
| 124 | slug: resolveComponentSlug(comp), |
no test coverage detected