Skip to content

Commit 119aa29

Browse files
committed
Simplify CDS project compile command
Upates the `compileProjectLevel` function of the CDS extractor to just use the project directory as the first argument to the `cds compile` command, which simplifies the code while accounting for a wide variety of project (directory and file) structures.
1 parent a6c89e6 commit 119aa29

File tree

3 files changed

+490
-49
lines changed

3 files changed

+490
-49
lines changed

extractors/cds/tools/src/cds/compiler/compile.ts

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -131,20 +131,9 @@ function compileProjectLevel(
131131
);
132132

133133
// For project-level compilation, compile the entire project together
134-
// This follows the CAP best practice of compiling db and srv directories together
134+
// Simply pass the project directory to cds compile and let it handle all subdirectories
135135
const projectAbsolutePath = join(sourceRoot, projectDir);
136136

137-
// Common directories in CAP projects that should be compiled together
138-
const capDirectories = ['db', 'srv', 'app'];
139-
const existingDirectories: string[] = [];
140-
141-
for (const dir of capDirectories) {
142-
const dirPath = join(projectAbsolutePath, dir);
143-
if (dirExists(dirPath)) {
144-
existingDirectories.push(dir);
145-
}
146-
}
147-
148137
// Check if there are any CDS files in the project at all before proceeding
149138
const allCdsFiles = globSync(join(projectAbsolutePath, '**/*.cds'), {
150139
nodir: true,
@@ -157,24 +146,6 @@ function compileProjectLevel(
157146
);
158147
}
159148

160-
if (existingDirectories.length === 0) {
161-
// If no standard directories, check if there are CDS files in the root
162-
const rootCdsFiles = globSync(join(projectAbsolutePath, '*.cds'));
163-
if (rootCdsFiles.length > 0) {
164-
existingDirectories.push('.');
165-
} else {
166-
// Find directories that contain CDS files
167-
const cdsFileParents = new Set(
168-
allCdsFiles.map((file: string) => {
169-
const relativePath = relative(projectAbsolutePath, file);
170-
const firstDir = relativePath.split('/')[0];
171-
return firstDir === relativePath ? '.' : firstDir;
172-
}),
173-
);
174-
existingDirectories.push(...Array.from(cdsFileParents));
175-
}
176-
}
177-
178149
// Generate output path for the compiled model - relative to sourceRoot for consistency
179150
const relativeOutputPath = join(projectDir, 'model.cds.json');
180151
const projectJsonOutPath = join(sourceRoot, relativeOutputPath);
@@ -185,14 +156,10 @@ function compileProjectLevel(
185156
cwd: sourceRoot, // Use sourceRoot as working directory for consistency
186157
};
187158

188-
// Convert directories to be relative to sourceRoot (include project prefix)
189-
const projectRelativeDirectories = existingDirectories.map(dir =>
190-
dir === '.' ? projectDir : join(projectDir, dir),
191-
);
192-
159+
// Simply compile the entire project directory - let cds compile handle all subdirectories
193160
const compileArgs = [
194161
'compile',
195-
...projectRelativeDirectories, // Use paths relative to sourceRoot
162+
projectDir, // Just pass the project directory, cds will find all relevant files
196163
'--to',
197164
'json',
198165
'--dest',
@@ -202,7 +169,7 @@ function compileProjectLevel(
202169
'warn',
203170
];
204171

205-
cdsExtractorLog('info', `Compiling CAP project directories: ${existingDirectories.join(', ')}`);
172+
cdsExtractorLog('info', `Compiling CAP project directory: ${projectDir}`);
206173
cdsExtractorLog(
207174
'info',
208175
`Executing CDS command in directory ${projectAbsolutePath}: command='${cdsCommand}' args='${JSON.stringify(compileArgs)}'`,

extractors/cds/tools/test/src/cds/compiler/compile.test.ts

Lines changed: 162 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ describe('compile .cds to .cds.json', () => {
421421
);
422422
});
423423

424-
it('should use sourceRoot as cwd for project-level compilation', () => {
424+
it('should use sourceRoot as cwd for project-level compilation with simplified approach', () => {
425425
// Setup
426426
const sourceRoot = '/source/root';
427427
const projectDir = 'test-project';
@@ -434,9 +434,6 @@ describe('compile .cds to .cds.json', () => {
434434
if (pattern.includes('**/*.cds')) {
435435
return ['test-project/srv/service.cds', 'test-project/db/schema.cds'];
436436
}
437-
if (pattern.includes('*.cds')) {
438-
return [];
439-
}
440437
return [];
441438
});
442439

@@ -450,12 +447,6 @@ describe('compile .cds to .cds.json', () => {
450447
};
451448
projectMap.set(projectDir, projectInfo);
452449

453-
// Mock filesystem checks
454-
(filesystem.dirExists as jest.Mock).mockImplementation(path => {
455-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
456-
return path.includes('/db') || path.includes('/srv');
457-
});
458-
459450
// Mock successful spawn process
460451
(childProcess.spawnSync as jest.Mock).mockReturnValue({
461452
status: 0,
@@ -477,14 +468,173 @@ describe('compile .cds to .cds.json', () => {
477468
expect(result.success).toBe(true);
478469
expect(result.compiledAsProject).toBe(true);
479470

480-
// CRITICAL: Verify that project-level compilation uses sourceRoot as cwd
471+
// CRITICAL: Verify that project-level compilation uses sourceRoot as cwd with simplified approach
481472
expect(childProcess.spawnSync).toHaveBeenCalledWith(
482473
'cds',
483-
expect.arrayContaining(['compile', 'test-project/db', 'test-project/srv']),
474+
expect.arrayContaining(['compile', 'test-project']), // Just the project directory
484475
expect.objectContaining({
485476
cwd: sourceRoot, // CRITICAL: Must be sourceRoot, not projectAbsolutePath
486477
}),
487478
);
479+
480+
// Ensure no specific subdirectories are passed with simplified approach
481+
const actualCall = (childProcess.spawnSync as jest.Mock).mock.calls[0];
482+
const actualArgs = actualCall[1];
483+
expect(actualArgs).not.toContain('test-project/db');
484+
expect(actualArgs).not.toContain('test-project/srv');
485+
expect(actualArgs).toContain('test-project'); // Just the base project directory
486+
});
487+
488+
it('should use simplified project-level compilation with entire directory', () => {
489+
// Setup
490+
const sourceRoot = '/source/root';
491+
const projectDir = 'bookshop-project';
492+
493+
// Set up the path.relative mock
494+
(path.relative as jest.Mock).mockImplementation(() => 'bookshop-project/index.cds');
495+
496+
// Mock globSync to return CDS files including root-level index.cds
497+
(globSync as jest.Mock).mockImplementation((pattern: string) => {
498+
if (pattern.includes('**/*.cds')) {
499+
// Return all CDS files in the project
500+
return [
501+
'bookshop-project/index.cds',
502+
'bookshop-project/srv/cat-service.cds',
503+
'bookshop-project/db/schema.cds',
504+
];
505+
}
506+
return [];
507+
});
508+
509+
// Create project dependency map with project-level compilation marker
510+
const projectMap = new Map();
511+
const projectInfo = {
512+
projectDir,
513+
cdsFiles: [
514+
'bookshop-project/index.cds',
515+
'bookshop-project/srv/cat-service.cds',
516+
'bookshop-project/db/schema.cds',
517+
],
518+
cdsFilesToCompile: ['__PROJECT_LEVEL_COMPILATION__'],
519+
imports: new Map(),
520+
};
521+
projectMap.set(projectDir, projectInfo);
522+
523+
// Mock successful spawn process
524+
(childProcess.spawnSync as jest.Mock).mockReturnValue({
525+
status: 0,
526+
stdout: Buffer.from('Compilation successful'),
527+
stderr: Buffer.from(''),
528+
});
529+
530+
// Execute
531+
const result = compileCdsToJson(
532+
'index.cds',
533+
sourceRoot,
534+
'cds',
535+
undefined,
536+
projectMap,
537+
projectDir,
538+
);
539+
540+
// Verify
541+
expect(result.success).toBe(true);
542+
expect(result.compiledAsProject).toBe(true);
543+
544+
// With simplified approach: just pass the project directory, let cds compile handle everything
545+
expect(childProcess.spawnSync).toHaveBeenCalledWith(
546+
'cds',
547+
expect.arrayContaining([
548+
'compile',
549+
'bookshop-project', // Only the project directory is needed
550+
]),
551+
expect.objectContaining({
552+
cwd: sourceRoot,
553+
}),
554+
);
555+
556+
// Ensure no specific subdirectories are passed - cds compile handles them automatically
557+
const actualCall = (childProcess.spawnSync as jest.Mock).mock.calls[0];
558+
const actualArgs = actualCall[1];
559+
expect(actualArgs).not.toContain('bookshop-project/db');
560+
expect(actualArgs).not.toContain('bookshop-project/srv');
561+
expect(actualArgs).toContain('bookshop-project'); // Just the base project directory
562+
});
563+
564+
it('should compile entire project directory with simplified approach', () => {
565+
// This test verifies the simplified approach: instead of determining specific subdirectories,
566+
// we just pass the project directory to cds compile and let it handle everything.
567+
568+
// Setup
569+
const sourceRoot = '/source/root';
570+
const projectDir = 'bookshop-project';
571+
572+
// Set up the path.relative mock
573+
(path.relative as jest.Mock).mockImplementation(() => 'bookshop-project/index.cds');
574+
575+
// Mock globSync to return CDS files including root-level index.cds
576+
(globSync as jest.Mock).mockImplementation((pattern: string) => {
577+
if (pattern.includes('**/*.cds')) {
578+
// Return all CDS files in the project
579+
return [
580+
'bookshop-project/index.cds',
581+
'bookshop-project/srv/cat-service.cds',
582+
'bookshop-project/db/schema.cds',
583+
];
584+
}
585+
return [];
586+
});
587+
588+
// Create project dependency map with project-level compilation marker
589+
const projectMap = new Map();
590+
const projectInfo = {
591+
projectDir,
592+
cdsFiles: [
593+
'bookshop-project/index.cds',
594+
'bookshop-project/srv/cat-service.cds',
595+
'bookshop-project/db/schema.cds',
596+
],
597+
cdsFilesToCompile: ['__PROJECT_LEVEL_COMPILATION__'],
598+
imports: new Map(),
599+
};
600+
projectMap.set(projectDir, projectInfo);
601+
602+
// Mock successful spawn process
603+
(childProcess.spawnSync as jest.Mock).mockReturnValue({
604+
status: 0,
605+
stdout: Buffer.from('Compilation successful'),
606+
stderr: Buffer.from(''),
607+
});
608+
609+
// Execute
610+
const result = compileCdsToJson(
611+
'index.cds',
612+
sourceRoot,
613+
'cds',
614+
undefined,
615+
projectMap,
616+
projectDir,
617+
);
618+
619+
// Verify
620+
expect(result.success).toBe(true);
621+
expect(result.compiledAsProject).toBe(true);
622+
623+
// With the simplified approach, we should just pass the project directory
624+
// and let cds compile handle all subdirectories automatically
625+
const actualCall = (childProcess.spawnSync as jest.Mock).mock.calls[0];
626+
const actualArgs = actualCall[1]; // Second argument is the args array
627+
628+
// The compile command should include only the project directory
629+
expect(actualArgs).toContain('bookshop-project');
630+
631+
// Verify the simplified command structure
632+
const compileIndex = actualArgs.indexOf('compile');
633+
const destIndex = actualArgs.indexOf('--to');
634+
const directoryArgs = actualArgs.slice(compileIndex + 1, destIndex);
635+
636+
// Should only contain the project directory - no need to specify subdirectories
637+
expect(directoryArgs).toEqual(['bookshop-project']);
488638
});
489639
});
490640
});

0 commit comments

Comments
 (0)