Skip to content

Commit 4e7c072

Browse files
committed
Remove discoverBackendFiles, flatten BackendFunctionRef into BackendFunction
1 parent 5e0dda3 commit 4e7c072

8 files changed

Lines changed: 37 additions & 150 deletions

File tree

packages/plugins/apps/src/backend/discovery.test.ts

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,8 @@
22
// This product includes software developed at Datadog (https://www.datadoghq.com/).
33
// Copyright 2019-Present Datadog, Inc.
44

5-
import { discoverBackendFiles, extractExportedFunctions } from '@dd/apps-plugin/backend/discovery';
6-
import { getMockLogger } from '@dd/tests/_jest/helpers/mocks';
5+
import { extractExportedFunctions } from '@dd/apps-plugin/backend/discovery';
76
import type { Program } from 'estree';
8-
import { globSync } from 'glob';
9-
10-
jest.mock('glob');
11-
12-
const log = getMockLogger();
13-
const projectRoot = '/project';
14-
15-
const mockedGlobSync = jest.mocked(globSync);
167

178
/**
189
* Helper to build a minimal ESTree Program node for testing.
@@ -156,55 +147,3 @@ describe('Backend Functions - extractExportedFunctions', () => {
156147
);
157148
});
158149
});
159-
160-
describe('Backend Functions - discoverBackendFiles', () => {
161-
afterEach(() => {
162-
jest.restoreAllMocks();
163-
});
164-
165-
test('Should discover .backend.ts files via glob', () => {
166-
mockedGlobSync.mockReturnValue([
167-
'/project/src/utils/mathUtils.backend.ts',
168-
'/project/src/auth/login.backend.ts',
169-
] as any);
170-
171-
const result = discoverBackendFiles(projectRoot, log);
172-
173-
expect(result).toEqual([
174-
{
175-
absolutePath: '/project/src/utils/mathUtils.backend.ts',
176-
refPath: 'src/utils/mathUtils',
177-
},
178-
{
179-
absolutePath: '/project/src/auth/login.backend.ts',
180-
refPath: 'src/auth/login',
181-
},
182-
]);
183-
});
184-
185-
test('Should return empty array when no .backend.ts files exist', () => {
186-
mockedGlobSync.mockReturnValue([]);
187-
188-
const result = discoverBackendFiles(projectRoot, log);
189-
expect(result).toEqual([]);
190-
});
191-
192-
test('Should strip .backend.{ext} to form the ref path', () => {
193-
mockedGlobSync.mockReturnValue(['/project/mathUtils.backend.tsx'] as any);
194-
195-
const result = discoverBackendFiles(projectRoot, log);
196-
expect(result[0].refPath).toBe('mathUtils');
197-
});
198-
199-
test('Should call globSync with correct pattern and options', () => {
200-
mockedGlobSync.mockReturnValue([]);
201-
202-
discoverBackendFiles(projectRoot, log);
203-
204-
expect(mockedGlobSync).toHaveBeenCalledWith('**/*.backend.{ts,tsx,js,jsx}', {
205-
cwd: projectRoot,
206-
ignore: ['**/node_modules/**', '**/dist/**', '**/.dist/**'],
207-
absolute: true,
208-
});
209-
});
210-
});

packages/plugins/apps/src/backend/discovery.ts

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,26 @@
55
import type { Logger } from '@dd/core/types';
66
import { createHash } from 'crypto';
77
import type { Declaration, Identifier, Program } from 'estree';
8-
import { globSync } from 'glob';
9-
import path from 'path';
108
import type { AstNode } from 'rollup';
119

