Skip to content

Commit 33d51d0

Browse files
committed
Improve handling of cds compile output files
1 parent c410382 commit 33d51d0

File tree

1 file changed

+88
-26
lines changed

1 file changed

+88
-26
lines changed

extractors/cds/tools/index-files.js

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const { execFileSync, spawnSync } = require('child_process');
2-
const { existsSync, readFileSync, statSync, writeFileSync } = require('fs');
2+
const { existsSync, readdirSync, readFileSync, renameSync, statSync } = require('fs');
33
const { arch, platform } = require('os');
4-
const { dirname, join, resolve } = require('path');
4+
const { dirname, format, join, parse, resolve } = require('path');
55
const { quote } = require('shell-quote');
66

77
// Terminate early if this script is not invoked with the required arguments.
@@ -186,29 +186,96 @@ try {
186186
cdsCommand = 'npx -y --package @sap/cds-dk cds';
187187
}
188188

189+
/**
190+
* Recursively renames all .json files to .cds.json in the given directory and
191+
* its subdirectories, except for those that already have .cds.json extension.
192+
*
193+
* @param {string} dirPath - The directory path to start recursion from
194+
*/
195+
function recursivelyRenameJsonFiles(dirPath) {
196+
// Make sure the directory exists
197+
if (!existsSync(dirPath) || !statSync(dirPath).isDirectory()) {
198+
console.log(`Directory not found or not a directory: ${dirPath}`);
199+
return;
200+
}
201+
console.log(`Processing JSON files in output directory: ${dirPath}`);
202+
// Get all entries in the directory
203+
const entries = readdirSync(dirPath, { withFileTypes: true });
204+
for (const entry of entries) {
205+
const fullPath = join(dirPath, entry.name);
206+
if (entry.isDirectory()) {
207+
// Recursively process subdirectories
208+
recursivelyRenameJsonFiles(fullPath);
209+
} else if (
210+
entry.isFile() &&
211+
entry.name.endsWith('.json') &&
212+
!entry.name.endsWith('.cds.json')
213+
) {
214+
// Rename .json files to .cds.json
215+
const newPath = format({ ...parse(fullPath), base: '', ext: '.cds.json' });
216+
renameSync(fullPath, newPath);
217+
console.log(`Renamed CDS output file from ${fullPath} to ${newPath}`);
218+
}
219+
}
220+
}
221+
189222
console.log('Processing CDS files to JSON ...');
190223

