@lumerahq/cli 0.36.6 → 0.37.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/README.md CHANGED
@@ -51,6 +51,30 @@ isolated overlay, so an older project lock cannot select a pre-Functions SDK.
51
51
  Function modules and test locations are configured under
52
52
  `[tool.lumera.functions]` in `pyproject.toml`.
53
53
 
54
+ ## Project display name
55
+
56
+ `lumera init` and `lumera register` copy the registered project's current
57
+ display name into `lumera.name`. If initialization cannot register, it omits
58
+ the generated display name so a first deploy preserves the server's name.
59
+
60
+ Set the display name in `package.json`:
61
+
62
+ ```json
63
+ {
64
+ "name": "pr-review-hub",
65
+ "lumera": { "name": "PR Review Hub" }
66
+ }
67
+ ```
68
+
69
+ Every app deployment applies `lumera.name` to the linked project before
70
+ publishing, including App API-only and frontend-only deployments. The npm
71
+ `name` remains the stable project identifier. Omitting `lumera.name` preserves
72
+ the current project name. Renaming requires project edit access; a rejected
73
+ rename stops deployment. UI renames update this field in GitHub through a durable
74
+ workflow before changing the project name. The UI waits for the save to finish.
75
+ Pull current repository changes into external checkouts before deploying:
76
+ an old checkout also reapplies its configured name, even after a UI rename.
77
+
54
78
  ## Scaffolding Projects
55
79
 
56
80
  ### Interactive Mode
@@ -3,7 +3,7 @@ import {
3
3
  getCredentials,
4
4
  getTokenSource,
5
5
  init_auth
6
- } from "./chunk-3JL2E65P.js";
6
+ } from "./chunk-MBTOP7VA.js";
7
7
  import {
8
8
  fetchWithRetry
9
9
  } from "./chunk-FJFIWC7G.js";
@@ -4,11 +4,11 @@ import {
4
4
  import {
5
5
  createApiClient,
6
6
  isApiErrorStatus
7
- } from "./chunk-T5SFSXJ4.js";
7
+ } from "./chunk-YJJGLYF7.js";
8
8
  import {
9
9
  findProjectRoot,
10
10
  getAppName
11
- } from "./chunk-3JL2E65P.js";
11
+ } from "./chunk-MBTOP7VA.js";
12
12
 
13
13
  // src/commands/deps.ts
14
14
  import pc from "picocolors";
@@ -8,7 +8,10 @@ import {
8
8
  } from "./chunk-CHUCWVFV.js";
9
9
  import {
10
10
  userContextHeaders
11
- } from "./chunk-T5SFSXJ4.js";
11
+ } from "./chunk-YJJGLYF7.js";
12
+ import {
13
+ readPackageJson
14
+ } from "./chunk-MBTOP7VA.js";
12
15
  import {
13
16
  fetchWithRetry
14
17
  } from "./chunk-FJFIWC7G.js";
@@ -20,6 +23,46 @@ import { existsSync, readFileSync } from "fs";
20
23
  import { resolve, dirname } from "path";
21
24
  import archiver from "archiver";
22
25
  import pc from "picocolors";
