@sap/cds-dk 9.5.0 → 9.6.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/bin/cds.js +1 -1
  3. package/bin/compile/index.js +3 -2
  4. package/bin/env.js +3 -3
  5. package/bin/up.js +22 -16
  6. package/bin/util/containerize.js +27 -14
  7. package/lib/build/provider/internalBuildPlugin.js +23 -0
  8. package/lib/build/util.js +33 -16
  9. package/lib/import/common.js +7 -1
  10. package/lib/import/odata/v4parser.js +25 -38
  11. package/lib/init/constants.js +1 -1
  12. package/lib/init/index.js +7 -1
  13. package/lib/init/projectReader.js +21 -10
  14. package/lib/init/template/ams/index.js +2 -2
  15. package/lib/init/template/app-frontend/files/ui5.yaml.hbs +1 -0
  16. package/lib/init/template/app-frontend/index.js +2 -2
  17. package/lib/init/template/application-logging/index.js +2 -2
  18. package/lib/init/template/approuter/index.js +9 -5
  19. package/lib/init/template/attachments/index.js +4 -2
  20. package/lib/init/template/audit-logging/index.js +4 -2
  21. package/lib/init/template/cloud-logging/index.js +4 -2
  22. package/lib/init/template/connectivity/index.js +2 -2
  23. package/lib/init/template/console/files/package.json +5 -0
  24. package/lib/init/template/console/index.js +25 -0
  25. package/lib/init/template/containerize/index.js +4 -2
  26. package/lib/init/template/destination/index.js +4 -2
  27. package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
  28. package/lib/init/template/dynatrace/index.js +4 -2
  29. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +3 -1
  30. package/lib/init/template/enterprise-messaging/index.js +4 -2
  31. package/lib/init/template/enterprise-messaging-shared/index.js +4 -2
  32. package/lib/init/template/github-actions/files/workflows/release.yaml.hbs +2 -2
  33. package/lib/init/template/github-actions/index.js +4 -4
  34. package/lib/init/template/hana/index.js +4 -4
  35. package/lib/init/template/helm/index.js +6 -3
  36. package/lib/init/template/helm-unified-runtime/index.js +1 -0
  37. package/lib/init/template/html5-repo/files/ui5.yaml.hbs +1 -0
  38. package/lib/init/template/html5-repo/index.js +9 -7
  39. package/lib/init/template/ias/index.js +2 -2
  40. package/lib/init/template/java/index.js +14 -0
  41. package/lib/init/template/kafka/index.js +4 -2
  42. package/lib/init/template/kyma/index.js +124 -13
  43. package/lib/init/template/malware-scanner/index.js +4 -2
  44. package/lib/init/template/mta/index.js +1 -1
  45. package/lib/init/template/multitenancy/index.js +7 -4
  46. package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -3
  47. package/lib/init/template/pipeline/files/ui5.yaml.hbs +1 -0
  48. package/lib/init/template/portal/index.js +6 -3
  49. package/lib/init/template/postgres/index.js +2 -2
  50. package/lib/init/template/redis/index.js +2 -3
  51. package/lib/init/template/redis-messaging/index.js +2 -3
  52. package/lib/init/template/sample/files/_i18n/i18n_de.properties +9 -0
  53. package/lib/init/template/sample/files/_i18n/i18n_fr.properties +1 -0
  54. package/lib/init/template/sample/files/app/_i18n/i18n.properties +17 -0
  55. package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +16 -2
  56. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n.properties +3 -11
  57. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_de.properties +3 -0
  58. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_en.properties +3 -0
  59. package/lib/init/template/sample/files/app/admin-authors/webapp/manifest.json +1 -1
  60. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +3 -11
  61. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_de.properties +3 -0
  62. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_en.properties +3 -0
  63. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +1 -1
  64. package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +6 -6
  65. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n.properties +3 -11
  66. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_de.properties +3 -0
  67. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_en.properties +3 -0
  68. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +1 -1
  69. package/lib/init/template/sample/files/app/common.cds +2 -1
  70. package/lib/init/template/sample/files/app/fiori-apps.html +32 -12
  71. package/lib/init/template/sample/files/app/genres/fiori-service.cds +1 -1
  72. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n.properties +3 -4
  73. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_de.properties +3 -2
  74. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_en.properties +3 -0
  75. package/lib/init/template/sample/files/app/genres/webapp/manifest.json +2 -2
  76. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.texts.csv +43 -0
  77. package/lib/init/template/sample/index.js +14 -1
  78. package/lib/init/template/sqlite/files/package.json.hbs +13 -0
  79. package/lib/init/template/sqlite/index.js +1 -1
  80. package/lib/init/template/xsuaa/index.js +5 -2
  81. package/lib/lint/eslintHelp.json +1 -1
  82. package/lib/util/checksum.js +19 -10
  83. package/lib/util/fs.js +8 -1
  84. package/lib/util/glob.js +26 -13
  85. package/lib/util/term.js +3 -1
  86. package/lib/watch/livereload-server.js +4 -3
  87. package/lib/watch/node-watch.js +5 -8
  88. package/lib/watch/watcher.js +136 -0
  89. package/npm-shrinkwrap.json +110 -117
  90. package/package.json +2 -3
  91. package/lib/init/template/sample/files/app/_i18n/i18n_en.properties +0 -8
  92. package/lib/init/template/sqlite/files/package.json +0 -5
  93. /package/lib/init/template/{helm → kyma}/files/Chart.yaml.hbs +0 -0
  94. /package/lib/init/template/{containerize → kyma}/files/containerize.yaml.hbs +0 -0
  95. /package/lib/init/template/{helm → kyma}/files/values.schema.json +0 -0
  96. /package/lib/init/template/{helm → kyma}/files/values.yaml.hbs +0 -0
