@sap/cds-dk 9.4.3 → 9.6.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 (168) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/bin/compile/index.js +1 -1
  3. package/bin/env.js +3 -3
  4. package/bin/export.js +1 -1
  5. package/bin/help.js +0 -1
  6. package/bin/import.js +8 -3
  7. package/bin/repl.js +7 -11
  8. package/bin/up.js +159 -23
  9. package/bin/util/containerize.js +79 -25
  10. package/bin/version.js +4 -4
  11. package/lib/bind/k8s.js +1 -1
  12. package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +1 -1
  13. package/lib/build/plugins/helm/index.js +1 -1
  14. package/lib/build/provider/hana/2migration.js +16 -24
  15. package/lib/build/provider/hana/index.js +1 -1
  16. package/lib/build/provider/hana/template/.hdiconfig-all +7 -1
  17. package/lib/build/provider/hana/template/package.json +1 -1
  18. package/lib/build/provider/internalBuildPlugin.js +15 -1
  19. package/lib/build/provider/java/index.js +12 -1
  20. package/lib/build/provider/mtx-sidecar/index.js +2 -1
  21. package/lib/build/provider/nodejs/index.js +2 -1
  22. package/lib/build/util.js +36 -19
  23. package/lib/compile/to_openapi.js +1 -5
  24. package/lib/import/common.js +9 -2
  25. package/lib/import/odata/ParserContext.js +1 -0
  26. package/lib/import/odata/v4parser.js +57 -25
  27. package/lib/import/openapi/importOpenAPI.js +6 -1
  28. package/lib/init/constants.js +1 -1
  29. package/lib/init/index.js +20 -6
  30. package/lib/init/merge.js +16 -0
  31. package/lib/init/projectReader.js +34 -8
  32. package/lib/init/registries/mta.js +2 -2
  33. package/lib/init/template/ams/index.js +3 -3
  34. package/lib/init/template/app-frontend/files/app-package.json.hbs +1 -7
  35. package/lib/init/template/app-frontend/files/ui5.yaml.hbs +21 -1
  36. package/lib/init/template/app-frontend/index.js +2 -6
  37. package/lib/init/template/application-logging/index.js +11 -16
  38. package/lib/init/template/approuter/files/mta.yaml.hbs +0 -2
  39. package/lib/init/template/approuter/index.js +13 -10
  40. package/lib/init/template/attachments/index.js +4 -2
  41. package/lib/init/template/audit-logging/index.js +4 -2
  42. package/lib/init/template/cloud-logging/index.js +6 -5
  43. package/lib/init/template/connectivity/index.js +2 -2
  44. package/lib/init/template/console/files/package.json +5 -0
  45. package/lib/init/template/console/index.js +25 -0
  46. package/lib/init/template/containerize/index.js +4 -6
  47. package/lib/init/template/data/as-json.js +18 -20
  48. package/lib/init/template/destination/index.js +4 -2
  49. package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
  50. package/lib/init/template/dynatrace/index.js +4 -4
  51. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +3 -1
  52. package/lib/init/template/enterprise-messaging/index.js +4 -2
  53. package/lib/init/template/enterprise-messaging-shared/index.js +4 -2
  54. package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +16 -1
  55. package/lib/init/template/github-actions/files/workflows/release.yaml.hbs +2 -2
  56. package/lib/init/template/github-actions/files/workflows/test.yaml.hbs +20 -0
  57. package/lib/init/template/github-actions/index.js +7 -10
  58. package/lib/init/template/hana/files/db/package.json +2 -2
  59. package/lib/init/template/hana/files/mta.yaml.hbs +0 -4
  60. package/lib/init/template/hana/index.js +7 -9
  61. package/lib/init/template/helm/index.js +8 -10
  62. package/lib/init/template/helm-unified-runtime/index.js +1 -4
  63. package/lib/init/template/html5-repo/files/app-package.json.hbs +1 -7
  64. package/lib/init/template/html5-repo/files/ui5.yaml.hbs +21 -1
  65. package/lib/init/template/html5-repo/index.js +14 -18
  66. package/lib/init/template/ias/files/values.yaml.hbs +6 -6
  67. package/lib/init/template/ias/index.js +13 -21
  68. package/lib/init/template/java/index.js +14 -0
  69. package/lib/init/template/kafka/index.js +4 -2
  70. package/lib/init/template/{containerize → kyma}/files/containerize.yaml.hbs +17 -2
  71. package/lib/init/template/kyma/index.js +129 -5
  72. package/lib/init/template/malware-scanner/files/mta.yaml.hbs +3 -1
  73. package/lib/init/template/malware-scanner/files/values.yaml +3 -1
  74. package/lib/init/template/malware-scanner/index.js +4 -2
  75. package/lib/init/template/multitenancy/files/mta.yaml.hbs +1 -3
  76. package/lib/init/template/multitenancy/files/values.yaml.hbs +8 -0
  77. package/lib/init/template/multitenancy/index.js +15 -7
  78. package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -3
  79. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  80. package/lib/init/template/pipeline/files/app-package.json.hbs +1 -7
  81. package/lib/init/template/pipeline/files/ui5.yaml.hbs +21 -1
  82. package/lib/init/template/pipeline/index.js +1 -2
  83. package/lib/init/template/portal/files/mta.yaml.hbs +5 -3
  84. package/lib/init/template/portal/index.js +8 -8
  85. package/lib/init/template/postgres/index.js +2 -2
  86. package/lib/init/template/redis/index.js +2 -3
  87. package/lib/init/template/redis-messaging/index.js +2 -3
  88. package/lib/init/template/sample/files/_i18n/i18n_de.properties +21 -0
  89. package/lib/init/template/sample/files/_i18n/i18n_en.properties +21 -0
  90. package/lib/init/template/sample/files/_i18n/i18n_fr.properties +21 -0
  91. package/lib/init/template/sample/files/_i18n/messages_de.properties +34 -0
  92. package/lib/init/template/sample/files/_i18n/messages_en.properties +34 -0
  93. package/lib/init/template/sample/files/_i18n/messages_fr.properties +34 -0
  94. package/lib/init/template/sample/files/app/_i18n/i18n.properties +13 -25
  95. package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +13 -25
  96. package/lib/init/template/sample/files/app/admin-authors/fiori-service.cds +52 -0
  97. package/lib/init/template/sample/files/app/admin-authors/webapp/Component.js.hbs +3 -0
  98. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n.properties +3 -0
  99. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_de.properties +3 -0
  100. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_en.properties +3 -0
  101. package/lib/init/template/sample/files/app/admin-authors/webapp/manifest.json +130 -0
  102. package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +81 -88
  103. package/lib/init/template/sample/files/app/admin-books/webapp/Component.js.hbs +3 -0
  104. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +3 -3
  105. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_de.properties +3 -3
  106. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_en.properties +3 -3
  107. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +24 -48
  108. package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +56 -5
  109. package/lib/init/template/sample/files/app/browse/fiori-service.cds +30 -30
  110. package/lib/init/template/sample/files/app/browse/webapp/Component.js.hbs +3 -0
  111. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n.properties +3 -4
  112. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_de.properties +3 -3
  113. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_en.properties +3 -4
  114. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +19 -24
  115. package/lib/init/template/sample/files/app/common.cds +127 -158
  116. package/lib/init/template/sample/files/app/fiori-apps.html +50 -0
  117. package/lib/init/template/sample/files/app/genres/fiori-service.cds +33 -0
  118. package/lib/init/template/sample/files/app/genres/tree-view.cds +42 -0
  119. package/lib/init/template/sample/files/app/genres/value-help.cds +6 -0
  120. package/lib/init/template/sample/files/app/genres/webapp/Component.js.hbs +3 -0
  121. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n.properties +3 -0
  122. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_de.properties +3 -0
  123. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_en.properties +3 -0
  124. package/lib/init/template/sample/files/app/genres/webapp/manifest.json +126 -0
  125. package/lib/init/template/sample/files/app/services.cds +6 -2
  126. package/lib/init/template/sample/files/db/currencies.cds +13 -0
  127. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books.csv +5 -5
  128. package/lib/init/template/sample/files/db/data/{sap.capire.bookshop-Books_texts.csv → sap.capire.bookshop-Books.texts.csv} +4 -4
  129. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.csv +42 -15
  130. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.texts.csv +43 -0
  131. package/lib/init/template/sample/files/db/data/sap.common-Currencies.csv +12 -0
  132. package/lib/init/template/sample/files/db/data/sap.common-Currencies.texts.csv +15 -0
  133. package/lib/init/template/sample/files/db/hana/index.cds +10 -0
  134. package/lib/init/template/sample/files/db/schema.cds +35 -22
  135. package/lib/init/template/sample/files/db/sqlite/index.cds +10 -0
  136. package/lib/init/template/sample/files/package.json.hbs +14 -1
  137. package/lib/init/template/sample/files/srv/access-control.cds +3 -0
  138. package/lib/init/template/sample/files/srv/admin-service.cds +3 -2
  139. package/lib/init/template/sample/files/srv/cat-service.cds +7 -11
  140. package/lib/init/template/sample/index.js +40 -18
  141. package/lib/init/template/sqlite/files/package.json.hbs +13 -0
  142. package/lib/init/template/sqlite/index.js +1 -1
  143. package/lib/init/template/telemetry/index.js +1 -3
  144. package/lib/init/template/typescript/files/package.json +1 -1
  145. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +0 -5
  146. package/lib/init/template/workzone-standard/index.js +0 -1
  147. package/lib/init/template/xsuaa/files/mta.yaml.hbs +4 -0
  148. package/lib/init/template/xsuaa/index.js +7 -6
  149. package/lib/lint/Linter.js +10 -4
  150. package/lib/lint/eslintHelp.json +1 -1
  151. package/lib/mtx/tenant_action.js +12 -4
  152. package/lib/util/checksum.js +41 -0
  153. package/lib/util/fs.js +8 -1
  154. package/lib/util/glob.js +26 -13
  155. package/lib/util/question.js +60 -7
  156. package/npm-shrinkwrap.json +141 -118
  157. package/package.json +2 -2
  158. package/lib/init/template/app-frontend/files/ui5-deploy.yaml.hbs +0 -25
  159. package/lib/init/template/github-actions/files/cf-setup/action.yaml +0 -53
  160. package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +0 -25
  161. package/lib/init/template/pipeline/files/ui5-deploy.yaml.hbs +0 -25
  162. package/lib/init/template/sample/files/app/admin-books/webapp/Component.js +0 -8
  163. package/lib/init/template/sample/files/app/browse/webapp/Component.js +0 -7
  164. package/lib/init/template/sample/files/app/index.html +0 -32
  165. package/lib/init/template/sqlite/files/package.json +0 -5
  166. /package/lib/init/template/{helm → kyma}/files/Chart.yaml.hbs +0 -0
  167. /package/lib/init/template/{helm → kyma}/files/values.schema.json +0 -0
  168. /package/lib/init/template/{helm → kyma}/files/values.yaml.hbs +0 -0
