@impactor/nodejs 3.0.1
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/LICENSE +21 -0
- package/README.md +129 -0
- package/index.d.ts +7 -0
- package/index.js +11 -0
- package/nx.json +190 -0
- package/package.json +72 -0
- package/src/cache-fs.d.ts +8 -0
- package/src/cache-fs.js +32 -0
- package/src/cache-fs.spec.js +38 -0
- package/src/fs-sync.d.ts +41 -0
- package/src/fs-sync.js +212 -0
- package/src/fs-sync.spec.js +262 -0
- package/src/fs.d.ts +16 -0
- package/src/fs.js +188 -0
- package/src/fs.spec.js +251 -0
- package/src/https.d.ts +3 -0
- package/src/https.js +52 -0
- package/src/https.spec.js +19 -0
- package/src/process.d.ts +13 -0
- package/src/process.js +203 -0
- package/src/process.spec.js +49 -0
package/src/fs-sync.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolve as _resolve,
|
|
3
|
+
basename,
|
|
4
|
+
dirname,
|
|
5
|
+
extname,
|
|
6
|
+
join,
|
|
7
|
+
normalize,
|
|
8
|
+
sep
|
|
9
|
+
} from "node:path";
|
|
10
|
+
import { objectType } from "@impactor/javascript";
|
|
11
|
+
import {
|
|
12
|
+
existsSync,
|
|
13
|
+
lstatSync,
|
|
14
|
+
renameSync,
|
|
15
|
+
readdirSync,
|
|
16
|
+
unlinkSync,
|
|
17
|
+
rmdirSync,
|
|
18
|
+
writeFileSync,
|
|
19
|
+
mkdirSync as _mkdirSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
copyFileSync
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { cleanJson } from "@impactor/javascript";
|
|
24
|
+
function resolve(...paths) {
|
|
25
|
+
let stringPaths = paths.filter(Boolean).map((path) => path instanceof URL ? path.pathname : path.toString());
|
|
26
|
+
return _resolve(normalize(join(...stringPaths)));
|
|
27
|
+
}
|
|
28
|
+
function parsePath(path) {
|
|
29
|
+
path = path.toString();
|
|
30
|
+
let extension = getExtensionSync(path);
|
|
31
|
+
return {
|
|
32
|
+
type: isDirSync(path) ? "dir" : "file",
|
|
33
|
+
// path of the containing dir
|
|
34
|
+
dir: dirname(path),
|
|
35
|
+
// basename (file or folder name) without extension
|
|
36
|
+
name: basename(path, `.${extension}`),
|
|
37
|
+
extension
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function getExtensionSync(file) {
|
|
41
|
+
return extname(file.toString()).toLowerCase().replace(/^\./, "");
|
|
42
|
+
}
|
|
43
|
+
function getSizeSync(path, unit = "b", filter) {
|
|
44
|
+
let units = { b: 0, kb: 1, mb: 2, gb: 3 };
|
|
45
|
+
let sizes = recursiveSync(
|
|
46
|
+
path,
|
|
47
|
+
(_path, type) => type === "file" ? lstatSync(_path).size / 1024 ** units[unit] : void 0,
|
|
48
|
+
filter
|
|
49
|
+
);
|
|
50
|
+
let sum = (sizes2) => {
|
|
51
|
+
let total = 0;
|
|
52
|
+
for (let size of sizes2) {
|
|
53
|
+
total += Array.isArray(size) ? sum(size) : size;
|
|
54
|
+
}
|
|
55
|
+
return total;
|
|
56
|
+
};
|
|
57
|
+
return Array.isArray(sizes) ? sum(sizes) : sizes;
|
|
58
|
+
}
|
|
59
|
+
function isDirSync(path) {
|
|
60
|
+
path = resolve(path);
|
|
61
|
+
return existsSync(path) && lstatSync(path).isDirectory();
|
|
62
|
+
}
|
|
63
|
+
function getModifiedTimeSync(path) {
|
|
64
|
+
return lstatSync(resolve(path)).mtimeMs;
|
|
65
|
+
}
|
|
66
|
+
function mkdirSync(path, mode = 511) {
|
|
67
|
+
if (Array.isArray(path)) {
|
|
68
|
+
return path.forEach((p) => {
|
|
69
|
+
mkdirSync(p, mode);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
let options = { mode, recursive: true };
|
|
73
|
+
if (!existsSync(path)) {
|
|
74
|
+
_mkdirSync(path, options);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
var MoveOptionsExisting = /* @__PURE__ */ ((MoveOptionsExisting2) => {
|
|
78
|
+
MoveOptionsExisting2[MoveOptionsExisting2["replace"] = 0] = "replace";
|
|
79
|
+
MoveOptionsExisting2[MoveOptionsExisting2["rename"] = 1] = "rename";
|
|
80
|
+
MoveOptionsExisting2[MoveOptionsExisting2["skip"] = 2] = "skip";
|
|
81
|
+
MoveOptionsExisting2[MoveOptionsExisting2["stop"] = 3] = "stop";
|
|
82
|
+
MoveOptionsExisting2[MoveOptionsExisting2["throw"] = 4] = "throw";
|
|
83
|
+
return MoveOptionsExisting2;
|
|
84
|
+
})(MoveOptionsExisting || {});
|
|
85
|
+
function moveSync(path, newPath, _options) {
|
|
86
|
+
return renameSync(path, newPath);
|
|
87
|
+
}
|
|
88
|
+
function removeSync(path, filter, keepDir = false) {
|
|
89
|
+
return recursiveSync(
|
|
90
|
+
path,
|
|
91
|
+
(file, type) => type === "file" ? unlinkSync(file) : keepDir ? void 0 : rmdirSync(file),
|
|
92
|
+
filter
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function copySync(source, destination, filter = () => true) {
|
|
96
|
+
source = resolve(source);
|
|
97
|
+
destination = resolve(destination);
|
|
98
|
+
return recursiveSync(source, (path, type) => {
|
|
99
|
+
if (type === "file" && filter(path)) {
|
|
100
|
+
let destination_ = path.replace(source.toString(), destination);
|
|
101
|
+
mkdirSync(dirname(destination_));
|
|
102
|
+
copyFileSync(path, destination_);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function writeSync(path, data, options) {
|
|
107
|
+
path = resolve(path);
|
|
108
|
+
mkdirSync(dirname(path));
|
|
109
|
+
let dataString = ["array", "object"].includes(objectType(data)) ? JSON.stringify(data, null, 2) : data;
|
|
110
|
+
return writeFileSync(path, dataString, options);
|
|
111
|
+
}
|
|
112
|
+
function readSync(path, options) {
|
|
113
|
+
path = resolve(path);
|
|
114
|
+
let opts = {
|
|
115
|
+
encoding: null,
|
|
116
|
+
flag: "r",
|
|
117
|
+
maxAge: 0,
|
|
118
|
+
...typeof options === "string" ? { encoding: options } : options
|
|
119
|
+
};
|
|
120
|
+
if (opts.maxAge && opts.maxAge > 0 && getModifiedTimeSync(path) + opts.maxAge * 1e3 < Date.now()) {
|
|
121
|
+
throw new Error(`[fs-sync] expired file ${path}`);
|
|
122
|
+
}
|
|
123
|
+
let data = readFileSync(path, {
|
|
124
|
+
encoding: opts.encoding,
|
|
125
|
+
flag: opts.flag
|
|
126
|
+
});
|
|
127
|
+
if (opts.encoding === void 0) {
|
|
128
|
+
return data;
|
|
129
|
+
}
|
|
130
|
+
data = data.toString();
|
|
131
|
+
return path.toString().trim().slice(-5) === ".json" ? JSON.parse(cleanJson(data)) : data;
|
|
132
|
+
}
|
|
133
|
+
function stripComments(content) {
|
|
134
|
+
return content.replaceAll(/(\/\/|#).*|\/\*(.|\n)*\*\//g, "");
|
|
135
|
+
}
|
|
136
|
+
function getEntriesSync(dir = ".", filter, depth, skip) {
|
|
137
|
+
if (Array.isArray(dir)) {
|
|
138
|
+
return dir.flatMap((el) => getEntriesSync(el, filter, depth));
|
|
139
|
+
}
|
|
140
|
+
dir = resolve(dir);
|
|
141
|
+
let filterFunction = filter === "files" ? (entry) => lstatSync(entry).isFile() : filter === "dirs" ? (entry) => lstatSync(entry).isDirectory() : filter instanceof RegExp ? (entry) => filter.test(entry) : typeof filter === "function" ? filter : void 0;
|
|
142
|
+
let skipFunction = skip instanceof RegExp ? (entry) => skip.test(entry) : typeof skip === "function" ? skip : (entry) => ["node_modules", "dist"].includes(entry);
|
|
143
|
+
let entries = readdirSync(dir);
|
|
144
|
+
let result = [];
|
|
145
|
+
for (let entry of entries) {
|
|
146
|
+
let fullPath = join(dir, entry);
|
|
147
|
+
if (!filterFunction || filterFunction?.(fullPath)) {
|
|
148
|
+
result.push(fullPath);
|
|
149
|
+
}
|
|
150
|
+
if ((depth === void 0 || depth > 0) && lstatSync(fullPath).isDirectory() && !skipFunction?.(entry)) {
|
|
151
|
+
let subEntries = getEntriesSync(
|
|
152
|
+
fullPath,
|
|
153
|
+
filterFunction,
|
|
154
|
+
depth === void 0 ? void 0 : depth - 1
|
|
155
|
+
);
|
|
156
|
+
result = [...result, ...subEntries];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
function recursiveSync(path, apply, filter = () => true) {
|
|
162
|
+
if (!path) {
|
|
163
|
+
throw new Error("path not provided");
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(path)) {
|
|
166
|
+
return path.map((p) => recursiveSync(p, apply, filter));
|
|
167
|
+
}
|
|
168
|
+
path = resolve(path.toString());
|
|
169
|
+
if (!existsSync(path)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
let result = [];
|
|
173
|
+
if (isDirSync(path)) {
|
|
174
|
+
if (filter(path, "dir")) {
|
|
175
|
+
readdirSync(path).forEach((file) => {
|
|
176
|
+
result.push(recursiveSync(`${path}/${file}`, apply, filter));
|
|
177
|
+
});
|
|
178
|
+
apply(path, "dir");
|
|
179
|
+
}
|
|
180
|
+
} else if (filter(path, "file")) {
|
|
181
|
+
return apply(path, "file");
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
function toPosix(path, removeDriverLetter = true) {
|
|
186
|
+
let newPath = path.toString().replaceAll("\\", "/");
|
|
187
|
+
return removeDriverLetter ? newPath.replace(/^[A-Za-z]:/, "") : newPath;
|
|
188
|
+
}
|
|
189
|
+
function validFilePath(path) {
|
|
190
|
+
let pathResolved = resolve(path), driverLetter = pathResolved.match(/^[A-Za-z]:/)?.[0] || "", filePath = pathResolved.split(/^[A-Za-z]:/)?.[1] || pathResolved;
|
|
191
|
+
return driverLetter + filePath.split(sep).map((part) => encodeURIComponent(part.trim())).join(sep);
|
|
192
|
+
}
|
|
193
|
+
export {
|
|
194
|
+
MoveOptionsExisting,
|
|
195
|
+
copySync,
|
|
196
|
+
getEntriesSync,
|
|
197
|
+
getExtensionSync,
|
|
198
|
+
getModifiedTimeSync,
|
|
199
|
+
getSizeSync,
|
|
200
|
+
isDirSync,
|
|
201
|
+
mkdirSync,
|
|
202
|
+
moveSync,
|
|
203
|
+
parsePath,
|
|
204
|
+
readSync,
|
|
205
|
+
recursiveSync,
|
|
206
|
+
removeSync,
|
|
207
|
+
resolve,
|
|
208
|
+
stripComments,
|
|
209
|
+
toPosix,
|
|
210
|
+
validFilePath,
|
|
211
|
+
writeSync
|
|
212
|
+
};
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
copySync,
|
|
4
|
+
getEntriesSync,
|
|
5
|
+
getExtensionSync,
|
|
6
|
+
getModifiedTimeSync,
|
|
7
|
+
getSizeSync,
|
|
8
|
+
isDirSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
moveSync,
|
|
11
|
+
parsePath,
|
|
12
|
+
readSync,
|
|
13
|
+
removeSync,
|
|
14
|
+
resolve,
|
|
15
|
+
writeSync
|
|
16
|
+
} from "./fs-sync";
|
|
17
|
+
import { objectType } from "@impactor/javascript";
|
|
18
|
+
import { existsSync, readFileSync, utimesSync } from "node:fs";
|
|
19
|
+
import { platform } from "node:process";
|
|
20
|
+
let dir = resolve(import.meta.dirname, "./test!!/fs-sync"), file = dir + "/file.txt";
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
removeSync(dir);
|
|
23
|
+
writeSync(file, "ok");
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => removeSync(dir));
|
|
26
|
+
test("mkdir", () => {
|
|
27
|
+
expect(existsSync(`${dir}/mkdir`)).toBeFalsy();
|
|
28
|
+
mkdirSync(`${dir}/mkdir`);
|
|
29
|
+
expect(existsSync(`${dir}/mkdir`)).toBeTruthy();
|
|
30
|
+
});
|
|
31
|
+
test("write", () => {
|
|
32
|
+
mkdirSync(dir);
|
|
33
|
+
expect(existsSync(`${dir}/write.txt`)).toBeFalsy();
|
|
34
|
+
writeSync(`${dir}/write.txt`, "ok");
|
|
35
|
+
expect(existsSync(`${dir}/write.txt`)).toBeTruthy();
|
|
36
|
+
expect(readFileSync(`${dir}/write.txt`).toString()).toEqual("ok");
|
|
37
|
+
});
|
|
38
|
+
test("write in non-existing dir", () => {
|
|
39
|
+
let file2 = dir + "/non-existing/file.txt";
|
|
40
|
+
expect(existsSync(file2)).toBeFalsy();
|
|
41
|
+
writeSync(file2, "ok");
|
|
42
|
+
expect(existsSync(file2)).toBeTruthy();
|
|
43
|
+
expect(readFileSync(file2).toString()).toEqual("ok");
|
|
44
|
+
});
|
|
45
|
+
test("resolve", () => {
|
|
46
|
+
try {
|
|
47
|
+
expect(resolve("/path", "to/file.js")).toEqual("/path/to/file.js");
|
|
48
|
+
} catch {
|
|
49
|
+
expect(resolve("/path", "to/file.js")).toContain(
|
|
50
|
+
String.raw`path\to\file.js`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
test("parsePath", () => {
|
|
55
|
+
expect(parsePath("/path/to/file.js")).toEqual({
|
|
56
|
+
type: "file",
|
|
57
|
+
dir: "/path/to",
|
|
58
|
+
name: "file",
|
|
59
|
+
extension: "js"
|
|
60
|
+
});
|
|
61
|
+
if (platform === "win32") {
|
|
62
|
+
expect(parsePath(String.raw`\path\to\file.js`)).toEqual({
|
|
63
|
+
type: "file",
|
|
64
|
+
dir: String.raw`\path\to`,
|
|
65
|
+
name: "file",
|
|
66
|
+
extension: "js"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
test("getExtension", () => {
|
|
71
|
+
expect(getExtensionSync("/path/to/file.js")).toEqual("js");
|
|
72
|
+
expect(getExtensionSync(".gitignore")).toEqual("");
|
|
73
|
+
expect(getExtensionSync("/path/to/.gitignore")).toEqual("");
|
|
74
|
+
expect(getExtensionSync("/path/to")).toEqual("");
|
|
75
|
+
});
|
|
76
|
+
test("size units", () => {
|
|
77
|
+
let size = 1234567890, units = { b: 0, kb: 1, mb: 2, gb: 3 };
|
|
78
|
+
expect(size / 1024 ** units.mb).toBeCloseTo(1177.3, 0);
|
|
79
|
+
});
|
|
80
|
+
test("getSize", () => {
|
|
81
|
+
writeSync(`${dir}/get-size/file1.txt`, "ok");
|
|
82
|
+
writeSync(`${dir}/get-size/file2.txt`, "ok");
|
|
83
|
+
expect(getSizeSync(`${dir}/get-size/file1.txt`)).toEqual(2);
|
|
84
|
+
expect(getSizeSync(`${dir}/get-size`)).toEqual(4);
|
|
85
|
+
expect(
|
|
86
|
+
getSizeSync([`${dir}/get-size/file1.txt`, `${dir}/get-size/file2.txt`])
|
|
87
|
+
).toEqual(4);
|
|
88
|
+
});
|
|
89
|
+
test("isDir", () => {
|
|
90
|
+
expect(isDirSync(file)).toEqual(false);
|
|
91
|
+
expect(isDirSync(dir)).toEqual(true);
|
|
92
|
+
});
|
|
93
|
+
test("getModifiedTime", () => {
|
|
94
|
+
expect(Math.floor(getModifiedTimeSync(file))).toBeGreaterThanOrEqual(
|
|
95
|
+
1624906832178
|
|
96
|
+
);
|
|
97
|
+
expect(Math.floor(getModifiedTimeSync(dir))).toBeGreaterThanOrEqual(
|
|
98
|
+
1624906832178
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
test("move", () => {
|
|
102
|
+
let file2 = dir + "/file2.txt";
|
|
103
|
+
expect(existsSync(file)).toBeTruthy();
|
|
104
|
+
expect(existsSync(file2)).toBeFalsy();
|
|
105
|
+
moveSync(file, file2);
|
|
106
|
+
expect(existsSync(file)).toBeFalsy();
|
|
107
|
+
expect(existsSync(file2)).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
test("read", () => {
|
|
110
|
+
let fileJson = dir + "/file.json", fileArray = dir + "/array.json", fileJsonComments = dir + "/comments.json";
|
|
111
|
+
writeSync(fileJson, { x: 1, y: 2 });
|
|
112
|
+
writeSync(fileArray, [1, 2, 3]);
|
|
113
|
+
writeSync(
|
|
114
|
+
fileJsonComments,
|
|
115
|
+
`// this file is created to test reading .json files that contains comments
|
|
116
|
+
// to test stripComments()
|
|
117
|
+
|
|
118
|
+
{
|
|
119
|
+
/* it should remove all comments */
|
|
120
|
+
/* even this
|
|
121
|
+
multi-line comment
|
|
122
|
+
*/
|
|
123
|
+
// also this comment
|
|
124
|
+
|
|
125
|
+
"x": 1,
|
|
126
|
+
"hello": "ok"
|
|
127
|
+
}
|
|
128
|
+
`
|
|
129
|
+
);
|
|
130
|
+
let txt = readSync(file), json = readSync(fileJson), jsonWithComments = readSync(fileJsonComments), array = readSync(fileArray);
|
|
131
|
+
expect(txt.length).toEqual(2);
|
|
132
|
+
expect(txt).toContain("ok");
|
|
133
|
+
expect(objectType(txt)).toEqual("string");
|
|
134
|
+
expect(objectType(json)).toEqual("object");
|
|
135
|
+
expect(objectType(jsonWithComments)).toEqual("object");
|
|
136
|
+
expect(objectType(array)).toEqual("array");
|
|
137
|
+
expect(json).toEqual({ x: 1, y: 2 });
|
|
138
|
+
expect(jsonWithComments).toEqual({ x: 1, hello: "ok" });
|
|
139
|
+
expect(array).toEqual([1, 2, 3]);
|
|
140
|
+
});
|
|
141
|
+
test("read from a non-existing file", () => {
|
|
142
|
+
expect(
|
|
143
|
+
() => readSync(`${dir}/non-existing.txt`, { maxAge: 24 * 60 * 60 })
|
|
144
|
+
).toThrow("no such file or directory");
|
|
145
|
+
});
|
|
146
|
+
test("read: maxAge", () => {
|
|
147
|
+
let file2 = dir + "/file.txt";
|
|
148
|
+
writeSync(file2, "ok");
|
|
149
|
+
expect(readSync(file2, { maxAge: 24 * 60 * 60 })).toEqual("ok");
|
|
150
|
+
});
|
|
151
|
+
test("read from an expired cache", () => {
|
|
152
|
+
let file2 = dir + "/file.txt";
|
|
153
|
+
writeSync(file2, "ok");
|
|
154
|
+
let date = /* @__PURE__ */ new Date(), today = date.getDate();
|
|
155
|
+
date.setDate(today - 1);
|
|
156
|
+
let yesterday = date.getTime() / 1e3;
|
|
157
|
+
utimesSync(file2, yesterday, yesterday);
|
|
158
|
+
expect(() => readSync(file2, { maxAge: 1 })).toThrow("expired file");
|
|
159
|
+
});
|
|
160
|
+
test("remove dir", () => {
|
|
161
|
+
expect(existsSync(file)).toBeTruthy();
|
|
162
|
+
expect(existsSync(dir)).toBeTruthy();
|
|
163
|
+
removeSync(dir);
|
|
164
|
+
expect(existsSync(file)).toBeFalsy();
|
|
165
|
+
expect(existsSync(dir)).toBeFalsy();
|
|
166
|
+
});
|
|
167
|
+
test("remove non-exists path", () => {
|
|
168
|
+
let file2 = `${dir}/non-existing/file.txt`;
|
|
169
|
+
removeSync(file2);
|
|
170
|
+
expect(existsSync(file2)).toBeFalsy();
|
|
171
|
+
});
|
|
172
|
+
test("copy a directory and its sub-directories", () => {
|
|
173
|
+
writeSync(`${dir}/copy-dir/file.txt`, "");
|
|
174
|
+
writeSync(`${dir}/copy-dir/sub-dir/file2.txt`, "");
|
|
175
|
+
copySync(`${dir}/copy-dir`, `${dir}/copy-dir2`);
|
|
176
|
+
expect(existsSync(`${dir}/copy-dir2/file.txt`)).toBeTruthy();
|
|
177
|
+
expect(existsSync(`${dir}/copy-dir2/sub-dir/file2.txt`)).toBeTruthy();
|
|
178
|
+
});
|
|
179
|
+
describe("getEntries", () => {
|
|
180
|
+
let entries = ["file.txt", "file.js"];
|
|
181
|
+
beforeEach(() => {
|
|
182
|
+
for (let element of entries) {
|
|
183
|
+
writeSync(`${dir}/${element}`, "");
|
|
184
|
+
writeSync(`${dir}/subdir/${element}`, "");
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
test("list all entries recursively", () => {
|
|
188
|
+
expect(getEntriesSync(dir).sort()).toEqual(
|
|
189
|
+
// all files in dir with full path
|
|
190
|
+
[
|
|
191
|
+
...entries.map((element) => resolve(`${dir}/${element}`)).concat(
|
|
192
|
+
entries.map((element) => resolve(`${dir}/subdir/${element}`))
|
|
193
|
+
),
|
|
194
|
+
resolve(`${dir}/subdir`)
|
|
195
|
+
].sort()
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
test("filter by function", () => {
|
|
199
|
+
expect(getEntriesSync(dir, (element) => element.includes(".js"))).toEqual([
|
|
200
|
+
resolve(`${dir}/file.js`),
|
|
201
|
+
resolve(`${dir}/subdir/file.js`)
|
|
202
|
+
]);
|
|
203
|
+
});
|
|
204
|
+
test("filter by regex", () => {
|
|
205
|
+
expect(getEntriesSync(dir, /subdir/).sort()).toEqual(
|
|
206
|
+
[
|
|
207
|
+
...entries.map((element) => resolve(`${dir}/subdir/${element}`)),
|
|
208
|
+
resolve(`${dir}/subdir`)
|
|
209
|
+
].sort()
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
test("filter by type: files", () => {
|
|
213
|
+
expect(getEntriesSync(dir, "files").sort()).toEqual(
|
|
214
|
+
entries.map((element) => resolve(`${dir}/${element}`)).concat(entries.map((element) => resolve(`${dir}/subdir/${element}`))).sort()
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
test("filter by type: dirs", () => {
|
|
218
|
+
expect(getEntriesSync(dir, "dirs")).toEqual([resolve(`${dir}/subdir`)]);
|
|
219
|
+
});
|
|
220
|
+
test("depth=0", () => {
|
|
221
|
+
for (let element of entries) {
|
|
222
|
+
writeSync(`${dir}/subdir/extra/${element}`, "");
|
|
223
|
+
}
|
|
224
|
+
expect(getEntriesSync(dir, void 0, 0).sort()).toEqual(
|
|
225
|
+
[
|
|
226
|
+
...entries.map((element) => resolve(`${dir}/${element}`)),
|
|
227
|
+
resolve(`${dir}/subdir`)
|
|
228
|
+
].sort()
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
test("depth=1", () => {
|
|
232
|
+
for (let element of entries) {
|
|
233
|
+
writeSync(`${dir}/subdir/extra/${element}`, "");
|
|
234
|
+
}
|
|
235
|
+
expect(getEntriesSync(dir, void 0, 1).sort()).toEqual(
|
|
236
|
+
[
|
|
237
|
+
...entries.map((element) => resolve(`${dir}/${element}`)),
|
|
238
|
+
resolve(`${dir}/subdir`),
|
|
239
|
+
resolve(`${dir}/subdir/extra`)
|
|
240
|
+
].concat(entries.map((element) => resolve(`${dir}/subdir/${element}`))).sort()
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
test("depth=2", () => {
|
|
244
|
+
for (let element of entries) {
|
|
245
|
+
writeSync(`${dir}/subdir/extra/${element}`, "");
|
|
246
|
+
}
|
|
247
|
+
expect(getEntriesSync(dir, void 0, 2).sort()).toEqual(
|
|
248
|
+
[
|
|
249
|
+
...entries.map((element) => resolve(`${dir}/${element}`)),
|
|
250
|
+
resolve(`${dir}/subdir`),
|
|
251
|
+
resolve(`${dir}/subdir/extra`)
|
|
252
|
+
].concat(entries.map((element) => resolve(`${dir}/subdir/${element}`))).concat(
|
|
253
|
+
entries.map((element) => resolve(`${dir}/subdir/extra/${element}`))
|
|
254
|
+
).sort()
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
test("non existing dir", () => {
|
|
258
|
+
expect(() => getEntriesSync(dir + "/non-existing")).toThrow(
|
|
259
|
+
"no such file or directory"
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
});
|
package/src/fs.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type PathLike, type WriteFileOptions } from 'node:fs';
|
|
2
|
+
import { type Abortable } from 'node:events';
|
|
3
|
+
import { type Filter, type MoveOptions, type ReadOptions } from './fs-sync';
|
|
4
|
+
import { type Obj } from '@impactor/javascript';
|
|
5
|
+
export declare function getSize(path: PathLike | PathLike[], unit?: 'b' | 'kb' | 'mb' | 'gb', filter?: Filter): Promise<number>;
|
|
6
|
+
export declare function isDir(path: PathLike): Promise<boolean>;
|
|
7
|
+
export declare function getModifiedTime(file: PathLike): Promise<number>;
|
|
8
|
+
export declare function mkdir(path: PathLike | PathLike[], mode?: number | string): Promise<void>;
|
|
9
|
+
export declare function move(path: PathLike, newPath: PathLike, _options?: MoveOptions): Promise<void>;
|
|
10
|
+
export declare function remove(path: PathLike | PathLike[], filter?: Filter, keepDir?: boolean): Promise<void>;
|
|
11
|
+
export declare function copy(source: PathLike, destination: string, filter?: Filter): Promise<any>;
|
|
12
|
+
export declare function write(path: PathLike, data: any, options?: WriteFileOptions & Abortable): Promise<void>;
|
|
13
|
+
export declare function read<T extends Buffer | string | Array<any> | Obj>(path: PathLike | URL, options?: ReadOptions | BufferEncoding): Promise<T>;
|
|
14
|
+
export declare function getEntries(dir?: string | string[], filter?: ((entry: string) => boolean) | RegExp | 'files' | 'dirs', depth?: number, skip?: ((entry: string) => boolean) | RegExp): Promise<Array<string>>;
|
|
15
|
+
export declare function recursive(path: PathLike | PathLike[], apply: (path: string, type: 'dir' | 'file') => any, filter?: Filter): Promise<any | any[]>;
|
|
16
|
+
export declare function exists(path: PathLike): Promise<boolean>;
|
package/src/fs.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
constants,
|
|
3
|
+
lstatSync,
|
|
4
|
+
readdirSync
|
|
5
|
+
} from "node:fs";
|
|
6
|
+
import {
|
|
7
|
+
mkdir as _mkdir,
|
|
8
|
+
access,
|
|
9
|
+
copyFile,
|
|
10
|
+
lstat,
|
|
11
|
+
readFile,
|
|
12
|
+
readdir,
|
|
13
|
+
rename,
|
|
14
|
+
rmdir,
|
|
15
|
+
unlink,
|
|
16
|
+
writeFile
|
|
17
|
+
} from "node:fs/promises";
|
|
18
|
+
import {
|
|
19
|
+
resolve
|
|
20
|
+
} from "./fs-sync";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
import { objectType } from "@impactor/javascript";
|
|
23
|
+
import { cleanJson } from "@impactor/javascript";
|
|
24
|
+
function getSize(path, unit = "b", filter) {
|
|
25
|
+
let units = { b: 0, kb: 1, mb: 2, gb: 3 };
|
|
26
|
+
return recursive(
|
|
27
|
+
path,
|
|
28
|
+
(_path, type) => type === "file" ? lstat(_path).then((stats) => stats.size / 1024 ** units[unit]) : (
|
|
29
|
+
// todo: for dirs, size = total sizes of its contents
|
|
30
|
+
void 0
|
|
31
|
+
),
|
|
32
|
+
filter
|
|
33
|
+
).then((sizes) => {
|
|
34
|
+
let sum = (sizes2) => {
|
|
35
|
+
let total = 0;
|
|
36
|
+
for (let size of sizes2.filter(Boolean)) {
|
|
37
|
+
total += Array.isArray(size) ? sum(size) : size;
|
|
38
|
+
}
|
|
39
|
+
return total;
|
|
40
|
+
};
|
|
41
|
+
return Array.isArray(sizes) ? sum(sizes) : sizes;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function isDir(path) {
|
|
45
|
+
return lstat(path).then((stats) => stats.isDirectory());
|
|
46
|
+
}
|
|
47
|
+
function getModifiedTime(file) {
|
|
48
|
+
return lstat(file).then((stats) => stats.mtimeMs);
|
|
49
|
+
}
|
|
50
|
+
function mkdir(path, mode = 511) {
|
|
51
|
+
if (Array.isArray(path)) {
|
|
52
|
+
return Promise.all(
|
|
53
|
+
path.map((p) => ({ [p.toString()]: mkdir(p, mode) }))
|
|
54
|
+
).then(() => {
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
let options = { mode, recursive: true };
|
|
58
|
+
return access(path, constants.R_OK).catch(() => _mkdir(path, options)).then(() => {
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async function move(path, newPath, _options) {
|
|
62
|
+
await mkdir(dirname(newPath.toString()));
|
|
63
|
+
return rename(path, newPath);
|
|
64
|
+
}
|
|
65
|
+
function remove(path, filter, keepDir = false) {
|
|
66
|
+
return recursive(
|
|
67
|
+
path,
|
|
68
|
+
(file, type) => type === "file" ? unlink(file) : keepDir ? void 0 : rmdir(file),
|
|
69
|
+
filter
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
function copy(source, destination, filter = () => true) {
|
|
73
|
+
source = resolve(source);
|
|
74
|
+
destination = resolve(destination);
|
|
75
|
+
return recursive(
|
|
76
|
+
source,
|
|
77
|
+
(path, type) => {
|
|
78
|
+
path = path.toString();
|
|
79
|
+
if (type === "file" && filter(path)) {
|
|
80
|
+
let destination_ = path.replace(source.toString(), destination);
|
|
81
|
+
return mkdir(dirname(destination_)).then(
|
|
82
|
+
() => copyFile(path, destination_)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
},
|
|
87
|
+
filter
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
function write(path, data, options) {
|
|
91
|
+
path = resolve(path);
|
|
92
|
+
return mkdir(dirname(path)).then(
|
|
93
|
+
() => ["array", "object"].includes(objectType(data)) ? JSON.stringify(data, null, 2) : data
|
|
94
|
+
).then((dataString) => writeFile(path, dataString, options));
|
|
95
|
+
}
|
|
96
|
+
function read(path, options) {
|
|
97
|
+
path = resolve(path);
|
|
98
|
+
let opts = {
|
|
99
|
+
encoding: null,
|
|
100
|
+
flag: "r",
|
|
101
|
+
maxAge: 0,
|
|
102
|
+
...typeof options === "string" ? { encoding: options } : options
|
|
103
|
+
};
|
|
104
|
+
return getModifiedTime(path).then((modified) => {
|
|
105
|
+
if (opts.maxAge && opts.maxAge > 0 && modified + opts.maxAge < Date.now()) {
|
|
106
|
+
throw new Error(`[fs-sync] expired file ${path}`);
|
|
107
|
+
}
|
|
108
|
+
return readFile(path, {
|
|
109
|
+
encoding: opts.encoding,
|
|
110
|
+
flag: opts.flag
|
|
111
|
+
}).then((data) => {
|
|
112
|
+
if (opts.encoding === void 0) {
|
|
113
|
+
return data;
|
|
114
|
+
}
|
|
115
|
+
data = data.toString();
|
|
116
|
+
return path.toString().trim().slice(-5) === ".json" ? JSON.parse(cleanJson(data)) : data;
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function getEntries(dir = ".", filter, depth, skip) {
|
|
121
|
+
if (Array.isArray(dir)) {
|
|
122
|
+
return Promise.all(dir.map((el) => getEntries(el, filter, depth))).then(
|
|
123
|
+
// combine an array of arrays into a single array
|
|
124
|
+
(result2) => result2.flat()
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
dir = resolve(dir);
|
|
128
|
+
let filterFunction = filter === "files" ? (entry) => lstatSync(entry).isFile() : filter === "dirs" ? (entry) => lstatSync(entry).isDirectory() : filter instanceof RegExp ? (entry) => filter.test(entry) : typeof filter === "function" ? filter : void 0;
|
|
129
|
+
let skipFunction = skip instanceof RegExp ? (entry) => skip.test(entry) : typeof skip === "function" ? skip : (entry) => ["node_modules", "dist"].includes(entry);
|
|
130
|
+
let entries = readdirSync(dir);
|
|
131
|
+
let result = [];
|
|
132
|
+
for (let entry of entries) {
|
|
133
|
+
let path = join(dir, entry), _fullPath = resolve(dir, entry);
|
|
134
|
+
if (!filterFunction || filterFunction(path)) {
|
|
135
|
+
result.push(path);
|
|
136
|
+
}
|
|
137
|
+
if ((depth === void 0 || depth > 0) && lstatSync(path).isDirectory() && !skipFunction?.(entry)) {
|
|
138
|
+
let subEntries = await getEntries(
|
|
139
|
+
path,
|
|
140
|
+
filterFunction,
|
|
141
|
+
depth === void 0 ? void 0 : depth - 1
|
|
142
|
+
);
|
|
143
|
+
result = result.concat(subEntries);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
async function recursive(path, apply, filter) {
|
|
149
|
+
if (!path) {
|
|
150
|
+
throw "path not provided";
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(path)) {
|
|
153
|
+
return Promise.all(
|
|
154
|
+
// todo: path.map((p) => ({ [p]: recursive(p as string, apply) }))
|
|
155
|
+
path.map((p) => recursive(p, apply, filter))
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
let _path = resolve(path);
|
|
159
|
+
await access(_path, constants.R_OK);
|
|
160
|
+
let _isDir = await isDir(_path);
|
|
161
|
+
if (filter?.(_path, _isDir ? "dir" : "file") === false) return;
|
|
162
|
+
if (_isDir) {
|
|
163
|
+
apply(_path, "dir");
|
|
164
|
+
let dir = await readdir(_path);
|
|
165
|
+
for (let file of dir) {
|
|
166
|
+
await recursive(`${_path}/${file}`, apply, filter);
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
apply(_path, "file");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function exists(path) {
|
|
173
|
+
return access(resolve(path), constants.R_OK).then(() => true).catch(() => false);
|
|
174
|
+
}
|
|
175
|
+
export {
|
|
176
|
+
copy,
|
|
177
|
+
exists,
|
|
178
|
+
getEntries,
|
|
179
|
+
getModifiedTime,
|
|
180
|
+
getSize,
|
|
181
|
+
isDir,
|
|
182
|
+
mkdir,
|
|
183
|
+
move,
|
|
184
|
+
read,
|
|
185
|
+
recursive,
|
|
186
|
+
remove,
|
|
187
|
+
write
|
|
188
|
+
};
|