@@ -0,0 +1,3 @@
1
+ appTitle = Zeige Bücher
2
+ appSubTitle = CAP Beispielanwendung
3
+ appDescription = CAP Beispielanwendung zur Anzeige von Büchern
@@ -0,0 +1,3 @@
1
+ appTitle = Browse Books
2
+ appSubTitle = CAP Sample Application
3
+ appDescription = CAP Sample Application to Browse Books
@@ -35,7 +35,7 @@
35
35
  "semanticObject": "Books",
36
36
  "action": "display",
37
37
  "title": "{{appTitle}}",
38
- "info": "{{appInfo}}",
38
+ "info": "{{appDescription}}",
39
39
  "subTitle": "{{appSubTitle}}",
40
40
  "icon": "sap-icon://course-book",
41
41
  "indicatorDataSource": {
@@ -4,6 +4,7 @@
4
4
 
5
5
  using { sap.capire.bookshop as my } from '../db/schema';
6
6
  using { sap.common } from '@sap/cds/common';
7
+ using { sap.common.Currencies } from '../db/currencies';
7
8
 
8
9
  ////////////////////////////////////////////////////////////////////////////
9
10
  //
@@ -225,7 +226,7 @@ annotate common.Currencies with @(UI : {
225
226
  //
226
227
  // Currencies Elements
227
228
  //
228
- annotate common.Currencies with {
229
+ annotate sap.common.Currencies with {
229
230
  numcode @title: '{i18n>NumCode}';
230
231
  minor @title: '{i18n>MinorUnit}';
231
232
  exponent @title: '{i18n>Exponent}';
@@ -8,23 +8,43 @@
8
8
  <title>Bookshop</title>
9
9
 
10
10
  <script>
11
- window["sap-ushell-config"] = {
12
- defaultRenderer: "fiori2",
13
- applications: {},
14
- };
11
+ window['sap-ushell-config'] = {
12
+ defaultRenderer: 'fiori2',
13
+ renderers: {
14
+ fiori2: {
15
+ componentData: {
16
+ config: {
17
+ enableSearch: false // suppress browser errors
18
+ }
19
+ }
20
+ }
21
+ },
22
+ applications: {}
23
+ }
24
+ </script>
25
+
26
+ <script
27
+ id="sap-ushell-bootstrap"
28
+ src="https://ui5.sap.com/test-resources/sap/ushell/bootstrap/sandbox.js">
29
+ </script>
30
+
31
+ <script
32
+ id="sap-ui-bootstrap"
33
+ src="https://ui5.sap.com/resources/sap-ui-core.js"
34
+ data-sap-ui-libs="sap.m,sap.ushell,sap.collaboration,sap.ui.layout"
35
+ data-sap-ui-compatVersion="edge"
36
+ data-sap-ui-theme="sap_horizon">
15
37
  </script>
16
38
 
17
- <script id="sap-ushell-bootstrap" src="https://ui5.sap.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
18
- <script id="sap-ui-bootstrap" src="https://ui5.sap.com/resources/sap-ui-core.js"
19
- data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout" data-sap-ui-compatVersion="edge"
20
- data-sap-ui-theme="sap_horizon"></script>
21
39
  <script>
22
- sap.ui.getCore().attachInit(() =>
23
- sap.ushell.Container.createRenderer().placeAt("content")
24
- );
40
+ sap.ui.getCore().attachInit(() => {
41
+ sap.ushell.Container
42
+ .createRenderer('fiori2', true)
43
+ .then(renderer => renderer.placeAt('content'))
44
+ })
25
45
  </script>
26
46
  </head>
27
47
 
28
48
  <body class="sapUiBody" id="content"></body>
29
49
 
30
- </html>
50
+ </html>
@@ -28,6 +28,6 @@ annotate Genres with @(UI : {
28
28
 
29
29
 
30
30
  // Tree Views
31
- // annotate AdminService.Genres with @hierarchy; // upcomming simplification
31
+ // annotate AdminService.Genres with @hierarchy; // upcoming simplification
32
32
  using from './tree-view';
33
33
  using from './value-help';
@@ -1,4 +1,3 @@
1
- #XTIT
2
- appTitle=Browse Genres
3
- #XTXT
4
- appDescription=Genres as Tree View
1
+ appTitle = Browse Genres
2
+ appSubTitle = CAP Sample Application
3
+ appDescription = Genres as Tree View
@@ -1,2 +1,3 @@
1
- appTitle=Zeige Genres
2
- appDescription=Genres als Baumansicht
1
+ appTitle = Zeige Genres
2
+ appSubTitle = CAP Beispielanwendung
3
+ appDescription = Genres als Baumansicht
@@ -0,0 +1,3 @@
1
+ appTitle = Browse Genres
2
+ appSubTitle = CAP Sample Application
3
+ appDescription = Genres as Tree View
@@ -25,7 +25,7 @@
25
25
  "additionalParameters": "allowed"
26
26
  },
27
27
  "title": "{{appTitle}}",
28
- "subTitle": "{{appDescription}}",
28
+ "subTitle": "{{appSubTitle}}",
29
29
  "semanticObject": "Genres",
30
30
  "action": "display"
31
31
  }
@@ -123,4 +123,4 @@
123
123
  "registrationIds": [],
124
124
  "archeType": "transactional"
125
125
  }
126
- }
126
+ }
@@ -0,0 +1,43 @@
1
+ ID,locale,name
2
+ 10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Belletristik
3
+ 11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Drama
4
+ 12aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Lyrik
5
+ 13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Fantasy
6
+ 131aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Märchen
7
+ 132aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Epische Fantasy
8
+ 133aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,High Fantasy
9
+ 134aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Schauerroman
10
+ 14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Science-Fiction
11
+ 141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Utopien und Dystopien
12
+ 1411aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Utopie
13
+ 1412aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Dystopie
14
+ 14121aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Cyberpunk
15
+ 141211aa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Steampunk
16
+ 142aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Space Opera
17
+ 143aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Zeitreise
18
+ 144aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Tech-Noir
19
+ 15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Liebesroman
20
+ 151aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Zeitgenössischer Liebesroman
21
+ 152aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Historischer Liebesroman
22
+ 153aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Romantischer Thriller
23
+ 16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Krimi
24
+ 161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Kriminalroman
25
+ 1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Thriller
26
+ 16111aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Polizeikrimi
27
+ 16112aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Justizthriller
28
+ 16113aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Medizinthriller
29
+ 16114aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Spionagethriller
30
+ 1612aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Detektivroman
31
+ 1613aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Spannung
32
+ 162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Noir
33
+ 1621aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Nordic Noir
34
+ 1622aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Tart Noir
35
+ 163aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Cosy Crime
36
+ 17aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Abenteuer
37
+ 18aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Kurzgeschichte
38
+ 19aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Graphic Novel
39
+ 20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Sachbuch
40
+ 21aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Biografie
41
+ 22aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Autobiografie
42
+ 23aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Essay
43
+ 24aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,de,Rede
@@ -49,11 +49,24 @@ module.exports = class SampleTemplate extends require('../../plugin') {
49
49
 
50
50
  await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: { appPath }})
51
51
 
52
- // manifest.json must be unique
52
+ // manifest.json IDs must be unique -> otherwise deployment failures on conflicts
53
53
  await Promise.all(project.appUIPaths.map(async p => {
54
54
  const manifest = await read(join(appPath, p, 'webapp/manifest.json'))
55
55
  manifest['sap.app'].id = appName + '.' + p
56
56
  await write(join(appPath, p, 'webapp/manifest.json'), manifest, { spaces: 2 })
57
57
  }))
58
+
59
+ if (project.isNodejs) { // REVISIT: align with Java samples
60
+ const fioriSandboxConfigPath = join(appPath, 'appconfig/fioriSandboxConfig.json')
61
+ const fioriSandboxConfig = await read(fioriSandboxConfigPath)
62
+ const sampleInbounds = { BrowseBooks: 'browse', ManageAuthors: 'admin-authors', BrowseGenres: 'genres', ManageBooks: 'admin-books' }
63
+ for (const [name, id] of Object.entries(sampleInbounds)) {
64
+ const service = fioriSandboxConfig.services?.ClientSideTargetResolution?.adapter?.config?.inbounds?.[name]
65
+ if (!service) continue
66
+ if (!service.resolutionResult) service.resolutionResult = {}
67
+ service.resolutionResult.additionalInformation = `SAPUI5.Component=${appName}.${id}`
68
+ await write(fioriSandboxConfigPath, fioriSandboxConfig, { spaces: 2 })
69
+ }
70
+ }
58
71
  }
