@sap/cds-dk 9.6.0 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/bin/add.js +5 -2
  3. package/bin/cds.js +11 -26
  4. package/bin/compile/index.js +3 -2
  5. package/bin/data-products/import.js +3 -0
  6. package/bin/debug.js +76 -16
  7. package/bin/env.js +2 -1
  8. package/bin/export.js +18 -13
  9. package/bin/help/export.md +2 -1
  10. package/bin/import.js +21 -18
  11. package/bin/init.js +8 -1
  12. package/bin/minify.js +11 -12
  13. package/bin/mock.js +17 -9
  14. package/bin/repl.js +2 -2
  15. package/bin/up.js +34 -4
  16. package/bin/version.js +202 -131
  17. package/lib/build/buildTaskEngine.js +4 -0
  18. package/lib/build/buildTaskFactory.js +5 -35
  19. package/lib/build/index.js +10 -10
  20. package/lib/build/plugin.js +6 -3
  21. package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +3 -1
  22. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/_helpers.tpl +1 -1
  23. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +8 -0
  24. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +18 -0
  25. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +11 -1
  26. package/lib/build/plugins.js +8 -16
  27. package/lib/build/provider/hana/index.js +16 -19
  28. package/lib/build/provider/internalBuildPlugin.js +28 -4
  29. package/lib/build/provider/internalBuildTaskProvider.js +26 -88
  30. package/lib/build/provider/mtx/index.js +0 -8
  31. package/lib/build/provider/mtx-sidecar/index.js +88 -80
  32. package/lib/build/provider/nodejs/index.js +3 -8
  33. package/lib/build/util.js +52 -87
  34. package/lib/deploy/to-hana/hana.js +117 -109
  35. package/lib/env/schemas/cds-rc.js +0 -4
  36. package/lib/import/odata/v4parser.js +80 -56
  37. package/lib/init/constants.js +5 -2
  38. package/lib/init/index.js +44 -41
  39. package/lib/init/merge.js +7 -2
  40. package/lib/init/projectReader.js +4 -10
  41. package/lib/init/template/ams/index.js +1 -4
  42. package/lib/init/template/attachments/files/package.json.hbs +1 -1
  43. package/lib/init/template/data/index.js +1 -1
  44. package/lib/init/template/extension/index.js +4 -4
  45. package/lib/init/template/hana/files/package.json.hbs +3 -3
  46. package/lib/init/template/handler/index.js +1 -1
  47. package/lib/init/template/helm/index.js +3 -3
  48. package/lib/init/template/helm-unified-runtime/index.js +5 -1
  49. package/lib/init/template/html5-repo/files/mta.yaml.hbs +1 -1
  50. package/lib/init/template/ias/files/mta.yaml.hbs +3 -3
  51. package/lib/init/template/initial/files/.vscode/tasks.json +10 -0
  52. package/lib/init/template/{nodejs/files/README.md → initial/files/readme.md} +2 -3
  53. package/lib/init/template/initial/index.js +15 -0
  54. package/lib/init/template/java/index.js +4 -11
  55. package/lib/init/template/kyma/index.js +21 -20
  56. package/lib/init/template/lint/index.js +3 -1
  57. package/lib/init/template/mta/index.js +0 -5
  58. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +1 -2
  59. package/lib/init/template/multitenancy/files/values.yaml.hbs +30 -0
  60. package/lib/init/template/nodejs/files/.vscode/tasks.json +1 -14
  61. package/lib/init/template/nodejs/files/package.json.hbs +2 -4
  62. package/lib/init/template/nodejs/index.js +8 -12
  63. package/lib/init/template/sample/files/app/common.cds +1 -1
  64. package/lib/init/template/sample/index.js +5 -6
  65. package/lib/init/template/sqlite/files/package.json.hbs +7 -1
  66. package/lib/init/template/test/index.js +1 -1
  67. package/lib/init/template/tiny-sample/files/nodejs/db/data/CatalogService.Books.csv +6 -0
  68. package/lib/init/template/tiny-sample/files/nodejs/srv/cat-service.cds +5 -0
  69. package/lib/init/template/tiny-sample/index.js +2 -2
  70. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +6 -0
  71. package/lib/init/template/workzone-standard/index.js +13 -3
  72. package/lib/init/validate.js +11 -6
  73. package/lib/util/checksum.js +2 -2
  74. package/lib/util/fs.js +109 -53
  75. package/lib/util/glob.js +2 -1
  76. package/lib/util/term.js +4 -8
  77. package/lib/util/write.js +2 -4
  78. package/lib/watch/livereload-server.js +4 -3
  79. package/lib/watch/node-watch.js +5 -8
  80. package/lib/watch/watched.js +1 -1
  81. package/lib/watch/watcher.js +136 -0
  82. package/npm-shrinkwrap.json +245 -284
  83. package/package.json +2 -3
  84. package/lib/init/template/nodejs/files/.vscode/settings.json +0 -11
  85. package/lib/init/template/tiny-sample/files/nodejs/db/data/my.bookshop-Books.csv +0 -3
  86. package/lib/init/template/tiny-sample/files/nodejs/db/schema.cds +0 -7
  87. package/lib/init/template/tiny-sample/files/nodejs/srv/cat-service.cds.hbs +0 -5
