🔧 chore: update eslint v2 configuration and suppressions (#12133)

* v2 init

* chore: update eslint suppressions and package dependencies

- Removed several eslint suppressions related to array sorting and reversing from eslint-suppressions.json to clean up the configuration.
- Updated @lobehub/lint package version from 2.0.0-beta.6 to 2.0.0-beta.7 in package.json for improvements and bug fixes.
- Made minor formatting adjustments in vitest.config.mts and various SKILL.md files for better readability and consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* fix: clean up import statements and formatting

- Removed unnecessary whitespace in replaceComponentImports.ts for improved readability.
- Standardized import statements in contextEngineering.ts and createAgentExecutors.ts by adding missing spaces for consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* chore: update eslint suppressions and clean up code formatting

* 🐛 fix: use vi.hoisted for mock variable initialization

Fix TDZ error in persona service test by using vi.hoisted() to ensure
mock variables are available when vi.mock factory runs.

---------

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2026-02-05 21:40:43 +08:00
committed by arvinxx
parent 2892e8fcff
commit fcdaf9d814
3488 changed files with 15606 additions and 13651 deletions
+2 -2
View File
@@ -1,7 +1,8 @@
import dotenv from 'dotenv';
import { writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import dotenv from 'dotenv';
import { Sitemap } from '@/server/sitemap';
dotenv.config();
@@ -13,5 +14,4 @@ const genSitemap = async () => {
writeFileSync(filename, sitemapIndexXML);
};
// eslint-disable-next-line unicorn/prefer-top-level-await
genSitemap().catch(console.error);
+5 -4
View File
@@ -1,9 +1,10 @@
import { consola } from 'consola';
import { writeJSONSync } from 'fs-extra';
import matter from 'gray-matter';
import { createHash } from 'node:crypto';
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { consola } from 'consola';
import { writeJSONSync } from 'fs-extra';
import matter from 'gray-matter';
import pMap from 'p-map';
import { uploader } from './uploader';
@@ -63,7 +64,7 @@ class ImageCDNUploader {
const links: string[][] = posts.map((post) => {
const mdx = readFileSync(post, 'utf8');
const { content, data } = matter(mdx);
let inlineLinks: string[] = extractHttpsLinks(content);
const inlineLinks: string[] = extractHttpsLinks(content);
// 添加特定字段中的图片链接
if (data?.image) inlineLinks.push(data.image);
+2 -7
View File
@@ -1,10 +1,5 @@
import {
GetObjectCommand,
PutObjectCommand,
PutObjectCommandOutput,
S3Client,
S3ClientConfig,
} from '@aws-sdk/client-s3';
import type { PutObjectCommandOutput, S3ClientConfig } from '@aws-sdk/client-s3';
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import type { ImgInfo, S3UserConfig, UploadResult } from './types';
+1 -1
View File
@@ -1,7 +1,7 @@
import CryptoJS from 'crypto-js';
import mime from 'mime';
import { ImgInfo } from './types';
import type { ImgInfo } from './types';
class FileNameGenerator {
date: Date;
+3 -3
View File
@@ -9,19 +9,19 @@ dotenv.config();
if (!process.env.DOC_S3_ACCESS_KEY_ID) {
consola.error('请配置 Doc S3 存储的环境变量: DOC_S3_ACCESS_KEY_ID');
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
}
if (!process.env.DOC_S3_SECRET_ACCESS_KEY) {
consola.error('请配置 Doc S3 存储的环境变量: DOC_S3_SECRET_ACCESS_KEY');
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
}
if (!process.env.DOC_S3_PUBLIC_DOMAIN) {
consola.error('请配置 Doc S3 存储的环境变量: DOC_S3_PUBLIC_DOMAIN');
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
}
+2 -1
View File
@@ -1,6 +1,7 @@
import { resolve } from 'node:path';
import { readJSONSync } from 'fs-extra';
import { globSync } from 'glob';
import { resolve } from 'node:path';
import { opimized, opimizedGif } from './optimized';
@@ -1,7 +1,8 @@
import { consola } from 'consola';
import { readJsonSync, writeJSONSync } from 'fs-extra';
import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { consola } from 'consola';
import { readJsonSync, writeJSONSync } from 'fs-extra';
import semver from 'semver';
import { markdownToTxt } from '@/utils/markdownToTxt';
@@ -18,7 +19,7 @@ export interface ChangelogStaticItem {
class BuildStaticChangelog {
private removeDetailsTag = (changelog: string): string => {
const detailsRegex: RegExp = /<details\b[^>]*>[\S\s]*?<\/details>/gi;
const detailsRegex: RegExp = /<details\b[^>]*>[\s\S]*?<\/details>/gi;
return changelog.replaceAll(detailsRegex, '');
};
+8 -6
View File
@@ -77,7 +77,7 @@ const checkConsoleLogs = () => {
}
const lines = output.trim().split('\n');
const violations: Array<{ content: string, file: string; line: string; }> = [];
const violations: Array<{ content: string; file: string; line: string }> = [];
for (const line of lines) {
// Parse git grep output: filename:lineNumber:content
@@ -105,7 +105,9 @@ const checkConsoleLogs = () => {
}
if (violations.length === 0) {
console.log('✅ No console.log violations found (all matches are whitelisted or in comments)!');
console.log(
'✅ No console.log violations found (all matches are whitelisted or in comments)!',
);
return;
}
@@ -118,7 +120,9 @@ const checkConsoleLogs = () => {
for (const violation of violations) {
if (isCI) {
// GitHub Actions warning annotation format
console.log(`::warning file=${violation.file},line=${violation.line}::console.log found: ${violation.content}`);
console.log(
`::warning file=${violation.file},line=${violation.line}::console.log found: ${violation.content}`,
);
} else {
console.log(` ${violation.file}:${violation.line}`);
console.log(` ${violation.content}\n`);
@@ -126,9 +130,7 @@ const checkConsoleLogs = () => {
}
console.log(`\n💡 Total violations: ${violations.length}`);
console.log(
`\n📝 To whitelist files, add them to ${WHITELIST_PATH} in the following format:`,
);
console.log(`\n📝 To whitelist files, add them to ${WHITELIST_PATH} in the following format:`);
console.log(`{
"files": ["path/to/file.ts"],
"patterns": ["scripts/**/*.mts", "**/*.test.ts"]
@@ -1,4 +1,5 @@
import './env';
import type { ClerkToBetterAuthMode, DatabaseDriver } from './types';
const DEFAULT_MODE: ClerkToBetterAuthMode = 'test';
+1 -1
View File
@@ -1,4 +1,4 @@
import { Pool as NeonPool, neonConfig } from '@neondatabase/serverless';
import { neonConfig, Pool as NeonPool } from '@neondatabase/serverless';
import { drizzle as neonDrizzle } from 'drizzle-orm/neon-serverless';
import { drizzle as nodeDrizzle } from 'drizzle-orm/node-postgres';
import { Pool as NodePool } from 'pg';
@@ -1,7 +1,7 @@
import { readFile } from 'node:fs/promises';
import { resolveDataPaths } from './config';
import { CSVUserRow, ClerkUser } from './types';
import type { ClerkUser, CSVUserRow } from './types';
export function parseCsvLine(line: string): string[] {
const cells: string[] = [];
@@ -1,9 +1,9 @@
/* eslint-disable unicorn/prefer-top-level-await, unicorn/no-process-exit */
import './_internal/env';
import { writeFile } from 'node:fs/promises';
import { getClerkSecret, getMigrationMode, resolveDataPaths } from './_internal/config';
import './_internal/env';
import { ClerkApiUser, ClerkUser } from './_internal/types';
import type { ClerkApiUser, ClerkUser } from './_internal/types';
/**
* Fetch all Clerk users via REST API and persist them into a local JSON file.
@@ -150,10 +150,7 @@ async function fetchAllClerkUsers(secretKey: string): Promise<ClerkUser[]> {
const userMap = new Map<string, ClerkUser>();
// Get total count first
const countResponse = await fetchClerkApi<{ total_count: number }>(
secretKey,
'/users/count',
);
const countResponse = await fetchClerkApi<{ total_count: number }>(secretKey, '/users/count');
const totalCount = countResponse.total_count;
const totalPages = Math.ceil(totalCount / PAGE_SIZE);
const offsets = Array.from({ length: totalPages }, (_, pageIndex) => pageIndex * PAGE_SIZE);
+11 -7
View File
@@ -1,10 +1,9 @@
/* eslint-disable unicorn/prefer-top-level-await */
import { sql } from 'drizzle-orm';
import { getMigrationMode } from './_internal/config';
import { db, pool, schema } from './_internal/db';
import { loadCSVData, loadClerkUsersFromFile } from './_internal/load-data-from-files';
import { ClerkExternalAccount } from './_internal/types';
import { loadClerkUsersFromFile, loadCSVData } from './_internal/load-data-from-files';
import type { ClerkExternalAccount } from './_internal/types';
import { generateBackupCodes, safeDateConversion } from './_internal/utils';
const BATCH_SIZE = Number(process.env.CLERK_TO_BETTERAUTH_BATCH_SIZE) || 300;
@@ -81,7 +80,7 @@ async function migrateFromClerk() {
let processed = 0;
let accountAttempts = 0;
let twoFactorAttempts = 0;
let skipped = csvUsers.length - unprocessedUsers.length;
const skipped = csvUsers.length - unprocessedUsers.length;
const startedAt = Date.now();
const accountCounts: Record<string, number> = {};
let missingScopeNonCredential = 0;
@@ -154,7 +153,7 @@ async function migrateFromClerk() {
createdAt: safeDateConversion(externalAccount.created_at),
id: externalAccount.id,
providerId,
scope: externalAccount.approved_scopes?.replace(/\s+/g, ',') || undefined,
scope: externalAccount.approved_scopes?.replaceAll(/\s+/g, ',') || undefined,
updatedAt: safeDateConversion(externalAccount.updated_at),
userId: user.id,
});
@@ -306,10 +305,15 @@ async function main() {
try {
await migrateFromClerk();
console.log('');
console.log(`${GREEN_BOLD}✅ Migration success!${RESET} (${formatDuration(Date.now() - startedAt)})`);
console.log(
`${GREEN_BOLD}✅ Migration success!${RESET} (${formatDuration(Date.now() - startedAt)})`,
);
} catch (error) {
console.log('');
console.error(`${RED_BOLD}❌ Migration failed${RESET} (${formatDuration(Date.now() - startedAt)}):`, error);
console.error(
`${RED_BOLD}❌ Migration failed${RESET} (${formatDuration(Date.now() - startedAt)}):`,
error,
);
process.exitCode = 1;
} finally {
await pool.end();
+3 -4
View File
@@ -1,8 +1,7 @@
/* eslint-disable unicorn/prefer-top-level-await */
import { getMigrationMode, resolveDataPaths } from './_internal/config';
import { db, pool, schema } from './_internal/db';
import { loadCSVData, loadClerkUsersFromFile } from './_internal/load-data-from-files';
import { ClerkExternalAccount, ClerkUser } from './_internal/types';
import { loadClerkUsersFromFile, loadCSVData } from './_internal/load-data-from-files';
import type { ClerkExternalAccount, ClerkUser } from './_internal/types';
type ExpectedAccount = {
accountId?: string;
@@ -48,7 +47,7 @@ function buildExpectedAccounts(
expectedAccounts.push({
accountId: external.provider_user_id,
providerId: providerIdFromExternal(external),
scope: external.approved_scopes?.replace(/\s+/g, ','),
scope: external.approved_scopes?.replaceAll(/\s+/g, ','),
userId: user.id,
});
}
+2 -1
View File
@@ -1,6 +1,7 @@
import { pgGenerate } from 'drizzle-dbml-generator';
import { join } from 'node:path';
import { pgGenerate } from 'drizzle-dbml-generator';
import * as schema from '../../packages/database/src/schemas';
const out = join(__dirname, '../../docs/development/database-schema.dbml');
+4 -3
View File
@@ -1,8 +1,9 @@
import { consola } from 'consola';
import matter from 'gray-matter';
import { createHash } from 'node:crypto';
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { consola } from 'consola';
import matter from 'gray-matter';
import pMap from 'p-map';
import { uploader } from '../cdnWorkflow/uploader';
@@ -60,7 +61,7 @@ class ImageCDNUploader {
const links: string[][] = posts.map((post) => {
const mdx = readFileSync(post, 'utf8');
const { content, data } = matter(mdx);
let inlineLinks: string[] = extractHttpsLinks(content);
const inlineLinks: string[] = extractHttpsLinks(content);
// 添加特定字段中的图片链接
if (data?.image) inlineLinks.push(data.image);
+2 -1
View File
@@ -1,6 +1,7 @@
import { globSync } from 'glob';
import { resolve } from 'node:path';
import { globSync } from 'glob';
export const WIKI_URL = 'https://github.com/lobehub/lobe-chat/wiki/';
export const ROOT = resolve(__dirname, '../..');
export const DOCS_DIR = resolve(ROOT, 'contributing');
+3 -2
View File
@@ -1,9 +1,10 @@
import { consola } from 'consola';
import { relative, resolve } from 'node:path';
import { consola } from 'consola';
import pMap from 'p-map';
import urlJoin from 'url-join';
import { DOCS_DIR, HOME_PATH, SIDEBAR_PATH, WIKI_URL, docsFiles } from './const';
import { DOCS_DIR, docsFiles, HOME_PATH, SIDEBAR_PATH, WIKI_URL } from './const';
import toc from './toc';
import { genMdLink, getTitle, updateDocs } from './utils';
+2 -1
View File
@@ -1,6 +1,7 @@
import { globSync } from 'glob';
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { globSync } from 'glob';
import remarkGfm from 'remark-gfm';
import remarkParse from 'remark-parse';
import { unified } from 'unified';
@@ -1,8 +1,8 @@
/* eslint-disable unicorn/no-process-exit */
import fs from 'fs-extra';
import { execSync } from 'node:child_process';
import path from 'node:path';
import fs from 'fs-extra';
type ReleaseChannel = 'stable' | 'beta' | 'nightly';
const rootDir = path.resolve(__dirname, '../..');
@@ -1,4 +1,3 @@
/* eslint-disable unicorn/no-process-exit */
import { execSync } from 'node:child_process';
import os from 'node:os';
@@ -1,6 +1,6 @@
/* eslint-disable unicorn/no-process-exit, unicorn/prefer-top-level-await */
import fs from 'node:fs';
import path from 'node:path';
import YAML from 'yaml';
// 配置
@@ -166,7 +166,7 @@ export const modifyNextConfig = async (TEMP_DIR: string) => {
edits.push({
end: range.start.index + 1,
start: range.start.index + 1,
text: "\n outputFileTracingRoot: process.env.ELECTRON_BUILD_PROJECT_ROOT || process.cwd(),",
text: '\n outputFileTracingRoot: process.env.ELECTRON_BUILD_PROJECT_ROOT || process.cwd(),',
});
}
}
@@ -34,7 +34,11 @@ const removeSuspenseWrapper = (code: string): string => {
for (const child of children) {
const kind = child.kind();
if (kind === 'jsx_element' || kind === 'jsx_self_closing_element' || kind === 'jsx_fragment') {
if (
kind === 'jsx_element' ||
kind === 'jsx_self_closing_element' ||
kind === 'jsx_fragment'
) {
childrenText = child.text();
break;
}
@@ -22,7 +22,8 @@ const isBusinessFeaturesEnabled = () => {
const extractDynamicImportsFromMap = (code: string): DynamicImportInfo[] => {
const results: DynamicImportInfo[] = [];
const regex = /\[SettingsTabs\.(\w+)]:\s*dynamic\(\s*\(\)\s*=>\s*import\(\s*["']([^"']+)["']\s*\)/g;
const regex =
/\[SettingsTabs\.(\w+)]:\s*dynamic\(\s*\(\)\s*=>\s*import\(\s*["']([^"']+)["']\s*\)/g;
let match;
while ((match = regex.exec(code)) !== null) {
@@ -99,10 +100,7 @@ export const convertSettingsContentToStatic = async (TEMP_DIR: string) => {
let result = code;
result = result.replace(
/import dynamic from ["']@\/libs\/next\/dynamic["'];\n?/,
'',
);
result = result.replace(/import dynamic from ["']@\/libs\/next\/dynamic["'];\n?/, '');
result = result.replace(
/import Loading from ["']@\/components\/Loading\/BrandTextLoading["'];\n?/,
@@ -112,11 +110,13 @@ export const convertSettingsContentToStatic = async (TEMP_DIR: string) => {
const ast = parse(Lang.Tsx, result);
const root = ast.root();
const lastImport = root.findAll({
rule: {
kind: 'import_statement',
},
}).at(-1);
const lastImport = root
.findAll({
rule: {
kind: 'import_statement',
},
})
.at(-1);
invariant(
lastImport,
@@ -124,7 +124,11 @@ export const convertSettingsContentToStatic = async (TEMP_DIR: string) => {
);
const insertPos = lastImport!.range().end.index;
result = result.slice(0, insertPos) + '\nimport type React from \'react\';\n' + staticImports + result.slice(insertPos);
result =
result.slice(0, insertPos) +
"\nimport type React from 'react';\n" +
staticImports +
result.slice(insertPos);
const componentMapRegex = /const componentMap = {[\S\s]*?\n};/;
invariant(
@@ -10,8 +10,10 @@ export const wrapChildrenWithClientOnly = async (TEMP_DIR: string) => {
await updateFile({
assertAfter: (code) => {
const hasClientOnlyImport = /import ClientOnly from ["']@\/components\/client\/ClientOnly["']/.test(code);
const hasLoadingImport = /import Loading from ["']@\/components\/Loading\/BrandTextLoading["']/.test(code);
const hasClientOnlyImport =
/import ClientOnly from ["']@\/components\/client\/ClientOnly["']/.test(code);
const hasLoadingImport =
/import Loading from ["']@\/components\/Loading\/BrandTextLoading["']/.test(code);
const hasClientOnlyWrapper = /<ClientOnly fallback={<Loading/.test(code);
return hasClientOnlyImport && hasLoadingImport && hasClientOnlyWrapper;
},
@@ -23,16 +25,23 @@ export const wrapChildrenWithClientOnly = async (TEMP_DIR: string) => {
let result = code;
const hasClientOnlyImport = /import ClientOnly from ["']@\/components\/client\/ClientOnly["']/.test(code);
const hasLoadingImport = /import Loading from ["']@\/components\/Loading\/BrandTextLoading["']/.test(code);
const hasClientOnlyImport =
/import ClientOnly from ["']@\/components\/client\/ClientOnly["']/.test(code);
const hasLoadingImport =
/import Loading from ["']@\/components\/Loading\/BrandTextLoading["']/.test(code);
const lastImport = root.findAll({
rule: {
kind: 'import_statement',
},
}).at(-1);
const lastImport = root
.findAll({
rule: {
kind: 'import_statement',
},
})
.at(-1);
invariant(lastImport, '[wrapChildrenWithClientOnly] No import statements found in layout.tsx');
invariant(
lastImport,
'[wrapChildrenWithClientOnly] No import statements found in layout.tsx',
);
const insertPos = lastImport!.range().end.index;
let importsToAdd = '';
+2 -1
View File
@@ -1,6 +1,7 @@
import fs from 'fs-extra';
import path from 'node:path';
import fs from 'fs-extra';
const rootDir = path.resolve(__dirname, '../..');
const exportSourceDir = path.join(rootDir, 'out');
@@ -1,7 +1,7 @@
/* eslint-disable unicorn/no-process-exit */
import fs from 'fs-extra';
import path from 'node:path';
import fs from 'fs-extra';
type ReleaseType = 'stable' | 'beta' | 'nightly';
// 获取脚本的命令行参数
+21 -21
View File
@@ -1,9 +1,9 @@
/* eslint-disable unicorn/prefer-top-level-await */
import * as fs from 'node:fs';
import * as path from 'node:path';
import { consola } from 'consola';
import { colors } from 'consola/utils';
import { glob } from 'glob';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { IGNORED_FILES, PROTECTED_KEY_PATTERNS } from './protectedPatterns';
@@ -83,7 +83,7 @@ function loadAllI18nKeys(): I18nKey[] {
try {
// Use require to load the TypeScript file (after it's compiled)
// eslint-disable-next-line @typescript-eslint/no-require-imports
const loadedModule = require(filePath);
const translations = loadedModule.default || loadedModule;
@@ -130,43 +130,43 @@ async function findAllTranslationCalls(): Promise<Set<string>> {
pattern: RegExp; // Whether pattern can capture namespace
}> = [
// Static patterns
{ pattern: /\bt[A-Z]?\w*\(\s*["'`]([^"'`]+)["'`]/g },
{ pattern: /\bt\w*\(\s*["'`]([^"'`]+)["'`]/g },
{
captureNs: true,
pattern: /\bt[A-Z]?\w*\(\s*["'`]([^"'`]+)["'`]\s*,\s*{[^}]*ns:\s*["'`]([^"'`]+)["'`]/g,
pattern: /\bt\w*\(\s*["'`]([^"'`]+)["'`]\s*,\s*\{[^}]*ns:\s*["'`]([^"'`]+)["'`]/g,
},
{ pattern: /i18n\.t\(\s*["'`]([^"'`]+)["'`]/g },
{ pattern: /\bt[A-Z]?\w*\([^)]*\?\s*["'`]([^"'`]+)["'`]/g },
{ pattern: /\bt[A-Z]?\w*\([^)]*:\s*["'`]([^"'`]+)["'`]/g },
{ pattern: /\bt\w*\([^)]*\?\s*["'`]([^"'`]+)["'`]/g },
{ pattern: /\bt\w*\([^)]*:\s*["'`]([^"'`]+)["'`]/g },
{ pattern: /<Trans[^>]+i18nKey=["']([^"']+)["']/g },
{ pattern: /<Trans[^>]+i18nKey={["']([^"']+)["']}/g },
{ captureNs: true, pattern: /<Trans[^>]+i18nKey=["']([^"']+)["'][\S\s]*?ns=["']([^"']+)["']/g },
{ pattern: /<Trans[^>]+i18nKey=\{["']([^"']+)["']\}/g },
{ captureNs: true, pattern: /<Trans[^>]+i18nKey=["']([^"']+)["'][\s\S]*?ns=["']([^"']+)["']/g },
{
captureNs: true,
pattern: /<Trans[^>]+i18nKey={["']([^"']+)["']}[\S\s]*?ns={["']([^"']+)["']}/g,
pattern: /<Trans[^>]+i18nKey=\{["']([^"']+)["']\}[\s\S]*?ns=\{["']([^"']+)["']\}/g,
},
// Dynamic patterns (template strings, concatenations, etc.)
// Pattern 1: t(`prefix.${var}.suffix`) - variable in the middle
{ captureNs: false, isDynamic: true, pattern: /\bt[A-Z]?\w*\(\s*`([^$`]+)\${[^}]+}([^`]*)`/g },
{ captureNs: false, isDynamic: true, pattern: /\bt\w*\(\s*`([^$`]+)\$\{[^}]+\}([^`]*)`/g },
// Pattern 2: t(`${var}.suffix`) - variable at the start
{ captureNs: false, isDynamic: true, pattern: /\bt[A-Z]?\w*\(\s*`\${[^}]+}([^`]+)`/g },
{ captureNs: false, isDynamic: true, pattern: /\bt\w*\(\s*`\$\{[^}]+\}([^`]+)`/g },
// Pattern 3: t(`prefix.${var}.suffix`, { ns: 'namespace' }) - with explicit ns
{
captureNs: true,
isDynamic: true,
pattern: /\bt[A-Z]?\w*\(\s*`([^$`]*)\${[^}]+}([^`]*)`\s*,\s*{[^}]*ns:\s*["'`]([^"'`]+)["'`]/g,
pattern: /\bt\w*\(\s*`([^$`]*)\$\{[^}]+\}([^`]*)`\s*,\s*\{[^}]*ns:\s*["'`]([^"'`]+)["'`]/g,
},
// Pattern 4: t(`${var}.suffix`, { ns: 'namespace' }) - variable at start with ns
{
captureNs: true,
isDynamic: true,
pattern: /\bt[A-Z]?\w*\(\s*`\${[^}]+}([^`]+)`\s*,\s*{[^}]*ns:\s*["'`]([^"'`]+)["'`]/g,
pattern: /\bt\w*\(\s*`\$\{[^}]+\}([^`]+)`\s*,\s*\{[^}]*ns:\s*["'`]([^"'`]+)["'`]/g,
},
// Pattern 5: String concatenation
{ isDynamic: true, pattern: /\bt[A-Z]?\w*\(\s*["'`]([^"'`]+)["'`]\s*\+/g },
{ isDynamic: true, pattern: /\bt\w*\(\s*["'`]([^"'`]+)["'`]\s*\+/g },
// Pattern 6: <Trans> with dynamic keys
{ isDynamic: true, pattern: /<Trans[^>]+i18nKey={`([^$`]+)\${[^}]+}([^`]*)`}/g },
{ isDynamic: true, pattern: /<Trans[^>]+i18nKey=\{`([^$`]+)\$\{[^}]+\}([^`]*)`\}/g },
];
let totalMatches = 0;
@@ -176,11 +176,11 @@ async function findAllTranslationCalls(): Promise<Set<string>> {
// Extract namespace from useTranslation hook
const useTranslationMatch = content.match(/useTranslation\(\s*["'`]([^"'`]+)["'`]\s*\)/g);
const useTranslationMultiMatch = content.match(/useTranslation\(\s*\[([^\]]+)]\s*\)/g);
const useTranslationMultiMatch = content.match(/useTranslation\(\s*\[([^\]]+)\]\s*\)/g);
// Extract aliases: const { t: tAuth } = useTranslation('auth')
const aliasPattern =
/const\s*{\s*t\s*:\s*(\w+)\s*}\s*=\s*useTranslation\(\s*["'`]([^"'`]+)["'`]\s*\)/g;
/const\s*\{\s*t\s*:\s*(\w+)\s*\}\s*=\s*useTranslation\(\s*["'`]([^"'`]+)["'`]\s*\)/g;
const aliasMatches = content.matchAll(aliasPattern);
const namespacesInFile = new Set<string>();
@@ -197,7 +197,7 @@ async function findAllTranslationCalls(): Promise<Set<string>> {
// Extract namespaces from useTranslation(['ns1', 'ns2'])
if (useTranslationMultiMatch) {
for (const match of useTranslationMultiMatch) {
const nsArray = match.match(/\[([^\]]+)]/)?.[1];
const nsArray = match.match(/\[([^\]]+)\]/)?.[1];
if (nsArray) {
const namespaces = nsArray.match(/["'`]([^"'`]+)["'`]/g);
if (namespaces) {
@@ -240,7 +240,7 @@ async function findAllTranslationCalls(): Promise<Set<string>> {
if (!key) continue;
// Extract function name (t, tAuth, tCommon, etc.)
const funcNameMatch = fullMatch.match(/\b(t[A-Z]?\w*)\(/);
const funcNameMatch = fullMatch.match(/\b(t\w*)\(/);
const funcName = funcNameMatch?.[1] || 't';
// Check if it's an alias with known namespace
+5 -6
View File
@@ -1,9 +1,9 @@
/* eslint-disable unicorn/prefer-top-level-await */
import { consola } from 'consola';
import { colors } from 'consola/utils';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { consola } from 'consola';
import { colors } from 'consola/utils';
import { IGNORED_FILES } from './protectedPatterns';
interface UnusedKey {
@@ -105,7 +105,6 @@ function cleanDefaultLocaleFiles(unusedKeys: UnusedKey[], dryRun: boolean = true
}
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const loadedModule = require(filePath);
const translations = loadedModule.default || loadedModule;
@@ -131,7 +130,7 @@ function cleanDefaultLocaleFiles(unusedKeys: UnusedKey[], dryRun: boolean = true
if (!dryRun) {
// Generate new content
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const newContent = generateTypeScriptContent(updatedTranslations);
// Write back to file
@@ -233,7 +232,7 @@ function needsQuotes(key: string): boolean {
// - Contains special characters (-, ., spaces, etc.)
// - Starts with a number
// - Is a reserved keyword
return !/^[$A-Z_a-z][\w$]*$/.test(key);
return !/^[$_a-z][\w$]*$/i.test(key);
}
/**
+5 -5
View File
@@ -1,10 +1,10 @@
/* eslint-disable unicorn/prefer-top-level-await */
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import prettier from '@prettier/sync';
import { consola } from 'consola';
import { colors } from 'consola/utils';
import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { toLodashPath } from '../../src/locales/utils';
import { localeDir, localeDirJsonList, localesDir, srcDefaultLocales } from './const';
@@ -134,6 +134,6 @@ const run = async () => {
run().catch((error) => {
consola.error(error);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
});
+2 -1
View File
@@ -1,8 +1,9 @@
import { existsSync } from 'node:fs';
import { consola } from 'consola';
import { colors } from 'consola/utils';
import { unset } from 'es-toolkit/compat';
import { diff } from 'just-diff';
import { existsSync } from 'node:fs';
import {
entryLocaleJsonFilepath,
+6 -6
View File
@@ -1,13 +1,13 @@
import { consola } from 'consola';
import { colors } from 'consola/utils';
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import prettier from "@prettier/sync";
import prettier from '@prettier/sync';
import { consola } from 'consola';
import { colors } from 'consola/utils';
import i18nConfig from './i18nConfig';
let prettierOptions = prettier.resolveConfig(
resolve(__dirname, '../../.prettierrc.js')
);
const prettierOptions = prettier.resolveConfig(resolve(__dirname, '../../.prettierrc.js'));
export const readJSON = (filePath: string) => {
const data = readFileSync(filePath, 'utf8');
+4 -3
View File
@@ -1,8 +1,9 @@
import { readFileSync, unlinkSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { consola } from 'consola';
import { globSync } from 'glob';
import matter from 'gray-matter';
import { readFileSync, unlinkSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
const fixWinPath = (path: string) => path.replaceAll('\\', '/');
@@ -32,7 +33,7 @@ const run = () => {
.replaceAll('}> width', '} width')
.replaceAll("'[https", "'https")
.replaceAll('"[https', '"https')
.replaceAll(/]\(http(.*)\/>\)/g, '')
.replaceAll(/\]\(http(.*)\/>\)/g, '')
.replaceAll(`\\*\\* `, '** ')
.replaceAll(` \\*\\*`, ' **')
.replaceAll(/\n{2,}/g, '\n\n');
+2 -2
View File
@@ -42,7 +42,7 @@ async function migrateFile(relativePath: string): Promise<MigrationResult | null
// Check what hooks are being imported from @/libs/next/navigation
const importMatch = content.match(
/import\s*{([^}]+)}\s*from\s*["']@\/libs\/next\/navigation["']/,
/import\s*\{([^}]+)\}\s*from\s*["']@\/libs\/next\/navigation["']/,
);
if (!importMatch) {
@@ -97,7 +97,7 @@ async function migrateFile(relativePath: string): Promise<MigrationResult | null
// Replace the old import with new imports
newContent = newContent.replace(
/import\s*{[^}]+}\s*from\s*["']@\/libs\/next\/navigation["'];?\n?/,
/import\s*\{[^}]+\}\s*from\s*["']@\/libs\/next\/navigation["'];?\n?/,
newImports.join('\n') + '\n',
);
+4 -5
View File
@@ -1,8 +1,9 @@
import { join } from 'node:path';
import * as dotenv from 'dotenv';
import dotenvExpand from 'dotenv-expand';
import { migrate as neonMigrate } from 'drizzle-orm/neon-serverless/migrator';
import { migrate as nodeMigrate } from 'drizzle-orm/node-postgres/migrator';
import { join } from 'node:path';
// @ts-ignore tsgo handle esm import cjs and compatibility issues
import { DB_FAIL_INIT_HINT, DUPLICATE_EMAIL_HINT, PGVECTOR_HINT } from './errorHint';
@@ -32,15 +33,14 @@ const runMigrations = async () => {
}
console.log('✅ database migration pass. use: %s ms', Date.now() - time);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0);
};
let connectionString = process.env.DATABASE_URL;
const connectionString = process.env.DATABASE_URL;
// only migrate database if the connection string is available
if (!isDesktop && connectionString) {
// eslint-disable-next-line unicorn/prefer-top-level-await
runMigrations().catch((err) => {
console.error('❌ Database migrate failed:', err);
@@ -56,7 +56,6 @@ if (!isDesktop && connectionString) {
console.info(DB_FAIL_INIT_HINT);
}
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
});
} else {
@@ -1,4 +1,4 @@
import { Pool as NeonPool, neonConfig } from '@neondatabase/serverless';
import { neonConfig, Pool as NeonPool } from '@neondatabase/serverless';
import { drizzle as neonDrizzle } from 'drizzle-orm/neon-serverless';
import { drizzle as nodeDrizzle } from 'drizzle-orm/node-postgres';
import { Pool as NodePool } from 'pg';
+7 -3
View File
@@ -1,4 +1,3 @@
/* eslint-disable unicorn/prefer-top-level-await */
import { sql } from 'drizzle-orm';
import { getBatchSize, getMigrationMode, isDryRun } from './_internal/config';
@@ -213,10 +212,15 @@ async function main() {
try {
await migrateFromNextAuth();
console.log('');
console.log(`${GREEN_BOLD}✅ Migration success!${RESET} (${formatDuration(Date.now() - startedAt)})`);
console.log(
`${GREEN_BOLD}✅ Migration success!${RESET} (${formatDuration(Date.now() - startedAt)})`,
);
} catch (error) {
console.log('');
console.error(`${RED_BOLD}❌ Migration failed${RESET} (${formatDuration(Date.now() - startedAt)}):`, error);
console.error(
`${RED_BOLD}❌ Migration failed${RESET} (${formatDuration(Date.now() - startedAt)}):`,
error,
);
process.exitCode = 1;
} finally {
await pool.end();
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable unicorn/prefer-top-level-await */
import { getMigrationMode } from './_internal/config';
import { db, pool, schema } from './_internal/db';
+23 -12
View File
@@ -1,9 +1,9 @@
import { execSync } from 'node:child_process';
import { createRequire } from 'node:module';
import * as dotenv from 'dotenv';
import dotenvExpand from 'dotenv-expand';
import { execSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { rm } from 'node:fs/promises';
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -22,8 +22,10 @@ if (isDesktop) {
dotenvExpand.expand(dotenv.config());
}
const AUTH_SECRET_DOC_URL = 'https://lobehub.com/docs/self-hosting/environment-variables/auth#auth-secret';
const KEY_VAULTS_SECRET_DOC_URL = 'https://lobehub.com/docs/self-hosting/environment-variables/basic#key-vaults-secret';
const AUTH_SECRET_DOC_URL =
'https://lobehub.com/docs/self-hosting/environment-variables/auth#auth-secret';
const KEY_VAULTS_SECRET_DOC_URL =
'https://lobehub.com/docs/self-hosting/environment-variables/basic#key-vaults-secret';
/**
* Check for required environment variables in server database mode
@@ -51,13 +53,14 @@ const checkRequiredEnvVars = () => {
console.error(` 📖 Documentation: ${docUrl}\n`);
}
console.error('Please configure these environment variables and redeploy.');
console.error('\n💡 TIP: If you previously used NEXT_AUTH_SECRET, simply rename it to AUTH_SECRET.');
console.error(
'\n💡 TIP: If you previously used NEXT_AUTH_SECRET, simply rename it to AUTH_SECRET.',
);
console.error('═'.repeat(70) + '\n');
process.exit(1);
}
};
const getCommandVersion = (command: string): string | null => {
try {
return execSync(`${command} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
@@ -87,7 +90,9 @@ const printEnvInfo = () => {
console.log(` APP_URL: ${process.env.APP_URL ?? '(not set)'}`);
console.log(` VERCEL_URL: ${process.env.VERCEL_URL ?? '(not set)'}`);
console.log(` VERCEL_BRANCH_URL: ${process.env.VERCEL_BRANCH_URL ?? '(not set)'}`);
console.log(` VERCEL_PROJECT_PRODUCTION_URL: ${process.env.VERCEL_PROJECT_PRODUCTION_URL ?? '(not set)'}`);
console.log(
` VERCEL_PROJECT_PRODUCTION_URL: ${process.env.VERCEL_PROJECT_PRODUCTION_URL ?? '(not set)'}`,
);
console.log(` AUTH_EMAIL_VERIFICATION: ${process.env.AUTH_EMAIL_VERIFICATION ?? '(not set)'}`);
console.log(` AUTH_ENABLE_MAGIC_LINK: ${process.env.AUTH_ENABLE_MAGIC_LINK ?? '(not set)'}`);
@@ -96,14 +101,18 @@ const printEnvInfo = () => {
console.log(` AUTH_SSO_PROVIDERS: ${ssoProviders ?? '(not set)'}`);
if (ssoProviders) {
const getEnvPrefix = (provider: string) => `AUTH_${provider.toUpperCase().replaceAll('-', '_')}`;
const getEnvPrefix = (provider: string) =>
`AUTH_${provider.toUpperCase().replaceAll('-', '_')}`;
const providers = ssoProviders.split(/[,]/).map(p => p.trim()).filter(Boolean);
const providers = ssoProviders
.split(/[,]/)
.map((p) => p.trim())
.filter(Boolean);
const missingProviders: string[] = [];
for (const provider of providers) {
const envPrefix = getEnvPrefix(provider);
const hasEnvVar = Object.keys(process.env).some(key => key.startsWith(envPrefix));
const hasEnvVar = Object.keys(process.env).some((key) => key.startsWith(envPrefix));
if (!hasEnvVar) {
missingProviders.push(provider);
}
@@ -112,7 +121,9 @@ const printEnvInfo = () => {
if (missingProviders.length > 0) {
console.log('\n ⚠️ SSO Provider Configuration Warning:');
for (const provider of missingProviders) {
console.log(` - "${provider}" is configured but no ${getEnvPrefix(provider)}_* env vars found`);
console.log(
` - "${provider}" is configured but no ${getEnvPrefix(provider)}_* env vars found`,
);
}
}
}
@@ -121,7 +132,7 @@ const printEnvInfo = () => {
};
// 创建需要排除的特性映射
/* eslint-disable sort-keys-fix/sort-keys-fix */
const partialBuildPages = [
// no need for bundle analyzer (frontend only)
{
+2 -1
View File
@@ -1,6 +1,7 @@
import { DEFAULT_MODEL_PROVIDER_LIST } from 'model-bank/modelProviders';
import { resolve } from 'node:path';
import { DEFAULT_MODEL_PROVIDER_LIST } from 'model-bank/modelProviders';
export const root = resolve(__dirname, '../..');
export interface DataItem {
+2 -1
View File
@@ -2,7 +2,8 @@ import { consola } from 'consola';
import { markdownTable } from 'markdown-table';
import urlJoin from 'url-join';
import { AGENT_SPLIT, DataItem } from './const';
import type { DataItem } from './const';
import { AGENT_SPLIT } from './const';
import {
fetchAgentIndex,
genLink,
+2 -1
View File
@@ -2,7 +2,8 @@ import { consola } from 'consola';
import { markdownTable } from 'markdown-table';
import urlJoin from 'url-join';
import { DataItem, PLUGIN_SPLIT } from './const';
import type { DataItem } from './const';
import { PLUGIN_SPLIT } from './const';
import {
fetchPluginIndex,
genLink,
+3 -2
View File
@@ -1,6 +1,7 @@
import { resolve } from 'node:path';
import { consola } from 'consola';
import { readJSONSync } from 'fs-extra';
import { resolve } from 'node:path';
import urlJoin from 'url-join';
import { PROVIDER_LIST, PROVIDER_SPLIT, root } from './const';
@@ -28,7 +29,7 @@ const runProviderTable = async (lang?: string) => {
)
.join('\n'),
`<details><summary><kbd>See more providers (+${PROVIDER_LIST.length - 10})</kbd></summary>`,
PROVIDER_LIST.slice(10, PROVIDER_LIST.length)
PROVIDER_LIST.slice(10)
.map((item) =>
genProviderTable({
...item,
+2 -1
View File
@@ -1,7 +1,8 @@
import { kebabCase } from 'es-toolkit/compat';
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { kebabCase } from 'es-toolkit/compat';
import { AGENT_I18N_URL, AGENT_URL, PLUGIN_I18N_URL, PLUGIN_URL, root } from './const';
const fetchIndex = async (url: string) => {
+2 -2
View File
@@ -1,4 +1,4 @@
import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { join, relative } from 'node:path';
interface ReplaceConfig {
@@ -230,7 +230,7 @@ function parseArgs(): ReplaceConfig | null {
if (!componentsStr || !fromPackage || !toPackage) {
console.error('❌ 错误: 必须指定 --components, --from 和 --to 参数');
console.error('使用 --help 查看帮助信息');
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
}
+1 -1
View File
@@ -22,7 +22,7 @@ const PROXYCHAINS_CONF_PATH = '/etc/proxychains4.conf';
const isValidIP = (ip, version = 4) => {
const ipv4Regex = /^(25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3}$/;
const ipv6Regex =
/^(([\da-f]{1,4}:){7}[\da-f]{1,4}|([\da-f]{1,4}:){1,7}:|([\da-f]{1,4}:){1,6}:[\da-f]{1,4}|([\da-f]{1,4}:){1,5}(:[\da-f]{1,4}){1,2}|([\da-f]{1,4}:){1,4}(:[\da-f]{1,4}){1,3}|([\da-f]{1,4}:){1,3}(:[\da-f]{1,4}){1,4}|([\da-f]{1,4}:){1,2}(:[\da-f]{1,4}){1,5}|[\da-f]{1,4}:((:[\da-f]{1,4}){1,6})|:((:[\da-f]{1,4}){1,7}|:)|fe80:(:[\da-f]{0,4}){0,4}%[\da-z]+|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}\d){0,1}\d)\.){3}(25[0-5]|(2[0-4]|1{0,1}\d){0,1}\d)|([\da-f]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}\d){0,1}\d)\.){3}(25[0-5]|(2[0-4]|1{0,1}\d){0,1}\d))$/;
/^(([\da-f]{1,4}:){7}[\da-f]{1,4}|([\da-f]{1,4}:){1,7}:|([\da-f]{1,4}:){1,6}:[\da-f]{1,4}|([\da-f]{1,4}:){1,5}(:[\da-f]{1,4}){1,2}|([\da-f]{1,4}:){1,4}(:[\da-f]{1,4}){1,3}|([\da-f]{1,4}:){1,3}(:[\da-f]{1,4}){1,4}|([\da-f]{1,4}:){1,2}(:[\da-f]{1,4}){1,5}|[\da-f]{1,4}:((:[\da-f]{1,4}){1,6})|:((:[\da-f]{1,4}){1,7}|:)|fe80:(:[\da-f]{0,4}){0,4}%[\da-z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d)|([\da-f]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d))$/;
switch (version) {
case 4: {
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# 停止并删除已存在的容器
docker stop postgres-paradedb 2>/dev/null && docker rm postgres-paradedb 2>/dev/null
docker stop postgres-paradedb 2> /dev/null && docker rm postgres-paradedb 2> /dev/null
# 启动pgvector容器
docker run --name postgres-paradedb \
+2 -2
View File
@@ -32,10 +32,10 @@ const shouldBuild = shouldProceedBuild();
console.log('shouldBuild:', shouldBuild);
if (shouldBuild) {
console.log('✅ - Build can proceed');
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
} else {
console.log('🛑 - Build cancelled');
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0);
}