UNPKG

14.2 kBJavaScriptView Raw
1var __assign = (this && this.__assign) || function () {
2 __assign = Object.assign || function(t) {
3 for (var s, i = 1, n = arguments.length; i < n; i++) {
4 s = arguments[i];
5 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6 t[p] = s[p];
7 }
8 return t;
9 };
10 return __assign.apply(this, arguments);
11};
12// Imports
13import { resolve } from 'path';
14import matchRange from 'version-range';
15import { errtion } from './util.js';
16import { versions as processVersions } from 'process';
17import { readFileSync } from 'fs';
18/**
19 * Load the {@link Edition} with the loader.
20 * @returns The result of the loaded edition.
21 * @throws If failed to load, an error is thrown with the reason.
22 */
23export function loadEdition(edition, opts) {
24 var entry = resolve(opts.cwd || '', edition.directory, opts.entry || edition.entry || '');
25 if (opts.loader == null) {
26 throw errtion({
27 message: "Could not load the edition [" + edition.description + "] as no loader was specified. This is probably due to a testing misconfiguration.",
28 code: 'editions-autoloader-loader-missing',
29 level: 'fatal',
30 });
31 }
32 try {
33 return opts.loader.call(edition, entry);
34 }
35 catch (loadError) {
36 // Note the error with more details
37 throw errtion({
38 message: "Failed to load the entry [" + entry + "] of edition [" + edition.description + "].",
39 code: 'editions-autoloader-loader-failed',
40 level: 'fatal',
41 }, loadError);
42 }
43}
44/**
45 * Verify the {@link Edition} has all the required properties.
46 * @returns if valid
47 * @throws if invalid
48 */
49export function isValidEdition(edition) {
50 if (!edition.description ||
51 !edition.directory ||
52 !edition.entry ||
53 edition.engines == null) {
54 throw errtion({
55 message: "An edition must have its [description, directory, entry, engines] fields defined, yet all this edition defined were [" + Object.keys(edition).join(', ') + "]",
56 code: 'editions-autoloader-invalid-edition',
57 level: 'fatal',
58 });
59 }
60 // valid
61 return true;
62}
63/**
64 * Is this {@link Edition} suitable for these versions?
65 * @returns if compatible
66 * @throws if incompatible
67 */
68export function isCompatibleVersion(range, version, opts) {
69 // prepare
70 var broadenRange = opts.broadenRange;
71 if (!version)
72 throw errtion({
73 message: "No version was specified to compare the range [" + range + "] against",
74 code: 'editions-autoloader-engine-version-missing',
75 level: 'fatal',
76 });
77 if (range == null || range === '')
78 throw errtion({
79 message: "The edition range was not specified, so unable to compare against the version [" + version + "]",
80 code: 'editions-autoloader-engine-range-missing',
81 });
82 if (range === false)
83 throw errtion({
84 message: "The edition range does not support this engine",
85 code: 'editions-autoloader-engine-unsupported',
86 });
87 if (range === true)
88 return true;
89 // original range
90 try {
91 if (matchRange(version, range))
92 return true;
93 }
94 catch (error) {
95 throw errtion({
96 message: "The range [" + range + "] was invalid, something is wrong with the Editions definition.",
97 code: 'editions-autoloader-invalid-range',
98 level: 'fatal',
99 }, error);
100 }
101 // broadened range
102 // https://github.com/bevry/editions/blob/master/HISTORY.md#v210-2018-november-15
103 // If none of the editions for a package match the current node version, editions will try to find a compatible package by converting strict version ranges likes 4 || 6 || 8 || 10 to looser ones like >=4, and if that fails, then it will attempt to load the last edition for the environment.
104 // This brings editions handling of engines closer in line with how node handles it, which is as a warning/recommendation, rather than a requirement/enforcement.
105 // This has the benefit that edition authors can specify ranges as the specific versions that they have tested the edition against that pass, rather than having to omit that information for runtime compatibility.
106 // As such editions will now automatically select the edition with guaranteed support for the environment, and if there are none with guaranteed support, then editions will select the one is most likely supported, and if there are none that are likely supported, then it will try the last edition, which should be the most compatible edition.
107 // This is timely, as node v11 is now the version most developers use, yet if edition authors specified only LTS releases, then the editions autoloader would reject loading on v11, despite compatibility being likely with the most upper edition.
108 // NOTE: That there is only one broadening chance per package, once a broadened edition has been returned, a load will be attempted, and if it fails, then the package failed. This is intentional.
109 if (broadenRange === true) {
110 // check if range can be broadened, validate it and extract
111 var broadenedRangeRegex = /^\s*([0-9.]+)\s*(\|\|\s*[0-9.]+\s*)*$/;
112 var broadenedRangeMatch = range.match(broadenedRangeRegex);
113 var lowestVersion = (broadenedRangeMatch && broadenedRangeMatch[1]) || '';
114 // ^ can't do number conversion, as 1.1.1 is not a number
115 // this also converts 0 to '' which is what we want for the next check
116 // confirm the validation
117 if (lowestVersion === '')
118 throw errtion({
119 message: "The range [" + range + "] is not able to be broadened, only ranges in format of [lowest] or [lowest || ... || ... ] can be broadened. Update the Editions definition and try again.",
120 code: 'editions-autoloader-unsupported-broadened-range',
121 level: 'fatal',
122 });
123 // create the broadened range, and attempt that
124 var broadenedRange = ">= " + lowestVersion;
125 try {
126 if (matchRange(version, broadenedRange))
127 return true;
128 }
129 catch (error) {
130 throw errtion({
131 message: "The broadened range [" + broadenedRange + "] was invalid, something is wrong within Editions.",
132 code: 'editions-autoloader-invalid-broadened-range',
133 level: 'fatal',
134 }, error);
135 }
136 // broadened range was incompatible
137 throw errtion({
138 message: "The edition range [" + range + "] does not support this engine version [" + version + "], even when broadened to [" + broadenedRange + "]",
139 code: 'editions-autoloader-engine-incompatible-broadened-range',
140 });
141 }
142 // give up
143 throw errtion({
144 message: "The edition range [" + range + "] does not support this engine version [" + version + "]",
145 code: 'editions-autoloader-engine-incompatible-original',
146 });
147}
148/**
149 * Checks that the provided engines are compatible against the provided versions.
150 * @returns if compatible
151 * @throws if incompatible
152 */
153export function isCompatibleEngines(engines, opts) {
154 // PRepare
155 var versions = opts.versions;
156 // Check engines exist
157 if (!engines) {
158 throw errtion({
159 message: "The edition had no engines to compare against the environment",
160 code: 'editions-autoloader-invalid-engines',
161 });
162 }
163 // Check versions exist
164 if (!versions) {
165 throw errtion({
166 message: "No versions were supplied to compare the engines against",
167 code: 'editions-autoloader-invalid-versions',
168 level: 'fatal',
169 });
170 }
171 // Check each version
172 var compatible = false;
173 for (var key in engines) {
174 if (engines.hasOwnProperty(key)) {
175 // deno's std/node/process provides both `deno` and `node` keys
176 // so we don't won't to compare node when it is actually deno
177 if (key === 'node' && versions.deno)
178 continue;
179 // prepare
180 var engine = engines[key];
181 var version = versions[key];
182 // skip for engines this edition does not care about
183 if (version == null)
184 continue;
185 // check compatibility against all the provided engines it does care about
186 try {
187 isCompatibleVersion(engine, version, opts);
188 compatible = true;
189 // if any incompatibility, it is thrown, so no need to set to false
190 }
191 catch (rangeError) {
192 throw errtion({
193 message: "The engine [" + key + "] range of [" + engine + "] was not compatible against version [" + version + "].",
194 code: 'editions-autoloader-engine-error',
195 }, rangeError);
196 }
197 }
198 }
199 // if there were no matching engines, then throw
200 if (!compatible) {
201 throw errtion({
202 message: "There were no supported engines in which this environment provides.",
203 code: 'editions-autoloader-engine-mismatch',
204 });
205 }
206 // valid
207 return true;
208}
209/**
210 * Checks that the {@link Edition} is compatible against the provided versions.
211 * @returns if compatible
212 * @throws if incompatible
213 */
214export function isCompatibleEdition(edition, opts) {
215 try {
216 return isCompatibleEngines(edition.engines, opts);
217 }
218 catch (compatibleError) {
219 throw errtion({
220 message: "The edition [" + edition.description + "] is not compatible with this environment.",
221 code: 'editions-autoloader-edition-incompatible',
222 }, compatibleError);
223 }
224}
225/**
226 * Determine which edition should be loaded.
227 * If {@link VersionOptions.broadenRange} is unspecified (the default behavior), then we attempt to determine a suitable edition without broadening the range, and if that fails, then we try again with the range broadened.
228 * @returns any suitable editions
229 * @throws if no suitable editions
230 */
231export function determineEdition(editions, opts) {
232 // Prepare
233 var broadenRange = opts.broadenRange;
234 // Check
235 if (!editions || editions.length === 0) {
236 throw errtion({
237 message: 'No editions were specified.',
238 code: 'editions-autoloader-editions-missing',
239 });
240 }
241 // Cycle through the editions determining the above
242 var failure = null;
243 for (var i = 0; i < editions.length; ++i) {
244 var edition = editions[i];
245 try {
246 isValidEdition(edition);
247 isCompatibleEdition(edition, opts);
248 // Success! Return the edition
249 return edition;
250 }
251 catch (error) {
252 if (error.level === 'fatal') {
253 throw errtion({
254 message: "Unable to determine a suitable edition due to failure.",
255 code: 'editions-autoloader-fatal',
256 level: 'fatal',
257 }, error);
258 }
259 else if (failure) {
260 failure = errtion(error, failure);
261 }
262 else {
263 failure = error;
264 }
265 }
266 }
267 // Report the failure from above
268 if (failure) {
269 // try broadened
270 if (broadenRange == null)
271 try {
272 // return if broadening successfully returned an edition
273 var broadenedEdition = determineEdition(editions, __assign(__assign({}, opts), { broadenRange: true }));
274 return __assign(__assign({}, broadenedEdition), {
275 // bubble the circumstances up in case the loading of the broadened edition fails and needs to be reported
276 debugging: errtion({
277 message: "The edition " + broadenedEdition.description + " was selected to be force loaded as its range was broadened.",
278 code: 'editions-autoloader-attempt-broadened',
279 }) });
280 }
281 catch (error) {
282 throw errtion({
283 message: "Unable to determine a suitable edition, even after broadening.",
284 code: 'editions-autoloader-none-broadened',
285 }, error);
286 }
287 // fail
288 throw errtion({
289 message: "Unable to determine a suitable edition, as none were suitable.",
290 code: 'editions-autoloader-none-suitable',
291 }, failure);
292 }
293 // this should never reach here
294 throw errtion({
295 message: "Unable to determine a suitable edition, as an unexpected pathway occurred.",
296 code: 'editions-autoloader-never',
297 });
298}
299/**
300 * Determine which edition should be loaded, and attempt to load it.
301 * @returns the loaded result of the suitable edition
302 * @throws if no suitable editions, or the edition failed to load
303 */
304export function solicitEdition(editions, opts) {
305 var edition = determineEdition(editions, opts);
306 try {
307 return loadEdition(edition, opts);
308 }
309 catch (error) {
310 throw errtion(error, edition.debugging);
311 }
312}
313/**
314 * Cycle through the editions for a package, determine the compatible edition, and load it.
315 * @returns the loaded result of the suitable edition
316 * @throws if no suitable editions, or if the edition failed to load
317 */
318export function requirePackage(cwd, loader, entry) {
319 var packagePath = resolve(cwd || '', 'package.json');
320 try {
321 // load editions
322 var editions = JSON.parse(readFileSync(packagePath, 'utf8')).editions;
323 // load edition
324 return solicitEdition(editions, {
325 versions: processVersions,
326 cwd: cwd,
327 loader: loader,
328 entry: entry,
329 });
330 }
331 catch (error) {
332 throw errtion({
333 message: "Unable to determine a suitable edition for the package [" + packagePath + "] and entry [" + entry + "]",
334 code: 'editions-autoloader-package',
335 }, error);
336 }
337}