@helloleo/plugins 0.1.13 → 0.1.14

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.
package/dist/index.js CHANGED
@@ -12675,9 +12675,8 @@ function indexCss(primaryColor, fontFamily, borderRadius) {
12675
12675
  * --warning each have a paired -foreground for text on that surface.
12676
12676
  * - --radius drives lg/md/sm via calc() in tailwind.config.js. Edit here,
12677
12677
  * not in component classes.
12678
- * - --font-sans for body, --font-heading for headings. The webfont is loaded
12679
- * via a Google Fonts <link> \u2014 in index.html (SPA) or the root route head
12680
- * (SSR). Keep the family here in sync with that <link>.
12678
+ * - --font-sans for body, --font-heading for headings. Both load from the
12679
+ * Google Fonts <link> in index.html.
12681
12680
  * - .dark variants mirror :root one-for-one. When adding a token, add it
12682
12681
  * to BOTH blocks or dark mode breaks.
12683
12682
  *
@@ -12764,7 +12763,10 @@ export function cn(...inputs: ClassValue[]) {
12764
12763
  return twMerge(clsx(inputs))
12765
12764
  }`;
12766
12765
  }
12767
- function proxyTs() {
12766
+ function projectUuidFromDir(dir) {
12767
+ return path2.basename(dir);
12768
+ }
12769
+ function proxyTs(projectUuid) {
12768
12770
  return `/*
12769
12771
  * HelloLeo Integration Proxy -- runtime helper for connected integrations.
12770
12772
  *
@@ -12806,10 +12808,8 @@ function proxyTs() {
12806
12808
  * from a Worker. Never embed secrets in frontend code.
12807
12809
  */
12808
12810
 
12809
- const PROJECT_UUID = import.meta.env.VITE_PROJECT_UUID ?? '';
12810
12811
  export const INTEGRATION_PROXY_URL =
12811
- import.meta.env.VITE_INTEGRATION_PROXY_URL ??
12812
- \`https://api.app.helloleo.dev/api/integration-proxy/\${PROJECT_UUID}\`;
12812
+ 'https://api.app.helloleo.dev/api/integration-proxy/${projectUuid}';
12813
12813
 
12814
12814
  export interface ProxyRequest {
12815
12815
  integration: string;
@@ -12919,7 +12919,7 @@ async function writeBaseScaffold(opts, dir) {
12919
12919
  ["postcss.config.js", postcssConfig()],
12920
12920
  [path2.join("src", "index.css"), indexCss(primary, font, radius)],
12921
12921
  [path2.join("src", "lib", "utils.ts"), utilsTs()],
12922
- [path2.join("src", "lib", "proxy.ts"), proxyTs()],
12922
+ [path2.join("src", "lib", "proxy.ts"), proxyTs(projectUuidFromDir(dir))],
12923
12923
  [path2.join("src", "main.tsx"), mainTsx()]
12924
12924
  ];
12925
12925
  const created = [];
@@ -12929,779 +12929,34 @@ async function writeBaseScaffold(opts, dir) {
12929
12929
  }
12930
12930
  return created;
12931
12931
  }
12932
- var REACT = "^19.0.0";
12933
- var VITE = "^6.0.5";
12934
- var TS = "^5.6.3";
12935
- var TANSTACK = "1.131.50";
12936
- var BIOME = "2.4.8";
12937
- function tsconfig() {
12938
- return JSON.stringify({
12939
- compilerOptions: {
12940
- target: "ES2022",
12941
- module: "ESNext",
12942
- moduleResolution: "Bundler",
12943
- lib: ["ES2022", "DOM", "DOM.Iterable"],
12944
- jsx: "react-jsx",
12945
- strict: true,
12946
- noUncheckedIndexedAccess: true,
12947
- noUnusedLocals: true,
12948
- noUnusedParameters: true,
12949
- verbatimModuleSyntax: true,
12950
- skipLibCheck: true,
12951
- noEmit: true,
12952
- baseUrl: ".",
12953
- paths: { "@/*": ["./src/*"] },
12954
- types: ["vite/client"]
12955
- },
12956
- include: ["src", "*.config.ts"]
12957
- }, null, 2);
12958
- }
12959
- function biomeJson() {
12960
- return JSON.stringify({
12961
- $schema: `https://biomejs.dev/schemas/${BIOME}/schema.json`,
12962
- vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
12963
- files: { ignoreUnknown: true },
12964
- formatter: { enabled: true, indentStyle: "tab", lineWidth: 80 },
12965
- linter: { enabled: true, rules: { recommended: true } },
12966
- javascript: { formatter: { quoteStyle: "double" } },
12967
- assist: { actions: { source: { organizeImports: "on" } } }
12968
- }, null, 2);
12969
- }
12970
- function componentsJson() {
12971
- return JSON.stringify({
12972
- $schema: "https://ui.shadcn.com/schema.json",
12973
- style: "new-york",
12974
- rsc: false,
12975
- tsx: true,
12976
- tailwind: {
12977
- config: "tailwind.config.js",
12978
- css: "src/styles/app.css",
12979
- baseColor: "slate",
12980
- cssVariables: true,
12981
- prefix: ""
12982
- },
12983
- aliases: {
12984
- components: "@/components",
12985
- utils: "@/lib/utils",
12986
- ui: "@/components/ui",
12987
- lib: "@/lib",
12988
- hooks: "@/hooks"
12989
- },
12990
- iconLibrary: "lucide"
12991
- }, null, 2);
12992
- }
12993
- function uiButton() {
12994
- return `import * as React from "react"
12995
- import { Slot } from "@radix-ui/react-slot"
12996
- import { cva, type VariantProps } from "class-variance-authority"
12997
- import { cn } from "@/lib/utils"
12998
-
12999
- const buttonVariants = cva(
13000
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0",
13001
- {
13002
- variants: {
13003
- variant: {
13004
- default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
13005
- destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
13006
- outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
13007
- secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
13008
- ghost: "hover:bg-accent hover:text-accent-foreground",
13009
- link: "text-primary underline-offset-4 hover:underline",
13010
- },
13011
- size: {
13012
- default: "h-9 px-4 py-2",
13013
- sm: "h-8 rounded-md px-3 text-xs",
13014
- lg: "h-10 rounded-md px-8",
13015
- icon: "h-9 w-9",
13016
- },
13017
- },
13018
- defaultVariants: { variant: "default", size: "default" },
13019
- },
13020
- )
13021
-
13022
- export interface ButtonProps
13023
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
13024
- VariantProps<typeof buttonVariants> {
13025
- asChild?: boolean
13026
- }
13027
-
13028
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
13029
- ({ className, variant, size, asChild = false, ...props }, ref) => {
13030
- const Comp = asChild ? Slot : "button"
13031
- return (
13032
- <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
13033
- )
13034
- },
13035
- )
13036
- Button.displayName = "Button"
13037
-
13038
- export { Button, buttonVariants }
13039
- `;
13040
- }
13041
- function uiCard() {
13042
- return `import * as React from "react"
13043
- import { cn } from "@/lib/utils"
13044
-
13045
- const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
13046
- ({ className, ...props }, ref) => (
13047
- <div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow", className)} {...props} />
13048
- ),
13049
- )
13050
- Card.displayName = "Card"
13051
-
13052
- const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
13053
- ({ className, ...props }, ref) => (
13054
- <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
13055
- ),
13056
- )
13057
- CardHeader.displayName = "CardHeader"
13058
-
13059
- const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
13060
- ({ className, ...props }, ref) => (
13061
- <div ref={ref} className={cn("font-heading font-semibold leading-none tracking-tight", className)} {...props} />
13062
- ),
13063
- )
13064
- CardTitle.displayName = "CardTitle"
13065
-
13066
- const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
13067
- ({ className, ...props }, ref) => (
13068
- <div ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
13069
- ),
13070
- )
13071
- CardDescription.displayName = "CardDescription"
13072
-
13073
- const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
13074
- ({ className, ...props }, ref) => (
13075
- <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
13076
- ),
13077
- )
13078
- CardContent.displayName = "CardContent"
13079
-
13080
- export { Card, CardHeader, CardTitle, CardDescription, CardContent }
13081
- `;
13082
- }
13083
- function uiInput() {
13084
- return `import * as React from "react"
13085
- import { cn } from "@/lib/utils"
13086
-
13087
- const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
13088
- ({ className, type, ...props }, ref) => (
13089
- <input
13090
- type={type}
13091
- ref={ref}
13092
- className={cn(
13093
- "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
13094
- className,
13095
- )}
13096
- {...props}
13097
- />
13098
- ),
13099
- )
13100
- Input.displayName = "Input"
13101
-
13102
- export { Input }
13103
- `;
13104
- }
13105
- function uiTextarea() {
13106
- return `import * as React from "react"
13107
- import { cn } from "@/lib/utils"
13108
-
13109
- const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttributes<HTMLTextAreaElement>>(
13110
- ({ className, ...props }, ref) => (
13111
- <textarea
13112
- ref={ref}
13113
- className={cn(
13114
- "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
13115
- className,
13116
- )}
13117
- {...props}
13118
- />
13119
- ),
13120
- )
13121
- Textarea.displayName = "Textarea"
13122
-
13123
- export { Textarea }
13124
- `;
13125
- }
13126
- function uiLabel() {
13127
- return `import * as React from "react"
13128
- import { cn } from "@/lib/utils"
13129
-
13130
- const Label = React.forwardRef<HTMLLabelElement, React.LabelHTMLAttributes<HTMLLabelElement>>(
13131
- ({ className, ...props }, ref) => (
13132
- <label
13133
- ref={ref}
13134
- className={cn(
13135
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
13136
- className,
13137
- )}
13138
- {...props}
13139
- />
13140
- ),
13141
- )
13142
- Label.displayName = "Label"
13143
-
13144
- export { Label }
13145
- `;
13146
- }
13147
- function uiBadge() {
13148
- return `import * as React from "react"
13149
- import { cva, type VariantProps } from "class-variance-authority"
13150
- import { cn } from "@/lib/utils"
13151
-
13152
- const badgeVariants = cva(
13153
- "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
13154
- {
13155
- variants: {
13156
- variant: {
13157
- default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
13158
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
13159
- destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
13160
- outline: "text-foreground",
13161
- },
13162
- },
13163
- defaultVariants: { variant: "default" },
13164
- },
13165
- )
13166
-
13167
- export interface BadgeProps
13168
- extends React.HTMLAttributes<HTMLDivElement>,
13169
- VariantProps<typeof badgeVariants> {}
13170
-
13171
- function Badge({ className, variant, ...props }: BadgeProps) {
13172
- return <div className={cn(badgeVariants({ variant }), className)} {...props} />
13173
- }
13174
-
13175
- export { Badge, badgeVariants }
13176
- `;
13177
- }
13178
- function spaPackageJson(name) {
13179
- return JSON.stringify({
13180
- name,
13181
- private: true,
13182
- version: "0.0.0",
13183
- type: "module",
13184
- scripts: {
13185
- dev: "vite dev --host",
13186
- build: "vite build",
13187
- preview: "vite preview",
13188
- lint: "biome check --write ."
13189
- },
13190
- dependencies: {
13191
- react: REACT,
13192
- "react-dom": REACT,
13193
- "@tanstack/react-router": TANSTACK,
13194
- "class-variance-authority": "^0.7.1",
13195
- clsx: "^2.1.1",
13196
- "tailwind-merge": "^2.6.0",
13197
- "lucide-react": "^0.468.0",
13198
- "@radix-ui/react-slot": "^1.1.1"
13199
- },
13200
- devDependencies: {
13201
- "@tanstack/router-plugin": TANSTACK,
13202
- "@types/react": "^19.0.0",
13203
- "@types/react-dom": "^19.0.0",
13204
- "@vitejs/plugin-react": "^4.3.4",
13205
- "@biomejs/biome": BIOME,
13206
- autoprefixer: "^10.4.20",
13207
- postcss: "^8.4.49",
13208
- tailwindcss: "^3.4.17",
13209
- typescript: TS,
13210
- vite: VITE
13211
- }
13212
- }, null, 2);
13213
- }
13214
- function spaViteConfig() {
13215
- return `import { defineConfig } from "vite"
13216
- import react from "@vitejs/plugin-react"
13217
- import { TanStackRouterVite } from "@tanstack/router-plugin/vite"
13218
- import path from "node:path"
13219
-
13220
- export default defineConfig({
13221
- // File-based routing: routes live in src/routes, routeTree.gen.ts is generated.
13222
- plugins: [TanStackRouterVite({ target: "react", autoCodeSplitting: true }), react()],
13223
- // allowedHosts:true is REQUIRED for the HelloLeo preview sandbox (proxied host).
13224
- server: { allowedHosts: true },
13225
- resolve: { alias: { "@": path.resolve(__dirname, "./src") } },
13226
- })
13227
- `;
13228
- }
13229
- function spaIndexHtml(fontFamily) {
13230
- const fontParam = fontFamily.replace(/\s+/g, "+");
13231
- return `<!DOCTYPE html>
13232
- <html lang="en">
13233
- <head>
13234
- <meta charset="UTF-8" />
13235
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
13236
- <link rel="preconnect" href="https://fonts.googleapis.com">
13237
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13238
- <link href="https://fonts.googleapis.com/css2?family=${fontParam}:wght@400;500;600;700&display=swap" rel="stylesheet">
13239
- <title>App</title>
13240
- </head>
13241
- <body>
13242
- <div id="root"></div>
13243
- <script type="module" src="/src/main.tsx"></script>
13244
- </body>
13245
- </html>
13246
- `;
13247
- }
13248
- function spaMainTsx() {
13249
- return `import React from "react"
13250
- import ReactDOM from "react-dom/client"
13251
- import { RouterProvider, createRouter } from "@tanstack/react-router"
13252
- import { routeTree } from "./routeTree.gen"
13253
- import "./styles/app.css"
13254
-
13255
- const router = createRouter({ routeTree, defaultPreload: "intent", scrollRestoration: true })
13256
-
13257
- declare module "@tanstack/react-router" {
13258
- interface Register {
13259
- router: typeof router
13260
- }
13261
- }
13262
-
13263
- ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
13264
- <React.StrictMode>
13265
- <RouterProvider router={router} />
13266
- </React.StrictMode>,
13267
- )
13268
- `;
13269
- }
13270
- function spaRootRoute() {
13271
- return `import { Outlet, createRootRoute } from "@tanstack/react-router"
13272
-
13273
- export const Route = createRootRoute({
13274
- errorComponent: ({ error }) => (
13275
- <main className="mx-auto flex min-h-screen max-w-2xl flex-col items-center justify-center gap-3 p-8">
13276
- <h1 className="text-2xl font-heading font-semibold text-destructive">Something went wrong</h1>
13277
- <pre className="max-w-full overflow-auto rounded-md bg-muted p-4 text-left text-xs text-muted-foreground">{error instanceof Error ? error.message : String(error)}</pre>
13278
- </main>
13279
- ),
13280
- notFoundComponent: () => (
13281
- <main className="mx-auto flex min-h-screen max-w-2xl flex-col items-center justify-center gap-2 p-8">
13282
- <h1 className="text-2xl font-heading font-semibold">404</h1>
13283
- <p className="text-muted-foreground">This page could not be found.</p>
13284
- </main>
13285
- ),
13286
- component: () => (
13287
- <div className="min-h-screen bg-background text-foreground">
13288
- <Outlet />
13289
- </div>
13290
- ),
13291
- })
13292
- `;
13293
- }
13294
- function spaIndexRoute() {
13295
- return `import { createFileRoute } from "@tanstack/react-router"
13296
- import { Button } from "@/components/ui/button"
13297
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
13298
-
13299
- export const Route = createFileRoute("/")({ component: Home })
13300
-
13301
- function Home() {
13302
- return (
13303
- <main className="mx-auto flex min-h-screen max-w-2xl flex-col items-center justify-center gap-6 p-8">
13304
- <Card className="w-full">
13305
- <CardHeader>
13306
- <CardTitle className="text-2xl">Welcome to your app</CardTitle>
13307
- <CardDescription>TanStack Router SPA \xB7 Tailwind \xB7 shadcn/ui \xB7 Biome</CardDescription>
13308
- </CardHeader>
13309
- <CardContent className="flex gap-3">
13310
- <Button>Get started</Button>
13311
- <Button variant="outline">Learn more</Button>
13312
- </CardContent>
13313
- </Card>
13314
- </main>
13315
- )
13316
- }
13317
- `;
13318
- }
13319
- function ssrPackageJson(name) {
13320
- return JSON.stringify({
13321
- name,
13322
- private: true,
13323
- version: "0.0.0",
13324
- type: "module",
13325
- scripts: {
13326
- predev: "drizzle-kit push --force",
13327
- dev: "vite dev --host",
13328
- build: "vite build",
13329
- prepreview: "drizzle-kit push --force",
13330
- preview: "node .output/server/index.mjs",
13331
- start: "node .output/server/index.mjs",
13332
- "db:generate": "drizzle-kit generate",
13333
- "db:push": "drizzle-kit push --force",
13334
- deploy: "wrangler deploy",
13335
- lint: "biome check --write ."
13336
- },
13337
- dependencies: {
13338
- react: REACT,
13339
- "react-dom": REACT,
13340
- "@tanstack/react-router": TANSTACK,
13341
- "@tanstack/react-start": TANSTACK,
13342
- "drizzle-orm": "^0.36.4",
13343
- "@libsql/client": "^0.14.0",
13344
- "class-variance-authority": "^0.7.1",
13345
- clsx: "^2.1.1",
13346
- "tailwind-merge": "^2.6.0",
13347
- "lucide-react": "^0.468.0",
13348
- "@radix-ui/react-slot": "^1.1.1"
13349
- },
13350
- devDependencies: {
13351
- wrangler: "^4.20.0",
13352
- "@cloudflare/workers-types": "^4.20250601.0",
13353
- "drizzle-kit": "^0.28.1",
13354
- "@types/node": "^22.0.0",
13355
- "@types/react": "^19.0.0",
13356
- "@types/react-dom": "^19.0.0",
13357
- "@vitejs/plugin-react": "^4.3.4",
13358
- "@biomejs/biome": BIOME,
13359
- autoprefixer: "^10.4.20",
13360
- postcss: "^8.4.49",
13361
- tailwindcss: "^3.4.17",
13362
- typescript: TS,
13363
- vite: VITE
13364
- }
13365
- }, null, 2);
13366
- }
13367
- function ssrViteConfig() {
13368
- return `import { defineConfig } from "vite"
13369
- import react from "@vitejs/plugin-react"
13370
- import { tanstackStart } from "@tanstack/react-start/plugin/vite"
13371
- import path from "node:path"
13372
-
13373
- // TanStack Start default (Node server) target -> dist/server/server.js + dist/client.
13374
- // The CF Sandbox container runs that server (PORT-aware), proven by the POC.
13375
- export default defineConfig({
13376
- plugins: [tanstackStart({ customViteReactPlugin: true }), react()],
13377
- // allowedHosts:true is REQUIRED for the HelloLeo preview sandbox (proxied host).
13378
- server: { allowedHosts: true },
13379
- // Pre-bundle shared deps at boot so vite doesn't re-optimize + restart the dev
13380
- // server mid-request (which 500s the first SSR load with "transport disconnected").
13381
- optimizeDeps: {
13382
- include: ["clsx", "tailwind-merge", "class-variance-authority", "lucide-react"],
13383
- },
13384
- resolve: { alias: { "@": path.resolve(__dirname, "./src") } },
13385
- // __WORKERD__ tells src/db which runtime it's building for: true only for the
13386
- // prod cloudflare-module build (NITRO_PRESET set by the publish flow), false
13387
- // for dev and the Node preview build. Lets getDb dead-code-eliminate the wrong
13388
- // DB driver per target.
13389
- define: {
13390
- __WORKERD__: JSON.stringify(process.env.NITRO_PRESET === "cloudflare-module"),
13391
- },
13392
- // "cloudflare:workers" is a workerd-only module (src/db reads the D1 binding
13393
- // from it in prod). vite's SSR pass can't resolve it \u2014 externalize so the
13394
- // workerd build keeps it as an import; on other targets it's DCE'd away.
13395
- build: { rollupOptions: { external: ["cloudflare:workers"] } },
13396
- })
13397
- `;
13398
- }
13399
- function ssrWranglerJsonc(name) {
13400
- return `{
13401
- // PROD target (Workers for Platforms). Dev/preview runs the Node server in the
13402
- // CF Sandbox container and does NOT use this file.
13403
- //
13404
- // NOTE: HelloLeo's publish flow REGENERATES this file at deploy time \u2014 it
13405
- // auto-creates the D1 database, fills in database_id, and pins its own
13406
- // compatibility_date. This file is the local-dev / manual \`wrangler deploy\`
13407
- // copy; for a manual deploy, run \`wrangler d1 create ${name}\` and paste the
13408
- // id below first. compatibility_date is kept at/under the runtime baseline so
13409
- // \`wrangler dev\` works on the pinned workerd binary.
13410
- "$schema": "node_modules/wrangler/config-schema.json",
13411
- "name": "${name}",
13412
- "compatibility_date": "2026-04-01",
13413
- "compatibility_flags": ["nodejs_compat"],
13414
- "observability": { "enabled": true },
13415
- "assets": { "directory": ".output/public", "binding": "ASSETS" },
13416
- "d1_databases": [
13417
- { "binding": "DB", "database_name": "${name}", "database_id": "" }
13418
- ]
13419
- }
13420
- `;
13421
- }
13422
- function ssrRouter() {
13423
- return `import { createRouter as createTanStackRouter } from "@tanstack/react-router"
13424
- import { routeTree } from "./routeTree.gen"
13425
-
13426
- export function createRouter() {
13427
- return createTanStackRouter({
13428
- routeTree,
13429
- defaultPreload: "intent",
13430
- scrollRestoration: true,
13431
- })
13432
- }
13433
-
13434
- declare module "@tanstack/react-router" {
13435
- interface Register {
13436
- router: ReturnType<typeof createRouter>
13437
- }
13438
- }
13439
- `;
13440
- }
13441
- function ssrRootRoute(fontFamily) {
13442
- const fontParam = fontFamily.replace(/\s+/g, "+");
13443
- return `import { Outlet, createRootRoute, HeadContent, Scripts } from "@tanstack/react-router"
13444
- import appCss from "../styles/app.css?url"
13445
-
13446
- export const Route = createRootRoute({
13447
- head: () => ({
13448
- meta: [
13449
- { charSet: "utf-8" },
13450
- { name: "viewport", content: "width=device-width, initial-scale=1" },
13451
- { title: "App" },
13452
- ],
13453
- // The design-system font is loaded here (SSR has no index.html). Keep the
13454
- // family in sync with --font-sans / --font-heading in src/styles/app.css.
13455
- links: [
13456
- { rel: "preconnect", href: "https://fonts.googleapis.com" },
13457
- { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
13458
- { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=${fontParam}:wght@400;500;600;700&display=swap" },
13459
- { rel: "stylesheet", href: appCss },
13460
- ],
13461
- }),
13462
- errorComponent: ({ error }) => (
13463
- <main className="mx-auto flex min-h-screen max-w-2xl flex-col items-center justify-center gap-3 p-8">
13464
- <h1 className="text-2xl font-heading font-semibold text-destructive">Something went wrong</h1>
13465
- <pre className="max-w-full overflow-auto rounded-md bg-muted p-4 text-left text-xs text-muted-foreground">{error instanceof Error ? error.message : String(error)}</pre>
13466
- </main>
13467
- ),
13468
- notFoundComponent: () => (
13469
- <main className="mx-auto flex min-h-screen max-w-2xl flex-col items-center justify-center gap-2 p-8">
13470
- <h1 className="text-2xl font-heading font-semibold">404</h1>
13471
- <p className="text-muted-foreground">This page could not be found.</p>
13472
- </main>
13473
- ),
13474
- component: RootComponent,
13475
- })
13476
-
13477
- function RootComponent() {
13478
- return (
13479
- <html lang="en">
13480
- <head>
13481
- <HeadContent />
13482
- </head>
13483
- <body className="min-h-screen bg-background text-foreground font-sans">
13484
- <Outlet />
13485
- <Scripts />
13486
- </body>
13487
- </html>
13488
- )
13489
- }
13490
- `;
13491
- }
13492
- function ssrIndexRoute() {
13493
- return `import { createFileRoute } from "@tanstack/react-router"
13494
- import { createServerFn } from "@tanstack/react-start"
13495
- import { Button } from "@/components/ui/button"
13496
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
13497
-
13498
- // Server functions (TanStack Start 1.131.x API \u2014 IMPORTANT, do not invent
13499
- // methods):
13500
- // - No input: createServerFn({ method: "GET" }).handler(async () => {...})
13501
- // - With input: createServerFn({ method: "POST" })
13502
- // .validator((d: { title: string }) => d) // NOT .inputType()
13503
- // .handler(async ({ data }) => { data.title }) // input is { data }
13504
- // Call it as: addTask({ data: { title } }) // NOT addTask({ title })
13505
- // - DB access: \`const db = await getDb()\` (async). Resolves D1 in prod,
13506
- // libsql in dev. Safe in server fns AND route loaders \u2014 just
13507
- // never at module top. Prefer server functions for mutations.
13508
- // Example DB-backed server fn (dev=libsql, prod=D1, same code):
13509
- // const getItems = createServerFn({ method: "GET" }).handler(async () => {
13510
- // const db = await getDb()
13511
- // return await db.select().from(items)
13512
- // })
13513
- const getServerInfo = createServerFn({ method: "GET" }).handler(async () => {
13514
- return { renderedAt: new Date().toISOString() }
13515
- })
13516
-
13517
- export const Route = createFileRoute("/")({
13518
- loader: () => getServerInfo(),
13519
- component: Home,
13520
- })
13521
-
13522
- function Home() {
13523
- const info = Route.useLoaderData()
13524
- return (
13525
- <main className="mx-auto flex min-h-screen max-w-2xl flex-col items-center justify-center gap-6 p-8">
13526
- <Card className="w-full">
13527
- <CardHeader>
13528
- <CardTitle className="text-2xl">Welcome to your app</CardTitle>
13529
- <CardDescription>TanStack Start SSR (workerd) \xB7 Tailwind \xB7 shadcn/ui \xB7 Biome \xB7 Drizzle/D1</CardDescription>
13530
- </CardHeader>
13531
- <CardContent className="flex flex-col gap-3">
13532
- <p className="text-sm text-muted-foreground">Server-rendered at {info.renderedAt}</p>
13533
- <div className="flex gap-3">
13534
- <Button>Get started</Button>
13535
- <Button variant="outline">Learn more</Button>
13536
- </div>
13537
- </CardContent>
13538
- </Card>
13539
- </main>
13540
- )
13541
- }
13542
- `;
13543
- }
13544
- function ssrDbSchema() {
13545
- return `import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"
13546
-
13547
- // Example table. dev uses local D1 (miniflare), prod uses the D1 binding "DB".
13548
- export const items = sqliteTable("items", {
13549
- id: integer("id").primaryKey({ autoIncrement: true }),
13550
- title: text("title").notNull(),
13551
- createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => new Date()),
13552
- })
13553
- `;
13554
- }
13555
- function ssrDbIndex() {
13556
- return `/// <reference types="@cloudflare/workers-types" />
13557
- import { drizzle as drizzleD1 } from "drizzle-orm/d1"
13558
- import * as schema from "./schema"
13559
-
13560
- // Resolve the DB for the CURRENT request. ASYNC \u2014 always \`await getDb()\`.
13561
- // Works from ANYWHERE on the server (route loaders, server functions,
13562
- // beforeLoad) because it does NOT depend on AsyncLocalStorage / getEvent (which
13563
- // is only populated inside server functions under workerd \u2014 calling getDb in a
13564
- // loader the old way threw "No HTTPEvent found in AsyncLocalStorage").
13565
- // - PROD (workerd): the D1 binding read straight from the per-request env.
13566
- // - DEV (node SSR): a local libsql file, imported lazily and behind the DEV
13567
- // guard so neither @libsql/client nor node:stream is ever bundled into the
13568
- // workerd build (which would break it).
13569
- // Same sqlite schema both ways.
13570
- // __WORKERD__ is replaced at build time by vite (see vite.config.ts): true only
13571
- // for the prod cloudflare-module build, false for dev + the Node preview build.
13572
- // Using it (not import.meta.env.DEV) is what lets the Node preview \u2014 also a
13573
- // production build \u2014 still take the libsql path. It's a literal, so the unused
13574
- // branch (and its driver) is dead-code-eliminated from each bundle.
13575
- declare const __WORKERD__: boolean
13576
-
13577
- export async function getDb() {
13578
- if (__WORKERD__) {
13579
- const { env } = await import("cloudflare:workers")
13580
- return drizzleD1((env as unknown as { DB: D1Database }).DB, { schema })
13581
- }
13582
- const { drizzle } = await import("drizzle-orm/libsql")
13583
- const { createClient } = await import("@libsql/client")
13584
- return drizzle(
13585
- createClient({ url: process.env.DATABASE_URL ?? "file:./local.db" }),
13586
- { schema },
13587
- )
13588
- }
13589
- `;
13590
- }
13591
- function ssrDrizzleConfig() {
13592
- return `import { defineConfig } from "drizzle-kit"
13593
-
13594
- // Dev/preview: push the schema straight into the local libsql file (no D1 creds
13595
- // needed) \u2014 this is what \`predev\` runs so tables exist before the dev server.
13596
- // Prod (D1) uses generated migrations applied via wrangler, not this push.
13597
- export default defineConfig({
13598
- dialect: "sqlite",
13599
- schema: "./src/db/schema.ts",
13600
- out: "./drizzle",
13601
- dbCredentials: { url: process.env.DATABASE_URL ?? "file:./local.db" },
13602
- })
13603
- `;
13604
- }
13605
- function gitignore(mode) {
13606
- const base = `node_modules
13607
- dist
13608
- .output
13609
- .nitro
13610
- .tanstack
13611
- .vite
13612
- local.db
13613
- *.local
13614
- .DS_Store
13615
-
13616
- # Generated by the TanStack Router/Start vite plugin
13617
- src/routeTree.gen.ts
13618
- `;
13619
- return mode === "ssr" ? `${base}.wrangler
13620
- .dev.vars
13621
- ` : base;
13622
- }
13623
- async function writeAppScaffold(opts, dir) {
13624
- const name = opts.name ?? "leo-app";
13625
- const mode = opts.mode ?? "spa";
13626
- const primary = normalizePrimaryColor(opts.primaryColor ?? "221.2 83.2% 53.3%");
13627
- const font = opts.fontFamily ?? "Inter";
13628
- const radius = opts.borderRadius ?? "0.5rem";
13629
- await mkdir(path2.join(dir, "src", "lib"), { recursive: true });
13630
- await mkdir(path2.join(dir, "src", "components", "ui"), { recursive: true });
13631
- await mkdir(path2.join(dir, "src", "routes"), { recursive: true });
13632
- await mkdir(path2.join(dir, "src", "styles"), { recursive: true });
13633
- const files = [
13634
- ["tsconfig.json", tsconfig()],
13635
- ["biome.json", biomeJson()],
13636
- ["components.json", componentsJson()],
13637
- ["tailwind.config.js", tailwindConfig()],
13638
- ["postcss.config.js", postcssConfig()],
13639
- [".gitignore", gitignore(mode)],
13640
- [path2.join("src", "styles", "app.css"), indexCss(primary, font, radius)],
13641
- [path2.join("src", "lib", "utils.ts"), utilsTs()],
13642
- [path2.join("src", "lib", "proxy.ts"), proxyTs()],
13643
- [path2.join("src", "components", "ui", "button.tsx"), uiButton()],
13644
- [path2.join("src", "components", "ui", "card.tsx"), uiCard()],
13645
- [path2.join("src", "components", "ui", "input.tsx"), uiInput()],
13646
- [path2.join("src", "components", "ui", "textarea.tsx"), uiTextarea()],
13647
- [path2.join("src", "components", "ui", "label.tsx"), uiLabel()],
13648
- [path2.join("src", "components", "ui", "badge.tsx"), uiBadge()]
13649
- ];
13650
- if (mode === "spa") {
13651
- files.push(["package.json", spaPackageJson(name)], ["vite.config.ts", spaViteConfig()], ["index.html", spaIndexHtml(font)], [path2.join("src", "main.tsx"), spaMainTsx()], [path2.join("src", "routes", "__root.tsx"), spaRootRoute()], [path2.join("src", "routes", "index.tsx"), spaIndexRoute()]);
13652
- } else {
13653
- await mkdir(path2.join(dir, "src", "db"), { recursive: true });
13654
- files.push(["package.json", ssrPackageJson(name)], ["vite.config.ts", ssrViteConfig()], ["wrangler.jsonc", ssrWranglerJsonc(name)], ["drizzle.config.ts", ssrDrizzleConfig()], [path2.join("src", "router.tsx"), ssrRouter()], [path2.join("src", "routes", "__root.tsx"), ssrRootRoute(font)], [path2.join("src", "routes", "index.tsx"), ssrIndexRoute()], [path2.join("src", "db", "schema.ts"), ssrDbSchema()], [path2.join("src", "db", "index.ts"), ssrDbIndex()]);
13655
- }
13656
- const created = [];
13657
- for (const [filePath, content] of files) {
13658
- await Bun.write(path2.join(dir, filePath), content);
13659
- created.push(filePath);
13660
- }
13661
- return created.sort();
13662
- }
13663
- var scaffoldApp = tool({
13664
- description: "Scaffold a modern TanStack app with the HelloLeo design system, ShadcnUI, " + "Biome and strict TypeScript. Two modes: mode='spa' (TanStack Router, " + "client-only -- use for static frontends / dashboards) or mode='ssr' " + "(TanStack Start on Cloudflare workerd -- use when the project needs a " + "backend, SSR, server functions, or a database via D1/Drizzle). " + "Creates package.json, vite.config.ts, tsconfig.json (strict), biome.json, " + "components.json, tailwind.config.js, postcss.config.js, " + "src/styles/app.css (design tokens, light+dark), src/lib/utils.ts + proxy.ts, " + "shadcn primitives in src/components/ui (Button, Card, Input, Textarea, " + "Label, Badge), and TanStack routes in src/routes. " + "Call this FIRST for any new project before writing feature routes/components. " + "For NetSuite projects use `setup_netsuite` instead.",
12932
+ var scaffold = tool({
12933
+ description: "Scaffold a new React + Vite + Tailwind CSS project with the HelloLeo design system. " + "Creates all mandatory boilerplate: package.json, index.html (with Google Fonts), " + "vite.config.js, tailwind.config.js, postcss.config.js, src/index.css (CSS variables " + "light+dark), src/lib/utils.ts (cn helper), src/lib/proxy.ts, and src/main.tsx. " + "Call this FIRST for any new project before writing App.tsx and components. " + "For NetSuite projects (NetSuite MCP in the live list), call `setup_netsuite` " + "INSTEAD -- it bundles base scaffold + OAuth wiring in one call. Do not call both.",
13665
12934
  args: {
13666
- name: tool.schema.string().optional().describe("Project name for package.json (default: leo-app)"),
13667
- mode: tool.schema.enum(["spa", "ssr"]).optional().describe("'spa' = TanStack Router client-only (default). 'ssr' = TanStack Start " + "on Cloudflare workerd with SSR + D1/Drizzle. Pick 'ssr' when the app " + "needs a backend, server functions, auth, or a database."),
13668
- primaryColor: tool.schema.string().optional().describe('Primary brand color as raw HSL triplet, e.g. "221.2 83.2% 53.3%". ' + 'Hex like "#3b82f6" is auto-converted. Default: blue.'),
13669
- fontFamily: tool.schema.string().optional().describe('Google Font family, e.g. "Inter", "Poppins" (default: Inter)'),
13670
- borderRadius: tool.schema.string().optional().describe('Base border radius, e.g. "0.5rem" (default: 0.5rem)')
12935
+ name: tool.schema.string().optional().describe("Project name for package.json (default: leo-project)"),
12936
+ primaryColor: tool.schema.string().optional().describe("Primary brand color. Prefer raw HSL triplet (no hsl() wrapper, no # prefix), " + 'e.g. "221.2 83.2% 53.3%". Hex like "#3b82f6" is also accepted and will ' + "be auto-converted. Default: blue."),
12937
+ fontFamily: tool.schema.string().optional().describe('Google Font family name, e.g. "Inter", "Poppins" (default: Inter)'),
12938
+ borderRadius: tool.schema.string().optional().describe('Base border radius, e.g. "0.5rem", "0.75rem" (default: 0.5rem)')
13671
12939
  },
13672
12940
  async execute(args, context) {
13673
- const mode = args.mode ?? "spa";
13674
- const created = await writeAppScaffold(args, context.directory);
13675
- const next = mode === "ssr" ? [
13676
- "Mode: SSR (TanStack Start, Node server \u2014 runs in the CF Sandbox container).",
13677
- "Dev server: `npm run dev`.",
13678
- "Add routes in src/routes/*.tsx; server logic via createServerFn.",
13679
- "Database: edit src/db/schema.ts, `npm run db:push`, query via `db` from src/db.",
13680
- " (dev = local libsql file; prod = D1, see wrangler.jsonc)."
13681
- ] : [
13682
- "Mode: SPA (TanStack Router, client-only).",
13683
- "Dev server: `npm run dev`.",
13684
- "Add routes in src/routes/*.tsx (file-based; routeTree.gen.ts is generated)."
13685
- ];
12941
+ const created = await writeBaseScaffold(args, context.directory);
13686
12942
  return [
13687
- `Scaffolded ${created.length} files (mode=${mode}):`,
12943
+ `Scaffolded ${created.length} files:`,
13688
12944
  ...created.map((f) => ` - ${f}`),
13689
12945
  "",
13690
- ...next,
13691
- "",
13692
- "Design system: src/styles/app.css tokens (light+dark) drive Tailwind +",
13693
- " shadcn. Never hardcode colors -- use bg-primary, text-foreground, etc.",
13694
- "UI primitives live in src/components/ui (Button, Card scaffolded).",
13695
- " Add more shadcn components consuming the same tokens.",
13696
- "Integrations: callIntegration() from src/lib/proxy.ts.",
13697
- "Lint/format: `npm run lint` (Biome)."
12946
+ "Next: create src/App.tsx with your application code.",
12947
+ "Design system reference: read the header comment in src/index.css",
12948
+ " before adding tokens or styling primitives.",
12949
+ "Integration calls: use callIntegration() from src/lib/proxy.ts.",
12950
+ " Header comment in that file documents slugs, response unwrapping,",
12951
+ " pagination, and frontend-only vs HelloLeo Cloud routing.",
12952
+ "Update package.json with any extra packages you need after scaffolding."
13698
12953
  ].join(`
13699
12954
  `);
13700
12955
  }
13701
12956
  });
13702
12957
  var ScaffoldPlugin = async () => {
13703
12958
  return {
13704
- tool: { scaffold: scaffoldApp }
12959
+ tool: { scaffold }
13705
12960
  };
13706
12961
  };
13707
12962
  // src/tools/setup-netsuite.ts