@sap/cds-dk 8.4.2 → 8.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/bin/debug.js +90 -0
- package/bin/watch.js +12 -2
- package/lib/bind/userCdsrcJsonStorageProvider.js +0 -19
- package/lib/build/buildTaskEngine.js +1 -1
- package/lib/build/provider/hana/2migration.js +34 -20
- package/lib/build/provider/hana/index.js +74 -41
- package/lib/build/provider/internalBuildTaskProvider.js +1 -1
- package/lib/build/provider/mtx-extension/index.js +4 -4
- package/lib/build/provider/mtx-sidecar/index.js +2 -2
- package/lib/build/provider/nodejs/index.js +3 -19
- package/lib/build/util.js +41 -17
- package/lib/deploy/to-hana/hdiDeployUtil.js +14 -10
- package/lib/import/message.js +2 -1
- package/lib/import/odata/metadataconverter/MetadataConverter.js +5 -0
- package/lib/import/odata/v4parser.js +32 -43
- package/lib/init/add.js +26 -0
- package/lib/init/constants.js +4 -5
- package/lib/init/index.js +17 -25
- package/lib/init/merge.js +1 -4
- package/lib/init/projectReader.js +75 -9
- package/lib/init/registries/mta.js +0 -1
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/cloud-logging/files/package.json.hbs +1 -12
- package/lib/init/template/cloud-logging/files/values.yaml.hbs +16 -0
- package/lib/init/template/cloud-logging/index.js +14 -10
- package/lib/init/template/data/as-json.js +1 -1
- package/lib/init/template/data/index.js +1 -26
- package/lib/init/template/esm/files/package.json.hbs +3 -0
- package/lib/init/template/esm/index.js +16 -0
- package/lib/init/template/handler/files/handler.xs.hbs +56 -0
- package/lib/init/template/handler/index.js +84 -9
- package/lib/init/template/helm/index.js +4 -1
- package/lib/init/template/http/index.js +102 -35
- package/lib/init/template/sample/files/nodejs/{admin-service.js → admin-service.js.hbs} +8 -1
- package/lib/init/template/sample/files/nodejs/{cat-service.js → cat-service.js.hbs} +8 -3
- package/lib/init/template/sample/index.js +4 -2
- package/lib/init/template/telemetry/files/package.json.hbs +18 -0
- package/lib/init/template/telemetry/index.js +8 -2
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/init/template/workzone-standard/index.js +1 -1
- package/lib/init/types.d.ts +0 -6
- package/lib/watch/node-watch.js +25 -9
- package/npm-shrinkwrap.json +26 -31
- package/package.json +1 -1
package/lib/build/util.js
CHANGED
|
@@ -7,6 +7,7 @@ const IS_WIN = require('os').platform() === 'win32'
|
|
|
7
7
|
const { SEVERITY_ERROR, FILE_EXT_CDS } = require('./constants');
|
|
8
8
|
const DEBUG = cds.debug('cli|build')
|
|
9
9
|
const SECRETS = /(passw)|(cert)|(ca)|(secret)|(key)/i
|
|
10
|
+
const WS_MAX_DEPTH = 2
|
|
10
11
|
|
|
11
12
|
function getProperty(src, segments) {
|
|
12
13
|
segments = Array.isArray(segments) ? segments : segments.split('.')
|
|
@@ -98,13 +99,13 @@ function _redacted(cred) {
|
|
|
98
99
|
if (!cred) return cred
|
|
99
100
|
if (Array.isArray(cred)) return cred.map(c => typeof c === 'string' ? '...' : _redacted(c))
|
|
100
101
|
if (typeof cred === 'object') {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
const newCred = Object.assign({}, cred)
|
|
103
|
+
Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
|
|
104
|
+
return newCred
|
|
104
105
|
}
|
|
105
106
|
return cred
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
/**
|
|
109
110
|
* Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
|
|
110
111
|
* E.g. the module might NOT have been installed.
|
|
@@ -173,28 +174,30 @@ async function pathExists(path) {
|
|
|
173
174
|
return fs.promises.access(path).then(() => true).catch(() => false);
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
async function getWorkspacePaths() {
|
|
178
|
+
const wsRoot = findWorkspaceRoot(cds.root)
|
|
179
|
+
if (!wsRoot) {
|
|
180
|
+
return []
|
|
181
|
+
}
|
|
182
|
+
return (await getWorkspaces(wsRoot)).filter(ws => cds.root !== path.join(wsRoot, ws)).map(ws => path.join(wsRoot, ws))
|
|
183
|
+
}
|
|
184
|
+
|
|
176
185
|
/**
|
|
177
186
|
* Returns the list of workspaces defined for the current project. A workspace dependency
|
|
178
187
|
* needs to be defined resulting in a corresponding sym-link.
|
|
179
|
-
* @returns the paths are relative to '
|
|
188
|
+
* @returns the paths are relative to 'wsRoot'.
|
|
180
189
|
*/
|
|
181
|
-
async function getWorkspaces(resolve = false, devDependencies = true
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} catch (e) {
|
|
186
|
-
if (e.code === 'MODULE_NOT_FOUND') {
|
|
187
|
-
DEBUG?.(`No package.json found in project '${wsRoot}', skipping workspace resolution.`)
|
|
188
|
-
return []
|
|
189
|
-
}
|
|
190
|
-
throw e
|
|
190
|
+
async function getWorkspaces(wsRoot, resolve = false, devDependencies = true) {
|
|
191
|
+
wsRoot ??= findWorkspaceRoot(cds.root)
|
|
192
|
+
if (!wsRoot) {
|
|
193
|
+
return []
|
|
191
194
|
}
|
|
192
|
-
|
|
193
|
-
const workspaces = []
|
|
195
|
+
const pgkJsonWsRoot = require(path.join(wsRoot, 'package.json'))
|
|
194
196
|
let pgkJson = pgkJsonWsRoot
|
|
195
197
|
if (wsRoot !== cds.root) {
|
|
196
198
|
pgkJson = require(path.join(cds.root, 'package.json'))
|
|
197
199
|
}
|
|
200
|
+
const workspaces = []
|
|
198
201
|
if (pgkJsonWsRoot.workspaces) {
|
|
199
202
|
if (resolve) {
|
|
200
203
|
_validateWsDependencies(wsRoot, pgkJson.dependencies, devDependencies ? pgkJson.devDependencies : {})
|
|
@@ -215,6 +218,25 @@ async function getWorkspaces(resolve = false, devDependencies = true, wsRoot = c
|
|
|
215
218
|
return workspaces
|
|
216
219
|
}
|
|
217
220
|
|
|
221
|
+
function findWorkspaceRoot(currentPath, depth = 0) {
|
|
222
|
+
if (depth > WS_MAX_DEPTH) {
|
|
223
|
+
DEBUG?.(`No workspaces exist, max depth of ${WS_MAX_DEPTH} reached`)
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
const packageJson = path.join(currentPath, 'package.json')
|
|
227
|
+
if (fs.existsSync(packageJson)) {
|
|
228
|
+
if (require(path.join(currentPath, 'package.json')).workspaces) {
|
|
229
|
+
return currentPath
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (fs.existsSync(path.join(currentPath, '.gitmodules'))) {
|
|
233
|
+
return // project root reached
|
|
234
|
+
}
|
|
235
|
+
if (path.dirname(currentPath) !== currentPath) {
|
|
236
|
+
return findWorkspaceRoot(path.dirname(currentPath), depth + 1)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
218
240
|
/**
|
|
219
241
|
* This JavaScript function filterFeaturePaths is used to filter and categorize file paths
|
|
220
242
|
* from a given model into two categories: base and features.
|
|
@@ -452,7 +474,9 @@ module.exports = {
|
|
|
452
474
|
getI18nDefaultFolder,
|
|
453
475
|
normalizePath,
|
|
454
476
|
pathExists,
|
|
477
|
+
getWorkspacePaths,
|
|
455
478
|
getWorkspaces,
|
|
479
|
+
findWorkspaceRoot,
|
|
456
480
|
filterFeaturePaths,
|
|
457
481
|
getFiles,
|
|
458
482
|
BuildMessage,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const util = require('util');
|
|
3
2
|
const { bold } = require('../../util/term');
|
|
4
3
|
|
|
5
4
|
const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
|
|
@@ -40,15 +39,20 @@ module.exports = new class HdiDeployUtil {
|
|
|
40
39
|
if (options.use_hdb === false) options.use_hdb = undefined
|
|
41
40
|
deployerEnv.HDI_DEPLOY_OPTIONS = JSON.stringify(options)
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
return await new Promise((resolve, reject) => {
|
|
43
|
+
deploy(dbDir, deployerEnv, (error, response) => {
|
|
44
|
+
if (error) return reject(error)
|
|
45
|
+
if (response?.exitCode) {
|
|
46
|
+
let message = `HDI deployment failed with exit code ${response.exitCode}.`
|
|
47
|
+
if (response.signal) message += `. ${response.signal}`
|
|
48
|
+
return reject(new Error(message))
|
|
49
|
+
}
|
|
50
|
+
return resolve()
|
|
51
|
+
}, {
|
|
52
|
+
stderrCB: buffer => console.error(buffer.toString()),
|
|
53
|
+
stdoutCB: buffer => console.log(buffer.toString())
|
|
54
|
+
})
|
|
55
|
+
})
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
_loadHdiDeployLib(cwd) {
|
package/lib/import/message.js
CHANGED
|
@@ -28,7 +28,7 @@ const messages = {
|
|
|
28
28
|
BOUND_ACTION_COLLISION: 'The combination of Action name and binding parameter type must be unique for Bound Action ',
|
|
29
29
|
STREAM_NO_COLLECTION: `'Edm.Stream' cannot be of Collection type.`,
|
|
30
30
|
UNSUPPORTED_PROPERTY_TYPE: 'Property cannot be of EntityType. Check the type: ',
|
|
31
|
-
|
|
31
|
+
UNRESOLVED_SCHEMA_NAMESPACE: `Could not resolve the schema `,
|
|
32
32
|
INVALID_DATATYPE: `Invalid Data Type `,
|
|
33
33
|
ELEMENT_NAME_COLLISION: 'Collision between EntityContainer Element Name and Schema Element Name is currently not supported. Please check the element name: ',
|
|
34
34
|
NO_DEFAULT_FOR_COLLECTIONS: 'WARNING: Collection type cannot have a Default value.',
|
|
@@ -38,6 +38,7 @@ const messages = {
|
|
|
38
38
|
NULLABLE_KEY: 'WARNING: Key element cannot be Nullable.',
|
|
39
39
|
COLLISION_DETECTED: `INFO: Element name collision detected. Adding suffix to affected type category.`,
|
|
40
40
|
SPECIFY_CONFIG: 'For --config option, please specify correct JSON Object.',
|
|
41
|
+
INVALID_ANNOTATION: 'Annotation element must have a Term attribute',
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
function getMessages() {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
|
|
3
|
+
const message = require("../../message").getMessages();
|
|
2
4
|
|
|
3
5
|
const vocabulariesMap = new Map([
|
|
4
6
|
['org.odata.core.v1', () => require('../vocabularies/Org.OData.Core.V1.json')],
|
|
@@ -1754,6 +1756,9 @@ class AnnotationExpression extends Expression {
|
|
|
1754
1756
|
annotate(result) { super.annotate(this.getTargetPropertyName(), this.getTarget(), result); }
|
|
1755
1757
|
interpret() {
|
|
1756
1758
|
const element = this.getElement();
|
|
1759
|
+
if(!Object.hasOwn(element, "attributes") || !Object.hasOwn(element.attributes, "Term")) {
|
|
1760
|
+
throw new Error(message.INVALID_ANNOTATION);
|
|
1761
|
+
}
|
|
1757
1762
|
let name = element.attributes.Term;
|
|
1758
1763
|
if (element.attributes.Qualifier) name += '#' + element.attributes.Qualifier;
|
|
1759
1764
|
name = '@' + name;
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
"use strict";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const messages = require("../message").getMessages();
|
|
8
|
+
const common = require("../common");
|
|
9
|
+
const versionInfo = require("../../../package.json").version;
|
|
10
|
+
const Multimap = require("./util/MultiMap");
|
|
11
11
|
const ignoreObjects = ["$Reference"]; // add all the objects that needs to be ignored for parsing.
|
|
12
12
|
const { warn, info } = require("../../util/term");
|
|
13
13
|
const MetadataConverterFactory = require("./metadataconverter/MetadataConverterFactory");
|
|
@@ -428,6 +428,11 @@ function _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTarg
|
|
|
428
428
|
function _validateAnnotationNamespace(annotationTerm, vocabNamespaceToAlias) {
|
|
429
429
|
let aliasOrNamespace = annotationTerm.substring(1, annotationTerm.lastIndexOf('.'));
|
|
430
430
|
|
|
431
|
+
// This check is for ignoring the @cds annotations given for special cases, e.g. @cds.validate = false for collided action/function
|
|
432
|
+
if (aliasOrNamespace === "cds") {
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
|
|
431
436
|
for (let alias of Object.values(known_vocabularies)) {
|
|
432
437
|
if (alias === aliasOrNamespace) {
|
|
433
438
|
return;
|
|
@@ -1358,29 +1363,6 @@ function _parseEntityContainer(csnDefs, entityContainerElements, parserContext)
|
|
|
1358
1363
|
return csnDefs;
|
|
1359
1364
|
}
|
|
1360
1365
|
|
|
1361
|
-
/**
|
|
1362
|
-
* @param {List<Object>} dataObjList, action/function object list
|
|
1363
|
-
* @returns unique parameter list
|
|
1364
|
-
*/
|
|
1365
|
-
function _collectAllParameters(dataObjList) {
|
|
1366
|
-
let uniqueParams = [], paramNames = [], params;
|
|
1367
|
-
|
|
1368
|
-
for (let dataObj of dataObjList) {
|
|
1369
|
-
params = dataObj.$Parameter;
|
|
1370
|
-
|
|
1371
|
-
if (params) {
|
|
1372
|
-
for (let i = 0; i < params.length; i++) {
|
|
1373
|
-
if (!paramNames.includes(params[i].$Name)) {
|
|
1374
|
-
paramNames.push(params[i].$Name);
|
|
1375
|
-
uniqueParams.push(params[i]);
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
return uniqueParams;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
1366
|
/**
|
|
1385
1367
|
* @param {Boolean} isAction, to identify action/function
|
|
1386
1368
|
* @param {String} fqName, fully qualified name
|
|
@@ -1389,7 +1371,7 @@ function _collectAllParameters(dataObjList) {
|
|
|
1389
1371
|
*
|
|
1390
1372
|
* Scenarios:
|
|
1391
1373
|
* 1. Unbound Actions can't have collision. Action name must be unique.
|
|
1392
|
-
* 2. Unbound Functions can have collision;
|
|
1374
|
+
* 2. Unbound Functions can have collision; we will parse only one function with the same name having no paramter and a generic String return type.
|
|
1393
1375
|
*/
|
|
1394
1376
|
function _populateUnboundActionFunction(isAction, fqName, schemaDataList, parserContext) {
|
|
1395
1377
|
let [schemaName, actFunName] = _extractSchemaNameAndElementName(fqName);
|
|
@@ -1409,8 +1391,12 @@ function _populateUnboundActionFunction(isAction, fqName, schemaDataList, parser
|
|
|
1409
1391
|
}
|
|
1410
1392
|
else {
|
|
1411
1393
|
if (dataList.length > 1) {
|
|
1412
|
-
actionFunctionObject['$Parameter'] =
|
|
1394
|
+
actionFunctionObject['$Parameter'] = [];
|
|
1413
1395
|
parserContext.unboundFunctions[fqName] = actionFunctionObject;
|
|
1396
|
+
parserContext.unboundFunctions[fqName]["@cds.validate"] = false;
|
|
1397
|
+
if(parserContext.unboundFunctions[fqName].$ReturnType){
|
|
1398
|
+
parserContext.unboundFunctions[fqName].$ReturnType.$Type = "Edm.String";
|
|
1399
|
+
}
|
|
1414
1400
|
}
|
|
1415
1401
|
else {
|
|
1416
1402
|
parserContext.unboundFunctions[fqName] = actionFunctionObject;
|
|
@@ -1439,12 +1425,11 @@ function _collectUnboundActionFunction(schemaDataList, parserContext) {
|
|
|
1439
1425
|
* @param {Object} parserContext
|
|
1440
1426
|
*
|
|
1441
1427
|
* Scenarios for bound actions, functions:
|
|
1442
|
-
* 1. Actions with same name and binding parameter type can
|
|
1443
|
-
* 2. Functions can have collision and we will
|
|
1428
|
+
* 1. Actions with same name and binding parameter type can have collision and we will parse only one action with that name having no paramter and a genric String return type.
|
|
1429
|
+
* 2. Functions can have collision and we will parse only one function with that name having no paramter and a genric String return type.
|
|
1444
1430
|
* Special scenario is when same function name is bound to different entity as well.
|
|
1445
1431
|
*/
|
|
1446
1432
|
function _collectBoundActionFunction(isAction, currNamespace, elementName, elementDataList, parserContext) {
|
|
1447
|
-
let functionObjList; // list to collect all the collision obj for function
|
|
1448
1433
|
|
|
1449
1434
|
for (let i = 0; i < elementDataList.length; i++) {
|
|
1450
1435
|
let dataObj = elementDataList[i];
|
|
@@ -1480,7 +1465,12 @@ function _collectBoundActionFunction(isAction, currNamespace, elementName, eleme
|
|
|
1480
1465
|
parserContext.entityToBoundActions[boundEntityName][elementName] = dataObj;
|
|
1481
1466
|
}
|
|
1482
1467
|
else {
|
|
1483
|
-
|
|
1468
|
+
parserContext.entityToBoundActions[boundEntityName][elementName]["@cds.validate"] = false;
|
|
1469
|
+
parserContext.entityToBoundActions[boundEntityName][elementName]["$Parameter"] = [];
|
|
1470
|
+
if (!parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType) {
|
|
1471
|
+
parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType = {};
|
|
1472
|
+
}
|
|
1473
|
+
parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType.$Type = "Edm.String";
|
|
1484
1474
|
}
|
|
1485
1475
|
}
|
|
1486
1476
|
else {
|
|
@@ -1488,18 +1478,12 @@ function _collectBoundActionFunction(isAction, currNamespace, elementName, eleme
|
|
|
1488
1478
|
if (!parserContext.entityToBoundFunctions[boundEntityName]) {
|
|
1489
1479
|
parserContext.entityToBoundFunctions[boundEntityName] = {};
|
|
1490
1480
|
}
|
|
1491
|
-
if (!functionObjList || !parserContext.entityToBoundFunctions[boundEntityName][elementName]) {
|
|
1492
|
-
functionObjList = [];
|
|
1493
|
-
}
|
|
1494
1481
|
parserContext.entityToBoundFunctions[boundEntityName][elementName] = dataObj;
|
|
1495
|
-
functionObjList.push(dataObj);
|
|
1496
1482
|
}
|
|
1497
1483
|
else {
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
functionObjList = [functionObject]; // reset the function object list with recently formed object
|
|
1502
|
-
parserContext.entityToBoundFunctions[boundEntityName][elementName] = functionObject;
|
|
1484
|
+
parserContext.entityToBoundFunctions[boundEntityName][elementName]["@cds.validate"] = false;
|
|
1485
|
+
parserContext.entityToBoundFunctions[boundEntityName][elementName]["$Parameter"] = [];
|
|
1486
|
+
parserContext.entityToBoundFunctions[boundEntityName][elementName].$ReturnType.$Type = "Edm.String";
|
|
1503
1487
|
}
|
|
1504
1488
|
// throw error if the return type is missing
|
|
1505
1489
|
if (!parserContext.entityToBoundFunctions[boundEntityName][elementName].$ReturnType) {
|
|
@@ -1688,7 +1672,12 @@ function _generateEDMX2JSON(edmx, name_space) {
|
|
|
1688
1672
|
if (missingReferencedDocuments?.length > 0) {
|
|
1689
1673
|
console.log(missingReferencedDocuments);
|
|
1690
1674
|
}
|
|
1691
|
-
|
|
1675
|
+
|
|
1676
|
+
if (error) {
|
|
1677
|
+
reject(error);
|
|
1678
|
+
} else {
|
|
1679
|
+
resolve(json);
|
|
1680
|
+
}
|
|
1692
1681
|
});
|
|
1693
1682
|
} catch (err) {
|
|
1694
1683
|
reject(err);
|
package/lib/init/add.js
CHANGED
|
@@ -15,5 +15,31 @@ module.exports = {
|
|
|
15
15
|
register: (name, plugin) => {
|
|
16
16
|
registered.set(name, plugin)
|
|
17
17
|
},
|
|
18
|
+
filterStringAsRegex,
|
|
18
19
|
registered
|
|
19
20
|
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Turns a filter string with optional meta characters into a regular expression.
|
|
24
|
+
* If no meta characters are found, the string is treated as an include pattern.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} spec the filter string
|
|
27
|
+
* @returns {RegExp} the regular expression
|
|
28
|
+
*/
|
|
29
|
+
function filterStringAsRegex(spec) {
|
|
30
|
+
if (typeof spec === 'string') {
|
|
31
|
+
try {
|
|
32
|
+
if (spec.match(/[\^$|*]/)) {
|
|
33
|
+
spec = spec.replace(/^\*/, '.*') // * at start would be invalid, so make it a .*
|
|
34
|
+
return new RegExp(spec, 'i')
|
|
35
|
+
}
|
|
36
|
+
else { // no meta chars -> include semantics
|
|
37
|
+
spec = spec.replace(/\./g, '\\.') // escape literal dot
|
|
38
|
+
return new RegExp(spec, 'i')
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
throw err.message // user error, so cut off stack trace
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return /.*/
|
|
45
|
+
}
|
package/lib/init/constants.js
CHANGED
|
@@ -13,9 +13,13 @@ module.exports = {
|
|
|
13
13
|
JAVA_MVN: 'java:mvn',
|
|
14
14
|
JAVA_PACKAGE: 'java:package',
|
|
15
15
|
NODEJS: 'nodejs',
|
|
16
|
+
ESM: 'esm',
|
|
16
17
|
SAMPLES: 'samples', // deprecated
|
|
17
18
|
TINY_SAMPLE: 'tiny-sample',
|
|
18
19
|
SAMPLE: 'sample',
|
|
20
|
+
TYPER: 'typer',
|
|
21
|
+
TYPESCRIPT: 'typescript',
|
|
22
|
+
HANDLER: 'handler', // influenced by esm, typer and typescript
|
|
19
23
|
MTA: 'mta',
|
|
20
24
|
CF_MANIFEST: 'cf-manifest',
|
|
21
25
|
HELM: 'helm',
|
|
@@ -57,8 +61,6 @@ module.exports = {
|
|
|
57
61
|
LINT_GLOBAL: 'lint:global',
|
|
58
62
|
LINT_DEV: 'lint:dev',
|
|
59
63
|
PIPELINE: 'pipeline',
|
|
60
|
-
TYPER: 'typer',
|
|
61
|
-
TYPESCRIPT: 'typescript'
|
|
62
64
|
}),
|
|
63
65
|
|
|
64
66
|
REGEX_JAVA_PACKAGE: /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/g,
|
|
@@ -71,9 +73,6 @@ module.exports = {
|
|
|
71
73
|
CAPIRE: 'https://cap.cloud.sap',
|
|
72
74
|
MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/developing-applications/building#the-maven-archetype`,
|
|
73
75
|
MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
|
|
74
|
-
CAP_JS_COMMUNITY_ODATA_V2: 'https://github.com/cap-js-community/odata-v2-adapter',
|
|
75
|
-
CAP_JS_COMMUNITY_EVENT_QUEUE: 'https://github.com/cap-js-community/event-queue',
|
|
76
|
-
CAP_JS_COMMUNITY_WEBSOCKET: 'https://github.com/cap-js-community/websocket',
|
|
77
76
|
CF_CLI: 'https://github.com/cloudfoundry/cli'
|
|
78
77
|
}),
|
|
79
78
|
|
package/lib/init/index.js
CHANGED
|
@@ -12,17 +12,13 @@ const DEBUG = /\b(y|all|cli)\b/.test(process.env.DEBUG) ? console.debug : undefi
|
|
|
12
12
|
|
|
13
13
|
module.exports = class CDSGenerator {
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const fromDk = readdirSync(join(__dirname, 'template'))
|
|
19
|
-
const fromPlugins = Object.keys(Object.fromEntries(require('./add').registered))
|
|
20
|
-
return [...fromDk, ...fromPlugins]
|
|
21
|
-
.filter(plugin => !excluded?.includes(plugin))
|
|
15
|
+
constructor() {
|
|
16
|
+
this.cwd = process.cwd()
|
|
17
|
+
this.uiConfig = require('./bas') // Used by CAP Generator
|
|
22
18
|
}
|
|
23
19
|
|
|
24
20
|
static help({ exclude = [] } = {}) {
|
|
25
|
-
if (!cds.add) return //
|
|
21
|
+
if (!cds.add) return // shell completion: cds.add is undefined and not needed
|
|
26
22
|
|
|
27
23
|
const plugins = this.readPlugins(exclude)
|
|
28
24
|
const nameFixedLength = Math.max(...plugins.map(plugin => plugin.name.length))
|
|
@@ -35,10 +31,14 @@ module.exports = class CDSGenerator {
|
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
/**
|
|
34
|
+
* @param excluded {string[]}
|
|
38
35
|
* @returns {{name: string, module: import('module')}[]}
|
|
39
36
|
*/
|
|
40
37
|
static readPlugins(excluded = []) {
|
|
41
|
-
const
|
|
38
|
+
const fromDk = readdirSync(join(__dirname, 'template'))
|
|
39
|
+
const fromPlugins = Object.keys(Object.fromEntries(require('./add').registered))
|
|
40
|
+
const all = [...fromDk, ...fromPlugins]
|
|
41
|
+
.filter(plugin => !excluded?.includes(plugin))
|
|
42
42
|
.map(plugin => ({
|
|
43
43
|
name: plugin,
|
|
44
44
|
module: cds.add.registered.get(plugin) ?? require('./template/' + plugin)
|
|
@@ -51,12 +51,7 @@ module.exports = class CDSGenerator {
|
|
|
51
51
|
if (b === -1) return -1
|
|
52
52
|
return a - b
|
|
53
53
|
}
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
constructor() {
|
|
58
|
-
this.cwd = process.cwd()
|
|
59
|
-
this.uiConfig = require('./bas') // Used by CAP Generator
|
|
54
|
+
return all.sort(byPriority)
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
/**
|
|
@@ -216,15 +211,14 @@ module.exports = class CDSGenerator {
|
|
|
216
211
|
}
|
|
217
212
|
|
|
218
213
|
const cmds = Object.values(OPTIONS)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (b === -1) return -1
|
|
214
|
+
const priorities = new Map(cmds.map((cmd, i) => [cmd, i]))
|
|
215
|
+
this.templateList = Array.from(templates.keys())
|
|
216
|
+
.sort((lhs, rhs) => {
|
|
217
|
+
const a = priorities.get(lhs) ?? Infinity
|
|
218
|
+
const b = priorities.get(rhs) ?? Infinity
|
|
225
219
|
return a - b
|
|
226
220
|
})
|
|
227
|
-
.map(
|
|
221
|
+
.map(key => templates.get(key))
|
|
228
222
|
|
|
229
223
|
DEBUG?.(`Features: ${this.templateList.map(({ name }) => name).join(', ')}`);
|
|
230
224
|
}
|
|
@@ -240,11 +234,9 @@ module.exports = class CDSGenerator {
|
|
|
240
234
|
|
|
241
235
|
const { options } = cds.cli
|
|
242
236
|
if (typeof options.add === 'string') {
|
|
243
|
-
tokens = options.add.split(/[,\s+]/g)
|
|
244
|
-
|
|
237
|
+
tokens = options.add.split(/[,\s+]/g)
|
|
245
238
|
} else if (Array.isArray(options.add)) {
|
|
246
239
|
tokens = options.add
|
|
247
|
-
|
|
248
240
|
} else if (options.add instanceof Set) {
|
|
249
241
|
tokens = [...options.add];
|
|
250
242
|
}
|
package/lib/init/merge.js
CHANGED
|
@@ -6,11 +6,8 @@ const { readYAML, writeYAML, readJSON } = require('../util/fs')
|
|
|
6
6
|
const { detectIndent } = require('./indent')
|
|
7
7
|
const { colors } = require('../util/term')
|
|
8
8
|
|
|
9
|
-
function _isObject(item) {
|
|
10
|
-
return item && typeof item === 'object' && !Array.isArray(item)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
9
|
function _mergeObject(target, source) {
|
|
10
|
+
const _isObject = item => item && typeof item === 'object' && !Array.isArray(item)
|
|
14
11
|
const unique = array => [...new Set(array.map(JSON.stringify))].map(JSON.parse)
|
|
15
12
|
if (_isObject(target) && _isObject(source)) {
|
|
16
13
|
for (const key in source) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
const { join, resolve, basename } = require('path')
|
|
3
|
-
const { fs, exists, isdir, path } = cds.utils
|
|
3
|
+
const { fs, exists, isfile, isdir, path } = cds.utils
|
|
4
4
|
const { parseXml } = require('./xml')
|
|
5
5
|
const { REGEX_JAVA_VERSION, JAVA_LTS_VERSIONS } = require('./constants')
|
|
6
6
|
const TRACE = cds.debug('trace')
|
|
@@ -12,13 +12,15 @@ module.exports = new class ProjectReader {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
#setPackageInfo() {
|
|
15
|
+
this.isJava = (exists('pom.xml') || cds.cli.options?.add?.has('java'))
|
|
16
|
+
&& !cds.env.for('cds').profiles.includes('node') // opt-out for hybrid projects like sflight
|
|
17
|
+
|
|
15
18
|
// 1. Use project name and static default values
|
|
16
|
-
this.appVersion =
|
|
19
|
+
this.appVersion = this.isJava ? '1.0.0-SNAPSHOT' : '1.0.0'
|
|
17
20
|
this.appName = this.appId = basename(cds.root)
|
|
18
21
|
this.appDescription = 'A simple CAP project.'
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
if (isJava) {
|
|
23
|
+
if (this.isJava) {
|
|
22
24
|
let artifactId, description, version, properties
|
|
23
25
|
try {
|
|
24
26
|
({ artifactId, description, version, properties } = parseXml(resolve(cds.root, 'pom.xml')) ?? {})
|
|
@@ -74,6 +76,10 @@ module.exports = new class ProjectReader {
|
|
|
74
76
|
* @property {boolean} isUI5 Indicates if the project is a UI5 project.
|
|
75
77
|
* @property {boolean} isJava Indicates if the project is using Java.
|
|
76
78
|
* @property {boolean} isNodejs Indicates if the project is using Node.js.
|
|
79
|
+
* @property {boolean} isESM Indicates if the project is an ESM project (type 'module')
|
|
80
|
+
* @property {boolean} hasEsmStyle Indicates if code supports ESM-style imports and exports. This is true for ESM and Typescript projects.
|
|
81
|
+
* @property {boolean} isTypescript Indicates if the project is a Typescript project.
|
|
82
|
+
* @property {boolean} hasTyper Indicates if code supports CDS typer.
|
|
77
83
|
* @property {boolean} hasHelmUnifiedRuntime Indicates if the project uses Unified Runtime Helm deployment.
|
|
78
84
|
* @property {boolean} hasHelm Indicates if the project uses CAP Helm deployment.
|
|
79
85
|
* @property {boolean} hasMta Indicates if the project uses MTA deployment.
|
|
@@ -95,7 +101,7 @@ module.exports = new class ProjectReader {
|
|
|
95
101
|
* @property {boolean} hasMTXRouteJava Indicates if the approuter should have a multitenant subscription route (java).
|
|
96
102
|
* @property {boolean} hasMalwareScanner Indicates if the project uses the SAP Malware Scanning service.
|
|
97
103
|
* @property {boolean} hasDynatrace Indicates if the project uses Dynatrace.
|
|
98
|
-
*
|
|
104
|
+
* @property {boolean} hasCloudLogging Indicates if the project uses Cloud Logging.
|
|
99
105
|
*/
|
|
100
106
|
|
|
101
107
|
/**
|
|
@@ -110,18 +116,27 @@ module.exports = new class ProjectReader {
|
|
|
110
116
|
const { appVersion, appName, appId, appDescription, jdkVersion } = this
|
|
111
117
|
TRACE?.({ env })
|
|
112
118
|
|
|
119
|
+
const pkgJson = () => exists('package.json') ? JSON.parse(fs.readFileSync(join(cds.root, 'package.json'))) : {}
|
|
120
|
+
const _isESM = () => pkgJson().type === 'module'
|
|
121
|
+
const _isTypescript = () => !!pkgJson().dependencies?.typescript || !!pkgJson().devDependencies?.typescript
|
|
122
|
+
const _hasTyper = () => !!pkgJson().dependencies?.['@cap-js/cds-typer'] || !!pkgJson().devDependencies?.['@cap-js/cds-typer']
|
|
123
|
+
|
|
113
124
|
const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env)
|
|
114
|
-
const _ui5 = () =>
|
|
125
|
+
const _ui5 = () => pkgJson().sapux
|
|
115
126
|
const _uiEmbedded = () => exists(join(env.folders.app, 'xs-app.json'))
|
|
116
127
|
const _uiModule = () => !_uiEmbedded() && isdir(env.folders.app) && fs.readdirSync(resolve(cds.root, env.folders.app)).length > 0
|
|
117
|
-
const _isJava = () =>
|
|
128
|
+
const _isJava = () => this.isJava
|
|
118
129
|
const isApp = name => name !== 'appconfig' && !name.startsWith('_') && name !== 'router' && name !== 'portal'
|
|
119
130
|
const reserved = {
|
|
120
131
|
has: x => _inProd(x),
|
|
121
132
|
profile: () => cds.cli.options?.for,
|
|
122
|
-
language: () =>
|
|
133
|
+
language: () => _isJava() ? 'java' : 'nodejs',
|
|
123
134
|
isJava: () => _isJava(),
|
|
124
135
|
isNodejs: () => !_isJava(),
|
|
136
|
+
isTypescript: () => _isTypescript(),
|
|
137
|
+
isESM: () => _isESM,
|
|
138
|
+
hasEsmStyle: () => _isTypescript() || _isESM(),
|
|
139
|
+
hasTyper: () => _hasTyper(),
|
|
125
140
|
srvPath: () => join(env.build.target, env.folders.srv.replace(/\/$/, '')).replace(/\\/g, '/'),
|
|
126
141
|
archiveName: () => {
|
|
127
142
|
const pomXmlPath = resolve(cds.root, env.folders.srv, 'pom.xml')
|
|
@@ -135,7 +150,7 @@ module.exports = new class ProjectReader {
|
|
|
135
150
|
const path = join(env.build.target, name).replace(/\\/g, '/')
|
|
136
151
|
return { folder, name, path }
|
|
137
152
|
},
|
|
138
|
-
configFile: () =>
|
|
153
|
+
configFile: () => _isJava() ? '.cdsrc.json' : 'package.json',
|
|
139
154
|
apps: () => {
|
|
140
155
|
const apps = fs.readdirSync(path.resolve(cds.root, cds.env.folders.app)).filter(e =>
|
|
141
156
|
isdir(path.join(cds.root, cds.env.folders.app, e)) && isApp(e)
|
|
@@ -191,3 +206,54 @@ module.exports = new class ProjectReader {
|
|
|
191
206
|
return project
|
|
192
207
|
}
|
|
193
208
|
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Finds and reads SpringBoot configuration in `cds.root/srv` dir.
|
|
212
|
+
* This is done in a best-effort way. In no way can we replciate all of SpringBoot's
|
|
213
|
+
* config variants.
|
|
214
|
+
* @param {string[]?} roots array of directories to search for application.yaml, like `srv`
|
|
215
|
+
* @returns {Promise<object[]>} array of objects with `cds` property
|
|
216
|
+
*/
|
|
217
|
+
module.exports._readSpringBootConfig = async function (roots=[cds.env.folders.srv]) {
|
|
218
|
+
const configNodes = []
|
|
219
|
+
const rootDirs = roots.map(d => join(cds.root, d))
|
|
220
|
+
for (let r of rootDirs) {
|
|
221
|
+
const file = isfile (join (r,'./src/main/resources/application.yaml'))
|
|
222
|
+
if (file) {
|
|
223
|
+
const yaml = cds.load.yaml(file)
|
|
224
|
+
for (let yamlDoc of Array.isArray(yaml) ? yaml : [yaml]) {
|
|
225
|
+
let cds = yamlDoc?.cds;
|
|
226
|
+
if (!cds) continue
|
|
227
|
+
cds = _normalizeSpringBootCfg(cds)
|
|
228
|
+
configNodes.push({cds})
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return configNodes
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Normalize SpringBoot's dots in keys to express nested objects
|
|
237
|
+
*
|
|
238
|
+
* @example: `cds.foo.bar = 1` --> `cds: { foo: { bar: 1 } }`
|
|
239
|
+
* @param {object} obj
|
|
240
|
+
* @returns {object}
|
|
241
|
+
*/
|
|
242
|
+
function _normalizeSpringBootCfg(obj) {
|
|
243
|
+
if (typeof obj !== 'object') return obj
|
|
244
|
+
Object.keys(obj).forEach(k => {
|
|
245
|
+
const prop = k.split('.')
|
|
246
|
+
const last = prop.pop()
|
|
247
|
+
// and define the object if not already defined
|
|
248
|
+
const res = prop.reduce((o, key) => {
|
|
249
|
+
// define the object if not defined and return
|
|
250
|
+
return o[key] = o[key] ?? {}
|
|
251
|
+
}, obj)
|
|
252
|
+
res[last] = obj[k]
|
|
253
|
+
// recursively normalize
|
|
254
|
+
_normalizeSpringBootCfg(obj[k])
|
|
255
|
+
// delete the original property from object if it was rewritten
|
|
256
|
+
if (prop.length) delete obj[k]
|
|
257
|
+
})
|
|
258
|
+
return obj
|
|
259
|
+
}
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"dependencies": {
|
|
3
|
-
"@cap-js/telemetry": ">=0",
|
|
4
|
-
"@grpc/grpc-js": "^1",
|
|
5
|
-
"@opentelemetry/exporter-trace-otlp-grpc": "^0",
|
|
6
|
-
"@opentelemetry/exporter-metrics-otlp-grpc": "^0",
|
|
7
|
-
"@opentelemetry/host-metrics": "^0"
|
|
8
|
-
},
|
|
9
2
|
"cds": {
|
|
10
3
|
"requires": {
|
|
11
|
-
"
|
|
12
|
-
"telemetry": {
|
|
13
|
-
"kind": "to-cloud-logging"
|
|
14
|
-
}
|
|
15
|
-
}
|
|
4
|
+
"cloud-logging": true
|
|
16
5
|
}
|
|
17
6
|
}
|
|
18
7
|
}
|