nextjs-studio 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +3 -1
  2. package/dist/bin/nextjs-studio.js +286 -34
  3. package/dist/bin/nextjs-studio.js.map +1 -1
  4. package/dist/cli/ui/.next/BUILD_ID +1 -0
  5. package/dist/cli/ui/.next/app-path-routes-manifest.json +12 -0
  6. package/dist/cli/ui/.next/build-manifest.json +20 -0
  7. package/dist/cli/ui/.next/export-marker.json +6 -0
  8. package/dist/cli/ui/.next/images-manifest.json +67 -0
  9. package/dist/cli/ui/.next/next-minimal-server.js.nft.json +1 -0
  10. package/dist/cli/ui/.next/next-server.js.nft.json +1 -0
  11. package/dist/cli/ui/.next/package.json +1 -0
  12. package/dist/cli/ui/.next/prerender-manifest.json +61 -0
  13. package/dist/cli/ui/.next/react-loadable-manifest.json +286 -0
  14. package/dist/cli/ui/.next/required-server-files.js +338 -0
  15. package/dist/cli/ui/.next/required-server-files.json +338 -0
  16. package/dist/cli/ui/.next/routes-manifest.json +125 -0
  17. package/dist/cli/ui/.next/server/app/_global-error/page.js +3 -0
  18. package/dist/cli/ui/.next/server/app/_global-error/page.js.nft.json +1 -0
  19. package/dist/cli/ui/.next/server/app/_global-error/page_client-reference-manifest.js +1 -0
  20. package/dist/cli/ui/.next/server/app/_global-error.html +2 -0
  21. package/dist/cli/ui/.next/server/app/_global-error.meta +16 -0
  22. package/dist/cli/ui/.next/server/app/_global-error.rsc +12 -0
  23. package/dist/cli/ui/.next/server/app/_global-error.segments/_full.segment.rsc +12 -0
  24. package/dist/cli/ui/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +5 -0
  25. package/dist/cli/ui/.next/server/app/_global-error.segments/_global-error.segment.rsc +4 -0
  26. package/dist/cli/ui/.next/server/app/_global-error.segments/_head.segment.rsc +5 -0
  27. package/dist/cli/ui/.next/server/app/_global-error.segments/_index.segment.rsc +4 -0
  28. package/dist/cli/ui/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  29. package/dist/cli/ui/.next/server/app/_not-found/page.js +2 -0
  30. package/dist/cli/ui/.next/server/app/_not-found/page.js.nft.json +1 -0
  31. package/dist/cli/ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  32. package/dist/cli/ui/.next/server/app/_not-found.html +1 -0
  33. package/dist/cli/ui/.next/server/app/_not-found.meta +16 -0
  34. package/dist/cli/ui/.next/server/app/_not-found.rsc +15 -0
  35. package/dist/cli/ui/.next/server/app/_not-found.segments/_full.segment.rsc +15 -0
  36. package/dist/cli/ui/.next/server/app/_not-found.segments/_head.segment.rsc +5 -0
  37. package/dist/cli/ui/.next/server/app/_not-found.segments/_index.segment.rsc +7 -0
  38. package/dist/cli/ui/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
  39. package/dist/cli/ui/.next/server/app/_not-found.segments/_not-found.segment.rsc +4 -0
  40. package/dist/cli/ui/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
  41. package/dist/cli/ui/.next/server/app/api/media/[collection]/[filename]/route.js +1 -0
  42. package/dist/cli/ui/.next/server/app/api/media/[collection]/[filename]/route.js.nft.json +1 -0
  43. package/dist/cli/ui/.next/server/app/api/media/[collection]/[filename]/route_client-reference-manifest.js +1 -0
  44. package/dist/cli/ui/.next/server/app/api/media/[collection]/route.js +1 -0
  45. package/dist/cli/ui/.next/server/app/api/media/[collection]/route.js.nft.json +1 -0
  46. package/dist/cli/ui/.next/server/app/api/media/[collection]/route_client-reference-manifest.js +1 -0
  47. package/dist/cli/ui/.next/server/app/api/public/[...path]/route.js +1 -0
  48. package/dist/cli/ui/.next/server/app/api/public/[...path]/route.js.nft.json +1 -0
  49. package/dist/cli/ui/.next/server/app/api/public/[...path]/route_client-reference-manifest.js +1 -0
  50. package/dist/cli/ui/.next/server/app/api/sync/[collection]/route.js +1 -0
  51. package/dist/cli/ui/.next/server/app/api/sync/[collection]/route.js.nft.json +1 -0
  52. package/dist/cli/ui/.next/server/app/api/sync/[collection]/route_client-reference-manifest.js +1 -0
  53. package/dist/cli/ui/.next/server/app/api/watch/route.js +3 -0
  54. package/dist/cli/ui/.next/server/app/api/watch/route.js.nft.json +1 -0
  55. package/dist/cli/ui/.next/server/app/api/watch/route_client-reference-manifest.js +1 -0
  56. package/dist/cli/ui/.next/server/app/collection/[name]/[slug]/page.js +99 -0
  57. package/dist/cli/ui/.next/server/app/collection/[name]/[slug]/page.js.nft.json +1 -0
  58. package/dist/cli/ui/.next/server/app/collection/[name]/[slug]/page_client-reference-manifest.js +1 -0
  59. package/dist/cli/ui/.next/server/app/collection/[name]/page.js +5 -0
  60. package/dist/cli/ui/.next/server/app/collection/[name]/page.js.nft.json +1 -0
  61. package/dist/cli/ui/.next/server/app/collection/[name]/page_client-reference-manifest.js +1 -0
  62. package/dist/cli/ui/.next/server/app/page.js +2 -0
  63. package/dist/cli/ui/.next/server/app/page.js.nft.json +1 -0
  64. package/dist/cli/ui/.next/server/app/page_client-reference-manifest.js +1 -0
  65. package/dist/cli/ui/.next/server/app-paths-manifest.json +12 -0
  66. package/dist/cli/ui/.next/server/chunks/1875.js +1 -0
  67. package/dist/cli/ui/.next/server/chunks/2198.js +1 -0
  68. package/dist/cli/ui/.next/server/chunks/2446.js +36 -0
  69. package/dist/cli/ui/.next/server/chunks/2474.js +148 -0
  70. package/dist/cli/ui/.next/server/chunks/2490.js +55 -0
  71. package/dist/cli/ui/.next/server/chunks/2717.js +56 -0
  72. package/dist/cli/ui/.next/server/chunks/2729.js +166 -0
  73. package/dist/cli/ui/.next/server/chunks/2774.js +1 -0
  74. package/dist/cli/ui/.next/server/chunks/2796.js +1 -0
  75. package/dist/cli/ui/.next/server/chunks/3145.js +1 -0
  76. package/dist/cli/ui/.next/server/chunks/3186.js +1 -0
  77. package/dist/cli/ui/.next/server/chunks/3494.js +24 -0
  78. package/dist/cli/ui/.next/server/chunks/3610.js +1 -0
  79. package/dist/cli/ui/.next/server/chunks/3659.js +93 -0
  80. package/dist/cli/ui/.next/server/chunks/3744.js +1 -0
  81. package/dist/cli/ui/.next/server/chunks/3806.js +136 -0
  82. package/dist/cli/ui/.next/server/chunks/408.js +1 -0
  83. package/dist/cli/ui/.next/server/chunks/4189.js +62 -0
  84. package/dist/cli/ui/.next/server/chunks/4293.js +1 -0
  85. package/dist/cli/ui/.next/server/chunks/4469.js +4 -0
  86. package/dist/cli/ui/.next/server/chunks/4484.js +1 -0
  87. package/dist/cli/ui/.next/server/chunks/449.js +82 -0
  88. package/dist/cli/ui/.next/server/chunks/4607.js +22 -0
  89. package/dist/cli/ui/.next/server/chunks/479.js +5 -0
  90. package/dist/cli/ui/.next/server/chunks/4931.js +63 -0
  91. package/dist/cli/ui/.next/server/chunks/528.js +1 -0
  92. package/dist/cli/ui/.next/server/chunks/5341.js +1 -0
  93. package/dist/cli/ui/.next/server/chunks/5585.js +29 -0
  94. package/dist/cli/ui/.next/server/chunks/5677.js +1 -0
  95. package/dist/cli/ui/.next/server/chunks/5724.js +1 -0
  96. package/dist/cli/ui/.next/server/chunks/5760.js +174 -0
  97. package/dist/cli/ui/.next/server/chunks/5784.js +24 -0
  98. package/dist/cli/ui/.next/server/chunks/6.js +262 -0
  99. package/dist/cli/ui/.next/server/chunks/6040.js +1 -0
  100. package/dist/cli/ui/.next/server/chunks/6067.js +13 -0
  101. package/dist/cli/ui/.next/server/chunks/6553.js +1 -0
  102. package/dist/cli/ui/.next/server/chunks/6656.js +201 -0
  103. package/dist/cli/ui/.next/server/chunks/6672.js +1 -0
  104. package/dist/cli/ui/.next/server/chunks/6872.js +1 -0
  105. package/dist/cli/ui/.next/server/chunks/7146.js +59 -0
  106. package/dist/cli/ui/.next/server/chunks/7354.js +1 -0
  107. package/dist/cli/ui/.next/server/chunks/7677.js +1 -0
  108. package/dist/cli/ui/.next/server/chunks/7781.js +1 -0
  109. package/dist/cli/ui/.next/server/chunks/8194.js +215 -0
  110. package/dist/cli/ui/.next/server/chunks/8231.js +1 -0
  111. package/dist/cli/ui/.next/server/chunks/8319.js +2 -0
  112. package/dist/cli/ui/.next/server/chunks/8339.js +1 -0
  113. package/dist/cli/ui/.next/server/chunks/8775.js +1 -0
  114. package/dist/cli/ui/.next/server/chunks/9503.js +43 -0
  115. package/dist/cli/ui/.next/server/chunks/958.js +131 -0
  116. package/dist/cli/ui/.next/server/chunks/9739.js +1 -0
  117. package/dist/cli/ui/.next/server/functions-config-manifest.json +4 -0
  118. package/dist/cli/ui/.next/server/interception-route-rewrite-manifest.js +1 -0
  119. package/dist/cli/ui/.next/server/middleware-build-manifest.js +1 -0
  120. package/dist/cli/ui/.next/server/middleware-manifest.json +6 -0
  121. package/dist/cli/ui/.next/server/middleware-react-loadable-manifest.js +1 -0
  122. package/dist/cli/ui/.next/server/next-font-manifest.js +1 -0
  123. package/dist/cli/ui/.next/server/next-font-manifest.json +1 -0
  124. package/dist/cli/ui/.next/server/pages/404.html +1 -0
  125. package/dist/cli/ui/.next/server/pages/500.html +2 -0
  126. package/dist/cli/ui/.next/server/pages-manifest.json +4 -0
  127. package/dist/cli/ui/.next/server/server-reference-manifest.js +1 -0
  128. package/dist/cli/ui/.next/server/server-reference-manifest.json +1 -0
  129. package/dist/cli/ui/.next/server/webpack-runtime.js +1 -0
  130. package/dist/cli/ui/.next/static/chunks/0937d497-2b220e2d0368e918.js +1 -0
  131. package/dist/cli/ui/.next/static/chunks/1013.5541c1c75688ef6e.js +1 -0
  132. package/dist/cli/ui/.next/static/chunks/1039.d26ef6a818f26d70.js +4 -0
  133. package/dist/cli/ui/.next/static/chunks/1325.cb67348b484ce7a0.js +24 -0
  134. package/dist/cli/ui/.next/static/chunks/1858-d10f8bead2218ad2.js +2 -0
  135. package/dist/cli/ui/.next/static/chunks/1882.05c68ab6bdcd8c31.js +1 -0
  136. package/dist/cli/ui/.next/static/chunks/2011.7e73842f1e8e2cde.js +1 -0
  137. package/dist/cli/ui/.next/static/chunks/2046.73486ea936c9afd2.js +1 -0
  138. package/dist/cli/ui/.next/static/chunks/2389.b73d3a1286a228d0.js +1 -0
  139. package/dist/cli/ui/.next/static/chunks/2430.08df5899978ebf26.js +43 -0
  140. package/dist/cli/ui/.next/static/chunks/2462-4255d8f51dc56f12.js +1 -0
  141. package/dist/cli/ui/.next/static/chunks/3098-4269da0468edc350.js +1 -0
  142. package/dist/cli/ui/.next/static/chunks/317.b4fcc40f219152b1.js +1 -0
  143. package/dist/cli/ui/.next/static/chunks/3197.d2f3398e552228e4.js +1 -0
  144. package/dist/cli/ui/.next/static/chunks/3212-1a9c9d110d15abab.js +4 -0
  145. package/dist/cli/ui/.next/static/chunks/3809.8df186de2ff997f8.js +1 -0
  146. package/dist/cli/ui/.next/static/chunks/3918.caaf4eb4f6450524.js +1 -0
  147. package/dist/cli/ui/.next/static/chunks/3987-6d3217e36ae5b4dc.js +1 -0
  148. package/dist/cli/ui/.next/static/chunks/3c774391-ec08594272867fd5.js +79 -0
  149. package/dist/cli/ui/.next/static/chunks/4075052f.9006147a6182ed95.js +1 -0
  150. package/dist/cli/ui/.next/static/chunks/4389.e7a3c58aa62118e3.js +82 -0
  151. package/dist/cli/ui/.next/static/chunks/46bdbe0e-f5103600b23143b8.js +1 -0
  152. package/dist/cli/ui/.next/static/chunks/4860.2ba51d1d81857c81.js +1 -0
  153. package/dist/cli/ui/.next/static/chunks/4882.794e2a9231f129e5.js +55 -0
  154. package/dist/cli/ui/.next/static/chunks/5149.8de9918b9ace58f5.js +166 -0
  155. package/dist/cli/ui/.next/static/chunks/5230.b7c87400f0d8516f.js +215 -0
  156. package/dist/cli/ui/.next/static/chunks/5263.7adc3ade53cd4ed3.js +24 -0
  157. package/dist/cli/ui/.next/static/chunks/5313.1dfcc2126f85a8f8.js +1 -0
  158. package/dist/cli/ui/.next/static/chunks/5350-17c792e27bf38c77.js +1 -0
  159. package/dist/cli/ui/.next/static/chunks/5638.e3a2119354708654.js +1 -0
  160. package/dist/cli/ui/.next/static/chunks/5758.33b45c6c407ecd94.js +1 -0
  161. package/dist/cli/ui/.next/static/chunks/589.019158de5d02c262.js +1 -0
  162. package/dist/cli/ui/.next/static/chunks/6013.e33e73569339afee.js +62 -0
  163. package/dist/cli/ui/.next/static/chunks/6084.7511ad4ebb4f9df5.js +1 -0
  164. package/dist/cli/ui/.next/static/chunks/612.d3aeebcaaaf06fb2.js +1 -0
  165. package/dist/cli/ui/.next/static/chunks/6239.6c0cd99ea36222b6.js +5 -0
  166. package/dist/cli/ui/.next/static/chunks/6299-79a74877b87acf9f.js +1 -0
  167. package/dist/cli/ui/.next/static/chunks/6484.d38aa531768c3452.js +148 -0
  168. package/dist/cli/ui/.next/static/chunks/6505.731f74c28b8980b3.js +1 -0
  169. package/dist/cli/ui/.next/static/chunks/6584.be94a55391bcdcce.js +262 -0
  170. package/dist/cli/ui/.next/static/chunks/6800.1066ad1f5add7c75.js +1 -0
  171. package/dist/cli/ui/.next/static/chunks/7052.89e2de773d10ac20.js +131 -0
  172. package/dist/cli/ui/.next/static/chunks/7127.360abfa43dc4427e.js +36 -0
  173. package/dist/cli/ui/.next/static/chunks/7430.2b247d9ac6ef694c.js +149 -0
  174. package/dist/cli/ui/.next/static/chunks/7444.66c4d6bbc19309b3.js +59 -0
  175. package/dist/cli/ui/.next/static/chunks/7546.4cb4db4685212384.js +63 -0
  176. package/dist/cli/ui/.next/static/chunks/7564.7b9271fcf06df80c.js +29 -0
  177. package/dist/cli/ui/.next/static/chunks/7664.8268c156d8988844.js +174 -0
  178. package/dist/cli/ui/.next/static/chunks/7722-04e64cc1b26cfa65.js +1 -0
  179. package/dist/cli/ui/.next/static/chunks/8701.3995b58a75e43147.js +56 -0
  180. package/dist/cli/ui/.next/static/chunks/8800.548c3da26cfebf06.js +1 -0
  181. package/dist/cli/ui/.next/static/chunks/8814-7b64110c79ce7209.js +15 -0
  182. package/dist/cli/ui/.next/static/chunks/9058.a195d15265251194.js +1 -0
  183. package/dist/cli/ui/.next/static/chunks/9211.91144a0d23e4cf6d.js +1 -0
  184. package/dist/cli/ui/.next/static/chunks/9690.c1f08c02a675b9ce.js +1 -0
  185. package/dist/cli/ui/.next/static/chunks/9919.1ccc9ca4a749d9b7.js +93 -0
  186. package/dist/cli/ui/.next/static/chunks/app/_global-error/page-ad53913b77389472.js +1 -0
  187. package/dist/cli/ui/.next/static/chunks/app/_not-found/page-202c1e16a4b86ac8.js +1 -0
  188. package/dist/cli/ui/.next/static/chunks/app/api/media/[collection]/[filename]/route-ad53913b77389472.js +1 -0
  189. package/dist/cli/ui/.next/static/chunks/app/api/media/[collection]/route-ad53913b77389472.js +1 -0
  190. package/dist/cli/ui/.next/static/chunks/app/api/public/[...path]/route-ad53913b77389472.js +1 -0
  191. package/dist/cli/ui/.next/static/chunks/app/api/sync/[collection]/route-ad53913b77389472.js +1 -0
  192. package/dist/cli/ui/.next/static/chunks/app/api/watch/route-ad53913b77389472.js +1 -0
  193. package/dist/cli/ui/.next/static/chunks/app/collection/[name]/[slug]/page-7ea9eff252374845.js +6 -0
  194. package/dist/cli/ui/.next/static/chunks/app/collection/[name]/loading-03fc0b653e7060ae.js +1 -0
  195. package/dist/cli/ui/.next/static/chunks/app/collection/[name]/page-ad5976bebc9820cf.js +1 -0
  196. package/dist/cli/ui/.next/static/chunks/app/layout-74cd2ebe3fac00e7.js +1 -0
  197. package/dist/cli/ui/.next/static/chunks/app/page-ed5acadf20146966.js +1 -0
  198. package/dist/cli/ui/.next/static/chunks/b1767599.6a816b06e55b02a1.js +1 -0
  199. package/dist/cli/ui/.next/static/chunks/c6164070.3b9741bce302db0f.js +136 -0
  200. package/dist/cli/ui/.next/static/chunks/fb2ceba8.7fc23e46ca00cfd1.js +53 -0
  201. package/dist/cli/ui/.next/static/chunks/framework-ada624c9bf38edc6.js +1 -0
  202. package/dist/cli/ui/.next/static/chunks/main-0513ff42a7f2e1a0.js +5 -0
  203. package/dist/cli/ui/.next/static/chunks/main-app-b8d23dfb755744e8.js +1 -0
  204. package/dist/cli/ui/.next/static/chunks/next/dist/client/components/builtin/app-error-ad53913b77389472.js +1 -0
  205. package/dist/cli/ui/.next/static/chunks/next/dist/client/components/builtin/forbidden-ad53913b77389472.js +1 -0
  206. package/dist/cli/ui/.next/static/chunks/next/dist/client/components/builtin/global-error-558ee1074f45044b.js +1 -0
  207. package/dist/cli/ui/.next/static/chunks/next/dist/client/components/builtin/not-found-ad53913b77389472.js +1 -0
  208. package/dist/cli/ui/.next/static/chunks/next/dist/client/components/builtin/unauthorized-ad53913b77389472.js +1 -0
  209. package/dist/cli/ui/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  210. package/dist/cli/ui/.next/static/chunks/webpack-acb903cd88aafa6f.js +1 -0
  211. package/dist/cli/ui/.next/static/css/e143949aa3f17880.css +3 -0
  212. package/dist/cli/ui/.next/static/media/034d78ad42e9620c-s.woff2 +0 -0
  213. package/dist/cli/ui/.next/static/media/0484562807a97172-s.p.woff2 +0 -0
  214. package/dist/cli/ui/.next/static/media/29a4aea02fdee119-s.woff2 +0 -0
  215. package/dist/cli/ui/.next/static/media/29e7bbdce9332268-s.woff2 +0 -0
  216. package/dist/cli/ui/.next/static/media/4c285fdca692ea22-s.p.woff2 +0 -0
  217. package/dist/cli/ui/.next/static/media/6c177e25b87fd9cd-s.woff2 +0 -0
  218. package/dist/cli/ui/.next/static/media/6c9a125e97d835e1-s.woff2 +0 -0
  219. package/dist/cli/ui/.next/static/media/8888a3826f4a3af4-s.p.woff2 +0 -0
  220. package/dist/cli/ui/.next/static/media/a1386beebedccca4-s.woff2 +0 -0
  221. package/dist/cli/ui/.next/static/media/b957ea75a84b6ea7-s.p.woff2 +0 -0
  222. package/dist/cli/ui/.next/static/media/c3bc380753a8436c-s.woff2 +0 -0
  223. package/dist/cli/ui/.next/static/media/db911767852bc875-s.woff2 +0 -0
  224. package/dist/cli/ui/.next/static/media/eafabf029ad39a43-s.p.woff2 +0 -0
  225. package/dist/cli/ui/.next/static/media/f10b8e9d91f3edcb-s.woff2 +0 -0
  226. package/dist/cli/ui/.next/static/media/fe0777f1195381cb-s.woff2 +0 -0
  227. package/dist/cli/ui/.next/static/zkOuvkSWApTVHCrj7neNk/_buildManifest.js +1 -0
  228. package/dist/cli/ui/.next/static/zkOuvkSWApTVHCrj7neNk/_ssgManifest.js +1 -0
  229. package/dist/cli/ui/next.config.js +1 -0
  230. package/dist/cli/ui/package.json +4 -0
  231. package/dist/core/index.d.ts +34 -391
  232. package/dist/core/index.js +112 -338
  233. package/dist/core/index.js.map +1 -1
  234. package/dist/core/server.d.ts +135 -0
  235. package/dist/core/server.js +707 -0
  236. package/dist/core/server.js.map +1 -0
  237. package/dist/query-builder-KXz9cPzF.d.ts +330 -0
  238. package/package.json +31 -10
