@lumerahq/cli 0.30.12 → 0.30.14

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.
@@ -5,11 +5,11 @@ import { join, resolve } from "path";
5
5
  import pc from "picocolors";
6
6
 
7
7
  // src/lib/functions-sdk.ts
8
- var FUNCTIONS_SDK_VERSION_RANGE = ">=0.36.2,<0.37.0";
9
- var FUNCTIONS_SDK_COMPATIBILITY_RANGE = ">=0.34.0,<0.37.0";
8
+ var FUNCTIONS_SDK_VERSION_RANGE = ">=0.38.1,<0.39.0";
9
+ var FUNCTIONS_SDK_COMPATIBILITY_RANGE = ">=0.34.0,<0.39.0";
10
10
  var FUNCTIONS_SDK_REQUIREMENT = `lumera[functions]${FUNCTIONS_SDK_COMPATIBILITY_RANGE}`;
11
11
  var DEFAULT_FUNCTIONS_SDK_REQUIREMENT = `lumera[functions]${FUNCTIONS_SDK_VERSION_RANGE}`;
12
- var RELEASE_SDK_REQUIREMENT = `lumera[release]${FUNCTIONS_SDK_COMPATIBILITY_RANGE}`;
12
+ var RELEASE_SDK_REQUIREMENT = `lumera[release]${FUNCTIONS_SDK_VERSION_RANGE}`;
13
13
 
14
14
  // src/commands/functions.ts
15
15
  var SUPPORTED_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "inspect", "invoke", "test"]);
@@ -4,7 +4,7 @@ import {
4
4
  findFunctionsProjectRoot,
5
5
  functions,
6
6
  runFunctionRunner
7
- } from "./chunk-ROARJJVX.js";
7
+ } from "./chunk-AEZLQMFF.js";
8
8
  import "./chunk-PNKVD2UK.js";
