@lumerahq/cli 0.19.10 → 0.19.12
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/{chunk-WWEIOMW6.js → chunk-4WZMUFC7.js} +26 -123
- package/dist/{chunk-EDRAYUWN.js → chunk-MDCAEFUH.js} +0 -4
- package/dist/{chunk-53NOF33P.js → chunk-OGG5TR4Y.js} +3 -2
- package/dist/{deps-WSZGH35V.js → deps-KVHWZARX.js} +2 -4
- package/dist/{dev-35XSSGOG.js → dev-EZTXUSD2.js} +8 -12
- package/dist/index.js +12 -12
- package/dist/{init-FW4RFXLL.js → init-QCNR4ULM.js} +1 -1
- package/dist/{register-HR2QQFWX.js → register-QBRKXWNX.js} +1 -1
- package/dist/{resources-M3ILBD6X.js → resources-DPGY7KC3.js} +5 -6
- package/dist/{run-YUL73K5O.js → run-R6MO23U7.js} +1 -1
- package/package.json +7 -6
- package/templates/default/AGENTS.md +30 -1
- package/templates/default/biome.json +1 -1
- package/templates/default/package.json +1 -1
- package/templates/default/src/lib/utils.ts +2 -2
- package/templates/default/src/main.tsx +1 -1
- package/templates/default/src/routes/index.tsx +21 -10
- package/templates/default/src/styles.css +1 -1
- package/templates/default/vite.config.ts +1 -4
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createApiClient,
|
|
6
6
|
isApiErrorStatus
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-MDCAEFUH.js";
|
|
8
8
|
import {
|
|
9
9
|
findProjectRoot,
|
|
10
10
|
getAppName
|
|
@@ -14,17 +14,7 @@ import {
|
|
|
14
14
|
import pc from "picocolors";
|
|
15
15
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "fs";
|
|
16
16
|
import { join } from "path";
|
|
17
|
-
var LEGACY_DEPS_FILE = "platform/project_deps.json";
|
|
18
17
|
var SHARED_COLLECTIONS_DIR = "platform/collections/shared";
|
|
19
|
-
function loadLegacyDeps(projectRoot) {
|
|
20
|
-
const depsPath = join(projectRoot, LEGACY_DEPS_FILE);
|
|
21
|
-
if (!existsSync(depsPath)) return null;
|
|
22
|
-
try {
|
|
23
|
-
return JSON.parse(readFileSync(depsPath, "utf-8"));
|
|
24
|
-
} catch {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
18
|
function sharedCollectionsDir(projectRoot) {
|
|
29
19
|
return join(projectRoot, SHARED_COLLECTIONS_DIR);
|
|
30
20
|
}
|
|
@@ -58,13 +48,6 @@ function loadSharedCollectionDeps(projectRoot) {
|
|
|
58
48
|
}
|
|
59
49
|
return deps2.sort((a, b) => a.file.localeCompare(b.file));
|
|
60
50
|
}
|
|
61
|
-
async function projectPermissionsModeEnabled(api) {
|
|
62
|
-
try {
|
|
63
|
-
return await api.isProjectPermissionsModeEnabled();
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
51
|
function declarationForShare(share, existing) {
|
|
69
52
|
return {
|
|
70
53
|
...existing ?? {},
|
|
@@ -134,156 +117,76 @@ async function syncResourceShareDeps(projectRoot, api, projectExternalId, opts)
|
|
|
134
117
|
}
|
|
135
118
|
return true;
|
|
136
119
|
}
|
|
137
|
-
async function syncLegacyDeps(projectRoot, api, quiet = false) {
|
|
138
|
-
const deps2 = loadLegacyDeps(projectRoot);
|
|
139
|
-
if (!deps2 || !deps2.dependencies || Object.keys(deps2.dependencies).length === 0) {
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
let allOk = true;
|
|
143
|
-
for (const [depProject, config] of Object.entries(deps2.dependencies)) {
|
|
144
|
-
try {
|
|
145
|
-
const manifest = await api.getProjectManifest(depProject);
|
|
146
|
-
const remoteNames = new Set(manifest.collections.map((c) => c.name));
|
|
147
|
-
for (const col of config.collections) {
|
|
148
|
-
if (remoteNames.has(col)) {
|
|
149
|
-
if (!quiet) console.log(pc.green(" \u2713"), `${depProject}/${col}`);
|
|
150
|
-
} else {
|
|
151
|
-
if (!quiet) console.log(pc.red(" \u2717"), `${depProject}/${col} \u2014 not found in project manifest`);
|
|
152
|
-
allOk = false;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
} catch (e) {
|
|
156
|
-
if (!quiet) console.log(pc.red(" \u2717"), `${depProject}: failed to fetch manifest \u2014 ${e}`);
|
|
157
|
-
allOk = false;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return allOk;
|
|
161
|
-
}
|
|
162
|
-
async function projectResourceDepsEnabled(projectRoot) {
|
|
163
|
-
const root = projectRoot ?? findProjectRoot();
|
|
164
|
-
loadEnv(root);
|
|
165
|
-
const appName = getAppName(root);
|
|
166
|
-
const api = createApiClient(void 0, void 0, appName);
|
|
167
|
-
return projectPermissionsModeEnabled(api);
|
|
168
|
-
}
|
|
169
120
|
async function syncDeps(projectRoot, options = {}) {
|
|
170
121
|
const root = projectRoot ?? findProjectRoot();
|
|
171
122
|
loadEnv(root);
|
|
172
123
|
const appName = getAppName(root);
|
|
173
124
|
const api = createApiClient(void 0, void 0, appName);
|
|
174
|
-
const flagOn = await projectPermissionsModeEnabled(api);
|
|
175
125
|
const quiet = options.quiet === true;
|
|
176
126
|
const write = options.write === true;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
if (options.legacyWhenDisabled === false) {
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
return syncLegacyDeps(root, api, quiet);
|
|
127
|
+
return syncResourceShareDeps(root, api, appName, {
|
|
128
|
+
write,
|
|
129
|
+
quiet,
|
|
130
|
+
ignorePermissionDenied: options.ignorePermissionDenied === true
|
|
131
|
+
});
|
|
188
132
|
}
|
|
189
133
|
async function deps(args) {
|
|
190
134
|
const sub = args[0];
|
|
191
135
|
const projectRoot = findProjectRoot();
|
|
192
136
|
loadEnv(projectRoot);
|
|
193
|
-
let cachedFlagOn;
|
|
194
|
-
async function flagOn() {
|
|
195
|
-
if (cachedFlagOn !== void 0) return cachedFlagOn;
|
|
196
|
-
cachedFlagOn = await projectResourceDepsEnabled(projectRoot);
|
|
197
|
-
return cachedFlagOn;
|
|
198
|
-
}
|
|
199
137
|
if (sub === "sync") {
|
|
200
|
-
const enabled = await flagOn();
|
|
201
138
|
console.log();
|
|
202
139
|
console.log(pc.cyan(pc.bold(" Deps Sync")));
|
|
203
|
-
console.log(pc.dim(
|
|
140
|
+
console.log(pc.dim(" Syncing incoming resource shares into platform/collections/shared..."));
|
|
204
141
|
console.log();
|
|
205
|
-
const ok = await syncDeps(projectRoot, { write:
|
|
142
|
+
const ok = await syncDeps(projectRoot, { write: true });
|
|
206
143
|
if (!ok) {
|
|
207
144
|
console.log();
|
|
208
|
-
console.log(pc.red(
|
|
145
|
+
console.log(pc.red(" Some resource-share dependencies could not be synced."));
|
|
209
146
|
process.exit(1);
|
|
210
147
|
}
|
|
211
148
|
console.log();
|
|
212
|
-
console.log(pc.green(
|
|
149
|
+
console.log(pc.green(" Resource shares synced."));
|
|
213
150
|
return;
|
|
214
151
|
}
|
|
215
152
|
if (sub === "list") {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
console.log(pc.dim(" No shared collection declarations in platform/collections/shared"));
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
console.log();
|
|
223
|
-
console.log(pc.cyan(pc.bold(" Shared Collection Dependencies")));
|
|
224
|
-
console.log();
|
|
225
|
-
for (const { file, dep } of sharedDeps) {
|
|
226
|
-
const source = dep.source_project ?? "unknown-source";
|
|
227
|
-
const collection = dep.collection ?? "unknown-table";
|
|
228
|
-
const privilege = dep.privilege ?? "collection.read";
|
|
229
|
-
console.log(` ${pc.bold(file)}`);
|
|
230
|
-
console.log(` ${source}/${collection} ${pc.dim(`(${privilege})`)}`);
|
|
231
|
-
}
|
|
232
|
-
console.log();
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
const depsData = loadLegacyDeps(projectRoot);
|
|
236
|
-
if (!depsData || Object.keys(depsData.dependencies).length === 0) {
|
|
237
|
-
console.log(pc.dim(" No dependencies declared in platform/project_deps.json"));
|
|
153
|
+
const sharedDeps = loadSharedCollectionDeps(projectRoot);
|
|
154
|
+
if (sharedDeps.length === 0) {
|
|
155
|
+
console.log(pc.dim(" No shared collection declarations in platform/collections/shared"));
|
|
238
156
|
return;
|
|
239
157
|
}
|
|
240
158
|
console.log();
|
|
241
|
-
console.log(pc.cyan(pc.bold("
|
|
159
|
+
console.log(pc.cyan(pc.bold(" Shared Collection Dependencies")));
|
|
242
160
|
console.log();
|
|
243
|
-
for (const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
161
|
+
for (const { file, dep } of sharedDeps) {
|
|
162
|
+
const source = dep.source_project ?? "unknown-source";
|
|
163
|
+
const collection = dep.collection ?? "unknown-table";
|
|
164
|
+
const privilege = dep.privilege ?? "collection.read";
|
|
165
|
+
console.log(` ${pc.bold(file)}`);
|
|
166
|
+
console.log(` ${source}/${collection} ${pc.dim(`(${privilege})`)}`);
|
|
248
167
|
}
|
|
249
168
|
console.log();
|
|
250
169
|
return;
|
|
251
170
|
}
|
|
252
171
|
if (sub === "init") {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
console.log(pc.green(" \u2713"), "Created platform/collections/shared/");
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const depsPath = join(projectRoot, LEGACY_DEPS_FILE);
|
|
260
|
-
if (existsSync(depsPath)) {
|
|
261
|
-
console.log(pc.yellow(" platform/project_deps.json already exists."));
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
const platformDir = join(projectRoot, "platform");
|
|
265
|
-
mkdirSync(platformDir, { recursive: true });
|
|
266
|
-
const initial = { dependencies: {} };
|
|
267
|
-
writeFileSync(depsPath, JSON.stringify(initial, null, 2) + "\n");
|
|
268
|
-
console.log(pc.green(" \u2713"), "Created platform/project_deps.json");
|
|
172
|
+
const dir = sharedCollectionsDir(projectRoot);
|
|
173
|
+
mkdirSync(dir, { recursive: true });
|
|
174
|
+
console.log(pc.green(" \u2713"), "Created platform/collections/shared/");
|
|
269
175
|
return;
|
|
270
176
|
}
|
|
271
177
|
console.log(`
|
|
272
178
|
${pc.bold("lumera deps")} \u2014 manage cross-project dependencies
|
|
273
179
|
|
|
274
180
|
${pc.bold("Commands:")}
|
|
275
|
-
sync Sync
|
|
276
|
-
resource shares to platform/collections/shared/*.json
|
|
181
|
+
sync Sync incoming resource shares to platform/collections/shared/*.json
|
|
277
182
|
list Show declared dependencies
|
|
278
|
-
init Create dependency storage
|
|
183
|
+
init Create dependency storage
|
|
279
184
|
|
|
280
|
-
${pc.bold("
|
|
281
|
-
${pc.bold("Legacy mode:")} platform/project_deps.json
|
|
185
|
+
${pc.bold("Dependencies:")} platform/collections/shared/*.json
|
|
282
186
|
`);
|
|
283
187
|
}
|
|
284
188
|
|
|
285
189
|
export {
|
|
286
|
-
projectResourceDepsEnabled,
|
|
287
190
|
syncDeps,
|
|
288
191
|
deps
|
|
289
192
|
};
|
|
@@ -90,10 +90,6 @@ var ApiClient = class {
|
|
|
90
90
|
async getMe() {
|
|
91
91
|
return this.request("/api/me");
|
|
92
92
|
}
|
|
93
|
-
async isProjectPermissionsModeEnabled() {
|
|
94
|
-
const me = await this.getMe();
|
|
95
|
-
return me.user?.feature_flags?.project_permissions_mode === true;
|
|
96
|
-
}
|
|
97
93
|
async listCollections() {
|
|
98
94
|
const result = await this.request("/api/pb/collections");
|
|
99
95
|
return result.items;
|
|
@@ -116,9 +116,10 @@ async function deploy(options) {
|
|
|
116
116
|
throw new Error(`Deploy failed: ${await res.text()}`);
|
|
117
117
|
}
|
|
118
118
|
const result = await res.json();
|
|
119
|
+
const launchUrl = result.launch_url || result.url;
|
|
119
120
|
console.log(pc.green(`
|
|
120
|
-
Deployed! ${
|
|
121
|
-
return { url:
|
|
121
|
+
Deployed! ${launchUrl}`));
|
|
122
|
+
return { url: launchUrl, version };
|
|
122
123
|
}
|
|
123
124
|
function isPortInUse(port, host = "127.0.0.1") {
|
|
124
125
|
return new Promise((resolve2) => {
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deps,
|
|
3
|
-
projectResourceDepsEnabled,
|
|
4
3
|
syncDeps
|
|
5
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4WZMUFC7.js";
|
|
6
5
|
import "./chunk-2CR762KB.js";
|
|
7
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-MDCAEFUH.js";
|
|
8
7
|
import "./chunk-ZH3NVYEQ.js";
|
|
9
8
|
import "./chunk-FJFIWC7G.js";
|
|
10
9
|
import "./chunk-PNKVD2UK.js";
|
|
11
10
|
export {
|
|
12
11
|
deps,
|
|
13
|
-
projectResourceDepsEnabled,
|
|
14
12
|
syncDeps
|
|
15
13
|
};
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dev
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OGG5TR4Y.js";
|
|
4
4
|
import {
|
|
5
|
-
projectResourceDepsEnabled,
|
|
6
5
|
syncDeps
|
|
7
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-4WZMUFC7.js";
|
|
8
7
|
import {
|
|
9
8
|
loadEnv
|
|
10
9
|
} from "./chunk-2CR762KB.js";
|
|
11
10
|
import {
|
|
12
11
|
createApiClient
|
|
13
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-MDCAEFUH.js";
|
|
14
13
|
import {
|
|
15
14
|
findProjectRoot,
|
|
16
15
|
getApiUrl,
|
|
@@ -125,14 +124,11 @@ async function dev2(args) {
|
|
|
125
124
|
const appName = getAppName(projectRoot);
|
|
126
125
|
const appTitle = getAppTitle(projectRoot);
|
|
127
126
|
const apiUrl = getApiUrl();
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
ignorePermissionDenied: true
|
|
134
|
-
});
|
|
135
|
-
}
|
|
127
|
+
await syncDeps(projectRoot, {
|
|
128
|
+
write: true,
|
|
129
|
+
quiet: true,
|
|
130
|
+
ignorePermissionDenied: true
|
|
131
|
+
});
|
|
136
132
|
if (!flags["skip-setup"]) {
|
|
137
133
|
const fresh = await isFreshProject(projectRoot);
|
|
138
134
|
if (fresh) {
|
package/dist/index.js
CHANGED
|
@@ -219,39 +219,39 @@ async function main() {
|
|
|
219
219
|
switch (command) {
|
|
220
220
|
// Resource commands
|
|
221
221
|
case "plan":
|
|
222
|
-
await import("./resources-
|
|
222
|
+
await import("./resources-DPGY7KC3.js").then((m) => m.plan(args.slice(1)));
|
|
223
223
|
break;
|
|
224
224
|
case "apply":
|
|
225
|
-
await import("./resources-
|
|
225
|
+
await import("./resources-DPGY7KC3.js").then((m) => m.apply(args.slice(1)));
|
|
226
226
|
break;
|
|
227
227
|
case "pull":
|
|
228
|
-
await import("./resources-
|
|
228
|
+
await import("./resources-DPGY7KC3.js").then((m) => m.pull(args.slice(1)));
|
|
229
229
|
break;
|
|
230
230
|
case "destroy":
|
|
231
|
-
await import("./resources-
|
|
231
|
+
await import("./resources-DPGY7KC3.js").then((m) => m.destroy(args.slice(1)));
|
|
232
232
|
break;
|
|
233
233
|
case "list":
|
|
234
|
-
await import("./resources-
|
|
234
|
+
await import("./resources-DPGY7KC3.js").then((m) => m.list(args.slice(1)));
|
|
235
235
|
break;
|
|
236
236
|
case "show":
|
|
237
|
-
await import("./resources-
|
|
237
|
+
await import("./resources-DPGY7KC3.js").then((m) => m.show(args.slice(1)));
|
|
238
238
|
break;
|
|
239
239
|
case "diff":
|
|
240
|
-
await import("./resources-
|
|
240
|
+
await import("./resources-DPGY7KC3.js").then((m) => m.diff(args.slice(1)));
|
|
241
241
|
break;
|
|
242
242
|
// Development
|
|
243
243
|
case "dev":
|
|
244
|
-
await import("./dev-
|
|
244
|
+
await import("./dev-EZTXUSD2.js").then((m) => m.dev(args.slice(1)));
|
|
245
245
|
break;
|
|
246
246
|
case "run":
|
|
247
|
-
await import("./run-
|
|
247
|
+
await import("./run-R6MO23U7.js").then((m) => m.run(args.slice(1)));
|
|
248
248
|
break;
|
|
249
249
|
// Project
|
|
250
250
|
case "init":
|
|
251
|
-
await import("./init-
|
|
251
|
+
await import("./init-QCNR4ULM.js").then((m) => m.init(args.slice(1)));
|
|
252
252
|
break;
|
|
253
253
|
case "register":
|
|
254
|
-
await import("./register-
|
|
254
|
+
await import("./register-QBRKXWNX.js").then((m) => m.register(args.slice(1)));
|
|
255
255
|
break;
|
|
256
256
|
case "templates":
|
|
257
257
|
await import("./templates-LNUOTNLN.js").then((m) => m.templates(subcommand, args.slice(2)));
|
|
@@ -268,7 +268,7 @@ async function main() {
|
|
|
268
268
|
break;
|
|
269
269
|
// Dependencies
|
|
270
270
|
case "deps":
|
|
271
|
-
await import("./deps-
|
|
271
|
+
await import("./deps-KVHWZARX.js").then((m) => m.deps(args.slice(1)));
|
|
272
272
|
break;
|
|
273
273
|
// Auth
|
|
274
274
|
case "login":
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deploy
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OGG5TR4Y.js";
|
|
4
4
|
import {
|
|
5
|
-
projectResourceDepsEnabled,
|
|
6
5
|
syncDeps
|
|
7
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-4WZMUFC7.js";
|
|
8
7
|
import {
|
|
9
8
|
loadEnv
|
|
10
9
|
} from "./chunk-2CR762KB.js";
|
|
11
10
|
import {
|
|
12
11
|
ApiError,
|
|
13
12
|
createApiClient
|
|
14
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-MDCAEFUH.js";
|
|
15
14
|
import {
|
|
16
15
|
findProjectRoot,
|
|
17
16
|
getApiUrl,
|
|
@@ -3385,9 +3384,9 @@ async function pull(args) {
|
|
|
3385
3384
|
console.log(pc2.bold(" Collections:"));
|
|
3386
3385
|
await pullCollections(api, platformDir, name || void 0, appName);
|
|
3387
3386
|
console.log();
|
|
3388
|
-
if (!name
|
|
3387
|
+
if (!name) {
|
|
3389
3388
|
console.log(pc2.bold(" Resource shares:"));
|
|
3390
|
-
await syncDeps(projectRoot, { write: true,
|
|
3389
|
+
await syncDeps(projectRoot, { write: true, ignorePermissionDenied: true });
|
|
3391
3390
|
console.log();
|
|
3392
3391
|
}
|
|
3393
3392
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumerahq/cli",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.12",
|
|
4
4
|
"description": "CLI for building and deploying Lumera apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
"dist",
|
|
14
14
|
"templates"
|
|
15
15
|
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts --format esm --clean",
|
|
18
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
19
|
+
"prepublishOnly": "pnpm build"
|
|
20
|
+
},
|
|
16
21
|
"dependencies": {
|
|
17
22
|
"acorn": "^8.16.0",
|
|
18
23
|
"archiver": "^7.0.1",
|
|
@@ -30,9 +35,5 @@
|
|
|
30
35
|
},
|
|
31
36
|
"publishConfig": {
|
|
32
37
|
"access": "public"
|
|
33
|
-
},
|
|
34
|
-
"scripts": {
|
|
35
|
-
"build": "tsup src/index.ts --format esm --clean",
|
|
36
|
-
"dev": "tsup src/index.ts --format esm --watch"
|
|
37
38
|
}
|
|
38
|
-
}
|
|
39
|
+
}
|
|
@@ -17,6 +17,12 @@ src/
|
|
|
17
17
|
scripts/ # Utility scripts (seed data, migrations, etc.)
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
The starter UI is intentionally throwaway. `src/routes/index.tsx` is just the
|
|
21
|
+
default landing page shown before the real app exists, and `index.html` is only
|
|
22
|
+
the Vite shell. When building the user's first real workflow or screen, replace
|
|
23
|
+
the starter home page with the app experience instead of preserving or building
|
|
24
|
+
around the placeholder content.
|
|
25
|
+
|
|
20
26
|
## Lumera Concepts
|
|
21
27
|
|
|
22
28
|
A Lumera app is built from these primitives — all defined as code in `platform/`:
|
|
@@ -110,6 +116,29 @@ fetch('https://app.lumerahq.com/api/pb/sql', ...);
|
|
|
110
116
|
- Token is not needed — the bridge handles auth via the parent session
|
|
111
117
|
- `X-Lumera-Project` header is not needed — the parent adds it automatically
|
|
112
118
|
|
|
119
|
+
## Shareable Links
|
|
120
|
+
|
|
121
|
+
For any URL a human will open (copy-link, invites, emails, QR codes), use `getShareableAppUrl()` or `buildShareableAppUrl()` from `@lumerahq/ui/lib` — **never `window.location.href`**. The app runs inside an iframe, so `window.location` returns the internal `/_apps/{company}/{app}/...` URL instead of the shell's `/app/{appId}` URL.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { buildShareableAppUrl, getShareableAppUrl } from '@lumerahq/ui/lib';
|
|
125
|
+
const link = getShareableAppUrl(); // preserves current path/search/hash
|
|
126
|
+
const invoiceLink = buildShareableAppUrl('/invoices/123', { router: 'hash' });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
When reporting a deployed app, use `launch_url` or `url` from the deploy response. Do not share `iframe_url`; it is only the internal iframe/static asset mount.
|
|
130
|
+
|
|
131
|
+
## Preview and Verification
|
|
132
|
+
|
|
133
|
+
The Studio environment already runs the Vite dev server and exposes it through
|
|
134
|
+
the portal's **Preview tab**. Users are not expected to run local developer
|
|
135
|
+
commands, and raw localhost URLs are not reachable from the Studio portal.
|
|
136
|
+
|
|
137
|
+
**Never tell users to run `pnpm dev`, `pnpm dev:vite`, `npm run dev`, or any
|
|
138
|
+
other dev-server command. Never tell users to open `localhost`, `127.0.0.1`, or
|
|
139
|
+
Vite ports such as `http://localhost:5173/`.** When explaining how to verify UI
|
|
140
|
+
changes, tell them to open the **Preview tab**.
|
|
141
|
+
|
|
113
142
|
|
|
114
143
|
## Workflow
|
|
115
144
|
|
|
@@ -122,7 +151,7 @@ Follow the user's lead. If they tell you exactly what to build, build it. The wo
|
|
|
122
151
|
|
|
123
152
|
### Step 2: Build (one slice at a time)
|
|
124
153
|
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.
|
|
154
|
+
5. **Stop and ask for feedback** — Tell the user to open the **Preview tab** to see the app. Do not mention local dev-server commands or localhost URLs. Iterate on the slice until they're happy.
|
|
126
155
|
6. **Repeat** — Move to the next step. Build, deploy, get feedback.
|
|
127
156
|
|
|
128
157
|
### Rules
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"check:ci": "biome check . && tsr generate && tsc --noEmit"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@lumerahq/ui": "^0.
|
|
27
|
+
"@lumerahq/ui": "^0.9.1",
|
|
28
28
|
"@tanstack/react-query": "^5.90.11",
|
|
29
29
|
"@tanstack/react-router": "1.155.0",
|
|
30
30
|
"@tanstack/router-plugin": "1.155.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createFileRoute } from '@tanstack/react-router';
|
|
2
|
+
import { Bot, LayoutGrid, MessageSquare, Workflow } from 'lucide-react';
|
|
2
3
|
import { useContext } from 'react';
|
|
3
|
-
import { MessageSquare, LayoutGrid, Bot, Workflow } from 'lucide-react';
|
|
4
4
|
import { AuthContext } from '../lib/auth';
|
|
5
5
|
|
|
6
6
|
export const Route = createFileRoute('/')({
|
|
@@ -14,8 +14,12 @@ function HomePage() {
|
|
|
14
14
|
<div className="space-y-10">
|
|
15
15
|
{/* Header */}
|
|
16
16
|
<div>
|
|
17
|
-
<h1 className="text-2xl font-semibold tracking-tight">
|
|
18
|
-
|
|
17
|
+
<h1 className="text-2xl font-semibold tracking-tight">
|
|
18
|
+
Welcome{auth?.user?.name ? `, ${auth.user.name}` : ''}
|
|
19
|
+
</h1>
|
|
20
|
+
<p className="text-muted-foreground mt-2 text-[0.95rem] leading-relaxed">
|
|
21
|
+
Your app is ready. Use Studio to start building.
|
|
22
|
+
</p>
|
|
19
23
|
</div>
|
|
20
24
|
|
|
21
25
|
{/* Getting Started */}
|
|
@@ -27,8 +31,8 @@ function HomePage() {
|
|
|
27
31
|
<div className="space-y-2">
|
|
28
32
|
<h2 className="font-semibold text-lg tracking-tight">Build with Studio</h2>
|
|
29
33
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
30
|
-
Switch to the <strong>Chat</strong> tab and tell the agent what you want to build.
|
|
31
|
-
|
|
34
|
+
Switch to the <strong>Chat</strong> tab and tell the agent what you want to build. It
|
|
35
|
+
will set up your data, write the logic, and build your UI — all from a conversation.
|
|
32
36
|
</p>
|
|
33
37
|
</div>
|
|
34
38
|
</div>
|
|
@@ -36,7 +40,9 @@ function HomePage() {
|
|
|
36
40
|
|
|
37
41
|
{/* Example prompts */}
|
|
38
42
|
<div>
|
|
39
|
-
<h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground mb-4">
|
|
43
|
+
<h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground mb-4">
|
|
44
|
+
Try asking the agent
|
|
45
|
+
</h2>
|
|
40
46
|
<div className="flex flex-wrap gap-2">
|
|
41
47
|
{[
|
|
42
48
|
'Build an invoice processing app with approval workflows',
|
|
@@ -57,27 +63,32 @@ function HomePage() {
|
|
|
57
63
|
|
|
58
64
|
{/* What you can build */}
|
|
59
65
|
<div>
|
|
60
|
-
<h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground mb-5">
|
|
66
|
+
<h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground mb-5">
|
|
67
|
+
What you can build
|
|
68
|
+
</h2>
|
|
61
69
|
<div className="grid gap-4 md:grid-cols-3">
|
|
62
70
|
<div className="group rounded-xl bg-card p-5 space-y-3 shadow-[0_1px_2px_0_oklch(0_0_0/0.03)] border border-border/60 hover:shadow-[0_4px_12px_-2px_oklch(0_0_0/0.06)] hover:border-border transition-all duration-300">
|
|
63
71
|
<LayoutGrid className="size-5 text-primary transition-colors duration-300" />
|
|
64
72
|
<h3 className="font-medium text-sm tracking-tight">Internal apps</h3>
|
|
65
73
|
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
66
|
-
Back-office tools for your team — dashboards, approval queues, and operational
|
|
74
|
+
Back-office tools for your team — dashboards, approval queues, and operational
|
|
75
|
+
workflows.
|
|
67
76
|
</p>
|
|
68
77
|
</div>
|
|
69
78
|
<div className="group rounded-xl bg-card p-5 space-y-3 shadow-[0_1px_2px_0_oklch(0_0_0/0.03)] border border-border/60 hover:shadow-[0_4px_12px_-2px_oklch(0_0_0/0.06)] hover:border-border transition-all duration-300">
|
|
70
79
|
<Bot className="size-5 text-primary transition-colors duration-300" />
|
|
71
80
|
<h3 className="font-medium text-sm tracking-tight">Agent-powered workflows</h3>
|
|
72
81
|
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
73
|
-
AI agents that extract data, run reviews, draft outputs, and route exceptions — humans
|
|
82
|
+
AI agents that extract data, run reviews, draft outputs, and route exceptions — humans
|
|
83
|
+
stay in the loop.
|
|
74
84
|
</p>
|
|
75
85
|
</div>
|
|
76
86
|
<div className="group rounded-xl bg-card p-5 space-y-3 shadow-[0_1px_2px_0_oklch(0_0_0/0.03)] border border-border/60 hover:shadow-[0_4px_12px_-2px_oklch(0_0_0/0.06)] hover:border-border transition-all duration-300">
|
|
77
87
|
<Workflow className="size-5 text-primary transition-colors duration-300" />
|
|
78
88
|
<h3 className="font-medium text-sm tracking-tight">Automations</h3>
|
|
79
89
|
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
80
|
-
Connect to your systems, process inbound emails, and trigger actions automatically —
|
|
90
|
+
Connect to your systems, process inbound emails, and trigger actions automatically —
|
|
91
|
+
with a full audit trail.
|
|
81
92
|
</p>
|
|
82
93
|
</div>
|
|
83
94
|
</div>
|
|
@@ -34,7 +34,7 @@ body {
|
|
|
34
34
|
--secondary-foreground: oklch(0.16 0.01 60);
|
|
35
35
|
/* Warm stone muted tones */
|
|
36
36
|
--muted: oklch(0.955 0.005 60);
|
|
37
|
-
--muted-foreground: oklch(0.
|
|
37
|
+
--muted-foreground: oklch(0.5 0.01 60);
|
|
38
38
|
/* Accent — lighter orange tint */
|
|
39
39
|
--accent: oklch(0.95 0.03 50);
|
|
40
40
|
--accent-foreground: oklch(0.35 0.12 35);
|