@sap/cds-dk 4.5.4 → 4.6.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/bin/bind.js +124 -8
  3. package/bin/cds-ts.js +1 -1
  4. package/bin/cds.js +1 -0
  5. package/bin/env.js +50 -9
  6. package/bin/help.js +1 -0
  7. package/bin/import.js +34 -14
  8. package/bin/watch.js +25 -4
  9. package/lib/bind/bindingResolver.js +138 -0
  10. package/lib/bind/cfKeyProvider.js +353 -0
  11. package/lib/bind/userCdsrcJsonStorageProvider.js +144 -0
  12. package/lib/client/auth_manager.js +4 -4
  13. package/lib/client/settings_manager.js +1 -1
  14. package/lib/import/common.js +10 -1
  15. package/lib/import/importapi.js +4 -3
  16. package/lib/import/index.js +34 -5
  17. package/lib/import/message.js +1 -0
  18. package/lib/import/utils/MetadataConverter.js +25 -4
  19. package/lib/import/utils/MetadataConverterFactory.js +1 -2
  20. package/lib/import/utils/MultiMap.js +26 -0
  21. package/lib/import/v2parser.js +131 -12
  22. package/lib/import/v4parser.js +212 -172
  23. package/lib/init/template/lint/files/.eslint/rules/no-entity-moo.js +36 -52
  24. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/rule.test.js +15 -0
  25. package/lib/init/template/lint/index.js +14 -6
  26. package/lib/init/template/mta/files/mta.yaml.hbs +0 -7
  27. package/lib/init/template/mtx/files/xs-security.json.hbs +3 -1
  28. package/lib/init/template/mtx/package/dependencies.package.json +1 -1
  29. package/lib/init/template/nodejs/files/.gitignore.hbs +3 -0
  30. package/lib/init/util/commandUtil.js +5 -0
  31. package/lib/lint/Linter.js +31 -20
  32. package/lib/lint/checks.js +5 -1
  33. package/lib/lint/io.js +59 -35
  34. package/lib/util/term.js +2 -0
  35. package/lib/watch/node-watch.js +38 -19
  36. package/npm-shrinkwrap.json +1782 -1138
  37. package/package.json +3 -3
  38. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/test.js +0 -10
