diff --git a/.github/workflows/web-sync-server-deploy.yml b/.github/workflows/web-sync-server-deploy.yml index 02ee94bab..ce690804c 100644 --- a/.github/workflows/web-sync-server-deploy.yml +++ b/.github/workflows/web-sync-server-deploy.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "26" - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 diff --git a/.github/workflows/web-test.yml b/.github/workflows/web-test.yml index 2db134797..40c24b601 100644 --- a/.github/workflows/web-test.yml +++ b/.github/workflows/web-test.yml @@ -21,12 +21,12 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '26' - name: Setup pnpm - uses: pnpm/action-setup@v4.0.0 + uses: pnpm/action-setup@v4 with: - version: 9 + package_json_file: moon/package.json - name: Get pnpm store directory shell: bash diff --git a/.gitignore b/.gitignore index 4bafee42c..4e0379e11 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,9 @@ orion-server/docker-compose.override.yml # Claude Code configuration and cache .claude +# pnpm content-addressable store (local / custom store-dir) +.pnpm-store + tools/** .libra diff --git a/moon/.gitignore b/moon/.gitignore index 757a9ed20..c30898020 100644 --- a/moon/.gitignore +++ b/moon/.gitignore @@ -4,6 +4,7 @@ node_modules .pnp .pnp.js +.pnpm-store dist *.tsbuildinfo diff --git a/moon/.nvmrc b/moon/.nvmrc index 209e3ef4b..6f4247a62 100644 --- a/moon/.nvmrc +++ b/moon/.nvmrc @@ -1 +1 @@ -20 +26 diff --git a/moon/apps/sync-server/Dockerfile b/moon/apps/sync-server/Dockerfile index b831accda..d5403fc9e 100644 --- a/moon/apps/sync-server/Dockerfile +++ b/moon/apps/sync-server/Dockerfile @@ -1,11 +1,11 @@ # syntax = docker/dockerfile:1 -FROM node:22-slim AS base +FROM node:26-slim AS base -# configure pnpm +# configure pnpm (node:26 images no longer ship corepack) ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME/bin:$PNPM_HOME:$PATH" -RUN corepack enable && corepack prepare pnpm@9.7.1 --activate +RUN npm install -g pnpm@11.17.0 # ENV SENTRY_PROPERTIES=sentry.properties diff --git a/moon/apps/sync-server/package.json b/moon/apps/sync-server/package.json index 173e17c5a..515796171 100644 --- a/moon/apps/sync-server/package.json +++ b/moon/apps/sync-server/package.json @@ -6,7 +6,6 @@ "build": "tsup", "clean": "rm -rf .turbo dist node_modules", "dev": "tsup --watch --onSuccess \"node dist/index.mjs\"", - "sentry:sourcemaps": "sentry-cli sourcemaps inject --org mega --project sync-server ./dist && sentry-cli sourcemaps upload --org mega --project sync-server --log-level=debug ./dist", "start": "node dist/index.mjs" }, "dependencies": { @@ -16,7 +15,6 @@ "@hocuspocus/extension-logger": "catalog:", "@hocuspocus/server": "catalog:", "@hocuspocus/transformer": "catalog:", - "@sentry/node": "catalog:", "@tiptap/core": "catalog:", "@tiptap/html": "catalog:", "@tiptap/pm": "catalog:", @@ -27,7 +25,6 @@ "devDependencies": { "@flydotio/dockerfile": "catalog:", "@gitmono/tsconfig": "workspace:*", - "@sentry/cli": "catalog:", "tsup": "catalog:", "typescript": "catalog:" } diff --git a/moon/apps/sync-server/src/database.ts b/moon/apps/sync-server/src/database.ts index 6131ed2a7..65d1d3083 100644 --- a/moon/apps/sync-server/src/database.ts +++ b/moon/apps/sync-server/src/database.ts @@ -1,7 +1,6 @@ import { Database } from '@hocuspocus/extension-database' import { Document } from '@hocuspocus/server' import { TiptapTransformer } from '@hocuspocus/transformer' -import * as Sentry from '@sentry/node' import { generateHTML, generateJSON } from '@tiptap/html' import { fromUint8Array, toUint8Array } from 'js-base64' import * as Y from 'yjs' @@ -14,14 +13,14 @@ import { Context } from './types' const extensions = getNoteExtensions() export function sendVersionToConnections(document: Document, version: number) { - document.connections.forEach((connection) => { - const connectionSchemaVersion = connection.connection.context.schemaVersion ?? 0 + document.getConnections().forEach((connection) => { + const connectionSchemaVersion = (connection.context as Context | undefined)?.schemaVersion ?? 0 // Update connections to readOnly if the schema version is lower than the current version - connection.connection.readOnly = connectionSchemaVersion < version + connection.readOnly = connectionSchemaVersion < version // Send the schema version to the client - connection.connection.sendStateless( + connection.sendStateless( JSON.stringify({ type: 'schema', version @@ -45,20 +44,41 @@ export async function getResource({ token, id, type, organization }: GetResource } } +function resolveContext(data: { + context?: Context + lastContext?: Context + requestParameters?: URLSearchParams +}): Context | undefined { + const fromPayload = data.lastContext ?? data.context + + if (fromPayload?.token && fromPayload.organization) { + return fromPayload + } + + const organization = data.requestParameters?.get('organization') ?? fromPayload?.organization + const type = data.requestParameters?.get('type') ?? fromPayload?.type ?? null + const token = fromPayload?.token + const schemaVersion = fromPayload?.schemaVersion ?? 0 + + if (!token || !organization) return fromPayload + + return { token, schemaVersion, organization, type } +} + export const database = new Database({ /** * Fetch the document state from Campsite, or generate a new document from the existing * HTML if the document has never been edited before. */ async fetch(data) { - const context: Context = data.context + const context = resolveContext(data) const id = data.documentName - const organization = data.requestParameters.get('organization') - const type = data.requestParameters.get('type') + const organization = context?.organization + const type = context?.type ?? null try { - if (!organization) return new Uint8Array() + if (!context?.token || !organization) return new Uint8Array() const state = await getResource({ token: context.token, id, type, organization }) @@ -79,16 +99,11 @@ export const database = new Database({ return Y.encodeStateAsUpdate(ydoc) } catch (error) { - Sentry.setContext('document', { - id, - organization, - type - }) - Sentry.setContext('context', { - schemaVersion: context.schemaVersion, - token: context.token + console.error('database.fetch failed', { + document: { id, organization, type }, + schemaVersion: context?.schemaVersion, + error }) - Sentry.captureException(error) throw error } }, @@ -97,14 +112,14 @@ export const database = new Database({ * Store the document state in Campsite. */ async store(data) { - const context: Context = data.context + const context = resolveContext(data) const id = data.documentName - const organization = data.requestParameters.get('organization') - const type = data.requestParameters.get('type') + const organization = context?.organization + const type = context?.type ?? null try { - if (!organization) return + if (!context?.token || !organization) return // Generate a state from the Yjs document const state = Y.encodeStateAsUpdate(data.document) @@ -128,16 +143,11 @@ export const database = new Database({ } ) } catch (error) { - Sentry.setContext('document', { - id, - organization, - type - }) - Sentry.setContext('context', { - schemaVersion: context.schemaVersion, - token: context.token + console.error('database.store failed', { + document: { id, organization, type }, + schemaVersion: context?.schemaVersion, + error }) - Sentry.captureException(error) throw error } } diff --git a/moon/apps/sync-server/src/index.ts b/moon/apps/sync-server/src/index.ts index 157049a46..c56636fab 100644 --- a/moon/apps/sync-server/src/index.ts +++ b/moon/apps/sync-server/src/index.ts @@ -1,12 +1,11 @@ import { Logger } from '@hocuspocus/extension-logger' -import { Hocuspocus } from '@hocuspocus/server' -import * as Sentry from '@sentry/node' +import { Server } from '@hocuspocus/server' import { PORT } from './config' import { database, getResource, sendVersionToConnections } from './database' import { AuthenticationError, Context } from './types' -const server = new Hocuspocus({ +const server = new Server({ port: PORT, async onAuthenticate(data): Promise { @@ -32,23 +31,20 @@ const server = new Hocuspocus({ const document = data.instance.documents.get(data.documentName) if (document) sendVersionToConnections(document, state.description_schema_version) - data.connection.readOnly = schemaVersion < state.description_schema_version + data.connectionConfig.readOnly = schemaVersion < state.description_schema_version return { token: data.token, - schemaVersion - } - } catch (error) { - Sentry.setContext('document', { - id: data.documentName, + schemaVersion, organization, type + } + } catch (error) { + console.error('onAuthenticate failed', { + document: { id: data.documentName, organization, type }, + schemaVersion, + error }) - Sentry.setContext('context', { - schemaVersion: schemaVersion, - token: data.token - }) - Sentry.captureException(error) throw error } }, diff --git a/moon/apps/sync-server/src/types.ts b/moon/apps/sync-server/src/types.ts index c27bedef7..40d280f30 100644 --- a/moon/apps/sync-server/src/types.ts +++ b/moon/apps/sync-server/src/types.ts @@ -1,6 +1,8 @@ export interface Context { token: string schemaVersion: number + organization: string + type: string | null } export type AuthenticationErrorType = 'no-token' | 'invalid-type' diff --git a/moon/apps/sync-server/tsconfig.json b/moon/apps/sync-server/tsconfig.json index c2b0d618d..8065e6e21 100644 --- a/moon/apps/sync-server/tsconfig.json +++ b/moon/apps/sync-server/tsconfig.json @@ -7,9 +7,6 @@ "outDir": "./dist", "sourceMap": true, "inlineSources": true, - // Set `sourceRoot` to "/" to strip the build path prefix - // from generated source code references. - // This improves issue grouping in Sentry. "sourceRoot": "/" } } diff --git a/moon/apps/sync-server/turbo.json b/moon/apps/sync-server/turbo.json index a8ca1c7f1..2e51c1a6e 100644 --- a/moon/apps/sync-server/turbo.json +++ b/moon/apps/sync-server/turbo.json @@ -3,14 +3,11 @@ "extends": ["//"], "tasks": { "dev": { - "dependsOn": ["build"], + "dependsOn": ["^build", "build"], "passThroughEnv": ["PORT"] }, "build": { - "env": ["SENTRY_AUTH_TOKEN", "SENTRY_ORG", "SENTRY_PROJECT", "TIPTAP_PRIVATE_REGISTRY_KEY", "NODE_ENV", "PORT"] - }, - "sentry:sourcemaps": { - "env": ["SENTRY_AUTH_TOKEN", "SENTRY_ORG", "SENTRY_PROJECT"] + "env": ["TIPTAP_PRIVATE_REGISTRY_KEY", "NODE_ENV", "PORT"] } } } diff --git a/moon/apps/web/.eslintrc.js b/moon/apps/web/.eslintrc.js deleted file mode 100644 index 517f3b456..000000000 --- a/moon/apps/web/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -/** @type {import("eslint").Linter.Config} */ -module.exports = { - root: true, - extends: ['@gitmono/eslint-config/base.js', '@gitmono/eslint-config/next.js', 'plugin:storybook/recommended'], - rules: { - // 禁用文件最大行数限制 - 'max-lines': 'off' - } -} diff --git a/moon/apps/web/.storybook/main.js b/moon/apps/web/.storybook/main.js index 7b344e5fb..7e2b6ef30 100644 --- a/moon/apps/web/.storybook/main.js +++ b/moon/apps/web/.storybook/main.js @@ -11,12 +11,7 @@ function getAbsolutePath(value) { /** @type { import('@storybook/nextjs').StorybookConfig } */ const config = { stories: ['../**/*.stories.@(js|jsx|mjs|ts|tsx)', '../../../packages/ui/**/*.stories.@(js|jsx|mjs|ts|tsx)'], - addons: [ - getAbsolutePath('@storybook/addon-links'), - getAbsolutePath('@storybook/addon-essentials'), - getAbsolutePath('@storybook/addon-interactions'), - getAbsolutePath('@storybook/addon-docs') - ], + addons: [getAbsolutePath('@storybook/addon-links'), getAbsolutePath('@storybook/addon-docs')], framework: { name: getAbsolutePath('@storybook/nextjs'), options: {} @@ -36,4 +31,5 @@ const config = { // }, staticDirs: ['../public'] } + export default config diff --git a/moon/apps/web/Dockerfile b/moon/apps/web/Dockerfile index 94c37e9e8..0279c7ab7 100644 --- a/moon/apps/web/Dockerfile +++ b/moon/apps/web/Dockerfile @@ -1,11 +1,11 @@ # syntax = docker/dockerfile:1 -FROM node:20-slim AS base +FROM node:26-slim AS base -# configure pnpm +# configure pnpm (node:26 images no longer ship corepack) ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME/bin:$PNPM_HOME:$PATH" -RUN corepack enable && corepack prepare pnpm@9.7.1 --activate +RUN npm install -g pnpm@11.17.0 # Throw-away build stage to reduce size of final image diff --git a/moon/apps/web/atoms/call.ts b/moon/apps/web/atoms/call.ts deleted file mode 100644 index 01eac4cda..000000000 --- a/moon/apps/web/atoms/call.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { HMSRoomState } from '@100mslive/react-sdk' -import { atom } from 'jotai' - -import { CallPeer, OrganizationMember } from '@gitmono/types' - -export const titleAtom = atom(null) -export const incomingCallRoomInvitationAtom = atom(undefined) -export const callRoomStateAtom = atom(HMSRoomState.Disconnected) -export const callChatOpenAtom = atom(false) - -export interface CallRoomInvitation { - call_room_id: string - call_room_url: string - creator_member: OrganizationMember - other_active_peers: CallPeer[] - skip_push: boolean -} - -type CallRoomState = HMSRoomState | 'Login' | 'Left' - -export const joinCallAtom = atom(null, (_get, set, payload: { title: string | null }) => { - set(titleAtom, payload.title) - set(incomingCallRoomInvitationAtom, undefined) -}) diff --git a/moon/apps/web/atoms/callToasts.ts b/moon/apps/web/atoms/callToasts.ts deleted file mode 100644 index c73dbfcae..000000000 --- a/moon/apps/web/atoms/callToasts.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { atom } from 'jotai' -import { v4 as uuid } from 'uuid' - -export const callToastsAtom = atom>(new Map()) - -export const addCallToastAtom = atom(undefined, (get, set, message: string) => { - const newMessageId = uuid() - - set(callToastsAtom, new Map(get(callToastsAtom).set(newMessageId, message))) - - setTimeout(() => { - set(deleteCallToastAtom, newMessageId) - }, 6000) -}) - -const deleteCallToastAtom = atom(undefined, (get, set, id: string) => { - const callToastMessages = get(callToastsAtom) - - callToastMessages.delete(id) - set(callToastsAtom, new Map(callToastMessages)) -}) diff --git a/moon/apps/web/components/AccountReview/PendingAccountReviewButton.tsx b/moon/apps/web/components/AccountReview/PendingAccountReviewButton.tsx index 903b14916..b79c04b31 100644 --- a/moon/apps/web/components/AccountReview/PendingAccountReviewButton.tsx +++ b/moon/apps/web/components/AccountReview/PendingAccountReviewButton.tsx @@ -20,7 +20,7 @@ export function PendingAccountReviewButton() { tooltip={`${pendingCount} user${pendingCount === 1 ? '' : 's'} pending review`} className='text-tertiary hover:text-primary' /> - + {pendingCount > 99 ? '99+' : pendingCount} diff --git a/moon/apps/web/components/Activity/Activity.tsx b/moon/apps/web/components/Activity/Activity.tsx index c82ed068e..6397cffb6 100644 --- a/moon/apps/web/components/Activity/Activity.tsx +++ b/moon/apps/web/components/Activity/Activity.tsx @@ -50,7 +50,7 @@ export function Activity() { setActivityOpen(false) }} className={cn( - 'text-primary group relative -mx-1 flex flex-none cursor-pointer select-none scroll-m-2 scroll-mt-12 items-start gap-3 rounded-lg px-2.5 py-3 text-[15px] outline-none ease-in-out will-change-[background,_color] focus-within:border-none focus-within:outline-none focus-within:ring-0 focus:border-none focus:outline-none focus:ring-0', + 'text-primary group relative -mx-1 flex flex-none cursor-pointer scroll-m-2 scroll-mt-12 items-start gap-3 rounded-lg px-2.5 py-3 text-[15px] outline-hidden ease-in-out will-change-[background,_color] select-none focus-within:border-none focus-within:ring-0 focus-within:outline-hidden focus:border-none focus:ring-0 focus:outline-hidden', { 'bg-blue-500/10 dark:bg-blue-500/20': !n.activity_seen, 'hover:bg-blue-500/15 aria-selected:bg-blue-500/15 dark:hover:bg-blue-500/25 dark:aria-selected:bg-blue-500/25': diff --git a/moon/apps/web/components/AdminGroups/AddMembersDialog.tsx b/moon/apps/web/components/AdminGroups/AddMembersDialog.tsx index f86579be4..294dc4fff 100644 --- a/moon/apps/web/components/AdminGroups/AddMembersDialog.tsx +++ b/moon/apps/web/components/AdminGroups/AddMembersDialog.tsx @@ -75,10 +75,10 @@ export const AddMembersDialog = ({ groupId, onClose }: AddMembersDialogProps) => if (groupId === null) return null return ( -
+
{/* Fixed header */} -
+

