@lobehub/lobehub 2.0.0-next.343 → 2.0.0-next.345
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.
- package/.cursor/rules/i18n.mdc +1 -1
- package/.cursor/rules/modal-imperative.mdc +162 -0
- package/.cursor/rules/rules-index.mdc +1 -0
- package/.env.example +0 -14
- package/.eslintrc.js +8 -1
- package/CHANGELOG.md +66 -0
- package/Dockerfile +3 -13
- package/README.md +3 -5
- package/README.zh-CN.md +3 -5
- package/changelog/v1.json +24 -0
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +11 -42
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +10 -41
- package/e2e/src/support/webServer.ts +2 -0
- package/locales/ar/error.json +0 -4
- package/locales/bg-BG/error.json +0 -4
- package/locales/de-DE/error.json +0 -4
- package/locales/en-US/error.json +0 -4
- package/locales/es-ES/error.json +0 -4
- package/locales/fa-IR/error.json +0 -4
- package/locales/fr-FR/error.json +0 -4
- package/locales/it-IT/error.json +0 -4
- package/locales/ja-JP/error.json +0 -4
- package/locales/ko-KR/error.json +0 -4
- package/locales/nl-NL/error.json +0 -4
- package/locales/pl-PL/error.json +0 -4
- package/locales/pt-BR/error.json +0 -4
- package/locales/ru-RU/error.json +0 -4
- package/locales/tr-TR/error.json +0 -4
- package/locales/vi-VN/error.json +0 -4
- package/locales/zh-CN/error.json +0 -4
- package/locales/zh-TW/error.json +0 -4
- package/package.json +7 -9
- package/packages/builtin-agents/package.json +2 -0
- package/packages/builtin-agents/src/agents/agent-builder/index.ts +4 -2
- package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +4 -2
- package/packages/builtin-agents/src/agents/page-agent/index.ts +5 -2
- package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +161 -12
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +9 -9
- package/packages/context-engine/src/providers/GroupContextInjector.ts +19 -33
- package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +79 -43
- package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +5 -15
- package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts +24 -3
- package/packages/model-bank/src/modelProviders/comfyui.ts +0 -1
- package/packages/model-bank/src/modelProviders/fal.ts +0 -1
- package/packages/types/src/fetch.ts +1 -2
- package/packages/utils/src/server/__tests__/auth.test.ts +0 -47
- package/packages/utils/src/server/auth.ts +1 -9
- package/scripts/_shared/checkDeprecatedClerkEnv.js +42 -0
- package/scripts/changelogWorkflow/buildStaticChangelog.ts +2 -1
- package/scripts/clerk-to-betterauth/_internal/types.ts +53 -20
- package/scripts/clerk-to-betterauth/export-clerk-users-with-api.ts +43 -36
- package/scripts/countEnWord.ts +1 -1
- package/scripts/electronWorkflow/modifiers/appCode.mts +2 -131
- package/scripts/i18nWorkflow/protectedPatterns.ts +1 -2
- package/scripts/prebuild.mts +10 -8
- package/scripts/serverLauncher/startServer.js +23 -5
- package/src/app/(backend)/middleware/auth/index.test.ts +8 -4
- package/src/app/(backend)/middleware/auth/index.ts +0 -15
- package/src/app/(backend)/middleware/auth/utils.test.ts +0 -28
- package/src/app/(backend)/middleware/auth/utils.ts +2 -17
- package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +3 -51
- package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -4
- package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +7 -6
- package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -16
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/index.tsx +1 -1
- package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +13 -13
- package/src/app/[variants]/(main)/home/features/RecentPage/Item.tsx +2 -2
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +3 -21
- package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -2
- package/src/app/[variants]/(main)/settings/security/index.tsx +1 -22
- package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +12 -14
- package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +8 -14
- package/src/app/[variants]/(main)/settings/skill/index.tsx +7 -5
- package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +2 -35
- package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -20
- package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -2
- package/src/app/[variants]/(mobile)/me/profile/features/Category.tsx +3 -13
- package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +2 -3
- package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
- package/src/app/[variants]/share/t/[id]/index.tsx +1 -1
- package/src/app/robots.tsx +1 -1
- package/src/envs/auth.ts +2 -27
- package/src/envs/llm.ts +2 -2
- package/src/features/AgentSetting/AgentPlugin/index.tsx +9 -12
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +7 -5
- package/src/features/ChatMiniMap/utils.ts +1 -1
- package/src/features/CommandMenu/SearchResults.tsx +1 -1
- package/src/features/Conversation/ChatList/components/AutoScroll/DebugInspector.tsx +166 -0
- package/src/features/Conversation/ChatList/components/AutoScroll/index.tsx +86 -0
- package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +11 -17
- package/src/features/Conversation/Messages/AgentCouncil/components/AutoScrollShadow.tsx +25 -14
- package/src/features/Conversation/Messages/AgentCouncil/components/CouncilMember.tsx +1 -1
- package/src/features/IntegrationDetailModal/IntegrationDetailContent.tsx +305 -0
- package/src/features/IntegrationDetailModal/index.tsx +21 -283
- package/src/features/MCPPluginDetail/Deployment/index.tsx +1 -1
- package/src/features/MCPPluginDetail/Schema/Prompts.tsx +1 -1
- package/src/features/MCPPluginDetail/Schema/Tools.tsx +1 -1
- package/src/features/ProfileEditor/AgentTool.tsx +14 -20
- package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/NoteFileItem.tsx +1 -1
- package/src/features/SkillStore/LobeHubList/index.tsx +50 -87
- package/src/features/SkillStore/Search/index.tsx +1 -1
- package/src/features/SkillStore/{Content.tsx → SkillStoreContent.tsx} +3 -8
- package/src/features/SkillStore/index.tsx +15 -33
- package/src/features/User/UserPanel/PanelContent.tsx +0 -8
- package/src/features/User/__tests__/PanelContent.test.tsx +1 -35
- package/src/features/User/__tests__/UserAvatar.test.tsx +30 -57
- package/src/features/User/__tests__/useMenu.test.tsx +2 -43
- package/src/layout/AuthProvider/index.tsx +0 -5
- package/src/libs/next/config/define-config.ts +6 -0
- package/src/libs/next/proxy/createRouteMatcher.test.ts +121 -0
- package/src/libs/next/proxy/createRouteMatcher.ts +18 -0
- package/src/libs/next/proxy/define-config.ts +4 -53
- package/src/libs/next-auth/adapter/index.ts +1 -2
- package/src/libs/oidc-provider/provider.test.ts +5 -316
- package/src/libs/trpc/lambda/context.test.ts +0 -13
- package/src/libs/trpc/lambda/context.ts +3 -22
- package/src/libs/trpc/middleware/userAuth.ts +2 -4
- package/src/libs/trusted-client/getSessionUser.ts +2 -17
- package/src/locales/default/error.ts +0 -6
- package/src/locales/default/index.ts +0 -2
- package/src/proxy.ts +0 -1
- package/src/server/routers/lambda/__tests__/user.test.ts +0 -71
- package/src/server/routers/lambda/user.ts +6 -63
- package/src/server/services/changelog/index.test.ts +3 -2
- package/src/server/services/changelog/index.ts +1 -1
- package/src/server/services/user/index.ts +0 -83
- package/src/services/chat/index.ts +1 -2
- package/src/services/chat/mecha/agentConfigResolver.test.ts +43 -0
- package/src/services/chat/mecha/agentConfigResolver.ts +3 -1
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +58 -14
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +10 -2
- package/src/store/user/slices/auth/action.test.ts +1 -81
- package/src/store/user/slices/auth/action.ts +3 -28
- package/src/store/user/slices/auth/initialState.ts +1 -18
- package/src/store/user/slices/auth/selectors.test.ts +2 -127
- package/src/store/user/slices/auth/selectors.ts +1 -21
- package/src/utils/errorResponse.ts +1 -4
- package/src/utils/markdownToTxt.ts +20 -0
- package/locales/ar/clerk.json +0 -545
- package/locales/bg-BG/clerk.json +0 -545
- package/locales/de-DE/clerk.json +0 -545
- package/locales/en-US/clerk.json +0 -545
- package/locales/es-ES/clerk.json +0 -545
- package/locales/fa-IR/clerk.json +0 -545
- package/locales/fr-FR/clerk.json +0 -545
- package/locales/it-IT/clerk.json +0 -545
- package/locales/ja-JP/clerk.json +0 -545
- package/locales/ko-KR/clerk.json +0 -545
- package/locales/nl-NL/clerk.json +0 -545
- package/locales/pl-PL/clerk.json +0 -545
- package/locales/pt-BR/clerk.json +0 -545
- package/locales/ru-RU/clerk.json +0 -545
- package/locales/tr-TR/clerk.json +0 -545
- package/locales/vi-VN/clerk.json +0 -545
- package/locales/zh-CN/clerk.json +0 -545
- package/locales/zh-TW/clerk.json +0 -545
- package/src/app/(backend)/api/webhooks/clerk/__tests__/fixtures/createUser.json +0 -73
- package/src/app/(backend)/api/webhooks/clerk/route.ts +0 -95
- package/src/app/(backend)/api/webhooks/clerk/validateRequest.ts +0 -22
- package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +0 -27
- package/src/app/[variants]/(main)/settings/security/features/ClerkProfile.tsx +0 -67
- package/src/features/Conversation/ChatList/components/AutoScroll.tsx +0 -25
- package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +0 -40
- package/src/layout/AuthProvider/Clerk/index.tsx +0 -54
- package/src/layout/AuthProvider/Clerk/useAppearance.ts +0 -133
- package/src/libs/clerk-auth/index.test.ts +0 -216
- package/src/libs/clerk-auth/index.ts +0 -80
- package/src/locales/default/clerk.ts +0 -677
- package/src/server/services/user/index.test.ts +0 -220
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
3
|
exports[`GroupContextInjector > Edge Cases > should handle empty members array 1`] = `
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
<group_context>
|
|
4
|
+
"<group_context>
|
|
7
5
|
You are "", acting as a in the multi-agent group "Empty Group".
|
|
8
6
|
Your internal agent ID is (for system use only, never expose to users).
|
|
9
7
|
|
|
@@ -24,9 +22,7 @@ Empty group description
|
|
|
24
22
|
`;
|
|
25
23
|
|
|
26
24
|
exports[`GroupContextInjector > Identity Rules Section > should always include identity rules 1`] = `
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
<group_context>
|
|
25
|
+
"<group_context>
|
|
30
26
|
You are "", acting as a in the multi-agent group "".
|
|
31
27
|
Your internal agent ID is (for system use only, never expose to users).
|
|
32
28
|
|
|
@@ -47,9 +43,7 @@ Your internal agent ID is (for system use only, never expose to users).
|
|
|
47
43
|
`;
|
|
48
44
|
|
|
49
45
|
exports[`GroupContextInjector > Variable Replacement > should handle config with only group info 1`] = `
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
<group_context>
|
|
46
|
+
"<group_context>
|
|
53
47
|
You are "", acting as a in the multi-agent group "Test Group".
|
|
54
48
|
Your internal agent ID is (for system use only, never expose to users).
|
|
55
49
|
|
|
@@ -70,9 +64,7 @@ Test group description
|
|
|
70
64
|
`;
|
|
71
65
|
|
|
72
66
|
exports[`GroupContextInjector > Variable Replacement > should handle config with only identity info 1`] = `
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
<group_context>
|
|
67
|
+
"<group_context>
|
|
76
68
|
You are "Editor", acting as a participant in the multi-agent group "".
|
|
77
69
|
Your internal agent ID is agt_editor (for system use only, never expose to users).
|
|
78
70
|
|
|
@@ -93,9 +85,7 @@ Your internal agent ID is agt_editor (for system use only, never expose to users
|
|
|
93
85
|
`;
|
|
94
86
|
|
|
95
87
|
exports[`GroupContextInjector > Variable Replacement > should handle empty config 1`] = `
|
|
96
|
-
"
|
|
97
|
-
|
|
98
|
-
<group_context>
|
|
88
|
+
"<group_context>
|
|
99
89
|
You are "", acting as a in the multi-agent group "".
|
|
100
90
|
Your internal agent ID is (for system use only, never expose to users).
|
|
101
91
|
|
package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts
CHANGED
|
@@ -50,9 +50,30 @@ describe('UserMemoryTopicRepository', () => {
|
|
|
50
50
|
|
|
51
51
|
it('should return concatenated user message content', async () => {
|
|
52
52
|
await serverDB.insert(messages).values([
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
{
|
|
54
|
+
id: 'msg-1',
|
|
55
|
+
content: 'Hello',
|
|
56
|
+
role: 'user',
|
|
57
|
+
topicId,
|
|
58
|
+
userId,
|
|
59
|
+
createdAt: new Date('2024-01-01'),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'msg-2',
|
|
63
|
+
content: 'Hi there!',
|
|
64
|
+
role: 'assistant',
|
|
65
|
+
topicId,
|
|
66
|
+
userId,
|
|
67
|
+
createdAt: new Date('2024-01-02'),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'msg-3',
|
|
71
|
+
content: 'How are you?',
|
|
72
|
+
role: 'user',
|
|
73
|
+
topicId,
|
|
74
|
+
userId,
|
|
75
|
+
createdAt: new Date('2024-01-03'),
|
|
76
|
+
},
|
|
56
77
|
]);
|
|
57
78
|
|
|
58
79
|
const result = await repo.getUserMessagesQueryForTopic(topicId);
|
|
@@ -12,7 +12,6 @@ const ComfyUI: ModelProviderCard = {
|
|
|
12
12
|
chatModels: [],
|
|
13
13
|
description:
|
|
14
14
|
'A powerful open-source workflow engine for image, video, and audio generation, supporting models like SD, FLUX, Qwen, Hunyuan, and WAN with node-based editing and private deployment.',
|
|
15
|
-
enabled: true,
|
|
16
15
|
id: 'comfyui',
|
|
17
16
|
name: 'ComfyUI',
|
|
18
17
|
settings: {
|
|
@@ -5,8 +5,7 @@ export const ChatErrorType = {
|
|
|
5
5
|
// ******* Business Error Semantics ******* //
|
|
6
6
|
|
|
7
7
|
InvalidAccessCode: 'InvalidAccessCode', // is in valid password
|
|
8
|
-
|
|
9
|
-
FreePlanLimit: 'FreePlanLimit', // is not Clerk User
|
|
8
|
+
FreePlanLimit: 'FreePlanLimit', // Free plan usage limit
|
|
10
9
|
SubscriptionPlanLimit: 'SubscriptionPlanLimit', // Subscription user limit exceeded
|
|
11
10
|
SubscriptionKeyMismatch: 'SubscriptionKeyMismatch', // Subscription key mismatch
|
|
12
11
|
|
|
@@ -4,34 +4,17 @@ import { extractBearerToken, getUserAuth } from '../auth';
|
|
|
4
4
|
|
|
5
5
|
// Mock auth constants
|
|
6
6
|
let mockEnableBetterAuth = false;
|
|
7
|
-
let mockEnableClerk = false;
|
|
8
7
|
let mockEnableNextAuth = false;
|
|
9
8
|
|
|
10
9
|
vi.mock('@/envs/auth', () => ({
|
|
11
10
|
get enableBetterAuth() {
|
|
12
11
|
return mockEnableBetterAuth;
|
|
13
12
|
},
|
|
14
|
-
get enableClerk() {
|
|
15
|
-
return mockEnableClerk;
|
|
16
|
-
},
|
|
17
13
|
get enableNextAuth() {
|
|
18
14
|
return mockEnableNextAuth;
|
|
19
15
|
},
|
|
20
16
|
}));
|
|
21
17
|
|
|
22
|
-
vi.mock('@/libs/clerk-auth', () => ({
|
|
23
|
-
ClerkAuth: class {
|
|
24
|
-
async getAuth() {
|
|
25
|
-
return {
|
|
26
|
-
clerkAuth: {
|
|
27
|
-
redirectToSignIn: vi.fn(),
|
|
28
|
-
},
|
|
29
|
-
userId: 'clerk-user-id',
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
18
|
vi.mock('@/libs/next-auth', () => ({
|
|
36
19
|
default: {
|
|
37
20
|
auth: vi.fn().mockResolvedValue({
|
|
@@ -62,7 +45,6 @@ describe('getUserAuth', () => {
|
|
|
62
45
|
beforeEach(() => {
|
|
63
46
|
vi.clearAllMocks();
|
|
64
47
|
mockEnableBetterAuth = false;
|
|
65
|
-
mockEnableClerk = false;
|
|
66
48
|
mockEnableNextAuth = false;
|
|
67
49
|
});
|
|
68
50
|
|
|
@@ -70,22 +52,7 @@ describe('getUserAuth', () => {
|
|
|
70
52
|
await expect(getUserAuth()).rejects.toThrow('Auth method is not enabled');
|
|
71
53
|
});
|
|
72
54
|
|
|
73
|
-
it('should return clerk auth when clerk is enabled', async () => {
|
|
74
|
-
mockEnableClerk = true;
|
|
75
|
-
mockEnableNextAuth = false;
|
|
76
|
-
|
|
77
|
-
const auth = await getUserAuth();
|
|
78
|
-
|
|
79
|
-
expect(auth).toEqual({
|
|
80
|
-
clerkAuth: {
|
|
81
|
-
redirectToSignIn: expect.any(Function),
|
|
82
|
-
},
|
|
83
|
-
userId: 'clerk-user-id',
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
55
|
it('should return next auth when next auth is enabled', async () => {
|
|
88
|
-
mockEnableClerk = false;
|
|
89
56
|
mockEnableNextAuth = true;
|
|
90
57
|
|
|
91
58
|
const auth = await getUserAuth();
|
|
@@ -100,20 +67,6 @@ describe('getUserAuth', () => {
|
|
|
100
67
|
});
|
|
101
68
|
});
|
|
102
69
|
|
|
103
|
-
it('should prioritize clerk auth over next auth when both are enabled', async () => {
|
|
104
|
-
mockEnableClerk = true;
|
|
105
|
-
mockEnableNextAuth = true;
|
|
106
|
-
|
|
107
|
-
const auth = await getUserAuth();
|
|
108
|
-
|
|
109
|
-
expect(auth).toEqual({
|
|
110
|
-
clerkAuth: {
|
|
111
|
-
redirectToSignIn: expect.any(Function),
|
|
112
|
-
},
|
|
113
|
-
userId: 'clerk-user-id',
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
70
|
it('should return better auth when better auth is enabled', async () => {
|
|
118
71
|
mockEnableBetterAuth = true;
|
|
119
72
|
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import { headers } from 'next/headers';
|
|
2
2
|
|
|
3
|
-
import { enableBetterAuth,
|
|
3
|
+
import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
|
|
4
4
|
|
|
5
5
|
export const getUserAuth = async () => {
|
|
6
|
-
if (enableClerk) {
|
|
7
|
-
const { ClerkAuth } = await import('@/libs/clerk-auth');
|
|
8
|
-
|
|
9
|
-
const clerkAuth = new ClerkAuth();
|
|
10
|
-
|
|
11
|
-
return await clerkAuth.getAuth();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
if (enableBetterAuth) {
|
|
15
7
|
const { auth: betterAuth } = await import('@/auth');
|
|
16
8
|
|
|
@@ -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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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:
|
|
47
|
-
created_at:
|
|
48
|
-
external_accounts: (
|
|
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:
|
|
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:
|
|
61
|
+
updated_at: acc.updated_at,
|
|
55
62
|
verificationStatus: acc.verification?.status === 'verified',
|
|
56
63
|
})),
|
|
57
|
-
id:
|
|
58
|
-
image_url:
|
|
59
|
-
lockout_expires_in_seconds:
|
|
60
|
-
password_enabled:
|
|
61
|
-
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:
|
|
64
|
-
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
|
-
|
|
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
|
|
80
|
-
limit: PAGE_SIZE,
|
|
81
|
-
offset,
|
|
82
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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,
|
|
166
|
+
const page = await fetchClerkUserPage(offset, secretKey, index);
|
|
160
167
|
|
|
161
168
|
for (const user of page) {
|
|
162
169
|
userMap.set(user.id, user);
|
package/scripts/countEnWord.ts
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
// 配置项
|
|
5
5
|
const config: Config = {
|
|
6
6
|
dirPath: './locales/en-US', // 替换为你的目录路径
|
|
7
|
-
ignoredFiles: ['
|
|
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.
|
|
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
|
-
//
|
|
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
|
]);
|