@@ -2,75 +2,61 @@ import fs from 'fs/promises';
22import pathlib from 'path' ;
33import * as core from '@actions/core' ;
44import { getExecOutput } from '@actions/exec' ;
5- import memoize from 'lodash/memoize.js' ;
6- import uniq from 'lodash/uniq.js' ;
75import { extractPkgsFromYarnLockV2 } from 'snyk-nodejs-lockfile-parser' ;
8- import { gitRoot } from './gitRoot.js' ;
6+ import { gitRoot } from '../gitRoot.js' ;
7+
8+ const packageNameRE = / ^ ( .+ ) @ .+ $ / ;
99
1010/**
11- * Parses and lockfile's contents and extracts all the different dependencies and
12- * versions
11+ * Lockfile specifications come in the form of package_name@resolution, but
12+ * we only want the package name. This function extracts that package name,
13+ * accounting for the fact that package names might start with '@'
1314 */
14- function processLockFileText ( contents : string ) {
15- const lockFile = extractPkgsFromYarnLockV2 ( contents ) ;
16- return Object . entries ( lockFile ) . reduce < Record < string , Set < string > > > ( ( res , [ key , { version } ] ) => {
17- const newKey = key . split ( '@' ) [ 0 ] ;
18-
19- if ( ! ( newKey in res ) ) {
20- res [ newKey ] = new Set ( ) ;
21- }
15+ export function extractPackageName ( raw : string ) {
16+ const match = packageNameRE . exec ( raw ) ;
17+ if ( ! match ) {
18+ throw new Error ( `Invalid package name: ${ raw } ` ) ;
19+ }
2220
23- res [ newKey ] . add ( version ) ;
24- return res ;
25- } , { } ) ;
21+ return match [ 1 ] ;
2622}
2723
2824/**
29- * Retrieves the contents of the lockfile on the master branch
25+ * Using `yarn why`, determine why the given package is present in the
26+ * lockfile.
3027 */
31- async function getMasterLockFile ( ) {
32- const { stdout, exitCode } = await getExecOutput (
33- 'git' ,
34- [
35- '--no-pager' ,
36- 'show' ,
37- 'origin/master:yarn.lock'
38- ] ,
39- ) ;
40-
28+ export async function getPackageReason ( pkg : string ) {
29+ const { stdout, stderr, exitCode } = await getExecOutput ( 'yarn why' , [ pkg , '-R' , '--json' ] , { silent : true } ) ;
4130 if ( exitCode !== 0 ) {
42- throw new Error ( 'git show exited with non-zero error-code' ) ;
31+ core . error ( stderr ) ;
32+ throw new Error ( `yarn why for '${ pkg } ' exited with non-zero exit code!` ) ;
4333 }
4434
45- return processLockFileText ( stdout ) ;
46- }
47-
48- /**
49- * Retrieves the contents of the lockfile in the repo
50- */
51- async function getCurrentLockFile ( ) {
52- const lockFilePath = pathlib . join ( gitRoot , 'yarn.lock' ) ;
53- const contents = await fs . readFile ( lockFilePath , 'utf-8' ) ;
54- return processLockFileText ( contents ) ;
35+ return stdout . trim ( ) . split ( '\n' ) . map ( each => {
36+ const entry = JSON . parse ( each ) . value ;
37+ return extractPackageName ( entry ) ;
38+ } ) ;
5539}
5640
5741/**
5842 * Determines the names of the packages that have changed versions
5943 */
60- async function getPackageDiffs ( ) {
44+ export async function getPackageDiffs ( ) {
6145 const [ currentLockFile , masterLockFile ] = await Promise . all ( [
6246 getCurrentLockFile ( ) ,
6347 getMasterLockFile ( )
6448 ] ) ;
6549
66- const packages = [ ] ;
50+ const packages = new Set < string > ( ) ;
51+
6752 for ( const [ depName , versions ] of Object . entries ( currentLockFile ) ) {
53+ if ( packages . has ( depName ) ) continue ;
54+
6855 if ( ! ( depName in masterLockFile ) ) {
6956 core . info ( `${ depName } in current lockfile, not in master lock file` ) ;
7057 continue ;
7158 }
7259
73- const args = depName . split ( '@' ) ;
7460 let needToAdd = false ;
7561 for ( const version of versions ) {
7662 if ( ! masterLockFile [ depName ] . has ( version ) ) {
@@ -86,65 +72,56 @@ async function getPackageDiffs() {
8672 }
8773 }
8874
89- if ( needToAdd ) {
90- if ( depName . startsWith ( '@' ) ) {
91- packages . push ( args [ 1 ] ) ;
92- } else {
93- packages . push ( args [ 0 ] ) ;
94- }
95- }
75+ if ( needToAdd ) packages . add ( depName ) ;
9676 }
9777
98- return uniq ( packages ) ;
78+ return packages ;
9979}
10080
10181/**
102- * Using `yarn why`, determine why the given package is present in the
103- * lockfile.
82+ * Retrieves the contents of the lockfile in the repo
10483 */
105- async function getPackageReason ( pkg : string ) {
106- const packageNameRE = / ^ ( @ ? s o u r c e a c a d e m y \/ .+ ) @ .+ $ / ;
84+ export async function getCurrentLockFile ( ) {
85+ const lockFilePath = pathlib . join ( gitRoot , 'yarn.lock' ) ;
86+ const contents = await fs . readFile ( lockFilePath , 'utf-8' ) ;
87+ return processLockFileText ( contents ) ;
88+ }
89+
90+ /**
91+ * Retrieves the contents of the lockfile on the master branch
92+ */
93+ export async function getMasterLockFile ( ) {
94+ const { stdout, exitCode } = await getExecOutput (
95+ 'git' ,
96+ [
97+ '--no-pager' ,
98+ 'show' ,
99+ 'origin/master:yarn.lock'
100+ ] ,
101+ { silent : true }
102+ ) ;
107103
108- const { stdout, stderr, exitCode } = await getExecOutput ( 'yarn why' , [ pkg , '-R' , '--json' ] , { silent : true } ) ;
109104 if ( exitCode !== 0 ) {
110- core . error ( stderr ) ;
111- throw new Error ( `yarn why for ${ pkg } exited with non-zero exit code!` ) ;
105+ throw new Error ( 'git show exited with non-zero error-code' ) ;
112106 }
113107
114- return stdout . trim ( ) . split ( '\n' ) . map ( each => {
115- const entry = JSON . parse ( each ) . value ;
116- const match = packageNameRE . exec ( entry ) ;
117- if ( ! match ) {
118- throw new Error ( 'failed to identify a package!' ) ;
119- }
120-
121- return match [ 1 ] ;
122- } ) ;
108+ return processLockFileText ( stdout ) ;
123109}
124110
125111/**
126- * Returns `true` if there are changes present in the given directory relative to
127- * the master branch\
128- * Used to determine, particularly for libraries, if running tests and tsc are necessary
112+ * Parses and lockfile's contents and extracts all the different dependencies and
113+ * versions
129114 */
130- export const hasLockFileChanged = memoize ( async ( ) => {
131- const { exitCode } = await getExecOutput (
132- 'git' ,
133- [ '--no-pager' , 'diff' , '--quiet' , 'origin/master' , '--' , 'yarn.lock' ] ,
134- {
135- failOnStdErr : false ,
136- ignoreReturnCode : true
115+ export function processLockFileText ( contents : string ) {
116+ const lockFile = extractPkgsFromYarnLockV2 ( contents ) ;
117+ return Object . entries ( lockFile ) . reduce < Record < string , Set < string > > > ( ( res , [ key , { version } ] ) => {
118+ const newKey = extractPackageName ( key ) ;
119+
120+ if ( ! ( newKey in res ) ) {
121+ res [ newKey ] = new Set ( ) ;
137122 }
138- ) ;
139- return exitCode !== 0 ;
140- } ) ;
141123
142- /**
143- * Gets a list of packages that have had some dependency of theirs
144- * change according to the lockfile but not their package.json
145- */
146- export async function getPackagesWithResolutionChanges ( ) {
147- const packages = await getPackageDiffs ( ) ;
148- const workspaces = await Promise . all ( packages . map ( getPackageReason ) ) ;
149- return uniq ( workspaces . flat ( ) ) ;
124+ res [ newKey ] . add ( version ) ;
125+ return res ;
126+ } , { } ) ;
150127}
0 commit comments