@xanots/sdk 0.0.11 → 0.0.13

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 (100) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +16 -9
  3. package/dist/.build-fingerprint +1 -1
  4. package/dist/{agent-file-refresh-QNKN5RYD.js → agent-file-refresh-GQWAAOBV.js} +4 -4
  5. package/dist/bin.js +11 -11
  6. package/dist/{branch-commands-2BLOC2GR.js → branch-commands-5KMPAAZV.js} +7 -7
  7. package/dist/bundle.d.ts +2 -2
  8. package/dist/bundle.js +2 -2
  9. package/dist/{capture-4WVJY4DQ.js → capture-YLUVAITI.js} +2 -2
  10. package/dist/{chunk-22TKBSDV.js → chunk-2AY3PKF4.js} +2 -2
  11. package/dist/chunk-2VTJSI6X.js +192 -0
  12. package/dist/{chunk-5XZ744TS.js → chunk-2ZCUO2UG.js} +1 -1
  13. package/dist/{chunk-XEOX6AM7.js → chunk-3LQGF2WS.js} +2 -2
  14. package/dist/{chunk-WP4OZZV4.js → chunk-3VFCKHOB.js} +2 -2
  15. package/dist/{chunk-XHEXOES3.js → chunk-5YDINUAP.js} +1 -1
  16. package/dist/{chunk-LU7TRWMC.js → chunk-6VNRMKFJ.js} +2 -2
  17. package/dist/{chunk-CTD5ZCV6.js → chunk-7WRJPKGK.js} +2 -2
  18. package/dist/{chunk-3INK4Y4E.js → chunk-AE3PDSDS.js} +1 -1
  19. package/dist/{chunk-DBFU47BJ.js → chunk-AOFFKSJC.js} +2 -2
  20. package/dist/{chunk-TCFIPDB3.js → chunk-AVGDL6RB.js} +1 -1
  21. package/dist/{chunk-UOZMSF4C.js → chunk-BYQHCCYU.js} +5 -5
  22. package/dist/{chunk-BC2C5GVI.js → chunk-DCMANKMX.js} +1 -1
  23. package/dist/{chunk-WHOJWOSV.js → chunk-EETVJZAZ.js} +1 -1
  24. package/dist/{chunk-VNQM3V2C.js → chunk-EXENFOWE.js} +2 -2
  25. package/dist/{chunk-AIZKXUNP.js → chunk-IMLYGQK6.js} +2 -2
  26. package/dist/{chunk-OWGCOGKK.js → chunk-MEFMTICH.js} +83 -8
  27. package/dist/{chunk-QK7ZQJLP.js → chunk-N74KDCBD.js} +39 -25
  28. package/dist/{chunk-3IGNIP6R.js → chunk-NDP7OUPS.js} +1 -1
  29. package/dist/{chunk-4Q7ZOHH7.js → chunk-OONT4ZL4.js} +3 -3
  30. package/dist/{chunk-EQW3YT5U.js → chunk-P6PVBQL6.js} +2 -2
  31. package/dist/{chunk-QYMAZRAU.js → chunk-PJNWOZMT.js} +5 -5
  32. package/dist/{chunk-BSK7ELHU.js → chunk-PR7OXHGZ.js} +1 -1
  33. package/dist/{chunk-RCT7UX7B.js → chunk-RLI6XD4O.js} +32 -27
  34. package/dist/{chunk-ZQ2PKR6R.js → chunk-RQ3FXV4K.js} +2 -2
  35. package/dist/{chunk-Q77KNEUL.js → chunk-RQNMTDXD.js} +1703 -2
  36. package/dist/{chunk-4IF54NU5.js → chunk-S3DOJOW4.js} +41 -21
  37. package/dist/{chunk-HYBN4H3F.js → chunk-SS2V2QOG.js} +27 -20
  38. package/dist/{chunk-QKM4U5UK.js → chunk-TJS2AF5Y.js} +2 -2
  39. package/dist/{chunk-DIA7CT7J.js → chunk-V5Y7D4LH.js} +11 -2
  40. package/dist/{chunk-KA6G2L7U.js → chunk-VK26K7AY.js} +3 -3
  41. package/dist/{chunk-VAF6A3YD.js → chunk-XWFRNJMQ.js} +1 -1
  42. package/dist/{chunk-7ZYW652H.js → chunk-YHS6VVLJ.js} +2 -2
  43. package/dist/{chunk-G4EJMQLD.js → chunk-YX22LKQE.js} +2 -2
  44. package/dist/{chunk-F6CYJ7TN.js → chunk-Z2ZIE5CO.js} +2 -2
  45. package/dist/cli.d.ts +13 -6
  46. package/dist/cli.js +10 -10
  47. package/dist/codegen-command-ZDHMGFWZ.js +47 -0
  48. package/dist/codegen.d.ts +5 -5
  49. package/dist/codegen.js +2 -2
  50. package/dist/{completion-WF46272M.js → completion-HJU5QEFB.js} +2 -2
  51. package/dist/{deploy-command-IP7V7GT4.js → deploy-command-CGVKRWUE.js} +19 -19
  52. package/dist/{ephemeral-command-U4AQ3TXX.js → ephemeral-command-2NKPEXUU.js} +8 -8
  53. package/dist/index.d.ts +88 -104
  54. package/dist/index.js +9 -9
  55. package/dist/init-command-DNDONP3O.js +32 -0
  56. package/dist/internal.d.ts +10 -16
  57. package/dist/internal.js +34 -34
  58. package/dist/{io-P2H75UV2.js → io-UBDMMDH6.js} +3 -3
  59. package/dist/{live-diff-IXKBVG4K.js → live-diff-HCOTN5WC.js} +3 -3
  60. package/dist/{lock-46FWYE4D.js → lock-HQ4KARU2.js} +2 -2
  61. package/dist/{lock-commands-ZZKZ4LZJ.js → lock-commands-6U7UIJGR.js} +11 -11
  62. package/dist/{login-command-Z6CHTA57.js → login-command-P7LXD5TE.js} +6 -6
  63. package/dist/{loop-D5NPL4VH.js → loop-IC5ISSYB.js} +3 -3
  64. package/dist/{marketplace-command-P4IPLJ6J.js → marketplace-command-T6JCHW7J.js} +4 -4
  65. package/dist/{meta-client-K2J4XH64.js → meta-client-LKRKR3L2.js} +4 -4
  66. package/dist/node.d.ts +6 -6
  67. package/dist/node.js +14 -14
  68. package/dist/{preflight-command-K346GPTY.js → preflight-command-UYE7SUQV.js} +16 -16
  69. package/dist/{profile-command-LJSBDV2L.js → profile-command-WBNSMQSI.js} +3 -3
  70. package/dist/{release-command-IMNTIVWK.js → release-command-CMMYT6XK.js} +26 -26
  71. package/dist/{response-BQVQ24l1.d.ts → response-D6xGLEIn.d.ts} +18 -23
  72. package/dist/{routes-manifest-PWZHDOI5.js → routes-manifest-MN6XBYRE.js} +25 -14
  73. package/dist/{runtime-V4C3AC3A.js → runtime-LSLIDALK.js} +1 -1
  74. package/dist/scaffold.d.ts +16 -8
  75. package/dist/scaffold.js +7 -11
  76. package/dist/{static-host-3WMV7IZO.js → static-host-4JDQCHUW.js} +1 -1
  77. package/dist/{status-command-AL47VG7H.js → status-command-VARULAR5.js} +4 -4
  78. package/dist/{store-BLyNeQ8S.d.ts → store-9Psd0jiF.d.ts} +115 -102
  79. package/dist/{test-command-YAZLKLGQ.js → test-command-YCAR4O35.js} +7 -7
  80. package/dist/{upgrade-command-BN3EHAOI.js → upgrade-command-A75DOIUH.js} +15 -16
  81. package/dist/{workspace-2COHDBM3.js → workspace-33KHFLX3.js} +2 -2
  82. package/dist/{workspace-command-MNK7Y7MQ.js → workspace-command-YELP47SJ.js} +26 -28
  83. package/dist/{workspace-export-DURY5WYL.js → workspace-export-MLYTYZS5.js} +3 -3
  84. package/dist/{xdo-BjJj5W_E.d.ts → xdo-ODuJklk6.d.ts} +27 -23
  85. package/guides/authoring.md +11 -2
  86. package/guides/typed-frontend.md +23 -6
  87. package/llms/kinds-realtime.md +2 -2
  88. package/llms/statements-data.md +3 -2
  89. package/llms/tests.md +1 -1
  90. package/llms/triggers.md +2 -2
  91. package/llms/values.md +1 -1
  92. package/llms-full.txt +26 -26
  93. package/llms.txt +17 -18
  94. package/manifest.json +6 -2
  95. package/package.json +1 -1
  96. package/dist/chunk-ANUDXFEX.js +0 -881
  97. package/dist/chunk-JGCWTCA7.js +0 -95
  98. package/dist/chunk-YBC3IKMF.js +0 -845
  99. package/dist/codegen-command-FUT2KJB6.js +0 -49
  100. package/dist/init-command-NPVL32L6.js +0 -34
@@ -1571,6 +1571,855 @@ function iconLibraryOf(id) {
1571
1571
  return id === void 0 ? defaultIconLibrary() : LIBRARIES[id];
1572
1572
  }
1573
1573
 
