mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Fix memory leak
This commit is contained in:
parent
d568ad5149
commit
560ee4c16e
@ -11,7 +11,7 @@
|
||||
"with-env:dev": "dotenv -c development --",
|
||||
"with-env:prod": "dotenv -c production --",
|
||||
"with-env:test": "dotenv -c test --",
|
||||
"dev": "BACKEND_PORT=${STACK_DEV_FALLBACK_BACKEND:+${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}10} && BACKEND_PORT=${BACKEND_PORT:-${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02} && concurrently -n \"dev,codegen,prisma-studio,email-queue,cron-jobs,bulldozer-studio\" -k \"next dev --port $BACKEND_PORT ${STACK_BACKEND_DEV_EXTRA_ARGS:-}\" \"pnpm run codegen:watch\" \"pnpm run prisma-studio\" \"pnpm run run-email-queue\" \"pnpm run run-cron-jobs\" \"pnpm run run-bulldozer-studio\"",
|
||||
"dev": "BACKEND_PORT=${STACK_DEV_FALLBACK_BACKEND:+${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}10} && BACKEND_PORT=${BACKEND_PORT:-${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02} && concurrently -n \"dev,codegen,prisma-studio,email-queue,cron-jobs,bulldozer-studio\" -k \"STACK_DISABLE_REACT_ASYNC_DEBUG_INFO=${STACK_DISABLE_REACT_ASYNC_DEBUG_INFO:-true} next dev --port $BACKEND_PORT ${STACK_BACKEND_DEV_EXTRA_ARGS:-}\" \"pnpm run codegen:watch\" \"pnpm run prisma-studio\" \"pnpm run run-email-queue\" \"pnpm run run-cron-jobs\" \"pnpm run run-bulldozer-studio\"",
|
||||
"dev:inspect": "STACK_BACKEND_DEV_EXTRA_ARGS=\"--inspect\" pnpm run dev",
|
||||
"dev:profile": "STACK_BACKEND_DEV_EXTRA_ARGS=\"--experimental-cpu-prof\" pnpm run dev",
|
||||
"build": "pnpm run codegen && next build",
|
||||
|
||||
@ -377,3 +377,9 @@ A: Any trigger callbacks written as `(changesTable) => ...` can fail against the
|
||||
|
||||
Q: How should `x-stack-override-error-status` behave in backend smart responses?
|
||||
A: In `apps/backend/src/route-handlers/smart-response.tsx`, only override `4xx` responses to `200` with `x-stack-actual-status`. Do not override `5xx`, so infrastructure/runtime failures still surface as real server errors.
|
||||
|
||||
Q: Why can `email-queue-step` heap growth still point at `app-page-turbo.runtime.dev.js` after disabling React async debug info in `react-server-dom-*` bundles?
|
||||
A: Next.js dev app-page runtimes (`app-page*.runtime.dev.js`) include their own inlined async debug hook (`async_hooks.createHook`) with `pendingOperations` nodes plus stack-frame arrays from `collectStackTracePrivate`. Patching only `react-server-dom-*` is not enough. Gate the app-page runtime hook with `STACK_DISABLE_REACT_ASYNC_DEBUG_INFO` too; once gated, inspector allocation samples stop showing `init`/`collectStackTracePrivate` in `app-page-turbo.runtime.dev.js` and per-burst retained deltas drop from multi-MB to near-baseline noise.
|
||||
|
||||
Q: How can we replace the huge `next@16.1.7` patch file with a resilient install-time rewrite?
|
||||
A: Use a strict root `postinstall` script that rewrites only Next `>=16` app-page dev runtime bundles (`app-page*.runtime.dev.js`) from `doNotLimit=new WeakSet;async_hooks.createHook(` to the guarded `STACK_DISABLE_REACT_ASYNC_DEBUG_INFO` form. Guardrails should fail loud on marker mismatches, mixed guarded/unguarded states, replacement counts not equal to one, or missing runtime fingerprints; the script should also be idempotent (`patched=0, alreadyPatched>0` on second run).
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
"pre-preinstall": "pnpx only-allow@1.2.2 pnpm && node -e \"if(process.env.STACK_SKIP_TEMPLATE_GENERATION !== 'true') require('child_process').execSync('pnpm exec tsx ./scripts/generate-sdks.ts', {stdio: 'inherit'})\"",
|
||||
"pre": "pnpm pre-preinstall",
|
||||
"preinstall": "pnpm pre-preinstall",
|
||||
"postinstall": "node ./scripts/postinstall-patch-next-async-debug-info.mjs",
|
||||
"typecheck": "pnpm pre && turbo typecheck --",
|
||||
"build:dev": "pnpm pre && NODE_ENV=development pnpm run build",
|
||||
"build": "pnpm pre && turbo build",
|
||||
|
||||
179
pnpm-lock.yaml
179
pnpm-lock.yaml
@ -1020,7 +1020,7 @@ importers:
|
||||
devDependencies:
|
||||
mint:
|
||||
specifier: ^4.2.487
|
||||
version: 4.2.487(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
|
||||
version: 4.2.487(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.9.2)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
|
||||
|
||||
examples/cjs-test:
|
||||
dependencies:
|
||||
@ -22672,6 +22672,16 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/checkbox@4.3.2(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/ansi': 1.0.2
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/figures': 1.0.15
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
yoctocolors-cjs: 2.1.3
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/confirm@5.1.21(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@20.17.6)
|
||||
@ -22679,6 +22689,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/confirm@5.1.21(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/core@10.3.2(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
'@inquirer/ansi': 1.0.2
|
||||
@ -22692,6 +22709,19 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/core@10.3.2(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/ansi': 1.0.2
|
||||
'@inquirer/figures': 1.0.15
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
cli-width: 4.1.0
|
||||
mute-stream: 2.0.0
|
||||
signal-exit: 4.1.0
|
||||
wrap-ansi: 6.2.0
|
||||
yoctocolors-cjs: 2.1.3
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/editor@4.2.23(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@20.17.6)
|
||||
@ -22700,6 +22730,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/editor@4.2.23(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/external-editor': 1.0.3(@types/node@24.9.2)
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/expand@4.0.23(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@20.17.6)
|
||||
@ -22708,6 +22746,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/expand@4.0.23(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
yoctocolors-cjs: 2.1.3
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/external-editor@1.0.3(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
chardet: 2.1.1
|
||||
@ -22715,6 +22761,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/external-editor@1.0.3(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
chardet: 2.1.1
|
||||
iconv-lite: 0.7.0
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/figures@1.0.15': {}
|
||||
|
||||
'@inquirer/figures@1.0.3': {}
|
||||
@ -22726,6 +22779,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/input@4.3.1(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/number@3.0.23(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@20.17.6)
|
||||
@ -22733,6 +22793,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/number@3.0.23(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/password@4.0.23(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
'@inquirer/ansi': 1.0.2
|
||||
@ -22741,6 +22808,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/password@4.0.23(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/ansi': 1.0.2
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/prompts@7.10.1(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
'@inquirer/checkbox': 4.3.2(@types/node@20.17.6)
|
||||
@ -22756,20 +22831,35 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/prompts@7.9.0(@types/node@20.17.6)':
|
||||
'@inquirer/prompts@7.10.1(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/checkbox': 4.3.2(@types/node@20.17.6)
|
||||
'@inquirer/confirm': 5.1.21(@types/node@20.17.6)
|
||||
'@inquirer/editor': 4.2.23(@types/node@20.17.6)
|
||||
'@inquirer/expand': 4.0.23(@types/node@20.17.6)
|
||||
'@inquirer/input': 4.3.1(@types/node@20.17.6)
|
||||
'@inquirer/number': 3.0.23(@types/node@20.17.6)
|
||||
'@inquirer/password': 4.0.23(@types/node@20.17.6)
|
||||
'@inquirer/rawlist': 4.1.11(@types/node@20.17.6)
|
||||
'@inquirer/search': 3.2.2(@types/node@20.17.6)
|
||||
'@inquirer/select': 4.4.2(@types/node@20.17.6)
|
||||
'@inquirer/checkbox': 4.3.2(@types/node@24.9.2)
|
||||
'@inquirer/confirm': 5.1.21(@types/node@24.9.2)
|
||||
'@inquirer/editor': 4.2.23(@types/node@24.9.2)
|
||||
'@inquirer/expand': 4.0.23(@types/node@24.9.2)
|
||||
'@inquirer/input': 4.3.1(@types/node@24.9.2)
|
||||
'@inquirer/number': 3.0.23(@types/node@24.9.2)
|
||||
'@inquirer/password': 4.0.23(@types/node@24.9.2)
|
||||
'@inquirer/rawlist': 4.1.11(@types/node@24.9.2)
|
||||
'@inquirer/search': 3.2.2(@types/node@24.9.2)
|
||||
'@inquirer/select': 4.4.2(@types/node@24.9.2)
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/prompts@7.9.0(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/checkbox': 4.3.2(@types/node@24.9.2)
|
||||
'@inquirer/confirm': 5.1.21(@types/node@24.9.2)
|
||||
'@inquirer/editor': 4.2.23(@types/node@24.9.2)
|
||||
'@inquirer/expand': 4.0.23(@types/node@24.9.2)
|
||||
'@inquirer/input': 4.3.1(@types/node@24.9.2)
|
||||
'@inquirer/number': 3.0.23(@types/node@24.9.2)
|
||||
'@inquirer/password': 4.0.23(@types/node@24.9.2)
|
||||
'@inquirer/rawlist': 4.1.11(@types/node@24.9.2)
|
||||
'@inquirer/search': 3.2.2(@types/node@24.9.2)
|
||||
'@inquirer/select': 4.4.2(@types/node@24.9.2)
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/rawlist@4.1.11(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
@ -22779,6 +22869,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/rawlist@4.1.11(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
yoctocolors-cjs: 2.1.3
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/search@3.2.2(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@20.17.6)
|
||||
@ -22788,6 +22886,15 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/search@3.2.2(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/figures': 1.0.15
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
yoctocolors-cjs: 2.1.3
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/select@4.4.2(@types/node@20.17.6)':
|
||||
dependencies:
|
||||
'@inquirer/ansi': 1.0.2
|
||||
@ -22798,10 +22905,24 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/select@4.4.2(@types/node@24.9.2)':
|
||||
dependencies:
|
||||
'@inquirer/ansi': 1.0.2
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/figures': 1.0.15
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
yoctocolors-cjs: 2.1.3
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@inquirer/type@3.0.10(@types/node@20.17.6)':
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.6
|
||||
|
||||
'@inquirer/type@3.0.10(@types/node@24.9.2)':
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.2
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
@ -22939,9 +23060,9 @@ snapshots:
|
||||
dependencies:
|
||||
langium: 3.3.1
|
||||
|
||||
'@mintlify/cli@4.0.1090(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
|
||||
'@mintlify/cli@4.0.1090(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.9.2)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
|
||||
dependencies:
|
||||
'@inquirer/prompts': 7.9.0(@types/node@20.17.6)
|
||||
'@inquirer/prompts': 7.9.0(@types/node@24.9.2)
|
||||
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
|
||||
'@mintlify/link-rot': 3.0.1010(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
|
||||
'@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
|
||||
@ -22954,7 +23075,7 @@ snapshots:
|
||||
front-matter: 4.0.2
|
||||
fs-extra: 11.2.0
|
||||
ink: 6.3.0(@types/react@18.3.12)(react@19.2.3)
|
||||
inquirer: 12.3.0(@types/node@20.17.6)
|
||||
inquirer: 12.3.0(@types/node@24.9.2)
|
||||
js-yaml: 4.1.0
|
||||
mdast-util-mdx-jsx: 3.2.0
|
||||
open: 8.4.2
|
||||
@ -30122,7 +30243,6 @@ snapshots:
|
||||
'@types/node@24.9.2':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
optional: true
|
||||
|
||||
'@types/nodemailer@6.4.15':
|
||||
dependencies:
|
||||
@ -33200,7 +33320,7 @@ snapshots:
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
||||
eslint-plugin-react: 7.37.2(eslint@8.57.1)
|
||||
eslint-plugin-react-hooks: 5.1.0(eslint@8.57.1)
|
||||
@ -33242,7 +33362,7 @@ snapshots:
|
||||
debug: 4.4.3
|
||||
enhanced-resolve: 5.17.1
|
||||
eslint: 8.57.1
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
|
||||
fast-glob: 3.3.3
|
||||
get-tsconfig: 4.8.1
|
||||
is-bun-module: 1.2.1
|
||||
@ -33285,7 +33405,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
@ -33363,7 +33483,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.15.1
|
||||
is-glob: 4.0.3
|
||||
@ -35269,12 +35389,12 @@ snapshots:
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
inquirer@12.3.0(@types/node@20.17.6):
|
||||
inquirer@12.3.0(@types/node@24.9.2):
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@20.17.6)
|
||||
'@inquirer/prompts': 7.10.1(@types/node@20.17.6)
|
||||
'@inquirer/type': 3.0.10(@types/node@20.17.6)
|
||||
'@types/node': 20.17.6
|
||||
'@inquirer/core': 10.3.2(@types/node@24.9.2)
|
||||
'@inquirer/prompts': 7.10.1(@types/node@24.9.2)
|
||||
'@inquirer/type': 3.0.10(@types/node@24.9.2)
|
||||
'@types/node': 24.9.2
|
||||
ansi-escapes: 4.3.2
|
||||
mute-stream: 2.0.0
|
||||
run-async: 3.0.0
|
||||
@ -36757,9 +36877,9 @@ snapshots:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
mint@4.2.487(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0):
|
||||
mint@4.2.487(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.9.2)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0):
|
||||
dependencies:
|
||||
'@mintlify/cli': 4.0.1090(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
|
||||
'@mintlify/cli': 4.0.1090(@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.9.2)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
|
||||
transitivePeerDependencies:
|
||||
- '@radix-ui/react-popover'
|
||||
- '@types/node'
|
||||
@ -40923,8 +41043,7 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici-types@7.16.0:
|
||||
optional: true
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
undici@6.19.8: {}
|
||||
|
||||
|
||||
199
scripts/postinstall-patch-next-async-debug-info.mjs
Normal file
199
scripts/postinstall-patch-next-async-debug-info.mjs
Normal file
@ -0,0 +1,199 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/**
|
||||
* Why this script exists:
|
||||
* - Next.js dev app-page runtimes (app-page*.runtime.dev.js) include an async debug hook
|
||||
* (`async_hooks.createHook`) that captures async stack-trace metadata in hot paths.
|
||||
* - In our backend dev workload (notably repeated email-queue-step requests), this hook
|
||||
* causes measurable heap growth/retention in dev mode.
|
||||
* - We disable that hook when STACK_DISABLE_REACT_ASYNC_DEBUG_INFO=true.
|
||||
*
|
||||
* Why we do this in postinstall:
|
||||
* - The equivalent pnpm patch touched minified one-line bundles and produced a multi-MB
|
||||
* patch file that is hard to review and noisy in diffs.
|
||||
* - A strict install-time rewrite keeps the repo clean while still being deterministic:
|
||||
* if assumptions no longer hold, we fail loudly instead of silently continuing.
|
||||
*/
|
||||
const LOG_PREFIX = "[patch-next-async-debug-info]";
|
||||
const MIN_TARGET_NEXT_MAJOR = 16;
|
||||
|
||||
// We only patch app-page dev runtimes where this hook is present and relevant.
|
||||
const APP_PAGE_RUNTIME_FILE_REGEX = /^app-page(?:-turbo)?(?:-experimental)?\.runtime\.dev\.js$/;
|
||||
const HOOK_NEEDLE = "doNotLimit=new WeakSet;async_hooks.createHook(";
|
||||
const GUARDED_HOOK =
|
||||
"doNotLimit=new WeakSet,shouldEnableAsyncDebugInfo=\"true\"!==process.env.STACK_DISABLE_REACT_ASYNC_DEBUG_INFO;shouldEnableAsyncDebugInfo&&async_hooks.createHook(";
|
||||
// Extra fingerprints reduce the chance of accidentally patching unrelated files.
|
||||
const RUNTIME_FINGERPRINTS = [
|
||||
"collectStackTracePrivate(",
|
||||
"pendingOperations",
|
||||
];
|
||||
|
||||
function fail(message) {
|
||||
throw new Error(`${LOG_PREFIX} ${message}`);
|
||||
}
|
||||
|
||||
function hasAllRuntimeFingerprints(content) {
|
||||
return RUNTIME_FINGERPRINTS.every((fingerprint) => content.includes(fingerprint));
|
||||
}
|
||||
|
||||
function patchRuntimeFile(filePath) {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const hasNeedle = content.includes(HOOK_NEEDLE);
|
||||
const hasGuard = content.includes(GUARDED_HOOK);
|
||||
|
||||
if (!hasAllRuntimeFingerprints(content)) {
|
||||
return { status: "ignored" };
|
||||
}
|
||||
|
||||
if (hasNeedle && hasGuard) {
|
||||
fail(`File ${filePath} contains both guarded and unguarded markers; refusing to continue.`);
|
||||
}
|
||||
|
||||
if (!hasNeedle && !hasGuard) {
|
||||
fail(`File ${filePath} no longer contains the expected async debug marker. Next.js internals likely changed.`);
|
||||
}
|
||||
|
||||
// Already guarded => idempotent no-op.
|
||||
if (hasGuard) {
|
||||
return { status: "already" };
|
||||
}
|
||||
|
||||
const needleCount = content.split(HOOK_NEEDLE).length - 1;
|
||||
if (needleCount !== 1) {
|
||||
fail(`File ${filePath} matched ${needleCount} unguarded markers (expected exactly 1).`);
|
||||
}
|
||||
|
||||
const patchedContent = content.replace(HOOK_NEEDLE, GUARDED_HOOK);
|
||||
|
||||
if (patchedContent === content) {
|
||||
fail(`File ${filePath} did not change after replacement.`);
|
||||
}
|
||||
|
||||
if (patchedContent.includes(HOOK_NEEDLE)) {
|
||||
fail(`File ${filePath} still contains unguarded marker after patch.`);
|
||||
}
|
||||
|
||||
if (!patchedContent.includes(GUARDED_HOOK)) {
|
||||
fail(`File ${filePath} is missing guarded marker after patch.`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, patchedContent);
|
||||
|
||||
return { status: "patched" };
|
||||
}
|
||||
|
||||
function listInstalledNextServerDirs(repoRoot) {
|
||||
const pnpmVirtualStoreDir = path.join(repoRoot, "node_modules", ".pnpm");
|
||||
if (!fs.existsSync(pnpmVirtualStoreDir)) {
|
||||
fail(`Missing ${pnpmVirtualStoreDir}. Run pnpm install before applying this patch.`);
|
||||
}
|
||||
|
||||
const dirEntries = fs.readdirSync(pnpmVirtualStoreDir, { withFileTypes: true });
|
||||
const nextServerDirs = dirEntries
|
||||
.filter((entry) => entry.isDirectory() && entry.name.startsWith("next@"))
|
||||
.map((entry) => {
|
||||
const versionMatch = entry.name.match(/^next@(\d+)\./);
|
||||
if (!versionMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const majorVersion = Number(versionMatch[1]);
|
||||
// This guard targets current Next 16 dev runtimes only; older installed versions
|
||||
// (e.g. transitive Next 14) may not contain the same runtime structure.
|
||||
if (majorVersion < MIN_TARGET_NEXT_MAJOR) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextServerDir = path.join(
|
||||
pnpmVirtualStoreDir,
|
||||
entry.name,
|
||||
"node_modules",
|
||||
"next",
|
||||
"dist",
|
||||
"compiled",
|
||||
"next-server",
|
||||
);
|
||||
|
||||
return fs.existsSync(nextServerDir) ? nextServerDir : null;
|
||||
})
|
||||
.filter((nextServerDir) => nextServerDir !== null);
|
||||
|
||||
if (nextServerDirs.length === 0) {
|
||||
fail(`No installed Next.js runtimes with major >= ${MIN_TARGET_NEXT_MAJOR} found in node_modules/.pnpm.`);
|
||||
}
|
||||
|
||||
return nextServerDirs;
|
||||
}
|
||||
|
||||
function patchAllNextRuntimeDirs(repoRoot) {
|
||||
const nextServerDirs = listInstalledNextServerDirs(repoRoot);
|
||||
|
||||
const summary = {
|
||||
nextServerDirs: nextServerDirs.length,
|
||||
candidateFiles: 0,
|
||||
fingerprintedFiles: 0,
|
||||
patchedFiles: 0,
|
||||
alreadyPatchedFiles: 0,
|
||||
};
|
||||
|
||||
for (const nextServerDir of nextServerDirs) {
|
||||
const runtimeFiles = fs.readdirSync(nextServerDir)
|
||||
.filter((fileName) => APP_PAGE_RUNTIME_FILE_REGEX.test(fileName))
|
||||
.map((fileName) => path.join(nextServerDir, fileName));
|
||||
|
||||
if (runtimeFiles.length === 0) {
|
||||
fail(`No app-page*.runtime.dev.js files found in ${nextServerDir}.`);
|
||||
}
|
||||
|
||||
summary.candidateFiles += runtimeFiles.length;
|
||||
|
||||
let touchedFingerprintFileInDir = 0;
|
||||
for (const runtimeFile of runtimeFiles) {
|
||||
const result = patchRuntimeFile(runtimeFile);
|
||||
if (result.status === "ignored") {
|
||||
continue;
|
||||
}
|
||||
|
||||
touchedFingerprintFileInDir += 1;
|
||||
summary.fingerprintedFiles += 1;
|
||||
|
||||
if (result.status === "patched") {
|
||||
summary.patchedFiles += 1;
|
||||
} else if (result.status === "already") {
|
||||
summary.alreadyPatchedFiles += 1;
|
||||
} else {
|
||||
fail(`Unexpected patch status "${result.status}" for ${runtimeFile}.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (touchedFingerprintFileInDir === 0) {
|
||||
fail(`Found app-page runtimes in ${nextServerDir}, but none matched expected async debug fingerprints.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (summary.fingerprintedFiles === 0) {
|
||||
fail("No runtime files matched expected async debug fingerprints.");
|
||||
}
|
||||
|
||||
if (summary.patchedFiles === 0 && summary.alreadyPatchedFiles === 0) {
|
||||
fail("Patch script completed without touching any files.");
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, "..");
|
||||
const summary = patchAllNextRuntimeDirs(repoRoot);
|
||||
|
||||
// Emit a compact machine-readable summary for local debugging and CI logs.
|
||||
console.log(
|
||||
`${LOG_PREFIX} patched=${summary.patchedFiles} alreadyPatched=${summary.alreadyPatchedFiles} ` +
|
||||
`fingerprinted=${summary.fingerprintedFiles} candidates=${summary.candidateFiles} nextDirs=${summary.nextServerDirs}`,
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
Loading…
Reference in New Issue
Block a user