@sap/cds-dk 7.6.0 → 7.7.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/cds.js +1 -1
- package/bin/import.js +2 -2
- package/bin/version.js +21 -20
- package/bin/watch.js +6 -0
- package/lib/build/buildTaskEngine.js +6 -4
- package/lib/build/buildTaskProviderFactory.js +5 -0
- package/lib/build/provider/edmxBuildPlugin.js +1 -1
- package/lib/build/provider/internalBuildTaskProvider.js +12 -10
- package/lib/build/provider/mtx/index.js +2 -2
- package/lib/build/provider/mtx/resourcesTarBuilder.js +1 -1
- package/lib/build/provider/mtx-extension/extensionCompilation.js +50 -0
- package/lib/build/provider/mtx-extension/index.js +50 -48
- package/lib/build/provider/mtx-sidecar/index.js +3 -3
- package/lib/build/util.js +9 -4
- package/lib/deploy/to-hana/hana.js +46 -76
- package/lib/deploy/to-hana/hdiDeployUtil.js +15 -18
- package/lib/deploy/to-hana/mtaUtil.js +1 -1
- package/lib/env/schema.js +1 -0
- package/lib/env/schemas/cds-rc.js +54 -1
- package/lib/import/asyncapi/parserContext.js +6 -0
- package/lib/import/common.js +18 -23
- package/lib/import/odata/metadataconverter/MetadataConverter.js +12 -8
- package/lib/import/odata/v4parser.js +67 -12
- package/lib/init/add.js +2 -0
- package/lib/init/constants.js +2 -4
- package/lib/init/merge.js +31 -13
- package/lib/init/projectReader.js +9 -1
- package/lib/init/registries/mta.js +19 -1
- package/lib/init/template/approuter/files/mta.yaml.hbs +1 -1
- package/lib/init/template/approuter/index.js +5 -5
- package/lib/init/template/connectivity/index.js +3 -4
- package/lib/init/template/html5-repo/files/app-package.json.hbs +16 -0
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +83 -0
- package/lib/init/template/html5-repo/files/package.json.hbs +17 -0
- package/lib/init/template/html5-repo/files/ui5.yaml.hbs +33 -0
- package/lib/init/template/html5-repo/files/xs-app.json.hbs +19 -0
- package/lib/init/template/html5-repo/index.js +56 -12
- package/lib/init/template/mta/files/mta.yaml.hbs +1 -0
- package/lib/init/template/mta/index.js +5 -0
- package/lib/init/template/nodejs/files/.vscode/extensions.json +1 -2
- package/lib/init/template/nodejs/files/README.md +1 -1
- package/lib/init/template/sample/files/app/index.html +8 -6
- package/lib/init/template/sample/index.js +12 -2
- package/lib/lint/Linter.js +2 -2
- package/lib/lint/checks.js +0 -51
- package/lib/mtx/auth_manager.js +46 -55
- package/lib/mtx/settings_manager.js +14 -19
- package/lib/mtx/util/dependencies.js +1 -1
- package/lib/mtx/util/logging.js +10 -8
- package/lib/util/axios.js +5 -0
- package/lib/util/cf.js +1 -0
- package/npm-shrinkwrap.json +96 -42
- package/package.json +1 -10
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
</script>
|
|
16
16
|
|
|
17
17
|
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
|
|
19
|
+
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
|
|
20
|
+
data-sap-ui-compatVersion="edge"
|
|
21
|
+
data-sap-ui-async="true"
|
|
22
|
+
data-sap-ui-preload="async"
|
|
23
|
+
data-sap-ui-theme="sap_horizon"
|
|
24
|
+
data-sap-ui-frameOptions="allow"
|
|
25
|
+
></script>
|
|
24
26
|
<script>
|
|
25
27
|
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
|
|
26
28
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { join } = require('path')
|
|
2
|
-
const cds = require('../../../cds'), { exists, copy } = cds.utils
|
|
2
|
+
const cds = require('../../../cds'), { exists, read, write, copy } = cds.utils
|
|
3
3
|
const { env4, readProject } = require('../../projectReader')
|
|
4
4
|
|
|
5
5
|
module.exports = class SampleTemplate extends require('../../plugin') {
|
|
@@ -13,11 +13,21 @@ module.exports = class SampleTemplate extends require('../../plugin') {
|
|
|
13
13
|
async run() {
|
|
14
14
|
const { db, srv, app } = env4('production').folders
|
|
15
15
|
const language = exists('pom.xml') ? 'java' : 'nodejs'
|
|
16
|
+
|
|
16
17
|
await Promise.all([
|
|
17
18
|
copy(join(__dirname, 'files', 'db')).to(db),
|
|
18
|
-
copy(join(__dirname, 'files', 'app')).to(app),
|
|
19
19
|
copy(join(__dirname, 'files', 'srv')).to(srv),
|
|
20
20
|
copy(join(__dirname, 'files', language)).to(srv)
|
|
21
21
|
])
|
|
22
|
+
|
|
23
|
+
await copy(join(__dirname, 'files', 'app')).to(app)
|
|
24
|
+
const { appName, appPath, appUIPaths } = readProject()
|
|
25
|
+
|
|
26
|
+
// manifest.json must be unique
|
|
27
|
+
await Promise.all(appUIPaths.map(async p => {
|
|
28
|
+
const manifest = await read(join(appPath, p, 'webapp/manifest.json'))
|
|
29
|
+
manifest['sap.app'].id = appName + '.' + p
|
|
30
|
+
await write(join(appPath, p, 'webapp/manifest.json'), manifest, { spaces: 2 })
|
|
31
|
+
}))
|
|
22
32
|
}
|
|
23
33
|
}
|
package/lib/lint/Linter.js
CHANGED
|
@@ -38,7 +38,7 @@ class Linter {
|
|
|
38
38
|
/**
|
|
39
39
|
* Initializes `cds lint` call and generates the required content
|
|
40
40
|
* object for `cds` executable
|
|
41
|
-
* @returns object
|
|
41
|
+
* @returns object containing help, options, flags and shortcuts
|
|
42
42
|
*/
|
|
43
43
|
init(help = "", options = [], flags = [], shortcuts = []) {
|
|
44
44
|
this.eslintCmd = checks.resolveEslint(this.cdsdkPath, this.globalPath);
|
|
@@ -56,7 +56,7 @@ class Linter {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Runner for 'cds lint' which is a wrapper for eslint cmd calls
|
|
59
|
-
* that
|
|
59
|
+
* that detects and adds the required cmd line arguments
|
|
60
60
|
* @param {*} args files/globs for eslint to lint
|
|
61
61
|
* @param {*} options options/flag passed by user
|
|
62
62
|
*/
|
package/lib/lint/checks.js
CHANGED
|
@@ -2,10 +2,8 @@ const fs = require("fs");
|
|
|
2
2
|
const os = require("os");
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const term = require("../util/term");
|
|
5
|
-
const commandUtil = require("../util/command");
|
|
6
5
|
|
|
7
6
|
const IS_WIN = os.platform() === "win32";
|
|
8
|
-
const { URLS } = require("../init/constants");
|
|
9
7
|
|
|
10
8
|
module.exports = {
|
|
11
9
|
/**
|
|
@@ -73,55 +71,6 @@ module.exports = {
|
|
|
73
71
|
return true;
|
|
74
72
|
},
|
|
75
73
|
|
|
76
|
-
/**
|
|
77
|
-
* Checks if VS Code is installed. If not, suggests to install it
|
|
78
|
-
* @param {*} inputPath CDS project root
|
|
79
|
-
* @returns boolean whether VS Code is installed
|
|
80
|
-
*/
|
|
81
|
-
async hasVscode(inputPath, logger = console) {
|
|
82
|
-
try {
|
|
83
|
-
await commandUtil.spawnCommand("code", ["--version"], {
|
|
84
|
-
cwd: inputPath,
|
|
85
|
-
shell: IS_WIN,
|
|
86
|
-
});
|
|
87
|
-
} catch (err) {
|
|
88
|
-
logger.log(`Consider installing VS Code: ${URLS.VSCODE}`);
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
return true;
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Checks whether VS Code extension <extension> is installed
|
|
96
|
-
* @param {*} extension VS Code extension
|
|
97
|
-
* @param {*} inputPath CDS roject root
|
|
98
|
-
* @returns boolean whether VS Code extension is installed
|
|
99
|
-
*/
|
|
100
|
-
async hasVscodeExtension(extension, inputPath, logger = console) {
|
|
101
|
-
try {
|
|
102
|
-
this.linterExtensions = await commandUtil.spawnCommand(
|
|
103
|
-
"code",
|
|
104
|
-
["--list-extensions", "--category", "linters"],
|
|
105
|
-
{
|
|
106
|
-
cwd: inputPath,
|
|
107
|
-
shell: IS_WIN,
|
|
108
|
-
},
|
|
109
|
-
true
|
|
110
|
-
);
|
|
111
|
-
} catch (err) {
|
|
112
|
-
// Do nothing
|
|
113
|
-
}
|
|
114
|
-
if (this.linterExtensions && this.linterExtensions.includes(extension)) {
|
|
115
|
-
return true;
|
|
116
|
-
} else {
|
|
117
|
-
logger.log(
|
|
118
|
-
"\n\nConsider installing the VS Code extension for ESLint:\n" +
|
|
119
|
-
term.info(`'dbaeumer.vscode-eslint': ${URLS.VSCODE_ESLINT}\n`)
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
return false;
|
|
123
|
-
},
|
|
124
|
-
|
|
125
74
|
/**
|
|
126
75
|
* Checks ESLint config file for proper setup
|
|
127
76
|
* in context of a local ESLint plugin installation
|
package/lib/mtx/auth_manager.js
CHANGED
|
@@ -3,29 +3,32 @@ const axios = require('axios');
|
|
|
3
3
|
const cds = require('../cds');
|
|
4
4
|
const { SettingsManager } = require('./settings_manager');
|
|
5
5
|
const { ParamCollection } = require('./params');
|
|
6
|
+
const { getMessage } = require('./util/logging');
|
|
6
7
|
|
|
7
8
|
require('../util/axios').pruneErrors();
|
|
8
|
-
const { getMessage } = require('./util/logging');
|
|
9
9
|
|
|
10
10
|
const DEBUG = cds.debug('cli');
|
|
11
|
+
const REASONS = { invalid_scope: /\binvalid_scope\b/ };
|
|
11
12
|
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
function assign(params, authData) {
|
|
14
|
+
if (authData?.access_token) {
|
|
15
|
+
params.set('token', authData.access_token);
|
|
16
|
+
if (authData.expires_in) {
|
|
17
|
+
params.set('tokenExpirationDate', Date.now() + authData.expires_in * 1000);
|
|
18
|
+
} else {
|
|
19
|
+
params.delete('tokenExpirationDate');
|
|
20
|
+
}
|
|
21
|
+
if (authData.refresh_token) {
|
|
22
|
+
params.set('refreshToken', authData.refresh_token);
|
|
23
|
+
} else {
|
|
24
|
+
params.delete('refreshToken');
|
|
25
|
+
}
|
|
18
26
|
}
|
|
19
|
-
if (authData
|
|
20
|
-
params.set('
|
|
21
|
-
} else {
|
|
22
|
-
params.delete('refreshToken');
|
|
27
|
+
if (authData?.passcode_url) {
|
|
28
|
+
params.set('passcodeUrl', authData.passcode_url);
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
|
-
function
|
|
26
|
-
params.set('passcodeUrl', authData.passcode_url);
|
|
27
|
-
}
|
|
28
|
-
function* reqParams(params) {
|
|
31
|
+
function reqParams(method, params) {
|
|
29
32
|
const url = params.get('tokenUrl');
|
|
30
33
|
const d = {};
|
|
31
34
|
if (params.has('subdomain')) {
|
|
@@ -44,16 +47,9 @@ function* reqParams(params) {
|
|
|
44
47
|
d.key = params.get('key');
|
|
45
48
|
}
|
|
46
49
|
const data = new URLSearchParams(d).toString();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
url
|
|
50
|
-
data
|
|
51
|
-
};
|
|
52
|
-
yield {
|
|
53
|
-
method: 'get',
|
|
54
|
-
url: `${url}?${data}`,
|
|
55
|
-
data: undefined
|
|
56
|
-
};
|
|
50
|
+
return method === 'post'
|
|
51
|
+
? { url, data }
|
|
52
|
+
: { url: `${url}?${data}`, data: undefined };
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
async function retrieveTokenOrPasscodeUrl(params) {
|
|
@@ -61,41 +57,37 @@ async function retrieveTokenOrPasscodeUrl(params) {
|
|
|
61
57
|
return;
|
|
62
58
|
}
|
|
63
59
|
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
for (const { method, url, data } of reqParams(params)) {
|
|
60
|
+
let response, error;
|
|
61
|
+
reqParams: for (const method of ['post', 'get']) {
|
|
62
|
+
do {
|
|
63
|
+
const { url, data } = reqParams(method, params);
|
|
69
64
|
try {
|
|
70
65
|
DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${params.obfuscateQueryParams(url)}`);
|
|
71
66
|
response = await axios[method](url, data);
|
|
72
|
-
} catch (
|
|
67
|
+
} catch (e) {
|
|
68
|
+
error = e;
|
|
73
69
|
if (error.status === 404) { // may represent 405 in case of wrong method
|
|
74
|
-
continue;
|
|
70
|
+
continue reqParams;
|
|
71
|
+
} else if (params.has('refreshToken')) {
|
|
72
|
+
DEBUG?.('Discarding invalid refresh token');
|
|
73
|
+
params.delete('refreshToken');
|
|
74
|
+
} else {
|
|
75
|
+
break reqParams;
|
|
75
76
|
}
|
|
76
|
-
throw getMessage('Failed to get authentication token', { error });
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (!response) {
|
|
83
|
-
throw 'Failed to get authentication token';
|
|
84
|
-
}
|
|
85
|
-
authData = response.data;
|
|
86
|
-
|
|
87
|
-
retry = !authData.access_token && params.has('refreshToken') && (params.delete('refreshToken'), true);
|
|
88
|
-
} while (retry);
|
|
89
|
-
|
|
90
|
-
if (authData.access_token) {
|
|
91
|
-
addTokenInfo(params, authData);
|
|
78
|
+
} while (!(response || error));
|
|
92
79
|
}
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
const data = response?.data ?? error?.auth;
|
|
81
|
+
assign(params, data);
|
|
82
|
+
if (!response) {
|
|
83
|
+
throw new Error(REASONS.invalid_scope.test(error.message)
|
|
84
|
+
? 'Token has invalid scope. Check if your user has the required roles'
|
|
85
|
+
: `No valid passcode or token provided. Get a passcode${
|
|
86
|
+
params.get('passcodeUrl') ? ' from ' + params.get('passcodeUrl') : ''
|
|
87
|
+
} and re-run the command with '-p <passcode>'`);
|
|
95
88
|
}
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
|
|
99
91
|
module.exports = class AuthManager {
|
|
100
92
|
|
|
101
93
|
static async login(paramValues) {
|
|
@@ -105,14 +97,13 @@ module.exports = class AuthManager {
|
|
|
105
97
|
if (params.has('username')) {
|
|
106
98
|
params.set('reqAuth', { auth: { username: params.get('username'), password: params.get('password') } });
|
|
107
99
|
} else if (!params.get('skipToken')) {
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
try {
|
|
101
|
+
await retrieveTokenOrPasscodeUrl(params);
|
|
102
|
+
} catch (error) {
|
|
110
103
|
if (params.get('saveData')) {
|
|
111
104
|
await SettingsManager.saveSettings(params);
|
|
112
105
|
}
|
|
113
|
-
throw
|
|
114
|
-
params.get('passcodeUrl') ? ' from ' + params.get('passcodeUrl') : ''
|
|
115
|
-
} and supply the code when re-running the command`;
|
|
106
|
+
throw getMessage('Failed to login', { error });
|
|
116
107
|
}
|
|
117
108
|
params.set('reqAuth', { headers: { Authorization: 'Bearer ' + params.get('token') } });
|
|
118
109
|
}
|
|
@@ -105,22 +105,17 @@ async function getTokenBaseUrl(params, renewUrl) {
|
|
|
105
105
|
return url.toString();
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
-
let
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
break;
|
|
115
|
-
} catch (error) {
|
|
116
|
-
if ([401, 404].includes(error.status)) {
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
108
|
+
let path = OAUTH_PATH_OLD_MTX;
|
|
109
|
+
try {
|
|
110
|
+
await axios.head(appUrlWith(OAUTH_PATH_STREAMLINED_MTX));
|
|
111
|
+
path = OAUTH_PATH_STREAMLINED_MTX;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (![401, 404].includes(error.status)) {
|
|
119
114
|
throw error;
|
|
120
115
|
}
|
|
116
|
+
DEBUG?.(`Falling back to legacy token URL: ${error.message}`);
|
|
121
117
|
}
|
|
122
|
-
|
|
123
|
-
return appUrlWith(existingPath);
|
|
118
|
+
return appUrlWith(path);
|
|
124
119
|
}
|
|
125
120
|
|
|
126
121
|
function getKeyringAccountName(params) {
|
|
@@ -184,7 +179,7 @@ class SettingsManager {
|
|
|
184
179
|
}
|
|
185
180
|
|
|
186
181
|
if (params.get('clearOtherTokenStorage')) {
|
|
187
|
-
// Delete
|
|
182
|
+
// Delete data from the other storage.
|
|
188
183
|
params.delete('clearOtherTokenStorage');
|
|
189
184
|
await this.deleteToken(params, { fromOtherStorage: true });
|
|
190
185
|
}
|
|
@@ -195,7 +190,7 @@ class SettingsManager {
|
|
|
195
190
|
await this.setKeytar(params);
|
|
196
191
|
await this._saveAuthToKeyring(params, params.toEntries(Persistence.auth));
|
|
197
192
|
} else {
|
|
198
|
-
console.log('Note:
|
|
193
|
+
console.log('Note: authentication data is not persisted by default. To save tokens for later commands, please run `cds login`.');
|
|
199
194
|
}
|
|
200
195
|
}
|
|
201
196
|
|
|
@@ -586,11 +581,11 @@ class SettingsManager {
|
|
|
586
581
|
return emptyData(all);
|
|
587
582
|
}
|
|
588
583
|
|
|
589
|
-
let allAuth
|
|
584
|
+
let allAuth;
|
|
590
585
|
try {
|
|
591
586
|
allAuth = JSON.parse((await fs.promises.readFile(CONFIG.paths.auth)).toString());
|
|
592
587
|
} catch (err) {
|
|
593
|
-
DEBUG?.(
|
|
588
|
+
DEBUG?.(`${capitalize(TOKEN_STORAGE_DESC.plain)} contains invalid saved auth`);
|
|
594
589
|
return emptyData(all);
|
|
595
590
|
}
|
|
596
591
|
|
|
@@ -599,14 +594,14 @@ class SettingsManager {
|
|
|
599
594
|
}
|
|
600
595
|
|
|
601
596
|
if (!allAuth[appUrl]) {
|
|
602
|
-
DEBUG?.(
|
|
597
|
+
DEBUG?.(`${capitalize(TOKEN_STORAGE_DESC.plain)} contains no authentication data for app URL ${appUrl}`);
|
|
603
598
|
return new ParamCollection();
|
|
604
599
|
}
|
|
605
600
|
|
|
606
601
|
const authForSubdomain = allAuth[appUrl][subdomain];
|
|
607
602
|
|
|
608
603
|
if (!authForSubdomain) {
|
|
609
|
-
DEBUG?.(
|
|
604
|
+
DEBUG?.(`${capitalize(TOKEN_STORAGE_DESC.plain)} contains no authentication data for subdomain ${subdomain} (app URL: ${appUrl})`);
|
|
610
605
|
return new ParamCollection();
|
|
611
606
|
}
|
|
612
607
|
|
|
@@ -24,7 +24,7 @@ function requireGlobal(packageId) {
|
|
|
24
24
|
return require(packagePath);
|
|
25
25
|
} catch (error) {
|
|
26
26
|
// Ignore error.
|
|
27
|
-
DEBUG?.(`Failed to find module '${packageId}' in ${globalModulesDir()}
|
|
27
|
+
DEBUG?.(`Failed to find module '${packageId}' in ${globalModulesDir()}:`, error);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
package/lib/mtx/util/logging.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
|
|
3
3
|
function getMessage(errorText, { error, command, help = command && getCommandHelp(command) } = {}) {
|
|
4
|
-
let message = errorText ? errorText : '';
|
|
4
|
+
let message = errorText ? errorText.trim() : '';
|
|
5
5
|
if (error) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
const includeStack = error.stack && require('../../cds').debug('cli');
|
|
7
|
+
message = message.replace(/(?<!\p{P})$/u, ':'); // Append colon if no punctuation at the end
|
|
8
|
+
message += includeStack ? '\n' : ' ';
|
|
9
|
+
const oriMsg = error.response?.data?.message // case: response body == error
|
|
10
|
+
|| error.response?.data?.error?.message // case: response body == { error }
|
|
11
|
+
|| (includeStack ? '' : error.message);
|
|
12
|
+
message += oriMsg;
|
|
13
|
+
if (includeStack) {
|
|
14
|
+
message += (oriMsg ? '\n' : '') + error.stack;
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
if (typeof help === 'string') {
|
package/lib/util/axios.js
CHANGED
package/lib/util/cf.js
CHANGED
|
@@ -192,6 +192,7 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
192
192
|
async getServices(app) {
|
|
193
193
|
const { spaceGuid: space_guids, orgGuid: organization_guids } = await this.getCfSpaceInfo();
|
|
194
194
|
const apps = await this._cfRequest('/v3/apps', { space_guids, organization_guids, names: app });
|
|
195
|
+
if (apps.resources.length < 1) throw `Cannot find app with name '${app}'`
|
|
195
196
|
const guid = apps.resources[0].guid;
|
|
196
197
|
const env = await this._cfRequest('/v3/apps/'+guid+'/env', { space_guids, organization_guids, names: app });
|
|
197
198
|
return Object.values(env.system_env_json.VCAP_SERVICES).flat();
|