From 85b130005dff944fc8ff3fe8908b72c9391356bf Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Wed, 19 Feb 2025 14:57:17 -0800 Subject: [PATCH] feat: runtime logging activation (#9683) * feat: runtime logging activation * fix bugs * more fixes * fix lint * fix build * fix more lint * nicer names --- .../babel-plugin-transform-logging.js | 76 ++++++++++++++++++- .../src/-private/utils/logging.ts | 19 +++++ packages/build-config/src/babel-macros.ts | 1 + packages/build-config/src/index.ts | 3 + packages/build-config/src/runtime.ts | 21 +++++ packages/build-config/tsconfig.json | 2 +- packages/build-config/vite.config.mjs | 1 + packages/request/src/index.ts | 8 ++ packages/store/src/-private/store-service.ts | 7 ++ 9 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 packages/build-config/src/-private/utils/logging.ts create mode 100644 packages/build-config/src/runtime.ts 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 c7645039673..e8e2088a4c5 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 00000000000..ce0d465fe20 --- /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 df79474b854..d49172e3426 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 2b12add9fab..ef9c0126165 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 00000000000..9e024dd9ff0 --- /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 62c40e41edd..a54770a4d6e 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 78033fcc611..33008e69858 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 a88eeaf79fc..eb2f9381bb3 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 a1c5cd98917..0ddb657d3c8 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