@wtasnorg/node-lib 0.0.5 → 0.0.6
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/DEV_CHECKLIST.md +0 -1
- package/docs/functions/createFindDirectories.md +38 -0
- package/docs/functions/hello.md +1 -1
- package/docs/functions/pojo.md +1 -1
- package/docs/globals.md +6 -0
- package/docs/interfaces/FileSystemDependencies.md +51 -0
- package/docs/interfaces/FindDirectoriesOptions.md +41 -0
- package/package.json +2 -2
- package/src/find.d.ts +25 -0
- package/src/find.js +62 -0
- package/src/find.test.d.ts +2 -0
- package/src/find.test.js +127 -0
- package/src/find.test.ts +156 -0
- package/src/find.ts +99 -0
- package/src/index.d.ts +4 -1
- package/src/index.js +2 -1
- package/src/index.ts +9 -1
- package/docs/docs.json +0 -300
package/DEV_CHECKLIST.md
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[**@wtasnorg/node-lib**](../README.md)
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
5
|
+
[@wtasnorg/node-lib](../globals.md) / createFindDirectories
|
|
6
|
+
|
|
7
|
+
# Function: createFindDirectories()
|
|
8
|
+
|
|
9
|
+
> **createFindDirectories**(`deps`): (`root`, `options`) => `Promise`\<`string`[]\>
|
|
10
|
+
|
|
11
|
+
Defined in: [find.ts:19](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/find.ts#L19)
|
|
12
|
+
|
|
13
|
+
Factory that produces an async findDirectories function with
|
|
14
|
+
injected filesystem dependencies for full testability.
|
|
15
|
+
|
|
16
|
+
## Parameters
|
|
17
|
+
|
|
18
|
+
### deps
|
|
19
|
+
|
|
20
|
+
[`FileSystemDependencies`](../interfaces/FileSystemDependencies.md)
|
|
21
|
+
|
|
22
|
+
## Returns
|
|
23
|
+
|
|
24
|
+
> (`root`, `options`): `Promise`\<`string`[]\>
|
|
25
|
+
|
|
26
|
+
### Parameters
|
|
27
|
+
|
|
28
|
+
#### root
|
|
29
|
+
|
|
30
|
+
`string`
|
|
31
|
+
|
|
32
|
+
#### options
|
|
33
|
+
|
|
34
|
+
[`FindDirectoriesOptions`](../interfaces/FindDirectoriesOptions.md) = `{}`
|
|
35
|
+
|
|
36
|
+
### Returns
|
|
37
|
+
|
|
38
|
+
`Promise`\<`string`[]\>
|
package/docs/functions/hello.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
> **hello**(): `Promise`\<`string`\>
|
|
10
10
|
|
|
11
|
-
Defined in: [hello.ts:6](https://github.com/wtasg/node-lib/blob/
|
|
11
|
+
Defined in: [hello.ts:6](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/hello.ts#L6)
|
|
12
12
|
|
|
13
13
|
A sample function that should work to test if lib is installed correctly.
|
|
14
14
|
|
package/docs/functions/pojo.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
> **pojo**\<`T`\>(`instance`): `Record`\<`string`, `any`\>
|
|
10
10
|
|
|
11
|
-
Defined in: [pojo.ts:10](https://github.com/wtasg/node-lib/blob/
|
|
11
|
+
Defined in: [pojo.ts:10](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/pojo.ts#L10)
|
|
12
12
|
|
|
13
13
|
Convert a class instance into a plain JavaScript object.
|
|
14
14
|
Copies only the instance's own enumerable data properties
|
package/docs/globals.md
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
# @wtasnorg/node-lib
|
|
6
6
|
|
|
7
|
+
## Interfaces
|
|
8
|
+
|
|
9
|
+
- [FileSystemDependencies](interfaces/FileSystemDependencies.md)
|
|
10
|
+
- [FindDirectoriesOptions](interfaces/FindDirectoriesOptions.md)
|
|
11
|
+
|
|
7
12
|
## Functions
|
|
8
13
|
|
|
14
|
+
- [createFindDirectories](functions/createFindDirectories.md)
|
|
9
15
|
- [hello](functions/hello.md)
|
|
10
16
|
- [pojo](functions/pojo.md)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[**@wtasnorg/node-lib**](../README.md)
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
5
|
+
[@wtasnorg/node-lib](../globals.md) / FileSystemDependencies
|
|
6
|
+
|
|
7
|
+
# Interface: FileSystemDependencies
|
|
8
|
+
|
|
9
|
+
Defined in: [find.ts:3](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/find.ts#L3)
|
|
10
|
+
|
|
11
|
+
## Properties
|
|
12
|
+
|
|
13
|
+
### readdir()
|
|
14
|
+
|
|
15
|
+
> **readdir**: (`path`, `opts`) => `Promise`\<`object`[]\>
|
|
16
|
+
|
|
17
|
+
Defined in: [find.ts:4](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/find.ts#L4)
|
|
18
|
+
|
|
19
|
+
#### Parameters
|
|
20
|
+
|
|
21
|
+
##### path
|
|
22
|
+
|
|
23
|
+
`string`
|
|
24
|
+
|
|
25
|
+
##### opts
|
|
26
|
+
|
|
27
|
+
###### withFileTypes
|
|
28
|
+
|
|
29
|
+
`true`
|
|
30
|
+
|
|
31
|
+
#### Returns
|
|
32
|
+
|
|
33
|
+
`Promise`\<`object`[]\>
|
|
34
|
+
|
|
35
|
+
***
|
|
36
|
+
|
|
37
|
+
### stat()
|
|
38
|
+
|
|
39
|
+
> **stat**: (`path`) => `Promise`\<\{ `isDirectory`: `boolean`; \}\>
|
|
40
|
+
|
|
41
|
+
Defined in: [find.ts:5](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/find.ts#L5)
|
|
42
|
+
|
|
43
|
+
#### Parameters
|
|
44
|
+
|
|
45
|
+
##### path
|
|
46
|
+
|
|
47
|
+
`string`
|
|
48
|
+
|
|
49
|
+
#### Returns
|
|
50
|
+
|
|
51
|
+
`Promise`\<\{ `isDirectory`: `boolean`; \}\>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[**@wtasnorg/node-lib**](../README.md)
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
5
|
+
[@wtasnorg/node-lib](../globals.md) / FindDirectoriesOptions
|
|
6
|
+
|
|
7
|
+
# Interface: FindDirectoriesOptions
|
|
8
|
+
|
|
9
|
+
Defined in: [find.ts:8](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/find.ts#L8)
|
|
10
|
+
|
|
11
|
+
## Properties
|
|
12
|
+
|
|
13
|
+
### allowlist?
|
|
14
|
+
|
|
15
|
+
> `optional` **allowlist**: `string`[] \| (`absPath`, `name`) => `boolean`
|
|
16
|
+
|
|
17
|
+
Defined in: [find.ts:11](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/find.ts#L11)
|
|
18
|
+
|
|
19
|
+
***
|
|
20
|
+
|
|
21
|
+
### blocklist?
|
|
22
|
+
|
|
23
|
+
> `optional` **blocklist**: `string`[] \| (`absPath`, `name`) => `boolean`
|
|
24
|
+
|
|
25
|
+
Defined in: [find.ts:12](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/find.ts#L12)
|
|
26
|
+
|
|
27
|
+
***
|
|
28
|
+
|
|
29
|
+
### followSymlinks?
|
|
30
|
+
|
|
31
|
+
> `optional` **followSymlinks**: `boolean`
|
|
32
|
+
|
|
33
|
+
Defined in: [find.ts:10](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/find.ts#L10)
|
|
34
|
+
|
|
35
|
+
***
|
|
36
|
+
|
|
37
|
+
### maxDepth?
|
|
38
|
+
|
|
39
|
+
> `optional` **maxDepth**: `number`
|
|
40
|
+
|
|
41
|
+
Defined in: [find.ts:9](https://github.com/wtasg/node-lib/blob/01d2e57d5a7dbc3451098355b40ab686f5320efe/src/find.ts#L9)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtasnorg/node-lib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "node library",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^24.10.1",
|
|
31
|
-
"typedoc": "^0.28.
|
|
31
|
+
"typedoc": "^0.28.15",
|
|
32
32
|
"typedoc-plugin-markdown": "^4.9.0",
|
|
33
33
|
"typescript": "^5.9.3"
|
|
34
34
|
}
|
package/src/find.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface FileSystemDependencies {
|
|
2
|
+
readdir: (path: string, opts: {
|
|
3
|
+
withFileTypes: true;
|
|
4
|
+
}) => Promise<{
|
|
5
|
+
name: string;
|
|
6
|
+
isDirectory(): boolean;
|
|
7
|
+
}[]>;
|
|
8
|
+
stat: (path: string) => Promise<{
|
|
9
|
+
isDirectory(): boolean;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
interface FindDirectoriesOptions {
|
|
13
|
+
maxDepth?: number;
|
|
14
|
+
followSymlinks?: boolean;
|
|
15
|
+
allowlist?: string[] | ((absPath: string, name: string) => boolean);
|
|
16
|
+
blocklist?: string[] | ((absPath: string, name: string) => boolean);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Factory that produces an async findDirectories function with
|
|
20
|
+
* injected filesystem dependencies for full testability.
|
|
21
|
+
*/
|
|
22
|
+
declare function createFindDirectories(deps: FileSystemDependencies): (root: string, options?: FindDirectoriesOptions) => Promise<string[]>;
|
|
23
|
+
export { createFindDirectories, };
|
|
24
|
+
export type { FileSystemDependencies, FindDirectoriesOptions, };
|
|
25
|
+
//# sourceMappingURL=find.d.ts.map
|
package/src/find.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { resolve, join } from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Factory that produces an async findDirectories function with
|
|
4
|
+
* injected filesystem dependencies for full testability.
|
|
5
|
+
*/
|
|
6
|
+
function createFindDirectories(deps) {
|
|
7
|
+
const { readdir } = deps;
|
|
8
|
+
return async function findDirectories(root, options = {}) {
|
|
9
|
+
const { maxDepth = 1, allowlist, blocklist } = options;
|
|
10
|
+
const absRoot = resolve(root);
|
|
11
|
+
const results = [];
|
|
12
|
+
function isAllowed(absPath, name) {
|
|
13
|
+
if (allowlist) {
|
|
14
|
+
if (Array.isArray(allowlist)) {
|
|
15
|
+
if (!allowlist.includes(name))
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
else if (!allowlist(absPath, name)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
function isBlocked(absPath, name) {
|
|
25
|
+
if (blocklist) {
|
|
26
|
+
if (Array.isArray(blocklist)) {
|
|
27
|
+
if (blocklist.includes(name))
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
else if (blocklist(absPath, name)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
async function walk(currentPath, depth) {
|
|
37
|
+
if (depth > maxDepth)
|
|
38
|
+
return;
|
|
39
|
+
const entries = await readdir(currentPath, { withFileTypes: true });
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const childPath = resolve(join(currentPath, entry.name));
|
|
42
|
+
let isDirectory = entry.isDirectory();
|
|
43
|
+
if (!isDirectory)
|
|
44
|
+
continue;
|
|
45
|
+
if (isBlocked(childPath, entry.name))
|
|
46
|
+
continue;
|
|
47
|
+
if (!isAllowed(childPath, entry.name))
|
|
48
|
+
continue;
|
|
49
|
+
results.push(childPath);
|
|
50
|
+
if (depth < maxDepth) {
|
|
51
|
+
await walk(childPath, depth + 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// match find(1): root is always included
|
|
56
|
+
results.push(absRoot);
|
|
57
|
+
await walk(absRoot, 1);
|
|
58
|
+
return results;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export { createFindDirectories, };
|
|
62
|
+
//# sourceMappingURL=find.js.map
|
package/src/find.test.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createFindDirectories } from "./find.js";
|
|
4
|
+
// Helper to build Dirent-like objects
|
|
5
|
+
function dir(name) {
|
|
6
|
+
return { name, isDirectory: () => true };
|
|
7
|
+
}
|
|
8
|
+
function file(name) {
|
|
9
|
+
return { name, isDirectory: () => false };
|
|
10
|
+
}
|
|
11
|
+
test("find: findDirectories success", async () => {
|
|
12
|
+
// Minimal mock FS: /root contains one directory "a" and one file "b"
|
|
13
|
+
const mockFS = {
|
|
14
|
+
readdir: async (path) => {
|
|
15
|
+
if (path === "/root") {
|
|
16
|
+
return [
|
|
17
|
+
{ name: "a", isDirectory: () => true },
|
|
18
|
+
{ name: "b", isDirectory: () => false },
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
},
|
|
23
|
+
stat: async (path) => ({
|
|
24
|
+
isDirectory: () => path === "/root" || path === "/root/a"
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
const findDirectories = createFindDirectories(mockFS);
|
|
28
|
+
const result = await findDirectories("/root", { maxDepth: 1 });
|
|
29
|
+
assert.deepEqual(result, [
|
|
30
|
+
"/root",
|
|
31
|
+
"/root/a"
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
34
|
+
test("find: findDirectories symlink is NOT treated as a directory", async () => {
|
|
35
|
+
const mockFS = {
|
|
36
|
+
readdir: async (path) => {
|
|
37
|
+
if (path === "/root") {
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
name: "linkToA",
|
|
41
|
+
isDirectory: () => false
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
return [];
|
|
46
|
+
},
|
|
47
|
+
stat: async (path) => ({
|
|
48
|
+
isDirectory: () => path === "/root" // only root is a directory
|
|
49
|
+
}),
|
|
50
|
+
readlink: async () => {
|
|
51
|
+
throw new Error("readlink should not be called when symlink support is removed");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const findDirectories = createFindDirectories(mockFS);
|
|
55
|
+
const result = await findDirectories("/root", { maxDepth: 1 });
|
|
56
|
+
assert.deepEqual(result, [
|
|
57
|
+
"/root"
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
test("find: findDirectories at depth=2", async () => {
|
|
61
|
+
const calls = [];
|
|
62
|
+
const deps = {
|
|
63
|
+
readdir: async (path) => {
|
|
64
|
+
calls.push(path);
|
|
65
|
+
switch (path) {
|
|
66
|
+
case "/root":
|
|
67
|
+
return [dir("a"), dir("b"), file("ignore.txt")];
|
|
68
|
+
case "/root/a":
|
|
69
|
+
return [dir("a1")];
|
|
70
|
+
case "/root/b":
|
|
71
|
+
return [dir("b1")];
|
|
72
|
+
default:
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
stat: async (_) => ({
|
|
77
|
+
isDirectory: () => false
|
|
78
|
+
})
|
|
79
|
+
};
|
|
80
|
+
const find = createFindDirectories(deps);
|
|
81
|
+
const result = await find("/root", { maxDepth: 2 });
|
|
82
|
+
assert.deepEqual(result.sort(), [
|
|
83
|
+
"/root",
|
|
84
|
+
"/root/a",
|
|
85
|
+
"/root/b",
|
|
86
|
+
"/root/a/a1",
|
|
87
|
+
"/root/b/b1"
|
|
88
|
+
].sort());
|
|
89
|
+
});
|
|
90
|
+
test("find: findDirectories at depth=3", async () => {
|
|
91
|
+
const calls = [];
|
|
92
|
+
const deps = {
|
|
93
|
+
readdir: async (path) => {
|
|
94
|
+
calls.push(path);
|
|
95
|
+
switch (path) {
|
|
96
|
+
case "/root":
|
|
97
|
+
return [dir("a"), dir("b")];
|
|
98
|
+
case "/root/a":
|
|
99
|
+
return [dir("a1")];
|
|
100
|
+
case "/root/b":
|
|
101
|
+
return [dir("b1")];
|
|
102
|
+
case "/root/a/a1":
|
|
103
|
+
case "/root/b/b1":
|
|
104
|
+
return [dir("c")];
|
|
105
|
+
case "/root/b/b1/c":
|
|
106
|
+
return [dir("d"), file("fd")];
|
|
107
|
+
default:
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
stat: async (_) => ({
|
|
112
|
+
isDirectory: () => false
|
|
113
|
+
})
|
|
114
|
+
};
|
|
115
|
+
const find = createFindDirectories(deps);
|
|
116
|
+
const result = await find("/root", { maxDepth: 3 });
|
|
117
|
+
assert.deepEqual(result.sort(), [
|
|
118
|
+
"/root",
|
|
119
|
+
"/root/a",
|
|
120
|
+
"/root/b",
|
|
121
|
+
"/root/a/a1",
|
|
122
|
+
"/root/a/a1/c",
|
|
123
|
+
"/root/b/b1",
|
|
124
|
+
"/root/b/b1/c",
|
|
125
|
+
].sort());
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=find.test.js.map
|
package/src/find.test.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { createFindDirectories } from "./find.js";
|
|
5
|
+
|
|
6
|
+
import type { FileSystemDependencies } from "./find.js";
|
|
7
|
+
|
|
8
|
+
// Helper to build Dirent-like objects
|
|
9
|
+
function dir(name: string): { name: string; isDirectory(): boolean } {
|
|
10
|
+
return { name, isDirectory: () => true };
|
|
11
|
+
}
|
|
12
|
+
function file(name: string): { name: string; isDirectory(): boolean } {
|
|
13
|
+
return { name, isDirectory: () => false };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("find: findDirectories success", async () => {
|
|
17
|
+
|
|
18
|
+
// Minimal mock FS: /root contains one directory "a" and one file "b"
|
|
19
|
+
const mockFS = {
|
|
20
|
+
readdir: async (path: string) => {
|
|
21
|
+
if (path === "/root") {
|
|
22
|
+
return [
|
|
23
|
+
{ name: "a", isDirectory: () => true },
|
|
24
|
+
{ name: "b", isDirectory: () => false },
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
stat: async (path: string) => ({
|
|
31
|
+
isDirectory: () => path === "/root" || path === "/root/a"
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const findDirectories = createFindDirectories(mockFS);
|
|
36
|
+
|
|
37
|
+
const result = await findDirectories("/root", { maxDepth: 1 });
|
|
38
|
+
|
|
39
|
+
assert.deepEqual(result, [
|
|
40
|
+
"/root",
|
|
41
|
+
"/root/a"
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("find: findDirectories symlink is NOT treated as a directory", async () => {
|
|
46
|
+
|
|
47
|
+
const mockFS = {
|
|
48
|
+
readdir: async (path: string) => {
|
|
49
|
+
if (path === "/root") {
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
name: "linkToA",
|
|
53
|
+
isDirectory: () => false
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
stat: async (path: string) => ({
|
|
61
|
+
isDirectory: () => path === "/root" // only root is a directory
|
|
62
|
+
}),
|
|
63
|
+
|
|
64
|
+
readlink: async () => {
|
|
65
|
+
throw new Error("readlink should not be called when symlink support is removed");
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const findDirectories = createFindDirectories(mockFS);
|
|
70
|
+
|
|
71
|
+
const result = await findDirectories("/root", { maxDepth: 1 });
|
|
72
|
+
|
|
73
|
+
assert.deepEqual(result, [
|
|
74
|
+
"/root"
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
test("find: findDirectories at depth=2", async () => {
|
|
80
|
+
const calls: string[] = [];
|
|
81
|
+
|
|
82
|
+
const deps: FileSystemDependencies = {
|
|
83
|
+
readdir: async (path: string): Promise<{ name: string; isDirectory(): boolean }[]> => {
|
|
84
|
+
calls.push(path);
|
|
85
|
+
|
|
86
|
+
switch (path) {
|
|
87
|
+
case "/root":
|
|
88
|
+
return [dir("a"), dir("b"), file("ignore.txt")];
|
|
89
|
+
case "/root/a":
|
|
90
|
+
return [dir("a1")];
|
|
91
|
+
case "/root/b":
|
|
92
|
+
return [dir("b1")];
|
|
93
|
+
default:
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
stat: async (_: string) => ({
|
|
98
|
+
isDirectory: () => false
|
|
99
|
+
})
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const find = createFindDirectories(deps);
|
|
103
|
+
|
|
104
|
+
const result = await find("/root", { maxDepth: 2 });
|
|
105
|
+
|
|
106
|
+
assert.deepEqual(result.sort(), [
|
|
107
|
+
"/root",
|
|
108
|
+
"/root/a",
|
|
109
|
+
"/root/b",
|
|
110
|
+
"/root/a/a1",
|
|
111
|
+
"/root/b/b1"
|
|
112
|
+
].sort());
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("find: findDirectories at depth=3", async () => {
|
|
116
|
+
const calls: string[] = [];
|
|
117
|
+
|
|
118
|
+
const deps: FileSystemDependencies = {
|
|
119
|
+
readdir: async (path: string): Promise<{ name: string; isDirectory(): boolean }[]> => {
|
|
120
|
+
calls.push(path);
|
|
121
|
+
|
|
122
|
+
switch (path) {
|
|
123
|
+
case "/root":
|
|
124
|
+
return [dir("a"), dir("b")];
|
|
125
|
+
case "/root/a":
|
|
126
|
+
return [dir("a1")];
|
|
127
|
+
case "/root/b":
|
|
128
|
+
return [dir("b1")];
|
|
129
|
+
case "/root/a/a1":
|
|
130
|
+
case "/root/b/b1":
|
|
131
|
+
return [dir("c")];
|
|
132
|
+
case "/root/b/b1/c":
|
|
133
|
+
return [dir("d"), file("fd")]
|
|
134
|
+
default:
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
stat: async (_: string) => ({
|
|
139
|
+
isDirectory: () => false
|
|
140
|
+
})
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const find = createFindDirectories(deps);
|
|
144
|
+
|
|
145
|
+
const result = await find("/root", { maxDepth: 3 });
|
|
146
|
+
|
|
147
|
+
assert.deepEqual(result.sort(), [
|
|
148
|
+
"/root",
|
|
149
|
+
"/root/a",
|
|
150
|
+
"/root/b",
|
|
151
|
+
"/root/a/a1",
|
|
152
|
+
"/root/a/a1/c",
|
|
153
|
+
"/root/b/b1",
|
|
154
|
+
"/root/b/b1/c",
|
|
155
|
+
].sort());
|
|
156
|
+
});
|
package/src/find.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { resolve, join } from "node:path";
|
|
2
|
+
|
|
3
|
+
interface FileSystemDependencies {
|
|
4
|
+
readdir: (path: string, opts: { withFileTypes: true }) => Promise<{ name: string; isDirectory(): boolean; }[]>;
|
|
5
|
+
stat: (path: string) => Promise<{ isDirectory(): boolean }>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface FindDirectoriesOptions {
|
|
9
|
+
maxDepth?: number;
|
|
10
|
+
followSymlinks?: boolean;
|
|
11
|
+
allowlist?: string[] | ((absPath: string, name: string) => boolean);
|
|
12
|
+
blocklist?: string[] | ((absPath: string, name: string) => boolean);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Factory that produces an async findDirectories function with
|
|
17
|
+
* injected filesystem dependencies for full testability.
|
|
18
|
+
*/
|
|
19
|
+
function createFindDirectories(deps: FileSystemDependencies) {
|
|
20
|
+
|
|
21
|
+
const { readdir } = deps;
|
|
22
|
+
|
|
23
|
+
return async function findDirectories(
|
|
24
|
+
root: string,
|
|
25
|
+
options: FindDirectoriesOptions = {}
|
|
26
|
+
): Promise<string[]> {
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
maxDepth = 1,
|
|
30
|
+
allowlist,
|
|
31
|
+
blocklist
|
|
32
|
+
} = options;
|
|
33
|
+
|
|
34
|
+
const absRoot = resolve(root);
|
|
35
|
+
const results: string[] = [];
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function isAllowed(absPath: string, name: string): boolean {
|
|
39
|
+
if (allowlist) {
|
|
40
|
+
if (Array.isArray(allowlist)) {
|
|
41
|
+
if (!allowlist.includes(name)) return false;
|
|
42
|
+
} else if (!allowlist(absPath, name)) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isBlocked(absPath: string, name: string): boolean {
|
|
50
|
+
if (blocklist) {
|
|
51
|
+
if (Array.isArray(blocklist)) {
|
|
52
|
+
if (blocklist.includes(name)) return true;
|
|
53
|
+
} else if (blocklist(absPath, name)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function walk(currentPath: string, depth: number): Promise<void> {
|
|
61
|
+
if (depth > maxDepth) return;
|
|
62
|
+
|
|
63
|
+
const entries = await readdir(currentPath, { withFileTypes: true });
|
|
64
|
+
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const childPath = resolve(join(currentPath, entry.name));
|
|
67
|
+
|
|
68
|
+
let isDirectory = entry.isDirectory();
|
|
69
|
+
|
|
70
|
+
if (!isDirectory) continue;
|
|
71
|
+
|
|
72
|
+
if (isBlocked(childPath, entry.name)) continue;
|
|
73
|
+
if (!isAllowed(childPath, entry.name)) continue;
|
|
74
|
+
|
|
75
|
+
results.push(childPath);
|
|
76
|
+
|
|
77
|
+
if (depth < maxDepth) {
|
|
78
|
+
await walk(childPath, depth + 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// match find(1): root is always included
|
|
84
|
+
results.push(absRoot);
|
|
85
|
+
|
|
86
|
+
await walk(absRoot, 1);
|
|
87
|
+
|
|
88
|
+
return results;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
createFindDirectories,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type {
|
|
97
|
+
FileSystemDependencies,
|
|
98
|
+
FindDirectoriesOptions,
|
|
99
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { hello } from "./hello.js";
|
|
2
2
|
import { pojo } from "./pojo.js";
|
|
3
|
-
|
|
3
|
+
import type { FindDirectoriesOptions, FileSystemDependencies } from "./find.js";
|
|
4
|
+
import { createFindDirectories } from "./find.js";
|
|
5
|
+
export { hello, pojo, createFindDirectories };
|
|
6
|
+
export type { FindDirectoriesOptions, FileSystemDependencies, };
|
|
4
7
|
//# sourceMappingURL=index.d.ts.map
|
package/src/index.js
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { hello } from "./hello.js";
|
|
2
2
|
import { pojo } from "./pojo.js";
|
|
3
|
+
import type { FindDirectoriesOptions, FileSystemDependencies } from "./find.js";
|
|
4
|
+
import { createFindDirectories } from "./find.js";
|
|
3
5
|
|
|
4
6
|
export {
|
|
5
7
|
hello,
|
|
6
|
-
pojo
|
|
8
|
+
pojo,
|
|
9
|
+
createFindDirectories
|
|
7
10
|
};
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
FindDirectoriesOptions,
|
|
14
|
+
FileSystemDependencies,
|
|
15
|
+
}
|
package/docs/docs.json
DELETED
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": "2.0",
|
|
3
|
-
"id": 0,
|
|
4
|
-
"name": "@wtasnorg/node-lib",
|
|
5
|
-
"variant": "project",
|
|
6
|
-
"kind": 1,
|
|
7
|
-
"flags": {},
|
|
8
|
-
"children": [
|
|
9
|
-
{
|
|
10
|
-
"id": 1,
|
|
11
|
-
"name": "hello",
|
|
12
|
-
"variant": "declaration",
|
|
13
|
-
"kind": 64,
|
|
14
|
-
"flags": {},
|
|
15
|
-
"sources": [
|
|
16
|
-
{
|
|
17
|
-
"fileName": "hello.ts",
|
|
18
|
-
"line": 6,
|
|
19
|
-
"character": 15,
|
|
20
|
-
"url": "https://github.com/wtasg/node-lib/blob/890fd5e866d9096470d56594a5788bf55e34bf7c/src/hello.ts#L6"
|
|
21
|
-
}
|
|
22
|
-
],
|
|
23
|
-
"signatures": [
|
|
24
|
-
{
|
|
25
|
-
"id": 2,
|
|
26
|
-
"name": "hello",
|
|
27
|
-
"variant": "signature",
|
|
28
|
-
"kind": 4096,
|
|
29
|
-
"flags": {},
|
|
30
|
-
"comment": {
|
|
31
|
-
"summary": [
|
|
32
|
-
{
|
|
33
|
-
"kind": "text",
|
|
34
|
-
"text": "A sample function that should work to test if lib is installed correctly."
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
"blockTags": [
|
|
38
|
-
{
|
|
39
|
-
"tag": "@returns",
|
|
40
|
-
"content": [
|
|
41
|
-
{
|
|
42
|
-
"kind": "text",
|
|
43
|
-
"text": "hello message"
|
|
44
|
-
}
|
|
45
|
-
]
|
|
46
|
-
}
|
|
47
|
-
]
|
|
48
|
-
},
|
|
49
|
-
"sources": [
|
|
50
|
-
{
|
|
51
|
-
"fileName": "hello.ts",
|
|
52
|
-
"line": 6,
|
|
53
|
-
"character": 15,
|
|
54
|
-
"url": "https://github.com/wtasg/node-lib/blob/890fd5e866d9096470d56594a5788bf55e34bf7c/src/hello.ts#L6"
|
|
55
|
-
}
|
|
56
|
-
],
|
|
57
|
-
"type": {
|
|
58
|
-
"type": "reference",
|
|
59
|
-
"target": {
|
|
60
|
-
"packageName": "typescript",
|
|
61
|
-
"packagePath": "lib/lib.es5.d.ts",
|
|
62
|
-
"qualifiedName": "Promise"
|
|
63
|
-
},
|
|
64
|
-
"typeArguments": [
|
|
65
|
-
{
|
|
66
|
-
"type": "intrinsic",
|
|
67
|
-
"name": "string"
|
|
68
|
-
}
|
|
69
|
-
],
|
|
70
|
-
"name": "Promise",
|
|
71
|
-
"package": "typescript"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
]
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
"id": 3,
|
|
78
|
-
"name": "pojo",
|
|
79
|
-
"variant": "declaration",
|
|
80
|
-
"kind": 64,
|
|
81
|
-
"flags": {},
|
|
82
|
-
"sources": [
|
|
83
|
-
{
|
|
84
|
-
"fileName": "pojo.ts",
|
|
85
|
-
"line": 10,
|
|
86
|
-
"character": 9,
|
|
87
|
-
"url": "https://github.com/wtasg/node-lib/blob/890fd5e866d9096470d56594a5788bf55e34bf7c/src/pojo.ts#L10"
|
|
88
|
-
}
|
|
89
|
-
],
|
|
90
|
-
"signatures": [
|
|
91
|
-
{
|
|
92
|
-
"id": 4,
|
|
93
|
-
"name": "pojo",
|
|
94
|
-
"variant": "signature",
|
|
95
|
-
"kind": 4096,
|
|
96
|
-
"flags": {},
|
|
97
|
-
"comment": {
|
|
98
|
-
"summary": [
|
|
99
|
-
{
|
|
100
|
-
"kind": "text",
|
|
101
|
-
"text": "Convert a class instance into a plain JavaScript object.\nCopies only the instance's own enumerable data properties\nand excludes methods or prototype values."
|
|
102
|
-
}
|
|
103
|
-
],
|
|
104
|
-
"blockTags": [
|
|
105
|
-
{
|
|
106
|
-
"tag": "@returns",
|
|
107
|
-
"content": [
|
|
108
|
-
{
|
|
109
|
-
"kind": "text",
|
|
110
|
-
"text": "A plain JavaScript object containing only data fields."
|
|
111
|
-
}
|
|
112
|
-
]
|
|
113
|
-
}
|
|
114
|
-
]
|
|
115
|
-
},
|
|
116
|
-
"sources": [
|
|
117
|
-
{
|
|
118
|
-
"fileName": "pojo.ts",
|
|
119
|
-
"line": 10,
|
|
120
|
-
"character": 9,
|
|
121
|
-
"url": "https://github.com/wtasg/node-lib/blob/890fd5e866d9096470d56594a5788bf55e34bf7c/src/pojo.ts#L10"
|
|
122
|
-
}
|
|
123
|
-
],
|
|
124
|
-
"typeParameters": [
|
|
125
|
-
{
|
|
126
|
-
"id": 5,
|
|
127
|
-
"name": "T",
|
|
128
|
-
"variant": "typeParam",
|
|
129
|
-
"kind": 131072,
|
|
130
|
-
"flags": {},
|
|
131
|
-
"type": {
|
|
132
|
-
"type": "intrinsic",
|
|
133
|
-
"name": "object"
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
],
|
|
137
|
-
"parameters": [
|
|
138
|
-
{
|
|
139
|
-
"id": 6,
|
|
140
|
-
"name": "instance",
|
|
141
|
-
"variant": "param",
|
|
142
|
-
"kind": 32768,
|
|
143
|
-
"flags": {},
|
|
144
|
-
"comment": {
|
|
145
|
-
"summary": [
|
|
146
|
-
{
|
|
147
|
-
"kind": "text",
|
|
148
|
-
"text": "A class instance to convert."
|
|
149
|
-
}
|
|
150
|
-
]
|
|
151
|
-
},
|
|
152
|
-
"type": {
|
|
153
|
-
"type": "reference",
|
|
154
|
-
"target": 5,
|
|
155
|
-
"name": "T",
|
|
156
|
-
"package": "@wtasnorg/node-lib",
|
|
157
|
-
"refersToTypeParameter": true
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
],
|
|
161
|
-
"type": {
|
|
162
|
-
"type": "reference",
|
|
163
|
-
"target": {
|
|
164
|
-
"packageName": "typescript",
|
|
165
|
-
"packagePath": "lib/lib.es5.d.ts",
|
|
166
|
-
"qualifiedName": "Record"
|
|
167
|
-
},
|
|
168
|
-
"typeArguments": [
|
|
169
|
-
{
|
|
170
|
-
"type": "intrinsic",
|
|
171
|
-
"name": "string"
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
"type": "intrinsic",
|
|
175
|
-
"name": "any"
|
|
176
|
-
}
|
|
177
|
-
],
|
|
178
|
-
"name": "Record",
|
|
179
|
-
"package": "typescript"
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
]
|
|
183
|
-
}
|
|
184
|
-
],
|
|
185
|
-
"groups": [
|
|
186
|
-
{
|
|
187
|
-
"title": "Functions",
|
|
188
|
-
"children": [
|
|
189
|
-
1,
|
|
190
|
-
3
|
|
191
|
-
]
|
|
192
|
-
}
|
|
193
|
-
],
|
|
194
|
-
"packageName": "@wtasnorg/node-lib",
|
|
195
|
-
"readme": [
|
|
196
|
-
{
|
|
197
|
-
"kind": "text",
|
|
198
|
-
"text": "# @wtasnorg/node-lib\n\nA library project for nodejs. #nodejs #typescript #library\n\n- [npm org](https://www.npmjs.com/org/wtasnorg)\n- [github repo](https://github.com/wtasg/node-lib)\n\n## Functions\n\n1. hello (for debugging)\n2. "
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
"kind": "code",
|
|
202
|
-
"text": "`pojo`"
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
"kind": "text",
|
|
206
|
-
"text": " for converting class objects to Plain Old Javascript Objects.\n\n## Develop\n\n"
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
"kind": "code",
|
|
210
|
-
"text": "```bash\ngit clone git@github.com:wtasg/node-lib.git\nnpm run build\nnpm run test\n# make changes...\n```"
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
"kind": "text",
|
|
214
|
-
"text": "\n\nWe are using "
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
"kind": "code",
|
|
218
|
-
"text": "`node --test`"
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
"kind": "text",
|
|
222
|
-
"text": " for testing.\n\n## Install and Usage\n\n"
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
"kind": "code",
|
|
226
|
-
"text": "```bash\nnpm install @wtasnorg/node-lib\n```"
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
"kind": "text",
|
|
230
|
-
"text": "\n\n"
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
"kind": "code",
|
|
234
|
-
"text": "```typescript\n# check if you can run code\nimport {hello} from \"@wtasnorg/node-lib\";\n\nawait hello(); \n// \"hello from @wtasnorg/node-lib\"\n```"
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
"kind": "text",
|
|
238
|
-
"text": "\n\n## License: MIT\n\n[MIT License file]("
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
"kind": "relative-link",
|
|
242
|
-
"text": "LICENSE",
|
|
243
|
-
"target": 2
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
"kind": "text",
|
|
247
|
-
"text": ")"
|
|
248
|
-
}
|
|
249
|
-
],
|
|
250
|
-
"symbolIdMap": {
|
|
251
|
-
"0": {
|
|
252
|
-
"packageName": "@wtasnorg/node-lib",
|
|
253
|
-
"packagePath": "src/index.ts",
|
|
254
|
-
"qualifiedName": ""
|
|
255
|
-
},
|
|
256
|
-
"1": {
|
|
257
|
-
"packageName": "@wtasnorg/node-lib",
|
|
258
|
-
"packagePath": "src/hello.ts",
|
|
259
|
-
"qualifiedName": "hello"
|
|
260
|
-
},
|
|
261
|
-
"2": {
|
|
262
|
-
"packageName": "@wtasnorg/node-lib",
|
|
263
|
-
"packagePath": "src/hello.ts",
|
|
264
|
-
"qualifiedName": "hello"
|
|
265
|
-
},
|
|
266
|
-
"3": {
|
|
267
|
-
"packageName": "@wtasnorg/node-lib",
|
|
268
|
-
"packagePath": "src/pojo.ts",
|
|
269
|
-
"qualifiedName": "pojo"
|
|
270
|
-
},
|
|
271
|
-
"4": {
|
|
272
|
-
"packageName": "@wtasnorg/node-lib",
|
|
273
|
-
"packagePath": "src/pojo.ts",
|
|
274
|
-
"qualifiedName": "pojo"
|
|
275
|
-
},
|
|
276
|
-
"5": {
|
|
277
|
-
"packageName": "@wtasnorg/node-lib",
|
|
278
|
-
"packagePath": "src/pojo.ts",
|
|
279
|
-
"qualifiedName": "T"
|
|
280
|
-
},
|
|
281
|
-
"6": {
|
|
282
|
-
"packageName": "@wtasnorg/node-lib",
|
|
283
|
-
"packagePath": "src/pojo.ts",
|
|
284
|
-
"qualifiedName": "instance"
|
|
285
|
-
}
|
|
286
|
-
},
|
|
287
|
-
"files": {
|
|
288
|
-
"entries": {
|
|
289
|
-
"1": "src/index.ts",
|
|
290
|
-
"2": "LICENSE",
|
|
291
|
-
"3": "README.md",
|
|
292
|
-
"4": ""
|
|
293
|
-
},
|
|
294
|
-
"reflections": {
|
|
295
|
-
"1": 0,
|
|
296
|
-
"3": 0,
|
|
297
|
-
"4": 0
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|