-
Notifications
You must be signed in to change notification settings - Fork 13
/
index.ts
214 lines (184 loc) · 9.3 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import * as express from 'express';
import * as fs from 'fs';
import * as fsAsync from 'fs/promises'
import * as appindex from '@sap/cds/app/index'
//import * as cds from '@sap/cds-dk';
import { parse, parseLines, stringify } from 'dot-properties';
const cds = require('@sap/cds-dk');
const cdsLaunchpadLogger = cds.log('cds-launchpad-plugin');
export interface LaunchpadConfig {
version?: string,
theme?: string,
basePath?: string,
appConfigPath?: string,
locale?: string, // TODO if it was possible to get sap-ui-language from request which retrieves the app config json, we wouldnt need this option
template?: string
}
export class cds_launchpad_plugin{
setup (): express.Router{
if (process.env.NODE_ENV === 'production') {
cdsLaunchpadLogger.debug('Sandbox launchpad not initialized as the process runs in production ')
return
}
let options: LaunchpadConfig = cds.env.launchpad;
const router = express.Router();
cds.on('serving', async (service) => {
const apiPath = options.basePath;
const mount = apiPath.replace('$','[\\$]')
cdsLaunchpadLogger._debug && cdsLaunchpadLogger.debug ('serving launchpad for ', {service: service.name, at: apiPath})
// Mount path for launchpad page
router.use(mount, async (request: express.Request, response: express.Response, next) => {
if(request.originalUrl === options.basePath){
response.send(await this.prepareTemplate(options));
} else {
next();
}
});
// Mount path for launchpad sandbox configuration
router.use('/appconfig/fioriSandboxConfig.json', async (request, response, next) => {
// debugger;
response.send(await this.prepareAppConfigJSON(options));
});
// Component preload generation
const componentPreloadCache = new Map()
const _componentPreload = async (appName: String) => {
if (componentPreloadCache.get(appName)) return componentPreloadCache.get(appName)
const [manifest, component] = await Promise.all([
fsAsync.readFile(cds.root + '/' + cds.env.folders.app + appName + '/webapp/manifest.json'),
fsAsync.readFile(cds.root + '/' + cds.env.folders.app + appName + '/webapp/Component.js')
])
const componentPreload = `//@ui5-bundle preview/Component-preload.js
jQuery.sap.registerPreloadedModules({
"version":"2.0",
"modules":{
"preview/Component.js": function(){${component.toString()}
},
"preview/manifest.json":${manifest.toString()}
}});`
componentPreloadCache.set(appName, componentPreload)
return componentPreload
}
router.get('/:app/webapp/Component-preload.js', async ({ params }, resp) => resp.send(await _componentPreload(params.app)))
});
// Modify default CAP index page (add launchpad link)
router.get('/', (req, res, next) => {
// store the references to the origin response methods
const { end } = res;
res.end = function(content: any, encoding: string): void {
// Manipulate index page to include Sandbox Launchpad link
if(typeof content !== 'string') {
content = content.toString();
}
const htmlContent = content.replace(/<h2> Web Applications: <\/h2>/, `<h2><b><a href="${options.basePath}">Sandbox Launchpad</a></b></h2><h2>Web Applications: </h2>`);
end.call(res, htmlContent, encoding);
} as any;
next();
})
return router;
}
async prepareTemplate(options: LaunchpadConfig): Promise<string>{
let url = `https://ui5.sap.com`;
let template = options.template === 'legacy' || options.template === '' || options.template === undefined ? 'legacy' : options.template;
const htmltemplate = fs.readFileSync(__dirname + `/../templates/${template}/launchpad.html`).toString();
if (options.version && options.version.startsWith('https://')) {
url = options.version
} else if(options.version !== undefined && options.version !== ''){
url = url + '/' + options.version;
}
return htmltemplate.replace(/LIB_URL/g, url)
.replace(/THEME/g, options.theme);
}
async prepareAppConfigJSON(options: LaunchpadConfig): Promise<string> {
let template = options.template === 'legacy' || options.template === '' || options.template === undefined ? 'legacy' : options.template;
// Read app config template
const config = JSON.parse(fs.readFileSync(__dirname + `/../templates/${template}/appconfig.json`).toString());
// Read externally provided config
const extConfig = options.appConfigPath ? JSON.parse(fs.readFileSync(options.appConfigPath).toString()) : {};
// merge the two
Object.assign(config, extConfig);
// Read CDS project package
const packagejson = JSON.parse(fs.readFileSync(cds.root + '/package.json').toString());
// Read manifest files for each UI project that is defined in the project package
if(Array.isArray(packagejson.sapux)){
const applications = {};
packagejson.sapux.forEach(element => {
const manifest = JSON.parse(fs.readFileSync(cds.root + '/' + cds.env.folders.app + element.replace(cds.env.folders.app, '') + '/webapp/manifest.json' ).toString());
const appId = manifest["sap.app"].id;
if (manifest["sap.flp"]?.type === 'plugin') {
const component = appId;
const name = component.split('.').pop();
config.bootstrapPlugins[name] = {
component,
url: name + "/webapp",
'sap-ushell-plugin-type': 'RendererExtensions',
enabled: true
}
return;
}
let i18nsetting = manifest["sap.app"].i18n;
let i18nPath = cds.root + '/' + cds.env.folders.app + element.replace(cds.env.folders.app, '') + '/webapp/';
if(typeof(i18nsetting) === "object") {
if(manifest._version < "1.21.0") {
cdsLaunchpadLogger.error(`manifest.json version of ${element} does not allow i18n being an object. Minumum version 1.21.0.`)
}
i18nPath += i18nsetting.bundleUrl;
} else {
i18nPath += i18nsetting;
}
if (options.locale) {
i18nPath = i18nPath.replace(/(\.properties)$/,`_${options.locale}$1`);
}
const i18n = parse(fs.readFileSync( i18nPath ).toString());
const tileconfigs = manifest["sap.app"]?.crossNavigation?.inbounds;
for (const tileconfigId in tileconfigs){
const tileconfig = tileconfigs[tileconfigId];
const tileId = `${appId}-${tileconfigId}`;
// Replace potential string templates used for tile title and description (take descriptions from default i18n file)
Object.keys(tileconfig).forEach(key => {
if(['title','subTitle','info'].includes(key)){
const strippedValue = tileconfig[key].toString().replace(`{{`, ``).replace(`}}`, ``);
if(i18n[strippedValue] !== undefined) {
tileconfig[key] = i18n[strippedValue];
}
}
});
// App URL
// Taking into account the use of cds-plugin-ui5 -> only the default route based on the component is supported for now
// If no cds-plugin-ui5 loaded -> use default CAP routes (component/webapp)
let url = `/${element.replace(cds.env.folders.app, '')}/webapp`;
if (cds.env?.plugins !== undefined && cds.env?.plugins['cds-plugin-ui5']) {
//url = `/${element.replace(cds.env.folders.app, '')}`
url = `/${appId}`; //cds-plugin-ui5 uses the appid as default route (combination namespace + component)
}
const component = `SAPUI5.Component=${appId}`;
// App tile template
config.services.LaunchPage.adapter.config.groups[0].tiles.push({
id: tileId,
properties: Object.assign({
targetURL: `#${tileconfig.semanticObject}-${tileconfig.action}`,
title: tileconfig.title,
info: tileconfig.info,
subtitle: tileconfig.subTitle,
icon: tileconfig.icon
}, tileconfig.indicatorDataSource ? {
serviceUrl: manifest["sap.app"].dataSources[tileconfig.indicatorDataSource.dataSource].uri + tileconfig.indicatorDataSource.path
} : {} ),
tileType: tileconfig.indicatorDataSource ? 'sap.ushell.ui.tile.DynamicTile' : 'sap.ushell.ui.tile.StaticTile',
serviceRefreshInterval: (tileconfig.indicatorDataSource && tileconfig.indicatorDataSource.refresh || 10) // defautl 10 sec
// multiplying by a large number basically means "never refresh" - this can stay this way as long as
// its not supported by the local adapter, see sap.ushell.adapters.local.LaunchPageAdapter, private function handleTileServiceCall,
// which does the service calls correctly and regularly, but doesnt update the tiles
* 1000
});
config.services.ClientSideTargetResolution.adapter.config.inbounds[tileId] = tileconfig;
config.services.ClientSideTargetResolution.adapter.config.inbounds[tileId].resolutionResult = {
"applicationType": "SAPUI5",
"additionalInformation": component,
"url": url
};
}
});
}
return config;
}
}