@lumerahq/cli 0.24.3 → 0.24.5

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.
@@ -24,21 +24,14 @@ function formatTimestampVersion(date = /* @__PURE__ */ new Date()) {
24
24
  pad(date.getUTCSeconds())
25
25
  ].join("") + "Z";
26
26
  }
27
- function resolveVersion(cwd) {
27
+ function resolveVersion(cwd, gitRelease = resolveGitReleaseState(cwd)) {
28
28
  const pkgPath = resolve(cwd, "package.json");
29
29
  try {
30
30
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
31
31
  if (pkg?.version && pkg.version !== "0.0.0") return String(pkg.version);
32
32
  } catch {
33
33
  }
34
- try {
35
- const sha = execSync("git rev-parse --short HEAD", {
36
- cwd,
37
- stdio: ["ignore", "pipe", "ignore"]
38
- }).toString().trim();
39
- if (sha) return sha;
40
- } catch {
41
- }
34
+ if (gitRelease.sha) return gitRelease.sha.slice(0, 7);
42
35
  return formatTimestampVersion();
43
36
  }
44
37
  function resolveUIVersion(cwd) {
@@ -59,14 +52,30 @@ function resolveUIVersion(cwd) {
59
52
  return "";
60
53
  }
61
54
  }
62
- function resolveGitSha(cwd) {
55
+ function assertDeployableGitRelease(gitRelease) {
56
+ if (gitRelease.sha && gitRelease.dirty) {
57
+ throw new Error(
58
+ "Deploy requires a clean Git working tree so the published app matches its recorded commit. Commit or stash changes, then run lumera apply again."
59
+ );
60
+ }
61
+ }
62
+ function resolveGitReleaseState(cwd) {
63
63
  try {
64
- return execSync("git rev-parse HEAD", {
65
- cwd,
66
- stdio: ["ignore", "pipe", "ignore"]
67
- }).toString().trim();
64
+ const lines = execSync(
65
+ "git status --porcelain=v2 --branch --untracked-files=normal",
66
+ {
67
+ cwd,
68
+ stdio: ["ignore", "pipe", "ignore"]
69
+ }
70
+ ).toString().trim().split("\n");
71
+ const oidLine = lines.find((line) => line.startsWith("# branch.oid "));
72
+ const oid = oidLine?.slice("# branch.oid ".length).trim() ?? "";
73
+ return {
74
+ sha: oid === "(initial)" ? "" : oid,
75
+ dirty: lines.some((line) => line !== "" && !line.startsWith("# "))
76
+ };
68
77
  } catch {
69
- return "";
78
+ return { sha: "", dirty: false };
70
79
  }
71
80
  }
72
81
  async function createTarball(distDir) {
@@ -114,19 +123,21 @@ async function deploy(options) {
114
123
  if (!existsSync(distDir)) {
115
124
  throw new Error(`dist directory not found: ${distDir}`);
116
125
  }
126
+ const projectDir = dirname(distDir);
127
+ const gitRelease = resolveGitReleaseState(projectDir);
128
+ assertDeployableGitRelease(gitRelease);
117
129
  console.log(pc.dim("Ensuring app record..."));
118
130
  await ensureAppRecord(apiUrl, token, appName, appTitle, accessLevel);
119
131
  console.log(pc.cyan(`Deploying ${appTitle}...`));
120
- const version = options.version || process.env.LUMERA_APP_VERSION?.trim() || resolveVersion(dirname(distDir));
132
+ const version = options.version || process.env.LUMERA_APP_VERSION?.trim() || resolveVersion(projectDir, gitRelease);
121
133
  console.log(pc.dim(`Version: ${version}`));
122
134
  const tarball = await createTarball(distDir);
123
135
  console.log(pc.dim(`Package: ${(tarball.length / 1024).toFixed(1)} KB`));
124
136
  const formData = new FormData();
125
137
  formData.append("tarball", new Blob([new Uint8Array(tarball)]), "dist.tar.gz");
126
138
  formData.append("version", version);
127
- const gitSha = resolveGitSha(dirname(distDir));
128
- if (gitSha) formData.append("git_sha", gitSha);
129
- const uiVersion = resolveUIVersion(dirname(distDir));
139
+ if (gitRelease.sha) formData.append("git_sha", gitRelease.sha);
140
+ const uiVersion = resolveUIVersion(projectDir);
130
141
  if (uiVersion) formData.append("ui_version", uiVersion);
131
142
  const res = await fetchWithRetry(`${apiUrl}/custom-apps/${appName}/deploy`, {
132
143
  method: "POST",
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  dev
3
- } from "./chunk-KBNM6R5X.js";
3
+ } from "./chunk-KC3VBP6R.js";
4
4
  import {
5
5
  syncDeps
6
6
  } from "./chunk-7ZTCXKII.js";
package/dist/index.js CHANGED
@@ -227,29 +227,29 @@ async function main() {
227
227
  switch (command) {
228
228
  // Resource commands
229
229
  case "plan":
230
- await import("./resources-CCVVK3H2.js").then((m) => m.plan(args.slice(1)));
230
+ await import("./resources-XQHHIQYV.js").then((m) => m.plan(args.slice(1)));
231
231
  break;
232
232
  case "apply":
233
- await import("./resources-CCVVK3H2.js").then((m) => m.apply(args.slice(1)));
233
+ await import("./resources-XQHHIQYV.js").then((m) => m.apply(args.slice(1)));
234
234
  break;
235
235
  case "pull":
236
- await import("./resources-CCVVK3H2.js").then((m) => m.pull(args.slice(1)));
236
+ await import("./resources-XQHHIQYV.js").then((m) => m.pull(args.slice(1)));
237
237
  break;
238
238
  case "destroy":
239
- await import("./resources-CCVVK3H2.js").then((m) => m.destroy(args.slice(1)));
239
+ await import("./resources-XQHHIQYV.js").then((m) => m.destroy(args.slice(1)));
240
240
  break;
241
241
  case "list":
242
- await import("./resources-CCVVK3H2.js").then((m) => m.list(args.slice(1)));
242
+ await import("./resources-XQHHIQYV.js").then((m) => m.list(args.slice(1)));
243
243
  break;
244
244
  case "show":
245
- await import("./resources-CCVVK3H2.js").then((m) => m.show(args.slice(1)));
245
+ await import("./resources-XQHHIQYV.js").then((m) => m.show(args.slice(1)));
246
246
  break;
247
247
  case "diff":
248
- await import("./resources-CCVVK3H2.js").then((m) => m.diff(args.slice(1)));
248
+ await import("./resources-XQHHIQYV.js").then((m) => m.diff(args.slice(1)));
249
249
  break;
250
250
  // Development
251
251
  case "dev":
252
- await import("./dev-N5CEEMFC.js").then((m) => m.dev(args.slice(1)));
252
+ await import("./dev-OJEV76DA.js").then((m) => m.dev(args.slice(1)));
253
253
  break;
254
254
  case "run":
255
255
  await import("./run-WHVUVIYB.js").then((m) => m.run(args.slice(1)));
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  deploy
3
- } from "./chunk-KBNM6R5X.js";
3
+ } from "./chunk-KC3VBP6R.js";
4
4
  import {
5
5
  syncDeps
6
6
  } from "./chunk-7ZTCXKII.js";
@@ -183,6 +183,11 @@ var collectionSchemaRule = {
183
183
  }
184
184
  if (!Array.isArray(index.fields) || !index.fields.every((field) => typeof field === "string" && field.trim() !== "")) {
185
185
  issues.push(error(target, `Index at index ${i} must include a non-empty string array "fields".`));
186
+ } else if (index.fields.length === 1 && index.fields[0].trim().toLowerCase() === "updated" && index.unique !== true) {
187
+ issues.push(error(
188
+ target,
189
+ `Index at index ${i} exactly duplicates Lumera's managed non-unique "updated" index. Remove the redundant declaration.`
190
+ ));
186
191
  }
187
192
  }
188
193
  }
@@ -898,6 +903,14 @@ function stripNamespacePrefix(name, appName) {
898
903
  }
899
904
  return name;
900
905
  }
906
+ function buildIndexSql(local, idx, appName) {
907
+ const fields = idx.fields.join(", ");
908
+ const unique = idx.unique ? "UNIQUE " : "";
909
+ const physicalId = appName ? sanitizeSlugForCollectionName(appName) + NAMESPACE_SEPARATOR + local.id : local.id;
910
+ const indexTypeTag = idx.unique ? "u" : "n";
911
+ const indexName = `idx_${physicalId}_${indexTypeTag}_${idx.fields.join("_")}`;
912
+ return `CREATE ${unique}INDEX ${indexName} ON ${local.name} (${fields})`;
913
+ }
901
914
  var AGENT_IDLE_REAP_TIMEOUT_MIN_SECONDS = 10;
902
915
  var AGENT_IDLE_REAP_TIMEOUT_MAX_SECONDS = 900;
903
916
  function buildCollectionIdMap(collections, appName) {
@@ -1516,7 +1529,7 @@ function loadLocalHooks(platformDir, filterName, appName) {
1516
1529
  }
1517
1530
  return hooks;
1518
1531
  }
1519
- function convertCollectionToApiFormat(local) {
1532
+ function convertCollectionToApiFormat(local, appName) {
1520
1533
  const schema = local.fields.map((field) => {
1521
1534
  const apiField = {
1522
1535
  name: field.name,
@@ -1545,12 +1558,7 @@ function convertCollectionToApiFormat(local) {
1545
1558
  }
1546
1559
  return apiField;
1547
1560
  });
1548
- const indexes = local.indexes?.map((idx) => {
1549
- const fields = idx.fields.join(", ");
1550
- const unique = idx.unique ? "UNIQUE " : "";
1551
- const indexName = `idx_${local.id}_${idx.fields.join("_")}`;
1552
- return `CREATE ${unique}INDEX ${indexName} ON ${local.name} (${fields})`;
1553
- });
1561
+ const indexes = local.indexes?.map((idx) => buildIndexSql(local, idx, appName));
1554
1562
  return {
1555
1563
  id: local.id,
1556
1564
  name: local.name,
@@ -1623,24 +1631,15 @@ function fieldsDiffer(local, remote) {
1623
1631
  if (localMax !== remoteMax) return true;
1624
1632
  return false;
1625
1633
  }
1626
- function localIndexesToSql(local) {
1634
+ function localIndexesToSql(local, appName) {
1627
1635
  if (!local.indexes || local.indexes.length === 0) return [];
1628
- return local.indexes.map((idx) => {
1629
- const fields = idx.fields.join(", ");
1630
- const unique = idx.unique ? "UNIQUE " : "";
1631
- const indexName = `idx_${local.id}_${idx.fields.join("_")}`;
1632
- return `CREATE ${unique}INDEX ${indexName} ON ${local.name} (${fields})`;
1633
- }).sort();
1636
+ return local.indexes.map((idx) => buildIndexSql(local, idx, appName)).sort();
1634
1637
  }
1635
1638
  function normalizeRemoteIndexes(remoteIndexes) {
1636
- return remoteIndexes.filter((idx) => {
1637
- const lower = idx.toLowerCase();
1638
- if (lower.includes("external_id") || lower.includes("updated")) return false;
1639
- return true;
1640
- }).sort();
1639
+ return [...remoteIndexes].sort();
1641
1640
  }
1642
- function indexesDiffer(local, remoteIndexes) {
1643
- const localSql = localIndexesToSql(local);
1641
+ function indexesDiffer(local, remoteIndexes, appName) {
1642
+ const localSql = localIndexesToSql(local, appName);
1644
1643
  const remoteSql = normalizeRemoteIndexes(remoteIndexes);
1645
1644
  if (localSql.length !== remoteSql.length) return true;
1646
1645
  for (let i = 0; i < localSql.length; i++) {
@@ -1668,7 +1667,7 @@ function normalizeCollectionSearch(search) {
1668
1667
  } : {}
1669
1668
  });
1670
1669
  }
1671
- async function planCollections(api, localCollections) {
1670
+ async function planCollections(api, localCollections, appName) {
1672
1671
  const changes = [];
1673
1672
  const remoteCollections = await api.listCollections();
1674
1673
  const remoteById = new Map(remoteCollections.map((c) => [c.id, c]));
@@ -1722,7 +1721,7 @@ async function planCollections(api, localCollections) {
1722
1721
  modified.push(name);
1723
1722
  }
1724
1723
  }
1725
- const indexesChanged = indexesDiffer(local, remote.indexes || []);
1724
+ const indexesChanged = indexesDiffer(local, remote.indexes || [], appName);
1726
1725
  const searchChanged = normalizeCollectionSearch(local.search) !== normalizeCollectionSearch(remote.search);
1727
1726
  const auditChanged = local.audit !== void 0 && local.audit !== remote.audit;
1728
1727
  if (added.length > 0 || removed.length > 0 || modified.length > 0 || indexesChanged || searchChanged || auditChanged) {
@@ -1992,7 +1991,7 @@ async function planHooks(api, localHooks, collections, projectId) {
1992
1991
  }
1993
1992
  return changes;
1994
1993
  }
1995
- async function applyCollections(api, localCollections) {
1994
+ async function applyCollections(api, localCollections, appName) {
1996
1995
  let errors = 0;
1997
1996
  const hasRelations = localCollections.some((c) => c.fields.some((f) => f.type === "relation"));
1998
1997
  if (hasRelations) {
@@ -2009,7 +2008,7 @@ async function applyCollections(api, localCollections) {
2009
2008
  }
2010
2009
  for (const local of localCollections) {
2011
2010
  if (pass1Failed.has(local.name)) continue;
2012
- const apiFormat = convertCollectionToApiFormat(local);
2011
+ const apiFormat = convertCollectionToApiFormat(local, appName);
2013
2012
  try {
2014
2013
  await api.ensureCollection(local.name, apiFormat);
2015
2014
  console.log(pc2.green(" \u2713"), `${local.name} (schema)`);
@@ -2020,7 +2019,7 @@ async function applyCollections(api, localCollections) {
2020
2019
  }
2021
2020
  } else {
2022
2021
  for (const local of localCollections) {
2023
- const apiFormat = convertCollectionToApiFormat(local);
2022
+ const apiFormat = convertCollectionToApiFormat(local, appName);
2024
2023
  try {
2025
2024
  await api.ensureCollection(local.name, apiFormat);
2026
2025
  console.log(pc2.green(" \u2713"), `${local.name}`);
@@ -3282,7 +3281,7 @@ async function plan(args) {
3282
3281
  if (!type || type === "collections") {
3283
3282
  const localCollections = loadLocalCollections(platformDir, name || void 0);
3284
3283
  if (localCollections.length > 0) {
3285
- const changes = await planCollections(api, localCollections);
3284
+ const changes = await planCollections(api, localCollections, appName);
3286
3285
  allChanges.push(...changes);
3287
3286
  }
3288
3287
  }
@@ -3429,7 +3428,7 @@ async function apply(args) {
3429
3428
  const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
3430
3429
  const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
3431
3430
  const localMailboxes = !type || type === "mailboxes" ? loadLocalMailboxes(platformDir, name || void 0) : [];
3432
- if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections));
3431
+ if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections, appName));
3433
3432
  if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations, projectId));
3434
3433
  if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections, projectId));
3435
3434
  if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents, projectId));
@@ -3504,7 +3503,7 @@ async function apply(args) {
3504
3503
  let totalSkipped = 0;
3505
3504
  if (localCollections.length > 0) {
3506
3505
  console.log(pc2.bold(" Collections:"));
3507
- totalErrors += await applyCollections(api, localCollections);
3506
+ totalErrors += await applyCollections(api, localCollections, appName);
3508
3507
  console.log();
3509
3508
  }
3510
3509
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.24.3",
3
+ "version": "0.24.5",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {