@lumerahq/cli 0.19.10 → 0.19.11
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 +12 -0
|
@@ -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.11",
|
|
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
|
+
}
|
|
@@ -110,6 +110,18 @@ fetch('https://app.lumerahq.com/api/pb/sql', ...);
|
|
|
110
110
|
- Token is not needed — the bridge handles auth via the parent session
|
|
111
111
|
- `X-Lumera-Project` header is not needed — the parent adds it automatically
|
|
112
112
|
|
|
113
|
+
## Shareable Links
|
|
114
|
+
|
|
115
|
+
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.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { buildShareableAppUrl, getShareableAppUrl } from '@lumerahq/ui/lib';
|
|
119
|
+
const link = getShareableAppUrl(); // preserves current path/search/hash
|
|
120
|
+
const invoiceLink = buildShareableAppUrl('/invoices/123', { router: 'hash' });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
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.
|
|
124
|
+
|
|
113
125
|
|
|
114
126
|
## Workflow
|
|
115
127
|
|