From 145ec69f21726c492ff9be891809b3ccc936e1f8 Mon Sep 17 00:00:00 2001 From: Scott Meyer Date: Thu, 16 Jul 2026 17:52:45 -0600 Subject: [PATCH] chore(apps): deprecate legacy backend runtime global --- .../apps/src/backend/virtual-entry.test.ts | 31 ++++++++++++------- .../plugins/apps/src/backend/virtual-entry.ts | 3 ++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/plugins/apps/src/backend/virtual-entry.test.ts b/packages/plugins/apps/src/backend/virtual-entry.test.ts index 376b15a3c..de3751feb 100644 --- a/packages/plugins/apps/src/backend/virtual-entry.test.ts +++ b/packages/plugins/apps/src/backend/virtual-entry.test.ts @@ -38,15 +38,6 @@ describe('Backend Functions - generateVirtualEntryContent', () => { expect(result).toContain('export async function main($)'); }); - test('Should set globalThis.$ = $', () => { - const result = generateVirtualEntryContent( - 'myHandler', - '/src/handler.ts', - PROJECT_ROOT, - ); - expect(result).toContain('globalThis.$ = $'); - }); - test('Should read args from $.backendFunctionArgs (no source-text substitution)', () => { const result = generateVirtualEntryContent( 'myHandler', @@ -70,6 +61,7 @@ describe('Backend Functions - generateVirtualEntryContent', () => { PROJECT_ROOT, ); expect(result).toContain('await myHandler(...args)'); + expect(result).not.toContain('await myHandler(...args, $)'); }); test('Should include the setExecuteActionImplementation bridge snippet', () => { @@ -164,6 +156,10 @@ describe('Backend Functions - args round-trip via $.backendFunctionArgs', () => jest.spyOn(shared, 'isActionCatalogInstalled').mockReturnValue(false); }); + afterEach(() => { + Reflect.deleteProperty(global, '$'); + }); + // Extract the body of the generated `main($)` function so we can eval it // directly with a custom $ that includes a mocked handler import. function buildMainFromSource(source: string, handler: (...args: unknown[]) => unknown) { @@ -176,8 +172,7 @@ describe('Backend Functions - args round-trip via $.backendFunctionArgs', () => .join('\n') // Replace the call site with a $-bound handler we control. .replace(/await myHandler\(\.\.\.args\)/, 'await $.__handler(...args)'); - // The generated code declares globalThis.$, which has no effect in this - // sandbox but is harmless. Wrap the body so we can return main(). + // Wrap the executable body so we can return main(). const wrapper = `${body}\nreturn main($);`; // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func const fn = new Function( @@ -229,4 +224,18 @@ describe('Backend Functions - args round-trip via $.backendFunctionArgs', () => await main({}); expect(received).toEqual([]); }); + + test('Should expose the runtime context globally for legacy handlers', async () => { + const source = generateVirtualEntryContent('myHandler', '/src/handler.ts', PROJECT_ROOT); + let exposedContext: unknown; + const handler = () => { + exposedContext = Reflect.get(global, '$'); + return 'ok'; + }; + const main = buildMainFromSource(source, handler); + + await main({ backendFunctionArgs: [], privateRuntimeValue: 'secret' }); + + expect(exposedContext).toMatchObject({ privateRuntimeValue: 'secret' }); + }); }); diff --git a/packages/plugins/apps/src/backend/virtual-entry.ts b/packages/plugins/apps/src/backend/virtual-entry.ts index 235d9b1a5..ac17de21b 100644 --- a/packages/plugins/apps/src/backend/virtual-entry.ts +++ b/packages/plugins/apps/src/backend/virtual-entry.ts @@ -28,6 +28,9 @@ export function generateVirtualEntryContent( lines.push(''); lines.push('/** @param {import("./context.types").Context} $ */'); lines.push('export async function main($) {'); + lines.push(' // Deprecated compatibility shim: existing customer functions may read'); + lines.push(' // the runtime context through globalThis.$. Remove only in a breaking'); + lines.push(' // release after customers migrate to @datadog/apps backend APIs.'); lines.push(' globalThis.$ = $;'); lines.push(''); lines.push(` // Register the $.Actions-based implementation for executeAction`);