feat: notice for backup (#1134)

Reviewed-on: daoyoucloud/tachybase#1134
This commit is contained in:
sealday 2024-06-06 13:47:47 +08:00
parent d5c1864092
commit 8b47ac3f7c
17 changed files with 205 additions and 43 deletions

View File

@ -42,6 +42,7 @@
"markdown-it-highlightjs": "3.3.1", "markdown-it-highlightjs": "3.3.1",
"mathjs": "^10.6.0", "mathjs": "^10.6.0",
"mermaid": "9.4.3", "mermaid": "9.4.3",
"mitt": "^3.0.1",
"nanoid": "^3.3.6", "nanoid": "^3.3.6",
"rc-tree-select": "^5.15.0", "rc-tree-select": "^5.15.0",
"rc-util": "^5.32.0", "rc-util": "^5.32.0",

View File

@ -1,6 +1,7 @@
import React, { createContext, ReactNode, useContext } from 'react'; import React, { createContext, ReactNode, useContext, useEffect } from 'react';
import { message, notification } from 'antd'; import { message, notification } from 'antd';
import mitt, { Emitter, EventType } from 'mitt';
import { Application } from './Application'; import { Application } from './Application';
import { useApp } from './hooks'; import { useApp } from './hooks';
@ -17,6 +18,7 @@ export const enum NoticeType {
STATUS = 'status', STATUS = 'status',
TOAST = 'toast', TOAST = 'toast',
NOTIFICATION = 'notification', NOTIFICATION = 'notification',
CUSTOM = 'custom',
} }
export interface NoticeManagerContextProps { export interface NoticeManagerContextProps {
@ -36,15 +38,29 @@ export const useNoticeManager = () => {
return useContext(NoticeManagerContext); return useContext(NoticeManagerContext);
}; };
export const useNoticeSub = (name: string, handler: () => {}) => {
const { manager } = useNoticeManager();
useEffect(() => {
manager.emitter.on(name, handler);
return () => {
manager.emitter.off(name, handler);
};
}, [manager.emitter, name, handler]);
};
export class NoticeManager { export class NoticeManager {
private ws: WebSocketClient; private ws: WebSocketClient;
public emitter: Emitter<Record<EventType, unknown>>;
constructor(private app: Application) { constructor(private app: Application) {
this.ws = app.ws; this.ws = app.ws;
this.emitter = mitt();
} }
on(data: { type: NoticeType; title?: string; content: string; level: NoticeLevel }) { on(data: { type: NoticeType; title?: string; content: string; level: NoticeLevel; eventType?: string; event?: any }) {
if (data.type === NoticeType.NOTIFICATION) { if (data.type === NoticeType.NOTIFICATION) {
this[data.type](data.title, data.content, data.level); this[data.type](data.title, data.content, data.level);
} else if (data.type === NoticeType.CUSTOM) {
this.emitter.emit(data.eventType, data.event);
} else { } else {
this[data.type](data.content, data.level); this[data.type](data.content, data.level);
} }

View File

@ -8,5 +8,6 @@ export * from './schema-initializer';
export * from './schema-settings'; export * from './schema-settings';
export * from './schema-toolbar'; export * from './schema-toolbar';
export * from './PluginSettingsManager'; export * from './PluginSettingsManager';
export * from './NoticesManager';
export * from './hoc'; export * from './hoc';
export { ApplicationContext } from './context'; export { ApplicationContext } from './context';

View File

@ -12,6 +12,7 @@ import {
findByUid, findByUid,
findMenuItem, findMenuItem,
NavigateIfNotSignIn, NavigateIfNotSignIn,
NoticeManagerProvider,
PinnedPluginList, PinnedPluginList,
RemoteCollectionManagerProvider, RemoteCollectionManagerProvider,
RemoteSchemaTemplateManagerPlugin, RemoteSchemaTemplateManagerPlugin,
@ -440,15 +441,17 @@ export const InternalAdminLayout = (props: any) => {
export const AdminProvider = (props) => { export const AdminProvider = (props) => {
return ( return (
<CurrentAppInfoProvider> <NoticeManagerProvider>
<NavigateIfNotSignIn> <CurrentAppInfoProvider>
<RemoteCollectionManagerProvider> <NavigateIfNotSignIn>
<VariablesProvider> <RemoteCollectionManagerProvider>
<ACLRolesCheckProvider>{props.children}</ACLRolesCheckProvider> <VariablesProvider>
</VariablesProvider> <ACLRolesCheckProvider>{props.children}</ACLRolesCheckProvider>
</RemoteCollectionManagerProvider> </VariablesProvider>
</NavigateIfNotSignIn> </RemoteCollectionManagerProvider>
</CurrentAppInfoProvider> </NavigateIfNotSignIn>
</CurrentAppInfoProvider>
</NoticeManagerProvider>
); );
}; };

View File

@ -6,7 +6,7 @@
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@hapi/topo": "^6.0.0", "@hapi/topo": "^6.0.0",
"@koa/cors": "^3.1.0", "@koa/cors": "^5.0.0",
"@koa/multer": "^3.0.2", "@koa/multer": "^3.0.2",
"@koa/router": "^9.4.0", "@koa/router": "^9.4.0",
"@tachybase/acl": "workspace:*", "@tachybase/acl": "workspace:*",
@ -55,6 +55,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/koa": "^2.13.4", "@types/koa": "^2.13.4",
"@types/koa-bodyparser": "^4.3.12",
"@types/koa__cors": "^5.0.0",
"@types/lodash": "4.17.0", "@types/lodash": "4.17.0",
"@types/qs": "^6.9.14", "@types/qs": "^6.9.14",
"@types/semver": "^7.3.9", "@types/semver": "^7.3.9",
@ -63,7 +65,7 @@
"async-mutex": "^0.3.2", "async-mutex": "^0.3.2",
"execa": "^5.1.1", "execa": "^5.1.1",
"fast-glob": "^3.3.1", "fast-glob": "^3.3.1",
"glob": "^7.1.6", "glob": "^10.4.1",
"koa-compose": "^4.1.0", "koa-compose": "^4.1.0",
"qs": "^6.11.2", "qs": "^6.11.2",
"redis": "^4.6.10" "redis": "^4.6.10"

View File

@ -1,6 +1,8 @@
import { applyMixins, AsyncEmitter } from '@tachybase/utils';
import { Mutex } from 'async-mutex';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { applyMixins, AsyncEmitter } from '@tachybase/utils';
import { Mutex } from 'async-mutex';
import Application, { ApplicationOptions, MaintainingCommandStatus } from './application'; import Application, { ApplicationOptions, MaintainingCommandStatus } from './application';
import { getErrorLevel } from './errors/handler'; import { getErrorLevel } from './errors/handler';

View File

@ -29,7 +29,6 @@ import lodash from 'lodash';
import _ from 'lodash'; import _ from 'lodash';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import semver from 'semver'; import semver from 'semver';
import winston from 'winston';
import packageJson from '../package.json'; import packageJson from '../package.json';
import { createACL } from './acl'; import { createACL } from './acl';
@ -50,9 +49,12 @@ import {
import { ApplicationVersion } from './helpers/application-version'; import { ApplicationVersion } from './helpers/application-version';
import { Locale } from './locale'; import { Locale } from './locale';
import { MainDataSource } from './main-data-source'; import { MainDataSource } from './main-data-source';
import { NoticeManager } from './notice';
import { Plugin } from './plugin'; import { Plugin } from './plugin';
import { InstallOptions, PluginManager } from './plugin-manager'; import { InstallOptions, PluginManager } from './plugin-manager';
export { Logger } from 'winston';
export type PluginType = string | typeof Plugin; export type PluginType = string | typeof Plugin;
export type PluginConfiguration = PluginType | [PluginType, any]; export type PluginConfiguration = PluginType | [PluginType, any];
@ -183,6 +185,7 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
private _maintainingCommandStatus: MaintainingCommandStatus; private _maintainingCommandStatus: MaintainingCommandStatus;
private _maintainingStatusBeforeCommand: MaintainingCommandStatus | null; private _maintainingStatusBeforeCommand: MaintainingCommandStatus | null;
private _actionCommand: Command; private _actionCommand: Command;
private _noticeManager: NoticeManager;
static KEY_CORE_APP_PREFIX = 'KEY_CORE_APP_'; static KEY_CORE_APP_PREFIX = 'KEY_CORE_APP_';
private currentId = nanoid(); private currentId = nanoid();
@ -193,6 +196,11 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
this.init(); this.init();
this._appSupervisor.addApp(this); this._appSupervisor.addApp(this);
this._noticeManager = new NoticeManager(this);
}
get noticeManager() {
return this._noticeManager;
} }
protected _loaded: boolean; protected _loaded: boolean;

View File

@ -1,14 +1,16 @@
import cors from '@koa/cors'; import { randomUUID } from 'crypto';
import fs from 'fs';
import { resolve } from 'path';
import { createHistogram, RecordableHistogram } from 'perf_hooks';
import { requestLogger } from '@tachybase/logger'; import { requestLogger } from '@tachybase/logger';
import { Resourcer } from '@tachybase/resourcer'; import { Resourcer } from '@tachybase/resourcer';
import { uid } from '@tachybase/utils'; import { uid } from '@tachybase/utils';
import cors from '@koa/cors';
import { Command } from 'commander'; import { Command } from 'commander';
import { randomUUID } from 'crypto';
import fs from 'fs';
import i18next from 'i18next'; import i18next from 'i18next';
import bodyParser from 'koa-bodyparser'; import bodyParser from 'koa-bodyparser';
import { resolve } from 'path';
import { createHistogram, RecordableHistogram } from 'perf_hooks';
import Application, { ApplicationOptions } from './application'; import Application, { ApplicationOptions } from './application';
import { parseVariables } from './middlewares'; import { parseVariables } from './middlewares';
import { dateTemplate } from './middlewares/data-template'; import { dateTemplate } from './middlewares/data-template';

View File

@ -6,3 +6,4 @@ export * from './plugin';
export * from './plugin-manager'; export * from './plugin-manager';
export * from './gateway'; export * from './gateway';
export * from './app-supervisor'; export * from './app-supervisor';
export * from './notice';

View File

@ -0,0 +1,59 @@
import Application from '../application';
import { Gateway } from '../gateway';
import { WSServer } from '../gateway/ws-server';
export const enum NoticeLevel {
INFO = 'info',
WARNING = 'warning',
SUCCESS = 'success',
ERROR = 'error',
}
export const enum NoticeType {
STATUS = 'status',
TOAST = 'toast',
NOTIFICATION = 'notification',
CUSTOM = 'custom',
}
export class NoticeManager {
private ws: WSServer;
constructor(private app: Application) {
const gateway = Gateway.getInstance();
this.ws = gateway['wsServer'];
}
#emit(msg: {
type: NoticeType;
title?: string;
content?: string;
level?: NoticeLevel;
eventType?: string;
event?: unknown;
}) {
this.ws.sendToConnectionsByTag('app', this.app.name, {
type: 'notice',
payload: msg,
});
}
notify(eventType: string, event: unknown) {
this.#emit({
type: NoticeType.CUSTOM,
eventType,
event,
});
}
status(content: string, level: NoticeLevel) {
this.#emit({ type: NoticeType.STATUS, content, level });
}
toast(content: string, level: NoticeLevel) {
this.#emit({ type: NoticeType.TOAST, content, level });
}
notification(title: string, content: string, level: NoticeLevel) {
this.#emit({ type: NoticeType.NOTIFICATION, title, content, level });
}
}

View File

@ -1,12 +1,14 @@
import fs from 'fs';
import { basename, resolve } from 'path';
import { Model } from '@tachybase/database'; import { Model } from '@tachybase/database';
import { LoggerOptions } from '@tachybase/logger'; import { LoggerOptions } from '@tachybase/logger';
import { fsExists, importModule } from '@tachybase/utils'; import { fsExists, importModule } from '@tachybase/utils';
import fs from 'fs';
import glob from 'glob'; import glob from 'glob';
import type { TFuncKey, TOptions } from 'i18next'; import type { TFuncKey, TOptions } from 'i18next';
import { basename, resolve } from 'path';
import { Application } from './application'; import { Application } from './application';
import { InstallOptions, getExposeChangelogUrl, getExposeReadmeUrl } from './plugin-manager'; import { getExposeChangelogUrl, getExposeReadmeUrl, InstallOptions } from './plugin-manager';
import { checkAndGetCompatible } from './plugin-manager/utils'; import { checkAndGetCompatible } from './plugin-manager/utils';
export interface PluginInterface { export interface PluginInterface {
@ -61,6 +63,10 @@ export abstract class Plugin<O = any> implements PluginInterface {
}); });
} }
get noticeManager() {
return this.app.noticeManager;
}
get name() { get name() {
return this.options.name as string; return this.options.name as string;
} }