package/README.md CHANGED
@@ -4,6 +4,8 @@ A Git-based, local-first CMS for Next.js projects.
4
4
 
5
5
  Content lives in your repository as plain files. No database, no backend, no sync service. Editing happens through a standalone local server. Everything resolves at build time.
6
6
 
7
+ ![image](screenshot.png)
8
+
7
9
  ## Features
8
10
 
9
11
  - **Content Collections** — folders inside `/contents` become collections automatically
@@ -12,7 +14,7 @@ Content lives in your repository as plain files. No database, no backend, no syn
12
14
  - **JSON Form Editor** — auto-generated forms for JSON objects with nested field support
13
15
  - **Schema Validation** — Zod-based validation with field-level type definitions
14
16
  - **Media Library** — per-collection media folders, upload via drag & drop, paste, toolbar, or slash commands
15
- - **Import Scripts** — run data import and sync scripts directly from the CMS UI
17
+ - **Sync Scripts** — run data import and sync scripts directly from the CMS UI
16
18
  - **File Watching** — live updates via chokidar and WebSocket
17
19
 
18
20
  ## Quick start
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin/nextjs-studio.ts
4
- import { existsSync } from "fs";
4
+ import { existsSync as existsSync2 } from "fs";
5
5
  import fs2 from "fs/promises";
