@xano/sdk 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 (336) hide show
  1. package/CHANGELOG.md +788 -0
  2. package/LICENSE +21 -0
  3. package/README.md +541 -0
  4. package/dist/.build-fingerprint +1 -0
  5. package/dist/agent-file-refresh-ZEMK33AX.js +38 -0
  6. package/dist/answer-shape-K67TAAPY.js +33 -0
  7. package/dist/backend-dir-2U53CIAL.js +39 -0
  8. package/dist/backend-slot-7QXW2QYC.js +28 -0
  9. package/dist/bin.d.ts +1 -0
  10. package/dist/bin.js +255 -0
  11. package/dist/branch-NCRNWQBQ.js +61 -0
  12. package/dist/branch-commands-ACIRCEQJ.js +79 -0
  13. package/dist/bundle.d.ts +223 -0
  14. package/dist/bundle.js +153 -0
  15. package/dist/capture-WAXPD7DR.js +41 -0
  16. package/dist/chunk-27GWNP2I.js +23 -0
  17. package/dist/chunk-2CMJZDCL.js +177 -0
  18. package/dist/chunk-2EZ3ZJZZ.js +302 -0
  19. package/dist/chunk-2GNFDZA2.js +390 -0
  20. package/dist/chunk-2MNLRIOX.js +11 -0
  21. package/dist/chunk-2TFRR6JV.js +104 -0
  22. package/dist/chunk-2ZUEE4EK.js +408 -0
  23. package/dist/chunk-36JUQZVQ.js +22 -0
  24. package/dist/chunk-3DRXWLJ5.js +83 -0
  25. package/dist/chunk-3JDU4VRL.js +137 -0
  26. package/dist/chunk-3KZCLN4Z.js +241 -0
  27. package/dist/chunk-3LDULPRE.js +168 -0
  28. package/dist/chunk-3M4LFYNQ.js +111 -0
  29. package/dist/chunk-3XI5CTUE.js +22 -0
  30. package/dist/chunk-3YW23UFH.js +50 -0
  31. package/dist/chunk-45UIT5T2.js +862 -0
  32. package/dist/chunk-4BW2SG2N.js +46 -0
  33. package/dist/chunk-4KHEMC32.js +79 -0
  34. package/dist/chunk-4POWHT2K.js +321 -0
  35. package/dist/chunk-4Q2GN7NW.js +192 -0
  36. package/dist/chunk-5A2PLKZB.js +70 -0
  37. package/dist/chunk-5FBYXK5P.js +159 -0
  38. package/dist/chunk-5HVXPTU4.js +323 -0
  39. package/dist/chunk-5QU2P5CA.js +304 -0
  40. package/dist/chunk-5V5XU32D.js +191 -0
  41. package/dist/chunk-5ZSO4T2G.js +10 -0
  42. package/dist/chunk-622HUCZT.js +10 -0
  43. package/dist/chunk-65HWOMGV.js +177 -0
  44. package/dist/chunk-6ELFRN3P.js +365 -0
  45. package/dist/chunk-6HO3NCSJ.js +1055 -0
  46. package/dist/chunk-6IUMKYWI.js +457 -0
  47. package/dist/chunk-6KEB3C3U.js +65 -0
  48. package/dist/chunk-6MR5TI5V.js +987 -0
  49. package/dist/chunk-6NNTNXN7.js +157 -0
  50. package/dist/chunk-6PBR37LN.js +97 -0
  51. package/dist/chunk-6QXLMMT3.js +155 -0
  52. package/dist/chunk-6ZGJCBZS.js +224 -0
  53. package/dist/chunk-73IVBKMA.js +250 -0
  54. package/dist/chunk-7G3CTU7I.js +286 -0
  55. package/dist/chunk-A3UDOZB6.js +222 -0
  56. package/dist/chunk-A6E77GDW.js +85 -0
  57. package/dist/chunk-AHJWCYQO.js +660 -0
  58. package/dist/chunk-AKNCFVKZ.js +36 -0
  59. package/dist/chunk-AM4FKL4J.js +237 -0
  60. package/dist/chunk-AN6BL47T.js +26 -0
  61. package/dist/chunk-AOSYVMUS.js +147 -0
  62. package/dist/chunk-APWXY5SL.js +182 -0
  63. package/dist/chunk-AWEIIR2U.js +218 -0
  64. package/dist/chunk-AWQ7BQTQ.js +73 -0
  65. package/dist/chunk-BAUZOWGX.js +393 -0
  66. package/dist/chunk-BHACRS6R.js +940 -0
  67. package/dist/chunk-BWPNT55T.js +680 -0
  68. package/dist/chunk-C6RIMRU4.js +187 -0
  69. package/dist/chunk-CGVQZXYG.js +263 -0
  70. package/dist/chunk-CIMQZRRE.js +162 -0
  71. package/dist/chunk-CJHQKJU4.js +113 -0
  72. package/dist/chunk-CTMO46PK.js +1897 -0
  73. package/dist/chunk-D53J55C5.js +106 -0
  74. package/dist/chunk-D7VGG3WS.js +90 -0
  75. package/dist/chunk-DAD3ONIS.js +245 -0
  76. package/dist/chunk-DGWHAVRG.js +162 -0
  77. package/dist/chunk-DX4QR4B2.js +483 -0
  78. package/dist/chunk-EHP3WPEG.js +21 -0
  79. package/dist/chunk-ELP2HMXC.js +432 -0
  80. package/dist/chunk-ERVIASGL.js +21 -0
  81. package/dist/chunk-ET4Y4XCB.js +865 -0
  82. package/dist/chunk-ETO6LGLD.js +4750 -0
  83. package/dist/chunk-EUUUMY7J.js +24 -0
  84. package/dist/chunk-FQJ2TTHP.js +218 -0
  85. package/dist/chunk-FQOF7E2E.js +133 -0
  86. package/dist/chunk-FWD7AL6N.js +21 -0
  87. package/dist/chunk-FXFV3KU6.js +138 -0
  88. package/dist/chunk-FXRQOWTE.js +171 -0
  89. package/dist/chunk-G554NZNI.js +210 -0
  90. package/dist/chunk-GSGDWJB5.js +13 -0
  91. package/dist/chunk-H2V3N23D.js +66 -0
  92. package/dist/chunk-H5C7MBCR.js +159 -0
  93. package/dist/chunk-HHN734BU.js +371 -0
  94. package/dist/chunk-HRVBLCVJ.js +243 -0
  95. package/dist/chunk-HVRUQXGD.js +1264 -0
  96. package/dist/chunk-I4F5TCJH.js +130 -0
  97. package/dist/chunk-IDOTCMDV.js +57 -0
  98. package/dist/chunk-IGOLHKWF.js +427 -0
  99. package/dist/chunk-JGE7CAWB.js +148 -0
  100. package/dist/chunk-JTUKLCXS.js +92 -0
  101. package/dist/chunk-JZGVVJSI.js +66 -0
  102. package/dist/chunk-K4WX6UUU.js +923 -0
  103. package/dist/chunk-K5BBGTYZ.js +217 -0
  104. package/dist/chunk-K6RXBGUB.js +427 -0
  105. package/dist/chunk-KTSDSD36.js +165 -0
  106. package/dist/chunk-L5JYN3NM.js +1038 -0
  107. package/dist/chunk-LIEOKXKD.js +354 -0
  108. package/dist/chunk-LLNZGEFK.js +51 -0
  109. package/dist/chunk-LPECSJ2J.js +55 -0
  110. package/dist/chunk-LQCMKAJ7.js +413 -0
  111. package/dist/chunk-LRN4ZJCQ.js +1145 -0
  112. package/dist/chunk-MJ4ZVHKN.js +17 -0
  113. package/dist/chunk-MXHSQOC4.js +3061 -0
  114. package/dist/chunk-N5GKCWE5.js +367 -0
  115. package/dist/chunk-NA3PXMEF.js +10 -0
  116. package/dist/chunk-NAQDMEHV.js +76 -0
  117. package/dist/chunk-NWKXM5RD.js +109 -0
  118. package/dist/chunk-NZOWWHU5.js +196 -0
  119. package/dist/chunk-OHX6MIUZ.js +184 -0
  120. package/dist/chunk-OOXWYAA4.js +15 -0
  121. package/dist/chunk-OPHB5ORL.js +1990 -0
  122. package/dist/chunk-OXA6PSZT.js +1725 -0
  123. package/dist/chunk-P2HAGF6H.js +73 -0
  124. package/dist/chunk-PTHVMXZI.js +3036 -0
  125. package/dist/chunk-PZ5AY32C.js +10 -0
  126. package/dist/chunk-Q325JKYB.js +189 -0
  127. package/dist/chunk-Q7DGTQ5G.js +255 -0
  128. package/dist/chunk-QCNYHBL7.js +601 -0
  129. package/dist/chunk-QO6BVGS5.js +145 -0
  130. package/dist/chunk-QXTKSOFI.js +76 -0
  131. package/dist/chunk-ROY4YAEQ.js +66 -0
  132. package/dist/chunk-RTJRZFFY.js +447 -0
  133. package/dist/chunk-SAVJUS2X.js +128 -0
  134. package/dist/chunk-SFLHVCKX.js +173 -0
  135. package/dist/chunk-STSFNCY4.js +127 -0
  136. package/dist/chunk-SWBF2LTK.js +170 -0
  137. package/dist/chunk-T5KELPAR.js +109 -0
  138. package/dist/chunk-TFFGOFVZ.js +1481 -0
  139. package/dist/chunk-TLHIFK4M.js +256 -0
  140. package/dist/chunk-TLQE7ZN3.js +156 -0
  141. package/dist/chunk-TPHWJ7UG.js +1981 -0
  142. package/dist/chunk-TRJCALW3.js +291 -0
  143. package/dist/chunk-TTEIYD5P.js +133 -0
  144. package/dist/chunk-U64O2MXY.js +407 -0
  145. package/dist/chunk-UCQ2VUTG.js +630 -0
  146. package/dist/chunk-UTI5G6OJ.js +44 -0
  147. package/dist/chunk-UVHDMMIP.js +188 -0
  148. package/dist/chunk-UZ6QQWJT.js +120 -0
  149. package/dist/chunk-VAX3UZOY.js +686 -0
  150. package/dist/chunk-W3HBUKFO.js +57 -0
  151. package/dist/chunk-WKQLGKCL.js +2823 -0
  152. package/dist/chunk-WQFNASK4.js +560 -0
  153. package/dist/chunk-WRBKLYSJ.js +224 -0
  154. package/dist/chunk-WTPZRR4I.js +17 -0
  155. package/dist/chunk-WZDJ2LLX.js +2385 -0
  156. package/dist/chunk-X2NL4KUO.js +14 -0
  157. package/dist/chunk-X4DVXBFY.js +38 -0
  158. package/dist/chunk-X677DMZM.js +7050 -0
  159. package/dist/chunk-XBKMWY7F.js +612 -0
  160. package/dist/chunk-XNCP4KYO.js +78 -0
  161. package/dist/chunk-XVBGLISP.js +35 -0
  162. package/dist/chunk-XW6KISBB.js +1192 -0
  163. package/dist/chunk-YGZHHAAB.js +8156 -0
  164. package/dist/chunk-YHEFQUQB.js +186 -0
  165. package/dist/chunk-YLDALTWZ.js +653 -0
  166. package/dist/chunk-YU4HSQW7.js +229 -0
  167. package/dist/chunk-ZDPP6RKB.js +27 -0
  168. package/dist/chunk-ZFDZKNGG.js +194 -0
  169. package/dist/chunk-ZIY7RNUT.js +49 -0
  170. package/dist/chunk-ZYJP6BOO.js +30 -0
  171. package/dist/cli.d.ts +1111 -0
  172. package/dist/cli.js +165 -0
  173. package/dist/codegen-command-VR2GWR6C.js +173 -0
  174. package/dist/codegen.d.ts +115 -0
  175. package/dist/codegen.js +19 -0
  176. package/dist/commands-GJMJ4XYZ.js +76 -0
  177. package/dist/completion-ZQLO6PB6.js +738 -0
  178. package/dist/config-MBYGCAFK.js +136 -0
  179. package/dist/deploy-command-JVZPWHMK.js +180 -0
  180. package/dist/deploy-source-3W45CXMP.js +81 -0
  181. package/dist/dev-env-sync-HN44K4D5.js +188 -0
  182. package/dist/diagnostics-UEAAHWVF.js +30 -0
  183. package/dist/display-name-hint-ABBPVQIR.js +46 -0
  184. package/dist/emit-CyhBDxli.d.ts +7007 -0
  185. package/dist/env-example-refresh-ET5HMMDW.js +126 -0
  186. package/dist/env-pull-command-TNMNB3CV.js +323 -0
  187. package/dist/env-target-O4HRKTGZ.js +40 -0
  188. package/dist/env-var-command-ZIRDCAZA.js +427 -0
  189. package/dist/ephemeral-P7F3PMAD.js +54 -0
  190. package/dist/ephemeral-command-CLJJKOFT.js +592 -0
  191. package/dist/ephemeral-state-PYCBSOGX.js +61 -0
  192. package/dist/file-lock-PT74GWYW.js +10 -0
  193. package/dist/generate-command-AYEOLA26.js +510 -0
  194. package/dist/gitignore-3SJ3WNRG.js +51 -0
  195. package/dist/guards-4EBNCZDC.js +162 -0
  196. package/dist/hosted-file-PSHJB3HX.js +28 -0
  197. package/dist/impersonate-command-5JAS3WSV.js +152 -0
  198. package/dist/import-EQSFU6JC.js +19 -0
  199. package/dist/index.d.ts +5470 -0
  200. package/dist/index.js +371 -0
  201. package/dist/init-command-X2FR4UAK.js +125 -0
  202. package/dist/init-modules-RY7UHAOL.js +50 -0
  203. package/dist/init-web-M4YZSZK6.js +42 -0
  204. package/dist/internal.d.ts +179 -0
  205. package/dist/internal.js +2634 -0
  206. package/dist/io-RC56QSN5.js +26 -0
  207. package/dist/keep-data-merge-7MPLVAVI.js +1119 -0
  208. package/dist/lambda-config-reconcile-OUZ4OYAB.js +116 -0
  209. package/dist/landed-753YLJ5H.js +40 -0
  210. package/dist/landing-record-SU52SKXU.js +61 -0
  211. package/dist/live-diff-YONW2VI3.js +65 -0
  212. package/dist/local-engine-choice-CH5X3W7E.js +79 -0
  213. package/dist/local-engine-command-Z3OL7VOD.js +766 -0
  214. package/dist/local-engine-config-5U3SC434.js +39 -0
  215. package/dist/local-engine-deploy-PCQ4SNPB.js +39 -0
  216. package/dist/local-engine-process-EHTE5LLU.js +63 -0
  217. package/dist/local-engine-state-A4FTRFAF.js +32 -0
  218. package/dist/local-engine-update-check-6L645VBL.js +162 -0
  219. package/dist/lock-MRRHW5GS.js +87 -0
  220. package/dist/lock-commands-2QG25HL7.js +963 -0
  221. package/dist/login-command-QPYRWOXZ.js +538 -0
  222. package/dist/logout-command-WOIQLDP6.js +56 -0
  223. package/dist/loop-ALIZLOSX.js +191 -0
  224. package/dist/marketplace-command-K5YL42NO.js +977 -0
  225. package/dist/marketplace-resolve-LY32JES5.js +40 -0
  226. package/dist/meta-client-YXGPW53C.js +244 -0
  227. package/dist/module-manifest-JFGHU2TC.js +38 -0
  228. package/dist/near-key.d.ts +13 -0
  229. package/dist/near-key.js +8 -0
  230. package/dist/node.d.ts +95 -0
  231. package/dist/node.js +969 -0
  232. package/dist/operation-outcome-TZSESNC7.js +51 -0
  233. package/dist/output-target-AL3I44GG.js +47 -0
  234. package/dist/package-manager-PRAHUSCN.js +34 -0
  235. package/dist/plan-disclosure-6G4MLZKD.js +114 -0
  236. package/dist/plugin-contributions-WRNY5ABG.js +28 -0
  237. package/dist/plugin-prompt-4VA3B4FF.js +20 -0
  238. package/dist/plugin-questions-JTSYA74Q.js +24 -0
  239. package/dist/plugin.d.ts +396 -0
  240. package/dist/plugin.js +1 -0
  241. package/dist/preflight-command-2T5Z36MI.js +321 -0
  242. package/dist/profile-command-AH3PIM4X.js +800 -0
  243. package/dist/profile-pointer-GRWJU3YD.js +31 -0
  244. package/dist/profile-select-74TRDEPD.js +23 -0
  245. package/dist/project-dependency-KSH63ODT.js +35 -0
  246. package/dist/project-sdk-REPPOIBD.js +24 -0
  247. package/dist/promote-command-PCWT5N6S.js +140 -0
  248. package/dist/prompt-II3F3LND.js +27 -0
  249. package/dist/publish-command-TTDUUJI5.js +414 -0
  250. package/dist/pull-command-VBO7C4S3.js +161 -0
  251. package/dist/pull-superseded-GJWLG7XO.js +20 -0
  252. package/dist/release-command-N4ZPKMHR.js +146 -0
  253. package/dist/release-ns-command-3KFDMZ6G.js +1745 -0
  254. package/dist/response-HlXahv4K.d.ts +925 -0
  255. package/dist/retry-command-AS5EVREX.js +34 -0
  256. package/dist/routes-manifest-SNWUVBT6.js +43 -0
  257. package/dist/runtime-XETHVF33.js +14 -0
  258. package/dist/scaffold.d.ts +1911 -0
  259. package/dist/scaffold.js +107 -0
  260. package/dist/secrets-file-LVZD6IIM.js +56 -0
  261. package/dist/secrets-fill-command-TTCPSVXN.js +218 -0
  262. package/dist/seed-VU7C3QYS.js +46 -0
  263. package/dist/sent-writes-N7X4OVXT.js +22 -0
  264. package/dist/source-resolve-S2XA3XZF.js +83 -0
  265. package/dist/static-host-B2Y6AG3W.js +352 -0
  266. package/dist/status-command-RUDXTF7I.js +554 -0
  267. package/dist/store-6VNCD4D7.js +20 -0
  268. package/dist/store-ARM6VMO5.js +98 -0
  269. package/dist/store-DuYfzKlW.d.ts +3144 -0
  270. package/dist/suggest-6FOOYKHG.js +12 -0
  271. package/dist/sveltekit.d.ts +154 -0
  272. package/dist/sveltekit.js +109 -0
  273. package/dist/sync-contributions-UCEROO26.js +415 -0
  274. package/dist/table-ENAVMITR.js +42 -0
  275. package/dist/tables-command-GUPXXSB5.js +156 -0
  276. package/dist/tenant-IIHPOHGP.js +42 -0
  277. package/dist/tenant-command-D5HGI2X4.js +965 -0
  278. package/dist/test-command-TIKZMDPZ.js +476 -0
  279. package/dist/tests-BGGIOK3L.js +26 -0
  280. package/dist/token-NICJUTMT.js +72 -0
  281. package/dist/toolchain-hooks-GHKLUJOH.js +96 -0
  282. package/dist/toolchain-modules-5EAZUONB.js +24 -0
  283. package/dist/tracked-backend-EIBUMDZA.js +80 -0
  284. package/dist/update-check-2W2NLDTC.js +136 -0
  285. package/dist/upgrade-command-QHNFWM7O.js +531 -0
  286. package/dist/verify-rollout-DRCK5TDD.js +61 -0
  287. package/dist/whoami-command-STGINTXH.js +51 -0
  288. package/dist/workspace-2CLHU2LK.js +93 -0
  289. package/dist/workspace-binding-56PIIPCP.js +54 -0
  290. package/dist/workspace-command-JEXQETJD.js +535 -0
  291. package/dist/workspace-env-F2RFMSC5.js +70 -0
  292. package/dist/workspace-export-OJSH2HIW.js +21 -0
  293. package/dist/writable-VA7OISSK.js +25 -0
  294. package/dist/xano-ICFUSPFR.js +42 -0
  295. package/dist/xanosdk-import-GN42TO2B.js +56 -0
  296. package/dist/xanosdk-project-S6DXJONQ.js +41 -0
  297. package/dist/xdo-CxDr2Q1C.d.ts +1371 -0
  298. package/guides/README.md +33 -0
  299. package/guides/authoring.md +921 -0
  300. package/guides/cli.md +401 -0
  301. package/guides/codegen.md +177 -0
  302. package/guides/codes.md +404 -0
  303. package/guides/coverage.md +74 -0
  304. package/guides/deploying.md +1525 -0
  305. package/guides/environment.md +400 -0
  306. package/guides/marketplace.md +415 -0
  307. package/guides/module-contract.md +558 -0
  308. package/guides/object-kinds.md +433 -0
  309. package/guides/project-structure.md +43 -0
  310. package/guides/scaffold.md +351 -0
  311. package/guides/typed-frontend.md +242 -0
  312. package/llms/client.md +63 -0
  313. package/llms/errors.md +56 -0
  314. package/llms/fields.md +176 -0
  315. package/llms/filters.md +183 -0
  316. package/llms/kinds-agent-mcp.md +30 -0
  317. package/llms/kinds-core.md +100 -0
  318. package/llms/kinds-knowledge.md +20 -0
  319. package/llms/kinds-realtime.md +76 -0
  320. package/llms/lambda.md +94 -0
  321. package/llms/legacy.md +44 -0
  322. package/llms/lock.md +40 -0
  323. package/llms/object-kinds.md +34 -0
  324. package/llms/statements-calls.md +62 -0
  325. package/llms/statements-catalog.md +320 -0
  326. package/llms/statements-data.md +66 -0
  327. package/llms/statements-runtime.md +68 -0
  328. package/llms/tests.md +59 -0
  329. package/llms/toolchain.md +16 -0
  330. package/llms/triggers.md +36 -0
  331. package/llms/values.md +29 -0
  332. package/llms-full.txt +2085 -0
  333. package/llms.txt +493 -0
  334. package/manifest.json +11224 -0
  335. package/package.json +162 -0
  336. package/types/lambda-globals.d.ts +129 -0
@@ -0,0 +1,1990 @@
1
+ import {
2
+ decodeMarkerWith,
3
+ iconLibraryOf,
4
+ reactPreset
5
+ } from "./chunk-UCQ2VUTG.js";
6
+ import {
7
+ composeBlock,
8
+ gitattributesSpec,
9
+ upsertBlock
10
+ } from "./chunk-DGWHAVRG.js";
11
+ import {
12
+ colorTokens,
13
+ defaultThemeChoice,
14
+ fontDependencies,
15
+ fontImports,
16
+ fontThemeVars,
17
+ orderedTokens
18
+ } from "./chunk-TFFGOFVZ.js";
19
+
20
+ // src/emit/init-templates.ts
21
+ var STANDALONE_NPM = { manager: "npm", member: "", declared: false };
22
+ function sdkDep(sdkVersion) {
23
+ const parsed = /^(\d+)\.(\d+)\.\d+/.exec(sdkVersion);
24
+ if (!parsed) return ">=1.0.0 <2.0.0";
25
+ const [major, minor] = [Number(parsed[1]), Number(parsed[2])];
26
+ const ceiling = major > 0 ? `${major + 1}.0.0` : `0.${minor + 1}.0`;
27
+ return `>=${sdkVersion} <${ceiling}`;
28
+ }
29
+ function sortedKeys(map) {
30
+ return Object.fromEntries(Object.entries(map).sort(([a], [b]) => a.localeCompare(b, "en")));
31
+ }
32
+ function renderPackageJson({ appName, sdkVersion }, preset, choice = defaultThemeChoice()) {
33
+ const check = `${preset.checkCmd} && tsc -p xano/lambdas`;
34
+ const routes = "xanosdk routes ./xano/index.ts --emit xano/routes.gen.ts";
35
+ const pkg = {
36
+ name: appName,
37
+ version: "0.1.0",
38
+ private: true,
39
+ type: "module",
40
+ scripts: {
41
+ dev: "npm run xano:routes && vite",
42
+ build: `npm run xano:routes && ${check} && vite build`,
43
+ preview: "vite preview",
44
+ typecheck: `npm run xano:routes && ${check}`,
45
+ "xano:routes": routes,
46
+ // Both are prefixed with a typecheck: the export pass validates what it
47
+ // can see in the encoded bundle, and TypeScript validates the rest. With
48
+ // `tsc` on `build` alone, anyone following the documented workflow would
49
+ // ship with the compiler's half of the checks never run.
50
+ // No `--lock` on either of these: every build maintains `xano/xano.lock`
51
+ // by default, so the flag would be redundant here and would read as
52
+ // though the lock were something a project opts into. It is not — object
53
+ // identity derives from `(type, name)`, and without a lock a rename
54
+ // re-derives the guid and the engine does delete-and-recreate instead of
55
+ // rename-in-place, so the file has to exist before the identities matter.
56
+ // Scaffolds written before the default flipped still pass `--lock`, and
57
+ // it stays accepted for exactly that reason.
58
+ "xano:export": `npm run xano:routes && ${check} && xanosdk export ./xano/index.ts --out workspace.json`,
59
+ // Runs `build` rather than `check` directly: `--static ./frontend/dist`
60
+ // needs that directory to EXIST, and only `vite build` writes it. Pointing
61
+ // the flag at a directory the script never produced meant a fresh clone
62
+ // running the one documented command had no `dist` to ship.
63
+ // `build` already prefixes the same typecheck, so this is one check, not
64
+ // two.
65
+ "xano:deploy": `npm run build && xanosdk deploy ./xano/index.ts --static ./frontend/dist`,
66
+ // The tight loop: redeploy the backend to an engine on this machine.
67
+ //
68
+ // No `--static` and no `npm run build`, and both omissions are the point
69
+ // rather than an economy: there is no frontend to ship here. The dev
70
+ // server is still serving it, and the deploy points that server at the
71
+ // engine on its way past, so the loop is backend-only by design.
72
+ // (`--static` does work on a local engine, for a built frontend the
73
+ // engine itself should serve; this loop has none.)
74
+ //
75
+ // The flag is bare deliberately. The engine's download URL is passed by
76
+ // hand once and cached for every run after; baking one into a scaffolded
77
+ // project would ship a coordinate that is not this project's to carry.
78
+ //
79
+ // `--keep-data`, because the engine is the developer's own backend and
80
+ // this loop runs once per code change: each run merges into what the last
81
+ // one left, so rows entered through the app survive the edit. The first
82
+ // run, and the first after the engine restarts, replaces and seeds.
83
+ // `--reset` on the command line still gives a clean slate.
84
+ "xano:deploy:local-engine": `npm run xano:routes && ${check} && xanosdk deploy ./xano/index.ts --local-engine --keep-data`,
85
+ // The frontend-only loop: rebuild and republish to the environment this
86
+ // project last deployed to, without recompiling or re-importing the
87
+ // backend. `build` runs first because publish ships only what is on disk.
88
+ "xano:deploy:frontend": "npm run build && xanosdk publish ./frontend/dist",
89
+ // The CI guard: fails instead of changing xano.lock, so an uncommitted
90
+ // identity change is caught in review rather than on a deploy. An
91
+ // installed toolchain module's own check rides on the same run: a module
92
+ // that owns a generated tree verifies it under this same flag.
93
+ // `--strict` fails on a stale or missing committed manifest rather than
94
+ // rewriting it, the same way `--frozen-lock` treats xano.lock.
95
+ //
96
+ // `export --check`, not an export to a file: a CI checkout has no
97
+ // `xano/.env` and no `xano/.secrets.json`, and a real export refuses a
98
+ // declared documentation gate it cannot supply. The check writes nothing
99
+ // — no bundle, no lock — so there is nothing a missing secret could clear,
100
+ // and it needs none. It implies `--frozen-lock`. `--strict` as the grounding
101
+ // tells every CI and unattended build to: a build warning is a hard
102
+ // failure. Env values never reach it — a declared name with no value is
103
+ // `""`, which only `deploy` refuses — so a checkout with no `xano/.env`
104
+ // passes it (a fresh scaffold and an `init --from` tree both do).
105
+ "xano:check": `${routes} --strict && ${check} && xanosdk export ./xano/index.ts --check --strict`,
106
+ // No compile step and no entry file: this runs what is DEPLOYED, so it
107
+ // pairs with `xano:deploy` rather than repeating its work. Exits 5 on a
108
+ // failing suite, which is what makes it usable as a CI gate.
109
+ "xano:test": "xanosdk test run-all",
110
+ // Framework-owned scripts, last so a preset can add to the set but the
111
+ // shared xano:* contract above stays the same in every scaffold.
112
+ ...preset.extraScripts
113
+ },
114
+ // Sorted, as npm writes them: an unsorted map is reordered by the first
115
+ // `npm install`, a diff in a file nobody edited.
116
+ dependencies: sortedKeys({
117
+ // `@xano/sdk` is the only Xano SDK dependency a scaffold ships with.
118
+ // Add-ons (`@xano-sdk/auth`, and the packages that follow it) are installed
119
+ // on demand — a project that never registers auth should not carry it,
120
+ // and an add-on's release cadence is its own, not the CLI's.
121
+ "@xano/sdk": sdkDep(sdkVersion),
122
+ ...preset.dependencies,
123
+ // The icon set and the typefaces are choices, not framework constants, so
124
+ // they are merged here rather than sitting in `preset.dependencies`. Both
125
+ // land after the preset's own entries: a preset that still names an icon
126
+ // package would otherwise win over the set the user actually picked.
127
+ ...preset.iconBinding(choice.icons).dependency,
128
+ ...fontDependencies(choice.fonts)
129
+ }),
130
+ devDependencies: sortedKeys({ ...preset.devDependencies }),
131
+ // Not ">=20": Vite 8 and vite-plugin-svelte 7 both declare
132
+ // `^20.19 || >=22.12`, so 20.0–20.18 installs the toolchain and then fails
133
+ // to run it. Stating the real floor turns that into an install-time
134
+ // warning instead of a confusing crash.
135
+ engines: { node: `>=${NODE_MIN}` }
136
+ };
137
+ return JSON.stringify(pkg, null, 2) + "\n";
138
+ }
139
+ function renderTsconfig(preset) {
140
+ const aliasPaths = Object.keys(preset.tsconfigPaths).length > 0 ? { paths: { ...preset.tsconfigPaths } } : {};
141
+ const tsconfig = {
142
+ // Present only when the framework generates a config to build on — see
143
+ // FrontendPreset.tsconfigExtends. The `include` below is re-declared on
144
+ // top of it deliberately: an extended config's `include` is REPLACED, not
145
+ // merged, and dropping `xano` here would leave the backend unchecked while
146
+ // every command still exits 0.
147
+ ...preset.tsconfigExtends === void 0 ? {} : { extends: preset.tsconfigExtends },
148
+ compilerOptions: {
149
+ target: "ES2022",
150
+ lib: ["ES2022", "DOM", "DOM.Iterable"],
151
+ module: "ESNext",
152
+ moduleResolution: "bundler",
153
+ ...preset.tsconfigOptions,
154
+ strict: true,
155
+ noEmit: true,
156
+ esModuleInterop: true,
157
+ skipLibCheck: true,
158
+ resolveJsonModule: true,
159
+ isolatedModules: true,
160
+ types: ["node", "vite/client"],
161
+ ...aliasPaths
162
+ },
163
+ // Both halves of the project typecheck together: the Xano SDK backend and
164
+ // the frontend that derives its types from the backend's defs. A preset
165
+ // that extends a generated config appends whatever that config listed —
166
+ // `include` is replaced, not merged, so anything it declared is otherwise
167
+ // silently dropped. See FrontendPreset.tsconfigInclude.
168
+ include: ["xano", "frontend/src", ...preset.tsconfigInclude ?? []],
169
+ // The lambda modules type-check under their own config, which declares the
170
+ // lambda runtime's globals; see renderLambdaTsconfig.
171
+ exclude: ["node_modules", "xano/lambdas"]
172
+ };
173
+ return JSON.stringify(tsconfig, null, 2) + "\n";
174
+ }
175
+ var LAMBDA_GLOBALS_TYPES = "@xano/sdk/lambda-globals";
176
+ function renderLambdaTsconfig() {
177
+ const tsconfig = {
178
+ extends: "../../tsconfig.json",
179
+ // No DOM and no node types: a lambda body runs in neither.
180
+ compilerOptions: { lib: ["ES2022"], types: [LAMBDA_GLOBALS_TYPES] },
181
+ files: [],
182
+ include: ["**/*.ts"],
183
+ exclude: []
184
+ };
185
+ return JSON.stringify(tsconfig, null, 2) + "\n";
186
+ }
187
+ function renderViteConfig(preset) {
188
+ const root = preset.viteRoot === void 0 ? "frontend" : preset.viteRoot;
189
+ const build = preset.viteBuild === void 0 ? { outDir: "dist", emptyOutDir: true } : preset.viteBuild;
190
+ const header = root === null ? `// Vite's root is the project root. The framework resolves its own file
191
+ // locations from the plugin config below \u2014 this file is the ONLY place that
192
+ // config lives \u2014 and routes the build through its adapter, which writes
193
+ // frontend/dist, the directory \`npm run xano:deploy\` ships as the frontend.` : `// Vite's root is the ${root}/ folder, so index.html and the app live there
194
+ // while the Xano SDK backend sits in xano/ as a peer. The build lands in
195
+ // ${root}/dist, which \`npm run xano:deploy\` ships as the static frontend.`;
196
+ const rootBlock = root === null ? "" : ` root: ${JSON.stringify(root)},
197
+ // Vite resolves \`.env\` files against \`root\`, which is ${root}/ here \u2014 but
198
+ // \`.env.example\` sits at the project root, so that is where anyone will
199
+ // actually put their \`.env.local\`. Point envDir back at this file's own
200
+ // directory so VITE_XANO_HOST is picked up in dev.
201
+ envDir: fileURLToPath(new URL(".", import.meta.url)),
202
+ `;
203
+ const buildBlock = build === null ? "" : ` build: ${renderInlineObject(build)},
204
+ `;
205
+ const aliasBlock = preset.aliasName === null ? "" : ` resolve: {
206
+ alias: {
207
+ // The alias the UI kit writes its imports against. Resolved from this
208
+ // file rather than from Vite's root so it points at the right directory
209
+ // either way. Keep in sync with the \`paths\` entry in tsconfig.json.
210
+ "${preset.aliasName}": fileURLToPath(new URL("./${preset.aliasTarget}", import.meta.url)),
211
+ },
212
+ },
213
+ `;
214
+ const fileUrlImport = rootBlock === "" && aliasBlock === "" ? "" : `import { fileURLToPath } from "node:url";
215
+ `;
216
+ return `${fileUrlImport}import { defineConfig } from "vite";
217
+ ${preset.viteImports.join("\n")}
218
+
219
+ ${header}
220
+ export default defineConfig({
221
+ ${rootBlock}${buildBlock} plugins: [${preset.vitePlugins.join(", ")}],
222
+ ${aliasBlock} server: { host: "127.0.0.1", port: 5173 },
223
+ });
224
+ `;
225
+ }
226
+ function renderInlineObject(obj) {
227
+ const entries = Object.entries(obj).map(([k, v]) => `${k}: ${JSON.stringify(v)}`);
228
+ return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
229
+ }
230
+ function renderIndexHtml({ appName }, preset, choice = defaultThemeChoice()) {
231
+ if (preset.entryScript === void 0) {
232
+ throw new Error(`The ${preset.id} preset owns its own HTML entry; renderIndexHtml does not apply.`);
233
+ }
234
+ return `<!doctype html>
235
+ <html lang="en">
236
+ <head>
237
+ <meta charset="UTF-8" />
238
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
239
+ <title>${appName}</title>
240
+ ${renderThemeScript(choice.dark)} </head>
241
+ <body>
242
+ <div id="root"></div>
243
+ <script type="module" src="${preset.entryScript}"></script>
244
+ </body>
245
+ </html>
246
+ `;
247
+ }
248
+ function renderGitignore(preset) {
249
+ const framework = (preset?.gitignoreEntries ?? []).map((e) => `${e}
250
+ `).join("");
251
+ return `node_modules/
252
+ dist/
253
+ workspace.json
254
+ .xano/
255
+ .env
256
+ .env.tmp-*
257
+ .env.local
258
+ *.local
259
+ .secrets.json
260
+ .secrets.json.tmp-*
261
+ ${framework}
262
+ # Ignored, on purpose: the bare \`.env\` above has no slash, so it matches at ANY
263
+ # depth \u2014 including xano/.env, which holds the VALUES of this backend's env vars.
264
+ # Its committed template is xano/.env.example, which lists the names only.
265
+ #
266
+ # \`.secrets.json\` is the second exception, and the same rule applies to it: it
267
+ # matches at any depth, so it covers xano/.secrets.json, which holds the tokens a
268
+ # pull brought down and a deploy sends back. The SDK writes that file; nobody
269
+ # edits it. The \`.tmp-*\` lines cover the staging file an atomic write of
270
+ # either leaves behind if the process dies between writing and renaming.
271
+ #
272
+ # Those two files are the sole exceptions inside an otherwise fully committed
273
+ # xano/. Do not "tidy" this by ignoring xano/ wholesale \u2014 the source is the
274
+ # review surface.
275
+ #
276
+ # Not ignored, on purpose: xano/xano.lock pins object identity and public URLs
277
+ # across renames and environments. Every build writes it. Commit it \u2014 ignoring
278
+ # it means each build mints identities that are thrown away and re-invented.
279
+ `;
280
+ }
281
+ var NODE_MIN = "20.19";
282
+ function renderGitattributes(contributions = []) {
283
+ let text = `# The export writes LF. Normalizing here keeps generated output
284
+ # byte-identical on every checkout, so \`npm run xano:check\` cannot fail over a
285
+ # contributor's core.autocrlf setting rather than over a real change.
286
+ * text=auto eol=lf
287
+ `;
288
+ for (const { pkg, version, lines } of contributions) {
289
+ const spec = gitattributesSpec(pkg);
290
+ text = upsertBlock(text, spec, composeBlock(spec, lines, version)).text;
291
+ }
292
+ return text;
293
+ }
294
+ function checkJobDir(install) {
295
+ return [install?.repoDir ?? "", install?.member ?? ""].filter((p) => p !== "").join("/");
296
+ }
297
+ function checkWorkflowName(install) {
298
+ const jobDir = checkJobDir(install);
299
+ if (jobDir === "") return "check.yml";
300
+ const slug = jobDir.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|-+$/g, "");
301
+ return `xanosdk-${slug === "" ? "project" : slug}.yml`;
302
+ }
303
+ function checkWorkflowPath(install) {
304
+ return `.github/workflows/${checkWorkflowName(install)}`;
305
+ }
306
+ function renderCheckWorkflow(install = STANDALONE_NPM) {
307
+ const { manager, member } = install;
308
+ const repoDir = install.repoDir ?? "";
309
+ const jobDir = checkJobDir(install);
310
+ const name = jobDir === "" ? "check" : checkWorkflowName(install).replace(/\.yml$/, "");
311
+ const memberNote = member !== "" ? `# This project is the workspace member ${member}. GitHub runs workflows only
312
+ # from the repository root's .github/workflows/, so move this file there. Its
313
+ # steps assume the workspace root is ${repoDir === "" ? "the repository root" : `${repoDir}/ in the repository`}: the install runs
314
+ # there, every other step in ${jobDir}.
315
+ ` : repoDir !== "" ? `# This project is ${repoDir}/ in its git repository. GitHub runs workflows
316
+ # only from the repository root's .github/workflows/, so move this file there.
317
+ # Its steps run in ${repoDir}.
318
+ ` : "";
319
+ const defaults = jobDir === "" ? "" : ` defaults:
320
+ run:
321
+ working-directory: ${JSON.stringify(jobDir)}
322
+ `;
323
+ const atRoot = member === "" ? "" : ` working-directory: ${repoDir === "" ? "." : JSON.stringify(repoDir)}
324
+ `;
325
+ return `${memberNote}name: ${name}
326
+
327
+ on:
328
+ push:
329
+ branches: [main]
330
+ pull_request:
331
+
332
+ jobs:
333
+ check:
334
+ runs-on: ubuntu-latest
335
+ ${defaults} steps:
336
+ - uses: actions/checkout@v4
337
+
338
+ - uses: actions/setup-node@v4
339
+ with:
340
+ node-version: '${NODE_MIN}'
341
+ ${managerSetup(install)}
342
+ # A frozen install whenever a lockfile is committed: package.json allows a
343
+ # @xano/sdk range that the lock pins, and an open install would rebuild
344
+ # derived state with whatever patch shipped most recently, failing the
345
+ # check below on a change that touched nothing relevant. The fallback
346
+ # covers a tree scaffolded with \`--no-install\`, which has no lockfile yet.
347
+ - name: Install
348
+ ${atRoot} run: ${frozenInstall(manager, install.ignoreWorkspace === true)}
349
+
350
+ # The gate. Typechecks, then runs
351
+ # \`npx xanosdk export ./xano/index.ts --check --strict\`: every check an export runs,
352
+ # writing nothing, so it needs no secrets. It fails instead of rewriting
353
+ # when xano.lock disagrees with the source, naming what would change.
354
+ # It also fails while the lock carries an entry no exported object
355
+ # matches, which is how a rename looks until its identity is moved across
356
+ # with \`npx xanosdk lock rename\` (or dropped with \`npx xanosdk lock prune\`). An
357
+ # installed toolchain module's own frozen check rides on this same run.
358
+ - name: Typecheck, check the export, and fail on stale derived state
359
+ run: ${manager} run xano:check
360
+
361
+ # Backstop: the step above covers the lock and any module's own check.
362
+ # This catches anything else the check leaves changed in the working tree.
363
+ - name: Working tree must be clean after the export
364
+ run: git diff --exit-code
365
+ `;
366
+ }
367
+ function frozenInstall(manager, ignoreWorkspace = false) {
368
+ switch (manager) {
369
+ case "npm":
370
+ return "if [ -f package-lock.json ]; then npm ci; else npm install; fi";
371
+ case "pnpm":
372
+ return ignoreWorkspace ? "if [ -f pnpm-lock.yaml ]; then pnpm install --ignore-workspace --frozen-lockfile; else pnpm install --ignore-workspace; fi" : "if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; else pnpm install; fi";
373
+ case "yarn":
374
+ return "if [ -f yarn.lock ]; then yarn install --frozen-lockfile; else yarn install; fi";
375
+ case "bun":
376
+ return "if [ -f bun.lock ] || [ -f bun.lockb ]; then bun install --frozen-lockfile; else bun install; fi";
377
+ }
378
+ }
379
+ function managerSetup({ manager, declared, version }) {
380
+ const major = version?.split(".")[0];
381
+ switch (manager) {
382
+ case "npm":
383
+ return "";
384
+ case "pnpm":
385
+ return `
386
+ - uses: pnpm/action-setup@v4
387
+ ${declared ? "" : ` with:
388
+ version: '${major ?? "latest"}'
389
+ `}`;
390
+ case "yarn":
391
+ return declared ? `
392
+ - run: corepack enable
393
+ ` : "";
394
+ case "bun":
395
+ return `
396
+ - uses: oven-sh/setup-bun@v2
397
+ ${version === void 0 ? "" : ` with:
398
+ bun-version: '${version}'
399
+ `}`;
400
+ }
401
+ }
402
+ function renderEnvExample() {
403
+ return `# Point the frontend at a deployed Xano backend. Leave unset to run the UI
404
+ # with no backend. When you \`npm run xano:deploy\`, the backend URL is injected
405
+ # as window.XANO_HOST at runtime instead \u2014 no rebuild needed.
406
+ VITE_XANO_HOST=https://your-instance.xano.io
407
+ `;
408
+ }
409
+ function renderReadme({ appName, install }, preset, choice = defaultThemeChoice()) {
410
+ return `# ${appName}
411
+
412
+ A [Xano SDK](https://www.npmjs.com/package/@xano/sdk) project: a Xano
413
+ backend authored in TypeScript under [\`xano/\`](xano/), and a ${preset.label}
414
+ frontend under [\`frontend/\`](frontend/) that derives its request paths and
415
+ types from the backend defs \u2014 so the two can't drift.
416
+
417
+ ## Quick start
418
+
419
+ \`\`\`bash
420
+ ${install?.manager ?? "npm"} install${install?.ignoreWorkspace === true ? " --ignore-workspace" : ""}
421
+ npm run dev # run the frontend (no backend needed yet)
422
+ \`\`\`
423
+
424
+ Then author your backend in [\`xano/index.ts\`](xano/index.ts) \u2014 start with the
425
+ walkthrough in [\`xano/EXAMPLE.md\`](xano/EXAMPLE.md).
426
+
427
+ ## Deploy
428
+
429
+ \`\`\`bash
430
+ npx xanosdk login # once, to authenticate against your Xano account
431
+ npm run xano:deploy # build the frontend, then ship it with the backend
432
+ \`\`\`
433
+
434
+ - \`npm run xano:deploy:local-engine\` redeploys just the backend to a local engine,
435
+ for a tight loop with no network round-trip. Keep \`npm run dev\` running \u2014 the
436
+ deploy points it at the engine. It works on a fresh machine with nothing set: the
437
+ first run downloads the latest engine once per machine and pins its version in
438
+ \`package.json\` (commit that change, so everyone on the project runs the same
439
+ engine). When a newer engine ships, a deploy offers it and never applies it without
440
+ a yes; \`npx xanosdk local-engine update\` moves the pin on purpose, and
441
+ \`npx xanosdk local-engine cache clear\` reclaims the disk space. To try another engine
442
+ without moving the pin, export \`XANOSDK_LOCAL_ENGINE_OVERRIDE\` with a version
443
+ (\`v0.1.5\`) or an engine archive path. It keeps the engine's
444
+ table rows across redeploys (\`--keep-data\`): the first run seeds, later runs merge
445
+ your changes in without re-seeding. Rows survive only in tables and columns that
446
+ keep their names \u2014 a rename drops the old one with its rows \u2014 and only while the
447
+ engine runs; an engine update or restart starts it empty and the next run seeds
448
+ again. \`npm run xano:deploy:local-engine -- --reset\` gives a clean, re-seeded slate.
449
+ - \`npm run xano:deploy:frontend\` rebuilds the frontend and republishes it to the
450
+ environment this project last deployed to \u2014 no backend compile or import, so it
451
+ is the quick loop for UI-only changes. \`publish\` does not reach a local engine, so after
452
+ a local-engine deploy run it as \`npm run xano:deploy:frontend -- --to ephemeral\`, or
453
+ have the engine serve a build with \`--static ./frontend/dist\` on the local deploy.
454
+ \`npx xanosdk publish ./frontend/dist --to workspace\` puts the same build in front of
455
+ your real workspace instead.
456
+ - \`npm run xano:export\` compiles the backend to \`workspace.json\` (don't commit it).
457
+ - \`npm run xano:deploy\` deploys the backend and the built frontend to a live
458
+ **ephemeral** environment and prints its URL. Run it again to refresh the same
459
+ environment; if it expired, a fresh one is created and the new URL is called out.
460
+ - \`npx xanosdk status\` says who you are signed in as, which workspace you are bound to, and
461
+ which environment this project last deployed to \u2014 its URL, and when it expires. You
462
+ never have to remember the environment's name.
463
+ - \`npm run xano:test\` runs the tests the DEPLOYED environment carries \u2014 the \`tests\`
464
+ on a query/function/middleware and any \`workflowTest()\`. It compiles nothing, so
465
+ deploy first. A failing suite exits 5, distinct from a crash. \`npx xanosdk deploy
466
+ ./xano/index.ts --test\` does both in one step.
467
+
468
+ ## \`xano.lock\` \u2014 commit it
469
+
470
+ Object identity derives from \`(type, name)\`, so a rename would otherwise change
471
+ an object's guid and the engine would **delete and recreate** it rather than
472
+ renaming it in place \u2014 losing its rows on a record-preserving import.
473
+ [\`xano/xano.lock\`](xano/xano.lock) freezes each guid and each API group's
474
+ canonical slug, so renames and re-deploys keep the same identities (and the same
475
+ public URLs).
476
+
477
+ Every build writes it \u2014 no flag \u2014 and it **must be committed**. Ignoring it means
478
+ each build mints identities and public URLs that are thrown away and re-invented
479
+ next time. If you release to a workspace that already exists, adopt what it
480
+ already serves first with \`npx xanosdk lock import <live-bundle.json> --lock=xano/xano.lock\`;
481
+ that is also the recovery path once identities have drifted.
482
+
483
+ \`\`\`bash
484
+ npm run xano:check # CI: fail on ANY build warning (it runs --strict), if the export
485
+ # would change xano.lock, or if the lock carries an entry no
486
+ # object matches \u2014 writes nothing and needs no secrets, so a
487
+ # fresh clone runs it as-is
488
+ \`\`\`
489
+
490
+ To rename an object: rename it in code and run \`npm run xano:export\`. Only if it
491
+ warns of an orphaned entry, run the \`npx xanosdk lock rename <kind> <old> <new>\` it prints, then
492
+ export again. Every \`lock\` subcommand finds \`xano/xano.lock\` from the project entry, as
493
+ \`export\` does \u2014 \`--entry=<file>\` names another entry, \`--lock=<path>\` the lock itself.
494
+
495
+ ## The one contract
496
+
497
+ [\`frontend/src/lib/api.ts\`](frontend/src/lib/api.ts) takes request paths from
498
+ \`xano/routes.gen.ts\` (\`routePath("GET notes/{id}", { id })\`) and request/response
499
+ types from the query defs (\`import type\` + \`InferInput\` / \`InferResponse\`).
500
+ Never hand-type a URL or a request body \u2014 change a def and the frontend follows.
501
+
502
+ \`npm run xano:routes\` writes \`xano/routes.gen.ts\` from the defs; \`dev\`, \`build\`
503
+ and \`typecheck\` run it first, and \`xano:check\` fails on a missing or stale copy \u2014 commit it.
504
+ \`init --from\` and \`pull\` write it with the decoded backend.
505
+ It is plain data that imports nothing. Importing a def as a value for its
506
+ \`getPath()\` would instead pull the backend graph and the SDK runtime into the
507
+ browser bundle.
508
+
509
+ > To spot-check a def from Node (read \`getPath()\`/\`verb\`, log a value), run a real
510
+ > file with \`tsx <file.ts>\` **from inside the project root** \u2014 not \`tsx -e\`, not
511
+ > bare \`node file.ts\`, and not from another directory (they mis-resolve the
512
+ > intra-workspace \`.js\` imports and the \`@xano/sdk\` specifier). Or use
513
+ > \`npx xanosdk routes xano/index.ts\` to list every endpoint's verb + path.
514
+
515
+ ## The frontend
516
+
517
+ ${preset.readmeFrontendSection(choice.icons)}
518
+
519
+ ${renderReadmeThemingSection(choice)}
520
+
521
+ ## Add-ons
522
+
523
+ Xano SDK is composable with other \`@xano-sdk/*\` packages:
524
+
525
+ - **[\`@xano-sdk/auth\`](https://www.npmjs.com/package/@xano-sdk/auth)** \u2014 turnkey
526
+ authentication (user/login/signup tables and endpoints). Install it with
527
+ \`npx xanosdk marketplace install @xano-sdk/auth\`, then register it in
528
+ \`xano/index.ts\`. Authentication only \u2014 **not** authorization: it has no
529
+ roles, permissions, or route guards, and its tokens carry no role claim.
530
+ Enforce roles off the caller's row with \`@xano/sdk\`: spread
531
+ \`...guard.role(userTable, "admin")\` into the endpoint's stack.
532
+ - More \`@xano-sdk/*\` packages register onto the same workspace. This list
533
+ does not update itself \u2014 run \`npx xanosdk marketplace list\` for the live
534
+ catalogue, \`npx xanosdk marketplace search <words>\` to narrow it, and
535
+ \`npx xanosdk marketplace details <package>\` to see what an add-on installs and
536
+ how to register it. All three work before you log in.
537
+
538
+ None of these ship with the scaffold. Install one only when you need it \u2014 an
539
+ add-on you never register is weight in \`package.json\` for nothing.
540
+ `;
541
+ }
542
+ function renderXanoIndex({ appName }) {
543
+ return `import { workspace } from "@xano/sdk";
544
+
545
+ /**
546
+ * The ${appName} backend.
547
+ *
548
+ * A workspace is assembled by registering typed objects onto a workspace()
549
+ * instance and default-exporting it. This starter is intentionally empty and
550
+ * already compiles + deploys \u2014 add your first table and endpoint below.
551
+ *
552
+ * \u2500\u2500 Add your first table + endpoint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
553
+ *
554
+ * import { workspace, table, apiGroup, query, f, input, s, ref, c, expect, resp } from "@xano/sdk";
555
+ *
556
+ * const notes = table({
557
+ * name: "notes",
558
+ * // \`id\` (int PK) + \`created_at\` (epochms) are auto-injected.
559
+ * schema: {
560
+ * body: f.text({ required: true }),
561
+ * },
562
+ * });
563
+ *
564
+ * const api = apiGroup({ name: "notes", canonical: "notes" }); // pin the slug
565
+ *
566
+ * const createNote = query({
567
+ * name: "create_note",
568
+ * verb: "POST",
569
+ * apiGroup: api,
570
+ * input: { body: input.text({ required: true }) },
571
+ * // ...build the stack with the s.* statement helpers...
572
+ * // Assertions ride along with the object they cover; \`npm run xano:test\`
573
+ * // runs them against whatever you last deployed.
574
+ * tests: [
575
+ * {
576
+ * name: "creates a note",
577
+ * input: { body: c.text("hello") },
578
+ * expect: [expect.to_be_defined(resp())],
579
+ * },
580
+ * ],
581
+ * });
582
+ *
583
+ * export default workspace("${appName}")
584
+ * .registerTables([notes])
585
+ * .registerApiGroups([api])
586
+ * .registerQueries([createNote]);
587
+ *
588
+ * Discover the exact builders and options from the package's own types and its
589
+ * shipped docs \u2014 read \`node_modules/@xano/sdk/llms.txt\` first (it ends with a
590
+ * map of the \`llms/*.md\` topic files), then the .d.ts files.
591
+ * See \`xano/EXAMPLE.md\` for the full walkthrough.
592
+ *
593
+ * \u2500\u2500 Optional add-ons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
594
+ * Nothing below is installed. Reach for an add-on when you need it, not before.
595
+ *
596
+ * @xano-sdk/auth registers turnkey auth (user/login/signup) onto this same
597
+ * workspace. Install it first (\`npx xanosdk marketplace install @xano-sdk/auth\`), then
598
+ * \`registerAuth(workspace("${appName}"), { canonical: "authn" })\` returns the
599
+ * instance to chain your own .register*() calls onto:
600
+ *
601
+ * registerAuth(workspace("${appName}"), { canonical: "authn" })
602
+ * .registerTables([notes])
603
+ * .registerApiGroups([api])
604
+ * .registerQueries([createNote]);
605
+ *
606
+ * That is not the whole catalogue. \`npx xanosdk marketplace list\` prints every
607
+ * published add-on and \`npx xanosdk marketplace details <package>\` prints what one
608
+ * installs plus the registration to paste here \u2014 no login required.
609
+ */
610
+ export default workspace("${appName}");
611
+ `;
612
+ }
613
+ function renderXanoExampleMd({ appName }) {
614
+ return `# Building your ${appName} backend
615
+
616
+ The backend lives in [\`index.ts\`](index.ts) and is a single default-exported
617
+ \`workspace()\`. You grow it by registering typed objects.
618
+
619
+ ## Learn the library from the library
620
+
621
+ Everything you need is in the package itself:
622
+
623
+ - \`node_modules/@xano/sdk/llms.txt\` \u2014 the router: the mental model, the deploy contract, every gotcha, and control flow. Read it in full first; it ends with a list of topic files and the condition for opening each.
624
+ - \`node_modules/@xano/sdk/llms/*.md\` \u2014 one file per surface. Open the one or two whose condition matches the task; skip the rest.
625
+ - The published TypeScript types and JSDoc (\`node_modules/@xano/sdk/**/*.d.ts\`).
626
+ - \`node_modules/@xano/sdk/manifest.json\` \u2014 the exhaustive reference; grep or \`jq\` the one entry you need rather than reading it whole.
627
+
628
+ Author against those signatures \u2014 don't invent an API that isn't there.
629
+
630
+ ## The shape
631
+
632
+ \`\`\`
633
+ xano/
634
+ \u251C\u2500\u2500 index.ts default export: the workspace registering everything below
635
+ \u251C\u2500\u2500 tables/<name>.ts a table (name, typed schema, indexes)
636
+ \u251C\u2500\u2500 api/<group>.ts an API group; pin its canonical slug so paths are stable
637
+ \u2514\u2500\u2500 api/<endpoint>.ts a query: name, verb, apiGroup, typed input, a stack, a response
638
+ \`\`\`
639
+
640
+ ## Steps
641
+
642
+ 1. **Define a table** under \`tables/\` with \`table({ name, schema: { ... } })\`.
643
+ \`id\` and \`created_at\` are auto-injected.
644
+ 2. **Define an API group** with \`apiGroup({ name, canonical })\`. Pinning the
645
+ canonical slug keeps the public path stable and lets \`npm run xano:routes\`
646
+ resolve it without a lock file.
647
+ 3. **Define endpoints** with \`query({ name, verb, apiGroup, input, ... })\`, building
648
+ the logic from the \`s.*\` statement helpers and the expression/column/input/
649
+ reference helpers.
650
+ 4. **Register everything** in \`index.ts\`:
651
+ \`\`\`ts
652
+ export default workspace("${appName}")
653
+ .registerTables([...])
654
+ .registerApiGroups([...])
655
+ .registerQueries([...]);
656
+ \`\`\`
657
+ 5. **Assert it works** \u2014 add a \`tests: [...]\` entry to a query or function
658
+ (named inputs plus \`expect.*\` assertions on its response), or a
659
+ \`workflowTest({ name, stack })\` when the behavior spans several objects.
660
+ They live beside the code they cover and ship with it. See "Testing" below.
661
+ 6. **Compile** with \`npm run xano:export\`, and **deploy** with
662
+ \`npm run xano:deploy\` (after \`npx xanosdk login\`). The first of either writes
663
+ \`xano/xano.lock\` \u2014 **commit it**. It pins every object's identity, so a later
664
+ rename renames the object instead of deleting and recreating it. See
665
+ "\`xano.lock\` \u2014 commit it" in the project README.
666
+
667
+ ## Testing
668
+
669
+ Two kinds of test are authored in \`xano/\` alongside the objects they cover, and
670
+ both run against a DEPLOYED environment:
671
+
672
+ - **Unit test** \u2014 \`tests: [...]\` on a \`query\`, \`defineFunction\`, or
673
+ \`middleware\`. Each entry is a named set of inputs run against that object, with
674
+ \`expect.*\` assertions on its response. A statement's \`mock\` (keyed by test
675
+ NAME) makes one step return a value instead of doing its work, but only while
676
+ that test runs.
677
+ - **Workflow test** \u2014 \`workflowTest({ name, stack })\`, a standalone object whose
678
+ stack calls others (\`s.function.call\`, \`s.api.call\`) and asserts with
679
+ \`s.expect.*\`. Use it for behavior that spans objects.
680
+
681
+ \`expect.*\` and \`s.expect.*\` are different builders \u2014 an assertion record versus
682
+ a workflow-test statement \u2014 and are not interchangeable.
683
+
684
+ \`\`\`bash
685
+ npm run xano:deploy # tests run against what is deployed, so deploy first
686
+ npm run xano:test # runs both kinds; exits 5 if any fail
687
+ \`\`\`
688
+
689
+ \`npx xanosdk deploy ./xano/index.ts --test\` does both in one step, and
690
+ \`npx xanosdk test list\` shows what a deployed environment carries without running
691
+ anything. Read \`node_modules/@xano/sdk/llms/tests.md\` before authoring either
692
+ kind.
693
+
694
+ ### Event-driven objects
695
+
696
+ A scheduled \`task\`, an \`mcpServer\`, and every trigger **fire normally on an
697
+ ephemeral** \u2014 where \`deploy\` sends them \u2014 so test them by deploying and letting
698
+ them run.
699
+
700
+ ## Wire the frontend
701
+
702
+ In [\`../frontend/src/lib/api.ts\`](../frontend/src/lib/api.ts), never hand-type a
703
+ URL or a request body:
704
+
705
+ - **Paths and verbs** come from \`xano/routes.gen.ts\`:
706
+ \`routePath("POST create_note")\`, \`ROUTES["POST create_note"].verb\` (a verb+name two api
707
+ groups share is keyed \`"<group>:<VERB> <name>"\`). Run
708
+ \`npm run xano:routes\` after adding or renaming an endpoint (\`dev\`, \`build\` and
709
+ \`typecheck\` also run it) and commit the file.
710
+ - **Shapes** come from the defs with \`import type\` \u2014 \`InferInput\`/\`InferResponse\`
711
+ erase to nothing.
712
+ - Never import a def as a value in the frontend. Its \`s.*\`/\`c.*\` stack calls run
713
+ at module load, so one import for its \`getPath()\` pulls the backend graph it
714
+ references and the SDK runtime into the bundle.
715
+ `;
716
+ }
717
+ function describeOrigin(o) {
718
+ switch (o.source) {
719
+ case "workspace":
720
+ return `workspace ${o.origin}`;
721
+ case "ephemeral":
722
+ return `ephemeral "${o.origin}"`;
723
+ case "local-engine":
724
+ return `local engine "${o.origin}"`;
725
+ case "tenant":
726
+ return `tenant "${o.origin}"`;
727
+ case "release":
728
+ return `release "${o.origin}"`;
729
+ case "file":
730
+ return `the bundle at ${o.origin}`;
731
+ }
732
+ }
733
+ function describeCommittedOrigin(o) {
734
+ if (o.source !== "file") return describeOrigin(o);
735
+ const name = o.origin.split(/[\\/]/).pop() || o.origin;
736
+ return `the bundle ${name}`;
737
+ }
738
+ function renderCodegenMarker({ sdkVersion }, origin, generatedAt, report, files) {
739
+ const head = {
740
+ source: origin.source,
741
+ origin: origin.origin,
742
+ sdkVersion,
743
+ generatedAt,
744
+ note: "Written by `xanosdk init --from`. Its presence lets a re-run refresh xano/ in place.",
745
+ // The findings, so parity is trackable release over release and gateable
746
+ // in CI without scraping stderr. Omitted rather than written empty when
747
+ // the caller has none to record.
748
+ ...report === void 0 ? {} : { report }
749
+ };
750
+ return JSON.stringify(files === void 0 ? head : decodeMarkerWith(head, files), null, 2) + "\n";
751
+ }
752
+ function renderCodegenReadme({ appName }, origin, envNames, preset) {
753
+ const secrets = envNames.length === 0 ? "" : `
754
+ ## Fill in the backend env values
755
+
756
+ The pull carried ${envNames.length} workspace env var${envNames.length === 1 ? "" : "s"} \u2014
757
+ ${envNames.map((n) => `\`${n}\``).join(", ")} \u2014 as NAMES only. The values stayed in the
758
+ workspace; no committed file holds a secret.
759
+
760
+ \`\`\`bash
761
+ cp xano/.env.example xano/.env # then edit in the values
762
+ npx xanosdk env pull # or fetch them from a running backend (confirms before replacing)
763
+ \`\`\`
764
+
765
+ Every command that compiles a bundle reads \`xano/.env\` with no flag. It and
766
+ \`xano/.secrets.json\` (documentation tokens) are gitignored and survive a \`npx xanosdk pull\` \u2014
767
+ the two files under \`xano/\` that are not committed. The source beside them is meant to
768
+ be reviewed, so never ignore \`xano/\` wholesale.
769
+
770
+ A deploy REPLACES the backend's env set, so a declared name with no value refuses the
771
+ deploy rather than clearing the live value. In CI, which does not have this file, mount
772
+ one and pass \`--backend-env-file <path>\`, or supply names with \`--env-var KEY=VALUE\`.
773
+
774
+ \`workspace.json\` is a different matter: a compiled bundle carries these values in
775
+ cleartext, which is why it is gitignored too.
776
+ `;
777
+ return `# ${appName}
778
+
779
+ A [Xano SDK](https://www.npmjs.com/package/@xano/sdk) project pulled from
780
+ ${describeCommittedOrigin(origin)}. The Xano backend lives in [\`xano/\`](xano/) as readable
781
+ TypeScript; the ${preset.label} frontend under [\`frontend/\`](frontend/) is a starter \u2014
782
+ the pull carries a backend, not a UI.
783
+
784
+ ## Deploy it
785
+
786
+ \`\`\`bash
787
+ npx xanosdk login # once, to authenticate against your Xano account
788
+ npm run xano:deploy # typecheck, build the frontend, ship both
789
+ \`\`\`
790
+
791
+ ## Read this before deploying
792
+
793
+ - **\`xano/\` is your source now** \u2014 edit it and commit it. \`npx xanosdk pull\` (or re-running
794
+ \`npx xanosdk init --from\`) refreshes it from a backend: it lists what will change and asks
795
+ first, keeps files you added, and overwrites the files it decodes \u2014 commit first.
796
+ - **\`npx xanosdk deploy\` is a full replace** of an **ephemeral** environment or a local engine
797
+ (\`npm run xano:deploy\`), unless \`--keep-data\` merges into the one an earlier deploy
798
+ filled. A real workspace is reached with \`npx xanosdk promote <release>\` or
799
+ \`npx xanosdk deploy --to workspace\`, which merge and leave table rows alone.
800
+ - **This is schema only.** Table rows are not carried, and neither are payload sections
801
+ this SDK models no kind for. A deploy recreates the structure, not the data.
802
+
803
+ [\`xano/README.md\`](xano/README.md) is the authoritative record of what did and did not
804
+ translate cleanly on this pull. Read it before trusting the tree.
805
+ ${secrets}
806
+ ## Working on it
807
+
808
+ \`\`\`bash
809
+ npm run dev # run the starter frontend
810
+ npm run typecheck # the whole project, both halves
811
+ npm run xano:export # compile the backend to workspace.json (don't commit it)
812
+ npm run xano:routes # regenerate xano/routes.gen.ts, the frontend's paths
813
+ \`\`\`
814
+
815
+ [\`frontend/src/lib/api.ts\`](frontend/src/lib/api.ts) shows the one contract: paths
816
+ from \`routePath()\` in \`xano/routes.gen.ts\`, types from the query defs in \`xano/\`.
817
+ `;
818
+ }
819
+ function renderReadmeThemingSection(choice) {
820
+ const { theme, dark } = choice;
821
+ const darkNote = {
822
+ system: `Dark mode follows the OS. An inline script in the HTML entry sets
823
+ \`class="dark"\` on \`<html>\` before first paint (so the page never flashes light
824
+ first), and the \`.dark\` block in the stylesheet supplies the palette. To let
825
+ people override it, add a control that toggles that class \u2014 or scaffold your
826
+ next project with \`--dark toggle\`, which ships one.`,
827
+ toggle: `Dark mode follows the OS until the user says otherwise. The pieces:
828
+ an inline script in the HTML entry applies the mode before first paint,
829
+ [\`frontend/src/lib/theme.ts\`](frontend/src/lib/theme.ts) holds and persists it,
830
+ and the mode toggle on the landing page cycles system \u2192 light \u2192 dark.`,
831
+ off: `This project was scaffolded with \`--dark off\`, so nothing switches
832
+ themes. The \`.dark\` block in the stylesheet is still there and still complete \u2014
833
+ add \`class="dark"\` to \`<html>\` to see it, and wire that to a control (or the OS
834
+ setting) to turn it on for real.`
835
+ }[dark];
836
+ return `## Theming
837
+
838
+ Scaffolded with the **${theme.label}** theme. Every color in the app comes from
839
+ the semantic tokens at the top of
840
+ [\`frontend/src/index.css\`](frontend/src/index.css) \u2014 \`--primary\`,
841
+ \`--muted-foreground\`, \`--border\`, the \`--chart-*\` ramp, the \`--sidebar-*\` set \u2014
842
+ and every shadcn component reads those names, so editing one value rebrands
843
+ everything that uses it. Style with the token classes (\`bg-primary\`,
844
+ \`text-muted-foreground\`) rather than raw palette classes like \`bg-gray-100\`, or
845
+ the theme stops being one.
846
+
847
+ Tailwind v4 has no \`tailwind.config.js\`; that stylesheet *is* the config.
848
+
849
+ To swap the whole palette later:
850
+
851
+ \`\`\`bash
852
+ npx shadcn@latest add https://ui.shadcn.com/r/themes/stone.json # or any registry theme
853
+ \`\`\`
854
+
855
+ ${darkNote}`;
856
+ }
857
+ var UI_DOCS_CTA = {
858
+ label: "Browse UI components",
859
+ href: "https://ui.shadcn.com/docs/components"
860
+ };
861
+ function initLanding({ appName }) {
862
+ return {
863
+ title: appName,
864
+ lead: [
865
+ { text: "Your Xano SDK project is ready. The backend lives in " },
866
+ { code: "xano/" },
867
+ { text: " and this frontend in " },
868
+ { code: "frontend/" },
869
+ { text: "." }
870
+ ],
871
+ steps: [
872
+ [
873
+ { text: "Author your first table + endpoint in " },
874
+ { code: "xano/index.ts" },
875
+ { text: " (see " },
876
+ { code: "xano/EXAMPLE.md" },
877
+ { text: ")." }
878
+ ],
879
+ [{ text: "Wire it into the UI from " }, { code: "frontend/src/lib/api.ts" }, { text: "." }],
880
+ [{ text: "Ship it: " }, { code: "npm run xano:deploy" }, { text: "." }]
881
+ ],
882
+ cta: UI_DOCS_CTA
883
+ };
884
+ }
885
+ function codegenLanding({ appName }, origin) {
886
+ return {
887
+ title: appName,
888
+ lead: [
889
+ { text: `This project was pulled from ${describeCommittedOrigin(origin)}. The backend lives in ` },
890
+ { code: "xano/" },
891
+ { text: " as readable TypeScript; this frontend is a starter." }
892
+ ],
893
+ steps: [
894
+ [
895
+ { text: "Read " },
896
+ { code: "xano/README.md" },
897
+ { text: " \u2014 what did and did not translate cleanly on the pull." }
898
+ ],
899
+ [
900
+ { text: "List the endpoints: " },
901
+ { code: "npx xanosdk routes xano/index.ts" },
902
+ { text: ", then wire them up in " },
903
+ { code: "frontend/src/lib/api.ts" },
904
+ { text: "." }
905
+ ],
906
+ [
907
+ { text: "Ship it: " },
908
+ { code: "npm run xano:deploy" },
909
+ { text: " (a full replace of an ephemeral env)." }
910
+ ]
911
+ ],
912
+ cta: UI_DOCS_CTA
913
+ };
914
+ }
915
+ function renderIndexCss(choice = defaultThemeChoice()) {
916
+ const { theme } = choice;
917
+ const colorMap = colorTokens(theme.light).map((token) => ` --color-${token}: var(--${token});`).join("\n");
918
+ const block = (vars) => orderedTokens(vars).map(([name, value]) => ` --${name}: ${value};`).join("\n");
919
+ const faces = fontImports(choice.fonts);
920
+ const fontVars = fontThemeVars(choice.fonts).map(([name, value]) => ` ${name}: ${value};`).join("\n");
921
+ const headingRule = choice.fonts?.heading === void 0 ? "" : `
922
+ h1,
923
+ h2,
924
+ h3,
925
+ h4,
926
+ h5,
927
+ h6 {
928
+ font-family: var(--font-heading);
929
+ }`;
930
+ return `${faces.length === 0 ? "" : faces.join("\n") + "\n"}@import "tailwindcss";
931
+ @import "tw-animate-css";
932
+
933
+ @custom-variant dark (&:is(.dark *));
934
+
935
+ /* Map the tokens below into Tailwind's theme so \`bg-primary\` and friends
936
+ resolve. Tailwind v4 does this in CSS; there is no tailwind.config.js. */
937
+ @theme inline {
938
+ ${colorMap}
939
+ --radius-sm: calc(var(--radius) - 4px);
940
+ --radius-md: calc(var(--radius) - 2px);
941
+ --radius-lg: var(--radius);
942
+ --radius-xl: calc(var(--radius) + 4px);${fontVars === "" ? "" : `
943
+ ${fontVars}`}
944
+ }
945
+
946
+ /* Theme: ${theme.label}. Rebrand here \u2014 these are the only colors the
947
+ components know about. Swap the whole palette for another shadcn theme with
948
+ \`npx shadcn@latest add <registry-theme-url>\`, or scaffold a different one
949
+ next time with \`npx @xano/sdk init --theme <base>-<accent>\`. */
950
+ :root {
951
+ ${block(theme.light)}
952
+ }
953
+
954
+ ${darkBlockComment(choice.dark)}
955
+ .dark {
956
+ ${block(theme.dark)}
957
+ }
958
+
959
+ @layer base {
960
+ * {
961
+ @apply border-border outline-ring/50;
962
+ }
963
+ body {
964
+ @apply bg-background text-foreground;
965
+ }${headingRule}
966
+ }
967
+ `;
968
+ }
969
+ function darkBlockComment(dark) {
970
+ switch (dark) {
971
+ case "system":
972
+ return `/* Applied by the inline script in index.html, which follows the OS setting.
973
+ Add a control that toggles this class to let the user override it. */`;
974
+ case "toggle":
975
+ return `/* Applied by frontend/src/lib/theme.ts \u2014 the OS setting by default, and the
976
+ user's choice once they use the mode toggle. */`;
977
+ case "off":
978
+ return `/* Applied by adding \`class="dark"\` to <html> \u2014 wire that to a toggle if you
979
+ want one; this scaffold was created with --dark off, so nothing does yet. */`;
980
+ }
981
+ }
982
+ function renderThemeScript(dark) {
983
+ if (dark === "off") return "";
984
+ const resolve = dark === "toggle" ? `var stored = localStorage.getItem("theme");
985
+ var dark = stored === "dark" || (stored !== "light" && media.matches);` : `var dark = media.matches;`;
986
+ return ` <script>
987
+ // Sets the \`dark\` class before first paint, so the page never flashes
988
+ // light before switching. Keep this inline and above the stylesheet.
989
+ (function () {
990
+ try {
991
+ var media = window.matchMedia("(prefers-color-scheme: dark)");
992
+ ${resolve}
993
+ document.documentElement.classList.toggle("dark", dark);
994
+ } catch (e) {}
995
+ })();
996
+ </script>
997
+ `;
998
+ }
999
+ function renderThemeModule() {
1000
+ return `/**
1001
+ * Color mode, persisted. The inline script in index.html applies the stored
1002
+ * value before first paint; this module is what changes it afterwards.
1003
+ *
1004
+ * "system" means no stored preference \u2014 the OS decides, and keeps deciding if
1005
+ * the user changes it while the page is open.
1006
+ */
1007
+ export type Mode = "light" | "dark" | "system";
1008
+
1009
+ const KEY = "theme";
1010
+
1011
+ const prefersDark = () =>
1012
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
1013
+
1014
+ /** The stored preference, or "system" when there is none. */
1015
+ export function getMode(): Mode {
1016
+ const stored = localStorage.getItem(KEY);
1017
+ return stored === "light" || stored === "dark" ? stored : "system";
1018
+ }
1019
+
1020
+ /** Whether \`mode\` renders dark right now. */
1021
+ export function isDark(mode: Mode): boolean {
1022
+ return mode === "dark" || (mode === "system" && prefersDark());
1023
+ }
1024
+
1025
+ /** Persist a mode and apply it. "system" clears the stored override. */
1026
+ export function setMode(mode: Mode): void {
1027
+ if (mode === "system") localStorage.removeItem(KEY);
1028
+ else localStorage.setItem(KEY, mode);
1029
+ document.documentElement.classList.toggle("dark", isDark(mode));
1030
+ }
1031
+
1032
+ /**
1033
+ * Re-apply on OS changes, and on changes made in another tab. Returns an
1034
+ * unsubscribe function \u2014 call it from your framework's cleanup hook.
1035
+ *
1036
+ * The OS listener re-reads the mode rather than closing over it, so it stays
1037
+ * correct after the user picks an explicit light/dark and then goes back to
1038
+ * system.
1039
+ */
1040
+ export function watchMode(onChange: (mode: Mode) => void): () => void {
1041
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
1042
+ const apply = () => {
1043
+ const mode = getMode();
1044
+ document.documentElement.classList.toggle("dark", isDark(mode));
1045
+ onChange(mode);
1046
+ };
1047
+ media.addEventListener("change", apply);
1048
+ window.addEventListener("storage", apply);
1049
+ return () => {
1050
+ media.removeEventListener("change", apply);
1051
+ window.removeEventListener("storage", apply);
1052
+ };
1053
+ }
1054
+ `;
1055
+ }
1056
+ function renderCnUtil(extra = "") {
1057
+ return `import { clsx, type ClassValue } from "clsx";
1058
+ import { twMerge } from "tailwind-merge";
1059
+
1060
+ export function cn(...inputs: ClassValue[]) {
1061
+ return twMerge(clsx(inputs));
1062
+ }
1063
+ ${extra}`;
1064
+ }
1065
+ function renderApiTs() {
1066
+ return `// The one contract: endpoint paths come from the generated route manifest, and
1067
+ // request/response *types* from your xanosdk query defs. Never hand-type a URL
1068
+ // or a request body: change a def and everything here follows.
1069
+ //
1070
+ // Keep the backend out of the browser bundle:
1071
+ // \u2022 Paths and verbs: \`routePath()\` / \`ROUTES\` from xano/routes.gen.ts, plain data
1072
+ // generated from the defs (\`npm run xano:routes\`; dev, build and typecheck
1073
+ // regenerate it). Never import a def as a VALUE for its getPath()/verb:
1074
+ // its s.*/c.* factory calls run at module load, so one import pulls in the
1075
+ // backend graph it references and the SDK runtime that builds it.
1076
+ // \u2022 Shapes: \`import type\` only. InferInput/InferResponse erase to nothing.
1077
+ //
1078
+ // Nothing here imports the manifest yet \u2014 an add-on's endpoints may already be
1079
+ // in it. Wire an endpoint (one in xano/, or an add-on's) like:
1080
+ //
1081
+ // import type { InferInput, InferResponse } from "@xano/sdk";
1082
+ // import type { createNoteQuery } from "../../../xano/api/create-note.js";
1083
+ // import { ROUTES, routePath } from "../../../xano/routes.gen.js";
1084
+ //
1085
+ // export type CreateNoteBody = InferInput<typeof createNoteQuery>;
1086
+ // export type Note = InferResponse<typeof createNoteQuery>;
1087
+ //
1088
+ // export async function createNote(body: CreateNoteBody): Promise<Note> {
1089
+ // const res = await fetch(XANO_HOST + routePath("POST create_note"), {
1090
+ // method: ROUTES["POST create_note"].verb,
1091
+ // headers: { "content-type": "application/json" },
1092
+ // body: JSON.stringify(body),
1093
+ // });
1094
+ // if (!res.ok) throw new Error(await res.text());
1095
+ // return res.json();
1096
+ // }
1097
+ //
1098
+ // Keys are "<VERB> <query name>"; path params are passed by name, e.g.
1099
+ // routePath("GET notes/{id}", { id }). A renamed endpoint is a compile error.
1100
+
1101
+ // Types the global the deploy injects, for every file in the project: the
1102
+ // documented \`window.XANO_HOST\` reads compile anywhere. \`undefined\` in dev.
1103
+ declare global {
1104
+ interface Window {
1105
+ XANO_HOST?: string;
1106
+ }
1107
+ }
1108
+
1109
+ /**
1110
+ * The deployed Xano backend's base URL. Injected as \`window.XANO_HOST\` by
1111
+ * \`npx xanosdk deploy <entry> --static <dir>\`, or read from \`VITE_XANO_HOST\` in dev.
1112
+ * Empty string when neither is set (the UI runs with no backend).
1113
+ */
1114
+ export const XANO_HOST: string =
1115
+ (typeof window !== "undefined" && window.XANO_HOST) ||
1116
+ import.meta.env.VITE_XANO_HOST ||
1117
+ "";
1118
+ `;
1119
+ }
1120
+
1121
+ // src/emit/frontend-svelte.ts
1122
+ var CODE_CLASS = "bg-muted rounded px-1.5 py-0.5 font-mono text-sm";
1123
+ function htmlText(s) {
1124
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1125
+ }
1126
+ function svelteText(s) {
1127
+ return htmlText(s).replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
1128
+ }
1129
+ function renderNode(node) {
1130
+ return "code" in node ? `<code class="${CODE_CLASS}">${svelteText(node.code)}</code>` : svelteText(node.text);
1131
+ }
1132
+ function renderNodes(nodes) {
1133
+ return nodes.map(renderNode).join("");
1134
+ }
1135
+ function renderApp(landing, choice) {
1136
+ const steps = landing.steps.map((step) => ` <li>${renderNodes(step)}</li>`).join("\n");
1137
+ const toggle = choice.dark === "toggle";
1138
+ const icons = iconLibraryOf(choice.icons).svelte;
1139
+ return `<script lang="ts">
1140
+ ${icons.arrowImport}
1141
+
1142
+ import { Button } from "$lib/components/ui/button/index.js";
1143
+ import * as Card from "$lib/components/ui/card/index.js";${toggle ? `
1144
+ import ModeToggle from "$lib/components/mode-toggle.svelte";` : ""}
1145
+ </script>
1146
+
1147
+ <main class="mx-auto flex min-h-screen max-w-2xl flex-col justify-center p-8">${toggle ? `
1148
+ <div class="mb-4 flex justify-end">
1149
+ <ModeToggle />
1150
+ </div>` : ""}
1151
+ <Card.Root>
1152
+ <Card.Header>
1153
+ <Card.Title class="text-3xl tracking-tight">${svelteText(landing.title)}</Card.Title>
1154
+ <Card.Description class="text-base">
1155
+ ${renderNodes(landing.lead)}
1156
+ </Card.Description>
1157
+ </Card.Header>
1158
+ <Card.Content>
1159
+ <ol class="text-muted-foreground list-inside list-decimal space-y-2">
1160
+ ${steps}
1161
+ </ol>
1162
+ </Card.Content>
1163
+ <Card.Footer>
1164
+ <!-- Components come from shadcn-svelte \u2014 add more with
1165
+ \`npx shadcn-svelte@latest add <name>\`. -->
1166
+ <Button href="${landing.cta.href}" target="_blank" rel="noreferrer">
1167
+ ${svelteText(landing.cta.label)}
1168
+ <${icons.arrowComponent} />
1169
+ </Button>
1170
+ </Card.Footer>
1171
+ </Card.Root>
1172
+ </main>
1173
+ `;
1174
+ }
1175
+ function renderSvelteKitPlugin() {
1176
+ return `sveltekit({
1177
+ // RUNES MODE, FORCED \u2014 not left to Svelte 5's per-component detection.
1178
+ //
1179
+ // By default a component that uses \`export let\` and \`<slot>\` simply
1180
+ // compiles in legacy mode and works, so the Svelte 4 API an assistant
1181
+ // reaches for from older training data produces no error at all. Pinning
1182
+ // runes here turns that into a compile failure naming the file, which is
1183
+ // the same guidance the agent brief gives, enforced instead of stated.
1184
+ //
1185
+ // \`node_modules\` is exempted (returning undefined restores detection):
1186
+ // an installed library compiles under whatever mode ITS author wrote for,
1187
+ // and forcing ours onto it would break dependencies this project does not
1188
+ // own. Removable in Svelte 6, where runes stop being opt-in.
1189
+ compilerOptions: {
1190
+ runes: ({ filename }) =>
1191
+ filename.split(/[/\\\\]/).includes("node_modules") ? undefined : true,
1192
+ },
1193
+ // No \`preprocess\`. vite-plugin-svelte 7 compiles \`<script lang="ts">\`
1194
+ // itself, so \`vitePreprocess()\` is the no-op it looks like \u2014 \`sv create\`
1195
+ // emits none either. Verified against a real build: components typecheck
1196
+ // and compile without it.
1197
+ //
1198
+ // The deploy target is a static host with NO server runtime \u2014 Xano is the
1199
+ // backend. Routes are PRERENDERED (see frontend/src/routes/+layout.ts):
1200
+ // each becomes its own HTML file at build time, and the host resolves a URL
1201
+ // to it by trying the exact key, then \`{path}.html\`, then
1202
+ // \`{path}/index.html\`. That is why no \`trailingSlash\` is configured \u2014 both
1203
+ // output shapes resolve, so this stays on SvelteKit's default.
1204
+ //
1205
+ // The SDK's adapter, writing to frontend/dist. Every prerendered route is
1206
+ // its own file; a route that sets \`prerender = false\` (a detail page for
1207
+ // rows created at runtime) has no file, so the host answers it with
1208
+ // 404.html, which the adapter writes as the app shell whenever such a
1209
+ // route exists: the shell boots the app and renders the route in the
1210
+ // browser, so a reload or a deep link works. While every route is
1211
+ // prerendered, 404.html stays the prerendered /404 page.
1212
+ //
1213
+ // Do not swap it for \`@sveltejs/adapter-static\` with a \`fallback\`: that
1214
+ // writes the shell at index.html, over the prerendered home page.
1215
+ //
1216
+ // \`xanosdk deploy --static-env\` still finds its anchor: every document
1217
+ // renders from app.html, so each has a <head> that gets the injected globals.
1218
+ adapter: adapter({ out: "frontend/dist" }),
1219
+ // Absolute asset URLs, pinned rather than left to the default. Prerendered
1220
+ // pages live at varying URL depths, and a relative \`../_app/\u2026\` computed for
1221
+ // one depth 404s at another.
1222
+ paths: { relative: false },
1223
+ prerender: {
1224
+ // A hash link in the shared layout \u2014 \`<a href="#services">\`, the default
1225
+ // shape of a one-page site's nav \u2014 renders into EVERY prerendered route,
1226
+ // including the scaffold's own /404. There it resolves to /404#services,
1227
+ // an anchor that by definition is not on that page, and SvelteKit's
1228
+ // default handler THROWS: the vite build succeeds and the prerender pass
1229
+ // after it kills the build, with a stack trace naming only files inside
1230
+ // node_modules and a route the author never wrote.
1231
+ //
1232
+ // So ignore a missing id on /404 specifically, rather than reaching for a
1233
+ // blanket \`handleMissingId: "warn"\`. A hash link that is genuinely broken
1234
+ // on a real route still fails the build, which is the check worth keeping.
1235
+ handleMissingId: ({ path, message }) => {
1236
+ if (path === "/404") return;
1237
+ throw new Error(message);
1238
+ },
1239
+ },
1240
+ // SvelteKit's project layout, redirected under frontend/ so this stays a
1241
+ // single-root project: one package.json, one tsconfig.json, and xano/
1242
+ // sitting alongside as the backend half. NOTE these are what make
1243
+ // frontend/src/routes the routes directory \u2014 a file added under a
1244
+ // top-level src/routes/ is not a route and will not be served.
1245
+ files: {
1246
+ routes: "frontend/src/routes",
1247
+ lib: "frontend/src/lib",
1248
+ appTemplate: "frontend/src/app.html",
1249
+ assets: "frontend/static",
1250
+ },
1251
+ })`;
1252
+ }
1253
+ function renderAppHtml({ appName }, choice) {
1254
+ return `<!doctype html>
1255
+ <html lang="en">
1256
+ <head>
1257
+ <meta charset="utf-8" />
1258
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1259
+ <title>${htmlText(appName)}</title>
1260
+ ${renderThemeScript(choice.dark)}
1261
+ <!-- Drop a favicon.png into frontend/static/ and link it here:
1262
+ <link rel="icon" href="%sveltekit.assets%/favicon.png" />
1263
+ Everything in that directory is copied to the site root verbatim. -->
1264
+ <!-- Keep this inside <head>: \`xanosdk deploy --static-env\` injects the
1265
+ backend URL here as a window global that frontend/src/lib/api.ts reads. -->
1266
+ %sveltekit.head%
1267
+ </head>
1268
+ <body data-sveltekit-preload-data="hover">
1269
+ <div style="display: contents">%sveltekit.body%</div>
1270
+ </body>
1271
+ </html>
1272
+ `;
1273
+ }
1274
+ function renderModeToggleSvelte(icons) {
1275
+ return `<script lang="ts">
1276
+ ${icons.modeImports.join("\n ")}
1277
+
1278
+ import { Button } from "$lib/components/ui/button/index.js";
1279
+ import { getMode, setMode, watchMode, type Mode } from "$lib/theme.js";
1280
+
1281
+ /** system \u2192 light \u2192 dark \u2192 system. */
1282
+ const next: Record<Mode, Mode> = { system: "light", light: "dark", dark: "system" };
1283
+
1284
+ const icons = {
1285
+ system: ${icons.modeComponents.system},
1286
+ light: ${icons.modeComponents.light},
1287
+ dark: ${icons.modeComponents.dark},
1288
+ };
1289
+
1290
+ // Starts at "system" so the prerendered HTML is deterministic; the effect
1291
+ // corrects it in the browser, after the inline script has already applied
1292
+ // the right class.
1293
+ let mode = $state<Mode>("system");
1294
+
1295
+ $effect(() => {
1296
+ mode = getMode();
1297
+ return watchMode((m) => (mode = m));
1298
+ });
1299
+
1300
+ function change() {
1301
+ const target = next[mode];
1302
+ setMode(target);
1303
+ mode = target;
1304
+ }
1305
+
1306
+ const Icon = $derived(icons[mode]);
1307
+ </script>
1308
+
1309
+ <Button variant="ghost" size="icon" onclick={change} aria-label="Switch to {next[mode]} theme">
1310
+ <Icon />
1311
+ </Button>
1312
+ `;
1313
+ }
1314
+ function renderAppDts() {
1315
+ return `// See https://svelte.dev/docs/kit/types#app.d.ts
1316
+ declare global {
1317
+ namespace App {
1318
+ // interface Error {}
1319
+ // interface Locals {}
1320
+ // interface PageData {}
1321
+ // interface PageState {}
1322
+ // interface Platform {}
1323
+ }
1324
+ }
1325
+
1326
+ export {};
1327
+ `;
1328
+ }
1329
+ function renderLayoutTs() {
1330
+ return `// There is no server. The backend is Xano, reached through $lib/api.ts,
1331
+ // and \`npm run xano:deploy\` ships this app to a static host with no runtime.
1332
+ //
1333
+ // prerender: true \u2014 every route is rendered to its own HTML file AT BUILD TIME
1334
+ // and served as a real document. That is what makes each page load as itself
1335
+ // rather than as an empty shell that fills in afterwards.
1336
+ //
1337
+ // Prerendering IS server-rendering, just at build time rather than per request.
1338
+ // So module-scope \`window\`/\`document\` access now fails the BUILD instead of the
1339
+ // browser \u2014 which is the better place to find out. Reach for browser globals
1340
+ // inside onMount, or guard with \`import { browser } from "$app/environment"\`.
1341
+ //
1342
+ // A route with dynamic segments (e.g. /posts/[id]) cannot be prerendered unless
1343
+ // the build knows which ones exist, so THE BUILD FAILS and names the route.
1344
+ // In that route's +page.ts, either list the ids (only those get a page):
1345
+ //
1346
+ // export const entries = () => [{ id: "1" }, { id: "2" }];
1347
+ //
1348
+ // or, for rows created at runtime, render it in the browser instead:
1349
+ //
1350
+ // export const prerender = false;
1351
+ //
1352
+ // Inherited by every route below this one.
1353
+ export const prerender = true;
1354
+ `;
1355
+ }
1356
+ function renderErrorState() {
1357
+ return `<script lang="ts">
1358
+ // Shared by the prerendered /404 route and +error.svelte.
1359
+ let { status = 404, message = "" }: { status?: number; message?: string } = $props();
1360
+ </script>
1361
+
1362
+ <main class="grid min-h-screen place-items-center p-8">
1363
+ <div class="max-w-md space-y-4 text-center">
1364
+ <p class="text-6xl font-semibold tracking-tight">{status}</p>
1365
+ <h1 class="text-xl font-medium">
1366
+ {status === 404 ? "We can't find that page." : "Something went wrong."}
1367
+ </h1>
1368
+ <p class="text-muted-foreground text-sm">
1369
+ {message !== "" ? message : "The link may be broken, or the page may have moved."}
1370
+ </p>
1371
+ <a
1372
+ href="/"
1373
+ class="bg-primary text-primary-foreground ring-offset-background focus-visible:ring-ring inline-flex h-10 items-center rounded-md px-5 text-sm font-medium transition-opacity hover:opacity-90 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
1374
+ >
1375
+ Back to safety
1376
+ </a>
1377
+ </div>
1378
+ </main>
1379
+ `;
1380
+ }
1381
+ function renderNotFoundRoute() {
1382
+ return `<script lang="ts">
1383
+ // Prerendered to 404.html, which is what tells the static host to answer an
1384
+ // unmatched path with a real 404 instead of quietly serving the app shell
1385
+ // with a 200 (a missing asset would then reach the browser as HTML, which it
1386
+ // reports as "Unexpected token '<'").
1387
+ //
1388
+ // It must be a route, not +error.svelte: SvelteKit never prerenders the error
1389
+ // component to a file, so relying on it would ship no 404.html at all.
1390
+ //
1391
+ // Delete this directory and an unmatched path serves the home page with a 200
1392
+ // instead \u2014 which is worse, not better, so keep it unless you have a reason.
1393
+ import ErrorState from "$lib/components/error-state.svelte";
1394
+ </script>
1395
+
1396
+ <svelte:head>
1397
+ <title>Page not found</title>
1398
+ </svelte:head>
1399
+
1400
+ <ErrorState status={404} />
1401
+ `;
1402
+ }
1403
+ function renderErrorSvelte() {
1404
+ return `<script lang="ts">
1405
+ // Rendered when a client-side navigation or load fails inside the running
1406
+ // app. The static host never sees these \u2014 for a path that matched no
1407
+ // document at all, it serves the prerendered routes/404 page instead.
1408
+ import { page } from "$app/state";
1409
+ import ErrorState from "$lib/components/error-state.svelte";
1410
+ </script>
1411
+
1412
+ <svelte:head>
1413
+ <title>{page.status === 404 ? "Page not found" : "Something went wrong"}</title>
1414
+ </svelte:head>
1415
+
1416
+ <ErrorState status={page.status} message={page.error?.message ?? ""} />
1417
+ `;
1418
+ }
1419
+ function renderLayoutSvelte() {
1420
+ return `<script lang="ts">
1421
+ // The shared token stylesheet \u2014 semantic colors (bg-primary,
1422
+ // text-muted-foreground) plus the Tailwind v4 theme. Imported once here so
1423
+ // every route gets it.
1424
+ import "../index.css";
1425
+
1426
+ let { children } = $props();
1427
+ </script>
1428
+
1429
+ {@render children()}
1430
+ `;
1431
+ }
1432
+ function renderComponentsJson(choice) {
1433
+ const config = {
1434
+ $schema: "https://shadcn-svelte.com/schema.json",
1435
+ tailwind: {
1436
+ // v4 has no config file; the theme lives in the stylesheet — the same
1437
+ // shared one the React preset points at.
1438
+ css: "frontend/src/index.css",
1439
+ // Tracks the theme the stylesheet was rendered from — see the React
1440
+ // preset's note; the shadcn-svelte CLI reads it the same way.
1441
+ baseColor: choice.theme.baseColor ?? "neutral"
1442
+ },
1443
+ aliases: {
1444
+ components: "$lib/components",
1445
+ utils: "$lib/utils",
1446
+ ui: "$lib/components/ui",
1447
+ hooks: "$lib/hooks",
1448
+ lib: "$lib"
1449
+ },
1450
+ typescript: true,
1451
+ registry: "https://shadcn-svelte.com/registry"
1452
+ };
1453
+ return JSON.stringify(config, null, 2) + "\n";
1454
+ }
1455
+ function renderButtonSvelte() {
1456
+ return `<script lang="ts" module>
1457
+ import { type VariantProps, tv } from "tailwind-variants";
1458
+
1459
+ export const buttonVariants = tv({
1460
+ base: "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1461
+ variants: {
1462
+ variant: {
1463
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
1464
+ destructive:
1465
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
1466
+ outline:
1467
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
1468
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1469
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1470
+ link: "text-primary underline-offset-4 hover:underline",
1471
+ },
1472
+ size: {
1473
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
1474
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1475
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1476
+ icon: "size-9",
1477
+ },
1478
+ },
1479
+ defaultVariants: {
1480
+ variant: "default",
1481
+ size: "default",
1482
+ },
1483
+ });
1484
+
1485
+ export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
1486
+ export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
1487
+
1488
+ export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
1489
+ WithElementRef<HTMLAnchorAttributes> & {
1490
+ variant?: ButtonVariant;
1491
+ size?: ButtonSize;
1492
+ };
1493
+ </script>
1494
+
1495
+ <script lang="ts">
1496
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
1497
+ import type { WithElementRef } from "bits-ui";
1498
+ import { cn } from "$lib/utils.js";
1499
+
1500
+ let {
1501
+ class: className,
1502
+ variant = "default",
1503
+ size = "default",
1504
+ ref = $bindable(null),
1505
+ href = undefined,
1506
+ type = "button",
1507
+ children,
1508
+ ...restProps
1509
+ }: ButtonProps = $props();
1510
+ </script>
1511
+
1512
+ {#if href}
1513
+ <a
1514
+ bind:this={ref}
1515
+ data-slot="button"
1516
+ class={cn(buttonVariants({ variant, size }), className)}
1517
+ {href}
1518
+ {...restProps}
1519
+ >
1520
+ {@render children?.()}
1521
+ </a>
1522
+ {:else}
1523
+ <button
1524
+ bind:this={ref}
1525
+ data-slot="button"
1526
+ class={cn(buttonVariants({ variant, size }), className)}
1527
+ {type}
1528
+ {...restProps}
1529
+ >
1530
+ {@render children?.()}
1531
+ </button>
1532
+ {/if}
1533
+ `;
1534
+ }
1535
+ function renderButtonIndex() {
1536
+ return `export {
1537
+ default as Button,
1538
+ buttonVariants,
1539
+ type ButtonProps,
1540
+ type ButtonSize,
1541
+ type ButtonVariant,
1542
+ } from "./button.svelte";
1543
+ `;
1544
+ }
1545
+ function cardPart(slot, tag, classes) {
1546
+ return `<script lang="ts">
1547
+ import type { HTMLAttributes } from "svelte/elements";
1548
+ import type { WithElementRef } from "bits-ui";
1549
+ import { cn } from "$lib/utils.js";
1550
+
1551
+ let {
1552
+ class: className,
1553
+ ref = $bindable(null),
1554
+ children,
1555
+ ...restProps
1556
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
1557
+ </script>
1558
+
1559
+ <${tag}
1560
+ bind:this={ref}
1561
+ data-slot="${slot}"
1562
+ class={cn("${classes}", className)}
1563
+ {...restProps}
1564
+ >
1565
+ {@render children?.()}
1566
+ </${tag}>
1567
+ `;
1568
+ }
1569
+ function renderCardIndex() {
1570
+ return `import Root from "./card.svelte";
1571
+ import Content from "./card-content.svelte";
1572
+ import Description from "./card-description.svelte";
1573
+ import Footer from "./card-footer.svelte";
1574
+ import Header from "./card-header.svelte";
1575
+ import Title from "./card-title.svelte";
1576
+
1577
+ export {
1578
+ Root,
1579
+ Content,
1580
+ Description,
1581
+ Footer,
1582
+ Header,
1583
+ Title,
1584
+ //
1585
+ Root as Card,
1586
+ Content as CardContent,
1587
+ Description as CardDescription,
1588
+ Footer as CardFooter,
1589
+ Header as CardHeader,
1590
+ Title as CardTitle,
1591
+ };
1592
+ `;
1593
+ }
1594
+ var sveltePreset = {
1595
+ id: "svelte",
1596
+ label: "SvelteKit",
1597
+ dependencies: {
1598
+ // shadcn-svelte's runtime surface. `bits-ui` is the headless primitive
1599
+ // library its components are built on (the Radix equivalent), and
1600
+ // `tailwind-variants` types the variants (the cva equivalent). clsx +
1601
+ // tailwind-merge back the `cn()` its components import from `$lib/utils`.
1602
+ // Pre-installed so `npx shadcn-svelte@latest add <component>` works
1603
+ // without a separate npm i.
1604
+ //
1605
+ // The icon package is `--icons`, merged in by renderPackageJson from
1606
+ // `iconBinding` below — see the React preset's note.
1607
+ "bits-ui": "^2.18.1",
1608
+ clsx: "^2.1.1",
1609
+ "tailwind-merge": "^3.0.1",
1610
+ "tailwind-variants": "^3.3.1"
1611
+ },
1612
+ devDependencies: {
1613
+ // The SDK's adapter (`@xano/sdk/sveltekit`) bundles its server half with
1614
+ // esbuild; Vite 8 does not install it.
1615
+ esbuild: "^0.28.1",
1616
+ // 2.70 peer-accepts vite ^8 and vite-plugin-svelte ^7 — the versions
1617
+ // pinned below. Bump this line and those two together.
1618
+ "@sveltejs/kit": "^2.70.2",
1619
+ // 7.x, matching what `sv create` installs. It peer-requires vite ^8, so
1620
+ // this line and the `vite` pin move together or not at all — and the React
1621
+ // preset's plugin has the same constraint, which is why the two presets
1622
+ // share one Vite major rather than each choosing.
1623
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
1624
+ "@tailwindcss/vite": "^4.1.5",
1625
+ "@types/node": "^20.19.43",
1626
+ svelte: "^5.56.9",
1627
+ "svelte-check": "^4.7.6",
1628
+ tailwindcss: "^4.1.5",
1629
+ "tw-animate-css": "^1.2.4",
1630
+ tsx: "^4.19.2",
1631
+ // 6.x, the major `sv create` pins. svelte-check peer-accepts `^5 || ^6`;
1632
+ // 7 is out but not yet in that range, so this tracks the canonical
1633
+ // scaffold rather than latest.
1634
+ typescript: "^6.0.3",
1635
+ vite: "^8.0.16"
1636
+ },
1637
+ // svelte-check REPLACES `tsc --noEmit` here rather than joining it.
1638
+ //
1639
+ // It is a superset: reading the same tsconfig, it checks `xano/`'s TypeScript
1640
+ // exactly as `tsc` would AND the components `tsc` cannot parse at all. Running
1641
+ // both is not merely redundant — `tsc` fails outright on the UI kit's barrels,
1642
+ // which re-export types from `.svelte` modules it only knows as default
1643
+ // exports. Verified against a real scaffold: a type error planted in
1644
+ // `xano/index.ts` and one planted in `App.svelte` are each caught by this
1645
+ // command alone.
1646
+ //
1647
+ // `svelte-kit sync` runs first, and is not optional: it generates
1648
+ // `.svelte-kit/tsconfig.json` (which the project's tsconfig extends) and the
1649
+ // route types. Without it svelte-check fails on a missing extends target
1650
+ // rather than on anything the user wrote.
1651
+ checkCmd: "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
1652
+ // SvelteKit supplies `$lib` on both sides — the Vite alias from its plugin
1653
+ // and the tsconfig path from the config it generates. Declaring either by
1654
+ // hand would be a second source of truth that can silently disagree.
1655
+ aliasName: null,
1656
+ aliasTarget: "frontend/src/lib",
1657
+ tsconfigPaths: {},
1658
+ tsconfigExtends: "./.svelte-kit/tsconfig.json",
1659
+ // What `svelte-kit sync` generates and the extended config lists — re-added
1660
+ // because our `include` replaces that config's rather than merging with it.
1661
+ // These are the declarations behind `$app/*`, `$env/*`, and `./$types`.
1662
+ tsconfigInclude: [
1663
+ ".svelte-kit/ambient.d.ts",
1664
+ ".svelte-kit/env.d.ts",
1665
+ ".svelte-kit/non-ambient.d.ts",
1666
+ ".svelte-kit/types/**/$types.d.ts"
1667
+ ],
1668
+ // `prepare` runs on `npm install`, so a fresh clone has `.svelte-kit/`
1669
+ // before anything reads tsconfig.json.
1670
+ extraScripts: { prepare: "svelte-kit sync" },
1671
+ // shadcn-svelte's generated components import these from `$lib/utils.js`
1672
+ // alongside `cn()`. Without them the scaffold type-checks until the first
1673
+ // `npx shadcn-svelte@latest add <name>` — which the agent guidance tells
1674
+ // assistants to run — and then fails inside components the user never wrote,
1675
+ // with an error naming a file they have no reason to suspect.
1676
+ //
1677
+ // Verbatim from shadcn-svelte's own utils.ts, so a component the CLI writes
1678
+ // resolves against exactly what upstream expects.
1679
+ libUtilsExtra: `
1680
+ export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
1681
+ export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
1682
+ export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
1683
+ export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
1684
+ `,
1685
+ gitignoreEntries: [".svelte-kit/"],
1686
+ // No `jsx`. `verbatimModuleSyntax` is what svelte-check expects so that
1687
+ // type-only imports in components are erased rather than emitted.
1688
+ tsconfigOptions: { verbatimModuleSyntax: true },
1689
+ viteImports: [
1690
+ `import { sveltekit } from "@sveltejs/kit/vite";`,
1691
+ `import adapter from "@xano/sdk/sveltekit";`,
1692
+ `import tailwindcss from "@tailwindcss/vite";`
1693
+ ],
1694
+ // tailwindcss() before sveltekit() — the order Tailwind v4 documents for
1695
+ // SvelteKit, so the stylesheet is processed before Kit consumes the graph.
1696
+ //
1697
+ // The whole SvelteKit config rides in the sveltekit() call. There is no
1698
+ // svelte.config.js by design — see renderSvelteKitPlugin().
1699
+ vitePlugins: ["tailwindcss()", renderSvelteKitPlugin()],
1700
+ // SvelteKit resolves its own paths from this file's directory and redirects
1701
+ // them into frontend/ via `files`; moving Vite's root under it puts the two
1702
+ // into disagreement. Output is the adapter's job, not `build.outDir`.
1703
+ viteRoot: null,
1704
+ viteBuild: null,
1705
+ // SvelteKit owns the page template — frontend/src/app.html. No `entryScript`
1706
+ // follows: there is no shared index.html for it to point at.
1707
+ ownsHtmlEntry: true,
1708
+ files(vars, landing, choice = defaultThemeChoice()) {
1709
+ const ui = "frontend/src/lib/components/ui";
1710
+ return [
1711
+ { path: "components.json", content: renderComponentsJson(choice) },
1712
+ { path: "frontend/src/app.html", content: renderAppHtml(vars, choice) },
1713
+ // Only for `--dark toggle` — see the React preset's note.
1714
+ ...choice.dark === "toggle" ? [
1715
+ {
1716
+ path: "frontend/src/lib/components/mode-toggle.svelte",
1717
+ content: renderModeToggleSvelte(iconLibraryOf(choice.icons).svelte)
1718
+ }
1719
+ ] : [],
1720
+ { path: "frontend/src/app.d.ts", content: renderAppDts() },
1721
+ { path: "frontend/src/routes/+layout.ts", content: renderLayoutTs() },
1722
+ { path: "frontend/src/routes/+layout.svelte", content: renderLayoutSvelte() },
1723
+ { path: "frontend/src/routes/+page.svelte", content: renderApp(landing, choice) },
1724
+ { path: "frontend/src/routes/+error.svelte", content: renderErrorSvelte() },
1725
+ { path: "frontend/src/routes/404/+page.svelte", content: renderNotFoundRoute() },
1726
+ { path: "frontend/src/lib/components/error-state.svelte", content: renderErrorState() },
1727
+ // `kit.files.assets` points here: static files copied to the site root
1728
+ // verbatim. A README rather than a .gitkeep because this one gets
1729
+ // PUBLISHED — everything here lands in frontend/dist, so the placeholder
1730
+ // that keeps the directory in git is a file real users will see deployed.
1731
+ {
1732
+ path: "frontend/static/README.md",
1733
+ content: `Files here are copied to the site root as-is \u2014 \`favicon.png\` is served
1734
+ at \`/favicon.png\`. Reference them from a component with a root-relative
1735
+ path, or from \`app.html\` via \`%sveltekit.assets%\`.
1736
+
1737
+ Delete this file once you add something real.
1738
+ `
1739
+ },
1740
+ { path: `${ui}/button/button.svelte`, content: renderButtonSvelte() },
1741
+ { path: `${ui}/button/index.ts`, content: renderButtonIndex() },
1742
+ {
1743
+ path: `${ui}/card/card.svelte`,
1744
+ content: cardPart(
1745
+ "card",
1746
+ "div",
1747
+ "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm"
1748
+ )
1749
+ },
1750
+ {
1751
+ path: `${ui}/card/card-header.svelte`,
1752
+ content: cardPart(
1753
+ "card-header",
1754
+ "div",
1755
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6"
1756
+ )
1757
+ },
1758
+ {
1759
+ path: `${ui}/card/card-title.svelte`,
1760
+ content: cardPart("card-title", "div", "leading-none font-semibold")
1761
+ },
1762
+ {
1763
+ path: `${ui}/card/card-description.svelte`,
1764
+ content: cardPart("card-description", "div", "text-sm text-muted-foreground")
1765
+ },
1766
+ {
1767
+ path: `${ui}/card/card-content.svelte`,
1768
+ content: cardPart("card-content", "div", "px-6")
1769
+ },
1770
+ {
1771
+ path: `${ui}/card/card-footer.svelte`,
1772
+ content: cardPart("card-footer", "div", "flex items-center px-6 [.border-t]:pt-6")
1773
+ },
1774
+ { path: `${ui}/card/index.ts`, content: renderCardIndex() }
1775
+ ];
1776
+ },
1777
+ iconBinding(id) {
1778
+ return iconLibraryOf(id).svelte;
1779
+ },
1780
+ readmeFrontendSection(icons) {
1781
+ return `[SvelteKit](https://svelte.dev/docs/kit) with Svelte 5, styled with
1782
+ [Tailwind CSS](https://tailwindcss.com) v4 and
1783
+ [shadcn-svelte](https://shadcn-svelte.com).
1784
+
1785
+ Pages live in [\`frontend/src/routes/\`](frontend/src/routes/) \u2014 \`+page.svelte\`
1786
+ is a page, a subdirectory is a nested route. Routes are **prerendered**: each
1787
+ becomes its own HTML document at build time, so a page loads as itself rather
1788
+ than as a shell that fills in afterwards, and an unmatched path returns a real
1789
+ 404 from [\`+error.svelte\`](frontend/src/routes/+error.svelte).
1790
+
1791
+ > **There is no server.** \`npm run xano:deploy\` ships this app to a static host
1792
+ > with no runtime of its own; Xano is the backend, reached through
1793
+ > [\`frontend/src/lib/api.ts\`](frontend/src/lib/api.ts). So \`+page.server.ts\`,
1794
+ > form actions, and server \`load\` have nothing to run on \u2014 and **nothing warns
1795
+ > you**: a project using them builds and deploys green, then fails in the
1796
+ > browser. Treat them as unavailable. That is the trade \u2014 the backend is
1797
+ > authored in [\`xano/\`](xano/) as TypeScript and deployed alongside the
1798
+ > frontend, so it is a real backend, just not this one.
1799
+ >
1800
+ > \`frontend/src/routes/+layout.ts\` sets \`prerender = true\` for the whole app.
1801
+ > Because prerendering renders at build time, module-scope \`window\`/\`document\`
1802
+ > access now fails the **build** rather than the browser \u2014 use \`onMount\`, or
1803
+ > guard with \`import { browser } from "$app/environment"\`.
1804
+
1805
+ **A dynamic route fails the build until it says how it renders.** A route like
1806
+ \`/posts/[id]\` cannot be prerendered unless the build knows which ids exist, so
1807
+ the build stops and names it. For rows created at runtime (the usual detail
1808
+ page), render it in the browser:
1809
+
1810
+ \`\`\`ts
1811
+ // frontend/src/routes/posts/[id]/+page.ts
1812
+ export const prerender = false;
1813
+ \`\`\`
1814
+
1815
+ The host has no file for \`/posts/42\`, so it answers with \`404.html\`, which the
1816
+ SDK's adapter (\`@xano/sdk/sveltekit\`, in \`vite.config.ts\`) writes as the app
1817
+ shell: it boots the app and renders the route, so a reload or a deep link works
1818
+ (the response status is still 404). Only for ids known at build time, list them
1819
+ instead \u2014 each gets its own page, and no other id has one:
1820
+ \`export const entries = () => [{ id: "1" }, { id: "2" }];\`
1821
+
1822
+ [\`frontend/src/routes/404/+page.svelte\`](frontend/src/routes/404/+page.svelte)
1823
+ prerenders to \`404.html\` while every route is prerendered, which is what makes
1824
+ an unmatched path return a real 404. It has to be a route: SvelteKit never
1825
+ prerenders \`+error.svelte\` to a file. Once a route sets \`prerender = false\`,
1826
+ the adapter writes the app shell there instead, and an unmatched path renders
1827
+ \`+error.svelte\` in the browser, still with a 404.
1828
+
1829
+ shadcn-svelte is not a dependency \u2014
1830
+ its components are copied into
1831
+ [\`frontend/src/lib/components/ui/\`](frontend/src/lib/components/ui/) and owned by
1832
+ this project, so edit them freely. \`Button\` and \`Card\` are already there; add
1833
+ more with:
1834
+
1835
+ \`\`\`bash
1836
+ npx shadcn-svelte@latest add dialog input form
1837
+ \`\`\`
1838
+
1839
+ [\`components.json\`](components.json) is pre-configured, so that works with no
1840
+ \`shadcn-svelte init\` step. ${iconLibraryOf(icons).guidance.svelte}
1841
+
1842
+ \`\`\`ts
1843
+ ${iconLibraryOf(icons).svelte.arrowImport}
1844
+ \`\`\`
1845
+
1846
+ [\`frontend/src/routes/+page.svelte\`](frontend/src/routes/+page.svelte) already
1847
+ does, and that is the form \`npx shadcn-svelte@latest add\` writes too.
1848
+
1849
+ Components import through the \`$lib\` alias
1850
+ (\`$lib/components/ui/button\`, \`$lib/utils\`), which SvelteKit points at
1851
+ \`frontend/src/lib/\` via \`kit.files\` in
1852
+ [\`vite.config.ts\`](vite.config.ts) \u2014 there is no \`paths\` entry to keep in
1853
+ sync, and adding one would just be a second answer that can disagree.
1854
+
1855
+ Colors come from the theme tokens in
1856
+ [\`frontend/src/index.css\`](frontend/src/index.css) \u2014 see **Theming** below.
1857
+
1858
+ > \`npm run typecheck\` runs \`svelte-kit sync && svelte-check\`, not \`tsc\`. It
1859
+ > checks both halves \u2014 the backend's TypeScript and the components \u2014 where
1860
+ > \`tsc\` cannot read \`.svelte\` files at all. Keep it in the script: it is what
1861
+ > makes the frontend unable to drift from the backend defs. \`sync\` regenerates
1862
+ > \`.svelte-kit/\`, which \`tsconfig.json\` extends; \`npm install\` runs it too.`;
1863
+ },
1864
+ agentGuidanceSection(icons) {
1865
+ return `- \`frontend/src/\` \u2014 the SvelteKit app. Svelte 5 + Tailwind v4 + shadcn-svelte.
1866
+ - **There is no server.** This deploys to a static host with no runtime, and
1867
+ Xano is the backend. Never write \`+page.server.ts\`, \`+layout.server.ts\`,
1868
+ form actions, or server \`load\` \u2014 reach the backend through
1869
+ \`frontend/src/lib/api.ts\` instead, and never stand up a second backend here.
1870
+ - **The build will not stop you.** A project containing \`+page.server.ts\`
1871
+ compiles and deploys green, then fails in the browser on the live site. A
1872
+ passing build is not evidence that a server feature works \u2014 there is
1873
+ nothing to run it.
1874
+ - \`frontend/src/routes/\` \u2014 file-based routing. A page is \`+page.svelte\`, a
1875
+ nested route is a subdirectory. **Routes live under \`frontend/src/\`, not a
1876
+ top-level \`src/\`** \u2014 \`files\` in the \`sveltekit()\` plugin config in
1877
+ \`vite.config.ts\` points SvelteKit here. A file added under a top-level
1878
+ \`src/routes/\` is not a route and is never served.
1879
+ - **Routes are prerendered.** \`frontend/src/routes/+layout.ts\` sets
1880
+ \`prerender = true\` for the whole app, so every route is rendered to its own
1881
+ HTML document at build time and served as a real page. A static route needs
1882
+ nothing registered \u2014 add \`+page.svelte\` and it works.
1883
+ - **Prerendering renders at BUILD time**, so module-scope \`window\`/\`document\`
1884
+ access now breaks the build, not the browser. Use \`onMount\`, or guard with
1885
+ \`import { browser } from "$app/environment"\`. This is a page option, not a
1886
+ server \u2014 do not "fix" it by reaching for \`ssr\` or a server file.
1887
+ - **A dynamic route fails the build until it says how it renders.**
1888
+ \`/posts/[id]\` cannot be prerendered unless the build knows which ids exist.
1889
+ For rows created at runtime (a detail page), export
1890
+ \`const prerender = false\` from that route's \`+page.ts\`: the host answers
1891
+ it with the \`404.html\` app shell the SDK's adapter writes, which renders it
1892
+ in the browser, so reloads and deep links work. \`entries\` only fits ids
1893
+ known at build time \u2014 any other id has no page.
1894
+ - \`frontend/src/routes/404/+page.svelte\` prerenders to \`404.html\`, which is
1895
+ what makes an unmatched path return a real 404 instead of silently serving a
1896
+ page with a 200. Keep it: without it a missing asset comes back as HTML and
1897
+ the browser reports \`Unexpected token '<'\`. It has to be a route \u2014 SvelteKit
1898
+ never prerenders \`+error.svelte\` to a file, so that alone would ship no
1899
+ \`404.html\`.
1900
+ - Keep the adapter in \`vite.config.ts\` (\`@xano/sdk/sveltekit\`). Do NOT
1901
+ swap in \`@sveltejs/adapter-static\` with a \`fallback\`: it writes an empty
1902
+ shell over the prerendered home page.
1903
+ - Data loading: call \`api.ts\` from the component, or from a universal
1904
+ \`+page.ts\` \`load\` if you need it before render. Both run in the browser.
1905
+ - \`frontend/src/lib/components/ui/\` \u2014 shadcn-svelte components, **copied in and
1906
+ owned by this project**. Edit them directly; there is no library to configure
1907
+ around.
1908
+ - Need one that isn't there? \`npx shadcn-svelte@latest add <name>\` \u2014 do not
1909
+ hand-roll it, and do not add a different component library. Note the
1910
+ \`-svelte\` suffix: the plain \`shadcn\` CLI writes React and will not work here.
1911
+ - ${iconLibraryOf(icons).guidance.svelte.replace(/\n/g, "\n ")}
1912
+ - That import form is what \`frontend/src/routes/+page.svelte\` already uses and
1913
+ what \`npx shadcn-svelte@latest add\` writes, so matching it keeps what you
1914
+ write consistent with what the CLI generates beside it. Do not add another
1915
+ icon library and do not paste raw inline \`<svg>\` markup.
1916
+ - Import via the \`$lib\` alias (\`$lib/components/ui/button\`, \`$lib/utils\`),
1917
+ which SvelteKit generates for both TypeScript and the bundler. Do not add a
1918
+ \`paths\` entry for it \u2014 a hand-written one is a second source of truth that
1919
+ can disagree with the generated one.
1920
+ - Components are Svelte 5 **runes** (\`$props()\`, \`$bindable()\`,
1921
+ \`{@render children?.()}\`) \u2014 not the Svelte 4 \`export let\` / slot API. Runes
1922
+ mode is FORCED in \`vite.config.ts\`, so \`export let\` is a compile error
1923
+ naming the file rather than a component that quietly builds in legacy mode.
1924
+ Do not "fix" that error by relaxing \`compilerOptions.runes\` \u2014 write the
1925
+ rune.
1926
+ - \`npm run typecheck\` runs \`svelte-kit sync && svelte-check\`, which checks both
1927
+ \`xano/\` and the components. Do not swap it for \`tsc --noEmit\`: \`tsc\` cannot
1928
+ read \`.svelte\` files and fails on the UI kit's barrels. \`sync\` regenerates
1929
+ \`.svelte-kit/\`, which \`tsconfig.json\` extends \u2014 run it after adding a route.`;
1930
+ }
1931
+ };
1932
+
1933
+ // src/emit/frontend-presets.ts
1934
+ var FRAMEWORKS = ["react", "svelte"];
1935
+ var DEFAULT_FRAMEWORK = "react";
1936
+ var PRESETS = {
1937
+ react: reactPreset,
1938
+ svelte: sveltePreset
1939
+ };
1940
+ function findFrontendPreset(id) {
1941
+ const normalized = id.trim().toLowerCase();
1942
+ return FRAMEWORKS.includes(normalized) ? PRESETS[normalized] : void 0;
1943
+ }
1944
+ function allFrontendPresets() {
1945
+ return FRAMEWORKS.map((id) => PRESETS[id]);
1946
+ }
1947
+ function resolveFrameworkFlag(raw) {
1948
+ const preset = findFrontendPreset(raw);
1949
+ if (preset === void 0) {
1950
+ throw new Error(
1951
+ `Unknown --framework "${raw}". Valid frameworks: ${FRAMEWORKS.join(", ")}.`
1952
+ );
1953
+ }
1954
+ return preset;
1955
+ }
1956
+
1957
+ export {
1958
+ sdkDep,
1959
+ renderPackageJson,
1960
+ renderTsconfig,
1961
+ renderLambdaTsconfig,
1962
+ renderViteConfig,
1963
+ renderIndexHtml,
1964
+ renderGitignore,
1965
+ NODE_MIN,
1966
+ renderGitattributes,
1967
+ checkJobDir,
1968
+ checkWorkflowName,
1969
+ checkWorkflowPath,
1970
+ renderCheckWorkflow,
1971
+ renderEnvExample,
1972
+ renderReadme,
1973
+ renderXanoIndex,
1974
+ renderXanoExampleMd,
1975
+ describeOrigin,
1976
+ renderCodegenMarker,
1977
+ renderCodegenReadme,
1978
+ initLanding,
1979
+ codegenLanding,
1980
+ renderIndexCss,
1981
+ renderThemeModule,
1982
+ renderCnUtil,
1983
+ renderApiTs,
1984
+ FRAMEWORKS,
1985
+ DEFAULT_FRAMEWORK,
1986
+ findFrontendPreset,
1987
+ allFrontendPresets,
1988
+ resolveFrameworkFlag
1989
+ };
1990
+ //# sourceMappingURL=chunk-OPHB5ORL.js.map