Skip to content

Commit 43d6089

Browse files
authored
add internal lock parser for extensibility and performance (#49)
1 parent b6e17ea commit 43d6089

File tree

8 files changed

+108
-25
lines changed

8 files changed

+108
-25
lines changed

.eslintrc.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
"sourceType": "module"
1616
},
1717
"rules": {
18-
"indent": ["error", 2],
1918
"no-var": "error",
2019
"prefer-arrow-callback": "error",
2120
"prefer-const": "error",

dist/index.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
"dependencies": {
1515
"@actions/core": "^1.6.0",
1616
"@actions/github": "^5.0.1",
17-
"@yarnpkg/lockfile": "^1.1.0",
1817
"js-base64": "^3.7.2",
1918
"markdown-table": "^3.0.2",
2019
"semver": "^7.3.7"
@@ -23,6 +22,7 @@
2322
"@babel/core": "^7.17.9",
2423
"@babel/preset-env": "^7.16.11",
2524
"@vercel/ncc": "^0.33.4",
25+
"@yarnpkg/lockfile": "^1.1.0",
2626
"babel-jest": "^27.5.1",
2727
"eslint": "^8.13.0",
2828
"eslint-config-prettier": "^8.5.0",

src/action.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
const { debug, getBooleanInput, getInput, setFailed, warning } = require('@actions/core');
22
const { context, getOctokit } = require('@actions/github');
3-
const lockfile = require('@yarnpkg/lockfile');
43
const fs = require('fs');
54
const { Base64 } = require('js-base64');
65
const path = require('path');
76

8-
const { STATUS, countStatuses, diffLocks } = require('./utils');
7+
const { STATUS, countStatuses, diffLocks, parseLock } = require('./utils');
98
const { createTable, createSummary } = require('./comment');
109

1110
const getCommentId = async (octokit, oktokitParams, issueNumber, commentHeader) => {
@@ -58,7 +57,7 @@ const run = async () => {
5857
}
5958

6059
const content = fs.readFileSync(lockPath, { encoding: 'utf8' });
61-
const updatedLock = lockfile.parse(content);
60+
const updatedLock = parseLock(content);
6261

6362
const oktokitParams = { owner, repo };
6463
debug('Oktokit params: ' + JSON.stringify(oktokitParams));
@@ -88,7 +87,7 @@ const run = async () => {
8887
throw Error('💥 Cannot fetch repository base lock file, aborting!');
8988
}
9089

91-
const baseLock = lockfile.parse(Base64.decode(baseLockData.data.content));
90+
const baseLock = parseLock(Base64.decode(baseLockData.data.content));
9291
const lockChanges = diffLocks(baseLock, updatedLock);
9392
const lockChangesCount = Object.keys(lockChanges).length;
9493

src/utils.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,67 @@ const formatLockEntry = obj =>
3636
})
3737
);
3838

