Skip to content

Commit b1d5064

Browse files
committed
Various fixes
Adds the originating branch name to the PR branch name so we can differentiate by originating PRs. FIX: don't await synchronous file append FIX: Add all errors to result FIX: Lockfile version retrieval fixes Remove dates from file names FIX: Don't push empty results to indirect deps FIX: Write entire file Clean data file before building data
1 parent 713bfbb commit b1d5064

File tree

4 files changed

+77
-79
lines changed

4 files changed

+77
-79
lines changed

.github/workflows/update.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ jobs:
4747
# github action which doesn't give us the option to output this data as
4848
# something like json which we can programmatically interogate.
4949
- name: Pipe our deps to a json file
50-
run: github-dependents-info --repo alphagov/govuk-frontend --sort stars --json > ./data/raw-deps.json
50+
run: github-dependents-info --repo alphagov/govuk-frontend --json > ./data/raw-deps.json
51+
52+
- name: Clean data files
53+
run: |
54+
rm -f ./data/filtered-data.json
5155
5256
- name: Build filtered data
5357
run: npm run build-filtered-data
@@ -56,9 +60,9 @@ jobs:
5660
uses: peter-evans/create-pull-request@v6
5761
with:
5862
token: ${{ secrets.GITHUB_TOKEN }}
59-
branch: update-filtered-data
63+
branch: update-filtered-data-${{ github.ref_name }}
6064
delete-branch: true
61-
commit-message: "Get latest filtered dependents data and rejections"
62-
title: "Get latest filtered dependents data and rejections"
65+
commit-message: "Get latest filtered dependents data and rejections - ${{ github.ref_name }}"
66+
title: "Get latest filtered dependents data and rejections - ${{ github.ref_name }}"
6367
body: "Generated automatically by github action"
6468
base: ${{ github.head_ref }}

build-filtered-data.mjs

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { appendFileSync } from 'fs'
1+
import { writeFileSync } from 'fs'
22
import { json2csv } from 'json-2-csv'
33
import { RequestError } from 'octokit'
44

@@ -10,16 +10,9 @@ import { Result } from './helpers/result.mjs'
1010

1111
import rawDeps from './data/raw-deps.json' with { type: 'json' }
1212

13-
// Set up date for file naming
14-
const currentDate = new Date()
15-
const yyyymmdd = currentDate.toISOString().split('T')[0]
16-
const timestamp = currentDate.getTime()
17-
1813
async function filterDeps () {
1914
const builtData = []
20-
const batchSize = 500
2115
const processedIndexes = []
22-
let batchCounter = 0
2316
console.log('Beginning dependency analysis...')
2417

2518
for (const repo of rawDeps.all_public_dependent_repos) {
@@ -28,7 +21,6 @@ async function filterDeps () {
2821
const repoData = await analyseRepo(repo)
2922
if (repoData) {
3023
builtData.push(repoData)
31-
batchCounter++
3224
}
3325
console.log(`${repo.name}: Analysis complete`)
3426

@@ -49,23 +41,11 @@ async function filterDeps () {
4941
continue
5042
}
5143
}
52-
if (batchCounter >= batchSize) {
53-
await writeBatchToFiles(builtData)
54-
builtData.length = 0
55-
batchCounter = 0
56-
}
5744
}
5845

5946
if (builtData.length > 0) {
60-
await writeBatchToFiles(builtData)
47+
writeToFiles(builtData, processedIndexes)
6148
}
62-
63-
const unprocessedItems = rawDeps.all_public_dependent_repos.filter((_, index) => !processedIndexes.includes(index))
64-
65-
await appendFileSync(
66-
`data/${yyyymmdd}-${timestamp}-unprocessedItems.json`,
67-
JSON.stringify(unprocessedItems, null, 2)
68-
)
6949
console.log("We're done!")
7050
}
7151

@@ -109,22 +89,28 @@ export async function analyseRepo (repo) {
10989
}
11090
} catch (error) {
11191
repoData.handleError(error)
112-
result.errorsThrown = repoData.errorsThrown
11392
}
93+
result.errorsThrown = repoData.errorsThrown
11494

11595
return result.getResult(repoData)
11696
}
11797

