@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.
- package/CHANGELOG.md +68 -1
- package/bin/activate.js +5 -1
- package/bin/add.js +2 -1
- package/bin/bind.js +121 -48
- package/bin/cds.js +8 -0
- package/bin/env.js +0 -2
- package/bin/repl.js +5 -2
- package/bin/watch.js +10 -7
- package/lib/bind/bindingResolver.js +26 -14
- package/lib/bind/bindingUtil.js +144 -0
- package/lib/bind/cfKeyProvider.js +2 -80
- package/lib/bind/kubernetesCredentialsProvider.js +157 -0
- package/lib/bind/userCdsrcJsonStorageProvider.js +7 -7
- package/lib/client/activate.js +16 -13
- package/lib/client/base_command.js +5 -1
- package/lib/client/helper/cf_helper.js +5 -2
- package/lib/client/params.js +16 -2
- package/lib/client/settings_manager.js +15 -8
- package/lib/compile/openapi/csdl2openapi.js +6 -8
- package/lib/compile/to-xsuaa.js +0 -14
- package/lib/import/utils/MetadataConverter.js +6 -4
- package/lib/import/utils/MultiMap.js +1 -0
- package/lib/import/v2parser.js +150 -64
- package/lib/import/v4parser.js +118 -56
- package/lib/index.js +6 -5
- package/lib/init/constants.js +11 -5
- package/lib/init/index.js +14 -7
- package/lib/init/template/_merging/existences.js +161 -0
- package/lib/init/template/approuter/files/default-env.json +9 -0
- package/lib/init/template/approuter/files/mta.yaml.hbs +48 -0
- package/lib/init/template/approuter/files/package.json +9 -0
- package/lib/init/template/approuter/files/xs-app.json +8 -0
- package/lib/init/template/approuter/index.js +66 -0
- package/lib/init/template/auditlog/files/dependencies.package.json +5 -0
- package/lib/init/template/auditlog/files/mta.yaml.hbs +13 -0
- package/lib/init/template/auditlog/index.js +38 -0
- package/lib/init/template/cf-manifest/index.js +1 -1
- package/lib/init/template/data/index.js +18 -6
- package/lib/init/template/hana/files/common/undeploy.json +5 -0
- package/lib/init/template/hana/files/java/package.json +3 -0
- package/lib/init/template/hana/files/mta.yaml.hbs +42 -0
- package/lib/init/template/hana/files/nodejs/dependencies.package.json +5 -0
- package/lib/init/template/hana/index.js +53 -24
- package/lib/init/template/java/index.js +1 -5
- package/lib/init/template/lint/index.js +17 -1
- package/lib/init/template/mta/files/mta.yaml.hbs +27 -216
- package/lib/init/template/mta/index.js +25 -17
- package/lib/init/template/mtx/files/cds.package.json +24 -0
- package/lib/init/template/mtx/files/dependencies.package.json +5 -0
- package/lib/init/template/mtx/files/mta.yaml.hbs +74 -0
- package/lib/init/template/mtx/files/xs-security.json.hbs +23 -9
- package/lib/init/template/mtx/index.js +126 -29
- package/lib/init/template/templateBase.js +14 -0
- package/lib/init/template/xsuaa/files/cds.package.json +9 -0
- package/lib/init/template/xsuaa/files/dependencies.package.json +6 -0
- package/lib/init/template/xsuaa/files/mta.yaml.hbs +17 -0
- package/lib/init/template/xsuaa/index.js +75 -0
- package/lib/init/util/fsUtil.js +18 -4
- package/lib/init/util/logger.js +3 -1
- package/lib/init/util/mvnArchetypeUtil.js +3 -1
- package/lib/init/{template/mta → util}/projectReader.js +26 -11
- package/lib/init/util/templateUtil.js +267 -3
- package/lib/lint/Linter.js +1 -2
- package/lib/lint/io.js +3 -12
- package/lib/util/exec.d.ts +54 -0
- package/lib/util/exec.js +210 -0
- package/lib/watch/livereload-server.js +43 -31
- package/lib/watch/node-watch.js +46 -20
- package/lib/watch/open.js +1 -1
- package/npm-shrinkwrap.json +3382 -9106
- package/package.json +8 -9
- package/readme.md +1 -1
- package/lib/init/template/mtx/package/cds.package.json +0 -29
- 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
|
+
}
|
package/lib/init/util/fsUtil.js
CHANGED
|
@@ -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:
|
|
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
|
|
38
|
-
return JSONC.parse(
|
|
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) {
|
package/lib/init/util/logger.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
-
const { nullLogger } = require('
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
|
package/lib/lint/Linter.js
CHANGED
|
@@ -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) =>
|
|
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 =
|
|
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 =
|
|
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 $ };
|