@tkeron/tools 0.1.1 → 0.2.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.
- package/bun.lock +6 -4
- package/package.json +9 -4
- package/src/getPaths.test.ts +492 -0
- package/src/getPaths.ts +112 -0
- package/src/index.ts +1 -0
- package/changelog.md +0 -3
package/bun.lock
CHANGED
|
@@ -7,18 +7,20 @@
|
|
|
7
7
|
"@types/bun": "latest",
|
|
8
8
|
},
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"typescript": "^5.
|
|
10
|
+
"typescript": "^5.7.3",
|
|
11
11
|
},
|
|
12
12
|
},
|
|
13
13
|
},
|
|
14
14
|
"packages": {
|
|
15
|
-
"@types/bun": ["@types/bun@1.2.
|
|
15
|
+
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
|
|
16
16
|
|
|
17
17
|
"@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
|
|
18
18
|
|
|
19
|
-
"@types/
|
|
19
|
+
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
|
20
20
|
|
|
21
|
-
"bun-types": ["bun-types@1.2.
|
|
21
|
+
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
|
|
22
|
+
|
|
23
|
+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
|
22
24
|
|
|
23
25
|
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
|
24
26
|
|
package/package.json
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tkeron/tools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Useful JavaScript utilities",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Useful JavaScript utilities for Bun runtime",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"module": "src/index.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"author": "tkeron",
|
|
9
9
|
"license": "MIT",
|
|
10
|
+
"engines": {
|
|
11
|
+
"bun": ">=1.0.0"
|
|
12
|
+
},
|
|
10
13
|
"devDependencies": {
|
|
11
14
|
"@types/bun": "latest"
|
|
12
15
|
},
|
|
13
16
|
"peerDependencies": {
|
|
14
|
-
"typescript": "^5.
|
|
17
|
+
"typescript": "^5.7.3"
|
|
15
18
|
},
|
|
16
19
|
"keywords": [
|
|
20
|
+
"bun",
|
|
17
21
|
"utilities",
|
|
18
|
-
"utils"
|
|
22
|
+
"utils",
|
|
23
|
+
"tools"
|
|
19
24
|
],
|
|
20
25
|
"repository": {
|
|
21
26
|
"url": "git@github.com:tkeron/tools.git"
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll, it } from "bun:test";
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { getPaths, getFilePaths, getDirectoryPaths } from "./getPaths";
|
|
5
|
+
|
|
6
|
+
const testDir = join(process.cwd(), "test-temp");
|
|
7
|
+
|
|
8
|
+
describe("getPaths Tests", () => {
|
|
9
|
+
|
|
10
|
+
beforeAll(() => {
|
|
11
|
+
mkdirSync(testDir, { recursive: true });
|
|
12
|
+
|
|
13
|
+
writeFileSync(join(testDir, "file1.txt"), "content");
|
|
14
|
+
writeFileSync(join(testDir, "file2.js"), "content");
|
|
15
|
+
writeFileSync(join(testDir, "README.md"), "content");
|
|
16
|
+
|
|
17
|
+
mkdirSync(join(testDir, "src"), { recursive: true });
|
|
18
|
+
writeFileSync(join(testDir, "src", "index.ts"), "content");
|
|
19
|
+
writeFileSync(join(testDir, "src", "utils.ts"), "content");
|
|
20
|
+
|
|
21
|
+
mkdirSync(join(testDir, "docs"), { recursive: true });
|
|
22
|
+
writeFileSync(join(testDir, "docs", "guide.md"), "content");
|
|
23
|
+
|
|
24
|
+
mkdirSync(join(testDir, "empty-dir"), { recursive: true });
|
|
25
|
+
|
|
26
|
+
mkdirSync(join(testDir, "src", "components"), { recursive: true });
|
|
27
|
+
writeFileSync(join(testDir, "src", "components", "Button.tsx"), "content");
|
|
28
|
+
|
|
29
|
+
writeFileSync(join(testDir, ".env"), "content");
|
|
30
|
+
mkdirSync(join(testDir, ".git"), { recursive: true });
|
|
31
|
+
writeFileSync(join(testDir, ".git", "config"), "content");
|
|
32
|
+
|
|
33
|
+
writeFileSync(join(testDir, "file with spaces.txt"), "content");
|
|
34
|
+
writeFileSync(join(testDir, "file-no-extension"), "content");
|
|
35
|
+
writeFileSync(join(testDir, "UPPERCASE.TXT"), "content");
|
|
36
|
+
writeFileSync(join(testDir, "números-ñ-ü.js"), "content");
|
|
37
|
+
|
|
38
|
+
writeFileSync(join(testDir, "config.json"), "content");
|
|
39
|
+
writeFileSync(join(testDir, "styles.css"), "content");
|
|
40
|
+
writeFileSync(join(testDir, "script.jsx"), "content");
|
|
41
|
+
|
|
42
|
+
mkdirSync(join(testDir, "deep", "very", "nested", "structure"), { recursive: true });
|
|
43
|
+
writeFileSync(join(testDir, "deep", "very", "nested", "structure", "deep-file.txt"), "content");
|
|
44
|
+
|
|
45
|
+
mkdirSync(join(testDir, "node_modules"), { recursive: true });
|
|
46
|
+
writeFileSync(join(testDir, "node_modules", "package.json"), "content");
|
|
47
|
+
mkdirSync(join(testDir, "build"), { recursive: true });
|
|
48
|
+
writeFileSync(join(testDir, "build", "output.js"), "content");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(() => {
|
|
52
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("getPaths", () => {
|
|
56
|
+
test("should return both files and directories when includeDirectories is 'yes'", () => {
|
|
57
|
+
const result = getPaths(testDir, "**/*", "yes");
|
|
58
|
+
|
|
59
|
+
expect(result.length).toBeGreaterThan(0);
|
|
60
|
+
expect(result.some(path => path.includes("file1.txt"))).toBe(true);
|
|
61
|
+
expect(result.some(path => path.includes("src"))).toBe(true);
|
|
62
|
+
expect(result.some(path => path.includes("docs"))).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("should return only files when includeDirectories is 'no'", () => {
|
|
66
|
+
const result = getPaths(testDir, "**/*", "no");
|
|
67
|
+
|
|
68
|
+
expect(result.length).toBeGreaterThan(0);
|
|
69
|
+
expect(result.some(path => path.includes("file1.txt"))).toBe(true);
|
|
70
|
+
expect(result.some(path => path.includes("index.ts"))).toBe(true);
|
|
71
|
+
expect(result.some(path => path.endsWith("src"))).toBe(false);
|
|
72
|
+
expect(result.some(path => path.endsWith("docs"))).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("should return only directories when includeDirectories is 'onlyDirectories'", () => {
|
|
76
|
+
const result = getPaths(testDir, "**/*", "onlyDirectories");
|
|
77
|
+
|
|
78
|
+
expect(result.length).toBeGreaterThan(0);
|
|
79
|
+
expect(result.some(path => path.endsWith("src"))).toBe(true);
|
|
80
|
+
expect(result.some(path => path.endsWith("docs"))).toBe(true);
|
|
81
|
+
expect(result.some(path => path.endsWith("empty-dir"))).toBe(true);
|
|
82
|
+
expect(result.some(path => path.includes("file1.txt"))).toBe(false);
|
|
83
|
+
expect(result.some(path => path.includes("index.ts"))).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("should use default includeDirectories value ('no')", () => {
|
|
87
|
+
const result = getPaths(testDir, "**/*");
|
|
88
|
+
const filesOnlyResult = getPaths(testDir, "**/*", "no");
|
|
89
|
+
|
|
90
|
+
expect(result).toEqual(filesOnlyResult);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("should work with specific patterns", () => {
|
|
94
|
+
const result = getPaths(testDir, "*.txt", "no");
|
|
95
|
+
|
|
96
|
+
expect(result.some(path => path.includes("file1.txt"))).toBe(true);
|
|
97
|
+
expect(result.some(path => path.includes("file2.js"))).toBe(false);
|
|
98
|
+
expect(result.some(path => path.includes("README.md"))).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("should include hidden files and directories", () => {
|
|
102
|
+
const allResult = getPaths(testDir, "**/*", "yes");
|
|
103
|
+
|
|
104
|
+
expect(allResult.some(path => path.includes(".env"))).toBe(true);
|
|
105
|
+
expect(allResult.some(path => path.includes(".git"))).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("should return empty array for non-existent path", () => {
|
|
109
|
+
const result = getPaths("/non/existent/path", "**/*", "yes");
|
|
110
|
+
|
|
111
|
+
expect(result).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("getFilePaths", () => {
|
|
116
|
+
test("should return only files", () => {
|
|
117
|
+
const result = getFilePaths(testDir, "**/*");
|
|
118
|
+
|
|
119
|
+
expect(result.length).toBeGreaterThan(0);
|
|
120
|
+
expect(result.some(path => path.includes("file1.txt"))).toBe(true);
|
|
121
|
+
expect(result.some(path => path.includes("index.ts"))).toBe(true);
|
|
122
|
+
expect(result.some(path => path.includes("Button.tsx"))).toBe(true);
|
|
123
|
+
expect(result.some(path => path.endsWith("src"))).toBe(false);
|
|
124
|
+
expect(result.some(path => path.endsWith("docs"))).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("should work with specific file patterns", () => {
|
|
128
|
+
const jsFiles = getFilePaths(testDir, "**/*.js");
|
|
129
|
+
const tsFiles = getFilePaths(testDir, "**/*.ts");
|
|
130
|
+
|
|
131
|
+
expect(jsFiles.some(path => path.includes("file2.js"))).toBe(true);
|
|
132
|
+
expect(jsFiles.some(path => path.includes("file1.txt"))).toBe(false);
|
|
133
|
+
|
|
134
|
+
expect(tsFiles.some(path => path.includes("index.ts"))).toBe(true);
|
|
135
|
+
expect(tsFiles.some(path => path.includes("utils.ts"))).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("should include hidden files", () => {
|
|
139
|
+
const result = getFilePaths(testDir, "**/*");
|
|
140
|
+
|
|
141
|
+
expect(result.some(path => path.includes(".env"))).toBe(true);
|
|
142
|
+
expect(result.some(path => path.includes(".git/config"))).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("should return empty array for non-existent path", () => {
|
|
146
|
+
const result = getFilePaths("/non/existent/path", "**/*");
|
|
147
|
+
|
|
148
|
+
expect(result).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("getDirectoryPaths", () => {
|
|
153
|
+
test("should return only directories", () => {
|
|
154
|
+
const result = getDirectoryPaths(testDir, "**/*");
|
|
155
|
+
|
|
156
|
+
expect(result.length).toBeGreaterThan(0);
|
|
157
|
+
expect(result.some(path => path.endsWith("src"))).toBe(true);
|
|
158
|
+
expect(result.some(path => path.endsWith("docs"))).toBe(true);
|
|
159
|
+
expect(result.some(path => path.endsWith("empty-dir"))).toBe(true);
|
|
160
|
+
expect(result.some(path => path.endsWith("components"))).toBe(true);
|
|
161
|
+
expect(result.some(path => path.includes("file1.txt"))).toBe(false);
|
|
162
|
+
expect(result.some(path => path.includes("index.ts"))).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("should include hidden directories", () => {
|
|
166
|
+
const result = getDirectoryPaths(testDir, "**/*");
|
|
167
|
+
|
|
168
|
+
expect(result.some(path => path.includes(".git"))).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("should work with directory patterns", () => {
|
|
172
|
+
const result = getDirectoryPaths(testDir, "src/**");
|
|
173
|
+
|
|
174
|
+
expect(result.some(path => path.endsWith("components"))).toBe(true);
|
|
175
|
+
expect(result.some(path => path.endsWith("docs"))).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("should return empty array for non-existent path", () => {
|
|
179
|
+
const result = getDirectoryPaths("/non/existent/path", "**/*");
|
|
180
|
+
|
|
181
|
+
expect(result).toEqual([]);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("Edge cases", () => {
|
|
186
|
+
test("should handle empty directory", () => {
|
|
187
|
+
const emptyDirPath = join(testDir, "empty-dir");
|
|
188
|
+
const files = getFilePaths(emptyDirPath, "**/*");
|
|
189
|
+
const dirs = getDirectoryPaths(emptyDirPath, "**/*");
|
|
190
|
+
|
|
191
|
+
expect(files).toEqual([]);
|
|
192
|
+
expect(dirs).toEqual([]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("should handle invalid glob patterns gracefully", () => {
|
|
196
|
+
const result = getPaths(testDir, "[invalid", "yes");
|
|
197
|
+
|
|
198
|
+
expect(Array.isArray(result)).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("all functions should return absolute paths by default", () => {
|
|
202
|
+
const files = getFilePaths(testDir, "**/*");
|
|
203
|
+
const dirs = getDirectoryPaths(testDir, "**/*");
|
|
204
|
+
const all = getPaths(testDir, "**/*", "yes");
|
|
205
|
+
|
|
206
|
+
files.forEach(path => {
|
|
207
|
+
expect(path).toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
dirs.forEach(path => {
|
|
211
|
+
expect(path).toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
all.forEach(path => {
|
|
215
|
+
expect(path).toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("all functions should return relative paths when absolute is false", () => {
|
|
220
|
+
const files = getFilePaths(testDir, "**/*", false);
|
|
221
|
+
const dirs = getDirectoryPaths(testDir, "**/*", false);
|
|
222
|
+
const all = getPaths(testDir, "**/*", "yes", false);
|
|
223
|
+
|
|
224
|
+
files.forEach(path => {
|
|
225
|
+
expect(path).not.toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
226
|
+
expect(path.length).toBeGreaterThan(0);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
dirs.forEach(path => {
|
|
230
|
+
expect(path).not.toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
231
|
+
expect(path.length).toBeGreaterThan(0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
all.forEach(path => {
|
|
235
|
+
expect(path).not.toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
236
|
+
expect(path.length).toBeGreaterThan(0);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("relative paths should contain expected file and directory names", () => {
|
|
241
|
+
const files = getFilePaths(testDir, "**/*", false);
|
|
242
|
+
const dirs = getDirectoryPaths(testDir, "**/*", false);
|
|
243
|
+
|
|
244
|
+
expect(files.some(path => path === "file1.txt")).toBe(true);
|
|
245
|
+
expect(files.some(path => path === ".env")).toBe(true);
|
|
246
|
+
expect(files.some(path => path === "src/index.ts")).toBe(true);
|
|
247
|
+
expect(files.some(path => path === "src/components/Button.tsx")).toBe(true);
|
|
248
|
+
|
|
249
|
+
const nestedFiles = files.filter(path => path.includes("/"));
|
|
250
|
+
expect(nestedFiles.length).toBeGreaterThan(0);
|
|
251
|
+
|
|
252
|
+
expect(dirs.some(path => path === "src")).toBe(true);
|
|
253
|
+
expect(dirs.some(path => path === ".git")).toBe(true);
|
|
254
|
+
|
|
255
|
+
expect(dirs.length).toBeGreaterThan(0);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("absolute parameter should work consistently across all function variants", () => {
|
|
259
|
+
const pattern = "src/**";
|
|
260
|
+
|
|
261
|
+
// Test with absolute = true
|
|
262
|
+
const absoluteFiles = getFilePaths(testDir, pattern, true);
|
|
263
|
+
const absoluteDirs = getDirectoryPaths(testDir, pattern, true);
|
|
264
|
+
const absoluteAll = getPaths(testDir, pattern, "yes", true);
|
|
265
|
+
|
|
266
|
+
// Test with absolute = false
|
|
267
|
+
const relativeFiles = getFilePaths(testDir, pattern, false);
|
|
268
|
+
const relativeDirs = getDirectoryPaths(testDir, pattern, false);
|
|
269
|
+
const relativeAll = getPaths(testDir, pattern, "yes", false);
|
|
270
|
+
|
|
271
|
+
expect(absoluteFiles.length).toBe(relativeFiles.length);
|
|
272
|
+
expect(absoluteAll.length).toBe(relativeAll.length);
|
|
273
|
+
|
|
274
|
+
expect(Array.isArray(absoluteDirs)).toBe(true);
|
|
275
|
+
expect(Array.isArray(relativeDirs)).toBe(true);
|
|
276
|
+
|
|
277
|
+
absoluteFiles.forEach(path => {
|
|
278
|
+
expect(path).toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
relativeFiles.forEach(path => {
|
|
282
|
+
expect(path).not.toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe("Advanced Glob Patterns", () => {
|
|
288
|
+
test("should work with multiple extension patterns", () => {
|
|
289
|
+
const result = getFilePaths(testDir, "*.{js,ts,json}");
|
|
290
|
+
|
|
291
|
+
expect(result.some(path => path.includes("file2.js"))).toBe(true);
|
|
292
|
+
expect(result.some(path => path.includes("config.json"))).toBe(true);
|
|
293
|
+
expect(result.some(path => path.includes("números-ñ-ü.js"))).toBe(true);
|
|
294
|
+
expect(result.some(path => path.includes("file1.txt"))).toBe(false);
|
|
295
|
+
expect(result.some(path => path.includes("styles.css"))).toBe(false);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("should work with exclusion patterns", () => {
|
|
299
|
+
const allFiles = getFilePaths(testDir, "**/*");
|
|
300
|
+
expect(allFiles.some(path => path.includes("node_modules"))).toBe(true);
|
|
301
|
+
expect(allFiles.some(path => path.includes("build"))).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("should work with specific directory depth patterns", () => {
|
|
305
|
+
const topLevel = getFilePaths(testDir, "*");
|
|
306
|
+
expect(topLevel.some(path => path.includes("file1.txt"))).toBe(true);
|
|
307
|
+
expect(topLevel.some(path => path.includes("index.ts"))).toBe(false);
|
|
308
|
+
|
|
309
|
+
const srcFiles = getFilePaths(testDir, "src/*");
|
|
310
|
+
expect(srcFiles.some(path => path.includes("index.ts"))).toBe(true);
|
|
311
|
+
expect(srcFiles.some(path => path.includes("Button.tsx"))).toBe(false);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("should work with character class patterns", () => {
|
|
315
|
+
const result = getFilePaths(testDir, "*[Ee]*");
|
|
316
|
+
|
|
317
|
+
expect(result.some(path => path.includes("README.md"))).toBe(true);
|
|
318
|
+
expect(result.some(path => path.includes(".env"))).toBe(true);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("should work with case sensitive patterns", () => {
|
|
322
|
+
const upperFiles = getFilePaths(testDir, "*.TXT");
|
|
323
|
+
const lowerFiles = getFilePaths(testDir, "*.txt");
|
|
324
|
+
|
|
325
|
+
expect(upperFiles.some(path => path.includes("UPPERCASE.TXT"))).toBe(true);
|
|
326
|
+
expect(lowerFiles.some(path => path.includes("file1.txt"))).toBe(true);
|
|
327
|
+
|
|
328
|
+
expect(upperFiles).not.toEqual(lowerFiles);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("should handle patterns that match nothing", () => {
|
|
332
|
+
const result = getFilePaths(testDir, "*.nonexistent");
|
|
333
|
+
expect(result).toEqual([]);
|
|
334
|
+
|
|
335
|
+
const dirs = getDirectoryPaths(testDir, "nonexistent-*");
|
|
336
|
+
expect(dirs).toEqual([]);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("should work with deep nested patterns", () => {
|
|
340
|
+
const deepFiles = getFilePaths(testDir, "**/very/**/*.txt");
|
|
341
|
+
expect(deepFiles.some(path => path.includes("deep-file.txt"))).toBe(true);
|
|
342
|
+
|
|
343
|
+
const deepDirs = getDirectoryPaths(testDir, "**/very/**");
|
|
344
|
+
expect(deepDirs.some(path => path.endsWith("nested"))).toBe(true);
|
|
345
|
+
expect(deepDirs.some(path => path.endsWith("structure"))).toBe(true);
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
describe("Function Consistency", () => {
|
|
350
|
+
test("getPaths('yes') should equal getFilePaths() + getDirectoryPaths()", () => {
|
|
351
|
+
const pattern = "**/*";
|
|
352
|
+
|
|
353
|
+
const allPaths = getPaths(testDir, pattern, "yes");
|
|
354
|
+
const files = getFilePaths(testDir, pattern);
|
|
355
|
+
const dirs = getDirectoryPaths(testDir, pattern);
|
|
356
|
+
|
|
357
|
+
const combined = [...files, ...dirs].sort();
|
|
358
|
+
const allSorted = allPaths.sort();
|
|
359
|
+
|
|
360
|
+
expect(allSorted).toEqual(combined);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("should not have duplicates in any function", () => {
|
|
364
|
+
const files = getFilePaths(testDir, "**/*");
|
|
365
|
+
const dirs = getDirectoryPaths(testDir, "**/*");
|
|
366
|
+
const all = getPaths(testDir, "**/*", "yes");
|
|
367
|
+
|
|
368
|
+
expect(files.length).toBe(new Set(files).size);
|
|
369
|
+
expect(dirs.length).toBe(new Set(dirs).size);
|
|
370
|
+
expect(all.length).toBe(new Set(all).size);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("files and directories should be mutually exclusive", () => {
|
|
374
|
+
const files = getFilePaths(testDir, "**/*");
|
|
375
|
+
const dirs = getDirectoryPaths(testDir, "**/*");
|
|
376
|
+
|
|
377
|
+
const intersection = files.filter(file => dirs.includes(file));
|
|
378
|
+
expect(intersection).toEqual([]);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("different patterns should be consistent across functions", () => {
|
|
382
|
+
const patterns = ["*.js", "src/**", "**/*.md", "**/components/*"];
|
|
383
|
+
|
|
384
|
+
patterns.forEach(pattern => {
|
|
385
|
+
const allPaths = getPaths(testDir, pattern, "yes");
|
|
386
|
+
const files = getFilePaths(testDir, pattern);
|
|
387
|
+
const dirs = getDirectoryPaths(testDir, pattern);
|
|
388
|
+
|
|
389
|
+
files.forEach(file => {
|
|
390
|
+
expect(allPaths).toContain(file);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
dirs.forEach(dir => {
|
|
394
|
+
expect(allPaths).toContain(dir);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("should maintain consistent behavior with default parameters", () => {
|
|
400
|
+
const result1 = getPaths(testDir);
|
|
401
|
+
const result2 = getPaths(testDir, "**/*");
|
|
402
|
+
const result3 = getPaths(testDir, "**/*", "no");
|
|
403
|
+
const result4 = getFilePaths(testDir);
|
|
404
|
+
const result5 = getFilePaths(testDir, "**/*");
|
|
405
|
+
|
|
406
|
+
expect(result1).toEqual(result2);
|
|
407
|
+
expect(result2).toEqual(result3);
|
|
408
|
+
expect(result3).toEqual(result4);
|
|
409
|
+
expect(result4).toEqual(result5);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe("Special File Names", () => {
|
|
414
|
+
test("should handle files with spaces", () => {
|
|
415
|
+
const files = getFilePaths(testDir, "**/*");
|
|
416
|
+
expect(files.some(path => path.includes("file with spaces.txt"))).toBe(true);
|
|
417
|
+
|
|
418
|
+
const specific = getFilePaths(testDir, "*with*");
|
|
419
|
+
expect(specific.some(path => path.includes("file with spaces.txt"))).toBe(true);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("should handle files without extensions", () => {
|
|
423
|
+
const files = getFilePaths(testDir, "**/*");
|
|
424
|
+
expect(files.some(path => path.includes("file-no-extension"))).toBe(true);
|
|
425
|
+
|
|
426
|
+
const withExt = getFilePaths(testDir, "*.txt");
|
|
427
|
+
expect(withExt.some(path => path.includes("file-no-extension"))).toBe(false);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("should handle files with unicode characters", () => {
|
|
431
|
+
const files = getFilePaths(testDir, "**/*");
|
|
432
|
+
expect(files.some(path => path.includes("números-ñ-ü.js"))).toBe(true);
|
|
433
|
+
|
|
434
|
+
const jsFiles = getFilePaths(testDir, "*.js");
|
|
435
|
+
expect(jsFiles.some(path => path.includes("números-ñ-ü.js"))).toBe(true);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("should handle uppercase/lowercase variations", () => {
|
|
439
|
+
const files = getFilePaths(testDir, "**/*");
|
|
440
|
+
expect(files.some(path => path.includes("UPPERCASE.TXT"))).toBe(true);
|
|
441
|
+
expect(files.some(path => path.includes("file1.txt"))).toBe(true);
|
|
442
|
+
|
|
443
|
+
// Should be case sensitive
|
|
444
|
+
const upper = getFilePaths(testDir, "*UPPER*");
|
|
445
|
+
const lower = getFilePaths(testDir, "*upper*");
|
|
446
|
+
expect(upper.length).toBeGreaterThan(0);
|
|
447
|
+
expect(lower).toEqual([]);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("should handle various file extensions", () => {
|
|
451
|
+
const extensions = ["js", "ts", "tsx", "json", "css", "jsx", "md", "txt"];
|
|
452
|
+
|
|
453
|
+
extensions.forEach(ext => {
|
|
454
|
+
const files = getFilePaths(testDir, `*.${ext}`);
|
|
455
|
+
expect(Array.isArray(files)).toBe(true);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
expect(getFilePaths(testDir, "*.css").some(path => path.includes("styles.css"))).toBe(true);
|
|
459
|
+
expect(getFilePaths(testDir, "*.jsx").some(path => path.includes("script.jsx"))).toBe(true);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test("should handle deeply nested paths correctly", () => {
|
|
463
|
+
const deepFiles = getFilePaths(testDir, "**/structure/*");
|
|
464
|
+
expect(deepFiles.some(path => path.includes("deep-file.txt"))).toBe(true);
|
|
465
|
+
|
|
466
|
+
const deepDirs = getDirectoryPaths(testDir, "**/very/*");
|
|
467
|
+
expect(deepDirs.some(path => path.endsWith("nested"))).toBe(true);
|
|
468
|
+
|
|
469
|
+
const allDeep = getPaths(testDir, "deep/**", "yes");
|
|
470
|
+
expect(allDeep.length).toBeGreaterThanOrEqual(4);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("should normalize paths consistently", () => {
|
|
474
|
+
const files = getFilePaths(testDir, "**/*");
|
|
475
|
+
const dirs = getDirectoryPaths(testDir, "**/*");
|
|
476
|
+
|
|
477
|
+
files.forEach(file => {
|
|
478
|
+
expect(file).toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
479
|
+
expect(file).not.toMatch(/[\/\\]$/);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
dirs.forEach(dir => {
|
|
483
|
+
expect(dir).toMatch(/^[\/\\]|^[a-zA-Z]:[\/\\]/);
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
|
package/src/getPaths.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Glob } from "bun";
|
|
2
|
+
import { statSync } from "fs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Gets file paths, directory paths, or both based on the specified parameters
|
|
6
|
+
* @param path - The base directory path to search in
|
|
7
|
+
* @param pattern - The glob pattern to match files/directories (default: **\/*)
|
|
8
|
+
* @param includeDirectories - Controls what to include: yes (files + dirs), no (files only), onlyDirectories (dirs only)
|
|
9
|
+
* @param absolute - Whether to return absolute paths (default: true)
|
|
10
|
+
* @returns Array of paths matching the criteria
|
|
11
|
+
*/
|
|
12
|
+
export const getPaths = (
|
|
13
|
+
path: string,
|
|
14
|
+
pattern: string = "**/*",
|
|
15
|
+
includeDirectories: "yes" | "no" | "onlyDirectories" = "no",
|
|
16
|
+
absolute: boolean = true,
|
|
17
|
+
): string[] => {
|
|
18
|
+
try {
|
|
19
|
+
if (includeDirectories === "no") return getFilePaths(path, pattern, absolute);
|
|
20
|
+
if (includeDirectories === "onlyDirectories") return getDirectoryPaths(path, pattern, absolute);
|
|
21
|
+
|
|
22
|
+
const paths: string[] = [];
|
|
23
|
+
|
|
24
|
+
const glob = new Glob(pattern);
|
|
25
|
+
|
|
26
|
+
const files = glob.scanSync({
|
|
27
|
+
cwd: path,
|
|
28
|
+
onlyFiles: false,
|
|
29
|
+
absolute: absolute,
|
|
30
|
+
dot: true,
|
|
31
|
+
followSymlinks: false,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
paths.push(...files);
|
|
35
|
+
|
|
36
|
+
return paths;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`Error getting paths from ${path}:`, error);
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gets only file paths (excluding directories) matching the pattern
|
|
46
|
+
* @param path - The base directory path to search in
|
|
47
|
+
* @param pattern - The glob pattern to match files (default: **\/*)
|
|
48
|
+
* @param absolute - Whether to return absolute paths (default: true)
|
|
49
|
+
* @returns Array of file paths
|
|
50
|
+
*/
|
|
51
|
+
export const getFilePaths = (path: string, pattern: string = "**/*", absolute: boolean = true): string[] => {
|
|
52
|
+
try {
|
|
53
|
+
const paths: string[] = [];
|
|
54
|
+
|
|
55
|
+
const glob = new Glob(pattern);
|
|
56
|
+
|
|
57
|
+
const files = glob.scanSync({
|
|
58
|
+
cwd: path,
|
|
59
|
+
onlyFiles: true,
|
|
60
|
+
absolute: absolute,
|
|
61
|
+
dot: true,
|
|
62
|
+
followSymlinks: false,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
paths.push(...files);
|
|
66
|
+
|
|
67
|
+
return paths;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(`Error getting file paths from ${path}:`, error);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Gets only directory paths (excluding files) matching the pattern
|
|
76
|
+
* @param path - The base directory path to search in
|
|
77
|
+
* @param pattern - The glob pattern to match directories (default: **\/*)
|
|
78
|
+
* @param absolute - Whether to return absolute paths (default: true)
|
|
79
|
+
* @returns Array of directory paths
|
|
80
|
+
*/
|
|
81
|
+
export const getDirectoryPaths = (path: string, pattern: string = "**/*", absolute: boolean = true): string[] => {
|
|
82
|
+
try {
|
|
83
|
+
const paths: string[] = [];
|
|
84
|
+
|
|
85
|
+
const glob = new Glob(pattern);
|
|
86
|
+
|
|
87
|
+
const allItems = [...glob.scanSync({
|
|
88
|
+
cwd: path,
|
|
89
|
+
onlyFiles: false,
|
|
90
|
+
absolute: absolute,
|
|
91
|
+
dot: true,
|
|
92
|
+
followSymlinks: false,
|
|
93
|
+
})];
|
|
94
|
+
|
|
95
|
+
const directories = allItems.filter(item => {
|
|
96
|
+
try {
|
|
97
|
+
return statSync(item).isDirectory();
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
paths.push(...directories);
|
|
104
|
+
|
|
105
|
+
return paths;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(`Error getting directory paths from ${path}:`, error);
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
|
package/src/index.ts
CHANGED
package/changelog.md
DELETED