118-
async function writeBatchToFiles (builtData) {
98+
function writeToFiles (builtData, processedIndexes) {
11999
// Write JSON file
120-
await appendFileSync(
121-
`data/${yyyymmdd}-${timestamp}-filtered-data.json`,
122-
JSON.stringify(builtData, null, 2)
123-
)
100+
const jsonData = JSON.stringify(builtData, null, 2)
101+
writeFileSync('data/filtered-data.json', jsonData)
102+
124103
// Write CSV file
125104
const csv = json2csv(builtData)
126-
await appendFileSync(`data/${yyyymmdd}-${timestamp}-filtered-data.csv`, csv)
105+
writeFileSync('data/filtered-data.csv', csv)
127106
console.log('Data file updated with batch of entries')
107+
108+
// Write Unprocessed Items file
109+
const unprocessedItems = rawDeps.all_public_dependent_repos.filter((_, index) => !processedIndexes.includes(index))
110+
writeFileSync(
111+
'data/unprocessedItems.json',
112+
JSON.stringify(unprocessedItems, null, 2)
113+
)
128114
}
129115

130116
filterDeps()

helpers/repo-data.mjs

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import { parse as parseYaml } from 'yaml'
77
import yarnLock from '@yarnpkg/lockfile'
88
import JSON5 from 'json5'
99

10-
export class UnsupportedLockfileError extends Error { }
11-
1210
/**
1311
* The RepoData class is used to store and manipulate data about a repository, and serves as an abstraction
1412
* of the GitHub API.
@@ -31,7 +29,7 @@ export class RepoData {
3129
this.repoOwner = repoOwner
3230
this.repoName = repoName
3331
this.errorsThrown = []
34-
this.lockfileUnsupported = false
32+
this.rootLockfileVersion = null
3533
}
3634

3735
/**
@@ -295,22 +293,30 @@ export class RepoData {
295293
const results = []
296294
// We want to try the root directory in all cases
297295
if (!packageObjects.some(pkg => pkg.path === 'package.json')) {
298-
packageObjects.push({ path: '', content: {} })
296+
packageObjects.push({ path: 'package.json', content: {} })
299297
}
300298
for (const packageObject of packageObjects) {
301299
try {
302300
const lockfileType = this.getLockfileType(packageObject.path, tree)
303-
const lockfilePath = packageObject.path.replace('package.json', lockfileType)
304-
const lockfile = await this.getRepoFileContent(packageObject.path.replace('package.json', lockfileType))
305-
const lockfileObject = this.parseLockfile(lockfile, lockfileType)
306-
results.push(await this.getIndirectDependencyFromLockfile(lockfileObject, lockfileType, lockfilePath))
301+
if (lockfileType) {
302+
const lockfilePath = packageObject.path.replace('package.json', lockfileType)
303+
if (this.checkFileExists(lockfilePath, tree)) {
304+
const lockfile = await this.getRepoFileContent(packageObject.path.replace('package.json', lockfileType))
305+
const lockfileObject = this.parseLockfile(lockfile, lockfileType)
306+
const deps = await this.getIndirectDependencyFromLockfile(lockfileObject, lockfileType, lockfilePath)
307+
if (deps.length > 0) {
308+
results.push(deps)
309+
}
310+
}
311+
}
307312
} catch (error) {
308313
this.handleError(error)
309314
}
310315
}
311316
this.log(`${results.length} indirect dependencies found.`)
312-
313-
return results
317+
if (results.length > 0) {
318+
return results
319+
}
314320
}
315321

316322
/**
@@ -324,17 +330,28 @@ export class RepoData {
324330
this.log('disambiguating direct dependencies')
325331

326332
const results = []
327-
// We want to try the root directory in all cases
328-
if (!dependencies.some(dep => dep.packagePath === 'package.json')) {
329-
dependencies.push({ packagePath: '', specifiedVersion: '*' })
333+
// We want to fall back to the root lockfile if we can't find a local lockfile
334+
if (!this.rootLockfileVersion) {
335+
const rootLockfileType = this.getLockfileType('package.json', tree)
336+
if (rootLockfileType) {
337+
const rootLockfile = await this.getRepoFileContent(rootLockfileType)
338+
const rootLockfileObject = this.parseLockfile(rootLockfile, rootLockfileType)
339+
this.rootLockfileVersion = await this.getLockfileVersion(rootLockfileObject, rootLockfileType, rootLockfileType, tree)
340+
}
330341
}
342+
331343
for (const dependency of dependencies) {
332344
try {
333345
if (/^[~^*]/.test(dependency.specifiedVersion)) {
334346
const lockfileType = this.getLockfileType(dependency.packagePath, tree)
335-
const lockfile = await this.getRepoFileContent(dependency.packagePath.replace('package.json', lockfileType))
336-
const lockfileObject = this.parseLockfile(lockfile, lockfileType)
337-
dependency.actualVersion = await this.getLockfileVersion(lockfileObject, lockfileType, dependency.packagePath)
347+
const lockfilePath = dependency.packagePath.replace('package.json', lockfileType)
348+
if (this.checkFileExists(lockfilePath, tree)) {
349+
const lockfile = await this.getRepoFileContent(lockfilePath)
350+
const lockfileObject = this.parseLockfile(lockfile, lockfileType)
351+
dependency.actualVersion = await this.getLockfileVersion(lockfileObject, lockfileType, dependency.packagePath, tree)
352+
} else {
353+
dependency.actualVersion = this.rootLockfileVersion ? this.rootLockfileVersion : dependency.specifiedVersion
354+
}
338355
} else {
339356
dependency.actualVersion = dependency.specifiedVersion
340357
}
@@ -357,12 +374,13 @@ export class RepoData {
357374
* @param {string} path - The path to the package.
358375
* @returns {Promise<String>} The lockfile govuk-frontend semver string.
359376
*/
360-
async getLockfileVersion (lockfileObject, lockfileType, path) {
377+
async getLockfileVersion (lockfileObject, lockfileType, path, tree) {
361378
let version
379+
362380
if (lockfileType === 'package-lock.json') {
363381
version =
364-
lockfileObject?.packages?.['node_modules/govuk-frontend']?.version ||
365-
lockfileObject?.dependencies?.['govuk-frontend']?.version
382+
lockfileObject?.packages?.['node_modules/govuk-frontend']?.version ||
383+
lockfileObject?.dependencies?.['govuk-frontend']?.version
366384
} else if (lockfileType === 'yarn.lock') {
367385
const dependencyKey = Object.keys(lockfileObject).find(key => key.startsWith('govuk-frontend@'))
368386
version = lockfileObject[dependencyKey]?.version
@@ -456,14 +474,14 @@ export class RepoData {
456474
*
457475
* @returns {string} - the lockfile type
458476
*/
459-
getLockfileType (packagePath = '', tree) {
477+
getLockfileType (packagePath = 'package.json', tree) {
460478
let lockfileType
461-
if (this.checkFileExists('package-lock.json', tree) || this.checkFileExists(packagePath.replace('package.json', 'package-lock.json'), tree)) {
479+
if (this.checkFileExists(packagePath.replace('package.json', 'package-lock.json'), tree)) {
462480
lockfileType = 'package-lock.json'
463-
} else if (this.checkFileExists('yarn.lock', tree) || this.checkFileExists(packagePath.replace('package.json', 'yarn.lock'), tree)) {
481+
} else if (this.checkFileExists(packagePath.replace('package.json', 'yarn.lock'), tree)) {
464482
lockfileType = 'yarn.lock'
465483
} else {
466-
throw new UnsupportedLockfileError()
484+
return null
467485
}
468486
return lockfileType
469487
}
@@ -476,9 +494,6 @@ export class RepoData {
476494
* @throws {Error} - If the error is not an expected type
477495
*/
478496
handleError (error) {
479-
if (error instanceof UnsupportedLockfileError) {
480-
this.lockfileUnsupported = true
481-
}
482497
this.log(`${error.message}. Added to result.`, 'error')
483498
this.errorsThrown.push(error.toString())
484499
}

helpers/repo-data.test.mjs

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect, vi } from 'vitest'
22
import * as yarnLock from '@yarnpkg/lockfile'
3-
import { RepoData, UnsupportedLockfileError } from './repo-data.mjs'
3+
import { RepoData } from './repo-data.mjs'
44
import {
55
getFileContent,
66
getTree,
@@ -283,15 +283,15 @@ describe('RepoData', () => {
283283
const repoData = new RepoData(repoOwner, repoName)
284284
const tree = [{ path: 'package-lock.json' }]
285285

286-
const lockfileType = repoData.getLockfileType('', tree)
286+
const lockfileType = repoData.getLockfileType('package.json', tree)
287287
expect(lockfileType).toBe('package-lock.json')
288288
})
289289

290290
it('should return yarn.lock if it exists', () => {
291291
const repoData = new RepoData(repoOwner, repoName)
292292
const tree = [{ path: 'yarn.lock' }]
293293

294-
const lockfileType = repoData.getLockfileType('', tree)
294+
const lockfileType = repoData.getLockfileType('package.json', tree)
295295
expect(lockfileType).toBe('yarn.lock')
296296
})
297297

@@ -311,11 +311,12 @@ describe('RepoData', () => {
311311
expect(lockfileType).toBe('yarn.lock')
312312
})
313313

314-
it('should throw Error if no supported lockfile exists', () => {
314+
it('should return null if no supported lockfile exists', () => {
315315
const repoData = new RepoData(repoOwner, repoName)
316316
const tree = [{ path: 'other-file.lock' }]
317317

318-
expect(() => repoData.getLockfileType('', tree)).toThrow(UnsupportedLockfileError)
318+
const lockfileType = repoData.getLockfileType('package.json', tree)
319+
expect(lockfileType).toBe(null)
319320
})
320321
})
321322

@@ -478,6 +479,7 @@ describe('RepoData', () => {
478479
repoData.getLockfileType = vi.fn().mockReturnValue('package-lock.json')
479480
repoData.getRepoFileContent = vi.fn().mockResolvedValue({ data: JSON.stringify(lockfileContent.data) })
480481
repoData.parseLockfile = vi.fn().mockReturnValue(lockfileContent.data)
482+
repoData.checkFileExists = vi.fn().mockReturnValue(true)
481483

482484
const result = await repoData.getIndirectDependencies(packageObjects)
483485
expect(result).toEqual([[{
@@ -504,6 +506,7 @@ describe('RepoData', () => {
504506
}
505507
repoData.getLockfileType = vi.fn().mockReturnValue('yarn.lock')
506508
repoData.getRepoFileContent = vi.fn().mockResolvedValue(lockfileContent)
509+
repoData.checkFileExists = vi.fn().mockReturnValue(true)
507510

508511
const result = await repoData.getIndirectDependencies(packageObjects)
509512
expect(result).toEqual([[{
@@ -522,9 +525,10 @@ describe('RepoData', () => {
522525
repoData.getLockfileType = vi.fn().mockReturnValue('package-lock.json')
523526
repoData.getRepoFileContent = vi.fn().mockRejectedValue(new Error('Failed to fetch lockfile content'))
524527
repoData.handleError = vi.fn()
528+
repoData.checkFileExists = vi.fn().mockReturnValue(true)
525529

526530
const result = await repoData.getIndirectDependencies(packageObjects)
527-
expect(result).toEqual([])
531+
expect(result).toEqual(undefined)
528532
expect(repoData.handleError).toHaveBeenCalledWith(new Error('Failed to fetch lockfile content'))
529533
})
530534
})
@@ -546,6 +550,7 @@ describe('RepoData', () => {
546550
repoData.getLockfileType = vi.fn().mockReturnValue('package-lock.json')
547551
repoData.getRepoFileContent = vi.fn().mockResolvedValue(JSON.stringify(lockfileContent))
548552
repoData.parseLockfile = vi.fn().mockReturnValue(lockfileContent)
553+
repoData.checkFileExists = vi.fn().mockReturnValue(true)
549554

550555
const result = await repoData.disambiguateDependencies(dependencies, tree)
551556
expect(result).toEqual([
@@ -569,6 +574,7 @@ describe('RepoData', () => {
569574
repoData.parseLockfile = vi.fn().mockReturnValue({
570575
'govuk-frontend@^3.11.0': { version: '3.11.0' }
571576
})
577+
repoData.checkFileExists = vi.fn().mockReturnValue(true)
572578

573579
const result = await repoData.disambiguateDependencies(dependencies)
574580
expect(result).toEqual([
@@ -581,24 +587,11 @@ describe('RepoData', () => {
581587
const dependencies = [
582588
{ packagePath: 'package.json', frontendVersion: '3.11.0' }
583589
]
590+
repoData.checkFileExists = vi.fn().mockReturnValue(true)
584591

585592
const result = await repoData.disambiguateDependencies(dependencies)
586593
expect(result).toEqual(dependencies)
587594
})
588-
589-
it('should handle errors when fetching lockfile content', async () => {
590-
const repoData = new RepoData(repoOwner, repoName)
591-
const dependencies = [
592-
{ packagePath: 'package.json', specifiedVersion: '^3.11.0' }
593-
]
594-
repoData.getLockfileType = vi.fn().mockReturnValue('package-lock.json')
595-
repoData.getRepoFileContent = vi.fn().mockRejectedValue(new Error('Failed to fetch lockfile content'))
596-
repoData.handleError = vi.fn()
597-
598-
const result = await repoData.disambiguateDependencies(dependencies)
599-
expect(result).toEqual([])
600-
expect(repoData.handleError).toHaveBeenCalledWith(new Error('Failed to fetch lockfile content'))
601-
})
602595
})
603596

604597
describe('getIndirectDependencyFromLockfile', () => {

0 commit comments

Comments
 (0)