@lobehub/lobehub 2.0.0-next.144 → 2.0.0-next.146
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/project-introduce.mdc +1 -1
- package/.github/workflows/test.yml +3 -0
- package/AGENTS.md +4 -0
- package/CHANGELOG.md +50 -0
- package/apps/desktop/package.json +2 -0
- package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +8 -12
- package/apps/desktop/src/main/core/infrastructure/__tests__/ProtocolManager.test.ts +1 -0
- package/apps/desktop/src/main/core/ui/__tests__/Tray.test.ts +2 -2
- package/apps/desktop/src/main/utils/__tests__/file-system.test.ts +1 -1
- package/apps/desktop/src/main/utils/__tests__/logger.test.ts +7 -7
- package/apps/desktop/src/main/utils/next-electron-rsc.ts +3 -1
- package/apps/desktop/src/preload/invoke.test.ts +4 -2
- package/apps/desktop/src/preload/routeInterceptor.test.ts +54 -9
- package/apps/desktop/src/preload/streamer.test.ts +32 -31
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +2 -1
- package/docs/self-hosting/advanced/auth.mdx +21 -10
- package/docs/self-hosting/advanced/auth.zh-CN.mdx +21 -10
- package/package.json +4 -3
- package/packages/database/migrations/0056_update_agent_slug_index.sql +2 -0
- package/packages/database/migrations/meta/0056_snapshot.json +8411 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +11 -2
- package/packages/database/src/schemas/agent.ts +2 -3
- package/packages/electron-client-ipc/src/events/system.ts +1 -3
- package/packages/electron-client-ipc/src/types/system.ts +1 -0
- package/src/envs/email.ts +11 -0
- package/src/libs/better-auth/email-templates/magic-link.ts +5 -5
- package/src/libs/better-auth/email-templates/reset-password.ts +4 -4
- package/src/libs/better-auth/email-templates/verification.ts +4 -4
- package/src/libs/mcp/__tests__/__snapshots__/index.test.ts.snap +9 -0
- package/src/server/services/email/README.md +19 -0
- package/src/server/services/email/impls/index.ts +5 -1
- package/src/server/services/email/impls/resend/index.ts +120 -0
- package/src/server/services/email/index.test.ts +1 -1
- package/src/server/services/email/index.ts +9 -1
- package/src/server/services/file/impls/index.ts +3 -3
- package/src/server/services/file/impls/local.ts +35 -35
- package/src/server/services/file/impls/s3.ts +1 -1
- package/src/server/services/file/impls/type.ts +11 -11
- package/src/server/services/file/index.ts +12 -12
|
@@ -4,7 +4,7 @@ alwaysApply: true
|
|
|
4
4
|
|
|
5
5
|
## Project Description
|
|
6
6
|
|
|
7
|
-
You are developing an open-source, modern-design AI
|
|
7
|
+
You are developing an open-source, modern-design AI Agent Workspace: LobeHub(previous LobeChat).
|
|
8
8
|
|
|
9
9
|
Supported platforms:
|
|
10
10
|
|
package/AGENTS.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
This document serves as a comprehensive guide for all team members when developing LobeChat.
|
|
4
4
|
|
|
5
|
+
## Project Description
|
|
6
|
+
|
|
7
|
+
You are developing an open-source, modern-design AI Agent Workspace: LobeHub(previous LobeChat).
|
|
8
|
+
|
|
5
9
|
## Tech Stack
|
|
6
10
|
|
|
7
11
|
Built with modern technologies:
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.146](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.145...v2.0.0-next.146)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-12-02**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Refactor agent slug schema.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Code refactoring
|
|
19
|
+
|
|
20
|
+
- **misc**: Refactor agent slug schema, closes [#10561](https://github.com/lobehub/lobe-chat/issues/10561) ([0d609d1](https://github.com/lobehub/lobe-chat/commit/0d609d1))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 2.0.0-next.145](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.144...v2.0.0-next.145)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2025-12-02**</sup>
|
|
33
|
+
|
|
34
|
+
#### ✨ Features
|
|
35
|
+
|
|
36
|
+
- **misc**: Email provider support resend.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's improved
|
|
44
|
+
|
|
45
|
+
- **misc**: Email provider support resend, closes [#10557](https://github.com/lobehub/lobe-chat/issues/10557) ([7449b29](https://github.com/lobehub/lobe-chat/commit/7449b29))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
## [Version 2.0.0-next.144](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.143...v2.0.0-next.144)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2025-12-02**</sup>
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"happy-dom": "^20.0.11",
|
|
66
66
|
"http-proxy-agent": "^7.0.2",
|
|
67
67
|
"https-proxy-agent": "^7.0.6",
|
|
68
|
+
"i18next": "^25.6.3",
|
|
68
69
|
"just-diff": "^6.0.2",
|
|
69
70
|
"lodash": "^4.17.21",
|
|
70
71
|
"lodash-es": "^4.17.21",
|
|
@@ -74,6 +75,7 @@
|
|
|
74
75
|
"tsx": "^4.20.6",
|
|
75
76
|
"typescript": "^5.9.3",
|
|
76
77
|
"undici": "^7.16.0",
|
|
78
|
+
"uuid": "^13.0.0",
|
|
77
79
|
"vite": "^7.2.4",
|
|
78
80
|
"vitest": "^3.2.4"
|
|
79
81
|
},
|
|
@@ -34,10 +34,9 @@ describe('UploadFileCtr', () => {
|
|
|
34
34
|
const params = {
|
|
35
35
|
hash: 'abc123',
|
|
36
36
|
path: '/test/file.txt',
|
|
37
|
-
|
|
38
|
-
data: 'dGVzdCBjb250ZW50',
|
|
37
|
+
content: new ArrayBuffer(16),
|
|
39
38
|
filename: 'file.txt',
|
|
40
|
-
|
|
39
|
+
type: 'text/plain',
|
|
41
40
|
};
|
|
42
41
|
const expectedResult = { id: 'file-id-123', url: '/files/file-id-123' };
|
|
43
42
|
mockFileService.uploadFile.mockResolvedValue(expectedResult);
|
|
@@ -52,10 +51,9 @@ describe('UploadFileCtr', () => {
|
|
|
52
51
|
const params = {
|
|
53
52
|
hash: 'abc123',
|
|
54
53
|
path: '/test/file.txt',
|
|
55
|
-
|
|
56
|
-
data: 'dGVzdCBjb250ZW50',
|
|
54
|
+
content: new ArrayBuffer(16),
|
|
57
55
|
filename: 'file.txt',
|
|
58
|
-
|
|
56
|
+
type: 'text/plain',
|
|
59
57
|
};
|
|
60
58
|
const error = new Error('Upload failed');
|
|
61
59
|
mockFileService.uploadFile.mockRejectedValue(error);
|
|
@@ -139,10 +137,9 @@ describe('UploadFileCtr', () => {
|
|
|
139
137
|
const params = {
|
|
140
138
|
hash: 'xyz789',
|
|
141
139
|
path: '/test/newfile.txt',
|
|
142
|
-
|
|
143
|
-
data: 'bmV3IGZpbGUgY29udGVudA==',
|
|
140
|
+
content: 'bmV3IGZpbGUgY29udGVudA==',
|
|
144
141
|
filename: 'newfile.txt',
|
|
145
|
-
|
|
142
|
+
type: 'text/plain',
|
|
146
143
|
};
|
|
147
144
|
const expectedResult = { id: 'new-file-id', url: '/files/new-file-id' };
|
|
148
145
|
mockFileService.uploadFile.mockResolvedValue(expectedResult);
|
|
@@ -157,10 +154,9 @@ describe('UploadFileCtr', () => {
|
|
|
157
154
|
const params = {
|
|
158
155
|
hash: 'xyz789',
|
|
159
156
|
path: '/test/newfile.txt',
|
|
160
|
-
|
|
161
|
-
data: 'bmV3IGZpbGUgY29udGVudA==',
|
|
157
|
+
content: 'bmV3IGZpbGUgY29udGVudA==',
|
|
162
158
|
filename: 'newfile.txt',
|
|
163
|
-
|
|
159
|
+
type: 'text/plain',
|
|
164
160
|
};
|
|
165
161
|
const error = new Error('Create failed');
|
|
166
162
|
mockFileService.uploadFile.mockRejectedValue(error);
|
|
@@ -315,6 +315,7 @@ describe('ProtocolManager', () => {
|
|
|
315
315
|
it('should show main window and dispatch to handler', async () => {
|
|
316
316
|
vi.mocked(parseProtocolUrl).mockReturnValue({
|
|
317
317
|
action: 'install',
|
|
318
|
+
originalUrl: 'lobehub://plugin/install?url=https://example.com',
|
|
318
319
|
params: { url: 'https://example.com' },
|
|
319
320
|
urlType: 'plugin',
|
|
320
321
|
});
|
|
@@ -242,7 +242,7 @@ describe('Tray', () => {
|
|
|
242
242
|
const templateArg = vi.mocked(Menu.buildFromTemplate).mock.calls[0][0];
|
|
243
243
|
const showMainWindowItem = templateArg.find((item: any) => item.label === 'Show Main Window');
|
|
244
244
|
|
|
245
|
-
showMainWindowItem?.click?.();
|
|
245
|
+
showMainWindowItem?.click?.(null as any, null as any, null as any);
|
|
246
246
|
|
|
247
247
|
expect(mockApp.browserManager.showMainWindow).toHaveBeenCalled();
|
|
248
248
|
});
|
|
@@ -253,7 +253,7 @@ describe('Tray', () => {
|
|
|
253
253
|
const templateArg = vi.mocked(Menu.buildFromTemplate).mock.calls[0][0];
|
|
254
254
|
const quitItem = templateArg.find((item: any) => item.label === 'Quit');
|
|
255
255
|
|
|
256
|
-
quitItem?.click?.();
|
|
256
|
+
quitItem?.click?.(null as any, null as any, null as any);
|
|
257
257
|
|
|
258
258
|
expect(app.quit).toHaveBeenCalled();
|
|
259
259
|
});
|
|
@@ -81,7 +81,7 @@ describe('file-system', () => {
|
|
|
81
81
|
vi.mocked(statSync).mockImplementation(() => {
|
|
82
82
|
throw new Error('ENOENT: no such file or directory');
|
|
83
83
|
});
|
|
84
|
-
vi.mocked(mkdirSync).mockImplementation(() =>
|
|
84
|
+
vi.mocked(mkdirSync).mockImplementation(() => undefined);
|
|
85
85
|
|
|
86
86
|
makeSureDirExist(dir);
|
|
87
87
|
|
|
@@ -27,7 +27,7 @@ describe('logger', () => {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
afterEach(() => {
|
|
30
|
-
delete process.env.NODE_ENV;
|
|
30
|
+
delete (process.env as NodeJS.ProcessEnv & { NODE_ENV?: string }).NODE_ENV;
|
|
31
31
|
delete process.env.DEBUG_VERBOSE;
|
|
32
32
|
});
|
|
33
33
|
|
|
@@ -73,7 +73,7 @@ describe('logger', () => {
|
|
|
73
73
|
|
|
74
74
|
describe('logger.error', () => {
|
|
75
75
|
it('should use electronLog.error in production', () => {
|
|
76
|
-
process.env.NODE_ENV = 'production';
|
|
76
|
+
(process.env as NodeJS.ProcessEnv & { NODE_ENV?: string }).NODE_ENV = 'production';
|
|
77
77
|
const logger = createLogger('test:error');
|
|
78
78
|
logger.error('error message', { error: 'details' });
|
|
79
79
|
|
|
@@ -82,7 +82,7 @@ describe('logger', () => {
|
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
it('should use console.error in development', () => {
|
|
85
|
-
process.env.NODE_ENV = 'development';
|
|
85
|
+
(process.env as NodeJS.ProcessEnv & { NODE_ENV?: string }).NODE_ENV = 'development';
|
|
86
86
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
87
87
|
const logger = createLogger('test:error');
|
|
88
88
|
logger.error('error message', { error: 'details' });
|
|
@@ -107,7 +107,7 @@ describe('logger', () => {
|
|
|
107
107
|
|
|
108
108
|
describe('logger.info', () => {
|
|
109
109
|
it('should use electronLog.info with namespace in production', () => {
|
|
110
|
-
process.env.NODE_ENV = 'production';
|
|
110
|
+
(process.env as NodeJS.ProcessEnv & { NODE_ENV?: string }).NODE_ENV = 'production';
|
|
111
111
|
const logger = createLogger('test:info');
|
|
112
112
|
logger.info('info message', { data: 'value' });
|
|
113
113
|
|
|
@@ -118,7 +118,7 @@ describe('logger', () => {
|
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
it('should use debug logger in development', () => {
|
|
121
|
-
process.env.NODE_ENV = 'development';
|
|
121
|
+
(process.env as NodeJS.ProcessEnv & { NODE_ENV?: string }).NODE_ENV = 'development';
|
|
122
122
|
const logger = createLogger('test:info');
|
|
123
123
|
logger.info('info message', { data: 'value' });
|
|
124
124
|
|
|
@@ -162,7 +162,7 @@ describe('logger', () => {
|
|
|
162
162
|
|
|
163
163
|
describe('logger.warn', () => {
|
|
164
164
|
it('should use electronLog.warn in production', () => {
|
|
165
|
-
process.env.NODE_ENV = 'production';
|
|
165
|
+
(process.env as NodeJS.ProcessEnv & { NODE_ENV?: string }).NODE_ENV = 'production';
|
|
166
166
|
const logger = createLogger('test:warn');
|
|
167
167
|
logger.warn('warn message', { warning: 'details' });
|
|
168
168
|
|
|
@@ -171,7 +171,7 @@ describe('logger', () => {
|
|
|
171
171
|
});
|
|
172
172
|
|
|
173
173
|
it('should not use electronLog.warn in development', () => {
|
|
174
|
-
process.env.NODE_ENV = 'development';
|
|
174
|
+
(process.env as NodeJS.ProcessEnv & { NODE_ENV?: string }).NODE_ENV = 'development';
|
|
175
175
|
const logger = createLogger('test:warn');
|
|
176
176
|
logger.warn('warn message');
|
|
177
177
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// copy from https://github.com/kirill-konshin/next-electron-rsc
|
|
2
2
|
import { serialize as serializeCookie } from 'cookie';
|
|
3
3
|
import { type Protocol, type Session } from 'electron';
|
|
4
|
+
// @ts-ignore
|
|
4
5
|
import type { NextConfig } from 'next';
|
|
6
|
+
// @ts-ignore
|
|
5
7
|
import type NextNodeServer from 'next/dist/server/next-server';
|
|
6
8
|
import assert from 'node:assert';
|
|
7
9
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
@@ -204,7 +206,7 @@ export function createHandler({
|
|
|
204
206
|
logger.info('Initializing Next.js app for production');
|
|
205
207
|
|
|
206
208
|
// https://github.com/lobehub/lobe-chat/pull/9851
|
|
207
|
-
// @ts-
|
|
209
|
+
// @ts-ignore
|
|
208
210
|
// noinspection JSConstantReassignment
|
|
209
211
|
process.env.NODE_ENV = 'production';
|
|
210
212
|
const next = require(resolve.sync('next', { basedir: standaloneDir }));
|
|
@@ -47,7 +47,8 @@ describe('invoke', () => {
|
|
|
47
47
|
const expectedResult = { processed: true };
|
|
48
48
|
mockIpcRendererInvoke.mockResolvedValue(expectedResult);
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
// Use 'as any' to bypass type checking for testing multiple parameters
|
|
51
|
+
const result = await (invoke as any)('someEvent', param1, param2, param3);
|
|
51
52
|
|
|
52
53
|
expect(mockIpcRendererInvoke).toHaveBeenCalledWith('someEvent', param1, param2, param3);
|
|
53
54
|
expect(mockIpcRendererInvoke).toHaveBeenCalledTimes(1);
|
|
@@ -109,7 +110,8 @@ describe('invoke', () => {
|
|
|
109
110
|
const expectedResponse: TestResponse = { message: 'success', status: 200 };
|
|
110
111
|
mockIpcRendererInvoke.mockResolvedValue(expectedResponse);
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
// Use 'as any' to bypass type checking for testing with mock event
|
|
114
|
+
const result = (await (invoke as any)('testEvent')) as TestResponse;
|
|
113
115
|
|
|
114
116
|
expect(result).toEqual(expectedResponse);
|
|
115
117
|
expect(typeof result.message).toBe('string');
|
|
@@ -110,7 +110,12 @@ describe('setupRouteInterceptors', () => {
|
|
|
110
110
|
it('should intercept internal link matching route pattern', async () => {
|
|
111
111
|
setupRouteInterceptors();
|
|
112
112
|
|
|
113
|
-
const matchedRoute = {
|
|
113
|
+
const matchedRoute = {
|
|
114
|
+
description: 'Developer Tools',
|
|
115
|
+
enabled: true,
|
|
116
|
+
pathPrefix: '/desktop/devtools',
|
|
117
|
+
targetWindow: 'devtools',
|
|
118
|
+
};
|
|
114
119
|
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
115
120
|
|
|
116
121
|
const link = document.createElement('a');
|
|
@@ -142,7 +147,12 @@ describe('setupRouteInterceptors', () => {
|
|
|
142
147
|
pathname: '/desktop/devtools/console',
|
|
143
148
|
});
|
|
144
149
|
|
|
145
|
-
const matchedRoute = {
|
|
150
|
+
const matchedRoute = {
|
|
151
|
+
description: 'Developer Tools',
|
|
152
|
+
enabled: true,
|
|
153
|
+
pathPrefix: '/desktop/devtools',
|
|
154
|
+
targetWindow: 'devtools',
|
|
155
|
+
};
|
|
146
156
|
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
147
157
|
|
|
148
158
|
const link = document.createElement('a');
|
|
@@ -183,7 +193,12 @@ describe('setupRouteInterceptors', () => {
|
|
|
183
193
|
it('should intercept pushState for matched routes', () => {
|
|
184
194
|
setupRouteInterceptors();
|
|
185
195
|
|
|
186
|
-
const matchedRoute = {
|
|
196
|
+
const matchedRoute = {
|
|
197
|
+
description: 'Developer Tools',
|
|
198
|
+
enabled: true,
|
|
199
|
+
pathPrefix: '/desktop/devtools',
|
|
200
|
+
targetWindow: 'devtools',
|
|
201
|
+
};
|
|
187
202
|
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
188
203
|
|
|
189
204
|
const originalLength = history.length;
|
|
@@ -208,7 +223,12 @@ describe('setupRouteInterceptors', () => {
|
|
|
208
223
|
pathname: '/desktop/devtools/console',
|
|
209
224
|
});
|
|
210
225
|
|
|
211
|
-
const matchedRoute = {
|
|
226
|
+
const matchedRoute = {
|
|
227
|
+
description: 'Developer Tools',
|
|
228
|
+
enabled: true,
|
|
229
|
+
pathPrefix: '/desktop/devtools',
|
|
230
|
+
targetWindow: 'devtools',
|
|
231
|
+
};
|
|
212
232
|
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
213
233
|
|
|
214
234
|
history.pushState({}, '', '/desktop/devtools/network');
|
|
@@ -248,7 +268,12 @@ describe('setupRouteInterceptors', () => {
|
|
|
248
268
|
it('should intercept replaceState for matched routes', () => {
|
|
249
269
|
setupRouteInterceptors();
|
|
250
270
|
|
|
251
|
-
const matchedRoute = {
|
|
271
|
+
const matchedRoute = {
|
|
272
|
+
description: 'Developer Tools',
|
|
273
|
+
enabled: true,
|
|
274
|
+
pathPrefix: '/desktop/devtools',
|
|
275
|
+
targetWindow: 'devtools',
|
|
276
|
+
};
|
|
252
277
|
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
253
278
|
|
|
254
279
|
history.replaceState({}, '', '/desktop/devtools');
|
|
@@ -270,7 +295,12 @@ describe('setupRouteInterceptors', () => {
|
|
|
270
295
|
pathname: '/desktop/devtools/console',
|
|
271
296
|
});
|
|
272
297
|
|
|
273
|
-
const matchedRoute = {
|
|
298
|
+
const matchedRoute = {
|
|
299
|
+
description: 'Developer Tools',
|
|
300
|
+
enabled: true,
|
|
301
|
+
pathPrefix: '/desktop/devtools',
|
|
302
|
+
targetWindow: 'devtools',
|
|
303
|
+
};
|
|
274
304
|
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
275
305
|
|
|
276
306
|
history.replaceState({}, '', '/desktop/devtools/network');
|
|
@@ -296,7 +326,12 @@ describe('setupRouteInterceptors', () => {
|
|
|
296
326
|
setupRouteInterceptors();
|
|
297
327
|
|
|
298
328
|
// First trigger a route interception to add path to preventedPaths
|
|
299
|
-
const matchedRoute = {
|
|
329
|
+
const matchedRoute = {
|
|
330
|
+
description: 'Developer Tools',
|
|
331
|
+
enabled: true,
|
|
332
|
+
pathPrefix: '/desktop/devtools',
|
|
333
|
+
targetWindow: 'devtools',
|
|
334
|
+
};
|
|
300
335
|
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
301
336
|
history.pushState({}, '', '/desktop/devtools');
|
|
302
337
|
|
|
@@ -338,7 +373,12 @@ describe('setupRouteInterceptors', () => {
|
|
|
338
373
|
|
|
339
374
|
setupRouteInterceptors();
|
|
340
375
|
|
|
341
|
-
const matchedRoute = {
|
|
376
|
+
const matchedRoute = {
|
|
377
|
+
description: 'Developer Tools',
|
|
378
|
+
enabled: true,
|
|
379
|
+
pathPrefix: '/desktop/devtools',
|
|
380
|
+
targetWindow: 'devtools',
|
|
381
|
+
};
|
|
342
382
|
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
343
383
|
|
|
344
384
|
history.pushState({}, '', '/desktop/devtools');
|
|
@@ -358,7 +398,12 @@ describe('setupRouteInterceptors', () => {
|
|
|
358
398
|
|
|
359
399
|
setupRouteInterceptors();
|
|
360
400
|
|
|
361
|
-
const matchedRoute = {
|
|
401
|
+
const matchedRoute = {
|
|
402
|
+
description: 'Developer Tools',
|
|
403
|
+
enabled: true,
|
|
404
|
+
pathPrefix: '/desktop/devtools',
|
|
405
|
+
targetWindow: 'devtools',
|
|
406
|
+
};
|
|
362
407
|
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
363
408
|
|
|
364
409
|
history.pushState({}, '', '/desktop/devtools');
|
|
@@ -30,9 +30,9 @@ describe('onStreamInvoke', () => {
|
|
|
30
30
|
|
|
31
31
|
it('should set up stream listeners and send start event', () => {
|
|
32
32
|
const params: ProxyTRPCRequestParams = {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
headers: { 'content-type': 'application/json' },
|
|
34
|
+
method: 'POST',
|
|
35
|
+
urlPath: '/trpc/lambda/test.endpoint',
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
const callbacks = {
|
|
@@ -78,9 +78,9 @@ describe('onStreamInvoke', () => {
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
const params: ProxyTRPCRequestParams = {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
headers: {},
|
|
82
|
+
method: 'GET',
|
|
83
|
+
urlPath: '/trpc/test',
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
onStreamInvoke(params, callbacks);
|
|
@@ -106,9 +106,9 @@ describe('onStreamInvoke', () => {
|
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
const params: ProxyTRPCRequestParams = {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
headers: {},
|
|
110
|
+
method: 'GET',
|
|
111
|
+
urlPath: '/trpc/test',
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
onStreamInvoke(params, callbacks);
|
|
@@ -138,9 +138,9 @@ describe('onStreamInvoke', () => {
|
|
|
138
138
|
};
|
|
139
139
|
|
|
140
140
|
const params: ProxyTRPCRequestParams = {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
headers: {},
|
|
142
|
+
method: 'GET',
|
|
143
|
+
urlPath: '/trpc/test',
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
onStreamInvoke(params, callbacks);
|
|
@@ -179,9 +179,9 @@ describe('onStreamInvoke', () => {
|
|
|
179
179
|
};
|
|
180
180
|
|
|
181
181
|
const params: ProxyTRPCRequestParams = {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
headers: {},
|
|
183
|
+
method: 'GET',
|
|
184
|
+
urlPath: '/trpc/test',
|
|
185
185
|
};
|
|
186
186
|
|
|
187
187
|
onStreamInvoke(params, callbacks);
|
|
@@ -221,9 +221,9 @@ describe('onStreamInvoke', () => {
|
|
|
221
221
|
};
|
|
222
222
|
|
|
223
223
|
const params: ProxyTRPCRequestParams = {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
headers: {},
|
|
225
|
+
method: 'GET',
|
|
226
|
+
urlPath: '/trpc/test',
|
|
227
227
|
};
|
|
228
228
|
|
|
229
229
|
const cleanup = onStreamInvoke(params, callbacks);
|
|
@@ -255,9 +255,9 @@ describe('onStreamInvoke', () => {
|
|
|
255
255
|
};
|
|
256
256
|
|
|
257
257
|
const params: ProxyTRPCRequestParams = {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
headers: {},
|
|
259
|
+
method: 'GET',
|
|
260
|
+
urlPath: '/trpc/test',
|
|
261
261
|
};
|
|
262
262
|
|
|
263
263
|
onStreamInvoke(params, callbacks);
|
|
@@ -290,13 +290,14 @@ describe('onStreamInvoke', () => {
|
|
|
290
290
|
};
|
|
291
291
|
|
|
292
292
|
const params: ProxyTRPCRequestParams = {
|
|
293
|
-
|
|
293
|
+
body: JSON.stringify({
|
|
294
294
|
filters: { active: true },
|
|
295
295
|
query: 'complex query',
|
|
296
296
|
sort: { field: 'date', order: 'desc' },
|
|
297
|
-
},
|
|
298
|
-
|
|
299
|
-
|
|
297
|
+
}),
|
|
298
|
+
headers: { 'content-type': 'application/json', 'x-custom-header': 'value' },
|
|
299
|
+
method: 'POST',
|
|
300
|
+
urlPath: '/trpc/lambda/complex.nested.endpoint',
|
|
300
301
|
};
|
|
301
302
|
|
|
302
303
|
onStreamInvoke(params, callbacks);
|
|
@@ -316,9 +317,9 @@ describe('onStreamInvoke', () => {
|
|
|
316
317
|
};
|
|
317
318
|
|
|
318
319
|
const params: ProxyTRPCRequestParams = {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
headers: {},
|
|
321
|
+
method: 'GET',
|
|
322
|
+
urlPath: '/trpc/test',
|
|
322
323
|
};
|
|
323
324
|
|
|
324
325
|
const cleanup = onStreamInvoke(params, callbacks);
|
|
@@ -346,9 +347,9 @@ describe('onStreamInvoke', () => {
|
|
|
346
347
|
};
|
|
347
348
|
|
|
348
349
|
const params: ProxyTRPCRequestParams = {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
headers: {},
|
|
351
|
+
method: 'GET',
|
|
352
|
+
urlPath: '/trpc/test',
|
|
352
353
|
};
|
|
353
354
|
|
|
354
355
|
onStreamInvoke(params, callbacks);
|
package/changelog/v1.json
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"children": {
|
|
4
|
+
"improvements": [
|
|
5
|
+
"Refactor agent slug schema."
|
|
6
|
+
]
|
|
7
|
+
},
|
|
8
|
+
"date": "2025-12-02",
|
|
9
|
+
"version": "2.0.0-next.146"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"children": {
|
|
13
|
+
"features": [
|
|
14
|
+
"Email provider support resend."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"date": "2025-12-02",
|
|
18
|
+
"version": "2.0.0-next.145"
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"children": {
|
|
4
22
|
"fixes": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
table agents {
|
|
2
2
|
id text [pk, not null]
|
|
3
|
-
slug varchar(100)
|
|
3
|
+
slug varchar(100)
|
|
4
4
|
title varchar(255)
|
|
5
5
|
description varchar(1000)
|
|
6
6
|
tags jsonb [default: `[]`]
|
|
@@ -27,6 +27,7 @@ table agents {
|
|
|
27
27
|
|
|
28
28
|
indexes {
|
|
29
29
|
(client_id, user_id) [name: 'client_id_user_id_unique', unique]
|
|
30
|
+
(slug, user_id) [name: 'agents_slug_user_id_unique', unique]
|
|
30
31
|
title [name: 'agents_title_idx']
|
|
31
32
|
description [name: 'agents_description_idx']
|
|
32
33
|
}
|
|
@@ -89,16 +89,27 @@ When configuring OAuth providers, use the following callback URL format:
|
|
|
89
89
|
|
|
90
90
|
### Email Service Configuration
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
| Environment Variable | Type
|
|
95
|
-
| ------------------------------------- |
|
|
96
|
-
| `NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION` | Optional
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
92
|
+
Used by email verification, password reset, and magic-link delivery. Choose a provider, then fill the matching variables:
|
|
93
|
+
|
|
94
|
+
| Environment Variable | Type | Description |
|
|
95
|
+
| ------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
96
|
+
| `NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION` | Optional | Set to `1` to require email verification before users can sign in |
|
|
97
|
+
| `EMAIL_SERVICE_PROVIDER` | Optional | Email provider selector: `nodemailer` (default, SMTP) or `resend` |
|
|
98
|
+
| `SMTP_HOST` | Required | SMTP server hostname (e.g., `smtp.gmail.com`). Used when `EMAIL_SERVICE_PROVIDER=nodemailer` |
|
|
99
|
+
| `SMTP_PORT` | Required | SMTP server port (usually `587` for TLS, `465` for SSL). Used when `EMAIL_SERVICE_PROVIDER=nodemailer` |
|
|
100
|
+
| `SMTP_SECURE` | Optional | `true` for SSL (port 465), `false` for TLS (port 587). Used when `EMAIL_SERVICE_PROVIDER=nodemailer` |
|
|
101
|
+
| `SMTP_USER` | Required | SMTP auth username. Used when `EMAIL_SERVICE_PROVIDER=nodemailer` |
|
|
102
|
+
| `SMTP_PASS` | Required | SMTP auth password. Used when `EMAIL_SERVICE_PROVIDER=nodemailer` |
|
|
103
|
+
| `RESEND_API_KEY` | Required | Resend API key. Required when `EMAIL_SERVICE_PROVIDER=resend` |
|
|
104
|
+
| `RESEND_FROM` | Recommended | Default sender address (e.g., `noreply@your-verified-domain.com`). Must be a domain verified in Resend. Used when `EMAIL_SERVICE_PROVIDER=resend` |
|
|
105
|
+
|
|
106
|
+
### Magic Link (Passwordless) Login
|
|
107
|
+
|
|
108
|
+
Enable BetterAuth magic-link login (depends on a working email provider above):
|
|
109
|
+
|
|
110
|
+
| Environment Variable | Type | Description |
|
|
111
|
+
| ------------------------------- | -------- | -------------------------------------------------- |
|
|
112
|
+
| `NEXT_PUBLIC_ENABLE_MAGIC_LINK` | Optional | Set to `1` to enable passwordless magic-link login |
|
|
102
113
|
|
|
103
114
|
<Callout type={'tip'}>
|
|
104
115
|
For detailed provider configuration, refer to the [Next Auth provider documentation](/docs/self-hosting/advanced/auth/next-auth) as most configurations are compatible, or visit the official [Better Auth documentation](https://www.better-auth.com/docs/introduction).
|