59
72
  }
@@ -0,0 +1,13 @@
1
+ {
2
+ "devDependencies": {
3
+ "@cap-js/sqlite": "^2"
4
+ }{{#profile}},
5
+ "cds": {
6
+ "requires": {
7
+ "[{{profile}}]": {
8
+ "db": "sqlite"
9
+ }
10
+ }
11
+ }
12
+ {{/profile}}
13
+ }
@@ -12,7 +12,7 @@ module.exports = class SQLiteTemplate extends require('../../plugin') {
12
12
  const project = readProject()
13
13
  const { isNodejs, isJava, configFile } = project
14
14
  if (isNodejs) {
15
- await merge(__dirname, 'files/package.json').into(configFile)
15
+ await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project })
16
16
  } else if (isJava) {
17
17
  await mvn.add('sqlite')
18
18
  }
@@ -3,6 +3,7 @@ const { exists, read } = cds.utils
3
3
  const { readProject } = require('../../projectReader')
4
4
  const { merge } = require('../../merge')
5
5
  const { srv4, xsuaa: xsuaaDefault, xsuaa4, mtxSidecar4 } = require('../../registries/mta')
6
+ const KymaTemplate = require('../kyma')
6
7
 
7
8
  module.exports = class XsuaaTemplate extends require('../../plugin') {
8
9
 
@@ -33,7 +34,7 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
33
34
 
34
35
  async combine() {
35
36
  const project = readProject()
36
- const { addMultitenancy, hasMultitenancy, addMta, hasMta, addHelm, hasHelm, hasHelmUnifiedRuntime, addHelmUnifiedRuntime, isJava, srvPath } = project
37
+ const { addMultitenancy, hasMultitenancy, addMta, hasMta, addKyma, isJava, srvPath, hasKyma } = project
37
38
 
38
39
  const cds = require('../../../../lib')
39
40
  const xsSecurity = await (async () => {
@@ -82,7 +83,7 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
82
83
  await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', { project, additions, overwrites, relationships })
83
84
  }
84
85
 
85
- if (addHelm || addHelmUnifiedRuntime || addMultitenancy && (hasHelm || hasHelmUnifiedRuntime)) {
86
+ if (addKyma || (addMultitenancy && hasKyma)) {
86
87
  const overwrites = addMultitenancy ? [{ item: 'xsuaa.parameters.tenant-mode', withValue: 'shared' }] : []
87
88
  await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project, overwrites })
88
89
  await merge({
@@ -92,6 +93,8 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
92
93
  }
93
94
  }
94
95
  }).into('chart/values.yaml')
96
+ await KymaTemplate.mergeDependency('service-instance', 'xsuaa')
97
+
95
98
  }
96
99
  }