26
+ async function syncProjectName(projectRoot, apiUrl, token, projectExternalId) {
27
+ const name = readPackageJson(projectRoot).lumera?.name;
28
+ if (name === void 0) return;
29
+ if (typeof name !== "string" || !name.trim()) {
30
+ throw new Error("package.json lumera.name must be a nonempty string");
31
+ }
32
+ const res = await fetchWithRetry(`${apiUrl}/projects/${encodeURIComponent(projectExternalId)}/settings`, {
33
+ method: "PATCH",
34
+ headers: {
35
+ Authorization: `Bearer ${token}`,
36
+ "Content-Type": "application/json",
37
+ "X-Lumera-Client": "lumera-cli",
38
+ ...userContextHeaders()
39
+ },
40
+ body: JSON.stringify({ name: name.trim() })
41
+ });
42
+ if (!res.ok) {
43
+ throw new Error(`Failed to update project name: ${await res.text()}`);
44
+ }
45
+ if (res.status === 202) {
46
+ const started = await res.json();
47
+ if (!started.task_id) throw new Error("Project rename returned no task ID");
48
+ const deadline = Date.now() + 18e4;
49
+ for (; ; ) {
50
+ const status = await fetchWithRetry(`${apiUrl}/workflow-runs/${encodeURIComponent(started.task_id)}`, {
51
+ headers: { Authorization: `Bearer ${token}`, "X-Lumera-Client": "lumera-cli", ...userContextHeaders() }
52
+ });
53
+ if (!status.ok) throw new Error(`Failed to check project rename: ${await status.text()}`);
54
+ const run = await status.json();
55
+ if (run.terminal) {
56
+ if (run.state !== "completed" || run.result?.value?.status !== "applied") {
57
+ throw new Error(`Project rename did not complete: ${JSON.stringify(run.failure ?? run.result)}. Task: ${started.task_id}`);
58
+ }
59
+ break;
60
+ }
61
+ if (Date.now() >= deadline) throw new Error(`Project rename is still running. Task: ${started.task_id}`);
62
+ await new Promise((resolve2) => setTimeout(resolve2, 1e3));
63
+ }
64
+ }
65
+ }
23
66
  function formatTimestampVersion(date = /* @__PURE__ */ new Date()) {
24
67
  const pad = (value) => String(value).padStart(2, "0");
25
68
  return [
@@ -160,6 +203,7 @@ async function deployAppAPI(options) {
160
203
  }
161
204
  const version = resolveDeploymentVersion(options.projectRoot, gitRelease, options.version);
162
205
  console.log(pc.dim(`Version: ${version}`));
206
+ await syncProjectName(options.projectRoot, options.apiUrl, options.token, options.appName);
163
207
  await deployProjectRuntime({
164
208
  apiUrl: options.apiUrl,
165
209
  token: options.token,
@@ -207,6 +251,7 @@ async function deploy(options) {
207
251
  const gitRelease = resolveGitReleaseState(projectDir);
208
252
  assertDeployableGitRelease(gitRelease);
209
253
  const projectRuntime = skipAppAPI ? { kind: "none" } : resolveConfiguredProjectRuntime(projectDir);
254
+ await syncProjectName(projectDir, apiUrl, token, appName);
210
255
  console.log(pc.dim("Ensuring app record..."));
211
256
  const appTitle = await ensureAppRecord(apiUrl, token, appName, accessLevel);
212
257
  console.log(pc.cyan(`Preparing ${appTitle}...`));
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getBaseUrl
3
- } from "./chunk-3JL2E65P.js";
3
+ } from "./chunk-MBTOP7VA.js";
4
4
  import {
5
5
  fetchWithRetry
6
6
  } from "./chunk-FJFIWC7G.js";
@@ -156,12 +156,13 @@ function getProjectId(projectRoot) {
156
156
  const pkg = readPackageJson(projectRoot);
157
157
  return pkg.lumera?.project_id;
158
158
  }
159
- function setProjectId(projectRoot, projectId) {
159
+ function setRegisteredProject(projectRoot, project) {
160
160
  const pkgPath = join2(projectRoot, "package.json");
161
161
  const raw = readFileSync2(pkgPath, "utf-8");
162
162
  const pkg = JSON.parse(raw);
163
163
  if (!pkg.lumera) pkg.lumera = {};
164
- pkg.lumera.project_id = projectId;
164
+ pkg.lumera.project_id = project.id;
165
+ pkg.lumera.name = project.name;
165
166
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
166
167
  }
167
168
  function getBaseUrl() {
@@ -193,7 +194,7 @@ export {
193
194
  getAppName,
194
195
  detectProjectVersion,
195
196
  getProjectId,
196
- setProjectId,
197
+ setRegisteredProject,
197
198
  getBaseUrl,
198
199
  getApiUrl
199
200
  };
@@ -2,7 +2,7 @@ import {
2
2
  getCredentials,
3
3
  getToken,
4
4
  init_auth
5
- } from "./chunk-3JL2E65P.js";
5
+ } from "./chunk-MBTOP7VA.js";
6
6
  import {
7
7
  fetchWithRetry
8
8
  } from "./chunk-FJFIWC7G.js";
@@ -2,10 +2,10 @@ import {
2
2
  deps,
3
3
  syncDeps,
4
4
  syncResourceShareDeps
5
- } from "./chunk-NNTGBSRL.js";
5
+ } from "./chunk-D35II5BI.js";
6
6
  import "./chunk-2CR762KB.js";
7
- import "./chunk-T5SFSXJ4.js";
8
- import "./chunk-3JL2E65P.js";
7
+ import "./chunk-YJJGLYF7.js";
8
+ import "./chunk-MBTOP7VA.js";
9
9
  import "./chunk-FJFIWC7G.js";
10
10
  import "./chunk-PNKVD2UK.js";
11
11
  export {
@@ -1,26 +1,26 @@
1
1
  import {
2
2
  dev
3
- } from "./chunk-6AKVB3FO.js";
3
+ } from "./chunk-FJIEHZWL.js";
4
4
  import "./chunk-UGAIWMQV.js";
5
5
  import "./chunk-CHUCWVFV.js";
6
6
  import "./chunk-PIY3XXJI.js";
7
7
  import "./chunk-KI57NBGA.js";
8
8
  import {
9
9
  syncDeps
10
- } from "./chunk-NNTGBSRL.js";
10
+ } from "./chunk-D35II5BI.js";
11
11
  import {
12
12
  loadEnv
13
13
  } from "./chunk-2CR762KB.js";
14
14
  import {
15
15
  createApiClient
16
- } from "./chunk-T5SFSXJ4.js";
16
+ } from "./chunk-YJJGLYF7.js";
17
17
  import {
18
18
  findProjectRoot,
19
19
  getApiUrl,
20
20
  getAppName,
21
21
  getToken,
22
22
  init_auth
23
- } from "./chunk-3JL2E65P.js";
23
+ } from "./chunk-MBTOP7VA.js";
24
24
  import "./chunk-FJFIWC7G.js";
