@lumerahq/cli 0.11.0 → 0.11.1

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
@@ -208,22 +208,22 @@ async function main() {
208
208
  switch (command) {
209
209
  // Resource commands
210
210
  case "plan":
211
- await import("./resources-GTG3QMVV.js").then((m) => m.plan(args.slice(1)));
211
+ await import("./resources-ZRAW4EFI.js").then((m) => m.plan(args.slice(1)));
212
212
  break;
213
213
  case "apply":
214
- await import("./resources-GTG3QMVV.js").then((m) => m.apply(args.slice(1)));
214
+ await import("./resources-ZRAW4EFI.js").then((m) => m.apply(args.slice(1)));
215
215
  break;
216
216
  case "pull":
217
- await import("./resources-GTG3QMVV.js").then((m) => m.pull(args.slice(1)));
217
+ await import("./resources-ZRAW4EFI.js").then((m) => m.pull(args.slice(1)));
218
218
  break;
219
219
  case "destroy":
220
- await import("./resources-GTG3QMVV.js").then((m) => m.destroy(args.slice(1)));
220
+ await import("./resources-ZRAW4EFI.js").then((m) => m.destroy(args.slice(1)));
221
221
  break;
222
222
  case "list":
223
- await import("./resources-GTG3QMVV.js").then((m) => m.list(args.slice(1)));
223
+ await import("./resources-ZRAW4EFI.js").then((m) => m.list(args.slice(1)));
224
224
  break;
225
225
  case "show":
226
- await import("./resources-GTG3QMVV.js").then((m) => m.show(args.slice(1)));
226
+ await import("./resources-ZRAW4EFI.js").then((m) => m.show(args.slice(1)));
227
227
  break;
228
228
  // Development
229
229
  case "dev":
@@ -234,7 +234,7 @@ async function main() {
234
234
  break;
235
235
  // Project
236
236
  case "init":
237
- await import("./init-VNJNSU4Q.js").then((m) => m.init(args.slice(1)));
237
+ await import("./init-4JSHTLX2.js").then((m) => m.init(args.slice(1)));
238
238
  break;
239
239
  case "templates":
240
240
  await import("./templates-6KMZWOYH.js").then((m) => m.templates(subcommand, args.slice(2)));
@@ -49,22 +49,17 @@ function spinner(message) {
49
49
  function toTitleCase(str) {
50
50
  return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
51
51
  }
52
- var TEMPLATE_DEFAULTS = {
53
- projectName: "my-lumera-app",
54
- projectTitle: "My Lumera App"
55
- };
56
- function processTemplate(content, vars) {
52
+ function processTemplate(content, replacements) {
57
53
  let result = content;
58
- for (const [key, value] of Object.entries(vars)) {
59
- const defaultValue = TEMPLATE_DEFAULTS[key];
60
- if (defaultValue && defaultValue !== value) {
61
- result = result.replaceAll(defaultValue, value);
54
+ for (const [from, to] of replacements) {
55
+ if (from !== to) {
56
+ result = result.replaceAll(from, to);
62
57
  }
63
58
  }
64
59
  return result;
65
60
  }
66
61
  var TEMPLATE_EXCLUDE = /* @__PURE__ */ new Set(["template.json"]);
67
- function copyDir(src, dest, vars, isRoot = true) {
62
+ function copyDir(src, dest, replacements, isRoot = true) {
68
63
  if (!existsSync(dest)) {
69
64
  mkdirSync(dest, { recursive: true });
70
65
  }
@@ -73,10 +68,10 @@ function copyDir(src, dest, vars, isRoot = true) {
73
68
  const srcPath = join(src, entry.name);
74
69
  const destPath = join(dest, entry.name);
75
70
  if (entry.isDirectory()) {
76
- copyDir(srcPath, destPath, vars, false);
71
+ copyDir(srcPath, destPath, replacements, false);
77
72
  } else {
78
73
  const content = readFileSync(srcPath, "utf-8");
79
- const processed = processTemplate(content, vars);
74
+ const processed = processTemplate(content, replacements);
80
75
  writeFileSync(destPath, processed);
81
76
  }
82
77
  }
@@ -277,7 +272,7 @@ async function init(args) {
277
272
  type: "text",
278
273
  name: "projectName",
279
274
  message: "What is your project name?",
280
- initial: "my-lumera-app",
275
+ initial: "my-app",
281
276
  validate: (value) => {
282
277
  if (!value) return "Project name is required";
283
278
  if (!/^[a-z0-9-]+$/.test(value)) {
@@ -346,11 +341,15 @@ async function init(args) {
346
341
  console.log(pc2.dim(` Creating ${projectName}...`));
347
342
  }
348
343
  console.log();
349
- const vars = {
350
- projectName,
351
- projectTitle
352
- };
353
- copyDir(templateDir, targetDir, vars);
344
+ const templatePkgPath = join(templateDir, "package.json");
345
+ const templatePkg = existsSync(templatePkgPath) ? JSON.parse(readFileSync(templatePkgPath, "utf-8")) : { name: "my-lumera-app" };
346
+ const sourceName = templatePkg.name || "my-lumera-app";
347
+ const sourceTitle = templatePkg.lumera?.name || toTitleCase(sourceName);
348
+ const replacements = [
349
+ [sourceName, projectName],
350
+ [sourceTitle, projectTitle]
351
+ ];
352
+ copyDir(templateDir, targetDir, replacements);
354
353
  function listFiles(dir, prefix = "") {
355
354
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
356
355
  const relativePath = prefix + entry.name;
@@ -233,7 +233,7 @@ function loadLocalCollections(platformDir, filterName) {
233
233
  }
234
234
  return collections;
235
235
  }
236
- function loadLocalAutomations(platformDir, filterName) {
236
+ function loadLocalAutomations(platformDir, filterName, appName) {
237
237
  const automationsDir = join(platformDir, "automations");
238
238
  if (!existsSync(automationsDir)) {
239
239
  return [];
@@ -260,8 +260,12 @@ function loadLocalAutomations(platformDir, filterName) {
260
260
  continue;
261
261
  }
262
262
  if (!config.external_id) {
263
- errors.push(`${entry.name}: missing external_id in config.json`);
264
- continue;
263
+ if (appName) {
264
+ config.external_id = `${appName}:${entry.name}`;
265
+ } else {
266
+ errors.push(`${entry.name}: missing external_id in config.json`);
267
+ continue;
268
+ }
265
269
  }
266
270
  if (!config.name) {
267
271
  errors.push(`${entry.name}: missing name in config.json`);
@@ -286,7 +290,7 @@ function loadLocalAutomations(platformDir, filterName) {
286
290
  }
287
291
  return automations;
288
292
  }
289
- function loadLocalHooks(platformDir, filterName) {
293
+ function loadLocalHooks(platformDir, filterName, appName) {
290
294
  const hooksDir = join(platformDir, "hooks");
291
295
  if (!existsSync(hooksDir)) {
292
296
  return [];
@@ -302,13 +306,20 @@ function loadLocalHooks(platformDir, filterName) {
302
306
  continue;
303
307
  }
304
308
  if (!config.external_id) {
305
- console.log(pc.yellow(` \u26A0 Skipping ${file}: missing external_id in config`));
306
- continue;
309
+ if (appName) {
310
+ config.external_id = `${appName}:${file.replace(/\.(js|ts)$/, "")}`;
311
+ } else {
312
+ console.log(pc.yellow(` \u26A0 Skipping ${file}: missing external_id in config`));
313
+ continue;
314
+ }
307
315
  }
308
316
  if (filterName && config.external_id !== filterName && file.replace(/\.(js|ts)$/, "") !== filterName) {
309
317
  continue;
310
318
  }
311
- const script = extractHookScript(content);
319
+ let script = extractHookScript(content);
320
+ if (appName) {
321
+ script = script.replaceAll("{{app}}", appName);
322
+ }
312
323
  hooks.push({ hook: config, script, fileName: file });
313
324
  }
314
325
  return hooks;
@@ -324,9 +335,9 @@ function parseHookConfig(content) {
324
335
  const collection = content.match(/collection:\s*['"]([^'"]+)['"]/)?.[1];
325
336
  const trigger = content.match(/trigger:\s*['"]([^'"]+)['"]/)?.[1];
326
337
  const enabled = content.match(/enabled:\s*(true|false)/)?.[1];
327
- if (!externalId || !collection || !trigger) return null;
338
+ if (!collection || !trigger) return null;
328
339
  return {
329
- external_id: externalId,
340
+ external_id: externalId || "",
330
341
  collection,
331
342
  trigger,
332
343
  enabled: enabled !== "false"
@@ -812,7 +823,7 @@ ${hook.script.split("\n").map((line) => " " + line).join("\n")}
812
823
  console.log(pc.green(" \u2713"), `${hook.name} \u2192 hooks/${fileName}`);
813
824
  }
814
825
  }
815
- async function listResources(api, platformDir, filterType) {
826
+ async function listResources(api, platformDir, filterType, appName) {
816
827
  const results = [];
817
828
  if (!filterType || filterType === "collections") {
818
829
  const localCollections = loadLocalCollections(platformDir);
@@ -846,7 +857,7 @@ async function listResources(api, platformDir, filterType) {
846
857
  }
847
858
  }
848
859
  if (!filterType || filterType === "automations") {
849
- const localAutomations = loadLocalAutomations(platformDir);
860
+ const localAutomations = loadLocalAutomations(platformDir, void 0, appName);
850
861
  const remoteAutomations = await api.listAutomations({ include_code: true });
851
862
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a]));
852
863
  const localIds = new Set(localAutomations.map((a) => a.automation.external_id));
@@ -872,7 +883,7 @@ async function listResources(api, platformDir, filterType) {
872
883
  }
873
884
  }
874
885
  if (!filterType || filterType === "hooks") {
875
- const localHooks = loadLocalHooks(platformDir);
886
+ const localHooks = loadLocalHooks(platformDir, void 0, appName);
876
887
  const remoteHooks = await api.listHooks();
877
888
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
878
889
  const localIds = new Set(localHooks.map((h) => h.hook.external_id));
@@ -967,7 +978,7 @@ function planCollectionDelete(collections, platformDir) {
967
978
  cycleEdges
968
979
  };
969
980
  }
970
- async function destroyResources(api, platformDir, resourceType, resourceName, skipConfirm, forceCycles) {
981
+ async function destroyResources(api, platformDir, resourceType, resourceName, skipConfirm, forceCycles, appName) {
971
982
  const toDelete = [];
972
983
  if (!resourceType || resourceType === "collections") {
973
984
  const localCollections = loadLocalCollections(platformDir, resourceName || void 0);
@@ -982,7 +993,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
982
993
  }
983
994
  if (!resourceType || resourceType === "automations") {
984
995
  try {
985
- const localAutomations = loadLocalAutomations(platformDir, resourceName || void 0);
996
+ const localAutomations = loadLocalAutomations(platformDir, resourceName || void 0, appName);
986
997
  const remoteAutomations = await api.listAutomations();
987
998
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
988
999
  for (const { automation } of localAutomations) {
@@ -995,7 +1006,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
995
1006
  }
996
1007
  }
997
1008
  if (!resourceType || resourceType === "hooks") {
998
- const localHooks = loadLocalHooks(platformDir, resourceName || void 0);
1009
+ const localHooks = loadLocalHooks(platformDir, resourceName || void 0, appName);
999
1010
  const remoteHooks = await api.listHooks();
1000
1011
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
1001
1012
  for (const { hook } of localHooks) {
@@ -1137,7 +1148,7 @@ async function destroyApp(skipConfirm) {
1137
1148
  }
1138
1149
  console.log(pc.green(" \u2713"), `App "${appRecord.name}" deleted from Lumera.`);
1139
1150
  }
1140
- async function showResource(api, platformDir, resourceType, resourceName) {
1151
+ async function showResource(api, platformDir, resourceType, resourceName, appName) {
1141
1152
  if (resourceType === "collections") {
1142
1153
  const localCollections = loadLocalCollections(platformDir, resourceName);
1143
1154
  const remoteCollections = await api.listCollections();
@@ -1201,7 +1212,7 @@ async function showResource(api, platformDir, resourceType, resourceName) {
1201
1212
  }
1202
1213
  console.log();
1203
1214
  } else if (resourceType === "automations") {
1204
- const localAutomations = loadLocalAutomations(platformDir, resourceName);
1215
+ const localAutomations = loadLocalAutomations(platformDir, resourceName, appName);
1205
1216
  const remoteAutomations = await api.listAutomations();
1206
1217
  const local = localAutomations[0];
1207
1218
  const remote = remoteAutomations.find((a) => a.external_id === resourceName || a.name === resourceName);
@@ -1224,7 +1235,7 @@ async function showResource(api, platformDir, resourceType, resourceName) {
1224
1235
  }
1225
1236
  console.log();
1226
1237
  } else if (resourceType === "hooks") {
1227
- const localHooks = loadLocalHooks(platformDir, resourceName);
1238
+ const localHooks = loadLocalHooks(platformDir, resourceName, appName);
1228
1239
  const remoteHooks = await api.listHooks();
1229
1240
  const local = localHooks[0];
1230
1241
  const remote = remoteHooks.find((h) => h.external_id === resourceName);
@@ -1243,14 +1254,14 @@ async function showResource(api, platformDir, resourceType, resourceName) {
1243
1254
  const projectRoot = findProjectRoot();
1244
1255
  loadEnv(projectRoot);
1245
1256
  const token = getToken(projectRoot);
1246
- const appName = getAppName(projectRoot);
1257
+ const appName2 = getAppName(projectRoot);
1247
1258
  const appTitle = getAppTitle(projectRoot);
1248
1259
  let apiUrl = getApiUrl().replace(/\/+$/, "").replace(/\/api$/, "");
1249
1260
  console.log();
1250
- console.log(pc.bold(` App: ${appTitle || appName}`));
1261
+ console.log(pc.bold(` App: ${appTitle || appName2}`));
1251
1262
  console.log();
1252
- console.log(` External ID: ${appName}`);
1253
- const filterParam = encodeURIComponent(JSON.stringify({ external_id: appName }));
1263
+ console.log(` External ID: ${appName2}`);
1264
+ const filterParam = encodeURIComponent(JSON.stringify({ external_id: appName2 }));
1254
1265
  const searchRes = await fetch(
1255
1266
  `${apiUrl}/api/pb/collections/lm_custom_apps/records?filter=${filterParam}`,
1256
1267
  { headers: { Authorization: `Bearer ${token}` } }
@@ -1275,9 +1286,11 @@ async function plan(args) {
1275
1286
  showPlanHelp();
1276
1287
  return;
1277
1288
  }
1278
- loadEnv();
1289
+ const projectRoot = findProjectRoot();
1290
+ loadEnv(projectRoot);
1279
1291
  const platformDir = getPlatformDir();
1280
1292
  const api = createApiClient();
1293
+ const appName = getAppName(projectRoot);
1281
1294
  const { type, name } = parseResource(args[0]);
1282
1295
  console.log();
1283
1296
  console.log(pc.cyan(pc.bold(" Plan")));
@@ -1299,14 +1312,14 @@ async function plan(args) {
1299
1312
  }
1300
1313
  }
1301
1314
  if (!type || type === "automations") {
1302
- const localAutomations = loadLocalAutomations(platformDir, name || void 0);
1315
+ const localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
1303
1316
  if (localAutomations.length > 0) {
1304
1317
  const changes = await planAutomations(api, localAutomations);
1305
1318
  allChanges.push(...changes);
1306
1319
  }
1307
1320
  }
1308
1321
  if (!type || type === "hooks") {
1309
- const localHooks = loadLocalHooks(platformDir, name || void 0);
1322
+ const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
1310
1323
  if (localHooks.length > 0) {
1311
1324
  const changes = await planHooks(api, localHooks, collections);
1312
1325
  allChanges.push(...changes);
@@ -1342,9 +1355,11 @@ async function apply(args) {
1342
1355
  showApplyHelp();
1343
1356
  return;
1344
1357
  }
1345
- loadEnv();
1358
+ const projectRoot = findProjectRoot();
1359
+ loadEnv(projectRoot);
1346
1360
  const platformDir = getPlatformDir();
1347
1361
  const api = createApiClient();
1362
+ const appName = getAppName(projectRoot);
1348
1363
  const { type, name } = parseResource(args[0]);
1349
1364
  console.log();
1350
1365
  console.log(pc.cyan(pc.bold(" Apply")));
@@ -1380,7 +1395,7 @@ async function apply(args) {
1380
1395
  collections = /* @__PURE__ */ new Map();
1381
1396
  }
1382
1397
  if (!type || type === "automations") {
1383
- const localAutomations = loadLocalAutomations(platformDir, name || void 0);
1398
+ const localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
1384
1399
  if (localAutomations.length > 0) {
1385
1400
  console.log(pc.bold(" Automations:"));
1386
1401
  totalErrors += await applyAutomations(api, localAutomations);
@@ -1391,7 +1406,7 @@ async function apply(args) {
1391
1406
  }
1392
1407
  }
1393
1408
  if (!type || type === "hooks") {
1394
- const localHooks = loadLocalHooks(platformDir, name || void 0);
1409
+ const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
1395
1410
  if (localHooks.length > 0) {
1396
1411
  console.log(pc.bold(" Hooks:"));
1397
1412
  totalErrors += await applyHooks(api, localHooks, collections);
@@ -1403,8 +1418,8 @@ async function apply(args) {
1403
1418
  }
1404
1419
  if (!type) {
1405
1420
  try {
1406
- const projectRoot = findProjectRoot();
1407
- if (existsSync(join(projectRoot, "dist")) || existsSync(join(projectRoot, "src"))) {
1421
+ const projectRoot2 = findProjectRoot();
1422
+ if (existsSync(join(projectRoot2, "dist")) || existsSync(join(projectRoot2, "src"))) {
1408
1423
  console.log(pc.bold(" App:"));
1409
1424
  await applyApp(args);
1410
1425
  console.log();
@@ -1456,9 +1471,11 @@ async function destroy(args) {
1456
1471
  showDestroyHelp();
1457
1472
  return;
1458
1473
  }
1459
- loadEnv();
1474
+ const projectRoot = findProjectRoot();
1475
+ loadEnv(projectRoot);
1460
1476
  const platformDir = getPlatformDir();
1461
1477
  const api = createApiClient();
1478
+ const appName = getAppName(projectRoot);
1462
1479
  const { type, name } = parseResource(args[0]);
1463
1480
  const skipConfirm = args.includes("--confirm");
1464
1481
  const forceCycles = args.includes("--force-cycles");
@@ -1468,7 +1485,7 @@ async function destroy(args) {
1468
1485
  if (type === "app") {
1469
1486
  await destroyApp(skipConfirm);
1470
1487
  } else {
1471
- await destroyResources(api, platformDir, type || void 0, name || void 0, skipConfirm, forceCycles);
1488
+ await destroyResources(api, platformDir, type || void 0, name || void 0, skipConfirm, forceCycles, appName);
1472
1489
  }
1473
1490
  console.log();
1474
1491
  }
@@ -1477,16 +1494,18 @@ async function list(args) {
1477
1494
  showListHelp();
1478
1495
  return;
1479
1496
  }
1480
- loadEnv();
1497
+ const projectRoot = findProjectRoot();
1498
+ loadEnv(projectRoot);
1481
1499
  const platformDir = getPlatformDir();
1482
1500
  const api = createApiClient();
1501
+ const appName = getAppName(projectRoot);
1483
1502
  const showAll = args.includes("--all");
1484
1503
  const positionalArgs = args.filter((a) => !a.startsWith("--"));
1485
1504
  const filterType = positionalArgs[0];
1486
1505
  console.log();
1487
1506
  console.log(pc.cyan(pc.bold(" Resources")));
1488
1507
  console.log();
1489
- const allResources = await listResources(api, platformDir, filterType);
1508
+ const allResources = await listResources(api, platformDir, filterType, appName);
1490
1509
  const remoteOnlyCount = allResources.filter((r) => r.status === "remote-only").length;
1491
1510
  const resources = showAll ? allResources : allResources.filter((r) => r.status !== "remote-only");
1492
1511
  if (resources.length === 0 && remoteOnlyCount === 0) {
@@ -1555,9 +1574,11 @@ async function show(args) {
1555
1574
  if (args.length === 0) process.exit(1);
1556
1575
  return;
1557
1576
  }
1558
- loadEnv();
1577
+ const projectRoot = findProjectRoot();
1578
+ loadEnv(projectRoot);
1559
1579
  const platformDir = getPlatformDir();
1560
1580
  const api = createApiClient();
1581
+ const appName = getAppName(projectRoot);
1561
1582
  const { type, name } = parseResource(args[0]);
1562
1583
  if (!type) {
1563
1584
  console.log(pc.red(` Invalid resource path: ${args[0]}`));
@@ -1565,13 +1586,13 @@ async function show(args) {
1565
1586
  process.exit(1);
1566
1587
  }
1567
1588
  if (type === "app") {
1568
- await showResource(api, platformDir, "app", "");
1589
+ await showResource(api, platformDir, "app", "", appName);
1569
1590
  } else if (!name) {
1570
1591
  console.log(pc.red(` Resource name required`));
1571
1592
  console.log(pc.dim(" Use format: <type>/<name> (e.g., collections/users)"));
1572
1593
  process.exit(1);
1573
1594
  } else {
1574
- await showResource(api, platformDir, type, name);
1595
+ await showResource(api, platformDir, type, name, appName);
1575
1596
  }
1576
1597
  }
1577
1598
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {