@lobehub/lobehub 2.0.0-next.344 → 2.0.0-next.346

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/.cursor/rules/i18n.mdc +1 -1
  2. package/.cursor/rules/modal-imperative.mdc +162 -0
  3. package/.cursor/rules/rules-index.mdc +1 -0
  4. package/.env.example +0 -14
  5. package/.eslintrc.js +8 -1
  6. package/CHANGELOG.md +66 -0
  7. package/CLAUDE.md +4 -2
  8. package/Dockerfile +3 -13
  9. package/README.md +3 -5
  10. package/README.zh-CN.md +3 -5
  11. package/changelog/v1.json +20 -0
  12. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +11 -42
  13. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +10 -41
  14. package/e2e/src/support/webServer.ts +2 -0
  15. package/locales/ar/error.json +0 -4
  16. package/locales/bg-BG/error.json +0 -4
  17. package/locales/de-DE/error.json +0 -4
  18. package/locales/en-US/error.json +0 -4
  19. package/locales/es-ES/error.json +0 -4
  20. package/locales/fa-IR/error.json +0 -4
  21. package/locales/fr-FR/error.json +0 -4
  22. package/locales/it-IT/error.json +0 -4
  23. package/locales/ja-JP/error.json +0 -4
  24. package/locales/ko-KR/error.json +0 -4
  25. package/locales/nl-NL/error.json +0 -4
  26. package/locales/pl-PL/error.json +0 -4
  27. package/locales/pt-BR/error.json +0 -4
  28. package/locales/ru-RU/error.json +0 -4
  29. package/locales/tr-TR/error.json +0 -4
  30. package/locales/vi-VN/error.json +0 -4
  31. package/locales/zh-CN/error.json +0 -4
  32. package/locales/zh-TW/error.json +0 -4
  33. package/package.json +12 -12
  34. package/packages/builtin-agents/package.json +2 -0
  35. package/packages/builtin-agents/src/agents/agent-builder/index.ts +4 -2
  36. package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +4 -2
  37. package/packages/builtin-agents/src/agents/page-agent/index.ts +5 -2
  38. package/packages/context-engine/src/engine/messages/MessagesEngine.ts +9 -9
  39. package/packages/context-engine/src/providers/GroupContextInjector.ts +19 -33
  40. package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +79 -43
  41. package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +5 -15
  42. package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts +24 -3
  43. package/packages/file-loaders/package.json +1 -1
  44. package/packages/file-loaders/src/loadFile.ts +10 -15
  45. package/packages/file-loaders/src/loaders/index.ts +68 -19
  46. package/packages/file-loaders/src/loaders/pdf/__snapshots__/index.test.ts.snap +1 -1
  47. package/packages/file-loaders/test/__snapshots__/loaders.test.ts.snap +1 -1
  48. package/packages/model-bank/src/modelProviders/comfyui.ts +0 -1
  49. package/packages/model-bank/src/modelProviders/fal.ts +0 -1
  50. package/packages/types/src/fetch.ts +1 -2
  51. package/packages/utils/src/server/__tests__/auth.test.ts +0 -47
  52. package/packages/utils/src/server/auth.ts +1 -9
  53. package/pnpm-workspace.yaml +1 -0
  54. package/scripts/_shared/checkDeprecatedClerkEnv.js +42 -0
  55. package/scripts/changelogWorkflow/buildStaticChangelog.ts +2 -1
  56. package/scripts/clerk-to-betterauth/_internal/types.ts +53 -20
  57. package/scripts/clerk-to-betterauth/export-clerk-users-with-api.ts +43 -36
  58. package/scripts/countEnWord.ts +1 -1
  59. package/scripts/electronWorkflow/modifiers/appCode.mts +2 -131
  60. package/scripts/i18nWorkflow/protectedPatterns.ts +1 -2
  61. package/scripts/prebuild.mts +10 -8
  62. package/scripts/serverLauncher/startServer.js +23 -5
  63. package/src/app/(backend)/middleware/auth/index.test.ts +8 -4
  64. package/src/app/(backend)/middleware/auth/index.ts +0 -15
  65. package/src/app/(backend)/middleware/auth/utils.test.ts +0 -28
  66. package/src/app/(backend)/middleware/auth/utils.ts +2 -17
  67. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +3 -51
  68. package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -4
  69. package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +7 -6
  70. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -16
  71. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/index.tsx +1 -1
  72. package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +13 -13
  73. package/src/app/[variants]/(main)/home/features/RecentPage/Item.tsx +2 -2
  74. package/src/app/[variants]/(main)/resource/features/store/action.ts +2 -2
  75. package/src/app/[variants]/(main)/resource/features/store/initialState.ts +2 -2
  76. package/src/app/[variants]/(main)/resource/store/action.ts +2 -2
  77. package/src/app/[variants]/(main)/resource/store/initialState.ts +2 -2
  78. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +3 -21
  79. package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -2
  80. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -22
  81. package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +12 -14
  82. package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +8 -14
  83. package/src/app/[variants]/(main)/settings/skill/index.tsx +7 -5
  84. package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +2 -35
  85. package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -20
  86. package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -2
  87. package/src/app/[variants]/(mobile)/me/profile/features/Category.tsx +3 -13
  88. package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +2 -3
  89. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
  90. package/src/app/[variants]/share/t/[id]/index.tsx +1 -1
  91. package/src/app/robots.tsx +1 -1
  92. package/src/envs/auth.ts +2 -27
  93. package/src/envs/llm.ts +2 -2
  94. package/src/features/AgentSetting/AgentPlugin/index.tsx +9 -12
  95. package/src/features/ChatInput/ActionBar/Tools/index.tsx +7 -5
  96. package/src/features/ChatMiniMap/utils.ts +1 -1
  97. package/src/features/CommandMenu/SearchResults.tsx +1 -1
  98. package/src/features/Conversation/ChatList/components/AutoScroll/DebugInspector.tsx +166 -0
  99. package/src/features/Conversation/ChatList/components/AutoScroll/index.tsx +86 -0
  100. package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +11 -17
  101. package/src/features/Conversation/Messages/AgentCouncil/components/AutoScrollShadow.tsx +25 -14
  102. package/src/features/Conversation/Messages/AgentCouncil/components/CouncilMember.tsx +1 -1
  103. package/src/features/FileViewer/Renderer/PDF/index.tsx +5 -8
  104. package/src/features/IntegrationDetailModal/IntegrationDetailContent.tsx +305 -0
  105. package/src/features/IntegrationDetailModal/index.tsx +21 -283
  106. package/src/features/MCPPluginDetail/Deployment/index.tsx +1 -1
  107. package/src/features/MCPPluginDetail/Schema/Prompts.tsx +1 -1
  108. package/src/features/MCPPluginDetail/Schema/Tools.tsx +1 -1
  109. package/src/features/ProfileEditor/AgentTool.tsx +14 -20
  110. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +0 -8
  111. package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/NoteFileItem.tsx +1 -1
  112. package/src/features/ResourceManager/index.tsx +1 -1
  113. package/src/features/ShareModal/SharePdf/PdfPreview.tsx +4 -4
  114. package/src/features/SkillStore/LobeHubList/index.tsx +50 -87
  115. package/src/features/SkillStore/Search/index.tsx +1 -1
  116. package/src/features/SkillStore/{Content.tsx → SkillStoreContent.tsx} +3 -8
  117. package/src/features/SkillStore/index.tsx +15 -33
  118. package/src/features/User/UserPanel/PanelContent.tsx +0 -8
  119. package/src/features/User/__tests__/PanelContent.test.tsx +1 -35
  120. package/src/features/User/__tests__/UserAvatar.test.tsx +30 -57
  121. package/src/features/User/__tests__/useMenu.test.tsx +2 -43
  122. package/src/layout/AuthProvider/index.tsx +0 -5
  123. package/src/libs/next/config/define-config.ts +20 -15
  124. package/src/libs/next/proxy/createRouteMatcher.test.ts +121 -0
  125. package/src/libs/next/proxy/createRouteMatcher.ts +18 -0
  126. package/src/libs/next/proxy/define-config.ts +4 -53
  127. package/src/libs/next-auth/adapter/index.ts +1 -2
  128. package/src/libs/oidc-provider/provider.test.ts +5 -316
  129. package/src/libs/pdfjs/pdf.worker.ts +1 -0
  130. package/src/libs/pdfjs/worker.ts +12 -0
  131. package/src/libs/trpc/lambda/context.test.ts +0 -13
  132. package/src/libs/trpc/lambda/context.ts +3 -22
  133. package/src/libs/trpc/middleware/userAuth.ts +2 -4
  134. package/src/libs/trusted-client/getSessionUser.ts +2 -17
  135. package/src/locales/default/error.ts +0 -6
  136. package/src/locales/default/index.ts +0 -2
  137. package/src/proxy.ts +0 -1
  138. package/src/server/routers/lambda/__tests__/user.test.ts +0 -71
  139. package/src/server/routers/lambda/user.ts +6 -63
  140. package/src/server/services/changelog/index.test.ts +3 -2
  141. package/src/server/services/changelog/index.ts +1 -1
  142. package/src/server/services/user/index.ts +0 -83
  143. package/src/services/chat/index.ts +1 -2
  144. package/src/services/chat/mecha/agentConfigResolver.test.ts +43 -0
  145. package/src/services/chat/mecha/agentConfigResolver.ts +3 -1
  146. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +58 -14
  147. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +10 -2
  148. package/src/store/user/slices/auth/action.test.ts +1 -81
  149. package/src/store/user/slices/auth/action.ts +3 -28
  150. package/src/store/user/slices/auth/initialState.ts +1 -18
  151. package/src/store/user/slices/auth/selectors.test.ts +2 -127
  152. package/src/store/user/slices/auth/selectors.ts +1 -21
  153. package/src/utils/errorResponse.ts +1 -4
  154. package/src/utils/markdownToTxt.ts +20 -0
  155. package/locales/ar/clerk.json +0 -545
  156. package/locales/bg-BG/clerk.json +0 -545
  157. package/locales/de-DE/clerk.json +0 -545
  158. package/locales/en-US/clerk.json +0 -545
  159. package/locales/es-ES/clerk.json +0 -545
  160. package/locales/fa-IR/clerk.json +0 -545
  161. package/locales/fr-FR/clerk.json +0 -545
  162. package/locales/it-IT/clerk.json +0 -545
  163. package/locales/ja-JP/clerk.json +0 -545
  164. package/locales/ko-KR/clerk.json +0 -545
  165. package/locales/nl-NL/clerk.json +0 -545
  166. package/locales/pl-PL/clerk.json +0 -545
  167. package/locales/pt-BR/clerk.json +0 -545
  168. package/locales/ru-RU/clerk.json +0 -545
  169. package/locales/tr-TR/clerk.json +0 -545
  170. package/locales/vi-VN/clerk.json +0 -545
  171. package/locales/zh-CN/clerk.json +0 -545
  172. package/locales/zh-TW/clerk.json +0 -545
  173. package/src/app/(backend)/api/webhooks/clerk/__tests__/fixtures/createUser.json +0 -73
  174. package/src/app/(backend)/api/webhooks/clerk/route.ts +0 -95
  175. package/src/app/(backend)/api/webhooks/clerk/validateRequest.ts +0 -22
  176. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +0 -27
  177. package/src/app/[variants]/(main)/settings/security/features/ClerkProfile.tsx +0 -67
  178. package/src/features/Conversation/ChatList/components/AutoScroll.tsx +0 -25
  179. package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +0 -40
  180. package/src/layout/AuthProvider/Clerk/index.tsx +0 -54
  181. package/src/layout/AuthProvider/Clerk/useAppearance.ts +0 -133
  182. package/src/libs/clerk-auth/index.test.ts +0 -216
  183. package/src/libs/clerk-auth/index.ts +0 -80
  184. package/src/locales/default/clerk.ts +0 -677
  185. package/src/server/services/user/index.test.ts +0 -220
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Shared utility to check for deprecated Clerk environment variables.
3
+ * Used by both prebuild.mts (build time) and startServer.js (Docker runtime).
4
+ *
5
+ * IMPORTANT: Keep this file as CommonJS (.js) for compatibility with startServer.js
6
+ */
7
+
8
+ const CLERK_MIGRATION_DOC_URL =
9
+ 'https://lobehub.com/docs/self-hosting/advanced/auth/clerk-to-betterauth';
10
+
11
+ const CLERK_ENV_VARS = [
12
+ 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
13
+ 'CLERK_SECRET_KEY',
14
+ 'CLERK_WEBHOOK_SECRET',
15
+ ];
16
+
17
+ /**
18
+ * Check for deprecated Clerk environment variables and exit if found
19
+ * @param {object} options
20
+ * @param {string} [options.action='redeploy'] - Action hint in error message ('redeploy' or 'restart')
21
+ */
22
+ function checkDeprecatedClerkEnv(options = {}) {
23
+ const { action = 'redeploy' } = options;
24
+ const foundClerkEnvVars = CLERK_ENV_VARS.filter((envVar) => process.env[envVar]);
25
+
26
+ if (foundClerkEnvVars.length > 0) {
27
+ console.error('\n' + '═'.repeat(70));
28
+ console.error('❌ ERROR: Clerk authentication is no longer supported!');
29
+ console.error('═'.repeat(70));
30
+ console.error('\nDetected deprecated Clerk environment variables:');
31
+ for (const envVar of foundClerkEnvVars) {
32
+ console.error(` • ${envVar}`);
33
+ }
34
+ console.error('\nClerk has been removed from LobeChat. Please migrate to Better Auth.');
35
+ console.error(`\n📖 Migration guide: ${CLERK_MIGRATION_DOC_URL}`);
36
+ console.error(`\nAfter migration, remove the Clerk environment variables and ${action}.`);
37
+ console.error('═'.repeat(70) + '\n');
38
+ process.exit(1);
39
+ }
40
+ }
41
+
42
+ module.exports = { checkDeprecatedClerkEnv };
@@ -1,10 +1,11 @@
1
1
  import { consola } from 'consola';
