Skip to content

Commit 23b16f8

Browse files
committed
feat: implement basic github action detection from repository
1 parent 9a95f8d commit 23b16f8

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
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
639655
.reply(403, 'Abuse detected');
640656

@@ -658,6 +674,8 @@ describe('detect-node-support', () => {
658674
.reply(200, {
659675
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
660676
})
677+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
678+
.reply(404)
661679
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
662680
.reply(200, {
663681
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -699,6 +717,8 @@ describe('detect-node-support', () => {
699717
.reply(200, {
700718
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
701719
})
720+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
721+
.reply(404)
702722
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
703723
.reply(404);
704724

@@ -784,6 +804,8 @@ describe('detect-node-support', () => {
784804
.reply(200, {
785805
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
786806
})
807+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
808+
.reply(404)
787809
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
788810
.reply(200, {
789811
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -828,6 +850,8 @@ describe('detect-node-support', () => {
828850
.reply(200, {
829851
content: Buffer.from(JSON.stringify({ name: 'something-else' })).toString('base64')
830852
})
853+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
854+
.reply(404)
831855
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
832856
.reply(200, {
833857
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -878,6 +902,8 @@ describe('detect-node-support', () => {
878902
.reply(200, {
879903
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
880904
})
905+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
906+
.reply(404)
881907
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
882908
.reply(200, {
883909
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -915,6 +941,8 @@ describe('detect-node-support', () => {
915941
.reply(200, {
916942
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
917943
})
944+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
945+
.reply(404)
918946
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
919947
.reply(200, {
920948
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -952,6 +980,8 @@ describe('detect-node-support', () => {
952980
.reply(200, {
953981
content: Fs.readFileSync(Path.join(__dirname, '..', 'package.json')).toString('base64')
954982
})
983+
.get('/repos/pkgjs/detect-node-support/contents/.github%2Fworkflows')
984+
.reply(404)
955985
.get('/repos/pkgjs/detect-node-support/contents/.travis.yml')
956986
.reply(200, {
957987
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -993,6 +1023,8 @@ describe('detect-node-support', () => {
9931023
.reply(200, {
9941024
content: Fs.readFileSync(Path.join(__dirname, 'fixtures', 'hapi-package.json')).toString('base64')
9951025
})
1026+
.get('/repos/hapijs/hapi/contents/.github%2Fworkflows')
1027+
.reply(404)
9961028
.get('/repos/hapijs/hapi/contents/.travis.yml')
9971029
.reply(200, {
9981030
content: Fs.readFileSync(Path.join(__dirname, '..', '.travis.yml')).toString('base64')
@@ -1045,34 +1077,47 @@ describe('detect-node-support', () => {
10451077
.reply(200, Fs.readFileSync(Path.join(__dirname, 'fixtures', 'packuments', 'rimraf.json')));
10461078

10471079
Nock('https://api.github.com')
1080+
10481081
.get('/repos/watson/is-ci/contents/package.json')
10491082
.reply(200, {
10501083
content: Buffer.from(JSON.stringify({ name: 'is-ci', version: '2.0.0' })).toString('base64')
10511084
})
1085+
.get('/repos/watson/is-ci/contents/.github%2Fworkflows')
1086+
.reply(404)
10521087
.get('/repos/watson/is-ci/contents/.travis.yml')
10531088
.reply(200, {
10541089
content: Fs.readFileSync(Path.join(__dirname, 'fixtures', 'travis-ymls', 'testing-single-version.yml')).toString('base64')
10551090
})
1091+
10561092
.get('/repos/watson/ci-info/contents/package.json')
10571093
.reply(200, {
10581094
content: Buffer.from(JSON.stringify({ name: 'ci-info', version: '2.0.0' })).toString('base64')
10591095
})
1096+
.get('/repos/watson/ci-info/contents/.github%2Fworkflows')
1097+
.reply(404)
10601098
.get('/repos/watson/ci-info/contents/.travis.yml')
10611099
.reply(200, {
10621100
content: Fs.readFileSync(Path.join(__dirname, 'fixtures', 'travis-ymls', 'testing-single-version.yml')).toString('base64')
10631101
})
1102+
10641103
.get('/repos/visionmedia/debug/contents/package.json')
10651104
.reply(200, {
10661105
content: Buffer.from(JSON.stringify({ name: 'debug', version: '4.1.1' })).toString('base64')
10671106
})
1107+
.get('/repos/visionmedia/debug/contents/.github%2Fworkflows')
1108+
.reply(404)
10681109
.get('/repos/visionmedia/debug/contents/.travis.yml')
10691110
.reply(404)
1111+
10701112
.get('/repos/zeit/ms/contents/package.json')
10711113
.reply(200, {
10721114
content: Buffer.from(JSON.stringify({ name: 'ms', version: '2.1.2' })).toString('base64')
10731115
})
1116+
.get('/repos/zeit/ms/contents/.github%2Fworkflows')
1117+
.reply(404)
10741118
.get('/repos/zeit/ms/contents/.travis.yml')
10751119
.reply(404)
1120+
10761121
.get('/repos/isaacs/rimraf/contents/package.json')
10771122
.reply(404);
10781123
});

0 commit comments

Comments
 (0)