@wtasnorg/node-lib 0.0.5 → 0.0.7

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.
@@ -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/5ccb6028429af225c9ab29e1b6007075025c601f/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`[]\>
@@ -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/890fd5e866d9096470d56594a5788bf55e34bf7c/src/hello.ts#L6)
11
+ Defined in: [hello.ts:6](https://github.com/wtasg/node-lib/blob/5ccb6028429af225c9ab29e1b6007075025c601f/src/hello.ts#L6)
12
12
 
13
13
  A sample function that should work to test if lib is installed correctly.
14
14
 
@@ -6,9 +6,9 @@
6
6
 
7
7
  # Function: pojo()
8
8
 
9
- > **pojo**\<`T`\>(`instance`): `Record`\<`string`, `any`\>
9
+ > **pojo**\<`T`\>(`instance`): `Record`\<`string`, `unknown`\>
10
10
 
11
- Defined in: [pojo.ts:10](https://github.com/wtasg/node-lib/blob/890fd5e866d9096470d56594a5788bf55e34bf7c/src/pojo.ts#L10)
11
+ Defined in: [pojo.ts:10](https://github.com/wtasg/node-lib/blob/5ccb6028429af225c9ab29e1b6007075025c601f/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
@@ -30,6 +30,6 @@ A class instance to convert.
30
30
 
31
31
  ## Returns
32
32
 
33
- `Record`\<`string`, `any`\>
33
+ `Record`\<`string`, `unknown`\>
34
34
 
35
35
  A plain JavaScript object containing only data fields.
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/5ccb6028429af225c9ab29e1b6007075025c601f/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/5ccb6028429af225c9ab29e1b6007075025c601f/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/5ccb6028429af225c9ab29e1b6007075025c601f/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/5ccb6028429af225c9ab29e1b6007075025c601f/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/5ccb6028429af225c9ab29e1b6007075025c601f/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/5ccb6028429af225c9ab29e1b6007075025c601f/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/5ccb6028429af225c9ab29e1b6007075025c601f/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/5ccb6028429af225c9ab29e1b6007075025c601f/src/find.ts#L9)
@@ -0,0 +1,47 @@
1
+ // @ts-check
2
+
3
+ import eslint from "@eslint/js";
4
+ import { defineConfig } from "eslint/config";
5
+ import tseslint from "typescript-eslint";
6
+ import prettier from "eslint-config-prettier";
7
+ import globals from "globals";
8
+ import stylistic from "@stylistic/eslint-plugin";
9
+
10
+ export default defineConfig([
11
+ eslint.configs.recommended,
12
+ ...tseslint.configs.recommended,
13
+ ...tseslint.configs.strict,
14
+ ...tseslint.configs.stylistic,
15
+ prettier,
16
+
17
+ {
18
+ languageOptions: {
19
+ globals: {
20
+ ...globals.node,
21
+ }
22
+ }
23
+ },
24
+
25
+ {
26
+ plugins: {
27
+ "@stylistic": stylistic
28
+ }
29
+ },
30
+
31
+ {
32
+ rules: {
33
+ "@typescript-eslint/no-unused-vars": [
34
+ "warn",
35
+ {
36
+ argsIgnorePattern: "^_",
37
+ varsIgnorePattern: "^_",
38
+ caughtErrorsIgnorePattern: "^_"
39
+ }
40
+ ],
41
+
42
+ semi: ["error", "always"],
43
+ // "no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
44
+ "@stylistic/quotes": ["error", "double"]
45
+ }
46
+ }
47
+ ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtasnorg/node-lib",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "node library",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -27,9 +27,15 @@
27
27
  "url": "git+https://github.com/wtasg/node-lib.git"
28
28
  },
29
29
  "devDependencies": {
30
+ "@eslint/js": "^9.39.1",
31
+ "@stylistic/eslint-plugin": "^5.6.1",
30
32
  "@types/node": "^24.10.1",
31
- "typedoc": "^0.28.14",
33
+ "eslint": "^9.39.1",
34
+ "eslint-config-prettier": "^10.1.8",
35
+ "globals": "^16.5.0",
36
+ "typedoc": "^0.28.15",
32
37
  "typedoc-plugin-markdown": "^4.9.0",
33
- "typescript": "^5.9.3"
38
+ "typescript": "^5.9.3",
39
+ "typescript-eslint": "^8.48.1"
34
40
  }
35
41
  }
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
+ const 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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=find.test.d.ts.map
@@ -0,0 +1,123 @@
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 deps = {
62
+ readdir: async (path) => {
63
+ switch (path) {
64
+ case "/root":
65
+ return [dir("a"), dir("b"), file("ignore.txt")];
66
+ case "/root/a":
67
+ return [dir("a1")];
68
+ case "/root/b":
69
+ return [dir("b1")];
70
+ default:
71
+ return [];
72
+ }
73
+ },
74
+ stat: async () => ({
75
+ isDirectory: () => false
76
+ })
77
+ };
78
+ const find = createFindDirectories(deps);
79
+ const result = await find("/root", { maxDepth: 2 });
80
+ assert.deepEqual(result.sort(), [
81
+ "/root",
82
+ "/root/a",
83
+ "/root/b",
84
+ "/root/a/a1",
85
+ "/root/b/b1"
86
+ ].sort());
87
+ });
88
+ test("find: findDirectories at depth=3", async () => {
89
+ const deps = {
90
+ readdir: async (path) => {
91
+ switch (path) {
92
+ case "/root":
93
+ return [dir("a"), dir("b")];
94
+ case "/root/a":
95
+ return [dir("a1")];
96
+ case "/root/b":
97
+ return [dir("b1")];
98
+ case "/root/a/a1":
99
+ case "/root/b/b1":
100
+ return [dir("c")];
101
+ case "/root/b/b1/c":
102
+ return [dir("d"), file("fd")];
103
+ default:
104
+ return [];
105
+ }
106
+ },
107
+ stat: async () => ({
108
+ isDirectory: () => false
109
+ })
110
+ };
111
+ const find = createFindDirectories(deps);
112
+ const result = await find("/root", { maxDepth: 3 });
113
+ assert.deepEqual(result.sort(), [
114
+ "/root",
115
+ "/root/a",
116
+ "/root/b",
117
+ "/root/a/a1",
118
+ "/root/a/a1/c",
119
+ "/root/b/b1",
120
+ "/root/b/b1/c",
121
+ ].sort());
122
+ });
123
+ //# sourceMappingURL=find.test.js.map
@@ -0,0 +1,148 @@
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 deps: FileSystemDependencies = {
81
+ readdir: async (path: string): Promise<{ name: string; isDirectory(): boolean }[]> => {
82
+ switch (path) {
83
+ case "/root":
84
+ return [dir("a"), dir("b"), file("ignore.txt")];
85
+ case "/root/a":
86
+ return [dir("a1")];
87
+ case "/root/b":
88
+ return [dir("b1")];
89
+ default:
90
+ return [];
91
+ }
92
+ },
93
+ stat: async () => ({
94
+ isDirectory: () => false
95
+ })
96
+ };
97
+
98
+ const find = createFindDirectories(deps);
99
+
100
+ const result = await find("/root", { maxDepth: 2 });
101
+
102
+ assert.deepEqual(result.sort(), [
103
+ "/root",
104
+ "/root/a",
105
+ "/root/b",
106
+ "/root/a/a1",
107
+ "/root/b/b1"
108
+ ].sort());
109
+ });
110
+
111
+ test("find: findDirectories at depth=3", async () => {
112
+ const deps: FileSystemDependencies = {
113
+ readdir: async (path: string): Promise<{ name: string; isDirectory(): boolean }[]> => {
114
+ switch (path) {
115
+ case "/root":
116
+ return [dir("a"), dir("b")];
117
+ case "/root/a":
118
+ return [dir("a1")];
119
+ case "/root/b":
120
+ return [dir("b1")];
121
+ case "/root/a/a1":
122
+ case "/root/b/b1":
123
+ return [dir("c")];
124
+ case "/root/b/b1/c":
125
+ return [dir("d"), file("fd")];
126
+ default:
127
+ return [];
128
+ }
129
+ },
130
+ stat: async () => ({
131
+ isDirectory: () => false
132
+ })
133
+ };
134
+
135
+ const find = createFindDirectories(deps);
136
+
137
+ const result = await find("/root", { maxDepth: 3 });
138
+
139
+ assert.deepEqual(result.sort(), [
140
+ "/root",
141
+ "/root/a",
142
+ "/root/b",
143
+ "/root/a/a1",
144
+ "/root/a/a1/c",
145
+ "/root/b/b1",
146
+ "/root/b/b1/c",
147
+ ].sort());
148
+ });