@lumerahq/cli 0.23.0-dev.2 → 0.23.0
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-WV5HOQNX.js → chunk-I7QEYKS7.js} +32 -4
- package/dist/{dev-UTPD4MN5.js → dev-VWHFYKGC.js} +1 -1
- package/dist/index.js +8 -8
- package/dist/{resources-7243WXIF.js → resources-AMXL4A64.js} +37 -83
- package/package.json +1 -1
- package/templates/default/src/main.tsx +34 -18
- package/templates/default/vite.config.ts +3 -1
|
@@ -41,6 +41,24 @@ function resolveVersion(cwd) {
|
|
|
41
41
|
}
|
|
42
42
|
return formatTimestampVersion();
|
|
43
43
|
}
|
|
44
|
+
function resolveUIVersion(cwd) {
|
|
45
|
+
try {
|
|
46
|
+
const installed = JSON.parse(
|
|
47
|
+
readFileSync(resolve(cwd, "node_modules/@lumerahq/ui/package.json"), "utf-8")
|
|
48
|
+
);
|
|
49
|
+
if (installed?.version) return String(installed.version);
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf-8"));
|
|
54
|
+
const spec = pkg?.dependencies?.["@lumerahq/ui"] ?? pkg?.devDependencies?.["@lumerahq/ui"];
|
|
55
|
+
if (!spec) return "none";
|
|
56
|
+
const cleaned = String(spec).replace(/^[\^~>=<\s]+/, "");
|
|
57
|
+
return /^\d+\.\d+/.test(cleaned) ? cleaned : "";
|
|
58
|
+
} catch {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
44
62
|
function resolveGitSha(cwd) {
|
|
45
63
|
try {
|
|
46
64
|
return execSync("git rev-parse HEAD", {
|
|
@@ -62,11 +80,14 @@ async function createTarball(distDir) {
|
|
|
62
80
|
archive.finalize();
|
|
63
81
|
});
|
|
64
82
|
}
|
|
65
|
-
async function ensureAppRecord(apiBase, token, appName, appTitle) {
|
|
83
|
+
async function ensureAppRecord(apiBase, token, appName, appTitle, accessLevel) {
|
|
66
84
|
const appData = {
|
|
67
85
|
name: appTitle,
|
|
68
86
|
hosting_type: "s3"
|
|
69
87
|
};
|
|
88
|
+
if (accessLevel) {
|
|
89
|
+
appData.access_level = accessLevel;
|
|
90
|
+
}
|
|
70
91
|
const res = await fetchWithRetry(`${apiBase}/custom-apps/${encodeURIComponent(appName)}/registration`, {
|
|
71
92
|
method: "PUT",
|
|
72
93
|
headers: {
|
|
@@ -87,13 +108,14 @@ async function deploy(options) {
|
|
|
87
108
|
appName,
|
|
88
109
|
appTitle,
|
|
89
110
|
distDir,
|
|
90
|
-
apiUrl
|
|
111
|
+
apiUrl,
|
|
112
|
+
accessLevel
|
|
91
113
|
} = options;
|
|
92
114
|
if (!existsSync(distDir)) {
|
|
93
115
|
throw new Error(`dist directory not found: ${distDir}`);
|
|
94
116
|
}
|
|
95
117
|
console.log(pc.dim("Ensuring app record..."));
|
|
96
|
-
await ensureAppRecord(apiUrl, token, appName, appTitle);
|
|
118
|
+
await ensureAppRecord(apiUrl, token, appName, appTitle, accessLevel);
|
|
97
119
|
console.log(pc.cyan(`Deploying ${appTitle}...`));
|
|
98
120
|
const version = options.version || process.env.LUMERA_APP_VERSION?.trim() || resolveVersion(dirname(distDir));
|
|
99
121
|
console.log(pc.dim(`Version: ${version}`));
|
|
@@ -104,6 +126,8 @@ async function deploy(options) {
|
|
|
104
126
|
formData.append("version", version);
|
|
105
127
|
const gitSha = resolveGitSha(dirname(distDir));
|
|
106
128
|
if (gitSha) formData.append("git_sha", gitSha);
|
|
129
|
+
const uiVersion = resolveUIVersion(dirname(distDir));
|
|
130
|
+
if (uiVersion) formData.append("ui_version", uiVersion);
|
|
107
131
|
const res = await fetchWithRetry(`${apiUrl}/custom-apps/${appName}/deploy`, {
|
|
108
132
|
method: "POST",
|
|
109
133
|
headers: {
|
|
@@ -118,9 +142,13 @@ async function deploy(options) {
|
|
|
118
142
|
}
|
|
119
143
|
const result = await res.json();
|
|
120
144
|
const launchUrl = result.launch_url;
|
|
145
|
+
const appUrl = result.app_url;
|
|
121
146
|
console.log(pc.green(`
|
|
122
147
|
Deployed! ${launchUrl}`));
|
|
123
|
-
|
|
148
|
+
if (appUrl) {
|
|
149
|
+
console.log(pc.green(`App URL: ${appUrl}`));
|
|
150
|
+
}
|
|
151
|
+
return { launchUrl, appUrl, version };
|
|
124
152
|
}
|
|
125
153
|
async function registerDevApp(apiBase, token, appName, appTitle, appUrl) {
|
|
126
154
|
const appData = {
|
package/dist/index.js
CHANGED
|
@@ -226,29 +226,29 @@ async function main() {
|
|
|
226
226
|
switch (command) {
|
|
227
227
|
// Resource commands
|
|
228
228
|
case "plan":
|
|
229
|
-
await import("./resources-
|
|
229
|
+
await import("./resources-AMXL4A64.js").then((m) => m.plan(args.slice(1)));
|
|
230
230
|
break;
|
|
231
231
|
case "apply":
|
|
232
|
-
await import("./resources-
|
|
232
|
+
await import("./resources-AMXL4A64.js").then((m) => m.apply(args.slice(1)));
|
|
233
233
|
break;
|
|
234
234
|
case "pull":
|
|
235
|
-
await import("./resources-
|
|
235
|
+
await import("./resources-AMXL4A64.js").then((m) => m.pull(args.slice(1)));
|
|
236
236
|
break;
|
|
237
237
|
case "destroy":
|
|
238
|
-
await import("./resources-
|
|
238
|
+
await import("./resources-AMXL4A64.js").then((m) => m.destroy(args.slice(1)));
|
|
239
239
|
break;
|
|
240
240
|
case "list":
|
|
241
|
-
await import("./resources-
|
|
241
|
+
await import("./resources-AMXL4A64.js").then((m) => m.list(args.slice(1)));
|
|
242
242
|
break;
|
|
243
243
|
case "show":
|
|
244
|
-
await import("./resources-
|
|
244
|
+
await import("./resources-AMXL4A64.js").then((m) => m.show(args.slice(1)));
|
|
245
245
|
break;
|
|
246
246
|
case "diff":
|
|
247
|
-
await import("./resources-
|
|
247
|
+
await import("./resources-AMXL4A64.js").then((m) => m.diff(args.slice(1)));
|
|
248
248
|
break;
|
|
249
249
|
// Development
|
|
250
250
|
case "dev":
|
|
251
|
-
await import("./dev-
|
|
251
|
+
await import("./dev-VWHFYKGC.js").then((m) => m.dev(args.slice(1)));
|
|
252
252
|
break;
|
|
253
253
|
case "run":
|
|
254
254
|
await import("./run-DJXAXDD2.js").then((m) => m.run(args.slice(1)));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deploy
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-I7QEYKS7.js";
|
|
4
4
|
import {
|
|
5
5
|
syncDeps
|
|
6
6
|
} from "./chunk-3ESHIPZH.js";
|
|
@@ -31,7 +31,7 @@ import pc2 from "picocolors";
|
|
|
31
31
|
import prompts from "prompts";
|
|
32
32
|
import { execFileSync, execSync } from "child_process";
|
|
33
33
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
34
|
-
import { join as join2,
|
|
34
|
+
import { join as join2, resolve } from "path";
|
|
35
35
|
|
|
36
36
|
// src/lib/lint/index.ts
|
|
37
37
|
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
@@ -124,7 +124,6 @@ var collectionSchemaRule = {
|
|
|
124
124
|
const id = parsed.id;
|
|
125
125
|
const name = parsed.name;
|
|
126
126
|
const fields = parsed.fields;
|
|
127
|
-
const audit = parsed.audit;
|
|
128
127
|
const indexes = parsed.indexes;
|
|
129
128
|
const search = parsed.search;
|
|
130
129
|
const localFieldTypes = /* @__PURE__ */ new Map();
|
|
@@ -164,20 +163,8 @@ var collectionSchemaRule = {
|
|
|
164
163
|
if (typeof field.type !== "string" || field.type.trim() === "") {
|
|
165
164
|
issues.push(error(target, `Field ${typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`} is missing required string field "type".`));
|
|
166
165
|
}
|
|
167
|
-
const fieldLabel = typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`;
|
|
168
|
-
if (field.type === "select") {
|
|
169
|
-
if (field.options !== void 0) {
|
|
170
|
-
issues.push(error(target, `Select field ${fieldLabel} must define choices with top-level "values", not "options.values".`));
|
|
171
|
-
}
|
|
172
|
-
if (!Array.isArray(field.values) || field.values.length === 0 || !field.values.every((value) => typeof value === "string" && value.trim() !== "")) {
|
|
173
|
-
issues.push(error(target, `Select field ${fieldLabel} must include a non-empty string array "values".`));
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
166
|
}
|
|
177
167
|
}
|
|
178
|
-
if (audit !== void 0 && typeof audit !== "boolean") {
|
|
179
|
-
issues.push(error(target, 'Optional field "audit" must be a boolean when present.'));
|
|
180
|
-
}
|
|
181
168
|
if (indexes !== void 0) {
|
|
182
169
|
if (!Array.isArray(indexes)) {
|
|
183
170
|
issues.push(error(target, 'Optional field "indexes" must be an array when present.'));
|
|
@@ -906,11 +893,6 @@ function stripNamespacePrefix(name, appName) {
|
|
|
906
893
|
}
|
|
907
894
|
return name;
|
|
908
895
|
}
|
|
909
|
-
function namespacedCollectionID(id, appName) {
|
|
910
|
-
if (!appName) return id;
|
|
911
|
-
const prefix = sanitizeSlugForCollectionName(appName) + NAMESPACE_SEPARATOR;
|
|
912
|
-
return id.startsWith(prefix) ? id : prefix + id;
|
|
913
|
-
}
|
|
914
896
|
var AGENT_IDLE_REAP_TIMEOUT_MIN_SECONDS = 10;
|
|
915
897
|
var AGENT_IDLE_REAP_TIMEOUT_MAX_SECONDS = 900;
|
|
916
898
|
function buildCollectionIdMap(collections, appName) {
|
|
@@ -922,7 +904,6 @@ function buildCollectionIdMap(collections, appName) {
|
|
|
922
904
|
for (const collection of collections) {
|
|
923
905
|
add(collection.id, collection.id);
|
|
924
906
|
add(collection.name, collection.id);
|
|
925
|
-
add(stripNamespacePrefix(collection.id, appName), collection.id);
|
|
926
907
|
const stripped = stripNamespacePrefix(collection.name, appName);
|
|
927
908
|
add(stripped, collection.id);
|
|
928
909
|
if (collection.name.includes(NAMESPACE_SEPARATOR)) {
|
|
@@ -1385,10 +1366,6 @@ function loadLocalCollections(platformDir, filterName) {
|
|
|
1385
1366
|
errors.push(`${file}: missing fields array`);
|
|
1386
1367
|
continue;
|
|
1387
1368
|
}
|
|
1388
|
-
if (collection.audit !== void 0 && typeof collection.audit !== "boolean") {
|
|
1389
|
-
errors.push(`${file}: audit must be a boolean when present`);
|
|
1390
|
-
continue;
|
|
1391
|
-
}
|
|
1392
1369
|
for (let i = 0; i < collection.fields.length; i++) {
|
|
1393
1370
|
const field = collection.fields[i];
|
|
1394
1371
|
if (!field || typeof field !== "object") {
|
|
@@ -1565,22 +1542,15 @@ function convertCollectionToApiFormat(local) {
|
|
|
1565
1542
|
const indexName = `idx_${local.id}_${idx.fields.join("_")}`;
|
|
1566
1543
|
return `CREATE ${unique}INDEX ${indexName} ON ${local.name} (${fields})`;
|
|
1567
1544
|
});
|
|
1568
|
-
return {
|
|
1569
|
-
id: local.id,
|
|
1570
|
-
name: local.name,
|
|
1571
|
-
schema,
|
|
1572
|
-
indexes,
|
|
1573
|
-
...local.audit !== void 0 ? { audit: local.audit } : {},
|
|
1574
|
-
search: local.search ?? null
|
|
1575
|
-
};
|
|
1545
|
+
return { id: local.id, name: local.name, schema, indexes, search: local.search ?? null };
|
|
1576
1546
|
}
|
|
1577
1547
|
function mapFieldType(type) {
|
|
1578
1548
|
const typeMap = {
|
|
1579
1549
|
text: "text",
|
|
1580
1550
|
number: "number",
|
|
1581
1551
|
bool: "bool",
|
|
1582
|
-
email: "
|
|
1583
|
-
url: "
|
|
1552
|
+
email: "text",
|
|
1553
|
+
url: "text",
|
|
1584
1554
|
date: "date",
|
|
1585
1555
|
select: "select",
|
|
1586
1556
|
relation: "relation",
|
|
@@ -1601,7 +1571,7 @@ function normalizeConstraintValue(fieldType, value) {
|
|
|
1601
1571
|
}
|
|
1602
1572
|
return value;
|
|
1603
1573
|
}
|
|
1604
|
-
function fieldsDiffer(local, remote
|
|
1574
|
+
function fieldsDiffer(local, remote) {
|
|
1605
1575
|
if (mapFieldType(local.type) !== remote.type) return true;
|
|
1606
1576
|
if ((local.required || false) !== (remote.required || false)) return true;
|
|
1607
1577
|
const opts = remote.options || {};
|
|
@@ -1615,8 +1585,7 @@ function fieldsDiffer(local, remote, appName) {
|
|
|
1615
1585
|
if (!localMultiple && remoteMaxSelect > 1) return true;
|
|
1616
1586
|
}
|
|
1617
1587
|
if (local.type === "relation") {
|
|
1618
|
-
|
|
1619
|
-
if (local.collection && local.collection !== remoteCollection) return true;
|
|
1588
|
+
if (local.collection && local.collection !== opts.collectionId) return true;
|
|
1620
1589
|
const localMultiple = local.multiple || false;
|
|
1621
1590
|
const remoteMaxSelect = opts.maxSelect || 1;
|
|
1622
1591
|
if (localMultiple && remoteMaxSelect <= 1) return true;
|
|
@@ -1683,22 +1652,19 @@ function normalizeCollectionSearch(search) {
|
|
|
1683
1652
|
} : {}
|
|
1684
1653
|
});
|
|
1685
1654
|
}
|
|
1686
|
-
async function planCollections(api, localCollections
|
|
1655
|
+
async function planCollections(api, localCollections) {
|
|
1687
1656
|
const changes = [];
|
|
1688
1657
|
const remoteCollections = await api.listCollections();
|
|
1689
1658
|
const remoteById = new Map(remoteCollections.map((c) => [c.id, c]));
|
|
1690
|
-
const remoteByLocalName = new Map(remoteCollections.map((c) => [stripNamespacePrefix(c.name, appName), c]));
|
|
1691
1659
|
for (const local of localCollections) {
|
|
1692
|
-
const remote =
|
|
1660
|
+
const remote = remoteById.get(local.id);
|
|
1693
1661
|
if (!remote) {
|
|
1694
|
-
const details = [`${local.fields.length} fields`];
|
|
1695
|
-
if (local.audit) details.push("record audit enabled");
|
|
1696
1662
|
changes.push({
|
|
1697
1663
|
type: "create",
|
|
1698
1664
|
resource: "collection",
|
|
1699
1665
|
id: local.id,
|
|
1700
1666
|
name: local.name,
|
|
1701
|
-
details:
|
|
1667
|
+
details: `${local.fields.length} fields`,
|
|
1702
1668
|
fieldDetails: local.fields.map((f) => ({
|
|
1703
1669
|
action: "+",
|
|
1704
1670
|
name: f.name,
|
|
@@ -1718,21 +1684,19 @@ async function planCollections(api, localCollections, appName) {
|
|
|
1718
1684
|
if (!remoteFieldNames.has(name)) continue;
|
|
1719
1685
|
const localField = localFieldMap.get(name);
|
|
1720
1686
|
const remoteField = remoteFieldMap.get(name);
|
|
1721
|
-
if (fieldsDiffer(localField, remoteField
|
|
1687
|
+
if (fieldsDiffer(localField, remoteField)) {
|
|
1722
1688
|
modified.push(name);
|
|
1723
1689
|
}
|
|
1724
1690
|
}
|
|
1725
1691
|
const indexesChanged = indexesDiffer(local, remote.indexes || []);
|
|
1726
1692
|
const searchChanged = normalizeCollectionSearch(local.search) !== normalizeCollectionSearch(remote.search);
|
|
1727
|
-
|
|
1728
|
-
if (added.length > 0 || removed.length > 0 || modified.length > 0 || indexesChanged || searchChanged || auditChanged) {
|
|
1693
|
+
if (added.length > 0 || removed.length > 0 || modified.length > 0 || indexesChanged || searchChanged) {
|
|
1729
1694
|
const details = [];
|
|
1730
1695
|
if (added.length > 0) details.push(`+${added.length} field${added.length > 1 ? "s" : ""}`);
|
|
1731
1696
|
if (removed.length > 0) details.push(`-${removed.length} field${removed.length > 1 ? "s" : ""}`);
|
|
1732
1697
|
if (modified.length > 0) details.push(`~${modified.length} field${modified.length > 1 ? "s" : ""} (${modified.join(", ")})`);
|
|
1733
1698
|
if (indexesChanged) details.push("indexes changed");
|
|
1734
1699
|
if (searchChanged) details.push("search changed");
|
|
1735
|
-
if (auditChanged) details.push(local.audit ? "record audit enabled" : "record audit disabled");
|
|
1736
1700
|
const fieldDetails = [];
|
|
1737
1701
|
for (const name of added) {
|
|
1738
1702
|
const f = localFieldMap.get(name);
|
|
@@ -2200,23 +2164,25 @@ async function applyApp(args) {
|
|
|
2200
2164
|
}
|
|
2201
2165
|
}
|
|
2202
2166
|
const distDir = resolve(projectRoot, "dist");
|
|
2203
|
-
|
|
2167
|
+
const accessLevel = getFlagValue(args, "access-level");
|
|
2168
|
+
const validAccessLevels = [
|
|
2169
|
+
"org_members_with_permission",
|
|
2170
|
+
"org_members",
|
|
2171
|
+
"invited",
|
|
2172
|
+
"signed_in",
|
|
2173
|
+
"public"
|
|
2174
|
+
];
|
|
2175
|
+
if (accessLevel && !validAccessLevels.includes(accessLevel)) {
|
|
2176
|
+
throw new Error(
|
|
2177
|
+
`Invalid --access-level "${accessLevel}". Valid values: ${validAccessLevels.join(", ")}`
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
2180
|
+
await deploy({ token, appName, appTitle, distDir, apiUrl, accessLevel });
|
|
2204
2181
|
}
|
|
2205
2182
|
async function pullCollections(api, platformDir, filterName, appName) {
|
|
2206
2183
|
const collectionsDir = join2(platformDir, "collections");
|
|
2207
2184
|
mkdirSync(collectionsDir, { recursive: true });
|
|
2208
2185
|
const collections = await api.listCollections();
|
|
2209
|
-
const existingFiles = /* @__PURE__ */ new Map();
|
|
2210
|
-
for (const file of readdirSync2(collectionsDir)) {
|
|
2211
|
-
if (!file.endsWith(".json")) continue;
|
|
2212
|
-
const filePath = join2(collectionsDir, file);
|
|
2213
|
-
try {
|
|
2214
|
-
const existing = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
2215
|
-
if (typeof existing.id === "string") existingFiles.set(existing.id, filePath);
|
|
2216
|
-
if (typeof existing.name === "string") existingFiles.set(existing.name, filePath);
|
|
2217
|
-
} catch {
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
2186
|
for (const collection of collections) {
|
|
2221
2187
|
if (collection.system || collection.managed) continue;
|
|
2222
2188
|
if (appName && collection.name.includes(NAMESPACE_SEPARATOR)) {
|
|
@@ -2224,14 +2190,12 @@ async function pullCollections(api, platformDir, filterName, appName) {
|
|
|
2224
2190
|
if (prefix !== sanitizeSlugForCollectionName(appName)) continue;
|
|
2225
2191
|
}
|
|
2226
2192
|
const localName = stripNamespacePrefix(collection.name, appName);
|
|
2227
|
-
|
|
2228
|
-
if (filterName && localName !== filterName && localID !== filterName && collection.name !== filterName && collection.id !== filterName) {
|
|
2193
|
+
if (filterName && localName !== filterName && collection.name !== filterName && collection.id !== filterName) {
|
|
2229
2194
|
continue;
|
|
2230
2195
|
}
|
|
2231
2196
|
const localFormat = {
|
|
2232
|
-
id:
|
|
2197
|
+
id: collection.id,
|
|
2233
2198
|
name: localName,
|
|
2234
|
-
audit: collection.audit,
|
|
2235
2199
|
fields: collection.schema.map((field) => {
|
|
2236
2200
|
const localField = {
|
|
2237
2201
|
name: field.name,
|
|
@@ -2240,9 +2204,7 @@ async function pullCollections(api, platformDir, filterName, appName) {
|
|
|
2240
2204
|
};
|
|
2241
2205
|
if (field.options) {
|
|
2242
2206
|
if (field.options.values) localField.values = field.options.values;
|
|
2243
|
-
if (field.options.collectionId)
|
|
2244
|
-
localField.collection = stripNamespacePrefix(field.options.collectionId, appName);
|
|
2245
|
-
}
|
|
2207
|
+
if (field.options.collectionId) localField.collection = field.options.collectionId;
|
|
2246
2208
|
if (field.options.maxSelect && field.options.maxSelect > 1) localField.multiple = true;
|
|
2247
2209
|
if (field.options.defaultCurrency) {
|
|
2248
2210
|
localField.defaultCurrency = field.options.defaultCurrency;
|
|
@@ -2262,10 +2224,10 @@ async function pullCollections(api, platformDir, filterName, appName) {
|
|
|
2262
2224
|
}).filter((idx) => idx !== null),
|
|
2263
2225
|
search: collection.search
|
|
2264
2226
|
};
|
|
2265
|
-
const fileName =
|
|
2266
|
-
const filePath =
|
|
2227
|
+
const fileName = toSafeFilename(localName);
|
|
2228
|
+
const filePath = join2(collectionsDir, `${fileName}.json`);
|
|
2267
2229
|
writeFileSync(filePath, JSON.stringify(localFormat, null, 2) + "\n");
|
|
2268
|
-
console.log(pc2.green(" \u2713"), `${localName} \u2192 collections/${
|
|
2230
|
+
console.log(pc2.green(" \u2713"), `${localName} \u2192 collections/${fileName}.json`);
|
|
2269
2231
|
}
|
|
2270
2232
|
}
|
|
2271
2233
|
async function pullAutomations(api, platformDir, filterName, projectId) {
|
|
@@ -2850,10 +2812,9 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
2850
2812
|
if (!resourceType || resourceType === "collections") {
|
|
2851
2813
|
const localCollections = loadLocalCollections(platformDir, resourceName || void 0);
|
|
2852
2814
|
const remoteCollections = await api.listCollections();
|
|
2853
|
-
const
|
|
2854
|
-
const remoteByLocalName = new Map(remoteCollections.map((c) => [stripNamespacePrefix(c.name, appName), c]));
|
|
2815
|
+
const remoteByName = new Map(remoteCollections.map((c) => [c.name, c]));
|
|
2855
2816
|
for (const local of localCollections) {
|
|
2856
|
-
const remote =
|
|
2817
|
+
const remote = remoteByName.get(local.name);
|
|
2857
2818
|
if (remote) {
|
|
2858
2819
|
toDelete.push({ type: "collection", id: local.id, name: local.name, remoteId: remote.id });
|
|
2859
2820
|
}
|
|
@@ -3056,9 +3017,7 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
3056
3017
|
const localCollections = loadLocalCollections(platformDir, resourceName);
|
|
3057
3018
|
const remoteCollections = await api.listCollections();
|
|
3058
3019
|
const local = localCollections[0];
|
|
3059
|
-
const remote = remoteCollections.find(
|
|
3060
|
-
(collection) => stripNamespacePrefix(collection.name, appName) === resourceName || stripNamespacePrefix(collection.id, appName) === resourceName
|
|
3061
|
-
);
|
|
3020
|
+
const remote = remoteCollections.find((c) => c.name === resourceName || c.id === resourceName);
|
|
3062
3021
|
if (!local && !remote) {
|
|
3063
3022
|
console.log(pc2.red(` Collection "${resourceName}" not found`));
|
|
3064
3023
|
process.exit(1);
|
|
@@ -3289,7 +3248,7 @@ async function plan(args) {
|
|
|
3289
3248
|
if (!type || type === "collections") {
|
|
3290
3249
|
const localCollections = loadLocalCollections(platformDir, name || void 0);
|
|
3291
3250
|
if (localCollections.length > 0) {
|
|
3292
|
-
const changes = await planCollections(api, localCollections
|
|
3251
|
+
const changes = await planCollections(api, localCollections);
|
|
3293
3252
|
allChanges.push(...changes);
|
|
3294
3253
|
}
|
|
3295
3254
|
}
|
|
@@ -3436,7 +3395,7 @@ async function apply(args) {
|
|
|
3436
3395
|
const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
|
|
3437
3396
|
const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
|
|
3438
3397
|
const localMailboxes = !type || type === "mailboxes" ? loadLocalMailboxes(platformDir, name || void 0) : [];
|
|
3439
|
-
if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections
|
|
3398
|
+
if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections));
|
|
3440
3399
|
if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations, projectId));
|
|
3441
3400
|
if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections, projectId));
|
|
3442
3401
|
if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents, projectId));
|
|
@@ -3958,15 +3917,10 @@ async function diff(args) {
|
|
|
3958
3917
|
}
|
|
3959
3918
|
export {
|
|
3960
3919
|
apply,
|
|
3961
|
-
applyCollections,
|
|
3962
|
-
convertCollectionToApiFormat,
|
|
3963
3920
|
destroy,
|
|
3964
|
-
destroyResources,
|
|
3965
3921
|
diff,
|
|
3966
3922
|
list,
|
|
3967
3923
|
plan,
|
|
3968
|
-
planCollections,
|
|
3969
3924
|
pull,
|
|
3970
|
-
pullCollections,
|
|
3971
3925
|
show
|
|
3972
3926
|
};
|
package/package.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type HostPayload,
|
|
3
|
+
isEmbedded,
|
|
4
|
+
onInitMessage,
|
|
5
|
+
postReadyMessage,
|
|
6
|
+
redirectToLogin,
|
|
7
|
+
} from '@lumerahq/ui/lib';
|
|
2
8
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
9
|
import { createHashHistory, createRouter, RouterProvider } from '@tanstack/react-router';
|
|
4
10
|
import { StrictMode, useEffect, useRef, useState } from 'react';
|
|
@@ -60,24 +66,27 @@ function RouteRestorer() {
|
|
|
60
66
|
|
|
61
67
|
const App = () => {
|
|
62
68
|
const [hostContext, setHostContext] = useState<AuthContextValue | null>(null);
|
|
63
|
-
const [status, setStatus] = useState<'pending' | 'ready' | '
|
|
69
|
+
const [status, setStatus] = useState<'pending' | 'ready' | 'unauthenticated'>('pending');
|
|
64
70
|
|
|
65
71
|
useEffect(() => {
|
|
66
72
|
if (typeof window === 'undefined') return;
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
setStatus('denied');
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
74
|
+
// The bridge picks the transport automatically: postMessage when embedded
|
|
75
|
+
// in the Lumera shell, same-origin /api with the app-session cookie when
|
|
76
|
+
// served standalone on the app's own hostname, and direct dev fetch when
|
|
77
|
+
// VITE_DEV_API_BASE_URL is set.
|
|
76
78
|
const cleanup = onInitMessage((payload?: HostPayload) => {
|
|
79
|
+
if (!payload) {
|
|
80
|
+
// No identity and no app context: authentication is required
|
|
81
|
+
// (payload.user alone being undefined means an anonymous visitor on
|
|
82
|
+
// a public app — that still counts as ready).
|
|
83
|
+
setStatus('unauthenticated');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
77
86
|
setHostContext({
|
|
78
|
-
company: payload
|
|
79
|
-
user: payload
|
|
80
|
-
sessionToken: payload
|
|
87
|
+
company: payload.company,
|
|
88
|
+
user: payload.user,
|
|
89
|
+
sessionToken: payload.session?.token,
|
|
81
90
|
});
|
|
82
91
|
setStatus('ready');
|
|
83
92
|
});
|
|
@@ -99,16 +108,23 @@ const App = () => {
|
|
|
99
108
|
);
|
|
100
109
|
}
|
|
101
110
|
|
|
102
|
-
if (status === '
|
|
111
|
+
if (status === 'unauthenticated' || !hostContext) {
|
|
103
112
|
return (
|
|
104
113
|
<main className="flex min-h-screen items-center justify-center bg-background">
|
|
105
114
|
<div className="text-center space-y-3">
|
|
106
115
|
<div className="inline-flex size-12 rounded-xl bg-muted items-center justify-center mb-2">
|
|
107
|
-
<span className="text-2xl font-semibold text-muted-foreground">
|
|
116
|
+
<span className="text-2xl font-semibold text-muted-foreground">401</span>
|
|
108
117
|
</div>
|
|
109
|
-
<p className="text-sm text-muted-foreground">
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
<p className="text-sm text-muted-foreground">Sign in to access this app.</p>
|
|
119
|
+
{!isEmbedded() && (
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
className="text-sm font-medium text-primary underline underline-offset-4"
|
|
123
|
+
onClick={() => redirectToLogin()}
|
|
124
|
+
>
|
|
125
|
+
Sign in with Lumera
|
|
126
|
+
</button>
|
|
127
|
+
)}
|
|
112
128
|
</div>
|
|
113
129
|
</main>
|
|
114
130
|
);
|
|
@@ -20,6 +20,8 @@ export default defineConfig({
|
|
|
20
20
|
dedupe: ['react', 'react-dom'],
|
|
21
21
|
},
|
|
22
22
|
server: {
|
|
23
|
-
|
|
23
|
+
// Local platform host plus standalone app hostnames
|
|
24
|
+
// (<project>--<company>.<env-suffix> on lumerahq.app).
|
|
25
|
+
allowedHosts: ['mac.lumerahq.com', '.lumerahq.app'],
|
|
24
26
|
},
|
|
25
27
|
});
|