-
+ CommandInputProps
+>(({ className, wrapperClassName, hideIcon, ...props }, ref) => (
+
+ {hideIcon ? null : (
+
+ )}
=6.9.0'}
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/compat-data@7.29.0':
resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
engines: {node: '>=6.9.0'}
@@ -368,6 +372,10 @@ packages:
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.27.1':
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'}
@@ -830,6 +838,10 @@ packages:
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
engines: {node: '>=6.9.0'}
+ '@babel/runtime@7.29.7':
+ resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.28.6':
resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
engines: {node: '>=6.9.0'}
@@ -1804,57 +1816,57 @@ packages:
'@types/react': '>=16'
react: '>=16'
- '@next/env@16.2.4':
- resolution: {integrity: sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==}
+ '@next/env@16.2.6':
+ resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==}
- '@next/swc-darwin-arm64@16.2.4':
- resolution: {integrity: sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==}
+ '@next/swc-darwin-arm64@16.2.6':
+ resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
- '@next/swc-darwin-x64@16.2.4':
- resolution: {integrity: sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==}
+ '@next/swc-darwin-x64@16.2.6':
+ resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
- '@next/swc-linux-arm64-gnu@16.2.4':
- resolution: {integrity: sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==}
+ '@next/swc-linux-arm64-gnu@16.2.6':
+ resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
- '@next/swc-linux-arm64-musl@16.2.4':
- resolution: {integrity: sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==}
+ '@next/swc-linux-arm64-musl@16.2.6':
+ resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
- '@next/swc-linux-x64-gnu@16.2.4':
- resolution: {integrity: sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==}
+ '@next/swc-linux-x64-gnu@16.2.6':
+ resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
- '@next/swc-linux-x64-musl@16.2.4':
- resolution: {integrity: sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==}
+ '@next/swc-linux-x64-musl@16.2.6':
+ resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
- '@next/swc-win32-arm64-msvc@16.2.4':
- resolution: {integrity: sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==}
+ '@next/swc-win32-arm64-msvc@16.2.6':
+ resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
- '@next/swc-win32-x64-msvc@16.2.4':
- resolution: {integrity: sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==}
+ '@next/swc-win32-x64-msvc@16.2.6':
+ resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -2693,7 +2705,7 @@ packages:
'@tsparticles/basic@3.9.1':
resolution: {integrity: sha512-ijr2dHMx0IQHqhKW3qA8tfwrR2XYbbWYdaJMQuBo2CkwBVIhZ76U+H20Y492j/NXpd1FUnt2aC0l4CEVGVGdeQ==}
- deprecated: tsParticles v4 is out, please upgrade for better performances and more features
+ deprecated: Please update to version 4.1.1
'@tsparticles/engine@3.9.1':
resolution: {integrity: sha512-DpdgAhWMZ3Eh2gyxik8FXS6BKZ8vyea+Eu5BC4epsahqTGY9V3JGGJcXC6lRJx6cPMAx1A0FaQAojPF3v6rkmQ==}
@@ -3232,8 +3244,8 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
- baseline-browser-mapping@2.10.21:
- resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==}
+ baseline-browser-mapping@2.10.33:
+ resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -3322,8 +3334,8 @@ packages:
camel-case@4.1.2:
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
- caniuse-lite@1.0.30001790:
- resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==}
+ caniuse-lite@1.0.30001793:
+ resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==}
case-sensitive-paths-webpack-plugin@2.4.0:
resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==}
@@ -4862,8 +4874,8 @@ packages:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
- next@16.2.4:
- resolution: {integrity: sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==}
+ next@16.2.6:
+ resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==}
engines: {node: '>=20.9.0'}
hasBin: true
peerDependencies:
@@ -5516,6 +5528,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ semver@7.8.1:
+ resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -6189,6 +6206,12 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
'@babel/compat-data@7.29.0': {}
'@babel/core@7.29.0':
@@ -6322,6 +6345,8 @@ snapshots:
'@babel/helper-validator-identifier@7.28.5': {}
+ '@babel/helper-validator-identifier@7.29.7': {}
+
'@babel/helper-validator-option@7.27.1': {}
'@babel/helper-wrap-function@7.28.6':
@@ -6909,6 +6934,8 @@ snapshots:
'@babel/runtime@7.29.2': {}
+ '@babel/runtime@7.29.7': {}
+
'@babel/template@7.28.6':
dependencies:
'@babel/code-frame': 7.29.0
@@ -7608,30 +7635,30 @@ snapshots:
'@types/react': 19.2.14
react: 19.2.5
- '@next/env@16.2.4': {}
+ '@next/env@16.2.6': {}
- '@next/swc-darwin-arm64@16.2.4':
+ '@next/swc-darwin-arm64@16.2.6':
optional: true
- '@next/swc-darwin-x64@16.2.4':
+ '@next/swc-darwin-x64@16.2.6':
optional: true
- '@next/swc-linux-arm64-gnu@16.2.4':
+ '@next/swc-linux-arm64-gnu@16.2.6':
optional: true
- '@next/swc-linux-arm64-musl@16.2.4':
+ '@next/swc-linux-arm64-musl@16.2.6':
optional: true
- '@next/swc-linux-x64-gnu@16.2.4':
+ '@next/swc-linux-x64-gnu@16.2.6':
optional: true
- '@next/swc-linux-x64-musl@16.2.4':
+ '@next/swc-linux-x64-musl@16.2.6':
optional: true
- '@next/swc-win32-arm64-msvc@16.2.4':
+ '@next/swc-win32-arm64-msvc@16.2.6':
optional: true
- '@next/swc-win32-x64-msvc@16.2.4':
+ '@next/swc-win32-x64-msvc@16.2.6':
optional: true
'@opentelemetry/api-logs@0.208.0':
@@ -8311,7 +8338,7 @@ snapshots:
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
- '@storybook/nextjs@10.3.5(esbuild@0.17.19)(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19))':
+ '@storybook/nextjs@10.3.5(esbuild@0.17.19)(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0)
@@ -8335,7 +8362,7 @@ snapshots:
css-loader: 6.11.0(webpack@5.106.2(esbuild@0.17.19))
image-size: 2.0.2
loader-utils: 3.3.1
- next: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
node-polyfill-webpack-plugin: 2.0.1(webpack@5.106.2(esbuild@0.17.19))
postcss: 8.5.15
postcss-loader: 8.2.1(postcss@8.5.15)(typescript@5.9.3)(webpack@5.106.2(esbuild@0.17.19))
@@ -8508,8 +8535,8 @@ snapshots:
'@testing-library/dom@10.4.1':
dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/runtime': 7.29.2
+ '@babel/code-frame': 7.29.7
+ '@babel/runtime': 7.29.7
'@types/aria-query': 5.0.4
aria-query: 5.3.0
dom-accessibility-api: 0.5.16
@@ -9196,7 +9223,7 @@ snapshots:
base64-js@1.5.1: {}
- baseline-browser-mapping@2.10.21: {}
+ baseline-browser-mapping@2.10.33: {}
big.js@5.2.2: {}
@@ -9265,8 +9292,8 @@ snapshots:
browserslist@4.28.2:
dependencies:
- baseline-browser-mapping: 2.10.21
- caniuse-lite: 1.0.30001790
+ baseline-browser-mapping: 2.10.33
+ caniuse-lite: 1.0.30001793
electron-to-chromium: 1.5.344
node-releases: 2.0.38
update-browserslist-db: 1.2.3(browserslist@4.28.2)
@@ -9310,7 +9337,7 @@ snapshots:
pascal-case: 3.1.2
tslib: 2.8.1
- caniuse-lite@1.0.30001790: {}
+ caniuse-lite@1.0.30001793: {}
case-sensitive-paths-webpack-plugin@2.4.0: {}
@@ -11201,25 +11228,25 @@ snapshots:
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
- next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
+ next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
dependencies:
- '@next/env': 16.2.4
+ '@next/env': 16.2.6
'@swc/helpers': 0.5.15
- baseline-browser-mapping: 2.10.21
- caniuse-lite: 1.0.30001790
+ baseline-browser-mapping: 2.10.33
+ caniuse-lite: 1.0.30001793
postcss: 8.4.31
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.5)
optionalDependencies:
- '@next/swc-darwin-arm64': 16.2.4
- '@next/swc-darwin-x64': 16.2.4
- '@next/swc-linux-arm64-gnu': 16.2.4
- '@next/swc-linux-arm64-musl': 16.2.4
- '@next/swc-linux-x64-gnu': 16.2.4
- '@next/swc-linux-x64-musl': 16.2.4
- '@next/swc-win32-arm64-msvc': 16.2.4
- '@next/swc-win32-x64-msvc': 16.2.4
+ '@next/swc-darwin-arm64': 16.2.6
+ '@next/swc-darwin-x64': 16.2.6
+ '@next/swc-linux-arm64-gnu': 16.2.6
+ '@next/swc-linux-arm64-musl': 16.2.6
+ '@next/swc-linux-x64-gnu': 16.2.6
+ '@next/swc-linux-x64-musl': 16.2.6
+ '@next/swc-win32-arm64-msvc': 16.2.6
+ '@next/swc-win32-x64-msvc': 16.2.6
'@opentelemetry/api': 1.9.1
sharp: 0.34.5
transitivePeerDependencies:
@@ -11272,12 +11299,12 @@ snapshots:
dependencies:
boolbase: 1.0.0
- nuqs@2.8.9(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5):
+ nuqs@2.8.9(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5):
dependencies:
'@standard-schema/spec': 1.0.0
react: 19.2.5
optionalDependencies:
- next: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
object-inspect@1.13.4: {}
@@ -11985,6 +12012,8 @@ snapshots:
semver@7.7.4: {}
+ semver@7.8.1: {}
+
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -12033,7 +12062,7 @@ snapshots:
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
- semver: 7.7.4
+ semver: 7.8.1
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
diff --git a/scripts/validate-registry-install.js b/scripts/validate-registry-install.js
new file mode 100644
index 00000000..115a9aae
--- /dev/null
+++ b/scripts/validate-registry-install.js
@@ -0,0 +1,355 @@
+import { execSync } from "node:child_process";
+import fs from "node:fs";
+import os from "node:os";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
+const REGISTRY_DIR = path.join(ROOT, "public", "r");
+const SITE_URL = (process.env.NEXT_PUBLIC_APP_URL || "https://animata.design").replace(/\/$/, "");
+
+/** Representative installs — one dry-run per bundle shape. */
+const INSTALL_FIXTURES = [
+ {
+ id: "simple",
+ label: "single file, no npm deps",
+ item: "preloader/split-reveal.json",
+ expectFiles: ["components/animata/preloader/split-reveal.tsx"],
+ expectDeps: [],
+ },
+ {
+ id: "doc-bundled-shapes",
+ label: "mdx bundled file refs + shape imports",
+ item: "card/card-stack.json",
+ expectFiles: [
+ "components/animata/card/card-stack.tsx",
+ "components/shapes/card-stack-mask-defs.tsx",
+ ],
+ expectDeps: ["lucide-react", "motion"],
+ },
+ {
+ id: "co-located-shared",
+ label: "co-located helper rewritten to ./shared",
+ item: "tabs/fluid-tabs.json",
+ expectFiles: ["components/animata/tabs/fluid-tabs.tsx", "components/animata/tabs/shared.ts"],
+ expectDeps: ["lucide-react", "motion"],
+ },
+ {
+ id: "bundled-hook",
+ label: "bundled hook from @/hooks import",
+ item: "image/trailing-image.json",
+ expectFiles: ["components/animata/image/trailing-image.tsx", "hooks/use-mouse-position.ts"],
+ expectDeps: ["motion"],
+ },
+ {
+ id: "registry-dependencies",
+ label: "registryDependencies resolve to sibling items",
+ item: "card/card-spread.json",
+ localizeDeps: true,
+ expectFiles: [
+ "components/animata/card/card-spread.tsx",
+ "components/animata/widget/notes.tsx",
+ "components/animata/widget/shopping-list.tsx",
+ ],
+ expectDeps: [],
+ },
+];
+
+let failed = false;
+
+function error(message) {
+ console.error(`[registry-install] ${message}`);
+ failed = true;
+}
+
+function info(message) {
+ console.log(`[registry-install] ${message}`);
+}
+
+function walkRegistryJson(dir, acc = []) {
+ if (!fs.existsSync(dir)) return acc;
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+ const full = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ walkRegistryJson(full, acc);
+ } else if (entry.isFile() && entry.name.endsWith(".json")) {
+ acc.push(full);
+ }
+ }
+ return acc;
+}
+
+function registryUrlToLocalPath(url) {
+ const prefix = `${SITE_URL}/r/`;
+ if (!url.startsWith(prefix)) return null;
+ return path.join(REGISTRY_DIR, url.slice(prefix.length));
+}
+
+function localizeRegistryItem(itemPath) {
+ const raw = JSON.parse(fs.readFileSync(itemPath, "utf8"));
+ if (!raw.registryDependencies?.length) return itemPath;
+
+ const localized = structuredClone(raw);
+ localized.registryDependencies = raw.registryDependencies.map((dep) => {
+ const local = registryUrlToLocalPath(dep);
+ if (!local) return dep;
+ if (!fs.existsSync(local)) {
+ error(
+ `missing local registry dependency for ${path.relative(REGISTRY_DIR, itemPath)}: ${dep}`,
+ );
+ return dep;
+ }
+ return local;
+ });
+
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "animata-registry-"));
+ const outPath = path.join(tmpDir, path.basename(itemPath));
+ fs.writeFileSync(outPath, `${JSON.stringify(localized, null, 2)}\n`);
+ return outPath;
+}
+
+function validateRegistryJsonStatic(jsonPath) {
+ const rel = path.relative(REGISTRY_DIR, jsonPath);
+ let item;
+
+ try {
+ item = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
+ } catch {
+ error(`${rel}: invalid JSON`);
+ return;
+ }
+
+ if (!item.name || !item.type) {
+ error(`${rel}: missing name or type`);
+ }
+
+ if (!Array.isArray(item.files) || item.files.length === 0) {
+ error(`${rel}: files array is empty`);
+ return;
+ }
+
+ const paths = new Set();
+ for (const file of item.files) {
+ if (
+ !file.path ||
+ !file.type ||
+ typeof file.content !== "string" ||
+ file.content.trim() === ""
+ ) {
+ error(`${rel}: file entry missing path, type, or content`);
+ continue;
+ }
+ if (paths.has(file.path)) {
+ error(`${rel}: duplicate file path ${file.path}`);
+ }
+ paths.add(file.path);
+ }
+
+ for (const dep of item.registryDependencies ?? []) {
+ const local = registryUrlToLocalPath(dep);
+ if (local && !fs.existsSync(local)) {
+ error(`${rel}: registryDependency not found locally — ${dep}`);
+ }
+ }
+}
+
+function createShadcnWorkspace() {
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "animata-shadcn-"));
+
+ fs.writeFileSync(
+ path.join(dir, "package.json"),
+ `${JSON.stringify({ name: "animata-registry-install-test", private: true, type: "module", version: "0.0.0" }, null, 2)}\n`,
+ );
+
+ fs.writeFileSync(
+ path.join(dir, "tsconfig.json"),
+ `${JSON.stringify(
+ {
+ compilerOptions: {
+ target: "ES2017",
+ lib: ["dom", "dom.iterable", "esnext"],
+ allowJs: true,
+ skipLibCheck: true,
+ strict: true,
+ noEmit: true,
+ esModuleInterop: true,
+ module: "esnext",
+ moduleResolution: "bundler",
+ jsx: "preserve",
+ paths: { "@/*": ["./*"] },
+ },
+ include: ["**/*.ts", "**/*.tsx"],
+ },
+ null,
+ 2,
+ )}\n`,
+ );
+
+ fs.mkdirSync(path.join(dir, "app"), { recursive: true });
+ fs.mkdirSync(path.join(dir, "lib"), { recursive: true });
+ fs.writeFileSync(path.join(dir, "app", "globals.css"), '@import "tailwindcss";\n');
+ fs.writeFileSync(
+ path.join(dir, "lib", "utils.ts"),
+ 'export function cn(...inputs: Array) {\n return inputs.filter(Boolean).join(" ");\n}\n',
+ );
+
+ fs.writeFileSync(
+ path.join(dir, "components.json"),
+ `${JSON.stringify(
+ {
+ $schema: "https://ui.shadcn.com/schema.json",
+ style: "new-york",
+ rsc: true,
+ tsx: true,
+ tailwind: {
+ css: "app/globals.css",
+ baseColor: "stone",
+ cssVariables: true,
+ prefix: "",
+ },
+ aliases: {
+ components: "@/components",
+ utils: "@/lib/utils",
+ },
+ },
+ null,
+ 2,
+ )}\n`,
+ );
+
+ return dir;
+}
+
+function parseDryRunOutput(output) {
+ const files = [];
+ const deps = [];
+ let inDeps = false;
+
+ for (const line of output.split("\n")) {
+ const fileMatch = line.match(/^│ \+ (.+?) {2}create$/);
+ if (fileMatch) {
+ files.push(fileMatch[1].trim());
+ continue;
+ }
+
+ if (line.includes("├ Dependencies")) {
+ inDeps = true;
+ continue;
+ }
+
+ if (inDeps) {
+ const depMatch = line.match(/^│ \+ (.+)$/);
+ if (depMatch) {
+ deps.push(depMatch[1].trim().split("@")[0]);
+ continue;
+ }
+ if (line.includes("├ Files") || line.includes("│ ") || line.startsWith("└")) {
+ inDeps = false;
+ }
+ }
+ }
+
+ return { files, deps };
+}
+
+function runShadcnDryRun(workspaceDir, registryItemPath) {
+ const command = `npx shadcn@latest add -y --dry-run ${JSON.stringify(registryItemPath)}`;
+
+ try {
+ const output = execSync(command, {
+ cwd: workspaceDir,
+ encoding: "utf8",
+ stdio: ["ignore", "pipe", "pipe"],
+ timeout: 120_000,
+ env: { ...process.env, CI: "true" },
+ });
+ return { ok: true, output };
+ } catch (err) {
+ const stdout = err.stdout?.toString?.() ?? "";
+ const stderr = err.stderr?.toString?.() ?? "";
+ return { ok: false, output: `${stdout}\n${stderr}`.trim() };
+ }
+}
+
+function assertFixtureInstall(fixture, workspaceDir) {
+ const itemPath = path.join(REGISTRY_DIR, fixture.item);
+ if (!fs.existsSync(itemPath)) {
+ error(`fixture ${fixture.id}: missing ${fixture.item}`);
+ return;
+ }
+
+ const installPath = fixture.localizeDeps ? localizeRegistryItem(itemPath) : itemPath;
+ const result = runShadcnDryRun(workspaceDir, installPath);
+
+ if (!result.ok) {
+ error(`fixture ${fixture.id} (${fixture.label}): shadcn dry-run failed\n${result.output}`);
+ return;
+ }
+
+ const { files, deps } = parseDryRunOutput(result.output);
+
+ for (const expected of fixture.expectFiles) {
+ if (!files.includes(expected)) {
+ error(
+ `fixture ${fixture.id}: expected file "${expected}" in dry-run output. Got: ${files.join(", ") || "(none)"}`,
+ );
+ }
+ }
+
+ for (const expectedDep of fixture.expectDeps) {
+ if (!deps.includes(expectedDep)) {
+ error(
+ `fixture ${fixture.id}: expected npm dep "${expectedDep}". Got: ${deps.join(", ") || "(none)"}`,
+ );
+ }
+ }
+
+ info(`fixture ${fixture.id} OK — ${files.length} file(s), ${deps.length} npm dep(s)`);
+}
+
+function main() {
+ if (process.argv.includes("--build")) {
+ execSync("node ./scripts/build-registry.js", { cwd: ROOT, stdio: "inherit" });
+ }
+
+ if (!fs.existsSync(REGISTRY_DIR)) {
+ error(`registry output missing at ${REGISTRY_DIR} — run node scripts/build-registry.js first`);
+ process.exit(1);
+ }
+
+ const registryFiles = walkRegistryJson(REGISTRY_DIR);
+ if (registryFiles.length === 0) {
+ error("no registry JSON files found");
+ process.exit(1);
+ }
+
+ info(`static validation — ${registryFiles.length} item(s)`);
+ for (const jsonPath of registryFiles) {
+ validateRegistryJsonStatic(jsonPath);
+ }
+
+ if (failed) {
+ process.exit(1);
+ }
+
+ info(`shadcn dry-run — ${INSTALL_FIXTURES.length} fixture(s)`);
+ const workspaceDir = createShadcnWorkspace();
+
+ try {
+ for (const fixture of INSTALL_FIXTURES) {
+ assertFixtureInstall(fixture, workspaceDir);
+ }
+ } finally {
+ fs.rmSync(workspaceDir, { recursive: true, force: true });
+ }
+
+ if (failed) {
+ process.exit(1);
+ }
+
+ info(
+ `OK — ${registryFiles.length} registry item(s), ${INSTALL_FIXTURES.length} install fixture(s)`,
+ );
+}
+
+main();