@lumerahq/cli 0.10.1 → 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.
@@ -2,11 +2,9 @@ import {
2
2
  deploy
3
3
  } from "./chunk-CDZZ3JYU.js";
4
4
  import {
5
- createApiClient
6
- } from "./chunk-WRAZC6SJ.js";
7
- import {
5
+ createApiClient,
8
6
  loadEnv
9
- } from "./chunk-2CR762KB.js";
7
+ } from "./chunk-HIYM7EM2.js";
10
8
  import {
11
9
  getToken
12
10
  } from "./chunk-NDLYGKS6.js";
@@ -235,7 +233,7 @@ function loadLocalCollections(platformDir, filterName) {
235
233
  }
236
234
  return collections;
237
235
  }
238
- function loadLocalAutomations(platformDir, filterName) {
236
+ function loadLocalAutomations(platformDir, filterName, appName) {
239
237
  const automationsDir = join(platformDir, "automations");
240
238
  if (!existsSync(automationsDir)) {
241
239
  return [];
@@ -262,8 +260,12 @@ function loadLocalAutomations(platformDir, filterName) {
262
260
  continue;
263
261
  }
264
262
  if (!config.external_id) {
265
- errors.push(`${entry.name}: missing external_id in config.json`);
266
- 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
+ }
267
269
  }
268
270
  if (!config.name) {
269
271
  errors.push(`${entry.name}: missing name in config.json`);
@@ -288,7 +290,7 @@ function loadLocalAutomations(platformDir, filterName) {
288
290
  }
289
291
  return automations;
290
292
  }
291
- function loadLocalHooks(platformDir, filterName) {
293
+ function loadLocalHooks(platformDir, filterName, appName) {
292
294
  const hooksDir = join(platformDir, "hooks");
293
295
  if (!existsSync(hooksDir)) {
294
296
  return [];
@@ -304,13 +306,20 @@ function loadLocalHooks(platformDir, filterName) {
304
306
  continue;
305
307
  }
306
308
  if (!config.external_id) {
307
- console.log(pc.yellow(` \u26A0 Skipping ${file}: missing external_id in config`));
308
- 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
+ }
309
315
  }
310
316
  if (filterName && config.external_id !== filterName && file.replace(/\.(js|ts)$/, "") !== filterName) {
311
317
  continue;
312
318
  }
313
- const script = extractHookScript(content);
319
+ let script = extractHookScript(content);
320
+ if (appName) {
321
+ script = script.replaceAll("{{app}}", appName);
322
+ }
314
323
  hooks.push({ hook: config, script, fileName: file });
315
324
  }
316
325
  return hooks;
@@ -326,9 +335,9 @@ function parseHookConfig(content) {
326
335
  const collection = content.match(/collection:\s*['"]([^'"]+)['"]/)?.[1];
327
336
  const trigger = content.match(/trigger:\s*['"]([^'"]+)['"]/)?.[1];
328
337
  const enabled = content.match(/enabled:\s*(true|false)/)?.[1];
329
- if (!externalId || !collection || !trigger) return null;
338
+ if (!collection || !trigger) return null;
330
339
  return {
331
- external_id: externalId,
340
+ external_id: externalId || "",
332
341
  collection,
333
342
  trigger,
334
343
  enabled: enabled !== "false"
@@ -814,7 +823,7 @@ ${hook.script.split("\n").map((line) => " " + line).join("\n")}
814
823
  console.log(pc.green(" \u2713"), `${hook.name} \u2192 hooks/${fileName}`);
815
824
  }
816
825
  }
817
- async function listResources(api, platformDir, filterType) {
826
+ async function listResources(api, platformDir, filterType, appName) {
818
827
  const results = [];
819
828
  if (!filterType || filterType === "collections") {
820
829
  const localCollections = loadLocalCollections(platformDir);
@@ -848,7 +857,7 @@ async function listResources(api, platformDir, filterType) {
848
857
  }
849
858
  }
850
859
  if (!filterType || filterType === "automations") {
851
- const localAutomations = loadLocalAutomations(platformDir);
860
+ const localAutomations = loadLocalAutomations(platformDir, void 0, appName);
852
861
  const remoteAutomations = await api.listAutomations({ include_code: true });
853
862
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a]));
854
863
  const localIds = new Set(localAutomations.map((a) => a.automation.external_id));
@@ -874,7 +883,7 @@ async function listResources(api, platformDir, filterType) {
874
883
  }
875
884
  }
876
885
  if (!filterType || filterType === "hooks") {
877
- const localHooks = loadLocalHooks(platformDir);
886
+ const localHooks = loadLocalHooks(platformDir, void 0, appName);
878
887
  const remoteHooks = await api.listHooks();
879
888
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
880
889
  const localIds = new Set(localHooks.map((h) => h.hook.external_id));
@@ -969,7 +978,7 @@ function planCollectionDelete(collections, platformDir) {
969
978
  cycleEdges
970
979
  };
971
980
  }
972
- async function destroyResources(api, platformDir, resourceType, resourceName, skipConfirm, forceCycles) {
981
+ async function destroyResources(api, platformDir, resourceType, resourceName, skipConfirm, forceCycles, appName) {
973
982
  const toDelete = [];
974
983
  if (!resourceType || resourceType === "collections") {
975
984
  const localCollections = loadLocalCollections(platformDir, resourceName || void 0);
@@ -984,7 +993,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
984
993
  }
985
994
  if (!resourceType || resourceType === "automations") {
986
995
  try {
987
- const localAutomations = loadLocalAutomations(platformDir, resourceName || void 0);
996
+ const localAutomations = loadLocalAutomations(platformDir, resourceName || void 0, appName);
988
997
  const remoteAutomations = await api.listAutomations();
989
998
  const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
990
999
  for (const { automation } of localAutomations) {
@@ -997,7 +1006,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
997
1006
  }
998
1007
  }
999
1008
  if (!resourceType || resourceType === "hooks") {
1000
- const localHooks = loadLocalHooks(platformDir, resourceName || void 0);
1009
+ const localHooks = loadLocalHooks(platformDir, resourceName || void 0, appName);
1001
1010
  const remoteHooks = await api.listHooks();
1002
1011
  const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
1003
1012
  for (const { hook } of localHooks) {
@@ -1139,7 +1148,7 @@ async function destroyApp(skipConfirm) {
1139
1148
  }
1140
1149
  console.log(pc.green(" \u2713"), `App "${appRecord.name}" deleted from Lumera.`);
1141
1150
  }
1142
- async function showResource(api, platformDir, resourceType, resourceName) {
1151
+ async function showResource(api, platformDir, resourceType, resourceName, appName) {
1143
1152
  if (resourceType === "collections") {
1144
1153
  const localCollections = loadLocalCollections(platformDir, resourceName);
1145
1154
  const remoteCollections = await api.listCollections();
@@ -1203,7 +1212,7 @@ async function showResource(api, platformDir, resourceType, resourceName) {
1203
1212
  }
1204
1213
  console.log();
1205
1214
  } else if (resourceType === "automations") {
1206
- const localAutomations = loadLocalAutomations(platformDir, resourceName);
1215
+ const localAutomations = loadLocalAutomations(platformDir, resourceName, appName);
1207
1216
  const remoteAutomations = await api.listAutomations();
1208
1217
  const local = localAutomations[0];
1209
1218
  const remote = remoteAutomations.find((a) => a.external_id === resourceName || a.name === resourceName);
@@ -1226,7 +1235,7 @@ async function showResource(api, platformDir, resourceType, resourceName) {
1226
1235
  }
1227
1236
  console.log();
1228
1237
  } else if (resourceType === "hooks") {
1229
- const localHooks = loadLocalHooks(platformDir, resourceName);
1238
+ const localHooks = loadLocalHooks(platformDir, resourceName, appName);
1230
1239
  const remoteHooks = await api.listHooks();
1231
1240
  const local = localHooks[0];
1232
1241
  const remote = remoteHooks.find((h) => h.external_id === resourceName);
@@ -1245,14 +1254,14 @@ async function showResource(api, platformDir, resourceType, resourceName) {
1245
1254
  const projectRoot = findProjectRoot();
1246
1255
  loadEnv(projectRoot);
1247
1256
  const token = getToken(projectRoot);
1248
- const appName = getAppName(projectRoot);
1257
+ const appName2 = getAppName(projectRoot);
1249
1258
  const appTitle = getAppTitle(projectRoot);
1250
1259
  let apiUrl = getApiUrl().replace(/\/+$/, "").replace(/\/api$/, "");
1251
1260
  console.log();
1252
- console.log(pc.bold(` App: ${appTitle || appName}`));
1261
+ console.log(pc.bold(` App: ${appTitle || appName2}`));
1253
1262
  console.log();
1254
- console.log(` External ID: ${appName}`);
1255
- const filterParam = encodeURIComponent(JSON.stringify({ external_id: appName }));
1263
+ console.log(` External ID: ${appName2}`);
1264
+ const filterParam = encodeURIComponent(JSON.stringify({ external_id: appName2 }));
1256
1265
  const searchRes = await fetch(
1257
1266
  `${apiUrl}/api/pb/collections/lm_custom_apps/records?filter=${filterParam}`,
1258
1267
  { headers: { Authorization: `Bearer ${token}` } }
@@ -1277,9 +1286,11 @@ async function plan(args) {
1277
1286
  showPlanHelp();
1278
1287
  return;
1279
1288
  }
1280
- loadEnv();
1289
+ const projectRoot = findProjectRoot();
1290
+ loadEnv(projectRoot);
1281
1291
  const platformDir = getPlatformDir();
1282
1292
  const api = createApiClient();
1293
+ const appName = getAppName(projectRoot);
1283
1294
  const { type, name } = parseResource(args[0]);
1284
1295
  console.log();
1285
1296
  console.log(pc.cyan(pc.bold(" Plan")));
@@ -1301,14 +1312,14 @@ async function plan(args) {
1301
1312
  }
1302
1313
  }
1303
1314
  if (!type || type === "automations") {
1304
- const localAutomations = loadLocalAutomations(platformDir, name || void 0);
1315
+ const localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
1305
1316
  if (localAutomations.length > 0) {
1306
1317
  const changes = await planAutomations(api, localAutomations);
1307
1318
  allChanges.push(...changes);
1308
1319
  }
1309
1320
  }
1310
1321
  if (!type || type === "hooks") {
1311
- const localHooks = loadLocalHooks(platformDir, name || void 0);
1322
+ const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
1312
1323
  if (localHooks.length > 0) {
1313
1324
  const changes = await planHooks(api, localHooks, collections);
1314
1325
  allChanges.push(...changes);
@@ -1344,9 +1355,11 @@ async function apply(args) {
1344
1355
  showApplyHelp();
1345
1356
  return;
1346
1357
  }
1347
- loadEnv();
1358
+ const projectRoot = findProjectRoot();
1359
+ loadEnv(projectRoot);
1348
1360
  const platformDir = getPlatformDir();
1349
1361
  const api = createApiClient();
1362
+ const appName = getAppName(projectRoot);
1350
1363
  const { type, name } = parseResource(args[0]);
1351
1364
  console.log();
1352
1365
  console.log(pc.cyan(pc.bold(" Apply")));
@@ -1382,7 +1395,7 @@ async function apply(args) {
1382
1395
  collections = /* @__PURE__ */ new Map();
1383
1396
  }
1384
1397
  if (!type || type === "automations") {
1385
- const localAutomations = loadLocalAutomations(platformDir, name || void 0);
1398
+ const localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
1386
1399
  if (localAutomations.length > 0) {
1387
1400
  console.log(pc.bold(" Automations:"));
1388
1401
  totalErrors += await applyAutomations(api, localAutomations);
@@ -1393,7 +1406,7 @@ async function apply(args) {
1393
1406
  }
1394
1407
  }
1395
1408
  if (!type || type === "hooks") {
1396
- const localHooks = loadLocalHooks(platformDir, name || void 0);
1409
+ const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
1397
1410
  if (localHooks.length > 0) {
1398
1411
  console.log(pc.bold(" Hooks:"));
1399
1412
  totalErrors += await applyHooks(api, localHooks, collections);
@@ -1405,8 +1418,8 @@ async function apply(args) {
1405
1418
  }
1406
1419
  if (!type) {
1407
1420
  try {
1408
- const projectRoot = findProjectRoot();
1409
- if (existsSync(join(projectRoot, "dist")) || existsSync(join(projectRoot, "src"))) {
1421
+ const projectRoot2 = findProjectRoot();
1422
+ if (existsSync(join(projectRoot2, "dist")) || existsSync(join(projectRoot2, "src"))) {
1410
1423
  console.log(pc.bold(" App:"));
1411
1424
  await applyApp(args);
1412
1425
  console.log();
@@ -1458,9 +1471,11 @@ async function destroy(args) {
1458
1471
  showDestroyHelp();
1459
1472
  return;
1460
1473
  }
1461
- loadEnv();
1474
+ const projectRoot = findProjectRoot();
1475
+ loadEnv(projectRoot);
1462
1476
  const platformDir = getPlatformDir();
1463
1477
  const api = createApiClient();
1478
+ const appName = getAppName(projectRoot);
1464
1479
  const { type, name } = parseResource(args[0]);
1465
1480
  const skipConfirm = args.includes("--confirm");
1466
1481
  const forceCycles = args.includes("--force-cycles");
@@ -1470,7 +1485,7 @@ async function destroy(args) {
1470
1485
  if (type === "app") {
1471
1486
  await destroyApp(skipConfirm);
1472
1487
  } else {
1473
- 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);
1474
1489
  }
1475
1490
  console.log();
1476
1491
  }
@@ -1479,16 +1494,18 @@ async function list(args) {
1479
1494
  showListHelp();
1480
1495
  return;
1481
1496
  }
1482
- loadEnv();
1497
+ const projectRoot = findProjectRoot();
1498
+ loadEnv(projectRoot);
1483
1499
  const platformDir = getPlatformDir();
1484
1500
  const api = createApiClient();
1501
+ const appName = getAppName(projectRoot);
1485
1502
  const showAll = args.includes("--all");
1486
1503
  const positionalArgs = args.filter((a) => !a.startsWith("--"));
1487
1504
  const filterType = positionalArgs[0];
1488
1505
  console.log();
1489
1506
  console.log(pc.cyan(pc.bold(" Resources")));
1490
1507
  console.log();
1491
- const allResources = await listResources(api, platformDir, filterType);
1508
+ const allResources = await listResources(api, platformDir, filterType, appName);
1492
1509
  const remoteOnlyCount = allResources.filter((r) => r.status === "remote-only").length;
1493
1510
  const resources = showAll ? allResources : allResources.filter((r) => r.status !== "remote-only");
1494
1511
  if (resources.length === 0 && remoteOnlyCount === 0) {
@@ -1557,9 +1574,11 @@ async function show(args) {
1557
1574
  if (args.length === 0) process.exit(1);
1558
1575
  return;
1559
1576
  }
1560
- loadEnv();
1577
+ const projectRoot = findProjectRoot();
1578
+ loadEnv(projectRoot);
1561
1579
  const platformDir = getPlatformDir();
1562
1580
  const api = createApiClient();
1581
+ const appName = getAppName(projectRoot);
1563
1582
  const { type, name } = parseResource(args[0]);
1564
1583
  if (!type) {
1565
1584
  console.log(pc.red(` Invalid resource path: ${args[0]}`));
@@ -1567,13 +1586,13 @@ async function show(args) {
1567
1586
  process.exit(1);
1568
1587
  }
1569
1588
  if (type === "app") {
1570
- await showResource(api, platformDir, "app", "");
1589
+ await showResource(api, platformDir, "app", "", appName);
1571
1590
  } else if (!name) {
1572
1591
  console.log(pc.red(` Resource name required`));
1573
1592
  console.log(pc.dim(" Use format: <type>/<name> (e.g., collections/users)"));
1574
1593
  process.exit(1);
1575
1594
  } else {
1576
- await showResource(api, platformDir, type, name);
1595
+ await showResource(api, platformDir, type, name, appName);
1577
1596
  }
1578
1597
  }
1579
1598
  export {
@@ -1,9 +1,7 @@
1
1
  import {
2
- createApiClient
3
- } from "./chunk-WRAZC6SJ.js";
4
- import {
2
+ createApiClient,
5
3
  loadEnv
6
- } from "./chunk-2CR762KB.js";
4
+ } from "./chunk-HIYM7EM2.js";
7
5
  import {
8
6
  getToken
9
7
  } from "./chunk-NDLYGKS6.js";
@@ -0,0 +1,194 @@
1
+ import {
2
+ listAllTemplates
3
+ } from "./chunk-CHRKCAIZ.js";
4
+
5
+ // src/commands/templates.ts
6
+ import pc from "picocolors";
7
+ import { existsSync, readdirSync, readFileSync } from "fs";
8
+ import { join, resolve, extname } from "path";
9
+ function showHelp() {
10
+ console.log(`
11
+ ${pc.dim("Usage:")}
12
+ lumera templates <command> [options]
13
+
14
+ ${pc.dim("Commands:")}
15
+ list, ls List available templates (default)
16
+ validate [dir] Validate a template directory
17
+
18
+ ${pc.dim("Options:")}
19
+ --verbose, -v Show full descriptions (list)
20
+ --help, -h Show this help
21
+
22
+ ${pc.dim("Examples:")}
23
+ lumera templates # List available templates
24
+ lumera templates list -v # Show with descriptions
25
+ lumera templates validate ./my-template # Validate a template dir
26
+ lumera init my-app -t invoice-processing # Use a template
27
+ `);
28
+ }
29
+ async function list(args) {
30
+ if (args.includes("--help") || args.includes("-h")) {
31
+ showHelp();
32
+ return;
33
+ }
34
+ const verbose = args.includes("--verbose") || args.includes("-v");
35
+ try {
36
+ const allTemplates = await listAllTemplates();
37
+ if (process.env.LUMERA_JSON) {
38
+ console.log(JSON.stringify(allTemplates, null, 2));
39
+ return;
40
+ }
41
+ console.log();
42
+ console.log(pc.cyan(pc.bold(" Available Templates")));
43
+ console.log();
44
+ if (allTemplates.length === 0) {
45
+ console.log(pc.dim(" No templates available."));
46
+ console.log();
47
+ return;
48
+ }
49
+ const byCategory = /* @__PURE__ */ new Map();
50
+ for (const t of allTemplates) {
51
+ const cat = t.category || "General";
52
+ if (!byCategory.has(cat)) byCategory.set(cat, []);
53
+ byCategory.get(cat).push(t);
54
+ }
55
+ for (const [category, items] of byCategory) {
56
+ console.log(pc.bold(` ${category}`));
57
+ console.log();
58
+ for (const t of items) {
59
+ console.log(` ${pc.green(t.title)} ${pc.dim(`(${t.name})`)}`);
60
+ if (verbose) {
61
+ console.log(` ${pc.dim(t.description)}`);
62
+ console.log();
63
+ }
64
+ }
65
+ if (!verbose) console.log();
66
+ }
67
+ console.log(pc.dim(` ${allTemplates.length} template${allTemplates.length === 1 ? "" : "s"} available.`));
68
+ console.log();
69
+ console.log(pc.dim(" Usage:"), pc.cyan("lumera init <name> --template <template-name>"));
70
+ console.log();
71
+ } catch (err) {
72
+ console.error(pc.red(` Error: ${err}`));
73
+ process.exit(1);
74
+ }
75
+ }
76
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".venv", "dist", "__pycache__", ".tanstack"]);
77
+ var TEXT_EXTS = /* @__PURE__ */ new Set([".json", ".ts", ".tsx", ".js", ".jsx", ".py", ".md", ".html", ".css", ".yaml", ".yml", ".toml"]);
78
+ function scanForPlaceholders(dir, prefix = "") {
79
+ const results = [];
80
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
81
+ if (SKIP_DIRS.has(entry.name)) continue;
82
+ const fullPath = join(dir, entry.name);
83
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
84
+ if (entry.isDirectory()) {
85
+ results.push(...scanForPlaceholders(fullPath, relativePath));
86
+ } else {
87
+ const ext = extname(entry.name);
88
+ if (!TEXT_EXTS.has(ext)) continue;
89
+ if (entry.name === "template.json") continue;
90
+ try {
91
+ const content = readFileSync(fullPath, "utf-8");
92
+ const lines = content.split("\n");
93
+ for (let i = 0; i < lines.length; i++) {
94
+ const matches = lines[i].match(/\{\{[^}]+\}\}/g);
95
+ if (matches) {
96
+ for (const match of matches) {
97
+ if (match.includes(":")) continue;
98
+ results.push({ file: relativePath, line: i + 1, text: match });
99
+ }
100
+ }
101
+ }
102
+ } catch {
103
+ }
104
+ }
105
+ }
106
+ return results;
107
+ }
108
+ async function validate(args) {
109
+ const dir = args.find((a) => !a.startsWith("-")) || ".";
110
+ const targetDir = resolve(process.cwd(), dir);
111
+ console.log();
112
+ console.log(pc.cyan(pc.bold(" Validate Template")));
113
+ console.log(pc.dim(` Directory: ${targetDir}`));
114
+ console.log();
115
+ const issues = [];
116
+ const warnings = [];
117
+ const templateJsonPath = join(targetDir, "template.json");
118
+ if (!existsSync(templateJsonPath)) {
119
+ issues.push("Missing template.json");
120
+ } else {
121
+ try {
122
+ const meta = JSON.parse(readFileSync(templateJsonPath, "utf-8"));
123
+ const required = ["name", "title", "description", "category"];
124
+ for (const field of required) {
125
+ if (!meta[field]) {
126
+ issues.push(`template.json: missing required field "${field}"`);
127
+ }
128
+ }
129
+ } catch {
130
+ issues.push("template.json: invalid JSON");
131
+ }
132
+ }
133
+ if (!existsSync(join(targetDir, "package.json"))) {
134
+ issues.push("Missing package.json");
135
+ }
136
+ if (!existsSync(join(targetDir, "platform"))) {
137
+ warnings.push("Missing platform/ directory");
138
+ } else if (!existsSync(join(targetDir, "platform", "collections"))) {
139
+ warnings.push("platform/ has no collections/ subdirectory");
140
+ }
141
+ const stalePatterns = scanForPlaceholders(targetDir);
142
+ for (const match of stalePatterns) {
143
+ warnings.push(`Stale placeholder: ${match.file}:${match.line} \u2014 ${match.text}`);
144
+ }
145
+ if (issues.length === 0 && warnings.length === 0) {
146
+ console.log(pc.green(" \u2713 Template is valid"));
147
+ console.log();
148
+ return;
149
+ }
150
+ if (issues.length > 0) {
151
+ console.log(pc.red(` ${issues.length} error${issues.length > 1 ? "s" : ""}:`));
152
+ for (const issue of issues) {
153
+ console.log(pc.red(` \u2717 ${issue}`));
154
+ }
155
+ console.log();
156
+ }
157
+ if (warnings.length > 0) {
158
+ console.log(pc.yellow(` ${warnings.length} warning${warnings.length > 1 ? "s" : ""}:`));
159
+ for (const warning of warnings) {
160
+ console.log(pc.yellow(` \u26A0 ${warning}`));
161
+ }
162
+ console.log();
163
+ }
164
+ if (issues.length > 0) {
165
+ process.exit(1);
166
+ }
167
+ }
168
+ async function templates(subcommand, args) {
169
+ if (subcommand === "--help" || subcommand === "-h") {
170
+ showHelp();
171
+ return;
172
+ }
173
+ const cmd = subcommand || "list";
174
+ switch (cmd) {
175
+ case "list":
176
+ case "ls":
177
+ await list(args);
178
+ break;
179
+ case "validate":
180
+ await validate(args);
181
+ break;
182
+ default:
183
+ if (cmd.startsWith("-")) {
184
+ await list([cmd, ...args]);
185
+ break;
186
+ }
187
+ console.error(pc.red(`Unknown templates command: ${cmd}`));
188
+ console.error(`Run ${pc.cyan("lumera templates --help")} for usage.`);
189
+ process.exit(1);
190
+ }
191
+ }
192
+ export {
193
+ templates
194
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,18 +0,0 @@
1
- // src/lib/env.ts
2
- import { config } from "dotenv";
3
- import { existsSync } from "fs";
4
- import { resolve } from "path";
5
- function loadEnv(cwd = process.cwd()) {
6
- const envPath = resolve(cwd, ".env");
7
- const envLocalPath = resolve(cwd, ".env.local");
8
- if (existsSync(envPath)) {
9
- config({ path: envPath });
10
- }
11
- if (existsSync(envLocalPath)) {
12
- config({ path: envLocalPath, override: true });
13
- }
14
- }
15
-
16
- export {
17
- loadEnv
18
- };
@@ -1,87 +0,0 @@
1
- import {
2
- dev
3
- } from "./chunk-CDZZ3JYU.js";
4
- import {
5
- loadEnv
6
- } from "./chunk-2CR762KB.js";
7
- import {
8
- getToken
9
- } from "./chunk-NDLYGKS6.js";
10
- import {
11
- findProjectRoot,
12
- getApiUrl,
13
- getAppName,
14
- getAppTitle
15
- } from "./chunk-D2BLSEGR.js";
16
-
17
- // src/commands/dev.ts
18
- import pc from "picocolors";
19
- function parseFlags(args) {
20
- const result = {};
21
- for (let i = 0; i < args.length; i++) {
22
- const arg = args[i];
23
- if (arg.startsWith("--")) {
24
- const key = arg.slice(2);
25
- const next = args[i + 1];
26
- if (next && !next.startsWith("--")) {
27
- result[key] = next;
28
- i++;
29
- } else {
30
- result[key] = true;
31
- }
32
- }
33
- }
34
- return result;
35
- }
36
- function showHelp() {
37
- console.log(`
38
- ${pc.dim("Usage:")}
39
- lumera dev [options]
40
-
41
- ${pc.dim("Description:")}
42
- Start the development server with Lumera registration.
43
- Registers your app with Lumera and starts a local dev server.
44
-
45
- ${pc.dim("Options:")}
46
- --port <number> Dev server port (default: 8080)
47
- --url <url> App URL for dev mode (default: http://localhost:{port})
48
- --help, -h Show this help
49
-
50
- ${pc.dim("Environment variables:")}
51
- LUMERA_TOKEN API token (overrides \`lumera login\`)
52
- LUMERA_API_URL API base URL (default: https://app.lumerahq.com/api)
53
- PORT Dev server port (default: 8080)
54
- APP_URL App URL for dev mode
55
-
56
- ${pc.dim("Examples:")}
57
- lumera dev # Start dev server on port 8080
58
- lumera dev --port 3000 # Start dev server on port 3000
59
- lumera dev --url http://192.168.1.100:8080 # Custom URL for mobile testing
60
- `);
61
- }
62
- async function dev2(args) {
63
- if (args.includes("--help") || args.includes("-h")) {
64
- showHelp();
65
- return;
66
- }
67
- const flags = parseFlags(args);
68
- const projectRoot = findProjectRoot();
69
- loadEnv(projectRoot);
70
- const token = getToken(projectRoot);
71
- const appName = getAppName(projectRoot);
72
- const appTitle = getAppTitle(projectRoot);
73
- const apiUrl = getApiUrl();
74
- const port = Number(flags.port || process.env.PORT || "8080");
75
- const appUrl = typeof flags.url === "string" ? flags.url : process.env.APP_URL;
76
- await dev({
77
- token,
78
- appName,
79
- appTitle,
80
- port,
81
- appUrl,
82
- apiUrl
83
- });
84
- }
85
- export {
86
- dev2 as dev
87
- };