11import {
2+ deleteMavenPackageRepoLinks ,
23 listMavenPackagesForRepoUrlRecompute ,
34 updateMavenRepositoryUrls ,
45} from '@crowd/data-access-layer'
56import { QueryExecutor } from '@crowd/data-access-layer/src/queryExecutor'
67import { getServiceChildLogger } from '@crowd/logging'
78
89import { normalizeScmUrl } from './extract'
10+ import { writeRepoLink } from './runMavenEnrichmentLoop'
911
1012const log = getServiceChildLogger ( 'maven-repo-url-backfill' )
1113
@@ -15,6 +17,8 @@ export type RepoUrlBackfillTotals = {
1517 cleared : number // Gap C: non-repo value → NULL
1618 rewritten : number // non-canonical value → different canonical value
1719 unchanged : number
20+ linked : number // repos/package_repos link (re)written for a fill or rewrite
21+ pruned : number // stale 'declared' link removed for a clear or rewrite
1822}
1923
2024/**
@@ -23,6 +27,15 @@ export type RepoUrlBackfillTotals = {
2327 * fetched — the raw SCM value is already in the DB. Fills recoverable NULLs
2428 * (Gap B) and clears non-repository values (Gap C) via direct UPDATE.
2529 *
30+ * Link tables: package_repos is kept consistent with the recomputed
31+ * repository_url. For rows that had a value and now change (rewrites) or are
32+ * cleared, the stale source='declared' link is deleted; for rows that gain a
33+ * canonical URL (fills and rewrites) the correct link is (re)written via the
34+ * same `writeRepoLink` the enrichment loop uses. This matters because consumers
35+ * such as security-contacts read the repo through repos ⋈ package_repos, not
36+ * packages.repository_url. Note this is stricter than the incremental
37+ * enrichment path, which is upsert-only and never prunes.
38+ *
2639 * Idempotent and resumable: the id cursor is derived from the scan, so a
2740 * re-run after an interrupt simply reprocesses from the start and skips rows
2841 * that already match.
@@ -38,6 +51,8 @@ export async function backfillMavenRepositoryUrls(
3851 cleared : 0 ,
3952 rewritten : 0 ,
4053 unchanged : 0 ,
54+ linked : 0 ,
55+ pruned : 0 ,
4156 }
4257
4358 let afterId = 0
@@ -51,6 +66,10 @@ export async function backfillMavenRepositoryUrls(
5166 if ( rows . length === 0 ) break
5267
5368 const updates : { id : number ; repositoryUrl : string | null } [ ] = [ ]
69+ // Rows that had a link and now change/clear — their stale 'declared' link is pruned.
70+ const pruneTargets : number [ ] = [ ]
71+ // Rows that gained a canonical URL — their repo link is (re)written after the update.
72+ const linkTargets : { id : number ; repositoryUrl : string } [ ] = [ ]
5473 for ( const row of rows ) {
5574 totals . scanned ++
5675 const desired = normalizeScmUrl ( row . declaredRepositoryUrl )
@@ -62,10 +81,27 @@ export async function backfillMavenRepositoryUrls(
6281 else if ( desired === null ) totals . cleared ++
6382 else totals . rewritten ++
6483 updates . push ( { id : row . id , repositoryUrl : desired } )
84+ // A 'declared' link only exists when the row already had a value.
85+ if ( row . repositoryUrl !== null ) pruneTargets . push ( row . id )
86+ if ( desired !== null ) linkTargets . push ( { id : row . id , repositoryUrl : desired } )
6587 }
6688
6789 if ( updates . length > 0 && ! dryRun ) {
68- await updateMavenRepositoryUrls ( qx , updates )
90+ // Atomic per batch: the repository_url UPDATE, the stale-link prune, and the
91+ // relink must commit together. Otherwise an interrupt between them leaves
92+ // packages.repository_url updated but package_repos out of sync — and on a
93+ // re-run the row is skipped (its repository_url already matches `desired`),
94+ // so the inconsistency would never be repaired. On rollback the row stays
95+ // unchanged and is reprocessed on the next run.
96+ await qx . tx ( async ( t ) => {
97+ await updateMavenRepositoryUrls ( t , updates )
98+ await deleteMavenPackageRepoLinks ( t , pruneTargets )
99+ for ( const target of linkTargets ) {
100+ await writeRepoLink ( t , target . id , target . repositoryUrl )
101+ }
102+ } )
103+ totals . pruned += pruneTargets . length
104+ totals . linked += linkTargets . length
69105 }
70106
71107 afterId = rows [ rows . length - 1 ] . id
0 commit comments