@warp-drive/build-config
Version:
Provides Build Configuration for projects using WarpDrive or EmberData
693 lines (666 loc) • 26.2 kB
JavaScript
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