@xanots/sdk 0.0.2 → 0.0.3

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 (43) hide show
  1. package/README.md +11 -2
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/{agent-file-refresh-7XCACU34.js → agent-file-refresh-6M5M7T24.js} +4 -3
  4. package/dist/bin.js +7 -7
  5. package/dist/{chunk-MUDLJG2O.js → chunk-2V4YE6QC.js} +25 -15
  6. package/dist/{chunk-UVLVQWWF.js → chunk-3NFUWXOC.js} +70 -20
  7. package/dist/chunk-6DHJVLYP.js +2044 -0
  8. package/dist/{chunk-IZW6MK3U.js → chunk-6USV65XA.js} +2 -2
  9. package/dist/{chunk-XLD4BUTF.js → chunk-BT2CSEC5.js} +2 -2
  10. package/dist/{chunk-VKFQJEMW.js → chunk-CMZLPTGW.js} +37 -406
  11. package/dist/{chunk-YYRDLFSJ.js → chunk-DYCLVQXW.js} +50 -3
  12. package/dist/chunk-HQ2CBRVI.js +530 -0
  13. package/dist/{chunk-ZPEY5WE4.js → chunk-W2G2WPTB.js} +3 -3
  14. package/dist/chunk-WUSKBXXD.js +25 -0
  15. package/dist/{chunk-ODNMVJ6H.js → chunk-YZ4GU6F5.js} +283 -252
  16. package/dist/{chunk-5YTBTF25.js → chunk-ZO3HJOCJ.js} +2 -2
  17. package/dist/cli.d.ts +31 -0
  18. package/dist/cli.js +5 -5
  19. package/dist/{codegen-command-RY2GWDLX.js → codegen-command-FIADGXRN.js} +13 -11
  20. package/dist/{completion-TIPPZN7H.js → completion-BKAFCZBE.js} +2 -2
  21. package/dist/{deploy-command-DSISLUW5.js → deploy-command-4R6BYC6G.js} +13 -13
  22. package/dist/{env-target-7C2GKBCY.js → env-target-XWS2ZZ2Y.js} +2 -2
  23. package/dist/{ephemeral-command-5K573ZSG.js → ephemeral-command-JL4TPIRQ.js} +15 -15
  24. package/dist/{init-command-4AWL6CLW.js → init-command-LOK75W64.js} +11 -9
  25. package/dist/internal.js +4 -3
  26. package/dist/{lock-commands-7Q5LNZKL.js → lock-commands-XOYS75YQ.js} +9 -9
  27. package/dist/{marketplace-command-RATXTKXN.js → marketplace-command-UVV3XAOL.js} +6 -6
  28. package/dist/meta-client-OW5WKWW7.js +1 -1
  29. package/dist/node.js +8 -8
  30. package/dist/{release-command-QXLRJMY7.js → release-command-HZUY2XZX.js} +15 -15
  31. package/dist/sandbox-export-command-QCJY4GMV.js +1 -1
  32. package/dist/scaffold.d.ts +1896 -0
  33. package/dist/scaffold.js +101 -0
  34. package/dist/{test-command-BOLIU57E.js → test-command-72Y5S22H.js} +4 -4
  35. package/dist/{validate-command-SYCFEYZN.js → validate-command-KDGH537H.js} +7 -7
  36. package/dist/{workspace-command-TOB2AJ6X.js → workspace-command-ICSI6PKO.js} +13 -11
  37. package/dist/workspace-export-AJMGN3CQ.js +1 -1
  38. package/llms/kinds-agent-mcp.md +2 -1
  39. package/llms-full.txt +3 -2
  40. package/llms.txt +1 -1
  41. package/manifest.json +145 -1
  42. package/package.json +7 -1
  43. package/dist/chunk-47WDWMBJ.js +0 -14
@@ -1,23 +1,13 @@
1
1
  import {
2
- presetFilePath,
3
- reactPreset,
4
- resolveAiPresets,
5
- scaffoldProject
6
- } from "./chunk-VKFQJEMW.js";
7
- import {
8
- readVersion
9
- } from "./chunk-UVLVQWWF.js";
10
- import {
11
- blank,
12
- detail,
13
- info,
14
- step,
15
- style,
16
- success
17
- } from "./chunk-EZG76F7R.js";
18
-
19
- // src/emit/init-command.ts
20
- import { basename, resolve } from "path";
2
+ colorTokens,
3
+ defaultThemeChoice,
4
+ fontDependencies,
5
+ fontImports,
6
+ fontThemeVars,
7
+ iconLibraryOf,
8
+ orderedTokens,
9
+ reactPreset
10
+ } from "./chunk-6DHJVLYP.js";
21
11
 
22
12
  // src/emit/init-templates.ts