Add Members to Group

{/* Debug info */} @@ -114,7 +114,7 @@ export const AddMembersDialog = ({ groupId, onClose }: AddMembersDialogProps) => type='text' value={memberSearchQuery} onChange={(e) => setMemberSearchQuery(e.target.value)} - className='border-primary bg-secondary text-primary w-full rounded-md border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500' + className='border-primary bg-secondary text-primary w-full rounded-md border px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:outline-hidden' placeholder='Search members by name or username...' disabled={!!membersError} /> @@ -180,7 +180,7 @@ export const AddMembersDialog = ({ groupId, onClose }: AddMembersDialogProps) => {member.user.display_name}

@@ -188,7 +188,7 @@ export const AddMembersDialog = ({ groupId, onClose }: AddMembersDialogProps) =>

@{member.user.username}

-
+
{member.user.display_name}

{member.user.display_name}

@{member.user.username}

-
+
{/* Fixed bottom buttons */} -
+
@@ -283,7 +283,7 @@ export function AdminGroupEditDialog({ open, onOpenChange, groupId, onSuccess }:
@@ -334,7 +334,7 @@ export function AdminGroupEditDialog({ open, onOpenChange, groupId, onSuccess }: > Basic Info {activeTab === 'basic' && ( -
+
)}
@@ -391,7 +391,7 @@ export function AdminGroupEditDialog({ open, onOpenChange, groupId, onSuccess }: onChange={(e) => setDescription(e.target.value)} placeholder='Enter group description' rows={3} - className='w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100' + className='w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-hidden dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100' />
@@ -409,27 +409,27 @@ export function AdminGroupEditDialog({ open, onOpenChange, groupId, onSuccess }:
) : ( <> -
+
- - - - - - @@ -559,7 +559,7 @@ export function AdminGroupEditDialog({ open, onOpenChange, groupId, onSuccess }:
+ Resource + Project + Read + Write + Admin + Actions