12-
export interface BackendFunctionRef {
10+
export interface BackendFunction {
1311
/** Relative path from project root to the .backend.ts file (without extension) */
1412
path: string;
1513
/** Exported function name */
1614
name: string;
17-
}
18-
19-
export interface BackendFunction {
20-
/** The BackendFunctionRef identifying this function */
21-
ref: BackendFunctionRef;
2215
/** Absolute path to the .backend.ts source file */
2316
entryPath: string;
2417
}
2518

26-
export interface BackendFileInfo {
27-
/** Absolute path to the .backend.ts source file */
28-
absolutePath: string;
29-
/** Relative path from project root, with .backend.{ext} stripped (used as BackendFunctionRef.path) */
30-
refPath: string;
31-
}
32-
3319
/**
34-
* Encode a BackendFunctionRef into an opaque query name string.
20+
* Encode a BackendFunction into an opaque query name string.
3521
* Uses the full SHA-256 hash of the path so that backend file structure
3622
* is never leaked into frontend assets.
3723
*
3824
* This is the single source of truth for query name encoding — used by
3925
* proxy codegen, the production build, and the dev server.
4026
*/
41-
export function encodeQueryName(ref: BackendFunctionRef): string {
27+
export function encodeQueryName(ref: Pick<BackendFunction, 'path' | 'name'>): string {
4228
const pathHash = createHash('sha256').update(ref.path).digest('hex');
4329
return `${pathHash}.${ref.name}`;
4430
}
@@ -135,38 +121,3 @@ function namesFromDeclaration(decl: Declaration): string[] {
135121
}
136122
return [];
137123
}
138-
139-
/**
140-
* Discover backend files by scanning for `*.backend.{ts,tsx,js,jsx}` files
141-
* anywhere in the project (excluding node_modules, dist, etc.).
142-
*
143-
* Returns file info only — no export parsing. Exports are discovered lazily
144-
* during the `transform` hook when Vite has already stripped TypeScript types.
145-
*
146-
* Must be sync because it runs in getPlugins() before the build starts.
147-
*/
148-
export function discoverBackendFiles(projectRoot: string, log: Logger): BackendFileInfo[] {
149-
const pattern = '**/*.backend.{ts,tsx,js,jsx}';
150-
const files = globSync(pattern, {
151-
cwd: projectRoot,
152-
ignore: ['**/node_modules/**', '**/dist/**', '**/.dist/**'],
153-
absolute: true,
154-
});
155-
156-
if (files.length === 0) {
157-
log.debug(`No .backend.ts files found in ${projectRoot}`);
158-
return [];
159-
}
160-
161-
const result: BackendFileInfo[] = [];
162-
for (const absolutePath of files) {
163-
const relativePath = path.relative(projectRoot, absolutePath);
164-
const refPath = relativePath.replace(/\.backend\.\w+$/, '');
165-
result.push({ absolutePath, refPath });
166-
}
167-
168-
log.debug(
169-
`Discovered ${result.length} backend file(s): ${result.map((f) => f.refPath).join(', ')}`,
170-
);
171-
return result;
172-
}

packages/plugins/apps/src/index.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { createArchive } from './archive';
1010
import type { Asset } from './assets';
1111
import { collectAssets } from './assets';
1212
import type { BackendFunction } from './backend/discovery';
13-
import { discoverBackendFiles, encodeQueryName, parseExportNames } from './backend/discovery';
13+
import { encodeQueryName, parseExportNames } from './backend/discovery';
1414
import { CONFIG_KEY, PLUGIN_NAME } from './constants';
1515
import { resolveIdentifier } from './identifier';
1616
import type { AppsOptions } from './types';
@@ -37,9 +37,9 @@ function buildProxyModule(
3737
const proxyExports: Array<{ exportName: string; queryName: string }> = [];
3838

3939
for (const exportName of exportNames) {
40-
const ref = { path: refPath, name: exportName };
41-
functions.push({ ref, entryPath: id });
42-
proxyExports.push({ exportName, queryName: encodeQueryName(ref) });
40+
const func = { path: refPath, name: exportName, entryPath: id };
41+
functions.push(func);
42+
proxyExports.push({ exportName, queryName: encodeQueryName(func) });
4343
}
4444

4545
return { functions, proxyCode: generateProxyModule(proxyExports) };
@@ -63,11 +63,7 @@ export const getPlugins: GetPlugins = ({ options, context, bundler }) => {
6363
return [];
6464
}
6565

66-
// Discover backend files (sync — must run before build starts).
67-
// Only globs for file paths; exports are discovered lazily during transform.
68-
const backendFiles = discoverBackendFiles(context.buildRoot, log);
6966
const backendOutputs = new Map<string, string>();
70-
const hasBackend = backendFiles.length > 0;
7167

7268
// Mutable array populated during transforms as .backend.ts files are processed.
7369
const backendFunctions: BackendFunction[] = [];
@@ -106,17 +102,18 @@ Either:
106102

107103
// Exclude backend output files from frontend assets if backend is active.
108104
const backendPaths = new Set(backendOutputs.values());
109-
const frontendOnly = hasBackend
110-
? assets.filter((a) => !backendPaths.has(a.absolutePath))
111-
: assets;
105+
const frontendOnly =
106+
backendOutputs.size > 0
107+
? assets.filter((a) => !backendPaths.has(a.absolutePath))
108+
: assets;
112109

113110
// Prefix all frontend assets with frontend/.
114111
const allAssets: Asset[] = frontendOnly.map((asset) => ({
115112
...asset,
116113
relativePath: `frontend/${asset.relativePath}`,
117114
}));
118115

119-
if (hasBackend) {
116+
if (backendOutputs.size > 0) {
120117
// Build backend assets from the outputs map populated during the build.
121118
// Keys are encoded query names ({hash(path)}.{name}).
122119
for (const [bundleName, absolutePath] of backendOutputs) {

packages/plugins/apps/src/vite/build-backend-functions.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,9 @@ export async function buildBackendFunctions(
3737
// Build each function individually so that each output is a single
3838
// self-contained JS file
3939
for (const func of functions) {
40-
const bundleName = encodeQueryName(func.ref);
40+
const bundleName = encodeQueryName(func);
4141
const virtualId = `${VIRTUAL_PREFIX}${bundleName}`;
42-
const virtualContent = generateVirtualEntryContent(
43-
func.ref.name,
44-
func.entryPath,
45-
buildRoot,
46-
);
42+
const virtualContent = generateVirtualEntryContent(func.name, func.entryPath, buildRoot);
4743

4844
const baseConfig = getBaseBackendBuildConfig(buildRoot, { [virtualId]: virtualContent });
4945

packages/plugins/apps/src/vite/dev-server.test.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ const DD_SITE = 'datadoghq.com';
1717

1818
const mockFunctions: BackendFunction[] = [
1919
{
20-
ref: { path: 'backend/greet', name: 'greet' },
20+
path: 'backend/greet',
21+
name: 'greet',
2122
entryPath: '/project/backend/greet.backend.ts',
2223
},
2324
{
24-
ref: { path: 'backend/compute', name: 'compute' },
25+
path: 'backend/compute',
26+
name: 'compute',
2527
entryPath: '/project/backend/compute.backend.ts',
2628
},
2729
];
@@ -127,7 +129,7 @@ describe('Dev Server Middleware', () => {
127129
mockViteBuild.mockResolvedValue(mockBuildResult('// bundled code'));
128130

129131
const req = createMockRequest('/__dd/debugBundle', {
130-
functionName: encodeQueryName(mockFunctions[0].ref),
132+
functionName: encodeQueryName(mockFunctions[0]),
131133
});
132134
const res = createMockResponse();
133135
const next = jest.fn();
@@ -159,7 +161,7 @@ describe('Dev Server Middleware', () => {
159161
});
160162

161163
const req = createMockRequest('/__dd/executeAction', {
162-
functionName: encodeQueryName(mockFunctions[0].ref),
164+
functionName: encodeQueryName(mockFunctions[0]),
163165
args: ['world'],
164166
});
165167
const res = createMockResponse();
@@ -215,7 +217,7 @@ describe('Dev Server Middleware', () => {
215217
mockViteBuild.mockResolvedValue(mockBuildResult('export function main($) {}'));
216218

217219
const req = createMockRequest('/__dd/debugBundle', {
218-
functionName: encodeQueryName(mockFunctions[0].ref),
220+
functionName: encodeQueryName(mockFunctions[0]),
219221
});
220222
const res = createMockResponse();
221223

@@ -231,7 +233,7 @@ describe('Dev Server Middleware', () => {
231233
mockViteBuild.mockResolvedValue(mockBuildResult('// code'));
232234

233235
const req = createMockRequest('/__dd/debugBundle', {
234-
functionName: encodeQueryName(mockFunctions[0].ref),
236+
functionName: encodeQueryName(mockFunctions[0]),
235237
args: [1, 2],
236238
});
237239
const res = createMockResponse();
@@ -301,7 +303,7 @@ describe('Dev Server Middleware', () => {
301303
.reply(403, 'Forbidden');
302304

303305
const req = createMockRequest('/__dd/executeAction', {
304-
functionName: encodeQueryName(mockFunctions[0].ref),
306+
functionName: encodeQueryName(mockFunctions[0]),
305307
args: [],
306308
});
307309
const res = createMockResponse();
@@ -332,7 +334,7 @@ describe('Dev Server Middleware', () => {
332334
});
333335

334336
const req = createMockRequest('/__dd/executeAction', {
335-
functionName: encodeQueryName(mockFunctions[0].ref),
337+
functionName: encodeQueryName(mockFunctions[0]),
336338
args: [],
337339
});
338340
const res = createMockResponse();
@@ -359,7 +361,7 @@ describe('Dev Server Middleware', () => {
359361
});
360362

361363
const req = createMockRequest('/__dd/executeAction', {
362-
functionName: encodeQueryName(mockFunctions[0].ref),
364+
functionName: encodeQueryName(mockFunctions[0]),
363365
args: [],
364366
});
365367
const res = createMockResponse();
@@ -387,7 +389,7 @@ describe('Dev Server Middleware', () => {
387389
});
388390

389391
const req = createMockRequest('/__dd/executeAction', {
390-
functionName: encodeQueryName(mockFunctions[0].ref),
392+
functionName: encodeQueryName(mockFunctions[0]),
391393
args: [],
392394
});
393395
const res = createMockResponse();

packages/plugins/apps/src/vite/dev-server.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type AuthConfig = Required<AuthOptionsWithDefaults>;
3737
* Format a BackendFunction for display in log/error messages.
3838
*/
3939
function formatRef(func: BackendFunction): string {
40-
return `${func.ref.path}/${func.ref.name}`;
40+
return `${func.path}/${func.name}`;
4141
}
4242

4343
/**
@@ -74,7 +74,7 @@ async function bundleBackendFunction(
7474
const displayName = formatRef(func);
7575
const virtualId = `${DEV_VIRTUAL_PREFIX}${displayName}`;
7676
const virtualContent = generateDevVirtualEntryContent(
77-
func.ref.name,
77+
func.name,
7878
func.entryPath,
7979
args,
8080
projectRoot,
@@ -323,7 +323,7 @@ async function handleExecuteAction(
323323
* as the browser requests modules — the array grows over time.
324324
*/
325325
function buildFunctionMap(backendFunctions: BackendFunction[]): Map<string, BackendFunction> {
326-
return new Map(backendFunctions.map((f) => [encodeQueryName(f.ref), f]));
326+
return new Map(backendFunctions.map((f) => [encodeQueryName(f), f]));
327327
}
328328

329329
/**

packages/plugins/apps/src/vite/index.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@ const log = getMockLogger();
1212

1313
const functions: BackendFunction[] = [
1414
{
15-
ref: { path: 'src/backend/myHandler', name: 'myHandler' },
15+
path: 'src/backend/myHandler',
16+
name: 'myHandler',
1617
entryPath: '/src/backend/myHandler.backend.ts',
1718
},
1819
{
19-
ref: { path: 'src/backend/otherFunc', name: 'otherFunc' },
20+
path: 'src/backend/otherFunc',
21+
name: 'otherFunc',
2022
entryPath: '/src/backend/otherFunc.backend.ts',
2123
},
2224
];
2325

24-
const bundleName1 = encodeQueryName(functions[0].ref);
25-
const bundleName2 = encodeQueryName(functions[1].ref);
26+
const bundleName1 = encodeQueryName(functions[0]);
27+
const bundleName2 = encodeQueryName(functions[1]);
2628

2729
const mockViteBuild = jest.fn().mockResolvedValue({
2830
output: [

packages/plugins/apps/src/vite/proxy-codegen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface ProxyExport {
1414
* is replaced with a wrapper that calls `executeBackendFunction` with the
1515
* pre-computed query name string.
1616
*
17-
* The raw BackendFunctionRef (file path) is never present in the generated
17+
* The raw backend file path is never present in the generated
1818
* code — only the hashed query name appears, preventing backend file
1919
* structure from leaking into frontend bundles.
2020
*

0 commit comments

Comments
 (0)