39+
export const parseLock = content => {
40+
const lines = content.replaceAll('\r', '').replaceAll('"', '').split('\n');
41+
42+
if (!lines[1].includes('v1')) {
43+
return {
44+
type: 'error',
45+
object: {}
46+
};
47+
}
48+
49+
const cleanedLines = lines.slice(4);
50+
const maxIndex = cleanedLines.length - 1;
51+
52+
const entryChunks = [];
53+
cleanedLines.reduce((previousValue, currentValue, currentIndex) => {
54+
if (currentValue !== '' && currentIndex !== maxIndex) {
55+
return [...previousValue, currentValue];
56+
} else {
57+
entryChunks.push([...previousValue, currentValue]);
58+
return [];
59+
}
60+
}, []);
61+
62+
const result = entryChunks
63+
.map(entryLines => {
64+
const keys = entryLines[0].replaceAll(':', '').split(',');
65+
66+
const dependencies = entryLines[4]
67+
? Object.assign(
68+
{},
69+
...entryLines.splice(5).map(dependencyLine => {
70+
const parts = dependencyLine.trim().split(' ');
71+
if (parts.length === 2) {
72+
return {
73+
[parts[0]]: parts[1]
74+
};
75+
} else {
76+
return {};
77+
}
78+
})
79+
)
80+
: undefined;
81+
82+
const entryObject = {
83+
version: entryLines[1].split('version ')[1],
84+
resolved: entryLines[2].split('resolved ')[1],
85+
integrity: entryLines[3].split('integrity ')[1],
86+
dependencies
87+
};
88+
89+
return Object.assign({}, ...keys.map(key => ({ [key.trim()]: entryObject })));
90+
})
91+
.filter(Boolean);
92+
93+
// Retain the official parser result structure for a while
94+
return {
95+
type: 'success',
96+
object: Object.assign({}, ...result)
97+
};
98+
};
99+
39100
export const diffLocks = (previous, current) => {
40101
const changes = {};
41102
const previousPackages = formatLockEntry(previous);

tests/performance/parse.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const lockfile = require('@yarnpkg/lockfile');
2+
3+
const { getTestLockContent } = require('../testUtils');
4+
const { parseLock } = require('../../src/utils');
5+
6+
test('naive performance test', () => {
7+
const contentA = getTestLockContent('downgrade', 'a.lock');
8+
const contentB = getTestLockContent('downgrade', 'b.lock');
9+
10+
console.time('@yarnpkg/lockfile');
11+
const start = performance.now();
12+
13+
lockfile.parse(contentA);
14+
lockfile.parse(contentB);
15+
16+
const end = performance.now();
17+
console.timeEnd('@yarnpkg/lockfile');
18+
19+
console.time('Internal parser');
20+
const internalStart = performance.now();
21+
22+
parseLock(contentA);
23+
parseLock(contentB);
24+
25+
const internalEnd = performance.now();
26+
console.timeEnd('Internal parser');
27+
28+
expect(end - start).toBeGreaterThan(internalEnd - internalStart);
29+
});

tests/testUtils.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
export const getTestLockContent = (testName, filename) => {
5+
return fs.readFileSync(path.resolve(process.cwd(), './tests/unit/', testName, filename), {
6+
encoding: 'utf8'
7+
});
8+
};

tests/unit/unit.test.js

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
1-
const lockfile = require('@yarnpkg/lockfile');
2-
const fs = require('fs');
3-
const path = require('path');
4-
5-
const { diffLocks, STATUS, countStatuses } = require('../../src/utils');
6-
7-
const getTestLockContent = (testName, filename) => {
8-
const content = fs.readFileSync(
9-
path.resolve(process.cwd(), './tests/unit/', testName, filename),
10-
{
11-
encoding: 'utf8'
12-
}
13-
);
14-
return content;
15-
};
1+
const { diffLocks, STATUS, countStatuses, parseLock } = require('../../src/utils');
2+
const { getTestLockContent } = require('../testUtils');
163

174
test('no downgrade detected', () => {
185
const contentA = getTestLockContent('downgrade', 'a.lock');
196
const contentB = getTestLockContent('downgrade', 'b.lock');
207

21-
const result = diffLocks(lockfile.parse(contentA), lockfile.parse(contentB));
8+
const result = diffLocks(parseLock(contentA), parseLock(contentB));
229

2310
expect(Object.keys(result).length).toBe(52);
2411

@@ -32,7 +19,7 @@ test('no downgrade detected, multiple cases', () => {
3219
const contentA = getTestLockContent('downgrade-complex', 'a.lock');
3320
const contentB = getTestLockContent('downgrade-complex', 'b.lock');
3421

35-
const result = diffLocks(lockfile.parse(contentA), lockfile.parse(contentB));
22+
const result = diffLocks(parseLock(contentA), parseLock(contentB));
3623

3724
expect(Object.keys(result).length).toBe(389);
3825

0 commit comments

Comments
 (0)