Skip to content

Commit 9a61616

Browse files
authored
Merge branch 'main' into fix/IN-1241-open-vuln-available
2 parents cac4e6a + 7f82c65 commit 9a61616

128 files changed

Lines changed: 3274 additions & 1383 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/node/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
"dotenv": "8.2.0"
1313
},
1414
"devDependencies": {
15-
"@types/node": "^24.13.3",
15+
"@types/node": "catalog:",
1616
"@typescript-eslint/eslint-plugin": "^6.7.4",
1717
"@typescript-eslint/parser": "^6.7.4",
1818
"@vercel/ncc": "^0.38.1",
19-
"eslint": "^8.50.0",
19+
"eslint": "catalog:",
2020
"eslint-config-prettier": "^9.0.0",
2121
"eslint-plugin-prettier": "^5.0.0",
22-
"prettier": "^3.0.3",
22+
"prettier": "catalog:",
2323
"ts-node": "^10.9.1",
24-
"typescript": "^5.6.3"
24+
"typescript": "catalog:"
2525
}
2626
}

.github/workflows/backend-lint.yaml

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ on:
77
paths:
88
- 'backend/**'
99
- 'services/**'
10+
- 'pnpm-workspace.yaml'
11+
- 'pnpm-lock.yaml'
1012
jobs:
1113
lint-format:
1214
runs-on: ubuntu-latest
@@ -19,13 +21,11 @@ jobs:
1921
- name: Check out repository code
2022
uses: actions/checkout@v4
2123

22-
- name: Setup Node
23-
uses: actions/setup-node@v6
24+
- name: Setup pnpm and node
25+
uses: pnpm/setup@84cb39b217b10273981911c288cd62326dc7c6d2 # v2.0.2
2426
with:
25-
node-version-file: '.nvmrc'
26-
27-
- name: Install dependencies
28-
run: cd .. && npm install -g corepack@latest && corepack enable pnpm && pnpm i --frozen-lockfile
27+
runtime: node@24
28+
cache: true
2929

3030
- name: Check linting
3131
run: pnpm run lint
@@ -47,13 +47,11 @@ jobs:
4747
- name: Check out repository code
4848
uses: actions/checkout@v4
4949

50-
- name: Setup Node
51-
uses: actions/setup-node@v6
50+
- name: Setup pnpm and node
51+
uses: pnpm/setup@84cb39b217b10273981911c288cd62326dc7c6d2 # v2.0.2
5252
with:
53-
node-version-file: '.nvmrc'
54-
55-
- name: Install dependencies
56-
run: cd .. && npm install -g corepack@latest && corepack enable pnpm && pnpm i --frozen-lockfile
53+
runtime: node@24
54+
cache: true
5755

5856
- name: Check library linting, format and typescript
5957
run: ./scripts/lint_libs.sh

.github/workflows/server-tests.yml

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,11 @@ jobs:
5454
- name: Check out repository code
5555
uses: actions/checkout@v4
5656

57-
- name: Setup pnpm
58-
uses: pnpm/action-setup@v4
59-
60-
- name: Setup Node
61-
uses: actions/setup-node@v6
57+
- name: Setup pnpm and node
58+
uses: pnpm/setup@84cb39b217b10273981911c288cd62326dc7c6d2 # v2.0.2
6259
with:
63-
node-version-file: '.nvmrc'
64-
cache: 'pnpm'
65-
66-
- name: Install dependencies
67-
run: pnpm i --frozen-lockfile
60+
runtime: node@24
61+
cache: true
6862

6963
- name: Set up Docker Buildx
7064
uses: docker/setup-buildx-action@v3

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# dependencies
44
/node_modules
55
node_modules/
6+
.pnpm-store/
67

78
# testing
89
/frontend/tests/screenshots

backend/.prettierrc.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ module.exports = {
1414
],
1515
importOrderSeparation: true,
1616
importOrderSortSpecifiers: true,
17-
plugins: ['@trivago/prettier-plugin-sort-imports'],
17+
plugins: [require.resolve('@trivago/prettier-plugin-sort-imports')],
1818
}

