Skip to content

Commit a65f106

Browse files
committed
feat: implement basic github action detection from repository
1 parent 1134fe0 commit a65f106

File tree

5 files changed

+174
-6
lines changed

5 files changed

+174
-6
lines changed

lib/github-actions/index.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict';
2+
3+
const Nv = require('@pkgjs/nv');
4+
5+
exports.detect = async (meta) => {
6+
7+
const files = await meta.loadFolder('.github/workflows');
8+
const rawSet = new Set();
9+
10+
for (const file of files) {
11+
12+
if (!file.endsWith('.yaml') && !file.endsWith('.yml')) {
13+
continue;
14+
}
15+
16+
const workflow = await meta.loadFile(`.github/workflows/${file}`, { yaml: true });
17+
18+
for (const job of Object.values(workflow.jobs)) {
19+
20+
const nodeSteps = job.steps.filter(({ uses }) => uses && uses.startsWith('actions/setup-node'));
21+
for (const step of nodeSteps) {
22+
const nodeVersion = step.with && step.with['node-version'];
23+
24+
if (!nodeVersion) {
25+
// @todo - no node version defined - use default? what is the default?
26+
continue;
27+
}
28+
29+
const matrixMatch = nodeVersion.match(/^\${{\s+matrix.(?<matrixVarName>.*)\s+}}$/);
30+
if (matrixMatch) {
31+
const matrix = job.strategy.matrix[matrixMatch.groups.matrixVarName];
32+
33+
for (const version of matrix) {
34+
rawSet.add(version);
35+
}
36+
37+
continue;
38+
}
39+
40+
const envMatch = nodeVersion.match(/^\${{\s+env.(?<envVarName>.*)\s+}}$/);
41+
if (envMatch) {
42+
rawSet.add(workflow.env[envMatch.groups.envVarName]);
43+
44+
continue;
45+
}
46+
47+
rawSet.add(nodeVersion);
48+
}
49+
}
50+
}
51+
52+
const raw = [...rawSet];
53+
54+
if (!raw.length) {
55+
return '';
56+
}
57+
58+
const resolved = {};
59+
60+
for (const version of raw) {
61+
62+
const nv = await Nv(version);
63+
64+
if (!nv.length) {
65+
resolved[version] = false;
66+
}
67+
else {
68+
resolved[version] = nv[nv.length - 1].version;
69+
}
70+
}
71+
72+
return { githubActions: { raw, resolved } };
73+
};

