diff --git a/.changeset/serious-suits-beam.md b/.changeset/serious-suits-beam.md new file mode 100644 index 0000000000..54798ab6d1 --- /dev/null +++ b/.changeset/serious-suits-beam.md @@ -0,0 +1,5 @@ +--- +'@sap-ux/fiori-annotation-api': patch +--- + +Fixed CDS compile error when new .cds files are created in memfs. diff --git a/.vscode/launch.json b/.vscode/launch.json index fff69d8f7d..103482a9a5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -340,6 +340,23 @@ }, "cwd": "${workspaceFolder}/packages/cds-annotation-parser" }, + { + "type": "node", + "request": "launch", + "name": "cds-odata-annotation-converter: Debug Current Jest File", + "program": "${workspaceFolder}/node_modules/jest/bin/jest.js", + "args": ["${fileBasenameNoExtension}", "--config", "jest.config.js", "--coverage=false", "--runInBand"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest" + }, + "cwd": "${workspaceFolder}/packages/cds-odata-annotation-converter", + "env": { + "UX_DEBUG": "false" + } + }, { "type": "node", "request": "launch", diff --git a/packages/cds-odata-annotation-converter/package.json b/packages/cds-odata-annotation-converter/package.json index 88d63aee41..7e019b454f 100644 --- a/packages/cds-odata-annotation-converter/package.json +++ b/packages/cds-odata-annotation-converter/package.json @@ -28,7 +28,7 @@ "@sap-ux/cds-annotation-parser": "workspace:*", "@sap-ux/odata-annotation-core": "workspace:*", "@sap-ux/odata-vocabularies": "workspace:*", - "@sap/ux-cds-compiler-facade": "1.16.0", + "@sap/ux-cds-compiler-facade": "1.16.1", "i18next": "20.6.1", "@sap-ux/text-document-utils": "workspace:*" }, diff --git a/packages/fiori-annotation-api/jest.setup.js b/packages/fiori-annotation-api/jest.setup.js index 42cea2b4e4..1231234e11 100644 --- a/packages/fiori-annotation-api/jest.setup.js +++ b/packages/fiori-annotation-api/jest.setup.js @@ -6,6 +6,7 @@ const fiveMinutes = 5 * 60000; const TEST_DATA_ROOT = join(__dirname, 'test', 'data'); const CDS_PROJECTS = [ join(TEST_DATA_ROOT, 'cds', 'cap-start'), + join(TEST_DATA_ROOT, 'cds', 'cap-no-apps'), join(TEST_DATA_ROOT, 'cds', 'layering'), join(TEST_DATA_ROOT, 'cds', 'term-deletion') ]; diff --git a/packages/fiori-annotation-api/package.json b/packages/fiori-annotation-api/package.json index 5cf1ee05d8..0e591d4ea9 100644 --- a/packages/fiori-annotation-api/package.json +++ b/packages/fiori-annotation-api/package.json @@ -29,7 +29,7 @@ "@sap-ux/project-access": "workspace:*", "@sap-ux/vocabularies-types": "0.13.0", "@sap-ux/xml-odata-annotation-converter": "workspace:*", - "@sap/ux-cds-compiler-facade": "1.16.0", + "@sap/ux-cds-compiler-facade": "1.16.1", "@xml-tools/ast": "5.0.5", "@xml-tools/parser": "1.0.11", "mem-fs": "2.1.0", diff --git a/packages/fiori-annotation-api/src/cds/service.ts b/packages/fiori-annotation-api/src/cds/service.ts index a63a9d239a..8c20df88eb 100644 --- a/packages/fiori-annotation-api/src/cds/service.ts +++ b/packages/fiori-annotation-api/src/cds/service.ts @@ -9,11 +9,17 @@ import type { CDSService, TextFile } from '../types'; * * @param projectRoot - Absolute path of the project. * @param serviceName - Name of the CDS service. + * @param fileCache * @param clearCache - Flag indicating if the CDS file resolution cache should be cleared. * @returns CDS service structure. */ -export async function getCDSService(projectRoot: string, serviceName: string, clearCache = false): Promise { - const files = await getCdsFiles(projectRoot, clearCache); +export async function getCDSService( + projectRoot: string, + serviceName: string, + fileCache: Map, + clearCache = false +): Promise { + const files = await getCdsFiles(projectRoot, fileCache, clearCache); const serviceFiles = files.map((uri): TextFile => { return { uri: pathToFileURL(uri).toString(), isReadOnly: uri.indexOf('node_modules') !== -1 }; }); diff --git a/packages/fiori-annotation-api/src/fiori-service.ts b/packages/fiori-annotation-api/src/fiori-service.ts index 9962bc1c1a..83d5ed1f76 100644 --- a/packages/fiori-annotation-api/src/fiori-service.ts +++ b/packages/fiori-annotation-api/src/fiori-service.ts @@ -157,7 +157,7 @@ export class FioriAnnotationService { project.projectType === 'CAPJava' || project.projectType === 'CAPNodejs' ); const finalOptions = getOptionsWithDefaults(options); - const service = await getService(project, serviceName, appName, finalOptions.clearFileResolutionCache); + const service = await getService(project, serviceName, appName, fs, finalOptions.clearFileResolutionCache); const adapter = createAdapter( project, service, @@ -241,12 +241,6 @@ export class FioriAnnotationService { this.fileCache.set(file.uri, file.content); } - // all the modified files should also be included in the cache - for (const [relativePath, value] of Object.entries(this.fs.dump())) { - const absolute = pathToFileURL(join(process.cwd(), relativePath)).toString(); - this.fileCache.set(absolute, value.contents); - } - await this.adapter.sync(this.fileCache); this.isInitialSyncCompleted = true; } @@ -470,12 +464,20 @@ async function getService( project: Project, serviceName: string, appName: string, + fsEditor: Editor | undefined, clearCache: boolean ): Promise { if (project.projectType === 'EDMXBackend') { return getLocalEDMXService(project, serviceName, appName); } else if (project.projectType === 'CAPJava' || project.projectType === 'CAPNodejs') { - return getCDSService(project.root, serviceName, clearCache); + const fileCache = new Map(); + if (fsEditor) { + for (const [relativePath, value] of Object.entries(fsEditor.dump())) { + const absolute = pathToFileURL(join(process.cwd(), relativePath)).toString(); + fileCache.set(absolute, value.contents); + } + } + return getCDSService(project.root, serviceName, fileCache, clearCache); } else { throw new Error(`Unsupported project type "${project.projectType}"!`); } diff --git a/packages/fiori-annotation-api/test/data/cds/cap-no-apps/db/schema.cds b/packages/fiori-annotation-api/test/data/cds/cap-no-apps/db/schema.cds new file mode 100644 index 0000000000..7072a6d7b6 --- /dev/null +++ b/packages/fiori-annotation-api/test/data/cds/cap-no-apps/db/schema.cds @@ -0,0 +1,100 @@ +namespace scp.cloud; + +using { + managed, + cuid, + sap.common +} from '@sap/cds/common'; + +type Url : String; + +type TechnicalBooleanFlag : Boolean @( + UI.Hidden, + Core.Computed +); +type Criticality : Integer @( + UI.Hidden, + Core.Computed +); + +type Identifier : String(100)@(title : 'Identifier'); +@cds.autoexpose +abstract entity identified : cuid { + identifier : Identifier not null; +} + +//Bolded display of first table column values can be achieved by defining annotations Common.SemanticKey and +//Common.TextArrangement for the entities key and referring to a 'human-readable' identifier to be displayed instead. + +annotate identified with @( + Common.SemanticKey : [identifier], + UI.Identification : [{Value : identifier}] +) { + + ID @Common : { + Text : identifier, + TextArrangement : #TextOnly + + }; +} +entity Incidents : managed, identified { + title : String(50) @title : '{i18n>Title}'; + category : Association to one Category @title : '{i18n>Category}'; + priority : Association to one Priority @title : '{i18n>Priority}'; + incidentStatus : Association to one IncidentStatus @title : '{i18n>IncidentStatus}'; + description : String(1000) @title : '{i18n>IncidentDescription}'; + assignedIndividual : Association to one Individual; + incidentFlow : Association to many IncidentFlow + on incidentFlow.incident = $self; + incidentProcessTimeline : Association to many IncidentProcessTimeline + on incidentProcessTimeline.incident = $self; + processingThreshold : Association to one ProcessingThreshold + on processingThreshold.incident = $self; +} + +entity ProcessingThreshold { + key id : String(10); + processingDays : Integer; + processingLimit : Integer; + incident : Association to one Incidents; +} +entity IncidentFlow : managed { + key id : UUID; + processStep : String(30)@title : '{i18n>ProcessStep}'; + stepStatus : String(10)@title : '{i18n>ProcessStepStatus}'; + criticality : Integer; + stepStartDate : Date @title : '{i18n>StepStartDate}'; + stepEndDate : Date @title : '{i18n>StepEndDate}'; + incident : Association to Incidents; +} + +entity IncidentProcessTimeline : managed { + key id : UUID; + text : String; + type : String; + startTime : DateTime; + endTime : DateTime; + incident : Association to Incidents; +} + +entity Individual : managed { + key id : UUID; +//Begin add additional properties + businessPartnerID : String; + addressID : String; +//End add additional properties + Incidents : Association to many Incidents + on Incidents.assignedIndividual = $self; +} + +entity IncidentsCodeList : common.CodeList { + key code : String(20); +} + +entity Category : IncidentsCodeList {} + +entity Priority : IncidentsCodeList { + criticality : Criticality not null default 3; +} + +entity IncidentStatus : IncidentsCodeList {} \ No newline at end of file diff --git a/packages/fiori-annotation-api/test/data/cds/cap-no-apps/package.json b/packages/fiori-annotation-api/test/data/cds/cap-no-apps/package.json new file mode 100644 index 0000000000..f654e94841 --- /dev/null +++ b/packages/fiori-annotation-api/test/data/cds/cap-no-apps/package.json @@ -0,0 +1,26 @@ +{ + "name": "teched2020-iis360", + "version": "1.0.0", + "description": "A simple CAP project.", + "repository": "", + "license": "UNLICENSED", + "private": true, + "dependencies": { + "@sap/cds": "6.7.0" + }, + "devDependencies": {}, + "cds": { + "requires": { + "db": { + "kind": "sql" + } + }, + "odata": { + "version": "v4" + } + }, + "scripts": { + "start": "cds run" + }, + "sapux": [] +} diff --git a/packages/fiori-annotation-api/test/data/cds/cap-no-apps/srv/common.cds b/packages/fiori-annotation-api/test/data/cds/cap-no-apps/srv/common.cds new file mode 100644 index 0000000000..abfb11dbb3 --- /dev/null +++ b/packages/fiori-annotation-api/test/data/cds/cap-no-apps/srv/common.cds @@ -0,0 +1,94 @@ +namespace scp.cloud; +using IncidentService as service from './incidentservice'; + +using { + cuid +} from '@sap/cds/common'; + +annotate cuid with { + ID @( + title : '{i18n>ID}', + UI.HiddenFilter, + Core.Computed + ); +} + + +annotate service.Incidents with { + ID @UI.Hidden: true; + assignedIndividual @UI.Hidden : true; +}; + +annotate service.Incidents with { + incidentStatus @Common : { + Text : incidentStatus.code, + TextArrangement : #TextOnly, + ValueListWithFixedValues + }; + category @Common : { + Text : category.code, + TextArrangement : #TextOnly, + ValueListWithFixedValues + }; + priority @Common : { + Text : priority.code, + TextArrangement : #TextOnly, + ValueListWithFixedValues + }; +}; + +annotate service.Category with { + code @Common : { + Text : name, + TextArrangement : #TextOnly + } @title : '{i18n>Category}' +}; + +annotate service.Priority with { + code @Common : { + Text : name, + TextArrangement : #TextOnly + } @title : '{i18n>Priority}' +}; + +annotate service.IncidentStatus with { + code @Common : { + Text : name, + TextArrangement : #TextOnly + } @title : '{i18n>IncidentStatus}' +}; + +annotate service.Incidents with @( + Aggregation.ApplySupported : { + $Type : 'Aggregation.ApplySupportedType', + Transformations : [ + 'aggregate', + 'topcount', + 'bottomcount', + 'identity', + 'concat', + 'groupby', + 'filter', + 'expand', + 'top', + 'skip', + 'orderby', + 'search' + ], + GroupableProperties : [ + category_code + ], + AggregatableProperties : [ + { + $Type : 'Aggregation.AggregatablePropertyType', + Property : ID + } + ] + }, + Analytics.AggregatedProperties : [{ + Name : 'IncidentsPerCategory', + AggregationMethod : 'countdistinct', + AggregatableProperty : ID, + ![@Common.Label] : '{i18n>IncidentsPerCategory}' + }] +); diff --git a/packages/fiori-annotation-api/test/data/cds/cap-no-apps/srv/incidentservice.cds b/packages/fiori-annotation-api/test/data/cds/cap-no-apps/srv/incidentservice.cds new file mode 100644 index 0000000000..e4331b37f9 --- /dev/null +++ b/packages/fiori-annotation-api/test/data/cds/cap-no-apps/srv/incidentservice.cds @@ -0,0 +1,19 @@ +using scp.cloud from '../db/schema'; + +service IncidentService { + + entity Incidents as projection on cloud.Incidents; + + entity IncidentFlow as projection on cloud.IncidentFlow; + + entity IncidentProcessTimeline as projection on cloud.IncidentProcessTimeline; + + entity ProcessingThreshold as projection on cloud.ProcessingThreshold; + + entity Individual as projection on cloud.Individual; + + entity Category as projection on cloud.Category; + + entity Priority as projection on cloud.Priority; + +} diff --git a/packages/fiori-annotation-api/test/unit/fiori-service.test.ts b/packages/fiori-annotation-api/test/unit/fiori-service.test.ts index df9c37bad1..c3cb77b912 100644 --- a/packages/fiori-annotation-api/test/unit/fiori-service.test.ts +++ b/packages/fiori-annotation-api/test/unit/fiori-service.test.ts @@ -664,7 +664,25 @@ describe('fiori annotation service', () => { expect(serialize(metadata.schema, PROJECTS.V4_CDS_START.root)).toMatchSnapshot(); }); - test('new file from memfs', async () => { + test('new file from memfs, no apps', async () => { + const project = PROJECTS.V4_CAP_NO_APPS; + const root = project.root; + const fsEditor = await createFsEditorForProject(root); + const servicesFilePath = pathFromUri(project.files.services); + const newFileName = 'new-file'; + const appName = 'incidents'; + const testData = `using from './${appName}/${newFileName}';\n`; + const annotationFilePath = join(root, 'app', appName, `${newFileName}.cds`); + fsEditor.write(annotationFilePath, ''); + fsEditor.write(servicesFilePath, testData); + + const service = await testRead(project.root, [], 'IncidentService', fsEditor); + const annotations = service.getSchema().schema.annotations; + + expect(annotations[pathToFileURL(servicesFilePath).toString()]).toHaveLength(0); + expect(annotations[pathToFileURL(annotationFilePath).toString()]).toHaveLength(0); + }); + test('new file from memfs, existing service file', async () => { const project = PROJECTS.V4_CDS_START; const root = project.root; const fsEditor = await createFsEditorForProject(root); diff --git a/packages/fiori-annotation-api/test/unit/projects.ts b/packages/fiori-annotation-api/test/unit/projects.ts index 1f1c206f5b..c0fe87422d 100644 --- a/packages/fiori-annotation-api/test/unit/projects.ts +++ b/packages/fiori-annotation-api/test/unit/projects.ts @@ -27,6 +27,11 @@ const V4_CAP_START: ProjectInfo = { name: 'cap-start', type: 'CAPNodejs' }; +const V4_CAP_NO_APPS: ProjectInfo = { + version: 'v4', + name: 'cap-no-apps', + type: 'CAPNodejs' +}; export interface ProjectTestModel> { info: ProjectInfo; root: string; @@ -38,6 +43,7 @@ const DATA_ROOT = join(__dirname, '..', 'data'); const V4_XML_START_ROOT = join(DATA_ROOT, 'v4-xml-start'); const V2_XML_START_ROOT = join(DATA_ROOT, 'v2-xml-start'); const V4_CAP_START_ROOT = join(DATA_ROOT, 'cds', 'cap-start'); +const V4_CAP_NO_APPS_ROOT = join(DATA_ROOT, 'cds', 'cap-no-apps'); const CDS_LAYERING_ROOT = join(DATA_ROOT, 'cds', 'layering'); export const PROJECTS = { @@ -70,6 +76,17 @@ export const PROJECTS = { schema: pathToFileURL(join(V4_CAP_START_ROOT, 'db', 'schema.cds')).toString() } }, + V4_CAP_NO_APPS: { + info: V4_CAP_NO_APPS, + root: V4_CAP_NO_APPS_ROOT, + serviceName: 'IncidentService', + files: { + annotations: pathToFileURL(join(V4_CAP_NO_APPS_ROOT, 'app', 'incidents', 'annotations.cds')).toString(), + metadata: pathToFileURL(join(V4_CAP_NO_APPS_ROOT, 'srv', 'common.cds')).toString(), + services: pathToFileURL(join(V4_CAP_NO_APPS_ROOT, 'app', 'services.cds')).toString(), + schema: pathToFileURL(join(V4_CAP_NO_APPS_ROOT, 'db', 'schema.cds')).toString() + } + }, CDS_LAYERING: { info: { version: 'v4', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df164b6a0e..d5a7081858 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1050,8 +1050,8 @@ importers: specifier: workspace:* version: link:../text-document-utils '@sap/ux-cds-compiler-facade': - specifier: 1.16.0 - version: 1.16.0(@sap-ux/odata-annotation-core-types@packages+odata-annotation-core-types)(@sap-ux/odata-annotation-core@packages+odata-annotation-core)(@sap-ux/project-access@packages+project-access) + specifier: 1.16.1 + version: 1.16.1(@sap-ux/odata-annotation-core-types@packages+odata-annotation-core-types)(@sap-ux/odata-annotation-core@packages+odata-annotation-core)(@sap-ux/project-access@packages+project-access) i18next: specifier: 20.6.1 version: 20.6.1 @@ -1789,8 +1789,8 @@ importers: specifier: workspace:* version: link:../xml-odata-annotation-converter '@sap/ux-cds-compiler-facade': - specifier: 1.16.0 - version: 1.16.0(@sap-ux/odata-annotation-core-types@packages+odata-annotation-core-types)(@sap-ux/odata-annotation-core@packages+odata-annotation-core)(@sap-ux/project-access@packages+project-access) + specifier: 1.16.1 + version: 1.16.1(@sap-ux/odata-annotation-core-types@packages+odata-annotation-core-types)(@sap-ux/odata-annotation-core@packages+odata-annotation-core)(@sap-ux/project-access@packages+project-access) '@xml-tools/ast': specifier: 5.0.5 version: 5.0.5 @@ -8478,8 +8478,8 @@ packages: temp-dir: 2.0.0 which: 2.0.2 - /@sap/ux-cds-compiler-facade@1.16.0(@sap-ux/odata-annotation-core-types@packages+odata-annotation-core-types)(@sap-ux/odata-annotation-core@packages+odata-annotation-core)(@sap-ux/project-access@packages+project-access): - resolution: {integrity: sha512-6YfFnQhxGsIxsS8k7eYE5r9l+HXtb7o/AaEuvt+1YnDwJypnh63gbMGkVEq1HcxqyEHJp6Md15QVGYVmquI4fA==} + /@sap/ux-cds-compiler-facade@1.16.1(@sap-ux/odata-annotation-core-types@packages+odata-annotation-core-types)(@sap-ux/odata-annotation-core@packages+odata-annotation-core)(@sap-ux/project-access@packages+project-access): + resolution: {integrity: sha512-H3RoeqJtuKT8fvtzgURbsWM2aCdktYt2XtFaciNW896UQIdMSVhi2EKD1oVGE8b8doP7i1i5J+I6Vw8dGuzYAA==} engines: {node: '>=18.16.0'} peerDependencies: '@sap-ux/odata-annotation-core': '>=0.2.2'