From 4ef387b5bbf615a6cd9f8794bee10cd5ef757792 Mon Sep 17 00:00:00 2001 From: Alexis Date: Thu, 2 Sep 2021 17:30:33 +0930 Subject: [PATCH] chore: lint all the files --- .eslintrc.cjs | 5 + README.md | 2 +- app/cli.ts | 104 +++++++++--------- app/consts.ts | 2 +- app/core/api-manager.ts | 1 - app/core/discovery/listen.ts | 23 ++-- app/core/errors/param-invalid-error.ts | 4 +- app/core/modules/add-plugin.ts | 10 +- app/core/modules/add-user.ts | 2 +- app/core/modules/array/add-disk-to-array.ts | 2 +- .../modules/array/remove-disk-from-array.ts | 2 +- app/core/modules/array/update-array.ts | 6 +- app/core/modules/array/update-parity-check.ts | 8 +- .../modules/docker/get-docker-containers.ts | 2 +- app/core/modules/get-parity-history.ts | 6 +- app/core/modules/get-permissions.ts | 8 +- app/core/modules/get-plugins.ts | 4 +- app/core/modules/get-services.ts | 56 ++++++---- app/core/modules/info/get-all-devices.ts | 104 +++++++++--------- app/core/modules/info/get-os.ts | 4 +- app/core/modules/info/get-unraid-version.ts | 2 +- app/core/modules/info/get-vms-count.ts | 10 +- .../{get-emhttpd.ts => get-emhttp.ts} | 4 +- app/core/modules/services/index.ts | 2 +- app/core/modules/shares/name/get-share.ts | 2 +- app/core/modules/users/id/add-role.ts | 2 +- app/core/modules/users/id/delete-user.ts | 2 +- app/core/modules/vms/get-domains.ts | 2 +- app/core/notifiers/email.ts | 5 +- app/core/notifiers/notifier.ts | 4 +- app/core/notifiers/unraid.ts | 4 +- app/core/states/devices.ts | 3 +- app/core/states/network.ts | 3 +- app/core/states/nfs-sec.ts | 3 +- app/core/states/shares.ts | 7 +- app/core/states/slots.ts | 3 +- app/core/states/smb-sec.ts | 3 +- app/core/states/state.ts | 1 + app/core/states/users.ts | 5 +- app/core/states/var.ts | 2 +- app/core/types/global.ts | 8 +- app/core/utils/casting.ts | 4 +- app/core/utils/clients/emcmd.ts | 4 +- app/core/utils/clients/nchan.ts | 69 +++++++----- app/core/utils/debugging/debug-timer.ts | 2 +- app/core/utils/misc/attempt-json-parse.ts | 2 +- app/core/utils/misc/attempt-read-file-sync.ts | 4 +- app/core/utils/misc/exit-app.ts | 2 + app/core/utils/misc/get-key-file.ts | 2 +- app/core/utils/misc/get-machine-id.ts | 2 +- app/core/utils/misc/global-error-handler.ts | 1 + app/core/utils/misc/parse-config.ts | 4 +- app/core/utils/misc/validate-api-key.ts | 40 +++---- app/core/utils/plugins/php-loader.ts | 8 +- app/core/utils/validation/has-fields.ts | 2 +- app/core/utils/vms/domain/sanitize-vendor.ts | 4 +- app/core/utils/vms/get-hypervisor.ts | 19 ++-- app/core/utils/vms/get-pci-devices.ts | 2 +- app/core/utils/write-to-boot.ts | 4 +- app/core/watchers/myservers.ts | 20 ++-- app/core/watchers/plugins.ts | 4 +- app/core/watchers/registration.ts | 52 ++++----- app/core/watchers/state-files.ts | 4 +- app/graphql/resolvers/query/disks.ts | 2 +- app/graphql/resolvers/query/display.ts | 8 +- app/graphql/schema/index.ts | 2 +- app/index.ts | 9 +- app/mothership/index.ts | 3 - app/run.ts | 6 +- app/server.ts | 28 ++--- app/types/index.d.ts | 1 - package.json | 14 +-- test/cli.spec.ts | 6 +- tsconfig.json | 6 +- 74 files changed, 404 insertions(+), 368 deletions(-) rename app/core/modules/services/{get-emhttpd.ts => get-emhttp.ts} (88%) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9525ab3aa..d65fd29e5 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,4 +5,9 @@ module.exports = { '@unraid', "plugin:unicorn/recommended" ], + rules: { + "unicorn/prefer-node-protocol": "error", + "unicorn/no-null": "off", + "unicorn/prevent-abbreviations": "off" + } }; \ No newline at end of file diff --git a/README.md b/README.md index 7cc343df6..35360f617 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ NCHAN=disable \ # Disable nchan polling PATHS_DYNAMIX_CONFIG=$(pwd)/dev/dynamix/dynamix.cfg \ # Dynamix's config file PATHS_MY_SERVERS_CONFIG=$(pwd)/dev/unraid.net/myservers.cfg \ # My servers config file PORT=8500 \ # What port unraid-api should start on (e.g. /var/run/unraid-api.sock or 8000) - node dist/cli.cjs --debug # Enable debug logging + node dist/cli.js --debug # Enable debug logging ``` ## Release diff --git a/app/cli.ts b/app/cli.ts index 43e03a6f1..a0bdfdcc0 100644 --- a/app/cli.ts +++ b/app/cli.ts @@ -1,6 +1,6 @@ -import fs from 'fs'; -import path from 'path'; -import { spawn, exec } from 'child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { spawn, exec } from 'node:child_process'; import { parse, ArgsParseOptions, ArgumentConfig } from 'ts-command-line-args'; import dotEnv from 'dotenv'; import findProcess from 'find-process'; @@ -10,14 +10,13 @@ import dedent from 'dedent-tabs'; import { version } from '../package.json'; import { paths } from './core/paths'; import { logger } from './core/log'; -import packageJson from '../package.json'; -const setEnv = (envName: string, value: any) => { +const setEnvironment = (key: string, value: any) => { if (!value || String(value).trim().length === 0) { return; } - process.env[envName] = String(value); + process.env[key] = String(value); }; interface Flags { @@ -31,18 +30,22 @@ interface Flags { version?: boolean; } -const args: ArgumentConfig = { +const arguments_: ArgumentConfig = { command: { type: String, defaultOption: true, optional: true }, help: { type: Boolean, optional: true, alias: 'h', description: 'Prints this usage guide.' }, debug: { type: Boolean, optional: true, alias: 'd', description: 'Enabled debug mode.' }, port: { type: String, optional: true, alias: 'p', description: 'Set the graphql port.' }, environment: { type: String, typeLabel: '{underline production/staging/development}', optional: true, description: 'Set the working environment.' }, - 'log-level': { type: (level?: string) => { - return ['error', 'warn', 'info', 'debug', 'trace', 'silly'].includes(level ?? '') ? level : undefined; - }, typeLabel: '{underline error/warn/info/debug/trace/silly}', optional: true, description: 'Set the log level.' }, - 'log-transport': { type: (transport?: string) => { - return ['console', 'syslog'].includes(transport ?? 'console') ? transport : 'console'; - }, typeLabel: '{underline console/syslog}', optional: true, description: 'Set the log transport. (default=syslog)' }, + 'log-level': { + type: (level?: string) => { + return ['error', 'warn', 'info', 'debug', 'trace', 'silly'].includes(level ?? '') ? level : undefined; + }, typeLabel: '{underline error/warn/info/debug/trace/silly}', optional: true, description: 'Set the log level.' + }, + 'log-transport': { + type: (transport?: string) => { + return ['console', 'syslog'].includes(transport ?? 'console') ? transport : 'console'; + }, typeLabel: '{underline console/syslog}', optional: true, description: 'Set the log transport. (default=syslog)' + }, version: { type: Boolean, optional: true, alias: 'v', description: 'Show version.' } }; @@ -61,7 +64,7 @@ const options: ArgsParseOptions = { footerContentSections: [{ header: '', content: 'Copyright © 2021 Lime Technology, Inc.' }] }; -const mainOptions = parse(args, { ...options, partial: true, stopAtFirstUnknown: true }); +const mainOptions = parse(arguments_, { ...options, partial: true, stopAtFirstUnknown: true }); const commandOptions = (mainOptions as Flags & { _unknown: string[] })._unknown || []; const command: string = (mainOptions as any).command; // Use the env passed by the user, then the flag inline, then default to production @@ -92,18 +95,18 @@ const commands = { process.chdir(paths.get('unraid-api-base')!); // Set envs - setEnv('DEBUG', mainOptions.debug); - setEnv('ENVIRONMENT', getEnvironment()); - setEnv('LOG_LEVEL', mainOptions['log-level'] ?? (mainOptions.debug ? 'debug' : 'info')); - setEnv('LOG_TRANSPORT', mainOptions['log-transport']); - setEnv('PORT', mainOptions.port); + setEnvironment('DEBUG', mainOptions.debug); + setEnvironment('ENVIRONMENT', getEnvironment()); + setEnvironment('LOG_LEVEL', mainOptions['log-level'] ?? (mainOptions.debug ? 'debug' : 'info')); + setEnvironment('LOG_TRANSPORT', mainOptions['log-transport']); + setEnvironment('PORT', mainOptions.port); - console.info(`Starting unraid-api v${packageJson.version as string}`); + console.info(`Starting unraid-api v${version}`); console.info(`Connecting to the "${getEnvironment()}" environment.`); // Load bundled index file const indexPath = './index.js'; - require(indexPath); + await import(indexPath); if (!mainOptions.debug) { if ('_DAEMONIZE_PROCESS' in process.env) { @@ -129,6 +132,7 @@ const commands = { console.log('Daemonized successfully!'); // Exit cleanly + // eslint-disable-next-line unicorn/no-process-exit process.exit(0); } } @@ -161,7 +165,7 @@ const commands = { * Print API version. */ async version() { - console.log(`Unraid API v${version as string}`); + console.log(`Unraid API v${version}`); }, async status() { // Find all processes called "unraid-api" which aren't this process @@ -190,53 +194,43 @@ const commands = { }, async 'switch-env'() { const basePath = paths.get('unraid-api-base')!; - const envFlashFilePath = paths.get('myservers-env')!; - const envFile = await fs.promises.readFile(envFlashFilePath, 'utf-8').catch(() => ''); + const environmentFlashFilePath = paths.get('myservers-env')!; + const environmentFile = await fs.promises.readFile(environmentFlashFilePath, 'utf-8').catch(() => ''); - logger.debug('Checking %s for current ENV, found %s', envFlashFilePath, envFile); + logger.debug('Checking %s for current ENV, found %s', environmentFlashFilePath, environmentFile); // Match the env file env="production" which would be [0] = env="production", [1] = env and [2] = production - const matchArray = /([a-zA-Z]+)=["]*([a-zA-Z]+)["]*/.exec(envFile); + const matchArray = /([A-Za-z]+)="*([A-Za-z]+)"*/.exec(environmentFile); // Get item from index 2 of the regex match or return undefined - const [,,currentEnvInFile] = matchArray && matchArray.length === 3 ? matchArray : []; + const [_, __, currentEnvironmentInFile] = matchArray && matchArray.length === 3 ? matchArray : []; - let newEnv = 'production'; - - // Switch from staging to production - if (currentEnvInFile === 'staging') { - newEnv = 'production'; - } - - // Switch from production to staging - if (currentEnvInFile === 'production') { - newEnv = 'staging'; - } - - if (currentEnvInFile) { - console.info('Switching from "%s" to "%s"...', currentEnvInFile, newEnv); + const newEnvironment = currentEnvironmentInFile === 'production' ? 'staging' : 'production'; + if (currentEnvironmentInFile) { + console.info('Switching from "%s" to "%s"...', currentEnvironmentInFile, newEnvironment); } else { console.info('No ENV found, setting env to "production"...'); } // Write new env to flash - const newEnvLine = `env="${newEnv}"`; - await fs.promises.writeFile(envFlashFilePath, newEnvLine); - logger.debug('Writing %s to %s', newEnvLine, envFlashFilePath); + const newEnvironmentLine = `env="${newEnvironment}"`; + await fs.promises.writeFile(environmentFlashFilePath, newEnvironmentLine); + logger.debug('Writing %s to %s', newEnvironmentLine, environmentFlashFilePath); // Copy the new env over to live location before restarting - const source = path.join(basePath, `.env.${newEnv}`); + const source = path.join(basePath, `.env.${newEnvironment}`); const destination = path.join(basePath, '.env'); logger.debug('Copying %s to %s', source, destination); await new Promise((resolve, reject) => { // Use the native cp command to ensure we're outside the virtual file system - exec(`cp "${source}" "${destination}"`, error => { - if (error) { - return reject(error); - } + exec(`cp "${source}" "${destination}"`, error => { + if (error) { + reject(error); + return; + } - resolve(); - }); - }); + resolve(); + }); + }); // If there's a process running restart it const unraidApiPid = await getUnraidApiPid(); @@ -244,7 +238,8 @@ const commands = { console.info('unraid-api is running, restarting...'); // Restart the process - return this.restart(); + await this.restart(); + return; } console.info('Run "unraid-api start" to start the API.'); @@ -255,11 +250,12 @@ async function main() { if (!command) { if (mainOptions.version) { await commands.version(); + // eslint-disable-next-line unicorn/no-process-exit process.exit(); } // Run help command - parse(args, { ...options, partial: true, stopAtFirstUnknown: true, argv: ['-h'] }); + parse(arguments_, { ...options, partial: true, stopAtFirstUnknown: true, argv: ['-h'] }); } // Unknown command diff --git a/app/consts.ts b/app/consts.ts index 924237118..fddd25fdd 100644 --- a/app/consts.ts +++ b/app/consts.ts @@ -2,7 +2,7 @@ import { config } from './core/config'; const internalWsAddress = () => { const port = config.get('port') as number | string; - return isNaN(port as any) ? + return Number.isNaN(port as any) ? // Unix Socket `ws+unix:${port}` : // Numbered port diff --git a/app/core/api-manager.ts b/app/core/api-manager.ts index 919fd43b7..4bcc9574d 100644 --- a/app/core/api-manager.ts +++ b/app/core/api-manager.ts @@ -156,7 +156,6 @@ export class ApiManager extends EventEmitter { replace(name: string, key: string, options: KeyOptions) { // Delete existing key // @ts-expect-error - // eslint-disable-next-line unicorn/no-null this.keys.items[name] = null; // Add new key diff --git a/app/core/discovery/listen.ts b/app/core/discovery/listen.ts index ba718ce33..7e932e816 100644 --- a/app/core/discovery/listen.ts +++ b/app/core/discovery/listen.ts @@ -7,22 +7,19 @@ import { discoveryLogger as log } from '../log'; export const listen = async () => { stw .on('up', service => { - if (service.type === 'unraid') { - if (service.txt?.is_setup === 'false') { - const ipv4 = service.addresses.find(address => address.includes('.')); - const ipv6 = service.addresses.find(address => address.includes(':')); - const ipAddress = ipv4 ?? ipv6; - // No ip? - if (!ipAddress) { - return; - } - - log.info(`Found a new local server [${ipAddress}], visit your my servers dashboard to claim.`); + if (service.type === 'unraid' && service.txt?.is_setup === 'false') { + const ipv4 = service.addresses.find(address => address.includes('.')); + const ipv6 = service.addresses.find(address => address.includes(':')); + const ipAddress = ipv4 ?? ipv6; + // No ip? + if (!ipAddress) { + return; } + + log.info(`Found a new local server [${ipAddress}], visit your my servers dashboard to claim.`); } - // Console.log(`${service.name} is up! (from ${referrer.address}`); }) - .on('down', (remoteService, _res, referrer) => { + .on('down', (remoteService, _response, referrer) => { log.debug(`${remoteService.name} is down! (from ${referrer.address})`); }); diff --git a/app/core/errors/param-invalid-error.ts b/app/core/errors/param-invalid-error.ts index 605aa048c..d37d0baab 100644 --- a/app/core/errors/param-invalid-error.ts +++ b/app/core/errors/param-invalid-error.ts @@ -3,13 +3,13 @@ * Written by: Alexis Tyler */ -import { format } from 'util'; +import { format } from 'node:util'; import { AppError } from './app-error'; /** * Invalid param provided to module */ -export class ParamInvalidError extends AppError { +export class ParameterInvalidError extends AppError { constructor(parameterName: string, parameter: any) { // Overriding both message and status code. super(format('Param invalid: %s = %s', parameterName, parameter), 500); diff --git a/app/core/modules/add-plugin.ts b/app/core/modules/add-plugin.ts index 3a2d6ba32..1d3d2124f 100644 --- a/app/core/modules/add-plugin.ts +++ b/app/core/modules/add-plugin.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import path from 'path'; +import path from 'node:path'; import packageJson from 'package-json'; import dlTgz from 'dl-tgz'; import observableToPromise from 'observable-to-promise'; @@ -35,19 +35,19 @@ export const addPlugin = async (context: Context): Promise => { // Validation const missingFields = hasFields(context.data, ['name']); - if (missingFields.length !== 0) { + if (missingFields.length > 0) { // Log first error. throw new FieldMissingError(missingFields[0]); } // Get package metadata const { name, version } = context.data; - const pkg = await packageJson(name, { + const package_ = await packageJson(name, { allVersions: Boolean(version) }); // Plugin tgz url - const latest = pkg.versions[version]; + const latest = package_.versions[version]; const url = latest.dist.tarball; const pluginCwd = paths.get('plugins')!; @@ -63,7 +63,7 @@ export const addPlugin = async (context: Context): Promise => { return { text: 'Plugin added successfully.', json: { - pkg + pkg: package_ } }; }; diff --git a/app/core/modules/add-user.ts b/app/core/modules/add-user.ts index b2cb763c9..cd3e7d5cf 100644 --- a/app/core/modules/add-user.ts +++ b/app/core/modules/add-user.ts @@ -37,7 +37,7 @@ export const addUser = async (context: Context): Promise => { const { name, description = '', password } = data; const missingFields = hasFields(data, ['name', 'password']); - if (missingFields.length !== 0) { + if (missingFields.length > 0) { // Only log first error. throw new FieldMissingError(missingFields[0]); } diff --git a/app/core/modules/array/add-disk-to-array.ts b/app/core/modules/array/add-disk-to-array.ts index db9afcf51..fb6c9f51a 100644 --- a/app/core/modules/array/add-disk-to-array.ts +++ b/app/core/modules/array/add-disk-to-array.ts @@ -22,7 +22,7 @@ export const addDiskToArray = async function (context: CoreContext): Promise 0) { // Just log first error throw new FieldMissingError(missingFields[0]); } diff --git a/app/core/modules/array/remove-disk-from-array.ts b/app/core/modules/array/remove-disk-from-array.ts index 706230f3b..49e615328 100644 --- a/app/core/modules/array/remove-disk-from-array.ts +++ b/app/core/modules/array/remove-disk-from-array.ts @@ -31,7 +31,7 @@ export const removeDiskFromArray = async (context: Context): Promise const missingFields = hasFields(data, ['id']); - if (missingFields.length !== 0) { + if (missingFields.length > 0) { // Only log first error throw new FieldMissingError(missingFields[0]); } diff --git a/app/core/modules/array/update-array.ts b/app/core/modules/array/update-array.ts index 472de1e43..f0e4aa727 100644 --- a/app/core/modules/array/update-array.ts +++ b/app/core/modules/array/update-array.ts @@ -5,7 +5,7 @@ import { CoreContext, CoreResult } from '../../types'; import { hasFields, ensurePermission, emcmd, arrayIsRunning, uppercaseFirstChar } from '../../utils'; -import { AppError, FieldMissingError, ParamInvalidError } from '../../errors'; +import { AppError, FieldMissingError, ParameterInvalidError } from '../../errors'; import { getArray } from '..'; // @TODO: Fix this not working across node apps @@ -25,7 +25,7 @@ export const updateArray = async (context: CoreContext): Promise => const missingFields = hasFields(data, ['state']); - if (missingFields.length !== 0) { + if (missingFields.length > 0) { // Only log first error throw new FieldMissingError(missingFields[0]); } @@ -35,7 +35,7 @@ export const updateArray = async (context: CoreContext): Promise => const pendingState = nextState === 'stop' ? 'stopping' : 'starting'; if (!['start', 'stop'].includes(nextState)) { - throw new ParamInvalidError('state', nextState); + throw new ParameterInvalidError('state', nextState); } // Prevent this running multiple times at once diff --git a/app/core/modules/array/update-parity-check.ts b/app/core/modules/array/update-parity-check.ts index a372c5d1e..1e679a836 100644 --- a/app/core/modules/array/update-parity-check.ts +++ b/app/core/modules/array/update-parity-check.ts @@ -4,7 +4,7 @@ */ import { CoreContext, CoreResult } from '../../types'; -import { FieldMissingError, ParamInvalidError } from '../../errors'; +import { FieldMissingError, ParameterInvalidError } from '../../errors'; import { emcmd, ensurePermission } from '../../utils'; import { varState } from '../../states'; @@ -36,7 +36,7 @@ export const updateParityCheck = async (context: Context): Promise = throw new FieldMissingError('state'); } - const { state: wantedState } = data; + const { state: wantedState, correct } = data; const running = varState?.data?.mdResync !== 0; const states = { pause: { @@ -62,11 +62,11 @@ export const updateParityCheck = async (context: Context): Promise = // Only allow states from states object if (!allowedStates.includes(wantedState)) { - throw new ParamInvalidError('state', wantedState); + throw new ParameterInvalidError('state', wantedState); } // Should we write correction to the parity during the check - const writeCorrectionsToParity = wantedState === 'start' && data.correct; + const writeCorrectionsToParity = wantedState === 'start' && correct; await emcmd({ startState: 'STARTED', diff --git a/app/core/modules/docker/get-docker-containers.ts b/app/core/modules/docker/get-docker-containers.ts index a0ec80b43..7ce0c2be4 100644 --- a/app/core/modules/docker/get-docker-containers.ts +++ b/app/core/modules/docker/get-docker-containers.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import fs from 'fs'; +import fs from 'node:fs'; import camelCaseKeys from 'camelcase-keys'; import { paths } from '../../paths'; import { docker, catchHandlers, ensurePermission } from '../../utils'; diff --git a/app/core/modules/get-parity-history.ts b/app/core/modules/get-parity-history.ts index eb0ead652..0b1495649 100644 --- a/app/core/modules/get-parity-history.ts +++ b/app/core/modules/get-parity-history.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import { promises as fs } from 'fs'; +import { promises as fs } from 'node:fs'; import { CoreResult, CoreContext } from '../types'; import { paths } from '../paths'; import { FileMissingError } from '../errors'; @@ -47,7 +47,7 @@ export const getParityHistory = async (context: CoreContext): Promise { + for (const check of parityChecks) { const array = Object.values({ ...check, speed: check.speed ? check.speed : 'Unavailable', @@ -55,7 +55,7 @@ export const getParityHistory = async (context: CoreContext): Promise { // @ts-expect-error const { $extend, ...grants } = grant; return [name, grants]; }) - .reduce((object, { + .map(({ 0: key, 1: value - }) => Object.assign(object, { - [key.toString()]: value - }), {}); + }) => [key.toString(), value])); return { text: `Scopes: ${JSON.stringify(scopes, null, 2)}`, diff --git a/app/core/modules/get-plugins.ts b/app/core/modules/get-plugins.ts index a35a7d1c4..cf476afb9 100644 --- a/app/core/modules/get-plugins.ts +++ b/app/core/modules/get-plugins.ts @@ -4,7 +4,7 @@ */ import { CoreContext, CoreResult } from '../types'; -import { ParamInvalidError } from '../errors'; +import { ParameterInvalidError } from '../errors'; import { Plugin, pluginManager } from '../plugin-manager'; import { ensurePermission } from '../utils'; @@ -32,7 +32,7 @@ export const getPlugins = (context: Readonly): Result => { const { filter = 'all' } = query; if (!['all', 'active', 'inactive'].includes(filter)) { - throw new ParamInvalidError('filter', filter); + throw new ParameterInvalidError('filter', filter); } const plugins = pluginManager.getAllPlugins().map(plugin => { diff --git a/app/core/modules/get-services.ts b/app/core/modules/get-services.ts index 2afda79d2..e0d86accd 100644 --- a/app/core/modules/get-services.ts +++ b/app/core/modules/get-services.ts @@ -3,10 +3,9 @@ * Written by: Alexis Tyler */ -import { getEmhttpdService, getUnraidApiService } from './services'; +import { getEmhttpService, getUnraidApiService } from './services'; import { coreLogger } from '../log'; -import { envs } from '../environments'; -import { NodeService } from '../utils'; +import { environmentVariables } from '../environments'; import { CoreResult, CoreContext } from '../types'; const devNames = [ @@ -18,7 +17,19 @@ const coreNames = [ 'unraid-api' ]; -interface ServiceResult extends CoreResult { +interface Uptime { + timestamp: string; + seconds?: number; +} + +interface NodeService { + name: string; + online?: boolean; + uptime: Uptime; + version?: string; +} + +interface ServiceResult extends CoreResult { json: NodeService; } @@ -27,33 +38,36 @@ interface NodeServiceWithName extends NodeService { } /** - * Add name to services. + * Add name to results. * - * @param services + * @param results * @param names */ -const addNameToService = (services: ServiceResult[], names: string[]): NodeServiceWithName[] => { - return services.map((service, index) => ({ - name: names[index], - ...service.json - })); +const addNameToResult = (results: Array, names: string[]): NodeServiceWithName[] => { + return results.map((result, index) => { + const { name: _name, ...ResultData } = result.json; + return ({ + name: names[index], + ...ResultData + }); + }); }; -interface Result extends CoreResult { +interface Result extends CoreResult { json: NodeServiceWithName[]; } +const logErrorAndReturnEmptyArray = (error: Error) => { + coreLogger.error(error); + return []; +}; + /** * Get all services. */ export const getServices = async (context: CoreContext): Promise => { - const logErrorAndReturnEmptyArray = (error: Error) => { - coreLogger.error(error); - return []; - }; - - const devServices = envs.NODE_ENV === 'development' ? await Promise.all([ - getEmhttpdService(context) + const devServices = environmentVariables.NODE_ENV === 'development' ? await Promise.all([ + getEmhttpService(context) ]).catch(logErrorAndReturnEmptyArray) : []; const coreServices = await Promise.all([ @@ -61,8 +75,8 @@ export const getServices = async (context: CoreContext): Promise => { ]).catch(logErrorAndReturnEmptyArray); const result = [ - ...addNameToService(devServices, devNames), - ...addNameToService(coreServices, coreNames) + ...addNameToResult(devServices, devNames), + ...addNameToResult(coreServices, coreNames) ]; return { diff --git a/app/core/modules/info/get-all-devices.ts b/app/core/modules/info/get-all-devices.ts index a44874591..37f88c2e8 100644 --- a/app/core/modules/info/get-all-devices.ts +++ b/app/core/modules/info/get-all-devices.ts @@ -89,7 +89,7 @@ const systemPciDevices = async (): Promise => { const processedDevices = await filterDevices(filteredDevices).then(async devices => { return Promise.all(devices // @ts-expect-error - .map(addDeviceClass) + .map(device => addDeviceClass(device)) .map(async device => { // Attempt to get the current kernel-bound driver for this pci device await isSymlink(`${basePath}${device.id}/driver`).then(symlink => { @@ -136,6 +136,48 @@ const systemAudioDevices = systemPciDevices().then(devices => { return devices.filter(device => device.class === 'audio' && !device.allowed); }); +const parseUsbDevices = (stdout: string) => stdout.split('\n').map(line => { + const regex = new RegExp(/^.+: ID (?\S+)(?.*)$/); + const result = regex.exec(line); + return (result!.groups as unknown as PciDevice); +}) || []; + +// Remove boot drive +const filterBootDrive = (device: Readonly): boolean => varState?.data?.flashGuid !== device.guid; + +// Clean up the name +const sanitizeVendorName = (device: Readonly) => { + const vendorname = sanitizeVendor(device.vendorname || ''); + return { + ...device, + vendorname + }; +}; + +const parseDeviceLine = (line?: Readonly): { value: string; string: string } => { + const emptyLine = { value: '', string: '' }; + + // If the line is blank return nothing + if (!line) { + return emptyLine; + } + + // Parse the line + const [, _] = line.split(/[\t ]{2,}/).filter(Boolean); + // eslint-disable-next-line @typescript-eslint/prefer-regexp-exec + const match = _.match(/^(\S+)\s(.*)/)?.slice(1); + + // If there's no match return nothing + if (!match) { + return emptyLine; + } + + return { + value: match[0], + string: match[1] + }; +}; + /** * System usb devices. * @returns Array of USB devices. @@ -150,46 +192,10 @@ const getSystemUSBDevices = async (): Promise => { }); }).catch(() => []); - // Remove boot drive - const filterBootDrive = (device: Readonly): boolean => varState?.data?.flashGuid !== device.guid; - // Remove usb hubs // @ts-expect-error const filterUsbHubs = (device: Readonly): boolean => !usbHubs.includes(device.id); - // Clean up the name - const sanitizeVendorName = (device: Readonly) => { - const vendorname = sanitizeVendor(device.vendorname || ''); - return { - ...device, - vendorname - }; - }; - - const parseDeviceLine = (line: Readonly): { value: string; string: string } => { - const emptyLine = { value: '', string: '' }; - - // If the line is blank return nothing - if (!line) { - return emptyLine; - } - - // Parse the line - const [, _] = line.split(/[ \t]{2,}/).filter(Boolean); - // eslint-disable-next-line @typescript-eslint/prefer-regexp-exec - const match = _.match(/^(\S+)\s(.*)/)?.slice(1); - - // If there's no match return nothing - if (!match) { - return emptyLine; - } - - return { - value: match[0], - string: match[1] - }; - }; - // Add extra fields to device const parseDevice = (device: Readonly) => { const modifiedDevice: PciDevice = { @@ -197,11 +203,11 @@ const getSystemUSBDevices = async (): Promise => { }; const info = execa.commandSync(`lsusb -d ${device.id} -v`).stdout.split('\n'); const deviceName = device.name.trim(); - const iSerial = parseDeviceLine(info.filter(line => line.includes('iSerial'))[0]); - const iProduct = parseDeviceLine(info.filter(line => line.includes('iProduct'))[0]); - const iManufacturer = parseDeviceLine(info.filter(line => line.includes('iManufacturer'))[0]); - const idProduct = parseDeviceLine(info.filter(line => line.includes('idProduct'))[0]); - const idVendor = parseDeviceLine(info.filter(line => line.includes('idVendor'))[0]); + const iSerial = parseDeviceLine(info.find(line => line.includes('iSerial'))); + const iProduct = parseDeviceLine(info.find(line => line.includes('iProduct'))); + const iManufacturer = parseDeviceLine(info.find(line => line.includes('iManufacturer'))); + const idProduct = parseDeviceLine(info.find(line => line.includes('idProduct'))); + const idVendor = parseDeviceLine(info.find(line => line.includes('idVendor'))); const serial = `${iSerial.string.slice(8).slice(0, 4)}-${iSerial.string.slice(8).slice(4)}`; const guid = `${idVendor.value.slice(2)}-${idProduct.value.slice(2)}-${serial}`; @@ -226,19 +232,13 @@ const getSystemUSBDevices = async (): Promise => { return modifiedDevice; }; - const parseUsbDevices = (stdout: string) => stdout.split('\n').map(line => { - const regex = new RegExp(/^.+: ID (?\S+)(?.*)$/); - const result = regex.exec(line); - return (result!.groups as unknown as PciDevice); - }) || []; - // Get all usb devices const usbDevices = await execa('lsusb').then(async ({ stdout }) => { return parseUsbDevices(stdout) - .map(parseDevice) - .filter(filterBootDrive) - .filter(filterUsbHubs) - .map(sanitizeVendorName); + .map(device => parseDevice(device)) + .filter(device => filterBootDrive(device)) + .filter(device => filterUsbHubs(device)) + .map(device => sanitizeVendorName(device)); }); return usbDevices; diff --git a/app/core/modules/info/get-os.ts b/app/core/modules/info/get-os.ts index 6d8118d7b..a48cc3697 100644 --- a/app/core/modules/info/get-os.ts +++ b/app/core/modules/info/get-os.ts @@ -3,13 +3,13 @@ * Written by: Alexis Tyler */ -import { uptime } from 'os'; +import { uptime } from 'node:os'; import si from 'systeminformation'; import { CoreContext, CoreResult } from '../../types'; import { ensurePermission } from '../../utils'; // Get uptime on boot and convert to date -const bootTimestamp = new Date(new Date().getTime() - (uptime() * 1000)); +const bootTimestamp = new Date(Date.now() - (uptime() * 1000)); /** * Get OS info diff --git a/app/core/modules/info/get-unraid-version.ts b/app/core/modules/info/get-unraid-version.ts index fee098c06..9423bf0c4 100644 --- a/app/core/modules/info/get-unraid-version.ts +++ b/app/core/modules/info/get-unraid-version.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import fs from 'fs'; +import fs from 'node:fs'; import semver from 'semver'; import { paths } from '../../paths'; import { CacheManager } from '../../cache-manager'; diff --git a/app/core/modules/info/get-vms-count.ts b/app/core/modules/info/get-vms-count.ts index cc2d1868d..918f413da 100644 --- a/app/core/modules/info/get-vms-count.ts +++ b/app/core/modules/info/get-vms-count.ts @@ -37,8 +37,12 @@ export const getVmsCount = async function (context: CoreContext): Promise => { +export const getEmhttpService = async (context: CoreContext): Promise => { const { user } = context; // Check permissions diff --git a/app/core/modules/services/index.ts b/app/core/modules/services/index.ts index 366730694..1bc3600c0 100644 --- a/app/core/modules/services/index.ts +++ b/app/core/modules/services/index.ts @@ -1,4 +1,4 @@ // Created from 'create-ts-index' -export * from './get-emhttpd'; +export * from './get-emhttp'; export * from './get-unraid-api'; diff --git a/app/core/modules/shares/name/get-share.ts b/app/core/modules/shares/name/get-share.ts index 0408306a2..4fc50f7dd 100644 --- a/app/core/modules/shares/name/get-share.ts +++ b/app/core/modules/shares/name/get-share.ts @@ -38,7 +38,7 @@ export const getShare = async function (context: Context): Promise { const share = [ userShare, diskShare - ].filter(_ => _)[0]; + ].find(_ => _); if (!share) { throw new AppError('No share found with that name.', 404); diff --git a/app/core/modules/users/id/add-role.ts b/app/core/modules/users/id/add-role.ts index 5d59f3d5b..cb3c3ebaa 100644 --- a/app/core/modules/users/id/add-role.ts +++ b/app/core/modules/users/id/add-role.ts @@ -32,7 +32,7 @@ export const addRole = async (context: Context): Promise => { const { name } = params; const missingFields = hasFields(params, ['name']); - if (missingFields.length !== 0) { + if (missingFields.length > 0) { throw new FieldMissingError(missingFields[0]); } diff --git a/app/core/modules/users/id/delete-user.ts b/app/core/modules/users/id/delete-user.ts index e2abe1f4f..43c9f2357 100644 --- a/app/core/modules/users/id/delete-user.ts +++ b/app/core/modules/users/id/delete-user.ts @@ -30,7 +30,7 @@ export const deleteUser = async (context: Context): Promise => { const { name } = params; const missingFields = hasFields(params, ['name']); - if (missingFields.length !== 0) { + if (missingFields.length > 0) { // Just throw the first error throw new FieldMissingError(missingFields[0]); } diff --git a/app/core/modules/vms/get-domains.ts b/app/core/modules/vms/get-domains.ts index 519a7d00d..20f444d1a 100644 --- a/app/core/modules/vms/get-domains.ts +++ b/app/core/modules/vms/get-domains.ts @@ -59,7 +59,7 @@ export const getDomains = async (context: CoreContext): Promise => { text: `Defined domains: ${JSON.stringify(activeDomainNames, null, 2)}\nActive domains: ${JSON.stringify(inactiveDomainNames, null, 2)}`, json: resolvedDomains }; - } catch (error: unknown) { + } catch { // If we hit an error expect libvirt to be offline return { text: `Defined domains: ${JSON.stringify([], null, 2)}\nActive domains: ${JSON.stringify([], null, 2)}`, diff --git a/app/core/notifiers/email.ts b/app/core/notifiers/email.ts index ccdfd1383..f72104f41 100644 --- a/app/core/notifiers/email.ts +++ b/app/core/notifiers/email.ts @@ -34,7 +34,7 @@ export class EmailNotifier extends Notifier { } send(options: SendOptions) { - const { type = 'generic', title = 'Unraid Server Notification' } = options; + const { type = 'generic', title = 'Unraid Server Notification', data, ...renderOptions } = options; const { to, from, replyTo, level } = this; // Only show info when in debug const silent = level !== 'debug'; @@ -50,7 +50,8 @@ export class EmailNotifier extends Notifier { // Render template this.template = Object.keys(templates).includes(type) ? templates[type] : templates.generic; - const html = this.render({ ...options, json: JSON.stringify(options.data, null, 2) }, this.helpers); + // eslint-disable-next-line unicorn/consistent-destructuring + const html = this.render({ type, title, ...renderOptions, json: JSON.stringify(data, null, 2) }, this.helpers); return sendMail({ from, diff --git a/app/core/notifiers/notifier.ts b/app/core/notifiers/notifier.ts index 463be7f52..7bd37307b 100644 --- a/app/core/notifiers/notifier.ts +++ b/app/core/notifiers/notifier.ts @@ -25,6 +25,8 @@ export interface NotifierSendOptions { computed: LooseObject; } +const generateHelper = (func: (text: string) => string) => (text: string, render: (text: string) => string) => func(render(text)); + /** * Base notifier. * @param Alert level. @@ -70,6 +72,6 @@ export class Notifier { * @param func Function to be wrapped. */ generateHelper(func: (text: string) => string) { - return () => (text: string, render: (text: string) => string) => func(render(text)); + return generateHelper(func); } } diff --git a/app/core/notifiers/unraid.ts b/app/core/notifiers/unraid.ts index 7913550aa..7c75a2fb1 100644 --- a/app/core/notifiers/unraid.ts +++ b/app/core/notifiers/unraid.ts @@ -31,8 +31,8 @@ export class UnraidNotifier extends HttpNotifier { */ async send(options: NotifierSendOptions) { const { endpoint, transport } = this; - const { type = 'generic', title = 'Unraid Server Notification' } = options; - const { ...body } = options.data; + const { type = 'generic', title = 'Unraid Server Notification', data } = options; + const { ...body } = data; const headers = { Accept: 'application/json, text/plain, */*', diff --git a/app/core/states/devices.ts b/app/core/states/devices.ts index 103822f7f..f05f398a3 100644 --- a/app/core/states/devices.ts +++ b/app/core/states/devices.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import path from 'path'; +import path from 'node:path'; import mm from 'micromongo'; import { paths } from '../paths'; import { parseConfig } from '../utils/misc'; @@ -60,6 +60,7 @@ class Devices extends ArrayState { } find(query?: LooseObject): Device[] { + // eslint-disable-next-line unicorn/no-array-callback-reference return super.find(query); } diff --git a/app/core/states/network.ts b/app/core/states/network.ts index 806bf02dd..87ab315d2 100644 --- a/app/core/states/network.ts +++ b/app/core/states/network.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import path from 'path'; +import path from 'node:path'; import mm from 'micromongo'; import { paths } from '../paths'; import { LooseObject, IniStringBoolean, CommaSeparatedString } from '../types'; @@ -105,6 +105,7 @@ class Network extends ArrayState { } find(query?: LooseObject): Network[] { + // eslint-disable-next-line unicorn/no-array-callback-reference return super.find(query); } diff --git a/app/core/states/nfs-sec.ts b/app/core/states/nfs-sec.ts index 881cfe3ec..76c20af32 100644 --- a/app/core/states/nfs-sec.ts +++ b/app/core/states/nfs-sec.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import path from 'path'; +import path from 'node:path'; import { paths } from '../paths'; import { ArrayState } from './state'; import { parseConfig } from '../utils/misc'; @@ -62,6 +62,7 @@ class NfsSec extends ArrayState { } find(query?: LooseObject): SecIni[] { + // eslint-disable-next-line unicorn/no-array-callback-reference return super.find(query); } } diff --git a/app/core/states/shares.ts b/app/core/states/shares.ts index 5df39d521..ffa138067 100644 --- a/app/core/states/shares.ts +++ b/app/core/states/shares.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import path from 'path'; +import path from 'node:path'; import mm from 'micromongo'; import { paths } from '../paths'; import { parseConfig } from '../utils/misc'; @@ -25,8 +25,8 @@ const parse = (state: SharesIni[]): Share[] => { .map(([_, item]) => { const { free, size, include, exclude, useCache, ...rest } = item; const share: Share = { - free: parseInt(free, 10), - size: parseInt(size, 10), + free: Number.parseInt(free, 10), + size: Number.parseInt(size, 10), include: include.split(',').filter(_ => _), exclude: exclude.split(',').filter(_ => _), cache: useCache === 'yes', @@ -73,6 +73,7 @@ class Shares extends ArrayState { } find(query?: LooseObject): Share[] { + // eslint-disable-next-line unicorn/no-array-callback-reference return super.find(query); } diff --git a/app/core/states/slots.ts b/app/core/states/slots.ts index 8b20fa397..5835fee6a 100644 --- a/app/core/states/slots.ts +++ b/app/core/states/slots.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import path from 'path'; +import path from 'node:path'; import mm from 'micromongo'; import { paths } from '../paths'; import { Slot } from '../types/states'; @@ -111,6 +111,7 @@ class Slots extends ArrayState { } find(query?: LooseObject): Slot[] { + // eslint-disable-next-line unicorn/no-array-callback-reference return super.find(query); } diff --git a/app/core/states/smb-sec.ts b/app/core/states/smb-sec.ts index 8d4f41b9c..738fe4911 100644 --- a/app/core/states/smb-sec.ts +++ b/app/core/states/smb-sec.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import path from 'path'; +import path from 'node:path'; import { paths } from '../paths'; import { ArrayState } from './state'; import { parseConfig } from '../utils/misc'; @@ -94,6 +94,7 @@ class SmbSec extends ArrayState { } find(query?: Record): any[] { + // eslint-disable-next-line unicorn/no-array-callback-reference return super.find(query); } } diff --git a/app/core/states/state.ts b/app/core/states/state.ts index ea8c23c66..e2480b28a 100644 --- a/app/core/states/state.ts +++ b/app/core/states/state.ts @@ -69,6 +69,7 @@ export class ArrayState extends State { } find(query: LooseObject = {}) { + // eslint-disable-next-line unicorn/no-array-callback-reference, unicorn/no-array-method-this-argument return mm.find(this.data, query); } diff --git a/app/core/states/users.ts b/app/core/states/users.ts index 0f7a917e1..f8e68f6b2 100644 --- a/app/core/states/users.ts +++ b/app/core/states/users.ts @@ -4,7 +4,7 @@ */ import mm from 'micromongo'; -import path from 'path'; +import path from 'node:path'; import { paths } from '../paths'; import { User } from '../types/states'; import { LooseObject } from '../types'; @@ -33,7 +33,7 @@ const parseUser = (state: UserIni): User => { return user; }; -const parse = (states: UserIni[]): User[] => Object.values(states).map(parseUser); +const parse = (states: UserIni[]): User[] => Object.values(states).map(state => parseUser(state)); class Users extends ArrayState { private static instance: Users; @@ -77,6 +77,7 @@ class Users extends ArrayState { } find(query?: LooseObject): User[] { + // eslint-disable-next-line unicorn/no-array-callback-reference, unicorn/no-array-method-this-argument return mm.find(this.data, query); } diff --git a/app/core/states/var.ts b/app/core/states/var.ts index 9f4ddb61a..03bb54d2e 100644 --- a/app/core/states/var.ts +++ b/app/core/states/var.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import path from 'path'; +import path from 'node:path'; import { paths } from '../paths'; import { Var } from '../types/states'; import { IniStringBooleanOrAuto, IniStringBoolean } from '../types/ini'; diff --git a/app/core/types/global.ts b/app/core/types/global.ts index 540c6dffd..6943263ee 100644 --- a/app/core/types/global.ts +++ b/app/core/types/global.ts @@ -22,11 +22,15 @@ export interface CoreContext { readonly user: Readonly; } +type AnyJson = boolean | number | string | null | JsonArray | JsonMap; +type JsonMap = Record; +interface JsonArray extends Array {} + /** * Result object */ -export interface CoreResult { - json?: Record | Array> | null; +export interface CoreResult { + json?: Json; text?: string; html?: string; } diff --git a/app/core/utils/casting.ts b/app/core/utils/casting.ts index 418dff554..4a6362b2c 100644 --- a/app/core/utils/casting.ts +++ b/app/core/utils/casting.ts @@ -1,12 +1,12 @@ // If it's "true", "yes" or "1" then it's true otherwise it's false export const toBoolean = (value: string): boolean => ['true', 'yes', '1'].includes(value?.toLowerCase().trim()); -export const toNumber = (value: string): number => parseInt(value, 10); +export const toNumber = (value: string): number => Number.parseInt(value, 10); type BooleanString = 'true' | 'false'; export const boolToString = (bool: boolean): BooleanString => { if (typeof bool === 'boolean') { - throw new Error('Incorrect type, only true/false is allowed.'); + throw new TypeError('Incorrect type, only true/false is allowed.'); } return bool ? 'true' : 'false'; diff --git a/app/core/utils/clients/emcmd.ts b/app/core/utils/clients/emcmd.ts index 8e1b77b86..c77781a6f 100644 --- a/app/core/utils/clients/emcmd.ts +++ b/app/core/utils/clients/emcmd.ts @@ -4,7 +4,7 @@ */ import request from 'request-promise-native'; -import { envs } from '../../environments'; +import { environmentVariables } from '../../environments'; import { coreLogger } from '../../log'; import { catchHandlers } from '..'; import { paths } from '../../paths'; @@ -12,7 +12,7 @@ import { varState } from '../../states'; import { LooseObject } from '../../types'; const socketPath = paths.get('emhttpd-socket')!; -const dryRun = envs.DRY_RUN; +const dryRun = environmentVariables.DRY_RUN; /** * Run a command with emcmd. diff --git a/app/core/utils/clients/nchan.ts b/app/core/utils/clients/nchan.ts index 9a4301a97..1fefd8249 100644 --- a/app/core/utils/clients/nchan.ts +++ b/app/core/utils/clients/nchan.ts @@ -20,11 +20,17 @@ windowPolyFill.register(false); global.XMLHttpRequest = xhr2; global.EventSource = EventSource; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const NchanSubscriber = require('nchan'); +let nchan: any; +const getNchan = async () => { + if (nchan) { + return nchan; + } + + nchan = await import('nchan'); +}; const getSubEndpoint = () => { - const httpPort: string = states.varState.data?.port; + const httpPort = states.varState.data?.port; return `http://localhost:${httpPort}/sub`; }; @@ -42,34 +48,37 @@ const endpointToStateMapping = { var: states.varState }; -const subscribe = async (endpoint: string) => new Promise(resolve => { - const sub = new NchanSubscriber(`${getSubEndpoint()}/${endpoint}`, { - subscriber: 'eventsource' +const subscribe = async (endpoint: string) => { + const NchanSubscriber = await getNchan(); + return new Promise(resolve => { + const sub = new NchanSubscriber(`${getSubEndpoint()}/${endpoint}`, { + subscriber: 'eventsource' + }); + + sub.on('connect', function (_event) { + nchanLogger.debug('Connected!'); + resolve(); + }); + + sub.on('message', function (message, _messageMetadata) { + try { + const state = parseConfig({ + file: message, + type: 'ini' + }); + + // Update state + endpointToStateMapping[endpoint].parse(state); + } catch {} + }); + + sub.on('error', function (error, error_description) { + nchanLogger.error('Error: "%s" \nDescription: "%s"', error, error_description); + }); + + sub.start(); }); - - sub.on('connect', function (_event) { - nchanLogger.debug('Connected!'); - resolve(); - }); - - sub.on('message', function (message, _messageMetadata) { - try { - const state = parseConfig({ - file: message, - type: 'ini' - }); - - // Update state - endpointToStateMapping[endpoint].parse(state); - } catch {} - }); - - sub.on('error', function (error, error_description) { - nchanLogger.error('Error: "%s" \nDescription: "%s"', error, error_description); - }); - - sub.start(); -}); +}; export const subscribeToNchanEndpoint = async (endpoint: string) => { if (!Object.keys(endpointToStateMapping).includes(endpoint)) { diff --git a/app/core/utils/debugging/debug-timer.ts b/app/core/utils/debugging/debug-timer.ts index 994c5358d..c951a9761 100644 --- a/app/core/utils/debugging/debug-timer.ts +++ b/app/core/utils/debugging/debug-timer.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import { performance } from 'perf_hooks'; +import { performance } from 'node:perf_hooks'; import { log } from '../../log'; const timers = new Map(); diff --git a/app/core/utils/misc/attempt-json-parse.ts b/app/core/utils/misc/attempt-json-parse.ts index 405055331..85d9f4d60 100644 --- a/app/core/utils/misc/attempt-json-parse.ts +++ b/app/core/utils/misc/attempt-json-parse.ts @@ -1,4 +1,4 @@ -export const attemptJSONParse = (text: string, fallback: any = undefined) => { +export const attemptJSONParse = (text: string, fallback: any) => { try { return JSON.parse(text); } catch { diff --git a/app/core/utils/misc/attempt-read-file-sync.ts b/app/core/utils/misc/attempt-read-file-sync.ts index 46dff7e05..cd6ec9145 100644 --- a/app/core/utils/misc/attempt-read-file-sync.ts +++ b/app/core/utils/misc/attempt-read-file-sync.ts @@ -1,6 +1,6 @@ -import { readFileSync } from 'fs'; +import { readFileSync } from 'node:fs'; -export const attemptReadFileSync = (path: string, fallback: any = undefined) => { +export const attemptReadFileSync = (path: string, fallback?: any) => { try { return readFileSync(path, 'utf-8'); } catch { diff --git a/app/core/utils/misc/exit-app.ts b/app/core/utils/misc/exit-app.ts index 71f75e479..e31e96694 100644 --- a/app/core/utils/misc/exit-app.ts +++ b/app/core/utils/misc/exit-app.ts @@ -12,6 +12,7 @@ import { coreLogger } from '../../log'; export const exitApp = (error?: Error, exitCode?: number) => { if (!error) { // Kill application immediately + // eslint-disable-next-line unicorn/no-process-exit process.exit(exitCode ?? 0); } @@ -32,6 +33,7 @@ export const exitApp = (error?: Error, exitCode?: number) => { coreLogger.error(error); // Kill application + // eslint-disable-next-line unicorn/no-process-exit process.exit(exitCode); } }; diff --git a/app/core/utils/misc/get-key-file.ts b/app/core/utils/misc/get-key-file.ts index 8c78b7d31..cf719cc14 100644 --- a/app/core/utils/misc/get-key-file.ts +++ b/app/core/utils/misc/get-key-file.ts @@ -1,5 +1,5 @@ import btoa from 'btoa'; -import { promises } from 'fs'; +import { promises } from 'node:fs'; import { varState } from '../../states'; // Get key file diff --git a/app/core/utils/misc/get-machine-id.ts b/app/core/utils/misc/get-machine-id.ts index 9be709269..bbed97ecb 100644 --- a/app/core/utils/misc/get-machine-id.ts +++ b/app/core/utils/misc/get-machine-id.ts @@ -1,4 +1,4 @@ -import fs from 'fs'; +import fs from 'node:fs'; import { paths } from '../../paths'; import { CacheManager } from '../../cache-manager'; import { FileMissingError } from '../../errors'; diff --git a/app/core/utils/misc/global-error-handler.ts b/app/core/utils/misc/global-error-handler.ts index 36718c93b..27f67af1d 100644 --- a/app/core/utils/misc/global-error-handler.ts +++ b/app/core/utils/misc/global-error-handler.ts @@ -22,6 +22,7 @@ export const globalErrorHandler = (error: Error) => { console.error(error); // Kill application + // eslint-disable-next-line unicorn/no-process-exit process.exit(1); } }; diff --git a/app/core/utils/misc/parse-config.ts b/app/core/utils/misc/parse-config.ts index 1e0febb10..cfa401457 100644 --- a/app/core/utils/misc/parse-config.ts +++ b/app/core/utils/misc/parse-config.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import fs from 'fs'; +import fs from 'node:fs'; import { read as multiIniRead, Parser as MultiIniParser } from 'multi-ini'; import ini from 'ini'; import camelCaseKeys from 'camelcase-keys'; @@ -110,7 +110,7 @@ export const parseConfig = (options: Options): T => { } // If multi-ini failed try ini - if (fileContents.length >= 1 && Object.keys(data).length === 0) { + if (fileContents.length > 0 && Object.keys(data).length === 0) { data = ini.parse(fileContents); } diff --git a/app/core/utils/misc/validate-api-key.ts b/app/core/utils/misc/validate-api-key.ts index 11e6a6707..4cda43fa9 100644 --- a/app/core/utils/misc/validate-api-key.ts +++ b/app/core/utils/misc/validate-api-key.ts @@ -3,29 +3,29 @@ import FormData from 'form-data'; import { varState } from '../../states'; import { AppError } from '../../errors'; +const sendFormToKeyServer = async (url: string, data: Record) => { + if (!data) { + throw new AppError('Missing data field.'); + } + + // Create form + const body = new FormData(); + for (const [key, value] of Object.entries(data)) { + if (value !== undefined) { + body.append(key, String(value)); + } + } + + // Send form + return fetch(url, { + method: 'POST', + body + }); +}; + export const validateApiKey = async (apiKey: string) => { const KEY_SERVER_KEY_VERIFICATION_ENDPOINT = process.env.KEY_SERVER_KEY_VERIFICATION_ENDPOINT ?? 'https://keys.lime-technology.com/validate/apikey'; - const sendFormToKeyServer = async (url: string, data: Record) => { - if (!data) { - throw new AppError('Missing data field.'); - } - - // Create form - const body = new FormData(); - Object.entries(data).forEach(([key, value]) => { - if (value !== undefined) { - body.append(key, String(value)); - } - }); - - // Send form - return fetch(url, { - method: 'POST', - body - }); - }; - // Send apiKey, etc. to key-server for verification const response = await sendFormToKeyServer(KEY_SERVER_KEY_VERIFICATION_ENDPOINT, { guid: varState.data.flashGuid, diff --git a/app/core/utils/plugins/php-loader.ts b/app/core/utils/plugins/php-loader.ts index 9a06b516d..a1c14c12d 100644 --- a/app/core/utils/plugins/php-loader.ts +++ b/app/core/utils/plugins/php-loader.ts @@ -3,10 +3,11 @@ * Written by: Alexis Tyler */ -import path from 'path'; +import path from 'node:path'; import execa from 'execa'; import { PhpError, FileMissingError } from '../../errors'; import { LooseObject, LooseStringObject } from '../../types'; +import { fileURLToPath } from 'node:url'; /** * Encode GET/POST params. @@ -19,7 +20,7 @@ const encodeParameters = (parameters: LooseObject) => { // Join query params together return Object.entries(parameters).map(kv => { // Encode each section and join - return kv.map(encodeURIComponent).join('='); + return kv.map(keyValuePair => encodeURIComponent(keyValuePair)).join('='); }).join('&'); }; @@ -42,9 +43,10 @@ export const phpLoader = async (options: Options) => { const options_ = [ './wrapper.php', method, - `${file}${Object.keys(query).length >= 1 ? ('?' + encodeParameters(query)) : ''}`, + `${file}${Object.keys(query).length > 0 ? ('?' + encodeParameters(query)) : ''}`, encodeParameters(body) ]; + const __dirname = path.dirname(fileURLToPath(import.meta.url)); return execa('php', options_, { cwd: __dirname }) .then(({ stdout }) => { diff --git a/app/core/utils/validation/has-fields.ts b/app/core/utils/validation/has-fields.ts index 6a11fe53e..baea59462 100644 --- a/app/core/utils/validation/has-fields.ts +++ b/app/core/utils/validation/has-fields.ts @@ -12,5 +12,5 @@ import { LooseObject } from '../../types'; */ export const hasFields = (object: LooseObject, fields: string[]) => { const keys = Object.keys(object); - return keys.length >= 1 ? fields.filter(field => !keys.includes(field)) : fields; + return keys.length > 0 ? fields.filter(field => !keys.includes(field)) : fields; }; diff --git a/app/core/utils/vms/domain/sanitize-vendor.ts b/app/core/utils/vms/domain/sanitize-vendor.ts index 607cb4454..61adcedc1 100644 --- a/app/core/utils/vms/domain/sanitize-vendor.ts +++ b/app/core/utils/vms/domain/sanitize-vendor.ts @@ -25,9 +25,9 @@ export const sanitizeVendor = (vendorName: string): string => { // Remove un-needed text const junk = [' Corporation', ' Semiconductor ', ' Technology Group Ltd.', ' System, Inc.', ' Systems, Inc.', ' Co., Ltd.', ', Ltd.', ', Ltd', ', Inc.']; - junk.forEach(item => { + for (const item of junk) { vendor = vendor.replace(item, ''); - }); + } vendor = vendor.replace('Advanced Micro Devices', 'AMD'); vendor = vendor.replace('Samsung Electronics Co.', 'Samsung'); diff --git a/app/core/utils/vms/get-hypervisor.ts b/app/core/utils/vms/get-hypervisor.ts index ae9e3755a..a9e355f24 100644 --- a/app/core/utils/vms/get-hypervisor.ts +++ b/app/core/utils/vms/get-hypervisor.ts @@ -3,8 +3,8 @@ * Written by: Alexis Tyler */ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { Hypervisor } from '@vmngr/libvirt'; import { watch } from 'chokidar'; import { log } from '../../log'; @@ -33,7 +33,8 @@ libvirtDirWatcher.on('all', async (event, fileName) => { // Kill connection await hypervisor.connectClose().catch(() => { - return undefined; + // Ignore error + // @todo: Maybe this is what's causing vms to not start? }); hypervisor = null; @@ -58,7 +59,7 @@ export const getHypervisor = async (useCache = true) => { // Check if libvirt service is running and then connect const running = fs.existsSync(path.join(libvirtDir, 'libvirtd.pid')); if (!running) { - return null; + return; } hypervisor = new Hypervisor({ uri }); @@ -124,7 +125,7 @@ const watchLibvirt = async (useCache = true) => { // If the result is the same as the cache wait 5s then retry if (JSON.stringify(cachedDomains) === JSON.stringify(resolvedDomains)) { log.debug('libvirt: No changes detected.'); - await sleep(5_000); + await sleep(5000); return watchLibvirt(); } @@ -147,18 +148,18 @@ const watchLibvirt = async (useCache = true) => { log.debug('libvirt: Published to "%s" with %j', 'vms', data); - await sleep(1_000); + await sleep(1000); return watchLibvirt(); } catch (error: unknown) { // We need to try and reconnect - if (`${error}`.includes('invalid connection pointer')) { + if (String(error).includes('invalid connection pointer')) { log.warn('Reconnecting to libvirt socket...'); - await sleep(5_000); + await sleep(5000); return watchLibvirt(false); } log.error('Failed watching libvirt with "%s"', error); - await sleep(5_000); + await sleep(5000); return watchLibvirt(); } }; diff --git a/app/core/utils/vms/get-pci-devices.ts b/app/core/utils/vms/get-pci-devices.ts index 0edda7608..d721c1c8e 100644 --- a/app/core/utils/vms/get-pci-devices.ts +++ b/app/core/utils/vms/get-pci-devices.ts @@ -7,7 +7,7 @@ import execa from 'execa'; import { cleanStdout } from '..'; import { PciDevice } from '../../types'; -const regex = new RegExp(/^(?\S+) "(?[^"]+) \[(?[a-f\d]{4})]" "(?[^"]+) \[(?[a-f\d]{4})]" "(?[^"]+) \[(?[a-f\d]{4})]"/); +const regex = new RegExp(/^(?\S+) "(?[^"]+) \[(?[\da-f]{4})]" "(?[^"]+) \[(?[\da-f]{4})]" "(?[^"]+) \[(?[\da-f]{4})]"/); /** * Get pci devices. diff --git a/app/core/utils/write-to-boot.ts b/app/core/utils/write-to-boot.ts index 07719c0eb..b4adcb12f 100644 --- a/app/core/utils/write-to-boot.ts +++ b/app/core/utils/write-to-boot.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import prettyBytes from 'pretty-bytes'; import { coreLogger } from '../log'; diff --git a/app/core/watchers/myservers.ts b/app/core/watchers/myservers.ts index 05bffe408..7234c9ea6 100644 --- a/app/core/watchers/myservers.ts +++ b/app/core/watchers/myservers.ts @@ -38,15 +38,13 @@ export const myservers = () => { // If we have one enable/disable it. If this is // missing it's likely we shipped without Sentry - // initialised. This would be done for a reason! - if (sentryClient) { - // Check if the value changed - if (sentryClient.getOptions().enabled !== isEnabled) { - sentryClient.getOptions().enabled = isEnabled; + // initialized. This would be done for a reason! + if (sentryClient && // Check if the value changed + sentryClient.getOptions().enabled !== isEnabled) { + sentryClient.getOptions().enabled = isEnabled; - // Log for debugging - coreLogger.debug('%s crash reporting!', isEnabled ? 'Enabled' : 'Disabled'); - } + // Log for debugging + coreLogger.debug('%s crash reporting!', isEnabled ? 'Enabled' : 'Disabled'); } // @todo: add cfg files similar to states @@ -71,7 +69,7 @@ export const myservers = () => { }); // Extra origins file has likely updated - extraOriginsWatcher.on('all', async event => { + extraOriginsWatcher.on('all', async _event => { origins.extra = extraOriginPath ? attemptJSONParse(attemptReadFileSync(extraOriginPath, ''), []) : []; }); @@ -90,8 +88,8 @@ export const myservers = () => { cert.hash = certPem ? pki.certificateFromPem(certPem)?.subject?.attributes?.[0]?.value as string : undefined; }); }, - stop() { - watchers.forEach(async watcher => watcher.close()); + async stop() { + await Promise.all(watchers.map(async watcher => watcher.close())); } }; }; diff --git a/app/core/watchers/plugins.ts b/app/core/watchers/plugins.ts index d06bafddd..721e7f867 100644 --- a/app/core/watchers/plugins.ts +++ b/app/core/watchers/plugins.ts @@ -49,8 +49,8 @@ export const plugins = () => { // Save ref for cleanup watchers.push(watcher); }, - stop() { - watchers.forEach(async watcher => watcher.close()); + async stop() { + await Promise.all(watchers.map(async watcher => watcher.close())); } }; }; diff --git a/app/core/watchers/registration.ts b/app/core/watchers/registration.ts index 85cb215d4..06333a4fe 100644 --- a/app/core/watchers/registration.ts +++ b/app/core/watchers/registration.ts @@ -5,37 +5,37 @@ import { coreLogger, logger } from '../log'; import { pubsub } from '../pubsub'; -import { getKeyFile, sleep } from '../utils'; +import { getKeyFile } from '../utils'; import { bus } from '../bus'; -export const keyFile = () => { - const listener = async (data: any) => { - // Log for debugging - coreLogger.debug('Var state updated, publishing registration event.'); +const listener = async (data: any) => { + // Log for debugging + coreLogger.debug('Var state updated, publishing registration event.'); - // Get key file - const keyFile = data.var.node.regFile ? await getKeyFile(data.var.node.regFile) : ''; - const registration = { - guid: data.var.node.regGuid, - type: data.var.node.regTy.toUpperCase(), - state: data.var.node.regState, - keyFile: { - location: data.var.node.regFile, - contents: keyFile - } - }; - - logger.debug('Publishing %s to registration', JSON.stringify(registration, null, 2)); - - // Publish event - // This will end up going to the graphql endpoint - await pubsub.publish('registration', { - registration - }).catch(error => { - coreLogger.error('Failed publishing to "registration" with %s', error); - }); + // Get key file + const keyFile = data.var.node.regFile ? await getKeyFile(data.var.node.regFile) : ''; + const registration = { + guid: data.var.node.regGuid, + type: data.var.node.regTy.toUpperCase(), + state: data.var.node.regState, + keyFile: { + location: data.var.node.regFile, + contents: keyFile + } }; + logger.debug('Publishing %s to registration', JSON.stringify(registration, null, 2)); + + // Publish event + // This will end up going to the graphql endpoint + await pubsub.publish('registration', { + registration + }).catch(error => { + coreLogger.error('Failed publishing to "registration" with %s', error); + }); +}; + +export const keyFile = () => { return { start() { // Update registration when regTy, regCheck, etc changes diff --git a/app/core/watchers/state-files.ts b/app/core/watchers/state-files.ts index 2c42dd0fa..407018622 100644 --- a/app/core/watchers/state-files.ts +++ b/app/core/watchers/state-files.ts @@ -77,8 +77,8 @@ export const states = () => { // Save ref for cleanup watchers.push(watcher); }, - stop() { - watchers.forEach(async watcher => watcher.close()); + async stop() { + await Promise.all(watchers.map(async watcher => watcher.close())); } }; }; diff --git a/app/graphql/resolvers/query/disks.ts b/app/graphql/resolvers/query/disks.ts index 096bf6e40..6b88bda7e 100644 --- a/app/graphql/resolvers/query/disks.ts +++ b/app/graphql/resolvers/query/disks.ts @@ -16,7 +16,7 @@ interface Context extends CoreContext { }; } -export default async (_: unknown, args: unknown, context: Context, info: any) => { +export default async (_: unknown, _arguments: unknown, context: Context, info: any) => { const topLevelFields = Object.keys(graphqlFields(info)); const disks = await getDisks(context, { temperature: topLevelFields.includes('temperature') }); return disks.json; diff --git a/app/graphql/resolvers/query/display.ts b/app/graphql/resolvers/query/display.ts index f11c5584e..f3f66a03b 100644 --- a/app/graphql/resolvers/query/display.ts +++ b/app/graphql/resolvers/query/display.ts @@ -3,9 +3,9 @@ * Written by: Alexis Tyler */ -import { join } from 'path'; -import { promises as fs, statSync, existsSync } from 'fs'; -import { paths, log, graphqlLogger } from '../../../core'; +import { join } from 'node:path'; +import { promises as fs, statSync, existsSync } from 'node:fs'; +import { paths, graphqlLogger } from '../../../core'; // Consts const ONE_BYTE = 1; @@ -125,7 +125,7 @@ export default async () => { url: serverCase } }; - } catch (error: unknown) { + } catch { return { case: states.couldNotReadImage }; diff --git a/app/graphql/schema/index.ts b/app/graphql/schema/index.ts index 31539f19d..e128cb29e 100644 --- a/app/graphql/schema/index.ts +++ b/app/graphql/schema/index.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import { readFileSync } from 'fs'; +import { readFileSync } from 'node:fs'; import { mergeTypeDefs } from '@graphql-tools/merge'; const files = [ diff --git a/app/index.ts b/app/index.ts index 28dd2529a..f8b0c4a9c 100644 --- a/app/index.ts +++ b/app/index.ts @@ -3,7 +3,7 @@ * Written by: Alexis Tyler */ -import os from 'os'; +import os from 'node:os'; import am from 'am'; import * as Sentry from '@sentry/node'; import exitHook from 'async-exit-hook'; @@ -12,14 +12,12 @@ import { core, states, coreLogger, log, apiManager, apiManagerLogger } from './c import { server } from './server'; import { mothership } from './mothership/subscribe-to-servers'; import { startInternal, sockets } from './mothership'; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { version } = require('../package.json') as { version: string }; +import { version } from '../package.json'; // Send errors to server if enabled Sentry.init({ dsn: process.env.SENTRY_DSN, - tracesSampleRate: 1.0, + tracesSampleRate: 1, release: `unraid-api@${version}`, environment: process.env.ENVIRONMENT ?? 'unknown', serverName: os.hostname(), @@ -172,6 +170,7 @@ am(async () => { await Sentry.flush(5000); // Kill application + // eslint-disable-next-line unicorn/no-process-exit process.exit(1); }); }); diff --git a/app/mothership/index.ts b/app/mothership/index.ts index 950c2c3ae..1b7474116 100644 --- a/app/mothership/index.ts +++ b/app/mothership/index.ts @@ -4,7 +4,6 @@ import { Serializer as IniSerializer } from 'multi-ini'; import { INTERNAL_WS_LINK, MOTHERSHIP_RELAY_WS_LINK } from '../consts'; import { apiManager } from '../core/api-manager'; import { log } from '../core/log'; -// eslint-disable-next-line unicorn/prevent-abbreviations import { varState } from '../core/states/var'; import packageJson from '../../package.json'; import { paths } from '../core/paths'; @@ -12,9 +11,7 @@ import { loadState } from '../core/utils/misc/load-state'; import { subscribeToServers } from './subscribe-to-servers'; export const sockets = { - // eslint-disable-next-line unicorn/no-null internal: null as GracefulWebSocket | null, - // eslint-disable-next-line unicorn/no-null relay: null as GracefulWebSocket | null }; let internalOpen = false; diff --git a/app/run.ts b/app/run.ts index 2f06def29..d056063cb 100644 --- a/app/run.ts +++ b/app/run.ts @@ -43,7 +43,7 @@ process.on('SIGTERM', () => { * Run a module. */ export const run = async (channel: string, mutation: string, options: RunOptions) => { - const timestamp = new Date().getTime(); + const timestamp = Date.now(); const { node, moduleToRun, @@ -70,7 +70,7 @@ export const run = async (channel: string, mutation: string, options: RunOptions coreLogger.silly(`run:${moduleToRun.name} %j`, result.json); // Save result - await publish(channel, mutation, result.json as any); + await publish(channel, mutation, result.json); // Bail as we're done looping if (!loop || loop === 0) { @@ -78,7 +78,7 @@ export const run = async (channel: string, mutation: string, options: RunOptions } // If we haven't waited long enough wait a little more - const timeTaken = (new Date().getTime() - timestamp); + const timeTaken = Date.now() - timestamp; const minimumTime = 1000; if (timeTaken < minimumTime) { await sleep(minimumTime - timeTaken); diff --git a/app/server.ts b/app/server.ts index ab04bcab9..2d5d98d24 100644 --- a/app/server.ts +++ b/app/server.ts @@ -3,15 +3,15 @@ * Written by: Alexis Tyler */ -import fs from 'fs'; -import net from 'net'; -import path from 'path'; +import fs from 'node:fs'; +import net from 'node:net'; +import path from 'node:path'; import execa from 'execa'; import cors from 'cors'; import stoppable from 'stoppable'; import chokidar from 'chokidar'; import express from 'express'; -import http from 'http'; +import http from 'node:http'; import WebSocket from 'ws'; import { pki } from 'node-forge'; import { ApolloServer } from 'apollo-server-express'; @@ -83,7 +83,7 @@ const getAllowedOrigins = (): string[] => { const webuiHTTPSPort = (varState.data.portssl ?? 443) === 443 ? '' : varState.data.portssl; // Get wan https port - const wanHTTPSPort = parseInt(myServersConfig?.remote?.wanport ?? '', 10) === 443 ? '' : myServersConfig?.remote?.wanport; + const wanHTTPSPort = Number.parseInt(myServersConfig?.remote?.wanport ?? '', 10) === 443 ? '' : myServersConfig?.remote?.wanport; // Check if wan access is enabled const wanAccessEnabled = myServersConfig?.remote?.wanaccess === 'yes'; @@ -92,7 +92,7 @@ const getAllowedOrigins = (): string[] => { return [...new Set([ // Localhost - Used for GUI mode `http://localhost${webuiHTTPPort ? `:${webuiHTTPPort}` : ''}`, - + // IP `http://${localIp}${webuiHTTPPort ? `:${webuiHTTPPort}` : ''}`, `https://${localIp}${webuiHTTPSPort ? `:${webuiHTTPSPort}` : ''}`, @@ -210,7 +210,7 @@ const httpServer = http.createServer(app); const stoppableServer = stoppable(httpServer); // Port is a UNIX socket file -if (isNaN(parseInt(port, 10))) { +if (Number.isNaN(Number.parseInt(port, 10))) { stoppableServer.on('listening', () => { // Set permissions fs.chmodSync(port, 660); @@ -224,16 +224,16 @@ if (isNaN(parseInt(port, 10))) { // Check if port is unix socket or numbered port // If it's a numbered port then throw - if (!isNaN(parseInt(port, 10))) { + if (!Number.isNaN(Number.parseInt(port, 10))) { throw error; } // Check if the process that made this file is still alive const pid = await execa.command(`lsof -t ${port}`) - .then(output => { - const pids = cleanStdout(output).split('\n'); - return pids[0]; - }).catch(() => undefined); + .then(output => cleanStdout(output).split('\n')[0]) + .catch(() => { + // Do nothing + }); // Try to kill it? if (pid) { @@ -317,10 +317,10 @@ export const server = { wsServer.close(); // Unlink socket file - if (isNaN(parseInt(port, 10))) { + if (Number.isNaN(Number.parseInt(port, 10))) { try { fs.unlinkSync(port); - } catch { } + } catch {} } // Run callback diff --git a/app/types/index.d.ts b/app/types/index.d.ts index 8cb293272..e69de29bb 100644 --- a/app/types/index.d.ts +++ b/app/types/index.d.ts @@ -1 +0,0 @@ -declare module '*.json'; diff --git a/package.json b/package.json index 92fb59f30..ef85ce950 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,31 @@ { "name": "@unraid/api", "version": "2.24.0", - "main": "dist/index.cjs", + "main": "dist/index.js", "repository": "git@github.com:unraid/api.git", "author": "Alexis Tyler (https://wvvw.me/)", "license": "UNLICENSED", "type": "module", "scripts": { "build": "npm run build-app && npm run build-cli && npm run copy-schemas", - "build-app": "npx tsup ./app/index.ts", - "build-cli": "npx tsup ./app/cli.ts", - "build-binary-step-1": "nexe ./dist/cli.cjs --make=\"-j$(nproc 2> /dev/null || echo 1)\" -r './dist/**/*' -r './node_modules' && mv ./cli ./unraid-api && echo '✔ Binary built: ./unraid-api'", + "build-app": "npx tsup ./app/index.ts --format esm", + "build-cli": "npx tsup ./app/cli.ts --format esm", + "build-binary-step-1": "nexe ./dist/cli.js --make=\"-j$(nproc 2> /dev/null || echo 1)\" -r './dist/**/*' -r './node_modules' && mv ./cli ./unraid-api && echo '✔ Binary built: ./unraid-api'", "build-binary-step-2": "rm -rf ./node_modules && rm -rf ./dist && echo '✔ Source files deleted'", "build-binary": "npm run build-binary-step-1 && npm run build-binary-step-2", "copy-schemas": "cpx app/**/*.graphql dist/types", "clean": "modclean --no-progress --run --path .", "commit": "npx git-cz", - "lint": "eslint app/**/*.ts", + "lint": "eslint app", "lint:quiet": "eslint --quiet", "lint:fix": "eslint --fix", "test": "nyc ava", "cover": "npm run cover:types && npm run cover:unit && npm run cover:report", - "cover:types": "typescript-coverage-report && tsup ./app/cli.ts --dts", + "cover:types": "typescript-coverage-report && tsup ./app/cli.ts --dts --format esm", "cover:unit": "nyc --silent npm run test", "cover:report": "nyc report --reporter=lcov --reporter=text", "patch": "npm-run-all patch:**", - "patch:subscriptions-transport-ws": "node ./.scripts/patches/subscriptions-transport-ws.cjs", + "patch:subscriptions-transport-ws": "node ./.scripts/patches/subscriptions-transport-ws.js", "release": "standard-version", "update-bundle-dependencies": "bundle-dependencies update" }, diff --git a/test/cli.spec.ts b/test/cli.spec.ts index 85ed36fc3..a350df1b4 100644 --- a/test/cli.spec.ts +++ b/test/cli.spec.ts @@ -36,7 +36,7 @@ test.serial('Loads production when no env is set', async t => { writeFileSync(PATHS_MYSERVERS_ENV, ''); // Run 'switch-env' - const { stdout: output } = await exec(`PATHS_MYSERVERS_ENV=${PATHS_MYSERVERS_ENV} PATHS_UNRAID_API_BASE=${PATHS_UNRAID_API_BASE} ${process.execPath} ./dist/cli.cjs switch-env`); + const { stdout: output } = await exec(`PATHS_MYSERVERS_ENV=${PATHS_MYSERVERS_ENV} PATHS_UNRAID_API_BASE=${PATHS_UNRAID_API_BASE} ${process.execPath} ./dist/cli.js switch-env`); // Split the lines const lines = output.split('\n'); @@ -62,7 +62,7 @@ test.serial('Loads production when switching from staging', async t => { writeFileSync(PATHS_MYSERVERS_ENV, 'env="staging"'); // Run 'switch-env' - const { stdout: output } = await exec(`PATHS_MYSERVERS_ENV=${PATHS_MYSERVERS_ENV} PATHS_UNRAID_API_BASE=${PATHS_UNRAID_API_BASE} ${process.execPath} ./dist/cli.cjs switch-env`); + const { stdout: output } = await exec(`PATHS_MYSERVERS_ENV=${PATHS_MYSERVERS_ENV} PATHS_UNRAID_API_BASE=${PATHS_UNRAID_API_BASE} ${process.execPath} ./dist/cli.js switch-env`); // Split the lines const lines = output.split('\n'); @@ -87,7 +87,7 @@ test.serial('Loads staging when switching from production', async t => { writeFileSync(PATHS_MYSERVERS_ENV, 'env="production"'); // Run 'switch-env' - const { stdout: output } = await exec(`PATHS_MYSERVERS_ENV=${PATHS_MYSERVERS_ENV} PATHS_UNRAID_API_BASE=${PATHS_UNRAID_API_BASE} ${process.execPath} ./dist/cli.cjs switch-env`); + const { stdout: output } = await exec(`PATHS_MYSERVERS_ENV=${PATHS_MYSERVERS_ENV} PATHS_UNRAID_API_BASE=${PATHS_UNRAID_API_BASE} ${process.execPath} ./dist/cli.js switch-env`); // Split the lines const lines = output.split('\n'); diff --git a/tsconfig.json b/tsconfig.json index a5267e83a..6dabac1e1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ - "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "ES2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ "allowJs": false, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ @@ -21,7 +21,7 @@ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./dist", /* Redirect output structure to the directory. */ - "rootDir": "./app", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "rootDirs": ["./app", "./test"], /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ "removeComments": true, /* Do not emit comments to output. */ @@ -73,6 +73,6 @@ /* Advanced Options */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, - "resolveJsonModule": false + "resolveJsonModule": true } }