@appium/support 7.0.4 → 7.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/build/lib/console.d.ts +42 -88
- package/build/lib/console.d.ts.map +1 -1
- package/build/lib/console.js +25 -85
- package/build/lib/console.js.map +1 -1
- package/build/lib/doctor.d.ts +6 -18
- package/build/lib/doctor.d.ts.map +1 -1
- package/build/lib/doctor.js +0 -15
- package/build/lib/doctor.js.map +1 -1
- package/build/lib/env.d.ts +14 -20
- package/build/lib/env.d.ts.map +1 -1
- package/build/lib/env.js +24 -61
- package/build/lib/env.js.map +1 -1
- package/build/lib/fs.d.ts +109 -148
- package/build/lib/fs.d.ts.map +1 -1
- package/build/lib/fs.js +130 -230
- package/build/lib/fs.js.map +1 -1
- package/build/lib/image-util.d.ts +7 -6
- package/build/lib/image-util.d.ts.map +1 -1
- package/build/lib/image-util.js +9 -6
- package/build/lib/image-util.js.map +1 -1
- package/build/lib/index.d.ts +19 -17
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/logger.d.ts +1 -1
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logger.js +1 -1
- package/build/lib/logger.js.map +1 -1
- package/build/lib/logging.d.ts +7 -15
- package/build/lib/logging.d.ts.map +1 -1
- package/build/lib/logging.js +36 -62
- package/build/lib/logging.js.map +1 -1
- package/build/lib/mjpeg.d.ts +19 -56
- package/build/lib/mjpeg.d.ts.map +1 -1
- package/build/lib/mjpeg.js +55 -78
- package/build/lib/mjpeg.js.map +1 -1
- package/build/lib/mkdirp.d.ts +4 -1
- package/build/lib/mkdirp.d.ts.map +1 -1
- package/build/lib/mkdirp.js +1 -2
- package/build/lib/mkdirp.js.map +1 -1
- package/build/lib/net.d.ts +52 -90
- package/build/lib/net.d.ts.map +1 -1
- package/build/lib/net.js +104 -193
- package/build/lib/net.js.map +1 -1
- package/build/lib/node.d.ts +16 -17
- package/build/lib/node.d.ts.map +1 -1
- package/build/lib/node.js +115 -120
- package/build/lib/node.js.map +1 -1
- package/build/lib/npm.d.ts +65 -86
- package/build/lib/npm.d.ts.map +1 -1
- package/build/lib/npm.js +64 -122
- package/build/lib/npm.js.map +1 -1
- package/build/lib/plist.d.ts +36 -29
- package/build/lib/plist.d.ts.map +1 -1
- package/build/lib/plist.js +62 -59
- package/build/lib/plist.js.map +1 -1
- package/build/lib/process.d.ts +19 -2
- package/build/lib/process.d.ts.map +1 -1
- package/build/lib/process.js +24 -7
- package/build/lib/process.js.map +1 -1
- package/build/lib/system.d.ts +41 -6
- package/build/lib/system.d.ts.map +1 -1
- package/build/lib/system.js +49 -14
- package/build/lib/system.js.map +1 -1
- package/build/lib/tempdir.d.ts +26 -49
- package/build/lib/tempdir.d.ts.map +1 -1
- package/build/lib/tempdir.js +46 -78
- package/build/lib/tempdir.js.map +1 -1
- package/build/lib/timing.d.ts +28 -22
- package/build/lib/timing.d.ts.map +1 -1
- package/build/lib/timing.js +16 -17
- package/build/lib/timing.js.map +1 -1
- package/build/lib/util.d.ts +164 -181
- package/build/lib/util.d.ts.map +1 -1
- package/build/lib/util.js +198 -253
- package/build/lib/util.js.map +1 -1
- package/build/lib/zip.d.ts +81 -139
- package/build/lib/zip.d.ts.map +1 -1
- package/build/lib/zip.js +235 -283
- package/build/lib/zip.js.map +1 -1
- package/lib/console.ts +139 -0
- package/lib/{doctor.js → doctor.ts} +6 -20
- package/lib/{env.js → env.ts} +34 -62
- package/lib/fs.ts +453 -0
- package/lib/image-util.ts +40 -0
- package/lib/index.ts +1 -0
- package/lib/{logger.js → logger.ts} +1 -1
- package/lib/logging.ts +157 -0
- package/lib/mjpeg.ts +186 -0
- package/lib/{mkdirp.js → mkdirp.ts} +2 -2
- package/lib/net.ts +305 -0
- package/lib/{node.js → node.ts} +136 -135
- package/lib/npm.ts +291 -0
- package/lib/plist.ts +187 -0
- package/lib/process.ts +62 -0
- package/lib/system.ts +95 -0
- package/lib/tempdir.ts +115 -0
- package/lib/{timing.js → timing.ts} +28 -33
- package/lib/util.ts +561 -0
- package/lib/{zip.js → zip.ts} +344 -299
- package/package.json +24 -26
- package/tsconfig.json +3 -5
- package/index.js +0 -1
- package/lib/console.js +0 -173
- package/lib/fs.js +0 -496
- package/lib/image-util.js +0 -32
- package/lib/logging.js +0 -145
- package/lib/mjpeg.js +0 -207
- package/lib/net.js +0 -336
- package/lib/npm.js +0 -310
- package/lib/plist.js +0 -182
- package/lib/process.js +0 -46
- package/lib/system.js +0 -48
- package/lib/tempdir.js +0 -131
- package/lib/util.js +0 -585
package/lib/fs.ts
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import B from 'bluebird';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import {
|
|
4
|
+
close,
|
|
5
|
+
constants,
|
|
6
|
+
createReadStream,
|
|
7
|
+
createWriteStream,
|
|
8
|
+
promises as fsPromises,
|
|
9
|
+
read,
|
|
10
|
+
write,
|
|
11
|
+
rmSync,
|
|
12
|
+
open,
|
|
13
|
+
type PathLike,
|
|
14
|
+
type MakeDirectoryOptions,
|
|
15
|
+
type ReadAsyncOptions,
|
|
16
|
+
type Stats,
|
|
17
|
+
} from 'node:fs';
|
|
18
|
+
import {promisify} from 'node:util';
|
|
19
|
+
import {glob} from 'glob';
|
|
20
|
+
import type {GlobOptions} from 'glob';
|
|
21
|
+
import klaw from 'klaw';
|
|
22
|
+
import type {Walker} from 'klaw';
|
|
23
|
+
import _ from 'lodash';
|
|
24
|
+
import ncp from 'ncp';
|
|
25
|
+
import {packageDirectorySync} from 'package-directory';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
import {readPackageSync, type NormalizeOptions, type NormalizedPackageJson} from 'read-pkg';
|
|
28
|
+
import sanitize from 'sanitize-filename';
|
|
29
|
+
import which from 'which';
|
|
30
|
+
import log from './logger';
|
|
31
|
+
import {Timer} from './timing';
|
|
32
|
+
import {isWindows} from './system';
|
|
33
|
+
import {pluralize} from './util';
|
|
34
|
+
|
|
35
|
+
const ncpAsync = promisify(ncp) as (
|
|
36
|
+
source: string,
|
|
37
|
+
dest: string,
|
|
38
|
+
opts?: ncp.Options
|
|
39
|
+
) => Promise<void>;
|
|
40
|
+
const findRootCached = _.memoize(
|
|
41
|
+
packageDirectorySync,
|
|
42
|
+
(opts: {cwd?: string} | undefined) => opts?.cwd
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
/** Options for {@linkcode fs.mv} */
|
|
46
|
+
export interface MvOptions {
|
|
47
|
+
/** Whether to automatically create the destination folder structure */
|
|
48
|
+
mkdirp?: boolean;
|
|
49
|
+
/** Set to false to throw if the destination file already exists */
|
|
50
|
+
clobber?: boolean;
|
|
51
|
+
/** @deprecated Legacy, not used */
|
|
52
|
+
limit?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Callback used during directory walking in {@linkcode fs.walkDir}.
|
|
57
|
+
* Return true to stop walking.
|
|
58
|
+
*/
|
|
59
|
+
export type WalkDirCallback = (
|
|
60
|
+
itemPath: string,
|
|
61
|
+
isDirectory: boolean
|
|
62
|
+
) => boolean | void | Promise<boolean | void>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Promisified fs.read signature.
|
|
66
|
+
* @template TBuffer - Buffer type (e.g. NodeJS.ArrayBufferView)
|
|
67
|
+
* @deprecated use `typeof read.__promisify__` instead
|
|
68
|
+
*/
|
|
69
|
+
export type ReadFn<TBuffer extends NodeJS.ArrayBufferView = NodeJS.ArrayBufferView> = (
|
|
70
|
+
fd: number,
|
|
71
|
+
buffer: TBuffer | ReadAsyncOptions<TBuffer>,
|
|
72
|
+
offset?: number,
|
|
73
|
+
length?: number,
|
|
74
|
+
position?: number | null
|
|
75
|
+
) => B<{bytesRead: number; buffer: TBuffer}>;
|
|
76
|
+
|
|
77
|
+
function isErrnoException(err: unknown): err is NodeJS.ErrnoException {
|
|
78
|
+
return err instanceof Error && 'code' in err;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const fs = {
|
|
82
|
+
/**
|
|
83
|
+
* Resolves `true` if `path` is _readable_.
|
|
84
|
+
* On Windows, ACLs are not supported, so this becomes a simple check for existence.
|
|
85
|
+
* This function will never reject.
|
|
86
|
+
*/
|
|
87
|
+
async hasAccess(filePath: PathLike): Promise<boolean> {
|
|
88
|
+
try {
|
|
89
|
+
await fsPromises.access(filePath, constants.R_OK);
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolves `true` if `path` is executable; `false` otherwise.
|
|
98
|
+
* On Windows, delegates to {@linkcode fs.hasAccess}.
|
|
99
|
+
* This function will never reject.
|
|
100
|
+
*/
|
|
101
|
+
async isExecutable(filePath: PathLike): Promise<boolean> {
|
|
102
|
+
try {
|
|
103
|
+
if (isWindows()) {
|
|
104
|
+
return await fs.hasAccess(filePath);
|
|
105
|
+
}
|
|
106
|
+
await fsPromises.access(filePath, constants.R_OK | constants.X_OK);
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/** Alias for {@linkcode fs.hasAccess} */
|
|
114
|
+
async exists(filePath: PathLike): Promise<boolean> {
|
|
115
|
+
return await fs.hasAccess(filePath);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Remove a directory and all its contents, recursively.
|
|
120
|
+
* @see https://nodejs.org/api/fs.html#fspromisesrmpath-options
|
|
121
|
+
*/
|
|
122
|
+
async rimraf(filepath: PathLike): Promise<void> {
|
|
123
|
+
return await fsPromises.rm(filepath, {recursive: true, force: true});
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Remove a directory and all its contents, recursively (sync).
|
|
128
|
+
* @see https://nodejs.org/api/fs.html#fsrmsyncpath-options
|
|
129
|
+
*/
|
|
130
|
+
rimrafSync(filepath: PathLike): void {
|
|
131
|
+
return rmSync(filepath, {recursive: true, force: true});
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Like Node.js `fsPromises.mkdir()`, but will not reject if the directory already exists.
|
|
136
|
+
* @see https://nodejs.org/api/fs.html#fspromisesmkdirpath-options
|
|
137
|
+
*/
|
|
138
|
+
async mkdir(
|
|
139
|
+
filepath: string | Buffer | URL,
|
|
140
|
+
opts: MakeDirectoryOptions = {}
|
|
141
|
+
): Promise<string | undefined> {
|
|
142
|
+
try {
|
|
143
|
+
return await fsPromises.mkdir(filepath, opts);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
if (isErrnoException(err) && err.code !== 'EEXIST') {
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Copies files and entire directories.
|
|
153
|
+
* @see https://npm.im/ncp
|
|
154
|
+
*/
|
|
155
|
+
async copyFile(
|
|
156
|
+
source: string,
|
|
157
|
+
destination: string,
|
|
158
|
+
opts: ncp.Options = {}
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
if (!(await fs.hasAccess(source))) {
|
|
161
|
+
throw new Error(`The file at '${source}' does not exist or is not accessible`);
|
|
162
|
+
}
|
|
163
|
+
return await ncpAsync(source, destination, opts);
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/** Create an MD5 hash of a file. */
|
|
167
|
+
async md5(filePath: PathLike): Promise<string> {
|
|
168
|
+
return await fs.hash(filePath, 'md5');
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Move a file or a folder.
|
|
173
|
+
*/
|
|
174
|
+
async mv(
|
|
175
|
+
from: string,
|
|
176
|
+
to: string,
|
|
177
|
+
opts: MvOptions = {}
|
|
178
|
+
): Promise<void> {
|
|
179
|
+
const ensureDestination = async (p: PathLike): Promise<boolean> => {
|
|
180
|
+
if (opts?.mkdirp && !(await this.exists(p))) {
|
|
181
|
+
await fsPromises.mkdir(p, {recursive: true});
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
};
|
|
186
|
+
const renameFile = async (
|
|
187
|
+
src: PathLike,
|
|
188
|
+
dst: PathLike,
|
|
189
|
+
skipExistenceCheck: boolean
|
|
190
|
+
): Promise<void> => {
|
|
191
|
+
if (!skipExistenceCheck && (await this.exists(dst))) {
|
|
192
|
+
if (opts?.clobber === false) {
|
|
193
|
+
const err = new Error(`The destination path '${dst}' already exists`) as NodeJS.ErrnoException;
|
|
194
|
+
err.code = 'EEXIST';
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
await this.rimraf(dst);
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
await fsPromises.rename(src, dst);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
if (isErrnoException(err) && err.code === 'EXDEV') {
|
|
203
|
+
await this.copyFile(String(src), String(dst));
|
|
204
|
+
await this.rimraf(src);
|
|
205
|
+
} else {
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
let fromStat: Stats;
|
|
212
|
+
try {
|
|
213
|
+
fromStat = await fsPromises.stat(from);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
if (isErrnoException(err) && err.code === 'ENOENT') {
|
|
216
|
+
throw new Error(`The source path '${from}' does not exist or is not accessible`);
|
|
217
|
+
}
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
if (fromStat.isFile()) {
|
|
221
|
+
const dstRootWasCreated = await ensureDestination(path.dirname(to));
|
|
222
|
+
await renameFile(from, to, dstRootWasCreated);
|
|
223
|
+
} else if (fromStat.isDirectory()) {
|
|
224
|
+
const dstRootWasCreated = await ensureDestination(to);
|
|
225
|
+
const items = await fsPromises.readdir(from, {withFileTypes: true});
|
|
226
|
+
for (const item of items) {
|
|
227
|
+
const srcPath = path.join(from, item.name);
|
|
228
|
+
const destPath = path.join(to, item.name);
|
|
229
|
+
if (item.isDirectory()) {
|
|
230
|
+
await this.mv(srcPath, destPath, opts);
|
|
231
|
+
} else if (item.isFile()) {
|
|
232
|
+
await renameFile(srcPath, destPath, dstRootWasCreated);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
await this.rimraf(from);
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/** Find path to an executable in system `PATH`. @see https://github.com/npm/node-which */
|
|
243
|
+
which,
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Given a glob pattern, resolve with list of files matching that pattern.
|
|
247
|
+
* @see https://github.com/isaacs/node-glob
|
|
248
|
+
*/
|
|
249
|
+
glob(pattern: string, options?: GlobOptions): Promise<string[]> {
|
|
250
|
+
return Promise.resolve(
|
|
251
|
+
(options ? glob(pattern, options) : glob(pattern)) as Promise<string[]>
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
/** Sanitize a filename. @see https://github.com/parshap/node-sanitize-filename */
|
|
256
|
+
sanitizeName: sanitize,
|
|
257
|
+
|
|
258
|
+
/** Create a hex digest of some file at `filePath`. */
|
|
259
|
+
async hash(filePath: PathLike, algorithm: string = 'sha1'): Promise<string> {
|
|
260
|
+
return await new Promise<string>((resolve, reject) => {
|
|
261
|
+
const fileHash = crypto.createHash(algorithm);
|
|
262
|
+
const readStream = createReadStream(filePath);
|
|
263
|
+
readStream.on('error', (e: Error) =>
|
|
264
|
+
reject(
|
|
265
|
+
new Error(
|
|
266
|
+
`Cannot calculate ${algorithm} hash for '${filePath}'. Original error: ${e.message}`
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
readStream.on('data', (chunk: Buffer | string) => fileHash.update(chunk));
|
|
271
|
+
readStream.on('end', () => resolve(fileHash.digest('hex')));
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Returns a Walker instance (readable stream / async iterator).
|
|
277
|
+
* @see https://www.npmjs.com/package/klaw
|
|
278
|
+
*/
|
|
279
|
+
walk(dir: string, opts?: klaw.Options): Walker {
|
|
280
|
+
return klaw(dir, opts);
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
/** Recursively create a directory. */
|
|
284
|
+
async mkdirp(dir: PathLike): Promise<string | undefined> {
|
|
285
|
+
return await fs.mkdir(dir, {recursive: true});
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Walks a directory; callback is invoked with path joined with dir.
|
|
290
|
+
* @param dir - Directory path to start walking
|
|
291
|
+
* @param recursive - If true, walk subdirectories
|
|
292
|
+
* @param callback - Called for each path; return true to stop
|
|
293
|
+
* @returns The found path or null if not found
|
|
294
|
+
*/
|
|
295
|
+
/* eslint-disable promise/prefer-await-to-callbacks -- walkDir uses callback + stream .on() + Promise executor */
|
|
296
|
+
async walkDir(
|
|
297
|
+
dir: string,
|
|
298
|
+
recursive: boolean,
|
|
299
|
+
callback: WalkDirCallback
|
|
300
|
+
): Promise<string | null> {
|
|
301
|
+
let isValidRoot = false;
|
|
302
|
+
let errMsg: string | null = null;
|
|
303
|
+
try {
|
|
304
|
+
isValidRoot = (await fs.stat(dir)).isDirectory();
|
|
305
|
+
} catch (e) {
|
|
306
|
+
errMsg = e instanceof Error ? e.message : String(e);
|
|
307
|
+
}
|
|
308
|
+
if (!isValidRoot) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`'${dir}' is not a valid root directory` +
|
|
311
|
+
(errMsg ? `. Original error: ${errMsg}` : '')
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let walker: Walker | undefined;
|
|
316
|
+
let fileCount = 0;
|
|
317
|
+
let directoryCount = 0;
|
|
318
|
+
const timer = new Timer().start();
|
|
319
|
+
return await new Promise<string | null>(function (resolve, reject) {
|
|
320
|
+
let lastFileProcessed: Promise<string | undefined> = Promise.resolve(undefined);
|
|
321
|
+
walker = klaw(dir, {
|
|
322
|
+
depthLimit: recursive ? -1 : 0,
|
|
323
|
+
});
|
|
324
|
+
walker
|
|
325
|
+
.on('data', function (item: klaw.Item) {
|
|
326
|
+
if (walker) {
|
|
327
|
+
walker.pause();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!item.stats.isDirectory()) {
|
|
331
|
+
fileCount++;
|
|
332
|
+
} else {
|
|
333
|
+
directoryCount++;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
lastFileProcessed = (async () => {
|
|
337
|
+
try {
|
|
338
|
+
const done = await callback(item.path, item.stats.isDirectory());
|
|
339
|
+
if (done) {
|
|
340
|
+
resolve(item.path);
|
|
341
|
+
return item.path;
|
|
342
|
+
}
|
|
343
|
+
if (walker) {
|
|
344
|
+
walker.resume();
|
|
345
|
+
}
|
|
346
|
+
} catch (err) {
|
|
347
|
+
reject(err);
|
|
348
|
+
}
|
|
349
|
+
})();
|
|
350
|
+
})
|
|
351
|
+
.on('error', function (err: Error, item?: {path: string}) {
|
|
352
|
+
log.warn(`Got an error while walking '${item?.path ?? 'unknown'}': ${err.message}`);
|
|
353
|
+
if (isErrnoException(err) && err.code === 'ENOENT') {
|
|
354
|
+
log.warn('All files may not have been accessed');
|
|
355
|
+
reject(err);
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
.on('end', function () {
|
|
359
|
+
(async () => {
|
|
360
|
+
try {
|
|
361
|
+
const file = await lastFileProcessed;
|
|
362
|
+
resolve(file ?? null);
|
|
363
|
+
} catch (err) {
|
|
364
|
+
log.warn(`Unexpected error: ${err instanceof Error ? err.message : err}`);
|
|
365
|
+
reject(err);
|
|
366
|
+
}
|
|
367
|
+
})();
|
|
368
|
+
});
|
|
369
|
+
}).finally(function () {
|
|
370
|
+
log.debug(
|
|
371
|
+
`Traversed ${pluralize('directory', directoryCount, true)} ` +
|
|
372
|
+
`and ${pluralize('file', fileCount, true)} ` +
|
|
373
|
+
`in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`
|
|
374
|
+
);
|
|
375
|
+
if (walker) {
|
|
376
|
+
walker.destroy();
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
/* eslint-enable promise/prefer-await-to-callbacks */
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Reads the closest `package.json` from absolute path `dir`.
|
|
384
|
+
* @throws If there were problems finding or reading `package.json`
|
|
385
|
+
*/
|
|
386
|
+
readPackageJsonFrom(
|
|
387
|
+
dir: string,
|
|
388
|
+
opts: NormalizeOptions & {cwd?: string} = {}
|
|
389
|
+
): NormalizedPackageJson {
|
|
390
|
+
const cwd = fs.findRoot(dir);
|
|
391
|
+
try {
|
|
392
|
+
return readPackageSync({normalize: true, ...opts, cwd});
|
|
393
|
+
} catch (err) {
|
|
394
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
395
|
+
(err as Error).message = `Failed to read a \`package.json\` from dir \`${dir}\`:\n\n${message}`;
|
|
396
|
+
throw err;
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Finds the project root directory from `dir`.
|
|
402
|
+
* @throws TypeError If `dir` is not a non-empty absolute path
|
|
403
|
+
* @throws Error If project root could not be found
|
|
404
|
+
*/
|
|
405
|
+
findRoot(dir: string): string {
|
|
406
|
+
if (!dir || !path.isAbsolute(dir)) {
|
|
407
|
+
throw new TypeError('`findRoot()` must be provided a non-empty, absolute path');
|
|
408
|
+
}
|
|
409
|
+
const result = findRootCached({cwd: dir});
|
|
410
|
+
if (!result) {
|
|
411
|
+
throw new Error(`\`findRoot()\` could not find \`package.json\` from ${dir}`);
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
access: fsPromises.access,
|
|
417
|
+
appendFile: fsPromises.appendFile,
|
|
418
|
+
chmod: fsPromises.chmod,
|
|
419
|
+
close: promisify(close),
|
|
420
|
+
constants,
|
|
421
|
+
createWriteStream,
|
|
422
|
+
createReadStream,
|
|
423
|
+
lstat: fsPromises.lstat,
|
|
424
|
+
/**
|
|
425
|
+
* Promisified fs.open. Resolves with a file descriptor (not FileHandle).
|
|
426
|
+
* Use fs.openFile for a FileHandle.
|
|
427
|
+
*/
|
|
428
|
+
open: promisify(open),
|
|
429
|
+
openFile: fsPromises.open,
|
|
430
|
+
readdir: fsPromises.readdir,
|
|
431
|
+
read: promisify(read),
|
|
432
|
+
readFile: fsPromises.readFile,
|
|
433
|
+
readlink: fsPromises.readlink,
|
|
434
|
+
realpath: fsPromises.realpath,
|
|
435
|
+
rename: fsPromises.rename,
|
|
436
|
+
stat: fsPromises.stat,
|
|
437
|
+
symlink: fsPromises.symlink,
|
|
438
|
+
unlink: fsPromises.unlink,
|
|
439
|
+
// TODO: replace with native promisify in Appium 4
|
|
440
|
+
write: B.promisify(write),
|
|
441
|
+
writeFile: fsPromises.writeFile,
|
|
442
|
+
|
|
443
|
+
/** @deprecated Use `constants.F_OK` instead. */
|
|
444
|
+
F_OK: constants.F_OK,
|
|
445
|
+
/** @deprecated Use `constants.R_OK` instead. */
|
|
446
|
+
R_OK: constants.R_OK,
|
|
447
|
+
/** @deprecated Use `constants.W_OK` instead. */
|
|
448
|
+
W_OK: constants.W_OK,
|
|
449
|
+
/** @deprecated Use `constants.X_OK` instead. */
|
|
450
|
+
X_OK: constants.X_OK,
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
export default fs;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type sharp from 'sharp';
|
|
2
|
+
|
|
3
|
+
let _sharp: typeof sharp | undefined;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @returns The sharp module for image processing
|
|
7
|
+
*/
|
|
8
|
+
export function requireSharp(): typeof sharp {
|
|
9
|
+
if (!_sharp) {
|
|
10
|
+
try {
|
|
11
|
+
_sharp = require('sharp') as typeof sharp;
|
|
12
|
+
} catch (err) {
|
|
13
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Cannot load the 'sharp' module needed for images processing. ` +
|
|
16
|
+
`Consider visiting https://sharp.pixelplumbing.com/install ` +
|
|
17
|
+
`for troubleshooting. Original error: ${message}`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return _sharp;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Crop the image by given rectangle (use base64 string as input and output)
|
|
26
|
+
*
|
|
27
|
+
* @param base64Image The string with base64 encoded image.
|
|
28
|
+
* Supports all image formats natively supported by Sharp library.
|
|
29
|
+
* @param rect The selected region of image
|
|
30
|
+
* @returns base64 encoded string of cropped image
|
|
31
|
+
*/
|
|
32
|
+
export async function cropBase64Image(
|
|
33
|
+
base64Image: string,
|
|
34
|
+
rect: sharp.Region
|
|
35
|
+
): Promise<string> {
|
|
36
|
+
const buf = await requireSharp()(Buffer.from(base64Image, 'base64'))
|
|
37
|
+
.extract(rect)
|
|
38
|
+
.toBuffer();
|
|
39
|
+
return buf.toString('base64');
|
|
40
|
+
}
|
package/lib/index.ts
CHANGED
package/lib/logging.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import globalLog, {
|
|
2
|
+
markSensitive as _markSensitive,
|
|
3
|
+
type Logger,
|
|
4
|
+
} from '@appium/logger';
|
|
5
|
+
import type {
|
|
6
|
+
AppiumLogger,
|
|
7
|
+
AppiumLoggerContext,
|
|
8
|
+
AppiumLoggerLevel,
|
|
9
|
+
AppiumLoggerPrefix,
|
|
10
|
+
} from '@appium/types';
|
|
11
|
+
import _ from 'lodash';
|
|
12
|
+
|
|
13
|
+
export const LEVELS: readonly AppiumLoggerLevel[] = [
|
|
14
|
+
'silly',
|
|
15
|
+
'verbose',
|
|
16
|
+
'debug',
|
|
17
|
+
'info',
|
|
18
|
+
'http',
|
|
19
|
+
'warn',
|
|
20
|
+
'error',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const MAX_LOG_RECORDS_COUNT = 3000;
|
|
24
|
+
|
|
25
|
+
interface GlobalWithNpmlog {
|
|
26
|
+
_global_npmlog?: Logger;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const globalWithNpmlog = globalThis as typeof globalThis & GlobalWithNpmlog;
|
|
30
|
+
|
|
31
|
+
// mock log object is used in testing mode to silence the output
|
|
32
|
+
const MOCK_LOG = {
|
|
33
|
+
unwrap: () => ({
|
|
34
|
+
loadSecureValuesPreprocessingRules: () =>
|
|
35
|
+
Promise.resolve({issues: [], rules: []}),
|
|
36
|
+
level: 'verbose',
|
|
37
|
+
prefix: '',
|
|
38
|
+
log: _.noop,
|
|
39
|
+
}),
|
|
40
|
+
...(_.fromPairs(LEVELS.map((l) => [l, _.noop])) as Record<
|
|
41
|
+
AppiumLoggerLevel,
|
|
42
|
+
(...args: any[]) => void
|
|
43
|
+
>),
|
|
44
|
+
} as unknown as Logger;
|
|
45
|
+
|
|
46
|
+
export const log = getLogger();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param prefix - Optional log prefix
|
|
50
|
+
* @returns A wrapped Appium logger instance
|
|
51
|
+
*/
|
|
52
|
+
export function getLogger(prefix: AppiumLoggerPrefix | null = null): AppiumLogger {
|
|
53
|
+
const {logger, defaultToVerbose} = _getLogger();
|
|
54
|
+
|
|
55
|
+
const wrappedLogger = {
|
|
56
|
+
unwrap: () => logger,
|
|
57
|
+
levels: [...LEVELS],
|
|
58
|
+
prefix: prefix ?? undefined,
|
|
59
|
+
errorWithException(...args: any[]) {
|
|
60
|
+
this.error(...args);
|
|
61
|
+
return _.isError(args[0]) ? args[0] : new Error(args.join('\n'));
|
|
62
|
+
},
|
|
63
|
+
errorAndThrow(...args: any[]) {
|
|
64
|
+
throw this.errorWithException(...args);
|
|
65
|
+
},
|
|
66
|
+
updateAsyncContext(contextInfo: AppiumLoggerContext, replace = false) {
|
|
67
|
+
this.unwrap().updateAsyncStorage?.(contextInfo, replace);
|
|
68
|
+
},
|
|
69
|
+
} as AppiumLogger;
|
|
70
|
+
|
|
71
|
+
Object.defineProperty(wrappedLogger, 'level', {
|
|
72
|
+
get() {
|
|
73
|
+
return logger.level;
|
|
74
|
+
},
|
|
75
|
+
set(newValue: string) {
|
|
76
|
+
logger.level = newValue;
|
|
77
|
+
},
|
|
78
|
+
enumerable: true,
|
|
79
|
+
configurable: true,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const isDebugTimestampLoggingEnabled = process.env._LOG_TIMESTAMP === '1';
|
|
83
|
+
|
|
84
|
+
for (const level of LEVELS) {
|
|
85
|
+
wrappedLogger[level] = function (
|
|
86
|
+
this: typeof wrappedLogger,
|
|
87
|
+
...args: any[]
|
|
88
|
+
) {
|
|
89
|
+
const finalPrefix = getFinalPrefix(
|
|
90
|
+
this.prefix,
|
|
91
|
+
isDebugTimestampLoggingEnabled
|
|
92
|
+
);
|
|
93
|
+
if (args.length) {
|
|
94
|
+
(logger as Record<string, (...a: any[]) => void>)[level](
|
|
95
|
+
finalPrefix,
|
|
96
|
+
...args
|
|
97
|
+
);
|
|
98
|
+
} else {
|
|
99
|
+
(logger as Record<string, (...a: any[]) => void>)[level](
|
|
100
|
+
finalPrefix,
|
|
101
|
+
''
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Default to verbose when the global was not already set (first use, or standalone);
|
|
108
|
+
// main server will override later.
|
|
109
|
+
if (defaultToVerbose) {
|
|
110
|
+
wrappedLogger.level = 'verbose';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return wrappedLogger;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Marks arbitrary log message as sensitive.
|
|
118
|
+
* This message will then be replaced with the default replacer
|
|
119
|
+
* while being logged by any `info`, `debug`, etc. methods if the
|
|
120
|
+
* asyncStorage has `isSensitive` flag enabled in its async context.
|
|
121
|
+
* The latter is enabled by the corresponding HTTP middleware
|
|
122
|
+
* in response to the `X-Appium-Is-Sensitive` request header
|
|
123
|
+
* being set to 'true'.
|
|
124
|
+
*/
|
|
125
|
+
export function markSensitive<T>(logMessage: T): {[k: string]: T} {
|
|
126
|
+
return _markSensitive(logMessage);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function _getLogger(): {logger: Logger; defaultToVerbose: boolean} {
|
|
130
|
+
const testingMode = process.env._TESTING === '1';
|
|
131
|
+
const forceLogMode = process.env._FORCE_LOGS === '1';
|
|
132
|
+
const defaultToVerbose = !globalWithNpmlog._global_npmlog;
|
|
133
|
+
const logger: Logger = testingMode && !forceLogMode
|
|
134
|
+
? MOCK_LOG
|
|
135
|
+
: (globalWithNpmlog._global_npmlog ?? globalLog);
|
|
136
|
+
if (!testingMode && !globalWithNpmlog._global_npmlog && logger === globalLog) {
|
|
137
|
+
globalWithNpmlog._global_npmlog = globalLog;
|
|
138
|
+
logger.maxRecordSize = MAX_LOG_RECORDS_COUNT;
|
|
139
|
+
}
|
|
140
|
+
return {logger, defaultToVerbose};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getFinalPrefix(
|
|
144
|
+
prefix: AppiumLoggerPrefix | null | undefined,
|
|
145
|
+
shouldLogTimestamp = false
|
|
146
|
+
): string {
|
|
147
|
+
const result = (_.isFunction(prefix) ? prefix() : prefix) ?? '';
|
|
148
|
+
if (!shouldLogTimestamp) {
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
const now = new Date();
|
|
152
|
+
const pad = (n: number, z = 2) => String(n).padStart(z, '0');
|
|
153
|
+
const formattedTimestamp = `[${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}:${pad(now.getMilliseconds(), 3)}]`;
|
|
154
|
+
return result ? `${formattedTimestamp} ${result}` : formattedTimestamp;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default log;
|