mirror of
https://github.com/appium/appium.git
synced 2026-10-22 00:45:27 +08:00
835119d1ce
BREAKING CHANGE: remove node.requirePackage() BREAKING CHANGE: remove system.macOsxVersion()
171 lines
4.7 KiB
TypeScript
171 lines
4.7 KiB
TypeScript
import {randomUUID} from 'node:crypto';
|
|
import _fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const OBJECTS_MAPPING = new WeakMap<object, string>();
|
|
|
|
/**
|
|
* Calculate the in-depth size in memory of the provided object.
|
|
* The original implementation is borrowed from https://github.com/miktam/sizeof.
|
|
*
|
|
* @param obj - An object whose size should be calculated
|
|
* @returns Object size in bytes.
|
|
*/
|
|
export function getObjectSize(obj: unknown): number {
|
|
return getCalculator(new WeakSet())(obj);
|
|
}
|
|
|
|
/**
|
|
* Calculates a unique object identifier
|
|
*
|
|
* @param object - Any valid ECMA object
|
|
* @returns A uuidV4 string that uniquely identifies given object
|
|
*/
|
|
export function getObjectId(object: object): string {
|
|
const existing = OBJECTS_MAPPING.get(object);
|
|
if (existing !== undefined) {
|
|
return existing;
|
|
}
|
|
const id = randomUUID();
|
|
OBJECTS_MAPPING.set(object, id);
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Perform deep freeze of the given object (e. g.
|
|
* all nested objects also become immutable).
|
|
* If the passed object is of a plain type
|
|
* then no change is done and the same object
|
|
* is returned.
|
|
* ! This function changes the given object,
|
|
* so it becomes immutable.
|
|
*
|
|
* @param object - Any valid ECMA object
|
|
* @returns The same object that was passed after it was made immutable.
|
|
*/
|
|
export function deepFreeze<T>(object: T): T {
|
|
let propNames: string[];
|
|
try {
|
|
propNames = Object.getOwnPropertyNames(object as object);
|
|
} catch {
|
|
return object;
|
|
}
|
|
for (const name of propNames) {
|
|
const value = (object as Record<string, unknown>)[name];
|
|
if (value && typeof value === 'object') {
|
|
deepFreeze(value);
|
|
}
|
|
}
|
|
return Object.freeze(object) as T;
|
|
}
|
|
|
|
/**
|
|
* Tries to synchronously detect the absolute path to the folder
|
|
* where the given `moduleName` is located.
|
|
*
|
|
* @param moduleName - The name of the module as it is written in package.json
|
|
* @param filePath - Full path to any of files that `moduleName` contains. Use
|
|
* `import.meta.filename` to find the root of the module where this helper is called.
|
|
* @returns Full path to the module root, or null if not found
|
|
*/
|
|
export function getModuleRootSync(moduleName: string, filePath: string): string | null {
|
|
let currentDir = path.dirname(path.resolve(filePath));
|
|
let isAtFsRoot = false;
|
|
while (!isAtFsRoot) {
|
|
const manifestPath = path.join(currentDir, 'package.json');
|
|
try {
|
|
if (
|
|
_fs.existsSync(manifestPath) &&
|
|
(JSON.parse(_fs.readFileSync(manifestPath, 'utf8')) as {name?: string}).name === moduleName
|
|
) {
|
|
return currentDir;
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
currentDir = path.dirname(currentDir);
|
|
isAtFsRoot = currentDir.length <= path.dirname(currentDir).length;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// #region Private
|
|
|
|
const ECMA_SIZES = Object.freeze({
|
|
STRING: 2,
|
|
BOOLEAN: 4,
|
|
NUMBER: 8,
|
|
});
|
|
|
|
type SizeCalculator = (obj: unknown) => number;
|
|
|
|
function extractAllProperties(obj: object): (string | symbol)[] {
|
|
const stringProperties: (string | symbol)[] = [];
|
|
for (const prop in obj) {
|
|
stringProperties.push(prop);
|
|
}
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
stringProperties.push(...Object.getOwnPropertySymbols(obj));
|
|
}
|
|
return stringProperties;
|
|
}
|
|
|
|
function _getSizeOfObject(seen: WeakSet<object>, object: object): number {
|
|
if (object == null) {
|
|
return 0;
|
|
}
|
|
|
|
let bytes = 0;
|
|
const properties = extractAllProperties(object);
|
|
const calc = getCalculator(seen);
|
|
for (const key of properties) {
|
|
const val = (object as Record<string | symbol, unknown>)[key];
|
|
if (typeof val === 'object' && val != null) {
|
|
if (seen.has(val as object)) {
|
|
continue;
|
|
}
|
|
seen.add(val as object);
|
|
}
|
|
|
|
bytes += calc(key);
|
|
try {
|
|
bytes += calc(val);
|
|
} catch (ex) {
|
|
if (ex instanceof RangeError) {
|
|
bytes = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
function getCalculator(seen: WeakSet<object>): SizeCalculator {
|
|
return function calculator(obj: unknown): number {
|
|
if (Buffer.isBuffer(obj)) {
|
|
return (obj as Buffer).length;
|
|
}
|
|
|
|
switch (typeof obj) {
|
|
case 'string':
|
|
return obj.length * ECMA_SIZES.STRING;
|
|
case 'boolean':
|
|
return ECMA_SIZES.BOOLEAN;
|
|
case 'number':
|
|
return ECMA_SIZES.NUMBER;
|
|
case 'symbol':
|
|
return typeof Symbol.keyFor === 'function' && Symbol.keyFor(obj)
|
|
? (Symbol.keyFor(obj) as string).length * ECMA_SIZES.STRING
|
|
: (obj.toString().length - 8) * ECMA_SIZES.STRING;
|
|
case 'object':
|
|
return Array.isArray(obj)
|
|
? obj.map(getCalculator(seen)).reduce((acc, curr) => acc + curr, 0)
|
|
: _getSizeOfObject(seen, obj as object);
|
|
default:
|
|
return 0;
|
|
}
|
|
};
|
|
}
|
|
|
|
// #endregion
|