6
- import path2 from "path";
6
+ import path3 from "path";
7
+ import { createRequire } from "module";
7
8
  import { spawn } from "child_process";
8
9
  import { Command } from "commander";
9
10
 
10
11
  // src/shared/constants.ts
11
12
  var CONTENTS_DIR = "contents";
12
13
  var CLI_PORT = 3030;
14
+ var CONFIG_FILENAMES = ["studio.config.ts", "studio.config.js", "studio.config.mjs"];
13
15
  var SUPPORTED_EXTENSIONS = [".mdx", ".json"];
14
16
  var COLLECTION_ORDER_FILE = "collection.json";
15
17
  var IMAGE_MIME_TYPES = [
@@ -31,8 +33,9 @@ var AUDIO_MIME_TYPES = [
31
33
  ];
32
34
  var MEDIA_MIME_TYPES = [...IMAGE_MIME_TYPES, ...VIDEO_MIME_TYPES, ...AUDIO_MIME_TYPES];
33
35
 
34
- // src/cli/adapters/fs-adapter.ts
36
+ // src/core/fs-adapter.ts
35
37
  import fs from "fs/promises";
38
+ import fsSync from "fs";
36
39
  import path from "path";
37
40
  var FsAdapter = class {
38
41
  basePath;
@@ -127,6 +130,33 @@ var FsAdapter = class {
127
130
  normalizeSlug(relativePath, ext) {
128
131
  return relativePath.replace(ext, "").split(path.sep).join("/");
129
132
  }
133
+ readFileSync(filePath) {
134
+ return fsSync.readFileSync(this.resolve(filePath), "utf-8");
135
+ }
136
+ existsSync(filePath) {
137
+ return fsSync.existsSync(this.resolve(filePath));
138
+ }
139
+ listFilesSync(dirPath, extensions) {
140
+ const fullPath = this.resolve(dirPath);
141
+ const filterExts = extensions ?? SUPPORTED_EXTENSIONS;
142
+ let entries;
143
+ try {
144
+ entries = fsSync.readdirSync(fullPath, { withFileTypes: true });
145
+ } catch {
146
+ return [];
147
+ }
148
+ return entries.filter((entry) => entry.isFile() && filterExts.some((ext) => entry.name.endsWith(ext))).map((entry) => this.join(dirPath, entry.name));
149
+ }
150
+ listDirectoriesSync(dirPath) {
151
+ const fullPath = this.resolve(dirPath);
152
+ let entries;
153
+ try {
154
+ entries = fsSync.readdirSync(fullPath, { withFileTypes: true });
155
+ } catch {
156
+ return [];
157
+ }
158
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => this.join(dirPath, entry.name));
159
+ }
130
160
  };
131
161
 
132
162
  // src/core/indexer.ts
@@ -134,9 +164,49 @@ import slugify from "@sindresorhus/slugify";
134
164
 
135
165
  // src/core/parsers/parser-mdx.ts
136
166
  import matter from "gray-matter";
137
- function parseMdx(content) {
138
- const { data, content: body } = matter(content);
139
- return { data, body: body.trim() };
167
+
168
+ // src/core/frontmatter-binder.ts
169
+ var TOKEN_REGEX = /\{frontmatter\.([a-zA-Z0-9_.]+)\}/g;
170
+ function bindFrontmatter(body, data) {
171
+ return body.replace(TOKEN_REGEX, (_match, path4) => {
172
+ const value = resolvePath(data, path4);
173
+ if (value === void 0 || value === null) return _match;
174
+ if (typeof value === "object") return JSON.stringify(value);
175
+ return String(value);
176
+ });
177
+ }
178
+ function resolvePath(obj, path4) {
179
+ const keys = path4.split(".");
180
+ let current = obj;
181
+ for (const key of keys) {
182
+ if (typeof current !== "object" || current === null) return void 0;
183
+ current = current[key];
184
+ }
185
+ return current;
186
+ }
187
+
188
+ // src/core/parsers/parser-mdx.ts
189
+ function normalizeDates(data) {
190
+ const result = {};
191
+ for (const [key, value] of Object.entries(data)) {
192
+ if (value instanceof Date) {
193
+ result[key] = value.toISOString().split("T")[0];
194
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
195
+ result[key] = normalizeDates(value);
196
+ } else {
197
+ result[key] = value;
198
+ }
199
+ }
200
+ return result;
201
+ }
202
+ function parseMdx(content, options) {
203
+ const { data: rawData, content: body } = matter(content);
204
+ const data = normalizeDates(rawData);
205
+ const trimmed = body.trim();
206
+ return {
207
+ data,
208
+ body: options?.bindTokens ? bindFrontmatter(trimmed, data) : trimmed
209
+ };
140
210
  }
141
211
 
142
212
  // src/core/parsers/parser-json.ts
@@ -163,6 +233,7 @@ var RE_ISO_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]
163
233
  var RE_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
164
234
  var RE_URL = /^https?:\/\/.+/;
165
235
  var LONG_TEXT_THRESHOLD = 200;
236
+ var RICH_TEXT_FIELD_NAMES = ["description", "descriptions", "text", "content"];
166
237
  function isISODate(value) {
167
238
  return RE_ISO_DATE.test(value);
168
239
  }
@@ -180,6 +251,7 @@ function inferStringField(name, strings) {
180
251
  if (strings.every(isUrl)) return { name, type: "url" };
181
252
  if (strings.every(isISODateTime)) return { name, type: "date", includeTime: true };
182
253
  if (strings.every(isISODate)) return { name, type: "date" };
254
+ if (RICH_TEXT_FIELD_NAMES.includes(name.toLowerCase())) return { name, type: "long-text" };
183
255
  const isLong = strings.some((s) => s.length > LONG_TEXT_THRESHOLD || s.includes("\n"));
184
256
  return { name, type: isLong ? "long-text" : "text" };
185
257
  }
@@ -223,6 +295,21 @@ function inferSchema(entries, collectionName) {
223
295
  return { collection: collectionName, fields: inferFields(rows) };
224
296
  }
225
297
 
298
+ // src/core/locale-parser.ts
299
+ var LOCALE_REGEX = /\.([a-z]{2}(?:-[A-Z]{2})?)\.mdx$/;
300
+ function parseLocaleFromFilename(filename) {
301
+ const match = LOCALE_REGEX.exec(filename);
302
+ return match?.[1];
303
+ }
304
+ function stripLocaleFromSlug(slug, locale) {
305
+ if (!locale) return slug;
306
+ const dotSuffix = `.${locale}`;
307
+ if (slug.endsWith(dotSuffix)) return slug.slice(0, -dotSuffix.length);
308
+ const dashSuffix = `-${locale}`;
309
+ if (slug.endsWith(dashSuffix)) return slug.slice(0, -dashSuffix.length);
310
+ return slug;
311
+ }
312
+
226
313
  // src/core/indexer.ts
227
314
  var ContentIndex = class {
228
315
  entries = /* @__PURE__ */ new Map();
@@ -241,6 +328,16 @@ var ContentIndex = class {
241
328
  await this.indexCollection(dirName, collectionName, collectionConfig?.schema);
242
329
  }
243
330
  }
331
+ buildSync(config) {
332
+ this.clear();
333
+ const dirs = this.fs.listDirectoriesSync(".");
334
+ for (const dir of dirs) {
335
+ const dirName = this.fs.basename(dir);
336
+ const collectionName = slugify(dirName);
337
+ const collectionConfig = config?.collections?.[collectionName];
338
+ this.indexCollectionSync(dirName, collectionName, collectionConfig?.schema);
339
+ }
340
+ }
244
341
  getCollection(name) {
245
342
  return this.entries.get(name) ?? [];
246
343
  }
@@ -251,6 +348,35 @@ var ContentIndex = class {
251
348
  this.entries.clear();
252
349
  this.collections.clear();
253
350
  }
351
+ updateEntry(collectionName, entry) {
352
+ const entries = this.entries.get(collectionName) ?? [];
353
+ const idx = entries.findIndex((e) => e.slug === entry.slug);
354
+ if (idx >= 0) {
355
+ entries[idx] = entry;
356
+ } else {
357
+ entries.push(entry);
358
+ }
359
+ this.entries.set(collectionName, entries);
360
+ this.updateCollectionMeta(collectionName);
361
+ }
362
+ removeEntry(collectionName, slug) {
363
+ const entries = this.entries.get(collectionName);
364
+ if (!entries) return;
365
+ const filtered = entries.filter((e) => e.slug !== slug);
366
+ this.entries.set(collectionName, filtered);
367
+ this.updateCollectionMeta(collectionName);
368
+ }
369
+ updateCollectionMeta(collectionName) {
370
+ const col = this.collections.get(collectionName);
371
+ const entries = this.entries.get(collectionName) ?? [];
372
+ if (col) {
373
+ this.collections.set(collectionName, {
374
+ ...col,
375
+ count: entries.length,
376
+ type: this.detectCollectionType(entries)
377
+ });
378
+ }
379
+ }
254
380
  async indexCollection(dirName, collectionName, manualSchema) {
255
381
  const entries = [];
256
382
  await this.scanDir(dirName, collectionName, dirName, entries);
@@ -269,6 +395,24 @@ var ContentIndex = class {
269
395
  schema
270
396
  });
271
397
  }
