@shapio/cms 0.2.1 → 0.3.1

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 (312) hide show
  1. package/dist/admin/apple-touch-icon.png +0 -0
  2. package/dist/admin/assets/{AssistError-hVUWpP3F.js → AssistError-BYR085vj.js} +2 -2
  3. package/dist/admin/assets/{AssistError-hVUWpP3F.js.map → AssistError-BYR085vj.js.map} +1 -1
  4. package/dist/admin/assets/{BlockMenuItems-B0g93m6M.js → BlockMenuItems-BnLF3Efc.js} +2 -2
  5. package/dist/admin/assets/{BlockMenuItems-B0g93m6M.js.map → BlockMenuItems-BnLF3Efc.js.map} +1 -1
  6. package/dist/admin/assets/Body-CCOZq2q9.js +2 -0
  7. package/dist/admin/assets/{Body-CFQKONK4.js.map → Body-CCOZq2q9.js.map} +1 -1
  8. package/dist/admin/assets/Builder-BZy9mphs.js +2 -0
  9. package/dist/admin/assets/Builder-BZy9mphs.js.map +1 -0
  10. package/dist/admin/assets/{ConfirmDialog-D6rs75TP.js → ConfirmDialog-Tu-gjEwm.js} +2 -2
  11. package/dist/admin/assets/{ConfirmDialog-D6rs75TP.js.map → ConfirmDialog-Tu-gjEwm.js.map} +1 -1
  12. package/dist/admin/assets/{CopyButton-Bc7crFDE.js → CopyButton-BuBaIlCk.js} +2 -2
  13. package/dist/admin/assets/{CopyButton-Bc7crFDE.js.map → CopyButton-BuBaIlCk.js.map} +1 -1
  14. package/dist/admin/assets/{CursorPager-BFPD_adV.js → CursorPager-Bvu3CXpD.js} +2 -2
  15. package/dist/admin/assets/{CursorPager-BFPD_adV.js.map → CursorPager-Bvu3CXpD.js.map} +1 -1
  16. package/dist/admin/assets/{ExternalLink-Cdfcyyed.js → ExternalLink-CCr5oRXI.js} +2 -2
  17. package/dist/admin/assets/{ExternalLink-Cdfcyyed.js.map → ExternalLink-CCr5oRXI.js.map} +1 -1
  18. package/dist/admin/assets/Form-Doc0JEzF.js +2 -0
  19. package/dist/admin/assets/Form-Doc0JEzF.js.map +1 -0
  20. package/dist/admin/assets/{FormCheckboxGroup-Bicl1CBP.js → FormCheckboxGroup-eg39IJvf.js} +2 -2
  21. package/dist/admin/assets/{FormCheckboxGroup-Bicl1CBP.js.map → FormCheckboxGroup-eg39IJvf.js.map} +1 -1
  22. package/dist/admin/assets/{FormRadioGroup-34iVSV4L.js → FormRadioGroup-ZppSTZK6.js} +2 -2
  23. package/dist/admin/assets/{FormRadioGroup-34iVSV4L.js.map → FormRadioGroup-ZppSTZK6.js.map} +1 -1
  24. package/dist/admin/assets/{FormSelectField-Ba4fIu-8.js → FormSelectField-Cosnxiz-.js} +2 -2
  25. package/dist/admin/assets/{FormSelectField-Ba4fIu-8.js.map → FormSelectField-Cosnxiz-.js.map} +1 -1
  26. package/dist/admin/assets/{FormSwitchField-DN6bfPC0.js → FormSwitchField-DsKQEZw6.js} +2 -2
  27. package/dist/admin/assets/{FormSwitchField-DN6bfPC0.js.map → FormSwitchField-DsKQEZw6.js.map} +1 -1
  28. package/dist/admin/assets/{FormTextareaField-WsNjLm32.js → FormTextareaField-BXidSgIE.js} +2 -2
  29. package/dist/admin/assets/{FormTextareaField-WsNjLm32.js.map → FormTextareaField-BXidSgIE.js.map} +1 -1
  30. package/dist/admin/assets/{HintedLabel-jKLM0ETt.js → HintedLabel-USQku9Yt.js} +2 -2
  31. package/dist/admin/assets/{HintedLabel-jKLM0ETt.js.map → HintedLabel-USQku9Yt.js.map} +1 -1
  32. package/dist/admin/assets/{InfoHint-DBqcrmZ3.js → InfoHint-Bz_yvehr.js} +2 -2
  33. package/dist/admin/assets/{InfoHint-DBqcrmZ3.js.map → InfoHint-Bz_yvehr.js.map} +1 -1
  34. package/dist/admin/assets/{InsertMenuItem-CRKXblK5.js → InsertMenuItem-DJXglsEd.js} +2 -2
  35. package/dist/admin/assets/{InsertMenuItem-CRKXblK5.js.map → InsertMenuItem-DJXglsEd.js.map} +1 -1
  36. package/dist/admin/assets/LinkTabs-D4U-rJyK.js +2 -0
  37. package/dist/admin/assets/{LinkTabs-Bn2JVqOL.js.map → LinkTabs-D4U-rJyK.js.map} +1 -1
  38. package/dist/admin/assets/Match-UAw1Ss8a.js +2 -0
  39. package/dist/admin/assets/{Match-DaYgKrk7.js.map → Match-UAw1Ss8a.js.map} +1 -1
  40. package/dist/admin/assets/{Panel-6wFe96Gr.js → Panel-BdjqCkKs.js} +2 -2
  41. package/dist/admin/assets/{Panel-6wFe96Gr.js.map → Panel-BdjqCkKs.js.map} +1 -1
  42. package/dist/admin/assets/{QueryView-DFZtm0fn.js → QueryView-Bkyizh1P.js} +2 -2
  43. package/dist/admin/assets/{QueryView-DFZtm0fn.js.map → QueryView-Bkyizh1P.js.map} +1 -1
  44. package/dist/admin/assets/{RadioTile-DsTyz7ml.js → RadioTile-VHTPl88w.js} +2 -2
  45. package/dist/admin/assets/RadioTile-VHTPl88w.js.map +1 -0
  46. package/dist/admin/assets/{RichTextField-BGkH403g.js → RichTextField-B21FqrT5.js} +4 -4
  47. package/dist/admin/assets/{RichTextField-BGkH403g.js.map → RichTextField-B21FqrT5.js.map} +1 -1
  48. package/dist/admin/assets/{RowTitle-sFt6NjO4.js → RowTitle-BD8QPs_v.js} +2 -2
  49. package/dist/admin/assets/{RowTitle-sFt6NjO4.js.map → RowTitle-BD8QPs_v.js.map} +1 -1
  50. package/dist/admin/assets/{SecretReveal-Z6MOFdcL.js → SecretReveal-BfJKUxXW.js} +2 -2
  51. package/dist/admin/assets/{SecretReveal-Z6MOFdcL.js.map → SecretReveal-BfJKUxXW.js.map} +1 -1
  52. package/dist/admin/assets/SharedGlyph-DDPdvsm_.js +2 -0
  53. package/dist/admin/assets/SharedGlyph-DDPdvsm_.js.map +1 -0
  54. package/dist/admin/assets/{StepList-BTP_EMzx.js → StepList-DKhkw1hN.js} +2 -2
  55. package/dist/admin/assets/{StepList-BTP_EMzx.js.map → StepList-DKhkw1hN.js.map} +1 -1
  56. package/dist/admin/assets/Structure-C7dr00Bp.js +2 -0
  57. package/dist/admin/assets/{Structure-D5-hCUuF.js.map → Structure-C7dr00Bp.js.map} +1 -1
  58. package/dist/admin/assets/SubNavLink-BAF2-wuF.js +2 -0
  59. package/dist/admin/assets/{SubNavLink-C5oN5mzB.js.map → SubNavLink-BAF2-wuF.js.map} +1 -1
  60. package/dist/admin/assets/{SuggestAltButton-BuN35BBL.js → SuggestAltButton-DEsbHvs4.js} +2 -2
  61. package/dist/admin/assets/{SuggestAltButton-BuN35BBL.js.map → SuggestAltButton-DEsbHvs4.js.map} +1 -1
  62. package/dist/admin/assets/TextLink-CegO3jom.js +2 -0
  63. package/dist/admin/assets/{TextLink-B4a6j3r6.js.map → TextLink-CegO3jom.js.map} +1 -1
  64. package/dist/admin/assets/{Thumbnail-u8NE5NhQ.js → Thumbnail-CTpKlrxR.js} +2 -2
  65. package/dist/admin/assets/{Thumbnail-u8NE5NhQ.js.map → Thumbnail-CTpKlrxR.js.map} +1 -1
  66. package/dist/admin/assets/UnsavedChangesGuard-Cbfg-Bgj.js +2 -0
  67. package/dist/admin/assets/{UnsavedChangesGuard-BYwMTDsE.js.map → UnsavedChangesGuard-Cbfg-Bgj.js.map} +1 -1
  68. package/dist/admin/assets/{UserAvatar-D-yX0ozh.js → UserAvatar-CiZ00-cr.js} +2 -2
  69. package/dist/admin/assets/{UserAvatar-D-yX0ozh.js.map → UserAvatar-CiZ00-cr.js.map} +1 -1
  70. package/dist/admin/assets/{arrow-up-Ws7W9KsZ.js → arrow-up-DwMJqZVV.js} +2 -2
  71. package/dist/admin/assets/{arrow-up-Ws7W9KsZ.js.map → arrow-up-DwMJqZVV.js.map} +1 -1
  72. package/dist/admin/assets/auth-BaVAo0u_.js +2 -0
  73. package/dist/admin/assets/auth-BaVAo0u_.js.map +1 -0
  74. package/dist/admin/assets/badge-IBeVOhKd.js +2 -0
  75. package/dist/admin/assets/{badge-CMox6-v3.js.map → badge-IBeVOhKd.js.map} +1 -1
  76. package/dist/admin/assets/boxes-BH0MDegO.js +2 -0
  77. package/dist/admin/assets/boxes-BH0MDegO.js.map +1 -0
  78. package/dist/admin/assets/{calendar-clock-9W0Lkt89.js → calendar-clock-DKUk-w81.js} +2 -2
  79. package/dist/admin/assets/{calendar-clock-9W0Lkt89.js.map → calendar-clock-DKUk-w81.js.map} +1 -1
  80. package/dist/admin/assets/{changeSets-CO7JdILW.js → changeSets-o_G67qPk.js} +2 -2
  81. package/dist/admin/assets/{changeSets-CO7JdILW.js.map → changeSets-o_G67qPk.js.map} +1 -1
  82. package/dist/admin/assets/chevron-left-D259hWJn.js +2 -0
  83. package/dist/admin/assets/{chevron-left-DyqYjcMB.js.map → chevron-left-D259hWJn.js.map} +1 -1
  84. package/dist/admin/assets/{circle-check-aJ2DW5t4.js → circle-check-Cidzt1Ww.js} +2 -2
  85. package/dist/admin/assets/{circle-check-aJ2DW5t4.js.map → circle-check-Cidzt1Ww.js.map} +1 -1
  86. package/dist/admin/assets/{circle-x-BnRBht9z.js → circle-x-c1RwmC6j.js} +2 -2
  87. package/dist/admin/assets/{circle-x-BnRBht9z.js.map → circle-x-c1RwmC6j.js.map} +1 -1
  88. package/dist/admin/assets/{clock-DRtFQbTs.js → clock-BOtPNp9-.js} +2 -2
  89. package/dist/admin/assets/{clock-DRtFQbTs.js.map → clock-BOtPNp9-.js.map} +1 -1
  90. package/dist/admin/assets/cn-M4_WHiur.js +2 -0
  91. package/dist/admin/assets/cn-M4_WHiur.js.map +1 -0
  92. package/dist/admin/assets/coerce-DWI5sgLW.js +2 -0
  93. package/dist/admin/assets/{coerce-COM3rk1i.js.map → coerce-DWI5sgLW.js.map} +1 -1
  94. package/dist/admin/assets/constants-CgUdkWmf.js +2 -0
  95. package/dist/admin/assets/constants-CgUdkWmf.js.map +1 -0
  96. package/dist/admin/assets/content-D25ainqV.js +3 -0
  97. package/dist/admin/assets/content-D25ainqV.js.map +1 -0
  98. package/dist/admin/assets/{contentCounts-Ct0tYgHD.js → contentCounts-T-LmvUf5.js} +2 -2
  99. package/dist/admin/assets/{contentCounts-Ct0tYgHD.js.map → contentCounts-T-LmvUf5.js.map} +1 -1
  100. package/dist/admin/assets/contentTypes-BSlp81l1.js +2 -0
  101. package/dist/admin/assets/contentTypes-BSlp81l1.js.map +1 -0
  102. package/dist/admin/assets/{copy-B5B8RjV0.js → copy-DT7dAzVm.js} +2 -2
  103. package/dist/admin/assets/{copy-B5B8RjV0.js.map → copy-DT7dAzVm.js.map} +1 -1
  104. package/dist/admin/assets/{dateTimeLocal-BuBv3it2.js → dateTimeLocal-DRq7DuYO.js} +2 -2
  105. package/dist/admin/assets/{dateTimeLocal-BuBv3it2.js.map → dateTimeLocal-DRq7DuYO.js.map} +1 -1
  106. package/dist/admin/assets/develop-qF10t9tM.js +11 -0
  107. package/dist/admin/assets/develop-qF10t9tM.js.map +1 -0
  108. package/dist/admin/assets/{dropdown-menu-CW3fabv5.js → dropdown-menu-Cl20LCvY.js} +2 -2
  109. package/dist/admin/assets/{dropdown-menu-CW3fabv5.js.map → dropdown-menu-Cl20LCvY.js.map} +1 -1
  110. package/dist/admin/assets/{ellipsis-DNdmromA.js → ellipsis-xDmHl1SK.js} +2 -2
  111. package/dist/admin/assets/{ellipsis-DNdmromA.js.map → ellipsis-xDmHl1SK.js.map} +1 -1
  112. package/dist/admin/assets/{external-link-YnuWXDBk.js → external-link-DtJAaOWk.js} +2 -2
  113. package/dist/admin/assets/{external-link-YnuWXDBk.js.map → external-link-DtJAaOWk.js.map} +1 -1
  114. package/dist/admin/assets/{file-text-CB7Bc0GA.js → file-text-BJkQMeQO.js} +2 -2
  115. package/dist/admin/assets/{file-text-CB7Bc0GA.js.map → file-text-BJkQMeQO.js.map} +1 -1
  116. package/dist/admin/assets/fileRoute-Buucm0CU.js +2 -0
  117. package/dist/admin/assets/{fileRoute-BYwireXG.js.map → fileRoute-Buucm0CU.js.map} +1 -1
  118. package/dist/admin/assets/followPlural-Cuocm4Sv.js +2 -0
  119. package/dist/admin/assets/followPlural-Cuocm4Sv.js.map +1 -0
  120. package/dist/admin/assets/{globe-Bvs3UC3V.js → globe-CAhrwBgv.js} +2 -2
  121. package/dist/admin/assets/{globe-Bvs3UC3V.js.map → globe-CAhrwBgv.js.map} +1 -1
  122. package/dist/admin/assets/{image-CeoxwJeM.js → image-CGN6ffEu.js} +2 -2
  123. package/dist/admin/assets/{image-CeoxwJeM.js.map → image-CGN6ffEu.js.map} +1 -1
  124. package/dist/admin/assets/{image-off-iVqYw2Zc.js → image-off-0zX_kODH.js} +2 -2
  125. package/dist/admin/assets/{image-off-iVqYw2Zc.js.map → image-off-0zX_kODH.js.map} +1 -1
  126. package/dist/admin/assets/inbox-CWq1hHqK.js +2 -0
  127. package/dist/admin/assets/{inbox-wDoqym--.js.map → inbox-CWq1hHqK.js.map} +1 -1
  128. package/dist/admin/assets/{inbox-DYbEOpaQ.js → inbox-CukkApHK.js} +2 -2
  129. package/dist/admin/assets/{inbox-DYbEOpaQ.js.map → inbox-CukkApHK.js.map} +1 -1
  130. package/dist/admin/assets/index-C9uJxg4J.js +4 -0
  131. package/dist/admin/assets/index-C9uJxg4J.js.map +1 -0
  132. package/dist/admin/assets/index-Cz3C-LxB.css +2 -0
  133. package/dist/admin/assets/{key-round-Bcw6qqcb.js → key-round-DnE-2gr0.js} +2 -2
  134. package/dist/admin/assets/{key-round-Bcw6qqcb.js.map → key-round-DnE-2gr0.js.map} +1 -1
  135. package/dist/admin/assets/link-2-off-Cbpvb3w4.js +2 -0
  136. package/dist/admin/assets/link-2-off-Cbpvb3w4.js.map +1 -0
  137. package/dist/admin/assets/{link-CKr_uUfb.js → link-DPniuk00.js} +2 -2
  138. package/dist/admin/assets/{link-CKr_uUfb.js.map → link-DPniuk00.js.map} +1 -1
  139. package/dist/admin/assets/{list-BElvKZw_.js → list-CkHF_4xh.js} +2 -2
  140. package/dist/admin/assets/{list-BElvKZw_.js.map → list-CkHF_4xh.js.map} +1 -1
  141. package/dist/admin/assets/{locales-Bnv3qcdZ.js → locales-DFLQ17ow.js} +2 -2
  142. package/dist/admin/assets/{locales-Bnv3qcdZ.js.map → locales-DFLQ17ow.js.map} +1 -1
  143. package/dist/admin/assets/{media-BfDqe_2P.js → media-CKTs3j83.js} +2 -2
  144. package/dist/admin/assets/{media-BfDqe_2P.js.map → media-CKTs3j83.js.map} +1 -1
  145. package/dist/admin/assets/media-CUfV74JZ.js +2 -0
  146. package/dist/admin/assets/{media-DjK3xGFN.js.map → media-CUfV74JZ.js.map} +1 -1
  147. package/dist/admin/assets/network-Fri1cjMt.js +2 -0
  148. package/dist/admin/assets/network-Fri1cjMt.js.map +1 -0
  149. package/dist/admin/assets/{pencil-Zd05MDs2.js → pencil-BqAgKC9v.js} +2 -2
  150. package/dist/admin/assets/{pencil-Zd05MDs2.js.map → pencil-BqAgKC9v.js.map} +1 -1
  151. package/dist/admin/assets/{plus-B0gd63Da.js → plus-DLRcpVhJ.js} +2 -2
  152. package/dist/admin/assets/{plus-B0gd63Da.js.map → plus-DLRcpVhJ.js.map} +1 -1
  153. package/dist/admin/assets/{preload-helper-CJKHb_rG.js → preload-helper-BHNBIF88.js} +2 -2
  154. package/dist/admin/assets/{preload-helper-CJKHb_rG.js.map → preload-helper-BHNBIF88.js.map} +1 -1
  155. package/dist/admin/assets/publishing-DaaE8PaU.js +2 -0
  156. package/dist/admin/assets/{publishing-vCVjaSGF.js.map → publishing-DaaE8PaU.js.map} +1 -1
  157. package/dist/admin/assets/{radio-group-DVR0mTpo.js → radio-group-CA3XlQcc.js} +2 -2
  158. package/dist/admin/assets/{radio-group-DVR0mTpo.js.map → radio-group-CA3XlQcc.js.map} +1 -1
  159. package/dist/admin/assets/{rotate-ccw-BOTDOdHf.js → rotate-ccw-CfrkcgYa.js} +2 -2
  160. package/dist/admin/assets/{rotate-ccw-BOTDOdHf.js.map → rotate-ccw-CfrkcgYa.js.map} +1 -1
  161. package/dist/admin/assets/{schedules-CG-ZphRK.js → schedules-hJQkjEhN.js} +2 -2
  162. package/dist/admin/assets/{schedules-CG-ZphRK.js.map → schedules-hJQkjEhN.js.map} +1 -1
  163. package/dist/admin/assets/schema--xSR7cf9.js +2 -0
  164. package/dist/admin/assets/schema--xSR7cf9.js.map +1 -0
  165. package/dist/admin/assets/{scroll-text-D-gHqYwR.js → scroll-text-BA9yh7Za.js} +2 -2
  166. package/dist/admin/assets/{scroll-text-D-gHqYwR.js.map → scroll-text-BA9yh7Za.js.map} +1 -1
  167. package/dist/admin/assets/{search-BcPL1qyc.js → search--71HzPvh.js} +2 -2
  168. package/dist/admin/assets/{search-BcPL1qyc.js.map → search--71HzPvh.js.map} +1 -1
  169. package/dist/admin/assets/searchSchema-Ypl6gq9g.js +2 -0
  170. package/dist/admin/assets/{searchSchema-BGrRDBL_.js.map → searchSchema-Ypl6gq9g.js.map} +1 -1
  171. package/dist/admin/assets/searchSchemas-C-nQbyhd.js +2 -0
  172. package/dist/admin/assets/searchSchemas-C-nQbyhd.js.map +1 -0
  173. package/dist/admin/assets/sections-BBN2JErq.js +2 -0
  174. package/dist/admin/assets/sections-BBN2JErq.js.map +1 -0
  175. package/dist/admin/assets/{sections-i9TeeK-h.js → sections-D0OZozD2.js} +2 -2
  176. package/dist/admin/assets/{sections-i9TeeK-h.js.map → sections-D0OZozD2.js.map} +1 -1
  177. package/dist/admin/assets/{select--nGB4WjU.js → select-De48SQdZ.js} +2 -2
  178. package/dist/admin/assets/{select--nGB4WjU.js.map → select-De48SQdZ.js.map} +1 -1
  179. package/dist/admin/assets/{send-Bu3RRfdI.js → send-NuSmLVSD.js} +2 -2
  180. package/dist/admin/assets/{send-Bu3RRfdI.js.map → send-NuSmLVSD.js.map} +1 -1
  181. package/dist/admin/assets/settings-CYXU73dP.js +2 -0
  182. package/dist/admin/assets/settings-CYXU73dP.js.map +1 -0
  183. package/dist/admin/assets/skeleton-CCwdO9oq.js +42 -0
  184. package/dist/admin/assets/skeleton-CCwdO9oq.js.map +1 -0
  185. package/dist/admin/assets/snapshots-BQeFK5cj.js +2 -0
  186. package/dist/admin/assets/{snapshots-CKjK-vkL.js.map → snapshots-BQeFK5cj.js.map} +1 -1
  187. package/dist/admin/assets/{sparkles-B_NX7PAY.js → sparkles-B-0I44es.js} +2 -2
  188. package/dist/admin/assets/{sparkles-B_NX7PAY.js.map → sparkles-B-0I44es.js.map} +1 -1
  189. package/dist/admin/assets/src-CQu-YxUM.js +4 -0
  190. package/dist/admin/assets/src-CQu-YxUM.js.map +1 -0
  191. package/dist/admin/assets/{stableId-Dmr2Afn_.js → stableId-rPnYVF-A.js} +2 -2
  192. package/dist/admin/assets/{stableId-Dmr2Afn_.js.map → stableId-rPnYVF-A.js.map} +1 -1
  193. package/dist/admin/assets/{store-B5xrQ1Ex.js → store-Bqd9L9DK.js} +2 -2
  194. package/dist/admin/assets/{store-B5xrQ1Ex.js.map → store-Bqd9L9DK.js.map} +1 -1
  195. package/dist/admin/assets/{switch-B-FbT-l8.js → switch-JmfyFAQG.js} +2 -2
  196. package/dist/admin/assets/{switch-B-FbT-l8.js.map → switch-JmfyFAQG.js.map} +1 -1
  197. package/dist/admin/assets/{table-DQzCPm7J.js → table-BEDv9IRM.js} +2 -2
  198. package/dist/admin/assets/{table-DQzCPm7J.js.map → table-BEDv9IRM.js.map} +1 -1
  199. package/dist/admin/assets/tabs-DSS1c-Kf.js +5 -0
  200. package/dist/admin/assets/{tabs-YThoOiE3.js.map → tabs-DSS1c-Kf.js.map} +1 -1
  201. package/dist/admin/assets/{textarea-_wcx8Mn1.js → textarea-eatLAWE_.js} +2 -2
  202. package/dist/admin/assets/{textarea-_wcx8Mn1.js.map → textarea-eatLAWE_.js.map} +1 -1
  203. package/dist/admin/assets/titles-Bi2fWNN4.js +2 -0
  204. package/dist/admin/assets/{titles-DyCBLXue.js.map → titles-Bi2fWNN4.js.map} +1 -1
  205. package/dist/admin/assets/{tokens-CqsxrApC.js → tokens-C2E7swse.js} +2 -2
  206. package/dist/admin/assets/{tokens-CqsxrApC.js.map → tokens-C2E7swse.js.map} +1 -1
  207. package/dist/admin/assets/{tooltip-BMe4HylJ.js → tooltip-Bji9fmSr.js} +2 -2
  208. package/dist/admin/assets/{tooltip-BMe4HylJ.js.map → tooltip-Bji9fmSr.js.map} +1 -1
  209. package/dist/admin/assets/{undo-2-DMOHOH6w.js → undo-2-BU-uqm7e.js} +2 -2
  210. package/dist/admin/assets/{undo-2-DMOHOH6w.js.map → undo-2-BU-uqm7e.js.map} +1 -1
  211. package/dist/admin/assets/{useAuditLabels-CSw_kwLs.js → useAuditLabels-D5nmbXqN.js} +2 -2
  212. package/dist/admin/assets/{useAuditLabels-CSw_kwLs.js.map → useAuditLabels-D5nmbXqN.js.map} +1 -1
  213. package/dist/admin/assets/useConfirmTarget-Y7_8kVX5.js +2 -0
  214. package/dist/admin/assets/useConfirmTarget-Y7_8kVX5.js.map +1 -0
  215. package/dist/admin/assets/useContentSchema-DJTqjlUk.js +2 -0
  216. package/dist/admin/assets/{useContentSchema-CS5QQcRk.js.map → useContentSchema-DJTqjlUk.js.map} +1 -1
  217. package/dist/admin/assets/{useCursorPager-Dt_axwd1.js → useCursorPager-CiJroOo7.js} +2 -2
  218. package/dist/admin/assets/{useCursorPager-Dt_axwd1.js.map → useCursorPager-CiJroOo7.js.map} +1 -1
  219. package/dist/admin/assets/{useDiscardGuard-7rRGpd4q.js → useDiscardGuard-t_N3l9TQ.js} +2 -2
  220. package/dist/admin/assets/{useDiscardGuard-7rRGpd4q.js.map → useDiscardGuard-t_N3l9TQ.js.map} +1 -1
  221. package/dist/admin/assets/{useMediaUploader-CBUcNW6d.js → useMediaUploader-CLHmKCwq.js} +2 -2
  222. package/dist/admin/assets/{useMediaUploader-CBUcNW6d.js.map → useMediaUploader-CLHmKCwq.js.map} +1 -1
  223. package/dist/admin/assets/{usePaletteItems-bbTIqpPu.js → usePaletteItems-BVjWCdow.js} +2 -2
  224. package/dist/admin/assets/{usePaletteItems-bbTIqpPu.js.map → usePaletteItems-BVjWCdow.js.map} +1 -1
  225. package/dist/admin/assets/usePlaces-CnSOZwtO.js +2 -0
  226. package/dist/admin/assets/usePlaces-CnSOZwtO.js.map +1 -0
  227. package/dist/admin/assets/{useQueries-DKGIdq6A.js → useQueries-BRolGUiw.js} +2 -2
  228. package/dist/admin/assets/{useQueries-DKGIdq6A.js.map → useQueries-BRolGUiw.js.map} +1 -1
  229. package/dist/admin/assets/useSchemaScopeAccess-bd8RQxuu.js +2 -0
  230. package/dist/admin/assets/useSchemaScopeAccess-bd8RQxuu.js.map +1 -0
  231. package/dist/admin/assets/{users-C1ZSee3N.js → users-CbErtM7w.js} +2 -2
  232. package/dist/admin/assets/{users-C1ZSee3N.js.map → users-CbErtM7w.js.map} +1 -1
  233. package/dist/admin/assets/{users-DMfQL0s4.js → users-bDGYpSXg.js} +2 -2
  234. package/dist/admin/assets/{users-DMfQL0s4.js.map → users-bDGYpSXg.js.map} +1 -1
  235. package/dist/admin/assets/{zod-Pf5A-ax4.js → zod-Gtw4e0qi.js} +2 -2
  236. package/dist/admin/assets/{zod-Pf5A-ax4.js.map → zod-Gtw4e0qi.js.map} +1 -1
  237. package/dist/admin/favicon-32.png +0 -0
  238. package/dist/admin/favicon.svg +2 -2
  239. package/dist/admin/index.html +61 -56
  240. package/dist/admin/theme-init.js +41 -9
  241. package/dist/cli.js +412 -168
  242. package/dist/cli.js.map +1 -1
  243. package/dist/config.d.ts +2 -2
  244. package/dist/config.js.map +1 -1
  245. package/dist/index.d.ts +15 -223
  246. package/dist/index.js +1 -1
  247. package/dist/{public-CKU0Gkfq.d.ts → public-B7fFHMNr.d.ts} +259 -5
  248. package/dist/{worker-C-MSYr4l.js → worker-BAmQPoTJ.js} +2867 -1314
  249. package/dist/worker-BAmQPoTJ.js.map +1 -0
  250. package/package.json +5 -6
  251. package/dist/admin/assets/Body-CFQKONK4.js +0 -2
  252. package/dist/admin/assets/Builder-Do5KVHV9.js +0 -2
  253. package/dist/admin/assets/Builder-Do5KVHV9.js.map +0 -1
  254. package/dist/admin/assets/LinkTabs-Bn2JVqOL.js +0 -2
  255. package/dist/admin/assets/Match-DaYgKrk7.js +0 -2
  256. package/dist/admin/assets/RadioTile-DsTyz7ml.js.map +0 -1
  257. package/dist/admin/assets/Structure-D5-hCUuF.js +0 -2
  258. package/dist/admin/assets/SubNavLink-C5oN5mzB.js +0 -2
  259. package/dist/admin/assets/TextLink-B4a6j3r6.js +0 -2
  260. package/dist/admin/assets/UnsavedChangesGuard-BYwMTDsE.js +0 -2
  261. package/dist/admin/assets/auth-b3_9QbtL.js +0 -2
  262. package/dist/admin/assets/auth-b3_9QbtL.js.map +0 -1
  263. package/dist/admin/assets/badge-CMox6-v3.js +0 -2
  264. package/dist/admin/assets/chevron-left-DyqYjcMB.js +0 -2
  265. package/dist/admin/assets/cn-DnXL75B0.js +0 -2
  266. package/dist/admin/assets/cn-DnXL75B0.js.map +0 -1
  267. package/dist/admin/assets/coerce-COM3rk1i.js +0 -2
  268. package/dist/admin/assets/constants-CS1ziZO0.js +0 -2
  269. package/dist/admin/assets/constants-CS1ziZO0.js.map +0 -1
  270. package/dist/admin/assets/content-Dic2fof_.js +0 -3
  271. package/dist/admin/assets/content-Dic2fof_.js.map +0 -1
  272. package/dist/admin/assets/contentTypes-D4_Ax9rb.js +0 -2
  273. package/dist/admin/assets/contentTypes-D4_Ax9rb.js.map +0 -1
  274. package/dist/admin/assets/develop-Brnb5YPC.js +0 -11
  275. package/dist/admin/assets/develop-Brnb5YPC.js.map +0 -1
  276. package/dist/admin/assets/errors-DfoXUVDI.js +0 -2
  277. package/dist/admin/assets/errors-DfoXUVDI.js.map +0 -1
  278. package/dist/admin/assets/fileRoute-BYwireXG.js +0 -2
  279. package/dist/admin/assets/inbox-wDoqym--.js +0 -2
  280. package/dist/admin/assets/index-57Mn6XLO.js +0 -4
  281. package/dist/admin/assets/index-57Mn6XLO.js.map +0 -1
  282. package/dist/admin/assets/index-BLYOgCKc.css +0 -2
  283. package/dist/admin/assets/link-2-DhM2vi4v.js +0 -2
  284. package/dist/admin/assets/link-2-DhM2vi4v.js.map +0 -1
  285. package/dist/admin/assets/link-2-off-DpDOMsa-.js +0 -2
  286. package/dist/admin/assets/link-2-off-DpDOMsa-.js.map +0 -1
  287. package/dist/admin/assets/media-DjK3xGFN.js +0 -2
  288. package/dist/admin/assets/network-CAJ76ILr.js +0 -2
  289. package/dist/admin/assets/network-CAJ76ILr.js.map +0 -1
  290. package/dist/admin/assets/publishing-vCVjaSGF.js +0 -2
  291. package/dist/admin/assets/schema-KXXSPVJn.js +0 -2
  292. package/dist/admin/assets/schema-KXXSPVJn.js.map +0 -1
  293. package/dist/admin/assets/searchSchema-BGrRDBL_.js +0 -2
  294. package/dist/admin/assets/searchSchemas-ChN9hhuQ.js +0 -2
  295. package/dist/admin/assets/searchSchemas-ChN9hhuQ.js.map +0 -1
  296. package/dist/admin/assets/sections-BrGcWOnw.js +0 -2
  297. package/dist/admin/assets/sections-BrGcWOnw.js.map +0 -1
  298. package/dist/admin/assets/settings-68BAxrPM.js +0 -2
  299. package/dist/admin/assets/settings-68BAxrPM.js.map +0 -1
  300. package/dist/admin/assets/skeleton-DUvclBAr.js +0 -42
  301. package/dist/admin/assets/skeleton-DUvclBAr.js.map +0 -1
  302. package/dist/admin/assets/snapshots-CKjK-vkL.js +0 -2
  303. package/dist/admin/assets/src-D6DABe5J.js +0 -4
  304. package/dist/admin/assets/src-D6DABe5J.js.map +0 -1
  305. package/dist/admin/assets/tabs-YThoOiE3.js +0 -5
  306. package/dist/admin/assets/titles-DyCBLXue.js +0 -2
  307. package/dist/admin/assets/useConfirmTarget-9JmfiY2X.js +0 -2
  308. package/dist/admin/assets/useConfirmTarget-9JmfiY2X.js.map +0 -1
  309. package/dist/admin/assets/useContentSchema-CS5QQcRk.js +0 -2
  310. package/dist/admin/assets/usePlaces-D1raoQ2s.js +0 -2
  311. package/dist/admin/assets/usePlaces-D1raoQ2s.js.map +0 -1
  312. package/dist/worker-C-MSYr4l.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as update, B as parseLockFile, C as BEFORE_EVENTS, D as listForAssets, E as buildEditorManifest, F as RESERVED_FIELD_API_KEYS, G as validateSchema, H as serializeLockFile, I as RESERVED_TYPE_NAMES, J as createStableId, K as RICHTEXT_FORMAT, L as foldApiKey, M as describeError, O as listLiveByDriver, P as RESERVED_DEFINITION_API_KEYS, R as hashDefinition, S as AFTER_EVENTS, T as EXTENSION_ROUTE_ROOT, U as suggestPlural, V as serializeDefinition, W as toTypeName, X as createDb, Y as validateRichText, Z as sqliteDriverOf, _ as createLogger, a as loadConfig, b as SITE_KEY_PATTERN, c as getPendingMigrations, d as createMediaStorage, f as createSite, g as generateToken, h as SYSTEM_CLI_ACTOR, i as assertDatabaseReachable, j as STORAGE_DRIVERS, k as lockById, l as migrateToLatest, m as createAdminDirect, n as startServer, o as loadEnvFileIfPresent, p as listAllSites, q as SAFE_LINK_PATTERN, r as copyObjectVerified, t as startDedicatedWorker, u as SHAPIO_VERSION, v as createUrlBuilder, w as EXTENSION_JOB_PREFIX, x as loadProjectConfig, y as loopbackUrl, z as parseDefinition } from "./worker-C-MSYr4l.js";
