From 705a371433910f4a4bb0e00612493c6d44648781 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 12:28:29 +1000
Subject: [PATCH 1/5] chore(deps): bump the npm_and_yarn group across 9
directories with 1 update (#7324)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps the npm_and_yarn group with 1 update in the
/examples/next/kitchen-sink directory:
[next](https://github.com/vercel/next.js).
Bumps the npm_and_yarn group with 1 update in the
/examples/next/tina-self-hosted-demo directory:
[next](https://github.com/vercel/next.js).
Bumps the npm_and_yarn group with 1 update in the
/packages/@tinacms/auth directory:
[next](https://github.com/vercel/next.js).
Bumps the npm_and_yarn group with 1 update in the
/packages/next-tinacms-azure directory:
[next](https://github.com/vercel/next.js).
Bumps the npm_and_yarn group with 1 update in the
/packages/next-tinacms-cloudinary directory:
[next](https://github.com/vercel/next.js).
Bumps the npm_and_yarn group with 1 update in the
/packages/next-tinacms-dos directory:
[next](https://github.com/vercel/next.js).
Bumps the npm_and_yarn group with 1 update in the
/packages/next-tinacms-s3 directory:
[next](https://github.com/vercel/next.js).
Bumps the npm_and_yarn group with 1 update in the /packages/tinacms
directory: [next](https://github.com/vercel/next.js).
Bumps the npm_and_yarn group with 1 update in the
/playwright/tina-playwright directory:
[next](https://github.com/vercel/next.js).
Updates `next` from 15.5.12 to 15.5.21
Release notes
When set to true, renderable entries (such as Markdown)
are not rendered during content sync. Instead, rendering is deferred
until the entry is actually rendered in a page, using the same on-demand
path that .mdx files already use.
This reduces memory usage during astro build for large
collections whose rendered output is much larger than the source — for
example, Markdown that uses heavy rehype plugins like
rehype-katex. Such builds could previously run out of
memory while storing the eagerly-rendered HTML for every entry.
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
By default deferRender is false, preserving
the existing behavior of rendering entries eagerly during sync so their
rendered HTML can be cached across builds.
#1729630698a2
Thanks @ematipico! - Adds
a new experimental collectionStorage option for controlling
how the content layer persists its data store
By default, Astro serializes the entire content layer data store to a
single file (.astro/data-store.json). For very large
content collections, this file can grow large enough to hit platform
file-size limits.
Set experimental.collectionStorage: 'chunked' to instead
split the data store across many smaller, content-addressed files inside
a .astro/data-store/ directory, described by a
manifest:
// astro.config.mjs
import { defineConfig } from 'astro/config';
Because each part file is named by a hash of its contents, unchanged
parts keep the same name across builds and are not rewritten, and
identical parts are deduplicated. The default value is
'single-file', which preserves the current behavior.
#1721444c4989
Thanks @ematipico! - Adds
support for the more specific CSP directives
script-src-elem, script-src-attr,
style-src-elem, and style-src-attr through a
new kind option.
Previously, CSP
was only scoped to generic
script-src/style-src directives. Now each
source or hash can be scoped to a narrower directive — for example, to
allow inline style attributes (such as those from
define:vars or Shiki) without loosening the policy for your
<style> and <link> elements.
When set to true, renderable entries (such as Markdown)
are not rendered during content sync. Instead, rendering is deferred
until the entry is actually rendered in a page, using the same on-demand
path that .mdx files already use.
This reduces memory usage during astro build for large
collections whose rendered output is much larger than the source — for
example, Markdown that uses heavy rehype plugins like
rehype-katex. Such builds could previously run out of
memory while storing the eagerly-rendered HTML for every entry.
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
By default deferRender is false, preserving
the existing behavior of rendering entries eagerly during sync so their
rendered HTML can be cached across builds.
#1729630698a2
Thanks @ematipico! - Adds
a new experimental collectionStorage option for controlling
how the content layer persists its data store
By default, Astro serializes the entire content layer data store to a
single file (.astro/data-store.json). For very large
content collections, this file can grow large enough to hit platform
file-size limits.
Set experimental.collectionStorage: 'chunked' to instead
split the data store across many smaller, content-addressed files inside
a .astro/data-store/ directory, described by a
manifest:
// astro.config.mjs
import { defineConfig } from 'astro/config';
Because each part file is named by a hash of its contents, unchanged
parts keep the same name across builds and are not rewritten, and
identical parts are deduplicated. The default value is
'single-file', which preserves the current behavior.
#1721444c4989
Thanks @ematipico! - Adds
support for the more specific CSP directives
script-src-elem, script-src-attr,
style-src-elem, and style-src-attr through a
new kind option.
Previously, CSP
was only scoped to generic
script-src/style-src directives. Now each
source or hash can be scoped to a narrower directive — for example, to
allow inline style attributes (such as those from
define:vars or Shiki) without loosening the policy for your
<style> and <link> elements.
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eli Kent [SSW] <69125238+kulesy@users.noreply.github.com>
Co-authored-by: kulesy
Co-authored-by: Claude Opus 5
---
examples/astro/kitchen-sink/package.json | 2 +-
examples/astro/visual-editing/package.json | 2 +-
packages/@tinacms/astro/package.json | 2 +-
pnpm-lock.yaml | 305 +++++++++++----------
4 files changed, 170 insertions(+), 141 deletions(-)
diff --git a/examples/astro/kitchen-sink/package.json b/examples/astro/kitchen-sink/package.json
index ba24b21d3e..1f404d8731 100644
--- a/examples/astro/kitchen-sink/package.json
+++ b/examples/astro/kitchen-sink/package.json
@@ -22,7 +22,7 @@
"@tailwindcss/typography": "catalog:",
"@tailwindcss/vite": "^4.2.1",
"@tinacms/datalayer": "workspace:*",
- "astro": "^7.0.6",
+ "astro": "^7.1.1",
"clsx": "^2.1.1",
"graphql": "^15.10.1",
"react": "^18.3.1",
diff --git a/examples/astro/visual-editing/package.json b/examples/astro/visual-editing/package.json
index f5fdcc104d..ed0c12e495 100644
--- a/examples/astro/visual-editing/package.json
+++ b/examples/astro/visual-editing/package.json
@@ -24,7 +24,7 @@
"@tinacms/astro": "workspace:*",
"@tinacms/datalayer": "workspace:*",
"@tinacms/mdx": "workspace:*",
- "astro": "^7.0.6",
+ "astro": "^7.1.1",
"clsx": "^2.1.1",
"graphql": "^15.10.1",
"react-icons": "^5.6.0",
diff --git a/packages/@tinacms/astro/package.json b/packages/@tinacms/astro/package.json
index b8128e02c6..bc7782b4b9 100644
--- a/packages/@tinacms/astro/package.json
+++ b/packages/@tinacms/astro/package.json
@@ -136,7 +136,7 @@
"devDependencies": {
"@tinacms/scripts": "workspace:*",
"@types/node": "^22.13.1",
- "astro": "^7.0.6",
+ "astro": "^7.1.1",
"typescript": "^5.7.3",
"vite": "^8.0.0",
"vitest": "^3.0.0"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 84163226a0..6dfdf9f769 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -716,7 +716,7 @@ importers:
dependencies:
'@astrojs/mdx':
specifier: ^7.0.2
- version: 7.0.2(@astrojs/markdown-satteri@0.3.3)(astro@7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))
+ version: 7.0.2(@astrojs/markdown-satteri@0.3.6)(astro@7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))
'@astrojs/react':
specifier: ^6.0.1
version: 6.0.1(@types/node@25.1.0)(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(esbuild@0.28.1)(jiti@2.6.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
@@ -730,8 +730,8 @@ importers:
specifier: workspace:*
version: link:../../../packages/@tinacms/datalayer
astro:
- specifier: ^7.0.6
- version: 7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
+ specifier: ^7.1.1
+ version: 7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
clsx:
specifier: ^2.1.1
version: 2.1.1
@@ -795,10 +795,10 @@ importers:
dependencies:
'@astrojs/mdx':
specifier: ^7.0.2
- version: 7.0.2(@astrojs/markdown-satteri@0.3.3)(astro@7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))
+ version: 7.0.2(@astrojs/markdown-satteri@0.3.6)(astro@7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))
'@astrojs/node':
specifier: ^11.0.2
- version: 11.0.2(astro@7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))
+ version: 11.0.2(astro@7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))
'@tailwindcss/typography':
specifier: 'catalog:'
version: 0.5.20(tailwindcss@4.2.1)
@@ -815,8 +815,8 @@ importers:
specifier: workspace:*
version: link:../../../packages/@tinacms/mdx
astro:
- specifier: ^7.0.6
- version: 7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
+ specifier: ^7.1.1
+ version: 7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
clsx:
specifier: ^2.1.1
version: 2.1.1
@@ -1282,8 +1282,8 @@ importers:
specifier: ^22.13.1
version: 22.19.17
astro:
- specifier: ^7.0.6
- version: 7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@22.19.17)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
+ specifier: ^7.1.1
+ version: 7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@22.19.17)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
typescript:
specifier: ^5.7.3
version: 5.9.3
@@ -3053,69 +3053,69 @@ packages:
peerDependencies:
typescript: ^5.0.0 || ^6.0.0
- '@astrojs/compiler-binding-darwin-arm64@0.3.0':
- resolution: {integrity: sha512-3n0uu+uJpnCq8b4JFi3uGDsIisAvHctxSmH+cIO9Gbei1H1Y1QXaYboXyiWJugUmprr3OEYP7+LdodzpVFzLMQ==}
+ '@astrojs/compiler-binding-darwin-arm64@0.3.2':
+ resolution: {integrity: sha512-MM8tn8CSimcfytaOla4b6acN8mKWiL/rlAA1fpT3/Wl7dNGSE4y8FjTN/zJVNnb63CsLWG5zZwCt01TXtDKh9g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
- '@astrojs/compiler-binding-darwin-x64@0.3.0':
- resolution: {integrity: sha512-scxNGKjOBydMo1QR4LtK0FMgh7ubQomJDv953nz2msQFkPKke/0FpPv/cQM0T/kuZdReZQFU8Oz3iOrP/6WHEg==}
+ '@astrojs/compiler-binding-darwin-x64@0.3.2':
+ resolution: {integrity: sha512-2lXOlzf8xb7jLomRsf/aswh61/NnGusynB2OwFkK6k4pmOtpfXMYnG0PLfXrEvxXYj69NdCnmUYXtHDd+JOOag==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
- '@astrojs/compiler-binding-linux-arm64-gnu@0.3.0':
- resolution: {integrity: sha512-NZrWLolVUANmrnl0zrFK/Sx5Sock1gEUT49ALfMTTCA5Ya2ec/BoJXMIg4KgE+wZcrdXJ8e+WyEhM7YLk/FJkA==}
+ '@astrojs/compiler-binding-linux-arm64-gnu@0.3.2':
+ resolution: {integrity: sha512-BmU3kWj7qnLrd4vzm49zFEPJ5oFnn1tCT4Vt9hZbqdU5Cmb8GZl7fn6VFsnNfe7B18a2gIFtVzbLINtYl5kBjQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
- '@astrojs/compiler-binding-linux-arm64-musl@0.3.0':
- resolution: {integrity: sha512-PjwRmKgMFDsFhg82g0poXlIY8Qn3fMA3hXjaR0coJWJzTJsRH9ATU0j2ocigjtU1h3vL/yR7yLUxGj/lTCq73g==}
+ '@astrojs/compiler-binding-linux-arm64-musl@0.3.2':
+ resolution: {integrity: sha512-f0heT9ZZEseSu5bHCeb80eL2DH07ArE6U9xi1WT/PEusNjzPmEr3GJsjG1tRLo5VYUUYX7h3ScaqGmGrMOVGmw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
- '@astrojs/compiler-binding-linux-x64-gnu@0.3.0':
- resolution: {integrity: sha512-Dr69VJYlnSfyL8gzELW6S4mE41P7TDPn1IKjwMnjdZ7+dxgJI50oMLFSk1LVe26bHmWB3ktuh8fDVK1THI9e9A==}
+ '@astrojs/compiler-binding-linux-x64-gnu@0.3.2':
+ resolution: {integrity: sha512-M8fOUt0itRpqiGyoEA/ij184s8O+hqbCz3+YozRusOOM3osgGljpDThhbKAJjqh82wOo6FioQ4w8PBvU1XMD5Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
- '@astrojs/compiler-binding-linux-x64-musl@0.3.0':
- resolution: {integrity: sha512-AEt+bRw8PfImCcyRH1lpXVB8CdmQ1K/wPo5u99iec4/U/XdNvQZ715YVuNzIJpbJXelgQeZ5H2+Ea7XwRyWY5g==}
+ '@astrojs/compiler-binding-linux-x64-musl@0.3.2':
+ resolution: {integrity: sha512-/Kebk8sO6HnLeSd691JkaAPfN7CqR9/KEXmWvyNPkaKNGmj8rTZ/lf2uXtnPu92Lan84UrKdIKVPy1fSo2encQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
- '@astrojs/compiler-binding-wasm32-wasi@0.3.0':
- resolution: {integrity: sha512-U80tA1j8V6LjhiTZzVCtG4E8hrNVVNXDGV5fCgJ94q8FU9CPH+XwdDDhLzBybfWhKfyItXmQiZNRPTiPCYTpVg==}
+ '@astrojs/compiler-binding-wasm32-wasi@0.3.2':
+ resolution: {integrity: sha512-pUA6xbcOSB7DhfzIArB8BCAkFfAIqriiR7zl5zOStd6oU2G0kIKj+GUdGnyXyhfiv881Hffyk5tC0mR18sDjDw==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
- '@astrojs/compiler-binding-win32-arm64-msvc@0.3.0':
- resolution: {integrity: sha512-CpY1RII2r1XMpOUVD1VR/F2wtuRsiOCkFULS10Khyj8/DFZMtxVuUCAWGw+CW2Ka0h6eP3Xc1CA+glFlvXMPxA==}
+ '@astrojs/compiler-binding-win32-arm64-msvc@0.3.2':
+ resolution: {integrity: sha512-ESruf+6Qkl1trHUFxI6GSf6t52j8yN2kCNSzMWdzt7V/T09tFHrYzrVaJQohb2C9bJUH76pNvX6Zb51+xCQc9Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
- '@astrojs/compiler-binding-win32-x64-msvc@0.3.0':
- resolution: {integrity: sha512-qmFbs769oeeGrRebAnCW7aBk8m71vf85W/dX/jddfx5Z06/w0wf7TZCfJPOX1Fld2t+4N+iXzfGEJG+zJQ+bzg==}
+ '@astrojs/compiler-binding-win32-x64-msvc@0.3.2':
+ resolution: {integrity: sha512-wzzVrEbOwbsLWOdEbocskjMRx2aZPxJ7ZbmL+jnpBamFwmigm+2M/wzuM6JWncocgYwLic1csSpalBh96kQKXA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
- '@astrojs/compiler-binding@0.3.0':
- resolution: {integrity: sha512-zlsOT5COD9hRwplJCgQhS21unxON5AKirf0vgt1ijXwuseYIaZdm2ZOpF8fsz+DY9EyXx+I/ukxtg7uoBep68A==}
+ '@astrojs/compiler-binding@0.3.2':
+ resolution: {integrity: sha512-8w/9CWmYrAJJ8N0SY3O43ws2BgxoW6u3QsD8u2mE140lMYAlwh+tlNoUeSBq22wVheFuiBbR212l6ixZ2IIgCQ==}
engines: {node: ^20.19.0 || >=22.12.0}
- '@astrojs/compiler-rs@0.3.0':
- resolution: {integrity: sha512-J2qEVHtIDjEM9TxwmwuebOGmZNwhKu/dR7P7qBpnJKGmBBX0vdweQ/4cEXhj8fBbWVUB5V12xWChri3CgKNULQ==}
+ '@astrojs/compiler-rs@0.3.2':
+ resolution: {integrity: sha512-xlx/T7JovIKduu4ucbTQUxQ5+Q8wxkHxhLjnZk3VlJbhbQ9RLvvuDk1p2YYFYFQ5y14dVm3FGGO4isQXa4F+Tg==}
engines: {node: '>=22.12.0'}
'@astrojs/compiler@2.13.1':
@@ -3124,6 +3124,9 @@ packages:
'@astrojs/internal-helpers@0.10.1':
resolution: {integrity: sha512-5phcroT/vmOOrYuuAxtkbPixy5hePtlz9i8K4OeDv3dNK6/UQRuXPOSRTxIOBbUY5Sonw2UaxjbuVc43Mcir6Q==}
+ '@astrojs/internal-helpers@0.10.3':
+ resolution: {integrity: sha512-HIx/t1NywcqSTFaVwZh15n8JsC3YQdCaJZDaTS/aurYTEvNiWDXUGS3Z9uQX3bFB+B1pCgN/nljckkzYTpHZ2w==}
+
'@astrojs/language-server@2.16.8':
resolution: {integrity: sha512-yg1pZF6hs9FaKr2fgXMOGbW7pDLgFexFjuhWilPAc8VybTU+WSnbfbhYaUL1exm6dAK4sM3aKXGcfVwss+HXbg==}
hasBin: true
@@ -3139,8 +3142,8 @@ packages:
'@astrojs/markdown-remark@7.2.1':
resolution: {integrity: sha512-jPVNIqTvk+yKviikszv/Y1U4jGUSKpp/Nw48QZV4qjWgp70j4Lkq3lhSDRbWwCfgKvEyO9GHuVbV1dM2WYXy1w==}
- '@astrojs/markdown-satteri@0.3.3':
- resolution: {integrity: sha512-Lje33Ittd8UQGgbIIWQvhPkj5X5c4b1sZnZWX3JQV/AWpfbuQGxVi2ONt6+ScydcwfR4egilslEWyczMclrJ1g==}
+ '@astrojs/markdown-satteri@0.3.6':
+ resolution: {integrity: sha512-kkeWP4Lh6Do/qz2R0DIAtPWCh6WPr1J4UrzDItVmHMK1HwyLHxG4JxM8uBNavhD7ivGOO9BUTsAP5D7ppa68CA==}
'@astrojs/mdx@7.0.2':
resolution: {integrity: sha512-l+sJY5U1KkGZUdr+bIL4Y6BefeS549qoSHVSkUSs6A9INwdCND+/0+vN0NroPBXwl5Vcg5u78t7VQRsJjePxbw==}
@@ -3170,8 +3173,8 @@ packages:
react: ^17.0.2 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0
- '@astrojs/telemetry@3.3.2':
- resolution: {integrity: sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ==}
+ '@astrojs/telemetry@3.3.3':
+ resolution: {integrity: sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==}
engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0}
'@astrojs/yaml2ts@0.2.3':
@@ -5993,6 +5996,13 @@ packages:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
+ '@napi-rs/wasm-runtime@1.2.3':
+ resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0}
+ peerDependencies:
+ '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4
+ '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4
+
'@next/env@15.5.19':
resolution: {integrity: sha512-sWWluFvcv5v3Fxznmf2ZfjyoVQt/64oCnYqS90inQWGzMPK1VjvekPiz3OPHKmFT30EnHrjlbyaHLt3M0vWabw==}
@@ -9209,12 +9219,12 @@ packages:
resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
hasBin: true
- astro@7.0.6:
- resolution: {integrity: sha512-Myw0sFia+zs/Y0yqfZEsUYXfDPh3ELcLf1f0Q/qQzVXBh/af1qO62WNT+P89DCcfGVV51nMoQhEfkBYqJmoUOQ==}
+ astro@7.2.3:
+ resolution: {integrity: sha512-CDf55Cw2nOev5BAM72yn7JQMpK/ByTY5tLSybbHbtZNSiMKk0OeyvCjA2foEUXfHXbATpHQQPBgfuXL8PdofcA==}
engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'}
hasBin: true
peerDependencies:
- '@astrojs/markdown-remark': 7.2.1
+ '@astrojs/markdown-remark': 7.2.3
peerDependenciesMeta:
'@astrojs/markdown-remark':
optional: true
@@ -9737,6 +9747,10 @@ packages:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'}
+ commander@14.0.3:
+ resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
+ engines: {node: '>=20'}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -9814,9 +9828,9 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
- cookie@1.1.1:
- resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
- engines: {node: '>=18'}
+ cookie@2.0.1:
+ resolution: {integrity: sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==}
+ engines: {node: '>=22'}
copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
@@ -10924,6 +10938,10 @@ packages:
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
engines: {node: '>= 0.8'}
+ find-process@2.1.1:
+ resolution: {integrity: sha512-SrQDx3QhlmHM90iqn9rdjCQcw/T+WlpOkHFsjoRgB+zTpDfltNA1VSNYeYELwhUTJy12UFxqjWhmhOrJc+o4sA==}
+ hasBin: true
+
find-root@1.1.0:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
@@ -11529,11 +11547,6 @@ packages:
is-decimal@2.0.1:
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
- is-docker@3.0.0:
- resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- hasBin: true
-
is-docker@4.0.0:
resolution: {integrity: sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==}
engines: {node: '>=20'}
@@ -11576,11 +11589,6 @@ packages:
is-hotkey@0.2.0:
resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==}
- is-inside-container@1.0.0:
- resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
- engines: {node: '>=14.16'}
- hasBin: true
-
is-interactive@2.0.0:
resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
engines: {node: '>=12'}
@@ -11696,10 +11704,6 @@ packages:
resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
engines: {node: '>=0.10.0'}
- is-wsl@3.1.1:
- resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
- engines: {node: '>=16'}
-
isarray@2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
@@ -12472,6 +12476,10 @@ packages:
resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
engines: {node: '>=18'}
+ loglevel@1.9.2:
+ resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==}
+ engines: {node: '>= 0.6.0'}
+
long@5.3.2:
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
@@ -12533,6 +12541,9 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+ magic-string@1.2.0:
+ resolution: {integrity: sha512-ptco+HFxTLgjafSLim2LojBSwfg5feBjd+SqyiwdGkzC38UPdZy3zgrHMI2CoTf5fJL38tbHMYWVzIH8BxGqJw==}
+
magicast@0.3.5:
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
@@ -13227,8 +13238,8 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
- neotraverse@0.6.18:
- resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==}
+ neotraverse@1.0.1:
+ resolution: {integrity: sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==}
engines: {node: '>= 10'}
next-auth@4.24.15:
@@ -15475,6 +15486,10 @@ packages:
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+ undici@8.10.0:
+ resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==}
+ engines: {node: '>=22.19.0'}
+
unicode-canonical-property-names-ecmascript@2.0.1:
resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
engines: {node: '>=4'}
@@ -15500,8 +15515,8 @@ packages:
unified@9.2.2:
resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==}
- unifont@0.7.4:
- resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==}
+ unifont@0.7.5:
+ resolution: {integrity: sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==}
unist-util-find-after@5.0.0:
resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
@@ -16240,10 +16255,6 @@ packages:
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
engines: {node: '>= 0.4'}
- which-pm-runs@1.1.0:
- resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
- engines: {node: '>=4'}
-
which-typed-array@1.1.19:
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
engines: {node: '>= 0.4'}
@@ -16496,56 +16507,56 @@ snapshots:
- prettier
- prettier-plugin-astro
- '@astrojs/compiler-binding-darwin-arm64@0.3.0':
+ '@astrojs/compiler-binding-darwin-arm64@0.3.2':
optional: true
- '@astrojs/compiler-binding-darwin-x64@0.3.0':
+ '@astrojs/compiler-binding-darwin-x64@0.3.2':
optional: true
- '@astrojs/compiler-binding-linux-arm64-gnu@0.3.0':
+ '@astrojs/compiler-binding-linux-arm64-gnu@0.3.2':
optional: true
- '@astrojs/compiler-binding-linux-arm64-musl@0.3.0':
+ '@astrojs/compiler-binding-linux-arm64-musl@0.3.2':
optional: true
- '@astrojs/compiler-binding-linux-x64-gnu@0.3.0':
+ '@astrojs/compiler-binding-linux-x64-gnu@0.3.2':
optional: true
- '@astrojs/compiler-binding-linux-x64-musl@0.3.0':
+ '@astrojs/compiler-binding-linux-x64-musl@0.3.2':
optional: true
- '@astrojs/compiler-binding-wasm32-wasi@0.3.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
+ '@astrojs/compiler-binding-wasm32-wasi@0.3.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
dependencies:
- '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
+ '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
transitivePeerDependencies:
- '@emnapi/core'
- '@emnapi/runtime'
optional: true
- '@astrojs/compiler-binding-win32-arm64-msvc@0.3.0':
+ '@astrojs/compiler-binding-win32-arm64-msvc@0.3.2':
optional: true
- '@astrojs/compiler-binding-win32-x64-msvc@0.3.0':
+ '@astrojs/compiler-binding-win32-x64-msvc@0.3.2':
optional: true
- '@astrojs/compiler-binding@0.3.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
+ '@astrojs/compiler-binding@0.3.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
optionalDependencies:
- '@astrojs/compiler-binding-darwin-arm64': 0.3.0
- '@astrojs/compiler-binding-darwin-x64': 0.3.0
- '@astrojs/compiler-binding-linux-arm64-gnu': 0.3.0
- '@astrojs/compiler-binding-linux-arm64-musl': 0.3.0
- '@astrojs/compiler-binding-linux-x64-gnu': 0.3.0
- '@astrojs/compiler-binding-linux-x64-musl': 0.3.0
- '@astrojs/compiler-binding-wasm32-wasi': 0.3.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
- '@astrojs/compiler-binding-win32-arm64-msvc': 0.3.0
- '@astrojs/compiler-binding-win32-x64-msvc': 0.3.0
+ '@astrojs/compiler-binding-darwin-arm64': 0.3.2
+ '@astrojs/compiler-binding-darwin-x64': 0.3.2
+ '@astrojs/compiler-binding-linux-arm64-gnu': 0.3.2
+ '@astrojs/compiler-binding-linux-arm64-musl': 0.3.2
+ '@astrojs/compiler-binding-linux-x64-gnu': 0.3.2
+ '@astrojs/compiler-binding-linux-x64-musl': 0.3.2
+ '@astrojs/compiler-binding-wasm32-wasi': 0.3.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
+ '@astrojs/compiler-binding-win32-arm64-msvc': 0.3.2
+ '@astrojs/compiler-binding-win32-x64-msvc': 0.3.2
transitivePeerDependencies:
- '@emnapi/core'
- '@emnapi/runtime'
- '@astrojs/compiler-rs@0.3.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
+ '@astrojs/compiler-rs@0.3.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
dependencies:
- '@astrojs/compiler-binding': 0.3.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
+ '@astrojs/compiler-binding': 0.3.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
transitivePeerDependencies:
- '@emnapi/core'
- '@emnapi/runtime'
@@ -16563,6 +16574,17 @@ snapshots:
smol-toml: 1.6.0
unified: 11.0.5
+ '@astrojs/internal-helpers@0.10.3':
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ js-yaml: 4.3.1
+ picomatch: 4.0.4
+ retext-smartypants: 6.2.0
+ shiki: 4.1.0
+ smol-toml: 1.6.0
+ unified: 11.0.5
+
'@astrojs/language-server@2.16.8(prettier@3.8.3)(typescript@5.9.3)':
dependencies:
'@astrojs/compiler': 2.13.1
@@ -16610,20 +16632,21 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@astrojs/markdown-satteri@0.3.3':
+ '@astrojs/markdown-satteri@0.3.6':
dependencies:
- '@astrojs/internal-helpers': 0.10.1
+ '@astrojs/internal-helpers': 0.10.3
'@astrojs/prism': 4.0.2
github-slugger: 2.0.0
+ hast-util-from-html: 2.0.3
satteri: 0.9.4
- '@astrojs/mdx@7.0.2(@astrojs/markdown-satteri@0.3.3)(astro@7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))':
+ '@astrojs/mdx@7.0.2(@astrojs/markdown-satteri@0.3.6)(astro@7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))':
dependencies:
'@astrojs/internal-helpers': 0.10.1
'@astrojs/markdown-remark': 7.2.1
'@mdx-js/mdx': 3.1.1
acorn: 8.16.0
- astro: 7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
+ astro: 7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
es-module-lexer: 2.1.0
estree-util-visit: 2.0.0
hast-util-to-html: 9.0.5
@@ -16635,14 +16658,14 @@ snapshots:
unist-util-visit: 5.1.0
vfile: 6.0.3
optionalDependencies:
- '@astrojs/markdown-satteri': 0.3.3
+ '@astrojs/markdown-satteri': 0.3.6
transitivePeerDependencies:
- supports-color
- '@astrojs/node@11.0.2(astro@7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))':
+ '@astrojs/node@11.0.2(astro@7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))':
dependencies:
'@astrojs/internal-helpers': 0.10.1
- astro: 7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
+ astro: 7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
send: 1.2.1
server-destroy: 1.0.1
transitivePeerDependencies:
@@ -16678,13 +16701,12 @@ snapshots:
- tsx
- yaml
- '@astrojs/telemetry@3.3.2':
+ '@astrojs/telemetry@3.3.3':
dependencies:
ci-info: 4.4.0
dset: 3.1.4
is-docker: 4.0.0
- is-wsl: 3.1.1
- which-pm-runs: 1.1.0
+ package-manager-detector: 1.6.0
'@astrojs/yaml2ts@0.2.3':
dependencies:
@@ -19011,7 +19033,7 @@ snapshots:
'@changesets/parse@0.4.2':
dependencies:
'@changesets/types': 6.1.0
- js-yaml: 4.1.1
+ js-yaml: 4.3.1
'@changesets/pre@2.0.2':
dependencies:
@@ -20829,6 +20851,13 @@ snapshots:
'@tybys/wasm-util': 0.10.3
optional: true
+ '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
+ dependencies:
+ '@emnapi/core': 1.11.1
+ '@emnapi/runtime': 1.11.1
+ '@tybys/wasm-util': 0.10.3
+ optional: true
+
'@next/env@15.5.19': {}
'@next/env@15.5.21': {}
@@ -25052,40 +25081,40 @@ snapshots:
astring@1.9.0: {}
- astro@7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@22.19.17)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2):
+ astro@7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@22.19.17)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2):
dependencies:
- '@astrojs/compiler-rs': 0.3.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
- '@astrojs/internal-helpers': 0.10.1
- '@astrojs/markdown-satteri': 0.3.3
- '@astrojs/telemetry': 3.3.2
+ '@astrojs/compiler-rs': 0.3.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
+ '@astrojs/internal-helpers': 0.10.3
+ '@astrojs/markdown-satteri': 0.3.6
+ '@astrojs/telemetry': 3.3.3
'@capsizecss/unpack': 4.0.0
'@clack/prompts': 1.4.0
'@oslojs/encoding': 1.1.0
- '@rollup/pluginutils': 5.3.0(rollup@4.53.4)
am-i-vibing: 0.4.0
aria-query: 5.3.2
axobject-query: 4.1.0
ci-info: 4.4.0
clsx: 2.1.1
common-ancestor-path: 2.0.0
- cookie: 1.1.1
+ cookie: 2.0.1
devalue: 5.8.1
diff: 8.0.3
dset: 3.1.4
es-module-lexer: 2.1.0
esbuild: 0.28.1
+ find-process: 2.1.1
flattie: 1.1.1
fontace: 0.4.1
get-tsconfig: 5.0.0-beta.4
github-slugger: 2.0.0
html-escaper: 3.0.3
http-cache-semantics: 4.2.0
- js-yaml: 4.1.1
+ js-yaml: 4.3.1
jsonc-parser: 3.3.1
- magic-string: 0.30.21
+ magic-string: 1.2.0
magicast: 0.5.2
mrmime: 2.0.1
- neotraverse: 0.6.18
+ neotraverse: 1.0.1
obug: 2.1.1
p-limit: 7.3.0
p-queue: 9.3.0
@@ -25100,7 +25129,7 @@ snapshots:
tinyexec: 1.2.2
tinyglobby: 0.2.17
ultrahtml: 1.6.0
- unifont: 0.7.4
+ unifont: 0.7.5
unstorage: 1.17.5(@azure/storage-blob@12.29.1)
vite: 8.1.3(@types/node@22.19.17)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
vitefu: 1.1.2(vite@8.1.3(@types/node@22.19.17)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))
@@ -25108,7 +25137,6 @@ snapshots:
yargs-parser: 22.0.0
zod: 4.4.3
optionalDependencies:
- '@astrojs/markdown-remark': 7.2.1
sharp: 0.34.5
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -25135,7 +25163,6 @@ snapshots:
- ioredis
- jiti
- less
- - rollup
- sass
- sass-embedded
- stylus
@@ -25145,40 +25172,40 @@ snapshots:
- uploadthing
- yaml
- astro@7.0.6(@astrojs/markdown-remark@7.2.1)(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(rollup@4.53.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2):
+ astro@7.2.3(@azure/storage-blob@12.29.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@25.1.0)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2):
dependencies:
- '@astrojs/compiler-rs': 0.3.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
- '@astrojs/internal-helpers': 0.10.1
- '@astrojs/markdown-satteri': 0.3.3
- '@astrojs/telemetry': 3.3.2
+ '@astrojs/compiler-rs': 0.3.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
+ '@astrojs/internal-helpers': 0.10.3
+ '@astrojs/markdown-satteri': 0.3.6
+ '@astrojs/telemetry': 3.3.3
'@capsizecss/unpack': 4.0.0
'@clack/prompts': 1.4.0
'@oslojs/encoding': 1.1.0
- '@rollup/pluginutils': 5.3.0(rollup@4.53.4)
am-i-vibing: 0.4.0
aria-query: 5.3.2
axobject-query: 4.1.0
ci-info: 4.4.0
clsx: 2.1.1
common-ancestor-path: 2.0.0
- cookie: 1.1.1
+ cookie: 2.0.1
devalue: 5.8.1
diff: 8.0.3
dset: 3.1.4
es-module-lexer: 2.1.0
esbuild: 0.28.1
+ find-process: 2.1.1
flattie: 1.1.1
fontace: 0.4.1
get-tsconfig: 5.0.0-beta.4
github-slugger: 2.0.0
html-escaper: 3.0.3
http-cache-semantics: 4.2.0
- js-yaml: 4.1.1
+ js-yaml: 4.3.1
jsonc-parser: 3.3.1
- magic-string: 0.30.21
+ magic-string: 1.2.0
magicast: 0.5.2
mrmime: 2.0.1
- neotraverse: 0.6.18
+ neotraverse: 1.0.1
obug: 2.1.1
p-limit: 7.3.0
p-queue: 9.3.0
@@ -25193,7 +25220,7 @@ snapshots:
tinyexec: 1.2.2
tinyglobby: 0.2.17
ultrahtml: 1.6.0
- unifont: 0.7.4
+ unifont: 0.7.5
unstorage: 1.17.5(@azure/storage-blob@12.29.1)
vite: 8.1.3(@types/node@25.1.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
vitefu: 1.1.2(vite@8.1.3(@types/node@25.1.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))
@@ -25201,7 +25228,6 @@ snapshots:
yargs-parser: 22.0.0
zod: 4.4.3
optionalDependencies:
- '@astrojs/markdown-remark': 7.2.1
sharp: 0.34.5
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -25228,7 +25254,6 @@ snapshots:
- ioredis
- jiti
- less
- - rollup
- sass
- sass-embedded
- stylus
@@ -25852,6 +25877,8 @@ snapshots:
commander@12.1.0: {}
+ commander@14.0.3: {}
+
commander@2.20.3:
optional: true
@@ -25911,7 +25938,7 @@ snapshots:
cookie@0.7.2: {}
- cookie@1.1.1: {}
+ cookie@2.0.1: {}
copy-to-clipboard@3.3.3:
dependencies:
@@ -27406,6 +27433,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ find-process@2.1.1:
+ dependencies:
+ chalk: 4.1.2
+ commander: 14.0.3
+ loglevel: 1.9.2
+
find-root@1.1.0: {}
find-up@4.1.0:
@@ -28128,8 +28161,6 @@ snapshots:
is-decimal@2.0.1: {}
- is-docker@3.0.0: {}
-
is-docker@4.0.0: {}
is-extendable@0.1.1: {}
@@ -28162,10 +28193,6 @@ snapshots:
is-hotkey@0.2.0: {}
- is-inside-container@1.0.0:
- dependencies:
- is-docker: 3.0.0
-
is-interactive@2.0.0: {}
is-map@2.0.3: {}
@@ -28257,10 +28284,6 @@ snapshots:
is-windows@1.0.2: {}
- is-wsl@3.1.1:
- dependencies:
- is-inside-container: 1.0.0
-
isarray@2.0.5: {}
isexe@2.0.0: {}
@@ -29577,6 +29600,8 @@ snapshots:
chalk: 5.6.2
is-unicode-supported: 1.3.0
+ loglevel@1.9.2: {}
+
long@5.3.2: {}
longest-streak@2.0.4: {}
@@ -29638,6 +29663,10 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
+ magic-string@1.2.0:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
magicast@0.3.5:
dependencies:
'@babel/parser': 7.29.7
@@ -30942,7 +30971,7 @@ snapshots:
neo-async@2.6.2: {}
- neotraverse@0.6.18: {}
+ neotraverse@1.0.1: {}
next-auth@4.24.15(next@15.5.19(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(sass@1.97.3))(react-dom@18.3.1(react@19.2.3))(react@19.2.3):
dependencies:
@@ -33861,6 +33890,8 @@ snapshots:
undici-types@7.16.0: {}
+ undici@8.10.0: {}
+
unicode-canonical-property-names-ecmascript@2.0.1: {}
unicode-match-property-ecmascript@2.0.0:
@@ -33902,11 +33933,11 @@ snapshots:
trough: 1.0.5
vfile: 4.2.1
- unifont@0.7.4:
+ unifont@0.7.5:
dependencies:
css-tree: 3.2.1
- ofetch: 1.5.1
ohash: 2.0.11
+ undici: 8.10.0
unist-util-find-after@5.0.0:
dependencies:
@@ -34860,8 +34891,6 @@ snapshots:
is-weakmap: 2.0.2
is-weakset: 2.0.4
- which-pm-runs@1.1.0: {}
-
which-typed-array@1.1.19:
dependencies:
available-typed-arrays: 1.0.7
From 4f9080666308063332e16d96d00a75ff7348c011 Mon Sep 17 00:00:00 2001
From: "Matt Wicks [SSW]"
Date: Thu, 20 Aug 2026 13:12:14 +1000
Subject: [PATCH 3/5] perf(mdx): ship one copy of acorn instead of three
(#7407)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
`@tinacms/mdx` bundles the acorn parser three times. This collapses it
to one, with no source change to the package and no change to emitted
output.
### The two causes
1. **`pnpm-workspace.yaml` pinned `acorn: 8.8.2`** while
`micromark-extension-mdxjs` pulls `^8.0.0`, resolving to 8.16.0 — two
8.x copies side by side. Aligned to `^8.16.0` (a caret, not an exact
pin: `micromark-extension-mdxjs` declares a range, so an exact pin would
re-split the moment a lockfile refresh moved it to 8.17.0).
2. **`acorn-jsx` reaches acorn via `require`**, which acorn's export map
answers with the CJS build while every other importer gets ESM — so
esbuild bundled `acorn.js` *and* `acorn.mjs`. The `@tinacms/mdx` esbuild
config now aliases `acorn` to the ESM entry acorn itself declares in its
`module` field.
### Measured
| Entry | raw before | raw after | Δ | gzip before | gzip after | Δ |
|---|---:|---:|---:|---:|---:|---:|
| `index.browser.js` | 1,976,421 | 1,578,764 | **−397,657 (−20.1%)** |
440,787 | 356,471 | **−84,316 (−19.1%)** |
| `index.js` | 2,013,419 | 1,615,828 | −397,591 (−19.7%) | 452,063 |
367,630 | −84,433 (−18.7%) |
Acorn versions in the browser bundle: `8.8.2` + `8.16.0` before,
`8.16.0` alone after.
### Output is unchanged
All 64 `src/next/tests/*` fixtures round-tripped through `parseMDX` →
`serializeMDX` from the old and new bundles produce **one identical
SHA-256**, across both the node and browser entries.
That check was mutation-proved. The first mutant survived — acorn's
module-level `parseExpressionAt` wrapper is never called, because
`micromark-extension-mdxjs` passes `Parser.extend(acornJsx())` and
reaches the **static** method (39 calls, measured). Unreachable by
construction, not a hole. Mutating the entry that *is* called flipped
the digest and changed 12 of 64 fixtures.
**What it does not cover:** only markdown/MDX fixtures in this repo — no
`parser.type: 'slatejson'`, no consumer-side integration, and the
browser bundle ran under node+jsdom rather than a real browser engine.
### Verification
`pnpm types` forced cold: 46/46. `pnpm test`: 60/60. `pnpm lint` and
`pnpm format` clean. Lockfile diff is 13 lines across 4 hunks — the
catalog entry, the mdx importer, and `acorn@8.8.2` dropped from both
sections.
No other published package changes size: `@tinacms/graphql` and
`packages/tinacms` externalise `@tinacms/mdx`, and `@tinacms/app` has no
build step. `packages/v4/**` are all `private: true`.
### Left alone
A **fourth** acorn (~138 KB) is vendored inside prettier's own
pre-bundled dist. esbuild cannot dedupe it — it is baked into prettier's
published bundle. It goes away with the prettier replacement stacked on
top of this PR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5
---
.changeset/dedupe-acorn-in-mdx-bundle.md | 6 ++++++
packages/@tinacms/scripts/src/index.ts | 17 +++++++++++++++++
pnpm-lock.yaml | 13 +++----------
pnpm-workspace.yaml | 2 +-
4 files changed, 27 insertions(+), 11 deletions(-)
create mode 100644 .changeset/dedupe-acorn-in-mdx-bundle.md
diff --git a/.changeset/dedupe-acorn-in-mdx-bundle.md b/.changeset/dedupe-acorn-in-mdx-bundle.md
new file mode 100644
index 0000000000..e0051f3b2a
--- /dev/null
+++ b/.changeset/dedupe-acorn-in-mdx-bundle.md
@@ -0,0 +1,6 @@
+---
+"@tinacms/scripts": patch
+"@tinacms/mdx": patch
+---
+
+Ship one copy of the `acorn` parser in `@tinacms/mdx` instead of three, cutting `dist/index.browser.js` from 1,976,421 to 1,578,764 bytes (440,787 to 356,471 gzipped) and `dist/index.js` from 2,013,419 to 1,615,828 bytes (452,063 to 367,630 gzipped). The catalog pinned `acorn` to 8.8.2 while `micromark-extension-mdxjs` pulled 8.16.0, so two 8.x copies were bundled side by side; separately, `acorn-jsx` reaches `acorn` through `require`, which acorn's export map answers with its CJS build while every other importer gets the ESM build, bundling the parser a second time. Aligning the catalog to `^8.16.0` and aliasing `acorn` to its ESM entry in the `@tinacms/mdx` esbuild config collapses all three into one. Parser and serializer output is unchanged — `parseMDX`/`serializeMDX` round-trips over the package's 64 markdown fixtures produce byte-identical results from the old and new bundles.
diff --git a/packages/@tinacms/scripts/src/index.ts b/packages/@tinacms/scripts/src/index.ts
index 6db22db296..bdb1f2ec88 100644
--- a/packages/@tinacms/scripts/src/index.ts
+++ b/packages/@tinacms/scripts/src/index.ts
@@ -1,5 +1,6 @@
import * as fs from 'fs';
import { exec } from 'node:child_process';
+import { createRequire } from 'node:module';
import path from 'node:path';
import chalk from 'chalk';
import chokidar from 'chokidar';
@@ -424,6 +425,20 @@ export class BuildTina {
});
} else if (['@tinacms/mdx'].includes(packageJSON.name)) {
const peerDeps = packageJSON.peerDependencies;
+
+ // acorn-jsx reaches acorn with `require`, which acorn's export map
+ // answers with the CJS build, while every other importer gets the ESM
+ // build. Without this the whole parser is bundled twice.
+ const acornManifest = createRequire(
+ path.join(process.cwd(), 'package.json')
+ ).resolve('acorn/package.json');
+ const alias = {
+ acorn: path.join(
+ path.dirname(acornManifest),
+ JSON.parse(fs.readFileSync(acornManifest, 'utf8')).module
+ ),
+ };
+
await esbuild({
entryPoints: [path.join(process.cwd(), entry)],
bundle: true,
@@ -432,6 +447,7 @@ export class BuildTina {
format: 'esm',
outfile: path.join(process.cwd(), 'dist', 'index.js'),
external: Object.keys({ ...peerDeps }),
+ alias,
});
// The ES version is targeting the browser. This is used by the rich-text's raw mode
@@ -446,6 +462,7 @@ export class BuildTina {
// and includes "development" export maps which actually throw errors during
// development, which we don't want to expose our users to.
external: Object.keys({ ...peerDeps }),
+ alias,
});
} else {
await esbuild({
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6dfdf9f769..51569d2d3c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -310,8 +310,8 @@ catalogs:
specifier: ^1.0.4
version: 1.0.4
acorn:
- specifier: 8.8.2
- version: 8.8.2
+ specifier: ^8.16.0
+ version: 8.16.0
altair-express-middleware:
specifier: ^7.3.6
version: 7.3.6
@@ -1724,7 +1724,7 @@ importers:
version: link:../schema-tools
acorn:
specifier: 'catalog:'
- version: 8.8.2
+ version: 8.16.0
ccount:
specifier: 'catalog:'
version: 2.0.1
@@ -9034,11 +9034,6 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
- acorn@8.8.2:
- resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
adm-zip@0.5.16:
resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==}
engines: {node: '>=12.0'}
@@ -24887,8 +24882,6 @@ snapshots:
acorn@8.16.0: {}
- acorn@8.8.2: {}
-
adm-zip@0.5.16: {}
agent-base@7.1.4: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 1dd8830667..ffdf7c0979 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -142,7 +142,7 @@ catalog:
"@vercel/stega": ^0.0.5
"@vitejs/plugin-react": ^4.7.0
abstract-level: ^1.0.4
- acorn: 8.8.2
+ acorn: ^8.16.0
altair-express-middleware: ^7.3.6
auto-bind: ^4.0.0
autoprefixer: ^10.4.20
From f32ad27481ededaa04a6d5e1474bb1820294e25e Mon Sep 17 00:00:00 2001
From: "Matt Wicks [SSW]"
Date: Thu, 20 Aug 2026 15:05:27 +1000
Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=A7=AA=20First=20production-build=20a?=
=?UTF-8?q?dmin=20browser=20suite=20+=20hostile=20fixture=20(#7317)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Closes #7239 · Part of #7237 (Phase 0 — baselines land before any risky
work)
The repo's first Playwright suite that runs against a **production**
`tinacms build` output — every existing suite boots `tinacms dev`, which
is structurally blind to bugs that only exist in production bundles.
This baselines today's vite-built admin; #7243 will rerun the identical
suite with the prebuilt flag on.
## The hostile fixture (`playwright/prebuilt-admin/`)
All seven ingredients from the issue, each guarding a named failure
class:
- custom field component colocated with the schema
- a component importing `next/image` (CJS dep → CJS `require('react')`
against the bundle's React)
- `aspect-w-9` (class the admin never uses) + `bg-blue-500` (must
resolve to Tina's themed `rgb(0,132,255)`)
- real `media.loadCustomStore` behind a dynamic `import()`
- `UsernamePasswordAuthJSProvider` from `tinacms-authjs/dist/tinacms`
(drags CJS `next-auth/react`); runtime-selected against
`LocalAuthProvider` so both stay in the bundle while specs choose which
is active
- `cmsCallback` registering a `createScreen` screen (react-router path)
- `build.basePath: 'my-site'`
## Specs (8 files, 12 tests)
`boot` (collection list; exactly one React renderer via an injected
devtools-hook stub — production React has no hook; zero console errors),
`custom-field` (save round-trips to disk), `screen` (navigates to the
registered screen **through the admin's own sidebar nav**, so
`useNavigate`/`NavLink` actually execute rather than a URL deep-link),
`tailwind` (computed styles for both probe classes), `basepath` (**with
and without** trailing slash), `media`, `auth`, plus a setup
**meta-assertion** that greps the built chunks for
`next/image`/custom-store/`next-auth`/screen markers — so the fixture
can never go tame silently.
`webServer` recipe: `tinacms build --local` emits the production SPA
into `public/admin` and keeps the local GraphQL/media server on :4001; a
zero-dep static server (`serve-admin.mjs`) serves the built SPA under
`/my-site/admin` (bare + trailing slash) on :3000.
## Notes for a reviewer
**The renderer-count + console-error check runs on the edit form, not
just the list.** That check is the suite's sharpest instrument, but
`FixtureField` — the component importing `next/image` — only mounts on
the edit form. Scoped to the collection list it would miss a render-time
throw from the very ingredient it exists to catch. Factored into
`trackConsoleErrors` / `assertHealthyRender`
(`e2e/utils/admin-helpers.ts`) and called from every spec that reaches
the form.
**The mutating spec is isolated and resets per attempt.** `custom-field`
writes to disk, so it owns `content/post/roundtrip.mdx`; the read-only
specs use `hello.mdx`. Snapshot/restore is `beforeEach`/`afterEach`, not
`beforeAll`/`afterAll` — with `retries: 2`, an attempt that saves
successfully then times out on the disk read would otherwise leave the
file mutated and make the retry fail deterministically, turning a
transient flake into a guaranteed red.
**`next` is pinned to 15.5.21.** 14.2.35 carries seven high-severity
advisories and `dependency-review` reads the fixture's manifest like any
other. `next/image` is still CJS and still does `require('react')` in
15, so the trap is intact; React stays on 18.3.1.
## Verified
- **CI green**, `prebuilt-admin-e2e` included, on the current head
- 12/12 green on repeated clean runs
- Trap re-introduction: breaking bare-path serving turns `basepath.spec`
red; removing the `next/image` import turns the meta-assertion red
- Workflow path-filtered on `packages/@tinacms/{cli,app}/**`,
`packages/tinacms/**`, `playwright/prebuilt-admin/**`; traces and the
HTML report upload on failure
## Finding (feeds #7243)
`tinacms build --local` does **not** bake the local apiUrl into the
admin — `buildProductionSpa` always uses `codegen.productionUrl`, so
with an empty clientId the SPA crashes on boot. The fixture works around
it with `contentApiUrlOverride: 'http://localhost:4001/graphql'`
(documented in its README). Worth an independent check on whether
kitchen-sink local builds are similarly pointed at TinaCloud.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5
---
.../workflows/playwright-prebuilt-admin.yml | 57 ++++++++
playwright/prebuilt-admin/.gitignore | 17 +++
playwright/prebuilt-admin/README.md | 83 ++++++++++++
.../prebuilt-admin/content/post/hello.mdx | 6 +
.../prebuilt-admin/content/post/roundtrip.mdx | 8 ++
playwright/prebuilt-admin/e2e/auth.spec.ts | 27 ++++
.../prebuilt-admin/e2e/basepath.spec.ts | 28 ++++
playwright/prebuilt-admin/e2e/boot.spec.ts | 31 +++++
.../prebuilt-admin/e2e/custom-field.spec.ts | 74 +++++++++++
playwright/prebuilt-admin/e2e/media.spec.ts | 22 ++++
playwright/prebuilt-admin/e2e/screen.spec.ts | 38 ++++++
playwright/prebuilt-admin/e2e/setup.spec.ts | 64 +++++++++
.../prebuilt-admin/e2e/tailwind.spec.ts | 55 ++++++++
.../prebuilt-admin/e2e/utils/admin-helpers.ts | 123 ++++++++++++++++++
playwright/prebuilt-admin/package.json | 25 ++++
.../prebuilt-admin/playwright.config.ts | 45 +++++++
playwright/prebuilt-admin/serve-admin.mjs | 109 ++++++++++++++++
.../prebuilt-admin/tina/collections/post.ts | 33 +++++
playwright/prebuilt-admin/tina/config.tsx | 73 +++++++++++
.../tina/fields/fixture-field.tsx | 36 +++++
.../tina/media/fixture-media-store.ts | 42 ++++++
playwright/prebuilt-admin/tsconfig.json | 16 +++
pnpm-lock.yaml | 54 +++++++-
23 files changed, 1065 insertions(+), 1 deletion(-)
create mode 100644 .github/workflows/playwright-prebuilt-admin.yml
create mode 100644 playwright/prebuilt-admin/.gitignore
create mode 100644 playwright/prebuilt-admin/README.md
create mode 100644 playwright/prebuilt-admin/content/post/hello.mdx
create mode 100644 playwright/prebuilt-admin/content/post/roundtrip.mdx
create mode 100644 playwright/prebuilt-admin/e2e/auth.spec.ts
create mode 100644 playwright/prebuilt-admin/e2e/basepath.spec.ts
create mode 100644 playwright/prebuilt-admin/e2e/boot.spec.ts
create mode 100644 playwright/prebuilt-admin/e2e/custom-field.spec.ts
create mode 100644 playwright/prebuilt-admin/e2e/media.spec.ts
create mode 100644 playwright/prebuilt-admin/e2e/screen.spec.ts
create mode 100644 playwright/prebuilt-admin/e2e/setup.spec.ts
create mode 100644 playwright/prebuilt-admin/e2e/tailwind.spec.ts
create mode 100644 playwright/prebuilt-admin/e2e/utils/admin-helpers.ts
create mode 100644 playwright/prebuilt-admin/package.json
create mode 100644 playwright/prebuilt-admin/playwright.config.ts
create mode 100644 playwright/prebuilt-admin/serve-admin.mjs
create mode 100644 playwright/prebuilt-admin/tina/collections/post.ts
create mode 100644 playwright/prebuilt-admin/tina/config.tsx
create mode 100644 playwright/prebuilt-admin/tina/fields/fixture-field.tsx
create mode 100644 playwright/prebuilt-admin/tina/media/fixture-media-store.ts
create mode 100644 playwright/prebuilt-admin/tsconfig.json
diff --git a/.github/workflows/playwright-prebuilt-admin.yml b/.github/workflows/playwright-prebuilt-admin.yml
new file mode 100644
index 0000000000..4416201e91
--- /dev/null
+++ b/.github/workflows/playwright-prebuilt-admin.yml
@@ -0,0 +1,57 @@
+name: Playwright — prebuilt admin (production build)
+
+on:
+ pull_request:
+ branches:
+ - main
+ paths:
+ - 'packages/@tinacms/cli/**'
+ - 'packages/@tinacms/app/**'
+ - 'packages/tinacms/**'
+ - 'packages/@tinacms/schema-tools/**'
+ - 'packages/@tinacms/mdx/**'
+ - 'packages/@tinacms/graphql/**'
+ - 'playwright/prebuilt-admin/**'
+ - '.github/workflows/playwright-prebuilt-admin.yml'
+
+permissions:
+ contents: read
+
+jobs:
+ e2e:
+ name: prebuilt-admin-e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - name: Setup Node.js environment
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
+ with:
+ node-version-file: '.nvmrc'
+ - name: Install pnpm
+ uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
+ with:
+ package_json_file: package.json
+ run_install: false
+ - name: Install dependencies
+ run: pnpm install
+ - name: Build workspace packages
+ run: pnpm build
+ - name: Install Playwright browsers
+ working-directory: playwright/prebuilt-admin
+ run: npx playwright install --with-deps chromium
+ - name: Run prebuilt-admin Playwright tests
+ working-directory: playwright/prebuilt-admin
+ run: npx playwright test
+ env:
+ CI: true
+ - name: Upload traces and test results
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ name: prebuilt-admin-playwright-report
+ path: |
+ playwright/prebuilt-admin/test-results/
+ playwright/prebuilt-admin/playwright-test-results.json
+ retention-days: 7
+ if-no-files-found: ignore
diff --git a/playwright/prebuilt-admin/.gitignore b/playwright/prebuilt-admin/.gitignore
new file mode 100644
index 0000000000..dc0ddf02b9
--- /dev/null
+++ b/playwright/prebuilt-admin/.gitignore
@@ -0,0 +1,17 @@
+# dependencies
+/node_modules
+
+# generated tina client + admin build output
+/tina/__generated__
+/public/admin
+
+# playwright
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
+/playwright-test-results.json
+
+# misc
+.DS_Store
+*.log
diff --git a/playwright/prebuilt-admin/README.md b/playwright/prebuilt-admin/README.md
new file mode 100644
index 0000000000..9d1a0994f3
--- /dev/null
+++ b/playwright/prebuilt-admin/README.md
@@ -0,0 +1,83 @@
+# prebuilt-admin
+
+The repo's first Playwright suite that runs against a **production `tinacms build`
+output** instead of `tinacms dev`. It is driven by a deliberately hostile fixture
+config so that a browser-only, production-only regression in the admin bundle
+fails here instead of slipping through CI.
+
+Green on `main` today is the baseline. Issue #7243 will rerun this identical
+suite with the prebuilt flag on; the specs are written to be **mode-agnostic**.
+
+## How the webServer works
+
+`pnpm serve:prod` runs:
+
+```
+tinacms build --local --skip-cloud-checks --noTelemetry -c "node serve-admin.mjs"
+```
+
+1. `tinacms build --local` builds the **production** admin SPA into
+ `public/admin/` (minified vite build — the same output a real deploy ships)
+ **and** keeps a local GraphQL + media server alive on `:4001`.
+2. The `-c` sub-command starts `serve-admin.mjs`, a tiny static server that
+ serves the built SPA under `/my-site/admin/` on `:3456`.
+
+The browser loads the production bundle from `:3456`; the admin queries the API
+cross-origin on `:4001` (localhost origins are CORS-allowed by the dev server).
+Playwright waits for `http://localhost:3456/my-site/admin/` before running.
+
+### Why `contentApiUrlOverride` is set
+
+`tinacms build` bakes `codegen.productionUrl` (the TinaCloud content URL) into
+the SPA **regardless of `--local`** — `--local` only affects the generated
+client SDK and runs a local server for build-time indexing. Without an override
+the admin would point at `https://content.tinajs.io/...` (with this fixture's
+empty clientId/branch that URL is even malformed and the admin crashes on boot).
+Setting `contentApiUrlOverride: 'http://localhost:4001/graphql'` redirects the
+baked SPA at the local server. This diverges from the issue brief's assumption
+that "in local mode the baked apiUrl points at the local GraphQL server"; it
+does not, and the override is the fix.
+
+## The hostile fixture (seven ingredients)
+
+Each guards a class of production-only bundling bug. `e2e/setup.spec.ts` greps
+the built chunks to prove they actually reached the bundle (guards against an
+accidentally-tame fixture).
+
+| Ingredient | Where |
+|---|---|
+| Custom field component colocated with the schema | `tina/fields/fixture-field.tsx` |
+| Component importing `next/image` (CJS `require('react')`) | `tina/fields/fixture-field.tsx` |
+| Arbitrary Tailwind class `aspect-w-9` + themed `bg-blue-500` | `tina/fields/fixture-field.tsx` |
+| Real `media.loadCustomStore` via dynamic `import()` | `tina/media/fixture-media-store.ts` |
+| `UsernamePasswordAuthJSProvider` from `tinacms-authjs/dist/tinacms` | `tina/config.tsx` |
+| `cmsCallback` registering a screen plugin | `tina/config.tsx` |
+| `build.basePath: 'my-site'` | `tina/config.tsx` |
+
+The auth provider is selected at runtime: `window.__TINA_FIXTURE_AUTHJS__`
+(set by `auth.spec` before boot) activates `UsernamePasswordAuthJSProvider` so
+its custom login screen renders; every other spec boots the local provider and
+clicks through the "Enter Edit Mode" dialog. Both providers are referenced so
+the bundler keeps `tinacms-authjs` (and its CJS `next-auth/react`) in the build
+either way.
+
+## Specs
+
+- `boot.spec` — collection list renders; single React reconciler; zero console errors on boot
+- `custom-field.spec` — custom field renders and a save round-trips to disk (its own document, `roundtrip.mdx`, isolated from the read-only specs)
+- `tailwind.spec` — `bg-blue-500` computes to Tina's `rgb(0, 132, 255)`; `aspect-w-9` compiled; also re-asserts the single-React/zero-console-error health check, since this is where `next/image` actually renders
+- `basepath.spec` — admin boots at `/my-site/admin/` **and** the bare `/my-site/admin`
+- `media.spec` — media manager lists via the custom store
+- `auth.spec` — the custom AuthJS login screen appears
+- `screen.spec` — navigates through the admin nav (not a deep link) to the `cmsCallback` screen plugin, guarding a second react-router-dom instance
+- `setup.spec` — meta-assertion: the CJS module, custom store and screen plugin are present in the built chunks
+
+## Running locally
+
+```
+pnpm install # from repo root
+pnpm build # build the workspace packages the fixture consumes
+cd playwright/prebuilt-admin
+npx playwright install chromium
+npx playwright test
+```
diff --git a/playwright/prebuilt-admin/content/post/hello.mdx b/playwright/prebuilt-admin/content/post/hello.mdx
new file mode 100644
index 0000000000..9d8d865ca7
--- /dev/null
+++ b/playwright/prebuilt-admin/content/post/hello.mdx
@@ -0,0 +1,6 @@
+---
+title: Hello Prebuilt
+marker: initial-marker
+---
+
+Hello from the prebuilt-admin fixture.
diff --git a/playwright/prebuilt-admin/content/post/roundtrip.mdx b/playwright/prebuilt-admin/content/post/roundtrip.mdx
new file mode 100644
index 0000000000..3112cde5ed
--- /dev/null
+++ b/playwright/prebuilt-admin/content/post/roundtrip.mdx
@@ -0,0 +1,8 @@
+---
+title: Roundtrip Fixture
+marker: initial-marker
+---
+
+Dedicated document for custom-field.spec's save round-trip. Kept separate
+from hello.mdx (read by boot.spec and tailwind.spec) so the mutating spec
+never races a read-only spec over the same file.
diff --git a/playwright/prebuilt-admin/e2e/auth.spec.ts b/playwright/prebuilt-admin/e2e/auth.spec.ts
new file mode 100644
index 0000000000..b682d811a4
--- /dev/null
+++ b/playwright/prebuilt-admin/e2e/auth.spec.ts
@@ -0,0 +1,27 @@
+import { expect, test } from '@playwright/test';
+import { ADMIN_ROOT } from './utils/admin-helpers';
+
+// The `UsernamePasswordAuthJSProvider` ingredient drags the ESM entry that
+// requires CJS `next-auth/react` into the bundle. When that provider is the
+// active one, the admin must render its custom username/password login screen
+// instead of the local edit-mode flow — proving next-auth/react loaded and ran
+// in the browser. The fixture selects this provider at runtime when
+// `window.__TINA_FIXTURE_AUTHJS__` is set before boot.
+test('custom AuthJS login screen appears', async ({ page }) => {
+ await page.addInitScript(() => {
+ (
+ window as unknown as { __TINA_FIXTURE_AUTHJS__: boolean }
+ ).__TINA_FIXTURE_AUTHJS__ = true;
+ });
+
+ await page.goto(ADMIN_ROOT, { waitUntil: 'domcontentloaded' });
+
+ // The username/password modal from UsernamePasswordAuthJSProvider.
+ await expect(page.getByPlaceholder('Username')).toBeVisible({
+ timeout: 30000,
+ });
+ await expect(page.getByPlaceholder('Password')).toBeVisible();
+ await expect(
+ page.getByRole('button', { name: 'Login', exact: true })
+ ).toBeVisible();
+});
diff --git a/playwright/prebuilt-admin/e2e/basepath.spec.ts b/playwright/prebuilt-admin/e2e/basepath.spec.ts
new file mode 100644
index 0000000000..f4b07e8919
--- /dev/null
+++ b/playwright/prebuilt-admin/e2e/basepath.spec.ts
@@ -0,0 +1,28 @@
+import { expect, test } from '@playwright/test';
+import { waitForAdminShell } from './utils/admin-helpers';
+
+// `build.basePath: 'my-site'` must serve a bootable admin at BOTH the
+// trailing-slash and bare forms. The bare form is the spike's trap: without
+// a trailing slash, `document.baseURI` drops the last segment and every
+// relative asset 404s. Assets are absolute in the production build, so the
+// bare form must still boot — this locks that in.
+for (const path of ['/my-site/admin/', '/my-site/admin']) {
+ test(`admin boots at "${path}"`, async ({ page }) => {
+ const consoleErrors: string[] = [];
+ page.on('console', (msg) => {
+ if (msg.type() === 'error') consoleErrors.push(msg.text());
+ });
+
+ await page.goto(path, { waitUntil: 'domcontentloaded' });
+ await waitForAdminShell(page);
+
+ // The shell mounted into #root (assets resolved).
+ const rootChildren = await page.evaluate(
+ () => document.getElementById('root')?.childElementCount ?? 0
+ );
+ expect(rootChildren).toBeGreaterThan(0);
+
+ // The build's inline asset-load guard never fired.
+ expect(consoleErrors.join('\n')).not.toContain('Failed to load assets');
+ });
+}
diff --git a/playwright/prebuilt-admin/e2e/boot.spec.ts b/playwright/prebuilt-admin/e2e/boot.spec.ts
new file mode 100644
index 0000000000..6558de7dd5
--- /dev/null
+++ b/playwright/prebuilt-admin/e2e/boot.spec.ts
@@ -0,0 +1,31 @@
+import { expect, test } from '@playwright/test';
+import {
+ ADMIN_INDEX,
+ DEVTOOLS_HOOK_STUB,
+ assertHealthyRender,
+ enterEditMode,
+ trackConsoleErrors,
+ waitForAdminShell,
+} from './utils/admin-helpers';
+
+// Boots the PRODUCTION admin bundle and asserts the health invariants that a
+// broken bundling of the hostile config would violate: the collection list
+// renders, exactly one React reconciler is live, and nothing logs an error
+// while the shell comes up.
+test('admin boots clean: collection list, single React, zero console errors', async ({
+ page,
+}) => {
+ await page.addInitScript(DEVTOOLS_HOOK_STUB);
+ const consoleErrors = trackConsoleErrors(page);
+
+ await page.goto(`${ADMIN_INDEX}#/collections/post`, {
+ waitUntil: 'domcontentloaded',
+ });
+ await waitForAdminShell(page);
+ await enterEditMode(page);
+
+ // Collection list renders — the seeded document appears in the table.
+ await expect(page.getByText('Hello Prebuilt')).toBeVisible();
+
+ await assertHealthyRender(page, consoleErrors);
+});
diff --git a/playwright/prebuilt-admin/e2e/custom-field.spec.ts b/playwright/prebuilt-admin/e2e/custom-field.spec.ts
new file mode 100644
index 0000000000..bc68707bfb
--- /dev/null
+++ b/playwright/prebuilt-admin/e2e/custom-field.spec.ts
@@ -0,0 +1,74 @@
+import { readFileSync, writeFileSync } from 'node:fs';
+import { join } from 'node:path';
+import { expect, test } from '@playwright/test';
+import {
+ ADMIN_INDEX,
+ DEVTOOLS_HOOK_STUB,
+ assertHealthyRender,
+ clickSave,
+ enterEditMode,
+ trackConsoleErrors,
+ waitForAdminShell,
+} from './utils/admin-helpers';
+
+// This is the only spec that mutates fixture content on disk. It has its own
+// document (roundtrip.mdx) — separate from hello.mdx, which boot.spec and
+// tailwind.spec read — so it never races a read-only spec over the same file
+// under `fullyParallel: true`.
+const ROUNDTRIP_PATH = join(
+ __dirname,
+ '..',
+ 'content',
+ 'post',
+ 'roundtrip.mdx'
+);
+
+// The colocated custom field component must render through the schema seam in
+// the production bundle, and a save must round-trip its value to disk.
+test.describe('custom field', () => {
+ let original: string;
+
+ // Snapshot/restore per attempt, not just per suite: with CI's `retries: 2`,
+ // an attempt that saves successfully and then fails a later assertion (a
+ // slow disk write racing `toPass`) would otherwise leave the file mutated
+ // going into the retry, turning a transient flake into a deterministic
+ // failure of the `toHaveValue('initial-marker')` assertion below.
+ test.beforeEach(() => {
+ original = readFileSync(ROUNDTRIP_PATH, 'utf8');
+ });
+
+ test.afterEach(() => {
+ writeFileSync(ROUNDTRIP_PATH, original, 'utf8');
+ });
+
+ test('renders and round-trips a save to disk', async ({ page }) => {
+ await page.addInitScript(DEVTOOLS_HOOK_STUB);
+ const consoleErrors = trackConsoleErrors(page);
+
+ await page.goto(`${ADMIN_INDEX}#/collections/edit/post/roundtrip`, {
+ waitUntil: 'domcontentloaded',
+ });
+ await waitForAdminShell(page);
+ await enterEditMode(page);
+
+ const input = page.getByTestId('fixture-field-input');
+ await expect(input).toBeVisible({ timeout: 30000 });
+ await expect(input).toHaveValue('initial-marker');
+
+ // The killer trap (next/image dragging a second React copy) only fires
+ // once FixtureField actually renders, which only happens on this edit
+ // form — boot.spec's collection list never mounts it.
+ await assertHealthyRender(page, consoleErrors);
+
+ const nextValue = `roundtrip-${Date.now()}`;
+ await input.fill(nextValue);
+ await clickSave(page);
+
+ // The mutation writes back through the filesystem bridge; give the write a
+ // beat then assert the new value is on disk.
+ await expect(async () => {
+ const onDisk = readFileSync(ROUNDTRIP_PATH, 'utf8');
+ expect(onDisk).toContain(`marker: ${nextValue}`);
+ }).toPass({ timeout: 10000 });
+ });
+});
diff --git a/playwright/prebuilt-admin/e2e/media.spec.ts b/playwright/prebuilt-admin/e2e/media.spec.ts
new file mode 100644
index 0000000000..5863048e8a
--- /dev/null
+++ b/playwright/prebuilt-admin/e2e/media.spec.ts
@@ -0,0 +1,22 @@
+import { expect, test } from '@playwright/test';
+import {
+ ADMIN_INDEX,
+ enterEditMode,
+ waitForAdminShell,
+} from './utils/admin-helpers';
+
+// The media library is backed by `media.loadCustomStore`'s dynamic import.
+// The Media Manager screen must list the item the custom store returns. We
+// route to the screen directly (`slugify('Media Manager') === 'media_manager'`)
+// so the assertion doesn't depend on the responsive nav drawer.
+test('media manager lists via the custom store', async ({ page }) => {
+ await page.goto(`${ADMIN_INDEX}#/screens/media_manager`, {
+ waitUntil: 'domcontentloaded',
+ });
+ await waitForAdminShell(page);
+ await enterEditMode(page);
+
+ await expect(page.getByText('prebuilt-fixture-media.png')).toBeVisible({
+ timeout: 30000,
+ });
+});
diff --git a/playwright/prebuilt-admin/e2e/screen.spec.ts b/playwright/prebuilt-admin/e2e/screen.spec.ts
new file mode 100644
index 0000000000..f32854d8cf
--- /dev/null
+++ b/playwright/prebuilt-admin/e2e/screen.spec.ts
@@ -0,0 +1,38 @@
+import { expect, test } from '@playwright/test';
+import {
+ ADMIN_INDEX,
+ enterEditMode,
+ trackConsoleErrors,
+ waitForAdminShell,
+} from './utils/admin-helpers';
+
+// The `cmsCallback` ingredient (tina/config.tsx) registers a screen plugin via
+// the raw `{ __type: 'screen' }` literal. It exists to catch a second
+// react-router-dom instance breaking `useNavigate`/`NavLink` — a bundling bug
+// that only shows up on a real navigation through the admin's own router, not
+// when the URL is deep-linked directly. So this spec drives the sidebar nav
+// (open the menu, click the screen link) rather than going straight to
+// `#/screens/fixture_screen`.
+test('screen plugin renders after navigating through the admin nav', async ({
+ page,
+}) => {
+ const consoleErrors = trackConsoleErrors(page);
+
+ await page.goto(`${ADMIN_INDEX}#/collections/post`, {
+ waitUntil: 'domcontentloaded',
+ });
+ await waitForAdminShell(page);
+ await enterEditMode(page);
+
+ await page.getByRole('button', { name: 'Toggle navigation menu' }).click();
+ await page.getByRole('link', { name: 'Fixture Screen' }).click();
+
+ await expect(page.getByTestId('fixture-screen')).toBeVisible({
+ timeout: 30000,
+ });
+
+ expect(
+ consoleErrors,
+ `console errors navigating to the screen:\n${consoleErrors.join('\n')}`
+ ).toEqual([]);
+});
diff --git a/playwright/prebuilt-admin/e2e/setup.spec.ts b/playwright/prebuilt-admin/e2e/setup.spec.ts
new file mode 100644
index 0000000000..43148af278
--- /dev/null
+++ b/playwright/prebuilt-admin/e2e/setup.spec.ts
@@ -0,0 +1,64 @@
+import { existsSync, readFileSync, readdirSync } from 'node:fs';
+import { join } from 'node:path';
+import { expect, test } from '@playwright/test';
+
+// Meta-assertion: guard against an accidentally-tame fixture. If the hostile
+// ingredients ever stop reaching the production bundle, the browser specs could
+// pass forever while real-world configs die. This greps the built admin chunks
+// to prove the risky modules actually made it in.
+const ASSETS_DIR = join(__dirname, '..', 'public', 'admin', 'assets');
+
+function readBuiltJs(): string {
+ expect(
+ existsSync(ASSETS_DIR),
+ `Built admin assets not found at ${ASSETS_DIR} — did the webServer run "tinacms build"?`
+ ).toBe(true);
+ return readdirSync(ASSETS_DIR)
+ .filter((f) => f.endsWith('.js'))
+ .map((f) => readFileSync(join(ASSETS_DIR, f), 'utf8'))
+ .join('\n');
+}
+
+test.describe('built bundle contains the hostile ingredients', () => {
+ test('next/image (CJS require(react)) is bundled', () => {
+ const js = readBuiltJs();
+ // next/image internals that survive minification.
+ const nextImageTokens = [
+ 'imageConfigDefault',
+ 'deviceSizes',
+ 'VALID_LOADERS',
+ '__NEXT_IMAGE',
+ ];
+ const present = nextImageTokens.filter((t) => js.includes(t));
+ expect(
+ present.length,
+ `Expected next/image markers in the build, found none of: ${nextImageTokens.join(', ')}`
+ ).toBeGreaterThan(0);
+ });
+
+ test('custom media store is bundled', () => {
+ const js = readBuiltJs();
+ expect(js).toContain('prebuilt-fixture-media');
+ });
+
+ test('tinacms-authjs / next-auth is bundled', () => {
+ const js = readBuiltJs();
+ // Two next-auth-specific string literals (minification-proof): the client
+ // logger's error constant and the default API basePath. Both must be
+ // present — a loose substring like 'next-auth' could match incidentally.
+ expect(js).toContain('CLIENT_FETCH_ERROR');
+ expect(js).toContain('/api/auth');
+ });
+
+ // Without this, screen.spec's nav-click + zero-console-errors assertion
+ // would be meaningless: it would pass vacuously if the `cmsCallback` plugin
+ // registration were ever dropped from the bundle (e.g. tree-shaken), since
+ // there'd simply be no "Fixture Screen" link to find. This proves the
+ // screen actually reached the built chunk, so a real react-router-dom
+ // duplication is what the runtime spec is left to catch.
+ test('screen plugin (react-router-dom singleton guard) is bundled', () => {
+ const js = readBuiltJs();
+ expect(js).toContain('fixture-screen');
+ expect(js).toContain('Fixture Screen');
+ });
+});
diff --git a/playwright/prebuilt-admin/e2e/tailwind.spec.ts b/playwright/prebuilt-admin/e2e/tailwind.spec.ts
new file mode 100644
index 0000000000..3505eae6c4
--- /dev/null
+++ b/playwright/prebuilt-admin/e2e/tailwind.spec.ts
@@ -0,0 +1,55 @@
+import { expect, test } from '@playwright/test';
+import {
+ ADMIN_INDEX,
+ DEVTOOLS_HOOK_STUB,
+ assertHealthyRender,
+ enterEditMode,
+ trackConsoleErrors,
+ waitForAdminShell,
+} from './utils/admin-helpers';
+
+// The admin build runs a second Tailwind pass over the colocated field code.
+// This proves that pass ran AND that it used Tina's theme rather than stock
+// Tailwind: `aspect-w-9` only exists because @tailwindcss/aspect-ratio was in
+// the pass, and `bg-blue-500` resolves to Tina's blue, not #3b82f6.
+test('fixture Tailwind classes compile with Tina theme', async ({ page }) => {
+ await page.addInitScript(DEVTOOLS_HOOK_STUB);
+ const consoleErrors = trackConsoleErrors(page);
+
+ await page.goto(`${ADMIN_INDEX}#/collections/edit/post/hello`, {
+ waitUntil: 'domcontentloaded',
+ });
+ await waitForAdminShell(page);
+ await enterEditMode(page);
+
+ // The custom field renders once the form loads; its text input is the
+ // reliable "form is ready" signal.
+ await expect(page.getByTestId('fixture-field-input')).toBeVisible({
+ timeout: 30000,
+ });
+
+ // The killer trap (next/image dragging a second React copy) only fires
+ // once FixtureField actually renders, which only happens on this edit
+ // form — boot.spec's collection list never mounts it.
+ await assertHealthyRender(page, consoleErrors);
+
+ // The probe is an empty aspect-ratio div (0 height), so assert attachment
+ // rather than visibility — computed styles read fine either way.
+ const probe = page.getByTestId('tw-probe');
+ await expect(probe).toBeAttached();
+
+ const styles = await probe.evaluate((el) => {
+ const cs = getComputedStyle(el);
+ return {
+ backgroundColor: cs.backgroundColor,
+ position: cs.position,
+ aspectW: cs.getPropertyValue('--tw-aspect-w').trim(),
+ };
+ });
+
+ // Tina's blue.500 is #0084FF.
+ expect(styles.backgroundColor).toBe('rgb(0, 132, 255)');
+ // aspect-w-9 from @tailwindcss/aspect-ratio.
+ expect(styles.position).toBe('relative');
+ expect(styles.aspectW).toBe('9');
+});
diff --git a/playwright/prebuilt-admin/e2e/utils/admin-helpers.ts b/playwright/prebuilt-admin/e2e/utils/admin-helpers.ts
new file mode 100644
index 0000000000..5a0425be15
--- /dev/null
+++ b/playwright/prebuilt-admin/e2e/utils/admin-helpers.ts
@@ -0,0 +1,123 @@
+import { type Page, expect } from '@playwright/test';
+
+// The fixture sets `build.basePath: 'my-site'`, so the admin lives under
+// /my-site/admin/. Hash routes hang off index.html.
+export const ADMIN_ROOT = '/my-site/admin/';
+export const ADMIN_INDEX = '/my-site/admin/index.html';
+
+/**
+ * A minimal React DevTools global hook. Production React only calls
+ * `__REACT_DEVTOOLS_GLOBAL_HOOK__.inject(...)` if the hook already exists —
+ * the browser extension normally supplies it. Injecting this stub via
+ * `addInitScript` (before any bundle runs) lets us count how many react-dom
+ * instances register: exactly one on a healthy single-React build.
+ */
+export const DEVTOOLS_HOOK_STUB = `
+ (() => {
+ const renderers = new Map();
+ let id = 0;
+ window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
+ supportsFiber: true,
+ isDisabled: false,
+ renderers,
+ inject(renderer) { const rid = ++id; renderers.set(rid, renderer); return rid; },
+ onCommitFiberRoot() {},
+ onCommitFiberUnmount() {},
+ onPostCommitFiberRoot() {},
+ on() {}, off() {}, emit() {}, sub() { return () => {}; },
+ checkDCE() {}, getFiberRoots() { return new Set(); },
+ };
+ })();
+`;
+
+/**
+ * Wait until the admin shell has mounted (the "local mode" chrome and the
+ * left-hand collection nav are both reliable signals across screens).
+ */
+export async function waitForAdminShell(page: Page): Promise {
+ await expect(page.getByText('You are in local mode').first()).toBeVisible({
+ timeout: 30000,
+ });
+}
+
+/**
+ * In local mode the LocalAuthProvider gates the admin behind an "Enter Edit
+ * Mode" dialog on first visit per browser context (it sets a localStorage
+ * flag). Click through it so the GraphQL-backed content renders. Safe to call
+ * when the dialog isn't shown.
+ */
+export async function enterEditMode(page: Page): Promise {
+ const btn = page.getByRole('button', {
+ name: 'Enter Edit Mode',
+ exact: true,
+ });
+ const appeared = await btn
+ .waitFor({ state: 'visible', timeout: 10000 })
+ .then(() => true)
+ .catch(() => false);
+ if (appeared) {
+ await btn.click();
+ await btn.waitFor({ state: 'hidden', timeout: 10000 }).catch(() => {});
+ }
+}
+
+/**
+ * Click Save and wait for the GraphQL mutation to resolve. The API is served
+ * cross-origin on :4001, so match on the /graphql suffix + 200.
+ */
+export async function clickSave(page: Page): Promise {
+ const saved = page.waitForResponse(
+ (resp) => resp.url().includes('/graphql') && resp.status() === 200,
+ { timeout: 15000 }
+ );
+ await page.getByRole('button', { name: 'Save', exact: true }).click();
+ await saved;
+}
+
+/**
+ * Start collecting console errors and page errors. Call before `page.goto`
+ * so nothing during boot is missed, then hand the returned array to
+ * `assertHealthyRender`.
+ */
+export function trackConsoleErrors(page: Page): string[] {
+ const consoleErrors: string[] = [];
+ page.on('console', (msg) => {
+ if (msg.type() === 'error') consoleErrors.push(msg.text());
+ });
+ page.on('pageerror', (err) => {
+ consoleErrors.push(`pageerror: ${err.message}`);
+ });
+ return consoleErrors;
+}
+
+/**
+ * The "killer trap" health check (single React reconciler + zero console
+ * errors), factored out so every spec that renders the hostile bundle —
+ * not just boot.spec's collection list — can assert it. `FixtureField`
+ * (which imports `next/image`) only mounts on the edit form, so the
+ * custom-field and tailwind specs must call this too: the collection list
+ * alone never exercises next/image's render, only its module import.
+ *
+ * Requires `DEVTOOLS_HOOK_STUB` to have been injected via `addInitScript`
+ * before `page.goto`, and `consoleErrors` to come from `trackConsoleErrors`.
+ */
+export async function assertHealthyRender(
+ page: Page,
+ consoleErrors: string[]
+): Promise {
+ // Exactly one react-dom registered with the DevTools hook. A second React
+ // dragged in through a CJS dep (e.g. next/image) would push this to 2.
+ const renderers = await page.evaluate(
+ () =>
+ (
+ window as unknown as {
+ __REACT_DEVTOOLS_GLOBAL_HOOK__: { renderers: Map };
+ }
+ ).__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size
+ );
+ expect(renderers).toBe(1);
+
+ expect(consoleErrors, `console errors:\n${consoleErrors.join('\n')}`).toEqual(
+ []
+ );
+}
diff --git a/playwright/prebuilt-admin/package.json b/playwright/prebuilt-admin/package.json
new file mode 100644
index 0000000000..02f5d15a24
--- /dev/null
+++ b/playwright/prebuilt-admin/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "playwright-prebuilt-admin",
+ "private": true,
+ "version": "0.0.0",
+ "scripts": {
+ "build:admin": "tinacms build --local --skip-cloud-checks --noTelemetry",
+ "serve:prod": "tinacms build --local --skip-cloud-checks --noTelemetry -c \"node serve-admin.mjs\"",
+ "test:e2e": "playwright test"
+ },
+ "dependencies": {
+ "next": "15.5.21",
+ "next-auth": "^4.24.11",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-is": "^18.3.1",
+ "tinacms": "workspace:*",
+ "tinacms-authjs": "workspace:*"
+ },
+ "devDependencies": {
+ "@playwright/test": "catalog:",
+ "@tinacms/cli": "workspace:*",
+ "@types/node": "^22.13.1",
+ "@types/react": "^18.3.18"
+ }
+}
diff --git a/playwright/prebuilt-admin/playwright.config.ts b/playwright/prebuilt-admin/playwright.config.ts
new file mode 100644
index 0000000000..afc1fd6eb3
--- /dev/null
+++ b/playwright/prebuilt-admin/playwright.config.ts
@@ -0,0 +1,45 @@
+import { defineConfig, devices } from '@playwright/test';
+
+/**
+ * Playwright config for the prebuilt-admin fixture.
+ *
+ * Unlike every other suite in the repo, the webServer here serves a PRODUCTION
+ * `tinacms build` output — never `tinacms dev`. `pnpm serve:prod` runs
+ * `tinacms build --local` (which builds the static admin SPA into
+ * public/admin AND keeps the local GraphQL/media server alive on :4001) with a
+ * `-c` sub-command that serves the built SPA under /my-site/admin/. The browser
+ * therefore loads the production bundle; the API is reached cross-origin on
+ * :4001. See README.md.
+ */
+export default defineConfig({
+ testDir: './e2e',
+ timeout: 60000,
+ expect: { timeout: 30000 },
+ reporter: [
+ ['list', { printSteps: true }],
+ ['json', { outputFile: 'playwright-test-results.json' }],
+ ],
+ fullyParallel: true,
+ forbidOnly: !!process.env.CI,
+ retries: process.env.CI ? 2 : 0,
+ workers: process.env.CI ? 1 : undefined,
+ use: {
+ baseURL: 'http://localhost:3456',
+ trace: 'on-first-retry',
+ screenshot: 'only-on-failure',
+ },
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ ],
+ webServer: {
+ command: 'pnpm serve:prod',
+ url: 'http://localhost:3456/my-site/admin/',
+ // Never reuse: the command rebuilds the production SPA, so a reused
+ // server would serve a stale public/admin from an earlier run.
+ reuseExistingServer: false,
+ timeout: 180000,
+ },
+});
diff --git a/playwright/prebuilt-admin/serve-admin.mjs b/playwright/prebuilt-admin/serve-admin.mjs
new file mode 100644
index 0000000000..c97e7a6920
--- /dev/null
+++ b/playwright/prebuilt-admin/serve-admin.mjs
@@ -0,0 +1,109 @@
+// Minimal static file server for the PRODUCTION `tinacms build` output.
+//
+// The fixture sets `build.basePath: 'my-site'`, so the built admin references
+// its assets at absolute `/my-site/admin/...` URLs and expects to be served
+// under that prefix. This server:
+// - serves files from ./public/admin under /my-site/admin/
+// - returns index.html for BOTH /my-site/admin/ and the bare /my-site/admin
+// (the trailing-slash bug the spike hit: document.baseURI drops a segment)
+// - falls back to index.html for unknown sub-paths (SPA hash routing)
+//
+// It never touches the GraphQL/media API — that is served cross-origin by the
+// `tinacms build --local` dev server on :4001, which this process keeps alive.
+
+import { readFile, stat } from 'node:fs/promises';
+import { createServer } from 'node:http';
+import { extname, join, normalize, sep } from 'node:path';
+import { dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const ADMIN_ROOT = join(__dirname, 'public', 'admin');
+const BASE_PREFIX = '/my-site/admin';
+const PORT = Number(process.env.PORT || 3456);
+
+const CONTENT_TYPES = {
+ '.html': 'text/html; charset=utf-8',
+ '.js': 'text/javascript; charset=utf-8',
+ '.mjs': 'text/javascript; charset=utf-8',
+ '.css': 'text/css; charset=utf-8',
+ '.json': 'application/json; charset=utf-8',
+ '.svg': 'image/svg+xml',
+ '.png': 'image/png',
+ '.jpg': 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.gif': 'image/gif',
+ '.ico': 'image/x-icon',
+ '.woff': 'font/woff',
+ '.woff2': 'font/woff2',
+ '.ttf': 'font/ttf',
+ '.map': 'application/json; charset=utf-8',
+ '.txt': 'text/plain; charset=utf-8',
+ '.webmanifest': 'application/manifest+json',
+};
+
+async function serveFile(res, absPath, fallbackToIndex) {
+ try {
+ const info = await stat(absPath);
+ if (info.isFile()) {
+ const body = await readFile(absPath);
+ res.writeHead(200, {
+ 'Content-Type':
+ CONTENT_TYPES[extname(absPath)] || 'application/octet-stream',
+ 'Cache-Control': 'no-store',
+ });
+ res.end(body);
+ return true;
+ }
+ } catch {
+ // fall through
+ }
+ if (fallbackToIndex) {
+ const index = await readFile(join(ADMIN_ROOT, 'index.html'));
+ res.writeHead(200, {
+ 'Content-Type': CONTENT_TYPES['.html'],
+ 'Cache-Control': 'no-store',
+ });
+ res.end(index);
+ return true;
+ }
+ return false;
+}
+
+const server = createServer(async (req, res) => {
+ try {
+ const url = decodeURIComponent((req.url || '/').split('?')[0]);
+
+ // Bare form (no trailing slash) and the root both boot the SPA.
+ if (url === BASE_PREFIX || url === `${BASE_PREFIX}/`) {
+ await serveFile(res, join(ADMIN_ROOT, 'index.html'), true);
+ return;
+ }
+
+ if (url.startsWith(`${BASE_PREFIX}/`)) {
+ const rel = url.slice(`${BASE_PREFIX}/`.length);
+ // Prevent path traversal outside the admin root.
+ const abs = normalize(join(ADMIN_ROOT, rel));
+ if (!(abs === ADMIN_ROOT || abs.startsWith(ADMIN_ROOT + sep))) {
+ res.writeHead(403);
+ res.end('Forbidden');
+ return;
+ }
+ await serveFile(res, abs, /* fallbackToIndex */ true);
+ return;
+ }
+
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
+ res.end(`Not found. Admin is served under ${BASE_PREFIX}/`);
+ } catch (err) {
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
+ res.end(`Server error: ${err}`);
+ }
+});
+
+server.listen(PORT, () => {
+ // eslint-disable-next-line no-console
+ console.log(
+ `prebuilt-admin static server: http://localhost:${PORT}${BASE_PREFIX}/`
+ );
+});
diff --git a/playwright/prebuilt-admin/tina/collections/post.ts b/playwright/prebuilt-admin/tina/collections/post.ts
new file mode 100644
index 0000000000..d3b3bce0d7
--- /dev/null
+++ b/playwright/prebuilt-admin/tina/collections/post.ts
@@ -0,0 +1,33 @@
+import type { Collection } from 'tinacms';
+import { FixtureField } from '../fields/fixture-field';
+
+export const post: Collection = {
+ label: 'Posts',
+ name: 'post',
+ path: 'content/post',
+ format: 'mdx',
+ fields: [
+ {
+ type: 'string',
+ name: 'title',
+ label: 'Title',
+ isTitle: true,
+ required: true,
+ },
+ {
+ // Colocated custom field component (JSX through the schema seam).
+ type: 'string',
+ name: 'marker',
+ label: 'Marker',
+ ui: {
+ component: FixtureField,
+ },
+ },
+ {
+ type: 'rich-text',
+ name: 'body',
+ label: 'Body',
+ isBody: true,
+ },
+ ],
+};
diff --git a/playwright/prebuilt-admin/tina/config.tsx b/playwright/prebuilt-admin/tina/config.tsx
new file mode 100644
index 0000000000..7cb95859dc
--- /dev/null
+++ b/playwright/prebuilt-admin/tina/config.tsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import { LocalAuthProvider, defineConfig } from 'tinacms';
+import { UsernamePasswordAuthJSProvider } from 'tinacms-authjs/dist/tinacms';
+import { post } from './collections/post';
+
+/**
+ * Runtime-selected auth provider.
+ *
+ * Both providers are referenced here so the bundler keeps `tinacms-authjs`
+ * — and the ESM entry that drags in CJS `next-auth/react` — inside the
+ * production build (the ingredient that guards the CJS-drag trap). Which one
+ * is *active* is decided in the browser: `auth.spec` sets
+ * `window.__TINA_FIXTURE_AUTHJS__` before boot to exercise the AuthJS login
+ * screen; every other spec boots the local provider and enters edit mode.
+ *
+ * At build time (`typeof window === 'undefined'`) the local provider is used —
+ * the value is irrelevant to the build, only the schema is.
+ */
+const selectAuthProvider = () => {
+ if (
+ typeof window !== 'undefined' &&
+ (window as unknown as { __TINA_FIXTURE_AUTHJS__?: boolean })
+ .__TINA_FIXTURE_AUTHJS__
+ ) {
+ return new UsernamePasswordAuthJSProvider();
+ }
+ return new LocalAuthProvider();
+};
+
+const FixtureScreenIcon = () => ★;
+
+export default defineConfig({
+ branch: '',
+ clientId: '',
+ token: '',
+ // Point the PRODUCTION admin bundle at the local GraphQL server that
+ // `tinacms build --local` keeps alive on :4001. Without this override
+ // `tinacms build` bakes the TinaCloud content URL into the SPA regardless
+ // of `--local` (it builds from `codegen.productionUrl`), so the admin can
+ // never reach the local API. See fixture README.
+ contentApiUrlOverride: 'http://localhost:4001/graphql',
+ authProvider: selectAuthProvider(),
+ build: {
+ publicFolder: 'public',
+ outputFolder: 'admin',
+ // Every URL bug the spike hit was basePath-shaped.
+ basePath: 'my-site',
+ },
+ media: {
+ // Real custom store behind a dynamic import (no `media.tina`).
+ loadCustomStore: async () => {
+ const pack = await import('./media/fixture-media-store');
+ return pack.FixtureMediaStore;
+ },
+ },
+ cmsCallback: (cms) => {
+ // Registering a screen plugin exercises the admin's screen/react-router
+ // path — it catches a second react-router-dom if the ABI misses it.
+ cms.plugins.add({
+ __type: 'screen',
+ name: 'Fixture Screen',
+ Icon: FixtureScreenIcon,
+ layout: 'popup',
+ Component: () => (
+
Prebuilt fixture screen
+ ),
+ });
+ return cms;
+ },
+ schema: {
+ collections: [post],
+ },
+});
diff --git a/playwright/prebuilt-admin/tina/fields/fixture-field.tsx b/playwright/prebuilt-admin/tina/fields/fixture-field.tsx
new file mode 100644
index 0000000000..3a2649781e
--- /dev/null
+++ b/playwright/prebuilt-admin/tina/fields/fixture-field.tsx
@@ -0,0 +1,36 @@
+import Image from 'next/image';
+import React from 'react';
+import { wrapFieldsWithMeta } from 'tinacms';
+
+// PREBUILT_FIXTURE_NEXT_IMAGE — a component that pulls `next/image` (a CJS
+// dependency that `require('react')`) into the admin bundle. If the production
+// build hands next/image its own React copy instead of the deduped one, this
+// field throws the classic "two copies of React" error at render time. A
+// 1x1 transparent PNG data URI keeps next/image self-contained (no Next
+// runtime image config needed).
+const PIXEL =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
+
+export const FixtureField = wrapFieldsWithMeta(({ input }: any) => {
+ return (
+
+
+ {/*
+ Tailwind trap: `aspect-w-9` is a class the admin shell never uses (it
+ only exists because the second Tailwind pass runs @tailwindcss/aspect-ratio
+ over the co-located field code), and `bg-blue-500` must resolve to Tina's
+ themed blue (rgb(0,132,255)), not stock Tailwind's.
+ */}
+
+ src}
+ />
+
Remove the original defer implementation in favor of
using raw promises via single fetch and turbo-stream. This
removes these exports from React Router: (#11744)
defer
AbortedDeferredError
type TypedDeferredData
UNSAFE_DeferredData
UNSAFE_DEFERRED_SYMBOL,
Use createRemixRouter/RouterProvider in
entry.client instead of RemixBrowser (#11469)
Add prefetching support to Link/NavLink
when using Remix SSR (#11402)
Enhance ScrollRestoration so it can restore properly on
an SSR'd document load (#11401)
Add built-in Remix-style hydration support to
RouterProvider. When running from a Remix-SSR'd HTML
payload with the proper window variables
(__remixContext, __remixManifest,
__remixRouteModules), you don't need to pass a
router prop and RouterProvider will create the
router for you internally. (#11396)
(#11400)