398
+ indexCollectionSync(dirName, collectionName, manualSchema) {
399
+ const entries = [];
400
+ this.scanDirSync(dirName, collectionName, dirName, entries);
401
+ const orderPath = this.fs.join(dirName, COLLECTION_ORDER_FILE);
402
+ const ordering = this.readOrderingSync(orderPath);
403
+ if (ordering) {
404
+ this.applyOrdering(entries, ordering);
405
+ }
406
+ const schema = manualSchema ?? inferSchema(entries, collectionName);
407
+ this.entries.set(collectionName, entries);
408
+ this.collections.set(collectionName, {
409
+ name: collectionName,
410
+ type: this.detectCollectionType(entries),
411
+ count: entries.length,
412
+ basePath: dirName,
413
+ schema
414
+ });
415
+ }
272
416
  async scanDir(dirName, collectionName, dirPath, entries) {
273
417
  const subDirs = await this.fs.listDirectories(dirPath);
274
418
  for (const subDir of subDirs) {
@@ -283,20 +427,43 @@ var ContentIndex = class {
283
427
  const relativePath = this.fs.relative(dirName, filePath);
284
428
  const slug = this.fs.normalizeSlug(relativePath, ext).split("/").map((segment) => slugify(segment)).join("/");
285
429
  if (ext === ".mdx") {
286
- entries.push(this.buildMdxEntry(collectionName, slug, content));
430
+ entries.push(this.buildMdxEntry(collectionName, slug, fileName, content));
431
+ } else if (ext === ".json") {
432
+ entries.push(...this.buildJsonEntries(collectionName, slug, content));
433
+ }
434
+ }
435
+ }
436
+ scanDirSync(dirName, collectionName, dirPath, entries) {
437
+ const subDirs = this.fs.listDirectoriesSync(dirPath);
438
+ for (const subDir of subDirs) {
439
+ this.scanDirSync(dirName, collectionName, subDir, entries);
440
+ }
441
+ const files = this.fs.listFilesSync(dirPath);
442
+ for (const filePath of files) {
443
+ const fileName = this.fs.basename(filePath);
444
+ if (fileName === COLLECTION_ORDER_FILE) continue;
445
+ const ext = this.fs.extname(fileName);
446
+ const content = this.fs.readFileSync(filePath);
447
+ const relativePath = this.fs.relative(dirName, filePath);
448
+ const slug = this.fs.normalizeSlug(relativePath, ext).split("/").map((segment) => slugify(segment)).join("/");
449
+ if (ext === ".mdx") {
450
+ entries.push(this.buildMdxEntry(collectionName, slug, fileName, content));
287
451
  } else if (ext === ".json") {
288
452
  entries.push(...this.buildJsonEntries(collectionName, slug, content));
289
453
  }
290
454
  }
291
455
  }
292
- buildMdxEntry(collectionName, slug, content) {
456
+ buildMdxEntry(collectionName, slug, fileName, content) {
293
457
  const parsed = parseMdx(content);
458
+ const locale = parseLocaleFromFilename(fileName);
459
+ const normalizedSlug = stripLocaleFromSlug(slug, locale);
294
460
  return {
295
461
  collection: collectionName,
296
- slug,
297
- path: `/${collectionName}/${slug}`,
462
+ slug: normalizedSlug,
463
+ path: `/${collectionName}/${normalizedSlug}`,
298
464
  body: parsed.body,
299
- data: parsed.data
465
+ data: parsed.data,
466
+ ...locale ? { locale } : {}
300
467
  };
301
468
  }
302
469
  buildJsonEntries(collectionName, slug, content) {
@@ -325,6 +492,17 @@ var ContentIndex = class {
325
492
  }
326
493
  return null;
327
494
  }
495
+ readOrderingSync(orderPath) {
496
+ if (!this.fs.existsSync(orderPath)) return null;
497
+ try {
498
+ const content = this.fs.readFileSync(orderPath);
499
+ const parsed = JSON.parse(content);
500
+ if (Array.isArray(parsed)) return parsed;
501
+ } catch (error) {
502
+ console.warn(`[Nextjs Studio] Failed to parse ordering file: ${orderPath}`, error);
503
+ }
504
+ return null;
505
+ }
328
506
  applyOrdering(entries, ordering) {
329
507
  const orderMap = new Map(ordering.map((slug, index) => [slug, index]));
330
508
  entries.sort((a, b) => {
@@ -444,20 +622,58 @@ function generateCollectionTypes(schemas) {
444
622
  ].join("\n");
445
623
  const interfaces = schemas.map(generateInterfaceForSchema).join("\n\n");
446
624
  const collectionRegistry = [
447
- "// Augment the nextjs-studio module so queryCollection() is fully typed.",
625
+ "// Augment both entry points so queryCollection() is fully typed regardless of import path.",
448
626
  "declare module 'nextjs-studio' {",
449
627
  " interface CollectionTypeMap {",
450
628
  schemas.map((schema) => ` ${JSON.stringify(schema.collection)}: ${toPascalCase(schema.collection)}Entry;`).join("\n"),
451
629
  " }",
630
+ "}",
631
+ "declare module 'nextjs-studio/server' {",
632
+ " interface CollectionTypeMap {",
633
+ schemas.map((schema) => ` ${JSON.stringify(schema.collection)}: ${toPascalCase(schema.collection)}Entry;`).join("\n"),
634
+ " }",
452
635
  "}"
453
636
  ].join("\n");
454
637
  return [banner, interfaces, collectionRegistry].join("\n\n") + "\n";
455
638
  }
456
639
 
640
+ // src/core/config-loader.ts
641
+ import { existsSync } from "fs";
642
+ import path2 from "path";
643
+ import { pathToFileURL } from "url";
644
+ function resolveConfigPath(projectRoot) {
645
+ for (const filename of CONFIG_FILENAMES) {
646
+ const fullPath = path2.resolve(projectRoot, filename);
647
+ if (existsSync(fullPath)) return fullPath;
648
+ }
649
+ return void 0;
650
+ }
651
+ async function loadStudioConfig(projectRoot) {
652
+ const configPath2 = resolveConfigPath(projectRoot);
653
+ if (!configPath2) return {};
654
+ return loadConfigFromPath(configPath2);
655
+ }
656
+ async function loadConfigFromPath(configPath2) {
657
+ try {
658
+ const fileUrl = pathToFileURL(configPath2).href;
659
+ const mod = await import(
660
+ /* webpackIgnore: true */
661
+ fileUrl
662
+ );
663
+ const config = mod.default ?? mod.config ?? mod;
664
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
665
+ return {};
666
+ }
667
+ return config;
668
+ } catch {
669
+ return {};
670
+ }
671
+ }
672
+
457
673
  // package.json
458
674
  var package_default = {
459
675
  name: "nextjs-studio",
460
- version: "0.4.0",
676
+ version: "1.0.0",
461
677
  description: "A Git-based, local-first CMS for Next.js projects",
462
678
  keywords: [
463
679
  "nextjs",
@@ -480,22 +696,43 @@ var package_default = {
480
696
  ".": {
481
697
  types: "./dist/core/index.d.ts",
482
698
  import: "./dist/core/index.js"
699
+ },
700
+ "./server": {
701
+ types: "./dist/core/server.d.ts",
702
+ import: "./dist/core/server.js"
483
703
  }
484
704
  },
485
705
  main: "./dist/core/index.js",
486
706
  types: "./dist/core/index.d.ts",
487
- bin: {
488
- "nextjs-studio": "dist/bin/nextjs-studio.js"
489
- },
707
+ bin: "dist/bin/nextjs-studio.js",
490
708
  files: [
491
- "dist",
709
+ "dist/bin",
710
+ "dist/core",
711
+ "dist/*.d.ts",
712
+ "dist/cli/ui/package.json",
713
+ "dist/cli/ui/next.config.js",
714
+ "dist/cli/ui/.next/static",
715
+ "dist/cli/ui/.next/server",
716
+ "dist/cli/ui/.next/BUILD_ID",
717
+ "dist/cli/ui/.next/app-path-routes-manifest.json",
718
+ "dist/cli/ui/.next/build-manifest.json",
719
+ "dist/cli/ui/.next/export-marker.json",
720
+ "dist/cli/ui/.next/images-manifest.json",
721
+ "dist/cli/ui/.next/next-minimal-server.js.nft.json",
722
+ "dist/cli/ui/.next/next-server.js.nft.json",
723
+ "dist/cli/ui/.next/package.json",
724
+ "dist/cli/ui/.next/prerender-manifest.json",
725
+ "dist/cli/ui/.next/react-loadable-manifest.json",
726
+ "dist/cli/ui/.next/required-server-files.js",
727
+ "dist/cli/ui/.next/required-server-files.json",
728
+ "dist/cli/ui/.next/routes-manifest.json",
492
729
  "README.md",
493
730
  "LICENSE"
494
731
  ],
495
732
  scripts: {
496
733
  dev: "tsx src/bin/nextjs-studio.ts --dir example/contents",
497
734
  "studio:dev": "cross-env STUDIO_CONTENTS_DIR=example/contents next dev --port 3030 --webpack src/cli/ui",
498
- "studio:build": "next build --webpack src/cli/ui",
735
+ "studio:build": `next build --webpack src/cli/ui && node -e "const fs=require('fs');fs.copyFileSync('src/cli/ui/package.json','dist/cli/ui/package.json');fs.writeFileSync('dist/cli/ui/next.config.js','module.exports={distDir:\\'.next\\'};')"`,
499
736
  build: "tsup && yarn studio:build",
500
737
  lint: "eslint src/",
501
738
  "type-check": "tsc --noEmit",
@@ -532,10 +769,7 @@ var package_default = {
532
769
  lowlight: "^3.3.0",
533
770
  "lucide-react": "^0.574.0",
534
771
  mermaid: "^11.6.0",
535
- next: "^16.1.6",
536
772
  "next-themes": "^0.4.6",
537
- react: "^19.2.4",
538
- "react-dom": "^19.2.4",
539
773
  "tailwind-merge": "^3.4.1",
540
774
  tailwindcss: "^4.1.18",
541
775
  "tippy.js": "^6.3.7",
@@ -552,7 +786,10 @@ var package_default = {
552
786
  chokidar: "^5.0.0",
553
787
  commander: "^14.0.3",
554
788
  "gray-matter": "^4.0.3",
555
- "lodash-es": "^4.17.23"
789
+ "lodash-es": "^4.17.23",
790
+ next: "^16.1.6",
791
+ react: "^19.2.4",
792
+ "react-dom": "^19.2.4"
556
793
  }
557
794
  };
558
795
 
@@ -560,14 +797,15 @@ var package_default = {
560
797
  var { version } = package_default;
561
798
  var program = new Command().name("Nextjs Studio").description("Local-first CMS for Next.js projects").version(version).option("-d, --dir <path>", "Path to contents directory", CONTENTS_DIR).option("-p, --port <number>", "Port to run the studio on", String(CLI_PORT)).option("--generate-types", "Generate TypeScript types for content collections").parse();
562
799
  var opts = program.opts();
563
- var contentsDir = path2.resolve(opts.dir);
800
+ var contentsDir = path3.resolve(opts.dir);
564
801
  var port = Number(opts.port);
565
802
  async function runGenerateTypes(sourceDir) {
566
- const outDir = path2.resolve(".studio");
567
- const outFile = path2.join(outDir, "studio.d.ts");
803
+ const outDir = path3.resolve(".studio");
804
+ const outFile = path3.join(outDir, "studio.d.ts");
568
805
  console.log(`Generating types from ${sourceDir}...`);
806
+ const config = await loadStudioConfig(process.cwd());
569
807
  const fsAdapter = new FsAdapter(sourceDir);
570
- const index = await loadContent(fsAdapter);
808
+ const index = await loadContent(fsAdapter, config);
571
809
  const schemas = index.getCollections().flatMap((c) => c.schema ? [c.schema] : []);
572
810
  const code = generateCollectionTypes(schemas);
573
811
  await fs2.mkdir(outDir, { recursive: true });
@@ -575,13 +813,19 @@ async function runGenerateTypes(sourceDir) {
575
813
  console.log(`Types written to ${outFile}`);
576
814
  }
577
815
  function resolveServerProcess(uiDir2, serverPort, env) {
578
- const standaloneServer = path2.resolve(uiDir2, ".next/standalone/src/cli/ui/server.js");
579
- if (existsSync(standaloneServer)) {
580
- return spawn("node", [standaloneServer], { stdio: "inherit", env });
816
+ const buildId = path3.resolve(uiDir2, ".next/BUILD_ID");
817
+ if (existsSync2(buildId)) {
818
+ const require2 = createRequire(import.meta.url);
819
+ const nextBin = require2.resolve("next/dist/bin/next");
820
+ return spawn("node", [nextBin, "start", "--port", String(serverPort)], {
821
+ cwd: uiDir2,
822
+ stdio: "inherit",
823
+ env
824
+ });
581
825
  }
582
- const uiPackageJson = path2.resolve(uiDir2, "package.json");
583
- if (existsSync(uiPackageJson)) {
584
- const nextBin = path2.resolve(uiDir2, "../../../node_modules/next/dist/bin/next");
826
+ const uiPackageJson = path3.resolve(uiDir2, "package.json");
827
+ if (existsSync2(uiPackageJson)) {
828
+ const nextBin = path3.resolve(uiDir2, "../../../node_modules/next/dist/bin/next");
585
829
  return spawn("node", [nextBin, "dev", "--port", String(serverPort), "--webpack"], {
586
830
  cwd: uiDir2,
587
831
  stdio: "inherit",
@@ -599,8 +843,16 @@ if (opts.generateTypes) {
599
843
  await runGenerateTypes(contentsDir);
600
844
  process.exit(0);
601
845
  }
602
- var uiDir = path2.resolve(import.meta.dirname, "../cli/ui");
603
- var serverEnv = { ...process.env, STUDIO_CONTENTS_DIR: contentsDir, PORT: String(port), HOSTNAME: "0.0.0.0" };
846
+ var uiDir = path3.resolve(import.meta.dirname, "../cli/ui");
847
+ var configPath = resolveConfigPath(process.cwd());
848
+ var serverEnv = {
849
+ ...process.env,
850
+ STUDIO_CONTENTS_DIR: contentsDir,
851
+ STUDIO_PROJECT_DIR: process.cwd(),
852
+ PORT: String(port),
853
+ HOSTNAME: "0.0.0.0",
854
+ ...configPath ? { STUDIO_CONFIG_PATH: configPath } : {}
855
+ };
604
856
  var serverProcess = resolveServerProcess(uiDir, port, serverEnv);
605
857
  if (!serverProcess) {
606
858
  console.error("Error: Studio UI server not found.");