Skip to content

Commit 76340e5

Browse files
committed
Fix CDS extractor cds-indexer deps
Attempt to fix the CDS extractor's support for private "cds-indexer" package installation by ensuring that the associated project's entire node_modules directory is copied instead of just the cachable subset of the CAP projects NodeJS dependencies.
1 parent 0711aaa commit 76340e5

File tree

2 files changed

+119
-0
lines changed

2 files changed

+119
-0
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { delimiter, join } from 'path';
55

66
import { addCdsIndexerDiagnostic } from '../diagnostics';
77
import { cdsExtractorLog } from '../logging';
8+
import { projectInstallDependencies } from '../packageManager';
89
import type { CdsDependencyGraph, CdsProject } from './parser/types';
910

1011
/** Maximum time (ms) allowed for a single cds-indexer invocation. */
@@ -199,6 +200,19 @@ export function orchestrateCdsIndexer(
199200

200201
if (result.success) {
201202
summary.successfulRuns++;
203+
204+
// Install the project's full dependencies so the CDS compiler can
205+
// resolve all CDS model imports (e.g. `@sap/cds-shim`) during
206+
// compilation. The cache directory only contains `@sap/cds`,
207+
// `@sap/cds-dk`, and `@sap/cds-indexer`, which is not enough for
208+
// projects that reference additional CDS packages.
209+
const installResult = projectInstallDependencies(project, sourceRoot);
210+
if (!installResult.success) {
211+
cdsExtractorLog(
212+
'warn',
213+
`Full dependency installation failed for project '${projectDir}' after successful cds-indexer run: ${installResult.error ?? 'unknown error'}`,
214+
);
215+
}
202216
} else {
203217
summary.failedRuns++;
204218

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

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
runCdsIndexer,
77
} from '../../../src/cds/indexer';
88
import { CdsDependencyGraph, CdsProject, PackageJson } from '../../../src/cds/parser/types';
9+
import * as projectInstaller from '../../../src/packageManager/projectInstaller';
910

1011
// Mock dependencies
1112
jest.mock('child_process', () => ({
@@ -27,6 +28,17 @@ jest.mock('../../../src/diagnostics', () => ({
2728
},
2829
}));
2930

31+
jest.mock('../../../src/packageManager/projectInstaller', () => ({
32+
projectInstallDependencies: jest.fn().mockReturnValue({
33+
success: true,
34+
projectDir: '/source/myProject',
35+
warnings: [],
36+
durationMs: 1000,
37+
timedOut: false,
38+
}),
39+
needsFullDependencyInstallation: jest.fn().mockReturnValue(false),
40+
}));
41+
3042
// Helper to create a minimal CdsProject mock
3143
function createMockProject(projectDir: string, packageJson?: PackageJson): CdsProject {
3244
return {
@@ -481,5 +493,98 @@ describe('cds/indexer', () => {
481493
}),
482494
);
483495
});
496+
497+
it('should install full project dependencies after successful cds-indexer run', () => {
498+
(childProcess.spawnSync as jest.Mock).mockReturnValue({
499+
status: 0,
500+
stdout: Buffer.from(''),
501+
stderr: Buffer.from(''),
502+
error: undefined,
503+
});
504+
505+
const project = createMockProject('myProject', {
506+
name: 'my-cap-app',
507+
dependencies: {
508+
'@sap/cds': '^7.0.0',
509+
'@sap/cds-indexer': '^1.0.0',
510+
},
511+
});
512+
const graph = createMockDependencyGraph([project]);
513+
514+
orchestrateCdsIndexer(graph, '/source', new Map());
515+
516+
expect(projectInstaller.projectInstallDependencies).toHaveBeenCalledWith(
517+
project,
518+
'/source',
519+
);
520+
});
521+
522+
it('should not install full project dependencies when cds-indexer fails', () => {
523+
(childProcess.spawnSync as jest.Mock).mockReturnValue({
524+
status: 1,
525+
stdout: Buffer.from(''),
526+
stderr: Buffer.from('indexer error'),
527+
error: undefined,
528+
});
529+
530+
const project = createMockProject('myProject', {
531+
name: 'my-cap-app',
532+
dependencies: {
533+
'@sap/cds': '^7.0.0',
534+
'@sap/cds-indexer': '^1.0.0',
535+
},
536+
});
537+
const graph = createMockDependencyGraph([project]);
538+
539+
orchestrateCdsIndexer(graph, '/source', new Map());
540+
541+
expect(projectInstaller.projectInstallDependencies).not.toHaveBeenCalled();
542+
});
543+
544+
it('should not install full project dependencies for projects without cds-indexer', () => {
545+
const project = createMockProject('myProject', {
546+
name: 'my-cap-app',
547+
dependencies: { '@sap/cds': '^7.0.0' },
548+
});
549+
const graph = createMockDependencyGraph([project]);
550+
551+
orchestrateCdsIndexer(graph, '/source', new Map());
552+
553+
expect(projectInstaller.projectInstallDependencies).not.toHaveBeenCalled();
554+
});
555+
556+
it('should continue even when full dependency installation fails after indexer success', () => {
557+
(childProcess.spawnSync as jest.Mock).mockReturnValue({
558+
status: 0,
559+
stdout: Buffer.from(''),
560+
stderr: Buffer.from(''),
561+
error: undefined,
562+
});
563+
564+
(projectInstaller.projectInstallDependencies as jest.Mock).mockReturnValue({
565+
success: false,
566+
projectDir: '/source/myProject',
567+
warnings: ['npm install failed'],
568+
durationMs: 500,
569+
timedOut: false,
570+
error: 'npm install failed',
571+
});
572+
573+
const project = createMockProject('myProject', {
574+
name: 'my-cap-app',
575+
dependencies: {
576+
'@sap/cds': '^7.0.0',
577+
'@sap/cds-indexer': '^1.0.0',
578+
},
579+
});
580+
const graph = createMockDependencyGraph([project]);
581+
582+
const summary = orchestrateCdsIndexer(graph, '/source', new Map());
583+
584+
// Indexer itself succeeded; dependency install failure is non-fatal
585+
expect(summary.successfulRuns).toBe(1);
586+
expect(summary.failedRuns).toBe(0);
587+
expect(projectInstaller.projectInstallDependencies).toHaveBeenCalled();
588+
});
484589
});
485590
});

0 commit comments

Comments
 (0)