@sap/cds-dk 8.9.4 → 9.0.4
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 +89 -2012
- package/LICENSE +15 -21
- package/bin/bind.js +1 -1
- package/bin/build.js +35 -37
- package/bin/cds.js +12 -29
- package/bin/compile/index.js +2 -2
- package/bin/debug.js +27 -7
- package/bin/deploy/index.js +6 -23
- package/bin/deploy/to-hana.js +4 -2
- package/bin/help.js +3 -1
- package/bin/init.js +1 -1
- package/bin/up.js +45 -12
- package/bin/watch.js +45 -63
- package/lib/bind/cf.js +2 -1
- package/lib/bind/k8s.js +2 -2
- package/lib/bind/plugin.js +1 -1
- package/lib/bind/shared.js +16 -25
- package/lib/build/buildTaskEngine.js +26 -10
- package/lib/build/buildTaskFactory.js +3 -2
- package/lib/build/buildTaskProvider.js +18 -7
- package/lib/build/buildTaskProviderFactory.js +35 -13
- package/lib/build/constants.js +4 -0
- package/lib/build/index.js +1 -1
- package/lib/build/plugin.js +89 -54
- package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +3 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/_helpers.tpl +4 -2
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/api-rule.yaml +4 -2
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/deployment.yaml +18 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml +0 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +34 -2
- package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +9 -2
- package/lib/build/plugins/helm/index.js +2 -1
- package/lib/build/plugins.js +24 -10
- package/lib/build/provider/edmxBuildPlugin.js +1 -11
- package/lib/build/provider/hana/index.js +12 -5
- package/lib/build/provider/hana/template/package.json +1 -1
- package/lib/build/provider/internalBuildPlugin.js +1 -1
- package/lib/build/provider/internalBuildTaskProvider.js +3 -24
- package/lib/build/provider/java/index.js +1 -1
- package/lib/build/provider/mtx-extension/index.js +3 -8
- package/lib/build/provider/nodejs/index.js +28 -6
- package/lib/build/util.js +186 -125
- package/lib/deploy/index.js +5 -0
- package/lib/deploy/to-hana/hana.js +27 -33
- package/lib/deploy/to-hana/hdiDeployUtil.js +2 -2
- package/lib/import/odata/v2parser.js +34 -3
- package/lib/import/openapi/importOpenAPI.js +4 -0
- package/lib/index.js +2 -11
- package/lib/init/constants.js +0 -1
- package/lib/init/merge.js +101 -77
- package/lib/init/mvn.js +3 -3
- package/lib/init/projectReader.js +1 -1
- package/lib/init/template/ams/files/mta.yaml.hbs +6 -1
- package/lib/init/template/ams/files/package.json.hbs +1 -1
- package/lib/init/template/approuter/files/mta.yaml.hbs +5 -0
- package/lib/init/template/cf-manifest/files/manifest.yml.hbs +1 -1
- package/lib/init/template/cloud-logging/files/package.json.hbs +3 -3
- package/lib/init/template/dynatrace/files/mta.yaml.hbs +1 -1
- package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
- package/lib/init/template/dynatrace/index.js +2 -1
- package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +6 -0
- package/lib/init/template/enterprise-messaging/index.js +5 -1
- package/lib/init/template/extensibility/files/package.json.hbs +1 -1
- package/lib/init/template/hana/files/db/package.json +1 -1
- package/lib/init/template/hana/files/package.json.hbs +2 -4
- package/lib/init/template/helm/index.js +13 -22
- package/lib/init/template/helm-unified-runtime/index.js +2 -1
- package/lib/init/template/html5-repo/files/values.yaml.hbs +12 -1
- package/lib/init/template/html5-repo/index.js +10 -5
- package/lib/init/template/http/index.js +1 -1
- package/lib/init/template/ias/files/mta.yaml.hbs +1 -1
- package/lib/init/template/ias/files/sidecar.package.json.hbs +0 -7
- package/lib/init/template/ias/index.js +5 -4
- package/lib/init/template/lint/files/package.json +6 -0
- package/lib/init/template/lint/index.js +61 -204
- package/lib/init/template/mta/files/package.json +1 -1
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +2 -2
- package/lib/init/template/multitenancy/files/package.json.hbs +1 -1
- package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +7 -4
- package/lib/init/template/multitenancy/index.js +3 -0
- package/lib/init/template/nodejs/files/package.json.hbs +5 -2
- package/lib/init/template/redis/files/package.json.hbs +1 -1
- package/lib/init/template/sqlite/files/package.json +1 -1
- package/lib/init/template/toggles/files/package.json.hbs +1 -1
- package/lib/init/template/typescript/files/package.json +1 -3
- package/lib/init/template/workzone-standard/index.js +2 -0
- package/lib/init/template/xsuaa/index.js +2 -6
- package/lib/lint/Linter.js +203 -331
- package/lib/lint/generators.js +52 -46
- package/lib/lint/io.js +77 -289
- package/lib/util/fs.js +2 -1
- package/lib/util/git.js +1 -1
- package/lib/util/glob.js +77 -0
- package/lib/watch/livereload-server.js +3 -4
- package/lib/watch/node-watch.js +3 -4
- package/lib/watch/watched.js +1 -1
- package/npm-shrinkwrap.json +59 -1232
- package/package.json +6 -8
- package/readme.md +1 -1
- package/lib/lint/checks.js +0 -135
package/lib/build/util.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
const fs = require('fs')
|
|
2
3
|
const path = require('path')
|
|
3
4
|
const cds = require('../cds')
|
|
@@ -6,23 +7,33 @@ const execAsync = require('util').promisify(cp.exec)
|
|
|
6
7
|
const IS_WIN = require('os').platform() === 'win32'
|
|
7
8
|
const { SEVERITY_ERROR, FILE_EXT_CDS, NODEJS_MODEL_EXCLUDE_LIST } = require('./constants');
|
|
8
9
|
const DEBUG = cds.debug('cli|build')
|
|
9
|
-
const SECRETS = /(passw)|(cert)|(ca)|(secret)|(key)/i
|
|
10
10
|
const WS_MAX_DEPTH = 2
|
|
11
11
|
|
|
12
|
+
/** @typedef {{col?: number, endCol?: number, line?: number, endLine?: number, file: string}} SourceLocation */
|
|
13
|
+
/** @typedef {object[] | object} Credentials */
|
|
14
|
+
/** @typedef {(string | NestedStringArray)[]} NestedStringArray */
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} src - object to get property from
|
|
18
|
+
* @param {string | string[]} segments - property path
|
|
19
|
+
*/
|
|
12
20
|
function getProperty(src, segments) {
|
|
13
21
|
segments = Array.isArray(segments) ? segments : segments.split('.')
|
|
14
22
|
return segments.reduce((p, n) => p && p[n], src)
|
|
15
23
|
}
|
|
16
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} src - object to set property on
|
|
27
|
+
* @param {string | string[]} segments - property path
|
|
28
|
+
* @param {unknown} value - value to set
|
|
29
|
+
*/
|
|
17
30
|
function setProperty(src, segments, value) {
|
|
18
31
|
segments = Array.isArray(segments) ? segments : segments.split('.')
|
|
19
32
|
segments.reduce((p, n, idx) => {
|
|
20
33
|
if (segments.length === idx + 1) {
|
|
21
34
|
p[n] = value
|
|
22
|
-
} else {
|
|
23
|
-
|
|
24
|
-
p[n] = {}
|
|
25
|
-
}
|
|
35
|
+
} else if (p[n] === undefined) {
|
|
36
|
+
p[n] = {}
|
|
26
37
|
}
|
|
27
38
|
return p[n]
|
|
28
39
|
}, src)
|
|
@@ -37,8 +48,9 @@ function hasJavaNature() {
|
|
|
37
48
|
|
|
38
49
|
/**
|
|
39
50
|
* Determines whether the both values are identical.
|
|
40
|
-
* @
|
|
41
|
-
* @param {
|
|
51
|
+
* @template {number | boolean} T
|
|
52
|
+
* @param {T} actual
|
|
53
|
+
* @param {T} expected
|
|
42
54
|
*/
|
|
43
55
|
function hasOptionValue(actual, expected) {
|
|
44
56
|
if (typeof expected === 'undefined') {
|
|
@@ -52,65 +64,29 @@ function hasOptionValue(actual, expected) {
|
|
|
52
64
|
return String(expected) === actual
|
|
53
65
|
}
|
|
54
66
|
}
|
|
55
|
-
if (typeof expected === 'number') {
|
|
67
|
+
if (typeof expected === 'number' && typeof actual === 'number') {
|
|
56
68
|
return (actual & expected) !== 0
|
|
57
69
|
}
|
|
58
70
|
return actual === expected
|
|
59
71
|
}
|
|
60
72
|
|
|
61
|
-
// Returning the project relative path representation of the given path(s),
|
|
62
|
-
function relativePaths(root, qualifiedPaths) {
|
|
63
|
-
qualifiedPaths = typeof qualifiedPaths === "string" ? [qualifiedPaths] : qualifiedPaths
|
|
64
|
-
if (Array.isArray(qualifiedPaths)) {
|
|
65
|
-
return qualifiedPaths.map(qualifiedPath => {
|
|
66
|
-
const relPath = path.relative(root, qualifiedPath)
|
|
67
|
-
return relPath || "."
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
return qualifiedPaths
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function redactCredentials(config) {
|
|
74
|
-
if (typeof config !== 'object' || config === null) {
|
|
75
|
-
return config;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (Array.isArray(config)) {
|
|
79
|
-
return config.map(item => redactCredentials(item));
|
|
80
|
-
} else {
|
|
81
|
-
const newConfig = Object.assign({}, config);
|
|
82
|
-
for (let key in newConfig) {
|
|
83
|
-
if (key === 'credentials') {
|
|
84
|
-
newConfig[key] = _redacted(newConfig[key]);
|
|
85
|
-
} else {
|
|
86
|
-
newConfig[key] = redactCredentials(newConfig[key]);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return newConfig;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
73
|
/**
|
|
94
|
-
*
|
|
95
|
-
* @param {
|
|
96
|
-
* @
|
|
74
|
+
* Returning the project relative path representation of the given path(s),
|
|
75
|
+
* @param {string} root - project root
|
|
76
|
+
* @param {string | string[]} qualifiedPaths - fully qualified path(s)
|
|
97
77
|
*/
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
|
|
104
|
-
return newCred
|
|
105
|
-
}
|
|
106
|
-
return cred
|
|
78
|
+
function relativePaths(root, qualifiedPaths) {
|
|
79
|
+
qualifiedPaths = typeof qualifiedPaths === "string" ? [qualifiedPaths] : qualifiedPaths
|
|
80
|
+
return Array.isArray(qualifiedPaths)
|
|
81
|
+
? qualifiedPaths.map(qualifiedPath => path.relative(root, qualifiedPath) ?? ".")
|
|
82
|
+
: qualifiedPaths
|
|
107
83
|
}
|
|
108
84
|
|
|
109
85
|
/**
|
|
110
86
|
* Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
|
|
111
87
|
* E.g. the module might NOT have been installed.
|
|
112
|
-
* @param {
|
|
113
|
-
* @returns {
|
|
88
|
+
* @param {string[]} modelPaths
|
|
89
|
+
* @returns {string[]}
|
|
114
90
|
*/
|
|
115
91
|
function resolveRequiredSapModels(modelPaths) {
|
|
116
92
|
return Array.isArray(modelPaths) && modelPaths.filter(p => {
|
|
@@ -121,13 +97,19 @@ function resolveRequiredSapModels(modelPaths) {
|
|
|
121
97
|
})
|
|
122
98
|
}
|
|
123
99
|
|
|
124
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Returns the default model options for the given workspace.
|
|
102
|
+
* @param {boolean} ws - whether to include workspace models
|
|
103
|
+
* @param {Function | undefined} filter - filter function for model paths
|
|
104
|
+
*/
|
|
105
|
+
async function getDefaultModelOptions(ws = false, filter = undefined) {
|
|
125
106
|
// Note: requires.toggles holds the actual kind representation and not the value 'true'
|
|
126
107
|
const wildcards = cds.env.requires.toggles && cds.env.features.folders ? ['*', cds.env.features.folders] : '*'
|
|
127
108
|
let paths = _filterModelPaths(cds.resolve(wildcards, false), filter)
|
|
128
109
|
|
|
129
110
|
if (ws) {
|
|
130
|
-
const workspaces = await getWorkspaces()
|
|
111
|
+
const workspaces = (await getWorkspaces())
|
|
112
|
+
.map(ws => ws.workspace)
|
|
131
113
|
const env = cds.env
|
|
132
114
|
const root = cds.root
|
|
133
115
|
try {
|
|
@@ -141,28 +123,44 @@ async function getDefaultModelOptions(ws = false, filter) {
|
|
|
141
123
|
cds.env = env
|
|
142
124
|
}
|
|
143
125
|
// exclude redundant built-in models starting with workspace path, e.g. 'books/@sap/cds-mtxs/srv/bootstrap'
|
|
144
|
-
paths = paths
|
|
126
|
+
paths = paths
|
|
127
|
+
.filter(p => typeof(p) === 'string' && (NODEJS_MODEL_EXCLUDE_LIST.includes(p) || !NODEJS_MODEL_EXCLUDE_LIST.some(ex => p.endsWith(ex))))
|
|
145
128
|
}
|
|
146
129
|
return paths
|
|
147
130
|
}
|
|
148
131
|
|
|
132
|
+
/**
|
|
133
|
+
* @overload
|
|
134
|
+
* @param {string} dir
|
|
135
|
+
* @returns {string}
|
|
136
|
+
*/
|
|
137
|
+
/**
|
|
138
|
+
* @overload
|
|
139
|
+
* @param {unknown} dir
|
|
140
|
+
* @returns {unknown}
|
|
141
|
+
*/
|
|
149
142
|
/**
|
|
150
143
|
* For valid paths replace '\\' with '/' and remove trailing '/'. Otherwise return as is
|
|
151
|
-
* @param {
|
|
144
|
+
* @param {string | unknown} dir
|
|
145
|
+
* @returns {string | unknown}
|
|
152
146
|
*/
|
|
153
147
|
function normalizePath(dir) {
|
|
154
148
|
return typeof dir === "string" ? dir.replace(/\\/g, '/').replace(/\/$/, '') : dir
|
|
155
149
|
}
|
|
156
150
|
|
|
151
|
+
/**
|
|
152
|
+
* @param {NestedStringArray} modelPaths
|
|
153
|
+
*/
|
|
157
154
|
function flatten(modelPaths) {
|
|
158
|
-
|
|
155
|
+
// TODO: I guess this could be modelPaths.flat(Infinity) in the future
|
|
156
|
+
return /** @type{string[]}*/(modelPaths.reduce((/** @type {string[]}*/acc, m) => {
|
|
159
157
|
if (Array.isArray(m)) {
|
|
160
158
|
acc = acc.concat(flatten(m))
|
|
161
159
|
} else if (m) {
|
|
162
160
|
acc.push(m)
|
|
163
161
|
}
|
|
164
162
|
return acc
|
|
165
|
-
}, [])
|
|
163
|
+
}, []))
|
|
166
164
|
}
|
|
167
165
|
|
|
168
166
|
function getI18nDefaultFolder() {
|
|
@@ -181,43 +179,103 @@ async function getWorkspacePaths() {
|
|
|
181
179
|
if (!wsRoot) {
|
|
182
180
|
return []
|
|
183
181
|
}
|
|
184
|
-
return (await getWorkspaces(wsRoot))
|
|
182
|
+
return (await getWorkspaces(wsRoot))
|
|
183
|
+
.map(ws => ws.workspace)
|
|
184
|
+
.filter(ws => cds.root !== path.join(wsRoot, ws))
|
|
185
|
+
.map(ws => path.join(wsRoot, ws))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @typedef {{
|
|
190
|
+
* path: string,
|
|
191
|
+
* workspace: string,
|
|
192
|
+
* packageName: string
|
|
193
|
+
* }} Workspace
|
|
194
|
+
*
|
|
195
|
+
* Returns a map of workspaces from a root project, with their paths and workspace names,
|
|
196
|
+
* I.e. for a monorepo setup this command finds all workspaces defined therein.
|
|
197
|
+
* @param {string} root - the root directory to search for workspaces
|
|
198
|
+
* @returns {Promise<Record<string, Workspace>>} - a map of workspaces with their paths and workspace names
|
|
199
|
+
*/
|
|
200
|
+
async function getWorkspacesDeclarations (root) {
|
|
201
|
+
const rootPackageJson = path.join(root, 'package.json')
|
|
202
|
+
if (!fs.existsSync(rootPackageJson)) return {}
|
|
203
|
+
const pkg = require(rootPackageJson)
|
|
204
|
+
if (!pkg?.workspaces) return {}
|
|
205
|
+
|
|
206
|
+
const workspaces = []
|
|
207
|
+
for (const workspace of pkg.workspaces) {
|
|
208
|
+
if (/[*?[\]]/.test(workspace)) {
|
|
209
|
+
// contains globs
|
|
210
|
+
const { glob } = require('../util/glob')
|
|
211
|
+
for await (const file of glob(workspace, { cwd: root })) {
|
|
212
|
+
try {
|
|
213
|
+
const projectPath = path.join(root, file)
|
|
214
|
+
const projectName = require(path.join(projectPath, 'package.json')).name
|
|
215
|
+
workspaces.push([projectName, {
|
|
216
|
+
packageName: projectName,
|
|
217
|
+
path: projectPath,
|
|
218
|
+
workspace: file
|
|
219
|
+
}])
|
|
220
|
+
} catch {
|
|
221
|
+
// not a directory or doesn't contain a package.json -> not a workspace -> skip
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
// no glob, just a path literal
|
|
226
|
+
const projectPath = path.join(root, workspace)
|
|
227
|
+
try {
|
|
228
|
+
const projectName = require(path.join(projectPath, 'package.json')).name
|
|
229
|
+
workspaces.push([projectName, { path: projectPath, workspace }])
|
|
230
|
+
} catch {
|
|
231
|
+
// not a directory or doesn't contain a package.json -> not a workspace -> skip
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return Object.fromEntries(workspaces)
|
|
185
236
|
}
|
|
186
237
|
|
|
187
238
|
/**
|
|
188
239
|
* Returns the list of workspaces defined for the current project. A workspace dependency
|
|
189
|
-
* needs to be defined resulting in a corresponding
|
|
190
|
-
* @returns the paths are relative to 'wsRoot'.
|
|
240
|
+
* needs to be defined resulting in a corresponding symlink.
|
|
241
|
+
* @returns {Promise<Workspace[]>} the paths are relative to 'wsRoot'.
|
|
191
242
|
*/
|
|
192
|
-
async function getWorkspaces(wsRoot, resolve = false, devDependencies = true) {
|
|
243
|
+
async function getWorkspaces(wsRoot, resolve = false, devDependencies = true, visited = new Set()) {
|
|
193
244
|
wsRoot ??= findWorkspaceRoot(cds.root)
|
|
194
245
|
if (!wsRoot) {
|
|
195
246
|
return []
|
|
196
247
|
}
|
|
197
|
-
const
|
|
198
|
-
let
|
|
248
|
+
const pkgJsonWsRoot = require(path.join(wsRoot, 'package.json'))
|
|
249
|
+
let pkgJson = pkgJsonWsRoot
|
|
199
250
|
if (wsRoot !== cds.root) {
|
|
200
|
-
|
|
251
|
+
pkgJson = require(path.join(cds.root, 'package.json'))
|
|
201
252
|
}
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
253
|
+
const workspaceDependencies = []
|
|
254
|
+
if (pkgJsonWsRoot.workspaces) {
|
|
204
255
|
if (resolve) {
|
|
205
|
-
_validateWsDependencies(wsRoot,
|
|
256
|
+
_validateWsDependencies(wsRoot, pkgJson.dependencies, devDependencies ? pkgJson.devDependencies : {})
|
|
206
257
|
}
|
|
207
258
|
// read npm workspaces
|
|
259
|
+
const workspaces = await getWorkspacesDeclarations(wsRoot)
|
|
208
260
|
const pkgs = await _execNpmWs(wsRoot)
|
|
209
|
-
|
|
261
|
+
for (const pkg of pkgs) {
|
|
210
262
|
// only create tarball archives for dependencies with version '*'
|
|
211
|
-
if (!resolve || devDependencies &&
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
214
|
-
|
|
263
|
+
if (!resolve || devDependencies && pkgJson.devDependencies?.[pkg] === '*' || pkgJson.dependencies?.[pkg] === '*') {
|
|
264
|
+
const subproject = workspaces[pkg]
|
|
265
|
+
if (subproject && !visited.has(pkg)) {
|
|
266
|
+
visited.add(pkg)
|
|
267
|
+
workspaceDependencies.push(subproject)
|
|
268
|
+
// temporarily set cds.root to the dependency to resolve transitive deps thereof
|
|
269
|
+
const cdsr = cds.root
|
|
270
|
+
cds.root = subproject.path
|
|
271
|
+
workspaceDependencies.push(...(await getWorkspaces(wsRoot, resolve, devDependencies, visited)))
|
|
272
|
+
cds.root = cdsr
|
|
215
273
|
}
|
|
216
274
|
}
|
|
217
|
-
}
|
|
275
|
+
}
|
|
218
276
|
}
|
|
219
|
-
DEBUG?.(`Found ${
|
|
220
|
-
return
|
|
277
|
+
DEBUG?.(`Found ${workspaceDependencies.length} workspace dependencies ${workspaceDependencies.join(', ')}`)
|
|
278
|
+
return workspaceDependencies
|
|
221
279
|
}
|
|
222
280
|
|
|
223
281
|
function findWorkspaceRoot(currentPath, depth = 0) {
|
|
@@ -279,39 +337,33 @@ function filterFeaturePaths(model, modelPaths) {
|
|
|
279
337
|
|
|
280
338
|
/**
|
|
281
339
|
* Read installed npm workspaces using 'npm ls -ws --json'.
|
|
282
|
-
*
|
|
340
|
+
* There is currently no easier way for this, as workspaces
|
|
341
|
+
* could contain globs. Glob resolution will be supported
|
|
342
|
+
* natively in the future (fs.glob is in experimental state as of Node23).
|
|
343
|
+
* @param {import('fs').PathLike} wsRoot - the root directory of the workspace
|
|
344
|
+
* @returns {Promise<string[]>} list of package paths, or an empty array if either
|
|
283
345
|
* no workspaces are defined or have not been installed.
|
|
284
346
|
*/
|
|
285
347
|
async function _execNpmWs(wsRoot) {
|
|
286
348
|
try {
|
|
287
349
|
const cmdLine = 'npm ls -ws --json'
|
|
288
350
|
DEBUG?.(`execute ${cmdLine}`)
|
|
289
|
-
|
|
351
|
+
// @ts-expect-error - TODO: shell should be a string. But can probably be removed altogether?
|
|
352
|
+
const result = await execAsync(cmdLine, { shell: IS_WIN, stdio: ['inherit', 'pipe', 'inherit'], cwd: wsRoot })
|
|
290
353
|
// DEBUG?.(result.stdout)
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
return Object.keys(
|
|
294
|
-
} else if (
|
|
295
|
-
return [
|
|
354
|
+
const jsonResult = JSON.parse(result.stdout)
|
|
355
|
+
if (jsonResult.dependencies) {
|
|
356
|
+
return Object.keys(jsonResult.dependencies)
|
|
357
|
+
} else if (jsonResult.name) {
|
|
358
|
+
return [jsonResult.name]
|
|
296
359
|
}
|
|
297
|
-
} catch (e) {
|
|
360
|
+
} catch (/** @type {{stderr: string}} */e) {
|
|
298
361
|
e.stderr = e.stderr?.replace(/npm ERR! /g, '')
|
|
299
362
|
throw e
|
|
300
363
|
}
|
|
301
364
|
return []
|
|
302
365
|
}
|
|
303
366
|
|
|
304
|
-
function _getWorkspace(wsRoot, pkg) {
|
|
305
|
-
try {
|
|
306
|
-
const pkgPath = require.resolve(path.join(pkg, 'package.json'), { paths: [wsRoot] })
|
|
307
|
-
if (!pkgPath.match(/(\/|\\)node_modules(\/|\\)?/) && pkgPath.startsWith(wsRoot)) {
|
|
308
|
-
return path.relative(wsRoot, path.dirname(pkgPath))
|
|
309
|
-
}
|
|
310
|
-
} catch (e) {
|
|
311
|
-
// ignore - workspace dependencies have already been validated
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
367
|
function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
|
|
316
368
|
const allDependencies = { ...dependencies, ...devDependencies }
|
|
317
369
|
for (const name in allDependencies) {
|
|
@@ -330,22 +382,22 @@ function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
|
|
|
330
382
|
|
|
331
383
|
/**
|
|
332
384
|
* Returns a flat array of existing files and folders, trailing '/' or '\\' characters have been removed.
|
|
333
|
-
* @param {
|
|
334
|
-
* @
|
|
385
|
+
* @param {NestedStringArray} modelPaths array of files and folders - optionally nested
|
|
386
|
+
* @param {((m: string) => boolean) | undefined} filter filter function to apply to the found files
|
|
387
|
+
* @returns
|
|
335
388
|
*/
|
|
336
|
-
function _filterModelPaths(modelPaths, filter) {
|
|
337
|
-
const model = new Set()
|
|
389
|
+
function _filterModelPaths(modelPaths, filter = undefined) {
|
|
390
|
+
const model = /** @type {Set<string>}*/(new Set())
|
|
338
391
|
// may contain nested arrays
|
|
339
|
-
|
|
392
|
+
const flatModelPaths = flatten(modelPaths)
|
|
340
393
|
const { roots } = cds.env
|
|
341
|
-
|
|
394
|
+
flatModelPaths.forEach(m => {
|
|
342
395
|
if (m && !model.has(m) && !model.has(m + "/")) {
|
|
343
396
|
// filter roots and absolute model paths that do not exist
|
|
344
397
|
if (roots.includes(m)) {
|
|
345
398
|
const dir = path.resolve(cds.root, m)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
} else if (fs.existsSync(dir + FILE_EXT_CDS)) { //might be cds file name, compatibility to old build configs
|
|
399
|
+
// dir + FILE_EXT_CDS: might be cds file name, compatibility to old build configs
|
|
400
|
+
if (fs.existsSync(dir) || fs.existsSync(dir + FILE_EXT_CDS)) {
|
|
349
401
|
model.add(m)
|
|
350
402
|
}
|
|
351
403
|
} else {
|
|
@@ -353,21 +405,23 @@ function _filterModelPaths(modelPaths, filter) {
|
|
|
353
405
|
}
|
|
354
406
|
}
|
|
355
407
|
})
|
|
356
|
-
return [...model]
|
|
408
|
+
return [...model]
|
|
409
|
+
.filter(m => filter ? filter(m) : true)
|
|
410
|
+
.map(m => normalizePath(m))
|
|
357
411
|
}
|
|
358
412
|
|
|
359
413
|
/**
|
|
360
414
|
* Returns a set of files found in the given directory and its subdirectories.
|
|
361
415
|
* @param {string} srcDir - the directory to search for files
|
|
362
|
-
* @param {
|
|
363
|
-
* @param {
|
|
364
|
-
* @returns {
|
|
416
|
+
* @param {(file: string) => boolean} [filter] - the filter function to apply to the found files
|
|
417
|
+
* @param {string[]} files - the set of files to add the found files to
|
|
418
|
+
* @returns {string[]} files found in the given directory and its subdirectories
|
|
365
419
|
*/
|
|
366
420
|
function getFiles(srcDir, filter, files = []) {
|
|
367
|
-
function handleResource(entry
|
|
421
|
+
function handleResource(/** @type {string} entry */ entry) {
|
|
368
422
|
if (!filter || filter.call(this, entry)) {
|
|
369
423
|
const stats = fs.lstatSync(entry)
|
|
370
|
-
if (
|
|
424
|
+
if (cds.utils.isdir(entry)) {
|
|
371
425
|
getFiles(entry, filter, files)
|
|
372
426
|
} else if (stats.isFile() || stats.isSymbolicLink()) {
|
|
373
427
|
files.push(entry)
|
|
@@ -377,11 +431,11 @@ function getFiles(srcDir, filter, files = []) {
|
|
|
377
431
|
let entries = []
|
|
378
432
|
try {
|
|
379
433
|
entries = fs.readdirSync(srcDir)
|
|
380
|
-
} catch
|
|
434
|
+
} catch {
|
|
381
435
|
// ignore if not existing
|
|
382
436
|
}
|
|
383
437
|
entries.map(subDirEntry => path.join(srcDir, subDirEntry)).forEach((entry) => {
|
|
384
|
-
handleResource(entry
|
|
438
|
+
handleResource(entry)
|
|
385
439
|
})
|
|
386
440
|
return files
|
|
387
441
|
}
|
|
@@ -392,7 +446,8 @@ function getFiles(srcDir, filter, files = []) {
|
|
|
392
446
|
* - 'File:Line:StartCol-EndCol' if Line = start.line = end.line
|
|
393
447
|
* - 'File:StartLine.StartCol-EndLine.EndCol' otherwise
|
|
394
448
|
*
|
|
395
|
-
* @param {
|
|
449
|
+
* @param {SourceLocation | undefined} loc
|
|
450
|
+
* @private
|
|
396
451
|
*/
|
|
397
452
|
function _locationString(loc) {
|
|
398
453
|
if (!loc)
|
|
@@ -415,22 +470,24 @@ function _locationString(loc) {
|
|
|
415
470
|
|
|
416
471
|
/**
|
|
417
472
|
* Class for individual build message.
|
|
418
|
-
*
|
|
419
|
-
* @class BuildMessage
|
|
420
473
|
*/
|
|
421
474
|
class BuildMessage {
|
|
422
475
|
/**
|
|
423
476
|
* Creates an instance of BuildMessage.
|
|
424
477
|
* @param {string} message The message text
|
|
425
|
-
* @param {
|
|
426
|
-
* @param {
|
|
427
|
-
*
|
|
478
|
+
* @param {import('./constants').Severity} [severity='Error'] severity - severity of the message
|
|
479
|
+
* @param {SourceLocation | undefined} location Location of the message
|
|
480
|
+
*
|
|
428
481
|
* @memberOf BuildMessage
|
|
429
482
|
*/
|
|
430
|
-
constructor(message, severity = SEVERITY_ERROR, location) {
|
|
483
|
+
constructor(message, severity = SEVERITY_ERROR, location = undefined) {
|
|
484
|
+
/** @type {string} */
|
|
431
485
|
this.message = message
|
|
486
|
+
/** @type {string} */
|
|
432
487
|
this.name = "BuildMessage"
|
|
488
|
+
/** @type {import('./constants').Severity} */
|
|
433
489
|
this.severity = severity
|
|
490
|
+
/** @type {SourceLocation} */
|
|
434
491
|
this.$location = location
|
|
435
492
|
}
|
|
436
493
|
toString() {
|
|
@@ -441,11 +498,16 @@ class BuildMessage {
|
|
|
441
498
|
/**
|
|
442
499
|
* Class for combined build and compiler errors.
|
|
443
500
|
* Additional members:
|
|
444
|
-
* messages:
|
|
445
|
-
* @class BuildError
|
|
446
|
-
* @extends {Error}
|
|
501
|
+
* messages: vector of detailed build messages
|
|
447
502
|
*/
|
|
448
503
|
class BuildError extends Error {
|
|
504
|
+
/** @type {string[] | {message: string}[]} */
|
|
505
|
+
messages
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* @param {string} message
|
|
509
|
+
* @param {string | string[]} messages
|
|
510
|
+
*/
|
|
449
511
|
constructor(message, messages = []) {
|
|
450
512
|
super(message)
|
|
451
513
|
this.name = "BuildError"
|
|
@@ -466,7 +528,6 @@ module.exports = {
|
|
|
466
528
|
getProperty,
|
|
467
529
|
setProperty,
|
|
468
530
|
hasJavaNature,
|
|
469
|
-
redactCredentials,
|
|
470
531
|
hasOptionValue,
|
|
471
532
|
relativePaths,
|
|
472
533
|
resolveRequiredSapModels,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
options: /** @type{const} */(['--to', '--for', '--out', '--on', '--tunnel-address', '--vcap-file', '--delta-from']),
|
|
3
|
+
flags: /** @type{const} */(['--no-save', '--auto-undeploy', '--dry', '--xdry', '--with-mocks', '--store-credentials', '--no-build', '--model-only', '--script', '--with-auto-schema-evolution']),
|
|
4
|
+
shortcuts: ['-2', '-4', '-o', '-n']
|
|
5
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
|
|
3
|
-
const fs = require('fs').promises;
|
|
4
|
-
|
|
5
3
|
const cds = require('../../../lib/cds');
|
|
6
4
|
const { exists, read, write, mkdirp } = cds.utils;
|
|
7
5
|
|
|
@@ -9,7 +7,6 @@ const { build } = require('../../build');
|
|
|
9
7
|
|
|
10
8
|
const hdi = require('./hdiDeployUtil');
|
|
11
9
|
const cfUtil = require('../../util/cf');
|
|
12
|
-
const gitUtil = require('../../util/git');
|
|
13
10
|
const mtaUtil = require('./mtaUtil');
|
|
14
11
|
|
|
15
12
|
const { bold, info, warn } = require('../../util/term');
|
|
@@ -17,8 +14,24 @@ const { bold, info, warn } = require('../../util/term');
|
|
|
17
14
|
const { bind, storeBindings } = require('../../bind/shared');
|
|
18
15
|
const { BUILD_TASK_HANA, OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, OUTPUT_MODE_RESULT } = require('../../build/constants');
|
|
19
16
|
|
|
17
|
+
const { options, flags } = require('../index');
|
|
18
|
+
const allOptions = [...options, ...flags];
|
|
19
|
+
|
|
20
20
|
const DEBUG = cds.debug('cli');
|
|
21
21
|
|
|
22
|
+
/** @type { (typeof allOptions[number])[] } */
|
|
23
|
+
const allowList = [
|
|
24
|
+
'--auto-undeploy',
|
|
25
|
+
'--dry',
|
|
26
|
+
'--for',
|
|
27
|
+
'--on',
|
|
28
|
+
'--store-credentials',
|
|
29
|
+
'--to',
|
|
30
|
+
'--tunnel-address',
|
|
31
|
+
'--vcap-file',
|
|
32
|
+
'--with-mocks'
|
|
33
|
+
]
|
|
34
|
+
|
|
22
35
|
module.exports = new class HanaDeployer {
|
|
23
36
|
|
|
24
37
|
getFromEnv(...varNames) {
|
|
@@ -36,7 +49,15 @@ module.exports = new class HanaDeployer {
|
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
async deploy(options = {}) {
|
|
39
|
-
let
|
|
52
|
+
let unknownOptions = Object.keys(options)
|
|
53
|
+
.map(key => `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`)
|
|
54
|
+
.filter(key => allOptions.includes(key) && !allowList.includes(key));
|
|
55
|
+
|
|
56
|
+
if (unknownOptions.length > 0) {
|
|
57
|
+
throw `One or more options are not supported by cds deploy to hana: ${unknownOptions.join(', ')}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let { model, serviceName: pServiceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {}, dry } = options;
|
|
40
61
|
|
|
41
62
|
options.for ??= 'hybrid';
|
|
42
63
|
|
|
@@ -89,7 +110,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
89
110
|
}
|
|
90
111
|
} else {
|
|
91
112
|
// the primary database is always built and deployed
|
|
92
|
-
const dest = path.join(cds.env.build.target, cds.env.folders.db);
|
|
113
|
+
const dest = buildResult.dest // path.join(cds.env.build.target, cds.env.folders.db);
|
|
93
114
|
|
|
94
115
|
if (undeployWhitelist) {
|
|
95
116
|
console.log('Writing undeploy.json');
|
|
@@ -167,9 +188,6 @@ spec:
|
|
|
167
188
|
vcapEnv.VCAP_SERVICES = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
|
|
168
189
|
}
|
|
169
190
|
|
|
170
|
-
if (storeCredentials) {
|
|
171
|
-
await this._addInstanceToDefaultEnvJson([dest, cds.root], cfServiceInstanceName, serviceKey);
|
|
172
|
-
}
|
|
173
191
|
if (!hasVCAPEnv) {
|
|
174
192
|
bindOptions = {
|
|
175
193
|
...options,
|
|
@@ -185,10 +203,6 @@ spec:
|
|
|
185
203
|
DEBUG?.({ dest, vcapEnv, hdiOptions })
|
|
186
204
|
await hdi.deploy(dest, vcapEnv, hdiOptions);
|
|
187
205
|
|
|
188
|
-
if (storeCredentials) {
|
|
189
|
-
await gitUtil.ensureFileIsGitignored('default-env.json', cds.root);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
206
|
if (!hasVCAPEnv) {
|
|
193
207
|
const services = await bind(bindOptions)
|
|
194
208
|
await storeBindings(services, bindOptions)
|
|
@@ -269,7 +283,7 @@ spec:
|
|
|
269
283
|
async _build() {
|
|
270
284
|
const buildTaskOptions = {
|
|
271
285
|
for: BUILD_TASK_HANA,
|
|
272
|
-
src: cds.env.folders.db,
|
|
286
|
+
// src: cds.env.folders.db,
|
|
273
287
|
[OUTPUT_MODE]: OUTPUT_MODE_FILESYSTEM | OUTPUT_MODE_RESULT
|
|
274
288
|
}
|
|
275
289
|
|
|
@@ -278,26 +292,6 @@ spec:
|
|
|
278
292
|
return buildResult[0].result;
|
|
279
293
|
}
|
|
280
294
|
|
|
281
|
-
async _addInstanceToDefaultEnvJson(currentFolders, serviceInstanceName, serviceKey) {
|
|
282
|
-
for (const currentFolder of currentFolders) {
|
|
283
|
-
const defaultEnvJsonPath = path.join(currentFolder, 'default-env.json');
|
|
284
|
-
const defaultEnvJson = exists(defaultEnvJsonPath) ? await read(defaultEnvJsonPath) : {};
|
|
285
|
-
|
|
286
|
-
defaultEnvJson.VCAP_SERVICES ??= {};
|
|
287
|
-
for (const [serviceKey, services] of Object.entries(defaultEnvJson.VCAP_SERVICES)) {
|
|
288
|
-
defaultEnvJson.VCAP_SERVICES[serviceKey] = services.filter(service => service.name !== serviceInstanceName);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const hanaEntry = this._getVCAPServicesEntry(serviceInstanceName, serviceKey)
|
|
292
|
-
Object.assign(defaultEnvJson.VCAP_SERVICES, hanaEntry);
|
|
293
|
-
|
|
294
|
-
console.log(`Writing ${defaultEnvJsonPath}`);
|
|
295
|
-
await fs.mkdir(path.dirname(defaultEnvJsonPath), { recursive: true })
|
|
296
|
-
await fs.writeFile(defaultEnvJsonPath, JSON.stringify(defaultEnvJson, null, 2));
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
295
|
_getVCAPServicesEntry(serviceInstanceName, serviceKey) {
|
|
302
296
|
return {
|
|
303
297
|
hana: [
|
|
@@ -5,13 +5,13 @@ const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
|
|
|
5
5
|
|
|
6
6
|
module.exports = new class HdiDeployUtil {
|
|
7
7
|
|
|
8
|
-
async deploy(dbDir, vcapEnv = {}, { autoUndeploy } = {}) {
|
|
8
|
+
async deploy(dbDir, vcapEnv = {}, { autoUndeploy, parameter={} } = {}) {
|
|
9
9
|
console.log(`Deploying to HANA from ${dbDir}`);
|
|
10
10
|
|
|
11
11
|
const { clean_env, deploy } = this._loadHdiDeployLib(dbDir);
|
|
12
12
|
|
|
13
13
|
const deployerEnv = JSON.parse(JSON.stringify(process.env));
|
|
14
|
-
const options = deployerEnv.HDI_DEPLOY_OPTIONS ? JSON.parse(deployerEnv.HDI_DEPLOY_OPTIONS) : {};
|
|
14
|
+
const options = deployerEnv.HDI_DEPLOY_OPTIONS ? JSON.parse(deployerEnv.HDI_DEPLOY_OPTIONS) : { parameter };
|
|
15
15
|
delete options.root;
|
|
16
16
|
|
|
17
17
|
clean_env?.(deployerEnv);
|