@sap/cds-dk 7.3.1 → 7.4.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/add.js +2 -0
- package/bin/build.js +24 -14
- package/bin/cds.js +4 -4
- package/bin/compile/index.js +11 -0
- package/bin/init.js +1 -1
- package/bin/migrate.js +2 -2
- package/bin/repl.js +0 -2
- package/bin/subscribe.js +10 -5
- package/bin/unsubscribe.js +10 -5
- package/bin/upgrade.js +10 -5
- package/bin/watch.js +5 -0
- package/bin/watched.js +1 -1
- package/lib/build/buildTaskFactory.js +1 -1
- package/lib/build/constants.js +1 -0
- package/lib/build/provider/edmxBuildPlugin.js +7 -3
- package/lib/build/provider/featureToggleBuildPlugin.js +2 -3
- package/lib/build/provider/hana/2migration.js +4 -4
- package/lib/build/provider/hana/index.js +20 -10
- package/lib/build/provider/java/index.js +2 -2
- package/lib/build/provider/nodejs/index.js +77 -56
- package/lib/client/auth_manager.js +1 -1
- package/lib/client/base_command.js +8 -18
- package/lib/client/download_legacy.js +0 -3
- package/lib/client/params.js +5 -4
- package/lib/client/pull.js +9 -1
- package/lib/client/settings_manager.js +22 -21
- package/lib/compile/asyncapi/components/schemas/annotations.js +20 -0
- package/lib/compile/openapi/csdl2openapi.js +3 -3
- package/lib/deploy/to-hana/hana.js +53 -35
- package/lib/index.js +6 -4
- package/lib/init/constants.js +3 -4
- package/lib/init/index.js +9 -4
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/enterprise-messaging/files/event-mesh.json.hbs +1 -1
- package/lib/init/template/enterprise-messaging/index.js +1 -1
- package/lib/init/template/enterprise-messaging-shared/files/event-mesh.json.hbs +1 -1
- package/lib/init/template/enterprise-messaging-shared/index.js +19 -5
- package/lib/init/template/hana/files/db/package.json +1 -1
- package/lib/init/template/helm/index.js +1 -1
- package/lib/init/template/local-messaging/files/package.json.hbs +15 -0
- package/lib/init/template/local-messaging/index.js +25 -0
- package/lib/init/template/multitenancy/index.js +3 -5
- package/lib/init/template/nodejs/files/.gitignore.hbs +3 -0
- package/lib/init/template/notifications/files/package.json.hbs +1 -10
- package/lib/init/template/notifications/index.js +7 -16
- package/lib/init/template/postgres/index.js +4 -3
- package/lib/init/template/templateBase.js +0 -13
- package/lib/init/template/typer/files/package.json +1 -1
- package/lib/init/util/projectReader.js +1 -0
- package/npm-shrinkwrap.json +377 -330
- package/package.json +1 -1
- package/lib/init/template/notifications/files/notificationtypes.json.hbs +0 -16
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const cds = require('../../../cds')
|
|
4
|
+
const cmd = require('../../../util/command');
|
|
4
5
|
const EdmxBuildPlugin = require('../edmxBuildPlugin')
|
|
5
|
-
const { BuildError } = require('../../util')
|
|
6
|
+
const { BuildError, pathExists } = require('../../util')
|
|
6
7
|
const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX, CONTENT_PACKAGELOCK_JSON,
|
|
7
8
|
CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
|
|
8
9
|
const { WARNING } = EdmxBuildPlugin
|
|
@@ -44,9 +45,10 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
|
|
|
44
45
|
await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
|
|
45
46
|
|
|
46
47
|
if (!this.hasBuildOption(CONTENT_EDMX, false)) {
|
|
47
|
-
//REVISIT: inferred model expected by cds.compile.to.edmx()
|
|
48
48
|
const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) }
|
|
49
|
-
|
|
49
|
+
// inferred flavor is required by edmx compiler backend
|
|
50
|
+
// using cds.compile instead of cds.compiler.compileSources ensures that cds.env options are correctly read
|
|
51
|
+
const baseModel = dictionary.base.meta.flavor !== 'inferred' ? await cds.compile(sources.base, super.options(), 'inferred') : dictionary.base
|
|
50
52
|
await this.compileToEdmx(baseModel, path.join(this.destSrv, 'odata', cds.env.odata.version), compileOptions)
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -60,59 +62,6 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
|
|
|
60
62
|
return this._result
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
// REVISIT: fix for simple mono repos using npm workspaces
|
|
64
|
-
// Support for HANA native artifacts defined in sub-modules required
|
|
65
|
-
async _addWorkspaceDependencies(dest) {
|
|
66
|
-
try {
|
|
67
|
-
const workspaceRoot = path.resolve(cds.root, this.getBuildOption("workspaceRoot") ?? '.')
|
|
68
|
-
if (workspaceRoot !== cds.root && !fs.existsSync(workspaceRoot)) {
|
|
69
|
-
throw new BuildError(`The given workspaceRoot path ${workspaceRoot} does not exist`)
|
|
70
|
-
}
|
|
71
|
-
const packageJson = require(path.join(dest, 'package.json'))
|
|
72
|
-
const workspaces = workspaceRoot === cds.root ? packageJson.workspaces : require(path.join(workspaceRoot, 'package.json')).workspaces
|
|
73
|
-
if (workspaces?.length) {
|
|
74
|
-
this.logger._debug && this.logger.debug(`Existing npm workspace configuration '${JSON.stringify(workspaces, ' ')}'`)
|
|
75
|
-
const { tar } = require('../../../../lib').utils
|
|
76
|
-
const { dependencies } = packageJson
|
|
77
|
-
const folders = fs.readdirSync(workspaceRoot).filter(res => {
|
|
78
|
-
try {
|
|
79
|
-
return fs.lstatSync(path.join(workspaceRoot, res)).isDirectory()
|
|
80
|
-
} catch (e) {
|
|
81
|
-
return false
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
this.logger._debug && this.logger.debug(`Existing workspace subfolders '${JSON.stringify(folders, ' ')}'`)
|
|
85
|
-
|
|
86
|
-
const workspaceNames = new Set()
|
|
87
|
-
let changed
|
|
88
|
-
for (const workspace of workspaces) {
|
|
89
|
-
//REVISIT: workspaces must only use simple glob like 'foo*', nested folders are not supported
|
|
90
|
-
const regex = new RegExp('^' + workspace.split('/').pop().replace(/\*/g, '.*') + '$')
|
|
91
|
-
for (const folder of folders) {
|
|
92
|
-
if (!workspaceNames.has(folder) && folder.match(regex)) {
|
|
93
|
-
workspaceNames.add(folder)
|
|
94
|
-
const plugin = require(require.resolve(path.join(workspaceRoot, folder, 'package.json')))
|
|
95
|
-
const tarFile = `${plugin.name.replace(/\//g, '-').replace(/@/, '')}-${plugin.version}.tgz`
|
|
96
|
-
if (dependencies[plugin.name] === '*') {
|
|
97
|
-
const tarFilePath = path.join(dest, tarFile)
|
|
98
|
-
dependencies[plugin.name] = `file:${tarFile}`
|
|
99
|
-
await tar.czfd(tarFilePath, path.join(workspaceRoot, folder))
|
|
100
|
-
this.pushFile(tarFilePath)
|
|
101
|
-
changed = true
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (changed) {
|
|
107
|
-
return this.write(JSON.stringify(packageJson, null, ' ')).to(path.join(dest, 'package.json'))
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
} catch (e) {
|
|
111
|
-
this.pushMessage(`Failed to add npm workspace dependencies: ${e.message}`)
|
|
112
|
-
this.logger.debug(e)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
65
|
async clean() {
|
|
117
66
|
// staging build content is deleted by BuildTaskEngine
|
|
118
67
|
if (cds.env.build.target === '.') {
|
|
@@ -208,6 +157,78 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
|
|
|
208
157
|
return srvAllowList.test(entry) && (!filter || filter.call(this, entry))
|
|
209
158
|
})
|
|
210
159
|
}
|
|
160
|
+
|
|
161
|
+
async _addWorkspaceDependencies(dest) {
|
|
162
|
+
let packageDescriptors
|
|
163
|
+
try {
|
|
164
|
+
packageDescriptors = await this._execNpmPack(dest)
|
|
165
|
+
} catch (e) {
|
|
166
|
+
this.logger.debug(e)
|
|
167
|
+
throw new BuildError(`Failed to package npm workspace dependencies\n${e.message}`)
|
|
168
|
+
}
|
|
169
|
+
if (packageDescriptors?.length) {
|
|
170
|
+
const packageJson = require(path.join(dest, 'package.json'))
|
|
171
|
+
const dependencies = packageJson.dependencies
|
|
172
|
+
const devDependencies = packageJson.devDependencies
|
|
173
|
+
let changed
|
|
174
|
+
const promises = []
|
|
175
|
+
for (const packageDescriptor of packageDescriptors) {
|
|
176
|
+
const {name, filename: fileName} = packageDescriptor
|
|
177
|
+
const filePath = path.join(dest, fileName)
|
|
178
|
+
if (dependencies?.[name] === '*' || devDependencies?.[name] === '*') {
|
|
179
|
+
if (dependencies?.[name] === '*') {
|
|
180
|
+
dependencies[name] = `file:${fileName}`
|
|
181
|
+
} else {
|
|
182
|
+
devDependencies[name] = `file:${fileName}`
|
|
183
|
+
}
|
|
184
|
+
this.pushFile(filePath)
|
|
185
|
+
changed |= true
|
|
186
|
+
} else {
|
|
187
|
+
if (await pathExists(filePath)) {
|
|
188
|
+
promises.push(fs.promises.rm(filePath))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (changed) {
|
|
193
|
+
promises.push(this.write(JSON.stringify(packageJson, 0, 2)).to(path.join(dest, 'package.json')))
|
|
194
|
+
} else {
|
|
195
|
+
throw new BuildError('No matching workspaces found!')
|
|
196
|
+
}
|
|
197
|
+
await Promise.all(promises)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async _execNpmPack(dest) {
|
|
202
|
+
function findWorkspaceRoot(dir) {
|
|
203
|
+
if(fs.existsSync(path.join(dir, 'package.json'))) {
|
|
204
|
+
if(require(path.join(dir, 'package.json')).workspaces) {
|
|
205
|
+
return dir
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if(fs.existsSync(path.join(dir, '.gitmodules')) || fs.existsSync(path.join(dir, 'node_modules'))) {
|
|
209
|
+
return // project root reached
|
|
210
|
+
}
|
|
211
|
+
if (path.dirname(dir) !== dir) {
|
|
212
|
+
return findWorkspaceRoot(path.dirname(dir))
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const wsRoot = findWorkspaceRoot(cds.root)
|
|
216
|
+
if (!wsRoot) {
|
|
217
|
+
throw new BuildError('No workspace configuration found!')
|
|
218
|
+
}
|
|
219
|
+
const args = ['pack', '-ws', '--json', `--pack-destination=${dest}`]
|
|
220
|
+
if (this.task.options?.workspaces) {
|
|
221
|
+
let {workspaces} = this.task.options
|
|
222
|
+
workspaces = Array.isArray(workspaces) ? workspaces : [workspaces]
|
|
223
|
+
workspaces.forEach(w => args.push(`-w=${w}`))
|
|
224
|
+
}
|
|
225
|
+
this.logger._debug && this.logger.debug(`execute command: npm ${args}`)
|
|
226
|
+
|
|
227
|
+
const result = await cmd.spawnCommand( 'npm', args, { cwd: wsRoot || cds.root }, true, !!this.logger._debug)
|
|
228
|
+
|
|
229
|
+
this.logger._debug && this.logger.debug(result)
|
|
230
|
+
return JSON.parse(result)
|
|
231
|
+
}
|
|
211
232
|
}
|
|
212
233
|
|
|
213
234
|
module.exports = NodejsBuildPlugin
|
|
@@ -66,7 +66,7 @@ async function retrieveTokenOrPasscodeUrl(params) {
|
|
|
66
66
|
let response;
|
|
67
67
|
for (const { method, url, data } of reqParams(params)) {
|
|
68
68
|
try {
|
|
69
|
-
DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${
|
|
69
|
+
DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${params.obfuscateQueryParams(url)}`);
|
|
70
70
|
response = await axios[method](url, data);
|
|
71
71
|
} catch (error) {
|
|
72
72
|
if (error.status === 404) { // may represent 405 in case of wrong method
|
|
@@ -38,27 +38,17 @@ module.exports = class BaseCommand {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
static concatUrls(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return trimBeginSlash(trimEndSlash(str));
|
|
50
|
-
}
|
|
51
|
-
const concatenated = new URL(base);
|
|
52
|
-
concatenated.pathname = trimEndSlash(concatenated.pathname) + '/' +
|
|
53
|
-
paths.map((p, i) => i < paths.length - 1 ? trimSlashes(p) : trimBeginSlash(p)).join('/');
|
|
54
|
-
return concatenated.toString();
|
|
41
|
+
static concatUrls(...parts) {
|
|
42
|
+
return parts
|
|
43
|
+
.map((p, i) => i === 0
|
|
44
|
+
? p.replace(/\/$/ /*end slash*/, '')
|
|
45
|
+
: i === parts.length - 1
|
|
46
|
+
? p.replace(/^\// /*begin slash*/, '')
|
|
47
|
+
: p.replace(/^\/|\/$/g /*both*/, ''))
|
|
48
|
+
.join('/');
|
|
55
49
|
}
|
|
56
50
|
|
|
57
51
|
static getMtxApiUrl(url, action) {
|
|
58
52
|
return BaseCommand.concatUrls(url, MTX_URL_PATH_PREFIX, action);
|
|
59
53
|
}
|
|
60
|
-
|
|
61
|
-
static getMtxApiUrlSync(url, action) {
|
|
62
|
-
return BaseCommand.concatUrls(url, MTX_URL_PATH_PREFIX, action);
|
|
63
|
-
}
|
|
64
54
|
}
|
package/lib/client/params.js
CHANGED
|
@@ -170,7 +170,7 @@ class BasicParams {
|
|
|
170
170
|
this.clearOtherTokenStorage = new Param('clearOtherTokenStorage', false, { internal: true });
|
|
171
171
|
|
|
172
172
|
// - persisted
|
|
173
|
-
this.appUrl = new Param('appUrl', '', { persist: Persistence.setting });
|
|
173
|
+
this.appUrl = new Param('appUrl', '', { persist: Persistence.setting }); // any trailing slash (/) is removed
|
|
174
174
|
this.url = this.appUrl.newAlias('url');
|
|
175
175
|
this.from = this.appUrl.newAlias('from');
|
|
176
176
|
this.to = this.appUrl.newAlias('to');
|
|
@@ -276,6 +276,7 @@ class ParamCollection {
|
|
|
276
276
|
|
|
277
277
|
/**
|
|
278
278
|
* Get the name of a main param (as opposed to an alias).
|
|
279
|
+
* Throws an error if the param is not part of this collection.
|
|
279
280
|
* @param name the param in question
|
|
280
281
|
*/
|
|
281
282
|
static mainName(name) {
|
|
@@ -287,7 +288,7 @@ class ParamCollection {
|
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
/**
|
|
290
|
-
*
|
|
291
|
+
* Return whether the given param is included in this collection.
|
|
291
292
|
* @param name the param in question
|
|
292
293
|
*/
|
|
293
294
|
has(name) {
|
|
@@ -295,7 +296,7 @@ class ParamCollection {
|
|
|
295
296
|
}
|
|
296
297
|
|
|
297
298
|
/**
|
|
298
|
-
* Return the value of the given param.
|
|
299
|
+
* Return the value of the given param or `undefined` if not part of this collection.
|
|
299
300
|
* @param name the param in question
|
|
300
301
|
*/
|
|
301
302
|
get(name) {
|
|
@@ -304,7 +305,7 @@ class ParamCollection {
|
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
/**
|
|
307
|
-
* Return the `Param` object for the given param.
|
|
308
|
+
* Return the `Param` object for the given param or `undefined` if not part of this collection.
|
|
308
309
|
* @param name the param in question
|
|
309
310
|
*/
|
|
310
311
|
getParam(name) {
|
package/lib/client/pull.js
CHANGED
|
@@ -49,6 +49,14 @@ module.exports = class Pull extends BaseCommand {
|
|
|
49
49
|
.catch(error => this.handleHttpError(error, params, { url: pullUrl }));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
static getPackageJsonIndentation(packageContents) {
|
|
53
|
+
return /^ +/m.exec(packageContents)?.[0].length ?? 2;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static formatPackageJson(newObj, oldContents) {
|
|
57
|
+
return JSON.stringify(newObj, undefined, this.getPackageJsonIndentation(oldContents));
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
static amendPackageJson(projectFolder, env) {
|
|
53
61
|
if ('extends' in env) {
|
|
54
62
|
return;
|
|
@@ -61,7 +69,7 @@ module.exports = class Pull extends BaseCommand {
|
|
|
61
69
|
} catch (error) {
|
|
62
70
|
throw getMessage('package.json missing or unreadable', { error });
|
|
63
71
|
}
|
|
64
|
-
fs.writeFileSync(packageJson,
|
|
72
|
+
fs.writeFileSync(packageJson, this.formatPackageJson({
|
|
65
73
|
...packageContents,
|
|
66
74
|
cds: {
|
|
67
75
|
...(packageContents.cds ?? {}),
|
|
@@ -96,19 +96,17 @@ async function getTokenBaseUrl(params, renewUrl) {
|
|
|
96
96
|
if (!params.has('appUrl')) {
|
|
97
97
|
throw 'Failed to determine token URL: app URL not given';
|
|
98
98
|
}
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
url.pathname = cleanPathname(pathname);
|
|
104
|
-
return url;
|
|
99
|
+
const appUrlWith = pathname => {
|
|
100
|
+
const url = new URL(params.get('appUrl'));
|
|
101
|
+
url.pathname = url.pathname.replace(/\/*$/, pathname);
|
|
102
|
+
return url.toString();
|
|
105
103
|
};
|
|
106
104
|
|
|
107
105
|
let existingPath;
|
|
108
106
|
const oauthPaths = [OAUTH_PATH_STREAMLINED_MTX];
|
|
109
107
|
for (const path of oauthPaths) {
|
|
110
108
|
try {
|
|
111
|
-
await axios.head(
|
|
109
|
+
await axios.head(appUrlWith(path));
|
|
112
110
|
existingPath = path;
|
|
113
111
|
break;
|
|
114
112
|
} catch (error) {
|
|
@@ -119,7 +117,7 @@ async function getTokenBaseUrl(params, renewUrl) {
|
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
119
|
existingPath = existingPath ?? OAUTH_PATH_OLD_MTX; // REVISIT can this just be included in the array?
|
|
122
|
-
return
|
|
120
|
+
return appUrlWith(existingPath);
|
|
123
121
|
}
|
|
124
122
|
|
|
125
123
|
function getKeyringAccountName(params) {
|
|
@@ -194,7 +192,6 @@ class SettingsManager {
|
|
|
194
192
|
|
|
195
193
|
const loadedSettings = await this._loadFromFile(projectFolder);
|
|
196
194
|
DEBUG?.(`Loaded project settings for ${params.get('projectFolder')}: ${loadedSettings.format()}`);
|
|
197
|
-
const loadedAppUrl = loadedSettings.get('appUrl');
|
|
198
195
|
|
|
199
196
|
if (params.has('passcode') && loadedSettings.has('username')) {
|
|
200
197
|
console.log(`Unsetting username${(loadedSettings.has('isEmptyPassword') ? ' and empty-password hint' : '')}`);
|
|
@@ -207,14 +204,14 @@ class SettingsManager {
|
|
|
207
204
|
if (params.has('username')) {
|
|
208
205
|
params.set('skipToken', true);
|
|
209
206
|
}
|
|
210
|
-
await this.updateUrls(params, logout,
|
|
207
|
+
await this.updateUrls(params, logout, loadedSettings);
|
|
211
208
|
await this.setKeytar(params, logout);
|
|
212
209
|
await this.addAuth(params, logout);
|
|
213
210
|
DEBUG?.(`Effective project settings: ${params.format()}`);
|
|
214
211
|
}
|
|
215
212
|
|
|
216
|
-
static async updateUrls(params, logout,
|
|
217
|
-
async function
|
|
213
|
+
static async updateUrls(params, logout, loadedSettings) {
|
|
214
|
+
async function updateAppUrl() {
|
|
218
215
|
if (!logout && !params.has('appUrl')) {
|
|
219
216
|
// Get appUrl from CF
|
|
220
217
|
DEBUG?.('App URL not given');
|
|
@@ -240,19 +237,23 @@ class SettingsManager {
|
|
|
240
237
|
DEBUG?.(`Added schema to app URL: ${appUrl}`);
|
|
241
238
|
}
|
|
242
239
|
params.set('appUrl', appUrl);
|
|
240
|
+
if (params.get('appUrl') !== loadedSettings.get('appUrl')) {
|
|
241
|
+
DEBUG?.(`Updated app URL from '${loadedSettings.get('appUrl')}' (loaded) to '${appUrl}'`);
|
|
242
|
+
}
|
|
243
243
|
}
|
|
244
|
-
async function
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
params.
|
|
244
|
+
async function updateTokenUrl() {
|
|
245
|
+
const tokenUrl = params.get('tokenUrl');
|
|
246
|
+
const renewUrl = !logout && !params.get('skipToken')
|
|
247
|
+
&& (!tokenUrl || tokenUrl.includes('//-/cds') || params.get('appUrl') !== loadedSettings.get('appUrl'));
|
|
248
|
+
if (renewUrl) {
|
|
249
|
+
const tokenUrl = await getTokenBaseUrl(params, renewUrl);
|
|
250
|
+
params.set('tokenUrl', tokenUrl);
|
|
251
|
+
DEBUG?.(`Updated token URL from '${loadedSettings.get('tokenUrl')}' (loaded) to '${tokenUrl}'`);
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
|
|
251
|
-
await
|
|
252
|
-
|
|
253
|
-
if (!logout && !params.get('skipToken')) {
|
|
254
|
-
await renewTokenUrl();
|
|
255
|
-
}
|
|
255
|
+
await updateAppUrl();
|
|
256
|
+
await updateTokenUrl();
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
static async setKeytar(params, logout = false) {
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
const odmAnnotationsMapping = Object.freeze({
|
|
3
|
+
'@ODM.entityName': 'x-sap-odm-entity-name',
|
|
4
|
+
'@ODM.oid': 'x-sap-odm-oid'
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
function addOdmExtensions(definition, object) {
|
|
8
|
+
for (const [annotation, asyncApiExtension] of Object.entries(odmAnnotationsMapping)) {
|
|
9
|
+
if (definition[annotation]) {
|
|
10
|
+
object[asyncApiExtension] = definition[annotation];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
1
15
|
module.exports = function addAnnotations(definition, object) {
|
|
2
16
|
if (typeof (definition['@title']) === 'string') {
|
|
3
17
|
object.title = definition['@title'];
|
|
@@ -9,5 +23,11 @@ module.exports = function addAnnotations(definition, object) {
|
|
|
9
23
|
object.description = definition.doc.replace(/\n/g, ' ');
|
|
10
24
|
}
|
|
11
25
|
|
|
26
|
+
if (definition['@ODM.root']) {
|
|
27
|
+
object['x-sap-root-entity'] = definition['@ODM.root']
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
addOdmExtensions(definition, object);
|
|
31
|
+
|
|
12
32
|
return object;
|
|
13
33
|
}
|
|
@@ -479,7 +479,7 @@ module.exports.csdl2openapi = function (
|
|
|
479
479
|
if (description) tag.description = description;
|
|
480
480
|
tags.push(tag);
|
|
481
481
|
})
|
|
482
|
-
return tags.sort(function(pre, next){return pre.name.localeCompare(next.name)});
|
|
482
|
+
return tags.sort(function (pre, next) { return pre.name.localeCompare(next.name) });
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
/**
|
|
@@ -655,7 +655,7 @@ module.exports.csdl2openapi = function (
|
|
|
655
655
|
schema: ref(element.$Type, SUFFIX.create),
|
|
656
656
|
}
|
|
657
657
|
}
|
|
658
|
-
},
|
|
658
|
+
},
|
|
659
659
|
responses: response(201, 'Created ' + lname, { $Type: element.$Type }, insertRestrictions.ErrorResponses, !countRestrictions),
|
|
660
660
|
};
|
|
661
661
|
if (insertRestrictions.LongDescription) pathItem.post.description = insertRestrictions.LongDescription;
|
|
@@ -1620,7 +1620,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
|
|
|
1620
1620
|
r[code] = {
|
|
1621
1621
|
description: description
|
|
1622
1622
|
};
|
|
1623
|
-
let CountPropertyObj = {[csdl.$Version > '4.0' ? '@count' : '@odata.count']
|
|
1623
|
+
let CountPropertyObj = { [csdl.$Version > '4.0' ? '@count' : '@odata.count']: ref('count') };
|
|
1624
1624
|
if (code != 204) {
|
|
1625
1625
|
const s = getSchema(type);
|
|
1626
1626
|
r[code].content = {
|
|
@@ -45,13 +45,16 @@ class HanaDeployer {
|
|
|
45
45
|
// arg object
|
|
46
46
|
// async deploy(model, serviceName, tunnelAddress, buildTaskOptions, vcapFile, undeployWhitelist, hdiOptions, bindCallback) { // NOSONAR
|
|
47
47
|
async deploy(options = {}) { // NOSONAR
|
|
48
|
-
let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions } = options;
|
|
48
|
+
let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions, dry } = options;
|
|
49
49
|
|
|
50
50
|
if (model) {
|
|
51
51
|
throw 'Specifying a model for hana deployment is not supported.';
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
if (dry) {
|
|
55
|
+
console.log('-- running in dry mode ---');
|
|
56
|
+
}
|
|
57
|
+
|
|
55
58
|
if (vcapFile) {
|
|
56
59
|
console.log(`Using VCAP_SERVICES from file ${vcapFile} (beta feature).`);
|
|
57
60
|
}
|
|
@@ -66,51 +69,66 @@ class HanaDeployer {
|
|
|
66
69
|
globalVcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
if (dry) {
|
|
73
|
+
for (const buildResult of buildResults) {
|
|
74
|
+
const model = await cds.load(buildResult.task.src);
|
|
75
|
+
const data = cds.compile.to.hdbtable(model);
|
|
76
|
+
for (const [src, { file }] of data) {
|
|
77
|
+
console.log(`-- ${file}`);
|
|
78
|
+
console.log(src);
|
|
79
|
+
console.log();
|
|
80
|
+
}
|
|
77
81
|
}
|
|
82
|
+
} else {
|
|
83
|
+
for (const buildResult of buildResults) {
|
|
84
|
+
let vcapEnv = globalVcapEnv;
|
|
85
|
+
let serviceKeyName;
|
|
86
|
+
const currentModelFolder = buildResult.task.dest;
|
|
87
|
+
|
|
88
|
+
if (undeployWhitelist) {
|
|
89
|
+
console.log('Writing undeploy.json');
|
|
90
|
+
await fs.write(path.join(currentModelFolder, 'undeploy.json'), JSON.stringify(undeployWhitelist, null, 2));
|
|
91
|
+
}
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
|
|
94
|
+
if (!serviceName && hasVCAPEnv) {
|
|
95
|
+
await fs.mkdir(currentModelFolder, { recursive: true });
|
|
96
|
+
} else {
|
|
97
|
+
const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
|
|
98
|
+
await this._getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress);
|
|
99
|
+
serviceKeyName = cfServiceInstanceKeyName;
|
|
100
|
+
serviceName = serviceName || cfServiceInstanceName;
|
|
87
101
|
|
|
88
|
-
|
|
102
|
+
vcapEnv = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
|
|
89
103
|
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
if (options.storeCredentials) {
|
|
105
|
+
await this._addInstanceToDefaultEnvJson([currentModelFolder, projectPath], cfServiceInstanceName, serviceKey);
|
|
106
|
+
}
|
|
92
107
|
}
|
|
93
|
-
}
|
|
94
108
|
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
console.log(`${bold('Starting deployment to SAP HANA ...')}`);
|
|
110
|
+
|
|
111
|
+
hdiOptions = hdiOptions || {}
|
|
112
|
+
await hdiDeployUtil.deploy(currentModelFolder, vcapEnv, hdiOptions);
|
|
97
113
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
if (!hasVCAPEnv) {
|
|
115
|
+
const bindOptions = {
|
|
116
|
+
...options,
|
|
117
|
+
to: `${serviceName}:${serviceKeyName}`,
|
|
118
|
+
for: options.for,
|
|
119
|
+
kind: buildResult.task.for
|
|
120
|
+
}
|
|
121
|
+
await bind(bindOptions);
|
|
104
122
|
}
|
|
105
|
-
await bind(bindOptions);
|
|
106
123
|
}
|
|
107
|
-
}
|
|
108
124
|
|
|
109
|
-
|
|
110
|
-
|
|
125
|
+
if (options.storeCredentials) {
|
|
126
|
+
await gitUtil.ensureFileIsGitignored('default-env.json', projectPath);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(`If not already done, use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
|
|
111
130
|
}
|
|
112
131
|
|
|
113
|
-
console.log(`If not already done, use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
|
|
114
132
|
console.log(`Done.`);
|
|
115
133
|
|
|
116
134
|
return { buildResults };
|
package/lib/index.js
CHANGED
|
@@ -19,13 +19,15 @@ if (cds.compile.to.constructor.name === 'Object') { // not a class as in cds 7
|
|
|
19
19
|
cds.compile.to.xsuaa = require('./compile/to-xsuaa')
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
cds
|
|
24
|
-
|
|
22
|
+
// add overlay4 method to cds.schema to retrieve schema with cds-dk features
|
|
23
|
+
// schema is cds version specific; add cds-dk parts not present in cds, e.g. build
|
|
24
|
+
// failsafe for legacy cds without schema.constructor
|
|
25
|
+
cds.extend(cds.schema?.constructor || {}).with(class {
|
|
26
|
+
get overlay4() { return super.overlay4 = require('./env/schema') }
|
|
25
27
|
})
|
|
26
28
|
|
|
27
29
|
// set build API, potentially removing the old API facade from sap/cds 7
|
|
28
|
-
delete cds.__proto__?.build
|
|
30
|
+
// delete cds.__proto__?.build //> not required, as its overriden below anyways
|
|
29
31
|
cds.extend (cds.constructor) .with (class {
|
|
30
32
|
get build() { return super.build = require('./build') }
|
|
31
33
|
})
|
package/lib/init/constants.js
CHANGED
|
@@ -4,7 +4,7 @@ module.exports = {
|
|
|
4
4
|
COMMAND_INIT: 'init',
|
|
5
5
|
COMMAND_ADD: 'add',
|
|
6
6
|
|
|
7
|
-
MAVEN_ARCHETYPE_VERSION: '2.
|
|
7
|
+
MAVEN_ARCHETYPE_VERSION: '2.4.0',
|
|
8
8
|
|
|
9
9
|
OPTIONS: Object.freeze({
|
|
10
10
|
JAVA: 'java',
|
|
@@ -27,9 +27,10 @@ module.exports = {
|
|
|
27
27
|
POSTGRES: 'postgres',
|
|
28
28
|
SQLITE: 'sqlite',
|
|
29
29
|
H2: 'h2',
|
|
30
|
+
LOCAL_MESSAGING: 'local-messaging',
|
|
31
|
+
FILE_BASED_MESSAGING: 'file-based-messaging',
|
|
30
32
|
ENTERPRISE_MESSAGING: 'enterprise-messaging',
|
|
31
33
|
ENTERPRISE_MESSAGING_SHARED: 'enterprise-messaging-shared',
|
|
32
|
-
FILE_BASED_MESSAGING: 'file-based-messaging',
|
|
33
34
|
REDIS_MESSAGING: 'redis-messaging',
|
|
34
35
|
APPROUTER: 'approuter',
|
|
35
36
|
DESTINATION: 'destination',
|
|
@@ -44,8 +45,6 @@ module.exports = {
|
|
|
44
45
|
NOTIFICATIONS: 'notifications'
|
|
45
46
|
}),
|
|
46
47
|
|
|
47
|
-
PROJECT_TYPE: Object.freeze({ nodejs: 1, java: 2, unknown: 3 }),
|
|
48
|
-
|
|
49
48
|
REGEX_JAVA_PACKAGE: /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/g,
|
|
50
49
|
// copied from mbt error message
|
|
51
50
|
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-]+)*)?$/,
|
package/lib/init/index.js
CHANGED
|
@@ -6,7 +6,7 @@ const { OPTIONS, COMMAND_INIT, PROJECT_FILES, REGEX_PROJECT_NAME } = require('./
|
|
|
6
6
|
const {
|
|
7
7
|
NODEJS, JAVA,
|
|
8
8
|
HANA, APPROUTER, XSUAA, KIBANA, ENTERPRISE_MESSAGING,
|
|
9
|
-
MTA,
|
|
9
|
+
MTA, MULTITENANCY, PIPELINE,
|
|
10
10
|
TINY_SAMPLE, SAMPLE
|
|
11
11
|
} = OPTIONS
|
|
12
12
|
|
|
@@ -23,7 +23,7 @@ module.exports = class CDSGenerator {
|
|
|
23
23
|
default: '',
|
|
24
24
|
target: 'projectName',
|
|
25
25
|
validate: async (value) => {
|
|
26
|
-
try {
|
|
26
|
+
try {
|
|
27
27
|
cds.root = path.resolve(this.cwd, value || '.');
|
|
28
28
|
|
|
29
29
|
await this._validateProjectName(value);
|
|
@@ -74,7 +74,7 @@ module.exports = class CDSGenerator {
|
|
|
74
74
|
default: '',
|
|
75
75
|
choices: [
|
|
76
76
|
{ name: 'Cloud Foundry: MTA Deployment', value: MTA },
|
|
77
|
-
{ name: 'Kyma: Helm Deployment', value: HELM },
|
|
77
|
+
// { name: 'Kyma: Helm Deployment', value: HELM },
|
|
78
78
|
{ name: 'Multitenancy', value: MULTITENANCY },
|
|
79
79
|
{ name: 'CI/CD Pipeline Integration', value: PIPELINE },
|
|
80
80
|
],
|
|
@@ -182,7 +182,11 @@ module.exports = class CDSGenerator {
|
|
|
182
182
|
|
|
183
183
|
if (cds.cli.command === COMMAND_INIT) {
|
|
184
184
|
const relativeProjectPath = path.relative(this.cwd, cds.root)
|
|
185
|
-
|
|
185
|
+
let message = 'Successfully created project.'
|
|
186
|
+
if (relativeProjectPath) {
|
|
187
|
+
message = message + ` Continue with '${term.bold('cd ' + relativeProjectPath)}'.`
|
|
188
|
+
}
|
|
189
|
+
console.log('\n' + message)
|
|
186
190
|
} else {
|
|
187
191
|
console.log(`\nSuccessfully added features to your project.`)
|
|
188
192
|
}
|
|
@@ -219,6 +223,7 @@ module.exports = class CDSGenerator {
|
|
|
219
223
|
// Compat
|
|
220
224
|
if (facet === 'sample-tiny') facet = 'tiny-sample'
|
|
221
225
|
if (facet === 'kibana-logging') facet = 'kibana'
|
|
226
|
+
if (facet === 'postgresql') facet = 'postgres'
|
|
222
227
|
|
|
223
228
|
if (!templates.has(facet)) {
|
|
224
229
|
const template = await this._createTemplate(facet)
|