@@ -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, 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 () => {
@@ -53,16 +54,14 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
53
54
  additions.push(xsuaa)
54
55
  if (addMta) {
55
56
  const srv = srv4(srvPath)
56
- additions.push(srv)
57
57
  relationships.push({
58
58
  insert: [xsuaa, 'name'],
59
59
  into: [srv, 'requires', 'name']
60
60
  })
61
61
  }
62
- if (addMultitenancy) {
62
+ if (hasMultitenancy) {
63
63
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
64
- additions.push(mtxSidecar)
65
- overwrites.push({ item: [mtxSidecar, 'parameters.tenant-mode'], withValue: 'shared' })
64
+ overwrites.push({ item: [xsuaa, 'parameters.config.tenant-mode'], withValue: 'shared' })
66
65
  relationships.push({
67
66
  insert: [xsuaa, 'name'],
68
67
  into: [mtxSidecar, 'requires', 'name']
@@ -84,7 +83,7 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
84
83
  await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', { project, additions, overwrites, relationships })
85
84
  }
86
85
 
87
- if (addHelm || addHelmUnifiedRuntime || addMultitenancy && (hasHelm || hasHelmUnifiedRuntime)) {
86
+ if (addKyma || (addMultitenancy && hasKyma)) {
88
87
  const overwrites = addMultitenancy ? [{ item: 'xsuaa.parameters.tenant-mode', withValue: 'shared' }] : []
89
88
  await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project, overwrites })
90
89
  await merge({
@@ -94,6 +93,8 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
94
93
  }
95
94
  }
96
95
  }).into('chart/values.yaml')
96
+ await KymaTemplate.mergeDependency('service-instance', 'xsuaa')
97
+
97
98
  }
98
99
  }