1574
+ // src/emit/init-templates.ts
1575
+ function sdkDep(sdkVersion) {
1576
+ const parsed = /^(\d+)\.(\d+)\.\d+/.exec(sdkVersion);
1577
+ if (!parsed) return ">=0.0.1 <0.1.0";
1578
+ const [major, minor] = [Number(parsed[1]), Number(parsed[2])];
1579
+ const ceiling = major > 0 ? `${major + 1}.0.0` : `0.${minor + 1}.0`;
1580
+ return `>=${sdkVersion} <${ceiling}`;
1581
+ }
1582
+ function renderPackageJson({ appName, sdkVersion }, preset, choice = defaultThemeChoice()) {
1583
+ const check = preset.checkCmd;
1584
+ const pkg = {
1585
+ name: appName,
1586
+ version: "0.1.0",
1587
+ private: true,
1588
+ type: "module",
1589
+ scripts: {
1590
+ dev: "vite",
1591
+ build: `${check} && vite build`,
1592
+ preview: "vite preview",
1593
+ typecheck: check,
1594
+ // Both are prefixed with a typecheck: the export pass validates what it
1595
+ // can see in the encoded bundle, and TypeScript validates the rest. Only
1596
+ // `build` used to run `tsc`, so anyone following the documented workflow
1597
+ // shipped with the compiler's half of the checks never run.
1598
+ // `--lock` on both: object identity derives from `(type, name)`, so
1599
+ // without a lock a rename re-derives the guid and the engine does
1600
+ // delete-and-recreate instead of rename-in-place. The lock has to be
1601
+ // adopted BEFORE the identities matter — by the time a rename lands
1602
+ // against data anyone cares about, the guids have already drifted and
1603
+ // recovery is `xanots lock import`. The scaffold is the earliest possible
1604
+ // moment, so it opts in from the first export (issue #26). Once the file
1605
+ // exists every export uses it, flag or not.
1606
+ "xano:export": `${check} && xanots export ./xano/index.ts --out workspace.json --lock`,
1607
+ // Runs `build` rather than `check` directly: `--static ./frontend/dist`
1608
+ // needs that directory to EXIST, and only `vite build` writes it. Pointing
1609
+ // the flag at a directory the script never produced meant a fresh clone
1610
+ // running the one documented command had no `dist` to ship (issue #241).
1611
+ // `build` already prefixes the same typecheck, so this is one check, not
1612
+ // two.
1613
+ "xano:deploy": `npm run build && xanots deploy ./xano/index.ts --static ./frontend/dist --lock`,
1614
+ // The CI guard: fails instead of changing xano.lock, so an uncommitted
1615
+ // identity change is caught in review rather than on a deploy.
1616
+ "xano:check": `${check} && xanots export ./xano/index.ts --out workspace.json --frozen-lock`,
1617
+ // No compile step and no entry file: this runs what is DEPLOYED, so it
1618
+ // pairs with `xano:deploy` rather than repeating its work. Exits 5 on a
1619
+ // failing suite, which is what makes it usable as a CI gate.
1620
+ "xano:test": "xanots test run-all",
1621
+ // Framework-owned scripts, last so a preset can add to the set but the
1622
+ // shared xano:* contract above stays the same in every scaffold.
1623
+ ...preset.extraScripts
1624
+ },
1625
+ dependencies: {
1626
+ // `@xanots/sdk` is the only XanoTS dependency a scaffold ships with.
1627
+ // Add-ons (`@xanots/auth`, and the packages that follow it) are installed
1628
+ // on demand — a project that never registers auth should not carry it,
1629
+ // and an add-on's release cadence is its own, not the CLI's.
1630
+ "@xanots/sdk": sdkDep(sdkVersion),
1631
+ ...preset.dependencies,
1632
+ // The icon set and the typefaces are choices, not framework constants, so
1633
+ // they are merged here rather than sitting in `preset.dependencies`. Both
1634
+ // land after the preset's own entries: a preset that still names an icon
1635
+ // package would otherwise win over the set the user actually picked.
1636
+ ...preset.iconBinding(choice.icons).dependency,
1637
+ ...fontDependencies(choice.fonts)
1638
+ },
1639
+ devDependencies: { ...preset.devDependencies },
1640
+ // Not ">=20": Vite 8 and vite-plugin-svelte 7 both declare
1641
+ // `^20.19 || >=22.12`, so 20.0–20.18 installs the toolchain and then fails
1642
+ // to run it. Stating the real floor turns that into an install-time
1643
+ // warning instead of a confusing crash.
1644
+ engines: { node: ">=20.19" }
1645
+ };
1646
+ return JSON.stringify(pkg, null, 2) + "\n";
1647
+ }
1648
+ function renderTsconfig(preset) {
1649
+ const aliasPaths = Object.keys(preset.tsconfigPaths).length > 0 ? { paths: { ...preset.tsconfigPaths } } : {};
1650
+ const tsconfig = {
1651
+ // Present only when the framework generates a config to build on — see
1652
+ // FrontendPreset.tsconfigExtends. The `include` below is re-declared on
1653
+ // top of it deliberately: an extended config's `include` is REPLACED, not
1654
+ // merged, and dropping `xano` here would leave the backend unchecked while
1655
+ // every command still exits 0.
1656
+ ...preset.tsconfigExtends === void 0 ? {} : { extends: preset.tsconfigExtends },
1657
+ compilerOptions: {
1658
+ target: "ES2022",
1659
+ lib: ["ES2022", "DOM", "DOM.Iterable"],
1660
+ module: "ESNext",
1661
+ moduleResolution: "bundler",
1662
+ ...preset.tsconfigOptions,
1663
+ strict: true,
1664
+ noEmit: true,
1665
+ esModuleInterop: true,
1666
+ skipLibCheck: true,
1667
+ resolveJsonModule: true,
1668
+ isolatedModules: true,
1669
+ types: ["node", "vite/client"],
1670
+ ...aliasPaths
1671
+ },
1672
+ // Both halves of the project typecheck together: the XanoTS backend and
1673
+ // the frontend that derives its types from the backend's defs. A preset
1674
+ // that extends a generated config appends whatever that config listed —
1675
+ // `include` is replaced, not merged, so anything it declared is otherwise
1676
+ // silently dropped. See FrontendPreset.tsconfigInclude.
1677
+ include: ["xano", "frontend/src", ...preset.tsconfigInclude ?? []]
1678
+ };
1679
+ return JSON.stringify(tsconfig, null, 2) + "\n";
1680
+ }
1681
+ function renderViteConfig(preset) {
1682
+ const root = preset.viteRoot === void 0 ? "frontend" : preset.viteRoot;
1683
+ const build = preset.viteBuild === void 0 ? { outDir: "dist", emptyOutDir: true } : preset.viteBuild;
1684
+ const header = root === null ? `// Vite's root is the project root. The framework resolves its own file
1685
+ // locations from the plugin config below \u2014 this file is the ONLY place that
1686
+ // config lives \u2014 and routes the build through its adapter, which writes
1687
+ // 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
1688
+ // while the XanoTS backend sits in xano/ as a peer. The build lands in
1689
+ // ${root}/dist, which \`npm run xano:deploy\` ships as the static frontend.`;
1690
+ const rootBlock = root === null ? "" : ` root: ${JSON.stringify(root)},
1691
+ // Vite resolves \`.env\` files against \`root\`, which is ${root}/ here \u2014 but
1692
+ // \`.env.example\` sits at the project root, so that is where anyone will
1693
+ // actually put their \`.env.local\`. Point envDir back at this file's own
1694
+ // directory so VITE_XANO_HOST is picked up in dev.
1695
+ envDir: fileURLToPath(new URL(".", import.meta.url)),
1696
+ `;
1697
+ const buildBlock = build === null ? "" : ` build: ${renderInlineObject(build)},
1698
+ `;
1699
+ const aliasBlock = preset.aliasName === null ? "" : ` resolve: {
1700
+ alias: {
1701
+ // The alias the UI kit writes its imports against. Resolved from this
1702
+ // file rather than from Vite's root so it points at the right directory
1703
+ // either way. Keep in sync with the \`paths\` entry in tsconfig.json.
1704
+ "${preset.aliasName}": fileURLToPath(new URL("./${preset.aliasTarget}", import.meta.url)),
1705
+ },
1706
+ },
1707
+ `;
1708
+ const fileUrlImport = rootBlock === "" && aliasBlock === "" ? "" : `import { fileURLToPath } from "node:url";
1709
+ `;
1710
+ return `${fileUrlImport}import { defineConfig } from "vite";
1711
+ ${preset.viteImports.join("\n")}
1712
+
1713
+ ${header}
1714
+ export default defineConfig({
1715
+ ${rootBlock}${buildBlock} plugins: [${preset.vitePlugins.join(", ")}],
1716
+ ${aliasBlock} server: { host: "127.0.0.1", port: 5173 },
1717
+ });
1718
+ `;
1719
+ }
1720
+ function renderInlineObject(obj) {
1721
+ const entries = Object.entries(obj).map(([k, v]) => `${k}: ${JSON.stringify(v)}`);
1722
+ return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
1723
+ }
1724
+ function renderIndexHtml({ appName }, preset, choice = defaultThemeChoice()) {
1725
+ if (preset.entryScript === void 0) {
1726
+ throw new Error(`The ${preset.id} preset owns its own HTML entry; renderIndexHtml does not apply.`);
1727
+ }
1728
+ return `<!doctype html>
1729
+ <html lang="en">
1730
+ <head>
1731
+ <meta charset="UTF-8" />
1732
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1733
+ <title>${appName}</title>
1734
+ ${renderThemeScript(choice.dark)} </head>
1735
+ <body>
1736
+ <div id="root"></div>
1737
+ <script type="module" src="${preset.entryScript}"></script>
1738
+ </body>
1739
+ </html>
1740
+ `;
1741
+ }
1742
+ function renderGitignore(preset) {
1743
+ const framework = (preset?.gitignoreEntries ?? []).map((e) => `${e}
1744
+ `).join("");
1745
+ return `node_modules/
1746
+ dist/
1747
+ workspace.json
1748
+ .xano/
1749
+ .env
1750
+ .env.local
1751
+ *.local
1752
+ ${framework}
1753
+ # Not ignored, on purpose: xano/xano.lock pins object identity across renames
1754
+ # and environments. Commit it.
1755
+ `;
1756
+ }
1757
+ function renderEnvExample() {
1758
+ return `# Point the frontend at a deployed Xano backend. Leave unset to run the UI
1759
+ # with no backend. When you \`npm run xano:deploy\`, the backend URL is injected
1760
+ # as window.XANO_HOST at runtime instead \u2014 no rebuild needed.
1761
+ VITE_XANO_HOST=https://your-instance.xano.io
1762
+ `;
1763
+ }
1764
+ function renderReadme({ appName }, preset, choice = defaultThemeChoice()) {
1765
+ return `# ${appName}
1766
+
1767
+ A [XanoTS](https://www.npmjs.com/package/@xanots/sdk) project: a Xano
1768
+ backend authored in TypeScript under [\`xano/\`](xano/), and a ${preset.label}
1769
+ frontend under [\`frontend/\`](frontend/) that derives its request paths and
1770
+ types from the backend defs \u2014 so the two can't drift.
1771
+
1772
+ ## Quick start
1773
+
1774
+ \`\`\`bash
1775
+ npm install
1776
+ npm run dev # run the frontend (no backend needed yet)
1777
+ \`\`\`
1778
+
1779
+ Then author your backend in [\`xano/index.ts\`](xano/index.ts) \u2014 start with the
1780
+ walkthrough in [\`xano/EXAMPLE.md\`](xano/EXAMPLE.md).
1781
+
1782
+ ## Deploy
1783
+
1784
+ \`\`\`bash
1785
+ xanots login # once, to authenticate against your Xano account
1786
+ npm run xano:deploy # build the frontend, then ship it with the backend
1787
+ \`\`\`
1788
+
1789
+ - \`npm run xano:export\` compiles the backend to \`workspace.json\` (don't commit it).
1790
+ - \`npm run xano:deploy\` deploys the backend and the built frontend to a live
1791
+ **ephemeral** environment and prints its URL. Run it again to refresh the same
1792
+ environment; if it expired, a fresh one is created and the new URL is called out.
1793
+ - \`xanots status\` says who you are signed in as, which workspace you are bound to, and
1794
+ which environment this project last deployed to \u2014 its URL, and when it expires. You
1795
+ never have to remember the environment's name.
1796
+ - \`npm run xano:test\` runs the tests the DEPLOYED environment carries \u2014 the \`tests\`
1797
+ on a query/function/middleware and any \`workflowTest()\`. It compiles nothing, so
1798
+ deploy first. A failing suite exits 5, distinct from a crash. \`xanots deploy
1799
+ ./xano/index.ts --test\` does both in one step.
1800
+
1801
+ ## \`xano.lock\` \u2014 commit it
1802
+
1803
+ Object identity derives from \`(type, name)\`, so a rename would otherwise change
1804
+ an object's guid and the engine would **delete and recreate** it rather than
1805
+ renaming it in place \u2014 losing its rows on a record-preserving import.
1806
+ [\`xano/xano.lock\`](xano/xano.lock) freezes each guid and each API group's
1807
+ canonical slug, so renames and re-deploys keep the same identities (and the same
1808
+ public URLs).
1809
+
1810
+ It is created by \`npm run xano:export\` or \`npm run xano:deploy\`, and **must be
1811
+ committed**. Adopt it from the first export: once identities have drifted, the
1812
+ recovery path is \`xanots lock import <live-bundle.json> --lock=xano/xano.lock\`
1813
+ against the deployed workspace.
1814
+
1815
+ \`\`\`bash
1816
+ npm run xano:check # CI: fail if the export would change xano.lock
1817
+ \`\`\`
1818
+
1819
+ To rename an object: rename it in code, run \`npm run xano:export\` (stderr prints
1820
+ the exact fix-up), run \`xanots lock rename <kind> <old> <new> --lock=xano/xano.lock\`,
1821
+ then export again. \`lock rename\` and \`lock import\` need that flag here \u2014 they take no
1822
+ entry file, so they look for the lock in the current directory, while
1823
+ \`lock prune ./xano/index.ts\` derives it from the entry like \`export\` does.
1824
+
1825
+ ## The one contract
1826
+
1827
+ [\`frontend/src/lib/api.ts\`](frontend/src/lib/api.ts) imports the XanoTS query
1828
+ defs and derives paths (\`getPath()\`) and request/response types
1829
+ (\`InferInput\` / \`InferResponse\`) from them. Never hand-type a URL or a request
1830
+ body \u2014 change a def and the frontend types follow.
1831
+
1832
+ > To spot-check a def from Node (read \`getPath()\`/\`verb\`, log a value), run a real
1833
+ > file with \`tsx <file.ts>\` **from inside the project root** \u2014 not \`tsx -e\`, not
1834
+ > bare \`node file.ts\`, and not from another directory (they mis-resolve the
1835
+ > intra-workspace \`.js\` imports and the \`@xanots/sdk\` specifier). Or use
1836
+ > \`xanots routes xano/index.ts\` to list every endpoint's verb + path.
1837
+
1838
+ ## The frontend
1839
+
1840
+ ${preset.readmeFrontendSection(choice.icons)}
1841
+
1842
+ ${renderReadmeThemingSection(choice)}
1843
+
1844
+ ## Add-ons
1845
+
1846
+ XanoTS is composable with other \`@xanots/*\` packages:
1847
+
1848
+ - **[\`@xanots/auth\`](https://www.npmjs.com/package/@xanots/auth)** \u2014 turnkey
1849
+ authentication (user/login/signup tables and endpoints). Install it with
1850
+ \`xanots marketplace install @xanots/auth\`, then register it in
1851
+ \`xano/index.ts\`. Authentication only \u2014 **not** authorization: it has no
1852
+ roles, permissions, or route guards. Build those with \`@xanots/sdk\` (a role
1853
+ column plus a \`s.precondition\` per endpoint).
1854
+ - More \`@xanots/*\` packages register onto the same workspace. This list
1855
+ does not update itself \u2014 run \`xanots marketplace list\` for the live
1856
+ catalogue, \`xanots marketplace search <words>\` to narrow it, and
1857
+ \`xanots marketplace details <package>\` to see what an add-on installs and
1858
+ how to register it. All three work before you log in.
1859
+
1860
+ None of these ship with the scaffold. Install one only when you need it \u2014 an
1861
+ add-on you never register is weight in \`package.json\` for nothing.
1862
+ `;
1863
+ }
1864
+ function renderXanoIndex({ appName }) {
1865
+ return `import { workspace } from "@xanots/sdk";
1866
+
1867
+ /**
1868
+ * The ${appName} backend.
1869
+ *
1870
+ * A workspace is assembled by registering typed objects onto a workspace()
1871
+ * instance and default-exporting it. This starter is intentionally empty and
1872
+ * already compiles + deploys \u2014 add your first table and endpoint below.
1873
+ *
1874
+ * \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
1875
+ *
1876
+ * import { workspace, table, apiGroup, query, f, input, s, ref, c, expect, resp } from "@xanots/sdk";
1877
+ *
1878
+ * const notes = table({
1879
+ * name: "notes",
1880
+ * // \`id\` (int PK) + \`created_at\` (epochms) are auto-injected.
1881
+ * schema: {
1882
+ * body: f.text({ required: true }),
1883
+ * },
1884
+ * });
1885
+ *
1886
+ * const api = apiGroup({ name: "notes", canonical: "notes" }); // pin the slug
1887
+ *
1888
+ * const createNote = query({
1889
+ * name: "create_note",
1890
+ * verb: "POST",
1891
+ * apiGroup: api,
1892
+ * input: { body: input.text({ required: true }) },
1893
+ * // ...build the stack with the s.* statement helpers...
1894
+ * // Assertions ride along with the object they cover; \`npm run xano:test\`
1895
+ * // runs them against whatever you last deployed.
1896
+ * tests: [
1897
+ * {
1898
+ * name: "creates a note",
1899
+ * input: { body: c.text("hello") },
1900
+ * expect: [expect.to_be_defined(resp())],
1901
+ * },
1902
+ * ],
1903
+ * });
1904
+ *
1905
+ * export default workspace("${appName}")
1906
+ * .registerTables([notes])
1907
+ * .registerApiGroups([api])
1908
+ * .registerQueries([createNote]);
1909
+ *
1910
+ * Discover the exact builders and options from the package's own types and its
1911
+ * shipped docs \u2014 read \`node_modules/@xanots/sdk/llms.txt\` first (it ends with a
1912
+ * map of the \`llms/*.md\` topic files), then the .d.ts files.
1913
+ * See \`xano/EXAMPLE.md\` for the full walkthrough.
1914
+ *
1915
+ * \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
1916
+ * Nothing below is installed. Reach for an add-on when you need it, not before.
1917
+ *
1918
+ * @xanots/auth registers turnkey auth (user/login/signup) onto this same
1919
+ * workspace. Install it first (\`xanots marketplace install @xanots/auth\`), then
1920
+ * \`registerAuth(workspace("${appName}"), { canonical: "authn" })\` returns the
1921
+ * instance to chain your own .register*() calls onto:
1922
+ *
1923
+ * registerAuth(workspace("${appName}"), { canonical: "authn" })
1924
+ * .registerTables([notes])
1925
+ * .registerApiGroups([api])
1926
+ * .registerQueries([createNote]);
1927
+ *
1928
+ * That is not the whole catalogue. \`xanots marketplace list\` prints every
1929
+ * published add-on and \`xanots marketplace details <package>\` prints what one
1930
+ * installs plus the registration to paste here \u2014 no login required.
1931
+ */
1932
+ export default workspace("${appName}");
1933
+ `;
1934
+ }
1935
+ function renderXanoExampleMd({ appName }) {
1936
+ return `# Building your ${appName} backend
1937
+
1938
+ The backend lives in [\`index.ts\`](index.ts) and is a single default-exported
1939
+ \`workspace()\`. You grow it by registering typed objects.
1940
+
1941
+ ## Learn the library from the library
1942
+
1943
+ Everything you need is in the package itself:
1944
+
1945
+ - \`node_modules/@xanots/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.
1946
+ - \`node_modules/@xanots/sdk/llms/*.md\` \u2014 one file per surface. Open the one or two whose condition matches the task; skip the rest.
1947
+ - The published TypeScript types and JSDoc (\`node_modules/@xanots/sdk/**/*.d.ts\`).
1948
+ - \`node_modules/@xanots/sdk/manifest.json\` \u2014 the exhaustive reference; grep or \`jq\` the one entry you need rather than reading it whole.
1949
+
1950
+ Author against those signatures \u2014 don't invent an API that isn't there.
1951
+
1952
+ ## The shape
1953
+
1954
+ \`\`\`
1955
+ xano/
1956
+ \u251C\u2500\u2500 index.ts default export: the workspace registering everything below
1957
+ \u251C\u2500\u2500 tables/<name>.ts a table (name, typed schema, indexes)
1958
+ \u251C\u2500\u2500 api/<group>.ts an API group; pin its canonical slug so paths are stable
1959
+ \u2514\u2500\u2500 api/<endpoint>.ts a query: name, verb, apiGroup, typed input, a stack, a response
1960
+ \`\`\`
1961
+
1962
+ ## Steps
1963
+
1964
+ 1. **Define a table** under \`tables/\` with \`table({ name, schema: { ... } })\`.
1965
+ \`id\` and \`created_at\` are auto-injected.
1966
+ 2. **Define an API group** with \`apiGroup({ name, canonical })\`. Pinning the
1967
+ canonical slug keeps the public path stable and lets \`getPath()\` resolve in
1968
+ the browser bundle without a lock file.
1969
+ 3. **Define endpoints** with \`query({ name, verb, apiGroup, input, ... })\`, building
1970
+ the logic from the \`s.*\` statement helpers and the expression/column/input/
1971
+ reference helpers.
1972
+ 4. **Register everything** in \`index.ts\`:
1973
+ \`\`\`ts
1974
+ export default workspace("${appName}")
1975
+ .registerTables([...])
1976
+ .registerApiGroups([...])
1977
+ .registerQueries([...]);
1978
+ \`\`\`
1979
+ 5. **Assert it works** \u2014 add a \`tests: [...]\` entry to a query or function
1980
+ (named inputs plus \`expect.*\` assertions on its response), or a
1981
+ \`workflowTest({ name, stack })\` when the behavior spans several objects.
1982
+ They live beside the code they cover and ship with it. See "Testing" below.
1983
+ 6. **Compile** with \`npm run xano:export\`, and **deploy** with
1984
+ \`npm run xano:deploy\` (after \`xanots login\`). The first of either writes
1985
+ \`xano/xano.lock\` \u2014 **commit it**. It pins every object's identity, so a later
1986
+ rename renames the object instead of deleting and recreating it. See
1987
+ "\`xano.lock\` \u2014 commit it" in the project README.
1988
+
1989
+ ## Testing
1990
+
1991
+ Two kinds of test are authored in \`xano/\` alongside the objects they cover, and
1992
+ both run against a DEPLOYED environment:
1993
+
1994
+ - **Unit test** \u2014 \`tests: [...]\` on a \`query\`, \`defineFunction\`, or
1995
+ \`middleware\`. Each entry is a named set of inputs run against that object, with
1996
+ \`expect.*\` assertions on its response. A statement's \`mock\` (keyed by test
1997
+ NAME) makes one step return a value instead of doing its work, but only while
1998
+ that test runs.
1999
+ - **Workflow test** \u2014 \`workflowTest({ name, stack })\`, a standalone object whose
2000
+ stack calls others (\`s.function.call\`, \`s.api.call\`) and asserts with
2001
+ \`s.expect.*\`. Use it for behavior that spans objects.
2002
+
2003
+ \`expect.*\` and \`s.expect.*\` are different builders \u2014 an assertion record versus
2004
+ a workflow-test statement \u2014 and are not interchangeable.
2005
+
2006
+ \`\`\`bash
2007
+ npm run xano:deploy # tests run against what is deployed, so deploy first
2008
+ npm run xano:test # runs both kinds; exits 5 if any fail
2009
+ \`\`\`
2010
+
2011
+ \`npx xanots deploy ./xano/index.ts --test\` does both in one step, and
2012
+ \`npx xanots test list\` shows what a deployed environment carries without running
2013
+ anything. Read \`node_modules/@xanots/sdk/llms/tests.md\` before authoring either
2014
+ kind.
2015
+
2016
+ ### Event-driven objects
2017
+
2018
+ A scheduled \`task\`, an \`mcpServer\`, and every trigger **fire normally on an
2019
+ ephemeral** \u2014 where \`deploy\` sends them \u2014 so test them by deploying and letting
2020
+ them run.
2021
+
2022
+ ## Wire the frontend
2023
+
2024
+ In [\`../frontend/src/lib/api.ts\`](../frontend/src/lib/api.ts), derive paths and
2025
+ types from your query defs (\`getPath()\`, \`InferInput\`, \`InferResponse\`) \u2014 never
2026
+ hand-type a URL or a request body.
2027
+
2028
+ Keep the client bundle lean (**split route metadata from stack-heavy authoring**):
2029
+
2030
+ - \`import type\` for shapes \u2014 \`InferInput\`/\`InferResponse\` erase to nothing.
2031
+ - Import the **one lean query def** for its \`getPath()\`/\`verb\`, never \`xano/index.ts\`
2032
+ (that pulls the whole workspace). A def's \`s.*\`/\`c.*\` stack calls run at module
2033
+ load to build it, so they can't be tree-shaken out of the bundle.
2034
+ - A def whose stack builds a heavy graph \u2014 an agent + its tools via \`s.ai.agent.run\`
2035
+ \u2014 drags that whole graph in. For those, don't import the def in the browser:
2036
+ declare its \`{ path, verb }\` as plain metadata (see the \`ROUTES\` example in
2037
+ \`api.ts\`) and verify it against the compiled bundle with
2038
+ \`npx xanots routes xano/index.ts\`.
2039
+ `;
2040
+ }
2041
+ function describeOrigin(o) {
2042
+ switch (o.source) {
2043
+ case "workspace":
2044
+ return `workspace ${o.origin}`;
2045
+ case "ephemeral":
2046
+ return `ephemeral "${o.origin}"`;
2047
+ case "file":
2048
+ return `the bundle at ${o.origin}`;
2049
+ }
2050
+ }
2051
+ function renderCodegenMarker({ sdkVersion }, origin, generatedAt, report) {
2052
+ return JSON.stringify(
2053
+ {
2054
+ source: origin.source,
2055
+ origin: origin.origin,
2056
+ sdkVersion,
2057
+ generatedAt,
2058
+ note: "Written by `xanots init --from`. Its presence lets a re-run refresh xano/ in place.",
2059
+ // The findings, so parity is trackable release over release and gateable
2060
+ // in CI without scraping stderr. Omitted rather than written empty when
2061
+ // the caller has none to record.
2062
+ ...report === void 0 ? {} : { report }
2063
+ },
2064
+ null,
2065
+ 2
2066
+ ) + "\n";
2067
+ }
2068
+ function renderCodegenReadme({ appName }, origin, envNames, preset) {
2069
+ const secrets = envNames.length === 0 ? "" : `
2070
+ ## Heads up: this tree contains your workspace env var values
2071
+
2072
+ The pull carried ${envNames.length} workspace env var${envNames.length === 1 ? "" : "s"} \u2014
2073
+ ${envNames.map((n) => `\`${n}\``).join(", ")} \u2014 **with their values**, inline in
2074
+ [\`xano/index.ts\`](xano/index.ts), because that is what a deploy has to send. If any of them
2075
+ is a secret, do not commit \`xano/\` as-is: add it to \`.gitignore\`, or replace the values
2076
+ before committing.
2077
+ `;
2078
+ return `# ${appName}
2079
+
2080
+ A [XanoTS](https://www.npmjs.com/package/@xanots/sdk) project pulled from
2081
+ ${describeOrigin(origin)}. The Xano backend lives in [\`xano/\`](xano/) as readable
2082
+ TypeScript; the ${preset.label} frontend under [\`frontend/\`](frontend/) is a starter \u2014
2083
+ the pull carries a backend, not a UI.
2084
+
2085
+ ## Deploy it
2086
+
2087
+ \`\`\`bash
2088
+ xanots login # once, to authenticate against your Xano account
2089
+ npm run xano:deploy # typecheck, build the frontend, ship both
2090
+ \`\`\`
2091
+
2092
+ ## Read this before deploying
2093
+
2094
+ - **\`xano/\` is disposable.** Re-running \`xanots init --from\` on this directory
2095
+ rewrites it wholesale \u2014 no merge, no diff, no preservation of hand edits. The rest of
2096
+ the project (this README, \`package.json\`, \`frontend/\`) is yours and is left alone.
2097
+ - **Deploying is a full replace.** The import path clears the target workspace and
2098
+ re-imports. Send this only to an **ephemeral** environment \u2014 never to a
2099
+ workspace holding data you care about. That is why \`npm run xano:deploy\` targets an
2100
+ ephemeral env and there is no deploy-to-your-real-workspace command.
2101
+ - **This is schema only.** Table rows are not carried, and neither are payload sections
2102
+ this SDK models no kind for. A deploy recreates the structure, not the data.
2103
+
2104
+ [\`xano/README.md\`](xano/README.md) is the authoritative record of what did and did not
2105
+ translate cleanly on this pull. Read it before trusting the tree.
2106
+ ${secrets}
2107
+ ## Working on it
2108
+
2109
+ \`\`\`bash
2110
+ npm run dev # run the starter frontend
2111
+ npm run typecheck # the whole project, both halves
2112
+ npm run xano:export # compile the backend to workspace.json (don't commit it)
2113
+ npx xanots routes xano/index.ts # list every endpoint's verb + path
2114
+ \`\`\`
2115
+
2116
+ [\`frontend/src/lib/api.ts\`](frontend/src/lib/api.ts) shows the one contract: derive
2117
+ request paths and types from the query defs in \`xano/\` rather than hand-typing a URL.
2118
+ `;
2119
+ }
2120
+ function renderReadmeThemingSection(choice) {
2121
+ const { theme, dark } = choice;
2122
+ const darkNote = {
2123
+ system: `Dark mode follows the OS. An inline script in the HTML entry sets
2124
+ \`class="dark"\` on \`<html>\` before first paint (so the page never flashes light
2125
+ first), and the \`.dark\` block in the stylesheet supplies the palette. To let
2126
+ people override it, add a control that toggles that class \u2014 or scaffold your
2127
+ next project with \`--dark toggle\`, which ships one.`,
2128
+ toggle: `Dark mode follows the OS until the user says otherwise. The pieces:
2129
+ an inline script in the HTML entry applies the mode before first paint,
2130
+ [\`frontend/src/lib/theme.ts\`](frontend/src/lib/theme.ts) holds and persists it,
2131
+ and the mode toggle on the landing page cycles system \u2192 light \u2192 dark.`,
2132
+ off: `This project was scaffolded with \`--dark off\`, so nothing switches
2133
+ themes. The \`.dark\` block in the stylesheet is still there and still complete \u2014
2134
+ add \`class="dark"\` to \`<html>\` to see it, and wire that to a control (or the OS
2135
+ setting) to turn it on for real.`
2136
+ }[dark];
2137
+ return `## Theming
2138
+
2139
+ Scaffolded with the **${theme.label}** theme. Every color in the app comes from
2140
+ the semantic tokens at the top of
2141
+ [\`frontend/src/index.css\`](frontend/src/index.css) \u2014 \`--primary\`,
2142
+ \`--muted-foreground\`, \`--border\`, the \`--chart-*\` ramp, the \`--sidebar-*\` set \u2014
2143
+ and every shadcn component reads those names, so editing one value rebrands
2144
+ everything that uses it. Style with the token classes (\`bg-primary\`,
2145
+ \`text-muted-foreground\`) rather than raw palette classes like \`bg-gray-100\`, or
2146
+ the theme stops being one.
2147
+
2148
+ Tailwind v4 has no \`tailwind.config.js\`; that stylesheet *is* the config.
2149
+
2150
+ To swap the whole palette later:
2151
+
2152
+ \`\`\`bash
2153
+ npx shadcn@latest add https://ui.shadcn.com/r/themes/stone.json # or any registry theme
2154
+ \`\`\`
2155
+
2156
+ ${darkNote}`;
2157
+ }
2158
+ var UI_DOCS_CTA = {
2159
+ label: "Browse UI components",
2160
+ href: "https://ui.shadcn.com/docs/components"
2161
+ };
2162
+ function initLanding({ appName }) {
2163
+ return {
2164
+ title: appName,
2165
+ lead: [
2166
+ { text: "Your XanoTS project is ready. The backend lives in " },
2167
+ { code: "xano/" },
2168
+ { text: " and this frontend in " },
2169
+ { code: "frontend/" },
2170
+ { text: "." }
2171
+ ],
2172
+ steps: [
2173
+ [
2174
+ { text: "Author your first table + endpoint in " },
2175
+ { code: "xano/index.ts" },
2176
+ { text: " (see " },
2177
+ { code: "xano/EXAMPLE.md" },
2178
+ { text: ")." }
2179
+ ],
2180
+ [{ text: "Wire it into the UI from " }, { code: "frontend/src/lib/api.ts" }, { text: "." }],
2181
+ [{ text: "Ship it: " }, { code: "npm run xano:deploy" }, { text: "." }]
2182
+ ],
2183
+ cta: UI_DOCS_CTA
2184
+ };
2185
+ }
2186
+ function codegenLanding({ appName }, origin) {
2187
+ return {
2188
+ title: appName,
2189
+ lead: [
2190
+ { text: `This project was pulled from ${describeOrigin(origin)}. The backend lives in ` },
2191
+ { code: "xano/" },
2192
+ { text: " as readable TypeScript; this frontend is a starter." }
2193
+ ],
2194
+ steps: [
2195
+ [
2196
+ { text: "Read " },
2197
+ { code: "xano/README.md" },
2198
+ { text: " \u2014 what did and did not translate cleanly on the pull." }
2199
+ ],
2200
+ [
2201
+ { text: "List the endpoints: " },
2202
+ { code: "npx xanots routes xano/index.ts" },
2203
+ { text: ", then wire them up in " },
2204
+ { code: "frontend/src/lib/api.ts" },
2205
+ { text: "." }
2206
+ ],
2207
+ [
2208
+ { text: "Ship it: " },
2209
+ { code: "npm run xano:deploy" },
2210
+ { text: " (a full replace of an ephemeral env)." }
2211
+ ]
2212
+ ],
2213
+ cta: UI_DOCS_CTA
2214
+ };
2215
+ }
2216
+ function renderIndexCss(choice = defaultThemeChoice()) {
2217
+ const { theme } = choice;
2218
+ const colorMap = colorTokens(theme.light).map((token) => ` --color-${token}: var(--${token});`).join("\n");
2219
+ const block = (vars) => orderedTokens(vars).map(([name, value]) => ` --${name}: ${value};`).join("\n");
2220
+ const faces = fontImports(choice.fonts);
2221
+ const fontVars = fontThemeVars(choice.fonts).map(([name, value]) => ` ${name}: ${value};`).join("\n");
2222
+ const headingRule = choice.fonts?.heading === void 0 ? "" : `
2223
+ h1,
2224
+ h2,
2225
+ h3,
2226
+ h4,
2227
+ h5,
2228
+ h6 {
2229
+ font-family: var(--font-heading);
2230
+ }`;
2231
+ return `${faces.length === 0 ? "" : faces.join("\n") + "\n"}@import "tailwindcss";
2232
+ @import "tw-animate-css";
2233
+
2234
+ @custom-variant dark (&:is(.dark *));
2235
+
2236
+ /* Map the tokens below into Tailwind's theme so \`bg-primary\` and friends
2237
+ resolve. Tailwind v4 does this in CSS; there is no tailwind.config.js. */
2238
+ @theme inline {
2239
+ ${colorMap}
2240
+ --radius-sm: calc(var(--radius) - 4px);
2241
+ --radius-md: calc(var(--radius) - 2px);
2242
+ --radius-lg: var(--radius);
2243
+ --radius-xl: calc(var(--radius) + 4px);${fontVars === "" ? "" : `
2244
+ ${fontVars}`}
2245
+ }
2246
+
2247
+ /* Theme: ${theme.label}. Rebrand here \u2014 these are the only colors the
2248
+ components know about. Swap the whole palette for another shadcn theme with
2249
+ \`npx shadcn@latest add <registry-theme-url>\`, or scaffold a different one
2250
+ next time with \`xanots init --theme <base>-<accent>\`. */
2251
+ :root {
2252
+ ${block(theme.light)}
2253
+ }
2254
+
2255
+ ${darkBlockComment(choice.dark)}
2256
+ .dark {
2257
+ ${block(theme.dark)}
2258
+ }
2259
+
2260
+ @layer base {
2261
+ * {
2262
+ @apply border-border outline-ring/50;
2263
+ }
2264
+ body {
2265
+ @apply bg-background text-foreground;
2266
+ }${headingRule}
2267
+ }
2268
+ `;
2269
+ }
2270
+ function darkBlockComment(dark) {
2271
+ switch (dark) {
2272
+ case "system":
2273
+ return `/* Applied by the inline script in index.html, which follows the OS setting.
2274
+ Add a control that toggles this class to let the user override it. */`;
2275
+ case "toggle":
2276
+ return `/* Applied by frontend/src/lib/theme.ts \u2014 the OS setting by default, and the
2277
+ user's choice once they use the mode toggle. */`;
2278
+ case "off":
2279
+ return `/* Applied by adding \`class="dark"\` to <html> \u2014 wire that to a toggle if you
2280
+ want one; this scaffold was created with --dark off, so nothing does yet. */`;
2281
+ }
2282
+ }
2283
+ function renderThemeScript(dark) {
2284
+ if (dark === "off") return "";
2285
+ const resolve = dark === "toggle" ? `var stored = localStorage.getItem("theme");
2286
+ var dark = stored === "dark" || (stored !== "light" && media.matches);` : `var dark = media.matches;`;
2287
+ return ` <script>
2288
+ // Sets the \`dark\` class before first paint, so the page never flashes
2289
+ // light before switching. Keep this inline and above the stylesheet.
2290
+ (function () {
2291
+ try {
2292
+ var media = window.matchMedia("(prefers-color-scheme: dark)");
2293
+ ${resolve}
2294
+ document.documentElement.classList.toggle("dark", dark);
2295
+ } catch (e) {}
2296
+ })();
2297
+ </script>
2298
+ `;
2299
+ }
2300
+ function renderThemeModule() {
2301
+ return `/**
2302
+ * Color mode, persisted. The inline script in index.html applies the stored
2303
+ * value before first paint; this module is what changes it afterwards.
2304
+ *
2305
+ * "system" means no stored preference \u2014 the OS decides, and keeps deciding if
2306
+ * the user changes it while the page is open.
2307
+ */
2308
+ export type Mode = "light" | "dark" | "system";
2309
+
2310
+ const KEY = "theme";
2311
+
2312
+ const prefersDark = () =>
2313
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
2314
+
2315
+ /** The stored preference, or "system" when there is none. */
2316
+ export function getMode(): Mode {
2317
+ const stored = localStorage.getItem(KEY);
2318
+ return stored === "light" || stored === "dark" ? stored : "system";
2319
+ }
2320
+
2321
+ /** Whether \`mode\` renders dark right now. */
2322
+ export function isDark(mode: Mode): boolean {
2323
+ return mode === "dark" || (mode === "system" && prefersDark());
2324
+ }
2325
+
2326
+ /** Persist a mode and apply it. "system" clears the stored override. */
2327
+ export function setMode(mode: Mode): void {
2328
+ if (mode === "system") localStorage.removeItem(KEY);
2329
+ else localStorage.setItem(KEY, mode);
2330
+ document.documentElement.classList.toggle("dark", isDark(mode));
2331
+ }
2332
+
2333
+ /**
2334
+ * Re-apply on OS changes, and on changes made in another tab. Returns an
2335
+ * unsubscribe function \u2014 call it from your framework's cleanup hook.
2336
+ *
2337
+ * The OS listener re-reads the mode rather than closing over it, so it stays
2338
+ * correct after the user picks an explicit light/dark and then goes back to
2339
+ * system.
2340
+ */
2341
+ export function watchMode(onChange: (mode: Mode) => void): () => void {
2342
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
2343
+ const apply = () => {
2344
+ const mode = getMode();
2345
+ document.documentElement.classList.toggle("dark", isDark(mode));
2346
+ onChange(mode);
2347
+ };
2348
+ media.addEventListener("change", apply);
2349
+ window.addEventListener("storage", apply);
2350
+ return () => {
2351
+ media.removeEventListener("change", apply);
2352
+ window.removeEventListener("storage", apply);
2353
+ };
2354
+ }
2355
+ `;
2356
+ }
2357
+ function renderCnUtil(extra = "") {
2358
+ return `import { clsx, type ClassValue } from "clsx";
2359
+ import { twMerge } from "tailwind-merge";
2360
+
2361
+ export function cn(...inputs: ClassValue[]) {
2362
+ return twMerge(clsx(inputs));
2363
+ }
2364
+ ${extra}`;
2365
+ }
2366
+ function renderApiTs() {
2367
+ return `// The one contract: derive paths and request/response *types* from your xanots
2368
+ // query defs. Never hand-type a URL or a request body \u2014 change a def and
2369
+ // everything here follows.
2370
+ //
2371
+ // Keep the client bundle lean (the split-route-metadata rule):
2372
+ // \u2022 \`import type\` for shapes \u2014 InferInput/InferResponse erase to nothing.
2373
+ // \u2022 Import the ONE lean query def module for its getPath()/verb \u2014 never
2374
+ // ../../../xano/index.js (that pulls the whole workspace) and never a def
2375
+ // whose stack builds a heavy graph (an agent + its tools via s.ai.agent.run):
2376
+ // those s.*/c.* factory calls run at module load and can't be tree-shaken out.
2377
+ // \u2022 For such a stack-heavy endpoint, don't import its def in the browser at all \u2014
2378
+ // declare its { path, verb } in the ROUTES table below and verify it against
2379
+ // the compiled bundle with \`npx xanots routes xano/index.ts\`.
2380
+ //
2381
+ // This starter has no endpoints yet. Once you add one in xano/, wire it like:
2382
+ //
2383
+ // // Types are free \u2014 always import them type-only.
2384
+ // import type { InferInput, InferResponse } from "@xanots/sdk";
2385
+ // import type { createNoteQuery } from "../../../xano/api/create-note.js";
2386
+ //
2387
+ // // Runtime path/verb: import the lean def value, OR (for a stack-heavy def)
2388
+ // // read it from ROUTES so the def never enters the bundle.
2389
+ // import { createNoteQuery } from "../../../xano/api/create-note.js";
2390
+ //
2391
+ // export type CreateNoteBody = InferInput<typeof createNoteQuery>;
2392
+ // export type Note = InferResponse<typeof createNoteQuery>;
2393
+ //
2394
+ // export async function createNote(body: CreateNoteBody): Promise<Note> {
2395
+ // const res = await fetch(XANO_HOST + createNoteQuery.getPath(), {
2396
+ // method: createNoteQuery.verb,
2397
+ // headers: { "content-type": "application/json" },
2398
+ // body: JSON.stringify(body),
2399
+ // });
2400
+ // if (!res.ok) throw new Error(await res.text());
2401
+ // return res.json();
2402
+ // }
2403
+ //
2404
+ // The stack-heavy escape hatch \u2014 plain metadata, no def import, no bundle cost.
2405
+ // Keep it in sync with \`npx xanots routes xano/index.ts\` (it prints verb + path):
2406
+ //
2407
+ // export const ROUTES = {
2408
+ // triageRequest: { path: "/api:notes/triage_request", verb: "POST" },
2409
+ // } as const;
2410
+
2411
+ /**
2412
+ * The deployed Xano backend's base URL. Injected as \`window.XANO_HOST\` by
2413
+ * \`xanots deploy <entry> --static <dir>\`, or read from \`VITE_XANO_HOST\` in dev.
2414
+ * Empty string when neither is set (the UI runs with no backend).
2415
+ */
2416
+ export const XANO_HOST: string =
2417
+ (typeof window !== "undefined" && (window as { XANO_HOST?: string }).XANO_HOST) ||
2418
+ import.meta.env.VITE_XANO_HOST ||
2419
+ "";
2420
+ `;
2421
+ }
2422
+
1574
2423
  // src/emit/frontend-react.ts
