@lumerahq/cli 0.17.1 → 0.18.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/index.js
CHANGED
|
@@ -215,25 +215,25 @@ async function main() {
|
|
|
215
215
|
switch (command) {
|
|
216
216
|
// Resource commands
|
|
217
217
|
case "plan":
|
|
218
|
-
await import("./resources-
|
|
218
|
+
await import("./resources-HLQTZV37.js").then((m) => m.plan(args.slice(1)));
|
|
219
219
|
break;
|
|
220
220
|
case "apply":
|
|
221
|
-
await import("./resources-
|
|
221
|
+
await import("./resources-HLQTZV37.js").then((m) => m.apply(args.slice(1)));
|
|
222
222
|
break;
|
|
223
223
|
case "pull":
|
|
224
|
-
await import("./resources-
|
|
224
|
+
await import("./resources-HLQTZV37.js").then((m) => m.pull(args.slice(1)));
|
|
225
225
|
break;
|
|
226
226
|
case "destroy":
|
|
227
|
-
await import("./resources-
|
|
227
|
+
await import("./resources-HLQTZV37.js").then((m) => m.destroy(args.slice(1)));
|
|
228
228
|
break;
|
|
229
229
|
case "list":
|
|
230
|
-
await import("./resources-
|
|
230
|
+
await import("./resources-HLQTZV37.js").then((m) => m.list(args.slice(1)));
|
|
231
231
|
break;
|
|
232
232
|
case "show":
|
|
233
|
-
await import("./resources-
|
|
233
|
+
await import("./resources-HLQTZV37.js").then((m) => m.show(args.slice(1)));
|
|
234
234
|
break;
|
|
235
235
|
case "diff":
|
|
236
|
-
await import("./resources-
|
|
236
|
+
await import("./resources-HLQTZV37.js").then((m) => m.diff(args.slice(1)));
|
|
237
237
|
break;
|
|
238
238
|
// Development
|
|
239
239
|
case "dev":
|
|
@@ -252,8 +252,10 @@ function parseResource(resourcePath) {
|
|
|
252
252
|
const parts = resourcePath.split("/");
|
|
253
253
|
const type = parts[0];
|
|
254
254
|
const name = parts.slice(1).join("/") || null;
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
const validTypes = ["collections", "automations", "hooks", "agents", "app"];
|
|
256
|
+
if (!validTypes.includes(type)) {
|
|
257
|
+
console.log(pc.red(` Unknown resource type "${type}". Valid types: ${validTypes.join(", ")}`));
|
|
258
|
+
process.exit(1);
|
|
257
259
|
}
|
|
258
260
|
return { type, name };
|
|
259
261
|
}
|
|
@@ -301,6 +303,16 @@ function loadLocalCollections(platformDir, filterName) {
|
|
|
301
303
|
errors.push(`${file}: collection name "${collection.name}" contains invalid characters`);
|
|
302
304
|
continue;
|
|
303
305
|
}
|
|
306
|
+
const existingById = collections.find((c) => c.id === collection.id);
|
|
307
|
+
if (existingById) {
|
|
308
|
+
errors.push(`${file}: duplicate collection id "${collection.id}" (also defined in another file)`);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const existingByName = collections.find((c) => c.name === collection.name);
|
|
312
|
+
if (existingByName) {
|
|
313
|
+
errors.push(`${file}: duplicate collection name "${collection.name}" (also defined in another file)`);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
304
316
|
collections.push(collection);
|
|
305
317
|
} catch (e) {
|
|
306
318
|
errors.push(`${file}: failed to parse - ${e}`);
|
|
@@ -357,6 +369,11 @@ function loadLocalAutomations(platformDir, filterName, appName) {
|
|
|
357
369
|
errors.push(`${entry.name}: missing inputs.schema in config.json`);
|
|
358
370
|
continue;
|
|
359
371
|
}
|
|
372
|
+
const existingByExtId = automations.find((a) => a.automation.external_id === config.external_id);
|
|
373
|
+
if (existingByExtId) {
|
|
374
|
+
errors.push(`${entry.name}: duplicate external_id "${config.external_id}" (also defined in ${existingByExtId.automation.name})`);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
360
377
|
const code = readFileSync(mainPath, "utf-8");
|
|
361
378
|
automations.push({ automation: config, code });
|
|
362
379
|
} catch (e) {
|
|
@@ -409,22 +426,28 @@ function loadLocalHooks(platformDir, filterName, appName) {
|
|
|
409
426
|
function parseHookConfig(content) {
|
|
410
427
|
const configMatch = content.match(/export\s+const\s+config\s*[=:]\s*(\{[\s\S]*?\});?/);
|
|
411
428
|
if (!configMatch) return null;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
429
|
+
const externalId = configMatch[1].match(/external_id\s*:\s*['"]([^'"]+)['"]/)?.[1];
|
|
430
|
+
const collection = configMatch[1].match(/collection\s*:\s*['"]([^'"]+)['"]/)?.[1];
|
|
431
|
+
const trigger = configMatch[1].match(/trigger\s*:\s*['"]([^'"]+)['"]/)?.[1];
|
|
432
|
+
const enabled = configMatch[1].match(/enabled\s*:\s*(true|false)/)?.[1];
|
|
433
|
+
const name = configMatch[1].match(/name\s*:\s*['"]([^'"]+)['"]/)?.[1];
|
|
434
|
+
if (!collection || !trigger) return null;
|
|
435
|
+
const hook = {
|
|
436
|
+
external_id: externalId || "",
|
|
437
|
+
collection,
|
|
438
|
+
trigger,
|
|
439
|
+
enabled: enabled !== "false"
|
|
440
|
+
};
|
|
441
|
+
if (name) hook.name = name;
|
|
442
|
+
const metadataMatch = configMatch[1].match(/metadata\s*:\s*(\{[^}]*\})/);
|
|
443
|
+
if (metadataMatch) {
|
|
444
|
+
try {
|
|
445
|
+
const metaStr = metadataMatch[1].replace(/'/g, '"').replace(/(\w+)\s*:/g, '"$1":').replace(/,\s*}/g, "}");
|
|
446
|
+
hook.metadata = JSON.parse(metaStr);
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
427
449
|
}
|
|
450
|
+
return hook;
|
|
428
451
|
}
|
|
429
452
|
function extractHookScript(content) {
|
|
430
453
|
const handlerMatch = content.match(
|
|
@@ -508,8 +531,37 @@ function fieldsDiffer(local, remote) {
|
|
|
508
531
|
if (localMultiple && remoteMaxSelect <= 1) return true;
|
|
509
532
|
if (!localMultiple && remoteMaxSelect > 1) return true;
|
|
510
533
|
}
|
|
511
|
-
|
|
512
|
-
|
|
534
|
+
const localMin = local.min !== void 0 ? local.min : void 0;
|
|
535
|
+
const remoteMin = opts.min !== void 0 ? opts.min : void 0;
|
|
536
|
+
if (localMin !== remoteMin) return true;
|
|
537
|
+
const localMax = local.max !== void 0 ? local.max : void 0;
|
|
538
|
+
const remoteMax = opts.max !== void 0 ? opts.max : void 0;
|
|
539
|
+
if (localMax !== remoteMax) return true;
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
function localIndexesToSql(local) {
|
|
543
|
+
if (!local.indexes || local.indexes.length === 0) return [];
|
|
544
|
+
return local.indexes.map((idx) => {
|
|
545
|
+
const fields = idx.fields.join(", ");
|
|
546
|
+
const unique = idx.unique ? "UNIQUE " : "";
|
|
547
|
+
const indexName = `idx_${local.id}_${idx.fields.join("_")}`;
|
|
548
|
+
return `CREATE ${unique}INDEX ${indexName} ON ${local.name} (${fields})`;
|
|
549
|
+
}).sort();
|
|
550
|
+
}
|
|
551
|
+
function normalizeRemoteIndexes(remoteIndexes) {
|
|
552
|
+
return remoteIndexes.filter((idx) => {
|
|
553
|
+
const lower = idx.toLowerCase();
|
|
554
|
+
if (lower.includes("external_id") || lower.includes("updated")) return false;
|
|
555
|
+
return true;
|
|
556
|
+
}).sort();
|
|
557
|
+
}
|
|
558
|
+
function indexesDiffer(local, remoteIndexes) {
|
|
559
|
+
const localSql = localIndexesToSql(local);
|
|
560
|
+
const remoteSql = normalizeRemoteIndexes(remoteIndexes);
|
|
561
|
+
if (localSql.length !== remoteSql.length) return true;
|
|
562
|
+
for (let i = 0; i < localSql.length; i++) {
|
|
563
|
+
if (localSql[i] !== remoteSql[i]) return true;
|
|
564
|
+
}
|
|
513
565
|
return false;
|
|
514
566
|
}
|
|
515
567
|
async function planCollections(api, localCollections) {
|
|
@@ -548,11 +600,13 @@ async function planCollections(api, localCollections) {
|
|
|
548
600
|
modified.push(name);
|
|
549
601
|
}
|
|
550
602
|
}
|
|
551
|
-
|
|
603
|
+
const indexesChanged = indexesDiffer(local, remote.indexes || []);
|
|
604
|
+
if (added.length > 0 || removed.length > 0 || modified.length > 0 || indexesChanged) {
|
|
552
605
|
const details = [];
|
|
553
606
|
if (added.length > 0) details.push(`+${added.length} field${added.length > 1 ? "s" : ""}`);
|
|
554
607
|
if (removed.length > 0) details.push(`-${removed.length} field${removed.length > 1 ? "s" : ""}`);
|
|
555
608
|
if (modified.length > 0) details.push(`~${modified.length} field${modified.length > 1 ? "s" : ""} (${modified.join(", ")})`);
|
|
609
|
+
if (indexesChanged) details.push("indexes changed");
|
|
556
610
|
const fieldDetails = [];
|
|
557
611
|
for (const name of added) {
|
|
558
612
|
const f = localFieldMap.get(name);
|
|
@@ -592,11 +646,19 @@ async function planAutomations(api, localAutomations) {
|
|
|
592
646
|
const codeChanged = remote.code !== code;
|
|
593
647
|
const nameChanged = remote.name !== automation.name;
|
|
594
648
|
const descChanged = (remote.description || "") !== (automation.description || "");
|
|
595
|
-
|
|
649
|
+
const localSchema = automation.inputs?.schema ? JSON.stringify(automation.inputs.schema) : "";
|
|
650
|
+
const remoteSchema = remote.input_schema ? typeof remote.input_schema === "string" ? remote.input_schema : JSON.stringify(remote.input_schema) : "";
|
|
651
|
+
const schemaChanged = localSchema !== remoteSchema;
|
|
652
|
+
const localCron = automation.schedule?.cron || "";
|
|
653
|
+
const remoteCron = remote.schedule || "";
|
|
654
|
+
const scheduleChanged = localCron !== remoteCron;
|
|
655
|
+
if (codeChanged || nameChanged || descChanged || schemaChanged || scheduleChanged) {
|
|
596
656
|
const details = [];
|
|
597
657
|
if (codeChanged) details.push("code");
|
|
598
658
|
if (nameChanged) details.push("name");
|
|
599
659
|
if (descChanged) details.push("description");
|
|
660
|
+
if (schemaChanged) details.push("input_schema");
|
|
661
|
+
if (scheduleChanged) details.push("schedule");
|
|
600
662
|
const textDiffs = [];
|
|
601
663
|
if (codeChanged) {
|
|
602
664
|
textDiffs.push({ field: "main.py", oldText: remote.code || "", newText: code });
|
|
@@ -635,10 +697,16 @@ async function planHooks(api, localHooks, collections) {
|
|
|
635
697
|
} else {
|
|
636
698
|
const scriptChanged = remote.script.trim() !== script.trim();
|
|
637
699
|
const eventChanged = remote.event !== hook.trigger;
|
|
638
|
-
|
|
700
|
+
const enabledChanged = remote.enabled !== void 0 && remote.enabled !== (hook.enabled !== false);
|
|
701
|
+
const nameChanged = hook.name && remote.name !== hook.name || false;
|
|
702
|
+
const collectionChanged = remote.collection_id !== collectionId;
|
|
703
|
+
if (scriptChanged || eventChanged || enabledChanged || nameChanged || collectionChanged) {
|
|
639
704
|
const details = [];
|
|
640
705
|
if (scriptChanged) details.push("script");
|
|
641
706
|
if (eventChanged) details.push("trigger");
|
|
707
|
+
if (enabledChanged) details.push("enabled");
|
|
708
|
+
if (nameChanged) details.push("name");
|
|
709
|
+
if (collectionChanged) details.push("collection");
|
|
642
710
|
const textDiffs = [];
|
|
643
711
|
if (scriptChanged) {
|
|
644
712
|
textDiffs.push({ field: fileName, oldText: remote.script || "", newText: script });
|
|
@@ -660,30 +728,25 @@ async function applyCollections(api, localCollections) {
|
|
|
660
728
|
let errors = 0;
|
|
661
729
|
const hasRelations = localCollections.some((c) => c.fields.some((f) => f.type === "relation"));
|
|
662
730
|
if (hasRelations) {
|
|
731
|
+
const pass1Failed = /* @__PURE__ */ new Set();
|
|
663
732
|
for (const local of localCollections) {
|
|
664
|
-
const relationFieldNames = new Set(local.fields.filter((f) => f.type === "relation").map((f) => f.name));
|
|
665
|
-
const withoutRelations = {
|
|
666
|
-
...local,
|
|
667
|
-
fields: local.fields.filter((f) => f.type !== "relation"),
|
|
668
|
-
indexes: local.indexes?.filter((idx) => !idx.fields.some((f) => relationFieldNames.has(f)))
|
|
669
|
-
};
|
|
670
|
-
const apiFormat = convertCollectionToApiFormat(withoutRelations);
|
|
671
733
|
try {
|
|
672
|
-
await api.ensureCollection(local.name,
|
|
734
|
+
await api.ensureCollection(local.name, { id: local.id });
|
|
673
735
|
console.log(pc.green(" \u2713"), `${local.name}`);
|
|
674
736
|
} catch (e) {
|
|
675
737
|
console.log(pc.red(" \u2717"), `${local.name}: ${e}`);
|
|
738
|
+
pass1Failed.add(local.name);
|
|
676
739
|
errors++;
|
|
677
740
|
}
|
|
678
741
|
}
|
|
679
|
-
const
|
|
680
|
-
|
|
742
|
+
for (const local of localCollections) {
|
|
743
|
+
if (pass1Failed.has(local.name)) continue;
|
|
681
744
|
const apiFormat = convertCollectionToApiFormat(local);
|
|
682
745
|
try {
|
|
683
746
|
await api.ensureCollection(local.name, apiFormat);
|
|
684
|
-
console.log(pc.green(" \u2713"), `${local.name} (
|
|
747
|
+
console.log(pc.green(" \u2713"), `${local.name} (schema)`);
|
|
685
748
|
} catch (e) {
|
|
686
|
-
console.log(pc.red(" \u2717"), `${local.name} (
|
|
749
|
+
console.log(pc.red(" \u2717"), `${local.name} (schema): ${e}`);
|
|
687
750
|
errors++;
|
|
688
751
|
}
|
|
689
752
|
}
|
|
@@ -746,6 +809,9 @@ async function applyAutomations(api, localAutomations) {
|
|
|
746
809
|
async function syncPresets(api, automationId, localPresets) {
|
|
747
810
|
const remotePresets = await api.listPresets(automationId);
|
|
748
811
|
const remoteByName = new Map(remotePresets.map((p) => [p.name, p]));
|
|
812
|
+
const localPresetNames = new Set(
|
|
813
|
+
Object.entries(localPresets).map(([key, preset]) => preset.label || key)
|
|
814
|
+
);
|
|
749
815
|
for (const [presetKey, preset] of Object.entries(localPresets)) {
|
|
750
816
|
const presetName = preset.label || presetKey;
|
|
751
817
|
const existing = remoteByName.get(presetName);
|
|
@@ -761,6 +827,16 @@ async function syncPresets(api, automationId, localPresets) {
|
|
|
761
827
|
console.log(pc.yellow(` \u26A0 Failed to sync preset ${presetName}: ${e}`));
|
|
762
828
|
}
|
|
763
829
|
}
|
|
830
|
+
for (const remote of remotePresets) {
|
|
831
|
+
if (!localPresetNames.has(remote.name)) {
|
|
832
|
+
try {
|
|
833
|
+
await api.deletePreset(remote.id);
|
|
834
|
+
console.log(pc.dim(` Deleted preset: ${remote.name}`));
|
|
835
|
+
} catch (e) {
|
|
836
|
+
console.log(pc.yellow(` \u26A0 Failed to delete preset ${remote.name}: ${e}`));
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
764
840
|
}
|
|
765
841
|
async function setSchedule(api, automationId, schedule, localPresets) {
|
|
766
842
|
const presetName = localPresets[schedule.preset]?.label || schedule.preset;
|
|
@@ -1706,7 +1782,8 @@ async function plan(args) {
|
|
|
1706
1782
|
const api = createApiClient();
|
|
1707
1783
|
const appName = getAppName(projectRoot);
|
|
1708
1784
|
const projectId = getProjectId(projectRoot);
|
|
1709
|
-
const
|
|
1785
|
+
const positionalArgs = args.filter((a) => !a.startsWith("-"));
|
|
1786
|
+
const { type, name } = parseResource(positionalArgs[0]);
|
|
1710
1787
|
console.log();
|
|
1711
1788
|
console.log(pc.cyan(pc.bold(" Plan")));
|
|
1712
1789
|
console.log(pc.dim(" Comparing local files to remote state..."));
|
|
@@ -1803,7 +1880,8 @@ async function apply(args) {
|
|
|
1803
1880
|
const api = createApiClient();
|
|
1804
1881
|
const appName = getAppName(projectRoot);
|
|
1805
1882
|
const projectId = getProjectId(projectRoot);
|
|
1806
|
-
const
|
|
1883
|
+
const positionalArgs = args.filter((a) => !a.startsWith("-"));
|
|
1884
|
+
const { type, name } = parseResource(positionalArgs[0]);
|
|
1807
1885
|
const autoConfirm = args.includes("--yes") || args.includes("-y") || !!process.env.CI;
|
|
1808
1886
|
if (type === "app") {
|
|
1809
1887
|
console.log();
|