11import { spawnSync , SpawnSyncOptions } from 'child_process' ;
2- import { resolve , join , delimiter , relative } from 'path' ;
3-
4- import { globSync } from 'glob' ;
2+ import { resolve , join , delimiter , relative , dirname , basename } from 'path' ;
53
64import { CdsCompilationResult } from './types' ;
75import { getCdsVersion } from './version' ;
@@ -22,12 +20,43 @@ function parseCommandForSpawn(commandString: string): { executable: string; base
2220}
2321
2422/**
25- * Compiles a CDS file to JSON using robust, project-aware compilation only.
26- * This function has been refactored to align with the autobuild.md vision by removing all
27- * forms of individual file compilation and ensuring only project-aware compilation is used.
23+ * Determines compilation targets for a CDS project according to the new project-only compilation approach.
24+ * @param project The CDS project
25+ * @param sourceRoot The source root directory
26+ * @returns Array of compilation targets (directories or files relative to project base)
27+ */
28+ function determineCompilationTargets ( project : BasicCdsProject , sourceRoot : string ) : string [ ] {
29+ const projectAbsolutePath = join ( sourceRoot , project . projectDir ) ;
30+
31+ // Check for standard CAP directories
32+ const capDirectories = [ 'db' , 'srv' , 'app' ] ;
33+ const existingCapDirs = capDirectories . filter ( dir => dirExists ( join ( projectAbsolutePath , dir ) ) ) ;
34+
35+ if ( existingCapDirs . length > 0 ) {
36+ // Use standard CAP directories
37+ return existingCapDirs ;
38+ }
39+
40+ // Check for root-level CDS files
41+ const rootCdsFiles = project . cdsFiles
42+ . filter ( file => dirname ( join ( sourceRoot , file ) ) === projectAbsolutePath )
43+ . map ( file => basename ( file ) ) ;
44+
45+ if ( rootCdsFiles . length > 0 ) {
46+ // Use root-level files
47+ return rootCdsFiles ;
48+ }
49+
50+ // Use all CDS files with their relative paths
51+ return project . cdsFiles . map ( file => relative ( projectAbsolutePath , join ( sourceRoot , file ) ) ) ;
52+ }
53+
54+ /**
55+ * Compiles a CDS project to JSON using project-level compilation only.
56+ * This function has been simplified to only use project-level compilation,
57+ * eliminating all individual file compilation logic and standardizing output
58+ * to a single model.cds.json file per project.
2859 *
29- * For root files, this will compile them to their 1:1 .cds.json representation if and only
30- * if the file is a true root file in a project.
3160 *
3261 * @param cdsFilePath The path to the CDS file to compile, relative to the `sourceRoot`.
3362 * @param sourceRoot The source root directory scanned by the CDS extractor.
@@ -73,131 +102,54 @@ export function compileCdsToJson(
73102 }
74103
75104 const project = projectMap . get ( projectDir ) ;
76- const relativePath = relative ( sourceRoot , resolvedCdsFilePath ) ;
77-
78- // Check if this is a project-level compilation marker.
79- if ( shouldUseProjectLevelCompilation ( project ) ) {
80- return compileProjectLevel (
81- resolvedCdsFilePath ,
82- sourceRoot ,
83- projectDir ,
84- cdsCommand ,
85- spawnOptions ,
86- versionInfo ,
87- ) ;
88- }
89105
90- // Check if this file is in the list of files to compile for this project.
91- if ( ! shouldCompileIndividually ( project , relativePath ) ) {
92- cdsExtractorLog (
93- 'info' ,
94- `${ resolvedCdsFilePath } is imported by other files - will be compiled as part of a project ${ versionInfo } ...` ,
95- ) ;
96- const cdsJsonOutPath = `${ resolvedCdsFilePath } .json` ;
97- return {
98- success : true ,
99- outputPath : cdsJsonOutPath ,
100- compiledAsProject : true ,
101- message : 'File was compiled as part of a project-based compilation' ,
102- } ;
103- } else {
104- // This is a root file - compile it using project-aware approach to its 1:1 representation
105- cdsExtractorLog (
106- 'info' ,
107- `${ resolvedCdsFilePath } identified as a root CDS file - using project-aware compilation for root file ${ versionInfo } ...` ,
108- ) ;
109- return compileRootFileAsProject (
110- resolvedCdsFilePath ,
111- sourceRoot ,
112- projectDir ,
113- cdsCommand ,
114- spawnOptions ,
115- ) ;
116- }
106+ // Always use project-level compilation
107+ return compileProject ( sourceRoot , projectDir , cdsCommand , spawnOptions , versionInfo , project ! ) ;
117108 } catch ( error ) {
118109 return { success : false , message : String ( error ) } ;
119110 }
120111}
121112
122113/**
123- * Handles project-level compilation for CAP projects with typical directory structure .
114+ * Handles project-level compilation for CAP projects.
124115 * CRITICAL: Uses the project base directory as cwd and calculates paths relative to project base directory.
125116 *
126- * @param resolvedCdsFilePath The resolved CDS file path that triggered this compilation
127117 * @param sourceRoot The source root directory
128118 * @param projectDir The project directory (relative to sourceRoot)
129119 * @param cdsCommand The CDS command to use
130120 * @param spawnOptions Pre-configured spawn options with project base directory as cwd
131121 * @param versionInfo Version information for logging
122+ * @param project The CDS project instance
132123 * @returns Compilation result
133124 */
134- function compileProjectLevel (
135- resolvedCdsFilePath : string ,
125+ function compileProject (
136126 sourceRoot : string ,
137127 projectDir : string ,
138128 cdsCommand : string ,
139129 spawnOptions : SpawnSyncOptions ,
140130 versionInfo : string ,
131+ project : BasicCdsProject ,
141132) : CdsCompilationResult {
142133 cdsExtractorLog (
143134 'info' ,
144- `${ resolvedCdsFilePath } is part of a CAP project - using project-aware compilation ${ versionInfo } ...` ,
135+ `Compiling CDS project using project-level compilation ${ versionInfo } ...` ,
145136 ) ;
146137
147- // For project-level compilation, compile the entire project together
148- // This follows the CAP best practice of compiling db and srv directories together
149- const projectAbsolutePath = join ( sourceRoot , projectDir ) ;
150-
151- // Common directories in CAP projects that should be compiled together
152- const capDirectories = [ 'db' , 'srv' , 'app' ] ;
153- const existingDirectories : string [ ] = [ ] ;
154-
155- for ( const dir of capDirectories ) {
156- const dirPath = join ( projectAbsolutePath , dir ) ;
157- if ( dirExists ( dirPath ) ) {
158- existingDirectories . push ( dir ) ;
159- }
160- }
138+ // Determine compilation targets using the new centralized logic
139+ const compilationTargets = determineCompilationTargets ( project , sourceRoot ) ;
161140
162- // Check if there are any CDS files in the project at all before proceeding
163- const allCdsFiles = globSync ( join ( projectAbsolutePath , '**/*.cds' ) , {
164- nodir : true ,
165- ignore : [ '**/node_modules/**' ] ,
166- } ) ;
167-
168- if ( allCdsFiles . length === 0 ) {
141+ if ( compilationTargets . length === 0 ) {
169142 throw new Error (
170143 `Project directory '${ projectDir } ' does not contain any CDS files and cannot be compiled` ,
171144 ) ;
172145 }
173146
174- if ( existingDirectories . length === 0 ) {
175- // If no standard directories, check if there are CDS files in the root of the project.
176- const rootCdsFiles = globSync ( join ( projectAbsolutePath , '*.cds' ) ) ;
177- if ( rootCdsFiles . length > 0 ) {
178- existingDirectories . push ( '.' ) ;
179- } else {
180- // Find directories that contain `.cds` files.
181- const cdsFileParents = new Set (
182- allCdsFiles . map ( ( file : string ) => {
183- const relativePath = relative ( projectAbsolutePath , file ) ;
184- const firstDir = relativePath . split ( '/' ) [ 0 ] ;
185- return firstDir === relativePath ? '.' : firstDir ;
186- } ) ,
187- ) ;
188- existingDirectories . push ( ...Array . from ( cdsFileParents ) ) ;
189- }
190- }
191-
192- // Generate output path for the compiled model - relative to project base directory.
147+ // Generate output path for the compiled model - always model.cds.json in project base directory
193148 const projectJsonOutPath = join ( sourceRoot , projectDir , 'model.cds.json' ) ;
194149
195- // Convert directories to be relative to project base directory.
196- const projectRelativeDirectories = existingDirectories ;
197-
198150 const compileArgs = [
199151 'compile' ,
200- ...projectRelativeDirectories , // Use paths relative to project base directory.
152+ ...compilationTargets ,
201153 '--to' ,
202154 'json' ,
203155 '--dest' ,
@@ -207,7 +159,7 @@ function compileProjectLevel(
207159 'warn' ,
208160 ] ;
209161
210- cdsExtractorLog ( 'info' , `Compiling CAP project directories : ${ existingDirectories . join ( ', ' ) } ` ) ;
162+ cdsExtractorLog ( 'info' , `Compiling CDS project targets : ${ compilationTargets . join ( ', ' ) } ` ) ;
211163 cdsExtractorLog (
212164 'info' ,
213165 `Running compilation task for CDS project '${ projectDir } ': command='${ cdsCommand } ' args='${ JSON . stringify ( compileArgs ) } '` ,
@@ -270,107 +222,6 @@ function compileProjectLevel(
270222 } ;
271223}
272224
273- /**
274- * Compiles a root CDS file using project-aware approach for 1:1 .cds.json representation.
275- * This follows the autobuild.md vision of project-aware compilation only.
276- *
277- * @param resolvedCdsFilePath The resolved CDS file path
278- * @param sourceRoot The source root directory
279- * @param projectDir The project directory
280- * @param cdsCommand The CDS command to use
281- * @param spawnOptions Pre-configured spawn options
282- * @param versionInfo Version information for logging
283- * @returns The {@link CdsCompilationResult}
284- */
285- function compileRootFileAsProject (
286- resolvedCdsFilePath : string ,
287- sourceRoot : string ,
288- projectDir : string ,
289- cdsCommand : string ,
290- spawnOptions : SpawnSyncOptions ,
291- ) : CdsCompilationResult {
292- // Calculate project base directory and file path relative to project
293- const projectBaseDir = join ( sourceRoot , projectDir ) ;
294- const relativeCdsPath = relative ( projectBaseDir , resolvedCdsFilePath ) ;
295- const cdsJsonOutPath = `${ resolvedCdsFilePath } .json` ;
296-
297- // Use project-aware compilation with specific file target
298- const compileArgs = [
299- 'compile' ,
300- relativeCdsPath , // Compile the specific file relative to project base directory
301- '--to' ,
302- 'json' ,
303- '--dest' ,
304- `${ relativeCdsPath } .json` ,
305- '--locations' ,
306- '--log-level' ,
307- 'warn' ,
308- ] ;
309-
310- cdsExtractorLog (
311- 'info' ,
312- `Compiling root CDS file using project-aware approach: ${ relativeCdsPath } ` ,
313- ) ;
314- cdsExtractorLog (
315- 'info' ,
316- `Executing CDS command: command='${ cdsCommand } ' args='${ JSON . stringify ( compileArgs ) } '` ,
317- ) ;
318-
319- // Execute the compilation
320- // Parse command for proper spawnSync execution
321- const { executable, baseArgs } = parseCommandForSpawn ( cdsCommand ) ;
322- const allArgs = [ ...baseArgs , ...compileArgs ] ;
323-
324- const result = spawnSync ( executable , allArgs , spawnOptions ) ;
325-
326- if ( result . error ) {
327- cdsExtractorLog ( 'error' , `SpawnSync error: ${ result . error . message } ` ) ;
328- throw new Error ( `Error executing CDS compiler: ${ result . error . message } ` ) ;
329- }
330-
331- // Log stderr for debugging even on success
332- if ( result . stderr && result . stderr . length > 0 ) {
333- cdsExtractorLog ( 'warn' , `CDS stderr output: ${ result . stderr . toString ( ) } ` ) ;
334- }
335-
336- if ( result . status !== 0 ) {
337- cdsExtractorLog ( 'error' , `CDS command failed with status ${ result . status } ` ) ;
338- cdsExtractorLog (
339- 'error' ,
340- `Command: ${ cdsCommand } ${ compileArgs . map ( arg => ( arg . includes ( ' ' ) ? `"${ arg } "` : arg ) ) . join ( ' ' ) } ` ,
341- ) ;
342- cdsExtractorLog ( 'error' , `Stdout: ${ result . stdout ?. toString ( ) || 'No stdout' } ` ) ;
343- cdsExtractorLog ( 'error' , `Stderr: ${ result . stderr ?. toString ( ) || 'No stderr' } ` ) ;
344- throw new Error (
345- `Could not compile the root CDS file ${ relativeCdsPath } .\nReported error(s):\n\`\`\`\n${
346- result . stderr ?. toString ( ) || 'Unknown error'
347- } \n\`\`\``,
348- ) ;
349- }
350-
351- if ( ! fileExists ( cdsJsonOutPath ) && ! dirExists ( cdsJsonOutPath ) ) {
352- throw new Error (
353- `Root CDS file '${ relativeCdsPath } ' was not compiled to JSON. Expected output: ${ cdsJsonOutPath } ` ,
354- ) ;
355- }
356-
357- // Handle directory output if the CDS compiler generated a directory
358- if ( dirExists ( cdsJsonOutPath ) ) {
359- cdsExtractorLog ( 'info' , `CDS compiler generated JSON to output directory: ${ cdsJsonOutPath } ` ) ;
360- // Recursively rename all .json files to have a .cds.json extension
361- recursivelyRenameJsonFiles ( cdsJsonOutPath ) ;
362- } else {
363- cdsExtractorLog ( 'info' , `CDS compiler generated JSON to file: ${ cdsJsonOutPath } ` ) ;
364- }
365-
366- return {
367- success : true ,
368- outputPath : cdsJsonOutPath ,
369- compiledAsProject : true ,
370- message : 'Root file compiled using project-aware compilation' ,
371- } ;
372- }
373-
374225/**
375226 * Creates spawn options for CDS compilation processes.
376227 * CRITICAL: Always sets cwd to project base directory to ensure generated JSON paths are relative to project base directory.
@@ -425,27 +276,3 @@ function createSpawnOptions(
425276
426277 return spawnOptions ;
427278}
428-
429- /**
430- * Determines if a file should be compiled individually or skipped because it's part of a project.
431- *
432- * @param project The CDS project
433- * @param relativePath The relative path of the file being checked
434- * @returns true if the file should be compiled individually
435- */
436- function shouldCompileIndividually (
437- project : BasicCdsProject | undefined ,
438- relativePath : string ,
439- ) : boolean {
440- return project ?. cdsFilesToCompile ?. includes ( relativePath ) ?? true ;
441- }
442-
443- /**
444- * Determines if the given project should use project-level compilation.
445- *
446- * @param project The CDS project to check
447- * @returns true if project-level compilation should be used
448- */
449- function shouldUseProjectLevelCompilation ( project : BasicCdsProject | undefined ) : boolean {
450- return project ?. cdsFilesToCompile ?. includes ( '__PROJECT_LEVEL_COMPILATION__' ) ?? false ;
451- }
0 commit comments