99
100
  }
@@ -65,10 +65,16 @@ async function findConfigPath (currentDir = '.') {
65
65
  let configDir = path.resolve(currentDir)
66
66
  while (configDir && configDir !== path.dirname(configDir)) {
67
67
  for (const configFile of configFiles) {
68
- const configPath = path.join(configDir, configFile)
69
- const config = await tryRequire(configPath)
70
- if ((configFile !== 'package.json' || config?.eslintConfig) && isfile(configPath)) {
71
- return configPath
68
+ try {
69
+ const configPath = path.join(configDir, configFile)
70
+ const config = await tryRequire(configPath)
71
+ if ((configFile !== 'package.json' || config?.eslintConfig) && isfile(configPath)) {
72
+ return configPath
73
+ }
74
+ } catch {
75
+ // attempting to load config files may fail for various reasons we can not easily verify by error code, for example
76
+ // "Cannot use import statement outside a module" in a TS context, or attempting to read .mts files without properly setting them up.
77
+ // We just ignore these errors and treat them as "no valid config found so far, continue searching", lest the exception flys up.
72
78
  }
73
79
  }
74
80
  configDir = path.dirname(configDir)
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "9.38.0",
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,6 +1,7 @@
1
1
  const cds = require('../cds');
2
2
  const { ParamCollection } = require('./params');
3
3
  const AuthManager = require('./auth_manager');
4
+ const { localhostRegex } = require('./util/urls');
4
5
 
5
6
  require('../util/axios').pruneErrors();
6
7
  const { getMessage } = require('./util/logging');
@@ -15,6 +16,9 @@ const COMMANDS = {
15
16
  unsubscribe: 'from'
16
17
  };
17
18
 
19
+ const DEFAULT_SUBSCRIBE_USER = 'yves';
20
+ const DEFAULT_SUBSCRIBE_PASSWORD = '';
21
+
18
22
  module.exports = class TenantAction {
19
23
 
20
24
  // TODO JSDoc
@@ -33,12 +37,19 @@ module.exports = class TenantAction {
33
37
  async run(paramValues, { tenant, local } = {}) {
34
38
  let params = new ParamCollection(paramValues);
35
39
  params.set('skipToken', true); // Token would have insufficient scopes for this command.
40
+
36
41
  if (local) {
37
42
  // Connect to locally-served MTXS
38
43
  await this.runWithServer(params, tenant);
39
44
  } else {
40
- // Connect to running MTXS server
41
45
  params = await AuthManager.login(params.toEntries());
46
+ // subscribe: use 'yves' as default username against localhost if no username is provided
47
+ if (this.command === 'subscribe' && !params.has('username') && params.has('appUrl') && localhostRegex.test(params.get('appUrl'))) {
48
+ params.set('username', DEFAULT_SUBSCRIBE_USER);
49
+ params.set('password', DEFAULT_SUBSCRIBE_PASSWORD);
50
+ params = await AuthManager.login(params.toEntries()); // set reqAuth param
51
+ }
52
+ // Connect to running MTXS server
42
53
  await this.runClientOnly(params, tenant);
43
54
  }
44
55
  console.log(`${capitalize(this.command)} successful.`);
@@ -80,9 +91,6 @@ module.exports = class TenantAction {
80
91
  }
81
92
 
82
93
  const { cds_mtx } = require(libPath);
83
- if (!cds_mtx) {
84
- throw '@sap/cds-mtxs outdated. Please upgrade to @sap/cds-mtxs@>=1.7.0 and re-run this command.';
85
- }
86
94
  const body = params.get('body')
87
95
  return cds_mtx(this.command, tenant, body);
88
96
  }
@@ -0,0 +1,41 @@
1
+ const cds = require('../../lib/cds')
2
+ const { read, stat } = cds.utils
3
+ const crypto = require('crypto')
4
+ const { getFiles } = require('../build/util')
5
+
6
+ /**
7
+ * Calculate a checksum for a directory, excluding specified patterns.
8
+ * @param {string} dirPath - The directory path to calculate checksum for.
9
+ * @param {(file: string) => boolean} [filter] - the filter function to apply to the found files
10
+ * @param {string} [hashFunction='sha256'] - The hash function to use (default is 'sha256').
11
+ * @returns {Promise<string>} - The checksum as a hex string.
12
+ */
13
+ async function calcDirChecksum(dirPath, filter = [], hashFunction = 'sha256', optionalFiles = []) {
14
+ const hash = crypto.createHash(hashFunction)
15
+
16
+ let stats
17
+ try {
18
+ stats = await stat(dirPath)
19
+ } catch {
20
+ return hash.digest('hex')
21
+ }
22
+
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
+ }
36
+ }
37
+
38
+ return hash.digest('hex')
39
+ }
40
+
41
+ module.exports = { calcDirChecksum }
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);
@@ -1,6 +1,14 @@
1
1
  const readline = require('node:readline')
2
+ const { Writable } = require('node:stream')
2
3
 
3
- async function ask4(...args) {
4
+ async function ask4([...args], { redacted = false } = {}) {
5
+ var mutableStdout = new Writable({
6
+ write: function (chunk, encoding, callback) {
7
+ if (!redacted)
8
+ process.stdout.write(chunk, encoding);
9
+ callback();
10
+ }
11
+ });
4
12
 
5
13
  // If not in interactive mode, read all input at once.
6
14
  if (!process.stdin.isTTY) {
@@ -13,19 +21,64 @@ async function ask4(...args) {
13
21
  }
14
22
 
15
23
  const answers = []
16
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
24
+ const rl = readline.createInterface({
25
+ input: process.stdin,
26
+ output: mutableStdout,
27
+ terminal: true
28
+ })
17
29
 
18
30
  const askQuestion = question =>
19
- new Promise(resolve => rl.question(question, answer => {
20
- answers.push(answer)
21
- resolve()
22
- }))
31
+ new Promise(resolve => {
32
+ if (redacted && process.stdin.isTTY) {
33
+ // Set raw mode to intercept keystrokes
34
+ process.stdin.setRawMode(true)
35
+ process.stdout.write(question)
36
+
37
+ let answer = ''
38
+ const onData = (char) => {
39
+ const byte = char.toString()
40
+
41
+ // Enter key
42
+ if (byte === '\n' || byte === '\r') {
43
+ process.stdin.setRawMode(false)
44
+ process.stdin.removeListener('data', onData)
45
+ process.stdout.write('\n')
46
+ answers.push(answer)
47
+ resolve()
48
+ }
49
+ // Backspace/Delete
50
+ else if (byte === '\b' || byte === '\x7f') {
51
+ if (answer.length > 0) {
52
+ answer = answer.slice(0, -1)
53
+ process.stdout.write('\b \b')
54
+ }
55
+ }
56
+ // Ctrl+C
57
+ else if (byte === '\x03') {
58
+ process.stdin.setRawMode(false)
59
+ process.exit()
60
+ }
61
+ // Regular character
62
+ else if (byte >= ' ' || byte === '\t') {
63
+ answer += byte
64
+ process.stdout.write('*')
65
+ }
66
+ }
67
+
68
+ process.stdin.on('data', onData)
69
+ } else {
70
+ rl.question(question, answer => {
71
+ answers.push(answer)
72
+ resolve()
73
+ })
74
+ }
75
+ })
23
76
 
24
77
  for (const question of args) {
25
78
  await askQuestion(question)
26
79
  }
27
80
  rl.close()
28
- return Array.isArray(args) ? answers[0] : answers
81
+ return args.length == 1 ? answers[0] : answers
29
82
  }
30
83
 
31
84
  module.exports = { ask4 }