@sap/cds-dk 7.1.1 → 7.2.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/bin/build.js +3 -3
- package/bin/compile/index.js +5 -1
- package/bin/gen.js +27 -0
- package/bin/help.js +2 -1
- package/bin/watch.js +3 -4
- package/bin/watched.js +2 -3
- package/lib/bind/bindingUtil.js +1 -0
- package/lib/build/buildTaskEngine.js +12 -17
- package/lib/build/buildTaskFactory.js +23 -27
- package/lib/build/buildTaskHandler.js +2 -5
- package/lib/build/buildTaskProviderFactory.js +9 -12
- package/lib/build/constants.js +2 -1
- package/lib/build/index.js +1 -1
- package/lib/build/provider/buildTaskHandlerInternal.js +2 -2
- package/lib/build/provider/buildTaskProviderInternal.js +15 -25
- package/lib/build/provider/hana/index.js +14 -7
- package/lib/build/provider/nodejs/index.js +51 -18
- package/lib/build/util.js +11 -19
- package/lib/compile/openapi/csdl2openapi.js +30 -21
- package/lib/compile/openapi/index.js +7 -3
- package/lib/import/common.js +1 -1
- package/lib/init/constants.js +6 -11
- package/lib/init/index.js +84 -71
- package/lib/init/template/_merging/registry-mta.js +0 -12
- package/lib/init/template/approuter/files/mta.yaml.hbs +1 -1
- package/lib/init/template/approuter/index.js +7 -6
- package/lib/init/template/cf-manifest/files/services-manifest.yml.hbs +0 -5
- package/lib/init/template/cf-manifest/index.js +2 -2
- package/lib/init/template/h2/index.js +16 -0
- package/lib/init/template/hana/index.js +2 -3
- package/lib/init/template/helm/chart/values.root.schema.json +0 -5
- package/lib/init/template/helm/chart/values.schema.json +0 -4
- package/lib/init/template/helm/index.js +1 -1
- package/lib/init/template/lint/index.js +3 -7
- package/lib/init/template/multitenancy/files/package.json.hbs +0 -3
- package/lib/init/template/multitenancy/index.js +2 -0
- package/lib/init/template/nodejs/index.js +3 -3
- package/lib/init/template/pipeline/index.js +2 -4
- package/lib/init/template/sample/index.js +5 -7
- package/lib/init/template/sqlite/index.js +8 -3
- package/lib/init/template/tiny-sample/index.js +16 -30
- package/lib/init/util/mvn.js +25 -1
- package/lib/init/util/projectReader.js +3 -7
- package/lib/init/util/template.js +7 -6
- package/lib/watch/node-watch.js +6 -1
- package/npm-shrinkwrap.json +324 -307
- package/package.json +3 -3
- package/lib/init/template/auditlog/files/mta.yaml.hbs +0 -14
- package/lib/init/template/auditlog/files/package.json.hbs +0 -27
- package/lib/init/template/auditlog/index.js +0 -74
|
@@ -52,31 +52,64 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
|
|
|
52
52
|
if (this.isStagingBuild() && this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM)) {
|
|
53
53
|
const srcSrv = this.task.src === cds.root ? path.resolve(this.task.src, cds.env.folders.srv) : this.task.src
|
|
54
54
|
await this._copyNativeContent(cds.root, srcSrv, destRoot, destSrv)
|
|
55
|
-
|
|
55
|
+
if (this.context.options.workspaces) {
|
|
56
|
+
await this._addWorkspaceDependencies(destRoot)
|
|
57
|
+
}
|
|
56
58
|
}
|
|
57
59
|
return this._result
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
// REVISIT: fix for
|
|
61
|
-
// Instead of adding relative paths we may better delete the entire entry as relative paths
|
|
62
|
-
// cannot be resolved in cloud deployments anyway.
|
|
62
|
+
// REVISIT: fix for simple mono repos using npm workspaces
|
|
63
63
|
// Support for HANA native artifacts defined in sub-modules required
|
|
64
|
-
async
|
|
65
|
-
const packageJson = path.join(dest, 'package.json')
|
|
64
|
+
async _addWorkspaceDependencies(dest) {
|
|
66
65
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
for (let dependency in dependencies) {
|
|
71
|
-
if (dependency.startsWith('@capire/')) {
|
|
72
|
-
dependencies[dependency] = path.relative(dest, path.dirname(require.resolve(dependency + '/package.json')))
|
|
73
|
-
changed = true
|
|
74
|
-
}
|
|
66
|
+
const workspaceRoot = path.resolve(cds.root, this.getBuildOption("workspaceRoot") ?? '.')
|
|
67
|
+
if (workspaceRoot !== cds.root && !fs.existsSync(workspaceRoot)) {
|
|
68
|
+
throw new BuildError(`The given workspaceRoot path ${workspaceRoot} does not exist`)
|
|
75
69
|
}
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
const packageJson = require(path.join(dest, 'package.json'))
|
|
71
|
+
const workspaces = workspaceRoot === cds.root ? packageJson.workspaces : require(path.join(workspaceRoot, 'package.json')).workspaces
|
|
72
|
+
if (workspaces?.length) {
|
|
73
|
+
this.logger._debug && this.logger.debug(`Existing npm workspace configuration '${JSON.stringify(workspaces, ' ')}'`)
|
|
74
|
+
const { tar } = require('../../../../lib').utils
|
|
75
|
+
const { dependencies } = packageJson
|
|
76
|
+
const folders = fs.readdirSync(workspaceRoot).filter(res => {
|
|
77
|
+
try {
|
|
78
|
+
return fs.lstatSync(path.join(workspaceRoot, res)).isDirectory()
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
this.logger._debug && this.logger.debug(`Existing workspace subfolders '${JSON.stringify(folders, ' ')}'`)
|
|
84
|
+
|
|
85
|
+
const workspaceNames = new Set()
|
|
86
|
+
let changed
|
|
87
|
+
for (const workspace of workspaces) {
|
|
88
|
+
//REVISIT: workspaces must only use simple glob like 'foo*', nested folders are not supported
|
|
89
|
+
const regex = new RegExp('^' + workspace.split('/').pop().replace(/\*/g, '.*') + '$')
|
|
90
|
+
for (const folder of folders) {
|
|
91
|
+
if (!workspaceNames.has(folder) && folder.match(regex)) {
|
|
92
|
+
workspaceNames.add(folder)
|
|
93
|
+
const plugin = require(require.resolve(path.join(workspaceRoot, folder, 'package.json')))
|
|
94
|
+
const tarFile = `${plugin.name.replace(/\//g,'-').replace(/@/, '')}-${plugin.version}.tgz`
|
|
95
|
+
if (dependencies[plugin.name] === '*') {
|
|
96
|
+
const tarFilePath = path.join(dest, tarFile)
|
|
97
|
+
dependencies[plugin.name] = `file:${tarFile}`
|
|
98
|
+
await tar.czfd(tarFilePath, path.join(workspaceRoot, folder))
|
|
99
|
+
this.pushFile(tarFilePath)
|
|
100
|
+
changed = true
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (changed) {
|
|
106
|
+
return this.write(JSON.stringify(packageJson, null, ' ')).to(path.join(dest, 'package.json'))
|
|
107
|
+
}
|
|
78
108
|
}
|
|
79
|
-
} catch (e) {
|
|
109
|
+
} catch (e) {
|
|
110
|
+
this.pushMessage(`Failed to add npm workspace dependencies: ${e.message}`)
|
|
111
|
+
this.logger.debug(e)
|
|
112
|
+
}
|
|
80
113
|
}
|
|
81
114
|
|
|
82
115
|
async clean() {
|
|
@@ -91,7 +124,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
|
|
|
91
124
|
|
|
92
125
|
async _copyNativeContent(srcRoot, srcSrv, destRoot, destSrv) {
|
|
93
126
|
// project/srv/** -> 'gen/srv/srv/**'
|
|
94
|
-
const filesFilter = await this.copySrvContent(srcSrv, destRoot, this.hasBuildOption('flatDeployLayout', true)? destRoot : destSrv)
|
|
127
|
+
const filesFilter = await this.copySrvContent(srcSrv, destRoot, this.hasBuildOption('flatDeployLayout', true) ? destRoot : destSrv)
|
|
95
128
|
|
|
96
129
|
// project/* -> 'gen/srv/*'
|
|
97
130
|
await this.copyProjectRootContent(srcRoot, destRoot, (entry) => !filesFilter.includes(path.basename(entry)))
|
package/lib/build/util.js
CHANGED
|
@@ -119,6 +119,14 @@ function getDefaultModelOptions() {
|
|
|
119
119
|
return _pushModelPaths(modelPaths)
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* For valid paths replace '\\' with '/' and remove trailing '/'. Otherwise return as is
|
|
124
|
+
* @param {*} dir
|
|
125
|
+
*/
|
|
126
|
+
function normalizePath(dir) {
|
|
127
|
+
return typeof dir === "string" ? dir.replace(/\\/g, '/').replace(/\/$/, '') : dir
|
|
128
|
+
}
|
|
129
|
+
|
|
122
130
|
function _pushModelPaths(...modelPaths) {
|
|
123
131
|
const model = new Set()
|
|
124
132
|
// may contain nested arrays
|
|
@@ -131,7 +139,7 @@ function _pushModelPaths(...modelPaths) {
|
|
|
131
139
|
if (roots.includes(m)) {
|
|
132
140
|
const dir = path.resolve(cds.root, m)
|
|
133
141
|
if (fs.existsSync(dir)) {
|
|
134
|
-
model.add(m
|
|
142
|
+
model.add(m)
|
|
135
143
|
} else if (fs.existsSync(dir + FILE_EXT_CDS)) { //might be cds file name, compatibility to old build configs
|
|
136
144
|
model.add(m)
|
|
137
145
|
}
|
|
@@ -140,7 +148,7 @@ function _pushModelPaths(...modelPaths) {
|
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
150
|
})
|
|
143
|
-
return [...model]
|
|
151
|
+
return [...model].map(m => normalizePath(m))
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
function flatten(modelPaths) {
|
|
@@ -154,22 +162,6 @@ function flatten(modelPaths) {
|
|
|
154
162
|
}, [])
|
|
155
163
|
}
|
|
156
164
|
|
|
157
|
-
/**
|
|
158
|
-
* Copy a file or directory. The directory can have contents.
|
|
159
|
-
* REVISIT: 'fs.promises.cp' replacement for nodejs 14
|
|
160
|
-
* @param src <String> Note that if src is a directory it will copy everything inside of this directory, not the entire directory itself.
|
|
161
|
-
* @param dest <String> Note that if src is a file, dest cannot be a directory.
|
|
162
|
-
*/
|
|
163
|
-
async function copy(src, dest) {
|
|
164
|
-
if ((await fs.promises.stat(src)).isDirectory()) {
|
|
165
|
-
const entries = await fs.promises.readdir(src)
|
|
166
|
-
return Promise.all(entries.map(async each => copy(path.join(src, each), path.join(dest, each))))
|
|
167
|
-
} else {
|
|
168
|
-
await fs.promises.mkdir(path.dirname(dest), { recursive: true })
|
|
169
|
-
return fs.promises.copyFile(src, dest)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
165
|
function getI18nDefaultFolder() {
|
|
174
166
|
// keep '_i18n' as preferred folder for compatibility reasons
|
|
175
167
|
if (cds.env.i18n.folders.includes('_i18n')) return '_i18n'
|
|
@@ -276,9 +268,9 @@ module.exports = {
|
|
|
276
268
|
resolveRequiredSapModels,
|
|
277
269
|
getDefaultModelOptions,
|
|
278
270
|
flatten,
|
|
279
|
-
copy,
|
|
280
271
|
getI18nDefaultFolder,
|
|
281
272
|
isClassicMtx,
|
|
273
|
+
normalizePath,
|
|
282
274
|
BuildMessage,
|
|
283
275
|
BuildError
|
|
284
276
|
}
|
|
@@ -52,6 +52,7 @@ module.exports.csdl2openapi = function (
|
|
|
52
52
|
{
|
|
53
53
|
url: serviceRoot,
|
|
54
54
|
servers: serversObject,
|
|
55
|
+
odataVersion: odataVersion,
|
|
55
56
|
scheme: scheme = 'https',
|
|
56
57
|
host: host = 'localhost',
|
|
57
58
|
basePath: basePath = '/service-root',
|
|
@@ -61,6 +62,7 @@ module.exports.csdl2openapi = function (
|
|
|
61
62
|
) {
|
|
62
63
|
// as preProcess below mutates the csdl, copy it before, to avoid side-effects on the caller side
|
|
63
64
|
csdl = JSON.parse(JSON.stringify(csdl))
|
|
65
|
+
csdl.$Version = odataVersion ? odataVersion : '4.01'
|
|
64
66
|
serviceRoot = serviceRoot || (scheme + '://' + host + basePath);
|
|
65
67
|
const queryOptionPrefix = csdl.$Version <= '4.0' ? '$' : '';
|
|
66
68
|
const typesToInline = {}; // filled in schema() and used in inlineTypes()
|
|
@@ -82,7 +84,7 @@ module.exports.csdl2openapi = function (
|
|
|
82
84
|
openapi: '3.0.2',
|
|
83
85
|
info: getInfo(csdl, entityContainer),
|
|
84
86
|
'x-sap-api-type': 'ODATAV4',
|
|
85
|
-
'x-odata-version':
|
|
87
|
+
'x-odata-version': csdl.$Version,
|
|
86
88
|
'x-sap-shortText': getShortText(csdl, entityContainer),
|
|
87
89
|
servers: getServers(serviceRoot, serversObject),
|
|
88
90
|
tags: entityContainer ? getTags(entityContainer) : {},
|
|
@@ -638,12 +640,12 @@ module.exports.csdl2openapi = function (
|
|
|
638
640
|
*/
|
|
639
641
|
function operationCreate(pathItem, element, name, sourceName, targetName, target, level, restrictions) {
|
|
640
642
|
const insertRestrictions = restrictions.InsertRestrictions || target && target[voc.Capabilities.InsertRestrictions] || {};
|
|
641
|
-
|
|
643
|
+
let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false); // count property will be added if CountRestrictions is false
|
|
642
644
|
if (insertRestrictions.Insertable !== false) {
|
|
643
645
|
const lname = pluralize.singular(splitName(name));
|
|
644
646
|
const type = modelElement(element.$Type);
|
|
645
647
|
pathItem.post = {
|
|
646
|
-
summary: insertRestrictions.Description || operationSummary('
|
|
648
|
+
summary: insertRestrictions.Description || operationSummary('Creates', name, sourceName, level, true, true),
|
|
647
649
|
tags: [sourceName],
|
|
648
650
|
requestBody: {
|
|
649
651
|
description: type && type[voc.Core.Description] || 'New ' + lname,
|
|
@@ -653,8 +655,8 @@ module.exports.csdl2openapi = function (
|
|
|
653
655
|
schema: ref(element.$Type, SUFFIX.create),
|
|
654
656
|
}
|
|
655
657
|
}
|
|
656
|
-
},
|
|
657
|
-
responses: response(201, 'Created ' + lname, { $Type: element.$Type }, insertRestrictions.ErrorResponses),
|
|
658
|
+
},
|
|
659
|
+
responses: response(201, 'Created ' + lname, { $Type: element.$Type }, insertRestrictions.ErrorResponses, !countRestrictions),
|
|
658
660
|
};
|
|
659
661
|
if (insertRestrictions.LongDescription) pathItem.post.description = insertRestrictions.LongDescription;
|
|
660
662
|
if (targetName && sourceName != targetName) pathItem.post.tags.push(targetName);
|
|
@@ -711,6 +713,7 @@ module.exports.csdl2openapi = function (
|
|
|
711
713
|
const readRestrictions = restrictions.ReadRestrictions || targetRestrictions || {};
|
|
712
714
|
const readByKeyRestrictions = readRestrictions.ReadByKeyRestrictions;
|
|
713
715
|
let readable = true;
|
|
716
|
+
let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false);
|
|
714
717
|
if (byKey && readByKeyRestrictions && readByKeyRestrictions.Readable !== undefined)
|
|
715
718
|
readable = readByKeyRestrictions.Readable;
|
|
716
719
|
else if (readRestrictions.Readable !== undefined)
|
|
@@ -722,11 +725,11 @@ module.exports.csdl2openapi = function (
|
|
|
722
725
|
const lname = splitName(name);
|
|
723
726
|
const collection = !byKey && element.$Collection;
|
|
724
727
|
const operation = {
|
|
725
|
-
summary: descriptions.Description || operationSummary('
|
|
728
|
+
summary: descriptions.Description || operationSummary('Retrieves', name, sourceName, level, element.$Collection, byKey),
|
|
726
729
|
tags: [sourceName],
|
|
727
730
|
parameters: [],
|
|
728
731
|
responses: response(200, 'Retrieved ' + (byKey ? pluralize.singular(lname) : lname), { $Type: element.$Type, $Collection: collection },
|
|
729
|
-
byKey ? readByKeyRestrictions?.ErrorResponses : readRestrictions?.ErrorResponses)
|
|
732
|
+
byKey ? readByKeyRestrictions?.ErrorResponses : readRestrictions?.ErrorResponses, !countRestrictions)
|
|
730
733
|
};
|
|
731
734
|
const deltaSupported = element[voc.Capabilities.ChangeTracking] && element[voc.Capabilities.ChangeTracking].Supported;
|
|
732
735
|
if (!byKey && deltaSupported) {
|
|
@@ -1165,11 +1168,11 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1165
1168
|
*/
|
|
1166
1169
|
function operationUpdate(pathItem, element, name, sourceName, target, level, restrictions, byKey) {
|
|
1167
1170
|
const updateRestrictions = restrictions.UpdateRestrictions || target && target[voc.Capabilities.UpdateRestrictions] || {};
|
|
1168
|
-
|
|
1171
|
+
let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false);
|
|
1169
1172
|
if (updateRestrictions.Updatable !== false) {
|
|
1170
1173
|
const type = modelElement(element.$Type);
|
|
1171
1174
|
const operation = {
|
|
1172
|
-
summary: updateRestrictions.Description || operationSummary('
|
|
1175
|
+
summary: updateRestrictions.Description || operationSummary('Changes', name, sourceName, level, element.$Collection, byKey),
|
|
1173
1176
|
tags: [sourceName],
|
|
1174
1177
|
requestBody: {
|
|
1175
1178
|
description: type && type[voc.Core.Description] || 'New property values',
|
|
@@ -1180,7 +1183,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1180
1183
|
}
|
|
1181
1184
|
}
|
|
1182
1185
|
},
|
|
1183
|
-
responses: response(204, "Success", undefined, updateRestrictions.ErrorResponses),
|
|
1186
|
+
responses: response(204, "Success", undefined, updateRestrictions.ErrorResponses, !countRestrictions),
|
|
1184
1187
|
};
|
|
1185
1188
|
if (updateRestrictions.LongDescription) operation.description = updateRestrictions.LongDescription;
|
|
1186
1189
|
customParameters(operation, updateRestrictions);
|
|
@@ -1202,12 +1205,12 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1202
1205
|
*/
|
|
1203
1206
|
function operationDelete(pathItem, element, name, sourceName, target, level, restrictions, byKey) {
|
|
1204
1207
|
const deleteRestrictions = restrictions.DeleteRestrictions || target && target[voc.Capabilities.DeleteRestrictions] || {};
|
|
1205
|
-
|
|
1208
|
+
let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false);
|
|
1206
1209
|
if (deleteRestrictions.Deletable !== false) {
|
|
1207
1210
|
pathItem.delete = {
|
|
1208
|
-
summary: deleteRestrictions.Description || operationSummary('
|
|
1211
|
+
summary: deleteRestrictions.Description || operationSummary('Deletes', name, sourceName, level, element.$Collection, byKey),
|
|
1209
1212
|
tags: [sourceName],
|
|
1210
|
-
responses: response(204, "Success", undefined, deleteRestrictions.ErrorResponses),
|
|
1213
|
+
responses: response(204, "Success", undefined, deleteRestrictions.ErrorResponses, !countRestrictions),
|
|
1211
1214
|
};
|
|
1212
1215
|
if (deleteRestrictions.LongDescription) pathItem.delete.description = deleteRestrictions.LongDescription;
|
|
1213
1216
|
customParameters(pathItem.delete, deleteRestrictions);
|
|
@@ -1423,7 +1426,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1423
1426
|
const name = actionName.indexOf('.') === -1 ? actionName : nameParts(actionName).name;
|
|
1424
1427
|
const pathItem = {
|
|
1425
1428
|
post: {
|
|
1426
|
-
summary: actionImport[voc.Core.Description] || overload[voc.Core.Description] || '
|
|
1429
|
+
summary: actionImport[voc.Core.Description] || overload[voc.Core.Description] || 'Invokes action ' + name,
|
|
1427
1430
|
tags: [overload[voc.Common.Label] || sourceName || 'Service Operations'],
|
|
1428
1431
|
responses: overload.$ReturnType ? response(200, "Success", overload.$ReturnType, overload[voc.Capabilities.OperationRestrictions]?.ErrorResponses)
|
|
1429
1432
|
: response(204, "Success", undefined, overload[voc.Capabilities.OperationRestrictions]?.ErrorResponses),
|
|
@@ -1479,7 +1482,6 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1479
1482
|
const name = functionName.indexOf('.') === -1 ? functionName : nameParts(functionName).name;
|
|
1480
1483
|
let parameters = overload.$Parameter || [];
|
|
1481
1484
|
if (overload.$IsBound) parameters = parameters.slice(1);
|
|
1482
|
-
|
|
1483
1485
|
const pathSegments = [];
|
|
1484
1486
|
const params = [];
|
|
1485
1487
|
|
|
@@ -1534,7 +1536,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1534
1536
|
const pathParameters = implicitAliases ? '' : '(' + pathSegments.join(',') + ')';
|
|
1535
1537
|
const pathItem = {
|
|
1536
1538
|
get: {
|
|
1537
|
-
summary: functionImport[voc.Core.Description] || overload[voc.Core.Description] || '
|
|
1539
|
+
summary: functionImport[voc.Core.Description] || overload[voc.Core.Description] || 'Invokes function ' + name,
|
|
1538
1540
|
tags: [overload[voc.Common.Label] || sourceName || 'Service Operations'],
|
|
1539
1541
|
parameters: prefixParameters.concat(params),
|
|
1540
1542
|
responses: response(200, "Success", overload.$ReturnType, overload[voc.Capabilities.OperationRestrictions]?.ErrorResponses),
|
|
@@ -1558,7 +1560,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1558
1560
|
const firstEntitySet = Object.keys(container).filter(child => isIdentifier(child) && container[child].$Collection)[0];
|
|
1559
1561
|
paths['/$batch'] = {
|
|
1560
1562
|
post: {
|
|
1561
|
-
summary: batchSupport[voc.Core.Description] || '
|
|
1563
|
+
summary: batchSupport[voc.Core.Description] || 'Sends a group of requests',
|
|
1562
1564
|
description: (batchSupport[voc.Core.LongDescription] || 'Group multiple requests into a single request payload, see '
|
|
1563
1565
|
+ '[Batch Requests](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_BatchRequests).')
|
|
1564
1566
|
+ '\n\n*Please note that "Try it out" is not supported for this request.*',
|
|
@@ -1613,11 +1615,12 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1613
1615
|
* @param {object} type Response type object
|
|
1614
1616
|
* @param {array} errors Array of operation-specific status codes with descriptions
|
|
1615
1617
|
*/
|
|
1616
|
-
function response(code, description, type, errors) {
|
|
1618
|
+
function response(code, description, type, errors, isCount = true) {
|
|
1617
1619
|
const r = {};
|
|
1618
1620
|
r[code] = {
|
|
1619
1621
|
description: description
|
|
1620
1622
|
};
|
|
1623
|
+
let CountPropertyObj = {[csdl.$Version > '4.0' ? '@count' : '@odata.count'] : ref('count')} ;
|
|
1621
1624
|
if (code != 204) {
|
|
1622
1625
|
const s = getSchema(type);
|
|
1623
1626
|
r[code].content = {
|
|
@@ -1627,7 +1630,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1627
1630
|
type: 'object',
|
|
1628
1631
|
title: 'Collection of ' + nameParts(type.$Type || 'Edm.String').name,
|
|
1629
1632
|
properties: {
|
|
1630
|
-
|
|
1633
|
+
...(isCount && CountPropertyObj),
|
|
1631
1634
|
value: s
|
|
1632
1635
|
}
|
|
1633
1636
|
}
|
|
@@ -1806,14 +1809,20 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1806
1809
|
const isKey = keyMap(type);
|
|
1807
1810
|
const required = Object.keys(isKey);
|
|
1808
1811
|
const schemaProperties = {};
|
|
1809
|
-
|
|
1812
|
+
let isCount = true;
|
|
1813
|
+
if (csdl[qualifier]?.$Annotations) {
|
|
1814
|
+
const annotations = csdl[qualifier].$Annotations[`${qualifier}.EntityContainer/${name}`];
|
|
1815
|
+
if (annotations && annotations[voc.Capabilities.CountRestrictions] && annotations[voc.Capabilities.CountRestrictions]?.Countable === false) {
|
|
1816
|
+
isCount = false;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1810
1819
|
const properties = propertiesOfStructuredType(type);
|
|
1811
1820
|
Object.keys(properties).forEach(iName => {
|
|
1812
1821
|
const property = properties[iName];
|
|
1813
1822
|
if (suffix === SUFFIX.read) schemaProperties[iName] = getSchema(property);
|
|
1814
1823
|
if ((Object.prototype.hasOwnProperty.call(property, '@Common.FieldControl')) && property['@Common.FieldControl'] === 'Mandatory') { required.push(iName) }
|
|
1815
1824
|
if (property.$Kind == 'NavigationProperty') {
|
|
1816
|
-
if (property.$Collection && suffix === "") {
|
|
1825
|
+
if (property.$Collection && suffix === "" && isCount === true) {
|
|
1817
1826
|
schemaProperties[`${iName}@${csdl.$Version === '4.0' ? 'odata.' : ''}count`] = ref('count');
|
|
1818
1827
|
}
|
|
1819
1828
|
if (property[voc.Core.Permissions] != "Read" && !property[voc.Core.Computed] && property.$ContainsTarget) {
|
|
@@ -34,11 +34,15 @@ function _getOpenApi(csdl, options) {
|
|
|
34
34
|
return openapi;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function toOpenApiOptions(csdl, csn, options={}) {
|
|
37
|
+
function toOpenApiOptions(csdl, csn, options = {}) {
|
|
38
38
|
const result = {}
|
|
39
39
|
for (const key in options) {
|
|
40
|
-
if (/^openapi:(.*)/.test(key) && RegExp.$1)
|
|
41
|
-
result[RegExp.$1] = options[key]
|
|
40
|
+
if (/^openapi:(.*)/.test(key) && RegExp.$1) {
|
|
41
|
+
result[RegExp.$1] = options[key];
|
|
42
|
+
}
|
|
43
|
+
else if (key === 'odata-version') {
|
|
44
|
+
result['odataVersion'] = options[key];
|
|
45
|
+
}
|
|
42
46
|
}
|
|
43
47
|
if (result.url) {
|
|
44
48
|
result.url = result.url.replace(/\/*\$\{service-path\}/g, servicePath(csdl, csn))
|
package/lib/import/common.js
CHANGED
|
@@ -7,7 +7,7 @@ const messages = require("./message").getMessages();
|
|
|
7
7
|
const cds = require('../cds');
|
|
8
8
|
const { fs, path, write, mkdirp, exists, read } = cds.utils;
|
|
9
9
|
const cdsCompiler = require('..').compile.to;
|
|
10
|
-
const md5 = require('md5');
|
|
10
|
+
const md5 = data => require('crypto').createHash('md5').update(data).digest('hex');
|
|
11
11
|
|
|
12
12
|
let optionsMapping = {
|
|
13
13
|
'keep-namespace': 'keepNamespace',
|
package/lib/init/constants.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const MAVEN_ARCHETYPE_VERSION = '2.1.0';
|
|
4
|
-
|
|
5
|
-
const constants = {
|
|
6
|
-
IS_WIN: (os.platform() === 'win32'),
|
|
1
|
+
module.exports = {
|
|
2
|
+
IS_WIN: require('os').platform() === 'win32',
|
|
7
3
|
|
|
8
4
|
COMMAND_INIT: 'init',
|
|
9
5
|
COMMAND_ADD: 'add',
|
|
10
6
|
|
|
11
|
-
MAVEN_ARCHETYPE_VERSION,
|
|
7
|
+
MAVEN_ARCHETYPE_VERSION: '2.2.0',
|
|
12
8
|
|
|
13
9
|
OPTIONS: Object.freeze({
|
|
14
10
|
JAVA: 'java',
|
|
@@ -29,7 +25,7 @@ const constants = {
|
|
|
29
25
|
XSUAA: 'xsuaa',
|
|
30
26
|
HANA: 'hana',
|
|
31
27
|
SQLITE: 'sqlite',
|
|
32
|
-
|
|
28
|
+
H2: 'h2',
|
|
33
29
|
ENTERPRISE_MESSAGING: 'enterprise-messaging',
|
|
34
30
|
ENTERPRISE_MESSAGING_SHARED: 'enterprise-messaging-shared',
|
|
35
31
|
FILE_BASED_MESSAGING: 'file-based-messaging',
|
|
@@ -48,6 +44,8 @@ const constants = {
|
|
|
48
44
|
PROJECT_TYPE: Object.freeze({ nodejs: 1, java: 2, unknown: 3 }),
|
|
49
45
|
|
|
50
46
|
REGEX_JAVA_PACKAGE: /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/g,
|
|
47
|
+
// copied from mbt error message
|
|
48
|
+
REGEX_JAVA_VERSION: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/,
|
|
51
49
|
REGEX_PROJECT_NAME: /[^0-9a-zA-Z_-]/g,
|
|
52
50
|
|
|
53
51
|
URLS: Object.freeze({
|
|
@@ -57,11 +55,8 @@ const constants = {
|
|
|
57
55
|
MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/development/#the-maven-archetype`,
|
|
58
56
|
MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
|
|
59
57
|
VSCODE: 'https://code.visualstudio.com/',
|
|
60
|
-
VSCODE_NBVIEWER: 'https://marketplace.visualstudio.com/items?itemName=ms-python.python',
|
|
61
58
|
VSCODE_ESLINT: 'https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint'
|
|
62
59
|
}),
|
|
63
60
|
|
|
64
61
|
PROJECT_FILES: ['package.json', 'pom.xml', '.cdsrc.json']
|
|
65
62
|
}
|
|
66
|
-
|
|
67
|
-
module.exports = constants;
|
package/lib/init/index.js
CHANGED
|
@@ -3,7 +3,12 @@ const path = require('path');
|
|
|
3
3
|
const term = require('../util/term');
|
|
4
4
|
|
|
5
5
|
const { OPTIONS, COMMAND_INIT, PROJECT_FILES, REGEX_PROJECT_NAME } = require('./constants')
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
NODEJS, JAVA,
|
|
8
|
+
HANA, APPROUTER, XSUAA, KIBANA, ENTERPRISE_MESSAGING,
|
|
9
|
+
MTA, HELM, MULTITENANCY, PIPELINE,
|
|
10
|
+
TINY_SAMPLE, SAMPLE
|
|
11
|
+
} = OPTIONS
|
|
7
12
|
|
|
8
13
|
const cds = require('../cds'), { exists, readdir } = cds.utils
|
|
9
14
|
const DEBUG = /\b(y|all|cli)\b/.test(process.env.DEBUG) ? console.debug : undefined
|
|
@@ -12,77 +17,86 @@ module.exports = class CDSGenerator {
|
|
|
12
17
|
constructor() {
|
|
13
18
|
this.cwd = process.cwd();
|
|
14
19
|
|
|
15
|
-
this.uiConfig = [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return true;
|
|
29
|
-
} catch (err) {
|
|
30
|
-
return err;
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
guiOptions: {
|
|
34
|
-
hint: 'This name is restricted to certain characters. A valid project name is compatible across platforms.',
|
|
35
|
-
mandatory: true
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
title: `Type`,
|
|
40
|
-
description: `Select your runtime.`,
|
|
41
|
-
default: NODEJS,
|
|
42
|
-
choices: [
|
|
43
|
-
{ name: 'Node.js', value: NODEJS },
|
|
44
|
-
{ name: 'Java', value: JAVA }
|
|
45
|
-
],
|
|
46
|
-
multiple: false,
|
|
47
|
-
target: 'add',
|
|
48
|
-
guiOptions: {
|
|
49
|
-
hint: `The preferred runtime can't be changed afterwards. To use a different runtime, create a new project.`,
|
|
50
|
-
mandatory: true
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
title: `Features`,
|
|
55
|
-
description: `Add features to your project.`,
|
|
56
|
-
default: '',
|
|
57
|
-
choices: [
|
|
58
|
-
{ name: 'CI/CD Pipeline Integration', value: PIPELINE },
|
|
59
|
-
{ name: 'Configuration for SAP HANA Deployment', value: HANA },
|
|
60
|
-
{ name: 'MTA based SAP Business Technology Platform Deployment', value: MTA },
|
|
61
|
-
// { name: 'Multitenancy', value: mtx },
|
|
62
|
-
{ name: `Cloud Foundry native Deployment`, value: CF_MANIFEST }
|
|
63
|
-
// { name: `CDS Linter for custom rules`, value: lint },
|
|
64
|
-
// { name: `CDS Linter for custom rules with sample`, value: lintDev }
|
|
65
|
-
],
|
|
66
|
-
multiple: true,
|
|
67
|
-
target: 'add',
|
|
68
|
-
guiOptions: {
|
|
69
|
-
hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${term.bold('cds add')}`
|
|
20
|
+
this.uiConfig = [{
|
|
21
|
+
title: 'Project Name',
|
|
22
|
+
description: 'Enter your project name.',
|
|
23
|
+
default: '',
|
|
24
|
+
target: 'projectName',
|
|
25
|
+
validate: async (value) => {
|
|
26
|
+
try {
|
|
27
|
+
cds.root = path.resolve(this.cwd, value || '.');
|
|
28
|
+
|
|
29
|
+
await this._validateProjectName(value);
|
|
30
|
+
await this._validateProjectFolder(path.join(this.cwd, value), this.cwd);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return e
|
|
70
33
|
}
|
|
34
|
+
|
|
35
|
+
return true;
|
|
71
36
|
},
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
37
|
+
guiOptions: {
|
|
38
|
+
hint: 'This name is restricted to certain characters. A valid project name is compatible across platforms.',
|
|
39
|
+
mandatory: true
|
|
40
|
+
}
|
|
41
|
+
}, {
|
|
42
|
+
title: 'Type',
|
|
43
|
+
description: `Select your runtime.`,
|
|
44
|
+
default: NODEJS,
|
|
45
|
+
choices: [
|
|
46
|
+
{ name: 'Node.js', value: NODEJS },
|
|
47
|
+
{ name: 'Java', value: JAVA }
|
|
48
|
+
],
|
|
49
|
+
multiple: false,
|
|
50
|
+
target: 'add',
|
|
51
|
+
guiOptions: {
|
|
52
|
+
hint: `The preferred runtime can't be changed afterwards. To use a different runtime, create a new project.`,
|
|
53
|
+
mandatory: true
|
|
54
|
+
}
|
|
55
|
+
}, {
|
|
56
|
+
title: 'Features',
|
|
57
|
+
description: 'Choose productive runtime capabilities for your application.',
|
|
58
|
+
default: '',
|
|
59
|
+
choices: [
|
|
60
|
+
{ name: 'SAP HANA Cloud', value: HANA },
|
|
61
|
+
{ name: 'SAP Application Router', value: APPROUTER },
|
|
62
|
+
{ name: 'User Authentication via XSUAA', value: XSUAA },
|
|
63
|
+
{ name: 'Kibana Logging Dashboard', value: KIBANA },
|
|
64
|
+
{ name: 'SAP BTP Event Mesh', value: ENTERPRISE_MESSAGING }
|
|
65
|
+
],
|
|
66
|
+
multiple: true,
|
|
67
|
+
target: 'add',
|
|
68
|
+
guiOptions: {
|
|
69
|
+
hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${term.bold('cds add')}`
|
|
70
|
+
}
|
|
71
|
+
}, {
|
|
72
|
+
title: 'Deployment',
|
|
73
|
+
description: 'Choose which way to deploy your project.',
|
|
74
|
+
default: '',
|
|
75
|
+
choices: [
|
|
76
|
+
{ name: 'Cloud Foundry: MTA Deployment', value: MTA },
|
|
77
|
+
{ name: 'Kyma: Helm Deployment', value: HELM },
|
|
78
|
+
{ name: 'Multitenancy', value: MULTITENANCY },
|
|
79
|
+
{ name: 'CI/CD Pipeline Integration', value: PIPELINE },
|
|
80
|
+
],
|
|
81
|
+
multiple: true,
|
|
82
|
+
target: 'add',
|
|
83
|
+
guiOptions: {
|
|
84
|
+
hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${term.bold('cds add')}`
|
|
85
|
+
}
|
|
86
|
+
}, {
|
|
87
|
+
title: 'Samples',
|
|
88
|
+
description: `Add sample content to your project.`,
|
|
89
|
+
default: '',
|
|
90
|
+
choices: [
|
|
91
|
+
{ name: 'Minimal Sample', value: TINY_SAMPLE },
|
|
92
|
+
{ name: 'Extended Sample with UI', value: SAMPLE }
|
|
93
|
+
],
|
|
94
|
+
multiple: true,
|
|
95
|
+
target: 'add',
|
|
96
|
+
guiOptions: {
|
|
97
|
+
hint: 'Speed up your ramp-up phase with sample files in your project.'
|
|
84
98
|
}
|
|
85
|
-
]
|
|
99
|
+
}]
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
/**
|
|
@@ -190,8 +204,7 @@ module.exports = class CDSGenerator {
|
|
|
190
204
|
);
|
|
191
205
|
const fuzzySearch = require('../../bin/util/fuzzySearch');
|
|
192
206
|
const [bestMatch] = fuzzySearch(filename, entries);
|
|
193
|
-
|
|
194
|
-
process.exit(1);
|
|
207
|
+
throw `Unknown facet '${term.bold(filename)}'. Did you mean ${term.bold(`cds add ${bestMatch}`)}?\n\nHaven't found the proper facet yet? Here are all supported facets:\n\n ${term.bold(entries.join('\n '))}\n`
|
|
195
208
|
}
|
|
196
209
|
throw err;
|
|
197
210
|
}
|
|
@@ -60,17 +60,6 @@ const connectivity = {
|
|
|
60
60
|
}]
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const auditlog = {
|
|
64
|
-
in: 'resources',
|
|
65
|
-
where: [{
|
|
66
|
-
property: 'type',
|
|
67
|
-
isEqualTo: 'org.cloudfoundry.managed-service',
|
|
68
|
-
}, {
|
|
69
|
-
property: 'parameters.service',
|
|
70
|
-
isEqualTo: 'auditlog-api',
|
|
71
|
-
}]
|
|
72
|
-
}
|
|
73
|
-
|
|
74
63
|
const redis = {
|
|
75
64
|
in: 'resources',
|
|
76
65
|
where: [{
|
|
@@ -260,7 +249,6 @@ module.exports = {
|
|
|
260
249
|
srvJava4,
|
|
261
250
|
approuter,
|
|
262
251
|
xsuaa,
|
|
263
|
-
auditlog,
|
|
264
252
|
destination,
|
|
265
253
|
connectivity,
|
|
266
254
|
enterpriseMessaging,
|