@helloleo/plugins 0.2.29-beta.0 → 0.2.29

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 (76) hide show
  1. package/dist/index.js +32 -585
  2. package/dist/templates/tanstack-start-2026-09-11/bun.lock +1217 -0
  3. package/dist/templates/tanstack-start-2026-09-11/bunfig.toml +11 -0
  4. package/dist/templates/tanstack-start-2026-09-11/package.json +92 -0
  5. package/package.json +1 -1
  6. package/dist/templates/tanstack-start-2026-07-30/bunfig.toml +0 -5
  7. package/dist/templates/tanstack-start-2026-07-30/package.json +0 -92
  8. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/components.json +0 -0
  9. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/drizzle/meta/_journal.json +0 -0
  10. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/drizzle.config.ts +0 -0
  11. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/error-component.tsx +0 -0
  12. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/accordion.tsx +0 -0
  13. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/alert-dialog.tsx +0 -0
  14. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/alert.tsx +0 -0
  15. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/aspect-ratio.tsx +0 -0
  16. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/avatar.tsx +0 -0
  17. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/badge.tsx +0 -0
  18. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/breadcrumb.tsx +0 -0
  19. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/button.tsx +0 -0
  20. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/calendar.tsx +0 -0
  21. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/card.tsx +0 -0
  22. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/carousel.tsx +0 -0
  23. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/chart.tsx +0 -0
  24. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/checkbox.tsx +0 -0
  25. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/collapsible.tsx +0 -0
  26. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/command.tsx +0 -0
  27. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/context-menu.tsx +0 -0
  28. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/dialog.tsx +0 -0
  29. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/drawer.tsx +0 -0
  30. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/dropdown-menu.tsx +0 -0
  31. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/form.tsx +0 -0
  32. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/hover-card.tsx +0 -0
  33. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/input-otp.tsx +0 -0
  34. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/input.tsx +0 -0
  35. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/label.tsx +0 -0
  36. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/menubar.tsx +0 -0
  37. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/navigation-menu.tsx +0 -0
  38. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/pagination.tsx +0 -0
  39. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/popover.tsx +0 -0
  40. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/progress.tsx +0 -0
  41. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/radio-group.tsx +0 -0
  42. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/resizable.tsx +0 -0
  43. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/scroll-area.tsx +0 -0
  44. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/select.tsx +0 -0
  45. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/separator.tsx +0 -0
  46. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/sheet.tsx +0 -0
  47. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/sidebar.tsx +0 -0
  48. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/skeleton.tsx +0 -0
  49. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/slider.tsx +0 -0
  50. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/sonner.tsx +0 -0
  51. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/switch.tsx +0 -0
  52. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/table.tsx +0 -0
  53. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/tabs.tsx +0 -0
  54. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/textarea.tsx +0 -0
  55. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/toggle-group.tsx +0 -0
  56. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/toggle.tsx +0 -0
  57. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/components/ui/tooltip.tsx +0 -0
  58. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/db/index.ts +0 -0
  59. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/db/schema.ts +0 -0
  60. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/global.d.ts +0 -0
  61. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/hooks/use-mobile.tsx +0 -0
  62. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/lib/cache.ts +0 -0
  63. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/lib/kv.ts +0 -0
  64. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/lib/proxy.ts +0 -0
  65. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/lib/storage.ts +0 -0
  66. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/lib/utils.ts +0 -0
  67. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/routeTree.gen.ts +0 -0
  68. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/router.tsx +0 -0
  69. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/routes/README.md +0 -0
  70. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/routes/__root.tsx +0 -0
  71. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/routes/api/health.ts +0 -0
  72. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/routes/index.tsx +0 -0
  73. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/server.ts +0 -0
  74. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/src/styles.css +0 -0
  75. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/tsconfig.json +0 -0
  76. /package/dist/templates/{tanstack-start-2026-07-30 → tanstack-start-2026-09-11}/vite.config.ts +0 -0
package/dist/index.js CHANGED
@@ -12661,7 +12661,7 @@ import path2 from "path";
12661
12661
  var {Glob } = globalThis.Bun;
12662
12662
  var TEMPLATES_ROOT = path2.join(import.meta.dir, "templates");
12663
12663
  var TOKEN_RE = /\{\{(LEO_[A-Z0-9_]+)\}\}/g;
