diff --git a/api/.env.development b/api/.env.development index ff9ec6fc9..b4cb25d3d 100644 --- a/api/.env.development +++ b/api/.env.development @@ -10,6 +10,7 @@ PATHS_MY_SERVERS_FB=./dev/Unraid.net/fb_keepalive # My servers flashbackup timek PATHS_KEYFILE_BASE=./dev/Unraid.net # Keyfile location PATHS_MACHINE_ID=./dev/data/machine-id PATHS_PARITY_CHECKS=./dev/states/parity-checks.log +PATHS_CONFIG_MODULES=./dev/configs ENVIRONMENT="development" NODE_ENV="development" PORT="3001" @@ -21,4 +22,4 @@ BYPASS_PERMISSION_CHECKS=false BYPASS_CORS_CHECKS=true CHOKIDAR_USEPOLLING=true LOG_TRANSPORT=console -LOG_LEVEL=trace \ No newline at end of file +LOG_LEVEL=trace diff --git a/api/.env.production b/api/.env.production index e6c6ac72e..b7083f371 100644 --- a/api/.env.production +++ b/api/.env.production @@ -2,3 +2,4 @@ ENVIRONMENT="production" NODE_ENV="production" PORT="/var/run/unraid-api.sock" MOTHERSHIP_GRAPHQL_LINK="https://mothership.unraid.net/ws" +PATHS_CONFIG_MODULES="/boot/config/plugins/dynamix.my.servers/configs" diff --git a/api/.env.staging b/api/.env.staging index b8e771c5f..cb526b0eb 100644 --- a/api/.env.staging +++ b/api/.env.staging @@ -2,3 +2,4 @@ ENVIRONMENT="staging" NODE_ENV="production" PORT="/var/run/unraid-api.sock" MOTHERSHIP_GRAPHQL_LINK="https://staging.mothership.unraid.net/ws" +PATHS_CONFIG_MODULES="/boot/config/plugins/dynamix.my.servers/configs" diff --git a/api/.env.test b/api/.env.test index 427fadf8f..12b9911b7 100644 --- a/api/.env.test +++ b/api/.env.test @@ -10,5 +10,6 @@ PATHS_MY_SERVERS_FB=./dev/Unraid.net/fb_keepalive # My servers flashbackup timek PATHS_KEYFILE_BASE=./dev/Unraid.net # Keyfile location PATHS_MACHINE_ID=./dev/data/machine-id PATHS_PARITY_CHECKS=./dev/states/parity-checks.log +PATHS_CONFIG_MODULES=./dev/configs PORT=5000 -NODE_ENV="test" \ No newline at end of file +NODE_ENV="test" diff --git a/api/dev/Unraid.net/myservers.cfg b/api/dev/Unraid.net/myservers.cfg index 1c664dad2..42c67a36b 100644 --- a/api/dev/Unraid.net/myservers.cfg +++ b/api/dev/Unraid.net/myservers.cfg @@ -1,5 +1,5 @@ [api] -version="4.4.1" +version="4.6.6" extraOrigins="https://google.com,https://test.com" [local] sandbox="yes" diff --git a/api/dev/configs/connect.json b/api/dev/configs/connect.json new file mode 100644 index 000000000..8157e677b --- /dev/null +++ b/api/dev/configs/connect.json @@ -0,0 +1,23 @@ +{ + "demo": "2025-04-21T14:27:27.631Z", + "wanaccess": "yes", + "wanport": "8443", + "upnpEnabled": "no", + "apikey": "_______________________BIG_API_KEY_HERE_________________________", + "localApiKey": "_______________________LOCAL_API_KEY_HERE_________________________", + "email": "test@example.com", + "username": "zspearmint", + "avatar": "https://via.placeholder.com/200", + "regWizTime": "1611175408732_0951-1653-3509-FBA155FA23C0", + "accesstoken": "", + "idtoken": "", + "refreshtoken": "", + "dynamicRemoteAccessType": "DISABLED", + "ssoSubIds": "", + "version": "4.6.6", + "extraOrigins": [ + "https://google.com", + "https://test.com" + ], + "sandbox": "yes" +} \ No newline at end of file diff --git a/api/generated-schema.graphql b/api/generated-schema.graphql index d2ca20aa3..4b750740a 100644 --- a/api/generated-schema.graphql +++ b/api/generated-schema.graphql @@ -1232,8 +1232,8 @@ type DockerNetwork { type Docker implements Node { id: ID! - containers(useCache: Boolean! = true): [DockerContainer!]! - networks: [DockerNetwork!]! + containers(skipCache: Boolean! = false): [DockerContainer!]! + networks(skipCache: Boolean! = false): [DockerNetwork!]! } type Flash implements Node { @@ -1423,6 +1423,7 @@ type Query { disks: [Disk!]! disk(id: String!): Disk! health: String! + getDemo: String! } type Mutation { @@ -1459,6 +1460,7 @@ type Mutation { setupRemoteAccess(input: SetupRemoteAccessInput!): Boolean! setAdditionalAllowedOrigins(input: AllowedOriginInput!): [String!]! enableDynamicRemoteAccess(input: EnableDynamicRemoteAccessInput!): Boolean! + setDemo: String! } input CreateApiKeyInput { diff --git a/api/justfile b/api/justfile index 6df60e90b..2542ccca3 100644 --- a/api/justfile +++ b/api/justfile @@ -14,3 +14,7 @@ default: alias b := build alias d := deploy + +sync-env server: + rsync -avz --progress --stats -e ssh .env* root@{{server}}:/usr/local/unraid-api + ssh root@{{server}} 'cp /usr/local/unraid-api/.env.staging /usr/local/unraid-api/.env' diff --git a/api/package.json b/api/package.json index 22b5df1fb..cfe1f2fb5 100644 --- a/api/package.json +++ b/api/package.json @@ -177,6 +177,7 @@ "@types/ini": "^4.1.1", "@types/ip": "^1.1.3", "@types/lodash": "^4.17.13", + "@types/lodash-es": "^4.17.12", "@types/mustache": "^4.2.5", "@types/node": "^22.13.4", "@types/pify": "^6.0.0", diff --git a/api/src/__test__/core/utils/files/config-file-normalizer.test.ts b/api/src/__test__/core/utils/files/config-file-normalizer.test.ts index 93f94d614..0975e2107 100644 --- a/api/src/__test__/core/utils/files/config-file-normalizer.test.ts +++ b/api/src/__test__/core/utils/files/config-file-normalizer.test.ts @@ -80,7 +80,6 @@ test('it creates a FLASH config with OPTIONAL values', () => { // 2fa & t2fa should be ignored basicConfig.remote['2Fa'] = 'yes'; basicConfig.local['2Fa'] = 'yes'; - basicConfig.local.showT2Fa = 'yes'; basicConfig.api.extraOrigins = 'myextra.origins'; basicConfig.remote.upnpEnabled = 'yes'; @@ -120,7 +119,6 @@ test('it creates a MEMORY config with OPTIONAL values', () => { // 2fa & t2fa should be ignored basicConfig.remote['2Fa'] = 'yes'; basicConfig.local['2Fa'] = 'yes'; - basicConfig.local.showT2Fa = 'yes'; basicConfig.api.extraOrigins = 'myextra.origins'; basicConfig.remote.upnpEnabled = 'yes'; basicConfig.connectionStatus.upnpStatus = 'Turned On'; diff --git a/api/src/environment.ts b/api/src/environment.ts index 222862011..4faa3aacd 100644 --- a/api/src/environment.ts +++ b/api/src/environment.ts @@ -5,40 +5,49 @@ import { fileURLToPath } from 'node:url'; import type { PackageJson, SetRequired } from 'type-fest'; +import { fileExistsSync } from '@app/core/utils/files/file-exists.js'; + /** - * Tries to get the package.json at the given location. - * @param location - The location of the package.json file, relative to the current file - * @returns The package.json object or undefined if unable to read + * Returns the absolute path to the given file. + * @param location - The location of the file, relative to the current file + * @returns The absolute path to the file */ -function readPackageJson(location: string): PackageJson | undefined { +function getAbsolutePath(location: string): string { try { - let packageJsonPath: string; - try { - const packageJsonUrl = import.meta.resolve(location); - packageJsonPath = fileURLToPath(packageJsonUrl); - } catch { - // Fallback (e.g. for local development): resolve the path relative to this module - packageJsonPath = fileURLToPath(new URL(location, import.meta.url)); - } - const packageJsonRaw = readFileSync(packageJsonPath, 'utf-8'); - return JSON.parse(packageJsonRaw) as PackageJson; + const fileUrl = import.meta.resolve(location); + return fileURLToPath(fileUrl); } catch { - return undefined; + return fileURLToPath(new URL(location, import.meta.url)); } } +/** + * Returns the path to the api's package.json file. Throws if unable to find. + * @param possiblePaths - The possible locations of the package.json file, relative to the current file + * @returns The absolute path to the package.json file + */ +export function getPackageJsonPath(possiblePaths = ['../package.json', '../../package.json']): string { + for (const location of possiblePaths) { + const packageJsonPath = getAbsolutePath(location); + if (fileExistsSync(packageJsonPath)) { + return packageJsonPath; + } + } + throw new Error( + `Could not find package.json in any of the expected locations: ${possiblePaths.join(', ')}` + ); +} /** - * Retrieves the Unraid API package.json. Throws if unable to find. + * Retrieves the Unraid API package.json. Throws if unable to find or parse. * This should be considered a fatal error. * + * @param pathOverride - The path to the package.json file. If not provided, the default path will be found & used. * @returns The package.json object */ -export const getPackageJson = () => { - const packageJson = readPackageJson('../package.json') || readPackageJson('../../package.json'); - if (!packageJson) { - throw new Error('Could not find package.json in any of the expected locations'); - } - return packageJson as SetRequired; +export const getPackageJson = (pathOverride?: string) => { + const packageJsonPath = pathOverride ?? getPackageJsonPath(); + const packageJsonRaw = readFileSync(packageJsonPath, 'utf-8'); + return JSON.parse(packageJsonRaw) as SetRequired; }; /** @@ -86,3 +95,4 @@ export const MOTHERSHIP_GRAPHQL_LINK = process.env.MOTHERSHIP_GRAPHQL_LINK : 'https://mothership.unraid.net/ws'; export const PM2_HOME = process.env.PM2_HOME ?? join(homedir(), '.pm2'); +export const PATHS_CONFIG_MODULES = process.env.PATHS_CONFIG_MODULES!; diff --git a/api/src/index.ts b/api/src/index.ts index 1b8dfd699..2664ce643 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -4,6 +4,7 @@ import '@app/dotenv.js'; import { type NestFastifyApplication } from '@nestjs/platform-fastify'; import { unlinkSync } from 'fs'; +import { mkdir } from 'fs/promises'; import http from 'http'; import https from 'https'; @@ -14,7 +15,7 @@ import { WebSocket } from 'ws'; import { logger } from '@app/core/log.js'; import { fileExistsSync } from '@app/core/utils/files/file-exists.js'; -import { environment, PORT } from '@app/environment.js'; +import { environment, PATHS_CONFIG_MODULES, PORT } from '@app/environment.js'; import * as envVars from '@app/environment.js'; import { setupNewMothershipSubscription } from '@app/mothership/subscribe-to-mothership.js'; import { loadDynamixConfigFile } from '@app/store/actions/load-dynamix-config-file.js'; @@ -44,6 +45,8 @@ export const viteNodeApp = async () => { logger.info('ENV %o', envVars); logger.info('PATHS %o', store.getState().paths); + await mkdir(PATHS_CONFIG_MODULES, { recursive: true }); + const cacheable = new CacheableLookup(); Object.assign(global, { WebSocket }); diff --git a/api/src/unraid-api/app/app.module.ts b/api/src/unraid-api/app/app.module.ts index 99d5f6bde..b57442f43 100644 --- a/api/src/unraid-api/app/app.module.ts +++ b/api/src/unraid-api/app/app.module.ts @@ -1,6 +1,6 @@ import { CacheModule } from '@nestjs/cache-manager'; import { Module } from '@nestjs/common'; -import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; +import { APP_GUARD } from '@nestjs/core'; import { ThrottlerModule } from '@nestjs/throttler'; import { AuthZGuard } from 'nest-authz'; @@ -12,7 +12,6 @@ import { AuthModule } from '@app/unraid-api/auth/auth.module.js'; import { AuthenticationGuard } from '@app/unraid-api/auth/authentication.guard.js'; import { CronModule } from '@app/unraid-api/cron/cron.module.js'; import { GraphModule } from '@app/unraid-api/graph/graph.module.js'; -import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js'; import { RestModule } from '@app/unraid-api/rest/rest.module.js'; import { UnraidFileModifierModule } from '@app/unraid-api/unraid-file-modifier/unraid-file-modifier.module.js'; @@ -49,7 +48,6 @@ import { UnraidFileModifierModule } from '@app/unraid-api/unraid-file-modifier/u }, ]), UnraidFileModifierModule, - PluginModule.register(), ], controllers: [], providers: [ diff --git a/api/src/unraid-api/cli/cli.module.ts b/api/src/unraid-api/cli/cli.module.ts index 4602e733b..c494c59a8 100644 --- a/api/src/unraid-api/cli/cli.module.ts +++ b/api/src/unraid-api/cli/cli.module.ts @@ -9,6 +9,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 { PluginCommandModule } from '@app/unraid-api/cli/plugins/plugin.cli.module.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'; @@ -26,6 +27,9 @@ import { SwitchEnvCommand } from '@app/unraid-api/cli/switch-env.command.js'; import { VersionCommand } from '@app/unraid-api/cli/version.command.js'; import { PluginCliModule } from '@app/unraid-api/plugin/plugin.module.js'; +// cli - plugin add/remove +// plugin generator + const DEFAULT_COMMANDS = [ ApiKeyCommand, ConfigCommand, @@ -57,7 +61,7 @@ const DEFAULT_PROVIDERS = [ ] as const; @Module({ - imports: [PluginCliModule.register()], + imports: [PluginCliModule.register(), PluginCommandModule], providers: [...DEFAULT_COMMANDS, ...DEFAULT_PROVIDERS], }) export class CliModule {} diff --git a/api/src/unraid-api/cli/log.service.ts b/api/src/unraid-api/cli/log.service.ts index 74cd21255..60123a8cd 100644 --- a/api/src/unraid-api/cli/log.service.ts +++ b/api/src/unraid-api/cli/log.service.ts @@ -19,39 +19,39 @@ export class LogService { return shouldLog; } - log(message: string): void { + log(...messages: unknown[]): void { if (this.shouldLog('info')) { - this.logger.log(message); + this.logger.log(...messages); } } - info(message: string): void { + info(...messages: unknown[]): void { if (this.shouldLog('info')) { - this.logger.info(message); + this.logger.info(...messages); } } - warn(message: string): void { + warn(...messages: unknown[]): void { if (this.shouldLog('warn')) { - this.logger.warn(message); + this.logger.warn(...messages); } } - error(message: string, trace: string = ''): void { + error(...messages: unknown[]): void { if (this.shouldLog('error')) { - this.logger.error(message, trace); + this.logger.error(...messages); } } - debug(message: any, ...optionalParams: any[]): void { + debug(...messages: unknown[]): void { if (this.shouldLog('debug')) { - this.logger.debug(message, ...optionalParams); + this.logger.debug(...messages); } } - trace(message: any, ...optionalParams: any[]): void { + trace(...messages: unknown[]): void { if (this.shouldLog('trace')) { - this.logger.log(message, ...optionalParams); + this.logger.log(...messages); } } } diff --git a/api/src/unraid-api/cli/plugins/dependency.service.ts b/api/src/unraid-api/cli/plugins/dependency.service.ts new file mode 100644 index 000000000..b94b60407 --- /dev/null +++ b/api/src/unraid-api/cli/plugins/dependency.service.ts @@ -0,0 +1,131 @@ +import { Injectable } from '@nestjs/common'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +import type { PackageJson } from 'type-fest'; +import { execa } from 'execa'; + +import { fileExists } from '@app/core/utils/files/file-exists.js'; +import { getPackageJson, getPackageJsonPath } from '@app/environment.js'; +import { LogService } from '@app/unraid-api/cli/log.service.js'; + +@Injectable() +export class DependencyService { + constructor(private readonly logger: LogService) {} + + /** + * Writes the package.json file for the api. + * + * @param data - The data to write to the package.json file. + * @throws {Error} from fs.writeFile if the file cannot be written. + */ + private async writePackageJson(data: PackageJson): Promise { + const packageJsonPath = getPackageJsonPath(); + await fs.writeFile(packageJsonPath, JSON.stringify(data, null, 2) + '\n'); + } + + // Basic parser, assumes format 'name' or 'name@version' + private parsePackageArg(packageArg: string): { name: string; version?: string } { + const atIndex = packageArg.lastIndexOf('@'); + // Handles scoped packages @scope/pkg or @scope/pkg@version and simple pkg@version + if (atIndex > 0) { + // Ensure '@' is not the first character + const name = packageArg.substring(0, atIndex); + const version = packageArg.substring(atIndex + 1); + // Basic check if version looks like a version (simplistic) + if (version && !version.includes('/')) { + // Avoid treating part of scope as version + return { name, version }; + } + } + return { name: packageArg }; // No version or scoped package without version + } + + /** + * Adds a peer dependency to the api. If bundled is true, the vendored package will be used. + * Note that this function does not check whether the package is, in fact, bundled. + * + * @param packageArg - The package name and version to add. + * @param bundled - Whether the package is bundled with the api. + * @returns The name, version, and bundled status of the added dependency. + */ + async addPeerDependency( + packageArg: string, + bundled: boolean + ): Promise<{ name: string; version: string; bundled: boolean }> { + const { name } = this.parsePackageArg(packageArg); + if (!name) { + throw new Error('Invalid package name provided.'); + } + const packageJson = getPackageJson(); + packageJson.peerDependencies = packageJson.peerDependencies ?? {}; + let finalVersion = ''; + + if (bundled) { + finalVersion = 'workspace:*'; + packageJson.peerDependencies[name] = finalVersion; + packageJson.peerDependenciesMeta = packageJson.peerDependenciesMeta ?? {}; + packageJson.peerDependenciesMeta[name] = { optional: true }; + await this.writePackageJson(packageJson); + return { name, version: finalVersion, bundled }; + } + + await execa('npm', ['install', '--save-peer', '--save-exact', packageArg], { + cwd: path.dirname(getPackageJsonPath()), + }); + const updatedPackageJson = getPackageJson(); + return { name, version: updatedPackageJson.peerDependencies?.[name] ?? 'unknown', bundled }; + } + + /** + * Removes a peer dependency from the api. + * + * @param packageName - The name of the package to remove. + * @throws {Error} if the package name is invalid. + */ + async removePeerDependency(packageName: string): Promise { + const packageJson = getPackageJson(); + const { name } = this.parsePackageArg(packageName); + if (!name) { + throw new Error('Invalid package name provided.'); + } + + if (packageJson.peerDependencies?.[name]) { + delete packageJson.peerDependencies[name]; + this.logger.log(`Removed peer dependency ${name}`); + } else { + this.logger.warn(`Peer dependency ${name} not found.`); + } + + if (packageJson.peerDependenciesMeta?.[name]) { + delete packageJson.peerDependenciesMeta[name]; + } + + await this.writePackageJson(packageJson); + } + + /** + * Installs dependencies for the api using npm. + * + * @throws {Error} from execa if the npm install command fails. + */ + async npmInstall(): Promise { + const packageJsonPath = getPackageJsonPath(); + await execa('npm', ['install'], { cwd: path.dirname(packageJsonPath) }); + } + + /** + * Rebuilds the vendored dependency archive for the api and stores it on the boot drive. + * If the rc.unraid-api script is not found, no action is taken, but a warning is logged. + * + * @throws {Error} from execa if the rc.unraid-api command fails. + */ + async rebuildVendorArchive(): Promise { + const rcUnraidApi = '/etc/rc.d/rc.unraid-api'; + if (!(await fileExists(rcUnraidApi))) { + this.logger.error('[rebuild-vendor-archive] rc.unraid-api not found; no action taken!'); + return; + } + await execa(rcUnraidApi, ['archive-dependencies']); + } +} diff --git a/api/src/unraid-api/cli/plugins/plugin.cli.module.ts b/api/src/unraid-api/cli/plugins/plugin.cli.module.ts new file mode 100644 index 000000000..d570603a0 --- /dev/null +++ b/api/src/unraid-api/cli/plugins/plugin.cli.module.ts @@ -0,0 +1,28 @@ +import { Module } from '@nestjs/common'; + +import { LogService } from '@app/unraid-api/cli/log.service.js'; +import { DependencyService } from '@app/unraid-api/cli/plugins/dependency.service.js'; +import { + InstallPluginCommand, + ListPluginCommand, + PluginCommand, + RemovePluginCommand, +} from '@app/unraid-api/cli/plugins/plugin.command.js'; +import { PM2Service } from '@app/unraid-api/cli/pm2.service.js'; +import { RestartCommand } from '@app/unraid-api/cli/restart.command.js'; + +const services = [DependencyService, LogService, PM2Service]; +const commands = [ + PluginCommand, + ListPluginCommand, + InstallPluginCommand, + RemovePluginCommand, + RestartCommand, +]; +const moduleResources = [...services, ...commands]; + +@Module({ + providers: moduleResources, + exports: moduleResources, +}) +export class PluginCommandModule {} diff --git a/api/src/unraid-api/cli/plugins/plugin.command.ts b/api/src/unraid-api/cli/plugins/plugin.command.ts new file mode 100644 index 000000000..2d86f95b6 --- /dev/null +++ b/api/src/unraid-api/cli/plugins/plugin.command.ts @@ -0,0 +1,118 @@ +import { Command, CommandRunner, Option, SubCommand } from 'nest-commander'; + +import { LogService } from '@app/unraid-api/cli/log.service.js'; +import { DependencyService } from '@app/unraid-api/cli/plugins/dependency.service.js'; +import { RestartCommand } from '@app/unraid-api/cli/restart.command.js'; +import { PluginService } from '@app/unraid-api/plugin/plugin.service.js'; + +interface InstallPluginCommandOptions { + bundled: boolean; +} + +@SubCommand({ + name: 'install', + aliases: ['i', 'add'], + description: 'Install a plugin as a peer dependency.', + arguments: '', +}) +export class InstallPluginCommand extends CommandRunner { + constructor( + private readonly dependencyService: DependencyService, + private readonly logService: LogService, + private readonly restartCommand: RestartCommand + ) { + super(); + } + + async run(passedParams: string[], options: InstallPluginCommandOptions): Promise { + const [packageName] = passedParams; + if (!packageName) { + this.logService.error('Package name is required.'); + process.exitCode = 1; + return; + } + try { + await this.dependencyService.addPeerDependency(packageName, options.bundled); + this.logService.log(`Added ${packageName} as a peer dependency.`); + if (!options.bundled) { + await this.dependencyService.rebuildVendorArchive(); + } + await this.restartCommand.run(); + } catch (error) { + this.logService.error(error); + process.exitCode = 1; + } + } + + @Option({ + flags: '-b, --bundled', + description: 'Install as a bundled plugin (peer dependency version "workspace:*" and optional)', + defaultValue: false, + }) + parseBundled(): boolean { + return true; + } +} + +@SubCommand({ + name: 'remove', + aliases: ['rm'], + description: 'Remove a plugin peer dependency.', + arguments: '', +}) +export class RemovePluginCommand extends CommandRunner { + constructor( + private readonly pluginService: DependencyService, + private readonly logService: LogService, + private readonly restartCommand: RestartCommand + ) { + super(); + } + + async run(passedParams: string[]): Promise { + const [packageName] = passedParams; + if (!packageName) { + this.logService.error('Package name is required.'); + process.exitCode = 1; + return; + } + try { + await this.pluginService.removePeerDependency(packageName); + await this.restartCommand.run(); + } catch (error) { + this.logService.error(`Failed to remove plugin '${packageName}':`, error); + process.exitCode = 1; + } + } +} + +@SubCommand({ + name: 'list', + description: 'List installed plugins (peer dependencies)', + options: { isDefault: true }, +}) +export class ListPluginCommand extends CommandRunner { + constructor(private readonly logService: LogService) { + super(); + } + + async run(): Promise { + const plugins = await PluginService.listPlugins(); + this.logService.log('Installed plugins:'); + plugins.forEach(([name, version]) => { + this.logService.log(`☑️ ${name}@${version}`); + }); + } +} + +@Command({ + name: 'plugins', + description: 'Manage Unraid API plugins (peer dependencies)', + subCommands: [InstallPluginCommand, RemovePluginCommand, ListPluginCommand], +}) +export class PluginCommand extends CommandRunner { + constructor() { + super(); + } + async run(): Promise {} +} diff --git a/api/src/unraid-api/cli/restart.command.ts b/api/src/unraid-api/cli/restart.command.ts index b9e48d696..6da0c2798 100644 --- a/api/src/unraid-api/cli/restart.command.ts +++ b/api/src/unraid-api/cli/restart.command.ts @@ -13,7 +13,7 @@ export class RestartCommand extends CommandRunner { super(); } - async run(_): Promise { + async run(): Promise { try { this.logger.info('Restarting the Unraid API...'); const { stderr, stdout } = await this.pm2.run( diff --git a/api/src/unraid-api/config/api-state.model.ts b/api/src/unraid-api/config/api-state.model.ts new file mode 100644 index 000000000..b6d15c944 --- /dev/null +++ b/api/src/unraid-api/config/api-state.model.ts @@ -0,0 +1,105 @@ +import { Logger } from '@nestjs/common'; +import { readFile } from 'node:fs/promises'; +import { join } from 'path'; + +import { fileExists } from '@app/core/utils/files/file-exists.js'; +import { PATHS_CONFIG_MODULES } from '@app/environment.js'; +import { makeConfigToken } from '@app/unraid-api/config/config.injection.js'; +import { ConfigPersistenceHelper } from '@app/unraid-api/config/persistence.helper.js'; + +export interface ApiStateConfigOptions { + /** + * The name of the config. + * + * - Must be unique. + * - Should be the key representing this config in the `ConfigFeatures` interface. + * - Used for logging and dependency injection. + */ + name: string; + defaultConfig: T; + parse: (data: unknown) => T; +} + +export class ApiStateConfig { + private config: T; + private logger: Logger; + + constructor( + readonly options: ApiStateConfigOptions, + readonly persistenceHelper: ConfigPersistenceHelper + ) { + // avoid sharing a reference with the given default config. This allows us to re-use it. + this.config = structuredClone(options.defaultConfig); + this.logger = new Logger(this.token); + } + + /** Unique token for this config. Used for Dependency Injection & logging. */ + get token() { + return makeConfigToken(this.options.name); + } + + get fileName() { + return `${this.options.name}.json`; + } + + get filePath() { + return join(PATHS_CONFIG_MODULES, this.fileName); + } + + /** + * Persists the config to the file system. Will never throw. + * @param config - The config to persist. + * @returns True if the config was written successfully, false otherwise. + */ + async persist(config = this.config) { + try { + await this.persistenceHelper.persistIfChanged(this.filePath, config); + return true; + } catch (error) { + this.logger.error(error, `Could not write config to ${this.filePath}.`); + return false; + } + } + + /** + * Reads the config from a path (defaults to the default file path of the config). + * @param opts - The options for the read operation. + * @param opts.filePath - The path to the config file. + * @returns The parsed config or undefined if the file does not exist. + * @throws If the file exists but is invalid. + */ + async parseConfig(opts: { filePath?: string } = {}): Promise { + const { filePath = this.filePath } = opts; + if (!(await fileExists(filePath))) return undefined; + + const rawConfig = JSON.parse(await readFile(filePath, 'utf8')); + return this.options.parse(rawConfig); + } + + /** + * Loads config from the file system. If the file does not exist, it will be created with the default config. + * If the config is invalid or corrupt, no action will be taken. The error will be logged. + * + * Will never throw. + */ + async load() { + try { + const config = await this.parseConfig(); + if (config) { + this.config = config; + } else { + this.logger.log(`Config file does not exist. Writing default config.`); + this.config = this.options.defaultConfig; + await this.persist(); + } + } catch (error) { + this.logger.warn(error, `Config file '${this.filePath}' is invalid. Not modifying config.`); + } + } + + update(config: Partial) { + const proposedConfig = this.options.parse({ ...this.config, ...config }); + this.config = proposedConfig; + return this; + } +} diff --git a/api/src/unraid-api/config/api-state.register.ts b/api/src/unraid-api/config/api-state.register.ts new file mode 100644 index 000000000..d810af0d2 --- /dev/null +++ b/api/src/unraid-api/config/api-state.register.ts @@ -0,0 +1,54 @@ +import type { DynamicModule, Provider } from '@nestjs/common'; +import { SchedulerRegistry } from '@nestjs/schedule'; + +import type { ApiStateConfigOptions } from '@app/unraid-api/config/api-state.model.js'; +import type { ApiStateConfigPersistenceOptions } from '@app/unraid-api/config/api-state.service.js'; +import { ApiStateConfig } from '@app/unraid-api/config/api-state.model.js'; +import { ScheduledConfigPersistence } from '@app/unraid-api/config/api-state.service.js'; +import { makeConfigToken } from '@app/unraid-api/config/config.injection.js'; +import { ConfigPersistenceHelper } from '@app/unraid-api/config/persistence.helper.js'; + +type ApiStateRegisterOptions = ApiStateConfigOptions & { + persistence?: ApiStateConfigPersistenceOptions; +}; + +export class ApiStateConfigModule { + static async register( + options: ApiStateRegisterOptions + ): Promise { + const { persistence, ...configOptions } = options; + const configToken = makeConfigToken(options.name); + const persistenceToken = makeConfigToken(options.name, ScheduledConfigPersistence.name); + const ConfigProvider = { + provide: configToken, + useFactory: async (helper: ConfigPersistenceHelper) => { + const config = new ApiStateConfig(configOptions, helper); + await config.load(); + return config; + }, + inject: [ConfigPersistenceHelper], + }; + + const providers: Provider[] = [ConfigProvider, ConfigPersistenceHelper]; + const exports = [configToken]; + if (persistence) { + providers.push({ + provide: persistenceToken, + useFactory: ( + schedulerRegistry: SchedulerRegistry, + config: ApiStateConfig + ) => { + return new ScheduledConfigPersistence(schedulerRegistry, config, persistence); + }, + inject: [SchedulerRegistry, configToken], + }); + exports.push(persistenceToken); + } + + return { + module: ApiStateConfigModule, + providers, + exports, + }; + } +} diff --git a/api/src/unraid-api/config/api-state.service.ts b/api/src/unraid-api/config/api-state.service.ts new file mode 100644 index 000000000..fe9456a44 --- /dev/null +++ b/api/src/unraid-api/config/api-state.service.ts @@ -0,0 +1,82 @@ +import type { OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; +import { SchedulerRegistry } from '@nestjs/schedule'; + +import type { ApiStateConfig } from '@app/unraid-api/config/api-state.model.js'; +import { makeConfigToken } from '@app/unraid-api/config/config.injection.js'; + +export interface ApiStateConfigPersistenceOptions { + /** How often to persist the config to the file system, in milliseconds. Defaults to 10 seconds. */ + intervalMs?: number; + /** How many consecutive failed persistence attempts to tolerate before stopping. Defaults to 5. */ + maxConsecutiveFailures?: number; + /** By default, the config will be persisted to the file system when the module is initialized and destroyed. + * Set this to true to disable this behavior. + */ + disableLifecycleHooks?: boolean; +} + +export class ScheduledConfigPersistence implements OnModuleInit, OnModuleDestroy { + private consecutiveFailures = 0; + private logger: Logger; + + constructor( + private readonly schedulerRegistry: SchedulerRegistry, + private readonly config: ApiStateConfig, + private readonly options: ApiStateConfigPersistenceOptions + ) { + this.logger = new Logger(this.token); + } + + get token() { + return makeConfigToken(this.configName, ScheduledConfigPersistence.name); + } + + get configName() { + return this.config.options.name; + } + + onModuleInit() { + if (this.options.disableLifecycleHooks) return; + this.setup(); + } + + async onModuleDestroy() { + if (this.options.disableLifecycleHooks) return; + this.stop(); + await this.config.persist(); + } + + stop() { + if (this.schedulerRegistry.getInterval(this.token)) { + this.schedulerRegistry.deleteInterval(this.token); + } + } + + setup() { + const interval = this.schedulerRegistry.getInterval(this.token); + if (interval) { + this.logger.warn(`Persistence interval for '${this.token}' already exists. Aborting setup.`); + return; + } + const ONE_MINUTE = 60_000; + const { intervalMs = ONE_MINUTE, maxConsecutiveFailures = 3 } = this.options; + + const callback = async () => { + const success = await this.config.persist(); + if (success) { + this.consecutiveFailures = 0; + return; + } + this.consecutiveFailures++; + if (this.consecutiveFailures > maxConsecutiveFailures) { + this.logger.warn( + `Failed to persist '${this.configName}' too many times in a row (${this.consecutiveFailures} attempts). Disabling persistence.` + ); + this.schedulerRegistry.deleteInterval(this.token); + } + }; + + this.schedulerRegistry.addInterval(this.token, setInterval(callback, intervalMs)); + } +} diff --git a/api/src/unraid-api/config/config.injection.ts b/api/src/unraid-api/config/config.injection.ts new file mode 100644 index 000000000..a79ad6028 --- /dev/null +++ b/api/src/unraid-api/config/config.injection.ts @@ -0,0 +1,22 @@ +import { Inject } from '@nestjs/common'; + +import type { ConfigFeatures } from '@app/unraid-api/config/config.interface.js'; + +/** + * Creates a string token representation of the arguements. Pure function. + * + * @param configName - The name of the config. + * @returns A colon-separated string + */ +export function makeConfigToken(configName: string, ...details: string[]) { + return ['ApiConfig', configName, ...details].join('.'); +} + +/** + * Custom decorator to inject a config by name. + * @param feature - The name of the config to inject. + * @returns Dependency injector for the config. + */ +export function InjectConfig(feature: K) { + return Inject(makeConfigToken(feature)); +} diff --git a/api/src/unraid-api/config/config.interface.ts b/api/src/unraid-api/config/config.interface.ts new file mode 100644 index 000000000..5ac1558cb --- /dev/null +++ b/api/src/unraid-api/config/config.interface.ts @@ -0,0 +1,15 @@ +/** + * Container record of config names to their types. Used for type completion on registered configs. + * Config authors should redeclare/merge this interface with their config names as the keys + * and implementation models as the types. + */ +export interface ConfigFeatures {} + +export interface ConfigMetadata { + /** Unique token for this config. Used for Dependency Injection, logging, etc. */ + token: string; + /** The path to the config file. */ + filePath?: string; + /** Validates a config of type `T`. */ + validate: (config: unknown) => Promise; +} diff --git a/api/src/unraid-api/config/persistence.helper.ts b/api/src/unraid-api/config/persistence.helper.ts new file mode 100644 index 000000000..3ea507a57 --- /dev/null +++ b/api/src/unraid-api/config/persistence.helper.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { readFile, writeFile } from 'fs/promises'; + +import { isEqual } from 'lodash-es'; + +@Injectable() +export class ConfigPersistenceHelper { + /** + * Persist the config to disk if the given data is different from the data on-disk. + * This helps preserve the boot flash drive's life by avoiding unnecessary writes. + * + * @param filePath - The path to the config file. + * @param data - The data to persist. + * @returns `true` if the config was persisted, `false` otherwise. + * + * @throws {Error} if the config file does not exist or is unreadable. + * @throws {Error} if the config file is not valid JSON. + * @throws {Error} if given data is not JSON (de)serializable. + * @throws {Error} if the config file is not writable. + */ + async persistIfChanged(filePath: string, data: unknown): Promise { + const currentData = JSON.parse(await readFile(filePath, 'utf8')); + const stagedData = JSON.parse(JSON.stringify(data)); + if (isEqual(currentData, stagedData)) { + return false; + } + await writeFile(filePath, JSON.stringify(stagedData, null, 2)); + return true; + } +} diff --git a/api/src/unraid-api/graph/graph.module.ts b/api/src/unraid-api/graph/graph.module.ts index 31e96cf22..cf47210aa 100644 --- a/api/src/unraid-api/graph/graph.module.ts +++ b/api/src/unraid-api/graph/graph.module.ts @@ -16,13 +16,14 @@ import { import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin.js'; import { ResolversModule } from '@app/unraid-api/graph/resolvers/resolvers.module.js'; import { sandboxPlugin } from '@app/unraid-api/graph/sandbox-plugin.js'; +import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js'; @Module({ imports: [ ResolversModule, GraphQLModule.forRootAsync({ driver: ApolloDriver, - imports: [], + imports: [PluginModule.register()], inject: [], useFactory: async () => { return { diff --git a/api/src/unraid-api/graph/resolvers/docker/docker.service.ts b/api/src/unraid-api/graph/resolvers/docker/docker.service.ts index d333e82d7..f67eae5cd 100644 --- a/api/src/unraid-api/graph/resolvers/docker/docker.service.ts +++ b/api/src/unraid-api/graph/resolvers/docker/docker.service.ts @@ -58,12 +58,17 @@ export class DockerService implements OnModuleInit { } public async onModuleInit() { - this.logger.debug('Warming Docker cache on startup...'); - await this.getContainers({ skipCache: true }); - await this.getNetworks({ skipCache: true }); - this.logger.debug('Docker cache warming complete.'); - const appInfo = await this.getAppInfo(); - await pubsub.publish(PUBSUB_CHANNEL.INFO, appInfo); + try { + this.logger.debug('Warming Docker cache on startup...'); + await this.getContainers({ skipCache: true }); + await this.getNetworks({ skipCache: true }); + this.logger.debug('Docker cache warming complete.'); + const appInfo = await this.getAppInfo(); + await pubsub.publish(PUBSUB_CHANNEL.INFO, appInfo); + } catch (error) { + this.logger.warn('Error initializing Docker module:', error); + this.logger.warn('Docker may be disabled under Settings -> Docker.'); + } } /** diff --git a/api/src/unraid-api/plugin/plugin.interface.ts b/api/src/unraid-api/plugin/plugin.interface.ts index 7c9d1a7f4..38b89f699 100644 --- a/api/src/unraid-api/plugin/plugin.interface.ts +++ b/api/src/unraid-api/plugin/plugin.interface.ts @@ -42,7 +42,6 @@ export const apiNestPluginSchema = z message: 'Invalid NestJS module: expected a class constructor', }) .optional(), - graphqlSchemaExtension: asyncString().optional(), }) .superRefine((data, ctx) => { // Ensure that at least one of ApiModule or CliModule is defined. @@ -53,14 +52,6 @@ export const apiNestPluginSchema = z path: ['ApiModule', 'CliModule'], }); } - // If graphqlSchemaExtension is provided, ensure that ApiModule is defined. - if (data.graphqlSchemaExtension && !data.ApiModule) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'If graphqlSchemaExtension is provided, ApiModule must be defined', - path: ['graphqlSchemaExtension'], - }); - } }); export type ApiNestPluginDefinition = z.infer; diff --git a/api/src/unraid-api/plugin/plugin.module.ts b/api/src/unraid-api/plugin/plugin.module.ts index 238dd0aae..1a0b5616d 100644 --- a/api/src/unraid-api/plugin/plugin.module.ts +++ b/api/src/unraid-api/plugin/plugin.module.ts @@ -5,7 +5,6 @@ import { PluginService } from '@app/unraid-api/plugin/plugin.service.js'; @Module({}) export class PluginModule { private static readonly logger = new Logger(PluginModule.name); - constructor(private readonly pluginService: PluginService) {} static async register(): Promise { const plugins = await PluginService.getPlugins(); diff --git a/api/src/unraid-api/plugin/plugin.service.ts b/api/src/unraid-api/plugin/plugin.service.ts index 2180581f5..c030c3ce4 100644 --- a/api/src/unraid-api/plugin/plugin.service.ts +++ b/api/src/unraid-api/plugin/plugin.service.ts @@ -1,8 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; -import type { SetRequired } from 'type-fest'; -import { parse } from 'graphql'; - import type { ApiNestPluginDefinition } from '@app/unraid-api/plugin/plugin.interface.js'; import { getPackageJson } from '@app/environment.js'; import { apiNestPluginSchema } from '@app/unraid-api/plugin/plugin.interface.js'; @@ -18,29 +15,6 @@ export class PluginService { return PluginService.plugins; } - static async getGraphQLSchemas() { - const plugins = (await PluginService.getPlugins()).filter( - (plugin): plugin is SetRequired => - plugin.graphqlSchemaExtension !== undefined - ); - const { data: schemas } = await batchProcess(plugins, async (plugin) => { - try { - const schema = await plugin.graphqlSchemaExtension(); - // Validate schema by parsing it - this will throw if invalid - parse(schema); - return schema; - } catch (error) { - // we can safely assert ApiModule's presence since we validate the plugin schema upon importing it. - // ApiModule must be defined when graphqlSchemaExtension is defined. - PluginService.logger.error( - `Error parsing GraphQL schema from ${plugin.ApiModule!.name}: ${JSON.stringify(error, null, 2)}` - ); - throw error; - } - }); - return schemas; - } - private static async importPlugins() { if (PluginService.plugins) { return PluginService.plugins; @@ -77,7 +51,7 @@ export class PluginService { return plugins.data; } - private static async listPlugins(): Promise<[string, string][]> { + static async listPlugins(): Promise<[string, string][]> { /** All api plugins must be npm packages whose name starts with this prefix */ const pluginPrefix = 'unraid-api-plugin-'; // All api plugins must be installed as dependencies of the unraid-api package diff --git a/api/vite.config.ts b/api/vite.config.ts index bba3f4766..6042a87ad 100644 --- a/api/vite.config.ts +++ b/api/vite.config.ts @@ -166,11 +166,7 @@ export default defineConfig(({ mode }): ViteUserConfig => { include: ['src/**/*'], reporter: ['text', 'json', 'html'], }, - setupFiles: [ - 'dotenv/config', - 'reflect-metadata', - 'src/__test__/setup.ts', - ], + setupFiles: ['dotenv/config', 'reflect-metadata', 'src/__test__/setup.ts'], exclude: ['**/deploy/**', '**/node_modules/**'], }, }; diff --git a/packages/unraid-api-plugin-connect/index.ts b/packages/unraid-api-plugin-connect/index.ts deleted file mode 100644 index 9275dd09c..000000000 --- a/packages/unraid-api-plugin-connect/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Module, Logger, Inject } from "@nestjs/common"; -import { ConfigModule, ConfigService, registerAs } from "@nestjs/config"; -import { Resolver, Query } from "@nestjs/graphql"; - -export const adapter = 'nestjs'; - -export const graphqlSchemaExtension = async () => ` - type Query { - health: String - } -`; - -@Resolver() -export class HealthResolver { - @Query(() => String) - health() { - // You can replace the return value with your actual health check logic - return 'I am healthy!'; - } -} - -const config = registerAs("connect", () => ({ - demo: true, -})); - -@Module({ - imports: [ConfigModule.forFeature(config)], - providers: [HealthResolver], -}) -class ConnectPluginModule { - logger = new Logger(ConnectPluginModule.name); - private readonly configService: ConfigService; - - /** - * @param {ConfigService} configService - */ - constructor(@Inject(ConfigService) configService: ConfigService) { - this.configService = configService; - } - - onModuleInit() { - this.logger.log("Connect plugin initialized"); - console.log("Connect plugin initialized", this.configService.get('connect')); - } -} - -export const ApiModule = ConnectPluginModule; diff --git a/packages/unraid-api-plugin-connect/package.json b/packages/unraid-api-plugin-connect/package.json index e04ee53ac..db8eb784d 100644 --- a/packages/unraid-api-plugin-connect/package.json +++ b/packages/unraid-api-plugin-connect/package.json @@ -20,7 +20,16 @@ "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.11", "@nestjs/graphql": "^13.0.3", + "@types/ini": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/node": "^22.14.0", + "camelcase-keys": "^9.1.3", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "ini": "^5.0.0", + "lodash-es": "^4.17.21", "nest-authz": "^2.14.0", + "rxjs": "^7.8.2", "typescript": "^5.8.2" }, "peerDependencies": { @@ -28,6 +37,12 @@ "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.11", "@nestjs/graphql": "^13.0.3", - "nest-authz": "^2.14.0" + "camelcase-keys": "^9.1.3", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "ini": "^5.0.0", + "lodash-es": "^4.17.21", + "nest-authz": "^2.14.0", + "rxjs": "^7.8.2" } } diff --git a/packages/unraid-api-plugin-connect/src/config.demo.ts b/packages/unraid-api-plugin-connect/src/config.demo.ts new file mode 100644 index 000000000..fc47e41db --- /dev/null +++ b/packages/unraid-api-plugin-connect/src/config.demo.ts @@ -0,0 +1,6 @@ +import { Field } from "@nestjs/graphql"; + +export class ConnectConfig { + @Field(() => String) + demo!: string; +} diff --git a/packages/unraid-api-plugin-connect/src/config.entity.ts b/packages/unraid-api-plugin-connect/src/config.entity.ts new file mode 100644 index 000000000..58015d65c --- /dev/null +++ b/packages/unraid-api-plugin-connect/src/config.entity.ts @@ -0,0 +1,109 @@ +import { registerAs } from "@nestjs/config"; +import { Field, ObjectType, InputType } from "@nestjs/graphql"; +import { + IsString, + IsEnum, + IsOptional, + IsEmail, + Matches, + IsBoolean, + IsNumber, + IsArray, +} from "class-validator"; +import { ConnectConfig } from "./config.demo.js"; +import { UsePipes, ValidationPipe } from "@nestjs/common"; + +export enum MinigraphStatus { + ONLINE = "online", + OFFLINE = "offline", + UNKNOWN = "unknown", +} + +export enum DynamicRemoteAccessType { + NONE = "none", + UPNP = "upnp", + MANUAL = "manual", +} + +@ObjectType() +@UsePipes(new ValidationPipe({ transform: true })) +@InputType("MyServersConfigInput") +export class MyServersConfig { + // Remote Access Configurationx + @Field(() => String) + @IsString() + wanaccess!: string; + + @Field(() => Number) + @IsNumber() + wanport!: number; + + @Field(() => Boolean) + @IsBoolean() + upnpEnabled!: boolean; + + @Field(() => String) + @IsString() + apikey!: string; + + @Field(() => String) + @IsString() + localApiKey!: string; + + // User Information + @Field(() => String) + @IsEmail() + email!: string; + + @Field(() => String) + @IsString() + username!: string; + + @Field(() => String) + @IsString() + avatar!: string; + + @Field(() => String) + @IsString() + regWizTime!: string; + + // Authentication Tokens + @Field(() => String) + @IsString() + accesstoken!: string; + + @Field(() => String) + @IsString() + idtoken!: string; + + @Field(() => String) + @IsString() + refreshtoken!: string; + + // Remote Access Settings + @Field(() => DynamicRemoteAccessType) + @IsEnum(DynamicRemoteAccessType) + dynamicRemoteAccessType!: DynamicRemoteAccessType; + + @Field(() => [String]) + @IsArray() + @Matches(/^[a-zA-Z0-9-]+$/, { + each: true, + message: "Each SSO ID must be alphanumeric with dashes", + }) + ssoSubIds!: string[]; + + // Connection Status + // @Field(() => MinigraphStatus) + // @IsEnum(MinigraphStatus) + // minigraph!: MinigraphStatus; + + @Field(() => String, { nullable: true }) + @IsString() + @IsOptional() + upnpStatus?: string | null; +} + +export const configFeature = registerAs("connect", () => ({ + demo: "hello.unraider", +})); diff --git a/packages/unraid-api-plugin-connect/src/config.persistence.ts b/packages/unraid-api-plugin-connect/src/config.persistence.ts new file mode 100644 index 000000000..519fe9278 --- /dev/null +++ b/packages/unraid-api-plugin-connect/src/config.persistence.ts @@ -0,0 +1,185 @@ +import { + Logger, + Injectable, + OnModuleInit, + OnModuleDestroy, +} from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { existsSync, readFileSync } from "fs"; +import { writeFile } from "fs/promises"; +import path from "path"; +import { debounceTime } from "rxjs/operators"; +import type { MyServersConfig as LegacyConfig } from "./helpers/my-servers-config.js"; +import { MyServersConfig } from "./config.entity.js"; +import { plainToInstance } from "class-transformer"; +import { csvStringToArray } from "./helpers/utils.js"; +import { parse as parseIni } from 'ini'; +import { isEqual } from "lodash-es"; +import { validateOrReject } from "class-validator"; + +@Injectable() +export class ConnectConfigPersister implements OnModuleInit, OnModuleDestroy { + constructor(private readonly configService: ConfigService) {} + + private logger = new Logger(ConnectConfigPersister.name); + get configPath() { + // PATHS_CONFIG_MODULES is a required environment variable. + // It is the directory where custom config files are stored. + return path.join( + this.configService.get("PATHS_CONFIG_MODULES")!, + "connect.json" + ); + } + + async onModuleDestroy() { + await this.persist(); + } + + async onModuleInit() { + this.logger.debug(`Config path: ${this.configPath}`); + await this.loadOrMigrateConfig(); + // Persist changes to the config. + const HALF_SECOND = 500; + this.configService.changes$.pipe(debounceTime(HALF_SECOND)).subscribe({ + next: async ({ newValue, oldValue, path }) => { + if (path.startsWith("connect.")) { + this.logger.debug( + `Config changed: ${path} from ${oldValue} to ${newValue}` + ); + await this.persist(); + } + }, + error: (err) => { + this.logger.error("Error receiving config changes:", err); + }, + }); + } + + /** + * Persist the config to disk if the given data is different from the data on-disk. + * This helps preserve the boot flash drive's life by avoiding unnecessary writes. + * + * @param config - The config object to persist. + * @returns `true` if the config was persisted, `false` otherwise. + */ + async persist(config = this.configService.get("connect")) { + try { + if (isEqual(config, await this.loadConfig())) { + this.logger.verbose(`Config is unchanged, skipping persistence`); + return false; + } + } catch (error) { + this.logger.error(`Error loading config (will overwrite file):`, error); + } + const data = JSON.stringify(config, null, 2); + this.logger.verbose(`Persisting config to ${this.configPath}: ${data}`); + try { + await writeFile(this.configPath, data); + this.logger.verbose(`Config persisted to ${this.configPath}`); + return true; + } catch (error) { + this.logger.error( + `Error persisting config to '${this.configPath}':`, + error + ); + return false; + } + } + + /** + * Validate the config object. + * @param config - The config object to validate. + * @returns The validated config instance. + */ + private async validate(config: object) { + let instance: MyServersConfig; + if (config instanceof MyServersConfig) { + instance = config; + } else { + instance = plainToInstance(MyServersConfig, config, { enableImplicitConversion: true }); + } + await validateOrReject(instance); + return instance; + } + + /** + * Load the config from the filesystem, or migrate the legacy config file to the new config format. + * When unable to load or migrate the config, messages are logged at WARN level, but no other action is taken. + * @returns true if the config was loaded successfully, false otherwise. + */ + private async loadOrMigrateConfig() { + try { + const config = await this.loadConfig(); + this.configService.set("connect", config); + this.logger.verbose(`Config loaded from ${this.configPath}`); + return true; + } catch (error) { + this.logger.warn("Error loading config:", error); + } + + try { + await this.migrateLegacyConfig(); + return this.persist(); + } catch (error) { + this.logger.warn("Error migrating legacy config:", error); + } + + this.logger.error( + "Failed to load or migrate config from filesystem. Config is not persisted. Using defaults in-memory." + ); + return false; + } + + /** + * Load the JSON config from the filesystem + * @throws {Error} - If the config file does not exist. + * @throws {Error} - If the config file is not parse-able. + * @throws {Error} - If the config file is not valid. + */ + private async loadConfig(configFilePath = this.configPath) { + if (!existsSync(configFilePath)) throw new Error(`Config file does not exist at '${configFilePath}'`); + return this.validate(JSON.parse(readFileSync(configFilePath, "utf8"))); + } + + /** + * Migrate the legacy config file to the new config format. + * Loads into memory, but does not persist. + * + * @throws {Error} - If the legacy config file does not exist. + * @throws {Error} - If the legacy config file is not parse-able. + */ + private async migrateLegacyConfig() { + const legacyConfig = await this.parseLegacyConfig(); + this.configService.set("connect", { + demo: new Date().toISOString(), + ...legacyConfig, + }); + } + + /** + * Parse the legacy config file and return a new config object. + * @param filePath - The path to the legacy config file. + * @returns A new config object. + * @throws {Error} - If the legacy config file does not exist. + * @throws {Error} - If the legacy config file is not parse-able. + */ + private async parseLegacyConfig(filePath?: string): Promise { + filePath ??= this.configService.get( + "PATHS_MY_SERVERS_CONFIG", + "/boot/config/plugins/dynamix.my.servers/myservers.cfg" + ); + if (!filePath) { + throw new Error("No legacy config file path provided"); + } + if (!existsSync(filePath)) { + throw new Error(`Legacy config file does not exist: ${filePath}`); + } + const config = parseIni(readFileSync(filePath, "utf8")) as LegacyConfig; + return this.validate({ + ...config.api, + ...config.local, + ...config.remote, + extraOrigins: csvStringToArray(config.api.extraOrigins), + }); + } +} diff --git a/packages/unraid-api-plugin-connect/src/connect.resolver.ts b/packages/unraid-api-plugin-connect/src/connect.resolver.ts new file mode 100644 index 000000000..48444a2d7 --- /dev/null +++ b/packages/unraid-api-plugin-connect/src/connect.resolver.ts @@ -0,0 +1,25 @@ +import { ConfigService } from "@nestjs/config"; +import { Resolver, Query, Mutation } from "@nestjs/graphql"; + +@Resolver() +export class HealthResolver { + constructor(private readonly configService: ConfigService) {} + + @Query(() => String) + health() { + // You can replace the return value with your actual health check logic + return "I am healthy!"; + } + + @Query(() => String) + getDemo() { + return this.configService.get("connect.demo"); + } + + @Mutation(() => String) + async setDemo() { + const newValue = new Date().toISOString(); + this.configService.set("connect.demo", newValue); + return newValue; + } +} diff --git a/packages/unraid-api-plugin-connect/src/helpers/my-servers-config.ts b/packages/unraid-api-plugin-connect/src/helpers/my-servers-config.ts new file mode 100644 index 000000000..919ac4202 --- /dev/null +++ b/packages/unraid-api-plugin-connect/src/helpers/my-servers-config.ts @@ -0,0 +1,61 @@ +// Schema for the legacy myservers.cfg configuration file. + +enum MinigraphStatus { + PRE_INIT = "PRE_INIT", + CONNECTING = "CONNECTING", + CONNECTED = "CONNECTED", + PING_FAILURE = "PING_FAILURE", + ERROR_RETRYING = "ERROR_RETRYING", +} + +enum DynamicRemoteAccessType { + STATIC = "STATIC", + UPNP = "UPNP", + DISABLED = "DISABLED", +} + +// TODO Currently registered in the main api, but this will eventually be the source of truth. +// +// registerEnumType(MinigraphStatus, { +// name: "MinigraphStatus", +// description: "The status of the minigraph", +// }); +// +// registerEnumType(DynamicRemoteAccessType, { +// name: "DynamicRemoteAccessType", +// description: "The type of dynamic remote access", +// }); + +export type MyServersConfig = { + api: { + version: string; + extraOrigins: string; + }; + local: { + sandbox: "yes" | "no"; + }; + remote: { + wanaccess: string; + wanport: string; + upnpEnabled: string; + apikey: string; + localApiKey: string; + email: string; + username: string; + avatar: string; + regWizTime: string; + accesstoken: string; + idtoken: string; + refreshtoken: string; + dynamicRemoteAccessType: DynamicRemoteAccessType; + ssoSubIds: string; + }; +}; + +/** In-Memory representation of the legacy myservers.cfg configuration file */ +export type MyServersConfigMemory = MyServersConfig & { + connectionStatus: { + minigraph: MinigraphStatus; + upnpStatus?: string | null; + }; +}; diff --git a/packages/unraid-api-plugin-connect/src/helpers/utils.ts b/packages/unraid-api-plugin-connect/src/helpers/utils.ts new file mode 100644 index 000000000..119c66983 --- /dev/null +++ b/packages/unraid-api-plugin-connect/src/helpers/utils.ts @@ -0,0 +1,43 @@ +import { accessSync } from 'fs'; +import { access } from 'fs/promises'; +import { F_OK } from 'node:constants'; + +export const fileExists = async (path: string) => + access(path, F_OK) + .then(() => true) + .catch(() => false); +export const fileExistsSync = (path: string) => { + try { + accessSync(path, F_OK); + return true; + } catch (error: unknown) { + return false; + } +}; + +/** + * Converts a Comma Separated (CSV) string to an array of strings. + * + * @example + * csvStringToArray('one,two,three') // ['one', 'two', 'three'] + * csvStringToArray('one, two, three') // ['one', 'two', 'three'] + * csvStringToArray(null) // [] + * csvStringToArray(undefined) // [] + * csvStringToArray('') // [] + * + * @param csvString - The Comma Separated string to convert + * @param opts - Options + * @param opts.noEmpty - Whether to omit empty strings. Default is true. + * @returns An array of strings + */ +export function csvStringToArray( + csvString?: string | null, + opts: { noEmpty?: boolean } = { noEmpty: true } +): string[] { + if (!csvString) return []; + const result = csvString.split(',').map((item) => item.trim()); + if (opts.noEmpty) { + return result.filter((item) => item !== ''); + } + return result; +} diff --git a/packages/unraid-api-plugin-connect/src/index.ts b/packages/unraid-api-plugin-connect/src/index.ts new file mode 100644 index 000000000..bcba1a382 --- /dev/null +++ b/packages/unraid-api-plugin-connect/src/index.ts @@ -0,0 +1,28 @@ +import { Module, Logger, Inject } from "@nestjs/common"; +import { ConfigModule, ConfigService } from "@nestjs/config"; +import { ConnectConfigPersister } from "./config.persistence.js"; +import { configFeature } from "./config.entity.js"; +import { HealthResolver } from "./connect.resolver.js"; + +export const adapter = "nestjs"; + +@Module({ + imports: [ConfigModule.forFeature(configFeature)], + providers: [HealthResolver, ConnectConfigPersister], +}) +class ConnectPluginModule { + logger = new Logger(ConnectPluginModule.name); + + constructor( + @Inject(ConfigService) private readonly configService: ConfigService + ) {} + + onModuleInit() { + this.logger.log( + "Connect plugin initialized with %o", + this.configService.get("connect") + ); + } +} + +export const ApiModule = ConnectPluginModule; diff --git a/packages/unraid-api-plugin-connect/tsconfig.json b/packages/unraid-api-plugin-connect/tsconfig.json index cbf07ab83..1c9beed57 100644 --- a/packages/unraid-api-plugin-connect/tsconfig.json +++ b/packages/unraid-api-plugin-connect/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "target": "ESNext", - "module": "ESNext", - "moduleResolution": "bundler", + "module": "NodeNext", + "moduleResolution": "nodenext", "experimentalDecorators": true, "emitDecoratorMetadata": true, "declaration": true, @@ -13,6 +13,6 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, - "include": ["index.ts"], + "include": ["src/**/*.ts"], "exclude": ["node_modules", "dist"] } diff --git a/packages/unraid-api-plugin-generator/package.json b/packages/unraid-api-plugin-generator/package.json new file mode 100644 index 000000000..7866ec497 --- /dev/null +++ b/packages/unraid-api-plugin-generator/package.json @@ -0,0 +1,34 @@ +{ + "name": "@unraid/create-api-plugin", + "version": "1.0.0", + "type": "module", + "bin": { + "create-api-plugin": "./dist/index.js" + }, + "scripts": { + "build": "tsc -p tsconfig.build.json", + "prepare": "npm run build" + }, + "dependencies": { + "chalk": "^5.4.1", + "change-case": "^5.4.4", + "commander": "^13.1.0", + "create-create-app": "^7.3.0", + "fs-extra": "^11.3.0", + "inquirer": "^12.5.2", + "validate-npm-package-name": "^6.0.0" + }, + "devDependencies": { + "@nestjs/common": "^11.0.11", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.11", + "@nestjs/graphql": "^13.0.3", + "@types/fs-extra": "^11.0.4", + "@types/inquirer": "^9.0.7", + "@types/node": "^22.14.1", + "@types/validate-npm-package-name": "^4.0.2", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "typescript": "^5.8.3" + } +} diff --git a/packages/unraid-api-plugin-generator/src/create-plugin.ts b/packages/unraid-api-plugin-generator/src/create-plugin.ts new file mode 100644 index 000000000..ad76d3d03 --- /dev/null +++ b/packages/unraid-api-plugin-generator/src/create-plugin.ts @@ -0,0 +1,128 @@ +import fs from 'fs-extra'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { pascalCase, kebabCase } from 'change-case'; +import validateNpmPackageName from 'validate-npm-package-name'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export const isValidName = (name: string) => { + const { validForNewPackages } = validateNpmPackageName(name); + return validForNewPackages; +}; + +export async function createPlugin(pluginName: string, targetDir: string = process.cwd()) { + if (!isValidName(pluginName)) { + throw new Error("Invalid plugin name"); + } + + const pascalName = pascalCase(pluginName); + const kebabName = kebabCase(pluginName); + const packageName = `unraid-api-plugin-${kebabName}`; + const pluginDir = path.join(targetDir, packageName); + + // Check if directory already exists + if (await fs.pathExists(pluginDir)) { + throw new Error(`Directory ${pluginDir} already exists`); + } + + // Create directory structure + await fs.ensureDir(path.join(pluginDir, 'src')); + + // Create package.json + const packageJson = { + name: packageName, + version: "1.0.0", + "unraidVersion": { + "min": "^6.12.15", + "max": "~7.1.0" + }, + main: "dist/index.js", + type: "module", + files: ["dist"], + scripts: { + test: "echo \"Error: no test specified\" && exit 1", + build: "tsc", + prepare: "npm run build" + }, + keywords: [], + license: "GPL-2.0-or-later", + description: `Plugin for Unraid API: ${pascalName}`, + devDependencies: { + "@nestjs/common": "^11.0.11", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.11", + "@nestjs/graphql": "^13.0.3", + "@types/ini": "^4.1.1", + "@types/node": "^22.14.0", + "camelcase-keys": "^9.1.3", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "ini": "^5.0.0", + "nest-authz": "^2.14.0", + "rxjs": "^7.8.2", + "typescript": "^5.8.2", + "zod": "^3.23.8" + }, + peerDependencies: { + "@nestjs/common": "^11.0.11", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.11", + "@nestjs/graphql": "^13.0.3", + "camelcase-keys": "^9.1.3", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "ini": "^5.0.0", + "nest-authz": "^2.14.0", + "rxjs": "^7.8.2", + "zod": "^3.23.8" + } + }; + + await fs.writeJson(path.join(pluginDir, 'package.json'), packageJson, { spaces: 2 }); + + // Create tsconfig.json + const tsconfig = { + compilerOptions: { + target: "ES2022", + module: "NodeNext", + moduleResolution: "NodeNext", + sourceMap: true, + forceConsistentCasingInFileNames: true, + experimentalDecorators: true, + emitDecoratorMetadata: true, + esModuleInterop: true, + strict: true, + outDir: "dist", + rootDir: "src" + }, + include: ["src/**/*"], + exclude: ["node_modules", "dist"] + }; + + await fs.writeJson(path.join(pluginDir, 'tsconfig.json'), tsconfig, { spaces: 2 }); + + // Read template files and replace variables + const templatesDir = path.join(__dirname, '../src/templates'); + + const replaceNames = (template: string) => { + return template + .replace(/PluginName/g, pascalName) + .replace(/plugin-name/g, kebabName); + }; + + // Process all template files + const templateFiles = await fs.readdir(templatesDir); + + for (const templateFile of templateFiles) { + // Read template content + const templateContent = await fs.readFile(path.join(templatesDir, templateFile), 'utf8'); + const processedContent = replaceNames(templateContent); + const outputFileName = replaceNames(templateFile); + + // Write to target directory + await fs.writeFile(path.join(pluginDir, 'src', outputFileName), processedContent); + } + + return pluginDir; +} diff --git a/packages/unraid-api-plugin-generator/src/index.ts b/packages/unraid-api-plugin-generator/src/index.ts new file mode 100644 index 000000000..4f9c3c551 --- /dev/null +++ b/packages/unraid-api-plugin-generator/src/index.ts @@ -0,0 +1,95 @@ +#!/usr/bin/env node +import { Command } from "commander"; +import { createPlugin, isValidName } from "./create-plugin.js"; +import chalk from "chalk"; +import { exec } from "child_process"; +import { promisify } from "util"; + +const execAsync = promisify(exec); + +const program = new Command(); + +async function getPluginName(name: string | undefined) { + if (name && isValidName(name)) return name; + const { pluginName } = await import("inquirer").then( + ({ default: inquirer }) => + inquirer.prompt([ + { + type: "input", + name: "pluginName", + message: "What would you like to name your plugin?", + validate: (input: string) => { + if (!input) return "Plugin name is required"; + if (!isValidName(input)) + return "Plugin name should only contain lowercase letters, numbers, and hyphens, and may not start with a hyphen"; + return true; + }, + }, + ]) + ); + return pluginName; +} + +program + .name("create-api-plugin") + .description("Create a new Unraid API plugin") + .argument("[name]", "Name of the plugin (e.g., my-plugin)") + .option( + "-d, --dir ", + "Directory to create the plugin in", + process.cwd() + ) + .option( + "-p, --package-manager ", + "Package manager to use (npm, yarn, pnpm)", + "npm" + ) + .option( + "-i, --install", + "Install dependencies after creating the plugin", + false + ) + .action( + async ( + name: string | undefined, + options: { dir: string; packageManager: string; install: boolean } + ) => { + try { + const pluginName = await getPluginName(name); + const pluginDir = await createPlugin(pluginName, options.dir); + console.log(chalk.green(`Successfully created plugin: ${pluginName}`)); + + if (options.install) { + console.log( + chalk.blue( + `\nInstalling dependencies using ${options.packageManager}...` + ) + ); + try { + await execAsync(`${options.packageManager} install`, { + cwd: pluginDir, + }); + console.log(chalk.green("Dependencies installed successfully!")); + } catch (error) { + console.error(chalk.red("Error installing dependencies:"), error); + process.exit(1); + } + } + const nextSteps = [`cd ${pluginDir}`]; + if (!options.install) { + nextSteps.push(`${options.packageManager} install`); + } + nextSteps.push(`Start developing your plugin!`); + + console.log(chalk.blue("\nNext steps:")); + nextSteps.forEach((step, index) => { + console.log(chalk.blue(`${index + 1}. ${step}`)); + }); + } catch (error) { + console.error(chalk.red("Error creating plugin:"), error); + process.exit(1); + } + } + ); + +program.parse(); diff --git a/packages/unraid-api-plugin-generator/src/templates/config.entity.ts b/packages/unraid-api-plugin-generator/src/templates/config.entity.ts new file mode 100644 index 000000000..698854664 --- /dev/null +++ b/packages/unraid-api-plugin-generator/src/templates/config.entity.ts @@ -0,0 +1,20 @@ +import { registerAs } from "@nestjs/config"; +import { Field, ObjectType } from "@nestjs/graphql"; +import { Exclude, Expose } from "class-transformer"; +import { IsBoolean } from "class-validator"; + +@Exclude() // Exclude properties by default +@ObjectType() +export class PluginNameConfig { + @Expose() // Expose this property for transformation + @Field(() => Boolean, { description: "Whether the plugin is enabled" }) + @IsBoolean() + enabled!: boolean; +} + +// This function provides the default config and registers it under the 'plugin-name' key. +export const configFeature = registerAs("plugin-name", () => { + return { + enabled: true, + }; +}); diff --git a/packages/unraid-api-plugin-generator/src/templates/config.persistence.ts b/packages/unraid-api-plugin-generator/src/templates/config.persistence.ts new file mode 100644 index 000000000..6d57d1360 --- /dev/null +++ b/packages/unraid-api-plugin-generator/src/templates/config.persistence.ts @@ -0,0 +1,71 @@ +import { Logger, Injectable, OnModuleInit } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { existsSync, readFileSync } from "fs"; +import { writeFile } from "fs/promises"; +import path from "path"; +import { debounceTime } from "rxjs/operators"; +import { PluginNameConfig } from "./config.entity.js"; + +@Injectable() +export class PluginNameConfigPersister implements OnModuleInit { + constructor(private readonly configService: ConfigService) {} + + private logger = new Logger(PluginNameConfigPersister.name); + + /** the file path to the config file for this plugin */ + get configPath() { + return path.join( + this.configService.get("PATHS_CONFIG_MODULES")!, + "plugin-name.json" // Use kebab-case for the filename + ); + } + + onModuleInit() { + this.logger.debug(`Config path: ${this.configPath}`); + // Load the config from the file if it exists, otherwise initialize it with defaults. + if (existsSync(this.configPath)) { + try { + const configFromFile = JSON.parse( + readFileSync(this.configPath, "utf8") + ); + this.configService.set("plugin-name", configFromFile); + this.logger.verbose(`Config loaded from ${this.configPath}`); + } catch (error) { + this.logger.error(`Error reading or parsing config file at ${this.configPath}. Using defaults.`, error); + // If loading fails, ensure default config is set and persisted + this.persist(); + } + } else { + this.logger.log(`Config file ${this.configPath} does not exist. Writing default config...`); + // Persist the default configuration provided by configFeature + this.persist(); + } + + // Automatically persist changes to the config file after a short delay. + const HALF_SECOND = 500; + this.configService.changes$.pipe(debounceTime(HALF_SECOND)).subscribe({ + next: ({ newValue, oldValue, path: changedPath }) => { + // Only persist if the change is within this plugin's config namespace + if (changedPath.startsWith("plugin-name.") && newValue !== oldValue) { + this.logger.debug(`Config changed: ${changedPath} from ${oldValue} to ${newValue}`); + // Persist the entire config object for this plugin + this.persist(); + } + }, + error: (err) => { + this.logger.error("Error subscribing to config changes:", err); + }, + }); + } + + async persist(config = this.configService.get("plugin-name")) { + const data = JSON.stringify(config, null, 2); + this.logger.verbose(`Persisting config to ${this.configPath}: ${data}`); + try { + await writeFile(this.configPath, data); + this.logger.verbose(`Config change persisted to ${this.configPath}`); + } catch (error) { + this.logger.error(`Error persisting config to '${this.configPath}':`, error); + } + } +} diff --git a/packages/unraid-api-plugin-generator/src/templates/index.ts b/packages/unraid-api-plugin-generator/src/templates/index.ts new file mode 100644 index 000000000..c353f1e27 --- /dev/null +++ b/packages/unraid-api-plugin-generator/src/templates/index.ts @@ -0,0 +1,28 @@ +import { Module, Logger, Inject } from "@nestjs/common"; +import { ConfigModule, ConfigService } from "@nestjs/config"; +import { PluginNameConfigPersister } from "./config.persistence.js"; +import { configFeature } from "./config.entity.js"; +import { PluginNameResolver } from "./plugin-name.resolver.js"; + +export const adapter = "nestjs"; + +@Module({ + imports: [ConfigModule.forFeature(configFeature)], + providers: [PluginNameResolver, PluginNameConfigPersister], +}) +class PluginNamePluginModule { + logger = new Logger(PluginNamePluginModule.name); + + constructor( + @Inject(ConfigService) private readonly configService: ConfigService + ) {} + + onModuleInit() { + this.logger.log( + "PluginName plugin initialized with %o", + this.configService.get("plugin-name") + ); + } +} + +export const ApiModule = PluginNamePluginModule; diff --git a/packages/unraid-api-plugin-generator/src/templates/plugin-name.resolver.ts b/packages/unraid-api-plugin-generator/src/templates/plugin-name.resolver.ts new file mode 100644 index 000000000..d83833bc9 --- /dev/null +++ b/packages/unraid-api-plugin-generator/src/templates/plugin-name.resolver.ts @@ -0,0 +1,23 @@ +import { Resolver, Query, Mutation } from "@nestjs/graphql"; +import { ConfigService } from "@nestjs/config"; + +@Resolver() +export class PluginNameResolver { + constructor(private readonly configService: ConfigService) {} + + @Query(() => String) + async PluginNameStatus() { + // Example query: Fetch a value from the config + return this.configService.get("plugin-name.enabled", true) ? "Enabled" : "Disabled"; + } + + @Mutation(() => Boolean) + async togglePluginNameStatus() { + // Example mutation: Update a value in the config + const currentStatus = this.configService.get("plugin-name.enabled", true); + const newStatus = !currentStatus; + this.configService.set("plugin-name.enabled", newStatus); + // The config persister will automatically save the changes. + return newStatus; + } +} diff --git a/packages/unraid-api-plugin-generator/tsconfig.build.json b/packages/unraid-api-plugin-generator/tsconfig.build.json new file mode 100644 index 000000000..8f3bbf360 --- /dev/null +++ b/packages/unraid-api-plugin-generator/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "nodenext", + "esModuleInterop": true, + "strict": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "src/templates/**/*"] +} diff --git a/packages/unraid-api-plugin-generator/tsconfig.json b/packages/unraid-api-plugin-generator/tsconfig.json new file mode 100644 index 000000000..96565250f --- /dev/null +++ b/packages/unraid-api-plugin-generator/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "nodenext", + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "strict": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6602d4d6b..9e8205154 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,7 +229,7 @@ importers: version: 2.15.0(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2) nest-commander: specifier: ^3.15.0 - version: 3.17.0(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(@types/inquirer@8.2.10)(typescript@5.8.2) + version: 3.17.0(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(@types/inquirer@9.0.7)(typescript@5.8.2) nestjs-pino: specifier: ^4.1.0 version: 4.4.0(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(pino-http@10.4.0)(pino@9.6.0)(rxjs@7.8.2) @@ -375,6 +375,9 @@ importers: '@types/lodash': specifier: ^4.17.13 version: 4.17.16 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 '@types/mustache': specifier: ^4.2.5 version: 4.2.5 @@ -492,13 +495,98 @@ importers: '@nestjs/graphql': specifier: ^13.0.3 version: 13.0.4(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.10.0)(reflect-metadata@0.1.14)(ts-morph@24.0.0) + '@types/ini': + specifier: ^4.1.1 + version: 4.1.1 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/node': + specifier: ^22.14.0 + version: 22.14.0 + camelcase-keys: + specifier: ^9.1.3 + version: 9.1.3 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.1 + version: 0.14.1 + ini: + specifier: ^5.0.0 + version: 5.0.0 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 nest-authz: specifier: ^2.14.0 version: 2.15.0(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2) + rxjs: + specifier: ^7.8.2 + version: 7.8.2 typescript: specifier: ^5.8.2 version: 5.8.2 + packages/unraid-api-plugin-generator: + dependencies: + chalk: + specifier: ^5.4.1 + version: 5.4.1 + change-case: + specifier: ^5.4.4 + version: 5.4.4 + commander: + specifier: ^13.1.0 + version: 13.1.0 + create-create-app: + specifier: ^7.3.0 + version: 7.3.0 + fs-extra: + specifier: ^11.3.0 + version: 11.3.0 + inquirer: + specifier: ^12.5.2 + version: 12.5.2(@types/node@22.14.1) + validate-npm-package-name: + specifier: ^6.0.0 + version: 6.0.0 + devDependencies: + '@nestjs/common': + specifier: ^11.0.11 + version: 11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^4.0.2 + version: 4.0.2(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(rxjs@7.8.2) + '@nestjs/core': + specifier: ^11.0.11 + version: 11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/graphql': + specifier: ^13.0.3 + version: 13.0.4(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.10.0)(reflect-metadata@0.1.14)(ts-morph@24.0.0) + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 + '@types/inquirer': + specifier: ^9.0.7 + version: 9.0.7 + '@types/node': + specifier: ^22.14.1 + version: 22.14.1 + '@types/validate-npm-package-name': + specifier: ^4.0.2 + version: 4.0.2 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.1 + version: 0.14.1 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + packages/unraid-api-plugin-health: devDependencies: '@nestjs/common': @@ -558,7 +646,7 @@ importers: version: 3.1.9 vitest: specifier: ^3.0.7 - version: 3.0.9(@types/node@22.14.0)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jiti@2.4.2)(jsdom@26.0.0)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + version: 3.0.9(@types/node@22.14.1)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jiti@2.4.2)(jsdom@26.0.0)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) unraid-ui: dependencies: @@ -2410,6 +2498,127 @@ packages: '@vue/compiler-sfc': optional: true + '@inquirer/checkbox@4.1.5': + resolution: {integrity: sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.9': + resolution: {integrity: sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.1.10': + resolution: {integrity: sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.10': + resolution: {integrity: sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.12': + resolution: {integrity: sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.11': + resolution: {integrity: sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==} + engines: {node: '>=18'} + + '@inquirer/input@4.1.9': + resolution: {integrity: sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.12': + resolution: {integrity: sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.12': + resolution: {integrity: sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.4.1': + resolution: {integrity: sha512-UlmM5FVOZF0gpoe1PT/jN4vk8JmpIWBlMvTL8M+hlvPmzN89K6z03+IFmyeu/oFCenwdwHDr2gky7nIGSEVvlA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.0.12': + resolution: {integrity: sha512-wNPJZy8Oc7RyGISPxp9/MpTOqX8lr0r+lCCWm7hQra+MDtYRgINv1hxw7R+vKP71Bu/3LszabxOodfV/uTfsaA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.0.12': + resolution: {integrity: sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.1.1': + resolution: {integrity: sha512-IUXzzTKVdiVNMA+2yUvPxWsSgOG4kfX93jOM4Zb5FgujeInotv5SPIJVeXQ+fO4xu7tW8VowFhdG5JRmmCyQ1Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.6': + resolution: {integrity: sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@internationalized/date@3.7.0': resolution: {integrity: sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==} @@ -3750,6 +3959,9 @@ packages: '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/graphql-fields@1.3.9': resolution: {integrity: sha512-HynTnp1HrE58uYcFcAK5UOfdrHSOIHDLCjvMU4yCmQLMj21uo7ZiZqnDGrD27pgCgHH5a1e8GYNK98Ndmma7ig==} @@ -3768,8 +3980,8 @@ packages: '@types/ini@4.1.1': resolution: {integrity: sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==} - '@types/inquirer@8.2.10': - resolution: {integrity: sha512-IdD5NmHyVjWM8SHWo/kPBgtzXatwPkfwzyP3fN1jF2g9BWt5WO+8hL2F4o2GKIYsU40PpqeevuUWvkS/roXJkA==} + '@types/inquirer@9.0.7': + resolution: {integrity: sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==} '@types/ip@1.1.3': resolution: {integrity: sha512-64waoJgkXFTYnCYDUWgSATJ/dXEBanVkaP5d4Sbk7P6U7cTTMhxVyROTckc6JKdwCrgnAjZMn0k3177aQxtDEA==} @@ -3786,6 +3998,12 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + '@types/lodash@4.17.16': resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==} @@ -3810,8 +4028,8 @@ packages: '@types/node-fetch@2.6.12': resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} - '@types/node@18.19.83': - resolution: {integrity: sha512-D69JeR5SfFS5H6FLbUaS0vE4r1dGhmMBbG4Ed6BNS4wkDK8GZjsdCShT5LCN59vOHEUHnFCY9J4aclXlIphMkA==} + '@types/node@18.19.86': + resolution: {integrity: sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==} '@types/node@22.13.13': resolution: {integrity: sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==} @@ -3819,6 +4037,9 @@ packages: '@types/node@22.14.0': resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} + '@types/node@22.14.1': + resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3884,6 +4105,9 @@ packages: '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + '@types/validate-npm-package-name@4.0.2': + resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} + '@types/validator@13.12.3': resolution: {integrity: sha512-2ipwZ2NydGQJImne+FhNdhgRM37e9lCev99KnqkbFHd94Xn/mErARWI1RSLem1QA19ch5kOhzIZd7e8CA2FI8g==} @@ -3896,6 +4120,15 @@ packages: '@types/wtfnode@0.7.3': resolution: {integrity: sha512-UMkHpx+o2xRWLJ7PmT3bBzvIA9/0oFw80oPtY/xO4jfdq+Gznn4wP7K9B/JjMxyxy+wF+5oRPIykxeBbEDjwRg==} + '@types/yargs-interactive@2.1.6': + resolution: {integrity: sha512-Gh6BwFnz2YEmojzg/hkiGzSZDJ0TlU4v5Ea+sBP9pQVRfeSC+lCXmqB4Zk0KNwM01Cx54IZ1vssAJoRs+p8k+A==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + '@types/zen-observable@0.8.3': resolution: {integrity: sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==} @@ -4506,6 +4739,10 @@ packages: resolution: {integrity: sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==} engines: {node: ^18.17.0 || >=20.5.0} + abind@1.0.5: + resolution: {integrity: sha512-dbaEZphdPje0ihqSdWg36Sb8S20TuqQomiz2593oIx+enQ9Q4vDZRjIzhnkWltGRKVKqC28kTribkgRLBexWVQ==} + engines: {node: '>=6', npm: '>=3'} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -4604,6 +4841,14 @@ packages: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} + ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + + ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -4660,6 +4905,14 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + argx@3.0.2: + resolution: {integrity: sha512-PUyi1r14HG1AH6raqPEW8+vKNWfvHrmerdBXnf5iz7JOnO1hRaG1cGsH9eay/y8dUIreN7NxSEfK208UCGd0wQ==} + engines: {node: '>=4', npm: '>=2'} + + argx@4.0.4: + resolution: {integrity: sha512-XLWeRTNBJRzQkbMweLIxdtnvpE7iYUBraPwrIJX57FjL4D1RHLMJRM1AyEP6KZHgvjW7TSnxF8MpGic7YdTGOA==} + engines: {node: '>=8', npm: '>=5'} + aria-hidden@1.2.4: resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} engines: {node: '>=10'} @@ -4708,6 +4961,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + arrayreduce@2.1.0: + resolution: {integrity: sha512-I5MwrsPJ4faMuuPXM8+EgEy83G16i+FqegFhhHX3geDJbyaqPDWNrVjkrRg9SZq5mepEZdNg36SDPOhiKPWTLA==} + engines: {node: '>=4.0.0'} + arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} @@ -4715,6 +4972,10 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + askconfig@4.0.4: + resolution: {integrity: sha512-fjB/vmAlUKxGVqcz4mLub3xF8m9rkazhqcXRvrDzeey0iaLhcAg2K8bhJL7pKjE2dFP9qDGv3+yXovYMV9XBJQ==} + engines: {node: '>=8', npm: '>=5'} + asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -4763,6 +5024,9 @@ packages: async-sema@3.1.1: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + async@1.5.2: + resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} + async@2.6.4: resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} @@ -4826,6 +5090,9 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + babel-runtime@6.26.0: + resolution: {integrity: sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==} + babel-walk@3.0.0-canary-5: resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} engines: {node: '>= 10.0.0'} @@ -5151,6 +5418,9 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + cli-color@1.4.0: + resolution: {integrity: sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -5183,10 +5453,17 @@ packages: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + clipboardy@4.0.0: resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==} engines: {node: '>=18'} + cliui@5.0.0: + resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==} + cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} @@ -5527,6 +5804,10 @@ packages: core-js-compat@3.41.0: resolution: {integrity: sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==} + core-js@2.6.12: + resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==} + deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. + core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -5580,6 +5861,10 @@ packages: resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} engines: {node: '>= 14'} + create-create-app@7.3.0: + resolution: {integrity: sha512-4BzSuq75JihB3hvFQHu1mqPDLUBcR3u5N9yShG6LSv84O7NjTNc/hE+cgDlSJZzfgr7LRahtI5FwPxLyVi/oPg==} + hasBin: true + cron@3.5.0: resolution: {integrity: sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==} @@ -5687,6 +5972,10 @@ packages: resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} engines: {node: '>= 10'} + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + dargs@7.0.0: resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} engines: {node: '>=8'} @@ -6071,6 +6360,9 @@ packages: emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + emoji-regex@7.0.3: + resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -6111,6 +6403,14 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + envinfo@7.14.0: + resolution: {integrity: sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==} + engines: {node: '>=4'} + hasBin: true + + epicfail@3.0.0: + resolution: {integrity: sha512-zf7vvWZ2tI2+P1674dmcyPWopD/0FC2BrAi0DvDY0uKGmrB66rwpRVlOYKFlGwRO4Q6bpkoCTPhjqvi5hMOavQ==} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -6157,9 +6457,23 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + + es6-weak-map@2.0.3: + resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + esbuild-android-64@0.14.54: resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} engines: {node: '>=12'} @@ -6487,6 +6801,10 @@ packages: esm-resolve@1.0.11: resolution: {integrity: sha512-LxF0wfUQm3ldUDHkkV2MIbvvY0TgzIpJ420jHSV1Dm+IlplBEWiJTKWM61GtxUfvjV6iD4OtTYFGAGM2uuIUWg==} + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + espree@10.3.0: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6530,6 +6848,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -6550,6 +6871,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -6558,6 +6883,10 @@ packages: resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} engines: {node: ^18.19.0 || >=20.5.0} + execcli@5.0.6: + resolution: {integrity: sha512-du+uy/Ew2P90PKjSHI89u/XuqVaBDzvaJ6ePn40JaOy7owFQNsYDbd5AoR5A559HEAb1i5HO22rJxtgVonf5Bg==} + engines: {node: '>=8', npm: '>=4'} + exit-hook@4.0.0: resolution: {integrity: sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==} engines: {node: '>=18'} @@ -6577,6 +6906,9 @@ packages: exsolve@1.0.4: resolution: {integrity: sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==} + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -6763,6 +7095,10 @@ packages: resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} engines: {node: '>=18'} + findout@3.0.2: + resolution: {integrity: sha512-eatRX+s8jm8ml/S9Y5NBBjR4W8i7IeEmyddB3Lidak/nPZNfDxGzLEIaMKgeNj5/LHA1i0dC4Gwsb13H1bx+AA==} + engines: {node: '>=7.6', npm: '>=4'} + findup-sync@4.0.0: resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==} engines: {node: '>= 8'} @@ -6909,6 +7245,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -6980,6 +7320,10 @@ packages: git-url-parse@16.0.1: resolution: {integrity: sha512-mcD36GrhAzX5JVOsIO52qNpgRyFzYWRbU1VSRFCvJt1IJvqfvH427wWw/CFqkWvjVPtdG5VTx4MKUeC5GeFPDQ==} + gitconfig@2.0.8: + resolution: {integrity: sha512-qOB1QswIHFNKAOPN0pEu7U1iyajLBv3Tz5X630UlkAtKM904I4dO7XIjH84wmR2SUVAgaVR99UC9U4ABJujAJQ==} + engines: {node: '>=6', npm: '>=3'} + gitconfiglocal@1.0.0: resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} @@ -7219,6 +7563,10 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hasbin@1.2.3: + resolution: {integrity: sha512-CCd8e/w2w28G8DyZvKgiHnQJ/5XXDz6qiUHnthvtag/6T5acUeN5lqq+HMoBqcmgWueWDhiCplrw0Kb1zDACRg==} + engines: {node: '>=0.10'} + hash-sum@2.0.0: resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} @@ -7350,6 +7698,10 @@ packages: httpxy@0.1.7: resolution: {integrity: sha512-pXNx8gnANKAndgga5ahefxc++tJvNL87CXoRwxn1cJE2ZkWEojF3tNfQIEhZX/vfpt+wzeAzpUI4qkediX1MLQ==} + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -7369,6 +7721,14 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + iftype@3.0.2: + resolution: {integrity: sha512-vA/NSyCG3E7XXWC1hmbEDj8WvsduSzLblmj4m2Idywx8YC6CKqGTYzrnoxbMrC+qBcHz85P7uwBwYEY2rX1jvQ==} + engines: {node: '>=4', npm: '>=2'} + + iftype@4.0.9: + resolution: {integrity: sha512-01Klo+04dkDzY193D1GVfOdQzmpqaYFJTAlZKRztkT/BOaU7sSnvxGimSln+7DMqLUP4tpDTNFgxqVPLYZVypA==} + engines: {node: '>=8', npm: '>=5'} + ignore-by-default@1.0.1: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} @@ -7452,6 +7812,19 @@ packages: resolution: {integrity: sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==} engines: {node: ^18.17.0 || >=20.5.0} + inquirer@12.5.2: + resolution: {integrity: sha512-qoDk/vdSTIaXNXAoNnlg7ubexpJfUo7t8GT2vylxvE49BrLhToFuPPdMViidG2boHV7+AcP1TCkJs/+PPoF2QQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + inquirer@7.3.3: + resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} + engines: {node: '>=8.0.0'} + inquirer@8.2.5: resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==} engines: {node: '>=12.0.0'} @@ -7561,6 +7934,10 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -8027,6 +8404,10 @@ packages: libphonenumber-js@1.12.6: resolution: {integrity: sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==} + license.js@3.1.2: + resolution: {integrity: sha512-anbqciJ9HfQVMRicsegiZOJ6nrP93ly24alImDOO7KndNLs3Um861fSEpXpWqGPMOv7PfZTJZL1p4cPq+Au4BQ==} + engines: {node: '>=8.0.0'} + light-my-request@6.3.0: resolution: {integrity: sha512-bWTAPJmeWQH5suJNYwG0f5cs0p6ho9e6f1Ppoxv5qMosY+s9Ir2+ZLvvHcgA7VTDop4zl/NCHhOVVqU+kd++Ow==} @@ -8218,6 +8599,9 @@ packages: resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} engines: {node: '>=16.14'} + lru-queue@0.1.0: + resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} + lucide-vue-next@0.488.0: resolution: {integrity: sha512-Za02FzR1r+XN2oxOQerH7HvZwLJz27qPfzbauYzphtewy1g59Bm2DraVS5Sb9iHcIe3Pqf8cTVufvpwqAKlsew==} peerDependencies: @@ -8295,6 +8679,10 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + memoizee@0.4.17: + resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==} + engines: {node: '>=0.12'} + memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} @@ -8509,6 +8897,10 @@ packages: mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -8576,6 +8968,9 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + nitropack@2.11.7: resolution: {integrity: sha512-ghqLa3Q4X9qaQiUyspWxxoU1fY2nwfSJqhOH+COqyCp7Vgj4oM1EM1L0YNSQUF16T2tAoOWg8woXGq0EH5Y6wQ==} engines: {node: ^16.11.0 || >=17.0.0} @@ -8686,6 +9081,10 @@ packages: notation@1.3.6: resolution: {integrity: sha512-DIuJmrP/Gg1DcXKaApsqcjsJD6jEccqKSfmU3BUx/f1GHsMiTJh70cERwYc64tOmTRTARCeMwkqNNzjh3AHhiw==} + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8768,6 +9167,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + objnest@5.1.1: + resolution: {integrity: sha512-C4fjNlHhUQbHiiFpgzvZse3/WUHq356Da3P8NZazg9JpPHFiQP2Y9lmYvpLU06midap0YpNz/MuA8GGSL8G0YQ==} + engines: {node: '>=8', npm: '>=5'} + obliterator@2.0.5: resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} @@ -9168,6 +9571,10 @@ packages: pkg-types@2.1.0: resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} + pkg-up@3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -9793,6 +10200,9 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} + regenerator-runtime@0.11.1: + resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -9994,12 +10404,20 @@ packages: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + 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'} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -10391,6 +10809,10 @@ packages: string-env-interpolation@1.0.1: resolution: {integrity: sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==} + string-width@3.1.0: + resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==} + engines: {node: '>=6'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -10421,6 +10843,10 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringcase@4.3.1: + resolution: {integrity: sha512-Ov7McNX1sFaEX9NWijD1hIOVDDhKdnFzN9tvoa1N8xgrclouhsO4kBPVrTPhjO/zP5mn1Ww03uZ2SThNMXS7zg==} + engines: {node: '>=8', npm: '>=5'} + stringify-object@5.0.0: resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} engines: {node: '>=14.16'} @@ -10429,6 +10855,10 @@ packages: resolution: {integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==} deprecated: This module is not used anymore, and has been replaced by @npmcli/package-json + strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -10445,6 +10875,10 @@ packages: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -10676,6 +11110,10 @@ packages: resolution: {integrity: sha512-YBGpG4bWsHoPvofT6y/5iqulfXIiIErl5B0LdtHT1mGXDFTAhhRrbUpTvBgYbovr+3cKblya2WAOcpoy90XguA==} engines: {node: '>=16'} + timers-ext@0.1.8: + resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==} + engines: {node: '>=0.12'} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -10886,6 +11324,9 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -10927,6 +11368,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + ua-parser-js@1.0.40: resolution: {integrity: sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==} hasBin: true @@ -11173,6 +11619,10 @@ packages: deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -11184,6 +11634,10 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + validate-npm-package-name@6.0.0: + resolution: {integrity: sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg==} + engines: {node: ^18.17.0 || >=20.5.0} + validator@13.12.0: resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} engines: {node: '>= 0.10'} @@ -11677,6 +12131,10 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@5.1.0: + resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==} + engines: {node: '>=6'} + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -11773,6 +12231,13 @@ packages: engines: {node: '>= 14'} hasBin: true + yargs-interactive@3.0.1: + resolution: {integrity: sha512-Jnp88uiuz+ZRpM10Lwvs0nRetWPog+6lcgQrhwKsyEanAe3wgTlaPPzcYlZWp53aOMTzOcR5wEpEsFOMOPmLlw==} + engines: {node: '>=8', npm: '>=6'} + + yargs-parser@15.0.3: + resolution: {integrity: sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA==} + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -11785,6 +12250,9 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs@14.2.3: + resolution: {integrity: sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==} + yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} @@ -11809,6 +12277,10 @@ packages: resolution: {integrity: sha512-KHBC7z61OJeaMGnF3wqNZj+GGNXOyypZviiKpQeiHirG5Ib1ImwcLBH70rbMSkKfSmUNBsdf2PwaEJtKvgmkNw==} engines: {node: '>=12.20'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + yoctocolors@2.1.1: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} @@ -13611,6 +14083,122 @@ snapshots: transitivePeerDependencies: - supports-color + '@inquirer/checkbox@4.1.5(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@22.14.1) + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/confirm@5.1.9(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/core@10.1.10(@types/node@22.14.1)': + dependencies: + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@22.14.1) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/editor@4.2.10(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + external-editor: 3.1.0 + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/expand@4.0.12(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/figures@1.0.11': {} + + '@inquirer/input@4.1.9(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/number@3.0.12(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/password@4.0.12(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + ansi-escapes: 4.3.2 + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/prompts@7.4.1(@types/node@22.14.1)': + dependencies: + '@inquirer/checkbox': 4.1.5(@types/node@22.14.1) + '@inquirer/confirm': 5.1.9(@types/node@22.14.1) + '@inquirer/editor': 4.2.10(@types/node@22.14.1) + '@inquirer/expand': 4.0.12(@types/node@22.14.1) + '@inquirer/input': 4.1.9(@types/node@22.14.1) + '@inquirer/number': 3.0.12(@types/node@22.14.1) + '@inquirer/password': 4.0.12(@types/node@22.14.1) + '@inquirer/rawlist': 4.0.12(@types/node@22.14.1) + '@inquirer/search': 3.0.12(@types/node@22.14.1) + '@inquirer/select': 4.1.1(@types/node@22.14.1) + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/rawlist@4.0.12(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/search@3.0.12(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@22.14.1) + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/select@4.1.1(@types/node@22.14.1)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@22.14.1) + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.14.1 + + '@inquirer/type@3.0.6(@types/node@22.14.1)': + optionalDependencies: + '@types/node': 22.14.1 + '@internationalized/date@3.7.0': dependencies: '@swc/helpers': 0.5.15 @@ -15032,9 +15620,9 @@ snapshots: find-package-json: 1.2.0 magic-string: 0.30.17 storybook: 8.6.9(prettier@3.5.3) - typescript: 5.8.2 + typescript: 5.8.3 vite: 6.2.3(@types/node@22.13.13)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vue-component-meta: 2.2.8(typescript@5.8.2) + vue-component-meta: 2.2.8(typescript@5.8.3) vue-docgen-api: 4.79.2(vue@3.5.13(typescript@5.8.2)) transitivePeerDependencies: - vue @@ -15217,7 +15805,7 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.14.0 + '@types/node': 22.14.1 '@types/bytes@3.1.5': {} @@ -15227,11 +15815,11 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 22.14.0 + '@types/node': 22.14.1 '@types/conventional-commits-parser@5.0.1': dependencies: - '@types/node': 22.14.0 + '@types/node': 22.14.1 optional: true '@types/cors@2.8.17': @@ -15244,7 +15832,7 @@ snapshots: '@types/docker-modem@3.0.6': dependencies: - '@types/node': 22.13.13 + '@types/node': 22.14.1 '@types/ssh2': 1.15.4 '@types/dockerode@3.3.35': @@ -15273,7 +15861,7 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 22.13.13 + '@types/node': 22.14.1 '@types/qs': 6.9.18 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -15285,6 +15873,11 @@ snapshots: '@types/qs': 6.9.18 '@types/serve-static': 1.15.7 + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 22.14.1 + '@types/graphql-fields@1.3.9': dependencies: graphql: 16.10.0 @@ -15301,7 +15894,7 @@ snapshots: '@types/ini@4.1.1': {} - '@types/inquirer@8.2.10': + '@types/inquirer@9.0.7': dependencies: '@types/through': 0.0.33 rxjs: 7.8.2 @@ -15314,7 +15907,7 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 22.13.13 + '@types/node': 22.14.1 '@types/tough-cookie': 4.0.5 parse5: 7.2.1 @@ -15322,6 +15915,14 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 22.14.1 + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.16 + '@types/lodash@4.17.16': {} '@types/long@4.0.2': {} @@ -15338,10 +15939,10 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 22.13.13 + '@types/node': 22.14.1 form-data: 4.0.1 - '@types/node@18.19.83': + '@types/node@18.19.86': dependencies: undici-types: 5.26.5 @@ -15353,6 +15954,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@22.14.1': + dependencies: + undici-types: 6.21.0 + '@types/normalize-package-data@2.4.4': {} '@types/parse-path@7.0.3': {} @@ -15378,19 +15983,19 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 22.14.0 + '@types/node': 22.14.1 '@types/sendmail@1.4.7': {} '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 22.14.0 + '@types/node': 22.14.1 '@types/send': 0.17.4 '@types/ssh2@1.15.4': dependencies: - '@types/node': 18.19.83 + '@types/node': 18.19.86 '@types/stoppable@1.1.3': dependencies: @@ -15407,7 +16012,7 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 22.14.0 + '@types/node': 22.14.1 '@types/tough-cookie@4.0.5': {} @@ -15418,6 +16023,8 @@ snapshots: '@types/uuid@9.0.8': {} + '@types/validate-npm-package-name@4.0.2': {} + '@types/validator@13.12.3': {} '@types/web-bluetooth@0.0.21': {} @@ -15428,6 +16035,16 @@ snapshots: '@types/wtfnode@0.7.3': {} + '@types/yargs-interactive@2.1.6': + dependencies: + '@types/yargs': 17.0.33 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + '@types/zen-observable@0.8.3': {} '@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': @@ -15589,7 +16206,7 @@ snapshots: '@vuedx/template-ast-types': 0.7.1 fast-glob: 3.3.3 prettier: 3.5.3 - typescript: 5.8.2 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -15751,13 +16368,13 @@ snapshots: optionalDependencies: vite: 6.2.3(@types/node@22.13.13)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - '@vitest/mocker@3.0.9(vite@6.2.3(@types/node@22.14.0)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': + '@vitest/mocker@3.0.9(vite@6.2.3(@types/node@22.14.1)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.0.9 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.2.3(@types/node@22.14.0)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + vite: 6.2.3(@types/node@22.14.1)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) '@vitest/mocker@3.1.1(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': dependencies: @@ -16025,6 +16642,19 @@ snapshots: optionalDependencies: typescript: 5.8.2 + '@vue/language-core@2.2.8(typescript@5.8.3)': + dependencies: + '@volar/language-core': 2.4.12 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.13 + alien-signals: 1.0.4 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.8.3 + '@vue/reactivity@3.5.13': dependencies: '@vue/shared': 3.5.13 @@ -16248,6 +16878,8 @@ snapshots: abbrev@3.0.0: {} + abind@1.0.5: {} + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -16331,6 +16963,10 @@ snapshots: dependencies: type-fest: 0.21.3 + ansi-regex@2.1.1: {} + + ansi-regex@4.1.1: {} + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -16386,6 +17022,14 @@ snapshots: argparse@2.0.1: {} + argx@3.0.2: + dependencies: + iftype: 3.0.2 + + argx@4.0.4: + dependencies: + iftype: 4.0.9 + aria-hidden@1.2.4: dependencies: tslib: 2.8.1 @@ -16453,10 +17097,18 @@ snapshots: get-intrinsic: 1.2.7 is-array-buffer: 3.0.5 + arrayreduce@2.1.0: {} + arrify@1.0.1: {} asap@2.0.6: {} + askconfig@4.0.4: + dependencies: + argx: 4.0.4 + cli-color: 1.4.0 + objnest: 5.1.1 + asn1@0.2.6: dependencies: safer-buffer: 2.1.2 @@ -16499,6 +17151,8 @@ snapshots: async-sema@3.1.1: {} + async@1.5.2: {} + async@2.6.4: dependencies: lodash: 4.17.21 @@ -16583,6 +17237,11 @@ snapshots: transitivePeerDependencies: - supports-color + babel-runtime@6.26.0: + dependencies: + core-js: 2.6.12 + regenerator-runtime: 0.11.1 + babel-walk@3.0.0-canary-5: dependencies: '@babel/types': 7.27.0 @@ -16983,6 +17642,15 @@ snapshots: clean-stack@2.2.0: {} + cli-color@1.4.0: + dependencies: + ansi-regex: 2.1.1 + d: 1.0.2 + es5-ext: 0.10.64 + es6-iterator: 2.0.3 + memoizee: 0.4.17 + timers-ext: 0.1.8 + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -17012,12 +17680,20 @@ snapshots: cli-width@3.0.0: {} + cli-width@4.1.0: {} + clipboardy@4.0.0: dependencies: execa: 8.0.1 is-wsl: 3.1.0 is64bit: 2.0.0 + cliui@5.0.0: + dependencies: + string-width: 3.1.0 + strip-ansi: 5.2.0 + wrap-ansi: 5.1.0 + cliui@6.0.0: dependencies: string-width: 4.2.3 @@ -17389,6 +18065,8 @@ snapshots: dependencies: browserslist: 4.24.4 + core-js@2.6.12: {} + core-util-is@1.0.2: {} core-util-is@1.0.3: {} @@ -17439,6 +18117,24 @@ snapshots: crc-32: 1.2.2 readable-stream: 4.7.0 + create-create-app@7.3.0: + dependencies: + '@types/yargs-interactive': 2.1.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + epicfail: 3.0.0 + execa: 5.1.1 + gitconfig: 2.0.8 + globby: 11.1.0 + handlebars: 4.7.8 + is-utf8: 0.2.1 + license.js: 3.1.2 + slash: 3.0.0 + uuid: 8.3.2 + yargs-interactive: 3.0.1 + transitivePeerDependencies: + - encoding + cron@3.5.0: dependencies: '@types/luxon': 3.4.2 @@ -17593,6 +18289,11 @@ snapshots: - '@types/node' - typescript + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + dargs@7.0.0: {} dashdash@1.14.1: @@ -17930,6 +18631,8 @@ snapshots: emoji-regex@10.4.0: {} + emoji-regex@7.0.3: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -17963,6 +18666,17 @@ snapshots: env-paths@2.2.1: {} + envinfo@7.14.0: {} + + epicfail@3.0.0: + dependencies: + chalk: 4.1.2 + envinfo: 7.14.0 + node-fetch: 2.7.0 + pkg-up: 3.1.0 + transitivePeerDependencies: + - encoding + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -18066,8 +18780,33 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + es6-error@4.1.1: {} + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + es6-weak-map@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esbuild-android-64@0.14.54: optional: true @@ -18483,6 +19222,13 @@ snapshots: esm-resolve@1.0.11: {} + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + espree@10.3.0: dependencies: acorn: 8.14.1 @@ -18519,6 +19265,11 @@ snapshots: etag@1.8.1: {} + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-target-shim@5.0.1: {} eventemitter2@5.0.1: {} @@ -18531,6 +19282,18 @@ snapshots: events@3.3.0: {} + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -18558,6 +19321,14 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.1 + execcli@5.0.6: + dependencies: + argx: 4.0.4 + arrayreduce: 2.1.0 + findout: 3.0.2 + hasbin: 1.2.3 + stringcase: 4.3.1 + exit-hook@4.0.0: {} expand-tilde@2.0.2: @@ -18604,6 +19375,10 @@ snapshots: exsolve@1.0.4: {} + ext@1.7.0: + dependencies: + type: 2.7.3 + extend@3.0.2: {} external-editor@3.1.0: @@ -18818,6 +19593,8 @@ snapshots: path-exists: 5.0.0 unicorn-magic: 0.1.0 + findout@3.0.2: {} + findup-sync@4.0.0: dependencies: detect-file: 1.0.0 @@ -18980,6 +19757,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@6.0.1: {} + get-stream@8.0.1: {} get-stream@9.0.1: @@ -19077,6 +19856,16 @@ snapshots: dependencies: git-up: 8.0.1 + gitconfig@2.0.8: + dependencies: + argx: 3.0.2 + arrayreduce: 2.1.0 + askconfig: 4.0.4 + co: 4.6.0 + execcli: 5.0.6 + lodash.get: 4.4.2 + objnest: 5.1.1 + gitconfiglocal@1.0.0: dependencies: ini: 1.3.8 @@ -19352,6 +20141,10 @@ snapshots: dependencies: has-symbols: 1.1.0 + hasbin@1.2.3: + dependencies: + async: 1.5.2 + hash-sum@2.0.0: {} hasown@2.0.2: @@ -19521,6 +20314,8 @@ snapshots: httpxy@0.1.7: {} + human-signals@2.1.0: {} + human-signals@5.0.0: {} human-signals@8.0.0: {} @@ -19535,6 +20330,12 @@ snapshots: ieee754@1.2.1: {} + iftype@3.0.2: + dependencies: + babel-runtime: 6.26.0 + + iftype@4.0.9: {} + ignore-by-default@1.0.1: {} ignore@5.3.2: {} @@ -19606,6 +20407,34 @@ snapshots: ini@5.0.0: {} + inquirer@12.5.2(@types/node@22.14.1): + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/prompts': 7.4.1(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + ansi-escapes: 4.3.2 + mute-stream: 2.0.0 + run-async: 3.0.0 + rxjs: 7.8.2 + optionalDependencies: + '@types/node': 22.14.1 + + inquirer@7.3.3: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + run-async: 2.4.1 + rxjs: 6.6.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + inquirer@8.2.5: dependencies: ansi-escapes: 4.3.2 @@ -19754,6 +20583,8 @@ snapshots: dependencies: call-bound: 1.0.3 + is-fullwidth-code-point@2.0.0: {} + is-fullwidth-code-point@3.0.0: {} is-generator-function@1.1.0: @@ -19969,7 +20800,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.14.0 + '@types/node': 22.14.1 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -20210,6 +21041,10 @@ snapshots: libphonenumber-js@1.12.6: {} + license.js@3.1.2: + dependencies: + pify: 3.0.0 + light-my-request@6.3.0: dependencies: cookie: 1.0.2 @@ -20400,6 +21235,10 @@ snapshots: lru-cache@8.0.5: {} + lru-queue@0.1.0: + dependencies: + es5-ext: 0.10.64 + lucide-vue-next@0.488.0(vue@3.5.13(typescript@5.8.2)): dependencies: vue: 3.5.13(typescript@5.8.2) @@ -20456,6 +21295,17 @@ snapshots: media-typer@0.3.0: {} + memoizee@0.4.17: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.2.2 + lru-queue: 0.1.0 + next-tick: 1.1.0 + timers-ext: 0.1.8 + memoizerific@1.11.3: dependencies: map-or-similar: 1.5.0 @@ -20627,6 +21477,8 @@ snapshots: mute-stream@0.0.8: {} + mute-stream@2.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -20666,13 +21518,13 @@ snapshots: reflect-metadata: 0.1.14 rxjs: 7.8.2 - nest-commander@3.17.0(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(@types/inquirer@8.2.10)(typescript@5.8.2): + nest-commander@3.17.0(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(@types/inquirer@9.0.7)(typescript@5.8.2): dependencies: '@fig/complete-commander': 3.2.0(commander@11.1.0) '@golevelup/nestjs-discovery': 4.0.3(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2)) '@nestjs/common': 11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 11.0.12(@nestjs/common@11.0.12(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@types/inquirer': 8.2.10 + '@types/inquirer': 9.0.7 commander: 11.1.0 cosmiconfig: 8.3.6(typescript@5.8.2) inquirer: 8.2.6 @@ -20688,6 +21540,8 @@ snapshots: netmask@2.0.2: {} + next-tick@1.1.0: {} + nitropack@2.11.7(typescript@5.8.2)(xml2js@0.6.2): dependencies: '@cloudflare/kv-asset-handler': 0.4.0 @@ -20892,6 +21746,10 @@ snapshots: notation@1.3.6: {} + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -21103,6 +21961,12 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + objnest@5.1.1: + dependencies: + '@babel/runtime': 7.27.0 + abind: 1.0.5 + extend: 3.0.2 + obliterator@2.0.5: {} ofetch@1.4.1: @@ -21359,7 +22223,7 @@ snapshots: dependencies: '@babel/code-frame': 7.26.2 index-to-position: 0.1.2 - type-fest: 4.37.0 + type-fest: 4.38.0 parse-json@8.2.0: dependencies: @@ -21553,6 +22417,10 @@ snapshots: exsolve: 1.0.4 pathe: 2.0.3 + pkg-up@3.1.0: + dependencies: + find-up: 3.0.0 + pluralize@8.0.0: {} pm2-axon-rpc@0.7.1: @@ -21945,7 +22813,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.14.0 + '@types/node': 22.14.1 long: 5.3.1 protocols@2.0.2: {} @@ -22120,7 +22988,7 @@ snapshots: dependencies: find-up-simple: 1.0.0 read-pkg: 9.0.1 - type-fest: 4.37.0 + type-fest: 4.38.0 read-pkg-up@3.0.0: dependencies: @@ -22151,7 +23019,7 @@ snapshots: '@types/normalize-package-data': 2.4.4 normalize-package-data: 6.0.2 parse-json: 8.1.0 - type-fest: 4.37.0 + type-fest: 4.38.0 unicorn-magic: 0.1.0 read@1.0.7: @@ -22236,6 +23104,8 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 + regenerator-runtime@0.11.1: {} + regenerator-runtime@0.14.1: {} regexp-ast-analysis@0.7.1: @@ -22487,12 +23357,18 @@ snapshots: run-async@2.4.1: {} + run-async@3.0.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 run-series@1.1.9: {} + rxjs@6.6.7: + dependencies: + tslib: 1.9.3 + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -22968,6 +23844,12 @@ snapshots: string-env-interpolation@1.0.1: {} + string-width@3.1.0: + dependencies: + emoji-regex: 7.0.3 + is-fullwidth-code-point: 2.0.0 + strip-ansi: 5.2.0 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -23017,6 +23899,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringcase@4.3.1: {} + stringify-object@5.0.0: dependencies: get-own-enumerable-keys: 1.0.0 @@ -23025,6 +23909,10 @@ snapshots: stringify-package@1.0.1: {} + strip-ansi@5.2.0: + dependencies: + ansi-regex: 4.1.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -23037,6 +23925,8 @@ snapshots: strip-bom@4.0.0: {} + strip-final-newline@2.0.0: {} + strip-final-newline@3.0.0: {} strip-final-newline@4.0.0: {} @@ -23317,6 +24207,11 @@ snapshots: timeout-signal@2.0.0: {} + timers-ext@0.1.8: + dependencies: + es5-ext: 0.10.64 + next-tick: 1.1.0 + tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -23474,6 +24369,8 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type@2.7.3: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.3 @@ -23525,6 +24422,8 @@ snapshots: typescript@5.8.2: {} + typescript@5.8.3: {} + ua-parser-js@1.0.40: {} ufo@1.5.4: {} @@ -23765,6 +24664,8 @@ snapshots: uuid@3.4.0: {} + uuid@8.3.2: {} + uuid@9.0.1: {} validate-npm-package-license@3.0.4: @@ -23774,6 +24675,8 @@ snapshots: validate-npm-package-name@5.0.1: {} + validate-npm-package-name@6.0.0: {} + validator@13.12.0: {} value-or-promise@1.0.12: {} @@ -23821,13 +24724,13 @@ snapshots: - tsx - yaml - vite-node@3.0.9(@types/node@22.14.0)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): + vite-node@3.0.9(@types/node@22.14.1)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): dependencies: cac: 6.7.14 debug: 4.4.0(supports-color@9.4.0) es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 6.2.3(@types/node@22.14.0)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + vite: 6.2.3(@types/node@22.14.1)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - jiti @@ -24022,13 +24925,13 @@ snapshots: tsx: 4.19.3 yaml: 2.7.0 - vite@6.2.3(@types/node@22.14.0)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): + vite@6.2.3(@types/node@22.14.1)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): dependencies: esbuild: 0.25.1 postcss: 8.5.3 rollup: 4.37.0 optionalDependencies: - '@types/node': 22.14.0 + '@types/node': 22.14.1 fsevents: 2.3.3 jiti: 2.4.2 stylus: 0.57.0 @@ -24105,10 +25008,10 @@ snapshots: - tsx - yaml - vitest@3.0.9(@types/node@22.14.0)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jiti@2.4.2)(jsdom@26.0.0)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): + vitest@3.0.9(@types/node@22.14.1)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jiti@2.4.2)(jsdom@26.0.0)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): dependencies: '@vitest/expect': 3.0.9 - '@vitest/mocker': 3.0.9(vite@6.2.3(@types/node@22.14.0)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) + '@vitest/mocker': 3.0.9(vite@6.2.3(@types/node@22.14.1)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) '@vitest/pretty-format': 3.0.9 '@vitest/runner': 3.0.9 '@vitest/snapshot': 3.0.9 @@ -24124,11 +25027,11 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.2.3(@types/node@22.14.0)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vite-node: 3.0.9(@types/node@22.14.0)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + vite: 6.2.3(@types/node@22.14.1)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + vite-node: 3.0.9(@types/node@22.14.1)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.14.0 + '@types/node': 22.14.1 '@vitest/ui': 3.0.9(vitest@3.0.9) happy-dom: 17.4.4 jsdom: 26.0.0 @@ -24201,14 +25104,14 @@ snapshots: dependencies: ufo: 1.5.4 - vue-component-meta@2.2.8(typescript@5.8.2): + vue-component-meta@2.2.8(typescript@5.8.3): dependencies: '@volar/typescript': 2.4.12 - '@vue/language-core': 2.2.8(typescript@5.8.2) + '@vue/language-core': 2.2.8(typescript@5.8.3) path-browserify: 1.0.1 vue-component-type-helpers: 2.2.8 optionalDependencies: - typescript: 5.8.2 + typescript: 5.8.3 vue-component-type-helpers@2.2.0: {} @@ -24533,6 +25436,12 @@ snapshots: wordwrap@1.0.0: {} + wrap-ansi@5.1.0: + dependencies: + ansi-styles: 3.2.1 + string-width: 3.1.0 + strip-ansi: 5.2.0 + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -24593,6 +25502,16 @@ snapshots: yaml@2.7.0: {} + yargs-interactive@3.0.1: + dependencies: + inquirer: 7.3.3 + yargs: 14.2.3 + + yargs-parser@15.0.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 @@ -24602,6 +25521,20 @@ snapshots: yargs-parser@21.1.1: {} + yargs@14.2.3: + dependencies: + cliui: 5.0.0 + decamelize: 1.2.0 + find-up: 3.0.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 3.1.0 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 15.0.3 + yargs@15.4.1: dependencies: cliui: 6.0.0 @@ -24642,6 +25575,8 @@ snapshots: yocto-queue@1.2.0: {} + yoctocolors-cjs@2.1.2: {} + yoctocolors@2.1.1: {} youch-core@0.3.2: