-
Notifications
You must be signed in to change notification settings - Fork 4
/
get_rel_project_reqs.js
171 lines (151 loc) · 6.17 KB
/
get_rel_project_reqs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// get the build requirements for the project, if they're present
// these are:
// - npm version
// - node version
// - OS
//
// some notes:
// - devs can specify a range of engines (npm, node) that their project works on.
// If a range is specified we just get one version in the valid range
// - if the project specifically doesn't work on linux, then we're bailing -- this
// only makes linux docker containers
// also this is in JS instead of python bc the python semver library is garbage
const semver = require('semver');
const subproc = require('child_process');
const fs = require('fs').promises;
// can specify OS version: https://docs.npmjs.com/cli/v9/configuring-npm/package-json#os
// can specify node/npm version: https://docs.npmjs.com/cli/v9/configuring-npm/package-json#engines
async function get_reqs_from_pkg_json(pkg_json) {
let reqs = {}
let engines = pkg_json["engines"] || {};
// if not specified, "*" any version
let npm_req = engines["npm"] || "*";
let node_req = engines["node"] || "*";
// if a range is specified, get a version in the valid range
let { node_version, npm_version } = await get_versions_in_range(node_req, npm_req);
reqs["node_version"] = node_version;
reqs["npm_version"] = npm_version;
oss = engines["os"] || [];
// explicit versions and linux is not listed
if (oss.length > 0 && oss.indexOf("linux") == -1)
reqs["linux"] = false
// explicitly excluding linux :'(
else if (oss.indexOf("!linux") != -1)
reqs["linux"] = false
else
reqs["linux"] = true
return reqs
}
const BANNED_VERSION_SUBSTRINGS = ["beta", "alpha", "pre"]
// using semver, let's get a version that matches our specs
async function get_versions_in_range(node_version, npm_version) {
let node_npm_version_pairs = [];
try {
node_npm_version_pairs = await get_node_npm_version_pairs();
} catch(e) {
console.log("Error getting npm/node pairs -- proceeding blind: " + e);
}
// normal route: we have the data.
// now just need to find a pair that matches
if (node_npm_version_pairs.length > 0) {
for (const pair of node_npm_version_pairs) {
if (is_banned(pair["npm"]) || is_banned(pair["node"])) {
continue;
}
if (semver.satisfies(pair["npm"], npm_version) && semver.satisfies(pair["node"], node_version)) {
return { "node_version": pair["node"], "npm_version": pair["npm"] }
}
}
}
// if we get here we didn't return in the if above
// we don't have the data: get the list of all node versions from nvm: `nvm ls-remote`
// and all npm versions from npm itself: `npm view npm versions`
// NOTE: node version takes precedence over the npm version bc it's more commonly specified,
// and because it's more important
if (node_version !== "*" ) {
// then we care about the node version
subproc.exec('nvm ls-remote', { shell: '/bin/bash'}, (err, stdout, stderr) => {
let versions = stdout.split("\n").map(v => v.trim().split(" ")[0]); // strip formatting and any space-delimited labels (LTS, etc)
for (vers of versions) {
if (is_banned(vers)) {
continue;
}
if (semver.satisfies(vers, node_version)) {
return { "node_version": vers, "npm_version": "*" }
}
}
})
}
// if we get here, then we didn't have the version pair data, and we also didn't care about the node version
// so let's get an npm version
if (npm_version !== "*") {
// then we care about the npm version
subproc.exec('npm view npm versions --json', { shell: '/bin/bash'}, (err, stdout, stderr) => {
let versions = JSON.parse(stdout);
for (vers of versions) {
if (is_banned(vers)) {
continue;
}
if (semver.satisfies(vers, npm_version)) {
return { "node_version": "*", "npm_version": vers }
}
}
})
}
// no matching pairs: we're flying blind folks
return { "node_version": "*", "npm_version": "*" }
}
// versions of node and the versions of npm they are bundled with
// see: https://stackoverflow.com/questions/51238643/which-versions-of-npm-came-with-which-versions-of-node
// read this file in -- from it we can get all the valid versions of npm and node
// for fetch usage: https://stackoverflow.com/questions/2499567/how-to-make-a-json-call-to-an-url/2499647#2499647
const NODE_NPM_VERSIONS_URL = 'https://nodejs.org/dist/index.json';
async function get_node_npm_version_pairs() {
let resp = await fetch(NODE_NPM_VERSIONS_URL);
// look for errors:
if (!resp.ok) {
throw new Error("Uh oh: error reaching npm/node version pairs");
}
let all_data = await resp.json();
let node_npm_pairs = [];
for (const vers_data of all_data) {
let node_version = vers_data["version"];
let npm_version = vers_data["npm"];
// if both were in the version data
if (node_version && npm_version)
node_npm_pairs.push({node: node_version, npm: npm_version})
}
return node_npm_pairs;
}
// check if a version is banned
function is_banned(vers) {
for (const banned of BANNED_VERSION_SUBSTRINGS) {
if (vers.indexOf(banned) > -1) {
return true;
}
}
return false;
}
function print_as_bash_vars(reqs) {
for ( key in reqs) {
console.log("export " + key + "=" + reqs[key]);
}
}
async function main(proj_dir) {
let pkg_json = {};
try {
pkg_json = JSON.parse(await fs.readFile(proj_dir + "/package.json", 'utf8'));
} catch(e) {
console.error("Error, bailing out: " + proj_dir + " invalid directory, could not load package.json");
process.exit();
}
// get the node and npm versions
let reqs = await get_reqs_from_pkg_json(pkg_json);
print_as_bash_vars(reqs);
}
if (process.argv.length != 3) {
console.error("Usage: node get_rel_project_req.js path_to_project_dir")
process.exit()
}
let proj_dir = process.argv[2];
main(proj_dir);