97
100
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "9.39.1",
2
+ "version": "9.39.2",
3
3
  "help": "\n# SYNOPSIS\n\n *cds lint* [options] file.js [file.js] [dir]\n\n Runs environment checks and/or checks the specified models\n based on the ESLint framework\n\n# OPTIONS\n \n Basic configuration:\n --no-config-lookup Disable look up for eslint.config.js\n -c, --config path::String Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs\n --inspect-config Open the config inspector with the current configuration\n --ext [String] Specify additional file extensions to lint\n --global [String] Define global variables\n --parser String Specify the parser to be used\n --parser-options Object Specify parser options\n \n Specify Rules and Plugins:\n --plugin [String] Specify plugins\n --rule Object Specify rules\n \n Fix Problems:\n --fix Automatically fix problems\n --fix-dry-run Automatically fix problems without saving the changes to the file system\n --fix-type Array Specify the types of fixes to apply (directive, problem, suggestion, layout)\n \n Ignore Files:\n --no-ignore Disable use of ignore files and patterns\n --ignore-pattern [String] Patterns of files to ignore\n \n Use stdin:\n --stdin Lint code provided on <STDIN> - default: false\n --stdin-filename String Specify filename to process STDIN as\n \n Handle Warnings:\n --quiet Report errors only - default: false\n --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1\n \n Output:\n -o, --output-file path::String Specify file to write report to\n -f, --format String Use a specific output format - default: stylish\n --color, --no-color Force enabling/disabling of color\n \n Inline configuration comments:\n --no-inline-config Prevent comments from changing config or rules\n --report-unused-disable-directives Adds reported errors for unused eslint-disable and eslint-enable directives\n --report-unused-disable-directives-severity String Chooses severity level for reporting unused eslint-disable and eslint-enable directives - either: off, warn, error, 0, 1, or 2\n --report-unused-inline-configs String Adds reported errors for unused eslint inline config comments - either: off, warn, error, 0, 1, or 2\n \n Caching:\n --cache Only check changed files - default: false\n --cache-file path::String Path to the cache file. Deprecated: use --cache-location - default: .eslintcache\n --cache-location path::String Path to the cache file or directory\n --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or content - default: metadata\n \n Suppressing Violations:\n --suppress-all Suppress all violations - default: false\n --suppress-rule [String] Suppress specific rules\n --suppressions-location path::String Specify the location of the suppressions file\n --prune-suppressions Prune unused suppressions - default: false\n --pass-on-unpruned-suppressions Ignore unused suppressions - default: false\n \n Miscellaneous:\n --init Run config initialization wizard - default: false\n --env-info Output execution environment information - default: false\n --no-error-on-unmatched-pattern Prevent errors when pattern is unmatched\n --exit-on-fatal-error Exit with exit code 2 in case of fatal error - default: false\n --no-warn-ignored Suppress warnings when the file list includes ignored files\n --pass-on-no-patterns Exit with exit code 0 in case no file patterns are passed\n --debug Output debugging information\n -h, --help Show help\n -v, --version Output the version number\n --print-config path::String Print the configuration for the given file\n --stats Add statistics to the lint report - default: false\n --flag [String] Enable a feature flag\n --mcp Start the ESLint MCP server\n --concurrency Int|String Number of linting threads, auto to choose automatically, off for no multithreading - default: off\n \n",
