@sap/cds-dk 4.6.2 → 4.7.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/add.js +2 -1
  3. package/bin/bind.js +121 -48
  4. package/bin/cds.js +8 -0
  5. package/bin/env.js +0 -2
  6. package/bin/repl.js +5 -2
  7. package/bin/watch.js +11 -8
  8. package/lib/bind/bindingResolver.js +26 -14
  9. package/lib/bind/bindingUtil.js +144 -0
  10. package/lib/bind/cfKeyProvider.js +2 -80
  11. package/lib/bind/kubernetesCredentialsProvider.js +157 -0
  12. package/lib/bind/userCdsrcJsonStorageProvider.js +7 -7
  13. package/lib/client/activate.js +9 -17
  14. package/lib/client/auth_manager.js +1 -1
  15. package/lib/client/extend.js +4 -9
  16. package/lib/import/index.js +3 -2
  17. package/lib/import/internalDoc/edm2cdsTypeMapping.md +38 -0
  18. package/lib/import/utils/MultiMap.js +1 -0
  19. package/lib/import/v2parser.js +102 -37
  20. package/lib/import/v4parser.js +59 -20
  21. package/lib/index.js +6 -5
  22. package/lib/init/constants.js +2 -2
  23. package/lib/init/index.js +1 -0
  24. package/lib/init/template/data/index.js +29 -7
  25. package/lib/init/template/hana/files/common/undeploy.json +5 -0
  26. package/lib/init/template/hana/files/java/package.json +3 -0
  27. package/lib/init/template/hana/index.js +19 -23
  28. package/lib/init/template/lint/index.js +17 -1
  29. package/lib/init/template/mta/files/mta.yaml.hbs +12 -5
  30. package/lib/init/template/mta/projectReader.js +1 -3
  31. package/lib/init/template/samples/index.js +1 -1
  32. package/lib/init/template/templateBase.js +10 -41
  33. package/lib/init/util/fsUtil.js +1 -1
  34. package/lib/init/util/mvnArchetypeUtil.js +3 -1
  35. package/lib/lint/Linter.js +6 -13
  36. package/lib/lint/io.js +21 -31
  37. package/lib/watch/node-watch.js +46 -20
  38. package/npm-shrinkwrap.json +10648 -5083
  39. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -6,6 +6,65 @@ This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](http://keepachangelog.com/).
8
8
 
9
+ ## Version 4.7.2 - 2021-12-15
10
+
11
+ ### Fixed
12
+
13
+ - `cds add lint` uses JSONC parser to read ESLint config files of type `.eslintrc[.js,.cjs,.json]`.
14
+
15
+ ### Changed
16
+
17
+ - `cds init` uses latest Maven Java archetype version `1.21.0` for creating Java projects.
18
+
19
+ ## Version 4.7.1 - 2021-12-06
20
+
21
+ ### Fixed
22
+
23
+ - `cds activate` authentication no longer fails
24
+ - `cds.compile.to.openapi` does no longer crash if `cds.compile` was already initialized
25
+
26
+ ## Version 4.7.0 - 2021-12-06
27
+
28
+ ### Added
29
+
30
+ - `cds add data --for` can be passed a regular expression, allowing more flexible name filters. For example `--for '^Supplier$'` would only match entity `Supplier`, but not `SupplierAddress`. Note that as before, `--for Supplier` is interpreted as `--for '^Supplier.*'`, i.e. matches both entities.
31
+ - `cds init --add hana` and `cds add hana` now add a `undeploy.json` file containing wildcards. This ensures that SAP HANA artefacts are automatically cleaned-up in case views, unique keys or constraint definitions of associations have been changed in CDS model.
32
+
33
+ ### Changed
34
+
35
+ - `cds import` now adds annotations for missing ON conditions in associations instead of appending it in `doc`.
36
+ - `cds import` has updated mapping for OData V2 and V4
37
+ - `cds add lint` now configures `csv` files in `.vscode/settings.json`
38
+ - `cds add hana` for Java projects now adds an `engines.node` version of `^16` to the generated `db/package.json`, to pin the Node.js version. This will help in the future when runtime environments change their default to some version higher than the one supported by `@sap/hdi-deploy`.
39
+ - `cds add mta` for Node.js projects now adds `npm ci` commands instead of `npm install`. This makes use of `package-lock.json` to enforce reproducible builds.
40
+ - `cds watch` now ignores folders named `target`, to avoid restarts when Maven's build output changes
41
+
42
+ ### Fixed
43
+
44
+ - `cds import` throws warning messages for unsupported data types.
45
+ - `cds watch` allows the explicit `--with-mocks` option, although this is anyways included by default
46
+ - `cds watch` no longer runs into multiple restarts if many files are changed at once, like in git branch changes
47
+
48
+ ## Version 4.6.4 - 2021-11-24
49
+
50
+ ### Fixed
51
+
52
+ - `cds import` fix for TypeError issue during OData V2 EDMX conversion to CSN.
53
+ - `cds import <file_name>` fix now updates the service name in the `package.json` with only the file name. Earlier, the service name sometimes used to be of the form `A.B`, with `A` being file name and `B` being a part of the schema namespace value in the EDMX file
54
+
55
+ ### Changed
56
+
57
+ - `cds init` uses latest Maven Java archetype version `1.20.3` for creating Java projects.
58
+
59
+ ## Version 4.6.3 - 2021-11-16
60
+
61
+ ### Fixed
62
+
63
+ - `cds watch` recovers again from compilation errors and properly prints these
64
+ - `cds import` fix for omitting empty `doc` components.
65
+ - `cds add lint` no longer duplicates initial contents in `package.json`
66
+ - `cds add data` no longer ignores imported services for which credentials are stored in the project
67
+
9
68
  ## Version 4.6.2 - 2021-11-09
10
69
 
11
70
  ### Fixed
package/bin/add.js CHANGED
@@ -36,7 +36,8 @@ module.exports = Object.assign(add, {
36
36
 
37
37
  *-4* | *--for* <entity name or prefix>
38
38
 
39
- Filters the CSN model with the given name or prefix
39
+ Filters entity names with the given string. If it contains meta characters like ^ or *
40
+ it is treated like a regular expression, otherwise as a prefix.
40
41
 
41
42
  *-o* | *--out* <target folder>
42
43
 
package/bin/bind.js CHANGED
@@ -1,24 +1,27 @@
1
-
2
-
3
1
  module.exports = Object.assign(cds_bind, {
4
- options: ['--to', '--profile', '--kind', '--output-file'],
5
- flags: [],
6
- shortcuts: ['-2', '-p', '-k', '-o'],
2
+ options: ['--to', '--on', '--profile', '--kind', '--output-file'],
3
+ flags: ['--exec'],
4
+ shortcuts: ['-2', '-n', '-p', '-k', '-o'],
7
5
  help: `
8
6
  # SYNOPSIS
9
7
 
10
- *cds bind* <service> [<options>] -- beta feature
8
+ *cds bind* <service> [<options>]
9
+ *cds bind --exec* [--profile <profile>] [--] <command> <args ...>
11
10
 
12
11
  Binds the given service to a service instance by storing connection information in *.cdsrc-private.json*
13
12
  in your project directory. Credentials are not stored in the file but rather retrieved dynamically during *cds watch*.
14
13
  Use option *--output-file* to specify a different path or file.
15
14
 
16
- Currently *cds bind* only supports Node.js projects.
15
+ With *--exec* you can execute arbitrary commands with your service bindings. The service bindings are provided
16
+ in the *VCAP_SERVICES* env variable to the command.
17
17
 
18
18
  # OPTIONS
19
19
 
20
- *-2 | --to* <instance>[:<key>]
21
- bind to a given instance
20
+ *-2 | --to* <instance>[:<key>] | <service-binding> | <secret>
21
+ bind to a given Cloud Foundry instance, Kubernetes service binding or Kubernetes secret.
22
+
23
+ *-n | --on* cf | k8s
24
+ bind to service on Cloud Foundry or Kubernetes, defaults to Cloud Foundry
22
25
 
23
26
  *-p | --profile* <profile>
24
27
  add the given profile, defaults to *hybrid*
@@ -26,7 +29,7 @@ module.exports = Object.assign(cds_bind, {
26
29
  *-k | --kind* <kind>
27
30
  the kind of service
28
31
 
29
- *-o | --output-file <path>*
32
+ *-o | --output-file* <path>
30
33
  save bindings to the given *.cdsrc.json* or *package.json* file. Default is *.cdsrc-private.json*.
31
34
  If *path* is a directory, then it will save it to the *.cdsrc.json* file in that directory.
32
35
 
@@ -36,13 +39,12 @@ module.exports = Object.assign(cds_bind, {
36
39
  cds bind --to my-hdi-container,my-xsuaa
37
40
  cds bind uaa --to my-xsuaa:my-xsuaa-key --kind xsuaa --profile myprofile
38
41
 
39
- cds bind --to my-hdi-container --output-file ~
42
+ cds bind --to my-hdi-container --output-file .
40
43
  cds bind --to my-hdi-container --output-file package.json
41
44
 
42
- `});
43
-
44
- const { constants, promises: fsp } = require('fs');
45
- const path = require('path');
45
+ cds bind --to my-hdi-container --on k8s
46
+ `
47
+ });
46
48
 
47
49
  const cds = require('@sap/cds/lib');
48
50
  const LOG = cds.log('bind');
@@ -50,19 +52,25 @@ const LOG = cds.log('bind');
50
52
  LOG.isDebug = LOG._debug;
51
53
  LOG.isTTY = process.stdout.isTTY;
52
54
 
53
- const { bold, info } = require('../lib/util/term');
55
+ const { highlight, bold, info } = require('../lib/util/term');
54
56
 
55
57
  const Spinner = require('../lib/init/util/spinner');
58
+ const resolveBinding = require('../lib/bind/bindingResolver').resolver(LOG);
59
+ const { determineKinds, getRequiresNameFromKind, execAndExit, checkIsJavaProject, throwError, platformText, bindingText } = require('../lib/bind/bindingUtil');
56
60
 
61
+ const VCAP_LABEL_PLACEHOLDER = '<replace with service label>';
57
62
 
58
- async function cds_bind(args, options) { // eslint-disable-line
59
-
60
- options.profile = options.profile || 'hybrid';
63
+ async function cds_bind(args, options) {
64
+ options.profile = parseProfile(options.profile);
65
+ setProfile(options.profile);
61
66
 
62
- if (await isJavaProject('.')) {
63
- throw new Error(`Found a 'pom.xml' file in your project root folder indicating a Java project. Currently, 'cds bind' only supports Node.js projects.`);
67
+ if (options.exec) {
68
+ cds_bind_exec(args, options)
69
+ return;
64
70
  }
65
71
 
72
+ const env = cds.env.for('cds', process.cwd());
73
+
66
74
  if (!options.to) {
67
75
  throw new Error(`use option --to or -2 to specify the target instance, e.g. cds bind --to myInstance:myService`);
68
76
  }
@@ -71,6 +79,8 @@ async function cds_bind(args, options) { // eslint-disable-line
71
79
  throw new Error(`Too many arguments: Please specify only one or no service.`);
72
80
  }
73
81
 
82
+ const isJavaProject = await checkIsJavaProject(process.cwd());
83
+
74
84
  let serviceArg = args[0]
75
85
  const targets = options.to.split(/,/g);
76
86
 
@@ -82,31 +92,72 @@ async function cds_bind(args, options) { // eslint-disable-line
82
92
  throw new Error(`The option '--kind' cannot be specified together with multiple targets ('--to') services. Use one service per call or omit the '--kind' option.`);
83
93
  }
84
94
 
85
- // prepare for different providers
86
- const keyProvider = require('../lib/bind/cfKeyProvider');
87
- keyProvider.setLogger(LOG);
88
95
  const spinner = new Spinner(LOG);
89
- const resolvedServices = await spinner.run(`Retrieving data from Cloud Foundry...`, async () => { // NOSONAR
90
- const resolvedServices = [];
91
- await Promise.all( targets.map( async target => {
92
- const [instance, key] = target.split(/:/);
93
- const resolvedService = await keyProvider.resolve(undefined, { instance, key });
94
- // if (!options["save-credentials"]) {
95
- delete resolvedService.credentials;
96
- resolvedService.binding.resolved = false;
97
- // }
98
- resolvedServices.push(resolvedService);
96
+
97
+ let on;
98
+ if (options.on === 'k8s' || options.on === 'kubernetes') {
99
+ on = 'k8s';
100
+ } else if (!options.on || options.on === 'cf' || options.on === 'cloudfoundry') {
101
+ on = 'cf';
102
+ } else {
103
+ throw new Error(`Invalid value '${options.on}' for option --on.`);
104
+ }
105
+
106
+ const onText = { 'cf': 'Cloud Foundry', 'k8s': 'Kubernetes' }[on];
107
+ const resolvedServices = await spinner.run(`Retrieving data from ${onText}...`, async () => { // NOSONAR
108
+ return await Promise.all( targets.map( async target => {
109
+
110
+ let binding;
111
+ if (on === 'k8s') {
112
+ binding = { type: on, name: target };
113
+ } else if (on === 'cf') {
114
+ const [instance, key] = target.split(/:/);
115
+ binding = { type: on, instance, key };
116
+ } else {
117
+ throwError();
118
+ }
119
+
120
+ const resolvedService = await resolveBinding(undefined, binding);
121
+ resolvedService._target = target;
122
+ delete resolvedService.credentials;
123
+ resolvedService.binding.resolved = false;
124
+ return resolvedService;
99
125
  }));
100
- return resolvedServices;
101
126
  });
102
127
 
103
- if (!options.kind) {
104
- keyProvider.determineKinds(resolvedServices);
128
+ if (options.kind) {
129
+ resolvedServices.forEach( service => service.kind = options.kind );
130
+ } else {
131
+ determineKinds(resolvedServices);
105
132
  }
106
133
 
107
134
  for (const resolvedService of resolvedServices) {
108
- resolvedService.kind = options.kind || resolvedService.kind;
109
- resolvedService.name = serviceArg || keyProvider.getRequiresNameFromKind(resolvedService.binding.instance, resolvedService.kind);
135
+ if (serviceArg) {
136
+ resolvedService.name = serviceArg;
137
+ } else if (resolvedService.kind) {
138
+ resolvedService.name = getRequiresNameFromKind(resolvedService.binding.instance, resolvedService.kind);
139
+ }
140
+
141
+ if (!resolvedService.name) {
142
+ if (isJavaProject) {
143
+ const { binding } = resolvedService;
144
+ // Generate a custom service entry for Java projects w/o the need to specify a name manually
145
+ resolvedService.name = `custom-service:${binding.instance || binding.secret}`
146
+ } else {
147
+ throw new Error(`Unknown CDS service name for service "${resolvedService._target}". Please specify as argument for "cds bind".`)
148
+ }
149
+ }
150
+
151
+ const { kind, name, binding } = resolvedService;
152
+ const kindText = kind && `with kind ${highlight(kind)}`;
153
+ const nameText = !isJavaProject && highlight(name);
154
+
155
+ LOG.log(['Binding', nameText, 'to', platformText(binding), bindingText(binding, highlight), kindText].filter( t => t ).join(' '));
156
+
157
+ if (isJavaProject && !hasVcapLabel(binding) && !hasVcapLabel(env.requires[resolvedService.name]) && !(kind && hasVcapLabel(env.requires[kind]))) {
158
+ resolvedService.vcap = { label: VCAP_LABEL_PLACEHOLDER };
159
+ LOG.log(`${info('INFO:')} Please add a "vcap.label" property to the binding for consumption in CAP Java projects`);
160
+ }
110
161
  }
111
162
 
112
163
  // prepare for different providers
@@ -118,16 +169,38 @@ async function cds_bind(args, options) { // eslint-disable-line
118
169
  });
119
170
 
120
171
  LOG.log();
121
- LOG.log(`${info("TIP:")} Run with cloud bindings: ${bold(`cds watch --profile ${options.profile}`)}`);
122
- // LOG.log();
172
+ LOG.log(`${info('TIP:')} Run with cloud bindings: ${bold(runCmd(options, isJavaProject))}`);
173
+ }
174
+
175
+ async function cds_bind_exec(command) { // eslint-disable-lint
176
+ const cds = require('@sap/cds/lib');
177
+ const env = cds.env.for('cds', process.cwd());
178
+ env._emulate_vcap_services();
179
+ await require('../lib/bind/bindingResolver').applyTo({env, output: 'info'});
180
+ execAndExit(...command);
181
+ }
182
+
183
+ function hasVcapLabel(service) {
184
+ return service && service.vcap && service.vcap.label && service.vcap.label != VCAP_LABEL_PLACEHOLDER;
123
185
  }
124
186
 
125
- async function isJavaProject(projectPath) {
126
- const pomXmlPath = path.join(projectPath, 'pom.xml');
127
- try {
128
- await fsp.access(pomXmlPath, constants.F_OK);
129
- return true;
130
- } catch (err) {
131
- return false;
187
+ function runCmd({profile}, isJavaProject) {
188
+ if (isJavaProject) {
189
+ const p = profile === 'hybrid' ? '' : ` --profile ${profile}`;
190
+ return `cds bind exec${p} mvn spring-boot:run`;
191
+ } else {
192
+ const p = profile === 'development' ? '' : ` --profile ${profile}`;
193
+ return `cds watch${p}`;
132
194
  }
133
195
  }
196
+
197
+ function setProfile(profile) {
198
+ profile = parseProfile(profile);
199
+ if (profile) process.env.CDS_ENV = profile;
200
+ }
201
+
202
+ function parseProfile(profile) {
203
+ if (!profile) return 'hybrid';
204
+ else if (profile === '-') return 'development';
205
+ else return profile;
206
+ }
package/bin/cds.js CHANGED
@@ -39,7 +39,15 @@ const cli = { //NOSONAR
39
39
 
40
40
  const task = this.load (cmd, /*else*/ (f)=> argv.unshift(cmd) && _defaultCompile (argv,f))
41
41
  if (task) {
42
+ // REVISIT: '--' handling for cds bind --exec. Should be moved to cds_cli
43
+ const endOfOptions = argv.indexOf('--')
44
+ let appendArgs = []
45
+ if (endOfOptions>=0) {
46
+ appendArgs = argv.slice(endOfOptions+1)
47
+ argv = argv.slice(0, endOfOptions)
48
+ }
42
49
  const args = cds_cli.args(task,argv)
50
+ args[0].push(...appendArgs)
43
51
  return task.apply (this, args)
44
52
  }
45
53
  },
package/bin/env.js CHANGED
@@ -50,8 +50,6 @@ async function env ([cmd, key, value], options={}) {
50
50
  throw new Error (`Don't understand '${cmd}' here...`)
51
51
  })
52
52
 
53
- if (options.profile) process.env.CDS_ENV = options.profile
54
-
55
53
  const env = options.for ? cds.env.for (options.for) : cds.env
56
54
  env['_home_cds-dk'] = path.resolve(__dirname, '..') // to help tools find the DK
57
55
 
package/bin/repl.js CHANGED
@@ -31,7 +31,8 @@ function repl (_, options={}) {
31
31
  const info = colors ? require('../lib/util/term').info : s => s
32
32
  log(info('Welcome to cds repl v'+ cds.version))
33
33
 
34
- const cdsr = cds.repl = require('repl').start ({...options, writer:_writer, ignoreUndefined:true })
34
+ const cdsr = require('repl').start ({...options, writer:_writer, ignoreUndefined:true })
35
+ Object.defineProperty (cds,'repl',{value:cdsr})
35
36
 
36
37
  cds.lazify (Object.assign (cdsr.context,{ cds,
37
38
  Foo: lazy => new cds.entity ({name:'Foo'}),
@@ -39,7 +40,9 @@ function repl (_, options={}) {
39
40
  }))
40
41
 
41
42
  // cosmetics: don't show cds.env when inspecting cds
42
- cds.env && Reflect.defineProperty (cds,'env',{ value:cds.env, enumerable:false })
43
+ if (Reflect.getOwnPropertyDescriptor(cds,'env')) {
44
+ Reflect.defineProperty (cds,'env',{ value:cds.env, enumerable:false })
45
+ }
43
46
 
44
47
  inspect.defaultOptions.depth = 11
45
48
  function _writer (o) {
package/bin/watch.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const watchOnlyOptions = ['--ext', '--livereload', '--open']
2
2
  module.exports = Object.assign ( watch, {
3
- flags: [ '--debug', '--in-memory', '--in-memory?' ],
3
+ flags: [ '--debug', '--in-memory', '--in-memory?', '--with-mocks' ],
4
4
  options: [ '--port', ...watchOnlyOptions ],
5
5
  help: `
6
6
  # SYNOPSIS
@@ -55,16 +55,19 @@ const t = module.exports.codes = {
55
55
  orange: '\x1b[38;2;255;140;0m' // darker orange, works with bright and dark background
56
56
  }
57
57
  const extDefaults = 'cds,csn,csv,ts,mjs,cjs,js,json,properties,edmx,xml,env'
58
- let ignore = /node_modules|\.cds-services\.json$/
58
+ let ignore = /node_modules|target|\.cds-services\.json$/
59
59
 
60
60
  async function watch ([cwd], {
61
- args = ['serve', 'all', '--with-mocks'],
61
+ args = ['serve', 'all'],
62
62
  ext = extDefaults,
63
63
  ...options
64
64
  }={}) {
65
- args.push (`--${Object.keys(options).find(a => /^in-memory\??$/.test(a)) || 'in-memory?'}`)
66
- args.push (...process.argv.slice(3)
65
+ if (!Object.keys(options).includes('with-mocks')) args.push ('--with-mocks')
66
+ if (!Object.keys(options).find(a => /^in-memory\??$/.test(a))) args.push ('--in-memory?')
67
+ args
68
+ .push (...(process.argv.slice(3).filter(a => !args.includes(a))) // add deduped command line args
67
69
  .filter(removeArgsIn(watchOnlyOptions)) // remove all watch-only options, as `cds serve` would not accept them
70
+ .filter(a => a !== '--watch') // from `cds serve --watch`
68
71
  .filter(a => a !== cwd))
69
72
 
70
73
  let env = Object.assign (process.env, {
@@ -105,7 +108,7 @@ async function watch ([cwd], {
105
108
 
106
109
  const bindingManager = new require('../lib/bind/bindingResolver').bindingManager({cwd})
107
110
  const bindings = await bindingManager.resolve()
108
- if (bindings) env = {...env, CDS_CONFIG: JSON.stringify(bindings)}
111
+ if (bindings) env = {...env, 'CDS_REQUIRES': JSON.stringify(bindings.requires)}
109
112
 
110
113
  const nodemon = require('../lib/watch/node-watch')
111
114
  const watcher = nodemon ({
@@ -121,7 +124,7 @@ async function watch ([cwd], {
121
124
  bindingManager.update({
122
125
  onBeforeUpdate: () => watcher.stop(),
123
126
  onAfterUpdate: bindings => {
124
- watcher.setEnv('CDS_CONFIG', bindings && JSON.stringify(bindings))
127
+ watcher.setEnv('CDS_REQUIRES', bindings && JSON.stringify(bindings.requires))
125
128
  watcher.restart()
126
129
  }
127
130
  })
@@ -206,7 +209,7 @@ if (!module.parent) (async()=>{ // launched by nodemon
206
209
  case 'NO_SERVICES_FOUND': return _no_services_found(e)
207
210
  case 'MODEL_NOT_FOUND': return _no_models_found(e)
208
211
  case 'EADDRINUSE': return process.send(e) //> tell cds watch
209
- default: console.error(e)
212
+ default: { throw e } // cli error handling will catch and format
210
213
  }}
211
214
  function _no_models_found(e) { console.log (`
212
215
  No models found in ${cds.resolve(e.model||'*',false)}.
@@ -1,6 +1,8 @@
1
1
  const Config = require('@sap/cds/lib/env')
2
2
  const cds = require('@sap/cds/lib');
3
3
  const LOG = cds.log('bind');
4
+ const resolveBinding = resolver(LOG);
5
+ const { platformText, bindingText } = require('./bindingUtil');
4
6
 
5
7
  function getUnresolvedBindings({cwd = process.cwd(), env}) {
6
8
  if (!env) env = Config.for('cds', cwd)
@@ -28,7 +30,7 @@ async function resolveBindings({bindings, cwd = process.cwd(), env, output = 'in
28
30
  const cacheKey = JSON.stringify(binding)
29
31
  if (!cache[cacheKey]) {
30
32
  cache[cacheKey] = config.requires[name] = await resolveBinding(name, binding)
31
- LOG[output](`bound ${name} to Cloud Foundry service instance ${binding.instance}`)
33
+ LOG[output](`bound ${name} to ${platformText(binding)} ${bindingText(binding)}`)
32
34
  } else {
33
35
  config.requires[name] = cache[cacheKey]
34
36
  }
@@ -43,17 +45,26 @@ async function resolveBindings({bindings, cwd = process.cwd(), env, output = 'in
43
45
  }
44
46
 
45
47
  let cfKeyProvider
46
-
47
- async function resolveBinding(name, binding) {
48
- switch (binding.type) {
49
- case 'cf':
50
- if (!cfKeyProvider) {
51
- cfKeyProvider = require('./cfKeyProvider');
52
- cfKeyProvider.setLogger(LOG);
53
- }
54
- return cfKeyProvider.resolve(name, binding);
55
- default:
56
- throw new Error(`Unsupported binding type "${binding.type}" for service "${name}".`)
48
+ let kubernetesCredentialsProvider
49
+
50
+ function resolver(log) {
51
+ return function resolveBinding(name, binding) {
52
+ switch (binding.type) {
53
+ case 'cf':
54
+ if (!cfKeyProvider) {
55
+ cfKeyProvider = require('./cfKeyProvider');
56
+ cfKeyProvider.setLogger(log);
57
+ }
58
+ return cfKeyProvider.resolve(name, binding)
59
+ case 'k8s':
60
+ if (!kubernetesCredentialsProvider) {
61
+ kubernetesCredentialsProvider = require('./kubernetesCredentialsProvider')
62
+ kubernetesCredentialsProvider.setLogger(log)
63
+ }
64
+ return kubernetesCredentialsProvider.resolve(name, binding)
65
+ default:
66
+ throw new Error(`Unsupported binding type "${binding.type}" for service "${name}".`)
67
+ }
57
68
  }
58
69
  }
59
70
 
@@ -67,8 +78,8 @@ async function applyTo(options) {
67
78
  Object.assign(env.requires[name], { binding, credentials })
68
79
  }
69
80
 
70
- if (Config._emulate_vcap_services) {
71
- Config._emulate_vcap_services()
81
+ if (env._emulate_vcap_services) {
82
+ env._emulate_vcap_services()
72
83
  }
73
84
  }
74
85
 
@@ -131,6 +142,7 @@ function feature(env) {
131
142
  }
132
143
 
133
144
  module.exports = {
145
+ resolver,
134
146
  applyTo,
135
147
  bindingManager: function(options) {
136
148
  return new BindingManager(options)
@@ -0,0 +1,144 @@
1
+ const KindToRequiresNameMap = {
2
+ xsuaa: 'uaa',
3
+ hana: 'db',
4
+ destinations: 'destinations',
5
+ connectivity: 'connectivity',
6
+ 'audit-log': 'audit-log',
7
+ 'enterprise-messaging': 'messaging',
8
+ 'enterprise-messaging-amqp': 'messaging',
9
+ 'enterprise-messaging-http': 'messaging',
10
+ 'enterprise-messaging-shared': 'messaging'
11
+ }
12
+
13
+ function getRequiresNameFromKind(service, kind) {
14
+ const requiresName = KindToRequiresNameMap[kind];
15
+ if (!requiresName) throw new Error(`Unknown CDS service name for service "${service}". Please specify as argument for "cds bind".`)
16
+ return requiresName;
17
+ }
18
+
19
+ /**
20
+ * Determine kind value for services based on VCAP binding
21
+ * information in the configuration.
22
+ */
23
+ function determineKinds(services) {
24
+ const vcapServices = {}
25
+
26
+ const getIndex = service => {
27
+ const { credentials } = service;
28
+ return credentials && credentials.binding && credentials.binding.index;
29
+ }
30
+
31
+ // Build VCAP_SERVICES
32
+ for (let index = 0; index<services.length; index++) {
33
+ const service = services[index];
34
+ service.kindCandidates = [ ]
35
+ service.nameCandidates = [ ]
36
+
37
+ const { label, name, tags } = service.binding && service.binding.vcap || {};
38
+ if (!label) continue;
39
+
40
+ if (!vcapServices[label]) vcapServices[label] = [];
41
+ vcapServices[label].push({ credentials: { binding: { index } }, label, name, tags });
42
+ }
43
+
44
+ // Auto configure services based on VCAP_SERVICES
45
+ const cds = require('@sap/cds/lib');
46
+ const VCAP_SERVICES = process.env.VCAP_SERVICES;
47
+ try {
48
+ process.env.VCAP_SERVICES = JSON.stringify(vcapServices);
49
+ const env = cds.env.for('cds', '.');
50
+
51
+ // Extract "kind" from auto configured services
52
+ for (const kind of Object.keys(env.requires)) {
53
+ const requireService = env.requires[kind];
54
+ const superKind = requireService.kind;
55
+ const index = getIndex(requireService);
56
+ if (!Number.isInteger(index)) continue;
57
+
58
+ const superRequireService = env.requires[superKind];
59
+ if (superRequireService) {
60
+ const superIndex = getIndex(superRequireService);
61
+ if (index === superIndex) continue;
62
+ }
63
+
64
+ services[index].kindCandidates.push(kind);
65
+ }
66
+
67
+ // Check if one "kind" per service was found
68
+ for (const service of services) {
69
+ if (service.kindCandidates.length === 1) {
70
+ service.kind = service.kindCandidates[0];
71
+ }
72
+
73
+ delete service.kindCandidates;
74
+ }
75
+ } finally {
76
+ if (VCAP_SERVICES)
77
+ process.env.VCAP_SERVICES = VCAP_SERVICES;
78
+ else
79
+ delete process.env.VCAP_SERVICES;
80
+ }
81
+ }
82
+
83
+ function execAndExit(command, ...args) {
84
+ const { spawnSync } = require('child_process');
85
+ const result = spawnSync(command, args, { stdio: 'inherit', shell: false });
86
+ process.exit(result.status);
87
+ }
88
+
89
+ async function checkIsJavaProject(projectPath) {
90
+ const { constants, promises: fsp } = require('fs');
91
+ const path = require('path');
92
+ const pomXmlPath = path.join(projectPath, 'pom.xml');
93
+ try {
94
+ await fsp.access(pomXmlPath, constants.F_OK);
95
+ return true;
96
+ } catch (err) {
97
+ return false;
98
+ }
99
+ }
100
+
101
+
102
+ /**
103
+ *
104
+ * @param {string} msg
105
+ * @returns {never}
106
+ */
107
+ function throwError(msg = 'Internal Error') {
108
+ throw new Error(msg);
109
+ }
110
+
111
+
112
+ const PLATFORM_TYPES = {
113
+ 'cf': 'Cloud Foundry',
114
+ 'k8s': 'Kubernetes'
115
+ }
116
+
117
+ function platformText(binding) {
118
+ return PLATFORM_TYPES[binding.type] || throwError(`Unexpected binding type "${binding.type}"`);
119
+ }
120
+
121
+ function bindingText(binding, highlight = t => t) {
122
+ switch (binding.type) {
123
+ case 'cf':
124
+ return `service ${highlight(binding.instance)} key ${highlight(binding.key)}`
125
+ case 'k8s':
126
+ if (binding.name) {
127
+ return `service binding ${highlight(binding.name)}`
128
+ } else {
129
+ return `secret ${highlight(binding.secret)}`
130
+ }
131
+ default:
132
+ throwError();
133
+ }
134
+ }
135
+
136
+ module.exports = {
137
+ getRequiresNameFromKind,
138
+ determineKinds,
139
+ execAndExit,
140
+ checkIsJavaProject,
141
+ throwError,
142
+ platformText,
143
+ bindingText
144
+ }