@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/{node.js → node.ts}
RENAMED
|
@@ -2,58 +2,29 @@ import {isWindows} from './system';
|
|
|
2
2
|
import log from './logger';
|
|
3
3
|
import _ from 'lodash';
|
|
4
4
|
import {exec} from 'teen_process';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import _fs from 'fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import _fs from 'node:fs';
|
|
7
7
|
import {v4 as uuidV4} from 'uuid';
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
STRING: 2,
|
|
11
|
-
BOOLEAN: 4,
|
|
12
|
-
NUMBER: 8,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Internal utility to link global package to local context
|
|
17
|
-
*
|
|
18
|
-
* @param {string} packageName - name of the package to link
|
|
19
|
-
* @throws {Error} If the command fails
|
|
20
|
-
*/
|
|
21
|
-
async function linkGlobalPackage(packageName) {
|
|
22
|
-
try {
|
|
23
|
-
log.debug(`Linking package '${packageName}'`);
|
|
24
|
-
const cmd = isWindows() ? 'npm.cmd' : 'npm';
|
|
25
|
-
await exec(cmd, ['link', packageName], {timeout: 20000});
|
|
26
|
-
} catch (err) {
|
|
27
|
-
const msg = `Unable to load package '${packageName}', linking failed: ${err.message}`;
|
|
28
|
-
log.debug(msg);
|
|
29
|
-
if (err.stderr) {
|
|
30
|
-
// log the stderr if there, but do not add to thrown error as it is
|
|
31
|
-
// _very_ verbose
|
|
32
|
-
log.debug(err.stderr);
|
|
33
|
-
}
|
|
34
|
-
throw new Error(msg);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
9
|
+
const OBJECTS_MAPPING = new WeakMap<object, string>();
|
|
37
10
|
|
|
38
11
|
/**
|
|
39
12
|
* Utility function to extend node functionality, allowing us to require
|
|
40
13
|
* modules that are installed globally. If the package cannot be required,
|
|
41
14
|
* this will attempt to link the package and then re-require it
|
|
42
15
|
*
|
|
43
|
-
* @param
|
|
44
|
-
* @returns
|
|
16
|
+
* @param packageName - the name of the package to be required
|
|
17
|
+
* @returns The package object
|
|
45
18
|
* @throws {Error} If the package is not found locally or globally
|
|
46
19
|
*/
|
|
47
|
-
async function requirePackage(packageName) {
|
|
48
|
-
// first, get it in the normal way (see https://nodejs.org/api/modules.html#modules_all_together)
|
|
20
|
+
export async function requirePackage(packageName: string): Promise<unknown> {
|
|
49
21
|
try {
|
|
50
22
|
log.debug(`Loading local package '${packageName}'`);
|
|
51
23
|
return require(packageName);
|
|
52
24
|
} catch (err) {
|
|
53
|
-
log.debug(`Failed to load local package '${packageName}': ${err.message}`);
|
|
25
|
+
log.debug(`Failed to load local package '${packageName}': ${(err as Error).message}`);
|
|
54
26
|
}
|
|
55
27
|
|
|
56
|
-
// second, get it from where it ought to be in the global node_modules
|
|
57
28
|
try {
|
|
58
29
|
const globalPackageName = path.resolve(
|
|
59
30
|
process.env.npm_config_prefix ?? '',
|
|
@@ -64,112 +35,45 @@ async function requirePackage(packageName) {
|
|
|
64
35
|
log.debug(`Loading global package '${globalPackageName}'`);
|
|
65
36
|
return require(globalPackageName);
|
|
66
37
|
} catch (err) {
|
|
67
|
-
log.debug(`Failed to load global package '${packageName}': ${err.message}`);
|
|
38
|
+
log.debug(`Failed to load global package '${packageName}': ${(err as Error).message}`);
|
|
68
39
|
}
|
|
69
40
|
|
|
70
|
-
// third, link the file and get locally
|
|
71
41
|
try {
|
|
72
42
|
await linkGlobalPackage(packageName);
|
|
73
43
|
log.debug(`Retrying load of linked package '${packageName}'`);
|
|
74
44
|
return require(packageName);
|
|
75
45
|
} catch (err) {
|
|
76
|
-
throw log.errorWithException(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
function extractAllProperties(obj) {
|
|
81
|
-
const stringProperties = [];
|
|
82
|
-
for (const prop in obj) {
|
|
83
|
-
stringProperties.push(prop);
|
|
84
|
-
}
|
|
85
|
-
if (_.isFunction(Object.getOwnPropertySymbols)) {
|
|
86
|
-
stringProperties.push(...Object.getOwnPropertySymbols(obj));
|
|
87
|
-
}
|
|
88
|
-
return stringProperties;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function _getSizeOfObject(seen, object) {
|
|
92
|
-
if (_.isNil(object)) {
|
|
93
|
-
return 0;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let bytes = 0;
|
|
97
|
-
const properties = extractAllProperties(object);
|
|
98
|
-
for (const key of properties) {
|
|
99
|
-
// Do not recalculate circular references
|
|
100
|
-
if (typeof object[key] === 'object' && !_.isNil(object[key])) {
|
|
101
|
-
if (seen.has(object[key])) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
seen.add(object[key]);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
bytes += getCalculator(seen)(key);
|
|
108
|
-
try {
|
|
109
|
-
bytes += getCalculator(seen)(object[key]);
|
|
110
|
-
} catch (ex) {
|
|
111
|
-
if (ex instanceof RangeError) {
|
|
112
|
-
// circular reference detected, final result might be incorrect
|
|
113
|
-
// let's be nice and not throw an exception
|
|
114
|
-
bytes = 0;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
46
|
+
throw log.errorWithException(
|
|
47
|
+
`Unable to load package '${packageName}': ${(err as Error).message}`
|
|
48
|
+
);
|
|
117
49
|
}
|
|
118
|
-
|
|
119
|
-
return bytes;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function getCalculator(seen) {
|
|
123
|
-
return function calculator(obj) {
|
|
124
|
-
if (_.isBuffer(obj)) {
|
|
125
|
-
return obj.length;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
switch (typeof obj) {
|
|
129
|
-
case 'string':
|
|
130
|
-
return obj.length * ECMA_SIZES.STRING;
|
|
131
|
-
case 'boolean':
|
|
132
|
-
return ECMA_SIZES.BOOLEAN;
|
|
133
|
-
case 'number':
|
|
134
|
-
return ECMA_SIZES.NUMBER;
|
|
135
|
-
case 'symbol':
|
|
136
|
-
return _.isFunction(Symbol.keyFor) && Symbol.keyFor(obj)
|
|
137
|
-
? /** @type {string} */ (Symbol.keyFor(obj)).length * ECMA_SIZES.STRING
|
|
138
|
-
: (obj.toString().length - 8) * ECMA_SIZES.STRING;
|
|
139
|
-
case 'object':
|
|
140
|
-
return _.isArray(obj)
|
|
141
|
-
? obj.map(getCalculator(seen)).reduce((acc, curr) => acc + curr, 0)
|
|
142
|
-
: _getSizeOfObject(seen, obj);
|
|
143
|
-
default:
|
|
144
|
-
return 0;
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
50
|
}
|
|
148
51
|
|
|
149
52
|
/**
|
|
150
53
|
* Calculate the in-depth size in memory of the provided object.
|
|
151
54
|
* The original implementation is borrowed from https://github.com/miktam/sizeof.
|
|
152
55
|
*
|
|
153
|
-
* @param
|
|
154
|
-
* @returns
|
|
56
|
+
* @param obj - An object whose size should be calculated
|
|
57
|
+
* @returns Object size in bytes.
|
|
155
58
|
*/
|
|
156
|
-
function getObjectSize(obj) {
|
|
59
|
+
export function getObjectSize(obj: unknown): number {
|
|
157
60
|
return getCalculator(new WeakSet())(obj);
|
|
158
61
|
}
|
|
159
62
|
|
|
160
|
-
const OBJECTS_MAPPING = new WeakMap();
|
|
161
|
-
|
|
162
63
|
/**
|
|
163
64
|
* Calculates a unique object identifier
|
|
164
65
|
*
|
|
165
|
-
* @param
|
|
166
|
-
* @returns
|
|
66
|
+
* @param object - Any valid ECMA object
|
|
67
|
+
* @returns A uuidV4 string that uniquely identifies given object
|
|
167
68
|
*/
|
|
168
|
-
function getObjectId(object) {
|
|
169
|
-
|
|
170
|
-
|
|
69
|
+
export function getObjectId(object: object): string {
|
|
70
|
+
const existing = OBJECTS_MAPPING.get(object);
|
|
71
|
+
if (existing !== undefined) {
|
|
72
|
+
return existing;
|
|
171
73
|
}
|
|
172
|
-
|
|
74
|
+
const id = uuidV4();
|
|
75
|
+
OBJECTS_MAPPING.set(object, id);
|
|
76
|
+
return id;
|
|
173
77
|
}
|
|
174
78
|
|
|
175
79
|
/**
|
|
@@ -181,50 +85,147 @@ function getObjectId(object) {
|
|
|
181
85
|
* ! This function changes the given object,
|
|
182
86
|
* so it becomes immutable.
|
|
183
87
|
*
|
|
184
|
-
* @param
|
|
185
|
-
* @returns
|
|
186
|
-
* function after it was made immutable.
|
|
88
|
+
* @param object - Any valid ECMA object
|
|
89
|
+
* @returns The same object that was passed after it was made immutable.
|
|
187
90
|
*/
|
|
188
|
-
function deepFreeze(object) {
|
|
189
|
-
let propNames;
|
|
91
|
+
export function deepFreeze<T>(object: T): T {
|
|
92
|
+
let propNames: string[];
|
|
190
93
|
try {
|
|
191
|
-
propNames = Object.getOwnPropertyNames(object);
|
|
94
|
+
propNames = Object.getOwnPropertyNames(object as object);
|
|
192
95
|
} catch {
|
|
193
96
|
return object;
|
|
194
97
|
}
|
|
195
98
|
for (const name of propNames) {
|
|
196
|
-
const value = object[name];
|
|
99
|
+
const value = (object as Record<string, unknown>)[name];
|
|
197
100
|
if (value && typeof value === 'object') {
|
|
198
101
|
deepFreeze(value);
|
|
199
102
|
}
|
|
200
103
|
}
|
|
201
|
-
return Object.freeze(object);
|
|
104
|
+
return Object.freeze(object) as T;
|
|
202
105
|
}
|
|
203
106
|
|
|
204
107
|
/**
|
|
205
108
|
* Tries to synchronously detect the absolute path to the folder
|
|
206
109
|
* where the given `moduleName` is located.
|
|
207
110
|
*
|
|
208
|
-
* @param
|
|
209
|
-
* @param
|
|
111
|
+
* @param moduleName - The name of the module as it is written in package.json
|
|
112
|
+
* @param filePath - Full path to any of files that `moduleName` contains. Use
|
|
210
113
|
* `__filename` to find the root of the module where this helper is called.
|
|
211
|
-
* @returns
|
|
114
|
+
* @returns Full path to the module root, or null if not found
|
|
212
115
|
*/
|
|
213
|
-
function getModuleRootSync
|
|
116
|
+
export function getModuleRootSync(moduleName: string, filePath: string): string | null {
|
|
214
117
|
let currentDir = path.dirname(path.resolve(filePath));
|
|
215
118
|
let isAtFsRoot = false;
|
|
216
119
|
while (!isAtFsRoot) {
|
|
217
120
|
const manifestPath = path.join(currentDir, 'package.json');
|
|
218
121
|
try {
|
|
219
|
-
if (
|
|
220
|
-
|
|
122
|
+
if (
|
|
123
|
+
_fs.existsSync(manifestPath) &&
|
|
124
|
+
(JSON.parse(_fs.readFileSync(manifestPath, 'utf8')) as {name?: string}).name === moduleName
|
|
125
|
+
) {
|
|
221
126
|
return currentDir;
|
|
222
127
|
}
|
|
223
|
-
} catch {
|
|
128
|
+
} catch {
|
|
129
|
+
// ignore
|
|
130
|
+
}
|
|
224
131
|
currentDir = path.dirname(currentDir);
|
|
225
132
|
isAtFsRoot = currentDir.length <= path.dirname(currentDir).length;
|
|
226
133
|
}
|
|
227
134
|
return null;
|
|
228
135
|
}
|
|
229
136
|
|
|
230
|
-
|
|
137
|
+
// #region Private
|
|
138
|
+
|
|
139
|
+
const ECMA_SIZES = Object.freeze({
|
|
140
|
+
STRING: 2,
|
|
141
|
+
BOOLEAN: 4,
|
|
142
|
+
NUMBER: 8,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
type SizeCalculator = (obj: unknown) => number;
|
|
146
|
+
|
|
147
|
+
async function linkGlobalPackage(packageName: string): Promise<void> {
|
|
148
|
+
try {
|
|
149
|
+
log.debug(`Linking package '${packageName}'`);
|
|
150
|
+
const cmd = isWindows() ? 'npm.cmd' : 'npm';
|
|
151
|
+
await exec(cmd, ['link', packageName], {timeout: 20000});
|
|
152
|
+
} catch (err) {
|
|
153
|
+
const e = err as Error & {stderr?: string};
|
|
154
|
+
const msg = `Unable to load package '${packageName}', linking failed: ${e.message}`;
|
|
155
|
+
log.debug(msg);
|
|
156
|
+
if (e.stderr) {
|
|
157
|
+
log.debug(e.stderr);
|
|
158
|
+
}
|
|
159
|
+
throw new Error(msg);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function extractAllProperties(obj: object): (string | symbol)[] {
|
|
164
|
+
const stringProperties: (string | symbol)[] = [];
|
|
165
|
+
for (const prop in obj) {
|
|
166
|
+
stringProperties.push(prop);
|
|
167
|
+
}
|
|
168
|
+
if (_.isFunction(Object.getOwnPropertySymbols)) {
|
|
169
|
+
stringProperties.push(...Object.getOwnPropertySymbols(obj));
|
|
170
|
+
}
|
|
171
|
+
return stringProperties;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function _getSizeOfObject(seen: WeakSet<object>, object: object): number {
|
|
175
|
+
if (_.isNil(object)) {
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let bytes = 0;
|
|
180
|
+
const properties = extractAllProperties(object);
|
|
181
|
+
const calc = getCalculator(seen);
|
|
182
|
+
for (const key of properties) {
|
|
183
|
+
const val = (object as Record<string | symbol, unknown>)[key];
|
|
184
|
+
if (typeof val === 'object' && !_.isNil(val)) {
|
|
185
|
+
if (seen.has(val as object)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
seen.add(val as object);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
bytes += calc(key);
|
|
192
|
+
try {
|
|
193
|
+
bytes += calc(val);
|
|
194
|
+
} catch (ex) {
|
|
195
|
+
if (ex instanceof RangeError) {
|
|
196
|
+
bytes = 0;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return bytes;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getCalculator(seen: WeakSet<object>): SizeCalculator {
|
|
205
|
+
return function calculator(obj: unknown): number {
|
|
206
|
+
if (_.isBuffer(obj)) {
|
|
207
|
+
return (obj as Buffer).length;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
switch (typeof obj) {
|
|
211
|
+
case 'string':
|
|
212
|
+
return obj.length * ECMA_SIZES.STRING;
|
|
213
|
+
case 'boolean':
|
|
214
|
+
return ECMA_SIZES.BOOLEAN;
|
|
215
|
+
case 'number':
|
|
216
|
+
return ECMA_SIZES.NUMBER;
|
|
217
|
+
case 'symbol':
|
|
218
|
+
return _.isFunction(Symbol.keyFor) && Symbol.keyFor(obj)
|
|
219
|
+
? (Symbol.keyFor(obj) as string).length * ECMA_SIZES.STRING
|
|
220
|
+
: (obj.toString().length - 8) * ECMA_SIZES.STRING;
|
|
221
|
+
case 'object':
|
|
222
|
+
return _.isArray(obj)
|
|
223
|
+
? obj.map(getCalculator(seen)).reduce((acc, curr) => acc + curr, 0)
|
|
224
|
+
: _getSizeOfObject(seen, obj as object);
|
|
225
|
+
default:
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// #endregion
|
package/lib/npm.ts
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import * as semver from 'semver';
|
|
3
|
+
import type {PackageJson} from 'type-fest';
|
|
4
|
+
import type {StringRecord} from '@appium/types';
|
|
5
|
+
import {hasAppiumDependency} from './env';
|
|
6
|
+
import {exec} from 'teen_process';
|
|
7
|
+
import type {ExecError, TeenProcessExecOptions} from 'teen_process';
|
|
8
|
+
import {fs} from './fs';
|
|
9
|
+
import * as util from './util';
|
|
10
|
+
import * as system from './system';
|
|
11
|
+
import resolveFrom from 'resolve-from';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Relative path to directory containing any Appium internal files
|
|
15
|
+
* XXX: this is duplicated in `appium/lib/constants.js`.
|
|
16
|
+
*/
|
|
17
|
+
export const CACHE_DIR_RELATIVE_PATH = path.join('node_modules', '.cache', 'appium');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Relative path to lockfile used when installing an extension via `appium`
|
|
21
|
+
*/
|
|
22
|
+
export const INSTALL_LOCKFILE_RELATIVE_PATH = path.join(CACHE_DIR_RELATIVE_PATH, '.install.lock');
|
|
23
|
+
|
|
24
|
+
/** Options for {@link NPM.exec} */
|
|
25
|
+
export interface ExecOpts {
|
|
26
|
+
/** Current working directory */
|
|
27
|
+
cwd: string;
|
|
28
|
+
/** If true, supply `--json` flag to npm and resolve w/ parsed JSON */
|
|
29
|
+
json?: boolean;
|
|
30
|
+
/** Path to lockfile to use */
|
|
31
|
+
lockFile?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Options for {@link NPM.installPackage} */
|
|
35
|
+
export interface InstallPackageOpts {
|
|
36
|
+
/** Name of the package to install */
|
|
37
|
+
pkgName: string;
|
|
38
|
+
/** Whether to install from a local path or from npm */
|
|
39
|
+
installType?: 'local' | string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Result of {@link NPM.installPackage} */
|
|
43
|
+
export interface NpmInstallReceipt {
|
|
44
|
+
/** Path to installed package */
|
|
45
|
+
installPath: string;
|
|
46
|
+
/** Package data */
|
|
47
|
+
pkg: PackageJson;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface NpmExecResult {
|
|
51
|
+
stdout: string;
|
|
52
|
+
stderr: string;
|
|
53
|
+
code: number | null;
|
|
54
|
+
json?: unknown;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* XXX: This should probably be a singleton, but it isn't. Maybe this module should just export functions?
|
|
59
|
+
*/
|
|
60
|
+
export class NPM {
|
|
61
|
+
/**
|
|
62
|
+
* Execute `npm` with given args.
|
|
63
|
+
*
|
|
64
|
+
* If the process exits with a nonzero code, the contents of `STDOUT` and `STDERR` will be in the
|
|
65
|
+
* `message` of any rejected error.
|
|
66
|
+
*/
|
|
67
|
+
async exec(
|
|
68
|
+
cmd: string,
|
|
69
|
+
args: string[],
|
|
70
|
+
opts: ExecOpts,
|
|
71
|
+
execOpts: Omit<TeenProcessExecOptions, 'cwd'> = {}
|
|
72
|
+
): Promise<NpmExecResult> {
|
|
73
|
+
const {cwd, json, lockFile} = opts;
|
|
74
|
+
|
|
75
|
+
const teenProcessExecOpts: TeenProcessExecOptions = {
|
|
76
|
+
...execOpts,
|
|
77
|
+
shell: system.isWindows() || execOpts.shell,
|
|
78
|
+
cwd,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const argsCopy = [cmd, ...args];
|
|
82
|
+
if (json) {
|
|
83
|
+
argsCopy.push('--json');
|
|
84
|
+
}
|
|
85
|
+
const npmCmd = system.isWindows() ? 'npm.cmd' : 'npm';
|
|
86
|
+
type ExecRunnerResult = {stdout: string; stderr: string; code: number | null};
|
|
87
|
+
let runner = async (): Promise<ExecRunnerResult> =>
|
|
88
|
+
await exec(npmCmd, argsCopy, teenProcessExecOpts);
|
|
89
|
+
if (lockFile) {
|
|
90
|
+
const acquireLock = util.getLockFileGuard(lockFile);
|
|
91
|
+
const _runner = runner;
|
|
92
|
+
runner = async () => (await acquireLock(_runner)) as ExecRunnerResult;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let ret: NpmExecResult;
|
|
96
|
+
try {
|
|
97
|
+
const {stdout, stderr, code} = await runner();
|
|
98
|
+
ret = {stdout, stderr, code};
|
|
99
|
+
try {
|
|
100
|
+
ret.json = JSON.parse(stdout);
|
|
101
|
+
} catch {
|
|
102
|
+
// ignore
|
|
103
|
+
}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
const {stdout = '', stderr = '', code = null} = e as ExecError;
|
|
106
|
+
throw new Error(
|
|
107
|
+
`npm command '${argsCopy.join(' ')}' failed with code ${code}.\n\nSTDOUT:\n${stdout.trim()}\n\nSTDERR:\n${stderr.trim()}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return ret;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Gets the latest published version of a package from npm registry.
|
|
115
|
+
*
|
|
116
|
+
* @param cwd - Current working directory for npm command
|
|
117
|
+
* @param pkg - Package name to query
|
|
118
|
+
* @returns Latest version string, or `null` if package not found (e.g. 404)
|
|
119
|
+
*/
|
|
120
|
+
async getLatestVersion(cwd: string, pkg: string): Promise<string | null> {
|
|
121
|
+
try {
|
|
122
|
+
const result = await this.exec('view', [pkg, 'dist-tags'], {json: true, cwd});
|
|
123
|
+
const json = result.json as {latest?: string} | undefined;
|
|
124
|
+
return json?.latest ?? null;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (!(err instanceof Error) || !err.message.includes('E404')) {
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Gets the latest version of a package that is a safe upgrade from the current version
|
|
135
|
+
* (same major, no prereleases). Fetches versions from npm and delegates to
|
|
136
|
+
* {@link NPM.getLatestSafeUpgradeFromVersions}.
|
|
137
|
+
*
|
|
138
|
+
* @param cwd - Current working directory for npm command
|
|
139
|
+
* @param pkg - Package name to query
|
|
140
|
+
* @param curVersion - Current installed version
|
|
141
|
+
* @returns Latest safe upgrade version string, or `null` if none or package not found
|
|
142
|
+
*/
|
|
143
|
+
async getLatestSafeUpgradeVersion(
|
|
144
|
+
cwd: string,
|
|
145
|
+
pkg: string,
|
|
146
|
+
curVersion: string
|
|
147
|
+
): Promise<string | null> {
|
|
148
|
+
try {
|
|
149
|
+
const result = await this.exec('view', [pkg, 'versions'], {json: true, cwd});
|
|
150
|
+
const allVersions = result.json;
|
|
151
|
+
if (!Array.isArray(allVersions)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return this.getLatestSafeUpgradeFromVersions(curVersion, allVersions as string[]);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (!(err instanceof Error) || !err.message.includes('E404')) {
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Runs `npm ls`, optionally for a particular package.
|
|
165
|
+
*/
|
|
166
|
+
async list(cwd: string, pkg?: string): Promise<unknown> {
|
|
167
|
+
return (await this.exec('list', pkg ? [pkg] : [], {cwd, json: true})).json;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Given a current version and a list of all versions for a package, return the version which is
|
|
172
|
+
* the highest safely-upgradable version (meaning not crossing any major revision boundaries, and
|
|
173
|
+
* not including any alpha/beta/rc versions)
|
|
174
|
+
*/
|
|
175
|
+
getLatestSafeUpgradeFromVersions(
|
|
176
|
+
curVersion: string,
|
|
177
|
+
allVersions: string[]
|
|
178
|
+
): string | null {
|
|
179
|
+
let safeUpgradeVer: semver.SemVer | null = null;
|
|
180
|
+
const curSemver = semver.parse(curVersion) ?? semver.parse(semver.coerce(curVersion));
|
|
181
|
+
if (curSemver === null) {
|
|
182
|
+
throw new Error(`Could not parse current version '${curVersion}'`);
|
|
183
|
+
}
|
|
184
|
+
for (const testVer of allVersions) {
|
|
185
|
+
const testSemver = semver.parse(testVer) ?? semver.parse(semver.coerce(testVer));
|
|
186
|
+
if (
|
|
187
|
+
testSemver === null ||
|
|
188
|
+
testSemver.prerelease.length > 0 ||
|
|
189
|
+
curSemver.compare(testSemver) === 1 ||
|
|
190
|
+
testSemver.major > curSemver.major
|
|
191
|
+
) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (safeUpgradeVer === null || testSemver.compare(safeUpgradeVer) === 1) {
|
|
195
|
+
safeUpgradeVer = testSemver;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return safeUpgradeVer ? safeUpgradeVer.format() : null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Installs a package w/ `npm`
|
|
203
|
+
*/
|
|
204
|
+
async installPackage(
|
|
205
|
+
cwd: string,
|
|
206
|
+
installStr: string,
|
|
207
|
+
opts: InstallPackageOpts
|
|
208
|
+
): Promise<NpmInstallReceipt> {
|
|
209
|
+
const {pkgName, installType} = opts;
|
|
210
|
+
let dummyPkgJson: Record<string, unknown>;
|
|
211
|
+
const dummyPkgPath = path.join(cwd, 'package.json');
|
|
212
|
+
try {
|
|
213
|
+
dummyPkgJson = JSON.parse(await fs.readFile(dummyPkgPath, 'utf8')) as Record<string, unknown>;
|
|
214
|
+
} catch (err) {
|
|
215
|
+
const nodeErr = err as NodeJS.ErrnoException;
|
|
216
|
+
if (nodeErr.code === 'ENOENT') {
|
|
217
|
+
dummyPkgJson = {};
|
|
218
|
+
await fs.writeFile(dummyPkgPath, JSON.stringify(dummyPkgJson, null, 2), 'utf8');
|
|
219
|
+
} else {
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const installOpts = ['--save-dev', '--no-progress', '--no-audit'];
|
|
225
|
+
if (!(await hasAppiumDependency(cwd))) {
|
|
226
|
+
if (process.env.APPIUM_OMIT_PEER_DEPS) {
|
|
227
|
+
installOpts.push('--omit=peer');
|
|
228
|
+
}
|
|
229
|
+
installOpts.push('--save-exact', '--global-style', '--no-package-lock');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const cmd = installType === 'local' ? 'link' : 'install';
|
|
233
|
+
const res = await this.exec(cmd, [...installOpts, installStr], {
|
|
234
|
+
cwd,
|
|
235
|
+
json: true,
|
|
236
|
+
lockFile: this._getInstallLockfilePath(cwd),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (res.json && typeof res.json === 'object' && 'error' in res.json && res.json.error) {
|
|
240
|
+
throw new Error(String((res.json as {error: unknown}).error));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const pkgJsonPath = resolveFrom(cwd, `${pkgName}/package.json`);
|
|
244
|
+
try {
|
|
245
|
+
const pkgJson = await fs.readFile(pkgJsonPath, 'utf8');
|
|
246
|
+
const pkg = JSON.parse(pkgJson) as PackageJson;
|
|
247
|
+
return {installPath: path.dirname(pkgJsonPath), pkg};
|
|
248
|
+
} catch {
|
|
249
|
+
throw new Error(
|
|
250
|
+
'The package was not downloaded correctly; its package.json ' +
|
|
251
|
+
'did not exist or was unreadable. We looked for it at ' +
|
|
252
|
+
pkgJsonPath
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Uninstalls a package with `npm uninstall`.
|
|
259
|
+
*
|
|
260
|
+
* @param cwd - Current working directory (project root)
|
|
261
|
+
* @param pkg - Package name to uninstall
|
|
262
|
+
*/
|
|
263
|
+
async uninstallPackage(cwd: string, pkg: string): Promise<void> {
|
|
264
|
+
await this.exec('uninstall', [pkg], {
|
|
265
|
+
cwd,
|
|
266
|
+
lockFile: this._getInstallLockfilePath(cwd),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Fetches package metadata from the npm registry via `npm info`.
|
|
272
|
+
*
|
|
273
|
+
* @param pkg - Npm package spec to query
|
|
274
|
+
* @param entries - Field names to be included into the resulting output. By default all fields are included.
|
|
275
|
+
* @returns Package metadata as a record of string values
|
|
276
|
+
*/
|
|
277
|
+
async getPackageInfo(pkg: string, entries: string[] = []): Promise<StringRecord> {
|
|
278
|
+
const result = await this.exec('info', [pkg, ...entries], {
|
|
279
|
+
cwd: process.cwd(),
|
|
280
|
+
json: true,
|
|
281
|
+
});
|
|
282
|
+
return (result.json ?? {}) as StringRecord;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Returns path to "install" lockfile */
|
|
286
|
+
private _getInstallLockfilePath(cwd: string): string {
|
|
287
|
+
return path.join(cwd, INSTALL_LOCKFILE_RELATIVE_PATH);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export const npm = new NPM();
|