camox 0.11.0 → 0.13.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.
@@ -14,6 +14,7 @@ const EnvironmentMenu = () => {
14
14
  const authCtx = React.useContext(AuthContext);
15
15
  if (!authCtx?.environmentName) return null;
16
16
  const isProduction = authCtx.environmentName === "production";
17
+ const label = isProduction ? "PROD" : "DEV";
17
18
  const badgeClassName = isProduction ? "bg-green-100 text-green-800 border border-green-300 hover:bg-green-100 dark:bg-green-900 dark:text-green-300 dark:border-green-700 dark:hover:bg-green-900 font-mono text-xs" : "bg-yellow-100 text-yellow-800 border border-yellow-300 hover:bg-yellow-100 dark:bg-yellow-900 dark:text-yellow-300 dark:border-yellow-700 dark:hover:bg-yellow-900 font-mono text-xs";
18
19
  let t0;
19
20
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
@@ -24,14 +25,14 @@ const EnvironmentMenu = () => {
24
25
  $[0] = t0;
25
26
  } else t0 = $[0];
26
27
  let t1;
27
- if ($[1] !== authCtx.environmentName || $[2] !== badgeClassName) {
28
+ if ($[1] !== badgeClassName || $[2] !== label) {
28
29
  t1 = /* @__PURE__ */ jsx(Badge, {
29
30
  variant: "secondary",
30
31
  className: badgeClassName,
31
- children: authCtx.environmentName
32
+ children: label
32
33
  });
33
- $[1] = authCtx.environmentName;
34
- $[2] = badgeClassName;
34
+ $[1] = badgeClassName;
35
+ $[2] = label;
35
36
  $[3] = t1;
36
37
  } else t1 = $[3];
37
38
  let t2;
@@ -61,7 +62,7 @@ const EnvironmentMenu = () => {
61
62
  children: "You are viewing the production environment."
62
63
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", {
63
64
  className: "text-sm",
64
- children: "This environment is your own space to iterate on content and data structures. It won't affect your teammates or production."
65
+ children: "This environment is your personal space to iterate on content and data structures. It won't affect your teammates or production."
65
66
  }), /* @__PURE__ */ jsx("p", {
66
67
  className: "text-muted-foreground text-xs",
67
68
  children: "You will be able to pull and push data between environments from here."
@@ -61,7 +61,7 @@ const Navbar = () => {
61
61
  const t4 = useIsPreviewSheetOpen() ? "opacity-100" : "opacity-0";
62
62
  let t5;
63
63
  if ($[7] !== t4) {
64
- t5 = cn("absolute top-0 left-0 w-full h-[calc(100%+2px)] bg-black/10 transition-opacity z-10 will-change-auto pointer-events-none supports-backdrop-filter:backdrop-blur-xs", t4);
64
+ t5 = cn("absolute top-0 left-0 w-full h-[calc(100%+2px)] bg-black/66 transition-opacity z-10 will-change-auto pointer-events-none", t4);
65
65
  $[7] = t4;
66
66
  $[8] = t5;
67
67
  } else t5 = $[8];
@@ -11,8 +11,14 @@ const SYNC_DEBOUNCE_DELAY_MS = 100;
11
11
  function throwIfSyncAuthError(error) {
12
12
  if (error instanceof Error && error.name === "ORPCError" && error.message.toLowerCase().includes("unauthorized")) throw new Error("[camox] Definition sync failed: invalid syncSecret.");
13
13
  }
14
+ function isNotFoundError(error) {
15
+ return error instanceof Error && error.name === "ORPCError" && error.message.toLowerCase().includes("not found");
16
+ }
17
+ function throwUnknownEnvironmentError(environmentName) {
18
+ throw new Error(`[camox] Environment "${environmentName}" does not exist. CAMOX_ENV must be "production" or a dev environment previously created by running the dev server while authenticated. Run \`npx camox login\` if needed.`);
19
+ }
14
20
  async function syncDefinitionsToApi(options) {
15
- const { camoxApp, projectSlug, apiUrl, syncSecret, environmentName, logger } = options;
21
+ const { camoxApp, projectSlug, apiUrl, syncSecret, environmentName, autoCreate, logger } = options;
16
22
  const client = createServerApiClient(apiUrl, environmentName);
17
23
  const blocks = camoxApp.getBlocks();
18
24
  const layoutDefinitions = camoxApp.getSerializableLayoutDefinitions();
@@ -33,13 +39,15 @@ async function syncDefinitionsToApi(options) {
33
39
  environmentCreated = (await client.blockDefinitions.sync({
34
40
  projectSlug,
35
41
  syncSecret,
42
+ autoCreate,
36
43
  definitions
37
44
  })).environmentCreated;
38
45
  } catch (error) {
39
46
  throwIfSyncAuthError(error);
47
+ if (!autoCreate && isNotFoundError(error)) throwUnknownEnvironmentError(environmentName);
40
48
  throw error;
41
49
  }
42
- if (environmentCreated && environmentName) logger.info(`[camox] Created environment "${environmentName}" (forked from production)`, { timestamp: true });
50
+ if (environmentCreated) logger.info(`[camox] Created empty environment "${environmentName}"`, { timestamp: true });
43
51
  logger.info(`[camox] Synced ${definitions.length} block definition${definitions.length === 1 ? "" : "s"}`, { timestamp: true });
44
52
  if (layoutDefinitions.length > 0) {
45
53
  let layoutSyncResults;
@@ -47,10 +55,12 @@ async function syncDefinitionsToApi(options) {
47
55
  layoutSyncResults = await client.layouts.sync({
48
56
  projectSlug,
49
57
  syncSecret,
58
+ autoCreate,
50
59
  layouts: layoutDefinitions
51
60
  });
52
61
  } catch (error) {
53
62
  throwIfSyncAuthError(error);
63
+ if (!autoCreate && isNotFoundError(error)) throwUnknownEnvironmentError(environmentName);
54
64
  throw error;
55
65
  }
56
66
  logger.info(`[camox] Synced ${layoutDefinitions.length} layout${layoutDefinitions.length === 1 ? "" : "s"} to Camox API`, { timestamp: true });
@@ -119,7 +129,7 @@ async function ssrLoadModule(server, modulePath) {
119
129
  }
120
130
  }
121
131
  async function syncDefinitions(server, options) {
122
- const { projectSlug, syncSecret, apiUrl, environmentName } = options;
132
+ const { projectSlug, syncSecret, apiUrl, environmentName, autoCreate } = options;
123
133
  const blocksDir = path.resolve(server.config.root, "src/camox/blocks");
124
134
  const client = createServerApiClient(apiUrl, environmentName);
125
135
  async function performInitialSync() {
@@ -136,6 +146,7 @@ async function syncDefinitions(server, options) {
136
146
  apiUrl,
137
147
  syncSecret,
138
148
  environmentName,
149
+ autoCreate,
139
150
  logger: server.config.logger
140
151
  });
141
152
  }
@@ -15,7 +15,7 @@ const HEADER = `/* =============================================================
15
15
  // @ts-nocheck
16
16
 
17
17
  `;
18
- function generateCamoxLayout(authenticationUrl, apiUrl, projectSlug, environmentName) {
18
+ function generateCamoxLayout(authenticationUrl) {
19
19
  return HEADER + `import { Outlet, createFileRoute } from "@tanstack/react-router";
20
20
  import { CamoxProvider } from "camox/CamoxProvider";
21
21
  import { camoxApp } from "@/camox/app";
@@ -26,14 +26,14 @@ export const Route = createFileRoute("/_camox")({
26
26
 
27
27
  function CamoxPathlessLayout() {
28
28
  return (
29
- <CamoxProvider camoxApp={camoxApp} authenticationUrl="${authenticationUrl}" apiUrl="${apiUrl}" projectSlug="${projectSlug}" environmentName="${environmentName}">
29
+ <CamoxProvider camoxApp={camoxApp} authenticationUrl="${authenticationUrl}" apiUrl={__CAMOX_API_URL__} projectSlug={__CAMOX_PROJECT_SLUG__} environmentName={__CAMOX_ENVIRONMENT_NAME__}>
30
30
  <Outlet />
31
31
  </CamoxProvider>
32
32
  );
33
33
  }
34
34
  `;
35
35
  }
36
- function generatePageRoute(apiUrl, projectSlug, environmentName) {
36
+ function generatePageRoute() {
37
37
  return HEADER + `import { createFileRoute } from "@tanstack/react-router";
38
38
  import {
39
39
  createMarkdownMiddleware,
@@ -43,8 +43,8 @@ import {
43
43
  } from "camox/_internal/pageRoute";
44
44
  import { camoxApp } from "@/camox/app";
45
45
 
46
- const markdownMiddleware = createMarkdownMiddleware("${apiUrl}", "${projectSlug}", "${environmentName}");
47
- const loader = createPageLoader("${apiUrl}", "${projectSlug}", "${environmentName}");
46
+ const markdownMiddleware = createMarkdownMiddleware(__CAMOX_API_URL__, __CAMOX_PROJECT_SLUG__, __CAMOX_ENVIRONMENT_NAME__);
47
+ const loader = createPageLoader(__CAMOX_API_URL__, __CAMOX_PROJECT_SLUG__, __CAMOX_ENVIRONMENT_NAME__);
48
48
  const head = createPageHead(camoxApp);
49
49
 
50
50
  export const Route = createFileRoute("/_camox/$")({
@@ -134,16 +134,16 @@ function RouteComponent() {
134
134
  }
135
135
  `;
136
136
  }
137
- function getRouteFileEntries({ routesDir, authenticationUrl, apiUrl, projectSlug, environmentName }) {
137
+ function getRouteFileEntries({ routesDir, authenticationUrl }) {
138
138
  const camoxDir = resolve(routesDir, "_camox");
139
139
  return [
140
140
  {
141
141
  path: resolve(routesDir, "_camox.tsx"),
142
- content: generateCamoxLayout(authenticationUrl, apiUrl, projectSlug, environmentName)
142
+ content: generateCamoxLayout(authenticationUrl)
143
143
  },
144
144
  {
145
145
  path: resolve(camoxDir, "$.tsx"),
146
- content: generatePageRoute(apiUrl, projectSlug, environmentName)
146
+ content: generatePageRoute()
147
147
  },
148
148
  {
149
149
  path: resolve(camoxDir, "og.tsx"),
@@ -8,6 +8,7 @@ import { homedir } from "node:os";
8
8
  import { dirname, join, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { createServer } from "vite";
11
+ import { z } from "zod";
11
12
 
12
13
  //#region src/features/vite/vite.ts
13
14
  const sdkRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
@@ -18,18 +19,33 @@ const RESOLVED_VIRTUAL_OVERLAY_CSS = "\0" + VIRTUAL_OVERLAY_CSS;
18
19
  const PRODUCTION_API_URL = "https://api.camox.ai";
19
20
  /** Authentication URL to use for Camox authentication (production Camox web app) */
20
21
  const DEFAULT_AUTHENTICATION_URL = "https://camox.ai";
21
- function resolveEnvironmentName(isDev, authenticationUrl) {
22
- if (!isDev) return "production";
22
+ const authTokenSchema = z.object({
23
+ token: z.string(),
24
+ name: z.string(),
25
+ email: z.string()
26
+ });
27
+ const authFileSchema = z.record(z.string(), authTokenSchema);
28
+ function readAuthEmail(authenticationUrl) {
23
29
  const authFile = join(homedir(), ".camox", "auth.json");
24
30
  const key = authenticationUrl.replace(/\/+$/, "");
25
- let auth;
26
31
  try {
27
- auth = JSON.parse(readFileSync(authFile, "utf-8"))[key];
32
+ const raw = JSON.parse(readFileSync(authFile, "utf-8"));
33
+ return authFileSchema.parse(raw)[key]?.email ?? null;
28
34
  } catch {
29
- throw new Error(`Camox: not authenticated for ${key}. Run \`camox login\` before starting the dev server.\nAuthentication is required so your dev environment is scoped to your user.`);
35
+ return null;
30
36
  }
31
- if (!auth?.email) throw new Error(`Camox: no session found for ${key} in ~/.camox/auth.json. Run \`camox login\` again.`);
32
- return `${auth.email.split("@")[0]}-dev`;
37
+ }
38
+ function resolveEnvironmentName(command, authenticationUrl) {
39
+ if (command === "serve") {
40
+ const email = readAuthEmail(authenticationUrl);
41
+ if (!email) throw new Error("Camox: not authenticated. Run `npx camox login` to create your personal dev environment.");
42
+ return `dev:${email}`;
43
+ }
44
+ const envFromProcess = process.env.CAMOX_ENV;
45
+ if (envFromProcess) return envFromProcess;
46
+ const email = readAuthEmail(authenticationUrl);
47
+ const suggestion = email ? ` CAMOX_ENV=dev:${email} (your personal dev environment)\n CAMOX_ENV=production (release build)` : ` CAMOX_ENV=production (release build)\n\nIf you want to build against a dev environment, run \`npx camox login\` first.`;
48
+ throw new Error(`Camox: CAMOX_ENV is required on build. Set it to one of:\n${suggestion}`);
33
49
  }
34
50
  function camox(options) {
35
51
  const apiUrl = options._internal?.apiUrl ?? PRODUCTION_API_URL;
@@ -65,7 +81,7 @@ function camox(options) {
65
81
  },
66
82
  config(_config, env) {
67
83
  isBuild = env.command === "build";
68
- environmentName = resolveEnvironmentName(env.command === "serve", authenticationUrl);
84
+ environmentName = resolveEnvironmentName(env.command, authenticationUrl);
69
85
  return {
70
86
  define: {
71
87
  __CAMOX_ANALYTICS_DISABLED__: JSON.stringify(!!options.disableAnalytics),
@@ -74,51 +90,55 @@ function camox(options) {
74
90
  __CAMOX_API_URL__: JSON.stringify(apiUrl),
75
91
  __CAMOX_PROJECT_SLUG__: JSON.stringify(options.projectSlug)
76
92
  },
77
- optimizeDeps: {
78
- include: [
79
- "camox > @base-ui/react/accordion",
80
- "camox > @base-ui/react/alert-dialog",
81
- "camox > @base-ui/react/avatar",
82
- "camox > @base-ui/react/dialog",
83
- "camox > @base-ui/react/input",
84
- "camox > @base-ui/react/menu",
85
- "camox > @base-ui/react/merge-props",
86
- "camox > @base-ui/react/popover",
87
- "camox > @base-ui/react/select",
88
- "camox > @base-ui/react/separator",
89
- "camox > @base-ui/react/switch",
90
- "camox > @base-ui/react/tabs",
91
- "camox > @base-ui/react/toggle",
92
- "camox > @base-ui/react/tooltip",
93
- "camox > @base-ui/react/use-render",
94
- "camox > @dnd-kit/core",
95
- "camox > @dnd-kit/modifiers",
96
- "camox > @dnd-kit/sortable",
97
- "camox > @dnd-kit/utilities",
98
- "camox > @lexical/react/LexicalComposer",
99
- "camox > @lexical/react/LexicalComposerContext",
100
- "camox > @lexical/react/LexicalContentEditable",
101
- "camox > @lexical/react/LexicalOnChangePlugin",
102
- "camox > @lexical/react/LexicalRichTextPlugin",
103
- "camox > @orpc/client",
104
- "camox > @orpc/client/fetch",
105
- "camox > @orpc/tanstack-query",
106
- "camox > @sinclair/typebox",
107
- "camox > @takumi-rs/image-response",
108
- "camox > @tanstack/react-form",
109
- "camox > @xstate/store",
110
- "camox > @xstate/store/react",
111
- "camox > @camox/ui > cmdk",
112
- "camox > fractional-indexing",
113
- "camox > lexical",
114
- "camox > posthog-js",
115
- "camox > shiki",
116
- "camox > @camox/ui > sonner",
117
- "camox > @tanstack/react-query-devtools/production",
118
- "camox > partysocket/react"
119
- ],
120
- exclude: ["virtual:tanstack-start-client-entry"]
121
- }
93
+ optimizeDeps: { include: [
94
+ "react",
95
+ "react-dom",
96
+ "react-dom/client",
97
+ "react/jsx-runtime",
98
+ "react/jsx-dev-runtime",
99
+ "camox > @base-ui/react/accordion",
100
+ "camox > @base-ui/react/alert-dialog",
101
+ "camox > @base-ui/react/avatar",
102
+ "camox > @base-ui/react/dialog",
103
+ "camox > @base-ui/react/input",
104
+ "camox > @base-ui/react/menu",
105
+ "camox > @base-ui/react/merge-props",
106
+ "camox > @base-ui/react/popover",
107
+ "camox > @base-ui/react/select",
108
+ "camox > @base-ui/react/separator",
109
+ "camox > @base-ui/react/switch",
110
+ "camox > @base-ui/react/tabs",
111
+ "camox > @base-ui/react/toggle",
112
+ "camox > @base-ui/react/tooltip",
113
+ "camox > @base-ui/react/use-render",
114
+ "camox > @dnd-kit/core",
115
+ "camox > @dnd-kit/modifiers",
116
+ "camox > @dnd-kit/sortable",
117
+ "camox > @dnd-kit/utilities",
118
+ "camox > @lexical/react/LexicalComposer",
119
+ "camox > @lexical/react/LexicalComposerContext",
120
+ "camox > @lexical/react/LexicalContentEditable",
121
+ "camox > @lexical/react/LexicalOnChangePlugin",
122
+ "camox > @lexical/react/LexicalRichTextPlugin",
123
+ "camox > @orpc/client",
124
+ "camox > @orpc/client/fetch",
125
+ "camox > @orpc/tanstack-query",
126
+ "camox > @sinclair/typebox",
127
+ "camox > @takumi-rs/image-response",
128
+ "camox > @tanstack/react-form",
129
+ "camox > @xstate/store",
130
+ "camox > @xstate/store/react",
131
+ "camox > @camox/ui > cmdk",
132
+ "camox > fractional-indexing",
133
+ "camox > lexical",
134
+ "camox > posthog-js",
135
+ "camox > shiki",
136
+ "camox > @camox/ui > sonner",
137
+ "camox > @camox/ui > lucide-react",
138
+ "camox > lucide-react",
139
+ "camox > @tanstack/react-query-devtools/production",
140
+ "camox > partysocket/react"
141
+ ] }
122
142
  };
123
143
  },
124
144
  configResolved(config) {
@@ -128,10 +148,7 @@ function camox(options) {
128
148
  generateAppFile(config.root);
129
149
  generateRouteFiles({
130
150
  routesDir,
131
- authenticationUrl,
132
- apiUrl,
133
- projectSlug: options.projectSlug,
134
- environmentName
151
+ authenticationUrl
135
152
  });
136
153
  generateSkillFiles(config.root);
137
154
  }
@@ -146,10 +163,7 @@ function camox(options) {
146
163
  watchRouteFiles({
147
164
  server,
148
165
  routesDir,
149
- authenticationUrl,
150
- apiUrl,
151
- projectSlug: options.projectSlug,
152
- environmentName
166
+ authenticationUrl
153
167
  });
154
168
  watchSkillFiles(server, server.config.root);
155
169
  watchNewBlockFiles(server);
@@ -159,7 +173,8 @@ function camox(options) {
159
173
  projectSlug: options.projectSlug,
160
174
  syncSecret: options.syncSecret,
161
175
  apiUrl,
162
- environmentName
176
+ environmentName,
177
+ autoCreate: true
163
178
  });
164
179
  });
165
180
  },
@@ -182,6 +197,7 @@ function camox(options) {
182
197
  apiUrl,
183
198
  syncSecret: options.syncSecret,
184
199
  environmentName,
200
+ autoCreate: false,
185
201
  logger: resolvedConfig.logger
186
202
  });
187
203
  } finally {
@@ -118,8 +118,7 @@
118
118
  .camox-sheet-overlay {
119
119
  position: absolute;
120
120
  inset: 0;
121
- background: rgba(0, 0, 0, 0.1);
122
- backdrop-filter: blur(4px);
121
+ background: rgba(0, 0, 0, 0.66);
123
122
  opacity: 0;
124
123
  transition: opacity 0.3s ease-in-out;
125
124
  pointer-events: none;