diff --git a/README.md b/README.md index 490215e..042d6a7 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,11 @@ export interface IResultOptions { * @default true */ resolveOnVersionControl?: boolean; + /** + * @description The version of the npm package (when `resolveOnNpmRegistry` only) to retrieve the scorecard for. + * @default "latest" + */ + npmPackageVersion?: string; } ``` diff --git a/package.json b/package.json index 3005a7e..f817ba4 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ }, "homepage": "https://github.com/NodeSecure/ossf-scorecard-sdk#readme", "devDependencies": { + "@matteo.collina/tspl": "^0.1.1", "@nodesecure/eslint-config": "^1.9.0", "@slimio/is": "^2.0.0", "@types/node": "^20.11.20", diff --git a/src/index.ts b/src/index.ts index 66bbd4d..6184ba0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,17 +61,22 @@ export interface IResultOptions { * @default true */ resolveOnVersionControl?: boolean; + /** + * @description The version of the npm package (when `resolveOnNpmRegistry` only) to retrieve the scorecard for. + * @default "latest" + */ + npmPackageVersion?: string; } -async function getNpmRepository(repository: string): Promise { +async function getNpmRepository(repository: string, version: string): Promise { const data = await packument(repository); - const latestVersion = data["dist-tags"].latest; + const latestVersion = data["dist-tags"].latest!; + const packageVersion = data.versions[version === "latest" ? latestVersion : version]; - if (!latestVersion) { - throw new Error("Cannot find the latest version of the given repository"); + if (!packageVersion) { + throw new Error(`Cannot find the version '${version}' of the given repository`); } - const packageVersion = data.versions[latestVersion]; const homepage = packageVersion.homepage || null; const repo = packageVersion.repository; const repoUrl = typeof repo === "string" ? repo : repo?.url; @@ -108,7 +113,8 @@ export async function result( const { platform = kDefaultPlatform, resolveOnNpmRegistry = true, - resolveOnVersionControl = true + resolveOnVersionControl = true, + npmPackageVersion = "latest" } = options; const [owner, repo] = repository.replace("@", "").split("/"); @@ -134,7 +140,7 @@ export async function result( } try { - formattedRepository = await getNpmRepository(repository); + formattedRepository = await getNpmRepository(repository, npmPackageVersion); } catch (error) { throw new Error(`Invalid repository, cannot find it on ${platformName} or NPM registry`, { @@ -145,7 +151,7 @@ export async function result( } else if (resolveOnNpmRegistry && !resolveOnVersionControl) { try { - formattedRepository = await getNpmRepository(repository); + formattedRepository = await getNpmRepository(repository, npmPackageVersion); } catch (error) { throw new Error(`Invalid repository, cannot find it on NPM registry`, { diff --git a/test/result.spec.ts b/test/result.spec.ts index df83a5f..4c3e0b8 100644 --- a/test/result.spec.ts +++ b/test/result.spec.ts @@ -6,6 +6,7 @@ import { after, afterEach, before, beforeEach, describe, it } from "node:test"; import { MockAgent, getGlobalDispatcher, setGlobalDispatcher, Interceptable } from "@myunisoft/httpie"; import is from "@slimio/is"; import * as npmRegistrySdk from "@nodesecure/npm-registry-sdk"; +import { tspl, type Plan } from "@matteo.collina/tspl"; // Import Internal Dependencies import * as scorecard from "../src/index.js"; @@ -188,14 +189,14 @@ describe("#result() FT", () => { }); it("should throw when given a package and npm resolve is falsy", async() => { - assert.rejects(async() => scorecard.result("@unknown-package/for-sure", { resolveOnNpmRegistry: false }), { + await assert.rejects(async() => scorecard.result("@unknown-package/for-sure", { resolveOnNpmRegistry: false }), { name: "Error", message: "Invalid repository, cannot find it on github" }); }); it("should throw when given a package and npm resolve is falsy (GitLab)", async() => { - assert.rejects(async() => scorecard.result("@unknown-package/for-sure", { + await assert.rejects(async() => scorecard.result("@unknown-package/for-sure", { platform: "gitlab.com", resolveOnNpmRegistry: false }), { @@ -205,14 +206,14 @@ describe("#result() FT", () => { }); it("should throw when given an unknown npm package", async() => { - assert.rejects(async() => await scorecard.result("@unknown-package/for-sure", { resolveOnNpmRegistry: true }), { + await assert.rejects(async() => await scorecard.result("@unknown-package/for-sure", { resolveOnNpmRegistry: true }), { name: "Error", message: "Invalid repository, cannot find it on github or NPM registry" }); }); it("should throw when given an unknown npm package (GitLab)", async() => { - assert.rejects(async() => await scorecard.result("@unknown-package/for-sure", { + await assert.rejects(async() => await scorecard.result("@unknown-package/for-sure", { platform: "gitlab.com", resolveOnNpmRegistry: true }), { @@ -220,6 +221,37 @@ describe("#result() FT", () => { message: "Invalid repository, cannot find it on gitlab or NPM registry" }); }); + + it("Should return a specific npm package ScorecardResult that does not have the repository set but has a homepage", async() => { + const result = await scorecard.result(`@topcli/prompts`, { + resolveOnVersionControl: false, + npmPackageVersion: "1.9.0" + }); + + assert.equal(is.plainObject(result), true); + assert.equal(result.repo.name, `github.com/TopCli/prompts`); + assert.deepStrictEqual( + Object.keys(result).sort(), + ["date", "repo", "scorecard", "score", "checks"].sort() + ); + }); + + it("Should throws when the given package version does not exists", async(testContext) => { + const tsplAssert: Plan = tspl(testContext, { plan: 3 }); + + // We cannot use assert.rejects to test Error.cause + try { + await scorecard.result(`@topcli/prompts`, { + resolveOnVersionControl: false, + npmPackageVersion: "99999.0.0" + }); + } + catch (error) { + tsplAssert.strictEqual(error.name, "Error"); + tsplAssert.strictEqual(error.message, "Invalid repository, cannot find it on NPM registry"); + tsplAssert.match(error.cause.message, /^Cannot find the version '99999.0.0' of the given repository/); + } + }); }); function getPath(repository: string): string {