diff --git a/api/ecosystem.config.json b/api/ecosystem.config.json deleted file mode 100644 index 4fea24e6e..000000000 --- a/api/ecosystem.config.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/pm2-ecosystem", - "apps": [ - { - "name": "unraid-api", - "script": "./dist/main.js", - "cwd": "/usr/local/unraid-api", - "exec_mode": "fork", - "wait_ready": true, - "listen_timeout": 15000, - "max_restarts": 10, - "min_uptime": 10000, - "watch": false, - "interpreter": "/usr/local/bin/node", - "ignore_watch": ["node_modules", "src", ".env.*", "myservers.cfg"], - "out_file": "/var/log/graphql-api.log", - "error_file": "/var/log/graphql-api.log", - "merge_logs": true, - "kill_timeout": 10000 - } - ] -} diff --git a/api/generated-schema.graphql b/api/generated-schema.graphql index 0dfe521f9..f0fbb669d 100644 --- a/api/generated-schema.graphql +++ b/api/generated-schema.graphql @@ -1673,8 +1673,8 @@ type PackageVersions { """npm version""" npm: String - """pm2 version""" - pm2: String + """nodemon version""" + nodemon: String """Git version""" git: String diff --git a/api/legacy/generated-schema-legacy.graphql b/api/legacy/generated-schema-legacy.graphql index 0928c60b9..b13c1ef31 100644 --- a/api/legacy/generated-schema-legacy.graphql +++ b/api/legacy/generated-schema-legacy.graphql @@ -1257,7 +1257,7 @@ type Versions { openssl: String perl: String php: String - pm2: String + nodemon: String postfix: String postgresql: String python: String diff --git a/api/nodemon.json b/api/nodemon.json new file mode 100644 index 000000000..91e2dfae2 --- /dev/null +++ b/api/nodemon.json @@ -0,0 +1,18 @@ +{ + "watch": [ + "dist/main.js", + "myservers.cfg" + ], + "ignore": [ + "node_modules", + "src", + ".env.*" + ], + "exec": "node ./dist/main.js", + "signal": "SIGTERM", + "ext": "js,json", + "restartable": "rs", + "env": { + "NODE_ENV": "production" + } +} diff --git a/api/package.json b/api/package.json index 26e51095b..a09f9500a 100644 --- a/api/package.json +++ b/api/package.json @@ -137,7 +137,7 @@ "pino": "9.9.0", "pino-http": "10.5.0", "pino-pretty": "13.1.1", - "pm2": "6.0.8", + "nodemon": "3.1.10", "reflect-metadata": "^0.1.14", "rxjs": "7.8.2", "semver": "7.7.2", @@ -203,7 +203,6 @@ "eslint-plugin-no-relative-import-paths": "1.6.1", "eslint-plugin-prettier": "5.5.4", "jiti": "2.5.1", - "nodemon": "3.1.10", "prettier": "3.6.2", "rollup-plugin-node-externals": "8.1.0", "supertest": "7.1.4", diff --git a/api/scripts/build.ts b/api/scripts/build.ts index 924b3f4ca..6c6bb98ac 100755 --- a/api/scripts/build.ts +++ b/api/scripts/build.ts @@ -7,7 +7,7 @@ import { exit } from 'process'; import type { PackageJson } from 'type-fest'; import { $, cd } from 'zx'; -import { getDeploymentVersion } from './get-deployment-version.js'; +import { getDeploymentVersion } from '@app/../scripts/get-deployment-version.js'; type ApiPackageJson = PackageJson & { version: string; @@ -94,7 +94,7 @@ try { await writeFile('./deploy/pack/package.json', JSON.stringify(parsedPackageJson, null, 4)); // Copy necessary files to the pack directory - await $`cp -r dist README.md .env.* ecosystem.config.json ./deploy/pack/`; + await $`cp -r dist README.md .env.* nodemon.json ./deploy/pack/`; // Change to the pack directory and install dependencies cd('./deploy/pack'); diff --git a/api/src/__test__/core/utils/pm2/dummy-process.js b/api/src/__test__/core/utils/pm2/dummy-process.js deleted file mode 100644 index 85ace81c0..000000000 --- a/api/src/__test__/core/utils/pm2/dummy-process.js +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable no-undef */ -// Dummy process for PM2 testing -setInterval(() => { - // Keep process alive -}, 1000); \ No newline at end of file diff --git a/api/src/__test__/core/utils/pm2/unraid-api-running.integration.test.ts b/api/src/__test__/core/utils/pm2/unraid-api-running.integration.test.ts deleted file mode 100644 index 6c05e817c..000000000 --- a/api/src/__test__/core/utils/pm2/unraid-api-running.integration.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { existsSync } from 'node:fs'; -import { homedir } from 'node:os'; -import { join } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -import { execa } from 'execa'; -import pm2 from 'pm2'; -import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; - -import { isUnraidApiRunning } from '@app/core/utils/pm2/unraid-api-running.js'; - -const __dirname = fileURLToPath(new URL('.', import.meta.url)); -const PROJECT_ROOT = join(__dirname, '../../../../..'); -const DUMMY_PROCESS_PATH = join(__dirname, 'dummy-process.js'); -const CLI_PATH = join(PROJECT_ROOT, 'dist/cli.js'); -const TEST_PROCESS_NAME = 'test-unraid-api'; - -// Shared PM2 connection state -let pm2Connected = false; - -// Helper to ensure PM2 connection is established -async function ensurePM2Connection() { - if (pm2Connected) return; - - return new Promise((resolve, reject) => { - pm2.connect((err) => { - if (err) { - reject(err); - return; - } - pm2Connected = true; - resolve(); - }); - }); -} - -// Helper to delete specific test processes (lightweight, reuses connection) -async function deleteTestProcesses() { - if (!pm2Connected) { - // No connection, nothing to clean up - return; - } - - const deletePromise = new Promise((resolve) => { - // Delete specific processes we might have created - const processNames = ['unraid-api', TEST_PROCESS_NAME]; - let deletedCount = 0; - - const deleteNext = () => { - if (deletedCount >= processNames.length) { - resolve(); - return; - } - - const processName = processNames[deletedCount]; - pm2.delete(processName, () => { - // Ignore errors, process might not exist - deletedCount++; - deleteNext(); - }); - }; - - deleteNext(); - }); - - const timeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve(), 3000); // 3 second timeout - }); - - return Promise.race([deletePromise, timeoutPromise]); -} - -// Helper to ensure PM2 is completely clean (heavy cleanup with daemon kill) -async function cleanupAllPM2Processes() { - // First delete test processes if we have a connection - if (pm2Connected) { - await deleteTestProcesses(); - } - - return new Promise((resolve) => { - // Always connect fresh for daemon kill (in case we weren't connected) - pm2.connect((err) => { - if (err) { - // If we can't connect, assume PM2 is not running - pm2Connected = false; - resolve(); - return; - } - - // Kill the daemon to ensure fresh state - pm2.killDaemon(() => { - pm2.disconnect(); - pm2Connected = false; - // Small delay to let PM2 fully shutdown - setTimeout(resolve, 500); - }); - }); - }); -} - -describe.skipIf(!!process.env.CI)('PM2 integration tests', () => { - beforeAll(async () => { - // Set PM2_HOME to use home directory for testing (not /var/log) - process.env.PM2_HOME = join(homedir(), '.pm2'); - - // Build the CLI if it doesn't exist (only for CLI tests) - if (!existsSync(CLI_PATH)) { - console.log('Building CLI for integration tests...'); - try { - await execa('pnpm', ['build'], { - cwd: PROJECT_ROOT, - stdio: 'inherit', - timeout: 120000, // 2 minute timeout for build - }); - } catch (error) { - console.error('Failed to build CLI:', error); - throw new Error( - 'Cannot run CLI integration tests without built CLI. Run `pnpm build` first.' - ); - } - } - - // Only do a full cleanup once at the beginning - await cleanupAllPM2Processes(); - }, 150000); // 2.5 minute timeout for setup - - afterAll(async () => { - // Only do a full cleanup once at the end - await cleanupAllPM2Processes(); - }); - - afterEach(async () => { - // Lightweight cleanup after each test - just delete our test processes - await deleteTestProcesses(); - }, 5000); // 5 second timeout for cleanup - - describe('isUnraidApiRunning function', () => { - it('should return false when PM2 is not running the unraid-api process', async () => { - const result = await isUnraidApiRunning(); - expect(result).toBe(false); - }); - - it('should return true when PM2 has unraid-api process running', async () => { - // Ensure PM2 connection - await ensurePM2Connection(); - - // Start a dummy process with the name 'unraid-api' - await new Promise((resolve, reject) => { - pm2.start( - { - script: DUMMY_PROCESS_PATH, - name: 'unraid-api', - }, - (startErr) => { - if (startErr) return reject(startErr); - resolve(); - } - ); - }); - - // Give PM2 time to start the process - await new Promise((resolve) => setTimeout(resolve, 2000)); - - const result = await isUnraidApiRunning(); - expect(result).toBe(true); - }, 30000); - - it('should return false when unraid-api process is stopped', async () => { - // Ensure PM2 connection - await ensurePM2Connection(); - - // Start and then stop the process - await new Promise((resolve, reject) => { - pm2.start( - { - script: DUMMY_PROCESS_PATH, - name: 'unraid-api', - }, - (startErr) => { - if (startErr) return reject(startErr); - - // Stop the process after starting - setTimeout(() => { - pm2.stop('unraid-api', (stopErr) => { - if (stopErr) return reject(stopErr); - resolve(); - }); - }, 1000); - } - ); - }); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const result = await isUnraidApiRunning(); - expect(result).toBe(false); - }, 30000); - - it('should handle PM2 connection errors gracefully', async () => { - // Disconnect PM2 first to ensure we're testing fresh connection - await new Promise((resolve) => { - pm2.disconnect(); - pm2Connected = false; - setTimeout(resolve, 100); - }); - - // Set an invalid PM2_HOME to force connection failure - const originalPM2Home = process.env.PM2_HOME; - process.env.PM2_HOME = '/invalid/path/that/does/not/exist'; - - const result = await isUnraidApiRunning(); - expect(result).toBe(false); - - // Restore original PM2_HOME - if (originalPM2Home) { - process.env.PM2_HOME = originalPM2Home; - } else { - delete process.env.PM2_HOME; - } - }, 15000); // 15 second timeout to allow for the Promise.race timeout - }); -}); diff --git a/api/src/__test__/core/utils/process/unraid-api-running.integration.test.ts b/api/src/__test__/core/utils/process/unraid-api-running.integration.test.ts new file mode 100644 index 000000000..124641e68 --- /dev/null +++ b/api/src/__test__/core/utils/process/unraid-api-running.integration.test.ts @@ -0,0 +1,54 @@ +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; + +describe('isUnraidApiRunning (nodemon pid detection)', () => { + let tempDir: string; + let pidPath: string; + + beforeAll(() => { + tempDir = mkdtempSync(join(tmpdir(), 'unraid-api-')); + pidPath = join(tempDir, 'nodemon.pid'); + }); + + afterAll(() => { + rmSync(tempDir, { recursive: true, force: true }); + }); + + afterEach(() => { + vi.resetModules(); + }); + + async function loadIsRunning() { + vi.doMock('@app/environment.js', async () => { + const actual = + await vi.importActual('@app/environment.js'); + return { ...actual, NODEMON_PID_PATH: pidPath }; + }); + + const module = await import('@app/core/utils/process/unraid-api-running.js'); + return module.isUnraidApiRunning; + } + + it('returns false when pid file is missing', async () => { + const isUnraidApiRunning = await loadIsRunning(); + + expect(await isUnraidApiRunning()).toBe(false); + }); + + it('returns true when a live pid is recorded', async () => { + writeFileSync(pidPath, `${process.pid}`); + const isUnraidApiRunning = await loadIsRunning(); + + expect(await isUnraidApiRunning()).toBe(true); + }); + + it('returns false when pid file is invalid', async () => { + writeFileSync(pidPath, 'not-a-number'); + const isUnraidApiRunning = await loadIsRunning(); + + expect(await isUnraidApiRunning()).toBe(false); + }); +}); diff --git a/api/src/core/log.ts b/api/src/core/log.ts index 84f66601f..4d0311b35 100644 --- a/api/src/core/log.ts +++ b/api/src/core/log.ts @@ -17,7 +17,7 @@ const nullDestination = pino.destination({ export const logDestination = process.env.SUPPRESS_LOGS === 'true' ? nullDestination : pino.destination(); -// Since PM2 captures stdout and writes to the log file, we should not colorize stdout +// Since process output is piped directly to the log file, we should not colorize stdout // to avoid ANSI escape codes in the log file const stream = SUPPRESS_LOGS ? nullDestination @@ -25,7 +25,7 @@ const stream = SUPPRESS_LOGS ? pretty({ singleLine: true, hideObject: false, - colorize: false, // No colors since PM2 writes stdout to file + colorize: false, // No colors since logs are written directly to file colorizeObjects: false, levelFirst: false, ignore: 'hostname,pid', diff --git a/api/src/core/utils/pm2/unraid-api-running.ts b/api/src/core/utils/pm2/unraid-api-running.ts deleted file mode 100644 index 4e65aa3ac..000000000 --- a/api/src/core/utils/pm2/unraid-api-running.ts +++ /dev/null @@ -1,40 +0,0 @@ -export const isUnraidApiRunning = async (): Promise => { - const { PM2_HOME } = await import('@app/environment.js'); - - // Set PM2_HOME if not already set - if (!process.env.PM2_HOME) { - process.env.PM2_HOME = PM2_HOME; - } - - const pm2Module = await import('pm2'); - const pm2 = pm2Module.default || pm2Module; - - const pm2Promise = new Promise((resolve) => { - pm2.connect(function (err) { - if (err) { - // Don't reject here, resolve with false since we can't connect to PM2 - resolve(false); - return; - } - - // Now try to describe unraid-api specifically - pm2.describe('unraid-api', function (err, processDescription) { - if (err || processDescription.length === 0) { - // Service not found or error occurred - resolve(false); - } else { - const isOnline = processDescription?.[0]?.pm2_env?.status === 'online'; - resolve(isOnline); - } - - pm2.disconnect(); - }); - }); - }); - - const timeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve(false), 10000); // 10 second timeout - }); - - return Promise.race([pm2Promise, timeoutPromise]); -}; diff --git a/api/src/core/utils/process/unraid-api-running.ts b/api/src/core/utils/process/unraid-api-running.ts new file mode 100644 index 000000000..d1361b21f --- /dev/null +++ b/api/src/core/utils/process/unraid-api-running.ts @@ -0,0 +1,23 @@ +import { readFile } from 'node:fs/promises'; + +import { fileExists } from '@app/core/utils/files/file-exists.js'; +import { NODEMON_PID_PATH } from '@app/environment.js'; + +export const isUnraidApiRunning = async (): Promise => { + if (!(await fileExists(NODEMON_PID_PATH))) { + return false; + } + + const pidText = (await readFile(NODEMON_PID_PATH, 'utf-8')).trim(); + const pid = Number.parseInt(pidText, 10); + if (Number.isNaN(pid)) { + return false; + } + + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +}; diff --git a/api/src/environment.ts b/api/src/environment.ts index b1d3c2bad..3ab43336b 100644 --- a/api/src/environment.ts +++ b/api/src/environment.ts @@ -98,13 +98,22 @@ export const MOTHERSHIP_GRAPHQL_LINK = process.env.MOTHERSHIP_GRAPHQL_LINK ? 'https://staging.mothership.unraid.net/ws' : 'https://mothership.unraid.net/ws'; -export const PM2_HOME = process.env.PM2_HOME ?? '/var/log/.pm2'; -export const PM2_PATH = join(import.meta.dirname, '../../', 'node_modules', 'pm2', 'bin', 'pm2'); -export const ECOSYSTEM_PATH = join(import.meta.dirname, '../../', 'ecosystem.config.json'); export const PATHS_LOGS_DIR = process.env.PATHS_LOGS_DIR ?? process.env.LOGS_DIR ?? '/var/log/unraid-api'; export const PATHS_LOGS_FILE = process.env.PATHS_LOGS_FILE ?? '/var/log/graphql-api.log'; +export const NODEMON_PATH = join( + import.meta.dirname, + '../../', + 'node_modules', + 'nodemon', + 'bin', + 'nodemon.js' +); +export const NODEMON_CONFIG_PATH = join(import.meta.dirname, '../../', 'nodemon.json'); +export const NODEMON_PID_PATH = process.env.NODEMON_PID_PATH ?? '/var/run/unraid-api/nodemon.pid'; +export const UNRAID_API_CWD = process.env.UNRAID_API_CWD ?? join(import.meta.dirname, '../../'); + export const PATHS_CONFIG_MODULES = process.env.PATHS_CONFIG_MODULES ?? '/boot/config/plugins/dynamix.my.servers/configs'; diff --git a/api/src/unraid-api/cli/__test__/report.command.test.ts b/api/src/unraid-api/cli/__test__/report.command.test.ts index bbedcfbaf..ffdbd4d77 100644 --- a/api/src/unraid-api/cli/__test__/report.command.test.ts +++ b/api/src/unraid-api/cli/__test__/report.command.test.ts @@ -26,10 +26,10 @@ const mockApiReportService = { generateReport: vi.fn(), }; -// Mock PM2 check +// Mock process manager check const mockIsUnraidApiRunning = vi.fn().mockResolvedValue(true); -vi.mock('@app/core/utils/pm2/unraid-api-running.js', () => ({ +vi.mock('@app/core/utils/process/unraid-api-running.js', () => ({ isUnraidApiRunning: () => mockIsUnraidApiRunning(), })); @@ -50,7 +50,7 @@ describe('ReportCommand', () => { // Clear mocks vi.clearAllMocks(); - // Reset PM2 mock to default + // Reset nodemon mock to default mockIsUnraidApiRunning.mockResolvedValue(true); }); @@ -150,7 +150,7 @@ describe('ReportCommand', () => { // Reset mocks vi.clearAllMocks(); - // Test with API running but PM2 check returns true + // Test with API running but status check returns true mockIsUnraidApiRunning.mockResolvedValue(true); await reportCommand.report(); expect(mockApiReportService.generateReport).toHaveBeenCalledWith(true); diff --git a/api/src/unraid-api/cli/cli-services.module.ts b/api/src/unraid-api/cli/cli-services.module.ts index 7f248390d..a92c12694 100644 --- a/api/src/unraid-api/cli/cli-services.module.ts +++ b/api/src/unraid-api/cli/cli-services.module.ts @@ -4,7 +4,7 @@ import { DependencyService } from '@app/unraid-api/app/dependency.service.js'; import { ApiKeyService } from '@app/unraid-api/auth/api-key.service.js'; import { ApiReportService } from '@app/unraid-api/cli/api-report.service.js'; import { LogService } from '@app/unraid-api/cli/log.service.js'; -import { PM2Service } from '@app/unraid-api/cli/pm2.service.js'; +import { NodemonService } from '@app/unraid-api/cli/nodemon.service.js'; import { ApiConfigModule } from '@app/unraid-api/config/api-config.module.js'; import { LegacyConfigModule } from '@app/unraid-api/config/legacy-config.module.js'; import { GlobalDepsModule } from '@app/unraid-api/plugin/global-deps.module.js'; @@ -21,7 +21,7 @@ import { UnraidFileModifierModule } from '@app/unraid-api/unraid-file-modifier/u PluginCliModule.register(), UnraidFileModifierModule, ], - providers: [LogService, PM2Service, ApiKeyService, DependencyService, ApiReportService], + providers: [LogService, NodemonService, ApiKeyService, DependencyService, ApiReportService], exports: [ApiReportService, LogService, ApiKeyService], }) export class CliServicesModule {} diff --git a/api/src/unraid-api/cli/cli.module.ts b/api/src/unraid-api/cli/cli.module.ts index 7befdcb0e..9569475cb 100644 --- a/api/src/unraid-api/cli/cli.module.ts +++ b/api/src/unraid-api/cli/cli.module.ts @@ -13,6 +13,7 @@ import { DeveloperCommand } from '@app/unraid-api/cli/developer/developer.comman import { DeveloperQuestions } from '@app/unraid-api/cli/developer/developer.questions.js'; import { LogService } from '@app/unraid-api/cli/log.service.js'; import { LogsCommand } from '@app/unraid-api/cli/logs.command.js'; +import { NodemonService } from '@app/unraid-api/cli/nodemon.service.js'; import { InstallPluginCommand, ListPluginCommand, @@ -20,7 +21,6 @@ import { RemovePluginCommand, } from '@app/unraid-api/cli/plugins/plugin.command.js'; import { RemovePluginQuestionSet } from '@app/unraid-api/cli/plugins/remove-plugin.questions.js'; -import { PM2Service } from '@app/unraid-api/cli/pm2.service.js'; import { ReportCommand } from '@app/unraid-api/cli/report.command.js'; import { RestartCommand } from '@app/unraid-api/cli/restart.command.js'; import { SSOCommand } from '@app/unraid-api/cli/sso/sso.command.js'; @@ -64,7 +64,7 @@ const DEFAULT_PROVIDERS = [ DeveloperQuestions, DeveloperToolsService, LogService, - PM2Service, + NodemonService, ApiKeyService, DependencyService, ApiReportService, diff --git a/api/src/unraid-api/cli/generated/graphql.ts b/api/src/unraid-api/cli/generated/graphql.ts index 97e116fcb..2c991a943 100644 --- a/api/src/unraid-api/cli/generated/graphql.ts +++ b/api/src/unraid-api/cli/generated/graphql.ts @@ -559,6 +559,17 @@ export type CpuLoad = { percentUser: Scalars['Float']['output']; }; +export type CpuPackages = Node & { + __typename?: 'CpuPackages'; + id: Scalars['PrefixedID']['output']; + /** Power draw per package (W) */ + power: Array; + /** Temperature per package (°C) */ + temp: Array; + /** Total CPU package power draw (W) */ + totalPower: Scalars['Float']['output']; +}; + export type CpuUtilization = Node & { __typename?: 'CpuUtilization'; /** CPU load for each core */ @@ -869,6 +880,7 @@ export type InfoCpu = Node & { manufacturer?: Maybe; /** CPU model */ model?: Maybe; + packages: CpuPackages; /** Number of physical processors */ processors?: Maybe; /** CPU revision */ @@ -885,6 +897,8 @@ export type InfoCpu = Node & { stepping?: Maybe; /** Number of CPU threads */ threads?: Maybe; + /** Per-package array of core/thread pairs, e.g. [[[0,1],[2,3]], [[4,5],[6,7]]] */ + topology: Array>>; /** CPU vendor */ vendor?: Maybe; /** CPU voltage */ @@ -1531,14 +1545,14 @@ export type PackageVersions = { nginx?: Maybe; /** Node.js version */ node?: Maybe; + /** nodemon version */ + nodemon?: Maybe; /** npm version */ npm?: Maybe; /** OpenSSL version */ openssl?: Maybe; /** PHP version */ php?: Maybe; - /** pm2 version */ - pm2?: Maybe; }; export type ParityCheck = { @@ -2053,6 +2067,7 @@ export type Subscription = { parityHistorySubscription: ParityCheck; serversSubscription: Server; systemMetricsCpu: CpuUtilization; + systemMetricsCpuTelemetry: CpuPackages; systemMetricsMemory: MemoryUtilization; upsUpdates: UpsDevice; }; diff --git a/api/src/unraid-api/cli/logs.command.ts b/api/src/unraid-api/cli/logs.command.ts index c15d8e25a..0e5d7085f 100644 --- a/api/src/unraid-api/cli/logs.command.ts +++ b/api/src/unraid-api/cli/logs.command.ts @@ -1,6 +1,6 @@ import { Command, CommandRunner, Option } from 'nest-commander'; -import { PM2Service } from '@app/unraid-api/cli/pm2.service.js'; +import { NodemonService } from '@app/unraid-api/cli/nodemon.service.js'; interface LogsOptions { lines: number; @@ -8,7 +8,7 @@ interface LogsOptions { @Command({ name: 'logs', description: 'View logs' }) export class LogsCommand extends CommandRunner { - constructor(private readonly pm2: PM2Service) { + constructor(private readonly nodemon: NodemonService) { super(); } @@ -20,13 +20,6 @@ export class LogsCommand extends CommandRunner { async run(_: string[], options?: LogsOptions): Promise { const lines = options?.lines ?? 100; - await this.pm2.run( - { tag: 'PM2 Logs', stdio: 'inherit' }, - 'logs', - 'unraid-api', - '--lines', - lines.toString(), - '--raw' - ); + await this.nodemon.logs(lines); } } diff --git a/api/src/unraid-api/cli/nodemon.service.spec.ts b/api/src/unraid-api/cli/nodemon.service.spec.ts new file mode 100644 index 000000000..7ee7dae84 --- /dev/null +++ b/api/src/unraid-api/cli/nodemon.service.spec.ts @@ -0,0 +1,99 @@ +import { createWriteStream } from 'node:fs'; +import * as fs from 'node:fs/promises'; + +import { execa } from 'execa'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { fileExists } from '@app/core/utils/files/file-exists.js'; +import { NodemonService } from '@app/unraid-api/cli/nodemon.service.js'; + +vi.mock('node:fs', () => ({ + createWriteStream: vi.fn(() => ({ pipe: vi.fn() })), +})); +vi.mock('node:fs/promises'); +vi.mock('execa', () => ({ execa: vi.fn() })); +vi.mock('@app/core/utils/files/file-exists.js', () => ({ + fileExists: vi.fn().mockResolvedValue(false), +})); +vi.mock('@app/environment.js', () => ({ + NODEMON_CONFIG_PATH: '/etc/unraid-api/nodemon.json', + NODEMON_PATH: '/usr/bin/nodemon', + NODEMON_PID_PATH: '/var/run/unraid-api/nodemon.pid', + PATHS_LOGS_DIR: '/var/log/unraid-api', + PATHS_LOGS_FILE: '/var/log/graphql-api.log', + UNRAID_API_CWD: '/usr/local/unraid-api', +})); + +describe('NodemonService', () => { + const logger = { + trace: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + log: vi.fn(), + info: vi.fn(), + debug: vi.fn(), + } as unknown as NodemonService['logger']; + + const mockMkdir = vi.mocked(fs.mkdir); + const mockWriteFile = vi.mocked(fs.writeFile); + const mockRm = vi.mocked(fs.rm); + + beforeEach(() => { + vi.clearAllMocks(); + mockMkdir.mockResolvedValue(undefined); + mockWriteFile.mockResolvedValue(undefined as unknown as void); + mockRm.mockResolvedValue(undefined as unknown as void); + vi.mocked(fileExists).mockResolvedValue(false); + }); + + it('ensures directories needed by nodemon exist', async () => { + const service = new NodemonService(logger); + + await service.ensureNodemonDependencies(); + + expect(mockMkdir).toHaveBeenCalledWith('/var/log/unraid-api', { recursive: true }); + expect(mockMkdir).toHaveBeenCalledWith('/var/run/unraid-api', { recursive: true }); + }); + + it('starts nodemon and writes pid file', async () => { + const service = new NodemonService(logger); + const stdout = { pipe: vi.fn() }; + const stderr = { pipe: vi.fn() }; + const unref = vi.fn(); + vi.mocked(execa).mockReturnValue({ + pid: 123, + stdout, + stderr, + unref, + } as unknown as ReturnType); + + await service.start({ env: { LOG_LEVEL: 'DEBUG' } }); + + expect(execa).toHaveBeenCalledWith( + '/usr/bin/nodemon', + ['--config', '/etc/unraid-api/nodemon.json', '--quiet'], + { + cwd: '/usr/local/unraid-api', + env: expect.objectContaining({ LOG_LEVEL: 'DEBUG' }), + detached: true, + stdio: ['ignore', 'pipe', 'pipe'], + } + ); + expect(createWriteStream).toHaveBeenCalledWith('/var/log/graphql-api.log', { flags: 'a' }); + expect(stdout.pipe).toHaveBeenCalled(); + expect(stderr.pipe).toHaveBeenCalled(); + expect(unref).toHaveBeenCalled(); + expect(mockWriteFile).toHaveBeenCalledWith('/var/run/unraid-api/nodemon.pid', '123'); + expect(logger.info).toHaveBeenCalledWith('Started nodemon (pid 123)'); + }); + + it('returns not running when pid file is missing', async () => { + const service = new NodemonService(logger); + vi.mocked(fileExists).mockResolvedValue(false); + + const result = await service.status(); + + expect(result).toBe(false); + expect(logger.info).toHaveBeenCalledWith('unraid-api is not running (no pid file).'); + }); +}); diff --git a/api/src/unraid-api/cli/nodemon.service.ts b/api/src/unraid-api/cli/nodemon.service.ts new file mode 100644 index 000000000..1671e4526 --- /dev/null +++ b/api/src/unraid-api/cli/nodemon.service.ts @@ -0,0 +1,133 @@ +import { Injectable } from '@nestjs/common'; +import { createWriteStream } from 'node:fs'; +import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { dirname } from 'node:path'; + +import { execa } from 'execa'; + +import { fileExists } from '@app/core/utils/files/file-exists.js'; +import { + NODEMON_CONFIG_PATH, + NODEMON_PATH, + NODEMON_PID_PATH, + PATHS_LOGS_DIR, + PATHS_LOGS_FILE, + UNRAID_API_CWD, +} from '@app/environment.js'; +import { LogService } from '@app/unraid-api/cli/log.service.js'; + +type StartOptions = { + env?: Record; +}; + +type StopOptions = { + /** When true, uses SIGKILL instead of SIGTERM */ + force?: boolean; + /** Suppress warnings when there is no pid file */ + quiet?: boolean; +}; + +@Injectable() +export class NodemonService { + constructor(private readonly logger: LogService) {} + + async ensureNodemonDependencies() { + try { + await mkdir(PATHS_LOGS_DIR, { recursive: true }); + await mkdir(dirname(NODEMON_PID_PATH), { recursive: true }); + } catch (error) { + this.logger.error( + `Failed to fully ensure nodemon dependencies: ${error instanceof Error ? error.message : error}` + ); + } + } + + private async getStoredPid(): Promise { + if (!(await fileExists(NODEMON_PID_PATH))) return null; + const contents = (await readFile(NODEMON_PID_PATH, 'utf-8')).trim(); + const pid = Number.parseInt(contents, 10); + return Number.isNaN(pid) ? null : pid; + } + + private async isPidRunning(pid: number): Promise { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } + } + + async start(options: StartOptions = {}) { + await this.ensureNodemonDependencies(); + await this.stop({ quiet: true }); + + const env = { ...process.env, ...options.env } as Record; + const logStream = createWriteStream(PATHS_LOGS_FILE, { flags: 'a' }); + + const nodemonProcess = execa(NODEMON_PATH, ['--config', NODEMON_CONFIG_PATH, '--quiet'], { + cwd: UNRAID_API_CWD, + env, + detached: true, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + nodemonProcess.stdout?.pipe(logStream); + nodemonProcess.stderr?.pipe(logStream); + nodemonProcess.unref(); + + if (nodemonProcess.pid) { + await writeFile(NODEMON_PID_PATH, `${nodemonProcess.pid}`); + this.logger.info(`Started nodemon (pid ${nodemonProcess.pid})`); + } else { + this.logger.error('Failed to determine nodemon pid.'); + } + } + + async stop(options: StopOptions = {}) { + const pid = await this.getStoredPid(); + if (!pid) { + if (!options.quiet) { + this.logger.warn('No nodemon pid file found. Nothing to stop.'); + } + return; + } + + const signal: NodeJS.Signals = options.force ? 'SIGKILL' : 'SIGTERM'; + try { + process.kill(pid, signal); + this.logger.trace(`Sent ${signal} to nodemon (pid ${pid})`); + } catch (error) { + this.logger.error(`Failed to stop nodemon (pid ${pid}): ${error}`); + } finally { + await rm(NODEMON_PID_PATH, { force: true }); + } + } + + async restart(options: StartOptions = {}) { + await this.stop({ quiet: true }); + await this.start(options); + } + + async status(): Promise { + const pid = await this.getStoredPid(); + if (!pid) { + this.logger.info('unraid-api is not running (no pid file).'); + return false; + } + + const running = await this.isPidRunning(pid); + if (running) { + this.logger.info(`unraid-api is running under nodemon (pid ${pid}).`); + } else { + this.logger.warn(`Found nodemon pid file (${pid}) but the process is not running.`); + await rm(NODEMON_PID_PATH, { force: true }); + } + return running; + } + + async logs(lines = 100) { + const { stdout } = await execa('tail', ['-n', `${lines}`, PATHS_LOGS_FILE]); + this.logger.log(stdout); + } +} diff --git a/api/src/unraid-api/cli/pm2.service.spec.ts b/api/src/unraid-api/cli/pm2.service.spec.ts deleted file mode 100644 index 8c16cd518..000000000 --- a/api/src/unraid-api/cli/pm2.service.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as fs from 'node:fs/promises'; - -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -import { LogService } from '@app/unraid-api/cli/log.service.js'; -import { PM2Service } from '@app/unraid-api/cli/pm2.service.js'; - -vi.mock('node:fs/promises'); -vi.mock('execa'); -vi.mock('@app/core/utils/files/file-exists.js', () => ({ - fileExists: vi.fn().mockResolvedValue(false), -})); -vi.mock('@app/environment.js', () => ({ - PATHS_LOGS_DIR: '/var/log/unraid-api', - PM2_HOME: '/var/log/.pm2', - PM2_PATH: '/path/to/pm2', - ECOSYSTEM_PATH: '/path/to/ecosystem.config.json', - SUPPRESS_LOGS: false, - LOG_LEVEL: 'info', -})); - -describe('PM2Service', () => { - let pm2Service: PM2Service; - let logService: LogService; - const mockMkdir = vi.mocked(fs.mkdir); - - beforeEach(() => { - vi.clearAllMocks(); - logService = { - trace: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - log: vi.fn(), - info: vi.fn(), - debug: vi.fn(), - } as unknown as LogService; - pm2Service = new PM2Service(logService); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - describe('ensurePm2Dependencies', () => { - it('should create logs directory and log that PM2 will handle its own directory', async () => { - mockMkdir.mockResolvedValue(undefined); - - await pm2Service.ensurePm2Dependencies(); - - expect(mockMkdir).toHaveBeenCalledWith('/var/log/unraid-api', { recursive: true }); - expect(mockMkdir).toHaveBeenCalledTimes(1); // Only logs directory, not PM2_HOME - expect(logService.trace).toHaveBeenCalledWith( - 'PM2_HOME will be created at /var/log/.pm2 when PM2 daemon starts' - ); - }); - - it('should log error but not throw when logs directory creation fails', async () => { - mockMkdir.mockRejectedValue(new Error('Disk full')); - - await expect(pm2Service.ensurePm2Dependencies()).resolves.not.toThrow(); - - expect(logService.error).toHaveBeenCalledWith( - expect.stringContaining('Failed to fully ensure PM2 dependencies: Disk full') - ); - }); - - it('should handle mkdir with recursive flag for nested logs path', async () => { - mockMkdir.mockResolvedValue(undefined); - - await pm2Service.ensurePm2Dependencies(); - - expect(mockMkdir).toHaveBeenCalledWith('/var/log/unraid-api', { recursive: true }); - expect(mockMkdir).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/api/src/unraid-api/cli/pm2.service.ts b/api/src/unraid-api/cli/pm2.service.ts deleted file mode 100644 index b16a4a40b..000000000 --- a/api/src/unraid-api/cli/pm2.service.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { mkdir, rm } from 'node:fs/promises'; -import { join } from 'node:path'; - -import type { Options, Result, ResultPromise } from 'execa'; -import { execa, ExecaError } from 'execa'; - -import { fileExists } from '@app/core/utils/files/file-exists.js'; -import { PATHS_LOGS_DIR, PM2_HOME, PM2_PATH } from '@app/environment.js'; -import { LogService } from '@app/unraid-api/cli/log.service.js'; - -type CmdContext = Options & { - /** A tag for logging & debugging purposes. Should represent the operation being performed. */ - tag: string; - /** Default: false. - * - * When true, results will not be automatically handled and logged. - * The caller must handle desired effects, such as logging, error handling, etc. - */ - raw?: boolean; -}; - -@Injectable() -export class PM2Service { - constructor(private readonly logger: LogService) {} - - // Type Overload: if raw is true, return an execa ResultPromise (which is a Promise with extra properties) - /** - * Executes a PM2 command with the specified context and arguments. - * Handles logging automatically (stdout -> trace, stderr -> error), unless the `raw` flag is - * set to true, in which case the caller must handle desired effects. - * - * @param context - Execa Options for command execution, such as a unique tag for logging - * and whether the result should be handled raw. - * @param args - The arguments to pass to the PM2 command. - * @returns ResultPromise\<@param context\> When raw is true - * @returns Promise\ When raw is false - */ - run(context: T & { raw: true }, ...args: string[]): ResultPromise; - - run(context: CmdContext & { raw?: false }, ...args: string[]): Promise; - - async run(context: CmdContext, ...args: string[]) { - const { tag, raw, ...execOptions } = context; - // Default to true to match execa's default behavior - execOptions.extendEnv ??= true; - execOptions.shell ??= 'bash'; - - // Ensure /usr/local/bin is in PATH for Node.js - const currentPath = execOptions.env?.PATH || process.env.PATH || '/usr/bin:/bin:/usr/sbin:/sbin'; - const needsPathUpdate = !currentPath.includes('/usr/local/bin'); - const finalPath = needsPathUpdate ? `/usr/local/bin:${currentPath}` : currentPath; - - // Always ensure PM2_HOME is set in the environment for every PM2 command - execOptions.env = { - ...execOptions.env, - PM2_HOME, - ...(needsPathUpdate && { PATH: finalPath }), - }; - - const pm2Args = args.some((arg) => arg === '--no-color') ? args : ['--no-color', ...args]; - const runCommand = () => execa(PM2_PATH, pm2Args, execOptions satisfies Options); - if (raw) { - return runCommand(); - } - return runCommand() - .then((result) => { - this.logger.trace(result.stdout); - return result; - }) - .catch((result: Result) => { - this.logger.error(`PM2 error occurred from tag "${tag}": ${result.stdout}\n`); - return result; - }); - } - - /** - * Deletes the PM2 dump file. - * - * This method removes the PM2 dump file located at `~/.pm2/dump.pm2` by default. - * It logs a message indicating that the PM2 dump has been cleared. - * - * @returns A promise that resolves once the dump file is removed. - */ - async deleteDump(dumpFile = join(PM2_HOME, 'dump.pm2')) { - await rm(dumpFile, { force: true }); - this.logger.trace('PM2 dump cleared.'); - } - - async forceKillPm2Daemon() { - try { - // Find all PM2 daemon processes and kill them - const pids = (await execa('pgrep', ['-i', 'PM2'])).stdout.split('\n').filter(Boolean); - if (pids.length > 0) { - await execa('kill', ['-9', ...pids]); - this.logger.trace(`Killed PM2 daemon processes: ${pids.join(', ')}`); - } - } catch (err) { - if (err instanceof ExecaError && err.exitCode === 1) { - this.logger.trace('No PM2 daemon processes found.'); - } else { - this.logger.error(`Error force killing PM2 daemon: ${err}`); - } - } - } - - async deletePm2Home() { - if ((await fileExists(PM2_HOME)) && (await fileExists(join(PM2_HOME, 'pm2.log')))) { - await rm(PM2_HOME, { recursive: true, force: true }); - this.logger.trace('PM2 home directory cleared.'); - } else { - this.logger.trace('PM2 home directory does not exist.'); - } - } - - /** - * Ensures that the dependencies necessary for PM2 to start and operate are present. - * Creates PM2_HOME directory with proper permissions if it doesn't exist. - */ - async ensurePm2Dependencies() { - try { - // Create logs directory - await mkdir(PATHS_LOGS_DIR, { recursive: true }); - - // PM2 automatically creates and manages its home directory when the daemon starts - this.logger.trace(`PM2_HOME will be created at ${PM2_HOME} when PM2 daemon starts`); - } catch (error) { - // Log error but don't throw - let PM2 fail with its own error messages if the setup is incomplete - this.logger.error( - `Failed to fully ensure PM2 dependencies: ${error instanceof Error ? error.message : error}. PM2 may encounter issues during operation.` - ); - } - } -} diff --git a/api/src/unraid-api/cli/report.command.ts b/api/src/unraid-api/cli/report.command.ts index 1e03dea5c..49188cf77 100644 --- a/api/src/unraid-api/cli/report.command.ts +++ b/api/src/unraid-api/cli/report.command.ts @@ -33,9 +33,9 @@ export class ReportCommand extends CommandRunner { async report(): Promise { try { // Check if API is running - const { isUnraidApiRunning } = await import('@app/core/utils/pm2/unraid-api-running.js'); + const { isUnraidApiRunning } = await import('@app/core/utils/process/unraid-api-running.js'); const apiRunning = await isUnraidApiRunning().catch((err) => { - this.logger.debug('failed to get PM2 state with error: ' + err); + this.logger.debug('failed to check nodemon state with error: ' + err); return false; }); diff --git a/api/src/unraid-api/cli/restart.command.ts b/api/src/unraid-api/cli/restart.command.ts index 66d54a513..166162fa6 100644 --- a/api/src/unraid-api/cli/restart.command.ts +++ b/api/src/unraid-api/cli/restart.command.ts @@ -2,9 +2,9 @@ import { Command, CommandRunner, Option } from 'nest-commander'; import type { LogLevel } from '@app/core/log.js'; import { levels } from '@app/core/log.js'; -import { ECOSYSTEM_PATH, LOG_LEVEL } from '@app/environment.js'; +import { LOG_LEVEL } from '@app/environment.js'; import { LogService } from '@app/unraid-api/cli/log.service.js'; -import { PM2Service } from '@app/unraid-api/cli/pm2.service.js'; +import { NodemonService } from '@app/unraid-api/cli/nodemon.service.js'; export interface LogLevelOptions { logLevel?: LogLevel; @@ -22,7 +22,7 @@ export function parseLogLevelOption(val: string, allowedLevels: string[] = [...l export class RestartCommand extends CommandRunner { constructor( private readonly logger: LogService, - private readonly pm2: PM2Service + private readonly nodemon: NodemonService ) { super(); } @@ -30,23 +30,9 @@ export class RestartCommand extends CommandRunner { async run(_?: string[], options: LogLevelOptions = {}): Promise { try { this.logger.info('Restarting the Unraid API...'); - const env = { LOG_LEVEL: options.logLevel }; - const { stderr, stdout } = await this.pm2.run( - { tag: 'PM2 Restart', raw: true, extendEnv: true, env }, - 'restart', - ECOSYSTEM_PATH, - '--update-env', - '--mini-list' - ); - - if (stderr) { - this.logger.error(stderr.toString()); - process.exit(1); - } else if (stdout) { - this.logger.info(stdout.toString()); - } else { - this.logger.info('Unraid API restarted'); - } + const env = { LOG_LEVEL: options.logLevel?.toUpperCase() }; + await this.nodemon.restart({ env }); + this.logger.info('Unraid API restarted'); } catch (error) { if (error instanceof Error) { this.logger.error(error.message); diff --git a/api/src/unraid-api/cli/start.command.ts b/api/src/unraid-api/cli/start.command.ts index 64c7d890d..61660612c 100644 --- a/api/src/unraid-api/cli/start.command.ts +++ b/api/src/unraid-api/cli/start.command.ts @@ -3,46 +3,23 @@ import { Command, CommandRunner, Option } from 'nest-commander'; import type { LogLevel } from '@app/core/log.js'; import type { LogLevelOptions } from '@app/unraid-api/cli/restart.command.js'; import { levels } from '@app/core/log.js'; -import { ECOSYSTEM_PATH, LOG_LEVEL } from '@app/environment.js'; +import { LOG_LEVEL } from '@app/environment.js'; import { LogService } from '@app/unraid-api/cli/log.service.js'; -import { PM2Service } from '@app/unraid-api/cli/pm2.service.js'; +import { NodemonService } from '@app/unraid-api/cli/nodemon.service.js'; import { parseLogLevelOption } from '@app/unraid-api/cli/restart.command.js'; @Command({ name: 'start', description: 'Start the Unraid API' }) export class StartCommand extends CommandRunner { constructor( private readonly logger: LogService, - private readonly pm2: PM2Service + private readonly nodemon: NodemonService ) { super(); } - async cleanupPM2State() { - await this.pm2.ensurePm2Dependencies(); - await this.pm2.run({ tag: 'PM2 Stop' }, 'stop', ECOSYSTEM_PATH); - await this.pm2.run({ tag: 'PM2 Update' }, 'update'); - await this.pm2.deleteDump(); - await this.pm2.run({ tag: 'PM2 Delete' }, 'delete', ECOSYSTEM_PATH); - } - async run(_: string[], options: LogLevelOptions): Promise { this.logger.info('Starting the Unraid API'); - await this.cleanupPM2State(); - const env = { LOG_LEVEL: options.logLevel }; - const { stderr, stdout } = await this.pm2.run( - { tag: 'PM2 Start', raw: true, extendEnv: true, env }, - 'start', - ECOSYSTEM_PATH, - '--update-env', - '--mini-list' - ); - if (stdout) { - this.logger.log(stdout.toString()); - } - if (stderr) { - this.logger.error(stderr.toString()); - process.exit(1); - } + await this.nodemon.start({ env: { LOG_LEVEL: options.logLevel?.toUpperCase() } }); } @Option({ diff --git a/api/src/unraid-api/cli/status.command.ts b/api/src/unraid-api/cli/status.command.ts index 6e1b6b6e2..489198e3b 100644 --- a/api/src/unraid-api/cli/status.command.ts +++ b/api/src/unraid-api/cli/status.command.ts @@ -1,18 +1,13 @@ import { Command, CommandRunner } from 'nest-commander'; -import { PM2Service } from '@app/unraid-api/cli/pm2.service.js'; +import { NodemonService } from '@app/unraid-api/cli/nodemon.service.js'; @Command({ name: 'status', description: 'Check status of unraid-api service' }) export class StatusCommand extends CommandRunner { - constructor(private readonly pm2: PM2Service) { + constructor(private readonly nodemon: NodemonService) { super(); } async run(): Promise { - await this.pm2.run( - { tag: 'PM2 Status', stdio: 'inherit', raw: true }, - 'status', - 'unraid-api', - '--mini-list' - ); + await this.nodemon.status(); } } diff --git a/api/src/unraid-api/cli/stop.command.ts b/api/src/unraid-api/cli/stop.command.ts index 995dd0743..376c89c6e 100644 --- a/api/src/unraid-api/cli/stop.command.ts +++ b/api/src/unraid-api/cli/stop.command.ts @@ -1,41 +1,28 @@ import { Command, CommandRunner, Option } from 'nest-commander'; -import { ECOSYSTEM_PATH } from '@app/environment.js'; -import { PM2Service } from '@app/unraid-api/cli/pm2.service.js'; +import { NodemonService } from '@app/unraid-api/cli/nodemon.service.js'; interface StopCommandOptions { - delete: boolean; + force: boolean; } @Command({ name: 'stop', description: 'Stop the Unraid API', }) export class StopCommand extends CommandRunner { - constructor(private readonly pm2: PM2Service) { + constructor(private readonly nodemon: NodemonService) { super(); } @Option({ - flags: '-d, --delete', - description: 'Delete the PM2 home directory', + flags: '-f, --force', + description: 'Forcefully stop the API process', }) - parseDelete(): boolean { + parseForce(): boolean { return true; } - async run(_: string[], options: StopCommandOptions = { delete: false }) { - if (options.delete) { - await this.pm2.run({ tag: 'PM2 Kill', stdio: 'inherit' }, 'kill', '--no-autorestart'); - await this.pm2.forceKillPm2Daemon(); - await this.pm2.deletePm2Home(); - } else { - await this.pm2.run( - { tag: 'PM2 Delete', stdio: 'inherit' }, - 'delete', - ECOSYSTEM_PATH, - '--no-autorestart', - '--mini-list' - ); - } + async run(_: string[], options: StopCommandOptions = { force: false }) { + await this.nodemon.stop({ force: options.force }); } } diff --git a/api/src/unraid-api/graph/resolvers/info/versions/versions.model.ts b/api/src/unraid-api/graph/resolvers/info/versions/versions.model.ts index dd6fe5d88..2080cbbb9 100644 --- a/api/src/unraid-api/graph/resolvers/info/versions/versions.model.ts +++ b/api/src/unraid-api/graph/resolvers/info/versions/versions.model.ts @@ -25,8 +25,8 @@ export class PackageVersions { @Field(() => String, { nullable: true, description: 'npm version' }) npm?: string; - @Field(() => String, { nullable: true, description: 'pm2 version' }) - pm2?: string; + @Field(() => String, { nullable: true, description: 'nodemon version' }) + nodemon?: string; @Field(() => String, { nullable: true, description: 'Git version' }) git?: string; diff --git a/api/src/unraid-api/graph/resolvers/info/versions/versions.resolver.ts b/api/src/unraid-api/graph/resolvers/info/versions/versions.resolver.ts index a711a17dd..836122b3b 100644 --- a/api/src/unraid-api/graph/resolvers/info/versions/versions.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/info/versions/versions.resolver.ts @@ -3,6 +3,7 @@ import { ResolveField, Resolver } from '@nestjs/graphql'; import { versions } from 'systeminformation'; +import { getPackageJson } from '@app/environment.js'; import { CoreVersions, InfoVersions, @@ -34,7 +35,7 @@ export class VersionsResolver { openssl: softwareVersions.openssl, node: softwareVersions.node, npm: softwareVersions.npm, - pm2: softwareVersions.pm2, + nodemon: getPackageJson().dependencies?.nodemon, git: softwareVersions.git, nginx: softwareVersions.nginx, php: softwareVersions.php, diff --git a/api/src/unraid-api/main.ts b/api/src/unraid-api/main.ts index 4b753abfa..cc07b5b63 100644 --- a/api/src/unraid-api/main.ts +++ b/api/src/unraid-api/main.ts @@ -140,16 +140,6 @@ export async function bootstrapNestServer(): Promise { apiLogger.info('Server listening on %s', result); } - // This 'ready' signal tells pm2 that the api has started. - // PM2 documents this as Graceful Start or Clean Restart. - // See https://pm2.keymetrics.io/docs/usage/signals-clean-restart/ - if (process.send) { - process.send('ready'); - } else { - apiLogger.warn( - 'Warning: process.send is unavailable. This will affect IPC communication with PM2.' - ); - } apiLogger.info('Nest Server is now listening'); return app; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8db98eb3a..a9a59a9b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -262,6 +262,9 @@ importers: node-window-polyfill: specifier: 1.0.4 version: 1.0.4 + nodemon: + specifier: 3.1.10 + version: 3.1.10 openid-client: specifier: 6.6.4 version: 6.6.4 @@ -286,9 +289,6 @@ importers: pino-pretty: specifier: 13.1.1 version: 13.1.1 - pm2: - specifier: 6.0.8 - version: 6.0.8 reflect-metadata: specifier: ^0.1.14 version: 0.1.14 @@ -458,9 +458,6 @@ importers: jiti: specifier: 2.5.1 version: 2.5.1 - nodemon: - specifier: 3.1.10 - version: 3.1.10 prettier: specifier: 3.6.2 version: 3.6.2 @@ -4028,20 +4025,6 @@ packages: resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@pm2/agent@2.1.1': - resolution: {integrity: sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==} - - '@pm2/io@6.1.0': - resolution: {integrity: sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==} - engines: {node: '>=6.0'} - - '@pm2/js-api@0.8.0': - resolution: {integrity: sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==} - engines: {node: '>=4.0'} - - '@pm2/pm2-version-check@1.0.4': - resolution: {integrity: sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==} - '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -4764,9 +4747,6 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@tootallnate/quickjs-emscripten@0.23.0': - resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@ts-morph/common@0.25.0': resolution: {integrity: sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==} @@ -5771,16 +5751,6 @@ packages: alien-signals@2.0.5: resolution: {integrity: sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==} - amp-message@0.1.2: - resolution: {integrity: sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==} - - amp@0.3.1: - resolution: {integrity: sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==} - - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -5824,10 +5794,6 @@ packages: ansi_up@6.0.6: resolution: {integrity: sha512-yIa1x3Ecf8jWP4UWEunNjqNX6gzE4vg2gGz+xqRGY+TBSucnYp6RRdPV4brmtg6bQ1ljD48mZ5iGSEj7QEpRKA==} - ansis@4.0.0-node10: - resolution: {integrity: sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==} - engines: {node: '>=10'} - ansis@4.1.0: resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} engines: {node: '>=14'} @@ -5935,10 +5901,6 @@ packages: resolution: {integrity: sha512-ZtfIlyTCmnAXPCQo4mSDtFsHL7L3q0sJfpVYPmy5uYPjs+fynzOuc1Cg6yQ9fF6h61RjEWtOlRFwV1Kc80Qs6A==} engines: {node: '>=4'} - ast-types@0.13.4: - resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} - engines: {node: '>=4'} - ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} @@ -5967,9 +5929,6 @@ packages: async@1.5.2: resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} - async@2.6.4: - resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} - async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -6055,10 +6014,6 @@ packages: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} - basic-ftp@5.0.5: - resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} - engines: {node: '>=10.0.0'} - bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} @@ -6082,17 +6037,9 @@ packages: blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - blessed@0.1.81: - resolution: {integrity: sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==} - engines: {node: '>= 0.8.0'} - hasBin: true - blob-to-buffer@1.2.9: resolution: {integrity: sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==} - bodec@0.1.0: - resolution: {integrity: sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==} - body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -6273,9 +6220,6 @@ packages: chardet@2.1.0: resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} - charm@0.1.2: - resolution: {integrity: sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==} - check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -6330,10 +6274,6 @@ packages: resolution: {integrity: sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==} engines: {node: '>= 0.2.0'} - cli-tableau@2.0.1: - resolution: {integrity: sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==} - engines: {node: '>=8.10.0'} - cli-truncate@4.0.0: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} engines: {node: '>=18'} @@ -6439,9 +6379,6 @@ packages: resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==} engines: {node: '>=20'} - commander@2.15.1: - resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==} - commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -6630,9 +6567,6 @@ packages: resolution: {integrity: sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==} engines: {node: '>=18.x'} - croner@4.1.97: - resolution: {integrity: sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==} - croner@9.1.0: resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==} engines: {node: '>=18.0'} @@ -6724,9 +6658,6 @@ packages: csv-parse@5.6.0: resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==} - culvert@0.1.2: - resolution: {integrity: sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==} - d@1.0.2: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} @@ -6735,10 +6666,6 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} - data-uri-to-buffer@6.0.2: - resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} - engines: {node: '>= 14'} - data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -6764,15 +6691,9 @@ packages: dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - dayjs@1.11.13: - resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dayjs@1.11.14: resolution: {integrity: sha512-E8fIdSxUlyqSA8XYGnNa3IkIzxtEmFjI+JU/6ic0P1zmSqyL6HyG5jHnpPjRguDNiaHLpfvHKWFiohNsJLqcJQ==} - dayjs@1.8.36: - resolution: {integrity: sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==} - db0@0.3.2: resolution: {integrity: sha512-xzWNQ6jk/+NtdfLyXEipbX55dmDSeteLFt/ayF+wZUU5bzKgmrDOxmInUTbyVRp46YwnJdkDA1KhB7WIXFofJw==} peerDependencies: @@ -6819,15 +6740,6 @@ packages: supports-color: optional: true - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -6911,10 +6823,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - degenerator@5.0.1: - resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} - engines: {node: '>= 14'} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -7151,10 +7059,6 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} - enquirer@2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -7407,11 +7311,6 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - eslint-config-prettier@10.1.8: resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true @@ -7602,9 +7501,6 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} - eventemitter2@5.0.1: - resolution: {integrity: sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==} - eventemitter2@6.4.9: resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} @@ -7674,9 +7570,6 @@ packages: resolution: {integrity: sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==} engines: {node: ^12.20 || >= 14.13} - extrareqp2@1.0.0: - resolution: {integrity: sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==} - fast-check@4.2.0: resolution: {integrity: sha512-buxrKEaSseOwFjt6K1REcGMeFOrb0wk3cXifeMAG8yahcE9kV20PjQn1OdzPGL6OBFTbYXfjleNBARf/aCfV1A==} engines: {node: '>=12.17.0'} @@ -7700,9 +7593,6 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-json-patch@3.1.1: - resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -7756,9 +7646,6 @@ packages: fbjs@3.0.5: resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==} - fclone@1.0.11: - resolution: {integrity: sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==} - fd-package-json@2.0.0: resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} @@ -8001,25 +7888,10 @@ packages: get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - get-uri@6.0.4: - resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} - engines: {node: '>= 14'} - giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true - git-node-fs@1.0.0: - resolution: {integrity: sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==} - peerDependencies: - js-git: ^0.7.8 - peerDependenciesMeta: - js-git: - optional: true - - git-sha1@0.1.2: - resolution: {integrity: sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==} - git-up@8.1.1: resolution: {integrity: sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==} @@ -8454,10 +8326,6 @@ packages: resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==} engines: {node: '>=12.22.0'} - ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} - engines: {node: '>= 12'} - ip@2.0.1: resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} @@ -8818,9 +8686,6 @@ packages: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} - js-git@0.7.8: - resolution: {integrity: sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==} - js-stringify@1.0.2: resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} @@ -8834,9 +8699,6 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - jsdom@26.1.0: resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} engines: {node: '>=18'} @@ -9415,11 +9277,6 @@ packages: mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} @@ -9434,9 +9291,6 @@ packages: mocked-exports@0.1.1: resolution: {integrity: sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA==} - module-details-from-path@1.0.3: - resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} - motion-dom@12.23.12: resolution: {integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==} @@ -9516,11 +9370,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - needle@2.4.0: - resolution: {integrity: sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==} - engines: {node: '>= 4.4.x'} - hasBin: true - negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -9561,10 +9410,6 @@ packages: resolution: {integrity: sha512-Nc3loyVASW59W+8fLDZT1lncpG7llffyZ2o0UQLx/Fr20i7P8oP+lE7+TEcFvXj9IUWU6LjB9P3BH+iFGyp+mg==} engines: {node: ^14.16.0 || >=16.0.0} - netmask@2.0.2: - resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} - engines: {node: '>= 0.4.0'} - next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} @@ -9912,14 +9757,6 @@ packages: resolution: {integrity: sha512-lwx6u1CotQYPVju77R+D0vFomni/AqRfqLmqQ8hekklqZ6gAY9rONh7lBQ0uxWMkC2AuX9b2DVAl8To0NyP1JA==} engines: {node: '>=12'} - pac-proxy-agent@7.1.0: - resolution: {integrity: sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==} - engines: {node: '>= 14'} - - pac-resolver@7.0.1: - resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} - engines: {node: '>= 14'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -10099,14 +9936,6 @@ packages: engines: {node: '>=0.10'} hasBin: true - pidusage@2.0.21: - resolution: {integrity: sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==} - engines: {node: '>=8'} - - pidusage@3.0.2: - resolution: {integrity: sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==} - engines: {node: '>=10'} - pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -10172,29 +10001,6 @@ packages: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} - pm2-axon-rpc@0.7.1: - resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==} - engines: {node: '>=5'} - - pm2-axon@4.0.1: - resolution: {integrity: sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==} - engines: {node: '>=5'} - - pm2-deploy@1.0.2: - resolution: {integrity: sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==} - engines: {node: '>=4.0.0'} - - pm2-multimeter@0.1.2: - resolution: {integrity: sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==} - - pm2-sysmonit@1.2.8: - resolution: {integrity: sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==} - - pm2@6.0.8: - resolution: {integrity: sha512-y7sO+UuGjfESK/ChRN+efJKAsHrBd95GY2p1GQfjVTtOfFtUfiW0NOuUhP5dN5QTF2F0EWcepgkLqbF32j90Iw==} - engines: {node: '>=16.0.0'} - hasBin: true - portfinder@1.0.35: resolution: {integrity: sha512-73JaFg4NwYNAufDtS5FsFu/PdM49ahJrO1i44aCRsDWju1z5wuGDaqyFUQWR6aJoK2JPDWlaYYAGFNIGTSUHSw==} engines: {node: '>= 10.12'} @@ -10512,9 +10318,6 @@ packages: promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} - promptly@2.2.0: - resolution: {integrity: sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==} - prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -10539,13 +10342,6 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-agent@6.4.0: - resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==} - engines: {node: '>= 14'} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} @@ -10662,10 +10458,6 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - read@1.0.7: - resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==} - engines: {node: '>=0.8'} - readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -10787,10 +10579,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - require-in-the-middle@5.2.0: - resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} - engines: {node: '>=6'} - require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} @@ -10916,9 +10704,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - run-series@1.1.9: - resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==} - rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} @@ -11073,9 +10858,6 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} - shimmer@1.2.1: - resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} - side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -11146,24 +10928,12 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - smob@1.5.0: resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} - - socks@2.8.4: - resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sonic-boom@4.2.0: resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} @@ -11219,9 +10989,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sprintf-js@1.1.2: - resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} - sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} @@ -11696,9 +11463,6 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - tslib@1.9.3: - resolution: {integrity: sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==} - tslib@2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} @@ -11713,19 +11477,12 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - tv4@1.3.0: - resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==} - engines: {node: '>= 0.8.0'} - tw-animate-css@1.3.7: resolution: {integrity: sha512-lvLb3hTIpB5oGsk8JmLoAjeCHV58nKa2zHYn8yWOoG5JJusH3bhJlF2DLAZ/5NmJ+jyH3ssiAx/2KmbhavJy/A==} tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - tx2@1.0.5: - resolution: {integrity: sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==} - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -12406,10 +12163,6 @@ packages: jsdom: optional: true - vizion@2.2.1: - resolution: {integrity: sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==} - engines: {node: '>=4.0'} - void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -12434,8 +12187,8 @@ packages: vue-component-type-helpers@3.0.6: resolution: {integrity: sha512-6CRM8X7EJqWCJOiKPvSLQG+hJPb/Oy2gyJx3pLjUEhY7PuaCthQu3e0zAGI1lqUBobrrk9IT0K8sG2GsCluxoQ==} - vue-component-type-helpers@3.1.3: - resolution: {integrity: sha512-V1dOD8XYfstOKCnXbWyEJIrhTBMwSyNjv271L1Jlx9ExpNlCSuqOs3OdWrGJ0V544zXufKbcYabi/o+gK8lyfQ==} + vue-component-type-helpers@3.1.4: + resolution: {integrity: sha512-Uws7Ew1OzTTqHW8ZVl/qLl/HB+jf08M0NdFONbVWAx0N4gMLK8yfZDgeB77hDnBmaigWWEn5qP8T9BG59jIeyQ==} vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} @@ -16053,56 +15806,6 @@ snapshots: '@pkgr/core@0.2.7': {} - '@pm2/agent@2.1.1': - dependencies: - async: 3.2.6 - chalk: 3.0.0 - dayjs: 1.8.36 - debug: 4.3.7 - eventemitter2: 5.0.1 - fast-json-patch: 3.1.1 - fclone: 1.0.11 - pm2-axon: 4.0.1 - pm2-axon-rpc: 0.7.1 - proxy-agent: 6.4.0 - semver: 7.5.4 - ws: 7.5.10 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@pm2/io@6.1.0': - dependencies: - async: 2.6.4 - debug: 4.3.7 - eventemitter2: 6.4.9 - require-in-the-middle: 5.2.0 - semver: 7.5.4 - shimmer: 1.2.1 - signal-exit: 3.0.7 - tslib: 1.9.3 - transitivePeerDependencies: - - supports-color - - '@pm2/js-api@0.8.0': - dependencies: - async: 2.6.4 - debug: 4.3.7 - eventemitter2: 6.4.9 - extrareqp2: 1.0.0(debug@4.3.7) - ws: 7.5.10 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@pm2/pm2-version-check@1.0.4': - dependencies: - debug: 4.4.1(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -16500,7 +16203,7 @@ snapshots: storybook: 9.1.3(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)) type-fest: 2.19.0 vue: 3.5.20(typescript@5.9.2) - vue-component-type-helpers: 3.1.3 + vue-component-type-helpers: 3.1.4 '@swc/core-darwin-arm64@1.13.5': optional: true @@ -16728,8 +16431,6 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@tootallnate/quickjs-emscripten@0.23.0': {} - '@ts-morph/common@0.25.0': dependencies: minimatch: 9.0.5 @@ -17885,14 +17586,6 @@ snapshots: alien-signals@2.0.5: {} - amp-message@0.1.2: - dependencies: - amp: 0.3.1 - - amp@0.3.1: {} - - ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -17923,8 +17616,6 @@ snapshots: ansi_up@6.0.6: {} - ansis@4.0.0-node10: {} - ansis@4.1.0: {} anymatch@3.1.3: @@ -18065,10 +17756,6 @@ snapshots: dependencies: tslib: 2.8.1 - ast-types@0.13.4: - dependencies: - tslib: 2.8.1 - ast-types@0.16.1: dependencies: tslib: 2.8.1 @@ -18096,10 +17783,6 @@ snapshots: async@1.5.2: {} - async@2.6.4: - dependencies: - lodash: 4.17.21 - async@3.2.6: {} asynckit@0.4.0: {} @@ -18138,7 +17821,7 @@ snapshots: axios@0.26.1: dependencies: - follow-redirects: 1.15.9(debug@4.3.7) + follow-redirects: 1.15.9 transitivePeerDependencies: - debug @@ -18203,8 +17886,6 @@ snapshots: dependencies: safe-buffer: 5.1.2 - basic-ftp@5.0.5: {} - bcrypt-pbkdf@1.0.2: dependencies: tweetnacl: 0.14.5 @@ -18229,12 +17910,8 @@ snapshots: blake3-wasm@2.1.5: {} - blessed@0.1.81: {} - blob-to-buffer@1.2.9: {} - bodec@0.1.0: {} - body-parser@1.20.3: dependencies: bytes: 3.1.2 @@ -18489,8 +18166,6 @@ snapshots: chardet@2.1.0: {} - charm@0.1.2: {} - check-error@2.1.1: {} chokidar@3.6.0: @@ -18556,10 +18231,6 @@ snapshots: dependencies: colors: 1.0.3 - cli-tableau@2.0.1: - dependencies: - chalk: 3.0.0 - cli-truncate@4.0.0: dependencies: slice-ansi: 5.0.0 @@ -18649,8 +18320,6 @@ snapshots: commander@14.0.0: {} - commander@2.15.1: {} - commander@2.20.3: {} commander@5.1.0: {} @@ -18844,8 +18513,6 @@ snapshots: '@types/luxon': 3.6.2 luxon: 3.6.1 - croner@4.1.97: {} - croner@9.1.0: {} cross-fetch@3.2.0: @@ -18969,8 +18636,6 @@ snapshots: csv-parse@5.6.0: {} - culvert@0.1.2: {} - d@1.0.2: dependencies: es5-ext: 0.10.64 @@ -18978,8 +18643,6 @@ snapshots: data-uri-to-buffer@4.0.1: {} - data-uri-to-buffer@6.0.2: {} - data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -19009,12 +18672,8 @@ snapshots: dateformat@4.6.3: {} - dayjs@1.11.13: {} - dayjs@1.11.14: {} - dayjs@1.8.36: {} - db0@0.3.2: {} de-indent@1.0.2: {} @@ -19029,10 +18688,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.7: - dependencies: - ms: 2.1.3 - debug@4.4.1(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -19116,12 +18771,6 @@ snapshots: defu@6.1.4: {} - degenerator@5.0.1: - dependencies: - ast-types: 0.13.4 - escodegen: 2.1.0 - esprima: 4.0.1 - delayed-stream@1.0.0: {} denque@2.1.0: {} @@ -19327,10 +18976,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.2 - enquirer@2.3.6: - dependencies: - ansi-colors: 4.1.3 - entities@4.5.0: {} entities@6.0.1: {} @@ -19671,14 +19316,6 @@ snapshots: escape-string-regexp@5.0.0: {} - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@2.5.1)): dependencies: eslint: 9.34.0(jiti@2.5.1) @@ -19910,8 +19547,6 @@ snapshots: event-target-shim@5.0.1: {} - eventemitter2@5.0.1: {} - eventemitter2@6.4.9: {} eventemitter3@3.1.2: {} @@ -20029,12 +19664,6 @@ snapshots: extract-files@11.0.0: {} - extrareqp2@1.0.0(debug@4.3.7): - dependencies: - follow-redirects: 1.15.9(debug@4.3.7) - transitivePeerDependencies: - - debug - fast-check@4.2.0: dependencies: pure-rand: 7.0.1 @@ -20057,8 +19686,6 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-json-patch@3.1.1: {} - fast-json-stable-stringify@2.1.0: {} fast-json-stringify@6.0.1: @@ -20150,8 +19777,6 @@ snapshots: transitivePeerDependencies: - encoding - fclone@1.0.11: {} - fd-package-json@2.0.0: dependencies: walk-up-path: 4.0.0 @@ -20254,9 +19879,7 @@ snapshots: dependencies: tabbable: 6.2.0 - follow-redirects@1.15.9(debug@4.3.7): - optionalDependencies: - debug: 4.3.7 + follow-redirects@1.15.9: {} fontaine@0.6.0: dependencies: @@ -20416,14 +20039,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - get-uri@6.0.4: - dependencies: - basic-ftp: 5.0.5 - data-uri-to-buffer: 6.0.2 - debug: 4.4.1(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - giget@2.0.0: dependencies: citty: 0.1.6 @@ -20433,12 +20048,6 @@ snapshots: nypm: 0.6.1 pathe: 2.0.3 - git-node-fs@1.0.0(js-git@0.7.8): - optionalDependencies: - js-git: 0.7.8 - - git-sha1@0.1.2: {} - git-up@8.1.1: dependencies: is-ssh: 1.4.1 @@ -20752,7 +20361,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.9(debug@4.3.7) + follow-redirects: 1.15.9 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -20932,11 +20541,6 @@ snapshots: transitivePeerDependencies: - supports-color - ip-address@9.0.5: - dependencies: - jsbn: 1.1.0 - sprintf-js: 1.1.3 - ip@2.0.1: {} ipaddr.js@1.9.1: {} @@ -21255,13 +20859,6 @@ snapshots: js-cookie@3.0.5: {} - js-git@0.7.8: - dependencies: - bodec: 0.1.0 - culvert: 0.1.2 - git-sha1: 0.1.2 - pako: 0.2.9 - js-stringify@1.0.2: {} js-tokens@4.0.0: {} @@ -21272,8 +20869,6 @@ snapshots: dependencies: argparse: 2.0.1 - jsbn@1.1.0: {} - jsdom@26.1.0: dependencies: cssstyle: 4.5.0 @@ -21815,8 +21410,6 @@ snapshots: mkdirp-classic@0.5.3: {} - mkdirp@1.0.4: {} - mkdirp@3.0.1: {} mlly@1.7.4: @@ -21835,8 +21428,6 @@ snapshots: mocked-exports@0.1.1: {} - module-details-from-path@1.0.3: {} - motion-dom@12.23.12: dependencies: motion-utils: 12.23.6 @@ -21896,14 +21487,6 @@ snapshots: natural-compare@1.4.0: {} - needle@2.4.0: - dependencies: - debug: 3.2.7 - iconv-lite: 0.4.24 - sax: 1.4.1 - transitivePeerDependencies: - - supports-color - negotiator@0.6.3: {} negotiator@0.6.4: {} @@ -21949,8 +21532,6 @@ snapshots: qs: 6.14.0 optional: true - netmask@2.0.2: {} - next-tick@1.1.0: {} nitropack@2.12.5(@netlify/blobs@9.1.2)(xml2js@0.6.2): @@ -22580,24 +22161,6 @@ snapshots: p-timeout: 6.1.4 optional: true - pac-proxy-agent@7.1.0: - dependencies: - '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.3 - debug: 4.4.1(supports-color@5.5.0) - get-uri: 6.0.4 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - pac-resolver@7.0.1: - dependencies: - degenerator: 5.0.1 - netmask: 2.0.2 - package-json-from-dist@1.0.1: {} package-json@10.0.1: @@ -22746,15 +22309,6 @@ snapshots: pidtree@0.6.0: {} - pidusage@2.0.21: - dependencies: - safe-buffer: 5.2.1 - optional: true - - pidusage@3.0.2: - dependencies: - safe-buffer: 5.2.1 - pify@2.3.0: {} pify@3.0.0: {} @@ -22840,79 +22394,6 @@ snapshots: dependencies: find-up: 3.0.0 - pm2-axon-rpc@0.7.1: - dependencies: - debug: 4.4.1(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - pm2-axon@4.0.1: - dependencies: - amp: 0.3.1 - amp-message: 0.1.2 - debug: 4.4.1(supports-color@5.5.0) - escape-string-regexp: 4.0.0 - transitivePeerDependencies: - - supports-color - - pm2-deploy@1.0.2: - dependencies: - run-series: 1.1.9 - tv4: 1.3.0 - - pm2-multimeter@0.1.2: - dependencies: - charm: 0.1.2 - - pm2-sysmonit@1.2.8: - dependencies: - async: 3.2.6 - debug: 4.4.1(supports-color@5.5.0) - pidusage: 2.0.21 - systeminformation: 5.27.8 - tx2: 1.0.5 - transitivePeerDependencies: - - supports-color - optional: true - - pm2@6.0.8: - dependencies: - '@pm2/agent': 2.1.1 - '@pm2/io': 6.1.0 - '@pm2/js-api': 0.8.0 - '@pm2/pm2-version-check': 1.0.4 - ansis: 4.0.0-node10 - async: 3.2.6 - blessed: 0.1.81 - chokidar: 3.6.0 - cli-tableau: 2.0.1 - commander: 2.15.1 - croner: 4.1.97 - dayjs: 1.11.13 - debug: 4.4.1(supports-color@5.5.0) - enquirer: 2.3.6 - eventemitter2: 5.0.1 - fclone: 1.0.11 - js-yaml: 4.1.0 - mkdirp: 1.0.4 - needle: 2.4.0 - pidusage: 3.0.2 - pm2-axon: 4.0.1 - pm2-axon-rpc: 0.7.1 - pm2-deploy: 1.0.2 - pm2-multimeter: 0.1.2 - promptly: 2.2.0 - semver: 7.7.2 - source-map-support: 0.5.21 - sprintf-js: 1.1.2 - vizion: 2.2.1 - optionalDependencies: - pm2-sysmonit: 1.2.8 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - portfinder@1.0.35: dependencies: async: 3.2.6 @@ -23162,10 +22643,6 @@ snapshots: dependencies: asap: 2.0.6 - promptly@2.2.0: - dependencies: - read: 1.0.7 - prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -23207,21 +22684,6 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-agent@6.4.0: - dependencies: - agent-base: 7.1.3 - debug: 4.4.1(supports-color@5.5.0) - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 7.18.3 - pac-proxy-agent: 7.1.0 - proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - proxy-from-env@1.1.0: {} - pstree.remy@1.1.8: {} pug-attrs@3.0.0: @@ -23362,10 +22824,6 @@ snapshots: dependencies: pify: 2.3.0 - read@1.0.7: - dependencies: - mute-stream: 0.0.8 - readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -23525,14 +22983,6 @@ snapshots: require-from-string@2.0.2: {} - require-in-the-middle@5.2.0: - dependencies: - debug: 4.4.1(supports-color@5.5.0) - module-details-from-path: 1.0.3 - resolve: 1.22.10 - transitivePeerDependencies: - - supports-color - require-main-filename@2.0.0: {} requires-port@1.0.0: {} @@ -23680,8 +23130,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - run-series@1.1.9: {} - rxjs@6.6.7: dependencies: tslib: 1.14.1 @@ -23924,8 +23372,6 @@ snapshots: shell-quote@1.8.3: {} - shimmer@1.2.1: {} - side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -24008,8 +23454,6 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 - smart-buffer@4.2.0: {} - smob@1.5.0: {} snake-case@3.0.4: @@ -24017,19 +23461,6 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 - socks-proxy-agent@8.0.5: - dependencies: - agent-base: 7.1.3 - debug: 4.4.1(supports-color@5.5.0) - socks: 2.8.4 - transitivePeerDependencies: - - supports-color - - socks@2.8.4: - dependencies: - ip-address: 9.0.5 - smart-buffer: 4.2.0 - sonic-boom@4.2.0: dependencies: atomic-sleep: 1.0.0 @@ -24078,8 +23509,6 @@ snapshots: sprintf-js@1.0.3: {} - sprintf-js@1.1.2: {} - sprintf-js@1.1.3: {} ssh2@1.16.0: @@ -24570,8 +23999,6 @@ snapshots: tslib@1.14.1: {} - tslib@1.9.3: {} - tslib@2.4.1: {} tslib@2.6.3: {} @@ -24585,17 +24012,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tv4@1.3.0: {} - tw-animate-css@1.3.7: {} tweetnacl@0.14.5: {} - tx2@1.0.5: - dependencies: - json-stringify-safe: 5.0.1 - optional: true - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -25311,13 +24731,6 @@ snapshots: - tsx - yaml - vizion@2.2.1: - dependencies: - async: 2.6.4 - git-node-fs: 1.0.0(js-git@0.7.8) - ini: 1.3.8 - js-git: 0.7.8 - void-elements@3.1.0: {} vscode-uri@3.1.0: {} @@ -25339,7 +24752,7 @@ snapshots: vue-component-type-helpers@3.0.6: {} - vue-component-type-helpers@3.1.3: {} + vue-component-type-helpers@3.1.4: {} vue-demi@0.14.10(vue@3.5.20(typescript@5.9.2)): dependencies: diff --git a/web/composables/gql/graphql.ts b/web/composables/gql/graphql.ts index a6171b677..833521150 100644 --- a/web/composables/gql/graphql.ts +++ b/web/composables/gql/graphql.ts @@ -1523,8 +1523,8 @@ export type PackageVersions = { openssl?: Maybe; /** PHP version */ php?: Maybe; - /** pm2 version */ - pm2?: Maybe; + /** nodemon version */ + nodemon?: Maybe; }; export type ParityCheck = { diff --git a/web/src/composables/gql/graphql.ts b/web/src/composables/gql/graphql.ts index e683aa0c0..a44fe13e7 100644 --- a/web/src/composables/gql/graphql.ts +++ b/web/src/composables/gql/graphql.ts @@ -559,6 +559,17 @@ export type CpuLoad = { percentUser: Scalars['Float']['output']; }; +export type CpuPackages = Node & { + __typename?: 'CpuPackages'; + id: Scalars['PrefixedID']['output']; + /** Power draw per package (W) */ + power: Array; + /** Temperature per package (°C) */ + temp: Array; + /** Total CPU package power draw (W) */ + totalPower: Scalars['Float']['output']; +}; + export type CpuUtilization = Node & { __typename?: 'CpuUtilization'; /** CPU load for each core */ @@ -869,6 +880,7 @@ export type InfoCpu = Node & { manufacturer?: Maybe; /** CPU model */ model?: Maybe; + packages: CpuPackages; /** Number of physical processors */ processors?: Maybe; /** CPU revision */ @@ -885,6 +897,8 @@ export type InfoCpu = Node & { stepping?: Maybe; /** Number of CPU threads */ threads?: Maybe; + /** Per-package array of core/thread pairs, e.g. [[[0,1],[2,3]], [[4,5],[6,7]]] */ + topology: Array>>; /** CPU vendor */ vendor?: Maybe; /** CPU voltage */ @@ -1531,14 +1545,14 @@ export type PackageVersions = { nginx?: Maybe; /** Node.js version */ node?: Maybe; + /** nodemon version */ + nodemon?: Maybe; /** npm version */ npm?: Maybe; /** OpenSSL version */ openssl?: Maybe; /** PHP version */ php?: Maybe; - /** pm2 version */ - pm2?: Maybe; }; export type ParityCheck = { @@ -2053,6 +2067,7 @@ export type Subscription = { parityHistorySubscription: ParityCheck; serversSubscription: Server; systemMetricsCpu: CpuUtilization; + systemMetricsCpuTelemetry: CpuPackages; systemMetricsMemory: MemoryUtilization; upsUpdates: UpsDevice; }; diff --git a/web/src/composables/gql/index.ts b/web/src/composables/gql/index.ts index c682b1e2f..0ea4a91cf 100644 --- a/web/src/composables/gql/index.ts +++ b/web/src/composables/gql/index.ts @@ -1,2 +1,2 @@ -export * from './fragment-masking'; -export * from './gql'; +export * from "./fragment-masking"; +export * from "./gql";