Fetch new package list

This commit is contained in:
Reinier Criel 2026-03-19 14:14:13 -07:00
parent 5864b09bde
commit cddcec9ba5
6 changed files with 564 additions and 11 deletions

View file

@ -11,6 +11,13 @@ const malwareDatabaseUrls = {
[ECOSYSTEM_PY]: "https://malware-list.aikido.dev/malware_pypi.json",
};
// TODO: replace with the real CDN URL once core publishes the S3 endpoint
const newPackagesListUrls = {
[ECOSYSTEM_JS]: "https://new-packages.aikido.dev/js_packages.json",
};
const DEFAULT_FETCH_RETRY_ATTEMPTS = 4;
/**
* @typedef {Object} MalwarePackage
* @property {string} package_name
@ -18,12 +25,19 @@ const malwareDatabaseUrls = {
* @property {string} reason
*/
/**
* @typedef {Object} NewPackageEntry
* @property {string} source
* @property {string} name
* @property {string} version
* @property {number} released_on - Unix timestamp (seconds)
* @property {number} scraped_on - Unix timestamp (seconds)
*/
/**
* @returns {Promise<{malwareDatabase: MalwarePackage[], version: string | undefined}>}
*/
export async function fetchMalwareDatabase() {
const numberOfAttempts = 4;
return retry(async () => {
const ecosystem = getEcoSystem();
const malwareDatabaseUrl =
@ -46,15 +60,13 @@ export async function fetchMalwareDatabase() {
} catch (/** @type {any} */ error) {
throw new Error(`Error parsing malware database: ${error.message}`);
}
}, numberOfAttempts);
}, DEFAULT_FETCH_RETRY_ATTEMPTS);
}
/**
* @returns {Promise<string | undefined>}
*/
export async function fetchMalwareDatabaseVersion() {
const numberOfAttempts = 4;
return retry(async () => {
const ecosystem = getEcoSystem();
const malwareDatabaseUrl =
@ -71,7 +83,63 @@ export async function fetchMalwareDatabaseVersion() {
);
}
return response.headers.get("etag") || undefined;
}, numberOfAttempts);
}, DEFAULT_FETCH_RETRY_ATTEMPTS);
}
/**
* @returns {Promise<{newPackagesList: NewPackageEntry[], version: string | undefined}>}
*/
export async function fetchNewPackagesList() {
return retry(async () => {
const ecosystem = getEcoSystem();
const url =
newPackagesListUrls[/** @type {keyof typeof newPackagesListUrls} */ (ecosystem)];
if (!url) {
return { newPackagesList: [], version: undefined };
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Error fetching ${ecosystem} new packages list: ${response.statusText}`
);
}
try {
const newPackagesList = await response.json();
return {
newPackagesList,
version: response.headers.get("etag") || undefined,
};
} catch (/** @type {any} */ error) {
throw new Error(`Error parsing new packages list: ${error.message}`);
}
}, DEFAULT_FETCH_RETRY_ATTEMPTS);
}
/**
* @returns {Promise<string | undefined>}
*/
export async function fetchNewPackagesListVersion() {
return retry(async () => {
const ecosystem = getEcoSystem();
const url =
newPackagesListUrls[/** @type {keyof typeof newPackagesListUrls} */ (ecosystem)];
if (!url) {
return undefined;
}
const response = await fetch(url, { method: "HEAD" });
if (!response.ok) {
throw new Error(
`Error fetching ${ecosystem} new packages list version: ${response.statusText}`
);
}
return response.headers.get("etag") || undefined;
}, DEFAULT_FETCH_RETRY_ATTEMPTS);
}
/**
@ -91,7 +159,7 @@ async function retry(func, attempts) {
return await func();
} catch (error) {
ui.writeVerbose(
"An error occurred while trying to download the Aikido Malware database",
"An error occurred while trying to download Aikido data",
error
);
lastError = error;