25
25
  import "./chunk-PNKVD2UK.js";
26
26
 
package/dist/index.js CHANGED
@@ -243,32 +243,32 @@ async function main() {
243
243
  switch (command) {
244
244
  // Resource commands
245
245
  case "plan":
246
- await import("./resources-N6ELCQGN.js").then((m) => m.plan(args.slice(1)));
246
+ await import("./resources-E5JW74Z5.js").then((m) => m.plan(args.slice(1)));
247
247
  break;
248
248
  case "apply":
249
- await import("./resources-N6ELCQGN.js").then((m) => m.apply(args.slice(1)));
249
+ await import("./resources-E5JW74Z5.js").then((m) => m.apply(args.slice(1)));
250
250
  break;
251
251
  case "pull":
252
- await import("./resources-N6ELCQGN.js").then((m) => m.pull(args.slice(1)));
252
+ await import("./resources-E5JW74Z5.js").then((m) => m.pull(args.slice(1)));
253
253
  break;
254
254
  case "destroy":
255
- await import("./resources-N6ELCQGN.js").then((m) => m.destroy(args.slice(1)));
255
+ await import("./resources-E5JW74Z5.js").then((m) => m.destroy(args.slice(1)));
256
256
  break;
257
257
  case "list":
258
- await import("./resources-N6ELCQGN.js").then((m) => m.list(args.slice(1)));
258
+ await import("./resources-E5JW74Z5.js").then((m) => m.list(args.slice(1)));
259
259
  break;
260
260
  case "show":
261
- await import("./resources-N6ELCQGN.js").then((m) => m.show(args.slice(1)));
261
+ await import("./resources-E5JW74Z5.js").then((m) => m.show(args.slice(1)));
262
262
  break;
263
263
  case "diff":
264
- await import("./resources-N6ELCQGN.js").then((m) => m.diff(args.slice(1)));
264
+ await import("./resources-E5JW74Z5.js").then((m) => m.diff(args.slice(1)));
265
265
  break;
266
266
  // Development
267
267
  case "dev":
268
- await import("./dev-EEYY7XPP.js").then((m) => m.dev(args.slice(1)));
268
+ await import("./dev-UH3RUSL5.js").then((m) => m.dev(args.slice(1)));
269
269
  break;
270
270
  case "run":
271
- await import("./run-VM434JES.js").then((m) => m.run(args.slice(1)));
271
+ await import("./run-BDHWM3WZ.js").then((m) => m.run(args.slice(1)));
272
272
  break;
273
273
  case "functions":
274
274
  await import("./functions-C5SGYMZK.js").then(
@@ -282,27 +282,27 @@ async function main() {
282
282
  break;
283
283
  // Project
284
284
  case "init":
285
- await import("./init-SOBX62TC.js").then((m) => m.init(args.slice(1)));
285
+ await import("./init-PAQ33ZFU.js").then((m) => m.init(args.slice(1)));
286
286
  break;
287
287
  case "register":
288
- await import("./register-Y7SLDXLU.js").then((m) => m.register(args.slice(1)));
288
+ await import("./register-A4RZWHWF.js").then((m) => m.register(args.slice(1)));
289
289
  break;
290
290
  case "templates":
291
291
  await import("./templates-LNUOTNLN.js").then((m) => m.templates(subcommand, args.slice(2)));
292
292
  break;
293
293
  case "status":
294
- await import("./status-ZRJ46LXB.js").then((m) => m.status(args.slice(1)));
294
+ await import("./status-T2QGWEU6.js").then((m) => m.status(args.slice(1)));
295
295
  break;
296
296
  case "migrate":
297
- await import("./migrate-YLKMCION.js").then((m) => m.migrate(args.slice(1)));
297
+ await import("./migrate-H47N34N5.js").then((m) => m.migrate(args.slice(1)));
298
298
  break;
299
299
  // Skills
300
300
  case "skills":
301
- await import("./skills-4IBZ43OD.js").then((m) => m.skills(subcommand, args.slice(2)));
301
+ await import("./skills-GZYWPF2I.js").then((m) => m.skills(subcommand, args.slice(2)));
302
302
  break;
303
303
  // Dependencies
304
304
  case "deps":
305
- await import("./deps-4HMRDTU7.js").then((m) => m.deps(args.slice(1)));
305
+ await import("./deps-YRGLEW2Y.js").then((m) => m.deps(args.slice(1)));
306
306
  break;
307
307
  // Feature flags (read the in-sandbox snapshot)
308
308
  case "flags":
@@ -310,13 +310,13 @@ async function main() {
310
310
  break;
311
311
  // Auth
312
312
  case "login":
313
- await import("./auth-OHPKPSWX.js").then((m) => m.login(args.slice(1)));
313
+ await import("./auth-LQTZKGIV.js").then((m) => m.login(args.slice(1)));
314
314
  break;
315
315
  case "logout":
316
- await import("./auth-OHPKPSWX.js").then((m) => m.logout(args.slice(1)));
316
+ await import("./auth-LQTZKGIV.js").then((m) => m.logout(args.slice(1)));
317
317
  break;
318
318
  case "whoami":
319
- await import("./auth-OHPKPSWX.js").then((m) => m.whoami());
319
+ await import("./auth-LQTZKGIV.js").then((m) => m.whoami());
320
320
  break;
321
321
  // Convenience aliases
322
322
  case "help":
@@ -8,16 +8,17 @@ import {
8
8
  import {
9
9
  installAllSkills,
10
10
  syncSkillDescriptions
11
- } from "./chunk-BE4FBC34.js";
11
+ } from "./chunk-GRQGEL75.js";
12
12
  import {
13
13
  createApiClient,
14
14
  validateProjectNameLength
15
- } from "./chunk-T5SFSXJ4.js";
15
+ } from "./chunk-YJJGLYF7.js";
16
16
  import {
17
17
  getToken,
18
18
  init_auth,
19
- setProjectId
20
- } from "./chunk-3JL2E65P.js";
19
+ readPackageJson,
20
+ setRegisteredProject
21
+ } from "./chunk-MBTOP7VA.js";
21
22
  import "./chunk-FJFIWC7G.js";
22
23
  import "./chunk-PNKVD2UK.js";
23
24
 
@@ -362,6 +363,13 @@ async function init(args) {
362
363
  [sourceTitle, projectTitle]
363
364
  ];
364
365
  copyDir(templateDir, targetDir, replacements);
366
+ if (existsSync(join(targetDir, "package.json"))) {
367
+ const generatedPackage = readPackageJson(targetDir);
368
+ if (generatedPackage.lumera?.name !== void 0) {
369
+ delete generatedPackage.lumera.name;
370
+ writeFileSync(join(targetDir, "package.json"), JSON.stringify(generatedPackage, null, 2) + "\n");
371
+ }
372
+ }
365
373
  const installCommand = existsSync(join(targetDir, "pnpm-lock.yaml")) ? "pnpm install --frozen-lockfile" : "pnpm install";
366
374
  const hasPythonProject = existsSync(join(targetDir, "pyproject.toml"));
367
375
  function listFiles(dir, prefix = "") {
@@ -447,7 +455,7 @@ async function init(args) {
447
455
  const stopRegister = spinner("Registering project on Lumera...");
448
456
  try {
449
457
  const project = await api.registerProject(finalProjectName);
450
- setProjectId(targetDir, project.id);
458
+ setRegisteredProject(targetDir, project);
451
459
  stopRegister(pc.green("\u2713") + pc.dim(` Project registered (${project.id})`));
452
460
  registered = true;
453
461
  } catch (e) {
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  detectProjectVersion,
10
10
  findProjectRoot
11
- } from "./chunk-3JL2E65P.js";
11
+ } from "./chunk-MBTOP7VA.js";
12
12
  import "./chunk-PNKVD2UK.js";
13
13
 
14
14
  // src/commands/migrate.ts
@@ -7,13 +7,13 @@ import {
7
7
  import {
8
8
  createApiClient,
9
9
  validateProjectExternalId
10
- } from "./chunk-T5SFSXJ4.js";
10
+ } from "./chunk-YJJGLYF7.js";
11
11
  import {
12
12
  findProjectRoot,
13
13
  getAppName,
14
14
  getProjectId,
15
- setProjectId
16
- } from "./chunk-3JL2E65P.js";
15
+ setRegisteredProject
16
+ } from "./chunk-MBTOP7VA.js";
17
17
  import "./chunk-FJFIWC7G.js";
18
18
  import "./chunk-PNKVD2UK.js";
19
19
 
@@ -26,7 +26,7 @@ ${pc.dim("Usage:")}
26
26
 
27
27
  ${pc.dim("Description:")}
28
28
  Register (or re-link) the current project with the Lumera platform.
29
- Creates an lm_projects record and writes the project ID to package.json.
29
+ Creates an lm_projects record and writes its ID and current display name to package.json.
30
30
 
31
31
  ${pc.dim("Options:")}
32
32
  --force, -f Re-register even if project_id already exists in package.json
@@ -64,7 +64,7 @@ async function register(args) {
64
64
  const stop = spinner(`Registering "${appName}" on Lumera...`);
65
65
  try {
66
66
  const project = await api.registerProject(appName);
67
- setProjectId(projectRoot, project.id);
67
+ setRegisteredProject(projectRoot, project);
68
68
  stop(pc.green("\u2713") + ` Project registered: ${pc.dim(project.id)}`);
69
69
  } catch (e) {
70
70
  stop(pc.red("\u2717") + ` Failed to register project`);
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  deploy,
3
3
  deployAppAPI
4
- } from "./chunk-6AKVB3FO.js";
4
+ } from "./chunk-FJIEHZWL.js";
5
5
  import "./chunk-UGAIWMQV.js";
6
6
  import "./chunk-CHUCWVFV.js";
7
7
  import "./chunk-PIY3XXJI.js";
8
8
  import "./chunk-KI57NBGA.js";
9
9
  import {
10
10
  syncDeps
11
- } from "./chunk-NNTGBSRL.js";
11
+ } from "./chunk-D35II5BI.js";
12
12
  import {
13
13
  loadEnv
14
14
  } from "./chunk-2CR762KB.js";
@@ -17,7 +17,7 @@ import {
17
17
  createApiClient,
18
18
  isApiErrorStatus,
19
19
  validateCollectionNameLength
20
- } from "./chunk-T5SFSXJ4.js";
20
+ } from "./chunk-YJJGLYF7.js";
21
21
  import {
22
22
  findProjectRoot,
23
23
  getApiUrl,
@@ -25,7 +25,7 @@ import {
25
25
  getProjectId,
26
26
  getToken,
27
27
  init_auth
28
- } from "./chunk-3JL2E65P.js";
28
+ } from "./chunk-MBTOP7VA.js";
29
29
  import {
30
30
  fetchWithRetry
31
31
  } from "./chunk-FJFIWC7G.js";
@@ -3,14 +3,14 @@ import {
3
3
  } from "./chunk-2CR762KB.js";
4
4
  import {
5
5
  createApiClient
6
- } from "./chunk-T5SFSXJ4.js";
6
+ } from "./chunk-YJJGLYF7.js";
7
7
  import {
8
8
  findProjectRoot,
9
9
  getApiUrl,
10
10
  getAppName,
11
11
  getToken,
12
12
  init_auth
13
- } from "./chunk-3JL2E65P.js";
13
+ } from "./chunk-MBTOP7VA.js";
14
14
  import {
15
15
  fetchWithRetry
16
16
  } from "./chunk-FJFIWC7G.js";
@@ -8,8 +8,8 @@ import {
8
8
  slugToDirName,
9
9
  slugToFilename,
10
10
  syncSkillDescriptions
11
- } from "./chunk-BE4FBC34.js";
12
- import "./chunk-3JL2E65P.js";
11
+ } from "./chunk-GRQGEL75.js";
12
+ import "./chunk-MBTOP7VA.js";
13
13
  import "./chunk-FJFIWC7G.js";
14
14
  import "./chunk-PNKVD2UK.js";
15
15
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApiClient
3
- } from "./chunk-T5SFSXJ4.js";
3
+ } from "./chunk-YJJGLYF7.js";
4
4
  import {
5
5
  detectProjectVersion,
6
6
  findProjectRoot,
@@ -10,7 +10,7 @@ import {
10
10
  getTokenSource,
11
11
  init_auth,
12
12
  readPackageJson
13
- } from "./chunk-3JL2E65P.js";
13
+ } from "./chunk-MBTOP7VA.js";
14
14
  import {
15
15
  fetchWithRetry
16
16
  } from "./chunk-FJFIWC7G.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.36.6",
3
+ "version": "0.37.0",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {