Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions packages/plugins/apps/src/backend/virtual-entry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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) {
Expand All @@ -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(
Expand Down Expand Up @@ -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' });
});
});
3 changes: 3 additions & 0 deletions packages/plugins/apps/src/backend/virtual-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentionally retained compatibility debt. Historical bundles call handlers with only ...backendFunctionArgs and may read raw context from globalThis.$; changing either behavior would break existing customer functions. The SDK/runtime bridge is the migration path, and removal requires a breaking release.

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`);
Expand Down
Loading