@sap/cds-dk 9.5.0 → 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 (88) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/bin/env.js +3 -3
  3. package/bin/up.js +22 -16
  4. package/bin/util/containerize.js +27 -14
  5. package/lib/build/util.js +33 -16
  6. package/lib/import/common.js +7 -1
  7. package/lib/import/odata/v4parser.js +25 -38
  8. package/lib/init/constants.js +1 -1
  9. package/lib/init/index.js +5 -0
  10. package/lib/init/projectReader.js +19 -8
  11. package/lib/init/template/ams/index.js +2 -2
  12. package/lib/init/template/app-frontend/files/ui5.yaml.hbs +1 -0
  13. package/lib/init/template/app-frontend/index.js +2 -2
  14. package/lib/init/template/application-logging/index.js +2 -2
  15. package/lib/init/template/approuter/index.js +9 -5
  16. package/lib/init/template/attachments/index.js +4 -2
  17. package/lib/init/template/audit-logging/index.js +4 -2
  18. package/lib/init/template/cloud-logging/index.js +4 -2
  19. package/lib/init/template/connectivity/index.js +2 -2
  20. package/lib/init/template/console/files/package.json +5 -0
  21. package/lib/init/template/console/index.js +25 -0
  22. package/lib/init/template/containerize/index.js +4 -2
  23. package/lib/init/template/destination/index.js +4 -2
  24. package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
  25. package/lib/init/template/dynatrace/index.js +4 -2
  26. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +3 -1
  27. package/lib/init/template/enterprise-messaging/index.js +4 -2
  28. package/lib/init/template/enterprise-messaging-shared/index.js +4 -2
  29. package/lib/init/template/github-actions/files/workflows/release.yaml.hbs +2 -2
  30. package/lib/init/template/github-actions/index.js +4 -4
  31. package/lib/init/template/hana/index.js +4 -4
  32. package/lib/init/template/helm/index.js +6 -3
  33. package/lib/init/template/helm-unified-runtime/index.js +1 -0
  34. package/lib/init/template/html5-repo/files/ui5.yaml.hbs +1 -0
  35. package/lib/init/template/html5-repo/index.js +9 -7
  36. package/lib/init/template/ias/index.js +2 -2
  37. package/lib/init/template/java/index.js +14 -0
  38. package/lib/init/template/kafka/index.js +4 -2
  39. package/lib/init/template/kyma/index.js +124 -13
  40. package/lib/init/template/malware-scanner/index.js +4 -2
  41. package/lib/init/template/multitenancy/index.js +7 -4
  42. package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -3
  43. package/lib/init/template/pipeline/files/ui5.yaml.hbs +1 -0
  44. package/lib/init/template/portal/index.js +6 -3
  45. package/lib/init/template/postgres/index.js +2 -2
  46. package/lib/init/template/redis/index.js +2 -3
  47. package/lib/init/template/redis-messaging/index.js +2 -3
  48. package/lib/init/template/sample/files/_i18n/i18n_de.properties +9 -0
  49. package/lib/init/template/sample/files/_i18n/i18n_fr.properties +1 -0
  50. package/lib/init/template/sample/files/app/_i18n/i18n.properties +17 -0
  51. package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +16 -2
  52. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n.properties +3 -11
  53. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_de.properties +3 -0
  54. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_en.properties +3 -0
  55. package/lib/init/template/sample/files/app/admin-authors/webapp/manifest.json +1 -1
  56. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +3 -11
  57. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_de.properties +3 -0
  58. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_en.properties +3 -0
  59. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +1 -1
  60. package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +6 -6
  61. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n.properties +3 -11
  62. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_de.properties +3 -0
  63. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_en.properties +3 -0
  64. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +1 -1
  65. package/lib/init/template/sample/files/app/common.cds +1 -0
  66. package/lib/init/template/sample/files/app/fiori-apps.html +32 -12
  67. package/lib/init/template/sample/files/app/genres/fiori-service.cds +1 -1
  68. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n.properties +3 -4
  69. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_de.properties +3 -2
  70. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_en.properties +3 -0
  71. package/lib/init/template/sample/files/app/genres/webapp/manifest.json +2 -2
  72. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.texts.csv +43 -0
  73. package/lib/init/template/sample/index.js +14 -1
  74. package/lib/init/template/sqlite/files/package.json.hbs +13 -0
  75. package/lib/init/template/sqlite/index.js +1 -1
  76. package/lib/init/template/xsuaa/index.js +5 -2
  77. package/lib/lint/eslintHelp.json +1 -1
  78. package/lib/util/checksum.js +19 -10
  79. package/lib/util/fs.js +8 -1
  80. package/lib/util/glob.js +26 -13
  81. package/npm-shrinkwrap.json +101 -102
  82. package/package.json +1 -1
  83. package/lib/init/template/sample/files/app/_i18n/i18n_en.properties +0 -8
  84. package/lib/init/template/sqlite/files/package.json +0 -5
  85. /package/lib/init/template/{helm → kyma}/files/Chart.yaml.hbs +0 -0
  86. /package/lib/init/template/{containerize → kyma}/files/containerize.yaml.hbs +0 -0
  87. /package/lib/init/template/{helm → kyma}/files/values.schema.json +0 -0
  88. /package/lib/init/template/{helm → kyma}/files/values.yaml.hbs +0 -0
@@ -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);