feat: remove many unneded simple libraries

This commit is contained in:
Eli Bosley
2024-10-24 18:32:15 -04:00
parent 5c020a62d6
commit 41e5de83a2
7 changed files with 544 additions and 640 deletions

922
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -70,6 +70,7 @@
"bycontract": "^2.0.11",
"bytes": "^3.1.2",
"cacheable-lookup": "^6.1.0",
"camelcase-keys": "^9.1.3",
"catch-exit": "^1.2.2",
"chokidar": "^3.6.0",
"cli-table": "^0.3.11",
@@ -79,11 +80,13 @@
"docker-event-emitter": "^0.3.0",
"dockerode": "^3.3.5",
"dotenv": "^16.4.5",
"execa": "^9.4.1",
"exit-hook": "^4.0.0",
"express": "^4.19.2",
"filenamify": "^6.0.0",
"fs-extra": "^11.2.0",
"global-agent": "^3.0.0",
"got": "^14.4.3",
"graphql": "^16.8.1",
"graphql-fields": "^2.0.3",
"graphql-scalars": "^1.23.0",
@@ -107,6 +110,7 @@
"p-retry": "^4.6.2",
"passport-custom": "^1.1.1",
"passport-http-header-strategy": "^1.1.0",
"path-type": "^6.0.0",
"pidusage": "^3.0.2",
"pino": "^9.1.0",
"pino-http": "^9.0.0",
@@ -160,22 +164,10 @@
"@types/wtfnode": "^0.7.3",
"@vitest/coverage-v8": "^2.1.1",
"@vitest/ui": "^2.1.1",
"camelcase-keys": "^8.0.2",
"cz-conventional-changelog": "3.3.0",
"eslint": "^9.12.0",
"execa": "^7.1.1",
"filter-obj": "^5.1.0",
"got": "^13",
"graphql-codegen-typescript-validation-schema": "^0.14.1",
"ip-regex": "^5.0.0",
"jiti": "^2.3.3",
"json-difference": "^1.16.1",
"map-obj": "^5.0.2",
"p-props": "^5.0.0",
"path-exists": "^5.0.0",
"path-type": "^5.0.0",
"pretty-bytes": "^6.1.1",
"pretty-ms": "^8.0.0",
"standard-version": "^9.5.0",
"typescript": "^5.4.5",
"typescript-eslint": "^8.10.0",

View File

@@ -1,7 +1,5 @@
import { parse as parseIni } from 'ini';
import camelCaseKeys from 'camelcase-keys';
import { includeKeys } from 'filter-obj';
import mapObject from 'map-obj';
import { AppError } from '@app/core/errors/app-error';
import { accessSync, readFileSync } from 'fs';
import { access } from 'fs/promises';
@@ -11,15 +9,15 @@ import { extname } from 'path';
type ConfigType = 'ini' | 'cfg';
type OptionsWithPath = {
/** Relative or absolute file path. */
filePath: string;
/** If the file is an "ini" or a "cfg". */
type?: ConfigType;
/** Relative or absolute file path. */
filePath: string;
/** If the file is an "ini" or a "cfg". */
type?: ConfigType;
};
type OptionsWithLoadedFile = {
file: string;
type: ConfigType;
file: string;
type: ConfigType;
};
/**
@@ -38,53 +36,66 @@ type OptionsWithLoadedFile = {
* ```
*/
const fixObjectArrays = (object: Record<string, any>) => {
// An object of arrays for keys that end in `:${number}`
const temporaryArrays = {};
// An object of arrays for keys that end in `:${number}`
const temporaryArrays = {};
// An object without any array items
const filteredObject = includeKeys(object, (key, value) => {
const [, name, index] = [...((key).match(/(.*):(\d+$)/) ?? [])];
if (!name || !index) {
return true;
}
// An object without any array items
const filteredObject = Object.fromEntries(
Object.entries(object).filter(([key, value]) => {
const match = key.match(/(.*):(\d+$)/);
if (!match) {
return true;
}
// Create initial array
if (!Array.isArray(temporaryArrays[name])) {
temporaryArrays[name] = [];
}
const [, name, index] = match;
if (!name || !index) {
return true;
}
// Add value
temporaryArrays[name].push(value);
// Create initial array
if (!Array.isArray(temporaryArrays[name])) {
temporaryArrays[name] = [];
}
// Remove the old field
return false;
});
// Add value
temporaryArrays[name].push(value);
return {
...filteredObject,
...temporaryArrays,
};
// Remove the old field
return false;
})
);
return {
...filteredObject,
...temporaryArrays,
};
};
export const fileExists = async (path: string) => access(path, F_OK).then(() => true).catch(() => false);
export const fileExists = async (path: string) =>
access(path, F_OK)
.then(() => true)
.catch(() => false);
export const fileExistsSync = (path: string) => {
try {
accessSync(path, F_OK);
return true;
} catch (error: unknown) {
return false;
}
try {
accessSync(path, F_OK);
return true;
} catch (error: unknown) {
return false;
}
};
export const getExtensionFromPath = (filePath: string): string => extname(filePath);
const isFilePathOptions = (options: OptionsWithLoadedFile | OptionsWithPath): options is OptionsWithPath => Object.keys(options).includes('filePath');
const isFileOptions = (options: OptionsWithLoadedFile | OptionsWithPath): options is OptionsWithLoadedFile => Object.keys(options).includes('file');
const isFilePathOptions = (
options: OptionsWithLoadedFile | OptionsWithPath
): options is OptionsWithPath => Object.keys(options).includes('filePath');
const isFileOptions = (
options: OptionsWithLoadedFile | OptionsWithPath
): options is OptionsWithLoadedFile => Object.keys(options).includes('file');
export const loadFileFromPathSync = (filePath: string): string => {
if (!fileExistsSync(filePath)) throw new Error(`Failed to load file at path: ${filePath}`);
return readFileSync(filePath, 'utf-8').toString();
if (!fileExistsSync(filePath)) throw new Error(`Failed to load file at path: ${filePath}`);
return readFileSync(filePath, 'utf-8').toString();
};
/**
@@ -94,48 +105,51 @@ export const loadFileFromPathSync = (filePath: string): string => {
*/
const isValidConfigExtension = (extension: string): boolean => ['ini', 'cfg'].includes(extension);
export const parseConfig = <T extends Record<string, any>>(options: OptionsWithLoadedFile | OptionsWithPath): T => {
let fileContents: string;
let extension: string;
export const parseConfig = <T extends Record<string, any>>(
options: OptionsWithLoadedFile | OptionsWithPath
): T => {
let fileContents: string;
let extension: string;
if (isFilePathOptions(options)) {
const { filePath, type } = options;
if (isFilePathOptions(options)) {
const { filePath, type } = options;
const validFile = fileExistsSync(filePath);
extension = type ?? getExtensionFromPath(filePath);
const validExtension = isValidConfigExtension(extension);
const validFile = fileExistsSync(filePath);
extension = type ?? getExtensionFromPath(filePath);
const validExtension = isValidConfigExtension(extension);
if (validFile && validExtension) {
fileContents = loadFileFromPathSync(options.filePath);
} else {
throw new AppError(`Invalid File Path: ${options.filePath}, or Extension: ${extension}`);
}
} else if (isFileOptions(options)) {
const { file, type } = options;
fileContents = file;
const extension = type;
if (!isValidConfigExtension(extension)) {
throw new AppError(`Invalid Extension for Ini File: ${extension}`);
}
} else {
throw new AppError('Invalid Parameters Passed to ParseConfig');
}
if (validFile && validExtension) {
fileContents = loadFileFromPathSync(options.filePath);
} else {
throw new AppError(`Invalid File Path: ${options.filePath}, or Extension: ${extension}`);
}
} else if (isFileOptions(options)) {
const { file, type } = options;
fileContents = file;
const extension = type;
if (!isValidConfigExtension(extension)) {
throw new AppError(`Invalid Extension for Ini File: ${extension}`);
}
} else {
throw new AppError('Invalid Parameters Passed to ParseConfig');
}
const data: Record<string, any> = parseIni(fileContents);
// Remove quotes around keys
const dataWithoutQuoteKeys = mapObject(data, (key, value) =>
// @SEE: https://stackoverflow.com/a/19156197/2311366
[(key).replace(/^"(.+(?="$))"$/, '$1'), value],
);
const data: Record<string, any> = parseIni(fileContents);
// Remove quotes around keys
const dataWithoutQuoteKeys = Object.fromEntries(
Object.entries(data).map(([key, value]) => [key.replace(/^"(.+(?="$))"$/, '$1'), value])
);
// Result object with array items as actual arrays
const result = Object.fromEntries(
Object.entries(dataWithoutQuoteKeys)
.map(([key, value]) => [key, typeof value === 'object' ? fixObjectArrays(value) : value]),
);
// Result object with array items as actual arrays
const result = Object.fromEntries(
Object.entries(dataWithoutQuoteKeys).map(([key, value]) => [
key,
typeof value === 'object' ? fixObjectArrays(value) : value,
])
);
// Convert all keys to camel case
return camelCaseKeys(result, {
deep: true,
}) as T;
// Convert all keys to camel case
return camelCaseKeys(result, {
deep: true,
}) as T;
};

View File

@@ -1,4 +1,3 @@
import pProps from 'p-props';
import { type Domain } from '@app/core/types';
import { getHypervisor } from '@app/core/utils/vms/get-hypervisor';
@@ -27,24 +26,34 @@ export const parseDomain = async (type: DomainLookupType, id: string): Promise<D
const domain = await client[method](id);
const info = await domain.getInfoAsync();
const results = await pProps({
uuid: domain.getUUIDAsync(),
osType: domain.getOSTypeAsync(),
autostart: domain.getAutostartAsync(),
maxMemory: domain.getMaxMemoryAsync(),
schedulerType: domain.getSchedulerTypeAsync(),
schedulerParameters: domain.getSchedulerParametersAsync(),
securityLabel: domain.getSecurityLabelAsync(),
name: domain.getNameAsync(),
const [uuid, osType, autostart, maxMemory, schedulerType, schedulerParameters, securityLabel, name] = await Promise.all([
domain.getUUIDAsync(),
domain.getOSTypeAsync(),
domain.getAutostartAsync(),
domain.getMaxMemoryAsync(),
domain.getSchedulerTypeAsync(),
domain.getSchedulerParametersAsync(),
domain.getSecurityLabelAsync(),
domain.getNameAsync(),
]);
const results = {
uuid,
osType,
autostart,
maxMemory,
schedulerType,
schedulerParameters,
securityLabel,
name,
...info,
state: info.state.replace(' ', '_'),
});
};
if (info.state === 'running') {
results.vcpus = await domain.getVcpusAsync();
results.memoryStats = await domain.getMemoryStatsAsync();
}
// @ts-expect-error fix pProps inferred type
return results;
};

View File

@@ -0,0 +1,15 @@
import fs from 'fs';
import path from 'path';
import { logger } from '@app/core/log';
import convert from 'convert';
const writeFile = async (filePath: string, fileContents: string | Buffer) => {
logger.debug(`Writing ${convert(fileContents.length, 'bytes').to('kilobytes')} to ${filePath}`);
await fs.promises.writeFile(filePath, fileContents);
};
export const writeToBoot = async (filePath: string, fileContents: string | Buffer) => {
const basePath = '/boot/config/plugins/dynamix/';
const resolvedPath = path.resolve(basePath, filePath);
await writeFile(resolvedPath, fileContents);
};

View File

@@ -22,7 +22,6 @@ import { getUnraidVersion } from '@app/common/dashboard/get-unraid-version';
import { AppError } from '@app/core/errors/app-error';
import { cleanStdout } from '@app/core/utils/misc/clean-stdout';
import { execaCommandSync, execa } from 'execa';
import { pathExists } from 'path-exists';
import { isSymlink } from 'path-type';
import type { PciDevice } from '@app/core/types';
import { vmRegExps } from '@app/core/utils/vms/domain/vm-regexps';
@@ -31,6 +30,7 @@ import { filterDevices } from '@app/core/utils/vms/filter-devices';
import { sanitizeVendor } from '@app/core/utils/vms/domain/sanitize-vendor';
import { sanitizeProduct } from '@app/core/utils/vms/domain/sanitize-product';
import { bootTimestamp } from '@app/common/dashboard/boot-timestamp';
import { access } from 'fs/promises';
export const generateApps = async (): Promise<InfoApps> => {
const installed = await docker
@@ -201,11 +201,12 @@ export const generateDevices = async (): Promise<Devices> => {
// Remove devices with no IOMMU support
const filteredDevices = await Promise.all(
devices.map((device: Readonly<PciDevice>) =>
pathExists(`${basePath}${device.id}/iommu_group/`).then((exists) =>
exists ? device : null
)
)
devices.map(async (device: Readonly<PciDevice>) => {
const exists = await access(`${basePath}${device.id}/iommu_group/`)
.then(() => true)
.catch(() => false);
return exists ? device : null;
})
).then((devices) => devices.filter((device) => device !== null));
/**
@@ -281,7 +282,6 @@ export const generateDevices = async (): Promise<Devices> => {
const usbHubs = await execa('cat /sys/bus/usb/drivers/hub/*/modalias', { shell: true })
.then(({ stdout }) =>
stdout.split('\n').map((line) => {
const [, id] = line.match(/usb:v(\w{9})/) ?? [];
return id.replace('p', ':');
})
@@ -316,7 +316,7 @@ export const generateDevices = async (): Promise<Devices> => {
// Parse the line
const [, _] = line.split(/[ \t]{2,}/).filter(Boolean);
const match = _.match(/^(\S+)\s(.*)/)?.slice(1);
// If there's no match return nothing

View File

@@ -1,5 +1,4 @@
import { startAppListening } from '@app/store/listeners/listener-middleware';
import { getDiff } from 'json-difference';
import { isEqual } from 'lodash-es';
import { logger } from '@app/core/log';
import {
@@ -40,15 +39,6 @@ export const enableConfigFileListener = (mode: ConfigType) => () =>
action.type !== loadConfigFile.fulfilled.type &&
action.type !== loadConfigFile.rejected.type
) {
logger.trace(
{
diff: getDiff(oldFlashConfig ?? {}, newFlashConfig),
},
`${mode} Config Changed!`,
'Action:',
action.type
);
return true;
}