backend/package.json

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"start:job-generator:dev": "nodemon --watch \"src/**/*.ts\" --watch ../services/libs -e ts,json --exec \"pnpm run start:job-generator\"",
1010
"start:job-generator:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && pnpm run start:job-generator:dev",
1111
"build": "tsc && cp package*json dist/ && cp .sequelizerc dist/.sequelizerc ",
12-
"sequelize-cli:source": "pnpm run build && npx sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations",
13-
"sequelize-cli:build": "npx sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations",
12+
"sequelize-cli:source": "pnpm run build && sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations",
13+
"sequelize-cli:build": "sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations",
1414
"lint": "eslint .",
1515
"format": "prettier --write .",
1616
"format-check": "prettier --check .",
@@ -76,7 +76,7 @@
7676
"@slack/web-api": "^7.19.0",
7777
"analytics-node": "^6.2.0",
7878
"aws-sdk": "2.814.0",
79-
"axios": "^1.19.0",
79+
"axios": "catalog:",
8080
"bcrypt": "6.0.0",
8181
"body-parser": "^1.20.1",
8282
"bufferutil": "^4.0.7",
@@ -86,7 +86,7 @@
8686
"cli-highlight": "2.1.6",
8787
"command-line-args": "^5.2.1",
8888
"command-line-usage": "^6.1.3",
89-
"config": "^3.3.12",
89+
"config": "catalog:",
9090
"cors": "2.8.5",
9191
"cron": "^2.1.0",
9292
"cron-time-generator": "^1.3.0",
@@ -122,7 +122,7 @@
122122
"passport-slack": "0.0.7",
123123
"peopledatalabs": "~6.1.5",
124124
"pg": "^8.7.3",
125-
"pg-promise": "^11.4.3",
125+
"pg-promise": "catalog:",
126126
"sanitize-html": "^2.7.1",
127127
"sequelize": "6.37.8",
128128
"sequelize-cli-typescript": "^3.2.0-c",
@@ -145,7 +145,7 @@
145145
"@types/cron": "^2.0.0",
146146
"@types/express": "^4.17.17",
147147
"@types/html-to-text": "^8.1.1",
148-
"@types/node": "^24.13.3",
148+
"@types/node": "catalog:",
149149
"@types/sanitize-html": "^2.6.2",
150150
"@types/superagent": "^4.1.15",
151151
"@types/uuid": "^9.0.2",
@@ -154,17 +154,16 @@
154154
"copyfiles": "2.4.1",
155155
"cross-env": "7.0.2",
156156
"deep-object-diff": "^1.1.9",
157-
"eslint": "^8.12.0",
157+
"eslint": "catalog:",
158158
"eslint-config-airbnb-base": "^15.0.0",
159159
"eslint-config-airbnb-typescript": "^16.1.4",
160160
"eslint-config-prettier": "^8.5.0",
161161
"eslint-plugin-import": "^2.25.4",
162162
"lint-staged": "^15.4.3",
163163
"nodemon": "2.0.4",
164-
"prettier": "^3.3.3",
164+
"prettier": "catalog:",
165165
"rdme": "^7.2.0",
166-
"tsx": "^4.20.3",
167-
"typescript": "^5.6.3"
168-
},
169-
"packageManager": "pnpm@9.15.0"
166+
"tsx": "catalog:",
167+
"typescript": "catalog:"
168+
}
170169
}

backend/src/api/public/v1/members/createMember.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Request, Response } from 'express'
22
import { z } from 'zod'
33

