@helloleo/plugins 0.2.15 → 0.2.17
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 +14 -2
- package/dist/templates/tanstack-router-2026-06-15/src/components/error-component.tsx +18 -15
- package/dist/templates/tanstack-start-2026-06-18/src/components/error-component.tsx +18 -15
- package/dist/templates/tanstack-start-2026-07-30/src/components/error-component.tsx +18 -15
- package/dist/templates/tanstack-start-2026-07-30/src/db/schema.ts +29 -15
- package/dist/templates/tanstack-start-2026-07-30/src/lib/storage.ts +24 -23
- package/package.json +1 -1
- package/src/templates/tanstack-router-2026-06-15/src/components/error-component.tsx +18 -15
- package/src/templates/tanstack-start-2026-06-18/src/components/error-component.tsx +18 -15
- package/src/templates/tanstack-start-2026-07-30/src/components/error-component.tsx +18 -15
- package/src/templates/tanstack-start-2026-07-30/src/db/schema.ts +29 -15
- package/src/templates/tanstack-start-2026-07-30/src/lib/storage.ts +24 -23
- package/src/tools/scaffold.ts +3 -2
- package/src/tools/setup-authentication/auth-files.ts +11 -0
package/dist/index.js
CHANGED
|
@@ -24955,8 +24955,9 @@ var scaffold = tool({
|
|
|
24955
24955
|
const typeNotes = isStart ? [
|
|
24956
24956
|
"- This is a TanStack Start project. The server entry is src/server.ts. There is no index.html or client entrypoint.",
|
|
24957
24957
|
"- For JSON/CRUD APIs, add server routes under src/routes/api/. Read src/routes/api/health.ts first, add routes as needed.",
|
|
24958
|
-
"- The database schema lives in src/db/schema.ts and is the single source of truth. Migration files under drizzle/ are generated and applied automatically. Never create, edit or delete them.
|
|
24959
|
-
"-
|
|
24958
|
+
"- The database schema lives in src/db/schema.ts and is the single source of truth. Migration files under drizzle/ are generated and applied automatically. Never create, edit or delete them.",
|
|
24959
|
+
"- Never rename existing tables or columns in schema.ts. Renames cannot be auto-migrated: no migration file is generated, the database keeps the old name, and every query against the new name fails at runtime with a 500. To reshape data, add a new column or table, write to it, and migrate old values in app code (e.g. fall back to the old column on read).",
|
|
24960
|
+
"- Persistence goes through @helloleo/runtime exclusively: database via #/db (see src/db/index.ts), key-value store via #/lib/kv (see src/lib/kv.ts), file storage via #/lib/storage (see src/lib/storage.ts -- uploads/downloads flow through your API routes; presigned URLs are NOT available), env/secrets via env from @helloleo/runtime. Read those files before server-side data work. Never install another DB/storage client, never import from cloudflare:workers, all of it is server-side only."
|
|
24960
24961
|
] : [
|
|
24961
24962
|
"- This is a TanStack Router SPA. The client entry is index.html -> src/main.tsx. There is no server: no API routes, no server functions, no database.",
|
|
24962
24963
|
"- Fetch data from external APIs only. If the app needs its own persistence or server-side code, it should have been scaffolded as tanstack-start."
|
|
@@ -25052,6 +25053,17 @@ function buildAuth() {
|
|
|
25052
25053
|
emailAndPassword: {
|
|
25053
25054
|
enabled: ${hasPassword},
|
|
25054
25055
|
},${socialProvidersBlock}
|
|
25056
|
+
// The preview runs inside an iframe on another site, so the session
|
|
25057
|
+
// cookie lives in a third-party context: it MUST be SameSite=None +
|
|
25058
|
+
// Secure + Partitioned (CHIPS) or Chrome/Safari silently drop it and
|
|
25059
|
+
// login never sticks.
|
|
25060
|
+
advanced: {
|
|
25061
|
+
defaultCookieAttributes: {
|
|
25062
|
+
sameSite: 'none',
|
|
25063
|
+
secure: true,
|
|
25064
|
+
partitioned: true,
|
|
25065
|
+
},
|
|
25066
|
+
},
|
|
25055
25067
|
plugins: [tanstackStartCookies()],
|
|
25056
25068
|
})
|
|
25057
25069
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -1,20 +1,34 @@
|
|
|
1
|
+
/* oxlint-disable no-unused-vars */
|
|
2
|
+
// ^ keeps the drizzle imports below from being stripped while unused.
|
|
3
|
+
// Remove this disable once your own tables use them.
|
|
1
4
|
// Single source of truth for the database schema. Define tables here — the
|
|
2
5
|
// SQL migration files under drizzle/ are generated and applied automatically
|
|
3
6
|
// (dev on server start, production on publish). Never hand-write SQL or touch
|
|
4
7
|
// drizzle/. Prefer ADDING columns/tables over renaming or deleting existing
|
|
5
8
|
// ones: renames cannot be migrated automatically.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Important note (do not remove):
|
|
12
|
+
*
|
|
13
|
+
* Never rename existing tables or columns in schema.ts. Renames cannot be
|
|
14
|
+
* auto-migrated: no migration file is generated, the database keeps the old
|
|
15
|
+
* name, and every query against the new name fails at runtime with a 500.
|
|
16
|
+
* To reshape data, add a new column or table, write to it, and migrate old
|
|
17
|
+
* values in app code (e.g. fall back to the old column on read).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// Example (change before deploying!) :
|
|
22
|
+
|
|
23
|
+
import { sql } from 'drizzle-orm'
|
|
24
|
+
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
|
25
|
+
|
|
26
|
+
export const notes = sqliteTable('notes', {
|
|
27
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
28
|
+
title: text('title').notNull(),
|
|
29
|
+
createdAt: integer('created_at')
|
|
30
|
+
.notNull()
|
|
31
|
+
.default(sql`(unixepoch())`),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export type Note = typeof notes.$inferSelect
|
|
@@ -15,30 +15,31 @@ import { storage } from '@helloleo/runtime'
|
|
|
15
15
|
*
|
|
16
16
|
* Rules:
|
|
17
17
|
* - Server-side only (route handlers, loaders, server functions).
|
|
18
|
-
* -
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* - Files flow THROUGH YOUR API ROUTES: upload = POST route reads the body
|
|
19
|
+
* and `storage.put`s it; download = GET route streams `obj.body` back.
|
|
20
|
+
* Do NOT use presignGet/presignPut — presigned URLs are not available.
|
|
21
21
|
* - Key layout is up to the app — namespace with prefixes ('avatars/',
|
|
22
|
-
* 'exports/').
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* 'exports/'). Store the key in a database row to reference the file.
|
|
23
|
+
*
|
|
24
|
+
* Upload route example (src/routes/api/images/upload.ts):
|
|
25
|
+
*
|
|
26
|
+
* POST: async ({ request }) => {
|
|
27
|
+
* const form = await request.formData()
|
|
28
|
+
* const file = form.get('file') as File
|
|
29
|
+
* const key = `images/${crypto.randomUUID()}-${file.name}`
|
|
30
|
+
* await storage.put(key, file.stream(), { contentType: file.type })
|
|
31
|
+
* return Response.json({ key }, { status: 201 })
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* Download/serve route example (src/routes/api/images/$key.ts):
|
|
35
|
+
*
|
|
36
|
+
* GET: async ({ params }) => {
|
|
37
|
+
* const obj = await storage.get(`images/${params.key}`)
|
|
38
|
+
* if (!obj) return new Response('Not found', { status: 404 })
|
|
39
|
+
* return new Response(obj.body, {
|
|
40
|
+
* headers: { 'content-type': obj.contentType ?? 'application/octet-stream' },
|
|
41
|
+
* })
|
|
42
|
+
* }
|
|
29
43
|
*/
|
|
30
|
-
export function getUploadUrl(
|
|
31
|
-
key: string,
|
|
32
|
-
opts?: { ttlSeconds?: number; contentType?: string },
|
|
33
|
-
): Promise<string> {
|
|
34
|
-
return storage.presignPut(key, opts?.ttlSeconds ?? 600, {
|
|
35
|
-
contentType: opts?.contentType,
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Presign a download — short-lived direct GET URL for the browser. */
|
|
40
|
-
export function getDownloadUrl(key: string, ttlSeconds = 600): Promise<string> {
|
|
41
|
-
return storage.presignGet(key, ttlSeconds)
|
|
42
|
-
}
|
|
43
44
|
|
|
44
45
|
export { storage }
|
package/package.json
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -1,20 +1,34 @@
|
|
|
1
|
+
/* oxlint-disable no-unused-vars */
|
|
2
|
+
// ^ keeps the drizzle imports below from being stripped while unused.
|
|
3
|
+
// Remove this disable once your own tables use them.
|
|
1
4
|
// Single source of truth for the database schema. Define tables here — the
|
|
2
5
|
// SQL migration files under drizzle/ are generated and applied automatically
|
|
3
6
|
// (dev on server start, production on publish). Never hand-write SQL or touch
|
|
4
7
|
// drizzle/. Prefer ADDING columns/tables over renaming or deleting existing
|
|
5
8
|
// ones: renames cannot be migrated automatically.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Important note (do not remove):
|
|
12
|
+
*
|
|
13
|
+
* Never rename existing tables or columns in schema.ts. Renames cannot be
|
|
14
|
+
* auto-migrated: no migration file is generated, the database keeps the old
|
|
15
|
+
* name, and every query against the new name fails at runtime with a 500.
|
|
16
|
+
* To reshape data, add a new column or table, write to it, and migrate old
|
|
17
|
+
* values in app code (e.g. fall back to the old column on read).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// Example (change before deploying!) :
|
|
22
|
+
|
|
23
|
+
import { sql } from 'drizzle-orm'
|
|
24
|
+
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
|
25
|
+
|
|
26
|
+
export const notes = sqliteTable('notes', {
|
|
27
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
28
|
+
title: text('title').notNull(),
|
|
29
|
+
createdAt: integer('created_at')
|
|
30
|
+
.notNull()
|
|
31
|
+
.default(sql`(unixepoch())`),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export type Note = typeof notes.$inferSelect
|
|
@@ -15,30 +15,31 @@ import { storage } from '@helloleo/runtime'
|
|
|
15
15
|
*
|
|
16
16
|
* Rules:
|
|
17
17
|
* - Server-side only (route handlers, loaders, server functions).
|
|
18
|
-
* -
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* - Files flow THROUGH YOUR API ROUTES: upload = POST route reads the body
|
|
19
|
+
* and `storage.put`s it; download = GET route streams `obj.body` back.
|
|
20
|
+
* Do NOT use presignGet/presignPut — presigned URLs are not available.
|
|
21
21
|
* - Key layout is up to the app — namespace with prefixes ('avatars/',
|
|
22
|
-
* 'exports/').
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* 'exports/'). Store the key in a database row to reference the file.
|
|
23
|
+
*
|
|
24
|
+
* Upload route example (src/routes/api/images/upload.ts):
|
|
25
|
+
*
|
|
26
|
+
* POST: async ({ request }) => {
|
|
27
|
+
* const form = await request.formData()
|
|
28
|
+
* const file = form.get('file') as File
|
|
29
|
+
* const key = `images/${crypto.randomUUID()}-${file.name}`
|
|
30
|
+
* await storage.put(key, file.stream(), { contentType: file.type })
|
|
31
|
+
* return Response.json({ key }, { status: 201 })
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* Download/serve route example (src/routes/api/images/$key.ts):
|
|
35
|
+
*
|
|
36
|
+
* GET: async ({ params }) => {
|
|
37
|
+
* const obj = await storage.get(`images/${params.key}`)
|
|
38
|
+
* if (!obj) return new Response('Not found', { status: 404 })
|
|
39
|
+
* return new Response(obj.body, {
|
|
40
|
+
* headers: { 'content-type': obj.contentType ?? 'application/octet-stream' },
|
|
41
|
+
* })
|
|
42
|
+
* }
|
|
29
43
|
*/
|
|
30
|
-
export function getUploadUrl(
|
|
31
|
-
key: string,
|
|
32
|
-
opts?: { ttlSeconds?: number; contentType?: string },
|
|
33
|
-
): Promise<string> {
|
|
34
|
-
return storage.presignPut(key, opts?.ttlSeconds ?? 600, {
|
|
35
|
-
contentType: opts?.contentType,
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Presign a download — short-lived direct GET URL for the browser. */
|
|
40
|
-
export function getDownloadUrl(key: string, ttlSeconds = 600): Promise<string> {
|
|
41
|
-
return storage.presignGet(key, ttlSeconds)
|
|
42
|
-
}
|
|
43
44
|
|
|
44
45
|
export { storage }
|
package/src/tools/scaffold.ts
CHANGED
|
@@ -152,8 +152,9 @@ export const scaffold = tool({
|
|
|
152
152
|
? [
|
|
153
153
|
"- This is a TanStack Start project. The server entry is src/server.ts. There is no index.html or client entrypoint.",
|
|
154
154
|
"- For JSON/CRUD APIs, add server routes under src/routes/api/. Read src/routes/api/health.ts first, add routes as needed.",
|
|
155
|
-
"- The database schema lives in src/db/schema.ts and is the single source of truth. Migration files under drizzle/ are generated and applied automatically. Never create, edit or delete them.
|
|
156
|
-
"-
|
|
155
|
+
"- The database schema lives in src/db/schema.ts and is the single source of truth. Migration files under drizzle/ are generated and applied automatically. Never create, edit or delete them.",
|
|
156
|
+
"- Never rename existing tables or columns in schema.ts. Renames cannot be auto-migrated: no migration file is generated, the database keeps the old name, and every query against the new name fails at runtime with a 500. To reshape data, add a new column or table, write to it, and migrate old values in app code (e.g. fall back to the old column on read).",
|
|
157
|
+
"- Persistence goes through @helloleo/runtime exclusively: database via #/db (see src/db/index.ts), key-value store via #/lib/kv (see src/lib/kv.ts), file storage via #/lib/storage (see src/lib/storage.ts -- uploads/downloads flow through your API routes; presigned URLs are NOT available), env/secrets via env from @helloleo/runtime. Read those files before server-side data work. Never install another DB/storage client, never import from cloudflare:workers, all of it is server-side only.",
|
|
157
158
|
]
|
|
158
159
|
: [
|
|
159
160
|
"- This is a TanStack Router SPA. The client entry is index.html -> src/main.tsx. There is no server: no API routes, no server functions, no database.",
|
|
@@ -78,6 +78,17 @@ function buildAuth() {
|
|
|
78
78
|
emailAndPassword: {
|
|
79
79
|
enabled: ${hasPassword},
|
|
80
80
|
},${socialProvidersBlock}
|
|
81
|
+
// The preview runs inside an iframe on another site, so the session
|
|
82
|
+
// cookie lives in a third-party context: it MUST be SameSite=None +
|
|
83
|
+
// Secure + Partitioned (CHIPS) or Chrome/Safari silently drop it and
|
|
84
|
+
// login never sticks.
|
|
85
|
+
advanced: {
|
|
86
|
+
defaultCookieAttributes: {
|
|
87
|
+
sameSite: 'none',
|
|
88
|
+
secure: true,
|
|
89
|
+
partitioned: true,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
81
92
|
plugins: [tanstackStartCookies()],
|
|
82
93
|
})
|
|
83
94
|
}
|