Skip to content

Commit 1b10e9f

Browse files
feat: Adapt CF ADP project structure to work with approuter backend middleware (#4436)
* feat: add approuter support * refactor: code improvements * refactor: interfaces and jsdocs * refactor: code and split methods * refactor: move types * chore: add cset * refactor: move types * refactor: set default env options in process * refactor: code improvements * refactor: middleware options * test: add test suites * test: add test suites * fix: sonar issues * refactor: improve code quality and tests * test: add test case * fix: sonar issue with regxp * feat: add service cred retrieval for approuter env options * test: add new tooling tests * test: add missing test cases * fix: sonar issues * fix: dest merging from env path * fix: review comments * feat: add approuter lazy loading, dynamic port allocation, loop prevention mechanism * fix: sonar issue * refactor: move interfaces * refactor: code improvements * feat: add needed configurations for bas * refactor: clear text * feat: update xsuaa service runtime * fix: incorrect credentials object return for vcap services * fix: add error handling for approuter start * Linting auto fix commit * fix: add wait flag for cf cli when updating service * refactor: adjust the structure of cf project and clenup files * refactor: bump package versions * chore: add cset * refactor: remove comment * chore: update cset * fix: review comments * fix: merge errors * chore: remove extra cset * test: update snapshot * fix: bump specVersion of cf backend middleware * chore: update cset * fix: version ^ --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent e3bdcd4 commit 1b10e9f

18 files changed

Lines changed: 160 additions & 1210 deletions

File tree

.changeset/strict-papers-shave.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@sap-ux/generator-adp': patch
3+
'@sap-ux/adp-tooling': patch
4+
'@sap-ux/create': patch
5+
'@sap-ux/backend-proxy-middleware-cf': patch
6+
---
7+
8+
feat: Adapt CF ADP project structure to work with approuter backend middleware

packages/adp-tooling/src/cf/app/discovery.ts

Lines changed: 1 addition & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import type AdmZip from 'adm-zip';
2-
import { readFileSync, existsSync } from 'node:fs';
3-
import { join } from 'node:path';
42

53
import type { ToolsLogger } from '@sap-ux/logger';
64

75
import { t } from '../../i18n';
86
import { extractXSApp } from '../utils';
97
import { getFDCApps } from '../services/api';
10-
import type { CfConfig, CFApp, ServiceKeys, XsApp, XsAppRoute } from '../../types';
8+
import type { CfConfig, CFApp, ServiceKeys, XsApp } from '../../types';
119

1210
/**
1311
* Get the app host ids from service keys.
@@ -56,30 +54,6 @@ export function getBackendUrlsFromServiceKeys(serviceKeys: ServiceKeys[]): strin
5654
return urls;
5755
}
5856

59-
/**
60-
* Extract destination to URL mapping from service key endpoints.
61-
*
62-
* @param {ServiceKeys[]} serviceKeys - The service keys containing endpoints.
63-
* @returns {Map<string, string>} Map of destination names to URLs.
64-
*/
65-
function extractDestinationToUrlMap(serviceKeys: ServiceKeys[]): Map<string, string> {
66-
const destinationToUrl = new Map<string, string>();
67-
const endpoints = serviceKeys[0]?.credentials?.endpoints as
68-
| Record<string, { url?: string; destination?: string }>
69-
| undefined;
70-
71-
if (endpoints && typeof endpoints === 'object') {
72-
for (const key in endpoints) {
73-
const endpoint = endpoints[key];
74-
if (endpoint?.url && endpoint.destination) {
75-
destinationToUrl.set(endpoint.destination, endpoint.url);
76-
}
77-
}
78-
}
79-
80-
return destinationToUrl;
81-
}
82-
8357
/**
8458
* Clean regex pattern from route source.
8559
*
@@ -105,116 +79,6 @@ function cleanRoutePath(source: string): string {
10579
return path;
10680
}
10781

108-
/**
109-
* Process a route and extract path and pathRewrite from source and target.
110-
*
111-
* @param {XsAppRoute} route - The route object from xs-app.json.
112-
* @param {Map<string, { paths: Set<string>; pathRewrite?: string }>} destinationToPaths - Map to store destination info.
113-
*/
114-
function processRouteForDestination(
115-
route: XsAppRoute,
116-
destinationToPaths: Map<string, { paths: Set<string>; pathRewrite?: string }>
117-
): void {
118-
const destination = route.destination;
119-
const service = route.service;
120-
121-
if (!destination || service === 'html5-apps-repo-rt' || !route.source) {
122-
return;
123-
}
124-
125-
const path = cleanRoutePath(route.source);
126-
if (path) {
127-
if (!destinationToPaths.has(destination)) {
128-
destinationToPaths.set(destination, { paths: new Set<string>() });
129-
}
130-
131-
const destInfo = destinationToPaths.get(destination)!;
132-
destInfo.paths.add(path);
133-
134-
// Extract pathRewrite from target if available
135-
if (route.target && typeof route.target === 'string') {
136-
const pathRewrite = cleanRoutePath(route.target);
137-
if (pathRewrite && !destInfo.pathRewrite) {
138-
destInfo.pathRewrite = pathRewrite;
139-
}
140-
}
141-
}
142-
}
143-
144-
/**
145-
* Extract destination to paths mapping from xs-app.json routes with pathRewrite info.
146-
*
147-
* @param {string} xsAppPath - Path to xs-app.json file.
148-
* @returns {Map<string, { paths: Set<string>; pathRewrite?: string }>} Map of destination names to path info.
149-
*/
150-
function extractDestinationToPathsMap(xsAppPath: string): Map<string, { paths: Set<string>; pathRewrite?: string }> {
151-
const destinationToPaths = new Map<string, { paths: Set<string>; pathRewrite?: string }>();
152-
153-
try {
154-
const xsAppContent = readFileSync(xsAppPath, 'utf8');
155-
const xsApp = JSON.parse(xsAppContent) as XsApp;
156-
157-
if (xsApp?.routes) {
158-
for (const route of xsApp.routes) {
159-
processRouteForDestination(route, destinationToPaths);
160-
}
161-
}
162-
} catch (e) {
163-
throw new Error(t('error.invalidXsAppJson', { error: (e as Error).message }));
164-
}
165-
166-
return destinationToPaths;
167-
}
168-
169-
/**
170-
* Maps backend URLs to their corresponding OAuth paths based on destination matching
171-
* between xs-app.json routes and credentials.json endpoints.
172-
*
173-
* @param {ServiceKeys[]} serviceKeys - The service keys containing endpoints with destinations.
174-
* @param {string} basePath - Path to the .adp/reuse folder containing xs-app.json files.
175-
* @returns {Array<{ url: string; paths: string[]; pathRewrite?: string }>} Array of URL-to-paths mappings with optional pathRewrite.
176-
*/
177-
export function getBackendUrlsWithPaths(
178-
serviceKeys: ServiceKeys[],
179-
basePath: string
180-
): Array<{ url: string; paths: string[]; pathRewrite?: string }> {
181-
const destinationToUrl = extractDestinationToUrlMap(serviceKeys);
182-
183-
const reuseXsAppPath = join(basePath, '.adp', 'reuse', 'xs-app.json');
184-
const distXsAppPath = join(basePath, 'dist', 'xs-app.json');
185-
186-
let xsAppPath: string;
187-
if (existsSync(reuseXsAppPath)) {
188-
xsAppPath = reuseXsAppPath;
189-
} else if (existsSync(distXsAppPath)) {
190-
xsAppPath = distXsAppPath;
191-
} else {
192-
throw new Error(t('error.xsAppJsonNotFound', { paths: `${reuseXsAppPath}, ${distXsAppPath}` }));
193-
}
194-
195-
const destinationToPaths = extractDestinationToPathsMap(xsAppPath);
196-
197-
const result = [];
198-
199-
for (const [destination, pathInfo] of destinationToPaths.entries()) {
200-
const url = destinationToUrl.get(destination);
201-
if (url) {
202-
const entry: { url: string; paths: string[]; pathRewrite?: string } = {
203-
url,
204-
paths: Array.from(pathInfo.paths)
205-
};
206-
207-
if (pathInfo.pathRewrite) {
208-
entry.pathRewrite = pathInfo.pathRewrite;
209-
}
210-
211-
result.push(entry);
212-
}
213-
}
214-
215-
return result;
216-
}
217-
21882
/**
21983
* Extract endpoint destinations from service keys.
22084
*

packages/adp-tooling/src/cf/project/ui5-app-info.ts

Lines changed: 0 additions & 44 deletions
This file was deleted.

packages/adp-tooling/src/cf/project/yaml.ts

Lines changed: 1 addition & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import yaml from 'js-yaml';
44
import type { Editor } from 'mem-fs-editor';
55

66
import type { ToolsLogger } from '@sap-ux/logger';
7-
import type { UI5Config } from '@sap-ux/ui5-config';
87

98
import type {
109
MtaModule,
@@ -19,9 +18,7 @@ import type {
1918
import { AppRouterType } from '../../types';
2019
import { createServices } from '../services/api';
2120
import { getProjectNameForXsSecurity, getYamlContent } from './yaml-loader';
22-
import { getBackendUrlsWithPaths, getServiceKeyDestinations } from '../app/discovery';
23-
import { getVariant } from '../../base/helper';
24-
import { getReusableLibraryPaths } from './ui5-app-info';
21+
import { getServiceKeyDestinations } from '../app/discovery';
2522

2623
const CF_MANAGED_SERVICE = 'org.cloudfoundry.managed-service';
2724
const HTML5_APPS_REPO = 'html5-apps-repo';
@@ -502,96 +499,3 @@ export async function adjustMtaYaml(
502499

503500
return yamlContent;
504501
}
505-
506-
/**
507-
* Add fiori-tools-servestatic configuration to ui5.yaml and removes previously added configuration.
508-
*
509-
* @param basePath - path to application root
510-
* @param ui5Config - UI5 configuration object
511-
* @param logger - logger instance
512-
*/
513-
export async function addServeStaticMiddleware(
514-
basePath: string,
515-
ui5Config: UI5Config,
516-
logger?: ToolsLogger
517-
): Promise<void> {
518-
try {
519-
if (ui5Config.findCustomMiddleware('fiori-tools-servestatic')) {
520-
ui5Config.removeCustomMiddleware('fiori-tools-servestatic');
521-
}
522-
523-
const paths: Array<{ path: string; src: string; fallthrough: boolean }> = [];
524-
525-
// Add reusable library paths from ui5AppInfo.json if it exists
526-
paths.push(...getReusableLibraryPaths(basePath, logger));
527-
528-
const variant = await getVariant(basePath);
529-
const builtVariantId = variant.id.replaceAll('.', '_');
530-
531-
paths.push(
532-
{
533-
path: `/changes/${builtVariantId}`,
534-
src: './webapp/changes',
535-
fallthrough: true
536-
},
537-
{
538-
path: `/${builtVariantId}/i18n`,
539-
src: './webapp/i18n',
540-
fallthrough: true
541-
}
542-
);
543-
544-
ui5Config.addCustomMiddleware([
545-
{
546-
name: 'fiori-tools-servestatic',
547-
beforeMiddleware: 'compression',
548-
configuration: {
549-
paths
550-
}
551-
}
552-
]);
553-
} catch (error) {
554-
logger?.warn(`Could not add fiori-tools-servestatic configuration: ${(error as Error).message}`);
555-
throw error;
556-
}
557-
}
558-
559-
/**
560-
* Add backend-proxy-middleware-cf configuration to ui5.yaml.
561-
*
562-
* @param basePath - path to application root
563-
* @param ui5Config - UI5 configuration object
564-
* @param serviceKeys - service keys from Cloud Foundry
565-
* @param logger - logger instance
566-
*/
567-
export function addBackendProxyMiddleware(
568-
basePath: string,
569-
ui5Config: UI5Config,
570-
serviceKeys: ServiceKeys[],
571-
logger?: ToolsLogger
572-
): void {
573-
try {
574-
if (ui5Config.findCustomMiddleware('backend-proxy-middleware-cf')) {
575-
ui5Config.removeCustomMiddleware('backend-proxy-middleware-cf');
576-
}
577-
578-
const urlsWithPaths = getBackendUrlsWithPaths(serviceKeys, basePath);
579-
580-
if (urlsWithPaths.length === 0) {
581-
logger?.info('No backend URLs with paths found. Skipping backend-proxy-middleware-cf configuration.');
582-
return;
583-
}
584-
585-
ui5Config.addCustomMiddleware([
586-
{
587-
name: 'backend-proxy-middleware-cf',
588-
afterMiddleware: 'compression',
589-
configuration: {
590-
backends: urlsWithPaths
591-
}
592-
}
593-
]);
594-
} catch (error) {
595-
logger?.warn(`Could not add backend-proxy-middleware-cf configuration: ${(error as Error).message}`);
596-
}
597-
}

packages/adp-tooling/src/writer/cf.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import {
1010
adjustMtaYaml,
1111
getAppHostIds,
1212
getOrCreateServiceInstanceKeys,
13-
addServeStaticMiddleware,
14-
addBackendProxyMiddleware,
1513
getCfUi5AppInfo,
1614
getProjectNameForXsSecurity
1715
} from '../cf';
@@ -121,24 +119,20 @@ export async function writeUi5AppInfo(basePath: string, ui5AppInfo: CfUi5AppInfo
121119
}
122120

123121
/**
124-
* Generate CF configuration for an adaptation project.
122+
* Setup CF adaptation project for local preview.
123+
* Fetches ui5AppInfo.json and builds the project.
125124
*
126125
* @param basePath - path to project root
127126
* @param yamlPath - path to the project configuration file in YAML format
128127
* @param cfConfig - CF configuration
129128
* @param logger - logger instance
130-
* @param fs - mem-fs editor instance
131-
* @returns updated mem-fs editor instance
132129
*/
133-
export async function generateCfConfig(
130+
export async function setupCfPreview(
134131
basePath: string,
135132
yamlPath: string,
136133
cfConfig: CfConfig,
137-
logger?: ToolsLogger,
138-
fs?: Editor
139-
): Promise<Editor> {
140-
fs ??= create(createStorage());
141-
134+
logger?: ToolsLogger
135+
): Promise<void> {
142136
const ui5Config = await readUi5Yaml(basePath, yamlPath);
143137

144138
const bundlerTask = ui5Config.findCustomTask<{ space?: string; serviceInstanceName?: string }>(
@@ -170,10 +164,5 @@ export async function generateCfConfig(
170164
}
171165

172166
await writeUi5AppInfo(basePath, ui5AppInfo, logger);
173-
await addServeStaticMiddleware(basePath, ui5Config, logger);
174167
await runBuild(basePath, { ADP_BUILDER_MODE: 'preview' });
175-
addBackendProxyMiddleware(basePath, ui5Config, serviceInfo.serviceKeys, logger);
176-
177-
fs.write(join(basePath, yamlPath), ui5Config.toString());
178-
return fs;
179168
}

0 commit comments

Comments
 (0)