@halooj/utils 1.5.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/lib/common.ts +237 -0
- package/lib/locate-pm2.ts +20 -0
- package/lib/search.ts +37 -0
- package/lib/sysinfo.ts +55 -0
- package/lib/utils.ts +369 -0
- package/package.json +30 -0
package/lib/common.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface String {
|
|
3
|
+
format: (...args: Array<any>) => string;
|
|
4
|
+
formatFromArray: (args: any[]) => string;
|
|
5
|
+
rawformat: (object: any) => string;
|
|
6
|
+
}
|
|
7
|
+
interface Math {
|
|
8
|
+
sum: (...args: Array<number[] | number>) => number;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const defaultDict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
|
13
|
+
|
|
14
|
+
export function randomstring(digit = 32, dict = defaultDict) {
|
|
15
|
+
let str = '';
|
|
16
|
+
for (let i = 1; i <= digit; i++) str += dict[Math.floor(Math.random() * dict.length)];
|
|
17
|
+
return str;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
String.prototype.format ||= function formatStr(...args) {
|
|
21
|
+
let result = this;
|
|
22
|
+
if (args.length) {
|
|
23
|
+
if (args.length === 1 && typeof args[0] === 'object') {
|
|
24
|
+
const t = args[0];
|
|
25
|
+
for (const key in t) {
|
|
26
|
+
if (!key.startsWith('_') && t[key] !== undefined) {
|
|
27
|
+
const reg = new RegExp(`(\\{${key}\\})`, 'g');
|
|
28
|
+
result = result.replace(reg, t[key]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} else return this.formatFromArray(args);
|
|
32
|
+
}
|
|
33
|
+
return result as string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
String.prototype.formatFromArray = function formatStr(args) {
|
|
37
|
+
let result = this;
|
|
38
|
+
for (let i = 0; i < args.length; i++) {
|
|
39
|
+
if (args[i] !== undefined) {
|
|
40
|
+
const reg = new RegExp(`(\\{)${i}(\\})`, 'g');
|
|
41
|
+
result = result.replace(reg, args[i]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result as string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
String.prototype.rawformat = function rawFormat(object) {
|
|
48
|
+
const res = this.split('{@}');
|
|
49
|
+
return [res[0], object, res[1]].join();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function diffArray(a, b) {
|
|
53
|
+
if (a.length !== b.length) return true;
|
|
54
|
+
a.sort();
|
|
55
|
+
b.sort();
|
|
56
|
+
for (const i in a) {
|
|
57
|
+
if (a[i] !== b[i]) return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function formatDate(date: Date, fmt = '%Y-%m-%d %H:%M:%S') {
|
|
63
|
+
let m = (date.getMonth() + 1).toString();
|
|
64
|
+
if (m.length < 2) m = `0${m}`;
|
|
65
|
+
let d = date.getDate().toString();
|
|
66
|
+
if (d.length < 2) d = `0${d}`;
|
|
67
|
+
let H = date.getHours().toString();
|
|
68
|
+
if (H.length < 2) H = `0${H}`;
|
|
69
|
+
let M = date.getMinutes().toString();
|
|
70
|
+
if (M.length < 2) M = `0${M}`;
|
|
71
|
+
let S = date.getSeconds().toString();
|
|
72
|
+
if (S.length < 2) S = `0${S}`;
|
|
73
|
+
return fmt
|
|
74
|
+
.replace('%Y', date.getFullYear().toString())
|
|
75
|
+
.replace('%m', m)
|
|
76
|
+
.replace('%d', d)
|
|
77
|
+
.replace('%H', H)
|
|
78
|
+
.replace('%M', M)
|
|
79
|
+
.replace('%S', S);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Math.sum = function sum(...args) {
|
|
83
|
+
let s = 0;
|
|
84
|
+
for (const i of args) {
|
|
85
|
+
if (i instanceof Array) {
|
|
86
|
+
for (const j of i) {
|
|
87
|
+
s += j;
|
|
88
|
+
}
|
|
89
|
+
} else s += i;
|
|
90
|
+
}
|
|
91
|
+
return s;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// TODO: remove these
|
|
95
|
+
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
Set.isSuperset = function isSuperset(set, subset) {
|
|
98
|
+
for (const elem of subset) {
|
|
99
|
+
if (!set.has(elem)) return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
Set.union = function Union<T>(setA: Set<T> | Array<T>, setB: Set<T> | Array<T>) {
|
|
106
|
+
const union = new Set(setA);
|
|
107
|
+
for (const elem of setB) union.add(elem);
|
|
108
|
+
return union;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// @ts-ignore
|
|
112
|
+
Set.intersection = function Intersection<T>(A: Set<T> | Array<T> = [], B: Set<T> | Array<T> = []) {
|
|
113
|
+
const intersection = new Set<T>();
|
|
114
|
+
if (A instanceof Array) A = new Set(A);
|
|
115
|
+
if (B instanceof Array) B = new Set(B);
|
|
116
|
+
for (const elem of B) if (A.has(elem)) intersection.add(elem);
|
|
117
|
+
return intersection;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export function sleep(timeout: number) {
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
setTimeout(resolve, timeout, true);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function deepen(modifyString: (source: string) => string) {
|
|
127
|
+
function modifyObject<T>(source: T): T {
|
|
128
|
+
if (typeof source !== 'object' || !source) return source;
|
|
129
|
+
if (Array.isArray(source)) return source.map(modifyObject) as any;
|
|
130
|
+
const result = {} as any;
|
|
131
|
+
for (const key in source) {
|
|
132
|
+
result[modifyString(key)] = modifyObject(source[key]);
|
|
133
|
+
}
|
|
134
|
+
return result as T;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return function t<T>(source: T): T {
|
|
138
|
+
if (typeof source === 'string') return modifyString(source) as any;
|
|
139
|
+
return modifyObject(source);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function noop() { }
|
|
144
|
+
|
|
145
|
+
export const camelCase = deepen((source) => source.replace(/[_-][a-z]/g, (str) => str.slice(1).toUpperCase()));
|
|
146
|
+
export const paramCase = deepen((source) => source.replace(/_/g, '-').replace(/(?<!^)[A-Z]/g, (str) => `-${str.toLowerCase()}`));
|
|
147
|
+
export const snakeCase = deepen((source) => source.replace(/-/g, '_').replace(/(?<!^)[A-Z]/g, (str) => `_${str.toLowerCase()}`));
|
|
148
|
+
|
|
149
|
+
const TIME_RE = /^([0-9]+(?:\.[0-9]*)?)([mu]?)s?$/i;
|
|
150
|
+
const TIME_UNITS = { '': 1000, m: 1, u: 0.001 } as Record<string, number>;
|
|
151
|
+
const MEMORY_RE = /^([0-9]+(?:\.[0-9]*)?)([kmg])b?$/i;
|
|
152
|
+
const MEMORY_UNITS = { k: 1 / 1024, m: 1, g: 1024 } as Record<string, number>;
|
|
153
|
+
|
|
154
|
+
export function parseTimeMS(str: string | number, throwOnError = true) {
|
|
155
|
+
if (typeof str === 'number' || Number.isSafeInteger(+str)) return +str;
|
|
156
|
+
const match = TIME_RE.exec(str);
|
|
157
|
+
if (!match && throwOnError) throw new Error(`${str} error parsing time`);
|
|
158
|
+
if (!match) return 1000;
|
|
159
|
+
return Math.floor(Number.parseFloat(match[1]) * TIME_UNITS[match[2].toLowerCase()]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function parseMemoryMB(str: string | number, throwOnError = true) {
|
|
163
|
+
if (typeof str === 'number' || Number.isSafeInteger(+str)) return +str;
|
|
164
|
+
const match = MEMORY_RE.exec(str);
|
|
165
|
+
if (!match && throwOnError) throw new Error(`${str} error parsing memory`);
|
|
166
|
+
if (!match) return 256;
|
|
167
|
+
return Math.ceil(Number.parseFloat(match[1]) * MEMORY_UNITS[match[2].toLowerCase()]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function _digit2(number: number) {
|
|
171
|
+
return number < 10 ? `0${number}` : number.toString();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function formatSeconds(_seconds: string | number = '0', showSeconds = true) {
|
|
175
|
+
const seconds = +_seconds;
|
|
176
|
+
let res = '{0}:{1}'.format(
|
|
177
|
+
showSeconds ? _digit2(Math.floor(seconds / 3600)) : Math.floor(seconds / 3600),
|
|
178
|
+
_digit2(Math.floor((seconds % 3600) / 60)),
|
|
179
|
+
);
|
|
180
|
+
if (showSeconds) res += `:${_digit2(seconds % 60)}`;
|
|
181
|
+
return res;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function size(s: number, base = 1) {
|
|
185
|
+
s *= base;
|
|
186
|
+
const unit = 1024;
|
|
187
|
+
const unitNames = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
|
188
|
+
for (const unitName of unitNames) {
|
|
189
|
+
if (s < unit) return '{0} {1}'.format(Math.round(s * 10) / 10, unitName);
|
|
190
|
+
s /= unit;
|
|
191
|
+
}
|
|
192
|
+
return `${Math.round(s * unit)} ${unitNames.at(-1)}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function randomPick<T>(arr: T[]): T {
|
|
196
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export type StringKeys<O> = {
|
|
200
|
+
[K in keyof O]: string extends O[K] ? K : never
|
|
201
|
+
}[keyof O];
|
|
202
|
+
const fSortR = /\D+|\d+/g;
|
|
203
|
+
export function sortFiles(files: string[]): string[];
|
|
204
|
+
export function sortFiles<T extends { _id: string }>(files: T[], key?: '_id'): T[];
|
|
205
|
+
export function sortFiles<T extends Record<string, any>>(files: T[], key: StringKeys<T>): T[];
|
|
206
|
+
export function sortFiles(files: Record<string, any>[] | string[], key = '_id') {
|
|
207
|
+
if (!files?.length) return [];
|
|
208
|
+
const isString = typeof files[0] === 'string';
|
|
209
|
+
const result = files
|
|
210
|
+
.map((i: any) => (isString ? { name: i, _weights: i.match(fSortR) } : { ...i, _weights: (i[key] || i.name).match(fSortR) }))
|
|
211
|
+
.sort((a, b) => {
|
|
212
|
+
let pos = 0;
|
|
213
|
+
const weightsA = a._weights;
|
|
214
|
+
const weightsB = b._weights;
|
|
215
|
+
let weightA = weightsA[pos];
|
|
216
|
+
let weightB = weightsB[pos];
|
|
217
|
+
while (weightA && weightB) {
|
|
218
|
+
const v = weightA - weightB;
|
|
219
|
+
if (!Number.isNaN(v) && v !== 0) return v;
|
|
220
|
+
if (weightA !== weightB) return weightA > weightB ? 1 : -1;
|
|
221
|
+
pos += 1;
|
|
222
|
+
weightA = weightsA[pos];
|
|
223
|
+
weightB = weightsB[pos];
|
|
224
|
+
}
|
|
225
|
+
return weightA ? 1 : -1;
|
|
226
|
+
});
|
|
227
|
+
return result.map((x) => (isString ? x.name : (delete x._weights && x)));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const getAlphabeticId = (() => {
|
|
231
|
+
// A...Z, AA...AZ, BA...BZ, ...
|
|
232
|
+
const f: (a: number) => string = (a: number) => (a < 0 ? '' : f(a / 26 - 1) + String.fromCharCode((a % 26) + 65));
|
|
233
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
234
|
+
const cache = alphabet.split('');
|
|
235
|
+
for (const ch of alphabet) cache.push(...alphabet.split('').map((c) => ch + c));
|
|
236
|
+
return (i: number) => cache[i] || (i < 0 ? '?' : f(i));
|
|
237
|
+
})();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const PATH = process.env.PATH?.split(':') || [];
|
|
5
|
+
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
const pm2: typeof import('pm2') | null = (() => {
|
|
8
|
+
for (const dir of PATH) {
|
|
9
|
+
try {
|
|
10
|
+
const info = fs.readlinkSync(path.resolve(dir, 'pm2'));
|
|
11
|
+
const p = path.resolve(dir, info);
|
|
12
|
+
if (p.startsWith('/nix')) return require(`${p.split('/bin')[0]}/lib/node_modules/pm2`);
|
|
13
|
+
// installed by yarn
|
|
14
|
+
return require(`${p.split('.bin')[0]}pm2`);
|
|
15
|
+
} catch (e) { }
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
})();
|
|
19
|
+
|
|
20
|
+
export default pm2;
|
package/lib/search.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SearchParserOptions, SearchParserResult } from 'search-query-parser';
|
|
2
|
+
import { parse } from 'search-query-parser';
|
|
3
|
+
|
|
4
|
+
export { parse, SearchParserOptions, SearchParserResult };
|
|
5
|
+
|
|
6
|
+
export function stringify(queryObject: SearchParserResult, options: SearchParserOptions = {}, prefix = ''): string {
|
|
7
|
+
if (!Object.keys(queryObject || {}).length) return '';
|
|
8
|
+
prefix ||= '';
|
|
9
|
+
const toArray = (val: string | string[]) => (typeof val === 'string' ? [val] : val);
|
|
10
|
+
const addQuotes = (s: string) => (s.includes(' ') ? JSON.stringify(s) : s);
|
|
11
|
+
const addPrefix = (s: string) => prefix + s;
|
|
12
|
+
const parts = [];
|
|
13
|
+
if (queryObject.text) {
|
|
14
|
+
const value = toArray(queryObject.text);
|
|
15
|
+
if (value.length) parts.push(value.map(addQuotes).map(addPrefix).join(' '));
|
|
16
|
+
}
|
|
17
|
+
for (const range of options.ranges || []) {
|
|
18
|
+
if (!queryObject[range]) continue;
|
|
19
|
+
let value = queryObject[range].from;
|
|
20
|
+
const to = queryObject[range].to;
|
|
21
|
+
if (to) value = `${value}-${to}`;
|
|
22
|
+
if (value) parts.push(addPrefix(`${range}:${value}`));
|
|
23
|
+
}
|
|
24
|
+
for (const keyword of options.keywords || []) {
|
|
25
|
+
if (!queryObject[keyword]) continue;
|
|
26
|
+
const value = toArray(queryObject[keyword]);
|
|
27
|
+
if (value.length > 0) {
|
|
28
|
+
parts.push(addPrefix(`${keyword}:${addQuotes(value.join(','))}`));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (Object.keys(queryObject.exclude || {}).length) {
|
|
32
|
+
parts.push(stringify(queryObject.exclude, options, '-'));
|
|
33
|
+
}
|
|
34
|
+
return parts.join(' ');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default { stringify, parse };
|
package/lib/sysinfo.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import systeminformation, { Systeminformation } from 'systeminformation';
|
|
2
|
+
|
|
3
|
+
const cache: any = {};
|
|
4
|
+
|
|
5
|
+
export interface StatusUpdate {
|
|
6
|
+
memory: Systeminformation.MemData;
|
|
7
|
+
load: Systeminformation.CurrentLoadData;
|
|
8
|
+
battery: Systeminformation.BatteryData;
|
|
9
|
+
CpuTemp: Systeminformation.CpuTemperatureData;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface StatusFull extends StatusUpdate {
|
|
13
|
+
mid: string;
|
|
14
|
+
cpu: Systeminformation.CpuData;
|
|
15
|
+
osinfo: Systeminformation.OsData;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function get(): Promise<StatusFull> {
|
|
19
|
+
const [uuid, cpu, memory, osinfo, load, CpuTemp, battery] = await Promise.all([
|
|
20
|
+
systeminformation.uuid(),
|
|
21
|
+
systeminformation.cpu(),
|
|
22
|
+
systeminformation.mem(),
|
|
23
|
+
systeminformation.osInfo(),
|
|
24
|
+
systeminformation.currentLoad(),
|
|
25
|
+
systeminformation.cpuTemperature(),
|
|
26
|
+
systeminformation.battery(),
|
|
27
|
+
]);
|
|
28
|
+
const mid = uuid.hardware;
|
|
29
|
+
delete osinfo.fqdn;
|
|
30
|
+
cache.cpu = cpu;
|
|
31
|
+
cache.osinfo = osinfo;
|
|
32
|
+
cache.mid = mid;
|
|
33
|
+
return {
|
|
34
|
+
mid, cpu, memory, osinfo, load, CpuTemp, battery,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function update(): Promise<[string, StatusUpdate, StatusFull]> {
|
|
39
|
+
const [memory, load, CpuTemp, battery] = await Promise.all([
|
|
40
|
+
systeminformation.mem(),
|
|
41
|
+
systeminformation.currentLoad(),
|
|
42
|
+
systeminformation.cpuTemperature(),
|
|
43
|
+
systeminformation.battery(),
|
|
44
|
+
]);
|
|
45
|
+
const { mid, cpu, osinfo } = cache;
|
|
46
|
+
return [
|
|
47
|
+
mid,
|
|
48
|
+
{
|
|
49
|
+
memory, load, battery, CpuTemp,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
mid, cpu, memory, osinfo, load, battery, CpuTemp,
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
}
|
package/lib/utils.ts
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { Duplex, PassThrough, Writable } from 'stream';
|
|
5
|
+
import { inspect } from 'util';
|
|
6
|
+
import type { Entry, ZipReader } from '@zip.js/zip.js';
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import type { Moment } from 'moment';
|
|
9
|
+
import { Exporter, Factory, Logger as Reggol } from 'reggol';
|
|
10
|
+
import type * as superagent from 'superagent';
|
|
11
|
+
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
export * from '@halooj/utils/lib/common.ts';
|
|
14
|
+
export * as fs from 'fs-extra';
|
|
15
|
+
|
|
16
|
+
Factory.formatters['d'] = (value, exporter) => Reggol.color(exporter, 3, value);
|
|
17
|
+
|
|
18
|
+
const factory = new Factory();
|
|
19
|
+
|
|
20
|
+
factory.addExporter(new Exporter.Console({
|
|
21
|
+
showDiff: false,
|
|
22
|
+
showTime: 'dd hh:mm:ss',
|
|
23
|
+
label: {
|
|
24
|
+
align: 'right',
|
|
25
|
+
width: 9,
|
|
26
|
+
margin: 1,
|
|
27
|
+
},
|
|
28
|
+
timestamp: Date.now(),
|
|
29
|
+
levels: { default: process.env.DEV ? 3 : 2 },
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
function createLogger(name: string) {
|
|
33
|
+
return factory.createLogger(name);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type Logger = Reggol & { new(name: string): Reggol & Logger };
|
|
37
|
+
export const Logger = createLogger as any as Logger;
|
|
38
|
+
|
|
39
|
+
const encrypt = (algorithm, content) => crypto.createHash(algorithm).update(content).digest('hex');
|
|
40
|
+
export const sha1 = (content: string) => encrypt('sha1', content);
|
|
41
|
+
export const sha256 = (content: string) => encrypt('sha256', content);
|
|
42
|
+
export const md5 = (content: string) => encrypt('md5', content);
|
|
43
|
+
|
|
44
|
+
export function folderSize(folderPath: string) {
|
|
45
|
+
let size = 0;
|
|
46
|
+
const _next = function a(p: string) {
|
|
47
|
+
if (p) {
|
|
48
|
+
const stats = fs.statSync(p);
|
|
49
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
50
|
+
if (!stats.isSymbolicLink()) size += stats.size;
|
|
51
|
+
} else {
|
|
52
|
+
size += stats.size;
|
|
53
|
+
const files = fs.readdirSync(p);
|
|
54
|
+
if (Array.isArray(files)) {
|
|
55
|
+
for (const file of files) _next(path.join(p, file));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
_next(folderPath);
|
|
61
|
+
return size;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
String.prototype.format = function formatStr(...args) {
|
|
65
|
+
let result = this;
|
|
66
|
+
if (args.length) {
|
|
67
|
+
if (args.length === 1 && typeof args[0] === 'object') {
|
|
68
|
+
const t = args[0];
|
|
69
|
+
for (const key in t) {
|
|
70
|
+
if (!key.startsWith('_') && t[key] !== undefined) {
|
|
71
|
+
if (t._inspect && typeof t[key] === 'object') {
|
|
72
|
+
t[key] = inspect(t[key], { colors: process?.stderr?.isTTY });
|
|
73
|
+
}
|
|
74
|
+
const reg = new RegExp(`(\\{${key}\\})`, 'g');
|
|
75
|
+
result = result.replace(reg, t[key]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else return this.formatFromArray(args);
|
|
79
|
+
}
|
|
80
|
+
return result.toString();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export function isClass(obj: any, strict = false): obj is new (...args: any) => any {
|
|
84
|
+
if (typeof obj !== 'function') return false;
|
|
85
|
+
if (obj.prototype === undefined) return false;
|
|
86
|
+
// FIXME cordis proxies the object and make this assertion fail
|
|
87
|
+
// if (obj.prototype.constructor !== obj) return false;
|
|
88
|
+
if (Object.getOwnPropertyNames(obj.prototype).length >= 2) return true;
|
|
89
|
+
const str = obj.toString();
|
|
90
|
+
if (str.slice(0, 5) === 'class') return true;
|
|
91
|
+
if (/^function\s+\(|^function\s+anonymous\(/.test(str)) return false;
|
|
92
|
+
if (strict && /^function\s+[A-Z]/.test(str)) return true;
|
|
93
|
+
if (/\b\(this\b|\bthis[.[]\b/.test(str)) {
|
|
94
|
+
if (!strict || /classCallCheck\(this/.test(str)) return true;
|
|
95
|
+
return /^function\sdefault_\d+\s*\(/.test(str);
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isSuperagentRequest(t: NodeJS.ReadableStream | superagent.Request): t is superagent.Request {
|
|
101
|
+
return 'req' in t;
|
|
102
|
+
}
|
|
103
|
+
export function streamToBuffer(input: NodeJS.ReadableStream | superagent.Request, maxSize = 0): Promise<Buffer> {
|
|
104
|
+
let stream: NodeJS.ReadableStream;
|
|
105
|
+
if (isSuperagentRequest(input)) {
|
|
106
|
+
const s = new PassThrough();
|
|
107
|
+
input.pipe(s);
|
|
108
|
+
stream = s;
|
|
109
|
+
} else stream = input;
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const buffers = [];
|
|
112
|
+
let length = 0;
|
|
113
|
+
function onData(data) {
|
|
114
|
+
buffers.push(data);
|
|
115
|
+
length += data.length;
|
|
116
|
+
if (maxSize && length > maxSize) {
|
|
117
|
+
stream.removeListener('data', onData);
|
|
118
|
+
reject(new Error('buffer length exceeded'));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
stream.on('error', reject);
|
|
122
|
+
stream.on('data', onData);
|
|
123
|
+
stream.on('end', () => resolve(Buffer.concat(buffers)));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function bufferToStream(buffer: Buffer): NodeJS.ReadableStream {
|
|
128
|
+
const stream = new Duplex();
|
|
129
|
+
stream.push(buffer);
|
|
130
|
+
stream.push(null);
|
|
131
|
+
return stream;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let ObjectId: typeof import('bson').ObjectId; // eslint-disable-line
|
|
135
|
+
let isMoment: (x: any) => x is Moment;
|
|
136
|
+
|
|
137
|
+
export const Time = {
|
|
138
|
+
second: 1000,
|
|
139
|
+
minute: 60 * 1000,
|
|
140
|
+
hour: 60 * 60 * 1000,
|
|
141
|
+
day: 24 * 60 * 60 * 1000,
|
|
142
|
+
week: 7 * 24 * 60 * 60 * 1000,
|
|
143
|
+
|
|
144
|
+
formatTimeShort(ms: number) {
|
|
145
|
+
const abs = Math.abs(ms);
|
|
146
|
+
if (abs >= Time.day - Time.hour / 2) return `${Math.round(ms / Time.day)}d`;
|
|
147
|
+
if (abs >= Time.hour - Time.minute / 2) return `${Math.round(ms / Time.hour)}h`;
|
|
148
|
+
if (abs >= Time.minute - Time.second / 2) return `${Math.round(ms / Time.minute)}m`;
|
|
149
|
+
if (abs >= Time.second) return `${Math.round(ms / Time.second)}s`;
|
|
150
|
+
return `${ms}ms`;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
getObjectID(timestamp: string | Date | Moment, allZero = true) {
|
|
154
|
+
try {
|
|
155
|
+
ObjectId ||= require('bson').ObjectId;
|
|
156
|
+
} catch (e) {
|
|
157
|
+
throw new Error('No bson module found');
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
isMoment ||= require('moment').isMoment;
|
|
161
|
+
} catch (e) {
|
|
162
|
+
throw new Error('No moment module found');
|
|
163
|
+
}
|
|
164
|
+
let _timestamp: number;
|
|
165
|
+
if (typeof timestamp === 'string') _timestamp = new Date(timestamp).getTime();
|
|
166
|
+
else if (isMoment(timestamp)) _timestamp = timestamp.toDate().getTime();
|
|
167
|
+
else _timestamp = timestamp.getTime();
|
|
168
|
+
const hexSeconds = Math.floor(_timestamp / 1000).toString(16);
|
|
169
|
+
return new ObjectId(`${hexSeconds}${allZero ? '0000000000000000' : new ObjectId().toHexString().substring(8)}`);
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export function errorMessage(err: Error | string) {
|
|
174
|
+
const t = typeof err === 'string' ? err : err.stack;
|
|
175
|
+
const lines = t.split('\n')
|
|
176
|
+
.filter((i) => !i.includes(' (node:') && !i.includes('(internal'));
|
|
177
|
+
let cursor = 1;
|
|
178
|
+
let count = 0;
|
|
179
|
+
while (cursor < lines.length) {
|
|
180
|
+
if (lines[cursor] !== lines[cursor - 1]) {
|
|
181
|
+
if (count) {
|
|
182
|
+
lines[cursor - 1] += ` [+${count}]`;
|
|
183
|
+
count = 0;
|
|
184
|
+
}
|
|
185
|
+
cursor++;
|
|
186
|
+
} else {
|
|
187
|
+
count++;
|
|
188
|
+
lines.splice(cursor, 1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const parsed = lines.join('\n')
|
|
192
|
+
.replace(/[A-Z]:\\.+\\@@halooj/oj\\/g, '@@halooj/oj\\')
|
|
193
|
+
.replace(/\/.+\/@@halooj/oj\//g, '\\')
|
|
194
|
+
.replace(/[A-Z]:\\.+\\@halooj/oj\\/g, '@halooj/oj\\')
|
|
195
|
+
.replace(/\/.+\/@halooj/oj\//g, '@halooj/oj/')
|
|
196
|
+
.replace(/[A-Z]:\\.+\\node_modules\\/g, '')
|
|
197
|
+
.replace(/\/.+\/node_modules\//g, '')
|
|
198
|
+
.replace(/\\/g, '/');
|
|
199
|
+
if (typeof err === 'string') return parsed;
|
|
200
|
+
err.stack = parsed;
|
|
201
|
+
return err;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function changeErrorType(err: any, Err: any) {
|
|
205
|
+
const e = new Err(err.message);
|
|
206
|
+
e.stack = err.stack;
|
|
207
|
+
if (err.params) e.params = err.params;
|
|
208
|
+
return e;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function findFile(pathname: string, doThrow = true) {
|
|
212
|
+
if (await fs.pathExists(path.resolve(pathname))) return path.resolve(pathname);
|
|
213
|
+
if (await fs.pathExists(path.resolve(process.cwd(), pathname))) return path.resolve(process.cwd(), pathname);
|
|
214
|
+
if (await fs.pathExists(path.resolve(__dirname, pathname))) return path.resolve(__dirname, pathname);
|
|
215
|
+
try {
|
|
216
|
+
return require.resolve(pathname);
|
|
217
|
+
} catch (e) { }
|
|
218
|
+
if (pathname.includes('/')) {
|
|
219
|
+
const eles = pathname.split('/');
|
|
220
|
+
let pkg = eles.shift();
|
|
221
|
+
if (pkg.startsWith('@')) pkg = `${pkg}/${eles.shift()}`;
|
|
222
|
+
const rest = eles.join('/');
|
|
223
|
+
try {
|
|
224
|
+
const p = path.dirname(require.resolve(path.join(pkg, 'package.json')));
|
|
225
|
+
if (await fs.pathExists(path.resolve(p, rest))) return path.resolve(p, rest);
|
|
226
|
+
} catch (e) { }
|
|
227
|
+
}
|
|
228
|
+
if (await fs.pathExists(path.resolve(os.homedir(), pathname))) return path.resolve(os.homedir(), pathname);
|
|
229
|
+
if (await fs.pathExists(path.resolve(os.homedir(), '.hydro', pathname))) return path.resolve(os.homedir(), '.hydro', pathname);
|
|
230
|
+
// eslint-disable-next-line max-len
|
|
231
|
+
if (await fs.pathExists(path.resolve(os.homedir(), '.config', 'hydro', pathname))) return path.resolve(os.homedir(), '.config', 'hydro', pathname);
|
|
232
|
+
if (doThrow) throw new Error(`File ${pathname} not found`);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function findFileSync(pathname: string, doThrow: boolean | Error = true) {
|
|
237
|
+
if (fs.pathExistsSync(path.resolve(pathname))) return path.resolve(pathname);
|
|
238
|
+
if (fs.pathExistsSync(path.resolve(process.cwd(), pathname))) return path.resolve(process.cwd(), pathname);
|
|
239
|
+
if (fs.pathExistsSync(path.resolve(__dirname, pathname))) return path.resolve(__dirname, pathname);
|
|
240
|
+
try {
|
|
241
|
+
return require.resolve(pathname);
|
|
242
|
+
} catch (e) { }
|
|
243
|
+
if (pathname.includes('/')) {
|
|
244
|
+
const eles = pathname.split('/');
|
|
245
|
+
let pkg = eles.shift();
|
|
246
|
+
if (pkg.startsWith('@')) pkg = `${pkg}/${eles.shift()}`;
|
|
247
|
+
const rest = eles.join('/');
|
|
248
|
+
try {
|
|
249
|
+
const p = path.dirname(require.resolve(path.join(pkg, 'package.json')));
|
|
250
|
+
if (fs.statSync(path.resolve(p, rest))) return path.resolve(p, rest);
|
|
251
|
+
} catch (e) { }
|
|
252
|
+
}
|
|
253
|
+
if (fs.pathExistsSync(path.resolve(os.homedir(), pathname))) return path.resolve(os.homedir(), pathname);
|
|
254
|
+
if (fs.pathExistsSync(path.resolve(os.homedir(), '.hydro', pathname))) return path.resolve(os.homedir(), '.hydro', pathname);
|
|
255
|
+
if (fs.pathExistsSync(path.resolve(os.homedir(), '.config', 'hydro', pathname))) return path.resolve(os.homedir(), '.config', 'hydro', pathname);
|
|
256
|
+
if (doThrow) throw (typeof doThrow !== 'boolean' ? doThrow : new Error(`File ${pathname} not found`));
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export const htmlEncode = (str: string) => str.replace(/[&<>'"]/g,
|
|
261
|
+
(tag: string) => ({
|
|
262
|
+
'&': '&',
|
|
263
|
+
'<': '<',
|
|
264
|
+
'>': '>',
|
|
265
|
+
"'": ''',
|
|
266
|
+
'"': '"',
|
|
267
|
+
}[tag]));
|
|
268
|
+
|
|
269
|
+
export function Counter<T extends (string | number) = string>() {
|
|
270
|
+
return new Proxy({}, {
|
|
271
|
+
get: (target, prop) => {
|
|
272
|
+
if (target[prop] === undefined) return 0;
|
|
273
|
+
return target[prop];
|
|
274
|
+
},
|
|
275
|
+
}) as Record<T, number>;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function canonical(p: string) {
|
|
279
|
+
if (!p) return '';
|
|
280
|
+
const safeSuffix = path.posix.normalize(`/${p.split('\\').join('/')}`);
|
|
281
|
+
return path.join('.', safeSuffix);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function sanitize(prefix: string, name: string) {
|
|
285
|
+
prefix = path.resolve(path.normalize(prefix));
|
|
286
|
+
const parts = name.split('/');
|
|
287
|
+
for (let i = 0, l = parts.length; i < l; i++) {
|
|
288
|
+
const p = path.normalize(path.join(prefix, parts.slice(i, l).join(path.sep)));
|
|
289
|
+
if (p.indexOf(prefix) === 0) {
|
|
290
|
+
return p;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return path.normalize(path.join(prefix, path.basename(name)));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function sanitizePath(pathname: string) {
|
|
297
|
+
const parts = pathname.replace(/\\/g, '/').split('/').filter((i) => i && i !== '.' && i !== '..');
|
|
298
|
+
return parts.join(path.sep);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface ExtractZipConfig {
|
|
302
|
+
overwrite?: boolean;
|
|
303
|
+
strip?: boolean;
|
|
304
|
+
signal?: AbortSignal;
|
|
305
|
+
parseError?: (err: Error) => Error;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* eslint-disable no-await-in-loop */
|
|
309
|
+
export async function extractZip<T>(zipOrEntries: ZipReader<T> | Entry[], dest: string, config: ExtractZipConfig = {}) {
|
|
310
|
+
const { overwrite = false, strip = false, signal } = config;
|
|
311
|
+
let entries: Entry[];
|
|
312
|
+
if (Array.isArray(zipOrEntries)) entries = zipOrEntries;
|
|
313
|
+
else {
|
|
314
|
+
try {
|
|
315
|
+
entries = await zipOrEntries.getEntries();
|
|
316
|
+
} catch (e) {
|
|
317
|
+
if (config.parseError) throw config.parseError(e);
|
|
318
|
+
throw e;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const shouldStrip = strip ? entries.every((i) => i.filename.startsWith(entries[0].filename)) : false;
|
|
322
|
+
for (const entry of entries) {
|
|
323
|
+
const name = shouldStrip ? entry.filename.substring(entries[0].filename.length) : entry.filename;
|
|
324
|
+
const d = sanitize(dest, canonical(name));
|
|
325
|
+
if (entry.directory === true) {
|
|
326
|
+
await fs.mkdir(d, { recursive: true });
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (fs.existsSync(d) && !overwrite) continue;
|
|
330
|
+
const dir = path.dirname(d);
|
|
331
|
+
if (!fs.existsSync(dir)) await fs.mkdir(dir, { recursive: true });
|
|
332
|
+
const content = await entry.getData(Writable.toWeb(fs.createWriteStream(d)), { signal });
|
|
333
|
+
if (!content) throw new Error('CANT_EXTRACT_FILE');
|
|
334
|
+
await fs.utimes(d, entry.lastModDate, entry.lastModDate);
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
[Symbol.asyncDispose]: () => fs.remove(dest),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export async function pipeRequest(req: superagent.Request, w: fs.WriteStream, timeout?: number, name?: string) {
|
|
342
|
+
try {
|
|
343
|
+
await new Promise((resolve, reject) => {
|
|
344
|
+
w.on('finish', () => {
|
|
345
|
+
resolve(null);
|
|
346
|
+
});
|
|
347
|
+
req.buffer(false).timeout({
|
|
348
|
+
response: Math.min(10000, timeout),
|
|
349
|
+
deadline: timeout,
|
|
350
|
+
}).parse((resp, cb) => {
|
|
351
|
+
if (resp.statusCode !== 200) throw new Error(`${resp.statusCode}`);
|
|
352
|
+
else {
|
|
353
|
+
resp.pipe(w);
|
|
354
|
+
resp.on('end', () => {
|
|
355
|
+
cb(null, undefined);
|
|
356
|
+
});
|
|
357
|
+
resp.on('error', (err) => {
|
|
358
|
+
cb(err, undefined);
|
|
359
|
+
reject(err);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}).catch(reject);
|
|
363
|
+
});
|
|
364
|
+
} catch (e: any) {
|
|
365
|
+
throw new Error(`Download${e.errno === 'ETIMEDOUT' ? 'Timedout' : 'Error'}(${name ? `${name}, ` : ''}${e.message})`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export * as yaml from 'js-yaml';
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@halooj/utils",
|
|
3
|
+
"version": "1.5.6",
|
|
4
|
+
"description": "hydrooj utils",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/utils.ts",
|
|
7
|
+
"repository": "https://github.com/hydro-dev/Hydro.git",
|
|
8
|
+
"author": "undefined <i@undefined.moe>",
|
|
9
|
+
"license": "AGPL-3.0-or-later",
|
|
10
|
+
"preferUnplugged": true,
|
|
11
|
+
"optionalPeerDependencies": {
|
|
12
|
+
"bson": "^7.0.0",
|
|
13
|
+
"moment-timezone": "^0.5.48"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"cac": "^7.0.0",
|
|
17
|
+
"fs-extra": "^11.3.5",
|
|
18
|
+
"js-yaml": "^4.3.0",
|
|
19
|
+
"reggol": "^2.1.0",
|
|
20
|
+
"search-query-parser": "^1.6.0",
|
|
21
|
+
"systeminformation": "^5.31.11",
|
|
22
|
+
"@halooj/register": "^1.0.4"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/fs-extra": "^11.0.4"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
}
|
|
30
|
+
}
|