1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-31 13:47:02 +02:00

Merge branch 'main' into melinda/upgrade-open-api-plugin

This commit is contained in:
Alvin Bryan 2024-10-18 14:13:36 +01:00
commit 74b13374d7
No known key found for this signature in database
30 changed files with 1690 additions and 1199 deletions

View File

@ -12,6 +12,7 @@ import { ADMIN } from '@server/types/permissions';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature'; import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
import { useState } from 'react'; import { useState } from 'react';
import { TabPanel } from 'component/common/TabNav/TabPanel/TabPanel'; import { TabPanel } from 'component/common/TabNav/TabPanel/TabPanel';
import { usePageTitle } from 'hooks/usePageTitle';
export const AuthSettings = () => { export const AuthSettings = () => {
const { authenticationType } = useUiConfig().uiConfig; const { authenticationType } = useUiConfig().uiConfig;
@ -46,6 +47,7 @@ export const AuthSettings = () => {
} }
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
usePageTitle(`Single sign-on: ${tabs[activeTab].label}`);
return ( return (
<div> <div>

View File

@ -14,14 +14,14 @@ describe('trim names and description', () => {
expect(result.current.name).toBe('my role'); expect(result.current.name).toBe('my role');
}); });
test('description is trimmed before being set', () => { test('description is not trimmed before being set', () => {
const { result } = renderHook(() => useRoleForm()); const { result } = renderHook(() => useRoleForm());
act(() => { act(() => {
result.current.setDescription(' my description '); result.current.setDescription(' my description ');
}); });
expect(result.current.description).toBe('my description'); expect(result.current.description).toBe(' my description ');
}); });
test('name that is just whitespace triggers an error', () => { test('name that is just whitespace triggers an error', () => {

View File

@ -29,8 +29,6 @@ export const useRoleForm = (
const [name, setName] = useState(initialName); const [name, setName] = useState(initialName);
const setTrimmedName = (newName: string) => setName(newName.trim()); const setTrimmedName = (newName: string) => setName(newName.trim());
const [description, setDescription] = useState(initialDescription); const [description, setDescription] = useState(initialDescription);
const setTrimmedDescription = (newDescription: string) =>
setDescription(newDescription.trim());
const [checkedPermissions, setCheckedPermissions] = const [checkedPermissions, setCheckedPermissions] =
useState<ICheckedPermissions>({}); useState<ICheckedPermissions>({});
const [errors, setErrors] = useState<IRoleFormErrors>(DEFAULT_ERRORS); const [errors, setErrors] = useState<IRoleFormErrors>(DEFAULT_ERRORS);
@ -147,7 +145,7 @@ export const useRoleForm = (
setName: setTrimmedName, setName: setTrimmedName,
validateName, validateName,
description, description,
setDescription: setTrimmedDescription, setDescription,
validateDescription, validateDescription,
checkedPermissions, checkedPermissions,
setCheckedPermissions, setCheckedPermissions,

View File

@ -14,6 +14,7 @@ import Add from '@mui/icons-material/Add';
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
import type { IRole } from 'interfaces/role'; import type { IRole } from 'interfaces/role';
import { TabLink } from 'component/common/TabNav/TabLink'; import { TabLink } from 'component/common/TabNav/TabLink';
import { usePageTitle } from 'hooks/usePageTitle';
const StyledHeader = styled('div')(() => ({ const StyledHeader = styled('div')(() => ({
display: 'flex', display: 'flex',
@ -31,6 +32,7 @@ const StyledActions = styled('div')({
}); });
export const RolesPage = () => { export const RolesPage = () => {
usePageTitle('Roles');
const { pathname } = useLocation(); const { pathname } = useLocation();
const { roles, projectRoles, loading } = useRoles(); const { roles, projectRoles, loading } = useRoles();

View File

@ -5,7 +5,7 @@ import { sortTypes } from 'utils/sortTypes';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes'; import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { Box, Typography } from '@mui/material'; import { Box } from '@mui/material';
import { import {
Table, Table,
TableBody, TableBody,
@ -150,18 +150,7 @@ export const FeatureTypesList = () => {
return ( return (
<PageContent <PageContent
isLoading={loading} isLoading={loading}
header={ header={<PageHeader title='Feature flag types' />}
<PageHeader>
<Typography
component='h2'
sx={(theme) => ({
fontSize: theme.fontSizes.mainHeader,
})}
>
Feature flag types
</Typography>
</PageHeader>
}
> >
<Table {...getTableProps()}> <Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups} /> <SortableTableHeader headerGroups={headerGroups} />

View File

@ -69,6 +69,7 @@ export const InsightsHeader: VFC<DashboardHeaderProps> = ({ actions }) => {
return ( return (
<> <>
<PageHeader <PageHeader
title='Insights'
titleElement={ titleElement={
<Typography <Typography
variant='h1' variant='h1'

View File

@ -27,6 +27,8 @@ import BillingIcon from '@mui/icons-material/CreditCardOutlined';
import EventLogIcon from '@mui/icons-material/EventNoteOutlined'; import EventLogIcon from '@mui/icons-material/EventNoteOutlined';
import GitHubIcon from '@mui/icons-material/GitHub'; import GitHubIcon from '@mui/icons-material/GitHub';
import LibraryBooksIcon from '@mui/icons-material/LibraryBooks'; import LibraryBooksIcon from '@mui/icons-material/LibraryBooks';
import LaunchIcon from '@mui/icons-material/Launch';
import PersonalDashboardIcon from '@mui/icons-material/DashboardOutlined';
import type { FC } from 'react'; import type { FC } from 'react';
// TODO: move to routes // TODO: move to routes
@ -56,6 +58,8 @@ const icons: Record<string, typeof SvgIcon> = {
'/admin/cors': CorsIcon, '/admin/cors': CorsIcon,
'/admin/billing': BillingIcon, '/admin/billing': BillingIcon,
'/history': EventLogIcon, '/history': EventLogIcon,
'/releases-management': LaunchIcon,
'/personal': PersonalDashboardIcon,
GitHub: GitHubIcon, GitHub: GitHubIcon,
Documentation: LibraryBooksIcon, Documentation: LibraryBooksIcon,
}; };

View File

@ -230,6 +230,20 @@ exports[`returns all baseRoutes 1`] = `
"title": "Strategy types", "title": "Strategy types",
"type": "protected", "type": "protected",
}, },
{
"component": [Function],
"enterprise": true,
"flag": "releasePlans",
"menu": {
"advanced": true,
"mode": [
"enterprise",
],
},
"path": "/releases-management",
"title": "Release management",
"type": "protected",
},
{ {
"component": [Function], "component": [Function],
"menu": {}, "menu": {},

View File

@ -48,6 +48,7 @@ import { Application } from 'component/application/Application';
import { Signals } from 'component/signals/Signals'; import { Signals } from 'component/signals/Signals';
import { LazyCreateProject } from '../project/Project/CreateProject/LazyCreateProject'; import { LazyCreateProject } from '../project/Project/CreateProject/LazyCreateProject';
import { PersonalDashboard } from '../personalDashboard/PersonalDashboard'; import { PersonalDashboard } from '../personalDashboard/PersonalDashboard';
import { ReleaseManagement } from 'component/releases/ReleaseManagement';
export const routes: IRoute[] = [ export const routes: IRoute[] = [
// Splash // Splash
@ -246,6 +247,15 @@ export const routes: IRoute[] = [
type: 'protected', type: 'protected',
menu: { mobile: true, advanced: true }, menu: { mobile: true, advanced: true },
}, },
{
path: '/releases-management',
title: 'Release management',
component: ReleaseManagement,
type: 'protected',
menu: { advanced: true, mode: ['enterprise'] },
flag: 'releasePlans',
enterprise: true,
},
{ {
path: '/environments/create', path: '/environments/create',
title: 'Environments', title: 'Environments',

View File

@ -19,6 +19,7 @@ import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi';
import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash'; import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
import { useDashboardState } from './useDashboardState'; import { useDashboardState } from './useDashboardState';
import { MyFlags } from './MyFlags'; import { MyFlags } from './MyFlags';
import { usePageTitle } from 'hooks/usePageTitle';
const WelcomeSection = styled('div')(({ theme }) => ({ const WelcomeSection = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
@ -103,9 +104,10 @@ export const PersonalDashboard = () => {
const { trackEvent } = usePlausibleTracker(); const { trackEvent } = usePlausibleTracker();
const { setSplashSeen } = useSplashApi(); const { setSplashSeen } = useSplashApi();
const { splash } = useAuthSplash(); const { splash } = useAuthSplash();
const name = user?.name; const name = user?.name;
usePageTitle(`Dashboard: ${name}`);
const { personalDashboard, refetch: refetchDashboard } = const { personalDashboard, refetch: refetchDashboard } =
usePersonalDashboard(); usePersonalDashboard();

View File

@ -0,0 +1,3 @@
export const ReleaseManagement = () => {
return null;
};

View File

@ -397,9 +397,10 @@ export const StrategiesList = () => {
<PageContent <PageContent
isLoading={loading} isLoading={loading}
header={ header={
<PageHeader> <PageHeader
<PredefinedStrategyTitle /> titleElement={<PredefinedStrategyTitle />}
</PageHeader> title='Strategy types'
/>
} }
> >
<Box> <Box>

View File

@ -92,6 +92,7 @@ export type UiFlags = {
personalDashboardUI?: boolean; personalDashboardUI?: boolean;
purchaseAdditionalEnvironments?: boolean; purchaseAdditionalEnvironments?: boolean;
unleashAI?: boolean; unleashAI?: boolean;
releasePlans?: boolean;
}; };
export interface IVersionInfo { export interface IVersionInfo {

View File

@ -2743,20 +2743,20 @@ __metadata:
linkType: hard linkType: hard
"@types/node@npm:*": "@types/node@npm:*":
version: 22.5.5 version: 22.7.6
resolution: "@types/node@npm:22.5.5" resolution: "@types/node@npm:22.7.6"
dependencies: dependencies:
undici-types: "npm:~6.19.2" undici-types: "npm:~6.19.2"
checksum: 10c0/ead9495cfc6b1da5e7025856dcce2591e9bae635357410c0d2dd619fce797d2a1d402887580ca4b336cb78168b195224869967de370a23f61663cf1e4836121c checksum: 10c0/d4406a63afce981c363fb1d1954aaf1759ad2d487c0833ebf667565ea4e45ff217d6fab4b5343badbdeccdf9d2e4a0841d633e0c929ceabcb33c288663dd0c73
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node@npm:^20.12.12, @types/node@npm:^20.12.13": "@types/node@npm:^20.12.12, @types/node@npm:^20.12.13":
version: 20.16.5 version: 20.16.12
resolution: "@types/node@npm:20.16.5" resolution: "@types/node@npm:20.16.12"
dependencies: dependencies:
undici-types: "npm:~6.19.2" undici-types: "npm:~6.19.2"
checksum: 10c0/6af7994129815010bcbc4cf8221865559c8116ff43e74a6549525c2108267596fc2d18aff5d5ecfe089fb60a119f975631343e2c65c52bfa0955ed9dc56733d6 checksum: 10c0/f6a3c90c6745881d47f8dae7eb39d0dd6df9a4060c8f1ab7004690f60b9b183d862c9c6b380b9e8d5a17dd670ac7b19d6318f24c170897c85a9729f9804f47cf
languageName: node languageName: node
linkType: hard linkType: hard

View File

@ -173,10 +173,10 @@
}, },
"devDependencies": { "devDependencies": {
"@apidevtools/swagger-parser": "10.1.0", "@apidevtools/swagger-parser": "10.1.0",
"@babel/core": "7.25.2", "@babel/core": "7.25.8",
"@biomejs/biome": "^1.8.3", "@biomejs/biome": "^1.8.3",
"@cyclonedx/yarn-plugin-cyclonedx": "^1.0.0-rc.7", "@cyclonedx/yarn-plugin-cyclonedx": "^1.0.0-rc.7",
"@swc/core": "1.7.26", "@swc/core": "1.7.35",
"@swc/jest": "0.2.36", "@swc/jest": "0.2.36",
"@types/bcryptjs": "2.4.6", "@types/bcryptjs": "2.4.6",
"@types/cors": "2.8.17", "@types/cors": "2.8.17",

View File

@ -4,11 +4,18 @@ import createStores from '../../../test/fixtures/store';
import VersionService from '../../services/version-service'; import VersionService from '../../services/version-service';
import { createFakeGetActiveUsers } from './getActiveUsers'; import { createFakeGetActiveUsers } from './getActiveUsers';
import { createFakeGetProductionChanges } from './getProductionChanges'; import { createFakeGetProductionChanges } from './getProductionChanges';
import { registerPrometheusMetrics } from '../../metrics';
import { register } from 'prom-client';
import type { IClientInstanceStore } from '../../types';
let instanceStatsService: InstanceStatsService; let instanceStatsService: InstanceStatsService;
let versionService: VersionService; let versionService: VersionService;
let clientInstanceStore: IClientInstanceStore;
let updateMetrics: () => Promise<void>;
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks();
register.clear();
const config = createTestConfig(); const config = createTestConfig();
const stores = createStores(); const stores = createStores();
versionService = new VersionService( versionService = new VersionService(
@ -17,6 +24,7 @@ beforeEach(() => {
createFakeGetActiveUsers(), createFakeGetActiveUsers(),
createFakeGetProductionChanges(), createFakeGetProductionChanges(),
); );
clientInstanceStore = stores.clientInstanceStore;
instanceStatsService = new InstanceStatsService( instanceStatsService = new InstanceStatsService(
stores, stores,
config, config,
@ -25,23 +33,28 @@ beforeEach(() => {
createFakeGetProductionChanges(), createFakeGetProductionChanges(),
); );
jest.spyOn(instanceStatsService, 'refreshAppCountSnapshot'); const { collectDbMetrics } = registerPrometheusMetrics(
jest.spyOn(instanceStatsService, 'getLabeledAppCounts'); config,
stores,
undefined as unknown as string,
config.eventBus,
instanceStatsService,
);
updateMetrics = collectDbMetrics;
jest.spyOn(clientInstanceStore, 'getDistinctApplicationsCount');
jest.spyOn(instanceStatsService, 'getStats'); jest.spyOn(instanceStatsService, 'getStats');
// validate initial state without calls to these methods
expect(instanceStatsService.refreshAppCountSnapshot).toHaveBeenCalledTimes(
0,
);
expect(instanceStatsService.getStats).toHaveBeenCalledTimes(0); expect(instanceStatsService.getStats).toHaveBeenCalledTimes(0);
}); });
test('get snapshot should not call getStats', async () => { test('get snapshot should not call getStats', async () => {
await instanceStatsService.refreshAppCountSnapshot(); await updateMetrics();
expect(instanceStatsService.getLabeledAppCounts).toHaveBeenCalledTimes(1); expect(
clientInstanceStore.getDistinctApplicationsCount,
).toHaveBeenCalledTimes(3);
expect(instanceStatsService.getStats).toHaveBeenCalledTimes(0); expect(instanceStatsService.getStats).toHaveBeenCalledTimes(0);
// subsequent calls to getStatsSnapshot don't call getStats
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
const { clientApps } = await instanceStatsService.getStats(); const { clientApps } = await instanceStatsService.getStats();
expect(clientApps).toStrictEqual([ expect(clientApps).toStrictEqual([
@ -51,12 +64,11 @@ test('get snapshot should not call getStats', async () => {
]); ]);
} }
// after querying the stats snapshot no call to getStats should be issued // after querying the stats snapshot no call to getStats should be issued
expect(instanceStatsService.getLabeledAppCounts).toHaveBeenCalledTimes(1); expect(
clientInstanceStore.getDistinctApplicationsCount,
).toHaveBeenCalledTimes(3);
}); });
test('before the snapshot is refreshed we can still get the appCount', async () => { test('before the snapshot is refreshed we can still get the appCount', async () => {
expect(instanceStatsService.refreshAppCountSnapshot).toHaveBeenCalledTimes(
0,
);
expect(instanceStatsService.getAppCountSnapshot('7d')).toBeUndefined(); expect(instanceStatsService.getAppCountSnapshot('7d')).toBeUndefined();
}); });

View File

@ -109,9 +109,9 @@ export class InstanceStatsService {
private appCount?: Partial<{ [key in TimeRange]: number }>; private appCount?: Partial<{ [key in TimeRange]: number }>;
private getActiveUsers: GetActiveUsers; getActiveUsers: GetActiveUsers;
private getProductionChanges: GetProductionChanges; getProductionChanges: GetProductionChanges;
private featureStrategiesReadModel: IFeatureStrategiesReadModel; private featureStrategiesReadModel: IFeatureStrategiesReadModel;
@ -180,25 +180,6 @@ export class InstanceStatsService {
this.featureStrategiesReadModel = featureStrategiesReadModel; this.featureStrategiesReadModel = featureStrategiesReadModel;
} }
async refreshAppCountSnapshot(): Promise<
Partial<{ [key in TimeRange]: number }>
> {
try {
this.appCount = await this.getLabeledAppCounts();
return this.appCount;
} catch (error) {
this.logger.warn(
'Unable to retrieve statistics. This will be retried',
error,
);
return {
'7d': 0,
'30d': 0,
allTime: 0,
};
}
}
getProjectModeCount(): Promise<ProjectModeCount[]> { getProjectModeCount(): Promise<ProjectModeCount[]> {
return this.projectStore.getProjectModeCounts(); return this.projectStore.getProjectModeCounts();
} }
@ -231,9 +212,6 @@ export class InstanceStatsService {
return settings?.enabled || false; return settings?.enabled || false;
} }
/**
* use getStatsSnapshot for low latency, sacrificing data-freshness
*/
async getStats(): Promise<InstanceStats> { async getStats(): Promise<InstanceStats> {
const versionInfo = await this.versionService.getVersionInfo(); const versionInfo = await this.versionService.getVersionInfo();
const [ const [
@ -265,22 +243,22 @@ export class InstanceStatsService {
] = await Promise.all([ ] = await Promise.all([
this.getToggleCount(), this.getToggleCount(),
this.getArchivedToggleCount(), this.getArchivedToggleCount(),
this.userStore.count(), this.getRegisteredUsers(),
this.userStore.countServiceAccounts(), this.countServiceAccounts(),
this.apiTokenStore.countByType(), this.countApiTokensByType(),
this.getActiveUsers(), this.getActiveUsers(),
this.getProjectModeCount(), this.getProjectModeCount(),
this.contextFieldStore.count(), this.contextFieldCount(),
this.groupStore.count(), this.groupCount(),
this.roleStore.count(), this.roleCount(),
this.roleStore.filteredCount({ type: CUSTOM_ROOT_ROLE_TYPE }), this.customRolesCount(),
this.roleStore.filteredCountInUse({ type: CUSTOM_ROOT_ROLE_TYPE }), this.customRolesCountInUse(),
this.environmentStore.count(), this.environmentCount(),
this.segmentStore.count(), this.segmentCount(),
this.strategyStore.count(), this.strategiesCount(),
this.hasSAML(), this.hasSAML(),
this.hasOIDC(), this.hasOIDC(),
this.appCount ? this.appCount : this.refreshAppCountSnapshot(), this.appCount ? this.appCount : this.getLabeledAppCounts(),
this.eventStore.deprecatedFilteredCount({ this.eventStore.deprecatedFilteredCount({
type: FEATURES_EXPORTED, type: FEATURES_EXPORTED,
}), }),
@ -288,7 +266,7 @@ export class InstanceStatsService {
type: FEATURES_IMPORTED, type: FEATURES_IMPORTED,
}), }),
this.getProductionChanges(), this.getProductionChanges(),
this.clientMetricsStore.countPreviousDayHourlyMetricsBuckets(), this.countPreviousDayHourlyMetricsBuckets(),
this.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(), this.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(),
this.featureStrategiesReadModel.getMaxConstraintValues(), this.featureStrategiesReadModel.getMaxConstraintValues(),
this.featureStrategiesReadModel.getMaxConstraintsPerStrategy(), this.featureStrategiesReadModel.getMaxConstraintsPerStrategy(),
@ -330,6 +308,59 @@ export class InstanceStatsService {
}; };
} }
groupCount(): Promise<number> {
return this.groupStore.count();
}
roleCount(): Promise<number> {
return this.roleStore.count();
}
customRolesCount(): Promise<number> {
return this.roleStore.filteredCount({ type: CUSTOM_ROOT_ROLE_TYPE });
}
customRolesCountInUse(): Promise<number> {
return this.roleStore.filteredCountInUse({
type: CUSTOM_ROOT_ROLE_TYPE,
});
}
segmentCount(): Promise<number> {
return this.segmentStore.count();
}
contextFieldCount(): Promise<number> {
return this.contextFieldStore.count();
}
strategiesCount(): Promise<number> {
return this.strategyStore.count();
}
environmentCount(): Promise<number> {
return this.environmentStore.count();
}
countPreviousDayHourlyMetricsBuckets(): Promise<{
enabledCount: number;
variantCount: number;
}> {
return this.clientMetricsStore.countPreviousDayHourlyMetricsBuckets();
}
countApiTokensByType(): Promise<Map<string, number>> {
return this.apiTokenStore.countByType();
}
getRegisteredUsers(): Promise<number> {
return this.userStore.count();
}
countServiceAccounts(): Promise<number> {
return this.userStore.countServiceAccounts();
}
async getLabeledAppCounts(): Promise< async getLabeledAppCounts(): Promise<
Partial<{ [key in TimeRange]: number }> Partial<{ [key in TimeRange]: number }>
> { > {
@ -338,11 +369,12 @@ export class InstanceStatsService {
this.clientInstanceStore.getDistinctApplicationsCount(30), this.clientInstanceStore.getDistinctApplicationsCount(30),
this.clientInstanceStore.getDistinctApplicationsCount(), this.clientInstanceStore.getDistinctApplicationsCount(),
]); ]);
return { this.appCount = {
'7d': t7d, '7d': t7d,
'30d': t30d, '30d': t30d,
allTime, allTime,
}; };
return this.appCount;
} }
getAppCountSnapshot(range: TimeRange): number | undefined { getAppCountSnapshot(range: TimeRange): number | undefined {

View File

@ -59,8 +59,12 @@ export const scheduleServices = async (
'updateLastSeen', 'updateLastSeen',
); );
// TODO this works fine for keeping labeledAppCounts up to date, but
// it would be nice if we can keep client_apps_total prometheus metric
// up to date. We'd need to have access to DbMetricsMonitor, which is
// where the metric is registered and call an update only for that metric
schedulerService.schedule( schedulerService.schedule(
instanceStatsService.refreshAppCountSnapshot.bind(instanceStatsService), instanceStatsService.getLabeledAppCounts.bind(instanceStatsService),
minutesToMilliseconds(5), minutesToMilliseconds(5),
'refreshAppCountSnapshot', 'refreshAppCountSnapshot',
); );

View File

@ -0,0 +1,114 @@
import { register } from 'prom-client';
import { createTestConfig } from '../test/config/test-config';
import type { IUnleashConfig } from './types';
import { DbMetricsMonitor } from './metrics-gauge';
const prometheusRegister = register;
let config: IUnleashConfig;
let dbMetrics: DbMetricsMonitor;
beforeAll(async () => {
config = createTestConfig({
server: {
serverMetrics: true,
},
});
});
beforeEach(async () => {
dbMetrics = new DbMetricsMonitor(config);
});
test('should collect registered metrics', async () => {
dbMetrics.registerGaugeDbMetric({
name: 'my_metric',
help: 'This is the answer to life, the univers, and everything',
labelNames: [],
query: () => Promise.resolve(42),
map: (result) => ({ value: result }),
});
await dbMetrics.refreshDbMetrics();
const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(/my_metric 42/);
});
test('should collect registered metrics with labels', async () => {
dbMetrics.registerGaugeDbMetric({
name: 'life_the_universe_and_everything',
help: 'This is the answer to life, the univers, and everything',
labelNames: ['test'],
query: () => Promise.resolve(42),
map: (result) => ({ value: result, labels: { test: 'case' } }),
});
await dbMetrics.refreshDbMetrics();
const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(
/life_the_universe_and_everything\{test="case"\} 42/,
);
});
test('should collect multiple registered metrics with and without labels', async () => {
dbMetrics.registerGaugeDbMetric({
name: 'my_first_metric',
help: 'This is the answer to life, the univers, and everything',
labelNames: [],
query: () => Promise.resolve(42),
map: (result) => ({ value: result }),
});
dbMetrics.registerGaugeDbMetric({
name: 'my_other_metric',
help: 'This is Eulers number',
labelNames: ['euler'],
query: () => Promise.resolve(Math.E),
map: (result) => ({ value: result, labels: { euler: 'number' } }),
});
await dbMetrics.refreshDbMetrics();
const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(/my_first_metric 42/);
expect(metrics).toMatch(/my_other_metric\{euler="number"\} 2.71828/);
});
test('should support different label and value pairs', async () => {
dbMetrics.registerGaugeDbMetric({
name: 'multi_dimensional',
help: 'This metric has different values for different labels',
labelNames: ['version', 'range'],
query: () => Promise.resolve(2),
map: (result) => [
{ value: result, labels: { version: '1', range: 'linear' } },
{
value: result * result,
labels: { version: '2', range: 'square' },
},
{ value: result / 2, labels: { version: '3', range: 'half' } },
],
});
await dbMetrics.refreshDbMetrics();
const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(
/multi_dimensional\{version="1",range="linear"\} 2\nmulti_dimensional\{version="2",range="square"\} 4\nmulti_dimensional\{version="3",range="half"\} 1/,
);
expect(
await dbMetrics.findValue('multi_dimensional', { range: 'linear' }),
).toBe(2);
expect(
await dbMetrics.findValue('multi_dimensional', { range: 'half' }),
).toBe(1);
expect(
await dbMetrics.findValue('multi_dimensional', { range: 'square' }),
).toBe(4);
expect(
await dbMetrics.findValue('multi_dimensional', { range: 'x' }),
).toBeUndefined();
expect(await dbMetrics.findValue('multi_dimensional')).toBe(2); // first match
expect(await dbMetrics.findValue('other')).toBeUndefined();
});

94
src/lib/metrics-gauge.ts Normal file
View File

@ -0,0 +1,94 @@
import type { Logger } from './logger';
import type { IUnleashConfig } from './types';
import { createGauge, type Gauge } from './util/metrics';
type Query<R> = () => Promise<R | undefined | null>;
type MetricValue<L extends string> = {
value: number;
labels?: Record<L, string | number>;
};
type MapResult<R, L extends string> = (
result: R,
) => MetricValue<L> | MetricValue<L>[];
type GaugeDefinition<T, L extends string> = {
name: string;
help: string;
labelNames: L[];
query: Query<T>;
map: MapResult<T, L>;
};
type Task = () => Promise<void>;
interface GaugeUpdater {
target: Gauge<string>;
task: Task;
}
export class DbMetricsMonitor {
private updaters: Map<string, GaugeUpdater> = new Map();
private log: Logger;
constructor({ getLogger }: Pick<IUnleashConfig, 'getLogger'>) {
this.log = getLogger('gauge-metrics');
}
private asArray<T>(value: T | T[]): T[] {
return Array.isArray(value) ? value : [value];
}
registerGaugeDbMetric<T, L extends string>(
definition: GaugeDefinition<T, L>,
): Task {
const gauge = createGauge(definition);
const task = async () => {
try {
const result = await definition.query();
if (result !== null && result !== undefined) {
const results = this.asArray(definition.map(result));
gauge.reset();
for (const r of results) {
if (r.labels) {
gauge.labels(r.labels).set(r.value);
} else {
gauge.set(r.value);
}
}
}
} catch (e) {
this.log.warn(`Failed to refresh ${definition.name}`, e);
}
};
this.updaters.set(definition.name, { target: gauge, task });
return task;
}
refreshDbMetrics = async () => {
const tasks = Array.from(this.updaters.entries()).map(
([name, updater]) => ({ name, task: updater.task }),
);
for (const { name, task } of tasks) {
this.log.debug(`Refreshing metric ${name}`);
await task();
}
};
async findValue(
name: string,
labels?: Record<string, string | number>,
): Promise<number | undefined> {
const gauge = await this.updaters.get(name)?.target.gauge?.get();
if (gauge && gauge.values.length > 0) {
const values = labels
? gauge.values.filter(({ labels: l }) => {
return Object.entries(labels).every(
([key, value]) => l[key] === value,
);
})
: gauge.values;
// return first value
return values.map(({ value }) => value).shift();
}
return undefined;
}
}

View File

@ -15,7 +15,11 @@ import {
FEATURE_UPDATED, FEATURE_UPDATED,
PROJECT_ENVIRONMENT_REMOVED, PROJECT_ENVIRONMENT_REMOVED,
} from './types/events'; } from './types/events';
import { createMetricsMonitor } from './metrics'; import {
createMetricsMonitor,
registerPrometheusMetrics,
registerPrometheusPostgresMetrics,
} from './metrics';
import createStores from '../test/fixtures/store'; import createStores from '../test/fixtures/store';
import { InstanceStatsService } from './features/instance-stats/instance-stats-service'; import { InstanceStatsService } from './features/instance-stats/instance-stats-service';
import VersionService from './services/version-service'; import VersionService from './services/version-service';
@ -46,6 +50,7 @@ let schedulerService: SchedulerService;
let featureLifeCycleStore: IFeatureLifecycleStore; let featureLifeCycleStore: IFeatureLifecycleStore;
let featureLifeCycleReadModel: IFeatureLifecycleReadModel; let featureLifeCycleReadModel: IFeatureLifecycleReadModel;
let db: ITestDb; let db: ITestDb;
let refreshDbMetrics: () => Promise<void>;
beforeAll(async () => { beforeAll(async () => {
const config = createTestConfig({ const config = createTestConfig({
@ -102,16 +107,16 @@ beforeAll(async () => {
}, },
}; };
await monitor.startMonitoring( const { collectDbMetrics, collectStaticCounters } =
config, registerPrometheusMetrics(
stores, config,
'4.0.0', stores,
eventBus, '4.0.0',
statsService, eventBus,
schedulerService, statsService,
// @ts-ignore - We don't want a full knex implementation for our tests, it's enough that it actually yields the numbers we want. );
metricsDbConf, refreshDbMetrics = collectDbMetrics;
); await collectStaticCounters();
}); });
afterAll(async () => { afterAll(async () => {
@ -212,6 +217,7 @@ test('should collect metrics for function timings', async () => {
}); });
test('should collect metrics for feature flag size', async () => { test('should collect metrics for feature flag size', async () => {
await refreshDbMetrics();
const metrics = await prometheusRegister.metrics(); const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(/feature_toggles_total\{version="(.*)"\} 0/); expect(metrics).toMatch(/feature_toggles_total\{version="(.*)"\} 0/);
}); });
@ -222,12 +228,13 @@ test('should collect metrics for archived feature flag size', async () => {
}); });
test('should collect metrics for total client apps', async () => { test('should collect metrics for total client apps', async () => {
await statsService.refreshAppCountSnapshot(); await refreshDbMetrics();
const metrics = await prometheusRegister.metrics(); const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(/client_apps_total\{range="(.*)"\} 0/); expect(metrics).toMatch(/client_apps_total\{range="(.*)"\} 0/);
}); });
test('Should collect metrics for database', async () => { test('Should collect metrics for database', async () => {
registerPrometheusPostgresMetrics(db.rawDatabase, eventBus, '15.0.0');
const metrics = await prometheusRegister.metrics(); const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(/db_pool_max/); expect(metrics).toMatch(/db_pool_max/);
expect(metrics).toMatch(/db_pool_min/); expect(metrics).toMatch(/db_pool_min/);

File diff suppressed because it is too large Load Diff

View File

@ -57,7 +57,7 @@ process.nextTick(async () => {
unleashAI: true, unleashAI: true,
webhookDomainLogging: true, webhookDomainLogging: true,
addonUsageMetrics: true, addonUsageMetrics: true,
releasePlans: true, releasePlans: false,
}, },
}, },
authentication: { authentication: {

View File

@ -6,10 +6,12 @@ import {
import getLogger from '../../../fixtures/no-logger'; import getLogger from '../../../fixtures/no-logger';
import type { IUnleashStores } from '../../../../lib/types'; import type { IUnleashStores } from '../../../../lib/types';
import { ApiTokenType } from '../../../../lib/types/models/api-token'; import { ApiTokenType } from '../../../../lib/types/models/api-token';
import { registerPrometheusMetrics } from '../../../../lib/metrics';
let app: IUnleashTest; let app: IUnleashTest;
let db: ITestDb; let db: ITestDb;
let stores: IUnleashStores; let stores: IUnleashStores;
let refreshDbMetrics: () => Promise<void>;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('instance_admin_api_serial', getLogger); db = await dbInit('instance_admin_api_serial', getLogger);
@ -26,6 +28,15 @@ beforeAll(async () => {
}, },
db.rawDatabase, db.rawDatabase,
); );
const { collectDbMetrics } = registerPrometheusMetrics(
app.config,
stores,
undefined as unknown as string,
app.config.eventBus,
app.services.instanceStatsService,
);
refreshDbMetrics = collectDbMetrics;
}); });
afterAll(async () => { afterAll(async () => {
@ -39,6 +50,8 @@ test('should return instance statistics', async () => {
createdByUserId: 9999, createdByUserId: 9999,
}); });
await refreshDbMetrics();
return app.request return app.request
.get('/api/admin/instance-admin/statistics') .get('/api/admin/instance-admin/statistics')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)

View File

@ -0,0 +1,235 @@
---
title: How to use feature flags with AI
slug: /feature-flag-tutorials/use-cases/ai
---
Hello,
Like many people in lots of tech companies today, you might be playing with generative AI and large language models (LLMs). You might even be integrating these AI technologies into your company's products, probably in the form of chatbots or content generators.
The main way to interact with LLMs today is via a set of APIs, usually either OpenAI, Anthropic or aggregators like Groq. Most of these APIs have similar parameters, like:
- `model`: The model and the specific version of the model to use.
- `prompt`: The prompt to give to the LLM.
Given how much these models vary in their capabilities, chances are you'll be testing multiple models, multiple versions of the same model, and multiple prompts.
This is where feature flags are super useful. They allow you to easily switch between different configurations. In this tutorial, well explore how to use feature flags with AI models.
We'll start with a basic chatbot. I know building a chatbot is a total cliché at this point, but it's a small, self-contained example that everyone knows how to interact with. You can take this example and apply it to any other use case where you need to interact with an LLM via an API.
First, clone the repo:
```sh
git clone https://github.com/alvinometric/feature-flags-ai
cd feature-flags-ai
```
Install the dependencies:
```sh
npm install
```
Copy the `.env.example` file to a new `.env` file, and add your OpenAI API key to it:
```sh
OPENAI_API_KEY=sk-...
```
Run the app:
```sh
npm run dev
```
You should now see a chatbot UI, like this:
![chatbot UI](./sveltekit-chatbot.png)
This is a simple SvelteKit app with a chatbot interface. If youre not familiar with SvelteKit, it works similarly to frameworks like Next.js, Nuxt, or SolidStart, where your file structure defines the routing.
The most important file for this tutorial is the `src/routes/api/chat/+server.js` file.
It creates an API endpoint at `/api/chat`. When your frontend sends a POST request to `/api/chat`, this is the code that handles the request.
```javascript
import { createOpenAI } from "@ai-sdk/openai";
import { streamText } from "ai";
import { env } from "$env/dynamic/private";
const openai = createOpenAI({
apiKey: env.OPENAI_API_KEY ?? "",
});
const variants = {
variant1: {
model: "gpt-3.5-turbo",
max_tokens: 4096,
temperature: 1,
},
variant2: {
model: "gpt-4-turbo",
max_tokens: 4096,
temperature: 1,
},
variant3: {
model: "gpt-4-vision-preview",
max_tokens: 4096,
temperature: 1,
},
};
export const POST = async ({ request }) => {
const { messages } = await request.json();
const variant = variants["variant1"];
const result = await streamText({
model: openai(variant.model),
messages,
max_tokens: variant.max_tokens,
temperature: variant.temperature,
});
return result.toDataStreamResponse();
};
```
This file is doing a few key things:
1. Sets up our OpenAI client with an API key.
2. Defines different AI model configurations in the `variants` object.
3. Handles incoming chat requests with the `POST` function.
When a request comes in, it:
- Extracts the messages from the request body.
- Selects a variant (currently hardcoded to 'variant1').
- Uses the OpenAI API to generate a response.
- Streams the response back to the client.
The `streamText` function is one of the utilities provided by [Vercel's AI SDK](https://sdk.vercel.ai/). It helps deal with the real-time streaming of the AI's responses.
## Creating a feature flag with AI variants
Instead of hardcoding `variant1`, we want to use feature flags to dynamically choose which AI model to use. This will let us easily switch between models, test different configurations, or even do some A/B testing to see which model performs better for which task. And we can do this without having to redeploy our app.
We can also disable it altogether if the upstream API stops working, or we run out of credits.
To implement this, we'll need to:
1. Set up a feature flag provider (we'll use Unleash).
2. Replace our static objects with dynamic feature flag variants.
3. Use the feature flag in our code to determine which AI model and settings to use for each request.
### Install a local feature flag provider
In this section, well install Unleash, run the instance locally, log in, and create a feature flag. If you prefer, you can use other tools instead of Unleash, but youll need to update the code accordingly.
Use Git to clone the Unleash repository and Docker to build and run it. Open a terminal window and run the following commands:
```sh
git clone https://github.com/unleash/unleash.git
cd unleash
docker compose up -d
```
You now have Unleash installed on your machine and running in the background. You can access this instance in your web browser at [http://localhost:4242](http://localhost:4242).
Log in to the platform using these credentials:
```
Username: admin
Password: unleash4all
```
Click **New feature flag** to create a new feature flag, called `ai-model`.
After that, and this is the most important part, we need to add a variant to our feature flag.
Add a strategy to the feature flag in the `development` environment, in that strategy, create a variant for each of these model configurations:
```javascript
const variants = {
variant1: {
model: "gpt-3.5-turbo",
max_tokens: 4096,
temperature: 1,
},
variant2: {
model: "gpt-4-turbo",
max_tokens: 4096,
temperature: 1,
},
variant3: {
model: "gpt-4-vision-preview",
max_tokens: 4096,
temperature: 1,
},
};
```
What we'll do is move all the model configurations from the code to the feature flag variants.
![a variant with parameters for an OpenAI model](./model-variant.png)
## Querying AI feature flags
Now let's go back to the code and grab our AI config from the feature flag that we just created.
First, install the Unleash Node.js client:
```sh
npm install unleash-client
```
Now, let's modify our `+server.js` file to use Unleash:
```javascript
import { initialize } from "unleash-client";
import { createOpenAI } from "@ai-sdk/openai";
import { streamText } from "ai";
import { env } from "$env/dynamic/private";
const openai = createOpenAI({
apiKey: env.OPENAI_API_KEY ?? "",
});
const unleash = initialize({
url: "http://localhost:4242/api/",
appName: "my-ai-app",
customHeaders: { Authorization: env.UNLEASH_API_KEY ?? "" },
});
export const POST = async ({ request }) => {
const { messages } = await request.json();
// Get the feature flag variant
const variant = unleash.getVariant("ai-model");
const result = await streamText({
model: openai(variant.model),
messages,
max_tokens: variant.max_tokens,
temperature: variant.temperature,
});
return result.toDataStreamResponse();
};
```
This setup uses the Unleash client to fetch the value of a feature flag called `ai-model`.
Now, instead of hardcoding `variant1`, we're dynamically choosing the AI model based on the feature flag variant.
This setup gives us a ton of flexibility.
Do you want to roll out GPT-4 to 10% of your users? Easy. Need to quickly switch everyone back to GPT-3.5 because of a bug? No problem.
You can do all of this from your Unleash dashboard without touching your code, and without needing to redeploy.
## Conclusion
Thanks for following along!
In this guide, we covered how to use feature flags to help you manage AI models.
That approach lets you switch between different model configurations, experiment with variations, and even roll out updates without needing to touch your code or redeploy. This gives you more control when experimenting with LLMs, and more power to respond to the unexpected things that will inevitably happen, like running out of credits or discovering a bug.

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -69,7 +69,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.25.2", "@babel/core": "7.25.8",
"@docusaurus/module-type-aliases": "2.3.1", "@docusaurus/module-type-aliases": "2.3.1",
"@tsconfig/docusaurus": "2.0.3", "@tsconfig/docusaurus": "2.0.3",
"babel-loader": "9.1.3", "babel-loader": "9.1.3",

View File

@ -116,6 +116,11 @@ module.exports = {
label: 'A/B Testing', label: 'A/B Testing',
id: 'feature-flag-tutorials/use-cases/a-b-testing', id: 'feature-flag-tutorials/use-cases/a-b-testing',
}, },
{
type: 'doc',
label: 'Feature Flags for AI',
id: 'feature-flag-tutorials/use-cases/ai',
},
], ],
}, },
{ {

457
yarn.lock
View File

@ -104,13 +104,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/code-frame@npm:^7.24.7": "@babel/code-frame@npm:^7.25.7":
version: 7.24.7 version: 7.25.7
resolution: "@babel/code-frame@npm:7.24.7" resolution: "@babel/code-frame@npm:7.25.7"
dependencies: dependencies:
"@babel/highlight": "npm:^7.24.7" "@babel/highlight": "npm:^7.25.7"
picocolors: "npm:^1.0.0" picocolors: "npm:^1.0.0"
checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6 checksum: 10c0/14825c298bdec914caf3d24d1383b6d4cd6b030714686004992f4fc251831ecf432236652896f99d5d341f17170ae9a07b58d8d7b15aa0df8cfa1c5a7d5474bc
languageName: node languageName: node
linkType: hard linkType: hard
@ -121,33 +121,33 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/compat-data@npm:^7.25.2": "@babel/compat-data@npm:^7.25.7":
version: 7.25.2 version: 7.25.8
resolution: "@babel/compat-data@npm:7.25.2" resolution: "@babel/compat-data@npm:7.25.8"
checksum: 10c0/5bf1f14d6e5f0d37c19543e99209ff4a94bb97915e1ce01e5334a144aa08cd56b6e62ece8135dac77e126723d63d4d4b96fc603a12c43b88c28f4b5e070270c5 checksum: 10c0/8b81c17580e5fb4cbb6a3c52079f8c283fc59c0c6bd2fe14cfcf9c44b32d2eaab71b02c5633e2c679f5896f73f8ac4036ba2e67a4c806e8f428e4b11f526d7f4
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/core@npm:7.25.2": "@babel/core@npm:7.25.8":
version: 7.25.2 version: 7.25.8
resolution: "@babel/core@npm:7.25.2" resolution: "@babel/core@npm:7.25.8"
dependencies: dependencies:
"@ampproject/remapping": "npm:^2.2.0" "@ampproject/remapping": "npm:^2.2.0"
"@babel/code-frame": "npm:^7.24.7" "@babel/code-frame": "npm:^7.25.7"
"@babel/generator": "npm:^7.25.0" "@babel/generator": "npm:^7.25.7"
"@babel/helper-compilation-targets": "npm:^7.25.2" "@babel/helper-compilation-targets": "npm:^7.25.7"
"@babel/helper-module-transforms": "npm:^7.25.2" "@babel/helper-module-transforms": "npm:^7.25.7"
"@babel/helpers": "npm:^7.25.0" "@babel/helpers": "npm:^7.25.7"
"@babel/parser": "npm:^7.25.0" "@babel/parser": "npm:^7.25.8"
"@babel/template": "npm:^7.25.0" "@babel/template": "npm:^7.25.7"
"@babel/traverse": "npm:^7.25.2" "@babel/traverse": "npm:^7.25.7"
"@babel/types": "npm:^7.25.2" "@babel/types": "npm:^7.25.8"
convert-source-map: "npm:^2.0.0" convert-source-map: "npm:^2.0.0"
debug: "npm:^4.1.0" debug: "npm:^4.1.0"
gensync: "npm:^1.0.0-beta.2" gensync: "npm:^1.0.0-beta.2"
json5: "npm:^2.2.3" json5: "npm:^2.2.3"
semver: "npm:^6.3.1" semver: "npm:^6.3.1"
checksum: 10c0/a425fa40e73cb72b6464063a57c478bc2de9dbcc19c280f1b55a3d88b35d572e87e8594e7d7b4880331addb6faef641bbeb701b91b41b8806cd4deae5d74f401 checksum: 10c0/8411ea506e6f7c8a39ab5c1524b00589fa3b087edb47389708f7fe07170929192171734666e3ea10b95a951643a531a6d09eedfe071572c9ea28516646265086
languageName: node languageName: node
linkType: hard linkType: hard
@ -197,27 +197,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/generator@npm:^7.24.7": "@babel/generator@npm:^7.25.7":
version: 7.24.7 version: 7.25.7
resolution: "@babel/generator@npm:7.24.7" resolution: "@babel/generator@npm:7.25.7"
dependencies: dependencies:
"@babel/types": "npm:^7.24.7" "@babel/types": "npm:^7.25.7"
"@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/gen-mapping": "npm:^0.3.5"
"@jridgewell/trace-mapping": "npm:^0.3.25" "@jridgewell/trace-mapping": "npm:^0.3.25"
jsesc: "npm:^2.5.1" jsesc: "npm:^3.0.2"
checksum: 10c0/06b1f3350baf527a3309e50ffd7065f7aee04dd06e1e7db794ddfde7fe9d81f28df64edd587173f8f9295496a7ddb74b9a185d4bf4de7bb619e6d4ec45c8fd35 checksum: 10c0/c03a26c79864d60d04ce36b649c3fa0d6fd7b2bf6a22e22854a0457aa09206508392dd73ee40e7bc8d50b3602f9ff068afa47770cda091d332e7db1ca382ee96
languageName: node
linkType: hard
"@babel/generator@npm:^7.25.0":
version: 7.25.0
resolution: "@babel/generator@npm:7.25.0"
dependencies:
"@babel/types": "npm:^7.25.0"
"@jridgewell/gen-mapping": "npm:^0.3.5"
"@jridgewell/trace-mapping": "npm:^0.3.25"
jsesc: "npm:^2.5.1"
checksum: 10c0/d0e2dfcdc8bdbb5dded34b705ceebf2e0bc1b06795a1530e64fb6a3ccf313c189db7f60c1616effae48114e1a25adc75855bc4496f3779a396b3377bae718ce7
languageName: node languageName: node
linkType: hard linkType: hard
@ -236,16 +224,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-compilation-targets@npm:^7.25.2": "@babel/helper-compilation-targets@npm:^7.25.7":
version: 7.25.2 version: 7.25.7
resolution: "@babel/helper-compilation-targets@npm:7.25.2" resolution: "@babel/helper-compilation-targets@npm:7.25.7"
dependencies: dependencies:
"@babel/compat-data": "npm:^7.25.2" "@babel/compat-data": "npm:^7.25.7"
"@babel/helper-validator-option": "npm:^7.24.8" "@babel/helper-validator-option": "npm:^7.25.7"
browserslist: "npm:^4.23.1" browserslist: "npm:^4.24.0"
lru-cache: "npm:^5.1.1" lru-cache: "npm:^5.1.1"
semver: "npm:^6.3.1" semver: "npm:^6.3.1"
checksum: 10c0/de10e986b5322c9f807350467dc845ec59df9e596a5926a3b5edbb4710d8e3b8009d4396690e70b88c3844fe8ec4042d61436dd4b92d1f5f75655cf43ab07e99 checksum: 10c0/705be7e5274a3fdade68e3e2cf42e2b600316ab52794e13b91299a16f16c926f15886b6e9d6df20eb943ccc1cdba5a363d4766f8d01e47b8e6f4e01175f5e66c
languageName: node languageName: node
linkType: hard linkType: hard
@ -263,15 +251,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-environment-visitor@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/helper-environment-visitor@npm:7.24.7"
dependencies:
"@babel/types": "npm:^7.24.7"
checksum: 10c0/36ece78882b5960e2d26abf13cf15ff5689bf7c325b10a2895a74a499e712de0d305f8d78bb382dd3c05cfba7e47ec98fe28aab5674243e0625cd38438dd0b2d
languageName: node
linkType: hard
"@babel/helper-function-name@npm:^7.23.0": "@babel/helper-function-name@npm:^7.23.0":
version: 7.23.0 version: 7.23.0
resolution: "@babel/helper-function-name@npm:7.23.0" resolution: "@babel/helper-function-name@npm:7.23.0"
@ -282,16 +261,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-function-name@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/helper-function-name@npm:7.24.7"
dependencies:
"@babel/template": "npm:^7.24.7"
"@babel/types": "npm:^7.24.7"
checksum: 10c0/e5e41e6cf86bd0f8bf272cbb6e7c5ee0f3e9660414174435a46653efba4f2479ce03ce04abff2aa2ef9359cf057c79c06cb7b134a565ad9c0e8a50dcdc3b43c4
languageName: node
linkType: hard
"@babel/helper-hoist-variables@npm:^7.22.5": "@babel/helper-hoist-variables@npm:^7.22.5":
version: 7.22.5 version: 7.22.5
resolution: "@babel/helper-hoist-variables@npm:7.22.5" resolution: "@babel/helper-hoist-variables@npm:7.22.5"
@ -301,15 +270,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-hoist-variables@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/helper-hoist-variables@npm:7.24.7"
dependencies:
"@babel/types": "npm:^7.24.7"
checksum: 10c0/19ee37563bbd1219f9d98991ad0e9abef77803ee5945fd85aa7aa62a67c69efca9a801696a1b58dda27f211e878b3327789e6fd2a6f6c725ccefe36774b5ce95
languageName: node
linkType: hard
"@babel/helper-module-imports@npm:^7.18.6": "@babel/helper-module-imports@npm:^7.18.6":
version: 7.18.6 version: 7.18.6
resolution: "@babel/helper-module-imports@npm:7.18.6" resolution: "@babel/helper-module-imports@npm:7.18.6"
@ -319,13 +279,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-module-imports@npm:^7.24.7": "@babel/helper-module-imports@npm:^7.25.7":
version: 7.24.7 version: 7.25.7
resolution: "@babel/helper-module-imports@npm:7.24.7" resolution: "@babel/helper-module-imports@npm:7.25.7"
dependencies: dependencies:
"@babel/traverse": "npm:^7.24.7" "@babel/traverse": "npm:^7.25.7"
"@babel/types": "npm:^7.24.7" "@babel/types": "npm:^7.25.7"
checksum: 10c0/97c57db6c3eeaea31564286e328a9fb52b0313c5cfcc7eee4bc226aebcf0418ea5b6fe78673c0e4a774512ec6c86e309d0f326e99d2b37bfc16a25a032498af0 checksum: 10c0/0fd0c3673835e5bf75558e184bcadc47c1f6dd2fe2016d53ebe1e5a6ae931a44e093015c2f9a6651c1a89f25c76d9246710c2b0b460b95ee069c464f2837fa2c
languageName: node languageName: node
linkType: hard linkType: hard
@ -345,17 +305,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-module-transforms@npm:^7.25.2": "@babel/helper-module-transforms@npm:^7.25.7":
version: 7.25.2 version: 7.25.7
resolution: "@babel/helper-module-transforms@npm:7.25.2" resolution: "@babel/helper-module-transforms@npm:7.25.7"
dependencies: dependencies:
"@babel/helper-module-imports": "npm:^7.24.7" "@babel/helper-module-imports": "npm:^7.25.7"
"@babel/helper-simple-access": "npm:^7.24.7" "@babel/helper-simple-access": "npm:^7.25.7"
"@babel/helper-validator-identifier": "npm:^7.24.7" "@babel/helper-validator-identifier": "npm:^7.25.7"
"@babel/traverse": "npm:^7.25.2" "@babel/traverse": "npm:^7.25.7"
peerDependencies: peerDependencies:
"@babel/core": ^7.0.0 "@babel/core": ^7.0.0
checksum: 10c0/adaa15970ace0aee5934b5a633789b5795b6229c6a9cf3e09a7e80aa33e478675eee807006a862aa9aa517935d81f88a6db8a9f5936e3a2a40ec75f8062bc329 checksum: 10c0/f37fa7d1d4df21690535b278468cbd5faf0133a3080f282000cfa4f3ffc9462a1458f866b04b6a2f2d1eec4691236cba9a867da61270dab3ab19846e62f05090
languageName: node languageName: node
linkType: hard linkType: hard
@ -375,13 +335,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-simple-access@npm:^7.24.7": "@babel/helper-simple-access@npm:^7.25.7":
version: 7.24.7 version: 7.25.7
resolution: "@babel/helper-simple-access@npm:7.24.7" resolution: "@babel/helper-simple-access@npm:7.25.7"
dependencies: dependencies:
"@babel/traverse": "npm:^7.24.7" "@babel/traverse": "npm:^7.25.7"
"@babel/types": "npm:^7.24.7" "@babel/types": "npm:^7.25.7"
checksum: 10c0/7230e419d59a85f93153415100a5faff23c133d7442c19e0cd070da1784d13cd29096ee6c5a5761065c44e8164f9f80e3a518c41a0256df39e38f7ad6744fed7 checksum: 10c0/eed1b499bfb4f613c18debd61517e3de77b6da2727ca025aa05ac81599e0269f1dddb5237db04e8bb598115d015874752e0a7f11ff38672d74a4976097417059
languageName: node languageName: node
linkType: hard linkType: hard
@ -403,15 +363,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-split-export-declaration@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/helper-split-export-declaration@npm:7.24.7"
dependencies:
"@babel/types": "npm:^7.24.7"
checksum: 10c0/0254577d7086bf09b01bbde98f731d4fcf4b7c3fa9634fdb87929801307c1f6202a1352e3faa5492450fa8da4420542d44de604daf540704ff349594a78184f6
languageName: node
linkType: hard
"@babel/helper-string-parser@npm:^7.19.4": "@babel/helper-string-parser@npm:^7.19.4":
version: 7.19.4 version: 7.19.4
resolution: "@babel/helper-string-parser@npm:7.19.4" resolution: "@babel/helper-string-parser@npm:7.19.4"
@ -433,10 +384,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-string-parser@npm:^7.24.8": "@babel/helper-string-parser@npm:^7.25.7":
version: 7.24.8 version: 7.25.7
resolution: "@babel/helper-string-parser@npm:7.24.8" resolution: "@babel/helper-string-parser@npm:7.25.7"
checksum: 10c0/6361f72076c17fabf305e252bf6d580106429014b3ab3c1f5c4eb3e6d465536ea6b670cc0e9a637a77a9ad40454d3e41361a2909e70e305116a23d68ce094c08 checksum: 10c0/73ef2ceb81f8294678a0afe8ab0103729c0370cac2e830e0d5128b03be5f6a2635838af31d391d763e3c5a4460ed96f42fd7c9b552130670d525be665913bc4c
languageName: node languageName: node
linkType: hard linkType: hard
@ -475,6 +426,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-validator-identifier@npm:^7.25.7":
version: 7.25.7
resolution: "@babel/helper-validator-identifier@npm:7.25.7"
checksum: 10c0/07438e5bf01ab2882a15027fdf39ac3b0ba1b251774a5130917907014684e2f70fef8fd620137ca062c4c4eedc388508d2ea7a3a7d9936a32785f4fe116c68c0
languageName: node
linkType: hard
"@babel/helper-validator-option@npm:^7.18.6": "@babel/helper-validator-option@npm:^7.18.6":
version: 7.18.6 version: 7.18.6
resolution: "@babel/helper-validator-option@npm:7.18.6" resolution: "@babel/helper-validator-option@npm:7.18.6"
@ -482,10 +440,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-validator-option@npm:^7.24.8": "@babel/helper-validator-option@npm:^7.25.7":
version: 7.24.8 version: 7.25.7
resolution: "@babel/helper-validator-option@npm:7.24.8" resolution: "@babel/helper-validator-option@npm:7.25.7"
checksum: 10c0/73db93a34ae89201351288bee7623eed81a54000779462a986105b54ffe82069e764afd15171a428b82e7c7a9b5fec10b5d5603b216317a414062edf5c67a21f checksum: 10c0/12ed418c8e3ed9ed44c8c80d823f4e42d399b5eb2e423adccb975e31a31a008cd3b5d8eab688b31f740caff4a1bb28fe06ea2fa7d635aee34cc0ad6995d50f0a
languageName: node languageName: node
linkType: hard linkType: hard
@ -500,13 +458,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helpers@npm:^7.25.0": "@babel/helpers@npm:^7.25.7":
version: 7.25.0 version: 7.25.7
resolution: "@babel/helpers@npm:7.25.0" resolution: "@babel/helpers@npm:7.25.7"
dependencies: dependencies:
"@babel/template": "npm:^7.25.0" "@babel/template": "npm:^7.25.7"
"@babel/types": "npm:^7.25.0" "@babel/types": "npm:^7.25.7"
checksum: 10c0/b7fe007fc4194268abf70aa3810365085e290e6528dcb9fbbf7a765d43c74b6369ce0f99c5ccd2d44c413853099daa449c9a0123f0b212ac8d18643f2e8174b8 checksum: 10c0/3b3ae9e373bd785414195ef8f59976a69d5a6ebe0ef2165fdcc5165e5c3ee09e0fcee94bb457df2ddb8c0532e4146d0a9b7a96b3497399a4bff4ffe196b30228
languageName: node languageName: node
linkType: hard linkType: hard
@ -532,15 +490,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/highlight@npm:^7.24.7": "@babel/highlight@npm:^7.25.7":
version: 7.24.7 version: 7.25.7
resolution: "@babel/highlight@npm:7.24.7" resolution: "@babel/highlight@npm:7.25.7"
dependencies: dependencies:
"@babel/helper-validator-identifier": "npm:^7.24.7" "@babel/helper-validator-identifier": "npm:^7.25.7"
chalk: "npm:^2.4.2" chalk: "npm:^2.4.2"
js-tokens: "npm:^4.0.0" js-tokens: "npm:^4.0.0"
picocolors: "npm:^1.0.0" picocolors: "npm:^1.0.0"
checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a checksum: 10c0/1f5894fdb0a0af6101fb2822369b2eeeae32cbeae2ef73ff73fc6a0a4a20471565cd9cfa589f54ed69df66adeca7c57266031ca9134b7bd244d023a488d419aa
languageName: node languageName: node
linkType: hard linkType: hard
@ -571,21 +529,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/parser@npm:^7.24.7": "@babel/parser@npm:^7.25.7, @babel/parser@npm:^7.25.8":
version: 7.24.7 version: 7.25.8
resolution: "@babel/parser@npm:7.24.7" resolution: "@babel/parser@npm:7.25.8"
dependencies:
"@babel/types": "npm:^7.25.8"
bin: bin:
parser: ./bin/babel-parser.js parser: ./bin/babel-parser.js
checksum: 10c0/8b244756872185a1c6f14b979b3535e682ff08cb5a2a5fd97cc36c017c7ef431ba76439e95e419d43000c5b07720495b00cf29a7f0d9a483643d08802b58819b checksum: 10c0/a1a13845b7e8dda4c970791814a4bbf60004969882f18f470e260ad822d2e1f8941948f851e9335895563610f240fa6c98481ce8019865e469502bbf21daafa4
languageName: node
linkType: hard
"@babel/parser@npm:^7.25.0":
version: 7.25.0
resolution: "@babel/parser@npm:7.25.0"
bin:
parser: ./bin/babel-parser.js
checksum: 10c0/4aecf13829fa6f4a66835429bd235458544d9cd14374b17c19bc7726f472727ca33f500e51e1298ddc72db93bdd77fcaa9ddc095200b0b792173069e6cf9742e
languageName: node languageName: node
linkType: hard linkType: hard
@ -783,25 +734,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/template@npm:^7.24.7": "@babel/template@npm:^7.25.7":
version: 7.24.7 version: 7.25.7
resolution: "@babel/template@npm:7.24.7" resolution: "@babel/template@npm:7.25.7"
dependencies: dependencies:
"@babel/code-frame": "npm:^7.24.7" "@babel/code-frame": "npm:^7.25.7"
"@babel/parser": "npm:^7.24.7" "@babel/parser": "npm:^7.25.7"
"@babel/types": "npm:^7.24.7" "@babel/types": "npm:^7.25.7"
checksum: 10c0/95b0b3ee80fcef685b7f4426f5713a855ea2cd5ac4da829b213f8fb5afe48a2a14683c2ea04d446dbc7f711c33c5cd4a965ef34dcbe5bc387c9e966b67877ae3 checksum: 10c0/8ae9e36e4330ee83d4832531d1d9bec7dc2ef6a2a8afa1ef1229506fd60667abcb17f306d1c3d7e582251270597022990c845d5d69e7add70a5aea66720decb9
languageName: node
linkType: hard
"@babel/template@npm:^7.25.0":
version: 7.25.0
resolution: "@babel/template@npm:7.25.0"
dependencies:
"@babel/code-frame": "npm:^7.24.7"
"@babel/parser": "npm:^7.25.0"
"@babel/types": "npm:^7.25.0"
checksum: 10c0/4e31afd873215744c016e02b04f43b9fa23205d6d0766fb2e93eb4091c60c1b88897936adb895fb04e3c23de98dfdcbe31bc98daaa1a4e0133f78bb948e1209b
languageName: node languageName: node
linkType: hard linkType: hard
@ -823,36 +763,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/traverse@npm:^7.24.7": "@babel/traverse@npm:^7.25.7":
version: 7.24.7 version: 7.25.7
resolution: "@babel/traverse@npm:7.24.7" resolution: "@babel/traverse@npm:7.25.7"
dependencies: dependencies:
"@babel/code-frame": "npm:^7.24.7" "@babel/code-frame": "npm:^7.25.7"
"@babel/generator": "npm:^7.24.7" "@babel/generator": "npm:^7.25.7"
"@babel/helper-environment-visitor": "npm:^7.24.7" "@babel/parser": "npm:^7.25.7"
"@babel/helper-function-name": "npm:^7.24.7" "@babel/template": "npm:^7.25.7"
"@babel/helper-hoist-variables": "npm:^7.24.7" "@babel/types": "npm:^7.25.7"
"@babel/helper-split-export-declaration": "npm:^7.24.7"
"@babel/parser": "npm:^7.24.7"
"@babel/types": "npm:^7.24.7"
debug: "npm:^4.3.1" debug: "npm:^4.3.1"
globals: "npm:^11.1.0" globals: "npm:^11.1.0"
checksum: 10c0/a5135e589c3f1972b8877805f50a084a04865ccb1d68e5e1f3b94a8841b3485da4142e33413d8fd76bc0e6444531d3adf1f59f359c11ffac452b743d835068ab checksum: 10c0/75d73e52c507a7a7a4c7971d6bf4f8f26fdd094e0d3a0193d77edf6a5efa36fc3db91ec5cc48e8b94e6eb5d5ad21af0a1040e71309172851209415fd105efb1a
languageName: node
linkType: hard
"@babel/traverse@npm:^7.25.2":
version: 7.25.2
resolution: "@babel/traverse@npm:7.25.2"
dependencies:
"@babel/code-frame": "npm:^7.24.7"
"@babel/generator": "npm:^7.25.0"
"@babel/parser": "npm:^7.25.0"
"@babel/template": "npm:^7.25.0"
"@babel/types": "npm:^7.25.2"
debug: "npm:^4.3.1"
globals: "npm:^11.1.0"
checksum: 10c0/1edcb602801d6ea577584e957a3f6ad48753c4ccb9373fce4c92ebfdee04833f5bd5f1b74758ab7d61fe66d6d83ffdd7c8d482f46199767feeaed6af7df2191e
languageName: node languageName: node
linkType: hard linkType: hard
@ -900,7 +822,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/types@npm:^7.24.7, @babel/types@npm:^7.8.3": "@babel/types@npm:^7.25.7, @babel/types@npm:^7.25.8":
version: 7.25.8
resolution: "@babel/types@npm:7.25.8"
dependencies:
"@babel/helper-string-parser": "npm:^7.25.7"
"@babel/helper-validator-identifier": "npm:^7.25.7"
to-fast-properties: "npm:^2.0.0"
checksum: 10c0/55ca2d6df6426c98db2769ce884ce5e9de83a512ea2dd7bcf56c811984dc14351cacf42932a723630c5afcff2455809323decd645820762182f10b7b5252b59f
languageName: node
linkType: hard
"@babel/types@npm:^7.8.3":
version: 7.24.7 version: 7.24.7
resolution: "@babel/types@npm:7.24.7" resolution: "@babel/types@npm:7.24.7"
dependencies: dependencies:
@ -911,17 +844,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2":
version: 7.25.2
resolution: "@babel/types@npm:7.25.2"
dependencies:
"@babel/helper-string-parser": "npm:^7.24.8"
"@babel/helper-validator-identifier": "npm:^7.24.7"
to-fast-properties: "npm:^2.0.0"
checksum: 10c0/e489435856be239f8cc1120c90a197e4c2865385121908e5edb7223cfdff3768cba18f489adfe0c26955d9e7bbb1fb10625bc2517505908ceb0af848989bd864
languageName: node
linkType: hard
"@bcoe/v8-coverage@npm:^0.2.3": "@bcoe/v8-coverage@npm:^0.2.3":
version: 0.2.3 version: 0.2.3
resolution: "@bcoe/v8-coverage@npm:0.2.3" resolution: "@bcoe/v8-coverage@npm:0.2.3"
@ -1652,92 +1574,92 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-darwin-arm64@npm:1.7.26": "@swc/core-darwin-arm64@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-darwin-arm64@npm:1.7.26" resolution: "@swc/core-darwin-arm64@npm:1.7.35"
conditions: os=darwin & cpu=arm64 conditions: os=darwin & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-darwin-x64@npm:1.7.26": "@swc/core-darwin-x64@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-darwin-x64@npm:1.7.26" resolution: "@swc/core-darwin-x64@npm:1.7.35"
conditions: os=darwin & cpu=x64 conditions: os=darwin & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-linux-arm-gnueabihf@npm:1.7.26": "@swc/core-linux-arm-gnueabihf@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.7.26" resolution: "@swc/core-linux-arm-gnueabihf@npm:1.7.35"
conditions: os=linux & cpu=arm conditions: os=linux & cpu=arm
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-linux-arm64-gnu@npm:1.7.26": "@swc/core-linux-arm64-gnu@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-linux-arm64-gnu@npm:1.7.26" resolution: "@swc/core-linux-arm64-gnu@npm:1.7.35"
conditions: os=linux & cpu=arm64 & libc=glibc conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-linux-arm64-musl@npm:1.7.26": "@swc/core-linux-arm64-musl@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-linux-arm64-musl@npm:1.7.26" resolution: "@swc/core-linux-arm64-musl@npm:1.7.35"
conditions: os=linux & cpu=arm64 & libc=musl conditions: os=linux & cpu=arm64 & libc=musl
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-linux-x64-gnu@npm:1.7.26": "@swc/core-linux-x64-gnu@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-linux-x64-gnu@npm:1.7.26" resolution: "@swc/core-linux-x64-gnu@npm:1.7.35"
conditions: os=linux & cpu=x64 & libc=glibc conditions: os=linux & cpu=x64 & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-linux-x64-musl@npm:1.7.26": "@swc/core-linux-x64-musl@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-linux-x64-musl@npm:1.7.26" resolution: "@swc/core-linux-x64-musl@npm:1.7.35"
conditions: os=linux & cpu=x64 & libc=musl conditions: os=linux & cpu=x64 & libc=musl
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-win32-arm64-msvc@npm:1.7.26": "@swc/core-win32-arm64-msvc@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-win32-arm64-msvc@npm:1.7.26" resolution: "@swc/core-win32-arm64-msvc@npm:1.7.35"
conditions: os=win32 & cpu=arm64 conditions: os=win32 & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-win32-ia32-msvc@npm:1.7.26": "@swc/core-win32-ia32-msvc@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-win32-ia32-msvc@npm:1.7.26" resolution: "@swc/core-win32-ia32-msvc@npm:1.7.35"
conditions: os=win32 & cpu=ia32 conditions: os=win32 & cpu=ia32
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core-win32-x64-msvc@npm:1.7.26": "@swc/core-win32-x64-msvc@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core-win32-x64-msvc@npm:1.7.26" resolution: "@swc/core-win32-x64-msvc@npm:1.7.35"
conditions: os=win32 & cpu=x64 conditions: os=win32 & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/core@npm:1.7.26": "@swc/core@npm:1.7.35":
version: 1.7.26 version: 1.7.35
resolution: "@swc/core@npm:1.7.26" resolution: "@swc/core@npm:1.7.35"
dependencies: dependencies:
"@swc/core-darwin-arm64": "npm:1.7.26" "@swc/core-darwin-arm64": "npm:1.7.35"
"@swc/core-darwin-x64": "npm:1.7.26" "@swc/core-darwin-x64": "npm:1.7.35"
"@swc/core-linux-arm-gnueabihf": "npm:1.7.26" "@swc/core-linux-arm-gnueabihf": "npm:1.7.35"
"@swc/core-linux-arm64-gnu": "npm:1.7.26" "@swc/core-linux-arm64-gnu": "npm:1.7.35"
"@swc/core-linux-arm64-musl": "npm:1.7.26" "@swc/core-linux-arm64-musl": "npm:1.7.35"
"@swc/core-linux-x64-gnu": "npm:1.7.26" "@swc/core-linux-x64-gnu": "npm:1.7.35"
"@swc/core-linux-x64-musl": "npm:1.7.26" "@swc/core-linux-x64-musl": "npm:1.7.35"
"@swc/core-win32-arm64-msvc": "npm:1.7.26" "@swc/core-win32-arm64-msvc": "npm:1.7.35"
"@swc/core-win32-ia32-msvc": "npm:1.7.26" "@swc/core-win32-ia32-msvc": "npm:1.7.35"
"@swc/core-win32-x64-msvc": "npm:1.7.26" "@swc/core-win32-x64-msvc": "npm:1.7.35"
"@swc/counter": "npm:^0.1.3" "@swc/counter": "npm:^0.1.3"
"@swc/types": "npm:^0.1.12" "@swc/types": "npm:^0.1.13"
peerDependencies: peerDependencies:
"@swc/helpers": "*" "@swc/helpers": "*"
dependenciesMeta: dependenciesMeta:
@ -1764,7 +1686,7 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
"@swc/helpers": "@swc/helpers":
optional: true optional: true
checksum: 10c0/6c5ce3d048cb100cd545145b1701a857207b1aeecc8f6bc44ed10b0a1792052834d155a6fa651dad20f38d3fff595034649cc75618946be8da751fa86a9c75b7 checksum: 10c0/aae11f2f311f16a21348e33768debe2295a7e0a04f4b37ffbbb15cf5303e0cd08cf0c72661b72f8e4e33cf530d82c15bb2cef090548c65c4bf3ab3854724465b
languageName: node languageName: node
linkType: hard linkType: hard
@ -1788,12 +1710,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@swc/types@npm:^0.1.12": "@swc/types@npm:^0.1.13":
version: 0.1.12 version: 0.1.13
resolution: "@swc/types@npm:0.1.12" resolution: "@swc/types@npm:0.1.13"
dependencies: dependencies:
"@swc/counter": "npm:^0.1.3" "@swc/counter": "npm:^0.1.3"
checksum: 10c0/f95fea7dee8fc07f8389afbb9578f3d0cd84b429b1d0dbff7fd99b2ef06ed88e96bc33631f36c3bc0505d5a783bee1374acd84b8fc2593001219b6c2caba241b checksum: 10c0/f85a850dead981ca9a26ae366529f2b383fa26324ffcbbee46d7b48399e6ed36d6a6a3d55398f17f87c65f550e28d642a35877d40f389c78765a31ecdfc88bd9
languageName: node languageName: node
linkType: hard linkType: hard
@ -2819,17 +2741,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"browserslist@npm:^4.23.1": "browserslist@npm:^4.24.0":
version: 4.23.2 version: 4.24.0
resolution: "browserslist@npm:4.23.2" resolution: "browserslist@npm:4.24.0"
dependencies: dependencies:
caniuse-lite: "npm:^1.0.30001640" caniuse-lite: "npm:^1.0.30001663"
electron-to-chromium: "npm:^1.4.820" electron-to-chromium: "npm:^1.5.28"
node-releases: "npm:^2.0.14" node-releases: "npm:^2.0.18"
update-browserslist-db: "npm:^1.1.0" update-browserslist-db: "npm:^1.1.0"
bin: bin:
browserslist: cli.js browserslist: cli.js
checksum: 10c0/0217d23c69ed61cdd2530c7019bf7c822cd74c51f8baab18dd62457fed3129f52499f8d3a6f809ae1fb7bb3050aa70caa9a529cc36c7478427966dbf429723a5 checksum: 10c0/95e76ad522753c4c470427f6e3c8a4bb5478ff448841e22b3d3e53f89ecaf17b6984666d6c7e715c370f1e7fa0cf684f42e34e554236a8b2fab38ea76b9e4c52
languageName: node languageName: node
linkType: hard linkType: hard
@ -2957,10 +2879,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"caniuse-lite@npm:^1.0.30001640": "caniuse-lite@npm:^1.0.30001663":
version: 1.0.30001644 version: 1.0.30001669
resolution: "caniuse-lite@npm:1.0.30001644" resolution: "caniuse-lite@npm:1.0.30001669"
checksum: 10c0/96de82909f3ba9f44e5b261c42d3d8814ba99b7b8b48eb8f8eafb7015804ccb2bc2120c5fbc5e193e14e0c87bf1cd0d4de920d8f5a5b477e66e8f0c3972d0eb7 checksum: 10c0/f125f23440d3dbb6c25ffb8d55f4ce48af36a84d0932b152b3b74f143a4170cbe92e02b0a9676209c86609bf7bf34119ff10cc2bc7c1b7ea40e936cc16598408
languageName: node languageName: node
linkType: hard linkType: hard
@ -3849,10 +3771,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"electron-to-chromium@npm:^1.4.820": "electron-to-chromium@npm:^1.5.28":
version: 1.5.3 version: 1.5.40
resolution: "electron-to-chromium@npm:1.5.3" resolution: "electron-to-chromium@npm:1.5.40"
checksum: 10c0/acd4dad650dafa16c4bd19868fe79c58ae3208f666d868ef8d4c81012707b2855b1816241d1c243b50c75a6933817a9e33401a5a17bc4222c52a5ee8abf457e8 checksum: 10c0/3f97360627cf179b344a7d45b3d12fd3f18f1287529d9835a8e802c7a3b99f09e326b4ed3097be1b135e45a33e8497e758b0c101e38e5bb405eaa6aa887eca82
languageName: node languageName: node
linkType: hard linkType: hard
@ -6066,6 +5988,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"jsesc@npm:^3.0.2":
version: 3.0.2
resolution: "jsesc@npm:3.0.2"
bin:
jsesc: bin/jsesc
checksum: 10c0/ef22148f9e793180b14d8a145ee6f9f60f301abf443288117b4b6c53d0ecd58354898dc506ccbb553a5f7827965cd38bc5fb726575aae93c5e8915e2de8290e1
languageName: node
linkType: hard
"json-diff@npm:^1.0.6": "json-diff@npm:^1.0.6":
version: 1.0.6 version: 1.0.6
resolution: "json-diff@npm:1.0.6" resolution: "json-diff@npm:1.0.6"
@ -7035,10 +6966,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"node-releases@npm:^2.0.14": "node-releases@npm:^2.0.18":
version: 2.0.14 version: 2.0.18
resolution: "node-releases@npm:2.0.14" resolution: "node-releases@npm:2.0.18"
checksum: 10c0/199fc93773ae70ec9969bc6d5ac5b2bbd6eb986ed1907d751f411fef3ede0e4bfdb45ceb43711f8078bea237b6036db8b1bf208f6ff2b70c7d615afd157f3ab9 checksum: 10c0/786ac9db9d7226339e1dc84bbb42007cb054a346bd9257e6aa154d294f01bc6a6cddb1348fa099f079be6580acbb470e3c048effd5f719325abd0179e566fd27
languageName: node languageName: node
linkType: hard linkType: hard
@ -9407,11 +9338,11 @@ __metadata:
resolution: "unleash-server@workspace:." resolution: "unleash-server@workspace:."
dependencies: dependencies:
"@apidevtools/swagger-parser": "npm:10.1.0" "@apidevtools/swagger-parser": "npm:10.1.0"
"@babel/core": "npm:7.25.2" "@babel/core": "npm:7.25.8"
"@biomejs/biome": "npm:^1.8.3" "@biomejs/biome": "npm:^1.8.3"
"@cyclonedx/yarn-plugin-cyclonedx": "npm:^1.0.0-rc.7" "@cyclonedx/yarn-plugin-cyclonedx": "npm:^1.0.0-rc.7"
"@slack/web-api": "npm:^7.3.4" "@slack/web-api": "npm:^7.3.4"
"@swc/core": "npm:1.7.26" "@swc/core": "npm:1.7.35"
"@swc/jest": "npm:0.2.36" "@swc/jest": "npm:0.2.36"
"@types/bcryptjs": "npm:2.4.6" "@types/bcryptjs": "npm:2.4.6"
"@types/cors": "npm:2.8.17" "@types/cors": "npm:2.8.17"