@sap/cds-dk 8.8.2 → 8.9.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 +24 -1
- package/bin/deploy/index.js +7 -3
- package/bin/init.js +5 -5
- package/bin/repl.js +4 -0
- package/bin/test.js +6 -4
- package/bin/up.js +77 -0
- package/bin/watch.js +0 -43
- package/lib/bind/shared.js +1 -1
- package/lib/build/buildTaskEngine.js +10 -9
- package/lib/build/buildTaskFactory.js +0 -45
- package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +8 -0
- package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +87 -0
- package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +29 -1
- package/lib/build/plugins/helm/files/subcharts/service-instance/Chart.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/service-instance/templates/serviceinstance.yaml +1 -0
- package/lib/build/plugins/helm/files/subcharts/service-instance/values.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/deployment.yaml +8 -0
- package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +87 -0
- package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +29 -1
- package/lib/build/provider/internalBuildTaskProvider.js +1 -1
- package/lib/build/provider/mtx-extension/index.js +5 -0
- package/lib/deploy/to-hana/hana.js +95 -43
- package/lib/deploy/to-hana/mtaUtil.js +1 -2
- package/lib/env/schema.js +12 -88
- package/lib/import/common.js +23 -15
- package/lib/import/odata.package.json +7 -0
- package/lib/init/constants.js +2 -1
- package/lib/init/index.js +13 -16
- package/lib/init/mvn.js +1 -1
- package/lib/init/plugin.js +1 -1
- package/lib/init/projectReader.js +5 -1
- package/lib/init/template/approuter/files/package.json +1 -4
- package/lib/init/template/attachments/files/package.json.hbs +1 -1
- package/lib/init/template/cloud-logging/files/java-cdsrc.json +5 -0
- package/lib/init/template/cloud-logging/index.js +9 -5
- package/lib/init/template/containerize/files/containerize.yaml.hbs +1 -1
- package/lib/init/template/data/index.js +3 -3
- package/lib/init/template/extension/files/.base/index.cds +15 -0
- package/lib/init/template/extension/files/app/extension.cds.hbs +47 -0
- package/lib/init/template/extension/files/test/data/Authors.csv +4 -0
- package/lib/init/template/extension/files/test/data/my.bookshop-Books.csv +8 -0
- package/lib/init/template/extension/index.js +99 -0
- package/lib/init/template/handler/files/extension/handler.xs.hbs +10 -0
- package/lib/init/template/handler/files/extension/package.json +10 -0
- package/lib/init/template/handler/index.js +83 -20
- package/lib/init/template/http/index.js +3 -3
- package/lib/init/template/java/index.js +3 -3
- package/lib/init/template/mta/index.js +1 -1
- package/lib/init/template/nodejs/index.js +3 -3
- package/lib/init/template/pipeline/index.js +1 -1
- package/lib/init/template/sample/index.js +6 -0
- package/lib/init/template/typer/files/package.json +0 -3
- package/lib/init/template/typer/index.js +2 -2
- package/lib/init/validate.js +4 -7
- package/lib/mtx/pull.js +9 -1
- package/lib/util/question.js +24 -21
- package/npm-shrinkwrap.json +18 -18
- package/package.json +2 -2
- /package/lib/init/template/handler/files/{handler.xs.hbs → standard/handler.xs.hbs} +0 -0
package/lib/init/index.js
CHANGED
|
@@ -60,7 +60,7 @@ module.exports = class CDSGenerator {
|
|
|
60
60
|
* @param {any} options, additional options
|
|
61
61
|
*/
|
|
62
62
|
async add(facets) {
|
|
63
|
-
if (!facets?.length) throw '
|
|
63
|
+
if (!facets?.length) throw 'you must specify a facet to add to the project'
|
|
64
64
|
|
|
65
65
|
this._initialize(null);
|
|
66
66
|
this._greetings();
|
|
@@ -110,7 +110,7 @@ module.exports = class CDSGenerator {
|
|
|
110
110
|
_initialize(projectName) {
|
|
111
111
|
// avoid config files to be created in the home dir
|
|
112
112
|
if (cds.cli.command === COMMAND_INIT && !projectName && process.cwd() === require('os').homedir()) {
|
|
113
|
-
throw `
|
|
113
|
+
throw `in your home directory, use 'cds init <project>'`
|
|
114
114
|
}
|
|
115
115
|
const { options } = cds.cli
|
|
116
116
|
this.cwd = options.cwd || process.cwd();
|
|
@@ -123,19 +123,19 @@ module.exports = class CDSGenerator {
|
|
|
123
123
|
_greetings() {
|
|
124
124
|
if (cds.cli.command === COMMAND_INIT) {
|
|
125
125
|
const relativeProjectPath = path.relative(this.cwd, cds.root)
|
|
126
|
-
const folderName = relativeProjectPath ? `.${path.sep}${relativeProjectPath}` : 'current folder'
|
|
127
|
-
console.log(`
|
|
126
|
+
const folderName = relativeProjectPath ? `.${path.sep}${relativeProjectPath}` : 'the current folder'
|
|
127
|
+
console.log(`creating new CAP project in ${term.bold(folderName)}\n`)
|
|
128
128
|
}
|
|
129
129
|
if (cds.cli.options.for && typeof cds.cli.options.for !== 'string') {
|
|
130
|
-
throw '
|
|
130
|
+
throw 'the --for argument must not be empty'
|
|
131
131
|
}
|
|
132
132
|
if (cds.cli.options.force && !cds.cli.options.dry) {
|
|
133
|
-
console.log(`
|
|
133
|
+
console.log(`using '--force' ... existing files will be overwritten`)
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
async _process(facets) {
|
|
138
|
-
DEBUG?.(`
|
|
138
|
+
DEBUG?.(`project path: ${cds.root}`);
|
|
139
139
|
|
|
140
140
|
const { options, command } = cds.cli
|
|
141
141
|
if (command === COMMAND_INIT) {
|
|
@@ -149,7 +149,7 @@ module.exports = class CDSGenerator {
|
|
|
149
149
|
const byPriority = (a, b) => cmds.indexOf(a) - cmds.indexOf(b)
|
|
150
150
|
const sorted = Array.from(plugins).sort(byPriority)
|
|
151
151
|
for (const [i, facet] of Object.entries(sorted)) {
|
|
152
|
-
if (!cds.cli.options.dry) console.log(`
|
|
152
|
+
if (!cds.cli.options.dry) console.log(`adding ${term.bold(facet)}`)
|
|
153
153
|
const template = this.templateList[i]
|
|
154
154
|
await template.run();
|
|
155
155
|
await template.combine();
|
|
@@ -157,19 +157,19 @@ module.exports = class CDSGenerator {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
if (cds.env['project-nature'] === 'java') {
|
|
160
|
-
console.log('
|
|
160
|
+
console.log('updating frozen dependencies in package-lock.json…')
|
|
161
161
|
await cmd.spawnCommand('npm' , ['i', '--package-lock-only'], { cwd:cds.root })
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
if (cds.cli.command === COMMAND_INIT) {
|
|
165
165
|
const relativeProjectPath = path.relative(this.cwd, cds.root)
|
|
166
|
-
let message = '
|
|
166
|
+
let message = 'successfully created project'
|
|
167
167
|
if (relativeProjectPath) {
|
|
168
|
-
message
|
|
168
|
+
message += ` – continue with ${term.bold('cd ' + relativeProjectPath)}`
|
|
169
169
|
}
|
|
170
170
|
console.log('\n' + message)
|
|
171
171
|
} else if (!cds.cli.options.dry) {
|
|
172
|
-
console.log(`\
|
|
172
|
+
console.log(`\nsuccessfully added features to your project`)
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
@@ -188,7 +188,7 @@ module.exports = class CDSGenerator {
|
|
|
188
188
|
return `${term.bold(name) + ' '.repeat(max - name.length)} ${term.dim(help)}`
|
|
189
189
|
}).join('\n')
|
|
190
190
|
DEBUG?.(err)
|
|
191
|
-
throw `
|
|
191
|
+
throw `unknown facet ${term.bold(plugin)} – did you mean ${term.bold(`cds add ${bestMatch}`)}?\n\nall supported facets:\n\n${allFacetsText}\n`
|
|
192
192
|
}
|
|
193
193
|
throw err
|
|
194
194
|
}
|
|
@@ -210,7 +210,6 @@ module.exports = class CDSGenerator {
|
|
|
210
210
|
if (!templates.has(plugin)) {
|
|
211
211
|
const template = await this._createTemplate(plugin)
|
|
212
212
|
if (await template.canRun()) {
|
|
213
|
-
DEBUG?.(`Adding feature '${plugin}'`)
|
|
214
213
|
templates.set(plugin, template)
|
|
215
214
|
const dependencies = await template.requires()
|
|
216
215
|
dependencies?.forEach(d => plugins.add(d))
|
|
@@ -229,8 +228,6 @@ module.exports = class CDSGenerator {
|
|
|
229
228
|
return a - b
|
|
230
229
|
})
|
|
231
230
|
.map(key => templates.get(key))
|
|
232
|
-
|
|
233
|
-
DEBUG?.(`Features: ${this.templateList.map(({ name }) => name).join(', ')}`);
|
|
234
231
|
}
|
|
235
232
|
|
|
236
233
|
async stepEnd() { // Also used by CAP Generator
|
package/lib/init/mvn.js
CHANGED
|
@@ -111,7 +111,7 @@ module.exports.generate = async function (feature, options = cds.cli.options) {
|
|
|
111
111
|
const cwd = options.cwd ?? cds.root
|
|
112
112
|
|
|
113
113
|
try {
|
|
114
|
-
console.log(`
|
|
114
|
+
console.log(`executing: `, term.highlight('mvn ' + command.join(' ')))
|
|
115
115
|
await cmd.spawnCommand('mvn', command, { cwd: cwd })
|
|
116
116
|
return command
|
|
117
117
|
} catch (err) {
|
package/lib/init/plugin.js
CHANGED
|
@@ -69,7 +69,7 @@ module.exports = class Plugin {
|
|
|
69
69
|
async combineSupported() {
|
|
70
70
|
const DEBUG = cds.log('cli|add')._debug
|
|
71
71
|
DEBUG && console.log()
|
|
72
|
-
DEBUG && console.log('
|
|
72
|
+
DEBUG && console.log('plugins active for production:')
|
|
73
73
|
const fromDk = await readdir(join(__dirname, './template'))
|
|
74
74
|
const { registered } = require('./add')
|
|
75
75
|
const { env4 } = require('./projectReader')
|
|
@@ -102,6 +102,7 @@ module.exports = new class ProjectReader {
|
|
|
102
102
|
* @property {boolean} hasMalwareScanner Indicates if the project uses the SAP Malware Scanning service.
|
|
103
103
|
* @property {boolean} hasDynatrace Indicates if the project uses Dynatrace.
|
|
104
104
|
* @property {boolean} hasCloudLogging Indicates if the project uses Cloud Logging.
|
|
105
|
+
* @property {String} extendsApp Indicates which project is extended if any.
|
|
105
106
|
*/
|
|
106
107
|
|
|
107
108
|
/**
|
|
@@ -125,8 +126,9 @@ module.exports = new class ProjectReader {
|
|
|
125
126
|
const _uiEmbedded = () => exists(join(env.folders.app, 'xs-app.json'))
|
|
126
127
|
const _uiModule = () => !_uiEmbedded() && isdir(env.folders.app) && fs.readdirSync(resolve(cds.root, env.folders.app)).length > 0
|
|
127
128
|
const _isJava = () => this.isJava
|
|
129
|
+
const _extends = () => pkgJson().extends
|
|
128
130
|
const isApp = name => {
|
|
129
|
-
return name !== 'appconfig' && !name.
|
|
131
|
+
return name !== 'appconfig' && !name.match(/^[._]/) && name !== 'router' && name !== 'portal' && name !== 'workzone'
|
|
130
132
|
}
|
|
131
133
|
const reserved = {
|
|
132
134
|
dependencies: () => pkgJson().dependencies,
|
|
@@ -192,6 +194,8 @@ module.exports = new class ProjectReader {
|
|
|
192
194
|
hasUI5: _ui5,
|
|
193
195
|
hasMTXRoute: () => _inProd('extensibility') || (_inProd('multitenancy') && !_isJava()),
|
|
194
196
|
hasMTXRouteJava: () => (_inProd('helm-unified-runtime') || _inProd('helm')) && _isJava() && _inProd('multitenancy'),
|
|
197
|
+
imageRegistry: () => exists('chart/values.yaml') ? cds.load.yaml('chart/values.yaml').global?.image?.registry ?? '<your-container-registry>' : '<your-container-registry>',
|
|
198
|
+
extendsApp: _extends,
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
// Automatically creates availability checks for `cds add` commands.
|
|
@@ -24,11 +24,15 @@ module.exports = class extends require('../../plugin') {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async run() {
|
|
27
|
-
const project = readProject(), { hasTelemetry, isNodejs } = project
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
const project = readProject(), { hasTelemetry, isNodejs, isJava } = project
|
|
28
|
+
if (cds.cli.options['with-telemetry'] || hasTelemetry) {
|
|
29
|
+
if (isNodejs) {
|
|
30
|
+
project.profile ??= 'production'
|
|
31
|
+
await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: project })
|
|
32
|
+
await sort('package.json', 'dependencies')
|
|
33
|
+
} else if (isJava) {
|
|
34
|
+
await merge(__dirname, 'files/java-cdsrc.json').into('.cdsrc.json')
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -140,7 +140,7 @@ function writeFileFor (entity, data, csn, dest, force) {
|
|
|
140
140
|
|
|
141
141
|
// continue only if file not already exists, or '--force' option provided
|
|
142
142
|
if (fileExists && !force) {
|
|
143
|
-
console.log(` ${dim('
|
|
143
|
+
console.log(` ${dim('skipping ' + relFilePath)}`)
|
|
144
144
|
return
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -149,8 +149,8 @@ function writeFileFor (entity, data, csn, dest, force) {
|
|
|
149
149
|
if (data.length) {
|
|
150
150
|
if (!exists(dest)) fs.mkdirSync(dest, {recursive: true})
|
|
151
151
|
fs.writeFileSync(dataFilePath, data)
|
|
152
|
-
if (fileExists) console.log(` ${dim('
|
|
153
|
-
else console.log(` ${dim('
|
|
152
|
+
if (fileExists) console.log(` ${dim('overwriting ' + relFilePath)}`)
|
|
153
|
+
else console.log(` ${dim('creating ' + relFilePath)}`)
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
entity my.bookshop.Books {
|
|
2
|
+
key ID : Integer;
|
|
3
|
+
title : String @title : 'Title';
|
|
4
|
+
stock : Integer @title : 'Stock';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
service CatalogService {
|
|
8
|
+
entity Books as projection on my.bookshop.Books;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
annotate CatalogService.Books with @UI.LineItem: [
|
|
12
|
+
{ Value: ID },
|
|
13
|
+
{ Value: title },
|
|
14
|
+
{ Value: stock },
|
|
15
|
+
];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
using { my.bookshop, CatalogService } from '{{{basePackage}}}';
|
|
2
|
+
|
|
3
|
+
namespace {{{extensionNamespace}}};
|
|
4
|
+
|
|
5
|
+
// new entity
|
|
6
|
+
entity x_Authors {
|
|
7
|
+
key ID : Integer;
|
|
8
|
+
name : String @title : 'Name';
|
|
9
|
+
books : Association to many bookshop.Books on books.x_author = $self;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// field extension incl. association
|
|
13
|
+
extend bookshop.Books with {
|
|
14
|
+
x_isbn : String @title : 'ISBN';
|
|
15
|
+
x_author : Association to x_Authors @title : 'Author';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// service extension
|
|
19
|
+
extend service CatalogService with {
|
|
20
|
+
@readonly entity Authors as projection on x_Authors;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// -------------------------------------------
|
|
24
|
+
// Fiori Annotations
|
|
25
|
+
|
|
26
|
+
annotate CatalogService.Books with @(
|
|
27
|
+
Common.SemanticKey: [x_isbn],
|
|
28
|
+
UI: {
|
|
29
|
+
LineItem: [
|
|
30
|
+
{ Value: x_isbn },
|
|
31
|
+
... up to { Value: title },
|
|
32
|
+
{ Value: x_author_ID },
|
|
33
|
+
...
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
) {
|
|
37
|
+
x_author @Common: { Text: x_author.name, TextArrangement: #TextOnly };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
annotate CatalogService.Authors with @(
|
|
41
|
+
UI: {
|
|
42
|
+
LineItem: [
|
|
43
|
+
{ Value: ID },
|
|
44
|
+
{ Value: name },
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ID,title,stock,x_isbn,x_author_ID
|
|
2
|
+
101,Harry Potter and the Philosopher's Stone,7,9780747532743,201
|
|
3
|
+
102,Harry Potter and the Chamber of Secrets,5,9780747538493,201
|
|
4
|
+
103,Harry Potter and the Prisoner of Azkaban,12,9780747546290,201
|
|
5
|
+
104,Harry Potter and the Goblet of Fire,3,9780747550990,201
|
|
6
|
+
105,Harry Potter and the Order of the Phoenix,2,9780747551002,201
|
|
7
|
+
106,Harry Potter and the Half-Blood Prince,1,9780747581085,201
|
|
8
|
+
107,Harry Potter and the Deathly Hallows,0,9780747591053,201
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const cds = require('../../../cds')
|
|
2
|
+
const { read, write, exists, copy, path:{join, relative}, readdir, rimraf } = cds.utils
|
|
3
|
+
const { mkdir, symlink } = require('fs/promises')
|
|
4
|
+
const { renderAndCopy } = require('../../template')
|
|
5
|
+
const projectReader = require('../../projectReader')
|
|
6
|
+
const npmPackageRegex = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i
|
|
7
|
+
|
|
8
|
+
module.exports = class ExtensionProjectTemplate extends require('../../plugin') {
|
|
9
|
+
|
|
10
|
+
static help() {
|
|
11
|
+
return 'Extension project'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
options() {
|
|
15
|
+
return {
|
|
16
|
+
'base': {
|
|
17
|
+
type: 'string',
|
|
18
|
+
short: 'p',
|
|
19
|
+
help: `The project to extend.`
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async run() {
|
|
26
|
+
let { base, force } = cds.cli.options
|
|
27
|
+
if (typeof base === 'string' && !base.match(npmPackageRegex)) throw `Illegal package name: ${base}`
|
|
28
|
+
if (typeof base !== 'string' || !base?.length) base = '@capire/bookshop'
|
|
29
|
+
|
|
30
|
+
const pkgJson = exists('package.json') ? await read('package.json') : { name: 'my-extension-project' }
|
|
31
|
+
if (!pkgJson.extends || force) {
|
|
32
|
+
pkgJson.extends = base
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!pkgJson.workspaces?.includes('.base')) {
|
|
36
|
+
pkgJson.workspaces ??= []
|
|
37
|
+
pkgJson.workspaces.push('.base')
|
|
38
|
+
}
|
|
39
|
+
await write('package.json', pkgJson, { spaces: 2 })
|
|
40
|
+
|
|
41
|
+
if (!exists('.base/package.json') || force) {
|
|
42
|
+
await write('.base/package.json', {name: base})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await nodeModulesLink(base)
|
|
46
|
+
// remove empty and irrelevant folders
|
|
47
|
+
await Promise.all([ rmIfEmpty('db') ])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async addSample() {
|
|
51
|
+
const proj = projectReader.readProject()
|
|
52
|
+
const base = proj.extendsApp
|
|
53
|
+
if (!base) throw 'No project to extend found in package.json. Create a new extension project with "cds init extension"'
|
|
54
|
+
|
|
55
|
+
const projectSimple = base.replace(/^.*?\//, '')
|
|
56
|
+
const extensionNamespace = `${namespacify(proj.appName)}.${namespacify(projectSimple)}`
|
|
57
|
+
const { force } = cds.cli.options
|
|
58
|
+
|
|
59
|
+
if (!exists('.base/index.cds') || force) {
|
|
60
|
+
await copy(join(__dirname, 'files/.base/index.cds'), '.base/index.cds')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!exists('app/extension.cds') || force) {
|
|
64
|
+
await renderAndCopy(join(__dirname, 'files/app'), 'app', {
|
|
65
|
+
basePackage: base,
|
|
66
|
+
extensionNamespace
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
if (!exists('test/data') || force) {
|
|
70
|
+
await copy(join(__dirname, 'files/test/data/my.bookshop-Books.csv'), 'test/data/my.bookshop-Books.csv')
|
|
71
|
+
await copy(join(__dirname, 'files/test/data/Authors.csv'), `test/data/${extensionNamespace}-x_Authors.csv`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// async finalize() {
|
|
77
|
+
// console.log(`For information on project "Piper" and its pipelines see ${URLS.PIPELINE_HELP}`);
|
|
78
|
+
// }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function namespacify(s) {
|
|
82
|
+
return s.replace(/[/.-]/g, '_').replace(/[@]/g, '').toLowerCase()
|
|
83
|
+
}
|
|
84
|
+
async function rmIfEmpty(s) {
|
|
85
|
+
exists(s) && !(await readdir(s)).length && await rimraf(s)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Allow subsequent templates like `cds add handler` to resolve the model w/o `npm i`.
|
|
89
|
+
// So create a symlink from node_modules to .base, anticipating what `npm i` would do.
|
|
90
|
+
async function nodeModulesLink(base) {
|
|
91
|
+
const link = join(cds.root, 'node_modules', base)
|
|
92
|
+
if (exists(link)) return
|
|
93
|
+
const scope = base.indexOf('/') > 0 && base.split('/')[0] // @scope/pkg
|
|
94
|
+
const target = join(cds.root, 'node_modules', scope||'')
|
|
95
|
+
await mkdir(target, { recursive: true })
|
|
96
|
+
const relTarget = relative(target, join(cds.root, '.base'))
|
|
97
|
+
// console.log(`> linking ${relTarget} to ${link}`)
|
|
98
|
+
await symlink(relTarget, link, 'junction')
|
|
99
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{{#hasTyper}}
|
|
2
|
+
const { {{entityNames}} } = require('#cds-models/{{{servicePath}}}')
|
|
3
|
+
{{/hasTyper}}
|
|
4
|
+
{{^hasTyper}}
|
|
5
|
+
const { {{entityNames}} } = this.entities
|
|
6
|
+
{{/hasTyper}}
|
|
7
|
+
|
|
8
|
+
module.exports = async function(req) {
|
|
9
|
+
{{#entityName}}console.log('{{when}} {{event}} {{entityName}}'){{/entityName}}{{^entityName}}console.log('{{when}} {{event}}'){{/entityName}}
|
|
10
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = require('../../../cds')
|
|
2
|
-
const { fs: { promises: { rename } }, path: { basename, dirname, join }, exists } = cds.utils
|
|
2
|
+
const { fs: { promises: { rename } }, read, write, path: { basename, dirname, join }, exists } = cds.utils
|
|
3
3
|
const { readProject } = require('../../projectReader')
|
|
4
4
|
const { renderAndCopy } = require('../../template')
|
|
5
5
|
const mvn = require('../../mvn')
|
|
@@ -36,34 +36,28 @@ module.exports = class Handler extends require('../../plugin') {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
async run() {
|
|
39
|
-
const
|
|
40
|
-
if (isJava) {
|
|
39
|
+
const proj = readProject()
|
|
40
|
+
if (proj.isJava) {
|
|
41
41
|
await mvn.generate('handler')
|
|
42
42
|
} else {
|
|
43
|
-
await createNodeHandler()
|
|
43
|
+
await createNodeHandler(proj)
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* @param {ReturnType<readProject} proj
|
|
50
|
+
*/
|
|
51
|
+
async function createNodeHandler(proj) {
|
|
49
52
|
const { filter, force, out=cds.env.folders.srv } = cds.cli.options
|
|
50
|
-
const proj = readProject()
|
|
51
53
|
const csn = await loadModel()
|
|
52
|
-
if (!csn) return console.log(dim(`>
|
|
54
|
+
if (!csn) return console.log(dim(`> skipping, no model found`))
|
|
53
55
|
|
|
54
56
|
const nameFilter = filterStringAsRegex(filter)
|
|
55
57
|
|
|
56
58
|
for (const service of csn.services) {
|
|
57
59
|
if (!service.$location.file) continue
|
|
58
60
|
|
|
59
|
-
const destFileType = proj.isTypescript ? '.ts' : '.js'
|
|
60
|
-
const outPath = join(out, basename(service.$location.file.replace('.cds', destFileType)))
|
|
61
|
-
const destFile = join(cds.root, outPath)
|
|
62
|
-
if (!force && exists(destFile)) {
|
|
63
|
-
console.log(dim(`> Skipping ${outPath}`))
|
|
64
|
-
continue
|
|
65
|
-
}
|
|
66
|
-
|
|
67
61
|
proj.serviceName = service.name
|
|
68
62
|
proj.serviceClass = service.name.split('.').pop()
|
|
69
63
|
proj.servicePath = proj.serviceName ? (proj.serviceName.replace(/\./g, '/')) : ''
|
|
@@ -92,11 +86,67 @@ async function createNodeHandler() {
|
|
|
92
86
|
else if (all.length)
|
|
93
87
|
proj.allNames = all.map(e => e.name).join(', ')
|
|
94
88
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
if (proj.extendsApp) {
|
|
90
|
+
await createExtCodeHandler(service, proj, out, force)
|
|
91
|
+
} else {
|
|
92
|
+
const destFileType = proj.isTypescript ? '.ts' : '.js'
|
|
93
|
+
const outPath = join(out, basename(service.$location.file.replace('.cds', destFileType)))
|
|
94
|
+
const destFile = join(cds.root, outPath)
|
|
95
|
+
if (!force && exists(destFile)) {
|
|
96
|
+
console.log(dim(`> skipping ${outPath}`))
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
console.log(dim(`> writing ${outPath}`))
|
|
100
|
+
const destPath = dirname(destFile)
|
|
101
|
+
await renderAndCopy(join(__dirname, 'files/standard'), destPath, proj)
|
|
102
|
+
await rename(join(destPath, 'handler.xs'), destFile)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function createExtCodeHandler(service, proj, out, force) {
|
|
108
|
+
const destFileType = '.js' // only JS for now, a TS toolchain in an ext. project would be too complex
|
|
109
|
+
const srvOutPath = join(cds.root, out, service.name)
|
|
110
|
+
|
|
111
|
+
// entities
|
|
112
|
+
const events = [['after', 'READ'], ['before', 'CREATE'], ['before', 'UPDATE']]
|
|
113
|
+
for (const ent of proj.entities) {
|
|
114
|
+
proj.entityName = ent.name
|
|
115
|
+
const entityOutPath = join(srvOutPath, ent.name)
|
|
116
|
+
for (const [when, event] of events) {
|
|
117
|
+
proj.when = when
|
|
118
|
+
proj.event = event
|
|
119
|
+
const outPath = join(entityOutPath, `${when}-${event}${destFileType}`)
|
|
120
|
+
if (!force && exists(outPath)) {
|
|
121
|
+
console.log(dim(`> skipping ${outPath}`))
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
console.log(dim(`> writing ${outPath}`))
|
|
125
|
+
await renderAndCopy(join(__dirname, 'files/extension'), entityOutPath, proj)
|
|
126
|
+
await rename(join(entityOutPath, 'handler.xs'), outPath)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// unbound actions
|
|
131
|
+
proj.when = 'on'
|
|
132
|
+
proj.entityName = ''
|
|
133
|
+
for (const op of proj.operations) {
|
|
134
|
+
proj.event = op.name
|
|
135
|
+
const outPath = join(srvOutPath, `on-${op.name}${destFileType}`)
|
|
136
|
+
if (!force && exists(outPath)) {
|
|
137
|
+
console.log(dim(`> skipping ${outPath}`))
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
console.log(dim(`> writing ${outPath}`))
|
|
141
|
+
await renderAndCopy(join(__dirname, 'files/extension'), dirname(outPath), proj)
|
|
142
|
+
await rename(join(dirname(outPath), 'handler.xs'), outPath)
|
|
99
143
|
}
|
|
144
|
+
|
|
145
|
+
// package.json
|
|
146
|
+
const pkgJson = await read('package.json')
|
|
147
|
+
const pkgJsonDelta = require('./files/extension/package.json')
|
|
148
|
+
deepMerge(pkgJson, pkgJsonDelta)
|
|
149
|
+
await write('package.json', pkgJson, { spaces: 2 })
|
|
100
150
|
}
|
|
101
151
|
|
|
102
152
|
/**
|
|
@@ -107,6 +157,19 @@ async function loadModel() {
|
|
|
107
157
|
return cds.linked(cds.minify(await cds.load([cds.env.roots])))
|
|
108
158
|
} catch (err) {
|
|
109
159
|
if (err.code === 'MODEL_NOT_FOUND') return null
|
|
110
|
-
throw err
|
|
160
|
+
throw new Error(`Error compiling CDS files. Run 'npm install' and try again.`, {cause:err})
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function deepMerge(obj1, obj2) {
|
|
165
|
+
for (const key in obj2) {
|
|
166
|
+
if (Object.prototype.hasOwnProperty.call(obj2, key)) {
|
|
167
|
+
if (obj2[key] instanceof Object && obj1[key] instanceof Object) {
|
|
168
|
+
obj1[key] = deepMerge(obj1[key], obj2[key])
|
|
169
|
+
} else {
|
|
170
|
+
obj1[key] = obj2[key]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
111
173
|
}
|
|
174
|
+
return obj1
|
|
112
175
|
}
|
|
@@ -304,13 +304,13 @@ module.exports = class HttpTemplate extends require('../../plugin') {
|
|
|
304
304
|
const outputPath = this.#determineOutputPath(serviceName)
|
|
305
305
|
if (exists(outputPath)) {
|
|
306
306
|
if (cds.cli.options.force) {
|
|
307
|
-
console.log(term.dim(`
|
|
307
|
+
console.log(term.dim(` overwriting ${outputPath}`))
|
|
308
308
|
} else {
|
|
309
|
-
console.log(term.dim(`
|
|
309
|
+
console.log(term.dim(` skipping ${outputPath}`))
|
|
310
310
|
return
|
|
311
311
|
}
|
|
312
312
|
} else {
|
|
313
|
-
console.log(term.dim(`
|
|
313
|
+
console.log(term.dim(` creating ${outputPath}`))
|
|
314
314
|
}
|
|
315
315
|
write(http).to(outputPath)
|
|
316
316
|
}
|
|
@@ -22,7 +22,7 @@ module.exports = class JavaTemplate extends require('../../plugin') {
|
|
|
22
22
|
throw `You can't change the runtime of an existing project.`
|
|
23
23
|
}
|
|
24
24
|
if (options.add?.has(NODEJS)) {
|
|
25
|
-
throw `
|
|
25
|
+
throw `only one runtime per project is supported – specify either ${bold(JAVA)} or ${bold(NODEJS)}`
|
|
26
26
|
}
|
|
27
27
|
return true
|
|
28
28
|
}
|
|
@@ -30,7 +30,7 @@ module.exports = class JavaTemplate extends require('../../plugin') {
|
|
|
30
30
|
async run() {
|
|
31
31
|
const projectName = path.basename(cds.root)
|
|
32
32
|
const { params, artifactId, archetypeVersion } = await mvn.init(projectName)
|
|
33
|
-
console.log(`
|
|
33
|
+
console.log(`using Maven archetype version ${archetypeVersion}`)
|
|
34
34
|
|
|
35
35
|
const temp = await mkdtemp(join(os.tmpdir(), `${path.basename(cds.root)}_`))
|
|
36
36
|
try {
|
|
@@ -47,6 +47,6 @@ module.exports = class JavaTemplate extends require('../../plugin') {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async finalize() {
|
|
50
|
-
console.log(`
|
|
50
|
+
console.log(`learn about next steps at ${link(URLS.CAPIRE)}`)
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -28,6 +28,6 @@ module.exports = class MtaTemplate extends require('../../plugin') {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
async finalize() {
|
|
31
|
-
if (!exists('pom.xml')) console.log(`\
|
|
31
|
+
if (!exists('pom.xml')) console.log(`\nrun ${bold('npm update --package-lock-only')} to freeze dependencies`)
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -21,7 +21,7 @@ module.exports = class NodejsTemplate extends require('../../plugin') {
|
|
|
21
21
|
throw `You can't change the runtime of an existing project.`
|
|
22
22
|
}
|
|
23
23
|
if (cds.cli.options.add?.has(JAVA)) {
|
|
24
|
-
throw `
|
|
24
|
+
throw `only one runtime per project is supported – specify either ${term.bold(JAVA)} or ${term.bold(NODEJS)}`
|
|
25
25
|
}
|
|
26
26
|
return true
|
|
27
27
|
}
|
|
@@ -33,7 +33,7 @@ module.exports = class NodejsTemplate extends require('../../plugin') {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
async finalize() {
|
|
36
|
-
console.log(
|
|
37
|
-
console.log(`
|
|
36
|
+
console.log(`\nfind samples on ${term.link(URLS.SAMPLES)}`)
|
|
37
|
+
console.log(`learn about next steps at ${term.link(URLS.CAPIRE)}`)
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -26,6 +26,6 @@ module.exports = class PipelineTemplate extends require('../../plugin') {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
async finalize() {
|
|
29
|
-
console.log(`
|
|
29
|
+
console.log(`for information on project "Piper" and its pipelines see ${URLS.PIPELINE_HELP}`);
|
|
30
30
|
}
|
|
31
31
|
}
|