@@ -108,3 +108,33 @@ sidecar:
108
108
  path: /health
109
109
  readiness:
110
110
  path: /health
111
+
112
+ mtx-upgrade:
113
+ bindings:
114
+ {{#isJava}}
115
+ {{#hasXsuaa}}
116
+ saas-registry:
117
+ serviceInstanceName: saas-registry
118
+ {{/hasXsuaa}}
119
+ {{^hasXsuaa}}
120
+ subscription-manager:
121
+ serviceInstanceName: subscription-manager
122
+ {{/hasXsuaa}}
123
+ {{/isJava}}
124
+ service-manager:
125
+ serviceInstanceName: service-manager
126
+ image:
127
+ repository: {{appName}}-sidecar
128
+ resources:
129
+ limits:
130
+ ephemeral-storage: 1G
131
+ memory: 1G
132
+ requests:
133
+ ephemeral-storage: 1G
134
+ cpu: 1000m
135
+ memory: 1G
136
+ command: ["launcher"]
137
+ args:
138
+ - "cds-mtx"
139
+ - "upgrade"
140
+ - '*'
@@ -4,20 +4,7 @@
4
4
  {
5
5
  "type": "shell",
6
6
  "label": "cds watch",
7
- "command": "cds",
8
- "args": ["watch"],
9
- "group": {
10
- "kind": "build",
11
- "isDefault": true
12
- },
13
- "problemMatcher": []
14
- },
15
- {
16
- "type": "shell",
17
- "label": "cds serve",
18
- "command": "cds",
19
- "args": ["serve", "--with-mocks", "--in-memory?"],
20
- "problemMatcher": []
7
+ "command": "cds watch",
21
8
  }
22
9
  ]
23
10
  }
@@ -1,10 +1,8 @@
1
1
  {
2
2
  "name": "{{ projectName }}",
3
3
  "version": "1.0.0",
4
- "description": "A simple CAP project.",
5
4
  "dependencies": {
6
- "@sap/cds": "^9",
7
- "express": "^4"
5
+ "@sap/cds": "^9"
8
6
  },
9
7
  "devDependencies": {
10
8
  "@cap-js/sqlite": "^2"
@@ -13,4 +11,4 @@
13
11
  "start": "cds-serve"
14
12
  },
15
13
  "private": true
16
- }
14
+ }
@@ -1,9 +1,8 @@
1
- const { join } = require('path')
2
1
  const cds = require('../../../cds')
3
2
  const { exists, mkdirp, path } = cds.utils
4
- const { env4 } = require('../../projectReader')
5
- const { URLS, OPTIONS: { NODEJS, JAVA }, COMMAND_ADD } = require('../../constants')
3
+ const { OPTIONS: { NODEJS, JAVA } } = require('../../constants')
6
4
  const term = require('../../../util/term')
5
+ const { merge } = require('../../merge')
7
6
  const { renderAndCopy } = require('../../template')
8
7
 
9
8
  module.exports = class NodejsTemplate extends require('../../plugin') {
@@ -17,9 +16,6 @@ module.exports = class NodejsTemplate extends require('../../plugin') {
17
16
  }
18
17
 
19
18
  async canRun() {
20
- if (cds.cli.command === COMMAND_ADD) {
21
- throw `You can't change the runtime of an existing project.`
22
- }
23
19
  if (cds.cli.options.add?.has(JAVA)) {
24
20
  throw `only one runtime per project is supported – specify either ${term.bold(JAVA)} or ${term.bold(NODEJS)}`
25
21
  }
@@ -27,13 +23,13 @@ module.exports = class NodejsTemplate extends require('../../plugin') {
27
23
  }
28
24
 
29
25
  async run() {
30
- const { db, srv, app } = env4('production').folders
31
- await renderAndCopy(join(__dirname, 'files'), cds.root, { projectName: path.basename(cds.root) })
26
+ const { db, srv, app } = cds.env.folders
27
+ if (cds.cli.command === 'init' && cds.cli.options.force) {
28
+ await renderAndCopy(path.join(__dirname, 'files'), cds.root, { projectName: path.basename(cds.root) })
29
+ } else {
30
+ await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: { projectName: path.basename(cds.root) }})
31
+ }
32
32
  await Promise.all([db, srv, app].map(p => mkdirp(p)))
33
33
  }
34
34
 
35
- async finalize() {
36
- console.log(`\nfind samples on ${term.link(URLS.SAMPLES)}`)
37
- console.log(`learn about next steps at ${term.link(URLS.CAPIRE)}`)
38
- }
39
35
  }
