UNPKG

@netlify/build

Version:
111 lines (110 loc) 6.11 kB
import _pEvery from 'p-every'; import pLocate from 'p-locate'; import semver from 'semver'; import { CONDITIONS } from './plugin_conditions.js'; // the types of that package seem to be not correct and demand a `pEvery.default()` usage which is wrong const pEvery = _pEvery; /** * Retrieve the `expectedVersion` of a plugin: * - This is the version which should be run * - This takes version pinning into account * - If this does not match the currently cached version, it is installed first * This is also used to retrieve the `compatibleVersion` of a plugin * - This is the most recent version compatible with this site * - This is the same logic except it does not use version pinning * - This is only used to print a warning message when the `compatibleVersion` * is older than the currently used version. */ export const getExpectedVersion = async function ({ versions, nodeVersion, packageJson, packageName, packagePath, buildDir, pinnedVersion, featureFlags, systemLog, authoritative, }) { const { version, conditions = [] } = await getCompatibleEntry({ versions, nodeVersion, packageJson, packageName, packagePath, buildDir, pinnedVersion, featureFlags, systemLog: authoritative ? systemLog : undefined, }); // Retrieve warning message shown when using an older version with `compatibility` const compatWarning = conditions.map(({ type, condition }) => CONDITIONS[type].warning(condition)).join(', '); return { version, compatWarning }; }; /** * This function finds the right `compatibility` entry to use with the plugin. * - `compatibility` entries are meant for backward compatibility * Plugins should define each major version in `compatibility`. * - The entries are sorted from most to least recent version. * - After their first successful run, plugins are pinned by their major * version which is passed as `pinnedVersion` to the next builds. * When the plugin does not have a `pinnedVersion`, we use the most recent * `compatibility` entry with a successful condition. * When the plugin has a `pinnedVersion`, we do not use the `compatibility` * conditions. Instead, we just use the most recent entry with a `version` * matching `pinnedVersion`. * When no `compatibility` entry matches, we use: * - If there is a `pinnedVersion`, use it unless `latestVersion` matches it * - Otherwise, use `latestVersion` */ const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson, packageName, packagePath, buildDir, pinnedVersion, featureFlags, systemLog = () => { // no-op }, }) { const compatibleEntry = await pLocate(versions, async ({ version, overridePinnedVersion, conditions }) => { // When there's a `pinnedVersion`, we typically pick the first version that // matches that range. The exception is if `overridePinnedVersion` is also // present. This property says that if the pinned version is within a given // range, the entry that has this property can be used instead, even if its // own version doesn't satisfy the pinned version. const overridesPin = Boolean(pinnedVersion && overridePinnedVersion && semver.intersects(overridePinnedVersion, pinnedVersion)); // If there's a pinned version and this entry doesn't satisfy that range, // discard it. The exception is if this entry overrides the pinned version. if (pinnedVersion && !overridesPin && !semver.satisfies(version, pinnedVersion, { includePrerelease: true })) { return false; } // no conditions means nothing to filter if (conditions.length === 0 && pinnedVersion === undefined) { return false; } return await pEvery(conditions, async ({ type, condition }) => CONDITIONS[type].test(condition, { nodeVersion, packageJson, packagePath, buildDir })); }); if (compatibleEntry) { systemLog(`Used compatible version '${compatibleEntry.version}' for plugin '${packageName}' (pinned version is ${pinnedVersion})`); return compatibleEntry; } if (pinnedVersion) { systemLog(`Used pinned version '${pinnedVersion}' for plugin '${packageName}'`); return { version: pinnedVersion, conditions: [] }; } const legacyFallback = { version: versions[0].version, conditions: [] }; const fallback = await getFirstCompatibleEntry({ versions, nodeVersion, packageJson, packagePath, buildDir }); if (featureFlags?.netlify_build_updated_plugin_compatibility) { if (legacyFallback.version !== fallback.version) { systemLog(`Detected mismatch in selected version for plugin '${packageName}': used new version of '${fallback.version}' over legacy version '${legacyFallback.version}'`); } return fallback; } if (legacyFallback.version !== fallback.version) { systemLog(`Detected mismatch in selected version for plugin '${packageName}': used legacy version '${legacyFallback.version}' over new version '${fallback.version}'`); } return legacyFallback; }; /** * Takes a list of plugin versions and returns the first entry that satisfies * the conditions (if any), without taking into account the pinned version. */ const getFirstCompatibleEntry = async function ({ versions, nodeVersion, packageJson, packagePath, buildDir, }) { const compatibleEntry = await pLocate(versions, async ({ conditions }) => { if (conditions.length === 0) { return true; } return await pEvery(conditions, async ({ type, condition }) => CONDITIONS[type].test(condition, { nodeVersion, packageJson, packagePath, buildDir })); }); if (compatibleEntry) { return compatibleEntry; } // We should never get here, because it means there are no plugin versions // that we can install. We're keeping this here because it has been the // default behavior for a long time, but we should look to remove it. return { version: versions[0].version, conditions: [] }; };