@allbluecn/web-app 0.10.3 → 0.11.1
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/package.json +24 -11
- package/public/favicon-dark.svg +1 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +1 -0
- package/public/logo/allblue-logo-app-icon.svg +7 -0
- package/public/logo/allblue-logo-dark-square.svg +4 -0
- package/public/logo/allblue-logo-dark.png +0 -0
- package/public/logo/allblue-logo-dark.svg +4 -0
- package/public/logo/allblue-logo-light-square.svg +4 -0
- package/public/logo/allblue-logo-light.png +0 -0
- package/public/logo/allblue-logo-light.svg +4 -0
- package/src/__tests__/bench/baselines/format-timestamp.json +44 -0
- package/src/__tests__/bench/core-hot-path.bench.ts +71 -0
- package/src/__tests__/integration/db-crud.integration.test.ts +79 -0
- package/src/__tests__/integration/login-email-acceptance.test.ts +202 -0
- package/src/__tests__/integration/login-email-p2-acceptance.test.ts +142 -0
- package/src/__tests__/rejects-tothrow-regression.test.ts +31 -0
- package/src/__tests__/security/ai/prompt-injection.test.ts +105 -0
- package/src/__tests__/security/ssrf-url-whitelist.test.ts +54 -0
- package/src/__tests__/security/xss-sanitize.test.ts +99 -0
- package/src/__tests__/setup.ts +9 -0
- package/src/__tests__/visual/__screenshots__/theme-surface.vrt.test.tsx/theme-surface-dark-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/__screenshots__/theme-surface.vrt.test.tsx/theme-surface-light-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/__screenshots__/user-avatar.vrt.test.tsx/user-avatar-initial-fallback-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/theme-surface.vrt.test.tsx +54 -0
- package/src/__tests__/visual/user-avatar.vrt.test.tsx +32 -0
- package/src/components/auth/icon-input.tsx +9 -1
- package/src/components/auth/password-input.tsx +9 -1
- package/src/components/changelog/changelog-page.tsx +44 -1
- package/src/components/error/global-error-boundary.tsx +2 -2
- package/src/components/forms/app-field.tsx +3 -1
- package/src/components/knowledge/general-groups.ts +15 -0
- package/src/components/knowledge/kb-filter-popover.tsx +143 -0
- package/src/components/knowledge/knowledge-toolbar.tsx +17 -21
- package/src/components/knowledge-public/legal-compliance-editor.tsx +4 -2
- package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +9 -6
- package/src/components/layout/sidebar-03/app-sidebar.tsx +4 -15
- package/src/components/layout/sidebar-03/nav-knowledge.tsx +19 -17
- package/src/components/layout/sidebar-03/nav-workspace.tsx +20 -22
- package/src/components/notifications/settings-notifications.tsx +41 -1
- package/src/components/settings/personal-team/account/settings-profile.tsx +68 -1
- package/src/components/shared-kit/avatars/notion-avatar.tsx +2 -1
- package/src/components/shared-kit/icons/__tests__/lucide-curated-icons.test.ts +2 -2
- package/src/components/shared-kit/icons/__tests__/lucide-icon-picker.test.tsx +3 -2
- package/src/components/shared-kit/icons/lucide-icon-picker/curated-icons.ts +5 -10
- package/src/components/shared-kit/icons/lucide-icon-picker/lucide-icon-by-name.tsx +3 -3
- package/src/components/shared-kit/theme/theme-surface.tsx +52 -0
- package/src/components/story/relations/general-groups.ts +1 -15
- package/src/components/story/relations/knowledge-select-dialog.tsx +13 -87
- package/src/components/story/story-list-view.tsx +7 -1
- package/src/components/tiptap/node/table-node/ui/table-move-row-column-button/use-table-move-row-column.ts +0 -2
- package/src/components/workspace/__tests__/activity/workspace-activity-card.test.tsx +36 -0
- package/src/components/workspace/__tests__/contribution/workspace-contribution-card.test.tsx +17 -0
- package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +63 -0
- package/src/components/workspace/__tests__/dialogs/delete-workspace-options.test.tsx +89 -0
- package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +68 -0
- package/src/components/workspace/__tests__/header/workspace-header.test.tsx +40 -0
- package/src/components/workspace/__tests__/kb/workspace-kb-card.test.tsx +154 -0
- package/src/components/workspace/__tests__/members/workspace-member-item.test.tsx +24 -0
- package/src/components/workspace/__tests__/members/workspace-members-manager.test.tsx +51 -0
- package/src/components/workspace/__tests__/projects/workspace-project-list.test.tsx +46 -0
- package/src/components/workspace/{create-workspace-dialog.tsx → dialogs/create-workspace-dialog.tsx} +1 -1
- package/src/components/workspace/{edit-workspace-dialog.tsx → dialogs/edit-workspace-dialog.tsx} +1 -1
- package/src/components/workspace/kb/workspace-kb-card.tsx +594 -0
- package/src/components/workspace/kb/workspace-kb-manage-dialog.tsx +174 -0
- package/src/components/workspace/kb/workspace-quick-doc-dialog.tsx +431 -0
- package/src/components/workspace/{workspace-members-manager.tsx → members/workspace-members-manager.tsx} +78 -40
- package/src/lib/__tests__/edition-desktop.test.ts +23 -0
- package/src/lib/__tests__/msw-sample.test.ts +68 -0
- package/src/lib/__tests__/pgvector.test.ts +2 -2
- package/src/lib/app-version.ts +1 -1
- package/src/lib/auth/auth.config.ts +123 -99
- package/src/lib/changelog/sdk-versions.ts +21 -21
- package/src/lib/collections/knowledge/knowledge-bases-collection.ts +3 -1
- package/src/lib/collections/reference/workspaces-collection.ts +3 -1
- package/src/lib/edition-desktop.ts +22 -0
- package/src/lib/fetch-json.ts +23 -0
- package/src/{components/workspace/workspace-kb-card.tsx → lib/i18n/resolver.ts} +6 -12
- package/src/lib/sanitize.ts +14 -1
- package/src/lib/session.ts +10 -2
- package/src/lib/vector-store/__tests__/factory.test.ts +1 -1
- package/src/lib/vector-store/__tests__/sqlite-vec-store.test.ts +2 -2
- package/src/plugins/core/tags/__tests__/tags.test.ts +10 -10
- package/src/routeTree.gen.ts +114 -0
- package/src/routes/__root.tsx +20 -3
- package/src/routes/__tests__/-auth-schemas.test.ts +62 -0
- package/src/routes/__tests__/-edition-routing.test.tsx +18 -5
- package/src/routes/__tests__/route-tree-structure.test.ts +66 -0
- package/src/routes/_login/changelog/confirm/route.lazy.tsx +72 -0
- package/src/routes/_login/changelog/confirm/route.tsx +28 -0
- package/src/routes/_login/email-unsubscribe/route.lazy.tsx +76 -0
- package/src/routes/_login/email-unsubscribe/route.tsx +28 -0
- package/src/routes/_login/forgot-password/route.lazy.tsx +2 -1
- package/src/routes/_login/login/route.lazy.tsx +4 -2
- package/src/routes/_login/register/route.lazy.tsx +28 -17
- package/src/routes/_login/register/route.tsx +8 -1
- package/src/routes/_login/reset-password/route.lazy.tsx +4 -2
- package/src/routes/_login/schemas/-auth.ts +30 -7
- package/src/routes/_login/verify-email/route.lazy.tsx +82 -0
- package/src/routes/_login/verify-email/route.tsx +28 -0
- package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +5 -2
- package/src/routes/_nav/knowledge/index.lazy.tsx +1 -1
- package/src/routes/_nav/knowledge/laws/index.lazy.tsx +5 -2
- package/src/routes/_nav/route.tsx +26 -0
- package/src/routes/_nav/workspaces/$workspaceId/route.lazy.tsx +13 -11
- package/src/routes/_nav/workspaces/$workspaceId/route.tsx +4 -2
- package/src/routes/_nav/workspaces/$workspaceId/settings/route.lazy.tsx +1 -1
- package/src/routes/_nav/workspaces/index.lazy.tsx +3 -3
- package/src/routes/_public/index/route.tsx +2 -1
- package/src/routes/api/auth/desktop-callback.ts +2 -2
- package/src/routes/api/billing-webhook.ts +4 -0
- package/src/routes/api/cron/changelog-release.ts +100 -0
- package/src/routes/api/cron/subscription-reminders.ts +110 -0
- package/src/routes/api.auth.$.ts +3 -5
- package/src/server/__tests__/agent-crud.test.ts +4 -4
- package/src/server/__tests__/auth-registration.test.ts +60 -0
- package/src/server/__tests__/chat-conversations.test.ts +1 -3
- package/src/server/__tests__/fetch-guard.test.ts +99 -0
- package/src/server/__tests__/permissions.test.ts +1 -3
- package/src/server/__tests__/resources.team-seats.test.ts +2 -0
- package/src/server/__tests__/skill-crud.test.ts +3 -3
- package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +95 -0
- package/src/server/ai-gateway/__tests__/kb-context-guard.test.ts +136 -0
- package/src/server/ai-gateway/__tests__/knowledge-search-fence.test.ts +240 -0
- package/src/server/ai-gateway/__tests__/llm-fence.test.ts +125 -0
- package/src/server/ai-gateway/__tests__/run-store.test.ts +52 -0
- package/src/server/ai-gateway/ai-output-guard.ts +77 -0
- package/src/server/ai-gateway/index.ts +5 -0
- package/src/server/ai-gateway/kb-context-guard.ts +46 -0
- package/src/server/ai-gateway/llm-fence.ts +82 -0
- package/src/server/ai-gateway/persistence/run-store.ts +19 -0
- package/src/server/ai-gateway/story-prompt.ts +4 -2
- package/src/server/ai-gateway/tools/definitions.ts +12 -6
- package/src/server/ai-gateway/tools/server.ts +97 -54
- package/src/server/auth-registration.ts +40 -0
- package/src/server/billing-email.ts +39 -0
- package/src/server/builtin-project.ts +21 -0
- package/src/server/email/__tests__/deliverability.test.ts +68 -0
- package/src/server/email/__tests__/index.test.ts +32 -0
- package/src/server/email/__tests__/outbox.test.tsx +129 -0
- package/src/server/email/__tests__/product-templates.test.tsx +106 -0
- package/src/server/email/__tests__/registry.test.tsx +50 -0
- package/src/server/email/deliverability.ts +57 -0
- package/src/server/email/drivers/console.ts +11 -0
- package/src/server/email/drivers/resend.ts +31 -0
- package/src/server/email/drivers/smtp.ts +27 -0
- package/src/server/email/index.ts +56 -0
- package/src/server/email/outbox.ts +141 -0
- package/src/server/email/preferences.ts +20 -0
- package/src/server/email/registry.ts +38 -0
- package/src/server/email/sender.ts +36 -13
- package/src/server/email/templates/_base/button.tsx +12 -0
- package/src/server/email/templates/_base/email-layout.tsx +117 -0
- package/src/server/email/templates/_base/theme-fonts.tsx +43 -0
- package/src/server/email/templates/_base/theme.ts +22 -0
- package/src/server/email/templates/auth/reset-password.tsx +58 -0
- package/src/server/email/templates/auth/verify-email.tsx +59 -0
- package/src/server/email/templates/auth/welcome.tsx +49 -0
- package/src/server/email/templates/billing/payment-receipt.tsx +51 -0
- package/src/server/email/templates/billing/subscription-canceled.tsx +51 -0
- package/src/server/email/templates/billing/subscription-ending.tsx +51 -0
- package/src/server/email/templates/notification/mirror.tsx +50 -0
- package/src/server/email/templates/product/changelog-confirm.tsx +53 -0
- package/src/server/email/templates/product/changelog-release.tsx +65 -0
- package/src/server/email/templates/team/apply-notify.tsx +53 -0
- package/src/server/email/templates/team/approved.tsx +53 -0
- package/src/server/email/templates/team/invite.tsx +57 -0
- package/src/server/fetch-guard.ts +170 -0
- package/src/server/middlewares/security-headers.ts +122 -0
- package/src/server/notifications/__tests__/email-mirror.test.ts +193 -0
- package/src/server/notifications/__tests__/verify-reminder.test.ts +94 -0
- package/src/server/notifications/notification-service.ts +71 -1
- package/src/server/notifications/verify-reminder.ts +55 -0
- package/src/server/oauth-config.ts +59 -0
- package/src/server/serverFns/__tests__/auth-server-schemas.test.ts +168 -0
- package/src/server/serverFns/__tests__/billing.test.ts +263 -0
- package/src/server/serverFns/__tests__/email-verification.test.ts +149 -0
- package/src/server/serverFns/__tests__/link-preview.test.ts +239 -0
- package/src/server/serverFns/__tests__/project-kb.test.ts +3 -3
- package/src/server/serverFns/__tests__/project-team-assign.test.ts +1 -1
- package/src/server/serverFns/__tests__/story-comments.test.ts +265 -0
- package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +266 -0
- package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +227 -0
- package/src/server/serverFns/__tests__/story-crud.test.ts +526 -0
- package/src/server/serverFns/__tests__/story-links.test.ts +262 -0
- package/src/server/serverFns/__tests__/story-list-query.test.ts +259 -0
- package/src/server/serverFns/__tests__/story-reactions.test.ts +230 -0
- package/src/server/serverFns/__tests__/story-tree.test.ts +317 -0
- package/src/server/serverFns/__tests__/story-types.test.ts +294 -0
- package/src/server/serverFns/__tests__/team-accept.test.ts +6 -6
- package/src/server/serverFns/__tests__/team-invitation.test.ts +5 -5
- package/src/server/serverFns/__tests__/team-join-request.test.ts +8 -8
- package/src/server/serverFns/__tests__/update-seats.test.ts +4 -4
- package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +160 -0
- package/src/server/serverFns/__tests__/workspace-kb-utils.test.ts +54 -0
- package/src/server/serverFns/__tests__/workspace-kb.test.ts +348 -0
- package/src/server/serverFns/__tests__/workspace.test.ts +28 -8
- package/src/server/serverFns/auth/email-verification.ts +132 -0
- package/src/server/serverFns/auth/forgot-password.ts +22 -2
- package/src/server/serverFns/auth/providers.ts +4 -3
- package/src/server/serverFns/auth/register.ts +63 -19
- package/src/server/serverFns/auth/registration-mode.ts +23 -0
- package/src/server/serverFns/auth/reset-password.ts +1 -1
- package/src/server/serverFns/auth/{invitation.ts → verify-reminder.ts} +9 -26
- package/src/server/serverFns/changelog/__tests__/subscribe.test.ts +119 -0
- package/src/server/serverFns/changelog/subscribe.ts +89 -0
- package/src/server/serverFns/email-unsubscribe.ts +66 -0
- package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +3 -5
- package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +1 -1
- package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +2 -2
- package/src/server/serverFns/kb-doc-search.ts +111 -0
- package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
- package/src/server/serverFns/notifications.ts +10 -0
- package/src/server/serverFns/story/__tests__/bulk.test.ts +1 -1
- package/src/server/serverFns/story/__tests__/label-suggest-fn.test.ts +2 -6
- package/src/server/serverFns/story/__tests__/story-assoc.test.ts +2 -4
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +2 -2
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +2 -2
- package/src/server/serverFns/story/kb-general-consts.ts +39 -0
- package/src/server/serverFns/story/kb-general.ts +30 -17
- package/src/server/serverFns/story/story-assoc.ts +13 -103
- package/src/server/serverFns/story/story-crud.ts +2 -5
- package/src/server/serverFns/story/story-groups.ts +4 -13
- package/src/server/serverFns/team.ts +50 -12
- package/src/server/serverFns/workspace-kb-utils.ts +138 -0
- package/src/server/serverFns/workspace-kb.ts +403 -0
- package/src/server/serverFns/workspace.ts +4 -1
- package/src/shared/ai-cli/__tests__/adapter.test.ts +67 -0
- package/src/shared/ai-cli/__tests__/cli-detector.test.ts +99 -0
- package/src/shared/ai-cli/adapter.ts +2 -2
- package/src/shared/ai-cli/cli-detector.ts +18 -9
- package/src/start.ts +2 -1
- package/src/styles/globals.css +114 -0
- package/src/types/jest-dom-vitest.d.ts +8 -7
- package/vite.shared.ts +105 -4
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/window.svg +0 -1
- package/src/lib/__tests__/cli-detector.test.ts +0 -80
- /package/src/components/workspace/{workspace-activity-card.tsx → activity/workspace-activity-card.tsx} +0 -0
- /package/src/components/workspace/{workspace-contribution-card.tsx → contribution/workspace-contribution-card.tsx} +0 -0
- /package/src/components/workspace/{delete-workspace-options.tsx → dialogs/delete-workspace-options.tsx} +0 -0
- /package/src/components/workspace/{workspace-header.tsx → header/workspace-header.tsx} +0 -0
- /package/src/components/workspace/{workspace-member-item.tsx → members/workspace-member-item.tsx} +0 -0
- /package/src/components/workspace/{workspace-project-list.tsx → projects/workspace-project-list.tsx} +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach, vi } from "vitest"
|
|
2
|
+
import type { CLIAgentInfo } from "@allbluecn/kernel"
|
|
3
|
+
|
|
4
|
+
const execFileSyncMock = vi.hoisted(() => vi.fn())
|
|
5
|
+
const execSyncMock = vi.hoisted(() => vi.fn())
|
|
6
|
+
|
|
7
|
+
vi.mock("node:child_process", async (importOriginal) => {
|
|
8
|
+
const actual = await importOriginal<typeof import("node:child_process")>()
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
execFileSync: execFileSyncMock,
|
|
12
|
+
execSync: execSyncMock,
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
vi.mock("child_process", async (importOriginal) => {
|
|
17
|
+
const actual = await importOriginal<typeof import("child_process")>()
|
|
18
|
+
return {
|
|
19
|
+
...actual,
|
|
20
|
+
execFileSync: execFileSyncMock,
|
|
21
|
+
execSync: execSyncMock,
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const testAgent: CLIAgentInfo = {
|
|
26
|
+
id: "claude-code",
|
|
27
|
+
name: "Claude Code",
|
|
28
|
+
binary: "claude",
|
|
29
|
+
version: "v1",
|
|
30
|
+
available: true,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("executeViaAgent 命令执行安全", () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
execFileSyncMock.mockReset()
|
|
36
|
+
execSyncMock.mockReset()
|
|
37
|
+
vi.resetModules()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("用 execFileSync 数组参数调用 CLI(无 shell 拼接)", async () => {
|
|
41
|
+
execFileSyncMock.mockReturnValue(" hello response \n")
|
|
42
|
+
const { executeViaAgent } = await import("../adapter")
|
|
43
|
+
const result = await executeViaAgent(testAgent, "你好", {} as never)
|
|
44
|
+
expect(result).toBe("hello response")
|
|
45
|
+
expect(execFileSyncMock).toHaveBeenCalledWith(
|
|
46
|
+
"claude",
|
|
47
|
+
["-p", "你好"],
|
|
48
|
+
expect.objectContaining({ timeout: 60000, encoding: "utf-8" })
|
|
49
|
+
)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("execSync 未被调用(锁定无 shell 注入不变量)", async () => {
|
|
53
|
+
execFileSyncMock.mockReturnValue("ok")
|
|
54
|
+
const { executeViaAgent } = await import("../adapter")
|
|
55
|
+
await executeViaAgent(testAgent, "test", {} as never)
|
|
56
|
+
expect(execSyncMock).not.toHaveBeenCalled()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("prompt 中的 shell 元字符作为字面量传递(防注入回归)", async () => {
|
|
60
|
+
const dangerous = `"; rm -rf /; echo $(whoami) \`id\`" && whoami`
|
|
61
|
+
execFileSyncMock.mockReturnValue("safe")
|
|
62
|
+
const { executeViaAgent } = await import("../adapter")
|
|
63
|
+
await executeViaAgent(testAgent, dangerous, {} as never)
|
|
64
|
+
expect(execFileSyncMock.mock.calls[0]![1]).toEqual(["-p", dangerous])
|
|
65
|
+
expect(execSyncMock).not.toHaveBeenCalled()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const execFileSyncMock = vi.hoisted(() => vi.fn())
|
|
4
|
+
const execSyncMock = vi.hoisted(() => vi.fn())
|
|
5
|
+
|
|
6
|
+
vi.mock('child_process', async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal<typeof import('child_process')>()
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
execFileSync: execFileSyncMock,
|
|
11
|
+
execSync: execSyncMock,
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
import { detectAgents, getApiAgent } from '../cli-detector'
|
|
16
|
+
|
|
17
|
+
describe('getApiAgent', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.resetModules()
|
|
20
|
+
delete process.env.OPENAI_API_KEY
|
|
21
|
+
delete process.env.AI_MODEL
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('未设置 API key 时返回 undefined', async () => {
|
|
25
|
+
const { getApiAgent: fn } = await import('../cli-detector')
|
|
26
|
+
expect(fn()).toBeUndefined()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('API key 为占位符时返回 undefined', async () => {
|
|
30
|
+
process.env.OPENAI_API_KEY = 'sk-your-api-key-here'
|
|
31
|
+
const { getApiAgent: fn } = await import('../cli-detector')
|
|
32
|
+
expect(fn()).toBeUndefined()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('API key 有效时返回 agent 信息', async () => {
|
|
36
|
+
process.env.OPENAI_API_KEY = 'sk-real-key'
|
|
37
|
+
const { getApiAgent: fn } = await import('../cli-detector')
|
|
38
|
+
const agent = fn()
|
|
39
|
+
expect(agent).toBeDefined()
|
|
40
|
+
expect(agent?.id).toBe('openai-api')
|
|
41
|
+
expect(agent?.available).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('使用 AI_MODEL 环境变量显示模型名', async () => {
|
|
45
|
+
process.env.OPENAI_API_KEY = 'sk-real-key'
|
|
46
|
+
process.env.AI_MODEL = 'gpt-4'
|
|
47
|
+
const { getApiAgent: fn } = await import('../cli-detector')
|
|
48
|
+
expect(fn()?.name).toContain('gpt-4')
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('detectAgents 安全探测', () => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
execFileSyncMock.mockReset()
|
|
55
|
+
execSyncMock.mockReset()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('用 execFileSync 数组参数探测版本(无 shell 拼接)', async () => {
|
|
59
|
+
execFileSyncMock.mockReturnValueOnce('v1.2.3')
|
|
60
|
+
const agents = await detectAgents()
|
|
61
|
+
const claude = agents.find((a) => a.id === 'claude-code')!
|
|
62
|
+
expect(claude.available).toBe(true)
|
|
63
|
+
expect(claude.version).toBe('v1.2.3')
|
|
64
|
+
expect(execFileSyncMock).toHaveBeenCalledWith(
|
|
65
|
+
expect.any(String),
|
|
66
|
+
expect.arrayContaining(['--version']),
|
|
67
|
+
expect.objectContaining({ timeout: expect.any(Number), encoding: 'utf-8' })
|
|
68
|
+
)
|
|
69
|
+
expect(execSyncMock).not.toHaveBeenCalled()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('--version 失败时回退 version 子命令(两步探测,无 shell ||)', async () => {
|
|
73
|
+
execFileSyncMock
|
|
74
|
+
.mockImplementationOnce(() => { throw new Error('not found') })
|
|
75
|
+
.mockReturnValueOnce('2.0.0')
|
|
76
|
+
const agents = await detectAgents()
|
|
77
|
+
const first = agents[0]!
|
|
78
|
+
expect(first.available).toBe(true)
|
|
79
|
+
expect(first.version).toBe('2.0.0')
|
|
80
|
+
const calls = execFileSyncMock.mock.calls
|
|
81
|
+
expect(calls[0]?.[1]).toEqual(['--version'])
|
|
82
|
+
expect(calls[1]?.[1]).toEqual(['version'])
|
|
83
|
+
expect(calls[0]![0]).toBe(calls[1]![0])
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('两步都失败标记不可用且不抛错', async () => {
|
|
87
|
+
execFileSyncMock.mockImplementation(() => { throw new Error('not found') })
|
|
88
|
+
const agents = await detectAgents()
|
|
89
|
+
expect(agents).toHaveLength(16)
|
|
90
|
+
expect(agents.every((a) => a.available === false)).toBe(true)
|
|
91
|
+
expect(agents.every((a) => a.version === '')).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('版本串截断到 50 字符且去除首尾空白', async () => {
|
|
95
|
+
execFileSyncMock.mockReturnValue(` ${'x'.repeat(80)} \n`)
|
|
96
|
+
const agents = await detectAgents()
|
|
97
|
+
expect(agents[0]!.version).toBe('x'.repeat(50))
|
|
98
|
+
})
|
|
99
|
+
})
|
|
@@ -50,8 +50,8 @@ export async function executeViaAgent(
|
|
|
50
50
|
return data.choices[0]?.message?.content || ""
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const {
|
|
54
|
-
const result =
|
|
53
|
+
const { execFileSync } = await import("child_process")
|
|
54
|
+
const result = execFileSync(agent.binary, ["-p", prompt], {
|
|
55
55
|
timeout: 60000,
|
|
56
56
|
encoding: "utf-8",
|
|
57
57
|
})
|
|
@@ -36,18 +36,27 @@ const KNOWN_AGENTS: { id: string; name: string; binary: string }[] = [
|
|
|
36
36
|
{ id: "mcp-cli", name: "MCP CLI", binary: "mcp" },
|
|
37
37
|
]
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
function probeVersion(
|
|
40
|
+
execFileSync: (file: string, args: string[], options: { timeout: number; encoding: "utf-8" }) => string,
|
|
41
|
+
binary: string,
|
|
42
|
+
): string {
|
|
43
|
+
const opts = { timeout: 3000, encoding: "utf-8" } as const
|
|
44
|
+
try {
|
|
45
|
+
return execFileSync(binary, ["--version"], opts).trim().slice(0, 50)
|
|
46
|
+
} catch {
|
|
42
47
|
try {
|
|
43
|
-
|
|
44
|
-
timeout: 3000,
|
|
45
|
-
encoding: "utf-8",
|
|
46
|
-
}).trim().slice(0, 50)
|
|
47
|
-
return { id, name, binary, version, available: true }
|
|
48
|
+
return execFileSync(binary, ["version"], opts).trim().slice(0, 50)
|
|
48
49
|
} catch {
|
|
49
|
-
return
|
|
50
|
+
return ""
|
|
50
51
|
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function detectAgents(): Promise<CLIAgentInfo[]> {
|
|
56
|
+
const { execFileSync } = await import("child_process")
|
|
57
|
+
return KNOWN_AGENTS.map(({ id, name, binary }) => {
|
|
58
|
+
const version = probeVersion(execFileSync, binary)
|
|
59
|
+
return { id, name, binary, version, available: version !== "" }
|
|
51
60
|
})
|
|
52
61
|
}
|
|
53
62
|
|
package/src/start.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { createStart, createCsrfMiddleware, createMiddleware } from '@tanstack/react-start'
|
|
19
|
+
import { securityHeadersMiddleware } from '@/server/middlewares/security-headers'
|
|
19
20
|
import { requestContextMiddleware } from '@/server/middlewares/request'
|
|
20
21
|
import { auditRequestMiddleware } from '@/server/middlewares/audit'
|
|
21
22
|
import { neonKeepaliveMiddleware } from '@/server/keepalive/middleware'
|
|
@@ -97,6 +98,6 @@ export const startInstance = createStart(async () => {
|
|
|
97
98
|
} as any)
|
|
98
99
|
}
|
|
99
100
|
return {
|
|
100
|
-
requestMiddleware: [requestContextMiddleware, neonKeepaliveMiddleware, aiPortsMiddleware, apiCorsMiddleware, csrfMiddleware, auditRequestMiddleware],
|
|
101
|
+
requestMiddleware: [securityHeadersMiddleware, requestContextMiddleware, neonKeepaliveMiddleware, aiPortsMiddleware, apiCorsMiddleware, csrfMiddleware, auditRequestMiddleware],
|
|
101
102
|
}
|
|
102
103
|
})
|
package/src/styles/globals.css
CHANGED
|
@@ -247,6 +247,120 @@
|
|
|
247
247
|
--color-5: oklch(90.7% 0.231 133);
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/* 知识库快捷文档标签莫兰迪色板(LABEL_COLORS 消费,light 原色 / dark 从种子色派生) */
|
|
251
|
+
:root {
|
|
252
|
+
--kb-row-mix: 45%;
|
|
253
|
+
--kb-seed-mrd: #9CAF97;
|
|
254
|
+
--kb-label-mrd-bg: #E8EFE5;
|
|
255
|
+
--kb-label-mrd-border: #9CAF97;
|
|
256
|
+
--kb-label-mrd-text: #5C705A;
|
|
257
|
+
--kb-seed-brd: #9FB2C4;
|
|
258
|
+
--kb-label-brd-bg: #E5EBF1;
|
|
259
|
+
--kb-label-brd-border: #9FB2C4;
|
|
260
|
+
--kb-label-brd-text: #54687A;
|
|
261
|
+
--kb-seed-feasibility: #C4B896;
|
|
262
|
+
--kb-label-feasibility-bg: #F1EDDF;
|
|
263
|
+
--kb-label-feasibility-border: #C4B896;
|
|
264
|
+
--kb-label-feasibility-text: #7A6E4C;
|
|
265
|
+
--kb-seed-competitor: #C4A48E;
|
|
266
|
+
--kb-label-competitor-bg: #F1E8E2;
|
|
267
|
+
--kb-label-competitor-border: #C4A48E;
|
|
268
|
+
--kb-label-competitor-text: #7A5F4C;
|
|
269
|
+
--kb-seed-charter: #B0A0B8;
|
|
270
|
+
--kb-label-charter-bg: #EDE7F0;
|
|
271
|
+
--kb-label-charter-border: #B0A0B8;
|
|
272
|
+
--kb-label-charter-text: #6A5A72;
|
|
273
|
+
--kb-seed-infoArchitecture: #96B8B0;
|
|
274
|
+
--kb-label-infoArchitecture-bg: #E2EFEC;
|
|
275
|
+
--kb-label-infoArchitecture-border: #96B8B0;
|
|
276
|
+
--kb-label-infoArchitecture-text: #4E6E68;
|
|
277
|
+
--kb-seed-techArchitecture: #8FA3AD;
|
|
278
|
+
--kb-label-techArchitecture-bg: #E1E8EB;
|
|
279
|
+
--kb-label-techArchitecture-border: #8FA3AD;
|
|
280
|
+
--kb-label-techArchitecture-text: #4E626C;
|
|
281
|
+
--kb-seed-dataStructure: #C09898;
|
|
282
|
+
--kb-label-dataStructure-bg: #F0E4E4;
|
|
283
|
+
--kb-label-dataStructure-border: #C09898;
|
|
284
|
+
--kb-label-dataStructure-text: #755050;
|
|
285
|
+
--kb-seed-integration: #B3A48E;
|
|
286
|
+
--kb-label-integration-bg: #EEE9E0;
|
|
287
|
+
--kb-label-integration-border: #B3A48E;
|
|
288
|
+
--kb-label-integration-text: #6E6046;
|
|
289
|
+
--kb-seed-copywriting: #C4A0A8;
|
|
290
|
+
--kb-label-copywriting-bg: #F2E7E9;
|
|
291
|
+
--kb-label-copywriting-border: #C4A0A8;
|
|
292
|
+
--kb-label-copywriting-text: #77545C;
|
|
293
|
+
--kb-seed-dictionary: #A8A88E;
|
|
294
|
+
--kb-label-dictionary-bg: #EBEBE0;
|
|
295
|
+
--kb-label-dictionary-border: #A8A88E;
|
|
296
|
+
--kb-label-dictionary-text: #68684C;
|
|
297
|
+
--kb-seed-legal: #8FA8B3;
|
|
298
|
+
--kb-label-legal-bg: #DCE8EC;
|
|
299
|
+
--kb-label-legal-border: #8FA8B3;
|
|
300
|
+
--kb-label-legal-text: #4C626C;
|
|
301
|
+
--kb-seed-compliance: #9FA88C;
|
|
302
|
+
--kb-label-compliance-bg: #E4E8DC;
|
|
303
|
+
--kb-label-compliance-border: #9FA88C;
|
|
304
|
+
--kb-label-compliance-text: #5E664E;
|
|
305
|
+
--kb-seed-kbDictionary: #B09A8C;
|
|
306
|
+
--kb-label-kbDictionary-bg: #EAE4DC;
|
|
307
|
+
--kb-label-kbDictionary-border: #B09A8C;
|
|
308
|
+
--kb-label-kbDictionary-text: #6E5A4C;
|
|
309
|
+
--kb-seed-website: #98A4B8;
|
|
310
|
+
--kb-label-website-bg: #E0E4EA;
|
|
311
|
+
--kb-label-website-border: #98A4B8;
|
|
312
|
+
--kb-label-website-text: #545E70;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.dark {
|
|
316
|
+
--kb-row-mix: 65%;
|
|
317
|
+
--kb-label-mrd-bg: color-mix(in srgb, var(--kb-seed-mrd) 20%, #1d1f1c);
|
|
318
|
+
--kb-label-mrd-border: color-mix(in srgb, var(--kb-seed-mrd) 45%, #1d1f1c);
|
|
319
|
+
--kb-label-mrd-text: color-mix(in srgb, var(--kb-seed-mrd) 60%, #dfe3dc);
|
|
320
|
+
--kb-label-brd-bg: color-mix(in srgb, var(--kb-seed-brd) 20%, #1c1e21);
|
|
321
|
+
--kb-label-brd-border: color-mix(in srgb, var(--kb-seed-brd) 45%, #1c1e21);
|
|
322
|
+
--kb-label-brd-text: color-mix(in srgb, var(--kb-seed-brd) 60%, #dce1e7);
|
|
323
|
+
--kb-label-feasibility-bg: color-mix(in srgb, var(--kb-seed-feasibility) 20%, #1f1d16);
|
|
324
|
+
--kb-label-feasibility-border: color-mix(in srgb, var(--kb-seed-feasibility) 45%, #1f1d16);
|
|
325
|
+
--kb-label-feasibility-text: color-mix(in srgb, var(--kb-seed-feasibility) 60%, #e6e2d5);
|
|
326
|
+
--kb-label-competitor-bg: color-mix(in srgb, var(--kb-seed-competitor) 20%, #1f1b18);
|
|
327
|
+
--kb-label-competitor-border: color-mix(in srgb, var(--kb-seed-competitor) 45%, #1f1b18);
|
|
328
|
+
--kb-label-competitor-text: color-mix(in srgb, var(--kb-seed-competitor) 60%, #e6ddd6);
|
|
329
|
+
--kb-label-charter-bg: color-mix(in srgb, var(--kb-seed-charter) 20%, #1d1b1f);
|
|
330
|
+
--kb-label-charter-border: color-mix(in srgb, var(--kb-seed-charter) 45%, #1d1b1f);
|
|
331
|
+
--kb-label-charter-text: color-mix(in srgb, var(--kb-seed-charter) 60%, #e2dde6);
|
|
332
|
+
--kb-label-infoArchitecture-bg: color-mix(in srgb, var(--kb-seed-infoArchitecture) 20%, #1b1e1d);
|
|
333
|
+
--kb-label-infoArchitecture-border: color-mix(in srgb, var(--kb-seed-infoArchitecture) 45%, #1b1e1d);
|
|
334
|
+
--kb-label-infoArchitecture-text: color-mix(in srgb, var(--kb-seed-infoArchitecture) 60%, #dbe4e1);
|
|
335
|
+
--kb-label-techArchitecture-bg: color-mix(in srgb, var(--kb-seed-techArchitecture) 20%, #1b1d1f);
|
|
336
|
+
--kb-label-techArchitecture-border: color-mix(in srgb, var(--kb-seed-techArchitecture) 45%, #1b1d1f);
|
|
337
|
+
--kb-label-techArchitecture-text: color-mix(in srgb, var(--kb-seed-techArchitecture) 60%, #dbe1e5);
|
|
338
|
+
--kb-label-dataStructure-bg: color-mix(in srgb, var(--kb-seed-dataStructure) 20%, #1f1b1b);
|
|
339
|
+
--kb-label-dataStructure-border: color-mix(in srgb, var(--kb-seed-dataStructure) 45%, #1f1b1b);
|
|
340
|
+
--kb-label-dataStructure-text: color-mix(in srgb, var(--kb-seed-dataStructure) 60%, #e6dcdc);
|
|
341
|
+
--kb-label-integration-bg: color-mix(in srgb, var(--kb-seed-integration) 20%, #1e1c18);
|
|
342
|
+
--kb-label-integration-border: color-mix(in srgb, var(--kb-seed-integration) 45%, #1e1c18);
|
|
343
|
+
--kb-label-integration-text: color-mix(in srgb, var(--kb-seed-integration) 60%, #e3ded4);
|
|
344
|
+
--kb-label-copywriting-bg: color-mix(in srgb, var(--kb-seed-copywriting) 20%, #1f1b1c);
|
|
345
|
+
--kb-label-copywriting-border: color-mix(in srgb, var(--kb-seed-copywriting) 45%, #1f1b1c);
|
|
346
|
+
--kb-label-copywriting-text: color-mix(in srgb, var(--kb-seed-copywriting) 60%, #e6dce0);
|
|
347
|
+
--kb-label-dictionary-bg: color-mix(in srgb, var(--kb-seed-dictionary) 20%, #1d1d19);
|
|
348
|
+
--kb-label-dictionary-border: color-mix(in srgb, var(--kb-seed-dictionary) 45%, #1d1d19);
|
|
349
|
+
--kb-label-dictionary-text: color-mix(in srgb, var(--kb-seed-dictionary) 60%, #e2e2da);
|
|
350
|
+
--kb-label-legal-bg: color-mix(in srgb, var(--kb-seed-legal) 20%, #1a1e20);
|
|
351
|
+
--kb-label-legal-border: color-mix(in srgb, var(--kb-seed-legal) 45%, #1a1e20);
|
|
352
|
+
--kb-label-legal-text: color-mix(in srgb, var(--kb-seed-legal) 60%, #d9e2e7);
|
|
353
|
+
--kb-label-compliance-bg: color-mix(in srgb, var(--kb-seed-compliance) 20%, #1c1e1a);
|
|
354
|
+
--kb-label-compliance-border: color-mix(in srgb, var(--kb-seed-compliance) 45%, #1c1e1a);
|
|
355
|
+
--kb-label-compliance-text: color-mix(in srgb, var(--kb-seed-compliance) 60%, #dee2d6);
|
|
356
|
+
--kb-label-kbDictionary-bg: color-mix(in srgb, var(--kb-seed-kbDictionary) 20%, #1e1c1a);
|
|
357
|
+
--kb-label-kbDictionary-border: color-mix(in srgb, var(--kb-seed-kbDictionary) 45%, #1e1c1a);
|
|
358
|
+
--kb-label-kbDictionary-text: color-mix(in srgb, var(--kb-seed-kbDictionary) 60%, #e3ddd6);
|
|
359
|
+
--kb-label-website-bg: color-mix(in srgb, var(--kb-seed-website) 20%, #1b1d21);
|
|
360
|
+
--kb-label-website-border: color-mix(in srgb, var(--kb-seed-website) 45%, #1b1d21);
|
|
361
|
+
--kb-label-website-text: color-mix(in srgb, var(--kb-seed-website) 60%, #dbe0e8);
|
|
362
|
+
}
|
|
363
|
+
|
|
250
364
|
@layer base {
|
|
251
365
|
* {
|
|
252
366
|
border-color: var(--color-border);
|
|
@@ -15,17 +15,18 @@
|
|
|
15
15
|
// You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
|
-
// Vitest
|
|
19
|
-
//
|
|
20
|
-
// `Assertion
|
|
21
|
-
//
|
|
18
|
+
// Vitest 5 类型兼容层:Vitest 5 将 expect 断言内联进主包(不再依赖 @vitest/expect),
|
|
19
|
+
// `declare module 'vitest'` 的接口增强直接生效。但 Vitest 5 的 `Assertion` 接口
|
|
20
|
+
// 使用双类型参数 `Assertion<R, T>`(R = matcher 返回类型,T = received 值类型),
|
|
21
|
+
// 而 @testing-library/jest-dom 官方 types/vitest.d.ts 仍声明单参数 `Assertion<T = any>`,
|
|
22
|
+
// 泛型参数个数不一致导致声明合并报 TS2428。这里以正确的双参数签名重写增强,
|
|
22
23
|
// 使 `toBeInTheDocument` / `toBeDisabled` 等 jest-dom matcher 类型可用。
|
|
23
24
|
// 运行时 matcher 注册在 src/__tests__/setup.ts 的 `import '@testing-library/jest-dom/vitest'`。
|
|
24
25
|
import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers'
|
|
25
26
|
|
|
26
|
-
declare module '
|
|
27
|
-
interface Assertion<T =
|
|
28
|
-
extends TestingLibraryMatchers<
|
|
27
|
+
declare module 'vitest' {
|
|
28
|
+
interface Assertion<R extends void | Promise<void> = void, T = unknown>
|
|
29
|
+
extends TestingLibraryMatchers<R, T> {}
|
|
29
30
|
interface AsymmetricMatchersContaining
|
|
30
31
|
extends TestingLibraryMatchers<any, any> {}
|
|
31
32
|
}
|
package/vite.shared.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
import { defineConfig, type PluginOption } from 'vite'
|
|
32
32
|
import path from 'node:path'
|
|
33
|
-
import { readFileSync } from 'node:fs'
|
|
33
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
34
34
|
import { execFileSync } from 'node:child_process'
|
|
35
35
|
|
|
36
36
|
// 从 apps/web/package.json 读取版本号,构建时注入到客户端/SSR
|
|
@@ -80,6 +80,90 @@ function prismaDirnamePolyfill(rootDir: string): PluginOption {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function cssTreeRuntimeAssetsPlugin(rootDir: string): PluginOption {
|
|
84
|
+
return {
|
|
85
|
+
name: 'css-tree-runtime-assets',
|
|
86
|
+
async closeBundle() {
|
|
87
|
+
// nitro 把 css-tree / jsdom(isomorphic-dompurify 依赖)外部化打平为
|
|
88
|
+
// _libs/*.mjs,其内部运行时 require 的资源文件不随 trace 拷贝 ——
|
|
89
|
+
// closeBundle 后按产物的解析路径补齐,否则冷启动 ENOENT 全站 500。
|
|
90
|
+
const { mkdir, readdir, copyFile } = await import('node:fs/promises')
|
|
91
|
+
const funcDir = path.resolve(rootDir, '.vercel/output/functions/__server.func')
|
|
92
|
+
const pnpmDir = path.resolve(rootDir, '../../node_modules/.pnpm')
|
|
93
|
+
try {
|
|
94
|
+
// a) jsdom 的 xhr-sync-worker.js(运行时相对 require)
|
|
95
|
+
const isoVdirs = (await readdir(pnpmDir)).filter((d) => d.startsWith('isomorphic-dompurify@'))
|
|
96
|
+
for (const v of isoVdirs) {
|
|
97
|
+
const worker = path.join(pnpmDir, v, 'node_modules/jsdom/lib/jsdom/living/xhr/xhr-sync-worker.js')
|
|
98
|
+
if (existsSync(worker)) {
|
|
99
|
+
await mkdir(path.join(funcDir, '_libs'), { recursive: true })
|
|
100
|
+
await copyFile(worker, path.join(funcDir, '_libs/xhr-sync-worker.js'))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// a2) jsdom XHR 的 syncWorker resolve 在 bundle 后路径失真(真实 worker 文件
|
|
104
|
+
// 已拷贝到 _libs 根,仍解析失败则降级空串——服务端 syncWorker 走 null 分支)
|
|
105
|
+
const isoLib = path.join(funcDir, '_libs/isomorphic-dompurify.mjs')
|
|
106
|
+
{
|
|
107
|
+
const { readFile, writeFile } = await import('node:fs/promises')
|
|
108
|
+
let code = await readFile(isoLib, 'utf8')
|
|
109
|
+
const target = '__require.resolve("./xhr-sync-worker.js")'
|
|
110
|
+
if (code.includes(target)) {
|
|
111
|
+
code = code.replaceAll(
|
|
112
|
+
target,
|
|
113
|
+
'(()=>{try{return __require.resolve("./xhr-sync-worker.js")}catch{return ""}})()',
|
|
114
|
+
)
|
|
115
|
+
await writeFile(isoLib, code)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// b) css-tree data/patch.json(dom-selector 内 createRequire('../data/patch.json'))
|
|
119
|
+
// css-tree 是 transitive 包,从 .pnpm 虚拟目录扫描定位
|
|
120
|
+
const ctVdirs = (await readdir(pnpmDir)).filter((d) => d.startsWith('css-tree@'))
|
|
121
|
+
let cssTreePkg: string | null = null
|
|
122
|
+
for (const v of ctVdirs) {
|
|
123
|
+
const cand = path.join(pnpmDir, v, 'node_modules/css-tree')
|
|
124
|
+
if (existsSync(path.join(cand, 'data/patch.json'))) {
|
|
125
|
+
cssTreePkg = cand
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!cssTreePkg) throw new Error('css-tree data/patch.json 未找到')
|
|
130
|
+
const patchDestDir = path.join(funcDir, '_libs/data')
|
|
131
|
+
await mkdir(patchDestDir, { recursive: true })
|
|
132
|
+
await copyFile(path.join(cssTreePkg, 'data/patch.json'), path.join(patchDestDir, 'patch.json'))
|
|
133
|
+
// c) mdn-data css json(require('mdn-data/css/*.json') 向上解析 node_modules)
|
|
134
|
+
const mdnCssDir = path.join(path.dirname(cssTreePkg), 'mdn-data/css')
|
|
135
|
+
const dest = path.join(funcDir, 'node_modules/mdn-data/css')
|
|
136
|
+
await mkdir(dest, { recursive: true })
|
|
137
|
+
for (const f of await readdir(mdnCssDir)) {
|
|
138
|
+
if (f.endsWith('.json')) await copyFile(path.join(mdnCssDir, f), path.join(dest, f))
|
|
139
|
+
}
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error('[css-tree-runtime-assets] 资源补齐失败:', e instanceof Error ? e.message : e)
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function runtimeResourceInlinePlugin(): PluginOption {
|
|
148
|
+
return {
|
|
149
|
+
name: 'runtime-resource-inline',
|
|
150
|
+
transform(code, id) {
|
|
151
|
+
// jsdom 模块顶层同步读取默认样式表,nitro 打平 bundle 后
|
|
152
|
+
// __dirname 相对路径失效,冷启动 ENOENT 全站 500 → 构建期内联
|
|
153
|
+
if (id.includes('jsdom') && id.includes('computed-style.js') && code.includes('default-stylesheet.css')) {
|
|
154
|
+
const cssPath = path.resolve(path.dirname(id), '../../../browser/default-stylesheet.css')
|
|
155
|
+
const css = readFileSync(cssPath, 'utf8')
|
|
156
|
+
const replaced = code.replace(
|
|
157
|
+
/fs\.readFileSync\(\s*path\.resolve\(__dirname,\s*"\.\.\/\.\.\/\.\.\/browser\/default-stylesheet\.css"\),\s*\{\s*encoding:\s*"utf-8"\s*\}\s*\)/,
|
|
158
|
+
JSON.stringify(css),
|
|
159
|
+
)
|
|
160
|
+
if (replaced !== code) return { code: replaced, map: null }
|
|
161
|
+
}
|
|
162
|
+
return undefined
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
83
167
|
function tiptapAliasPlugin(rootDir: string): PluginOption {
|
|
84
168
|
const tiptapSrc = path.resolve(rootDir, '../../packages/tiptap/src')
|
|
85
169
|
return {
|
|
@@ -108,7 +192,7 @@ export function createWebViteConfig(opts: WebViteConfigOptions) {
|
|
|
108
192
|
const appUpdateDate = getAppUpdateDate(opts.rootDir)
|
|
109
193
|
return defineConfig(async () => {
|
|
110
194
|
const deployTarget = process.env.DEPLOY_TARGET
|
|
111
|
-
const plugins: PluginOption[] = [...opts.basePlugins, tiptapAliasPlugin(opts.rootDir)]
|
|
195
|
+
const plugins: PluginOption[] = [...opts.basePlugins, tiptapAliasPlugin(opts.rootDir), runtimeResourceInlinePlugin()]
|
|
112
196
|
|
|
113
197
|
// Vercel 平台自动注入 VERCEL_ENV 环境变量,nitro 插件始终挂载。DEPLOY_TARGET=vercel
|
|
114
198
|
// 保留作为本地兼容触发器(Pro 仓 build.sh 及本地 vercel preview 均依赖它)。
|
|
@@ -116,7 +200,7 @@ export function createWebViteConfig(opts: WebViteConfigOptions) {
|
|
|
116
200
|
if (isVercelTarget) {
|
|
117
201
|
const { nitro } = await import('nitro/vite')
|
|
118
202
|
plugins.splice(2, 0, nitro({ preset: 'vercel' }))
|
|
119
|
-
plugins.push(prismaDirnamePolyfill(opts.rootDir))
|
|
203
|
+
plugins.push(prismaDirnamePolyfill(opts.rootDir), cssTreeRuntimeAssetsPlugin(opts.rootDir))
|
|
120
204
|
}
|
|
121
205
|
|
|
122
206
|
if (deployTarget === 'cloudflare') {
|
|
@@ -139,8 +223,25 @@ export function createWebViteConfig(opts: WebViteConfigOptions) {
|
|
|
139
223
|
},
|
|
140
224
|
server: {
|
|
141
225
|
port: 3000,
|
|
142
|
-
|
|
226
|
+
host: true,
|
|
227
|
+
// host.docker.internal:容器内安全扫描(ZAP/Nuclei,见
|
|
228
|
+
// scripts/security/)经该地址访问宿主 dev server;Vite 默认拒绝
|
|
229
|
+
// 非白名单 Host 头(返回 403 "Blocked request")。仅 dev server 生效。
|
|
230
|
+
allowedHosts: ['localhost', 'host.docker.internal'],
|
|
143
231
|
cors: { origin: false },
|
|
232
|
+
// Vite 内部中间件(/src/*、/@fs/*、.map)的响应绕过 TanStack Start
|
|
233
|
+
// requestMiddleware,故在此层补基础安全头;CSP/HSTS 仍由 middleware
|
|
234
|
+
// 统一注入(apps/web/src/server/middlewares/security-headers.ts),
|
|
235
|
+
// 生产构建无 Vite dev server,middleware 即唯一来源。
|
|
236
|
+
headers: {
|
|
237
|
+
'X-Content-Type-Options': 'nosniff',
|
|
238
|
+
'X-Frame-Options': 'DENY',
|
|
239
|
+
'Cross-Origin-Resource-Policy': 'same-origin',
|
|
240
|
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
241
|
+
'Permissions-Policy':
|
|
242
|
+
'camera=(), microphone=(), geolocation=(), payment=(), usb=(), fullscreen=(self)',
|
|
243
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
244
|
+
},
|
|
144
245
|
},
|
|
145
246
|
plugins,
|
|
146
247
|
resolve: {
|
package/public/file.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
package/public/globe.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
package/public/window.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
// AllBlue - 理想之海
|
|
3
|
-
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
-
//
|
|
5
|
-
// This program is free software: you can redistribute it and/or modify
|
|
6
|
-
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
-
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
-
// (at your option) any later version.
|
|
9
|
-
//
|
|
10
|
-
// This program is distributed in the hope that it will be useful,
|
|
11
|
-
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
// GNU Affero General Public License for more details.
|
|
14
|
-
//
|
|
15
|
-
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
-
|
|
18
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
19
|
-
|
|
20
|
-
describe('getApiAgent', () => {
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
vi.resetModules()
|
|
23
|
-
delete process.env.OPENAI_API_KEY
|
|
24
|
-
delete process.env.AI_MODEL
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('returns undefined when no API key set', async () => {
|
|
28
|
-
const { getApiAgent } = await import('@/shared/ai-cli/cli-detector')
|
|
29
|
-
expect(getApiAgent()).toBeUndefined()
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('returns undefined when API key is placeholder', async () => {
|
|
33
|
-
process.env.OPENAI_API_KEY = 'sk-your-api-key-here'
|
|
34
|
-
const { getApiAgent } = await import('@/shared/ai-cli/cli-detector')
|
|
35
|
-
expect(getApiAgent()).toBeUndefined()
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('returns agent info when API key is valid', async () => {
|
|
39
|
-
process.env.OPENAI_API_KEY = 'sk-real-key'
|
|
40
|
-
const { getApiAgent } = await import('@/shared/ai-cli/cli-detector')
|
|
41
|
-
const agent = getApiAgent()
|
|
42
|
-
expect(agent).toBeDefined()
|
|
43
|
-
expect(agent?.id).toBe('openai-api')
|
|
44
|
-
expect(agent?.available).toBe(true)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('uses AI_MODEL env in display name', async () => {
|
|
48
|
-
process.env.OPENAI_API_KEY = 'sk-real-key'
|
|
49
|
-
process.env.AI_MODEL = 'gpt-4'
|
|
50
|
-
const { getApiAgent } = await import('@/shared/ai-cli/cli-detector')
|
|
51
|
-
expect(getApiAgent()?.name).toContain('gpt-4')
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
describe('detectAgents', () => {
|
|
56
|
-
beforeEach(() => {
|
|
57
|
-
vi.resetModules()
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('returns 16 known agents with executable check', async () => {
|
|
61
|
-
const { detectAgents } = await import('@/shared/ai-cli/cli-detector')
|
|
62
|
-
const agents = await detectAgents()
|
|
63
|
-
expect(agents).toHaveLength(16)
|
|
64
|
-
agents.forEach(a => {
|
|
65
|
-
expect(a.id).toBeDefined()
|
|
66
|
-
expect(a.binary).toBeDefined()
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('handles all agents failing gracefully', async () => {
|
|
71
|
-
vi.doMock('child_process', () => ({
|
|
72
|
-
execSync: vi.fn(() => { throw new Error('not found') }),
|
|
73
|
-
}))
|
|
74
|
-
|
|
75
|
-
const { detectAgents } = await import('@/shared/ai-cli/cli-detector')
|
|
76
|
-
const agents = await detectAgents()
|
|
77
|
-
expect(agents).toHaveLength(16)
|
|
78
|
-
agents.forEach(a => expect(a.available).toBe(false))
|
|
79
|
-
})
|
|
80
|
-
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/src/components/workspace/{workspace-member-item.tsx → members/workspace-member-item.tsx}
RENAMED
|
File without changes
|
/package/src/components/workspace/{workspace-project-list.tsx → projects/workspace-project-list.tsx}
RENAMED
|
File without changes
|