Skip to content

Commit 412a8a7

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix_excessive_debug_info
# Conflicts: # extractors/cds/tools/dist/cds-extractor.bundle.js.map
2 parents a6e15fd + f133c7d commit 412a8a7

38 files changed

Lines changed: 2688 additions & 2581 deletions

extractors/cds/tools/dist/cds-extractor.bundle.js

Lines changed: 2412 additions & 2378 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extractors/cds/tools/dist/cds-extractor.bundle.js.map

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,25 @@ import { existsSync, readdirSync } from 'fs';
33
import { join, resolve } from 'path';
44

55
import type { ValidatedCdsCommand } from './types';
6+
import { getPlatformInfo } from '../../environment';
67
import { fileExists } from '../../filesystem';
78
import { cdsExtractorLog } from '../../logging';
89
import type { CdsDependencyGraph } from '../parser/types';
910

1011
/** Default timeout for command execution in milliseconds. **/
1112
export const DEFAULT_COMMAND_TIMEOUT_MS = 10000;
1213

14+
/**
15+
* Returns the platform-appropriate name of the local cds binary as
16+
* created by `npm install` under `node_modules/.bin/`. On Windows the
17+
* directly-executable file is `cds.cmd`; on Unix it is `cds` (a shell
18+
* script). Using the wrong name causes execFileSync to fail (EINVAL on
19+
* Windows + Node 20+ for shimless paths).
20+
*/
21+
function localCdsBinName(): string {
22+
return getPlatformInfo().isWindows ? 'cds.cmd' : 'cds';
23+
}
24+
1325
/**
1426
* Cache for CDS command test results to avoid running the same CLI commands repeatedly.
1527
*/
@@ -336,7 +348,7 @@ function discoverAvailableCacheDirs(sourceRoot: string): string[] {
336348
for (const entry of entries) {
337349
if (entry.isDirectory() && entry.name.startsWith('cds-')) {
338350
const cacheDir = join(cacheRootDir, entry.name);
339-
const cdsBin = join(cacheDir, 'node_modules', '.bin', 'cds');
351+
const cdsBin = join(cacheDir, 'node_modules', '.bin', localCdsBinName());
340352
if (fileExists(cdsBin)) {
341353
availableDirs.push(cacheDir);
342354
}
@@ -370,7 +382,7 @@ function getBestCdsCommand(
370382

371383
// If a specific cache directory is provided and valid, prefer it
372384
if (cacheDir) {
373-
const localCdsBin = join(cacheDir, 'node_modules', '.bin', 'cds');
385+
const localCdsBin = join(cacheDir, 'node_modules', '.bin', localCdsBinName());
374386
const command = createCdsCommandForPath(localCdsBin);
375387
if (command) {
376388
const result = testCdsCommand(command, sourceRoot, true);
@@ -382,7 +394,7 @@ function getBestCdsCommand(
382394

383395
// Try any available cache directories
384396
for (const availableCacheDir of cdsCommandCache.availableCacheDirs) {
385-
const localCdsBin = join(availableCacheDir, 'node_modules', '.bin', 'cds');
397+
const localCdsBin = join(availableCacheDir, 'node_modules', '.bin', localCdsBinName());
386398
const command = createCdsCommandForPath(localCdsBin);
387399
if (command) {
388400
const result = testCdsCommand(command, sourceRoot, true);
@@ -508,6 +520,10 @@ function testCdsCommand(
508520
timeout: DEFAULT_COMMAND_TIMEOUT_MS, // timeout after 10 seconds
509521
cwd: sourceRoot,
510522
env: cleanEnv,
523+
// Required on Windows + Node 20+ to execute .cmd shims (e.g. cds.cmd, npx.cmd).
524+
// All inputs here come from the extractor's own command construction, so there is
525+
// no shell-injection surface from external input.
526+
shell: getPlatformInfo().isWindows,
511527
},
512528
).toString();
513529

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,11 @@ function createSpawnOptions(
255255
const binPathPosix = 'node_modules/.bin/';
256256
const isDirectBinary = cdsCommand.includes(binPathNative) || cdsCommand.includes(binPathPosix);
257257

258-
// Only enable shell on Windows for npx-style commands where .cmd resolution is needed.
259-
// Direct binary paths (node_modules/.bin/cds) don't need shell on any platform.
260-
const useShell = getPlatformInfo().isWindows && !isDirectBinary;
258+
// On Windows we always need shell: true so spawnSync can execute .cmd shims —
259+
// both the npx.cmd entry point AND the cds.cmd shim under node_modules/.bin/.
260+
// Node 20.12.2+ (CVE-2024-27980) refuses to spawn .cmd/.bat files without a shell.
261+
// On non-Windows, only npx-style commands need shell to resolve the executable on PATH.
262+
const useShell = getPlatformInfo().isWindows || !isDirectBinary;
261263

262264
const spawnOptions: SpawnSyncOptions = {
263265
cwd: projectBaseDir, // CRITICAL: Always use project base directory as cwd to ensure correct path generation
@@ -266,14 +268,25 @@ function createSpawnOptions(
266268
env: { ...process.env },
267269
};
268270

271+
// Always make the project's own node_modules visible to the spawned cds
272+
// process via NODE_PATH. The cds CLI's plugin loader (`require('@cap-js/...')`)
273+
// walks up node_modules from inside the cds-dk install location — when cds-dk
274+
// lives in a cache directory outside the project, that walk never reaches the
275+
// project's node_modules. Adding the project node_modules to NODE_PATH gives
276+
// Node's resolver an explicit fallback so installed plugins are found.
277+
const projectNodeModules = join(projectBaseDir, 'node_modules');
278+
269279
// Only set up Node.js environment for npx-style commands, not for direct binary execution
270280
if (cacheDir && !isDirectBinary) {
271281
const nodePath = join(cacheDir, 'node_modules');
272282

273-
// Set up environment to use the cached dependencies
283+
// Set up environment to use the cached dependencies, with the project's
284+
// node_modules appended so locally-installed cds plugins remain resolvable.
274285
spawnOptions.env = {
275286
...process.env,
276-
NODE_PATH: `${nodePath}${delimiter}${process.env.NODE_PATH ?? ''}`,
287+
NODE_PATH: [nodePath, projectNodeModules, process.env.NODE_PATH ?? '']
288+
.filter(Boolean)
289+
.join(delimiter),
277290
PATH: `${join(nodePath, '.bin')}${delimiter}${process.env.PATH}`,
278291
// Add NPM configuration to ensure dependencies are resolved from the cache directory
279292
npm_config_prefix: cacheDir,
@@ -291,6 +304,12 @@ function createSpawnOptions(
291304
delete cleanEnv.npm_config_global;
292305
delete cleanEnv.CDS_HOME;
293306

307+
// Re-add the project's node_modules to NODE_PATH so the cds plugin loader
308+
// can resolve packages the project itself installed (e.g. @cap-js/telemetry).
309+
// Without this, the cache binary fails on any project that uses cds-plugin
310+
// packages declared in its package.json.
311+
cleanEnv.NODE_PATH = projectNodeModules;
312+
294313
spawnOptions.env = cleanEnv;
295314
}
296315

extractors/cds/tools/src/cds/indexer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { spawnSync } from 'child_process';
44
import { delimiter, join } from 'path';
55

66
import { addCdsIndexerDiagnostic } from '../diagnostics';
7-
import { npxExecutable } from '../environment';
7+
import { npxExecutable, getPlatformInfo } from '../environment';
88
import { cdsExtractorLog } from '../logging';
99
import { projectInstallDependencies } from '../packageManager';
1010
import type { CdsDependencyGraph, CdsProject } from './parser/types';
@@ -116,6 +116,8 @@ export function runCdsIndexer(
116116
env,
117117
stdio: 'pipe',
118118
timeout: CDS_INDEXER_TIMEOUT_MS,
119+
// .cmd/.bat shims (npx.cmd) require shell: true on Windows + Node 20+ (CVE-2024-27980).
120+
shell: getPlatformInfo().isWindows,
119121
});
120122

121123
result.durationMs = Date.now() - startTime;

extractors/cds/tools/src/packageManager/cacheInstaller.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { dirname, join, resolve } from 'path';
66
import type { CdsDependencyCombination } from './types';
77
import { CdsDependencyGraph, CdsProject } from '../cds/parser/types';
88
import { DiagnosticSeverity } from '../diagnostics';
9-
import { npmExecutable } from '../environment';
9+
import { npmExecutable, getPlatformInfo } from '../environment';
1010
import { cdsExtractorLog } from '../logging';
1111
import { resolveCdsVersions } from './versionResolver';
1212

@@ -495,10 +495,20 @@ function installDependenciesInCache(
495495
}
496496

497497
try {
498-
execFileSync(npmExecutable(), ['install', '--quiet', '--no-audit', '--no-fund'], {
499-
cwd: cacheDir,
500-
stdio: 'inherit',
501-
});
498+
execFileSync(
499+
npmExecutable(),
500+
// --engine-strict=false: transitive deps occasionally pin obsolete Node ranges
501+
// (e.g. engines.node ^18) which would otherwise abort the install on newer Node.
502+
// npm's default is non-strict; we make that explicit so a project-level .npmrc
503+
// copied into the cache can't flip it on.
504+
['install', '--engine-strict=false', '--quiet', '--no-audit', '--no-fund'],
505+
{
506+
cwd: cacheDir,
507+
stdio: 'inherit',
508+
// .cmd/.bat shims (npm.cmd) require shell: true on Windows + Node 20+ (CVE-2024-27980).
509+
shell: getPlatformInfo().isWindows,
510+
},
511+
);
502512

503513
// Add warning diagnostic if using fallback versions
504514
if (isFallback && warning && packageJsonPath && codeqlExePath) {

extractors/cds/tools/src/packageManager/projectInstaller.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { execFileSync } from 'child_process';
44
import { join } from 'path';
55

6-
import { npmExecutable } from '../environment';
6+
import { npmExecutable, getPlatformInfo } from '../environment';
77
import type { FullDependencyInstallationResult } from './types';
88
import type { CdsProject } from '../cds/parser';
99
import { cdsExtractorLog } from '../logging';
@@ -74,11 +74,24 @@ export function projectInstallDependencies(
7474
try {
7575
execFileSync(
7676
npmExecutable(),
77-
['install', '--ignore-scripts', '--quiet', '--no-audit', '--no-fund'],
77+
// --engine-strict=false: transitive deps occasionally pin obsolete Node ranges
78+
// (e.g. engines.node ^18) which would otherwise abort the install on newer Node.
79+
// npm's default is non-strict; we make that explicit so the project's .npmrc
80+
// can't flip it on and break the retry path.
81+
[
82+
'install',
83+
'--engine-strict=false',
84+
'--ignore-scripts',
85+
'--quiet',
86+
'--no-audit',
87+
'--no-fund',
88+
],
7889
{
7990
cwd: projectPath,
8091
stdio: 'inherit',
8192
timeout: 120000, // 2-minute timeout
93+
// .cmd/.bat shims (npm.cmd) require shell: true on Windows + Node 20+ (CVE-2024-27980).
94+
shell: getPlatformInfo().isWindows,
8295
},
8396
);
8497

extractors/cds/tools/src/packageManager/versionResolver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export function getAvailableVersions(packageName: CachedPackageName): string[] {
146146
try {
147147
const output = execSync(`npm view ${packageName} versions --json`, {
148148
encoding: 'utf8',
149-
timeout: 30000, // 30 second timeout
149+
timeout: 120000, // 120 second timeout
150150
});
151151

152152
const versions: unknown = JSON.parse(output);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
DEFAULT_COMMAND_TIMEOUT_MS,
1010
determineVersionAwareCdsCommands,
1111
} from '../../../../src/cds/compiler/command';
12+
import { getPlatformInfo } from '../../../../src/environment';
1213
import { fileExists } from '../../../../src/filesystem';
1314
import { cdsExtractorLog } from '../../../../src/logging';
1415

@@ -66,6 +67,7 @@ describe('cds compiler command', () => {
6667
stdio: 'pipe',
6768
timeout: DEFAULT_COMMAND_TIMEOUT_MS,
6869
cwd: '/mock/source/root',
70+
shell: getPlatformInfo().isWindows,
6971
env: expect.objectContaining({
7072
CODEQL_EXTRACTOR_CDS_WIP_DATABASE: undefined,
7173
CODEQL_RUNNER: undefined,
@@ -98,6 +100,7 @@ describe('cds compiler command', () => {
98100
stdio: 'pipe',
99101
timeout: DEFAULT_COMMAND_TIMEOUT_MS,
100102
cwd: '/mock/source/root',
103+
shell: getPlatformInfo().isWindows,
101104
env: expect.objectContaining({
102105
CODEQL_EXTRACTOR_CDS_WIP_DATABASE: undefined,
103106
CODEQL_RUNNER: undefined,
@@ -111,6 +114,7 @@ describe('cds compiler command', () => {
111114
stdio: 'pipe',
112115
timeout: DEFAULT_COMMAND_TIMEOUT_MS,
113116
cwd: '/mock/source/root',
117+
shell: getPlatformInfo().isWindows,
114118
env: expect.objectContaining({
115119
CODEQL_EXTRACTOR_CDS_WIP_DATABASE: undefined,
116120
CODEQL_RUNNER: undefined,

extractors/cds/tools/test/src/packageManager/cacheInstaller.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ describe('installer', () => {
172172
expect(fs.writeFileSync).toHaveBeenCalled();
173173
expect(childProcess.execFileSync).toHaveBeenCalledWith(
174174
'npm',
175-
['install', '--quiet', '--no-audit', '--no-fund'],
175+
['install', '--engine-strict=false', '--quiet', '--no-audit', '--no-fund'],
176176
expect.objectContaining({ cwd: expect.stringContaining('cds-') }),
177177
);
178178
expect(result.size).toBe(1);
@@ -327,7 +327,7 @@ describe('installer', () => {
327327
expect(childProcess.execFileSync).toHaveBeenCalledTimes(1);
328328
expect(childProcess.execFileSync).toHaveBeenCalledWith(
329329
'npm',
330-
['install', '--quiet', '--no-audit', '--no-fund'],
330+
['install', '--engine-strict=false', '--quiet', '--no-audit', '--no-fund'],
331331
expect.objectContaining({ cwd: expect.stringContaining('cds-') }),
332332
);
333333

@@ -785,7 +785,7 @@ describe('installer', () => {
785785

786786
expect(childProcess.execFileSync).toHaveBeenCalledWith(
787787
'npm',
788-
['install', '--quiet', '--no-audit', '--no-fund'],
788+
['install', '--engine-strict=false', '--quiet', '--no-audit', '--no-fund'],
789789
expect.any(Object),
790790
);
791791
expect(result.size).toBe(1);

0 commit comments

Comments
 (0)