2
+ import { $ as toTypeName, A as update, B as checkThemeContrast, C as BEFORE_EVENTS, D as listForAssets, E as buildEditorManifest, F as RESERVED_FIELD_API_KEYS, G as mergePulledLock, H as hashDefinition, I as RESERVED_TYPE_NAMES, J as schemaFilePath, K as parseDefinition, L as SCHEMA_KIND_DIRECTORIES, M as describeError, O as listLiveByDriver, P as RESERVED_DEFINITION_API_KEYS, Q as suggestPlural, R as SITES_DIRECTORY, S as AFTER_EVENTS, T as EXTENSION_ROUTE_ROOT, U as lockCoversSite, V as foldApiKey, W as lockEntriesForSite, X as serializeDefinition, Y as scopeDirectory, Z as serializeLockFile, _ as createLogger, a as loadConfig, at as createStableId, b as SITE_KEY_PATTERN, c as getPendingMigrations, ct as sqliteDriverOf, d as createMediaStorage, et as upgradeLockFile, f as createSite, g as generateToken, h as SYSTEM_CLI_ACTOR, i as assertDatabaseReachable, it as SAFE_LINK_PATTERN, j as STORAGE_DRIVERS, k as lockById, l as migrateToLatest, m as createAdminDirect, n as startServer, nt as withSiteCovered, o as loadEnvFileIfPresent, ot as validateRichText, p as listAllSites, q as parseLockFile, r as copyObjectVerified, rt as RICHTEXT_FORMAT, st as createDb, t as startDedicatedWorker, tt as validateSchema, u as SHAPIO_VERSION, v as createUrlBuilder, w as EXTENSION_JOB_PREFIX, x as loadProjectConfig, y as loopbackUrl, z as THEME_VARIANTS } from "./worker-BAmQPoTJ.js";
3
3
  import { createDecipheriv, createHash, scryptSync } from "node:crypto";
