Skip to content
Closed
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
44 changes: 42 additions & 2 deletions componentDetection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,48 @@ test("Runs CLI", async () => {
}, 10000);

test("Parses CLI output", async () => {
await ComponentDetection.downloadLatestRelease();
await ComponentDetection.runComponentDetection("./test");
// Mock the CLI output file
const mockOutput = JSON.stringify({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could be stored and ready from a file versus inline?

componentsFound: [
{
component: {
packageUrl: {
Scheme: "pkg",
Type: "npm",
Namespace: "github",
Name: "component-detection-action",
Version: "0.0.2",
Qualifiers: { arch: "amd64", os: "linux" }
},
id: "1"
},
isDevelopmentDependency: false,
topLevelReferrers: [],
locationsFoundAt: ["/test/package.json"],
containerDetailIds: [],
containerLayerIds: []
},
{
component: {
packageUrl: {
Scheme: "pkg",
Type: "npm",
Namespace: "github",
Name: "component-detection-action",
Version: "0.0.3",
Qualifiers: { arch: "amd64", os: "linux" }
},
id: "2"
},
isDevelopmentDependency: true,
topLevelReferrers: [],
locationsFoundAt: ["/test/package-lock.json"],
containerDetailIds: [],
containerLayerIds: []
}
]
});
fs.writeFileSync(ComponentDetection.outputPath, mockOutput, { encoding: 'utf8' });
var manifests = await ComponentDetection.getManifestsFromResults();
expect(manifests?.length == 2);
});
Expand Down
83 changes: 53 additions & 30 deletions componentDetection.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import * as github from '@actions/github'
import * as core from '@actions/core'
import { Octokit, App } from "octokit"
import {
PackageCache,
BuildTarget,
Package,
Snapshot,
Manifest,
submitSnapshot
} from '@github/dependency-submission-toolkit'
Comment on lines -4 to -11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to remove these imports?

