Skip to content

Commit 0b02516

Browse files
authored
Merge pull request #30 from pkgjs/feat-3
Travis imports
2 parents 69b3f2d + e242cac commit 0b02516

29 files changed

+734
-20
lines changed

lib/deps.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ const Tmp = require('tmp');
99
const Package = require('./package');
1010
const Utils = require('./utils');
1111

12-
const internals = {};
13-
14-
15-
internals.log = Debug('detect-node-support');
12+
const internals = {
13+
log: Debug('detect-node-support')
14+
};
1615

1716

1817
internals.resolve = async ({ packageJson, lockfile }, options) => {

lib/loader.js

+16
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const Wreck = require('@hapi/wreck');
1111
const Utils = require('./utils');
1212

1313
const internals = {
14+
cache: new Map(),
1415
log: Debug('detect-node-support:loader'),
1516
error: Debug('detect-node-support:error')
1617
};
@@ -95,9 +96,18 @@ internals.createRepositoryLoader = (repository) => {
9596
const url = `https://raw.githubusercontent.com/${parsedRepository.full_name}/HEAD/${filename}`;
9697
internals.log('Loading: %s', url);
9798

99+
if (options === undefined && internals.cache.has(url)) {
100+
internals.log('From cache: %s', url);
101+
return internals.cache.get(url);
102+
}
103+
98104
try {
99105
const { payload } = await Wreck.get(url, options);
100106

107+
if (options === undefined) {
108+
internals.cache.set(url, payload);
109+
}
110+
101111
internals.log('Loaded: %s', url);
102112
return payload;
103113
}
@@ -164,3 +174,9 @@ exports.create = ({ path, repository, packageName }) => {
164174

165175
return internals.createPathLoader(path);
166176
};
177+
178+
179+
exports.clearCache = () => {
180+
181+
internals.cache = new Map();
182+
};

lib/travis/imports.js

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'use strict';
2+
3+
const Yaml = require('js-yaml');
4+
5+
const Loader = require('../loader');
6+
const Utils = require('../utils');
7+
8+
const TravisMerge = require('./merge');
9+
10+
11+
const internals = {
12+
validMergeModes: new Set(['deep_merge_append', 'deep_merge_prepend', 'deep_merge', 'merge'])
13+
};
14+
15+
16+
internals.normalizeImports = (travisYaml, { relativeTo, breadcrumb }) => {
17+
18+
const context = relativeTo ? relativeTo.source : '.travis.yml';
19+
20+
return Utils.toArray(travisYaml.import)
21+
.map((entry) => {
22+
23+
if (typeof entry === 'string') {
24+
entry = { source: entry };
25+
}
26+
27+
const original = entry.source;
28+
29+
if (entry.source.startsWith('./')) {
30+
entry.source = entry.source.substring(2);
31+
32+
if (relativeTo) {
33+
const relativeParts = relativeTo.source.split('/');
34+
relativeParts.pop();
35+
relativeParts.push(entry.source);
36+
entry.source = relativeParts.join('/');
37+
}
38+
}
39+
40+
if (!entry.mode) {
41+
entry.mode = 'deep_merge_append';
42+
}
43+
44+
if (!internals.validMergeModes.has(entry.mode)) {
45+
throw new Error(`Invalid merge mode for ${original} in ${context}: ${entry.mode}`);
46+
}
47+
48+
if (original.includes('@')) {
49+
throw new Error(`Importing at commitish unsupported in ${context}: ${original}`);
50+
}
51+
52+
const alreadyImported = breadcrumb.indexOf(entry.source);
53+
if (alreadyImported >= 0) {
54+
throw new Error(`Circular dependency ${entry.source} requested by ${context} (already imported at ${breadcrumb[alreadyImported - 1]})`);
55+
}
56+
57+
return entry;
58+
})
59+
.filter((entry) => !entry.if); // @todo: log a warning
60+
};
61+
62+
63+
internals.loadSource = async (source, { loadFile }) => {
64+
65+
let path = source;
66+
67+
if (source.includes(':')) {
68+
const [repository, fileName] = source.split(':');
69+
const loader = await Loader.create({ repository: `https://github.com/${repository}` });
70+
71+
path = fileName;
72+
loadFile = loader.loadFile;
73+
}
74+
75+
return loadFile(path);
76+
};
77+
78+
79+
exports.apply = async (yaml, { loadFile, relativeTo, breadcrumb = ['.travis.yml'] }) => {
80+
81+
if (!yaml.import) {
82+
return;
83+
}
84+
85+
const imports = internals.normalizeImports(yaml, { relativeTo, breadcrumb });
86+
87+
for (const entry of imports) {
88+
89+
const buffer = await internals.loadSource(entry.source, { loadFile });
90+
91+
const imported = Yaml.safeLoad(buffer, {
92+
schema: Yaml.FAILSAFE_SCHEMA,
93+
json: true
94+
});
95+
96+
await exports.apply(imported, { loadFile, relativeTo: entry, breadcrumb: [...breadcrumb, entry.source] });
97+
98+
delete imported.import;
99+
100+
TravisMerge[entry.mode](yaml, imported);
101+
}
102+
};

lib/travis/index.js

+11-15
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,35 @@
33
const Nv = require('@pkgjs/nv');
44
const Yaml = require('js-yaml');
55

6+
const TravisImports = require('./imports');
7+
const Utils = require('../utils');
8+
69

710
const internals = {};
811

12+
913
internals.nodeAliases = {
1014
latest: 'active',
1115
node: 'active',
1216
stable: 'active'
1317
};
1418

1519

16-
internals.toArray = (v) => {
17-
18-
if (v === undefined) {
19-
return [];
20-
}
21-
22-
return Array.isArray(v) ? v : [v];
23-
};
24-
20+
internals.scan = async (travisYaml, options) => {
2521

26-
internals.scan = async (travisYaml) => {
22+
await TravisImports.apply(travisYaml, options);
2723

2824
const rawSet = new Set();
2925

30-
for (const v of internals.toArray(travisYaml.node_js)) {
26+
for (const v of Utils.toArray(travisYaml.node_js)) {
3127
rawSet.add(v);
3228
}
3329

3430
if (travisYaml.env) {
3531

36-
for (const env of internals.toArray(travisYaml.env.matrix)) {
32+
for (const env of Utils.toArray(travisYaml.env.matrix)) {
3733

38-
const matches = env.match(/(?:NODEJS_VER|TRAVIS_NODE_VERSION|NODE_VER)="?(node\/)?(?<version>[\w./*]+)"?/); /* hack syntax highlighter 🤦‍♂️ */
34+
const matches = env.match(/(?:NODEJS_VER|TRAVIS_NODE_VERSION|NODE_VER)="?(node\/)?(?<version>[\w./*]+)"?/);
3935

4036
if (matches) {
4137
rawSet.add(matches.groups.version);
@@ -45,7 +41,7 @@ internals.scan = async (travisYaml) => {
4541

4642
if (travisYaml.matrix) {
4743

48-
for (const include of internals.toArray(travisYaml.matrix.include)) {
44+
for (const include of Utils.toArray(travisYaml.matrix.include)) {
4945

5046
if (include.node_js) {
5147
rawSet.add(include.node_js);
@@ -96,6 +92,6 @@ exports.detect = async ({ loadFile }) => {
9692
});
9793

9894
return {
99-
travis: await internals.scan(travisYaml)
95+
travis: await internals.scan(travisYaml, { loadFile })
10096
};
10197
};

lib/travis/merge.js

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict';
2+
3+
// ref: https://github.com/travis-ci/travis-yml/blob/bf82881491134c72a64778f9664a8dd3f97158e7/lib/travis/yml/support/merge.rb
4+
5+
const internals = {};
6+
7+
8+
internals.isObject = (arg) => typeof arg === 'object' && !Array.isArray(arg);
9+
10+
11+
exports.deep_merge_append = (left, right) => {
12+
13+
for (const key in right) {
14+
15+
if (internals.isObject(left[key]) && internals.isObject(right[key])) {
16+
exports.deep_merge_append(left[key], right[key]);
17+
continue;
18+
}
19+
20+
if (Array.isArray(left[key]) && Array.isArray(right[key])) {
21+
left[key].push(...right[key]);
22+
continue;
23+
}
24+
25+
left[key] = right[key];
26+
}
27+
};
28+
29+
exports.deep_merge_prepend = (left, right) => {
30+
31+
for (const key in right) {
32+
33+
if (internals.isObject(left[key]) && internals.isObject(right[key])) {
34+
exports.deep_merge_prepend(left[key], right[key]);
35+
continue;
36+
}
37+
38+
if (Array.isArray(left[key]) && Array.isArray(right[key])) {
39+
left[key].unshift(...right[key]);
40+
continue;
41+
}
42+
43+
left[key] = right[key];
44+
}
45+
};
46+
47+
exports.deep_merge = (left, right) => {
48+
49+
for (const key in right) {
50+
51+
if (internals.isObject(left[key]) && internals.isObject(right[key])) {
52+
exports.deep_merge(left[key], right[key]);
53+
continue;
54+
}
55+
56+
left[key] = right[key];
57+
}
58+
};
59+
60+
exports.merge = (left, right) => {
61+
62+
for (const key in right) {
63+
left[key] = right[key];
64+
}
65+
};

lib/utils.js

+10
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,13 @@ exports.getErrorMessage = (error) => {
2222

2323
return null;
2424
};
25+
26+
27+
exports.toArray = (v) => {
28+
29+
if (v === undefined) {
30+
return [];
31+
}
32+
33+
return Array.isArray(v) ? v : [v];
34+
};

test/fixtures/index.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const SimpleGit = require('simple-git/promise');
77
const Sinon = require('sinon');
88
const Tmp = require('tmp');
99

10+
const Loader = require('../../lib/loader');
1011
const Utils = require('../../lib/utils');
1112

1213

@@ -28,6 +29,8 @@ module.exports = class TestContext {
2829

2930
cleanup() {
3031

32+
Loader.clearCache();
33+
3134
Sinon.restore();
3235

3336
this._cleanup.forEach((cleanup) => cleanup());
@@ -75,7 +78,7 @@ module.exports = class TestContext {
7578
});
7679
}
7780

78-
async setupRepoFolder({ travisYml, packageJson, npmShrinkwrapJson, packageLockJson, git = true } = {}) {
81+
async setupRepoFolder({ travisYml, partials, packageJson, npmShrinkwrapJson, packageLockJson, git = true } = {}) {
7982

8083
const tmpObj = Tmp.dirSync({ unsafeCleanup: true });
8184

@@ -87,6 +90,22 @@ module.exports = class TestContext {
8790
Fs.copyFileSync(Path.join(__dirname, 'travis-ymls', travisYml), Path.join(this.path, '.travis.yml'));
8891
}
8992

93+
if (partials) {
94+
Fs.mkdirSync(Path.join(this.path, 'partials'));
95+
const partialYmls = [
96+
'circular.yml',
97+
'commitish.yml',
98+
'indirect-node-14.yml',
99+
'merge-invalid.yml',
100+
'node-10.yml',
101+
'node-12.yml',
102+
'node-14.yml'
103+
];
104+
for (const fn of partialYmls) {
105+
Fs.copyFileSync(Path.join(__dirname, 'travis-ymls', 'testing-imports', 'partials', fn), Path.join(this.path, 'partials', fn));
106+
}
107+
}
108+
90109
if (packageJson !== false) {
91110
Fs.writeFileSync(Path.join(this.path, 'package.json'), JSON.stringify(packageJson || {
92111
name: 'test-module',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
language: node_js
2+
import:
3+
- source: pkgjs/detect-node-support:test/fixtures/travis-ymls/testing-imports/partials/indirect-node-14.yml
4+
- source: pkgjs/detect-node-support:test/fixtures/travis-ymls/testing-imports/partials/node-14.yml # cache hit
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
language: node_js
2+
import:
3+
- source: partials/circular.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
language: node_js
2+
import:
3+
- source: partials/commitish.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
language: node_js
2+
import:
3+
- source: partials/indirect-node-14.yml
4+
if: branch = master
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
language: node_js
2+
import:
3+
- source: ./partials/indirect-node-14.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
language: node_js
2+
import:
3+
- source: partials/indirect-node-14.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
language: node_js
2+
node_js:
3+
- "8"
4+
import:
5+
- source: partials/node-14.yml # default merge: deep_merge_append
6+
- source: partials/node-12.yml
7+
mode: deep_merge_prepend
8+
- source: partials/node-10.yml
9+
mode: deep_merge_append
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
language: node_js
2+
node_js:
3+
- "8"
4+
import:
5+
- source: partials/node-14.yml # default merge: deep_merge_append
6+
- source: partials/node-12.yml
7+
mode: deep_merge
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
language: node_js
2+
node_js:
3+
- "8"
4+
import:
5+
- source: partials/merge-invalid.yml

0 commit comments

Comments
 (0)