Skip to content

Commit

Permalink
feat(scanner/depWalker): add missing metadata when manifest does not …
Browse files Browse the repository at this point in the history
…exists on remote registry
  • Loading branch information
PierreDemailly committed Feb 20, 2025
1 parent d06b76e commit b2dba04
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 10 deletions.
3 changes: 3 additions & 0 deletions workspaces/npm-types/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export interface ManifestResult {
export type ManifestVersion = Pick<
PackumentVersion,
| "_hasShrinkwrap"
| "author"
| "bin"
| "bundleDependencies"
| "bundledDependencies"
Expand All @@ -221,9 +222,11 @@ export type ManifestVersion = Pick<
| "directories"
| "dist"
| "engines"
| "homepage"
| "name"
| "optionalDependencies"
| "peerDependencies"
| "repository"
| "version"
>

Expand Down
19 changes: 18 additions & 1 deletion workspaces/scanner/src/depWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import type { ManifestVersion, PackageJSON } from "@nodesecure/npm-types";

// Import Internal Dependencies
import {
getDependenciesWarnings, addMissingVersionFlags, getUsedDeps
getDependenciesWarnings,
addMissingVersionFlags,
getUsedDeps,
manifestAuthor,
getManifestLinks
} from "./utils/index.js";
import { packageMetadata, manifestMetadata } from "./npmRegistry.js";
import { Logger, ScannerLoggerEvents } from "./class/logger.class.js";
Expand Down Expand Up @@ -234,6 +238,19 @@ export async function depWalker(
...addMissingVersionFlags(new Set(verDescriptor.flags), dependency)
);

if (verDescriptor.existOnRemoteRegistry === false && packageName === manifest.name) {
Object.assign(dependency.metadata, {
author: manifestAuthor(manifest.author),
homepage: manifest.homepage
});

Object.assign(verDescriptor, {
author: manifestAuthor(manifest.author),
links: getManifestLinks(manifest),
repository: manifest.repository
});
}

const usedDeps = npmTreeWalker.relationsMap.get(`${packageName}@${verStr}`) || new Set();
if (usedDeps.size === 0) {
continue;
Expand Down
17 changes: 16 additions & 1 deletion workspaces/scanner/src/utils/getLinks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Import Third-party Dependencies
import type { PackumentVersion } from "@nodesecure/npm-types";
import type { ManifestVersion, PackageJSON, PackumentVersion } from "@nodesecure/npm-types";

// CONSTANTS
const kVCSHosts = new Set(["github.com", "gitlab.com"]);
Expand Down Expand Up @@ -44,3 +44,18 @@ export function getLinks(
getVCSRepositoryURL(repositoryUrl)
};
}

export function getManifestLinks(manifest: ManifestVersion | PackageJSON) {
const homepage = manifest.homepage ?? null;
const repositoryUrl = typeof manifest.repository === "string" ?
manifest.repository :
manifest.repository?.url ?? null;

return {
npm: null,
homepage,
repository:
getVCSRepositoryURL(homepage) ??
getVCSRepositoryURL(repositoryUrl)
};
}
1 change: 1 addition & 0 deletions workspaces/scanner/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./addMissingVersionFlags.js";
export * from "./getLinks.js";
export * from "./urlToString.js";
export * from "./getUsedDeps.js";
export * from "./manifestAuthor.js";

export const NPM_TOKEN = typeof process.env.NODE_SECURE_TOKEN === "string" ?
{ token: process.env.NODE_SECURE_TOKEN } :
Expand Down
24 changes: 24 additions & 0 deletions workspaces/scanner/src/utils/manifestAuthor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Import Third-party Dependencies
import type { Contact } from "@nodesecure/npm-types";

// CONSTANTS
const kStrAuthorRegexp = /^([^<(]+?)?[ \t]*(?:<([^>(]+?)>)?[ \t]*(?:\(([^)]+?)\)|$)/g;

export function manifestAuthor(author: string | Contact | undefined): Contact | null {
if (author === void 0) {
return null;
}

if (typeof author === "string") {
if (author.trim() === "") {
return null;
}

const [_, name, email, url] = kStrAuthorRegexp.exec(author) ?? [];

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on
library input
may run slow on strings with many repetitions of '\t'.
kStrAuthorRegexp.lastIndex = 0;

return { name, email, url };
}

return author;
}
46 changes: 38 additions & 8 deletions workspaces/scanner/test/depWalker.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Import Node.js Dependencies
import { join } from "node:path";
import path from "node:path";
import url from "node:url";
import { readFileSync } from "node:fs";
import { test } from "node:test";
import assert from "node:assert";
Expand All @@ -10,24 +11,25 @@ import { getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";

// Import Internal Dependencies
import { depWalker } from "../src/depWalker.js";
import { from, type Payload, type DependencyVersion } from "../src/index.js";
import { from, type Payload, type DependencyVersion, cwd } from "../src/index.js";

// CONSTANTS
const FIXTURE_PATH = join("fixtures", "depWalker");
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
const kFixturePath = path.join(__dirname, "fixtures", "depWalker");

// JSON PAYLOADS
const is = JSON.parse(readFileSync(
new URL(join(FIXTURE_PATH, "slimio.is.json"), import.meta.url),
path.join(kFixturePath, "slimio.is.json"),
"utf8"
));

const config = JSON.parse(readFileSync(
new URL(join(FIXTURE_PATH, "slimio.config.json"), import.meta.url),
path.join(kFixturePath, "slimio.config.json"),
"utf8"
));

const pkgGitdeps = JSON.parse(readFileSync(
new URL(join(FIXTURE_PATH, "pkg.gitdeps.json"), import.meta.url),
path.join(kFixturePath, "pkg.gitdeps.json"),
"utf8"
));

Expand Down Expand Up @@ -62,8 +64,7 @@ test("execute depWalker on @slimio/is", async() => {
const resultAsJSON = JSON.parse(JSON.stringify(result.dependencies, null, 2));
cleanupPayload(resultAsJSON);

const expectedResult = JSON.parse(readFileSync(join("test", FIXTURE_PATH, "slimio.is-result.json"), "utf-8"));
// console.log(JSON.stringify(resultAsJSON, null, 2));
const expectedResult = JSON.parse(readFileSync(path.join(kFixturePath, "slimio.is-result.json"), "utf-8"));
assert.deepEqual(resultAsJSON, expectedResult);
});

Expand Down Expand Up @@ -194,3 +195,32 @@ test("highlight contacts from a remote package", async() => {
maintainer.dependencies.includes(spec)
);
});

test("should parse author, homepage and links for non-npm package", async() => {
const result = await cwd(path.join(kFixturePath, "non-npm-package"));

const dep = result.dependencies["non-npm-package"];
const v1 = dep.versions["1.0.0"];

assert.deepEqual(v1.author, {
email: void 0,
name: "NodeSecure",
url: void 0
});
assert.deepStrictEqual(v1.links, {
npm: null,
homepage: "https://nodesecure.com",
repository: "https://github.com/NodeSecure/non-npm-package"
});
assert.deepStrictEqual(v1.repository, {
type: "git",
url: "https://github.com/NodeSecure/non-npm-package.git"
});

assert.deepStrictEqual(dep.metadata.author, {
email: void 0,
name: "NodeSecure",
url: void 0
});
assert.strictEqual(dep.metadata.homepage, "https://nodesecure.com");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "non-npm-package",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"description": "",
"dependencies": {
},
"repository": {
"type": "git",
"url": "https://github.com/NodeSecure/non-npm-package.git"
},
"author": "NodeSecure",
"homepage": "https://nodesecure.com"
}
91 changes: 91 additions & 0 deletions workspaces/scanner/test/utils/getLinks.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Import Node.js Dependencies
import assert from "node:assert/strict";
import { describe, it } from "node:test";

// Import Third-party Dependencies
import { PackageJSON, PackumentVersion } from "@nodesecure/npm-types";

// Import Internal Dependencies
import * as utils from "../../src/utils/index.js";

describe("utils.getLinks", () => {
it("should return all links", () => {
assert.deepStrictEqual(utils.getLinks({
homepage: "https://github.com/foo/bar",
repository: "git@github.com:foo/bar.git",
name: "foo",
version: "1.0.0"
} as any as PackumentVersion), {
npm: "https://www.npmjs.com/package/foo/v/1.0.0",
homepage: "https://github.com/foo/bar",
repository: "https://github.com/foo/bar"
});
});

it("homepage should be null but repository should be parsed", () => {
assert.deepStrictEqual(utils.getLinks({
homepage: null,
repository: "https://github.com/foo/bar.git",
name: "foo",
version: "1.0.0"
} as any), {
npm: "https://www.npmjs.com/package/foo/v/1.0.0",
homepage: null,
repository: "https://github.com/foo/bar"
});
});

it("should return repository.url", () => {
assert.deepStrictEqual(utils.getLinks({
name: "foo",
version: "1.0.0",
homepage: "https://github.com/foo/bar",
repository: {
type: "git",
url: "github.com/foo/bar"
}
} as any), {
npm: "https://www.npmjs.com/package/foo/v/1.0.0",
homepage: "https://github.com/foo/bar",
repository: "https://github.com/foo/bar"
});
});
});

describe("utils.getManifestLinks", () => {
it("should return homepage and repository", () => {
assert.deepStrictEqual(utils.getManifestLinks({
homepage: "https://github.com/foo/bar",
repository: "https://github.com/foo/bar"
} as PackageJSON), {
npm: null,
homepage: "https://github.com/foo/bar",
repository: "https://github.com/foo/bar"
});
});

it("should return repository only", () => {
assert.deepStrictEqual(utils.getManifestLinks({
homepage: null,
repository: "https://github.com/foo/bar"
} as any), {
npm: null,
homepage: null,
repository: "https://github.com/foo/bar"
});
});

it("should return repository.url", () => {
assert.deepStrictEqual(utils.getManifestLinks({
homepage: null,
repository: {
type: "git",
url: "https://github.com/foo/bar"
}
} as any), {
npm: null,
homepage: null,
repository: "https://github.com/foo/bar"
});
});
});
50 changes: 50 additions & 0 deletions workspaces/scanner/test/utils/manifestAuthor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Import Node.js Dependencies
import assert from "node:assert/strict";
import { describe, it } from "node:test";

// Import Internal Dependencies
import * as utils from "../../src/utils/index.js";

describe("utils.manifestAuthor", () => {
it("should return null when given undefined", () => {
assert.strictEqual(utils.manifestAuthor(undefined), null);
});

it("should return null when given empty string", () => {
assert.strictEqual(utils.manifestAuthor(""), null);
});

it("should return author object with only name", () => {
assert.deepStrictEqual(utils.manifestAuthor("John Doe"), {
name: "John Doe",
email: void 0,
url: void 0
});
});

it("should return author object with name and email", () => {
assert.deepStrictEqual(utils.manifestAuthor("John Doe <john@doe.com>"), {
name: "John Doe",
email: "john@doe.com",
url: void 0
});
});

it("should return author object with name, email and url", () => {
assert.deepStrictEqual(utils.manifestAuthor("John Doe <john@doe.com> (john.com)"), {
name: "John Doe",
email: "john@doe.com",
url: "john.com"
});
});

it("should return given author object", () => {
const author = {
name: "John Doe",
email: "john@doe.com",
url: "john.com"
};

assert.deepStrictEqual(utils.manifestAuthor(author), author);
});
});

0 comments on commit b2dba04

Please sign in to comment.