@sap/cds-dk 4.6.4 → 4.8.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 (74) hide show
  1. package/CHANGELOG.md +68 -1
  2. package/bin/activate.js +5 -1
  3. package/bin/add.js +2 -1
  4. package/bin/bind.js +121 -48
  5. package/bin/cds.js +8 -0
  6. package/bin/env.js +0 -2
  7. package/bin/repl.js +5 -2
  8. package/bin/watch.js +10 -7
  9. package/lib/bind/bindingResolver.js +26 -14
  10. package/lib/bind/bindingUtil.js +144 -0
  11. package/lib/bind/cfKeyProvider.js +2 -80
  12. package/lib/bind/kubernetesCredentialsProvider.js +157 -0
  13. package/lib/bind/userCdsrcJsonStorageProvider.js +7 -7
  14. package/lib/client/activate.js +16 -13
  15. package/lib/client/base_command.js +5 -1
  16. package/lib/client/helper/cf_helper.js +5 -2
  17. package/lib/client/params.js +16 -2
  18. package/lib/client/settings_manager.js +15 -8
  19. package/lib/compile/openapi/csdl2openapi.js +6 -8
  20. package/lib/compile/to-xsuaa.js +0 -14
  21. package/lib/import/utils/MetadataConverter.js +6 -4
  22. package/lib/import/utils/MultiMap.js +1 -0
  23. package/lib/import/v2parser.js +150 -64
  24. package/lib/import/v4parser.js +118 -56
  25. package/lib/index.js +6 -5
  26. package/lib/init/constants.js +11 -5
  27. package/lib/init/index.js +14 -7
  28. package/lib/init/template/_merging/existences.js +161 -0
  29. package/lib/init/template/approuter/files/default-env.json +9 -0
  30. package/lib/init/template/approuter/files/mta.yaml.hbs +48 -0
  31. package/lib/init/template/approuter/files/package.json +9 -0
  32. package/lib/init/template/approuter/files/xs-app.json +8 -0
  33. package/lib/init/template/approuter/index.js +66 -0
  34. package/lib/init/template/auditlog/files/dependencies.package.json +5 -0
  35. package/lib/init/template/auditlog/files/mta.yaml.hbs +13 -0
  36. package/lib/init/template/auditlog/index.js +38 -0
  37. package/lib/init/template/cf-manifest/index.js +1 -1
  38. package/lib/init/template/data/index.js +18 -6
  39. package/lib/init/template/hana/files/common/undeploy.json +5 -0
  40. package/lib/init/template/hana/files/java/package.json +3 -0
  41. package/lib/init/template/hana/files/mta.yaml.hbs +42 -0
  42. package/lib/init/template/hana/files/nodejs/dependencies.package.json +5 -0
  43. package/lib/init/template/hana/index.js +53 -24
  44. package/lib/init/template/java/index.js +1 -5
  45. package/lib/init/template/lint/index.js +17 -1
  46. package/lib/init/template/mta/files/mta.yaml.hbs +27 -216
  47. package/lib/init/template/mta/index.js +25 -17
  48. package/lib/init/template/mtx/files/cds.package.json +24 -0
  49. package/lib/init/template/mtx/files/dependencies.package.json +5 -0
  50. package/lib/init/template/mtx/files/mta.yaml.hbs +74 -0
  51. package/lib/init/template/mtx/files/xs-security.json.hbs +23 -9
  52. package/lib/init/template/mtx/index.js +126 -29
  53. package/lib/init/template/templateBase.js +14 -0
  54. package/lib/init/template/xsuaa/files/cds.package.json +9 -0
  55. package/lib/init/template/xsuaa/files/dependencies.package.json +6 -0
  56. package/lib/init/template/xsuaa/files/mta.yaml.hbs +17 -0
  57. package/lib/init/template/xsuaa/index.js +75 -0
  58. package/lib/init/util/fsUtil.js +18 -4
  59. package/lib/init/util/logger.js +3 -1
  60. package/lib/init/util/mvnArchetypeUtil.js +3 -1
  61. package/lib/init/{template/mta → util}/projectReader.js +26 -11
  62. package/lib/init/util/templateUtil.js +267 -3
  63. package/lib/lint/Linter.js +1 -2
  64. package/lib/lint/io.js +3 -12
  65. package/lib/util/exec.d.ts +54 -0
  66. package/lib/util/exec.js +210 -0
  67. package/lib/watch/livereload-server.js +43 -31
  68. package/lib/watch/node-watch.js +46 -20
  69. package/lib/watch/open.js +1 -1
  70. package/npm-shrinkwrap.json +3382 -9106
  71. package/package.json +8 -9
  72. package/readme.md +1 -1
  73. package/lib/init/template/mtx/package/cds.package.json +0 -29
  74. package/lib/init/template/mtx/package/dependencies.package.json +0 -9
