@lumerahq/cli 0.19.2 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/chunk-H357NP7T.js +77 -0
- package/dist/{chunk-GKI2HQJC.js → chunk-JKXLKK5I.js} +14 -1
- package/dist/chunk-P5HFNAVN.js +280 -0
- package/dist/{deps-ULTIIDYK.js → deps-EC3VRNN7.js} +4 -2
- package/dist/{dev-EVREBGR6.js → dev-R43VQCZD.js} +8 -1
- package/dist/index.js +13 -13
- package/dist/{init-37XOMJLU.js → init-TDIQAOG4.js} +29 -9
- package/dist/{register-JJUMS4FI.js → register-JFJADKAJ.js} +1 -1
- package/dist/{resources-IPAIPW63.js → resources-OP7EECKZ.js} +10 -4
- package/dist/{run-5ZOSPBGO.js → run-EJP5WCQU.js} +1 -1
- package/dist/{templates-M3RDNDDY.js → templates-LNUOTNLN.js} +2 -3
- package/package.json +1 -1
- package/templates/default/.agents/skills/.gitkeep +0 -0
- package/templates/default/AGENTS.md +151 -0
- package/templates/default/README.md +18 -0
- package/templates/default/_gitignore +14 -0
- package/templates/default/architecture.md +29 -0
- package/templates/default/biome.json +38 -0
- package/templates/default/components.json +21 -0
- package/templates/default/icon.svg +29 -0
- package/templates/default/index.html +16 -0
- package/templates/default/package.json +53 -0
- package/templates/default/platform/automations/.gitkeep +0 -0
- package/templates/default/platform/collections/.gitkeep +0 -0
- package/templates/default/platform/hooks/.gitkeep +0 -0
- package/templates/default/pyproject.toml +14 -0
- package/templates/default/scripts/.gitkeep +0 -0
- package/templates/default/src/components/layout.tsx +11 -0
- package/templates/default/src/lib/auth.ts +9 -0
- package/templates/default/src/lib/queries.ts +17 -0
- package/templates/default/src/lib/utils.ts +6 -0
- package/templates/default/src/main.tsx +130 -0
- package/templates/default/src/routes/__root.tsx +10 -0
- package/templates/default/src/routes/index.tsx +87 -0
- package/templates/default/src/styles.css +128 -0
- package/templates/default/template.json +7 -0
- package/templates/default/tsconfig.json +22 -0
- package/templates/default/vite.config.ts +28 -0
- package/dist/chunk-OQW5E7UT.js +0 -159
- package/dist/chunk-XDTWVFPE.js +0 -120
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
listAllTemplates
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-FJFIWC7G.js";
|
|
3
|
+
} from "./chunk-H357NP7T.js";
|
|
5
4
|
import "./chunk-PNKVD2UK.js";
|
|
6
5
|
|
|
7
6
|
// src/commands/templates.ts
|
|
@@ -25,7 +24,7 @@ ${pc.dim("Examples:")}
|
|
|
25
24
|
lumera templates # List available templates
|
|
26
25
|
lumera templates list -v # Show with descriptions
|
|
27
26
|
lumera templates validate ./my-template # Validate a template dir
|
|
28
|
-
lumera init my-app -t
|
|
27
|
+
lumera init my-app -t default # Use a template
|
|
29
28
|
`);
|
|
30
29
|
}
|
|
31
30
|
async function list(args) {
|
package/package.json
CHANGED
|
File without changes
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# {{projectTitle}}
|
|
2
|
+
|
|
3
|
+
A Lumera app built with collections, automations, hooks, and a React frontend.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
platform/
|
|
9
|
+
├── collections/ # Collection schemas (JSON) — deployed via lumera apply
|
|
10
|
+
├── automations/ # Python automations (config.json + run.py per automation)
|
|
11
|
+
├── hooks/ # JavaScript hooks on collection lifecycle events
|
|
12
|
+
src/
|
|
13
|
+
├── routes/index.tsx # Home page
|
|
14
|
+
├── lib/queries.ts # Data fetching helpers
|
|
15
|
+
├── components/ # Shared UI components
|
|
16
|
+
├── components/ui/ # shadcn UI primitives (installed via MCP)
|
|
17
|
+
scripts/ # Utility scripts (seed data, migrations, etc.)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Lumera Concepts
|
|
21
|
+
|
|
22
|
+
A Lumera app is built from these primitives — all defined as code in `platform/`:
|
|
23
|
+
|
|
24
|
+
- **Collections** (`platform/collections/*.json`) — Data tables with typed fields. Deployed via `lumera apply`.
|
|
25
|
+
- **Automations** (`platform/automations/*/`) — Python scripts that run on Lumera's servers. Each has a `config.json` and `run.py`.
|
|
26
|
+
- **Hooks** (`platform/hooks/*.js`) — JavaScript on collection lifecycle events (`before_create`, `after_update`, etc.).
|
|
27
|
+
- **Webhooks** — Receive events from external services (Stripe, GitHub, etc.). Events land in `lm_event_log`; process them with hooks or automations.
|
|
28
|
+
- **Mailbox** — Each tenant gets an email address. Inbound emails are persisted to `lm_mailbox_messages` — use hooks to trigger automations on new mail.
|
|
29
|
+
- **Email** — Send transactional emails from automations via `from lumera import email`. Logged to `lm_email_logs`.
|
|
30
|
+
|
|
31
|
+
All resources use **external IDs** in the format `<app-name>:<resource-name>` (auto-derived from `package.json` name + directory/file name).
|
|
32
|
+
|
|
33
|
+
## Project Namespacing
|
|
34
|
+
|
|
35
|
+
**All collection names are automatically namespaced by project.** Always use bare names (e.g. `orders`, not `myproject__orders`) everywhere — in collection JSON files, Python SDK calls, JavaScript hooks, and frontend queries. The platform resolves bare names to the project-scoped version transparently.
|
|
36
|
+
|
|
37
|
+
- `pb.ensure_collection("orders", ...)` → stored as `{project}__orders`
|
|
38
|
+
- `pb.search("orders", ...)` → resolves to `{project}__orders`
|
|
39
|
+
- `ctx.dao.find("orders", ...)` → resolves to `{project}__orders`
|
|
40
|
+
- `pbList("orders", ...)` in frontend → resolves via `X-Lumera-Project` header
|
|
41
|
+
|
|
42
|
+
**Never manually prefix collection names with `{project}__`.** Two projects can each have an `orders` collection — they are fully isolated.
|
|
43
|
+
|
|
44
|
+
For detailed technical reference (data models, relationships, design decisions), see [architecture.md](architecture.md).
|
|
45
|
+
|
|
46
|
+
## UI Components
|
|
47
|
+
|
|
48
|
+
**Always use shadcn components for UI.** Never hand-code primitives like buttons, dialogs, cards, inputs, tables, etc.
|
|
49
|
+
|
|
50
|
+
### Installing components
|
|
51
|
+
|
|
52
|
+
Use `bunx` to install shadcn components (the sandbox has `bun`, not `npm`/`npx`):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bunx shadcn@latest add button card dialog # Install specific components
|
|
56
|
+
bunx shadcn@latest add table input label select # Install more as needed
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Components install into `src/components/ui/` and are fully editable.
|
|
60
|
+
|
|
61
|
+
### shadcn MCP server
|
|
62
|
+
|
|
63
|
+
A `shadcn` MCP server is available to browse and search the registry. Use `mcp({ server: "shadcn" })` to list available tools, then:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
mcp({ search: "button" }) # Search across all MCP servers
|
|
67
|
+
mcp({ server: "shadcn" }) # List shadcn tools
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Useful MCP tools:
|
|
71
|
+
- **search_items_in_registries** — fuzzy search components by name
|
|
72
|
+
- **view_items_in_registries** — view component source code
|
|
73
|
+
- **get_item_examples_from_registries** — find usage examples with full code
|
|
74
|
+
- **get_add_command_for_items** — get the install command
|
|
75
|
+
|
|
76
|
+
All tools use the `@shadcn` registry (e.g. `@shadcn/button`, `@shadcn/card`).
|
|
77
|
+
|
|
78
|
+
### Project setup
|
|
79
|
+
|
|
80
|
+
- `components.json` — configures shadcn paths, aliases, and theme
|
|
81
|
+
- `src/styles.css` — shadcn neutral theme CSS variables
|
|
82
|
+
- `src/lib/utils.ts` — `cn()` utility, import from `@/lib/utils`
|
|
83
|
+
- `src/components/ui/` — installed shadcn components (editable)
|
|
84
|
+
|
|
85
|
+
**Do not hand-code UI primitives.** If you need a button, card, dialog, table, form, select, tabs, tooltip, or any standard UI element — install it from shadcn first.
|
|
86
|
+
|
|
87
|
+
## Frontend API Calls
|
|
88
|
+
|
|
89
|
+
Custom apps run in an **iframe**. **Always use the `@lumerahq/ui` bridge for API calls.** Never use raw `fetch()` against Lumera API endpoints.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// ✅ Correct — uses postMessage bridge, no CORS issues
|
|
93
|
+
import { pbSql, pbList, pbCreate, pbUpdate, pbDelete } from '@lumerahq/ui/lib';
|
|
94
|
+
|
|
95
|
+
// CRUD
|
|
96
|
+
const records = await pbList('orders', { filter: JSON.stringify({ status: "pending" }) });
|
|
97
|
+
await pbCreate('orders', { amount: 100 });
|
|
98
|
+
await pbUpdate('orders', recordId, { status: 'done' });
|
|
99
|
+
await pbDelete('orders', recordId);
|
|
100
|
+
|
|
101
|
+
// ❌ WRONG — direct fetch will fail with CORS errors
|
|
102
|
+
fetch('/api/pb/sql', { method: 'POST', body: JSON.stringify({ sql: '...' }) });
|
|
103
|
+
fetch('https://app.lumerahq.com/api/pb/sql', ...);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Why:** The bridge sends requests to the parent window via `postMessage`. The parent makes the actual API call on its own origin — no CORS, and auth is handled automatically.
|
|
107
|
+
|
|
108
|
+
**Rules:**
|
|
109
|
+
- Always import from `@lumerahq/ui/lib` — never write a custom `apiFetch` with raw `fetch()`
|
|
110
|
+
- Token is not needed — the bridge handles auth via the parent session
|
|
111
|
+
- `X-Lumera-Project` header is not needed — the parent adds it automatically
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## Workflow
|
|
115
|
+
|
|
116
|
+
Follow the user's lead. If they tell you exactly what to build, build it. The workflow below is the default when they describe a goal and leave the approach to you.
|
|
117
|
+
|
|
118
|
+
### Step 1: Plan
|
|
119
|
+
1. **Read skills first** — Read the matching skill files for API details and patterns.
|
|
120
|
+
2. **Discuss the plan** — Start from the **simplest thing that works** — one collection, one screen, one feature. Propose incremental steps that layer on complexity but if the decisions are obvious, you can execute multiple steps in one go. Each step should be a complete horizontal slice (collection + backend logic + UI).
|
|
121
|
+
3. **Stop and ask the user to approve.** Iterate until they're happy with the plan. They may reorder steps, drop features, or add ones you didn't think of.
|
|
122
|
+
|
|
123
|
+
### Step 2: Build (one slice at a time)
|
|
124
|
+
4. **Build horizontally** — Pick the first step. Build the full slice: collection schema → `lumera apply` → seed data → UI route/components → commit. Each slice should be deployable and usable on its own.
|
|
125
|
+
5. **Stop and ask for feedback** — Tell the user to open the **Preview tab** to see the app. The dev server starts automatically — you do NOT need to run `pnpm dev` or start any server. Iterate on the slice until they're happy.
|
|
126
|
+
6. **Repeat** — Move to the next step. Build, deploy, get feedback.
|
|
127
|
+
|
|
128
|
+
### Rules
|
|
129
|
+
7. **Code is source of truth** — Edit files in `platform/`, then deploy with `lumera apply`. Don't edit in the Lumera UI.
|
|
130
|
+
8. **Keep docs current** — After each slice, update `architecture.md` with what was built (data models, relationships, hook logic, design decisions). Also update the project description at the top of this file (`AGENTS.md`) so it reflects what the project actually does now — not the original template description.
|
|
131
|
+
9. **Commit and push** — After each slice or significant change: `git add -A && git commit -m "descriptive message" && git push`. The sandbox is ephemeral — uncommitted work is lost if recycled.
|
|
132
|
+
10. **Deploy marker** — When your changes need `lumera apply`, include at the end of your response: `<!-- DEPLOY: short commit message -->`. Skip for frontend-only changes.
|
|
133
|
+
|
|
134
|
+
## File Artifacts
|
|
135
|
+
|
|
136
|
+
When you create a file the user should **see** in chat (HTML pages, SVG graphics, CSV exports, PDFs, images, charts, etc.), call `create_artifact` with the file path after writing it. This uploads the file and renders an inline preview or download card in the chat. Without this step, the file exists in the sandbox but the user cannot see it.
|
|
137
|
+
|
|
138
|
+
Always call `create_artifact` for: `.html`, `.svg`, `.csv`, `.json`, `.xml`, `.md`, `.txt`, `.pdf`, `.png`, `.jpg`, `.gif`, `.xlsx`, `.docx`, `.pptx`, `.zip` — any file the user asked to see or download.
|
|
139
|
+
|
|
140
|
+
## Python
|
|
141
|
+
|
|
142
|
+
Python 3.14 is pre-installed with common data packages (pandas, numpy, matplotlib, pdfplumber, openpyxl, etc.). To install additional packages use `uv add <package>` (preferred) or `pip install <package>`.
|
|
143
|
+
|
|
144
|
+
## Key Commands
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
lumera plan # Preview changes (dry run)
|
|
148
|
+
lumera apply # Deploy everything
|
|
149
|
+
lumera run scripts/seed.py # Run a script remotely
|
|
150
|
+
lumera status # Show sync status
|
|
151
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# {{projectTitle}}
|
|
2
|
+
|
|
3
|
+
Blank Lumera starter — a minimal React app with platform scaffolding. No pre-built collections, automations, or agents. Start building from scratch.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
lumera plan # Preview changes
|
|
9
|
+
lumera apply # Deploy resources
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Structure
|
|
13
|
+
|
|
14
|
+
- `platform/collections/` — Collection schemas (JSON)
|
|
15
|
+
- `platform/automations/` — Python automations
|
|
16
|
+
- `platform/hooks/` — JavaScript hooks
|
|
17
|
+
- `src/` — React frontend (TanStack Router + Query)
|
|
18
|
+
- `scripts/` — Utility scripts
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
> Update this file as you build — it should always reflect the current state of the project so any agent or developer can understand the system from this file alone.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This is a blank starter. No collections, automations, or hooks are defined yet. Build your app by adding resources to `platform/` and deploying with `lumera apply`.
|
|
8
|
+
|
|
9
|
+
## Collections
|
|
10
|
+
|
|
11
|
+
_None yet — add collection schemas to `platform/collections/` as you build._
|
|
12
|
+
|
|
13
|
+
## Automations
|
|
14
|
+
|
|
15
|
+
_None yet — add automation configs to `platform/automations/` as you build._
|
|
16
|
+
|
|
17
|
+
## Hooks
|
|
18
|
+
|
|
19
|
+
_None yet — add hook scripts to `platform/hooks/` as you build._
|
|
20
|
+
|
|
21
|
+
## Frontend Routes
|
|
22
|
+
|
|
23
|
+
| Route | Purpose |
|
|
24
|
+
|-------|---------|
|
|
25
|
+
| `/` | Home page — blank canvas, ready to build |
|
|
26
|
+
|
|
27
|
+
## Design Decisions
|
|
28
|
+
|
|
29
|
+
_Document key decisions here as you make them._
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": false,
|
|
10
|
+
"includes": ["**", "!!**/node_modules", "!!**/dist", "!!**/*routeTree.gen.ts"]
|
|
11
|
+
},
|
|
12
|
+
"formatter": {
|
|
13
|
+
"enabled": true,
|
|
14
|
+
"indentStyle": "space",
|
|
15
|
+
"indentWidth": 2,
|
|
16
|
+
"lineWidth": 100
|
|
17
|
+
},
|
|
18
|
+
"linter": {
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"rules": {
|
|
21
|
+
"recommended": true,
|
|
22
|
+
"suspicious": { "noExplicitAny": "off" },
|
|
23
|
+
"style": { "noNonNullAssertion": "off" }
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"javascript": {
|
|
27
|
+
"formatter": {
|
|
28
|
+
"quoteStyle": "single",
|
|
29
|
+
"semicolons": "always",
|
|
30
|
+
"trailingCommas": "es5"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"css": {
|
|
34
|
+
"parser": {
|
|
35
|
+
"tailwindDirectives": true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "radix-luma",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"aliases": {
|
|
15
|
+
"components": "@/components",
|
|
16
|
+
"utils": "@/lib/utils",
|
|
17
|
+
"ui": "@/components/ui",
|
|
18
|
+
"lib": "@/lib",
|
|
19
|
+
"hooks": "@/hooks"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop offset="0%" stop-color="#ecfdf5"/>
|
|
5
|
+
<stop offset="100%" stop-color="#d1fae5"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="g1" x1="0" y1="0" x2="1" y2="1">
|
|
8
|
+
<stop offset="0%" stop-color="#34d399"/>
|
|
9
|
+
<stop offset="100%" stop-color="#059669"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
<rect width="80" height="80" rx="18" fill="url(#bg)"/>
|
|
13
|
+
<!-- Rocket body -->
|
|
14
|
+
<path d="M40 12 C40 12 28 28 28 44 C28 52 33 58 40 58 C47 58 52 52 52 44 C52 28 40 12 40 12Z" fill="url(#g1)" opacity="0.9"/>
|
|
15
|
+
<!-- Window -->
|
|
16
|
+
<circle cx="40" cy="34" r="5" fill="#ecfdf5"/>
|
|
17
|
+
<circle cx="40" cy="34" r="3" fill="#059669" opacity="0.3"/>
|
|
18
|
+
<!-- Fins -->
|
|
19
|
+
<path d="M28 44 L20 54 L28 50Z" fill="#34d399" opacity="0.7"/>
|
|
20
|
+
<path d="M52 44 L60 54 L52 50Z" fill="#34d399" opacity="0.7"/>
|
|
21
|
+
<!-- Flame -->
|
|
22
|
+
<path d="M36 58 Q38 68 40 72 Q42 68 44 58" fill="#fbbf24"/>
|
|
23
|
+
<path d="M38 58 Q39 65 40 68 Q41 65 42 58" fill="#f97316"/>
|
|
24
|
+
<!-- Stars -->
|
|
25
|
+
<circle cx="18" cy="18" r="1.5" fill="#059669" opacity="0.5"/>
|
|
26
|
+
<circle cx="62" cy="14" r="1" fill="#059669" opacity="0.4"/>
|
|
27
|
+
<circle cx="14" cy="38" r="1" fill="#059669" opacity="0.3"/>
|
|
28
|
+
<circle cx="66" cy="32" r="1.5" fill="#059669" opacity="0.4"/>
|
|
29
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="theme-color" content="#000000" />
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
<title>{{projectTitle}}</title>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="app"></div>
|
|
14
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"pnpm": {
|
|
6
|
+
"overrides": {
|
|
7
|
+
"@tanstack/router-core": "1.155.0"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"lumera": {
|
|
11
|
+
"version": 1,
|
|
12
|
+
"name": "{{projectTitle}}"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "pnpm dlx @lumerahq/cli dev",
|
|
16
|
+
"deploy": "pnpm dlx @lumerahq/cli apply app",
|
|
17
|
+
"dev:vite": "vite",
|
|
18
|
+
"build": "vite build && tsc",
|
|
19
|
+
"preview": "vite preview",
|
|
20
|
+
"typecheck": "tsr generate && tsc --noEmit",
|
|
21
|
+
"lint": "biome lint --write .",
|
|
22
|
+
"format": "biome format --write .",
|
|
23
|
+
"check": "biome check --write .",
|
|
24
|
+
"check:ci": "biome check . && tsr generate && tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@lumerahq/ui": "^0.7.6",
|
|
28
|
+
"@tanstack/react-query": "^5.90.11",
|
|
29
|
+
"@tanstack/react-router": "1.155.0",
|
|
30
|
+
"@tanstack/router-plugin": "1.155.0",
|
|
31
|
+
"class-variance-authority": "^0.7.1",
|
|
32
|
+
"clsx": "^2.1.1",
|
|
33
|
+
"lucide-react": "^1.7.0",
|
|
34
|
+
"radix-ui": "^1.4.3",
|
|
35
|
+
"shadcn": "^4.1.2",
|
|
36
|
+
"tailwind-merge": "^3.4.0",
|
|
37
|
+
"tw-animate-css": "^1.4.0",
|
|
38
|
+
"react": "^19.2.0",
|
|
39
|
+
"react-dom": "^19.2.0",
|
|
40
|
+
"sonner": "^2.0.7",
|
|
41
|
+
"tailwindcss": "^4.2.2"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@biomejs/biome": "^2.4.10",
|
|
45
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
46
|
+
"@tanstack/router-cli": "1.155.0",
|
|
47
|
+
"@types/react": "^19.2.14",
|
|
48
|
+
"@types/react-dom": "^19.2.3",
|
|
49
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
50
|
+
"typescript": "^6.0.2",
|
|
51
|
+
"vite": "^8.0.3"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export function AppLayout({ children }: { children: ReactNode }) {
|
|
4
|
+
return (
|
|
5
|
+
<div className="min-h-screen bg-background overflow-hidden">
|
|
6
|
+
<main className="overflow-auto scroll-smooth">
|
|
7
|
+
<div className="p-8 max-w-5xl mx-auto">{children}</div>
|
|
8
|
+
</main>
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export type AuthContextValue = {
|
|
4
|
+
company?: { id: string; name?: string; apiName?: string };
|
|
5
|
+
user?: { id: string; name: string; email: string; role?: string };
|
|
6
|
+
sessionToken?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const AuthContext = createContext<AuthContextValue | null>(null);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// import { type PbRecord, pbList, pbSql } from '@lumerahq/ui/lib';
|
|
2
|
+
|
|
3
|
+
// Add your collection types and query functions here.
|
|
4
|
+
//
|
|
5
|
+
// Example:
|
|
6
|
+
//
|
|
7
|
+
// export type Order = PbRecord & {
|
|
8
|
+
// title: string;
|
|
9
|
+
// amount: number;
|
|
10
|
+
// status: string;
|
|
11
|
+
// };
|
|
12
|
+
//
|
|
13
|
+
// export async function listOrders(page = 1) {
|
|
14
|
+
// return pbList<Order>('orders', { page, perPage: 20, sort: '-created' });
|
|
15
|
+
// }
|
|
16
|
+
//
|
|
17
|
+
// Use bare collection names — namespacing is handled automatically.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { type HostPayload, isEmbedded, onInitMessage, postReadyMessage } from '@lumerahq/ui/lib';
|
|
2
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
+
import { createHashHistory, createRouter, RouterProvider } from '@tanstack/react-router';
|
|
4
|
+
import { StrictMode, useEffect, useRef, useState } from 'react';
|
|
5
|
+
import ReactDOM from 'react-dom/client';
|
|
6
|
+
import { Toaster } from 'sonner';
|
|
7
|
+
|
|
8
|
+
import { AuthContext, type AuthContextValue } from './lib/auth';
|
|
9
|
+
import { routeTree } from './routeTree.gen';
|
|
10
|
+
import './styles.css';
|
|
11
|
+
|
|
12
|
+
const queryClient = new QueryClient({
|
|
13
|
+
defaultOptions: {
|
|
14
|
+
queries: { staleTime: 30_000, retry: 1 },
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const hashHistory = createHashHistory();
|
|
19
|
+
|
|
20
|
+
const router = createRouter({
|
|
21
|
+
routeTree,
|
|
22
|
+
history: hashHistory,
|
|
23
|
+
context: {},
|
|
24
|
+
defaultPreload: 'intent',
|
|
25
|
+
scrollRestoration: true,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
declare module '@tanstack/react-router' {
|
|
29
|
+
interface Register {
|
|
30
|
+
router: typeof router;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ROUTE_STORAGE_KEY = '{{projectName}}-route';
|
|
35
|
+
|
|
36
|
+
function RouteRestorer() {
|
|
37
|
+
const isFirstLoad = useRef(true);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (isFirstLoad.current) {
|
|
41
|
+
isFirstLoad.current = false;
|
|
42
|
+
const savedRoute = localStorage.getItem(ROUTE_STORAGE_KEY);
|
|
43
|
+
if (savedRoute && savedRoute !== '/' && router.state.location.pathname === '/') {
|
|
44
|
+
router.navigate({ to: savedRoute });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return router.subscribe('onResolved', ({ toLocation }) => {
|
|
49
|
+
localStorage.setItem(ROUTE_STORAGE_KEY, toLocation.href);
|
|
50
|
+
});
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const App = () => {
|
|
57
|
+
const [hostContext, setHostContext] = useState<AuthContextValue | null>(null);
|
|
58
|
+
const [status, setStatus] = useState<'pending' | 'ready' | 'denied'>('pending');
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (typeof window === 'undefined') return;
|
|
62
|
+
|
|
63
|
+
const standaloneDevMode =
|
|
64
|
+
import.meta.env.DEV && !isEmbedded() && !!import.meta.env.VITE_DEV_API_BASE_URL;
|
|
65
|
+
|
|
66
|
+
if (!isEmbedded() && !standaloneDevMode) {
|
|
67
|
+
setStatus('denied');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const cleanup = onInitMessage((payload?: HostPayload) => {
|
|
72
|
+
setHostContext({
|
|
73
|
+
company: payload?.company,
|
|
74
|
+
user: payload?.user,
|
|
75
|
+
sessionToken: payload?.session?.token,
|
|
76
|
+
});
|
|
77
|
+
setStatus('ready');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
postReadyMessage();
|
|
81
|
+
return cleanup;
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
if (status === 'pending') {
|
|
85
|
+
return (
|
|
86
|
+
<main className="flex min-h-screen items-center justify-center bg-background">
|
|
87
|
+
<div className="flex flex-col items-center gap-4">
|
|
88
|
+
<div className="size-10 rounded-xl bg-gradient-to-br from-[var(--accent-gradient-from)] to-[var(--accent-gradient-to)] flex items-center justify-center text-white font-semibold text-lg shadow-sm animate-pulse">
|
|
89
|
+
L
|
|
90
|
+
</div>
|
|
91
|
+
<p className="text-sm text-muted-foreground">Loading...</p>
|
|
92
|
+
</div>
|
|
93
|
+
</main>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (status === 'denied' || !hostContext) {
|
|
98
|
+
return (
|
|
99
|
+
<main className="flex min-h-screen items-center justify-center bg-background">
|
|
100
|
+
<div className="text-center space-y-3">
|
|
101
|
+
<div className="inline-flex size-12 rounded-xl bg-muted items-center justify-center mb-2">
|
|
102
|
+
<span className="text-2xl font-semibold text-muted-foreground">403</span>
|
|
103
|
+
</div>
|
|
104
|
+
<p className="text-sm text-muted-foreground">
|
|
105
|
+
Access denied, this app can only be accessed from within Lumera.
|
|
106
|
+
</p>
|
|
107
|
+
</div>
|
|
108
|
+
</main>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<AuthContext.Provider value={hostContext}>
|
|
114
|
+
<QueryClientProvider client={queryClient}>
|
|
115
|
+
<RouterProvider router={router} />
|
|
116
|
+
<RouteRestorer />
|
|
117
|
+
<Toaster position="top-right" richColors />
|
|
118
|
+
</QueryClientProvider>
|
|
119
|
+
</AuthContext.Provider>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const rootElement = document.getElementById('app');
|
|
124
|
+
if (rootElement && !rootElement.innerHTML) {
|
|
125
|
+
ReactDOM.createRoot(rootElement).render(
|
|
126
|
+
<StrictMode>
|
|
127
|
+
<App />
|
|
128
|
+
</StrictMode>,
|
|
129
|
+
);
|
|
130
|
+
}
|