Skip to content

Refacto/#380 extract licenses apis to support manifestmanager input #384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 61 additions & 16 deletions workspaces/conformance/src/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,83 @@ import {
const kManifestFileName = "package.json";
const kInvalidLicense = "invalid license";

export type ManifestManagerLike = string | ManifestManager;

async function getManifestManagerAndDirAsync(
input: ManifestManagerLike
): Promise<{ mama: ManifestManager; dir: string; }> {
if (typeof input === "string") {
return ManifestManager.fromPackageJSON(input).then((mama) => {
return {
mama,
dir: input
};
});
}
else if (input instanceof ManifestManager) {
const manifestPath = input.manifestLocation;
if (!manifestPath) {
throw new Error("ManifestManager instance must have a manifestLocation property set.");
}
const dir = path.dirname(manifestPath);

return Promise.resolve({ mama: input, dir });
}
throw new TypeError("Input must be a string or a ManifestManager instance");
}

function getManifestManagerAndDirSync(
input: ManifestManagerLike,
fsEngine: typeof fsSync
): { mama: ManifestManager; dir: string; } {
if (typeof input === "string") {
const packageStr = fsEngine.readFileSync(
path.join(input, kManifestFileName), "utf-8"
);

const packageJSON = JSON.parse(packageStr);

return {
mama: new ManifestManager(packageJSON, path.join(input, kManifestFileName)),
dir: input
};
}
else if (input instanceof ManifestManager) {
const manifestPath = input.manifestLocation;
if (!manifestPath) {
throw new Error("ManifestManager instance must have a manifestLocation property set.");
}
const dir = path.dirname(manifestPath);

return { mama: input, dir };
}
throw new TypeError("Input must be a string or a ManifestManager instance");
}

export interface ExtractAsyncOptions {
fsEngine?: typeof fs;
}

export async function extractLicenses(
location: string,
input: ManifestManagerLike,
options: ExtractAsyncOptions = {}
): Promise<SpdxExtractedResult> {
const { fsEngine = fs } = options;

const mama = await ManifestManager.fromPackageJSON(
location
);
const { mama, dir } = await getManifestManagerAndDirAsync(input);

const licenseData = new LicenseResult()
.addLicenseID(
mama.license ?? kInvalidLicense,
kManifestFileName
);

const dirents = await fsEngine.readdir(location, {
const dirents = await fsEngine.readdir(dir, {
withFileTypes: true
});
await Promise.allSettled(
utils.extractDirentLicenses(dirents).map(async(file) => {
const contentStr = await fsEngine.readFile(
path.join(location, file),
path.join(dir, file),
"utf-8"
);
licenseData.addLicenseIDFromSource(contentStr, file);
Expand All @@ -60,29 +110,24 @@ export interface ExtractSyncOptions {
}

export function extractLicensesSync(
location: string,
input: ManifestManagerLike,
options: ExtractSyncOptions = {}
): SpdxExtractedResult {
const { fsEngine = fsSync } = options;

const packageStr = fsEngine.readFileSync(
path.join(location, kManifestFileName), "utf-8"
);
const packageJSON = JSON.parse(packageStr);
const mama = new ManifestManager(packageJSON);
const { mama, dir } = getManifestManagerAndDirSync(input, fsEngine);

const licenseData = new LicenseResult();
licenseData.addLicenseID(
mama.license ?? kInvalidLicense,
kManifestFileName
);

const dirents = fsEngine.readdirSync(location, {
const dirents = fsEngine.readdirSync(dir, {
withFileTypes: true
});
for (const file of utils.extractDirentLicenses(dirents)) {
const contentStr = fsEngine.readFileSync(
path.join(location, file),
path.join(dir, file),
"utf-8"
);
licenseData.addLicenseIDFromSource(contentStr, file);
Expand Down
25 changes: 25 additions & 0 deletions workspaces/conformance/test/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import path from "node:path";
import assert from "node:assert";
import { fileURLToPath } from "node:url";
import { describe, it } from "node:test";
import fs from "node:fs/promises";
import fsSync from "node:fs";

// Import Internal Dependencies
import {
extractLicenses,
extractLicensesSync
} from "../src/index.js";
import expectedParsedLicense from "./fixtures/parseLicense.snap.js";
import { ManifestManager } from "@nodesecure/mama";

// CONSTANTS
const __dirname = path.dirname(fileURLToPath(import.meta.url));
Expand All @@ -28,6 +31,17 @@ describe("extractLicenses", () => {

assert.deepStrictEqual(result.uniqueLicenseIds, ["Artistic-2.0"]);
});

it("should work with a ManifestManager instance (async)", async() => {
const projectPath = path.join(kFixturePath, "project1");
const manifestPath = path.join(projectPath, "package.json");
const packageStr = await fs.readFile(manifestPath, "utf-8");
const packageJSON = JSON.parse(packageStr);
const mama = new ManifestManager(packageJSON, manifestPath);
const result = await extractLicenses(mama);
assert.deepStrictEqual(result.uniqueLicenseIds, ["ISC", "MIT"]);
assert.deepStrictEqual(result, expectedParsedLicense);
});
});

describe("extractLicensesSync", () => {
Expand All @@ -43,4 +57,15 @@ describe("extractLicensesSync", () => {

assert.deepStrictEqual(result.uniqueLicenseIds, ["Artistic-2.0"]);
});

it("should work with a ManifestManager instance (sync)", () => {
const projectPath = path.join(kFixturePath, "project1");
const manifestPath = path.join(projectPath, "package.json");
const packageStr = fsSync.readFileSync(manifestPath, "utf-8");
const packageJSON = JSON.parse(packageStr);
const mama = new ManifestManager(packageJSON, manifestPath);
const result = extractLicensesSync(mama);
assert.deepStrictEqual(result.uniqueLicenseIds, ["ISC", "MIT"]);
assert.deepStrictEqual(result, expectedParsedLicense);
});
});
8 changes: 6 additions & 2 deletions workspaces/mama/src/ManifestManager.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,22 @@ export class ManifestManager<
ManifestManagerDocument,
NonOptionalPackageJSONProperties
>;
public manifestLocation?: string;

public flags = Object.seal({
hasUnsafeScripts: false,
isNative: false
});

constructor(
document: ManifestManagerDocument
document: ManifestManagerDocument,
manifestLocation?: string
) {
this.document = Object.assign(
{ ...ManifestManager.Default },
structuredClone(document)
);
this.manifestLocation = manifestLocation;

this.flags.isNative = [
...this.dependencies,
Expand Down Expand Up @@ -211,7 +214,8 @@ export class ManifestManager<
) as PackageJSON | WorkspacesPackageJSON;

return new ManifestManager(
packageJSON
packageJSON,
packageLocation
);
}
catch (cause) {
Expand Down
10 changes: 10 additions & 0 deletions workspaces/mama/test/ManifestManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ describe("ManifestManager", () => {
mama.spec,
`${kMinimalPackageJSON.name}@${kMinimalPackageJSON.version}`
);
assert.strictEqual(
mama.manifestLocation,
location
);
});

test("Given an invalid JSON, it should throw a custom Error with the parsing error as a cause", async(t: TestContext) => {
Expand Down Expand Up @@ -136,6 +140,12 @@ describe("ManifestManager", () => {
}
);
});

it("Should store manifestLocation if provided", () => {
const location = "/tmp/fake/path/package.json";
const mama = new ManifestManager(kMinimalPackageJSON, location);
assert.strictEqual(mama.manifestLocation, location);
});
});

describe("get dependencies", () => {
Expand Down