44
import { captureApiChange, memberCreateAction, memberEditIdentitiesAction } from '@crowd/audit-logs'
5-
import { ConflictError, getProperDisplayName } from '@crowd/common'
5+
import { ConflictError, normalizeDisplayName } from '@crowd/common'
66
import {
77
findMemberIdByVerifiedIdentity,
88
findMembersByIdentities,
@@ -41,7 +41,7 @@ export async function createMember(req: Request, res: Response): Promise<void> {
4141
const { displayName, identities } = validateOrThrow(bodySchema, req.body)
4242
const qx = optionsQx(req)
4343

44-
const normalizedDisplayName = getProperDisplayName(displayName)
44+
const normalizedDisplayName = normalizeDisplayName(displayName)
4545

4646
const { dbMember, dbIdentities } = await qx.tx(async (tx) => {
4747
// Unverified identities aren't unique in the db, so the same handle or
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
-- rank_packages() applied its ranking to all of `packages` in one UPDATE (9.66M
2+
-- rows), which under REPLICA IDENTITY FULL blew up the Sequin replication slot.
3+
-- rank_packages_chunked() stages the same ranking into an UNLOGGED table, then
4+
-- applies it in committed keyset chunks so the slot advances continuously.
5+
-- rank_packages() is left in place until the new worker deploys.
6+
7+
CREATE UNLOGGED TABLE IF NOT EXISTS staging.package_rank (
8+
package_id bigint PRIMARY KEY,
9+
impact numeric(10, 4),
10+
is_critical bool NOT NULL,
11+
rank_in_ecosystem int NOT NULL
12+
);
13+
14+
CREATE OR REPLACE PROCEDURE rank_packages_chunked(
15+
coverage_cutoff numeric DEFAULT 0.90,
16+
ecosystems text[] DEFAULT NULL,
17+
chunk_size int DEFAULT 25000,
18+
INOUT applied_rows int DEFAULT 0
19+
)
20+
LANGUAGE plpgsql AS $$
21+
DECLARE
22+
effective_ecosystems text[];
23+
staged_count int;
24+
batch_rows int;
25+
cursor_id bigint := 0;
26+
BEGIN
27+
SET LOCAL max_parallel_workers_per_gather = 4;
28+
29+
IF chunk_size IS NULL OR chunk_size <= 0 THEN
30+
RAISE EXCEPTION 'rank_packages_chunked: chunk_size must be a positive integer, got %', chunk_size;
31+
END IF;
32+
33+
-- Session-level: survives the internal COMMITs below
34+
IF NOT pg_try_advisory_lock(hashtextextended('rank_packages_chunked', 0)) THEN
35+
RAISE EXCEPTION 'rank_packages_chunked: another execution is already in progress';
36+
END IF;
37+
38+
applied_rows := 0;
39+
40+
IF ecosystems IS NULL THEN
41+
SELECT ARRAY_AGG(DISTINCT ecosystem)
42+
INTO effective_ecosystems
43+
FROM packages;
44+
ELSE
45+
effective_ecosystems := ecosystems;
46+
END IF;
47+
48+
TRUNCATE staging.package_rank;
49+
50+
-- Scoring CTE chain, unchanged from rank_packages() (V1783123201).
51+
INSERT INTO staging.package_rank (package_id, impact, is_critical, rank_in_ecosystem)
52+
WITH base AS (
53+
SELECT
54+
id,
55+
ecosystem,
56+
COALESCE(downloads_last_30d, 0) AS downloads,
57+
COALESCE(dependent_count, 0) AS direct_dependents,
58+
COALESCE(transitive_dependent_count, 0) AS transitive_dependents,
59+
COALESCE(sonatype_popularity_score, 0) AS sonatype_popularity,
60+
SUM(COALESCE(downloads_last_30d, 0)) OVER (PARTITION BY ecosystem) AS ecosystem_total_downloads,
61+
SUM(COALESCE(dependent_count, 0)) OVER (PARTITION BY ecosystem) AS ecosystem_total_direct_dependents,
62+
SUM(COALESCE(transitive_dependent_count, 0)) OVER (PARTITION BY ecosystem) AS ecosystem_total_transitive_dependents,
63+
SUM(COALESCE(sonatype_popularity_score, 0)) OVER (PARTITION BY ecosystem) AS ecosystem_total_sonatype
64+
FROM packages
65+
WHERE ecosystem = ANY(effective_ecosystems)
66+
),
67+
walked AS (
68+
SELECT
69+
id,
70+
ecosystem,
71+
SUM(signal_value) OVER coverage_window / ecosystem_signal_total::numeric AS cumulative_share_inclusive,
72+
(SUM(signal_value) OVER coverage_window - signal_value) / ecosystem_signal_total::numeric AS cumulative_share_exclusive
73+
FROM base
74+
CROSS JOIN LATERAL (VALUES
75+
('downloads', downloads, ecosystem_total_downloads),
76+
('direct_dependents', direct_dependents, ecosystem_total_direct_dependents),
77+
('transitive_dependents', transitive_dependents, ecosystem_total_transitive_dependents),
78+
('sonatype_popularity', sonatype_popularity, ecosystem_total_sonatype)
79+
) AS signal(signal_name, signal_value, ecosystem_signal_total)
80+
WHERE ecosystem_signal_total > 0
81+
WINDOW coverage_window AS (
82+
PARTITION BY ecosystem, signal_name
83+
ORDER BY signal_value DESC, id
84+
ROWS UNBOUNDED PRECEDING
85+
)
86+
),
87+
combined AS (
88+
SELECT
89+
id,
90+
ecosystem,
91+
AVG(1.0 - cumulative_share_inclusive)::numeric(10, 4) AS new_impact,
92+
BOOL_OR(cumulative_share_exclusive < coverage_cutoff) AS new_is_critical
93+
FROM walked
94+
GROUP BY id, ecosystem
95+
),
96+
final AS (
97+
SELECT
98+
combined.id,
99+
combined.new_impact,
100+
combined.new_is_critical OR (spotlight.package_id IS NOT NULL) AS new_is_critical,
101+
ROW_NUMBER() OVER (
102+
PARTITION BY combined.ecosystem
103+
ORDER BY combined.new_impact DESC NULLS LAST, combined.id
104+
) AS new_rank_in_ecosystem
105+
FROM combined
106+
LEFT JOIN package_criticality_spotlight spotlight ON spotlight.package_id = combined.id
107+
)
108+
SELECT id, new_impact, new_is_critical, new_rank_in_ecosystem::int
109+
FROM final;
110+
111+
GET DIAGNOSTICS staged_count = ROW_COUNT;
112+
113+
IF staged_count = 0 THEN
114+
RAISE EXCEPTION 'rank_packages_chunked: computed 0 rows, refusing to apply an empty ranking';
115+
END IF;
116+
117+
ANALYZE staging.package_rank;
118+
119+
COMMIT;
120+
121+
LOOP
122+
WITH batch AS (
123+
SELECT package_id, impact, is_critical, rank_in_ecosystem
124+
FROM staging.package_rank
125+
WHERE package_id > cursor_id
126+
ORDER BY package_id
127+
LIMIT chunk_size
128+
),
129+
updated AS (
130+
UPDATE packages p
131+
SET impact = b.impact,
132+
is_critical = b.is_critical,
133+
rank_in_ecosystem = b.rank_in_ecosystem,
134+
last_rank_pass_at = NOW(),
135+
last_synced_at = NOW()
136+
FROM batch b
137+
WHERE p.id = b.package_id
138+
RETURNING p.id
139+
)
140+
SELECT COUNT(*), COALESCE(MAX(b.package_id), cursor_id)
141+
INTO batch_rows, cursor_id
142+
FROM batch b;
143+
144+
applied_rows := applied_rows + batch_rows;
145+
146+
COMMIT;
147+
148+
EXIT WHEN batch_rows < chunk_size;
149+
END LOOP;
150+
151+
PERFORM pg_advisory_unlock(hashtextextended('rank_packages_chunked', 0));
152+
END;
153+
$$;

backend/src/services/memberService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import {
99
calculateReach,
1010
getAttributeValue,
1111
getCountry,
12-
getProperDisplayName,
1312
hasAttributeValue,
1413
isDomainExcluded,
14+
normalizeDisplayName,
1515
} from '@crowd/common'
1616
import {
1717
CommonMemberService,
@@ -279,7 +279,7 @@ export default class MemberService extends LoggerBase {
279279
}
280280

281281
if (!data.displayName) {
282-
data.displayName = getProperDisplayName(data.username[data.platform][0].username)
282+
data.displayName = normalizeDisplayName(data.username[data.platform][0].username)
283283
}
284284

285285
if (!(data.platform in data.username)) {
@@ -815,7 +815,7 @@ export default class MemberService extends LoggerBase {
815815
transaction = repoOptions.transaction
816816

817817
if (data.displayName) {
818-
data.displayName = getProperDisplayName(data.displayName)
818+
data.displayName = normalizeDisplayName(data.displayName)
819819
}
820820

821821
if (data.attributes) {

0 commit comments

Comments
 (0)