瀏覽代碼

Merge branch 'master' into support/migrate-to-y-websocket

Yuki Takei 2 周之前
父節點
當前提交
137006706e

+ 2 - 0
.claude/settings.json

@@ -11,6 +11,8 @@
       "Bash(pnpm run lint:*)",
       "Bash(pnpm run lint:*)",
       "Bash(pnpm vitest run *)",
       "Bash(pnpm vitest run *)",
       "Bash(pnpm biome check *)",
       "Bash(pnpm biome check *)",
+      "Bash(pnpm ls *)",
+      "Bash(pnpm why *)",
       "Bash(cat *)",
       "Bash(cat *)",
       "Bash(echo *)",
       "Bash(echo *)",
       "Bash(find *)",
       "Bash(find *)",

+ 1 - 1
apps/app/.claude/rules/package-dependencies.md

@@ -24,7 +24,7 @@ ls apps/app/.next/node_modules/ | grep <package-name>
 |---|---|
 |---|---|
 | `import foo from 'pkg'` at module level in SSR-executed code | `dependencies` |
 | `import foo from 'pkg'` at module level in SSR-executed code | `dependencies` |
 | `import type { Foo } from 'pkg'` only | `devDependencies` (type-erased at build) |
 | `import type { Foo } from 'pkg'` only | `devDependencies` (type-erased at build) |
-| `await import('pkg')` inside `useEffect` / event handler | Check `.next/node_modules/` — may still be externalised |
+| `await import('pkg')` inside `useEffect` / event handler | Check `.next/node_modules/` — may still be externalised (see `fix-broken-next-symlinks` skill) |
 | Used only in `*.spec.ts`, build scripts, or CI | `devDependencies` |
 | Used only in `*.spec.ts`, build scripts, or CI | `devDependencies` |
 
 
 ## Common Misconceptions
 ## Common Misconceptions

+ 86 - 0
apps/app/.claude/skills/learned/fix-broken-next-symlinks/SKILL.md

@@ -0,0 +1,86 @@
+---
+name: fix-broken-next-symlinks
+description: Fix broken symlinks in .next/node_modules/ — diagnose, decide allowlist vs dependencies, and verify
+---
+
+## Problem
+
+Turbopack externalizes packages into `.next/node_modules/` as symlinks, even for packages imported only via dynamic `import()` inside `useEffect`. After `assemble-prod.sh` runs `pnpm deploy --prod`, `devDependencies` are excluded, breaking those symlinks. `check-next-symlinks.sh` detects these and fails the build.
+
+## Diagnosis
+
+### Step 1 — Reproduce locally
+
+```bash
+turbo run build --filter @growi/app
+bash apps/app/bin/assemble-prod.sh
+bash apps/app/bin/check-next-symlinks.sh
+```
+
+If the check reports `BROKEN: apps/app/.next/node_modules/<package>-<hash>`, proceed to Step 2.
+
+### Step 2 — Determine the fix
+
+Search all import sites of the broken package:
+
+```bash
+grep -rn "from ['\"]<package-name>['\"]" apps/app/src/
+grep -rn "import(['\"]<package-name>['\"])" apps/app/src/
+```
+
+Apply the decision tree:
+
+```
+Is the package imported ONLY via:
+  - `import type { ... } from 'pkg'`  (erased at compile time)
+  - `await import('pkg')` inside useEffect / event handler  (client-side only, never SSR)
+
+  YES → Add to ALLOWED_BROKEN in check-next-symlinks.sh  (Step 3a)
+  NO  → Move from devDependencies to dependencies          (Step 3b)
+```
+
+### Step 3a — Add to allowlist
+
+Edit `apps/app/bin/check-next-symlinks.sh`:
+
+```bash
+ALLOWED_BROKEN=(
+  fslightbox-react
+  @emoji-mart/data
+  @emoji-mart/react
+  socket.io-client
+  <new-package>          # <-- add here
+)
+```
+
+Use the bare package name (e.g., `socket.io-client`), not the hashed symlink name (`socket.io-client-46e5ba4d4c848156`).
+
+### Step 3b — Move to dependencies
+
+In `apps/app/package.json`, move the package from `devDependencies` to `dependencies`, then run `pnpm install`.
+
+### Step 4 — Verify the fix
+
+Re-run the full sequence:
+
+```bash
+turbo run build --filter @growi/app
+bash apps/app/bin/assemble-prod.sh
+bash apps/app/bin/check-next-symlinks.sh
+```
+
+Expected output: `OK: All apps/app/.next/node_modules symlinks resolve correctly.`
+
+## Example
+
+`socket.io-client` is used in two files:
+- `src/states/socket-io/global-socket.ts` — `import type` + `await import()` inside `useEffect`
+- `src/features/admin/states/socket-io.ts` — `import type` + `import()` inside `useEffect`
+
+Both are client-only dynamic imports → added to `ALLOWED_BROKEN`, stays as `devDependencies`.
+
+## When to Apply
+
+- CI fails at "Check for broken symlinks in .next/node_modules" step
+- `check-next-symlinks.sh` reports `BROKEN: apps/app/.next/node_modules/<package>-<hash>`
+- After adding a new package or changing import patterns in apps/app

