From 4c115cf3258ddc4ad8e3fc96e7dafabe867391dc Mon Sep 17 00:00:00 2001 From: CaasianVale <1544257291@qq.com> Date: Fri, 7 Mar 2025 03:33:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E6=98=BE=E7=A4=BA&=E4=BF=AE=E5=A4=8D=E8=8B=A5=E5=B9=B2bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + frontend/index.html | 4 +- frontend/package-lock.json | 24 +- frontend/package.json | 4 +- frontend/src/App.vue | 3 +- frontend/src/components/ApiConfigPanel.vue | 365 +++- frontend/src/components/LoginPage.vue | 536 ++++++ frontend/src/components/MarketTimeDisplay.vue | 97 +- frontend/src/components/StockAnalysisApp.vue | 209 ++- frontend/src/components/StockCard.vue | 385 ++++- frontend/src/components/StockSearch.vue | 8 +- frontend/src/main.ts | 2 + frontend/src/router/index.ts | 89 + frontend/src/services/api.ts | 97 +- frontend/src/types/index.ts | 12 + frontend/src/utils/stockValidator.ts | 172 ++ frontend/vite.config.ts | 25 + frontend/yarn.lock | 1485 ++++++++++++++++- fund_service.py | 51 - requirements.txt | 2 + services/ai_analyzer.py | 104 +- services/fund_service_async.py | 5 +- services/stock_analyzer_service.py | 24 + services/stock_data_provider.py | 132 +- services/us_stock_service_async.py | 5 +- stock_analyzer.py | 758 --------- us_stock_service.py | 53 - utils/api_control.py | 43 - web_server.py | 239 ++- 29 files changed, 3726 insertions(+), 1209 deletions(-) create mode 100644 frontend/src/components/LoginPage.vue create mode 100644 frontend/src/router/index.ts create mode 100644 frontend/src/utils/stockValidator.ts delete mode 100644 fund_service.py delete mode 100644 stock_analyzer.py delete mode 100644 us_stock_service.py delete mode 100644 utils/api_control.py diff --git a/README.md b/README.md index d72310e..d510ace 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,11 @@ docker run -d \ -e API_URL=替换为你的api地址 \ -e API_MODEL=替换为你的模型 \ -e API_TIMEOUT=60 \ + -e LOGIN_PASSWORD=替换为你的密码 \ lanzhihong/stock-scanner:latest API_TIMEOUT=60 202503040712版本开始 (AI分析发生错误,查看日志是否有timed out类似错误,需要增加你的API超时时间) +LOGIN_PASSWORD 为空时,表示不需要登录,否则需要经过登录接口验证 注意⚠️: 环境变量名变更,更新版本后需要调整!!! diff --git a/frontend/index.html b/frontend/index.html index cbb7c0b..ad351f4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,9 +3,9 @@ - 股票分析系统 + 股票AI分析系统 - +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 96ad038..e9038b3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,8 @@ "axios": "^1.8.1", "marked": "^15.0.7", "naive-ui": "^2.41.0", - "vue": "^3.5.13" + "vue": "^3.5.13", + "vue-router": "^4.5.0" }, "devDependencies": { "@types/marked": "^5.0.2", @@ -298,6 +299,12 @@ "he": "^1.2.0" } }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, "node_modules/@vue/language-core": { "version": "2.2.8", "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.8.tgz", @@ -1217,6 +1224,21 @@ } } }, + "node_modules/vue-router": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", + "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/vue-tsc": { "version": "2.2.8", "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.2.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index cbe1418..0a9d388 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,9 @@ "axios": "^1.8.1", "marked": "^15.0.7", "naive-ui": "^2.41.0", - "vue": "^3.5.13" + "npm": "^11.2.0", + "vue": "^3.5.13", + "vue-router": "4" }, "devDependencies": { "@types/marked": "^5.0.2", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 84095a0..07a97be 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -4,7 +4,7 @@ - + @@ -21,7 +21,6 @@ import { NDialogProvider, NNotificationProvider, } from 'naive-ui' -import StockAnalysisApp from './components/StockAnalysisApp.vue' // 主题设置 (默认使用亮色主题) const theme = ref(null) // 可以切换为 darkTheme 以启用暗色模式 diff --git a/frontend/src/components/ApiConfigPanel.vue b/frontend/src/components/ApiConfigPanel.vue index f840b70..dde2b99 100644 --- a/frontend/src/components/ApiConfigPanel.vue +++ b/frontend/src/components/ApiConfigPanel.vue @@ -5,16 +5,17 @@ size="small" @click="toggleConfig" :quaternary="true" + :type="expanded ? 'primary' : 'default'" > - API配置 {{ expanded ? '收起' : '展开' }} + API配置 {{ expanded ? '收起' : '展开' }} - - + + @@ -28,21 +29,32 @@ - - + + + round + > + + - + + round + > + + - + - + round + > + + + + - + + :show-button="false" + class="timeout-input" + > + + @@ -92,22 +159,36 @@ :loading="testingConnection" :disabled="!isConfigValid" @click="testConnection" + round > + 测试连接 - + + 重置 + + + +
+ + {{ connectionStatus.message }} +
+ + \ No newline at end of file diff --git a/frontend/src/components/MarketTimeDisplay.vue b/frontend/src/components/MarketTimeDisplay.vue index 2b9e48a..3acda4a 100644 --- a/frontend/src/components/MarketTimeDisplay.vue +++ b/frontend/src/components/MarketTimeDisplay.vue @@ -13,9 +13,16 @@

A股市场

-

- {{ marketInfo.cnMarket.isOpen ? '交易中' : '已休市' }} -

+
+ + + 交易中 + + + + 已休市 + +

{{ marketInfo.cnMarket.nextTime }}

@@ -24,9 +31,16 @@

港股市场

-

- {{ marketInfo.hkMarket.isOpen ? '交易中' : '已休市' }} -

+
+ + + 交易中 + + + + 已休市 + +

{{ marketInfo.hkMarket.nextTime }}

@@ -35,9 +49,16 @@

美股市场

-

- {{ marketInfo.usMarket.isOpen ? '交易中' : '已休市' }} -

+
+ + + 交易中 + + + + 已休市 + +

{{ marketInfo.usMarket.nextTime }}

@@ -47,7 +68,11 @@ diff --git a/frontend/src/components/StockSearch.vue b/frontend/src/components/StockSearch.vue index ed7eed7..44abb46 100644 --- a/frontend/src/components/StockSearch.vue +++ b/frontend/src/components/StockSearch.vue @@ -68,7 +68,7 @@ const searchKeyword = ref(''); const results = ref([]); const loading = ref(false); const showResults = ref(false); -const searchInputRef = ref(null); +const searchInputRef = ref(null); // 创建防抖搜索函数 const debouncedSearch = debounce(async (keyword: string) => { @@ -83,7 +83,9 @@ const debouncedSearch = debounce(async (keyword: string) => { try { if (props.marketType === 'US') { // 美股搜索 - results.value = await apiService.searchUsStocks(keyword); + const searchResults = await apiService.searchUsStocks(keyword); + // 限制只显示前10个结果 + results.value = searchResults.slice(0, 10); } else { // 其他市场搜索 (后端需要实现对应的接口) results.value = []; @@ -128,7 +130,7 @@ function formatMarketValue(value: number): string { function handleClickOutside(event: MouseEvent) { if ( searchInputRef.value && - !searchInputRef.value.contains(event.target as Node) + !searchInputRef.value.$el.contains(event.target as Node) ) { showResults.value = false; } diff --git a/frontend/src/main.ts b/frontend/src/main.ts index e03fc18..e3a485c 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,6 +1,8 @@ import { createApp } from 'vue' import './style.css' import App from './App.vue' +import router from './router' const app = createApp(App) +app.use(router) app.mount('#app') diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..e2bd7f0 --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,89 @@ +import { createRouter, createWebHistory } from 'vue-router'; +import type { RouteRecordRaw } from 'vue-router'; +import { apiService } from '@/services/api'; +import StockAnalysisApp from '@/components/StockAnalysisApp.vue'; +import LoginPage from '@/components/LoginPage.vue'; + +const routes: Array = [ + { + path: '/', + name: 'Home', + component: StockAnalysisApp, + meta: { requiresAuth: true } + }, + { + path: '/login', + name: 'Login', + component: LoginPage, + meta: { requiresAuth: false } + }, + { + path: '/:pathMatch(.*)*', + redirect: '/' + } +]; + +const router = createRouter({ + history: createWebHistory(), + routes +}); + +// 全局前置守卫 +router.beforeEach(async (to, from, next) => { + console.log(`路由跳转: 从 ${from.path} 到 ${to.path}`); + + // 如果已经在登录页面,直接通过 + if (to.path === '/login') { + next(); + return; + } + + // 检查路由是否需要认证 + if (to.matched.some(record => record.meta.requiresAuth)) { + console.log('当前路由需要认证'); + + try { + // 先检查系统是否需要登录 + const requireLogin = await apiService.checkNeedLogin(); + console.log('系统是否需要登录:', requireLogin); + + if (!requireLogin) { + // 系统不需要登录,直接通过 + console.log('系统不需要登录,允许访问'); + next(); + return; + } + + // 系统需要登录,检查本地是否有token + const token = localStorage.getItem('token'); + if (!token) { + console.log('本地没有token,跳转到登录页'); + next({ name: 'Login' }); + return; + } + + const isAuthenticated = await apiService.checkAuth(); + console.log('认证检查结果:', isAuthenticated); + + if (!isAuthenticated) { + // 未登录,重定向到登录页 + console.log('认证失败,跳转到登录页'); + next({ name: 'Login' }); + } else { + // 已登录,允许访问 + console.log('认证成功,允许访问'); + next(); + } + } catch (error) { + console.error('认证检查失败:', error); + // 认证检查失败,重定向到登录页 + next({ name: 'Login' }); + } + } else { + // 不需要认证的路由,直接访问 + console.log('当前路由不需要认证,直接访问'); + next(); + } +}); + +export default router; \ No newline at end of file diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 6c38f9a..082f02f 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1,13 +1,88 @@ import axios from 'axios'; -import type { AnalyzeRequest, TestApiRequest, TestApiResponse, SearchResult } from '@/types'; +import type { AnalyzeRequest, TestApiRequest, TestApiResponse, SearchResult, LoginRequest, LoginResponse } from '@/types'; -// 在开发环境中前缀为空,因为已经在vite.config.ts中配置了代理 +// 在开发环境中使用完整URL const API_PREFIX = ''; +// 创建axios实例 +const axiosInstance = axios.create({ + baseURL: API_PREFIX +}); + +// 请求拦截器,添加token +axiosInstance.interceptors.request.use( + (config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// 响应拦截器,处理401错误 +axiosInstance.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + if (error.response && error.response.status === 401) { + // 清除token + localStorage.removeItem('token'); + // 不要在这里跳转,避免循环重定向 + } + return Promise.reject(error); + } +); + export const apiService = { + // 用户登录 + login: async (request: LoginRequest): Promise => { + try { + const response = await axios.post(`${API_PREFIX}/login`, request); + if (response.data.access_token) { + localStorage.setItem('token', response.data.access_token); + } + return response.data; + } catch (error: any) { + if (error.response) { + return { + success: false, + message: error.response.data.detail || '登录失败', + }; + } + return { + success: false, + message: error.message || '登录失败' + }; + } + }, + + // 检查认证状态 + checkAuth: async (): Promise => { + try { + const response = await axiosInstance.get(`${API_PREFIX}/check_auth`); + return response.data.authenticated === true; + } catch (error) { + // 认证失败,清除token + localStorage.removeItem('token'); + return false; + } + }, + + // 登出 + logout: () => { + localStorage.removeItem('token'); + // 简化登出逻辑 + window.location.href = '/login'; + }, + // 分析股票 analyzeStocks: async (request: AnalyzeRequest) => { - return axios.post(`${API_PREFIX}/analyze`, request, { + return axiosInstance.post(`${API_PREFIX}/analyze`, request, { responseType: 'stream' }); }, @@ -15,7 +90,7 @@ export const apiService = { // 测试API连接 testApiConnection: async (request: TestApiRequest): Promise => { try { - const response = await axios.post(`${API_PREFIX}/test_api_connection`, request); + const response = await axiosInstance.post(`${API_PREFIX}/test_api_connection`, request); return response.data; } catch (error: any) { if (error.response) { @@ -31,7 +106,7 @@ export const apiService = { // 搜索美股 searchUsStocks: async (keyword: string): Promise => { try { - const response = await axios.get(`${API_PREFIX}/search_us_stocks`, { + const response = await axiosInstance.get(`${API_PREFIX}/search_us_stocks`, { params: { keyword } }); return response.data.results || []; @@ -55,5 +130,17 @@ export const apiService = { default_api_timeout: '60' }; } + }, + + // 检查是否需要登录 + checkNeedLogin: async (): Promise => { + try { + const response = await axios.get(`${API_PREFIX}/need_login`); + return response.data.require_login; + } catch (error) { + console.error('检查是否需要登录时出错:', error); + // 默认为需要登录,确保安全 + return true; + } } }; diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index d73a9f9..a6f75ff 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -7,6 +7,18 @@ export interface ApiConfig { saveApiConfig: boolean; } +// 登录相关类型 +export interface LoginRequest { + password: string; +} + +export interface LoginResponse { + access_token?: string; + token_type?: string; + success?: boolean; + message?: string; +} + export interface StockInfo { code: string; name: string; diff --git a/frontend/src/utils/stockValidator.ts b/frontend/src/utils/stockValidator.ts new file mode 100644 index 0000000..edbbc58 --- /dev/null +++ b/frontend/src/utils/stockValidator.ts @@ -0,0 +1,172 @@ + /** + * 股票代码验证工具 + * 用于验证不同市场类型的股票代码格式 + */ + +/** + * 市场类型枚举 + */ +export enum MarketType { + A = 'A', // A股 + HK = 'HK', // 港股 + US = 'US', // 美股 + ETF = 'ETF', // ETF基金 + LOF = 'LOF' // LOF基金 +} + +/** + * 验证A股股票代码 + * @param code 股票代码 + * @returns 是否为有效的A股代码 + */ +export const validateAStock = (code: string): boolean => { + // 上海证券交易所股票代码以6开头,6位数字 + // 深圳证券交易所股票代码以0或3开头,6位数字 + // 科创板股票代码以688开头,6位数字 + // 北京证券交易所股票代码以8开头,一般为5位数字(如80XXX) + // 注意:中小板、创业板代码格式已合并处理 + + // 验证上海证券交易所(以6开头的6位数字) + if (code.startsWith('6') && /^\d{6}$/.test(code)) { + return true; + } + + // 验证深圳证券交易所(以0或3开头的6位数字) + if ((code.startsWith('0') || code.startsWith('3')) && /^\d{6}$/.test(code)) { + return true; + } + + // 验证科创板(以688开头的6位数字) + if (code.startsWith('688') && /^\d{6}$/.test(code)) { + return true; + } + + // 验证北京证券交易所(以8开头的股票) + // 北交所股票一般是5位数字,格式为8xxxx + if (code.startsWith('8') && /^\d{5}$/.test(code)) { + return true; + } + + return false; +}; + +/** + * 验证港股股票代码 + * @param code 股票代码 + * @returns 是否为有效的港股代码 + */ +export const validateHKStock = (code: string): boolean => { + // 港股通常是5位数字 + return /^\d{5}$/.test(code); +}; + +/** + * 验证美股股票代码 + * @param code 股票代码 + * @returns 是否为有效的美股代码 + */ +export const validateUSStock = (code: string): boolean => { + // 美股代码通常由字母组成,长度在1-5之间 + return /^[A-Za-z]{1,5}$/.test(code); +}; + +/** + * 验证ETF/LOF基金代码 + * @param code 基金代码 + * @returns 是否为有效的基金代码 + */ +export const validateFund = (code: string): boolean => { + // 基金代码通常为6位数字 + return /^\d{6}$/.test(code); +}; + +/** + * 根据市场类型验证股票代码 + * @param code 股票代码 + * @param marketType 市场类型 + * @returns 包含验证结果和错误信息的对象 + */ +export const validateStockCode = ( + code: string, + marketType: MarketType +): { valid: boolean; errorMessage?: string } => { + + if (!code || code.trim() === '') { + return { + valid: false, + errorMessage: '股票代码不能为空' + }; + } + + switch (marketType) { + case MarketType.A: + if (!validateAStock(code)) { + return { + valid: false, + errorMessage: `无效的A股股票代码格式: ${code}。A股代码应以0、3、6、688或8开头,且为6位数字或5位数字` + }; + } + break; + + case MarketType.HK: + if (!validateHKStock(code)) { + return { + valid: false, + errorMessage: `无效的港股代码格式: ${code}。港股代码应为5位数字` + }; + } + break; + + case MarketType.US: + if (!validateUSStock(code)) { + return { + valid: false, + errorMessage: `无效的美股代码格式: ${code}。美股代码应为1-5位字母` + }; + } + break; + + case MarketType.ETF: + case MarketType.LOF: + if (!validateFund(code)) { + return { + valid: false, + errorMessage: `无效的${marketType}基金代码格式: ${code}。基金代码应为6位数字` + }; + } + break; + + default: + return { + valid: false, + errorMessage: `不支持的市场类型: ${marketType}` + }; + } + + return { valid: true }; +}; + +/** + * 批量验证多个股票代码 + * @param codes 股票代码数组 + * @param marketType 市场类型 + * @returns 包含所有无效代码及其错误信息的数组 + */ +export const validateMultipleStockCodes = ( + codes: string[], + marketType: MarketType +): { code: string; errorMessage: string }[] => { + const invalidCodes: { code: string; errorMessage: string }[] = []; + + for (const code of codes) { + const result = validateStockCode(code, marketType); + if (!result.valid && result.errorMessage) { + invalidCodes.push({ + code, + errorMessage: result.errorMessage + }); + } + } + + return invalidCodes; +}; \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 181d624..8009f22 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ }, }, server: { + cors: true, proxy: { '/api': { target: 'http://127.0.0.1:8888', @@ -38,6 +39,30 @@ export default defineConfig({ target: 'http://127.0.0.1:8888', changeOrigin: true, }, + '/login': { + target: 'http://127.0.0.1:8888', + changeOrigin: true, + }, + '/check_auth': { + target: 'http://127.0.0.1:8888', + changeOrigin: true, + }, + '/need_login': { + target: 'http://127.0.0.1:8888', + changeOrigin: true, + }, + '/us_stock_detail': { + target: 'http://127.0.0.1:8888', + changeOrigin: true, + }, + '/fund_detail': { + target: 'http://127.0.0.1:8888', + changeOrigin: true, + }, + '/search_funds': { + target: 'http://127.0.0.1:8888', + changeOrigin: true, + }, }, }, }) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5584045..2adc86a 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -167,6 +167,30 @@ resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz" integrity sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + +"@isaacs/string-locale-compare@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.0" resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" @@ -177,6 +201,180 @@ resolved "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz" integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== +"@npmcli/agent@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-3.0.0.tgz#1685b1fbd4a1b7bb4f930cbb68ce801edfe7aa44" + integrity sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q== + dependencies: + agent-base "^7.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + lru-cache "^10.0.1" + socks-proxy-agent "^8.0.3" + +"@npmcli/arborist@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-9.0.1.tgz#5574221af060d6192e1d73f4c3af5f9e3f7b502e" + integrity sha512-m00iV8hgbmli0IMf4Os+UmEq5JRTgqOHR+x5h07O7mO/60q5hLaYwZUMamJ73wlMG68c3WB8ZloOxON/knF5vg== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/fs" "^4.0.0" + "@npmcli/installed-package-contents" "^3.0.0" + "@npmcli/map-workspaces" "^4.0.1" + "@npmcli/metavuln-calculator" "^9.0.0" + "@npmcli/name-from-folder" "^3.0.0" + "@npmcli/node-gyp" "^4.0.0" + "@npmcli/package-json" "^6.0.1" + "@npmcli/query" "^4.0.0" + "@npmcli/redact" "^3.0.0" + "@npmcli/run-script" "^9.0.1" + bin-links "^5.0.0" + cacache "^19.0.1" + common-ancestor-path "^1.0.1" + hosted-git-info "^8.0.0" + json-stringify-nice "^1.1.4" + lru-cache "^10.2.2" + minimatch "^9.0.4" + nopt "^8.0.0" + npm-install-checks "^7.1.0" + npm-package-arg "^12.0.0" + npm-pick-manifest "^10.0.0" + npm-registry-fetch "^18.0.1" + pacote "^21.0.0" + parse-conflict-json "^4.0.0" + proc-log "^5.0.0" + proggy "^3.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^3.0.1" + read-package-json-fast "^4.0.0" + semver "^7.3.7" + ssri "^12.0.0" + treeverse "^3.0.0" + walk-up-path "^4.0.0" + +"@npmcli/config@^10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-10.1.0.tgz#fe6a0bd26da87ddc92c85e0443b8ef3855c359c0" + integrity sha512-ygyCJATTr+xmuQHiX28adNT3tbDcWIfHQggNtLL2ykSyH4VCF5YeG4SilZaYIxf+72GZA6CJpESaDyhq9Boozg== + dependencies: + "@npmcli/map-workspaces" "^4.0.1" + "@npmcli/package-json" "^6.0.1" + ci-info "^4.0.0" + ini "^5.0.0" + nopt "^8.1.0" + proc-log "^5.0.0" + semver "^7.3.5" + walk-up-path "^4.0.0" + +"@npmcli/fs@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-4.0.0.tgz#a1eb1aeddefd2a4a347eca0fab30bc62c0e1c0f2" + integrity sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q== + dependencies: + semver "^7.3.5" + +"@npmcli/git@^6.0.0", "@npmcli/git@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-6.0.3.tgz#966cbb228514372877de5244db285b199836f3aa" + integrity sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ== + dependencies: + "@npmcli/promise-spawn" "^8.0.0" + ini "^5.0.0" + lru-cache "^10.0.1" + npm-pick-manifest "^10.0.0" + proc-log "^5.0.0" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^5.0.0" + +"@npmcli/installed-package-contents@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz#2c1170ff4f70f68af125e2842e1853a93223e4d1" + integrity sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q== + dependencies: + npm-bundled "^4.0.0" + npm-normalize-package-bin "^4.0.0" + +"@npmcli/map-workspaces@^4.0.1", "@npmcli/map-workspaces@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-4.0.2.tgz#d02c5508bf55624f60aaa58fe413748a5c773802" + integrity sha512-mnuMuibEbkaBTYj9HQ3dMe6L0ylYW+s/gfz7tBDMFY/la0w9Kf44P9aLn4/+/t3aTR3YUHKoT6XQL9rlicIe3Q== + dependencies: + "@npmcli/name-from-folder" "^3.0.0" + "@npmcli/package-json" "^6.0.0" + glob "^10.2.2" + minimatch "^9.0.0" + +"@npmcli/metavuln-calculator@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.0.tgz#7e54d7c9f33999fde0ad2998904e0edd1627e26d" + integrity sha512-znLKqdy1ZEGNK3VB9j/RzGyb/P0BJb3fGpvEbHIAyBAXsps2l1ce8SVHfsGAFLl9s8072PxafqTn7RC8wSnQPg== + dependencies: + cacache "^19.0.0" + json-parse-even-better-errors "^4.0.0" + pacote "^21.0.0" + proc-log "^5.0.0" + semver "^7.3.5" + +"@npmcli/name-from-folder@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-3.0.0.tgz#ed49b18d16b954149f31240e16630cfec511cd57" + integrity sha512-61cDL8LUc9y80fXn+lir+iVt8IS0xHqEKwPu/5jCjxQTVoSCmkXvw4vbMrzAMtmghz3/AkiBjhHkDKUH+kf7kA== + +"@npmcli/node-gyp@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz#01f900bae62f0f27f9a5a127b40d443ddfb9d4c6" + integrity sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA== + +"@npmcli/package-json@^6.0.0", "@npmcli/package-json@^6.0.1", "@npmcli/package-json@^6.1.0", "@npmcli/package-json@^6.1.1": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-6.1.1.tgz#78ff92d138fdcb85f31cab907455d5db96d017cb" + integrity sha512-d5qimadRAUCO4A/Txw71VM7UrRZzV+NPclxz/dc+M6B2oYwjWTjqh8HA/sGQgs9VZuJ6I/P7XIAlJvgrl27ZOw== + dependencies: + "@npmcli/git" "^6.0.0" + glob "^10.2.2" + hosted-git-info "^8.0.0" + json-parse-even-better-errors "^4.0.0" + proc-log "^5.0.0" + semver "^7.5.3" + validate-npm-package-license "^3.0.4" + +"@npmcli/promise-spawn@^8.0.0", "@npmcli/promise-spawn@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-8.0.2.tgz#053688f8bc2b4ecc036d2d52c691fd82af58ea5e" + integrity sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ== + dependencies: + which "^5.0.0" + +"@npmcli/query@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/query/-/query-4.0.0.tgz#7a2470254f5a12a1499d2296a7343043c7847568" + integrity sha512-3pPbese0fbCiFJ/7/X1GBgxAKYFE8sxBddA7GtuRmOgNseH4YbGsXJ807Ig3AEwNITjDUISHglvy89cyDJnAwA== + dependencies: + postcss-selector-parser "^6.1.2" + +"@npmcli/redact@^3.0.0", "@npmcli/redact@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/redact/-/redact-3.1.1.tgz#ac295c148d01c70a5a006d2e162388b3cef15195" + integrity sha512-3Hc2KGIkrvJWJqTbvueXzBeZlmvoOxc2jyX00yzr3+sNFquJg0N8hH4SAPLPVrkWIRQICVpVgjrss971awXVnA== + +"@npmcli/run-script@^9.0.0", "@npmcli/run-script@^9.0.1": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-9.0.2.tgz#621f993d59bae770104a5b655a38c6579d5ce6be" + integrity sha512-cJXiUlycdizQwvqE1iaAb4VRUM3RX09/8q46zjvy+ct9GhfZRWd7jXYVc1tn/CfRlGPVkX/u4sstRlepsm7hfw== + dependencies: + "@npmcli/node-gyp" "^4.0.0" + "@npmcli/package-json" "^6.0.0" + "@npmcli/promise-spawn" "^8.0.0" + node-gyp "^11.0.0" + proc-log "^5.0.0" + which "^5.0.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@rollup/rollup-android-arm-eabi@4.34.9": version "4.34.9" resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz#661a45a4709c70e59e596ec78daa9cb8b8d27604" @@ -272,6 +470,65 @@ resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz" integrity sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw== +"@sigstore/bundle@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-3.1.0.tgz#74f8f3787148400ddd364be8a9a9212174c66646" + integrity sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag== + dependencies: + "@sigstore/protobuf-specs" "^0.4.0" + +"@sigstore/core@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sigstore/core/-/core-2.0.0.tgz#f888a8e4c8fdaa27848514a281920b6fd8eca955" + integrity sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg== + +"@sigstore/protobuf-specs@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.4.0.tgz#7524509d93efcb14e77d0bc34c43a1ae85f851c5" + integrity sha512-o09cLSIq9EKyRXwryWDOJagkml9XgQCoCSRjHOnHLnvsivaW7Qznzz6yjfV7PHJHhIvyp8OH7OX8w0Dc5bQK7A== + +"@sigstore/sign@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@sigstore/sign/-/sign-3.1.0.tgz#5d098d4d2b59a279e9ac9b51c794104cda0c649e" + integrity sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw== + dependencies: + "@sigstore/bundle" "^3.1.0" + "@sigstore/core" "^2.0.0" + "@sigstore/protobuf-specs" "^0.4.0" + make-fetch-happen "^14.0.2" + proc-log "^5.0.0" + promise-retry "^2.0.1" + +"@sigstore/tuf@^3.0.0", "@sigstore/tuf@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@sigstore/tuf/-/tuf-3.1.0.tgz#f533ac8ac572c9f7e36f5e08f1effa6b2244f55a" + integrity sha512-suVMQEA+sKdOz5hwP9qNcEjX6B45R+hFFr4LAWzbRc5O+U2IInwvay/bpG5a4s+qR35P/JK/PiKiRGjfuLy1IA== + dependencies: + "@sigstore/protobuf-specs" "^0.4.0" + tuf-js "^3.0.1" + +"@sigstore/verify@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@sigstore/verify/-/verify-2.1.0.tgz#63e31dd69b678ed6d98cbfdc6d6c104b82d0905c" + integrity sha512-kAAM06ca4CzhvjIZdONAL9+MLppW3K48wOFy1TbuaWFW/OMfl8JuTgW0Bm02JB1WJGT/ET2eqav0KTEKmxqkIA== + dependencies: + "@sigstore/bundle" "^3.1.0" + "@sigstore/core" "^2.0.0" + "@sigstore/protobuf-specs" "^0.4.0" + +"@tufjs/canonical-json@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz#a52f61a3d7374833fca945b2549bc30a2dd40d0a" + integrity sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA== + +"@tufjs/models@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@tufjs/models/-/models-3.0.1.tgz#5aebb782ebb9e06f071ae7831c1f35b462b0319c" + integrity sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA== + dependencies: + "@tufjs/canonical-json" "2.0.0" + minimatch "^9.0.5" + "@types/estree@1.0.6": version "1.0.6" resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz" @@ -392,6 +649,11 @@ de-indent "^1.0.2" he "^1.2.0" +"@vue/devtools-api@^6.6.4": + version "6.6.4" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343" + integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g== + "@vue/language-core@2.2.8": version "2.2.8" resolved "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.8.tgz" @@ -471,11 +733,53 @@ dependencies: vue "^3.5.13" +abbrev@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-3.0.0.tgz#c29a6337e167ac61a84b41b80461b29c5c271a27" + integrity sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA== + +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" + integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== + alien-signals@^1.0.3: version "1.0.4" resolved "https://registry.npmmirror.com/alien-signals/-/alien-signals-1.0.4.tgz" integrity sha512-DJqqQD3XcsaQcQ1s+iE2jDUZmmQpXwHiR6fCAim/w87luaW+vmLY8fMlrdkmRwzaFXhkxf3rqPCR59tKVv1MDw== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +aproba@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +archy@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== + async-validator@^4.2.5: version "4.2.5" resolved "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz" @@ -500,6 +804,22 @@ balanced-match@^1.0.0: resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +bin-links@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-5.0.0.tgz#2b0605b62dd5e1ddab3b92a3c4e24221cae06cca" + integrity sha512-sdleLVfCjBtgO5cNjA2HVRvWBJAHs4zwenaCPMNJAJU0yNxpzj80IpjOIimkpkr+mhlA+how5poQtt53PygbHA== + dependencies: + cmd-shim "^7.0.0" + npm-normalize-package-bin "^4.0.0" + proc-log "^5.0.0" + read-cmd-shim "^5.0.0" + write-file-atomic "^6.0.0" + +binary-extensions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-3.0.0.tgz#14ce687f80e3ebab2a2fb78bb8611584c29f12c3" + integrity sha512-X0RfwMgXPEesg6PCXzytQZt9Unh9gtc4SfeTNJvKifUL//Oegcc/Yf31z6hThNZ8dnD3Ir3wkHVN0eWrTvP5ww== + brace-expansion@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz" @@ -507,6 +827,24 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" +cacache@^19.0.0, cacache@^19.0.1: + version "19.0.1" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-19.0.1.tgz#3370cc28a758434c85c2585008bd5bdcff17d6cd" + integrity sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ== + dependencies: + "@npmcli/fs" "^4.0.0" + fs-minipass "^3.0.0" + glob "^10.2.2" + lru-cache "^10.0.1" + minipass "^7.0.3" + minipass-collect "^2.0.1" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^7.0.2" + ssri "^12.0.0" + tar "^7.4.3" + unique-filename "^4.0.0" + call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" @@ -515,6 +853,58 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" +chalk@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" + integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + +ci-info@^4.0.0, ci-info@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83" + integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A== + +cidr-regex@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-4.1.3.tgz#df94af8ac16fc2e0791e2824693b957ff1ac4d3e" + integrity sha512-86M1y3ZeQvpZkZejQCcS+IaSWjlDUC+ORP0peScQ4uEUFCZ8bEQVz7NlJHqysoUb6w3zCjx4Mq/8/2RHhMwHYw== + dependencies: + ip-regex "^5.0.0" + +cli-columns@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-4.0.0.tgz#9fe4d65975238d55218c41bd2ed296a7fa555646" + integrity sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ== + dependencies: + string-width "^4.2.3" + strip-ansi "^6.0.1" + +cmd-shim@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-7.0.0.tgz#23bcbf69fff52172f7e7c02374e18fb215826d95" + integrity sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz" @@ -522,6 +912,20 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + css-render@^0.15.10, css-render@^0.15.14: version "0.15.14" resolved "https://registry.npmmirror.com/css-render/-/css-render-0.15.14.tgz" @@ -530,6 +934,11 @@ css-render@^0.15.10, css-render@^0.15.14: "@emotion/hash" "~0.8.0" csstype "~3.0.5" +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + csstype@^3.1.3: version "3.1.3" resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz" @@ -555,11 +964,23 @@ de-indent@^1.0.2: resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz" integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== +debug@4, debug@^4.3.4, debug@^4.3.6: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +diff@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a" + integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== + dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz" @@ -569,11 +990,43 @@ dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + entities@^4.5.0: version "4.5.0" resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz" @@ -642,11 +1095,29 @@ evtd@^0.2.2, evtd@^0.2.4: resolved "https://registry.npmmirror.com/evtd/-/evtd-0.2.4.tgz" integrity sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw== +exponential-backoff@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.2.tgz#a8f26adb96bf78e8cd8ad1037928d5e5c0679d91" + integrity sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA== + +fastest-levenshtein@^1.0.16: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + follow-redirects@^1.15.6: version "1.15.9" resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz" integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== +foreground-child@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + form-data@^4.0.0: version "4.0.2" resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz" @@ -657,6 +1128,20 @@ form-data@^4.0.0: es-set-tostringtag "^2.1.0" mime-types "^2.1.12" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-minipass@^3.0.0, fs-minipass@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.3.tgz#79a85981c4dc120065e96f62086bf6f9dc26cc54" + integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw== + dependencies: + minipass "^7.0.3" + fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -691,11 +1176,28 @@ get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" +glob@^10.2.2, glob@^10.3.10, glob@^10.3.7, glob@^10.4.5: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== +graceful-fs@^4.2.11, graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz" @@ -725,6 +1227,249 @@ highlight.js@^11.8.0: resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz" integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w== +hosted-git-info@^8.0.0, hosted-git-info@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-8.0.2.tgz#5bd7d8b5395616e41cc0d6578381a32f669b14b2" + integrity sha512-sYKnA7eGln5ov8T8gnYlkSOxFJvywzEx9BueN6xo/GKO8PGiI6uK6xx+DIGe45T3bdVjLAQDQW1aicT8z8JwQg== + dependencies: + lru-cache "^10.0.1" + +http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +https-proxy-agent@^7.0.1: + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore-walk@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-7.0.0.tgz#8350e475cf4375969c12eb49618b3fd9cca6704f" + integrity sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ== + dependencies: + minimatch "^9.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +ini@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-5.0.0.tgz#a7a4615339843d9a8ccc2d85c9d81cf93ffbc638" + integrity sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw== + +init-package-json@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-8.0.0.tgz#feaa2e5e949c68bec3bcfb25c1b7075ffe6fc88b" + integrity sha512-zKgxfaGt6Zzi8VBSInOK0CYDigA9gzDCWPnSzGIoUlTU/5w7qIyi+6MyJYX96mMlxDGrIR85FhQszVyodYfB9g== + dependencies: + "@npmcli/package-json" "^6.1.0" + npm-package-arg "^12.0.0" + promzard "^2.0.0" + read "^4.0.0" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^6.0.0" + +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + +ip-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-5.0.0.tgz#cd313b2ae9c80c07bd3851e12bf4fa4dc5480632" + integrity sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw== + +is-cidr@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-5.1.1.tgz#83ec462922c2b9209bc64794c4e3b2a890d23994" + integrity sha512-AwzRMjtJNTPOgm7xuYZ71715z99t+4yRnSnSzgK5err5+heYi4zMuvmpUadaJ28+KCXCQo8CjUrKQZRWSPmqTQ== + dependencies: + cidr-regex "^4.1.1" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isexe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" + integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + +json-parse-even-better-errors@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz#d3f67bd5925e81d3e31aa466acc821c8375cec43" + integrity sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA== + +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== + +jsonparse@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +just-diff-apply@^5.2.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-5.5.0.tgz#771c2ca9fa69f3d2b54e7c3f5c1dfcbcc47f9f0f" + integrity sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw== + +just-diff@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-6.0.2.tgz#03b65908543ac0521caf6d8eb85035f7d27ea285" + integrity sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA== + +libnpmaccess@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-10.0.0.tgz#a5279a683af982fa971599d37ed471e59339bd01" + integrity sha512-Nz9Lolajvh6nPA5ixdKNfN2BJS0N7LvqTXPqy3+F37i3T4mcped24JCjwnp5KCPCB0ewX3ccopwUnhaTS1/yXg== + dependencies: + npm-package-arg "^12.0.0" + npm-registry-fetch "^18.0.1" + +libnpmdiff@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/libnpmdiff/-/libnpmdiff-8.0.1.tgz#b8348a759306dff56330ba6228eaa58af8abf33e" + integrity sha512-3HoZq96FtqpEq1miPKQVj49T+KAKF4bP1UflWBBQ1YZDwm77tgNnYttuSRj6N41R5B2bxL5wK8a0zFbFGIN7tw== + dependencies: + "@npmcli/arborist" "^9.0.1" + "@npmcli/installed-package-contents" "^3.0.0" + binary-extensions "^3.0.0" + diff "^7.0.0" + minimatch "^9.0.4" + npm-package-arg "^12.0.0" + pacote "^21.0.0" + tar "^6.2.1" + +libnpmexec@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/libnpmexec/-/libnpmexec-10.1.0.tgz#451435c04d0feae8e874851aab9bfdfa1d58ec34" + integrity sha512-ojQgfhwlC4PCzHUSVRaTUg3aKxrJbArtc/9KwC3mED1Wc1FSW11pHo0Ufs5DJLDbRK5LhjjEQ8AxzwRIUQVY+A== + dependencies: + "@npmcli/arborist" "^9.0.1" + "@npmcli/package-json" "^6.1.1" + "@npmcli/run-script" "^9.0.1" + ci-info "^4.0.0" + npm-package-arg "^12.0.0" + pacote "^21.0.0" + proc-log "^5.0.0" + read "^4.0.0" + read-package-json-fast "^4.0.0" + semver "^7.3.7" + walk-up-path "^4.0.0" + +libnpmfund@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/libnpmfund/-/libnpmfund-7.0.1.tgz#36d4393706b7f8a9094868adb2f0a3ec9fbd6fc5" + integrity sha512-bkam0l6uKTTwBZ5LhG05tdTnMt75g/jrL5tzPaWIpSMDWlcqFqLy+aqT+FkQCi4fCp8XDEOCZ2POqCyAFUiJuA== + dependencies: + "@npmcli/arborist" "^9.0.1" + +libnpmorg@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/libnpmorg/-/libnpmorg-8.0.0.tgz#e133658149f7e70f5536511a8501ad9348559c2e" + integrity sha512-VO/mxds3Qu67S7/3TsFbykN+7kzpes14P/RiO3ECtLtUYQdlE5ddXGArRgU2tP4hUHZRvyBhc4sSiAXEzTA4eQ== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^18.0.1" + +libnpmpack@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/libnpmpack/-/libnpmpack-9.0.1.tgz#21f4977a84734ec3a5d98dd5507442289a30fe1f" + integrity sha512-0b9x8h0xAiu99ZewqZqZInf82dCxVx2AWB6jqwooIHzey4i2XYcIi0Tcik9GhkayU5nJ5WLD/W9PVyZcIWcm2w== + dependencies: + "@npmcli/arborist" "^9.0.1" + "@npmcli/run-script" "^9.0.1" + npm-package-arg "^12.0.0" + pacote "^21.0.0" + +libnpmpublish@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-11.0.0.tgz#7b1bd0d4e2b388add88e53cc8d689307adbad355" + integrity sha512-c+cBWLWXafHzmSEQwRVKjHP6KkWntvqvAAT83agwmWrOwRpEXWDtiIlkopwzPcLRau6BcS6BwOttTlAWboH3BQ== + dependencies: + ci-info "^4.0.0" + normalize-package-data "^7.0.0" + npm-package-arg "^12.0.0" + npm-registry-fetch "^18.0.1" + proc-log "^5.0.0" + semver "^7.3.7" + sigstore "^3.0.0" + ssri "^12.0.0" + +libnpmsearch@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/libnpmsearch/-/libnpmsearch-9.0.0.tgz#dfe2e86ef28a24d8687f79480a045f002b38542d" + integrity sha512-uMUbX5ynU/imuXlijCPathemyi1EZVtka9PEbaIqghdrjdHmMJITbyTsmSB+muzBWm1NUUFwRRKdpwktEmvipg== + dependencies: + npm-registry-fetch "^18.0.1" + +libnpmteam@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/libnpmteam/-/libnpmteam-8.0.0.tgz#d537352ce727aa57fb1acef318d69571b011d04c" + integrity sha512-GfbxITlY4rVe3PKUU6wBjfNNc4Xho9Jv03N0sdzqho9H+9hynFjiwJpfWGwfVBdtimH+kPQW58qRUMott/Bkyg== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^18.0.1" + +libnpmversion@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/libnpmversion/-/libnpmversion-8.0.0.tgz#68998a1eb2c9c068e721e9221073d78edb4efc73" + integrity sha512-nqHD/YQtC/xLRquvFj2W2hvTNAIWSssJdz5ULCV0jAGBxjlQaPS9s8FNIiJ3w+iina+pCJo5AmlBjA7oWew0JQ== + dependencies: + "@npmcli/git" "^6.0.1" + "@npmcli/run-script" "^9.0.1" + json-parse-even-better-errors "^4.0.0" + proc-log "^5.0.0" + semver "^7.3.7" + lodash-es@^4.17.21: version "4.17.21" resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz" @@ -735,6 +1480,11 @@ lodash@^4.17.21: resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lru-cache@^10.0.1, lru-cache@^10.2.0, lru-cache@^10.2.2: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + magic-string@^0.30.11: version "0.30.17" resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz" @@ -742,6 +1492,23 @@ magic-string@^0.30.11: dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" +make-fetch-happen@^14.0.0, make-fetch-happen@^14.0.1, make-fetch-happen@^14.0.2, make-fetch-happen@^14.0.3: + version "14.0.3" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz#d74c3ecb0028f08ab604011e0bc6baed483fcdcd" + integrity sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ== + dependencies: + "@npmcli/agent" "^3.0.0" + cacache "^19.0.1" + http-cache-semantics "^4.1.1" + minipass "^7.0.2" + minipass-fetch "^4.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^1.0.0" + proc-log "^5.0.0" + promise-retry "^2.0.1" + ssri "^12.0.0" + marked@^15.0.7: version "15.0.7" resolved "https://registry.npmmirror.com/marked/-/marked-15.0.7.tgz" @@ -764,18 +1531,110 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" -minimatch@^9.0.3: +minimatch@^9.0.0, minimatch@^9.0.3, minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.5" resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" +minipass-collect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-2.0.1.tgz#1621bc77e12258a12c60d34e2276ec5c20680863" + integrity sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw== + dependencies: + minipass "^7.0.3" + +minipass-fetch@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-4.0.1.tgz#f2d717d5a418ad0b1a7274f9b913515d3e78f9e5" + integrity sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ== + dependencies: + minipass "^7.0.3" + minipass-sized "^1.0.3" + minizlib "^3.0.1" + optionalDependencies: + encoding "^0.1.13" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.1, minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +minizlib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" + integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== + dependencies: + minipass "^7.0.4" + rimraf "^5.0.5" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + +ms@^2.1.2, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + muggle-string@^0.4.1: version "0.4.1" resolved "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz" integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ== +mute-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" + integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== + naive-ui@^2.41.0: version "2.41.0" resolved "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.41.0.tgz" @@ -806,16 +1665,266 @@ nanoid@^3.3.8: resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +node-gyp@^11.0.0, node-gyp@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-11.1.0.tgz#212a1d9c167c50d727d42659410780b40e07bbd3" + integrity sha512-/+7TuHKnBpnMvUQnsYEb0JOozDZqarQbfNuSGLXIjhStMT0fbw7IdSqWgopOP5xhRZE+lsbIvAHcekddruPZgQ== + dependencies: + env-paths "^2.2.0" + exponential-backoff "^3.1.1" + glob "^10.3.10" + graceful-fs "^4.2.6" + make-fetch-happen "^14.0.3" + nopt "^8.0.0" + proc-log "^5.0.0" + semver "^7.3.5" + tar "^7.4.3" + which "^5.0.0" + +nopt@^8.0.0, nopt@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-8.1.0.tgz#b11d38caf0f8643ce885818518064127f602eae3" + integrity sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A== + dependencies: + abbrev "^3.0.0" + +normalize-package-data@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-7.0.0.tgz#ab4f49d02f2e25108d3f4326f3c13f0de6fa6a0a" + integrity sha512-k6U0gKRIuNCTkwHGZqblCfLfBRh+w1vI6tBo+IeJwq2M8FUiOqhX7GH+GArQGScA7azd1WfyRCvxoXDO3hQDIA== + dependencies: + hosted-git-info "^8.0.0" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + +npm-audit-report@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/npm-audit-report/-/npm-audit-report-6.0.0.tgz#0262e5e2b674fabf0ea47e900fc7384b83de0fbb" + integrity sha512-Ag6Y1irw/+CdSLqEEAn69T8JBgBThj5mw0vuFIKeP7hATYuQuS5jkMjK6xmVB8pr7U4g5Audbun0lHhBDMIBRA== + +npm-bundled@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-4.0.0.tgz#f5b983f053fe7c61566cf07241fab2d4e9d513d3" + integrity sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA== + dependencies: + npm-normalize-package-bin "^4.0.0" + +npm-install-checks@^7.1.0, npm-install-checks@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-7.1.1.tgz#e9d679fc8a1944c75cdcc96478a22f9d0f763632" + integrity sha512-u6DCwbow5ynAX5BdiHQ9qvexme4U3qHW3MWe5NqH+NeBm0LbiH6zvGjNNew1fY+AZZUtVHbOPF3j7mJxbUzpXg== + dependencies: + semver "^7.1.1" + +npm-normalize-package-bin@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz#df79e70cd0a113b77c02d1fe243c96b8e618acb1" + integrity sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w== + +npm-package-arg@^12.0.0, npm-package-arg@^12.0.2: + version "12.0.2" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-12.0.2.tgz#3b1e04ebe651cc45028e298664e8c15ce9c0ca40" + integrity sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA== + dependencies: + hosted-git-info "^8.0.0" + proc-log "^5.0.0" + semver "^7.3.5" + validate-npm-package-name "^6.0.0" + +npm-packlist@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-10.0.0.tgz#35634f0a90f84a811ebdf565eb78d2b36252888c" + integrity sha512-rht9U6nS8WOBDc53eipZNPo5qkAV4X2rhKE2Oj1DYUQ3DieXfj0mKkVmjnf3iuNdtMd8WfLdi2L6ASkD/8a+Kg== + dependencies: + ignore-walk "^7.0.0" + +npm-pick-manifest@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz#6cc120c6473ceea56dfead500f00735b2b892851" + integrity sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ== + dependencies: + npm-install-checks "^7.1.0" + npm-normalize-package-bin "^4.0.0" + npm-package-arg "^12.0.0" + semver "^7.3.5" + +npm-profile@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-11.0.1.tgz#6ffac43f3d186316d37e80986d84aef2470269a2" + integrity sha512-HP5Cw9WHwFS9vb4fxVlkNAQBUhVL5BmW6rAR+/JWkpwqcFJid7TihKUdYDWqHl0NDfLd0mpucheGySqo8ysyfw== + dependencies: + npm-registry-fetch "^18.0.0" + proc-log "^5.0.0" + +npm-registry-fetch@^18.0.0, npm-registry-fetch@^18.0.1, npm-registry-fetch@^18.0.2: + version "18.0.2" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz#340432f56b5a8b1af068df91aae0435d2de646b5" + integrity sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ== + dependencies: + "@npmcli/redact" "^3.0.0" + jsonparse "^1.3.1" + make-fetch-happen "^14.0.0" + minipass "^7.0.2" + minipass-fetch "^4.0.0" + minizlib "^3.0.1" + npm-package-arg "^12.0.0" + proc-log "^5.0.0" + +npm-user-validate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-3.0.0.tgz#9b1410796bf1f1d78297a8096328c55d3083f233" + integrity sha512-9xi0RdSmJ4mPYTC393VJPz1Sp8LyCx9cUnm/L9Qcb3cFO8gjT4mN20P9FAsea8qDHdQ7LtcN8VLh2UT47SdKCw== + +npm@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/npm/-/npm-11.2.0.tgz#fcf483cf8719864a3ab9749cbc5aed691c1c9120" + integrity sha512-PcnFC6gTo9VDkxVaQ1/mZAS3JoWrDjAI+a6e2NgfYQSGDwftJlbdV0jBMi2V8xQPqbGcWaa7p3UP0SKF+Bhm2g== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/arborist" "^9.0.1" + "@npmcli/config" "^10.1.0" + "@npmcli/fs" "^4.0.0" + "@npmcli/map-workspaces" "^4.0.2" + "@npmcli/package-json" "^6.1.1" + "@npmcli/promise-spawn" "^8.0.2" + "@npmcli/redact" "^3.1.1" + "@npmcli/run-script" "^9.0.1" + "@sigstore/tuf" "^3.0.0" + abbrev "^3.0.0" + archy "~1.0.0" + cacache "^19.0.1" + chalk "^5.4.1" + ci-info "^4.1.0" + cli-columns "^4.0.0" + fastest-levenshtein "^1.0.16" + fs-minipass "^3.0.3" + glob "^10.4.5" + graceful-fs "^4.2.11" + hosted-git-info "^8.0.2" + ini "^5.0.0" + init-package-json "^8.0.0" + is-cidr "^5.1.1" + json-parse-even-better-errors "^4.0.0" + libnpmaccess "^10.0.0" + libnpmdiff "^8.0.1" + libnpmexec "^10.1.0" + libnpmfund "^7.0.1" + libnpmorg "^8.0.0" + libnpmpack "^9.0.1" + libnpmpublish "^11.0.0" + libnpmsearch "^9.0.0" + libnpmteam "^8.0.0" + libnpmversion "^8.0.0" + make-fetch-happen "^14.0.3" + minimatch "^9.0.5" + minipass "^7.1.1" + minipass-pipeline "^1.2.4" + ms "^2.1.2" + node-gyp "^11.1.0" + nopt "^8.1.0" + normalize-package-data "^7.0.0" + npm-audit-report "^6.0.0" + npm-install-checks "^7.1.1" + npm-package-arg "^12.0.2" + npm-pick-manifest "^10.0.0" + npm-profile "^11.0.1" + npm-registry-fetch "^18.0.2" + npm-user-validate "^3.0.0" + p-map "^7.0.3" + pacote "^21.0.0" + parse-conflict-json "^4.0.0" + proc-log "^5.0.0" + qrcode-terminal "^0.12.0" + read "^4.1.0" + semver "^7.7.1" + spdx-expression-parse "^4.0.0" + ssri "^12.0.0" + supports-color "^10.0.0" + tar "^6.2.1" + text-table "~0.2.0" + tiny-relative-date "^1.3.0" + treeverse "^3.0.0" + validate-npm-package-name "^6.0.0" + which "^5.0.0" + +p-map@^7.0.2, p-map@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.3.tgz#7ac210a2d36f81ec28b736134810f7ba4418cdb6" + integrity sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA== + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +pacote@^21.0.0: + version "21.0.0" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-21.0.0.tgz#5fe3878a9f808ca5c455c4c1d8ca46eb13351f7b" + integrity sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA== + dependencies: + "@npmcli/git" "^6.0.0" + "@npmcli/installed-package-contents" "^3.0.0" + "@npmcli/package-json" "^6.0.0" + "@npmcli/promise-spawn" "^8.0.0" + "@npmcli/run-script" "^9.0.0" + cacache "^19.0.0" + fs-minipass "^3.0.0" + minipass "^7.0.2" + npm-package-arg "^12.0.0" + npm-packlist "^10.0.0" + npm-pick-manifest "^10.0.0" + npm-registry-fetch "^18.0.0" + proc-log "^5.0.0" + promise-retry "^2.0.1" + sigstore "^3.0.0" + ssri "^12.0.0" + tar "^6.1.11" + +parse-conflict-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-conflict-json/-/parse-conflict-json-4.0.0.tgz#996b1edfc0c727583b56c7644dbb3258fc9e9e4b" + integrity sha512-37CN2VtcuvKgHUs8+0b1uJeEsbGn61GRHz469C94P5xiOoqpDYJYwjg4RY9Vmz39WyZAVkR5++nbJwLMIgOCnQ== + dependencies: + json-parse-even-better-errors "^4.0.0" + just-diff "^6.0.0" + just-diff-apply "^5.2.0" + path-browserify@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz" integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + picocolors@^1.1.1: version "1.1.1" resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== +postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss@^8.4.48, postcss@^8.5.3: version "8.5.3" resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz" @@ -825,11 +1934,83 @@ postcss@^8.4.48, postcss@^8.5.3: picocolors "^1.1.1" source-map-js "^1.2.1" +proc-log@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8" + integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ== + +proggy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/proggy/-/proggy-3.0.0.tgz#874e91fed27fe00a511758e83216a6b65148bd6c" + integrity sha512-QE8RApCM3IaRRxVzxrjbgNMpQEX6Wu0p0KBeoSiSEw5/bsGwZHsshF4LCxH2jp/r6BU+bqA3LrMDEYNfJnpD8Q== + +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/promise-call-limit/-/promise-call-limit-3.0.2.tgz#524b7f4b97729ff70417d93d24f46f0265efa4f9" + integrity sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +promzard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/promzard/-/promzard-2.0.0.tgz#03ad0e4db706544dfdd4f459281f13484fc10c49" + integrity sha512-Ncd0vyS2eXGOjchIRg6PVCYKetJYrW1BSbbIo+bKdig61TB6nH2RQNF2uP+qMpsI73L/jURLWojcw8JNIKZ3gg== + dependencies: + read "^4.0.0" + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +qrcode-terminal@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" + integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== + +read-cmd-shim@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-5.0.0.tgz#6e5450492187a0749f6c80dcbef0debc1117acca" + integrity sha512-SEbJV7tohp3DAAILbEMPXavBjAnMN0tVnh4+9G8ihV4Pq3HYF9h8QNez9zkJ1ILkv9G2BjdzwctznGZXgu/HGw== + +read-package-json-fast@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz#8ccbc05740bb9f58264f400acc0b4b4eee8d1b39" + integrity sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg== + dependencies: + json-parse-even-better-errors "^4.0.0" + npm-normalize-package-bin "^4.0.0" + +read@^4.0.0, read@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/read/-/read-4.1.0.tgz#d97c2556b009b47b16b5bb82311d477cc7503548" + integrity sha512-uRfX6K+f+R8OOrYScaM3ixPY4erg69f8DN6pgTvMcA9iRc8iDhwrA4m3Yu8YYKsXJgVvum+m8PkRboZwwuLzYA== + dependencies: + mute-stream "^2.0.0" + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +rimraf@^5.0.5: + version "5.0.10" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" + integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== + dependencies: + glob "^10.3.7" + rollup@^4.30.1: version "4.34.9" resolved "https://registry.npmmirror.com/rollup/-/rollup-4.34.9.tgz" @@ -858,21 +2039,229 @@ rollup@^4.30.1: "@rollup/rollup-win32-x64-msvc" "4.34.9" fsevents "~2.3.2" +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + seemly@^0.3.6, seemly@^0.3.8: version "0.3.10" resolved "https://registry.npmmirror.com/seemly/-/seemly-0.3.10.tgz" integrity sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q== +semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +sigstore@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-3.1.0.tgz#08dc6c0c425263e9fdab85ffdb6477550e2c511d" + integrity sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q== + dependencies: + "@sigstore/bundle" "^3.1.0" + "@sigstore/core" "^2.0.0" + "@sigstore/protobuf-specs" "^0.4.0" + "@sigstore/sign" "^3.1.0" + "@sigstore/tuf" "^3.1.0" + "@sigstore/verify" "^2.1.0" + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^8.0.3: + version "8.0.5" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz#b9cdb4e7e998509d7659d689ce7697ac21645bee" + integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== + dependencies: + agent-base "^7.1.2" + debug "^4.3.4" + socks "^2.8.3" + +socks@^2.8.3: + version "2.8.4" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.4.tgz#07109755cdd4da03269bda4725baa061ab56d5cc" + integrity sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + source-map-js@^1.2.0, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-expression-parse@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz#a23af9f3132115465dac215c099303e4ceac5794" + integrity sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.21" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz#6d6e980c9df2b6fc905343a3b2d702a6239536c3" + integrity sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg== + +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +ssri@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832" + integrity sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ== + dependencies: + minipass "^7.0.3" + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +supports-color@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-10.0.0.tgz#32000d5e49f1ae70b2645d47701004644a1d7b90" + integrity sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ== + +tar@^6.1.11, tar@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tar@^7.4.3: + version "7.4.3" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" + integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.0.1" + mkdirp "^3.0.1" + yallist "^5.0.0" + +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tiny-relative-date@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" + integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== + treemate@^0.3.11: version "0.3.11" resolved "https://registry.npmmirror.com/treemate/-/treemate-0.3.11.tgz" integrity sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg== +treeverse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/treeverse/-/treeverse-3.0.0.tgz#dd82de9eb602115c6ebd77a574aae67003cb48c8" + integrity sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ== + +tuf-js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tuf-js/-/tuf-js-3.0.1.tgz#e3f07ed3d8e87afaa70607bd1ef801d5c1f57177" + integrity sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA== + dependencies: + "@tufjs/models" "3.0.1" + debug "^4.3.6" + make-fetch-happen "^14.0.1" + typescript@~5.7.2: version "5.7.3" resolved "https://registry.npmmirror.com/typescript/-/typescript-5.7.3.tgz" @@ -883,6 +2272,38 @@ undici-types@~6.20.0: resolved "https://registry.npmmirror.com/undici-types/-/undici-types-6.20.0.tgz" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +unique-filename@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-4.0.0.tgz#a06534d370e7c977a939cd1d11f7f0ab8f1fed13" + integrity sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ== + dependencies: + unique-slug "^5.0.0" + +unique-slug@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-5.0.0.tgz#ca72af03ad0dbab4dad8aa683f633878b1accda8" + integrity sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg== + dependencies: + imurmurhash "^0.1.4" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +validate-npm-package-license@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validate-npm-package-name@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-6.0.0.tgz#3add966c853cfe36e0e8e6a762edd72ae6f1d6ac" + integrity sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg== + vdirs@^0.1.4, vdirs@^0.1.8: version "0.1.8" resolved "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz" @@ -913,6 +2334,13 @@ vscode-uri@^3.0.8: resolved "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz" integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ== +vue-router@4: + version "4.5.0" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.5.0.tgz#58fc5fe374e10b6018f910328f756c3dae081f14" + integrity sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w== + dependencies: + "@vue/devtools-api" "^6.6.4" + vue-tsc@^2.2.4: version "2.2.8" resolved "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.2.8.tgz" @@ -944,3 +2372,58 @@ vueuc@^0.4.63: seemly "^0.3.6" vdirs "^0.1.4" vooks "^0.2.4" + +walk-up-path@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-4.0.0.tgz#590666dcf8146e2d72318164f1f2ac6ef51d4198" + integrity sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +which@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-5.0.0.tgz#d93f2d93f79834d4363c7d0c23e00d07c466c8d6" + integrity sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ== + dependencies: + isexe "^3.1.1" + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +write-file-atomic@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-6.0.0.tgz#e9c89c8191b3ef0606bc79fb92681aa1aa16fa93" + integrity sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== diff --git a/fund_service.py b/fund_service.py deleted file mode 100644 index fa4218b..0000000 --- a/fund_service.py +++ /dev/null @@ -1,51 +0,0 @@ -import akshare as ak -import pandas as pd - -class FundService: - def search_funds(self, keyword, market_type='ETF'): - """ - 搜索基金代码 - :param keyword: 搜索关键词 - :return: 匹配的基金列表 - """ - try: - # 获取ETF和LOF数据 - if market_type == 'ETF': - df = ak.fund_etf_spot_em() - else: - df = ak.fund_lof_spot_em() - - # 转换列名 - df = df.rename(columns={ - "代码": "symbol", - "名称": "name", - "最新价": "price", - "涨跌额": "price_change", - "涨跌幅": "price_change_percent", - "成交量": "volume", - "流通市值": "market_value", - "总市值": "total_value", - "基金折价率": "discount_rate", - }) - - # 模糊匹配搜索(同时匹配代码和名称) - mask = (df['name'].str.contains(keyword, case=False, na=False) | - df['symbol'].str.contains(keyword, case=False, na=False)) - results = df[mask] - - # 格式化返回结果并处理 NaN 值 - formatted_results = [] - for _, row in results.iterrows(): - formatted_results.append({ - 'name': row['name'] if pd.notna(row['name']) else '', - 'symbol': str(row['symbol']) if pd.notna(row['symbol']) else '', - 'price': float(row['price']) if pd.notna(row['price']) else 0.0, - 'volume': float(row['volume']) if pd.notna(row['volume']) else 0.0, - 'market_value': float(row['market_value']) if pd.notna(row['market_value']) else 0.0, - 'total_value': float(row['total_value']) if pd.notna(row['total_value']) else 0.0, - }) - - return formatted_results - - except Exception as e: - raise Exception(f"搜索基金代码失败: {str(e)}") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f758152..988cf92 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,3 +35,5 @@ html5lib==1.1 lxml==4.9.4 jsonpath==0.82.2 openpyxl==3.1.5 +python-jose[cryptography]==3.4.0 +passlib==1.7.4 diff --git a/services/ai_analyzer.py b/services/ai_analyzer.py index f7fa01e..eed82ff 100644 --- a/services/ai_analyzer.py +++ b/services/ai_analyzer.py @@ -231,35 +231,83 @@ class AIAnalyzer: async for chunk in response.aiter_text(): if chunk: - chunk_str = chunk.strip() - if chunk_str.startswith("data: "): - chunk_str = chunk_str[6:] # 去除"data: "前缀 - - if chunk_str == "[DONE]": - logger.debug("收到流结束标记 [DONE]") - continue - - try: - # 解析数据块 - chunk_data = json.loads(chunk_str) - delta = chunk_data.get("choices", [{}])[0].get("delta", {}) - content = delta.get("content", "") - - if content: - chunk_count += 1 - buffer += content - collected_messages.append(content) + # 分割多行响应(处理某些API可能在一个chunk中返回多行) + lines = chunk.strip().split('\n') + for line in lines: + line = line.strip() + if not line: + continue - # 直接发送每个内容片段,不累积 - yield json.dumps({ - "stock_code": stock_code, - "ai_analysis_chunk": content, - "status": "analyzing" - }) - except json.JSONDecodeError: - # 忽略无法解析的块 - logger.error(f"JSON解析错误,块内容: {chunk_str[:100]}...") - continue + # 处理以data:开头的行 + if line.startswith("data: "): + line = line[6:] # 去除"data: "前缀 + + if line == "[DONE]": + logger.debug("收到流结束标记 [DONE]") + continue + + try: + # 处理特殊错误情况 + if "error" in line.lower(): + error_msg = line + try: + error_data = json.loads(line) + error_msg = error_data.get("error", line) + except: + pass + + logger.error(f"流式响应中收到错误: {error_msg}") + yield json.dumps({ + "stock_code": stock_code, + "error": f"流式响应错误: {error_msg}", + "status": "error" + }) + continue + + # 尝试解析JSON + chunk_data = json.loads(line) + + # 检查是否有finish_reason + finish_reason = chunk_data.get("choices", [{}])[0].get("finish_reason") + if finish_reason == "stop": + logger.debug("收到finish_reason=stop,流结束") + continue + + # 获取delta内容 + delta = chunk_data.get("choices", [{}])[0].get("delta", {}) + + # 检查delta是否为空对象 + if not delta or delta == {}: + logger.debug("收到空的delta对象,跳过") + continue + + content = delta.get("content", "") + + if content: + chunk_count += 1 + buffer += content + collected_messages.append(content) + + # 直接发送每个内容片段,不累积 + yield json.dumps({ + "stock_code": stock_code, + "ai_analysis_chunk": content, + "status": "analyzing" + }) + except json.JSONDecodeError: + # 记录解析错误并尝试恢复 + logger.error(f"JSON解析错误,块内容: {line}") + + # 如果是特定错误模式,处理它 + if "streaming failed after retries" in line.lower(): + logger.error("检测到流式传输失败") + yield json.dumps({ + "stock_code": stock_code, + "error": "流式传输失败,请稍后重试", + "status": "error" + }) + return + continue logger.info(f"AI流式处理完成,共收到 {chunk_count} 个内容片段,总长度: {len(buffer)}") diff --git a/services/fund_service_async.py b/services/fund_service_async.py index 8bafada..e232188 100644 --- a/services/fund_service_async.py +++ b/services/fund_service_async.py @@ -56,8 +56,11 @@ class FundServiceAsync: 'market_value': float(row['market_value']) if pd.notna(row['market_value']) else 0.0, 'total_value': float(row['total_value']) if pd.notna(row['total_value']) else 0.0, }) + # 限制只返回前10个结果 + if len(formatted_results) >= 10: + break - logger.info(f"基金搜索完成,找到 {len(formatted_results)} 个匹配项") + logger.info(f"基金搜索完成,找到 {len(formatted_results)} 个匹配项(限制显示前10个)") return formatted_results except Exception as e: diff --git a/services/stock_analyzer_service.py b/services/stock_analyzer_service.py index 191663f..d73738f 100644 --- a/services/stock_analyzer_service.py +++ b/services/stock_analyzer_service.py @@ -60,6 +60,30 @@ class StockAnalyzerService: # 获取股票数据 df = await self.data_provider.get_stock_data(stock_code, market_type) + # 检查是否有错误 + if hasattr(df, 'error'): + error_msg = df.error + logger.error(f"获取股票数据时出错: {error_msg}") + yield json.dumps({ + "stock_code": stock_code, + "market_type": market_type, + "error": error_msg, + "status": "error" + }) + return + + # 检查数据是否为空 + if df.empty: + error_msg = f"获取到的股票 {stock_code} 数据为空" + logger.error(error_msg) + yield json.dumps({ + "stock_code": stock_code, + "market_type": market_type, + "error": error_msg, + "status": "error" + }) + return + # 计算技术指标 df_with_indicators = self.indicator.calculate_indicators(df) diff --git a/services/stock_data_provider.py b/services/stock_data_provider.py index 48cebb4..af2afa0 100644 --- a/services/stock_data_provider.py +++ b/services/stock_data_provider.py @@ -5,6 +5,7 @@ import asyncio import os from typing import Dict, List, Optional, Tuple, Any from logger import get_logger +import re # 获取日志器 logger = get_logger() @@ -57,27 +58,16 @@ class StockDataProvider: if end_date is None: end_date = datetime.now().strftime('%Y%m%d') + # 确保日期格式统一(移除可能的'-'符号) + if isinstance(start_date, str) and '-' in start_date: + start_date = start_date.replace('-', '') + if isinstance(end_date, str) and '-' in end_date: + end_date = end_date.replace('-', '') + try: - # 验证股票代码格式 if market_type == 'A': - # 上海证券交易所股票代码以6开头 - # 深圳证券交易所股票代码以0或3开头 - # 科创板股票代码以688开头 - # 北京证券交易所股票代码以8开头 - valid_prefixes = ['0', '3', '6', '688', '8'] - valid_format = False - - for prefix in valid_prefixes: - if stock_code.startswith(prefix): - valid_format = True - break - - if not valid_format: - error_msg = f"无效的A股股票代码格式: {stock_code}。A股代码应以0、3、6、688或8开头" - logger.error(f"[股票代码格式错误] {error_msg}") - raise ValueError(error_msg) - logger.debug(f"获取A股数据: {stock_code}") + df = ak.stock_zh_a_hist( symbol=stock_code, start_date=start_date, @@ -96,13 +86,72 @@ class StockDataProvider: elif market_type in ['US']: logger.debug(f"获取美股数据: {stock_code}") - df = ak.stock_us_daily( - symbol=stock_code, - adjust="qfq" - ) - # 过滤日期 - df = df[(df.index >= start_date) & (df.index <= end_date)] + try: + df = ak.stock_us_daily( + symbol=stock_code, + adjust="qfq" + ) + logger.debug(f"美股数据原始列: {df.columns.tolist()}") + logger.debug(f"美股数据形状: {df.shape}") + + # 确保索引是日期时间类型 + if not isinstance(df.index, pd.DatetimeIndex): + # 如果存在命名为'date'的列,将其设为索引 + if 'date' in df.columns: + df['date'] = pd.to_datetime(df['date']) + df.set_index('date', inplace=True) + logger.debug("已将'date'列设置为索引") + else: + # 否则将当前索引转换为日期类型 + df.index = pd.to_datetime(df.index) + logger.debug("已将索引转换为DatetimeIndex") + + # 计算美股的成交额(Amount)= 成交量(Volume)× 收盘价(Close) + volume_col = next((col for col in df.columns if col.lower() == 'volume'), None) + close_col = next((col for col in df.columns if col.lower() == 'close'), None) + + if volume_col and close_col: + df['amount'] = df[volume_col] * df[close_col] + logger.debug("已为美股数据计算成交额(amount)字段") + else: + logger.warning(f"美股数据缺少volume或close列,无法计算amount。当前列: {df.columns.tolist()}") + # 添加空的amount列,避免后续处理错误 + df['amount'] = 0.0 + + # 将所有列名转为小写以进行统一处理 + df.columns = [col.lower() for col in df.columns] + + except Exception as e: + logger.error(f"获取美股数据失败 {stock_code}: {str(e)}") + raise ValueError(f"获取美股数据失败 {stock_code}: {str(e)}") + # 将字符串日期转换为日期时间对象进行比较 + try: + # 尝试多种格式解析日期 + # 如果日期是数字格式(20220101),使用适当的格式 + if start_date.isdigit() and len(start_date) == 8: + start_date_dt = pd.to_datetime(start_date, format='%Y%m%d') + else: + # 否则让pandas自动推断格式 + start_date_dt = pd.to_datetime(start_date) + + if end_date.isdigit() and len(end_date) == 8: + end_date_dt = pd.to_datetime(end_date, format='%Y%m%d') + else: + end_date_dt = pd.to_datetime(end_date) + except Exception as e: + logger.warning(f"日期转换出错: {str(e)},使用默认值") + # 如果转换失败,使用合理的默认值 + start_date_dt = pd.to_datetime('20000101', format='%Y%m%d') + end_date_dt = pd.to_datetime(datetime.now().strftime('%Y%m%d'), format='%Y%m%d') + + # 过滤日期 + try: + df = df[(df.index >= start_date_dt) & (df.index <= end_date_dt)] + logger.debug(f"日期过滤后数据点数: {len(df)}") + except Exception as e: + logger.warning(f"日期过滤出错: {str(e)},返回原始数据") + elif market_type in ['ETF', 'LOF']: logger.debug(f"获取{market_type}基金数据: {stock_code}") df = ak.fund_etf_hist_sina( @@ -122,8 +171,31 @@ class StockDataProvider: # 实际数据列:['日期', '股票代码', '开盘', '收盘', '最高', '最低', '成交量', '成交额', '振幅', '涨跌幅', '涨跌额', '换手率'] df.columns = ['Date', 'Code', 'Open', 'Close', 'High', 'Low', 'Volume', 'Amount', 'Amplitude', 'Change_pct', 'Change', 'Turnover'] elif market_type in ['HK', 'US']: - # 根据实际情况调整 - df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Amount'] + # 美股数据列可能不同,需要通过映射处理 + columns_mapping = { + 'open': 'Open', + 'high': 'High', + 'low': 'Low', + 'close': 'Close', + 'volume': 'Volume', + 'amount': 'Amount' + } + + # 创建新的DataFrame以确保列顺序和存在性 + new_df = pd.DataFrame(index=df.index) + + # 遍历映射,填充新DataFrame + for orig_col, new_col in columns_mapping.items(): + if orig_col in df.columns: + new_df[new_col] = df[orig_col] + else: + # 如果原始列不存在,创建一个填充0的列 + logger.warning(f"数据中缺少{orig_col}列,使用0值填充") + new_df[new_col] = 0.0 + + # 替换原始df + df = new_df + elif market_type in ['ETF', 'LOF']: # 基金数据可能有不同的列 df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Amount'] @@ -143,7 +215,11 @@ class StockDataProvider: error_msg = f"获取{market_type}数据失败 {stock_code}: {str(e)}" logger.error(error_msg) logger.exception(e) - raise Exception(error_msg) + # 使用空的DataFrame并添加错误信息,而不是抛出异常 + # 这样上层调用者可以检查是否有错误并适当处理 + df = pd.DataFrame() + df.error = error_msg # 添加错误属性 + return df async def get_multiple_stocks_data(self, stock_codes: List[str], market_type: str = 'A', @@ -181,4 +257,4 @@ class StockDataProvider: results = await asyncio.gather(*tasks) # 构建结果字典,过滤掉失败的请求 - return {code: df for code, df in results if df is not None} \ No newline at end of file + return {code: df for code, df in results if df is not None} \ No newline at end of file diff --git a/services/us_stock_service_async.py b/services/us_stock_service_async.py index d0a5b0b..97c94b1 100644 --- a/services/us_stock_service_async.py +++ b/services/us_stock_service_async.py @@ -49,8 +49,11 @@ class USStockServiceAsync: 'price': float(row['price']) if pd.notna(row['price']) else 0.0, 'market_value': float(row['market_value']) if pd.notna(row['market_value']) else 0.0 }) + # 限制只返回前10个结果 + if len(formatted_results) >= 10: + break - logger.info(f"美股搜索完成,找到 {len(formatted_results)} 个匹配项") + logger.info(f"美股搜索完成,找到 {len(formatted_results)} 个匹配项(限制显示前10个)") return formatted_results except Exception as e: diff --git a/stock_analyzer.py b/stock_analyzer.py deleted file mode 100644 index c28d4ac..0000000 --- a/stock_analyzer.py +++ /dev/null @@ -1,758 +0,0 @@ -import pandas as pd -import numpy as np -from datetime import datetime, timedelta -import os -import requests -from typing import Dict, List, Optional, Tuple, Generator -from dotenv import load_dotenv -import json -from logger import get_logger -from utils.api_utils import APIUtils - -# 获取日志器 -logger = get_logger() - -class StockAnalyzer: - def __init__(self, initial_cash=1000000, custom_api_url=None, custom_api_key=None, custom_api_model=None, custom_api_timeout=None): - - # 加载环境变量 - load_dotenv() - - # 设置 API 配置,优先使用自定义配置,否则使用环境变量 - self.API_URL = custom_api_url or os.getenv('API_URL') - self.API_KEY = custom_api_key or os.getenv('API_KEY') - self.API_MODEL = custom_api_model or os.getenv('API_MODEL', 'gpt-3.5-turbo') - self.API_TIMEOUT = int(custom_api_timeout or os.getenv('API_TIMEOUT', 60)) - - logger.debug(f"初始化StockAnalyzer: API_URL={self.API_URL}, API_MODEL={self.API_MODEL}, API_KEY={'已提供' if self.API_KEY else '未提供'}, API_TIMEOUT={self.API_TIMEOUT}") - - # 配置参数 - self.params = { - 'ma_periods': {'short': 5, 'medium': 20, 'long': 60}, - 'rsi_period': 14, - 'bollinger_period': 20, - 'bollinger_std': 2, - 'volume_ma_period': 20, - 'atr_period': 14 - } - - - def get_stock_data(self, stock_code, market_type='A', start_date=None, end_date=None): - """获取股票或基金数据""" - import akshare as ak - - if start_date is None: - start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d') - if end_date is None: - end_date = datetime.now().strftime('%Y%m%d') - - try: - # 验证股票代码格式 - if market_type == 'A': - # 上海证券交易所股票代码以6开头 - # 深圳证券交易所股票代码以0或3开头 - # 科创板股票代码以688开头 - # 北京证券交易所股票代码以8开头 - valid_prefixes = ['0', '3', '6', '688', '8'] - valid_format = False - - for prefix in valid_prefixes: - if stock_code.startswith(prefix): - valid_format = True - break - - if not valid_format: - error_msg = f"无效的A股股票代码格式: {stock_code}。A股代码应以0、3、6、688或8开头" - logger.error(f"[股票代码格式错误] {error_msg}") - raise ValueError(error_msg) - - df = ak.stock_zh_a_hist( - symbol=stock_code, - start_date=start_date, - end_date=end_date, - adjust="qfq" - ) - elif market_type == 'HK': - df = ak.stock_hk_daily( - symbol=stock_code, - adjust="qfq" - ) - elif market_type == 'US': - df = ak.stock_us_hist( - symbol=stock_code, - start_date=start_date, - end_date=end_date, - adjust="qfq" - ) - elif market_type == 'ETF': - df = ak.fund_etf_hist_em( - symbol=stock_code, - period="daily", - start_date=start_date, - end_date=end_date, - adjust="qfq" - ) - elif market_type == 'LOF': - df = ak.fund_lof_hist_em( - symbol=stock_code, - period="daily", - start_date=start_date, - end_date=end_date, - adjust="qfq" - ) - else: - raise ValueError(f"不支持的市场类型: {market_type}") - - # 重命名列名以匹配分析需求 - df = df.rename(columns={ - "日期": "date", - "开盘": "open", - "收盘": "close", - "最高": "high", - "最低": "low", - "成交量": "volume" - }) - - # 确保日期格式正确 - df['date'] = pd.to_datetime(df['date']) - - # 数据类型转换 - numeric_columns = ['open', 'close', 'high', 'low', 'volume'] - df[numeric_columns] = df[numeric_columns].apply(pd.to_numeric, errors='coerce') - - # 删除空值 - df = df.dropna() - - return df.sort_values('date') - - # except ValueError as ve: - # # 捕获格式验证错误 - # logger.error(f"[股票代码格式错误] {str(ve)}") - # raise Exception(f"股票代码格式错误: {str(ve)}") - except Exception as e: - logger.error(f"[获取数据失败] {str(e)}") - raise Exception(f"获取数据失败: {str(e)}") - - def calculate_ema(self, series, period): - """计算指数移动平均线""" - return series.ewm(span=period, adjust=False).mean() - - def calculate_rsi(self, series, period): - """计算RSI指标""" - delta = series.diff() - gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() - loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() - rs = gain / loss - return 100 - (100 / (1 + rs)) - - def calculate_macd(self, series): - """计算MACD指标""" - exp1 = series.ewm(span=12, adjust=False).mean() - exp2 = series.ewm(span=26, adjust=False).mean() - macd = exp1 - exp2 - signal = macd.ewm(span=9, adjust=False).mean() - hist = macd - signal - return macd, signal, hist - - def calculate_bollinger_bands(self, series, period, std_dev): - """计算布林带""" - middle = series.rolling(window=period).mean() - std = series.rolling(window=period).std() - upper = middle + (std * std_dev) - lower = middle - (std * std_dev) - return upper, middle, lower - - def calculate_atr(self, df, period): - """计算ATR指标""" - high = df['high'] - low = df['low'] - close = df['close'].shift(1) - - tr1 = high - low - tr2 = abs(high - close) - tr3 = abs(low - close) - - tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1) - return tr.rolling(window=period).mean() - - def calculate_indicators(self, df): - """计算技术指标""" - try: - # 计算移动平均线 - df['MA5'] = self.calculate_ema(df['close'], self.params['ma_periods']['short']) - df['MA20'] = self.calculate_ema(df['close'], self.params['ma_periods']['medium']) - df['MA60'] = self.calculate_ema(df['close'], self.params['ma_periods']['long']) - - # 计算RSI - df['RSI'] = self.calculate_rsi(df['close'], self.params['rsi_period']) - - # 计算MACD - df['MACD'], df['Signal'], df['MACD_hist'] = self.calculate_macd(df['close']) - - # 计算布林带 - df['BB_upper'], df['BB_middle'], df['BB_lower'] = self.calculate_bollinger_bands( - df['close'], - self.params['bollinger_period'], - self.params['bollinger_std'] - ) - - # 成交量分析 - df['Volume_MA'] = df['volume'].rolling(window=self.params['volume_ma_period']).mean() - df['Volume_Ratio'] = df['volume'] / df['Volume_MA'] - - # 计算ATR和波动率 - df['ATR'] = self.calculate_atr(df, self.params['atr_period']) - df['Volatility'] = df['ATR'] / df['close'] * 100 - - # 动量指标 - df['ROC'] = df['close'].pct_change(periods=10) * 100 - - return df - - except Exception as e: - print(f"计算技术指标时出错: {str(e)}") - raise - - def calculate_score(self, df): - """计算评分""" - try: - score = 0 - latest = df.iloc[-1] - - # 趋势得分 (30分) - if latest['MA5'] > latest['MA20']: - score += 15 - if latest['MA20'] > latest['MA60']: - score += 15 - - # RSI得分 (20分) - if 30 <= latest['RSI'] <= 70: - score += 20 - elif latest['RSI'] < 30: # 超卖 - score += 15 - - # MACD得分 (20分) - if latest['MACD'] > latest['Signal']: - score += 20 - - # 成交量得分 (30分) - if latest['Volume_Ratio'] > 1.5: - score += 30 - elif latest['Volume_Ratio'] > 1: - score += 15 - - return score - - except Exception as e: - print(f"计算评分时出错: {str(e)}") - raise - - def get_ai_analysis(self, df, stock_code, market_type='A', stream=False): - """使用 OpenAI 进行 AI 分析""" - try: - logger.info(f"开始AI分析 {stock_code}, 流式模式: {stream}") - recent_data = df.tail(14).to_dict('records') - - technical_summary = { - 'trend': 'upward' if df.iloc[-1]['MA5'] > df.iloc[-1]['MA20'] else 'downward', - 'volatility': f"{df.iloc[-1]['Volatility']:.2f}%", - 'volume_trend': 'increasing' if df.iloc[-1]['Volume_Ratio'] > 1 else 'decreasing', - 'rsi_level': df.iloc[-1]['RSI'] - } - - # 根据市场类型调整分析提示 - if market_type in ['ETF', 'LOF']: - prompt = f""" - 分析基金 {stock_code}: - - 技术指标概要: - {technical_summary} - - 近14日交易数据: - {recent_data} - - 请提供: - 1. 净值走势分析(包含支撑位和压力位) - 2. 成交量分析及其对净值的影响 - 3. 风险评估(包含波动率和折溢价分析) - 4. 短期和中期净值预测 - 5. 关键价格位分析 - 6. 申购赎回建议(包含止损位) - - 请基于技术指标和市场表现进行分析,给出具体数据支持。 - """ - elif market_type == 'US': - prompt = f""" - 分析美股 {stock_code}: - - 技术指标概要: - {technical_summary} - - 近14日交易数据: - {recent_data} - - 请提供: - 1. 趋势分析(包含支撑位和压力位,美元计价) - 2. 成交量分析及其含义 - 3. 风险评估(包含波动率和美股市场特有风险) - 4. 短期和中期目标价位(美元) - 5. 关键技术位分析 - 6. 具体交易建议(包含止损位) - - 请基于技术指标和美股市场特点进行分析,给出具体数据支持。 - """ - elif market_type == 'HK': - prompt = f""" - 分析港股 {stock_code}: - - 技术指标概要: - {technical_summary} - - 近14日交易数据: - {recent_data} - - 请提供: - 1. 趋势分析(包含支撑位和压力位,港币计价) - 2. 成交量分析及其含义 - 3. 风险评估(包含波动率和港股市场特有风险) - 4. 短期和中期目标价位(港币) - 5. 关键技术位分析 - 6. 具体交易建议(包含止损位) - - 请基于技术指标和港股市场特点进行分析,给出具体数据支持。 - """ - else: # A股 - prompt = f""" - 分析A股 {stock_code}: - - 技术指标概要: - {technical_summary} - - 近14日交易数据: - {recent_data} - - 请提供: - 1. 趋势分析(包含支撑位和压力位) - 2. 成交量分析及其含义 - 3. 风险评估(包含波动率分析) - 4. 短期和中期目标价位 - 5. 关键技术位分析 - 6. 具体交易建议(包含止损位) - - 请基于技术指标和A股市场特点进行分析,给出具体数据支持。 - """ - - logger.debug(f"生成的AI分析提示词: {self._truncate_json_for_logging(prompt, 100)}...") - - # 检查API配置 - if not self.API_URL: - error_msg = "API URL未配置,无法进行AI分析" - logger.error(f"[API配置错误] {error_msg}") - return error_msg if not stream else (yield json.dumps({"error": error_msg})) - - if not self.API_KEY: - error_msg = "API Key未配置,无法进行AI分析" - logger.error(f"[API配置错误] {error_msg}") - return error_msg if not stream else (yield json.dumps({"error": error_msg})) - - # 标准化API URL - api_url = APIUtils.format_api_url(self.API_URL) - - logger.debug(f"标准化后的API URL: {api_url}") - - # 构建请求头和请求体 - headers = { - "Authorization": f"Bearer {self.API_KEY}", - "Content-Type": "application/json" - } - - payload = { - "model": self.API_MODEL, - "messages": [{"role": "user", "content": prompt}] - } - - # 流式处理设置 - if stream: - logger.debug(f"配置流式参数,使用API URL: {api_url}") - payload["stream"] = True # 明确设置stream参数为True - - try: - logger.debug(f"发起流式API请求: {api_url}") - logger.debug(f"请求载荷: {self._truncate_json_for_logging(payload)}") - - response = requests.post( - api_url, - headers=headers, - json=payload, - timeout=self.API_TIMEOUT, # 增加超时时间 - stream=True - ) - - logger.debug(f"API流式响应状态码: {response.status_code}") - - if response.status_code == 200: - logger.info(f"成功获取API流式响应,开始处理") - yield from self._process_ai_stream(response, stock_code) - else: - try: - error_response = response.json() - error_text = self._truncate_json_for_logging(error_response) - except: - error_text = response.text[:500] if response.text else "无响应内容" - - error_msg = f"API请求失败: 状态码 {response.status_code}, 响应: {error_text}" - logger.error(f"[API请求失败] {error_msg}") - yield json.dumps({"stock_code": stock_code, "error": error_msg}) - - except Exception as e: - error_msg = f"流式API请求异常: {str(e)}" - logger.error(f"[流式API异常] {error_msg}") - logger.exception(e) - yield json.dumps({"stock_code": stock_code, "error": error_msg}) - else: - # 非流式处理 - logger.debug(f"发起非流式API请求: {api_url}") - - try: - response = requests.post( - api_url, - headers=headers, - json=payload, - timeout=self.API_TIMEOUT - ) - - logger.debug(f"API非流式响应状态码: {response.status_code}") - - if response.status_code == 200: - api_response = response.json() - content = api_response['choices'][0]['message']['content'] - logger.info(f"成功获取AI分析结果,长度: {len(content)}") - logger.debug(f"AI分析结果前100字符: {content[:100]}...") - return content - else: - try: - error_response = response.json() - error_text = self._truncate_json_for_logging(error_response) - except: - error_text = response.text[:500] if response.text else "无响应内容" - - error_msg = f"API请求失败: 状态码 {response.status_code}, 响应: {error_text}" - logger.error(f"[API请求失败] {error_msg}") - return error_msg - - except Exception as e: - error_msg = f"非流式API请求异常: {str(e)}" - logger.error(f"[非流式API异常] {error_msg}") - logger.exception(e) - return error_msg - - except Exception as e: - error_msg = f"AI 分析过程中发生错误: {str(e)}" - logger.error(f"[AI分析异常] {error_msg}") - logger.exception(e) - - if stream: - logger.debug("在流式模式下返回异常信息") - error_json = json.dumps({"stock_code": stock_code, "error": error_msg}) - logger.info(f"流式异常输出: {error_json}") - yield error_json - else: - return error_msg - - def _truncate_json_for_logging(self, json_obj, max_length=500): - """截断JSON对象用于日志记录,避免日志过大 - - Args: - json_obj: 要截断的JSON对象 - max_length: 最大字符长度,默认500 - - Returns: - str: 截断后的JSON字符串 - """ - json_str = json.dumps(json_obj, ensure_ascii=False) - if len(json_str) <= max_length: - return json_str - return json_str[:max_length] + f"... [截断,总长度: {len(json_str)}字符]" - - def _process_ai_stream(self, response, stock_code) -> Generator[str, None, None]: - """处理AI流式响应""" - logger.info(f"开始处理股票 {stock_code} 的AI流式响应\n") - buffer = "" - chunk_count = 0 - - try: - for line in response.iter_lines(): - if line: - line = line.decode('utf-8') - - # 跳过保持连接的空行 - if line.strip() == '': - logger.debug("跳过空行") - continue - - # 数据行通常以"data: "开头 - if line.startswith('data: '): - data_content = line[6:] # 移除 "data: " 前缀 - - # 检查是否为流的结束 - if data_content.strip() == '[DONE]': - logger.debug("收到流结束标记 [DONE]") - break - - try: - json_data = json.loads(data_content) - - # 检查 choices 列表是否为空 - if 'choices' in json_data and json_data['choices']: - delta = json_data['choices'][0].get('delta', {}) - content = delta.get('content', '') - - if content: - chunk_count += 1 - buffer += content - - # 创建包含AI分析片段的JSON - chunk_json = json.dumps({ - "stock_code": stock_code, - "ai_analysis_chunk": content - }) - yield chunk_json - except json.JSONDecodeError as e: - logger.error(f"[JSON解析错误] {str(e)}, 行内容: {data_content}") - # 忽略无法解析的JSON - pass - else: - logger.warning(f"收到非'data:'开头的行: {line}") - - logger.info(f"AI流式处理完成,共收到 {chunk_count} 个内容片段,总长度: {len(buffer)}") - - # 如果buffer不为空,最后一次发送完整内容 - if buffer and not buffer.endswith('\n'): - logger.debug("发送换行符") - yield json.dumps({"stock_code": stock_code, "ai_analysis_chunk": "\n"}) - - except Exception as e: - error_msg = f"处理AI流式响应时出错: {str(e)}" - logger.error(f"[流式响应异常] {error_msg}") - logger.exception(e) - yield json.dumps({"stock_code": stock_code, "error": error_msg}) - - - def get_recommendation(self, score): - """根据得分给出建议""" - logger.debug(f"根据评分 {score} 生成投资建议") - if score >= 80: - return '强烈推荐买入' - elif score >= 60: - return '建议买入' - elif score >= 40: - return '观望' - elif score >= 20: - return '建议卖出' - else: - return '强烈建议卖出' - - def analyze_stock(self, stock_code, market_type='A', stream=False): - """分析单只""" - logger.info(f"开始分析 {stock_code}, 市场类型: {market_type}, 流式模式: {stream}") - - try: - # 获取股票数据 - try: - df = self.get_stock_data(stock_code, market_type) - except Exception as e: - # 捕获股票数据获取异常 - error_msg = str(e) - logger.error(f"[数据获取异常] {error_msg}") - - # 格式化错误响应 - error_response = { - 'stock_code': stock_code, - 'error': error_msg, - 'status': 'error', - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - - if stream: - return (yield json.dumps(error_response)) - else: - return error_response - - # 检查数据是否为空 - if df.empty: - error_msg = f" {stock_code} 数据为空" - logger.error(f"[空数据] {error_msg}") - - # 格式化错误响应 - error_response = { - 'stock_code': stock_code, - 'error': error_msg, - 'status': 'error', - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - - if stream: - return (yield json.dumps(error_response)) - else: - return error_response - - # 计算技术指标 - logger.debug(f"计算 {stock_code} 技术指标") - df = self.calculate_indicators(df) - - # 评分系统 - logger.debug(f"计算 {stock_code} 评分") - score = self.calculate_score(df) - logger.info(f"{stock_code} 评分结果: {score}") - - # 获取最新数据 - latest = df.iloc[-1] - prev = df.iloc[-2] - - # 生成报告 - report = { - 'stock_code': stock_code, - 'market_type': market_type, # 添加市场类型 - 'analysis_date': datetime.now().strftime('%Y-%m-%d'), - 'score': score, - 'price': latest['close'], - 'price_change': (latest['close'] - prev['close']) / prev['close'] * 100, - 'ma_trend': 'UP' if latest['MA5'] > latest['MA20'] else 'DOWN', - 'rsi': latest['RSI'] if not pd.isna(latest['RSI']) else None, - 'macd_signal': 'BUY' if latest['MACD'] > latest['Signal'] else 'SELL', - 'volume_status': 'HIGH' if latest['Volume_Ratio'] > 1.5 else 'NORMAL', - 'recommendation': self.get_recommendation(score) - } - logger.debug(f"生成 {stock_code} 基础报告: {self._truncate_json_for_logging(report, 100)}...") - - if stream: - logger.info(f"以流式模式返回 {stock_code} 分析结果") - # 先返回基本报告结构 - base_report = dict(report) - base_report['ai_analysis'] = '' - base_report_json = json.dumps(base_report) - logger.debug(f"基础报告JSON: {self._truncate_json_for_logging(base_report_json, 100)}...") - logger.info(f"发送基础报告: {base_report_json}") - yield base_report_json - - # 然后流式返回AI分析部分 - logger.debug(f"开始获取 {stock_code} 的流式AI分析") - ai_chunks_count = 0 - for ai_chunk in self.get_ai_analysis(df, stock_code, market_type, stream=True): - ai_chunks_count += 1 - yield ai_chunk - logger.info(f" {stock_code} 流式AI分析完成,共发送 {ai_chunks_count} 个块") - else: - logger.info(f"以非流式模式返回 {stock_code} 分析结果") - logger.debug(f"开始获取 {stock_code} 的AI分析") - report['ai_analysis'] = self.get_ai_analysis(df, stock_code, market_type) - logger.debug(f"AI分析结果长度: {len(report['ai_analysis'])}") - return report - - except Exception as e: - error_msg = f"分析 {stock_code} 时出错: {str(e)}\n" - logger.error(f"[分析异常] {error_msg}") - logger.exception(e) - - # 格式化错误响应 - error_response = { - 'stock_code': stock_code, - 'error': error_msg, - 'status': 'error', - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - - if stream: - return (yield json.dumps(error_response)) - else: - return error_response - - def scan_stocks(self, stock_codes, market_type='A', min_score=60, stream=False): - """扫描多只""" - logger.info(f"开始扫描 {len(stock_codes)} 只, 市场类型: {market_type}, 最低评分: {min_score}, 流式模式: {stream}") - - if not stream: - # 非流式模式 - recommended_stocks = [] - stock_count = 0 - error_count = 0 - - for stock_code in stock_codes: - stock_count += 1 - logger.info(f"扫描进度: {stock_count}/{len(stock_codes)}, 当前: {stock_code}") - - try: - logger.debug(f"分析: {stock_code}") - report = self.analyze_stock(stock_code, market_type) - - # 检查是否有错误 - if isinstance(report, dict) and 'error' in report: - error_count += 1 - logger.warning(f"[扫描错误] {stock_code}: {report['error']}") - continue - - # 检查评分是否达到最低要求 - if report['score'] >= min_score: - logger.info(f" {stock_code} 评分 {report['score']} >= {min_score},添加到推荐列表") - recommended_stocks.append(report) - else: - logger.debug(f" {stock_code} 评分 {report['score']} < {min_score},不添加到推荐列表") - except Exception as e: - error_count += 1 - error_msg = f"分析 {stock_code} 时出错: {str(e)}" - logger.error(f"[扫描异常] {error_msg}") - logger.exception(e) - - # 添加错误信息到推荐列表,确保前端能看到错误 - error_response = { - 'stock_code': stock_code, - 'error': error_msg, - 'status': 'error', - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - recommended_stocks.append(error_response) - continue - - logger.info(f"扫描完成,共 {stock_count} 只,{error_count} 只出错,{len(recommended_stocks)} 只推荐") - return recommended_stocks - else: - # 流式模式 - stock_count = 0 - error_count = 0 - - for stock_code in stock_codes: - stock_count += 1 - logger.info(f"流式扫描进度: {stock_count}/{len(stock_codes)}, 当前: {stock_code}") - - try: - chunk_count = 0 - for chunk in self.analyze_stock(stock_code, market_type, stream=True): - chunk_count += 1 - # 检查是否有错误信息 - try: - chunk_data = json.loads(chunk) - if 'error' in chunk_data: - error_count += 1 - logger.warning(f"[流式扫描错误] {stock_code}: {chunk_data['error']}") - except: - pass - yield chunk - logger.debug(f" {stock_code} 流式分析完成,共 {chunk_count} 个块") - except Exception as e: - error_count += 1 - error_msg = f"分析 {stock_code} 时出错: {str(e)}" - logger.error(f"[流式扫描异常] {error_msg}") - logger.exception(e) - - # 格式化错误响应 - error_response = { - 'stock_code': stock_code, - 'error': error_msg, - 'status': 'error', - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - error_json = json.dumps(error_response) - logger.info(f"流式错误输出: {error_json}") - yield error_json - - logger.info(f"流式扫描完成,共处理 {stock_count} ,{error_count} 只出错") diff --git a/us_stock_service.py b/us_stock_service.py deleted file mode 100644 index 852d565..0000000 --- a/us_stock_service.py +++ /dev/null @@ -1,53 +0,0 @@ -import akshare as ak -import pandas as pd - -class USStockService: - - def search_us_stocks(self, keyword): - """ - 搜索美股代码 - :param keyword: 搜索关键词 - :return: 匹配的股票列表 - """ - try: - # 获取美股数据 - df = ak.stock_us_spot_em() - - # 转换列名 - df = df.rename(columns={ - "序号": "index", - "名称": "name", - "最新价": "price", - "涨跌额": "price_change", - "涨跌幅": "price_change_percent", - "开盘价": "open", - "最高价": "high", - "最低价": "low", - "昨收价": "pre_close", - "总市值": "market_value", - "市盈率": "pe_ratio", - "成交量": "volume", - "成交额": "turnover", - "振幅": "amplitude", - "换手率": "turnover_rate", - "代码": "symbol" - }) - - # 模糊匹配搜索 - mask = df['name'].str.contains(keyword, case=False, na=False) - results = df[mask] - - # 格式化返回结果并处理 NaN 值 - formatted_results = [] - for _, row in results.iterrows(): - formatted_results.append({ - 'name': row['name'] if pd.notna(row['name']) else '', - 'symbol': str(row['symbol']) if pd.notna(row['symbol']) else '', - 'price': float(row['price']) if pd.notna(row['price']) else 0.0, - 'market_value': float(row['market_value']) if pd.notna(row['market_value']) else 0.0 - }) - - return formatted_results - - except Exception as e: - raise Exception(f"搜索美股代码失败: {str(e)}") \ No newline at end of file diff --git a/utils/api_control.py b/utils/api_control.py deleted file mode 100644 index ba10342..0000000 --- a/utils/api_control.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- - -from typing import Optional, Any -from fastapi import Response -from pydantic import BaseModel - - -class ResponseModel(BaseModel): - """ - 统一返回模型 - """ - code: int = 200 - msg: str = "Success" - data: Optional[Any] = None - - -class ApiResponse: - @staticmethod - def __response(code: int, msg: str, data: Optional[Any] = None) -> ResponseModel: - return ResponseModel(code=code, msg=msg, data=data) - - @classmethod - def success(cls, *, code: int = 200, msg: str = 'Success', data: Optional[Any] = None) -> Response: - response_model = cls.__response(code=code, msg=msg, data=data) - return cls(content=response_model.model_dump()) - - @classmethod - def fail(cls, *, code: int = 400, msg: str = 'Bad Request', data: Optional[Any] = None) -> Response: - response_model = cls.__response(code=code, msg=msg, data=data) - return cls(content=response_model.model_dump()) - -response_api = ApiResponse() - -""" 示例 -@app.get("/example-success") -async def example_success(): - return response_api.success(data={"key": "value"}) - -@app.get("/example-fail") -async def example_fail(): - return response_api.fail(msg="Something went wrong", data={"error": "details"}) -""" diff --git a/web_server.py b/web_server.py index 5550a49..8e06659 100644 --- a/web_server.py +++ b/web_server.py @@ -2,28 +2,40 @@ from fastapi import FastAPI, Request, Response, Depends, HTTPException, Backgrou from fastapi.responses import JSONResponse, StreamingResponse, FileResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from pydantic import BaseModel, Field from typing import List, Optional, Dict, Any, Generator from services.stock_analyzer_service import StockAnalyzerService -# 导入新的异步服务 from services.us_stock_service_async import USStockServiceAsync from services.fund_service_async import FundServiceAsync -import asyncio -import threading import os -import traceback import httpx from logger import get_logger from utils.api_utils import APIUtils -# 加载环境变量 -from dotenv import load_dotenv +from dotenv import load_dotenv, dotenv_values import uvicorn +import json +import secrets +from datetime import datetime, timedelta +from jose import JWTError, jwt load_dotenv() # 获取日志器 logger = get_logger() +# JWT相关配置 +SECRET_KEY = os.getenv("JWT_SECRET_KEY", secrets.token_hex(32)) +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 10080 # Token过期时间一周 + +LOGIN_PASSWORD = os.getenv("LOGIN_PASSWORD", "") +print(LOGIN_PASSWORD) + +# 是否需要登录 +REQUIRE_LOGIN = bool(LOGIN_PASSWORD.strip()) + + app = FastAPI( title="Stock Scanner API", description="异步股票分析API", @@ -42,7 +54,7 @@ app.add_middleware( # 设置静态文件 frontend_dist = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'frontend', 'dist') if os.path.exists(frontend_dist): - app.mount("/", StaticFiles(directory=frontend_dist, html=True), name="frontend") + app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dist, "assets")), name="assets") # 初始化异步服务 # StockAnalyzerService 不需要全局初始化,在 /analyze 接口中按需创建 @@ -64,35 +76,121 @@ class TestAPIRequest(BaseModel): api_model: Optional[str] = None api_timeout: Optional[int] = 10 -@app.get("/") -async def index(request: Request): - # 检查是否使用前端构建版本 - if os.path.exists(frontend_dist): - index_file = os.path.join(frontend_dist, 'index.html') - return FileResponse(index_file) - else: - # 不再使用模板渲染,而是重定向到API文档页面 - logger.warning("前端构建目录不存在,重定向到API文档页面") - return RedirectResponse(url="/docs") +class LoginRequest(BaseModel): + password: str +class Token(BaseModel): + access_token: str + token_type: str + +# 自定义依赖项,在REQUIRE_LOGIN=False时不要求token +class OptionalOAuth2PasswordBearer(OAuth2PasswordBearer): + async def __call__(self, request: Request) -> Optional[str]: + if not REQUIRE_LOGIN: + return None + try: + return await super().__call__(request) + except HTTPException: + if not REQUIRE_LOGIN: + return None + raise + +# 使用自定义的依赖项 +optional_oauth2_scheme = OptionalOAuth2PasswordBearer(tokenUrl="login") + +# 创建访问令牌 +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + +# 验证令牌 +async def verify_token(token: Optional[str] = Depends(optional_oauth2_scheme)): + # 如果未设置密码,则不需要验证 + if not REQUIRE_LOGIN: + return "guest" + + # 如果没有token且不需要登录,返回guest + if token is None and not REQUIRE_LOGIN: + return "guest" + + credentials_exception = HTTPException( + status_code=401, + detail="无效的认证凭据", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # 如果需要登录但没有token,抛出异常 + if token is None: + raise credentials_exception + + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + return username + except JWTError: + raise credentials_exception + +# 用户登录接口 +@app.post("/login") +async def login(request: LoginRequest): + """用户登录接口""" + # 如果未设置密码,表示不需要登录 + if not REQUIRE_LOGIN: + access_token = create_access_token(data={"sub": "guest"}) + return {"access_token": access_token, "token_type": "bearer"} + + if request.password != LOGIN_PASSWORD: + logger.warning("登录失败:密码错误") + raise HTTPException(status_code=401, detail="密码错误") + + # 创建访问令牌 + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": "user"}, expires_delta=access_token_expires + ) + logger.info("用户登录成功") + return {"access_token": access_token, "token_type": "bearer"} + +# 检查用户认证状态 +@app.get("/check_auth") +async def check_auth(username: str = Depends(verify_token)): + """检查用户认证状态""" + return {"authenticated": True, "username": username} + +# 获取系统配置 @app.get("/config") async def get_config(): """返回系统配置信息""" config = { 'announcement': os.getenv('ANNOUNCEMENT_TEXT') or '', 'default_api_url': os.getenv('API_URL', ''), - 'default_api_model': os.getenv('API_MODEL', 'gpt-3.5-turbo'), + 'default_api_model': os.getenv('API_MODEL', ''), 'default_api_timeout': os.getenv('API_TIMEOUT', '60') } return config +# AI分析股票 @app.post("/analyze") -async def analyze(request: AnalyzeRequest): +async def analyze(request: AnalyzeRequest, username: str = Depends(verify_token)): try: logger.info("开始处理分析请求") stock_codes = request.stock_codes market_type = request.market_type + # 后端再次去重,确保安全 + original_count = len(stock_codes) + stock_codes = list(dict.fromkeys(stock_codes)) # 保持原有顺序的去重方法 + if len(stock_codes) < original_count: + logger.info(f"后端去重: 从{original_count}个代码中移除了{original_count - len(stock_codes)}个重复项") + logger.debug(f"接收到分析请求: stock_codes={stock_codes}, market_type={market_type}") # 获取自定义API配置 @@ -122,7 +220,8 @@ async def analyze(request: AnalyzeRequest): stock_code = stock_codes[0].strip() logger.info(f"开始单股流式分析: {stock_code}") - init_message = f'{{"stream_type": "single", "stock_code": "{stock_code}"}}\n' + stock_code_json = json.dumps(stock_code) + init_message = f'{{"stream_type": "single", "stock_code": {stock_code_json}}}\n' yield init_message logger.debug(f"开始处理股票 {stock_code} 的流式响应") @@ -138,7 +237,8 @@ async def analyze(request: AnalyzeRequest): # 批量分析流式处理 logger.info(f"开始批量流式分析: {stock_codes}") - init_message = f'{{"stream_type": "batch", "stock_codes": {stock_codes}}}\n' + stock_codes_json = json.dumps(stock_codes) + init_message = f'{{"stream_type": "batch", "stock_codes": {stock_codes_json}}}\n' yield init_message logger.debug(f"开始处理批量股票的流式响应") @@ -165,8 +265,9 @@ async def analyze(request: AnalyzeRequest): logger.exception(e) raise HTTPException(status_code=500, detail=error_msg) +# 搜索美股代码 @app.get("/search_us_stocks") -async def search_us_stocks(keyword: str = ""): +async def search_us_stocks(keyword: str = "", username: str = Depends(verify_token)): try: if not keyword: raise HTTPException(status_code=400, detail="请输入搜索关键词") @@ -179,8 +280,9 @@ async def search_us_stocks(keyword: str = ""): logger.error(f"搜索美股代码时出错: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) +# 搜索基金代码 @app.get("/search_funds") -async def search_funds(keyword: str = "", market_type: str = ""): +async def search_funds(keyword: str = "", market_type: str = "", username: str = Depends(verify_token)): try: if not keyword: raise HTTPException(status_code=400, detail="请输入搜索关键词") @@ -193,8 +295,39 @@ async def search_funds(keyword: str = "", market_type: str = ""): logger.error(f"搜索基金代码时出错: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) +# 获取美股详情 +@app.get("/us_stock_detail/{symbol}") +async def get_us_stock_detail(symbol: str, username: str = Depends(verify_token)): + try: + if not symbol: + raise HTTPException(status_code=400, detail="请提供股票代码") + + # 使用异步服务获取详情 + detail = await us_stock_service.get_us_stock_detail(symbol) + return detail + + except Exception as e: + logger.error(f"获取美股详情时出错: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + +# 获取基金详情 +@app.get("/fund_detail/{symbol}") +async def get_fund_detail(symbol: str, market_type: str = "ETF", username: str = Depends(verify_token)): + try: + if not symbol: + raise HTTPException(status_code=400, detail="请提供基金代码") + + # 使用异步服务获取详情 + detail = await fund_service.get_fund_detail(symbol, market_type) + return detail + + except Exception as e: + logger.error(f"获取基金详情时出错: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + +# 测试API连接 @app.post("/test_api_connection") -async def test_api_connection(request: TestAPIRequest): +async def test_api_connection(request: TestAPIRequest, username: str = Depends(verify_token)): """测试API连接""" try: logger.info("开始测试API连接") @@ -226,7 +359,7 @@ async def test_api_connection(request: TestAPIRequest): "Content-Type": "application/json" }, json={ - "model": api_model or "gpt-3.5-turbo", + "model": api_model or "", "messages": [ {"role": "user", "content": "Hello, this is a test message. Please respond with 'API connection successful'."} ], @@ -261,36 +394,34 @@ async def test_api_connection(request: TestAPIRequest): content={"success": False, "message": f"API 测试连接时出错: {str(e)}"} ) -# 新增 API 端点:获取美股详情 -@app.get("/us_stock_detail/{symbol}") -async def get_us_stock_detail(symbol: str): - try: - if not symbol: - raise HTTPException(status_code=400, detail="请提供股票代码") - - # 使用异步服务获取详情 - detail = await us_stock_service.get_us_stock_detail(symbol) - return detail - - except Exception as e: - logger.error(f"获取美股详情时出错: {str(e)}") - raise HTTPException(status_code=500, detail=str(e)) +# 检查是否需要登录 +@app.get("/need_login") +async def need_login(): + """检查是否需要登录""" + return {"require_login": REQUIRE_LOGIN} -# 新增 API 端点:获取基金详情 -@app.get("/fund_detail/{symbol}") -async def get_fund_detail(symbol: str, market_type: str = "ETF"): - try: - if not symbol: - raise HTTPException(status_code=400, detail="请提供基金代码") - - # 使用异步服务获取详情 - detail = await fund_service.get_fund_detail(symbol, market_type) - return detail - - except Exception as e: - logger.error(f"获取基金详情时出错: {str(e)}") - raise HTTPException(status_code=500, detail=str(e)) +# 前端路由处理,必须放在所有API路由之后 +@app.get("/{full_path:path}") +async def serve_frontend(full_path: str, request: Request): + """处理所有前端路由请求,返回index.html""" + # 排除API路径和静态资源 + if full_path.startswith(("api/", "assets/", "docs", "openapi.json")) or \ + full_path in ["check_auth", "config", "analyze", + "search_us_stocks", "search_funds", + "test_api_connection", "us_stock_detail", + "fund_detail"]: + # 对于API路径,让FastAPI继续处理 + raise HTTPException(status_code=404, detail="API路径不存在") + + # 检查是否使用前端构建版本 + if os.path.exists(frontend_dist): + index_file = os.path.join(frontend_dist, 'index.html') + return FileResponse(index_file) + else: + # 不再使用模板渲染,而是重定向到API文档页面 + logger.warning("前端构建目录不存在,重定向到API文档页面") + return RedirectResponse(url="/docs") if __name__ == '__main__': - logger.info("股票分析系统启动") + logger.info("股票AI分析系统启动") uvicorn.run("web_server:app", host="127.0.0.1", port=8888, reload=True) \ No newline at end of file