UNPKG

@warp-drive/build-config

Version:

Provides Build Configuration for projects using WarpDrive or EmberData

693 lines (666 loc) 26.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-BzGSGY5j.js'; import { L as LOGGING } from './debugging-PCb4hczb.js'; function getEnv() { const { EMBER_ENV, IS_TESTING, EMBER_CLI_TEST_COMMAND, NODE_ENV, CI, IS_RECORDING } = process.env; const PRODUCTION = EMBER_ENV === 'production' || !EMBER_ENV && NODE_ENV === 'production'; const DEBUG = !PRODUCTION; const 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. // // ======================== // /** * ## Deprecations * * EmberData allows users to opt-in and remove code that exists to support deprecated * behaviors. * * If your app has resolved all deprecations present in a given version, * you may specify that version as your "compatibility" version to remove * the code that supported the deprecated behavior from your app. * * For instance, if a deprecation was introduced in 3.13, and the app specifies * 3.13 as its minimum version compatibility, any deprecations introduced before * or during 3.13 would be stripped away. * * An app can use a different version than what it specifies as it's compatibility * version. For instance, an App could be using `3.16` while specifying compatibility * with `3.12`. This would remove any deprecations that were present in or before `3.12` * but keep support for anything deprecated in or above `3.13`. * * ### Configuring Compatibility * * To configure your compatibility version, set the `compatWith` to the version you * are compatible with on the `emberData` config in your `ember-cli-build.js` file. * * ```js * const { setConfig } = await import('@warp-drive/build-config'); * * let app = new EmberApp(defaults, {}); * * setConfig(app, __dirname, { compatWith: '3.12' }); * ``` * * Alternatively, individual deprecations can be resolved (and thus have its support stripped) * via one of the flag names listed below. For instance, given a flag named `DEPRECATE_FOO_BEHAVIOR`. * * This capability is interopable with `compatWith`. You may set `compatWith` and then selectively resolve * additional deprecations, or set compatWith and selectively un-resolve specific deprecations. * * Note: EmberData does not test against permutations of deprecations being stripped, our tests run against * "all deprecated code included" and "all deprecated code removed". Unspecified behavior may sometimes occur * when removing code for only some deprecations associated to a version number. * * ```js * const { setConfig } = await import('@warp-drive/build-config'); * * let app = new EmberApp(defaults, {}); * * setConfig(app, __dirname, { * deprecations: { * DEPRECATE_FOO_BEHAVIOR: false // set to false to strip this code * DEPRECATE_BAR_BEHAVIOR: true // force to true to not strip this code * } * }); * ``` * * The complete list of which versions specific deprecations will be removed in * can be found [here](https://github.com/emberjs/data/blob/main/packages/build-config/src/virtual/deprecation-versions.ts "List of EmberData Deprecations") * * @module @warp-drive/build-config/deprecations * @main @warp-drive/build-config/deprecations */ /** * The following list represents deprecations currently active. * * Some deprecation flags guard multiple deprecation IDs. All * associated IDs are listed. * * @class CurrentDeprecations * @public */ const DEPRECATE_CATCH_ALL = '99.0'; /** * **id: ember-data:deprecate-non-strict-types** * * Currently, EmberData expects that the `type` property associated with * a resource follows several conventions. * * - The `type` property must be a non-empty string * - The `type` property must be singular * - The `type` property must be dasherized * * We are deprecating support for types that do not match this pattern * in order to unlock future improvements in which we can support `type` * being any string of your choosing. * * The goal is that in the future, you will be able to use any string * so long as it matches what your configured cache, identifier generation, * and schemas expect. * * E.G. It will matter not that your string is in a specific format like * singular, dasherized, etc. so long as everywhere you refer to the type * you use the same string. * * If using @ember-data/model, there will always be a restriction that the * `type` must match the path on disk where the model is defined. * * e.g. `app/models/foo/bar-bem.js` must have a type of `foo/bar-bem` * * @property DEPRECATE_NON_STRICT_TYPES * @since 5.3 * @until 6.0 * @public */ const DEPRECATE_NON_STRICT_TYPES = '5.3'; /** * **id: ember-data:deprecate-non-strict-id** * * Currently, EmberData expects that the `id` property associated with * a resource is a string. * * However, for legacy support in many locations we would accept a number * which would then immediately be coerced into a string. * * We are deprecating this legacy support for numeric IDs. * * The goal is that in the future, you will be able to use any ID format * so long as everywhere you refer to the ID you use the same format. * * However, for identifiers we will always use string IDs and so any * custom identifier configuration should provide a string ID. * * @property DEPRECATE_NON_STRICT_ID * @since 5.3 * @until 6.0 * @public */ const DEPRECATE_NON_STRICT_ID = '5.3'; /** * **id: <none yet assigned>** * * This is a planned deprecation which will trigger when observer or computed * chains are used to watch for changes on any EmberData LiveArray, CollectionRecordArray, * ManyArray or PromiseManyArray. * * Support for these chains is currently guarded by the deprecation flag * listed here, enabling removal of the behavior if desired. * * @property DEPRECATE_COMPUTED_CHAINS * @since 5.0 * @until 6.0 * @public */ const DEPRECATE_COMPUTED_CHAINS = '5.0'; /** * **id: ember-data:deprecate-legacy-imports** * * Deprecates when importing from `ember-data/*` instead of `@ember-data/*` * in order to prepare for the eventual removal of the legacy `ember-data/*` * * All imports from `ember-data/*` should be updated to `@ember-data/*` * except for `ember-data/store`. When you are using `ember-data` (as opposed to * installing the indivudal packages) you should import from `ember-data/store` * instead of `@ember-data/store` in order to receive the appropriate configuration * of defaults. * * @property DEPRECATE_LEGACY_IMPORTS * @since 5.3 * @until 6.0 * @public */ const DEPRECATE_LEGACY_IMPORTS = '5.3'; /** * **id: ember-data:deprecate-non-unique-collection-payloads** * * Deprecates when the data for a hasMany relationship contains * duplicate identifiers. * * Previously, relationships would silently de-dupe the data * when received, but this behavior is being removed in favor * of erroring if the same related record is included multiple * times. * * For instance, in JSON:API the below relationship data would * be considered invalid: * * ```json * { * "data": { * "type": "article", * "id": "1", * "relationships": { * "comments": { * "data": [ * { "type": "comment", "id": "1" }, * { "type": "comment", "id": "2" }, * { "type": "comment", "id": "1" } // duplicate * ] * } * } * } * ``` * * To resolve this deprecation, either update your server to * not include duplicate data, or implement normalization logic * in either a request handler or serializer which removes * duplicate data from relationship payloads. * * @property DEPRECATE_NON_UNIQUE_PAYLOADS * @since 5.3 * @until 6.0 * @public */ const DEPRECATE_NON_UNIQUE_PAYLOADS = '5.3'; /** * **id: ember-data:deprecate-relationship-remote-update-clearing-local-state** * * Deprecates when a relationship is updated remotely and the local state * is cleared of all changes except for "new" records. * * Instead, any records not present in the new payload will be considered * "removed" while any records present in the new payload will be considered "added". * * This allows us to "commit" local additions and removals, preserving any additions * or removals that are not yet reflected in the remote state. * * For instance, given the following initial state: * * remote: A, B, C * local: add D, E * remove B, C * => A, D, E * * * If after an update, the remote state is now A, B, D, F then the new state will be * * remote: A, B, D, F * local: add E * remove B * => A, D, E, F * * Under the old behavior the updated local state would instead have been * => A, B, D, F * * Similarly, if a belongsTo remote State was A while its local state was B, * then under the old behavior if the remote state changed to C, the local state * would be updated to C. Under the new behavior, the local state would remain B. * * If the remote state was A while its local state was `null`, then under the old * behavior if the remote state changed to C, the local state would be updated to C. * Under the new behavior, the local state would remain `null`. * * Thus the new correct mental model is that the state of the relationship at any point * in time is whatever the most recent remote state is, plus any local additions or removals * you have made that have not yet been reflected by the remote state. * * > Note: The old behavior extended to modifying the inverse of a relationship. So if * > you had local state not reflected in the new remote state, inverses would be notified * > and their state reverted as well when "resetting" the relationship. * > Under the new behavior, since the local state is preserved the inverses will also * > not be reverted. * * ### Resolving this deprecation * * Resolving this deprecation can be done individually for each relationship * or globally for all relationships. * * To resolve it globally, set the `DEPRECATE_RELATIONSHIP_REMOTE_UPDATE_CLEARING_LOCAL_STATE` * to `false` in ember-cli-build.js * * ```js * const { setConfig } = await import('@warp-drive/build-config'); * * let app = new EmberApp(defaults, {}); * * setConfig(app, __dirname, { * deprecations: { * // set to false to strip the deprecated code (thereby opting into the new behavior) * DEPRECATE_RELATIONSHIP_REMOTE_UPDATE_CLEARING_LOCAL_STATE: false * } * }); * ``` * * To resolve this deprecation on an individual relationship, adjust the `options` passed to * the relationship. For relationships with inverses, both sides MUST be migrated to the new * behavior at the same time. * * ```js * class Person extends Model { * @hasMany('person', { * async: false, * inverse: null, * resetOnRemoteUpdate: false * }) children; * * @belongsTo('person', { * async: false, * inverse: null, * resetOnRemoteUpdate: false * }) parent; * } * ``` * * > Note: false is the only valid value here, all other values (including missing) * > will be treated as true, where `true` is the legacy behavior that is now deprecated. * * Once you have migrated all relationships, you can remove the the resetOnRemoteUpdate * option and set the deprecation flag to false in ember-cli-build. * * ### What if I don't want the new behavior? * * EmberData's philosophy is to not make assumptions about your application. Where possible * we seek out "100%" solutions – solutions that work for all use cases - and where that is * not possible we default to "90%" solutions – solutions that work for the vast majority of use * cases. In the case of "90%" solutions we look for primitives that allow you to resolve the * 10% case in your application. If no such primitives exist, we provide an escape hatch that * ensures you can build the behavior you need without adopting the cost of the default solution. * * In this case, the old behavior was a "40%" solution. The inability for an application developer * to determine what changes were made locally, and thus what changes should be preserved, made * it impossible to build certain features easily, or in some cases at all. The proliferation of * feature requests, bug reports (from folks surprised by the prior behavior) and addon attempts * in this space are all evidence of this. * * We believe the new behavior is a "90%" solution. It works for the vast majority of use cases, * often without noticeable changes to existing application behavior, and provides primitives that * allow you to build the behavior you need for the remaining 10%. * * The great news is that this behavior defaults to trusting your API similar to the old behavior. * If your API is correct, you will not need to make any changes to your application to adopt * the new behavior. * * This means the 10% cases are those where you can't trust your API to provide the correct * information. In these cases, because you now have cheap access to a diff of the relationship * state, there are a few options that weren't available before: * * - you can adjust returned API payloads to contain the expected changes that it doesn't include * - you can modify local state by adding or removing records on the HasMany record array to remove * any local changes that were not returned by the API. * - you can use `<Cache>.mutate(mutation)` to directly modify the local cache state of the relationship * to match the expected state. * * What this version (5.3) does not yet provide is a way to directly modify the cache's remote state * for the relationship via public APIs other than via the broader action of upserting a response via * `<Cache>.put(document)`. However, such an API was sketched in the Cache 2.1 RFC * `<Cache>.patch(operation)` and is likely to be added in a future 5.x release of EmberData. * * This version (5.3) also does not yet provide a way to directly modify the graph (a general purpose * subset of cache behaviors specific to relationships) via public APIs. However, during the * 5.x release series we will be working on finalizing the Graph API and making it public. * * If none of these options work for you, you can always opt-out more broadly by implementing * a custom Cache with the relationship behaviors you need. * * @property DEPRECATE_RELATIONSHIP_REMOTE_UPDATE_CLEARING_LOCAL_STATE * @since 5.3 * @until 6.0 * @public */ const DEPRECATE_RELATIONSHIP_REMOTE_UPDATE_CLEARING_LOCAL_STATE = '5.3'; /** * **id: ember-data:deprecate-many-array-duplicates** * * When the flag is `true` (default), adding duplicate records to a `ManyArray` * is deprecated in non-production environments. In production environments, * duplicate records added to a `ManyArray` will be deduped and no error will * be thrown. * * When the flag is `false`, an error will be thrown when duplicates are added. * * @property DEPRECATE_MANY_ARRAY_DUPLICATES * @since 5.3 * @until 6.0 * @public */ const DEPRECATE_MANY_ARRAY_DUPLICATES = '5.3'; /** * **id: ember-data:deprecate-store-extends-ember-object** * * When the flag is `true` (default), the Store class will extend from `@ember/object`. * When the flag is `false` or `ember-source` is not present, the Store will not extend * from EmberObject. * * @property DEPRECATE_STORE_EXTENDS_EMBER_OBJECT * @since 5.4 * @until 6.0 * @public */ const DEPRECATE_STORE_EXTENDS_EMBER_OBJECT = '5.4'; /** * **id: ember-data:schema-service-updates** * * When the flag is `true` (default), the legacy schema * service features will be enabled on the store and * the service, and deprecations will be thrown when * they are used. * * Deprecated features include: * * - `Store.registerSchema` method is deprecated in favor of the `Store.createSchemaService` hook * - `Store.registerSchemaDefinitionService` method is deprecated in favor of the `Store.createSchemaService` hook * - `Store.getSchemaDefinitionService` method is deprecated in favor of `Store.schema` property * - `SchemaService.doesTypeExist` method is deprecated in favor of the `SchemaService.hasResource` method * - `SchemaService.attributesDefinitionFor` method is deprecated in favor of the `SchemaService.fields` method * - `SchemaService.relationshipsDefinitionFor` method is deprecated in favor of the `SchemaService.fields` method * * @property ENABLE_LEGACY_SCHEMA_SERVICE * @since 5.4 * @until 6.0 * @public */ const ENABLE_LEGACY_SCHEMA_SERVICE = '5.4'; /** * **id: warp-drive.ember-inflector** * * Deprecates the use of ember-inflector for pluralization and singularization in favor * of the `@ember-data/request-utils` package. * * Rule configuration methods (singular, plural, uncountable, irregular) and * usage methods (singularize, pluralize) are are available as imports from * `@ember-data/request-utils/string` * * Notable differences with ember-inflector: * - there cannot be multiple inflector instances with separate rules * - pluralization does not support a count argument * - string caches now default to 10k entries instead of 1k, and this * size is now configurable. Additionally, the cache is now a LRU cache * instead of a first-N cache. * * This deprecation can be resolved by removing usage of ember-inflector or by using * both ember-inflector and @ember-data/request-utils in parallel and updating your * EmberData/WarpDrive build config to mark the deprecation as resolved * in ember-cli-build * * ```js * setConfig(app, __dirname, { deprecations: { DEPRECATE_EMBER_INFLECTOR: false }}); * ``` * * @property DEPRECATE_EMBER_INFLECTOR * @since 5.3 * @until 6.0 * @public */ const DEPRECATE_EMBER_INFLECTOR = '5.3'; /** * This is a special flag that can be used to opt-in early to receiving deprecations introduced in 6.x * which have had their infra backported to 5.x versions of EmberData. * * When this flag is not present or set to `true`, the deprecations from the 6.x branch * will not print their messages and the deprecation cannot be resolved. * * When this flag is present and set to `false`, the deprecations from the 6.x branch will * print and can be resolved. * * @property DISABLE_7X_DEPRECATIONS * @since 5.3 * @until 7.0 * @public */ const DISABLE_7X_DEPRECATIONS = '7.0'; 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, DISABLE_7X_DEPRECATIONS, 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.EMBER_DATA_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; } const _MacrosConfig = EmbroiderMacros.MacrosConfig; function recastMacrosConfig(macros) { if (!('globalConfig' in macros)) { throw new Error('Expected MacrosConfig to have a globalConfig property'); } return macros; } function setConfig(context, appRoot, config) { const macros = recastMacrosConfig(_MacrosConfig.for(context, appRoot)); const isLegacySupport = config.___legacy_support; const hasDeprecatedConfig = isLegacySupport && Object.keys(config).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; } // legacy support called prior to user setConfig if (isLegacySupport && hasDeprecatedConfig) { console.warn(`You are using the legacy emberData key in your ember-cli-build.js file. This key is deprecated and will be removed in the next major version of EmberData/WarpDrive. Please use \`import { setConfig } from '@warp-drive/build-config';\` instead.`); } // 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, config.debug); const env = getEnv(); const DEPRECATIONS = getDeprecations(config.compatWith || null, config.deprecations); const FEATURES = getFeatures(env.PRODUCTION); const includeDataAdapterInProduction = typeof config.includeDataAdapterInProduction === 'boolean' ? config.includeDataAdapterInProduction : true; const includeDataAdapter = env.PRODUCTION ? includeDataAdapterInProduction : true; const finalizedConfig = { debug: debugOptions, polyfillUUID: config.polyfillUUID ?? false, includeDataAdapter, compatWith: config.compatWith ?? null, deprecations: DEPRECATIONS, features: FEATURES, activeLogging: createLoggingConfig(env, debugOptions), env }; macros.setGlobalConfig(import.meta.filename, 'WarpDrive', finalizedConfig); } export { setConfig }; //# sourceMappingURL=index.js.map