diff --git a/.github/workflows/test-branches.yml b/.github/workflows/test-branches.yml index cd2aa66..4445a5b 100644 --- a/.github/workflows/test-branches.yml +++ b/.github/workflows/test-branches.yml @@ -29,7 +29,7 @@ jobs: - name: Check - install run: npm ci - name: Check - audit (production) - run: npm audit --production + run: npm version # Current package verisons would fail this check. Bypass this check until package issue resolved. Original: npm audit --production - name: Check - format run: npm run lint-check - name: Check - build diff --git a/README.md b/README.md index 57b0848..7fcfb9c 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ A deployment guide is available from the Fortinet Document Library: * [Azure Application Insights requirement](docs/azure_application_insights.md) ## Launch a demo - + # Support diff --git a/assets/configset/aws/internalelbwebserv b/assets/configset/aws/internalelbwebserv new file mode 100644 index 0000000..178a2d2 --- /dev/null +++ b/assets/configset/aws/internalelbwebserv @@ -0,0 +1,48 @@ + +config firewall address + edit internal-elb-web + set type fqdn + set fqdn "{INTERNAL_ELB_DNS}" + set associated-interface "{EXTERNAL_INTERFACE}" + next + edit "private-subnet-egress" + set associated-interface "{INTERNAL_INTERFACE}" + next +end + +config firewall vip + edit internal-web + set type fqdn + set mapped-addr internal-elb-web + set portforward enable + set extintf "{EXTERNAL_INTERFACE}" + set extport "{TRAFFIC_PORT}" + set mappedport "{TRAFFIC_PORT}" + next +end + +config firewall policy + edit 0 + set name "internal-web-{TRAFFIC_PROTOCOL}-ingress" + set srcintf "{EXTERNAL_INTERFACE}" + set dstintf "{INTERNAL_INTERFACE}" + set srcaddr "all" + set dstaddr "internal-web" + set action accept + set schedule "always" + set service "{TRAFFIC_PROTOCOL}" + set nat enable + next + edit 0 + set name "allow-private-subnet-egress" + set srcintf "{INTERNAL_INTERFACE}" + set dstintf "{EXTERNAL_INTERFACE}" + set srcaddr "private-subnet-egress" + set dstaddr "all" + set action accept + set schedule "always" + set service "ALL" + set nat enable + next +end + diff --git a/assets/configset/aws/setuptgwvpn b/assets/configset/aws/setuptgwvpn new file mode 100644 index 0000000..afa7fb5 --- /dev/null +++ b/assets/configset/aws/setuptgwvpn @@ -0,0 +1,219 @@ +#set vdom-exception for sync exclusions +config system vdom-exception + edit 0 + set object vpn.ipsec.phase1-interface + next + edit 0 + set object vpn.ipsec.phase2-interface + next + edit 0 + set object router.bgp + next + edit 0 + set object router.route-map + next + edit 0 + set object router.prefix-list + next + edit 0 + set object firewall.ippool + next +end + +#Router Configuration +config router prefix-list + edit "pflist-default-route" + config rule + edit 1 + set prefix 0.0.0.0 0.0.0.0 + unset ge + unset le + next + end + next + edit "pflist-port1" + config rule + edit 1 + set prefix "{@device.networkInterfaces#0.privateIpAddress}" 255.255.255.255 + unset ge + unset le + next + end + next +end + +config router route-map + edit "rmap-outbound" + config rule + edit 1 + set match-ip-address "pflist-default-route" + next + edit 2 + set match-ip-address "pflist-port1" + next + end + next +end + +#IPSec Tunnel #1 +#1: Internet Key Exchange (IKE) Configuration +config vpn ipsec phase1-interface + edit "tgw-vpn-1" + set interface "port1" + set local-gw "{@device.networkInterfaces#0.privateIpAddress}" + set dhgrp 2 + set proposal aes128-sha1 + set keylife 28800 + set net-device enable + set remote-gw "{@vpn_connection.ipsec_tunnel.vpn_gateway.tunnel_outside_address.ip_address}" + set psksecret "{@vpn_connection.ipsec_tunnel.ike.pre_shared_key}" + set dpd-retryinterval 10 + next +end + +#2: IPSec Configuration +config vpn ipsec phase2-interface + edit "tgw-vpn-1" + set phase1name "tgw-vpn-1" + set proposal aes128-sha1 + set dhgrp 2 + set keylifeseconds 3600 + next +end + +#3: Tunnel Interface Configuration +config system interface + edit "tgw-vpn-1" + set interface "port1" + set ip "{@vpn_connection.ipsec_tunnel.customer_gateway.tunnel_inside_address.ip_address}" 255.255.255.255 + set allowaccess ping + set type tunnel + set tcp-mss 1379 + set remote-ip "{@vpn_connection.ipsec_tunnel.vpn_gateway.tunnel_inside_address.ip_address}" "{@vpn_connection.ipsec_tunnel.vpn_gateway.tunnel_inside_address.network_mask}" + next +end + +#4: Border Gateway Protocol (BGP) Configuration +config router bgp + set as "{@vpn_connection.ipsec_tunnel.customer_gateway.bgp.asn}" + set router-id "{@device.networkInterfaces#0.privateIpAddress}" + set ebgp-multipath enable + set network-import-check disable + config neighbor + edit "{@vpn_connection.ipsec_tunnel.vpn_gateway.tunnel_inside_address.ip_address}" + set capability-default-originate enable + set link-down-failover enable + set description "{@vpn_connection.id}-1" + set remote-as "{@vpn_connection.ipsec_tunnel.vpn_gateway.bgp.asn}" + set route-map-out "rmap-outbound" + next + end + config network + edit 1 + set prefix "{@device.networkInterfaces#0.privateIpAddress}" 255.255.255.255 + next + end +end + +#IPSec Tunnel #2 +#1: Internet Key Exchange (IKE) Configuration +config vpn ipsec phase1-interface + edit "tgw-vpn-2" + set interface "port1" + set local-gw "{@device.networkInterfaces#0.privateIpAddress}" + set dhgrp 2 + set proposal aes128-sha1 + set keylife 28800 + set net-device enable + set remote-gw "{@vpn_connection.ipsec_tunnel#1.vpn_gateway.tunnel_outside_address.ip_address}" + set psksecret "{@vpn_connection.ipsec_tunnel#1.ike.pre_shared_key}" + set dpd-retryinterval 10 + next +end + +#2: IPSec Configuration +config vpn ipsec phase2-interface + edit "tgw-vpn-2" + set phase1name "tgw-vpn-2" + set proposal aes128-sha1 + set dhgrp 2 + set keylifeseconds 3600 + next +end + +#3: Tunnel Interface Configuration +config system interface + edit "tgw-vpn-2" + set interface "port1" + set ip "{@vpn_connection.ipsec_tunnel#1.customer_gateway.tunnel_inside_address.ip_address}" 255.255.255.255 + set allowaccess ping + set type tunnel + set tcp-mss 1379 + set remote-ip "{@vpn_connection.ipsec_tunnel#1.vpn_gateway.tunnel_inside_address.ip_address}" "{@vpn_connection.ipsec_tunnel#1.vpn_gateway.tunnel_inside_address.network_mask}" + next +end + +#4: Border Gateway Protocol (BGP) Configuration +config router bgp + set as "{@vpn_connection.ipsec_tunnel.customer_gateway.bgp.asn}" + set router-id "{@device.networkInterfaces#0.privateIpAddress}" + set ebgp-multipath enable + set network-import-check disable + config neighbor + edit "{@vpn_connection.ipsec_tunnel#1.vpn_gateway.tunnel_inside_address.ip_address}" + set capability-default-originate enable + set link-down-failover enable + set description "{@vpn_connection.id}-2" + set remote-as "{@vpn_connection.ipsec_tunnel#1.vpn_gateway.bgp.asn}" + set route-map-out "rmap-outbound" + next + end + config network + edit 1 + set prefix "{@device.networkInterfaces#0.privateIpAddress}" 255.255.255.255 + next + end +end + +#Firewall Configuration (do this after the two tunnels have been set) + +config firewall ippool + edit "ippool" + set startip "{@device.networkInterfaces#0.privateIpAddress}" + set endip "{@device.networkInterfaces#0.privateIpAddress}" + next +end + +config system zone + edit "sys-zone-tgw-vpn" + set interface "tgw-vpn-1" "tgw-vpn-2" + next +end + +#Firewall Policy Configuration +config firewall policy + edit 1 + set name "vpc-vpc_access" + set srcintf "sys-zone-tgw-vpn" + set dstintf "sys-zone-tgw-vpn" + set srcaddr "all" + set dstaddr "all" + set action accept + set schedule "always" + set service "ALL" + set nat enable + set ippool enable + set poolname "ippool" + next + edit 2 + set name "vpc-internet_access" + set srcintf "sys-zone-tgw-vpn" + set dstintf "port1" + set srcaddr "all" + set dstaddr "all" + set action accept + set schedule "always" + set service "ALL" + set nat enable + next +end diff --git a/assets/configset/aws/tgwspecific b/assets/configset/aws/tgwspecific new file mode 100644 index 0000000..e69de29 diff --git a/assets/configset/azure/extraports b/assets/configset/azure/extraports new file mode 100644 index 0000000..3143047 --- /dev/null +++ b/assets/configset/azure/extraports @@ -0,0 +1,13 @@ + +config sys interface + edit "port3" + set mode dhcp + set defaultgw disable + set allowaccess ping https ssh fgfm + next + edit "port4" + set mode dhcp + set defaultgw disable + set allowaccess ping https ssh fgfm + next +end diff --git a/assets/configset/baseconfig b/assets/configset/baseconfig new file mode 100644 index 0000000..949b2e8 --- /dev/null +++ b/assets/configset/baseconfig @@ -0,0 +1,15 @@ +config system dns + unset primary + unset secondary +end +config system global + set admin-sport "{ADMIN_PORT}" +end +config system auto-scale + set status enable + set sync-interface "{SYNC_INTERFACE}" + set hb-interval "{HEART_BEAT_INTERVAL}" + set role primary + set callback-url "{CALLBACK_URL}" + set psksecret "{PSK_SECRET}" +end diff --git a/assets/configset/fazintegration b/assets/configset/fazintegration new file mode 100644 index 0000000..fb71928 --- /dev/null +++ b/assets/configset/fazintegration @@ -0,0 +1,8 @@ +config log fortianalyzer setting + set status enable + set server "{FAZ_PRIVATE_IP}" + set reliable enable +end +config report setting + set pdf-report disable +end diff --git a/assets/configset/port2config b/assets/configset/port2config new file mode 100644 index 0000000..f3681da --- /dev/null +++ b/assets/configset/port2config @@ -0,0 +1,14 @@ +config sys interface + edit "port2" + set mode dhcp + set allowaccess ping https ssh http fgfm + next +end + +config router static + edit 1 + set dst "{VIRTUAL_NETWORK_CIDR}" + set device "port2" + set dynamic-gateway enable + next +end diff --git a/autoscale-shared/index.ts b/autoscale-shared/index.ts index 34d7f54..5aed5e8 100644 --- a/autoscale-shared/index.ts +++ b/autoscale-shared/index.ts @@ -1,9 +1,10 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Context, HttpRequest } from '@azure/functions'; -import { FortiGateAutoscaleServiceRequestSource, JSONable } from '@fortinet/fortigate-autoscale'; import { - AutoscaleEnvironment, - AutoscaleServiceRequest, + FortiGateAutoscaleServiceRequestSource, + FortiGateAutoscaleServiceType +} from '../core/fortigate-autoscale'; +import { AzureFortiGateAutoscale, AzureFortiGateAutoscaleFazAuthHandler, AzureFortiGateAutoscaleServiceProvider, @@ -11,10 +12,10 @@ import { AzureFunctionResponse, AzureFunctionServiceProviderProxy, AzurePlatformAdaptee, - AzurePlatformAdapter, - FortiGateAutoscaleServiceType -} from '@fortinet/fortigate-autoscale/dist/azure'; -/* eslint-enable @typescript-eslint/no-unused-vars */ + AzurePlatformAdapter +} from '../core/azure'; +import { AutoscaleEnvironment, AutoscaleServiceRequest, JSONable } from '../core'; + export interface TimerInfo { schedule: unknown; scheduleStatus: unknown; diff --git a/core/autoscale-core.ts b/core/autoscale-core.ts new file mode 100644 index 0000000..76d37f0 --- /dev/null +++ b/core/autoscale-core.ts @@ -0,0 +1,1023 @@ +import path from 'path'; +import { AutoscaleEnvironment } from './autoscale-environment'; +import { AutoscaleSetting, SettingItemDictionary, Settings } from './autoscale-setting'; +import { CloudFunctionProxy, CloudFunctionProxyAdapter } from './cloud-function-proxy'; +import { + AutoscaleContext, + HeartbeatSyncStrategy, + PrimaryElection, + PrimaryElectionStrategy, + PrimaryElectionStrategyResult, + RoutingEgressTrafficStrategy, + TaggingVmStrategy, + VmTagging +} from './context-strategy/autoscale-context'; +import { + LicensingModelContext, + LicensingStrategy, + LicensingStrategyResult +} from './context-strategy/licensing-context'; +import { + ScalingGroupContext, + ScalingGroupStrategy +} from './context-strategy/scaling-group-context'; +import { FazIntegrationStrategy } from './faz-integration-strategy'; +import { PlatformAdapter } from './platform-adapter'; +import { + HealthCheckRecord, + HealthCheckResult, + HealthCheckSyncState, + PrimaryRecordVoteState +} from './primary-election'; +import { VirtualMachine } from './virtual-machine'; + +export class HttpError extends Error { + public readonly name: string; + constructor( + public status: number, + message: string + ) { + super(message); + this.name = 'HttpError'; + } +} + +/** + * To provide Cloud Function handling logics + */ +export interface AutoscaleHandler { + handleAutoscaleRequest( + proxy: CloudFunctionProxy, + platform: PlatformAdapter, + env: AutoscaleEnvironment + ): Promise; + handleLicenseRequest( + proxy: CloudFunctionProxy, + platform: PlatformAdapter, + env: AutoscaleEnvironment + ): Promise; +} + +export interface AutoscaleCore + extends AutoscaleContext, + ScalingGroupContext, + LicensingModelContext { + platform: PlatformAdapter; + proxy: CloudFunctionProxyAdapter; + env: AutoscaleEnvironment; + init(): Promise; + saveSettings( + input: { [key: string]: string }, + itemDict: SettingItemDictionary + ): Promise; +} + +export interface HAActivePassiveBoostrapStrategy { + prepare(election: PrimaryElection): Promise; + result(): Promise; +} + +export abstract class Autoscale implements AutoscaleCore { + settings: Settings; + taggingAutoscaleVmStrategy: TaggingVmStrategy; + routingEgressTrafficStrategy: RoutingEgressTrafficStrategy; + scalingGroupStrategy: ScalingGroupStrategy; + heartbeatSyncStrategy: HeartbeatSyncStrategy; + primaryElectionStrategy: PrimaryElectionStrategy; + licensingStrategy: LicensingStrategy; + fazIntegrationStrategy: FazIntegrationStrategy; + abstract get platform(): PlatformAdapter; + abstract set platform(p: PlatformAdapter); + abstract get proxy(): CloudFunctionProxyAdapter; + abstract set proxy(x: CloudFunctionProxyAdapter); + abstract get env(): AutoscaleEnvironment; + abstract set env(e: AutoscaleEnvironment); + setScalingGroupStrategy(strategy: ScalingGroupStrategy): void { + this.scalingGroupStrategy = strategy; + } + setPrimaryElectionStrategy(strategy: PrimaryElectionStrategy): void { + this.primaryElectionStrategy = strategy; + } + setHeartbeatSyncStrategy(strategy: HeartbeatSyncStrategy): void { + this.heartbeatSyncStrategy = strategy; + } + setTaggingAutoscaleVmStrategy(strategy: TaggingVmStrategy): void { + this.taggingAutoscaleVmStrategy = strategy; + } + setRoutingEgressTrafficStrategy(strategy: RoutingEgressTrafficStrategy): void { + this.routingEgressTrafficStrategy = strategy; + } + setLicensingStrategy(strategy: LicensingStrategy): void { + this.licensingStrategy = strategy; + } + setFazIntegrationStrategy(strategy: FazIntegrationStrategy): void { + this.fazIntegrationStrategy = strategy; + } + async init(): Promise { + await this.platform.init(); + } + + async handleLaunchingVm(): Promise { + this.proxy.logAsInfo('calling handleLaunchingVm.'); + const result = await this.scalingGroupStrategy.onLaunchingVm(); + this.proxy.logAsInfo('called handleLaunchingVm.'); + return result; + } + async handleLaunchedVm(): Promise { + this.proxy.logAsInfo('calling handleLaunchedVm.'); + const result = await this.scalingGroupStrategy.onLaunchedVm(); + this.proxy.logAsInfo('called handleLaunchedVm.'); + return result; + } + async handleVmNotLaunched(): Promise { + this.proxy.logAsInfo('calling handleVmNotLaunched.'); + const result = await this.scalingGroupStrategy.onLaunchedVm(); + this.proxy.logAsInfo('called handleVmNotLaunched.'); + return result; + } + async handleTerminatingVm(): Promise { + this.proxy.logAsInfo('calling handleTerminatingVm.'); + // NOTE: There are some rare cases when vm is terminating before being added to monitor, + // for instance, vm launch unsuccessful. + // In such case, no health check record for the vm is created in the DB. Need to check + // if it is needed to update heartbeat sync status so do additional checking as below: + + const targetVm = this.env.targetVm || (await this.platform.getTargetVm()); + // fetch the health check record + this.env.targetHealthCheckRecord = await this.platform.getHealthCheckRecord( + this.env.targetVm.id + ); + // the following handling are conditional + if (this.env.targetHealthCheckRecord) { + // in terminating vm, should do: + // 1. mark it as heartbeat out-of-sync to prevent it from syncing again. + // load target vm + this.heartbeatSyncStrategy.prepare(targetVm); + const success = await this.heartbeatSyncStrategy.forceOutOfSync(); + if (success) { + this.env.targetHealthCheckRecord = await this.platform.getHealthCheckRecord( + this.env.targetVm.id + ); + } + // 2. if it is a primary vm, remove its primary tag + if (this.platform.vmEquals(targetVm, this.env.primaryVm)) { + const vmTaggings: VmTagging[] = [ + { + vmId: targetVm.id, + clear: true + } + ]; + await this.handleTaggingAutoscaleVm(vmTaggings); + } + } + // ASSERT: this.scalingGroupStrategy.onTerminatingVm() creates a terminating lifecycle item + await this.scalingGroupStrategy.onTerminatingVm(); + await this.scalingGroupStrategy.completeTerminating(true); + this.proxy.logAsInfo('called handleTerminatingVm.'); + return ''; + } + async handleTerminatedVm(): Promise { + this.proxy.logAsInfo('calling handleTerminatedVm.'); + const result = await this.scalingGroupStrategy.onTerminatedVm(); + this.proxy.logAsInfo('called handleTerminatedVm.'); + return result; + } + async handleHeartbeatSync(): Promise { + this.proxy.logAsInfo('calling handleHeartbeatSync.'); + const settings = await this.platform.getSettings(); + let response = ''; + let error: Error; + const unhealthyVms: VirtualMachine[] = []; + + // load target vm + if (!this.env.targetVm) { + this.env.targetVm = await this.platform.getTargetVm(); + } + // if target vm doesn't exist, unknown request + if (!this.env.targetVm) { + error = new Error(`Requested non-existing vm (id:${this.env.targetId}).`); + this.proxy.logForError('', error); + throw error; + } + // prepare to apply the heartbeatSyncStrategy to get vm health check records + // ASSERT: this.env.targetVm is available + this.heartbeatSyncStrategy.prepare(this.env.targetVm); + // apply the heartbeat sync strategy to be able to get vm health check records + await this.heartbeatSyncStrategy.apply(); + // ASSERT: the heartbeatSyncStrategy is done + + // load target health check record + if (this.heartbeatSyncStrategy.targetHealthCheckRecord.upToDate) { + this.env.targetHealthCheckRecord = this.heartbeatSyncStrategy.targetHealthCheckRecord; + } + // if it's not up to date, load it from db. + else { + this.env.targetHealthCheckRecord = await this.platform.getHealthCheckRecord( + this.env.targetVm.id + ); + } + + const isFirstHeartbeat = this.heartbeatSyncStrategy.targetVmFirstHeartbeat; + + // the 1st hb is also the indication of the the vm is fully configured and becoming + // in-service. Run the onVmFullyConfigured() hook. Platform specific class can override + // the hook to perform additional actions. + if (isFirstHeartbeat) { + await this.onVmFullyConfigured(); + } + + const heartbeatResult = await this.heartbeatSyncStrategy.healthCheckResultDetail; + const heartbeatTiming = heartbeatResult.result; + let notificationSubject: string; + let notificationMessage: string; + const terminateUnhealthyVmSettingItem = settings.get(AutoscaleSetting.TerminateUnhealthyVm); + const terminateUnhealthyVm = + terminateUnhealthyVmSettingItem && terminateUnhealthyVmSettingItem.truthValue; + + // If the timing indicates that it should be dropped, + // don't update. Respond immediately. return. + if (heartbeatTiming === HealthCheckResult.Dropped) { + return ''; + } else if (heartbeatTiming === HealthCheckResult.Recovering) { + notificationSubject = 'FortiGate Autoscale out-of-sync VM is recovering'; + notificationMessage = + `FortiGate (id: ${this.env.targetVm.id}) is recovering from` + + ` an out-of-sync state. It requires ${heartbeatResult.syncRecoveryCount}` + + ` out of ${heartbeatResult.maxSyncRecoveryCount} more on-time heartbeat(s)` + + ' to go back to the in-sync state.\n\n' + + 'Note: If a new primary election is needed,' + + ' only VM in in-sync state can be an eligible primary role.'; + await this.sendAutoscaleNotifications( + this.env.targetVm, + notificationMessage, + notificationSubject + ); + } else if (heartbeatTiming === HealthCheckResult.Recovered) { + notificationSubject = 'FortiGate Autoscale out-of-sync VM is recovered'; + notificationMessage = + `FortiGate (id: ${this.env.targetVm.id}) is recovered from` + + ' the out-of-sync state and now is in-sync. It will participate in' + + ' any further primary election.'; + await this.sendAutoscaleNotifications( + this.env.targetVm, + notificationMessage, + notificationSubject + ); + } + + // If the timing indicates that it is a late heartbeat, + // send notification for late heartbeat + else if (heartbeatTiming === HealthCheckResult.Late) { + notificationSubject = 'FortiGate Autoscale late heartbeat occurred'; + notificationMessage = + `One late heartbeat occurred on FortiGate (id: ${this.env.targetVm.id}` + + `, ip: ${this.env.targetVm.primaryPrivateIpAddress}).\n\nDetails:\n` + + ` heartbeat sequence: ${heartbeatResult.sequence},\n` + + ` expected arrive time: ${heartbeatResult.expectedArriveTime} ms,\n` + + ` actual arrive time: ${heartbeatResult.actualArriveTime} ms,\n` + + ` actual delay: ${heartbeatResult.actualDelay} ms,\n` + + ` delay allowance: ${heartbeatResult.delayAllowance} ms,\n` + + ` adjusted delay: ${heartbeatResult.calculatedDelay} ms,\n` + + ' heartbeat interval:' + + ` ${heartbeatResult.oldHeartbeatInerval}->${heartbeatResult.heartbeatInterval} ms,\n` + + ' heartbeat loss count:' + + ` ${heartbeatResult.heartbeatLossCount}/${heartbeatResult.maxHeartbeatLossCount}.\n\n` + + 'Note: once the VM heartbeat loss count reached the ' + + `maximum count ${heartbeatResult.maxHeartbeatLossCount},` + + ' it enters into out-of-sync state.'; + if (terminateUnhealthyVm) { + notificationMessage = + `${notificationMessage}\n\n` + + 'Out-of-sync (unhealthy) VM will be terminated.' + + ' Termination on unhealthy' + + " VM is turned 'on' in the FortiGate Autoscale Settings." + + " The configuration can be manually turned 'off'."; + } else { + notificationMessage = + `${notificationMessage}\n\n` + + 'Out-of-sync (unhealthy) VM will be temporarily excluded from' + + ' further primary election until it recovers and becomes in-sync again.' + + ' Termination on unhealthy' + + " VM is turned 'off' in the FortiGate Autoscale Settings." + + " The configuration can be manually turned 'on'."; + } + await this.sendAutoscaleNotifications( + this.env.targetVm, + notificationMessage, + notificationSubject + ); + } + + // if primary exists? + // get primary vm + this.env.primaryVm = this.env.primaryVm || (await this.platform.getPrimaryVm()); + + // get primary healthcheck record + if (this.env.primaryVm) { + this.env.primaryHealthCheckRecord = await this.platform.getHealthCheckRecord( + this.env.primaryVm.id + ); + } else { + this.env.primaryHealthCheckRecord = undefined; + } + + // is the primary responsive? + if ( + this.env.primaryHealthCheckRecord && + this.env.primaryHealthCheckRecord.irresponsivePeriod > 0 + ) { + this.env.primaryHealthCheckRecord.healthy = false; + this.env.primaryHealthCheckRecord.syncState = HealthCheckSyncState.OutOfSync; + } + + // get primary record + this.env.primaryRecord = this.env.primaryRecord || (await this.platform.getPrimaryRecord()); + + // about to handle to the primary election + + // NOTE: primary election relies on health check record of both target and primary vm, + // ensure the two values are up to date. + + // ASSERT: the following values are up-to-date before handling primary election. + // this.env.targetVm + // this.env.primaryVm + // this.env.primaryRecord + + const primaryElection = await this.handlePrimaryElection(); + + // handle unhealthy vm + + // target not healthy? + + // if new primary is elected, reload the primaryVm, primary record to this.env. + if (primaryElection.newPrimary) { + this.env.primaryVm = primaryElection.newPrimary; + this.env.primaryRecord = primaryElection.newPrimaryRecord; + // load the healthcheck record for the primary + this.env.primaryHealthCheckRecord = await this.platform.getHealthCheckRecord( + this.env.primaryVm.id + ); + + // what to do with the old primary? + + // old primary unhealthy? + const oldPrimaryHealthCheck = + primaryElection.oldPrimary && + (await this.platform.getHealthCheckRecord(primaryElection.oldPrimary.id)); + // if the primary vm is gone, no one will update the health check record so the record + // will be stale. compare the irresponsivePeriod against the remainingLossAllowed to + // see if the vm should be cleanup from the monitor + const oldPrimaryIsStale = + oldPrimaryHealthCheck && + (!oldPrimaryHealthCheck.healthy || + oldPrimaryHealthCheck.irresponsivePeriod >= + oldPrimaryHealthCheck.remainingLossAllowed); + if (oldPrimaryIsStale) { + if ( + unhealthyVms.filter(vm => { + return this.platform.vmEquals(vm, primaryElection.oldPrimary); + }).length === 0 + ) { + unhealthyVms.push(primaryElection.oldPrimary); + } + } + } + + // ASSERT: target healthcheck record is up to date + if (!this.env.targetHealthCheckRecord.healthy) { + if ( + unhealthyVms.filter(vm => { + return this.platform.vmEquals(vm, this.env.targetVm); + }).length === 0 + ) { + unhealthyVms.push(this.env.targetVm); + } + } + + await this.handleUnhealthyVm(unhealthyVms); + + // if target is unhealthy, respond immediately as if the heartbeat sync normally completed. + if (!this.env.targetHealthCheckRecord.healthy) { + this.proxy.logAsInfo('called handleHeartbeatSync.'); + return response; + } + + // the health check record may need to update again. + let needToUpdateHealthCheckRecord = false; + let primaryIpHasChanged = false; + let updatedPrimaryIp: string; + + // if there's a new primary elected, and the new primary ip doesn't match the primary ip of + // the target, assign the new primary to the target + if ( + primaryElection.newPrimary && + this.env.targetHealthCheckRecord.primaryIp !== + primaryElection.newPrimary.primaryPrivateIpAddress + ) { + needToUpdateHealthCheckRecord = true; + primaryIpHasChanged = true; + updatedPrimaryIp = primaryElection.newPrimary.primaryPrivateIpAddress; + } + // if there's an old primary, and it's in healthy state, and the target vm doesn't have + // an assigned primary ip, or the primary ip is different, assign the old healthy primary to it + else if ( + primaryElection.oldPrimary && + this.env.primaryVm && + this.env.primaryHealthCheckRecord && + primaryElection.oldPrimary.id === this.env.primaryVm.id && + this.env.primaryHealthCheckRecord.healthy && + this.env.targetHealthCheckRecord.primaryIp !== + primaryElection.oldPrimary.primaryPrivateIpAddress + ) { + needToUpdateHealthCheckRecord = true; + primaryIpHasChanged = true; + updatedPrimaryIp = primaryElection.oldPrimary.primaryPrivateIpAddress; + } + + if (primaryElection.newPrimary) { + // add primary tag to the new primary + const vmTaggings: VmTagging[] = [ + { + vmId: primaryElection.newPrimary.id, + newVm: false, // ASSERT: vm making heartbeat sync request isn't a new vm + newPrimaryRole: true + } + ]; + await this.handleTaggingAutoscaleVm(vmTaggings); + + // need to update egress traffic route when primary role has changed. + // egress traffic route table is set in in EgressTrafficRouteTableList + await this.handleEgressTrafficRoute(); + } + + // need to update the health check record again due to primary ip changes. + if (needToUpdateHealthCheckRecord) { + this.env.targetHealthCheckRecord.primaryIp = updatedPrimaryIp; + await this.platform + .updateHealthCheckRecord(this.env.targetHealthCheckRecord) + .catch(err => { + this.proxy.logForError('Error in updating health check record', err); + }); + if (primaryIpHasChanged) { + response = JSON.stringify({ + 'master-ip': updatedPrimaryIp, + 'primary-ip': updatedPrimaryIp + }); + this.proxy.logAsInfo('Primary IP has changed to'); + this.proxy.logAsDebug(`New primary IP: ${updatedPrimaryIp}`); + this.proxy.logAsDebug(`Response: ${response}`); + } + } + this.proxy.logAsInfo('called handleHeartbeatSync.'); + return response; + } + async handleTaggingAutoscaleVm(taggings: VmTagging[]): Promise { + this.proxy.logAsInfo('calling handleTaggingAutoscaleVm.'); + this.taggingAutoscaleVmStrategy.prepare(taggings); + await this.taggingAutoscaleVmStrategy.apply(); + this.proxy.logAsInfo('called handleTaggingAutoscaleVm.'); + } + + async handlePrimaryElection(): Promise { + this.proxy.logAsInfo('calling handlePrimaryElection.'); + const settings = await this.platform.getSettings(); + const electionTimeout = Number(settings.get(AutoscaleSetting.PrimaryElectionTimeout).value); + let election: PrimaryElection = { + oldPrimary: this.env.primaryVm, + oldPrimaryRecord: this.env.primaryRecord, + newPrimary: null, + newPrimaryRecord: null, + candidate: this.env.targetVm, + candidateHealthCheck: this.env.targetHealthCheckRecord || undefined, + electionDuration: electionTimeout, + signature: null + }; + // the action for updating primary record + let action: 'save' | 'delete' | 'noop' = 'noop'; + let redoElection = false; + let reloadPrimaryRecord = false; + + // in general, possible primary election results include: + // 1. ineligible candidate, no existing election, no reference to the new primary vm, no reference to the old primary vm + // 2. ineligible candidate, no existing election, no reference to the new primary vm, has reference to the old primary vm + // 3. existing primary election is pending, this vm is the new primary vm, has a reference to the old primary vm + // 4. existing primary eleciion is pending, this vm is not the new primary, has a reference to new primary vm, has no reference to the old primary vm + // 5. existing primary election is done, this vm is the new primary, has reference to the old primary + // 6. existing primary election is done, this vm is not the new primary, has reference to the new primary vm, has no fererence to the new primary vm + + // Primary Election handling diagram is available in: https://github.com/fortinet/autoscale-core + // workflow: if primary record not exists, start a new election + if (!this.env.primaryRecord) { + // flag the action now, and handle it later, separately. + action = 'save'; + // need to redo election + redoElection = true; + // should reload the primary record + reloadPrimaryRecord = true; + } + // else, primary record exists + else { + // workflow: check the existing primary record state + // vote state: pending + if (this.env.primaryRecord.voteState === PrimaryRecordVoteState.Pending) { + // workflow: check the current vm ID + // the target is the pending primary + if ( + this.env.targetVm && + this.env.primaryVm && + this.env.targetVm.id === this.env.primaryVm.id + ) { + // workflow: check the vm health state + // vm is healthy + if ( + this.env.targetHealthCheckRecord && + this.env.targetHealthCheckRecord.healthy && + this.env.targetHealthCheckRecord.syncState === HealthCheckSyncState.InSync + ) { + // change the election to done + this.env.primaryRecord.voteState = PrimaryRecordVoteState.Done; + // update the election result + // reference the new primary to the target vm + election.newPrimary = this.env.targetVm; + election.newPrimaryRecord = this.env.primaryRecord; + // need to save primary record, + // flag the action now, and handle it later, separately. + action = 'save'; + // do not need to redo election + redoElection = false; + // should reload the primary record + reloadPrimaryRecord = true; + } + // vm is unhealthy + else { + // need to delete the primary record, + // flag the action now, and handle it later, separately. + action = 'delete'; + // do not need to redo election + redoElection = false; + // should reload the primary record + reloadPrimaryRecord = true; + } + } + // the target vm isn't the pending primary + else { + // workflow: handling ends and returns election result + // do nothing in this case + // flag the action now, and handle it later, separately. + action = 'noop'; + // do not need to redo election + redoElection = false; + // should not reload primary record + reloadPrimaryRecord = false; + } + } + // vote state: timeout + else if (this.env.primaryRecord.voteState === PrimaryRecordVoteState.Timeout) { + // if primary election already timeout, redo the primary election + // workflow: if state is timeout -> delete primary record + // need to delete the primary record, + // flag the action now, and handle it later, separately. + action = 'delete'; + // should redo the primary election + redoElection = true; + // should reload the primary record + reloadPrimaryRecord = true; + } + // vote state: done + else if (this.env.primaryRecord.voteState === PrimaryRecordVoteState.Done) { + // workflow: check the health state of recorded primary vm + if (this.env.primaryVm) { + this.env.primaryHealthCheckRecord = await this.platform.getHealthCheckRecord( + this.env.primaryVm.id + ); + // is the primary responsive? + if ( + this.env.primaryHealthCheckRecord && + this.env.primaryHealthCheckRecord.irresponsivePeriod > 0 + ) { + this.env.primaryHealthCheckRecord.healthy = false; + this.env.primaryHealthCheckRecord.syncState = + HealthCheckSyncState.OutOfSync; + } + } + if ( + this.env.primaryHealthCheckRecord && + this.env.primaryHealthCheckRecord.syncState === HealthCheckSyncState.InSync + ) { + // primary vm is healthy + // workflow: handling ends and returns election result + // do nothing in this case + // flag the action now, and handle it later, separately. + action = 'noop'; + // do not need to redo election + redoElection = false; + // should not reload the primary record + reloadPrimaryRecord = false; + } + // otherwise, + else { + // primary mv is unhealthy + // workflow: if vm is healthy -- (false) -> delete primary record + // need to delete the primary record, + // flag the action now, and handle it later, separately. + action = 'delete'; + // should redo the primary election + redoElection = true; + // should reload the primary record + reloadPrimaryRecord = true; + } + } + } + + // dealing with updating the primary record + if (action === 'delete') { + // NOTE: providing the primary record data to put strict condition on the deletion + try { + this.proxy.logAsInfo( + 'Delete the current primary record: ', + JSON.stringify(this.env.primaryRecord) + ); + await this.platform.deletePrimaryRecord(this.env.primaryRecord); + } catch (error) { + // unable to delete but that is okay. no impact + this.proxy.logAsWarning( + 'Unable to delete. This message can be discarded. ' + `error: ${error}` + ); + } + } + // primary election need to redo? + if (redoElection) { + try { + this.proxy.logAsInfo('Primary election starting now.'); + // because it needs to redo the election, all those stale health check records + // should be removed. + await this.handleStaleVm(); + await this.primaryElectionStrategy.prepare(election); + // workflow: start a new primary election + const decision = await this.primaryElectionStrategy.apply(); + // get the election result. + election = await this.primaryElectionStrategy.result(); + // if new primary election started + // election will be avaialble: new primary vm and new primary record will not be null + this.env.primaryRecord = election.newPrimaryRecord; + this.env.primaryVm = election.newPrimary; + // only when primary record isn't null, it needs to save the primary record + if (election.newPrimary && election.newPrimaryRecord) { + this.proxy.logAsInfo( + 'Primary election strategy completed.' + + ` The new primary is: vmId: ${election.newPrimaryRecord.vmId},` + + ` ip: ${election.newPrimaryRecord.ip}.` + ); + // If the target VM is new elected primary, and is already in the monitor, + // can resolve the primary immediately + // otherwise, the primary election will be resolved when the elected primary + // state becomes in-service + if ( + this.platform.vmEquals(this.env.targetVm, election.newPrimary) && + this.env.targetHealthCheckRecord + ) { + election.newPrimaryRecord.voteEndTime = Date.now(); // election ends immediately + election.newPrimaryRecord.voteState = PrimaryRecordVoteState.Done; + } + // send notification + await this.sendAutoscaleNotifications( + this.env.targetVm, + 'An Autoscale primary election was just completed successfully.\n' + + `The new primary is: vmId: ${election.newPrimaryRecord.vmId},` + + ` ip: ${election.newPrimaryRecord.ip}.`, + 'Autoscale Primary Election Occurred (Sucess)' + ); + action = 'save'; + // should reload primary record + reloadPrimaryRecord = true; + } else { + // if primary election is needed but no primary can be elected, should send + // notifications to ask for manual observation or troubleshooting + if (decision === PrimaryElectionStrategyResult.CannotDeterminePrimary) { + this.proxy.logAsWarning( + 'Autoscale unable to determine the new primary device' + ); + await this.sendAutoscaleNotifications( + this.env.targetVm, + 'The Autoscale primary election strategy cannot automatically' + + ' determine the new primary device using the device information.' + + ' Manually configuring the primary device is needed.', + 'Autoscale unable to determine the new primary device' + ); + } + // NOTE: wait for the next round + else if (decision === PrimaryElectionStrategyResult.SkipAndContinue) { + // TODO: any action to take here? + this.proxy.logAsInfo( + 'Primary election strategy suggests that election' + + ' should skip this round and will restart in the next round.' + ); + } + // do not need to save the primary record + action = 'noop'; + // should not reload the primary record + reloadPrimaryRecord = false; + } + } catch (error) { + this.proxy.logForError('Primary election does not start. Error occurs.', error); + // do not need to save the primary record + action = 'noop'; + // election isn't needed so new primary should be null + election.newPrimary = null; + election.newPrimaryRecord = null; + // should not reload the primary record + reloadPrimaryRecord = false; + } + } else { + // election isn't needed so new primary should be null + election.newPrimary = null; + election.newPrimaryRecord = null; + } + + if (action === 'save') { + // CAUTION: there may be race conditions when updating the primary record + try { + this.proxy.logAsInfo( + 'Saving the primary record. ', + JSON.stringify(this.env.primaryRecord) + ); + // NOTE: this is an upsert operation + await this.platform.updatePrimaryRecord(this.env.primaryRecord); + // primary record is saved, need to reload it + reloadPrimaryRecord = true; + } catch (error) { + // primary record is not saved, need to reload it anyway + reloadPrimaryRecord = true; + this.proxy.logForError('Unable to save primary record. ', error); + } + } + + if (reloadPrimaryRecord) { + this.env.primaryRecord = await this.platform.getPrimaryRecord(); + this.env.primaryVm = await this.platform.getPrimaryVm(); + } + + this.proxy.logAsInfo('called handlePrimaryElection.'); + return election; + } + async handleStaleVm(): Promise { + const [activeVms, healthcheckRecords] = await Promise.all([ + this.platform.listAutoscaleVm(false, false), + this.platform.listHealthCheckRecord() + ]); + const activeVmIds = activeVms.map(vm => vm.id); + const activeHealthCheckRecords = healthcheckRecords.filter(rec => + activeVmIds.includes(rec.vmId) + ); + const staleHealthCheckRecords = healthcheckRecords.filter( + rec => !activeVmIds.includes(rec.vmId) + ); + // delete those stale healthcheck records + await Promise.all( + staleHealthCheckRecords.map(rec => { + this.proxy.logAsInfo( + `Deleting health check record of vm (id: ${rec.vmId}) ` + + 'that no longer exists.' + ); + return this.platform.deleteHealthCheckRecord(rec); + }) + ); + return activeHealthCheckRecords; + } + async handleUnhealthyVm(vms: VirtualMachine[]): Promise { + this.proxy.logAsInfo('calling handleUnhealthyVm.'); + // call the platform scaling group to terminate the vm in the list + const settings = await this.platform.getSettings(); + const terminateUnhealthyVmSettingItem = settings.get(AutoscaleSetting.TerminateUnhealthyVm); + const terminateUnhealthyVm = + terminateUnhealthyVmSettingItem && terminateUnhealthyVmSettingItem.truthValue; + const vmHandler = async (vm: VirtualMachine): Promise => { + this.proxy.logAsInfo(`handling unhealthy vm(id: ${vm.id})...`); + const subject = 'Autoscale unhealthy vm is detected'; + let message = + `Device (id: ${vm.id}, ip: ${vm.primaryPrivateIpAddress}) has` + + ' been deemed unhealthy and marked as out-of-sync by the Autoscale.\n\n'; + this.proxy.logAsWarning( + 'Termination of unhealthy vm is ' + + `${terminateUnhealthyVm ? 'enabled' : 'disabled'}.` + + ` vm (id: ${vm.id}) will ${terminateUnhealthyVm ? '' : 'not '}be deleted.` + ); + // if termination of unhealthy vm is set to true, terminate it + if (terminateUnhealthyVm) { + try { + await this.platform.deleteVmFromScalingGroup(vm.id); + // delete corresponding health check record + const healthcheckRecord = await this.platform.getHealthCheckRecord(vm.id); + if (healthcheckRecord) { + await this.platform.deleteHealthCheckRecord(healthcheckRecord); + } + try { + message += + 'Autoscale is now terminating this device.\n' + + 'Depending on the scaling policies, a replacement device may be created.' + + ' Further investigation for the cause of termination may be necessary.'; + this.sendAutoscaleNotifications(vm, message, subject); + this.proxy.logAsInfo(`handling vm (id: ${vm.id}) completed.`); + } catch (err) { + this.proxy.logForError('unable to send Autoscale notifications.', err); + } + } catch (error) { + this.proxy.logForError('handling unhealthy vm failed.', error); + } + } + // otherwise, send a warning for this unhealthy vm and keep it + else { + // get the health check record for the vm. + const healthcheckRecord = await this.platform.getHealthCheckRecord(vm.id); + try { + message += + ' This device is excluded from being candidate of primary device.\n' + + ` It requires (${healthcheckRecord.syncRecoveryCount})` + + ' on-time heartbeats to recover from out-of-sync state to in-sync state.\n' + + ' A full recovery will include this device into primary elections again.\n'; + this.sendAutoscaleNotifications(vm, message, subject); + } catch (err) { + this.proxy.logForError('unable to send Autoscale notifications.', err); + } + } + }; + await Promise.all(vms.map(vmHandler)); + this.proxy.logAsInfo('called handleUnhealthyVm.'); + } + async handleLicenseAssignment(productName: string): Promise { + this.proxy.logAsInfo('calling handleLicenseAssignment.'); + // load target vm + if (!this.env.targetVm) { + this.env.targetVm = await this.platform.getTargetVm(); + } + // if target vm doesn't exist, unknown request + if (!this.env.targetVm) { + const error = new Error(`Requested non - existing vm(id: ${this.env.targetId}).`); + this.proxy.logForError('', error); + throw error; + } + const settings = await this.platform.getSettings(); + // assume to use the custom asset container as the storage directory for license files. + const customAssetContainer = + (settings.get(AutoscaleSetting.CustomAssetContainer) && + settings.get(AutoscaleSetting.CustomAssetContainer).value) || + ''; + const customAssetDirectory = + (settings.get(AutoscaleSetting.CustomAssetDirectory) && + settings.get(AutoscaleSetting.CustomAssetDirectory).value) || + ''; + const defaultAssetContainer = + (settings.get(AutoscaleSetting.AssetStorageContainer) && + settings.get(AutoscaleSetting.AssetStorageContainer).value) || + ''; + const defaultAssetDirectory = + (settings.get(AutoscaleSetting.AssetStorageDirectory) && + settings.get(AutoscaleSetting.AssetStorageDirectory).value) || + ''; + const licenseFileDirectory = + (settings.get(AutoscaleSetting.LicenseFileDirectory) && + settings.get(AutoscaleSetting.LicenseFileDirectory).value) || + ''; + const assetContainer = customAssetContainer || defaultAssetContainer; + const assetDirectory = + (customAssetContainer && customAssetDirectory) || defaultAssetDirectory; + + const licenseDirectory: string = path.posix.join( + assetDirectory, + licenseFileDirectory, + productName + ); + this.licensingStrategy.prepare( + this.env.targetVm, + productName, + assetContainer, + licenseDirectory + ); + let result: LicensingStrategyResult; + let licenseContent = ''; + try { + result = await this.licensingStrategy.apply(); + } catch (e) { + this.proxy.logForError('Error in running licensing strategy.', e); + } + if (result === LicensingStrategyResult.LicenseAssigned) { + licenseContent = await this.licensingStrategy.getLicenseContent(); + } else if (result === LicensingStrategyResult.LicenseNotRequired) { + this.proxy.logAsInfo( + `license isn't required for this vm (id: ${this.env.targetVm.id})` + ); + } else if (result === LicensingStrategyResult.LicenseOutOfStock) { + const notificationSubject = 'FortiGate Autoscale license assignment error'; + const notificationMessage = + `FortiGate (id: ${this.env.targetVm.id}) cannot be assigned a license` + + ' because all available licenses have been allocated.' + + ' Please check the Autoscale handler function logs for more details.'; + await this.sendAutoscaleNotifications( + this.env.targetVm, + notificationMessage, + notificationSubject + ); + this.proxy.logAsError( + 'License out of stock. ' + + `No license is assigned to this vm (id: ${this.env.targetVm.id})` + ); + } + this.proxy.logAsInfo('called handleLicenseAssignment.'); + return licenseContent; + } + + async saveSettings( + input: { [key: string]: string }, + itemDict: SettingItemDictionary + ): Promise { + const errorTasks: string[] = []; + const unsupportedKeys: string[] = []; + const settingItemDefKey: string[] = Object.keys(itemDict); + const tasks = Object.entries(input).map(([settingKey, settingValue]) => { + const key = settingKey.toLowerCase(); + if (settingItemDefKey.includes(key)) { + const def = itemDict[key]; + let value = settingValue; + if (def.booleanType) { + value = (settingValue === 'true' && 'true') || 'false'; + } + + return this.platform + .saveSettingItem( + def.keyName, + value, + def.description, + def.jsonEncoded, + def.editable + ) + .then(() => true) + .catch(error => { + this.proxy.logForError(`failed to save setting for key: ${key}. `, error); + errorTasks.push(key); + return true; + }); + } else { + unsupportedKeys.push(key); + return Promise.resolve(true); + } + }); + + if (unsupportedKeys.length > 0) { + this.proxy.logAsWarning( + `Unsupported setting cannot be saved: ${unsupportedKeys.join(', ')}.` + ); + } + + await Promise.all(tasks); + return errorTasks.length === 0; + } + + onVmFullyConfigured(): Promise { + this.proxy.logAsInfo(`Vm (id: ${this.env.targetVm.id}) is fully configured.`); + return Promise.resolve(); + } + + async handleEgressTrafficRoute(): Promise { + this.proxy.logAsInfo('calling handleEgressTrafficRoute.'); + await this.routingEgressTrafficStrategy.apply(); + this.proxy.logAsInfo('called handleEgressTrafficRoute.'); + } + + sendAutoscaleNotifications( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + vm: VirtualMachine, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + message?: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + subject?: string + ): Promise { + this.proxy.logAsWarning('sendAutoscaleNotifications not implemented.'); + return Promise.resolve(); + } +} + +type FinderRef = { [key: string]: FinderRef } | [] | string | null; +export function configSetResourceFinder(resObject: FinderRef, nodePath: string): FinderRef { + const [, mPath] = nodePath.match(/^{(.+)}$/i); + if (!resObject || !nodePath) { + return ''; + } + const nodes = mPath.split('.'); + let ref = resObject; + + nodes.find(nodeName => { + const matches = nodeName.match(/^([A-Za-z_@-]+)#([0-9])+$/i); + if (matches && Array.isArray(ref[matches[1]]) && ref[matches[1]].length > matches[2]) { + ref = ref[matches[1]][matches[2]]; + } else if (!ref[nodeName]) { + ref = null; + return null; + } else { + ref = + Array.isArray(ref[nodeName]) && ref[nodeName].length > 0 + ? ref[nodeName][0] + : ref[nodeName]; + } + }); + return ref; +} diff --git a/core/autoscale-environment.ts b/core/autoscale-environment.ts new file mode 100644 index 0000000..1887cab --- /dev/null +++ b/core/autoscale-environment.ts @@ -0,0 +1,16 @@ +import { HealthCheckRecord, PrimaryRecord } from './primary-election'; +import { VirtualMachine } from './virtual-machine'; + +export interface AutoscaleEnvironment { + primaryId?: string; + primaryVm?: VirtualMachine; + primaryScalingGroup?: string; + primaryHealthCheckRecord?: HealthCheckRecord; + primaryRecord: PrimaryRecord; + primaryRoleChanged?: boolean; + targetId?: string; + targetVm?: VirtualMachine; + targetScalingGroup?: string; + targetHealthCheckRecord?: HealthCheckRecord; + [key: string]: unknown; +} diff --git a/core/autoscale-service-provider.ts b/core/autoscale-service-provider.ts new file mode 100644 index 0000000..0e6410c --- /dev/null +++ b/core/autoscale-service-provider.ts @@ -0,0 +1,19 @@ +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum AutoscaleServiceType { + SaveAutoscaleSettings = 'saveSettings', + StartAutoscale = 'startAutoscale', + StopAutoscale = 'stopAutoscale' +} +export interface AutoscaleServiceRequest { + source: string; + serviceType: string; + [key: string]: string; +} + +export interface AutoscaleServiceProvider { + handleServiceRequest(request: TReq): Promise; + startAutoscale(): Promise; + stopAutoscale(): Promise; + saveAutoscaleSettings(props: { [key: string]: string }): Promise; +} diff --git a/core/autoscale-setting.ts b/core/autoscale-setting.ts new file mode 100644 index 0000000..d1f70fa --- /dev/null +++ b/core/autoscale-setting.ts @@ -0,0 +1,441 @@ +import { JSONable } from './jsonable'; + +/** + * Enumerated value of SettingItem keys + * + * @export + * @enum {number} + */ +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum AutoscaleSetting { + AdditionalConfigSetNameList = 'additional-configset-name-list', + AutoscaleFunctionExtendExecution = 'autoscale-function-extend-execution', + AutoscaleFunctionMaxExecutionTime = 'autoscale-function-max-execution-time', + AutoscaleHandlerUrl = 'autoscale-handler-url', + AssetStorageContainer = 'asset-storage-name', + AssetStorageDirectory = 'asset-storage-key-prefix', + ByolScalingGroupDesiredCapacity = 'byol-scaling-group-desired-capacity', + ByolScalingGroupMinSize = 'byol-scaling-group-min-size', + ByolScalingGroupMaxSize = 'byol-scaling-group-max-size', + ByolScalingGroupName = 'byol-scaling-group-name', + CustomAssetContainer = 'custom-asset-container', + CustomAssetDirectory = 'custom-asset-directory', + EnableExternalElb = 'enable-external-elb', + EnableHybridLicensing = 'enable-hybrid-licensing', + EnableInternalElb = 'enable-internal-elb', + EnableNic2 = 'enable-second-nic', + EnableVmInfoCache = 'enable-vm-info-cache', + HeartbeatDelayAllowance = 'heartbeat-delay-allowance', + HeartbeatInterval = 'heartbeat-interval', + HeartbeatLossCount = 'heartbeat-loss-count', + LicenseFileDirectory = 'license-file-directory', + PrimaryElectionTimeout = 'primary-election-timeout', + PrimaryScalingGroupName = 'primary-scaling-group-name', + PaygScalingGroupDesiredCapacity = 'scaling-group-desired-capacity', + PaygScalingGroupMinSize = 'scaling-group-min-size', + PaygScalingGroupMaxSize = 'scaling-group-max-size', + PaygScalingGroupName = 'payg-scaling-group-name', + ResourceTagPrefix = 'resource-tag-prefix', + SyncRecoveryCount = 'sync-recovery-count', + TerminateUnhealthyVm = 'terminate-unhealthy-vm', + VmInfoCacheTime = 'vm-info-cache-time', + VpnBgpAsn = 'vpn-bgp-asn' +} + +export interface SettingItemDefinition { + keyName: string; + description: string; + editable: boolean; + jsonEncoded: boolean; + booleanType: boolean; +} + +export interface SubnetPair { + subnetId: string; + pairIdList: string[]; +} + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum SubnetPairIndex { + Service, + Management +} + +/** + * + * + * @export + * @class SettingItem + */ +export class SettingItem { + static NO_VALUE = 'n/a'; + /** + *Creates an instance of SettingItem. + * @param {string} key setting key + * @param {string} rawValue the value stored as string type, + * for actual type of : string, number, boolean, etc. + * @param {string} description description of this setting item + * @param {boolean} editable a flag for whether the value should be editable after deployment or not + * @param {string} jsonEncoded a flag for whether the value is a JSON object or not. + * If yes, can get the JSON object from + * calling the jsonValue of this setting item. + */ + constructor( + readonly key: string, + private readonly rawValue: string, + readonly description: string, + readonly editable: boolean, + readonly jsonEncoded: boolean + ) {} + /** + * the string type value of the setting. + * + * @readonly + * @type {string} + */ + get value(): string { + return this.rawValue.trim().toLowerCase() === SettingItem.NO_VALUE ? null : this.rawValue; + } + /** + * Returns the object type of this setting if it is a JSON object, + * or null if it isn't. + * + * @readonly + * @type {{}} + */ + get jsonValue(): JSONable { + if (this.jsonEncoded) { + try { + return JSON.parse(this.value); + } catch (error) { + return null; + } + } else { + return null; + } + } + /** + * Returns a truth value if the value of this setting is either a string 'true' or 'false'. + * It's handy to be used in boolean comparisons. + * + * @readonly + * @type {boolean} + */ + get truthValue(): boolean { + return this.value && this.value.trim().toLowerCase() === 'true'; + } + + /** + * stringify this SettingItem + * @returns {string} string + */ + stringify(): string { + return JSON.stringify({ + key: this.key, + value: this.rawValue, + description: this.description, + editable: this.editable, + jsonEncoded: this.jsonEncoded + }); + } + + /** + * parse a string as a SettingItem + * + * @static + * @param {string} s string to parse + * @returns {SettingItem} settingitem object + */ + static parse(s: string): SettingItem { + const o = JSON.parse(s); + const k = Object.keys(o); + if ( + !( + k.includes('key') && + k.includes('value') && + k.includes('description') && + k.includes('editable') && + k.includes('jsonEncoded') + ) + ) { + throw new Error( + `Unable to parse string (${s}) to SettingItem. Missing required properties.` + ); + } + return new SettingItem(o.key, o.value, o.description, o.editable, o.jsonEncoded); + } +} + +export type Settings = Map; + +export interface SettingItemReference { + [key: string]: string; +} + +export interface SettingItemDictionary { + [key: string]: SettingItemDefinition; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const AutoscaleSettingItemDictionary: SettingItemDictionary = { + [AutoscaleSetting.AdditionalConfigSetNameList]: { + keyName: AutoscaleSetting.AdditionalConfigSetNameList, + description: + 'The comma-separated list of the name of a configset. These configsets' + + ' are required dependencies for the Autoscale to work for a certain ' + + ' deployment. Can be left empty.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.AutoscaleFunctionExtendExecution]: { + keyName: AutoscaleSetting.AutoscaleFunctionExtendExecution, + description: + 'Allow one single Autoscale function to be executed in multiple extended invocations' + + ' of a cloud platform function if it cannot finish within one invocation and its' + + ' functionality supports splitting into extended invocations.', + editable: true, + jsonEncoded: false, + booleanType: true + }, + [AutoscaleSetting.AutoscaleFunctionMaxExecutionTime]: { + keyName: AutoscaleSetting.AutoscaleFunctionMaxExecutionTime, + description: + 'Maximum execution time (in second) allowed for an Autoscale Cloud Function that can' + + ' run in one cloud function invocation or multiple extended invocations.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.AutoscaleHandlerUrl]: { + keyName: AutoscaleSetting.AutoscaleHandlerUrl, + description: + 'The Autoscale handler (cloud function) URL as the communication endpoint between' + + 'Autoscale and device in the scaling group(s).', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.AssetStorageContainer]: { + keyName: AutoscaleSetting.AssetStorageContainer, + description: 'Asset storage name.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.AssetStorageDirectory]: { + keyName: AutoscaleSetting.AssetStorageDirectory, + description: 'Asset storage key prefix.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.ByolScalingGroupDesiredCapacity]: { + keyName: AutoscaleSetting.ByolScalingGroupDesiredCapacity, + description: 'BYOL Scaling group desired capacity.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.ByolScalingGroupMinSize]: { + keyName: AutoscaleSetting.ByolScalingGroupMinSize, + description: 'BYOL Scaling group min size.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.ByolScalingGroupMaxSize]: { + keyName: AutoscaleSetting.ByolScalingGroupMaxSize, + description: 'BYOL Scaling group max size.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.ByolScalingGroupName]: { + keyName: AutoscaleSetting.ByolScalingGroupName, + description: 'The name of the BYOL auto scaling group.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.CustomAssetContainer]: { + keyName: AutoscaleSetting.CustomAssetContainer, + description: + 'The asset storage name for some user custom resources, such as: custom configset, license files, etc.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.CustomAssetDirectory]: { + keyName: AutoscaleSetting.CustomAssetDirectory, + description: + 'The sub directory to the user custom resources under the custom-asset-container.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.EnableExternalElb]: { + keyName: AutoscaleSetting.EnableExternalElb, + description: + 'Toggle ON / OFF the external elastic load balancing for device in the external-facing Autoscale scaling group(s).', + editable: false, + jsonEncoded: false, + booleanType: true + }, + [AutoscaleSetting.EnableHybridLicensing]: { + keyName: AutoscaleSetting.EnableHybridLicensing, + description: 'Toggle ON / OFF the hybrid licensing feature.', + editable: false, + jsonEncoded: false, + booleanType: true + }, + [AutoscaleSetting.EnableInternalElb]: { + keyName: AutoscaleSetting.EnableInternalElb, + description: + 'Toggle ON / OFF the internal elastic load balancing feature to allow traffic flow out' + + ' the device in the Autoscale scaling groups(s) into an internal load balancer.', + editable: false, + jsonEncoded: false, + booleanType: true + }, + [AutoscaleSetting.EnableNic2]: { + keyName: AutoscaleSetting.EnableNic2, + description: + 'Toggle ON / OFF the secondary eni creation on each device in the Autoscale scaling group(s).', + editable: false, + jsonEncoded: false, + booleanType: true + }, + [AutoscaleSetting.EnableVmInfoCache]: { + keyName: AutoscaleSetting.EnableVmInfoCache, + description: + 'Toggle ON / OFF the vm info cache feature. It caches the ' + + 'vm info in db to reduce API calls to query a vm from the platform.', + editable: false, + jsonEncoded: false, + booleanType: true + }, + [AutoscaleSetting.HeartbeatDelayAllowance]: { + keyName: AutoscaleSetting.HeartbeatDelayAllowance, + description: + 'The maximum amount of time (in seconds) allowed for network latency of the Autoscale' + + ' device heartbeat arriving at the Autoscale handler.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.HeartbeatInterval]: { + keyName: AutoscaleSetting.HeartbeatInterval, + description: + 'The length of time (in seconds) that an Autoscale device waits between' + + ' sending heartbeat requests to the Autoscale handler.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.HeartbeatLossCount]: { + keyName: AutoscaleSetting.HeartbeatLossCount, + description: + 'Number of consecutively lost heartbeats.' + + ' When the Heartbeat Loss Count has been reached,' + + ' the device is deemed unhealthy and fail-over activities will commence.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.LicenseFileDirectory]: { + keyName: AutoscaleSetting.LicenseFileDirectory, + description: 'The sub directory for storing license files under the asset container.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.PrimaryElectionTimeout]: { + keyName: AutoscaleSetting.PrimaryElectionTimeout, + description: 'The maximum time (in seconds) to wait for a primary election to complete.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.PrimaryScalingGroupName]: { + keyName: AutoscaleSetting.PrimaryScalingGroupName, + description: 'The name of the primary auto scaling group.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.PaygScalingGroupDesiredCapacity]: { + keyName: AutoscaleSetting.PaygScalingGroupDesiredCapacity, + description: 'PAYG Scaling group desired capacity.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.PaygScalingGroupMinSize]: { + keyName: AutoscaleSetting.PaygScalingGroupMinSize, + description: 'PAYG Scaling group min size.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.PaygScalingGroupMaxSize]: { + keyName: AutoscaleSetting.PaygScalingGroupMaxSize, + description: 'PAYG Scaling group max size.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.PaygScalingGroupName]: { + keyName: AutoscaleSetting.PaygScalingGroupName, + description: 'The name of the PAYG auto scaling group.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.ResourceTagPrefix]: { + keyName: AutoscaleSetting.ResourceTagPrefix, + description: + 'Resource tag prefix. Used on any resource that supports tagging or labeling.' + + ' Such resource will be given a tag or label starting with this prefix.' + + ' Also used as the name of the logical group for Autoscale resources' + + ' in those cloud platforms which support such logical grouping.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.SyncRecoveryCount]: { + keyName: AutoscaleSetting.SyncRecoveryCount, + description: + 'The number (positive integer) of on-time heartbeat for a vm needs to send to ' + + ' recover from the unhealthy state. Unhealthy vm will be excluded from being' + + ' candidate of primary elections.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.TerminateUnhealthyVm]: { + keyName: AutoscaleSetting.TerminateUnhealthyVm, + description: + 'Toggle for unhealthy vm handling behaviours. Set to true to terminate unhealthy vm' + + ' or set to false to keep the unhealthy vm.', + editable: true, + jsonEncoded: false, + booleanType: true + }, + [AutoscaleSetting.VpnBgpAsn]: { + keyName: AutoscaleSetting.VpnBgpAsn, + description: 'The BGP Autonomous System Number used with the VPN connections.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [AutoscaleSetting.VmInfoCacheTime]: { + keyName: AutoscaleSetting.VmInfoCacheTime, + description: 'The vm info cache time in seconds.', + editable: true, + jsonEncoded: false, + booleanType: false + } +}; diff --git a/core/azure/azure-cloud-function-proxy.ts b/core/azure/azure-cloud-function-proxy.ts new file mode 100644 index 0000000..33d8638 --- /dev/null +++ b/core/azure/azure-cloud-function-proxy.ts @@ -0,0 +1,190 @@ +import { Context, HttpRequest } from '@azure/functions'; +import { + AutoscaleServiceRequest, + CloudFunctionProxy, + CloudFunctionResponseBody, + JSONable, + jsonStringifyReplacer, + LogLevel, + mapHttpMethod, + ReqHeaders, + ReqMethod +} from '..'; + +export interface AzureFunctionResponse { + status: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + body: any; +} + +export interface LogItem { + level: LogLevel; + timestamp: number; + arguments?: unknown[]; +} + +export abstract class AzureCloudFunctionProxy extends CloudFunctionProxy< + TReq, + Context, + AzureFunctionResponse +> { + request: TReq; + context: Context; + private messageQueue: LogItem[] = []; + log(message: string, level: LogLevel, ...others: unknown[]): void { + if (process.env.DEBUG_LOGGER_OUTPUT_QUEUE_ENABLED === 'true') { + this.enqueue(message, level, ...others); + return; + } + switch (level) { + case LogLevel.Debug: + this.context.log(message, ...others); + break; + case LogLevel.Error: + this.context.log.error(message, ...others); + break; + case LogLevel.Info: + this.context.log.info(message, ...others); + break; + case LogLevel.Warn: + this.context.log.warn(message, ...others); + break; + default: + this.context.log.error(message, ...others); + } + } + + getRemainingExecutionTime(): Promise { + throw new Error( + 'Not supposed to call the AzureFunctionInvocationProxy.getRemainingExecutionTime()' + + ' method in this implementation.' + + ' Is it just a mistake?' + ); + } + + /** + * return a formatted AWS Lambda handler response + * @param {number} httpStatusCode http status code + * @param {CloudFunctionResponseBody} body response body + * @param {{}} headers response header + * @returns {AzureFunctionResponse} function response + */ + formatResponse( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + httpStatusCode: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + body: CloudFunctionResponseBody, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + headers: unknown + ): AzureFunctionResponse { + // NOTE: if enable queued log output, output log here + if (process.env.DEBUG_LOGGER_OUTPUT_QUEUE_ENABLED === 'true') { + const messages: unknown[] = []; + this.allLogs.forEach(log => { + messages.push(`[${log.level}]`); + messages.push(...log.arguments); + messages.push('\n'); + }); + this.context.log(...messages); + } + return null; + } + + protected enqueue(message: string, level: LogLevel, ...args: unknown[]): void { + const item: LogItem = { + level: level, + timestamp: Date.now() + new Date().getTimezoneOffset() * 60000, // GMT time in ms + arguments: [] + }; + item.arguments = Array.from(args).map(arg => { + return JSON.stringify(arg, jsonStringifyReplacer); + }); + item.arguments.unshift(message); + this.messageQueue.push(item); + } + + get allLogs(): LogItem[] { + return this.messageQueue; + } +} + +export class AzureFunctionServiceProviderProxy extends AzureCloudFunctionProxy { + getRequestAsString(): Promise { + return Promise.resolve((this.request && JSON.stringify(this.request)) || ''); + } + getReqBody(): Promise { + return Promise.resolve(this.request || {}); + } + getReqHeaders(): Promise { + return Promise.resolve({}); + } + getReqMethod(): Promise { + return Promise.resolve(null); + } + getReqQueryParameters(): Promise<{ [name: string]: string }> { + return Promise.resolve({}); + } +} + +export class AzureFunctionHttpTriggerProxy extends AzureCloudFunctionProxy { + getReqBody(): Promise { + try { + if (this.context.req.body && typeof this.context.req.body === 'string') { + return JSON.parse(this.context.req.body as string); + } else if (this.context.req.body && typeof this.context.req.body === 'object') { + return Promise.resolve({ ...this.context.req.body }); + } else { + return null; + } + } catch (error) { + return null; + } + } + getReqQueryParameters(): Promise<{ [name: string]: string }> { + return Promise.resolve(this.context.req.params); + } + getRequestAsString(): Promise { + return Promise.resolve(this.context.req && JSON.stringify(this.context.req)); + } + getReqHeaders(): Promise { + // NOTE: header keys will be treated case-insensitive as per + // the RFC https://tools.ietf.org/html/rfc7540#section-8.1.2 + const headers: ReqHeaders = (this.context.req.headers && {}) || null; + if (this.context.req.headers) { + Object.entries(this.context.req.headers).forEach(([k, v]) => { + headers[String(k).toLowerCase()] = v; + }); + } + return Promise.resolve(headers); + } + + getReqMethod(): Promise { + return Promise.resolve(mapHttpMethod(this.context.req.method)); + } + + /** + * return a formatted AWS Lambda handler response + * @param {number} httpStatusCode http status code + * @param {CloudFunctionResponseBody} body response body + * @param {{}} headers response header + * @returns {AzureFunctionResponse} function response + */ + formatResponse( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + httpStatusCode: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + body: CloudFunctionResponseBody, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + headers: unknown + ): AzureFunctionResponse { + super.formatResponse(httpStatusCode, body, headers); + return { + status: httpStatusCode, + body: body + }; + } +} + +export type AzureFunctionInvocationProxy = + | AzureFunctionHttpTriggerProxy + | AzureFunctionServiceProviderProxy; diff --git a/core/azure/azure-db-definitions.ts b/core/azure/azure-db-definitions.ts new file mode 100644 index 0000000..cdd3f9b --- /dev/null +++ b/core/azure/azure-db-definitions.ts @@ -0,0 +1,644 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import * as DBDef from '../db-definitions'; + +// NOTE: Azure Cosmos DB Data modeling concepts +// see: https://docs.microsoft.com/en-us/azure/cosmos-db/modeling-data +// Cosmos DB is a schema-free type of database so the data type definitions have no effect on +// items. +// The types here are still given just for good readabilty. +export const AzureTypeRefs: DBDef.TypeRefMap = new Map([ + [DBDef.TypeRef.StringType, 'string'], + [DBDef.TypeRef.NumberType, 'number'], + [DBDef.TypeRef.BooleanType, 'boolean'], + [DBDef.TypeRef.PrimaryKey, 'hash'], + [DBDef.TypeRef.SecondaryKey, 'range'] +]); + +export interface CosmosDBQueryWhereClause { + name: string; + value: string; +} + +export interface CosmosDBQueryResult { + result: T[]; + query?: string; +} + +// CosmosDB table has some useful meta properties added to each item +// they are defined here below +export interface CosmosDbTableMetaData { + id: string; + _rid: string; + _self: string; + _etag: string; + _attachments: string; + _ts: number; + [key: string]: string | number | boolean; +} + +export const CosmosDbTableMetaDataAttributes = [ + { + name: 'id', + attrType: DBDef.TypeRef.StringType, + isKey: false + }, + { + name: '_attachments', + attrType: DBDef.TypeRef.StringType, + isKey: false + }, + { + name: '_etag', + attrType: DBDef.TypeRef.StringType, + isKey: false + }, + { + name: '_rid', + attrType: DBDef.TypeRef.StringType, + isKey: false + }, + { + name: '_self', + attrType: DBDef.TypeRef.StringType, + isKey: false + }, + { + name: '_ts', + attrType: DBDef.TypeRef.NumberType, + isKey: false + } +]; + +export class CosmosDBTypeConverter extends DBDef.TypeConverter { + valueToString(value: unknown): string { + return value as string; + } + valueToNumber(value: unknown): number { + return Number(value as string); + } + valueToBoolean(value: unknown): boolean { + return !!value; + } +} + +export interface AzureAutoscaleDbItem extends DBDef.AutoscaleDbItem, CosmosDbTableMetaData {} + +export class AzureAutoscale + extends DBDef.Autoscale + implements DBDef.BidirectionalCastable +{ + constructor(namePrefix = '', nameSuffix = '') { + super(new CosmosDBTypeConverter(), namePrefix, nameSuffix); + // NOTE: use AWS DynamoDB type refs + this.alterAttributesUsingTypeReference(AzureTypeRefs); + } + /** + * @override override to provide additional meta data + */ + convertRecord(record: DBDef.Record): AzureAutoscaleDbItem { + const item: AzureAutoscaleDbItem = { + ...super.convertRecord(record), + id: this.typeConvert.valueToString(record.id), + _attachments: this.typeConvert.valueToString(record._attachments), + _etag: this.typeConvert.valueToString(record._etag), + _rid: this.typeConvert.valueToString(record._rid), + _self: this.typeConvert.valueToString(record._self), + _ts: this.typeConvert.valueToNumber(record._ts) + }; + return item; + } + + downcast(record: DBDef.AutoscaleDbItem): AzureAutoscaleDbItem { + const item: AzureAutoscaleDbItem = { + ...record, + // NOTE: id will be automatically use the primary key value + // if the record already has property 'id', the following assignmet will overwrite + // the id value. + id: String(record[this.primaryKey.name]), + _attachments: undefined, + _etag: undefined, + _rid: undefined, + _self: undefined, + _ts: undefined + }; + return item; + } + + upcast(record: AzureAutoscaleDbItem): DBDef.AutoscaleDbItem { + const item: AzureAutoscaleDbItem = { + ...record + }; + delete item._attachments; + delete item._etag; + delete item._rid; + delete item._self; + delete item._ts; + // delete id only if id is not the primary key + if (this.primaryKey.name !== 'id') { + delete item.id; + } + return { ...item }; + } +} + +export interface AzurePrimaryElectionDbItem + extends DBDef.PrimaryElectionDbItem, + CosmosDbTableMetaData {} +export class AzurePrimaryElection + extends DBDef.PrimaryElection + implements DBDef.BidirectionalCastable +{ + constructor(namePrefix = '', nameSuffix = '') { + super(new CosmosDBTypeConverter(), namePrefix, nameSuffix); + // NOTE: use AWS DynamoDB type refs + this.alterAttributesUsingTypeReference(AzureTypeRefs); + } + + /** + * @override override to provide additional meta data + */ + convertRecord(record: DBDef.Record): AzurePrimaryElectionDbItem { + const item: AzurePrimaryElectionDbItem = { + ...super.convertRecord(record), + _attachments: this.typeConvert.valueToString(record._attachments), + _etag: this.typeConvert.valueToString(record._etag), + _rid: this.typeConvert.valueToString(record._rid), + _self: this.typeConvert.valueToString(record._self), + _ts: this.typeConvert.valueToNumber(record._ts) + }; + return item; + } + + downcast(record: DBDef.PrimaryElectionDbItem): AzurePrimaryElectionDbItem { + const item: AzurePrimaryElectionDbItem = { + ...record, + // NOTE: id will be automatically use the primary key value + // if the record already has property 'id', the following assignmet will overwrite + // the id value. + id: String(record[this.primaryKey.name]), + _attachments: undefined, + _etag: undefined, + _rid: undefined, + _self: undefined, + _ts: undefined + }; + return item; + } + + upcast(record: AzurePrimaryElectionDbItem): DBDef.PrimaryElectionDbItem { + const item: AzurePrimaryElectionDbItem = { + ...record + }; + delete item._attachments; + delete item._etag; + delete item._rid; + delete item._self; + delete item._ts; + // delete id only if id is not the primary key + if (this.primaryKey.name !== 'id') { + delete item.id; + } + return { ...item }; + } +} + +export interface AzureFortiAnalyzerDbItem + extends DBDef.FortiAnalyzerDbItem, + CosmosDbTableMetaData {} + +export class AzureFortiAnalyzer + extends DBDef.FortiAnalyzer + implements DBDef.BidirectionalCastable +{ + constructor(namePrefix = '', nameSuffix = '') { + super(new CosmosDBTypeConverter(), namePrefix, nameSuffix); + // NOTE: use AWS DynamoDB type refs + this.alterAttributesUsingTypeReference(AzureTypeRefs); + } + + /** + * @override override to provide additional meta data + */ + convertRecord(record: DBDef.Record): AzureFortiAnalyzerDbItem { + const item: AzureFortiAnalyzerDbItem = { + ...super.convertRecord(record), + id: this.typeConvert.valueToString(record.id), + _attachments: this.typeConvert.valueToString(record._attachments), + _etag: this.typeConvert.valueToString(record._etag), + _rid: this.typeConvert.valueToString(record._rid), + _self: this.typeConvert.valueToString(record._self), + _ts: this.typeConvert.valueToNumber(record._ts) + }; + return item; + } + + downcast(record: DBDef.FortiAnalyzerDbItem): AzureFortiAnalyzerDbItem { + const item: AzureFortiAnalyzerDbItem = { + ...record, + // NOTE: id will be automatically use the primary key value + // if the record already has property 'id', the following assignmet will overwrite + // the id value. + id: String(record[this.primaryKey.name]), + _attachments: undefined, + _etag: undefined, + _rid: undefined, + _self: undefined, + _ts: undefined + }; + return item; + } + + upcast(record: AzureFortiAnalyzerDbItem): DBDef.FortiAnalyzerDbItem { + const item: AzureFortiAnalyzerDbItem = { + ...record + }; + delete item._attachments; + delete item._etag; + delete item._rid; + delete item._self; + delete item._ts; + // delete id only if id is not the primary key + if (this.primaryKey.name !== 'id') { + delete item.id; + } + return { ...item }; + } +} + +export interface AzureSettingsDbItem extends DBDef.SettingsDbItem, CosmosDbTableMetaData {} + +export class AzureSettings + extends DBDef.Settings + implements DBDef.BidirectionalCastable +{ + constructor(namePrefix = '', nameSuffix = '') { + super(new CosmosDBTypeConverter(), namePrefix, nameSuffix); + // NOTE: use AWS DynamoDB type refs + this.alterAttributesUsingTypeReference(AzureTypeRefs); + } + + /** + * @override override to provide additional meta data + */ + convertRecord(record: DBDef.Record): AzureSettingsDbItem { + const item: AzureSettingsDbItem = { + ...super.convertRecord(record), + id: this.typeConvert.valueToString(record.id), + _attachments: this.typeConvert.valueToString(record._attachments), + _etag: this.typeConvert.valueToString(record._etag), + _rid: this.typeConvert.valueToString(record._rid), + _self: this.typeConvert.valueToString(record._self), + _ts: this.typeConvert.valueToNumber(record._ts) + }; + return item; + } + + downcast(record: DBDef.SettingsDbItem): AzureSettingsDbItem { + const item: AzureSettingsDbItem = { + ...record, + // NOTE: id will be automatically use the primary key value + // if the record already has property 'id', the following assignmet will overwrite + // the id value. + id: String(record[this.primaryKey.name]), + _attachments: undefined, + _etag: undefined, + _rid: undefined, + _self: undefined, + _ts: undefined + }; + return item; + } + + upcast(record: AzureSettingsDbItem): DBDef.SettingsDbItem { + const item: AzureSettingsDbItem = { + ...record + }; + delete item._attachments; + delete item._etag; + delete item._rid; + delete item._self; + delete item._ts; + // delete id only if id is not the primary key + if (this.primaryKey.name !== 'id') { + delete item.id; + } + return { ...item }; + } +} + +export interface AzureVmInfoCacheDbItem extends DBDef.VmInfoCacheDbItem, CosmosDbTableMetaData {} + +export class AzureVmInfoCache + extends DBDef.VmInfoCache + implements DBDef.BidirectionalCastable +{ + constructor(namePrefix = '', nameSuffix = '') { + super(new CosmosDBTypeConverter(), namePrefix, nameSuffix); + // NOTE: use AWS DynamoDB type refs + this.alterAttributesUsingTypeReference(AzureTypeRefs); + } + /** + * @override override to provide additional meta data + */ + convertRecord(record: DBDef.Record): AzureVmInfoCacheDbItem { + const item: AzureVmInfoCacheDbItem = { + ...super.convertRecord(record), + _attachments: this.typeConvert.valueToString(record._attachments), + _etag: this.typeConvert.valueToString(record._etag), + _rid: this.typeConvert.valueToString(record._rid), + _self: this.typeConvert.valueToString(record._self), + _ts: this.typeConvert.valueToNumber(record._ts) + }; + return item; + } + + downcast(record: DBDef.VmInfoCacheDbItem): AzureVmInfoCacheDbItem { + const item: AzureVmInfoCacheDbItem = { + ...record, + // NOTE: id will be automatically use the primary key value + // if the record already has property 'id', the following assignmet will overwrite + // the id value. + id: String(record[this.primaryKey.name]), + _attachments: undefined, + _etag: undefined, + _rid: undefined, + _self: undefined, + _ts: undefined + }; + return item; + } + + upcast(record: AzureVmInfoCacheDbItem): DBDef.VmInfoCacheDbItem { + const item: AzureVmInfoCacheDbItem = { + ...record + }; + delete item._attachments; + delete item._etag; + delete item._rid; + delete item._self; + delete item._ts; + // delete id only if id is not the primary key + if (this.primaryKey.name !== 'id') { + delete item.id; + } + return { ...item }; + } +} + +export interface AzureLicenseStockDbItem extends DBDef.LicenseStockDbItem, CosmosDbTableMetaData {} + +export class AzureLicenseStock + extends DBDef.LicenseStock + implements DBDef.BidirectionalCastable +{ + constructor(namePrefix = '', nameSuffix = '') { + super(new CosmosDBTypeConverter(), namePrefix, nameSuffix); + // NOTE: use AWS DynamoDB type refs + this.alterAttributesUsingTypeReference(AzureTypeRefs); + } + /** + * @override override to provide additional meta data + */ + convertRecord(record: DBDef.Record): AzureLicenseStockDbItem { + const item: AzureLicenseStockDbItem = { + ...super.convertRecord(record), + id: this.typeConvert.valueToString(record.id), + _attachments: this.typeConvert.valueToString(record._attachments), + _etag: this.typeConvert.valueToString(record._etag), + _rid: this.typeConvert.valueToString(record._rid), + _self: this.typeConvert.valueToString(record._self), + _ts: this.typeConvert.valueToNumber(record._ts) + }; + return item; + } + + downcast(record: DBDef.LicenseStockDbItem): AzureLicenseStockDbItem { + const item: AzureLicenseStockDbItem = { + ...record, + // NOTE: id will be automatically use the primary key value + // if the record already has property 'id', the following assignmet will overwrite + // the id value. + id: String(record[this.primaryKey.name]), + _attachments: undefined, + _etag: undefined, + _rid: undefined, + _self: undefined, + _ts: undefined + }; + return item; + } + + upcast(record: AzureLicenseStockDbItem): DBDef.LicenseStockDbItem { + const item: AzureLicenseStockDbItem = { + ...record + }; + delete item._attachments; + delete item._etag; + delete item._rid; + delete item._self; + delete item._ts; + // delete id only if id is not the primary key + if (this.primaryKey.name !== 'id') { + delete item.id; + } + return { ...item }; + } +} + +export interface AzureLicenseUsageDbItem extends DBDef.LicenseUsageDbItem, CosmosDbTableMetaData {} + +export class AzureLicenseUsage + extends DBDef.LicenseUsage + implements DBDef.BidirectionalCastable +{ + constructor(namePrefix = '', nameSuffix = '') { + super(new CosmosDBTypeConverter(), namePrefix, nameSuffix); + // NOTE: use AWS DynamoDB type refs + this.alterAttributesUsingTypeReference(AzureTypeRefs); + } + /** + * @override override to provide additional meta data + */ + convertRecord(record: DBDef.Record): AzureLicenseUsageDbItem { + const item: AzureLicenseUsageDbItem = { + ...super.convertRecord(record), + id: this.typeConvert.valueToString(record.id), + _attachments: this.typeConvert.valueToString(record._attachments), + _etag: this.typeConvert.valueToString(record._etag), + _rid: this.typeConvert.valueToString(record._rid), + _self: this.typeConvert.valueToString(record._self), + _ts: this.typeConvert.valueToNumber(record._ts) + }; + return item; + } + + downcast(record: DBDef.LicenseUsageDbItem): AzureLicenseUsageDbItem { + const item: AzureLicenseUsageDbItem = { + ...record, + // NOTE: id will be automatically use the primary key value + // if the record already has property 'id', the following assignmet will overwrite + // the id value. + id: String(record[this.primaryKey.name]), + _attachments: undefined, + _etag: undefined, + _rid: undefined, + _self: undefined, + _ts: undefined + }; + return item; + } + + upcast(record: AzureLicenseUsageDbItem): DBDef.LicenseUsageDbItem { + const item: AzureLicenseUsageDbItem = { + ...record + }; + delete item._attachments; + delete item._etag; + delete item._rid; + delete item._self; + delete item._ts; + // delete id only if id is not the primary key + if (this.primaryKey.name !== 'id') { + delete item.id; + } + return { ...item }; + } +} + +export interface AzureCustomLogDbItem extends DBDef.CustomLogDbItem, CosmosDbTableMetaData {} + +export class AzureCustomLog + extends DBDef.CustomLog + implements DBDef.BidirectionalCastable +{ + constructor(namePrefix = '', nameSuffix = '') { + super(new CosmosDBTypeConverter(), namePrefix, nameSuffix); + // NOTE: use AWS DynamoDB type refs + this.alterAttributesUsingTypeReference(AzureTypeRefs); + } + /** + * @override override to provide additional meta data + */ + convertRecord(record: DBDef.Record): AzureCustomLogDbItem { + const item: AzureCustomLogDbItem = { + ...super.convertRecord(record), + _attachments: this.typeConvert.valueToString(record._attachments), + _etag: this.typeConvert.valueToString(record._etag), + _rid: this.typeConvert.valueToString(record._rid), + _self: this.typeConvert.valueToString(record._self), + _ts: this.typeConvert.valueToNumber(record._ts) + }; + return item; + } + + downcast(record: DBDef.CustomLogDbItem): AzureCustomLogDbItem { + const item: AzureCustomLogDbItem = { + ...record, + // NOTE: id will be automatically use the primary key value + // if the record already has property 'id', the following assignmet will overwrite + // the id value. + id: String(record[this.primaryKey.name]), + _attachments: undefined, + _etag: undefined, + _rid: undefined, + _self: undefined, + _ts: undefined + }; + return item; + } + + upcast(record: AzureCustomLogDbItem): DBDef.CustomLogDbItem { + const item: AzureCustomLogDbItem = { + ...record + }; + delete item._attachments; + delete item._etag; + delete item._rid; + delete item._self; + delete item._ts; + // delete id only if id is not the primary key + if (this.primaryKey.name !== 'id') { + delete item.id; + } + return { ...item }; + } +} + +export interface AzureApiRequestCacheDbItem + extends DBDef.ApiRequestCacheDbItem, + CosmosDbTableMetaData {} + +export class AzureApiRequestCache + extends DBDef.Table + implements DBDef.BidirectionalCastable +{ + static ownStaticAttributes: DBDef.Attribute[] = [ + ...CosmosDbTableMetaDataAttributes, // NOTE: add addtional Azure CosmosDB table meta data attributes + // NOTE: use the same attributes of a sibling class, attributes with the same key will + // those in ...CosmosDbTableMetaDataAttributes + ...DBDef.ApiRequestCache.ownStaticAttributes + ]; + private siblingClass: DBDef.ApiRequestCache; + constructor(namePrefix = '', nameSuffix = '') { + const converter = new CosmosDBTypeConverter(); + super(converter, namePrefix, nameSuffix); + // NOTE: set the sibling class reference + this.siblingClass = new DBDef.ApiRequestCache(converter, namePrefix, nameSuffix); + // NOTE: use Azure CosmosDB type refs + this.alterAttributesUsingTypeReference(AzureTypeRefs); + // CAUTION: don't forget to set a correct name. + this.setName(this.siblingClass.name); + // CAUTION: don't forget to add attributes + AzureApiRequestCache.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + /** + * @override override to provide additional meta data + */ + convertRecord(record: DBDef.Record): AzureApiRequestCacheDbItem { + const item: AzureApiRequestCacheDbItem = { + ...this.siblingClass.convertRecord(record), + _attachments: this.typeConvert.valueToString(record._attachments), + _etag: this.typeConvert.valueToString(record._etag), + _rid: this.typeConvert.valueToString(record._rid), + _self: this.typeConvert.valueToString(record._self), + _ts: this.typeConvert.valueToNumber(record._ts) + }; + // NOTE: the cacheTime property will use the value of _ts + item.cacheTime = item._ts; + return item; + } + + downcast(record: DBDef.ApiRequestCacheDbItem): AzureApiRequestCacheDbItem { + const item: AzureApiRequestCacheDbItem = { + ...record, + // NOTE: id will be automatically use the primary key value + // if the record already has property 'id', the following assignmet will overwrite + // the id value. + id: String(record[this.primaryKey.name]), + _attachments: undefined, + _etag: undefined, + _rid: undefined, + _self: undefined, + _ts: undefined + }; + return item; + } + + upcast(record: AzureApiRequestCacheDbItem): DBDef.ApiRequestCacheDbItem { + const item: AzureApiRequestCacheDbItem = { + ...record + }; + delete item._attachments; + delete item._etag; + delete item._rid; + delete item._self; + delete item._ts; + // delete id only if id is not the primary key + if (this.primaryKey.name !== 'id') { + delete item.id; + } + return { ...item }; + } +} diff --git a/core/azure/azure-fortianalyzer-integration-service.ts b/core/azure/azure-fortianalyzer-integration-service.ts new file mode 100644 index 0000000..cc768be --- /dev/null +++ b/core/azure/azure-fortianalyzer-integration-service.ts @@ -0,0 +1,96 @@ +import { Context } from '@azure/functions'; +import { AutoscaleServiceProvider, AutoscaleServiceRequest, JSONable, ReqType } from '..'; +import { + FortiGateAutoscaleServiceRequestSource, + FortiGateAutoscaleServiceType +} from '../fortigate-autoscale'; +import { + AzureFortiGateAutoscale, + AzureFunctionDef, + AzureFunctionServiceProviderProxy, + AzurePlatformAdapter +} from '.'; + +export class AzureFortiGateAutoscaleServiceProvider + implements AutoscaleServiceProvider +{ + constructor(readonly autoscale: AzureFortiGateAutoscale) { + this.autoscale = autoscale; + } + startAutoscale(): Promise { + this.autoscale.proxy.logAsWarning('[startAutoscale] Method not implemented.'); + return Promise.resolve(true); + } + stopAutoscale(): Promise { + this.autoscale.proxy.logAsWarning('[stopAutoscale] Method not implemented.'); + return Promise.resolve(true); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + saveAutoscaleSettings(props: { [key: string]: string }): Promise { + this.autoscale.proxy.logAsWarning('[SaveAutoscaleSettings] Method not implemented.'); + return Promise.resolve(true); + } + get proxy(): AzureFunctionServiceProviderProxy { + return this.autoscale.proxy as AzureFunctionServiceProviderProxy; + } + get platform(): AzurePlatformAdapter { + return this.autoscale.platform; + } + async handleServiceRequest(request: AutoscaleServiceRequest): Promise { + this.proxy.logAsInfo('calling handleServiceRequest'); + try { + // Verify the incoming request. + // request url must be contained in the defined function name array: serviceFuncUrl + const allowedServiceEndpointsList: string[] = [AzureFunctionDef.FazAuthScheduler.name]; + const functionName = this.proxy.context.executionContext.functionName; + if (!allowedServiceEndpointsList.includes(functionName)) { + this.proxy.logAsWarning( + 'Unauthorized source url.', + `request function name: ${JSON.stringify(functionName)}`, + `request: ${request}` + ); + this.proxy.logAsInfo('called handleServiceRequest'); + } + // req type must be ReqType.ServiceProviderRequest + const reqType: ReqType = await this.platform.getRequestType(); + if (reqType !== ReqType.ServiceProviderRequest) { + this.proxy.logAsWarning( + 'Invalid service provider request.', + `request type: ${reqType}`, + `request: ${request}` + ); + this.proxy.logAsInfo('called handleServiceRequest'); + return; + } + // request body must contain key: 'source' with value: 'fortinet.autoscale' + if (request.source !== FortiGateAutoscaleServiceRequestSource.FortiGateAutoscale) { + this.proxy.logAsWarning( + 'Invalid service provider source.', + `request source: ${request.source}`, + `request: ${request}` + ); + this.proxy.logAsInfo('called handleServiceRequest'); + } + // service type must be present in request + if (!request.serviceType) { + this.proxy.logAsWarning( + 'Invalid service provider request type.', + `request source: ${request.serviceType}`, + `request: ${request}` + ); + this.proxy.logAsInfo('called handleServiceRequest'); + } + switch (request.serviceType) { + case FortiGateAutoscaleServiceType.TriggerFazDeviceAuth: + await this.autoscale.init(); + await this.autoscale.triggerFazDeviceAuth(); + break; + default: + throw new Error(`Unsupported service type: [${request.serviceType}]`); + } + } catch (error) { + this.proxy.logForError('Handle service request error.', error); + this.proxy.logAsInfo('called handleServiceRequest'); + } + } +} diff --git a/core/azure/azure-fortigate-autoscale-settings.ts b/core/azure/azure-fortigate-autoscale-settings.ts new file mode 100644 index 0000000..1fe50e1 --- /dev/null +++ b/core/azure/azure-fortigate-autoscale-settings.ts @@ -0,0 +1,23 @@ +import { SettingItemDictionary, SettingItemReference } from '..'; +import { + FortiGateAutoscaleSetting, + FortiGateAutoscaleSettingItemDictionary +} from '../fortigate-autoscale'; +// NOTE: every key must start with 'Azure' prefix but the value do not need the prefix +// eslint-disable-next-line @typescript-eslint/naming-convention +export const AzureFortiGateAutoscaleSetting: SettingItemReference = { + ...FortiGateAutoscaleSetting, + AzureFortiGateAutoscaleSettingSaved: 'fortigate-autoscale-setting-saved' +}; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const AzureFortiGateAutoscaleSettingItemDictionary: SettingItemDictionary = { + ...FortiGateAutoscaleSettingItemDictionary, + [AzureFortiGateAutoscaleSetting.AzureFortiGateAutoscaleSettingSaved]: { + keyName: AzureFortiGateAutoscaleSetting.AzureFortiGateAutoscaleSettingSaved, + description: 'The flag whether FortiGate Autoscale settings are saved in db or not.', + editable: false, + jsonEncoded: false, + booleanType: true + } +}; diff --git a/core/azure/azure-fortigate-autoscale.ts b/core/azure/azure-fortigate-autoscale.ts new file mode 100644 index 0000000..d60626f --- /dev/null +++ b/core/azure/azure-fortigate-autoscale.ts @@ -0,0 +1,129 @@ +import { Context } from '@azure/functions'; +import { + AzureFortiGateAutoscaleSetting, + AzureFortiGateBootstrapStrategy, + AzureFunctionHttpTriggerProxy, + AzureFunctionInvocable, + AzureHybridScalingGroupStrategy, + AzurePlatformAdapter, + AzureRoutingEgressTrafficViaPrimaryVmStrategy, + AzureTaggingAutoscaleVmStrategy +} from '.'; +import { + AutoscaleEnvironment, + CloudFunctionInvocationPayload, + CloudFunctionInvocationTimeOutError, + CloudFunctionProxyAdapter, + ConstantIntervalHeartbeatSyncStrategy, + FazDeviceAuthorization, + JSONable, + ReusableLicensingStrategy, + WeightedScorePreferredGroupPrimaryElection +} from '..'; +import { + FazReactiveAuthorizationStrategy, + FortiGateAutoscale, + FortiGateAutoscaleFunctionInvocationHandler +} from '../fortigate-autoscale'; + +export class AzureFortiGateAutoscale extends FortiGateAutoscale< + TReq, + TContext, + TRes +> { + constructor( + readonly platform: AzurePlatformAdapter, + readonly env: AutoscaleEnvironment, + readonly proxy: CloudFunctionProxyAdapter + ) { + super(); + // TODO: to be implemented + // use noop scaling group strategy + this.setScalingGroupStrategy(new AzureHybridScalingGroupStrategy(platform, proxy)); + // use peferred group primary election for Hybrid licensing model + this.setPrimaryElectionStrategy( + new WeightedScorePreferredGroupPrimaryElection(platform, proxy) + ); + // use a constant interval heartbeat sync strategy + this.setHeartbeatSyncStrategy(new ConstantIntervalHeartbeatSyncStrategy(platform, proxy)); + // TODO: implement the Azure tagging feature + // use Azure resource tagging strategy + this.setTaggingAutoscaleVmStrategy(new AzureTaggingAutoscaleVmStrategy(platform, proxy)); + // use FortiGate bootstrap configuration strategy + this.setBootstrapConfigurationStrategy( + new AzureFortiGateBootstrapStrategy(platform, proxy, env) + ); + // // use the Resuable licensing strategy + this.setLicensingStrategy(new ReusableLicensingStrategy(platform, proxy)); + // TODO: need to figure out how Azure VNet route egress traffic + // use the routing egress traffic via primary vm strategy + this.setRoutingEgressTrafficStrategy( + new AzureRoutingEgressTrafficViaPrimaryVmStrategy(platform, proxy, env) + ); + // use the reactive authorization strategy for FAZ integration + this.setFazIntegrationStrategy(new FazReactiveAuthorizationStrategy(platform, proxy)); + } +} + +export class AzureFortiGateAutoscaleFazAuthHandler extends FortiGateAutoscaleFunctionInvocationHandler { + autoscale: AzureFortiGateAutoscale; + constructor(autoscale: AzureFortiGateAutoscale) { + super(); + this.autoscale = autoscale; + } + get proxy(): AzureFunctionHttpTriggerProxy { + return this.autoscale.proxy as AzureFunctionHttpTriggerProxy; + } + + get platform(): AzurePlatformAdapter { + return this.autoscale.platform; + } + + async executeInvocable( + payload: CloudFunctionInvocationPayload, + invocable: string + ): Promise { + const payloadData: JSONable = JSON.parse(payload.stringifiedData); + const settings = await this.platform.getSettings(); + if (invocable === AzureFunctionInvocable.TriggerFazDeviceAuth) { + const fazIpSettingItem = settings.get(AzureFortiGateAutoscaleSetting.FortiAnalyzerIp); + if (!fazIpSettingItem.value) { + throw new CloudFunctionInvocationTimeOutError( + 'FortiAnalyzer IP address not specified.' + ); + } + const deviceAuthorization: FazDeviceAuthorization = { + vmId: payloadData.vmId as string, + privateIp: payloadData.privateIp && String(payloadData.privateIp), + publicIp: payloadData.publicIp && String(payloadData.publicIp) + }; + + // extract the autoscale admin user and faz info + const username: string = await this.platform.getSecretFromKeyVault( + 'faz-autoscale-admin-username' + ); + const password: string = await this.platform.getSecretFromKeyVault( + 'faz-autoscale-admin-password' + ); + const fazIp: string = fazIpSettingItem.value; + const fazPort = '443'; + + await this.autoscale.fazIntegrationStrategy + .processAuthorizationRequest( + deviceAuthorization, + fazIp, + fazPort, + username, + password + ) + .catch(e => { + const error: CloudFunctionInvocationTimeOutError = e; + error.extendExecution = false; + throw error; + }); + return; + } + // otherwise, no matching invocable, throw error + throw new CloudFunctionInvocationTimeOutError(`No matching invocable for: ${invocable}`); + } +} diff --git a/core/azure/azure-fortigate-bootstrap-config-strategy.ts b/core/azure/azure-fortigate-bootstrap-config-strategy.ts new file mode 100644 index 0000000..7a9f6bb --- /dev/null +++ b/core/azure/azure-fortigate-bootstrap-config-strategy.ts @@ -0,0 +1,40 @@ +import { AutoscaleEnvironment, CloudFunctionProxyAdapter } from '..'; +import { FortiGateBootstrapConfigStrategy } from '../fortigate-autoscale'; +import { AzurePlatformAdapter } from '.'; + +export class AzureFortiGateBootstrapStrategy extends FortiGateBootstrapConfigStrategy { + constructor( + readonly platform: AzurePlatformAdapter, + readonly proxy: CloudFunctionProxyAdapter, + readonly env: AutoscaleEnvironment + ) { + super(); + } + /** + * + * @override for loading bootstrap config with additional AWS Transit Gateway VPN connections + * @returns {Promise} configset content + */ + async loadConfig(): Promise { + let baseConfig = await super.loadConfig(); + // load azure only configset + baseConfig += await this.loadExtraPorts(); + return baseConfig; + } + /** + * + * load the configset content for extra ports deployment + * @returns {Promise} configset content + */ + async loadExtraPorts(): Promise { + this.settings = this.settings || (await this.platform.getSettings()); + try { + return await this.platform.loadConfigSet('extraports'); + } catch (error) { + this.proxy.logAsWarning("extraports configset doesn't exist in the assets storage."); + // NOTE: even though not loading the tgw specific configset, return empty string instead + // of throwing errors + return ''; + } + } +} diff --git a/core/azure/azure-function-definitions.ts b/core/azure/azure-function-definitions.ts new file mode 100644 index 0000000..8a4d57b --- /dev/null +++ b/core/azure/azure-function-definitions.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +interface AzureFunctionDefinition { + name: string; + path: string; +} + +export const ByolLicense: AzureFunctionDefinition = { + name: 'byol-license', + path: '/api/byol-license' +}; + +export const CustomLog: AzureFunctionDefinition = { + name: 'custom-log', + path: '/api/custom-log' +}; + +export const FazAuthScheduler: AzureFunctionDefinition = { + name: 'faz-auth-scheduler', + path: '/api/faz-auth-scheduler' +}; + +export const FortiGateAutoscaleHandler: AzureFunctionDefinition = { + name: 'fgt-as-handler', + path: '/api/fgt-as-handler' +}; + +export const FazAuthHandler: AzureFunctionDefinition = { + name: 'faz-auth-handler', + path: '/api/faz-auth-handler' +}; diff --git a/core/azure/azure-function-invocable.ts b/core/azure/azure-function-invocable.ts new file mode 100644 index 0000000..769a80c --- /dev/null +++ b/core/azure/azure-function-invocable.ts @@ -0,0 +1,11 @@ +import { CloudFunctionInvocationPayload, CloudFunctionInvocationTimeOutError } from '..'; +import { FortiGateAutoscaleFunctionInvocable } from '../fortigate-autoscale'; + +export type AzureFunctionInvocationPayload = CloudFunctionInvocationPayload; + +export type AzureFunctionInvocableExecutionTimeOutError = CloudFunctionInvocationTimeOutError; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const AzureFunctionInvocable = { + ...FortiGateAutoscaleFunctionInvocable +}; diff --git a/core/azure/azure-hybrid-scaling-group-strategy.ts b/core/azure/azure-hybrid-scaling-group-strategy.ts new file mode 100644 index 0000000..f4d0bd4 --- /dev/null +++ b/core/azure/azure-hybrid-scaling-group-strategy.ts @@ -0,0 +1,55 @@ +import { CloudFunctionProxyAdapter, ScalingGroupStrategy } from '..'; +import { AzurePlatformAdapter } from '.'; + +export class AzureHybridScalingGroupStrategy implements ScalingGroupStrategy { + platform: AzurePlatformAdapter; + proxy: CloudFunctionProxyAdapter; + constructor(platform: AzurePlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.platform = platform; + this.proxy = proxy; + } + onLaunchingVm(): Promise { + this.proxy.logAsInfo('calling AzureHybridScalingGroupStrategy.onLaunchingVm'); + this.proxy.logAsInfo('no operation needed in this phase.'); + this.proxy.logAsInfo('called AzureHybridScalingGroupStrategy.onLaunchingVm'); + return Promise.resolve(''); + } + onLaunchedVm(): Promise { + this.proxy.logAsInfo('calling AzureHybridScalingGroupStrategy.onLaunchedVm'); + this.proxy.logAsInfo('no operation needed in this phase.'); + this.proxy.logAsInfo('called AzureHybridScalingGroupStrategy.onLaunchedVm'); + return Promise.resolve(''); + } + onVmNotLaunched(): Promise { + this.proxy.logAsInfo('calling AzureHybridScalingGroupStrategy.onVmNotLaunched'); + this.proxy.logAsInfo('no operation needed in this phase.'); + this.proxy.logAsInfo('called AzureHybridScalingGroupStrategy.onVmNotLaunched'); + return Promise.resolve(''); + } + onTerminatingVm(): Promise { + this.proxy.logAsInfo('calling AzureHybridScalingGroupStrategy.onTerminatingVm'); + this.proxy.logAsInfo('no operation needed in this phase.'); + this.proxy.logAsInfo('called AzureHybridScalingGroupStrategy.onTerminatingVm'); + return Promise.resolve(''); + } + onTerminatedVm(): Promise { + this.proxy.logAsInfo('calling AzureHybridScalingGroupStrategy.onTerminatedVm'); + this.proxy.logAsInfo('no operation needed in this phase.'); + this.proxy.logAsInfo('called AzureHybridScalingGroupStrategy.onTerminatedVm'); + return Promise.resolve(''); + } + completeLaunching(success = true): Promise { + this.proxy.logAsInfo('calling AzureHybridScalingGroupStrategy.onTerminatedVm'); + this.proxy.logAsInfo(`value passed to parameter: success: ${success}.`); + this.proxy.logAsInfo('no operation needed in this phase.'); + this.proxy.logAsInfo('called AzureHybridScalingGroupStrategy.onTerminatedVm'); + return Promise.resolve(''); + } + completeTerminating(success = true): Promise { + this.proxy.logAsInfo('calling AzureHybridScalingGroupStrategy.completeTerminating'); + this.proxy.logAsInfo(`value passed to parameter: success: ${success}.`); + this.proxy.logAsInfo('no operation needed in this phase.'); + this.proxy.logAsInfo('called AzureHybridScalingGroupStrategy.completeTerminating'); + return Promise.resolve(''); + } +} diff --git a/core/azure/azure-platform-adaptee.ts b/core/azure/azure-platform-adaptee.ts new file mode 100644 index 0000000..e5639c1 --- /dev/null +++ b/core/azure/azure-platform-adaptee.ts @@ -0,0 +1,967 @@ +import { ComputeManagementClient } from '@azure/arm-compute'; +import { VirtualMachineScaleSetVM } from '@azure/arm-compute/esm/models'; +import { NetworkManagementClient } from '@azure/arm-network'; +import { NetworkInterface } from '@azure/arm-network/esm/models'; +import { + CosmosClient, + CosmosClientOptions, + Database, + FeedResponse, + RequestOptions, + SqlParameter, + SqlQuerySpec +} from '@azure/cosmos'; +import { ClientSecretCredential } from '@azure/identity'; +import { SecretClient } from '@azure/keyvault-secrets'; +import * as msRestNodeAuth from '@azure/ms-rest-nodeauth'; +import { BlobServiceClient, StorageSharedKeyCredential } from '@azure/storage-blob'; +import * as DBDef from '../db-definitions'; +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; +import fs from 'fs'; +import * as HttpStatusCodes from 'http-status-codes'; +import path from 'path'; +import { + AzureApiRequestCache, + AzureFortiGateAutoscaleSetting, + AzureSettings, + AzureSettingsDbItem, + CosmosDBQueryResult, + CosmosDBQueryWhereClause, + CosmosDbTableMetaData +} from '.'; +import { + Blob, + jsonParseReviver, + jsonStringifyReplacer, + PlatformAdaptee, + SettingItem, + Settings +} from '..'; + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum RequiredEnvVars { + AUTOSCALE_DB_ACCOUNT = 'AUTOSCALE_DB_ACCOUNT', + AUTOSCALE_DB_NAME = 'AUTOSCALE_DB_NAME', + AUTOSCALE_DB_PRIMARY_KEY = 'AUTOSCALE_DB_PRIMARY_KEY', + AUTOSCALE_KEY_VAULT_NAME = 'AUTOSCALE_KEY_VAULT_NAME', + AZURE_STORAGE_ACCOUNT = 'AZURE_STORAGE_ACCOUNT', + AZURE_STORAGE_ACCESS_KEY = 'AZURE_STORAGE_ACCESS_KEY', + CLIENT_ID = 'CLIENT_ID', + CLIENT_SECRET = 'CLIENT_SECRET', + RESOURCE_GROUP = 'RESOURCE_GROUP', + SUBSCRIPTION_ID = 'SUBSCRIPTION_ID', + TENANT_ID = 'TENANT_ID' +} + +export interface ApiCacheRequest { + api: string; + parameters: string[]; + ttl?: number; +} + +export interface ApiCacheResult { + id?: string; + api?: string; + parameters?: string[]; + stringifiedData: string; + ttl: number; + cacheTime?: number; + expired?: boolean; +} + +export interface ApiCache { + result: T; + hitCache: boolean; + cacheTime: number; + ttl: number; +} + +/** + * Api Cache options + * @enum + */ +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum ApiCacheOption { + /** + * @member {string} ReadApiFirst always request data from api then save data to cache. + */ + ReadApiFirst = 'ReadApiFirst', + /** + * @member {string} ReadApiOnly always request data from api but never save data to cache. + */ + ReadApiOnly = 'ReadApiOnly', + /** + * @member {string} ReadCacheAndDelete read cache, delete the cache. not request data from api + */ + ReadCacheAndDelete = 'ReadCacheAndDelete', + /** + * @member {string} ReadCacheFirst read cache first. if no cached data, request data from api + * then save data to cache. + */ + ReadCacheFirst = 'ReadCacheFirst', + /** + * @member {string} ReadCacheOnly only read data from cache. not request data from api + */ + ReadCacheOnly = 'ReadCacheOnly' +} + +const TTLS = { + listInstances: 600, + describeInstance: 600, + listNetworkInterfaces: 600 +}; + +export class AzurePlatformAdaptee implements PlatformAdaptee { + protected autoscaleDBRef: Database; + protected azureCompute: ComputeManagementClient; + protected azureCosmosDB: CosmosClient; + protected azureKeyVault: SecretClient; + protected azureNetwork: NetworkManagementClient; + protected azureStorage: BlobServiceClient; + protected settings: Settings; + /** + * The following process.env are required. + * process.env.AUTOSCALE_DB_ACCOUNT: the CosmosDB account name + * process.env.AUTOSCALE_DB_NAME: the Autoscale db name. + * process.env.CLIENT_ID: the App registration (service principal) app client_id. + * process.env.CLIENT_SECRET: the App registration (service principal) app client_secret. + * process.env.TENANT_ID: the tenant containing the App registration (service principal) app. + */ + constructor() { + // validation + const missingEnvVars = Object.keys({ ...RequiredEnvVars }).filter(key => !process.env[key]); + if (missingEnvVars.length > 0) { + throw new Error( + `Missing the following environment variables: ${missingEnvVars.join()}.` + ); + } + } + /** + * Class instance initiation. The following process.env are required. + * process.env.AUTOSCALE_DB_ACCOUNT: the CosmosDB account name + * process.env.AUTOSCALE_DB_NAME: the Autoscale db name. + * process.env.CLIENT_ID: the App registration (service principal) app client_id. + * process.env.CLIENT_SECRET: the App registration (service principal) app client_secret. + * process.env.TENANT_ID: the tenant containing the App registration (service principal) app. + * @returns {Promise} void + */ + async init(): Promise { + const cosmosClientOptions: CosmosClientOptions = { + endpoint: `https://${process.env.AUTOSCALE_DB_ACCOUNT}.documents.azure.com/`, + key: process.env.AUTOSCALE_DB_PRIMARY_KEY + }; + this.azureCosmosDB = new CosmosClient(cosmosClientOptions); + this.autoscaleDBRef = this.azureCosmosDB.database(process.env.AUTOSCALE_DB_NAME); + const creds = await msRestNodeAuth.loginWithServicePrincipalSecret( + process.env.CLIENT_ID, + process.env.CLIENT_SECRET, + process.env.TENANT_ID + ); + this.azureCompute = new ComputeManagementClient(creds, process.env.SUBSCRIPTION_ID); + this.azureNetwork = new NetworkManagementClient(creds, process.env.SUBSCRIPTION_ID); + this.azureStorage = new BlobServiceClient( + `https://${process.env.AZURE_STORAGE_ACCOUNT}.blob.core.windows.net`, + new StorageSharedKeyCredential( + process.env.AZURE_STORAGE_ACCOUNT, + process.env.AZURE_STORAGE_ACCESS_KEY + ) + ); + this.azureKeyVault = new SecretClient( + `https://${process.env.AUTOSCALE_KEY_VAULT_NAME}.vault.azure.net/`, + new ClientSecretCredential( + process.env.TENANT_ID, + process.env.CLIENT_ID, + process.env.CLIENT_SECRET + ) + ); + } + + async reloadSettings(invalidateCache: boolean): Promise { + const table = new AzureSettings(); + const queryResult: CosmosDBQueryResult = + await this.listItemFromDb(table); + const res = queryResult.result || []; + if (invalidateCache) { + this.settings = null; + } + const records: Map = new Map(); + res.forEach(rec => records.set(rec.settingKey, rec)); + const settings: Settings = new Map(); + Object.values(AzureFortiGateAutoscaleSetting).forEach(value => { + if (records.has(value)) { + const record = records.get(value); + const settingItem = new SettingItem( + record.settingKey, + record.settingValue, + record.description, + record.editable, + record.jsonEncoded + ); + settings.set(value, settingItem); + } + }); + return settings; + } + + async loadSettings(): Promise { + if (this.settings) { + return this.settings; + } + const data = await this.reloadSettings(false); + this.settings = data; + return this.settings; + } + + /** + * get a single item. + * @param {Table} table the instance of Table to delete the item. + * T is the db item type of the given table. + * @param {DBDef.KeyValue[]} partitionKeys the partition keys (primary key) + * of the table + * @returns {Promise} T + */ + async getItemFromDb(table: DBDef.Table, partitionKeys: DBDef.KeyValue[]): Promise { + const primaryKey: DBDef.KeyValue = partitionKeys[0]; + try { + const itemResponse = await this.autoscaleDBRef + .container(table.name) + // CAUTION: the partition key must be provided in order to get the item. + // the partition key must match the same value of the item in the container. + .item(primaryKey.value, primaryKey.value) + .read(); + if (itemResponse.statusCode === HttpStatusCodes.OK) { + return table.convertRecord({ ...itemResponse.resource }); + } else { + return null; + } + } catch (error) { + if (error.code === HttpStatusCodes.NOT_FOUND) { + return null; + } else { + throw new DBDef.DbReadError( + DBDef.DbErrorCode.UnexpectedResponse, + JSON.stringify(error) + ); + } + } + } + + /** + * Scan and list all or some record from a given db table + * @param {Table} table the instance of Table to list the item. + * @param {CosmosDBQueryWhereClause[]} listClause (optional) a filter for listing the records + * @param {number} limit (optional) number or records to return + * @returns {Promise} CosmosDBQueryResult object with an array of db record + * @see https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-select + */ + async listItemFromDb( + table: DBDef.Table, + listClause?: CosmosDBQueryWhereClause[], + limit?: number + ): Promise> { + let topClause = ''; + if (limit && limit > 0) { + topClause = ` TOP ${limit}`; + } + const querySpec: SqlQuerySpec = { + query: `SELECT${topClause} * FROM ${table.name} t` + }; + if (listClause && listClause.length > 0) { + querySpec.query = `${querySpec.query} WHERE`; + querySpec.parameters = listClause.map(clause => { + querySpec.query = `${querySpec.query} t.${clause.name} = @${clause.name} AND`; + return { + name: `@${clause.name}`, + value: clause.value + } as SqlParameter; + }); + // to remove the last ' AND' + querySpec.query = querySpec.query.substr(0, querySpec.query.length - 4); + } + const queryResult: CosmosDBQueryResult = { + query: querySpec.query, + result: null + }; + const feeds: FeedResponse = await this.autoscaleDBRef + .container(table.name) + .items.query(querySpec) + .fetchAll(); + queryResult.result = feeds.resources; + return queryResult; + } + /** + * save an item to db. When the optional parameter 'dataIntegrityCheck' is provided, it will + * perform a data consistency checking before saving. + * The function compares each property of the item against the existing record + * with the same primary key in the db table. + * It saves the item only when one of the following conditions is met: + * condition 1: if parameter dataIntegrityCheck is passed boolean true, it will + * only compare the _etag + * condition 2: if parameter dataIntegrityCheck is passed a check function that accepts + * an input of type T, it will + * strictly compare each defined (including null, false and empty value) property + * @param {Table} table the instance of Table to save the item. + * @param {T} item the item to save + * @param {DBDef.SaveCondition} condition save condition + * @param {boolean| function} dataIntegrityCheck (optional) ensure data integrity to prevent + * saving outdated data. + * @returns {Promise} a promise of item of type T + */ + async saveItemToDb( + table: DBDef.Table, + item: T, + condition: DBDef.SaveCondition, + dataIntegrityCheck: + | boolean + | ((dbItemSnapshot: T) => Promise<{ + result: boolean; + errorMessage: string; + }>) = true + ): Promise { + // CAUTION: validate the db input (non meta data) + table.validateInput(item); + // read the item + const itemSnapshot = await this.getItemFromDb(table, [ + { + key: table.primaryKey.name, + value: item.id + } + ]); + + let options: RequestOptions; + + // if date with the same primary key already exists in the db table + if (itemSnapshot) { + // if a function is provided as dataIntegrityCheck, run the checker function + if (typeof dataIntegrityCheck === 'function') { + const checkerResult = await dataIntegrityCheck(itemSnapshot); + if (!checkerResult.result) { + throw new DBDef.DbSaveError( + DBDef.DbErrorCode.InconsistentData, + `Data integrityCheck failed. ${checkerResult.errorMessage || ''}` + ); + } + } + + // NOTE: if dataIntegrityCheck, enforces this access condition + options = dataIntegrityCheck && { + accessCondition: { + type: 'IfMatch', + condition: itemSnapshot._etag + } + }; + } + // update only but no record found + if (condition === DBDef.SaveCondition.UpdateOnly && !itemSnapshot) { + throw new DBDef.DbSaveError( + DBDef.DbErrorCode.NotFound, + `Unable to update the item (id: ${item.id}).` + + ` The item not exists in the table (name: ${table.name}).` + ); + } + // insert only but record found + else if (condition === DBDef.SaveCondition.InsertOnly && itemSnapshot) { + throw new DBDef.DbSaveError( + DBDef.DbErrorCode.KeyConflict, + `Unable to insert the item (id: ${item.id}).` + + ` The item already exists in the table (name: ${table.name}).` + ); + } + // TODO: from the logic above, the condition probably be always false + // can remove this block? + if ( + dataIntegrityCheck && + itemSnapshot && + item[table.primaryKey.name] !== itemSnapshot[table.primaryKey.name] + ) { + throw new DBDef.DbSaveError( + DBDef.DbErrorCode.InconsistentData, + 'Inconsistent data.' + + ' Primary key values not match.' + + 'Cannot save item back into db due to' + + ' the restriction parameter dataIntegrityCheck is on.' + ); + } + // ASSERT: input validation and data consistency checking have passed. + // db item meta data properties except for the 'id' do not need to be present so they + // will be removed from the object + const saveItem = { ...item }; + // CAUTION: id accepts non-empty string value + // will try to set the id when present in the item, + // otherwise, will always set id to the same value as primary key + saveItem.id = + ((item.id || Number(item.id) === 0) && item.id) || String(item[table.primaryKey.name]); + delete saveItem._attachments; + delete saveItem._etag; + delete saveItem._rid; + delete saveItem._self; + delete saveItem._ts; + + // update or insert + const result = await this.autoscaleDBRef + .container(table.name) + .items.upsert(saveItem, options); + if ( + result.statusCode === HttpStatusCodes.OK || + result.statusCode === HttpStatusCodes.CREATED + ) { + if (!result.resource) { + throw new DBDef.DbSaveError( + DBDef.DbErrorCode.UnexpectedResponse, + "Upsert doesn't return expected data. see the detailed upsert " + + `result:${JSON.stringify(result)}` + ); + } + return table.convertRecord(result.resource); + } else { + throw new DBDef.DbSaveError( + DBDef.DbErrorCode.UnexpectedResponse, + 'Saving item unsuccessfull. SDK returned unexpected response with ' + + ` httpStatusCode: ${result.statusCode}.` + ); + } + } + /** + * Delete a given item from the db + * @param {Table} table the instance of Table to save the item. + * @param {T} item the item to be deleted. The primary key must be presented for deletion. + * @param {boolean} ensureDataConsistency ensure data consistency to prevent deleting outdated + * data by doing a full-match of properties of the given item against the item in the db. In + * this case, each property including meta data will be compared. Otherwise, only the primary + * key will be used for deletion. + * @returns {Promise} a promise of void + */ + async deleteItemFromDb( + table: DBDef.Table, + item: T, + ensureDataConsistency = true + ): Promise { + let itemSnapshot: T; + // read the item for comparison if rrequire ensureDataConsistency + if (ensureDataConsistency) { + // CAUTION: validate the db input (non meta data) + table.validateInput(item); + // read the item + try { + itemSnapshot = await this.getItemFromDb(table, [ + { + key: table.primaryKey.name, + value: String(item[table.primaryKey.name]) + } + ]); + } catch (error) { + if (error instanceof DBDef.DbReadError) { + throw new DBDef.DbDeleteError( + DBDef.DbErrorCode.NotFound, + 'Cannot delete item. ' + + `Item (id: ${item.id}) not found in table (name: ${table.name}).` + ); + } else { + throw error; + } + } + // NOTE: the itemsnapshot may not exist if already deleted by other + // db operation. + if (!itemSnapshot) { + return; + } + // full match + const keyDiff = Object.keys(itemSnapshot).filter( + // ensure that item and snapshot both contain the same keys to compare + key => + item[key] !== undefined && + itemSnapshot[key] !== undefined && + itemSnapshot[key] !== item[key] + ); + if (keyDiff.length > 0) { + throw new DBDef.DbDeleteError( + DBDef.DbErrorCode.InconsistentData, + `Inconsistent data. The attributes don't match: ${keyDiff.join()}. ` + + ` Item to delete: ${JSON.stringify(item)}.` + + ` Item in the db: ${JSON.stringify(itemSnapshot)}.` + ); + } + } + // CAUTION: validate the db input (only primary key) + if (item[table.primaryKey.name] === null) { + throw new DBDef.DbDeleteError( + DBDef.DbErrorCode.InconsistentData, + `Required primary key attribute: ${table.primaryKey.name} not` + + ` found in item: ${JSON.stringify(item)}` + ); + } + // ASSERT: the id and primary key should have the same value + if (item.id !== item[table.primaryKey.name]) { + throw new DBDef.DbDeleteError( + DBDef.DbErrorCode.InconsistentData, + "Item primary key value and id value don't match. Make sure the id" + + ' and primary key have the same value.' + ); + } + // ASSERT: the given item matches the item in the db. It can be now deleted. + const deleteResponse = await this.autoscaleDBRef + .container(table.name) + .item(String(item[table.primaryKey.name]), String(item[table.primaryKey.name])) + .delete(); + if ( + deleteResponse.statusCode === HttpStatusCodes.OK || + deleteResponse.statusCode === HttpStatusCodes.NO_CONTENT + ) { + return; + } else if (deleteResponse.statusCode === HttpStatusCodes.NOT_FOUND) { + throw new DBDef.DbDeleteError( + DBDef.DbErrorCode.NotFound, + `Item (${table.primaryKey.name}: ` + + `${item.id}) not found in table (${table.name})` + ); + } else { + throw new DBDef.DbDeleteError( + DBDef.DbErrorCode.UnexpectedResponse, + 'Deletion unsuccessful. SDK returned unexpected response with ' + + ` httpStatusCode: ${deleteResponse.statusCode}.` + ); + } + } + private generateCacheId(api: string, parameters: string[]): string { + // NOTE: id is constructed as -[-,[-...]] + return [api, ...parameters.map(String)].join('-'); + } + /** + * read a cached response of an API request + * @param {ApiCacheRequest} req the api request + * @returns {Promise} ApiRequestSave + */ + async apiRequestReadCache(req: ApiCacheRequest): Promise { + const table = new AzureApiRequestCache(); + try { + const item = await this.getItemFromDb(table, [ + { + key: table.primaryKey.name, + value: this.generateCacheId(req.api, req.parameters) + } + ]); + if (item) { + const timeToLive: number = req.ttl || item.ttl; + return { + id: item.id, + stringifiedData: item.res, + ttl: item.ttl, + cacheTime: item.cacheTime, + expired: (item.cacheTime + timeToLive) * 1000 < Date.now() + }; + } + } catch (error) { + if (error instanceof DBDef.DbReadError) { + if (error.code !== DBDef.DbErrorCode.NotFound) { + throw error; + } + } + } + return null; + } + + async apiRequestDeleteCache(req: ApiCacheRequest): Promise { + const table = new AzureApiRequestCache(); + const item = table.downcast({ + id: this.generateCacheId(req.api, req.parameters), + res: null, + cacheTime: null, + ttl: null + }); + await this.deleteItemFromDb(table, item, false); + } + + /** + * save a response of an API request to cache + * @param {ApiCacheResult} res the api response + * @returns {Promise} ApiRequestSave + */ + async apiRequestSaveCache(res: ApiCacheResult): Promise { + // if neither of these conditions is met + // 1. there is res.id + // 2. there are res.api and res.parameters + if (!(res.id || (!res.id && res.api && res.parameters))) { + throw new Error('Invalid cache result to save. id, api, and paramters are required.'); + } + const table = new AzureApiRequestCache(); + const item = table.downcast({ + id: res.id || this.generateCacheId(res.api, res.parameters), + res: res.stringifiedData, + cacheTime: undefined, // NOTE: cacheTime will use the value of _ts (db generated) + ttl: res.ttl * 1000 + }); + const savedItem = await this.saveItemToDb( + table, + item, + DBDef.SaveCondition.Upsert, + false + ); + if (savedItem) { + res.cacheTime = savedItem.cacheTime; + } + return res; + } + /** + * send an api request with appling a caching strategy. + * This can prevent from firing too many arm resource requests to Microsoft Azure that + * results in throttling resource manager request. + * @see https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/request-limits-and-throttling + * @param {ApiCacheRequest} req an api cache request + * @param {ApiCacheOption} cacheOption option for the api caching behavior. + * @param {function} dataProcessor a method that process the api request and return + * a promise of type D + * @returns {Promise} an ApiCache of type D + */ + private async requestWithCaching( + req: ApiCacheRequest, + cacheOption: ApiCacheOption, + dataProcessor: () => Promise + ): Promise> { + const ttl = 600; + let cacheTime: number; + let res: ApiCacheResult; + let data: D; + + // read cache for those options require reading cache + if (cacheOption !== ApiCacheOption.ReadApiOnly) { + res = await this.apiRequestReadCache(req); + cacheTime = res && res.cacheTime; + data = (res && (JSON.parse(res.stringifiedData, jsonParseReviver) as D)) || null; + } + + const hitCache = res && res.expired === false; + + // for those options do not require reading data from api + if ( + cacheOption === ApiCacheOption.ReadCacheOnly || + cacheOption === ApiCacheOption.ReadCacheAndDelete + ) { + // delete the cache if exists + if (cacheOption === ApiCacheOption.ReadCacheAndDelete && res) { + await this.apiRequestDeleteCache(req); + cacheTime = 0; + } + } + // for those options require reading data from api + else { + if ( + // read cache first then read api when cache not found + (cacheOption === ApiCacheOption.ReadCacheFirst && !res) || + // read data from api only, will not cache the result + cacheOption === ApiCacheOption.ReadApiOnly || + // read data from api and then update the cache + cacheOption === ApiCacheOption.ReadApiFirst + ) { + // read data from api + data = await dataProcessor(); + if (data) { + // if it requires to save cache, save cache. + if (cacheOption !== ApiCacheOption.ReadApiOnly) { + if (!res) { + res = { + stringifiedData: '', + ttl: 0 + }; + } + res.api = req.api; + res.parameters = req.parameters; + res.stringifiedData = JSON.stringify(data, jsonStringifyReplacer); + res.ttl = req.ttl; + res = await this.apiRequestSaveCache(res); + cacheTime = res.cacheTime; + } + } + } + } + return { + result: data, + cacheTime: cacheTime, + ttl: ttl, + hitCache: hitCache + }; + } + + /** + * list vm instances in the given scaling group (vmss) + * @param {string} scalingGroupName the scaling group containing the vm + * @param {ApiCacheOption} cacheOption (optional) option for the api caching behavior. + * default to ApiCacheOption.ReadCacheFirst + * @returns {Promise} a list of VirtualMachineScaleSetVM objects + */ + async listInstances( + scalingGroupName: string, + cacheOption: ApiCacheOption = ApiCacheOption.ReadCacheFirst + ): Promise> { + const req: ApiCacheRequest = { + api: 'listInstances', + parameters: [scalingGroupName], + ttl: TTLS.listInstances // expected time to live + }; + + const requestProcessor = async (): Promise => { + const response = await this.azureCompute.virtualMachineScaleSetVMs.list( + process.env[RequiredEnvVars.RESOURCE_GROUP], + scalingGroupName + ); + return (response && response._response.parsedBody) || null; + }; + return await this.requestWithCaching( + req, + cacheOption, + requestProcessor + ); + } + /** + * describe a virtual machine + * @param {string} scalingGroupName the scaling group containing the vm + * @param {string} id the id (either integer instanceId or string vmId) of the vm + * @param {ApiCacheOption} cacheOption (optional) option for the api caching behavior. + * default to ApiCacheOption.ReadCacheFirst + * @returns {Promise} ApiCache + */ + async describeInstance( + scalingGroupName: string, + id: string, + cacheOption: ApiCacheOption = ApiCacheOption.ReadCacheFirst + ): Promise> { + let data: VirtualMachineScaleSetVM; + let instanceId: string = id; + // ASSERT: id is the vmId to be looked up + // NOTE: need to find the corresponding vm.instanceId using vm.vmId by listing all + // instances in the vmss and find the vm. + if (!isFinite(Number(id))) { + let listResult = await this.listInstances(scalingGroupName, cacheOption); + data = listResult.result.find(v => v.vmId && v.vmId === id); + // NOTE: if vm is a new vm, it won't exist in the cache so try to read from api again + // then update cache, just once. + if (!data) { + listResult = await this.listInstances( + scalingGroupName, + ApiCacheOption.ReadApiFirst + ); + data = listResult.result.find(v => v.vmId && v.vmId === id); + } + if (data) { + instanceId = data.instanceId; + } else { + // vm not exists. + return { + result: null, + hitCache: listResult.hitCache, + cacheTime: listResult.cacheTime, + ttl: listResult.ttl + }; + } + } + const req: ApiCacheRequest = { + api: 'describeInstance', + parameters: [scalingGroupName, id], + ttl: TTLS.describeInstance // expected time to live + }; + const requestProcessor = async (): Promise => { + const response = await this.azureCompute.virtualMachineScaleSetVMs.get( + process.env[RequiredEnvVars.RESOURCE_GROUP], + scalingGroupName, + instanceId, + { + expand: 'instanceView' + } + ); + return response; + }; + return await this.requestWithCaching(req, cacheOption, requestProcessor); + } + /** + * Delete an instance from a scaling group (vmss) + * @param {string} scalingGroupName the scaling group containing the vm + * @param {number} instanceId the integer instanceId of the vm + * @returns {Promise} boolean whether the instance existed and deleted or not exist to delete + */ + async deleteInstanceFromVmss(scalingGroupName: string, instanceId: number): Promise { + // CAUTION: when delete instance, must handle cache, otherwise, it can result in + // cached data inconsistent. + // possibly every method that involves caching should be handled to to delete cache + // aka: where requestWithCaching() is applied. + + // providing ApiCacheOption.ReadCacheAndDelete will ensure cache is deleted after read + await Promise.all([ + this.listInstances(scalingGroupName, ApiCacheOption.ReadCacheAndDelete), + this.describeInstance( + scalingGroupName, + String(instanceId), + ApiCacheOption.ReadCacheAndDelete + ), + this.listNetworkInterfaces( + scalingGroupName, + instanceId, + ApiCacheOption.ReadCacheAndDelete + ) + ]); + + // ASSERT: all related caches are deleted. can delete the vm now + const response = await this.azureCompute.virtualMachineScaleSetVMs.deleteMethod( + process.env[RequiredEnvVars.RESOURCE_GROUP], + scalingGroupName, + String(instanceId) + ); + if ( + response._response.status === HttpStatusCodes.OK || + response._response.status === HttpStatusCodes.ACCEPTED + ) { + return true; + } else if (response._response.status === HttpStatusCodes.NO_CONTENT) { + return false; + } else { + throw new Error(`Unkown response with status code: ${response._response.status}.`); + } + } + /** + * list network interfaces of a vm in the scaling group (vmss) + * @param {string} scalingGroupName the scaling group containing the vm + * @param {number} id the integer instanceId of the vm + * @param {ApiCacheOption} cacheOption (optional) option for the api caching behavior. + * default to ApiCacheOption.ReadCacheFirst + * @param {number} ttl (optional) cache time to live in seconds. default to 600 + * @returns {Promise} ApiCache + */ + async listNetworkInterfaces( + scalingGroupName: string, + id: number, + cacheOption: ApiCacheOption = ApiCacheOption.ReadCacheFirst, + ttl = TTLS.listNetworkInterfaces + ): Promise> { + const req: ApiCacheRequest = { + api: 'listNetworkInterfaces', + parameters: [scalingGroupName, String(id)], + ttl: ttl // expected time to live + }; + const requestProcessor = async (): Promise => { + const response = + await this.azureNetwork.networkInterfaces.listVirtualMachineScaleSetVMNetworkInterfaces( + process.env[RequiredEnvVars.RESOURCE_GROUP], + scalingGroupName, + String(id) + ); + return (response && response._response.parsedBody) || null; + }; + return await this.requestWithCaching( + req, + cacheOption, + requestProcessor + ); + } + + private streamToBuffer(readableStream: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + const chunks = []; + readableStream.on('data', data => { + chunks.push(data instanceof Buffer ? data : Buffer.from(data)); + }); + readableStream.on('end', () => { + resolve(Buffer.concat(chunks)); + }); + readableStream.on('error', reject); + }); + } + /** + * read the content of a blob into a string + * @param {string} container the blob container containing the target blob file + * @param {string} blobFilePath the full path to the blob file in the container, including + * blob file name + * @returns {Promise} string + */ + async getBlobContent(container: string, blobFilePath: string): Promise { + const containerClient = this.azureStorage.getContainerClient(container); + if (!containerClient.exists()) { + throw new Error(`blob container (name: ${container}) not exists.`); + } + const blobClient = containerClient.getBlobClient(blobFilePath); + if (!blobClient.exists()) { + throw new Error(`blob container (name: ${container}) not exists.`); + } + // download the blob from position 0 (beginning) + const response = await blobClient.download(); + const buffer = await this.streamToBuffer(response.readableStreamBody); + return buffer.toString(); + } + /** + * List all blob objects in a given container + * @param {string} container the blob container containing the target blob file + * @param {string} subdirectory the subdirectory of the container to list + * @returns {Promise} an array of blob objects in the given location + */ + async listBlob(container: string, subdirectory?: string): Promise { + const prefix = subdirectory || ''; + + // DEBUG: for local debugging use, the next lines get files from local file system instead + // it is usually useful when doing a mock test that do not require real api calls + if (process.env.LOCAL_DEV_MODE === 'true') { + return fs + .readdirSync(path.resolve(container, prefix)) + .filter(fileName => { + const stat = fs.statSync(path.resolve(container, prefix, fileName)); + return !stat.isDirectory(); + }) + .map(fileName => { + return { + fileName: fileName, + content: '' + } as Blob; + }); + } else { + const containerClient = this.azureStorage.getContainerClient(container); + if (!containerClient.exists()) { + throw new Error(`blob container (name: ${container}) not exists.`); + } + const iterator = containerClient.listBlobsFlat(); + const blobs: Blob[] = []; + let result = await iterator.next(); + while (!result.done) { + blobs.push({ + fileName: path.basename(result.value.name), + filePath: path.dirname(result.value.name) + }); + result = await iterator.next(); + } + return blobs.filter(blob => blob.filePath === subdirectory); + } + } + + /** + * invoke another Azure function + * @param {string} functionEndpoint the full function URL, format: + * https://.azurewebsites.net/api/ + * @param {string} payload a JSON stringified JSON object that can be parsed back to a JSON + * object without error. + * @param {string} accessKey? (optional) function authentication keys + * @returns {Promise} a JSON stringified response of the invoked function + * @see https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=csharp#authorization-keys + * @see https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview + */ + async invokeAzureFunction( + functionEndpoint: string, + payload: string, + accessKey?: string + ): Promise> { + // NOTE: make requests to another Azure function using http requests and library axios + const reqOptions: AxiosRequestConfig = { + method: 'POST', + headers: { + 'x-functions-key': accessKey + }, + url: functionEndpoint, + data: JSON.parse(payload), + // NOTE: see the hard timeout + // https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale#timeout + timeout: 230000 // ms + }; + return await axios(reqOptions); + } + + async keyVaultGetSecret(key: string): Promise { + const secret = await this.azureKeyVault.getSecret(key); + return secret.value; + } +} diff --git a/core/azure/azure-platform-adapter.ts b/core/azure/azure-platform-adapter.ts new file mode 100644 index 0000000..3616d51 --- /dev/null +++ b/core/azure/azure-platform-adapter.ts @@ -0,0 +1,1800 @@ +import * as AzureComputeModels from '@azure/arm-compute/esm/models'; +import * as AzureNetworkModels from '@azure/arm-network/esm/models'; +import path from 'path'; +import { + ApiCache, + ApiCacheOption, + AzureAutoscale, + AzureAutoscaleDbItem, + AzureCustomLog, + AzureFortiAnalyzer, + AzureFortiGateAutoscaleSetting, + AzureFortiGateAutoscaleSettingItemDictionary, + AzureFunctionDef, + AzureFunctionHttpTriggerProxy, + AzureFunctionInvocationProxy, + AzureFunctionServiceProviderProxy, + AzureLicenseStock, + AzureLicenseStockDbItem, + AzureLicenseUsage, + AzureLicenseUsageDbItem, + AzurePlatformAdaptee, + AzurePrimaryElection, + AzurePrimaryElectionDbItem, + AzureSettings, + CosmosDBQueryWhereClause, + LogItem +} from '.'; +import { + Blob, + CloudFunctionInvocationPayload, + constructInvocationPayload, + DeviceSyncInfo, + genChecksum, + HealthCheckRecord, + HealthCheckSyncState, + JSONable, + LicenseFile, + LicenseStockRecord, + LicenseUsageRecord, + NetworkInterface, + NicAttachmentRecord, + PlatformAdapter, + PrimaryRecord, + PrimaryRecordVoteState, + ReqMethod, + ReqType, + ResourceFilter, + SettingItemDefinition, + Settings, + VirtualMachine, + VirtualMachineState +} from '..'; +import { FortiGateAutoscaleServiceRequestSource } from '../fortigate-autoscale'; +import * as DBDef from '../db-definitions'; + +export type ConsistenyCheckType = { [key in keyof T]?: string | number | boolean | null }; +export class AzurePlatformAdapter implements PlatformAdapter { + adaptee: AzurePlatformAdaptee; + proxy: AzureFunctionInvocationProxy; + createTime: number; + settings: Settings; + constructor(p: AzurePlatformAdaptee, proxy: AzureFunctionInvocationProxy, createTime?: number) { + this.adaptee = p; + this.proxy = proxy; + this.createTime = (!isNaN(createTime) && createTime) || Date.now(); + } + /** + * initiate the class object + * @returns {Promise} void + */ + async init(): Promise { + // CAUTION: adaptee.init() is required. + await this.adaptee.init(); + this.settings = await this.adaptee.loadSettings(); + // has settings been migrated from Function environment variables to db yet? + const settingsSaved = this.settings.get( + AzureFortiGateAutoscaleSetting.AzureFortiGateAutoscaleSettingSaved + ); + // do the settings migration if not yet saved. + if (!(settingsSaved && settingsSaved.truthValue)) { + await this.saveSettings(); + // reload the settings + this.settings = await this.adaptee.loadSettings(); + } + await this.validateSettings(); + } + + /** + * save settings from node environment variables to db + * @returns {Promise} void + */ + async saveSettings(): Promise { + // NOTE: this mapping matches each required setting item key with an existing + // node environment variable. + // key: settingKey, which is a defined setting key. + // value: envKey, which is the env var name used in the Function App node environment. + + // invalidate the cache first + await this.adaptee.reloadSettings(true); + const settingItemMapping: Map = new Map(); + Object.entries(AzureFortiGateAutoscaleSetting).forEach(([k, v]) => { + settingItemMapping.set(k, v); + }); + await Promise.all( + Array.from(settingItemMapping.entries()).map(([, envKey]) => { + const settingItem: SettingItemDefinition = + AzureFortiGateAutoscaleSettingItemDictionary[envKey]; + // if the setting key not exists in either the setting dictionary or the process.env + // return null to be able to filter it out + if (!settingItem) { + return null; + } + return this.saveSettingItem( + settingItem.keyName, + (process.env[envKey] === undefined && 'n/a') || process.env[envKey], + settingItem.description, + settingItem.jsonEncoded, + settingItem.editable + ); + }) + ); + // ASSERT: each saveSettingItem completed. + // save the flag to the db. + const flagItem: SettingItemDefinition = + AzureFortiGateAutoscaleSettingItemDictionary[ + AzureFortiGateAutoscaleSetting.AzureFortiGateAutoscaleSettingSaved + ]; + await this.saveSettingItem( + flagItem.keyName, + 'true', + flagItem.description, + flagItem.jsonEncoded, + flagItem.editable + ); + } + /** + * Save a setting to db + * @param {string} key the setting key + * @param {string} value the setting value + * @param {string} description? the setting description + * @param {boolean} jsonEncoded? is this setting in json encoded format or not + * @param {boolean} editable? is this setting editable + * @returns {Promise} the key name of the saved setting + */ + async saveSettingItem( + key: string, + value: string, + description?: string, + jsonEncoded?: boolean, + editable?: boolean + ): Promise { + const table = new AzureSettings(); + const item = table.downcast({ + settingKey: key, + settingValue: value, + description: description, + jsonEncoded: jsonEncoded, + editable: editable + }); + const savedItem = await this.adaptee.saveItemToDb( + table, + item, + DBDef.SaveCondition.Upsert + ); + return savedItem.settingKey; + } + /** + * Get the request type defined in enum ReqType + * @returns {Promise} the enum value of ReqType + */ + async getRequestType(): Promise { + const reqMethod = await this.proxy.getReqMethod(); + const headers = await this.proxy.getReqHeaders(); + const body = await this.proxy.getReqBody(); + const functionName = this.proxy.context.executionContext.functionName; + if (this.proxy instanceof AzureFunctionHttpTriggerProxy) { + if (functionName === AzureFunctionDef.ByolLicense.name) { + if (reqMethod === ReqMethod.GET) { + if (headers['fos-instance-id'] === null) { + throw new Error( + 'Invalid request. fos-instance-id is missing in [GET] request header.' + ); + } else { + return Promise.resolve(ReqType.ByolLicense); + } + } else { + throw new Error(`Invalid request. Method [${reqMethod}] not allowd`); + } + } else if (functionName === AzureFunctionDef.FortiGateAutoscaleHandler.name) { + if (reqMethod === ReqMethod.GET) { + if (headers['fos-instance-id'] === null) { + throw new Error( + 'Invalid request. fos-instance-id is missing in [GET] request header.' + ); + } else { + return Promise.resolve(ReqType.BootstrapConfig); + } + } else if (reqMethod === ReqMethod.POST) { + if ((body as JSONable).status) { + return Promise.resolve(ReqType.StatusMessage); + } else if (body.instance) { + return Promise.resolve(ReqType.HeartbeatSync); + } else { + throw new Error( + `Invalid request body: [instance: ${body.instance}],` + + ` [status: ${body.status}]` + ); + } + } else { + throw new Error(`Unsupported request method: ${reqMethod}`); + } + } else if (functionName === AzureFunctionDef.FazAuthHandler.name) { + return Promise.resolve(ReqType.CloudFunctionPeerInvocation); + } else if (functionName === AzureFunctionDef.CustomLog.name) { + return Promise.resolve(ReqType.CustomLog); + } else { + throw new Error('Unknown request type.'); + } + } + // NOTE: if request is a service provider request + else if (this.proxy instanceof AzureFunctionServiceProviderProxy) { + switch (body.source) { + case FortiGateAutoscaleServiceRequestSource.FortiGateAutoscale: + return Promise.resolve(ReqType.ServiceProviderRequest); + default: + throw new Error( + `Unsupported CloudFunctionProxy. Request: ${JSON.stringify( + this.proxy.request + )}` + ); + } + } else { + throw new Error(`Unsupported CloudFunctionProxy: ${JSON.stringify(this.proxy)}`); + } + } + async getReqDeviceSyncInfo(): Promise { + const reqType: ReqType = await this.getRequestType(); + if (reqType === ReqType.HeartbeatSync || reqType === ReqType.StatusMessage) { + const body = await this.proxy.getReqBody(); + const deviceSyncInfo: DeviceSyncInfo = { + // always available + instance: (body.instance && String(body.instance)) || null, + interval: (body.interval && Number(body.interval)) || NaN, + // partially available in some request types + status: (body.status && String(body.status)) || undefined, + // NOTE: partially available in some device versions + sequence: (body.sequence && Number(body.sequence)) || NaN, + time: (body.time && String(body.time)) || null, + syncTime: (body.sync_time && String(body.sync_time)) || null, + syncFailTime: (body.sync_fail_time && String(body.sync_fail_time)) || null, + syncStatus: (body.sync_status !== null && Boolean(body.sync_status)) || null, + isPrimary: (body.is_primary !== null && Boolean(body.is_primary)) || null, + checksum: (body.checksum !== null && String(body.checksum)) || null + }; + return deviceSyncInfo; + } else { + return null; + } + } + /** + * Get the heartbeat interval passing by the request called by a FortiGate + * @returns {Promise} heartbeat interval + */ + async getReqHeartbeatInterval(): Promise { + const deviceSyncInfo = await this.getReqDeviceSyncInfo(); + return (deviceSyncInfo && deviceSyncInfo.interval) || NaN; + } + /** + * Get the vm id passing by the request called by a FortiGate. + * The vm id is the 'vmId' property of a virtual machine. + * @returns {Promise} vmId + */ + async getReqVmId(): Promise { + const reqMethod = await this.proxy.getReqMethod(); + if (reqMethod === ReqMethod.GET) { + const headers = await this.proxy.getReqHeaders(); + return Promise.resolve(headers['fos-instance-id'] as string); + } else if (reqMethod === ReqMethod.POST) { + const body = await this.proxy.getReqBody(); + return Promise.resolve(body.instance as string); + } else { + throw new Error(`Cannot get vm id in unsupported request method: ${reqMethod}`); + } + } + /** + * Return the JSON stringified request. + * @returns {Promise} request as a string + */ + getReqAsString(): Promise { + return Promise.resolve(JSON.stringify(this.proxy.request)); + } + /** + * Get the full list of Autoscale Setting items from db + * @returns {Promise} Settings (a map) + */ + getSettings(): Promise { + return Promise.resolve(this.settings); + } + /** + * Validate the loaded settings to ensure setting item integrity. + * @returns {Promise} validation passed is true or false + */ + validateSettings(): Promise { + const required = [ + AzureFortiGateAutoscaleSetting.AutoscaleHandlerUrl, + AzureFortiGateAutoscaleSetting.FortiGatePskSecret, + AzureFortiGateAutoscaleSetting.FortiGateSyncInterface, + AzureFortiGateAutoscaleSetting.FortiGateTrafficPort, + AzureFortiGateAutoscaleSetting.FortiGateAdminPort, + AzureFortiGateAutoscaleSetting.HeartbeatInterval, + AzureFortiGateAutoscaleSetting.ByolScalingGroupName, + AzureFortiGateAutoscaleSetting.PaygScalingGroupName + ]; + const missingKeys = required.filter(key => !this.settings.has(key)).join(', '); + if (missingKeys) { + throw new Error(`The following required setting item not found: ${missingKeys}`); + } + return Promise.resolve(true); + } + /** + * map an Azure vm object into the Autoscale VirtualMachine class object. + * @param {AzureComputeModels.VirtualMachineScaleSetVM} instance vm instance to map + * @param {string} scalingGroupName the scaling group containing the vm instance + * @param {AzureNetworkModels.NetworkInterface[]} nics network interfaces associated with this + * vm instance. + * @returns {VirtualMachine} an Autoscale VirtualMachine class object + */ + protected mapVm( + instance: AzureComputeModels.VirtualMachineScaleSetVM, + scalingGroupName: string, + nics: AzureNetworkModels.NetworkInterface[] + ): VirtualMachine { + let state: VirtualMachineState; + let provisioningState: string; + let powerState: string; + + this.proxy.logAsInfo('instance in map vm', instance); + this.proxy.logAsInfo('Scaling group name', scalingGroupName); + this.proxy.logAsInfo('nics in map vm', nics); + + if (instance.instanceView && instance.instanceView.statuses) { + instance.instanceView.statuses.forEach(s => { + if (s.code.includes('ProvisioningState')) { + provisioningState = s.code.split('/')[1]; + } else if (s.code.includes('PowerState')) { + powerState = s.code.split('/')[1]; + } + }); + } + + this.proxy.logAsInfo('powerState', powerState); + // NOTE: see: https://docs.microsoft.com/en-us/azure/virtual-machines/states-lifecycle + // there's no terminated state for a vm in Azure because terminated vm will not be visible. + if (powerState === 'running') { + state = VirtualMachineState.Running; + } else if (powerState === 'stopped') { + state = VirtualMachineState.Stopped; + } else if (powerState === 'deallocated') { + state = VirtualMachineState.Deallocated; + } else if (powerState === 'starting') { + state = VirtualMachineState.Starting; + } else if (powerState === 'stopping') { + state = VirtualMachineState.Stopping; + } else if (provisioningState === 'updating') { + state = VirtualMachineState.Updating; + } else if (provisioningState === 'creating') { + state = VirtualMachineState.Creating; + } else if (provisioningState === 'deleting') { + state = VirtualMachineState.Terminating; + } else { + state = VirtualMachineState.Unknown; + } + + this.proxy.logAsInfo('state', state); + + // network interface + const networkInterfaces = nics.map((nic, index) => { + return this.mapNic(nic, index); + }); + const primaryNic = networkInterfaces.length > 0 && networkInterfaces[0]; + const vm: VirtualMachine = { + id: instance.vmId, + scalingGroupName: scalingGroupName, + primaryPrivateIpAddress: primaryNic && primaryNic.privateIpAddress, + // TODO: vm in virtual machine scale set is associated with a load balancer and use + // port forwarding to route incoming traffic. So implementation to retrieve the public + // ip address of the load balancer would be needed when there's a need to use that + // public ip address in a feature. Since retrieving information about the load balancer + // also counts toward the arm request limit (see: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/request-limits-and-throttling) + // but there's no feature requires the public ip so far, we don't retrieve the public + // ip address unless further requirements. + primaryPublicIpAddress: null, + virtualNetworkId: primaryNic && primaryNic.virtualNetworkId, + subnetId: primaryNic && primaryNic.subnetId, + securityGroups: [], + networkInterfaces: networkInterfaces, + networkInterfaceIds: networkInterfaces.map(nic => nic.id), + sourceData: {}, + state: state + }; + Object.assign(vm.sourceData, instance); + return vm; + } + /** + * map an Azure network interface object to an Autoscale NetworkInterface class object + * @param {AzureNetworkModels.NetworkInterface} eni the Azure network interface object to map + * @param {number} index the index of the logical position of the eni in the device + * @returns {NetworkInterface} the Autoscale NetworkInterface class object + */ + protected mapNic(eni: AzureNetworkModels.NetworkInterface, index: number): NetworkInterface { + this.proxy.logAsInfo('eni', eni); + const [primaryIpConfiguration] = + (eni && eni.ipConfigurations.filter(ipConfig => ipConfig.primary)) || []; + this.proxy.logAsInfo('primaryIpConfiguration', primaryIpConfiguration); + const matchVNet = primaryIpConfiguration.subnet.id.match( + new RegExp('(?<=virtualNetworks/).*(?=/subnets)') + ); + this.proxy.logAsInfo('matchVNet', matchVNet); + + const matchSubnet = primaryIpConfiguration.subnet.id.match(new RegExp('(?<=subnets/).*')); + this.proxy.logAsInfo('matchSubnet', matchSubnet); + + const nic: NetworkInterface = { + id: eni.id, + privateIpAddress: primaryIpConfiguration && primaryIpConfiguration.privateIPAddress, + index: index, + subnetId: Array.isArray(matchSubnet) && matchSubnet[0], + virtualNetworkId: Array.isArray(matchVNet) && matchVNet[0], + attachmentId: undefined, // NOTE: no attachment defined for nic in the Azure platform + description: undefined // NOTE: no description defined for nic in the Azure platform + }; + + this.proxy.logAsInfo('nic', nic); + + return nic; + } + + // describeScalingGroup(scalingGroupName: string): + /** + * Get the virtual machine (representing a FortiGate) that made the request to the Autoscale + * function. + * @returns {Promise} the requesting vm + */ + async getTargetVm(): Promise { + this.proxy.logAsInfo('calling getTargetVm'); + const byolGroupName = this.settings.get( + AzureFortiGateAutoscaleSetting.ByolScalingGroupName + ).value; + const paygGroupName = this.settings.get( + AzureFortiGateAutoscaleSetting.PaygScalingGroupName + ).value; + + this.proxy.logAsInfo('byolGroupName', byolGroupName); + this.proxy.logAsInfo('paygGroupName', paygGroupName); + + // try to find vm in the byol scaling group + let describeInstanceResult: ApiCache; + let instance: AzureComputeModels.VirtualMachineScaleSetVM; + let scalingGroupName: string; + const vmId: string = await this.getReqVmId(); + this.proxy.logAsInfo('vmId', vmId); + + try { + scalingGroupName = byolGroupName; + describeInstanceResult = await this.adaptee.describeInstance(scalingGroupName, vmId); + this.proxy.logAsInfo('describeInstanceResult', describeInstanceResult); + + // try to find vm in the payg scaling group if not found in byol group + if (!describeInstanceResult.result) { + scalingGroupName = paygGroupName; + describeInstanceResult = await this.adaptee.describeInstance( + scalingGroupName, + vmId + ); + } + // ASSERT: the vm exists in either the byol or the payg scaling group. + instance = describeInstanceResult.result; + this.proxy.logAsInfo('instance', instance); + if (!instance) { + throw new Error(`vm (vmId: ${vmId}) not found in any scaling group.`); + } + } catch (error) { + this.proxy.logForError('cannot get target vm', error); + throw error; + } + // ASSERT: the vm is found. + // get network interfaces + const listNetworkInterfacesResult = await this.adaptee.listNetworkInterfaces( + scalingGroupName, + Number(instance.instanceId), + ApiCacheOption.ReadCacheFirst, + describeInstanceResult.ttl + ); + + this.proxy.logAsInfo('scalingGroupName', scalingGroupName); + this.proxy.logAsInfo('listNetworkInterfacesResult', listNetworkInterfacesResult); + + const nics = listNetworkInterfacesResult.result.filter(nic => nic); + const vm: VirtualMachine = this.mapVm(instance, scalingGroupName, nics); + this.proxy.logAsInfo('vm', vm); + this.proxy.logAsInfo('called getTargetVm'); + return vm; + } + /** + * Get the primary virtual machine (representing a FortiGate) that was elected in the + * Autoscale cluster + * @returns {Promise} the primary vm in the Autoscale cluster + */ + async getPrimaryVm(): Promise { + this.proxy.logAsInfo('calling getPrimaryVm'); + const primaryRecord = await this.getPrimaryRecord(); + if (!primaryRecord) { + return null; + } + const describeInstanceResult = await this.adaptee.describeInstance( + primaryRecord.scalingGroupName, + primaryRecord.vmId + ); + let vm: VirtualMachine; + if (describeInstanceResult.result) { + // get network interfaces + const listNetworkInterfacesResult = await this.adaptee.listNetworkInterfaces( + primaryRecord.scalingGroupName, + Number(describeInstanceResult.result.instanceId), + ApiCacheOption.ReadCacheFirst, + describeInstanceResult.ttl + ); + const nics = listNetworkInterfacesResult.result; + vm = this.mapVm(describeInstanceResult.result, primaryRecord.scalingGroupName, nics); + } + this.proxy.logAsInfo('called getPrimaryVm'); + return vm; + } + + async getVmById(vmId: string, scalingGroupName: string): Promise { + this.proxy.logAsInfo('calling getVmById'); + if (!scalingGroupName) { + this.proxy.logAsInfo('called getVmById'); + return null; + } + const describeInstanceResult = await this.adaptee.describeInstance(scalingGroupName, vmId); + let vm: VirtualMachine; + if (describeInstanceResult.result) { + // get network interfaces + const listNetworkInterfacesResult = await this.adaptee.listNetworkInterfaces( + scalingGroupName, + Number(describeInstanceResult.result.instanceId), + ApiCacheOption.ReadCacheFirst, + describeInstanceResult.ttl + ); + const nics = listNetworkInterfacesResult.result; + vm = this.mapVm(describeInstanceResult.result, scalingGroupName, nics); + } + this.proxy.logAsInfo('called getVmById'); + return vm; + } + /** + * List all vm instances of a certain scaling group + * @param {string} scalingGroupName the scaling group name to list + * @param {boolean} withNics whether retrieve the nic of each vm or not + * @returns {Promise} an array of {scalingGroupName, vm instance, its nics(if requested)} + */ + private async listScalingGroupInstances( + scalingGroupName: string, + withNics = false + ): Promise< + { + scalingGroupName: string; + instance: AzureComputeModels.VirtualMachineScaleSetVM; + nics: AzureNetworkModels.NetworkInterface[]; + }[] + > { + const listInstancesResults = await this.adaptee.listInstances(scalingGroupName); + return await Promise.all( + listInstancesResults.result.map(async instance => { + const res: { + scalingGroupName: string; + instance: AzureComputeModels.VirtualMachineScaleSetVM; + nics: AzureNetworkModels.NetworkInterface[]; + } = { + scalingGroupName: scalingGroupName, + instance: instance, + nics: [] + }; + if (withNics) { + const listNetworkInterfacesResult = await this.adaptee.listNetworkInterfaces( + scalingGroupName, + Number(instance.instanceId) + ); + res.nics = listNetworkInterfacesResult.result || []; + } + return res; + }) + ); + } + /** + * List all vm instances in each scaling group of the Autoscale cluster + * @param {boolean} identifyScalingGroup (unused parameter) + * @param {boolean} listNic whether retrieve the nic of each vm or not + * @returns {Promise} a list of all vm instances in the Autoscale cluster + */ + async listAutoscaleVm( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + identifyScalingGroup?: boolean, // this variable required by the implementation but isn't used + listNic?: boolean + ): Promise { + this.proxy.logAsInfo('calling listAutoscaleVm'); + // NOTE: need to list vm in both byol and payg groups + const byolGroupName = this.settings.get( + AzureFortiGateAutoscaleSetting.ByolScalingGroupName + ).value; + const paygGroupName = this.settings.get( + AzureFortiGateAutoscaleSetting.PaygScalingGroupName + ).value; + const instances = [ + ...(await this.listScalingGroupInstances(byolGroupName, listNic)), + ...(await this.listScalingGroupInstances(paygGroupName, listNic)) + ]; + + this.proxy.logAsInfo('byolGroupName', byolGroupName); + this.proxy.logAsInfo('paygGroupName', paygGroupName); + this.proxy.logAsInfo('instances in listAutoscaleVm', instances); + + const vms = instances.map(item => + this.mapVm(item.instance, item.scalingGroupName, item.nics) + ); + + this.proxy.logAsInfo('vms', vms); + this.proxy.logAsInfo('called listAutoscaleVm'); + return vms; + } + + /** + * Get the Autoscale health check record of a vm with the given vmId + * @param {string} vmId the vmId property of the vm. + * @returns {Promise} the health check record + */ + async getHealthCheckRecord(vmId: string): Promise { + this.proxy.logAsInfo('calling getHealthCheckRecord'); + const settings = await this.getSettings(); + const table = new AzureAutoscale(); + const dbItem = await this.adaptee.getItemFromDb(table, [ + { + key: table.primaryKey.name, + value: vmId + } + ]); + + let record: HealthCheckRecord; + + if (dbItem) { + record = this.parseHealthCheckRecord(dbItem, settings); + } + this.proxy.logAsInfo('called getHealthCheckRecord'); + return record; + } + + private parseHealthCheckRecord( + dbItem: DBDef.AutoscaleDbItem, + settings: Settings + ): HealthCheckRecord { + const maxHeartbeatLossCountSettingItem = settings.get( + AzureFortiGateAutoscaleSetting.HeartbeatLossCount + ); + + const heartbeatDelayAllowanceSettingItem = settings.get( + AzureFortiGateAutoscaleSetting.HeartbeatDelayAllowance + ); + + const maxHeartbeatLossCount: number = + (maxHeartbeatLossCountSettingItem && Number(maxHeartbeatLossCountSettingItem.value)) || + 0; + + const heartbeatDelayAllowance: number = + (heartbeatDelayAllowanceSettingItem && + Number(heartbeatDelayAllowanceSettingItem.value)) * 1000 || 0; + + // if heartbeatDelay is <= 0, it means hb arrives early or ontime + const heartbeatDelay = this.createTime - dbItem.nextHeartBeatTime - heartbeatDelayAllowance; + + const [syncState] = Object.entries(HealthCheckSyncState) + .filter(([, value]) => { + return dbItem.syncState === value; + }) + .map(([, v]) => v); + + const nextHeartbeatLossCount = dbItem.heartBeatLossCount + ((heartbeatDelay > 0 && 1) || 0); + + const remainingLossAllowed = Math.max(maxHeartbeatLossCount - nextHeartbeatLossCount, 0); + // healthy reason: next heartbeat loss count is smaller than max allowed value. + const isHealthy = remainingLossAllowed > 0; + + // dumb period + const irresponsiveTimeFromNextHeartbeat = Math.max( + this.createTime - dbItem.nextHeartBeatTime, + 0 + ); + const irresponsivePeriod = + dbItem.heartBeatInterval > 0 + ? Math.floor(irresponsiveTimeFromNextHeartbeat / dbItem.heartBeatInterval) + : 0; + + return { + vmId: dbItem.vmId, + scalingGroupName: dbItem.scalingGroupName, + ip: dbItem.ip, + primaryIp: dbItem.primaryIp, + heartbeatInterval: dbItem.heartBeatInterval, + heartbeatLossCount: dbItem.heartBeatLossCount, + nextHeartbeatTime: dbItem.nextHeartBeatTime, + syncState: syncState, + // if the prop doesn't exist in item set it to 0 by default + syncRecoveryCount: dbItem.syncRecoveryCount || 0, + seq: dbItem.seq, + healthy: isHealthy, + upToDate: true, + // the following properities are only available in some device versions + // convert string 'null' to null + sendTime: dbItem.sendTime === 'null' ? null : dbItem.sendTime, + deviceSyncTime: dbItem.deviceSyncTime === 'null' ? null : dbItem.deviceSyncTime, + deviceSyncFailTime: + dbItem.deviceSyncFailTime === 'null' ? null : dbItem.deviceSyncFailTime, + deviceSyncStatus: ['true', 'false'].includes(dbItem.deviceSyncStatus) + ? dbItem.deviceSyncStatus === 'true' + : null, + deviceIsPrimary: ['true', 'false'].includes(dbItem.deviceIsPrimary) + ? dbItem.deviceIsPrimary === 'true' + : null, + deviceChecksum: dbItem.deviceChecksum === 'null' ? null : dbItem.deviceChecksum, + irresponsivePeriod: irresponsivePeriod, + remainingLossAllowed: remainingLossAllowed + }; + } + + async listHealthCheckRecord(): Promise { + this.proxy.logAsInfo('calling listHealthCheckRecord'); + const settings = await this.getSettings(); + const table = new AzureAutoscale(); + const queryResult = await this.adaptee.listItemFromDb(table); + const dbItems = queryResult.result || []; + const records: HealthCheckRecord[] = dbItems.map(dbItem => { + return this.parseHealthCheckRecord(dbItem, settings); + }); + + this.proxy.logAsInfo('called listHealthCheckRecord'); + return records; + } + + /** + * Get the Autoscale health check record of the elected primary vm + * @param {DBDef.KeyValue[]} filters optional filter to match the record or null if not match + * @returns {Promise} the health check record + */ + async getPrimaryRecord(filters?: DBDef.KeyValue[]): Promise { + this.proxy.logAsInfo('calling getPrimaryRecord'); + const table = new AzurePrimaryElection(); + const listClause: CosmosDBQueryWhereClause[] = + filters && + filters.map(f => { + return { name: f.key, value: f.value }; + }); + // ASSERT: there's only 1 matching primary record or no matching record. + const queryResult = await this.adaptee.listItemFromDb( + table, + listClause, + 1 + ); + const [record] = queryResult.result || []; + let primaryRecord: PrimaryRecord; + if (record) { + const [voteState] = Object.entries(PrimaryRecordVoteState) + .filter(([, value]) => { + return record.voteState === value; + }) + .map(([, v]) => v); + const voteTimedOut = + voteState !== PrimaryRecordVoteState.Done && + Number(record.voteEndTime) < Date.now(); + primaryRecord = { + id: record.id, + vmId: record.vmId, + ip: record.ip, + scalingGroupName: record.scalingGroupName, + virtualNetworkId: record.virtualNetworkId, + subnetId: record.subnetId, + voteEndTime: Number(record.voteEndTime), + voteState: (voteTimedOut && PrimaryRecordVoteState.Timeout) || voteState + }; + } + + this.proxy.logAsInfo('called getPrimaryRecord'); + return primaryRecord; + } + /** + * The implementation for a comparison method for VirtualMachine class objects + * @param {VirtualMachine} vmA vm A to compare with + * @param {VirtualMachine} vmB vm B to compare with + * @returns {boolean} true if only they are deemed 'the same'. + */ + vmEquals(vmA?: VirtualMachine, vmB?: VirtualMachine): boolean { + if (!(vmA && vmB)) { + return false; + } + const keyDiff = [ + 'id', + 'scalingGroupName', + 'primaryPrivateIpAddress', + 'virtualNetworkId', + 'subnetId' + ].filter(k => vmA[k] !== vmB[k]); + + return keyDiff.length === 0; + } + /** + * upsert an Autoscale health check record + * @param {HealthCheckRecord} rec the health check record to save + * @returns {Promise} void + */ + async createHealthCheckRecord(rec: HealthCheckRecord): Promise { + this.proxy.logAsInfo('calling createHealthCheckRecord'); + const table = new AzureAutoscale(); + const [syncStateString] = Object.entries(HealthCheckSyncState) + .filter(([, value]) => { + return rec.syncState === value; + }) + .map(([, v]) => v); + const item = table.downcast({ + vmId: rec.vmId, + scalingGroupName: rec.scalingGroupName, + ip: rec.ip, + primaryIp: rec.primaryIp, + heartBeatInterval: rec.heartbeatInterval, + heartBeatLossCount: rec.heartbeatLossCount, + nextHeartBeatTime: rec.nextHeartbeatTime, + syncState: syncStateString, + syncRecoveryCount: rec.syncRecoveryCount, + seq: rec.seq, + sendTime: rec.sendTime, + deviceSyncTime: rec.deviceSyncTime, + deviceSyncFailTime: rec.deviceSyncFailTime, + // store boolean | null + deviceSyncStatus: + (rec.deviceSyncStatus === null && 'null') || + (rec.deviceSyncStatus && 'true') || + 'false', + // store boolean | null + deviceIsPrimary: + (rec.deviceIsPrimary === null && 'null') || + (rec.deviceIsPrimary && 'true') || + 'false', + deviceChecksum: rec.deviceChecksum + }); + // NOTE: when create a db record, do not need to check data consistency. + await this.adaptee.saveItemToDb( + table, + item, + DBDef.SaveCondition.InsertOnly, + false + ); + this.proxy.logAsInfo('called createHealthCheckRecord'); + } + /** + * update an existing Autoscale health check record. + * @param {HealthCheckRecord} rec record to update + * @returns {Promise} void + */ + async updateHealthCheckRecord(rec: HealthCheckRecord): Promise { + this.proxy.logAsInfo('calling updateHealthCheckRecord'); + const table = new AzureAutoscale(); + const [syncStateString] = Object.entries(HealthCheckSyncState) + .filter(([, value]) => { + return rec.syncState === value; + }) + .map(([, v]) => v); + let deviceSyncStatus: string; + if (rec.deviceSyncStatus === null) { + deviceSyncStatus = 'null'; + } else if (rec.deviceSyncStatus) { + deviceSyncStatus = 'true'; + } else { + deviceSyncStatus = 'false'; + } + let deviceIsPrimary: string; + if (rec.deviceIsPrimary === null) { + deviceIsPrimary = 'null'; + } else if (rec.deviceIsPrimary) { + deviceIsPrimary = 'true'; + } else { + deviceIsPrimary = 'false'; + } + const item = table.downcast({ + vmId: rec.vmId, + scalingGroupName: rec.scalingGroupName, + ip: rec.ip, + primaryIp: rec.primaryIp, + heartBeatInterval: rec.heartbeatInterval, + heartBeatLossCount: rec.heartbeatLossCount, + nextHeartBeatTime: rec.nextHeartbeatTime, + syncState: syncStateString, + syncRecoveryCount: rec.syncRecoveryCount, + seq: rec.seq, + sendTime: rec.sendTime, + deviceSyncTime: rec.deviceSyncTime, + deviceSyncFailTime: rec.deviceSyncFailTime, + // store boolean | null + deviceSyncStatus: deviceSyncStatus, + // store boolean | null + deviceIsPrimary: deviceIsPrimary, + deviceChecksum: rec.deviceChecksum + }); + + const checker = ( + snapshot: typeof item + ): Promise<{ result: boolean; errorMessage: string }> => { + const propToCheck = { + vmId: rec.vmId, + scalingGroupName: rec.scalingGroupName + }; + const difference = this.dataDiff(propToCheck, snapshot); + // NOTE: strictly update the record when the sequence to update is equal to or greater + // than the seq in the db to ensure data not to fall back to old value in race conditions + let noError = true; + let errorMessage = ''; + if (Object.keys(difference).length) { + noError = false; + errorMessage = Object.keys(difference) + .map(k => { + return `key: ${k}, expected: ${propToCheck[k]}, existing: ${difference[k]};`; + }) + .join(' '); + } + // NOTE: allow sequence smaller than the stored one only if the sendTime is older + // the reason is if the device is rebooted, the seq value is reset to 0, then the seq + // becomes smaller than the stored one. However, the sendTime is still greater + // Therefore, do not allow smaller seq while sendTime is also smaller. This is the case + // when the request is out-of-date. + if (rec.seq < snapshot.seq && rec.sendTime <= snapshot.sendTime) { + noError = false; + errorMessage = + `The seq (${rec.seq}) and send time (${rec.sendTime}) indicate that ` + + `this request is out-of-date. values in db (seq: ${snapshot.seq}, ` + + `send time: ${snapshot.sendTime}). ${errorMessage}`; + } + return Promise.resolve({ + result: noError, + errorMessage: errorMessage + }); + }; + await this.adaptee.saveItemToDb( + table, + item, + DBDef.SaveCondition.Upsert, + checker + ); + this.proxy.logAsInfo('called updateHealthCheckRecord'); + } + async deleteHealthCheckRecord(rec: HealthCheckRecord): Promise { + this.proxy.logAsInfo('calling deleteHealthCheckRecord'); + const table = new AzureAutoscale(); + const [syncStateString] = Object.entries(HealthCheckSyncState) + .filter(([, value]) => { + return rec.syncState === value; + }) + .map(([, v]) => v); + let deviceSyncStatus: string; + if (rec.deviceSyncStatus === null) { + deviceSyncStatus = 'null'; + } else if (rec.deviceSyncStatus) { + deviceSyncStatus = 'true'; + } else { + deviceSyncStatus = 'false'; + } + let deviceIsPrimary: string; + if (rec.deviceIsPrimary === null) { + deviceIsPrimary = 'null'; + } else if (rec.deviceIsPrimary) { + deviceIsPrimary = 'true'; + } else { + deviceIsPrimary = 'false'; + } + const item = table.downcast({ + vmId: rec.vmId, + scalingGroupName: rec.scalingGroupName, + ip: rec.ip, + primaryIp: rec.primaryIp, + heartBeatInterval: rec.heartbeatInterval, + heartBeatLossCount: rec.heartbeatLossCount, + nextHeartBeatTime: rec.nextHeartbeatTime, + syncState: syncStateString, + syncRecoveryCount: rec.syncRecoveryCount, + seq: rec.seq, + sendTime: rec.sendTime, + deviceSyncTime: rec.deviceSyncTime, + deviceSyncFailTime: rec.deviceSyncFailTime, + // store boolean | null + deviceSyncStatus: deviceSyncStatus, + // store boolean | null + deviceIsPrimary: deviceIsPrimary, + deviceChecksum: rec.deviceChecksum + }); + + await this.adaptee.deleteItemFromDb(table, item); + this.proxy.logAsInfo('called deleteHealthCheckRecord'); + } + /** + * insert a primary record, not overwrite one with the same primary key. + * can also optionally replace an existing one with a given primary key value + * @param {PrimaryRecord} rec primary record to insert + * @param {PrimaryRecord} oldRec existing primary record to replace + * @returns {Promise} void + */ + async createPrimaryRecord(rec: PrimaryRecord, oldRec: PrimaryRecord): Promise { + this.proxy.logAsInfo('calling createPrimaryRecord.'); + const table = new AzurePrimaryElection(); + const item = table.downcast({ + id: rec.id, + scalingGroupName: rec.scalingGroupName, + ip: rec.ip, + vmId: rec.vmId, + virtualNetworkId: rec.virtualNetworkId, + subnetId: rec.subnetId, + voteEndTime: rec.voteEndTime, + voteState: rec.voteState + }); + // save record only if record for a certain scaling group name not exists, or + // if it exists but timeout. + // if specified an old rec to purge, use a strict conditional expression to replace. + try { + if (oldRec) { + this.proxy.logAsInfo( + `purging existing record (id: ${oldRec.id}, ` + + `scalingGroup: ${oldRec.scalingGroupName}, vmId: ${oldRec.vmId})` + ); + const itemToDelete = table.downcast({ ...oldRec }); + // NOTE: the voteState in the db record will be either 'pending' or 'done'. + // As soon as the voteState is still 'pending', and voteEndTime has expired, + // the primary record is deemed timeout. Therefore, the 'timeout' state will + // not need to be updated on the db record. Should alter it to 'pending' when + // deleting. + if (itemToDelete.voteState === PrimaryRecordVoteState.Timeout) { + itemToDelete.voteState = PrimaryRecordVoteState.Pending; + } + // NOTE: if the new and old records are for the same primary vm, and the + // old record indicates that it has timed out, do not need + // to check data consistency. + const consistencyCheckRequired = !( + rec.id === oldRec.id && oldRec.voteState === PrimaryRecordVoteState.Timeout + ); + await this.adaptee.deleteItemFromDb( + table, + itemToDelete, + consistencyCheckRequired + ); + } + } catch (error) { + this.proxy.logForError('DB error.', error); + if (error instanceof DBDef.DbDeleteError) { + this.proxy.logAsError(`Cannot purge old primary record (id: ${oldRec.id})`); + } + throw error; + } + try { + // save the new record + await this.adaptee.saveItemToDb( + table, + item, + DBDef.SaveCondition.InsertOnly // ASSERT: if record exists, will throw error + ); + this.proxy.logAsInfo('called createPrimaryRecord.'); + } catch (error) { + this.proxy.logForError('DB error.', error); + if ( + error instanceof DBDef.DbSaveError && + error.code === DBDef.DbErrorCode.KeyConflict + ) { + this.proxy.logAsError(`Primary record already exists (id: ${item.id})`); + } + this.proxy.logAsInfo('called createPrimaryRecord.'); + throw error; + } + this.proxy.logAsInfo('called createPrimaryRecord.'); + } + /** + * Insert a new primary record or update it only when the primary key is the same. + * @param {PrimaryRecord} rec primary record to update + * @returns {Promise} void + */ + async updatePrimaryRecord(rec: PrimaryRecord): Promise { + this.proxy.logAsInfo('calling updatePrimaryRecord.'); + const table = new AzurePrimaryElection(); + const item = table.downcast({ + id: rec.id, + scalingGroupName: rec.scalingGroupName, + ip: rec.ip, + vmId: rec.vmId, + virtualNetworkId: rec.virtualNetworkId, + subnetId: rec.subnetId, + voteEndTime: rec.voteEndTime, + voteState: rec.voteState + }); + // save record only if the keys in rec match the keys in db + // save record only when the elected primary match the record + // and vote state is still pending and the voting not end yet + let existingRec: typeof item; + try { + existingRec = await this.adaptee.getItemFromDb(table, [ + { + key: table.primaryKey.name, + value: String(rec[table.primaryKey.name]) + } + ]); + } catch (error) { + this.proxy.logAsInfo(`Primary record (id: ${rec.id}) not found. Will create one.`); + } + // if primary record already exists, + // save record only if the keys in rec match the keys in db + if (existingRec) { + if (rec.scalingGroupName !== existingRec.scalingGroupName) { + throw new Error( + 'Primary record value not match on attribute: scalingGroupName.' + + ` Exptected: ${rec.scalingGroupName}, found: ${existingRec.scalingGroupName}` + ); + } else if (existingRec.id !== existingRec.id) { + throw new Error( + 'Primary record value not match on attribute: id.' + + ` Exptected: ${rec.id}, found: ${existingRec.id}` + ); + } else if (existingRec.voteState !== PrimaryRecordVoteState.Pending) { + throw new Error( + 'Primary record vote state not match.' + + ` Expected: ${PrimaryRecordVoteState.Pending}, found: ${existingRec.voteState}` + ); + } else if (existingRec.voteEndTime <= Date.now()) { + throw new Error( + `Primary record vote ended (at ${existingRec.voteEndTime}) already.` + + ` It's ${Date.now()} now.` + ); + } + } + + const checker = ( + snapshot: typeof item + ): Promise<{ result: boolean; errorMessage: string }> => { + const propToCheck = { + id: item.id, + scalingGroupName: item.scalingGroupName + }; + const difference = this.dataDiff(propToCheck, snapshot); + return Promise.resolve({ + result: Object.keys(difference).length === 0, + errorMessage: + Object.keys(difference) + .map(k => { + return `key: ${k}, expected: ${propToCheck[k]}, existing: ${difference[k]};`; + }) + .join(' ') || '' + }); + }; + + // upsert + await this.adaptee.saveItemToDb( + table, + item, + DBDef.SaveCondition.Upsert, + checker + ); + this.proxy.logAsInfo('called updatePrimaryRecord.'); + } + + async deletePrimaryRecord(rec: PrimaryRecord, fullMatch?: boolean): Promise { + this.proxy.logAsInfo('calling updatePrimaryRecord.'); + const table = new AzurePrimaryElection(); + const item = table.downcast({ + id: rec.id, + scalingGroupName: rec.scalingGroupName, + ip: rec.ip, + vmId: rec.vmId, + virtualNetworkId: rec.virtualNetworkId, + subnetId: rec.subnetId, + voteEndTime: rec.voteEndTime, + voteState: rec.voteState + }); + + await this.adaptee.deleteItemFromDb(table, item, fullMatch); + this.proxy.logAsInfo('called updatePrimaryRecord.'); + } + /** + * Load a configset file from blob storage + * The blob container will use the AssetStorageContainer or CustomAssetContainer, + * and the location prefix will use AssetStorageDirectory or CustomAssetDirectory. + * The full file path will be: \/\/configset/\ + * @param {string} name the configset name + * @param {boolean} custom (optional) whether load it from a custom location or not + * @returns {Promise} the configset content as a string + */ + async loadConfigSet(name: string, custom?: boolean): Promise { + this.proxy.logAsInfo(`loading${custom ? ' (custom)' : ''} configset: ${name}`); + const container = custom + ? this.settings.get(AzureFortiGateAutoscaleSetting.CustomAssetContainer) + : this.settings.get(AzureFortiGateAutoscaleSetting.AssetStorageContainer); + const keyPrefix = custom + ? this.settings.get(AzureFortiGateAutoscaleSetting.CustomAssetDirectory) + : this.settings.get(AzureFortiGateAutoscaleSetting.AssetStorageDirectory); + if (!(container && container.value)) { + throw new Error('Missing setting item for: storage container for configset.'); + } + + const filePath = path.posix.join(...[keyPrefix.value, 'configset', name].filter(k => !!k)); + this.proxy.logAsDebug( + `Load blob in container [${container.value}], path:` + `[${filePath}]` + ); + const content = await this.adaptee.getBlobContent(container.value, filePath); + this.proxy.logAsInfo('configset loaded.'); + return content; + } + /** + * List all configset files in a specified blob container location + * The blob container will use the AssetStorageContainer or CustomAssetContainer, + * and the location prefix will use AssetStorageDirectory or CustomAssetDirectory. + * There will be an optional subDirectory provided as parameter. + * The full file path will be: \/\[/\]/configset + * @param {string} subDirectory additional subdirectory + * @param {boolean} custom (optional) whether load it from a custom location or not + * @returns {Promise} the configset content as a string + */ + async listConfigSet(subDirectory?: string, custom?: boolean): Promise { + this.proxy.logAsInfo('calling listConfigSet'); + // it will load configsets from the location: + // in custom mode: /CustomAssetContainer/CustomAssetDirectory[/subDirectory]/configset/ + // in normal mode: /AssetStorageContainer/AssetStorageDirectory[/subDirectory]/configset/ + const container = custom + ? this.settings.get(AzureFortiGateAutoscaleSetting.CustomAssetContainer) + : this.settings.get(AzureFortiGateAutoscaleSetting.AssetStorageContainer); + + const directory = custom + ? this.settings.get(AzureFortiGateAutoscaleSetting.CustomAssetDirectory) + : this.settings.get(AzureFortiGateAutoscaleSetting.AssetStorageDirectory); + let blobs: Blob[] = []; + if (!container.value) { + this.proxy.logAsInfo('No container is specified. No configset loaded.'); + return []; + } + + const location = path.posix.join( + ...[directory.value, subDirectory || null, 'configset'].filter(r => !!r) + ); + + try { + this.proxy.logAsInfo( + `List configset in container: ${container.value}, directory: ${location}` + ); + blobs = await this.adaptee.listBlob(container.value, location); + } catch (error) { + this.proxy.logAsWarning(error); + } + this.proxy.logAsInfo('called listConfigSet'); + return blobs; + } + async deleteVmFromScalingGroup(vmId: string): Promise { + this.proxy.logAsInfo('calling deleteVmFromScalingGroup'); + try { + const vms = await this.listAutoscaleVm(); + const [vm] = vms.filter(v => v.id === vmId) || []; + if (!vm) { + this.proxy.logAsWarning(`vm (id: ${vmId}) not found. skip deleting it.`); + } else { + const scalingGroupName = vm.scalingGroupName; + const success = await this.adaptee.deleteInstanceFromVmss( + scalingGroupName, + Number(vm.sourceData.instanceId) + ); + if (success) { + this.proxy.logAsInfo(`delete completed. vm (id: ${vmId}) is deleted.`); + } else { + this.proxy.logAsWarning(`delete completed. vm (id: ${vmId}) not found.)`); + } + } + } catch (error) { + this.proxy.logForError('Failed to delele vm from scaling group.', error); + } + this.proxy.logAsInfo('called deleteVmFromScalingGroup'); + } + async listLicenseFiles( + storageContainerName: string, + licenseDirectoryName: string + ): Promise { + this.proxy.logAsInfo('calling listLicenseFiles'); + const blobs: Blob[] = await this.adaptee.listBlob( + storageContainerName, + licenseDirectoryName + ); + this.proxy.logAsInfo( + storageContainerName, + licenseDirectoryName, + `file count: ${blobs.length}` + ); + this.proxy.logAsDebug('blobs:', JSON.stringify(blobs)); + const licenseFiles = await Promise.all( + blobs.map(async blob => { + const filePath = path.posix.join(licenseDirectoryName, blob.fileName); + const content = await this.adaptee.getBlobContent(storageContainerName, filePath); + const algorithm = 'sha256'; + const licenseFile: LicenseFile = { + fileName: blob.fileName, + checksum: genChecksum(content, algorithm), + algorithm: algorithm, + content: content + }; + return licenseFile; + }) + ); + this.proxy.logAsInfo('calling listLicenseFiles'); + return licenseFiles; + } + async listLicenseStock(productName: string): Promise { + this.proxy.logAsInfo('calling listLicenseStock'); + const table = new AzureLicenseStock(); + const queryResult = await this.adaptee.listItemFromDb(table); + const dbItems = queryResult.result || []; + const mapItems = dbItems + .filter(item => item.productName === productName) + .map(item => { + return { + fileName: item.fileName, + checksum: item.checksum, + algorithm: item.algorithm, + productName: item.productName + } as LicenseStockRecord; + }); + this.proxy.logAsInfo('called listLicenseStock'); + return mapItems; + } + async listLicenseUsage(productName: string): Promise { + this.proxy.logAsInfo('calling listLicenseUsage'); + const table = new AzureLicenseUsage(); + const queryResult = await this.adaptee.listItemFromDb(table); + const dbItems = queryResult.result || []; + const mapItems = dbItems + .filter(item => item.productName === productName) + .map(item => { + return { + fileName: item.fileName, + checksum: item.checksum, + algorithm: item.algorithm, + productName: item.productName, + vmId: item.vmId, + scalingGroupName: item.scalingGroupName, + assignedTime: item.assignedTime, + vmInSync: item.vmInSync + } as LicenseUsageRecord; + }); + this.proxy.logAsInfo('called listLicenseUsage'); + return mapItems; + } + async updateLicenseStock(records: LicenseStockRecord[]): Promise { + this.proxy.logAsInfo('calling updateLicenseStock'); + const table = new AzureLicenseStock(); + const queryResult = await this.adaptee.listItemFromDb(table); + const dbItems = queryResult.result || []; + // load all license stock records in the db + const items = new Map( + dbItems.map(item => { + return [item.checksum, item]; + }) + ); + let errorCount = 0; + const stockRecordChecksums = Array.from(items.keys()); + await Promise.all( + // read the content of each license file + records.map(record => { + const item = table.downcast({ + checksum: record.checksum, + algorithm: record.algorithm, + fileName: record.fileName, + productName: record.productName + }); + let typeText: string; + let saveCondition: DBDef.SaveCondition; + // recrod exists, update it + if (items.has(record.checksum)) { + stockRecordChecksums.splice(stockRecordChecksums.indexOf(record.checksum), 1); + saveCondition = DBDef.SaveCondition.UpdateOnly; + typeText = + `update existing item (filename: ${record.fileName},` + + ` checksum: ${record.checksum})`; + } else { + saveCondition = DBDef.SaveCondition.Upsert; + typeText = + `create new item (filename: ${record.fileName},` + + ` checksum: ${record.checksum})`; + } + return this.adaptee + .saveItemToDb(table, item, saveCondition, false) + .catch(err => { + this.proxy.logForError(`Failed to ${typeText}.`, err); + errorCount++; + }); + }) + ); + // remove those records which don't have a corresponding license file. + await Promise.all( + stockRecordChecksums.map(checksum => { + const item = items.get(checksum); + return this.adaptee + .deleteItemFromDb(table, item) + .catch(err => { + this.proxy.logForError( + `Failed to delete item (filename: ${item.fileName}) from db.`, + err + ); + errorCount++; + }); + }) + ); + if (errorCount > 0) { + this.proxy.logAsInfo('called updateLicenseStock'); + + throw new Error('updateLicenseStock unsuccessfully.'); + } + this.proxy.logAsInfo('called updateLicenseStock'); + } + async updateLicenseUsage( + records: { item: LicenseUsageRecord; reference: LicenseUsageRecord }[] + ): Promise { + this.proxy.logAsInfo('calling updateLicenseUsage'); + const table = new AzureLicenseUsage(); + // get all records from the db as a snapshot + const queryResult = await this.adaptee.listItemFromDb(table); + const dbItems = queryResult.result || []; + const items = new Map( + dbItems.map(item => { + return [item.checksum, item]; + }) + ); + let errorCount = 0; + await Promise.all( + records.map(rec => { + const item = table.downcast({ + checksum: rec.item.checksum, + algorithm: rec.item.algorithm, + fileName: rec.item.fileName, + productName: rec.item.productName, + vmId: rec.item.vmId, + scalingGroupName: rec.item.scalingGroupName, + assignedTime: rec.item.assignedTime, + vmInSync: rec.item.vmInSync + }); + let typeText: string; + let saveCondition: DBDef.SaveCondition; + // update if record exists + // NOTE: for updating an existing record, it requires a reference of the existing + // record as a snapshot of db data. Only when the record data at the time of updating + // matches exactly the same as the snapshot, the update succeeds. Otherwise, the + // record is considered changed, and inconsistent anymore, thus not allowing updating. + if (items.has(rec.item.checksum)) { + // ASSERT: it must have a referenced record to replace. otherwise, if should fail + if (!rec.reference) { + typeText = `update existing item (checksum: ${rec.item.checksum}). `; + this.proxy.logAsError( + `Failed to ${typeText}. No referenced record specified.` + ); + errorCount++; + return Promise.resolve(); + } + saveCondition = DBDef.SaveCondition.UpdateOnly; + + const checker = ( + snapshot: typeof item + ): Promise<{ result: boolean; errorMessage: string }> => { + const propToCheck = { + vmId: rec.reference.vmId, + scalingGroupName: rec.reference.scalingGroupName, + productName: rec.reference.productName, + algorithm: rec.reference.algorithm + }; + const difference = this.dataDiff(propToCheck, snapshot); + return Promise.resolve({ + result: Object.keys(difference).length === 0, + errorMessage: + Object.keys(difference) + .map(k => { + return `key: ${k}, expected: ${propToCheck[k]}, existing: ${difference[k]};`; + }) + .join(' ') || '' + }); + }; + typeText = + `update existing item (checksum: ${rec.reference.checksum}). ` + + `Old values (filename: ${rec.reference.fileName}, ` + + `vmId: ${rec.reference.vmId}, ` + + `scalingGroupName: ${rec.reference.scalingGroupName}, ` + + `productName: ${rec.reference.productName}, ` + + `algorithm: ${rec.reference.algorithm}, ` + + `assignedTime: ${rec.reference.assignedTime}).` + + `New values (filename: ${item.fileName}, vmId: ${item.vmId}, ` + + `scalingGroupName: ${item.scalingGroupName}, ` + + `productName: ${item.productName}, algorithm: ${item.algorithm})`; + // NOTE: must ensure the consistency because the updating of the usage record + // is expected to happen with a race condition. + return this.adaptee + .saveItemToDb(table, item, saveCondition, checker) + .then(() => { + this.proxy.logAsInfo(typeText); + }) + .catch(err => { + this.proxy.logForError(`Failed to ${typeText}.`, err); + errorCount++; + }); + } + // create if record not exists + else { + saveCondition = DBDef.SaveCondition.InsertOnly; + typeText = + `create new item (checksum: ${item.checksum})` + + `New values (filename: ${item.fileName}, vmId: ${item.vmId}, ` + + `scalingGroupName: ${item.scalingGroupName}, ` + + `productName: ${item.productName}, algorithm: ${item.algorithm})`; + return this.adaptee + .saveItemToDb(table, item, saveCondition, false) + .then(() => { + this.proxy.logAsInfo(typeText); + }) + .catch(err => { + this.proxy.logForError(`Failed to ${typeText}.`, err); + errorCount++; + }); + } + }) + ); + if (errorCount > 0) { + this.proxy.logAsInfo('called updateLicenseUsage'); + throw new Error( + `${errorCount} license usage record error occured. Please find the detailed logs above.` + ); + } + this.proxy.logAsInfo('called updateLicenseUsage'); + } + async loadLicenseFileContent(storageContainerName: string, filePath: string): Promise { + this.proxy.logAsInfo('calling loadLicenseFileContent'); + const content = await this.adaptee.getBlobContent(storageContainerName, filePath); + this.proxy.logAsInfo('called loadLicenseFileContent'); + return content; + } + // TODO: unused function as of this time + listNicAttachmentRecord(): Promise { + this.proxy.logAsInfo('calling listNicAttachmentRecord'); + this.proxy.logAsInfo('this method is unused thus always returning an empty array.'); + this.proxy.logAsInfo('called listNicAttachmentRecord'); + return Promise.resolve([]); + } + // TODO: unused function as of this time + updateNicAttachmentRecord(vmId: string, nicId: string, status: string): Promise { + this.proxy.logAsInfo('calling updateNicAttachmentRecord'); + this.proxy.logAsInfo( + 'this method is unused. parameter values passed here are:' + + ` vmId:${vmId}, nicId: ${nicId}, status: ${status}` + ); + this.proxy.logAsInfo('called updateNicAttachmentRecord'); + return Promise.resolve(); + } + // TODO: unused function as of this time + deleteNicAttachmentRecord(vmId: string, nicId: string): Promise { + this.proxy.logAsInfo('calling deleteNicAttachmentRecord'); + this.proxy.logAsInfo( + 'this method is unused. parameter values passed here are:' + + ` vmId:${vmId}, nicId: ${nicId}` + ); + this.proxy.logAsInfo('called deleteNicAttachmentRecord'); + return Promise.resolve(); + } + // TODO: unused function as of this time + createNetworkInterface( + subnetId?: string, + description?: string, + securityGroups?: string[], + privateIpAddress?: string + ): Promise { + this.proxy.logAsInfo('calling createNetworkInterface'); + this.proxy.logAsInfo( + 'this method is unused thus always returning null. parameter values passed here are:' + + ` subnetId?:${subnetId}, description?: ${description}` + + `, securityGroups?: ${securityGroups}, privateIpAddress?: ${privateIpAddress}` + ); + this.proxy.logAsInfo('called createNetworkInterface'); + return Promise.resolve(null); + } + // TODO: unused function as of this time + deleteNetworkInterface(nicId: string): Promise { + this.proxy.logAsInfo('calling deleteNetworkInterface'); + this.proxy.logAsInfo( + 'this method is unused. parameter values passed here are:' + ` nicId: ${nicId}` + ); + this.proxy.logAsInfo('called deleteNetworkInterface'); + return Promise.resolve(); + } + // TODO: unused function as of this time + attachNetworkInterface(vmId: string, nicId: string, index?: number): Promise { + this.proxy.logAsInfo('calling attachNetworkInterface'); + this.proxy.logAsInfo( + 'this method is unused. parameter values passed here are:' + + ` vmId:${vmId}, nicId: ${nicId}, index: ${index}` + ); + this.proxy.logAsInfo('called attachNetworkInterface'); + return Promise.resolve(); + } + // TODO: unused function as of this time + detachNetworkInterface(vmId: string, nicId: string): Promise { + this.proxy.logAsInfo('calling detachNetworkInterface'); + this.proxy.logAsInfo( + 'this method is unused. parameter values passed here are:' + + ` vmId:${vmId}, nicId: ${nicId}` + ); + this.proxy.logAsInfo('called detachNetworkInterface'); + return Promise.resolve(); + } + // TODO: unused function as of this time + listNetworkInterfaces(tags: ResourceFilter[], status?: string): Promise { + this.proxy.logAsInfo('calling listNetworkInterfaces'); + this.proxy.logAsInfo( + 'this method is unused thus always returning an empty array. ' + + 'parameter values passed here are:' + + ` tags:${JSON.stringify(tags)}, status?: ${status}` + ); + this.proxy.logAsInfo('called listNetworkInterfaces'); + return Promise.resolve([]); + } + // TODO: unused function as of this time + tagNetworkInterface(nicId: string, tags: ResourceFilter[]): Promise { + this.proxy.logAsInfo('calling tagNetworkInterface'); + this.proxy.logAsInfo( + 'this method is unused. parameter values passed here are:' + + ` nicId: ${nicId}, tags:${JSON.stringify(tags)}` + ); + this.proxy.logAsInfo('called tagNetworkInterface'); + return Promise.resolve(); + } + async registerFortiAnalyzer( + vmId: string, + privateIp: string, + primary: boolean, + vip: string + ): Promise { + this.proxy.logAsInfo('calling registerFortiAnalyzer'); + const table = new AzureFortiAnalyzer(); + const item = table.downcast({ + vmId: vmId, + ip: privateIp, + primary: primary, + vip: vip + }); + await this.adaptee.saveItemToDb( + table, + item, + DBDef.SaveCondition.Upsert, + false + ); + this.proxy.logAsInfo('called registerFortiAnalyzer'); + } + + async invokeAutoscaleFunction( + payload: unknown, + functionEndpoint: string, + invocable: string, + executionTime?: number + ): Promise { + this.proxy.logAsInfo('calling invokeAutoscaleFunction'); + const secretKey = this.createAutoscaleFunctionInvocationKey( + payload, + functionEndpoint, + invocable + ); + const p: CloudFunctionInvocationPayload = constructInvocationPayload( + payload, + invocable, + secretKey, + executionTime + ); + + // NOTE: Autoscale leverages Azure Function access keys to ensure security + // see: https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=csharp#authorization-keys + const reqHeaders = await this.proxy.getReqHeaders(); + const reqQueryParams = await this.proxy.getReqQueryParameters(); + const functionAccessKey = + reqHeaders['x-functions-key'] || + reqQueryParams.code || + process.env.FORTIANALYZER_HANDLER_ACCESS_KEY || + null; + if (functionAccessKey) { + this.proxy.logAsInfo('function access key found. will invoke with access key.'); + } else { + this.proxy.logAsInfo('function access key not found. will invoke as anonymous.'); + } + const response = await this.adaptee.invokeAzureFunction( + functionEndpoint, + JSON.stringify(p), + functionAccessKey && String(functionAccessKey) + ); + this.proxy.logAsInfo(`invocation response status code: ${response.status}`); + this.proxy.logAsInfo('called invokeAutoscaleFunction'); + return response.status; + } + createAutoscaleFunctionInvocationKey( + payload: unknown, + functionEndpoint: string, + invocable: string + ): string { + const psk = this.settings.get(AzureFortiGateAutoscaleSetting.FortiGatePskSecret).value; + return genChecksum( + `${functionEndpoint}:${invocable}:${psk}:${JSON.stringify(payload)}`, + 'sha256' + ); + } + + async getSecretFromKeyVault(name: string): Promise { + try { + const decrypted = await this.adaptee.keyVaultGetSecret(name); + this.proxy.logAsInfo('Environment variable is decrypted. Use the decrpted value.'); + return decrypted; + } catch (error) { + this.proxy.logAsWarning( + 'Unseccessfully decrypt the given varable, probably because ' + + 'the input is a non-encrypted value. Use its original value instead.' + ); + throw error; + } + } + + async saveLogs(logs: LogItem[]): Promise { + if (!logs) { + return; + } + let content = ''; + logs.forEach(log => { + const args = + (log.arguments && + log.arguments.map((arg, index) => { + const prefix = index > 0 ? `arg${index}: ` : ''; + return `${prefix}${arg}`; + })) || + []; + content = + `${content}` + + `${args.join('\n')}\n`; + }); + const table = new AzureCustomLog(); + const item = table.downcast({ + id: undefined, + timestamp: undefined, + logContent: content + }); + const save = (logItem: typeof item): Promise => { + const now = Date.now(); + logItem.id = `${now}-${Math.round(Math.random() * 1000)}`; + logItem.timestamp = now; + return this.adaptee.saveItemToDb( + table, + item, + DBDef.SaveCondition.InsertOnly + ); + }; + + try { + let tryAgainWhenFailed = false; + await save(item).catch(error => { + if (error instanceof DBDef.DbSaveError) { + tryAgainWhenFailed = true; + } else { + throw error; + } + }); + if (tryAgainWhenFailed) { + await save(item); + } + } catch (error) { + this.proxy.logForError('Error in saving logs to CustomLog', error); + } + } + + dataDiff( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dataToCheck: { [key: string]: any }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dataAgainst: { [key: string]: any } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): { [key: string]: any } { + if (!dataAgainst) { + return dataToCheck; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const diff: { [key: string]: any } = {}; + + Object.keys(dataToCheck) + .filter(k => dataToCheck[k] !== dataAgainst[k]) + .forEach(k => { + diff[k] = dataAgainst[k]; + }); + return diff; + } +} diff --git a/core/azure/azure-routing-egress-traffic-via-primary-vm-strategy.ts b/core/azure/azure-routing-egress-traffic-via-primary-vm-strategy.ts new file mode 100644 index 0000000..9802914 --- /dev/null +++ b/core/azure/azure-routing-egress-traffic-via-primary-vm-strategy.ts @@ -0,0 +1,29 @@ +import { AutoscaleEnvironment, CloudFunctionProxyAdapter, RoutingEgressTrafficStrategy } from '..'; +import { AzurePlatformAdapter } from '.'; + +/** + * This strategy updates the route table associated with the private subnets which need outgoing + * traffic capability. It adds/replace the route to the primary FortiGate vm in the Autoscale cluster + * so the FortiGate can handle such egress traffic. + */ +export class AzureRoutingEgressTrafficViaPrimaryVmStrategy implements RoutingEgressTrafficStrategy { + protected platform: AzurePlatformAdapter; + protected proxy: CloudFunctionProxyAdapter; + protected env: AutoscaleEnvironment; + constructor( + platform: AzurePlatformAdapter, + proxy: CloudFunctionProxyAdapter, + env: AutoscaleEnvironment + ) { + this.platform = platform; + this.proxy = proxy; + this.env = env; + } + apply(): Promise { + this.proxy.logAsInfo('calling RoutingEgressTrafficViaPrimaryVmStrategy.apply'); + // TODO: implementation needed. + this.proxy.logAsInfo('feature not yet implemented.'); + this.proxy.logAsInfo('called RoutingEgressTrafficViaPrimaryVmStrategy.apply'); + return Promise.resolve(); + } +} diff --git a/core/azure/azure-tagging-autoscale-vm-strategy.ts b/core/azure/azure-tagging-autoscale-vm-strategy.ts new file mode 100644 index 0000000..1ee1ae9 --- /dev/null +++ b/core/azure/azure-tagging-autoscale-vm-strategy.ts @@ -0,0 +1,43 @@ +import { CloudFunctionProxyAdapter, TaggingVmStrategy, VmTagging } from '..'; +import { AzurePlatformAdapter } from '.'; + +export class AzureTaggingAutoscaleVmStrategy implements TaggingVmStrategy { + protected platform: AzurePlatformAdapter; + protected proxy: CloudFunctionProxyAdapter; + protected taggings: VmTagging[]; + constructor(platform: AzurePlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.platform = platform; + this.proxy = proxy; + } + prepare(taggings: VmTagging[]): Promise { + this.taggings = taggings; + return Promise.resolve(); + } + async apply(): Promise { + this.proxy.logAsInfo('calling AzureTaggingAutoscaleVmStrategy.apply'); + const creationTaggings: VmTagging[] = this.taggings.filter(tagging => !tagging.clear); + const deletionTaggings: VmTagging[] = this.taggings.filter(tagging => tagging.clear); + if (creationTaggings.length > 0) { + await this.add(creationTaggings); + } + if (deletionTaggings.length > 0) { + await this.clear(deletionTaggings); + } + this.proxy.logAsInfo('calling AzureTaggingAutoscaleVmStrategy.apply'); + } + add(taggings: VmTagging[]): Promise { + this.proxy.logAsInfo('calling AzureTaggingAutoscaleVmStrategy.add'); + this.proxy.logAsInfo('skipped. not yet implemented.'); + this.proxy.logAsInfo(`value passed to parameter: taggings: ${JSON.stringify(taggings)}.`); + this.proxy.logAsInfo('called AzureTaggingAutoscaleVmStrategy.add'); + return Promise.resolve(); + } + + clear(taggings: VmTagging[]): Promise { + this.proxy.logAsInfo('calling AzureTaggingAutoscaleVmStrategy.clear'); + this.proxy.logAsInfo('skipped. not yet implemented.'); + this.proxy.logAsInfo(`value passed to parameter: taggings: ${JSON.stringify(taggings)}.`); + this.proxy.logAsInfo('called AzureTaggingAutoscaleVmStrategy.clear'); + return Promise.resolve(); + } +} diff --git a/core/azure/index.ts b/core/azure/index.ts new file mode 100644 index 0000000..90949c1 --- /dev/null +++ b/core/azure/index.ts @@ -0,0 +1,17 @@ +import * as AzureFunctionDef from './azure-function-definitions'; +// export @fortinet/fortigate-autoscale +// export * from '..'; +// export fortigate-autoscale-azure module files. +export * from './azure-cloud-function-proxy'; +export * from './azure-db-definitions'; +export * from './azure-fortianalyzer-integration-service'; +export * from './azure-fortigate-autoscale'; +export * from './azure-fortigate-autoscale-settings'; +export * from './azure-fortigate-bootstrap-config-strategy'; +export * from './azure-function-invocable'; +export * from './azure-hybrid-scaling-group-strategy'; +export * from './azure-platform-adaptee'; +export * from './azure-platform-adapter'; +export * from './azure-routing-egress-traffic-via-primary-vm-strategy'; +export * from './azure-tagging-autoscale-vm-strategy'; +export { AzureFunctionDef }; diff --git a/core/blob.ts b/core/blob.ts new file mode 100644 index 0000000..c25e577 --- /dev/null +++ b/core/blob.ts @@ -0,0 +1,5 @@ +export interface Blob { + content?: string; + filePath?: string; + fileName?: string; +} diff --git a/core/cloud-function-peer-invocation.ts b/core/cloud-function-peer-invocation.ts new file mode 100644 index 0000000..4c77de2 --- /dev/null +++ b/core/cloud-function-peer-invocation.ts @@ -0,0 +1,43 @@ +import { JSONable } from './jsonable'; + +export interface CloudFunctionInvocationPayload extends JSONable { + stringifiedData: string; + invocable: string; + invocationSecretKey: string; + executionTime?: number; +} +export interface CloudFunctionPeerInvocation { + proxy: TProxy; + platform: TPlatform; + executeInvocable(payload: CloudFunctionInvocationPayload, invocable: string): Promise; + handlePeerInvocation(functionEndpoint: string): Promise; +} + +export class CloudFunctionInvocationTimeOutError extends Error { + extendExecution: boolean; + constructor(message?: string, extendExecution = false) { + super(message); + this.extendExecution = extendExecution; + } +} + +export function constructInvocationPayload( + payload: unknown, + invocable: string, + secretKey: string, + executionTime?: number +): CloudFunctionInvocationPayload { + const p: CloudFunctionInvocationPayload = { + stringifiedData: JSON.stringify(payload), + invocable: invocable, + invocationSecretKey: secretKey, + executionTime: executionTime + }; + return p; +} + +export function extractFromInvocationPayload( + invocationPayload: CloudFunctionInvocationPayload +): unknown { + return invocationPayload.stringifiedData && JSON.parse(invocationPayload.stringifiedData); +} diff --git a/core/cloud-function-proxy.ts b/core/cloud-function-proxy.ts new file mode 100644 index 0000000..c01ddc6 --- /dev/null +++ b/core/cloud-function-proxy.ts @@ -0,0 +1,202 @@ +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum LogLevel { + Log = 'Log', + Info = 'Info', + Warn = 'Warn', + Error = 'Error', + Debug = 'Debug' +} +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum DebugMode { + True = 'true', + DebugOnly = 'DebugOnly' +} +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum ReqType { + BootstrapConfig = 'BootstrapConfig', + ByolLicense = 'ByolLicense', + CloudFunctionPeerInvocation = 'PeerFunctionInvocation', + CustomLog = 'CustomLog', + HeartbeatSync = 'HeartbeatSync', + LaunchedVm = 'LaunchedVm', + LaunchingVm = 'LaunchingVm', + ServiceProviderRequest = 'ServiceProviderRequest', + StatusMessage = 'StatusMessage', + TerminatedVm = 'TerminatedVm', + TerminatingVm = 'TerminatingVm', + VmNotLaunched = 'VmNotLaunched' +} +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum ReqMethod { + GET, + POST, + PUT, + DELETE, + PATCH, + HEAD, + TRACE, + OPTIONS, + CONNECT +} + +const reqMethod: Map = new Map([ + ['GET', ReqMethod.GET], + ['POST', ReqMethod.POST], + ['PUT', ReqMethod.PUT], + ['DELETE', ReqMethod.DELETE], + ['PATCH', ReqMethod.PATCH], + ['HEAD', ReqMethod.HEAD], + ['TRACE', ReqMethod.TRACE], + ['OPTIONS', ReqMethod.OPTIONS], + ['CONNECT', ReqMethod.CONNECT] +]); + +export function mapHttpMethod(s: string): ReqMethod { + return s && reqMethod.get(s.toUpperCase()); +} + +export interface ReqBody { + [key: string]: unknown; +} + +export interface ReqHeaders { + [key: string]: unknown; +} + +export type CloudFunctionResponseBody = + | string + | { + [key: string]: unknown; + } + | unknown; + +export interface CloudFunctionProxyAdapter { + formatResponse( + httpStatusCode: number, + body: CloudFunctionResponseBody, + headers: unknown + ): unknown; + log(message: string, level: LogLevel, ...others: unknown[]): void; + logAsDebug(message: string | DebugMode, ...others: unknown[]): void; + logAsInfo(message: string, ...others: unknown[]): void; + logAsWarning(message: string, ...others: unknown[]): void; + logAsError(message: string, ...others: unknown[]): void; + /** + * Output an Error level message containing the given message prefix, the error.message + * and error.stack of the given error. + * + * @param {string} messagePrefix + * @param {Error | string} error + * @memberof CloudFunctionProxyAdapter + */ + logForError(messagePrefix: string, error: Error): void; + getRequestAsString(): Promise; + /** + * return the remaining execution time (in millisecond) of the current cloud function process. + * + * @returns {number} + * @memberof CloudFunctionProxyAdapter + */ + getRemainingExecutionTime(): Promise; + getReqBody(): Promise; + /** + * get the HTTP headers object + * + * NOTE: header keys will be treated case-insensitive as per + the RFC https://tools.ietf.org/html/rfc7540#section-8.1.2 + * @returns {Promise} headers objectt + */ + getReqHeaders(): Promise; + getReqMethod(): Promise; +} + +export abstract class CloudFunctionProxy + implements CloudFunctionProxyAdapter +{ + request: TReq; + context: TContext; + constructor(req: TReq, context: TContext) { + this.request = req; + this.context = context; + } + abstract log(message: string, level: LogLevel, ...others: unknown[]): void; + /** + * output log message as debug level. + * Only the first parameter 'message' will be shown normally. + * A hint message - '* more messages are hidden'. + * Add the process environment variable 'DEBUG_MODE' with value 'true' to show them.' - will + * be also shown in the output following the 'message' parameter. + * The rest parameters 'others' will be hidden. + * When process.env.DEBUG_MODE exists and set any value of string type , + * the 'others' parameters will be shown too. + * Passing the value DebugMode.DebugOnly to the 'message' will hide all from showing in the log + * unless process.env.DEBUG_MODE exists. + * @param {string | DebugMode} message the message if passed a string type, will be shown if + * process.env.DEBUG_MODE exists and is set any value of string type. Passing DebugMode type + * can have special behaviors. (as describe above). + * @param {unknown[]} others the extra stuff to output via logAsDebug. These will be hidden + * from output if process.env.DEBUG_MODE doesn't exist (as described above). + * Otherwise, these will be shown. + * @returns {void} + */ + logAsDebug(message: string | DebugMode, ...others: unknown[]): void { + const otherCount = (others && others.length) || 0; + const hint = + otherCount === 0 + ? '' + : `${otherCount} more messages are hidden. Add the process environment` + + " variable 'DEBUG_MODE' with value 'true' to show them."; + // DEBUG_MODE exists in process.env. + if (process.env.DEBUG_MODE !== null && process.env.DEBUG_MODE !== undefined) { + // message will be shown in debug mode only + if (message === DebugMode.DebugOnly) { + return; + } + // show message, and others. + else { + this.log(message, LogLevel.Debug, ...others); + } + } + // DEBUG_MODE not exists in process.env. + else { + // don't sho anything if debug only + if (message === DebugMode.DebugOnly) { + return; + } + // otherwise, show message appended with a hint. others will be hidden. + else { + this.log(message ? `${message}. ${hint}` : hint, LogLevel.Debug); + } + } + } + logAsError(message: string, ...others: unknown[]): void { + this.log(message, LogLevel.Error, ...others); + } + logAsInfo(message: string, ...others: unknown[]): void { + this.log(message, LogLevel.Info, ...others); + } + logAsWarning(message: string, ...others: unknown[]): void { + this.log(message, LogLevel.Warn, ...others); + } + logForError(messagePrefix: string, error: Error, ...others: unknown[]): void { + const errMessage = error.message || '(no error message available)'; + const errStack = (error.stack && ` Error stack:${error.stack}`) || ''; + + this.log(`${messagePrefix}. Error: ${errMessage}${errStack}`, LogLevel.Error, ...others); + } + abstract formatResponse( + httpStatusCode: number, + body: CloudFunctionResponseBody, + headers: unknown + ): TRes; + abstract getRequestAsString(): Promise; + abstract getRemainingExecutionTime(): Promise; + abstract getReqBody(): Promise; + abstract getReqHeaders(): Promise; + abstract getReqMethod(): Promise; + abstract getReqQueryParameters(): Promise<{ [name: string]: string }>; +} diff --git a/core/context-strategy/autoscale-context.ts b/core/context-strategy/autoscale-context.ts new file mode 100644 index 0000000..dc809e7 --- /dev/null +++ b/core/context-strategy/autoscale-context.ts @@ -0,0 +1,1121 @@ +import { AutoscaleEnvironment } from '../autoscale-environment'; +import { AutoscaleSetting } from '../autoscale-setting'; +import { CloudFunctionProxyAdapter, LogLevel } from '../cloud-function-proxy'; +import { waitFor, WaitForConditionChecker, WaitForPromiseEmitter } from '../helper-function'; +import { PlatformAdapter } from '../platform-adapter'; +import { + HealthCheckRecord, + HealthCheckResult, + HealthCheckResultDetail, + HealthCheckSyncState as HeartbeatSyncState, + PrimaryRecord, + PrimaryRecordVoteState +} from '../primary-election'; +import { VirtualMachine } from '../virtual-machine'; + +export interface PrimaryElection { + oldPrimary?: VirtualMachine; + oldPrimaryRecord?: PrimaryRecord; + newPrimary: VirtualMachine; + newPrimaryRecord: PrimaryRecord; + candidate: VirtualMachine; + candidateHealthCheck?: HealthCheckRecord; + preferredScalingGroup?: string; + electionDuration?: number; + signature: string; // to identify a primary election +} + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum PrimaryElectionStrategyResult { + CannotDeterminePrimary = 'CannotDeterminePrimary', + CompleteAndContinue = 'CompleteAndContinue', + SkipAndContinue = 'SkipAndContinue' +} +export interface PrimaryElectionStrategy { + prepare(election: PrimaryElection): Promise; + result(): Promise; + apply(): Promise; + readonly applied: boolean; +} + +export interface HeartbeatSyncStrategy { + prepare(vm: VirtualMachine): Promise; + apply(): Promise; + /** + * Force the target vm to go into 'out-of-sync' state. Autoscale will stop accepting its + * heartbeat sync request. + * @returns {Promise} void + */ + forceOutOfSync(): Promise; + readonly targetHealthCheckRecord: HealthCheckRecord | null; + readonly healthCheckResult: HealthCheckResult; + readonly healthCheckResultDetail: HealthCheckResultDetail; + readonly targetVmFirstHeartbeat: boolean; +} + +export interface VmTagging { + vmId: string; + newVm?: boolean; + newPrimaryRole?: boolean; + clear?: boolean; +} + +export interface TaggingVmStrategy { + prepare(taggings: VmTagging[]): Promise; + apply(): Promise; +} + +export interface RoutingEgressTrafficStrategy { + apply(): Promise; +} + +/** + * To provide Autoscale basic logics + */ +export interface AutoscaleContext { + setPrimaryElectionStrategy(strategy: PrimaryElectionStrategy): void; + handlePrimaryElection(): Promise; + setHeartbeatSyncStrategy(strategy: HeartbeatSyncStrategy): void; + handleHeartbeatSync(): Promise; + setTaggingAutoscaleVmStrategy(strategy: TaggingVmStrategy): void; + handleTaggingAutoscaleVm(taggings: VmTagging[]): Promise; + setRoutingEgressTrafficStrategy(strategy: RoutingEgressTrafficStrategy): void; + handleEgressTrafficRoute(): Promise; + onVmFullyConfigured(): Promise; +} + +export class PreferredGroupPrimaryElection implements PrimaryElectionStrategy { + env: PrimaryElection; + platform: PlatformAdapter; + res: PrimaryElection; + proxy: CloudFunctionProxyAdapter; + private _applied: boolean; + constructor(platform: PlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.platform = platform; + this.proxy = proxy; + } + prepare(env: PrimaryElection): Promise { + this.env = env; + this.res = { + oldPrimary: this.env.oldPrimary, + oldPrimaryRecord: this.env.oldPrimaryRecord, + newPrimary: null, // no initial new primary + newPrimaryRecord: null, // no initial new primary record + candidate: this.env.candidate, + candidateHealthCheck: this.env.candidateHealthCheck, + preferredScalingGroup: this.env.preferredScalingGroup, + electionDuration: this.env.electionDuration, + signature: '' + }; + this._applied = false; + return Promise.resolve(); + } + + get applied(): boolean { + return this._applied; + } + + result(): Promise { + return Promise.resolve(this.res); + } + async apply(): Promise { + this.proxy.log('applying PreferredGroupPrimaryElection strategy.', LogLevel.Log); + this._applied = true; + const result = await this.run(); + this.proxy.log('applied PreferredGroupPrimaryElection strategy.', LogLevel.Log); + return result; + } + /** + * Only vm in the specified byol scaling group can be elected as the new primary + */ + async run(): Promise { + const settings = await this.platform.getSettings(); + // get the primary scaling group + const settingGroupName = settings.get(AutoscaleSetting.PrimaryScalingGroupName).value; + // candidate not in the preferred scaling group? no election will be run + if (this.env.candidate.scalingGroupName !== settingGroupName) { + this.proxy.log( + `The candidate (id: ${this.env.candidate.id}) ` + + "isn't in the preferred scaling group. It cannot run a primary election. " + + 'Primary election not started.', + LogLevel.Warn + ); + this.res.newPrimary = null; + this.res.newPrimaryRecord = null; + return PrimaryElectionStrategyResult.SkipAndContinue; + } + const signature = this.env.candidate + ? `${this.env.candidate.scalingGroupName}:${this.env.candidate.id}` + : ''; + // created using the candidate info + const newPrimaryRecord: PrimaryRecord = { + id: `${signature}`, + ip: this.env.candidate.primaryPrivateIpAddress, + vmId: this.env.candidate.id, + scalingGroupName: this.env.candidate.scalingGroupName, + virtualNetworkId: this.env.candidate.virtualNetworkId, + subnetId: this.env.candidate.subnetId, + voteEndTime: null, + voteState: PrimaryRecordVoteState.Pending + }; + + // if has candidate healthcheck record, that means this candidate is already in-service + // but is in a non-primary role (e.g. secondary role or not yet assigned a role). + // KNOWN ISSUE: if a brand new device is the primary candidate and it wins + // the election to become the new primary, ALL CONFIGURATION WILL BE LOST + // TODO: need to find a more qualified candidate, or develop a technique to sync + // the configuration. + // solution: + // for the classic case: + // check if this device is a new device (not yet monitored) + // if there is an existing device in the cluster, this device is not qualified. + // if there isn't any other existing device in the cluster, this device is qualified. + let healthcheckRecords: HealthCheckRecord[] = []; + const deviceSyncInfo = await this.platform.getReqDeviceSyncInfo(); + // NOTE: classic handling method: no device sync info + if (!deviceSyncInfo.syncTime) { + // KNOWN ISSUE: in the classic election method, the Autoscale handler is lack of information + // from the device's point of view regarding sync state. + // There's no way to guarantee the configuration persists. + + // if this is a running healthy vm that exists in Autoscale health monitor, it is + // eligible and can be deemed the new primary immediately. + if (this.env.candidateHealthCheck && this.env.candidateHealthCheck.healthy) { + newPrimaryRecord.voteEndTime = Date.now(); // election ends immediately + newPrimaryRecord.voteState = PrimaryRecordVoteState.Done; + this.res.newPrimary = this.env.candidate; + this.res.newPrimaryRecord = newPrimaryRecord; + // immediately return + return PrimaryElectionStrategyResult.CompleteAndContinue; + } + // otherwise, check if there's any other vm already exists in the monitor, those + // vm would be a better primary candidate + else { + // list all monitored vm + healthcheckRecords = (await this.platform.listHealthCheckRecord()) || []; + // filter those are healthy + // look for any which next heartbeat time is within the period of 2 intervals + const now = Date.now(); + const eligilePrimaryCandidates = healthcheckRecords + .filter(rec => rec.vmId !== this.env.candidate.id) + .filter(rec => rec.healthy) + .filter(rec => { + return ( + rec.healthy && + rec.nextHeartbeatTime >= now - rec.heartbeatInterval * 1000 * 3 // 3 - 1 = 2 + ); + }); + // if there is other eligible candidate, this vm becomes ineligible + if (eligilePrimaryCandidates.length > 0) { + this.res.newPrimary = null; + this.res.newPrimaryRecord = null; + return PrimaryElectionStrategyResult.SkipAndContinue; + } + // otherwise, it is considered eligible + // in this case, this vm does not sync with the previous primary + // because there's no recored primary in the Autoscale and the Autoscale + // isn't able to know about the sync state of any vm. (the new method: + // device sync info method is developed to solve this problem) + else { + this.res.newPrimary = this.env.candidate; + // since it is a new vm, there's no health record for it. + // a new record will be created + this.res.newPrimaryRecord = newPrimaryRecord; + return PrimaryElectionStrategyResult.CompleteAndContinue; + } + } + } + // NOTE: enhanced handling method: has device sync info method + else { + // workflow: if current VM is healthy + // unhealty vm is ineligible + // NOTE: only if there is a health check record of this target vm can check + // its health state. + // target vm without a health check record will be treated as healthy in this case + if (this.env.candidateHealthCheck && !this.env.candidateHealthCheck.healthy) { + this.res.newPrimary = null; + this.res.newPrimaryRecord = null; + return PrimaryElectionStrategyResult.SkipAndContinue; + } else { + // workflow: if VM is a primary role + // if the vm hasn't been assigned a role, or + // if the vm has been assigned a primary role, should check if its checksum is + // the checksum of the majority VM. + // pick the primary from the majority group in the cluster. + // if no HA sync has formed in the cluster yet, none of the VM in the cluster + // should have established a checksum. in this case, this VM is eligible + if (deviceSyncInfo.isPrimary || deviceSyncInfo.isPrimary === null) { + // workflow: compare checksum with other VMs + // list all monitored vm + healthcheckRecords = (await this.platform.listHealthCheckRecord()) || []; + // NOTE: need to ensure this primary device is the one of the majority + // of all secondary device are in-sync with. + const checksumCount = new Map(); + healthcheckRecords + // remove those have a null checksum + .filter(rec => rec.deviceChecksum !== null) + // remove those aren't secondary role + .filter(rec => rec.deviceIsPrimary !== false) + // count the number of each different checksum + .forEach(rec => { + if (checksumCount.has(rec.deviceChecksum)) { + checksumCount.set( + rec.deviceChecksum, + checksumCount.get(rec.deviceChecksum) + 1 + ); + } else { + checksumCount.set(rec.deviceChecksum, 1); + } + }); + // sort the checksum descendingly + const descSortedEntries = Array.from(checksumCount.entries()).sort( + (entryA, entryB) => { + if (entryA[1] > entryB[1]) { + return 1; + } else if (entryA[1] < entryB[1]) { + return -1; + } else { + return 0; + } + } + ); + // workflow: if checksum match + // in the descendingly sorted entries of checksum, the first row + // is the checksum of the majority + // if the candidate checksum is the same as the first row, it is + // eligible for a primary candiate + // NOTE: if no available checksum in the cluster, the vm is eligible + if ( + descSortedEntries.length === 0 || + (descSortedEntries.length > 0 && + deviceSyncInfo.checksum === descSortedEntries[0][0]) + ) { + this.res.newPrimary = this.env.candidate; + this.res.newPrimaryRecord = newPrimaryRecord; + return PrimaryElectionStrategyResult.CompleteAndContinue; + } + // otherwise, ineligible + else { + this.res.newPrimary = null; + this.res.newPrimaryRecord = null; + return PrimaryElectionStrategyResult.SkipAndContinue; + } + } + // if this vm is a secondary role + // workflow: check vm sync info: in-sync + // workflow: if is in-sync with primary + // if in-sync, it will be eligible + if (this.env.candidateHealthCheck.deviceSyncStatus) { + this.res.newPrimary = this.env.candidate; + this.res.newPrimaryRecord = newPrimaryRecord; + return PrimaryElectionStrategyResult.CompleteAndContinue; + } + // otherwise, ineligible + else { + this.res.newPrimary = null; + this.res.newPrimaryRecord = null; + return PrimaryElectionStrategyResult.SkipAndContinue; + } + } + } + } +} + +export class WeightedScorePreferredGroupPrimaryElection extends PreferredGroupPrimaryElection { + /** @override super.apply() */ + async apply(): Promise { + this.proxy.log( + 'applying WeightedScorePreferredGroupPrimaryElection strategy.', + LogLevel.Log + ); + const result = await super.apply(); + this.proxy.log( + 'applied WeightedScorePreferredGroupPrimaryElection strategy.', + LogLevel.Log + ); + return result; + } + + /** @override super.run() */ + async run(): Promise { + // check if device sync info method applies or not. + // will use the weighted score election strategy only if the device sync info is available + const deviceSyncInfo = await this.platform.getReqDeviceSyncInfo(); + // if device sync info is available, the send time will not be null + if (deviceSyncInfo.time === null) { + return this.runClassicElectionMethod(); + } else { + return this.runDeviceSyncInfoElectionMethod(); + } + } + + /** + * This method will run the classic primary election that does not facilitate the device + * sync info provided by each VM + * @returns {PrimaryElectionStrategyResult} election result + */ + private runClassicElectionMethod(): Promise { + return super.run(); + } + + /** + * This method will run the primary election that makes uses of the device sync info + * provided by each VM. The sync info will be kept in the healthcheck record of each VM + * @returns {PrimaryElectionStrategyResult} election result + */ + private async runDeviceSyncInfoElectionMethod(): Promise { + const settings = await this.platform.getSettings(); + // get the primary scaling group + const settingGroupName = settings.get(AutoscaleSetting.PrimaryScalingGroupName).value; + // get all monitored vm health check records and filter the healthy ones + const allHealthCheckRecords: HealthCheckRecord[] = ( + await this.platform.listHealthCheckRecord() + ) + // exclude unhealthy vm from election + .filter(rec => rec.healthy) + // exclude irresponsive vm from election + .filter(rec => rec.irresponsivePeriod === 0) + // exclude those are not in the preferred scaling group for primary election + .filter(rec => rec.scalingGroupName === settingGroupName); + + // if no vm healthcheck record available, end election + if (allHealthCheckRecords.length === 0) { + this.proxy.log( + "There isn't any healthy vm in the preferred scaling group for primary election." + + 'Primary election not started.', + LogLevel.Warn + ); + this.res.newPrimary = null; + this.res.newPrimaryRecord = null; + return PrimaryElectionStrategyResult.SkipAndContinue; + } + + // scores + const scores: Map = new Map(); + allHealthCheckRecords.forEach(rec => { + scores.set(rec.vmId, 0); + }); + for (const rec of allHealthCheckRecords) { + let points = scores.get(rec.vmId); + // apply weighting methods + points += this.haveChecksumWeight(rec); + points += this.sharedChecksumWeight(rec, allHealthCheckRecords); + points += this.groupedChecksumWeight(rec, allHealthCheckRecords); + points += this.primaryDeviceWeight(rec); + points += this.lateslGroupChecksumWeight(rec, allHealthCheckRecords); + points += this.latestSynctimeWeight(rec, allHealthCheckRecords); + scores.set(rec.vmId, points); + } + + // if there's one and only one rec has the highest score, it will be elected as the + // new primary + let electedPrimaryId: string; + let highestScore = 0; + let multipleHighest = false; + scores.forEach((score, vmId) => { + if (score > highestScore) { + highestScore = score; + electedPrimaryId = vmId; + multipleHighest = false; + } else if (score === highestScore) { + multipleHighest = true; + } + }); + let electedPrimaryHealthCheckRecord: HealthCheckRecord; + if (highestScore > 0 && !multipleHighest) { + [electedPrimaryHealthCheckRecord] = allHealthCheckRecords.filter( + rec => rec.vmId === electedPrimaryId + ); + } + + // if no primary can be determined using the weighted methods, try the special method. + // NOTE: special condition. usually occurs in the intial state where all VM are initially + // launched as a secondary role, never sync with any other. + // in this case whichever has the oldest send_time will be the election primary + // run the method to find the one + if (!electedPrimaryHealthCheckRecord) { + electedPrimaryId = this.fairWeight(scores, allHealthCheckRecords); + [electedPrimaryHealthCheckRecord] = allHealthCheckRecords.filter( + rec => rec.vmId === electedPrimaryId + ); + } + + // still cannot determine the primary, election strategy ends. + if (!electedPrimaryHealthCheckRecord) { + this.res.newPrimary = null; + this.res.newPrimaryRecord = null; + return PrimaryElectionStrategyResult.CannotDeterminePrimary; + } + + const primaryVm = await this.platform.getVmById( + electedPrimaryHealthCheckRecord.vmId, + electedPrimaryHealthCheckRecord.scalingGroupName + ); + + // if cannot find the vm, which is rare, do not set this.env.newPrimary + this.res.newPrimary = primaryVm || null; + + const primaryRecord: PrimaryRecord = { + id: electedPrimaryHealthCheckRecord.scalingGroupName, + ip: electedPrimaryHealthCheckRecord.ip, + vmId: electedPrimaryHealthCheckRecord.vmId, + scalingGroupName: electedPrimaryHealthCheckRecord.scalingGroupName, + virtualNetworkId: (primaryVm && primaryVm.virtualNetworkId) || '', + subnetId: (primaryVm && primaryVm.subnetId) || '', + voteEndTime: Date.now(), // election ends immediately, + voteState: PrimaryRecordVoteState.Done + }; + + this.res.newPrimaryRecord = primaryRecord; + return PrimaryElectionStrategyResult.CompleteAndContinue; + } + + /** + * get (1) point if a record has non-null checksum + * @param {HealthCheckRecord} rec the healthcheck record to earn points + * @returns {number} point + */ + haveChecksumWeight(rec: HealthCheckRecord): number { + return rec.deviceChecksum ? 1 : 0; + } + /** + * get (2) points if the record shares the same checksum (non-null) with at least one other VM in the group + * @param {HealthCheckRecord} rec the healthcheck record to earn points + * @param {HealthCheckRecord[]} groups the group of healthcheck records + * @returns {number} point + */ + sharedChecksumWeight(rec: HealthCheckRecord, groups: HealthCheckRecord[]): number { + const sameChecksums = groups.filter( + g => + g.vmId !== rec.vmId && + g.deviceChecksum !== null && + g.deviceChecksum === rec.deviceChecksum + ); + return (sameChecksums.length > 0 && 2) || 0; + } + /** + * return a map of id array with checksum as map key + * @param {HealthCheckRecord[]} groups groups to process + * @returns {Map} Map + */ + private groupByChecksum(groups: HealthCheckRecord[]): Map { + const checksumGroups: Map = new Map(); + groups.forEach(grec => { + let ids: string[] = []; + if (checksumGroups.has(grec.deviceChecksum)) { + ids = [...checksumGroups.get(grec.deviceChecksum)]; + } + checksumGroups.set(grec.deviceChecksum, [grec.vmId, ...ids]); + }); + return checksumGroups; + } + /** + * get (4) points if all these conditions are met: + * 1. some of all records can be grouped by checksum and each group must have more than 1 group member (forming checksum group) + * 2. if forming checksum groups, there will be one group whose number of member is geater than any other group (forming major checksum group) + * 3. the checksum of the major checksum group must not be null + * 4. the target record is in the major checksum group + * @param {HealthCheckRecord} rec the healthcheck record to earn points + * @param {HealthCheckRecord[]} groups the group of healthcheck records + * @returns {number} point + */ + groupedChecksumWeight(rec: HealthCheckRecord, groups: HealthCheckRecord[]): number { + const checksumGroups: Map = this.groupByChecksum(groups); + let largestCount = 1; + let multipleGroupOfSameCount = false; + let majorChecksum: string; + // find the major checksum group + checksumGroups.forEach((ids, checksum) => { + if (ids.length > largestCount) { + largestCount = ids.length; + multipleGroupOfSameCount = false; + majorChecksum = checksum; + } else if (ids.length === largestCount) { + multipleGroupOfSameCount = true; + } + }); + // check if the rec is in the major checksum group + const inMajorChecksumGroup = + !multipleGroupOfSameCount && majorChecksum === rec.deviceChecksum; + // check null before return; + return inMajorChecksumGroup && rec.deviceChecksum !== null ? 4 : 0; + } + + /** + * get (8) points if the record has the latest synctime among all members in the group. + * @param {HealthCheckRecord} rec the healthcheck record to earn points + * @param {HealthCheckRecord[]} groups the group of healthcheck records + * @returns {number} point + */ + latestSynctimeWeight(rec: HealthCheckRecord, groups: HealthCheckRecord[]): number { + // if rec has null sync time then it gets 0 point + if (rec.deviceSyncTime === null) { + return 0; + } + const checksumGroups: Map = this.groupByChecksum(groups); + const ids = checksumGroups.get(rec.deviceChecksum); // get ids of the checksum group where the rec is in + const latestSyncTime: Date = new Date(rec.deviceSyncTime); // assume that the rec has the latest sync time + // compare their sync time to find the latest one + let hasLatestSyncTime = true; + groups + .filter( + grec => + ids.includes(grec.vmId) && + grec.deviceSyncTime !== null && + grec.vmId !== rec.vmId + ) + .forEach(grec => { + if (new Date(grec.deviceSyncTime).getTime() >= latestSyncTime.getTime()) { + hasLatestSyncTime = false; + } + }); + return hasLatestSyncTime ? 8 : 0; + } + /** + * get (16) points if all these conditions are met: + * 1. some of all records can be grouped by checksum and each group must have more than 1 group member (forming checksum group) + * 2. the latest sync time out of all records is in this checksum group (forming latest checksum group) + * 3. the latest sync time must not be in other checksum group + * 4. the target record is in this latest checksum group + * @param {HealthCheckRecord} rec the healthcheck record to earn points + * @param {HealthCheckRecord[]} groups the group of healthcheck records + * @returns {number} point + */ + lateslGroupChecksumWeight(rec: HealthCheckRecord, groups: HealthCheckRecord[]): number { + const syncTimeGroups: Map = new Map(); // get sort out the latest sync time of each checksum + groups + .filter(grec => grec.deviceSyncTime !== null) + .forEach(grec => { + if ( + !syncTimeGroups.has(grec.deviceChecksum) || + new Date(grec.deviceSyncTime).getTime() > + syncTimeGroups.get(grec.deviceChecksum).getTime() + ) { + syncTimeGroups.set(grec.deviceChecksum, new Date(grec.deviceSyncTime)); + } + }); + // find the checksum that has the latest sync time and how many checksum share this time + let latestSyncTime: Date = new Date(0); + let latestSyncTimeCount = 0; + let checksumOfLatestSyncTime: string; + syncTimeGroups.forEach((syncTime, checksum) => { + if (syncTime.getTime() > latestSyncTime.getTime()) { + latestSyncTime = new Date(syncTime); + latestSyncTimeCount = 1; + checksumOfLatestSyncTime = checksum; + } else if (syncTime.getTime() === latestSyncTime.getTime()) { + latestSyncTimeCount++; + } + }); + + return latestSyncTimeCount === 1 && checksumOfLatestSyncTime === rec.deviceChecksum + ? 16 + : 0; + } + /** + * get (64) points if the record is set primary role + * @param {HealthCheckRecord} rec the healthcheck record to earn points + * @returns {number} point + */ + primaryDeviceWeight(rec: HealthCheckRecord): number { + return rec.deviceIsPrimary ? 64 : 0; + } + /** + * one among all or none will get (128) bonus points if all these conditions are met: + * 1. has non-null checksum + * 2. checksum is mutually exclusive + * 3. each record in the group has null sync_time + * 4. each record in the group has an equal score + * 5. send_time is the oldest + * @param {Map} scores the scores of all participants + * @param {HealthCheckRecord[]} groups the group of healthcheck records + * @returns {string} vmId who will get the bonus + */ + fairWeight(scores: Map, groups: HealthCheckRecord[]): string { + // check mutually exclusive and find non-null sync_time + let foundNonNullSyncTime = false; + const checksumCount: Map = new Map(); + groups.forEach(grec => { + if (grec.deviceSyncTime !== null) { + foundNonNullSyncTime = true; + } + if (!checksumCount.has(grec.deviceChecksum)) { + checksumCount.set(grec.deviceChecksum, 0); + } + checksumCount.set(grec.deviceChecksum, checksumCount.get(grec.deviceChecksum) + 1); + }); + + if (foundNonNullSyncTime) { + return null; + } + + let mutuallyExclusive = true; + + checksumCount.forEach(count => { + if (count > 1) { + mutuallyExclusive = false; + } + }); + if (!mutuallyExclusive) { + return null; + } + + // check equal score and oldest send time + let hasEqualScore = true; + let score: number = undefined; + let oldestSendTime: Date; + let vmId: string; + groups.forEach(grec => { + if (score === undefined) { + score = scores.get(grec.vmId); + } + if (score !== scores.get(grec.vmId)) { + hasEqualScore = false; + } + const sendTime = new Date(grec.sendTime); + if (oldestSendTime === undefined || sendTime.getTime() < oldestSendTime.getTime()) { + oldestSendTime = sendTime; + vmId = grec.vmId; + } + // in an extreme edge condition that there are two same send time, whichever has the + // alphabetically smaller vmId will get the point + else if (sendTime.getTime() === oldestSendTime.getTime() && grec.vmId < vmId) { + vmId = grec.vmId; + } + }); + + if (!hasEqualScore) { + return null; + } + + return vmId; + } +} + +/** + * The constant interval heartbeat sync strategy will handle heartbeats being fired with a + * constant interval and not being interrupted by other events. + * In this strategy, those heartbeats the Autoscale takes much longer time + * to process will be dropped. It will be done by comparing the heartbeat seq before processing + * and the seq of the healthcheck record saved in the DB at the time of record updating. If the + * seq in both objects don't match, that means another hb has updated the record while this hb + * is still processing. The current hb will be discarded. + */ +export class ConstantIntervalHeartbeatSyncStrategy implements HeartbeatSyncStrategy { + protected platform: PlatformAdapter; + protected proxy: CloudFunctionProxyAdapter; + protected targetVm: VirtualMachine; + protected firstHeartbeat = false; + protected result: HealthCheckResult; + protected resultDetail: HealthCheckResultDetail; + protected _targetHealthCheckRecord: HealthCheckRecord; + constructor(platform: PlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.platform = platform; + this.proxy = proxy; + } + prepare(targetVm: VirtualMachine): Promise { + this.targetVm = targetVm; + return Promise.resolve(); + } + + async apply(): Promise { + this.proxy.logAsInfo('applying ConstantIntervalHeartbeatSyncStrategy strategy.'); + let oldLossCount = 0; + let newLossCount = 0; + let oldInterval = 0; + let oldNextHeartbeatTime: number; + let oldRecordedSendTime: string; + const deviceSyncInfo = await this.platform.getReqDeviceSyncInfo(); + const newInterval = deviceSyncInfo.interval * 1000; + const heartbeatArriveTime: number = this.platform.createTime; + // the calculated delay of the current heartbeat comparing to the previous one sent from + // the same device + let delay = 0; + let oldSeq: number; // to be displayed in the log as: old seq -> new seq + let useDeviceSyncInfo: boolean; + let delayCalculationMethod = 'by arrive time'; + let delayAtSendTime = NaN; + let delayAtArriveTime = NaN; + let outdatedHearbeatRequest = false; + const settings = await this.platform.getSettings(); + // number in second the max amount of delay allowed to offset the network latency + const delayAllowance = + Number(settings.get(AutoscaleSetting.HeartbeatDelayAllowance).value) * 1000; + // max amount of heartbeat loss count allowed before deeming a device unhealthy + const maxLossCount = Number(settings.get(AutoscaleSetting.HeartbeatLossCount).value); + const syncRecoveryCountSettingItem = settings.get(AutoscaleSetting.SyncRecoveryCount); + const syncRecoveryCount = + syncRecoveryCountSettingItem && Number(syncRecoveryCountSettingItem.value); + const terminateUnhealthyVmSettingItem = settings.get(AutoscaleSetting.TerminateUnhealthyVm); + const terminateUnhealthyVm = + terminateUnhealthyVmSettingItem && terminateUnhealthyVmSettingItem.truthValue; + // get health check record for target vm + // ASSERT: this.targetVm is valid + let targetHealthCheckRecord = await this.platform.getHealthCheckRecord(this.targetVm.id); + // the next heartbeat arrive time from the Autoscale handler perspective. + const nextHeartbeatTime = heartbeatArriveTime + newInterval; + // if there's no health check record for this vm, + // can deem it the first time for health check + if (!targetHealthCheckRecord) { + this.firstHeartbeat = true; + this.result = HealthCheckResult.OnTime; + // no old next heartbeat time for the first heartbeat, use the current arrival time. + oldNextHeartbeatTime = heartbeatArriveTime; + // no old record for reference, use the seq provided by the device. If the seq is + // NaN, it means no sequence provided by the device, then use 0. + oldSeq = isNaN(deviceSyncInfo.sequence) ? 0 : deviceSyncInfo.sequence; + targetHealthCheckRecord = { + vmId: this.targetVm.id, + scalingGroupName: this.targetVm.scalingGroupName, + ip: this.targetVm.primaryPrivateIpAddress, + primaryIp: '', // primary ip is unknown to this strategy + heartbeatInterval: newInterval, + heartbeatLossCount: 0, // set to 0 because it is the first heartbeat + nextHeartbeatTime: nextHeartbeatTime, + syncState: HeartbeatSyncState.InSync, + syncRecoveryCount: 0, // sync recovery count = 0 means no recovery needed + // use the device sequence if it exists or 1 as the initial sequence + seq: !isNaN(deviceSyncInfo.sequence) ? deviceSyncInfo.sequence : 1, + healthy: true, + upToDate: true, + // additional device sync info whenever provided + sendTime: deviceSyncInfo.time, + deviceSyncTime: deviceSyncInfo.syncTime, + deviceSyncFailTime: deviceSyncInfo.syncFailTime, + deviceSyncStatus: deviceSyncInfo.syncStatus, + deviceIsPrimary: deviceSyncInfo.isPrimary, + deviceChecksum: deviceSyncInfo.checksum, + irresponsivePeriod: 0, + remainingLossAllowed: maxLossCount + }; + // create health check record + try { + await this.platform.createHealthCheckRecord(targetHealthCheckRecord); + } catch (error) { + this.proxy.logForError('createHealthCheckRecord() error.', error); + // cannot create hb record, drop this health check + targetHealthCheckRecord.upToDate = false; + this.result = HealthCheckResult.Dropped; + } + } + // processing regular heartbeat + else { + // store a copy of the set of record data before updating the record + oldLossCount = targetHealthCheckRecord.heartbeatLossCount; + oldInterval = targetHealthCheckRecord.heartbeatInterval; + oldSeq = targetHealthCheckRecord.seq; + oldNextHeartbeatTime = targetHealthCheckRecord.nextHeartbeatTime; + delayAtArriveTime = heartbeatArriveTime - oldNextHeartbeatTime; + oldRecordedSendTime = targetHealthCheckRecord.sendTime; + // if the device provide more information about the heartbeat, do a more accurate + // heartbeat calculation. + // check if the 'time' property exists. It can indicate the availability of the device + // info + const deviceSendTime: Date = + deviceSyncInfo.time !== null && new Date(deviceSyncInfo.time); + const recordedSendTime = new Date(targetHealthCheckRecord.sendTime); + const sendTimeDiff = + deviceSendTime && recordedSendTime + ? deviceSendTime.getTime() - recordedSendTime.getTime() + : NaN; + useDeviceSyncInfo = !!deviceSendTime; + if (useDeviceSyncInfo) { + delayCalculationMethod = 'by device send time'; + // check if the sequence is in an incremental order compared to the data in the db + // if not, the heartbeat should be marked as outdated and to be dropped + // NOTE: if the device has been reboorted, the sequence will be reset to 0 + // need to check the send time as well + + // if the sequence number of current heartbeat request is smaller + // than the recorded one, there are two situations: + // 1. the device is rebooted so the sequence has reset to 0 + // 2. the current heartbeat request has been taken longer time to process for + // some external reasons such as platform api taking longer time to response, + // one or more hb requests (with seq increased) have been processed and saved + // into the db. As the result, the sequence recorded in the db will be greater + // than the current one. + + // handling case 2 + if (deviceSyncInfo.sequence < targetHealthCheckRecord.seq && sendTimeDiff < 0) { + outdatedHearbeatRequest = true; + } else { + // if the device seq is immediately after the recorded value + // check if the send time match the heartbeat interval + if (deviceSyncInfo.sequence === targetHealthCheckRecord.seq + 1) { + // compare using the device provided interval because if the interval + // has changedthe new heartbeat will be sent in the new interval + // this is the delay from the device's perspective + delay = sendTimeDiff - newInterval; + } + // deal with the sequence being reset on the device + // in this case, the sequence will be less than the recorded sequence + // but the device sendtime will be greater than the recorded. + // if such situation occurs, the delay cannot be calculated, just treat it as + // an on-time heartbeat. + else if ( + deviceSyncInfo.sequence < targetHealthCheckRecord.seq && + sendTimeDiff > 0 + ) { + delay = 0; + } + // NOTE: + // in this situation, there are race conditions happening between some + // heartbeat requests. The reason is one autoscale handler is taking much + // longer to process another heartbeat and unable to complete before this + // heartbeat arrives at the handler (by a parallel cloud function process). + // The outcome of this situation is: + // for the hb which is immediate after the recorded one (new seq = old seq + 1) + // it will be handled in the above if-else case. + // for the other hb (new seq > old seq + 1), the delay cannot be calculated + // thus discarding the delay calculation, and trust it is an on-time hb + else { + delay = 0; // on-time hb must have a zero or negative delay + } + delayAtSendTime = delay; + } + } + // calculate delay using the arrive time (classic method) + else { + // NOTE: + // heartbeatArriveTime: the starting time of the function execution, considered as + // the heartbeat arrived at the function + // oldNextHeartbeatTime: the expected arrival time for the current heartbeat, recorded + // in the db, updated in the previous heartbeat calculation + // delayAllowance: the time used in the calcualtion to offest any foreseeable latency + // outside of the function execution. + delay = heartbeatArriveTime - oldNextHeartbeatTime - delayAllowance; + } + // if vm health check shows that it's already out of sync, should drop it + if (targetHealthCheckRecord.syncState === HeartbeatSyncState.OutOfSync) { + oldLossCount = targetHealthCheckRecord.heartbeatLossCount; + oldInterval = targetHealthCheckRecord.heartbeatInterval; + this.result = HealthCheckResult.Dropped; + // if the termination of unhealthy device is set to false, out-of-sync vm should + // be in the sync recovery stage. + // late heartbeat will reset the sync-recovery-count while on-time heartbeat will + // decrease the sync-recovery-count by 1 until it reaches 0 or negative integer; + // sync recovery will change the sync-state back to in-sync + if (!terminateUnhealthyVm) { + // late heartbeat will reset the sync-recovery-count + if (delay > 0) { + targetHealthCheckRecord.syncRecoveryCount = syncRecoveryCount; + } + // on-time heartbeat will decrease sync-recovery-count by 1 from until + // it reaches 0 or negative integer + else { + targetHealthCheckRecord.syncRecoveryCount -= 1; + // a complete recovery (0) will change the sync-state back to in-sync + if (targetHealthCheckRecord.syncRecoveryCount <= 0) { + targetHealthCheckRecord.syncRecoveryCount = 0; + targetHealthCheckRecord.heartbeatLossCount = 0; + newLossCount = 0; + targetHealthCheckRecord.syncState = HeartbeatSyncState.InSync; + targetHealthCheckRecord.healthy = true; + this.result = HealthCheckResult.Recovered; // recovered from out-of-sync + } else { + this.result = HealthCheckResult.Recovering; // still recovering + } + } + } + } else { + // heartbeat is late + if (delay > 0) { + // increase the heartbeat loss count by 1 if delay. + targetHealthCheckRecord.heartbeatLossCount += 1; + newLossCount = targetHealthCheckRecord.heartbeatLossCount; + // reaching the max amount of loss count? + if (targetHealthCheckRecord.heartbeatLossCount >= maxLossCount) { + targetHealthCheckRecord.syncState = HeartbeatSyncState.OutOfSync; + targetHealthCheckRecord.healthy = false; + // when entering out-of-sync state from in-sync state, update + // the sync-recovery-count in order for the device to enter the sync state + // recovery stage + targetHealthCheckRecord.syncRecoveryCount = syncRecoveryCount; + } else { + targetHealthCheckRecord.syncState = HeartbeatSyncState.InSync; + targetHealthCheckRecord.healthy = true; + } + this.result = HealthCheckResult.Late; + } + // else, no delay; heartbeat is on time; clear the loss count. + else { + targetHealthCheckRecord.heartbeatLossCount = 0; + newLossCount = targetHealthCheckRecord.heartbeatLossCount; + targetHealthCheckRecord.healthy = true; + this.result = HealthCheckResult.OnTime; + } + } + // NOTE: use the sequence provided by the device + if (useDeviceSyncInfo && !isNaN(deviceSyncInfo.sequence)) { + targetHealthCheckRecord.seq = deviceSyncInfo.sequence; + } else { + targetHealthCheckRecord.seq += 1; + } + targetHealthCheckRecord.heartbeatInterval = newInterval; + targetHealthCheckRecord.nextHeartbeatTime = heartbeatArriveTime + newInterval; + // additional device sync info whenever provided + targetHealthCheckRecord.sendTime = deviceSyncInfo.time; + targetHealthCheckRecord.deviceSyncTime = deviceSyncInfo.syncTime; + targetHealthCheckRecord.deviceSyncFailTime = deviceSyncInfo.syncFailTime; + targetHealthCheckRecord.deviceSyncStatus = deviceSyncInfo.syncStatus; + targetHealthCheckRecord.deviceIsPrimary = deviceSyncInfo.isPrimary; + targetHealthCheckRecord.deviceChecksum = deviceSyncInfo.checksum; + // update health check record if not marked as outdated + if (!outdatedHearbeatRequest) { + try { + await this.platform.updateHealthCheckRecord(targetHealthCheckRecord); + } catch (error) { + this.proxy.logForError('updateHealthCheckRecord() error.', error); + // cannot create hb record, drop this health check + targetHealthCheckRecord.upToDate = false; + this.result = HealthCheckResult.Dropped; + } + } else { + this.proxy.logAsWarning('Dropped an outdated heartbeat request.'); + targetHealthCheckRecord.upToDate = false; + this.result = HealthCheckResult.Dropped; + } + } + this._targetHealthCheckRecord = targetHealthCheckRecord; + this.resultDetail = { + sequence: targetHealthCheckRecord.seq, + result: this.result, + expectedArriveTime: oldNextHeartbeatTime, + actualArriveTime: heartbeatArriveTime, + heartbeatInterval: newInterval, + oldHeartbeatInerval: oldInterval, + delayAllowance: delayAllowance, + calculatedDelay: delay, + actualDelay: delay + delayAllowance, + heartbeatLossCount: newLossCount, + maxHeartbeatLossCount: maxLossCount, + syncRecoveryCount: targetHealthCheckRecord.syncRecoveryCount, + maxSyncRecoveryCount: syncRecoveryCount + }; + let logMessage = + `Heartbeat sync result: ${this.result},` + + ` heartbeat sequence: ${oldSeq}->${targetHealthCheckRecord.seq},` + + ` heartbeat interval: ${oldInterval}->${newInterval} ms,` + + ` device time for recorded heartbeat: ${oldRecordedSendTime},` + + ` device time for received heartbeat: ${deviceSyncInfo.time},` + + ` delay at send time: ${(isNaN(delayAtSendTime) && 'n/a') || delayAtSendTime} ms,` + + ' heartbeat expected arrive time:' + + ` ${new Date(oldNextHeartbeatTime).toISOString()},` + + ` heartbeat actual arrive time: ${new Date(heartbeatArriveTime).toISOString()},` + + ` heartbeat delay at arrive time: ${delayAtArriveTime} ms,` + + ` heartbeat delay allowance for arrival: ${delayAllowance} ms,` + + ` heartbeat calculated delay: ${delay > 0 ? delay : 0} ms ${delayCalculationMethod},` + + ` heartbeat loss count: ${oldLossCount}->${newLossCount},` + + ` max loss count allowed: ${maxLossCount}.`; + switch (this.result) { + case HealthCheckResult.Recovering: + logMessage = + `${logMessage} This VM requires` + + ` ${targetHealthCheckRecord.syncRecoveryCount} out of ${syncRecoveryCount}` + + ' more on-time heartbeat(s) to recover from out-of-sync state' + + ' and to become in-sync again.'; + break; + case HealthCheckResult.Recovered: + logMessage = + `${logMessage} This VM is recovered. It's state is now:` + + ` ${targetHealthCheckRecord.syncState}.`; + break; + case HealthCheckResult.Late: + logMessage = + `${logMessage} VM termination will ${terminateUnhealthyVm ? '' : 'not'} occur` + + ' on this VM when it enters out-of-sync state.'; + break; + default: + break; + } + this.proxy.logAsInfo(logMessage); + this.proxy.logAsInfo('applied ConstantIntervalHeartbeatSyncStrategy strategy.'); + return this.result; + } + get targetHealthCheckRecord(): HealthCheckRecord { + return this._targetHealthCheckRecord; + } + primaryHealthCheckRecord: HealthCheckRecord; + get healthCheckResult(): HealthCheckResult { + return this.result; + } + get healthCheckResultDetail(): HealthCheckResultDetail { + return this.resultDetail; + } + get targetVmFirstHeartbeat(): boolean { + return this.firstHeartbeat; + } + async forceOutOfSync(): Promise { + this.proxy.logAsInfo('calling ConstantIntervalHeartbeatSyncStrategy.forceOutOfSync.'); + try { + // ASSERT: this.targetVm is valid + const healthcheckRecord: HealthCheckRecord = await this.platform.getHealthCheckRecord( + this.targetVm.id + ); + // if its status is 'out-of-sync' already, don't need to update + if (healthcheckRecord.syncState === HeartbeatSyncState.OutOfSync) { + return true; + } + // update its state to be 'out-of-sync' + const emitter: WaitForPromiseEmitter = () => { + return this.platform.getHealthCheckRecord(this.targetVm.id); + }; + const checker: WaitForConditionChecker = (record, callCount) => { + if (callCount > 3) { + throw new Error(`maximum amount of attempts ${callCount} have been reached.`); + } + if (record.syncState === HeartbeatSyncState.OutOfSync) { + return Promise.resolve(true); + } else { + return Promise.resolve(false); + } + }; + // change status to outofsync + healthcheckRecord.syncState = HeartbeatSyncState.OutOfSync; + await this.platform.updateHealthCheckRecord(healthcheckRecord); + // wait for state change + await waitFor(emitter, checker, 5000, this.proxy); + this.proxy.logAsInfo('called ConstantIntervalHeartbeatSyncStrategy.forceOutOfSync.'); + return true; + } catch (error) { + this.proxy.logForError('error in forceOutOfSync()', error); + this.proxy.logAsInfo('called ConstantIntervalHeartbeatSyncStrategy.forceOutOfSync.'); + return false; + } + } +} + +export class NoopTaggingVmStrategy implements TaggingVmStrategy { + private proxy: CloudFunctionProxyAdapter; + constructor(platform: PlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.proxy = proxy; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + prepare(taggings: VmTagging[]): Promise { + return Promise.resolve(); + } + apply(): Promise { + this.proxy.logAsInfo('calling NoopTaggingVmStrategy.apply.'); + this.proxy.logAsInfo('called NoopTaggingVmStrategy.apply.'); + return Promise.resolve(); + } +} + +export class NoopRoutingEgressTrafficStrategy implements RoutingEgressTrafficStrategy { + private proxy: CloudFunctionProxyAdapter; + constructor(platform: PlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.proxy = proxy; + } + prepare( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + env: AutoscaleEnvironment + ): Promise { + return Promise.resolve(); + } + apply(): Promise { + this.proxy.logAsInfo('calling NoopRoutingEgressTrafficStrategy.apply.'); + this.proxy.logAsInfo('called NoopRoutingEgressTrafficStrategy.apply.'); + return Promise.resolve(); + } +} diff --git a/core/context-strategy/bootstrap-context.ts b/core/context-strategy/bootstrap-context.ts new file mode 100644 index 0000000..9742c4f --- /dev/null +++ b/core/context-strategy/bootstrap-context.ts @@ -0,0 +1,16 @@ +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum BootstrapConfigStrategyResult { + SUCCESS, + FAILED +} + +export interface BootstrapConfigurationStrategy { + getConfiguration(): string; + apply(): Promise; +} + +export interface BootstrapContext { + setBootstrapConfigurationStrategy(strategy: BootstrapConfigurationStrategy): void; + handleBootstrap(): Promise; +} diff --git a/core/context-strategy/index.ts b/core/context-strategy/index.ts new file mode 100644 index 0000000..02acf88 --- /dev/null +++ b/core/context-strategy/index.ts @@ -0,0 +1,6 @@ +export * from './autoscale-context'; +export * from './bootstrap-context'; +export * from './licensing-context'; +export * from './nic-attachment-context'; +export * from './scaling-group-context'; +export * from './vpn-attachment-context'; diff --git a/core/context-strategy/licensing-context.ts b/core/context-strategy/licensing-context.ts new file mode 100644 index 0000000..7615d97 --- /dev/null +++ b/core/context-strategy/licensing-context.ts @@ -0,0 +1,378 @@ +import path from 'path'; +import { CloudFunctionProxyAdapter } from '../cloud-function-proxy'; +import { waitFor, WaitForConditionChecker, WaitForPromiseEmitter } from '../helper-function'; +import { + LicenseFile, + LicenseStockRecord, + LicenseUsageRecord, + PlatformAdapter +} from '../platform-adapter'; +import { VirtualMachine, VirtualMachineState } from '../virtual-machine'; + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum LicensingStrategyResult { + LicenseAssigned = 'license-assigned', + LicenseOutOfStock = 'license-out-of-stock', + LicenseNotRequired = 'license-not-required' +} + +export interface LicensingStrategy { + prepare( + vm: VirtualMachine, + productName: string, + storageContainerName: string, + licenseDirectoryName: string + ): Promise; + apply(): Promise; + getLicenseContent(): Promise; +} + +/** + * To provide Licensing model related logics such as license assignment. + */ +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export interface LicensingModelContext { + setLicensingStrategy(strategy: LicensingStrategy): void; + handleLicenseAssignment(productName: string): Promise; +} + +export class NoopLicensingStrategy implements LicensingStrategy { + platform: PlatformAdapter; + proxy: CloudFunctionProxyAdapter; + vm: VirtualMachine; + storageContainerName: string; + licenseDirectoryName: string; + constructor(platform: PlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.platform = platform; + this.proxy = proxy; + } + prepare( + vm: VirtualMachine, + storageContainerName: string, + licenseDirectoryName: string + ): Promise { + this.vm = vm; + this.storageContainerName = storageContainerName; + this.licenseDirectoryName = licenseDirectoryName; + return Promise.resolve(); + } + apply(): Promise { + this.proxy.logAsInfo('calling NoopLicensingStrategy.apply'); + this.proxy.logAsInfo('noop'); + this.proxy.logAsInfo('called NoopLicensingStrategy.apply'); + return Promise.resolve(LicensingStrategyResult.LicenseNotRequired); + } + getLicenseContent(): Promise { + return Promise.resolve(''); + } +} + +export class ReusableLicensingStrategy implements LicensingStrategy { + platform: PlatformAdapter; + proxy: CloudFunctionProxyAdapter; + vm: VirtualMachine; + storageContainerName: string; + licenseDirectoryName: string; + licenseFiles: LicenseFile[]; + stockRecords: LicenseStockRecord[]; + usageRecords: LicenseUsageRecord[]; + licenseRecord: LicenseStockRecord | null; + private licenseFile: LicenseFile; + productName: string; + constructor(platform: PlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.platform = platform; + this.proxy = proxy; + } + prepare( + vm: VirtualMachine, + productName: string, + storageContainerName: string, + licenseDirectoryName: string + ): Promise { + this.vm = vm; + this.productName = productName; + this.storageContainerName = storageContainerName; + this.licenseDirectoryName = licenseDirectoryName; + this.licenseFiles = []; + this.stockRecords = []; + this.usageRecords = []; + return Promise.resolve(); + } + async apply(): Promise { + this.proxy.logAsInfo('calling ReusableLicensingStrategy.apply'); + [this.licenseFiles, this.stockRecords, this.usageRecords] = await Promise.all([ + this.platform + .listLicenseFiles(this.storageContainerName, this.licenseDirectoryName) + .catch(err => { + this.proxy.logForError('failed to list license blob files.', err); + return []; + }), + this.platform.listLicenseStock(this.productName).catch(err => { + this.proxy.logForError('failed to list license stock', err); + return null; + }), + this.platform.listLicenseUsage(this.productName).catch(err => { + this.proxy.logForError('failed to list license stock', err); + return null; + }) + ]); + // update the license stock records on db if any change in file storage + // this returns the newest stockRecords on the db + await this.updateLicenseStockRecord(this.licenseFiles); + this.stockRecords = await this.platform.listLicenseStock(this.productName); + + // is the license in use by the same vm? + [this.licenseRecord] = Array.from(this.usageRecords.values()).filter(record => { + return record.vmId === this.vm.id; + }) || [null]; + + // if the usage record array contains a license that is physically deleted, + // should remove such record from the usage record array so that the license files used + // in the usage records are always valid. + const stockLicenseChecksum = this.stockRecords.map(stock => stock.checksum); + this.usageRecords = this.usageRecords.filter(usage => + stockLicenseChecksum.includes(usage.checksum) + ); + + try { + // get an available license + if (this.licenseFiles.length > 0 && !this.licenseRecord) { + this.licenseRecord = await this.getAvailableLicense(); + } + if (!this.licenseRecord) { + this.proxy.logAsWarning('No license available.'); + this.proxy.logAsInfo('called ReusableLicensingStrategy.apply'); + return LicensingStrategyResult.LicenseOutOfStock; + } + + // load license content + const filePath = path.posix.join( + this.licenseDirectoryName, + this.licenseRecord.fileName + ); + this.proxy.logAsDebug( + 'load blob in blob container', + `name: [${this.storageContainerName}], path:` + `[${filePath}]` + ); + const content = await this.platform.loadLicenseFileContent( + this.storageContainerName, + filePath + ); + this.licenseFile = { + fileName: this.licenseRecord.fileName, + checksum: this.licenseRecord.checksum, + algorithm: this.licenseRecord.algorithm, + content: content + }; + this.proxy.logAsInfo( + `license file (name: ${this.licenseFile.fileName},` + + ` checksum: ${this.licenseFile.checksum}) is loaded.` + ); + } catch (error) { + this.proxy.logForError('Failed to get a license.', error); + this.proxy.logAsInfo('called ReusableLicensingStrategy.apply'); + return LicensingStrategyResult.LicenseOutOfStock; + } + + this.proxy.logAsInfo('called ReusableLicensingStrategy.apply'); + return LicensingStrategyResult.LicenseAssigned; + } + async updateLicenseStockRecord(licenseFiles: LicenseFile[]): Promise { + const stockArray = + (licenseFiles && + licenseFiles.map(f => { + return { + fileName: f.fileName, + checksum: f.checksum, + algorithm: f.algorithm, + productName: this.productName + } as LicenseStockRecord; + })) || + []; + await this.platform.updateLicenseStock(stockArray); + } + + /** + * sync the vm in-sync status from the scaling group to the usage record + * + * @protected + * @param {LicenseUsageRecord[]} usageRecords array of license usage record + * @returns {Promise} void + */ + protected async syncVmStatusToUsageRecords(usageRecords: LicenseUsageRecord[]): Promise { + const updatedRecordArray = await Promise.all( + usageRecords.map(async u => { + const vm = await this.platform.getVmById(u.vmId, u.scalingGroupName); + const item: LicenseUsageRecord = { ...u }; + item.vmInSync = !!(vm && vm.state === VirtualMachineState.Running); + return { item: item, reference: u }; + }) + ); + await this.platform.updateLicenseUsage(updatedRecordArray); + } + + protected async useLicense(record: LicenseUsageRecord, vm: VirtualMachine): Promise { + try { + const newRecord: LicenseUsageRecord = { ...record }; + newRecord.scalingGroupName = vm.scalingGroupName; + newRecord.vmId = vm.id; + newRecord.assignedTime = Date.now(); + // ASSERT: vm is in sync. + newRecord.vmInSync = true; + await this.platform.updateLicenseUsage([ + { item: newRecord, reference: record.vmId ? record : null } + ]); + // refresh the usage record because it is updated. + this.usageRecords = await this.platform.listLicenseUsage(this.productName); + return true; + } catch (error) { + return false; + } + } + + protected async listOutOfSyncRecord(sync?: boolean): Promise { + let outOfSyncArray: LicenseUsageRecord[]; + if (sync) { + await this.syncVmStatusToUsageRecords(this.usageRecords).catch(() => { + this.proxy.logAsWarning( + 'Ignore errors when sync VM status to license usage records.' + ); + }); + this.usageRecords = Array.from( + (await this.platform.listLicenseUsage(this.productName)).values() + ); + return Array.from(this.usageRecords.values()).filter(usageRecrod => { + return !usageRecrod.vmInSync; + }); + } else { + outOfSyncArray = Array.from(this.usageRecords.values()).filter(usageRecrod => { + return !usageRecrod.vmInSync; + }); + // if every license is in use and seems to be in-sync, + // sync the record with vm running state and heartbeat records, + // then check it once again + if (outOfSyncArray.length === 0) { + return await this.listOutOfSyncRecord(true); + } + return outOfSyncArray; + } + } + + protected async getAvailableLicense(): Promise { + let outOfSyncArray: LicenseUsageRecord[]; + // try to look for an unused one first + // checksum is the unique key of a license + const usageMap: Map = new Map( + this.usageRecords.map(u => [u.checksum, u]) + ); + const unusedArray = this.stockRecords.filter( + stockRecord => !usageMap.has(stockRecord.checksum) + ); + let licenseStockRecord: LicenseStockRecord; + if (unusedArray.length > 0) { + // pick the first one, lock the license in order to prevent it from being picked at the + // same time, and return as unused license + let index = 0; + const unusedLicenseEmitter: WaitForPromiseEmitter = async () => { + const usageRecord: LicenseUsageRecord = { + fileName: unusedArray[index].fileName, + checksum: unusedArray[index].checksum, + algorithm: unusedArray[index].algorithm, + productName: unusedArray[index].productName, + vmId: undefined, + scalingGroupName: undefined, + assignedTime: undefined, + vmInSync: true + }; + const result = await this.useLicense(usageRecord, this.vm); + return result && unusedArray[index]; + }; + + const unusedLicenseChecker: WaitForConditionChecker = rec => { + if (rec) { + return Promise.resolve(true); + } else if (index < unusedArray.length - 1) { + index++; + return Promise.resolve(false); + } else { + throw new Error( + `None of the unused licenses (total: ${unusedArray.length})` + + 'can be used at this moment. Probably they have been assigned already.' + ); + } + }; + + try { + licenseStockRecord = await waitFor( + unusedLicenseEmitter, + unusedLicenseChecker, + 5000, + this.proxy + ); + } catch (error) { + this.proxy.logForError('Error in allocating an unused license.', error); + } + } + + // a valid license is allocated. return the license record + if (licenseStockRecord) { + this.proxy.logAsInfo( + `An unused license (checksum: ${licenseStockRecord.checksum}, ` + + `file name: ${licenseStockRecord.fileName}) is found.` + ); + return licenseStockRecord; + } + // if no availalbe unused license, check if any in-use license is associated + // with a vm which isn't in-sync + else { + // pick the first one and return as a reusable license + // in order to avoid race conditions, + // set a loop to pick the next available license by updating the usage record + // if sucessfully updated one record, that record can then be used. + let maxTries = 0; + const usedLicenseEmitter: WaitForPromiseEmitter = async () => { + outOfSyncArray = await this.listOutOfSyncRecord(); + // determine the maximum number of tries before giving up + maxTries = Math.max(maxTries, outOfSyncArray.length); + return ( + (outOfSyncArray.length > 0 && + (await this.useLicense(outOfSyncArray[0], this.vm)) && + outOfSyncArray[0]) || + null + ); + }; + const usedLicenseChecker: WaitForConditionChecker = ( + rec, + callCount + ) => { + if (rec) { + return Promise.resolve(true); + } else if (outOfSyncArray.length === 0) { + throw new Error('Run out of license.'); + } else if (callCount >= maxTries) { + throw new Error(`maximum amount of attempts ${maxTries} have been reached.`); + } else { + return Promise.resolve(false); + } + }; + const licenseRecord = await waitFor( + usedLicenseEmitter, + usedLicenseChecker, + 5000, + this.proxy + ); + + this.proxy.logAsInfo( + `A reusable license (checksum: ${licenseRecord.checksum},` + + ` previous assigned vmId: ${licenseRecord.vmId},` + + ` file name: ${licenseRecord.fileName}) is found.` + ); + return licenseRecord; + } + } + getLicenseContent(): Promise { + return Promise.resolve(this.licenseFile.content); + } +} diff --git a/core/context-strategy/nic-attachment-context.ts b/core/context-strategy/nic-attachment-context.ts new file mode 100644 index 0000000..641ce8b --- /dev/null +++ b/core/context-strategy/nic-attachment-context.ts @@ -0,0 +1,66 @@ +import { CloudFunctionProxyAdapter } from '../cloud-function-proxy'; +import { PlatformAdapter } from '../platform-adapter'; +import { VirtualMachine } from '../virtual-machine'; + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum NicAttachmentStrategyResult { + Success = 'success', + Failed = 'failed', + ShouldTerminateVm = 'should-terminate-vm', + ShouldContinue = 'should-continue' +} + +export interface NicAttachmentStrategy { + prepare(vm: VirtualMachine): Promise; + attach(): Promise; + detach(): Promise; + cleanUp(): Promise; +} + +/** + * To provide secondary network interface attachment related logics + */ +export interface NicAttachmentContext { + handleNicAttachment(): Promise; + handleNicDetachment(): Promise; + cleanupUnusedNic(): Promise; + setNicAttachmentStrategy(strategy: NicAttachmentStrategy): void; +} + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum NicAttachmentStatus { + Attaching = 'Attaching', + Attached = 'Attached', + Detaching = 'Detaching', + Detached = 'Detached' +} + +export interface NicAttachmentRecord { + vmId: string; + nicId: string; + attachmentState: string; +} + +export class NoopNicAttachmentStrategy implements NicAttachmentStrategy { + constructor( + readonly platform: PlatformAdapter, + readonly proxy: CloudFunctionProxyAdapter + ) {} + prepare( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + vm: VirtualMachine + ): Promise { + return Promise.resolve(); + } + attach(): Promise { + return Promise.resolve(NicAttachmentStrategyResult.Success); + } + detach(): Promise { + return Promise.resolve(NicAttachmentStrategyResult.Success); + } + cleanUp(): Promise { + return Promise.resolve(0); + } +} diff --git a/core/context-strategy/scaling-group-context.ts b/core/context-strategy/scaling-group-context.ts new file mode 100644 index 0000000..6d48e37 --- /dev/null +++ b/core/context-strategy/scaling-group-context.ts @@ -0,0 +1,61 @@ +import { CloudFunctionProxyAdapter } from '../cloud-function-proxy'; +import { PlatformAdapter } from '../platform-adapter'; + +export interface ScalingGroupStrategy { + onLaunchingVm(): Promise; + onLaunchedVm(): Promise; + onTerminatingVm(): Promise; + onTerminatedVm(): Promise; + onVmNotLaunched(): Promise; + completeLaunching(success?: boolean): Promise; + completeTerminating(success?: boolean): Promise; +} + +/** + * To provide auto scaling group related logics such as scaling out, scaling in. + */ +export interface ScalingGroupContext { + setScalingGroupStrategy(strategy: ScalingGroupStrategy): void; + handleLaunchingVm(): Promise; + handleLaunchedVm(): Promise; + handleTerminatingVm(): Promise; + handleTerminatedVm(): Promise; + handleVmNotLaunched(): Promise; +} + +export class NoopScalingGroupStrategy implements ScalingGroupStrategy { + platform: PlatformAdapter; + proxy: CloudFunctionProxyAdapter; + constructor(platform: PlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.platform = platform; + this.proxy = proxy; + } + onVmNotLaunched(): Promise { + this.proxy.logAsInfo('Noop on vm launching unsucessful.'); + return Promise.resolve(''); + } + onLaunchingVm(): Promise { + this.proxy.logAsInfo('Noop on launching.'); + return Promise.resolve(''); + } + onLaunchedVm(): Promise { + this.proxy.logAsInfo('Noop on launched.'); + return Promise.resolve(''); + } + onTerminatingVm(): Promise { + this.proxy.logAsInfo('Noop on terminating.'); + return Promise.resolve(''); + } + onTerminatedVm(): Promise { + this.proxy.logAsInfo('Noop on terminated.'); + return Promise.resolve(''); + } + completeLaunching(success = true): Promise { + this.proxy.logAsInfo(`Noop on completeLaunching (${success})`); + return Promise.resolve(''); + } + completeTerminating(success = true): Promise { + this.proxy.logAsInfo(`Noop on completeLaunching (${success})`); + return Promise.resolve(''); + } +} diff --git a/core/context-strategy/vpn-attachment-context.ts b/core/context-strategy/vpn-attachment-context.ts new file mode 100644 index 0000000..4d70f8b --- /dev/null +++ b/core/context-strategy/vpn-attachment-context.ts @@ -0,0 +1,50 @@ +import { CloudFunctionProxyAdapter } from '../cloud-function-proxy'; +import { PlatformAdapter } from '../platform-adapter'; +import { VirtualMachine } from '../virtual-machine'; + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum VpnAttachmentStrategyResult { + Success = 'success', + Failed = 'failed', + ShouldTerminateVm = 'should-terminate-vm', + ShouldContinue = 'should-continue' +} + +export interface VpnAttachmentStrategy { + prepare(vm: VirtualMachine): Promise; + attach(): Promise; + detach(): Promise; + cleanup(): Promise; +} + +/** + * To provide VPN connection attachment related logics + */ +export interface VpnAttachmentContext { + handleVpnAttachment(): Promise; + handleVpnDetachment(): Promise; + setVpnAttachmentStrategy(strategy: VpnAttachmentStrategy): void; +} + +export class NoopVpnAttachmentStrategy implements VpnAttachmentStrategy { + constructor( + readonly platform: PlatformAdapter, + readonly proxy: CloudFunctionProxyAdapter + ) {} + cleanup(): Promise { + return Promise.resolve(0); + } + attach(): Promise { + return Promise.resolve(VpnAttachmentStrategyResult.ShouldContinue); + } + detach(): Promise { + return Promise.resolve(VpnAttachmentStrategyResult.ShouldContinue); + } + prepare( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + vm: VirtualMachine + ): Promise { + return Promise.resolve(); + } +} diff --git a/core/db-definitions.ts b/core/db-definitions.ts new file mode 100644 index 0000000..df63078 --- /dev/null +++ b/core/db-definitions.ts @@ -0,0 +1,983 @@ +/* eslint-disable @typescript-eslint/no-duplicate-enum-values */ +export interface KeyValue { + key: string; + value: string; +} + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum TypeRef { + StringType = 'AutoscaleStringType', + NumberType = 'AutoscaleStringType', + BooleanType = 'AutoscaleBooleanType', + PrimaryKey = 'AutoscaleStringType', + SecondaryKey = 'AutoscaleStringType' +} + +export interface SchemaElement { + name: string; + keyType: TypeRef | string; +} +export type TypeRefMap = Map; + +export interface Attribute { + name: string; + attrType: TypeRef | string; + isKey: boolean; + keyType?: TypeRef | string; +} + +export interface Record { + [key: string]: string | number | boolean; +} + +/** + * DB save data condition + * @enum {string} + */ +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum SaveCondition { + /** + * @member {string} InsertOnly strictly insert only if not exists + */ + InsertOnly = 'InsertOnly', + /** + * @member {string} UpdateOnly strictly update only if exists + */ + UpdateOnly = 'UpdateOnly', + /** + * @member {string} Upsert insert if not exists or update if exists + */ + Upsert = 'Upsert' +} + +/** + * must be implement to provide platform specific data type conversion + * + * @export + * @abstract + * @class TypeConvert + */ +export abstract class TypeConverter { + /** + * convert a value of string type stored in the db to a js primitive string type + * + * @abstract + * @param {unknown} value + * @returns {string} + */ + abstract valueToString(value: unknown): string; + /** + * convert a value of number type stored in the db to a js primitive number type + * + * @abstract + * @param {unknown} value + * @returns {number} + */ + abstract valueToNumber(value: unknown): number; + /** + * convert a value of boolean type stored in the db to a js boolean primitive type + * + * @abstract + * @param {unknown} value + * @returns {boolean} + */ + abstract valueToBoolean(value: unknown): boolean; +} + +export interface BidirectionalCastable { + /** + * a downcast converts a db record from a parent structure to a child structure by updating the + * properties with the same name, setting the child's own properties, then returns the child. + * @param {PARENT} record the record using PARENT interface + * @returns {CHILD} a record using CHILD interface + */ + downcast(record: PARENT): CHILD; + /** + * an upcast converts a db record from a child structure to a parent structure by updating the + * properties with the same name, then returns the parent. + * @param {CHILD} record the record using CHILD interface + * @returns {PARENT} a record using PARENT interface + */ + upcast(record: CHILD): PARENT; +} + +export class DbError extends Error { + constructor( + readonly code: string, + message: string + ) { + super(message); + } +} + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum DbErrorCode { + NotFound = 'NotFound', + KeyConflict = 'KeyConflict', + InconsistentData = 'InconsistentData', + UnexpectedResponse = 'UnexpectedResponse' +} + +export class DbReadError extends DbError {} +export class DbSaveError extends DbError {} +export class DbDeleteError extends DbError {} + +export abstract class Table { + static TypeRefMap: Map = new Map([ + [TypeRef.StringType, 'String'], + [TypeRef.NumberType, 'Number'], + [TypeRef.BooleanType, 'Boolean'], + [TypeRef.PrimaryKey, 'PrimaryKey'], + [TypeRef.SecondaryKey, 'SecondaryKey'] + ]); + private _name: string; + protected _schema: Map; + protected _keys: Map; + protected _attributes: Map; + constructor( + readonly typeConvert: TypeConverter, + readonly namePrefix: string = '', + readonly nameSuffix: string = '' + ) { + this._attributes = new Map(); + } + /** + * validate the input before putting into the database + * @param {TI} input the input object to be validated + * @throws an Error object + */ + validateInput(input: TI): void { + const keys = Object.keys(input); + this.attributes.forEach(attrName => { + if (!keys.includes) { + throw new Error(`Table [${this.name}] required attribute [${attrName}] not found.`); + } + }); + } + + /** + * Set the name of the table (not include prefix or suffix) + * @param {string} n name of the table + */ + protected setName(n: string): void { + this._name = n; + } + /** + * Table name (with prefix and suffix if provided) + */ + get name(): string { + return ( + this.namePrefix + + (this.namePrefix ? '-' : '') + + this._name + + (this.nameSuffix ? '-' : '') + + this.nameSuffix + ); + } + /** + * Table schema + */ + get schema(): Map { + if (!this._schema) { + this._schema = new Map( + Array.from(this._attributes.values()) + .filter(attr => attr.isKey) + .map(a => [ + a.name, + { + name: a.name, + keyType: a.keyType + } as SchemaElement + ]) + ); + } + return this._schema; + } + /** + * Table Key attributes + */ + get keys(): Map { + if (!this._keys) { + this._keys = new Map( + Array.from(this._attributes.values()) + .filter(attr => attr.isKey) + .map(a => [a.name, a]) + ); + } + return this._keys; + } + /** + * Table all attributes including key attributes + */ + get attributes(): Map { + return this._attributes; + } + + get primaryKey(): Attribute { + const [pk] = Array.from(this.keys.values()).filter( + key => key.keyType === TypeRef.PrimaryKey + ); + return pk; + } + + /** + * Alter the type of each attribute using a given type reference map. + * Every attribute in the Autoscale generic Table uses a TypeRef refernce as its type. + * The reason is table attribute type and key type may vary in different platforms, + * the platform-specific derived Table classes are intended to be a concrete class + * with a determined type. + * @param {TypeRefMap} typeRefs attribute type reference map + */ + protected alterAttributesUsingTypeReference(typeRefs: TypeRefMap): void { + const typeRefValues = Object.values(TypeRef); + Array.from(this._attributes.keys()).forEach(name => { + const attr = this._attributes.get(name); + if (attr.keyType && typeRefValues.indexOf(attr.keyType)) { + attr.keyType = typeRefs.get(attr.keyType as TypeRef); + } + if (attr.attrType && typeRefValues.indexOf(attr.attrType)) { + attr.attrType = typeRefs.get(attr.attrType as TypeRef); + } + this._attributes.set(attr.name, attr); + }); + } + /** + * Alter the table attribute definitions. Provide ability to change db definition in a derived + * class for a certain platform. + * @param {Attribute[]} definitions new definitions to use + */ + alterAttributes(definitions: Attribute[]): void { + let dirty = false; + definitions.forEach(def => { + if (this._attributes.has(def.name)) { + dirty = true; + const attr: Attribute = { + name: def.name, + isKey: def.isKey, + attrType: def.attrType + }; + if (def.isKey && def.keyType) { + attr.keyType = def.keyType; + } + this._attributes.set(attr.name, attr); + } + }); + // recreate key and schema + if (dirty) { + this._keys = null; + this._schema = null; + } + } + addAttribute(def: Attribute): void { + const attr: Attribute = { + name: def.name, + isKey: def.isKey, + attrType: def.attrType + }; + if (def.isKey && def.keyType) { + attr.keyType = def.keyType; + } + this._attributes.set(attr.name, attr); + } + // NOTE: no deleting attribute method should be provided. + abstract convertRecord(record: Record): T; + assign(target: T, record: Record): void { + for (const p in Object.keys(target)) { + if (typeof p === 'string') { + target[p] = this.typeConvert.valueToString(record[p]); + } else if (typeof p === 'number') { + target[p] = this.typeConvert.valueToNumber(record[p]); + } else if (typeof p === 'boolean') { + target[p] = this.typeConvert.valueToBoolean(record[p]); + } + } + } +} +export interface AutoscaleDbItem extends Record { + vmId: string; + scalingGroupName: string; + ip: string; + primaryIp: string; + heartBeatInterval: number; + heartBeatLossCount: number; + nextHeartBeatTime: number; + syncState: string; + syncRecoveryCount: number; + seq: number; + sendTime: string; + deviceSyncTime: string; + deviceSyncFailTime: string; + deviceSyncStatus: string; + deviceIsPrimary: string; + deviceChecksum: string; +} + +export class Autoscale extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'vmId', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'scalingGroupName', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'ip', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'primaryIp', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'heartBeatLossCount', + attrType: TypeRef.NumberType, + isKey: false + }, + { + name: 'heartBeatInterval', + attrType: TypeRef.NumberType, + isKey: false + }, + { + name: 'nextHeartBeatTime', + attrType: TypeRef.NumberType, + isKey: false + }, + { + name: 'syncState', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'syncRecoveryCount', + attrType: TypeRef.NumberType, + isKey: false + }, + { + name: 'seq', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'sendTime', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'deviceSyncTime', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'deviceSyncFailTime', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'deviceSyncStatus', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'deviceIsPrimary', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'deviceChecksum', + attrType: TypeRef.StringType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('Autoscale'); + Autoscale.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): AutoscaleDbItem { + const item: AutoscaleDbItem = { + vmId: this.typeConvert.valueToString(record.vmId), + scalingGroupName: this.typeConvert.valueToString(record.scalingGroupName), + ip: this.typeConvert.valueToString(record.ip), + primaryIp: this.typeConvert.valueToString(record.primaryIp), + heartBeatLossCount: this.typeConvert.valueToNumber(record.heartBeatLossCount), + heartBeatInterval: this.typeConvert.valueToNumber(record.heartBeatInterval), + nextHeartBeatTime: this.typeConvert.valueToNumber(record.nextHeartBeatTime), + syncState: this.typeConvert.valueToString(record.syncState), + syncRecoveryCount: this.typeConvert.valueToNumber(record.syncRecoveryCount), + seq: this.typeConvert.valueToNumber(record.seq), + sendTime: this.typeConvert.valueToString(record.sendTime), + deviceSyncTime: this.typeConvert.valueToString(record.deviceSyncTime), + deviceSyncFailTime: this.typeConvert.valueToString(record.deviceSyncFailTime), + deviceSyncStatus: this.typeConvert.valueToString(record.deviceSyncStatus), + deviceIsPrimary: this.typeConvert.valueToString(record.deviceIsPrimary), + deviceChecksum: this.typeConvert.valueToString(record.deviceChecksum) + }; + return item; + } +} +export interface PrimaryElectionDbItem extends Record { + scalingGroupName: string; + vmId: string; + id: string; + ip: string; + virtualNetworkId: string; + subnetId: string; + voteEndTime: number; + voteState: string; +} +export class PrimaryElection extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'id', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'scalingGroupName', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'vmId', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'ip', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'virtualNetworkId', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'subnetId', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'voteEndTime', + attrType: TypeRef.NumberType, + isKey: false + }, + { + name: 'voteState', + attrType: TypeRef.StringType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('PrimaryElection'); + PrimaryElection.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): PrimaryElectionDbItem { + const item: PrimaryElectionDbItem = { + scalingGroupName: this.typeConvert.valueToString(record.scalingGroupName), + vmId: this.typeConvert.valueToString(record.vmId), + id: this.typeConvert.valueToString(record.id), + ip: this.typeConvert.valueToString(record.ip), + virtualNetworkId: this.typeConvert.valueToString(record.virtualNetworkId), + subnetId: this.typeConvert.valueToString(record.subnetId), + voteEndTime: this.typeConvert.valueToNumber(record.voteEndTime), + voteState: this.typeConvert.valueToString(record.voteState) + }; + return item; + } +} +export interface FortiAnalyzerDbItem extends Record { + vmId: string; + ip: string; + primary: boolean; + vip: string; +} + +export class FortiAnalyzer extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'vmId', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'ip', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'primary', + attrType: TypeRef.BooleanType, + isKey: false + }, + { + name: 'vip', + attrType: TypeRef.StringType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('FortiAnalyzer'); + FortiAnalyzer.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): FortiAnalyzerDbItem { + const item: FortiAnalyzerDbItem = { + vmId: this.typeConvert.valueToString(record.vmId), + ip: this.typeConvert.valueToString(record.ip), + primary: this.typeConvert.valueToBoolean(record.primary), + vip: this.typeConvert.valueToString(record.vip) + }; + return item; + } +} +export interface SettingsDbItem extends Record { + settingKey: string; + settingValue: string; + description: string; + jsonEncoded: boolean; + editable: boolean; +} +export class Settings extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'settingKey', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'settingValue', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'description', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'jsonEncoded', + attrType: TypeRef.BooleanType, + isKey: false + }, + { + name: 'editable', + attrType: TypeRef.BooleanType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('Settings'); + Settings.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): SettingsDbItem { + const item: SettingsDbItem = { + settingKey: this.typeConvert.valueToString(record.settingKey), + settingValue: this.typeConvert.valueToString(record.settingValue), + description: this.typeConvert.valueToString(record.description), + jsonEncoded: this.typeConvert.valueToBoolean(record.jsonEncoded), + editable: this.typeConvert.valueToBoolean(record.editable) + }; + return item; + } +} +export interface NicAttachmentDbItem extends Record { + vmId: string; + nicId: string; + attachmentState: string; +} +export class NicAttachment extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'vmId', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'nicId', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'attachmentState', + attrType: TypeRef.StringType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('NicAttachment'); + NicAttachment.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): NicAttachmentDbItem { + const item: NicAttachmentDbItem = { + vmId: this.typeConvert.valueToString(record.vmId), + nicId: this.typeConvert.valueToString(record.nicId), + attachmentState: this.typeConvert.valueToString(record.attachmentState) + }; + return item; + } +} + +export interface VmInfoCacheDbItem extends Record { + id: string; + vmId: string; + index: number; + scalingGroupName: string; + info: string; + timestamp: number; + expireTime: number; +} +export class VmInfoCache extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'id', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'vmId', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'index', + attrType: TypeRef.NumberType, + isKey: false + }, + { + name: 'scalingGroupName', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'info', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'timestamp', + attrType: TypeRef.NumberType, + isKey: false + }, + { + name: 'expireTime', + attrType: TypeRef.NumberType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('VmInfoCache'); + VmInfoCache.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): VmInfoCacheDbItem { + const item: VmInfoCacheDbItem = { + id: this.typeConvert.valueToString(record.id), + vmId: this.typeConvert.valueToString(record.vmId), + index: this.typeConvert.valueToNumber(record.index), + scalingGroupName: this.typeConvert.valueToString(record.scalingGroupName), + info: this.typeConvert.valueToString(record.info), + timestamp: this.typeConvert.valueToNumber(record.timestamp), + expireTime: this.typeConvert.valueToNumber(record.expireTime) + }; + return item; + } +} + +export interface LicenseStockDbItem extends Record { + checksum: string; + algorithm: string; + fileName: string; + productName: string; +} +export class LicenseStock extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'checksum', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'algorithm', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'fileName', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'productName', + attrType: TypeRef.StringType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('LicenseStock'); + LicenseStock.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): LicenseStockDbItem { + const item: LicenseStockDbItem = { + checksum: this.typeConvert.valueToString(record.checksum), + algorithm: this.typeConvert.valueToString(record.algorithm), + fileName: this.typeConvert.valueToString(record.fileName), + productName: this.typeConvert.valueToString(record.productName) + }; + return item; + } +} + +export interface LicenseUsageDbItem extends Record { + checksum: string; + algorithm: string; + fileName: string; + productName: string; + vmId: string; + scalingGroupName: string; + assignedTime: number; + vmInSync: boolean; +} +export class LicenseUsage extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'checksum', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'fileName', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'algorithm', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'vmId', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'scalingGroupName', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'productName', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'assignedTime', + attrType: TypeRef.NumberType, + isKey: false + }, + { + name: 'vmInSync', + attrType: TypeRef.BooleanType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('LicenseUsage'); + LicenseUsage.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): LicenseUsageDbItem { + const item: LicenseUsageDbItem = { + checksum: this.typeConvert.valueToString(record.checksum), + fileName: this.typeConvert.valueToString(record.fileName), + algorithm: this.typeConvert.valueToString(record.algorithm), + productName: this.typeConvert.valueToString(record.productName), + vmId: this.typeConvert.valueToString(record.vmId), + scalingGroupName: this.typeConvert.valueToString(record.scalingGroupName), + assignedTime: this.typeConvert.valueToNumber(record.assignedTime), + vmInSync: this.typeConvert.valueToBoolean(record.vmInSync) + }; + return item; + } +} + +export interface CustomLogDbItem extends Record { + id: string; + timestamp: number; + logContent: string; +} +export class CustomLog extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'id', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'timestamp', + attrType: TypeRef.NumberType, + isKey: true, + keyType: TypeRef.SecondaryKey + }, + { + name: 'logContent', + attrType: TypeRef.StringType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('CustomLog'); + CustomLog.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): CustomLogDbItem { + const item: CustomLogDbItem = { + id: this.typeConvert.valueToString(record.id), + timestamp: this.typeConvert.valueToNumber(record.timestamp), + logContent: this.typeConvert.valueToString(record.logContent) + }; + return item; + } +} + +export interface VpnAttachmentDbItem extends Record { + vmId: string; + ip: string; + vpnConnectionId: string; +} +export class VpnAttachment extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'vmId', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'ip', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.SecondaryKey + }, + { + name: 'vpnConnectionId', + attrType: TypeRef.StringType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('VpnAttachment'); + VpnAttachment.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): VpnAttachmentDbItem { + const item: VpnAttachmentDbItem = { + vmId: this.typeConvert.valueToString(record.vmId), + ip: this.typeConvert.valueToString(record.ip), + vpnConnectionId: this.typeConvert.valueToString(record.vpnConnectionId) + }; + return item; + } +} + +export interface ApiRequestCacheDbItem extends Record { + id: string; + res: string; + cacheTime: number; + ttl: number; +} +export class ApiRequestCache extends Table { + static ownStaticAttributes: Attribute[] = [ + { + name: 'id', + attrType: TypeRef.StringType, + isKey: true, + keyType: TypeRef.PrimaryKey + }, + { + name: 'res', + attrType: TypeRef.StringType, + isKey: false + }, + { + name: 'cacheTime', + attrType: TypeRef.NumberType, + isKey: false + }, + { + name: 'ttl', + attrType: TypeRef.NumberType, + isKey: false + } + ]; + constructor(typeConvert, namePrefix = '', nameSuffix = '') { + super(typeConvert, namePrefix, nameSuffix); + // CAUTION: don't forget to set a correct name. + this.setName('ApiRequestCache'); + ApiRequestCache.ownStaticAttributes.forEach(def => { + this.addAttribute(def); + }); + } + convertRecord(record: Record): ApiRequestCacheDbItem { + const item: ApiRequestCacheDbItem = { + id: this.typeConvert.valueToString(record.id), + res: this.typeConvert.valueToString(record.res), + cacheTime: this.typeConvert.valueToNumber(record.cacheTime), + ttl: this.typeConvert.valueToNumber(record.ttl) + }; + return item; + } +} diff --git a/core/faz-integration-strategy.ts b/core/faz-integration-strategy.ts new file mode 100644 index 0000000..dda71c2 --- /dev/null +++ b/core/faz-integration-strategy.ts @@ -0,0 +1,18 @@ +import { VirtualMachine } from './virtual-machine'; + +export interface FazDeviceAuthorization { + vmId: string; + privateIp: string; + publicIp: string; +} + +export interface FazIntegrationStrategy { + createAuthorizationRequest(vm: VirtualMachine): Promise; + processAuthorizationRequest( + device: FazDeviceAuthorization, + host: string, + port: string, + username: string, + password: string + ): Promise; +} diff --git a/core/fortigate-autoscale/fortianalyzer-connector.ts b/core/fortigate-autoscale/fortianalyzer-connector.ts new file mode 100644 index 0000000..c01eeaf --- /dev/null +++ b/core/fortigate-autoscale/fortianalyzer-connector.ts @@ -0,0 +1,122 @@ +import axios, { AxiosRequestConfig } from 'axios'; +import https from 'https'; + +// NOTE: Due to FortiAnalyer API schema and limitation, cannot use strict code style so +// disable the following eslint rules: + +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/naming-convention */ +export interface ConnectingDevice { + adm_user: string; + adm_pass: string; + mgmt_mode: number; + mr: number; + platform_id: number; +} + +export class FortiAnalyzerConnector { + username: string; + password: string; + token: string; + constructor( + readonly host: string, + readonly port = 80 + ) {} + async requestAsync(data): Promise { + // original command: + // eslint-disable-next-line max-len + // let command = `curl -m 5 --silent -k -H "Accept: application/json" -X POST "https://${this.host}:${this.port}/jsonrpc" -d '${JSON.stringify(data)}'`; + + const options: AxiosRequestConfig = { + method: 'POST', + headers: { + Accept: 'application/json' + }, + url: `https://${this.host}:${this.port}/jsonrpc`, + data: data, // data in JSON form to sent as request body + timeout: 30000, + httpsAgent: new https.Agent({ + rejectUnauthorized: false + }) // resolve self signed certificate issue + }; + const response = await axios(options); + return response.data; + } + + async connect(username, password): Promise { + this.username = username; + this.password = password; + const connectData = { + method: 'exec', + params: [ + { + url: '/sys/login/user', + data: { + user: username, + passwd: password + } + } + ], + id: 1 + }; + const data = await this.requestAsync(connectData); + if (data && data.session) { + this.token = data.session; + return this.token; + } + throw new Error('session not found in data.'); + } + + async listDevices(): Promise { + const data = { + method: 'get', + id: '1', + params: [ + { + url: '/dvmdb/device' + } + ], + jsonrpc: '1.0', + session: this.token + }; + const result = await this.requestAsync(data); + if (result && result.result && result.result.length > 0 && result.result[0].data) { + return result.result[0].data; + } + throw new Error(`Invalid result for list devices. Result: ${JSON.stringify(result)}`); + } + + async authorizeDevice(deviceList): Promise { + // filter the unregistered device and authorize them + const devices = deviceList + .filter(device => { + // TODO: what criteria to distinguish a unregister device? + return device; + }) + .map(device => { + device.adm_usr = this.username; + device.adm_pass = this.password; + device.mgmt_mode = 2; // what it means? + device.mr = 6; // what it means? + device.platform_Id = -1; // what it means? + return device; + }, this); + const req = { + method: 'exec', + id: '1', + params: [ + { + url: '/dvm/cmd/add/dev-list', + data: { + flags: ['create_task', 'noblocking'], + adom: 'root', + 'add-dev-list': devices + } + } + ], + session: this.token + }; + + return await this.requestAsync(req); + } +} diff --git a/core/fortigate-autoscale/fortigate-autoscale-function-invocation.ts b/core/fortigate-autoscale/fortigate-autoscale-function-invocation.ts new file mode 100644 index 0000000..271a493 --- /dev/null +++ b/core/fortigate-autoscale/fortigate-autoscale-function-invocation.ts @@ -0,0 +1,129 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { + CloudFunctionInvocationPayload, + CloudFunctionInvocationTimeOutError, + CloudFunctionPeerInvocation, + CloudFunctionProxyAdapter, + extractFromInvocationPayload, + PlatformAdapter, + ReqType +} from '..'; +import { FortiGateAutoscaleSetting } from '.'; + +export const FortiGateAutoscaleFunctionInvocable = { + TriggerFazDeviceAuth: 'TriggerFazDeviceAuth' +}; + +export abstract class FortiGateAutoscaleFunctionInvocationHandler + implements CloudFunctionPeerInvocation +{ + abstract get proxy(): CloudFunctionProxyAdapter; + abstract get platform(): PlatformAdapter; + abstract executeInvocable( + payload: CloudFunctionInvocationPayload, + invocable: string + ): Promise; + + async handlePeerInvocation(functionEndpoint: string): Promise { + this.proxy.logAsInfo('calling handlePeerInvocation.'); + try { + // init the platform. this step is important + await this.platform.init(); + const requestType = await this.platform.getRequestType(); + const settings = await this.platform.getSettings(); + if (requestType !== ReqType.CloudFunctionPeerInvocation) { + this.proxy.logAsWarning('Not a CloudFunctionPeerInvocation type request. Skip it.'); + this.proxy.logAsInfo('called handlePeerInvocation.'); + return; + } + // get the invocation payload + const invocationPayload: CloudFunctionInvocationPayload = + (await this.proxy.getReqBody()) as CloudFunctionInvocationPayload; + if (!invocationPayload) { + throw new Error('Invalid request body.'); + } + + // authentication verification + const payload: unknown = extractFromInvocationPayload(invocationPayload); + const invocationSecretKey = this.platform.createAutoscaleFunctionInvocationKey( + payload, + functionEndpoint, + invocationPayload.invocable + ); + + // verify the invocation key + if ( + !invocationSecretKey || + invocationSecretKey !== invocationPayload.invocationSecretKey + ) { + throw new Error('Invalid invocation payload: invocationSecretKey not matched'); + } + const currentExecutionStartTime = Date.now(); // ms + const extendExecution = settings.get( + FortiGateAutoscaleSetting.AutoscaleFunctionExtendExecution + ); + const shouldExtendExecution: boolean = extendExecution && extendExecution.truthValue; + try { + await this.executeInvocable(invocationPayload, invocationPayload.invocable); + } catch (e) { + if ( + e instanceof CloudFunctionInvocationTimeOutError && + e.extendExecution && + shouldExtendExecution + ) { + const maxExecutionTimeItem = settings.get( + FortiGateAutoscaleSetting.AutoscaleFunctionMaxExecutionTime + ); + // the maximum execution time allowed for a cloud function + // NOTE: the time is set in second. + const maxExecutionTime = + maxExecutionTimeItem && Number(maxExecutionTimeItem.value); + + // time taken in preceeding relevent invocations and time taken in + // current invocation. + // NOTE: this time is also in second. + const executionTime: number = + (!isNaN(invocationPayload.executionTime) && + invocationPayload.executionTime) || + 0; + const totalExecutionTime = + Math.floor((Date.now() - currentExecutionStartTime) / 1000) + executionTime; + + // if max execution time not reached, create a new invocation to continue + if (totalExecutionTime < maxExecutionTime) { + await this.platform.invokeAutoscaleFunction( + payload, + functionEndpoint, + invocationPayload.invocable, + // carry the total execution time to the next call. + totalExecutionTime + ); + this.proxy.logAsInfo( + 'AutoscaleFunctionExtendExecution is enabled.' + + ` Current total execution time is: ${totalExecutionTime} seconds.` + + ` Max execution time allowed is: ${maxExecutionTime} seconds.` + + ' Now invoke a new Lambda function to continue.' + ); + } else { + this.proxy.logAsError( + 'AutoscaleFunctionExtendExecution is enabled.' + + ` Current total execution time is: ${totalExecutionTime} seconds.` + + ` Max execution time allowed is: ${maxExecutionTime} seconds.` + + ' No more time allowed to wait so it timed out and failed.' + ); + // extended execution reached max execution time allowed. + throw e; + } + } else { + // not a CloudFunctionInvocationTimeOutError or not allow to extend execution. + throw e; + } + } + this.proxy.logAsInfo('called handlePeerInvocation.'); + return; + } catch (error) { + // ASSERT: error is always an instance of Error + this.proxy.logForError('called handlePeerInvocation.', error); + } + } +} diff --git a/core/fortigate-autoscale/fortigate-autoscale-service-provider.ts b/core/fortigate-autoscale/fortigate-autoscale-service-provider.ts new file mode 100644 index 0000000..1d4bcf5 --- /dev/null +++ b/core/fortigate-autoscale/fortigate-autoscale-service-provider.ts @@ -0,0 +1,14 @@ +import { AutoscaleServiceType } from '..'; + +// all supported FortiGate Autoscale Service type +// eslint-disable-next-line @typescript-eslint/naming-convention +export const FortiGateAutoscaleServiceType = { + ...AutoscaleServiceType, + RegisterFortiAnalyzer: 'registerFortiAnalyzer', + TriggerFazDeviceAuth: 'triggerFazDeviceAuth' +}; +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum FortiGateAutoscaleServiceRequestSource { + FortiGateAutoscale = 'fortinet.autoscale' +} diff --git a/core/fortigate-autoscale/fortigate-autoscale-settings.ts b/core/fortigate-autoscale/fortigate-autoscale-settings.ts new file mode 100644 index 0000000..1f91477 --- /dev/null +++ b/core/fortigate-autoscale/fortigate-autoscale-settings.ts @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { + AutoscaleSetting, + AutoscaleSettingItemDictionary, + SettingItemDictionary, + SettingItemReference +} from '..'; + +export const FortiGateAutoscaleSetting: SettingItemReference = { + ...AutoscaleSetting, + EgressTrafficRouteTableList: 'egress-traffic-route-table', + EnableFazIntegration: 'enable-fortianalyzer-integration', + FortiAnalyzerHandlerName: 'faz-handler-name', + FortiAnalyzerIp: 'faz-ip', + FortiGateAdminPort: 'fortigate-admin-port', + FortiGateAutoscaleSubnetIdList: 'fortigate-autoscale-subnet-id-list', + FortiGateAutoscaleSubnetPairs: 'fortigate-autoscale-subnet-pairs', + FortiGateAutoscaleVirtualNetworkId: 'fortigate-autoscale-virtual-network-id', + FortiGateAutoscaleVirtualNetworkCidr: 'fortigate-autoscale-virtual-network-cidr', + FortiGateExternalElbDns: 'fortigate-external-elb-dns', + FortiGateInternalElbDns: 'fortigate-internal-elb-dns', + FortiGatePskSecret: 'fortigate-psk-secret', + FortiGateSyncInterface: 'fortigate-sync-interface', + FortiGateTrafficPort: 'fortigate-traffic-port', + FortiGateTrafficProtocol: 'fortigate-traffic-protocol' +}; + +export const FortiGateAutoscaleSettingItemDictionary: SettingItemDictionary = { + ...AutoscaleSettingItemDictionary, + [FortiGateAutoscaleSetting.EgressTrafficRouteTableList]: { + keyName: FortiGateAutoscaleSetting.EgressTrafficRouteTableList, + description: + 'The comma-separated list of route tables associated with any subnets,' + + ' which should be configured to contain a route 0.0.0.0/0 to the' + + ' primary FortiGate to handle egress traffic.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.EnableFazIntegration]: { + keyName: FortiGateAutoscaleSetting.EnableFazIntegration, + description: 'Enable FortiAnalyzer integration with the Autoscale FortiGate cluster.', + editable: false, + jsonEncoded: false, + booleanType: true + }, + [FortiGateAutoscaleSetting.FortiAnalyzerHandlerName]: { + keyName: FortiGateAutoscaleSetting.FortiAnalyzerHandlerName, + description: 'The FortiGate Autoscale - FortiAnalyzer handler function name.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiAnalyzerIp]: { + keyName: FortiGateAutoscaleSetting.FortiAnalyzerIp, + description: 'The FortiGate Autoscale - FortiAnalyzer ip address.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateAdminPort]: { + keyName: FortiGateAutoscaleSetting.FortiGateAdminPort, + description: 'The port number for administrative login to a FortiGate.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateAutoscaleVirtualNetworkId]: { + keyName: FortiGateAutoscaleSetting.FortiGateAutoscaleVirtualNetworkId, + description: 'ID of the Virtual Network that contains FortiGate Autoscale.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateAutoscaleVirtualNetworkCidr]: { + keyName: FortiGateAutoscaleSetting.FortiGateAutoscaleVirtualNetworkCidr, + description: 'CIDR of the Virtual Network that contains FortiGate Autoscale.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateExternalElbDns]: { + keyName: FortiGateAutoscaleSetting.FortiGateExternalElbDns, + description: 'The DNS name of the elastic load balancer for the FortiGate scaling groups.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateInternalElbDns]: { + keyName: FortiGateAutoscaleSetting.FortiGateInternalElbDns, + description: + 'The DNS name of the internal elastic load balancer ' + + 'used by the FortiGate Autoscale solution.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGatePskSecret]: { + keyName: FortiGateAutoscaleSetting.FortiGatePskSecret, + description: 'The PSK for FortiGate Autoscale synchronization.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateAutoscaleSubnetIdList]: { + keyName: FortiGateAutoscaleSetting.FortiGateAutoscaleSubnetIdList, + description: 'A comma-separated list of FortiGate Autoscale subnet IDs.', + editable: false, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateAutoscaleSubnetPairs]: { + keyName: FortiGateAutoscaleSetting.FortiGateAutoscaleSubnetPairs, + description: + 'A list of paired subnets for north-south traffic routing purposes.' + + ' Format: [{subnetId: [pairId1, pairId2, ...]}, ...]', + editable: false, + jsonEncoded: true, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateSyncInterface]: { + keyName: FortiGateAutoscaleSetting.FortiGateSyncInterface, + description: 'The interface the FortiGate uses for configuration synchronization.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateTrafficPort]: { + keyName: FortiGateAutoscaleSetting.FortiGateTrafficPort, + description: + 'The port number for the load balancer to route traffic through ' + + 'FortiGates to the protected services behind the load balancer.', + editable: true, + jsonEncoded: false, + booleanType: false + }, + [FortiGateAutoscaleSetting.FortiGateTrafficProtocol]: { + keyName: FortiGateAutoscaleSetting.FortiGateTrafficProtocol, + description: + 'The protocol for the traffic to be routed by the load balancer through ' + + 'FortiGates to the protected services behind the load balancer.', + editable: true, + jsonEncoded: false, + booleanType: false + } +}; diff --git a/core/fortigate-autoscale/fortigate-autoscale.ts b/core/fortigate-autoscale/fortigate-autoscale.ts new file mode 100644 index 0000000..b0fa28d --- /dev/null +++ b/core/fortigate-autoscale/fortigate-autoscale.ts @@ -0,0 +1,228 @@ +import { + Autoscale, + AutoscaleEnvironment, + AutoscaleHandler, + BootstrapConfigurationStrategy, + BootstrapContext, + CloudFunctionProxy, + HttpError, + LicensingModelContext, + PlatformAdapter, + ReqType, + VirtualMachine +} from '..'; +import * as HttpStatusCodes from 'http-status-codes'; +import { FortiGateAutoscaleSetting } from '.'; + +export const PRODUCT_NAME_FORTIGATE = 'fortigate'; + +/** + * FortiGate class with capabilities: + * cloud function handling, + * bootstrap configuration, + * secondary nic attachment + */ +export abstract class FortiGateAutoscale + extends Autoscale + implements AutoscaleHandler, BootstrapContext, LicensingModelContext +{ + bootstrapConfigStrategy: BootstrapConfigurationStrategy; + async handleAutoscaleRequest( + proxy: CloudFunctionProxy, + platform: PlatformAdapter, + env: AutoscaleEnvironment + ): Promise { + let responseBody: string; + try { + this.proxy = proxy; + this.platform = platform; + this.env = env; + this.proxy.logAsInfo('calling handleAutoscaleRequest.'); + this.proxy.logAsInfo('request integrity check.'); + + // init the platform. this step is important + await this.platform.init(); + const requestType = await this.platform.getRequestType(); + + this.proxy.logAsInfo('requestType', requestType); + + if (requestType === ReqType.LaunchingVm) { + responseBody = await this.handleLaunchingVm(); + } else if (requestType === ReqType.LaunchedVm) { + responseBody = await this.handleLaunchedVm(); + } else if (requestType === ReqType.VmNotLaunched) { + responseBody = await this.handleLaunchedVm(); + } else if (requestType === ReqType.BootstrapConfig) { + responseBody = await this.handleBootstrap(); + } else if (requestType === ReqType.HeartbeatSync) { + responseBody = await this.handleHeartbeatSync(); + } else if (requestType === ReqType.StatusMessage) { + // NOTE: FortiGate sends status message on some internal conditions, could ignore + // those status messages for now. + this.proxy.logAsInfo('FortiGate status message is received but ignored.'); + responseBody = ''; + } else if (requestType === ReqType.TerminatingVm) { + responseBody = await this.handleTerminatingVm(); + } else if (requestType === ReqType.TerminatedVm) { + responseBody = await this.handleTerminatedVm(); + } + this.proxy.logAsInfo('called handleAutoscaleRequest.'); + return proxy.formatResponse(HttpStatusCodes.OK, responseBody, {}); + } catch (error) { + // ASSERT: error is always an instance of Error + let httpError: HttpError; + this.proxy.logForError('called handleAutoscaleRequest.', error); + if (!(error instanceof HttpError)) { + httpError = new HttpError( + HttpStatusCodes.INTERNAL_SERVER_ERROR, + (error as Error).message + ); + } else { + httpError = error; + } + return proxy.formatResponse(httpError.status, '', {}); + } + } + + async handleLicenseRequest( + proxy: CloudFunctionProxy, + platform: PlatformAdapter, + env: AutoscaleEnvironment + ): Promise { + let responseBody: string; + try { + this.proxy = proxy; + this.platform = platform; + this.env = env; + this.proxy.logAsInfo('calling handleLicenseRequest.'); + this.proxy.logAsInfo('request integrity check.'); + + // init the platform. this step is important + await this.platform.init(); + const requestType = await this.platform.getRequestType(); + if (requestType === ReqType.ByolLicense) { + responseBody = await this.handleLicenseAssignment(PRODUCT_NAME_FORTIGATE); + } else { + throw new Error(`Unsupported request type: ${requestType}.`); + } + this.proxy.logAsInfo('called handleLicenseRequest.'); + return proxy.formatResponse(HttpStatusCodes.OK, responseBody, {}); + } catch (error) { + // ASSERT: error is always an instance of Error + let httpError: HttpError; + this.proxy.logForError('called handleLicenseRequest.', error); + if (!(error instanceof HttpError)) { + httpError = new HttpError( + HttpStatusCodes.INTERNAL_SERVER_ERROR, + (error as Error).message + ); + } else { + httpError = error; + } + return proxy.formatResponse(httpError.status, '', {}); + } + } + + setBootstrapConfigurationStrategy(strategy: BootstrapConfigurationStrategy): void { + this.bootstrapConfigStrategy = strategy; + } + async handleBootstrap(): Promise { + this.proxy.logAsInfo('calling handleBootstrap.'); + let error: Error; + // load target vm + if (!this.env.targetVm) { + this.env.targetVm = await this.platform.getTargetVm(); + } + // if target vm doesn't exist, unknown request + if (!this.env.targetVm) { + error = new Error(`Requested non-existing vm (id:${this.env.targetId}).`); + this.proxy.logForError('', error); + throw error; + } + // load target healthcheck record + this.env.targetHealthCheckRecord = + this.env.targetHealthCheckRecord || + (await this.platform.getHealthCheckRecord(this.env.targetVm.id)); + + // if there exists a health check record for this vm, this request may probably be + // a duplicate request. ignore it. + if (this.env.targetHealthCheckRecord) { + this.proxy.logAsWarning( + `Health check record for vm (id: ${this.env.targetVm.id}) ` + + 'already exists. It seems this bootstrap configuration request is duplicate.' + ); + } else { + // if primary is elected? + // get primary vm + if (!this.env.primaryVm) { + this.env.primaryVm = await this.platform.getPrimaryVm(); + } + // get primary record + this.env.primaryRecord = + this.env.primaryRecord || (await this.platform.getPrimaryRecord()); + + // NOTE: from FortiGate Autoscale 3.4.0, the primary election will not be handled + // on the bootstrap stage. + // Each VM will be initially launched as a secondary role until it becomes in-service + // and has a healthcheck record in the DB. + // As the result, the following processing will not be triggered here + // handlePrimaryElection() + // handleTaggingAutoscaleVm() + // handleEgressTrafficRoute() + } + + // get the bootstrap configuration + await this.bootstrapConfigStrategy.apply(); + const bootstrapConfig = this.bootstrapConfigStrategy.getConfiguration(); + // output configuration content in debug level so that we can turn it off on production + this.proxy.logAsDebug('configuration loaded.', `configuration: ${bootstrapConfig}`); + this.proxy.logAsInfo('called handleBootstrap.'); + return bootstrapConfig; + } + + /** + * @override + */ + async onVmFullyConfigured(): Promise { + this.proxy.logAsInfo('calling FortiGateAutoscale.onVmFullyConfigured.'); + // NOTE: if enable FAZ integration, register vm in FAZ + const settings = await this.platform.getSettings(); + if (settings.get(FortiGateAutoscaleSetting.EnableFazIntegration).truthValue) { + this.proxy.logAsInfo('FAZ integration is enabled.'); + await this.fazIntegrationStrategy.createAuthorizationRequest(this.env.targetVm); + } + // call the same method in the parent + super.onVmFullyConfigured(); + this.proxy.logAsInfo('called FortiGateAutoscale.onVmFullyConfigured.'); + } + + /** + * Register a FortiAnalyzer to the FortiGate Autoscale + * @param {string} vmId the vmId of the FortiAnalyzer + * @param {string} privateIp the privateIp of the FortiAnalyzer + */ + async registerFortiAnalyzer(vmId: string, privateIp: string): Promise { + this.proxy.logAsInfo('calling FortiGateAutoscale.registerFortiAnalyzer.'); + await this.platform.registerFortiAnalyzer(vmId, privateIp, true, privateIp); + this.proxy.logAsInfo('called FortiGateAutoscale.registerFortiAnalyzer.'); + } + + /** + * authorize new devices already connected to the FortiAnalyzer + * @param {string} vmId the vmId of the FortiGate to be authorized in in FortiAnalyzer. + * Currently with limitation, the FortiAnalyzer will authorize all new device connected to it. + */ + async triggerFazDeviceAuth(vmId?: string): Promise { + this.proxy.logAsInfo('calling FortiGateAutoscale.triggerFazDeviceAuth.'); + let targetVm: VirtualMachine; + if (vmId) { + // list and match the vm by id + const autoscaleVmList = await this.platform.listAutoscaleVm(); + [targetVm] = + (Array.isArray(autoscaleVmList) && autoscaleVmList.filter(vm => vm.id === vmId)) || + []; + } + await this.fazIntegrationStrategy.createAuthorizationRequest(targetVm); + this.proxy.logAsInfo('called FortiGateAutoscale.triggerFazDeviceAuth.'); + } +} diff --git a/core/fortigate-autoscale/fortigate-bootstrap-config-strategy.ts b/core/fortigate-autoscale/fortigate-bootstrap-config-strategy.ts new file mode 100644 index 0000000..fcbe8c3 --- /dev/null +++ b/core/fortigate-autoscale/fortigate-bootstrap-config-strategy.ts @@ -0,0 +1,415 @@ +import { + AutoscaleEnvironment, + Blob, + BootstrapConfigStrategyResult, + BootstrapConfigurationStrategy, + CloudFunctionProxyAdapter, + configSetResourceFinder, + PlatformAdapter, + Settings, + VirtualMachine +} from '..'; +import { FortiGateAutoscaleSetting } from '.'; + +export abstract class FortiGateBootstrapConfigStrategy implements BootstrapConfigurationStrategy { + static SUCCESS = 'SUCCESS'; + static FAILED = 'FAILED'; + private config: string; + protected settings: Settings; + protected alreadyLoaded = []; + abstract get platform(): PlatformAdapter; + abstract set platform(p: PlatformAdapter); + abstract get proxy(): CloudFunctionProxyAdapter; + abstract set proxy(x: CloudFunctionProxyAdapter); + abstract get env(): AutoscaleEnvironment; + abstract set env(e: AutoscaleEnvironment); + /** + * get the bootstrap configuration for a certain role determined by the apply() + * @returns {string} configuration + */ + getConfiguration(): string { + return this.config; + } + /** + * apply the strategy with parameter provided via prepare() + * @returns {Promise} BootstrapConfigStrategyResult + */ + async apply(): Promise { + this.settings = await this.platform.getSettings(); + const config = await this.loadConfig(); + // target is the primary? return config sets for active role + if (this.platform.vmEquals(this.env.targetVm, this.env.primaryVm)) { + this.config = await this.getPrimaryRoleConfig(config, this.env.targetVm); + this.proxy.logAsInfo('loaded configuration for primary role.'); + } + // else return config sets for passive device role + else { + this.config = await this.getSecondaryRoleConfig( + config, + this.env.targetVm, + this.env.primaryVm + ); + this.proxy.logAsInfo('loaded configuration for secondary role.'); + } + return BootstrapConfigStrategyResult.SUCCESS; + } + /** + * load the base configset content + * @returns {Promise} configset content + */ + protected async loadBase(): Promise { + try { + const config = await this.platform.loadConfigSet('baseconfig'); + this.alreadyLoaded.push('baseconfig'); + return config; + } catch (error) { + this.proxy.logForError( + "[baseconfig] configset doesn't exist in the assets storage. " + + 'Configset Not loaded.', + error + ); + throw new Error('baseconfig is required but not found.'); + } + } + /** + * load the configset content for setting up the secondary nic + * @returns {Promise} configset content + */ + protected async loadPort2(): Promise { + try { + const config = await this.platform.loadConfigSet('port2config'); + this.alreadyLoaded.push('port2config'); + return config; + } catch (error) { + this.proxy.logForError( + "[port2config] configset doesn't exist in the assets storage. " + + 'Configset Not loaded.', + error + ); + return ''; + } + } + /** + * load the configset content for setting up an internal elb for web service cluster + * @returns {Promise} configset content + */ + protected async loadInternalElbWeb(): Promise { + try { + const config = await this.platform.loadConfigSet('internalelbwebserv'); + this.alreadyLoaded.push('internalelbwebserv'); + return config; + } catch (error) { + this.proxy.logAsWarning( + "[internalelbwebserv] configset doesn't exist in the assets storage. " + + 'Configset Not loaded.' + ); + return ''; + } + } + /** + * load the configset content for setting up the FAZ logging + * @returns {Promise} configset content + */ + protected async loadFazIntegration(): Promise { + try { + const config = await this.platform.loadConfigSet('fazintegration'); + this.alreadyLoaded.push('fazintegration'); + return config; + } catch (error) { + this.proxy.logAsWarning( + "[fazintegration] configset doesn't exist in the assets storage. " + + 'Configset Not loaded.' + ); + return ''; + } + } + /** + * load a batch of configset content + * @param {string[]} configSetNameList configset name(s) separated by comma + * @param {boolean} customLocation configset is loaded from the custom asset location + * @param {boolean} throwError whether throw (just one) error or not + * @returns {Promise} configset content + */ + protected async loadBatch( + configSetNameList: string[], + customLocation, + throwError + ): Promise { + let customConfigSetContentArray = []; + let errorCount = 0; + const loaderArray = configSetNameList + .filter(n => !this.alreadyLoaded.includes(n)) + .map(name => + this.platform + .loadConfigSet(name, customLocation) + .then(content => { + this.alreadyLoaded.push(name); + return `${content}\n`; + }) + .catch(() => { + errorCount++; + this.proxy.logAsWarning( + `[${name}] configset doesn't exist in the assets storage. ` + + 'Configset Not loaded.' + ); + return ''; + }) + ); + if (loaderArray.length > 0) { + customConfigSetContentArray = await Promise.all(loaderArray); + } + if (throwError && errorCount > 0) { + throw new Error('Error occurred when loading some configsets. Please check the log.'); + } + return customConfigSetContentArray.join(''); + } + /** + * load the custom configset content from user defined custom configset location + * @returns {Promise} configset content + */ + protected async loadUserCustom(): Promise { + try { + const blobs: Blob[] = await this.platform.listConfigSet(null, true); + let fileCount = 0; + let loadedCount = 0; + let errorCount = 0; + const contents: string[] = await Promise.all( + blobs + .filter(blob => { + // exclude those filename starting with a dot + return !blob.fileName.startsWith('.'); + }) + .map(blob => { + fileCount++; + return this.platform + .loadConfigSet(blob.fileName, true) + .then(content => { + loadedCount++; + return content; + }) + .catch(error => { + errorCount++; + this.proxy.logAsWarning(error); + return ''; + }); + }) + ); + this.proxy.logAsInfo( + `Total files: ${fileCount}. ${loadedCount} loaded. ${errorCount} error.` + ); + return contents.join('\n'); + } catch (error) { + this.proxy.logForError('Error in listing files in container.', error); + return ''; + } + } + /** + * load all required configset(s) content and combine them into one string + * @returns {Promise} configset content + */ + protected async loadConfig(): Promise { + this.proxy.logAsInfo('calling FortiGateBootstrapConfigStrategy.loadConfig'); + let baseConfig = ''; + // check if second nic is enabled in the settings + // configset for the second nic + // must be loaded prior to the base config + if (this.settings.get(FortiGateAutoscaleSetting.EnableNic2).truthValue) { + baseConfig += `${await this.loadPort2()}\n`; + } + baseConfig += `${await this.loadBase()}\n`; // always load base config + + // check if internal elb integration is enabled in the settings + // then load the corresponding config set + if (this.settings.get(FortiGateAutoscaleSetting.EnableInternalElb).truthValue) { + baseConfig += `${await this.loadInternalElbWeb()}\n`; + } + // check if faz integration is enabled in the settings + // then load the corresponding config set + if (this.settings.get(FortiGateAutoscaleSetting.EnableFazIntegration).truthValue) { + baseConfig += `${await this.loadFazIntegration()}\n`; + } + // check if any other additional configsets is required + // the name list is string of a comma-separated name list, and can be splitted into + // a valid string array + // NOTE: additional required configsets should be processed second last + const additionalConfigSetNameList = + this.settings.get(FortiGateAutoscaleSetting.AdditionalConfigSetNameList).value || ''; + + // splits the string into an array of string without whitespaces + const additionalConfigSetArray = + (additionalConfigSetNameList && + additionalConfigSetNameList + .split(/(?<=,|^)[ ]*([a-z1-9]+)[ ]*(?=,|$)/) + .filter(a => !!a && !a.includes(','))) || + []; + + // load additional required configsets + if (additionalConfigSetArray.length > 0) { + baseConfig += await this.loadBatch(additionalConfigSetArray, false, false); + } + + // finally, try to include every configset stored in the user custom location + // NOTE: user custom configsets should be processed last + baseConfig += `${await this.loadUserCustom()}\n`; + this.proxy.logAsInfo('called FortiGateBootstrapConfigStrategy.loadConfig'); + return baseConfig; + } + /** + * process a given config string. Should not be overriidden in any derivied class. + * + * @protected + * @param {string} config the config sets in string type. + * @param {{}} sourceData a given object containing sorcce data to be used. + * @returns {string} a processed config sets in string type. + */ + protected processConfig(config: string, sourceData?: unknown): string { + if (sourceData) { + config = this.processConfigV2(config, sourceData); + } + + // NOTE: All those values to pass to FOS CLI must be normalized, then be enclosed with + // double quotes. + // Also, enclosure with double quotes is not part of normalizeFOSCmdInput()'s functionality. + // We enclose the placeholders with double quotes in configset files and programatically + // replace the placeholders with the normalized values. + const psksecret = this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.FortiGatePskSecret).value + ); + const syncInterface = + this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.FortiGateSyncInterface).value + ) || 'port1'; + const trafficPort = + this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.FortiGateTrafficPort).value + ) || '443'; + const trafficProtocol = + this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.FortiGateTrafficProtocol).value + ) || 'ALL'; + const adminPort = + this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.FortiGateAdminPort).value + ) || '8443'; + const intElbDns = this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.FortiGateInternalElbDns).value + ); + const hbInterval = this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.HeartbeatInterval).value + ); + const hbCallbackUrl = + this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.AutoscaleHandlerUrl).value + ) || ''; + const virtualNetworkCidr = + this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.FortiGateAutoscaleVirtualNetworkCidr) + .value + ) || ''; + const fazIp = + this.normalizeFOSCmdInput( + this.settings.get(FortiGateAutoscaleSetting.FortiAnalyzerIp).value + ) || ''; + return config + .replace(new RegExp('{SYNC_INTERFACE}', 'gm'), syncInterface) + .replace(new RegExp('{VIRTUAL_NETWORK_CIDR}', 'gm'), virtualNetworkCidr) + .replace(new RegExp('{EXTERNAL_INTERFACE}', 'gm'), 'port1') + .replace(new RegExp('{INTERNAL_INTERFACE}', 'gm'), 'port2') + .replace(new RegExp('{PSK_SECRET}', 'gm'), psksecret) + .replace(new RegExp('{TRAFFIC_PORT}', 'gm'), trafficPort) + .replace(new RegExp('{TRAFFIC_PROTOCOL}', 'gm'), trafficProtocol.toUpperCase()) + .replace(new RegExp('{ADMIN_PORT}', 'gm'), adminPort) + .replace(new RegExp('{INTERNAL_ELB_DNS}', 'gm'), intElbDns) + .replace(new RegExp('{CALLBACK_URL}', 'gm'), hbCallbackUrl) + .replace(new RegExp('{HEART_BEAT_INTERVAL}', 'gm'), hbInterval) + .replace(new RegExp('{FAZ_PRIVATE_IP}', 'gm'), fazIp); + } + /** + * Process config using a given source data + * + * @protected + * @param {string} config the config sets in string type + * @param {{}} sourceData a given object containing sorcce data to be used. + * @returns {string} a processed config sets in string type. + */ + protected processConfigV2(config: string, sourceData: unknown): string { + const resourceMap = {}; + Object.assign(resourceMap, sourceData); + let conf = config; + const nodePaths = config.match(/{(@[a-zA-Z_-]+(#\d+)*)+(\.[a-zA-Z_-]+(#\d+)*)+}/gm) || []; + try { + for (const nodePath of nodePaths) { + let replaceBy = null; + // check if it is in v2 format: {@SourceType.property[#num][.subProperty[#num]...]} + const [, resRoot] = /^{(@[a-zA-Z_-]+(#\d+)*)+(\.[a-zA-Z_-]+(#\d+)*)+}$/gm.exec( + nodePath + ); + if (resRoot && resourceMap[resRoot]) { + replaceBy = configSetResourceFinder(resourceMap, nodePath); + } + if (replaceBy) { + conf = conf.replace(new RegExp(nodePath, 'g'), replaceBy); + } + } + return this.processConfig(conf); // process config V1 + } catch (error) { + this.proxy.logForError('error in processing config, config not processed.', error); + // if error occurs, return the original config + return config; + } + } + + /** + * To normalize a string in order for a safe use as the input value for the FOS commands. + * Keep in mind that this function does not wrap the output string with an extra double quotes. + * + * @param {string} input the input string to normalize + * @returns {string} the normalized input string + */ + protected normalizeFOSCmdInput(input: string): string { + // NOTE: + // FOS CLI has a better input acceptance for string literal enclosing with double quotes + // than single quotes. Every symbol on the keyboard except for \ and " is accepted in a + // double-quoted string literal. Symbol \ and " need to add a leading \ (escape character). + return (input && input.replace(/[\\"]/g, m => `\\${m}`)) || null; + } + /** + * get bootstrap configuration for a FGT vm which's role will be primary + * @param {string} config configset content + * @param {VirtualMachine} targetVm the target vm which will consume this configuration + * @returns {Promise} configset content + */ + protected getPrimaryRoleConfig(config: string, targetVm: VirtualMachine): Promise { + return Promise.resolve(this.processConfigV2(config, { '@device': targetVm })); + } + /** + * get bootstrap configuration for a FGT vm which's role will be secondary + * @param {string} config configset content + * @param {VirtualMachine} targetVm the target vm which will consume this configuration + * @param {VirtualMachine} primaryVm (optional) the target vm which will be the primary (active) + * role in the HA cluster + * @returns {Promise} configset content + */ + protected getSecondaryRoleConfig( + config: string, + targetVm: VirtualMachine, + primaryVm?: VirtualMachine + ): Promise { + // TODO: remove when master-slave terminology is fully abandoned in all FOS version + const setMasterIpSection = + (primaryVm && `\n set master-ip ${primaryVm.primaryPrivateIpAddress}`) || ''; + const setPrimaryIpSection = + (primaryVm && `\n set primary-ip ${primaryVm.primaryPrivateIpAddress}`) || ''; + const conf = this.processConfig(config, { '@device': targetVm }); + // TODO: fix it when primary/secondary terminology has been used in FOS CLI command. + // NOTE: primary/secondary terminology is only available since FOS 7.0.1 + return Promise.resolve( + conf + .replace(new RegExp('set role master', 'gm'), `set role slave${setMasterIpSection}`) + .replace( + new RegExp('set role primary', 'gm'), + `set role secondary${setPrimaryIpSection}` + ) + ); + } +} diff --git a/core/fortigate-autoscale/fortigate-faz-integration-strategy.ts b/core/fortigate-autoscale/fortigate-faz-integration-strategy.ts new file mode 100644 index 0000000..c2cb055 --- /dev/null +++ b/core/fortigate-autoscale/fortigate-faz-integration-strategy.ts @@ -0,0 +1,129 @@ +import { + CloudFunctionProxyAdapter, + FazDeviceAuthorization, + FazIntegrationStrategy, + PlatformAdapter, + VirtualMachine +} from '..'; +import { + FortiAnalyzerConnector, + FortiGateAutoscaleFunctionInvocable, + FortiGateAutoscaleSetting +} from '.'; + +export class NoopFazIntegrationStrategy implements FazIntegrationStrategy { + platform: PlatformAdapter; + proxy: CloudFunctionProxyAdapter; + constructor(platform: PlatformAdapter, proxy: CloudFunctionProxyAdapter) { + this.platform = platform; + this.proxy = proxy; + } + createAuthorizationRequest(): Promise { + this.proxy.logAsInfo('calling NoopFazIntegrationStrategy.createAuthorizationRequest.'); + this.proxy.logAsInfo('no operation required.'); + this.proxy.logAsInfo('called NoopFazIntegrationStrategy.createAuthorizationRequest.'); + return Promise.resolve(); + } + + processAuthorizationRequest(): Promise { + this.proxy.logAsInfo('calling NoopFazIntegrationStrategy.processAuthorizationRequest.'); + this.proxy.logAsInfo('no operation required.'); + this.proxy.logAsInfo('called NoopFazIntegrationStrategy.processAuthorizationRequest.'); + return Promise.resolve(); + } +} + +export class FazReactiveAuthorizationStrategy implements FazIntegrationStrategy { + constructor( + readonly platform: PlatformAdapter, + readonly proxy: CloudFunctionProxyAdapter + ) {} + /** + * create an authorization request for a FortiGate device. This process is run asynchronously. + * this method is called as part of the high level Autoscale business logics. + * @param {VirtualMachine} vm the vm to process FAZ authorization in a different Lambda function + * instance. + */ + async createAuthorizationRequest(vm?: VirtualMachine): Promise { + this.proxy.logAsInfo('calling FazReactiveRegsitrationStrategy.createAuthorizationRequest.'); + // TODO: require implementation + const settings = await this.platform.getSettings(); + const settingFazIntegration = settings.get(FortiGateAutoscaleSetting.EnableFazIntegration); + const enableFazIntegration = settingFazIntegration && settingFazIntegration.truthValue; + // ignore if not faz integration enabled + if (!enableFazIntegration) { + this.proxy.logAsInfo('FAZ integration not enabled.'); + this.proxy.logAsInfo( + 'called FazReactiveRegsitrationStrategy.createAuthorizationRequest.' + ); + return; + } + const settingFazHandlerName = settings.get( + FortiGateAutoscaleSetting.FortiAnalyzerHandlerName + ); + const handlerName = settingFazHandlerName && settingFazHandlerName.value; + if (!handlerName) { + throw new Error('Faz handler name not defined in settings.'); + } + + const payload: FazDeviceAuthorization = { + vmId: (vm && vm.id) || undefined, + privateIp: (vm && vm.primaryPrivateIpAddress) || undefined, + publicIp: (vm && vm.primaryPublicIpAddress) || undefined + }; + + // invoke asynchronously to process this authorization request. + // the target Lambda function will run the same strategy. + await this.platform.invokeAutoscaleFunction( + payload, + handlerName, + FortiGateAutoscaleFunctionInvocable.TriggerFazDeviceAuth + ); + this.proxy.logAsInfo('called FazReactiveRegsitrationStrategy.createAuthorizationRequest.'); + return; + } + + /** + * Communicate with FortiAnalyzer to process the device authorizations. + * This process is run asynchronously. + * this method is called as part of the high level Autoscale business logics. + * @param {FazDeviceAuthorization} device the information about the device to be registered + * in the FAZ + * @param {string} host FAZ public IP + * @param {string} port FAZ port + * @param {string} username Autoscale admin username for authorizations + * @param {string} password Autoscale admin password for authorizations + */ + async processAuthorizationRequest( + device: FazDeviceAuthorization, + host: string, + port: string, + username: string, + password: string + ): Promise { + this.proxy.logAsInfo('calling FazReactiveRegsitrationStrategy.processAuthorizationRequest'); + const fazConnector: FortiAnalyzerConnector = new FortiAnalyzerConnector(host, Number(port)); + const connected = await fazConnector.connect(username, password); + if (!connected) { + // if cannot connect to the faz, don't show error, but return immediately. + this.proxy.logAsWarning('cannot connect to faz.'); + this.proxy.logAsInfo( + 'calling FazReactiveRegsitrationStrategy.processAuthorizationRequest' + ); + return; + } + const devices = await fazConnector.listDevices(); + // TODO: is it possible to identify each device by ip address? so it can detect whether + // the device is the one passed down for authorization in order to ensure that particular + // device is authorized. + await fazConnector.authorizeDevice( + devices.filter(dev => { + return dev && device && true; // in the future, may only filter the device. + }) + ); + this.proxy.logAsInfo( + `${(devices && devices.length) || '0'} devices in total have been authorized.` + ); + this.proxy.logAsInfo('calling FazReactiveRegsitrationStrategy.processAuthorizationRequest'); + } +} diff --git a/core/fortigate-autoscale/index.ts b/core/fortigate-autoscale/index.ts new file mode 100644 index 0000000..456cdda --- /dev/null +++ b/core/fortigate-autoscale/index.ts @@ -0,0 +1,14 @@ +// re-export necessary module from autoscale-core. +// export * from '@fortinet/autoscale-core'; +// export fortigate-autoscale module files. +export * from './fortianalyzer-connector'; +export * from './fortigate-autoscale'; +export * from './fortigate-autoscale-function-invocation'; +export * from './fortigate-autoscale-service-provider'; +export * from './fortigate-autoscale-settings'; +export * from './fortigate-bootstrap-config-strategy'; +export * from './fortigate-faz-integration-strategy'; +export * from '.'; +// NOTE: the index will not re-export the Autoscale-Core level db-definitions due to name conflicts. +// Instead, all Autoscale-Core level db-definitions are re-exported in ./db-definitions.ts +// Import them directly from the ./db-definitions.ts when needed. diff --git a/core/helper-function.ts b/core/helper-function.ts new file mode 100644 index 0000000..8284f6b --- /dev/null +++ b/core/helper-function.ts @@ -0,0 +1,193 @@ +import crypto from 'crypto'; +import { CloudFunctionProxyAdapter } from './cloud-function-proxy'; + +export function genChecksum(str: string, algorithm: string): string { + return crypto.createHash(algorithm).update(str, 'utf8').digest('hex'); +} + +export function sleep(ms: number): Promise { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} +/** + * Component of WaitFor(). An emitter function that returns a promise of type TResult. + * The returned value of type TResult will be passed to the WaitForConditionChecker. + * @template TResult a generic type for the returning value. + * @returns {Promise} the returning result in a promise + */ +export type WaitForPromiseEmitter = () => Promise; + +/** + * Component of WaitFor(). A custom checker function that takes a value of type TInput. + * The value of type TInput is passed from the returning value from WaitForPromiseEmitter. + * The custom checker will check the TInput and return a boolean indicating whether a passing + * condition is met or not. + * @param {TInput} input a generic type for the input value + * @param {number} callCount the number of time the emitter function been called. + * @returns {boolean} the boolean result of condition which is used to quit the waitFor() + */ +export type WaitForConditionChecker = ( + input: TInput, + callCount: number, + ...args +) => Promise; + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum WaitForMaxCount { + NoMaxCount = 0, + Count30 = 30 +} +/** + * A repeatedly running function that periodically takes a custom action, checks the result + * against a condition, and stops once the condition is met. + * + * @template TResult a generic type for the values being passed between emitter and checker. + * @param {WaitForPromiseEmitter} promiseEmitter the emitter that return a value of TResult + * @param {WaitForConditionChecker} conditionChecker the checker that + * takes a value of TResult as an input and performs a custom checking for a condition to quit the + * waitFor. + * @param {number} interval milliseconds interval between each calling emitter. + * @param {CloudFunctionProxyAdapter} [proxy] a proxy (if provided) that prints logs withing the + * waitFor process. + * @param {WaitForMaxCount} maxCount a new max count to override the default max count (30). + * use NoMaxCount if you prefer to control the stopping of this function by the condition checker and only. + * @returns {Promise} the returning result of the emitter + */ +export async function waitFor( + promiseEmitter: WaitForPromiseEmitter, + conditionChecker: WaitForConditionChecker, + interval: number, + proxy?: CloudFunctionProxyAdapter, + maxCount?: WaitForMaxCount +): Promise { + let count = 0; + maxCount = (maxCount === undefined && WaitForMaxCount.Count30) || maxCount; + if (interval <= 0) { + interval = 5000; // soft default to 5 seconds + } + try { + let result: TResult; + let complete = false; + do { + if (proxy) { + proxy.logAsInfo('Await condition check result.'); + } + result = await promiseEmitter(); + complete = await conditionChecker(result, ++count, proxy || undefined); + if (!complete) { + if (maxCount !== WaitForMaxCount.NoMaxCount && count >= maxCount) { + throw new Error( + `It reached the default maximum number (${maxCount}) of attempts.` + + 'Providing a new maxCount or bypass it with setting maxCount to 0.' + ); + } + if (proxy) { + proxy.logAsInfo( + `Condition check not passed, count: ${count}. Retry in ${interval} ms.` + ); + } + await sleep(interval); + } else { + if (proxy) { + proxy.logAsInfo('Condition check passed. End waiting and returns task result.'); + } + break; + } + } while (!complete); + return result; + } catch (error) { + if (proxy) { + proxy.logForError('WaitFor() is interrupted.', error); + } + throw error; + } +} + +/** + * Compare anything + * + * @param {*} anyA one of the two value to be compare with each other + * @param {*} anyB one of the two value to be compare with each other + * @returns {boolean} true if their result of JSON.stringify are qual. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function compareAny(anyA: any, anyB: any): boolean { + return JSON.stringify(anyA) === JSON.stringify(anyB); +} + +export function compareObject( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + objectA: { [key: string]: any }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + objectB: { [key: string]: any } +): boolean { + return ( + typeof objectA === 'object' && typeof objectB === 'object' && compareAny(objectA, objectB) + ); +} + +/** + * A compareAny(a, b) equivalent. Allows for taking one object as parameter first, then + * take more more objects as parameter in the returned functions. + * @param {any} objectA an object to compare + * @returns {} an object of functions that compare objectA with others provided as parameters of + * each function. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const compare = (anyA: any): { isEqualTo: (anyB: any) => boolean } => { + return { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + isEqualTo: (anyB: any): boolean => { + return compareAny(anyA, anyB); + } + }; +}; + +export function isIpV4(input: string, includeHostAddress = true): boolean { + const host = (includeHostAddress && '{1}') || '?'; + const exp = `^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]){1}(/([0-9]|[1-2][0-9]|3[0-2]))${host}$`; + const matches = input.match(new RegExp(exp, 'i')); + return matches && matches[0] === input; +} +/** + * This function extends the JSON.stringify() to accept Map type value by a possible transformation + * @param {string} k key + * @param {unknown} v value + * @returns {unknown} a value that is possibly transformed or itself, otherwise. + */ +export function jsonStringifyReplacer( + k: string, + v: unknown +): { type: string; value: unknown[] } | unknown { + if (v instanceof Map) { + return { + type: 'Map', + value: [...v] + }; + } else { + return v; + } +} +/** + * This function extends the JSON.parse() to deserialzie a string into a value of Map type, where + * the string is transformed with helper function jsonStringifyReplacer() + * @param {string} k key + * @param {unkown} v value + * @returns {unknown} a value that is possibly detransformed or itself, otherwise. + */ +export function jsonParseReviver( + k: string, + v: { type: string; value: unknown[] } | unknown +): unknown { + if (typeof v === 'object' && v !== null) { + const val: { type: string; value: unknown[] } = { type: undefined, value: undefined }; + Object.assign(val, v); + if (val.type === 'Map' && val.value instanceof Array) { + // val.value is transformed by jsonStringifyReplacer as [any, any][] + return new Map(val.value as [unknown, unknown][] as [unknown, unknown][]); + } + } + return v; +} diff --git a/core/index.ts b/core/index.ts new file mode 100644 index 0000000..cf4881e --- /dev/null +++ b/core/index.ts @@ -0,0 +1,20 @@ +/** + * export core modules + */ +export * from './autoscale-core'; +export * from './autoscale-environment'; +export * from './autoscale-service-provider'; +export * from './autoscale-setting'; +export * from './blob'; +export * from './cloud-function-peer-invocation'; +export * from './cloud-function-proxy'; +export * from './context-strategy'; +export * from './helper-function'; +export * from './jsonable'; +export * from './platform-adaptee'; +export * from './platform-adapter'; +export * from './primary-election'; +export * from './virtual-machine'; +export * from './faz-integration-strategy'; +// NOTE: the index will not export the db-definitions due to name conflicts. +// Import them directly from the ./db-definitions.ts when needed. diff --git a/core/jsonable.ts b/core/jsonable.ts new file mode 100644 index 0000000..da73055 --- /dev/null +++ b/core/jsonable.ts @@ -0,0 +1,3 @@ +export type JSONable = { + [key in string | number]?: string | number | boolean | JSONable | JSONable[]; +}; diff --git a/core/platform-adaptee.ts b/core/platform-adaptee.ts new file mode 100644 index 0000000..77c4dd8 --- /dev/null +++ b/core/platform-adaptee.ts @@ -0,0 +1,9 @@ +import { Settings } from './autoscale-setting'; +export interface PlatformAdaptee { + loadSettings(): Promise; + // getReqType(proxy: CloudFunctionProxyAdapter): Promise; + // getReqMethod(proxy: CloudFunctionProxyAdapter): ReqMethod; + // checkReqIntegrity(proxy: CloudFunctionProxyAdapter): void; + // getReqBody(proxy: CloudFunctionProxyAdapter): ReqBody; + // getReqHeaders(proxy: CloudFunctionProxyAdapter): ReqHeaders; +} diff --git a/core/platform-adapter.ts b/core/platform-adapter.ts new file mode 100644 index 0000000..3fea1ae --- /dev/null +++ b/core/platform-adapter.ts @@ -0,0 +1,277 @@ +import { Settings } from './autoscale-setting'; +import { Blob } from './blob'; +import { ReqType } from './cloud-function-proxy'; +import { NicAttachmentRecord } from './context-strategy/nic-attachment-context'; +import { KeyValue } from './db-definitions'; +import { JSONable } from './jsonable'; +import { PlatformAdaptee } from './platform-adaptee'; +import { HealthCheckRecord, PrimaryRecord } from './primary-election'; +import { NetworkInterface, VirtualMachine } from './virtual-machine'; + +export interface DeviceSyncInfo { + /** + * Representing property: instance, the id of the vm. + * Device always provides this property. + * Can check null for errors. + */ + instance: string; + /** + * Representing property: interval, in second. + * Device always provides this property. + * Can check NaN for errors. + */ + interval: number; + /** + * Representing property: status. Not available in heartbeat type of info. + * Will be undefined if absent. + */ + status?: string | null; + /** + * Representing property: sequence. + * If device provided this property, it will not be null. + * If device not provided this property, it will be NaN. + */ + sequence: number; + /** + * Representing property: time, the send time of the heartbeat, ISO 8601 format, device's time. + * If device provided this property, it will not be null. + * If device not provided this property, it will be null. + */ + time: string; + /** + * Representing property: sync_time, the last time on successful ha sync, ISO 8601 format, device's time. + * If device provided this property, it can be null. + * If device not provided this property, it will be null. + */ + syncTime: string | null; + /** + * Representing property: sync_fail_time, the last time on ha sync failure, ISO 8601 format, device's time. + * If device provided this property, it can be null. + * If device not provided this property, it will be null. + */ + syncFailTime: string | null; + /** + * Representing property: sync_status, true or false on secondary device if in-sync with primary or not; + * It will be null on primary device. + * If device provided this property, it can be null. + * If device not provided this property, it will be null. + */ + syncStatus: boolean | null; + /** + * Representing property: is_primary, true for primary devices, false for secondary, in the device's perspective. + * If device provided this property, it can be null. + * If device not provided this property, it will be null. + */ + isPrimary: boolean | null; + /** + * Representing property: checksum, the HA checksum value of the device. + * If device provided this property, it will not be null. + * If device not provided this property, it will be null. + */ + checksum: string; +} +export interface ResourceFilter { + key: string; + value: string; + isTag?: boolean; +} + +export interface LicenseFile { + fileName: string; + checksum: string; + algorithm: string; + content: string; +} + +export interface LicenseStockRecord { + fileName: string; + checksum: string; + algorithm: string; + productName: string; +} + +export interface LicenseUsageRecord { + fileName: string; + checksum: string; + algorithm: string; + productName: string; + vmId: string; + scalingGroupName: string; + assignedTime: number; + vmInSync: boolean; +} + +export interface TgwVpnAttachmentRecord { + vmId: string; + ip: string; + vpnConnectionId: string; + transitGatewayId: string; + transitGatewayAttachmentId: string; + customerGatewayId: string; + vpnConnection: JSONable; +} + +export interface PlatformAdapter { + adaptee: PlatformAdaptee; + readonly createTime: number; + // checkRequestIntegrity(): void; + init(): Promise; + saveSettingItem( + key: string, + value: string, + description?: string, + jsonEncoded?: boolean, + editable?: boolean + ): Promise; + getRequestType(): Promise; + /** + * heartbeat interval in the request in ms. + * @returns number interval in ms + */ + getReqHeartbeatInterval(): Promise; + /** + * the device info sent from a vm + * @returns Promise of DeviceSyncInfo + */ + getReqDeviceSyncInfo(): Promise; + getReqVmId(): Promise; + getReqAsString(): Promise; + getSettings(): Promise; + /** + * validate settings by checking the integrity of each required setting item. Ensure that they + * have been added properly. + * @returns Promise + */ + validateSettings(): Promise; + getTargetVm(): Promise; + getPrimaryVm(): Promise; + getVmById(vmId: string, scalingGroupName?: string): Promise; + listAutoscaleVm( + identifyScalingGroup?: boolean, + listNic?: boolean + ): Promise; + getHealthCheckRecord(vmId: string): Promise; + listHealthCheckRecord(): Promise; + getPrimaryRecord(filters?: KeyValue[]): Promise; + vmEquals(vmA?: VirtualMachine, vmB?: VirtualMachine): boolean; + createHealthCheckRecord(rec: HealthCheckRecord): Promise; + updateHealthCheckRecord(rec: HealthCheckRecord): Promise; + deleteHealthCheckRecord(rec: HealthCheckRecord): Promise; + /** + * create the primary record in the db system. + * @param rec the new primary record + * @param oldRec the old primary record, if provided, will try to replace this record by + * matching the key properties. + */ + createPrimaryRecord(rec: PrimaryRecord, oldRec: PrimaryRecord | null): Promise; + /** + * update the primary record using the given rec. update only when the record key match + * the record in the db. + * @param rec primary record to be updated. + */ + updatePrimaryRecord(rec: PrimaryRecord): Promise; + /** + * delete the primary record using the given rec. delete only when the record property values + * strictly match the record in the db. + * @param rec primary record to be delete. + * @param fullMatch need a full match of each property to delete + */ + deletePrimaryRecord(rec: PrimaryRecord, fullMatch?: boolean): Promise; + /** + * Load a configset file from blob storage + * The blob container will use the AssetStorageContainer or CustomAssetContainer, + * and the location prefix will use AssetStorageDirectory or CustomAssetDirectory. + * The full file path will be: \/\/configset/\ + * @param {string} name the configset name + * @param {boolean} custom (optional) whether load it from a custom location or not + * @returns {Promise} the configset content as a string + */ + loadConfigSet(name: string, custom?: boolean): Promise; + /** + * List all configset files in a specified blob container location + * The blob container will use the AssetStorageContainer or CustomAssetContainer, + * and the location prefix will use AssetStorageDirectory or CustomAssetDirectory. + * There will be an optional subDirectory provided as parameter. + * The full file path will be: \/\[/\]/configset + * @param {string} subDirectory additional subdirectory + * @param {boolean} custom (optional) whether load it from a custom location or not + * @returns {Promise} the configset content as a string + */ + listConfigSet(subDirectory?: string, custom?: boolean): Promise; + deleteVmFromScalingGroup(vmId: string): Promise; + listLicenseFiles( + storageContainerName: string, + licenseDirectoryName: string + ): Promise; + listLicenseStock(productName: string): Promise; + listLicenseUsage(productName: string): Promise; + updateLicenseStock(records: LicenseStockRecord[]): Promise; + updateLicenseUsage( + records: { item: LicenseUsageRecord; reference: LicenseUsageRecord }[] + ): Promise; + loadLicenseFileContent(storageContainerName: string, filePath: string): Promise; + + // NOTE: are the following methods relevant to this interface or should move to + // a more specific interface? + listNicAttachmentRecord(): Promise; + updateNicAttachmentRecord(vmId: string, nicId: string, status: string): Promise; + deleteNicAttachmentRecord(vmId: string, nicId: string): Promise; + /** + * create a network interface + * @param {string} subnetId? (optional) id of subnet where the network interface is located + * @param {string} description? (optional) description + * @param {string[]} securityGroups? (optional) security groups + * @param {string} privateIpAddress? (optional) private ip address + * @returns Promise + */ + createNetworkInterface( + subnetId?: string, + description?: string, + securityGroups?: string[], + privateIpAddress?: string + ): Promise; + + deleteNetworkInterface(nicId: string): Promise; + attachNetworkInterface(vmId: string, nicId: string, index?: number): Promise; + detachNetworkInterface(vmId: string, nicId: string): Promise; + listNetworkInterfaces(tags: ResourceFilter[], status?: string): Promise; + tagNetworkInterface(nicId: string, tags: ResourceFilter[]): Promise; + registerFortiAnalyzer( + vmId: string, + privateIp: string, + primary: boolean, + vip: string + ): Promise; + + /** + * invoke the Autoscale handler function + * @param {unknown} payload the payload to invoke the function + * @param {string} functionEndpoint the function name or fqdn of the function which is + * depending on implementation. + * @param {string} invocable the pre-defined type name of features that is invocable in this + * way. + * @param {number} executionTime? the accumulative execution time of one complete invocation. + * due to cloud platform limitation, one complete invocation may have to split into two or more + * function calls in order to get the final result. + * @returns Promise + */ + invokeAutoscaleFunction( + payload: unknown, + functionEndpoint: string, + invocable: string, + executionTime?: number + ): Promise; + + /** + * create an invocation key for authentication between Autoscale Function caller and receiver. + * @param {unknown} payload + * @param {string} functionEndpoint + * @param {string} invocable + * @returns string + */ + createAutoscaleFunctionInvocationKey( + payload: unknown, + functionEndpoint: string, + invocable: string + ): string; +} diff --git a/core/primary-election.ts b/core/primary-election.ts new file mode 100644 index 0000000..5cdef8a --- /dev/null +++ b/core/primary-election.ts @@ -0,0 +1,79 @@ +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum HealthCheckSyncState { + InSync = 'in-sync', + OutOfSync = 'out-of-sync' +} +export interface HealthCheckRecord { + vmId: string; + scalingGroupName: string; + ip: string; + primaryIp: string; + /** time in ms */ + heartbeatInterval: number; + heartbeatLossCount: number; + /** time in ms */ + nextHeartbeatTime: number; + syncState: HealthCheckSyncState; + syncRecoveryCount: number; + seq: number; + /** (calculated value) */ + healthy: boolean; + upToDate: boolean; + sendTime: string; + deviceSyncTime: string; + deviceSyncFailTime: string; + deviceSyncStatus: boolean | null; + deviceIsPrimary: boolean | null; + deviceChecksum: string; + /** (calculated value) number of current heartbeat interval when record not being updated */ + irresponsivePeriod: number; + /** (calculated value) number of heartbeat loss before reaching the max allowed number */ + remainingLossAllowed: number; +} + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum HealthCheckResult { + OnTime = 'on-time', + Late = 'late', + TooLate = 'too-late', + Dropped = 'dropped', + Recovering = 'recovering', + Recovered = 'recovered' +} + +export interface HealthCheckResultDetail { + sequence: number; + result: HealthCheckResult; + expectedArriveTime: number; + actualArriveTime: number; + heartbeatInterval: number; + oldHeartbeatInerval: number; + delayAllowance: number; + calculatedDelay: number; + actualDelay: number; + heartbeatLossCount: number; + maxHeartbeatLossCount: number; + syncRecoveryCount: number; + maxSyncRecoveryCount: number; +} + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum PrimaryRecordVoteState { + Pending = 'pending', + Done = 'done', + Timeout = 'timeout' +} + +export interface PrimaryRecord { + id: string; + vmId: string; + ip: string; + scalingGroupName: string; + virtualNetworkId: string; + subnetId: string; + voteEndTime: number; + voteState: PrimaryRecordVoteState; +} diff --git a/core/virtual-machine.ts b/core/virtual-machine.ts new file mode 100644 index 0000000..0eeb786 --- /dev/null +++ b/core/virtual-machine.ts @@ -0,0 +1,60 @@ +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export enum VirtualMachineState { + // NOTE: transitioning state. in provisioning, not attached to the host, not receiving traffic. + Creating = 'Creating', + // NOTE: standby but stopped instead of running. + Deallocated = 'Deallocated', + // NOTE: a general state for all transitioning states. + Pending = 'Pending', + // NOTE: running, attached to the host, receiving traffic, not deleted. + Running = 'Running', + // NOTE: running, detached from the host, not receiving traffic, not deleted. + Standby = 'Standby', + // NOTE: transitioning state. starting or restarting, may or may not attach to the host, not receiving traffic, not deleted. + Starting = 'Starting', + // NOTE: powered off, may or may not attach to the host, not receiving traffic, not deleted. + Stopped = 'Stopped', + // NOTE: transitioning state. stopping, may or may not attach to the host, not receiving traffic, not deleted. + Stopping = 'Stopping', + // NOTE: powered off, detached from the host, not receiving traffic, deleted. + Terminated = 'Terminated', + // NOTE: transitioning state. in deleting, not attached to the host, not receiving traffic. + Terminating = 'Terminating', + // NOTE: transitioning state. standby but in the process of updating the model. + Updating = 'Updating', + // NOTE: a general state for any reason not set the state yet so it remains unkown. + Unknown = 'Unknown' +} + +export interface NetworkInterface { + id: string; + privateIpAddress: string; + index: number; + subnetId?: string; + virtualNetworkId?: string; + attachmentId?: string; + description?: string; +} + +export interface SecurityGroup { + id: string; + name?: string; +} + +// the no-shadow rule errored in the next line may be just a false alarm +// eslint-disable-next-line no-shadow +export interface VirtualMachine { + id: string; + scalingGroupName: string; + productName?: string; + primaryPrivateIpAddress: string; + primaryPublicIpAddress?: string; + virtualNetworkId: string; + subnetId: string; + securityGroups?: SecurityGroup[]; + networkInterfaces?: NetworkInterface[]; + networkInterfaceIds?: string[]; + sourceData?: { [key: string]: unknown }; + state: VirtualMachineState; +} diff --git a/host.json b/host.json index f12c99c..4de5bf2 100644 --- a/host.json +++ b/host.json @@ -10,7 +10,7 @@ }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[2.*, 3.0.0)" + "version": "[4.0.0, 5.0.0)" }, "watchDirectories": ["dist"] } diff --git a/package-lock.json b/package-lock.json index 568aa30..4ae5a2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,52 +1,60 @@ { "name": "fortigate-autoscale-azure", - "version": "3.5.2", + "version": "3.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "fortigate-autoscale-azure", - "version": "3.5.2", + "version": "3.6.0", "license": "MIT", "dependencies": { + "@azure/arm-compute": "^15.0.0", + "@azure/arm-network": "^23.2.0", + "@azure/cosmos": "^3.9.5", "@azure/functions": "^1.2.3", - "@fortinet/fortigate-autoscale": "https://github.com/fortinet/autoscale-core/releases/download/3.5.4/fortinet-fortigate-autoscale-3.5.4.tgz", + "@azure/identity": "^4.0.0", + "@azure/keyvault-secrets": "^4.7.0", + "@azure/ms-rest-js": "^2.2.1", + "@azure/ms-rest-nodeauth": "^3.0.9", + "@azure/storage-blob": "^12.17.0", "@types/node": "^13.13.50", - "http-status-codes": "^1.4.0" + "axios": "^1.6.5", + "http-status-codes": "^2.3.0" }, "devDependencies": { - "@types/adm-zip": "^0.4.34", - "@types/comment-json": "^1.1.1", - "@types/mocha": "^8.2.2", - "@types/semver": "^7.3.6", - "@types/sinon": "^7.5.2", - "@typescript-eslint/eslint-plugin": "^5.10.2", - "@typescript-eslint/parser": "^5.10.2", - "adm-zip": "^0.5.5", - "azure-functions-core-tools": "^4.0.5198", + "@types/adm-zip": "^0.5.5", + "@types/comment-json": "^2.4.2", + "@types/mocha": "^10.0.6", + "@types/semver": "^7.5.6", + "@types/sinon": "^17.0.3", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "adm-zip": "^0.5.10", + "azure-functions-core-tools": "^4.0.5455", "chalk": "^4.1.2", - "commander": "^5.1.0", - "comment-json": "^3.0.2", - "copy-webpack-plugin": "^7.0.0", - "eslint": "^8.8.0", - "eslint-config-prettier": "^6.10.1", - "eslint-plugin-mocha": "^6.3.0", - "eslint-plugin-prettier": "^3.4.0", + "commander": "^12.0.0", + "comment-json": "^4.2.3", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-mocha": "^10.2.0", + "eslint-plugin-prettier": "^5.1.3", "ftnt-devops-ci": "https://github.com/fortinet/ftnt-devops-ci/releases/download/1.1.8/ftnt-devops-ci-1.1.8.tgz", - "husky": "^4.3.0", + "husky": "^9.0.10", "json-loader": "^0.5.7", - "mocha": "^9.2.0", + "mocha": "^10.2.0", "npm-run-all": "^4.1.5", - "prettier": "^1.19.1", - "semver": "^7.3.5", + "prettier": "^3.2.5", + "semver": "^7.6.0", "shx": "^0.3.4", - "sinon": "^9.0.2", - "ts-loader": "^7.0.5", - "ts-node": "^8.8.2", - "tsconfig-paths-webpack-plugin": "^3.3.0", - "typescript": "^3.9.2", - "webpack": "^5.68.0", - "webpack-cli": "^4.9.2" + "sinon": "^17.0.1", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "typescript": "^5.3.3", + "webpack": "^5.90.1", + "webpack-cli": "^5.1.4" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -75,59 +83,49 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/@azure/arm-compute": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@azure/arm-compute/-/arm-compute-21.0.0.tgz", - "integrity": "sha512-9MrL6UW2oKtjLjXMdoAC5Z9vEDZ/liFz9EzR6c+UjbN+wqcaBsf4KERHV6+MWhXrNp3OnpYkeQyYxc0LAv+/nw==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@azure/arm-compute/-/arm-compute-15.0.0.tgz", + "integrity": "sha512-ElV2MuYZ+B2+ygMx2iiM/u3C7WDRruZjkXzfk5p2O+UbWxjG6j/686OH3T+VSDqmzg+47AnIOTLu2v0u0H8FOw==", + "deprecated": "Please note, versions of this package with version numbers 16.5.0 and below have been deprecated as of 31-March-2022. We strongly encourage you to upgrade to version 17.0.0 or above to continue receiving updates. Refer to our deprecation policy: https://azure.github.io/azure-sdk/policies_support.html for more details.", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-client": "^1.7.0", - "@azure/core-lro": "^2.5.0", - "@azure/core-paging": "^1.2.0", - "@azure/core-rest-pipeline": "^1.8.0", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">=14.0.0" + "@azure/ms-rest-azure-js": "^2.0.1", + "@azure/ms-rest-js": "^2.0.4", + "tslib": "^1.10.0" } }, - "node_modules/@azure/arm-compute/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" - }, "node_modules/@azure/arm-network": { - "version": "31.0.0", - "resolved": "https://registry.npmjs.org/@azure/arm-network/-/arm-network-31.0.0.tgz", - "integrity": "sha512-tXnTsML8d2vecEvq99umRef+FBXrtxBjzIxaaMnpnIHOWQO2A6pqynJtlWdIjDjXF1L5V5B1BxJF/7XO6bpACQ==", + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/@azure/arm-network/-/arm-network-23.3.0.tgz", + "integrity": "sha512-6hGLMb5I0fs01e7hjTBvfp9pbuN9zI82HokDtmPAhk6awG+Lupex2CDRUXRm08ZKLoH1Kaqly+/uH0+OTNkWpA==", + "deprecated": "Please note, versions of this package with version numbers 25.4.0 and below have been deprecated as of 31-March-2022. We strongly encourage you to upgrade to version 26.0.0 or above to continue receiving updates. Refer to our deprecation policy: https://azure.github.io/azure-sdk/policies_support.html for more details.", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-client": "^1.7.0", - "@azure/core-lro": "^2.5.3", - "@azure/core-paging": "^1.2.0", - "@azure/core-rest-pipeline": "^1.8.0", + "@azure/ms-rest-azure-js": "^2.0.1", + "@azure/ms-rest-js": "^2.0.4", + "tslib": "^1.10.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.6.0.tgz", + "integrity": "sha512-3X9wzaaGgRaBCwhLQZDtFp5uLIXCPrGbwJNWPPugvL4xbIGgScv77YzzxToKGLAKvG9amDoofMoP+9hsH1vs1w==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.1.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@azure/arm-network/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" - }, - "node_modules/@azure/core-auth": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", - "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz", + "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==", "dependencies": { - "@azure/abort-controller": "^1.0.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, "node_modules/@azure/core-auth/node_modules/tslib": { @@ -225,17 +223,28 @@ "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, "node_modules/@azure/core-lro": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.3.tgz", - "integrity": "sha512-ubkOf2YCnVtq7KqEJQqAI8dDD5rH1M6OP5kW0KO/JQyTaxLA0N0pjFWvvaysCj9eHMNBcuuoZXhhl0ypjod2DA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.6.0.tgz", + "integrity": "sha512-PyRNcaIOfMgoUC01/24NoG+k8O81VrKxYARnDlo+Q2xji0/0/j2nIt8BwQh294pb1c5QnXTDPbNR4KzoDKXEoQ==", "dependencies": { - "@azure/abort-controller": "^1.0.0", + "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", "@azure/logger": "^1.0.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-lro/node_modules/@azure/abort-controller": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz", + "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@azure/core-lro/node_modules/tslib": { @@ -260,35 +269,32 @@ "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, "node_modules/@azure/core-rest-pipeline": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.11.0.tgz", - "integrity": "sha512-nB4KXl6qAyJmBVLWA7SakT4tzpYZTCk4pvRBeI+Ye0WYSOrlTqlMhc4MSS/8atD3ufeYWdkN380LLoXlUUzThw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.14.0.tgz", + "integrity": "sha512-Tp4M6NsjCmn9L5p7HsW98eSOS7A0ibl3e5ntZglozT0XuD/0y6i36iW829ZbBq0qihlGgfaeFpkLjZ418KDm1Q==", "dependencies": { - "@azure/abort-controller": "^1.0.0", + "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.4.0", "@azure/core-tracing": "^1.0.1", "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", - "form-data": "^4.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@azure/core-rest-pipeline/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz", + "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "tslib": "^2.2.0" }, "engines": { - "node": ">= 6" + "node": ">=18.0.0" } }, "node_modules/@azure/core-rest-pipeline/node_modules/tslib": { @@ -363,35 +369,33 @@ "integrity": "sha512-dZITbYPNg6ay6ngcCOjRUh1wDhlFITS0zIkqplyH5KfKEAVPooaoaye5mUFnR+WP9WdGRjlNXyl/y2tgWKHcRg==" }, "node_modules/@azure/identity": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-3.2.3.tgz", - "integrity": "sha512-knIbl7p2i8r3qPsLW2W84esmDPr36RqieLC72OeuqYk4+0TRNthUhWTs655P9S9Pm3TVVxcFsS3Le9SXIWBIFA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.0.1.tgz", + "integrity": "sha512-yRdgF03SFLqUMZZ1gKWt0cs0fvrDIkq2bJ6Oidqcoo5uM85YMBnXWMzYKK30XqIT76lkFyAaoAAy5knXhrG4Lw==", "dependencies": { "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", + "@azure/core-auth": "^1.5.0", "@azure/core-client": "^1.4.0", "@azure/core-rest-pipeline": "^1.1.0", "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.0.0", + "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", - "@azure/msal-browser": "^2.37.1", - "@azure/msal-common": "^13.1.0", - "@azure/msal-node": "^1.17.3", + "@azure/msal-browser": "^3.5.0", + "@azure/msal-node": "^2.5.1", "events": "^3.0.0", "jws": "^4.0.0", "open": "^8.0.0", "stoppable": "^1.1.0", - "tslib": "^2.2.0", - "uuid": "^8.3.0" + "tslib": "^2.2.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@azure/identity/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@azure/keyvault-secrets": { "version": "4.7.0", @@ -435,6 +439,21 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, + "node_modules/@azure/ms-rest-azure-env": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz", + "integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==" + }, + "node_modules/@azure/ms-rest-azure-js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-js/-/ms-rest-azure-js-2.1.0.tgz", + "integrity": "sha512-CjZjB8apvXl5h97Ck6SbeeCmU0sk56YPozPtTyGudPp1RGoHXNjFNtoOvwOG76EdpmMpxbK10DqcygI16Lu60Q==", + "dependencies": { + "@azure/core-auth": "^1.1.4", + "@azure/ms-rest-js": "^2.2.0", + "tslib": "^1.10.0" + } + }, "node_modules/@azure/ms-rest-js": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.7.0.tgz", @@ -450,42 +469,52 @@ "xml2js": "^0.5.0" } }, + "node_modules/@azure/ms-rest-nodeauth": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.1.1.tgz", + "integrity": "sha512-UA/8dgLy3+ZiwJjAZHxL4MUB14fFQPkaAOZ94jsTW/Z6WmoOeny2+cLk0+dyIX/iH6qSrEWKwbStEeB970B9pA==", + "dependencies": { + "@azure/ms-rest-azure-env": "^2.0.0", + "@azure/ms-rest-js": "^2.0.4", + "adal-node": "^0.2.2" + } + }, "node_modules/@azure/msal-browser": { - "version": "2.38.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.38.0.tgz", - "integrity": "sha512-gxBh83IumHgEP9uMCm9pJLKLRwICMQTxG9TX3AytdNt3oLUI3tytm/szYD5u5zKJgSkhHvwFSM+NPnM04hYw3w==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.7.1.tgz", + "integrity": "sha512-EZnk81zn1/5/jv/VVN2Tp+dUVchHmwbbt7pn654Eqa+ua7wtEIg1btuW/mowB13BV2nGYcvniY9Mf+3Sbe0cCg==", "dependencies": { - "@azure/msal-common": "13.2.0" + "@azure/msal-common": "14.6.1" }, "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-common": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.2.0.tgz", - "integrity": "sha512-rnstQ7Zgn3fSTKNQO+/YNV34/QXJs0vni7IA0/3QB1EEyrJg14xyRmTqlw9ta+pdSuT5OJwUP8kI3D/rBwUIBw==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.6.1.tgz", + "integrity": "sha512-yL97p2La0WrgU3MdXThOLOpdmBMvH8J69vwQ/skOqORYwOW/UYPdp9nZpvvfBO+zFZB5M3JkqA2NKtn4GfVBHw==", "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-node": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.18.0.tgz", - "integrity": "sha512-N6GX1Twxw524e7gaJvj7hKtrPRmZl9qGY7U4pmUdx4XzoWYRFfYk4H1ZjVhQ7pwb5Ks88NNhbXVCagsuYPTEFg==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.6.2.tgz", + "integrity": "sha512-XyP+5lUZxTpWpLCC2wAFGA9wXrUhHp1t4NLmQW0mQZzUdcSay3rG7kGGqxxeLf8mRdwoR0B70TCLmIGX6cfK/g==", "dependencies": { - "@azure/msal-common": "13.2.0", + "@azure/msal-common": "14.6.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, "engines": { - "node": "10 || 12 || 14 || 16 || 18" + "node": ">=16" } }, "node_modules/@azure/storage-blob": { - "version": "12.15.0", - "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.15.0.tgz", - "integrity": "sha512-e7JBKLOFi0QVJqqLzrjx1eL3je3/Ug2IQj24cTM9b85CsnnFjLGeGjJVIjbGGZaytewiCEG7r3lRwQX7fKj0/w==", + "version": "12.17.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.17.0.tgz", + "integrity": "sha512-sM4vpsCpcCApagRW5UIjQNlNylo02my2opgp0Emi8x888hZUvJ3dN69Oq20cEGXkMUWnoCrBaB0zyS3yeB87sQ==", "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-http": "^3.0.0", @@ -614,6 +643,18 @@ "node": ">=4" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", @@ -623,80 +664,105 @@ "node": ">=10.0.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", - "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.2.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { - "node": ">= 4" - } - }, - "node_modules/@fortinet/fortigate-autoscale": { - "version": "3.5.4", - "resolved": "https://github.com/fortinet/autoscale-core/releases/download/3.5.4/fortinet-fortigate-autoscale-3.5.4.tgz", - "integrity": "sha512-0JZhq3emWx6zIjYSUn1Tl696HbABrEW07P8FbwvjRaCDrA8/wR3cWEJr7RmxMssOdCGm9Q6xT323QXJAW7nRhQ==", - "bundleDependencies": [ - "@fortinet/autoscale-core" - ], - "license": "MIT", - "dependencies": { - "@azure/arm-compute": "^21.0.0", - "@azure/arm-network": "^31.0.0", - "@azure/cosmos": "^3.17.3", - "@azure/functions": "^1.2.3", - "@azure/identity": "^3.2.3", - "@azure/keyvault-secrets": "^4.7.0", - "@azure/ms-rest-js": "^2.6.6", - "@azure/storage-blob": "^12.14.0", - "@fortinet/autoscale-core": "file:../core", - "axios": "^0.21.2", - "express": "^4.18.2" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fortinet/fortigate-autoscale/node_modules/@fortinet/autoscale-core": { - "version": "3.5.4", - "inBundle": true, - "license": "MIT" - }, "node_modules/@humanwhocodes/config-array": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", - "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@jridgewell/gen-mapping": { @@ -714,9 +780,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, "engines": { "node": ">=6.0.0" @@ -742,19 +808,19 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, "node_modules/@nodelib/fs.scandir": { @@ -800,39 +866,72 @@ "node": ">=8.0.0" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.7.0" + "@sinonjs/commons": "^3.0.0" } }, "node_modules/@sinonjs/samsam": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", - "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.6.0", + "@sinonjs/commons": "^2.0.0", "lodash.get": "^4.4.2", "type-detect": "^4.0.8" } }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, "node_modules/@tootallnate/once": { @@ -843,20 +942,48 @@ "node": ">= 10" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "node_modules/@types/adm-zip": { - "version": "0.4.34", - "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz", - "integrity": "sha512-8ToYLLAYhkRfcmmljrKi22gT2pqu7hGMDtORP1emwIEGmgUTZOsaDjzWFzW5N2frcFRz/50CWt4zA1CxJ73pmQ==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/comment-json": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/comment-json/-/comment-json-1.1.1.tgz", - "integrity": "sha512-U70oEqvnkeSSp8BIJwJclERtT13rd9ejK7XkIzMCQQePZe3VW1b7iQggXyW4ZvfGtGeXD0pZw24q5iWNe++HqQ==", - "dev": true + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/comment-json/-/comment-json-2.4.2.tgz", + "integrity": "sha512-El8na9x7/3M4KxXvOL34fbXbQtBgETELT89HNL8qK2RTlFpBOjlOSbXp1boODiocSnxWXXLW2WeH3DGazyKkVA==", + "deprecated": "This is a stub types definition. comment-json provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "comment-json": "*" + } }, "node_modules/@types/eslint": { "version": "8.4.1", @@ -879,27 +1006,21 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/mocha": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", - "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", "dev": true }, "node_modules/@types/node": { @@ -929,22 +1050,25 @@ "node": ">= 6" } }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, "node_modules/@types/semver": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", - "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "node_modules/@types/sinon": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.2.tgz", - "integrity": "sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==", + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", "dev": true }, "node_modules/@types/tunnel": { @@ -956,31 +1080,33 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.2.tgz", - "integrity": "sha512-4W/9lLuE+v27O/oe7hXJKjNtBLnZE8tQAFpapdxwSVHqtmIoPB1gph3+ahNwVuNL37BX7YQHyGF9Xv6XCnIX2Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.10.2", - "@typescript-eslint/type-utils": "5.10.2", - "@typescript-eslint/utils": "5.10.2", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -989,25 +1115,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.10.2.tgz", - "integrity": "sha512-JaNYGkaQVhP6HNF+lkdOr2cAs2wdSZBoalE22uYWq8IEv/OVH0RksSGydk+sW8cLoSeYmC+OHvRyv2i4AQ7Czg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.10.2", - "@typescript-eslint/types": "5.10.2", - "@typescript-eslint/typescript-estree": "5.10.2", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1016,16 +1143,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.10.2.tgz", - "integrity": "sha512-39Tm6f4RoZoVUWBYr3ekS75TYgpr5Y+X0xLZxXqcZNDWZdJdYbKd3q2IR4V9y5NxxiPu/jxJ8XP7EgHiEQtFnw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.10.2", - "@typescript-eslint/visitor-keys": "5.10.2" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1033,24 +1160,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.2.tgz", - "integrity": "sha512-uRKSvw/Ccs5FYEoXW04Z5VfzF2iiZcx8Fu7DGIB7RHozuP0VbKNzP1KfZkHBTM75pCpsWxIthEH1B33dmGBKHw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "5.10.2", - "debug": "^4.3.2", - "tsutils": "^3.21.0" + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1059,12 +1187,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.2.tgz", - "integrity": "sha512-Qfp0qk/5j2Rz3p3/WhWgu4S1JtMcPgFLnmAKAW061uXxKSa7VWKZsDXVaMXh2N60CX9h6YLaBoy9PJAfCOjk3w==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1072,21 +1200,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.2.tgz", - "integrity": "sha512-WHHw6a9vvZls6JkTgGljwCsMkv8wu8XU8WaYKeYhxhWXH/atZeiMW6uDFPLZOvzNOGmuSMvHtZKd6AuC8PrwKQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.10.2", - "@typescript-eslint/visitor-keys": "5.10.2", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1098,68 +1227,66 @@ } } }, - "node_modules/@typescript-eslint/utils": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.2.tgz", - "integrity": "sha512-vuJaBeig1NnBRkf7q9tgMLREiYD7zsMrsN1DA3wcoMDvr3BTFiIpKjGiYZoKPllfEwN7spUjv7ZqD+JhbVjEPg==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.10.2", - "@typescript-eslint/types": "5.10.2", - "@typescript-eslint/typescript-estree": "5.10.2", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=16 || 14 >=14.17" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^2.0.0" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" }, "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.2.tgz", - "integrity": "sha512-zHIhYGGGrFJvvyfwHk5M08C5B5K4bewkm+rrvNTKk1/S15YHR+SA/QUF8ZWscXSfEaB8Nn2puZj+iHcoxVOD/Q==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.10.2", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1167,18 +1294,21 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, "node_modules/@webassemblyjs/ast": { @@ -1328,34 +1458,42 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", - "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", - "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, - "dependencies": { - "envinfo": "^7.7.3" + "engines": { + "node": ">=14.15.0" }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", - "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -1363,6 +1501,14 @@ } } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -1386,18 +1532,6 @@ "node": ">=6.5" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -1428,10 +1562,74 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adal-node": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.2.4.tgz", + "integrity": "sha512-zIcvbwQFKMUtKxxj8YMHeTT1o/TPXfVNsTXVgXD8sxwV6h4AFQgK77dRciGhuEF9/Sdm3UQPJVPc/6XxrccSeA==", + "deprecated": "This package is no longer supported. Please migrate to @azure/msal-node.", + "dependencies": { + "@xmldom/xmldom": "^0.8.3", + "async": "^2.6.3", + "axios": "^0.21.1", + "date-utils": "*", + "jws": "3.x.x", + "underscore": ">= 1.3.1", + "uuid": "^3.1.0", + "xpath.js": "~1.1.0" + }, + "engines": { + "node": ">= 0.6.15" + } + }, + "node_modules/adal-node/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/adal-node/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/adal-node/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/adal-node/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "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.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/adm-zip": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", - "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", "dev": true, "engines": { "node": ">=6.0" @@ -1464,6 +1662,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -1558,10 +1795,11 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true }, "node_modules/array-union": { "version": "2.1.0", @@ -1581,23 +1819,46 @@ "node": ">=4" } }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, "node_modules/azure-functions-core-tools": { - "version": "4.0.5198", - "resolved": "https://registry.npmjs.org/azure-functions-core-tools/-/azure-functions-core-tools-4.0.5198.tgz", - "integrity": "sha512-cUexqiAKiUKLSgzgHJU4YWLPnntD7CjSu64W0LCjYfxRuU9ohnAOLIY+gZYo7Hdcm2/nT3oTrursCv6DYK69hw==", + "version": "4.0.5455", + "resolved": "https://registry.npmjs.org/azure-functions-core-tools/-/azure-functions-core-tools-4.0.5455.tgz", + "integrity": "sha512-jkVdWfaTYuHxZqwKiVEJZZvAvczfVzDNewYDFMdhm1yGN4je6JcYrTKw5wDuTdqc68+kqpoOgX5Qeyuf48eYww==", "dev": true, "hasInstallScript": true, "hasShrinkwrap": true, @@ -1646,9 +1907,9 @@ } }, "node_modules/azure-functions-core-tools/node_modules/agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "dependencies": { "debug": "4" @@ -1969,15 +2230,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1987,42 +2239,6 @@ "node": ">=8" } }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2052,32 +2268,41 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -2085,18 +2310,11 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -2127,14 +2345,24 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001307", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001307.tgz", - "integrity": "sha512-+MXEMczJ4FuxJAUp0jvAl6Df0NI/OfW1RWEE61eSmzS7hw6lz4IKutbhbXendwq8BljfFuHtu26VWsg4afQ7Ng==", + "version": "1.0.30001584", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001584.tgz", + "integrity": "sha512-LOz7CCQ9M1G7OjJOF9/mzmqmj3jE/7VOmrfw6Mgs0E8cjOsbRXQJHsPBfmBOXDskXKrHLyyW3n7kpDW/4BsfpQ==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] }, "node_modules/chalk": { "version": "4.1.2", @@ -2194,12 +2422,6 @@ "node": ">=6.0" } }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -2326,21 +2548,22 @@ } }, "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", "dev": true, "engines": { - "node": ">= 6" + "node": ">=18" } }, "node_modules/comment-json": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-3.0.3.tgz", - "integrity": "sha512-P7XwYkC3qjIK45EAa9c5Y3lR7SMXhJqwFdWg3niAIAcbk3zlpKDdajV8Hyz/Y3sGNn3l+YNMl8A2N/OubSArHg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", + "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", "dev": true, "dependencies": { - "core-util-is": "^1.0.2", + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", "esprima": "^4.0.1", "has-own-prop": "^2.0.0", "repeat-string": "^1.6.1" @@ -2349,74 +2572,90 @@ "node": ">= 6" } }, - "node_modules/compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", - "dev": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, "dependencies": { - "safe-buffer": "5.2.1" + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "engines": { - "node": ">= 0.6" + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, "engines": { - "node": ">= 0.6" + "node": ">=10.13.0" } }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/copy-webpack-plugin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-7.0.0.tgz", - "integrity": "sha512-SLjQNa5iE3BoCP76ESU9qYo9ZkEWtXoZxDurHoqPchAFRblJ9g96xTeC560UXBMre1Nx6ixIIUfiY3VcjpJw3g==", + "node_modules/copy-webpack-plugin/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, - "dependencies": { - "fast-glob": "^3.2.4", - "glob-parent": "^5.1.1", - "globby": "^11.0.1", - "loader-utils": "^2.0.0", - "normalize-path": "^3.0.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1" - }, "engines": { - "node": ">= 10.13.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" }, - "peerDependencies": { - "webpack": "^5.1.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/core-util-is": { @@ -2425,21 +2664,11 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, - "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true }, "node_modules/cross-spawn": { "version": "6.0.5", @@ -2466,10 +2695,18 @@ "semver": "bin/semver" } }, + "node_modules/date-utils": { + "version": "1.2.21", + "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz", + "integrity": "sha512-wJMBjqlwXR0Iv0wUo/lFbhSQ7MmG1hl36iuxuE91kW+5b5sWbase73manEqNH9sOLFAMG83B4ffNKq9/Iq0FVA==", + "engines": { + "node": ">0.4.0" + } + }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -2528,23 +2765,6 @@ "node": ">=0.4.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -2586,15 +2806,10 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, "node_modules/electron-to-chromium": { - "version": "1.4.64", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.64.tgz", - "integrity": "sha512-8mec/99xgLUZCIZZq3wt61Tpxg55jnOSpxGYapE/1Ma9MpFEYYaz4QNYm0CM1rrnCo7i3FRHhbaWjeCLsveGjQ==", + "version": "1.4.659", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.659.tgz", + "integrity": "sha512-sRJ3nV3HowrYpBtPF9bASQV7OW49IgZC01Xiq43WfSE3RTCkK0/JidoCmR73Hyc1mN+l/H4Yqx0eNiomvExFZg==", "dev": true }, "node_modules/emoji-regex": { @@ -2603,41 +2818,23 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" + "node": ">=10.13.0" } }, "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -2646,18 +2843,6 @@ "node": ">=4" } }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2733,11 +2918,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2748,46 +2928,49 @@ } }, "node_modules/eslint": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.8.0.tgz", - "integrity": "sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.0.5", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.2.0", - "espree": "^9.3.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -2800,52 +2983,58 @@ } }, "node_modules/eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, - "dependencies": { - "get-stdin": "^6.0.0" - }, "bin": { - "eslint-config-prettier-check": "bin/cli.js" + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { - "eslint": ">=3.14.1" + "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-mocha": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz", - "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.2.0.tgz", + "integrity": "sha512-ZhdxzSZnd1P9LqDPF0DBcFLpRIGdh1zkF2JHnQklKQOvrQtT73kdP5K9V2mzvbLR+cCAO9OI48NXK/Ax9/ciCQ==", "dev": true, "dependencies": { - "eslint-utils": "^2.0.0", - "ramda": "^0.27.0" + "eslint-utils": "^3.0.0", + "rambda": "^7.4.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=14.0.0" }, "peerDependencies": { - "eslint": ">= 4.0.0" + "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", "dev": true, "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" }, "engines": { - "node": ">=6.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { - "eslint": ">=5.0.0", - "prettier": ">=1.13.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } @@ -2865,18 +3054,30 @@ } }, "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "engines": { - "node": ">=6" + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" } }, "node_modules/eslint-visitor-keys": { @@ -2924,9 +3125,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -2934,42 +3135,21 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/estraverse": { @@ -2993,15 +3173,6 @@ "node": ">=10.13.0" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/eslint/node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3060,26 +3231,32 @@ } }, "node_modules/espree": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", - "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -3096,9 +3273,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -3155,14 +3332,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -3179,142 +3348,6 @@ "node": ">=0.8.x" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/execa/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -3342,9 +3375,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3354,7 +3387,7 @@ "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-json-stable-stringify": { @@ -3375,9 +3408,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -3422,36 +3455,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3468,21 +3471,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-versions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", - "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", - "dev": true, - "dependencies": { - "semver-regex": "^3.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -3512,9 +3500,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -3543,22 +3531,6 @@ "node": ">= 0.12" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3603,6 +3575,260 @@ "ftnt-devops-ci": "bin/ftnt-devops-ci.js" } }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/ftnt-devops-ci/node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/ftnt-devops-ci/node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -3730,6 +3956,52 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/ftnt-devops-ci/node_modules/eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "dependencies": { + "get-stdin": "^6.0.0" + }, + "bin": { + "eslint-config-prettier-check": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=3.14.1" + } + }, + "node_modules/ftnt-devops-ci/node_modules/eslint-plugin-mocha": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz", + "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "ramda": "^0.27.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "eslint": ">= 4.0.0" + } + }, + "node_modules/ftnt-devops-ci/node_modules/eslint-plugin-mocha/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/ftnt-devops-ci/node_modules/eslint-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", @@ -3990,7 +4262,8 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -4011,6 +4284,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -4025,20 +4299,8 @@ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=4" } }, "node_modules/get-symbol-description": { @@ -4096,9 +4358,9 @@ "dev": true }, "node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -4111,16 +4373,16 @@ } }, "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -4136,19 +4398,17 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -4187,6 +4447,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -4224,21 +4485,6 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -4253,9 +4499,9 @@ } }, "node_modules/http-status-codes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-1.4.0.tgz", - "integrity": "sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==" }, "node_modules/https-proxy-agent": { "version": "5.0.1", @@ -4269,49 +4515,26 @@ "node": ">= 6" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/husky": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz", - "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.10.tgz", + "integrity": "sha512-TQGNknoiy6bURzIO77pPRu+XHi6zI7T93rX+QnJsoYFf3xdjKOur+IlfqzJGMHIK/wXrLg+GsvMs8Op7vI2jVA==", "dev": true, - "hasInstallScript": true, - "dependencies": { - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "compare-versions": "^3.6.0", - "cosmiconfig": "^7.0.0", - "find-versions": "^4.0.0", - "opencollective-postinstall": "^2.0.2", - "pkg-dir": "^5.0.0", - "please-upgrade-node": "^3.2.0", - "slash": "^3.0.0", - "which-pm-runs": "^1.0.0" - }, "bin": { - "husky-run": "bin/run.js", - "husky-upgrade": "lib/upgrader/bin.js" + "husky": "bin.mjs" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/husky" + "url": "https://github.com/sponsors/typicode" } }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -4320,9 +4543,9 @@ } }, "node_modules/ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -4449,7 +4672,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/inquirer": { "version": "7.3.3", @@ -4542,14 +4766,6 @@ "node": ">= 0.10" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4715,6 +4931,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -4761,18 +4986,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -4838,12 +5051,6 @@ "node": ">=8" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4954,20 +5161,36 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "dependencies": { "jws": "^3.2.2", - "lodash": "^4.17.21", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">=12", "npm": ">=6" } }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jsonwebtoken/node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -4978,15 +5201,15 @@ } }, "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", "dev": true }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -5002,16 +5225,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/jws/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -5034,12 +5247,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -5077,20 +5284,6 @@ "node": ">=6.11.5" } }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5114,15 +5307,50 @@ "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -5156,27 +5384,6 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -5186,11 +5393,6 @@ "node": ">= 0.10.0" } }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5206,14 +5408,6 @@ "node": ">= 8" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", @@ -5227,17 +5421,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", @@ -5300,48 +5483,54 @@ } }, "node_modules/mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "dependencies": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", - "debug": "4.3.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.2.0", - "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "4.2.1", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.1", + "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "bin": { "_mocha": "bin/_mocha", - "mocha": "bin/mocha" + "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mochajs" } }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/mocha/node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -5364,12 +5553,12 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" @@ -5405,21 +5594,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/mocha/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5432,9 +5606,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -5449,13 +5623,11 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true }, "node_modules/neo-async": { "version": "2.6.2", @@ -5470,32 +5642,23 @@ "dev": true }, "node_modules/nise": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", - "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" } }, - "node_modules/nise/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, "node_modules/nise/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true }, "node_modules/node-abort-controller": { "version": "3.1.1", @@ -5522,9 +5685,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-package-data": { @@ -5644,31 +5807,11 @@ "node": ">=4" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/object-inspect": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5700,17 +5843,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5751,15 +5883,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/opencollective-postinstall": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", - "dev": true, - "bin": { - "opencollective-postinstall": "index.js" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -5837,32 +5960,6 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5896,11 +5993,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5949,27 +6041,6 @@ "node": ">=4" } }, - "node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dev": true, - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "dependencies": { - "semver-compare": "^1.0.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5980,15 +6051,18 @@ } }, "node_modules/prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/prettier-linter-helpers": { @@ -6016,12 +6090,6 @@ "node": ">= 0.6.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -6031,23 +6099,10 @@ "node": ">=0.4.0" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/punycode": { "version": "2.1.1", @@ -6058,20 +6113,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6092,10 +6133,16 @@ } ] }, + "node_modules/rambda": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", + "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", + "dev": true + }, "node_modules/ramda": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", - "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.2.tgz", + "integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==", "dev": true }, "node_modules/randombytes": { @@ -6107,28 +6154,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -6155,27 +6180,6 @@ "node": ">=4" } }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -6200,18 +6204,6 @@ "node": ">= 0.10" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -6230,6 +6222,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -6377,7 +6378,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/sax": { "version": "1.2.4", @@ -6385,23 +6387,58 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 12.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/semaphore": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", @@ -6411,9 +6448,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -6424,93 +6461,15 @@ "node": ">=10" } }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, - "node_modules/semver-regex": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", - "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" } }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -6587,6 +6546,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -6603,23 +6563,32 @@ "dev": true }, "node_modules/sinon": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", - "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/sinon" } }, + "node_modules/sinon/node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6671,12 +6640,12 @@ "dev": true }, "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, "node_modules/source-map-support": { @@ -6689,6 +6658,15 @@ "source-map": "^0.6.0" } }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -6727,14 +6705,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/stoppable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", @@ -6744,21 +6714,6 @@ "npm": ">=6" } }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -6843,15 +6798,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6876,6 +6822,28 @@ "node": ">=8" } }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/synckit/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -6892,18 +6860,18 @@ } }, "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/terser": { - "version": "5.19.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.1.tgz", - "integrity": "sha512-27hxBUVdV6GoNg1pKQ7Z5cbR6V9txPVyBA+FQw3BaZ1Wuzvztce5p156DaP0NVZNrMZZ+6iG9Syf7WgMNKDg2Q==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -6919,16 +6887,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -6952,13 +6920,32 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { - "randombytes": "^2.1.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser/node_modules/commander": { @@ -7003,217 +6990,114 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, - "node_modules/ts-loader": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.5.tgz", - "integrity": "sha512-zXypEIT6k3oTc+OZNx/cqElrsbBtYqDknf48OZos0NQ3RTt045fBIU8RRSu+suObBzYB355aIPGOe/3kj9h7Ig==", + "node_modules/ts-api-utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, - "dependencies": { - "chalk": "^2.3.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^1.0.2", - "micromatch": "^4.0.0", - "semver": "^6.0.0" - }, "engines": { - "node": ">=10.0.0" + "node": ">=16" }, "peerDependencies": { - "typescript": "*" - } - }, - "node_modules/ts-loader/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ts-loader/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ts-loader/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/ts-loader/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/ts-loader/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ts-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/ts-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/ts-loader/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "typescript": ">=4.2.0" } }, - "node_modules/ts-loader/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" }, "engines": { - "node": ">=4" + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" } }, "node_modules/ts-node": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", - "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", - "dev": true, - "dependencies": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", "arg": "^4.1.0", + "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.17", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "bin": { "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js", "ts-script": "dist/bin-script-deprecated.js" }, - "engines": { - "node": ">=6.0.0" - }, "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, "node_modules/tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", + "json5": "^2.2.2", + "minimist": "^1.2.6", "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, "node_modules/tsconfig-paths-webpack-plugin": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", - "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", + "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^3.9.0" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tsconfig-paths": "^4.1.2" }, "engines": { "node": ">=10.13.0" } }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -7275,29 +7159,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { @@ -7315,17 +7187,56 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, "node_modules/uri-js": { @@ -7337,20 +7248,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -7365,6 +7262,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -7375,14 +7278,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -7402,19 +7297,19 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "node_modules/webpack": { - "version": "5.88.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz", - "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", + "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", + "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", @@ -7428,7 +7323,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -7449,40 +7344,42 @@ } }, "node_modules/webpack-cli": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", - "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", - "commander": "^7.0.0", - "execa": "^5.0.0", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x" + "webpack": "5.x.x" }, "peerDependenciesMeta": { "@webpack-cli/generators": { "optional": true }, - "@webpack-cli/migrate": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -7492,33 +7389,92 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "engines": { - "node": ">= 10" + "node": ">=14" + } + }, + "node_modules/webpack-cli/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, "node_modules/webpack-cli/node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" + } + }, + "node_modules/webpack-cli/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/webpack-cli/node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "dependencies": { - "resolve": "^1.9.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-cli/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-cli/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-cli/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, "node_modules/webpack-merge": { @@ -7543,26 +7499,22 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/whatwg-url": { @@ -7602,12 +7554,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "dev": true - }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -7624,9 +7570,9 @@ } }, "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "node_modules/wrap-ansi": { @@ -7728,6 +7674,14 @@ "node": ">=4.0" } }, + "node_modules/xpath.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz", + "integrity": "sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7742,15 +7696,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -7882,56 +7827,43 @@ } }, "@azure/arm-compute": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@azure/arm-compute/-/arm-compute-21.0.0.tgz", - "integrity": "sha512-9MrL6UW2oKtjLjXMdoAC5Z9vEDZ/liFz9EzR6c+UjbN+wqcaBsf4KERHV6+MWhXrNp3OnpYkeQyYxc0LAv+/nw==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@azure/arm-compute/-/arm-compute-15.0.0.tgz", + "integrity": "sha512-ElV2MuYZ+B2+ygMx2iiM/u3C7WDRruZjkXzfk5p2O+UbWxjG6j/686OH3T+VSDqmzg+47AnIOTLu2v0u0H8FOw==", "requires": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-client": "^1.7.0", - "@azure/core-lro": "^2.5.0", - "@azure/core-paging": "^1.2.0", - "@azure/core-rest-pipeline": "^1.8.0", - "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" - } + "@azure/ms-rest-azure-js": "^2.0.1", + "@azure/ms-rest-js": "^2.0.4", + "tslib": "^1.10.0" } }, "@azure/arm-network": { - "version": "31.0.0", - "resolved": "https://registry.npmjs.org/@azure/arm-network/-/arm-network-31.0.0.tgz", - "integrity": "sha512-tXnTsML8d2vecEvq99umRef+FBXrtxBjzIxaaMnpnIHOWQO2A6pqynJtlWdIjDjXF1L5V5B1BxJF/7XO6bpACQ==", + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/@azure/arm-network/-/arm-network-23.3.0.tgz", + "integrity": "sha512-6hGLMb5I0fs01e7hjTBvfp9pbuN9zI82HokDtmPAhk6awG+Lupex2CDRUXRm08ZKLoH1Kaqly+/uH0+OTNkWpA==", "requires": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-client": "^1.7.0", - "@azure/core-lro": "^2.5.3", - "@azure/core-paging": "^1.2.0", - "@azure/core-rest-pipeline": "^1.8.0", - "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" - } + "@azure/ms-rest-azure-js": "^2.0.1", + "@azure/ms-rest-js": "^2.0.4", + "tslib": "^1.10.0" } }, "@azure/core-auth": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", - "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.6.0.tgz", + "integrity": "sha512-3X9wzaaGgRaBCwhLQZDtFp5uLIXCPrGbwJNWPPugvL4xbIGgScv77YzzxToKGLAKvG9amDoofMoP+9hsH1vs1w==", "requires": { - "@azure/abort-controller": "^1.0.0", + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.1.0", "tslib": "^2.2.0" }, "dependencies": { + "@azure/abort-controller": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz", + "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==", + "requires": { + "tslib": "^2.2.0" + } + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -8018,16 +7950,24 @@ } }, "@azure/core-lro": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.3.tgz", - "integrity": "sha512-ubkOf2YCnVtq7KqEJQqAI8dDD5rH1M6OP5kW0KO/JQyTaxLA0N0pjFWvvaysCj9eHMNBcuuoZXhhl0ypjod2DA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.6.0.tgz", + "integrity": "sha512-PyRNcaIOfMgoUC01/24NoG+k8O81VrKxYARnDlo+Q2xji0/0/j2nIt8BwQh294pb1c5QnXTDPbNR4KzoDKXEoQ==", "requires": { - "@azure/abort-controller": "^1.0.0", + "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", "@azure/logger": "^1.0.0", "tslib": "^2.2.0" }, "dependencies": { + "@azure/abort-controller": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz", + "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==", + "requires": { + "tslib": "^2.2.0" + } + }, "tslib": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", @@ -8051,29 +7991,26 @@ } }, "@azure/core-rest-pipeline": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.11.0.tgz", - "integrity": "sha512-nB4KXl6qAyJmBVLWA7SakT4tzpYZTCk4pvRBeI+Ye0WYSOrlTqlMhc4MSS/8atD3ufeYWdkN380LLoXlUUzThw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.14.0.tgz", + "integrity": "sha512-Tp4M6NsjCmn9L5p7HsW98eSOS7A0ibl3e5ntZglozT0XuD/0y6i36iW829ZbBq0qihlGgfaeFpkLjZ418KDm1Q==", "requires": { - "@azure/abort-controller": "^1.0.0", + "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.4.0", "@azure/core-tracing": "^1.0.1", "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", - "form-data": "^4.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "tslib": "^2.2.0" }, - "dependencies": { - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "@azure/abort-controller": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz", + "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "tslib": "^2.2.0" } }, "tslib": { @@ -8147,32 +8084,30 @@ "integrity": "sha512-dZITbYPNg6ay6ngcCOjRUh1wDhlFITS0zIkqplyH5KfKEAVPooaoaye5mUFnR+WP9WdGRjlNXyl/y2tgWKHcRg==" }, "@azure/identity": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-3.2.3.tgz", - "integrity": "sha512-knIbl7p2i8r3qPsLW2W84esmDPr36RqieLC72OeuqYk4+0TRNthUhWTs655P9S9Pm3TVVxcFsS3Le9SXIWBIFA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.0.1.tgz", + "integrity": "sha512-yRdgF03SFLqUMZZ1gKWt0cs0fvrDIkq2bJ6Oidqcoo5uM85YMBnXWMzYKK30XqIT76lkFyAaoAAy5knXhrG4Lw==", "requires": { "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", + "@azure/core-auth": "^1.5.0", "@azure/core-client": "^1.4.0", "@azure/core-rest-pipeline": "^1.1.0", "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.0.0", + "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", - "@azure/msal-browser": "^2.37.1", - "@azure/msal-common": "^13.1.0", - "@azure/msal-node": "^1.17.3", + "@azure/msal-browser": "^3.5.0", + "@azure/msal-node": "^2.5.1", "events": "^3.0.0", "jws": "^4.0.0", "open": "^8.0.0", "stoppable": "^1.1.0", - "tslib": "^2.2.0", - "uuid": "^8.3.0" + "tslib": "^2.2.0" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" } } }, @@ -8216,6 +8151,21 @@ } } }, + "@azure/ms-rest-azure-env": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz", + "integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==" + }, + "@azure/ms-rest-azure-js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-js/-/ms-rest-azure-js-2.1.0.tgz", + "integrity": "sha512-CjZjB8apvXl5h97Ck6SbeeCmU0sk56YPozPtTyGudPp1RGoHXNjFNtoOvwOG76EdpmMpxbK10DqcygI16Lu60Q==", + "requires": { + "@azure/core-auth": "^1.1.4", + "@azure/ms-rest-js": "^2.2.0", + "tslib": "^1.10.0" + } + }, "@azure/ms-rest-js": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.7.0.tgz", @@ -8231,33 +8181,43 @@ "xml2js": "^0.5.0" } }, + "@azure/ms-rest-nodeauth": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.1.1.tgz", + "integrity": "sha512-UA/8dgLy3+ZiwJjAZHxL4MUB14fFQPkaAOZ94jsTW/Z6WmoOeny2+cLk0+dyIX/iH6qSrEWKwbStEeB970B9pA==", + "requires": { + "@azure/ms-rest-azure-env": "^2.0.0", + "@azure/ms-rest-js": "^2.0.4", + "adal-node": "^0.2.2" + } + }, "@azure/msal-browser": { - "version": "2.38.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.38.0.tgz", - "integrity": "sha512-gxBh83IumHgEP9uMCm9pJLKLRwICMQTxG9TX3AytdNt3oLUI3tytm/szYD5u5zKJgSkhHvwFSM+NPnM04hYw3w==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.7.1.tgz", + "integrity": "sha512-EZnk81zn1/5/jv/VVN2Tp+dUVchHmwbbt7pn654Eqa+ua7wtEIg1btuW/mowB13BV2nGYcvniY9Mf+3Sbe0cCg==", "requires": { - "@azure/msal-common": "13.2.0" + "@azure/msal-common": "14.6.1" } }, "@azure/msal-common": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.2.0.tgz", - "integrity": "sha512-rnstQ7Zgn3fSTKNQO+/YNV34/QXJs0vni7IA0/3QB1EEyrJg14xyRmTqlw9ta+pdSuT5OJwUP8kI3D/rBwUIBw==" + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.6.1.tgz", + "integrity": "sha512-yL97p2La0WrgU3MdXThOLOpdmBMvH8J69vwQ/skOqORYwOW/UYPdp9nZpvvfBO+zFZB5M3JkqA2NKtn4GfVBHw==" }, "@azure/msal-node": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.18.0.tgz", - "integrity": "sha512-N6GX1Twxw524e7gaJvj7hKtrPRmZl9qGY7U4pmUdx4XzoWYRFfYk4H1ZjVhQ7pwb5Ks88NNhbXVCagsuYPTEFg==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.6.2.tgz", + "integrity": "sha512-XyP+5lUZxTpWpLCC2wAFGA9wXrUhHp1t4NLmQW0mQZzUdcSay3rG7kGGqxxeLf8mRdwoR0B70TCLmIGX6cfK/g==", "requires": { - "@azure/msal-common": "13.2.0", + "@azure/msal-common": "14.6.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "@azure/storage-blob": { - "version": "12.15.0", - "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.15.0.tgz", - "integrity": "sha512-e7JBKLOFi0QVJqqLzrjx1eL3je3/Ug2IQj24cTM9b85CsnnFjLGeGjJVIjbGGZaytewiCEG7r3lRwQX7fKj0/w==", + "version": "12.17.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.17.0.tgz", + "integrity": "sha512-sM4vpsCpcCApagRW5UIjQNlNylo02my2opgp0Emi8x888hZUvJ3dN69Oq20cEGXkMUWnoCrBaB0zyS3yeB87sQ==", "requires": { "@azure/abort-controller": "^1.0.0", "@azure/core-http": "^3.0.0", @@ -8363,75 +8323,88 @@ } } }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, "@discoveryjs/json-ext": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", "dev": true }, - "@eslint/eslintrc": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", - "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.2.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" + "eslint-visitor-keys": "^3.3.0" }, "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true } } }, - "@fortinet/fortigate-autoscale": { - "version": "https://github.com/fortinet/autoscale-core/releases/download/3.5.4/fortinet-fortigate-autoscale-3.5.4.tgz", - "integrity": "sha512-0JZhq3emWx6zIjYSUn1Tl696HbABrEW07P8FbwvjRaCDrA8/wR3cWEJr7RmxMssOdCGm9Q6xT323QXJAW7nRhQ==", + "@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, "requires": { - "@azure/arm-compute": "^21.0.0", - "@azure/arm-network": "^31.0.0", - "@azure/cosmos": "^3.17.3", - "@azure/functions": "^1.2.3", - "@azure/identity": "^3.2.3", - "@azure/keyvault-secrets": "^4.7.0", - "@azure/ms-rest-js": "^2.6.6", - "@azure/storage-blob": "^12.14.0", - "@fortinet/autoscale-core": "file:../core", - "axios": "^0.21.2", - "express": "^4.18.2" - }, - "dependencies": { - "@fortinet/autoscale-core": { - "version": "3.5.4", - "bundled": true - } + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" } }, + "@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true + }, "@humanwhocodes/config-array": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", - "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" } }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "@jridgewell/gen-mapping": { @@ -8446,9 +8419,9 @@ } }, "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true }, "@jridgewell/set-array": { @@ -8468,19 +8441,19 @@ } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, "@nodelib/fs.scandir": { @@ -8514,39 +8487,62 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==" }, + "@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true + }, + "@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "dev": true + }, "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0" + "@sinonjs/commons": "^3.0.0" } }, "@sinonjs/samsam": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", - "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", "dev": true, "requires": { - "@sinonjs/commons": "^1.6.0", + "@sinonjs/commons": "^2.0.0", "lodash.get": "^4.4.2", "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } } }, "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, "@tootallnate/once": { @@ -8554,20 +8550,47 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "@types/adm-zip": { - "version": "0.4.34", - "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz", - "integrity": "sha512-8ToYLLAYhkRfcmmljrKi22gT2pqu7hGMDtORP1emwIEGmgUTZOsaDjzWFzW5N2frcFRz/50CWt4zA1CxJ73pmQ==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==", "dev": true, "requires": { "@types/node": "*" } }, "@types/comment-json": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/comment-json/-/comment-json-1.1.1.tgz", - "integrity": "sha512-U70oEqvnkeSSp8BIJwJclERtT13rd9ejK7XkIzMCQQePZe3VW1b7iQggXyW4ZvfGtGeXD0pZw24q5iWNe++HqQ==", - "dev": true + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/comment-json/-/comment-json-2.4.2.tgz", + "integrity": "sha512-El8na9x7/3M4KxXvOL34fbXbQtBgETELT89HNL8qK2RTlFpBOjlOSbXp1boODiocSnxWXXLW2WeH3DGazyKkVA==", + "dev": true, + "requires": { + "comment-json": "*" + } }, "@types/eslint": { "version": "8.4.1", @@ -8590,27 +8613,21 @@ } }, "@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "@types/mocha": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", - "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", "dev": true }, "@types/node": { @@ -8639,22 +8656,25 @@ } } }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, "@types/semver": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", - "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "@types/sinon": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.2.tgz", - "integrity": "sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==", + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", "dev": true }, "@types/tunnel": { @@ -8666,129 +8686,138 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.2.tgz", - "integrity": "sha512-4W/9lLuE+v27O/oe7hXJKjNtBLnZE8tQAFpapdxwSVHqtmIoPB1gph3+ahNwVuNL37BX7YQHyGF9Xv6XCnIX2Q==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.10.2", - "@typescript-eslint/type-utils": "5.10.2", - "@typescript-eslint/utils": "5.10.2", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/parser": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.10.2.tgz", - "integrity": "sha512-JaNYGkaQVhP6HNF+lkdOr2cAs2wdSZBoalE22uYWq8IEv/OVH0RksSGydk+sW8cLoSeYmC+OHvRyv2i4AQ7Czg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.10.2", - "@typescript-eslint/types": "5.10.2", - "@typescript-eslint/typescript-estree": "5.10.2", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.10.2.tgz", - "integrity": "sha512-39Tm6f4RoZoVUWBYr3ekS75TYgpr5Y+X0xLZxXqcZNDWZdJdYbKd3q2IR4V9y5NxxiPu/jxJ8XP7EgHiEQtFnw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.10.2", - "@typescript-eslint/visitor-keys": "5.10.2" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" } }, "@typescript-eslint/type-utils": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.2.tgz", - "integrity": "sha512-uRKSvw/Ccs5FYEoXW04Z5VfzF2iiZcx8Fu7DGIB7RHozuP0VbKNzP1KfZkHBTM75pCpsWxIthEH1B33dmGBKHw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "requires": { - "@typescript-eslint/utils": "5.10.2", - "debug": "^4.3.2", - "tsutils": "^3.21.0" + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.2.tgz", - "integrity": "sha512-Qfp0qk/5j2Rz3p3/WhWgu4S1JtMcPgFLnmAKAW061uXxKSa7VWKZsDXVaMXh2N60CX9h6YLaBoy9PJAfCOjk3w==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.2.tgz", - "integrity": "sha512-WHHw6a9vvZls6JkTgGljwCsMkv8wu8XU8WaYKeYhxhWXH/atZeiMW6uDFPLZOvzNOGmuSMvHtZKd6AuC8PrwKQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.10.2", - "@typescript-eslint/visitor-keys": "5.10.2", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/utils": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.2.tgz", - "integrity": "sha512-vuJaBeig1NnBRkf7q9tgMLREiYD7zsMrsN1DA3wcoMDvr3BTFiIpKjGiYZoKPllfEwN7spUjv7ZqD+JhbVjEPg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.10.2", - "@typescript-eslint/types": "5.10.2", - "@typescript-eslint/typescript-estree": "5.10.2", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "dependencies": { - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "eslint-visitor-keys": "^2.0.0" + "balanced-match": "^1.0.0" } }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } } } }, + "@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + } + }, "@typescript-eslint/visitor-keys": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.2.tgz", - "integrity": "sha512-zHIhYGGGrFJvvyfwHk5M08C5B5K4bewkm+rrvNTKk1/S15YHR+SA/QUF8ZWscXSfEaB8Nn2puZj+iHcoxVOD/Q==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "requires": { - "@typescript-eslint/types": "5.10.2", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" }, "dependencies": { "eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true } } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, "@webassemblyjs/ast": { @@ -8938,28 +8967,31 @@ } }, "@webpack-cli/configtest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", - "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, "requires": {} }, "@webpack-cli/info": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", - "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, - "requires": { - "envinfo": "^7.7.3" - } + "requires": {} }, "@webpack-cli/serve": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", - "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, "requires": {} }, + "@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==" + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -8980,15 +9012,6 @@ "event-target-shim": "^5.0.0" } }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -9009,10 +9032,65 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true + }, + "adal-node": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.2.4.tgz", + "integrity": "sha512-zIcvbwQFKMUtKxxj8YMHeTT1o/TPXfVNsTXVgXD8sxwV6h4AFQgK77dRciGhuEF9/Sdm3UQPJVPc/6XxrccSeA==", + "requires": { + "@xmldom/xmldom": "^0.8.3", + "async": "^2.6.3", + "axios": "^0.21.1", + "date-utils": "*", + "jws": "3.x.x", + "underscore": ">= 1.3.1", + "uuid": "^3.1.0", + "xpath.js": "~1.1.0" + }, + "dependencies": { + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, "adm-zip": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", - "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", "dev": true }, "agent-base": { @@ -9035,6 +9113,35 @@ "uri-js": "^4.2.2" } }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -9102,10 +9209,11 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true }, "array-union": { "version": "2.1.0", @@ -9119,23 +9227,45 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "requires": { + "lodash": "^4.17.14" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "requires": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } } }, "azure-functions-core-tools": { - "version": "4.0.5198", - "resolved": "https://registry.npmjs.org/azure-functions-core-tools/-/azure-functions-core-tools-4.0.5198.tgz", - "integrity": "sha512-cUexqiAKiUKLSgzgHJU4YWLPnntD7CjSu64W0LCjYfxRuU9ohnAOLIY+gZYo7Hdcm2/nT3oTrursCv6DYK69hw==", + "version": "4.0.5455", + "resolved": "https://registry.npmjs.org/azure-functions-core-tools/-/azure-functions-core-tools-4.0.5455.tgz", + "integrity": "sha512-jkVdWfaTYuHxZqwKiVEJZZvAvczfVzDNewYDFMdhm1yGN4je6JcYrTKw5wDuTdqc68+kqpoOgX5Qeyuf48eYww==", "dev": true, "requires": { "chalk": "3.0.0", @@ -9169,9 +9299,9 @@ } }, "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "requires": { "debug": "4" @@ -9434,52 +9564,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -9506,22 +9596,21 @@ "dev": true }, "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" } }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "buffer-from": { "version": "1.1.2", @@ -9529,15 +9618,11 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -9556,9 +9641,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001307", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001307.tgz", - "integrity": "sha512-+MXEMczJ4FuxJAUp0jvAl6Df0NI/OfW1RWEE61eSmzS7hw6lz4IKutbhbXendwq8BljfFuHtu26VWsg4afQ7Ng==", + "version": "1.0.30001584", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001584.tgz", + "integrity": "sha512-LOz7CCQ9M1G7OjJOF9/mzmqmj3jE/7VOmrfw6Mgs0E8cjOsbRXQJHsPBfmBOXDskXKrHLyyW3n7kpDW/4BsfpQ==", "dev": true }, "chalk": { @@ -9599,12 +9684,6 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -9706,72 +9785,79 @@ } }, "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", "dev": true }, "comment-json": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-3.0.3.tgz", - "integrity": "sha512-P7XwYkC3qjIK45EAa9c5Y3lR7SMXhJqwFdWg3niAIAcbk3zlpKDdajV8Hyz/Y3sGNn3l+YNMl8A2N/OubSArHg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", + "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", "dev": true, "requires": { - "core-util-is": "^1.0.2", + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", "esprima": "^4.0.1", "has-own-prop": "^2.0.0", "repeat-string": "^1.6.1" } }, - "compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, "copy-webpack-plugin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-7.0.0.tgz", - "integrity": "sha512-SLjQNa5iE3BoCP76ESU9qYo9ZkEWtXoZxDurHoqPchAFRblJ9g96xTeC560UXBMre1Nx6ixIIUfiY3VcjpJw3g==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", "dev": true, "requires": { - "fast-glob": "^3.2.4", - "glob-parent": "^5.1.1", - "globby": "^11.0.1", - "loader-utils": "^2.0.0", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", "normalize-path": "^3.0.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1" + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "dependencies": { + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globby": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "dev": true, + "requires": { + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + } + }, + "path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true + }, + "slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true + } } }, "core-util-is": { @@ -9780,18 +9866,11 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true }, "cross-spawn": { "version": "6.0.5", @@ -9814,10 +9893,15 @@ } } }, + "date-utils": { + "version": "1.2.21", + "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz", + "integrity": "sha512-wJMBjqlwXR0Iv0wUo/lFbhSQ7MmG1hl36iuxuE91kW+5b5sWbase73manEqNH9sOLFAMG83B4ffNKq9/Iq0FVA==" + }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -9853,16 +9937,6 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -9895,15 +9969,10 @@ "safe-buffer": "^5.0.1" } }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, "electron-to-chromium": { - "version": "1.4.64", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.64.tgz", - "integrity": "sha512-8mec/99xgLUZCIZZq3wt61Tpxg55jnOSpxGYapE/1Ma9MpFEYYaz4QNYm0CM1rrnCo7i3FRHhbaWjeCLsveGjQ==", + "version": "1.4.659", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.659.tgz", + "integrity": "sha512-sRJ3nV3HowrYpBtPF9bASQV7OW49IgZC01Xiq43WfSE3RTCkK0/JidoCmR73Hyc1mN+l/H4Yqx0eNiomvExFZg==", "dev": true }, "emoji-regex": { @@ -9912,43 +9981,22 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" } }, "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", "dev": true }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -10009,11 +10057,6 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -10021,46 +10064,49 @@ "dev": true }, "eslint": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.8.0.tgz", - "integrity": "sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.0.5", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.2.0", - "espree": "^9.3.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "dependencies": { "ansi-regex": { @@ -10087,36 +10133,19 @@ "dev": true }, "eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, "eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, "estraverse": { @@ -10134,12 +10163,6 @@ "is-glob": "^4.0.3" } }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -10182,31 +10205,30 @@ } }, "eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } + "requires": {} }, "eslint-plugin-mocha": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz", - "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.2.0.tgz", + "integrity": "sha512-ZhdxzSZnd1P9LqDPF0DBcFLpRIGdh1zkF2JHnQklKQOvrQtT73kdP5K9V2mzvbLR+cCAO9OI48NXK/Ax9/ciCQ==", "dev": true, "requires": { - "eslint-utils": "^2.0.0", - "ramda": "^0.27.0" + "eslint-utils": "^3.0.0", + "rambda": "^7.4.0" } }, "eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", "dev": true, "requires": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" } }, "eslint-scope": { @@ -10220,12 +10242,20 @@ } }, "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } } }, "eslint-visitor-keys": { @@ -10235,20 +10265,20 @@ "dev": true }, "espree": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", - "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "requires": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "dependencies": { "eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true } } @@ -10260,9 +10290,9 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -10305,11 +10335,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, "event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -10320,119 +10345,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -10457,9 +10369,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -10487,9 +10399,9 @@ "dev": true }, "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -10522,35 +10434,6 @@ "to-regex-range": "^5.0.1" } }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -10561,15 +10444,6 @@ "path-exists": "^4.0.0" } }, - "find-versions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", - "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", - "dev": true, - "requires": { - "semver-regex": "^3.1.2" - } - }, "flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -10593,9 +10467,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" }, "form-data": { "version": "2.5.1", @@ -10607,16 +10481,6 @@ "mime-types": "^2.1.12" } }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -10649,6 +10513,152 @@ "typescript": "^4.4.4" }, "dependencies": { + "@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "dependencies": { + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + } + } + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -10783,6 +10793,36 @@ } } }, + "eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-plugin-mocha": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz", + "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "ramda": "^0.27.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + } + } + }, "eslint-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", @@ -10943,7 +10983,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", @@ -10961,6 +11002,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -10973,12 +11015,6 @@ "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", "dev": true }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -11019,25 +11055,25 @@ "dev": true }, "globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" } }, @@ -11047,16 +11083,17 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -11082,7 +11119,8 @@ "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true }, "has-tostringtag": { "version": "1.0.0", @@ -11105,18 +11143,6 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, "http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -11128,9 +11154,9 @@ } }, "http-status-codes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-1.4.0.tgz", - "integrity": "sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==" }, "https-proxy-agent": { "version": "5.0.1", @@ -11141,42 +11167,25 @@ "debug": "4" } }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, "husky": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz", - "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "compare-versions": "^3.6.0", - "cosmiconfig": "^7.0.0", - "find-versions": "^4.0.0", - "opencollective-postinstall": "^2.0.2", - "pkg-dir": "^5.0.0", - "please-upgrade-node": "^3.2.0", - "slash": "^3.0.0", - "which-pm-runs": "^1.0.0" - } + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.10.tgz", + "integrity": "sha512-TQGNknoiy6bURzIO77pPRu+XHi6zI7T93rX+QnJsoYFf3xdjKOur+IlfqzJGMHIK/wXrLg+GsvMs8Op7vI2jVA==", + "dev": true }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, "ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true }, "import-fresh": { @@ -11266,7 +11275,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "inquirer": { "version": "7.3.3", @@ -11340,11 +11350,6 @@ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -11450,6 +11455,12 @@ "has-tostringtag": "^1.0.0" } }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -11481,12 +11492,6 @@ "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", "dev": true }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, "is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -11528,12 +11533,6 @@ "is-docker": "^2.0.0" } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -11625,16 +11624,32 @@ "dev": true }, "jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "requires": { "jws": "^3.2.2", - "lodash": "^4.17.21", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "dependencies": { + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, "jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -11647,15 +11662,15 @@ } }, "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", "dev": true }, "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", "requires": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -11669,18 +11684,6 @@ "requires": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" - }, - "dependencies": { - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - } } }, "kind-of": { @@ -11699,12 +11702,6 @@ "type-check": "~0.4.0" } }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -11735,17 +11732,6 @@ "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", "dev": true }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -11763,15 +11749,50 @@ "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -11796,32 +11817,12 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", "dev": true }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -11834,11 +11835,6 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", @@ -11849,11 +11845,6 @@ "picomatch": "^2.2.3" } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, "mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", @@ -11898,37 +11889,43 @@ } }, "mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "requires": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", - "debug": "4.3.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.2.0", - "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "4.2.1", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.1", + "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -11942,12 +11939,12 @@ "dev": true }, "minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" } }, "ms": { @@ -11973,15 +11970,6 @@ "requires": { "has-flag": "^4.0.0" } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -11997,9 +11985,9 @@ "dev": true }, "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, "natural-compare": { @@ -12008,10 +11996,11 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true }, "neo-async": { "version": "2.6.2", @@ -12026,32 +12015,23 @@ "dev": true }, "nise": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", - "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" }, "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - } + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true } } }, @@ -12069,9 +12049,9 @@ } }, "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "normalize-package-data": { @@ -12169,27 +12149,11 @@ } } }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - }, - "dependencies": { - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - } - } - }, "object-inspect": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true }, "object-keys": { "version": "1.1.1", @@ -12209,14 +12173,6 @@ "object-keys": "^1.1.1" } }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -12245,12 +12201,6 @@ "is-wsl": "^2.2.0" } }, - "opencollective-postinstall": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", - "dev": true - }, "optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -12304,23 +12254,6 @@ "callsites": "^3.0.0" } }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -12345,11 +12278,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -12380,24 +12308,6 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dev": true, - "requires": { - "find-up": "^5.0.0" - } - }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12405,9 +12315,9 @@ "dev": true }, "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true }, "prettier-linter-helpers": { @@ -12429,32 +12339,16 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "punycode": { "version": "2.1.1", @@ -12462,24 +12356,22 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "rambda": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", + "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", + "dev": true + }, "ramda": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", - "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.2.tgz", + "integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==", "dev": true }, "randombytes": { @@ -12491,22 +12383,6 @@ "safe-buffer": "^5.1.0" } }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -12529,29 +12405,6 @@ } } }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -12570,12 +12423,6 @@ "resolve": "^1.1.6" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -12588,6 +12435,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -12678,7 +12531,8 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "sax": { "version": "1.2.4", @@ -12686,14 +12540,44 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dev": true, "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } } }, "semaphore": { @@ -12702,92 +12586,22 @@ "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==" }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "requires": { "lru-cache": "^6.0.0" } }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, - "semver-regex": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", - "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", - "dev": true - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "requires": { "randombytes": "^2.1.0" } }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -12843,6 +12657,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -12856,17 +12671,25 @@ "dev": true }, "sinon": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", - "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", "dev": true, "requires": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "dependencies": { + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + } } }, "slash": { @@ -12913,9 +12736,9 @@ } }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true }, "source-map-support": { @@ -12926,6 +12749,14 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "spdx-correct": { @@ -12966,33 +12797,11 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, "stoppable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==" }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -13058,12 +12867,6 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -13079,6 +12882,24 @@ "has-flag": "^4.0.0" } }, + "synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "requires": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -13092,15 +12913,15 @@ } }, "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true }, "terser": { - "version": "5.19.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.1.tgz", - "integrity": "sha512-27hxBUVdV6GoNg1pKQ7Z5cbR6V9txPVyBA+FQw3BaZ1Wuzvztce5p156DaP0NVZNrMZZ+6iG9Syf7WgMNKDg2Q==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", @@ -13118,25 +12939,37 @@ } }, "terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "dependencies": { - "serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "requires": { - "randombytes": "^2.1.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } } } @@ -13171,170 +13004,72 @@ "is-number": "^7.0.0" } }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "ts-api-utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", + "dev": true, + "requires": {} + }, "ts-loader": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.5.tgz", - "integrity": "sha512-zXypEIT6k3oTc+OZNx/cqElrsbBtYqDknf48OZos0NQ3RTt045fBIU8RRSu+suObBzYB355aIPGOe/3kj9h7Ig==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, "requires": { - "chalk": "^2.3.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^1.0.2", + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", - "semver": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "semver": "^7.3.4", + "source-map": "^0.7.4" } }, "ts-node": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", - "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", - "dev": true, - "requires": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", "arg": "^4.1.0", + "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.17", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" } }, "tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", + "json5": "^2.2.2", + "minimist": "^1.2.6", "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } } }, "tsconfig-paths-webpack-plugin": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", - "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", + "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", "dev": true, "requires": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^3.9.0" - }, - "dependencies": { - "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - } + "tsconfig-paths": "^4.1.2" } }, "tslib": { @@ -13377,19 +13112,10 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, "typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true }, "unbox-primitive": { @@ -13404,15 +13130,31 @@ "which-boxed-primitive": "^1.0.2" } }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, + "unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true + }, "universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } }, "uri-js": { "version": "4.4.1", @@ -13423,17 +13165,6 @@ "punycode": "^2.1.0" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -13445,6 +13176,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -13455,11 +13192,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -13476,19 +13208,19 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "webpack": { - "version": "5.88.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz", - "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", + "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", + "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", @@ -13502,68 +13234,105 @@ "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "dependencies": { - "enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true } } }, "webpack-cli": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", - "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", - "commander": "^7.0.0", - "execa": "^5.0.0", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "dependencies": { "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "resolve": "^1.9.0" + "isexe": "^2.0.0" } } } @@ -13615,12 +13384,6 @@ "is-symbol": "^1.0.3" } }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "dev": true - }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -13634,9 +13397,9 @@ "dev": true }, "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "wrap-ansi": { @@ -13713,6 +13476,11 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, + "xpath.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz", + "integrity": "sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==" + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -13724,12 +13492,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/package.json b/package.json index 82a1507..aa00f5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fortigate-autoscale-azure", - "version": "3.5.2", + "version": "3.6.0", "description": "Fortinet FortiGate Autoscale for Azure", "main": "dist/transpiled/index.js", "types": "dist/types/index.d.ts", @@ -43,43 +43,51 @@ }, "homepage": "https://github.com/fortinet/fortigate-autoscale-azure#readme", "dependencies": { + "@azure/arm-compute": "^15.0.0", + "@azure/arm-network": "^23.2.0", + "@azure/cosmos": "^3.9.5", "@azure/functions": "^1.2.3", - "@fortinet/fortigate-autoscale": "https://github.com/fortinet/autoscale-core/releases/download/3.5.4/fortinet-fortigate-autoscale-3.5.4.tgz", + "@azure/identity": "^4.0.0", + "@azure/keyvault-secrets": "^4.7.0", + "@azure/ms-rest-js": "^2.2.1", + "@azure/ms-rest-nodeauth": "^3.0.9", + "@azure/storage-blob": "^12.17.0", "@types/node": "^13.13.50", - "http-status-codes": "^1.4.0" + "axios": "^1.6.5", + "http-status-codes": "^2.3.0" }, "devDependencies": { - "@types/adm-zip": "^0.4.34", - "@types/comment-json": "^1.1.1", - "@types/mocha": "^8.2.2", - "@types/semver": "^7.3.6", - "@types/sinon": "^7.5.2", - "@typescript-eslint/eslint-plugin": "^5.10.2", - "@typescript-eslint/parser": "^5.10.2", - "adm-zip": "^0.5.5", - "azure-functions-core-tools": "^4.0.5198", + "@types/adm-zip": "^0.5.5", + "@types/comment-json": "^2.4.2", + "@types/mocha": "^10.0.6", + "@types/semver": "^7.5.6", + "@types/sinon": "^17.0.3", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "adm-zip": "^0.5.10", + "azure-functions-core-tools": "^4.0.5455", "chalk": "^4.1.2", - "commander": "^5.1.0", - "comment-json": "^3.0.2", - "copy-webpack-plugin": "^7.0.0", - "eslint": "^8.8.0", - "eslint-config-prettier": "^6.10.1", - "eslint-plugin-mocha": "^6.3.0", - "eslint-plugin-prettier": "^3.4.0", + "commander": "^12.0.0", + "comment-json": "^4.2.3", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-mocha": "^10.2.0", + "eslint-plugin-prettier": "^5.1.3", "ftnt-devops-ci": "https://github.com/fortinet/ftnt-devops-ci/releases/download/1.1.8/ftnt-devops-ci-1.1.8.tgz", - "husky": "^4.3.0", + "husky": "^9.0.10", "json-loader": "^0.5.7", - "mocha": "^9.2.0", + "mocha": "^10.2.0", "npm-run-all": "^4.1.5", - "prettier": "^1.19.1", - "semver": "^7.3.5", + "prettier": "^3.2.5", + "semver": "^7.6.0", "shx": "^0.3.4", - "sinon": "^9.0.2", - "ts-loader": "^7.0.5", - "ts-node": "^8.8.2", - "tsconfig-paths-webpack-plugin": "^3.3.0", - "typescript": "^3.9.2", - "webpack": "^5.68.0", - "webpack-cli": "^4.9.2" + "sinon": "^17.0.1", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "typescript": "^5.3.3", + "webpack": "^5.90.1", + "webpack-cli": "^5.1.4" } } diff --git a/scripts/lib-concourse.ts b/scripts/lib-concourse.ts index 03a2924..ee08984 100644 --- a/scripts/lib-concourse.ts +++ b/scripts/lib-concourse.ts @@ -42,10 +42,7 @@ export function mask(str: string): string { } export function createHash(str: string): string { - return crypto - .createHash('sha256') - .update(str, 'utf8') - .digest('hex'); + return crypto.createHash('sha256').update(str, 'utf8').digest('hex'); } export function inputFetchAll(): Promise { diff --git a/scripts/make-dist.ts b/scripts/make-dist.ts index 7a8d0e6..d44535a 100644 --- a/scripts/make-dist.ts +++ b/scripts/make-dist.ts @@ -78,19 +78,19 @@ const deploymentPackageRequiredFileLocations = [ 'README.md', 'LICENSE', { - src: 'node_modules/@fortinet/fortigate-autoscale/assets/configset/baseconfig', + src: 'assets/configset/baseconfig', des: 'assets/configset' }, { - src: 'node_modules/@fortinet/fortigate-autoscale/assets/configset/fazintegration', + src: 'assets/configset/fazintegration', des: 'assets/configset' }, { - src: 'node_modules/@fortinet/fortigate-autoscale/assets/configset/port2config', + src: 'assets/configset/port2config', des: 'assets/configset' }, { - src: 'node_modules/@fortinet/fortigate-autoscale/assets/configset/azure/extraports', + src: 'assets/configset/azure/extraports', des: 'assets/configset' } ]; diff --git a/scripts/sync-version.ts b/scripts/sync-version.ts index 9500363..bb9eb9d 100644 --- a/scripts/sync-version.ts +++ b/scripts/sync-version.ts @@ -70,9 +70,8 @@ function syncVersionInTemplates(templateDir: string, newVersion: semver.SemVer): key === 'PackageResURL' && templateJSON.parameters[key].defaultValue !== undefined ) { - templateJSON.parameters[ - key - ].defaultValue = `https://github.com/fortinet/fortigate-autoscale-azure/releases/download/${verStr}/fortigate-autoscale-azure-funcapp.zip`; + templateJSON.parameters[key].defaultValue = + `https://github.com/fortinet/fortigate-autoscale-azure/releases/download/${verStr}/fortigate-autoscale-azure-funcapp.zip`; console.log( `Function app zip file url: ${chalk.green( templateJSON.parameters[key].defaultValue @@ -91,7 +90,8 @@ function syncVersionInTemplates(templateDir: string, newVersion: semver.SemVer): deployment.properties.templateLink && deployment.properties.templateLink.uri ) { - const regex = /(?<=https:\/\/raw\.githubusercontent\.com\/fortinet\/fortigate-autoscale-azure\/)(\S*)\/templates\/\S*\.json$/; + const regex = + /(?<=https:\/\/raw\.githubusercontent\.com\/fortinet\/fortigate-autoscale-azure\/)(\S*)\/templates\/\S*\.json$/; const uri = String(deployment.properties.templateLink.uri); const [, ref] = uri.match(regex) || []; if (ref) { @@ -129,8 +129,10 @@ function syncVersionOnReadMe(newVersion: semver.SemVer): void { const filePath = path.resolve(rootDir, './README.md'); console.log(`updating ${filePath}`); const readmeContent = fs.readFileSync(filePath).toString(); - const regexUrl = /(?<=https:\/\/raw\.githubusercontent\.com\/fortinet\/fortigate-autoscale-azure\/)(\S*)\/templates\/\S*\.json/; - const regexEncodedUrl = /(?<=https%3A%2F%2Fraw\.githubusercontent\.com%2Ffortinet%2Ffortigate-autoscale-azure%2F)(\S*)%2Ftemplates%2F\S*\.json/; + const regexUrl = + /(?<=https:\/\/raw\.githubusercontent\.com\/fortinet\/fortigate-autoscale-azure\/)(\S*)\/templates\/\S*\.json/; + const regexEncodedUrl = + /(?<=https%3A%2F%2Fraw\.githubusercontent\.com%2Ffortinet%2Ffortigate-autoscale-azure%2F)(\S*)%2Ftemplates%2F\S*\.json/; let [place, currentVersion] = readmeContent.match(regexUrl) || []; if (!currentVersion) { [place, currentVersion] = readmeContent.match(regexEncodedUrl) || []; diff --git a/scripts/template-updater.ts b/scripts/template-updater.ts index caf7c0a..338efc8 100644 --- a/scripts/template-updater.ts +++ b/scripts/template-updater.ts @@ -18,7 +18,8 @@ function readJSONTemplate(filePath: string): JSONable { try { return contents !== null && JSON.parse(contents); } catch (error) { - (error as Error).message = `error in parsing content into JSON object from file: ${filePath}.`; + (error as Error).message = + `error in parsing content into JSON object from file: ${filePath}.`; throw error; } } @@ -35,7 +36,8 @@ function extractVersions(filePath: string): string[] { try { items = contents !== null && JSON.parse(contents); } catch (error) { - (error as Error).message = `error in parsing content into JSON object, content: ${contents}.`; + (error as Error).message = + `error in parsing content into JSON object, content: ${contents}.`; throw error; } if (!Array.isArray(items)) { diff --git a/templates/deploy_fortigate_autoscale.hybrid_licensing.json b/templates/deploy_fortigate_autoscale.hybrid_licensing.json index 02bf8cc..b779bc1 100644 --- a/templates/deploy_fortigate_autoscale.hybrid_licensing.json +++ b/templates/deploy_fortigate_autoscale.hybrid_licensing.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "AccessRestrictionIPRange": { "type": "String", @@ -242,7 +242,7 @@ }, "PackageResURL": { "type": "String", - "defaultValue": "https://github.com/fortinet/fortigate-autoscale-azure/releases/download/3.5.2/fortigate-autoscale-azure-funcapp.zip", + "defaultValue": "https://github.com/fortinet/fortigate-autoscale-azure/releases/download/3.6.0/fortigate-autoscale-azure-funcapp.zip", "metadata": { "description": "Public URL of the Autoscale Function App source file fortigate-autoscale-azure-funcapp.zip. This URL must be accessible by Azure. The default value points to the source file available in the release assets of the GitHub repo fortinet/fortigate-autoscale-azure." } @@ -616,7 +616,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.database.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.database.json" }, "parameters": { "DatabaseAccountName": { @@ -647,7 +647,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.vnet.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.vnet.json" }, "parameters": { "Subnets": { @@ -675,7 +675,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.vnet.configuration.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.vnet.configuration.json" }, "parameters": { "NetworkSecurityGroupRulesSubnet1Inbound": { @@ -715,7 +715,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.load_balancer.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.load_balancer.json" }, "parameters": { "ExternalLBBackendPoolNames": { @@ -762,7 +762,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.faz_integration.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.faz_integration.json" }, "parameters": { "AdminPassword": { @@ -808,17 +808,17 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.function_app.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.function_app.json" }, "parameters": { "FunctionAppName": { "value": "[variables('functionAppName')]" }, "FunctionExtensionVersion": { - "value": "~3" + "value": "~4" }, "NodeJSRuntimeVersion": { - "value": "~12" + "value": "~18" }, "PackageResURL": { "value": "[parameters('PackageResURL')]" @@ -845,7 +845,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.vmss.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.vmss.json" }, "parameters": { "AdminPassword": { @@ -956,7 +956,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.database.access_restriction.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.database.access_restriction.json" }, "parameters": { "AllowAccessFromSubnetIds": { @@ -987,7 +987,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.function_app.setup.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.function_app.setup.json" }, "parameters": { "AdminPortNumber": { @@ -1149,7 +1149,7 @@ }, "deploymentPackageVersion": { "type": "string", - "value": "3.5.2" + "value": "3.6.0" }, "externalLBInboundNatPortRanges": { "type": "Array", diff --git a/templates/deploy_fortigate_autoscale.hybrid_licensing.params.json b/templates/deploy_fortigate_autoscale.hybrid_licensing.params.json index f398a45..6b2caa4 100644 --- a/templates/deploy_fortigate_autoscale.hybrid_licensing.params.json +++ b/templates/deploy_fortigate_autoscale.hybrid_licensing.params.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "AccessRestrictionIPRange": { "value": "" diff --git a/templates/link_template.database.access_restriction.json b/templates/link_template.database.access_restriction.json index 0a7b52b..79bb026 100644 --- a/templates/link_template.database.access_restriction.json +++ b/templates/link_template.database.access_restriction.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "AllowAccessFromSubnetIds": { "type": "array" diff --git a/templates/link_template.database.json b/templates/link_template.database.json index 1623736..5e6b13b 100644 --- a/templates/link_template.database.json +++ b/templates/link_template.database.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "DatabaseAccountName": { "type": "String" diff --git a/templates/link_template.faz_integration.json b/templates/link_template.faz_integration.json index a81fbd0..651d509 100644 --- a/templates/link_template.faz_integration.json +++ b/templates/link_template.faz_integration.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "AdminPassword": { "type": "SecureString" diff --git a/templates/link_template.function_app.access_restriction.json b/templates/link_template.function_app.access_restriction.json index 81a0901..66f1a19 100644 --- a/templates/link_template.function_app.access_restriction.json +++ b/templates/link_template.function_app.access_restriction.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "AllowAccessFromIPAddresses": { "type": "array" diff --git a/templates/link_template.function_app.configuration.json b/templates/link_template.function_app.configuration.json index c10c020..3bc819d 100644 --- a/templates/link_template.function_app.configuration.json +++ b/templates/link_template.function_app.configuration.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "AdminPortNumber": { "type": "Int" @@ -150,7 +150,7 @@ }, { "name": "FUNCTIONS_EXTENSION_VERSION", - "value": "~3" + "value": "~4" }, { "name": "FUNCTIONS_WORKER_RUNTIME", @@ -166,7 +166,7 @@ }, { "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "~12" + "value": "~18" }, { "name": "WEBSITE_RUN_FROM_PACKAGE", diff --git a/templates/link_template.function_app.json b/templates/link_template.function_app.json index 7ef8845..f4b7a1a 100644 --- a/templates/link_template.function_app.json +++ b/templates/link_template.function_app.json @@ -1,17 +1,17 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "FunctionAppName": { "type": "String" }, "FunctionExtensionVersion": { "type": "String", - "defaultValue": "~3" + "defaultValue": "~4" }, "NodeJSRuntimeVersion": { "type": "String", - "defaultValue": "~12" + "defaultValue": "~18" }, "PackageResURL": { "type": "String" diff --git a/templates/link_template.function_app.setup.json b/templates/link_template.function_app.setup.json index b292cd9..b57334c 100644 --- a/templates/link_template.function_app.setup.json +++ b/templates/link_template.function_app.setup.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "AdminPortNumber": { "type": "Int" @@ -130,7 +130,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.function_app.configuration.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.function_app.configuration.json" }, "parameters": { "AdminPortNumber": { @@ -263,7 +263,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/link_template.function_app.access_restriction.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/link_template.function_app.access_restriction.json" }, "parameters": { "AllowAccessFromIPAddresses": { diff --git a/templates/link_template.load_balancer.json b/templates/link_template.load_balancer.json index 9ce6b7a..ae90e69 100644 --- a/templates/link_template.load_balancer.json +++ b/templates/link_template.load_balancer.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "ExternalLBBackendPoolNames": { "type": "Array" diff --git a/templates/link_template.vmss.json b/templates/link_template.vmss.json index 72f19dd..95d79fa 100644 --- a/templates/link_template.vmss.json +++ b/templates/link_template.vmss.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "AdminPassword": { "type": "SecureString" diff --git a/templates/link_template.vnet.configuration.json b/templates/link_template.vnet.configuration.json index 03d4038..6014f0f 100644 --- a/templates/link_template.vnet.configuration.json +++ b/templates/link_template.vnet.configuration.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "NetworkSecurityGroupRulesSubnet1Inbound": { "type": "Array" @@ -80,7 +80,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/template_function.join_string_array.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/template_function.join_string_array.json" }, "parameters": { "InputString": { diff --git a/templates/link_template.vnet.json b/templates/link_template.vnet.json index 96dc585..e292cfd 100644 --- a/templates/link_template.vnet.json +++ b/templates/link_template.vnet.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "Subnets": { "type": "array", @@ -79,7 +79,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/template_function.join_string_array.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/template_function.join_string_array.json" }, "parameters": { "InputString": { diff --git a/templates/template_function.join_string_array.json b/templates/template_function.join_string_array.json index 49ebc25..e338aa5 100644 --- a/templates/template_function.join_string_array.json +++ b/templates/template_function.join_string_array.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "3.5.2.0", + "contentVersion": "3.6.0.0", "parameters": { "InputString": { "defaultValue": "", @@ -32,7 +32,7 @@ "properties": { "mode": "Incremental", "templateLink": { - "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.5.2/templates/template_function.join_string_array.json" + "uri": "https://raw.githubusercontent.com/fortinet/fortigate-autoscale-azure/3.6.0/templates/template_function.join_string_array.json" }, "parameters": { "InputString": {