| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 | /* @flow *//* eslint no-console:0 */'use strict'// Importsconst pathUtil = require('path')// Helper class to display nested error in a sensible wayclass 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)	}}// Environment fetchingconst 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 booleansconst syntaxFailedCombitions = {}  // sorted lowercase syntax combination => Error instance of failureconst 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')// Blacklist non-esnext node versions from esnextif ( process && process.versions && process.versions.node ) {	const EARLIEST_ESNEXT_NODE_VERSION = [0, 12]	const NODE_VERSION = process.versions.node.split('.').map((n) => parseInt(n, 10))	const ESNEXT_UNSUPPORTED = NODE_VERSION[0] < EARLIEST_ESNEXT_NODE_VERSION[0] || (		NODE_VERSION[0] === EARLIEST_ESNEXT_NODE_VERSION[0] &&		NODE_VERSION[1] < EARLIEST_ESNEXT_NODE_VERSION[1]	)	if ( ESNEXT_UNSUPPORTED )  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 blacklistif ( 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 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 {*} */function requireEdition ( edition /* :edition */, opts /* :options */ ) /* :any */ {	// Prevent require from being included in debug logs	Object.defineProperty(opts, 'require', {value: opts.require, enumerable: false})	// 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 DetailedError('No editions were specified:', {opts})	}	// Note the last error message	const editionFailures = []	// Cycle through the editions	for ( let i = 0; i < editions.length; ++i ) {		const edition = editions[i]		try {			return requireEdition(edition, opts)		}		catch ( err ) {			editionFailures.push(err)		}	}	// 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)}// Exportsmodule.exports = {requireEdition, requireEditions, requirePackage}
 |