diff --git a/packages/build-config/cjs-src/transforms/babel-plugin-transform-logging.js b/packages/build-config/cjs-src/transforms/babel-plugin-transform-logging.js index c764503967..e8e2088a4c 100644 --- a/packages/build-config/cjs-src/transforms/babel-plugin-transform-logging.js +++ b/packages/build-config/cjs-src/transforms/babel-plugin-transform-logging.js @@ -25,6 +25,9 @@ export default function (babel) { } let localBindingName = specifier.node.local.name; let binding = specifier.scope.getBinding(localBindingName); + const enableRuntimeActivation = Boolean(state.opts.runtimeKey); + const strippableKey = enableRuntimeActivation ? state.opts.runtimeKey : state.opts.configKey; + binding.referencePaths.forEach((p) => { let negateStatement = false; let node = p; @@ -38,18 +41,85 @@ export default function (babel) { t.callExpression(state.importer.import(p, '@embroider/macros', 'getGlobalConfig'), []), t.identifier('WarpDrive') ), - t.identifier(state.opts.configKey) + t.identifier(strippableKey) ), t.identifier(name) ); + node.replaceWith( - // if (LOG_FOO) + // if (LOG_FOO) { + // // ... + // } // => - // if (macroCondition(getGlobalConfig('WarpDrive').debug.LOG_FOO)) + // if (macroCondition(getGlobalConfig('WarpDrive').debug.LOG_FOO)) { + // // ... + // } t.callExpression(state.importer.import(p, '@embroider/macros', 'macroCondition'), [ negateStatement ? t.unaryExpression('!', getConfig) : getConfig, ]) ); + + if (enableRuntimeActivation) { + // we do not yet support arbitrary runtime activation locations, + // the only supported locations are `if (LOG)` style statements, no + // ternaries or other more complex expressions + const parentIfStatement = node.parentPath.type === 'IfStatement' ? node.parentPath : null; + if (!parentIfStatement) { + throw new Error( + `Runtime activation of logging flags is only supported in if statements, but found node '${node.parentPath.type}'` + ); + } + + // if (LOG_FOO) { + // // ... + // } + // => + // if (macroCondition(getGlobalConfig('WarpDrive').activeLogging.LOG_FOO)) { + // if (getGlobalConfig('WarpDrive').debug.LOG_FOO || globalThis.getWarpDriveRuntimeConfig().debug.LOG_FOO) { + // // ... + // } + // } + // + // the outer-if is generated by the node-replace above. The inner if is generated here. + const originalBody = parentIfStatement.node.consequent; + + // getGlobalConfig('WarpDrive').debug.LOG_FOO + const getActualConfig = t.memberExpression( + t.memberExpression( + t.memberExpression( + t.callExpression(state.importer.import(p, '@embroider/macros', 'getGlobalConfig'), []), + t.identifier('WarpDrive') + ), + t.identifier(state.opts.configKey) + ), + t.identifier(name) + ); + + // globalThis.getWarpDriveRuntimeConfig().debug.LOG_FOO + const getRuntimeConfig = t.memberExpression( + t.memberExpression( + t.callExpression( + t.memberExpression(t.identifier('globalThis'), t.identifier('getWarpDriveRuntimeConfig')), + [] + ), + t.identifier(state.opts.configKey) + ), + t.identifier(name) + ); + + // || + const ifExp = t.logicalExpression('||', getActualConfig, getRuntimeConfig); + + // if (( || )) + const innerIfStatement = t.ifStatement( + negateStatement ? t.unaryExpression('!', ifExp) : ifExp, + originalBody + ); + + // replace the original body with the new if statement + parentIfStatement.node.consequent = t.blockStatement([innerIfStatement]); + } else { + } }); specifier.scope.removeOwnBinding(localBindingName); specifier.remove(); diff --git a/packages/build-config/src/-private/utils/logging.ts b/packages/build-config/src/-private/utils/logging.ts new file mode 100644 index 0000000000..ce0d465fe2 --- /dev/null +++ b/packages/build-config/src/-private/utils/logging.ts @@ -0,0 +1,19 @@ +import * as LOGGING from '../../debugging.ts'; + +type LOG_CONFIG_KEY = keyof typeof LOGGING; +export type LOG_CONFIG = { [key in LOG_CONFIG_KEY]: boolean }; + +export function createLoggingConfig(env: { DEBUG: boolean; TESTING: boolean; PRODUCTION: boolean }, debug: LOG_CONFIG) { + const config = {} as LOG_CONFIG; + const keys = Object.keys(LOGGING) as LOG_CONFIG_KEY[]; + + for (const key of keys) { + if (env.DEBUG || env.TESTING) { + config[key] = true; + } else { + config[key] = debug[key] || false; + } + } + + return config; +} diff --git a/packages/build-config/src/babel-macros.ts b/packages/build-config/src/babel-macros.ts index df79474b85..d49172e342 100644 --- a/packages/build-config/src/babel-macros.ts +++ b/packages/build-config/src/babel-macros.ts @@ -47,6 +47,7 @@ export function macros() { { source: '@warp-drive/build-config/debugging', configKey: 'debug', + runtimeKey: 'activeLogging', flags: config.debug, }, '@warp-drive/build-config/debugging-stripping', diff --git a/packages/build-config/src/index.ts b/packages/build-config/src/index.ts index 2b12add9fa..ef9c012616 100644 --- a/packages/build-config/src/index.ts +++ b/packages/build-config/src/index.ts @@ -4,6 +4,7 @@ import { getDeprecations } from './-private/utils/deprecations.ts'; import { getFeatures } from './-private/utils/features.ts'; import * as LOGGING from './debugging.ts'; import type { MacrosConfig } from '@embroider/macros/src/node.js'; +import { createLoggingConfig } from './-private/utils/logging.ts'; const _MacrosConfig = EmbroiderMacros.MacrosConfig as unknown as typeof MacrosConfig; @@ -25,6 +26,7 @@ type InternalWarpDriveConfig = { compatWith: `${number}.${number}` | null; deprecations: ReturnType; features: ReturnType; + activeLogging: { [key in LOG_CONFIG_KEY]: boolean }; env: { TESTING: boolean; PRODUCTION: boolean; @@ -90,6 +92,7 @@ export function setConfig(context: object, appRoot: string, config: WarpDriveCon compatWith: config.compatWith ?? null, deprecations: DEPRECATIONS, features: FEATURES, + activeLogging: createLoggingConfig(env, debugOptions), env, }; diff --git a/packages/build-config/src/runtime.ts b/packages/build-config/src/runtime.ts new file mode 100644 index 0000000000..9e024dd9ff --- /dev/null +++ b/packages/build-config/src/runtime.ts @@ -0,0 +1,21 @@ +import { LOG_CONFIG } from './-private/utils/logging'; + +const RuntimeConfig = { + debug: {}, +}; + +export function getRuntimeConfig() { + return RuntimeConfig; +} + +/** + * Upserts the specified logging configuration into the runtime + * config. + * + * globalThis.setWarpDriveLogging({ LOG_PAYLOADS: true } }); + * + * @typedoc + */ +export function setLogging(config: Partial) { + Object.assign(RuntimeConfig.debug, config); +} diff --git a/packages/build-config/tsconfig.json b/packages/build-config/tsconfig.json index 62c40e41ed..a54770a4d6 100644 --- a/packages/build-config/tsconfig.json +++ b/packages/build-config/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src/**/*"], + "include": ["src/**/**/*"], "compilerOptions": { "lib": ["DOM", "ESNext"], "module": "ESNext", diff --git a/packages/build-config/vite.config.mjs b/packages/build-config/vite.config.mjs index 78033fcc61..33008e6985 100644 --- a/packages/build-config/vite.config.mjs +++ b/packages/build-config/vite.config.mjs @@ -10,6 +10,7 @@ export const entryPoints = [ './src/debugging.ts', './src/deprecations.ts', './src/canary-features.ts', + './src/runtime.ts', ]; export default createConfig( diff --git a/packages/request/src/index.ts b/packages/request/src/index.ts index a88eeaf79f..eb2f9381bb 100644 --- a/packages/request/src/index.ts +++ b/packages/request/src/index.ts @@ -1,3 +1,5 @@ +import { getRuntimeConfig, setLogging } from '@warp-drive/build-config/runtime'; + export { RequestManager as default } from './-private/manager'; export { createDeferred } from './-private/future'; export type { Future, Handler, CacheHandler, NextFn } from './-private/types'; @@ -12,3 +14,9 @@ export type { } from '@warp-drive/core-types/request'; export { setPromiseResult, getPromiseResult } from './-private/promise-cache'; export type { Awaitable } from './-private/promise-cache'; + +// @ts-expect-error adding to globalThis +globalThis.setWarpDriveLogging = setLogging; + +// @ts-expect-error adding to globalThis +globalThis.getWarpDriveRuntimeConfig = getRuntimeConfig; diff --git a/packages/store/src/-private/store-service.ts b/packages/store/src/-private/store-service.ts index a1c5cd9891..0ddb657d3c 100644 --- a/packages/store/src/-private/store-service.ts +++ b/packages/store/src/-private/store-service.ts @@ -15,6 +15,7 @@ import { } from '@warp-drive/build-config/deprecations'; import { DEBUG, TESTING } from '@warp-drive/build-config/env'; import { assert } from '@warp-drive/build-config/macros'; +import { getRuntimeConfig, setLogging } from '@warp-drive/build-config/runtime'; import type { Cache } from '@warp-drive/core-types/cache'; import type { Graph } from '@warp-drive/core-types/graph'; import type { @@ -62,6 +63,12 @@ import { coerceId, ensureStringId } from './utils/coerce-id'; import { constructResource } from './utils/construct-resource'; import { normalizeModelName } from './utils/normalize-model-name'; +// @ts-expect-error adding to globalThis +globalThis.setWarpDriveLogging = setLogging; + +// @ts-expect-error adding to globalThis +globalThis.getWarpDriveRuntimeConfig = getRuntimeConfig; + export { storeFor }; // We inline this list of methods to avoid importing EmberObject