lib/loader/path.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ exports.create = async (path) => {
2525

2626
return simpleGit.revparse(['HEAD']);
2727
},
28+
loadFolder: () => {
29+
30+
// @todo
31+
return [];
32+
},
2833
loadFile: (filename, options = {}) => {
2934

3035
const fullPath = Path.join(path, filename);

lib/loader/repository.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,48 @@ exports.create = (repository) => {
3131

3232
return head;
3333
},
34+
loadFolder: async (path) => {
35+
36+
if (parsedRepository.source !== 'github.com') {
37+
throw new Error('Only github.com paths supported, feel free to PR at https://github.com/pkgjs/detect-node-support');
38+
}
39+
40+
const resource = `${parsedRepository.full_name}:${path}@HEAD`;
41+
Logger.log(['loader'], 'Loading: %s', resource);
42+
43+
const octokit = OctokitWrapper.create();
44+
45+
try {
46+
47+
let result;
48+
if (internals.cache.has(resource)) {
49+
Logger.log(['loader'], 'From cache: %s', resource);
50+
result = internals.cache.get(resource);
51+
}
52+
else {
53+
result = await octokit.repos.getContent({
54+
owner: parsedRepository.owner,
55+
repo: parsedRepository.name,
56+
path
57+
});
58+
}
59+
60+
internals.cache.set(resource, result);
61+
62+
Logger.log(['loader'], 'Loaded: %s', resource);
63+
64+
return result.data.map(({ name }) => name);
65+
}
66+
catch (err) {
67+
68+
if (err.status === 404) {
69+
return []; // @todo: is this right?
70+
}
71+
72+
Logger.error(['loader'], 'Failed to load: %s', resource);
73+
throw err;
74+
}
75+
},
3476
loadFile: async (filename, options = {}) => {
3577

3678
if (parsedRepository.source !== 'github.com') {

lib/package.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const Fs = require('fs');
44
const { URL } = require('url');
55

6+
const GithubActions = require('./github-actions');
67
const Engines = require('./engines');
78
const Loader = require('./loader');
89
const Travis = require('./travis');
@@ -43,14 +44,15 @@ exports.detect = async (what) => {
4344

4445
const { path, repository, packageName } = internals.what(what);
4546

46-
const { loadFile, getCommit } = await Loader.create({ path, repository, packageName });
47+
const { loadFile, loadFolder, getCommit } = await Loader.create({ path, repository, packageName });
4748

4849
const packageJson = await loadFile('package.json', { json: true });
4950

5051
const meta = {
5152
packageJson,
5253
getCommit,
53-
loadFile
54+
loadFile,
55+
loadFolder
5456
};
5557

5658
const result = {};
@@ -60,10 +62,11 @@ exports.detect = async (what) => {
6062
result.commit = await meta.getCommit();
6163
result.timestamp = Date.now();
6264

63-
const travis = await Travis.detect(meta);
64-
const engines = await Engines.detect(meta);
65-
66-
Object.assign(result, travis, engines);
65+
Object.assign(result, ...await Promise.all([
66+
GithubActions.detect(meta),
67+
Travis.detect(meta),
68+
Engines.detect(meta)
69+
]));
6770

6871
return { result, meta };
6972
};

test/index.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,8 @@ describe('detect-node-support', () => {
431431
.reply(200, {
432432
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
433433
})
434+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
435+
.reply(404)
434436
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
435437
.reply(200, {
436438
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -468,6 +470,8 @@ describe('detect-node-support', () => {
468470
.reply(200, {
469471
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
470472
})
473+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
474+
.reply(404)
471475
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
472476
.reply(200, {
473477
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -505,6 +509,8 @@ describe('detect-node-support', () => {
505509
.reply(200, {
506510
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
507511
})
512+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
513+
.reply(404)
508514
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
509515
.reply(404);
510516

@@ -532,6 +538,8 @@ describe('detect-node-support', () => {
532538
.reply(200, {
533539
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
534540
})
541+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
542+
.reply(404)
535543
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
536544
.reply(500, 'Simulated server error');
537545

@@ -546,6 +554,8 @@ describe('detect-node-support', () => {
546554
Nock('https://api.github.com')
547555
.get('/repos/pkgjs/detect-node-support/contents/package.json')
548556
.reply(404)
557+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
558+
.reply(404)
549559
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
550560
.reply(200, {
551561
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -560,6 +570,8 @@ describe('detect-node-support', () => {
560570
Nock('https://api.github.com')
561571
.get('/repos/pkgjs/detect-node-support/contents/package.json')
562572
.reply(500)
573+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
574+
.reply(404)
563575
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
564576
.reply(200, {
565577
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -601,6 +613,8 @@ describe('detect-node-support', () => {
601613
'x-ratelimit-remaining': '0',
602614
'x-ratelimit-reset': `${Math.round(Date.now() / 1000) + 1}`
603615
})
616+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
617+
.reply(404)
604618
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
605619
.reply(200, {
606620
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -635,6 +649,8 @@ describe('detect-node-support', () => {
635649
.reply(200, {
636650
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
637651
})
652+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
653+
.reply(404)
638654
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits
639655
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
640656
.reply(403, 'You have exceeded a secondary rate limit');
@@ -659,6 +675,8 @@ describe('detect-node-support', () => {
659675
.reply(200, {
660676
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
661677
})
678+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
679+
.reply(404)
662680
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
663681
.reply(200, {
664682
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -700,6 +718,8 @@ describe('detect-node-support', () => {
700718
.reply(200, {
701719
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
702720
})
721+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
722+
.reply(404)
703723
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
704724
.reply(404);
705725

@@ -785,6 +805,8 @@ describe('detect-node-support', () => {
785805
.reply(200, {
786806
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
787807
})
808+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
809+
.reply(404)
788810
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
789811
.reply(200, {
790812
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -829,6 +851,8 @@ describe('detect-node-support', () => {
829851
.reply(200, {
830852
content: Buffer.from(JSON.stringify({ name: 'something-else' })).toString('base64')
831853
})
854+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
855+
.reply(404)
832856
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
833857
.reply(200, {
834858
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -879,6 +903,8 @@ describe('detect-node-support', () => {
879903
.reply(200, {
880904
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
881905
})
906+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
907+
.reply(404)
882908
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
883909
.reply(200, {
884910
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -916,6 +942,8 @@ describe('detect-node-support', () => {
916942
.reply(200, {
917943
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
918944
})
945+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
946+
.reply(404)
919947
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
920948
.reply(200, {
921949
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -953,6 +981,8 @@ describe('detect-node-support', () => {
953981
.reply(200, {
954982
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
955983
})
984+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
985+
.reply(404)
956986
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
957987
.reply(200, {
958988
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -994,6 +1024,8 @@ describe('detect-node-support', () => {
9941024
.reply(200, {
9951025
content: Fs.readFileSync(Path.join(__dirname, 'fixtures', 'hapi-package.json')).toString('base64')
9961026
})
1027+
.get('/repos/hapijs/hapi/contents/.github%2Fworkflows')
1028+
.reply(404)
9971029
.get('/repos/hapijs/hapi/contents/.travis.yml')
9981030
.reply(200, {
9991031
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -1046,34 +1078,47 @@ describe('detect-node-support', () => {
10461078
.reply(200, Fs.readFileSync(Path.join(__dirname, 'fixtures', 'packuments', 'rimraf.json')));
10471079

10481080
Nock('https://api.github.com')
1081+
10491082
.get('/repos/watson/is-ci/contents/package.json')
10501083
.reply(200, {
10511084
content: Buffer.from(JSON.stringify({ name: 'is-ci', version: '2.0.0' })).toString('base64')
10521085
})
1086+
.get('/repos/watson/is-ci/contents/.github%2Fworkflows')
1087+
.reply(404)
10531088
.get('/repos/watson/is-ci/contents/.travis.yml')
10541089
.reply(200, {
10551090
content: Fs.readFileSync(Path.join(__dirname, 'fixtures', 'travis-ymls', 'testing-single-version.yml')).toString('base64')
10561091
})
1092+
10571093
.get('/repos/watson/ci-info/contents/package.json')
10581094
.reply(200, {
10591095
content: Buffer.from(JSON.stringify({ name: 'ci-info', version: '2.0.0' })).toString('base64')
10601096
})
1097+
.get('/repos/watson/ci-info/contents/.github%2Fworkflows')
1098+
.reply(404)
10611099
.get('/repos/watson/ci-info/contents/.travis.yml')
10621100
.reply(200, {
10631101
content: Fs.readFileSync(Path.join(__dirname, 'fixtures', 'travis-ymls', 'testing-single-version.yml')).toString('base64')
10641102
})
1103+
10651104
.get('/repos/visionmedia/debug/contents/package.json')
10661105
.reply(200, {
10671106
content: Buffer.from(JSON.stringify({ name: 'debug', version: '4.1.1' })).toString('base64')
10681107
})
1108+
.get('/repos/visionmedia/debug/contents/.github%2Fworkflows')
1109+
.reply(404)
10691110
.get('/repos/visionmedia/debug/contents/.travis.yml')
10701111
.reply(404)
1112+
10711113
.get('/repos/zeit/ms/contents/package.json')
10721114
.reply(200, {
10731115
content: Buffer.from(JSON.stringify({ name: 'ms', version: '2.1.2' })).toString('base64')
10741116
})
1117+
.get('/repos/zeit/ms/contents/.github%2Fworkflows')
1118+
.reply(404)
10751119
.get('/repos/zeit/ms/contents/.travis.yml')
10761120
.reply(404)
1121+
10771122
.get('/repos/isaacs/rimraf/contents/package.json')
10781123
.reply(404);
10791124
});

0 commit comments

Comments
 (0)