4
4
  import { createReadStream, createWriteStream, existsSync, openAsBlob } from "node:fs";
5
5
  import { basename, dirname, extname, isAbsolute, join, posix, relative, resolve } from "node:path";
@@ -17,7 +17,7 @@ import { parseFragment } from "parse5";
17
17
  import { marked } from "marked";
18
18
  import { extract } from "tar";
19
19
  import { SaxesParser } from "saxes";
20
- //#region ../../packages/client/dist/errors-BXMW0adL.js
20
+ //#region ../../packages/client/dist/errors-DAG8eK8c.js
21
21
  /** Every admin route the client calls (packages B, D, F and G, `apps/api/src/routes/admin/**`). */
22
22
  const ADMIN_PATHS = {
23
23
  setup: "/api/admin/setup",
@@ -54,6 +54,7 @@ const ADMIN_PATHS = {
54
54
  contentHealth: "/api/admin/content-health",
55
55
  presence: "/api/admin/presence",
56
56
  editorManifest: "/api/admin/extensions/editors",
57
+ extensionThemes: "/api/admin/extensions/themes",
57
58
  jobs: "/api/admin/jobs",
58
59
  schedules: "/api/admin/publishing/schedules",
59
60
  changeSets: "/api/admin/change-sets",
@@ -355,9 +356,12 @@ const createContentApi = (request) => ({
355
356
  body: { expectedVersion }
356
357
  })
357
358
  },
358
- extensions: {
359
- /** Custom field editors the project installed (`shapio.config` + `extensions/editors/*.js`). */
360
- editors: () => request(ADMIN_PATHS.editorManifest) }
359
+ extensions: {
360
+ /** Custom field editors the project installed (`shapio.config` + `extensions/editors/*.js`). */
361
+ editors: () => request(ADMIN_PATHS.editorManifest),
362
+ /** Admin colour themes the project declares (`shapio.config` `themes`). Public: no session needed. */
363
+ themes: () => request(ADMIN_PATHS.extensionThemes)
364
+ }
361
365
  });
