@aigne/doc-smith 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.9](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.8...v0.2.9) (2025-08-13)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * polish ignore check ([#25](https://github.com/AIGNE-io/aigne-doc-smith/issues/25)) ([90bc866](https://github.com/AIGNE-io/aigne-doc-smith/commit/90bc866513fef7b47047b1016e07bf38881c101c))
9
+
3
10
  ## [0.2.8](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.7...v0.2.8) (2025-08-13)
4
11
 
5
12
 
@@ -8,15 +8,8 @@ function getActionText(isTranslate, baseText) {
8
8
  }
9
9
 
10
10
  export default async function findItemByPath(
11
- {
12
- "doc-path": docPath,
13
- structurePlanResult,
14
- boardId,
15
- docsDir,
16
- isTranslate,
17
- feedback,
18
- },
19
- options
11
+ { "doc-path": docPath, structurePlanResult, boardId, docsDir, isTranslate, feedback },
12
+ options,
20
13
  ) {
21
14
  let foundItem = null;
22
15
  let selectedFileContent = null;
@@ -30,9 +23,7 @@ export default async function findItemByPath(
30
23
  // Filter for main language .md files (exclude _sidebar.md and language-specific files)
31
24
  const mainLanguageFiles = files.filter(
32
25
  (file) =>
33
- file.endsWith(".md") &&
34
- file !== "_sidebar.md" &&
35
- !file.match(/\.\w+(-\w+)?\.md$/) // Exclude language-specific files like .en.md, .zh-CN.md, etc.
26
+ file.endsWith(".md") && file !== "_sidebar.md" && !file.match(/\.\w+(-\w+)?\.md$/), // Exclude language-specific files like .en.md, .zh-CN.md, etc.
36
27
  );
37
28
 
38
29
  if (mainLanguageFiles.length === 0) {
@@ -52,7 +43,7 @@ export default async function findItemByPath(
52
43
 
53
44
  const searchTerm = input.trim().toLowerCase();
54
45
  const filteredFiles = mainLanguageFiles.filter((file) =>
55
- file.toLowerCase().includes(searchTerm)
46
+ file.toLowerCase().includes(searchTerm),
56
47
  );
57
48
 
58
49
  return filteredFiles.map((file) => ({
@@ -71,10 +62,7 @@ export default async function findItemByPath(
71
62
  const selectedFilePath = join(docsDir, selectedFile);
72
63
  selectedFileContent = await readFile(selectedFilePath, "utf-8");
73
64
  } catch (readError) {
74
- console.warn(
75
- `⚠️ Could not read content from ${selectedFile}:`,
76
- readError.message
77
- );
65
+ console.warn(`⚠️ Could not read content from ${selectedFile}:`, readError.message);
78
66
  selectedFileContent = null;
79
67
  }
80
68
 
@@ -87,9 +75,7 @@ export default async function findItemByPath(
87
75
 
88
76
  // First try without boardId prefix
89
77
  foundItemByFile = structurePlanResult.find((item) => {
90
- const itemFlattenedPath = item.path
91
- .replace(/^\//, "")
92
- .replace(/\//g, "-");
78
+ const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
93
79
  return itemFlattenedPath === flatName;
94
80
  });
95
81
  if (!foundItemByFile) {
@@ -102,8 +88,8 @@ export default async function findItemByPath(
102
88
  throw new Error(
103
89
  getActionText(
104
90
  isTranslate,
105
- "Please provide a doc-path parameter to specify which document to {action}"
106
- )
91
+ "Please provide a doc-path parameter to specify which document to {action}",
92
+ ),
107
93
  );
108
94
  }
109
95
  }
@@ -121,18 +107,14 @@ export default async function findItemByPath(
121
107
  // Find item by comparing flattened paths
122
108
  foundItem = structurePlanResult.find((item) => {
123
109
  // Convert item.path to flattened format (replace / with -)
124
- const itemFlattenedPath = item.path
125
- .replace(/^\//, "")
126
- .replace(/\//g, "-");
110
+ const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
127
111
  return itemFlattenedPath === flattenedPath;
128
112
  });
129
113
  }
130
114
  }
131
115
 
132
116
  if (!foundItem) {
133
- throw new Error(
134
- `Item with path "${docPath}" not found in structurePlanResult`
135
- );
117
+ throw new Error(`Item with path "${docPath}" not found in structurePlanResult`);
136
118
  }
137
119
 
138
120
  // Prompt for feedback if not provided
@@ -140,7 +122,7 @@ export default async function findItemByPath(
140
122
  if (!userFeedback) {
141
123
  const feedbackMessage = getActionText(
142
124
  isTranslate,
143
- "Please provide feedback for the {action} (press Enter to skip):"
125
+ "Please provide feedback for the {action} (press Enter to skip):",
144
126
  );
145
127
 
146
128
  userFeedback = await options.prompts.input({
@@ -1,16 +1,8 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import chalk from "chalk";
4
- import {
5
- DOCUMENT_STYLES,
6
- SUPPORTED_LANGUAGES,
7
- TARGET_AUDIENCES,
8
- } from "../utils/constants.mjs";
9
- import {
10
- getAvailablePaths,
11
- getProjectInfo,
12
- validatePath,
13
- } from "../utils/utils.mjs";
4
+ import { DOCUMENT_STYLES, SUPPORTED_LANGUAGES, TARGET_AUDIENCES } from "../utils/constants.mjs";
5
+ import { getAvailablePaths, getProjectInfo, validatePath } from "../utils/utils.mjs";
14
6
 
15
7
  // UI constants
16
8
  const _PRESS_ENTER_TO_FINISH = "Press Enter to finish";
@@ -23,12 +15,8 @@ const _PRESS_ENTER_TO_FINISH = "Press Enter to finish";
23
15
  * @returns {Promise<Object>}
24
16
  */
25
17
  export default async function init(
26
- {
27
- outputPath = ".aigne/doc-smith",
28
- fileName = "config.yaml",
29
- skipIfExists = false,
30
- },
31
- options
18
+ { outputPath = ".aigne/doc-smith", fileName = "config.yaml", skipIfExists = false },
19
+ options,
32
20
  ) {
33
21
  if (skipIfExists) {
34
22
  const filePath = join(outputPath, fileName);
@@ -104,7 +92,7 @@ export default async function init(
104
92
  // 4. Translation languages
105
93
  // Filter out the primary language from available choices
106
94
  const availableTranslationLanguages = SUPPORTED_LANGUAGES.filter(
107
- (lang) => lang.code !== primaryLanguageChoice
95
+ (lang) => lang.code !== primaryLanguageChoice,
108
96
  );
109
97
 
110
98
  const translateLanguageChoices = await options.prompts.checkbox({
@@ -154,11 +142,7 @@ export default async function init(
154
142
  });
155
143
 
156
144
  // Check if user chose to exit
157
- if (
158
- !selectedPath ||
159
- selectedPath.trim() === "" ||
160
- selectedPath === "Press Enter to finish"
161
- ) {
145
+ if (!selectedPath || selectedPath.trim() === "" || selectedPath === "Press Enter to finish") {
162
146
  break;
163
147
  }
164
148
 
@@ -207,13 +191,9 @@ export default async function init(
207
191
  console.log(chalk.cyan("---"));
208
192
  console.log(chalk.cyan(yamlContent));
209
193
  console.log(chalk.cyan("---"));
194
+ console.log("💡 You can edit the configuration file anytime to modify settings.\n");
210
195
  console.log(
211
- "💡 You can edit the configuration file anytime to modify settings.\n"
212
- );
213
- console.log(
214
- `🚀 Run ${chalk.cyan(
215
- "'aigne doc generate'"
216
- )} to start documentation generation!\n`
196
+ `🚀 Run ${chalk.cyan("'aigne doc generate'")} to start documentation generation!\n`,
217
197
  );
218
198
 
219
199
  return {};
@@ -284,5 +264,4 @@ function generateYAML(input) {
284
264
  return yaml;
285
265
  }
286
266
 
287
- init.description =
288
- "Generate a configuration file for the documentation generation process";
267
+ init.description = "Generate a configuration file for the documentation generation process";
@@ -1,87 +1,9 @@
1
1
  import { access, readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { glob } from "glob";
4
3
  import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_INCLUDE_PATTERNS } from "../utils/constants.mjs";
4
+ import { getFilesWithGlob, loadGitignore } from "../utils/file-utils.mjs";
5
5
  import { getCurrentGitHead, getModifiedFilesBetweenCommits } from "../utils/utils.mjs";
6
6
 
7
- /**
8
- * Load .gitignore patterns from a directory
9
- * @param {string} dir - Directory path
10
- * @returns {object|null} Ignore instance or null if no .gitignore found
11
- */
12
- async function loadGitignore(dir) {
13
- const gitignorePath = path.join(dir, ".gitignore");
14
- try {
15
- await access(gitignorePath);
16
- const gitignoreContent = await readFile(gitignorePath, "utf8");
17
- // Create ignore patterns from .gitignore content
18
- const ignorePatterns = gitignoreContent
19
- .split("\n")
20
- .map((line) => line.trim())
21
- .filter((line) => line && !line.startsWith("#"))
22
- .map((line) => line.replace(/^\//, "")); // Remove leading slash
23
-
24
- return ignorePatterns.length > 0 ? ignorePatterns : null;
25
- } catch {
26
- // .gitignore file doesn't exist
27
- return null;
28
- }
29
- }
30
-
31
- /**
32
- * Get files using glob patterns
33
- * @param {string} dir - Directory to search
34
- * @param {string[]} includePatterns - Include patterns
35
- * @param {string[]} excludePatterns - Exclude patterns
36
- * @param {string[]} gitignorePatterns - .gitignore patterns
37
- * @returns {Promise<string[]>} Array of file paths
38
- */
39
- async function getFilesWithGlob(dir, includePatterns, excludePatterns, gitignorePatterns) {
40
- // Prepare all ignore patterns
41
- const allIgnorePatterns = [];
42
-
43
- if (excludePatterns) {
44
- allIgnorePatterns.push(...excludePatterns);
45
- }
46
-
47
- if (gitignorePatterns) {
48
- allIgnorePatterns.push(...gitignorePatterns);
49
- }
50
-
51
- // Add default exclusions if not already present
52
- const defaultExclusions = ["node_modules/**", "test/**", "temp/**"];
53
- for (const exclusion of defaultExclusions) {
54
- if (!allIgnorePatterns.includes(exclusion)) {
55
- allIgnorePatterns.push(exclusion);
56
- }
57
- }
58
-
59
- // Convert patterns to be relative to the directory
60
- const patterns = includePatterns.map((pattern) => {
61
- // If pattern doesn't start with / or **, make it relative to dir
62
- if (!pattern.startsWith("/") && !pattern.startsWith("**")) {
63
- return `**/${pattern}`; // Use ** to search recursively
64
- }
65
- return pattern;
66
- });
67
-
68
- try {
69
- const files = await glob(patterns, {
70
- cwd: dir,
71
- ignore: allIgnorePatterns.length > 0 ? allIgnorePatterns : undefined,
72
- absolute: true,
73
- nodir: true, // Only return files, not directories
74
- dot: false, // Don't include dot files by default
75
- gitignore: true, // Enable .gitignore support
76
- });
77
-
78
- return files;
79
- } catch (error) {
80
- console.warn(`Warning: Error during glob search in ${dir}: ${error.message}`);
81
- return [];
82
- }
83
- }
84
-
85
7
  export default async function loadSources({
86
8
  sources = [],
87
9
  sourcesPath = [],
@@ -242,6 +164,21 @@ export default async function loadSources({
242
164
  }
243
165
  }
244
166
 
167
+ // Count words and lines in allSources
168
+ let totalWords = 0;
169
+ let totalLines = 0;
170
+
171
+ for (const source of Object.values(allSources)) {
172
+ if (typeof source === "string") {
173
+ // Count English words (simple regex for words containing a-zA-Z)
174
+ const words = source.match(/[a-zA-Z]+/g) || [];
175
+ totalWords += words.length;
176
+
177
+ // Count lines
178
+ totalLines += source.split("\n").length;
179
+ }
180
+ }
181
+
245
182
  return {
246
183
  datasourcesList: sourceFiles,
247
184
  datasources: allSources,
@@ -249,6 +186,8 @@ export default async function loadSources({
249
186
  originalStructurePlan,
250
187
  files,
251
188
  modifiedFiles,
189
+ totalWords,
190
+ totalLines,
252
191
  };
253
192
  }
254
193
 
@@ -18,11 +18,7 @@ const DEFAULT_APP_URL = "https://docsmith.aigne.io";
18
18
  * @returns {Promise<string>} - The access token
19
19
  */
20
20
  async function getAccessToken(appUrl) {
21
- const DOC_SMITH_ENV_FILE = join(
22
- homedir(),
23
- ".aigne",
24
- "doc-smith-connected.yaml"
25
- );
21
+ const DOC_SMITH_ENV_FILE = join(homedir(), ".aigne", "doc-smith-connected.yaml");
26
22
  const { hostname } = new URL(appUrl);
27
23
 
28
24
  let accessToken = process.env.DOC_DISCUSS_KIT_ACCESS_TOKEN;
@@ -47,10 +43,7 @@ async function getAccessToken(appUrl) {
47
43
  // If still no access token, prompt user to authorize
48
44
  if (!accessToken) {
49
45
  const DISCUSS_KIT_URL = appUrl;
50
- const connectUrl = joinURL(
51
- new URL(DISCUSS_KIT_URL).origin,
52
- WELLKNOWN_SERVICE_PATH_PREFIX
53
- );
46
+ const connectUrl = joinURL(new URL(DISCUSS_KIT_URL).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
54
47
 
55
48
  try {
56
49
  const result = await createConnect({
@@ -59,8 +52,7 @@ async function getAccessToken(appUrl) {
59
52
  source: `AIGNE DocSmith connect to Discuss Kit`,
60
53
  closeOnSuccess: true,
61
54
  appName: "AIGNE DocSmith",
62
- appLogo:
63
- "https://www.aigne.io/image-bin/uploads/a7910a71364ee15a27e86f869ad59009.svg",
55
+ appLogo: "https://www.aigne.io/image-bin/uploads/a7910a71364ee15a27e86f869ad59009.svg",
64
56
  openPage: (pageUrl) => open(pageUrl),
65
57
  });
66
58
 
@@ -85,12 +77,12 @@ async function getAccessToken(appUrl) {
85
77
  DOC_DISCUSS_KIT_ACCESS_TOKEN: accessToken,
86
78
  DOC_DISCUSS_KIT_URL: DISCUSS_KIT_URL,
87
79
  },
88
- })
80
+ }),
89
81
  );
90
82
  } catch (error) {
91
83
  console.error("Failed to get access token:", error);
92
84
  throw new Error(
93
- "Failed to obtain access token. Please check your network connection and try again later."
85
+ "Failed to obtain access token. Please check your network connection and try again later.",
94
86
  );
95
87
  }
96
88
  }
@@ -100,7 +92,7 @@ async function getAccessToken(appUrl) {
100
92
 
101
93
  export default async function publishDocs(
102
94
  { docsDir, appUrl, boardId, projectName, projectDesc, projectLogo },
103
- options
95
+ options,
104
96
  ) {
105
97
  // Check if DOC_DISCUSS_KIT_URL is set in environment variables
106
98
  const envAppUrl = process.env.DOC_DISCUSS_KIT_URL;
@@ -184,7 +176,7 @@ export default async function publishDocs(
184
176
  await saveValueToConfig(
185
177
  "boardId",
186
178
  newBoardId,
187
- "⚠️ Warning: boardId is auto-generated by system, please do not edit manually"
179
+ "⚠️ Warning: boardId is auto-generated by system, please do not edit manually",
188
180
  );
189
181
  }
190
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/doc-smith",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,205 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import { execSync } from "node:child_process";
3
+ import path from "node:path";
4
+ import { glob } from "glob";
5
+
6
+ /**
7
+ * Check if a directory is inside a git repository using git command
8
+ * @param {string} dir - Directory path to check
9
+ * @returns {boolean} True if inside a git repository
10
+ */
11
+ function isInGitRepository(dir) {
12
+ try {
13
+ execSync("git rev-parse --is-inside-work-tree", {
14
+ cwd: dir,
15
+ stdio: "pipe",
16
+ encoding: "utf8",
17
+ });
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Find git repository root directory using git command
26
+ * @param {string} startDir - Starting directory path
27
+ * @returns {string|null} Git repository root path or null if not found
28
+ */
29
+ function findGitRoot(startDir) {
30
+ try {
31
+ const gitRoot = execSync("git rev-parse --show-toplevel", {
32
+ cwd: startDir,
33
+ stdio: "pipe",
34
+ encoding: "utf8",
35
+ }).trim();
36
+ return gitRoot;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Convert gitignore patterns to glob-compatible patterns
44
+ * @param {string} pattern - A single gitignore pattern
45
+ * @returns {string[]} Array of glob patterns that match gitignore behavior
46
+ */
47
+ function gitignoreToGlobPatterns(pattern) {
48
+ const patterns = [];
49
+
50
+ // Remove leading slash (already handled by gitignore parsing)
51
+ const cleanPattern = pattern.replace(/^\//, "");
52
+
53
+ // If pattern doesn't contain wildcards and doesn't end with /
54
+ // it could match both files and directories
55
+ if (!cleanPattern.includes("*") && !cleanPattern.includes("?") && !cleanPattern.endsWith("/")) {
56
+ // Add patterns to match both file and directory
57
+ patterns.push(cleanPattern); // Exact match
58
+ patterns.push(`${cleanPattern}/**`); // Directory contents
59
+ patterns.push(`**/${cleanPattern}`); // Nested exact match
60
+ patterns.push(`**/${cleanPattern}/**`); // Nested directory contents
61
+ } else if (cleanPattern.endsWith("/")) {
62
+ // Directory-only pattern
63
+ const dirPattern = cleanPattern.slice(0, -1);
64
+ patterns.push(`${dirPattern}/**`);
65
+ patterns.push(`**/${dirPattern}/**`);
66
+ } else {
67
+ // Pattern with wildcards or specific file
68
+ patterns.push(cleanPattern);
69
+ if (!cleanPattern.startsWith("**/")) {
70
+ patterns.push(`**/${cleanPattern}`);
71
+ }
72
+ }
73
+
74
+ return patterns;
75
+ }
76
+
77
+ /**
78
+ * Parse .gitignore content into patterns
79
+ * @param {string} content - .gitignore file content
80
+ * @returns {string[]} Array of ignore patterns converted to glob format
81
+ */
82
+ function parseGitignoreContent(content) {
83
+ const lines = content
84
+ .split("\n")
85
+ .map((line) => line.trim())
86
+ .filter((line) => line && !line.startsWith("#"))
87
+ .map((line) => line.replace(/^\//, "")); // Remove leading slash
88
+
89
+ // Convert each gitignore pattern to glob patterns
90
+ const allPatterns = [];
91
+ for (const line of lines) {
92
+ allPatterns.push(...gitignoreToGlobPatterns(line));
93
+ }
94
+
95
+ return [...new Set(allPatterns)]; // Remove duplicates
96
+ }
97
+
98
+ /**
99
+ * Load .gitignore patterns from multiple directories (current + all parent directories up to git root)
100
+ * @param {string} dir - Directory path (will search up to find all .gitignore files)
101
+ * @returns {string[]|null} Array of merged ignore patterns or null if no .gitignore found
102
+ */
103
+ export async function loadGitignore(dir) {
104
+ // First, check if we're in a git repository
105
+ const inGitRepo = isInGitRepository(dir);
106
+ if (!inGitRepo) {
107
+ // Not in a git repository, just check the current directory
108
+ const gitignorePath = path.join(dir, ".gitignore");
109
+ try {
110
+ await access(gitignorePath);
111
+ const gitignoreContent = await readFile(gitignorePath, "utf8");
112
+ const ignorePatterns = parseGitignoreContent(gitignoreContent);
113
+ return ignorePatterns.length > 0 ? ignorePatterns : null;
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+
119
+ // We're in a git repository, collect all .gitignore files from current dir to git root
120
+ const gitRoot = findGitRoot(dir);
121
+ if (!gitRoot) {
122
+ return null;
123
+ }
124
+
125
+ const allPatterns = [];
126
+ let currentDir = path.resolve(dir);
127
+
128
+ // Collect .gitignore patterns from current directory up to git root
129
+ while (currentDir.startsWith(gitRoot)) {
130
+ const gitignorePath = path.join(currentDir, ".gitignore");
131
+ try {
132
+ await access(gitignorePath);
133
+ const gitignoreContent = await readFile(gitignorePath, "utf8");
134
+ const patterns = parseGitignoreContent(gitignoreContent);
135
+
136
+ // Add patterns with context of which directory they came from
137
+ // Patterns from deeper directories take precedence
138
+ allPatterns.unshift(...patterns);
139
+ } catch {
140
+ // .gitignore doesn't exist in this directory, continue
141
+ }
142
+
143
+ // Move up one directory
144
+ if (currentDir === gitRoot) {
145
+ break;
146
+ }
147
+ currentDir = path.dirname(currentDir);
148
+ }
149
+
150
+ return allPatterns.length > 0 ? [...new Set(allPatterns)] : null;
151
+ }
152
+
153
+ /**
154
+ * Get files using glob patterns
155
+ * @param {string} dir - Directory to search
156
+ * @param {string[]} includePatterns - Include patterns
157
+ * @param {string[]} excludePatterns - Exclude patterns
158
+ * @param {string[]} gitignorePatterns - .gitignore patterns
159
+ * @returns {Promise<string[]>} Array of file paths
160
+ */
161
+ export async function getFilesWithGlob(dir, includePatterns, excludePatterns, gitignorePatterns) {
162
+ // Prepare all ignore patterns
163
+ const allIgnorePatterns = [];
164
+
165
+ if (excludePatterns) {
166
+ allIgnorePatterns.push(...excludePatterns);
167
+ }
168
+
169
+ if (gitignorePatterns) {
170
+ allIgnorePatterns.push(...gitignorePatterns);
171
+ }
172
+
173
+ // Add default exclusions if not already present
174
+ const defaultExclusions = ["node_modules/**", "test/**", "temp/**"];
175
+ for (const exclusion of defaultExclusions) {
176
+ if (!allIgnorePatterns.includes(exclusion)) {
177
+ allIgnorePatterns.push(exclusion);
178
+ }
179
+ }
180
+
181
+ // Convert patterns to be relative to the directory
182
+ const patterns = includePatterns.map((pattern) => {
183
+ // If pattern doesn't start with / or **, make it relative to dir
184
+ if (!pattern.startsWith("/") && !pattern.startsWith("**")) {
185
+ return `**/${pattern}`; // Use ** to search recursively
186
+ }
187
+ return pattern;
188
+ });
189
+
190
+ try {
191
+ const files = await glob(patterns, {
192
+ cwd: dir,
193
+ ignore: allIgnorePatterns.length > 0 ? allIgnorePatterns : undefined,
194
+ absolute: true,
195
+ nodir: true, // Only return files, not directories
196
+ dot: false, // Don't include dot files by default
197
+ gitignore: true, // Enable .gitignore support
198
+ });
199
+
200
+ return files;
201
+ } catch (error) {
202
+ console.warn(`Warning: Error during glob search in ${dir}: ${error.message}`);
203
+ return [];
204
+ }
205
+ }