@lumerahq/cli 0.23.0-dev.0 → 0.23.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -226,25 +226,25 @@ async function main() {
226
226
  switch (command) {
227
227
  // Resource commands
228
228
  case "plan":
229
- await import("./resources-DWTWFCSA.js").then((m) => m.plan(args.slice(1)));
229
+ await import("./resources-7243WXIF.js").then((m) => m.plan(args.slice(1)));
230
230
  break;
231
231
  case "apply":
232
- await import("./resources-DWTWFCSA.js").then((m) => m.apply(args.slice(1)));
232
+ await import("./resources-7243WXIF.js").then((m) => m.apply(args.slice(1)));
233
233
  break;
234
234
  case "pull":
235
- await import("./resources-DWTWFCSA.js").then((m) => m.pull(args.slice(1)));
235
+ await import("./resources-7243WXIF.js").then((m) => m.pull(args.slice(1)));
236
236
  break;
237
237
  case "destroy":
238
- await import("./resources-DWTWFCSA.js").then((m) => m.destroy(args.slice(1)));
238
+ await import("./resources-7243WXIF.js").then((m) => m.destroy(args.slice(1)));
239
239
  break;
240
240
  case "list":
241
- await import("./resources-DWTWFCSA.js").then((m) => m.list(args.slice(1)));
241
+ await import("./resources-7243WXIF.js").then((m) => m.list(args.slice(1)));
242
242
  break;
243
243
  case "show":
244
- await import("./resources-DWTWFCSA.js").then((m) => m.show(args.slice(1)));
244
+ await import("./resources-7243WXIF.js").then((m) => m.show(args.slice(1)));
245
245
  break;
246
246
  case "diff":
247
- await import("./resources-DWTWFCSA.js").then((m) => m.diff(args.slice(1)));
247
+ await import("./resources-7243WXIF.js").then((m) => m.diff(args.slice(1)));
248
248
  break;
249
249
  // Development
250
250
  case "dev":
@@ -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, resolve } from "path";
34
+ import { join as join2, basename as basename2, resolve } from "path";
35
35
 
36
36
  // src/lib/lint/index.ts
37
37
  import { existsSync, readFileSync, readdirSync } from "fs";
@@ -164,6 +164,15 @@ var collectionSchemaRule = {
164
164
  if (typeof field.type !== "string" || field.type.trim() === "") {
165
165
  issues.push(error(target, `Field ${typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`} is missing required string field "type".`));
166
166
  }
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
+ }
167
176
  }
168
177
  }
169
178
  if (audit !== void 0 && typeof audit !== "boolean") {
@@ -897,6 +906,11 @@ function stripNamespacePrefix(name, appName) {
897
906
  }
898
907
  return name;
899
908
  }
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
+ }
900
914
  var AGENT_IDLE_REAP_TIMEOUT_MIN_SECONDS = 10;
901
915
  var AGENT_IDLE_REAP_TIMEOUT_MAX_SECONDS = 900;
902
916
  function buildCollectionIdMap(collections, appName) {
@@ -908,6 +922,7 @@ function buildCollectionIdMap(collections, appName) {
908
922
  for (const collection of collections) {
909
923
  add(collection.id, collection.id);
910
924
  add(collection.name, collection.id);
925
+ add(stripNamespacePrefix(collection.id, appName), collection.id);
911
926
  const stripped = stripNamespacePrefix(collection.name, appName);
912
927
  add(stripped, collection.id);
913
928
  if (collection.name.includes(NAMESPACE_SEPARATOR)) {
@@ -1564,8 +1579,8 @@ function mapFieldType(type) {
1564
1579
  text: "text",
1565
1580
  number: "number",
1566
1581
  bool: "bool",
1567
- email: "text",
1568
- url: "text",
1582
+ email: "email",
1583
+ url: "url",
1569
1584
  date: "date",
1570
1585
  select: "select",
1571
1586
  relation: "relation",
@@ -1586,7 +1601,7 @@ function normalizeConstraintValue(fieldType, value) {
1586
1601
  }
1587
1602
  return value;
1588
1603
  }
1589
- function fieldsDiffer(local, remote) {
1604
+ function fieldsDiffer(local, remote, appName) {
1590
1605
  if (mapFieldType(local.type) !== remote.type) return true;
1591
1606
  if ((local.required || false) !== (remote.required || false)) return true;
1592
1607
  const opts = remote.options || {};
@@ -1600,7 +1615,8 @@ function fieldsDiffer(local, remote) {
1600
1615
  if (!localMultiple && remoteMaxSelect > 1) return true;
1601
1616
  }
1602
1617
  if (local.type === "relation") {
1603
- if (local.collection && local.collection !== opts.collectionId) return true;
1618
+ const remoteCollection = stripNamespacePrefix(opts.collectionId || "", appName);
1619
+ if (local.collection && local.collection !== remoteCollection) return true;
1604
1620
  const localMultiple = local.multiple || false;
1605
1621
  const remoteMaxSelect = opts.maxSelect || 1;
1606
1622
  if (localMultiple && remoteMaxSelect <= 1) return true;
@@ -1667,12 +1683,13 @@ function normalizeCollectionSearch(search) {
1667
1683
  } : {}
1668
1684
  });
1669
1685
  }
1670
- async function planCollections(api, localCollections) {
1686
+ async function planCollections(api, localCollections, appName) {
1671
1687
  const changes = [];
1672
1688
  const remoteCollections = await api.listCollections();
1673
1689
  const remoteById = new Map(remoteCollections.map((c) => [c.id, c]));
1690
+ const remoteByLocalName = new Map(remoteCollections.map((c) => [stripNamespacePrefix(c.name, appName), c]));
1674
1691
  for (const local of localCollections) {
1675
- const remote = remoteById.get(local.id);
1692
+ const remote = remoteByLocalName.get(local.name) ?? remoteById.get(namespacedCollectionID(local.id, appName)) ?? (!appName ? remoteById.get(local.id) : void 0);
1676
1693
  if (!remote) {
1677
1694
  const details = [`${local.fields.length} fields`];
1678
1695
  if (local.audit) details.push("record audit enabled");
@@ -1701,7 +1718,7 @@ async function planCollections(api, localCollections) {
1701
1718
  if (!remoteFieldNames.has(name)) continue;
1702
1719
  const localField = localFieldMap.get(name);
1703
1720
  const remoteField = remoteFieldMap.get(name);
1704
- if (fieldsDiffer(localField, remoteField)) {
1721
+ if (fieldsDiffer(localField, remoteField, appName)) {
1705
1722
  modified.push(name);
1706
1723
  }
1707
1724
  }
@@ -2189,6 +2206,17 @@ async function pullCollections(api, platformDir, filterName, appName) {
2189
2206
  const collectionsDir = join2(platformDir, "collections");
2190
2207
  mkdirSync(collectionsDir, { recursive: true });
2191
2208
  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
+ }
2192
2220
  for (const collection of collections) {
2193
2221
  if (collection.system || collection.managed) continue;
2194
2222
  if (appName && collection.name.includes(NAMESPACE_SEPARATOR)) {
@@ -2196,11 +2224,12 @@ async function pullCollections(api, platformDir, filterName, appName) {
2196
2224
  if (prefix !== sanitizeSlugForCollectionName(appName)) continue;
2197
2225
  }
2198
2226
  const localName = stripNamespacePrefix(collection.name, appName);
2199
- if (filterName && localName !== filterName && collection.name !== filterName && collection.id !== filterName) {
2227
+ const localID = stripNamespacePrefix(collection.id, appName);
2228
+ if (filterName && localName !== filterName && localID !== filterName && collection.name !== filterName && collection.id !== filterName) {
2200
2229
  continue;
2201
2230
  }
2202
2231
  const localFormat = {
2203
- id: collection.id,
2232
+ id: localID,
2204
2233
  name: localName,
2205
2234
  audit: collection.audit,
2206
2235
  fields: collection.schema.map((field) => {
@@ -2211,7 +2240,9 @@ async function pullCollections(api, platformDir, filterName, appName) {
2211
2240
  };
2212
2241
  if (field.options) {
2213
2242
  if (field.options.values) localField.values = field.options.values;
2214
- if (field.options.collectionId) localField.collection = field.options.collectionId;
2243
+ if (field.options.collectionId) {
2244
+ localField.collection = stripNamespacePrefix(field.options.collectionId, appName);
2245
+ }
2215
2246
  if (field.options.maxSelect && field.options.maxSelect > 1) localField.multiple = true;
2216
2247
  if (field.options.defaultCurrency) {
2217
2248
  localField.defaultCurrency = field.options.defaultCurrency;
@@ -2231,10 +2262,10 @@ async function pullCollections(api, platformDir, filterName, appName) {
2231
2262
  }).filter((idx) => idx !== null),
2232
2263
  search: collection.search
2233
2264
  };
2234
- const fileName = toSafeFilename(localName);
2235
- const filePath = join2(collectionsDir, `${fileName}.json`);
2265
+ const fileName = `${toSafeFilename(localName)}.json`;
2266
+ const filePath = existingFiles.get(localFormat.id) || existingFiles.get(localFormat.name) || join2(collectionsDir, fileName);
2236
2267
  writeFileSync(filePath, JSON.stringify(localFormat, null, 2) + "\n");
2237
- console.log(pc2.green(" \u2713"), `${localName} \u2192 collections/${fileName}.json`);
2268
+ console.log(pc2.green(" \u2713"), `${localName} \u2192 collections/${basename2(filePath)}`);
2238
2269
  }
2239
2270
  }
2240
2271
  async function pullAutomations(api, platformDir, filterName, projectId) {
@@ -2820,9 +2851,9 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
2820
2851
  const localCollections = loadLocalCollections(platformDir, resourceName || void 0);
2821
2852
  const remoteCollections = await api.listCollections();
2822
2853
  const remoteById = new Map(remoteCollections.map((c) => [c.id, c]));
2823
- const remoteByName = new Map(remoteCollections.map((c) => [c.name, c]));
2854
+ const remoteByLocalName = new Map(remoteCollections.map((c) => [stripNamespacePrefix(c.name, appName), c]));
2824
2855
  for (const local of localCollections) {
2825
- const remote = remoteById.get(local.id) || remoteByName.get(local.name);
2856
+ const remote = remoteByLocalName.get(local.name) ?? remoteById.get(namespacedCollectionID(local.id, appName)) ?? (!appName ? remoteById.get(local.id) : void 0);
2826
2857
  if (remote) {
2827
2858
  toDelete.push({ type: "collection", id: local.id, name: local.name, remoteId: remote.id });
2828
2859
  }
@@ -3025,7 +3056,9 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
3025
3056
  const localCollections = loadLocalCollections(platformDir, resourceName);
3026
3057
  const remoteCollections = await api.listCollections();
3027
3058
  const local = localCollections[0];
3028
- const remote = remoteCollections.find((c) => c.name === resourceName || c.id === resourceName);
3059
+ const remote = remoteCollections.find(
3060
+ (collection) => stripNamespacePrefix(collection.name, appName) === resourceName || stripNamespacePrefix(collection.id, appName) === resourceName
3061
+ );
3029
3062
  if (!local && !remote) {
3030
3063
  console.log(pc2.red(` Collection "${resourceName}" not found`));
3031
3064
  process.exit(1);
@@ -3256,7 +3289,7 @@ async function plan(args) {
3256
3289
  if (!type || type === "collections") {
3257
3290
  const localCollections = loadLocalCollections(platformDir, name || void 0);
3258
3291
  if (localCollections.length > 0) {
3259
- const changes = await planCollections(api, localCollections);
3292
+ const changes = await planCollections(api, localCollections, appName);
3260
3293
  allChanges.push(...changes);
3261
3294
  }
3262
3295
  }
@@ -3403,7 +3436,7 @@ async function apply(args) {
3403
3436
  const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
3404
3437
  const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
3405
3438
  const localMailboxes = !type || type === "mailboxes" ? loadLocalMailboxes(platformDir, name || void 0) : [];
3406
- if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections));
3439
+ if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections, appName));
3407
3440
  if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations, projectId));
3408
3441
  if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections, projectId));
3409
3442
  if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents, projectId));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.23.0-dev.0",
3
+ "version": "0.23.0-dev.2",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {
@@ -24,7 +24,7 @@
24
24
  "check:ci": "biome check . && tsr generate && tsc --noEmit"
25
25
  },
26
26
  "dependencies": {
27
- "@lumerahq/ui": "^0.12.0",
27
+ "@lumerahq/ui": "^0.13.0",
28
28
  "@tanstack/react-query": "^5.90.11",
29
29
  "@tanstack/react-router": "1.155.0",
30
30
  "clsx": "^2.1.1",