362
366
  const entryPath = (base, modelKey, id) => withId(withId(base, modelKey), id);
363
367
  /** The entry document's endpoints: pre-flight, content health (the Inbox), presence and counts. */
@@ -472,7 +476,8 @@ const DEFINITION_PATHS = {
472
476
  const createDefinitionApi = (request, category) => {
473
477
  const base = DEFINITION_PATHS[category];
474
478
  return {
475
- list: async () => (await request(base)).items,
479
+ /** The site's view (shared definitions and its own); `{ scope: 'network' }` lists the shared ones. */
480
+ list: async (options = {}) => (await request(`${base}${toQueryString(options)}`)).items,
476
481
  get: (id) => request(withId(base, id)),
477
482
  /** Creates and activates a definition (`pending` when prerequisites must run first). */
478
483
  create: (body) => request(base, {
@@ -480,9 +485,17 @@ const createDefinitionApi = (request, category) => {
480
485
  body
481
486
  }),
482
487
  /** Plan preview of a create: classification and impact, nothing written. */
483
- planCreate: (definition) => request(`${base}/plan`, {
488
+ planCreate: (definition, scope) => request(`${base}/plan`, {
484
489
  method: "POST",
485
- body: { definition }
490
+ body: scope ? {
491
+ definition,
492
+ scope
493
+ } : { definition }
494
+ }),
495
+ /** Shares a definition with all sites, or keeps it on one (needs schema.create on every site). */
496
+ changeScope: (id, body) => request(`${withId(base, id)}/scope`, {
497
+ method: "PUT",
498
+ body
486
499
  }),
487
500
  /** Changes a definition; 409 SCHEMA_VERSION_CONFLICT when `expectedVersion` is stale. */
488
501
  update: (id, body) => request(withId(base, id), {
@@ -508,7 +521,7 @@ const createSchemaApi = (request) => ({
508
521
  summary: () => request(ADMIN_PATHS.schema),
509
522
  change: (changeId) => request(withId(ADMIN_PATHS.schemaChanges, changeId)),
510
523
  /** Every definition in the pull format, with the schema version they come from (`shapio schema pull`). */
511
- export: () => request(ADMIN_PATHS.schemaExport),
524
+ export: (options = {}) => request(`${ADMIN_PATHS.schemaExport}${toQueryString(options)}`),
512
525
  /** Applies definition files live through the change planner; refuses when the base moved (409). */
513
526
  apply: (body) => request(ADMIN_PATHS.schemaApply, {
514
527
  method: "POST",
@@ -2802,41 +2815,48 @@ const writeOrder = (entries) => {
2802
2815
  deferred
2803
2816
  };
2804
2817
  };
2805
- /** Schema files: one canonical JSON file per definition, `models/<apiKey>.json` or `components/<apiKey>.json`. */
2806
- const KIND_DIRECTORIES = ["models", "components"];
2807
- const definitionPath = (dir, definition) => join(dir, definition.kind === "component" ? "components" : "models", `${definition.apiKey}.json`);
2818
+ /** The file of a definition in a scope (`site`: its site key, null when shared). */
2819
+ const definitionPath = (dir, definition, site) => join(dir, ...schemaFilePath(definition, site).split("/"));
2808
2820
  const isMissing = (error) => error.code === "ENOENT";
2809
- /** Every schema file under `dir`, with its ID and canonical hash when it parses. */
2810
- const readLocalFiles = async (dir) => {
2811
- const files = [];
2812
- for (const kindDir of KIND_DIRECTORIES) {
2813
- let names;
2814
- try {
2815
- names = (await readdir(join(dir, kindDir))).filter((name) => name.endsWith(".json")).sort();
2816
- } catch (error) {
2817
- if (isMissing(error)) continue;
2818
- throw error;
2819
- }
2820
- for (const name of names) {
2821
- const path = join(dir, kindDir, name);
2822
- let raw;
2823
- try {
2824
- raw = JSON.parse(await readFile(path, "utf8"));
2825
- } catch (error) {
2826
- throw new Error(`${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
2827
- }
2828
- const parsed = parseDefinition(raw);
2829
- files.push(parsed.ok ? {
2830
- path,
2831
- raw,
2832
- id: parsed.definition.id,
2833
- hash: await hashDefinition(parsed.definition)
2834
- } : {
2835
- path,
2836
- raw
2837
- });
2838
- }
2821
+ const listNames = async (path, filter) => {
2822
+ try {
2823
+ return (await readdir(path, { withFileTypes: true })).filter(filter).map((entry) => entry.name).sort();
2824
+ } catch (error) {
2825
+ if (isMissing(error)) return [];
2826
+ throw error;
2839
2827
  }
2828
+ };
2829
+ const readLocalFile = async (path, site) => {
2830
+ let raw;
2831
+ try {
2832
+ raw = JSON.parse(await readFile(path, "utf8"));
2833
+ } catch (error) {
2834
+ throw new Error(`${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
2835
+ }
2836
+ const parsed = parseDefinition(raw);
2837
+ return parsed.ok ? {
2838
+ path,
2839
+ raw,
2840
+ site,
2841
+ id: parsed.definition.id,
2842
+ hash: await hashDefinition(parsed.definition)
2843
+ } : {
2844
+ path,
2845
+ raw,
2846
+ site
2847
+ };
2848
+ };
2849
+ /** The files of one scope's folder (`models/` and `components/` under it). */
2850
+ const readScopeFiles = async (dir, site) => {
2851
+ const base = join(dir, ...scopeDirectory(site).split("/").filter(Boolean));
2852
+ const files = [];
2853
+ for (const kindDir of SCHEMA_KIND_DIRECTORIES) for (const name of await listNames(join(base, kindDir), (entry) => entry.isFile() && entry.name.endsWith(".json"))) files.push(await readLocalFile(join(base, kindDir, name), site));
2854
+ return files;
2855
+ };
2856
+ /** Every schema file under `dir`: the shared folders first, then each site folder by key. */
2857
+ const readLocalFiles = async (dir) => {
2858
+ const files = await readScopeFiles(dir, null);
2859
+ for (const siteKey of await listNames(join(dir, SITES_DIRECTORY), (entry) => entry.isDirectory() && !entry.name.startsWith("."))) files.push(...await readScopeFiles(dir, siteKey));
2840
2860
  return files;
2841
2861
  };
2842
2862
  const readLockFile = async (path) => {
@@ -2851,8 +2871,8 @@ const writeLockFile = async (path, lock) => {
2851
2871
  await mkdir(dirname(path), { recursive: true });
2852
2872
  await writeFile(path, serializeLockFile(lock));
2853
2873
  };
2854
- const writeDefinitionFile = async (dir, definition) => {
2855
- const path = definitionPath(dir, definition);
2874
+ const writeDefinitionFile = async (dir, definition, site) => {
2875
+ const path = definitionPath(dir, definition, site);
2856
2876
  await mkdir(dirname(path), { recursive: true });
2857
2877
  await writeFile(path, serializeDefinition(definition));
2858
2878
  return path;
@@ -2905,23 +2925,103 @@ const formatItem = (item, names) => {
2905
2925
  ].filter(Boolean);
2906
2926
  return [`${item.kind === "component" ? "component" : "model"} ${item.apiKey}: ${describeDecision(item)}${flags.length ? ` [${flags.join(", ")}]` : ""}`, ...item.changes.map((change) => ` ${formatChange(change, names)}`)].join("\n");
2907
2927
  };
2908
- /** Explains a refused apply: conflicts with diffs, invalid files, missing acknowledgements. */
2928
+ const FOLDER_OF = {
2929
+ network: "the shared folders",
2930
+ site: "the site’s folder"
2931
+ };
2932
+ const PLACE_OF = {
2933
+ network: "shared",
2934
+ site: "on one site"
2935
+ };
2936
+ const SCOPE_OF = {
2937
+ network: "shared",
2938
+ site: "site"
2939
+ };
2940
+ /** One line per item of a scope refusal (`SCOPE_MISMATCH`, `FORBIDDEN_SCOPE`). */
2941
+ const scopeItemLines = (code, items) => items.map((item) => {
2942
+ const name = item.apiKey ?? item.definitionId ?? "?";
2943
+ return code === "SCOPE_MISMATCH" ? ` ${name}: its file is in ${FOLDER_OF[item.local ?? ""] ?? "?"}, but on the instance it is ${PLACE_OF[item.remote ?? ""] ?? "?"}` : ` ${name} (${SCOPE_OF[item.scope ?? ""] ?? item.scope ?? "?"}): ${item.message ?? "not allowed"}`;
2944
+ });
2945
+ /** What to do next, per refusal. */
2946
+ const HINTS = {
2947
+ SCHEMA_CHANGE_NOT_ACKNOWLEDGED: "Review the changes with `shapio schema diff`, then re-run with --allow-breaking and/or --allow-destructive.",
2948
+ SCHEMA_SYNC_CONFLICT: "Commit your files, run `shapio schema pull --force`, reconcile with git, then apply again.",
2949
+ SCOPE_MISMATCH: "Sync never moves a definition between sites: run `shapio schema pull` to get the instance’s layout, or change the scope with `shapio schema scope`.",
2950
+ FORBIDDEN_SCOPE: "Shared definitions (in models/ and components/) need schema permission on every site: apply with a network token, or leave those files unchanged."
2951
+ };
2952
+ const issueWhere = (found, filePaths) => {
2953
+ const match = /^\/definitions\/(\d+)(.*)$/.exec(found.path ?? "");
2954
+ const where = match ? `${filePaths[Number(match[1])] ?? `definition ${match[1]}`} ${match[2] || "/"}` : found.path ?? "";
2955
+ return found.siteKey ? `${where} (site ${found.siteKey})` : where;
2956
+ };
2957
+ const siteCountLines = (sites) => (sites ?? []).flatMap((site) => typeof site === "object" && site.entries !== void 0 ? [` site ${site.key ?? site.siteId ?? "?"}: ${site.entries} entr${site.entries === 1 ? "y" : "ies"}`] : []);
2958
+ /** Explains a refused request: conflicts with diffs, invalid files, scope refusals, missing acknowledgements. */
2909
2959
  const formatApiError = (error, filePaths, names) => {
2910
2960
  const details = error.details ?? {};
2911
2961
  const lines = [`${error.code}: ${error.message}`];
2912
2962
  for (const conflict of details.conflicts ?? []) lines.push(formatItem(conflict, names));
2913
- for (const found of details.issues ?? []) {
2914
- const match = /^\/definitions\/(\d+)(.*)$/.exec(found.path ?? "");
2915
- const where = match ? `${filePaths[Number(match[1])] ?? `definition ${match[1]}`} ${match[2] || "/"}` : found.path ?? "";
2916
- lines.push(` ${where}: ${found.message ?? ""}`);
2917
- }
2918
- if (error.code === "SCHEMA_CHANGE_NOT_ACKNOWLEDGED") lines.push("Review the changes with `shapio schema diff`, then re-run with --allow-breaking and/or --allow-destructive.");
2919
- if (error.code === "SCHEMA_SYNC_CONFLICT") lines.push("Commit your files, run `shapio schema pull --force`, reconcile with git, then apply again.");
2963
+ for (const found of details.issues ?? []) lines.push(` ${issueWhere(found, filePaths)}: ${found.message ?? ""}`);
2964
+ lines.push(...scopeItemLines(error.code, details.items ?? []));
2965
+ if (error.code === "SCOPE_IN_USE") lines.push(...siteCountLines(details.sites));
2966
+ const hint = HINTS[error.code];
2967
+ if (hint) lines.push(hint);
2920
2968
  return `${lines.join("\n")}\n`;
2921
2969
  };
2970
+ /**
2971
+ * Which part of a schema tree one site's sync is about (plan site-schema): the shared folders and that site's
2972
+ * own folder. One tree may hold several sites (`sites/<key>/`); a pull or apply for one never reads, sends,
2973
+ * rewrites or removes another site's files or lock entries.
2974
+ */
2975
+ const inView = (site, siteKey) => site === null || site === siteKey;
2976
+ /** The files an apply for `siteKey` sends: the shared ones and that site's. */
2977
+ const filesForSite = (files, siteKey) => files.filter((file) => inView(file.site, siteKey));
2978
+ /** Each sent file's scope, parallel to the files: its folder decides (only creates use it). */
2979
+ const scopesOf = (files) => files.map((file) => file.site === null ? "network" : "site");
2980
+ /**
2981
+ * The base an apply for `siteKey` sends: the lock in format 2 with only the shared entries and that site's,
2982
+ * so neither the three-way decision nor `--prune` can ever see another site's IDs. `sites` is kept: the
2983
+ * server refuses a tree that covers other sites only.
2984
+ */
2985
+ const baseForSite = (lock, siteKey) => {
2986
+ const upgraded = upgradeLockFile(lock);
2987
+ return {
2988
+ ...upgraded,
2989
+ definitions: lockEntriesForSite(upgraded, siteKey)
2990
+ };
2991
+ };
2992
+ /** Files in the site's view that differ from what was last pulled (or were never pulled). */
2993
+ const editedFilesForSite = (files, lock, siteKey) => {
2994
+ const base = lockEntriesForSite(lock, siteKey);
2995
+ if (Object.keys(base).length === 0) return [];
2996
+ return filesForSite(files, siteKey).filter((file) => {
2997
+ const entry = file.id ? base[file.id] : void 0;
2998
+ return !entry || file.hash !== entry.hash;
2999
+ });
3000
+ };
3001
+ /**
3002
+ * Files a pull of `siteKey` leaves stale: in the pulled scopes and not written (deleted or renamed on the
3003
+ * instance), plus any other file of a definition that was just written elsewhere (its scope changed on the
3004
+ * instance: it is the same definition, and two files with one ID would make the next apply fail).
3005
+ */
3006
+ const staleFilesAfterPull = (files, siteKey, written) => {
3007
+ const writtenPaths = new Set(written.values());
3008
+ return files.filter((file) => {
3009
+ if (writtenPaths.has(file.path)) return false;
3010
+ return inView(file.site, siteKey) || file.id !== void 0 && written.has(file.id);
3011
+ });
3012
+ };
3013
+ /**
3014
+ * The lock after an apply for `siteKey` recorded its entries: the tree covers the site once it holds one of
3015
+ * the site's own definitions. A shared-only tree stays uncovered, so it still applies to every site.
3016
+ */
3017
+ const coverSiteIfHeld = (lock, siteKey) => Object.values(lock.definitions).some((entry) => entry.site === siteKey) ? {
3018
+ ...lock,
3019
+ sites: withSiteCovered(lock.sites, siteKey)
3020
+ } : lock;
2922
3021
  const EMPTY_LOCK = {
2923
- formatVersion: 1,
3022
+ formatVersion: 2,
2924
3023
  schemaVersion: 0,
3024
+ sites: [],
2925
3025
  definitions: {}
2926
3026
  };
2927
3027
  /** Field and definition names by ID from the local files, for readable diffs. */
@@ -2942,17 +3042,45 @@ const readLocalState = async (options) => {
2942
3042
  names: collectNames(files)
2943
3043
  };
2944
3044
  };
2945
- /** The schema sync endpoints (`export`, `apply`, `change`) of @shapio/client. */
2946
- const apiFor = (options) => createClient({
3045
+ /** The admin client for the command's site (`--site`, else the token's site, else the primary). */
3046
+ const clientFor = (options) => createClient({
2947
3047
  baseUrl: options.baseUrl,
2948
- token: options.token
2949
- }).admin.schema;
2950
- /** Sends the local files with their lock-file bases; the server decides per definition (three-way). */
3048
+ token: options.token,
3049
+ ...options.site ? { site: options.site } : {}
3050
+ });
3051
+ /** The schema sync endpoints (`export`, `apply`, `change`) of @shapio/client. */
3052
+ const apiFor = (options) => clientFor(options).admin.schema;
3053
+ /** The key of the site the command works on, as the instance resolved it (an unnamed site is the default). */
3054
+ const resolveSiteKey = async (options) => {
3055
+ if (options.site) return options.site;
3056
+ const { site } = await apiFor(options).export({ scope: "network" });
3057
+ if (!site) throw new Error("The instance did not say which site this token works on; pass --site <key>");
3058
+ return site.key;
3059
+ };
3060
+ /**
3061
+ * The local tree narrowed to one site (shared files and `sites/<key>/`), or undefined (after explaining)
3062
+ * when the tree was pulled for other sites only.
3063
+ */
3064
+ const readSiteState = async (options, io) => {
3065
+ const siteKey = await resolveSiteKey(options);
3066
+ const state = await readLocalState(options);
3067
+ if (!lockCoversSite(state.lock, siteKey)) {
3068
+ io.stderr(`${displayPath(options.lockPath)} covers the site(s) ${(state.lock.sites ?? []).join(", ")}, not "${siteKey}".\nApply it with --site <one of those>, or pull "${siteKey}" into this tree first: shapio schema pull --site ${siteKey}\n`);
3069
+ return;
3070
+ }
3071
+ return {
3072
+ ...state,
3073
+ siteKey,
3074
+ sent: filesForSite(state.files, siteKey)
3075
+ };
3076
+ };
3077
+ /** Sends the site's files with their lock-file bases; the server decides per definition (three-way). */
2951
3078
  const sendApply = async (options, state, dryRun, io) => {
2952
3079
  try {
2953
3080
  return await apiFor(options).apply({
2954
- definitions: state.files.map((file) => file.raw),
2955
- base: state.lock,
3081
+ definitions: state.sent.map((file) => file.raw),
3082
+ scopes: scopesOf(state.sent),
3083
+ base: baseForSite(state.lock, state.siteKey),
2956
3084
  prune: options.prune,
2957
3085
  dryRun,
2958
3086
  acknowledgeBreaking: options.allowBreaking,
@@ -2960,7 +3088,7 @@ const sendApply = async (options, state, dryRun, io) => {
2960
3088
  });
2961
3089
  } catch (error) {
2962
3090
  if (error instanceof ShapioApiError) {
2963
- io.stderr(formatApiError(error, state.files.map((file) => file.path), state.names));
3091
+ io.stderr(formatApiError(error, state.sent.map((file) => displayPath(file.path)), state.names));
2964
3092
  return;
2965
3093
  }
2966
3094
  throw error;
@@ -3048,11 +3176,12 @@ const checkPlanDirectory = async (dir, force) => {
3048
3176
  if (Object.keys(previous.state.entries).length > 0 || Object.keys(previous.state.media).length > 0) throw new Error(`${dir} holds an import that has started (${Object.keys(previous.state.entries).length} entries created): re-run --map to continue it, or plan into a new directory`);
3049
3177
  if (!force) throw new Error(`${dir} already holds a plan: pass --force to replace it (new IDs), or use another directory`);
3050
3178
  };
3051
- const plannedMap = (source, info, ids) => ({
3179
+ const plannedMap = (source, info, ids, site) => ({
3052
3180
  format: IMPORT_MAP_FORMAT,
3053
3181
  formatVersion: 1,
3054
3182
  source: info,
3055
3183
  plannedAt: (/* @__PURE__ */ new Date()).toISOString(),
3184
+ site: site ?? null,
3056
3185
  definitions: ids,
3057
3186
  media: Object.fromEntries(source.media.map((media) => [media.sourceId, {
3058
3187
  filename: media.filename,
@@ -3070,9 +3199,11 @@ const plannedMap = (source, info, ids) => ({
3070
3199
  /**
3071
3200
  * `--plan`: writes one pull-format schema file per planned definition under `<dir>/schema`, an empty lock file
3072
3201
  * beside them (so `schema apply` treats every definition as new and leaves the project's own lock alone), and
3073
- * `import-map.json`. Nothing is sent to an instance.
3202
+ * `import-map.json`. Nothing is sent to an instance. With `site` (`--site` / SHAPIO_SITE) the files go under
3203
+ * `schema/sites/<site>/`, so `schema apply --site <site>` creates them on that site; without one they go in
3204
+ * the shared folders and are created shared with all sites.
3074
3205
  */
3075
- const writePlan = async (dir, source, info, { force = false } = {}) => {
3206
+ const writePlan = async (dir, source, info, { force = false, site } = {}) => {
3076
3207
  await checkPlanDirectory(dir, force);
3077
3208
  const ids = assignIds(source.definitions);
3078
3209
  const definitions = buildDefinitions(source, ids);
@@ -3081,9 +3212,9 @@ const writePlan = async (dir, source, info, { force = false } = {}) => {
3081
3212
  recursive: true,
3082
3213
  force: true
3083
3214
  });
3084
- for (const definition of definitions) await writeDefinitionFile(schemaDir, definition);
3215
+ for (const definition of definitions) await writeDefinitionFile(schemaDir, definition, site ?? null);
3085
3216
  await writeLockFile(join(dir, PLAN_LOCK_FILE), EMPTY_LOCK);
3086
- const map = plannedMap(source, info, ids);
3217
+ const map = plannedMap(source, info, ids, site);
3087
3218
  await writeImportMap(dir, map);
3088
3219
  return {
3089
3220
  definitions,
@@ -3198,17 +3329,27 @@ var ImportRefusedError = class extends Error {
3198
3329
  this.name = "ImportRefusedError";
3199
3330
  }
3200
3331
  };
3332
+ /**
3333
+ * The site `--map` writes to: `--site` (or SHAPIO_SITE), else the site the plan was made for. A requested site
3334
+ * that disagrees with the planned one is refused: the planned models exist on that site only.
3335
+ */
3336
+ const resolveMapSite = (map, requested) => {
3337
+ const planned = map.site ?? void 0;
3338
+ if (requested !== void 0 && planned !== void 0 && requested !== planned) throw new ImportRefusedError(`This import was planned for site "${planned}", not "${requested}": run --map with --site ${planned}, or plan again with --site ${requested}`);
3339
+ return requested ?? planned;
3340
+ };
3201
3341
  const messageOf = (error) => error instanceof Error ? error.message : String(error);
3202
- const checkSourceAndSite = async (map, options) => {
3342
+ const checkSourceAndSite = async (map, site) => {
3203
3343
  if (await sha256File(map.source.path).catch((error) => {
3204
3344
  throw new ImportRefusedError(`The source ${map.source.path} cannot be read: ${messageOf(error)}`);
3205
3345
  }) !== map.source.sha256) throw new ImportRefusedError(`${map.source.path} changed since it was planned: plan again into a new directory`);
3206
- if ((Object.keys(map.state.entries).length > 0 || Object.keys(map.state.media).length > 0) && (map.state.site ?? null) !== (options.site ?? null)) throw new ImportRefusedError(`This import was started on site ${map.state.site ?? "(default)"}: re-run it with the same --site`);
3346
+ if ((Object.keys(map.state.entries).length > 0 || Object.keys(map.state.media).length > 0) && (map.state.site ?? null) !== (site ?? null)) throw new ImportRefusedError(`This import was started on site ${map.state.site ?? "(default)"}: re-run it with the same --site`);
3207
3347
  };
3208
- const loadLiveSchema = async (client, map, dir) => {
3348
+ /** The target site's view (the client names the site): its own definitions and the shared ones. */
3349
+ const loadLiveSchema = async (client, map, dir, site) => {
3209
3350
  const exported = await client.admin.schema.export();
3210
3351
  const live = resolveLiveSchema(map, exported.definitions.map((entry) => entry.definition));
3211
- if (live.missing.length > 0) throw new ImportRefusedError(`The instance does not have the planned models yet: ${live.missing.join(", ")}.\nApply them first: shapio schema apply --dir ${join(dir, PLAN_SCHEMA_DIR)} --lock ${join(dir, PLAN_LOCK_FILE)}`);
3352
+ if (live.missing.length > 0) throw new ImportRefusedError(`The instance does not have the planned models yet: ${live.missing.join(", ")}.\nApply them first: shapio schema apply --dir ${join(dir, PLAN_SCHEMA_DIR)} --lock ${join(dir, PLAN_LOCK_FILE)}${site ? ` --site ${site}` : ""}`);
3212
3353
  return live;
3213
3354
  };
3214
3355
  const checkLocales = async (client, source) => {
@@ -3362,17 +3503,18 @@ const printSummary = (run, source, changeSetFailures) => {
3362
3503
  };
3363
3504
  const runMap = async (options, io) => {
3364
3505
  const map = await readImportMap(options.dir);
3365
- await checkSourceAndSite(map, options);
3506
+ const site = resolveMapSite(map, options.site);
3507
+ await checkSourceAndSite(map, site);
3366
3508
  const client = createClient({
3367
3509
  baseUrl: options.baseUrl,
3368
3510
  token: options.token,
3369
3511
  fetch: createRateLimitedFetch(options.fetch),
3370
- ...options.site ? { site: options.site } : {}
3512
+ ...site ? { site } : {}
3371
3513
  });
3372
- const live = await loadLiveSchema(client, map, options.dir);
3514
+ const live = await loadLiveSchema(client, map, options.dir, site);
3373
3515
  const source = await options.load(map);
3374
3516
  await checkLocales(client, source);
3375
- map.state.site = options.site ?? null;
3517
+ map.state.site = site ?? null;
3376
3518
  map.state.baseUrl = options.baseUrl;
3377
3519
  const run = {
3378
3520
  options,
@@ -3396,7 +3538,7 @@ const display = (path) => {
3396
3538
  return !shown ? "." : shown.startsWith("..") || isAbsolute(shown) ? path : shown;
3397
3539
  };
3398
3540
  /** What `--plan` wrote and what to do next. */
3399
- const formatPlanSummary = (source, definitions, dir, command) => {
3541
+ const formatPlanSummary = (source, definitions, dir, command, site) => {
3400
3542
  const keyOf = new Map(source.definitions.map((definition) => [definition.apiKey, definition.key]));
3401
3543
  const lines = [`Planned ${definitions.length} definition(s) in ${display(`${dir}/${PLAN_SCHEMA_DIR}`)}:`];
3402
3544
  for (const definition of definitions) {
@@ -3407,7 +3549,9 @@ const formatPlanSummary = (source, definitions, dir, command) => {
3407
3549
  lines.push(`Media: ${source.media.length} file(s) to import.`);
3408
3550
  lines.push("Every model has draft and publish on: nothing goes live until you ship the change set(s) the import opens.");
3409
3551
  lines.push(...source.notes);
3410
- lines.push("Next:", " 1. Review the files: rename API IDs or delete fields you do not want (entries follow the stable IDs).", ` 2. shapio schema apply --dir ${display(`${dir}/${PLAN_SCHEMA_DIR}`)} --lock ${display(`${dir}/${PLAN_LOCK_FILE}`)}`, ` 3. shapio import ${command} --map ${display(dir)}`);
3552
+ lines.push(site ? `The models and components will belong to site "${site}".` : "No --site given: the models and components will be shared with all sites. Plan again with --site <key> to keep them on one site.");
3553
+ const siteFlag = site ? ` --site ${site}` : "";
3554
+ lines.push("Next:", " 1. Review the files: rename API IDs or delete fields you do not want (entries follow the stable IDs).", ` 2. shapio schema apply --dir ${display(`${dir}/${PLAN_SCHEMA_DIR}`)} --lock ${display(`${dir}/${PLAN_LOCK_FILE}`)}${siteFlag}`, ` 3. shapio import ${command} --map ${display(dir)}${siteFlag}`);
3411
3555
  return `${lines.join("\n")}\n`;
3412
3556
  };
3413
3557
  const parse = (args, spec) => {
@@ -3436,24 +3580,30 @@ const parse = (args, spec) => {
3436
3580
  file: positionals[0]
3437
3581
  };
3438
3582
  };
3583
+ /** `--site`, else SHAPIO_SITE: the site the plan's schema belongs to and the map step writes to. */
3584
+ const siteOf = (values, io) => (values.site ?? io.env.SHAPIO_SITE)?.trim() || void 0;
3439
3585
  const runPlan = async (spec, file, values, io) => {
3440
3586
  const dir = resolve(String(values.plan));
3441
3587
  const path = resolve(file);
3442
3588
  await checkPlanDirectory(dir, values.force === true);
3443
3589
  const source = await spec.plan(path, dir, values);
3590
+ const site = siteOf(values, io);
3444
3591
  const { definitions } = await writePlan(dir, source, {
3445
3592
  kind: source.kind,
3446
3593
  path,
3447
3594
  sha256: await sha256File(path)
3448
- }, { force: values.force === true });
3449
- io.stdout(formatPlanSummary(source, definitions, dir, spec.name));
3595
+ }, {
3596
+ force: values.force === true,
3597
+ site
3598
+ });
3599
+ io.stdout(formatPlanSummary(source, definitions, dir, spec.name, site));
3450
3600
  return 0;
3451
3601
  };
3452
3602
  const runMapStep = (spec, values, io) => {
3453
3603
  const token = values.token ?? io.env.SHAPIO_TOKEN;
3454
3604
  if (!token) throw new UsageError$1("An admin API token is required: pass --token or set SHAPIO_TOKEN");
3455
3605
  const dir = resolve(String(values.map));
3456
- const site = values.site ?? io.env.SHAPIO_SITE;
3606
+ const site = siteOf(values, io);
3457
3607
  return runMap({
3458
3608
  dir,
3459
3609
  baseUrl: values.url ?? io.env.SHAPIO_URL ?? "http://localhost:4300",
@@ -3492,7 +3642,7 @@ const importStrapiCommand = createImporterCommand({
3492
3642
  name: "strapi",
3493
3643
  label: "Strapi",
3494
3644
  summary: "Import a Strapi 5 export: --plan writes schema files, --map creates drafts and change sets",
3495
- usage: "shapio import strapi <export.tar.gz[.enc]> --plan <dir> [--key <encryption key>] [--force]\n shapio import strapi --map <dir> [--url <origin>] [--token <admin token>] [--site <key>]\n --plan unpacks the export into <dir>/source (--key for an encrypted export) and writes the planned models\n and components (<dir>/schema) and <dir>/import-map.json; it sends nothing. Apply the schema with\n `shapio schema apply --dir <dir>/schema --lock <dir>/schema-lock.json`, then run --map: it uploads the\n media, creates every document as a draft and opens change sets with the published ones. Strapi 5 only.",
3645
+ usage: "shapio import strapi <export.tar.gz[.enc]> --plan <dir> [--key <encryption key>] [--site <key>] [--force]\n shapio import strapi --map <dir> [--url <origin>] [--token <admin token>] [--site <key>]\n --plan unpacks the export into <dir>/source (--key for an encrypted export) and writes the planned models\n and components (<dir>/schema) and <dir>/import-map.json; it sends nothing. With --site (or SHAPIO_SITE)\n they belong to that site (<dir>/schema/sites/<key>/); without, they are shared (--map\n without --site imports into the planned site). Apply the schema with\n `shapio schema apply --dir <dir>/schema --lock <dir>/schema-lock.json [--site <key>]`, then run --map:\n it uploads the media, creates every document as a draft and opens change sets with the published ones.\n Strapi 5 only.",
3496
3646
  options: ["key"],
3497
3647
  plan: async (file, dir, values) => {
3498
3648
  await extractStrapiExport(file, sourceDir(dir), typeof values.key === "string" ? values.key : void 0);
@@ -4086,7 +4236,7 @@ const importWordPressCommand = createImporterCommand({
4086
4236
  name: "wordpress",
4087
4237
  label: "WordPress",
4088
4238
  summary: "Import a WordPress export (WXR): --plan writes schema files, --map creates drafts and change sets",
4089
- usage: "shapio import wordpress <export.xml> --plan <dir> [--force]\n shapio import wordpress --map <dir> [--url <origin>] [--token <admin token>] [--site <key>] [--media-dir <uploads dir>]\n --plan reads the export and writes the planned models (<dir>/schema) and <dir>/import-map.json; it sends nothing.\n Apply the models with `shapio schema apply --dir <dir>/schema --lock <dir>/schema-lock.json`, then run --map:\n it uploads the media (downloaded from the site, or read from --media-dir, a copy of wp-content/uploads),\n creates every post and page as a draft, and opens change sets with the published ones. Re-run --map to resume.",
4239
+ usage: "shapio import wordpress <export.xml> --plan <dir> [--site <key>] [--force]\n shapio import wordpress --map <dir> [--url <origin>] [--token <admin token>] [--site <key>] [--media-dir <uploads dir>]\n --plan reads the export and writes the planned models (<dir>/schema) and <dir>/import-map.json; it sends nothing.\n With --site (or SHAPIO_SITE) the models belong to that site (<dir>/schema/sites/<key>/); without, they are shared;\n --map without --site then imports into the planned site.\n Apply the models with `shapio schema apply --dir <dir>/schema --lock <dir>/schema-lock.json [--site <key>]`, then run --map:\n it uploads the media (downloaded from the site, or read from --media-dir, a copy of wp-content/uploads),\n creates every post and page as a draft, and opens change sets with the published ones. Re-run --map to resume.",
4090
4240
  options: ["media-dir"],
4091
4241
  plan: async (file) => wordpressSource(await readWxr(file)),
4092
4242
  load: async (map, _dir, values) => wordpressSource(await readWxr(map.source.path), mediaDirOf(values))
@@ -4268,13 +4418,28 @@ var UsageError = class extends Error {
4268
4418
  this.name = "UsageError";
4269
4419
  }
4270
4420
  };
4271
- /** Shared flags of `shapio schema pull|diff|apply`. URL and token also come from SHAPIO_URL / SHAPIO_TOKEN. */
4421
+ const CONNECTION_FLAGS = {
4422
+ url: { type: "string" },
4423
+ token: { type: "string" },
4424
+ site: { type: "string" }
4425
+ };
4426
+ /** URL, token and site from the flags, else SHAPIO_URL / SHAPIO_TOKEN / SHAPIO_SITE. */
4427
+ const resolveConnection = (values, io) => {
4428
+ const token = values.token ?? io.env.SHAPIO_TOKEN;
4429
+ if (!token) throw new UsageError("An admin API token is required: pass --token or set SHAPIO_TOKEN");
4430
+ const site = (values.site ?? io.env.SHAPIO_SITE)?.trim() || void 0;
4431
+ return {
4432
+ baseUrl: values.url ?? io.env.SHAPIO_URL ?? "http://localhost:4300",
4433
+ token,
4434
+ ...site ? { site } : {}
4435
+ };
4436
+ };
4437
+ /** Shared flags of `shapio schema pull|diff|apply`. */
4272
4438
  const parseSchemaOptions = (args, io) => {
4273
4439
  const { values } = parseArgs({
4274
4440
  args: [...args],
4275
4441
  options: {
4276
- url: { type: "string" },
4277
- token: { type: "string" },
4442
+ ...CONNECTION_FLAGS,
4278
4443
  dir: { type: "string" },
4279
4444
  lock: { type: "string" },
4280
4445
  prune: {
@@ -4301,13 +4466,11 @@ const parseSchemaOptions = (args, io) => {
4301
4466
  },
4302
4467
  allowPositionals: false
4303
4468
  });
4304
- const token = values.token ?? io.env.SHAPIO_TOKEN;
4305
- if (!token) throw new UsageError("An admin API token is required: pass --token or set SHAPIO_TOKEN");
4469
+ const connection = resolveConnection(values, io);
4306
4470
  const waitTimeoutSeconds = Number(values["wait-timeout"] ?? 600);
4307
4471
  if (!Number.isFinite(waitTimeoutSeconds) || waitTimeoutSeconds <= 0) throw new UsageError("--wait-timeout must be a positive number of seconds");
4308
4472
  return {
4309
- baseUrl: values.url ?? io.env.SHAPIO_URL ?? "http://localhost:4300",
4310
- token,
4473
+ ...connection,
4311
4474
  dir: resolve(values.dir ?? "schema"),
4312
4475
  lockPath: resolve(values.lock ?? ".shapio/schema-lock.json"),
4313
4476
  prune: values.prune,
@@ -4318,7 +4481,7 @@ const parseSchemaOptions = (args, io) => {
4318
4481
  waitTimeoutMs: waitTimeoutSeconds * 1e3
4319
4482
  };
4320
4483
  };
4321
- const COMMON_USAGE = "[--url <origin>] [--token <admin token>] [--dir schema] [--lock .shapio/schema-lock.json]";
4484
+ const COMMON_USAGE = `[--url <origin>] [--token <admin token>] [--site <key>] [--dir schema] [--lock .shapio/schema-lock.json]`;
4322
4485
  const POLL_INTERVAL_MS = 500;
4323
4486
  /** Waits for planned changes (prerequisite jobs) to activate or fail. Returns the failed ones. */
4324
4487
  const waitForChanges = async (options, items, io) => {
@@ -4350,16 +4513,18 @@ const waitForChanges = async (options, items, io) => {
4350
4513
  };
4351
4514
  /**
4352
4515
  * Records the new state in the lock file and rewrites the files of applied definitions in canonical form
4353
- * (e.g. with server-assigned field IDs). Definitions skipped because they were unchanged locally keep
4354
- * their old base, so a later apply still treats them as untouched.
4516
+ * (e.g. with server-assigned field IDs), in their scope's folder. Definitions skipped because they were
4517
+ * unchanged locally keep their old base, so a later apply still treats them as untouched. Other sites'
4518
+ * entries are kept as they are.
4355
4519
  */
4356
4520
  const updateLocalState = async (options, state, response) => {
4357
4521
  const exported = await apiFor(options).export();
4358
4522
  const remote = new Map(exported.definitions.map((entry) => [entry.definition.id, entry]));
4523
+ const upgraded = upgradeLockFile(state.lock);
4359
4524
  const lock = {
4360
- ...state.lock,
4525
+ ...upgraded,
4361
4526
  schemaVersion: exported.schemaVersion,
4362
- definitions: { ...state.lock.definitions }
4527
+ definitions: { ...upgraded.definitions }
4363
4528
  };
4364
4529
  for (const item of response.results) {
4365
4530
  if (!(item.outcome === "activated" || item.decision.action === "skip" && item.decision.reason === "alreadyApplied")) continue;
@@ -4368,97 +4533,152 @@ const updateLocalState = async (options, state, response) => {
4368
4533
  delete lock.definitions[item.definitionId];
4369
4534
  continue;
4370
4535
  }
4371
- const previous = state.files.find((file) => file.id === item.definitionId);
4372
- const path = await writeDefinitionFile(options.dir, entry.definition);
4536
+ const previous = state.sent.find((file) => file.id === item.definitionId);
4537
+ const path = await writeDefinitionFile(options.dir, entry.definition, entry.site);
4373
4538
  if (previous && previous.path !== path) await removeFile(previous.path);
4374
4539
  lock.definitions[item.definitionId] = {
4375
4540
  kind: entry.definition.kind,
4376
4541
  apiKey: entry.definition.apiKey,
4377
4542
  version: entry.version,
4378
- hash: entry.hash
4543
+ hash: entry.hash,
4544
+ site: entry.site
4379
4545
  };
4380
4546
  }
4381
- await writeLockFile(options.lockPath, lock);
4547
+ await writeLockFile(options.lockPath, coverSiteIfHeld(lock, state.siteKey));
4548
+ };
4549
+ /**
4550
+ * `shapio schema apply`: applies local schema files to a running instance live, through the same planner as
4551
+ * the admin UI. Three-way per definition against the lock file; refuses (and changes nothing) on conflicts.
4552
+ * It sends the shared files and the site's own (`sites/<key>/`), never another site's.
4553
+ */
4554
+ const schemaApplyCommand = {
4555
+ summary: "Apply local schema files to the instance live (three-way, per model; refuses on conflicts)",
4556
+ usage: `shapio schema apply ${COMMON_USAGE} [--prune] [--allow-breaking] [--allow-destructive] [--no-wait] [--wait-timeout <s>]`,
4557
+ run: async (args, io) => {
4558
+ const options = parseSchemaOptions(args, io);
4559
+ const state = await readSiteState(options, io);
4560
+ if (!state) return 1;
4561
+ const response = await sendApply(options, state, false, io);
4562
+ if (!response) return 1;
4563
+ printResults(response, state, io, false);
4564
+ const pending = response.results.filter((item) => item.outcome === "pending");
4565
+ const failed = options.wait ? await waitForChanges(options, pending, io) : [];
4566
+ await updateLocalState(options, state, response);
4567
+ const applied = response.results.filter((item) => item.outcome === "activated").length;
4568
+ io.stdout(`Applied ${applied} definition(s) at schema version ${response.schemaVersion}.\n`);
4569
+ return failed.length > 0 ? 1 : 0;
4570
+ }
4571
+ };
4572
+ /** `shapio schema diff`: what `apply` would do, per definition, without changing anything. */
4573
+ const schemaDiffCommand = {
4574
+ summary: "Show what `shapio schema apply` would change on the instance (dry run)",
4575
+ usage: `shapio schema diff ${COMMON_USAGE} [--prune]`,
4576
+ run: async (args, io) => {
4577
+ const options = parseSchemaOptions(args, io);
4578
+ const state = await readSiteState(options, io);
4579
+ if (!state) return 1;
4580
+ const response = await sendApply(options, state, true, io);
4581
+ if (!response) return 1;
4582
+ printResults(response, state, io, false);
4583
+ const pending = response.results.filter((item) => [
4584
+ "create",
4585
+ "update",
4586
+ "delete"
4587
+ ].includes(item.decision.action));
4588
+ io.stdout(pending.length === 0 ? "Nothing to apply.\n" : `${pending.length} definition(s) would change.\n`);
4589
+ return 0;
4590
+ }
4591
+ };
4592
+ /**
4593
+ * `shapio schema pull`: writes one site's view of the instance (the shared definitions in `models/` and
4594
+ * `components/`, the site's own under `sites/<key>/`) as canonical JSON, one file per model or component,
4595
+ * and records what was pulled in the lock file. Another site's folder and lock entries are left alone, so one
4596
+ * tree can hold several sites. Refuses to overwrite local edits in the pulled folders unless --force.
4597
+ */
4598
+ const schemaPullCommand = {
4599
+ summary: "Write a site’s models and components (shared and its own) to schema files and the lock file",
4600
+ usage: `shapio schema pull ${COMMON_USAGE} [--force]`,
4601
+ run: async (args, io) => {
4602
+ const options = parseSchemaOptions(args, io);
4603
+ const exported = await apiFor(options).export();
4604
+ if (!exported.site) throw new Error("The instance did not say which site this view is; pass --site <key>");
4605
+ const siteKey = exported.site.key;
4606
+ const state = await readLocalState(options);
4607
+ const edited = editedFilesForSite(state.files, state.lock, siteKey);
4608
+ if (edited.length > 0 && !options.force) {
4609
+ io.stderr(`Local schema files have changes that are not on the instance:\n${edited.map((file) => ` ${displayPath(file.path)}`).join("\n")}\nApply them first, or commit them and re-run with --force to overwrite them with the instance’s schema.
4610
+ `);
4611
+ return 1;
4612
+ }
4613
+ const written = /* @__PURE__ */ new Map();
4614
+ for (const { definition, site } of exported.definitions) written.set(definition.id, await writeDefinitionFile(options.dir, definition, site));
4615
+ for (const file of staleFilesAfterPull(state.files, siteKey, written)) await removeFile(file.path);
4616
+ await writeLockFile(options.lockPath, mergePulledLock(state.lock, {
4617
+ schemaVersion: exported.schemaVersion,
4618
+ siteKey,
4619
+ definitions: exported.definitions
4620
+ }));
4621
+ io.stdout(`Pulled ${exported.definitions.length} definition(s) of site "${siteKey}" at schema version ${exported.schemaVersion} into ${displayPath(options.dir)}\n`);
4622
+ return 0;
4623
+ }
4624
+ };
4625
+ const parseScopeOptions = (args, io) => {
4626
+ const { values, positionals } = parseArgs({
4627
+ args: [...args],
4628
+ options: {
4629
+ ...CONNECTION_FLAGS,
4630
+ shared: {
4631
+ type: "boolean",
4632
+ default: false
4633
+ },
4634
+ version: { type: "string" }
4635
+ },
4636
+ allowPositionals: true
4637
+ });
4638
+ const [apiKey, ...extra] = positionals;
4639
+ if (!apiKey || extra.length > 0) throw new UsageError("Name one model or component by its API ID");
4640
+ if (!values.shared && !values.site) throw new UsageError("Pass --site <key> to keep it on that site, or --shared to share it with all sites");
4641
+ const version = values.version === void 0 ? void 0 : Number(values.version);
4642
+ if (version !== void 0 && (!Number.isInteger(version) || version < 1)) throw new UsageError("--version must be a positive integer");
4643
+ return {
4644
+ ...resolveConnection(values, io),
4645
+ apiKey,
4646
+ shared: values.shared,
4647
+ version
4648
+ };
4382
4649
  };
4383
4650
  const SUBCOMMANDS$1 = {
4384
- pull: {
4385
- summary: "Write the instance’s models and components to schema files and the lock file",
4386
- usage: `shapio schema pull ${COMMON_USAGE} [--force]`,
4651
+ pull: schemaPullCommand,
4652
+ diff: schemaDiffCommand,
4653
+ apply: schemaApplyCommand,
4654
+ scope: {
4655
+ summary: "Keep a shared model or component on one site (--site), or share a site’s with all sites (--shared)",
4656
+ usage: "shapio schema scope <apiKey> (--site <key> | --shared [--site <key>]) [--url <origin>] [--token <admin token>] [--version <n>]",
4387
4657
  run: async (args, io) => {
4388
- const options = parseSchemaOptions(args, io);
4389
- const state = await readLocalState(options);
4390
- const edited = state.files.filter((file) => {
4391
- const base = file.id ? state.lock.definitions[file.id] : void 0;
4392
- return !base || file.hash !== base.hash;
4393
- });
4394
- if (edited.length > 0 && !options.force && Object.keys(state.lock.definitions).length > 0) {
4395
- io.stderr(`Local schema files have changes that are not on the instance:\n${edited.map((file) => ` ${displayPath(file.path)}`).join("\n")}\nApply them first, or commit them and re-run with --force to overwrite them with the instance’s schema.
4396
- `);
4658
+ const options = parseScopeOptions(args, io);
4659
+ const client = clientFor(options);
4660
+ const exported = await client.admin.schema.export();
4661
+ const siteKey = options.site ?? exported.site?.key ?? "(default)";
4662
+ const found = exported.definitions.find(({ definition }) => definition.apiKey === options.apiKey);
4663
+ if (!found) {
4664
+ io.stderr(`Site "${siteKey}" has no model or component with API ID "${options.apiKey}".\n`);
4397
4665
  return 1;
4398
4666
  }
4399
- const exported = await apiFor(options).export();
4400
- const lock = {
4401
- formatVersion: 1,
4402
- schemaVersion: exported.schemaVersion,
4403
- definitions: {}
4404
- };
4405
- const written = /* @__PURE__ */ new Set();
4406
- for (const { definition, version, hash } of exported.definitions) {
4407
- written.add(await writeDefinitionFile(options.dir, definition));
4408
- lock.definitions[definition.id] = {
4409
- kind: definition.kind,
4410
- apiKey: definition.apiKey,
4411
- version,
4412
- hash
4413
- };
4414
- }
4415
- for (const file of state.files.filter((candidate) => !written.has(candidate.path))) await removeFile(file.path);
4416
- await writeLockFile(options.lockPath, lock);
4417
- io.stdout(`Pulled ${exported.definitions.length} definition(s) at schema version ${exported.schemaVersion} into ${displayPath(options.dir)}\n`);
4418
- return 0;
4419
- }
4420
- },
4421
- diff: {
4422
- summary: "Show what `shapio schema apply` would change on the instance (dry run)",
4423
- usage: `shapio schema diff ${COMMON_USAGE} [--prune]`,
4424
- run: async (args, io) => {
4425
- const options = parseSchemaOptions(args, io);
4426
- const state = await readLocalState(options);
4427
- const response = await sendApply(options, state, true, io);
4428
- if (!response) return 1;
4429
- printResults(response, state, io, false);
4430
- const pending = response.results.filter((item) => [
4431
- "create",
4432
- "update",
4433
- "delete"
4434
- ].includes(item.decision.action));
4435
- io.stdout(pending.length === 0 ? "Nothing to apply.\n" : `${pending.length} definition(s) would change.\n`);
4667
+ const outcome = await (found.definition.kind === "component" ? client.admin.components : client.admin.models).changeScope(found.definition.id, {
4668
+ scope: options.shared ? "network" : "site",
4669
+ version: options.version ?? found.version
4670
+ });
4671
+ const where = outcome.scope === "network" ? "shared with all sites" : `on site "${siteKey}" only`;
4672
+ io.stdout(outcome.status === "unchanged" ? `${options.apiKey} is already ${where}.\n` : `${options.apiKey} is now ${where} (version ${outcome.version}, schema version ${outcome.schemaVersion}).\nPull to move its file: shapio schema pull --site ${siteKey}\n`);
4436
4673
  return 0;
4437
4674
  }
4438
- },
4439
- apply: {
4440
- summary: "Apply local schema files to the instance live (three-way, per model; refuses on conflicts)",
4441
- usage: `shapio schema apply ${COMMON_USAGE} [--prune] [--allow-breaking] [--allow-destructive] [--no-wait] [--wait-timeout <s>]`,
4442
- run: async (args, io) => {
4443
- const options = parseSchemaOptions(args, io);
4444
- const state = await readLocalState(options);
4445
- const response = await sendApply(options, state, false, io);
4446
- if (!response) return 1;
4447
- printResults(response, state, io, false);
4448
- const pending = response.results.filter((item) => item.outcome === "pending");
4449
- const failed = options.wait ? await waitForChanges(options, pending, io) : [];
4450
- await updateLocalState(options, state, response);
4451
- const applied = response.results.filter((item) => item.outcome === "activated").length;
4452
- io.stdout(`Applied ${applied} definition(s) at schema version ${response.schemaVersion}.\n`);
4453
- return failed.length > 0 ? 1 : 0;
4454
- }
4455
4675
  }
4456
4676
  };
4457
4677
  const help$1 = () => `Usage:\n${Object.values(SUBCOMMANDS$1).map((command) => ` ${command.usage}\n ${command.summary}`).join("\n")}\n`;
4458
- /** `shapio schema <pull|diff|apply>`: git-like schema sync over HTTP with an admin API token. */
4678
+ /** `shapio schema <pull|diff|apply|scope>`: git-like schema sync over HTTP with an admin API token. */
4459
4679
  const schemaCommand = {
4460
- summary: "Sync models and components with schema files: pull | diff | apply",
4461
- usage: "shapio schema <pull|diff|apply> [options]",
4680
+ summary: "Sync models and components with schema files: pull | diff | apply | scope",
4681
+ usage: "shapio schema <pull|diff|apply|scope> [options]",
4462
4682
  run: async (args, io) => {
4463
4683
  const [name, ...rest] = args;
4464
4684
  const command = name ? SUBCOMMANDS$1[name] : void 0;
@@ -4473,6 +4693,10 @@ const schemaCommand = {
4473
4693
  io.stderr(`shapio schema ${name}: ${error.message}\n`);
4474
4694
  return 1;
4475
4695
  }
4696
+ if (error instanceof ShapioApiError) {
4697
+ io.stderr(formatApiError(error, [], /* @__PURE__ */ new Map()));
4698
+ return 1;
4699
+ }
4476
4700
  throw error;
4477
4701
  }
4478
4702
  }
@@ -4594,6 +4818,22 @@ const backupDatabase = async (db, target) => {
4594
4818
  return path;
4595
4819
  };
4596
4820
  //#endregion
4821
+ //#region src/extensions/themeContrast.ts
4822
+ const HEX = /^#[0-9a-fA-F]{6}$/;
4823
+ /**
4824
+ * WCAG AA warnings for extension themes (`shapio extensions check`): the pairs the built-in themes are held
4825
+ * to. Warnings, not errors: a project may knowingly ship a theme that fails. Only `#rrggbb` values can be
4826
+ * measured; a variant with other values is reported as not fully checked.
4827
+ */
4828
+ const themeContrastWarnings = (themes) => themes.flatMap((theme) => THEME_VARIANTS.flatMap((variant) => {
4829
+ const tokens = theme[variant];
4830
+ if (!tokens) return [];
4831
+ const values = Object.fromEntries(Object.entries(tokens).filter((entry) => typeof entry[1] === "string"));
4832
+ const failures = checkThemeContrast(values, variant).filter(({ ratio, required }) => ratio !== void 0 && ratio < required).map(({ foreground, background, ratio, required }) => `${theme.key} (${variant}): --${foreground} on --${background} is ${(ratio ?? 0).toFixed(2)}:1, needs ${required}:1`);
4833
+ const unmeasured = Object.values(values).some((value) => !HEX.test(value)) ? [`${theme.key} (${variant}): only #rrggbb values are checked for contrast`] : [];
4834
+ return [...failures, ...unmeasured];
4835
+ }));
4836
+ //#endregion
4597
4837
  //#region src/extensions/check.ts
4598
4838
  const AFTER = new Set(AFTER_EVENTS);
4599
4839
  /** Collects the editor manifest's complaints (missing files, bad names) instead of printing log lines. */
@@ -4619,6 +4859,8 @@ const checkExtensions = async (options) => {
4619
4859
  const editorProblems = [];
4620
4860
  const editors = buildEditorManifest(projectDir, config.editors ?? [], collectingLogger(editorProblems));
4621
4861
  const hooks = Object.entries(config.hooks ?? {}).flatMap(([key, modelHooks]) => [...BEFORE_EVENTS, ...AFTER_EVENTS].filter((event) => modelHooks[event]).map((event) => `${key}.${event} (${AFTER.has(event) ? "after commit, as a job" : "in the transaction"})`));
4862
+ const themes = config.themes ?? [];
4863
+ const contrastWarnings = themeContrastWarnings(themes);
4622
4864
  const section = (title, items) => `${title} (${items.length})\n${items.map((item) => ` ${item}\n`).join("")}`;
4623
4865
  const text = [
4624
4866
  `Config: ${relative(process.cwd(), loaded.file) || loaded.file}\n`,
@@ -4627,7 +4869,9 @@ const checkExtensions = async (options) => {
4627
4869
  section("Services", Object.keys(config.services ?? {})),
4628
4870
  section("Jobs", Object.keys(config.jobs ?? {}).map((name) => `${EXTENSION_JOB_PREFIX}${name}`)),
4629
4871
  section("Editors", editors.entries.map((entry) => entry.file)),
4630
- ...editorProblems.length > 0 ? [section("Problems", editorProblems)] : []
4872
+ section("Themes", themes.map((theme) => `${theme.key} (${theme.name})`)),
4873
+ ...editorProblems.length > 0 ? [section("Problems", editorProblems)] : [],
4874
+ ...contrastWarnings.length > 0 ? [section("Warnings", contrastWarnings)] : []
4631
4875
  ].join("");
4632
4876
  return {
4633
4877
  ok: editorProblems.length === 0,
@@ -5050,7 +5294,7 @@ const adminCommand = {
5050
5294
  }
5051
5295
  }
5052
5296
  };
5053
- const SITES_USAGE = "shapio sites list\nshapio sites create --key <key> --name <name>\n Lists or creates sites directly in the database (sites share the schema, admins and roles; each has its\n own content, media, tokens and snapshots). Keys are lower case and fixed once created.";
5297
+ const SITES_USAGE = "shapio sites list\nshapio sites create --key <key> --name <name>\n Lists or creates sites directly in the database (sites share the admins, roles and shared content types;\n each has its own content types, content, media, tokens and snapshots). Keys are lower case and fixed once\n created.";
5054
5298
  const SITE_KEY_RE = new RegExp(SITE_KEY_PATTERN);
5055
5299
  const runSites = async (subcommand, options, io) => {
5056
5300
  const config = loadConfig();