UNPKG

@warp-drive/build-config

Version:

Provides Build Configuration for projects using WarpDrive

387 lines (367 loc) 13.2 kB
import EmbroiderMacros from '@embroider/macros/src/node.js'; import semver from 'semver'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { C as CURRENT_FEATURES } from './canary-features-BUBNGiZ-.js'; import { L as LOGGING } from './debugging-DgRWKeE4.js'; import { buildMacros } from '@embroider/macros/src/babel.js'; function getEnv(forceMode) { const FORCE_TESTING = forceMode === 'testing' || forceMode === 'development' || forceMode === 'debug'; const FORCE_DEBUG = forceMode === 'development' || forceMode === 'debug'; const FORCE_PRODUCTION = forceMode === 'production'; const { EMBER_ENV, IS_TESTING, EMBER_CLI_TEST_COMMAND, NODE_ENV, CI, IS_RECORDING } = process.env; const PRODUCTION = FORCE_PRODUCTION || EMBER_ENV === 'production' || !EMBER_ENV && NODE_ENV === 'production'; const DEBUG = FORCE_DEBUG || !PRODUCTION; const TESTING = FORCE_TESTING || DEBUG || Boolean(EMBER_ENV === 'test' || IS_TESTING || EMBER_CLI_TEST_COMMAND); const SHOULD_RECORD = Boolean(!CI || IS_RECORDING); return { TESTING, PRODUCTION, DEBUG, IS_RECORDING: Boolean(IS_RECORDING), IS_CI: Boolean(CI), SHOULD_RECORD }; } // ======================== // FOR CONTRIBUTING AUTHORS // // Deprecations here should also have guides PR'd to the emberjs deprecation app // // github: https://github.com/ember-learn/deprecation-app // website: https://deprecations.emberjs.com // // Each deprecation should also be given an associated URL pointing to the // relevant guide. // // URLs should be of the form: https://deprecations.emberjs.com/v<major>.x#toc_<fileName> // where <major> is the major version of the deprecation and <fileName> is the // name of the markdown file in the guides repo. // // ======================== // const DEPRECATE_CATCH_ALL = '99.0'; const DEPRECATE_NON_STRICT_TYPES = '5.3'; const DEPRECATE_NON_STRICT_ID = '5.3'; const DEPRECATE_COMPUTED_CHAINS = '7.0'; const DEPRECATE_LEGACY_IMPORTS = '5.3'; const DEPRECATE_NON_UNIQUE_PAYLOADS = '5.3'; const DEPRECATE_RELATIONSHIP_REMOTE_UPDATE_CLEARING_LOCAL_STATE = '5.3'; const DEPRECATE_MANY_ARRAY_DUPLICATES = '5.3'; const DEPRECATE_STORE_EXTENDS_EMBER_OBJECT = '5.4'; const ENABLE_LEGACY_SCHEMA_SERVICE = '5.4'; const DEPRECATE_EMBER_INFLECTOR = '5.3'; const DISABLE_7X_DEPRECATIONS = '7.0'; const DEPRECATE_TRACKING_PACKAGE = '5.5'; const ENABLE_LEGACY_REQUEST_METHODS = '5.6'; const CURRENT_DEPRECATIONS = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, DEPRECATE_CATCH_ALL, DEPRECATE_COMPUTED_CHAINS, DEPRECATE_EMBER_INFLECTOR, DEPRECATE_LEGACY_IMPORTS, DEPRECATE_MANY_ARRAY_DUPLICATES, DEPRECATE_NON_STRICT_ID, DEPRECATE_NON_STRICT_TYPES, DEPRECATE_NON_UNIQUE_PAYLOADS, DEPRECATE_RELATIONSHIP_REMOTE_UPDATE_CLEARING_LOCAL_STATE, DEPRECATE_STORE_EXTENDS_EMBER_OBJECT, DEPRECATE_TRACKING_PACKAGE, DISABLE_7X_DEPRECATIONS, ENABLE_LEGACY_REQUEST_METHODS, ENABLE_LEGACY_SCHEMA_SERVICE }, Symbol.toStringTag, { value: 'Module' })); function deprecationIsResolved(deprecatedSince, compatVersion) { return semver.lte(semver.minVersion(deprecatedSince), semver.minVersion(compatVersion)); } const NextMajorVersion = '6.'; function deprecationIsNextMajorCycle(deprecatedSince) { return deprecatedSince.startsWith(NextMajorVersion); } function getDeprecations(compatVersion, deprecations) { const flags = {}; const keys = Object.keys(CURRENT_DEPRECATIONS); const DISABLE_7X_DEPRECATIONS = deprecations?.DISABLE_7X_DEPRECATIONS ?? true; keys.forEach(flag => { const deprecatedSince = CURRENT_DEPRECATIONS[flag]; const isDeactivatedDeprecationNotice = DISABLE_7X_DEPRECATIONS && deprecationIsNextMajorCycle(deprecatedSince); let flagState = true; // default to no code-stripping if (!isDeactivatedDeprecationNotice) { // if we have a specific flag setting, use it if (typeof deprecations?.[flag] === 'boolean') { flagState = deprecations?.[flag]; } else if (compatVersion) { // if we are told we are compatible with a version // we check if we can strip this flag const isResolved = deprecationIsResolved(deprecatedSince, compatVersion); // if we've resolved, we strip (by setting the flag to false) /* if (DEPRECATED_FEATURE) { // deprecated code path } else { // if needed a non-deprecated code path } */ flagState = !isResolved; } } // console.log(`${flag}=${flagState} (${deprecatedSince} <= ${compatVersion})`); flags[flag] = flagState; }); return flags; } const dirname = typeof __dirname !== 'undefined' ? __dirname : fileURLToPath(new URL(".", import.meta.url)); const relativePkgPath = path.join(dirname, '../package.json'); const version = JSON.parse(fs.readFileSync(relativePkgPath, 'utf-8')).version; const isCanary = version.includes('alpha'); function getFeatures(isProd) { const features = Object.assign({}, CURRENT_FEATURES); const keys = Object.keys(features); if (!isCanary) { // disable all features with a current value of `null` for (const feature of keys) { let featureValue = features[feature]; if (featureValue === null) { features[feature] = false; } } return features; } const FEATURE_OVERRIDES = process.env.WARP_DRIVE_FEATURE_OVERRIDE; if (FEATURE_OVERRIDES === 'ENABLE_ALL_OPTIONAL') { // enable all features with a current value of `null` for (const feature of keys) { let featureValue = features[feature]; if (featureValue === null) { features[feature] = true; } } } else if (FEATURE_OVERRIDES === 'DISABLE_ALL') { // disable all features, including those with a value of `true` for (const feature of keys) { features[feature] = false; } } else if (FEATURE_OVERRIDES) { // enable only the specific features listed in the environment // variable (comma separated) const forcedFeatures = FEATURE_OVERRIDES.split(','); for (let i = 0; i < forcedFeatures.length; i++) { let featureName = forcedFeatures[i]; if (!keys.includes(featureName)) { throw new Error(`Unknown feature flag: ${featureName}`); } features[featureName] = true; } } if (isProd) { // disable all features with a current value of `null` for (const feature of keys) { let featureValue = features[feature]; if (featureValue === null) { features[feature] = false; } } } return features; } function createLoggingConfig(env, debug) { const config = {}; const keys = Object.keys(LOGGING); for (const key of keys) { if (env.DEBUG || env.TESTING) { config[key] = true; } else { config[key] = debug[key] || false; } } return config; } /** * This package provides a build-plugin that enables configuration of deprecations, * optional features, development/testing support and debug logging. * * This configuration is done using `setConfig` in `ember-cli-build`. * * ```ts [ember-cli-build.js] * 'use strict'; * * const EmberApp = require('ember-cli/lib/broccoli/ember-app'); * * module.exports = async function (defaults) { * const { setConfig } = await import('@warp-drive/build-config'); // [!code focus] * * const app = new EmberApp(defaults, {}); * * setConfig(app, __dirname, { // [!code focus:3] * // settings here * }); * * const { buildOnce } = await import('@embroider/vite'); * const { compatBuild } = await import('@embroider/compat'); * * return compatBuild(app, buildOnce); * }; * * ``` * * Available settings include: * * - {@link LOGGING | debugging} * - {@link DEPRECATIONS | deprecations} * - {@link FEATURES | features} * - {@link WarpDriveConfig.polyfillUUID | polyfillUUID} * - {@link WarpDriveConfig.includeDataAdapterInProduction | includeDataAdapterInProduction} * - {@link WarpDriveConfig.compatWith | compatWith} * * @module */ /** * Create the Babel plugin for WarpDrive * * Note: If your project already uses [@embroider/macros](https://www.npmjs.com/package/@embroider/macros) * then you should use {@link setConfig} instead of this function. * * @param options WarpDrive configuration options * @returns An array of Babel plugins */ function babelPlugin(options) { const macros = buildMacros({ configure: config => { setConfig(config, options); } }); const env = getEnv(options.forceMode); return { gts: macros.templateMacros, js: [ // babel-plugin-debug-macros is temporarily needed // to convert deprecation/warn calls into console.warn [resolve('babel-plugin-debug-macros'), { flags: [], debugTools: { isDebug: env.DEBUG, source: '@ember/debug', assertPredicateIndex: 1 } }, 'ember-data-specific-macros-stripping-test'], ...macros.babelMacros] }; } function resolve(module) { const filePath = import.meta.resolve(module); const file = filePath.replace('/node_modules/.vite-temp/', '/'); if (file.startsWith('file://')) { return file.slice(7); } return file; } const _MacrosConfig = EmbroiderMacros.MacrosConfig; /** * Build Configuration options for WarpDrive that * allow adjusting logging, deprecations, canary features * and optional features. */ function recastMacrosConfig(macros) { if (!('globalConfig' in macros)) { throw new Error('Expected MacrosConfig to have a globalConfig property'); } return macros; } /** * Sets the build configuration for WarpDrive that ensures * environment specific behaviors are activated/deactivated * and enables adjusting log instrumentation, removing code * that supports deprecated features, enabling canary features * and enabling/disabling optional features. * * The library uses [@embroider/macros](https://www.npmjs.com/package/@embroider/macros) * to perform this final configuration code transform. * * This is a low level API for configuring WarpDrive. If your * project does not use `@embroider/macros` then you should use * {@link babelPlugin} instead of this function. * * ### Example * * ```ts * import { setConfig } from '@warp-drive/core/build-config'; * import { buildMacros } from '@embroider/macros/babel'; * * const Macros = buildMacros({ * configure: (config) => { * setConfig(config, { * compatWith: '5.6' * }); * }, * }); * * export default { * plugins: [ * // babel-plugin-debug-macros is temporarily needed * // to convert deprecation/warn calls into console.warn * [ * 'babel-plugin-debug-macros', * { * flags: [], * * debugTools: { * isDebug: true, * source: '@ember/debug', * assertPredicateIndex: 1, * }, * }, * 'ember-data-specific-macros-stripping-test', * ], * ...Macros.babelMacros, * ], * }; * ``` */ function setConfig(context, appRootOrConfig, config) { const isEmberClassicUsage = arguments.length === 3; const macros = recastMacrosConfig(isEmberClassicUsage ? _MacrosConfig.for(context, appRootOrConfig) : context); const userConfig = isEmberClassicUsage ? config : appRootOrConfig; const isLegacySupport = userConfig.___legacy_support; const hasDeprecatedConfig = isLegacySupport && Object.keys(userConfig).length > 1; const hasInitiatedConfig = macros.globalConfig['WarpDrive']; // setConfig called by user prior to legacy support called if (isLegacySupport && hasInitiatedConfig) { if (hasDeprecatedConfig) { throw new Error('You have provided a config object to setConfig, but are also using the legacy emberData options key in ember-cli-build. Please remove the emberData key from options.'); } return; } // included hooks run during class initialization of the EmberApp instance // so our hook will run before the user has a chance to call setConfig // else we could print a useful message here // else if (isLegacySupport) { // console.warn( // `WarpDrive requires your ember-cli-build file to set a base configuration for the project.\n\nUsage:\n\t\`import { setConfig } from '@warp-drive/build-config';\n\tsetConfig(app, __dirname, {});\`` // ); // } const debugOptions = Object.assign({}, LOGGING, userConfig.debug); const env = getEnv(userConfig.forceMode); const DEPRECATIONS = getDeprecations(userConfig.compatWith || null, userConfig.deprecations); const FEATURES = getFeatures(env.PRODUCTION); const includeDataAdapterInProduction = typeof userConfig.includeDataAdapterInProduction === 'boolean' ? userConfig.includeDataAdapterInProduction : true; const includeDataAdapter = env.PRODUCTION ? includeDataAdapterInProduction : true; const finalizedConfig = { debug: debugOptions, polyfillUUID: userConfig.polyfillUUID ?? false, includeDataAdapter, compatWith: userConfig.compatWith ?? null, deprecations: DEPRECATIONS, features: FEATURES, activeLogging: createLoggingConfig(env, debugOptions), env }; macros.setGlobalConfig(import.meta.filename, 'WarpDrive', finalizedConfig); } export { babelPlugin, setConfig };