Skip to content

Commit 7af1f7b

Browse files
ditch lodash
1 parent 34efb7d commit 7af1f7b

7 files changed

Lines changed: 101 additions & 60 deletions

File tree

Scripts/fetch-prebuilt-wda.mjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { fileURLToPath } from 'node:url';
33
import { readFileSync } from 'node:fs';
44
import axios from 'axios';
55
import { logger, fs, mkdirp, net } from '@appium/support';
6-
import _ from 'lodash';
76

87
const __filename = fileURLToPath(import.meta.url);
98
const __dirname = path.dirname(__filename);
@@ -53,7 +52,10 @@ async function fetchPrebuiltWebDriverAgentAssets () {
5352
const url = asset.browser_download_url;
5453
log.info(`Downloading: ${url}`);
5554
try {
56-
const nameOfAgent = _.last(url.split('/'));
55+
const nameOfAgent = url.split('/').at(-1);
56+
if (!nameOfAgent) {
57+
continue;
58+
}
5759
agentsDownloading.push(downloadAgent(url, path.join(webdriveragentsDir, nameOfAgent)));
5860
} catch { }
5961
}

lib/utils.ts

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {exec, SubProcess} from 'teen_process';
33
import path, {dirname} from 'node:path';
44
import {fileURLToPath} from 'node:url';
55
import {log} from './logger';
6-
import _ from 'lodash';
76
import {PLATFORM_NAME_TVOS} from './constants';
87
import _fs from 'node:fs';
98
import {waitForCondition} from 'asyncbox';
@@ -18,13 +17,18 @@ const currentFilename =
1817

1918
const currentDirname = dirname(currentFilename);
2019

20+
let moduleRootCache: string | undefined;
21+
2122
/**
2223
* Calculates the path to the current module's root folder
2324
*
2425
* @returns {string} The full path to module root
2526
* @throws {Error} If the current module root folder cannot be determined
2627
*/
27-
const getModuleRoot = _.memoize(function getModuleRoot(): string {
28+
const getModuleRoot = function getModuleRoot(): string {
29+
if (moduleRootCache) {
30+
return moduleRootCache;
31+
}
2832
let currentDir = currentDirname;
2933
let isAtFsRoot = false;
3034
while (!isAtFsRoot) {
@@ -34,14 +38,15 @@ const getModuleRoot = _.memoize(function getModuleRoot(): string {
3438
_fs.existsSync(manifestPath) &&
3539
JSON.parse(_fs.readFileSync(manifestPath, 'utf8')).name === 'appium-webdriveragent'
3640
) {
41+
moduleRootCache = currentDir;
3742
return currentDir;
3843
}
3944
} catch {}
4045
currentDir = path.dirname(currentDir);
4146
isAtFsRoot = currentDir.length <= path.dirname(currentDir).length;
4247
}
4348
throw new Error('Cannot find the root folder of the appium-webdriveragent Node.js module');
44-
});
49+
};
4550

4651
export const BOOTSTRAP_PATH = getModuleRoot();
4752