1575
2424
  var CODE_CLASS = "bg-muted rounded px-1.5 py-0.5 font-mono text-sm";
1576
2425
  function jsxText(s) {
@@ -1997,6 +2846,834 @@ Colors come from the theme tokens in
1997
2846
  }
1998
2847
  };
1999
2848
 
2849
+ // src/emit/frontend-svelte.ts
2850
+ var CODE_CLASS2 = "bg-muted rounded px-1.5 py-0.5 font-mono text-sm";
2851
+ function htmlText(s) {
2852
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2853
+ }
2854
+ function svelteText(s) {
2855
+ return htmlText(s).replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
2856
+ }
2857
+ function renderNode2(node) {
2858
+ return "code" in node ? `<code class="${CODE_CLASS2}">${svelteText(node.code)}</code>` : svelteText(node.text);
2859
+ }
2860
+ function renderNodes2(nodes) {
2861
+ return nodes.map(renderNode2).join("");
2862
+ }
2863
+ function renderApp2(landing, choice) {
2864
+ const steps = landing.steps.map((step) => ` <li>${renderNodes2(step)}</li>`).join("\n");
2865
+ const toggle = choice.dark === "toggle";
2866
+ const icons = iconLibraryOf(choice.icons).svelte;
2867
+ return `<script lang="ts">
2868
+ ${icons.arrowImport}
2869
+
2870
+ import { Button } from "$lib/components/ui/button/index.js";
2871
+ import * as Card from "$lib/components/ui/card/index.js";${toggle ? `
2872
+ import ModeToggle from "$lib/components/mode-toggle.svelte";` : ""}
2873
+ </script>
2874
+
2875
+ <main class="mx-auto flex min-h-screen max-w-2xl flex-col justify-center p-8">${toggle ? `
2876
+ <div class="mb-4 flex justify-end">
2877
+ <ModeToggle />
2878
+ </div>` : ""}
2879
+ <Card.Root>
2880
+ <Card.Header>
2881
+ <Card.Title class="text-3xl tracking-tight">${svelteText(landing.title)}</Card.Title>
2882
+ <Card.Description class="text-base">
2883
+ ${renderNodes2(landing.lead)}
2884
+ </Card.Description>
2885
+ </Card.Header>
2886
+ <Card.Content>
2887
+ <ol class="text-muted-foreground list-inside list-decimal space-y-2">
2888
+ ${steps}
2889
+ </ol>
2890
+ </Card.Content>
2891
+ <Card.Footer>
2892
+ <!-- Components come from shadcn-svelte \u2014 add more with
2893
+ \`npx shadcn-svelte@latest add <name>\`. -->
2894
+ <Button href="${landing.cta.href}" target="_blank" rel="noreferrer">
2895
+ ${svelteText(landing.cta.label)}
2896
+ <${icons.arrowComponent} />
2897
+ </Button>
2898
+ </Card.Footer>
2899
+ </Card.Root>
2900
+ </main>
2901
+ `;
2902
+ }
2903
+ function renderSvelteKitPlugin() {
2904
+ return `sveltekit({
2905
+ // RUNES MODE, FORCED \u2014 not left to Svelte 5's per-component detection.
2906
+ //
2907
+ // By default a component that uses \`export let\` and \`<slot>\` simply
2908
+ // compiles in legacy mode and works, so the Svelte 4 API an assistant
2909
+ // reaches for from older training data produces no error at all. Pinning
2910
+ // runes here turns that into a compile failure naming the file, which is
2911
+ // the same guidance the agent brief gives, enforced instead of stated.
2912
+ //
2913
+ // \`node_modules\` is exempted (returning undefined restores detection):
2914
+ // an installed library compiles under whatever mode ITS author wrote for,
2915
+ // and forcing ours onto it would break dependencies this project does not
2916
+ // own. Removable in Svelte 6, where runes stop being opt-in.
2917
+ compilerOptions: {
2918
+ runes: ({ filename }) =>
2919
+ filename.split(/[/\\\\]/).includes("node_modules") ? undefined : true,
2920
+ },
2921
+ // No \`preprocess\`. vite-plugin-svelte 7 compiles \`<script lang="ts">\`
2922
+ // itself, so \`vitePreprocess()\` is the no-op it looks like \u2014 \`sv create\`
2923
+ // emits none either. Verified against a real build: components typecheck
2924
+ // and compile without it.
2925
+ //
2926
+ // The deploy target is a static host with NO server runtime \u2014 Xano is the
2927
+ // backend. Routes are PRERENDERED (see frontend/src/routes/+layout.ts):
2928
+ // each becomes its own HTML file at build time, and the host resolves a URL
2929
+ // to it by trying the exact key, then \`{path}.html\`, then
2930
+ // \`{path}/index.html\`. That is why no \`trailingSlash\` is configured \u2014 both
2931
+ // output shapes resolve, so this stays on SvelteKit's default.
2932
+ //
2933
+ // NO \`fallback\`. It looks harmless \u2014 it used to be how every URL got served
2934
+ // \u2014 but adapter-static writes the fallback shell AT index.html, overwriting
2935
+ // the prerendered home page with an empty document. Measured on a real
2936
+ // build: 1936 bytes of empty shell instead of 4920 bytes of rendered page,
2937
+ // on the most-visited route in the app.
2938
+ //
2939
+ // Dropping it also gets the better failure. With no fallback, adapter-static
2940
+ // is \`strict\` by default, so a route it cannot prerender (a dynamic \`[id]\`
2941
+ // with no \`entries\`) FAILS THE BUILD with a message naming the route \u2014
2942
+ // instead of shipping and 404ing for real users.
2943
+ //
2944
+ // \`xanots deploy --static-env\` still finds its anchor: every prerendered
2945
+ // route renders from app.html, so each document has a <head> and each one
2946
+ // gets the injected globals.
2947
+ adapter: adapter({
2948
+ pages: "frontend/dist",
2949
+ assets: "frontend/dist",
2950
+ precompress: false,
2951
+ }),
2952
+ // Absolute asset URLs, pinned rather than left to the default. Prerendered
2953
+ // pages live at varying URL depths, and a relative \`../_app/\u2026\` computed for
2954
+ // one depth 404s at another.
2955
+ paths: { relative: false },
2956
+ prerender: {
2957
+ // A hash link in the shared layout \u2014 \`<a href="#services">\`, the default
2958
+ // shape of a one-page site's nav \u2014 renders into EVERY prerendered route,
2959
+ // including the scaffold's own /404. There it resolves to /404#services,
2960
+ // an anchor that by definition is not on that page, and SvelteKit's
2961
+ // default handler THROWS: the vite build succeeds and the prerender pass
2962
+ // after it kills the build, with a stack trace naming only files inside
2963
+ // node_modules and a route the author never wrote.
2964
+ //
2965
+ // So ignore a missing id on /404 specifically, rather than reaching for a
2966
+ // blanket \`handleMissingId: "warn"\`. A hash link that is genuinely broken
2967
+ // on a real route still fails the build, which is the check worth keeping.
2968
+ handleMissingId: ({ path, message }) => {
2969
+ if (path === "/404") return;
2970
+ throw new Error(message);
2971
+ },
2972
+ },
2973
+ // SvelteKit's project layout, redirected under frontend/ so this stays a
2974
+ // single-root project: one package.json, one tsconfig.json, and xano/
2975
+ // sitting alongside as the backend half. NOTE these are what make
2976
+ // frontend/src/routes the routes directory \u2014 a file added under a
2977
+ // top-level src/routes/ is not a route and will not be served.
2978
+ files: {
2979
+ routes: "frontend/src/routes",
2980
+ lib: "frontend/src/lib",
2981
+ appTemplate: "frontend/src/app.html",
2982
+ assets: "frontend/static",
2983
+ },
2984
+ })`;
2985
+ }
2986
+ function renderAppHtml({ appName }, choice) {
2987
+ return `<!doctype html>
2988
+ <html lang="en">
2989
+ <head>
2990
+ <meta charset="utf-8" />
2991
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2992
+ <title>${htmlText(appName)}</title>
2993
+ ${renderThemeScript(choice.dark)}
2994
+ <!-- Drop a favicon.png into frontend/static/ and link it here:
2995
+ <link rel="icon" href="%sveltekit.assets%/favicon.png" />
2996
+ Everything in that directory is copied to the site root verbatim. -->
2997
+ <!-- Keep this inside <head>: \`xanots deploy --static-env\` injects the
2998
+ backend URL here as a window global that frontend/src/lib/api.ts reads. -->
2999
+ %sveltekit.head%
3000
+ </head>
3001
+ <body data-sveltekit-preload-data="hover">
3002
+ <div style="display: contents">%sveltekit.body%</div>
3003
+ </body>
3004
+ </html>
3005
+ `;
3006
+ }
3007
+ function renderModeToggleSvelte(icons) {
3008
+ return `<script lang="ts">
3009
+ ${icons.modeImports.join("\n ")}
3010
+
3011
+ import { Button } from "$lib/components/ui/button/index.js";
3012
+ import { getMode, setMode, watchMode, type Mode } from "$lib/theme.js";
3013
+
3014
+ /** system \u2192 light \u2192 dark \u2192 system. */
3015
+ const next: Record<Mode, Mode> = { system: "light", light: "dark", dark: "system" };
3016
+
3017
+ const icons = {
3018
+ system: ${icons.modeComponents.system},
3019
+ light: ${icons.modeComponents.light},
3020
+ dark: ${icons.modeComponents.dark},
3021
+ };
3022
+
3023
+ // Starts at "system" so the prerendered HTML is deterministic; the effect
3024
+ // corrects it in the browser, after the inline script has already applied
3025
+ // the right class.
3026
+ let mode = $state<Mode>("system");
3027
+
3028
+ $effect(() => {
3029
+ mode = getMode();
3030
+ return watchMode((m) => (mode = m));
3031
+ });
3032
+
3033
+ function change() {
3034
+ const target = next[mode];
3035
+ setMode(target);
3036
+ mode = target;
3037
+ }
3038
+
3039
+ const Icon = $derived(icons[mode]);
3040
+ </script>
3041
+
3042
+ <Button variant="ghost" size="icon" onclick={change} aria-label="Switch to {next[mode]} theme">
3043
+ <Icon />
3044
+ </Button>
3045
+ `;
3046
+ }
3047
+ function renderAppDts() {
3048
+ return `// See https://svelte.dev/docs/kit/types#app.d.ts
3049
+ declare global {
3050
+ namespace App {
3051
+ // interface Error {}
3052
+ // interface Locals {}
3053
+ // interface PageData {}
3054
+ // interface PageState {}
3055
+ // interface Platform {}
3056
+ }
3057
+ }
3058
+
3059
+ export {};
3060
+ `;
3061
+ }
3062
+ function renderLayoutTs() {
3063
+ return `// There is no server. The backend is Xano, reached through $lib/api.ts,
3064
+ // and \`npm run xano:deploy\` ships this app to a static host with no runtime.
3065
+ //
3066
+ // prerender: true \u2014 every route is rendered to its own HTML file AT BUILD TIME
3067
+ // and served as a real document. That is what makes each page load as itself
3068
+ // rather than as an empty shell that fills in afterwards.
3069
+ //
3070
+ // Prerendering IS server-rendering, just at build time rather than per request.
3071
+ // So module-scope \`window\`/\`document\` access now fails the BUILD instead of the
3072
+ // browser \u2014 which is the better place to find out. Reach for browser globals
3073
+ // inside onMount, or guard with \`import { browser } from "$app/environment"\`.
3074
+ //
3075
+ // A route with dynamic segments (e.g. /posts/[id]) cannot be prerendered unless
3076
+ // the build knows which ones exist, so THE BUILD FAILS and names the route.
3077
+ // That is deliberate \u2014 better than shipping a page that 404s for real users.
3078
+ // Give it entries, in that route's +page.ts:
3079
+ //
3080
+ // export const entries = () => [{ id: "1" }, { id: "2" }];
3081
+ //
3082
+ // Inherited by every route below this one.
3083
+ export const prerender = true;
3084
+ `;
3085
+ }
3086
+ function renderErrorState() {
3087
+ return `<script lang="ts">
3088
+ // Shared by the prerendered /404 route and +error.svelte.
3089
+ let { status = 404, message = "" }: { status?: number; message?: string } = $props();
3090
+ </script>
3091
+
3092
+ <main class="grid min-h-screen place-items-center p-8">
3093
+ <div class="max-w-md space-y-4 text-center">
3094
+ <p class="text-6xl font-semibold tracking-tight">{status}</p>
3095
+ <h1 class="text-xl font-medium">
3096
+ {status === 404 ? "We can't find that page." : "Something went wrong."}
3097
+ </h1>
3098
+ <p class="text-muted-foreground text-sm">
3099
+ {message !== "" ? message : "The link may be broken, or the page may have moved."}
3100
+ </p>
3101
+ <a
3102
+ href="/"
3103
+ 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"
3104
+ >
3105
+ Back to safety
3106
+ </a>
3107
+ </div>
3108
+ </main>
3109
+ `;
3110
+ }
3111
+ function renderNotFoundRoute() {
3112
+ return `<script lang="ts">
3113
+ // Prerendered to 404.html, which is what tells the static host to answer an
3114
+ // unmatched path with a real 404 instead of quietly serving the app shell
3115
+ // with a 200 (a missing asset would then reach the browser as HTML, which it
3116
+ // reports as "Unexpected token '<'").
3117
+ //
3118
+ // It must be a route, not +error.svelte: SvelteKit never prerenders the error
3119
+ // component to a file, so relying on it would ship no 404.html at all.
3120
+ //
3121
+ // Delete this directory and an unmatched path serves the home page with a 200
3122
+ // instead \u2014 which is worse, not better, so keep it unless you have a reason.
3123
+ import ErrorState from "$lib/components/error-state.svelte";
3124
+ </script>
3125
+
3126
+ <svelte:head>
3127
+ <title>Page not found</title>
3128
+ </svelte:head>
3129
+
3130
+ <ErrorState status={404} />
3131
+ `;
3132
+ }
3133
+ function renderErrorSvelte() {
3134
+ return `<script lang="ts">
3135
+ // Rendered when a client-side navigation or load fails inside the running
3136
+ // app. The static host never sees these \u2014 for a path that matched no
3137
+ // document at all, it serves the prerendered routes/404 page instead.
3138
+ import { page } from "$app/state";
3139
+ import ErrorState from "$lib/components/error-state.svelte";
3140
+ </script>
3141
+
3142
+ <svelte:head>
3143
+ <title>{page.status === 404 ? "Page not found" : "Something went wrong"}</title>
3144
+ </svelte:head>
3145
+
3146
+ <ErrorState status={page.status} message={page.error?.message ?? ""} />
3147
+ `;
3148
+ }
3149
+ function renderLayoutSvelte() {
3150
+ return `<script lang="ts">
3151
+ // The shared token stylesheet \u2014 semantic colors (bg-primary,
3152
+ // text-muted-foreground) plus the Tailwind v4 theme. Imported once here so
3153
+ // every route gets it.
3154
+ import "../index.css";
3155
+
3156
+ let { children } = $props();
3157
+ </script>
3158
+
3159
+ {@render children()}
3160
+ `;
3161
+ }
3162
+ function renderComponentsJson2(choice) {
3163
+ const config = {
3164
+ $schema: "https://shadcn-svelte.com/schema.json",
3165
+ tailwind: {
3166
+ // v4 has no config file; the theme lives in the stylesheet — the same
3167
+ // shared one the React preset points at.
3168
+ css: "frontend/src/index.css",
3169
+ // Tracks the theme the stylesheet was rendered from — see the React
3170
+ // preset's note; the shadcn-svelte CLI reads it the same way.
3171
+ baseColor: choice.theme.baseColor ?? "neutral"
3172
+ },
3173
+ aliases: {
3174
+ components: "$lib/components",
3175
+ utils: "$lib/utils",
3176
+ ui: "$lib/components/ui",
3177
+ hooks: "$lib/hooks",
3178
+ lib: "$lib"
3179
+ },
3180
+ typescript: true,
3181
+ registry: "https://shadcn-svelte.com/registry"
3182
+ };
3183
+ return JSON.stringify(config, null, 2) + "\n";
3184
+ }
3185
+ function renderButtonSvelte() {
3186
+ return `<script lang="ts" module>
3187
+ import { type VariantProps, tv } from "tailwind-variants";
3188
+
3189
+ export const buttonVariants = tv({
3190
+ 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",
3191
+ variants: {
3192
+ variant: {
3193
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
3194
+ destructive:
3195
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
3196
+ outline:
3197
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
3198
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
3199
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
3200
+ link: "text-primary underline-offset-4 hover:underline",
3201
+ },
3202
+ size: {
3203
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
3204
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
3205
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
3206
+ icon: "size-9",
3207
+ },
3208
+ },
3209
+ defaultVariants: {
3210
+ variant: "default",
3211
+ size: "default",
3212
+ },
3213
+ });
3214
+
3215
+ export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
3216
+ export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
3217
+
3218
+ export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
3219
+ WithElementRef<HTMLAnchorAttributes> & {
3220
+ variant?: ButtonVariant;
3221
+ size?: ButtonSize;
3222
+ };
3223
+ </script>
3224
+
3225
+ <script lang="ts">
3226
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
3227
+ import type { WithElementRef } from "bits-ui";
3228
+ import { cn } from "$lib/utils.js";
3229
+
3230
+ let {
3231
+ class: className,
3232
+ variant = "default",
3233
+ size = "default",
3234
+ ref = $bindable(null),
3235
+ href = undefined,
3236
+ type = "button",
3237
+ children,
3238
+ ...restProps
3239
+ }: ButtonProps = $props();
3240
+ </script>
3241
+
3242
+ {#if href}
3243
+ <a
3244
+ bind:this={ref}
3245
+ data-slot="button"
3246
+ class={cn(buttonVariants({ variant, size }), className)}
3247
+ {href}
3248
+ {...restProps}
3249
+ >
3250
+ {@render children?.()}
3251
+ </a>
3252
+ {:else}
3253
+ <button
3254
+ bind:this={ref}
3255
+ data-slot="button"
3256
+ class={cn(buttonVariants({ variant, size }), className)}
3257
+ {type}
3258
+ {...restProps}
3259
+ >
3260
+ {@render children?.()}
3261
+ </button>
3262
+ {/if}
3263
+ `;
3264
+ }
3265
+ function renderButtonIndex() {
3266
+ return `export {
3267
+ default as Button,
3268
+ buttonVariants,
3269
+ type ButtonProps,
3270
+ type ButtonSize,
3271
+ type ButtonVariant,
3272
+ } from "./button.svelte";
3273
+ `;
3274
+ }
3275
+ function cardPart(slot, tag, classes) {
3276
+ return `<script lang="ts">
3277
+ import type { HTMLAttributes } from "svelte/elements";
3278
+ import type { WithElementRef } from "bits-ui";
3279
+ import { cn } from "$lib/utils.js";
3280
+
3281
+ let {
3282
+ class: className,
3283
+ ref = $bindable(null),
3284
+ children,
3285
+ ...restProps
3286
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
3287
+ </script>
3288
+
3289
+ <${tag}
3290
+ bind:this={ref}
3291
+ data-slot="${slot}"
3292
+ class={cn("${classes}", className)}
3293
+ {...restProps}
3294
+ >
3295
+ {@render children?.()}
3296
+ </${tag}>
3297
+ `;
3298
+ }
3299
+ function renderCardIndex() {
3300
+ return `import Root from "./card.svelte";
3301
+ import Content from "./card-content.svelte";
3302
+ import Description from "./card-description.svelte";
3303
+ import Footer from "./card-footer.svelte";
3304
+ import Header from "./card-header.svelte";
3305
+ import Title from "./card-title.svelte";
3306
+
3307
+ export {
3308
+ Root,
3309
+ Content,
3310
+ Description,
3311
+ Footer,
3312
+ Header,
3313
+ Title,
3314
+ //
3315
+ Root as Card,
3316
+ Content as CardContent,
3317
+ Description as CardDescription,
3318
+ Footer as CardFooter,
3319
+ Header as CardHeader,
3320
+ Title as CardTitle,
3321
+ };
3322
+ `;
3323
+ }
3324
+ var sveltePreset = {
3325
+ id: "svelte",
3326
+ label: "SvelteKit",
3327
+ dependencies: {
3328
+ // shadcn-svelte's runtime surface. `bits-ui` is the headless primitive
3329
+ // library its components are built on (the Radix equivalent), and
3330
+ // `tailwind-variants` types the variants (the cva equivalent). clsx +
3331
+ // tailwind-merge back `cn()` exactly as in the React preset. Pre-installed
3332
+ // so `npx shadcn-svelte@latest add <component>` works without a separate
3333
+ // npm i.
3334
+ //
3335
+ // The icon package is `--icons`, merged in by renderPackageJson from
3336
+ // `iconBinding` below — see the React preset's note.
3337
+ "bits-ui": "^2.18.1",
3338
+ clsx: "^2.1.1",
3339
+ "tailwind-merge": "^3.0.1",
3340
+ "tailwind-variants": "^3.3.1"
3341
+ },
3342
+ devDependencies: {
3343
+ // Writes the static bundle `deploy --static` ships. Peer-depends on kit ^2.
3344
+ "@sveltejs/adapter-static": "^3.0.10",
3345
+ // 2.70 peer-accepts vite ^8 and vite-plugin-svelte ^7 — the versions
3346
+ // pinned below. Bump this line and those two together.
3347
+ "@sveltejs/kit": "^2.70.2",
3348
+ // 7.x, matching what `sv create` installs. It peer-requires vite ^8, so
3349
+ // this line and the `vite` pin move together or not at all — and the React
3350
+ // preset's plugin has the same constraint, which is why the two presets
3351
+ // share one Vite major rather than each choosing.
3352
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
3353
+ "@tailwindcss/vite": "^4.1.5",
3354
+ "@types/node": "^20.19.43",
3355
+ svelte: "^5.56.9",
3356
+ "svelte-check": "^4.7.6",
3357
+ tailwindcss: "^4.1.5",
3358
+ "tw-animate-css": "^1.2.4",
3359
+ tsx: "^4.19.2",
3360
+ // 6.x, the major `sv create` pins. svelte-check peer-accepts `^5 || ^6`;
3361
+ // 7 is out but not yet in that range, so this tracks the canonical
3362
+ // scaffold rather than latest.
3363
+ typescript: "^6.0.3",
3364
+ vite: "^8.0.16"
3365
+ },
3366
+ // svelte-check REPLACES `tsc --noEmit` here rather than joining it.
3367
+ //
3368
+ // It is a superset: reading the same tsconfig, it checks `xano/`'s TypeScript
3369
+ // exactly as `tsc` would AND the components `tsc` cannot parse at all. Running
3370
+ // both is not merely redundant — `tsc` fails outright on the UI kit's barrels,
3371
+ // which re-export types from `.svelte` modules it only knows as default
3372
+ // exports. Verified against a real scaffold: a type error planted in
3373
+ // `xano/index.ts` and one planted in `App.svelte` are each caught by this
3374
+ // command alone.
3375
+ //
3376
+ // `svelte-kit sync` runs first, and is not optional: it generates
3377
+ // `.svelte-kit/tsconfig.json` (which the project's tsconfig extends) and the
3378
+ // route types. Without it svelte-check fails on a missing extends target
3379
+ // rather than on anything the user wrote.
3380
+ checkCmd: "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
3381
+ // SvelteKit supplies `$lib` on both sides — the Vite alias from its plugin
3382
+ // and the tsconfig path from the config it generates. Declaring either by
3383
+ // hand would be a second source of truth that can silently disagree.
3384
+ aliasName: null,
3385
+ aliasTarget: "frontend/src/lib",
3386
+ tsconfigPaths: {},
3387
+ tsconfigExtends: "./.svelte-kit/tsconfig.json",
3388
+ // What `svelte-kit sync` generates and the extended config lists — re-added
3389
+ // because our `include` replaces that config's rather than merging with it.
3390
+ // These are the declarations behind `$app/*`, `$env/*`, and `./$types`.
3391
+ tsconfigInclude: [
3392
+ ".svelte-kit/ambient.d.ts",
3393
+ ".svelte-kit/env.d.ts",
3394
+ ".svelte-kit/non-ambient.d.ts",
3395
+ ".svelte-kit/types/**/$types.d.ts"
3396
+ ],
3397
+ // `prepare` runs on `npm install`, so a fresh clone has `.svelte-kit/`
3398
+ // before anything reads tsconfig.json.
3399
+ extraScripts: { prepare: "svelte-kit sync" },
3400
+ // shadcn-svelte's generated components import these from `$lib/utils.js`
3401
+ // alongside `cn()`. Without them the scaffold type-checks until the first
3402
+ // `npx shadcn-svelte@latest add <name>` — which the agent guidance tells
3403
+ // assistants to run — and then fails inside components the user never wrote,
3404
+ // with an error naming a file they have no reason to suspect.
3405
+ //
3406
+ // Verbatim from shadcn-svelte's own utils.ts, so a component the CLI writes
3407
+ // resolves against exactly what upstream expects.
3408
+ libUtilsExtra: `
3409
+ export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
3410
+ export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
3411
+ export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
3412
+ export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
3413
+ `,
3414
+ gitignoreEntries: [".svelte-kit/"],
3415
+ // No `jsx`. `verbatimModuleSyntax` is what svelte-check expects so that
3416
+ // type-only imports in components are erased rather than emitted.
3417
+ tsconfigOptions: { verbatimModuleSyntax: true },
3418
+ viteImports: [
3419
+ `import { sveltekit } from "@sveltejs/kit/vite";`,
3420
+ `import adapter from "@sveltejs/adapter-static";`,
3421
+ `import tailwindcss from "@tailwindcss/vite";`
3422
+ ],
3423
+ // tailwindcss() before sveltekit() — the order Tailwind v4 documents for
3424
+ // SvelteKit, so the stylesheet is processed before Kit consumes the graph.
3425
+ //
3426
+ // The whole SvelteKit config rides in the sveltekit() call. There is no
3427
+ // svelte.config.js by design — see renderSvelteKitPlugin().
3428
+ vitePlugins: ["tailwindcss()", renderSvelteKitPlugin()],
3429
+ // SvelteKit resolves its own paths from this file's directory and redirects
3430
+ // them into frontend/ via `files`; moving Vite's root under it puts the two
3431
+ // into disagreement. Output is the adapter's job, not `build.outDir`.
3432
+ viteRoot: null,
3433
+ viteBuild: null,
3434
+ // SvelteKit owns the page template — frontend/src/app.html. No `entryScript`
3435
+ // follows: there is no shared index.html for it to point at.
3436
+ ownsHtmlEntry: true,
3437
+ files(vars, landing, choice = defaultThemeChoice()) {
3438
+ const ui = "frontend/src/lib/components/ui";
3439
+ return [
3440
+ { path: "components.json", content: renderComponentsJson2(choice) },
3441
+ { path: "frontend/src/app.html", content: renderAppHtml(vars, choice) },
3442
+ // Only for `--dark toggle` — see the React preset's note.
3443
+ ...choice.dark === "toggle" ? [
3444
+ {
3445
+ path: "frontend/src/lib/components/mode-toggle.svelte",
3446
+ content: renderModeToggleSvelte(iconLibraryOf(choice.icons).svelte)
3447
+ }
3448
+ ] : [],
3449
+ { path: "frontend/src/app.d.ts", content: renderAppDts() },
3450
+ { path: "frontend/src/routes/+layout.ts", content: renderLayoutTs() },
3451
+ { path: "frontend/src/routes/+layout.svelte", content: renderLayoutSvelte() },
3452
+ { path: "frontend/src/routes/+page.svelte", content: renderApp2(landing, choice) },
3453
+ { path: "frontend/src/routes/+error.svelte", content: renderErrorSvelte() },
3454
+ { path: "frontend/src/routes/404/+page.svelte", content: renderNotFoundRoute() },
3455
+ { path: "frontend/src/lib/components/error-state.svelte", content: renderErrorState() },
3456
+ // `kit.files.assets` points here: static files copied to the site root
3457
+ // verbatim. A README rather than a .gitkeep because this one gets
3458
+ // PUBLISHED — everything here lands in frontend/dist, so the placeholder
3459
+ // that keeps the directory in git is a file real users will see deployed.
3460
+ {
3461
+ path: "frontend/static/README.md",
3462
+ content: `Files here are copied to the site root as-is \u2014 \`favicon.png\` is served
3463
+ at \`/favicon.png\`. Reference them from a component with a root-relative
3464
+ path, or from \`app.html\` via \`%sveltekit.assets%\`.
3465
+
3466
+ Delete this file once you add something real.
3467
+ `
3468
+ },
3469
+ { path: `${ui}/button/button.svelte`, content: renderButtonSvelte() },
3470
+ { path: `${ui}/button/index.ts`, content: renderButtonIndex() },
3471
+ {
3472
+ path: `${ui}/card/card.svelte`,
3473
+ content: cardPart(
3474
+ "card",
3475
+ "div",
3476
+ "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm"
3477
+ )
3478
+ },
3479
+ {
3480
+ path: `${ui}/card/card-header.svelte`,
3481
+ content: cardPart(
3482
+ "card-header",
3483
+ "div",
3484
+ "@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"
3485
+ )
3486
+ },
3487
+ {
3488
+ path: `${ui}/card/card-title.svelte`,
3489
+ content: cardPart("card-title", "div", "leading-none font-semibold")
3490
+ },
3491
+ {
3492
+ path: `${ui}/card/card-description.svelte`,
3493
+ content: cardPart("card-description", "div", "text-sm text-muted-foreground")
3494
+ },
3495
+ {
3496
+ path: `${ui}/card/card-content.svelte`,
3497
+ content: cardPart("card-content", "div", "px-6")
3498
+ },
3499
+ {
3500
+ path: `${ui}/card/card-footer.svelte`,
3501
+ content: cardPart("card-footer", "div", "flex items-center px-6 [.border-t]:pt-6")
3502
+ },
3503
+ { path: `${ui}/card/index.ts`, content: renderCardIndex() }
3504
+ ];
3505
+ },
3506
+ iconBinding(id) {
3507
+ return iconLibraryOf(id).svelte;
3508
+ },
3509
+ readmeFrontendSection(icons) {
3510
+ return `[SvelteKit](https://svelte.dev/docs/kit) with Svelte 5, styled with
3511
+ [Tailwind CSS](https://tailwindcss.com) v4 and
3512
+ [shadcn-svelte](https://shadcn-svelte.com).
3513
+
3514
+ Pages live in [\`frontend/src/routes/\`](frontend/src/routes/) \u2014 \`+page.svelte\`
3515
+ is a page, a subdirectory is a nested route. Routes are **prerendered**: each
3516
+ becomes its own HTML document at build time, so a page loads as itself rather
3517
+ than as a shell that fills in afterwards, and an unmatched path returns a real
3518
+ 404 from [\`+error.svelte\`](frontend/src/routes/+error.svelte).
3519
+
3520
+ > **There is no server.** \`npm run xano:deploy\` ships this app to a static host
3521
+ > with no runtime of its own; Xano is the backend, reached through
3522
+ > [\`frontend/src/lib/api.ts\`](frontend/src/lib/api.ts). So \`+page.server.ts\`,
3523
+ > form actions, and server \`load\` have nothing to run on \u2014 and **nothing warns
3524
+ > you**: a project using them builds and deploys green, then fails in the
3525
+ > browser. Treat them as unavailable. That is the trade \u2014 the backend is
3526
+ > authored in [\`xano/\`](xano/) as TypeScript and deployed alongside the
3527
+ > frontend, so it is a real backend, just not this one.
3528
+ >
3529
+ > \`frontend/src/routes/+layout.ts\` sets \`prerender = true\` for the whole app.
3530
+ > Because prerendering renders at build time, module-scope \`window\`/\`document\`
3531
+ > access now fails the **build** rather than the browser \u2014 use \`onMount\`, or
3532
+ > guard with \`import { browser } from "$app/environment"\`.
3533
+
3534
+ **A dynamic route needs \`entries\`, or the build fails.** A route like
3535
+ \`/posts/[id]\` cannot be prerendered unless the build knows which ids exist, so
3536
+ the build stops and names it \u2014 deliberately, rather than shipping a page that
3537
+ 404s for real users:
3538
+
3539
+ \`\`\`ts
3540
+ // frontend/src/routes/posts/[id]/+page.ts
3541
+ export const entries = () => [{ id: "1" }, { id: "2" }];
3542
+ \`\`\`
3543
+
3544
+ [\`frontend/src/routes/404/+page.svelte\`](frontend/src/routes/404/+page.svelte)
3545
+ prerenders to \`404.html\`, which is what makes an unmatched path return a real
3546
+ 404. It has to be a route: SvelteKit never prerenders \`+error.svelte\` to a file,
3547
+ so that alone would ship no \`404.html\` at all.
3548
+
3549
+ shadcn-svelte is not a dependency \u2014
3550
+ its components are copied into
3551
+ [\`frontend/src/lib/components/ui/\`](frontend/src/lib/components/ui/) and owned by
3552
+ this project, so edit them freely. \`Button\` and \`Card\` are already there; add
3553
+ more with:
3554
+
3555
+ \`\`\`bash
3556
+ npx shadcn-svelte@latest add dialog input form
3557
+ \`\`\`
3558
+
3559
+ [\`components.json\`](components.json) is pre-configured, so that works with no
3560
+ \`shadcn-svelte init\` step. ${iconLibraryOf(icons).guidance.svelte}
3561
+
3562
+ \`\`\`ts
3563
+ ${iconLibraryOf(icons).svelte.arrowImport}
3564
+ \`\`\`
3565
+
3566
+ [\`frontend/src/routes/+page.svelte\`](frontend/src/routes/+page.svelte) already
3567
+ does, and that is the form \`npx shadcn-svelte@latest add\` writes too.
3568
+
3569
+ Components import through the \`$lib\` alias
3570
+ (\`$lib/components/ui/button\`, \`$lib/utils\`), which SvelteKit points at
3571
+ \`frontend/src/lib/\` via \`kit.files\` in
3572
+ [\`vite.config.ts\`](vite.config.ts) \u2014 there is no \`paths\` entry to keep in
3573
+ sync, and adding one would just be a second answer that can disagree.
3574
+
3575
+ Colors come from the theme tokens in
3576
+ [\`frontend/src/index.css\`](frontend/src/index.css) \u2014 see **Theming** below.
3577
+
3578
+ > \`npm run typecheck\` runs \`svelte-kit sync && svelte-check\`, not \`tsc\`. It
3579
+ > checks both halves \u2014 the backend's TypeScript and the components \u2014 where
3580
+ > \`tsc\` cannot read \`.svelte\` files at all. Keep it in the script: it is what
3581
+ > makes the frontend unable to drift from the backend defs. \`sync\` regenerates
3582
+ > \`.svelte-kit/\`, which \`tsconfig.json\` extends; \`npm install\` runs it too.`;
3583
+ },
3584
+ agentGuidanceSection(icons) {
3585
+ return `- \`frontend/src/\` \u2014 the SvelteKit app. Svelte 5 + Tailwind v4 + shadcn-svelte.
3586
+ - **There is no server.** This deploys to a static host with no runtime, and
3587
+ Xano is the backend. Never write \`+page.server.ts\`, \`+layout.server.ts\`,
3588
+ form actions, or server \`load\` \u2014 reach the backend through
3589
+ \`frontend/src/lib/api.ts\` instead, and never stand up a second backend here.
3590
+ - **The build will not stop you.** A project containing \`+page.server.ts\`
3591
+ compiles and deploys green, then fails in the browser on the live site. A
3592
+ passing build is not evidence that a server feature works \u2014 there is
3593
+ nothing to run it.
3594
+ - \`frontend/src/routes/\` \u2014 file-based routing. A page is \`+page.svelte\`, a
3595
+ nested route is a subdirectory. **Routes live under \`frontend/src/\`, not a
3596
+ top-level \`src/\`** \u2014 \`files\` in the \`sveltekit()\` plugin config in
3597
+ \`vite.config.ts\` points SvelteKit here. A file added under a top-level
3598
+ \`src/routes/\` is not a route and is never served.
3599
+ - **Routes are prerendered.** \`frontend/src/routes/+layout.ts\` sets
3600
+ \`prerender = true\` for the whole app, so every route is rendered to its own
3601
+ HTML document at build time and served as a real page. A static route needs
3602
+ nothing registered \u2014 add \`+page.svelte\` and it works.
3603
+ - **Prerendering renders at BUILD time**, so module-scope \`window\`/\`document\`
3604
+ access now breaks the build, not the browser. Use \`onMount\`, or guard with
3605
+ \`import { browser } from "$app/environment"\`. This is a page option, not a
3606
+ server \u2014 do not "fix" it by reaching for \`ssr\` or a server file.
3607
+ - **A dynamic route needs \`entries\`, or the build fails.** \`/posts/[id]\`
3608
+ cannot be prerendered unless the build knows which ids exist, so the build
3609
+ stops and names the route. Export
3610
+ \`const entries = () => [{ id: "1" }, { id: "2" }]\` from that route's
3611
+ \`+page.ts\`. This is the single most likely thing to catch you out when
3612
+ adding a detail page \u2014 and it is a build error, not a runtime surprise, so
3613
+ you will see it immediately.
3614
+ - \`frontend/src/routes/404/+page.svelte\` prerenders to \`404.html\`, which is
3615
+ what makes an unmatched path return a real 404 instead of silently serving a
3616
+ page with a 200. Keep it: without it a missing asset comes back as HTML and
3617
+ the browser reports \`Unexpected token '<'\`. It has to be a route \u2014 SvelteKit
3618
+ never prerenders \`+error.svelte\` to a file, so that alone would ship no
3619
+ \`404.html\`.
3620
+ - Do NOT add \`fallback\` to the adapter config. It writes an empty shell over
3621
+ the prerendered home page, and it turns the un-prerenderable-route build
3622
+ error into a silent runtime 404.
3623
+ - Data loading: call \`api.ts\` from the component, or from a universal
3624
+ \`+page.ts\` \`load\` if you need it before render. Both run in the browser.
3625
+ - \`frontend/src/lib/components/ui/\` \u2014 shadcn-svelte components, **copied in and
3626
+ owned by this project**. Edit them directly; there is no library to configure
3627
+ around.
3628
+ - Need one that isn't there? \`npx shadcn-svelte@latest add <name>\` \u2014 do not
3629
+ hand-roll it, and do not add a different component library. Note the
3630
+ \`-svelte\` suffix: the plain \`shadcn\` CLI writes React and will not work here.
3631
+ - ${iconLibraryOf(icons).guidance.svelte.replace(/\n/g, "\n ")}
3632
+ - That import form is what \`frontend/src/routes/+page.svelte\` already uses and
3633
+ what \`npx shadcn-svelte@latest add\` writes, so matching it keeps what you
3634
+ write consistent with what the CLI generates beside it. Do not add another
3635
+ icon library and do not paste raw inline \`<svg>\` markup.
3636
+ - Import via the \`$lib\` alias (\`$lib/components/ui/button\`, \`$lib/utils\`),
3637
+ which SvelteKit generates for both TypeScript and the bundler. Do not add a
3638
+ \`paths\` entry for it \u2014 a hand-written one is a second source of truth that
3639
+ can disagree with the generated one.
3640
+ - Components are Svelte 5 **runes** (\`$props()\`, \`$bindable()\`,
3641
+ \`{@render children?.()}\`) \u2014 not the Svelte 4 \`export let\` / slot API. Runes
3642
+ mode is FORCED in \`vite.config.ts\`, so \`export let\` is a compile error
3643
+ naming the file rather than a component that quietly builds in legacy mode.
3644
+ Do not "fix" that error by relaxing \`compilerOptions.runes\` \u2014 write the
3645
+ rune.
3646
+ - \`npm run typecheck\` runs \`svelte-kit sync && svelte-check\`, which checks both
3647
+ \`xano/\` and the components. Do not swap it for \`tsc --noEmit\`: \`tsc\` cannot
3648
+ read \`.svelte\` files and fails on the UI kit's barrels. \`sync\` regenerates
3649
+ \`.svelte-kit/\`, which \`tsconfig.json\` extends \u2014 run it after adding a route.`;
3650
+ }
3651
+ };
3652
+
3653
+ // src/emit/frontend-presets.ts
3654
+ var FRAMEWORKS = ["react", "svelte"];
3655
+ var DEFAULT_FRAMEWORK = "react";
3656
+ var PRESETS = {
3657
+ react: reactPreset,
3658
+ svelte: sveltePreset
3659
+ };
3660
+ function findFrontendPreset(id) {
3661
+ const normalized = id.trim().toLowerCase();
3662
+ return FRAMEWORKS.includes(normalized) ? PRESETS[normalized] : void 0;
3663
+ }
3664
+ function allFrontendPresets() {
3665
+ return FRAMEWORKS.map((id) => PRESETS[id]);
3666
+ }
3667
+ function resolveFrameworkFlag(raw) {
3668
+ const preset = findFrontendPreset(raw);
3669
+ if (preset === void 0) {
3670
+ throw new Error(
3671
+ `Unknown --framework "${raw}". Valid frameworks: ${FRAMEWORKS.join(", ")}.`
3672
+ );
3673
+ }
3674
+ return preset;
3675
+ }
3676
+
2000
3677
  export {
2001
3678
  BASE_TOKENS,
2002
3679
  ACCENT_TOKENS,
@@ -2039,6 +3716,30 @@ export {
2039
3716
  defaultIconLibrary,
2040
3717
  resolveIconsFlag,
2041
3718
  iconLibraryOf,
2042
- reactPreset
3719
+ reactPreset,
3720
+ sdkDep,
3721
+ renderPackageJson,
3722
+ renderTsconfig,
3723
+ renderViteConfig,
3724
+ renderIndexHtml,
3725
+ renderGitignore,
3726
+ renderEnvExample,
3727
+ renderReadme,
3728
+ renderXanoIndex,
3729
+ renderXanoExampleMd,
3730
+ describeOrigin,
3731
+ renderCodegenMarker,
3732
+ renderCodegenReadme,
3733
+ initLanding,
3734
+ codegenLanding,
3735
+ renderIndexCss,
3736
+ renderThemeModule,
3737
+ renderCnUtil,
3738
+ renderApiTs,
3739
+ FRAMEWORKS,
3740
+ DEFAULT_FRAMEWORK,
3741
+ findFrontendPreset,
3742
+ allFrontendPresets,
3743
+ resolveFrameworkFlag
2043
3744
  };
2044
- //# sourceMappingURL=chunk-Q77KNEUL.js.map
3745
+ //# sourceMappingURL=chunk-RQNMTDXD.js.map