Comparing version 1.2.1 to 1.3.1
@@ -13,124 +13,198 @@ 'use strict'; | ||
// Imports | ||
var pathUtil = require('path'); | ||
// Helper class to display nested error in a sensible way | ||
var NestedError = function (_Error) { | ||
_inherits(NestedError, _Error); | ||
function NestedError(message /* :string */, parent /* :Error */) { | ||
_classCallCheck(this, NestedError); | ||
var DetailedError = function (_Error) { | ||
_inherits(DetailedError, _Error); | ||
message += ' due to next parent error'; | ||
function DetailedError(message /* :string */, details /* :Object */) { | ||
_classCallCheck(this, DetailedError); | ||
var _this = _possibleConstructorReturn(this, (NestedError.__proto__ || Object.getPrototypeOf(NestedError)).call(this, message)); | ||
_this.stack += '\n\nParent ' + (parent.stack || parent.message || parent).toString(); | ||
return _this; | ||
Object.keys(details).forEach(function (key) { | ||
var data = details[key]; | ||
var value = require('util').inspect(data.stack || data.message || data); | ||
message += '\n' + key + ': ' + value; | ||
}); | ||
return _possibleConstructorReturn(this, (DetailedError.__proto__ || Object.getPrototypeOf(DetailedError)).call(this, message)); | ||
} | ||
return NestedError; | ||
return DetailedError; | ||
}(Error); | ||
// Cache of which syntax combinations are supported or unsupported, hash of booleans | ||
// Always the same | ||
var EARLIST_NODE_VERSION_THAT_SUPPORTS_ESNEXT = 0.12; | ||
// Environment fetching | ||
var blacklist = process && process.env && process.env.EDITIONS_SYNTAX_BLACKLIST && process.env.EDITIONS_SYNTAX_BLACKLIST.split(','); | ||
// Cache of which syntax combinations are supported or unsupported, hash of booleans | ||
var syntaxFailedCombitions = {}; // sorted lowercase syntax combination => Error instance of failure | ||
var syntaxSupport = { | ||
esnext: process && process.versions && process.versions.node && Number(process.versions.node.split('.').slice(0, 2).join('.')) < EARLIST_NODE_VERSION_THAT_SUPPORTS_ESNEXT ? false : null, | ||
import: false, | ||
coffeescript: false, | ||
typescript: false | ||
var syntaxBlacklist = {}; | ||
syntaxBlacklist.import = new Error('The import syntax is skipped as the module package.json field eliminates the need for autoloader support'); | ||
syntaxBlacklist.coffeescript = new Error('The coffeescript syntax is skipped as we want to use a precompiled edition rather than compiling at runtime'); | ||
syntaxBlacklist.typescript = new Error('The typescript syntax is skipped as we want to use a precompiled edition rather than compiling at runtime'); | ||
if (process && process.versions && process.versions.node && Number(process.versions.node.split('.').slice(0, 2).join('.')) < EARLIST_NODE_VERSION_THAT_SUPPORTS_ESNEXT) { | ||
syntaxBlacklist.esnext = new Error('The esnext syntax is skipped on early node versions as attempting to use esnext features will output debugging information on these node versions'); | ||
} | ||
// Check the environment configuration for a syntax blacklist | ||
if (blacklist) { | ||
for (var i = 0; i < blacklist.length; ++i) { | ||
var syntax = blacklist[i].trim().toLowerCase(); | ||
syntaxBlacklist[syntax] = new DetailedError('The EDITIONS_SYNTAX_BLACKLIST environment variable has blacklisted an edition syntax:', { syntax: syntax, blacklist: blacklist }); | ||
} | ||
} | ||
/* :: | ||
type edition = { | ||
name:number, | ||
description?:string, | ||
directory?:string, | ||
entry?:string, | ||
syntaxes?:Array<string> | ||
}; | ||
type options = { | ||
cwd?:string, | ||
package?:string, | ||
entry?:string, | ||
require:function | ||
}; | ||
*/ | ||
/** | ||
* Cycle through the editions for a package and require the correct one | ||
* @param {string} cwd - the path of the package, used to load package.json:editions and handle relative edition entry points | ||
* @param {function} _require - the require method of the calling module, used to ensure require paths remain correct | ||
* @param {string} [customEntry] - an optional override for the entry of an edition, requires the edition to specify a `directory` property | ||
* @returns {void} | ||
* Cycle through the editions and require the correct one | ||
* @protected internal function that is untested for public consumption | ||
* @param {edition} edition - the edition entry | ||
* @param {Object} opts - the following options | ||
* @param {string} opts.require - the require method of the calling module, used to ensure require paths remain correct | ||
* @param {string} [opts.cwd] - if provided, this will be the cwd for entries | ||
* @param {string} [opts.entry] - if provided, should be a relative or absolute path to the entry point of the edition | ||
* @param {string} [opts.package] - if provided, should be the name of the package that we are loading the editions for | ||
* @returns {*} | ||
*/ | ||
module.exports.requirePackage = function requirePackage(cwd /* :string */, _require /* :function */, customEntry /* :: ?:string */) /* : void */{ | ||
// Fetch the result of the debug value | ||
// It is here to allow the environment to change it at runtime as needed | ||
var debug = process && process.env && process.env.DEBUG_BEVRY_EDITIONS; | ||
// const blacklist = process && process.env && process.env.DEBUG_BEVRY_IGNORE_BLACKLIST | ||
function requireEdition(edition /* :edition */, opts /* :options */) /* :any */{ | ||
// Prevent require from being included in debug logs | ||
Object.defineProperty(opts, 'require', { value: opts.require, enumerable: false }); | ||
// Load the package.json file to fetch `name` for debugging and `editions` for loading | ||
var pathUtil = require('path'); | ||
var packagePath = pathUtil.join(cwd, 'package.json'); | ||
// Get the correct entry path | ||
// As older versions o | ||
var cwd = opts.cwd || ''; | ||
var dir = edition.directory || ''; | ||
var entry = opts.entry || edition.entry || ''; | ||
if (dir && entry && entry.indexOf(dir + '/') === 0) entry = entry.substring(dir.length + 1); | ||
// ^ this should not be needed, but as previous versions of editions included the directory inside the entry | ||
// it unfortunately is, as such this is a stepping stone for the new format, the new format being | ||
// if entry is specified by itself, it is cwd => entry | ||
// if entry is specified with a directory, it is cwd => dir => entry | ||
// if entry is not specified but dir is, it is cwd => dir | ||
// if neither entry nor dir are specified, we have a problem | ||
if (!dir && !entry) { | ||
var editionFailure = new DetailedError('Skipped edition due to no entry or directory being specified:', { edition: edition, cwd: cwd, dir: dir, entry: entry }); | ||
throw editionFailure; | ||
} | ||
var entryPath = pathUtil.resolve(cwd, dir, entry); | ||
var _require2 = require(packagePath); | ||
// Check syntax support | ||
// Convert syntaxes into a sorted lowercase string | ||
var syntaxes = edition.syntaxes && edition.syntaxes.map(function (i) { | ||
return i.toLowerCase(); | ||
}).sort(); | ||
var syntaxCombination = syntaxes && syntaxes.join(', '); | ||
if (syntaxes && syntaxCombination) { | ||
// Check if any of the syntaxes are unsupported | ||
var unsupportedSyntaxes = syntaxes.filter(function (i) { | ||
return syntaxBlacklist[i.toLowerCase()]; | ||
}); | ||
if (unsupportedSyntaxes.length) { | ||
var _editionFailure = new DetailedError('Skipped edition due to it containing an unsupported syntax:', { edition: edition, unsupportedSyntaxes: unsupportedSyntaxes }); | ||
throw _editionFailure; | ||
} | ||
// Is this syntax combination unsupported? If so skip it with a soft failure to try the next edition | ||
else if (syntaxFailedCombitions[syntaxCombination]) { | ||
var previousCombinationFailure = syntaxFailedCombitions[syntaxCombination]; | ||
var _editionFailure2 = new DetailedError('Skipped edition due to its syntax combinatiom failing previously:', { edition: edition, previousCombinationFailure: previousCombinationFailure }); | ||
throw _editionFailure2; | ||
} | ||
} | ||
var name = _require2.name; | ||
var editions = _require2.editions; | ||
// name:string, editions:array | ||
// Try and load this syntax combination | ||
try { | ||
return opts.require(entryPath); | ||
} catch (error) { | ||
// Note the error with more details | ||
var _editionFailure3 = new DetailedError('Failed to load the edition due to a load error:', { edition: edition, error: error.stack }); | ||
// Blacklist the combination, even if it may have worked before | ||
// Perhaps in the future note if that if it did work previously, then we should instruct module owners to be more specific with their syntaxes | ||
if (syntaxCombination) syntaxFailedCombitions[syntaxCombination] = _editionFailure3; | ||
// Continue to the next edition | ||
throw _editionFailure3; | ||
} | ||
} | ||
/** | ||
* Cycle through the editions and require the correct one | ||
* @protected internal function that is untested for public consumption | ||
* @param {Array<edition>} editions - an array of edition entries | ||
* @param {Object} opts - the following options | ||
* @param {string} opts.require - the require method of the calling module, used to ensure require paths remain correct | ||
* @param {string} [opts.cwd] - if provided, this will be the cwd for entries | ||
* @param {string} [opts.entry] - if provided, should be a relative path to the entry point of the edition | ||
* @param {string} [opts.package] - if provided, should be the name of the package that we are loading the editions for | ||
* @returns {*} | ||
*/ | ||
function requireEditions(editions /* :Array<edition> */, opts /* :options */) /* :any */{ | ||
// Extract | ||
if (opts.package == null) opts.package = 'custom runtime package'; | ||
// Check | ||
if (!editions || editions.length === 0) { | ||
throw new Error('No editions have been specified for the package [' + name + ']'); | ||
throw new DetailedError('No editions were specified:', { opts: opts }); | ||
} | ||
// Note the last error message | ||
var lastEditionFailure = void 0; | ||
var editionFailures = []; | ||
// Cycle through the editions | ||
for (var i = 0; i < editions.length; ++i) { | ||
// Extract relevant parts out of the edition | ||
// and generate a lower case, sorted, and joined combination of our syntax for caching | ||
var _editions$i = editions[i]; | ||
var syntaxes = _editions$i.syntaxes; | ||
var entry = _editions$i.entry; | ||
var directory = _editions$i.directory; | ||
// syntaxes:?array, entry:?string, directory:?string | ||
for (var _i = 0; _i < editions.length; ++_i) { | ||
var edition = editions[_i]; | ||
try { | ||
return requireEdition(edition, opts); | ||
} catch (err) { | ||
editionFailures.push(err); | ||
} | ||
} | ||
// Checks with hard failures to alert the developer | ||
// Through the error as no edition loaded | ||
throw new DetailedError('There are no suitable editions for this environment:', { opts: opts, editions: editions, failures: editionFailures }); | ||
} | ||
if (customEntry && !directory) { | ||
throw new Error('The package [' + name + '] has no directory property on its editions which is required when using custom entry point: ' + customEntry); | ||
} else if (!entry) { | ||
throw new Error('The package [' + name + '] has no entry property on its editions which is required when using default entry'); | ||
} | ||
/** | ||
* Cycle through the editions for a package and require the correct one | ||
* @param {string} cwd - the path of the package, used to load package.json:editions and handle relative edition entry points | ||
* @param {function} require - the require method of the calling module, used to ensure require paths remain correct | ||
* @param {string} [entry] - an optional override for the entry of an edition, requires the edition to specify a `directory` property | ||
* @returns {*} | ||
*/ | ||
function requirePackage(cwd /* :string */, require /* :function */, entry /* :: ?:string */) /* :any */{ | ||
// Load the package.json file to fetch `name` for debugging and `editions` for loading | ||
var packagePath = pathUtil.resolve(cwd, 'package.json'); | ||
// Get the correct entry path | ||
var entryPath = customEntry ? pathUtil.resolve(cwd, directory, customEntry) : pathUtil.resolve(cwd, entry); | ||
var _require = require(packagePath); | ||
// Check syntax support | ||
// Convert syntaxes into a sorted lowercase string | ||
var syntaxCombination = syntaxes && syntaxes.map(function (i) { | ||
return i.toLowerCase(); | ||
}).sort().join(', '); | ||
if (syntaxCombination) { | ||
// Check if any of the syntaxes are unsupported | ||
var unsupportedSyntaxes = syntaxes.filter(function (i) { | ||
return syntaxSupport[i.toLowerCase()] === false; | ||
}); | ||
if (unsupportedSyntaxes.length) { | ||
lastEditionFailure = new Error('Skipped package [' + name + '] edition at [' + entryPath + '] with unsupported syntaxes [' + unsupportedSyntaxes + ']'); | ||
if (debug) console.error('DEBUG: ' + lastEditionFailure.stack); | ||
continue; | ||
} | ||
// Is this syntax combination unsupported? If so skip it with a soft failure to try the next edition | ||
else if (syntaxFailedCombitions[syntaxCombination]) { | ||
var syntaxFailure = syntaxFailedCombitions[syntaxCombination]; | ||
lastEditionFailure = new NestedError('Skipped package [' + name + '] edition at [' + entryPath + '] with blacklisted syntax combination [' + syntaxCombination + ']', syntaxFailure); | ||
if (debug) console.error('DEBUG: ' + lastEditionFailure.stack); | ||
continue; | ||
} | ||
} | ||
var name = _require.name; | ||
var editions = _require.editions; | ||
// Try and load this syntax combination | ||
try { | ||
return _require(entryPath); | ||
} catch (error) { | ||
// Note the error with more details | ||
lastEditionFailure = new NestedError('Unable to load package [' + name + '] edition at [' + entryPath + '] with syntax [' + (syntaxCombination || 'no syntaxes specified') + ']', error); | ||
if (debug) console.error('DEBUG: ' + lastEditionFailure.stack); | ||
var opts /* :options */ = { cwd: cwd, require: require }; | ||
if (name) opts.package = name; | ||
if (entry) opts.entry = entry; | ||
return requireEditions(editions, opts); | ||
} | ||
// Blacklist the combination, even if it may have worked before | ||
// Perhaps in the future note if that if it did work previously, then we should instruct module owners to be more specific with their syntaxes | ||
if (syntaxCombination) syntaxFailedCombitions[syntaxCombination] = lastEditionFailure; | ||
} | ||
} | ||
// No edition was returned, so there is no suitable edition | ||
if (!lastEditionFailure) lastEditionFailure = new Error('The package [' + name + '] failed without any actual errors...'); | ||
throw new NestedError('The package [' + name + '] has no suitable edition for this environment', lastEditionFailure); | ||
}; | ||
// Exports | ||
module.exports = { requireEdition: requireEdition, requireEditions: requireEditions, requirePackage: requirePackage }; |
# History | ||
## v1.3.1 2016 October 11 | ||
- Fixed failure to load editions that had the edition directory within the edition entry | ||
- Thanks to [Jordan Harband](https://github.com/ljharb) for [issue #20](https://github.com/bevry/editions/issues/20) | ||
## v1.3.0 2016 October 11 | ||
- Added support for `EDITIONS_SYNTAX_BLACKLIST` environment variable | ||
- Thanks to [Damon Maria](https://github.com/damonmaria) for [issue #10](https://github.com/bevry/editions/issues/10) | ||
- Dropped need for `DEBUG_BEVRY_EDITIONS` as failures will not output all the necessary debugging information | ||
## v1.2.1 2016 October 10 | ||
@@ -4,0 +13,0 @@ - Change `esnext` skip from v8 engines < 4 to node engines < 0.12 |
{ | ||
"name": "editions", | ||
"version": "1.2.1", | ||
"version": "1.3.1", | ||
"description": "Publish multiple editions for your JavaScript packages consistently and easily (e.g. source edition, esnext edition, es2015 edition)", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/bevry/editions", |
@@ -5,103 +5,182 @@ /* @flow */ | ||
// Imports | ||
const pathUtil = require('path') | ||
// Helper class to display nested error in a sensible way | ||
class NestedError extends Error { | ||
constructor (message /* :string */, parent /* :Error */) { | ||
message += ' due to next parent error' | ||
class DetailedError extends Error { | ||
constructor (message /* :string */, details /* :Object */) { | ||
Object.keys(details).forEach(function (key) { | ||
const data = details[key] | ||
const value = require('util').inspect(data.stack || data.message || data) | ||
message += `\n${key}: ${value}` | ||
}) | ||
super(message) | ||
this.stack += '\n\nParent ' + (parent.stack || parent.message || parent).toString() | ||
} | ||
} | ||
// Always the same | ||
const EARLIST_NODE_VERSION_THAT_SUPPORTS_ESNEXT = 0.12 | ||
// Environment fetching | ||
const blacklist = process && process.env && process.env.EDITIONS_SYNTAX_BLACKLIST && process.env.EDITIONS_SYNTAX_BLACKLIST.split(',') | ||
// Cache of which syntax combinations are supported or unsupported, hash of booleans | ||
const EARLIST_NODE_VERSION_THAT_SUPPORTS_ESNEXT = 0.12 | ||
const syntaxFailedCombitions = {} // sorted lowercase syntax combination => Error instance of failure | ||
const syntaxSupport = { | ||
esnext: (process && process.versions && process.versions.node && Number(process.versions.node.split('.').slice(0, 2).join('.')) < EARLIST_NODE_VERSION_THAT_SUPPORTS_ESNEXT) ? false : null, | ||
import: false, | ||
coffeescript: false, | ||
typescript: false | ||
const syntaxBlacklist = {} | ||
syntaxBlacklist.import = new Error('The import syntax is skipped as the module package.json field eliminates the need for autoloader support') | ||
syntaxBlacklist.coffeescript = new Error('The coffeescript syntax is skipped as we want to use a precompiled edition rather than compiling at runtime') | ||
syntaxBlacklist.typescript = new Error('The typescript syntax is skipped as we want to use a precompiled edition rather than compiling at runtime') | ||
if ( process && process.versions && process.versions.node && Number(process.versions.node.split('.').slice(0, 2).join('.')) < EARLIST_NODE_VERSION_THAT_SUPPORTS_ESNEXT ) { | ||
syntaxBlacklist.esnext = new Error('The esnext syntax is skipped on early node versions as attempting to use esnext features will output debugging information on these node versions') | ||
} | ||
// Check the environment configuration for a syntax blacklist | ||
if ( blacklist ) { | ||
for ( let i = 0; i < blacklist.length; ++i ) { | ||
const syntax = blacklist[i].trim().toLowerCase() | ||
syntaxBlacklist[syntax] = new DetailedError('The EDITIONS_SYNTAX_BLACKLIST environment variable has blacklisted an edition syntax:', {syntax, blacklist}) | ||
} | ||
} | ||
/* :: | ||
type edition = { | ||
name:number, | ||
description?:string, | ||
directory?:string, | ||
entry?:string, | ||
syntaxes?:Array<string> | ||
}; | ||
type options = { | ||
cwd?:string, | ||
package?:string, | ||
entry?:string, | ||
require:function | ||
}; | ||
*/ | ||
/** | ||
* Cycle through the editions for a package and require the correct one | ||
* @param {string} cwd - the path of the package, used to load package.json:editions and handle relative edition entry points | ||
* @param {function} _require - the require method of the calling module, used to ensure require paths remain correct | ||
* @param {string} [customEntry] - an optional override for the entry of an edition, requires the edition to specify a `directory` property | ||
* @returns {void} | ||
* Cycle through the editions and require the correct one | ||
* @protected internal function that is untested for public consumption | ||
* @param {edition} edition - the edition entry | ||
* @param {Object} opts - the following options | ||
* @param {string} opts.require - the require method of the calling module, used to ensure require paths remain correct | ||
* @param {string} [opts.cwd] - if provided, this will be the cwd for entries | ||
* @param {string} [opts.entry] - if provided, should be a relative or absolute path to the entry point of the edition | ||
* @param {string} [opts.package] - if provided, should be the name of the package that we are loading the editions for | ||
* @returns {*} | ||
*/ | ||
module.exports.requirePackage = function requirePackage (cwd /* :string */, _require /* :function */, customEntry /* :: ?:string */ ) /* : void */ { | ||
// Fetch the result of the debug value | ||
// It is here to allow the environment to change it at runtime as needed | ||
const debug = process && process.env && process.env.DEBUG_BEVRY_EDITIONS | ||
// const blacklist = process && process.env && process.env.DEBUG_BEVRY_IGNORE_BLACKLIST | ||
function requireEdition ( edition /* :edition */, opts /* :options */ ) /* :any */ { | ||
// Prevent require from being included in debug logs | ||
Object.defineProperty(opts, 'require', {value: opts.require, enumerable: false}) | ||
// Load the package.json file to fetch `name` for debugging and `editions` for loading | ||
const pathUtil = require('path') | ||
const packagePath = pathUtil.join(cwd, 'package.json') | ||
const {name, editions} = require(packagePath) | ||
// name:string, editions:array | ||
// Get the correct entry path | ||
// As older versions o | ||
const cwd = opts.cwd || '' | ||
const dir = edition.directory || '' | ||
let entry = opts.entry || edition.entry || '' | ||
if ( dir && entry && entry.indexOf(dir + '/') === 0 ) entry = entry.substring(dir.length + 1) | ||
// ^ this should not be needed, but as previous versions of editions included the directory inside the entry | ||
// it unfortunately is, as such this is a stepping stone for the new format, the new format being | ||
// if entry is specified by itself, it is cwd => entry | ||
// if entry is specified with a directory, it is cwd => dir => entry | ||
// if entry is not specified but dir is, it is cwd => dir | ||
// if neither entry nor dir are specified, we have a problem | ||
if ( !dir && !entry ) { | ||
const editionFailure = new DetailedError('Skipped edition due to no entry or directory being specified:', {edition, cwd, dir, entry}) | ||
throw editionFailure | ||
} | ||
const entryPath = pathUtil.resolve(cwd, dir, entry) | ||
// Check syntax support | ||
// Convert syntaxes into a sorted lowercase string | ||
const syntaxes = edition.syntaxes && edition.syntaxes.map((i) => i.toLowerCase()).sort() | ||
const syntaxCombination = syntaxes && syntaxes.join(', ') | ||
if ( syntaxes && syntaxCombination ) { | ||
// Check if any of the syntaxes are unsupported | ||
const unsupportedSyntaxes = syntaxes.filter((i) => syntaxBlacklist[i.toLowerCase()]) | ||
if ( unsupportedSyntaxes.length ) { | ||
const editionFailure = new DetailedError('Skipped edition due to it containing an unsupported syntax:', {edition, unsupportedSyntaxes}) | ||
throw editionFailure | ||
} | ||
// Is this syntax combination unsupported? If so skip it with a soft failure to try the next edition | ||
else if ( syntaxFailedCombitions[syntaxCombination] ) { | ||
const previousCombinationFailure = syntaxFailedCombitions[syntaxCombination] | ||
const editionFailure = new DetailedError('Skipped edition due to its syntax combinatiom failing previously:', {edition, previousCombinationFailure}) | ||
throw editionFailure | ||
} | ||
} | ||
// Try and load this syntax combination | ||
try { | ||
return opts.require(entryPath) | ||
} | ||
catch ( error ) { | ||
// Note the error with more details | ||
const editionFailure = new DetailedError('Failed to load the edition due to a load error:', {edition, error: error.stack}) | ||
// Blacklist the combination, even if it may have worked before | ||
// Perhaps in the future note if that if it did work previously, then we should instruct module owners to be more specific with their syntaxes | ||
if ( syntaxCombination ) syntaxFailedCombitions[syntaxCombination] = editionFailure | ||
// Continue to the next edition | ||
throw editionFailure | ||
} | ||
} | ||
/** | ||
* Cycle through the editions and require the correct one | ||
* @protected internal function that is untested for public consumption | ||
* @param {Array<edition>} editions - an array of edition entries | ||
* @param {Object} opts - the following options | ||
* @param {string} opts.require - the require method of the calling module, used to ensure require paths remain correct | ||
* @param {string} [opts.cwd] - if provided, this will be the cwd for entries | ||
* @param {string} [opts.entry] - if provided, should be a relative path to the entry point of the edition | ||
* @param {string} [opts.package] - if provided, should be the name of the package that we are loading the editions for | ||
* @returns {*} | ||
*/ | ||
function requireEditions ( editions /* :Array<edition> */, opts /* :options */ ) /* :any */ { | ||
// Extract | ||
if ( opts.package == null ) opts.package = 'custom runtime package' | ||
// Check | ||
if ( !editions || editions.length === 0 ) { | ||
throw new Error(`No editions have been specified for the package [${name}]`) | ||
throw new DetailedError('No editions were specified:', {opts}) | ||
} | ||
// Note the last error message | ||
let lastEditionFailure | ||
const editionFailures = [] | ||
// Cycle through the editions | ||
for ( let i = 0; i < editions.length; ++i ) { | ||
// Extract relevant parts out of the edition | ||
// and generate a lower case, sorted, and joined combination of our syntax for caching | ||
const {syntaxes, entry, directory} = editions[i] | ||
// syntaxes:?array, entry:?string, directory:?string | ||
// Checks with hard failures to alert the developer | ||
if ( customEntry && !directory ) { | ||
throw new Error(`The package [${name}] has no directory property on its editions which is required when using custom entry point: ${customEntry}`) | ||
} | ||
else if ( !entry ) { | ||
throw new Error(`The package [${name}] has no entry property on its editions which is required when using default entry`) | ||
} | ||
// Get the correct entry path | ||
const entryPath = customEntry ? pathUtil.resolve(cwd, directory, customEntry) : pathUtil.resolve(cwd, entry) | ||
// Check syntax support | ||
// Convert syntaxes into a sorted lowercase string | ||
const syntaxCombination = syntaxes && syntaxes.map((i) => i.toLowerCase()).sort().join(', ') | ||
if ( syntaxCombination ) { | ||
// Check if any of the syntaxes are unsupported | ||
const unsupportedSyntaxes = syntaxes.filter((i) => syntaxSupport[i.toLowerCase()] === false) | ||
if ( unsupportedSyntaxes.length ) { | ||
lastEditionFailure = new Error(`Skipped package [${name}] edition at [${entryPath}] with unsupported syntaxes [${unsupportedSyntaxes}]`) | ||
if ( debug ) console.error(`DEBUG: ${lastEditionFailure.stack}`) | ||
continue | ||
} | ||
// Is this syntax combination unsupported? If so skip it with a soft failure to try the next edition | ||
else if ( syntaxFailedCombitions[syntaxCombination] ) { | ||
const syntaxFailure = syntaxFailedCombitions[syntaxCombination] | ||
lastEditionFailure = new NestedError(`Skipped package [${name}] edition at [${entryPath}] with blacklisted syntax combination [${syntaxCombination}]`, syntaxFailure) | ||
if ( debug ) console.error(`DEBUG: ${lastEditionFailure.stack}`) | ||
continue | ||
} | ||
} | ||
// Try and load this syntax combination | ||
const edition = editions[i] | ||
try { | ||
return _require(entryPath) | ||
return requireEdition(edition, opts) | ||
} | ||
catch ( error ) { | ||
// Note the error with more details | ||
lastEditionFailure = new NestedError(`Unable to load package [${name}] edition at [${entryPath}] with syntax [${syntaxCombination || 'no syntaxes specified'}]`, error) | ||
if ( debug ) console.error(`DEBUG: ${lastEditionFailure.stack}`) | ||
// Blacklist the combination, even if it may have worked before | ||
// Perhaps in the future note if that if it did work previously, then we should instruct module owners to be more specific with their syntaxes | ||
if ( syntaxCombination ) syntaxFailedCombitions[syntaxCombination] = lastEditionFailure | ||
catch ( err ) { | ||
editionFailures.push(err) | ||
} | ||
} | ||
// No edition was returned, so there is no suitable edition | ||
if ( !lastEditionFailure ) lastEditionFailure = new Error(`The package [${name}] failed without any actual errors...`) | ||
throw new NestedError(`The package [${name}] has no suitable edition for this environment`, lastEditionFailure) | ||
// Through the error as no edition loaded | ||
throw new DetailedError('There are no suitable editions for this environment:', {opts, editions, failures: editionFailures}) | ||
} | ||
/** | ||
* Cycle through the editions for a package and require the correct one | ||
* @param {string} cwd - the path of the package, used to load package.json:editions and handle relative edition entry points | ||
* @param {function} require - the require method of the calling module, used to ensure require paths remain correct | ||
* @param {string} [entry] - an optional override for the entry of an edition, requires the edition to specify a `directory` property | ||
* @returns {*} | ||
*/ | ||
function requirePackage (cwd /* :string */, require /* :function */, entry /* :: ?:string */ ) /* :any */ { | ||
// Load the package.json file to fetch `name` for debugging and `editions` for loading | ||
const packagePath = pathUtil.resolve(cwd, 'package.json') | ||
const {name, editions} = require(packagePath) | ||
const opts /* :options */ = {cwd, require} | ||
if ( name ) opts.package = name | ||
if ( entry ) opts.entry = entry | ||
return requireEditions(editions, opts) | ||
} | ||
// Exports | ||
module.exports = {requireEdition, requireEditions, requirePackage} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
31192
341
11
1