@sap/cds-dk 9.7.2 → 9.8.1
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 +42 -2
- package/bin/bind.js +7 -2
- package/bin/build.js +14 -4
- package/bin/cds.js +2 -2
- package/bin/completion/completionFs.js +1 -1
- package/bin/completion/index.js +2 -2
- package/bin/data-products/get-csn-via-dp-cli.js +1 -1
- package/bin/debug.js +1 -1
- package/bin/deploy/index.js +3 -2
- package/bin/gen.js +1 -1
- package/bin/help.js +1 -1
- package/bin/import.js +3 -0
- package/bin/minify.js +1 -1
- package/bin/mock.js +1 -1
- package/bin/repl.js +212 -39
- package/bin/test.js +0 -2
- package/bin/up.js +27 -3
- package/bin/util/containerize.js +1 -3
- package/bin/version.js +30 -9
- package/bin/watch.js +20 -4
- package/lib/bind/cf.js +2 -2
- package/lib/bind/k8s.js +0 -1
- package/lib/bind/plugin.js +2 -0
- package/lib/bind/shared.js +6 -1
- package/lib/build/constants.js +0 -8
- package/lib/build/index.js +569 -25
- package/lib/build/{provider/edmxBuildPlugin.js → plugins/edmx.js} +6 -15
- package/lib/build/{provider/featureToggleBuildPlugin.js → plugins/featureToggles.js} +8 -27
- package/lib/build/{provider → plugins}/fiori/index.js +27 -40
- package/lib/build/{provider → plugins}/hana/index.js +67 -85
- package/lib/build/{provider → plugins}/hana/migrationtable.js +4 -3
- package/lib/build/plugins/helm/index.js +6 -6
- package/lib/build/{provider/internalBuildPlugin.js → plugins/internal.js} +7 -31
- package/lib/build/{provider → plugins}/java/index.js +18 -26
- package/lib/build/plugins/mtx/index.js +41 -0
- package/lib/build/plugins/mtx/resourcesTarBuilder.js +41 -0
- package/lib/build/{provider → plugins}/mtx-extension/extensionCompilation.js +1 -1
- package/lib/build/{provider → plugins}/mtx-extension/index.js +35 -44
- package/lib/build/{provider → plugins}/mtx-sidecar/index.js +17 -3
- package/lib/build/{provider → plugins}/nodejs/index.js +29 -28
- package/lib/build/{plugin.js → plugins/plugin.js} +57 -60
- package/lib/build/plugins.js +11 -8
- package/lib/build/util.js +0 -8
- package/lib/compile/to-xsuaa.js +2 -2
- package/lib/deploy/to-hana/hana.js +64 -56
- package/lib/import/common.js +1 -1
- package/lib/import/odata/metadataconverter/MetadataConverter.js +4 -3
- package/lib/import/odata/v2parser.js +28 -28
- package/lib/import/odata/v4parser.js +37 -2
- package/lib/import/openapi/index.js +1 -1
- package/lib/init/constants.js +1 -2
- package/lib/init/template/app-frontend/index.js +1 -1
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/completion/completionSetup.js +1 -1
- package/lib/init/template/data/as-csv.js +1 -1
- package/lib/init/template/github-actions/index.js +1 -1
- package/lib/init/template/handler/index.js +1 -1
- package/lib/init/template/helm/index.js +4 -4
- package/lib/init/template/kyma/index.js +3 -3
- package/lib/init/template/lint/index.js +7 -21
- package/lib/init/template/multitenancy/files/package.json.hbs +0 -5
- package/lib/init/template/postgres/files/containerize.yaml.hbs +15 -0
- package/lib/init/template/postgres/files/package.json.hbs +35 -0
- package/lib/init/template/postgres/files/values.yaml.hbs +23 -0
- package/lib/init/template/postgres/index.js +34 -9
- package/lib/init/template/test/files/package.json +1 -1
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/init/template/workzone-standard/files/mta.yaml.hbs +1 -0
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/lint/io.js +9 -13
- package/lib/mtx/pull.js +1 -1
- package/lib/mtx/settings_manager.js +11 -5
- package/lib/mtx/tenant_action.js +1 -1
- package/lib/mtx/util/fs.js +2 -2
- package/lib/util/fs.js +3 -3
- package/lib/util/git.js +1 -1
- package/lib/util/glob.js +1 -1
- package/lib/watch/hot-reload.js +202 -0
- package/lib/watch/livereload-server.js +6 -4
- package/lib/watch/node-watch.js +5 -1
- package/lib/watch/watched.js +7 -3
- package/lib/watch/watcher.js +41 -3
- package/npm-shrinkwrap.json +46 -31
- package/package.json +1 -1
- package/lib/build/buildTaskEngine.js +0 -377
- package/lib/build/buildTaskFactory.js +0 -198
- package/lib/build/buildTaskProvider.js +0 -38
- package/lib/build/buildTaskProviderFactory.js +0 -233
- package/lib/build/provider/internalBuildTaskProvider.js +0 -227
- package/lib/build/provider/java-cf/index.js +0 -1
- package/lib/build/provider/mtx/index.js +0 -36
- package/lib/build/provider/mtx/resourcesTarBuilder.js +0 -102
- package/lib/build/provider/node-cf/index.js +0 -1
- package/lib/init/template/lint/files/.eslint/rules/no-entity-moo.js +0 -40
- package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/fixed/schema.cds +0 -4
- package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/invalid/schema.cds +0 -4
- package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/rule.test.js +0 -15
- package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/valid/schema.cds +0 -6
- package/lib/init/types.d.ts +0 -23
- /package/lib/build/{provider → plugins}/hana/2migration.js +0 -0
- /package/lib/build/{provider → plugins}/hana/template/.hdiconfig-all +0 -0
- /package/lib/build/{provider → plugins}/hana/template/.hdiconfig-cloud +0 -0
- /package/lib/build/{provider → plugins}/hana/template/.hdinamespace +0 -0
- /package/lib/build/{provider → plugins}/hana/template/package.json +0 -0
- /package/lib/build/{provider → plugins}/hana/template/undeploy.json +0 -0
package/lib/build/index.js
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
const
|
|
2
|
-
const {
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
1
|
+
const path = require('node:path')
|
|
2
|
+
const { inspect } = require('node:util')
|
|
3
|
+
// @ts-expect-error - _log not declared in cds-types
|
|
4
|
+
const cds = require('../cds'), { _log: log } = cds
|
|
5
|
+
const { exists, isdir, rimraf, mkdirp, write, fs, colors: { YELLOW, RESET } } = cds.utils
|
|
6
|
+
const Plugin = require('./plugins/plugin')
|
|
7
|
+
const { relativePaths, BuildError, BuildMessage, resolveRequiredSapModels, normalizePath, getDefaultModelPaths, hasOptionValue } = require('./util')
|
|
8
|
+
const { sortMessagesSeverityAware, deduplicateMessages, CompilationError } = cds.compiler
|
|
9
|
+
const { SEVERITIES, SEVERITY_ERROR, LOG_LEVELS, DEFAULT_SRC_FOLDER, OPTION_WS, IGNORE_DEFAULT_MODELS, NODEJS_MODEL_EXCLUDE_LIST, BUILD_TASK_NODEJS, BUILD_TASK_NODE_CF, BUILD_TASK_JAVA_CF, BUILD_TASK_JAVA } = require("./constants")
|
|
10
|
+
const COMPILATION_ERROR = 'CompilationError'
|
|
11
|
+
const COMPILE_MESSAGE = 'CompileMessage'
|
|
12
|
+
const DEBUG = cds.debug('cli|build')
|
|
13
|
+
const LOG_LIMIT = 30
|
|
6
14
|
|
|
7
|
-
module.exports = { build, register, Plugin, BuildError }
|
|
8
|
-
|
|
9
|
-
// reuse schema definition from cds-rc.js used for package.json code completion
|
|
10
|
-
/** @typedef{import('../env/schemas/cds-rc')['$defs']['buildTaskType']['enum'][number]} BuildFor */
|
|
11
|
-
|
|
12
|
-
// using (string & {}) prevents widening of `'x' | string` to just `string`
|
|
13
15
|
/**
|
|
16
|
+
* @typedef {import('./plugins/plugin')} Plugin_
|
|
17
|
+
* @typedef {import('./plugins/plugin').Task} Task
|
|
18
|
+
* @typedef {import('../env/schemas/cds-rc')['$defs']['buildTaskType']['enum'][number]} BuildFor
|
|
19
|
+
* using (string & {}) prevents widening of `'x' | string` to just `string`
|
|
14
20
|
* @typedef {{
|
|
15
21
|
* clean?: boolean,
|
|
16
|
-
* outputMode?: number,
|
|
17
22
|
* for?: BuildFor | (string & {}),
|
|
18
23
|
* src?: string,
|
|
19
24
|
* dest?: string,
|
|
20
25
|
* model?: string[],
|
|
21
|
-
* root?: string
|
|
26
|
+
* root?: string
|
|
22
27
|
* } & {[key:string]:unknown}} BuildOptions
|
|
23
28
|
*/
|
|
24
29
|
|
|
@@ -27,23 +32,562 @@ module.exports = { build, register, Plugin, BuildError }
|
|
|
27
32
|
*
|
|
28
33
|
* @param {BuildOptions} options - command options
|
|
29
34
|
*/
|
|
30
|
-
async function build({ root = cds.root, ...options } = {}) {
|
|
35
|
+
async function build({ root = cds.root, tasks, ...options } = {}) {
|
|
31
36
|
if (!exists(root) || !isdir(root)) {
|
|
32
37
|
throw `Project folder '${root}' does not exist`
|
|
33
38
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} catch (e) {
|
|
37
|
-
if (e instanceof BuildError || e.constructor.name === 'CompilationError') {
|
|
38
|
-
cds._log(e.messages.length ? e.messages : e.message, { 'log-level': options['log-level'] || cds.env['log-level'] })
|
|
39
|
-
console.log()
|
|
40
|
-
// CompilationError.message also includes the detail messages - do not log twice
|
|
41
|
-
throw e.constructor.name === 'CompilationError' ? 'CDS compilation failed' : e.messages.length ? e.message : 'CDS build failed'
|
|
42
|
-
}
|
|
43
|
-
throw e
|
|
44
|
-
}
|
|
39
|
+
|
|
40
|
+
return await new BuildTaskEngine(options).processTasks(tasks)
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
function register(id, plugin) {
|
|
48
44
|
return require('./plugins').register(id, plugin)
|
|
49
45
|
}
|
|
46
|
+
|
|
47
|
+
class BuildTaskEngine {
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {BuildOptions} options
|
|
51
|
+
*/
|
|
52
|
+
constructor(options = {}) {
|
|
53
|
+
if (options.clean === undefined) {
|
|
54
|
+
options.clean = true
|
|
55
|
+
}
|
|
56
|
+
for (const key of ['for', 'src', 'dest']) if (options[key]) {
|
|
57
|
+
if (typeof options[key] !== 'string') {
|
|
58
|
+
throw new BuildError(`Invalid build options - property '${key}' must be a string`)
|
|
59
|
+
}
|
|
60
|
+
if (key === 'src' || key === 'dest') {
|
|
61
|
+
// normalize path separator and remove trailing slash
|
|
62
|
+
options[key] = normalizePath(options[key])
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** @type {{ options: BuildOptions, tasks: Task[]}} */
|
|
66
|
+
this._context = { options, tasks: [] } // resolved tasks
|
|
67
|
+
const plugins = require('./plugins')
|
|
68
|
+
this._plugins = plugins.all
|
|
69
|
+
plugins.registerDefaults()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get options() {
|
|
73
|
+
return this._context.options
|
|
74
|
+
}
|
|
75
|
+
get context() {
|
|
76
|
+
return this._context
|
|
77
|
+
}
|
|
78
|
+
get plugins() {
|
|
79
|
+
return this._plugins
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async processTasks(tasks) {
|
|
83
|
+
const startTime = Date.now()
|
|
84
|
+
|
|
85
|
+
tasks ??= await this.getTasks()
|
|
86
|
+
console.log(`building project with`, inspect({
|
|
87
|
+
versions: { cds: cds.version, compiler: cds.compiler.version(), dk: require('../../package.json').version },
|
|
88
|
+
target: cds.env.build.target,
|
|
89
|
+
tasks,
|
|
90
|
+
...(!this.options.clean && { clean: this.options.clean }),
|
|
91
|
+
}, { depth: 11, colors: cds.utils.colors.enabled, compact: 3, breakLength: 250 }))
|
|
92
|
+
|
|
93
|
+
// validate required @sap namespace models - log only
|
|
94
|
+
const unresolved = BuildTaskEngine._resolveRequiredSapServices(tasks)
|
|
95
|
+
if (unresolved.length > 0) {
|
|
96
|
+
throw new BuildError(`Required CDS models [${unresolved.join(', ')}] cannot be resolved. Run the 'npm install' command to install up-to-date versions of the missing packages.`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// create build plugins
|
|
100
|
+
const plugins = []
|
|
101
|
+
tasks.forEach((task) => {
|
|
102
|
+
if (task) {
|
|
103
|
+
const plugin = this.createPlugin(task)
|
|
104
|
+
plugin.init()
|
|
105
|
+
plugins.push(plugin)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
await this._executeCleanBuildTasks(plugins)
|
|
111
|
+
|
|
112
|
+
// throwing Exception in case of compilation errors
|
|
113
|
+
const buildResult = await this._executeBuildTasks(plugins)
|
|
114
|
+
|
|
115
|
+
await this._writeGenerationLog(plugins)
|
|
116
|
+
this._logOutput(plugins)
|
|
117
|
+
this._logMessages(plugins.reduce((acc, plugin) => acc.concat(plugin.messages), []))
|
|
118
|
+
console.log(`build completed in ${Date.now() - startTime} ms`)
|
|
119
|
+
|
|
120
|
+
return buildResult
|
|
121
|
+
} catch (error) {
|
|
122
|
+
this._logOutput(plugins)
|
|
123
|
+
throw error
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async getTasks() {
|
|
128
|
+
const tasks = await this._createTasks()
|
|
129
|
+
const resolvedTasks = this.resolveTasks(tasks)
|
|
130
|
+
if (this.options.resolve) return resolvedTasks
|
|
131
|
+
return tasks
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Create a Plugin instance for the given build task.
|
|
136
|
+
* @param {Task} task
|
|
137
|
+
*/
|
|
138
|
+
createPlugin(task) {
|
|
139
|
+
const PluginClass = this.getPlugin(task)
|
|
140
|
+
const resolvedTask = this._resolveTask(task)
|
|
141
|
+
DEBUG?.(`loaded build plugin [${resolvedTask.for}]`)
|
|
142
|
+
|
|
143
|
+
const plugin = new PluginClass()
|
|
144
|
+
if (!(plugin instanceof PluginClass)) {
|
|
145
|
+
throw new Error(`Invalid Build plugin type ${task.for}`)
|
|
146
|
+
}
|
|
147
|
+
plugin._task = resolvedTask
|
|
148
|
+
plugin._context = this.context
|
|
149
|
+
this.context.tasks.push(resolvedTask)
|
|
150
|
+
DEBUG?.(`created build plugin [${resolvedTask.for}]`)
|
|
151
|
+
return plugin
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async _createTasks() {
|
|
155
|
+
DEBUG?.(`determining build tasks for project [${cds.root}].`)
|
|
156
|
+
|
|
157
|
+
let tasks = Array.isArray(cds.env.build?.tasks) ? cds.clone(cds.env.build.tasks) : []
|
|
158
|
+
if (tasks.length === 0) {
|
|
159
|
+
tasks = await this.lookupTasks()
|
|
160
|
+
this._applyCliOptions(tasks)
|
|
161
|
+
} else {
|
|
162
|
+
this._applyCliOptions(tasks)
|
|
163
|
+
// 1. apply default values including task.for and ensure that for all tasks a provider exists - throwing error otherwise
|
|
164
|
+
await this.applyTaskDefaults(tasks)
|
|
165
|
+
// ensure that dependencies get wired up before filtering
|
|
166
|
+
await this.lookupTasks(tasks, true)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 2. filters the list of build tasks
|
|
170
|
+
let existingTasks = tasks
|
|
171
|
+
tasks = await this._filterTasksForCli(tasks)
|
|
172
|
+
if (tasks.length === 0) return tasks
|
|
173
|
+
|
|
174
|
+
// 3. add dependencies
|
|
175
|
+
existingTasks = [...tasks]
|
|
176
|
+
await this.lookupTasks(tasks, true)
|
|
177
|
+
if (tasks.length > existingTasks.length) {
|
|
178
|
+
const newTasks = tasks.filter(task => !existingTasks.includes(task))
|
|
179
|
+
this._applyCliOptions(newTasks)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await BuildTaskEngine._applyCommonTaskDefaults(tasks)
|
|
183
|
+
|
|
184
|
+
// Java projects use "." as the default build target folder
|
|
185
|
+
if (cds.env['project-nature'] === 'java' && cds.env.build.target !== ".") {
|
|
186
|
+
const userEnv = cds.env.for("cds", cds.root, false)
|
|
187
|
+
if (!userEnv.build?.target) {
|
|
188
|
+
cds.env.build.target = "."
|
|
189
|
+
DEBUG?.("using inplace build for java project instead of default staging build")
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return tasks
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static async _applyCommonTaskDefaults(tasks) {
|
|
196
|
+
tasks.forEach(task => {
|
|
197
|
+
if (task.options?.model && !Array.isArray(task.options.model)) {
|
|
198
|
+
task.options.model = [task.options.model]
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
const modelPaths = await getDefaultModelPaths(false)
|
|
202
|
+
let wsModelPaths
|
|
203
|
+
if (cds.cli.options?.[OPTION_WS] || tasks.some(task => hasOptionValue(task.options?.[OPTION_WS], true))) {
|
|
204
|
+
wsModelPaths = await getDefaultModelPaths(true)
|
|
205
|
+
}
|
|
206
|
+
tasks.forEach(task => {
|
|
207
|
+
if (!task.src) {
|
|
208
|
+
throw new BuildError(`Mandatory property 'src' not defined for build task '${task.for}'.`)
|
|
209
|
+
}
|
|
210
|
+
const optionWs = cds.cli.options?.[OPTION_WS] || hasOptionValue(task.options?.[OPTION_WS], true)
|
|
211
|
+
this._setTaskModelOptions(task, optionWs ? [...wsModelPaths] : [...modelPaths])
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async _filterTasksForCli(tasks) {
|
|
216
|
+
const options = this.options
|
|
217
|
+
let resultTasks = tasks.filter(task => {
|
|
218
|
+
return (!options.for || options.for === task.for)
|
|
219
|
+
&& (!options.src || options.src === task.src)
|
|
220
|
+
})
|
|
221
|
+
if (resultTasks.length === 0) {
|
|
222
|
+
if (options.for) {
|
|
223
|
+
const task = this.getTask({ for: options.for })
|
|
224
|
+
if (options.src) task.src = options.src
|
|
225
|
+
resultTasks.push(task)
|
|
226
|
+
this._applyCliOptions(resultTasks)
|
|
227
|
+
await this.applyTaskDefaults(resultTasks)
|
|
228
|
+
}
|
|
229
|
+
} else if (resultTasks.length <= tasks.length) {
|
|
230
|
+
tasks.length = 0
|
|
231
|
+
resultTasks.forEach(task => tasks.push(task))
|
|
232
|
+
resultTasks = tasks
|
|
233
|
+
}
|
|
234
|
+
return resultTasks
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_applyCliOptions(tasks) {
|
|
238
|
+
const options = this.options
|
|
239
|
+
tasks.forEach(task => {
|
|
240
|
+
if (options.dest) task.dest = options.dest
|
|
241
|
+
if (options.taskOptions) {
|
|
242
|
+
const taskOptions = cds.clone(options.taskOptions)
|
|
243
|
+
task.options = task.options ? Object.assign(task.options, taskOptions) : taskOptions
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static escapeRegExpInput(str) {
|
|
249
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static _setTaskModelOptions(task, defaultModelPaths) {
|
|
253
|
+
if (task.for !== BUILD_TASK_NODEJS && task.for !== BUILD_TASK_NODE_CF) {
|
|
254
|
+
defaultModelPaths = defaultModelPaths.filter(p => !NODEJS_MODEL_EXCLUDE_LIST.includes(p))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let taskModelPaths = task.options?.model
|
|
258
|
+
if (taskModelPaths && !Array.isArray(taskModelPaths)) {
|
|
259
|
+
taskModelPaths = [taskModelPaths]
|
|
260
|
+
}
|
|
261
|
+
const allowList = new RegExp(`^@sap/cds${cds.env.requires.toggles
|
|
262
|
+
? '|^' + BuildTaskEngine.escapeRegExpInput(cds.env.features.folders.replace('*', ''))
|
|
263
|
+
: ''}`)
|
|
264
|
+
task.options = task.options || {}
|
|
265
|
+
let modelPaths = []
|
|
266
|
+
if (taskModelPaths?.length) {
|
|
267
|
+
if (!hasOptionValue(task.options?.[IGNORE_DEFAULT_MODELS], true)) {
|
|
268
|
+
modelPaths = defaultModelPaths.filter(p => p.match(allowList))
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
modelPaths = [...defaultModelPaths]
|
|
272
|
+
if (!modelPaths.includes(task.src)) modelPaths.push(task.src)
|
|
273
|
+
if (hasOptionValue(task.options?.[IGNORE_DEFAULT_MODELS], true)) {
|
|
274
|
+
modelPaths = modelPaths.filter(p => !p.match(allowList))
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
task.options.model = [...new Set(taskModelPaths?.length ? taskModelPaths.concat(modelPaths) : modelPaths)]
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getTask(key) {
|
|
281
|
+
this._normalizeKey(key)
|
|
282
|
+
this._assertProvided(key)
|
|
283
|
+
const task = cds.clone(this.plugins.get(key.for)?.taskDefaults ?? {})
|
|
284
|
+
task.for = key.for
|
|
285
|
+
task.src ??= DEFAULT_SRC_FOLDER
|
|
286
|
+
task.src = task.src.replace(/\/$/, '')
|
|
287
|
+
return task
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async applyTaskDefaults(tasks) {
|
|
291
|
+
return Promise.all(tasks.map(async (task) => {
|
|
292
|
+
if (!task.for) throw new BuildError(`Mandatory property 'for' not defined for build task.`)
|
|
293
|
+
return this._applyTaskDefaults(task)
|
|
294
|
+
}))
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async lookupTasks(tasks = [], dependencies) {
|
|
298
|
+
const existingTasks = [...tasks]
|
|
299
|
+
|
|
300
|
+
if (!dependencies) {
|
|
301
|
+
for (const [id, PluginClass] of this.plugins) {
|
|
302
|
+
if (PluginClass.hasTask()) tasks.push(this.getTask({ for: id }))
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
const toAdd = []
|
|
306
|
+
for (const task of tasks) {
|
|
307
|
+
const PluginClass = this.getPlugin(task)
|
|
308
|
+
const reqs = new PluginClass().requires || []
|
|
309
|
+
for (const req of reqs) {
|
|
310
|
+
const reqId = this._normalizeKey({ for: req }).for
|
|
311
|
+
const ReqPluginClass = this.plugins.get(reqId)
|
|
312
|
+
if (ReqPluginClass && !tasks.some(t => t.for === reqId) && !toAdd.some(t => t.for === reqId)) {
|
|
313
|
+
const defaultTaskSrc = this.getTask({ for: reqId }).src
|
|
314
|
+
const configuredTasks = cds.env.build?.tasks?.filter(t => t.for === reqId)
|
|
315
|
+
let configuredTask
|
|
316
|
+
if (configuredTasks?.length) {
|
|
317
|
+
configuredTask = configuredTasks.find(t => !t.src || t.src === defaultTaskSrc) || configuredTasks[0]
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (configuredTask) {
|
|
321
|
+
configuredTask = cds.clone(configuredTask)
|
|
322
|
+
await this._applyTaskDefaults(configuredTask)
|
|
323
|
+
toAdd.push(configuredTask)
|
|
324
|
+
} else if (ReqPluginClass.hasTask?.()) {
|
|
325
|
+
toAdd.push(this.getTask({ for: reqId }))
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
tasks.push(...toAdd)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (existingTasks.length < tasks.length) {
|
|
334
|
+
const newTasks = tasks.filter(task => !existingTasks.includes(task))
|
|
335
|
+
await Promise.all(newTasks.map(t => this._applyTaskDefaults(t)))
|
|
336
|
+
DEBUG?.(`Build task provider ${this.constructor.name} returned build tasks ${JSON.stringify(newTasks)}`)
|
|
337
|
+
}
|
|
338
|
+
return tasks
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Loads the build plugin implementation for the given build task.
|
|
343
|
+
* @param {Task} task
|
|
344
|
+
* @returns {typeof import('./plugins/plugin')}
|
|
345
|
+
*/
|
|
346
|
+
getPlugin(task) {
|
|
347
|
+
this._normalizeKey(task)
|
|
348
|
+
this._assertProvided(task)
|
|
349
|
+
try {
|
|
350
|
+
return this.plugins.get(task.for)
|
|
351
|
+
} catch (e) {
|
|
352
|
+
console.error(`Provider failed to load build plugin class ${task.for} - provider: ${this.constructor.name}`)
|
|
353
|
+
throw e
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* @param {Task[]} tasks
|
|
359
|
+
*/
|
|
360
|
+
resolveTasks(tasks) {
|
|
361
|
+
return tasks.map(task => this._resolveTask(task))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @param {Task} task
|
|
366
|
+
*/
|
|
367
|
+
_resolveTask(task) {
|
|
368
|
+
const resolvedTask = cds.clone(task)
|
|
369
|
+
resolvedTask.src = path.resolve(cds.root, task.src)
|
|
370
|
+
if (!this.options.ws) {
|
|
371
|
+
try {
|
|
372
|
+
fs.realpathSync(resolvedTask.src)
|
|
373
|
+
} catch {
|
|
374
|
+
throw new BuildError(`The 'src' folder '${path.resolve(cds.root, task.src)}' for build task '${resolvedTask.for}' does not exist`)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
resolvedTask.dest = path.resolve(cds.root, cds.env.build.target, task.dest || task.src)
|
|
378
|
+
resolvedTask.options = task.options || {}
|
|
379
|
+
return resolvedTask
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
_normalizeKey(key) {
|
|
383
|
+
const compat = { [BUILD_TASK_NODE_CF]: BUILD_TASK_NODEJS, [BUILD_TASK_JAVA_CF]: BUILD_TASK_JAVA }
|
|
384
|
+
if (compat[key.for]) {
|
|
385
|
+
console.warn(`${YELLOW}Build task '${key.for}' is deprecated, use '${compat[key.for]}' instead${RESET}`)
|
|
386
|
+
key.for = compat[key.for]
|
|
387
|
+
}
|
|
388
|
+
return key
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
_assertProvided(key) {
|
|
392
|
+
if (!this.plugins.has(key.for)) {
|
|
393
|
+
throw new BuildError(`No provider found for build task '${key.for}'. Ensure that all required dependencies have been added and 'npm install' has been executed.`)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async _applyTaskDefaults(task) {
|
|
398
|
+
const defaultTask = this.getTask(task)
|
|
399
|
+
const names = Object.getOwnPropertyNames(defaultTask)
|
|
400
|
+
names.forEach(name => {
|
|
401
|
+
task[name] ??= defaultTask[name]
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async _executeCleanBuildTasks(plugins) {
|
|
406
|
+
if (!this.options.clean) return
|
|
407
|
+
|
|
408
|
+
if (cds.env.build.target !== '.') {
|
|
409
|
+
const target = path.resolve(cds.root, cds.env.build.target)
|
|
410
|
+
DEBUG?.(`cleaning staging folder ${target}`)
|
|
411
|
+
await rimraf(target)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const results = await Promise.allSettled(plugins.map((plugin) => {
|
|
415
|
+
DEBUG?.(`cleaning, plugin [${plugin.constructor.name}], src [${relativePaths(cds.root, plugin.task.src)}]`)
|
|
416
|
+
return plugin.clean()
|
|
417
|
+
}))
|
|
418
|
+
this._resolveHandlerResponse(results)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async _executeBuildTasks(plugins) {
|
|
422
|
+
const levels = new Map()
|
|
423
|
+
const getLevel = (PluginClass) => {
|
|
424
|
+
if (levels.has(PluginClass)) return levels.get(PluginClass)
|
|
425
|
+
const plugin = new PluginClass()
|
|
426
|
+
if (plugin.priority && plugin.priority < 0) { // cds10: compat for cds-plugin-ui5 using undocumented priority
|
|
427
|
+
const level = Number.MAX_SAFE_INTEGER
|
|
428
|
+
levels.set(PluginClass, level)
|
|
429
|
+
return level
|
|
430
|
+
}
|
|
431
|
+
let maxDepLevel = -1
|
|
432
|
+
const reqs = plugin.requires || []
|
|
433
|
+
for (const req of reqs) {
|
|
434
|
+
const reqId = this._normalizeKey({ for: req }).for
|
|
435
|
+
const ReqPluginClass = this.plugins.get(reqId)
|
|
436
|
+
if (ReqPluginClass) maxDepLevel = Math.max(maxDepLevel, getLevel(ReqPluginClass))
|
|
437
|
+
}
|
|
438
|
+
const level = maxDepLevel + 1
|
|
439
|
+
levels.set(PluginClass, level)
|
|
440
|
+
return level
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
plugins.forEach(p => getLevel(p.constructor))
|
|
444
|
+
|
|
445
|
+
const groupsMap = new Map()
|
|
446
|
+
plugins.forEach(p => {
|
|
447
|
+
const lvl = levels.get(p.constructor)
|
|
448
|
+
if (!groupsMap.has(lvl)) groupsMap.set(lvl, [])
|
|
449
|
+
groupsMap.get(lvl).push(p)
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
const sortedLevels = [...groupsMap.keys()].sort((a, b) => a - b)
|
|
453
|
+
const pipeline = sortedLevels.map(lvl => groupsMap.get(lvl))
|
|
454
|
+
|
|
455
|
+
const results = await this._executePipeline(pipeline)
|
|
456
|
+
return this._resolveHandlerResponse(results, plugins.flatMap(plugin => plugin.messages))
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async _executePipeline(pipeline) {
|
|
460
|
+
let allResults = []
|
|
461
|
+
for (const group of pipeline) {
|
|
462
|
+
const results = await Promise.allSettled(group.map((plugin) => {
|
|
463
|
+
DEBUG?.(`building, plugin [${plugin.constructor.name}], src [${relativePaths(cds.root, plugin.task.src)}]`)
|
|
464
|
+
return plugin.build().then(pluginResult => Promise.resolve({
|
|
465
|
+
task: plugin.task,
|
|
466
|
+
result: pluginResult,
|
|
467
|
+
messages: this._sortMessagesUnique(plugin.messages)
|
|
468
|
+
}))
|
|
469
|
+
}))
|
|
470
|
+
allResults = allResults.concat(results)
|
|
471
|
+
}
|
|
472
|
+
return allResults
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
_resolveHandlerResponse(results, pluginMessages = []) {
|
|
476
|
+
/** @type {(BuildError | Error)[]}*/
|
|
477
|
+
const errors = []
|
|
478
|
+
const resolvedResults = results.reduce((acc, r) => {
|
|
479
|
+
if (r.status === 'fulfilled') acc.push(r.value)
|
|
480
|
+
if (r.status === 'rejected' && r.reason) errors.push(r.reason)
|
|
481
|
+
return acc
|
|
482
|
+
}, [])
|
|
483
|
+
|
|
484
|
+
if (errors.length > 0) {
|
|
485
|
+
const error = errors.find(e => e.constructor.name !== COMPILATION_ERROR)
|
|
486
|
+
if (error) {
|
|
487
|
+
if (!(error instanceof BuildError)) throw error
|
|
488
|
+
error.messages = error.messages?.map(m => typeof m === 'string' ? new BuildMessage(m, SEVERITY_ERROR) : m)
|
|
489
|
+
error.messages = this._sortMessagesUnique(pluginMessages.filter(m => m instanceof BuildMessage), error.messages)
|
|
490
|
+
throw error
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const compileErrors = errors.filter(e => e.constructor.name === COMPILATION_ERROR)
|
|
494
|
+
const compileMessages = pluginMessages.filter(message => message.constructor.name === COMPILE_MESSAGE)
|
|
495
|
+
if (compileErrors.length) {
|
|
496
|
+
throw new CompilationError(this._sortMessagesUnique(BuildTaskEngine._getErrorMessages(compileErrors), compileMessages))
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return resolvedResults
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
_logOutput(plugins) {
|
|
503
|
+
const files = BuildTaskEngine._getOutput(plugins)
|
|
504
|
+
if (files.length > 0) {
|
|
505
|
+
const term = require('../util/term')
|
|
506
|
+
if (!process.env.DEBUG && files.length > LOG_LIMIT) {
|
|
507
|
+
const length = files.length
|
|
508
|
+
files.length = LOG_LIMIT
|
|
509
|
+
files.push(`... ${length - files.length} more. Run with DEBUG=build to show all files.`)
|
|
510
|
+
}
|
|
511
|
+
console.log(`done > wrote output to:\n ${term.dim(files.join("\n "))}\n`)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async _writeGenerationLog(plugins) {
|
|
516
|
+
const outputFile = cds.env.build.outputfile || process.env.GENERATION_LOG
|
|
517
|
+
if (outputFile) {
|
|
518
|
+
const files = BuildTaskEngine._getOutput(plugins)
|
|
519
|
+
console.log(`writing generation log to [${outputFile}]\n`)
|
|
520
|
+
try {
|
|
521
|
+
await mkdirp(path.dirname(outputFile))
|
|
522
|
+
await write(outputFile, files.join('\n'))
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.error(`failed to write generation log`)
|
|
525
|
+
console.error(error.stack || error)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
static _getOutput(plugins) {
|
|
531
|
+
const files = plugins.flatMap(plugin => plugin.files).sort()
|
|
532
|
+
return files.map(file => {
|
|
533
|
+
if (path.isAbsolute(cds.env.build.target)) return file
|
|
534
|
+
return path.relative(cds.root, file)
|
|
535
|
+
})
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
_logMessages(messages) {
|
|
539
|
+
if (messages.length > 0) {
|
|
540
|
+
const options = {
|
|
541
|
+
log: console.log,
|
|
542
|
+
"log-level": this._getLogLevel()
|
|
543
|
+
}
|
|
544
|
+
deduplicateMessages(messages)
|
|
545
|
+
sortMessagesSeverityAware(messages)
|
|
546
|
+
log(messages, options)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
static _resolveRequiredSapServices(tasks) {
|
|
551
|
+
const taskModelPaths = tasks.reduce((acc, task) => {
|
|
552
|
+
const model = task.options?.model
|
|
553
|
+
if (model) {
|
|
554
|
+
if (Array.isArray(model)) model.forEach(m => acc.add(m))
|
|
555
|
+
else acc.add(model)
|
|
556
|
+
}
|
|
557
|
+
return acc
|
|
558
|
+
}, new Set())
|
|
559
|
+
|
|
560
|
+
return resolveRequiredSapModels([...taskModelPaths])
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* @param {(string | BuildError)[]} errors
|
|
565
|
+
*/
|
|
566
|
+
static _getErrorMessages(errors) {
|
|
567
|
+
let messages = []
|
|
568
|
+
errors.forEach(error => {
|
|
569
|
+
if (typeof error !== 'string' && Array.isArray(error.errors) && error.errors.length > 0) {
|
|
570
|
+
messages = messages.concat(this._getErrorMessages(error.errors))
|
|
571
|
+
} else {
|
|
572
|
+
messages.push(error)
|
|
573
|
+
}
|
|
574
|
+
})
|
|
575
|
+
return messages
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
_sortMessagesUnique(...messages) {
|
|
579
|
+
const logLevelIdx = LOG_LEVELS.indexOf(this._getLogLevel())
|
|
580
|
+
messages = messages.reduce((acc, m) => acc.concat(m), [])
|
|
581
|
+
const filteredMessages = messages.filter(message => message && (!message.severity || logLevelIdx >= SEVERITIES.indexOf(message.severity)))
|
|
582
|
+
// @ts-expect-error - formally expects CompileMessage, but actually, an .severity is the only expected property
|
|
583
|
+
deduplicateMessages(filteredMessages)
|
|
584
|
+
// @ts-expect-error - see above
|
|
585
|
+
return sortMessagesSeverityAware(filteredMessages)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
_getLogLevel() {
|
|
589
|
+
return this.options["log-level"] || cds.env["log-level"]
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
module.exports = { build, register, Plugin, BuildError, BuildTaskEngine }
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
const path = require('path')
|
|
1
|
+
const path = require('node:path')
|
|
2
2
|
const cds = require('../../cds')
|
|
3
|
-
const
|
|
4
|
-
const { OUTPUT_MODE, OUTPUT_MODE_RESULT, FLAVOR_LOCALIZED_EDMX } = require('../constants')
|
|
3
|
+
const { FLAVOR_LOCALIZED_EDMX } = require('../constants')
|
|
5
4
|
const DEBUG = cds.debug('cli|build')
|
|
6
5
|
|
|
7
|
-
class EdmxBuildPlugin extends
|
|
8
|
-
|
|
6
|
+
module.exports = class EdmxBuildPlugin extends require('./featureToggles') {
|
|
7
|
+
|
|
8
|
+
async compileToEdmx(model, edmxDest, compileOptions = {}) {
|
|
9
9
|
const promises = []
|
|
10
10
|
|
|
11
11
|
// generate edmx for services only that have the odata protocol
|
|
@@ -34,9 +34,6 @@ class EdmxBuildPlugin extends FeatureToggleBuildPlugin {
|
|
|
34
34
|
}
|
|
35
35
|
for (let [content, key] of result) {
|
|
36
36
|
const serviceName = key.file ? key.file : key.name
|
|
37
|
-
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
38
|
-
this._result.services.add(serviceName)
|
|
39
|
-
}
|
|
40
37
|
const locResult = cds.localize(model, langs, content)
|
|
41
38
|
if (locResult[Symbol.iterator]) { // multi result
|
|
42
39
|
for (let [localizedContent, { lang }] of locResult) {
|
|
@@ -65,16 +62,10 @@ class EdmxBuildPlugin extends FeatureToggleBuildPlugin {
|
|
|
65
62
|
|
|
66
63
|
_writeEdmxForLang(content, serviceName, lang, edmxDest) {
|
|
67
64
|
const fileName = serviceName + (lang ? '_' + lang + '.xml' : '.xml')
|
|
68
|
-
if (this.
|
|
69
|
-
this._result.languages.add(lang)
|
|
70
|
-
this._result.edmx.set(fileName, content)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
//edmxDest might be null
|
|
65
|
+
if (this._cachedEdmx) this._cachedEdmx.set(fileName, content)
|
|
74
66
|
if (edmxDest) {
|
|
75
67
|
return this.write(content).to(path.join(edmxDest, fileName))
|
|
76
68
|
}
|
|
77
69
|
return Promise.resolve()
|
|
78
70
|
}
|
|
79
71
|
}
|
|
80
|
-
module.exports = EdmxBuildPlugin
|