@helloleo/plugins 0.2.0-beta.0 → 0.2.0-beta.2
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 -6
- package/dist/templates/tanstack-start-2026-06-18/bunfig.toml +5 -0
- package/dist/templates/tanstack-start-2026-06-18/package.json +1 -1
- package/dist/templates/tanstack-start-2026-06-18/src/routes/README.md +85 -0
- package/dist/templates/tanstack-start-2026-06-18/src/routes/api/health.ts +81 -0
- package/package.json +1 -1
- package/src/templates/tanstack-start-2026-06-18/bunfig.toml +5 -0
- package/src/templates/tanstack-start-2026-06-18/package.json +1 -1
- package/src/templates/tanstack-start-2026-06-18/src/routes/README.md +85 -0
- package/src/templates/tanstack-start-2026-06-18/src/routes/api/health.ts +81 -0
- package/src/tools/scaffold.ts +14 -2
- package/src/tools/setup-netsuite.ts +7 -4
- package/dist/templates/tanstack-start-2026-06-18/src/api/health.ts +0 -66
- package/src/templates/tanstack-start-2026-06-18/src/api/health.ts +0 -66
package/dist/index.js
CHANGED
|
@@ -24886,12 +24886,16 @@ function projectUuidFromDir(dir) {
|
|
|
24886
24886
|
return path3.basename(dir);
|
|
24887
24887
|
}
|
|
24888
24888
|
var scaffoldOptionsSchema = exports_external2.object({
|
|
24889
|
-
name: exports_external2.string().describe("
|
|
24889
|
+
name: exports_external2.string().describe("Human-readable name for SEO, e.g. 'Shipping Platform'"),
|
|
24890
24890
|
description: exports_external2.string().describe("One sentence describing the app. Used as the meta description.")
|
|
24891
24891
|
});
|
|
24892
|
+
function slugify(name) {
|
|
24893
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "leo-project";
|
|
24894
|
+
}
|
|
24892
24895
|
async function writeBaseScaffold({ name, description }, dir) {
|
|
24893
24896
|
const created = await renderTemplate(SCAFFOLD_VERSION, {
|
|
24894
24897
|
LEO_NAME: name,
|
|
24898
|
+
LEO_SLUG: slugify(name),
|
|
24895
24899
|
LEO_DESCRIPTION: description,
|
|
24896
24900
|
LEO_PROJECT_UUID: projectUuidFromDir(dir)
|
|
24897
24901
|
}, dir);
|
|
@@ -24917,7 +24921,8 @@ var scaffold = tool({
|
|
|
24917
24921
|
"",
|
|
24918
24922
|
"Notes:",
|
|
24919
24923
|
"- This is a TanStack Start project. The server entry is src/server.ts. There is no index.html or client entrypoint.",
|
|
24920
|
-
"- Add routes as files in src/routes/. Do not default export route components.",
|
|
24924
|
+
"- Add routes as files in src/routes/. Do not default export route components. Routing and layout conventions are documented in src/routes/README.md, read it before adding routes.",
|
|
24925
|
+
"- For JSON/CRUD APIs, add server routes under src/routes/api/. Read src/routes/api/health.ts first, add routes as needed.",
|
|
24921
24926
|
"- shadcn/ui components are already in src/components/ui.",
|
|
24922
24927
|
"- Design system: edit the tokens in src/styles.css (light and dark blocks).",
|
|
24923
24928
|
"- The database schema lives in src/db/schema.ts. Migrations run automatically.",
|
|
@@ -25004,17 +25009,20 @@ export function signInWithNetSuite(returnTo: string = window.location.href) {
|
|
|
25004
25009
|
// refuses to render -- looks like "nothing happens" to the user. Navigate
|
|
25005
25010
|
// the top frame instead so the OAuth flow runs at the top level and the
|
|
25006
25011
|
// callback redirect lands on the same top frame, which then reads the
|
|
25007
|
-
// #nssid= fragment.
|
|
25008
|
-
//
|
|
25012
|
+
// #nssid= fragment. Use \`location.href =\` (NOT \`location.assign()\`):
|
|
25013
|
+
// reading the \`.assign\` method off a cross-origin \`window.top.location\`
|
|
25014
|
+
// throws a SecurityError, which would drop us into the same-frame fallback
|
|
25015
|
+
// and load NetSuite inside the iframe (X-Frame-Options: DENY -> "refused to
|
|
25016
|
+
// connect"). Assigning \`.href\` is a navigation, allowed cross-origin.
|
|
25009
25017
|
try {
|
|
25010
25018
|
if (window.top && window.top !== window) {
|
|
25011
|
-
window.top.location.
|
|
25019
|
+
window.top.location.href = url.toString();
|
|
25012
25020
|
return;
|
|
25013
25021
|
}
|
|
25014
25022
|
} catch {
|
|
25015
25023
|
/* top window not accessible \u2014 fall through to same-frame navigation */
|
|
25016
25024
|
}
|
|
25017
|
-
window.location.
|
|
25025
|
+
window.location.href = url.toString();
|
|
25018
25026
|
}
|
|
25019
25027
|
|
|
25020
25028
|
export interface NetSuiteSession {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Routes
|
|
2
|
+
|
|
3
|
+
TanStack Start uses file-based routing. Every `.ts` and `.tsx` file in this directory is a route. Do not create `src/pages/`, `app/layout.tsx`, or similar, those are Next.js / Remix conventions. The only root layout is `src/routes/__root.tsx`.
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
|
|
7
|
+
| File | URL |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `index.tsx` | `/` |
|
|
10
|
+
| `about.tsx` | `/about` |
|
|
11
|
+
| `users/index.tsx` | `/users` |
|
|
12
|
+
| `users/$id.tsx` | `/users/:id` (dynamic — bare `$`, no curly braces) |
|
|
13
|
+
| `posts.$postId.tsx` | `/posts/:postId` (dots nest without folders) |
|
|
14
|
+
| `admin/route.tsx` | layout for every `/admin/*` page; must render `<Outlet />` |
|
|
15
|
+
| `admin_.login.tsx` | `/admin/login`, but outside the admin layout (trailing `_` escapes it) |
|
|
16
|
+
| `_auth.tsx` | pathless layout; wraps `_auth/*` files without adding a URL segment |
|
|
17
|
+
| `api/health.ts` | server route for defining API endpoints |
|
|
18
|
+
| `__root.tsx` | app shell, wraps every page; preserve `<Outlet />` |
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
|
|
22
|
+
- A layout route without `<Outlet />` renders its children nowhere.
|
|
23
|
+
- Do not `export default` route components, it breaks code splitting. Use a named function passed as `component:`.
|
|
24
|
+
- `routeTree.gen.ts` is auto-generated. Don't edit it by hand.
|
|
25
|
+
|
|
26
|
+
## Authenticated routes
|
|
27
|
+
|
|
28
|
+
Put protected pages inside a pathless `_authed` layout and keep login/signup
|
|
29
|
+
at the top level, outside it. Never special-case the login pathname inside
|
|
30
|
+
the layout guard.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
routes/
|
|
34
|
+
__root.tsx loads the session into router context (context.user)
|
|
35
|
+
_authed.tsx pathless guard — no URL segment of its own
|
|
36
|
+
_authed/
|
|
37
|
+
dashboard.tsx /dashboard (protected)
|
|
38
|
+
posts.tsx /posts (protected)
|
|
39
|
+
index.tsx / (public)
|
|
40
|
+
login.tsx /login (public)
|
|
41
|
+
logout.tsx /logout
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`_authed.tsx` does the guarding, all children inherit it:
|
|
45
|
+
|
|
46
|
+
```tsx _authed.tsx
|
|
47
|
+
export const Route = createFileRoute('/_authed')({
|
|
48
|
+
beforeLoad: ({ context }) => {
|
|
49
|
+
if (!context.user) {
|
|
50
|
+
throw redirect({ to: '/login' })
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The session is loaded once in `__root.tsx`'s `beforeLoad` and returned into
|
|
57
|
+
the router context so every route can read `context.user`. `getUser()` is
|
|
58
|
+
whatever your auth uses (e.g. verify a JWT from localStorage) and resolves to
|
|
59
|
+
the user or null:
|
|
60
|
+
|
|
61
|
+
```tsx __root.tsx
|
|
62
|
+
// (keep the existing head() and component, add this)
|
|
63
|
+
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
|
64
|
+
beforeLoad: async () => {
|
|
65
|
+
const user = await getUser()
|
|
66
|
+
return { user }
|
|
67
|
+
},
|
|
68
|
+
// ...head, component unchanged
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Components read it with `Route.useRouteContext()` (e.g. to swap a Login link
|
|
73
|
+
for a Logout button in the shell):
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
function Route() {
|
|
77
|
+
const { user } = Route.useRouteContext()
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<nav>
|
|
81
|
+
{user ? <Link to="/logout">Logout</Link> : <Link to="/login">Login</Link>}
|
|
82
|
+
</nav>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { db } from '#/db'
|
|
3
|
+
import { entries } from '#/db/schema'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Server route example: GET /api/health. Use this file as the base for
|
|
7
|
+
* building JSON APIs (CRUD endpoints, webhooks) with TanStack Start
|
|
8
|
+
* server routes:
|
|
9
|
+
* https://tanstack.com/start/latest/docs/framework/react/guide/server-routes
|
|
10
|
+
*
|
|
11
|
+
* How server routes work:
|
|
12
|
+
* - Files under src/routes/ can export server handlers. The URL matches
|
|
13
|
+
* the file path: src/routes/api/health.ts serves /api/health.
|
|
14
|
+
* - Add one handler per HTTP method under server.handlers (GET, POST,
|
|
15
|
+
* PUT, PATCH, DELETE). A handler receives ({ request, params }) and
|
|
16
|
+
* returns a Response, usually Response.json().
|
|
17
|
+
* - Dynamic segments use $: src/routes/api/todos/$id.ts serves
|
|
18
|
+
* /api/todos/:id and the handler reads params.id.
|
|
19
|
+
* - Read the request body with await request.json() and validate it
|
|
20
|
+
* before use. Return proper status codes (201 created, 404 not found).
|
|
21
|
+
* - Query the database with `import { db } from '#/db'` INSIDE the
|
|
22
|
+
* handler, never at module scope (the binding only exists per request).
|
|
23
|
+
*/
|
|
24
|
+
export const Route = createFileRoute('/api/health')({
|
|
25
|
+
server: {
|
|
26
|
+
handlers: {
|
|
27
|
+
GET: async () => {
|
|
28
|
+
const startedAt = Date.now()
|
|
29
|
+
|
|
30
|
+
const server = {
|
|
31
|
+
status: 'ok' as const,
|
|
32
|
+
uptimeSeconds: Math.round(
|
|
33
|
+
typeof process !== 'undefined' &&
|
|
34
|
+
typeof process.uptime === 'function'
|
|
35
|
+
? process.uptime()
|
|
36
|
+
: 0,
|
|
37
|
+
),
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
runtime: 'cloudflare-workers',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Database health: lightweight query against a known table
|
|
43
|
+
let database: {
|
|
44
|
+
status: 'ok' | 'error'
|
|
45
|
+
latencyMs: number
|
|
46
|
+
error: string | null
|
|
47
|
+
}
|
|
48
|
+
const dbStart = Date.now()
|
|
49
|
+
try {
|
|
50
|
+
await db.select({ id: entries.id }).from(entries).limit(1)
|
|
51
|
+
database = {
|
|
52
|
+
status: 'ok',
|
|
53
|
+
latencyMs: Date.now() - dbStart,
|
|
54
|
+
error: null,
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
database = {
|
|
58
|
+
status: 'error',
|
|
59
|
+
latencyMs: Date.now() - dbStart,
|
|
60
|
+
error: err instanceof Error ? err.message : String(err),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const overall =
|
|
65
|
+
server.status === 'ok' && database.status === 'ok'
|
|
66
|
+
? 'ok'
|
|
67
|
+
: 'degraded'
|
|
68
|
+
|
|
69
|
+
return Response.json(
|
|
70
|
+
{
|
|
71
|
+
status: overall,
|
|
72
|
+
server,
|
|
73
|
+
database,
|
|
74
|
+
checkedInMs: Date.now() - startedAt,
|
|
75
|
+
},
|
|
76
|
+
{ status: overall === 'ok' ? 200 : 503 },
|
|
77
|
+
)
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
})
|
package/package.json
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Routes
|
|
2
|
+
|
|
3
|
+
TanStack Start uses file-based routing. Every `.ts` and `.tsx` file in this directory is a route. Do not create `src/pages/`, `app/layout.tsx`, or similar, those are Next.js / Remix conventions. The only root layout is `src/routes/__root.tsx`.
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
|
|
7
|
+
| File | URL |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `index.tsx` | `/` |
|
|
10
|
+
| `about.tsx` | `/about` |
|
|
11
|
+
| `users/index.tsx` | `/users` |
|
|
12
|
+
| `users/$id.tsx` | `/users/:id` (dynamic — bare `$`, no curly braces) |
|
|
13
|
+
| `posts.$postId.tsx` | `/posts/:postId` (dots nest without folders) |
|
|
14
|
+
| `admin/route.tsx` | layout for every `/admin/*` page; must render `<Outlet />` |
|
|
15
|
+
| `admin_.login.tsx` | `/admin/login`, but outside the admin layout (trailing `_` escapes it) |
|
|
16
|
+
| `_auth.tsx` | pathless layout; wraps `_auth/*` files without adding a URL segment |
|
|
17
|
+
| `api/health.ts` | server route for defining API endpoints |
|
|
18
|
+
| `__root.tsx` | app shell, wraps every page; preserve `<Outlet />` |
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
|
|
22
|
+
- A layout route without `<Outlet />` renders its children nowhere.
|
|
23
|
+
- Do not `export default` route components, it breaks code splitting. Use a named function passed as `component:`.
|
|
24
|
+
- `routeTree.gen.ts` is auto-generated. Don't edit it by hand.
|
|
25
|
+
|
|
26
|
+
## Authenticated routes
|
|
27
|
+
|
|
28
|
+
Put protected pages inside a pathless `_authed` layout and keep login/signup
|
|
29
|
+
at the top level, outside it. Never special-case the login pathname inside
|
|
30
|
+
the layout guard.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
routes/
|
|
34
|
+
__root.tsx loads the session into router context (context.user)
|
|
35
|
+
_authed.tsx pathless guard — no URL segment of its own
|
|
36
|
+
_authed/
|
|
37
|
+
dashboard.tsx /dashboard (protected)
|
|
38
|
+
posts.tsx /posts (protected)
|
|
39
|
+
index.tsx / (public)
|
|
40
|
+
login.tsx /login (public)
|
|
41
|
+
logout.tsx /logout
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`_authed.tsx` does the guarding, all children inherit it:
|
|
45
|
+
|
|
46
|
+
```tsx _authed.tsx
|
|
47
|
+
export const Route = createFileRoute('/_authed')({
|
|
48
|
+
beforeLoad: ({ context }) => {
|
|
49
|
+
if (!context.user) {
|
|
50
|
+
throw redirect({ to: '/login' })
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The session is loaded once in `__root.tsx`'s `beforeLoad` and returned into
|
|
57
|
+
the router context so every route can read `context.user`. `getUser()` is
|
|
58
|
+
whatever your auth uses (e.g. verify a JWT from localStorage) and resolves to
|
|
59
|
+
the user or null:
|
|
60
|
+
|
|
61
|
+
```tsx __root.tsx
|
|
62
|
+
// (keep the existing head() and component, add this)
|
|
63
|
+
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
|
64
|
+
beforeLoad: async () => {
|
|
65
|
+
const user = await getUser()
|
|
66
|
+
return { user }
|
|
67
|
+
},
|
|
68
|
+
// ...head, component unchanged
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Components read it with `Route.useRouteContext()` (e.g. to swap a Login link
|
|
73
|
+
for a Logout button in the shell):
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
function Route() {
|
|
77
|
+
const { user } = Route.useRouteContext()
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<nav>
|
|
81
|
+
{user ? <Link to="/logout">Logout</Link> : <Link to="/login">Login</Link>}
|
|
82
|
+
</nav>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { db } from '#/db'
|
|
3
|
+
import { entries } from '#/db/schema'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Server route example: GET /api/health. Use this file as the base for
|
|
7
|
+
* building JSON APIs (CRUD endpoints, webhooks) with TanStack Start
|
|
8
|
+
* server routes:
|
|
9
|
+
* https://tanstack.com/start/latest/docs/framework/react/guide/server-routes
|
|
10
|
+
*
|
|
11
|
+
* How server routes work:
|
|
12
|
+
* - Files under src/routes/ can export server handlers. The URL matches
|
|
13
|
+
* the file path: src/routes/api/health.ts serves /api/health.
|
|
14
|
+
* - Add one handler per HTTP method under server.handlers (GET, POST,
|
|
15
|
+
* PUT, PATCH, DELETE). A handler receives ({ request, params }) and
|
|
16
|
+
* returns a Response, usually Response.json().
|
|
17
|
+
* - Dynamic segments use $: src/routes/api/todos/$id.ts serves
|
|
18
|
+
* /api/todos/:id and the handler reads params.id.
|
|
19
|
+
* - Read the request body with await request.json() and validate it
|
|
20
|
+
* before use. Return proper status codes (201 created, 404 not found).
|
|
21
|
+
* - Query the database with `import { db } from '#/db'` INSIDE the
|
|
22
|
+
* handler, never at module scope (the binding only exists per request).
|
|
23
|
+
*/
|
|
24
|
+
export const Route = createFileRoute('/api/health')({
|
|
25
|
+
server: {
|
|
26
|
+
handlers: {
|
|
27
|
+
GET: async () => {
|
|
28
|
+
const startedAt = Date.now()
|
|
29
|
+
|
|
30
|
+
const server = {
|
|
31
|
+
status: 'ok' as const,
|
|
32
|
+
uptimeSeconds: Math.round(
|
|
33
|
+
typeof process !== 'undefined' &&
|
|
34
|
+
typeof process.uptime === 'function'
|
|
35
|
+
? process.uptime()
|
|
36
|
+
: 0,
|
|
37
|
+
),
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
runtime: 'cloudflare-workers',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Database health: lightweight query against a known table
|
|
43
|
+
let database: {
|
|
44
|
+
status: 'ok' | 'error'
|
|
45
|
+
latencyMs: number
|
|
46
|
+
error: string | null
|
|
47
|
+
}
|
|
48
|
+
const dbStart = Date.now()
|
|
49
|
+
try {
|
|
50
|
+
await db.select({ id: entries.id }).from(entries).limit(1)
|
|
51
|
+
database = {
|
|
52
|
+
status: 'ok',
|
|
53
|
+
latencyMs: Date.now() - dbStart,
|
|
54
|
+
error: null,
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
database = {
|
|
58
|
+
status: 'error',
|
|
59
|
+
latencyMs: Date.now() - dbStart,
|
|
60
|
+
error: err instanceof Error ? err.message : String(err),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const overall =
|
|
65
|
+
server.status === 'ok' && database.status === 'ok'
|
|
66
|
+
? 'ok'
|
|
67
|
+
: 'degraded'
|
|
68
|
+
|
|
69
|
+
return Response.json(
|
|
70
|
+
{
|
|
71
|
+
status: overall,
|
|
72
|
+
server,
|
|
73
|
+
database,
|
|
74
|
+
checkedInMs: Date.now() - startedAt,
|
|
75
|
+
},
|
|
76
|
+
{ status: overall === 'ok' ? 200 : 503 },
|
|
77
|
+
)
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
})
|
package/src/tools/scaffold.ts
CHANGED
|
@@ -29,12 +29,22 @@ export function projectUuidFromDir(dir: string): string {
|
|
|
29
29
|
const scaffoldOptionsSchema = z.object({
|
|
30
30
|
name: z
|
|
31
31
|
.string()
|
|
32
|
-
.describe("
|
|
32
|
+
.describe("Human-readable name for SEO, e.g. 'Shipping Platform'"),
|
|
33
33
|
description: z
|
|
34
34
|
.string()
|
|
35
35
|
.describe("One sentence describing the app. Used as the meta description."),
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
/** Kebab-case slug of the product name, for package.json. */
|
|
39
|
+
function slugify(name: string): string {
|
|
40
|
+
return (
|
|
41
|
+
name
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
44
|
+
.replace(/^-+|-+$/g, "") || "leo-project"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
/**
|
|
39
49
|
* Write the base scaffold by rendering the TanStack Start template into dir,
|
|
40
50
|
* then adding .leo/project.json (template metadata the tool owns, not part of
|
|
@@ -48,6 +58,7 @@ export async function writeBaseScaffold(
|
|
|
48
58
|
SCAFFOLD_VERSION,
|
|
49
59
|
{
|
|
50
60
|
LEO_NAME: name,
|
|
61
|
+
LEO_SLUG: slugify(name),
|
|
51
62
|
LEO_DESCRIPTION: description,
|
|
52
63
|
LEO_PROJECT_UUID: projectUuidFromDir(dir),
|
|
53
64
|
},
|
|
@@ -87,7 +98,8 @@ export const scaffold = tool({
|
|
|
87
98
|
"",
|
|
88
99
|
"Notes:",
|
|
89
100
|
"- This is a TanStack Start project. The server entry is src/server.ts. There is no index.html or client entrypoint.",
|
|
90
|
-
"- Add routes as files in src/routes/. Do not default export route components.",
|
|
101
|
+
"- Add routes as files in src/routes/. Do not default export route components. Routing and layout conventions are documented in src/routes/README.md, read it before adding routes.",
|
|
102
|
+
"- For JSON/CRUD APIs, add server routes under src/routes/api/. Read src/routes/api/health.ts first, add routes as needed.",
|
|
91
103
|
"- shadcn/ui components are already in src/components/ui.",
|
|
92
104
|
"- Design system: edit the tokens in src/styles.css (light and dark blocks).",
|
|
93
105
|
"- The database schema lives in src/db/schema.ts. Migrations run automatically.",
|
|
@@ -82,17 +82,20 @@ export function signInWithNetSuite(returnTo: string = window.location.href) {
|
|
|
82
82
|
// refuses to render -- looks like "nothing happens" to the user. Navigate
|
|
83
83
|
// the top frame instead so the OAuth flow runs at the top level and the
|
|
84
84
|
// callback redirect lands on the same top frame, which then reads the
|
|
85
|
-
// #nssid= fragment.
|
|
86
|
-
//
|
|
85
|
+
// #nssid= fragment. Use \`location.href =\` (NOT \`location.assign()\`):
|
|
86
|
+
// reading the \`.assign\` method off a cross-origin \`window.top.location\`
|
|
87
|
+
// throws a SecurityError, which would drop us into the same-frame fallback
|
|
88
|
+
// and load NetSuite inside the iframe (X-Frame-Options: DENY -> "refused to
|
|
89
|
+
// connect"). Assigning \`.href\` is a navigation, allowed cross-origin.
|
|
87
90
|
try {
|
|
88
91
|
if (window.top && window.top !== window) {
|
|
89
|
-
window.top.location.
|
|
92
|
+
window.top.location.href = url.toString();
|
|
90
93
|
return;
|
|
91
94
|
}
|
|
92
95
|
} catch {
|
|
93
96
|
/* top window not accessible — fall through to same-frame navigation */
|
|
94
97
|
}
|
|
95
|
-
window.location.
|
|
98
|
+
window.location.href = url.toString();
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
export interface NetSuiteSession {
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import { supabaseAdmin } from "@/integrations/supabase/client.server";
|
|
3
|
-
|
|
4
|
-
export const Route = createFileRoute("/api/health")({
|
|
5
|
-
server: {
|
|
6
|
-
handlers: {
|
|
7
|
-
GET: async () => {
|
|
8
|
-
const startedAt = Date.now();
|
|
9
|
-
|
|
10
|
-
// Server health
|
|
11
|
-
const server = {
|
|
12
|
-
status: "ok" as const,
|
|
13
|
-
uptimeSeconds: Math.round(
|
|
14
|
-
typeof process !== "undefined" &&
|
|
15
|
-
typeof process.uptime === "function"
|
|
16
|
-
? process.uptime()
|
|
17
|
-
: 0,
|
|
18
|
-
),
|
|
19
|
-
timestamp: new Date().toISOString(),
|
|
20
|
-
runtime: "cloudflare-workers",
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// Database health — lightweight query against a known table
|
|
24
|
-
let database: {
|
|
25
|
-
status: "ok" | "error";
|
|
26
|
-
latencyMs: number;
|
|
27
|
-
error: string | null;
|
|
28
|
-
};
|
|
29
|
-
const dbStart = Date.now();
|
|
30
|
-
try {
|
|
31
|
-
const { error } = await supabaseAdmin
|
|
32
|
-
.from("notes")
|
|
33
|
-
.select("id", { count: "exact", head: true })
|
|
34
|
-
.limit(1);
|
|
35
|
-
if (error) throw error;
|
|
36
|
-
database = {
|
|
37
|
-
status: "ok",
|
|
38
|
-
latencyMs: Date.now() - dbStart,
|
|
39
|
-
error: null,
|
|
40
|
-
};
|
|
41
|
-
} catch (err) {
|
|
42
|
-
database = {
|
|
43
|
-
status: "error",
|
|
44
|
-
latencyMs: Date.now() - dbStart,
|
|
45
|
-
error: err instanceof Error ? err.message : String(err),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const overall =
|
|
50
|
-
server.status === "ok" && database.status === "ok"
|
|
51
|
-
? "ok"
|
|
52
|
-
: "degraded";
|
|
53
|
-
|
|
54
|
-
return Response.json(
|
|
55
|
-
{
|
|
56
|
-
status: overall,
|
|
57
|
-
server,
|
|
58
|
-
database,
|
|
59
|
-
checkedInMs: Date.now() - startedAt,
|
|
60
|
-
},
|
|
61
|
-
{ status: overall === "ok" ? 200 : 503 },
|
|
62
|
-
);
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import { supabaseAdmin } from "@/integrations/supabase/client.server";
|
|
3
|
-
|
|
4
|
-
export const Route = createFileRoute("/api/health")({
|
|
5
|
-
server: {
|
|
6
|
-
handlers: {
|
|
7
|
-
GET: async () => {
|
|
8
|
-
const startedAt = Date.now();
|
|
9
|
-
|
|
10
|
-
// Server health
|
|
11
|
-
const server = {
|
|
12
|
-
status: "ok" as const,
|
|
13
|
-
uptimeSeconds: Math.round(
|
|
14
|
-
typeof process !== "undefined" &&
|
|
15
|
-
typeof process.uptime === "function"
|
|
16
|
-
? process.uptime()
|
|
17
|
-
: 0,
|
|
18
|
-
),
|
|
19
|
-
timestamp: new Date().toISOString(),
|
|
20
|
-
runtime: "cloudflare-workers",
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// Database health — lightweight query against a known table
|
|
24
|
-
let database: {
|
|
25
|
-
status: "ok" | "error";
|
|
26
|
-
latencyMs: number;
|
|
27
|
-
error: string | null;
|
|
28
|
-
};
|
|
29
|
-
const dbStart = Date.now();
|
|
30
|
-
try {
|
|
31
|
-
const { error } = await supabaseAdmin
|
|
32
|
-
.from("notes")
|
|
33
|
-
.select("id", { count: "exact", head: true })
|
|
34
|
-
.limit(1);
|
|
35
|
-
if (error) throw error;
|
|
36
|
-
database = {
|
|
37
|
-
status: "ok",
|
|
38
|
-
latencyMs: Date.now() - dbStart,
|
|
39
|
-
error: null,
|
|
40
|
-
};
|
|
41
|
-
} catch (err) {
|
|
42
|
-
database = {
|
|
43
|
-
status: "error",
|
|
44
|
-
latencyMs: Date.now() - dbStart,
|
|
45
|
-
error: err instanceof Error ? err.message : String(err),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const overall =
|
|
50
|
-
server.status === "ok" && database.status === "ok"
|
|
51
|
-
? "ok"
|
|
52
|
-
: "degraded";
|
|
53
|
-
|
|
54
|
-
return Response.json(
|
|
55
|
-
{
|
|
56
|
-
status: overall,
|
|
57
|
-
server,
|
|
58
|
-
database,
|
|
59
|
-
checkedInMs: Date.now() - startedAt,
|
|
60
|
-
},
|
|
61
|
-
{ status: overall === "ok" ? 200 : 503 },
|
|
62
|
-
);
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
});
|