+ 8 - 1
apps/app/bin/check-next-symlinks.sh

@@ -11,6 +11,7 @@ ALLOWED_BROKEN=(
   fslightbox-react
   fslightbox-react
   @emoji-mart/data
   @emoji-mart/data
   @emoji-mart/react
   @emoji-mart/react
+  socket.io-client
 )
 )
 
 
 # Build a grep -v pattern from the allowlist
 # Build a grep -v pattern from the allowlist
@@ -29,7 +30,13 @@ done | grep -v "${grep_args[@]}" || true)
 if [ -n "$broken" ]; then
 if [ -n "$broken" ]; then
   echo "ERROR: Broken symlinks found in $NEXT_MODULES:"
   echo "ERROR: Broken symlinks found in $NEXT_MODULES:"
   echo "$broken"
   echo "$broken"
-  echo "Move these packages from devDependencies to dependencies in apps/app/package.json."
+  echo ""
+  echo "Each broken package must be either:"
+  echo "  1. Moved from devDependencies to dependencies in apps/app/package.json"
+  echo "  2. Added to ALLOWED_BROKEN in this script (if only used via useEffect + dynamic import)"
+  echo ""
+  echo "See: apps/app/.claude/skills/learned/fix-broken-next-symlinks/SKILL.md"
+  echo "See also: apps/app/.claude/rules/package-dependencies.md"
   exit 1
   exit 1
 fi
 fi
 
 

+ 23 - 0
packages/editor/vite.config.ts

@@ -51,6 +51,29 @@ export default defineConfig({
       entryRoot: 'src',
       entryRoot: 'src',
       exclude: [...excludeFiles],
       exclude: [...excludeFiles],
       copyDtsFiles: true,
       copyDtsFiles: true,
+      // Fix TS2345/TS2719 "Two different types with this name exist" errors
+      // during declaration file generation.
+      //
+      // vite-plugin-dts internally creates its own TypeScript program, which
+      // resolves @codemirror/state and @codemirror/view through different pnpm
+      // symlink chains depending on whether the import originates from
+      // @growi/editor source or from @uiw/react-codemirror's re-exports.
+      // Although both chains point to the same physical package, TypeScript
+      // treats them as distinct types because private fields (e.g.
+      // SelectionRange#flags) create nominal type identity per declaration.
+      //
+      // Pinning paths here forces vite-plugin-dts to resolve these packages
+      // to a single location regardless of the import origin.
+      compilerOptions: {
+        paths: {
+          '@codemirror/state': [
+            path.resolve(__dirname, 'node_modules/@codemirror/state'),
+          ],
+          '@codemirror/view': [
+            path.resolve(__dirname, 'node_modules/@codemirror/view'),
+          ],
+        },
+      },
     }),
     }),
     {
     {
       ...nodeExternals({
       ...nodeExternals({