4
4
  "options": [
5
5
  "--config",
@@ -1,5 +1,5 @@
1
1
  const cds = require('../../lib/cds')
2
- const { read } = cds.utils
2
+ const { read, stat } = cds.utils
3
3
  const crypto = require('crypto')
4
4
  const { getFiles } = require('../build/util')
5
5
 
@@ -12,18 +12,27 @@ const { getFiles } = require('../build/util')
12
12
  */
13
13
  async function calcDirChecksum(dirPath, filter = [], hashFunction = 'sha256', optionalFiles = []) {
14
14
  const hash = crypto.createHash(hashFunction)
15
- const files = await getFiles(dirPath, filter)
16
15
 
17
- for (const file of files.sort()) {
18
- const content = await read(file, "binary")
19
- hash.update(file)
20
- hash.update(content)
16
+ let stats
17
+ try {
18
+ stats = await stat(dirPath)
19
+ } catch {
20
+ return hash.digest('hex')
21
21
  }
22
22
 
23
- for (const file of optionalFiles.sort()) {
24
- const content = await read(file, "binary")
25
- hash.update(file)
26
- hash.update(content)
23
+ if (stats.isFile()) {
24
+ hash.update(await read(dirPath, "binary"))
25
+ return hash.digest('hex')
26
+ }
27
+
28
+ const files = [...(await getFiles(dirPath, filter, [], true)), ...optionalFiles].sort()
29
+ for (const file of files) {
30
+ try {
31
+ hash.update(file)
32
+ hash.update(await read(file, "binary"))
33
+ } catch {
34
+ // ignore
35
+ }
27
36
  }
28
37
 
29
38
  return hash.digest('hex')
package/lib/util/fs.js CHANGED
@@ -1,6 +1,8 @@
1
+ const { parse } = require('node:path')
2
+
1
3
  const YAML = require('yaml')
2
4
  const { read, write } = require('../cds').utils
3
- const JSONC = require('./jsonc');
5
+ const JSONC = require('./jsonc')
4
6
 
5
7
  // REVISIT: Consider merging with template util
6
8
 
@@ -59,4 +61,9 @@ module.exports = new class FsUtil {
59
61
  const json = await this.readJSON(src, project)
60
62
  await write(dst, json, { spaces: 2 })
61
63
  }
64
+
65
+ isRoot(folder) {
66
+ const { root, dir } = parse(folder)
67
+ return root === dir
68
+ }
62
69
  }
package/lib/util/glob.js CHANGED
@@ -1,13 +1,16 @@
1
1
  const path = require('node:path');
2
2
  const { join } = path;
3
- const { existsSync } = require('node:fs');
4
3
  const { readdir } = require('node:fs').promises;
5
4
 
5
+ const IGNORE_DIRS = new Set(['node_modules', '.git', '.hg', '.svn', 'dist', 'target', 'build', 'out']);
6
+
7
+
6
8
  /**
7
9
  * Returns an AsyncIterator of those (recursive) subdirectories that match the glob pattern.
8
- * @param pattern - The glob pattern to match against.
9
- * @param cwd - The current working directory to start the search from.
10
- * @returns {AsyncIterator<string>} - An array of relative paths that match the glob pattern.
10
+ * Skips common large directories (node_modules, .git, .hg, .svn, dist, target, build, out).
11
+ * @param {string} pattern - The glob pattern to match against.
12
+ * @param {string} cwd - The current working directory to start the search from.
13
+ * @returns An array of relative paths that match the glob pattern.
11
14
  */
12
15
  async function* glob(pattern, { cwd } = { cwd: process.cwd() }) {
13
16
  if (/\{.*}/.test(pattern)) {
@@ -18,32 +21,42 @@ async function* glob(pattern, { cwd } = { cwd: process.cwd() }) {
18
21
  }
19
22
  return globNative(pattern, { cwd });
20
23
  }
21
- yield* (await globArray(pattern, { cwd }));
24
+ yield* (await globArray(pattern, cwd));
22
25
  }
23
26
 
24
27
  /**
25
28
  * Returns those subdirectories, recursively, that match the glob pattern.
26
- * @param pattern - The glob pattern to match against.
27
- * @param cwd - The current working directory to start the search from.
29
+ * Skips common large directories (node_modules, .git, .hg, .svn, dist, target, build, out).
30
+ * @param {string} pattern - The glob pattern to match against.
31
+ * @param {string} cwd - The current working directory to start the search from.
28
32
  * @returns {Promise<string[]>} - An array of relative paths that match the glob pattern.
29
33
  */
30
- async function globArray(pattern, { cwd }) {
34
+ async function globArray(pattern, cwd) {
31
35
  const regex = globToRegex(pattern);
32
36
  return (await subdirs(cwd))
33
37
  .map(fullPath => path.relative(cwd, fullPath))
34
- .filter(relativePath => regex.test(relativePath));
38
+ .filter(relativePath => regex.test(relativePath.replace(/\\/g, '/')));
35
39
  }
36
40
 
37
41
  /**
38
42
  * Recursively returns all subdirectories of a given directory.
39
43
  * Returns only non-hidden directories (those not starting with a dot).
40
- * @param {import('fs').PathLike} dir - The directory to search in.
44
+ * Skips common large directories like node_modules, .git, etc.
45
+ * @param {string} dir - The directory to search in.
41
46
  * @returns {Promise<String[]>} - An array of paths to subdirectories.
42
47
  */
43
48
  async function subdirs(dir) {
44
- if (!existsSync(dir)) return [];
45
- const children = (await readdir(dir, { withFileTypes: true }))
46
- .filter(child => child.isDirectory() && !child.name.startsWith('.'));
49
+ let entries;
50
+ try {
51
+ entries = await readdir(dir, { withFileTypes: true });
52
+ } catch (err) {
53
+ return [];
54
+ }
55
+
56
+ const children = entries.filter(child =>
57
+ child.isDirectory() && !child.name.startsWith('.') && !IGNORE_DIRS.has(child.name)
58
+ );
59
+
47
60
  return (await Promise.all(
48
61
  children.map(async child => {
49
62
  const childPath = join(dir, child.name);
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)
@@ -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
  }
@@ -0,0 +1,136 @@
1
+ const fs = require('node:fs')
2
+ const { resolve, sep } = require('node:path')
3
+ const { EventEmitter } = require('node:events')
4
+
5
+ module.exports = function watch(paths, { filter, ignore } = {}) {
6
+ const ee = new EventEmitter()
7
+ const roots = [...new Set((Array.isArray(paths) ? paths : [paths]).filter(Boolean).map(p => resolve(p)))]
8
+ const ac = new AbortController() // eslint-disable-line no-undef
9
+ let closed = false
10
+
11
+ const _allow = p => !filter || (filter instanceof RegExp ? filter.test(p) : filter(p))
12
+
13
+ const useNativeRecursive = process.platform === 'darwin' || process.platform === 'win32'
14
+ if (useNativeRecursive) {
15
+ const _watch = async root => {
16
+ try {
17
+ if (!fs.existsSync(root)) return
18
+ const watcher = fs.promises.watch(root, { recursive: true, signal: ac.signal })
19
+ for await (const event of watcher) {
20
+ if (closed) break
21
+ const name = event.filename?.toString?.() ?? event.filename ?? ''
22
+ const fullPath = resolve(root, name)
23
+ if (_allow(fullPath)) {
24
+ ee.emit('change', fs.existsSync(fullPath) ? 'update' : 'remove', fullPath)
25
+ }
26
+ }
27
+ } catch (e) {
28
+ if (e.name !== 'AbortError') ee.emit('error', e)
29
+ }
30
+ }
31
+
32
+ roots.forEach(r => _watch(r).catch(e => ee.emit('error', e)))
33
+
34
+ ee.close = () => {
35
+ if (closed) return
36
+ closed = true
37
+ ac.abort()
38
+ ee.emit('close')
39
+ }
40
+
41
+ return ee
42
+ } else {
43
+ return _linuxCompat()
44
+ }
45
+
46
+ // On Linux, recursive watchers allocates OS watchers for the whole tree,
47
+ // so we avoid it to skip ignored dirs before allocating watchers.
48
+ function _linuxCompat() {
49
+ const watching = new Set()
50
+ const queued = new Set()
51
+ const queue = []
52
+ const active = new Set()
53
+ const max = 32
54
+
55
+ const _ignored = p => {
56
+ if (!ignore) return false
57
+ const test = ignore instanceof RegExp ? s => ignore.test(s) : ignore
58
+ return test(p) || test(p + sep) // allow trailing sep
59
+ }
60
+ const _enqueue = d => {
61
+ if (closed) return
62
+ const dir = resolve(d)
63
+ if (_ignored(dir)) return
64
+ if (watching.has(dir) || queued.has(dir)) return
65
+ queued.add(dir)
66
+ queue.push(dir)
67
+ _pump()
68
+ }
69
+ const _startWatch = dir => {
70
+ if (watching.has(dir)) return
71
+ watching.add(dir)
72
+ ;(async () => {
73
+ try {
74
+ if (!fs.existsSync(dir)) return
75
+ const watcher = fs.promises.watch(dir, { signal: ac.signal }) // non-recursive
76
+ for await (const event of watcher) {
77
+ if (closed) break
78
+ const name = event.filename?.toString?.() ?? event.filename ?? ''
79
+ const fullPath = name ? resolve(dir, name) : dir
80
+
81
+ // if a new dir appears, start watching it (unless ignored)
82
+ if (name && !_ignored(fullPath) && fs.existsSync(fullPath)) {
83
+ try {
84
+ if (fs.lstatSync(fullPath).isDirectory()) _enqueue(fullPath)
85
+ } catch { /* ignore */ }
86
+ }
87
+ if (_allow(fullPath)) {
88
+ ee.emit('change', fs.existsSync(fullPath) ? 'update' : 'remove', fullPath)
89
+ }
90
+ }
91
+ } catch (e) {
92
+ if (e.name !== 'AbortError' && !closed) ee.emit('error', e)
93
+ }
94
+ })().catch(e => ee.emit('error', e))
95
+ }
96
+
97
+ const _scan = async dir => {
98
+ _startWatch(dir)
99
+
100
+ let entries = []
101
+ try { entries = await fs.promises.readdir(dir, { withFileTypes: true }) }
102
+ catch { return }
103
+
104
+ for (const e of entries) {
105
+ if (e.isSymbolicLink?.()) continue
106
+ if (!e.isDirectory?.()) continue
107
+ const child = resolve(dir, e.name)
108
+ if (!_ignored(child)) _enqueue(child)
109
+ }
110
+ }
111
+
112
+ const _pump = () => {
113
+ while (!closed && queue.length && active.size < max) {
114
+ const dir = queue.shift()
115
+ queued.delete(dir)
116
+
117
+ const p = _scan(dir)
118
+ .catch(e => ee.emit('error', e))
119
+ .finally(() => {
120
+ active.delete(p)
121
+ _pump()
122
+ })
123
+
124
+ active.add(p)
125
+ }
126
+ }
127
+ roots.forEach(_enqueue)
128
+ ee.close = () => {
129
+ if (closed) return
130
+ closed = true
131
+ ac.abort()
132
+ ee.emit('close')
133
+ }
134
+ return ee
135
+ }
136
+ }