@@ -226,7 +226,7 @@ annotate common.Currencies with @(UI : {
226
226
  //
227
227
  // Currencies Elements
228
228
  //
229
- annotate common.Currencies with {
229
+ annotate sap.common.Currencies with {
230
230
  numcode @title: '{i18n>NumCode}';
231
231
  minor @title: '{i18n>MinorUnit}';
232
232
  exponent @title: '{i18n>Exponent}';
@@ -1,6 +1,7 @@
1
1
  const { join } = require('path')
2
- const cds = require('../../../cds'), { read, write, copy } = cds.utils
3
- const { env4, readProject } = require('../../projectReader')
2
+ const cds = require('../../../cds')
3
+ const { read, write, copy } = cds.utils
4
+ const { readProject } = require('../../projectReader')
4
5
  const { merge } = require('../../merge')
5
6
  const { renderAndCopy } = require('../../template')
6
7
  const mvn = require('../../mvn')
@@ -18,10 +19,8 @@ module.exports = class SampleTemplate extends require('../../plugin') {
18
19
  const ExtensionGenerator = require('../extension')
19
20
  return new ExtensionGenerator().addSample()
20
21
  }
21
-
22
- const env = env4('production')
23
- const { db, srv, app } = env.folders
24
- const i18n = env.i18n.folders[0]
22
+ const { db, srv, app } = cds.env.folders
23
+ const i18n = cds.env.i18n.folders[0]
25
24
  const { appName, appPath } = project
26
25
 
27
26
  if (project.language === 'java') {
@@ -1,7 +1,13 @@
1
1
  {
2
+ {{#profileProduction}}
3
+ "dependencies": {
4
+ "@cap-js/sqlite": "^2"
5
+ }
6
+ {{/profileProduction}}
7
+ {{^profileProduction}}
2
8
  "devDependencies": {
3
9
  "@cap-js/sqlite": "^2"
4
- }{{#profile}},
10
+ }{{/profileProduction}}{{#profile}},
5
11
  "cds": {
6
12
  "requires": {
7
13
  "[{{profile}}]": {
@@ -144,7 +144,7 @@ async function createNodeTest(proj) {
144
144
  */
145
145
  async function loadModel() {
146
146
  try {
147
- return cds.linked(cds.minify(await cds.load([cds.env.roots])))
147
+ return cds.linked(cds.minify(await cds.load(cds.env.roots)))
148
148
  } catch (err) {
149
149
  if (err.code === 'MODEL_NOT_FOUND') return null
150
150
  throw new Error(`Error compiling CDS files. Run 'npm install' and try again.`, {cause:err})
@@ -0,0 +1,6 @@
1
+ ID,title,author
2
+ 1,Wuthering Heights,Emily Brontë
3
+ 2,Jane Eyre,Charlotte Brontë
4
+ 3,The Raven,Edgar Allen Poe
5
+ 4,Eleonora,Edgar Allen Poe
6
+ 5,Catweazle,Richard Carpenter
@@ -0,0 +1,5 @@
1
+ @odata service CatalogService {
2
+ entity Books {
3
+ key ID:Integer; title:String; author:String;
4
+ }
5
+ }
@@ -2,7 +2,7 @@ const { join } = require('path')
2
2
  const cds = require('../../../../lib/cds'), { copy } = cds.utils
3
3
  const { renderAndCopy } = require('../../template')
4
4
  const mvn = require('../../mvn')
5
- const { env4, readProject } = require('../../projectReader')
5
+ const { readProject } = require('../../projectReader')
6
6
  const { JAVA } = require('../../constants')
7
7
 
8
8
  module.exports = class TinySampleTemplate extends require('../../plugin') {
@@ -18,7 +18,7 @@ module.exports = class TinySampleTemplate extends require('../../plugin') {
18
18
  await mvn.add('tiny_sample')
19
19
  }
20
20
  } else {
21
- const { db, srv } = env4('production').folders
21
+ const { db, srv } = cds.env.folders
22
22
  await Promise.all([
23
23
  copy(join(__dirname, 'files', 'nodejs', 'db')).to(db),
24
24
  renderAndCopy(join(__dirname, 'files', 'nodejs', 'srv'), srv, { dbFolder: db.replace(/[\\/]+$/, '') })
@@ -88,3 +88,9 @@ resources:
88
88
  parameters:
89
89
  service: html5-apps-repo
90
90
  service-plan: app-host
91
+
92
+ - name: {{appName}}-auth
93
+ type: org.cloudfoundry.managed-service
94
+ parameters:
95
+ service: xsuaa
96
+ service-plan: application
@@ -3,7 +3,7 @@ const { read, write, exists } = cds.utils
3
3
  const { readProject } = require('../../projectReader')
4
4
  const { merge } = require('../../merge')
5
5
  const { join } = require('path')
6
- const { destination, destinations, srv4, html5RepoHost, mtxSidecar4 } = require('../../registries/mta')
6
+ const { destination, destinations, xsuaa, srv4, html5RepoHost, mtxSidecar4 } = require('../../registries/mta')
7
7
  const { odataApi } = require('../../registries/xs-app')
8
8
 
9
9
  module.exports = class extends require('../../plugin') {
@@ -41,7 +41,7 @@ module.exports = class extends require('../../plugin') {
41
41
 
42
42
  async combine() {
43
43
  const project = readProject()
44
- const { isJava, addMta, hasMta, srvPath, apps, appPath, appUIPaths, addMultitenancy, strippedAppName } = project
44
+ const { isJava, addMta, hasMta, srvPath, apps, appPath, appUIPaths, addMultitenancy, addXsuaa, strippedAppName } = project
45
45
 
46
46
  await Promise.all(appUIPaths.map(async p => {
47
47
  project.uiApp = p
@@ -78,7 +78,7 @@ module.exports = class extends require('../../plugin') {
78
78
  await write('mtx/sidecar/package.json', mtxPackageJson, { spaces: 2 })
79
79
  }
80
80
 
81
- if (addMta || addMultitenancy && hasMta) {
81
+ if (addMta || (addMultitenancy || addXsuaa) && hasMta) {
82
82
  const srv = srv4(srvPath)
83
83
  const additions = [srv, destinations, destination, {
84
84
  in: [destination, 'requires'],
@@ -91,6 +91,16 @@ module.exports = class extends require('../../plugin') {
91
91
  into: [mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar'), 'requires', 'name']
92
92
  })
93
93
  }
94
+ if (addXsuaa) {
95
+ relationships.push({
96
+ insert: [xsuaa],
97
+ into: [destinations, 'requires']
98
+ })
99
+ additions.push({
100
+ in: [destinations, 'parameters.content.instance.destinations' ],
101
+ where: { Authentication: 'OAuth2UserTokenExchange' }
102
+ })
103
+ }
94
104
 
95
105
  await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', { with: project, additions, relationships })
96
106
  }
@@ -1,15 +1,20 @@
1
1
  const { PROJECT_FILES } = require('./constants')
2
- const REGEX_PROJECT_NAME = /[^0-9a-zA-Z_-]+/g
2
+
3
+ // max. 64 chars, starts with letter, number, '_', contains only letters (upper and lower case), numbers, '_' or '-'
4
+ const REGEX_PROJECT_NAME = /^[0-9a-z_][0-9a-z_-]{0,63}$/i
5
+
3
6
  const cds = require('../../lib/cds')
4
7
  const { exists } = cds.utils
5
8
  const { bold } = require('../../lib/util/term')
6
9
 
7
10
  module.exports.projectName = projectName => {
8
- if (!projectName) throw 'specify a project name'
9
- const invalidChars = new Set(projectName.match(REGEX_PROJECT_NAME)) // remove duplicates
10
- if (invalidChars.size > 0) {
11
- throw `project name '${projectName}' contains at least one character ('${[...invalidChars]}') which is not supported in npm package names.
12
- Specify a different project name by replacing characters with '_' or '-', for example '${projectName.replace(REGEX_PROJECT_NAME, '_')}' or '${projectName.replace(REGEX_PROJECT_NAME, '-')}'.`
11
+ if (!projectName) throw 'Specify a project name.'
12
+ if (!projectName.match(REGEX_PROJECT_NAME)) {
13
+ throw `This project name is not supported.
14
+ Specify a different project name which
15
+ starts with a letter, number or '_' and
16
+ contains only letters, numbers, '_' or '-' and
17
+ is 64 characters or less.`
13
18
  }
14
19
  }
15
20
 
@@ -1,7 +1,7 @@
1
1
  const cds = require('../../lib/cds')
2
2
  const { read, stat } = cds.utils
3
3
  const crypto = require('crypto')
4
- const { getFiles } = require('../build/util')
4
+ const { find } = require('../util/fs')
5
5
 
6
6
  /**
7
7
  * Calculate a checksum for a directory, excluding specified patterns.
@@ -25,7 +25,7 @@ async function calcDirChecksum(dirPath, filter = [], hashFunction = 'sha256', op
25
25
  return hash.digest('hex')
26
26
  }
27
27
 
28
- const files = [...(await getFiles(dirPath, filter, [], true)), ...optionalFiles].sort()
28
+ const files = [...(await find(dirPath, { filter, ignoreSymlinks: true })), ...optionalFiles].sort()
29
29
  for (const file of files) {
30
30
  try {
31
31
  hash.update(file)
package/lib/util/fs.js CHANGED
@@ -1,69 +1,125 @@
1
- const { parse } = require('node:path')
2
-
1
+ const os = require('node:os')
2
+ const { parse, join } = require('node:path')
3
+ const { promises: fsp } = require('node:fs')
3
4
  const YAML = require('yaml')
4
5
  const { read, write } = require('../cds').utils
5
6
  const JSONC = require('./jsonc')
6
7
 
7
- // REVISIT: Consider merging with template util
8
-
9
- module.exports = new class FsUtil {
8
+ /* YAML */
10
9
 
11
- constructor() {
12
- this.copyRenderedJSON = this.copyRenderedJSON.bind(this)
13
- this.copyRenderedYAML = this.copyRenderedYAML.bind(this)
10
+ async function readYAML(src, project) {
11
+ let file
12
+ try {
13
+ file = await read(src, 'utf8')
14
+ } catch (e) {
15
+ if (e.code === 'ENOENT') return undefined
16
+ throw e
14
17
  }
15
-
16
- async readYAML(src, project) {
17
- let file
18
- try {
19
- file = await read(src, 'utf8')
20
- } catch (e) {
21
- if (e.code === 'ENOENT') return undefined
22
- throw e
23
- }
24
- if (project) {
25
- const Mustache = require('mustache')
26
- const renderedFile = Mustache.render(file, project)
27
- return YAML.parseDocument(renderedFile)
28
- }
29
- return YAML.parseDocument(file)
18
+ if (project) {
19
+ const Mustache = require('mustache')
20
+ const renderedFile = Mustache.render(file, project)
21
+ return YAML.parseDocument(renderedFile)
30
22
  }
23
+ return YAML.parseDocument(file)
24
+ }
31
25
 
32
- async writeYAML(dst, yaml) {
33
- const content = YAML.stringify(yaml, { collectionStyle: 'block', lineWidth: 150, directives: false })
34
- await write(content).to(dst)
35
- }
26
+ async function writeYAML(dst, yaml) {
27
+ const content = YAML.stringify(yaml, { collectionStyle: 'block', lineWidth: 150, directives: false })
28
+ await write(content).to(dst)
29
+ }
36
30
 
37
- async copyRenderedYAML(src, dst, project) {
38
- const yaml = await this.readYAML(src, project)
39
- await write(yaml.toString()).to(dst)
40
- }
31
+ async function copyRenderedYAML(src, dst, project) {
32
+ const yaml = await readYAML(src, project)
33
+ await write(yaml.toString()).to(dst)
34
+ }
41
35
 
42
- async readJSON(src, project) {
43
- const file = await read(src, 'utf8')
44
- if (project) {
45
- const Mustache = require('mustache')
46
- const renderedFile = Mustache.render(file, project)
47
- return JSON.parse(renderedFile)
48
- }
49
- try {
50
- return JSON.parse(file)
51
- } catch (error) {
52
- throw new Error(error.message + '\n' + file)
53
- }
54
- }
36
+ /* JSON and JSONC */
55
37
 
56
- async readJSONC(src) {
57
- return JSONC.parse(await read(src, 'utf8'))
38
+ async function readJSON(src, project) {
39
+ const file = await read(src, 'utf8')
40
+ if (project) {
41
+ const Mustache = require('mustache')
42
+ const renderedFile = Mustache.render(file, project)
43
+ return JSON.parse(renderedFile)
58
44
  }
59
-
60
- async copyRenderedJSON(src, dst, project) {
61
- const json = await this.readJSON(src, project)
62
- await write(dst, json, { spaces: 2 })
45
+ try {
46
+ return JSON.parse(file)
47
+ } catch (error) {
48
+ throw new Error(error.message + '\n' + file)
63
49
  }
50
+ }
51
+
52
+ async function readJSONC(src) {
53
+ return JSONC.parse(await read(src, 'utf8'))
54
+ }
55
+
56
+ async function copyRenderedJSON(src, dst, project) {
57
+ const json = await readJSON(src, project)
58
+ await write(dst, json, { spaces: 2 })
59
+ }
60
+
61
+ /* General */
62
+
63
+ function isRoot(folder) {
64
+ const { root, dir } = parse(folder)
65
+ return root === dir
66
+ }
67
+
68
+ const DEFAULT_CONCURRENCY = (() => {
69
+ const cpu = os.availableParallelism()
70
+ const uv = Number(process.env.UV_THREADPOOL_SIZE) || 4
71
+ // Keep libuv busy without building huge backlogs. Clamp to [8,64].
72
+ return Math.min(64, Math.max(8, Math.min(cpu * 4, uv * 2)))
73
+ })()
64
74
 
65
- isRoot(folder) {
66
- const { root, dir } = parse(folder)
67
- return root === dir
75
+ /**
76
+ * Recursively finds files in a directory tree with parallel traversal.
77
+ *
78
+ * @param {string} dir - Root directory to search
79
+ * @param {Object} [options] - Search options
80
+ * @param {(file: string) => boolean} [options.filter] - Filter predicate for paths
81
+ * @param {boolean} [options.ignoreSymlinks=false] - Skip symbolic links
82
+ * @returns {Promise<string[]>} Array of matching file paths
83
+ */
84
+ async function find(dir, { filter, ignoreSymlinks = false } = {}) {
85
+ const files = /** @type {string[]} */([])
86
+ const queue = /** @type {string[]} */([dir])
87
+ const active = new Set()
88
+ const visit = async (/** @type {string} */ d) => {
89
+ let entries = /** @type {import('fs').Dirent[]} */([])
90
+ try { entries = await fsp.readdir(d, { withFileTypes: true }) }
91
+ catch { return }
92
+
93
+ await Promise.all(entries.map(async dirent => {
94
+ const p = join(d, dirent.name)
95
+ if (filter && !filter(p)) return
96
+ if (dirent.isDirectory()) { queue.push(p); return }
97
+ if (dirent.isFile()) { files.push(p); return }
98
+ if (!ignoreSymlinks && dirent.isSymbolicLink()) {
99
+ try {
100
+ const stat = await fsp.stat(p)
101
+ stat.isDirectory() ? queue.push(p) : files.push(p)
102
+ } catch { /* ignore */ }
103
+ }
104
+ }))
68
105
  }
106
+ while (queue.length || active.size) {
107
+ while (queue.length && active.size < DEFAULT_CONCURRENCY) {
108
+ const p = visit(queue.shift()).finally(() => active.delete(p))
109
+ active.add(p)
110
+ }
111
+ if (active.size) await Promise.race(active)
112
+ }
113
+ return files
114
+ }
115
+
116
+ module.exports = {
117
+ readYAML,
118
+ writeYAML,
119
+ copyRenderedYAML,
120
+ readJSON,
121
+ readJSONC,
122
+ copyRenderedJSON,
123
+ find,
124
+ isRoot
69
125
  }
package/lib/util/glob.js CHANGED
@@ -9,7 +9,8 @@ const IGNORE_DIRS = new Set(['node_modules', '.git', '.hg', '.svn', 'dist', 'tar
9
9
  * Returns an AsyncIterator of those (recursive) subdirectories that match the glob pattern.
10
10
  * Skips common large directories (node_modules, .git, .hg, .svn, dist, target, build, out).
11
11
  * @param {string} pattern - The glob pattern to match against.
12
- * @param {string} cwd - The current working directory to start the search from.
12
+ * @param {object} options
13
+ * @param {string} options.cwd - The current working directory to start the search from.
13
14
  * @returns An array of relative paths that match the glob pattern.
14
15
  */
15
16
  async function* glob(pattern, { cwd } = { cwd: process.cwd() }) {
package/lib/util/term.js CHANGED
@@ -34,7 +34,9 @@ const format = module.exports.format = (o, severity='Error', asInternalError=fal
34
34
  default : return format.info (o)
35
35
  }
36
36
  }
37
- format.colors = process.stdout.isTTY && process.stderr.isTTY && process.env.NO_COLOR !== 'true' || (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== 'false')
37
+ const forceColor = process.env.FORCE_COLOR && process.env.FORCE_COLOR !== 'false'
38
+ const rendersColors = process.stdout.isTTY && process.stderr.isTTY || process.env.GITHUB_ACTIONS
39
+ format.colors = rendersColors && process.env.NO_COLOR !== 'true' || forceColor
38
40
 
39
41
  // decorate.error, .warning, .info
40
42
  // 'Error: foo' -> '[ERROR] foo' (Maven-like, allows for better grepping in logs)
@@ -47,13 +49,7 @@ Object.assign (format, {
47
49
  return `[${asErr('ERROR')}] ${toString(o, 'Error', withStack)}`
48
50
  },
49
51
  warn: o => DEBUG ? o : `[${asWarn('WARNING')}] ${toString(o, 'Warning')}`,
50
- info: o => DEBUG ? o : `[${asInfo('INFO')}] ${toString(o, 'Info')}`,
51
-
52
- poorMarkdown: (md) => { return md
53
- .replace(/\n# ([^\n]*)\n/g, `\n${as(t.bold, '$1')}\n`)
54
- .replace(/ \*([^*]+)\*/g, ` ${as(t.bold, '$1')}`)
55
- .replace(/ _([^_]+)_/g, ` ${as(t.link, '$1')}`)
56
- }
52
+ info: o => DEBUG ? o : `[${asInfo('INFO')}] ${toString(o, 'Info')}`
57
53
  })
58
54
 
59
55
  function toString(o, severity, withStack) {
package/lib/util/write.js CHANGED
@@ -30,7 +30,7 @@ const write = module.exports = Object.assign (
30
30
  * Fluent API to write (output) .to (dest...)
31
31
  * @param content - the content to be written; will be JSON.stringified if it's not a string
32
32
  */
33
- (content) => ({ // NOSONAR
33
+ (content) => ({
34
34
  /**
35
35
  * @param {string} [folder] - optional separately specified destination folder
36
36
  * @param {string} file - the filename to write to
@@ -56,7 +56,7 @@ const write = module.exports = Object.assign (
56
56
  /**
57
57
  * @returns {Promise<[string]>} a Promise resolving to an array of all written filenames
58
58
  */
59
- function _write (content, /*to:*/ dest, _many) { // NOSONAR
59
+ function _write (content, /*to:*/ dest, _many) {
60
60
 
61
61
  if (content instanceof Promise) return content.then (c => _write(c, dest, _many))
62
62
 
@@ -95,5 +95,3 @@ function _write (content, /*to:*/ dest, _many) { // NOSONAR
95
95
  .then (()=> file)
96
96
 
97
97
  }
98
-
99
- /* eslint no-console:off */
@@ -5,10 +5,11 @@ const ws = require('ws')
5
5
  const express = require('express')
6
6
  const { URL } = require('url')
7
7
  const { relative, sep } = require ('path')
8
+ const escapedSep = sep === '\\' ? '\\\\' : sep
8
9
 
9
10
  const ext = 'css,gif,html,jpg,png,svg,ts,mjs,cjs,js,json,properties,change,variant,ctrl_variant,ctrl_variant_change,ctrl_variant_management_change'
10
11
  const watchIncludes = RegExp(`\\.(${ext.replace(/,/g,'|')})$|\\${sep}(webapp|appconfig)`)
11
- const watchExcludes = RegExp(`@cds-models\\${sep}|@types\\${sep}`)
12
+ const watchExcludes = new RegExp(`${escapedSep}(node_modules|\\.git|dist)(${escapedSep}|$)|@cds-models${escapedSep}|@types${escapedSep}`)
12
13
 
13
14
  module.exports = class LiveReload {
14
15
  async start(port, cwd, delay=0) {
@@ -83,14 +84,14 @@ module.exports = class LiveReload {
83
84
  })
84
85
 
85
86
  // Watching for touched files...
86
- const watch = require('node-watch')
87
+ const watch = require('./watcher')
87
88
  const filter = f => {
88
89
  const isIn = !watchExcludes.test(f) && watchIncludes.test(f)
89
90
  if (!isIn) DEBUG?. (`livereload ignored: ${relative(cwd||process.cwd(), f)}`)
90
91
  // else DEBUG?. (`livereload NOT ignored: ${relative(cwd||process.cwd(), f)}`, watchIncludes)
91
92
  return isIn
92
93
  }
93
- const watcher = watch (cwd||process.cwd(),{ recursive:true, filter, delay:0 })
94
+ const watcher = watch (cwd||process.cwd(),{ filter, ignore: watchExcludes })
94
95
  if (DEBUG) watcher.on('close', ()=> DEBUG('⚡️', 'cds watch - livereload file watcher closed'))
95
96
  watcher.on('change', _coalesceEvents(delay, (files) => {
96
97
  DEBUG?. (`livereload for: ${files.map(f => f.name)}`)
@@ -49,7 +49,7 @@ module.exports = ({ cwd = process.cwd(), script, ext, ignore=RegExp(), watched,
49
49
  process.on('shutdown', ()=> _kill(child))
50
50
 
51
51
  // Watching for touched files...
52
- const watch = require('node-watch')
52
+ const watch = require('./watcher')
53
53
  const include = RegExp(`\\.(${ext.replace(/,/g,'|')})$`)
54
54
  const filter = f => {
55
55
  const isIn = !ignore.test(f) && include.test(f)
@@ -57,7 +57,7 @@ module.exports = ({ cwd = process.cwd(), script, ext, ignore=RegExp(), watched,
57
57
  // else DEBUG?. (`NOT ignored: ${relative(cwd||process.cwd(), f)}`, include.test(f), include)
58
58
  return isIn
59
59
  }
60
- const watcher = watch (watched,{ recursive:true, filter, delay:0 })
60
+ const watcher = watch (watched,{ filter, ignore })
61
61
  if (DEBUG) watcher.on('close', ()=> DEBUG('⚡️', 'cds watch - file watcher closed'))
62
62
  watcher.on('change', restart)
63
63
  process.on('shutdown', ()=> watcher.close())
@@ -78,12 +78,9 @@ module.exports = ({ cwd = process.cwd(), script, ext, ignore=RegExp(), watched,
78
78
 
79
79
  emitter.restart = restart
80
80
  emitter.stop = () => _kill(child)
81
- emitter.setEnv = (name, value) => {
82
- if (typeof value === "undefined") {
83
- delete env[name]
84
- } else {
85
- env[name] // REVISIT: no value assigned ???
86
- }
81
+ emitter.setEnv = (name, value) => {
82
+ if (typeof value === 'undefined') delete env[name]
83
+ else env[name] = value
87
84
  }
88
85
  return emitter
89
86
  }
@@ -26,7 +26,7 @@
26
26
  }}
27
27
 
28
28
  function _no_models_found(e) { console.log (`
29
- No models found in ${e.locations||cds.resolve(e.model||'*',false)}.
29
+ No models found in ${(e.files||cds.resolve(e.model||'*',false)).filter(f => !(f in {schema:1,services:2}))}.
30
30
  Waiting for some to arrive...
31
31
  `)}
32
32