@sap/cds-dk 7.8.2 → 7.9.2
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 +28 -7
- package/bin/bind.js +64 -21
- package/bin/build.js +35 -3
- package/bin/cds.js +31 -13
- package/bin/compile/index.js +44 -3
- package/bin/completion/completionFs.js +61 -0
- package/bin/completion/index.js +212 -0
- package/bin/completion/scripts/cds-bash.sh +17 -0
- package/bin/completion/scripts/cds-ps.ps1 +18 -0
- package/bin/completion/scripts/cds-zsh.sh +12 -0
- package/bin/help.js +8 -2
- package/bin/init.js +34 -4
- package/bin/subscribe.js +5 -1
- package/bin/watch.js +4 -2
- package/lib/bind/KubernetesApiAccessor.js +3 -2
- package/lib/bind/bindingResolver.js +8 -4
- package/lib/bind/bindingUtil.js +10 -8
- package/lib/bind/cfKeyProvider.js +58 -153
- package/lib/bind/index.js +49 -36
- package/lib/bind/kubernetesCredentialsProvider.js +6 -5
- package/lib/bind/userCdsrcJsonStorageProvider.js +25 -3
- package/lib/build/buildTaskEngine.js +1 -1
- package/lib/build/provider/hana/index.js +1 -1
- package/lib/build/provider/hana/template/undeploy.json +3 -1
- package/lib/build/provider/nodejs/index.js +1 -1
- package/lib/compile/openapi/index.js +5 -0
- package/lib/deploy/to-hana/hana.js +25 -7
- package/lib/env/schemas/cds-rc.js +29 -2
- package/lib/import/odata/v4parser.js +78 -25
- package/lib/init/constants.js +2 -1
- package/lib/init/index.js +18 -13
- package/lib/init/merge.js +1 -0
- package/lib/init/mvn.js +8 -47
- package/lib/init/plugin.js +0 -4
- package/lib/init/projectReader.js +17 -10
- package/lib/init/template/approuter/files/values.yaml.hbs +0 -12
- package/lib/init/template/completion/completionSetup.js +184 -0
- package/lib/init/template/completion/index.js +22 -0
- package/lib/init/template/data/as-csv.js +25 -17
- package/lib/init/template/data/as-json.js +148 -44
- package/lib/init/template/data/index.js +28 -15
- package/lib/init/template/hana/files/undeploy.json +2 -1
- package/lib/init/template/helm/files/chart/values.schema.json +1 -1
- package/lib/init/template/helm/files/content-deployment/Chart.yaml +0 -2
- package/lib/init/template/helm/files/content-deployment/values.schema.json +0 -1
- package/lib/init/template/helm/files/service-instance/Chart.yaml +0 -2
- package/lib/init/template/helm/files/service-instance/values.schema.json +0 -1
- package/lib/init/template/helm/files/web-application/Chart.yaml +0 -2
- package/lib/init/template/helm/files/web-application/values.schema.json +0 -1
- package/lib/init/template/helm-unified-runtime/files/values.schema.json +1 -1
- package/lib/init/template/http/index.js +150 -104
- package/lib/init/template/kafka/index.js +6 -0
- package/lib/init/template/lint/index.js +12 -13
- package/lib/init/template/mta/files/mta.yaml.hbs +2 -2
- package/lib/init/template/multitenancy/files/values.yaml.hbs +0 -5
- package/lib/init/template/multitenancy/index.js +13 -5
- package/lib/init/template/nodejs/files/.vscode/settings.json +11 -0
- package/lib/init/template/nodejs/files/eslint.config.js +13 -0
- package/lib/init/template/nodejs/files/package.json.hbs +3 -1
- package/lib/init/template/sample/index.js +18 -20
- package/lib/init/template/tiny-sample/index.js +1 -2
- package/lib/init/template/typer/index.js +2 -2
- package/lib/lint/Linter.js +133 -55
- package/lib/lint/checks.js +5 -6
- package/lib/lint/io.js +119 -10
- package/lib/mtx/auth_manager.js +5 -5
- package/lib/mtx/params.js +4 -2
- package/lib/mtx/settings_manager.js +1 -1
- package/lib/mtx/tenant_action.js +12 -2
- package/lib/mtx/util/logging.js +10 -4
- package/lib/util/axios.js +2 -1
- package/lib/util/cf.js +32 -37
- package/lib/util/git.js +1 -1
- package/lib/util/term.js +1 -1
- package/{bin → lib/watch}/watched.js +2 -2
- package/npm-shrinkwrap.json +35 -52
- package/package.json +2 -2
- package/lib/init/template/nodejs/files/.eslintrc +0 -26
package/lib/lint/io.js
CHANGED
|
@@ -11,7 +11,7 @@ module.exports = {
|
|
|
11
11
|
* @param {*} configPath config file path to read from
|
|
12
12
|
* @returns config file contents
|
|
13
13
|
*/
|
|
14
|
-
async
|
|
14
|
+
async readEslintConfigLegacy(configPath) {
|
|
15
15
|
let config;
|
|
16
16
|
const configFile = path.basename(configPath);
|
|
17
17
|
const configString = await read(configPath, "utf8");
|
|
@@ -33,7 +33,7 @@ module.exports = {
|
|
|
33
33
|
config = await readJSONC(configPath);
|
|
34
34
|
break;
|
|
35
35
|
case "package.json":
|
|
36
|
-
config =
|
|
36
|
+
config = await read(configPath, "json");
|
|
37
37
|
if ("eslintConfig" in config) {
|
|
38
38
|
config = config["eslintConfig"];
|
|
39
39
|
} else {
|
|
@@ -46,16 +46,68 @@ module.exports = {
|
|
|
46
46
|
return config;
|
|
47
47
|
},
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Reads ESLint config contents from file
|
|
51
|
+
* @param {*} configPath config file path to read from
|
|
52
|
+
* @returns config file contents
|
|
53
|
+
*/
|
|
54
|
+
async readEslintConfig(configPath) {
|
|
55
|
+
let config;
|
|
56
|
+
const configFile = path.basename(configPath);
|
|
57
|
+
switch (configFile) {
|
|
58
|
+
case "eslint.config.js":
|
|
59
|
+
case "eslint.config.cjs":
|
|
60
|
+
case "eslint.config.mjs":
|
|
61
|
+
config = this.safeRequire(configPath)
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return config;
|
|
67
|
+
},
|
|
68
|
+
|
|
49
69
|
/**
|
|
50
70
|
* Checks ESLint config file contents for locally installed
|
|
51
71
|
* plugin with:
|
|
52
72
|
* (1) All CDS recommended rules "on"
|
|
53
|
-
* (2)
|
|
54
|
-
* (3) Add's custom rule example 'no-entity-moo' if requested
|
|
73
|
+
* (2) Add's custom rule example 'no-entity-moo' if requested
|
|
55
74
|
* @param {*} configPath ESLint config file
|
|
56
75
|
* @returns {void}
|
|
57
76
|
*/
|
|
58
77
|
async sanitizeEslintConfig(configIn, customRuleExample = false, logger = console) {
|
|
78
|
+
let configContents;
|
|
79
|
+
const isFilePath = typeof configIn === "string";
|
|
80
|
+
if (isFilePath) {
|
|
81
|
+
configContents = this.safeRequire(configIn); // TODO error handling invalid json
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
configContents ??= { ...configIn };
|
|
85
|
+
|
|
86
|
+
if (customRuleExample) {
|
|
87
|
+
const ruleName = "no-entity-moo";
|
|
88
|
+
const ruleValue = 2;
|
|
89
|
+
if ("rules" in configContents) {
|
|
90
|
+
configContents["rules"][ruleName] = ruleValue;
|
|
91
|
+
} else {
|
|
92
|
+
configContents["rules"] = { [ruleName]: ruleValue };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (isFilePath) {
|
|
96
|
+
await this.writeEslintConfig(configIn, logger);
|
|
97
|
+
}
|
|
98
|
+
return configContents;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Checks ESLint config file contents for locally installed
|
|
103
|
+
* plugin with:
|
|
104
|
+
* (1) All CDS recommended rules "on"
|
|
105
|
+
* (2) Option "root": false to stop further config merging
|
|
106
|
+
* (3) Add's custom rule example 'no-entity-moo' if requested
|
|
107
|
+
* @param {*} configPath ESLint config file
|
|
108
|
+
* @returns {void}
|
|
109
|
+
*/
|
|
110
|
+
async sanitizeEslintConfigLegacy(configIn, customRuleExample = false, logger = console) {
|
|
59
111
|
let configContents = {};
|
|
60
112
|
const isFilePath = typeof configIn === "string";
|
|
61
113
|
if (isFilePath) {
|
|
@@ -93,19 +145,20 @@ module.exports = {
|
|
|
93
145
|
}
|
|
94
146
|
}
|
|
95
147
|
if (isFilePath) {
|
|
96
|
-
await this.
|
|
148
|
+
await this.writeEslintConfigLegacy(configIn, configContents, logger);
|
|
97
149
|
}
|
|
98
150
|
return configContents;
|
|
99
151
|
},
|
|
100
152
|
|
|
153
|
+
|
|
101
154
|
/**
|
|
102
155
|
* Writes ESLint config contents to file
|
|
103
156
|
* @param {*} configContents config file contents
|
|
104
157
|
* @param {*} configPath config file path to write to
|
|
105
158
|
* @returns {Promise<void>}
|
|
106
159
|
*/
|
|
107
|
-
async
|
|
108
|
-
const configType = "recommended";
|
|
160
|
+
async writeEslintConfigLegacy(configPath, configContents, logger = console) {
|
|
161
|
+
const configType = "recommended-legacy";
|
|
109
162
|
const extendsValue = `plugin:@sap/cds/${configType}`;
|
|
110
163
|
let contents = {};
|
|
111
164
|
let configFile = path.basename(configPath);
|
|
@@ -114,7 +167,7 @@ module.exports = {
|
|
|
114
167
|
case ".eslintrc.js":
|
|
115
168
|
case ".eslintrc.cjs":
|
|
116
169
|
try {
|
|
117
|
-
configContents = await this.
|
|
170
|
+
configContents = await this.readEslintConfigLegacy(configPath);
|
|
118
171
|
} catch (err) {
|
|
119
172
|
configContents = {};
|
|
120
173
|
}
|
|
@@ -146,7 +199,7 @@ module.exports = {
|
|
|
146
199
|
break;
|
|
147
200
|
case "package.json":
|
|
148
201
|
if (exists(configPath)) {
|
|
149
|
-
contents =
|
|
202
|
+
contents = await read(configPath, "json");
|
|
150
203
|
} else {
|
|
151
204
|
contents = {};
|
|
152
205
|
}
|
|
@@ -162,6 +215,54 @@ module.exports = {
|
|
|
162
215
|
}
|
|
163
216
|
},
|
|
164
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Suggest ESLint config contents to file
|
|
220
|
+
* @param {*} configContents config file contents
|
|
221
|
+
* @param {*} configPath config file path to write to
|
|
222
|
+
* @returns {Promise<void>}
|
|
223
|
+
*/
|
|
224
|
+
async writeEslintConfig(configPath, logger = console) {
|
|
225
|
+
let configFile = path.basename(configPath);
|
|
226
|
+
switch (configFile) {
|
|
227
|
+
case "eslint.config.js":
|
|
228
|
+
case "eslint.config.cjs":
|
|
229
|
+
case "eslint.config.mjs":
|
|
230
|
+
if (exists(configPath)) {
|
|
231
|
+
logger.log(
|
|
232
|
+
`${term.warn(
|
|
233
|
+
`\n\nPlease add the following to your "${configFile}":` +
|
|
234
|
+
'\n\n 1. At the top of your file, import the following:')}` +
|
|
235
|
+
`\n\n ${term.bold('const cds = require(\'@sap/eslint-plugin-cds\')')}` +
|
|
236
|
+
`${term.warn(
|
|
237
|
+
'\n\n 2. In your export, add the following to the top of the array:')}` +
|
|
238
|
+
`\n\n ${term.bold('cds.configs.recommended')}`
|
|
239
|
+
);
|
|
240
|
+
} else {
|
|
241
|
+
await write(configPath, `
|
|
242
|
+
const cds = require('@sap/eslint-plugin-cds')
|
|
243
|
+
|
|
244
|
+
module.exports = [
|
|
245
|
+
cds.configs.recommended,
|
|
246
|
+
{
|
|
247
|
+
plugins: {
|
|
248
|
+
"@sap/cds": cds
|
|
249
|
+
},
|
|
250
|
+
"files": [
|
|
251
|
+
...cds.configs.recommended.files
|
|
252
|
+
],
|
|
253
|
+
"rules": {
|
|
254
|
+
...cds.configs.recommended.rules
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
`)
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
default:
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
|
|
165
266
|
/**
|
|
166
267
|
* Read VS Code settings from file
|
|
167
268
|
* @param {*} settingsPath settings file path to read from
|
|
@@ -204,7 +305,7 @@ module.exports = {
|
|
|
204
305
|
* (3) "Global" case adds configFile and removes rulePaths
|
|
205
306
|
* @param {*} settingsPath VS Code settings file
|
|
206
307
|
*/
|
|
207
|
-
async sanitizeVscodeSettings(
|
|
308
|
+
async sanitizeVscodeSettings(settingsPath, lintType, lintFileTypes, customRuleExample = false) {
|
|
208
309
|
let settings = (await this.readVscodeSettings(settingsPath)) || {};
|
|
209
310
|
const rulePaths = [path.join(".eslint/rules")];
|
|
210
311
|
|
|
@@ -253,4 +354,12 @@ module.exports = {
|
|
|
253
354
|
}
|
|
254
355
|
return userOpts;
|
|
255
356
|
},
|
|
357
|
+
|
|
358
|
+
safeRequire(module) {
|
|
359
|
+
try {
|
|
360
|
+
return require(module)
|
|
361
|
+
} catch (e) {
|
|
362
|
+
return null
|
|
363
|
+
}
|
|
364
|
+
}
|
|
256
365
|
};
|
package/lib/mtx/auth_manager.js
CHANGED
|
@@ -65,26 +65,26 @@ async function retrieveTokenOrPasscodeUrl(params) {
|
|
|
65
65
|
DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${params.obfuscateQueryParams(url)}`);
|
|
66
66
|
response = await axios[method](url, data);
|
|
67
67
|
} catch (e) {
|
|
68
|
-
|
|
69
|
-
if (error.status === 404) { // may represent 405 in case of wrong method
|
|
68
|
+
if (e.status === 404) { // may represent 405 in case of wrong method
|
|
70
69
|
continue reqParams;
|
|
71
70
|
} else if (params.has('refreshToken')) {
|
|
72
71
|
DEBUG?.('Discarding invalid refresh token');
|
|
73
72
|
params.delete('refreshToken');
|
|
74
73
|
} else {
|
|
74
|
+
error = e;
|
|
75
75
|
break reqParams;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
} while (!
|
|
78
|
+
} while (!response);
|
|
79
79
|
}
|
|
80
80
|
const data = response?.data ?? error?.auth;
|
|
81
81
|
assign(params, data);
|
|
82
|
-
if (
|
|
82
|
+
if (error) {
|
|
83
83
|
throw new Error(REASONS.invalid_scope.test(error.message)
|
|
84
84
|
? 'Token has invalid scope. Check if your user has the required roles'
|
|
85
85
|
: `No valid passcode or token provided. Get a passcode${
|
|
86
86
|
params.get('passcodeUrl') ? ' from ' + params.get('passcodeUrl') : ''
|
|
87
|
-
} and re-run the command with '-p <passcode>'
|
|
87
|
+
} and re-run the command with '-p <passcode>'`, { cause: error });
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
package/lib/mtx/params.js
CHANGED
|
@@ -213,6 +213,7 @@ class AllParams extends BasicParams {
|
|
|
213
213
|
// `extend` PARAMS
|
|
214
214
|
this.force = new Param('force', false);
|
|
215
215
|
this.templates = new Param('templates', false);
|
|
216
|
+
this["download-migrated-projects"] = new Param('download-migrated-projects', false)
|
|
216
217
|
|
|
217
218
|
// `activate` PARAMS
|
|
218
219
|
this.undeploy = new Param('undeploy', false);
|
|
@@ -220,6 +221,9 @@ class AllParams extends BasicParams {
|
|
|
220
221
|
this.sync = new Param('sync', false);
|
|
221
222
|
this.async = new Param('async', false);
|
|
222
223
|
|
|
224
|
+
// `subscribe` PARAMS
|
|
225
|
+
this.body = new Param('body', '', { persist: Persistence.none });
|
|
226
|
+
|
|
223
227
|
// `login` PARAMS
|
|
224
228
|
this.skipToken = new Param('skipToken', false, { internal: true });
|
|
225
229
|
|
|
@@ -229,8 +233,6 @@ class AllParams extends BasicParams {
|
|
|
229
233
|
this.clearInvalid = new Param('clearInvalid', false);
|
|
230
234
|
this["clear-invalid"] = this.clearInvalid.newAlias('clear-invalid');
|
|
231
235
|
|
|
232
|
-
// download for mxts
|
|
233
|
-
this["download-migrated-projects"] = new Param('download-migrated-projects', false)
|
|
234
236
|
}
|
|
235
237
|
}
|
|
236
238
|
|
|
@@ -252,7 +252,7 @@ class SettingsManager {
|
|
|
252
252
|
async function updateTokenUrl() {
|
|
253
253
|
const tokenUrl = params.get('tokenUrl');
|
|
254
254
|
const renewUrl = !logout && !params.get('skipToken')
|
|
255
|
-
&& (!tokenUrl || tokenUrl.includes('//-/cds') || params.get('appUrl') !== loadedSettings.get('appUrl'));
|
|
255
|
+
&& (!tokenUrl || tokenUrl.includes('//-/cds') || tokenUrl.includes(OAUTH_PATH_OLD_MTX) || params.get('appUrl') !== loadedSettings.get('appUrl'));
|
|
256
256
|
if (renewUrl) {
|
|
257
257
|
const tokenUrl = await getTokenBaseUrl(params, renewUrl);
|
|
258
258
|
params.set('tokenUrl', tokenUrl);
|
package/lib/mtx/tenant_action.js
CHANGED
|
@@ -48,8 +48,17 @@ module.exports = class TenantAction {
|
|
|
48
48
|
console.log(capitalize(this.gerund), { tenant }, this.preposition, { url: params.get('appUrl') });
|
|
49
49
|
const url = `${params.get('appUrl')}/-/cds/deployment/${this.command}`;
|
|
50
50
|
DEBUG?.(`Sending request to ${url}`);
|
|
51
|
+
const body = params.get('body');
|
|
52
|
+
let parsedBody;
|
|
53
|
+
if (body) {
|
|
54
|
+
try {
|
|
55
|
+
parsedBody = JSON.parse(body)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw getMessage('Invalid subscription body', { error, command: this.command });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
51
60
|
try {
|
|
52
|
-
await require('axios').post(url, { tenant }, params.get('reqAuth'));
|
|
61
|
+
await require('axios').post(url, { tenant, metadata: parsedBody, options: parsedBody }, params.get('reqAuth'));
|
|
53
62
|
} catch (error) {
|
|
54
63
|
handleHttpError(error, params, { url });
|
|
55
64
|
}
|
|
@@ -74,6 +83,7 @@ module.exports = class TenantAction {
|
|
|
74
83
|
if (!cds_mtx) {
|
|
75
84
|
throw '@sap/cds-mtxs outdated. Please upgrade to @sap/cds-mtxs@>=1.7.0 and re-run this command.';
|
|
76
85
|
}
|
|
77
|
-
|
|
86
|
+
const body = params.get('body')
|
|
87
|
+
return cds_mtx(this.command, tenant, body);
|
|
78
88
|
}
|
|
79
89
|
}
|
package/lib/mtx/util/logging.js
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
function getMessage(errorText, { error, command, help = command && getCommandHelp(command) } = {}) {
|
|
4
4
|
let message = errorText ? errorText.trim() : '';
|
|
5
5
|
if (error) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
message
|
|
6
|
+
const debug = require('../../cds').debug('cli');
|
|
7
|
+
const includeStack = debug && error.stack?.includes('\n');
|
|
8
|
+
if (message.length) {
|
|
9
|
+
message = message.replace(/(?<!\p{P})$/u, ':'); // Append colon if no punctuation at the end
|
|
10
|
+
message += includeStack ? '\n' : ' ';
|
|
11
|
+
}
|
|
9
12
|
const oriMsg = error.response?.data?.message // case: response body == error
|
|
10
13
|
|| error.response?.data?.error?.message // case: response body == { error }
|
|
11
14
|
|| (includeStack ? '' : error.message);
|
|
@@ -13,9 +16,12 @@ function getMessage(errorText, { error, command, help = command && getCommandHel
|
|
|
13
16
|
if (includeStack) {
|
|
14
17
|
message += (oriMsg ? '\n' : '') + error.stack;
|
|
15
18
|
}
|
|
19
|
+
if (debug && error.cause) {
|
|
20
|
+
message += '\nCaused by: ' + getMessage('', { error: error.cause }).replace(/^(?= *at)/gm, ' ');
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
if (typeof help === 'string') {
|
|
18
|
-
message += require('../../../bin/help').formatHelp(help);
|
|
24
|
+
message += '\n' + require('../../../bin/help').formatHelp(help);
|
|
19
25
|
}
|
|
20
26
|
return message;
|
|
21
27
|
}
|
package/lib/util/axios.js
CHANGED
|
@@ -15,7 +15,8 @@ function pruneErrors() {
|
|
|
15
15
|
response => response,
|
|
16
16
|
axError => {
|
|
17
17
|
const { inspect } = require('util');
|
|
18
|
-
|
|
18
|
+
let { url, method } = axError.config ?? {};
|
|
19
|
+
url = url?.replace(/(passcode|refreshToken|clientsecret|key)=[^&]+/g, '$1=...');
|
|
19
20
|
const { errno, code, response = {}, message: axMessage } = axError;
|
|
20
21
|
const prefix = (url && method ? `${method.toUpperCase()} ${url}` : 'Request') + ' failed';
|
|
21
22
|
|
package/lib/util/cf.js
CHANGED
|
@@ -28,12 +28,8 @@ const OPERATION_STATE_SUCCEEDED = 'succeeded';
|
|
|
28
28
|
|
|
29
29
|
const CF_CLIENT_MINIMUM_VERSION = 8;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
_clear() {
|
|
35
|
-
this.spaceInfo = null;
|
|
36
|
-
}
|
|
31
|
+
module.exports = new class CfUtil {
|
|
32
|
+
spaceInfo;
|
|
37
33
|
|
|
38
34
|
async _sleep(ms) {
|
|
39
35
|
return new Promise((resolve) => {
|
|
@@ -41,7 +37,7 @@ class CfUtil {
|
|
|
41
37
|
});
|
|
42
38
|
}
|
|
43
39
|
|
|
44
|
-
async
|
|
40
|
+
async cfRun(...args) {
|
|
45
41
|
args = args.map(arg => arg.replace(/"/g, '\\"'));
|
|
46
42
|
const cmdLine = `${CF_COMMAND} "${args.join('" "')}"`;
|
|
47
43
|
DEBUG && console.time(cmdLine);
|
|
@@ -65,12 +61,15 @@ class CfUtil {
|
|
|
65
61
|
}
|
|
66
62
|
}
|
|
67
63
|
|
|
68
|
-
async
|
|
64
|
+
async cfRequest(urlPath, queryObj, bodyObj) {
|
|
69
65
|
if (queryObj) {
|
|
70
66
|
const entries = Object.entries(queryObj);
|
|
71
67
|
const queryStr = entries.map(([key, value]) => {
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
value = value.join(',');
|
|
70
|
+
}
|
|
72
71
|
// commas cause problems in cf curl when not double encoded
|
|
73
|
-
value = value.replace(/,/g, encodeURIComponent(','));
|
|
72
|
+
// value = value.replace(/,/g, encodeURIComponent(','));
|
|
74
73
|
return `${key}=${encodeURIComponent(value)}`;
|
|
75
74
|
}).join('&');
|
|
76
75
|
|
|
@@ -84,7 +83,7 @@ class CfUtil {
|
|
|
84
83
|
args.push(JSON.stringify(bodyObj)); // cfRun uses spawn so no special handling for quotes on cli required
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
const result = await this.
|
|
86
|
+
const result = await this.cfRun(...args);
|
|
88
87
|
let response = {};
|
|
89
88
|
if (result.stdout) {
|
|
90
89
|
response = JSON.parse(result.stdout);
|
|
@@ -108,7 +107,7 @@ class CfUtil {
|
|
|
108
107
|
throw new Error(errorMsg);
|
|
109
108
|
}
|
|
110
109
|
|
|
111
|
-
async
|
|
110
|
+
async _getCfTargetFromConfigFile() {
|
|
112
111
|
const cfHome = process.env.CF_HOME || process.env.cf_home || path.join(os.homedir(), '.cf');
|
|
113
112
|
try {
|
|
114
113
|
const fileContent = await fsp.readFile(path.join(cfHome, 'config.json'));
|
|
@@ -125,8 +124,8 @@ class CfUtil {
|
|
|
125
124
|
}
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
async
|
|
129
|
-
const result = await this.
|
|
127
|
+
async _getCfTargetFromCli() {
|
|
128
|
+
const result = await this.cfRun('target');
|
|
130
129
|
if (result?.stdout) {
|
|
131
130
|
return {
|
|
132
131
|
apiEndpoint: this._extract(result.stdout, /api endpoint\s*:\s*([^\s]+)/i, `CF API endpoint is missing. Use 'cf login' to login.`),
|
|
@@ -137,23 +136,23 @@ class CfUtil {
|
|
|
137
136
|
}
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
async
|
|
139
|
+
async _getCfAuthorization() {
|
|
141
140
|
DEBUG?.('getting authorization');
|
|
142
141
|
try {
|
|
143
|
-
return (await this.
|
|
142
|
+
return (await this.cfRun('oauth-token')).stdout;
|
|
144
143
|
} catch (err) {
|
|
145
144
|
throw err.message;
|
|
146
145
|
}
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
async getCfTarget() {
|
|
150
|
-
await this.
|
|
151
|
-
await this.
|
|
152
|
-
return await this.
|
|
149
|
+
await this._checkCliVersion();
|
|
150
|
+
await this._getCfAuthorization();
|
|
151
|
+
return await this._getCfTargetFromConfigFile() || await this._getCfTargetFromCli();
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
async
|
|
156
|
-
const result = await this.
|
|
154
|
+
async _checkCliVersion() {
|
|
155
|
+
const result = await this.cfRun('-v');
|
|
157
156
|
const version = result?.stdout?.match(/version.*(\d+\.\d+\.\d+)/i)
|
|
158
157
|
if (parseInt(version?.[1]) < CF_CLIENT_MINIMUM_VERSION) {
|
|
159
158
|
console.log(warn(`
|
|
@@ -170,13 +169,13 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
170
169
|
const target = await this.getCfTarget();
|
|
171
170
|
|
|
172
171
|
const { org, space } = target;
|
|
173
|
-
const orgs = await this.
|
|
172
|
+
const orgs = await this.cfRequest(`/v3/organizations`, { names: org });
|
|
174
173
|
if (!orgs?.resources?.length) {
|
|
175
174
|
throw new Error(`CF org ${bold(org)} not found!`);
|
|
176
175
|
}
|
|
177
176
|
|
|
178
177
|
const orgGuid = orgs.resources[0].guid;
|
|
179
|
-
const spaces = await this.
|
|
178
|
+
const spaces = await this.cfRequest(`/v3/spaces`, { names: space, organization_guids: orgGuid });
|
|
180
179
|
if (!spaces?.resources?.length) {
|
|
181
180
|
throw new Error(`CF space ${bold(space)} not found in org ${bold(org)}!`);
|
|
182
181
|
}
|
|
@@ -196,10 +195,10 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
196
195
|
|
|
197
196
|
async getEnv(app) {
|
|
198
197
|
const { spaceGuid: space_guids, orgGuid: organization_guids } = await this.getCfSpaceInfo();
|
|
199
|
-
const apps = await this.
|
|
198
|
+
const apps = await this.cfRequest('/v3/apps', { space_guids, organization_guids, names: app });
|
|
200
199
|
if (apps.resources.length < 1) throw `Cannot find app with name '${app}'`
|
|
201
200
|
const guid = apps.resources[0].guid;
|
|
202
|
-
return await this.
|
|
201
|
+
return await this.cfRequest('/v3/apps/' + guid + '/env', { space_guids, organization_guids, names: app });
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
async getService(serviceName, showMessage = true) {
|
|
@@ -209,7 +208,7 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
209
208
|
let counter = POLL_COUNTER;
|
|
210
209
|
while (counter > 0) {
|
|
211
210
|
counter--;
|
|
212
|
-
const serviceInstances = await this.
|
|
211
|
+
const serviceInstances = await this.cfRequest('/v3/service_instances', {
|
|
213
212
|
names: serviceName,
|
|
214
213
|
space_guids: spaceInfo.spaceGuid,
|
|
215
214
|
organization_guids: spaceInfo.orgGuid
|
|
@@ -253,7 +252,7 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
253
252
|
|
|
254
253
|
const spaceInfo = await this.getCfSpaceInfo();
|
|
255
254
|
|
|
256
|
-
const servicePlan = await this.
|
|
255
|
+
const servicePlan = await this.cfRequest(`/v3/service_plans`, {
|
|
257
256
|
names: planName,
|
|
258
257
|
space_guids: spaceInfo.spaceGuid,
|
|
259
258
|
organization_guids: spaceInfo.orgGuid,
|
|
@@ -286,7 +285,7 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
286
285
|
body.parameters = { ...options };
|
|
287
286
|
}
|
|
288
287
|
|
|
289
|
-
const postResult = await this.
|
|
288
|
+
const postResult = await this.cfRequest('/v3/service_instances', undefined, body);
|
|
290
289
|
if (postResult?.errors) {
|
|
291
290
|
throw new Error(postResult.errors[0].detail);
|
|
292
291
|
}
|
|
@@ -299,14 +298,13 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
299
298
|
throw new Error(`Could not create service ${bold(serviceName)}`);
|
|
300
299
|
}
|
|
301
300
|
|
|
302
|
-
|
|
303
|
-
async getServiceKey(serviceInstance, serviceKeyName, showMessage = true) {
|
|
301
|
+
async _getServiceKey(serviceInstance, serviceKeyName, showMessage = true) {
|
|
304
302
|
showMessage && console.log(`Getting service key ${bold(serviceKeyName)}`);
|
|
305
303
|
|
|
306
304
|
let counter = POLL_COUNTER;
|
|
307
305
|
while (counter > 0) {
|
|
308
306
|
counter--;
|
|
309
|
-
const bindings = await this.
|
|
307
|
+
const bindings = await this.cfRequest(`/v3/service_credential_bindings`, { names: serviceKeyName, service_instance_guids: serviceInstance.guid });
|
|
310
308
|
if (!bindings?.resources?.length) {
|
|
311
309
|
return null;
|
|
312
310
|
}
|
|
@@ -319,7 +317,7 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
319
317
|
break;
|
|
320
318
|
|
|
321
319
|
case OPERATION_STATE_SUCCEEDED: {
|
|
322
|
-
const keyDetails = await this.
|
|
320
|
+
const keyDetails = await this.cfRequest(`/v3/service_credential_bindings/${encodeURIComponent(binding.guid)}/details`);
|
|
323
321
|
return keyDetails.credentials;
|
|
324
322
|
}
|
|
325
323
|
|
|
@@ -335,9 +333,8 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
335
333
|
throw new Error(`Timeout occurred while getting service key ${bold(serviceKeyName)}`);
|
|
336
334
|
}
|
|
337
335
|
|
|
338
|
-
|
|
339
336
|
async getOrCreateServiceKey(serviceInstance, serviceKeyName, parameters, { silent = false, create = true } = {}) {
|
|
340
|
-
const serviceKey = await this.
|
|
337
|
+
const serviceKey = await this._getServiceKey(serviceInstance, serviceKeyName, false);
|
|
341
338
|
if (serviceKey || !create) {
|
|
342
339
|
if (serviceKey && !silent) console.log(`Getting service key ${bold(serviceKeyName)}`);
|
|
343
340
|
return serviceKey;
|
|
@@ -358,12 +355,12 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
358
355
|
parameters
|
|
359
356
|
}
|
|
360
357
|
|
|
361
|
-
const postResult = await this.
|
|
358
|
+
const postResult = await this.cfRequest('/v3/service_credential_bindings', undefined, body);
|
|
362
359
|
if (postResult?.errors) {
|
|
363
360
|
throw new Error(postResult.errors[0].detail);
|
|
364
361
|
}
|
|
365
362
|
|
|
366
|
-
const newServiceKey = await this.
|
|
363
|
+
const newServiceKey = await this._getServiceKey(serviceInstance, serviceKeyName, false);
|
|
367
364
|
if (newServiceKey) {
|
|
368
365
|
return newServiceKey;
|
|
369
366
|
}
|
|
@@ -371,5 +368,3 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
|
|
|
371
368
|
throw new Error(`Could not create service key ${bold(serviceKeyName)}`);
|
|
372
369
|
}
|
|
373
370
|
}
|
|
374
|
-
|
|
375
|
-
module.exports = new CfUtil();
|
package/lib/util/git.js
CHANGED
|
@@ -35,7 +35,7 @@ class GitUtil {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
console.log(`
|
|
38
|
+
console.log(`Adding entry '${file}' to ${GIT_IGNORE_FILE}.`);
|
|
39
39
|
let gitignore = await this.readFileSafely(gitIgnorePath);
|
|
40
40
|
gitignore = gitignore + `${os.EOL}# added by cds${os.EOL}${file}${os.EOL}`;
|
|
41
41
|
|
package/lib/util/term.js
CHANGED
|
@@ -26,7 +26,7 @@ module.exports.info = o => as(t.green, o)
|
|
|
26
26
|
module.exports.link = o => as(t.link, o)
|
|
27
27
|
module.exports.bold = o => as(t.bold, o)
|
|
28
28
|
module.exports.highlight = o => as(t.blue,as(t.bold, o))
|
|
29
|
-
module.exports.
|
|
29
|
+
module.exports.dim = o => as(t.grey, o)
|
|
30
30
|
|
|
31
31
|
const format = module.exports.format = (o, severity='Error', asInternalError=false, withStack=false) => {
|
|
32
32
|
switch (severity) {
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
// run through cli in order to use its error handling
|
|
9
9
|
const args = JSON.parse(process.env._args)
|
|
10
|
-
const cds = require('../
|
|
11
|
-
const bootstrap = require('
|
|
10
|
+
const cds = require('../cds'); cds.watched = true
|
|
11
|
+
const bootstrap = require('../../bin/cds')(...args)
|
|
12
12
|
|
|
13
13
|
cds.once('listening', ({server}) => {
|
|
14
14
|
process.send ({ code:'listening', port: server.address().port })
|