import fetch from 'cross-fetch'
import tar from 'tar'
import fs from 'fs'
Expand All @@ -23,7 +14,7 @@ export default class ComponentDetection {
public static outputPath = './output.json';

// This is the default entry point for this class.
static async scanAndGetManifests(path: string): Promise<Manifest[] | undefined> {
static async scanAndGetManifests(path: string): Promise<any[] | undefined> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return a manifest

await this.downloadLatestRelease();
await this.runComponentDetection(path);
return await this.getManifestsFromResults();
Expand All @@ -33,26 +24,34 @@ export default class ComponentDetection {
try {
core.debug(`Downloading latest release for ${process.platform}`);
const downloadURL = await this.getLatestReleaseURL();
core.info(`Download URL: ${downloadURL}`);
const blob = await (await fetch(new URL(downloadURL))).blob();
const arrayBuffer = await blob.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);

// Write the blob to a file
core.debug(`Writing binary to file ${this.componentDetectionPath}`);
await fs.writeFileSync(this.componentDetectionPath, buffer, { mode: 0o777, flag: 'w' });
core.info(`Binary written to ${this.componentDetectionPath}`);
core.info(`File exists: ${fs.existsSync(this.componentDetectionPath)}`);
core.info(`File permissions: ${fs.statSync(this.componentDetectionPath).mode.toString(8)}`);
} catch (error: any) {
core.error(error);
core.info(`Download or write failed: ${error.message}`);
}
}

// Run the component-detection CLI on the path specified
public static async runComponentDetection(path: string) {
core.info("Running component-detection");

core.info(`CLI path: ${this.componentDetectionPath}`);
core.info(`Output path: ${this.outputPath}`);
try {
await exec.exec(`${this.componentDetectionPath} scan --SourceDirectory ${path} --ManifestFile ${this.outputPath} ${this.getComponentDetectionParameters()}`);
core.info(`CLI run complete. Output exists: ${fs.existsSync(this.outputPath)}`);
} catch (error: any) {
core.error(error);
core.info(`CLI run failed: ${error.message}`);
}
}

Expand All @@ -65,11 +64,34 @@ export default class ComponentDetection {
return parameters;
}

public static async getManifestsFromResults(): Promise<Manifest[] | undefined> {
public static async getManifestsFromResults(): Promise<any[] | undefined> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to any

core.info("Getting manifests from results");
// Dynamically import toolkit
const toolkit = await import('@github/dependency-submission-toolkit');
const PackageCache = toolkit.PackageCache;
const Manifest = toolkit.Manifest;
const Package = toolkit.Package;
// Define a dynamic class extending Package
class ComponentDetectionPackage extends Package {
id: any;
isDevelopmentDependency: any;
topLevelReferrers: any;
locationsFoundAt: any;
containerDetailIds: any;
containerLayerIds: any;
constructor(packageUrl: string, id: any, isDevelopmentDependency: any, topLevelReferrers: any, locationsFoundAt: any, containerDetailIds: any, containerLayerIds: any) {
super(packageUrl);
this.id = id;
this.isDevelopmentDependency = isDevelopmentDependency;
this.topLevelReferrers = topLevelReferrers;
this.locationsFoundAt = locationsFoundAt;
this.containerDetailIds = containerDetailIds;
this.containerLayerIds = containerLayerIds;
}
}
// Parse the result file and add the packages to the package cache
const packageCache = new PackageCache();
const packages: Array<ComponentDetectionPackage> = [];
const packages: Array<any> = [];

const results = await fs.readFileSync(this.outputPath, 'utf8');

Expand All @@ -78,16 +100,23 @@ export default class ComponentDetection {
const packageUrl = ComponentDetection.makePackageUrl(component.component.packageUrl);

if (!packageCache.hasPackage(packageUrl)) {
const pkg = new ComponentDetectionPackage(packageUrl, component.component.id,
component.isDevelopmentDependency, component.topLevelReferrers, component.locationsFoundAt, component.containerDetailIds, component.containerLayerIds);
const pkg = new ComponentDetectionPackage(
packageUrl,
component.component.id,
component.isDevelopmentDependency,
component.topLevelReferrers,
component.locationsFoundAt,
component.containerDetailIds,
component.containerLayerIds
);
packageCache.addPackage(pkg);
packages.push(pkg);
}
});

// Set the transitive dependencies
core.debug("Sorting out transitive dependencies");
packages.forEach(async (pkg: ComponentDetectionPackage) => {
packages.forEach(async (pkg: any) => {
pkg.topLevelReferrers.forEach(async (referrer: any) => {
const referrerPackage = packageCache.lookupPackage(ComponentDetection.makePackageUrl(referrer.packageUrl));
if (referrerPackage) {
Expand All @@ -97,26 +126,26 @@ export default class ComponentDetection {
});

// Create manifests
const manifests: Array<Manifest> = [];
const manifests: Array<any> = [];

// Check the locationsFoundAt for every package and add each as a manifest
packages.forEach(async (pkg: ComponentDetectionPackage) => {
packages.forEach(async (pkg: any) => {
pkg.locationsFoundAt.forEach(async (location: any) => {
if (!manifests.find((manifest: Manifest) => manifest.name == location)) {
if (!manifests.find((manifest: any) => manifest.name == location)) {
const manifest = new Manifest(location, location);
manifests.push(manifest);
}
if (pkg.topLevelReferrers.length == 0) {
manifests.find((manifest: Manifest) => manifest.name == location)?.addDirectDependency(pkg, ComponentDetection.getDependencyScope(pkg));
manifests.find((manifest: any) => manifest.name == location)?.addDirectDependency(pkg, ComponentDetection.getDependencyScope(pkg));
} else {
manifests.find((manifest: Manifest) => manifest.name == location)?.addIndirectDependency(pkg, ComponentDetection.getDependencyScope(pkg));
manifests.find((manifest: any) => manifest.name == location)?.addIndirectDependency(pkg, ComponentDetection.getDependencyScope(pkg));
}
});
});
return manifests;
}

private static getDependencyScope(pkg: ComponentDetectionPackage) {
private static getDependencyScope(pkg: any) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to any type isn't a great idea. Please can you update to a specific type

return pkg.isDevelopmentDependency ? 'development' : 'runtime'
}

Expand Down Expand Up @@ -150,6 +179,8 @@ export default class ComponentDetection {
if (ghesMode) {
githubToken = "";
}
// Dynamically import octokit
const { Octokit } = await import("octokit");
const octokit = new Octokit({ auth: githubToken, baseUrl: githubAPIURL, request: { fetch: fetch}, log: {
debug: core.debug,
info: core.info,
Expand Down Expand Up @@ -182,14 +213,6 @@ export default class ComponentDetection {
}
}

class ComponentDetectionPackage extends Package {

constructor(packageUrl: string, public id: string, public isDevelopmentDependency: boolean, public topLevelReferrers: [],
public locationsFoundAt: [], public containerDetailIds: [], public containerLayerIds: []) {
super(packageUrl);
}
}




Expand Down
5 changes: 2 additions & 3 deletions dist/componentDetection.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 31 additions & 16 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

19 changes: 7 additions & 12 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@
module.exports = {
transform: {
"^.+\\.tsx?$": "ts-jest",
"^.+\\.[t|j]sx?$": "babel-jest", // Use Babel for transforming JS and TS files
"^.+\\.mjs$": "jest-transform-stub", // Use the stub to handle ES modules
"^.+\\.[t|j]sx?$": "babel-jest"
},
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
"^octokit$": "<rootDir>/node_modules/octokit/dist-bundle/index.js",
"^@github/dependency-submission-toolkit$": "<rootDir>/node_modules/@github/dependency-submission-toolkit/dist/index.js",
"^octokit$": "<rootDir>/node_modules/octokit/dist-bundle/index.js"
// Removed toolkit mapping for ESM compatibility
},
extensionsToTreatAsEsm: [".ts"],
transformIgnorePatterns: ["/node_modules/(?!(octokit|\\@github\\/dependency-submission-toolkit)/)"], // Ensure octokit and @github/dependency-submission-toolkit are transformed
globals: {
"ts-jest": {
useESM: true,
},
},
};
transformIgnorePatterns: [
"/node_modules/(?!(octokit)/)" // Only octokit is transformed, toolkit is left as ESM
],
};
Loading