2
2
  import { readJsonSync, writeJSONSync } from 'fs-extra';
3
- import { markdownToTxt } from 'markdown-to-txt';
4
3
  import { existsSync, readFileSync } from 'node:fs';
5
4
  import { resolve } from 'node:path';
6
5
  import semver from 'semver';
7
6
 
7
+ import { markdownToTxt } from '@/utils/markdownToTxt';
8
+
8
9
  import { CHANGELOG_DIR, CHANGELOG_FILE } from './const';
9
10
 
10
11
  export interface ChangelogStaticItem {
@@ -1,5 +1,3 @@
1
- import type { ExternalAccountJSON, UserJSON } from '@clerk/backend';
2
-
3
1
  export type ClerkToBetterAuthMode = 'test' | 'prod';
4
2
  export type DatabaseDriver = 'neon' | 'node';
5
3
 
@@ -19,27 +17,62 @@ export type CSVUserRow = {
19
17
  verified_phone_numbers: string;
20
18
  };
21
19
 
22
- export type ClerkExternalAccount = Pick<
23
- ExternalAccountJSON,
24
- 'id' | 'provider' | 'provider_user_id' | 'approved_scopes'
25
- > & {
20
+ // Clerk API response types (no SDK dependency)
21
+ export interface ClerkApiExternalAccount {
22
+ approved_scopes: string;
23
+ created_at?: number;
24
+ id: string;
25
+ provider: string;
26
+ provider_user_id: string;
27
+ updated_at?: number;
28
+ verification?: { status: string };
29
+ }
30
+
31
+ export interface ClerkApiEmailAddress {
32
+ email_address: string;
33
+ id: string;
34
+ }
35
+
36
+ export interface ClerkApiUser {
37
+ banned: boolean;
38
+ created_at: number;
39
+ email_addresses?: ClerkApiEmailAddress[];
40
+ external_accounts?: ClerkApiExternalAccount[];
41
+ id: string;
42
+ image_url: string;
43
+ lockout_expires_in_seconds: number | null;
44
+ password_enabled: boolean;
45
+ password_last_updated_at: number | null;
46
+ primary_email_address_id: string | null;
47
+ two_factor_enabled: boolean;
48
+ updated_at: number;
49
+ }
50
+
51
+ export interface ClerkApiUserListResponse {
52
+ data: ClerkApiUser[];
53
+ total_count: number;
54
+ }
55
+
56
+ export interface ClerkExternalAccount {
57
+ approved_scopes: string;
26
58
  created_at?: number;
59
+ id: string;
60
+ provider: string;
61
+ provider_user_id: string;
27
62
  updated_at?: number;
28
63
  verificationStatus?: boolean;
29
- };
64
+ }
30
65
 
31
- export type ClerkUser = Pick<
32
- UserJSON,
33
- | 'id'
34
- | 'image_url'
35
- | 'created_at'
36
- | 'updated_at'
37
- | 'password_last_updated_at'
38
- | 'password_enabled'
39
- | 'banned'
40
- | 'two_factor_enabled'
41
- | 'lockout_expires_in_seconds'
42
- > & {
66
+ export interface ClerkUser {
67
+ banned: boolean;
68
+ created_at: number;
43
69
  external_accounts: ClerkExternalAccount[];
70
+ id: string;
71
+ image_url: string;
72
+ lockout_expires_in_seconds: number | null;
73
+ password_enabled: boolean;
74
+ password_last_updated_at: number | null;
44
75
  primaryEmail?: string;
45
- };
76
+ two_factor_enabled: boolean;
77
+ updated_at: number;
78
+ }
@@ -1,10 +1,9 @@
1
1
  /* eslint-disable unicorn/prefer-top-level-await, unicorn/no-process-exit */
2
- import { type User, createClerkClient } from '@clerk/backend';
3
2
  import { writeFile } from 'node:fs/promises';
4
3
 
5
4
  import { getClerkSecret, getMigrationMode, resolveDataPaths } from './_internal/config';
6
5
  import './_internal/env';
7
- import { ClerkUser } from './_internal/types';
6
+ import { ClerkApiUser, ClerkUser } from './_internal/types';
8
7
 
9
8
  /**
10
9
  * Fetch all Clerk users via REST API and persist them into a local JSON file.
@@ -24,50 +23,58 @@ const ORDER_BY = '+created_at';
24
23
  const DEFAULT_OUTPUT_PATH = resolveDataPaths().clerkUsersPath;
25
24
  const formatDuration = (ms: number) => `${(ms / 1000).toFixed(1)}s`;
26
25
 
26
+ const CLERK_API_BASE = 'https://api.clerk.com/v1';
27
+
27
28
  const sleep = (ms: number) =>
28
29
  new Promise<void>((resolve) => {
29
30
  setTimeout(resolve, ms);
30
31
  });
31
32
 
32
- function getClerkClient(secretKey: string) {
33
- return createClerkClient({
34
- secretKey,
33
+ async function fetchClerkApi<T>(secretKey: string, endpoint: string): Promise<T> {
34
+ const response = await fetch(`${CLERK_API_BASE}${endpoint}`, {
35
+ headers: {
36
+ Authorization: `Bearer ${secretKey}`,
37
+ },
35
38
  });
36
- }
37
39
 
38
- function mapClerkUser(user: User): ClerkUser {
39
- const raw = user.raw!;
40
+ if (!response.ok) {
41
+ throw new Error(`Clerk API error: ${response.status} ${response.statusText}`);
42
+ }
43
+
44
+ return response.json() as Promise<T>;
45
+ }
40
46
 
41
- const primaryEmail = raw.email_addresses?.find(
42
- (email) => email.id === raw.primary_email_address_id,
47
+ function mapClerkUser(user: ClerkApiUser): ClerkUser {
48
+ const primaryEmail = user.email_addresses?.find(
49
+ (email) => email.id === user.primary_email_address_id,
43
50
  )?.email_address;
44
51
 
45
52
  return {
46
- banned: raw.banned,
47
- created_at: raw.created_at,
48
- external_accounts: (raw.external_accounts ?? []).map((acc) => ({
53
+ banned: user.banned,
54
+ created_at: user.created_at,
55
+ external_accounts: (user.external_accounts ?? []).map((acc) => ({
49
56
  approved_scopes: acc.approved_scopes,
50
- created_at: (acc as any).created_at,
57
+ created_at: acc.created_at,
51
58
  id: acc.id,
52
59
  provider: acc.provider,
53
60
  provider_user_id: acc.provider_user_id,
54
- updated_at: (acc as any).updated_at,
61
+ updated_at: acc.updated_at,
55
62
  verificationStatus: acc.verification?.status === 'verified',
56
63
  })),
57
- id: raw.id,
58
- image_url: raw.image_url,
59
- lockout_expires_in_seconds: raw.lockout_expires_in_seconds,
60
- password_enabled: raw.password_enabled,
61
- password_last_updated_at: raw.password_last_updated_at,
64
+ id: user.id,
65
+ image_url: user.image_url,
66
+ lockout_expires_in_seconds: user.lockout_expires_in_seconds,
67
+ password_enabled: user.password_enabled,
68
+ password_last_updated_at: user.password_last_updated_at,
62
69
  primaryEmail,
63
- two_factor_enabled: raw.two_factor_enabled,
64
- updated_at: raw.updated_at,
70
+ two_factor_enabled: user.two_factor_enabled,
71
+ updated_at: user.updated_at,
65
72
  } satisfies ClerkUser;
66
73
  }
67
74
 
68
75
  async function fetchClerkUserPage(
69
76
  offset: number,
70
- clerkClient: ReturnType<typeof getClerkClient>,
77
+ secretKey: string,
71
78
  pageIndex: number,
72
79
  ): Promise<ClerkUser[]> {
73
80
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt += 1) {
@@ -76,12 +83,14 @@ async function fetchClerkUserPage(
76
83
  `🚚 [clerk-export] Fetching page #${pageIndex + 1} offset=${offset} limit=${PAGE_SIZE} (attempt ${attempt}/${MAX_RETRIES})`,
77
84
  );
78
85
 
79
- const { data } = await clerkClient.users.getUserList({
80
- limit: PAGE_SIZE,
81
- offset,
82
- orderBy: ORDER_BY,
86
+ const params = new URLSearchParams({
87
+ limit: String(PAGE_SIZE),
88
+ offset: String(offset),
89
+ order_by: ORDER_BY,
83
90
  });
84
91
 
92
+ const data = await fetchClerkApi<ClerkApiUser[]>(secretKey, `/users?${params}`);
93
+
85
94
  console.log(
86
95
  `📥 [clerk-export] Received page #${pageIndex + 1} (${data.length} users) offset=${offset}`,
87
96
  );
@@ -138,16 +147,14 @@ async function runWithConcurrency<T>(
138
147
  }
139
148
 
140
149
  async function fetchAllClerkUsers(secretKey: string): Promise<ClerkUser[]> {
141
- const clerkClient = getClerkClient(secretKey);
142
150
  const userMap = new Map<string, ClerkUser>();
143
151
 
144
- const firstPageResponse = await clerkClient.users.getUserList({
145
- limit: PAGE_SIZE,
146
- offset: 0,
147
- orderBy: ORDER_BY,
148
- });
149
-
150
- const totalCount = firstPageResponse.totalCount ?? firstPageResponse.data.length;
152
+ // Get total count first
153
+ const countResponse = await fetchClerkApi<{ total_count: number }>(
154
+ secretKey,
155
+ '/users/count',
156
+ );
157
+ const totalCount = countResponse.total_count;
151
158
  const totalPages = Math.ceil(totalCount / PAGE_SIZE);
152
159
  const offsets = Array.from({ length: totalPages }, (_, pageIndex) => pageIndex * PAGE_SIZE);
153
160
 
@@ -156,7 +163,7 @@ async function fetchAllClerkUsers(secretKey: string): Promise<ClerkUser[]> {
156
163
  );
157
164
 
158
165
  await runWithConcurrency(offsets, CONCURRENCY, async (offset, index) => {
159
- const page = await fetchClerkUserPage(offset, clerkClient, index);
166
+ const page = await fetchClerkUserPage(offset, secretKey, index);
160
167
 
161
168
  for (const user of page) {
162
169
  userMap.set(user.id, user);
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  // 配置项
5
5
  const config: Config = {
6
6
  dirPath: './locales/en-US', // 替换为你的目录路径
7
- ignoredFiles: ['clerk', 'models', 'providers', 'auth'], // 需要忽略的文件名
7
+ ignoredFiles: ['models', 'providers', 'auth'], // 需要忽略的文件名
8
8
  };
9
9
 
10
10
  interface FileCount {
@@ -138,124 +138,6 @@ const assertSpeedInsightsAndAnalyticsRemoved = (code: string) =>
138
138
  !/import\s+\{\s*SpeedInsights\s*\}\s+from\b/.test(code) &&
139
139
  !/import\s+Analytics\s+from\b/.test(code);
140
140
 
141
- const removeClerkLogic = (code: string) => {
142
- const ast = parse(Lang.Tsx, code);
143
- const root = ast.root();
144
- const edits: Array<{ start: number; end: number; text: string }> = [];
145
-
146
- // Remove Clerk import - try multiple patterns
147
- const clerkImportPatterns = [
148
- { pattern: 'import Clerk from $SOURCE' },
149
- { pattern: "import Clerk from './Clerk'" },
150
- { pattern: "import Clerk from './Clerk/index'" },
151
- ];
152
-
153
- for (const pattern of clerkImportPatterns) {
154
- const clerkImport = root.find({
155
- rule: pattern,
156
- });
157
- if (clerkImport) {
158
- const range = clerkImport.range();
159
- edits.push({ start: range.start.index, end: range.end.index, text: '' });
160
- break;
161
- }
162
- }
163
-
164
- const findClerkIfStatement = () => {
165
- const directMatch = root.find({
166
- rule: {
167
- pattern: 'if (authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH) { $$$ }',
168
- },
169
- });
170
-
171
- if (directMatch) return directMatch;
172
-
173
- const allIfStatements = root.findAll({
174
- rule: {
175
- kind: 'if_statement',
176
- },
177
- });
178
-
179
- for (const ifStmt of allIfStatements) {
180
- const condition = ifStmt.find({
181
- rule: {
182
- pattern: 'authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH',
183
- },
184
- });
185
-
186
- if (condition) return ifStmt;
187
- }
188
-
189
- return null;
190
- };
191
-
192
- const clerkIfStatement = findClerkIfStatement();
193
-
194
- if (clerkIfStatement) {
195
- const ifRange = clerkIfStatement.range();
196
- const elseClause = clerkIfStatement.find({
197
- rule: {
198
- kind: 'else_clause',
199
- },
200
- });
201
-
202
- if (elseClause) {
203
- const elseIfStmt = elseClause.find({
204
- rule: {
205
- kind: 'if_statement',
206
- },
207
- });
208
-
209
- if (elseIfStmt) {
210
- // Promote the first else-if to a top-level if and keep the rest of the chain
211
- const elseRange = elseClause.range();
212
- const replacement = code
213
- .slice(elseRange.start.index, elseRange.end.index)
214
- .replace(/^\s*else\s+/, '');
215
-
216
- edits.push({
217
- start: ifRange.start.index,
218
- end: ifRange.end.index,
219
- text: replacement,
220
- });
221
- } else {
222
- const elseBlock = elseClause.find({
223
- rule: {
224
- kind: 'statement_block',
225
- },
226
- });
227
-
228
- if (elseBlock) {
229
- edits.push({
230
- start: ifRange.start.index,
231
- end: ifRange.end.index,
232
- text: code.slice(elseBlock.range().start.index, elseBlock.range().end.index),
233
- });
234
- } else {
235
- edits.push({ start: ifRange.start.index, end: ifRange.end.index, text: '' });
236
- }
237
- }
238
- } else {
239
- edits.push({ start: ifRange.start.index, end: ifRange.end.index, text: '' });
240
- }
241
- }
242
-
243
- // Apply edits
244
- if (edits.length === 0) return code;
245
-
246
- edits.sort((a, b) => b.start - a.start);
247
- let result = code;
248
- for (const edit of edits) {
249
- result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
250
- }
251
-
252
- return result;
253
- };
254
-
255
- const assertClerkLogicRemoved = (code: string) =>
256
- !/\bNEXT_PUBLIC_ENABLE_CLERK_AUTH\b/.test(code) &&
257
- !/\bauthEnv\.NEXT_PUBLIC_ENABLE_CLERK_AUTH\b/.test(code);
258
-
259
141
  const removeManifestFromMetadata = (code: string) => {
260
142
  const ast = parse(Lang.Tsx, code);
261
143
  const root = ast.root();
@@ -387,17 +269,7 @@ export const modifyAppCode = async (TEMP_DIR: string) => {
387
269
  assertAfter: assertSpeedInsightsAndAnalyticsRemoved,
388
270
  });
389
271
 
390
- // 6. Remove Clerk logic from src/layout/AuthProvider/index.tsx
391
- const authProviderPath = path.join(TEMP_DIR, 'src/layout/AuthProvider/index.tsx');
392
- console.log(' Processing src/layout/AuthProvider/index.tsx...');
393
- await updateFile({
394
- filePath: authProviderPath,
395
- name: 'modifyAppCode:removeClerkLogic',
396
- transformer: removeClerkLogic,
397
- assertAfter: assertClerkLogicRemoved,
398
- });
399
-
400
- // 7. Replace mdx Image component with next/image export
272
+ // 6. Replace mdx Image component with next/image export
401
273
  const mdxImagePath = path.join(TEMP_DIR, 'src/components/mdx/Image.tsx');
402
274
  console.log(' Processing src/components/mdx/Image.tsx...');
403
275
  await writeFileEnsuring({
@@ -407,7 +279,7 @@ export const modifyAppCode = async (TEMP_DIR: string) => {
407
279
  assertAfter: (code) => normalizeEol(code).trim() === "export { default } from 'next/image';",
408
280
  });
409
281
 
410
- // 8. Remove manifest from metadata
282
+ // 7. Remove manifest from metadata
411
283
  const metadataPath = path.join(TEMP_DIR, 'src/app/[variants]/metadata.ts');
412
284
  console.log(' Processing src/app/[variants]/metadata.ts...');
413
285
  await updateFile({
@@ -424,7 +296,6 @@ if (isDirectRun(import.meta.url)) {
424
296
  { lang: Lang.Tsx, path: 'src/layout/GlobalProvider/index.tsx' },
425
297
  { lang: Lang.Tsx, path: 'src/app/[variants]/(main)/settings/features/SettingsContent.tsx' },
426
298
  { lang: Lang.Tsx, path: 'src/app/[variants]/layout.tsx' },
427
- { lang: Lang.Tsx, path: 'src/layout/AuthProvider/index.tsx' },
428
299
  { lang: Lang.Tsx, path: 'src/components/mdx/Image.tsx' },
429
300
  { lang: Lang.Tsx, path: 'src/app/[variants]/metadata.ts' },
430
301
  ]);
@@ -11,7 +11,6 @@
11
11
  * Files to ignore at file level (won't be scanned at all)
12
12
  */
13
13
  export const IGNORED_FILES = [
14
- 'clerk.ts', // Clerk third-party library translations
15
14
  'providers.ts', // Dynamically generated from DEFAULT_MODEL_PROVIDER_LIST
16
15
  'models.ts', // Dynamically generated from LOBE_DEFAULT_MODEL_LIST
17
16
  'auth.ts', // Auth-related dynamic keys
@@ -77,7 +76,7 @@ export const PROTECTED_KEY_PATTERNS = [
77
76
  * How to use:
78
77
  *
79
78
  * 1. IGNORED_FILES - Files to completely skip during analysis:
80
- * Add filename with .ts extension (e.g., 'clerk.ts')
79
+ * Add filename with .ts extension (e.g., 'auth.ts')
81
80
  * These files won't be scanned at all
82
81
  *
83
82
  * 2. PROTECTED_KEY_PATTERNS - Namespace/patterns to protect:
@@ -1,4 +1,5 @@
1
1
  import { execSync } from 'node:child_process';
2
+ import { createRequire } from 'node:module';
2
3
  import * as dotenv from 'dotenv';
3
4
  import dotenvExpand from 'dotenv-expand';
4
5
  import { existsSync } from 'node:fs';
@@ -6,6 +7,10 @@ import { rm } from 'node:fs/promises';
6
7
  import path from 'node:path';
7
8
  import { fileURLToPath } from 'node:url';
8
9
 
10
+ // Use createRequire for CommonJS module compatibility
11
+ const require = createRequire(import.meta.url);
12
+ const { checkDeprecatedClerkEnv } = require('./_shared/checkDeprecatedClerkEnv.js');
13
+
9
14
  const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
10
15
  const isBundleAnalyzer = process.env.ANALYZE === 'true' && process.env.CI === 'true';
11
16
 
@@ -17,13 +22,9 @@ if (isDesktop) {
17
22
  }
18
23
 
19
24
  // Auth flags - use process.env directly for build-time dead code elimination
20
- const enableClerk =
21
- process.env.NEXT_PUBLIC_ENABLE_CLERK_AUTH === '1'
22
- ? true
23
- : !!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
24
- const enableBetterAuth = process.env.NEXT_PUBLIC_ENABLE_BETTER_AUTH === '1';
25
+ // Better Auth is the default auth solution when NextAuth is not explicitly enabled
25
26
  const enableNextAuth = process.env.NEXT_PUBLIC_ENABLE_NEXT_AUTH === '1';
26
- const enableAuth = enableClerk || enableBetterAuth || enableNextAuth || false;
27
+ const enableBetterAuth = !enableNextAuth;
27
28
 
28
29
  const getCommandVersion = (command: string): string | null => {
29
30
  try {
@@ -62,10 +63,8 @@ const printEnvInfo = () => {
62
63
 
63
64
  // Auth flags
64
65
  console.log('\n Auth Flags:');
65
- console.log(` enableClerk: ${enableClerk}`);
66
66
  console.log(` enableBetterAuth: ${enableBetterAuth}`);
67
67
  console.log(` enableNextAuth: ${enableNextAuth}`);
68
- console.log(` enableAuth: ${enableAuth}`);
69
68
 
70
69
  console.log('─'.repeat(50));
71
70
  };
@@ -161,6 +160,9 @@ export const runPrebuild = async (targetDir: string = 'src') => {
161
160
  const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
162
161
 
163
162
  if (isMainModule) {
163
+ // Check for deprecated Clerk env vars first - fail fast if found
164
+ checkDeprecatedClerkEnv();
165
+
164
166
  printEnvInfo();
165
167
  // 执行删除操作
166
168
  console.log('\nStarting prebuild cleanup...');
@@ -1,6 +1,17 @@
1
1
  const dns = require('node:dns').promises;
2
2
  const fs = require('node:fs').promises;
3
+ const path = require('node:path');
3
4
  const { spawn } = require('node:child_process');
5
+ const { existsSync } = require('node:fs');
6
+
7
+ // Resolve shared module path for both local dev and Docker environments
8
+ // Local: scripts/serverLauncher/startServer.js -> scripts/_shared/...
9
+ // Docker: /app/startServer.js -> /app/scripts/_shared/...
10
+ const localPath = path.join(__dirname, '..', '_shared', 'checkDeprecatedClerkEnv.js');
11
+ const dockerPath = '/app/scripts/_shared/checkDeprecatedClerkEnv.js';
12
+ const sharedModulePath = existsSync(localPath) ? localPath : dockerPath;
13
+
14
+ const { checkDeprecatedClerkEnv } = require(sharedModulePath);
4
15
 
5
16
  // Set file paths
6
17
  const DB_MIGRATION_SCRIPT_PATH = '/app/docker.cjs';
@@ -9,8 +20,7 @@ const PROXYCHAINS_CONF_PATH = '/etc/proxychains4.conf';
9
20
 
10
21
  // Function to check if a string is a valid IP address
11
22
  const isValidIP = (ip, version = 4) => {
12
- const ipv4Regex =
13
- /^(25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3}$/;
23
+ 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}$/;
14
24
  const ipv6Regex =
15
25
  /^(([\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))$/;
16
26
 
@@ -74,10 +84,13 @@ const runProxyChainsConfGenerator = async (url) => {
74
84
 
75
85
  let ip = isValidIP(host, 4) ? host : await resolveHostIP(host, 4);
76
86
 
77
- const proxyDNSConfig = process.env.ENABLE_PROXY_DNS === '1' ? `
87
+ const proxyDNSConfig =
88
+ process.env.ENABLE_PROXY_DNS === '1'
89
+ ? `
78
90
  proxy_dns
79
91
  remote_dns_subnet 224
80
- `.trim() : '';
92
+ `.trim()
93
+ : '';
81
94
 
82
95
  const configContent = `
83
96
  localnet 127.0.0.0/8
@@ -91,7 +104,9 @@ tcp_connect_time_out 8000
91
104
  tcp_read_time_out 15000
92
105
  [ProxyList]
93
106
  ${protocol} ${ip} ${port} ${user} ${pass}
94
- `.replace(/\n{2,}/g, '\n').trim();
107
+ `
108
+ .replaceAll(/\n{2,}/g, '\n')
109
+ .trim();
95
110
 
96
111
  await fs.writeFile(PROXYCHAINS_CONF_PATH, configContent);
97
112
  console.log(`✅ ProxyChains: All outgoing traffic routed via ${url}.`);
@@ -124,6 +139,9 @@ const runServer = async () => {
124
139
 
125
140
  // Main execution block
126
141
  (async () => {
142
+ // Check for deprecated Clerk env vars first - fail fast if found
143
+ checkDeprecatedClerkEnv({ action: 'restart' });
144
+
127
145
  console.log('🌐 DNS Server:', dns.getServers());
128
146
  console.log('-------------------------------------');
129
147
 
@@ -8,10 +8,6 @@ import { createErrorResponse } from '@/utils/errorResponse';
8
8
  import { RequestHandler, checkAuth } from './index';
9
9
  import { checkAuthMethod } from './utils';
10
10
 
11
- vi.mock('@clerk/nextjs/server', () => ({
12
- getAuth: vi.fn(),
13
- }));
14
-
15
11
  vi.mock('@/utils/errorResponse', () => ({
16
12
  createErrorResponse: vi.fn(),
17
13
  }));
@@ -24,6 +20,14 @@ vi.mock('@lobechat/utils/server', () => ({
24
20
  getXorPayload: vi.fn(),
25
21
  }));
26
22
 
23
+ vi.mock('@/envs/auth', async (importOriginal) => {
24
+ const actual = await importOriginal<typeof import('@/envs/auth')>();
25
+ return {
26
+ ...actual,
27
+ enableBetterAuth: false,
28
+ };
29
+ });
30
+
27
31
  describe('checkAuth', () => {
28
32
  const mockHandler: RequestHandler = vi.fn();
29
33
  const mockRequest = new Request('https://example.com');
@@ -1,4 +1,3 @@
1
- import { type AuthObject } from '@clerk/backend';
2
1
  import {
3
2
  AgentRuntimeError,
4
3
  type ChatCompletionErrorPayload,
@@ -6,7 +5,6 @@ import {
6
5
  } from '@lobechat/model-runtime';
7
6
  import { ChatErrorType, type ClientSecretPayload } from '@lobechat/types';
8
7
  import { getXorPayload } from '@lobechat/utils/server';
9
- import { type NextRequest } from 'next/server';
10
8
 
11
9
  import { getServerDB } from '@/database/core/db-adaptor';
12
10
  import { type LobeChatDatabase } from '@/database/type';
@@ -15,9 +13,7 @@ import {
15
13
  LOBE_CHAT_OIDC_AUTH_HEADER,
16
14
  OAUTH_AUTHORIZED,
17
15
  enableBetterAuth,
18
- enableClerk,
19
16
  } from '@/envs/auth';
20
- import { ClerkAuth } from '@/libs/clerk-auth';
21
17
  import { validateOIDCJWT } from '@/libs/oidc-provider/jwt';
22
18
  import { createErrorResponse } from '@/utils/errorResponse';
23
19
 
@@ -77,16 +73,6 @@ export const checkAuth =
77
73
 
78
74
  if (!authorization) throw AgentRuntimeError.createError(ChatErrorType.Unauthorized);
79
75
 
80
- // check the Auth With payload and clerk auth
81
- let clerkAuth = {} as AuthObject;
82
-
83
- // TODO: V2 完整移除 client 模式下的 clerk 集成代码
84
- if (enableClerk) {
85
- const auth = new ClerkAuth();
86
- const data = auth.getAuthFromRequest(req as NextRequest);
87
- clerkAuth = data.clerkAuth;
88
- }
89
-
90
76
  jwtPayload = getXorPayload(authorization);
91
77
 
92
78
  const oidcAuthorization = req.headers.get(LOBE_CHAT_OIDC_AUTH_HEADER);
@@ -106,7 +92,6 @@ export const checkAuth =
106
92
  checkAuthMethod({
107
93
  apiKey: jwtPayload.apiKey,
108
94
  betterAuthAuthorized,
109
- clerkAuth,
110
95
  nextAuthAuthorized: oauthAuthorized,
111
96
  });
112
97
  } catch (e) {