191224
/**
192225
* Run the cds compile command on each file in the response files list, outputting the
193226
* compiled JSON to a file with the same name but with a .json extension appended.
194227
*/
195-
responseFiles.forEach(rawCdsFilePath => {
196-
const cdsFilePath = quote([rawCdsFilePath]);
197-
const cdsJsonFilePath = `${cdsFilePath}.json`;
198-
console.log(`Processing CDS file ${cdsFilePath} to ${cdsJsonFilePath} ...`);
199-
const result = spawnSync(
200-
cdsCommand,
201-
[
202-
'compile', cdsFilePath,
203-
'-2', 'json',
204-
'--locations',
205-
'--log-level', 'warn'
206-
],
207-
{ shell: true, stdio: 'pipe' }
208-
);
209-
if (result.error || result.status !== 0 || !result.stdout) {
210-
const errorMessage = `Could not compile the file ${cdsFilePath}.\nReported error(s):\n\`\`\`\n${result.stderr.toString()}\n\`\`\``;
211-
console.log(errorMessage);
228+
for (const rawCdsFilePath of responseFiles) {
229+
const cdsFilePath = resolve(quote([rawCdsFilePath]));
230+
try {
231+
if (!existsSync(cdsFilePath)) {
232+
throw new Error(`Expected CDS file '${cdsFilePath}' does not exist.`);
233+
}
234+
const cdsJsonOutPath = `${cdsFilePath}.json`;
235+
console.log(`Processing CDS file ${cdsFilePath} to ${cdsJsonOutPath} ...`);
236+
const result = spawnSync(
237+
cdsCommand,
238+
[
239+
'compile', cdsFilePath,
240+
'--to', 'json',
241+
'--dest', cdsJsonOutPath,
242+
'--locations',
243+
'--log-level', 'warn'
244+
],
245+
{ cwd: sourceRoot, shell: true, stdio: 'pipe' }
246+
);
247+
if (result.error || result.status !== 0) {
248+
throw new Error(
249+
`Could not compile the file ${cdsFilePath}.\nReported error(s):\n\`\`\`\n${result.stderr.toString()}\n\`\`\``
250+
);
251+
}
252+
/**
253+
* The `cds compile` command chooses how it outputs the JSON. If it creates the
254+
* output files in a directory (at cdsJsonOutPath), then it will create the
255+
* directory when it runs and will choose the file names within that directory.
256+
* If it creates the output as a single file (at cdsJsonOutPath), then there is
257+
* nothing more to do as we create the output path by simple appending `.json` to
258+
* the input file path/name, where the input path should already end with `.cds`
259+
* (or else it shouldn't be in the response file).
260+
*
261+
* Therefore, if the output is a directory, we need to rename the output files
262+
* to have a `.cds.json` extension, not just `.json`, so that the JS extractor
263+
* recognizes them as CDS files to be indexed.
264+
*/
265+
if (!existsSync(cdsJsonOutPath) || (!statSync(cdsJsonOutPath).isFile() && !statSync(cdsJsonOutPath).isDirectory())) {
266+
throw new Error(
267+
`CDS source file '${cdsFilePath}' was not compiled to JSON. This is likely because the file does not exist or is not a valid CDS file.`
268+
);
269+
}
270+
if (statSync(cdsJsonOutPath).isDirectory()) {
271+
console.log(`CDS compiler generated JSON to output directory: ${cdsJsonOutPath}`);
272+
// Recursively rename all .json files to have a .cds.json extension
273+
recursivelyRenameJsonFiles(cdsJsonOutPath);
274+
} else {
275+
console.log(`CDS compiler generated JSON to file: ${cdsJsonOutPath}`);
276+
}
277+
} catch (errorMessage) {
278+
console.error(`ERROR: adding diagnostic for source file=${cdsFilePath} : ${errorMessage} ...`);
212279
try {
213280
execFileSync(
214281
codeqlExePath,
@@ -228,12 +295,10 @@ responseFiles.forEach(rawCdsFilePath => {
228295
);
229296
console.log(`Added error diagnostic for source file: ${cdsFilePath}`);
230297
} catch (err) {
231-
console.error(`Failed to add error diagnostic for source file=${cdsFilePath} : ${err}`);
298+
console.error(`ERROR: Failed to add error diagnostic for source file=${cdsFilePath} : ${err}`);
232299
}
233300
}
234-
// Write the compiled JSON result to cdsJsonFilePath.
235-
writeFileSync(cdsJsonFilePath, result.stdout);
236-
});
301+
}
237302

238303
let excludeFilters = '';
239304
/**
@@ -274,9 +339,6 @@ console.log(`Set $LGTM_INDEX_FILTERS to:\n${process.env.LGTM_INDEX_FILTERS}`);
274339
process.env.LGTM_INDEX_TYPESCRIPT = 'NONE';
275340
// Configure to copy over the .cds files as well, by pretending they are JSON.
276341
process.env.LGTM_INDEX_FILETYPES = '.cds:JSON';
277-
// Ignore the LGTM_INDEX_INCLUDE variable for this purpose as it may explicitly
278-
// refer to .js or .ts files.
279-
delete process.env.LGTM_INDEX_INCLUDE;
280342

281343
console.log(
282344
`Extracting the .cds.json files by running the 'javascript' extractor autobuild script:

0 commit comments

Comments
 (0)