23
13
  function sdkDep(sdkVersion) {
@@ -27,7 +17,7 @@ function sdkDep(sdkVersion) {
27
17
  const ceiling = major > 0 ? `${major + 1}.0.0` : `0.${minor + 1}.0`;
28
18
  return `>=${sdkVersion} <${ceiling}`;
29
19
  }
30
- function renderPackageJson({ appName, sdkVersion }, preset) {
20
+ function renderPackageJson({ appName, sdkVersion }, preset, choice = defaultThemeChoice()) {
31
21
  const check = preset.checkCmd;
32
22
  const pkg = {
33
23
  name: appName,
@@ -76,7 +66,13 @@ function renderPackageJson({ appName, sdkVersion }, preset) {
76
66
  // on demand — a project that never registers auth should not carry it,
77
67
  // and an add-on's release cadence is its own, not the CLI's.
78
68
  "@xanots/sdk": sdkDep(sdkVersion),
79
- ...preset.dependencies
69
+ ...preset.dependencies,
70
+ // The icon set and the typefaces are choices, not framework constants, so
71
+ // they are merged here rather than sitting in `preset.dependencies`. Both
72
+ // land after the preset's own entries: a preset that still names an icon
73
+ // package would otherwise win over the set the user actually picked.
74
+ ...preset.iconBinding(choice.icons).dependency,
75
+ ...fontDependencies(choice.fonts)
80
76
  },
81
77
  devDependencies: { ...preset.devDependencies },
82
78
  // Not ">=20": Vite 8 and vite-plugin-svelte 7 both declare
@@ -163,7 +159,7 @@ function renderInlineObject(obj) {
163
159
  const entries = Object.entries(obj).map(([k, v]) => `${k}: ${JSON.stringify(v)}`);
164
160
  return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
165
161
  }
166
- function renderIndexHtml({ appName }, preset) {
162
+ function renderIndexHtml({ appName }, preset, choice = defaultThemeChoice()) {
167
163
  if (preset.entryScript === void 0) {
168
164
  throw new Error(`The ${preset.id} preset owns its own HTML entry; renderIndexHtml does not apply.`);
169
165
  }
@@ -173,7 +169,7 @@ function renderIndexHtml({ appName }, preset) {
173
169
  <meta charset="UTF-8" />
174
170
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
175
171
  <title>${appName}</title>
176
- </head>
172
+ ${renderThemeScript(choice.dark)} </head>
177
173
  <body>
178
174
  <div id="root"></div>
179
175
  <script type="module" src="${preset.entryScript}"></script>
@@ -203,7 +199,7 @@ function renderEnvExample() {
203
199
  VITE_XANO_HOST=https://your-instance.xano.io
204
200
  `;
205
201
  }
206
- function renderReadme({ appName }, preset) {
202
+ function renderReadme({ appName }, preset, choice = defaultThemeChoice()) {
207
203
  return `# ${appName}
208
204
 
209
205
  A [XanoTS](https://www.npmjs.com/package/@xanots/sdk) project: a Xano
@@ -277,7 +273,9 @@ body \u2014 change a def and the frontend types follow.
277
273
 
278
274
  ## The frontend
279
275
 
280
- ${preset.readmeFrontendSection()}
276
+ ${preset.readmeFrontendSection(choice.icons)}
277
+
278
+ ${renderReadmeThemingSection(choice)}
281
279
 
282
280
  ## Add-ons
283
281
 
@@ -566,6 +564,44 @@ npx xanots paths xano/index.ts # list every endpoint's verb + path
566
564
  request paths and types from the query defs in \`xano/\` rather than hand-typing a URL.
567
565
  `;
568
566
  }
567
+ function renderReadmeThemingSection(choice) {
568
+ const { theme, dark } = choice;
569
+ const darkNote = {
570
+ system: `Dark mode follows the OS. An inline script in the HTML entry sets
571
+ \`class="dark"\` on \`<html>\` before first paint (so the page never flashes light
572
+ first), and the \`.dark\` block in the stylesheet supplies the palette. To let
573
+ people override it, add a control that toggles that class \u2014 or scaffold your
574
+ next project with \`--dark toggle\`, which ships one.`,
575
+ toggle: `Dark mode follows the OS until the user says otherwise. The pieces:
576
+ an inline script in the HTML entry applies the mode before first paint,
577
+ [\`frontend/src/lib/theme.ts\`](frontend/src/lib/theme.ts) holds and persists it,
578
+ and the mode toggle on the landing page cycles system \u2192 light \u2192 dark.`,
579
+ off: `This project was scaffolded with \`--dark off\`, so nothing switches
580
+ themes. The \`.dark\` block in the stylesheet is still there and still complete \u2014
581
+ add \`class="dark"\` to \`<html>\` to see it, and wire that to a control (or the OS
582
+ setting) to turn it on for real.`
583
+ }[dark];
584
+ return `## Theming
585
+
586
+ Scaffolded with the **${theme.label}** theme. Every color in the app comes from
587
+ the semantic tokens at the top of
588
+ [\`frontend/src/index.css\`](frontend/src/index.css) \u2014 \`--primary\`,
589
+ \`--muted-foreground\`, \`--border\`, the \`--chart-*\` ramp, the \`--sidebar-*\` set \u2014
590
+ and every shadcn component reads those names, so editing one value rebrands
591
+ everything that uses it. Style with the token classes (\`bg-primary\`,
592
+ \`text-muted-foreground\`) rather than raw palette classes like \`bg-gray-100\`, or
593
+ the theme stops being one.
594
+
595
+ Tailwind v4 has no \`tailwind.config.js\`; that stylesheet *is* the config.
596
+
597
+ To swap the whole palette later:
598
+
599
+ \`\`\`bash
600
+ npx shadcn@latest add https://ui.shadcn.com/r/themes/stone.json # or any registry theme
601
+ \`\`\`
602
+
603
+ ${darkNote}`;
604
+ }
569
605
  var UI_DOCS_CTA = {
570
606
  label: "Browse UI components",
571
607
  href: "https://ui.shadcn.com/docs/components"
@@ -624,8 +660,22 @@ function codegenLanding({ appName }, origin) {
624
660
  cta: UI_DOCS_CTA
625
661
  };
626
662
  }
627
- function renderIndexCss() {
628
- return `@import "tailwindcss";
663
+ function renderIndexCss(choice = defaultThemeChoice()) {
664
+ const { theme } = choice;
665
+ const colorMap = colorTokens(theme.light).map((token) => ` --color-${token}: var(--${token});`).join("\n");
666
+ const block = (vars) => orderedTokens(vars).map(([name, value]) => ` --${name}: ${value};`).join("\n");
667
+ const faces = fontImports(choice.fonts);
668
+ const fontVars = fontThemeVars(choice.fonts).map(([name, value]) => ` ${name}: ${value};`).join("\n");
669
+ const headingRule = choice.fonts?.heading === void 0 ? "" : `
670
+ h1,
671
+ h2,
672
+ h3,
673
+ h4,
674
+ h5,
675
+ h6 {
676
+ font-family: var(--font-heading);
677
+ }`;
678
+ return `${faces.length === 0 ? "" : faces.join("\n") + "\n"}@import "tailwindcss";
629
679
  @import "tw-animate-css";
630
680
 
631
681
  @custom-variant dark (&:is(.dark *));
@@ -633,74 +683,25 @@ function renderIndexCss() {
633
683
  /* Map the tokens below into Tailwind's theme so \`bg-primary\` and friends
634
684
  resolve. Tailwind v4 does this in CSS; there is no tailwind.config.js. */
635
685
  @theme inline {
636
- --color-background: var(--background);
637
- --color-foreground: var(--foreground);
638
- --color-card: var(--card);
639
- --color-card-foreground: var(--card-foreground);
640
- --color-popover: var(--popover);
641
- --color-popover-foreground: var(--popover-foreground);
642
- --color-primary: var(--primary);
643
- --color-primary-foreground: var(--primary-foreground);
644
- --color-secondary: var(--secondary);
645
- --color-secondary-foreground: var(--secondary-foreground);
646
- --color-muted: var(--muted);
647
- --color-muted-foreground: var(--muted-foreground);
648
- --color-accent: var(--accent);
649
- --color-accent-foreground: var(--accent-foreground);
650
- --color-destructive: var(--destructive);
651
- --color-border: var(--border);
652
- --color-input: var(--input);
653
- --color-ring: var(--ring);
686
+ ${colorMap}
654
687
  --radius-sm: calc(var(--radius) - 4px);
655
688
  --radius-md: calc(var(--radius) - 2px);
656
689
  --radius-lg: var(--radius);
657
- --radius-xl: calc(var(--radius) + 4px);
690
+ --radius-xl: calc(var(--radius) + 4px);${fontVars === "" ? "" : `
691
+ ${fontVars}`}
658
692
  }
659
693
 
660
- /* Rebrand here: these are the only colors the components know about. */
694
+ /* Theme: ${theme.label}. Rebrand here \u2014 these are the only colors the
695
+ components know about. Swap the whole palette for another shadcn theme with
696
+ \`npx shadcn@latest add <registry-theme-url>\`, or scaffold a different one
697
+ next time with \`xanots init --theme <base>-<accent>\`. */
661
698
  :root {
662
- --radius: 0.625rem;
663
- --background: oklch(1 0 0);
664
- --foreground: oklch(0.145 0 0);
665
- --card: oklch(1 0 0);
666
- --card-foreground: oklch(0.145 0 0);
667
- --popover: oklch(1 0 0);
668
- --popover-foreground: oklch(0.145 0 0);
669
- --primary: oklch(0.205 0 0);
670
- --primary-foreground: oklch(0.985 0 0);
671
- --secondary: oklch(0.97 0 0);
672
- --secondary-foreground: oklch(0.205 0 0);
673
- --muted: oklch(0.97 0 0);
674
- --muted-foreground: oklch(0.556 0 0);
675
- --accent: oklch(0.97 0 0);
676
- --accent-foreground: oklch(0.205 0 0);
677
- --destructive: oklch(0.577 0.245 27.325);
678
- --border: oklch(0.922 0 0);
679
- --input: oklch(0.922 0 0);
680
- --ring: oklch(0.708 0 0);
699
+ ${block(theme.light)}
681
700
  }
682
701
 
683
- /* Applied by adding \`class="dark"\` to <html> \u2014 wire that to a toggle if you
684
- want one; the scaffold ships light-only. */
702
+ ${darkBlockComment(choice.dark)}
685
703
  .dark {
686
- --background: oklch(0.145 0 0);
687
- --foreground: oklch(0.985 0 0);
688
- --card: oklch(0.205 0 0);
689
- --card-foreground: oklch(0.985 0 0);
690
- --popover: oklch(0.205 0 0);
691
- --popover-foreground: oklch(0.985 0 0);
692
- --primary: oklch(0.922 0 0);
693
- --primary-foreground: oklch(0.205 0 0);
694
- --secondary: oklch(0.269 0 0);
695
- --secondary-foreground: oklch(0.985 0 0);
696
- --muted: oklch(0.269 0 0);
697
- --muted-foreground: oklch(0.708 0 0);
698
- --accent: oklch(0.269 0 0);
699
- --accent-foreground: oklch(0.985 0 0);
700
- --destructive: oklch(0.704 0.191 22.216);
701
- --border: oklch(1 0 0 / 10%);
702
- --input: oklch(1 0 0 / 15%);
703
- --ring: oklch(0.556 0 0);
704
+ ${block(theme.dark)}
704
705
  }
705
706
 
706
707
  @layer base {
@@ -709,8 +710,95 @@ function renderIndexCss() {
709
710
  }
710
711
  body {
711
712
  @apply bg-background text-foreground;
713
+ }${headingRule}
714
+ }
715
+ `;
716
+ }
717
+ function darkBlockComment(dark) {
718
+ switch (dark) {
719
+ case "system":
720
+ return `/* Applied by the inline script in index.html, which follows the OS setting.
721
+ Add a control that toggles this class to let the user override it. */`;
722
+ case "toggle":
723
+ return `/* Applied by frontend/src/lib/theme.ts \u2014 the OS setting by default, and the
724
+ user's choice once they use the mode toggle. */`;
725
+ case "off":
726
+ return `/* Applied by adding \`class="dark"\` to <html> \u2014 wire that to a toggle if you
727
+ want one; this scaffold was created with --dark off, so nothing does yet. */`;
712
728
  }
713
729
  }
730
+ function renderThemeScript(dark) {
731
+ if (dark === "off") return "";
732
+ const resolve = dark === "toggle" ? `var stored = localStorage.getItem("theme");
733
+ var dark = stored === "dark" || (stored !== "light" && media.matches);` : `var dark = media.matches;`;
734
+ return ` <script>
735
+ // Sets the \`dark\` class before first paint, so the page never flashes
736
+ // light before switching. Keep this inline and above the stylesheet.
737
+ (function () {
738
+ try {
739
+ var media = window.matchMedia("(prefers-color-scheme: dark)");
740
+ ${resolve}
741
+ document.documentElement.classList.toggle("dark", dark);
742
+ } catch (e) {}
743
+ })();
744
+ </script>
745
+ `;
746
+ }
747
+ function renderThemeModule() {
748
+ return `/**
749
+ * Color mode, persisted. The inline script in index.html applies the stored
750
+ * value before first paint; this module is what changes it afterwards.
751
+ *
752
+ * "system" means no stored preference \u2014 the OS decides, and keeps deciding if
753
+ * the user changes it while the page is open.
754
+ */
755
+ export type Mode = "light" | "dark" | "system";
756
+
757
+ const KEY = "theme";
758
+
759
+ const prefersDark = () =>
760
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
761
+
762
+ /** The stored preference, or "system" when there is none. */
763
+ export function getMode(): Mode {
764
+ const stored = localStorage.getItem(KEY);
765
+ return stored === "light" || stored === "dark" ? stored : "system";
766
+ }
767
+
768
+ /** Whether \`mode\` renders dark right now. */
769
+ export function isDark(mode: Mode): boolean {
770
+ return mode === "dark" || (mode === "system" && prefersDark());
771
+ }
772
+
773
+ /** Persist a mode and apply it. "system" clears the stored override. */
774
+ export function setMode(mode: Mode): void {
775
+ if (mode === "system") localStorage.removeItem(KEY);
776
+ else localStorage.setItem(KEY, mode);
777
+ document.documentElement.classList.toggle("dark", isDark(mode));
778
+ }
779
+
780
+ /**
781
+ * Re-apply on OS changes, and on changes made in another tab. Returns an
782
+ * unsubscribe function \u2014 call it from your framework's cleanup hook.
783
+ *
784
+ * The OS listener re-reads the mode rather than closing over it, so it stays
785
+ * correct after the user picks an explicit light/dark and then goes back to
786
+ * system.
787
+ */
788
+ export function watchMode(onChange: (mode: Mode) => void): () => void {
789
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
790
+ const apply = () => {
791
+ const mode = getMode();
792
+ document.documentElement.classList.toggle("dark", isDark(mode));
793
+ onChange(mode);
794
+ };
795
+ media.addEventListener("change", apply);
796
+ window.addEventListener("storage", apply);
797
+ return () => {
798
+ media.removeEventListener("change", apply);
799
+ window.removeEventListener("storage", apply);
800
+ };
801
+ }
714
802
  `;
715
803
  }
716
804
  function renderCnUtil(extra = "") {
@@ -779,9 +867,6 @@ export const XANO_HOST: string =
779
867
  `;
780
868
  }
781
869
 
782
- // src/emit/frontend-resolve.ts
783
- import { createInterface } from "readline/promises";
784
-
785
870
  // src/emit/frontend-svelte.ts
786
871
  var CODE_CLASS = "bg-muted rounded px-1.5 py-0.5 font-mono text-sm";
787
872
  function htmlText(s) {
@@ -796,16 +881,22 @@ function renderNode(node) {
796
881
  function renderNodes(nodes) {
797
882
  return nodes.map(renderNode).join("");
798
883
  }
799
- function renderApp(landing) {
800
- const steps = landing.steps.map((step2) => ` <li>${renderNodes(step2)}</li>`).join("\n");
884
+ function renderApp(landing, choice) {
885
+ const steps = landing.steps.map((step) => ` <li>${renderNodes(step)}</li>`).join("\n");
886
+ const toggle = choice.dark === "toggle";
887
+ const icons = iconLibraryOf(choice.icons).svelte;
801
888
  return `<script lang="ts">
802
- import ArrowRight from "@lucide/svelte/icons/arrow-right";
889
+ ${icons.arrowImport}
803
890
 
804
891
  import { Button } from "$lib/components/ui/button/index.js";
805
- import * as Card from "$lib/components/ui/card/index.js";
892
+ import * as Card from "$lib/components/ui/card/index.js";${toggle ? `
893
+ import ModeToggle from "$lib/components/mode-toggle.svelte";` : ""}
806
894
  </script>
807
895
 
808
- <main class="mx-auto flex min-h-screen max-w-2xl flex-col justify-center p-8">
896
+ <main class="mx-auto flex min-h-screen max-w-2xl flex-col justify-center p-8">${toggle ? `
897
+ <div class="mb-4 flex justify-end">
898
+ <ModeToggle />
899
+ </div>` : ""}
809
900
  <Card.Root>
810
901
  <Card.Header>
811
902
  <Card.Title class="text-3xl tracking-tight">${svelteText(landing.title)}</Card.Title>
@@ -823,7 +914,7 @@ ${steps}
823
914
  \`npx shadcn-svelte@latest add <name>\`. -->
824
915
  <Button href="${landing.cta.href}" target="_blank" rel="noreferrer">
825
916
  ${svelteText(landing.cta.label)}
826
- <ArrowRight />
917
+ <${icons.arrowComponent} />
827
918
  </Button>
828
919
  </Card.Footer>
829
920
  </Card.Root>
@@ -913,13 +1004,14 @@ function renderSvelteKitPlugin() {
913
1004
  },
914
1005
  })`;
915
1006
  }
916
- function renderAppHtml({ appName }) {
1007
+ function renderAppHtml({ appName }, choice) {
917
1008
  return `<!doctype html>
918
1009
  <html lang="en">
919
1010
  <head>
920
1011
  <meta charset="utf-8" />
921
1012
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
922
1013
  <title>${htmlText(appName)}</title>
1014
+ ${renderThemeScript(choice.dark)}
923
1015
  <!-- Drop a favicon.png into frontend/static/ and link it here:
924
1016
  <link rel="icon" href="%sveltekit.assets%/favicon.png" />
925
1017
  Everything in that directory is copied to the site root verbatim. -->
@@ -933,6 +1025,46 @@ function renderAppHtml({ appName }) {
933
1025
  </html>
934
1026
  `;
935
1027
  }
1028
+ function renderModeToggleSvelte(icons) {
1029
+ return `<script lang="ts">
1030
+ ${icons.modeImports.join("\n ")}
1031
+
1032
+ import { Button } from "$lib/components/ui/button/index.js";
1033
+ import { getMode, setMode, watchMode, type Mode } from "$lib/theme.js";
1034
+
1035
+ /** system \u2192 light \u2192 dark \u2192 system. */
1036
+ const next: Record<Mode, Mode> = { system: "light", light: "dark", dark: "system" };
1037
+
1038
+ const icons = {
1039
+ system: ${icons.modeComponents.system},
1040
+ light: ${icons.modeComponents.light},
1041
+ dark: ${icons.modeComponents.dark},
1042
+ };
1043
+
1044
+ // Starts at "system" so the prerendered HTML is deterministic; the effect
1045
+ // corrects it in the browser, after the inline script has already applied
1046
+ // the right class.
1047
+ let mode = $state<Mode>("system");
1048
+
1049
+ $effect(() => {
1050
+ mode = getMode();
1051
+ return watchMode((m) => (mode = m));
1052
+ });
1053
+
1054
+ function change() {
1055
+ const target = next[mode];
1056
+ setMode(target);
1057
+ mode = target;
1058
+ }
1059
+
1060
+ const Icon = $derived(icons[mode]);
1061
+ </script>
1062
+
1063
+ <Button variant="ghost" size="icon" onclick={change} aria-label="Switch to {next[mode]} theme">
1064
+ <Icon />
1065
+ </Button>
1066
+ `;
1067
+ }
936
1068
  function renderAppDts() {
937
1069
  return `// See https://svelte.dev/docs/kit/types#app.d.ts
938
1070
  declare global {
@@ -1048,14 +1180,16 @@ function renderLayoutSvelte() {
1048
1180
  {@render children()}
1049
1181
  `;
1050
1182
  }
1051
- function renderComponentsJson() {
1183
+ function renderComponentsJson(choice) {
1052
1184
  const config = {
1053
1185
  $schema: "https://shadcn-svelte.com/schema.json",
1054
1186
  tailwind: {
1055
1187
  // v4 has no config file; the theme lives in the stylesheet — the same
1056
1188
  // shared one the React preset points at.
1057
1189
  css: "frontend/src/index.css",
1058
- baseColor: "neutral"
1190
+ // Tracks the theme the stylesheet was rendered from — see the React
1191
+ // preset's note; the shadcn-svelte CLI reads it the same way.
1192
+ baseColor: choice.theme.baseColor ?? "neutral"
1059
1193
  },
1060
1194
  aliases: {
1061
1195
  components: "$lib/components",
@@ -1215,14 +1349,14 @@ var sveltePreset = {
1215
1349
  // shadcn-svelte's runtime surface. `bits-ui` is the headless primitive
1216
1350
  // library its components are built on (the Radix equivalent), and
1217
1351
  // `tailwind-variants` types the variants (the cva equivalent). clsx +
1218
- // tailwind-merge back `cn()` exactly as in the React preset; @lucide/svelte
1219
- // is the icon set. Pre-installed so `npx shadcn-svelte@latest add
1220
- // <component>` works without a separate npm i.
1352
+ // tailwind-merge back `cn()` exactly as in the React preset. Pre-installed
1353
+ // so `npx shadcn-svelte@latest add <component>` works without a separate
1354
+ // npm i.
1355
+ //
1356
+ // The icon package is `--icons`, merged in by renderPackageJson from
1357
+ // `iconBinding` below — see the React preset's note.
1221
1358
  "bits-ui": "^2.18.1",
1222
1359
  clsx: "^2.1.1",
1223
- // Not the same version line as `lucide-react`: the Svelte icons ship from a
1224
- // separate package on their own 1.x.
1225
- "@lucide/svelte": "^1.31.0",
1226
1360
  "tailwind-merge": "^3.0.1",
1227
1361
  "tailwind-variants": "^3.3.1"
1228
1362
  },
@@ -1321,15 +1455,22 @@ export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?:
1321
1455
  // SvelteKit owns the page template — frontend/src/app.html. No `entryScript`
1322
1456
  // follows: there is no shared index.html for it to point at.
1323
1457
  ownsHtmlEntry: true,
1324
- files(vars, landing) {
1458
+ files(vars, landing, choice = defaultThemeChoice()) {
1325
1459
  const ui = "frontend/src/lib/components/ui";
1326
1460
  return [
1327
- { path: "components.json", content: renderComponentsJson() },
1328
- { path: "frontend/src/app.html", content: renderAppHtml(vars) },
1461
+ { path: "components.json", content: renderComponentsJson(choice) },
1462
+ { path: "frontend/src/app.html", content: renderAppHtml(vars, choice) },
1463
+ // Only for `--dark toggle` — see the React preset's note.
1464
+ ...choice.dark === "toggle" ? [
1465
+ {
1466
+ path: "frontend/src/lib/components/mode-toggle.svelte",
1467
+ content: renderModeToggleSvelte(iconLibraryOf(choice.icons).svelte)
1468
+ }
1469
+ ] : [],
1329
1470
  { path: "frontend/src/app.d.ts", content: renderAppDts() },
1330
1471
  { path: "frontend/src/routes/+layout.ts", content: renderLayoutTs() },
1331
1472
  { path: "frontend/src/routes/+layout.svelte", content: renderLayoutSvelte() },
1332
- { path: "frontend/src/routes/+page.svelte", content: renderApp(landing) },
1473
+ { path: "frontend/src/routes/+page.svelte", content: renderApp(landing, choice) },
1333
1474
  { path: "frontend/src/routes/+error.svelte", content: renderErrorSvelte() },
1334
1475
  { path: "frontend/src/routes/404/+page.svelte", content: renderNotFoundRoute() },
1335
1476
  { path: "frontend/src/lib/components/error-state.svelte", content: renderErrorState() },
@@ -1383,7 +1524,10 @@ Delete this file once you add something real.
1383
1524
  { path: `${ui}/card/index.ts`, content: renderCardIndex() }
1384
1525
  ];
1385
1526
  },
1386
- readmeFrontendSection() {
1527
+ iconBinding(id) {
1528
+ return iconLibraryOf(id).svelte;
1529
+ },
1530
+ readmeFrontendSection(icons) {
1387
1531
  return `[SvelteKit](https://svelte.dev/docs/kit) with Svelte 5, styled with
1388
1532
  [Tailwind CSS](https://tailwindcss.com) v4 and
1389
1533
  [shadcn-svelte](https://shadcn-svelte.com).
@@ -1434,12 +1578,10 @@ npx shadcn-svelte@latest add dialog input form
1434
1578
  \`\`\`
1435
1579
 
1436
1580
  [\`components.json\`](components.json) is pre-configured, so that works with no
1437
- \`shadcn-svelte init\` step. Icons are [Lucide](https://lucide.dev/icons),
1438
- installed as \`@lucide/svelte\`. Import one per module, with the icon name
1439
- kebab-cased into the path:
1581
+ \`shadcn-svelte init\` step. ${iconLibraryOf(icons).guidance.svelte}
1440
1582
 
1441
1583
  \`\`\`ts
1442
- import ArrowRight from "@lucide/svelte/icons/arrow-right";
1584
+ ${iconLibraryOf(icons).svelte.arrowImport}
1443
1585
  \`\`\`
1444
1586
 
1445
1587
  [\`frontend/src/routes/+page.svelte\`](frontend/src/routes/+page.svelte) already
@@ -1451,9 +1593,8 @@ Components import through the \`$lib\` alias
1451
1593
  [\`vite.config.ts\`](vite.config.ts) \u2014 there is no \`paths\` entry to keep in
1452
1594
  sync, and adding one would just be a second answer that can disagree.
1453
1595
 
1454
- To rebrand, edit the color tokens at the top of
1455
- [\`frontend/src/index.css\`](frontend/src/index.css). Tailwind v4 has no
1456
- \`tailwind.config.js\`; the theme lives in that stylesheet.
1596
+ Colors come from the theme tokens in
1597
+ [\`frontend/src/index.css\`](frontend/src/index.css) \u2014 see **Theming** below.
1457
1598
 
1458
1599
  > \`npm run typecheck\` runs \`svelte-kit sync && svelte-check\`, not \`tsc\`. It
1459
1600
  > checks both halves \u2014 the backend's TypeScript and the components \u2014 where
@@ -1461,7 +1602,7 @@ To rebrand, edit the color tokens at the top of
1461
1602
  > makes the frontend unable to drift from the backend defs. \`sync\` regenerates
1462
1603
  > \`.svelte-kit/\`, which \`tsconfig.json\` extends; \`npm install\` runs it too.`;
1463
1604
  },
1464
- agentGuidanceSection() {
1605
+ agentGuidanceSection(icons) {
1465
1606
  return `- \`frontend/src/\` \u2014 the SvelteKit app. Svelte 5 + Tailwind v4 + shadcn-svelte.
1466
1607
  - **There is no server.** This deploys to a static host with no runtime, and
1467
1608
  Xano is the backend. Never write \`+page.server.ts\`, \`+layout.server.ts\`,
@@ -1508,16 +1649,11 @@ To rebrand, edit the color tokens at the top of
1508
1649
  - Need one that isn't there? \`npx shadcn-svelte@latest add <name>\` \u2014 do not
1509
1650
  hand-roll it, and do not add a different component library. Note the
1510
1651
  \`-svelte\` suffix: the plain \`shadcn\` CLI writes React and will not work here.
1511
- - Icons are [Lucide](https://lucide.dev/icons), already installed as
1512
- \`@lucide/svelte\` \u2014 **not** \`lucide-react\`, which is the wrong package here in
1513
- the same way the plain \`shadcn\` CLI is.
1514
- - **Import icons one per module, and kebab-case the path.** The icon is the
1515
- module's DEFAULT export, and \`ArrowRight\` lives at \`arrow-right\`:
1516
- \`import ArrowRight from "@lucide/svelte/icons/arrow-right";\` \u2014
1517
- \`frontend/src/routes/+page.svelte\` already does. This is the form
1518
- \`npx shadcn-svelte@latest add\` writes, so matching it keeps the components
1519
- you write consistent with the ones the CLI generates beside them. Do not add
1520
- another icon library and do not paste raw inline \`<svg>\` markup.
1652
+ - ${iconLibraryOf(icons).guidance.svelte.replace(/\n/g, "\n ")}
1653
+ - That import form is what \`frontend/src/routes/+page.svelte\` already uses and
1654
+ what \`npx shadcn-svelte@latest add\` writes, so matching it keeps what you
1655
+ write consistent with what the CLI generates beside it. Do not add another
1656
+ icon library and do not paste raw inline \`<svg>\` markup.
1521
1657
  - Import via the \`$lib\` alias (\`$lib/components/ui/button\`, \`$lib/utils\`),
1522
1658
  which SvelteKit generates for both TypeScript and the bundler. Do not add a
1523
1659
  \`paths\` entry for it \u2014 a hand-written one is a second source of truth that
@@ -1528,10 +1664,6 @@ To rebrand, edit the color tokens at the top of
1528
1664
  naming the file rather than a component that quietly builds in legacy mode.
1529
1665
  Do not "fix" that error by relaxing \`compilerOptions.runes\` \u2014 write the
1530
1666
  rune.
1531
- - Style with the semantic tokens (\`bg-primary\`, \`text-muted-foreground\`,
1532
- \`border-input\`), never raw palette classes like \`bg-gray-100\` \u2014 the tokens are
1533
- defined in \`frontend/src/index.css\`, which is also where you rebrand. Tailwind
1534
- v4 has no \`tailwind.config.js\`.
1535
1667
  - \`npm run typecheck\` runs \`svelte-kit sync && svelte-check\`, which checks both
1536
1668
  \`xano/\` and the components. Do not swap it for \`tsc --noEmit\`: \`tsc\` cannot
1537
1669
  read \`.svelte\` files and fails on the UI kit's barrels. \`sync\` regenerates
@@ -1563,130 +1695,29 @@ function resolveFrameworkFlag(raw) {
1563
1695
  return preset;
1564
1696
  }
1565
1697
 
1566
- // src/emit/frontend-resolve.ts
1567
- function defaultFrontendPreset() {
1568
- const preset = findFrontendPreset(DEFAULT_FRAMEWORK);
1569
- if (preset === void 0) {
1570
- throw new Error(`Default frontend preset "${DEFAULT_FRAMEWORK}" is missing from the table.`);
1571
- }
1572
- return preset;
1573
- }
1574
- async function promptFramework() {
1575
- const presets = allFrontendPresets();
1576
- const rl = createInterface({ input: process.stdin, output: process.stderr });
1577
- try {
1578
- process.stderr.write(
1579
- `
1580
- Which frontend framework?
1581
- ` + presets.map((p, i) => ` ${i + 1}) ${p.label}${p.id === DEFAULT_FRAMEWORK ? " (default)" : ""}`).join("\n") + `
1582
- `
1583
- );
1584
- const answer = await rl.question(`Enter a number or name, or leave blank for the default: `);
1585
- const token = answer.trim().toLowerCase();
1586
- if (token === "") return defaultFrontendPreset();
1587
- const byIndex = Number.parseInt(token, 10);
1588
- if (Number.isInteger(byIndex) && byIndex >= 1 && byIndex <= presets.length) {
1589
- return presets[byIndex - 1];
1590
- }
1591
- return findFrontendPreset(token) ?? defaultFrontendPreset();
1592
- } finally {
1593
- rl.close();
1594
- }
1595
- }
1596
- async function resolveFrontendPreset(flag) {
1597
- if (flag !== void 0 && flag !== "") return resolveFrameworkFlag(flag);
1598
- if (process.stdin.isTTY && process.stderr.isTTY) return promptFramework();
1599
- return defaultFrontendPreset();
1600
- }
1601
-
1602
- // src/emit/init-command.ts
1603
- function sanitizeAppName(raw) {
1604
- const name = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1605
- return name === "" ? "app" : name;
1606
- }
1607
- function projectShellFiles(vars, preset, parts) {
1608
- return [
1609
- { path: "package.json", content: renderPackageJson(vars, preset) },
1610
- { path: "tsconfig.json", content: renderTsconfig(preset) },
1611
- { path: "vite.config.ts", content: renderViteConfig(preset) },
1612
- { path: ".gitignore", content: renderGitignore(preset) },
1613
- { path: ".env.example", content: renderEnvExample() },
1614
- { path: "README.md", content: parts.readme },
1615
- // Skipped when the framework supplies its own entry document — see
1616
- // FrontendPreset.ownsHtmlEntry. A second index.html beside SvelteKit's
1617
- // app.html would never be served and would still read as the entry point.
1618
- ...preset.ownsHtmlEntry === true ? [] : [{ path: "frontend/index.html", content: renderIndexHtml(vars, preset) }],
1619
- // Shared across every framework: both UI kits resolve against the same
1620
- // Tailwind v4 semantic tokens, back `cn()` with clsx + tailwind-merge, and
1621
- // look for library code at frontend/src/lib/. Only the alias NAME pointing
1622
- // at that directory differs, and the preset owns that.
1623
- { path: "frontend/src/index.css", content: renderIndexCss() },
1624
- { path: "frontend/src/lib/api.ts", content: renderApiTs() },
1625
- { path: "frontend/src/lib/utils.ts", content: renderCnUtil(preset.libUtilsExtra) },
1626
- // The framework's own half: entry module, app component, vendored UI kit,
1627
- // and whatever tool config that kit's CLI reads.
1628
- ...preset.files(vars, parts.landing)
1629
- ];
1630
- }
1631
- function buildFileSet(vars, preset) {
1632
- return [
1633
- ...projectShellFiles(vars, preset, {
1634
- readme: renderReadme(vars, preset),
1635
- landing: initLanding(vars)
1636
- }),
1637
- { path: "xano/index.ts", content: renderXanoIndex(vars) },
1638
- { path: "xano/EXAMPLE.md", content: renderXanoExampleMd(vars) }
1639
- ];
1640
- }
1641
- async function runInitCommand(args) {
1642
- const targetArg = args.positionals[0] ?? ".";
1643
- const targetDir = resolve(targetArg);
1644
- const appName = sanitizeAppName(args.name ?? basename(targetDir));
1645
- const preset = await resolveFrontendPreset(args.framework);
1646
- const presets = await resolveAiPresets(args.ai);
1647
- const vars = { appName, sdkVersion: readVersion() };
1648
- step(`Scaffolding ${style.bold(appName)} (${preset.label}) in ${targetDir}`);
1649
- const result = await scaffoldProject({
1650
- targetDir,
1651
- files: buildFileSet(vars, preset),
1652
- presets,
1653
- appName,
1654
- force: args.force,
1655
- noInstall: args.noInstall,
1656
- // An `init` project's `xano/` is hand-authored, never machine-written: it is
1657
- // not refreshable, and `--force` must not clear it.
1658
- regenerable: false,
1659
- sdkVersion: readVersion(),
1660
- frontend: preset
1661
- });
1662
- if (presets.length > 0) {
1663
- info(`AI instructions: ${presets.map(presetFilePath).join(", ")}`);
1664
- }
1665
- blank();
1666
- success("Project ready.");
1667
- const cdHint = targetDir === process.cwd() ? "" : ` cd ${targetArg}
1668
- `;
1669
- detail(
1670
- `Next steps:
1671
- ` + cdHint + (result.install === "installed" ? `` : ` npm install
1672
- `) + ` npm run dev # run the frontend
1673
- xanots login # authenticate with Xano
1674
- npm run xano:deploy # build the frontend, then deploy \u2192 live ephemeral URL
1675
-
1676
- The first export or deploy writes xano/xano.lock \u2014 commit it. It pins each
1677
- object's identity, so renaming one later renames it instead of deleting and
1678
- recreating it. See "xano.lock \u2014 commit it" in the README.`
1679
- );
1680
- }
1681
-
1682
1698
  export {
1699
+ renderPackageJson,
1700
+ renderTsconfig,
1701
+ renderViteConfig,
1702
+ renderIndexHtml,
1703
+ renderGitignore,
1704
+ renderEnvExample,
1705
+ renderReadme,
1706
+ renderXanoIndex,
1707
+ renderXanoExampleMd,
1683
1708
  describeOrigin,
1684
1709
  renderCodegenMarker,
1685
1710
  renderCodegenReadme,
1711
+ initLanding,
1686
1712
  codegenLanding,
1687
- resolveFrontendPreset,
1688
- sanitizeAppName,
1689
- projectShellFiles,
1690
- runInitCommand
1713
+ renderIndexCss,
1714
+ renderThemeModule,
1715
+ renderCnUtil,
1716
+ renderApiTs,
1717
+ FRAMEWORKS,
1718
+ DEFAULT_FRAMEWORK,
1719
+ findFrontendPreset,
1720
+ allFrontendPresets,
1721
+ resolveFrameworkFlag
1691
1722
  };
1692
- //# sourceMappingURL=chunk-ODNMVJ6H.js.map
1723
+ //# sourceMappingURL=chunk-YZ4GU6F5.js.map