9
9
  export {
10
10
  buildFunctionRunnerArgs,
package/dist/index.js CHANGED
@@ -268,12 +268,12 @@ async function main() {
268
268
  await import("./run-EBTA3FKD.js").then((m) => m.run(args.slice(1)));
269
269
  break;
270
270
  case "functions":
271
- await import("./functions-G37WQWTG.js").then(
271
+ await import("./functions-DZCNHDRJ.js").then(
272
272
  (m) => m.functions(subcommand, args.slice(2))
273
273
  );
274
274
  break;
275
275
  case "project":
276
- await import("./project-GAVODH6S.js").then(
276
+ await import("./project-3HITJUM4.js").then(
277
277
  (m) => m.project(subcommand, args.slice(2))
278
278
  );
279
279
  break;
@@ -1,13 +1,11 @@
1
1
  import {
2
2
  RELEASE_SDK_REQUIREMENT,
3
3
  findFunctionsProjectRoot
4
- } from "./chunk-ROARJJVX.js";
4
+ } from "./chunk-AEZLQMFF.js";
5
5
  import "./chunk-PNKVD2UK.js";
6
6
 
7
7
  // src/commands/project.ts
8
8
  import { spawn } from "child_process";
9
- import { mkdirSync } from "fs";
10
- import { join } from "path";
11
9
  import pc from "picocolors";
12
10
  var RELEASE_PROTOCOL_VERSION = 1;
13
11
  var RELEASE_COMMANDS = /* @__PURE__ */ new Set([
@@ -17,32 +15,6 @@ var RELEASE_COMMANDS = /* @__PURE__ */ new Set([
17
15
  "deploy",
18
16
  "deploy-runtime"
19
17
  ]);
20
- function releaseCommandOutput(command, args, result) {
21
- if (command !== "deploy" || !result.envelope.ok || result.envelope.activation_required === true) {
22
- return result.raw;
23
- }
24
- const deployment = result.envelope.deployment;
25
- const release = typeof deployment === "object" && deployment !== null ? deployment.release : void 0;
26
- const releaseId = typeof release === "object" && release !== null ? String(release.id ?? "") : "";
27
- const projectIndex = args.indexOf("--project");
28
- const projectRef = projectIndex >= 0 ? String(args[projectIndex + 1] ?? "") : "";
29
- if (!releaseId || !projectRef) return result.raw;
30
- return `${JSON.stringify({
31
- ...result.envelope,
32
- activation_required: true,
33
- message: "Release uploaded but inactive; deploy it before invoking Functions.",
34
- next_command: [
35
- "lumera",
36
- "project",
37
- "release",
38
- "deploy-runtime",
39
- releaseId,
40
- "--project",
41
- projectRef
42
- ]
43
- })}
44
- `;
45
- }
46
18
  function showHelp() {
47
19
  console.log(`
48
20
  ${pc.dim("Usage:")}
@@ -76,17 +48,11 @@ function buildReleaseRunnerArgs(command, args, projectRoot) {
76
48
  ...args
77
49
  ];
78
50
  }
79
- async function runReleaseRunner(runnerArgs, projectRoot, releaseCommand, spawnRunner = (command, args, options) => spawn(command, args, options)) {
80
- let runnerEnvironment = process.env;
81
- if (releaseCommand === "build") {
82
- const releaseTempRoot = join(projectRoot, ".lumera", "tmp");
83
- mkdirSync(releaseTempRoot, { recursive: true, mode: 448 });
84
- runnerEnvironment = { ...process.env, TMPDIR: releaseTempRoot };
85
- }
51
+ async function runReleaseRunner(runnerArgs, projectRoot, spawnRunner = (command, args, options) => spawn(command, args, options)) {
86
52
  return await new Promise((resolvePromise, reject) => {
87
53
  const child = spawnRunner("uv", runnerArgs, {
88
54
  cwd: projectRoot,
89
- env: runnerEnvironment,
55
+ env: process.env,
90
56
  shell: false,
91
57
  stdio: ["inherit", "pipe", "inherit"]
92
58
  });
@@ -130,10 +96,9 @@ async function project(subcommand, args, dependencies = {}) {
130
96
  const projectRoot = findProjectRoot();
131
97
  const result = await runRunner(
132
98
  buildReleaseRunnerArgs(releaseCommand, releaseArgs, projectRoot),
133
- projectRoot,
134
- releaseCommand
99
+ projectRoot
135
100
  );
136
- writeOutput(releaseCommandOutput(releaseCommand, releaseArgs, result));
101
+ writeOutput(result.raw);
137
102
  if (result.code !== 0) process.exitCode = result.code;
138
103
  } catch (error) {
139
104
  writeOutput(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.30.12",
3
+ "version": "0.30.14",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {
@@ -4,7 +4,7 @@ version = "0.1.0"
4
4
  description = "{{projectTitle}} - Lumera custom app"
5
5
  requires-python = ">=3.11"
6
6
  dependencies = [
7
- "lumera[functions]>=0.36.2,<0.37.0",
7
+ "lumera[functions]>=0.38.1,<0.39.0",
8
8
  ]
9
9
 
10
10
  [project.optional-dependencies]
@@ -1,9 +1,47 @@
1
- import { createContext } from 'react';
1
+ import { createContext, useContext } from 'react';
2
+
3
+ export type ViewerMemberTag = {
4
+ id: string;
5
+ key: string;
6
+ label: string;
7
+ color: string;
8
+ };
9
+
10
+ export type ViewerContext = {
11
+ version: 1;
12
+ isSignedIn: boolean;
13
+ subjectType: 'anonymous' | 'org_member' | 'public_app_user';
14
+ belongsToOrganization: boolean;
15
+ identity: { id: string; name: string; email: string } | null;
16
+ organizationMembership: {
17
+ id: string;
18
+ role: string;
19
+ isCompanyAdmin: boolean;
20
+ department: ViewerMemberTag | null;
21
+ tags: ViewerMemberTag[];
22
+ } | null;
23
+ appUser: { id: string; role: string } | null;
24
+ };
2
25
 
3
26
  export type AuthContextValue = {
4
27
  company?: { id: string; name?: string; apiName?: string };
5
28
  user?: { id: string; name: string; email: string; role?: string };
29
+ viewer: ViewerContext;
6
30
  sessionToken?: string;
7
31
  };
8
32
 
9
33
  export const AuthContext = createContext<AuthContextValue | null>(null);
34
+
35
+ export const useAuth = (): AuthContextValue => {
36
+ const value = useContext(AuthContext);
37
+ if (!value) throw new Error('useAuth must be used inside AuthContext.Provider');
38
+ return value;
39
+ };
40
+
41
+ export const hasMemberTag = (viewer: ViewerContext, key: string): boolean =>
42
+ viewer.organizationMembership?.tags.some(
43
+ (tag) => tag.key.trim().toLowerCase() === key.trim().toLowerCase()
44
+ ) === true;
45
+
46
+ export const isDepartment = (viewer: ViewerContext, key: string): boolean =>
47
+ viewer.organizationMembership?.department?.key.trim().toLowerCase() === key.trim().toLowerCase();
@@ -11,7 +11,12 @@ import { StrictMode, useEffect, useRef, useState } from 'react';
11
11
  import ReactDOM from 'react-dom/client';
12
12
  import { Toaster } from 'sonner';
13
13
 
14
- import { AuthContext, type AuthContextValue } from './lib/auth';
14
+ import {
15
+ AuthContext,
16
+ type AuthContextValue,
17
+ type ViewerContext,
18
+ type ViewerMemberTag,
19
+ } from './lib/auth';
15
20
  import { routeTree } from './routeTree.gen';
16
21
  import './styles.css';
17
22
 
@@ -39,6 +44,117 @@ declare module '@tanstack/react-router' {
39
44
 
40
45
  const ROUTE_STORAGE_KEY = '{{projectName}}-route';
41
46
 
47
+ type ViewerHostPayload = HostPayload & { viewer?: ViewerContext };
48
+
49
+ type ViewerResponse = {
50
+ version?: number;
51
+ is_signed_in?: boolean;
52
+ subject_type?: 'anonymous' | 'org_member' | 'public_app_user';
53
+ belongs_to_organization?: boolean;
54
+ identity?: { id?: string; name?: string; email?: string } | null;
55
+ organization_membership?: {
56
+ id?: string;
57
+ role?: string;
58
+ is_company_admin?: boolean;
59
+ department?: ViewerMemberTag | null;
60
+ tags?: ViewerMemberTag[];
61
+ } | null;
62
+ app_user?: { id?: string; role?: string } | null;
63
+ };
64
+
65
+ const viewerFromPayload = (payload: ViewerHostPayload): ViewerContext => {
66
+ if (payload.viewer) return payload.viewer;
67
+ const user = payload.user;
68
+ if (user && (payload.subjectType === 'app_user' || payload.subjectType === 'public_app_user')) {
69
+ return {
70
+ version: 1,
71
+ isSignedIn: true,
72
+ subjectType: 'public_app_user',
73
+ belongsToOrganization: false,
74
+ identity: { id: user.id, name: user.name, email: user.email },
75
+ organizationMembership: null,
76
+ appUser: { id: user.id, role: user.role ?? '' },
77
+ };
78
+ }
79
+ if (user) {
80
+ return {
81
+ version: 1,
82
+ isSignedIn: true,
83
+ subjectType: 'org_member',
84
+ belongsToOrganization: true,
85
+ identity: { id: user.id, name: user.name, email: user.email },
86
+ organizationMembership: {
87
+ id: user.id,
88
+ role: user.role ?? '',
89
+ isCompanyAdmin: user.role?.toLowerCase() === 'admin',
90
+ department: null,
91
+ tags: [],
92
+ },
93
+ appUser: null,
94
+ };
95
+ }
96
+ return {
97
+ version: 1,
98
+ isSignedIn: false,
99
+ subjectType: 'anonymous',
100
+ belongsToOrganization: false,
101
+ identity: null,
102
+ organizationMembership: null,
103
+ appUser: null,
104
+ };
105
+ };
106
+
107
+ const viewerFromResponse = (response: ViewerResponse): ViewerContext => ({
108
+ version: 1,
109
+ isSignedIn: response.is_signed_in === true,
110
+ subjectType: response.subject_type ?? 'anonymous',
111
+ belongsToOrganization: response.belongs_to_organization === true,
112
+ identity: response.identity
113
+ ? {
114
+ id: response.identity.id ?? '',
115
+ name: response.identity.name ?? '',
116
+ email: response.identity.email ?? '',
117
+ }
118
+ : null,
119
+ organizationMembership: response.organization_membership
120
+ ? {
121
+ id: response.organization_membership.id ?? '',
122
+ role: response.organization_membership.role ?? '',
123
+ isCompanyAdmin: response.organization_membership.is_company_admin === true,
124
+ department: response.organization_membership.department ?? null,
125
+ tags: response.organization_membership.tags ?? [],
126
+ }
127
+ : null,
128
+ appUser: response.app_user
129
+ ? {
130
+ id: response.app_user.id ?? '',
131
+ role: response.app_user.role ?? '',
132
+ }
133
+ : null,
134
+ });
135
+
136
+ const resolveViewer = async (payload: ViewerHostPayload): Promise<ViewerContext> => {
137
+ if (payload.viewer) return payload.viewer;
138
+
139
+ // The checked-in lockfile can briefly trail a newly published UI bridge.
140
+ // Keep standalone apps complete during that release window by reading the
141
+ // canonical block from the same authenticated /api/me endpoint.
142
+ if (!isEmbedded()) {
143
+ try {
144
+ const response = await fetch('/api/me', { credentials: 'same-origin' });
145
+ if (response.ok) {
146
+ const body = (await response.json()) as { viewer?: ViewerResponse };
147
+ if (body.viewer) return viewerFromResponse(body.viewer);
148
+ }
149
+ } catch {
150
+ // Compatibility fallback below preserves the pre-viewer behavior when
151
+ // an older app host does not yet expose the canonical response.
152
+ }
153
+ }
154
+
155
+ return viewerFromPayload(payload);
156
+ };
157
+
42
158
  function RouteRestorer() {
43
159
  const isFirstLoad = useRef(true);
44
160
 
@@ -75,7 +191,8 @@ const App = () => {
75
191
  // in the Lumera shell, same-origin /api with the app-session cookie when
76
192
  // served standalone on the app's own hostname, and direct dev fetch when
77
193
  // VITE_DEV_API_BASE_URL is set.
78
- const cleanup = onInitMessage((payload?: HostPayload) => {
194
+ const cleanup = onInitMessage((rawPayload?: HostPayload) => {
195
+ const payload = rawPayload as ViewerHostPayload | undefined;
79
196
  if (!payload) {
80
197
  // No identity and no app context: authentication is required
81
198
  // (payload.user alone being undefined means an anonymous visitor on
@@ -83,12 +200,15 @@ const App = () => {
83
200
  setStatus('unauthenticated');
84
201
  return;
85
202
  }
86
- setHostContext({
87
- company: payload.company,
88
- user: payload.user,
89
- sessionToken: payload.session?.token,
203
+ void resolveViewer(payload).then((viewer) => {
204
+ setHostContext({
205
+ company: payload.company,
206
+ user: payload.user,
207
+ viewer,
208
+ sessionToken: payload.session?.token,
209
+ });
210
+ setStatus('ready');
90
211
  });
91
- setStatus('ready');
92
212
  });
93
213
 
94
214
  postReadyMessage();