package/CHANGELOG.md CHANGED
@@ -6,6 +6,25 @@ 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.6.0 - 2021-11-02
10
+ ### Added
11
+
12
+ - `cds bind` binds the given service to a hana instance by storing the credentials in `.cdsrc.json` in your user home directory [beta].
13
+ - `cds import` introduced option `--include-namespaces` which imports the custom defined namespaces.
14
+ - `cds import` preserve documentation in CSN file for actions and functions from OData V4 EDMX file.
15
+ - `cds import` now supports reading values of the `as` option from cds.env
16
+
17
+ ### Fixed
18
+
19
+ - `cds import` fix for reflecting only supported datatypes in csn for actions and functions for OData V4.
20
+ - `cds import` fix for TypeError issue (#9868) during OData V4 EDMX conversion to CSN.
21
+ - `cds watch` no longer omits starting some services every other time it is restarting the process
22
+ - `cds-ts watch` now honors a local `tsconfig.json` file
23
+ - `cds import` fix for TypeError issue (#9950) during OData V2 EDMX conversion to CSN.
24
+ - `cds import` fix to generate the csn (#9950) during OData V2 EDMX conversion to CSN.
25
+ - `cds import` fix to replace the aliases with original schema namespace value.
26
+ - `cds import` adds `@cds.persistence.skip` back to imported models. Its accidental removal in 4.5.0 caused wrong DB deployments of imported entities.
27
+
9
28
  ## Version 4.5.4 - 2021-10-07
10
29
 
11
30
  ### Fixed
@@ -64,6 +83,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
64
83
  + no longer skips over entities marked with `@cds.persistence.skip`, which is true for 'external' entities created by `cds import`.
65
84
  - `cds init --add` handles comma list correctly.
66
85
  - `cds import` fix for multi-line documentation text in OData V4 EDMX file.
86
+ - `cds import` fix preserves the missing entities in csn for OData V2 EDMX file.
67
87
 
68
88
 
69
89
  ## Version 4.4.2 - 2021-09-03
package/bin/bind.js CHANGED
@@ -1,17 +1,133 @@
1
- module.exports = Object.assign (
2
- cds_bind, {help: `
1
+
2
+
3
+ module.exports = Object.assign(cds_bind, {
4
+ options: ['--to', '--profile', '--kind', '--output-file'],
5
+ flags: [],
6
+ shortcuts: ['-2', '-p', '-k', '-o'],
7
+ help: `
3
8
  # SYNOPSIS
4
9
 
5
- *cds bind* <service> [<options>]
10
+ *cds bind* <service> [<options>] -- beta feature
6
11
 
7
- Binds the given service to
12
+ Binds the given service to a service instance by storing connection information in *.cdsrc-private.json*
13
+ in your project directory. Credentials are not stored in the file but rather retrieved dynamically during *cds watch*.
14
+ Use option *--output-file* to specify a different path or file.
15
+
16
+ Currently *cds bind* only supports Node.js projects.
8
17
 
9
18
  # OPTIONS
10
19
 
11
- *-2|--to* <credentials>
20
+ *-2 | --to* <instance>[:<key>]
21
+ bind to a given instance
22
+
23
+ *-p | --profile* <profile>
24
+ add the given profile, defaults to *hybrid*
25
+
26
+ *-k | --kind* <kind>
27
+ the kind of service
28
+
29
+ *-o | --output-file <path>*
30
+ save bindings to the given *.cdsrc.json* or *package.json* file. Default is *.cdsrc-private.json*.
31
+ If *path* is a directory, then it will save it to the *.cdsrc.json* file in that directory.
32
+
33
+ # EXAMPLE
34
+
35
+ cds bind --to my-hdi-container
36
+ cds bind --to my-hdi-container,my-xsuaa
37
+ cds bind uaa --to my-xsuaa:my-xsuaa-key --kind xsuaa --profile myprofile
38
+
39
+ cds bind --to my-hdi-container --output-file ~
40
+ cds bind --to my-hdi-container --output-file package.json
41
+
42
+ `});
43
+
44
+ const { constants, promises: fsp } = require('fs');
45
+ const path = require('path');
46
+
47
+ const cds = require('@sap/cds/lib');
48
+ const LOG = cds.log('bind');
49
+ // required for spinner
50
+ LOG.isDebug = LOG._debug;
51
+ LOG.isTTY = process.stdout.isTTY;
52
+
53
+ const { bold, info } = require('../lib/util/term');
54
+
55
+ const Spinner = require('../lib/init/util/spinner');
56
+
12
57
 
13
- `})
58
+ async function cds_bind(args, options) { // eslint-disable-line
59
+
60
+ options.profile = options.profile || 'hybrid';
61
+
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.`);
64
+ }
65
+
66
+ if (!options.to) {
67
+ throw new Error(`use option --to or -2 to specify the target instance, e.g. cds bind --to myInstance:myService`);
68
+ }
69
+
70
+ if (args.length > 1) {
71
+ throw new Error(`Too many arguments: Please specify only one or no service.`);
72
+ }
73
+
74
+ let serviceArg = args[0]
75
+ const targets = options.to.split(/,/g);
76
+
77
+ if (targets.length >= 2 && serviceArg) {
78
+ throw new Error(`Service argument cannot be specified together with multiple targets ('--to') services. Use one service per call or omit the service argument.`);
79
+ }
80
+
81
+ if (targets.length >= 2 && options.kind) {
82
+ 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
+ }
84
+
85
+ // prepare for different providers
86
+ const keyProvider = require('../lib/bind/cfKeyProvider');
87
+ keyProvider.setLogger(LOG);
88
+ 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);
99
+ }));
100
+ return resolvedServices;
101
+ });
102
+
103
+ if (!options.kind) {
104
+ keyProvider.determineKinds(resolvedServices);
105
+ }
106
+
107
+ for (const resolvedService of resolvedServices) {
108
+ resolvedService.kind = options.kind || resolvedService.kind;
109
+ resolvedService.name = serviceArg || keyProvider.getRequiresNameFromKind(resolvedService.binding.instance, resolvedService.kind);
110
+ }
111
+
112
+ // prepare for different providers
113
+ const userCdsrcJsonStorageProvider = require('../lib/bind/userCdsrcJsonStorageProvider');
114
+
115
+ await userCdsrcJsonStorageProvider.storeServices(resolvedServices, {
116
+ ...options,
117
+ logger: LOG
118
+ });
119
+
120
+ LOG.log();
121
+ LOG.log(`${info("TIP:")} Run with cloud bindings: ${bold(`cds watch --profile ${options.profile}`)}`);
122
+ // LOG.log();
123
+ }
14
124
 
15
- function cds_bind (projects, options) { // eslint-disable-line
16
- console.log (`cds bind is not yet released`)
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;
132
+ }
17
133
  }
package/bin/cds-ts.js CHANGED
@@ -1,2 +1,2 @@
1
- #!/usr/bin/env ts-node
1
+ #!/usr/bin/env ts-node-cwd
2
2
  require('./cds').apply(process.argv)
package/bin/cds.js CHANGED
@@ -5,6 +5,7 @@ const cli = { //NOSONAR
5
5
  Shortcuts: {
6
6
  i: 'init',
7
7
  a: 'add',
8
+ y: 'bind',
8
9
  m: 'import',
9
10
  c: 'compile',
10
11
  s: 'serve',
package/bin/env.js CHANGED
@@ -1,5 +1,5 @@
1
1
  module.exports = Object.assign (env, {
2
- options: ['--for', '--mocked'], flags: [], shortcuts: ['-4'],
2
+ options: ['--for', '--mocked'], flags: ['--process-env', '--resolve-bindings'], shortcuts: ['-4', undefined, '-P', '-b'],
3
3
  help: `
4
4
  # SYNOPSIS
5
5
 
@@ -11,13 +11,29 @@ module.exports = Object.assign (env, {
11
11
 
12
12
  # EXPLANATION
13
13
 
14
- Without any arguments, this will display the effective configuration in
15
- the current environment in JavaScript object format. <ls | list> displays
16
- it in _.properties_ format.
14
+ Lists the effective configuration of the current environment in
15
+ _.properties_ format if no command, *ls*, or *list* is given.
17
16
 
18
- The other variants either get or set a single option or display the sources
19
- from which the effective configuration has been loaded.
17
+ The other commands either get or set a single property or display
18
+ the sources from which the effective configuration has been loaded.
20
19
 
20
+ # OPTIONS
21
+
22
+ *-4* | *--for* <path>
23
+
24
+ Load configuration from path instead of the current working directory.
25
+
26
+ *--profile* <profile,...>
27
+
28
+ Load configuration for the specified profile(s).
29
+
30
+ *-P* | *--process-env*
31
+
32
+ Show properties from Node.js _process.env_.
33
+
34
+ *-b* | *--resolve-bindings*
35
+
36
+ Resolve _cds bind_ bindings.
21
37
  `})
22
38
 
23
39
  const path = require('path')
@@ -29,15 +45,20 @@ const info = (s) => process.stdout.isTTY ? term.info (s) : s
29
45
  async function env ([cmd, key, value], options={}) {
30
46
 
31
47
  const cds = require('@sap/cds/lib')
32
- const fn = commands[cmd] || (()=>{
48
+ const fn = (options["process-env"] ? processEnvCommands[cmd] : commands[cmd]) || (()=>{
33
49
  this.help ('env')
34
50
  throw new Error (`Don't understand '${cmd}' here...`)
35
51
  })
36
52
 
53
+ if (options.profile) process.env.CDS_ENV = options.profile
54
+
37
55
  const env = options.for ? cds.env.for (options.for) : cds.env
38
56
  env['_home_cds-dk'] = path.resolve(__dirname, '..') // to help tools find the DK
39
57
 
58
+ if (options["resolve-bindings"]) await require('../lib/bind/bindingResolver').applyTo({env, output: "debug"})
59
+
40
60
  if (!options.for && options.mocked) await cds.service.bindings
61
+
41
62
  fn (env, key, value)
42
63
  }
43
64
 
@@ -69,19 +90,39 @@ const commands = {
69
90
  if (!process.stdout.isTTY) return commands.json (conf, key, val)
70
91
  // tty: display formatted object
71
92
  const obj = (key ? conf.get(key) : conf)
72
- console.log (inspect (obj, {depth:22,colors:true}))
93
+ console.log (inspect (obj, {depth:22,colors:true}).replace(/\s+use:.*\[Getter\].*/g,''))
73
94
  },
74
95
 
75
96
  // eslint-disable-next-line no-unused-vars
76
97
  json (conf, key, val) {
77
98
  const obj = (key ? conf.get(key) : conf)
78
- return console.log (JSON.stringify(obj, null, 2))
99
+ const str = JSON.stringify(obj, null, 2)
100
+ return console.log (str ? str.replace(/\s+use:.*\[Getter\].*/g,'') : str)
79
101
  },
80
102
 
81
103
  set () {
82
104
  throw new Error ('cds config set is not yet implemented, sorry')
105
+ }
106
+ }
107
+
108
+ const processEnvCommands = {
109
+ get [undefined]() { return processEnvCommands.list },
110
+ get ''() { return processEnvCommands.list },
111
+ get ls() { return process.list },
112
+
113
+ list (_, key) {
114
+ if (key) {
115
+ processEnvCommands.get(undefined, key)
116
+ } else {
117
+ for (const name in process.env) {
118
+ console.log(`${name}=${process.env[name]}`)
119
+ }
120
+ }
83
121
  },
84
122
 
123
+ get (_, key) {
124
+ console.log (process.env[key] || "")
125
+ }
85
126
  }
86
127
 
87
128
  /* eslint no-console:0 */
package/bin/help.js CHANGED
@@ -10,6 +10,7 @@ module.exports = exports = Object.assign (help, {help:`
10
10
 
11
11
  *i* | *init* jump-start cds-based projects
12
12
  *a* | *add* add a feature to an existing project
13
+ *y* | *bind* [beta] bind a service to a hana instance
13
14
  *m* | *import* add models from external sources
14
15
  *c* | *compile* compile cds models to different outputs
15
16
  *s* | *serve* run your services in local server
package/bin/import.js CHANGED
@@ -1,9 +1,9 @@
1
1
 
2
2
  module.exports = Object.assign(_import, {
3
3
  _name: 'import',
4
- options: ['--out', '--into', '--as'],
4
+ options: ['--out', '--into', '--as', '--include-namespaces'],
5
5
  flags: ['--dry', '--force', '--no-copy', '--no-save', '--keep-namespace'],
6
- shortcuts: ['-o', '-in', '-as', '-', '-f'],
6
+ shortcuts: ['-o', '-in', '-as', '-ns', '-', '-f'],
7
7
  help: `
8
8
  # SYNOPSIS
9
9
 
@@ -42,7 +42,10 @@ module.exports = Object.assign(_import, {
42
42
  Converts the input edmx or xml file into one of these supported formats "cds", "csn", "json"
43
43
 
44
44
  *-f | --force*
45
- Forcefully overwrites the contents in the CDS file and updates the checksum
45
+ Forcefully overwrites the contents in the CDS file and updates the checksum
46
+
47
+ *-ns | --include-namespaces <string_of_namespaces> or* *
48
+ Imports only entities matching to the given list of namespaces.
46
49
 
47
50
 
48
51
 
@@ -52,6 +55,8 @@ module.exports = Object.assign(_import, {
52
55
  cds import ~/Downloads/API_BUSINESS_PARTNER.edmx
53
56
  cds import - ~/Downloads/API_BUSINESS_PARTNER.edmx
54
57
  cds import ~/Downloads/API_BUSINESS_PARTNER.edmx --as cds
58
+ cds import ~/Downloads/API_BUSINESS_PARTNER.edmx --include-namespaces "sap,c4c"
59
+ cds import ~/Downloads/API_BUSINESS_PARTNER.edmx --include-namespaces "*"
55
60
 
56
61
  `})
57
62
  const cds = require('@sap/cds/lib')
@@ -62,12 +67,19 @@ const common = require('../lib/import/common');
62
67
  const cdsCompiler = require('../lib/compile').compile.to;
63
68
  const md5 = require('md5');
64
69
  let cwd;
65
- async function _import(file, { out, dry, into, as,
70
+ async function _import(file, { out, dry, into, as,
71
+ 'include-namespaces': include_namespaces,
66
72
  'no-save': no_save,
67
73
  'no-copy': no_copy,
68
74
  'keep-namespace': keep_namespace,
69
75
  force
70
76
  } = {}) {
77
+ if (!as) {
78
+ if (cds.env.import) as = cds.env.import.as;
79
+ }
80
+ if (!include_namespaces) {
81
+ if(cds.env.import) include_namespaces = cds.env.import.include_namespaces;
82
+ }
71
83
  cwd = into || process.cwd();
72
84
  if (as && !common.supportedFormats().includes(as))
73
85
  throw new Error(messages.INVALID_AS_OPTION);
@@ -78,6 +90,14 @@ async function _import(file, { out, dry, into, as,
78
90
  const dst = path.join(external, path.basename(file))
79
91
  if (!dry && !no_copy) file = await _copy_to_srv_external(file, dst)
80
92
  const src = await edm2csn.getMetadataFromFile(file)
93
+ this.include_all_namespaces = false;
94
+ let namespaces = [];
95
+ if(include_namespaces) {
96
+ if(include_namespaces === "*") this.include_all_namespaces = true;
97
+ else if(typeof include_namespaces === 'string' && !include_namespaces.startsWith('-')) namespaces = include_namespaces.replace(/ /g, "").split(',');
98
+ else throw new Error(messages.INCORRECT_INPUT_NAMESPACES);
99
+ }
100
+ this.namespaces = namespaces;
81
101
  let csn = await edm2csn.edmx2csn(src, !keep_namespace && file, this)
82
102
  const service = _service_name_from(csn);
83
103
  if (out) out = edm2csn.validateAndComputeOutputFilePath(out, as);
@@ -96,23 +116,23 @@ async function _generateChecksumValidate(dest, output, force) {
96
116
  return output;
97
117
  }
98
118
  else {
99
- let fileContent = await read(dest, "utf-8");
100
- fileContent = String(fileContent);
101
- if (fileContent.indexOf("checksum") === -1){
102
- let checksum = md5(fileContent);
119
+ let existingFileContent = await read(dest, "utf-8");
120
+ existingFileContent = String(existingFileContent);
121
+ if (existingFileContent.indexOf("checksum") === -1){
122
+ let checksum = md5(existingFileContent);
103
123
  output = "/* checksum : " + checksum + " */\n" + output;
104
124
  return output;
105
125
  }
106
- let fileChecksum = fileContent.substring(fileContent.indexOf("checksum") + 11, fileContent.indexOf(" */"));
107
- let newFile = fileContent.substring(fileContent.indexOf('*/') + 3);
108
- let checksum = md5(newFile);
126
+ let fileChecksum = existingFileContent.substring(existingFileContent.indexOf("checksum") + 11, existingFileContent.indexOf(" */"));
127
+ existingFileContent = existingFileContent.substring(existingFileContent.indexOf('*/') + 3);
128
+ let checksum = md5(existingFileContent);
109
129
  if (fileChecksum === checksum) {
110
- output = "/* checksum : " + checksum + " */\n" + output;
130
+ output = "/* checksum : " + checksum + " */\n" + existingFileContent;
111
131
  return output;
112
132
  }
113
133
  else {
114
134
  if (force) {
115
- output = "/* checksum : " + checksum + " */\n" + newFile;
135
+ output = "/* checksum : " + md5(output) + " */\n" + output;
116
136
  return output;
117
137
  }
118
138
  else {
@@ -195,4 +215,4 @@ async function _update_package_json(dest, service, version) {
195
215
  await write(package_json, JSON.stringify(conf, null, ' '))
196
216
  console.log(`[cds] - updated ./package.json`)
197
217
  }
198
- }
218
+ }
package/bin/watch.js CHANGED
@@ -67,7 +67,7 @@ async function watch ([cwd], {
67
67
  .filter(removeArgsIn(watchOnlyOptions)) // remove all watch-only options, as `cds serve` would not accept them
68
68
  .filter(a => a !== cwd))
69
69
 
70
- const env = Object.assign (process.env, {
70
+ let env = Object.assign (process.env, {
71
71
  _args: JSON.stringify(args),
72
72
  _options: JSON.stringify(options)
73
73
  })
@@ -100,8 +100,12 @@ async function watch ([cwd], {
100
100
  log (`watching: ${ext}...`)
101
101
  if (liveReload) log (`live reload enabled for browsers`)
102
102
 
103
+ const bindingManager = new require('../lib/bind/bindingResolver').bindingManager({cwd})
104
+ const bindings = await bindingManager.resolve()
105
+ if (bindings) env = {...env, CDS_CONFIG: JSON.stringify(bindings)}
106
+
103
107
  const nodemon = require('../lib/watch/node-watch')
104
- return nodemon ({
108
+ const watcher = nodemon ({
105
109
  watch: ['*','.env','.cdsrc.json'],
106
110
  script:__filename, cwd, env, ext, ignore,
107
111
  delay: parseInt(process.env.CDS_WATCH_DELAY) || 200,
@@ -111,12 +115,18 @@ async function watch ([cwd], {
111
115
  if (liveReload) liveReload.markPending(files)
112
116
  if (this.delayed) clearTimeout (this.delayed)
113
117
  this.delayed = setTimeout(()=>{
118
+ bindingManager.update({
119
+ onBeforeUpdate: () => watcher.stop(),
120
+ onAfterUpdate: bindings => {
121
+ watcher.setEnv('CDS_CONFIG', bindings && JSON.stringify(bindings))
122
+ watcher.restart()
123
+ }
124
+ })
114
125
  log (`${t.bold} _______________________\n`)
115
126
  FileHandlers.changed (files,cwd)
116
127
  }, 111)
117
128
  }).on('quit', ()=>{
118
129
  log (`${t.bold+t.green}\n[cds] - my watch has ended.\n`)
119
- process.exit()
120
130
  }).on('message', async (msg) => { switch (msg.code) { // messages from child process
121
131
  case 'listening':
122
132
  env.PORT = (msg.address && msg.address.port) || msg.port
@@ -127,6 +137,8 @@ async function watch ([cwd], {
127
137
  case 'EADDRINUSE': env.PORT = 0; _addr_in_use(msg); break
128
138
  default: console.error (msg)
129
139
  }})
140
+
141
+ return watcher
130
142
  }
131
143
 
132
144
 
@@ -168,8 +180,17 @@ if (!module.parent) (async()=>{ // launched by nodemon
168
180
  // run through cli in order to use its error handling
169
181
  const bootstrap = require('./cds')(...JSON.parse(process.env._args))
170
182
  const {cds} = global
183
+
184
+ cds.once('listening', ({server, url}) => {
185
+ process.send ({ code:'listening', port: server.address().port })
186
+ // Need to give bindings an explicit call to purge,
187
+ // as its .exit handler has no chance to execute on Windows.
188
+ process.on ('message', (msg)=> { if (msg && msg.exit) { // see node-watch
189
+ (new cds.service.bindings(url)).purge()
190
+ }})
191
+ })
192
+
171
193
  try {
172
- cds.once('listening', ({server}) => process.send ({ code:'listening', port: server.address().port }))
173
194
  await bootstrap
174
195
  if (!cds.service.providers.length) return _no_services_found()
175
196
  } catch (e) { switch (e.code) {
@@ -0,0 +1,138 @@
1
+ const Config = require('@sap/cds/lib/env')
2
+ const cds = require('@sap/cds/lib');
3
+ const LOG = cds.log('bind');
4
+
5
+ function getUnresolvedBindings({cwd = process.cwd(), env}) {
6
+ if (!env) env = Config.for('cds', cwd)
7
+ if (feature(env) === false || feature(env) === "false") return {}
8
+
9
+ const requires = env.requires || {}
10
+ let bindings = {}
11
+ for (const name in requires) {
12
+ const service = requires[name]
13
+ if (service && service.binding && !service.binding.resolved) {
14
+ bindings[name] = {...service.binding, kind: service.kind }
15
+ }
16
+ }
17
+
18
+ return bindings
19
+ }
20
+
21
+ async function resolveBindings({bindings, cwd = process.cwd(), env, output = 'info', cache = {} }) {
22
+ const config = { requires: { } }
23
+ bindings = bindings || getUnresolvedBindings({cwd, env})
24
+ if (Object.keys(bindings).length===0) return config;
25
+ LOG[output](`resolving cloud service bindings...`)
26
+ await Promise.all( Object.keys(bindings).map( async name => {
27
+ const binding = bindings[name]
28
+ const cacheKey = JSON.stringify(binding)
29
+ if (!cache[cacheKey]) {
30
+ cache[cacheKey] = config.requires[name] = await resolveBinding(name, binding)
31
+ LOG[output](`bound ${name} to Cloud Foundry service instance ${binding.instance}`)
32
+ } else {
33
+ config.requires[name] = cache[cacheKey]
34
+ }
35
+ } ));
36
+
37
+ // Add kind to result, so that it can be applied to old @sap/cds versions that do not read `.cdsrc-private.json`
38
+ for (const name in config.requires) {
39
+ if (bindings[name].kind) config.requires[name].kind = bindings[name].kind
40
+ }
41
+
42
+ return config
43
+ }
44
+
45
+ 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}".`)
57
+ }
58
+ }
59
+
60
+ async function applyTo(options) {
61
+ const config = await resolveBindings(options)
62
+ if (!config) return
63
+ const {env} = options
64
+ for (const name in config.requires) {
65
+ const service = config.requires[name]
66
+ const { binding, credentials } = service
67
+ Object.assign(env.requires[name], { binding, credentials })
68
+ }
69
+
70
+ if (Config._emulate_vcap_services) {
71
+ Config._emulate_vcap_services()
72
+ }
73
+ }
74
+
75
+ class BindingManager {
76
+ constructor(options) {
77
+ this.cache = {}
78
+ this.bindingsStr = ''
79
+ this.options = options
80
+ }
81
+
82
+ getBindings() {
83
+ const bindings = getUnresolvedBindings(this.options)
84
+ return bindings
85
+ }
86
+
87
+ async resolve(bindings) {
88
+ if (!bindings) {
89
+ bindings = this.getBindings(this.options)
90
+ this.bindingsStr = JSON.stringify(bindings)
91
+ }
92
+
93
+ const config = await resolveBindings({...this.options, bindings, cache: this.cache})
94
+ if (config && Object.keys(config.requires || {}).length > 0) return config;
95
+ }
96
+
97
+ async update({ onBeforeUpdate, onAfterUpdate } = {}) {
98
+ const oldBindingsStr = this.bindingsStr
99
+ const bindings = this.getBindings()
100
+ const bindingsStr = JSON.stringify(bindings)
101
+ this.bindingsStr = bindingsStr
102
+
103
+ if (this.bindingsStr === oldBindingsStr) return // No change
104
+ if (onBeforeUpdate) await onBeforeUpdate()
105
+
106
+ try {
107
+ if (this.pendingUpdate) await this.pendingUpdate
108
+ } catch(error) {
109
+ // ignore
110
+ }
111
+ this.pendingUpdate = undefined
112
+
113
+ if (this.bindingsStr !== bindingsStr) return // Next update in progress
114
+
115
+ let resolvedBindings
116
+ try {
117
+ this.pendingUpdate = this.resolve(bindings)
118
+ resolvedBindings = await this.pendingUpdate
119
+ } catch(error) {
120
+ LOG.error(error.message)
121
+ return
122
+ }
123
+
124
+ if (this.bindingsStr !== bindingsStr) return // Next update in progress
125
+ if (onAfterUpdate) onAfterUpdate(resolvedBindings)
126
+ }
127
+ }
128
+
129
+ function feature(env) {
130
+ return env.features && env.features.dk && env.features.dk.resolveBindingsForTesting
131
+ }
132
+
133
+ module.exports = {
134
+ applyTo,
135
+ bindingManager: function(options) {
136
+ return new BindingManager(options)
137
+ }
138
+ }