appium-webdriveragent 14.2.0 → 15.0.0
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/CHANGELOG.md +17 -0
- package/WebDriverAgentLib/Info.plist +2 -2
- package/build/lib/types.d.ts +44 -4
- package/build/lib/types.d.ts.map +1 -1
- package/build/lib/utils/index.d.ts +11 -0
- package/build/lib/utils/index.d.ts.map +1 -0
- package/build/lib/utils/index.js +27 -0
- package/build/lib/utils/index.js.map +1 -0
- package/build/lib/utils/module.d.ts +6 -0
- package/build/lib/utils/module.d.ts.map +1 -0
- package/build/lib/utils/module.js +31 -0
- package/build/lib/utils/module.js.map +1 -0
- package/build/lib/utils/platform.d.ts +7 -0
- package/build/lib/utils/platform.d.ts.map +1 -0
- package/build/lib/utils/platform.js +13 -0
- package/build/lib/utils/platform.js.map +1 -0
- package/build/lib/utils/processes.d.ts +23 -0
- package/build/lib/utils/processes.d.ts.map +1 -0
- package/build/lib/utils/processes.js +132 -0
- package/build/lib/utils/processes.js.map +1 -0
- package/build/lib/utils/security.d.ts +5 -0
- package/build/lib/utils/security.d.ts.map +1 -0
- package/build/lib/utils/security.js +15 -0
- package/build/lib/utils/security.js.map +1 -0
- package/build/lib/utils/xctestrun.d.ts +51 -0
- package/build/lib/utils/xctestrun.d.ts.map +1 -0
- package/build/lib/utils/xctestrun.js +120 -0
- package/build/lib/utils/xctestrun.js.map +1 -0
- package/build/lib/wda-strategies.d.ts +69 -0
- package/build/lib/wda-strategies.d.ts.map +1 -0
- package/build/lib/wda-strategies.js +253 -0
- package/build/lib/wda-strategies.js.map +1 -0
- package/build/lib/webdriveragent.d.ts +3 -14
- package/build/lib/webdriveragent.d.ts.map +1 -1
- package/build/lib/webdriveragent.js +78 -127
- package/build/lib/webdriveragent.js.map +1 -1
- package/build/lib/xcodebuild.d.ts.map +1 -1
- package/build/lib/xcodebuild.js +29 -7
- package/build/lib/xcodebuild.js.map +1 -1
- package/lib/types.ts +58 -4
- package/lib/utils/index.ts +20 -0
- package/lib/utils/module.ts +29 -0
- package/lib/utils/platform.ts +10 -0
- package/lib/utils/processes.ts +135 -0
- package/lib/utils/security.ts +15 -0
- package/lib/utils/xctestrun.ts +160 -0
- package/lib/wda-strategies.ts +340 -0
- package/lib/webdriveragent.ts +89 -163
- package/lib/xcodebuild.ts +34 -17
- package/package.json +3 -4
- package/build/lib/utils.d.ts +0 -105
- package/build/lib/utils.d.ts.map +0 -1
- package/build/lib/utils.js +0 -413
- package/build/lib/utils.js.map +0 -1
- package/lib/utils.ts +0 -442
package/lib/utils.ts
DELETED
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
import {fs, plist} from '@appium/support';
|
|
2
|
-
import {exec} from 'teen_process';
|
|
3
|
-
import type {SubProcess} from 'teen_process';
|
|
4
|
-
import path, {dirname} from 'node:path';
|
|
5
|
-
import {fileURLToPath} from 'node:url';
|
|
6
|
-
import {log} from './logger';
|
|
7
|
-
import {PLATFORM_NAME_TVOS} from './constants';
|
|
8
|
-
import _fs from 'node:fs';
|
|
9
|
-
import {waitForCondition} from 'asyncbox';
|
|
10
|
-
import {arch} from 'node:os';
|
|
11
|
-
import type {DeviceInfo} from './types';
|
|
12
|
-
|
|
13
|
-
// Get current filename - works in both CommonJS and ESM
|
|
14
|
-
const currentFilename =
|
|
15
|
-
typeof __filename !== 'undefined'
|
|
16
|
-
? __filename
|
|
17
|
-
: fileURLToPath(new Function('return import.meta.url')());
|
|
18
|
-
|
|
19
|
-
const currentDirname = dirname(currentFilename);
|
|
20
|
-
|
|
21
|
-
let moduleRootCache: string | undefined;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Calculates the path to the current module's root folder
|
|
25
|
-
*
|
|
26
|
-
* @returns {string} The full path to module root
|
|
27
|
-
* @throws {Error} If the current module root folder cannot be determined
|
|
28
|
-
*/
|
|
29
|
-
const getModuleRoot = function getModuleRoot(): string {
|
|
30
|
-
if (moduleRootCache) {
|
|
31
|
-
return moduleRootCache;
|
|
32
|
-
}
|
|
33
|
-
let currentDir = currentDirname;
|
|
34
|
-
let isAtFsRoot = false;
|
|
35
|
-
while (!isAtFsRoot) {
|
|
36
|
-
const manifestPath = path.join(currentDir, 'package.json');
|
|
37
|
-
try {
|
|
38
|
-
if (
|
|
39
|
-
_fs.existsSync(manifestPath) &&
|
|
40
|
-
JSON.parse(_fs.readFileSync(manifestPath, 'utf8')).name === 'appium-webdriveragent'
|
|
41
|
-
) {
|
|
42
|
-
moduleRootCache = currentDir;
|
|
43
|
-
return currentDir;
|
|
44
|
-
}
|
|
45
|
-
} catch {}
|
|
46
|
-
currentDir = path.dirname(currentDir);
|
|
47
|
-
isAtFsRoot = currentDir.length <= path.dirname(currentDir).length;
|
|
48
|
-
}
|
|
49
|
-
throw new Error('Cannot find the root folder of the appium-webdriveragent Node.js module');
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const BOOTSTRAP_PATH = getModuleRoot();
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Arguments for setting xctestrun file
|
|
56
|
-
*/
|
|
57
|
-
export interface XctestrunFileArgs {
|
|
58
|
-
deviceInfo: DeviceInfo;
|
|
59
|
-
sdkVersion: string;
|
|
60
|
-
bootstrapPath: string;
|
|
61
|
-
wdaRemotePort: number | string;
|
|
62
|
-
wdaBindingIP?: string;
|
|
63
|
-
maxHttpRequestBodySize?: number | string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Find and terminate all processes matching the given pgrep pattern.
|
|
68
|
-
*/
|
|
69
|
-
export async function killAppUsingPattern(pgrepPattern: string): Promise<void> {
|
|
70
|
-
const signals = [2, 15, 9];
|
|
71
|
-
for (const signal of signals) {
|
|
72
|
-
const matchedPids = await getPIDsUsingPattern(pgrepPattern);
|
|
73
|
-
if (matchedPids.length === 0) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const args = [`-${signal}`, ...matchedPids];
|
|
77
|
-
try {
|
|
78
|
-
await exec('kill', args);
|
|
79
|
-
} catch (err: any) {
|
|
80
|
-
log.debug(`kill ${args.join(' ')} -> ${err.message}`);
|
|
81
|
-
}
|
|
82
|
-
if (signal === signals[signals.length - 1]) {
|
|
83
|
-
// there is no need to wait after SIGKILL
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
await waitForCondition(
|
|
88
|
-
async () => {
|
|
89
|
-
const pidCheckPromises = matchedPids.map(async (pid) => {
|
|
90
|
-
try {
|
|
91
|
-
await exec('kill', ['-0', pid]);
|
|
92
|
-
// the process is still alive
|
|
93
|
-
return false;
|
|
94
|
-
} catch {
|
|
95
|
-
// the process is dead
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
return (await Promise.all(pidCheckPromises)).every((x) => x === true);
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
waitMs: 1000,
|
|
103
|
-
intervalMs: 100,
|
|
104
|
-
},
|
|
105
|
-
);
|
|
106
|
-
return;
|
|
107
|
-
} catch {
|
|
108
|
-
// try the next signal
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Return true if the platformName is tvOS
|
|
115
|
-
* @param platformName The name of the platorm
|
|
116
|
-
* @returns Return true if the platformName is tvOS
|
|
117
|
-
*/
|
|
118
|
-
export function isTvOS(platformName: string): boolean {
|
|
119
|
-
return platformName?.toLowerCase() === PLATFORM_NAME_TVOS.toLowerCase();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Configure keychain access required for real-device code signing.
|
|
124
|
-
*/
|
|
125
|
-
export async function setRealDeviceSecurity(
|
|
126
|
-
keychainPath: string,
|
|
127
|
-
keychainPassword: string,
|
|
128
|
-
): Promise<void> {
|
|
129
|
-
log.debug('Setting security for iOS device');
|
|
130
|
-
await exec('security', ['-v', 'list-keychains', '-s', keychainPath]);
|
|
131
|
-
await exec('security', ['-v', 'unlock-keychain', '-p', keychainPassword, keychainPath]);
|
|
132
|
-
await exec('security', ['set-keychain-settings', '-t', '3600', '-l', keychainPath]);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Creates xctestrun file per device & platform version.
|
|
137
|
-
* We expects to have WebDriverAgentRunner_iphoneos${sdkVersion|platformVersion}-arm64.xctestrun for real device
|
|
138
|
-
* and WebDriverAgentRunner_iphonesimulator${sdkVersion|platformVersion}-${x86_64|arm64}.xctestrun for simulator located @bootstrapPath
|
|
139
|
-
* Newer Xcode (Xcode 10.0 at least) generate xctestrun file following sdkVersion.
|
|
140
|
-
* e.g. Xcode which has iOS SDK Version 12.2 on an intel Mac host machine generates WebDriverAgentRunner_iphonesimulator.2-x86_64.xctestrun
|
|
141
|
-
* even if the cap has platform version 11.4
|
|
142
|
-
*
|
|
143
|
-
* @param args
|
|
144
|
-
* @return returns xctestrunFilePath for given device
|
|
145
|
-
* @throws if WebDriverAgentRunner_iphoneos${sdkVersion|platformVersion}-arm64.xctestrun for real device
|
|
146
|
-
* or WebDriverAgentRunner_iphonesimulator${sdkVersion|platformVersion}-x86_64.xctestrun for simulator is not found @bootstrapPath,
|
|
147
|
-
* then it will throw a file not found exception
|
|
148
|
-
*/
|
|
149
|
-
export async function setXctestrunFile(args: XctestrunFileArgs): Promise<string> {
|
|
150
|
-
const {
|
|
151
|
-
deviceInfo,
|
|
152
|
-
sdkVersion,
|
|
153
|
-
bootstrapPath,
|
|
154
|
-
wdaRemotePort,
|
|
155
|
-
wdaBindingIP,
|
|
156
|
-
maxHttpRequestBodySize,
|
|
157
|
-
} = args;
|
|
158
|
-
const xctestrunFilePath = await getXctestrunFilePath(deviceInfo, sdkVersion, bootstrapPath);
|
|
159
|
-
const xctestRunContent = await plist.parsePlistFile(xctestrunFilePath);
|
|
160
|
-
const updateWDAPort = getAdditionalRunContent(
|
|
161
|
-
deviceInfo.platformName,
|
|
162
|
-
wdaRemotePort,
|
|
163
|
-
wdaBindingIP,
|
|
164
|
-
maxHttpRequestBodySize,
|
|
165
|
-
);
|
|
166
|
-
const newXctestRunContent = mergeObjects(xctestRunContent, updateWDAPort);
|
|
167
|
-
await plist.updatePlistFile(xctestrunFilePath, newXctestRunContent, true);
|
|
168
|
-
|
|
169
|
-
return xctestrunFilePath;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Return the WDA object which appends existing xctest runner content
|
|
174
|
-
* @param platformName - The name of the platform
|
|
175
|
-
* @param wdaRemotePort - The remote port number
|
|
176
|
-
* @param wdaBindingIP - The IP address to bind to. If not given, it binds to all interfaces.
|
|
177
|
-
* @param maxHttpRequestBodySize - The maximum HTTP request body size in bytes.
|
|
178
|
-
* @return returns a runner object which has USE_PORT and optionally USE_IP
|
|
179
|
-
*/
|
|
180
|
-
export function getAdditionalRunContent(
|
|
181
|
-
platformName: string,
|
|
182
|
-
wdaRemotePort: number | string,
|
|
183
|
-
wdaBindingIP?: string,
|
|
184
|
-
maxHttpRequestBodySize?: number | string,
|
|
185
|
-
): Record<string, any> {
|
|
186
|
-
const runner = `WebDriverAgentRunner${isTvOS(platformName) ? '_tvOS' : ''}`;
|
|
187
|
-
return {
|
|
188
|
-
[runner]: {
|
|
189
|
-
EnvironmentVariables: {
|
|
190
|
-
// USE_PORT must be 'string'
|
|
191
|
-
USE_PORT: `${wdaRemotePort}`,
|
|
192
|
-
...(wdaBindingIP ? {USE_IP: wdaBindingIP} : {}),
|
|
193
|
-
...(maxHttpRequestBodySize
|
|
194
|
-
? {MAX_HTTP_REQUEST_BODY_SIZE: `${maxHttpRequestBodySize}`}
|
|
195
|
-
: {}),
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Return the path of xctestrun if it exists
|
|
203
|
-
* @param deviceInfo
|
|
204
|
-
* @param sdkVersion - The Xcode SDK version of OS.
|
|
205
|
-
* @param bootstrapPath - The folder path containing xctestrun file.
|
|
206
|
-
*/
|
|
207
|
-
export async function getXctestrunFilePath(
|
|
208
|
-
deviceInfo: DeviceInfo,
|
|
209
|
-
sdkVersion: string,
|
|
210
|
-
bootstrapPath: string,
|
|
211
|
-
): Promise<string> {
|
|
212
|
-
// First try the SDK path, for Xcode 10 (at least)
|
|
213
|
-
const sdkBased: [string, string] = [
|
|
214
|
-
path.resolve(bootstrapPath, `${deviceInfo.udid}_${sdkVersion}.xctestrun`),
|
|
215
|
-
sdkVersion,
|
|
216
|
-
];
|
|
217
|
-
// Next try Platform path, for earlier Xcode versions
|
|
218
|
-
const platformBased: [string, string] = [
|
|
219
|
-
path.resolve(bootstrapPath, `${deviceInfo.udid}_${deviceInfo.platformVersion}.xctestrun`),
|
|
220
|
-
deviceInfo.platformVersion,
|
|
221
|
-
];
|
|
222
|
-
|
|
223
|
-
for (const [filePath, version] of [sdkBased, platformBased]) {
|
|
224
|
-
if (await fs.exists(filePath)) {
|
|
225
|
-
log.info(`Using '${filePath}' as xctestrun file`);
|
|
226
|
-
return filePath;
|
|
227
|
-
}
|
|
228
|
-
const originalXctestrunFile = path.resolve(
|
|
229
|
-
bootstrapPath,
|
|
230
|
-
getXctestrunFileName(deviceInfo, version),
|
|
231
|
-
);
|
|
232
|
-
if (await fs.exists(originalXctestrunFile)) {
|
|
233
|
-
// If this is first time run for given device, then first generate xctestrun file for device.
|
|
234
|
-
// We need to have a xctestrun file **per device** because we cant not have same wda port for all devices.
|
|
235
|
-
await fs.copyFile(originalXctestrunFile, filePath);
|
|
236
|
-
log.info(`Using '${filePath}' as xctestrun file copied by '${originalXctestrunFile}'`);
|
|
237
|
-
return filePath;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
throw new Error(
|
|
242
|
-
`If you are using 'useXctestrunFile' capability then you ` +
|
|
243
|
-
`need to have a xctestrun file (expected: ` +
|
|
244
|
-
`'${path.resolve(bootstrapPath, getXctestrunFileName(deviceInfo, sdkVersion))}')`,
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Return the name of xctestrun file
|
|
250
|
-
* @param deviceInfo
|
|
251
|
-
* @param version - The Xcode SDK version of OS.
|
|
252
|
-
* @return returns xctestrunFilePath for given device
|
|
253
|
-
*/
|
|
254
|
-
export function getXctestrunFileName(deviceInfo: DeviceInfo, version: string): string {
|
|
255
|
-
const archSuffix = deviceInfo.isRealDevice
|
|
256
|
-
? `os${version}-arm64`
|
|
257
|
-
: `simulator${version}-${arch() === 'arm64' ? 'arm64' : 'x86_64'}`;
|
|
258
|
-
return `WebDriverAgentRunner_${isTvOS(deviceInfo.platformName) ? 'tvOS_appletv' : 'iphone'}${archSuffix}.xctestrun`;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Ensures the process is killed after the timeout
|
|
263
|
-
*/
|
|
264
|
-
export async function killProcess(
|
|
265
|
-
name: string,
|
|
266
|
-
proc: SubProcess | null | undefined,
|
|
267
|
-
): Promise<void> {
|
|
268
|
-
if (!proc || !proc.isRunning) {
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
log.info(`Shutting down '${name}' process (pid '${proc.proc?.pid}')`);
|
|
273
|
-
|
|
274
|
-
log.info(`Sending 'SIGTERM'...`);
|
|
275
|
-
try {
|
|
276
|
-
await proc.stop('SIGTERM', 1000);
|
|
277
|
-
return;
|
|
278
|
-
} catch (err: any) {
|
|
279
|
-
if (!err.message.includes(`Process didn't end after`)) {
|
|
280
|
-
throw err;
|
|
281
|
-
}
|
|
282
|
-
log.debug(`${name} process did not end in a timely fashion: '${err.message}'.`);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
log.info(`Sending 'SIGKILL'...`);
|
|
286
|
-
try {
|
|
287
|
-
await proc.stop('SIGKILL');
|
|
288
|
-
} catch (err: any) {
|
|
289
|
-
if (err.message.includes('not currently running')) {
|
|
290
|
-
// the process ended but for some reason we were not informed
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
throw err;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Generate a random integer in range [low, high). `low` is inclusive and `high` is exclusive.
|
|
299
|
-
*/
|
|
300
|
-
export function randomInt(low: number, high: number): number {
|
|
301
|
-
return Math.floor(Math.random() * (high - low) + low);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Retrieves WDA upgrade timestamp. The manifest only gets modified on package upgrade.
|
|
306
|
-
*/
|
|
307
|
-
export async function getWDAUpgradeTimestamp(): Promise<number | null> {
|
|
308
|
-
const packageManifest = path.resolve(getModuleRoot(), 'package.json');
|
|
309
|
-
if (!(await fs.exists(packageManifest))) {
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
312
|
-
const {mtime} = await fs.stat(packageManifest);
|
|
313
|
-
return mtime.getTime();
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Escape regular expression metacharacters in a string.
|
|
318
|
-
*/
|
|
319
|
-
export function escapeRegExp(value: string): string {
|
|
320
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Truncate a string to the given length and append ellipsis if needed.
|
|
325
|
-
*/
|
|
326
|
-
export function truncateString(value: string, length: number): string {
|
|
327
|
-
if (value.length <= length) {
|
|
328
|
-
return value;
|
|
329
|
-
}
|
|
330
|
-
return `${value.slice(0, Math.max(0, length - 1))}…`;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Kills running XCTest processes for the particular device.
|
|
335
|
-
*/
|
|
336
|
-
export async function resetTestProcesses(udid: string, isSimulator: boolean): Promise<void> {
|
|
337
|
-
const processPatterns = [`xcodebuild.*${udid}`];
|
|
338
|
-
if (isSimulator) {
|
|
339
|
-
processPatterns.push(`${udid}.*XCTRunner`);
|
|
340
|
-
// Some XCTest launches might not include xcodebuild in their command line
|
|
341
|
-
processPatterns.push(`xctest.*${udid}`);
|
|
342
|
-
}
|
|
343
|
-
log.debug(`Killing running processes '${processPatterns.join(', ')}' for the device ${udid}...`);
|
|
344
|
-
await Promise.all(processPatterns.map(killAppUsingPattern));
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Get the IDs of processes listening on the particular system port.
|
|
349
|
-
* It is also possible to apply additional filtering based on the
|
|
350
|
-
* process command line.
|
|
351
|
-
*
|
|
352
|
-
* @param port - The port number.
|
|
353
|
-
* @param filteringFunc - Optional lambda function, which
|
|
354
|
-
* receives command line string of the particular process
|
|
355
|
-
* listening on given port, and is expected to return
|
|
356
|
-
* either true or false to include/exclude the corresponding PID
|
|
357
|
-
* from the resulting array.
|
|
358
|
-
* @returns - the list of matched process ids.
|
|
359
|
-
*/
|
|
360
|
-
export async function getPIDsListeningOnPort(
|
|
361
|
-
port: string | number,
|
|
362
|
-
filteringFunc: ((cmdline: string) => boolean | Promise<boolean>) | null = null,
|
|
363
|
-
): Promise<string[]> {
|
|
364
|
-
const result: string[] = [];
|
|
365
|
-
try {
|
|
366
|
-
// This only works since Mac OS X El Capitan
|
|
367
|
-
const {stdout} = await exec('lsof', ['-ti', `tcp:${port}`]);
|
|
368
|
-
result.push(...stdout.trim().split(/\n+/));
|
|
369
|
-
} catch (e: any) {
|
|
370
|
-
if (e.code !== 1) {
|
|
371
|
-
// code 1 means no processes. Other errors need reporting
|
|
372
|
-
log.debug(`Error getting processes listening on port '${port}': ${e.stderr || e.message}`);
|
|
373
|
-
}
|
|
374
|
-
return result;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
if (typeof filteringFunc !== 'function') {
|
|
378
|
-
return result;
|
|
379
|
-
}
|
|
380
|
-
const filtered = await Promise.all(
|
|
381
|
-
result.map(async (pid) => {
|
|
382
|
-
let stdout: string;
|
|
383
|
-
try {
|
|
384
|
-
({stdout} = await exec('ps', ['-p', pid, '-o', 'command']));
|
|
385
|
-
} catch (e: any) {
|
|
386
|
-
if (e.code === 1) {
|
|
387
|
-
// The process does not exist anymore, there's nothing to filter
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
throw e;
|
|
391
|
-
}
|
|
392
|
-
return (await filteringFunc(stdout)) ? pid : null;
|
|
393
|
-
}),
|
|
394
|
-
);
|
|
395
|
-
return filtered.filter((pid): pid is string => Boolean(pid));
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Private functions
|
|
399
|
-
|
|
400
|
-
async function getPIDsUsingPattern(pattern: string): Promise<string[]> {
|
|
401
|
-
const args = [
|
|
402
|
-
'-if', // case insensitive, full cmdline match
|
|
403
|
-
pattern,
|
|
404
|
-
];
|
|
405
|
-
try {
|
|
406
|
-
const {stdout} = await exec('pgrep', args);
|
|
407
|
-
return stdout
|
|
408
|
-
.split(/\s+/)
|
|
409
|
-
.map((x) => parseInt(x, 10))
|
|
410
|
-
.filter(Number.isInteger)
|
|
411
|
-
.map((x) => `${x}`);
|
|
412
|
-
} catch (err: any) {
|
|
413
|
-
log.debug(
|
|
414
|
-
`'pgrep ${args.join(' ')}' didn't detect any matching processes. Return code: ${err.code}`,
|
|
415
|
-
);
|
|
416
|
-
return [];
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function mergeObjects<T extends Record<string, any>, U extends Record<string, any>>(
|
|
421
|
-
target: T,
|
|
422
|
-
source: U,
|
|
423
|
-
): T & U {
|
|
424
|
-
const output: Record<string, any> = {...target};
|
|
425
|
-
for (const [key, sourceValue] of Object.entries(source)) {
|
|
426
|
-
const targetValue = output[key];
|
|
427
|
-
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
428
|
-
output[key] = mergeObjects(targetValue, sourceValue);
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
output[key] = sourceValue;
|
|
432
|
-
}
|
|
433
|
-
return output as T & U;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function isPlainObject(value: unknown): value is Record<string, any> {
|
|
437
|
-
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
438
|
-
return false;
|
|
439
|
-
}
|
|
440
|
-
const prototype = Object.getPrototypeOf(value);
|
|
441
|
-
return prototype === Object.prototype || prototype === null;
|
|
442
|
-
}
|