@@ -63,7 +68,7 @@ export async function killAppUsingPattern(pgrepPattern: string): Promise<void> {
6368
const signals = [2, 15, 9];
6469
for (const signal of signals) {
6570
const matchedPids = await getPIDsUsingPattern(pgrepPattern);
66-
if (_.isEmpty(matchedPids)) {
71+
if (matchedPids.length === 0) {
6772
return;
6873
}
6974
const args = [`-${signal}`, ...matchedPids];
@@ -72,7 +77,7 @@ export async function killAppUsingPattern(pgrepPattern: string): Promise<void> {
7277
} catch (err: any) {
7378
log.debug(`kill ${args.join(' ')} -> ${err.message}`);
7479
}
75-
if (signal === _.last(signals)) {
80+
if (signal === signals[signals.length - 1]) {
7681
// there is no need to wait after SIGKILL
7782
return;
7883
}
@@ -109,7 +114,7 @@ export async function killAppUsingPattern(pgrepPattern: string): Promise<void> {
109114
* @returns Return true if the platformName is tvOS
110115
*/
111116
export function isTvOS(platformName: string): boolean {
112-
return _.toLower(platformName) === _.toLower(PLATFORM_NAME_TVOS);
117+
return platformName?.toLowerCase() === PLATFORM_NAME_TVOS.toLowerCase();
113118
}
114119

115120
/**
@@ -148,7 +153,7 @@ export async function setXctestrunFile(args: XctestrunFileArgs): Promise<string>
148153
wdaRemotePort,
149154
wdaBindingIP,
150155
);
151-
const newXctestRunContent = _.merge(xctestRunContent, updateWDAPort);
156+
const newXctestRunContent = mergeObjects(xctestRunContent, updateWDAPort);
152157
await plist.updatePlistFile(xctestrunFilePath, newXctestRunContent, true);
153158

154159
return xctestrunFilePath;
@@ -293,6 +298,23 @@ export async function getWDAUpgradeTimestamp(): Promise<number | null> {
293298
return mtime.getTime();
294299
}
295300

301+
/**
302+
* Escape regular expression metacharacters in a string.
303+
*/
304+
export function escapeRegExp(value: string): string {
305+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
306+
}
307+
308+
/**
309+
* Truncate a string to the given length and append ellipsis if needed.
310+
*/
311+
export function truncateString(value: string, length: number): string {
312+
if (value.length <= length) {
313+
return value;
314+
}
315+
return `${value.slice(0, Math.max(0, length - 1))}…`;
316+
}
317+
296318
/**
297319
* Kills running XCTest processes for the particular device.
298320
*/
@@ -337,7 +359,7 @@ export async function getPIDsListeningOnPort(
337359
return result;
338360
}
339361

340-
if (!_.isFunction(filteringFunc)) {
362+
if (typeof filteringFunc !== 'function') {
341363
return result;
342364
}
343365
const filtered = await Promise.all(
@@ -370,7 +392,7 @@ async function getPIDsUsingPattern(pattern: string): Promise<string[]> {
370392
return stdout
371393
.split(/\s+/)
372394
.map((x) => parseInt(x, 10))
373-
.filter(_.isInteger)
395+
.filter(Number.isInteger)
374396
.map((x) => `${x}`);
375397
} catch (err: any) {
376398
log.debug(
@@ -379,3 +401,30 @@ async function getPIDsUsingPattern(pattern: string): Promise<string[]> {
379401
return [];
380402
}
381403
}
404+
405+
function mergeObjects<T extends Record<string, any>, U extends Record<string, any>>(
406+
target: T,
407+
source: U,
408+
): T & U {
409+
const output: Record<string, any> = {...target};
410+
for (const [key, sourceValue] of Object.entries(source)) {
411+
const targetValue = output[key];
412+
if (
413+
isPlainObject(targetValue) &&
414+
isPlainObject(sourceValue)
415+
) {
416+
output[key] = mergeObjects(targetValue, sourceValue);
417+
continue;
418+
}
419+
output[key] = sourceValue;
420+
}
421+
return output as T & U;
422+
}
423+
424+
function isPlainObject(value: unknown): value is Record<string, any> {
425+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
426+
return false;
427+
}
428+
const prototype = Object.getPrototypeOf(value);
429+
return prototype === Object.prototype || prototype === null;
430+
}

lib/webdriveragent.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {waitForCondition} from 'asyncbox';
2-
import _ from 'lodash';
32
import path from 'node:path';
43
import {JWProxy} from '@appium/base-driver';
54
import {fs, util, plist} from '@appium/support';
@@ -75,7 +74,7 @@ export class WebDriverAgent {
7574
* @param log - Optional logger instance
7675
*/
7776
constructor(args: WebDriverAgentArgs, log: AppiumLogger | null = null) {
78-
this.args = _.clone(args);
77+
this.args = {...args};
7978
this.log = log ?? defaultLogger;
8079

8180
this.device = args.device;
@@ -252,7 +251,7 @@ export class WebDriverAgent {
252251
!cmdLine.toLowerCase().includes(this.device.udid.toLowerCase()),
253252
);
254253

255-
if (_.isEmpty(obsoletePids)) {
254+
if (obsoletePids.length === 0) {
256255
this.log.debug(
257256
`No obsolete cached processes from previous WDA sessions ` +
258257
`listening on port ${this.url.port} have been found`,
@@ -455,7 +454,7 @@ export class WebDriverAgent {
455454
if (
456455
actualUpgradeTimestamp &&
457456
upgradedAt &&
458-
_.toLower(`${actualUpgradeTimestamp}`) !== _.toLower(`${upgradedAt}`)
457+
`${actualUpgradeTimestamp}`.toLowerCase() !== `${upgradedAt}`.toLowerCase()
459458
) {
460459
this.log.info(
461460
'Will uninstall running WDA since it has different version in comparison to the one ' +
@@ -499,7 +498,7 @@ export class WebDriverAgent {
499498
const wdaBundlePaths = await fs.glob(`${this.derivedDataPath}/**/*${WDA_RUNNER_APP}/`, {
500499
absolute: true,
501500
});
502-
if (_.isEmpty(wdaBundlePaths)) {
501+
if (wdaBundlePaths.length === 0) {
503502
throw new Error(`Could not find the WDA bundle in '${this.derivedDataPath}'`);
504503
}
505504
return wdaBundlePaths[0];
@@ -588,7 +587,7 @@ export class WebDriverAgent {
588587
const sendGetStatus = async () =>
589588
(await noSessionProxy.command('/status', 'GET')) as StringRecord;
590589

591-
if (_.isNil(timeoutMs) || timeoutMs <= 0) {
590+
if (timeoutMs == null || timeoutMs <= 0) {
592591
try {
593592
return await sendGetStatus();
594593
} catch (err: any) {
@@ -635,7 +634,7 @@ export class WebDriverAgent {
635634
private async uninstall(): Promise<void> {
636635
try {
637636
const bundleIds = await this.device.getUserInstalledBundleIdsByBundleName(WDA_CF_BUNDLE_NAME);
638-
if (_.isEmpty(bundleIds)) {
637+
if (bundleIds.length === 0) {
639638
this.log.debug('No WDAs on the device.');
640639
return;
641640
}

lib/xcodebuild.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {
99
killProcess,
1010
getWDAUpgradeTimestamp,
1111
isTvOS,
12+
escapeRegExp,
13+
truncateString,
1214
} from './utils';
13-
import _ from 'lodash';
1415
import path from 'node:path';
1516
import {WDA_RUNNER_BUNDLE_ID} from './constants';
1617
import type {AppleDevice, XcodeBuildArgs} from './types';
@@ -29,7 +30,7 @@ const IGNORED_ERRORS = [
2930
'Failed to remove screenshot at path',
3031
];
3132
const IGNORED_ERRORS_PATTERN = new RegExp(
32-
'(' + IGNORED_ERRORS.map((errStr) => _.escapeRegExp(errStr)).join('|') + ')',
33+
'(' + IGNORED_ERRORS.map((errStr) => escapeRegExp(errStr)).join('|') + ')',
3334
);
3435

3536
const RUNNER_SCHEME_TV = 'WebDriverAgentRunner_tvOS';
@@ -118,7 +119,7 @@ export class XcodeBuild {
118119

119120
this.mjpegServerPort = args.mjpegServerPort;
120121

121-
this.prebuildDelay = _.isNumber(args.prebuildDelay) ? args.prebuildDelay : PREBUILD_DELAY;
122+
this.prebuildDelay = typeof args.prebuildDelay === 'number' ? args.prebuildDelay : PREBUILD_DELAY;
122123

123124
this.allowProvisioningDeviceRegistration = args.allowProvisioningDeviceRegistration;
124125

@@ -182,7 +183,7 @@ export class XcodeBuild {
182183
const pattern = /^\s*BUILD_DIR\s+=\s+(\/.*)/m;
183184
const match = pattern.exec(stdout);
184185
if (!match) {
185-
this.log.warn(`Cannot parse WDA build dir from ${_.truncate(stdout, {length: 300})}`);
186+
this.log.warn(`Cannot parse WDA build dir from ${truncateString(stdout, 300)}`);
186187
return;
187188
}
188189
this.log.debug(`Parsed BUILD_DIR configuration value: '${match[1]}'`);
@@ -414,7 +415,7 @@ export class XcodeBuild {
414415
});
415416

416417
let logXcodeOutput = !!this.showXcodeLog;
417-
const logMsg = _.isBoolean(this.showXcodeLog)
418+
const logMsg = typeof this.showXcodeLog === 'boolean'
418419
? `Output from xcodebuild ${this.showXcodeLog ? 'will' : 'will not'} be logged`
419420
: 'Output from xcodebuild will only be logged if any errors are present there';
420421
this.log.debug(`${logMsg}. To change this, use 'showXcodeLog' desired capability`);

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
"@appium/types": "^1.0.0-rc.1",
5555
"@semantic-release/changelog": "^6.0.1",
5656
"@semantic-release/git": "^10.0.1",
57-
"@types/lodash": "^4.14.196",
5857
"@types/mocha": "^10.0.1",
5958
"@types/node": "^25.0.0",
6059
"appium-xcode": "^6.0.0",
@@ -79,7 +78,6 @@
7978
"async-lock": "^1.0.0",
8079
"asyncbox": "^6.1.0",
8180
"axios": "^1.4.0",
82-
"lodash": "^4.17.11",
8381
"teen_process": "^4.0.7"
8482
},
8583
"files": [

test/functional/helpers/simulator.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import _ from 'lodash';
21
import {Simctl} from 'node-simctl';
32
import {retryInterval} from 'asyncbox';
43
import {killAllSimulators as simKill} from 'appium-ios-simulator';
@@ -7,7 +6,7 @@ import type {AppleDevice} from '../../../lib/types';
76

87
export async function killAllSimulators(): Promise<void> {
98
const simctl = new Simctl();
10-
const allDevices = _.flatMap(_.values(await simctl.getDevices()));
9+
const allDevices = Object.values(await simctl.getDevices()).flat();
1110
const bootedDevices = allDevices.filter((device) => device.state === 'Booted');
1211

1312
for (const {udid} of bootedDevices) {

0 commit comments

Comments
 (0)