View File

@ -1,7 +1,7 @@
import './actions'; import './actions';
import path from 'path'; import path from 'path';
import Application, { InstallOptions, Plugin, type PluginOptions } from '@tachybase/server'; import Application, { InstallOptions, NoticeLevel, Plugin, type PluginOptions } from '@tachybase/server';
import { Container } from '@tachybase/utils'; import { Container } from '@tachybase/utils';
import { DepartmentsPlugin } from './features/departments'; import { DepartmentsPlugin } from './features/departments';

View File

@ -14,6 +14,7 @@
"@koa/multer": "^3.0.2", "@koa/multer": "^3.0.2",
"@tachybase/components": "workspace:*", "@tachybase/components": "workspace:*",
"@types/archiver": "^5.3.1", "@types/archiver": "^5.3.1",
"@types/file-saver": "^2.0.7",
"@types/lodash": "4.17.0", "@types/lodash": "4.17.0",
"antd": "5.16.1", "antd": "5.16.1",
"archiver": "^5.3.1", "archiver": "^5.3.1",

View File

@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Checkbox, DatePicker, useAPIClient, useCompile } from '@tachybase/client'; import { Checkbox, DatePicker, useAPIClient, useCompile, useNoticeSub } from '@tachybase/client';
import { FormItem } from '@tachybase/components'; import { FormItem } from '@tachybase/components';
import { InboxOutlined, PlusOutlined, ReloadOutlined, UploadOutlined } from '@ant-design/icons'; import { InboxOutlined, PlusOutlined, ReloadOutlined, UploadOutlined } from '@ant-design/icons';
@ -142,7 +142,7 @@ const LearnMore: any = (props: { collectionsData?: any; isBackup?: boolean }) =>
); );
}; };
const Restore: React.FC<any> = ({ ButtonComponent = Button, title, upload = false, fileData }) => { const Restore = ({ ButtonComponent = Button, title, upload = false, fileData }: any) => {
const { t } = useDuplicatorTranslation(); const { t } = useDuplicatorTranslation();
const [dataTypes, setDataTypes] = useState<any[]>(['required']); const [dataTypes, setDataTypes] = useState<any[]>(['required']);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@ -232,7 +232,7 @@ const Restore: React.FC<any> = ({ ButtonComponent = Button, title, upload = fals
); );
}; };
const NewBackup: React.FC<any> = ({ ButtonComponent = Button, refresh }) => { const NewBackup = ({ ButtonComponent = Button, refresh }) => {
const { t } = useDuplicatorTranslation(); const { t } = useDuplicatorTranslation();
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [dataTypes, setBackupData] = useState<any[]>(['required']); const [dataTypes, setBackupData] = useState<any[]>(['required']);
@ -295,7 +295,7 @@ const NewBackup: React.FC<any> = ({ ButtonComponent = Button, refresh }) => {
); );
}; };
const RestoreUpload: React.FC<any> = (props: any) => { const RestoreUpload = (props: any) => {
const { t } = useDuplicatorTranslation(); const { t } = useDuplicatorTranslation();
const uploadProps: UploadProps = { const uploadProps: UploadProps = {
multiple: false, multiple: false,
@ -338,6 +338,12 @@ export const BackupAndRestoreList = () => {
return apiClient.resource('backupFiles'); return apiClient.resource('backupFiles');
}, [apiClient]); }, [apiClient]);
const handleRefresh = useCallback(async () => {
await queryFieldList();
}, []);
useNoticeSub('backup', handleRefresh);
useEffect(() => { useEffect(() => {
queryFieldList(); queryFieldList();
}, []); }, []);
@ -361,9 +367,6 @@ export const BackupAndRestoreList = () => {
const blob = new Blob([data.data]); const blob = new Blob([data.data]);
saveAs(blob, fileData.name); saveAs(blob, fileData.name);
}; };
const handleRefresh = async () => {
await queryFieldList();
};
const handleDestory = (fileData) => { const handleDestory = (fileData) => {
modal.confirm({ modal.confirm({
title: t('Delete record', { ns: 'client' }), title: t('Delete record', { ns: 'client' }),
@ -393,7 +396,7 @@ export const BackupAndRestoreList = () => {
/> />
<NewBackup refresh={handleRefresh} /> <NewBackup refresh={handleRefresh} />
</Space> </Space>
<Table <Table<any>
dataSource={dataSource} dataSource={dataSource}
loading={loading} loading={loading}
columns={[ columns={[

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { CurrentAppInfoProvider, SchemaComponentOptions } from '@tachybase/client'; import { SchemaComponentOptions } from '@tachybase/client';
export const DuplicatorProvider = function (props) { export const DuplicatorProvider = function (props) {
return <SchemaComponentOptions>{props.children}</SchemaComponentOptions>; return <SchemaComponentOptions>{props.children}</SchemaComponentOptions>;

View File

@ -237,6 +237,7 @@ export class Dumper extends AppMigrator {
}).finally(() => { }).finally(() => {
this.cleanLockFile(backupFileName); this.cleanLockFile(backupFileName);
Dumper.dumpTasks.delete(backupFileName); Dumper.dumpTasks.delete(backupFileName);
this.app.noticeManager.notify('backup', { msg: 'done' });
}); });
Dumper.dumpTasks.set(backupFileName, promise); Dumper.dumpTasks.set(backupFileName, promise);

View File

@ -536,6 +536,9 @@ importers:
mermaid: mermaid:
specifier: 9.4.3 specifier: 9.4.3
version: 9.4.3 version: 9.4.3
mitt:
specifier: ^3.0.1
version: 3.0.1
nanoid: nanoid:
specifier: ^3.3.6 specifier: ^3.3.6
version: 3.3.7 version: 3.3.7
@ -1053,8 +1056,8 @@ importers:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.2 version: 6.0.2
'@koa/cors': '@koa/cors':
specifier: ^3.1.0 specifier: ^5.0.0
version: 3.4.3 version: 5.0.0
'@koa/multer': '@koa/multer':
specifier: ^3.0.2 specifier: ^3.0.2
version: 3.0.2(multer@1.4.4) version: 3.0.2(multer@1.4.4)
@ -1194,6 +1197,12 @@ importers:
'@types/koa': '@types/koa':
specifier: ^2.13.4 specifier: ^2.13.4
version: 2.13.12 version: 2.13.12
'@types/koa-bodyparser':
specifier: ^4.3.12
version: 4.3.12
'@types/koa__cors':
specifier: ^5.0.0
version: 5.0.0
'@types/lodash': '@types/lodash':
specifier: 4.17.0 specifier: 4.17.0
version: 4.17.0 version: 4.17.0
@ -1219,8 +1228,8 @@ importers:
specifier: ^3.3.1 specifier: ^3.3.1
version: 3.3.2 version: 3.3.2
glob: glob:
specifier: ^7.1.6 specifier: ^10.4.1
version: 7.2.3 version: 10.4.1
koa-compose: koa-compose:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0 version: 4.1.0
@ -2125,6 +2134,9 @@ importers:
'@types/archiver': '@types/archiver':
specifier: ^5.3.1 specifier: ^5.3.1
version: 5.3.4 version: 5.3.4
'@types/file-saver':
specifier: ^2.0.7
version: 2.0.7
'@types/lodash': '@types/lodash':
specifier: 4.17.0 specifier: 4.17.0
version: 4.17.0 version: 4.17.0
@ -9735,9 +9747,9 @@ packages:
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
dev: false dev: false
/@koa/cors@3.4.3: /@koa/cors@5.0.0:
resolution: {integrity: sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==} resolution: {integrity: sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==}
engines: {node: '>= 8.0.0'} engines: {node: '>= 14.0.0'}
dependencies: dependencies:
vary: 1.1.2 vary: 1.1.2
dev: false dev: false
@ -12062,6 +12074,10 @@ packages:
'@types/qs': 6.9.14 '@types/qs': 6.9.14
'@types/serve-static': 1.15.5 '@types/serve-static': 1.15.5
/@types/file-saver@2.0.7:
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
dev: true
/@types/fs-extra@11.0.4: /@types/fs-extra@11.0.4:
resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
dependencies: dependencies:
@ -12164,7 +12180,6 @@ packages:
resolution: {integrity: sha512-hKMmRMVP889gPIdLZmmtou/BijaU1tHPyMNmcK7FAHAdATnRcGQQy78EqTTxLH1D4FTsrxIzklAQCso9oGoebQ==} resolution: {integrity: sha512-hKMmRMVP889gPIdLZmmtou/BijaU1tHPyMNmcK7FAHAdATnRcGQQy78EqTTxLH1D4FTsrxIzklAQCso9oGoebQ==}
dependencies: dependencies:
'@types/koa': 2.13.12 '@types/koa': 2.13.12
dev: false
/@types/koa-compose@3.2.8: /@types/koa-compose@3.2.8:
resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==} resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==}
@ -12195,6 +12210,12 @@ packages:
'@types/koa-compose': 3.2.8 '@types/koa-compose': 3.2.8
'@types/node': 20.12.8 '@types/node': 20.12.8
/@types/koa__cors@5.0.0:
resolution: {integrity: sha512-LCk/n25Obq5qlernGOK/2LUwa/2YJb2lxHUkkvYFDOpLXlVI6tKcdfCHRBQnOY4LwH6el5WOLs6PD/a8Uzau6g==}
dependencies:
'@types/koa': 2.13.12
dev: true
/@types/linkify-it@3.0.5: /@types/linkify-it@3.0.5:
resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==}
dev: true dev: true
@ -19276,8 +19297,21 @@ packages:
path-scurry: 1.10.2 path-scurry: 1.10.2
dev: true dev: true
/glob@10.4.1:
resolution: {integrity: sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==}
engines: {node: '>=16 || 14 >=14.18'}
hasBin: true
dependencies:
foreground-child: 3.1.1
jackspeak: 3.3.0
minimatch: 9.0.4
minipass: 7.1.2
path-scurry: 1.11.1
dev: true
/glob@7.1.6: /glob@7.1.6:
resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}
deprecated: Glob versions prior to v9 are no longer supported
dependencies: dependencies:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
inflight: 1.0.6 inflight: 1.0.6
@ -20560,6 +20594,15 @@ packages:
'@pkgjs/parseargs': 0.11.0 '@pkgjs/parseargs': 0.11.0
dev: true dev: true
/jackspeak@3.3.0:
resolution: {integrity: sha512-glPiBfKguqA7v8JsXO3iLjJWZ9FV1vNpoI0I9hI9Mnk5yetO9uPLSpiCEmiVijAssv2f54HpvtzvAHfhPieiDQ==}
engines: {node: '>=14'}
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
dev: true
/javascript-natural-sort@0.7.1: /javascript-natural-sort@0.7.1:
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
@ -22049,6 +22092,11 @@ packages:
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
dev: true dev: true
/minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
dev: true
/minizlib@2.1.2: /minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -23099,6 +23147,14 @@ packages:
minipass: 7.0.4 minipass: 7.0.4
dev: true dev: true
/path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
dependencies:
lru-cache: 10.2.0
minipass: 7.1.2
dev: true
/path-temp@2.1.0: /path-temp@2.1.0:
resolution: {integrity: sha512-cMMJTAZlion/RWRRC48UbrDymEIt+/YSD/l8NqjneyDw2rDOBQcP5yRkMB4CYGn47KMhZvbblBP7Z79OsMw72w==} resolution: {integrity: sha512-cMMJTAZlion/RWRRC48UbrDymEIt+/YSD/l8NqjneyDw2rDOBQcP5yRkMB4CYGn47KMhZvbblBP7Z79OsMw72w==}
engines: {node: '>=8.15'} engines: {node: '>=8.15'}