12664
- var SKIP = /(^|\/)(node_modules|dist|dist-ssr|\.tanstack|\.output|bun\.lock|bun\.lockb|package-lock\.json|yarn\.lock|pnpm-lock\.yaml)(\/|$)/;
12664
+ var SKIP = /(^|\/)(node_modules|dist|dist-ssr|\.tanstack|\.output|bun\.lockb|package-lock\.json|yarn\.lock|pnpm-lock\.yaml)(\/|$)/;
12665
12665
  async function renderTemplate(templateId, vars, destDir) {
12666
12666
  const root = path2.join(TEMPLATES_ROOT, templateId);
12667
12667
  const glob = new Glob("**/*");
@@ -12682,7 +12682,7 @@ async function renderTemplate(templateId, vars, destDir) {
12682
12682
  }
12683
12683
 
12684
12684
  // src/tools/scaffold.ts
12685
- var SCAFFOLD_VERSION = "tanstack-start-2026-07-30";
12685
+ var SCAFFOLD_VERSION = "tanstack-start-2026-09-11";
12686
12686
  function resolveTemplateId() {
12687
12687
  return SCAFFOLD_VERSION;
12688
12688
  }
@@ -12733,7 +12733,6 @@ var scaffold = tool({
12733
12733
  ];
12734
12734
  const nextSteps = [
12735
12735
  "Next: read src/routes/index.tsx and continue with the user's request.",
12736
- "For authentication (password and/or OAuth), call setup_authentication.",
12737
12736
  "For NetSuite projects, call setup_netsuite."
12738
12737
  ];
12739
12738
  return [
@@ -12758,574 +12757,23 @@ var ScaffoldPlugin = async () => {
12758
12757
  tool: { scaffold }
12759
12758
  };
12760
12759
  };
12761
- // src/tools/setup-authentication/index.ts
12762
- import path6 from "path";
12763
-
12764
- // src/tools/setup-authentication/auth-files.ts
12765
- function authLibTs(providers) {
12766
- const hasPassword = providers.includes("password");
12767
- const oauth = providers.filter((p) => p !== "password");
12768
- const envFields = [
12769
- "BETTER_AUTH_SECRET?: string",
12770
- "HELLOLEO_URL?: string",
12771
- ...oauth.flatMap((p) => {
12772
- const prefix = p.toUpperCase();
12773
- return [
12774
- `${prefix}_CLIENT_ID?: string`,
12775
- `${prefix}_CLIENT_SECRET?: string`
12776
- ];
12777
- })
12778
- ].join(`
12779
- `);
12780
- const socialProvidersBlock = oauth.length ? `
12781
- socialProviders: {
12782
- ${oauth.map((p) => {
12783
- const prefix = p.toUpperCase();
12784
- return ` ${p}: { clientId: requireEnv(e.${prefix}_CLIENT_ID, '${prefix}_CLIENT_ID'), clientSecret: requireEnv(e.${prefix}_CLIENT_SECRET, '${prefix}_CLIENT_SECRET') },`;
12785
- }).join(`
12786
- `)}
12787
- },` : "";
12788
- const requireEnvFn = oauth.length ? `
12789
- function requireEnv(value: string | undefined, name: string): string {
12790
- if (!value) {
12791
- throw new Error(\`\${name} is not set. Add it to .env.\`)
12792
- }
12793
- return value
12794
- }
12795
- ` : "";
12796
- return `import { betterAuth } from 'better-auth'
12797
- import { drizzleAdapter } from '@better-auth/drizzle-adapter'
12798
- import { tanstackStartCookies } from 'better-auth/tanstack-start'
12799
- import { db } from '#/db'
12800
- import * as schema from '#/db/schema'
12801
- import { env } from '@helloleo/runtime'
12802
-
12803
- /*
12804
- * buildAuth() only runs from INSIDE a request (see getAuth() below) --
12805
- * never call it at module scope. \`db\` is a lazy Proxy over the D1 binding
12806
- * and env.* Worker secrets don't exist until a request is in flight;
12807
- * touching either while workerd boots the isolate throws.
12808
- */
12809
- ${requireEnvFn}
12810
- function buildAuth() {
12811
- const e = env as {
12812
- ${envFields}
12813
- }
12814
-
12815
- // HELLOLEO_URL is injected by the platform as a bare host, no scheme.
12816
- return betterAuth({
12817
- secret: e.BETTER_AUTH_SECRET,
12818
- baseURL: e.HELLOLEO_URL ? \`https://\${e.HELLOLEO_URL}\` : 'http://localhost:3000',
12819
- database: drizzleAdapter(db, { provider: 'sqlite', schema }),
12820
- emailAndPassword: {
12821
- enabled: ${hasPassword},
12822
- },${socialProvidersBlock}
12823
- // The preview runs inside an iframe on another site, so the session
12824
- // cookie lives in a third-party context: it MUST be SameSite=None +
12825
- // Secure + Partitioned (CHIPS) or Chrome/Safari silently drop it and
12826
- // login never sticks.
12827
- advanced: {
12828
- defaultCookieAttributes: {
12829
- sameSite: 'none',
12830
- secure: true,
12831
- partitioned: true,
12832
- },
12833
- },
12834
- plugins: [tanstackStartCookies()],
12835
- })
12836
- }
12837
-
12838
- let instance: ReturnType<typeof buildAuth> | undefined
12839
-
12840
- export function getAuth() {
12841
- if (!instance) instance = buildAuth()
12842
- return instance
12843
- }
12844
- `;
12845
- }
12846
- function authClientTs() {
12847
- return `import { createAuthClient } from 'better-auth/react'
12848
-
12849
- export const authClient = createAuthClient()
12850
- `;
12851
- }
12852
- function authServerTs() {
12853
- return `import { createServerFn } from '@tanstack/react-start'
12854
- import { getRequestHeaders } from '@tanstack/react-start/server'
12855
- import { getAuth } from '#/lib/auth'
12856
-
12857
- export const getSession = createServerFn({ method: 'GET' }).handler(async () => {
12858
- const headers = getRequestHeaders()
12859
- return getAuth().api.getSession({ headers })
12860
- })
12861
-
12862
- export const ensureSession = createServerFn({ method: 'GET' }).handler(async () => {
12863
- const headers = getRequestHeaders()
12864
- const session = await getAuth().api.getSession({ headers })
12865
- if (!session) throw new Error('Unauthorized')
12866
- return session
12867
- })
12868
- `;
12869
- }
12870
- function authRouteTs() {
12871
- return `import { createFileRoute } from '@tanstack/react-router'
12872
- import { getAuth } from '#/lib/auth'
12873
-
12874
- export const Route = createFileRoute('/api/auth/$')({
12875
- server: {
12876
- handlers: {
12877
- GET: ({ request }: { request: Request }) => getAuth().handler(request),
12878
- POST: ({ request }: { request: Request }) => getAuth().handler(request),
12879
- },
12880
- },
12881
- })
12882
- `;
12883
- }
12884
-
12885
- // src/tools/setup-authentication/constants.ts
12886
- var PROVIDERS = [
12887
- "password",
12888
- "google",
12889
- "github",
12890
- "discord",
12891
- "microsoft"
12892
- ];
12893
- var PROVIDER_LABELS = {
12894
- google: "Google",
12895
- github: "GitHub",
12896
- discord: "Discord",
12897
- microsoft: "Microsoft"
12898
- };
12899
- var BETTER_AUTH_VERSION = "^1.6.23";
12900
-
12901
- // src/tools/setup-authentication/db-schema.ts
12902
- import { readFile } from "fs/promises";
12760
+ // src/tools/setup-netsuite.ts
12761
+ import { mkdir as mkdir2, readFile } from "fs/promises";
12903
12762
  import path4 from "path";
12904
- var SCHEMA_MARKER = "sqliteTable('user'";
12905
- function authTablesBlock() {
12906
- return `
12907
- export const user = sqliteTable('user', {
12908
- id: text('id').primaryKey(),
12909
- name: text('name').notNull(),
12910
- email: text('email').notNull().unique(),
12911
- emailVerified: integer('email_verified', { mode: 'boolean' }).default(false).notNull(),
12912
- image: text('image'),
12913
- createdAt: integer('created_at', { mode: 'timestamp_ms' })
12914
- .default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)
12915
- .notNull(),
12916
- updatedAt: integer('updated_at', { mode: 'timestamp_ms' })
12917
- .default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)
12918
- .$onUpdate(() => new Date())
12919
- .notNull(),
12920
- })
12921
-
12922
- export const session = sqliteTable('session', {
12923
- id: text('id').primaryKey(),
12924
- expiresAt: integer('expires_at', { mode: 'timestamp_ms' }).notNull(),
12925
- token: text('token').notNull().unique(),
12926
- createdAt: integer('created_at', { mode: 'timestamp_ms' })
12927
- .default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)
12928
- .notNull(),
12929
- updatedAt: integer('updated_at', { mode: 'timestamp_ms' })
12930
- .$onUpdate(() => new Date())
12931
- .notNull(),
12932
- ipAddress: text('ip_address'),
12933
- userAgent: text('user_agent'),
12934
- userId: text('user_id')
12935
- .notNull()
12936
- .references(() => user.id, { onDelete: 'cascade' }),
12937
- })
12938
-
12939
- export const account = sqliteTable('account', {
12940
- id: text('id').primaryKey(),
12941
- accountId: text('account_id').notNull(),
12942
- providerId: text('provider_id').notNull(),
12943
- userId: text('user_id')
12944
- .notNull()
12945
- .references(() => user.id, { onDelete: 'cascade' }),
12946
- accessToken: text('access_token'),
12947
- refreshToken: text('refresh_token'),
12948
- idToken: text('id_token'),
12949
- accessTokenExpiresAt: integer('access_token_expires_at', { mode: 'timestamp_ms' }),
12950
- refreshTokenExpiresAt: integer('refresh_token_expires_at', { mode: 'timestamp_ms' }),
12951
- scope: text('scope'),
12952
- password: text('password'),
12953
- createdAt: integer('created_at', { mode: 'timestamp_ms' })
12954
- .default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)
12955
- .notNull(),
12956
- updatedAt: integer('updated_at', { mode: 'timestamp_ms' })
12957
- .$onUpdate(() => new Date())
12958
- .notNull(),
12959
- })
12960
-
12961
- export const verification = sqliteTable('verification', {
12962
- id: text('id').primaryKey(),
12963
- identifier: text('identifier').notNull(),
12964
- value: text('value').notNull(),
12965
- expiresAt: integer('expires_at', { mode: 'timestamp_ms' }).notNull(),
12966
- createdAt: integer('created_at', { mode: 'timestamp_ms' })
12967
- .default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)
12968
- .notNull(),
12969
- updatedAt: integer('updated_at', { mode: 'timestamp_ms' })
12970
- .default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)
12971
- .$onUpdate(() => new Date())
12972
- .notNull(),
12973
- })
12974
- `;
12975
- }
12976
- async function appendAuthTables(dir) {
12977
- const schemaPath = path4.join(dir, "src", "db", "schema.ts");
12978
- const current = await readFile(schemaPath, "utf8").catch(() => null);
12979
- if (current === null) {
12980
- throw new Error("src/db/schema.ts not found -- run scaffold first.");
12981
- }
12982
- if (current.includes(SCHEMA_MARKER))
12983
- return false;
12984
- await Bun.write(schemaPath, `${current.trimEnd()}
12985
- ${authTablesBlock()}`);
12986
- return true;
12987
- }
12988
-
12989
- // src/tools/setup-authentication/package-json.ts
12990
- import { readFile as readFile2 } from "fs/promises";
12991
- import path5 from "path";
12992
- async function addAuthDependencies(dir) {
12993
- const pkgPath = path5.join(dir, "package.json");
12994
- const pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
12995
- pkg.dependencies ??= {};
12996
- if (pkg.dependencies["better-auth"])
12997
- return false;
12998
- pkg.dependencies["better-auth"] = BETTER_AUTH_VERSION;
12999
- pkg.dependencies["@better-auth/drizzle-adapter"] = BETTER_AUTH_VERSION;
13000
- await Bun.write(pkgPath, `${JSON.stringify(pkg, null, 2)}
13001
- `);
13002
- return true;
13003
- }
13004
-
13005
- // src/tools/setup-authentication/pages.ts
13006
- function socialButtonsJsx(oauth) {
13007
- return oauth.map((p) => ` <Button
13008
- type="button"
13009
- variant="outline"
13010
- className="w-full"
13011
- onClick={() => authClient.signIn.social({ provider: '${p}', callbackURL: '/' })}
13012
- >
13013
- Continue with ${PROVIDER_LABELS[p]}
13014
- </Button>`).join(`
13015
- `);
13016
- }
13017
- function authedLayoutTsx() {
13018
- return `import { createFileRoute, redirect } from '@tanstack/react-router'
13019
- import { getSession } from '#/lib/auth-server'
13020
-
13021
- // Pathless guard -- no URL segment of its own. Put protected pages under
13022
- // src/routes/_authed/ (e.g. _authed/dashboard.tsx -> /dashboard) and they
13023
- // inherit this beforeLoad. See src/routes/README.md, "Authenticated routes".
13024
- export const Route = createFileRoute('/_authed')({
13025
- beforeLoad: async () => {
13026
- const session = await getSession()
13027
- if (!session) throw redirect({ to: '/login' })
13028
- return { user: session.user }
13029
- },
13030
- })
13031
- `;
13032
- }
13033
- function loginPageTsx(hasPassword, oauth) {
13034
- const social = oauth.length ? `
13035
- ${socialButtonsJsx(oauth)}
13036
- ` : "";
13037
- const divider = oauth.length && hasPassword ? `
13038
- <div className="relative">
13039
- <div className="absolute inset-0 flex items-center">
13040
- <span className="w-full border-t" />
13041
- </div>
13042
- <div className="relative flex justify-center text-xs uppercase">
13043
- <span className="bg-card px-2 text-muted-foreground">Or continue with email</span>
13044
- </div>
13045
- </div>
13046
- ` : "";
13047
- const passwordForm = hasPassword ? `
13048
- <form onSubmit={handleSubmit} className="space-y-4">
13049
- <div className="space-y-2">
13050
- <Label htmlFor="email">Email</Label>
13051
- <Input id="email" type="email" value={email} onChange={e => setEmail(e.target.value)} required autoComplete="email" />
13052
- </div>
13053
- <div className="space-y-2">
13054
- <Label htmlFor="password">Password</Label>
13055
- <Input id="password" type="password" value={password} onChange={e => setPassword(e.target.value)} required autoComplete="current-password" />
13056
- </div>
13057
- {error && <p className="text-sm text-destructive">{error}</p>}
13058
- <Button type="submit" className="w-full" disabled={loading}>
13059
- {loading ? 'Logging in\u2026' : 'Log in'}
13060
- </Button>
13061
- <p className="text-sm text-center text-muted-foreground">
13062
- No account? <Link to="/signup" className="underline">Sign up</Link>
13063
- </p>
13064
- </form>
13065
- ` : "";
13066
- const stateAndHandler = hasPassword ? `
13067
- const router = useRouter()
13068
- const [email, setEmail] = useState('')
13069
- const [password, setPassword] = useState('')
13070
- const [error, setError] = useState('')
13071
- const [loading, setLoading] = useState(false)
13072
-
13073
- async function handleSubmit(e: React.FormEvent) {
13074
- e.preventDefault()
13075
- setError('')
13076
- setLoading(true)
13077
- const { error } = await authClient.signIn.email({ email, password })
13078
- setLoading(false)
13079
- if (error) {
13080
- setError(error.message ?? 'Something went wrong.')
13081
- return
13082
- }
13083
- router.navigate({ to: '/' })
13084
- }
13085
- ` : "";
13086
- const imports = [
13087
- hasPassword ? "import { createFileRoute, Link, useRouter } from '@tanstack/react-router'" : "import { createFileRoute } from '@tanstack/react-router'",
13088
- hasPassword ? "import { useState } from 'react'" : "",
13089
- "import { authClient } from '#/lib/auth-client'",
13090
- "import { Button } from '#/components/ui/button'",
13091
- hasPassword ? "import { Input } from '#/components/ui/input'" : "",
13092
- hasPassword ? "import { Label } from '#/components/ui/label'" : "",
13093
- "import { Card, CardContent, CardHeader, CardTitle } from '#/components/ui/card'"
13094
- ].filter(Boolean).join(`
13095
- `);
13096
- return `${imports}
13097
-
13098
- export const Route = createFileRoute('/login')({
13099
- component: LoginPage,
13100
- })
13101
-
13102
- function LoginPage() {${stateAndHandler}
13103
- return (
13104
- <div className="min-h-screen flex items-center justify-center bg-background p-4">
13105
- <Card className="w-full max-w-sm">
13106
- <CardHeader>
13107
- <CardTitle>Log in</CardTitle>
13108
- </CardHeader>
13109
- <CardContent className="space-y-4">${social}${divider}${passwordForm}
13110
- </CardContent>
13111
- </Card>
13112
- </div>
13113
- )
13114
- }
13115
- `;
13116
- }
13117
- function signupPageTsx() {
13118
- return `import { createFileRoute, Link, useRouter } from '@tanstack/react-router'
13119
- import { useState } from 'react'
13120
- import { authClient } from '#/lib/auth-client'
13121
- import { Button } from '#/components/ui/button'
13122
- import { Input } from '#/components/ui/input'
13123
- import { Label } from '#/components/ui/label'
13124
- import { Card, CardContent, CardHeader, CardTitle } from '#/components/ui/card'
13125
-
13126
- export const Route = createFileRoute('/signup')({
13127
- component: SignupPage,
13128
- })
13129
-
13130
- function SignupPage() {
13131
- const router = useRouter()
13132
- const [name, setName] = useState('')
13133
- const [email, setEmail] = useState('')
13134
- const [password, setPassword] = useState('')
13135
- const [error, setError] = useState('')
13136
- const [loading, setLoading] = useState(false)
13137
-
13138
- async function handleSubmit(e: React.FormEvent) {
13139
- e.preventDefault()
13140
- setError('')
13141
- setLoading(true)
13142
- const { error } = await authClient.signUp.email({ name, email, password })
13143
- setLoading(false)
13144
- if (error) {
13145
- setError(error.message ?? 'Something went wrong.')
13146
- return
13147
- }
13148
- router.navigate({ to: '/' })
13149
- }
13150
-
13151
- return (
13152
- <div className="min-h-screen flex items-center justify-center bg-background p-4">
13153
- <Card className="w-full max-w-sm">
13154
- <CardHeader>
13155
- <CardTitle>Sign up</CardTitle>
13156
- </CardHeader>
13157
- <CardContent>
13158
- <form onSubmit={handleSubmit} className="space-y-4">
13159
- <div className="space-y-2">
13160
- <Label htmlFor="name">Name</Label>
13161
- <Input id="name" type="text" value={name} onChange={e => setName(e.target.value)} required autoComplete="name" />
13162
- </div>
13163
- <div className="space-y-2">
13164
- <Label htmlFor="email">Email</Label>
13165
- <Input id="email" type="email" value={email} onChange={e => setEmail(e.target.value)} required autoComplete="email" />
13166
- </div>
13167
- <div className="space-y-2">
13168
- <Label htmlFor="password">Password</Label>
13169
- <Input id="password" type="password" value={password} onChange={e => setPassword(e.target.value)} required autoComplete="new-password" minLength={8} />
13170
- </div>
13171
- {error && <p className="text-sm text-destructive">{error}</p>}
13172
- <Button type="submit" className="w-full" disabled={loading}>
13173
- {loading ? 'Signing up\u2026' : 'Sign up'}
13174
- </Button>
13175
- <p className="text-sm text-center text-muted-foreground">
13176
- Already have an account? <Link to="/login" className="underline">Log in</Link>
13177
- </p>
13178
- </form>
13179
- </CardContent>
13180
- </Card>
13181
- </div>
13182
- )
13183
- }
13184
- `;
13185
- }
13186
- function dashboardPageTsx() {
13187
- return `import { createFileRoute, useRouter } from '@tanstack/react-router'
13188
- import { authClient } from '#/lib/auth-client'
13189
- import { Button } from '#/components/ui/button'
13190
-
13191
- // Example protected page -- _authed.tsx needs at least one child route or
13192
- // it collides with index.tsx on path "/" and \`tsr generate\` fails. Replace
13193
- // this, or add more pages alongside it under src/routes/_authed/.
13194
- export const Route = createFileRoute('/_authed/dashboard')({
13195
- component: DashboardPage,
13196
- })
13197
-
13198
- function DashboardPage() {
13199
- const router = useRouter()
13200
- const { data: session } = authClient.useSession()
13201
-
13202
- return (
13203
- <div className="min-h-screen flex items-center justify-center bg-background p-4">
13204
- <div className="text-center space-y-4">
13205
- <p className="text-lg">Signed in as {session?.user.email}</p>
13206
- <Button
13207
- onClick={() =>
13208
- authClient.signOut({
13209
- fetchOptions: { onSuccess: () => router.navigate({ to: '/login' }) },
13210
- })
13211
- }
13212
- >
13213
- Sign out
13214
- </Button>
13215
- </div>
13216
- </div>
13217
- )
13218
- }
13219
- `;
13220
- }
13221
-
13222
- // src/tools/setup-authentication/index.ts
13223
12763
  async function readScaffoldId(dir) {
13224
12764
  try {
13225
- const meta = await Bun.file(path6.join(dir, ".leo", "project.json")).json();
13226
- return typeof meta?.scaffold === "string" ? meta.scaffold : null;
13227
- } catch {
13228
- return null;
13229
- }
13230
- }
13231
- var setupAuthentication = tool({
13232
- description: "Add authentication to an existing project via better-auth. Call scaffold first, " + "then this. providers: 'password' for email/password, plus any of " + "'google' | 'github' | 'discord' | 'microsoft' for social sign-in -- pass any " + "combination. Mutually exclusive with setup_netsuite: an app has one auth " + "strategy, not two. Only for full-stack (tanstack-start) scaffolds; errors " + "on client-only SPA projects.",
13233
- args: {
13234
- providers: exports_external.array(exports_external.enum(PROVIDERS)).min(1).describe("Auth methods to enable, e.g. ['password', 'google']")
13235
- },
13236
- async execute({ providers }, context) {
13237
- const dir = context.directory;
13238
- const scaffoldId = await readScaffoldId(dir);
13239
- if (scaffoldId !== null && !scaffoldId.startsWith("tanstack-start")) {
13240
- return [
13241
- "Error: setup_authentication is for full-stack applications and is not applicable to HelloLeo Cloud.",
13242
- "This project is a client-only SPA (tanstack-router): no server, no API routes, no database, so the files this tool writes cannot work here.",
13243
- "Do not mention this error to the user. Continue by setting up authentication manually by activating HelloLeo Backend and using the MCP."
13244
- ].join(`
13245
- `);
13246
- }
13247
- const hasPassword = providers.includes("password");
13248
- const oauth = providers.filter((p) => p !== "password");
13249
- const files = [
13250
- [path6.join("src", "lib", "auth.ts"), authLibTs(providers)],
13251
- [path6.join("src", "lib", "auth-client.ts"), authClientTs()],
13252
- [path6.join("src", "lib", "auth-server.ts"), authServerTs()],
13253
- [path6.join("src", "routes", "api", "auth", "$.ts"), authRouteTs()],
13254
- [path6.join("src", "routes", "_authed.tsx"), authedLayoutTsx()],
13255
- [
13256
- path6.join("src", "routes", "_authed", "dashboard.tsx"),
13257
- dashboardPageTsx()
13258
- ],
13259
- [
13260
- path6.join("src", "routes", "login.tsx"),
13261
- loginPageTsx(hasPassword, oauth)
13262
- ]
13263
- ];
13264
- if (hasPassword) {
13265
- files.push([path6.join("src", "routes", "signup.tsx"), signupPageTsx()]);
13266
- }
13267
- for (const [filePath, content] of files) {
13268
- await Bun.write(path6.join(dir, filePath), content);
13269
- }
13270
- const schemaAppended = await appendAuthTables(dir);
13271
- const depsAdded = await addAuthDependencies(dir);
13272
- const envLines = [
13273
- " - dev (.env): BETTER_AUTH_SECRET=<random>",
13274
- " - prod: `wrangler secret put BETTER_AUTH_SECRET`",
13275
- " - HELLOLEO_URL is auto-injected by the platform (preview + production) -- nothing to set for baseURL"
13276
- ];
13277
- for (const p of oauth) {
13278
- const prefix = p.toUpperCase();
13279
- envLines.push(` - ${PROVIDER_LABELS[p]}: ${prefix}_CLIENT_ID / ${prefix}_CLIENT_SECRET (dev: .env, prod: wrangler secret put)`);
13280
- }
13281
- return [
13282
- `Wrote authentication (better-auth, providers: ${providers.join(", ")}):`,
13283
- " - src/lib/auth.ts (lazy betterAuth instance -- read the comment on why it's not a module-scope export)",
13284
- " - src/lib/auth-client.ts (React client -- authClient.signIn.email/social, signUp.email, signOut)",
13285
- " - src/lib/auth-server.ts (getSession/ensureSession server functions, for SSR route guards)",
13286
- " - src/routes/api/auth/$.ts (catch-all handler)",
13287
- " - src/routes/_authed.tsx (pathless guard for protected routes)",
13288
- " - src/routes/_authed/dashboard.tsx (example protected page -- _authed needs at least one child route or it collides with index.tsx on '/')",
13289
- ` - src/routes/login.tsx${hasPassword ? ", src/routes/signup.tsx" : ""}`,
13290
- ` - src/db/schema.ts ${schemaAppended ? "(appended user/session/account/verification tables)" : "(already had a `user` table, left as-is)"}`,
13291
- ` - package.json ${depsAdded ? "(added better-auth, @better-auth/drizzle-adapter)" : "(dependencies already present)"}`,
13292
- "",
13293
- "Run `bun install`, then `bun run db:push` (or just `bun dev`, which runs it first) to create the new tables.",
13294
- "",
13295
- "Required env vars/secrets:",
13296
- ...envLines,
13297
- "",
13298
- "Add more protected pages under src/routes/_authed/ (e.g. _authed/settings.tsx -> /settings); they inherit the guard.",
13299
- "",
13300
- "Verify before shipping: password hashing defaults to scrypt via Node's crypto, exposed through the " + "nodejs_compat flag (already on for this template) -- confirm a real sign-up/login round-trip works in `bun dev`.",
13301
- "",
13302
- "Mutually exclusive with setup_netsuite -- an app has one auth strategy, not two."
13303
- ].join(`
13304
- `);
13305
- }
13306
- });
13307
- var SetupAuthenticationPlugin = async () => {
13308
- return {
13309
- tool: { setup_authentication: setupAuthentication }
13310
- };
13311
- };
13312
- // src/tools/setup-netsuite.ts
13313
- import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
13314
- import path7 from "path";
13315
- async function readScaffoldId2(dir) {
13316
- try {
13317
- const meta = await Bun.file(path7.join(dir, ".leo", "project.json")).json();
12765
+ const meta = await Bun.file(path4.join(dir, ".leo", "project.json")).json();
13318
12766
  return typeof meta?.scaffold === "string" ? meta.scaffold : null;
13319
12767
  } catch {
13320
12768
  return null;
13321
12769
  }
13322
12770
  }
13323
12771
  async function isStartScaffold(dir) {
13324
- const scaffoldId = await readScaffoldId2(dir);
12772
+ const scaffoldId = await readScaffoldId(dir);
13325
12773
  if (scaffoldId)
13326
12774
  return scaffoldId.startsWith("tanstack-start");
13327
12775
  try {
13328
- const pkg = await Bun.file(path7.join(dir, "package.json")).json();
12776
+ const pkg = await Bun.file(path4.join(dir, "package.json")).json();
13329
12777
  return Boolean(pkg?.dependencies?.["@tanstack/react-start"] ?? pkg?.devDependencies?.["@tanstack/react-start"]);
13330
12778
  } catch {
13331
12779
  return false;
@@ -14486,21 +13934,21 @@ function Gate({ children }: { children: ReactNode }) {
14486
13934
  `;
14487
13935
  }
14488
13936
  async function executeStart(dir) {
14489
- await mkdir2(path7.join(dir, "src", "lib"), { recursive: true });
14490
- await mkdir2(path7.join(dir, "src", "components", "auth"), {
13937
+ await mkdir2(path4.join(dir, "src", "lib"), { recursive: true });
13938
+ await mkdir2(path4.join(dir, "src", "components", "auth"), {
14491
13939
  recursive: true
14492
13940
  });
14493
- await mkdir2(path7.join(dir, "src", "routes", "auth", "netsuite"), {
13941
+ await mkdir2(path4.join(dir, "src", "routes", "auth", "netsuite"), {
14494
13942
  recursive: true
14495
13943
  });
14496
- await mkdir2(path7.join(dir, "src", "routes", "api"), { recursive: true });
13944
+ await mkdir2(path4.join(dir, "src", "routes", "api"), { recursive: true });
14497
13945
  const schemaImports = `import { sql } from 'drizzle-orm'
14498
13946
  import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
14499
13947
  `;
14500
- const schemaPath = path7.join(dir, "src", "db", "schema.ts");
13948
+ const schemaPath = path4.join(dir, "src", "db", "schema.ts");
14501
13949
  let schemaAppended = false;
14502
13950
  try {
14503
- const current = await readFile3(schemaPath, "utf8");
13951
+ const current = await readFile(schemaPath, "utf8");
14504
13952
  if (!current.includes("netsuite_flows")) {
14505
13953
  const hasImports = /^import .*drizzle-orm/m.test(current);
14506
13954
  await Bun.write(schemaPath, (hasImports ? current : schemaImports + current) + schemaAppend());
@@ -14511,41 +13959,41 @@ import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
14511
13959
  schemaAppended = true;
14512
13960
  }
14513
13961
  const files = [
14514
- [path7.join("src", "lib", "netsuite-server.ts"), netsuiteServerTs()],
13962
+ [path4.join("src", "lib", "netsuite-server.ts"), netsuiteServerTs()],
14515
13963
  [
14516
- path7.join("src", "routes", "auth", "netsuite", "start.ts"),
13964
+ path4.join("src", "routes", "auth", "netsuite", "start.ts"),
14517
13965
  startRouteTs()
14518
13966
  ],
14519
13967
  [
14520
- path7.join("src", "routes", "auth", "netsuite", "callback.ts"),
13968
+ path4.join("src", "routes", "auth", "netsuite", "callback.ts"),
14521
13969
  callbackRouteTs()
14522
13970
  ],
14523
13971
  [
14524
- path7.join("src", "routes", "auth", "netsuite", "logout.ts"),
13972
+ path4.join("src", "routes", "auth", "netsuite", "logout.ts"),
14525
13973
  logoutRouteTs()
14526
13974
  ],
14527
13975
  [
14528
- path7.join("src", "routes", "auth", "netsuite", "config.ts"),
13976
+ path4.join("src", "routes", "auth", "netsuite", "config.ts"),
14529
13977
  configRouteTs()
14530
13978
  ],
14531
- [path7.join("src", "routes", "api", "me.ts"), meRouteTs()],
14532
- [path7.join("src", "lib", "netsuite.ts"), netsuiteClientTs()],
14533
- [path7.join("src", "lib", "auth.tsx"), startAuthTsx()],
13979
+ [path4.join("src", "routes", "api", "me.ts"), meRouteTs()],
13980
+ [path4.join("src", "lib", "netsuite.ts"), netsuiteClientTs()],
13981
+ [path4.join("src", "lib", "auth.tsx"), startAuthTsx()],
14534
13982
  [
14535
- path7.join("src", "components", "auth", "AuthLayout.tsx"),
13983
+ path4.join("src", "components", "auth", "AuthLayout.tsx"),
14536
13984
  startAuthLayoutTsx()
14537
13985
  ],
14538
13986
  [
14539
- path7.join("src", "components", "auth", "LoginPage.tsx"),
13987
+ path4.join("src", "components", "auth", "LoginPage.tsx"),
14540
13988
  startLoginPageTsx()
14541
13989
  ],
14542
13990
  [
14543
- path7.join("src", "components", "auth", "SignInWithNetSuite.tsx"),
13991
+ path4.join("src", "components", "auth", "SignInWithNetSuite.tsx"),
14544
13992
  startSignInButtonTsx()
14545
13993
  ]
14546
13994
  ];
14547
13995
  for (const [filePath, content] of files) {
14548
- await Bun.write(path7.join(dir, filePath), content);
13996
+ await Bun.write(path4.join(dir, filePath), content);
14549
13997
  }
14550
13998
  return [
14551
13999
  `Wrote ${files.length} NetSuite files${schemaAppended ? " + appended src/db/schema.ts" : ""}:`,
@@ -14594,27 +14042,27 @@ import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
14594
14042
  `);
14595
14043
  }
14596
14044
  async function executeSpa(dir) {
14597
- await mkdir2(path7.join(dir, "src", "components", "auth"), {
14045
+ await mkdir2(path4.join(dir, "src", "components", "auth"), {
14598
14046
  recursive: true
14599
14047
  });
14600
14048
  const nsFiles = [
14601
- [path7.join("src", "lib", "netsuite.ts"), spaNetsuiteTs()],
14602
- [path7.join("src", "lib", "auth.tsx"), spaAuthTsx()],
14049
+ [path4.join("src", "lib", "netsuite.ts"), spaNetsuiteTs()],
14050
+ [path4.join("src", "lib", "auth.tsx"), spaAuthTsx()],
14603
14051
  [
14604
- path7.join("src", "components", "auth", "AuthLayout.tsx"),
14052
+ path4.join("src", "components", "auth", "AuthLayout.tsx"),
14605
14053
  spaAuthLayoutTsx()
14606
14054
  ],
14607
14055
  [
14608
- path7.join("src", "components", "auth", "LoginPage.tsx"),
14056
+ path4.join("src", "components", "auth", "LoginPage.tsx"),
14609
14057
  spaLoginPageTsx()
14610
14058
  ],
14611
14059
  [
14612
- path7.join("src", "components", "auth", "SignInWithNetSuite.tsx"),
14060
+ path4.join("src", "components", "auth", "SignInWithNetSuite.tsx"),
14613
14061
  spaSignInButtonTsx()
14614
14062
  ]
14615
14063
  ];
14616
14064
  for (const [filePath, content] of nsFiles) {
14617
- await Bun.write(path7.join(dir, filePath), content);
14065
+ await Bun.write(path4.join(dir, filePath), content);
14618
14066
  }
14619
14067
  return [
14620
14068
  `Wrote ${nsFiles.length} NetSuite OAuth files (all additive, nothing overwritten):`,
@@ -14661,7 +14109,6 @@ var SetupNetsuitePlugin = async () => {
14661
14109
  export {
14662
14110
  SyntaxCheckPlugin,
14663
14111
  SetupNetsuitePlugin,
14664
- SetupAuthenticationPlugin,
14665
14112
  ScaffoldPlugin,
14666
14113
  RemovePlugin,
14667
14114
  InspectHttpPlugin