(entry: StackEntry)
| 121 | url.trim().replace(/\/+$/, "").replace(/^http:\/\//, "https://").toLowerCase(); |
| 122 | |
| 123 | async function syncEntry(entry: StackEntry): Promise<Result> { |
| 124 | const { data } = entry; |
| 125 | const changes: Change[] = []; |
| 126 | const notes: string[] = []; |
| 127 | const opts = { noCache: NO_CACHE }; |
| 128 | |
| 129 | const ref = parseRepository(data.repository); |
| 130 | let meta: RepoMeta | null = null; |
| 131 | |
| 132 | if (ref) { |
| 133 | try { |
| 134 | meta = await fetchRepoMeta(ref, opts); |
| 135 | if (!meta) notes.push(`repository not found upstream: ${data.repository}`); |
| 136 | } catch (e) { |
| 137 | notes.push(`repository lookup failed: ${(e as Error).message}`); |
| 138 | } |
| 139 | } else if (data.repository) { |
| 140 | notes.push(`unsupported repository host, skipped: ${data.repository}`); |
| 141 | } |
| 142 | |
| 143 | const suggestions: Change[] = []; |
| 144 | |
| 145 | /** |
| 146 | * Route a divergence: applied outright, or held for review when the API is |
| 147 | * describing the repository rather than the software. |
| 148 | */ |
| 149 | const propose = (field: string, from: unknown, to: string | number) => { |
| 150 | const change = { field, from, to }; |
| 151 | if (AUTO_APPLIED.has(field) || from == null || from === "") { |
| 152 | changes.push(change); |
| 153 | return; |
| 154 | } |
| 155 | if (ACCEPTED.has(`${entry.slug}:${field}`)) { |
| 156 | changes.push(change); |
| 157 | return; |
| 158 | } |
| 159 | if (isRejected(OVERRIDES, entry.slug, field, String(to))) return; |
| 160 | suggestions.push(change); |
| 161 | }; |
| 162 | |
| 163 | if (meta) { |
| 164 | // repository — upstream resolves renames/transfers, so a differing |
| 165 | // html_url means the project moved and our link is stale. |
| 166 | if (canonical(meta.url) !== canonical(data.repository ?? "")) { |
| 167 | propose("repository", data.repository, meta.url); |
| 168 | } |
| 169 | |
| 170 | // stars |
| 171 | const stars = roundStars(meta.stars); |
| 172 | if (stars !== data.stars) propose("stars", data.stars ?? null, stars); |
| 173 | |
| 174 | // homepage — only when upstream declares one. An empty upstream field is |
| 175 | // an absence of information, never a reason to drop a curated URL. A |
| 176 | // homepage that merely repeats the repository is not information either: |
| 177 | // `repository` already says that, and it would push a real product page |
| 178 | // out of the catalogue. |
| 179 | if ( |
| 180 | meta.homepage && |
no test coverage detected