@@ -0,0 +1,17 @@
1
+ ---
2
+ modules:
3
+ - name: {{cap.appName}}-srv
4
+ type: {{cap.pLanguage}}
5
+ requires:
6
+ - name: {{cap.appName}}-uaa
7
+
8
+ resources:
9
+ - name: {{cap.appName}}-uaa{{#cap.isMultitenant}}-mt{{/cap.isMultitenant}}
10
+ type: org.cloudfoundry.managed-service
11
+ parameters:
12
+ service: xsuaa
13
+ service-plan: application
14
+ path: ./xs-security.json
15
+ config:
16
+ xsappname: {{cap.appName}}-${org}-${space}
17
+ tenant-mode: {{#cap.isMultitenant}}shared{{/cap.isMultitenant}}{{^cap.isMultitenant}}dedicated{{/cap.isMultitenant}}
@@ -0,0 +1,75 @@
1
+ const TemplateBase = require('../templateBase')
2
+ const ProjectReader = require('../../util/projectReader')
3
+ const { fse } = require('@sap/cds-foss')
4
+ const path = require('path')
5
+ const { srvNode, srvJava, xsuaa } = require('../_merging/existences');
6
+
7
+ module.exports = class XsuaaTemplate extends TemplateBase {
8
+ constructor(projectPath, generator) {
9
+ super(projectPath, generator, __dirname)
10
+ this.projectReader = new ProjectReader(this)
11
+ }
12
+
13
+ async runMTAMerging() {
14
+ const projectDescriptor = await this.projectReader.read(this.options)
15
+ const {projectPath} = this
16
+ const mtaYAMLPath = path.join(projectPath, 'mta.yaml')
17
+ if (!fse.existsSync(mtaYAMLPath)) {
18
+ console.warn(`There is no MTA file in your project. Run \`cds add mta\` to add an MTA file or create the XSUAA services yourself.`)
19
+ return
20
+ }
21
+ const srv = projectDescriptor.cap.isNodejs ? srvNode : srvJava
22
+ await this.templateUtil.mergeYAML(
23
+ mtaYAMLPath,
24
+ `${__dirname}/files/mta.yaml.hbs`,
25
+ projectDescriptor,
26
+ {
27
+ existences: [srv, xsuaa],
28
+ relationships: [{
29
+ in: srv.ref,
30
+ inKeyPath: ["requires"],
31
+ into: "name",
32
+ existences: [xsuaa.ref],
33
+ existenceKeyPath: ["name"],
34
+ }],
35
+ }
36
+ )
37
+
38
+ // Re-applying the merging part of `cds add mtx` because cds.compile.to.xsuaa
39
+ // has overwritten the existing `xs-security.json`.
40
+ // TODO: Find a generic solution for situation like these.
41
+ if (projectDescriptor.cap.isMultitenant) {
42
+ const MtxTemplate = require(`../mtx`)
43
+ const mtxTemplate = new MtxTemplate(this.projectPath, this.generator)
44
+ await mtxTemplate.runMTAMerging()
45
+ }
46
+ }
47
+
48
+ async run() {
49
+ const {projectPath} = this
50
+ const templatePath = path.join(__dirname, 'files')
51
+ const packageJSONPath = path.join(projectPath, 'package.json')
52
+ await this.templateUtil.mergeJSON(packageJSONPath, `${templatePath}/cds.package.json`)
53
+ await this.templateUtil.mergeJSON(packageJSONPath, `${templatePath}/dependencies.package.json`)
54
+ const xsSecurityPath = path.join(projectPath, 'xs-security.json')
55
+ const cds = require('../../../../lib/compile')
56
+ const xsuaa = await (async () => { try {
57
+ const models = await cds.load(`${projectPath}/srv`)
58
+ return cds.compile.to.xsuaa(models)
59
+ } catch (error) { /* ignore */ }
60
+ })() || { scopes: [], attributes: [], 'role-templates': [] }
61
+ const projectDescriptor = await this.projectReader.read(this.options)
62
+ await this.fsUtil.writeJSON(xsSecurityPath, xsuaa)
63
+
64
+ // TODO: Get rid of this by auto-creating a merging definition from
65
+ // the current xs-security.json. This should also solve role-templates
66
+ // being thrown out
67
+ if (projectDescriptor.cap.isMultitenant) {
68
+ const MtxTemplate = require(`../mtx`)
69
+ const mtxTemplate = new MtxTemplate(this.projectPath, this.generator)
70
+ await mtxTemplate.runXSSecurityMerging()
71
+ }
72
+
73
+ await this.runMTAMerging()
74
+ }
75
+ }
@@ -1,6 +1,6 @@
1
1
  const path = require('path');
2
2
 
3
- const {fse} = require('@sap/cds-foss')
3
+ const {fse, yaml} = require('@sap/cds-foss')
4
4
  const JSONC = require('../../util/jsonc');
5
5
 
6
6
  const UTF_8 = 'utf-8';
@@ -18,13 +18,17 @@ module.exports = class FsUtil {
18
18
  await fse.outputFile(filepath, content, UTF_8);
19
19
  }
20
20
 
21
- async writeJSON(filepath, object, options = { spaces: 4 }, skipFileLog = false) {
21
+ async writeJSON(filepath, object, options = { spaces: 2 }, skipFileLog = false) {
22
22
  if (!skipFileLog) {
23
23
  this._addTouchedFile(filepath);
24
24
  }
25
25
  await fse.outputJSON(filepath, object, options);
26
26
  }
27
27
 
28
+ async writeYAML(filepath, yaml, skipFileLog = false) {
29
+ await this.writeFile(filepath, String(yaml), skipFileLog);
30
+ }
31
+
28
32
  async readFile(src) {
29
33
  return await fse.readFile(src, UTF_8);
30
34
  }
@@ -34,8 +38,18 @@ module.exports = class FsUtil {
34
38
  }
35
39
 
36
40
  async readJSONC(src) {
37
- const fileConents = await this.readFile(src);
38
- return JSONC.parse(fileConents);
41
+ const fileContents = await this.readFile(src);
42
+ return JSONC.parse(fileContents);
43
+ }
44
+
45
+ async readYAML(src, projectDescriptor) {
46
+ const file = await this.readFile(src);
47
+ if (projectDescriptor) {
48
+ const Mustache = require('mustache');
49
+ const renderedFile = Mustache.render(file, projectDescriptor);
50
+ return yaml.parseDocument(renderedFile);
51
+ }
52
+ return yaml.parseDocument(file);
39
53
  }
40
54
 
41
55
  async copy(source, destination, options = {}, skipFileLog = false) {
@@ -12,7 +12,9 @@ const defaultLogger = {
12
12
  },
13
13
 
14
14
  log: (message = '') => {
15
- LOG.log(message);
15
+ if (typeof LOG.level !== 'number' || LOG.level > 0) {
16
+ console.log(message);
17
+ }
16
18
  },
17
19
 
18
20
  warn: (message = '') => {
@@ -31,7 +31,9 @@ class MvnArchetypeUtil {
31
31
 
32
32
  params.set('-B');
33
33
 
34
- defaultLogger.isDebug && params.set('-X');
34
+ if (defaultLogger.isDebug) {
35
+ params.set('-X'); params.set('-e');
36
+ }
35
37
 
36
38
  return Array.from(params, (e) => {
37
39
  let result = e[0];
@@ -1,5 +1,5 @@
1
1
  const path = require('path')
2
- const { nullLogger } = require('../../util/logger')
2
+ const { nullLogger } = require('../util/logger')
3
3
  const { BuildTaskFactory } = require('@sap/cds/bin/build')
4
4
  const P_LANGUAGE_JAVA = 'java'
5
5
  const P_LANGUAGE_NODEJS = 'nodejs'
@@ -41,11 +41,11 @@ module.exports = class ProjectReader {
41
41
  return this.template.projectPath
42
42
  }
43
43
 
44
- async read() {
44
+ async read(options) {
45
45
  const env = await this.getEnv();
46
46
  this.logger.isDebug && this.logger.debug(JSON.stringify(env, null, 1))
47
47
 
48
- const capDescriptor = await this._getCapDescriptor(env)
48
+ const capDescriptor = await this._getCapDescriptor(env, options)
49
49
  const uiDescriptor = await this._getUiDescriptor(env)
50
50
  const projectDescriptor = {
51
51
  cap: capDescriptor,
@@ -58,14 +58,14 @@ module.exports = class ProjectReader {
58
58
  return projectDescriptor
59
59
  }
60
60
 
61
- async _getCapDescriptor(env) {
61
+ async _getCapDescriptor(env, options) {
62
62
  let capDescriptor = {
63
63
  pLanguage: DEFAULT_P_LANGUAGE,
64
64
  srv: {},
65
65
  db: [],
66
66
  requires: [],
67
67
  get isMultitenant() {
68
- return env.requires.multitenancy || env.requires.db && env.requires.db.multiTenant || false
68
+ return options && options.add.has('mtx') || env.requires.multitenancy || env.requires.db && env.requires.db.multiTenant || false
69
69
  },
70
70
  get isNodejs() {
71
71
  return this.pLanguage === P_LANGUAGE_NODEJS
@@ -79,10 +79,15 @@ module.exports = class ProjectReader {
79
79
  get hasRequiresDb() {
80
80
  return this.db.length > 0
81
81
  },
82
- get needsSidecar() {
83
- // {{#if (and (gt cap.db.length 0) (or (eq cap.pLanguage 'java') (ne cap.isMultitenant true) ) ) }}
84
- return this.db.length > 0 && (this.isJava || !this.isMultitenant)
82
+ get hasHana() {
83
+ return this.requires.some(({resource}) => resource.isHana)
84
+ },
85
+ get hasXsuaa() {
86
+ return this.requires.some(({resource}) => resource.isXsuaa)
85
87
  },
88
+ get needsSidecar() {
89
+ return (options && options.add.has('hana') || this.db.length > 0) && (this.isJava || !this.isMultitenant)
90
+ }
86
91
  }
87
92
 
88
93
  // setting the effective env as it might have changed by already executed 'add' commands, e.g. cds init foo --add hana,mta
@@ -169,9 +174,19 @@ module.exports = class ProjectReader {
169
174
  appPath: null
170
175
  }
171
176
 
172
- if (await this.fsUtil.pathExists(path.join(this.projectPath, env.folders.app))) {
177
+ const absoluteAppPath = path.join(this.projectPath, env.folders.app)
178
+ if (await this.fsUtil.pathExists(absoluteAppPath)) {
173
179
  uiDescriptor.appPath = env.folders.app
174
180
  }
181
+
182
+ const packageJson = await (async () => { try {
183
+ return await this.fsUtil.readFile(absoluteAppPath+'package.json')
184
+ } catch(error) { /* ignore */ }
185
+ })()
186
+
187
+ if (packageJson && packageJson.dependencies) {
188
+ uiDescriptor.appRouterEnabled = packageJson.dependencies['@sap/approuter'] !== undefined
189
+ }
175
190
  return uiDescriptor
176
191
  }
177
192
 
@@ -366,7 +381,7 @@ module.exports = class ProjectReader {
366
381
  // use different service name to avoid conflicts when switching from single tenant to multitenant
367
382
  // otherwise we would enforce that users have to delete the db container first
368
383
  if (resource.isMultitenant) {
369
- resource.name += '-mt'
384
+ //resource.name += '-mt'
370
385
  resource.service = 'service-manager'
371
386
  resource.vcap.plan = 'container'
372
387
  } else {
@@ -459,7 +474,7 @@ module.exports = class ProjectReader {
459
474
  }))
460
475
  }
461
476
  } else {
462
- this.logger.warn(`No SAP HANA service binding defined in the requires section of the project's package.json. Database module omitted`) //NOSONAR
477
+ //this.logger.warn(`No SAP HANA service binding defined in the requires section of the project's package.json. Database module omitted`) //NOSONAR
463
478
  }
464
479
  }
465
480
 
@@ -1,5 +1,5 @@
1
1
  const path = require('path');
2
-
2
+ const YAML = require('@sap/cds-foss')('yaml');
3
3
  const Mustache = require('mustache');
4
4
 
5
5
  const TEMPLATE_EXTENSION = '.hbs';
@@ -41,7 +41,7 @@ module.exports = class TemplateUtil {
41
41
  }
42
42
 
43
43
  async _processTemplateFile(src, templateValues) {
44
- let content = await this.fsUtil.readFile(src);
44
+ const content = await this.fsUtil.readFile(src);
45
45
  return Mustache.render(content, templateValues);
46
46
  }
47
47
 
@@ -52,4 +52,268 @@ module.exports = class TemplateUtil {
52
52
  _isTemplateFile(filename) {
53
53
  return path.extname(filename) === TEMPLATE_EXTENSION;
54
54
  }
55
- }
55
+
56
+ /*
57
+ Will do a syntactic merge when `semantics` is undefined.
58
+ Syntactic merges are sufficient in some cases,
59
+ e.g merging dependencies in a package.json, as they are
60
+ just maps where you can do a key-value replacement.
61
+ */
62
+ _deepMerge(target, source) {
63
+ if (this._isObject(target) && this._isObject(source)) {
64
+ for (const key in source) {
65
+ if (this._isObject(source[key])) {
66
+ if (!target[key]) Object.assign(target, { [key]: source[key] });
67
+ if ('#overwrite' in source[key]) Object.assign(target, { [key]: source[key]["#overwrite"] })
68
+ else this._deepMerge(target[key], source[key]);
69
+ } else if (Array.isArray(source[key]) && Array.isArray(target[key])) {
70
+ const unique = array => [...new Set(array.map(JSON.stringify))].map(JSON.parse);
71
+ target[key] = unique([...source[key], ...target[key]])
72
+ } else {
73
+ Object.assign(target, { [key]: target[key] || source[key] });
74
+ }
75
+ }
76
+ } else if (Array.isArray(target) && Array.isArray(source)) {
77
+ const unique = array => [...new Set(array.map(JSON.stringify))].map(JSON.parse);
78
+ target = unique([...source, ...target])
79
+ }
80
+ return target || source;
81
+ }
82
+
83
+ async mergeJSON(targetPath, sourcePath, projectDescriptor, semantics) {
84
+ const source = await this.fsUtil.readJSON(sourcePath, projectDescriptor)
85
+
86
+ if (! await this.fsUtil.pathExists(targetPath)) {
87
+ await this.fsUtil.writeJSON(targetPath, source);
88
+ } else if (semantics) {
89
+ const target = await this.fsUtil.readJSON(targetPath)
90
+
91
+ if (!target) return // Has no JSON file
92
+
93
+ const targetYAML = await YAML.parseDocument(YAML.stringify(target))
94
+ const sourceYAML = await YAML.parseDocument(YAML.stringify(source))
95
+ const resultYAML = await this.mergeYAML(targetYAML, sourceYAML, projectDescriptor, semantics)
96
+ const result = YAML.parse(YAML.stringify(resultYAML))
97
+ await this.fsUtil.writeJSON(targetPath, result)
98
+ } else {
99
+ const target = await this.fsUtil.readJSON(targetPath);
100
+ const sourceFile = await this._processTemplateFile(sourcePath, projectDescriptor);
101
+ const source = JSON.parse(sourceFile);
102
+ const result = target && source ? this._deepMerge(target, source) : target || source;
103
+ await this.fsUtil.writeJSON(targetPath, result);
104
+ }
105
+ }
106
+
107
+ async mergeYAML(into, from, projectDescriptor, semantics) {
108
+ const target = typeof into === 'string' ? await this.fsUtil.readYAML(into) : into
109
+ const source = typeof from === 'string' ? await this.fsUtil.readYAML(from, projectDescriptor) : from
110
+
111
+ if (!target || !target.contents) return // Has no YAML file
112
+
113
+ const existenceMap = new Map()
114
+ const templateExistenceMap = new Map()
115
+
116
+ const existences = semantics.existences && semantics.existences.filter(e => e)
117
+
118
+ existences && existences.forEach(existence => {
119
+ existenceMap.set(existence.ref, undefined)
120
+ templateExistenceMap.set(existence.ref, undefined)
121
+ });
122
+
123
+ const missingRelationshipsMap = new Map()
124
+
125
+ const relationships = semantics.relationships && semantics.relationships.filter(r => r)
126
+ relationships && relationships.forEach(relationship =>
127
+ missingRelationshipsMap.set(relationship.in, undefined)
128
+ )
129
+
130
+ let collectionStack = [target.contents]
131
+
132
+ const _getProperty = (object, keyPath) => keyPath.reduce((p, n) => p && p[n], object)
133
+ const _getYAMLProperty = (object, keyPath) => keyPath.reduce((p, n) => p && p.get(n), object)
134
+
135
+ const _validateConstraints = (node, dict, index) => {
136
+ existences.forEach(existence => {
137
+ const json = JSON.parse(String(node))
138
+ const constraintsFulfilled = existence.constraints.every(constraint =>
139
+ constraint.value === _getProperty(json, constraint.comparisonKeyPath)
140
+ )
141
+ if (constraintsFulfilled) dict.set(existence.ref, { json, node, index })
142
+ })
143
+ }
144
+
145
+ function _traverseYAMLNode(node, index, actions, templateNode) {
146
+ let shifted = false
147
+ switch (node.type) {
148
+ case 'MAP':
149
+ actions && actions.visitMap && actions.visitMap(node, index)
150
+ if (collectionStack[0] && collectionStack[0].type === 'SEQ') { collectionStack.unshift(node); shifted = true}
151
+ break;
152
+ case 'SEQ':
153
+ break;
154
+ case 'PAIR':
155
+ if (node.value && node.value.items) collectionStack.unshift(node.value)
156
+ break;
157
+ case 'PLAIN':
158
+ break;
159
+ }
160
+
161
+ if (node.items) {
162
+ if ((node.type === 'SEQ' || node.type === 'FLOW_SEQ') && templateNode && templateNode.items) {
163
+ actions.mergeCollection && actions.mergeCollection(node, templateNode)
164
+ node.type === 'SEQ' && _traverseYAMLCollection(node, actions, templateNode)
165
+ } else {
166
+ _traverseYAMLCollection(node, actions, templateNode)
167
+ }
168
+ } else if (node.value && node.value.items) {
169
+ _traverseYAMLNode(node.value, index, actions, templateNode && templateNode.value)
170
+ }
171
+
172
+ if (node.value && node.value.items || shifted) {
173
+ collectionStack.shift()
174
+ }
175
+ }
176
+ _traverseYAMLNode.bind(this)
177
+
178
+ const _traverseYAMLCollection = (collection, actions, templateCollection) => {
179
+ if (!collection) return
180
+
181
+ const keyIndexMap = new Map()
182
+ const templateIndexMap = new Map()
183
+
184
+ // Map collection items to their semantic counterpart(s)
185
+ collection.items.forEach((node, i) => {
186
+ if (node.key) keyIndexMap.set(node.key.value, i)
187
+ const existence = [...existenceMap.entries()].find(([,value]) => value && value.node === node)
188
+ if (existence) {
189
+ const [existenceKey] = existence
190
+ const templateExistence = existenceKey && templateExistenceMap.get(existenceKey)
191
+ const templateIndex = templateExistence && templateExistence.index
192
+ if (templateIndex !== undefined) templateIndexMap.set(templateIndex, i)
193
+ }
194
+ if (!templateCollection) _traverseYAMLNode(node, i, actions)
195
+ });
196
+
197
+ if (!templateCollection) return
198
+
199
+ templateCollection.items.forEach((templateNode, templateIndex) => {
200
+ if (templateNode.type === 'PAIR') {
201
+ const i = keyIndexMap.get(templateNode.key.value)
202
+ const [collection] = collectionStack
203
+ const targetNode = collection.items && collection.items[i]
204
+ if (targetNode) {
205
+ _traverseYAMLNode(targetNode, templateIndex, actions, templateNode)
206
+ } else if (actions.mergePair) {
207
+ actions.mergePair(collection, templateNode)
208
+ }
209
+ } else if (templateNode.type === 'MAP') {
210
+ const targetNode = collection.items[templateIndexMap.get(templateIndex)]
211
+ if (actions.mergeCollection) actions.mergeCollection(targetNode, templateNode)
212
+ if (targetNode) {
213
+ _traverseYAMLNode(targetNode, templateIndex, actions, templateNode);
214
+ } else if (!actions.mergeCollection) {
215
+ collection.add(YAML.createNode(templateNode))
216
+ //this.fsUtil.writeYAML(targetPath, target)
217
+ }
218
+ }
219
+ })
220
+ }
221
+ _traverseYAMLCollection.bind(this)
222
+
223
+ // 1. Validate the existences in the template
224
+ _traverseYAMLNode(source.contents, null, {
225
+ visitMap: (node, index) => {
226
+ _validateConstraints(node, templateExistenceMap, index)
227
+ }
228
+ })
229
+
230
+ // 2. Validate the existences in the project
231
+ _traverseYAMLNode(target.contents, null, {
232
+ visitMap: (node, index) => {
233
+ _validateConstraints(node, existenceMap, index)
234
+ }
235
+ })
236
+
237
+ // 3. Apply overwrites to already found existences
238
+ semantics.overwrites && semantics.overwrites.forEach(({ in: ref, keyPath, replacement }) => {
239
+ const existence = existenceMap.get(ref)
240
+ if (existence) {
241
+ _getYAMLProperty(existence.node, keyPath.slice(0, keyPath.length - 1))
242
+ .set(keyPath[keyPath.length - 1], replacement)
243
+ }
244
+ })
245
+
246
+ // 4. Create missing existences and pairs
247
+ _traverseYAMLNode(target.contents, null, {
248
+ mergePair: (collection, templateNode) => {
249
+ const childIsArray = false//templateNode.value.type !== 'MAP' && !!templateNode.value.items
250
+ if (collection.type === 'MAP' && !childIsArray) {
251
+ collection.add(YAML.createNode(templateNode))
252
+ //this.fsUtil.writeYAML(targetPath, target)
253
+ }
254
+ },
255
+ mergeCollection: (targetNode, templateNode) => {
256
+ if (templateNode.type === 'SEQ' || templateNode.type === 'FLOW_SEQ') {
257
+ const [,parent] = collectionStack
258
+ const missingExistences = semantics.existences
259
+ .filter(e => e)
260
+ .filter(existence =>
261
+ parent.get(existence.keyPath[0]) === targetNode
262
+ ).filter(existence =>
263
+ !existenceMap.get(existence.ref)
264
+ )
265
+ missingExistences.forEach(existence => {
266
+ const templateExistence = YAML.createNode(templateExistenceMap.get(existence.ref).node)
267
+ templateExistence.spaceBefore = existence.spaceBefore
268
+ targetNode.add(templateExistence)
269
+ existenceMap.set(existence.ref, templateExistence)
270
+ //this.fsUtil.writeYAML(targetPath, target)
271
+ })
272
+ }
273
+ },
274
+ }, source.contents)
275
+
276
+ // 5. Re-validate the existences in the project
277
+ _traverseYAMLNode(target.contents, null, {
278
+ visitMap: (node, index) => {
279
+ _validateConstraints(node, existenceMap, index)
280
+ }
281
+ })
282
+
283
+ // 6. Create missing relationships
284
+ _traverseYAMLNode(target.contents, null, {
285
+ mergeCollection: (targetNode, templateNode) => {
286
+ if (templateNode.type === 'SEQ') {
287
+ const targetJSON = YAML.parse(String(targetNode))
288
+ const relationship = semantics.relationships && semantics.relationships
289
+ .filter(r => r)
290
+ .find(relationship => {
291
+ const existingNode = _getYAMLProperty(existenceMap.get(relationship.in).node, relationship.inKeyPath)
292
+ return targetNode === existingNode
293
+ })
294
+ if (!relationship) return
295
+
296
+ const missingPairs = relationship.existences
297
+ .filter(existenceKey =>
298
+ !targetJSON.some(item =>
299
+ _getProperty(existenceMap.get(existenceKey).json, relationship.existenceKeyPath) === item[relationship.into]
300
+ )
301
+ )
302
+ .map(existenceKey => existenceMap.get(existenceKey).node.get(relationship.into))
303
+ missingPairs.forEach(pair => {
304
+ targetNode.add(YAML.createNode({ [relationship.into]: pair }))
305
+ //await this.fsUtil.writeYAML(targetPath, target)
306
+ })
307
+ }
308
+ }
309
+ }, source.contents)
310
+
311
+ typeof into === 'string' && await this.fsUtil.writeYAML(into, target)
312
+ return target
313
+ }
314
+
315
+ _isObject(item) {
316
+ return item && typeof item === 'object' && !Array.isArray(item)
317
+ }
318
+ };
319
+
@@ -23,7 +23,6 @@ class Linter {
23
23
  this.eslintCmdOpts = {};
24
24
  this.eslintCmdOptsIndex = 0;
25
25
  this.eslintCmdFileExpr = "";
26
- this.formatter = false;
27
26
  this.isFile = false;
28
27
  this.help = "";
29
28
  this.debug = "";
@@ -141,7 +140,7 @@ class Linter {
141
140
 
142
141
  _addExtensions() {
143
142
  // Add CDS file extensions to lint
144
- const extensions = ALLOWED_FILE_EXTENSIONS.map((ext) => ext.replace('*.', '.'));
143
+ const extensions = ALLOWED_FILE_EXTENSIONS.map((ext) => path.extname(ext));
145
144
  this.eslintCmdOpts = io.mergeWithUserOpts(
146
145
  this.eslintCmdOpts,
147
146
  "ext",
package/lib/lint/io.js CHANGED
@@ -22,7 +22,7 @@ module.exports = {
22
22
  case ".eslintrc.js":
23
23
  case ".eslintrc.cjs":
24
24
  try {
25
- config = JSON.parse(configString.replace(/^module\.exports =/, ""));
25
+ config = JSONC.parse((configString.replace(/^module\.exports =/, "")));
26
26
  } catch (err) {
27
27
  config = {};
28
28
  }
@@ -33,7 +33,7 @@ module.exports = {
33
33
  break;
34
34
  case ".eslintrc.json":
35
35
  case ".eslintrc":
36
- config = JSON.parse(await fsp.readFile(configPath, "utf8"));
36
+ config = await fsUtil.readJSONC(configPath);
37
37
  break;
38
38
  case "package.json":
39
39
  config = JSON.parse(await fsp.readFile(configPath, "utf8"));
@@ -201,18 +201,9 @@ module.exports = {
201
201
  * (3) "Global" case adds configFile and removes rulePaths
202
202
  * @param {*} settingsPath VS Code settings file
203
203
  */
204
- async sanitizeVscodeSettings(projectPath, settingsPath, lintType, customRuleExample = false) {
204
+ async sanitizeVscodeSettings(projectPath, settingsPath, lintType, lintFileTypes, customRuleExample = false) {
205
205
  let settings = (await this.readVscodeSettings(settingsPath)) || {};
206
- const lintFileTypes = ["cds", "csn"];
207
206
  const rulePaths = [path.join(".eslint/rules")];
208
- const configFile = path.join(__dirname, ".eslintrc.js");
209
-
210
- function _addArrayOrString(array) {
211
- if (array.length === 1) {
212
- return array[0];
213
- }
214
- return array;
215
- }
216
207
 
217
208
  if (settings["eslint.validate"]) {
218
209
  settings["eslint.validate"] = this.mergeArrays(settings["eslint.validate"], lintFileTypes);
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Typings only for common use cases
3
+ */
4
+
5
+ interface AdditionalExecOptions {
6
+ acceptStatuses? : Array<Number> | '*',
7
+ appendEnv?: {[key: string]: string},
8
+ log?: object
9
+ }
10
+
11
+ interface SpawnReturns {
12
+ status : number,
13
+ stdout? : string,
14
+ stderr? : string
15
+ }
16
+
17
+ import { SpawnOptions, SpawnSyncOptions, SpawnSyncReturns } from "child_process";
18
+
19
+ type execTemplate<T> = (template: TemplateStringsArray, ...args : any[]) => T;
20
+
21
+ /**
22
+ * Execute commands with template strings asynchronously.
23
+ *
24
+ * @example
25
+ *
26
+ * await exec `ls -l .`
27
+ * await exec `ls -l ${'spaces are safe in expressions'}` => spawn('ls', [ '-l', 'spaces are safe in expressions' ])
28
+ * await exec `ls -l "dir ${'with spaces'}"
29
+ * await exec `ls -l ${['db', 'srv']}`
30
+ * const out = (await exec({stdio: ['inherit', 'pipe', 'inherit']}) `ls -l`).stdout
31
+ * const out = await exec({result: 'stdout}) `ls -l`
32
+ */
33
+ export function exec( template : TemplateStringsArray, ...args : any[] ) : Promise<SpawnReturns>
34
+ export function exec( options : { result: 'stdout' | 'stderr' } & SpawnOptions & AdditionalExecOptions) : execTemplate<Promise<string>>;
35
+ export function exec( options : SpawnOptions & AdditionalExecOptions) : execTemplate<Promise<SpawnReturns>>;
36
+
37
+ /**
38
+ * Execute commands with template strings synchronously.
39
+ *
40
+ * @example
41
+ *
42
+ * execSync `ls -l .`
43
+ * execSync `ls -l ${'spaces are safe in expressions'}` => spawn('ls', [ '-l', 'spaces are safe in expressions' ])
44
+ * execSync `ls -l "dir ${'with spaces'}"
45
+ * execSync `ls -l ${['db', 'srv']}`
46
+ * const out = (execSync({stdio: ['inherit', 'pipe', 'inherit']}) `ls -l`).stdout
47
+ * const out = execSync({result: 'stdout}) `ls -l`
48
+ */
49
+ export function execSync( template : TemplateStringsArray, ...args : any[] ) : SpawnSyncReturns<string>;
50
+ export function execSync( options : { result: 'stdout' | 'stderr' } & SpawnSyncOptions & AdditionalExecOptions) : execTemplate<string>;
51
+ export function execSync( options : { encoding : 'buffer' | null } & SpawnOptions & AdditionalExecOptions) : execTemplate<SpawnSyncReturns<Buffer>>;
52
+ export function execSync( options : SpawnOptions & AdditionalExecOptions) : execTemplate<SpawnSyncReturns<string>>;
53
+
54
+ export { execSync as $ };