@lumerahq/cli 0.31.2 → 0.32.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/README.md CHANGED
@@ -20,7 +20,9 @@ lumera whoami # Show current user
20
20
  lumera status # Show project info
21
21
 
22
22
  lumera dev # Start dev server
23
- lumera apply app # Deploy frontend
23
+ lumera apply app # Deploy frontend and App API
24
+ lumera apply app-api # Deploy only the App API
25
+ lumera apply app --skip-app-api # Deploy only the frontend
24
26
  lumera destroy app # Delete app from Lumera
25
27
 
26
28
  lumera plan # Preview changes (with inline diffs)
@@ -8,10 +8,168 @@ import {
8
8
  // src/lib/deploy.ts
9
9
  import { execSync, spawn } from "child_process";
10
10
  import { createConnection } from "net";
11
- import { existsSync, readFileSync } from "fs";
12
- import { resolve, dirname } from "path";
13
- import archiver from "archiver";
11
+ import { existsSync as existsSync2, readFileSync } from "fs";
12
+ import { resolve as resolve2, dirname } from "path";
13
+ import archiver2 from "archiver";
14
14
  import pc from "picocolors";
15
+
16
+ // src/lib/app-runtime-deploy.ts
17
+ import { execFileSync } from "child_process";
18
+ import {
19
+ cpSync,
20
+ existsSync,
21
+ mkdtempSync,
22
+ rmSync
23
+ } from "fs";
24
+ import { tmpdir } from "os";
25
+ import { basename, join, relative, resolve, sep } from "path";
26
+ import archiver from "archiver";
27
+ var APP_RUNTIME_RELATIVE_DIR = join("app-api", "lambda");
28
+ var APP_RUNTIME_PYTHON_VERSION = "3.13";
29
+ var APP_RUNTIME_PYTHON_PLATFORM = "aarch64-manylinux_2_34";
30
+ var MAX_APP_RUNTIME_ZIP_BYTES = 45 * 1024 * 1024;
31
+ var APP_RUNTIME_TARGET_ARGS = [
32
+ "--python-version",
33
+ APP_RUNTIME_PYTHON_VERSION,
34
+ "--python-platform",
35
+ APP_RUNTIME_PYTHON_PLATFORM
36
+ ];
37
+ var EXCLUDED_SOURCE_DIRECTORIES = /* @__PURE__ */ new Set([
38
+ ".git",
39
+ ".venv",
40
+ "venv",
41
+ "__pycache__",
42
+ ".pytest_cache",
43
+ ".ruff_cache",
44
+ ".lambda-build",
45
+ "build",
46
+ "dist"
47
+ ]);
48
+ function resolveAppRuntimeDirectory(projectRoot) {
49
+ const runtimeDir = resolve(projectRoot, APP_RUNTIME_RELATIVE_DIR);
50
+ if (!existsSync(runtimeDir)) return void 0;
51
+ if (!existsSync(join(runtimeDir, "server.py"))) {
52
+ throw new Error(`App API directory exists but is missing ${join(APP_RUNTIME_RELATIVE_DIR, "server.py")}`);
53
+ }
54
+ return runtimeDir;
55
+ }
56
+ function shouldCopyRuntimeSource(runtimeDir, sourcePath) {
57
+ const sourceRelativePath = relative(runtimeDir, sourcePath);
58
+ if (!sourceRelativePath) return true;
59
+ const segments = sourceRelativePath.split(sep);
60
+ if (segments.some((segment) => EXCLUDED_SOURCE_DIRECTORIES.has(segment))) return false;
61
+ if (segments.some((segment) => segment === ".env" || segment.startsWith(".env."))) return false;
62
+ const name = basename(sourcePath);
63
+ if (name === "lambda.zip") return false;
64
+ return !name.endsWith(".pyc") && !name.endsWith(".pyo");
65
+ }
66
+ function copyRuntimeSources(runtimeDir, buildDir) {
67
+ cpSync(runtimeDir, buildDir, {
68
+ recursive: true,
69
+ filter: (candidate) => shouldCopyRuntimeSource(runtimeDir, candidate)
70
+ });
71
+ }
72
+ function ensureUVAvailable(runCommand) {
73
+ try {
74
+ runCommand("uv", ["--version"], { stdio: "ignore" });
75
+ } catch (error) {
76
+ throw new Error(
77
+ "uv is required to package App APIs. Install uv: https://docs.astral.sh/uv/",
78
+ { cause: error }
79
+ );
80
+ }
81
+ }
82
+ async function createZip(directory) {
83
+ return new Promise((resolveZip, reject) => {
84
+ const chunks = [];
85
+ const archive = archiver("zip", { zlib: { level: 9 } });
86
+ archive.on("data", (chunk) => chunks.push(chunk));
87
+ archive.on("end", () => resolveZip(Buffer.concat(chunks)));
88
+ archive.on("error", reject);
89
+ archive.directory(directory, false);
90
+ archive.finalize();
91
+ });
92
+ }
93
+ async function packageAppRuntime(runtimeDir, options = {}) {
94
+ if (!existsSync(join(runtimeDir, "server.py"))) {
95
+ throw new Error(`App API must contain server.py at ${join(runtimeDir, "server.py")}`);
96
+ }
97
+ const requirementsPath = join(runtimeDir, "requirements.txt");
98
+ const pyprojectPath = join(runtimeDir, "pyproject.toml");
99
+ if (!existsSync(requirementsPath) && !existsSync(pyprojectPath)) {
100
+ throw new Error("App API must contain requirements.txt or pyproject.toml");
101
+ }
102
+ const buildDir = mkdtempSync(join(tmpdir(), "lumera-app-runtime-"));
103
+ const runCommand = options.runCommand ?? execFileSync;
104
+ try {
105
+ ensureUVAvailable(runCommand);
106
+ let installRequirementsPath = requirementsPath;
107
+ let generatedRequirements = false;
108
+ if (!existsSync(requirementsPath)) {
109
+ installRequirementsPath = join(buildDir, "requirements.generated.txt");
110
+ generatedRequirements = true;
111
+ runCommand(
112
+ "uv",
113
+ [
114
+ "pip",
115
+ "compile",
116
+ "pyproject.toml",
117
+ ...APP_RUNTIME_TARGET_ARGS,
118
+ "-o",
119
+ installRequirementsPath
120
+ ],
121
+ {
122
+ cwd: runtimeDir,
123
+ stdio: "inherit"
124
+ }
125
+ );
126
+ }
127
+ runCommand("uv", [
128
+ "pip",
129
+ "install",
130
+ "--target",
131
+ buildDir,
132
+ ...APP_RUNTIME_TARGET_ARGS,
133
+ "--only-binary=:all:",
134
+ "-r",
135
+ installRequirementsPath
136
+ ], { stdio: "inherit" });
137
+ if (generatedRequirements) {
138
+ rmSync(installRequirementsPath, { force: true });
139
+ }
140
+ copyRuntimeSources(runtimeDir, buildDir);
141
+ const lambdaZip = await createZip(buildDir);
142
+ if (lambdaZip.length > MAX_APP_RUNTIME_ZIP_BYTES) {
143
+ throw new Error(
144
+ `App API package is too large (${lambdaZip.length} bytes > ${MAX_APP_RUNTIME_ZIP_BYTES}); remove heavy dependencies or split the app`
145
+ );
146
+ }
147
+ return lambdaZip;
148
+ } finally {
149
+ rmSync(buildDir, { recursive: true, force: true });
150
+ }
151
+ }
152
+ async function deployAppRuntime(options) {
153
+ const formData = new FormData();
154
+ formData.append("lambda_zip", new Blob([new Uint8Array(options.lambdaZip)]), "lambda.zip");
155
+ formData.append("version", options.version);
156
+ const res = await fetchWithRetry(`${options.apiUrl}/app-runtimes/current-project/deploy`, {
157
+ method: "POST",
158
+ headers: {
159
+ Authorization: `Bearer ${options.token}`,
160
+ "X-Lumera-Client": "lumera-cli",
161
+ "X-Lumera-Project": options.projectExternalId,
162
+ ...userContextHeaders()
163
+ },
164
+ body: formData
165
+ });
166
+ if (!res.ok) {
167
+ throw new Error(`App API deploy failed: ${await res.text()}`);
168
+ }
169
+ await res.arrayBuffer();
170
+ }
171
+
172
+ // src/lib/deploy.ts
15
173
  function formatTimestampVersion(date = /* @__PURE__ */ new Date()) {
16
174
  const pad = (value) => String(value).padStart(2, "0");
17
175
  return [
@@ -25,7 +183,7 @@ function formatTimestampVersion(date = /* @__PURE__ */ new Date()) {
25
183
  ].join("") + "Z";
26
184
  }
27
185
  function resolveVersion(cwd, gitRelease = resolveGitReleaseState(cwd)) {
28
- const pkgPath = resolve(cwd, "package.json");
186
+ const pkgPath = resolve2(cwd, "package.json");
29
187
  try {
30
188
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
31
189
  if (pkg?.version && pkg.version !== "0.0.0") return String(pkg.version);
@@ -37,13 +195,13 @@ function resolveVersion(cwd, gitRelease = resolveGitReleaseState(cwd)) {
37
195
  function resolveUIVersion(cwd) {
38
196
  try {
39
197
  const installed = JSON.parse(
40
- readFileSync(resolve(cwd, "node_modules/@lumerahq/ui/package.json"), "utf-8")
198
+ readFileSync(resolve2(cwd, "node_modules/@lumerahq/ui/package.json"), "utf-8")
41
199
  );
42
200
  if (installed?.version) return String(installed.version);
43
201
  } catch {
44
202
  }
45
203
  try {
46
- const pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf-8"));
204
+ const pkg = JSON.parse(readFileSync(resolve2(cwd, "package.json"), "utf-8"));
47
205
  const spec = pkg?.dependencies?.["@lumerahq/ui"] ?? pkg?.devDependencies?.["@lumerahq/ui"];
48
206
  if (!spec) return "none";
49
207
  const cleaned = String(spec).replace(/^[\^~>=<\s]+/, "");
@@ -79,16 +237,56 @@ function resolveGitReleaseState(cwd) {
79
237
  }
80
238
  }
81
239
  async function createTarball(distDir) {
82
- return new Promise((resolve2, reject) => {
240
+ return new Promise((resolve3, reject) => {
83
241
  const chunks = [];
84
- const archive = archiver("tar", { gzip: true });
242
+ const archive = archiver2("tar", { gzip: true });
85
243
  archive.on("data", (chunk) => chunks.push(chunk));
86
- archive.on("end", () => resolve2(Buffer.concat(chunks)));
244
+ archive.on("end", () => resolve3(Buffer.concat(chunks)));
87
245
  archive.on("error", reject);
88
246
  archive.directory(distDir, false);
89
247
  archive.finalize();
90
248
  });
91
249
  }
250
+ async function packageResolvedAppRuntime(appRuntimeDir) {
251
+ console.log(pc.dim("Packaging App API..."));
252
+ const lambdaZip = await packageAppRuntime(appRuntimeDir);
253
+ console.log(pc.dim(`App API package: ${(lambdaZip.length / 1024).toFixed(1)} KB`));
254
+ return lambdaZip;
255
+ }
256
+ async function packageOptionalAppRuntime(projectRoot) {
257
+ const appRuntimeDir = resolveAppRuntimeDirectory(projectRoot);
258
+ return appRuntimeDir ? packageResolvedAppRuntime(appRuntimeDir) : void 0;
259
+ }
260
+ async function packageRequiredAppRuntime(projectRoot) {
261
+ const appRuntimeDir = resolveAppRuntimeDirectory(projectRoot);
262
+ if (!appRuntimeDir) {
263
+ throw new Error("App API not found. Expected app-api/lambda/server.py");
264
+ }
265
+ return packageResolvedAppRuntime(appRuntimeDir);
266
+ }
267
+ function resolveDeploymentVersion(projectRoot, gitRelease, explicitVersion) {
268
+ return explicitVersion || process.env.LUMERA_APP_VERSION?.trim() || resolveVersion(projectRoot, gitRelease);
269
+ }
270
+ async function publishAppRuntime(options) {
271
+ console.log(pc.cyan("Deploying App API..."));
272
+ await deployAppRuntime(options);
273
+ console.log(pc.green("App API deployed!"));
274
+ }
275
+ async function deployAppAPI(options) {
276
+ const gitRelease = resolveGitReleaseState(options.projectRoot);
277
+ assertDeployableGitRelease(gitRelease);
278
+ const lambdaZip = await packageRequiredAppRuntime(options.projectRoot);
279
+ const version = resolveDeploymentVersion(options.projectRoot, gitRelease, options.version);
280
+ console.log(pc.dim(`Version: ${version}`));
281
+ await publishAppRuntime({
282
+ apiUrl: options.apiUrl,
283
+ token: options.token,
284
+ projectExternalId: options.appName,
285
+ version,
286
+ lambdaZip
287
+ });
288
+ return version;
289
+ }
92
290
  async function ensureAppRecord(apiBase, token, appName, appTitle, accessLevel) {
93
291
  const appData = {
94
292
  name: appTitle,
@@ -118,18 +316,20 @@ async function deploy(options) {
118
316
  appTitle,
119
317
  distDir,
120
318
  apiUrl,
121
- accessLevel
319
+ accessLevel,
320
+ skipAppAPI
122
321
  } = options;
123
- if (!existsSync(distDir)) {
322
+ if (!existsSync2(distDir)) {
124
323
  throw new Error(`dist directory not found: ${distDir}`);
125
324
  }
126
325
  const projectDir = dirname(distDir);
127
326
  const gitRelease = resolveGitReleaseState(projectDir);
128
327
  assertDeployableGitRelease(gitRelease);
328
+ const lambdaZip = skipAppAPI ? void 0 : await packageOptionalAppRuntime(projectDir);
129
329
  console.log(pc.dim("Ensuring app record..."));
130
330
  await ensureAppRecord(apiUrl, token, appName, appTitle, accessLevel);
131
- console.log(pc.cyan(`Deploying ${appTitle}...`));
132
- const version = options.version || process.env.LUMERA_APP_VERSION?.trim() || resolveVersion(projectDir, gitRelease);
331
+ console.log(pc.cyan(`Preparing ${appTitle}...`));
332
+ const version = resolveDeploymentVersion(projectDir, gitRelease, options.version);
133
333
  console.log(pc.dim(`Version: ${version}`));
134
334
  const tarball = await createTarball(distDir);
135
335
  console.log(pc.dim(`Package: ${(tarball.length / 1024).toFixed(1)} KB`));
@@ -139,6 +339,16 @@ async function deploy(options) {
139
339
  if (gitRelease.sha) formData.append("git_sha", gitRelease.sha);
140
340
  const uiVersion = resolveUIVersion(projectDir);
141
341
  if (uiVersion) formData.append("ui_version", uiVersion);
342
+ if (lambdaZip) {
343
+ await publishAppRuntime({
344
+ apiUrl,
345
+ token,
346
+ projectExternalId: appName,
347
+ version,
348
+ lambdaZip
349
+ });
350
+ }
351
+ console.log(pc.cyan(`Deploying ${appTitle} frontend...`));
142
352
  const res = await fetchWithRetry(`${apiUrl}/custom-apps/${appName}/deploy`, {
143
353
  method: "POST",
144
354
  headers: {
@@ -181,19 +391,19 @@ async function registerDevApp(apiBase, token, appName, appTitle, appUrl) {
181
391
  }
182
392
  }
183
393
  function isPortInUse(port, host = "127.0.0.1") {
184
- return new Promise((resolve2) => {
394
+ return new Promise((resolve3) => {
185
395
  const socket = createConnection({ port, host });
186
396
  socket.once("connect", () => {
187
397
  socket.destroy();
188
- resolve2(true);
398
+ resolve3(true);
189
399
  });
190
400
  socket.once("error", () => {
191
401
  socket.destroy();
192
- resolve2(false);
402
+ resolve3(false);
193
403
  });
194
404
  socket.setTimeout(1e3, () => {
195
405
  socket.destroy();
196
- resolve2(false);
406
+ resolve3(false);
197
407
  });
198
408
  });
199
409
  }
@@ -213,7 +423,7 @@ function parsePackageManagerSpecifier(value) {
213
423
  }
214
424
  function packageManagerFromPackageJson(projectRoot) {
215
425
  try {
216
- const pkg = JSON.parse(readFileSync(resolve(projectRoot, "package.json"), "utf-8"));
426
+ const pkg = JSON.parse(readFileSync(resolve2(projectRoot, "package.json"), "utf-8"));
217
427
  return parsePackageManagerSpecifier(pkg.packageManager);
218
428
  } catch {
219
429
  return void 0;
@@ -243,7 +453,7 @@ function detectRunner(projectRoot) {
243
453
  ];
244
454
  for (const { pm, file } of lockfileManagers) {
245
455
  const runner = runnerForPackageManager(pm);
246
- if (existsSync(resolve(projectRoot, file)) && commandAvailable(runner[0])) return runner;
456
+ if (existsSync2(resolve2(projectRoot, file)) && commandAvailable(runner[0])) return runner;
247
457
  }
248
458
  for (const pm of ["pnpm", "bun", "yarn", "npm"]) {
249
459
  const runner = runnerForPackageManager(pm);
@@ -296,8 +506,8 @@ async function dev(options) {
296
506
  );
297
507
  const runner = detectRunner(projectRoot);
298
508
  const viteArgs = ["vite", "--port", String(port), "--strictPort"];
299
- const sandboxConfig = resolve(projectRoot, "vite.sandbox.config.ts");
300
- if (existsSync(sandboxConfig)) {
509
+ const sandboxConfig = resolve2(projectRoot, "vite.sandbox.config.ts");
510
+ if (existsSync2(sandboxConfig)) {
301
511
  viteArgs.push("--config", "vite.sandbox.config.ts");
302
512
  }
303
513
  if (host) {
@@ -325,6 +535,7 @@ async function dev(options) {
325
535
  }
326
536
 
327
537
  export {
538
+ deployAppAPI,
328
539
  deploy,
329
540
  dev
330
541
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  dev
3
- } from "./chunk-IVII7OZZ.js";
3
+ } from "./chunk-KE26WWAQ.js";
4
4
  import {
5
5
  syncDeps
6
6
  } from "./chunk-HS63O4MH.js";
package/dist/index.js CHANGED
@@ -193,12 +193,14 @@ ${pc.dim("Resource Types:")}
193
193
  automations Background Python scripts
194
194
  hooks Collection lifecycle handlers
195
195
  agents AI agents with skills and prompts
196
- app Frontend application
196
+ app Frontend and App API
197
+ app-api FastAPI application backend
197
198
 
198
199
  ${pc.dim("Examples:")}
199
200
  lumera apply # Apply all resources
200
201
  lumera apply collections/users # Apply single collection
201
- lumera apply app # Deploy frontend
202
+ lumera apply app # Deploy frontend and App API
203
+ lumera apply app-api # Deploy only the App API
202
204
  lumera list # List all resources
203
205
  lumera show collections/users # Show collection details
204
206
  lumera run scripts/seed.py # Run a script
@@ -240,29 +242,29 @@ async function main() {
240
242
  switch (command) {
241
243
  // Resource commands
242
244
  case "plan":
243
- await import("./resources-FHL4IUQD.js").then((m) => m.plan(args.slice(1)));
245
+ await import("./resources-4677FHV6.js").then((m) => m.plan(args.slice(1)));
244
246
  break;
245
247
  case "apply":
246
- await import("./resources-FHL4IUQD.js").then((m) => m.apply(args.slice(1)));
248
+ await import("./resources-4677FHV6.js").then((m) => m.apply(args.slice(1)));
247
249
  break;
248
250
  case "pull":
249
- await import("./resources-FHL4IUQD.js").then((m) => m.pull(args.slice(1)));
251
+ await import("./resources-4677FHV6.js").then((m) => m.pull(args.slice(1)));
250
252
  break;
251
253
  case "destroy":
252
- await import("./resources-FHL4IUQD.js").then((m) => m.destroy(args.slice(1)));
254
+ await import("./resources-4677FHV6.js").then((m) => m.destroy(args.slice(1)));
253
255
  break;
254
256
  case "list":
255
- await import("./resources-FHL4IUQD.js").then((m) => m.list(args.slice(1)));
257
+ await import("./resources-4677FHV6.js").then((m) => m.list(args.slice(1)));
256
258
  break;
257
259
  case "show":
258
- await import("./resources-FHL4IUQD.js").then((m) => m.show(args.slice(1)));
260
+ await import("./resources-4677FHV6.js").then((m) => m.show(args.slice(1)));
259
261
  break;
260
262
  case "diff":
261
- await import("./resources-FHL4IUQD.js").then((m) => m.diff(args.slice(1)));
263
+ await import("./resources-4677FHV6.js").then((m) => m.diff(args.slice(1)));
262
264
  break;
263
265
  // Development
264
266
  case "dev":
265
- await import("./dev-7ZQRZJXX.js").then((m) => m.dev(args.slice(1)));
267
+ await import("./dev-ANVS25HB.js").then((m) => m.dev(args.slice(1)));
266
268
  break;
267
269
  case "run":
268
270
  await import("./run-7KJPBOIY.js").then((m) => m.run(args.slice(1)));
@@ -1,6 +1,7 @@
1
1
  import {
2
- deploy
3
- } from "./chunk-IVII7OZZ.js";
2
+ deploy,
3
+ deployAppAPI
4
+ } from "./chunk-KE26WWAQ.js";
4
5
  import {
5
6
  syncDeps
6
7
  } from "./chunk-HS63O4MH.js";
@@ -1457,11 +1458,13 @@ ${pc2.dim("Resources:")}
1457
1458
  skills/<slug> Apply single project skill
1458
1459
  mailboxes Apply only mailboxes
1459
1460
  mailboxes/<slug> Apply single mailbox
1460
- app Deploy the frontend app
1461
+ app Deploy the frontend and App API
1462
+ app-api Deploy only the App API
1461
1463
 
1462
1464
  ${pc2.dim("Options:")}
1463
1465
  --yes, -y Skip confirmation prompt (for CI/CD)
1464
- --skip-build Skip build step when applying app
1466
+ --skip-build Skip the frontend build; still package the App API
1467
+ --skip-app-api Publish only the frontend, even if an App API exists
1465
1468
  --no-app Apply resources only; do not build/deploy the app
1466
1469
  --resources-only Alias for --no-app
1467
1470
  --package-manager Package manager for app builds (pnpm, bun, yarn, npm)
@@ -1471,8 +1474,10 @@ ${pc2.dim("Examples:")}
1471
1474
  lumera apply collections # Apply all collections
1472
1475
  lumera apply collections/users # Apply single collection
1473
1476
  lumera apply agents -y # Apply agents without confirmation
1474
- lumera apply app # Deploy frontend
1475
- lumera apply app --skip-build # Deploy without rebuilding
1477
+ lumera apply app # Deploy frontend and App API
1478
+ lumera apply app-api # Deploy only the App API
1479
+ lumera apply app --skip-build # Reuse dist; package and deploy App API
1480
+ lumera apply app --skip-app-api # Deploy only the frontend
1476
1481
  lumera apply --no-app -y # Apply resources without app build/deploy
1477
1482
  lumera apply app --package-manager pnpm
1478
1483
  `);
@@ -2478,6 +2483,7 @@ async function applyHooks(api, localHooks, collections, projectId) {
2478
2483
  }
2479
2484
  async function applyApp(args) {
2480
2485
  const skipBuild = args.includes("--skip-build");
2486
+ const skipAppAPI = args.includes("--skip-app-api");
2481
2487
  const projectRoot = findProjectRoot();
2482
2488
  loadEnv(projectRoot);
2483
2489
  const token = getToken(projectRoot);
@@ -2508,7 +2514,15 @@ async function applyApp(args) {
2508
2514
  `Invalid --access-level "${accessLevel}". Valid values: ${validAccessLevels.join(", ")}`
2509
2515
  );
2510
2516
  }
2511
- await deploy({ token, appName, appTitle, distDir, apiUrl, accessLevel });
2517
+ await deploy({ token, appName, appTitle, distDir, apiUrl, accessLevel, skipAppAPI });
2518
+ }
2519
+ async function applyAppAPI(projectRoot) {
2520
+ await deployAppAPI({
2521
+ token: getToken(projectRoot),
2522
+ appName: getAppName(projectRoot),
2523
+ projectRoot,
2524
+ apiUrl: getApiUrl()
2525
+ });
2512
2526
  }
2513
2527
  async function pullCollections(api, platformDir, filterName, projectId) {
2514
2528
  const collectionsDir = join2(platformDir, "collections");
@@ -4351,11 +4365,25 @@ async function apply(args) {
4351
4365
  }
4352
4366
  const projectRoot = findProjectRoot();
4353
4367
  loadEnv(projectRoot);
4368
+ const positionalArgs = getPositionalArgs(args);
4369
+ if (positionalArgs[0] === "app-api") {
4370
+ if (args.includes("--skip-app-api")) {
4371
+ throw new Error("Cannot combine app-api resource with --skip-app-api");
4372
+ }
4373
+ console.log();
4374
+ console.log(pc2.cyan(pc2.bold(" Apply")));
4375
+ console.log();
4376
+ console.log(pc2.bold(" App API:"));
4377
+ await applyAppAPI(projectRoot);
4378
+ console.log();
4379
+ console.log(pc2.green(" Done!"));
4380
+ console.log();
4381
+ return;
4382
+ }
4354
4383
  const platformDir = getPlatformDir();
4355
4384
  const appName = getAppName(projectRoot);
4356
4385
  const api = createApiClient(void 0, void 0, appName);
4357
4386
  const projectId = getProjectId(projectRoot);
4358
- const positionalArgs = getPositionalArgs(args);
4359
4387
  const { type, name } = parseResource(positionalArgs[0]);
4360
4388
  const autoConfirm = args.includes("--yes") || args.includes("-y") || !!process.env.CI;
4361
4389
  const skipApp = args.includes("--no-app") || args.includes("--resources-only");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.31.2",
3
+ "version": "0.32.1",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {
@@ -31,7 +31,7 @@
31
31
  "check:ci": "biome check . && tsr generate && tsc --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@lumerahq/ui": "^0.20.0",
34
+ "@lumerahq/ui": "^0.20.1",
35
35
  "@tanstack/react-query": "^5.90.11",
36
36
  "@tanstack/react-router": "1.155.0",
37
37
  "clsx": "^2.1.1",
@@ -19,8 +19,8 @@ importers:
19
19
  .:
20
20
  dependencies:
21
21
  '@lumerahq/ui':
22
- specifier: ^0.20.0
23
- version: 0.20.0(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
22
+ specifier: ^0.20.1
23
+ version: 0.20.1(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
24
24
  '@tanstack/react-query':
25
25
  specifier: ^5.90.11
26
26
  version: 5.101.0(react@19.2.7)
@@ -464,8 +464,8 @@ packages:
464
464
  '@jridgewell/trace-mapping@0.3.31':
465
465
  resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
466
466
 
467
- '@lumerahq/ui@0.20.0':
468
- resolution: {integrity: sha512-WMfDaM122WUvoZ/JYmoCGxqE13zKTwCnEJCx/BYu5NFFZMb2I+5ZhMMgFmMLSX112AmxPe30FjnqBtEEhhIuZw==}
467
+ '@lumerahq/ui@0.20.1':
468
+ resolution: {integrity: sha512-oFY5/G/KY8yVdX0kPChZptcO0Nm4OBjTIjU9UHSxzEpJcXXjJJXE80qseJVa5on9B1N7e0MlEPS5wdgesnHoww==}
469
469
  peerDependencies:
470
470
  react: ^18.0.0 || ^19.0.0
471
471
  react-dom: ^18.0.0 || ^19.0.0
@@ -2318,7 +2318,7 @@ snapshots:
2318
2318
  '@jridgewell/resolve-uri': 3.1.2
2319
2319
  '@jridgewell/sourcemap-codec': 1.5.5
2320
2320
 
2321
- '@lumerahq/ui@0.20.0(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
2321
+ '@lumerahq/ui@0.20.1(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
2322
2322
  dependencies:
2323
2323
  '@base-ui/react': 1.5.0(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
2324
2324
  '@tanstack/react-query': 5.101.0(react@19.2.7)