Skip to content

fix: re-use canonicalizeRepoUrl & insert package_repos in go and nuget#4331

Merged
mbani01 merged 7 commits into
mainfrom
fix/repos_normalization_and_storing
Jul 13, 2026
Merged

fix: re-use canonicalizeRepoUrl & insert package_repos in go and nuget#4331
mbani01 merged 7 commits into
mainfrom
fix/repos_normalization_and_storing

Conversation

@mbani01

@mbani01 mbani01 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

This pull request adds improved repository handling for both Go and NuGet package enrichment. It introduces fetching and parsing of repository URLs from NuGet .nuspec files, canonicalizes and links repositories to packages in the database, and updates the data models to store normalized repository information. The most important changes are outlined below.

NuGet repository enrichment and normalization:

  • Added a new fetchNuspec function in nuget/client.ts to retrieve raw .nuspec XML files for extracting repository URLs not available in the NuGet registration API.
  • Updated the normalization logic in nuget/normalize.ts to parse and canonicalize repository URLs from .nuspec XML, and to store repository info as a new repo object instead of a plain URL. [1] [2] [3] [4] [5] [6]
  • Modified the NuGet enrichment loop in runNuGetEnrichmentLoop.ts to fetch and use .nuspec data, upsert repository records, and link packages to repositories in the database. [1] [2] [3]

Go package repository linking:

  • Updated Go enrichment (activities.ts) to canonicalize repository URLs, upsert repository records, and link packages to repositories within a transaction. Also, updated the GoRow type and related queries to include package IDs for linking. [1] [2] [3] [4] [5] [6] [7]

General improvements:

  • Refactored code to use canonical repository handling utilities and updated data-access calls to support repository linking for both ecosystems. [1] [2] [3]

These changes ensure that repository information is more reliably and consistently extracted, normalized, and linked for both Go and NuGet packages.


Note

Medium Risk
Touches ingestion transactions and shared URL canonicalization, so bad parsing or linking could mis-associate packages to repos across ecosystems; scope is limited to packages_worker enrichment paths.

Overview
Go and NuGet enrichment now follow the same repository linking pattern as npm: URLs go through shared canonicalizeRepoUrl, getOrCreateRepoByUrl creates/updates repos, and upsertPackageRepo writes package_repos with source declared and confidence 0.8, with audit fields included in the same DB transaction.

Go loads package id and declared_repository_url, prefers the proxy repo URL when present otherwise the declared URL, and runs package updates plus repo linking inside qx.tx.

NuGet adds fetchNuspec (flat-container .nuspec XML) because registration JSON often omits <repository>, parses that URL in parseNuspecRepositoryUrl, and re-normalizes with nuspec-first then catalog/homepage fallbacks. The normalized shape exposes repo: CanonicalRepo | null instead of a bare repositoryUrl string.

canonicalizeRepoUrl now builds paths with a multi-segment owner path on non–known-host URLs (last segment = repo name), not only owner/name on GitHub/GitLab/Bitbucket.

Reviewed by Cursor Bugbot for commit db5be1a. Bugbot is set up for automated code reviews on this repo. Configure here.

Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
@mbani01 mbani01 self-assigned this Jul 13, 2026
Copilot AI review requested due to automatic review settings July 13, 2026 11:00
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Jira Issue Key Missing

Your PR title doesn't contain a Jira issue key. Consider adding it for better traceability.

Example:

  • feat: add user authentication (CM-123)
  • feat: add user authentication (IN-123)

Projects:

  • CM: Community Data Platform
  • IN: Insights

Please add a Jira issue key to your PR title.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Canonicalizes Go and NuGet repository URLs and persists package-to-repository links.

Changes:

  • Uses shared repository URL canonicalization.
  • Adds transactional package_repos linking and audit logging.
  • Includes Go package IDs for repository associations.

Issues: Existing declared links are not removed when repositories change. The title also incorrectly references Cargo and lacks a JIRA key.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
nuget/types.ts Adds canonical repository typing.
nuget/normalize.ts Canonicalizes repository metadata.
nuget/runNuGetEnrichmentLoop.ts Persists NuGet repository links.
go/activities.ts Canonicalizes and links Go repositories.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread services/apps/packages_worker/src/nuget/runNuGetEnrichmentLoop.ts
Comment thread services/apps/packages_worker/src/go/activities.ts
Comment thread services/apps/packages_worker/src/nuget/normalize.ts
@mbani01 mbani01 changed the title fix: re-use canonicalizeRepoUrl & insert package_repos in go and cargo fix: re-use canonicalizeRepoUrl & insert package_repos in go and nuget Jul 13, 2026
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 13, 2026 11:26
let nuspecXml: string | null = null
if (preliminary.latestVersion) {
const nuspecResult = await fetchNuspec(packageId, preliminary.latestVersion)
nuspecXml = isNuGetFetchError(nuspecResult) ? null : nuspecResult

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nuspec fetch failure blocks package sync

Medium Severity

After registration and search succeed, fetchNuspec can throw on transient HTTP or network errors. That aborts processPackage, marks the package in error, and skips the DB upsert even though enrichment used to complete using catalog and homepage fallbacks when nuspec was not fetched.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9ac2223. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Comment thread services/apps/packages_worker/src/nuget/runNuGetEnrichmentLoop.ts
Comment thread services/apps/packages_worker/src/nuget/runNuGetEnrichmentLoop.ts
Comment on lines +110 to +112
const repo =
(nuspecRepoUrl ? canonicalizeRepoUrl(nuspecRepoUrl) : null) ??
(isScmUrl(homepage) ? canonicalizeRepoUrl(homepage) : null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbani01 do you think it maybe worth toreview this ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4d69373

Comment on lines +101 to +105
const preliminary = normalizeNuGetPackage(packageId, searchItem, registrationResult)

let nuspecXml: string | null = null
if (preliminary.latestVersion) {
const nuspecResult = await fetchNuspec(packageId, preliminary.latestVersion)
Comment thread services/apps/packages_worker/src/go/activities.ts
Comment thread services/apps/packages_worker/src/go/activities.ts
Comment thread services/apps/packages_worker/src/go/activities.ts
@mbani01 mbani01 requested a review from ulemons July 13, 2026 14:02
ulemons
ulemons previously approved these changes Jul 13, 2026
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 13, 2026 15:25
const repositoryUrl = normalizeRepoUrl(repoCandidate ?? undefined)
const repo =
(nuspecRepoUrl ? canonicalizeRepoUrl(nuspecRepoUrl) : null) ??
(isScmUrl(homepage) ? canonicalizeRepoUrl(homepage) : null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Homepage used when declared repo fails

Medium Severity

normalizeNuGetPackage resolves repo with nullish coalescing after canonicalizeRepoUrl on nuspecRepoUrl. When a declared repository URL is present but cannot be canonicalized, the code still links via the SCM-shaped homepage. That contradicts the nearby comment and can create incorrect package_repos rows while declaredRepositoryUrl stays different.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 36f39e4. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Comment on lines +71 to +73
const isKnownHost = hostname in HOST_ENUM
let ownerPath = isKnownHost ? [segments[0]] : segments.slice(0, -1)
let name = (isKnownHost ? segments[1] : segments[segments.length - 1]).replace(/\.git$/, '')
Comment on lines +72 to 83
let ownerPath = isKnownHost ? [segments[0]] : segments.slice(0, -1)
let name = (isKnownHost ? segments[1] : segments[segments.length - 1]).replace(/\.git$/, '')
if (!name || ownerPath.length === 0 || ownerPath.some((seg) => !seg)) return null

if (CASE_INSENSITIVE_HOSTS.has(hostname)) {
owner = owner.toLowerCase()
ownerPath = ownerPath.map((seg) => seg.toLowerCase())
name = name.toLowerCase()
}

return {
url: `https://${hostname}/${owner}/${name}`,
url: `https://${hostname}/${[...ownerPath, name].join('/')}`,
host: HOST_ENUM[hostname] ?? 'other',
Comment on lines +159 to +165
const resp = await axios.get<string>(
`${FLAT_CONTAINER_BASE_URL}/${lowerId}/${lowerVersion}/${lowerId}.nuspec`,
{
responseType: 'text',
transformResponse: (data) => data,
timeout: 15000,
},
Comment on lines +33 to +42
const nuspecParser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' })

function normalizeRepoUrl(url: string | undefined): string | null {
if (!url) return null
return url
.trim()
.replace(/\.git$/, '')
.replace(/^git\+/, '')
.replace(/^git:\/\//, 'https://')
.replace(/^http:\/\/github\.com\//, 'https://github.com/')
export function parseNuspecRepositoryUrl(nuspecXml: string): string | null {
try {
const doc = nuspecParser.parse(nuspecXml)
const url = doc?.package?.metadata?.repository?.['@_url']
return typeof url === 'string' && url.trim() !== '' ? url.trim() : null
} catch {
return null
}
Copilot AI review requested due to automatic review settings July 13, 2026 15:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread services/apps/packages_worker/src/utils/canonicalizeRepoUrl.ts
Comment thread services/apps/packages_worker/src/utils/canonicalizeRepoUrl.ts
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 13, 2026 16:07
Comment thread services/apps/packages_worker/src/go/activities.ts
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

let owner = segments[0]
let name = segments[1].replace(/\.git$/, '')
if (!owner || !name) return null
const isKnownHost = hostname in HOST_ENUM
Comment on lines +72 to +73
let ownerPath = isKnownHost ? [segments[0]] : segments.slice(0, -1)
let name = (isKnownHost ? segments[1] : segments[segments.length - 1]).replace(/\.git$/, '')
Comment on lines +108 to +112
const nuspecRepoUrl = fetchedNuspecRepoUrl ?? catalogRepoUrl
const declaredRepositoryUrl = nuspecRepoUrl ?? null
const repoCandidate = nuspecRepoUrl ?? (isScmUrl(homepage) ? homepage : null)
const repositoryUrl = normalizeRepoUrl(repoCandidate ?? undefined)
const repo =
(nuspecRepoUrl ? canonicalizeRepoUrl(nuspecRepoUrl) : null) ??
(isScmUrl(homepage) ? canonicalizeRepoUrl(homepage) : null)
Comment on lines +143 to +147
const linkChanged = await upsertPackageRepo(
t,
packageDbId.toString(),
repoId,
'declared',
)
changedFields.push(...repoChanged)

const linkChanged = await upsertPackageRepo(t, row.id, repoId, 'declared', 0.8)
Copilot AI review requested due to automatic review settings July 13, 2026 16:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment on lines +71 to +73
const isKnownHost = hostname in HOST_ENUM
let ownerPath = isKnownHost ? [segments[0]] : segments.slice(0, -1)
let name = (isKnownHost ? segments[1] : segments[segments.length - 1]).replace(/\.git$/, '')
Comment on lines +132 to +133
const repoToLink = repo ?? declaredRepo
if (repoToLink) {
})
pkgChanged.forEach((f) => changed.add(f))

if (normalized.repo) {
Copilot AI review requested due to automatic review settings July 13, 2026 17:00
@mbani01 mbani01 merged commit 6710afe into main Jul 13, 2026
17 checks passed
@mbani01 mbani01 deleted the fix/repos_normalization_and_storing branch July 13, 2026 17:01

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Reviewed by Cursor Bugbot for commit db5be1a. Configure here.

} catch (err) {
const classified = classifyError(err)
if (classified) return classified
throw err

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nuspec fetch aborts enrichment

Medium Severity

fetchNuspec rethrows most HTTP and network errors after registration has already succeeded. processPackage does not treat those as optional, so a transient flat-container failure can mark the whole package as errored instead of continuing without nuspec data.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit db5be1a. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Comment on lines +71 to +73
const isKnownHost = hostname in HOST_ENUM
let ownerPath = isKnownHost ? [segments[0]] : segments.slice(0, -1)
let name = (isKnownHost ? segments[1] : segments[segments.length - 1]).replace(/\.git$/, '')
Comment on lines +143 to +149
const linkChanged = await upsertPackageRepo(
t,
packageDbId.toString(),
repoId,
'declared',
0.8,
)
Comment on lines +132 to +142
const repoToLink = repo ?? declaredRepo
if (repoToLink) {
const { id: repoId, changedFields: repoChanged } = await getOrCreateRepoByUrl(
t,
repoToLink.url,
repoToLink.host,
)
changedFields.push(...repoChanged)

const linkChanged = await upsertPackageRepo(t, row.id, repoId, 'declared', 0.8)
changedFields.push(...linkChanged)
Comment on lines +149 to +150
// The registration API never exposes the nuspec <repository> element — it must be read from the
// raw nuspec XML, served by the flat-container endpoint.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants