UNPKG

@fontoxml/fontoxml-development-tools

Version:

Development tools for Fonto.

437 lines (408 loc) 12.5 kB
import BackendAppRepository from './BackendAppRepository.js'; import EditorRepository from './EditorRepository.js'; import { getUpgradeSupportedVersions } from './versionMapping.js'; /** @typedef {import('./App').default} App */ /** @typedef {import('./Version').default} Version */ /** @typedef {import('./request/FdtRequest').default} FdtRequest */ /** @typedef {import('./response/FdtResponse').default} FdtResponse */ /** * All intents check if the specified version is the same minor range as the base version (i.e. the * fdt version). The only exception is `instance-for-update`, which also allows the specified * version to be one minor version lower. * * The intents are also used to output specific warning/error messages. * * @typedef {'instance' | 'instance-for-upgrade' | 'upgrade' | 'initialize' | 'download' | 'schema-compile'} CompatibilityIntent */ /** * @param {CompatibilityIntent} intent * @param {string} productLabel * @param {string|null} versionString * * @returns {string} */ function getDescriptionFromIntent(intent, productLabel, versionString = null) { switch (intent) { case 'initialize': case 'download': return ` to ${intent} ${productLabel}${ versionString ? ` ${versionString}` : '' }`; case 'upgrade': return ` to upgrade ${productLabel}${ versionString ? ` to ${versionString}` : '' }`; case 'schema-compile': return ` to compile a schema for ${productLabel}${ versionString ? ` ${versionString}` : '' }`; default: return ''; } } /** * @template {CompatibilityIntent} Intent * * @param {Intent} intent * @param {Version} fdtVersion * @param {Version} version * @param {string} productLabel * * @returns {Intent extends 'instance-for-upgrade' ? null : string} */ export function createCompatibilityMessageForTrue( intent, fdtVersion, version, productLabel ) { return `The FDT version ${fdtVersion.format()} does not match version ${version.format()} of ${productLabel}. To guarantee compatibility, please use exactly matching versions${getDescriptionFromIntent( intent, productLabel )}.`; } /** * @template {CompatibilityIntent} Intent * * @param {Intent} intent * @param {Version} fdtVersion * @param {Version} version * @param {string} productLabel * @param {Intent extends 'instance-for-upgrade' ? Version[] : undefined} upgradeSupportedVersions * * @returns {string} */ export function createCompatibilityMessageForFalse( intent, fdtVersion, version, productLabel, upgradeSupportedVersions ) { const versionsMessage = `The FDT version ${fdtVersion.format()} is a different minor version than ${productLabel} version ${version.format()}.`; const supportedTargetVersions = fdtVersion.isUnversioned ? fdtVersion.format() : `${fdtVersion.format('minor')}.x`; switch (intent) { case 'instance': return `${versionsMessage} This version of FDT only supports versions ${supportedTargetVersions}.`; case 'instance-for-upgrade': { const supportedVersions = upgradeSupportedVersions .map( (version) => `"${ version.isUnversioned ? version.format() : `${version.format('minor')}.x` }"` ) .join(', '); return `${versionsMessage} This version of FDT only supports upgrading from the following ${productLabel} versions: ${supportedVersions}.`; } case 'upgrade': case 'initialize': case 'schema-compile': case 'download': return `The FDT version ${fdtVersion.format()} should not be used${getDescriptionFromIntent( intent, productLabel, version.format() )}. This version of FDT only supports versions ${supportedTargetVersions}.`; default: throw new Error( `Cannot create compatibility message for false with intent ${intent}.` ); } } /** * @param {CompatibilityIntent} _intent * @param {Version} fdtVersion * @param {Version} version * @param {string} productLabel * * @returns {string} */ export function createCompatibilityMessageForNightlyA( _intent, fdtVersion, version, productLabel ) { return `The FDT version ${fdtVersion.format()} is a nightly. This may not be compatible with ${productLabel} version ${version.format()}.`; } /** * @param {CompatibilityIntent} intent * @param {Version} fdtVersion * @param {Version} version * @param {string} productLabel * * @returns {string} */ export function createCompatibilityMessageForNightlyB( intent, fdtVersion, version, productLabel ) { switch (intent) { case 'instance': case 'instance-for-upgrade': return `The current ${productLabel} version ${version.format()} is a nightly. This may not be compatible with FDT version ${fdtVersion.format()}.`; case 'upgrade': case 'initialize': case 'schema-compile': case 'download': return `You selected version ${version.format()}${getDescriptionFromIntent( intent, productLabel )}. This is a nightly, which may not be compatible with FDT version ${fdtVersion.format()}.`; default: throw new Error( `Cannot create compatibility message for nightly_b with intent ${intent}.` ); } } /** * @param {CompatibilityIntent} _intent * @param {Version} fdtVersion * @param {Version} version * @param {string} productLabel * * @returns {string} */ function createCompatibilityMessageForUnparsedLiteralA( _intent, fdtVersion, version, productLabel ) { return `Unable to parse the FDT version ${fdtVersion.format()}. Please try reinstalling FDT, or otherwise make sure it is compatible with ${productLabel} version ${version.format()}.`; } /** * @param {CompatibilityIntent} _intent * @param {Version} fdtVersion * @param {Version} version * @param {string} productLabel * * @returns {string} */ function createCompatibilityMessageForUnparsedLiteralB( _intent, fdtVersion, version, productLabel ) { return `Unable to parse version ${version.format()} of ${productLabel}. Please make sure it is compatible with FDT version ${fdtVersion.format()}.`; } /** * @param {FdtRequest} request * @param {FdtResponse} response * @param {Version} version * @param {CompatibilityIntent} intent * @param {string} [productId] * @param {string} [productLabel] * * @throws An error when it is not compatible. * @return {void | never} */ function ensureCompatibilityWithFdt( request, response, version, intent, productId, productLabel ) { /** @type {Version} */ const fdtVersion = request.fdt.version; /** @type {boolean} */ const fdtIsLocalVersion = !!request.fdt.isLocalVersion; // For upgrade: // * fdtVersion === versionToUpgradeTo (Should be checked in the upgrade commands) // * currentVersion === versionToUpgradeTo - 1minor // * currentVersion === fdtVersion - 1minor // Determine the product label. if (!productLabel) { productLabel = productId ? request.fdt.license.getProductLabel(productId) || `Fonto ${productId}` : 'Fonto project'; } const upgradeSupportedVersions = intent === 'instance-for-upgrade' ? getUpgradeSupportedVersions(fdtVersion) : null; const compatibilityResult = fdtVersion.checkCompatibility( version, intent === 'instance-for-upgrade' ? 'upgrade_minor' : 'minor', upgradeSupportedVersions ); switch (compatibilityResult) { case true: if ( !request.command.isRawOutput(request) && fdtVersion.compare(version) !== 0 && intent !== 'instance-for-upgrade' ) { // Output a warning to encourage the use of the exact same full version (including // patch, pre-release, etc.) if it is not. // Except for when the intent is 'instance-for-upgrade' because the current version // will most likely not match the FDT version, as you are upgrading to the FDT // version (of compatible version). response.notice( createCompatibilityMessageForTrue( intent, fdtVersion, version, productLabel ) ); response.break(); } return; case false: { const error = new response.ErrorWithSolution( createCompatibilityMessageForFalse( intent, fdtVersion, version, productLabel, upgradeSupportedVersions || [] ) ); // eslint-disable-next-line no-unneeded-ternary const defaultEnforceCompatibility = fdtIsLocalVersion ? false : // TODO: Change the `defaultEnforceCompatibility` to `true` when we want to start enforcing version compatibility. false; const enforceCompatibility = typeof process.env.FDT_ENFORCE_COMPATIBILITY !== 'string' ? defaultEnforceCompatibility : !['0', 'false', 'no', 'n', 'off'].includes( process.env.FDT_ENFORCE_COMPATIBILITY ); if (!enforceCompatibility) { if (!request.command.isRawOutput(request)) { response.error(error.message); if (error.solution) { response.break(); response.notice(error.solution); } response.break(); } return; } throw error; } case 'nightly_a': if (!request.command.isRawOutput(request)) { response.notice( createCompatibilityMessageForNightlyA( intent, fdtVersion, version, productLabel ) ); response.break(); } return; case 'nightly_b': if (!request.command.isRawOutput(request)) { response.notice( createCompatibilityMessageForNightlyB( intent, fdtVersion, version, productLabel ) ); response.break(); } return; case 'unparsed_literal_a': // Note that this can only happen when one of the versions is constructed with // `allowUnparsedLiteral` set to true. if (!request.command.isRawOutput(request)) { response.notice( createCompatibilityMessageForUnparsedLiteralA( intent, fdtVersion, version, productLabel ) ); response.break(); } return; case 'unparsed_literal_b': // Note that this can only happen when one of the versions is constructed with // `allowUnparsedLiteral` set to true. if (!request.command.isRawOutput(request)) { response.notice( createCompatibilityMessageForUnparsedLiteralB( intent, fdtVersion, version, productLabel ) ); response.break(); } return; default: throw new Error( `Version compatibility check did not return positive for ${intent} ("${compatibilityResult}").` ); } } /** * @param {FdtRequest} request * @param {FdtResponse} response * @param {App} app * * @return {Promise<void>} */ export default async function enrichRequestObject(request, response, app) { const backendAppRepository = new BackendAppRepository(); await backendAppRepository.init(app, request); const editorRepository = new EditorRepository(); await editorRepository.init(app, request); request.fdt = { version: app.version, isLocalVersion: !!app.isLocalVersion, backendAppRepository, editorRepository, getExportFromModule: app.getExportFromModule.bind(app), getPathToModule: app.getPathToModule.bind(app), license: app.license, /** * @param {Version} version The version to check compatibility with FDT for. * @param {CompatibilityIntent} intent Indicates the intent for the compatibility * check which affects the rules and messages. * @param {string} [productId] Used to determine the product label if * `productLabel` is not specified. * @param {string} [productLabel] Will try to get the label based on the * `procuctId` if not specified, or default to * 'Fonto project' if no `productId` is specified. * * @throws An error when it is not compatible. * @return {void | never} */ ensureCompatibilityWithFdt: ( version, intent, productId, productLabel ) => { return ensureCompatibilityWithFdt( request, response, version, intent, productId, productLabel ); }, }; }