From ce599d306f81e3ebb0f6eaa5b540d701a6d4085d Mon Sep 17 00:00:00 2001 From: rockymelody <939555035@qq.com> Date: Sun, 1 Mar 2026 17:44:03 +0800 Subject: [PATCH 01/24] =?UTF-8?q?=E9=9D=92=E9=BE=99=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E9=89=B4=E6=9D=83=E7=BB=95=E8=BF=87=E6=BC=8F=E6=B4=9E=E5=B7=B2?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20(#2935)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 已实施的安全加固措施 第一层防御:启用Express严格路由(第17-18行) app.set('case sensitive routing', true); // 路由大小写敏感 app.set('strict routing', true); // 严格路由匹配 第二层防御:路径标准化检查中间件(第23-37行) app.use((req, res, next) => { const originalPath = req.path; const normalizedPath = originalPath.toLowerCase(); // 检测并拦截大小写混淆攻击 if (originalPath !== normalizedPath && (normalizedPath.startsWith('/api/') || normalizedPath.startsWith('/open/'))) { return res.status(400).json({ code: 400, message: 'Invalid path format' }); } next(); }); 作用:主动检测并拒绝含有大小写变体的恶意请求 第三层防御:JWT中间件正则表达式修复(第59行) // 修复前: path: [...config.apiWhiteList, /^\/(?!api\/).*/], // 修复后:添加大小写不敏感标志 'i' path: [...config.apiWhiteList, /^(\/(?!api\/).*)$/i], 作用:防御正则匹配层面的绕过 第四层防御:自定义Token中间件路径标准化(第74-87行) // 修复前: if (!['/open/', '/api/'].some((x) => req.path.startsWith(x))) { // 修复后:统一转小写比较 const pathLower = req.path.toLowerCase(); if (!['/open/', '/api/'].some((x) => pathLower.startsWith(x))) { } 作用:确保Token验证逻辑对所有路径变体生效 第五层防御:初始化接口路径检查修复(第122-123行) // 修复前: if (!['/api/user/init', '/api/user/notification/init'].includes(req.path)) { // 修复后: const pathLower = req.path.toLowerCase(); if (!['/api/user/init', '/api/user/notification/init'].includes(pathLower)) { --- back/loaders/express.ts | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/back/loaders/express.ts b/back/loaders/express.ts index 63065a21..2807ece0 100644 --- a/back/loaders/express.ts +++ b/back/loaders/express.ts @@ -13,9 +13,29 @@ import { isValidToken } from '../shared/auth'; import path from 'path'; export default ({ app }: { app: Application }) => { + // Security: Enable strict routing to prevent case-insensitive path bypass + app.set('case sensitive routing', true); + app.set('strict routing', true); app.set('trust proxy', 'loopback'); app.use(cors()); + // Security: Path normalization middleware to prevent case variation attacks + app.use((req, res, next) => { + const originalPath = req.path; + const normalizedPath = originalPath.toLowerCase(); + + // Block requests with case variations on protected paths + if (originalPath !== normalizedPath && + (normalizedPath.startsWith('/api/') || normalizedPath.startsWith('/open/'))) { + return res.status(400).json({ + code: 400, + message: 'Invalid path format' + }); + } + + next(); + }); + // Rewrite URLs to strip baseUrl prefix if configured // This allows the rest of the app to work without baseUrl awareness if (config.baseUrl) { @@ -36,7 +56,7 @@ export default ({ app }: { app: Application }) => { secret: config.jwt.secret, algorithms: ['HS384'], }).unless({ - path: [...config.apiWhiteList, /^\/(?!api\/).*/], + path: [...config.apiWhiteList, /^(\/(?!api\/).*)$/i], }), ); @@ -51,19 +71,20 @@ export default ({ app }: { app: Application }) => { }); app.use(async (req: Request, res, next) => { - if (!['/open/', '/api/'].some((x) => req.path.startsWith(x))) { + const pathLower = req.path.toLowerCase(); + if (!['/open/', '/api/'].some((x) => pathLower.startsWith(x))) { return next(); } const headerToken = getToken(req); - if (req.path.startsWith('/open/')) { + if (pathLower.startsWith('/open/')) { const apps = await shareStore.getApps(); const doc = apps?.filter((x) => x.tokens?.find((y) => y.value === headerToken), )?.[0]; if (doc && doc.tokens && doc.tokens.length > 0) { const currentToken = doc.tokens.find((x) => x.value === headerToken); - const keyMatch = req.path.match(/\/open\/([a-z]+)\/*/); + const keyMatch = pathLower.match(/\/open\/([a-z]+)\/*/); const key = keyMatch && keyMatch[1]; if ( doc.scopes.includes(key as any) && @@ -98,7 +119,8 @@ export default ({ app }: { app: Application }) => { }); app.use(async (req, res, next) => { - if (!['/api/user/init', '/api/user/notification/init'].includes(req.path)) { + const pathLower = req.path.toLowerCase(); + if (!['/api/user/init', '/api/user/notification/init'].includes(pathLower)) { return next(); } const authInfo = From 6bec52dca158481258315ba0fc2f11206df7b719 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Mar 2026 18:02:21 +0800 Subject: [PATCH 02/24] Fix /open/user/init auth bypass allowing credential reset on initialized systems (#2941) * Initial plan * fix: add /open/user/init paths to init guard to prevent auth bypass Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> Co-authored-by: whyour --- back/loaders/express.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/back/loaders/express.ts b/back/loaders/express.ts index 2807ece0..b5bf221b 100644 --- a/back/loaders/express.ts +++ b/back/loaders/express.ts @@ -120,7 +120,14 @@ export default ({ app }: { app: Application }) => { app.use(async (req, res, next) => { const pathLower = req.path.toLowerCase(); - if (!['/api/user/init', '/api/user/notification/init'].includes(pathLower)) { + if ( + ![ + '/api/user/init', + '/api/user/notification/init', + '/open/user/init', + '/open/user/notification/init', + ].includes(req.path) + ) { return next(); } const authInfo = From 544c432f49ac417a9071a862f92030ddfaf31cda Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 1 Mar 2026 20:35:19 +0800 Subject: [PATCH 03/24] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20PATH=20=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/loaders/initData.ts | 4 ++-- docker/310.Dockerfile | 5 +++-- docker/Dockerfile | 5 +++-- docker/docker-entrypoint.sh | 2 -- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/back/loaders/initData.ts b/back/loaders/initData.ts index f8b5315a..37bbdd6b 100644 --- a/back/loaders/initData.ts +++ b/back/loaders/initData.ts @@ -13,7 +13,7 @@ import { AuthDataType, SystemModel } from '../data/system'; import SystemService from '../services/system'; import UserService from '../services/user'; import { writeFile, readFile } from 'fs/promises'; -import { createRandomString, fileExist, safeJSONParse } from '../config/util'; +import { createRandomString, fileExist, isDemoEnv, safeJSONParse } from '../config/util'; import OpenService from '../services/open'; import { shareStore } from '../shared/store'; import Logger from './logger'; @@ -50,7 +50,7 @@ export default async () => { const [authConfig] = await SystemModel.findOrCreate({ where: { type: AuthDataType.authConfig }, }); - if (!authConfig?.info) { + if (!authConfig?.info || isDemoEnv()) { let authInfo = { username: 'admin', password: 'admin', diff --git a/docker/310.Dockerfile b/docker/310.Dockerfile index 8a092081..ebd0af81 100644 --- a/docker/310.Dockerfile +++ b/docker/310.Dockerfile @@ -69,9 +69,10 @@ RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \ ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \ - PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 + PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \ + HOME=/root -ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin \ +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \ PIP_CACHE_DIR=${PYTHON_HOME}/pip \ PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages diff --git a/docker/Dockerfile b/docker/Dockerfile index 6617b4ee..8ca6c947 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -69,9 +69,10 @@ RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \ ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \ - PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 + PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \ + HOME=/root -ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin \ +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \ PIP_CACHE_DIR=${PYTHON_HOME}/pip \ PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index eb2027f2..5d784cc3 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -1,7 +1,5 @@ #!/bin/bash -export PATH="$HOME/bin:$PATH" - dir_shell=/ql/shell . $dir_shell/share.sh From 275d8af4e2374e9e288ed16348e6d1ef23bb7eca Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 1 Mar 2026 20:35:25 +0800 Subject: [PATCH 04/24] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC=20v2?= =?UTF-8?q?.20.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- version.yaml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/version.yaml b/version.yaml index 2ab325e8..65648012 100644 --- a/version.yaml +++ b/version.yaml @@ -1,11 +1,6 @@ -version: 2.20.1 -changeLogLink: https://t.me/jiao_long/433 -publishTime: 2025-12-26 22:00 +version: 2.20.2 +changeLogLink: https://t.me/jiao_long/434 +publishTime: 2026-03-01 1800 changeLog: | - 1. 修复获取依赖管理列表 - 2. notify.js 修复 TG_PROXY_AUTH 参数拼接 - 3. QLAPI.notify larkSecret 参数 - 4. 修复 cron parser 定时规则校验 - 5. 修复设置 baseUrl 后无法访问 - 6. 修复环境变量排序 - 7. 修复定时任务无法停止 \ No newline at end of file + 1. 修复 path 安全漏洞(重要) + \ No newline at end of file From c39f4ef846c9373dc4149881dd4181fa1d9beeec Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 7 Mar 2026 21:31:24 +0800 Subject: [PATCH 05/24] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=20multer?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E5=86=B3=20cve=20=E6=BC=8F=E6=B4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- pnpm-lock.yaml | 819 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 565 insertions(+), 256 deletions(-) diff --git a/package.json b/package.json index 038f8c1d..5858eaf2 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", - "multer": "1.4.5-lts.1", + "multer": "2.1.1", "node-schedule": "^2.1.0", "nodemailer": "^6.9.16", "p-queue-cjs": "7.3.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c8e0b0f..54c1a45e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - overrides: sqlite3: git+https://github.com/whyour/node-sqlite3.git#v1.0.3 @@ -90,8 +86,8 @@ dependencies: specifier: ^4.17.21 version: 4.17.21 multer: - specifier: 1.4.5-lts.1 - version: 1.4.5-lts.1 + specifier: 2.1.1 + version: 2.1.1 node-schedule: specifier: ^2.1.0 version: 2.1.1 @@ -240,10 +236,10 @@ devDependencies: version: 8.3.4 '@uiw/codemirror-extensions-langs': specifier: ^4.21.9 - version: 4.23.6(@codemirror/autocomplete@6.18.3)(@codemirror/language-data@6.5.1)(@codemirror/language@6.10.6)(@codemirror/legacy-modes@6.4.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/javascript@1.4.21)(@lezer/lr@1.4.2) + version: 4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/language-data@6.5.2)(@codemirror/language@6.12.2)(@codemirror/legacy-modes@6.5.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8) '@uiw/react-codemirror': specifier: ^4.21.9 - version: 4.23.6(@babel/runtime@7.26.0)(@codemirror/autocomplete@6.18.3)(@codemirror/language@6.10.6)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.35.0)(codemirror@6.0.1)(react-dom@18.3.1)(react@18.3.1) + version: 4.23.6(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.35.0)(codemirror@6.0.2)(react-dom@18.3.1)(react@18.3.1) '@umijs/max': specifier: ^4.4.4 version: 4.4.4(@types/node@17.0.45)(@types/react-dom@18.3.1)(@types/react@18.3.12)(prettier@2.8.8)(react-dom@18.3.1)(react@18.3.1)(sockjs-client@1.6.1)(typescript@5.2.2) @@ -934,7 +930,7 @@ packages: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -957,7 +953,7 @@ packages: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1350,6 +1346,11 @@ packages: regenerator-runtime: 0.14.1 dev: true + /@babel/runtime@7.28.6: + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/template@7.25.9: resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} @@ -1368,7 +1369,7 @@ packages: '@babel/parser': 7.26.2 '@babel/template': 7.25.9 '@babel/types': 7.26.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -1416,6 +1417,24 @@ packages: '@lezer/common': 1.2.3 dev: true + /@codemirror/autocomplete@6.20.1: + resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==} + dependencies: + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.35.0 + '@lezer/common': 1.5.1 + dev: true + + /@codemirror/commands@6.10.2: + resolution: {integrity: sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==} + dependencies: + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.35.0 + '@lezer/common': 1.5.1 + dev: true + /@codemirror/commands@6.7.1: resolution: {integrity: sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==} dependencies: @@ -1436,6 +1455,17 @@ packages: '@lezer/lr': 1.4.2 dev: true + /@codemirror/lang-angular@0.1.4: + resolution: {integrity: sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==} + dependencies: + '@codemirror/lang-html': 6.4.11 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/language': 6.12.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@codemirror/lang-cpp@6.0.2: resolution: {integrity: sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==} dependencies: @@ -1443,6 +1473,13 @@ packages: '@lezer/cpp': 1.1.2 dev: true + /@codemirror/lang-cpp@6.0.3: + resolution: {integrity: sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==} + dependencies: + '@codemirror/language': 6.12.2 + '@lezer/cpp': 1.1.5 + dev: true + /@codemirror/lang-css@6.3.1(@codemirror/view@6.35.0): resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} dependencies: @@ -1455,16 +1492,28 @@ packages: - '@codemirror/view' dev: true - /@codemirror/lang-go@6.0.1(@codemirror/view@6.35.0): + /@codemirror/lang-go@6.0.1: resolution: {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) - '@codemirror/language': 6.10.6 + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 '@codemirror/state': 6.4.1 - '@lezer/common': 1.2.3 - '@lezer/go': 1.0.0 - transitivePeerDependencies: - - '@codemirror/view' + '@lezer/common': 1.5.1 + '@lezer/go': 1.0.1 + dev: true + + /@codemirror/lang-html@6.4.11: + resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==} + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.35.0 + '@lezer/common': 1.5.1 + '@lezer/css': 1.3.1 + '@lezer/html': 1.3.13 dev: true /@codemirror/lang-html@6.4.9: @@ -1488,6 +1537,13 @@ packages: '@lezer/java': 1.1.3 dev: true + /@codemirror/lang-java@6.0.2: + resolution: {integrity: sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==} + dependencies: + '@codemirror/language': 6.12.2 + '@lezer/java': 1.1.3 + dev: true + /@codemirror/lang-javascript@6.2.2: resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==} dependencies: @@ -1500,6 +1556,28 @@ packages: '@lezer/javascript': 1.4.21 dev: true + /@codemirror/lang-javascript@6.2.5: + resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==} + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/lint': 6.9.5 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.35.0 + '@lezer/common': 1.5.1 + '@lezer/javascript': 1.5.4 + dev: true + + /@codemirror/lang-jinja@6.0.0: + resolution: {integrity: sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==} + dependencies: + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@codemirror/lang-json@6.0.1: resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==} dependencies: @@ -1507,6 +1585,13 @@ packages: '@lezer/json': 1.0.2 dev: true + /@codemirror/lang-json@6.0.2: + resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + dependencies: + '@codemirror/language': 6.12.2 + '@lezer/json': 1.0.3 + dev: true + /@codemirror/lang-less@6.0.2(@codemirror/view@6.35.0): resolution: {integrity: sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==} dependencies: @@ -1541,6 +1626,19 @@ packages: '@lezer/lr': 1.4.2 dev: true + /@codemirror/lang-liquid@6.3.2: + resolution: {integrity: sha512-6PDVU3ZnfeYyz1at1E/ttorErZvZFXXt1OPhtfe1EZJ2V2iDFa0CwPqPgG5F7NXN0yONGoBogKmFAafKTqlwIw==} + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.35.0 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@codemirror/lang-markdown@6.3.1: resolution: {integrity: sha512-y3sSPuQjBKZQbQwe3ZJKrSW6Silyl9PnrU/Mf0m2OQgIlPoSYTtOvEL7xs94SVMkb8f4x+SQFnzXPdX4Wk2lsg==} dependencies: @@ -1553,6 +1651,18 @@ packages: '@lezer/markdown': 1.3.2 dev: true + /@codemirror/lang-markdown@6.5.0: + resolution: {integrity: sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==} + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.35.0 + '@lezer/common': 1.5.1 + '@lezer/markdown': 1.6.3 + dev: true + /@codemirror/lang-php@6.0.1: resolution: {integrity: sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==} dependencies: @@ -1563,6 +1673,16 @@ packages: '@lezer/php': 1.0.2 dev: true + /@codemirror/lang-php@6.0.2: + resolution: {integrity: sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==} + dependencies: + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.4.1 + '@lezer/common': 1.5.1 + '@lezer/php': 1.0.5 + dev: true + /@codemirror/lang-python@6.1.6(@codemirror/view@6.35.0): resolution: {integrity: sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==} dependencies: @@ -1575,6 +1695,16 @@ packages: - '@codemirror/view' dev: true + /@codemirror/lang-python@6.2.1: + resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==} + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.4.1 + '@lezer/common': 1.5.1 + '@lezer/python': 1.1.18 + dev: true + /@codemirror/lang-rust@6.0.1: resolution: {integrity: sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==} dependencies: @@ -1582,6 +1712,13 @@ packages: '@lezer/rust': 1.0.2 dev: true + /@codemirror/lang-rust@6.0.2: + resolution: {integrity: sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==} + dependencies: + '@codemirror/language': 6.12.2 + '@lezer/rust': 1.0.2 + dev: true + /@codemirror/lang-sass@6.0.2(@codemirror/view@6.35.0): resolution: {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==} dependencies: @@ -1594,6 +1731,17 @@ packages: - '@codemirror/view' dev: true + /@codemirror/lang-sql@6.10.0: + resolution: {integrity: sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==} + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.4.1 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@codemirror/lang-sql@6.8.0(@codemirror/view@6.35.0): resolution: {integrity: sha512-aGLmY4OwGqN3TdSx3h6QeA1NrvaYtF7kkoWR/+W7/JzB0gQtJ+VJxewlnE3+VImhA4WVlhmkJr109PefOOhjLg==} dependencies: @@ -1638,44 +1786,44 @@ packages: '@lezer/xml': 1.0.5 dev: true - /@codemirror/lang-yaml@6.1.1(@codemirror/view@6.35.0): - resolution: {integrity: sha512-HV2NzbK9bbVnjWxwObuZh5FuPCowx51mEfoFT9y3y+M37fA3+pbxx4I7uePuygFzDsAmCTwQSc/kXh/flab4uw==} + /@codemirror/lang-yaml@6.1.2: + resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) - '@codemirror/language': 6.10.6 + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 '@codemirror/state': 6.4.1 - '@lezer/common': 1.2.3 - '@lezer/highlight': 1.2.1 - '@lezer/yaml': 1.0.3 - transitivePeerDependencies: - - '@codemirror/view' + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + '@lezer/yaml': 1.0.4 dev: true - /@codemirror/language-data@6.5.1(@codemirror/view@6.35.0): - resolution: {integrity: sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==} + /@codemirror/language-data@6.5.2(@codemirror/view@6.35.0): + resolution: {integrity: sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==} dependencies: - '@codemirror/lang-angular': 0.1.3 - '@codemirror/lang-cpp': 6.0.2 + '@codemirror/lang-angular': 0.1.4 + '@codemirror/lang-cpp': 6.0.3 '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) - '@codemirror/lang-go': 6.0.1(@codemirror/view@6.35.0) - '@codemirror/lang-html': 6.4.9 - '@codemirror/lang-java': 6.0.1 - '@codemirror/lang-javascript': 6.2.2 - '@codemirror/lang-json': 6.0.1 + '@codemirror/lang-go': 6.0.1 + '@codemirror/lang-html': 6.4.11 + '@codemirror/lang-java': 6.0.2 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/lang-jinja': 6.0.0 + '@codemirror/lang-json': 6.0.2 '@codemirror/lang-less': 6.0.2(@codemirror/view@6.35.0) - '@codemirror/lang-liquid': 6.2.2 - '@codemirror/lang-markdown': 6.3.1 - '@codemirror/lang-php': 6.0.1 - '@codemirror/lang-python': 6.1.6(@codemirror/view@6.35.0) - '@codemirror/lang-rust': 6.0.1 + '@codemirror/lang-liquid': 6.3.2 + '@codemirror/lang-markdown': 6.5.0 + '@codemirror/lang-php': 6.0.2 + '@codemirror/lang-python': 6.2.1 + '@codemirror/lang-rust': 6.0.2 '@codemirror/lang-sass': 6.0.2(@codemirror/view@6.35.0) - '@codemirror/lang-sql': 6.8.0(@codemirror/view@6.35.0) + '@codemirror/lang-sql': 6.10.0 '@codemirror/lang-vue': 0.1.3 '@codemirror/lang-wast': 6.0.2 '@codemirror/lang-xml': 6.1.0 - '@codemirror/lang-yaml': 6.1.1(@codemirror/view@6.35.0) - '@codemirror/language': 6.10.6 - '@codemirror/legacy-modes': 6.4.2 + '@codemirror/lang-yaml': 6.1.2 + '@codemirror/language': 6.12.2 + '@codemirror/legacy-modes': 6.5.2 transitivePeerDependencies: - '@codemirror/view' dev: true @@ -1691,10 +1839,21 @@ packages: style-mod: 4.1.2 dev: true - /@codemirror/legacy-modes@6.4.2: - resolution: {integrity: sha512-HsvWu08gOIIk303eZQCal4H4t65O/qp1V4ul4zVa3MHK5FJ0gz3qz3O55FIkm+aQUcshUOjBx38t2hPiJwW5/g==} + /@codemirror/language@6.12.2: + resolution: {integrity: sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==} dependencies: - '@codemirror/language': 6.10.6 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.35.0 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + style-mod: 4.1.3 + dev: true + + /@codemirror/legacy-modes@6.5.2: + resolution: {integrity: sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==} + dependencies: + '@codemirror/language': 6.12.2 dev: true /@codemirror/lint@6.8.4: @@ -1705,25 +1864,39 @@ packages: crelt: 1.0.6 dev: true - /@codemirror/search@6.5.8: - resolution: {integrity: sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==} + /@codemirror/lint@6.9.5: + resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==} dependencies: '@codemirror/state': 6.4.1 '@codemirror/view': 6.35.0 crelt: 1.0.6 dev: true + /@codemirror/search@6.6.0: + resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==} + dependencies: + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.39.16 + crelt: 1.0.6 + dev: true + /@codemirror/state@6.4.1: resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==} dev: true - /@codemirror/theme-one-dark@6.1.2: - resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==} + /@codemirror/state@6.5.4: + resolution: {integrity: sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==} dependencies: - '@codemirror/language': 6.10.6 + '@marijn/find-cluster-break': 1.0.2 + dev: true + + /@codemirror/theme-one-dark@6.1.3: + resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} + dependencies: + '@codemirror/language': 6.12.2 '@codemirror/state': 6.4.1 '@codemirror/view': 6.35.0 - '@lezer/highlight': 1.2.1 + '@lezer/highlight': 1.2.3 dev: true /@codemirror/view@6.35.0: @@ -1734,6 +1907,15 @@ packages: w3c-keyname: 2.2.8 dev: true + /@codemirror/view@6.39.16: + resolution: {integrity: sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==} + dependencies: + '@codemirror/state': 6.5.4 + crelt: 1.0.6 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + dev: true + /@colors/colors@1.6.0: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} @@ -2643,7 +2825,7 @@ packages: '@antfu/install-pkg': 0.1.1 '@antfu/utils': 0.7.10 '@iconify/types': 2.0.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 kolorist: 1.8.0 local-pkg: 0.4.3 transitivePeerDependencies: @@ -2656,7 +2838,7 @@ packages: dependencies: string-width: 5.1.2 string-width-cjs: /string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 @@ -2809,6 +2991,10 @@ packages: resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} dev: true + /@lezer/common@1.5.1: + resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==} + dev: true + /@lezer/cpp@1.1.2: resolution: {integrity: sha512-macwKtyeUO0EW86r3xWQCzOV9/CF8imJLpJlPv3sDY57cPGeUZ8gXWOWNlJr52TVByMV3PayFQCA5SHEERDmVQ==} dependencies: @@ -2817,6 +3003,14 @@ packages: '@lezer/lr': 1.4.2 dev: true + /@lezer/cpp@1.1.5: + resolution: {integrity: sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw==} + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@lezer/css@1.1.9: resolution: {integrity: sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==} dependencies: @@ -2825,12 +3019,20 @@ packages: '@lezer/lr': 1.4.2 dev: true - /@lezer/go@1.0.0: - resolution: {integrity: sha512-co9JfT3QqX1YkrMmourYw2Z8meGC50Ko4d54QEcQbEYpvdUvN4yb0NBZdn/9ertgvjsySxHsKzH3lbm3vqJ4Jw==} + /@lezer/css@1.3.1: + resolution: {integrity: sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==} dependencies: - '@lezer/common': 1.2.3 - '@lezer/highlight': 1.2.1 - '@lezer/lr': 1.4.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + + /@lezer/go@1.0.1: + resolution: {integrity: sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==} + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 dev: true /@lezer/highlight@1.2.1: @@ -2839,6 +3041,12 @@ packages: '@lezer/common': 1.2.3 dev: true + /@lezer/highlight@1.2.3: + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + dependencies: + '@lezer/common': 1.5.1 + dev: true + /@lezer/html@1.3.10: resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==} dependencies: @@ -2847,6 +3055,14 @@ packages: '@lezer/lr': 1.4.2 dev: true + /@lezer/html@1.3.13: + resolution: {integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==} + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@lezer/java@1.1.3: resolution: {integrity: sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==} dependencies: @@ -2863,6 +3079,14 @@ packages: '@lezer/lr': 1.4.2 dev: true + /@lezer/javascript@1.5.4: + resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@lezer/json@1.0.2: resolution: {integrity: sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==} dependencies: @@ -2871,6 +3095,14 @@ packages: '@lezer/lr': 1.4.2 dev: true + /@lezer/json@1.0.3: + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@lezer/lezer@1.1.2: resolution: {integrity: sha512-O8yw3CxPhzYHB1hvwbdozjnAslhhR8A5BH7vfEMof0xk3p+/DFDfZkA9Tde6J+88WgtwaHy4Sy6ThZSkaI0Evw==} dependencies: @@ -2884,6 +3116,12 @@ packages: '@lezer/common': 1.2.3 dev: true + /@lezer/lr@1.4.8: + resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==} + dependencies: + '@lezer/common': 1.5.1 + dev: true + /@lezer/markdown@1.3.2: resolution: {integrity: sha512-Wu7B6VnrKTbBEohqa63h5vxXjiC4pO5ZQJ/TDbhJxPQaaIoRD/6UVDhSDtVsCwVZV12vvN9KxuLL3ATMnlG0oQ==} dependencies: @@ -2891,6 +3129,13 @@ packages: '@lezer/highlight': 1.2.1 dev: true + /@lezer/markdown@1.6.3: + resolution: {integrity: sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==} + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + dev: true + /@lezer/php@1.0.2: resolution: {integrity: sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==} dependencies: @@ -2899,6 +3144,14 @@ packages: '@lezer/lr': 1.4.2 dev: true + /@lezer/php@1.0.5: + resolution: {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==} + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@lezer/python@1.1.14: resolution: {integrity: sha512-ykDOb2Ti24n76PJsSa4ZoDF0zH12BSw1LGfQXCYJhJyOGiFTfGaX0Du66Ze72R+u/P35U+O6I9m8TFXov1JzsA==} dependencies: @@ -2907,6 +3160,14 @@ packages: '@lezer/lr': 1.4.2 dev: true + /@lezer/python@1.1.18: + resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==} + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + dev: true + /@lezer/rust@1.0.2: resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==} dependencies: @@ -2931,12 +3192,12 @@ packages: '@lezer/lr': 1.4.2 dev: true - /@lezer/yaml@1.0.3: - resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==} + /@lezer/yaml@1.0.4: + resolution: {integrity: sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==} dependencies: - '@lezer/common': 1.2.3 - '@lezer/highlight': 1.2.1 - '@lezer/lr': 1.4.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 dev: true /@loadable/component@5.15.2(react@18.3.1): @@ -2958,20 +3219,24 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true dependencies: - detect-libc: 2.0.3 + detect-libc: 2.1.2 https-proxy-agent: 5.0.1 make-dir: 3.1.0 node-fetch: 2.7.0 nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.6.3 + semver: 7.7.4 tar: 6.2.1 transitivePeerDependencies: - encoding - supports-color dev: false + /@marijn/find-cluster-break@1.0.2: + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + dev: true + /@monaco-editor/loader@1.4.0(monaco-editor@0.33.0): resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} peerDependencies: @@ -3006,8 +3271,8 @@ packages: state-local: 1.0.7 dev: true - /@napi-rs/nice-android-arm-eabi@1.0.1: - resolution: {integrity: sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==} + /@napi-rs/nice-android-arm-eabi@1.1.1: + resolution: {integrity: sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==} engines: {node: '>= 10'} cpu: [arm] os: [android] @@ -3015,8 +3280,8 @@ packages: dev: true optional: true - /@napi-rs/nice-android-arm64@1.0.1: - resolution: {integrity: sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==} + /@napi-rs/nice-android-arm64@1.1.1: + resolution: {integrity: sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==} engines: {node: '>= 10'} cpu: [arm64] os: [android] @@ -3024,8 +3289,8 @@ packages: dev: true optional: true - /@napi-rs/nice-darwin-arm64@1.0.1: - resolution: {integrity: sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==} + /@napi-rs/nice-darwin-arm64@1.1.1: + resolution: {integrity: sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -3033,8 +3298,8 @@ packages: dev: true optional: true - /@napi-rs/nice-darwin-x64@1.0.1: - resolution: {integrity: sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==} + /@napi-rs/nice-darwin-x64@1.1.1: + resolution: {integrity: sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -3042,8 +3307,8 @@ packages: dev: true optional: true - /@napi-rs/nice-freebsd-x64@1.0.1: - resolution: {integrity: sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==} + /@napi-rs/nice-freebsd-x64@1.1.1: + resolution: {integrity: sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] @@ -3051,8 +3316,8 @@ packages: dev: true optional: true - /@napi-rs/nice-linux-arm-gnueabihf@1.0.1: - resolution: {integrity: sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==} + /@napi-rs/nice-linux-arm-gnueabihf@1.1.1: + resolution: {integrity: sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -3060,8 +3325,8 @@ packages: dev: true optional: true - /@napi-rs/nice-linux-arm64-gnu@1.0.1: - resolution: {integrity: sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==} + /@napi-rs/nice-linux-arm64-gnu@1.1.1: + resolution: {integrity: sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -3069,8 +3334,8 @@ packages: dev: true optional: true - /@napi-rs/nice-linux-arm64-musl@1.0.1: - resolution: {integrity: sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==} + /@napi-rs/nice-linux-arm64-musl@1.1.1: + resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -3078,8 +3343,8 @@ packages: dev: true optional: true - /@napi-rs/nice-linux-ppc64-gnu@1.0.1: - resolution: {integrity: sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==} + /@napi-rs/nice-linux-ppc64-gnu@1.1.1: + resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] @@ -3087,8 +3352,8 @@ packages: dev: true optional: true - /@napi-rs/nice-linux-riscv64-gnu@1.0.1: - resolution: {integrity: sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==} + /@napi-rs/nice-linux-riscv64-gnu@1.1.1: + resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] @@ -3096,8 +3361,8 @@ packages: dev: true optional: true - /@napi-rs/nice-linux-s390x-gnu@1.0.1: - resolution: {integrity: sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==} + /@napi-rs/nice-linux-s390x-gnu@1.1.1: + resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] @@ -3105,8 +3370,8 @@ packages: dev: true optional: true - /@napi-rs/nice-linux-x64-gnu@1.0.1: - resolution: {integrity: sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==} + /@napi-rs/nice-linux-x64-gnu@1.1.1: + resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -3114,8 +3379,8 @@ packages: dev: true optional: true - /@napi-rs/nice-linux-x64-musl@1.0.1: - resolution: {integrity: sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==} + /@napi-rs/nice-linux-x64-musl@1.1.1: + resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -3123,8 +3388,17 @@ packages: dev: true optional: true - /@napi-rs/nice-win32-arm64-msvc@1.0.1: - resolution: {integrity: sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==} + /@napi-rs/nice-openharmony-arm64@1.1.1: + resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [openharmony] + requiresBuild: true + dev: true + optional: true + + /@napi-rs/nice-win32-arm64-msvc@1.1.1: + resolution: {integrity: sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -3132,8 +3406,8 @@ packages: dev: true optional: true - /@napi-rs/nice-win32-ia32-msvc@1.0.1: - resolution: {integrity: sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==} + /@napi-rs/nice-win32-ia32-msvc@1.1.1: + resolution: {integrity: sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -3141,8 +3415,8 @@ packages: dev: true optional: true - /@napi-rs/nice-win32-x64-msvc@1.0.1: - resolution: {integrity: sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==} + /@napi-rs/nice-win32-x64-msvc@1.1.1: + resolution: {integrity: sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3150,27 +3424,28 @@ packages: dev: true optional: true - /@napi-rs/nice@1.0.1: - resolution: {integrity: sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==} + /@napi-rs/nice@1.1.1: + resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} engines: {node: '>= 10'} requiresBuild: true optionalDependencies: - '@napi-rs/nice-android-arm-eabi': 1.0.1 - '@napi-rs/nice-android-arm64': 1.0.1 - '@napi-rs/nice-darwin-arm64': 1.0.1 - '@napi-rs/nice-darwin-x64': 1.0.1 - '@napi-rs/nice-freebsd-x64': 1.0.1 - '@napi-rs/nice-linux-arm-gnueabihf': 1.0.1 - '@napi-rs/nice-linux-arm64-gnu': 1.0.1 - '@napi-rs/nice-linux-arm64-musl': 1.0.1 - '@napi-rs/nice-linux-ppc64-gnu': 1.0.1 - '@napi-rs/nice-linux-riscv64-gnu': 1.0.1 - '@napi-rs/nice-linux-s390x-gnu': 1.0.1 - '@napi-rs/nice-linux-x64-gnu': 1.0.1 - '@napi-rs/nice-linux-x64-musl': 1.0.1 - '@napi-rs/nice-win32-arm64-msvc': 1.0.1 - '@napi-rs/nice-win32-ia32-msvc': 1.0.1 - '@napi-rs/nice-win32-x64-msvc': 1.0.1 + '@napi-rs/nice-android-arm-eabi': 1.1.1 + '@napi-rs/nice-android-arm64': 1.1.1 + '@napi-rs/nice-darwin-arm64': 1.1.1 + '@napi-rs/nice-darwin-x64': 1.1.1 + '@napi-rs/nice-freebsd-x64': 1.1.1 + '@napi-rs/nice-linux-arm-gnueabihf': 1.1.1 + '@napi-rs/nice-linux-arm64-gnu': 1.1.1 + '@napi-rs/nice-linux-arm64-musl': 1.1.1 + '@napi-rs/nice-linux-ppc64-gnu': 1.1.1 + '@napi-rs/nice-linux-riscv64-gnu': 1.1.1 + '@napi-rs/nice-linux-s390x-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-musl': 1.1.1 + '@napi-rs/nice-openharmony-arm64': 1.1.1 + '@napi-rs/nice-win32-arm64-msvc': 1.1.1 + '@napi-rs/nice-win32-ia32-msvc': 1.1.1 + '@napi-rs/nice-win32-x64-msvc': 1.1.1 dev: true optional: true @@ -3219,7 +3494,7 @@ packages: requiresBuild: true dependencies: '@gar/promisify': 1.1.3 - semver: 7.6.3 + semver: 7.7.4 dev: false optional: true @@ -3396,7 +3671,7 @@ packages: engines: {node: '>=14.0.0'} dev: true - /@replit/codemirror-lang-csharp@6.2.0(@codemirror/autocomplete@6.18.3)(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2): + /@replit/codemirror-lang-csharp@6.2.0(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8): resolution: {integrity: sha512-6utbaWkoymhoAXj051mkRp+VIJlpwUgCX9Toevz3YatiZsz512fw3OVCedXQx+WcR0wb6zVHjChnuxqfCLtFVQ==} peerDependencies: '@codemirror/autocomplete': ^6.0.0 @@ -3407,16 +3682,16 @@ packages: '@lezer/highlight': ^1.0.0 '@lezer/lr': ^1.0.0 dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) - '@codemirror/language': 6.10.6 + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 '@codemirror/state': 6.4.1 '@codemirror/view': 6.35.0 - '@lezer/common': 1.2.3 - '@lezer/highlight': 1.2.1 - '@lezer/lr': 1.4.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 dev: true - /@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.18.3)(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2): + /@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8): resolution: {integrity: sha512-lvzjoYn9nfJzBD5qdm3Ut6G3+Or2wEacYIDJ49h9+19WSChVnxv4ojf+rNmQ78ncuxIt/bfbMvDLMeMP0xze6g==} peerDependencies: '@codemirror/autocomplete': ^6.0.0 @@ -3427,25 +3702,25 @@ packages: '@lezer/highlight': ^1.0.0 '@lezer/lr': ^1.0.0 dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) - '@codemirror/language': 6.10.6 + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 '@codemirror/state': 6.4.1 '@codemirror/view': 6.35.0 - '@lezer/common': 1.2.3 - '@lezer/highlight': 1.2.1 - '@lezer/lr': 1.4.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 dev: true - /@replit/codemirror-lang-solidity@6.0.2(@codemirror/language@6.10.6): + /@replit/codemirror-lang-solidity@6.0.2(@codemirror/language@6.12.2): resolution: {integrity: sha512-/dpTVH338KFV6SaDYYSadkB4bI/0B0QRF/bkt1XS3t3QtyR49mn6+2k0OUQhvt2ZSO7kt10J+OPilRAtgbmX0w==} peerDependencies: '@codemirror/language': ^6.0.0 dependencies: - '@codemirror/language': 6.10.6 + '@codemirror/language': 6.12.2 '@lezer/highlight': 1.2.1 dev: true - /@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.18.3)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.2)(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/javascript@1.4.21)(@lezer/lr@1.4.2): + /@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.20.1)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.2)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8): resolution: {integrity: sha512-U2OqqgMM6jKelL0GNWbAmqlu1S078zZNoBqlJBW+retTc5M4Mha6/Y2cf4SVg6ddgloJvmcSpt4hHrVoM4ePRA==} peerDependencies: '@codemirror/autocomplete': ^6.0.0 @@ -3460,17 +3735,17 @@ packages: '@lezer/javascript': ^1.2.0 '@lezer/lr': ^1.0.0 dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) + '@codemirror/autocomplete': 6.20.1 '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) '@codemirror/lang-html': 6.4.9 '@codemirror/lang-javascript': 6.2.2 - '@codemirror/language': 6.10.6 + '@codemirror/language': 6.12.2 '@codemirror/state': 6.4.1 '@codemirror/view': 6.35.0 - '@lezer/common': 1.2.3 - '@lezer/highlight': 1.2.1 - '@lezer/javascript': 1.4.21 - '@lezer/lr': 1.4.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/javascript': 1.5.4 + '@lezer/lr': 1.4.8 dev: true /@sideway/address@4.1.5: @@ -4145,12 +4420,12 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.35.0)(typescript@5.2.2) '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.2.2) - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 eslint: 8.35.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 - semver: 7.6.3 + semver: 7.7.4 tsutils: 3.21.0(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: @@ -4172,7 +4447,7 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 eslint: 8.35.0 typescript: 5.2.2 transitivePeerDependencies: @@ -4201,7 +4476,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.2.2) - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 eslint: 8.35.0 tsutils: 3.21.0(typescript@5.2.2) typescript: 5.2.2 @@ -4225,10 +4500,10 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 + semver: 7.7.4 tsutils: 3.21.0(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: @@ -4252,7 +4527,7 @@ packages: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) eslint: 8.35.0 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.4 transitivePeerDependencies: - supports-color - typescript @@ -4266,7 +4541,7 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@uiw/codemirror-extensions-basic-setup@4.23.6(@codemirror/autocomplete@6.18.3)(@codemirror/commands@6.7.1)(@codemirror/language@6.10.6)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0): + /@uiw/codemirror-extensions-basic-setup@4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.7.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0): resolution: {integrity: sha512-bvtq8IOvdkLJMhoJBRGPEzU51fMpPDwEhcAHp9xCR05MtbIokQgsnLXrmD1aZm6e7s/3q47H+qdSfAAkR5MkLA==} peerDependencies: '@codemirror/autocomplete': '>=6.0.0' @@ -4277,16 +4552,16 @@ packages: '@codemirror/state': '>=6.0.0' '@codemirror/view': '>=6.0.0' dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) + '@codemirror/autocomplete': 6.20.1 '@codemirror/commands': 6.7.1 - '@codemirror/language': 6.10.6 - '@codemirror/lint': 6.8.4 - '@codemirror/search': 6.5.8 + '@codemirror/language': 6.12.2 + '@codemirror/lint': 6.9.5 + '@codemirror/search': 6.6.0 '@codemirror/state': 6.4.1 '@codemirror/view': 6.35.0 dev: true - /@uiw/codemirror-extensions-langs@4.23.6(@codemirror/autocomplete@6.18.3)(@codemirror/language-data@6.5.1)(@codemirror/language@6.10.6)(@codemirror/legacy-modes@6.4.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/javascript@1.4.21)(@lezer/lr@1.4.2): + /@uiw/codemirror-extensions-langs@4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/language-data@6.5.2)(@codemirror/language@6.12.2)(@codemirror/legacy-modes@6.5.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8): resolution: {integrity: sha512-VKWbEXmVq3EFYrJPWXH4Ei1f92zxuAg6dOlo8suSmwjmEc0qjNEP5Ss2CUi9LlzuWMGMmZgdKw56I3L71wYOog==} peerDependencies: '@codemirror/language-data': '>=6.0.0' @@ -4311,13 +4586,13 @@ packages: '@codemirror/lang-vue': 0.1.3 '@codemirror/lang-wast': 6.0.2 '@codemirror/lang-xml': 6.1.0 - '@codemirror/language-data': 6.5.1(@codemirror/view@6.35.0) - '@codemirror/legacy-modes': 6.4.2 + '@codemirror/language-data': 6.5.2(@codemirror/view@6.35.0) + '@codemirror/legacy-modes': 6.5.2 '@nextjournal/lang-clojure': 1.0.0 - '@replit/codemirror-lang-csharp': 6.2.0(@codemirror/autocomplete@6.18.3)(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2) - '@replit/codemirror-lang-nix': 6.0.1(@codemirror/autocomplete@6.18.3)(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2) - '@replit/codemirror-lang-solidity': 6.0.2(@codemirror/language@6.10.6) - '@replit/codemirror-lang-svelte': 6.0.0(@codemirror/autocomplete@6.18.3)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.2)(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/javascript@1.4.21)(@lezer/lr@1.4.2) + '@replit/codemirror-lang-csharp': 6.2.0(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8) + '@replit/codemirror-lang-nix': 6.0.1(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8) + '@replit/codemirror-lang-solidity': 6.0.2(@codemirror/language@6.12.2) + '@replit/codemirror-lang-svelte': 6.0.0(@codemirror/autocomplete@6.20.1)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.2)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8) codemirror-lang-mermaid: 0.5.0 transitivePeerDependencies: - '@codemirror/autocomplete' @@ -4330,7 +4605,7 @@ packages: - '@lezer/lr' dev: true - /@uiw/react-codemirror@4.23.6(@babel/runtime@7.26.0)(@codemirror/autocomplete@6.18.3)(@codemirror/language@6.10.6)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.35.0)(codemirror@6.0.1)(react-dom@18.3.1)(react@18.3.1): + /@uiw/react-codemirror@4.23.6(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.35.0)(codemirror@6.0.2)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-caYKGV6TfGLRV1HHD3p0G3FiVzKL1go7wes5XT2nWjB0+dTdyzyb81MKRSacptgZcotujfNO6QXn65uhETRAMw==} peerDependencies: '@babel/runtime': '>=7.11.0' @@ -4346,13 +4621,13 @@ packages: react-dom: optional: true dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.28.6 '@codemirror/commands': 6.7.1 '@codemirror/state': 6.4.1 - '@codemirror/theme-one-dark': 6.1.2 + '@codemirror/theme-one-dark': 6.1.3 '@codemirror/view': 6.35.0 - '@uiw/codemirror-extensions-basic-setup': 4.23.6(@codemirror/autocomplete@6.18.3)(@codemirror/commands@6.7.1)(@codemirror/language@6.10.6)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0) - codemirror: 6.0.1(@lezer/common@1.2.3) + '@uiw/codemirror-extensions-basic-setup': 4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.7.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0) + codemirror: 6.0.2 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -4726,7 +5001,7 @@ packages: react-error-overlay: 6.0.9 react-refresh: 0.14.2 resolve: 1.22.8 - semver: 7.6.3 + semver: 7.7.4 yargs-parser: 21.1.1 optionalDependencies: '@umijs/mako-darwin-arm64': 0.11.1 @@ -5110,7 +5385,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 transitivePeerDependencies: - supports-color dev: false @@ -5120,6 +5395,15 @@ packages: engines: {node: '>= 8.0.0'} dependencies: humanize-ms: 1.2.1 + dev: true + + /agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: false + optional: true /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} @@ -5224,8 +5508,8 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - /ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + /ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} dev: true @@ -5367,8 +5651,8 @@ packages: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} dev: false - /aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + /aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} dev: false /are-we-there-yet@2.0.0: @@ -5814,9 +6098,16 @@ packages: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 + dev: true - /brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + /brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} dependencies: balanced-match: 1.0.2 dev: true @@ -6198,18 +6489,16 @@ packages: '@lezer/lr': 1.4.2 dev: true - /codemirror@6.0.1(@lezer/common@1.2.3): - resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} + /codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) - '@codemirror/commands': 6.7.1 - '@codemirror/language': 6.10.6 - '@codemirror/lint': 6.8.4 - '@codemirror/search': 6.5.8 + '@codemirror/autocomplete': 6.20.1 + '@codemirror/commands': 6.10.2 + '@codemirror/language': 6.12.2 + '@codemirror/lint': 6.9.5 + '@codemirror/search': 6.6.0 '@codemirror/state': 6.4.1 '@codemirror/view': 6.35.0 - transitivePeerDependencies: - - '@lezer/common' dev: true /color-convert@1.9.3: @@ -6333,13 +6622,13 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - /concat-stream@1.6.2: - resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} - engines: {'0': node >= 0.8} + /concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} dependencies: buffer-from: 1.1.2 inherits: 2.0.4 - readable-stream: 2.3.8 + readable-stream: 3.6.2 typedarray: 0.0.6 dev: false @@ -6436,6 +6725,7 @@ packages: /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: true /cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} @@ -6616,7 +6906,7 @@ packages: postcss-modules-scope: 3.2.1(postcss@8.4.49) postcss-modules-values: 4.0.0(postcss@8.4.49) postcss-value-parser: 4.2.0 - semver: 7.6.3 + semver: 7.7.4 dev: true /css-prefers-color-scheme@6.0.3(postcss@8.4.49): @@ -6795,6 +7085,17 @@ packages: ms: 2.1.3 supports-color: 5.5.0 + /debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -6914,8 +7215,8 @@ packages: hasBin: true dev: true - /detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + /detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} dev: false @@ -8082,10 +8383,10 @@ packages: deepmerge: 4.3.1 fs-extra: 10.1.0 memfs: 3.5.3 - minimatch: 3.1.2 + minimatch: 3.1.5 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.6.3 + semver: 7.7.4 tapable: 2.2.1 typescript: 5.2.2 dev: true @@ -8185,7 +8486,7 @@ packages: engines: {node: '>=10'} deprecated: This package is no longer supported. dependencies: - aproba: 2.0.0 + aproba: 2.1.0 color-support: 1.1.3 console-control-strings: 1.1.0 has-unicode: 2.0.1 @@ -8202,7 +8503,7 @@ packages: deprecated: This package is no longer supported. requiresBuild: true dependencies: - aproba: 2.0.0 + aproba: 2.1.0 color-support: 1.1.3 console-control-strings: 1.1.0 has-unicode: 2.0.1 @@ -8286,26 +8587,27 @@ packages: is-glob: 4.0.3 dev: true - /glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + /glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true dependencies: foreground-child: 3.3.0 jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 + minimatch: 9.0.9 + minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 dev: true /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 @@ -8575,9 +8877,8 @@ packages: entities: 2.2.0 dev: true - /http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - requiresBuild: true + /http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} dev: false optional: true @@ -8605,7 +8906,7 @@ packages: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 transitivePeerDependencies: - supports-color dev: false @@ -8645,7 +8946,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 transitivePeerDependencies: - supports-color dev: false @@ -8822,13 +9123,9 @@ packages: loose-envify: 1.4.0 dev: true - /ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + /ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} - requiresBuild: true - dependencies: - jsbn: 1.1.0 - sprintf-js: 1.1.3 dev: false optional: true @@ -9171,6 +9468,7 @@ packages: /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: true /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -9325,12 +9623,6 @@ packages: dependencies: argparse: 2.0.1 - /jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - requiresBuild: true - dev: false - optional: true - /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -9741,7 +10033,7 @@ packages: ansi-escapes: 5.0.0 cli-cursor: 4.0.0 slice-ansi: 5.0.0 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 wrap-ansi: 8.1.0 dev: true @@ -9830,9 +10122,9 @@ packages: engines: {node: '>= 10'} requiresBuild: true dependencies: - agentkeepalive: 4.5.0 + agentkeepalive: 4.6.0 cacache: 15.3.0 - http-cache-semantics: 4.1.1 + http-cache-semantics: 4.2.0 http-proxy-agent: 4.0.1 https-proxy-agent: 5.0.1 is-lambda: 1.0.1 @@ -10018,12 +10310,18 @@ packages: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 + dev: true - /minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + /minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + dependencies: + brace-expansion: 1.1.12 + + /minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 dev: true /minimist-options@4.1.0: @@ -10037,6 +10335,7 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true /minipass-collect@1.0.2: resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} @@ -10099,8 +10398,8 @@ packages: engines: {node: '>=8'} dev: false - /minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + /minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} dev: true @@ -10117,6 +10416,7 @@ packages: hasBin: true dependencies: minimist: 1.2.8 + dev: true /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} @@ -10151,17 +10451,14 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /multer@1.4.5-lts.1: - resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==} - engines: {node: '>= 6.0.0'} + /multer@2.1.1: + resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} + engines: {node: '>= 10.16.0'} dependencies: append-field: 1.0.0 busboy: 1.6.0 - concat-stream: 1.6.2 - mkdirp: 0.5.6 - object-assign: 4.1.1 + concat-stream: 2.0.0 type-is: 1.6.18 - xtend: 4.0.2 dev: false /mz@2.7.0: @@ -10263,7 +10560,7 @@ packages: nopt: 5.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.6.3 + semver: 7.7.4 tar: 6.2.1 which: 2.0.2 transitivePeerDependencies: @@ -10395,7 +10692,7 @@ packages: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.15.1 - semver: 7.6.3 + semver: 7.7.4 validate-npm-package-license: 3.0.4 dev: true @@ -10781,7 +11078,7 @@ packages: engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 + minipass: 7.1.3 dev: true /path-to-regexp@0.1.12: @@ -10892,7 +11189,7 @@ packages: /piscina@4.7.0: resolution: {integrity: sha512-b8hvkpp9zS0zsfa939b/jXbe64Z2gZv0Ha7FYPNUiDIB1y2AtxcOZdfP8xN8HFjUaqQiT9gRlfjAsoL8vdJ1Iw==} optionalDependencies: - '@napi-rs/nice': 1.0.1 + '@napi-rs/nice': 1.1.1 dev: true /point-in-polygon@1.1.0: @@ -11530,6 +11827,7 @@ packages: /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: true /process-okam@0.11.10: resolution: {integrity: sha512-p8e5nl6/OCeMalVb9dSojND5B9m/nq64WsyUfRmrTdLMKcNYcDN++/2I8WV1mTQDqrh2PQ6tIIb2A7/A38eSvw==} @@ -12951,6 +13249,7 @@ packages: safe-buffer: 5.1.2 string_decoder: 1.1.1 util-deprecate: 1.0.2 + dev: true /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} @@ -13156,7 +13455,7 @@ packages: engines: {node: '>=14'} hasBin: true dependencies: - glob: 10.4.5 + glob: 10.5.0 dev: true /ripemd160@2.0.2: @@ -13221,6 +13520,7 @@ packages: /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: true /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -13301,6 +13601,11 @@ packages: engines: {node: '>=10'} hasBin: true + /semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + /send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -13555,19 +13860,18 @@ packages: requiresBuild: true dependencies: agent-base: 6.0.2 - debug: 4.3.7(supports-color@5.5.0) - socks: 2.8.3 + debug: 4.4.3 + socks: 2.8.7 transitivePeerDependencies: - supports-color dev: false optional: true - /socks@2.8.3: - resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + /socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - requiresBuild: true dependencies: - ip-address: 9.0.5 + ip-address: 10.1.0 smart-buffer: 4.2.0 dev: false optional: true @@ -13654,7 +13958,7 @@ packages: /spdy-transport@3.0.0: resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} dependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -13668,7 +13972,7 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} dependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -13702,12 +14006,6 @@ packages: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true - /sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - requiresBuild: true - dev: false - optional: true - /ssri@8.0.1: resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} engines: {node: '>= 8'} @@ -13810,7 +14108,7 @@ packages: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 dev: true /string.prototype.matchall@4.0.11: @@ -13868,6 +14166,7 @@ packages: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 + dev: true /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -13880,11 +14179,11 @@ packages: dependencies: ansi-regex: 5.0.1 - /strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + /strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} dependencies: - ansi-regex: 6.1.0 + ansi-regex: 6.2.2 dev: true /strip-final-newline@2.0.0: @@ -13913,6 +14212,10 @@ packages: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} dev: true + /style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + dev: true + /style-search@0.1.0: resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} dev: true @@ -14152,6 +14455,7 @@ packages: /tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me dependencies: chownr: 2.0.0 fs-minipass: 2.1.0 @@ -14178,7 +14482,7 @@ packages: dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 - minimatch: 3.1.2 + minimatch: 3.1.5 dev: true /text-hex@1.0.0: @@ -15123,7 +15427,7 @@ packages: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 dev: true /wrappy@1.0.2: @@ -15140,6 +15444,7 @@ packages: /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + dev: true /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} @@ -15225,3 +15530,7 @@ packages: - encoding - supports-color dev: false + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false From fd516977e3bc66ced448be0529828b35e47e056e Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 7 Mar 2026 22:35:18 +0800 Subject: [PATCH 06/24] chore: upgrade nodemailer --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5858eaf2..1494538a 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "lodash": "^4.17.21", "multer": "2.1.1", "node-schedule": "^2.1.0", - "nodemailer": "^6.9.16", + "nodemailer": "^8.0.1", "p-queue-cjs": "7.3.4", "@bufbuild/protobuf": "^2.10.0", "ps-tree": "^1.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54c1a45e..03414b75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,8 +92,8 @@ dependencies: specifier: ^2.1.0 version: 2.1.1 nodemailer: - specifier: ^6.9.16 - version: 6.9.16 + specifier: ^8.0.1 + version: 8.0.1 p-queue-cjs: specifier: 7.3.4 version: 7.3.4 @@ -10647,8 +10647,8 @@ packages: sorted-array-functions: 1.3.0 dev: false - /nodemailer@6.9.16: - resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==} + /nodemailer@8.0.1: + resolution: {integrity: sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==} engines: {node: '>=6.0.0'} dev: false From 07bf0c705b13deba5b1850c886c72c2868b8ac91 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:43:56 +0800 Subject: [PATCH 07/24] fix: respect QlPort env var in Docker health check (#2963) * Initial plan * fix: use QlPort env variable in health check with fallback to default 5700 Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- docker/310.Dockerfile | 2 +- docker/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/310.Dockerfile b/docker/310.Dockerfile index ebd0af81..fb690b57 100644 --- a/docker/310.Dockerfile +++ b/docker/310.Dockerfile @@ -84,6 +84,6 @@ COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/ WORKDIR ${QL_DIR} HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ - CMD curl -sf --noproxy '*' http://127.0.0.1:5700/api/health || exit 1 + CMD curl -sf --noproxy '*' http://127.0.0.1:${QlPort:-5700}/api/health || exit 1 ENTRYPOINT ["./docker/docker-entrypoint.sh"] diff --git a/docker/Dockerfile b/docker/Dockerfile index 8ca6c947..5682ae9d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -84,6 +84,6 @@ COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/ WORKDIR ${QL_DIR} HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ - CMD curl -sf --noproxy '*' http://127.0.0.1:5700/api/health || exit 1 + CMD curl -sf --noproxy '*' http://127.0.0.1:${QlPort:-5700}/api/health || exit 1 ENTRYPOINT ["./docker/docker-entrypoint.sh"] From 66700ebe1a02e675542538737f97ccf874a6b4d5 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:01:36 +0800 Subject: [PATCH 08/24] Add OpeniLink notification channel (#2988) * Initial plan * Add OpeniLink notification channel support Agent-Logs-Url: https://github.com/whyour/qinglong/sessions/c80b4882-1bd7-4ffe-9180-cd3220da5986 Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> * Add context_token and hub_url to OpeniLink notification channel Agent-Logs-Url: https://github.com/whyour/qinglong/sessions/a5e66f5a-dab8-4a65-96ca-960d35fa9d50 Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/data/notify.ts | 10 +++++++- back/services/notify.ts | 32 ++++++++++++++++++++++++ sample/config.sample.sh | 9 +++++++ sample/notify.js | 54 +++++++++++++++++++++++++++++++++++++++++ sample/notify.py | 43 ++++++++++++++++++++++++++++++++ src/utils/config.ts | 22 +++++++++++++++++ 6 files changed, 169 insertions(+), 1 deletion(-) diff --git a/back/data/notify.ts b/back/data/notify.ts index b0899500..1d048dfe 100644 --- a/back/data/notify.ts +++ b/back/data/notify.ts @@ -20,6 +20,7 @@ export enum NotificationMode { 'chronocat' = 'Chronocat', 'ntfy' = 'ntfy', 'wxPusherBot' = 'wxPusherBot', + 'openiLink' = 'openiLink', } abstract class NotificationBaseInfo { @@ -161,6 +162,12 @@ export class WxPusherBotNotification extends NotificationBaseInfo { public wxPusherBotUids = ''; } +export class OpeniLinkNotification extends NotificationBaseInfo { + public openiLinkAppToken = ''; + public openiLinkHubUrl = ''; + public openiLinkContextToken = ''; +} + export interface NotificationInfo extends GoCqHttpBotNotification, GotifyNotification, @@ -182,4 +189,5 @@ export interface NotificationInfo ChronocatNotification, LarkNotification, NtfyNotification, - WxPusherBotNotification {} + WxPusherBotNotification, + OpeniLinkNotification {} diff --git a/back/services/notify.ts b/back/services/notify.ts index 22609748..a8b2aeea 100644 --- a/back/services/notify.ts +++ b/back/services/notify.ts @@ -34,6 +34,7 @@ export default class NotificationService { ['chronocat', this.chronocat], ['ntfy', this.ntfy], ['wxPusherBot', this.wxPusherBot], + ['openiLink', this.openiLink], ]); private title = ''; @@ -858,4 +859,35 @@ export default class NotificationService { } return {}; } + + private async openiLink() { + const { openiLinkAppToken, openiLinkHubUrl, openiLinkContextToken } = + this.params; + const baseUrl = openiLinkHubUrl?.replace(/\/$/, '') || 'https://hub.openilink.com'; + const url = `${baseUrl}/bot/v1/message/send`; + const body: Record = { + type: 'text', + content: `${this.title}\n\n${this.content}`, + }; + if (openiLinkContextToken) { + body.context_token = openiLinkContextToken; + } + try { + const res = await httpClient.post(url, { + ...this.gotOption, + json: body, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${openiLinkAppToken}`, + }, + }); + if (res.ok) { + return true; + } else { + throw new Error(JSON.stringify(res)); + } + } catch (error: any) { + throw new Error(error.response ? error.response.body : error); + } + } } diff --git a/sample/config.sample.sh b/sample/config.sample.sh index 734f4ae5..9c93e78f 100644 --- a/sample/config.sample.sh +++ b/sample/config.sample.sh @@ -259,4 +259,13 @@ export WEBHOOK_METHOD="" ## 支持 text/plain、application/json、multipart/form-data、application/x-www-form-urlencoded export WEBHOOK_CONTENT_TYPE="" +## 23. OpeniLink +## 官方文档: https://openilink.com/docs/hub/apps +## 在 OpeniLink Hub 后台安装 App 后获取 app_token +export OPENILINK_APP_TOKEN="" +## OpeniLink Hub 地址,默认为 https://hub.openilink.com,自建 Hub 时填写自己的地址 +export OPENILINK_HUB_URL="" +## OpeniLink 的 context_token,用于标识消息会话上下文,可从消息事件中获取 +export OPENILINK_CONTEXT_TOKEN="" + ## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可 diff --git a/sample/notify.js b/sample/notify.js index f935e8ce..89803264 100644 --- a/sample/notify.js +++ b/sample/notify.js @@ -151,6 +151,11 @@ const push_config = { WXPUSHER_APP_TOKEN: '', // wxpusher 的 appToken WXPUSHER_TOPIC_IDS: '', // wxpusher 的 主题ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行 WXPUSHER_UIDS: '', // wxpusher 的 用户ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行 + + // 官方文档: https://openilink.com/docs/hub/apps + OPENILINK_APP_TOKEN: '', // OpeniLink 的 app_token,在 OpeniLink Hub 后台安装 App 后获取 + OPENILINK_HUB_URL: '', // OpeniLink Hub 地址,默认为 https://hub.openilink.com,自建 Hub 时填写自己的地址 + OPENILINK_CONTEXT_TOKEN: '', // OpeniLink 的 context_token,用于标识消息会话上下文,可从消息事件中获取 }; for (const key in push_config) { @@ -1408,6 +1413,54 @@ function wxPusherNotify(text, desp) { }); } +function openiLinkNotify(text, desp) { + return new Promise((resolve) => { + const { OPENILINK_APP_TOKEN, OPENILINK_HUB_URL, OPENILINK_CONTEXT_TOKEN } = + push_config; + if (OPENILINK_APP_TOKEN) { + const baseUrl = OPENILINK_HUB_URL + ? OPENILINK_HUB_URL.replace(/\/$/, '') + : 'https://hub.openilink.com'; + const body = { + type: 'text', + content: `${text}\n\n${desp}`, + }; + if (OPENILINK_CONTEXT_TOKEN) { + body.context_token = OPENILINK_CONTEXT_TOKEN; + } + const options = { + url: `${baseUrl}/bot/v1/message/send`, + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${OPENILINK_APP_TOKEN}`, + }, + timeout, + }; + + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log('OpeniLink 发送通知消息失败!\n', err); + } else { + if (data.ok) { + console.log('OpeniLink 发送通知消息成功!'); + } else { + console.log(`OpeniLink 发送通知消息异常:${data.error}`); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + } else { + resolve(); + } + }); +} + function parseString(input, valueFormatFn) { const regex = /(\w+):\s*((?:(?!\n\w+:).)*)/g; const matches = {}; @@ -1538,6 +1591,7 @@ async function sendNotify(text, desp, params = {}) { qmsgNotify(text, desp), // 自定义通知 ntfyNotify(text, desp), // Ntfy wxPusherNotify(text, desp), // wxpusher + openiLinkNotify(text, desp), // OpeniLink ]); } diff --git a/sample/notify.py b/sample/notify.py index 8dd885ca..46383498 100644 --- a/sample/notify.py +++ b/sample/notify.py @@ -135,6 +135,10 @@ push_config = { 'WXPUSHER_APP_TOKEN': '', # wxpusher 的 appToken 官方文档: https://wxpusher.zjiecode.com/docs/ 管理后台: https://wxpusher.zjiecode.com/admin/ 'WXPUSHER_TOPIC_IDS': '', # wxpusher 的 主题ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行 'WXPUSHER_UIDS': '', # wxpusher 的 用户ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行 + + 'OPENILINK_APP_TOKEN': '', # OpeniLink 的 app_token,在 OpeniLink Hub 后台安装 App 后获取 官方文档: https://openilink.com/docs/hub/apps + 'OPENILINK_HUB_URL': '', # OpeniLink Hub 地址,默认为 https://hub.openilink.com,自建 Hub 时填写自己的地址 + 'OPENILINK_CONTEXT_TOKEN': '', # OpeniLink 的 context_token,用于标识消息会话上下文,可从消息事件中获取 } # fmt: on @@ -898,6 +902,43 @@ def wxpusher_bot(title: str, content: str) -> None: print(f"wxpusher 推送失败!错误信息:{response.get('msg')}") +def openilink(title: str, content: str) -> None: + """ + 通过 OpeniLink 推送消息。 + 支持的环境变量: + - OPENILINK_APP_TOKEN: 在 OpeniLink Hub 后台安装 App 后获取的 app_token + - OPENILINK_HUB_URL: OpeniLink Hub 地址,默认为 https://hub.openilink.com + - OPENILINK_CONTEXT_TOKEN: 消息会话上下文 token,可从消息事件中获取 + """ + if not push_config.get("OPENILINK_APP_TOKEN"): + return + + print("OpeniLink 服务启动") + + base_url = ( + push_config.get("OPENILINK_HUB_URL", "").rstrip("/") + or "https://hub.openilink.com" + ) + url = f"{base_url}/bot/v1/message/send" + headers = { + "Content-Type": "application/json", + "Authorization": f'Bearer {push_config.get("OPENILINK_APP_TOKEN")}', + } + data = { + "type": "text", + "content": f"{title}\n\n{content}", + } + if push_config.get("OPENILINK_CONTEXT_TOKEN"): + data["context_token"] = push_config.get("OPENILINK_CONTEXT_TOKEN") + + response = requests.post(url=url, json=data, headers=headers).json() + + if response.get("ok"): + print("OpeniLink 推送成功!") + else: + print(f'OpeniLink 推送失败!错误信息:{response.get("error")}') + + def parse_headers(headers): if not headers: return {} @@ -1063,6 +1104,8 @@ def add_notify_function(): push_config.get("WXPUSHER_TOPIC_IDS") or push_config.get("WXPUSHER_UIDS") ): notify_function.append(wxpusher_bot) + if push_config.get("OPENILINK_APP_TOKEN"): + notify_function.append(openilink) if not notify_function: print(f"无推送渠道,请检查通知变量是否正确") return notify_function diff --git a/src/utils/config.ts b/src/utils/config.ts index b529a7d0..f15a48b5 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -98,6 +98,7 @@ export default { { value: 'pushPlus', label: 'PushPlus' }, { value: 'wePlusBot', label: intl.get('微加机器人') }, { value: 'wxPusherBot', label: 'wxPusher' }, + { value: 'openiLink', label: 'OpeniLink' }, { value: 'chat', label: intl.get('群晖chat') }, { value: 'email', label: intl.get('邮箱') }, { value: 'lark', label: intl.get('飞书机器人') }, @@ -387,6 +388,27 @@ export default { required: false, }, ], + openiLink: [ + { + label: 'openiLinkAppToken', + tip: intl.get( + 'OpeniLink的app_token,在OpeniLink Hub后台安装App后获取,参考 https://openilink.com/docs/hub/apps', + ), + required: true, + }, + { + label: 'openiLinkHubUrl', + tip: intl.get( + 'OpeniLink Hub地址,默认为 https://hub.openilink.com,自建Hub时填写自己的地址', + ), + }, + { + label: 'openiLinkContextToken', + tip: intl.get( + 'OpeniLink的context_token,用于标识消息会话上下文,可从消息事件中获取', + ), + }, + ], lark: [ { label: 'larkKey', From a1ae08da58c047d27e07725219532212638dda1e Mon Sep 17 00:00:00 2001 From: TengDream <42526114+tenoms@users.noreply.github.com> Date: Wed, 6 May 2026 00:32:29 +0800 Subject: [PATCH 09/24] fix: create root .tmp directory on init for data export (#2993) The data export feature (system backup) writes data.tgz to `config.tmpPath` which resolves to `/.tmp/`. However, `initFile.ts` only created `/log/.tmp/` (used for crontab list temp files), never the root-level `.tmp/` directory. In Docker deployments, `shell/share.sh`'s `fix_config()` creates `$dir_root/.tmp` during shell initialization, but local/non-Docker deployments that start the Node service directly skip the shell init, causing a 404 ENOENT error when attempting to export/backup data. Add `rootTmpPath` (`/.tmp/`) to the directories array in `initFile.ts` so it is created during Node service startup regardless of deployment method. --- back/loaders/initFile.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/back/loaders/initFile.ts b/back/loaders/initFile.ts index 2eab9446..ac629d63 100644 --- a/back/loaders/initFile.ts +++ b/back/loaders/initFile.ts @@ -20,6 +20,7 @@ const uploadPath = path.join(dataPath, 'upload/'); const bakPath = path.join(dataPath, 'bak/'); const samplePath = path.join(rootPath, 'sample/'); const tmpPath = path.join(logPath, '.tmp/'); +const rootTmpPath = path.join(rootPath, '.tmp/'); const confFile = path.join(configPath, 'config.sh'); const sampleConfigFile = path.join(samplePath, 'config.sample.sh'); const sampleTaskShellFile = path.join(samplePath, 'task.sample.sh'); @@ -44,6 +45,7 @@ const directories = [ preloadPath, logPath, tmpPath, + rootTmpPath, uploadPath, sshPath, bakPath, From 1315578878b5db655af86a00e06eaf929a61b883 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 6 May 2026 00:34:26 +0800 Subject: [PATCH 10/24] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E6=8E=A5=E6=94=B6=E9=82=AE=E7=AE=B1=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=20(#2973)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 支持自定义接收邮箱地址 * 新增支持多个接收邮箱,同步到node和系统内置版本 --- back/services/notify.ts | 11 ++++++++++- sample/config.sample.sh | 4 +++- sample/notify.js | 26 ++++++++++++++++++++++---- sample/notify.py | 16 ++++++++-------- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/back/services/notify.ts b/back/services/notify.ts index a8b2aeea..39a0e47b 100644 --- a/back/services/notify.ts +++ b/back/services/notify.ts @@ -91,6 +91,14 @@ export default class NotificationService { return true; } + private parseMailRecipients(value?: string) { + const recipients = (value || '') + .split(/[;;]/) + .map((item) => item.trim()) + .filter(Boolean); + return recipients.length > 0 ? recipients : undefined; + } + private async gotify() { const { gotifyUrl, gotifyToken, gotifyPriority = 1 } = this.params; try { @@ -592,6 +600,7 @@ export default class NotificationService { private async email() { const { emailPass, emailService, emailUser, emailTo } = this.params; + const recipients = this.parseMailRecipients(emailTo) || emailUser; try { const transporter = nodemailer.createTransport({ @@ -604,7 +613,7 @@ export default class NotificationService { const info = await transporter.sendMail({ from: `"青龙快讯" <${emailUser}>`, - to: emailTo ? emailTo.split(';') : emailUser, + to: recipients, subject: `${this.title}`, html: `${this.content.replace(/\n/g, '
')}`, }); diff --git a/sample/config.sample.sh b/sample/config.sample.sh index 9c93e78f..9f653e56 100644 --- a/sample/config.sample.sh +++ b/sample/config.sample.sh @@ -195,12 +195,14 @@ export SMTP_SERVER="" ## SMTP 发送邮件服务器是否使用 SSL,填写 true 或 false export SMTP_SSL="" -## smtp_email 填写 SMTP 收发件邮箱,通知将会由自己发给自己 +## smtp_email 填写 SMTP 发件邮箱 export SMTP_EMAIL="" ## smtp_password 填写 SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定 export SMTP_PASSWORD="" ## smtp_name 填写 SMTP 收发件人姓名,可随意填写 export SMTP_NAME="" +## smtp_email_to 填写 SMTP 收件邮箱,多个用英文;分隔,不填默认发给发件邮箱 +export SMTP_EMAIL_TO="" ## 17. PushMe ## 官方说明文档:https://push.i-i.me/ diff --git a/sample/notify.js b/sample/notify.js index 89803264..c0f3db43 100644 --- a/sample/notify.js +++ b/sample/notify.js @@ -121,7 +121,8 @@ const push_config = { SMTP_SERVICE: '', // 邮箱服务名称,比如 126、163、Gmail、QQ 等,支持列表 https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json SMTP_EMAIL: '', // SMTP 发件邮箱 - SMTP_TO: '', // SMTP 收件邮箱,默认通知将会发给发件邮箱 + SMTP_TO: '', // SMTP 收件邮箱,兼容旧参数名,默认通知将会发给发件邮箱 + SMTP_EMAIL_TO: '', // SMTP 收件邮箱,多个分号分隔,默认发给发件邮箱 SMTP_PASSWORD: '', // SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定 SMTP_NAME: '', // SMTP 收发件人姓名,可随意填写 @@ -1051,8 +1052,14 @@ function fsBotNotify(text, desp) { } async function smtpNotify(text, desp) { - const { SMTP_EMAIL, SMTP_TO, SMTP_PASSWORD, SMTP_SERVICE, SMTP_NAME } = - push_config; + const { + SMTP_EMAIL, + SMTP_TO, + SMTP_EMAIL_TO, + SMTP_PASSWORD, + SMTP_SERVICE, + SMTP_NAME, + } = push_config; if (![SMTP_EMAIL, SMTP_PASSWORD].every(Boolean) || !SMTP_SERVICE) { return; } @@ -1068,9 +1075,20 @@ async function smtpNotify(text, desp) { }); const addr = SMTP_NAME ? `"${SMTP_NAME}" <${SMTP_EMAIL}>` : SMTP_EMAIL; + const recipients = [SMTP_EMAIL_TO, SMTP_TO].reduce((list, value) => { + if (!value) { + return list; + } + return list.concat( + value + .split(/[;;]/) + .map((item) => item.trim()) + .filter(Boolean), + ); + }, []); const info = await transporter.sendMail({ from: addr, - to: SMTP_TO ? SMTP_TO.split(';') : addr, + to: recipients.length ? recipients : SMTP_EMAIL, subject: text, html: `${desp.replace(/\n/g, '
')}`, }); diff --git a/sample/notify.py b/sample/notify.py index 46383498..da8bfdcf 100644 --- a/sample/notify.py +++ b/sample/notify.py @@ -107,7 +107,8 @@ push_config = { 'SMTP_SERVER': '', # SMTP 发送邮件服务器,形如 smtp.exmail.qq.com:465 'SMTP_SSL': 'false', # SMTP 发送邮件服务器是否使用 SSL,填写 true 或 false - 'SMTP_EMAIL': '', # SMTP 收发件邮箱,通知将会由自己发给自己 + 'SMTP_EMAIL': '', # SMTP 发件邮箱 + 'SMTP_EMAIL_TO': '', # SMTP 收件邮箱,多个分号分隔,默认发给发件邮箱 'SMTP_PASSWORD': '', # SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定 'SMTP_NAME': '', # SMTP 收发件人姓名,可随意填写 @@ -694,6 +695,10 @@ def smtp(title: str, content: str) -> None: return print("SMTP 邮件 服务启动") + email_to = push_config.get("SMTP_EMAIL_TO") or push_config.get("SMTP_EMAIL") + email_to_list = [ + item.strip() for item in re.split(r"[;;]", email_to) if item.strip() + ] message = MIMEText(content, "plain", "utf-8") message["From"] = formataddr( ( @@ -701,12 +706,7 @@ def smtp(title: str, content: str) -> None: push_config.get("SMTP_EMAIL"), ) ) - message["To"] = formataddr( - ( - Header(push_config.get("SMTP_NAME"), "utf-8").encode(), - push_config.get("SMTP_EMAIL"), - ) - ) + message["To"] = ",".join(email_to_list) message["Subject"] = Header(title, "utf-8") try: @@ -720,7 +720,7 @@ def smtp(title: str, content: str) -> None: ) smtp_server.sendmail( push_config.get("SMTP_EMAIL"), - push_config.get("SMTP_EMAIL"), + email_to_list, message.as_bytes(), ) smtp_server.close() From 3464c4da61ebe2eba518fa3442aedbfc11d3d36b Mon Sep 17 00:00:00 2001 From: whyour Date: Wed, 6 May 2026 01:29:01 +0800 Subject: [PATCH 11/24] fix IPv6 connectivity --- back/config/index.ts | 4 ++++ back/schedule/client.ts | 2 +- back/services/grpc.ts | 39 ++++++++++++++++++++++++------- back/services/http.ts | 52 ++++++++++++++++++++++++++++------------- 4 files changed, 71 insertions(+), 26 deletions(-) diff --git a/back/config/index.ts b/back/config/index.ts index f7c7a54c..fb1c9ca5 100644 --- a/back/config/index.ts +++ b/back/config/index.ts @@ -9,6 +9,8 @@ dotenv.config({ interface Config { port: number; grpcPort: number; + bindHost: string; + bindHostGrpc: string; nodeEnv: string; isDevelopment: boolean; isProduction: boolean; @@ -31,6 +33,8 @@ interface Config { const config: Config = { port: parseInt(process.env.BACK_PORT || '5700', 10), grpcPort: parseInt(process.env.GRPC_PORT || '5500', 10), + bindHost: process.env.BIND_HOST || '::', + bindHostGrpc: process.env.BIND_HOST_GRPC || '::', nodeEnv: process.env.NODE_ENV || 'development', isDevelopment: process.env.NODE_ENV === 'development', isProduction: process.env.NODE_ENV === 'production', diff --git a/back/schedule/client.ts b/back/schedule/client.ts index 99bf364a..6b086452 100644 --- a/back/schedule/client.ts +++ b/back/schedule/client.ts @@ -10,7 +10,7 @@ import config from '../config'; class Client { private client = new CronClient( - `0.0.0.0:${config.grpcPort}`, + `localhost:${config.grpcPort}`, credentials.createInsecure(), { 'grpc.enable_http_proxy': 0 }, ); diff --git a/back/services/grpc.ts b/back/services/grpc.ts index e8ea2282..5edd9d2c 100644 --- a/back/services/grpc.ts +++ b/back/services/grpc.ts @@ -16,6 +16,13 @@ import { Service } from 'typedi'; export class GrpcServerService { private server: Server = new Server({ 'grpc.enable_http_proxy': 0 }); + private formatGrpcAddress(host: string, port: number): string { + if (host === '::') { + return `[::]:${port}`; + } + return `${host}:${port}`; + } + async initialize() { try { this.server.addService(HealthService, { check }); @@ -23,18 +30,32 @@ export class GrpcServerService { this.server.addService(ApiService, Api); const grpcPort = config.grpcPort; + const hostsToTry = [ + config.bindHostGrpc, + ...(config.bindHostGrpc !== '0.0.0.0' ? ['0.0.0.0'] : []) + ]; const bindAsync = promisify(this.server.bindAsync).bind(this.server); - await bindAsync( - `0.0.0.0:${grpcPort}`, - ServerCredentials.createInsecure(), - ); - Logger.debug(`✌️ gRPC service started successfully`); - metricsService.record('grpc_service_start', 1, { - port: grpcPort.toString(), - }); + let lastError: Error | null = null; - return grpcPort; + for (const host of hostsToTry) { + try { + const address = this.formatGrpcAddress(host, grpcPort); + await bindAsync(address, ServerCredentials.createInsecure()); + Logger.debug(`✌️ gRPC service started successfully on ${address}`); + metricsService.record('grpc_service_start', 1, { + port: grpcPort.toString(), + host + }); + return grpcPort; + } catch (err) { + lastError = err as Error; + Logger.warn(`Failed to bind gRPC on ${host}:${grpcPort}, trying next...`, err); + } + } + + Logger.error('Failed to start gRPC service on all hosts'); + throw lastError || new Error('Failed to start gRPC service'); } catch (err) { Logger.error('Failed to start gRPC service:', err); throw err; diff --git a/back/services/http.ts b/back/services/http.ts index 5391c842..bbce8ff7 100644 --- a/back/services/http.ts +++ b/back/services/http.ts @@ -3,31 +3,51 @@ import Logger from '../loaders/logger'; import { metricsService } from './metrics'; import { Service } from 'typedi'; import { Server } from 'http'; +import config from '../config'; @Service() export class HttpServerService { private server?: Server = undefined; async initialize(expressApp: express.Application, port: number) { - try { - return new Promise((resolve, reject) => { - this.server = expressApp.listen(port, '0.0.0.0', () => { - Logger.debug(`✌️ HTTP service started successfully`); - metricsService.record('http_service_start', 1, { - port: port.toString(), - }); - resolve(this.server); - }); + const hostsToTry = [ + config.bindHost, + ...(config.bindHost !== '0.0.0.0' ? ['0.0.0.0'] : []) + ]; - this.server?.on('error', (err: Error) => { - Logger.error('Failed to start HTTP service:', err); - reject(err); + let lastError: Error | null = null; + + for (const host of hostsToTry) { + try { + const server = await this.tryListen(expressApp, port, host); + Logger.debug(`✌️ HTTP service started successfully on ${host}:${port}`); + metricsService.record('http_service_start', 1, { + port: port.toString(), + host }); - }); - } catch (err) { - Logger.error('Failed to start HTTP service:', err); - throw err; + this.server = server; + return server; + } catch (err) { + lastError = err as Error; + Logger.warn(`Failed to bind HTTP on ${host}:${port}, trying next...`, err); + } } + + Logger.error('Failed to start HTTP service on all hosts'); + throw lastError || new Error('Failed to start HTTP service'); + } + + private async tryListen(expressApp: express.Application, port: number, host: string): Promise { + return new Promise((resolve, reject) => { + const server = expressApp.listen(port, host, () => { + resolve(server); + }); + + server.on('error', (err: Error) => { + server.close(); + reject(err); + }); + }); } async shutdown() { From 40d4de90179a579e15574c4a77fd7b7b66a842fe Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 17 May 2026 23:43:09 +0800 Subject: [PATCH 12/24] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20localhost=20?= =?UTF-8?q?=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 5 +++++ back/schedule/health.ts | 2 +- back/shared/pLimit.ts | 2 +- docker/310.Dockerfile | 2 +- docker/Dockerfile | 2 +- docker/docker-entrypoint.sh | 10 ++++++++++ 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 0832167e..f86264af 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,11 @@ GRPC_PORT=5500 BACK_PORT=5700 +# 服务绑定地址,默认 ::(IPv6 通配,双栈系统同时支持 IPv4/IPv6) +# 纯 IPv4 环境自动 fallback 到 0.0.0.0,也可手动指定 +# BIND_HOST=0.0.0.0 +# BIND_HOST_GRPC=0.0.0.0 + LOG_LEVEL='info' JWT_SECRET= diff --git a/back/schedule/health.ts b/back/schedule/health.ts index f77a914b..dc90f7e6 100644 --- a/back/schedule/health.ts +++ b/back/schedule/health.ts @@ -10,7 +10,7 @@ const check = async ( switch (call.request.service) { case 'cron': const res = await promiseExec( - `curl -s --noproxy '*' http://0.0.0.0:${config.port}/api/system`, + `curl -s --noproxy '*' http://localhost:${config.port}/api/system`, ); if (res.includes('200')) { diff --git a/back/shared/pLimit.ts b/back/shared/pLimit.ts index f84763ad..2f04d5be 100644 --- a/back/shared/pLimit.ts +++ b/back/shared/pLimit.ts @@ -37,7 +37,7 @@ class TaskLimit { concurrency: Math.max(os.cpus().length, 4), }); private client = new ApiClient( - `0.0.0.0:${config.grpcPort}`, + `localhost:${config.grpcPort}`, credentials.createInsecure(), { 'grpc.enable_http_proxy': 0 }, ); diff --git a/docker/310.Dockerfile b/docker/310.Dockerfile index fb690b57..f9cb29b7 100644 --- a/docker/310.Dockerfile +++ b/docker/310.Dockerfile @@ -84,6 +84,6 @@ COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/ WORKDIR ${QL_DIR} HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ - CMD curl -sf --noproxy '*' http://127.0.0.1:${QlPort:-5700}/api/health || exit 1 + CMD curl -sf --noproxy '*' http://localhost:${QlPort:-5700}/api/health || exit 1 ENTRYPOINT ["./docker/docker-entrypoint.sh"] diff --git a/docker/Dockerfile b/docker/Dockerfile index 5682ae9d..6cde05d9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -84,6 +84,6 @@ COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/ WORKDIR ${QL_DIR} HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ - CMD curl -sf --noproxy '*' http://127.0.0.1:${QlPort:-5700}/api/health || exit 1 + CMD curl -sf --noproxy '*' http://localhost:${QlPort:-5700}/api/health || exit 1 ENTRYPOINT ["./docker/docker-entrypoint.sh"] diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 5d784cc3..b0693fe3 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -25,6 +25,16 @@ if [ -f /etc/alpine-release ]; then fi fi +# 确保 /etc/hosts 包含 localhost 解析(应对精简镜像或仅 IPv4/IPv6 环境) +if ! grep -qE '^127\.0\.0\.1[[:space:]]+.*localhost' /etc/hosts 2>/dev/null; then + echo "127.0.0.1 localhost" >> /etc/hosts + log_with_style "INFO" "🔧 0. 已添加 IPv4 localhost 解析" +fi +if ! grep -qE '^::1[[:space:]]+.*localhost' /etc/hosts 2>/dev/null; then + echo "::1 localhost ip6-localhost ip6-loopback" >> /etc/hosts + log_with_style "INFO" "🔧 0. 已添加 IPv6 localhost 解析" +fi + log_with_style "INFO" "🚀 1. 检测配置文件..." load_ql_envs export_ql_envs From 400e4770dea3999f44d5f987c94e6aac1ebad973 Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 17 May 2026 23:53:43 +0800 Subject: [PATCH 13/24] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=8F=98=E9=87=8F=20position=20=E6=95=B0=E6=8D=AE=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=8F=AF=E8=83=BD=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/services/env.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/back/services/env.ts b/back/services/env.ts index cd4d0a84..ed74ab5f 100644 --- a/back/services/env.ts +++ b/back/services/env.ts @@ -27,7 +27,7 @@ export default class EnvService { envs.length > 0 && typeof envs[envs.length - 1].position === 'number' ) { - position = envs[envs.length - 1].position!; + position = this.getPrecisionPosition(envs[envs.length - 1].position!); } const tabs = payloads.map((x) => { position = position - stepPosition; @@ -100,7 +100,7 @@ export default class EnvService { } private async checkPosition(position: number, edge: number = 0) { - const precisionPosition = parseFloat(position.toPrecision(16)); + const precisionPosition = this.getPrecisionPosition(position); if ( precisionPosition < minPosition || precisionPosition > maxPosition || @@ -116,7 +116,7 @@ export default class EnvService { } private getPrecisionPosition(position: number): number { - return parseFloat(position.toPrecision(16)); + return Math.trunc(parseFloat(position.toPrecision(16))); } public async envs(searchText: string = '', query: any = {}): Promise { From 0c5e3b5d04d3d3296048eb50a5098155296308d6 Mon Sep 17 00:00:00 2001 From: whyour Date: Mon, 18 May 2026 00:12:22 +0800 Subject: [PATCH 14/24] ci: update workflow actions --- .github/workflows/build-docker-image.yml | 41 ++++++++++++------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 63c42609..10a59c5e 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -46,13 +46,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v6 with: version: "8.3.1" - uses: actions/setup-node@v6 with: cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml - name: build front and back run: | @@ -121,12 +122,13 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v6 with: version: "8.3.1" - uses: actions/setup-node@v6 with: cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml - name: Read version from version.yaml id: version @@ -136,18 +138,17 @@ jobs: echo "Version: $VERSION" - name: Setup timezone - uses: szenius/set-timezone@v2.0 - with: - timezoneLinux: Asia/Shanghai + run: | + sudo timedatectl set-timezone Asia/Shanghai - name: Login to DockerHub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -155,7 +156,7 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | ${{ github.repository }} @@ -169,14 +170,14 @@ jobs: type=semver,pattern={{version}} - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and push id: docker_build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: build-args: | MAINTAINER=${{ github.repository_owner }} @@ -209,12 +210,13 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v6 with: version: "8.3.1" - uses: actions/setup-node@v6 with: cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml - name: Read version from version.yaml id: version @@ -224,32 +226,31 @@ jobs: echo "Version: $VERSION" - name: Setup timezone - uses: szenius/set-timezone@v2.0 - with: - timezoneLinux: Asia/Shanghai + run: | + sudo timedatectl set-timezone Asia/Shanghai - name: Login to DockerHub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and push python3.10 id: docker_build_310 - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: build-args: | MAINTAINER=${{ github.repository_owner }} From 66f9457be82b7ae8117fd924ff450a27c8fa1629 Mon Sep 17 00:00:00 2001 From: whyour Date: Mon, 18 May 2026 01:05:46 +0800 Subject: [PATCH 15/24] ci: replace hub-mirror-action with direct git push for Gitee sync Replace Yikun/hub-mirror-action with manual git push for Gitee mirrors. The action HTTPS API call to gitee.com timed out (60s). Use set +e with explicit notice/warning status. Also add .deepseek/ to .gitignore. --- .github/workflows/build-docker-image.yml | 63 +++++++++++++++++------- .gitignore | 2 + 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 10a59c5e..6b399d28 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -33,14 +33,38 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 - - uses: Yikun/hub-mirror-action@master - with: - src: github/whyour - dst: gitee/whyour - dst_key: ${{ secrets.GITLAB_SSH_PK }} - dst_token: ${{ secrets.GITEE_TOKEN }} - static_list: "qinglong" - force_update: true + - name: Setup SSH and push to Gitee + env: + GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }} + run: | + set +e + mkdir -p ~/.ssh + printf '%s\n' "${{ secrets.GITLAB_SSH_PK }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -T 10 gitee.com >> ~/.ssh/known_hosts 2>/dev/null + git remote add gitee git@gitee.com:whyour/qinglong.git 2>/dev/null + + if git push --force --all gitee 2>&1; then + echo "::notice::Gitee push --all succeeded" + else + echo "::warning::Push --all failed, trying to create repo via API..." + curl -sS --connect-timeout 30 --max-time 60 \ + -X POST "https://gitee.com/api/v5/user/repos" \ + -H "Content-Type: application/json" \ + -d '{"name":"qinglong","private":"false"}' \ + "?access_token=$GITEE_TOKEN" 2>/dev/null + if git push --force --all gitee 2>&1; then + echo "::notice::Gitee push --all succeeded after repo creation" + else + echo "::warning::Gitee push --all failed after retry" + fi + fi + + if git push --force --tags gitee 2>&1; then + echo "::notice::Gitee push --tags succeeded" + else + echo "::warning::Gitee push --tags failed" + fi build-static: runs-on: ubuntu-latest @@ -98,17 +122,18 @@ jobs: needs: build-static runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - uses: Yikun/hub-mirror-action@master - with: - src: github/whyour - dst: gitee/whyour - dst_key: ${{ secrets.GITLAB_SSH_PK }} - dst_token: ${{ secrets.GITEE_TOKEN }} - static_list: "qinglong-static" - force_update: true + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.GITLAB_SSH_PK }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan github.com gitee.com >> ~/.ssh/known_hosts + - name: Mirror qinglong-static to Gitee + run: | + git clone --mirror https://github.com/whyour/qinglong-static.git static-mirror + cd static-mirror + git remote set-url origin git@gitee.com:whyour/qinglong-static.git + git push --force --mirror build: if: ${{ !startsWith(github.ref, 'refs/tags/') }} diff --git a/.gitignore b/.gitignore index 709791ed..c9fc9200 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ __pycache__ /shell/preload/notify.* /shell/preload/*-notify.json /shell/preload/__ql_notify__.* + +.deepseek/ From 7a8917f8e40c4365a2381213fe627124d3cda009 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 20:48:10 +0800 Subject: [PATCH 16/24] Fix CodeMirror "multiple instances of @codemirror/state" crash on page navigation (#2969) * Initial plan * Fix CodeMirror multiple instances error by pinning to single versions Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- package.json | 8 +- pnpm-lock.yaml | 251 ++++++++++++++++++++++--------------------------- 2 files changed, 120 insertions(+), 139 deletions(-) diff --git a/package.json b/package.json index 1494538a..eb0b5af4 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,9 @@ } }, "overrides": { - "sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3" + "sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3", + "@codemirror/state": "6.5.4", + "@codemirror/view": "6.39.16" } }, "dependencies": { @@ -104,8 +106,8 @@ "moment": "2.30.1", "@ant-design/icons": "^5.0.1", "@ant-design/pro-layout": "6.38.22", - "@codemirror/view": "^6.34.1", - "@codemirror/state": "^6.4.1", + "@codemirror/view": "6.39.16", + "@codemirror/state": "6.5.4", "@monaco-editor/react": "4.2.1", "@react-hook/resize-observer": "^2.0.2", "react-router-dom": "6.26.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03414b75..b26696ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,6 +2,8 @@ lockfileVersion: '6.0' overrides: sqlite3: git+https://github.com/whyour/node-sqlite3.git#v1.0.3 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 dependencies: '@bufbuild/protobuf': @@ -145,11 +147,11 @@ devDependencies: specifier: 6.38.22 version: 6.38.22(prop-types@15.8.1)(react-dom@18.3.1)(react@18.3.1) '@codemirror/state': - specifier: ^6.4.1 - version: 6.4.1 + specifier: 6.5.4 + version: 6.5.4 '@codemirror/view': - specifier: ^6.34.1 - version: 6.35.0 + specifier: 6.39.16 + version: 6.39.16 '@monaco-editor/react': specifier: 4.2.1 version: 4.2.1(monaco-editor@0.33.0)(react-dom@18.3.1)(react@18.3.1) @@ -236,10 +238,10 @@ devDependencies: version: 8.3.4 '@uiw/codemirror-extensions-langs': specifier: ^4.21.9 - version: 4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/language-data@6.5.2)(@codemirror/language@6.12.2)(@codemirror/legacy-modes@6.5.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8) + version: 4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/language-data@6.5.2)(@codemirror/language@6.12.2)(@codemirror/legacy-modes@6.5.2)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8) '@uiw/react-codemirror': specifier: ^4.21.9 - version: 4.23.6(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.35.0)(codemirror@6.0.2)(react-dom@18.3.1)(react@18.3.1) + version: 4.23.6(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.39.16)(codemirror@6.0.2)(react-dom@18.3.1)(react@18.3.1) '@umijs/max': specifier: ^4.4.4 version: 4.4.4(@types/node@17.0.45)(@types/react-dom@18.3.1)(@types/react@18.3.12)(prettier@2.8.8)(react-dom@18.3.1)(react@18.3.1)(sockjs-client@1.6.1)(typescript@5.2.2) @@ -1403,7 +1405,7 @@ packages: tinycolor2: 1.6.0 dev: true - /@codemirror/autocomplete@6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3): + /@codemirror/autocomplete@6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.2.3): resolution: {integrity: sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==} peerDependencies: '@codemirror/language': ^6.0.0 @@ -1412,8 +1414,8 @@ packages: '@lezer/common': ^1.0.0 dependencies: '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.2.3 dev: true @@ -1421,8 +1423,8 @@ packages: resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==} dependencies: '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 dev: true @@ -1430,8 +1432,8 @@ packages: resolution: {integrity: sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==} dependencies: '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 dev: true @@ -1439,8 +1441,8 @@ packages: resolution: {integrity: sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==} dependencies: '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.2.3 dev: true @@ -1480,12 +1482,12 @@ packages: '@lezer/cpp': 1.1.5 dev: true - /@codemirror/lang-css@6.3.1(@codemirror/view@6.35.0): + /@codemirror/lang-css@6.3.1(@codemirror/view@6.39.16): resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) + '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.2.3) '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.2.3 '@lezer/css': 1.1.9 transitivePeerDependencies: @@ -1497,7 +1499,7 @@ packages: dependencies: '@codemirror/autocomplete': 6.20.1 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.5.1 '@lezer/go': 1.0.1 dev: true @@ -1506,11 +1508,11 @@ packages: resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==} dependencies: '@codemirror/autocomplete': 6.20.1 - '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) + '@codemirror/lang-css': 6.3.1(@codemirror/view@6.39.16) '@codemirror/lang-javascript': 6.2.5 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 '@lezer/css': 1.3.1 '@lezer/html': 1.3.13 @@ -1519,12 +1521,12 @@ packages: /@codemirror/lang-html@6.4.9: resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) - '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) + '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.2.3) + '@codemirror/lang-css': 6.3.1(@codemirror/view@6.39.16) '@codemirror/lang-javascript': 6.2.2 '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.2.3 '@lezer/css': 1.1.9 '@lezer/html': 1.3.10 @@ -1547,11 +1549,11 @@ packages: /@codemirror/lang-javascript@6.2.2: resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) + '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.2.3) '@codemirror/language': 6.10.6 '@codemirror/lint': 6.8.4 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.2.3 '@lezer/javascript': 1.4.21 dev: true @@ -1562,8 +1564,8 @@ packages: '@codemirror/autocomplete': 6.20.1 '@codemirror/language': 6.12.2 '@codemirror/lint': 6.9.5 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 '@lezer/javascript': 1.5.4 dev: true @@ -1592,10 +1594,10 @@ packages: '@lezer/json': 1.0.3 dev: true - /@codemirror/lang-less@6.0.2(@codemirror/view@6.35.0): + /@codemirror/lang-less@6.0.2(@codemirror/view@6.39.16): resolution: {integrity: sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==} dependencies: - '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) + '@codemirror/lang-css': 6.3.1(@codemirror/view@6.39.16) '@codemirror/language': 6.10.6 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 @@ -1608,7 +1610,7 @@ packages: resolution: {integrity: sha512-WHwjI7OqKFBEfkunohweqA5B/jIlxaZso6Nl3weVckz8EafYbPZldQEKSDb4QQ9H9BUkle4PVELP4sftKoA0uQ==} dependencies: '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.2.3 '@lezer/lezer': 1.1.2 dev: true @@ -1616,11 +1618,11 @@ packages: /@codemirror/lang-liquid@6.2.2: resolution: {integrity: sha512-7Dm841fk37+JQW6j2rI1/uGkJyESrjzyhiIkaLjbbR0U6aFFQvMrJn35WxQreRMADMhzkyVkZM4467OR7GR8nQ==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) + '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.2.3) '@codemirror/lang-html': 6.4.9 '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 @@ -1632,8 +1634,8 @@ packages: '@codemirror/autocomplete': 6.20.1 '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.8 @@ -1642,11 +1644,11 @@ packages: /@codemirror/lang-markdown@6.3.1: resolution: {integrity: sha512-y3sSPuQjBKZQbQwe3ZJKrSW6Silyl9PnrU/Mf0m2OQgIlPoSYTtOvEL7xs94SVMkb8f4x+SQFnzXPdX4Wk2lsg==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) + '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.2.3) '@codemirror/lang-html': 6.4.9 '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.2.3 '@lezer/markdown': 1.3.2 dev: true @@ -1657,8 +1659,8 @@ packages: '@codemirror/autocomplete': 6.20.1 '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 '@lezer/markdown': 1.6.3 dev: true @@ -1668,7 +1670,7 @@ packages: dependencies: '@codemirror/lang-html': 6.4.9 '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.2.3 '@lezer/php': 1.0.2 dev: true @@ -1678,17 +1680,17 @@ packages: dependencies: '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.5.1 '@lezer/php': 1.0.5 dev: true - /@codemirror/lang-python@6.1.6(@codemirror/view@6.35.0): + /@codemirror/lang-python@6.1.6(@codemirror/view@6.39.16): resolution: {integrity: sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) + '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.2.3) '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.2.3 '@lezer/python': 1.1.14 transitivePeerDependencies: @@ -1700,7 +1702,7 @@ packages: dependencies: '@codemirror/autocomplete': 6.20.1 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.5.1 '@lezer/python': 1.1.18 dev: true @@ -1719,12 +1721,12 @@ packages: '@lezer/rust': 1.0.2 dev: true - /@codemirror/lang-sass@6.0.2(@codemirror/view@6.35.0): + /@codemirror/lang-sass@6.0.2(@codemirror/view@6.39.16): resolution: {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==} dependencies: - '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) + '@codemirror/lang-css': 6.3.1(@codemirror/view@6.39.16) '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.2.3 '@lezer/sass': 1.0.7 transitivePeerDependencies: @@ -1736,18 +1738,18 @@ packages: dependencies: '@codemirror/autocomplete': 6.20.1 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.5.1 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.8 dev: true - /@codemirror/lang-sql@6.8.0(@codemirror/view@6.35.0): + /@codemirror/lang-sql@6.8.0(@codemirror/view@6.39.16): resolution: {integrity: sha512-aGLmY4OwGqN3TdSx3h6QeA1NrvaYtF7kkoWR/+W7/JzB0gQtJ+VJxewlnE3+VImhA4WVlhmkJr109PefOOhjLg==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) + '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.2.3) '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 @@ -1778,10 +1780,10 @@ packages: /@codemirror/lang-xml@6.1.0: resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==} dependencies: - '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3) + '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.2.3) '@codemirror/language': 6.10.6 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.2.3 '@lezer/xml': 1.0.5 dev: true @@ -1791,32 +1793,32 @@ packages: dependencies: '@codemirror/autocomplete': 6.20.1 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@lezer/common': 1.5.1 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.8 '@lezer/yaml': 1.0.4 dev: true - /@codemirror/language-data@6.5.2(@codemirror/view@6.35.0): + /@codemirror/language-data@6.5.2(@codemirror/view@6.39.16): resolution: {integrity: sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==} dependencies: '@codemirror/lang-angular': 0.1.4 '@codemirror/lang-cpp': 6.0.3 - '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) + '@codemirror/lang-css': 6.3.1(@codemirror/view@6.39.16) '@codemirror/lang-go': 6.0.1 '@codemirror/lang-html': 6.4.11 '@codemirror/lang-java': 6.0.2 '@codemirror/lang-javascript': 6.2.5 '@codemirror/lang-jinja': 6.0.0 '@codemirror/lang-json': 6.0.2 - '@codemirror/lang-less': 6.0.2(@codemirror/view@6.35.0) + '@codemirror/lang-less': 6.0.2(@codemirror/view@6.39.16) '@codemirror/lang-liquid': 6.3.2 '@codemirror/lang-markdown': 6.5.0 '@codemirror/lang-php': 6.0.2 '@codemirror/lang-python': 6.2.1 '@codemirror/lang-rust': 6.0.2 - '@codemirror/lang-sass': 6.0.2(@codemirror/view@6.35.0) + '@codemirror/lang-sass': 6.0.2(@codemirror/view@6.39.16) '@codemirror/lang-sql': 6.10.0 '@codemirror/lang-vue': 0.1.3 '@codemirror/lang-wast': 6.0.2 @@ -1831,19 +1833,19 @@ packages: /@codemirror/language@6.10.6: resolution: {integrity: sha512-KrsbdCnxEztLVbB5PycWXFxas4EOyk/fPAfruSOnDDppevQgid2XZ+KbJ9u+fDikP/e7MW7HPBTvTb8JlZK9vA==} dependencies: - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 - style-mod: 4.1.2 + style-mod: 4.1.3 dev: true /@codemirror/language@6.12.2: resolution: {integrity: sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==} dependencies: - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.8 @@ -1859,31 +1861,27 @@ packages: /@codemirror/lint@6.8.4: resolution: {integrity: sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==} dependencies: - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 crelt: 1.0.6 dev: true /@codemirror/lint@6.9.5: resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==} dependencies: - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 crelt: 1.0.6 dev: true /@codemirror/search@6.6.0: resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==} dependencies: - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@codemirror/view': 6.39.16 crelt: 1.0.6 dev: true - /@codemirror/state@6.4.1: - resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==} - dev: true - /@codemirror/state@6.5.4: resolution: {integrity: sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==} dependencies: @@ -1894,19 +1892,11 @@ packages: resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} dependencies: '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/highlight': 1.2.3 dev: true - /@codemirror/view@6.35.0: - resolution: {integrity: sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==} - dependencies: - '@codemirror/state': 6.4.1 - style-mod: 4.1.2 - w3c-keyname: 2.2.8 - dev: true - /@codemirror/view@6.39.16: resolution: {integrity: sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==} dependencies: @@ -2668,13 +2658,13 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.4.3 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 - minimatch: 3.1.2 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color @@ -2799,8 +2789,8 @@ packages: deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7(supports-color@5.5.0) - minimatch: 3.1.2 + debug: 4.4.3 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color dev: true @@ -3671,7 +3661,7 @@ packages: engines: {node: '>=14.0.0'} dev: true - /@replit/codemirror-lang-csharp@6.2.0(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8): + /@replit/codemirror-lang-csharp@6.2.0(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8): resolution: {integrity: sha512-6utbaWkoymhoAXj051mkRp+VIJlpwUgCX9Toevz3YatiZsz512fw3OVCedXQx+WcR0wb6zVHjChnuxqfCLtFVQ==} peerDependencies: '@codemirror/autocomplete': ^6.0.0 @@ -3684,14 +3674,14 @@ packages: dependencies: '@codemirror/autocomplete': 6.20.1 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.8 dev: true - /@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8): + /@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8): resolution: {integrity: sha512-lvzjoYn9nfJzBD5qdm3Ut6G3+Or2wEacYIDJ49h9+19WSChVnxv4ojf+rNmQ78ncuxIt/bfbMvDLMeMP0xze6g==} peerDependencies: '@codemirror/autocomplete': ^6.0.0 @@ -3704,8 +3694,8 @@ packages: dependencies: '@codemirror/autocomplete': 6.20.1 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.8 @@ -3720,7 +3710,7 @@ packages: '@lezer/highlight': 1.2.1 dev: true - /@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.20.1)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.2)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8): + /@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.20.1)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.2)(@codemirror/language@6.12.2)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8): resolution: {integrity: sha512-U2OqqgMM6jKelL0GNWbAmqlu1S078zZNoBqlJBW+retTc5M4Mha6/Y2cf4SVg6ddgloJvmcSpt4hHrVoM4ePRA==} peerDependencies: '@codemirror/autocomplete': ^6.0.0 @@ -3736,12 +3726,12 @@ packages: '@lezer/lr': ^1.0.0 dependencies: '@codemirror/autocomplete': 6.20.1 - '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) + '@codemirror/lang-css': 6.3.1(@codemirror/view@6.39.16) '@codemirror/lang-html': 6.4.9 '@codemirror/lang-javascript': 6.2.2 '@codemirror/language': 6.12.2 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 '@lezer/common': 1.5.1 '@lezer/highlight': 1.2.3 '@lezer/javascript': 1.5.4 @@ -4541,7 +4531,7 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@uiw/codemirror-extensions-basic-setup@4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.7.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0): + /@uiw/codemirror-extensions-basic-setup@4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.7.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16): resolution: {integrity: sha512-bvtq8IOvdkLJMhoJBRGPEzU51fMpPDwEhcAHp9xCR05MtbIokQgsnLXrmD1aZm6e7s/3q47H+qdSfAAkR5MkLA==} peerDependencies: '@codemirror/autocomplete': '>=6.0.0' @@ -4557,11 +4547,11 @@ packages: '@codemirror/language': 6.12.2 '@codemirror/lint': 6.9.5 '@codemirror/search': 6.6.0 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 dev: true - /@uiw/codemirror-extensions-langs@4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/language-data@6.5.2)(@codemirror/language@6.12.2)(@codemirror/legacy-modes@6.5.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8): + /@uiw/codemirror-extensions-langs@4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/language-data@6.5.2)(@codemirror/language@6.12.2)(@codemirror/legacy-modes@6.5.2)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8): resolution: {integrity: sha512-VKWbEXmVq3EFYrJPWXH4Ei1f92zxuAg6dOlo8suSmwjmEc0qjNEP5Ss2CUi9LlzuWMGMmZgdKw56I3L71wYOog==} peerDependencies: '@codemirror/language-data': '>=6.0.0' @@ -4569,30 +4559,30 @@ packages: dependencies: '@codemirror/lang-angular': 0.1.3 '@codemirror/lang-cpp': 6.0.2 - '@codemirror/lang-css': 6.3.1(@codemirror/view@6.35.0) + '@codemirror/lang-css': 6.3.1(@codemirror/view@6.39.16) '@codemirror/lang-html': 6.4.9 '@codemirror/lang-java': 6.0.1 '@codemirror/lang-javascript': 6.2.2 '@codemirror/lang-json': 6.0.1 - '@codemirror/lang-less': 6.0.2(@codemirror/view@6.35.0) + '@codemirror/lang-less': 6.0.2(@codemirror/view@6.39.16) '@codemirror/lang-lezer': 6.0.1 '@codemirror/lang-liquid': 6.2.2 '@codemirror/lang-markdown': 6.3.1 '@codemirror/lang-php': 6.0.1 - '@codemirror/lang-python': 6.1.6(@codemirror/view@6.35.0) + '@codemirror/lang-python': 6.1.6(@codemirror/view@6.39.16) '@codemirror/lang-rust': 6.0.1 - '@codemirror/lang-sass': 6.0.2(@codemirror/view@6.35.0) - '@codemirror/lang-sql': 6.8.0(@codemirror/view@6.35.0) + '@codemirror/lang-sass': 6.0.2(@codemirror/view@6.39.16) + '@codemirror/lang-sql': 6.8.0(@codemirror/view@6.39.16) '@codemirror/lang-vue': 0.1.3 '@codemirror/lang-wast': 6.0.2 '@codemirror/lang-xml': 6.1.0 - '@codemirror/language-data': 6.5.2(@codemirror/view@6.35.0) + '@codemirror/language-data': 6.5.2(@codemirror/view@6.39.16) '@codemirror/legacy-modes': 6.5.2 '@nextjournal/lang-clojure': 1.0.0 - '@replit/codemirror-lang-csharp': 6.2.0(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8) - '@replit/codemirror-lang-nix': 6.0.1(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8) + '@replit/codemirror-lang-csharp': 6.2.0(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8) + '@replit/codemirror-lang-nix': 6.0.1(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8) '@replit/codemirror-lang-solidity': 6.0.2(@codemirror/language@6.12.2) - '@replit/codemirror-lang-svelte': 6.0.0(@codemirror/autocomplete@6.20.1)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.2)(@codemirror/language@6.12.2)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8) + '@replit/codemirror-lang-svelte': 6.0.0(@codemirror/autocomplete@6.20.1)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.2)(@codemirror/language@6.12.2)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8) codemirror-lang-mermaid: 0.5.0 transitivePeerDependencies: - '@codemirror/autocomplete' @@ -4605,7 +4595,7 @@ packages: - '@lezer/lr' dev: true - /@uiw/react-codemirror@4.23.6(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.35.0)(codemirror@6.0.2)(react-dom@18.3.1)(react@18.3.1): + /@uiw/react-codemirror@4.23.6(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.39.16)(codemirror@6.0.2)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-caYKGV6TfGLRV1HHD3p0G3FiVzKL1go7wes5XT2nWjB0+dTdyzyb81MKRSacptgZcotujfNO6QXn65uhETRAMw==} peerDependencies: '@babel/runtime': '>=7.11.0' @@ -4623,10 +4613,10 @@ packages: dependencies: '@babel/runtime': 7.28.6 '@codemirror/commands': 6.7.1 - '@codemirror/state': 6.4.1 + '@codemirror/state': 6.5.4 '@codemirror/theme-one-dark': 6.1.3 - '@codemirror/view': 6.35.0 - '@uiw/codemirror-extensions-basic-setup': 4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.7.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0) + '@codemirror/view': 6.39.16 + '@uiw/codemirror-extensions-basic-setup': 4.23.6(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.7.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16) codemirror: 6.0.2 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -6093,13 +6083,6 @@ packages: big-integer: 1.6.52 dev: true - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - /brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} dependencies: @@ -6497,8 +6480,8 @@ packages: '@codemirror/language': 6.12.2 '@codemirror/lint': 6.9.5 '@codemirror/search': 6.6.0 - '@codemirror/state': 6.4.1 - '@codemirror/view': 6.35.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.16 dev: true /color-convert@1.9.3: @@ -7832,7 +7815,7 @@ packages: eslint: 8.35.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 + minimatch: 3.1.5 object.entries: 1.1.8 object.fromentries: 2.0.8 object.hasown: 1.1.4 @@ -10309,7 +10292,7 @@ packages: /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 dev: true /minimatch@3.1.5: @@ -13792,7 +13775,7 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} dependencies: - semver: 7.6.3 + semver: 7.7.4 dev: true /single-spa@5.9.5: @@ -14208,10 +14191,6 @@ packages: engines: {node: '>=8'} dev: true - /style-mod@4.1.2: - resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} - dev: true - /style-mod@4.1.3: resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} dev: true From 8bc09069499f9b098f4480291fb33b9a343aece0 Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 23 May 2026 23:21:38 +0800 Subject: [PATCH 17/24] feat: add environment variable labels --- back/api/env.ts | 45 +++ back/data/env.ts | 3 + back/loaders/db.ts | 1 + back/services/env.ts | 38 +++ docs/PROJECT_ARCHITECTURE.md | 550 +++++++++++++++++++++++++++++++++++ src/components/tag.tsx | 4 +- src/locales/en-US.json | 3 + src/locales/zh-CN.json | 3 + src/pages/env/index.tsx | 53 +++- src/pages/env/modal.tsx | 101 ++++++- 10 files changed, 792 insertions(+), 9 deletions(-) create mode 100644 docs/PROJECT_ARCHITECTURE.md diff --git a/back/api/env.ts b/back/api/env.ts index 834bdd51..e58cd3ae 100644 --- a/back/api/env.ts +++ b/back/api/env.ts @@ -18,6 +18,10 @@ const storage = multer.diskStorage({ }, }); const upload = multer({ storage: storage }); +const labelSchema = Joi.array() + .items(Joi.string().trim().required()) + .min(1) + .required(); export default (app: Router) => { app.use('/envs', route); @@ -44,6 +48,7 @@ export default (app: Router) => { .required() .pattern(/^[a-zA-Z_][0-9a-zA-Z_]*$/), remarks: Joi.string().optional().allow(''), + labels: Joi.array().items(Joi.string().trim()).optional(), }), ), }), @@ -70,6 +75,7 @@ export default (app: Router) => { name: Joi.string().required(), remarks: Joi.string().optional().allow('').allow(null), id: Joi.number().required(), + labels: Joi.array().items(Joi.string().trim()).optional(), }), }), async (req: Request, res: Response, next: NextFunction) => { @@ -230,6 +236,44 @@ export default (app: Router) => { }, ); + route.post( + '/labels', + celebrate({ + body: Joi.object({ + ids: Joi.array().items(Joi.number().required()).min(1).required(), + labels: labelSchema, + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + try { + const envService = Container.get(EnvService); + const data = await envService.addLabels(req.body.ids, req.body.labels); + return res.send({ code: 200, data }); + } catch (e) { + return next(e); + } + }, + ); + + route.delete( + '/labels', + celebrate({ + body: Joi.object({ + ids: Joi.array().items(Joi.number().required()).min(1).required(), + labels: labelSchema, + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + try { + const envService = Container.get(EnvService); + const data = await envService.removeLabels(req.body.ids, req.body.labels); + return res.send({ code: 200, data }); + } catch (e) { + return next(e); + } + }, + ); + route.post( '/upload', upload.single('env'), @@ -248,6 +292,7 @@ export default (app: Router) => { name: x.name, value: x.value, remarks: x.remarks, + labels: x.labels, })), ); return res.send({ code: 200, data: result }); diff --git a/back/data/env.ts b/back/data/env.ts index bd24f22a..ea238a95 100644 --- a/back/data/env.ts +++ b/back/data/env.ts @@ -10,6 +10,7 @@ export class Env { name?: string; remarks?: string; isPinned?: 1 | 0; + labels?: string[]; constructor(options: Env) { this.value = options.value; @@ -23,6 +24,7 @@ export class Env { this.name = options.name; this.remarks = options.remarks || ''; this.isPinned = options.isPinned || 0; + this.labels = options.labels || []; } } @@ -45,4 +47,5 @@ export const EnvModel = sequelize.define('Env', { name: { type: DataTypes.STRING, unique: 'compositeIndex' }, remarks: DataTypes.STRING, isPinned: DataTypes.NUMBER, + labels: DataTypes.JSON, }); diff --git a/back/loaders/db.ts b/back/loaders/db.ts index 49fefb6b..987c6cbe 100644 --- a/back/loaders/db.ts +++ b/back/loaders/db.ts @@ -40,6 +40,7 @@ export default async () => { type: 'NUMBER', }, { table: 'Envs', column: 'isPinned', type: 'NUMBER' }, + { table: 'Envs', column: 'labels', type: 'JSON' }, ]; for (const migration of migrations) { diff --git a/back/services/env.ts b/back/services/env.ts index ed74ab5f..5efd148b 100644 --- a/back/services/env.ts +++ b/back/services/env.ts @@ -199,6 +199,44 @@ export default class EnvService { await EnvModel.update({ isPinned: 0 }, { where: { id: ids } }); } + public async addLabels(ids: number[], labels: string[]) { + await sequelize.transaction(async (transaction) => { + const docs = await EnvModel.findAll({ + where: { id: ids }, + transaction, + }); + for (const doc of docs) { + const env = doc.get({ plain: true }); + await EnvModel.update( + { labels: Array.from(new Set([...(env.labels || []), ...labels])) }, + { where: { id: env.id }, transaction }, + ); + } + }); + return await this.find({ id: ids }); + } + + public async removeLabels(ids: number[], labels: string[]) { + await sequelize.transaction(async (transaction) => { + const docs = await EnvModel.findAll({ + where: { id: ids }, + transaction, + }); + for (const doc of docs) { + const env = doc.get({ plain: true }); + await EnvModel.update( + { + labels: (env.labels || []).filter( + (label: string) => !labels.includes(label), + ), + }, + { where: { id: env.id }, transaction }, + ); + } + }); + return await this.find({ id: ids }); + } + public async set_envs() { const envs = await this.envs('', { name: { [Op.not]: null }, diff --git a/docs/PROJECT_ARCHITECTURE.md b/docs/PROJECT_ARCHITECTURE.md new file mode 100644 index 00000000..8ea6ffac --- /dev/null +++ b/docs/PROJECT_ARCHITECTURE.md @@ -0,0 +1,550 @@ +# Project Architecture Guide + +This document is written for AI coding agents and maintainers who need to understand and modify this project safely. It focuses on where behavior lives, how the application starts, and which files are usually involved for common changes. + +## Project Summary + +Qinglong is a timed task management platform. It provides a web admin panel for managing cron jobs, scripts, environment variables, subscriptions, dependencies, logs, configuration files, and system settings. + +The repository is organized as a full-stack TypeScript application: + +- `src/`: frontend admin panel, built with Umi Max, React, Ant Design, and Ant Design Pro Layout. +- `back/`: backend application, built with Express, TypeScript, typedi, Sequelize, SQLite, gRPC, and worker processes. +- `shell/`: runtime shell scripts used to execute tasks and preload task environments. +- `data/`: local runtime data, including scripts, logs, configs, SQLite database, uploaded files, and cloned repositories. +- `static/`: built frontend and backend artifacts. +- `docker/`: Docker images, compose file, and entrypoint. +- `sample/`: sample scripts and default config templates. + +## High-Level Runtime Flow + +```text +Browser + -> src/pages/* + -> src/utils/http.tsx + -> /api/* + -> back/api/* + -> back/services/* + -> back/data/* Sequelize models + -> data/db/database.sqlite + +Cron/task execution + -> back/services/cron.ts + -> shell/task.sh or shell/otask.sh + -> data/scripts/* + -> data/log/* + +Frontend assets in production + -> static/dist/* + -> served by back/loaders/express.ts +``` + +## Main Startup Path + +Development starts from `package.json`: + +```bash +pnpm start +``` + +This runs: + +- `start:back`: `nodemon ./back/app.ts` +- `start:front`: `max dev` + +Backend startup begins in `back/app.ts`. + +Important details: + +- The backend uses Node `cluster`. +- The primary process initializes the database first. +- A gRPC worker starts before the HTTP worker. +- The HTTP worker starts Express and serves API routes plus frontend static files. +- If the gRPC worker restarts, the HTTP worker is asked to re-register cron jobs. + +Production-style backend output is generated by: + +```bash +pnpm run build:back +``` + +The compiled backend is placed under `static/build`. + +Frontend output is generated by: + +```bash +pnpm run build:front +``` + +The compiled frontend is placed under `static/dist`. + +## Backend Architecture + +### Entry Point + +- `back/app.ts` + +Responsibilities: + +- Creates the Express application. +- Starts primary/worker process logic. +- Initializes database in the primary process. +- Starts gRPC and HTTP workers. +- Handles graceful shutdown. +- Re-registers cron jobs after gRPC worker recovery. + +### Loaders + +- `back/loaders/app.ts` +- `back/loaders/express.ts` +- `back/loaders/db.ts` +- `back/loaders/depInjector.ts` +- `back/loaders/initData.ts` +- `back/loaders/initFile.ts` +- `back/loaders/initTask.ts` +- `back/loaders/server.ts` +- `back/loaders/sock.ts` + +Loader responsibilities: + +- Register dependency injection bindings. +- Sync Sequelize models. +- Initialize files and default data. +- Initialize scheduled tasks. +- Configure Express middleware. +- Register routes. +- Attach socket/server behavior. + +`back/loaders/express.ts` is the main HTTP middleware and routing setup. It handles: + +- CORS. +- Helmet. +- body parser. +- static frontend serving. +- JWT validation. +- token validation against shared auth state. +- `/open/*` rewrite to `/api/*`. +- route mounting through `back/api/index.ts`. +- frontend fallback to `static/dist/index.html`. +- API error handling. + +### API Routes + +- `back/api/index.ts` + +This file registers all API modules: + +- `user.ts`: login, initialization, authentication-related user endpoints. +- `env.ts`: environment variable endpoints. +- `config.ts`: config file endpoints. +- `log.ts`: log endpoints. +- `cron.ts`: cron/task endpoints. +- `script.ts`: script file endpoints. +- `open.ts`: open API/app token endpoints. +- `dependence.ts`: dependency management endpoints. +- `system.ts`: system information/settings endpoints. +- `subscription.ts`: subscription endpoints. +- `update.ts`: update/check endpoints. +- `health.ts`: health check endpoints. + +Route files should stay thin. They should validate input, get a service from `typedi`'s `Container`, call the service, and return `{ code, data, message }` style responses. + +### Services + +- `back/services/*` + +Services contain most business logic. Common examples: + +- `cron.ts`: create/update/delete/run cron jobs, generate crontab data, manage logs, call scheduler client. +- `env.ts`: manage environment variables. +- `config.ts`: read/write config files. +- `script.ts`: manage script files. +- `subscription.ts`: manage script subscriptions and repository pulls. +- `dependence.ts`: install/manage runtime dependencies. +- `system.ts`: system info and settings. +- `notify.ts`: notification behavior. +- `sock.ts`: socket/log stream behavior. +- `grpc.ts`: gRPC server lifecycle. +- `http.ts`: HTTP server lifecycle. + +When changing backend behavior, first find the API route, then follow it into the matching service. In most cases, the service is the right place for behavioral changes. + +### Data Models + +- `back/data/index.ts` +- `back/data/*.ts` + +The backend uses Sequelize with SQLite. Database storage is configured in `back/data/index.ts`: + +```text +data/db/database.sqlite +``` + +Common model files: + +- `cron.ts`: cron job model. +- `cronView.ts`: saved cron table views. +- `env.ts`: environment variable model. +- `dependence.ts`: dependency model. +- `open.ts`: open API app/token model. +- `subscription.ts`: subscription model. +- `system.ts`: system settings model. +- `notify.ts`: notification-related data. + +Model sync and simple column migrations are currently handled in `back/loaders/db.ts`. + +### Configuration + +- `back/config/index.ts` + +This is the central runtime config file. It reads `.env`, establishes `QL_DIR`, and defines important paths: + +- `dataPath`: runtime data root. +- `configPath`: config files. +- `scriptPath`: user scripts. +- `repoPath`: subscription repositories. +- `logPath`: task logs. +- `dbPath`: SQLite database location. +- `uploadPath`: uploaded files. +- `shellPath`: shell runtime scripts. +- `preloadPath`: JS/Python/Shell preload files. + +Before hardcoding paths, check `back/config/index.ts`. + +### Scheduling And gRPC + +- `back/schedule/*` +- `back/protos/*` +- `back/services/grpc.ts` + +The project has two scheduling paths: + +- Standard crontab-style tasks are persisted and written through backend cron logic. +- Node/gRPC scheduler logic handles cases such as second-level cron expressions or additional schedules. + +`back/services/cron.ts` decides whether a task needs the Node scheduler using schedule shape and `extra_schedules`. + +### Shared Backend Utilities + +- `back/shared/*` +- `back/config/util.ts` +- `back/config/share.ts` +- `back/config/http.ts` + +Use these before adding new global helpers. Existing shared code includes: + +- auth helpers. +- shared store. +- log stream manager. +- task runner helpers. +- concurrency limits. +- file locking utilities. +- HTTP/proxy helpers. + +## Frontend Architecture + +### Umi Config + +- `.umirc.ts` + +Important behavior: + +- Dev server proxies API requests to `http://127.0.0.1:5700/`. +- Frontend build output is `static/dist`. +- Runtime env script is loaded from `./api/env.js`. +- `QlBaseUrl` affects frontend public path and routing base. + +### App Initialization + +- `src/app.ts` + +Responsibilities: + +- Load Chinese and English locale JSON. +- Determine locale from URL/cookie/localStorage. +- Set Umi locale. +- Apply `QlBaseUrl` as public path and router basename. + +### Layout And Routes + +- `src/layouts/defaultProps.tsx` +- `src/layouts/index.tsx` + +`defaultProps.tsx` defines the main route/menu list. If adding a new page visible in the sidebar, update this file. + +Current major pages: + +- `src/pages/crontab`: timed task management. +- `src/pages/subscription`: subscription management. +- `src/pages/env`: environment variables. +- `src/pages/config`: config files. +- `src/pages/script`: script management. +- `src/pages/dependence`: dependency management. +- `src/pages/log`: log management. +- `src/pages/diff`: diff tool. +- `src/pages/setting`: system settings. +- `src/pages/login`: login. +- `src/pages/initialization`: first-run initialization. +- `src/pages/error`: error page. + +### Frontend Utilities + +- `src/utils/http.tsx`: API request helper. +- `src/utils/websocket.ts`: socket connection behavior. +- `src/utils/config.ts`: frontend config helpers. +- `src/utils/const.ts`: constants. +- `src/utils/date.ts`: date formatting helpers. +- `src/utils/init.ts`: initialization helpers. +- `src/utils/codemirror/*`: CodeMirror integration. +- `src/utils/monaco/*`: Monaco integration. + +When changing a page's API behavior, inspect both the page file and `src/utils/http.tsx`. + +### Components And Styling + +- `src/components/*`: reusable UI components. +- `src/pages/**/index.less`: page-level styles. +- `src/pages/script/index.module.less` and `src/pages/log/index.module.less`: CSS module styles. +- `src/assets/fonts/*`: bundled fonts. +- `src/locales/*.json`: i18n text. + +Follow the existing Ant Design and Ant Design Pro patterns when modifying UI. + +## Shell Runtime + +- `shell/task.sh`: task execution path. +- `shell/otask.sh`: alternate/manual task execution path. +- `shell/api.sh`: shell-side API helpers. +- `shell/env.sh`: environment setup. +- `shell/check.sh`: runtime check helpers. +- `shell/update.sh`: update helpers. +- `shell/rmlog.sh`: log cleanup. +- `shell/share.sh`: shared shell helpers. +- `shell/preload/*`: preload files injected into JS/Python/Shell task environments. + +The backend often coordinates task execution, but the actual user script process environment is shaped by files in `shell/`. + +## Runtime Data Directory + +- `data/` + +This directory is runtime state, not just source code. Be careful when modifying or deleting files here. + +Important subdirectories: + +- `data/db`: SQLite database. +- `data/config`: generated and user-edited config files. +- `data/scripts`: user scripts. +- `data/repo`: cloned subscription repositories. +- `data/log`: task logs. +- `data/upload`: uploaded files. +- `data/syslog`: system logs. +- `data/ssh.d`: SSH-related runtime data. +- `data/dep_cache`: dependency cache, when present. + +Many bugs that appear as "backend logic" may involve state stored under `data/`. + +## Docker And Release Files + +- `docker/Dockerfile` +- `docker/310.Dockerfile` +- `docker/docker-compose.yml` +- `docker/docker-entrypoint.sh` +- `ecosystem.config.js` +- `version.yaml` + +Use these when changing deployment, container startup, PM2 behavior, or release metadata. + +## Common Modification Map + +### Add Or Modify A Backend API + +Typical files: + +1. Add or update route in `back/api/.ts`. +2. Add or update service logic in `back/services/.ts`. +3. Add or update model in `back/data/.ts` if persistence changes. +4. Add validation with `celebrate`/`Joi` near the route. +5. Update frontend caller in `src/pages/**` or `src/utils/**`. + +### Add A New Frontend Page + +Typical files: + +1. Create `src/pages//index.tsx`. +2. Add styles in `src/pages//index.less` if needed. +3. Register route/menu in `src/layouts/defaultProps.tsx`. +4. Add locale strings in `src/locales/zh-CN.json` and `src/locales/en-US.json`. +5. Add API calls through the existing request helper. + +### Change Cron/Task Behavior + +Start with: + +- `back/api/cron.ts` +- `back/services/cron.ts` +- `back/schedule/*` +- `shell/task.sh` +- `shell/otask.sh` +- `shell/preload/*` + +Also inspect: + +- `back/data/cron.ts` +- `back/validation/schedule.ts` +- `data/config/crontab.list` +- `data/log/*` + +### Change Environment Variable Behavior + +Start with: + +- `back/api/env.ts` +- `back/services/env.ts` +- `back/data/env.ts` +- `src/pages/env/index.tsx` + +Also inspect: + +- `shell/preload/env.sh` +- `shell/preload/env.js` +- `shell/preload/env.py` + +### Change Script Management + +Start with: + +- `back/api/script.ts` +- `back/services/script.ts` +- `src/pages/script/index.tsx` +- `data/scripts/*` + +### Change Login/Auth/Security + +Start with: + +- `back/api/user.ts` +- `back/services/user.ts` +- `back/shared/auth.ts` +- `back/shared/store.ts` +- `back/loaders/express.ts` +- `back/token.ts` +- `src/pages/login/index.tsx` +- `src/pages/initialization/index.tsx` + +Be careful with: + +- JWT behavior. +- open API token behavior. +- first-run initialization. +- platform-specific session limits. + +### Change Subscription Behavior + +Start with: + +- `back/api/subscription.ts` +- `back/services/subscription.ts` +- `back/data/subscription.ts` +- `src/pages/subscription/index.tsx` +- `data/repo/*` + +### Change Dependency Management + +Start with: + +- `back/api/dependence.ts` +- `back/services/dependence.ts` +- `back/data/dependence.ts` +- `src/pages/dependence/index.tsx` +- `data/deps` +- `data/dep_cache` + +### Change Logs Or Live Log Streaming + +Start with: + +- `back/api/log.ts` +- `back/services/log.ts` +- `back/services/sock.ts` +- `back/shared/logStreamManager.ts` +- `src/pages/log/index.tsx` +- `src/components/terminal.tsx` +- `data/log/*` + +## Coding Conventions + +Backend: + +- Prefer adding business logic to services, not route files. +- Use `typedi` services consistently. +- Use existing config paths from `back/config/index.ts`. +- Return API responses in the existing `{ code, data, message }` shape. +- Use existing utilities before adding new helpers. +- Preserve current SQLite/Sequelize style unless doing a larger data-layer refactor. + +Frontend: + +- Follow existing Umi/React/Ant Design patterns. +- Keep route/menu changes in `src/layouts/defaultProps.tsx`. +- Use existing request/WebSocket helpers. +- Add or update locale strings for visible UI text. +- Keep page-specific styles near the page. + +Shell/runtime: + +- Treat `shell/` as part of production behavior. +- Test task execution changes with realistic scripts when possible. +- Be careful with path quoting and environment variable propagation. + +Data: + +- Treat `data/` as mutable runtime state. +- Do not delete runtime state unless explicitly requested. +- Schema changes should account for existing SQLite databases. + +## Suggested First Steps For AI Agents + +When asked to modify behavior: + +1. Identify whether the change is frontend, backend, shell runtime, data model, or deployment. +2. Search by feature name in `src/pages`, `back/api`, and `back/services`. +3. Read the route file and matching service before editing. +4. If persistence is involved, read the matching `back/data` model and `back/loaders/db.ts`. +5. If task execution is involved, inspect `shell/` and `back/services/cron.ts`. +6. Make the smallest scoped change that matches existing patterns. +7. Run the most relevant check: + - `pnpm run build:back` for backend TypeScript changes. + - `pnpm run build:front` for frontend build changes. + - targeted manual task/API checks for shell and scheduler changes. + +## Quick Directory Reference + +```text +. +├── back/ Backend TypeScript application +│ ├── api/ Express route modules +│ ├── config/ Runtime config, paths, constants, helpers +│ ├── data/ Sequelize models and SQLite connection +│ ├── loaders/ Startup initialization and Express setup +│ ├── middlewares/ Express middlewares +│ ├── protos/ gRPC proto files and generated TS +│ ├── schedule/ Scheduler/gRPC client helpers +│ ├── services/ Business logic services +│ ├── shared/ Shared backend utilities +│ └── validation/ Joi validation schemas +├── src/ Frontend Umi/React application +│ ├── assets/ Fonts and static frontend assets +│ ├── components/ Shared UI components +│ ├── hooks/ Frontend hooks +│ ├── layouts/ Main layout and menu route config +│ ├── locales/ i18n JSON +│ ├── pages/ Feature pages +│ └── utils/ HTTP, WebSocket, editor, date, and config utilities +├── shell/ Task runtime shell scripts and preload files +├── data/ Runtime state: db, logs, scripts, repos, configs +├── docker/ Docker build and compose files +├── sample/ Sample scripts and default config templates +├── static/ Built frontend/backend artifacts +└── docs/ Project documentation +``` diff --git a/src/components/tag.tsx b/src/components/tag.tsx index f9b5db51..2df5cb50 100644 --- a/src/components/tag.tsx +++ b/src/components/tag.tsx @@ -66,9 +66,7 @@ const EditableTagGroup = ({ }, [inputVisible]); useEffect(() => { - if (value) { - setTags(value); - } + setTags(value || []); }, [value]); return ( diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 0a1a2a2a..29e4bdbf 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -437,6 +437,9 @@ "Cron表达式格式有误": "Incorrect Cron Expression Format", "添加Labels成功": "Labels added successfully", "删除Labels成功": "Labels deleted successfully", + "添加标签成功": "Tags added successfully", + "删除标签成功": "Tags deleted successfully", + "请至少输入一个标签": "Please enter at least one tag", "编辑视图": "Edit View", "排序方式": "Sort Order", "开始时间": "Start Time", diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index 448e140c..01cdf4ab 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -437,6 +437,9 @@ "Cron表达式格式有误": "Cron表达式格式有误", "添加Labels成功": "添加Labels成功", "删除Labels成功": "删除Labels成功", + "添加标签成功": "添加标签成功", + "删除标签成功": "删除标签成功", + "请至少输入一个标签": "请至少输入一个标签", "编辑视图": "编辑视图", "排序方式": "排序方式", "开始时间": "开始时间", diff --git a/src/pages/env/index.tsx b/src/pages/env/index.tsx index fde76fda..0cb2c610 100644 --- a/src/pages/env/index.tsx +++ b/src/pages/env/index.tsx @@ -36,7 +36,7 @@ import { useVT } from 'virtualizedtableforantd4'; import Copy from '../../components/copy'; import EditNameModal from './editNameModal'; import './index.less'; -import EnvModal from './modal'; +import EnvModal, { EnvLabelModal } from './modal'; const { Paragraph } = Typography; const { Search } = Input; @@ -121,6 +121,25 @@ const Env = () => { ); }, }, + { + title: intl.get('标签'), + dataIndex: 'labels', + key: 'labels', + render: (labels: string[] | null) => { + const envLabels = Array.isArray(labels) ? labels : []; + return ( + + {envLabels + .filter((label) => label) + .map((label) => ( + + {label} + + ))} + + ); + }, + }, { title: intl.get('更新时间'), dataIndex: 'timestamp', @@ -238,6 +257,7 @@ const Env = () => { const [loading, setLoading] = useState(true); const [isModalVisible, setIsModalVisible] = useState(false); const [isEditNameModalVisible, setIsEditNameModalVisible] = useState(false); + const [isLabelModalVisible, setIsLabelModalVisible] = useState(false); const [editedEnv, setEditedEnv] = useState(); const [selectedRowIds, setSelectedRowIds] = useState([]); const [searchText, setSearchText] = useState(''); @@ -408,6 +428,13 @@ const Env = () => { getEnvs(); }; + const handleLabelCancel = (needUpdate?: boolean) => { + setIsLabelModalVisible(false); + if (needUpdate) { + getEnvs(); + } + }; + const [vt, setVT] = useVT( () => ({ scroll: { y: tableScrollHeight } }), [tableScrollHeight], @@ -542,7 +569,12 @@ const Env = () => { const exportEnvs = () => { const envs = value .filter((x) => selectedRowIds.includes(x.id)) - .map((x) => ({ value: x.value, name: x.name, remarks: x.remarks })); + .map((x) => ({ + value: x.value, + name: x.name, + remarks: x.remarks, + labels: x.labels, + })); exportJson('env.json', JSON.stringify(envs)); }; @@ -550,6 +582,10 @@ const Env = () => { setIsEditNameModalVisible(true); }; + const modifyLabels = () => { + setIsLabelModalVisible(true); + }; + const onSearch = (value: string) => { setSearchText(value.trim()); }; @@ -622,6 +658,13 @@ const Env = () => { > {intl.get('批量修改变量名称')} + , + , + , + ]} + centered + maskClosable={false} + forceRender + onCancel={() => handleCancel(false)} + > +
+ + + +
+ + ); +}; From bb6d436c19d069271c03ebd4d1f4549e49825d8b Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 24 May 2026 00:15:24 +0800 Subject: [PATCH 18/24] fix: run scripts from their own directory --- shell/otask.sh | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/shell/otask.sh b/shell/otask.sh index 516a9439..f997911d 100755 --- a/shell/otask.sh +++ b/shell/otask.sh @@ -83,6 +83,25 @@ clear_non_sh_env() { fi } +enter_script_workdir() { + local use_dot_prefix="$1" + + cd $dir_scripts + if [[ ${file_param} =~ "/" ]]; then + local script_dir="${file_param%/*}" + local script_name="${file_param##*/}" + + if [[ -d ${script_dir} ]]; then + cd ${script_dir} + if [[ "${use_dot_prefix}" == "true" ]]; then + file_param="./${script_name}" + else + file_param="${script_name}" + fi + fi + fi +} + ## 正常运行单个脚本,$1:传入参数 run_normal() { local file_param=$1 @@ -90,12 +109,7 @@ run_normal() { random_delay "$file_param" fi - cd $dir_scripts - local relative_path="${file_param%/*}" - if [[ ${file_param} != /* ]] && [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then - cd ${relative_path} - file_param=${file_param/$relative_path\//} - fi + enter_script_workdir if [[ $isJsOrPythonFile == 'false' ]]; then clear_non_sh_env @@ -128,12 +142,7 @@ run_concurrent() { time=$(date "+$mtime_format") single_log_time=$(format_log_time "$mtime_format" "$time") - cd $dir_scripts - local relative_path="${file_param%/*}" - if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then - cd ${relative_path} - file_param=${file_param/$relative_path\//} - fi + enter_script_workdir local j=0 for i in ${array_run[@]}; do @@ -182,12 +191,7 @@ run_designated() { clear_non_sh_env fi - cd $dir_scripts - local relative_path="${file_param%/*}" - if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then - cd ${relative_path} - file_param=${file_param/$relative_path\//} - fi + enter_script_workdir envParam="${env_param}" numParam="${num_param}" $timeoutCmd $which_program $file_param "${script_params[@]}" } @@ -196,12 +200,7 @@ run_designated() { run_else() { local file_param="$1" - cd $dir_scripts - local relative_path="${file_param%/*}" - if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then - cd ${relative_path} - file_param=${file_param/$relative_path\//.\/} - fi + enter_script_workdir true shift From 57d58c871e4f4b51f562c719d8eaa8d64f9990bf Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 24 May 2026 01:40:07 +0800 Subject: [PATCH 19/24] fix: isolate task node dependencies --- docker/310.Dockerfile | 2 +- docker/Dockerfile | 2 +- shell/otask.sh | 15 ++++++++++++++- shell/preload/sitecustomize.js | 31 +++++++++++++++++++++++++++++++ shell/share.sh | 12 +----------- 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/docker/310.Dockerfile b/docker/310.Dockerfile index f9cb29b7..6cbbc127 100644 --- a/docker/310.Dockerfile +++ b/docker/310.Dockerfile @@ -73,7 +73,7 @@ ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ HOME=/root ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ - NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \ + NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules \ PIP_CACHE_DIR=${PYTHON_HOME}/pip \ PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages diff --git a/docker/Dockerfile b/docker/Dockerfile index 6cde05d9..405260ac 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -73,7 +73,7 @@ ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ HOME=/root ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ - NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \ + NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules \ PIP_CACHE_DIR=${PYTHON_HOME}/pip \ PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages diff --git a/shell/otask.sh b/shell/otask.sh index f997911d..c6612bd1 100755 --- a/shell/otask.sh +++ b/shell/otask.sh @@ -83,6 +83,16 @@ clear_non_sh_env() { fi } +append_node_dependency_path() { + export PREV_NODE_PATH="${NODE_PATH:=}" + + local pnpm_global_path=$(pnpm root -g 2>/dev/null) + if [[ -n "$pnpm_global_path" ]]; then + export QL_NODE_GLOBAL_PATH="$pnpm_global_path" + export NODE_PATH="${NODE_PATH:+${NODE_PATH}:}${pnpm_global_path}" + fi +} + enter_script_workdir() { local use_dot_prefix="$1" @@ -241,7 +251,7 @@ check_nounset() { } main() { - if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then + if [[ $1 == *.js ]] || [[ $1 == *.mjs ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then if [[ $1 == *.sh ]]; then timeoutCmd="" fi @@ -277,6 +287,7 @@ main() { handle_task_start "${task_shell_params[@]}" check_file "${task_shell_params[@]}" +append_node_dependency_path if [[ $isJsOrPythonFile == 'false' ]]; then run_task_before "${task_shell_params[@]}" fi @@ -286,6 +297,8 @@ main "${task_shell_params[@]}" if [[ "$set_u_on" == 'true' ]]; then set -u fi +export NODE_PATH="${PREV_NODE_PATH}" +unset QL_NODE_GLOBAL_PATH if [[ $isJsOrPythonFile == 'true' ]]; then export NODE_OPTIONS="${PREV_NODE_OPTIONS}" export PYTHONPATH="${PREV_PYTHONPATH}" diff --git a/shell/preload/sitecustomize.js b/shell/preload/sitecustomize.js index dbb9198f..44dc1bea 100644 --- a/shell/preload/sitecustomize.js +++ b/shell/preload/sitecustomize.js @@ -1,7 +1,36 @@ const { execSync } = require('child_process'); +const Module = require('module'); +const path = require('path'); const client = require('./client.js'); require(`./env.js`); +function preferGlobalNodeModules() { + const { QL_NODE_GLOBAL_PATH } = process.env; + if (!QL_NODE_GLOBAL_PATH || Module._qlGlobalPathPatched) { + return; + } + + const originalResolveFilename = Module._resolveFilename; + Module._resolveFilename = function (request, parent, isMain, options) { + if ( + !Module.builtinModules.includes(request) && + !request.startsWith('node:') && + !request.startsWith('.') && + !path.isAbsolute(request) + ) { + try { + return originalResolveFilename.call(this, request, parent, isMain, { + ...options, + paths: [QL_NODE_GLOBAL_PATH], + }); + } catch (error) {} + } + + return originalResolveFilename.call(this, request, parent, isMain, options); + }; + Module._qlGlobalPathPatched = true; +} + function expandRange(rangeStr, max) { const tempRangeStr = rangeStr .trim() @@ -113,6 +142,8 @@ try { return; } + preferGlobalNodeModules(); + process.on('SIGTERM', (code) => { process.exit(15); }); diff --git a/shell/share.sh b/shell/share.sh index cad30659..585d885d 100755 --- a/shell/share.sh +++ b/shell/share.sh @@ -65,17 +65,7 @@ link_name=( ) init_env() { - local pnpm_global_path=$(pnpm root -g 2>/dev/null) - export NODE_PATH="/usr/local/bin:/usr/local/lib/node_modules${pnpm_global_path:+:${pnpm_global_path}}" - - # 如果存在 pnpm 全局路径,创建软链接 - if [[ -n "$pnpm_global_path" ]]; then - # 确保目标目录存在 - mkdir -p "${dir_root}/node_modules" - # 链接全局模块到项目的 node_modules - ln -sf "${pnpm_global_path}/"* "${dir_root}/node_modules/" 2>/dev/null || true - fi - + export NODE_PATH="/usr/local/bin:/usr/local/lib/node_modules" export PYTHONUNBUFFERED=1 } From 84d730d510b8a6343b2f4b2e6c4acf9ae227a31c Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 30 May 2026 18:03:51 +0800 Subject: [PATCH 20/24] =?UTF-8?q?=E7=BB=9F=E4=B8=80=20Alpine/Debian=20?= =?UTF-8?q?=E5=88=86=E6=94=AF=EF=BC=8CQL=5FSCHEDULER=20=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=8C=96=E8=B0=83=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修改获取示例文件 api path * 增加 debian-slim 基础镜像 * 修复 debian apt 命令,支持 qinglong 命令 * 更新 npm 版本 0.7.7 * 更新 npm v0.8.4 * 修复linux依赖检测 (#2082) * 修复拉取私有仓库 * 修复 shell check_server * 修复 qinglong 命令 * 更新 npm 版本 v0.13.2 * 增加 debian 开发版本 * 修改切换 linux 镜像源 * 修复 qinglong 命令 * 移除 qinglong 命令 npm 默认镜像源 * 修复 workflow * 更新 npm 版本 v0.14.5 * 增加 npx 命令 * 更新 workflow action 版本 * 更新 npm 版本 v0.16.0 * 修复 linux 镜像源 * 更新 npm 版本 v0.17.0 * 更新 npm 版本 v0.18.0 * 修改 npm 安装启动命令 * 更新 npm 版本 v0.19.9 * 修复 debian netcat 包名 * 更新 npm 版本 v0.20.4 * 安装 linux 依赖自动识别 alpine 和 debian * 修改 apt 命令 * 更新 npm 版本 v0.21.2 * 修改 ts 文件执行依赖 * npm 启动增加 reload 逻辑 * 更新 npm 版本 v2.17.8 * 修复 qinglong 命令 * 更新 npm 版本 v2.17.9 * 更新 npm 版本 v2.17.10 * 更新 npm 版本 v2.17.11 * 修改 debian 版本为 12 bookworm * 更新 npm 版本 v2.17.12 * 修改本地服务启动提示 * 更新 npm 版本 v2.17.13 * 写入文件增加文件锁 * 修复系统安装依赖提示 * 更新 npm 版本 v2.18.2-6 * 更新 nodejs 版本 * 更新 npm 版本 v2.18.3-3 * 修复 command 变量 * 移除自动清除 deb * 修复 npm 启动脚本 * 修复发布 npm包依赖文件 * 修改 linux 启动文件逻辑 * 更新 npm 版本 v2.19.0-10 * 修复 apt 命令 * 更新 npm 版本 v2.19.1-0 * 更新 npm 版本 v2.19.2-2 * 增加 packageManager * 增加用户 qinglong * 更新 pipeline * 移除 init_nginx * 更新 npm 版本 v2.20.0 * 更新 npm 版本 2.20.1 * 更新 npm 版本 2.20.2 * fix: 修复非 root 用户启动 * chore: 合并 debian 和 alpine 逻辑 --------- Co-authored-by: dream10201 --- .github/workflows/build-docker-image.yml | 207 +++++++++++++++++++---- .npmignore | 22 +++ back/api/config.ts | 2 +- back/config/const.ts | 35 ++++ back/config/util.ts | 200 +++++++++++++++++++++- back/services/cron.ts | 49 ++++-- back/services/dependence.ts | 78 +++++++-- back/services/system.ts | 34 ++-- docker/310.Dockerfile | 2 +- docker/310.Dockerfile.debian | 112 ++++++++++++ docker/Dockerfile | 2 +- docker/Dockerfile.debian | 112 ++++++++++++ docker/docker-entrypoint.sh | 87 +++++++++- package.json | 18 +- shell/bot.sh | 17 +- shell/pub.sh | 16 +- shell/start.sh | 132 +++++++++++++++ src/pages/diff/index.tsx | 2 +- src/pages/setting/dependence.tsx | 4 +- 19 files changed, 1018 insertions(+), 113 deletions(-) create mode 100644 .npmignore create mode 100644 docker/310.Dockerfile.debian create mode 100644 docker/Dockerfile.debian create mode 100644 shell/start.sh diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 6b399d28..420dc9f4 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -89,9 +89,6 @@ jobs: env: GITHUB_REPO: github.com/${{ github.repository_owner }}/qinglong-static GITHUB_BRANCH: ${{ github.ref_name }} - REPO_GITEE: git@gitee.com:whyour/qinglong-static.git - REPO_GITLAB: git@gitlab.com:whyour/qinglong-static.git - PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PK }} run: | mkdir -p tmp cd ./tmp @@ -135,16 +132,13 @@ jobs: git remote set-url origin git@gitee.com:whyour/qinglong-static.git git push --force --mirror - build: + build-alpine: if: ${{ !startsWith(github.ref, 'refs/tags/') }} needs: build-static - runs-on: ubuntu-22.04 - permissions: packages: write contents: read - steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v6 @@ -160,11 +154,9 @@ jobs: run: | VERSION=$(grep '^version:' version.yaml | awk '{print $2}') echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Version: $VERSION" - name: Setup timezone - run: | - sudo timedatectl set-timezone Asia/Shanghai + run: sudo timedatectl set-timezone Asia/Shanghai - name: Login to DockerHub uses: docker/login-action@v4 @@ -190,7 +182,7 @@ jobs: latest=false tags: | type=ref,event=branch,enable=${{ github.ref == format('refs/heads/{0}', 'develop') }} - type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }} + type=ref,event=branch,enable=${{ github.ref == format('refs/heads/{0}', 'master') }} type=raw,value=${{ steps.version.outputs.version }},enable=${{ github.ref == format('refs/heads/{0}', 'master') }} type=semver,pattern={{version}} @@ -200,8 +192,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - - name: Build and push - id: docker_build + - name: Build and push (Alpine) uses: docker/build-push-action@v7 with: build-args: | @@ -209,30 +200,22 @@ jobs: QL_BRANCH=${{ github.ref_name }} SOURCE_COMMIT=${{ github.sha }} network: host - # linux/s390x npm 暂不可用 platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/386 context: . file: ./docker/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=registry,ref=whyour/qinglong:cache - cache-to: type=registry,ref=whyour/qinglong:cache,mode=max + cache-from: type=registry,ref=whyour/qinglong:cache-alpine + cache-to: type=registry,ref=whyour/qinglong:cache-alpine,mode=max - - name: Image digest - run: | - echo ${{ steps.docker_build.outputs.digest }} - - build310: - if: ${{ github.ref_name == 'master' }} + build-debian: + if: ${{ !startsWith(github.ref, 'refs/tags/') }} needs: build-static - runs-on: ubuntu-22.04 - permissions: packages: write contents: read - steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v6 @@ -248,11 +231,85 @@ jobs: run: | VERSION=$(grep '^version:' version.yaml | awk '{print $2}') echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Version: $VERSION" - name: Setup timezone + run: sudo timedatectl set-timezone Asia/Shanghai + + - name: Login to DockerHub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v6 + with: + images: | + ${{ github.repository }} + ghcr.io/${{ github.repository }} + flavor: | + latest=false + tags: | + type=raw,value=debian-dev,enable=${{ github.ref == format('refs/heads/{0}', 'develop') }} + type=raw,value=debian,enable=${{ github.ref == format('refs/heads/{0}', 'master') }} + type=raw,value=${{ steps.version.outputs.version }}-debian,enable=${{ github.ref == format('refs/heads/{0}', 'master') }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build and push (Debian) + uses: docker/build-push-action@v7 + with: + build-args: | + MAINTAINER=${{ github.repository_owner }} + QL_BRANCH=${{ github.ref_name }} + SOURCE_COMMIT=${{ github.sha }} + network: host + platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x + context: . + file: ./docker/Dockerfile.debian + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=whyour/qinglong:cache-debian + cache-to: type=registry,ref=whyour/qinglong:cache-debian,mode=max + + build-alpine310: + if: ${{ github.ref_name == 'master' }} + needs: build-static + runs-on: ubuntu-22.04 + permissions: + packages: write + contents: read + steps: + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v6 + with: + version: "8.3.1" + - uses: actions/setup-node@v6 + with: + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml + + - name: Read version from version.yaml + id: version run: | - sudo timedatectl set-timezone Asia/Shanghai + VERSION=$(grep '^version:' version.yaml | awk '{print $2}') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Setup timezone + run: sudo timedatectl set-timezone Asia/Shanghai - name: Login to DockerHub uses: docker/login-action@v4 @@ -273,8 +330,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - - name: Build and push python3.10 - id: docker_build_310 + - name: Build and push (Alpine Python 3.10) uses: docker/build-push-action@v7 with: build-args: | @@ -282,7 +338,6 @@ jobs: QL_BRANCH=${{ github.ref_name }} SOURCE_COMMIT=${{ github.sha }} network: host - # linux/s390x npm 暂不可用 platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/386 context: . file: ./docker/310.Dockerfile @@ -290,9 +345,93 @@ jobs: tags: | whyour/qinglong:python3.10 whyour/qinglong:${{ steps.version.outputs.version }}-python3.10 - cache-from: type=registry,ref=whyour/qinglong:cache-python3.10 - cache-to: type=registry,ref=whyour/qinglong:cache-python3.10,mode=max + cache-from: type=registry,ref=whyour/qinglong:cache-alpine-python3.10 + cache-to: type=registry,ref=whyour/qinglong:cache-alpine-python3.10,mode=max - - name: Image digest + build-debian310: + if: ${{ github.ref_name == 'master' }} + needs: build-static + runs-on: ubuntu-22.04 + permissions: + packages: write + contents: read + steps: + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v6 + with: + version: "8.3.1" + - uses: actions/setup-node@v6 + with: + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml + + - name: Read version from version.yaml + id: version run: | - echo ${{ steps.docker_build_310.outputs.digest }} + VERSION=$(grep '^version:' version.yaml | awk '{print $2}') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Setup timezone + run: sudo timedatectl set-timezone Asia/Shanghai + + - name: Login to DockerHub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build and push (Debian Python 3.10) + uses: docker/build-push-action@v7 + with: + build-args: | + MAINTAINER=${{ github.repository_owner }} + QL_BRANCH=${{ github.ref_name }} + SOURCE_COMMIT=${{ github.sha }} + network: host + platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x + context: . + file: ./docker/310.Dockerfile.debian + push: true + tags: | + whyour/qinglong:debian-python3.10 + whyour/qinglong:${{ steps.version.outputs.version }}-debian-python3.10 + cache-from: type=registry,ref=whyour/qinglong:cache-debian-python3.10 + cache-to: type=registry,ref=whyour/qinglong:cache-debian-python3.10,mode=max + + publish: + if: ${{ github.ref_name == 'master' }} + needs: [build-alpine, build-debian] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v3 + with: + version: "8.3.1" + + - uses: actions/setup-node@v3 + with: + cache: "pnpm" + + - name: build front and back + run: | + pnpm install --frozen-lockfile + pnpm build:front + pnpm build:back + + - name: publish npm package + run: | + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc + npm publish diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..230f2ff4 --- /dev/null +++ b/.npmignore @@ -0,0 +1,22 @@ +/.tmp/ +/.github/ +/.vscode/ +/.history/ +/back/**/*.ts +/back/**/*.json +/cli/ +/data/ +/src/ +/static/**/*.js.map +/static/**/*.gz +/.editorconfig +/.gitignore +/.prettierignore +/.prettierrc +/.umirc.ts +/nodemon.json +/pnpm-lock.yaml +/tsconfig.back.json +/tsconfig.json +/typings.d.ts +/.env \ No newline at end of file diff --git a/back/api/config.ts b/back/api/config.ts index 99649fb3..55c740fd 100644 --- a/back/api/config.ts +++ b/back/api/config.ts @@ -14,7 +14,7 @@ export default (app: Router) => { app.use('/configs', route); route.get( - '/sample', + '/samples', async (req: Request, res: Response, next: NextFunction) => { try { res.send({ diff --git a/back/config/const.ts b/back/config/const.ts index e5083f43..74be9a93 100644 --- a/back/config/const.ts +++ b/back/config/const.ts @@ -49,3 +49,38 @@ export const NotificationModeStringMap = { 19: 'ntfy', 20: 'wxPusherBot', } as const; + +export const LINUX_DEPENDENCE_COMMAND: Record< + 'Debian' | 'Ubuntu' | 'Alpine', + { + install: string; + uninstall: string; + info: string; + check(info: string): boolean; + } +> = { + Debian: { + install: 'apt-get install -y', + uninstall: 'apt-get remove -y', + info: 'dpkg-query -s', + check(info: string) { + return info.includes('install ok installed'); + }, + }, + Ubuntu: { + install: 'apt-get install -y', + uninstall: 'apt-get remove -y', + info: 'dpkg-query -s', + check(info: string) { + return info.includes('install ok installed'); + }, + }, + Alpine: { + install: 'apk add --no-check-certificate', + uninstall: 'apk del', + info: 'apk info -es', + check(info: string) { + return info.includes('installed'); + }, + }, +}; diff --git a/back/config/util.ts b/back/config/util.ts index 7d61f74f..0f050193 100644 --- a/back/config/util.ts +++ b/back/config/util.ts @@ -1,6 +1,6 @@ import * as fs from 'fs/promises'; import * as path from 'path'; -import { exec } from 'child_process'; +import { exec, execSync } from 'child_process'; import psTreeFun from 'ps-tree'; import { promisify } from 'util'; import { load } from 'js-yaml'; @@ -10,9 +10,38 @@ import Logger from '../loaders/logger'; import { writeFileWithLock } from '../shared/utils'; import { DependenceTypes } from '../data/dependence'; import { FormData } from 'undici'; +import os from 'os'; export * from './share'; +let osType: 'Debian' | 'Ubuntu' | 'Alpine' | undefined; + +function getOsTypeSync(): 'Debian' | 'Ubuntu' | 'Alpine' | undefined { + // 1. 环境变量覆盖 + const envOs = process.env.QL_OS_TYPE?.toLowerCase(); + if (envOs === 'alpine') return 'Alpine'; + if (envOs === 'debian') return 'Debian'; + if (envOs === 'ubuntu') return 'Ubuntu'; + + // 2. 模块缓存(由 detectOS 设置) + if (osType) return osType; + + // 3. 能力检测:检查包管理器二进制 + try { + execSync('which apt-get', { stdio: 'ignore' }); + return 'Debian'; + } catch { + try { + execSync('which apk', { stdio: 'ignore' }); + return 'Alpine'; + } catch { + // macOS / 未知系统 + } + } + + return undefined; +} + export async function getFileContentByName(fileName: string) { const _exsit = await fileExist(fileName); if (_exsit) { @@ -550,7 +579,9 @@ except: spec=u.find_spec(name) print(name if spec else '') ''')"`, - [DependenceTypes.linux]: `apk info -es ${name}`, + [DependenceTypes.linux]: getOsTypeSync() === 'Alpine' + ? `apk info -es ${name}` + : `dpkg-query -s ${name}`, }; return baseCommands[type]; @@ -561,7 +592,9 @@ export function getInstallCommand(type: DependenceTypes, name: string): string { [DependenceTypes.nodejs]: 'pnpm add -g', [DependenceTypes.python3]: 'pip3 install --disable-pip-version-check --root-user-action=ignore', - [DependenceTypes.linux]: 'apk add --no-check-certificate', + [DependenceTypes.linux]: getOsTypeSync() === 'Alpine' + ? 'apk add --no-check-certificate' + : 'apt-get install -y', }; let command = baseCommands[type]; @@ -581,7 +614,9 @@ export function getUninstallCommand( [DependenceTypes.nodejs]: 'pnpm remove -g', [DependenceTypes.python3]: 'pip3 uninstall --disable-pip-version-check --root-user-action=ignore -y', - [DependenceTypes.linux]: 'apk del', + [DependenceTypes.linux]: getOsTypeSync() === 'Alpine' + ? 'apk del' + : 'apt-get remove -y', }; return `${baseCommands[type]} ${name.trim()}`; @@ -590,3 +625,160 @@ export function getUninstallCommand( export function isDemoEnv() { return process.env.DeployEnv === 'demo'; } + +async function getOSReleaseInfo(): Promise { + const osRelease = await fs.readFile('/etc/os-release', 'utf8'); + return osRelease; +} + +function isDebian(osReleaseInfo: string): boolean { + return osReleaseInfo.includes('Debian'); +} + +function isUbuntu(osReleaseInfo: string): boolean { + return osReleaseInfo.includes('Ubuntu'); +} + +function isCentOS(osReleaseInfo: string): boolean { + return osReleaseInfo.includes('CentOS') || osReleaseInfo.includes('Red Hat'); +} + +function isAlpine(osReleaseInfo: string): boolean { + return osReleaseInfo.includes('Alpine'); +} + +export async function detectOS(): Promise< + 'Debian' | 'Ubuntu' | 'Alpine' | undefined +> { + if (osType) return osType; + + const envOs = process.env.QL_OS_TYPE?.toLowerCase(); + if (envOs === 'alpine') { + osType = 'Alpine'; + return osType; + } + if (envOs === 'debian') { + osType = 'Debian'; + return osType; + } + if (envOs === 'ubuntu') { + osType = 'Ubuntu'; + return osType; + } + + const platform = os.platform(); + + if (platform === 'linux') { + const osReleaseInfo = await getOSReleaseInfo(); + if (isDebian(osReleaseInfo)) { + osType = 'Debian'; + } else if (isUbuntu(osReleaseInfo)) { + osType = 'Ubuntu'; + } else if (isAlpine(osReleaseInfo)) { + osType = 'Alpine'; + } else { + Logger.error(`Unknown Linux Distribution: ${osReleaseInfo}`); + console.error(`Unknown Linux Distribution: ${osReleaseInfo}`); + } + } else if (platform === 'darwin') { + osType = undefined; + } else { + Logger.error(`Unsupported platform: ${platform}`); + console.error(`Unsupported platform: ${platform}`); + } + + return osType; +} + +async function getCurrentMirrorDomain( + filePath: string, +): Promise { + const fileContent = await fs.readFile(filePath, 'utf8'); + const lines = fileContent.split('\n'); + for (const line of lines) { + if (line.trim().startsWith('#')) { + continue; + } + const match = line.match(/https?:\/\/[^\/]+/); + if (match) { + return match[0]; + } + } + return null; +} + +async function replaceDomainInFile( + filePath: string, + oldDomainWithScheme: string, + newDomainWithScheme: string, +): Promise { + let fileContent = await fs.readFile(filePath, 'utf8'); + let updatedContent = fileContent.replace( + new RegExp(oldDomainWithScheme, 'g'), + newDomainWithScheme, + ); + + if (!newDomainWithScheme.endsWith('/')) { + newDomainWithScheme += '/'; + } + + await writeFileWithLock(filePath, updatedContent); +} + +async function _updateLinuxMirror( + osType: string, + mirrorDomainWithScheme: string, +): Promise { + let filePath: string, currentDomainWithScheme: string | null; + switch (osType) { + case 'Debian': + filePath = '/etc/apt/sources.list.d/debian.sources'; + currentDomainWithScheme = await getCurrentMirrorDomain(filePath); + if (currentDomainWithScheme) { + await replaceDomainInFile( + filePath, + currentDomainWithScheme, + mirrorDomainWithScheme || 'http://deb.debian.org', + ); + return 'apt-get update'; + } else { + throw Error(`Current mirror domain not found.`); + } + case 'Ubuntu': + filePath = '/etc/apt/sources.list.d/ubuntu.sources'; + currentDomainWithScheme = await getCurrentMirrorDomain(filePath); + if (currentDomainWithScheme) { + await replaceDomainInFile( + filePath, + currentDomainWithScheme, + mirrorDomainWithScheme || 'http://archive.ubuntu.com', + ); + return 'apt-get update'; + } else { + throw Error(`Current mirror domain not found.`); + } + case 'Alpine': + filePath = '/etc/apk/repositories'; + currentDomainWithScheme = await getCurrentMirrorDomain(filePath); + if (currentDomainWithScheme) { + await replaceDomainInFile( + filePath, + currentDomainWithScheme, + mirrorDomainWithScheme || 'http://dl-cdn.alpinelinux.org', + ); + return 'apk update'; + } else { + throw Error(`Current mirror domain not found.`); + } + default: + throw Error('Unsupported OS type for updating mirrors.'); + } +} + +export async function updateLinuxMirrorFile(mirror: string): Promise { + const detectedOS = await detectOS(); + if (!detectedOS) { + throw Error(`Unknown Linux Distribution`); + } + return await _updateLinuxMirror(detectedOS, mirror); +} diff --git a/back/services/cron.ts b/back/services/cron.ts index 94cdd95a..def418a4 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -39,6 +39,25 @@ export default class CronService { return false; } + private get schedulerMode(): 'system' | 'node' { + const env = process.env.QL_SCHEDULER; + if (env === 'system') return 'system'; + if (env === 'node') return 'node'; + try { + execSync('which crond', { stdio: 'ignore' }); + return 'system'; + } catch { + return 'node'; + } + } + + private shouldUseCronClient(cron: Crontab): boolean { + if (this.schedulerMode === 'node') { + return !this.isSpecialSchedule(cron.schedule); + } + return this.isNodeCron(cron) && !this.isSpecialSchedule(cron.schedule); + } + private isOnceSchedule(schedule?: string) { return schedule?.startsWith(ScheduleType.ONCE); } @@ -80,7 +99,7 @@ export default class CronService { return doc; } - if (this.isNodeCron(doc) && !this.isSpecialSchedule(doc.schedule)) { + if (this.shouldUseCronClient(doc)) { await cronClient.addCron([ { name: doc.name || '', @@ -111,11 +130,9 @@ export default class CronService { return newDoc; } - if (this.isNodeCron(doc)) { - await cronClient.delCron([String(doc.id)]); - } + await cronClient.delCron([String(newDoc.id)]); - if (this.isNodeCron(newDoc) && !this.isSpecialSchedule(newDoc.schedule)) { + if (this.shouldUseCronClient(newDoc)) { await cronClient.addCron([ { name: doc.name || '', @@ -577,8 +594,8 @@ export default class CronService { public async enabled(ids: number[]) { await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } }); const docs = await CrontabModel.findAll({ where: { id: ids } }); - const sixCron = docs - .filter((x) => this.isNodeCron(x) && !this.isSpecialSchedule(x.schedule)) + const crons = docs + .filter((x) => this.shouldUseCronClient(x)) .map((doc) => ({ name: doc.name || '', id: String(doc.id), @@ -590,7 +607,8 @@ export default class CronService { if (isDemoEnv()) { return; } - await cronClient.addCron(sixCron); + + await cronClient.addCron(crons); await this.setCrontab(); } @@ -690,11 +708,13 @@ export default class CronService { await writeFileWithLock(config.crontabFile, crontab_string); - try { - execSync(`crontab ${config.crontabFile}`); - } catch (error: any) { - const errorMsg = error.message || String(error); - this.logger.error('[crontab] Failed to update system crontab:', errorMsg); + if (this.schedulerMode === 'system') { + try { + execSync(`crontab ${config.crontabFile}`); + } catch (error: any) { + const errorMsg = error.message || String(error); + this.logger.error('[crontab] Failed to update system crontab:', errorMsg); + } } await CrontabModel.update({ saved: true }, { where: {} }); @@ -745,8 +765,7 @@ export default class CronService { .filter( (x) => x.isDisabled !== 1 && - this.isNodeCron(x) && - !this.isSpecialSchedule(x.schedule), + this.shouldUseCronClient(x), ) .map((doc) => ({ name: doc.name || '', diff --git a/back/services/dependence.ts b/back/services/dependence.ts index eac054be..c00422b3 100644 --- a/back/services/dependence.ts +++ b/back/services/dependence.ts @@ -22,6 +22,8 @@ import { } from '../config/util'; import dayjs from 'dayjs'; import taskLimit from '../shared/pLimit'; +import { detectOS } from '../config/util'; +import { LINUX_DEPENDENCE_COMMAND } from '../config/const'; @Service() export default class DependenceService { @@ -159,8 +161,19 @@ export default class DependenceService { const docs = await DependenceModel.findAll({ where: { id: ids } }); for (const doc of docs) { taskLimit.removeQueuedDependency(doc); - const depInstallCommand = getInstallCommand(doc.type, doc.name); - const depUnInstallCommand = getUninstallCommand(doc.type, doc.name); + let depInstallCommand = getInstallCommand(doc.type, doc.name); + let depUnInstallCommand = getUninstallCommand(doc.type, doc.name); + const isLinuxDependence = doc.type === DependenceTypes.linux; + + if (isLinuxDependence) { + const osType = await detectOS(); + if (!osType) { + continue; + } + const linuxCommand = LINUX_DEPENDENCE_COMMAND[osType]; + depInstallCommand = `${linuxCommand.install} ${doc.name.trim()}`; + depUnInstallCommand = `${linuxCommand.uninstall} ${doc.name.trim()}`; + } const pids = await Promise.all([ getPid(depInstallCommand), getPid(depUnInstallCommand), @@ -217,23 +230,54 @@ export default class DependenceService { if (taskLimit.firstDependencyId !== dependency.id) { return resolve(null); } - - taskLimit.removeQueuedDependency(dependency); - const depIds = [dependency.id!]; + let depName = dependency.name.trim(); + const actionText = isInstall ? '安装' : '删除'; + const socketMessageType = isInstall + ? 'installDependence' + : 'uninstallDependence'; + const isNodeDependence = dependency.type === DependenceTypes.nodejs; + const isLinuxDependence = dependency.type === DependenceTypes.linux; + const isPythonDependence = dependency.type === DependenceTypes.python3; + const osType = await detectOS(); + let linuxCommand = {} as typeof LINUX_DEPENDENCE_COMMAND.Alpine; + taskLimit.removeQueuedDependency(dependency); + if (isLinuxDependence) { + if (!osType) { + await DependenceModel.update( + { status: DependenceStatus.installFailed }, + { where: { id: depIds } }, + ); + const startTime = dayjs(); + const message = `开始${actionText}依赖 ${depName},开始时间 ${startTime.format( + 'YYYY-MM-DD HH:mm:ss', + )}\n\n当前系统不支持\n\n依赖${actionText}失败,结束时间 ${startTime.format( + 'YYYY-MM-DD HH:mm:ss', + )},耗时 ${startTime.diff(startTime, 'second')} 秒`; + this.sockService.sendMessage({ + type: socketMessageType, + message, + references: depIds, + }); + this.updateLog(depIds, message); + return resolve(null); + } + linuxCommand = LINUX_DEPENDENCE_COMMAND[osType]; + } + const status = isInstall ? DependenceStatus.installing : DependenceStatus.removing; await DependenceModel.update({ status }, { where: { id: depIds } }); - const socketMessageType = isInstall - ? 'installDependence' - : 'uninstallDependence'; - let depName = dependency.name.trim(); - const command = isInstall + let command = isInstall ? getInstallCommand(dependency.type, depName) : getUninstallCommand(dependency.type, depName); - const actionText = isInstall ? '安装' : '删除'; + if (isLinuxDependence) { + command = isInstall + ? `${linuxCommand.install} ${depName.trim()}` + : `${linuxCommand.uninstall} ${depName.trim()}`; + } const startTime = dayjs(); const message = `开始${actionText}依赖 ${depName},开始时间 ${startTime.format( @@ -248,8 +292,12 @@ export default class DependenceService { // 判断是否已经安装过依赖 if (isInstall && !force) { - const getCommand = getGetCommand(dependency.type, depName); + let getCommand = getGetCommand(dependency.type, depName); const depVersionStr = versionDependenceCommandTypes[dependency.type]; + if (isLinuxDependence) { + getCommand = `${linuxCommand.info} ${depName}`; + } + let depVersion = ''; if (depName.includes(depVersionStr)) { const symbolRegx = new RegExp( @@ -261,10 +309,6 @@ export default class DependenceService { depVersion = _depVersion; } } - const isNodeDependence = dependency.type === DependenceTypes.nodejs; - const isLinuxDependence = dependency.type === DependenceTypes.linux; - const isPythonDependence = - dependency.type === DependenceTypes.python3; const depInfo = (await promiseExecSuccess(getCommand)) .replace(/\s{2,}/, ' ') .replace(/\s+$/, ''); @@ -273,7 +317,7 @@ export default class DependenceService { depInfo && ((isNodeDependence && depInfo.split(' ')?.[0] === depName) || (isLinuxDependence && - depInfo.toLocaleLowerCase().includes('installed')) || + linuxCommand.check(depInfo.toLocaleLowerCase())) || isPythonDependence) && (!depVersion || depInfo.includes(depVersion)) ) { diff --git a/back/services/system.ts b/back/services/system.ts index ecc2a732..97b500f1 100644 --- a/back/services/system.ts +++ b/back/services/system.ts @@ -37,6 +37,7 @@ import ScheduleService, { TaskCallbacks } from './schedule'; import SockService from './sock'; import os from 'os'; import dayjs from 'dayjs'; +import { updateLinuxMirrorFile } from '../config/util'; @Service() export default class SystemService { @@ -214,33 +215,11 @@ export default class SystemService { onEnd?: () => void, ) { const oDoc = await this.getSystemConfig(); - await this.updateAuthDb({ - ...oDoc, - info: { ...oDoc.info, ...info }, - }); - let defaultDomain = 'https://dl-cdn.alpinelinux.org'; - let targetDomain = 'https://dl-cdn.alpinelinux.org'; if (os.platform() !== 'linux') { return; } - const content = await fs.promises.readFile('/etc/apk/repositories', { - encoding: 'utf-8', - }); - const domainMatch = content.match(/(http.*)\/alpine\/.*/); - if (domainMatch) { - defaultDomain = domainMatch[1]; - } - if (info.linuxMirror) { - targetDomain = info.linuxMirror; - } - const command = `sed -i 's/${defaultDomain.replace( - /\//g, - '\\/', - )}/${targetDomain.replace( - /\//g, - '\\/', - )}/g' /etc/apk/repositories && apk update -f`; - + const command = await updateLinuxMirrorFile(info.linuxMirror || ''); + let hasError = false; this.scheduleService.runTask( command, { @@ -254,8 +233,15 @@ export default class SystemService { message: 'update linux mirror end', }); onEnd?.(); + if (!hasError) { + await this.updateAuthDb({ + ...oDoc, + info: { ...oDoc.info, ...info }, + }); + } }, onError: async (message: string) => { + hasError = true; this.sockService.sendMessage({ type: 'updateLinuxMirror', message }); }, onLog: async (message: string) => { diff --git a/docker/310.Dockerfile b/docker/310.Dockerfile index 6cbbc127..6bc02e53 100644 --- a/docker/310.Dockerfile +++ b/docker/310.Dockerfile @@ -22,7 +22,7 @@ ENV QL_DIR=/ql \ PS1="\u@\h:\w \$ " VOLUME /ql/data - + EXPOSE 5700 COPY --from=builder /usr/local/lib/node_modules/. /usr/local/lib/node_modules/ diff --git a/docker/310.Dockerfile.debian b/docker/310.Dockerfile.debian new file mode 100644 index 00000000..194a6faa --- /dev/null +++ b/docker/310.Dockerfile.debian @@ -0,0 +1,112 @@ +FROM node:22-slim AS nodebuilder + +FROM python:3.10-slim-bookworm AS builder +COPY package.json .npmrc pnpm-lock.yaml /tmp/build/ +COPY --from=nodebuilder /usr/local/bin/node /usr/local/bin/ +COPY --from=nodebuilder /usr/local/lib/node_modules/. /usr/local/lib/node_modules/ +RUN set -x && \ + ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ + apt-get update && \ + apt-get install --no-install-recommends -y libatomic1 && \ + npm i -g pnpm@8.3.1 && \ + cd /tmp/build && \ + pnpm install --prod + +FROM python:3.10-slim-bookworm + +ARG QL_MAINTAINER="whyour" +LABEL maintainer="${QL_MAINTAINER}" +ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git +ARG QL_BRANCH=develop +ARG PYTHON_SHORT_VERSION=3.10 + +ENV QL_DIR=/ql \ + QL_BRANCH=${QL_BRANCH} \ + LANG=C.UTF-8 \ + SHELL=/bin/bash \ + PS1="\u@\h:\w \$ " + +ARG QL_UID=5432 +ARG QL_GID=5432 +RUN groupadd -g ${QL_GID} qinglong && \ + useradd -m -u ${QL_UID} -g ${QL_GID} -s /bin/bash qinglong && \ + mkdir -p /home/qinglong/bin /home/qinglong/.ssh && \ + chmod 700 /home/qinglong/.ssh && \ + chown -R ${QL_UID}:${QL_GID} /home/qinglong + +ENV QL_USER=qinglong +ENV QL_HOME=/home/$QL_USER + +COPY --from=nodebuilder /usr/local/bin/node /usr/local/bin/ +COPY --from=nodebuilder /usr/local/lib/node_modules/. /usr/local/lib/node_modules/ + +RUN set -x && \ + ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ + apt-get update && \ + apt-get upgrade -y && \ + apt-get install --no-install-recommends -y git \ + curl \ + wget \ + tzdata \ + perl \ + openssl \ + openssh-client \ + jq \ + procps \ + netcat-openbsd \ + unzip \ + libatomic1 && \ + apt-get clean && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo "Asia/Shanghai" >/etc/timezone && \ + git config --global user.email "qinglong@users.noreply.github.com" && \ + git config --global user.name "qinglong" && \ + git config --global http.postBuffer 524288000 && \ + npm install -g pnpm@8.3.1 pm2 ts-node && \ + rm -rf /root/.cache && \ + rm -rf /root/.npm && \ + rm -rf /etc/apt/apt.conf.d/docker-clean && \ + ulimit -c 0 + +RUN mkdir -p ${QL_DIR} ${QL_DIR}/data && \ + chown -R ${QL_UID}:${QL_GID} ${QL_DIR} + +USER qinglong + +ARG SOURCE_COMMIT +RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} && \ + cd ${QL_DIR} && \ + cp -f .env.example .env && \ + chmod 777 ${QL_DIR}/shell/*.sh && \ + chmod 777 ${QL_DIR}/docker/*.sh && \ + git clone --depth=1 -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /tmp/static && \ + mkdir -p ${QL_DIR}/static && \ + cp -rf /tmp/static/* ${QL_DIR}/static && \ + rm -rf /tmp/static + +ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ + PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \ + PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \ + HOME=/root + +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ + NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules \ + PIP_CACHE_DIR=${PYTHON_HOME}/pip \ + PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages + +RUN pip3 install --prefix ${PYTHON_HOME} requests + +COPY --chown=qinglong:qinglong --from=builder /tmp/build/node_modules/. /ql/node_modules/ + +USER root + +WORKDIR ${QL_DIR} + +HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ + CMD curl -sf --noproxy '*' http://localhost:${QlPort:-5700}/api/health || exit 1 + +ENTRYPOINT ["./docker/docker-entrypoint.sh"] + +VOLUME /ql/data + +EXPOSE 5700 diff --git a/docker/Dockerfile b/docker/Dockerfile index 405260ac..352a6cb6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -22,7 +22,7 @@ ENV QL_DIR=/ql \ PS1="\u@\h:\w \$ " VOLUME /ql/data - + EXPOSE 5700 COPY --from=builder /usr/local/lib/node_modules/. /usr/local/lib/node_modules/ diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian new file mode 100644 index 00000000..45ba5104 --- /dev/null +++ b/docker/Dockerfile.debian @@ -0,0 +1,112 @@ +FROM node:22-slim AS nodebuilder + +FROM python:3.11-slim-bookworm AS builder +COPY package.json .npmrc pnpm-lock.yaml /tmp/build/ +COPY --from=nodebuilder /usr/local/bin/node /usr/local/bin/ +COPY --from=nodebuilder /usr/local/lib/node_modules/. /usr/local/lib/node_modules/ +RUN set -x && \ + ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ + apt-get update && \ + apt-get install --no-install-recommends -y libatomic1 && \ + npm i -g pnpm@8.3.1 && \ + cd /tmp/build && \ + pnpm install --prod + +FROM python:3.11-slim-bookworm + +ARG QL_MAINTAINER="whyour" +LABEL maintainer="${QL_MAINTAINER}" +ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git +ARG QL_BRANCH=develop +ARG PYTHON_SHORT_VERSION=3.11 + +ENV QL_DIR=/ql \ + QL_BRANCH=${QL_BRANCH} \ + LANG=C.UTF-8 \ + SHELL=/bin/bash \ + PS1="\u@\h:\w \$ " + +ARG QL_UID=5432 +ARG QL_GID=5432 +RUN groupadd -g ${QL_GID} qinglong && \ + useradd -m -u ${QL_UID} -g ${QL_GID} -s /bin/bash qinglong && \ + mkdir -p /home/qinglong/bin /home/qinglong/.ssh && \ + chmod 700 /home/qinglong/.ssh && \ + chown -R ${QL_UID}:${QL_GID} /home/qinglong + +ENV QL_USER=qinglong +ENV QL_HOME=/home/$QL_USER + +COPY --from=nodebuilder /usr/local/bin/node /usr/local/bin/ +COPY --from=nodebuilder /usr/local/lib/node_modules/. /usr/local/lib/node_modules/ + +RUN set -x && \ + ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ + ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx && \ + apt-get update && \ + apt-get upgrade -y && \ + apt-get install --no-install-recommends -y git \ + curl \ + wget \ + tzdata \ + perl \ + openssl \ + openssh-client \ + jq \ + procps \ + netcat-openbsd \ + unzip \ + libatomic1 && \ + apt-get clean && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo "Asia/Shanghai" >/etc/timezone && \ + git config --global user.email "qinglong@users.noreply.github.com" && \ + git config --global user.name "qinglong" && \ + git config --global http.postBuffer 524288000 && \ + npm install -g pnpm@8.3.1 pm2 ts-node && \ + rm -rf /root/.cache && \ + rm -rf /root/.npm && \ + rm -rf /etc/apt/apt.conf.d/docker-clean && \ + ulimit -c 0 + +RUN mkdir -p ${QL_DIR} ${QL_DIR}/data && \ + chown -R ${QL_UID}:${QL_GID} ${QL_DIR} + +USER qinglong +ARG SOURCE_COMMIT +RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} && \ + cd ${QL_DIR} && \ + cp -f .env.example .env && \ + chmod 777 ${QL_DIR}/shell/*.sh && \ + chmod 777 ${QL_DIR}/docker/*.sh && \ + git clone --depth=1 -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /tmp/static && \ + mkdir -p ${QL_DIR}/static && \ + cp -rf /tmp/static/* ${QL_DIR}/static && \ + rm -rf /tmp/static + +ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ + PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \ + PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \ + HOME=/root + +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ + NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules \ + PIP_CACHE_DIR=${PYTHON_HOME}/pip \ + PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages + +RUN pip3 install --prefix ${PYTHON_HOME} requests + +COPY --chown=qinglong:qinglong --from=builder /tmp/build/node_modules/. /ql/node_modules/ + +USER root + +WORKDIR ${QL_DIR} + +HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ + CMD curl -sf --noproxy '*' http://localhost:${QlPort:-5700}/api/health || exit 1 + +ENTRYPOINT ["./docker/docker-entrypoint.sh"] + +VOLUME /ql/data + +EXPOSE 5700 diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index b0693fe3..3015073a 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -15,9 +15,68 @@ log_with_style() { printf "\n[%s] [%7s] %s\n" "${timestamp}" "${level}" "${message}" } +# ============================================ +# 确保当前用户对 /ql 和 /ql/data 目录有写入权限 +# /ql/data 是 Docker Volume 挂载点,权限可能与 /ql 不同,需单独检测 +# ============================================ +ensure_ql_permissions() { + local current_uid + local current_gid + current_uid=$(id -u) + current_gid=$(id -g) + + if [ "$current_uid" -eq 0 ]; then + return 0 + fi + + # ---- 检查 /ql 目录 ---- + if ! mkdir -p "$QL_DIR/.tmp" 2>/dev/null; then + if chown -R "$current_uid:$current_gid" "$QL_DIR" 2>/dev/null; then + log_with_style "INFO" "已修正 /ql 目录权限: UID=$current_uid GID=$current_gid" + else + local ql_owner + ql_owner=$(stat -c '%u' "$QL_DIR" 2>/dev/null || stat -f '%u' "$QL_DIR" 2>/dev/null) + log_with_style "ERROR" "=============================================" + log_with_style "ERROR" " 权限错误:无法写入 /ql 目录" + log_with_style "ERROR" " 当前用户 UID: $current_uid" + log_with_style "ERROR" " /ql 目录所有者 UID: ${ql_owner:-未知}" + log_with_style "ERROR" "" + log_with_style "ERROR" " 解决方案:" + log_with_style "ERROR" " 1. 使用镜像内置用户: docker run --user ${ql_owner:-5432}:${ql_owner:-5432} ..." + log_with_style "ERROR" " 2. 使用 root 运行: 移除 --user 参数" + log_with_style "ERROR" " 3. 修正宿主机数据目录: chown -R $current_uid:$current_gid /path/to/ql/data" + log_with_style "ERROR" "=============================================" + exit 1 + fi + fi + rmdir "$QL_DIR/.tmp" 2>/dev/null || true + + # ---- 检查 /ql/data 目录(Volume 挂载点,不在用户数据卷内创建临时文件) ---- + if [ ! -w "$QL_DIR/data" ] || [ ! -x "$QL_DIR/data" ]; then + if chown "$current_uid:$current_gid" "$QL_DIR/data" 2>/dev/null; then + log_with_style "INFO" "已修正 /ql/data 目录权限: UID=$current_uid GID=$current_gid" + if [ ! -w "$QL_DIR/data" ] || [ ! -x "$QL_DIR/data" ]; then + log_with_style "ERROR" "修正后仍无法写入 /ql/data,请检查挂载的数据卷权限" + log_with_style "ERROR" "确保宿主机目录: chown -R $current_uid:$current_gid /your/data" + exit 1 + fi + else + local data_owner + data_owner=$(stat -c '%u' "$QL_DIR/data" 2>/dev/null || stat -f '%u' "$QL_DIR/data" 2>/dev/null) + log_with_style "ERROR" "=============================================" + log_with_style "ERROR" " 权限错误:无法写入 /ql/data (Volume 挂载点)" + log_with_style "ERROR" " 当前用户 UID: $current_uid" + log_with_style "ERROR" " /ql/data 所有者 UID: ${data_owner:-未知}" + log_with_style "ERROR" "" + log_with_style "ERROR" " 请修正宿主机数据目录权限:" + log_with_style "ERROR" " chown -R $current_uid:$current_gid /your/ql/data" + log_with_style "ERROR" "=============================================" + exit 1 + fi + fi +} + # Fix DNS resolution issues in Alpine Linux -# Alpine uses musl libc which has known DNS resolver issues with certain domains -# Adding ndots:0 prevents unnecessary search domain appending if [ -f /etc/alpine-release ]; then if ! grep -q "^options ndots:0" /etc/resolv.conf 2>/dev/null; then echo "options ndots:0" >> /etc/resolv.conf @@ -35,6 +94,15 @@ if ! grep -qE '^::1[[:space:]]+.*localhost' /etc/hosts 2>/dev/null; then log_with_style "INFO" "🔧 0. 已添加 IPv6 localhost 解析" fi +# 在一切操作之前检查目录权限 +ensure_ql_permissions + +# Dockerfile 中 HOME=/root,非 root 用户无法写入 +# 将 HOME 修正为临时目录,PM2/npm/pip 等工具的运行时数据无需持久化 +if [ ! -w "$HOME" ]; then + export HOME="$QL_DIR/.tmp" +fi + log_with_style "INFO" "🚀 1. 检测配置文件..." load_ql_envs export_ql_envs @@ -60,6 +128,19 @@ fi log_with_style "SUCCESS" "🎉 容器启动成功!" -crond -f >/dev/null +# 自动检测调度模式:有 crond 二进制 → system 模式,否则 node 模式 +if [ -z "$QL_SCHEDULER" ]; then + if command -v crond &>/dev/null; then + export QL_SCHEDULER="system" + else + export QL_SCHEDULER="node" + fi +fi + +if [ "$QL_SCHEDULER" = "system" ]; then + crond -f > /dev/null +else + tail -f /dev/null +fi exec "$@" diff --git a/package.json b/package.json index eb0b5af4..3e942705 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,17 @@ { - "private": true, + "name": "@whyour/qinglong", "packageManager": "pnpm@8.3.1", + "version": "2.20.2-3", + "description": "Timed task management platform supporting Python3, JavaScript, Shell, Typescript", + "repository": { + "type": "git", + "url": "https://github.com/whyour/qinglong.git" + }, + "author": "whyour", + "license": "Apache License 2.0", + "bugs": { + "url": "https://github.com/whyour/qinglong/issues" + }, "scripts": { "start": "concurrently -n w: npm:start:*", "start:back": "nodemon ./back/app.ts", @@ -25,6 +36,11 @@ "prettier --parser=typescript --write" ] }, + "bin": { + "ql": "shell/update.sh", + "task": "shell/task.sh", + "qinglong": "shell/start.sh" + }, "pnpm": { "peerDependencyRules": { "ignoreMissing": [ diff --git a/shell/bot.sh b/shell/bot.sh index f30cad26..524a44e9 100755 --- a/shell/bot.sh +++ b/shell/bot.sh @@ -9,7 +9,22 @@ else fi echo -e "\n1、安装bot依赖...\n" -apk --no-cache add -f zlib-dev gcc jpeg-dev python3-dev musl-dev freetype-dev +os_name="${QL_OS_TYPE:-}" +if [ -z "$os_name" ]; then + os_name=$(source /etc/os-release && echo "$ID") +fi +case "$os_name" in + alpine) + apk --no-cache add -f zlib-dev gcc jpeg-dev python3-dev musl-dev freetype-dev + ;; + debian|ubuntu) + apt-get install -y gcc python3-dev musl-dev zlib1g-dev libjpeg-dev libfreetype-dev + ;; + *) + echo -e "暂不支持此系统 $os_name" + exit 1 + ;; +esac echo -e "\nbot依赖安装成功...\n" echo -e "2、下载bot所需文件...\n" diff --git a/shell/pub.sh b/shell/pub.sh index 1d126bc6..1d5794f4 100755 --- a/shell/pub.sh +++ b/shell/pub.sh @@ -1,26 +1,26 @@ #!/usr/bin/env bash echo -e "开始发布" -echo -e "切换master分支" -git branch -D master -git checkout -b master -git push --set-upstream origin master -f +echo -e "切换 debian 分支" +git branch -D debian +git checkout -b debian +git push --set-upstream origin debian -f echo -e "更新cdn文件" ts-node-transpile-only sample/tool.ts string=$(cat version.yaml | grep "version" | egrep "[^ ]*" -o | egrep "\d\.*") version="v$string" -echo -e "当前版本$version" +echo -e "当前版本$version-debian" echo -e "删除已经存在的本地tag" -git tag -d "$version" &>/dev/null +git tag -d "$version-debian" &>/dev/null echo -e "删除已经存在的远程tag" -git push origin :refs/tags/$version &>/dev/null +git push origin :refs/tags/$version-debian &>/dev/null echo -e "创建新tag" -git tag -a "$version" -m "release $version" +git tag -a "$version-debian" -m "release $version-debian" echo -e "提交tag" git push --tags diff --git a/shell/start.sh b/shell/start.sh new file mode 100644 index 00000000..1f015f2b --- /dev/null +++ b/shell/start.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +# 前置依赖 nodejs、npm、python3 +set -e +set -x + +if [[ ! $QL_DIR ]]; then + npm_dir=$(npm root -g) + pnpm_dir=$(pnpm root -g) + if [[ -d "$npm_dir/@whyour/qinglong" ]]; then + QL_DIR="$npm_dir/@whyour/qinglong" + elif [[ -d "$pnpm_dir/@whyour/qinglong" ]]; then + QL_DIR="$pnpm_dir/@whyour/qinglong" + else + echo -e "未找到 qinglong 模块,请先执行 npm i -g @whyour/qinglong 安装" + fi + + if [[ $QL_DIR ]]; then + echo -e "请先手动设置 export QL_DIR=$QL_DIR,环境变量,并手动添加到系统环境变量,然后再次执行命令 qinglong 启动服务" + fi + + exit 1 +fi + +if [[ ! $QL_DATA_DIR ]]; then + echo -e "请先手动设置数据存储目录 export QL_DATA_DIR 环境变量,目录必须以斜杠开头的绝对路径,并且以 /data 结尾,例如 /ql/data 并手动添加到系统环境变量" + exit 1 +fi + +if [[ $QL_DATA_DIR != */data ]]; then + echo -e "QL_DATA_DIR 必须以 /data 结尾,例如 /ql/data,如果有历史数据,请新建 data 目录,把历史数据放到 data 目录中" + exit 1 +fi + +command="$1" + +if [[ $command != "reload" ]]; then + # 安装依赖 + os_name="${QL_OS_TYPE:-}" + if [ -z "$os_name" ]; then + os_name=$(source /etc/os-release && echo "$ID") + fi + + case "$os_name" in + alpine) + apk update + apk add -f bash \ + coreutils \ + git \ + curl \ + wget \ + tzdata \ + perl \ + openssl \ + jq \ + nginx \ + openssh \ + procps \ + netcat-openbsd + ;; + debian|ubuntu) + apt-get update + apt-get install -y git curl wget tzdata perl openssl jq nginx procps netcat-openbsd openssh-client + ;; + *) + echo -e "暂不支持此系统部署 $os_name" + exit 1 + ;; + esac + + npm install -g pnpm@8.3.1 pm2 ts-node +fi + +export PYTHON_SHORT_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') +export PNPM_HOME=${QL_DIR}/data/dep_cache/node +export PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 +export PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 + +export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin +export NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules +export PIP_CACHE_DIR=${PYTHON_HOME}/pip +export PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages + +if [[ $command != "reload" ]]; then + pip3 install --prefix ${PYTHON_HOME} requests +fi + +cd ${QL_DIR} +cp -f .env.example .env +chmod 777 ${QL_DIR}/shell/*.sh + +. ${QL_DIR}/shell/share.sh +. ${QL_DIR}/shell/env.sh + +log_with_style() { + local level="$1" + local message="$2" + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + printf "\n[%s] [%7s] %s\n" "${timestamp}" "${level}" "${message}" +} + +log_with_style "INFO" "🚀 1. 检测配置文件..." +import_config "$@" +make_dir /etc/nginx/conf.d +make_dir /run/nginx +fix_config + +pm2 l &>/dev/null + +log_with_style "INFO" "🔄 2. 启动 nginx..." +nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf + +log_with_style "INFO" "⚙️ 3. 启动 pm2 服务..." +reload_pm2 + +if [[ $command != "reload" ]]; then + if [[ $AutoStartBot == true ]]; then + log_with_style "INFO" "🤖 4. 启动 bot..." + nohup ql bot >$dir_log/bot.log 2>&1 & + fi + + if [[ $EnableExtraShell == true ]]; then + log_with_style "INFO" "🛠️ 5. 执行自定义脚本..." + nohup ql extra >$dir_log/extra.log 2>&1 & + fi + + pm2 startup + pm2 save +fi + +log_with_style "SUCCESS" "🎉 启动成功!" diff --git a/src/pages/diff/index.tsx b/src/pages/diff/index.tsx index 5897cb2e..3b42d91e 100644 --- a/src/pages/diff/index.tsx +++ b/src/pages/diff/index.tsx @@ -64,7 +64,7 @@ const Diff = () => { const getFiles = () => { setLoading(true); request - .get(`${config.apiPrefix}configs/sample`) + .get(`${config.apiPrefix}configs/samples`) .then(({ code, data }) => { if (code === 200) { setFiles(data); diff --git a/src/pages/setting/dependence.tsx b/src/pages/setting/dependence.tsx index 536c7861..9a0974aa 100644 --- a/src/pages/setting/dependence.tsx +++ b/src/pages/setting/dependence.tsx @@ -215,12 +215,12 @@ const Dependence = () => { { setSystemConfig({ From abad29cbf990484ca86e62394a428f2529915b61 Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 30 May 2026 23:55:57 +0800 Subject: [PATCH 21/24] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=9D=9E=20root=20debi?= =?UTF-8?q?an=20dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-docker-image.yml | 7 +- README-en.md | 11 + README.md | 11 + back/config/const.ts | 12 +- back/config/util.ts | 10 +- docker/Dockerfile | 4 + docker/{310.Dockerfile => Dockerfile.310} | 4 + docker/Dockerfile.debian | 15 +- ...Dockerfile.debian => Dockerfile.debian310} | 15 +- docker/docker-entrypoint.sh | 11 +- sample/notify.js | 4 +- sample/notify.py | 4 +- sample/notify.py.save | 1010 ----------------- shell/bot.sh | 11 +- shell/start.sh | 14 +- 15 files changed, 97 insertions(+), 1046 deletions(-) rename docker/{310.Dockerfile => Dockerfile.310} (93%) rename docker/{310.Dockerfile.debian => Dockerfile.debian310} (89%) delete mode 100644 sample/notify.py.save diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 420dc9f4..5630916c 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -11,6 +11,9 @@ on: - "v*" workflow_dispatch: +permissions: + contents: read + jobs: code_gitlab: runs-on: ubuntu-latest @@ -340,7 +343,7 @@ jobs: network: host platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/386 context: . - file: ./docker/310.Dockerfile + file: ./docker/Dockerfile.310 push: true tags: | whyour/qinglong:python3.10 @@ -403,7 +406,7 @@ jobs: network: host platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x context: . - file: ./docker/310.Dockerfile.debian + file: ./docker/Dockerfile.debian310 push: true tags: | whyour/qinglong:debian-python3.10 diff --git a/README-en.md b/README-en.md index e0e1eb59..4570d6d5 100644 --- a/README-en.md +++ b/README-en.md @@ -48,6 +48,17 @@ docker pull whyour/qinglong:latest docker pull whyour/qinglong:debian ``` +When running the `debian` image as a non-root user, specify `--user qinglong`: + +```bash +docker run -d \ + -v /path/to/ql/data:/ql/data \ + -p 5700:5700 \ + --user qinglong \ + --name qinglong \ + whyour/qinglong:debian +``` + ### npm The npm version supports `debian/ubuntu/alpine` systems and requires `node/npm/python3/pip3/pnpm` to be installed. diff --git a/README.md b/README.md index fadc841b..9b7cdd55 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,17 @@ docker pull whyour/qinglong:latest docker pull whyour/qinglong:debian ``` +使用 `debian` 镜像以非 root 用户运行时,需指定 `--user qinglong`: + +```bash +docker run -d \ + -v /path/to/ql/data:/ql/data \ + -p 5700:5700 \ + --user qinglong \ + --name qinglong \ + whyour/qinglong:debian +``` + ### npm npm 版本支持 `debian/ubuntu/alpine` 系统,需要自行安装 `node/npm/python3/pip3/pnpm` diff --git a/back/config/const.ts b/back/config/const.ts index 74be9a93..59350a98 100644 --- a/back/config/const.ts +++ b/back/config/const.ts @@ -60,17 +60,17 @@ export const LINUX_DEPENDENCE_COMMAND: Record< } > = { Debian: { - install: 'apt-get install -y', - uninstall: 'apt-get remove -y', - info: 'dpkg-query -s', + install: 'sudo apt-get install -y', + uninstall: 'sudo apt-get remove -y', + info: 'sudo dpkg-query -s', check(info: string) { return info.includes('install ok installed'); }, }, Ubuntu: { - install: 'apt-get install -y', - uninstall: 'apt-get remove -y', - info: 'dpkg-query -s', + install: 'sudo apt-get install -y', + uninstall: 'sudo apt-get remove -y', + info: 'sudo dpkg-query -s', check(info: string) { return info.includes('install ok installed'); }, diff --git a/back/config/util.ts b/back/config/util.ts index 0f050193..1b994e31 100644 --- a/back/config/util.ts +++ b/back/config/util.ts @@ -581,7 +581,7 @@ except: ''')"`, [DependenceTypes.linux]: getOsTypeSync() === 'Alpine' ? `apk info -es ${name}` - : `dpkg-query -s ${name}`, + : `sudo dpkg-query -s ${name}`, }; return baseCommands[type]; @@ -594,7 +594,7 @@ export function getInstallCommand(type: DependenceTypes, name: string): string { 'pip3 install --disable-pip-version-check --root-user-action=ignore', [DependenceTypes.linux]: getOsTypeSync() === 'Alpine' ? 'apk add --no-check-certificate' - : 'apt-get install -y', + : 'sudo apt-get install -y', }; let command = baseCommands[type]; @@ -616,7 +616,7 @@ export function getUninstallCommand( 'pip3 uninstall --disable-pip-version-check --root-user-action=ignore -y', [DependenceTypes.linux]: getOsTypeSync() === 'Alpine' ? 'apk del' - : 'apt-get remove -y', + : 'sudo apt-get remove -y', }; return `${baseCommands[type]} ${name.trim()}`; @@ -740,7 +740,7 @@ async function _updateLinuxMirror( currentDomainWithScheme, mirrorDomainWithScheme || 'http://deb.debian.org', ); - return 'apt-get update'; + return 'sudo apt-get update'; } else { throw Error(`Current mirror domain not found.`); } @@ -753,7 +753,7 @@ async function _updateLinuxMirror( currentDomainWithScheme, mirrorDomainWithScheme || 'http://archive.ubuntu.com', ); - return 'apt-get update'; + return 'sudo apt-get update'; } else { throw Error(`Current mirror domain not found.`); } diff --git a/docker/Dockerfile b/docker/Dockerfile index 352a6cb6..b874649f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -81,6 +81,10 @@ RUN pip3 install --prefix ${PYTHON_HOME} requests COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/ +RUN ln -sf ${QL_DIR}/shell/task.sh /usr/local/bin/task \ + && ln -sf ${QL_DIR}/shell/update.sh /usr/local/bin/ql \ + && chmod +x /usr/local/bin/task /usr/local/bin/ql + WORKDIR ${QL_DIR} HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ diff --git a/docker/310.Dockerfile b/docker/Dockerfile.310 similarity index 93% rename from docker/310.Dockerfile rename to docker/Dockerfile.310 index 6bc02e53..6312448b 100644 --- a/docker/310.Dockerfile +++ b/docker/Dockerfile.310 @@ -81,6 +81,10 @@ RUN pip3 install --prefix ${PYTHON_HOME} requests COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/ +RUN ln -sf ${QL_DIR}/shell/task.sh /usr/local/bin/task \ + && ln -sf ${QL_DIR}/shell/update.sh /usr/local/bin/ql \ + && chmod +x /usr/local/bin/task /usr/local/bin/ql + WORKDIR ${QL_DIR} HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 45ba5104..e4d791f3 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -32,7 +32,9 @@ RUN groupadd -g ${QL_GID} qinglong && \ useradd -m -u ${QL_UID} -g ${QL_GID} -s /bin/bash qinglong && \ mkdir -p /home/qinglong/bin /home/qinglong/.ssh && \ chmod 700 /home/qinglong/.ssh && \ - chown -R ${QL_UID}:${QL_GID} /home/qinglong + chown -R ${QL_UID}:${QL_GID} /home/qinglong && \ + mkdir -p /etc/sudoers.d && \ + echo 'qinglong ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/qinglong ENV QL_USER=qinglong ENV QL_HOME=/home/$QL_USER @@ -55,6 +57,7 @@ RUN set -x && \ jq \ procps \ netcat-openbsd \ + sudo \ unzip \ libatomic1 && \ apt-get clean && \ @@ -69,7 +72,7 @@ RUN set -x && \ rm -rf /etc/apt/apt.conf.d/docker-clean && \ ulimit -c 0 -RUN mkdir -p ${QL_DIR} ${QL_DIR}/data && \ +RUN mkdir -p ${QL_DIR} && \ chown -R ${QL_UID}:${QL_GID} ${QL_DIR} USER qinglong @@ -87,7 +90,7 @@ RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} && \ ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \ PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \ - HOME=/root + HOME=/home/qinglong ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules \ @@ -100,6 +103,10 @@ COPY --chown=qinglong:qinglong --from=builder /tmp/build/node_modules/. /ql/node USER root +RUN ln -sf ${QL_DIR}/shell/task.sh /usr/local/bin/task \ + && ln -sf ${QL_DIR}/shell/update.sh /usr/local/bin/ql \ + && chmod +x /usr/local/bin/task /usr/local/bin/ql + WORKDIR ${QL_DIR} HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ @@ -108,5 +115,5 @@ HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ ENTRYPOINT ["./docker/docker-entrypoint.sh"] VOLUME /ql/data - + EXPOSE 5700 diff --git a/docker/310.Dockerfile.debian b/docker/Dockerfile.debian310 similarity index 89% rename from docker/310.Dockerfile.debian rename to docker/Dockerfile.debian310 index 194a6faa..bc110ed7 100644 --- a/docker/310.Dockerfile.debian +++ b/docker/Dockerfile.debian310 @@ -32,7 +32,9 @@ RUN groupadd -g ${QL_GID} qinglong && \ useradd -m -u ${QL_UID} -g ${QL_GID} -s /bin/bash qinglong && \ mkdir -p /home/qinglong/bin /home/qinglong/.ssh && \ chmod 700 /home/qinglong/.ssh && \ - chown -R ${QL_UID}:${QL_GID} /home/qinglong + chown -R ${QL_UID}:${QL_GID} /home/qinglong && \ + mkdir -p /etc/sudoers.d && \ + echo 'qinglong ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/qinglong ENV QL_USER=qinglong ENV QL_HOME=/home/$QL_USER @@ -54,6 +56,7 @@ RUN set -x && \ jq \ procps \ netcat-openbsd \ + sudo \ unzip \ libatomic1 && \ apt-get clean && \ @@ -68,7 +71,7 @@ RUN set -x && \ rm -rf /etc/apt/apt.conf.d/docker-clean && \ ulimit -c 0 -RUN mkdir -p ${QL_DIR} ${QL_DIR}/data && \ +RUN mkdir -p ${QL_DIR} && \ chown -R ${QL_UID}:${QL_GID} ${QL_DIR} USER qinglong @@ -87,7 +90,7 @@ RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} && \ ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \ PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \ - HOME=/root + HOME=/home/qinglong ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules \ @@ -100,6 +103,10 @@ COPY --chown=qinglong:qinglong --from=builder /tmp/build/node_modules/. /ql/node USER root +RUN ln -sf ${QL_DIR}/shell/task.sh /usr/local/bin/task \ + && ln -sf ${QL_DIR}/shell/update.sh /usr/local/bin/ql \ + && chmod +x /usr/local/bin/task /usr/local/bin/ql + WORKDIR ${QL_DIR} HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ @@ -108,5 +115,5 @@ HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ ENTRYPOINT ["./docker/docker-entrypoint.sh"] VOLUME /ql/data - + EXPOSE 5700 diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 3015073a..978a92cd 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -94,15 +94,16 @@ if ! grep -qE '^::1[[:space:]]+.*localhost' /etc/hosts 2>/dev/null; then log_with_style "INFO" "🔧 0. 已添加 IPv6 localhost 解析" fi -# 在一切操作之前检查目录权限 -ensure_ql_permissions - -# Dockerfile 中 HOME=/root,非 root 用户无法写入 -# 将 HOME 修正为临时目录,PM2/npm/pip 等工具的运行时数据无需持久化 +# 自定义用户(非 qinglong/root)可能 HOME 为空或不可写 +# 修正 HOME 确保 npm/pip/pm2 等工具有可用的缓存目录 if [ ! -w "$HOME" ]; then + mkdir -p "$QL_DIR/.tmp" export HOME="$QL_DIR/.tmp" fi +# 在一切操作之前检查目录权限 +ensure_ql_permissions + log_with_style "INFO" "🚀 1. 检测配置文件..." load_ql_envs export_ql_envs diff --git a/sample/notify.js b/sample/notify.js index c0f3db43..49d5fdc8 100644 --- a/sample/notify.js +++ b/sample/notify.js @@ -108,8 +108,8 @@ const push_config = { QYWX_KEY: '', // 企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa - TG_BOT_TOKEN: '', // tg 机器人的 TG_BOT_TOKEN,例:1407203283:AAG9rt-6RDaaX0HBLZQq0laNOh898iFYaRQ - TG_USER_ID: '', // tg 机器人的 TG_USER_ID,例:1434078534 + TG_BOT_TOKEN: '', // tg 机器人的 TG_BOT_TOKEN,例:1234567890:ABCdefGHIjklMNOpqrsTUVwxyz + TG_USER_ID: '', // tg 机器人的 TG_USER_ID,例:1234567890 TG_API_HOST: 'https://api.telegram.org', // tg 代理 api TG_PROXY_AUTH: '', // tg 代理认证参数 TG_PROXY_HOST: '', // tg 机器人的 TG_PROXY_HOST diff --git a/sample/notify.py b/sample/notify.py index da8bfdcf..74c9bd70 100644 --- a/sample/notify.py +++ b/sample/notify.py @@ -94,8 +94,8 @@ push_config = { 'QYWX_KEY': '', # 企业微信机器人 - 'TG_BOT_TOKEN': '', # tg 机器人的 TG_BOT_TOKEN,例:1407203283:AAG9rt-6RDaaX0HBLZQq0laNOh898iFYaRQ - 'TG_USER_ID': '', # tg 机器人的 TG_USER_ID,例:1434078534 + 'TG_BOT_TOKEN': '', # tg 机器人的 TG_BOT_TOKEN,例:1234567890:ABCdefGHIjklMNOpqrsTUVwxyz + 'TG_USER_ID': '', # tg 机器人的 TG_USER_ID,例:1234567890 'TG_API_HOST': '', # tg 代理 api 'TG_PROXY_AUTH': '', # tg 代理认证参数 'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST diff --git a/sample/notify.py.save b/sample/notify.py.save deleted file mode 100644 index 0bfe668a..00000000 --- a/sample/notify.py.save +++ /dev/null @@ -1,1010 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding:utf-8 _*_ -import base64 -import hashlib -import hmac -import json -import os -import re -import threading -import time -import urllib.parse -import smtplib -from email.mime.text import MIMEText -from email.header import Header -from email.utils import formataddr - -import requests - -# 原先的 print 函数和主线程的锁 -_print = print -mutex = threading.Lock() - - -# 定义新的 print 函数 -def print(text, *args, **kw): - """ - 使输出有序进行,不出现多线程同一时间输出导致错乱的问题。 - """ - with mutex: - _print(text, *args, **kw) - - -# 通知服务 -# fmt: off -push_config = { - 'HITOKOTO': True, # 启用一言(随机句子) - - 'BARK_PUSH': '', # bark IP 或设备码,例:https://api.day.app/DxHcxxxxxRxxxxxxcm/ - 'BARK_ARCHIVE': '', # bark 推送是否存档 - 'BARK_GROUP': '', # bark 推送分组 - 'BARK_SOUND': '', # bark 推送声音 - 'BARK_ICON': '', # bark 推送图标 - 'BARK_LEVEL': '', # bark 推送时效性 - 'BARK_URL': '', # bark 推送跳转URL - - 'CONSOLE': False, # 控制台输出 - - 'DD_BOT_SECRET': '', # 钉钉机器人的 DD_BOT_SECRET - 'DD_BOT_TOKEN': '', # 钉钉机器人的 DD_BOT_TOKEN - - 'FSKEY': '', # 飞书机器人的 FSKEY - - 'GOBOT_URL': '', # go-cqhttp - # 推送到个人QQ:http://127.0.0.1/send_private_msg - # 群:http://127.0.0.1/send_group_msg - 'GOBOT_QQ': '', # go-cqhttp 的推送群或用户 - # GOBOT_URL 设置 /send_private_msg 时填入 user_id=个人QQ - # /send_group_msg 时填入 group_id=QQ群 - 'GOBOT_TOKEN': '', # go-cqhttp 的 access_token - - 'GOTIFY_URL': '', # gotify地址,如https://push.example.de:8080 - 'GOTIFY_TOKEN': '', # gotify的消息应用token - 'GOTIFY_PRIORITY': 0, # 推送消息优先级,默认为0 - - 'IGOT_PUSH_KEY': '', # iGot 聚合推送的 IGOT_PUSH_KEY - - 'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版 - - 'DEER_KEY': '', # PushDeer 的 PUSHDEER_KEY - 'DEER_URL': '', # PushDeer 的 PUSHDEER_URL - - 'CHAT_URL': '', # synology chat url - 'CHAT_TOKEN': '', # synology chat token - - 'PUSH_PLUS_TOKEN': '', # push+ 微信推送的用户令牌 - 'PUSH_PLUS_USER': '', # push+ 微信推送的群组编码 - - 'WE_PLUS_BOT_TOKEN': '', # 微加机器人的用户令牌 - 'WE_PLUS_BOT_RECEIVER': '', # 微加机器人的消息接收者 - 'WE_PLUS_BOT_VERSION': 'pro', # 微加机器人的调用版本 - - 'QMSG_KEY': '', # qmsg 酱的 QMSG_KEY - 'QMSG_TYPE': '', # qmsg 酱的 QMSG_TYPE - - 'QYWX_ORIGIN': '', # 企业微信代理地址 - - 'QYWX_AM': '', # 企业微信应用 - - 'QYWX_KEY': '', # 企业微信机器人 - - 'TG_BOT_TOKEN': '', # tg 机器人的 TG_BOT_TOKEN,例:1407203283:AAG9rt-6RDaaX0HBLZQq0laNOh898iFYaRQ - 'TG_USER_ID': '', # tg 机器人的 TG_USER_ID,例:1434078534 - 'TG_API_HOST': '', # tg 代理 api - 'TG_PROXY_AUTH': '', # tg 代理认证参数 - 'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST - 'TG_PROXY_PORT': '', # tg 机器人的 TG_PROXY_PORT - - 'AIBOTK_KEY': '', # 智能微秘书 个人中心的apikey 文档地址:http://wechat.aibotk.com/docs/about - 'AIBOTK_TYPE': '', # 智能微秘书 发送目标 room 或 contact - 'AIBOTK_NAME': '', # 智能微秘书 发送群名 或者好友昵称和type要对应好 - - 'SMTP_SERVER': '', # SMTP 发送邮件服务器,形如 smtp.exmail.qq.com:465 - 'SMTP_SSL': 'false', # SMTP 发送邮件服务器是否使用 SSL,填写 true 或 false - 'SMTP_EMAIL': '', # SMTP 收发件邮箱,通知将会由自己发给自己 - 'SMTP_PASSWORD': '', # SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定 - 'SMTP_NAME': '', # SMTP 收发件人姓名,可随意填写 - - 'PUSHME_KEY': '', # PushMe 的 PUSHME_KEY - 'PUSHME_URL': '', # PushMe 的 PUSHME_URL - - 'CHRONOCAT_QQ': '', # qq号 - 'CHRONOCAT_TOKEN': '', # CHRONOCAT 的token - 'CHRONOCAT_URL': '', # CHRONOCAT的url地址 - - 'WEBHOOK_URL': '', # 自定义通知 请求地址 - 'WEBHOOK_BODY': '', # 自定义通知 请求体 - 'WEBHOOK_HEADERS': '', # 自定义通知 请求头 - 'WEBHOOK_METHOD': '', # 自定义通知 请求方法 - 'WEBHOOK_CONTENT_TYPE': '' # 自定义通知 content-type - - 'NTFY_URL': '', # ntfy地址,如https://ntfy.sh - 'NTFY_TOPIC': '', # ntfy的消息应用topic - 'NTFY_PRIORITY': '3', # 推送消息优先级,默认为3 -} -# fmt: on - -for k in push_config: - if os.getenv(k): - v = os.getenv(k) - push_config[k] = v - - -def bark(title: str, content: str) -> None: - """ - 使用 bark 推送消息。 - """ - if not push_config.get("BARK_PUSH"): - print("bark 服务的 BARK_PUSH 未设置!!\n取消推送") - return - print("bark 服务启动") - - if push_config.get("BARK_PUSH").startswith("http"): - url = f'{push_config.get("BARK_PUSH")}' - else: - url = f'https://api.day.app/{push_config.get("BARK_PUSH")}' - - bark_params = { - "BARK_ARCHIVE": "isArchive", - "BARK_GROUP": "group", - "BARK_SOUND": "sound", - "BARK_ICON": "icon", - "BARK_LEVEL": "level", - "BARK_URL": "url", - } - data = { - "title": title, - "body": content, - } - for pair in filter( - lambda pairs: pairs[0].startswith("BARK_") - and pairs[0] != "BARK_PUSH" - and pairs[1] - and bark_params.get(pairs[0]), - push_config.items(), - ): - data[bark_params.get(pair[0])] = pair[1] - headers = {"Content-Type": "application/json;charset=utf-8"} - response = requests.post( - url=url, data=json.dumps(data), headers=headers, timeout=15 - ).json() - - if response["code"] == 200: - print("bark 推送成功!") - else: - print("bark 推送失败!") - - -def console(title: str, content: str) -> None: - """ - 使用 控制台 推送消息。 - """ - print(f"{title}\n\n{content}") - - -def dingding_bot(title: str, content: str) -> None: - """ - 使用 钉钉机器人 推送消息。 - """ - if not push_config.get("DD_BOT_SECRET") or not push_config.get("DD_BOT_TOKEN"): - print("钉钉机器人 服务的 DD_BOT_SECRET 或者 DD_BOT_TOKEN 未设置!!\n取消推送") - return - print("钉钉机器人 服务启动") - - timestamp = str(round(time.time() * 1000)) - secret_enc = push_config.get("DD_BOT_SECRET").encode("utf-8") - string_to_sign = "{}\n{}".format(timestamp, push_config.get("DD_BOT_SECRET")) - string_to_sign_enc = string_to_sign.encode("utf-8") - hmac_code = hmac.new( - secret_enc, string_to_sign_enc, digestmod=hashlib.sha256 - ).digest() - sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) - url = f'https://oapi.dingtalk.com/robot/send?access_token={push_config.get("DD_BOT_TOKEN")}×tamp={timestamp}&sign={sign}' - headers = {"Content-Type": "application/json;charset=utf-8"} - data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}} - response = requests.post( - url=url, data=json.dumps(data), headers=headers, timeout=15 - ).json() - - if not response["errcode"]: - print("钉钉机器人 推送成功!") - else: - print("钉钉机器人 推送失败!") - - -def feishu_bot(title: str, content: str) -> None: - """ - 使用 飞书机器人 推送消息。 - """ - if not push_config.get("FSKEY"): - print("飞书 服务的 FSKEY 未设置!!\n取消推送") - return - print("飞书 服务启动") - - url = f'https://open.feishu.cn/open-apis/bot/v2/hook/{push_config.get("FSKEY")}' - data = {"msg_type": "text", "content": {"text": f"{title}\n\n{content}"}} - response = requests.post(url, data=json.dumps(data)).json() - - if response.get("StatusCode") == 0 or response.get("code") == 0: - print("飞书 推送成功!") - else: - print("飞书 推送失败!错误信息如下:\n", response) - - -def go_cqhttp(title: str, content: str) -> None: - """ - 使用 go_cqhttp 推送消息。 - """ - if not push_config.get("GOBOT_URL") or not push_config.get("GOBOT_QQ"): - print("go-cqhttp 服务的 GOBOT_URL 或 GOBOT_QQ 未设置!!\n取消推送") - return - print("go-cqhttp 服务启动") - - url = f'{push_config.get("GOBOT_URL")}?access_token={push_config.get("GOBOT_TOKEN")}&{push_config.get("GOBOT_QQ")}&message=标题:{title}\n内容:{content}' - response = requests.get(url).json() - - if response["status"] == "ok": - print("go-cqhttp 推送成功!") - else: - print("go-cqhttp 推送失败!") - - -def gotify(title: str, content: str) -> None: - """ - 使用 gotify 推送消息。 - """ - if not push_config.get("GOTIFY_URL") or not push_config.get("GOTIFY_TOKEN"): - print("gotify 服务的 GOTIFY_URL 或 GOTIFY_TOKEN 未设置!!\n取消推送") - return - print("gotify 服务启动") - - url = f'{push_config.get("GOTIFY_URL")}/message?token={push_config.get("GOTIFY_TOKEN")}' - data = { - "title": title, - "message": content, - "priority": push_config.get("GOTIFY_PRIORITY"), - } - response = requests.post(url, data=data).json() - - if response.get("id"): - print("gotify 推送成功!") - else: - print("gotify 推送失败!") - - -def iGot(title: str, content: str) -> None: - """ - 使用 iGot 推送消息。 - """ - if not push_config.get("IGOT_PUSH_KEY"): - print("iGot 服务的 IGOT_PUSH_KEY 未设置!!\n取消推送") - return - print("iGot 服务启动") - - url = f'https://push.hellyw.com/{push_config.get("IGOT_PUSH_KEY")}' - data = {"title": title, "content": content} - headers = {"Content-Type": "application/x-www-form-urlencoded"} - response = requests.post(url, data=data, headers=headers).json() - - if response["ret"] == 0: - print("iGot 推送成功!") - else: - print(f'iGot 推送失败!{response["errMsg"]}') - - -def serverJ(title: str, content: str) -> None: - """ - 通过 serverJ 推送消息。 - """ - if not push_config.get("PUSH_KEY"): - print("serverJ 服务的 PUSH_KEY 未设置!!\n取消推送") - return - print("serverJ 服务启动") - - data = {"text": title, "desp": content.replace("\n", "\n\n")} - if push_config.get("PUSH_KEY").startswith("sctp"): - url = f'https://{push_config.get("PUSH_KEY")}.push.ft07.com/send' - else: - url = f'https://sctapi.ftqq.com/{push_config.get("PUSH_KEY")}.send' - response = requests.post(url, data=data).json() - - if response.get("errno") == 0 or response.get("code") == 0: - print("serverJ 推送成功!") - else: - print(f'serverJ 推送失败!错误码:{response["message"]}') - - -def pushdeer(title: str, content: str) -> None: - """ - 通过PushDeer 推送消息 - """ - if not push_config.get("DEER_KEY"): - print("PushDeer 服务的 DEER_KEY 未设置!!\n取消推送") - return - print("PushDeer 服务启动") - data = { - "text": title, - "desp": content, - "type": "markdown", - "pushkey": push_config.get("DEER_KEY"), - } - url = "https://api2.pushdeer.com/message/push" - if push_config.get("DEER_URL"): - url = push_config.get("DEER_URL") - - response = requests.post(url, data=data).json() - - if len(response.get("content").get("result")) > 0: - print("PushDeer 推送成功!") - else: - print("PushDeer 推送失败!错误信息:", response) - - -def chat(title: str, content: str) -> None: - """ - 通过Chat 推送消息 - """ - if not push_config.get("CHAT_URL") or not push_config.get("CHAT_TOKEN"): - print("chat 服务的 CHAT_URL或CHAT_TOKEN 未设置!!\n取消推送") - return - print("chat 服务启动") - data = "payload=" + json.dumps({"text": title + "\n" + content}) - url = push_config.get("CHAT_URL") + push_config.get("CHAT_TOKEN") - response = requests.post(url, data=data) - - if response.status_code == 200: - print("Chat 推送成功!") - else: - print("Chat 推送失败!错误信息:", response) - - -def pushplus_bot(title: str, content: str) -> None: - """ - 通过 push+ 推送消息。 - """ - if not push_config.get("PUSH_PLUS_TOKEN"): - print("PUSHPLUS 服务的 PUSH_PLUS_TOKEN 未设置!!\n取消推送") - return - print("PUSHPLUS 服务启动") - - url = "http://www.pushplus.plus/send" - data = { - "token": push_config.get("PUSH_PLUS_TOKEN"), - "title": title, - "content": content, - "topic": push_config.get("PUSH_PLUS_USER"), - } - body = json.dumps(data).encode(encoding="utf-8") - headers = {"Content-Type": "application/json"} - response = requests.post(url=url, data=body, headers=headers).json() - - if response["code"] == 200: - print("PUSHPLUS 推送成功!") - - else: - url_old = "http://pushplus.hxtrip.com/send" - headers["Accept"] = "application/json" - response = requests.post(url=url_old, data=body, headers=headers).json() - - if response["code"] == 200: - print("PUSHPLUS(hxtrip) 推送成功!") - - else: - print("PUSHPLUS 推送失败!") - - -def weplus_bot(title: str, content: str) -> None: - """ - 通过 微加机器人 推送消息。 - """ - if not push_config.get("WE_PLUS_BOT_TOKEN"): - print("微加机器人 服务的 WE_PLUS_BOT_TOKEN 未设置!!\n取消推送") - return - print("微加机器人 服务启动") - - template = "txt" - if len(content) > 800: - template = "html" - - url = "https://www.weplusbot.com/send" - data = { - "token": push_config.get("WE_PLUS_BOT_TOKEN"), - "title": title, - "content": content, - "template": template, - "receiver": push_config.get("WE_PLUS_BOT_RECEIVER"), - "version": push_config.get("WE_PLUS_BOT_VERSION"), - } - body = json.dumps(data).encode(encoding="utf-8") - headers = {"Content-Type": "application/json"} - response = requests.post(url=url, data=body, headers=headers).json() - - if response["code"] == 200: - print("微加机器人 推送成功!") - else: - print("微加机器人 推送失败!") - - -def qmsg_bot(title: str, content: str) -> None: - """ - 使用 qmsg 推送消息。 - """ - if not push_config.get("QMSG_KEY") or not push_config.get("QMSG_TYPE"): - print("qmsg 的 QMSG_KEY 或者 QMSG_TYPE 未设置!!\n取消推送") - return - print("qmsg 服务启动") - - url = f'https://qmsg.zendee.cn/{push_config.get("QMSG_TYPE")}/{push_config.get("QMSG_KEY")}' - payload = {"msg": f'{title}\n\n{content.replace("----", "-")}'.encode("utf-8")} - response = requests.post(url=url, params=payload).json() - - if response["code"] == 0: - print("qmsg 推送成功!") - else: - print(f'qmsg 推送失败!{response["reason"]}') - - -def wecom_app(title: str, content: str) -> None: - """ - 通过 企业微信 APP 推送消息。 - """ - if not push_config.get("QYWX_AM"): - print("QYWX_AM 未设置!!\n取消推送") - return - QYWX_AM_AY = re.split(",", push_config.get("QYWX_AM")) - if 4 < len(QYWX_AM_AY) > 5: - print("QYWX_AM 设置错误!!\n取消推送") - return - print("企业微信 APP 服务启动") - - corpid = QYWX_AM_AY[0] - corpsecret = QYWX_AM_AY[1] - touser = QYWX_AM_AY[2] - agentid = QYWX_AM_AY[3] - try: - media_id = QYWX_AM_AY[4] - except IndexError: - media_id = "" - wx = WeCom(corpid, corpsecret, agentid) - # 如果没有配置 media_id 默认就以 text 方式发送 - if not media_id: - message = title + "\n\n" + content - response = wx.send_text(message, touser) - else: - response = wx.send_mpnews(title, content, media_id, touser) - - if response == "ok": - print("企业微信推送成功!") - else: - print("企业微信推送失败!错误信息如下:\n", response) - - -class WeCom: - def __init__(self, corpid, corpsecret, agentid): - self.CORPID = corpid - self.CORPSECRET = corpsecret - self.AGENTID = agentid - self.ORIGIN = "https://qyapi.weixin.qq.com" - if push_config.get("QYWX_ORIGIN"): - self.ORIGIN = push_config.get("QYWX_ORIGIN") - - def get_access_token(self): - url = f"{self.ORIGIN}/cgi-bin/gettoken" - values = { - "corpid": self.CORPID, - "corpsecret": self.CORPSECRET, - } - req = requests.post(url, params=values) - data = json.loads(req.text) - return data["access_token"] - - def send_text(self, message, touser="@all"): - send_url = ( - f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}" - ) - send_values = { - "touser": touser, - "msgtype": "text", - "agentid": self.AGENTID, - "text": {"content": message}, - "safe": "0", - } - send_msges = bytes(json.dumps(send_values), "utf-8") - respone = requests.post(send_url, send_msges) - respone = respone.json() - return respone["errmsg"] - - def send_mpnews(self, title, message, media_id, touser="@all"): - send_url = ( - f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}" - ) - send_values = { - "touser": touser, - "msgtype": "mpnews", - "agentid": self.AGENTID, - "mpnews": { - "articles": [ - { - "title": title, - "thumb_media_id": media_id, - "author": "Author", - "content_source_url": "", - "content": message.replace("\n", "
"), - "digest": message, - } - ] - }, - } - send_msges = bytes(json.dumps(send_values), "utf-8") - respone = requests.post(send_url, send_msges) - respone = respone.json() - return respone["errmsg"] - - -def wecom_bot(title: str, content: str) -> None: - """ - 通过 企业微信机器人 推送消息。 - """ - if not push_config.get("QYWX_KEY"): - print("企业微信机器人 服务的 QYWX_KEY 未设置!!\n取消推送") - return - print("企业微信机器人服务启动") - - origin = "https://qyapi.weixin.qq.com" - if push_config.get("QYWX_ORIGIN"): - origin = push_config.get("QYWX_ORIGIN") - - url = f"{origin}/cgi-bin/webhook/send?key={push_config.get('QYWX_KEY')}" - headers = {"Content-Type": "application/json;charset=utf-8"} - data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}} - response = requests.post( - url=url, data=json.dumps(data), headers=headers, timeout=15 - ).json() - - if response["errcode"] == 0: - print("企业微信机器人推送成功!") - else: - print("企业微信机器人推送失败!") - - -def telegram_bot(title: str, content: str) -> None: - """ - 使用 telegram 机器人 推送消息。 - """ - if not push_config.get("TG_BOT_TOKEN") or not push_config.get("TG_USER_ID"): - print("tg 服务的 bot_token 或者 user_id 未设置!!\n取消推送") - return - print("tg 服务启动") - - if push_config.get("TG_API_HOST"): - url = f"{push_config.get('TG_API_HOST')}/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage" - else: - url = ( - f"https://api.telegram.org/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage" - ) - headers = {"Content-Type": "application/x-www-form-urlencoded"} - payload = { - "chat_id": str(push_config.get("TG_USER_ID")), - "text": f"{title}\n\n{content}", - "disable_web_page_preview": "true", - } - proxies = None - if push_config.get("TG_PROXY_HOST") and push_config.get("TG_PROXY_PORT"): - if push_config.get("TG_PROXY_AUTH") is not None and "@" not in push_config.get( - "TG_PROXY_HOST" - ): - push_config["TG_PROXY_HOST"] = ( - push_config.get("TG_PROXY_AUTH") - + "@" - + push_config.get("TG_PROXY_HOST") - ) - proxyStr = "http://{}:{}".format( - push_config.get("TG_PROXY_HOST"), push_config.get("TG_PROXY_PORT") - ) - proxies = {"http": proxyStr, "https": proxyStr} - response = requests.post( - url=url, headers=headers, params=payload, proxies=proxies - ).json() - - if response["ok"]: - print("tg 推送成功!") - else: - print("tg 推送失败!") - - -def aibotk(title: str, content: str) -> None: - """ - 使用 智能微秘书 推送消息。 - """ - if ( - not push_config.get("AIBOTK_KEY") - or not push_config.get("AIBOTK_TYPE") - or not push_config.get("AIBOTK_NAME") - ): - print( - "智能微秘书 的 AIBOTK_KEY 或者 AIBOTK_TYPE 或者 AIBOTK_NAME 未设置!!\n取消推送" - ) - return - print("智能微秘书 服务启动") - - if push_config.get("AIBOTK_TYPE") == "room": - url = "https://api-bot.aibotk.com/openapi/v1/chat/room" - data = { - "apiKey": push_config.get("AIBOTK_KEY"), - "roomName": push_config.get("AIBOTK_NAME"), - "message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"}, - } - else: - url = "https://api-bot.aibotk.com/openapi/v1/chat/contact" - data = { - "apiKey": push_config.get("AIBOTK_KEY"), - "name": push_config.get("AIBOTK_NAME"), - "message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"}, - } - body = json.dumps(data).encode(encoding="utf-8") - headers = {"Content-Type": "application/json"} - response = requests.post(url=url, data=body, headers=headers).json() - print(response) - if response["code"] == 0: - print("智能微秘书 推送成功!") - else: - print(f'智能微秘书 推送失败!{response["error"]}') - - -def smtp(title: str, content: str) -> None: - """ - 使用 SMTP 邮件 推送消息。 - """ - if ( - not push_config.get("SMTP_SERVER") - or not push_config.get("SMTP_SSL") - or not push_config.get("SMTP_EMAIL") - or not push_config.get("SMTP_PASSWORD") - or not push_config.get("SMTP_NAME") - ): - print( - "SMTP 邮件 的 SMTP_SERVER 或者 SMTP_SSL 或者 SMTP_EMAIL 或者 SMTP_PASSWORD 或者 SMTP_NAME 未设置!!\n取消推送" - ) - return - print("SMTP 邮件 服务启动") - - message = MIMEText(content, "plain", "utf-8") - message["From"] = formataddr( - ( - Header(push_config.get("SMTP_NAME"), "utf-8").encode(), - push_config.get("SMTP_EMAIL"), - ) - ) - message["To"] = formataddr( - ( - Header(push_config.get("SMTP_NAME"), "utf-8").encode(), - push_config.get("SMTP_EMAIL"), - ) - ) - message["Subject"] = Header(title, "utf-8") - - try: - smtp_server = ( - smtplib.SMTP_SSL(push_config.get("SMTP_SERVER")) - if push_config.get("SMTP_SSL") == "true" - else smtplib.SMTP(push_config.get("SMTP_SERVER")) - ) - smtp_server.login( - push_config.get("SMTP_EMAIL"), push_config.get("SMTP_PASSWORD") - ) - smtp_server.sendmail( - push_config.get("SMTP_EMAIL"), - push_config.get("SMTP_EMAIL"), - message.as_bytes(), - ) - smtp_server.close() - print("SMTP 邮件 推送成功!") - except Exception as e: - print(f"SMTP 邮件 推送失败!{e}") - - -def pushme(title: str, content: str) -> None: - """ - 使用 PushMe 推送消息。 - """ - if not push_config.get("PUSHME_KEY"): - print("PushMe 服务的 PUSHME_KEY 未设置!!\n取消推送") - return - print("PushMe 服务启动") - - url = ( - push_config.get("PUSHME_URL") - if push_config.get("PUSHME_URL") - else "https://push.i-i.me/" - ) - data = { - "push_key": push_config.get("PUSHME_KEY"), - "title": title, - "content": content, - "date": push_config.get("date") if push_config.get("date") else "", - "type": push_config.get("type") if push_config.get("type") else "", - } - response = requests.post(url, data=data) - - if response.status_code == 200 and response.text == "success": - print("PushMe 推送成功!") - else: - print(f"PushMe 推送失败!{response.status_code} {response.text}") - - -def chronocat(title: str, content: str) -> None: - """ - 使用 CHRONOCAT 推送消息。 - """ - if ( - not push_config.get("CHRONOCAT_URL") - or not push_config.get("CHRONOCAT_QQ") - or not push_config.get("CHRONOCAT_TOKEN") - ): - print("CHRONOCAT 服务的 CHRONOCAT_URL 或 CHRONOCAT_QQ 未设置!!\n取消推送") - return - - print("CHRONOCAT 服务启动") - - user_ids = re.findall(r"user_id=(\d+)", push_config.get("CHRONOCAT_QQ")) - group_ids = re.findall(r"group_id=(\d+)", push_config.get("CHRONOCAT_QQ")) - - url = f'{push_config.get("CHRONOCAT_URL")}/api/message/send' - headers = { - "Content-Type": "application/json", - "Authorization": f'Bearer {push_config.get("CHRONOCAT_TOKEN")}', - } - - for chat_type, ids in [(1, user_ids), (2, group_ids)]: - if not ids: - continue - for chat_id in ids: - data = { - "peer": {"chatType": chat_type, "peerUin": chat_id}, - "elements": [ - { - "elementType": 1, - "textElement": {"content": f"{title}\n\n{content}"}, - } - ], - } - response = requests.post(url, headers=headers, data=json.dumps(data)) - if response.status_code == 200: - if chat_type == 1: - print(f"QQ个人消息:{ids}推送成功!") - else: - print(f"QQ群消息:{ids}推送成功!") - else: - if chat_type == 1: - print(f"QQ个人消息:{ids}推送失败!") - else: - print(f"QQ群消息:{ids}推送失败!") - -def ntfy(title: str, content: str) -> None: - """ - 使用 Ntfy 推送消息。 - """ - if not push_config.get("Ntfy_T"): - print("PushMe 服务的 PUSHME_KEY 未设置!!\n取消推送") - return - print("PushMe 服务启动") - - url = push_config.get("PUSHME_URL") if push_config.get("PUSHME_URL") else "https://push.i-i.me/" - data = { - "push_key": push_config.get("PUSHME_KEY"), - "title": title, - "content": content, - "date": push_config.get("date") if push_config.get("date") else "", - "type": push_config.get("type") if push_config.get("type") else "", - } - response = requests.post(url, data=data) - - if response.status_code == 200 and response.text == "success": - print("PushMe 推送成功!") - else: - print(f"PushMe 推送失败!{response.status_code} {response.text}") - - -def parse_headers(headers): - if not headers: - return {} - - parsed = {} - lines = headers.split("\n") - - for line in lines: - i = line.find(":") - if i == -1: - continue - - key = line[:i].strip().lower() - val = line[i + 1 :].strip() - parsed[key] = parsed.get(key, "") + ", " + val if key in parsed else val - - return parsed - - -def parse_string(input_string, value_format_fn=None): - matches = {} - pattern = r"(\w+):\s*((?:(?!\n\w+:).)*)" - regex = re.compile(pattern) - for match in regex.finditer(input_string): - key, value = match.group(1).strip(), match.group(2).strip() - try: - value = value_format_fn(value) if value_format_fn else value - json_value = json.loads(value) - matches[key] = json_value - except: - matches[key] = value - return matches - - -def parse_body(body, content_type, value_format_fn=None): - if not body or content_type == "text/plain": - return value_format_fn(body) if value_format_fn and body else body - - parsed = parse_string(body, value_format_fn) - - if content_type == "application/x-www-form-urlencoded": - data = urllib.parse.urlencode(parsed, doseq=True) - return data - - if content_type == "application/json": - data = json.dumps(parsed) - return data - - return parsed - - -def custom_notify(title: str, content: str) -> None: - """ - 通过 自定义通知 推送消息。 - """ - if not push_config.get("WEBHOOK_URL") or not push_config.get("WEBHOOK_METHOD"): - print("自定义通知的 WEBHOOK_URL 或 WEBHOOK_METHOD 未设置!!\n取消推送") - return - - print("自定义通知服务启动") - - WEBHOOK_URL = push_config.get("WEBHOOK_URL") - WEBHOOK_METHOD = push_config.get("WEBHOOK_METHOD") - WEBHOOK_CONTENT_TYPE = push_config.get("WEBHOOK_CONTENT_TYPE") - WEBHOOK_BODY = push_config.get("WEBHOOK_BODY") - WEBHOOK_HEADERS = push_config.get("WEBHOOK_HEADERS") - - if "$title" not in WEBHOOK_URL and "$title" not in WEBHOOK_BODY: - print("请求头或者请求体中必须包含 $title 和 $content") - return - - headers = parse_headers(WEBHOOK_HEADERS) - body = parse_body( - WEBHOOK_BODY, - WEBHOOK_CONTENT_TYPE, - lambda v: v.replace("$title", title.replace("\n", "\\n")).replace( - "$content", content.replace("\n", "\\n") - ), - ) - formatted_url = WEBHOOK_URL.replace( - "$title", urllib.parse.quote_plus(title) - ).replace("$content", urllib.parse.quote_plus(content)) - response = requests.request( - method=WEBHOOK_METHOD, url=formatted_url, headers=headers, timeout=15, data=body - ) - - if response.status_code == 200: - print("自定义通知推送成功!") - else: - print(f"自定义通知推送失败!{response.status_code} {response.text}") - - -def one() -> str: - """ - 获取一条一言。 - :return: - """ - url = "https://v1.hitokoto.cn/" - res = requests.get(url).json() - return res["hitokoto"] + " ----" + res["from"] - - -def add_notify_function(): - notify_function = [] - if push_config.get("BARK_PUSH"): - notify_function.append(bark) - if push_config.get("CONSOLE"): - notify_function.append(console) - if push_config.get("DD_BOT_TOKEN") and push_config.get("DD_BOT_SECRET"): - notify_function.append(dingding_bot) - if push_config.get("FSKEY"): - notify_function.append(feishu_bot) - if push_config.get("GOBOT_URL") and push_config.get("GOBOT_QQ"): - notify_function.append(go_cqhttp) - if push_config.get("GOTIFY_URL") and push_config.get("GOTIFY_TOKEN"): - notify_function.append(gotify) - if push_config.get("IGOT_PUSH_KEY"): - notify_function.append(iGot) - if push_config.get("PUSH_KEY"): - notify_function.append(serverJ) - if push_config.get("DEER_KEY"): - notify_function.append(pushdeer) - if push_config.get("CHAT_URL") and push_config.get("CHAT_TOKEN"): - notify_function.append(chat) - if push_config.get("PUSH_PLUS_TOKEN"): - notify_function.append(pushplus_bot) - if push_config.get("WE_PLUS_BOT_TOKEN"): - notify_function.append(weplus_bot) - if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"): - notify_function.append(qmsg_bot) - if push_config.get("QYWX_AM"): - notify_function.append(wecom_app) - if push_config.get("QYWX_KEY"): - notify_function.append(wecom_bot) - if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"): - notify_function.append(telegram_bot) - if ( - push_config.get("AIBOTK_KEY") - and push_config.get("AIBOTK_TYPE") - and push_config.get("AIBOTK_NAME") - ): - notify_function.append(aibotk) - if ( - push_config.get("SMTP_SERVER") - and push_config.get("SMTP_SSL") - and push_config.get("SMTP_EMAIL") - and push_config.get("SMTP_PASSWORD") - and push_config.get("SMTP_NAME") - ): - notify_function.append(smtp) - if push_config.get("PUSHME_KEY"): - notify_function.append(pushme) - if ( - push_config.get("CHRONOCAT_URL") - and push_config.get("CHRONOCAT_QQ") - and push_config.get("CHRONOCAT_TOKEN") - ): - notify_function.append(chronocat) - if push_config.get("WEBHOOK_URL") and push_config.get("WEBHOOK_METHOD"): - notify_function.append(custom_notify) - - if not notify_function: - print(f"无推送渠道,请检查通知变量是否正确") - return notify_function - - -def send(title: str, content: str, ignore_default_config: bool = False, **kwargs): - if kwargs: - global push_config - if ignore_default_config: - push_config = kwargs # 清空从环境变量获取的配置 - else: - push_config.update(kwargs) - - if not content: - print(f"{title} 推送内容为空!") - return - - # 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔 - skipTitle = os.getenv("SKIP_PUSH_TITLE") - if skipTitle: - if title in re.split("\n", skipTitle): - print(f"{title} 在SKIP_PUSH_TITLE环境变量内,跳过推送!") - return - - hitokoto = push_config.get("HITOKOTO") - content += "\n\n" + one() if hitokoto != "false" else "" - - notify_function = add_notify_function() - ts = [ - threading.Thread(target=mode, args=(title, content), name=mode.__name__) - for mode in notify_function - ] - [t.start() for t in ts] - [t.join() for t in ts] - - -def main(): - send("title", "content") - - -if __name__ == "__main__": - main() diff --git a/shell/bot.sh b/shell/bot.sh index 524a44e9..3d8e799d 100755 --- a/shell/bot.sh +++ b/shell/bot.sh @@ -13,12 +13,19 @@ os_name="${QL_OS_TYPE:-}" if [ -z "$os_name" ]; then os_name=$(source /etc/os-release && echo "$ID") fi + +# 非 root 用户使用 sudo +SUDO="" +if [ "$(id -u)" -ne 0 ]; then + SUDO="sudo" +fi + case "$os_name" in alpine) - apk --no-cache add -f zlib-dev gcc jpeg-dev python3-dev musl-dev freetype-dev + $SUDO apk --no-cache add -f zlib-dev gcc jpeg-dev python3-dev musl-dev freetype-dev ;; debian|ubuntu) - apt-get install -y gcc python3-dev musl-dev zlib1g-dev libjpeg-dev libfreetype-dev + $SUDO apt-get install -y gcc python3-dev musl-dev zlib1g-dev libjpeg-dev libfreetype-dev ;; *) echo -e "暂不支持此系统 $os_name" diff --git a/shell/start.sh b/shell/start.sh index 1f015f2b..3f4bb9cd 100644 --- a/shell/start.sh +++ b/shell/start.sh @@ -41,10 +41,16 @@ if [[ $command != "reload" ]]; then os_name=$(source /etc/os-release && echo "$ID") fi + # 非 root 用户使用 sudo + SUDO="" + if [ "$(id -u)" -ne 0 ]; then + SUDO="sudo" + fi + case "$os_name" in alpine) - apk update - apk add -f bash \ + $SUDO apk update + $SUDO apk add -f bash \ coreutils \ git \ curl \ @@ -59,8 +65,8 @@ if [[ $command != "reload" ]]; then netcat-openbsd ;; debian|ubuntu) - apt-get update - apt-get install -y git curl wget tzdata perl openssl jq nginx procps netcat-openbsd openssh-client + $SUDO apt-get update + $SUDO apt-get install -y git curl wget tzdata perl openssl jq nginx procps netcat-openbsd openssh-client ;; *) echo -e "暂不支持此系统部署 $os_name" From 2fe9470ff08ae891dd466a4205fe38b1418c52a8 Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 31 May 2026 00:14:22 +0800 Subject: [PATCH 22/24] =?UTF-8?q?fix:=20gRPC=20extra=5Fschedules=20?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA=E6=97=B6=E5=BA=8F=E5=88=97=E5=8C=96=E6=8A=A5?= =?UTF-8?q?=E9=94=99=20not=20iterable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/schedule/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/schedule/api.ts b/back/schedule/api.ts index 5bcc7b1f..81747958 100644 --- a/back/schedule/api.ts +++ b/back/schedule/api.ts @@ -250,7 +250,7 @@ const normalizeCronData = (data: CronItem | null): CronItem | undefined => { return { ...data, sub_id: data.sub_id ?? undefined, - extra_schedules: data.extra_schedules ?? undefined, + extra_schedules: data.extra_schedules ?? [], pid: data.pid ?? undefined, task_before: data.task_before ?? undefined, task_after: data.task_after ?? undefined, From 3aab1233bbcbaadb248390e5205d7573e2770922 Mon Sep 17 00:00:00 2001 From: jmclulu <2817852344@QQ.com> Date: Sun, 31 May 2026 00:32:04 +0800 Subject: [PATCH 23/24] fix: correct typos in source code and locales (#3003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix Countrys -> Countries in comment - Fix Scrolldown -> ScrollDown in variable name - Fix completeTowFactor -> completeTwoFactor (3x) - Fix deactiveTowFactor -> deactivateTwoFactor (3x) - Fix activeOrDeactiveTwoFactor -> activeOrDeactivateTwoFactor - Fix API route /two-factor/deactive -> /two-factor/deactivate - Fix elment -> element in function param - Fix synolog -> synology in comment - Fix Chinese comment 制定 -> 指定 - Fix swapped BARK English translations on lines 360-361 --- back/api/user.ts | 4 ++-- back/services/user.ts | 2 +- sample/notify.js | 2 +- src/components/terminal.tsx | 8 ++++---- src/locales/en-US.json | 4 ++-- src/pages/login/index.tsx | 6 +++--- src/pages/setting/security.tsx | 12 ++++++------ src/utils/config.ts | 2 +- src/utils/index.ts | 6 +++--- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/back/api/user.ts b/back/api/user.ts index bed0fd78..6155f0a4 100644 --- a/back/api/user.ts +++ b/back/api/user.ts @@ -140,12 +140,12 @@ export default (app: Router) => { ); route.put( - '/two-factor/deactive', + '/two-factor/deactivate', async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const userService = Container.get(UserService); - const data = await userService.deactiveTwoFactor(); + const data = await userService.deactivateTwoFactor(); res.send({ code: 200, data }); } catch (e) { return next(e); diff --git a/back/services/user.ts b/back/services/user.ts index 068b850c..362cb693 100644 --- a/back/services/user.ts +++ b/back/services/user.ts @@ -330,7 +330,7 @@ export default class UserService { } } - public async deactiveTwoFactor() { + public async deactivateTwoFactor() { const authInfo = await this.getAuthInfo(); await this.updateAuthInfo(authInfo, { twoFactorActivated: false, diff --git a/sample/notify.js b/sample/notify.js index 49d5fdc8..2b4d91d3 100644 --- a/sample/notify.js +++ b/sample/notify.js @@ -1598,7 +1598,7 @@ async function sendNotify(text, desp, params = {}) { iGotNotify(text, desp, params), // iGot gobotNotify(text, desp), // go-cqhttp gotifyNotify(text, desp), // gotify - chatNotify(text, desp), // synolog chat + chatNotify(text, desp), // synology chat pushDeerNotify(text, desp), // PushDeer aibotkNotify(text, desp), // 智能微秘书 fsBotNotify(text, desp), // 飞书机器人 diff --git a/src/components/terminal.tsx b/src/components/terminal.tsx index 0492595b..8b4d1804 100644 --- a/src/components/terminal.tsx +++ b/src/components/terminal.tsx @@ -29,10 +29,10 @@ const Terminal = ({ const lastLineRef = useRef(null); // An effect that handles scrolling into view the last line of terminal input or output - const performScrolldown = useRef(false); + const performScrollDown = useRef(false); useEffect(() => { - if (performScrolldown.current) { - // skip scrolldown when the component first loads + if (performScrollDown.current) { + // skip scrollDown when the component first loads setTimeout( () => lastLineRef?.current?.scrollIntoView({ @@ -42,7 +42,7 @@ const Terminal = ({ 500, ); } - performScrolldown.current = true; + performScrollDown.current = true; }, [lineData.length]); const renderedLineData = lineData.map((ld, i) => { diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 29e4bdbf..4662e9b7 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -357,8 +357,8 @@ "BARK推送图标,自定义推送图标 (需iOS15或以上才能显示)": "BARK push icon, custom push icon (requires iOS 15 or above to display)", "BARK推送铃声,铃声列表去APP查看复制填写": "BARK push ringtone, check and copy from the APP's ringtone list", "BARK推送消息的分组,默认为qinglong": "BARK push message grouping, default is qinglong", - "BARK推送消息的时效性,默认为active": "BARK push message redirecting URL", - "BARK推送消息的跳转URL": "BARK push message grouping, default is qinglong", + "BARK推送消息的时效性,默认为active": "BARK push message timeliness, default is active", + "BARK推送消息的跳转URL": "BARK push message redirecting URL", "BARK是否保存推送消息": "Does BARK save push messages", "telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw": "Telegram Bot token, e.g., 1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw", "telegram用户的id,例如:129xxx206": "Telegram user ID, e.g., 129xxx206", diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index be88e737..60755c0d 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -50,7 +50,7 @@ const Login = () => { }); }; - const completeTowFactor = (values: any) => { + const completeTwoFactor = (values: any) => { setVerifying(true); request .put(`${config.apiPrefix}user/two-factor/login`, { @@ -129,7 +129,7 @@ const Login = () => { const { value } = e.target as any; const regx = /^[0-9]{6}$/; if (regx.test(value)) { - completeTowFactor({ code: value }); + completeTwoFactor({ code: value }); } }; @@ -156,7 +156,7 @@ const Login = () => {
{twoFactor ? ( -
+ { }); }; - const activeOrDeactiveTwoFactor = () => { + const activeOrDeactivateTwoFactor = () => { if (twoFactorActivated) { - deactiveTowFactor(); + deactivateTwoFactor(); } else { getTwoFactorInfo(); setTwoFactoring(true); } }; - const deactiveTowFactor = () => { + const deactivateTwoFactor = () => { request - .put(`${config.apiPrefix}user/two-factor/deactive`) + .put(`${config.apiPrefix}user/two-factor/deactivate`) .then(({ code, data }) => { if (code === 200 && data) { setTwoFactorActivated(false); @@ -60,7 +60,7 @@ const SecuritySettings = ({ user, userChange }: any) => { }); }; - const completeTowFactor = () => { + const completeTwoFactor = () => { setLoading(true); request .put(`${config.apiPrefix}user/two-factor/active`, { code }) @@ -162,7 +162,7 @@ const SecuritySettings = ({ user, userChange }: any) => { onChange={(e) => setCode(e.target.value)} placeholder="123456" /> -
diff --git a/src/utils/config.ts b/src/utils/config.ts index f15a48b5..310fa1cd 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -18,7 +18,7 @@ export default { /* I18n configuration, `languages` and `defaultLanguage` are required currently. */ i18n: { - /* Countrys flags: https://www.flaticon.com/packs/countrys-flags */ + /* Countries flags: https://www.flaticon.com/packs/countries-flags */ languages: [ { key: 'pt-br', diff --git a/src/utils/index.ts b/src/utils/index.ts index 9260a100..d393df5c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -179,7 +179,7 @@ export default function browserType() { /** * 获取第一个表格的可视化高度 * @param {*} extraHeight 额外的高度(表格底部的内容高度 Number类型,默认为74) - * @param {*} id 当前页面中有多个table时需要制定table的id + * @param {*} id 当前页面中有多个table时需要指定table的id */ export function getTableScroll({ extraHeight, @@ -208,7 +208,7 @@ export function getTableScroll({ } // 自动触发点击事件 -function automaticClick(elment: HTMLElement) { +function automaticClick(element: HTMLElement) { const ev = document.createEvent('MouseEvents'); ev.initMouseEvent( 'click', @@ -227,7 +227,7 @@ function automaticClick(elment: HTMLElement) { 0, null, ); - elment.dispatchEvent(ev); + element.dispatchEvent(ev); } // 导出文件 From c2eac8b8fdf2fe90a8044195bb3fe8d26b3a7313 Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 31 May 2026 00:36:54 +0800 Subject: [PATCH 24/24] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=9C=AA=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/setting/security.tsx | 2 +- src/utils/config.ts | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/pages/setting/security.tsx b/src/pages/setting/security.tsx index 04707aae..29b92f00 100644 --- a/src/pages/setting/security.tsx +++ b/src/pages/setting/security.tsx @@ -230,7 +230,7 @@ const SecuritySettings = ({ user, userChange }: any) => { diff --git a/src/utils/config.ts b/src/utils/config.ts index 310fa1cd..3249f67c 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -15,29 +15,6 @@ export default { exclude: [/(\/(en|zh))*\/login/], }, ], - - /* I18n configuration, `languages` and `defaultLanguage` are required currently. */ - i18n: { - /* Countries flags: https://www.flaticon.com/packs/countries-flags */ - languages: [ - { - key: 'pt-br', - title: 'Português', - flag: '/portugal.svg', - }, - { - key: 'en', - title: 'English', - flag: '/america.svg', - }, - { - key: 'zh', - title: intl.get('中文'), - flag: '/china.svg', - }, - ], - defaultLanguage: 'en', - }, scopes: [ { name: intl.get('定时任务'),