@@ -16,55 +16,142 @@ import dotenv from 'dotenv'
1616import { Context } from '@actions/github/lib/context'
1717import { unmockedModulePathPatterns } from './jest.config'
1818dotenv . config ( ) ;
19+ export default class ComponentDetection {
20+ private componentDetectionPath = './component-detection' ;
21+ private outputPath = './output.json' ;
22+
23+ // This is the default entry point for this class.
24+ static async scanAndGetManifests ( path : string ) : Promise < Manifest [ ] | undefined > {
25+ await this . downloadLatestRelease ( ) ;
26+ await this . runComponentDetection ( path ) ;
27+ return await this . getManifestsFromResults ( ) ;
28+ }
29+ // Get the latest release from the component-detection repo, download the tarball, and extract it
30+ private static async downloadLatestRelease ( ) {
31+ try {
32+ const downloadURL = await getLatestReleaseURL ( ) ;
33+ const blob = await ( await fetch ( new URL ( downloadURL ) ) ) . blob ( ) ;
34+ const arrayBuffer = await blob . arrayBuffer ( ) ;
35+ const buffer = Buffer . from ( arrayBuffer ) ;
36+
37+ // Write the blob to a file
38+ await fs . writeFile ( componentDetectionPath , buffer , { mode : 0o777 , flag : 'w' } ,
39+ ( err : any ) => {
40+ if ( err ) {
41+ core . error ( err ) ;
42+ }
43+ } ) ;
44+ } catch ( error : any ) {
45+ core . error ( error ) ;
46+ }
47+ }
1948
20- export const componentDetectionPath = './component-detection' ;
21- const outputPath = './output.json' ;
22- dependencyGraphs: {
23- manifest : {
24- graph : {
25- dependencies : [ ] ,
26- } ,
27- explicitlyReferencedComponentIds : [ ] ,
28- developmentDependencies : [ ] ,
29- dependencies : [ ]
30- }
31- } ,
32- componentsFound : [
33- {
34- locationsFoundAt : [
35- filePath : string
36- ] ,
37- component : {
38- name : string ,
39- version : string ,
40- hash : string ,
41- author : string ,
42- type : string ,
43- id : string ,
44- packageUrl : {
45- Scheme : string ,
46- Type : string ,
47- Namespace : string ,
48- Name : string ,
49- Version : string ,
50- Qualifiers : string ,
51- Subpath : string
52- } ,
53- } ,
54- detectorId : string ,
55- isDevelopmentDependency : boolean ,
56- dependencyScope : string ,
57- topLevelReferrers : [ ] ,
58- containerDetailIds : [ ] ,
59- containerLayerIds : [ ]
49+ // Run the component-detection CLI on the path specified
50+ private static async runComponentDetection ( path : string ) {
51+ try {
52+ await exec . exec ( `${ componentDetectionPath } scan --SourceDirectory ${ path } --ManifestFile ${ outputPath } ` ) ;
53+ } catch ( error : any ) {
54+ core . error ( error ) ;
55+ }
56+ }
57+
58+ private static async getManifestsFromResults ( ) : Promise < Manifest [ ] | undefined > {
59+
60+ try {
61+ // Parse the result file and add the packages to the package cache
62+ const packageCache = new PackageCache ( ) ;
63+ const packages : Array < ComponentDetectionPackage > = [ ] ;
64+
65+ const results = await fs . readFileSync ( outputPath , 'utf8' ) ;
66+
67+ var json : any = JSON . parse ( results ) ;
68+ json . componentsFound . forEach ( async ( component : any ) => {
69+ const packageUrl = makePackageUrl ( component . component . packageUrl ) ;
70+
71+ if ( ! packageCache . hasPackage ( packageUrl ) ) {
72+ const pkg = new ComponentDetectionPackage ( packageUrl , component . component . id ,
73+ component . isDevelopmentDependency , component . topLevelReferrers , component . locationsFoundAt , component . containerDetailIds , component . containerLayerIds ) ;
74+ packageCache . addPackage ( pkg ) ;
75+ packages . push ( pkg ) ;
76+ }
77+ } ) ;
78+
79+ // Set the transitive dependencies
80+ packages . forEach ( async ( pkg : ComponentDetectionPackage ) => {
81+ pkg . toplevelReferrers . forEach ( async ( referrer : any ) => {
82+ const referrerPackage = packageCache . lookupPackage ( makePackageUrl ( referrer . packageUrl ) ) ;
83+ if ( referrerPackage ) {
84+ referrerPackage . dependsOn ( pkg ) ;
85+ }
86+ } ) ;
87+ } ) ;
88+
89+ // Create manifests
90+ const manifests : Array < Manifest > = [ ] ;
91+
92+ // Check the locationsFoundAt for every package and add each as a manifest
93+ packages . forEach ( async ( pkg : ComponentDetectionPackage ) => {
94+ pkg . locationsFoundAt . forEach ( async ( location : any ) => {
95+ if ( ! manifests [ location . filePath ] ) {
96+ const manifest = new Manifest ( location . filePath , location . filePath ) ;
97+ manifests . push ( manifest ) ;
98+ }
99+ if ( pkg . toplevelReferrers . length == 0 ) {
100+ manifests . find ( ( manifest : Manifest ) => manifest . file == location . filePath ) ?. addDirectDependency ( pkg , getDependencyScope ( pkg ) ) ;
101+ } else {
102+ manifests . find ( ( manifest : Manifest ) => manifest . file == location . filePath ) ?. addIndirectDependency ( pkg , getDependencyScope ( pkg ) ) ; }
103+ } ) ;
104+ } ) ;
105+ core . debug ( JSON . stringify ( manifests ) ) ;
106+ return manifests ;
107+ } catch ( error : any ) {
108+ core . error ( error ) ;
109+ return undefined ;
110+ }
111+ }
112+
113+ private static getDependencyScope ( pkg : ComponentDetectionPackage ) {
114+ return pkg . isDevelopmentDependency ? 'development' : 'runtime'
115+ }
116+
117+ private static makePackageUrl ( packageUrlJson : any ) : string {
118+ var packageUrl = `${ packageUrlJson . Scheme } :${ packageUrlJson . Type } /` ;
119+ if ( packageUrlJson . Namespace ) {
120+ packageUrl += `${ packageUrlJson . Namespace . replace ( "@" , "%40" ) } /` ;
121+ }
122+ packageUrl += `${ packageUrlJson . Name . replace ( "@" , "%40" ) } ` ;
123+ if ( packageUrlJson . Version ) {
124+ packageUrl += `@${ packageUrlJson . Version } ` ;
125+ }
126+ if ( packageUrlJson . Qualifiers ) {
127+ packageUrl += `?${ packageUrlJson . Qualifiers } ` ;
128+ }
129+ return packageUrl ;
130+ }
131+
132+ private static getLatestReleaseURL ( ) : Promise < string > {
133+ const githubToken = core . getInput ( 'token' ) || process . env . GITHUB_TOKEN2 || "" ;
134+ const octokit = github . getOctokit ( githubToken ) ;
135+ const owner = "microsoft" ;
136+ const repo = "component-detection" ;
137+
138+ const latestRelease = await octokit . rest . repos . getLatestRelease ( {
139+ owner, repo
140+ } ) ;
141+
142+ var downloadURL : string = "" ;
143+ latestRelease . data . assets . forEach ( ( asset : any ) => {
144+ if ( asset . name === "component-detection-linux-x64" ) {
145+ downloadURL = asset . browser_download_url ;
60146 }
61- ] ,
62- detectorsInScan : [ ] ,
63- sourceDirectory : string ,
64- } ;
65- * /
147+ } ) ;
148+
149+ return downloadURL ;
150+ }
151+ }
66152
67153class ComponentDetectionPackage extends Package {
154+
68155 constructor ( packageUrl : string , id : string , isDevelopmentDependency :boolean , topLevelReferrers : [ ] ,
69156 locationsFoundAt : [ ] , containerDetailIds : [ ] , containerLayerIds : [ ] ) {
70157 super ( packageUrl ) ;
@@ -84,129 +171,11 @@ class ComponentDetectionPackage extends Package {
84171}
85172
86173
87- // Get the latest release from the component-detection repo, download the tarball, and extract it
88- export async function downloadLatestRelease ( ) {
89- try {
90- const downloadURL = await getLatestReleaseURL ( ) ;
91- const blob = await ( await fetch ( new URL ( downloadURL ) ) ) . blob ( ) ;
92- const arrayBuffer = await blob . arrayBuffer ( ) ;
93- const buffer = Buffer . from ( arrayBuffer ) ;
94-
95- // Write the blob to a file
96- await fs . writeFile ( componentDetectionPath , buffer , { mode : 0o777 , flag : 'w' } ,
97- ( err : any ) => {
98- if ( err ) {
99- core . error ( err ) ;
100- }
101- } ) ;
102- } catch ( error : any ) {
103- core . error ( error ) ;
104- }
105- }
106-
107- // Run the component-detection CLI on the path specified
108- export async function runComponentDetection ( path : string ) {
109- try {
110- await exec . exec ( `${ componentDetectionPath } scan --SourceDirectory ${ path } --ManifestFile ${ outputPath } ` ) ;
111- } catch ( error : any ) {
112- core . error ( error ) ;
113- }
114- }
115-
116-
117-
118- export async function getManifestsFromResults ( ) : Promise < Manifest [ ] | undefined > {
119-
120- try {
121- // Parse the result file and add the packages to the package cache
122- const packageCache = new PackageCache ( ) ;
123- const packages : Array < ComponentDetectionPackage > = [ ] ;
124-
125- const results = await fs . readFileSync ( outputPath , 'utf8' ) ;
126-
127- var json : any = JSON . parse ( results ) ;
128- json . componentsFound . forEach ( async ( component : any ) => {
129- const packageUrl = makePackageUrl ( component . component . packageUrl ) ;
130-
131- if ( ! packageCache . hasPackage ( packageUrl ) ) {
132- const pkg = new ComponentDetectionPackage ( packageUrl , component . component . id ,
133- component . isDevelopmentDependency , component . topLevelReferrers , component . locationsFoundAt , component . containerDetailIds , component . containerLayerIds ) ;
134- packageCache . addPackage ( pkg ) ;
135- packages . push ( pkg ) ;
136- }
137- } ) ;
138-
139- // Set the transitive dependencies
140- packages . forEach ( async ( pkg : ComponentDetectionPackage ) => {
141- pkg . toplevelReferrers . forEach ( async ( referrer : any ) => {
142- const referrerPackage = packageCache . lookupPackage ( makePackageUrl ( referrer . packageUrl ) ) ;
143- if ( referrerPackage ) {
144- referrerPackage . dependsOn ( pkg ) ;
145- }
146- } ) ;
147- } ) ;
148-
149- // Create manifests
150- const manifests : Array < Manifest > = [ ] ;
151-
152- // Check the locationsFoundAt for every package and add each as a manifest
153- packages . forEach ( async ( pkg : ComponentDetectionPackage ) => {
154- pkg . locationsFoundAt . forEach ( async ( location : any ) => {
155- if ( ! manifests [ location . filePath ] ) {
156- const manifest = new Manifest ( location . filePath , location . filePath ) ;
157- manifests . push ( manifest ) ;
158- }
159- if ( pkg . toplevelReferrers . length == 0 ) {
160- manifests . find ( ( manifest : Manifest ) => manifest . file == location . filePath ) ?. addDirectDependency ( pkg , getDependencyScope ( pkg ) ) ;
161- } else {
162- manifests . find ( ( manifest : Manifest ) => manifest . file == location . filePath ) ?. addIndirectDependency ( pkg , getDependencyScope ( pkg ) ) ; }
163- } ) ;
164- } ) ;
165- core . debug ( JSON . stringify ( manifests ) ) ;
166- return manifests ;
167- } catch ( error : any ) {
168- core . error ( error ) ;
169- return undefined ;
170- }
171- }
172174
173175
174176
175- function getDependencyScope ( pkg : ComponentDetectionPackage ) {
176- return pkg . isDevelopmentDependency ? 'development' : 'runtime'
177- }
178177
179- function makePackageUrl ( packageUrlJson : any ) : string {
180- var packageUrl = `${ packageUrlJson . Scheme } :${ packageUrlJson . Type } /` ;
181- if ( packageUrlJson . Namespace ) {
182- packageUrl += `${ packageUrlJson . Namespace . replace ( "@" , "%40" ) } /` ;
183- }
184- packageUrl += `${ packageUrlJson . Name . replace ( "@" , "%40" ) } ` ;
185- if ( packageUrlJson . Version ) {
186- packageUrl += `@${ packageUrlJson . Version } ` ;
187- }
188- if ( packageUrlJson . Qualifiers ) {
189- packageUrl += `?${ packageUrlJson . Qualifiers } ` ;
190- }
191- return packageUrl ;
192- }
193178
194- async function getLatestReleaseURL ( ) : Promise < string > {
195- const githubToken = core . getInput ( 'token' ) || process . env . GITHUB_TOKEN2 || "" ;
196- const octokit = github . getOctokit ( githubToken ) ;
197- const owner = "microsoft" ;
198- const repo = "component-detection" ;
199179
200- const latestRelease = await octokit . rest . repos . getLatestRelease ( {
201- owner, repo
202- } ) ;
203180
204- var downloadURL : string = "" ;
205- latestRelease . data . assets . forEach ( ( asset : any ) => {
206- if ( asset . name === "component-detection-linux-x64" ) {
207- downloadURL = asset . browser_download_url ;
208- }
209- } ) ;
210181
211- return downloadURL ;
212- }
0 commit comments