camox 0.59.0 → 0.60.2

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.
Files changed (43) hide show
  1. package/dist/core/editing/createEditableBlock.js +1 -1
  2. package/dist/features/preview/components/SelectionEditor.js +1 -1
  3. package/dist/features/renderer/RendererApp.js +187 -103
  4. package/dist/features/runtime/loopbackFetch.d.ts +8 -0
  5. package/dist/features/runtime/publicCache.d.ts +21 -0
  6. package/dist/features/runtime/publishingDraft.d.ts +4 -0
  7. package/dist/features/runtime/runtime.js +54 -3
  8. package/dist/features/runtime/worker.js +1 -4
  9. package/dist/features/runtime/workerRequest.d.ts +8 -0
  10. package/dist/features/runtime/workerRuntime.d.ts +5 -0
  11. package/dist/features/runtime/workerRuntime.js +6 -0
  12. package/dist/features/sdkBundle/buildSdkBundle.d.ts +40 -0
  13. package/dist/features/sdkBundle/buildSdkBundle.js +323 -0
  14. package/dist/features/sdkBundle/localSdkBundle.d.ts +44 -0
  15. package/dist/features/sdkBundle/localSdkBundle.js +162 -0
  16. package/dist/features/sdkBundle/sdkBundle.d.ts +3 -0
  17. package/dist/features/sdkBundle/sdkBundle.js +4 -0
  18. package/dist/features/sdkBundle/sdkBundleName.d.ts +59 -0
  19. package/dist/features/sdkBundle/sdkBundleName.js +164 -0
  20. package/dist/features/sdkBundle/specifiers.d.ts +34 -0
  21. package/dist/features/sdkBundle/specifiers.js +146 -0
  22. package/dist/features/settings/SocialProfilesSettings.js +284 -151
  23. package/dist/features/studio/FollowAgentProvider.js +71 -12
  24. package/dist/features/studio/components/ChatWidget.js +85 -43
  25. package/dist/features/studio/components/Patrol.js +311 -208
  26. package/dist/features/studio/components/Squareling.js +164 -66
  27. package/dist/features/studio/components/ThreadComposer.js +139 -69
  28. package/dist/features/studio/components/ThreadMessages.js +115 -45
  29. package/dist/features/studio/editorWhereContext.js +11 -0
  30. package/dist/features/studio/useEditorSelection.js +135 -0
  31. package/dist/features/vite/hostedChecks.js +1 -1
  32. package/dist/features/vite/runtimeDev.js +2 -1
  33. package/dist/features/vite/sdkBundleBuild.js +74 -0
  34. package/dist/features/vite/staticAssetHeaders.js +7 -4
  35. package/dist/features/vite/vite.d.ts +0 -2
  36. package/dist/features/vite/vite.js +33 -3
  37. package/dist/lib/auth.js +24 -58
  38. package/dist/lib/server-auth-cookie.js +12 -2
  39. package/dist/lib/stored-auth-cookies.js +76 -0
  40. package/dist/lib/use-thread.js +9 -5
  41. package/dist/package.js +1 -1
  42. package/dist/studio.css +1 -1
  43. package/package.json +17 -4
@@ -14,9 +14,9 @@ import { MessageScroller, MessageScrollerButton, MessageScrollerContent, Message
14
14
  * A thread's messages, and the agent's squareling, which always lives in it. An empty thread has
15
15
  * it greet the editor; when the first message arrives, it moves to the end of the thread, and
16
16
  * stays there after each message. While a run is going or a message is still `sending` (on its
17
- * way to the thread, which hasn't accepted it yet), its face says whether the agent thinks or
18
- * acts (uses its tools, or builds its code). While the end of a turn builds the agent's code, or
19
- * the agent fixes a build error, it says so in words too. Each editor's message shows its
17
+ * way to the thread, which hasn't accepted it yet), its face and a line beside it say what the
18
+ * agent does, never how: thinking, looking around, making changes, building, fixing the build,
19
+ * or starting a draft. Each editor's message shows its
20
20
  * sender's avatar, without their name. A Live turn handed off to a draft ends on the draft's
21
21
  * card, which `onOpenDraft` opens.
22
22
  */
@@ -154,73 +154,143 @@ function useSquarelingMorph(empty) {
154
154
  return node;
155
155
  }
156
156
  /**
157
- * The squareling at the end of the thread: idle between turns, at work during one. Shown in the
158
- * last message, not after it: the scroller only brings a message the editor just sent into view
159
- * when it's appended last, and it arrives while the agent is already working.
157
+ * The squareling at the end of the thread: idle between turns, at work during one, saying what
158
+ * the agent does. Shown in the last message, not after it: the scroller only brings a message the
159
+ * editor just sent into view when it's appended last, and it arrives while the agent is already
160
+ * working.
160
161
  */
161
162
  function AgentPresence(t0) {
162
- const $ = c(13);
163
+ const $ = c(8);
163
164
  const { view: t1, working, squareling } = t0;
164
- const { status, usingTools } = t1;
165
- let t2;
166
- if ($[0] !== status || $[1] !== usingTools || $[2] !== working) {
167
- t2 = working ? squarelingStatus(status, usingTools) : "idle";
168
- $[0] = status;
169
- $[1] = usingTools;
170
- $[2] = working;
171
- $[3] = t2;
172
- } else t2 = $[3];
165
+ const { activity } = t1;
166
+ const shown = useSteadyActivity(working ? activity ?? "thinking" : void 0);
167
+ const t2 = shown ? SQUARELING_STATUSES[shown] : "idle";
173
168
  let t3;
174
- if ($[4] !== squareling || $[5] !== t2) {
169
+ if ($[0] !== squareling || $[1] !== t2) {
175
170
  t3 = /* @__PURE__ */ jsx(Squareling, {
176
171
  ref: squareling,
177
172
  status: t2,
178
173
  className: "size-6"
179
174
  });
180
- $[4] = squareling;
181
- $[5] = t2;
182
- $[6] = t3;
183
- } else t3 = $[6];
175
+ $[0] = squareling;
176
+ $[1] = t2;
177
+ $[2] = t3;
178
+ } else t3 = $[2];
184
179
  let t4;
185
- if ($[7] !== status || $[8] !== working) {
186
- t4 = working && (status ? /* @__PURE__ */ jsx("span", {
180
+ if ($[3] !== shown) {
181
+ t4 = shown && /* @__PURE__ */ jsx("span", {
187
182
  className: "shimmer",
188
- children: STATUS_LINES[status]
189
- }) : /* @__PURE__ */ jsx("span", {
190
- className: "sr-only",
191
- children: "Working…"
192
- }));
193
- $[7] = status;
194
- $[8] = working;
195
- $[9] = t4;
196
- } else t4 = $[9];
183
+ children: ACTIVITY_LINES[shown]
184
+ });
185
+ $[3] = shown;
186
+ $[4] = t4;
187
+ } else t4 = $[4];
197
188
  let t5;
198
- if ($[10] !== t3 || $[11] !== t4) {
189
+ if ($[5] !== t3 || $[6] !== t4) {
199
190
  t5 = /* @__PURE__ */ jsxs("div", {
200
191
  role: "status",
201
192
  className: "text-muted-foreground flex items-center gap-2 text-sm",
202
193
  children: [t3, t4]
203
194
  });
204
- $[10] = t3;
205
- $[11] = t4;
206
- $[12] = t5;
207
- } else t5 = $[12];
195
+ $[5] = t3;
196
+ $[6] = t4;
197
+ $[7] = t5;
198
+ } else t5 = $[7];
208
199
  return t5;
209
200
  }
210
- function squarelingStatus(status, usingTools) {
211
- if (status === "fixing") return "fixing";
212
- return status === "building" || usingTools ? "acting" : "thinking";
201
+ /**
202
+ * How long a line saying what the agent does stays at least, in ms, so editors have time to read
203
+ * it: a round of tool calls can take far less.
204
+ */
205
+ const ACTIVITY_MIN_DURATION = 2e3;
206
+ /**
207
+ * How long the agent must keep doing something before a line says so, in ms: thinking briefly
208
+ * between two rounds of tool calls never shows.
209
+ */
210
+ const ACTIVITY_SETTLE_DURATION = 800;
211
+ /**
212
+ * `activity` as a line shows it, steadied: a new activity shows once it lasted
213
+ * `ACTIVITY_SETTLE_DURATION` and the line before held for `ACTIVITY_MIN_DURATION`, so a brief
214
+ * one never shows. Only the line lags: a turn that starts or ends shows at once.
215
+ */
216
+ function useSteadyActivity(activity) {
217
+ const $ = c(10);
218
+ let t0;
219
+ if ($[0] !== activity) {
220
+ t0 = { activity };
221
+ $[0] = activity;
222
+ $[1] = t0;
223
+ } else t0 = $[1];
224
+ const [latest, setLatest] = React.useState(t0);
225
+ const [lasted, setLasted] = React.useState();
226
+ let t1;
227
+ if ($[2] !== activity) {
228
+ t1 = { activity };
229
+ $[2] = activity;
230
+ $[3] = t1;
231
+ } else t1 = $[3];
232
+ const [held, setHeld] = React.useState(t1);
233
+ const [settled, setSettled] = React.useState();
234
+ let t2;
235
+ let t3;
236
+ if ($[4] !== latest) {
237
+ t2 = () => {
238
+ const timer = setTimeout(() => setLasted(latest), 800);
239
+ return () => clearTimeout(timer);
240
+ };
241
+ t3 = [latest];
242
+ $[4] = latest;
243
+ $[5] = t2;
244
+ $[6] = t3;
245
+ } else {
246
+ t2 = $[5];
247
+ t3 = $[6];
248
+ }
249
+ React.useEffect(t2, t3);
250
+ let t4;
251
+ let t5;
252
+ if ($[7] !== held) {
253
+ t4 = () => {
254
+ const timer_0 = setTimeout(() => setSettled(held), ACTIVITY_MIN_DURATION);
255
+ return () => clearTimeout(timer_0);
256
+ };
257
+ t5 = [held];
258
+ $[7] = held;
259
+ $[8] = t4;
260
+ $[9] = t5;
261
+ } else {
262
+ t4 = $[8];
263
+ t5 = $[9];
264
+ }
265
+ React.useEffect(t4, t5);
266
+ if (activity !== latest.activity) setLatest({ activity });
267
+ else if (activity !== held.activity) {
268
+ if (!activity || !held.activity || lasted === latest && settled === held) setHeld({ activity });
269
+ }
270
+ return activity && (held.activity ?? activity);
213
271
  }
272
+ const SQUARELING_STATUSES = {
273
+ thinking: "thinking",
274
+ exploring: "exploring",
275
+ changing: "changing",
276
+ building: "building",
277
+ fixing: "fixing",
278
+ startingDraft: "changing"
279
+ };
280
+ const ACTIVITY_LINES = {
281
+ thinking: "Thinking…",
282
+ exploring: "Looking around…",
283
+ changing: "Making changes…",
284
+ building: "Building…",
285
+ fixing: "Fixing the build…",
286
+ startingDraft: "Starting a draft…"
287
+ };
214
288
  /** How a member's pushed code built, as the thread says it. */
215
289
  const PUSH_OUTCOMES = {
216
290
  built: "Built, and in the preview.",
217
291
  typeErrors: "Built, with type errors that block publishing.",
218
292
  notPreviewed: "Built, but the preview can’t show it."
219
293
  };
220
- const STATUS_LINES = {
221
- building: "Building…",
222
- fixing: "Fixing a build error…"
223
- };
224
294
  function ThreadMessageRow(t0) {
225
295
  const $ = c(36);
226
296
  const { message, onOpenDraft } = t0;
@@ -506,4 +576,4 @@ function SenderAvatar(t0) {
506
576
  }
507
577
 
508
578
  //#endregion
509
- export { ThreadMessages };
579
+ export { ACTIVITY_MIN_DURATION, ThreadMessages };
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/features/studio/editorWhereContext.ts
4
+ /**
5
+ * Where the editor is in the Website Section: the Page they look at, or the Canvas with no page
6
+ * selected. Undefined elsewhere in the Studio. Their messages to the agent carry it as a hint.
7
+ */
8
+ const EditorWhereContext = React.createContext(void 0);
9
+
10
+ //#endregion
11
+ export { EditorWhereContext };
@@ -0,0 +1,135 @@
1
+ import { placedRecords } from "../../lib/normalized-data.js";
2
+ import { reads, useReadQuery } from "../../lib/studio-reads.js";
3
+ import { useBlockDefinition, useCollectionDefinition } from "../../lib/studio-definitions.js";
4
+ import { isBlockPlacement, previewStore, selectionBlockId, selectionForOwner, selectionItemId } from "../preview/previewStore.js";
5
+ import { entityName } from "../preview/entityName.js";
6
+ import { contentFieldSchema } from "../preview/components/contentFieldSchema.js";
7
+ import { formatFieldName } from "../preview/components/ItemFieldsEditor.js";
8
+ import { getSchemaForItem } from "../preview/components/SelectionEditor.js";
9
+ import { c } from "react/compiler-runtime";
10
+ import { useSelector } from "@xstate/store-react";
11
+
12
+ //#region src/features/studio/useEditorSelection.ts
13
+ /** The owner the page the editor looks at edits, as the editing frame names it. */
14
+ function ownerOf(where) {
15
+ if (where?.on !== "page") return null;
16
+ if (where.layoutId != null) return {
17
+ kind: "layout",
18
+ layoutId: where.layoutId
19
+ };
20
+ if (where.pageId != null) return {
21
+ kind: "page",
22
+ pageId: where.pageId
23
+ };
24
+ return null;
25
+ }
26
+ /** The selection as the agent reads it, from the parts the studio loaded. */
27
+ function editorSelectionOf({ selection, block, items, schema, records, recordSchema }) {
28
+ const fieldLabel = (fieldSchema, fieldName) => contentFieldSchema(fieldSchema, fieldName)?.title ?? formatFieldName(fieldName);
29
+ const path = [];
30
+ const blockId = selectionBlockId(selection) ?? void 0;
31
+ if (blockId) path.push(block?.title ?? "Block");
32
+ for (const item of items) path.push(entityName(item, "Item"));
33
+ const recordSelected = selection.type === "record" || selection.type === "record-field";
34
+ if (recordSelected) path.push(...records.length > 0 ? records.map((record) => record.title || "Record") : ["Record"]);
35
+ let fieldName;
36
+ if (selection.type === "block-field" || selection.type === "item-field") {
37
+ fieldName = selection.fieldName;
38
+ path.push(fieldLabel(schema, fieldName));
39
+ } else if (selection.type === "record-field") {
40
+ fieldName = selection.recordFieldName;
41
+ path.push(fieldLabel(recordSchema, fieldName));
42
+ }
43
+ const itemId = selectionItemId(selection) ?? void 0;
44
+ const recordId = recordSelected ? selection.nested?.recordId ?? selection.recordId : void 0;
45
+ return {
46
+ path,
47
+ ...blockId ? { blockId } : {},
48
+ ...itemId ? { itemId } : {},
49
+ ...recordId ? { recordId } : {},
50
+ ...fieldName ? { fieldName } : {}
51
+ };
52
+ }
53
+ /**
54
+ * What the editor has selected on the page they look at, as the agent reads it, or undefined
55
+ * with nothing selected there.
56
+ */
57
+ function useEditorSelection(where) {
58
+ const $ = c(12);
59
+ let t0;
60
+ if ($[0] !== where) {
61
+ t0 = ownerOf(where);
62
+ $[0] = where;
63
+ $[1] = t0;
64
+ } else t0 = $[1];
65
+ const owner = t0;
66
+ let t1;
67
+ if ($[2] !== owner) {
68
+ t1 = (state) => selectionForOwner(state.context, owner);
69
+ $[2] = owner;
70
+ $[3] = t1;
71
+ } else t1 = $[3];
72
+ const selection = useSelector(previewStore, t1);
73
+ const blockId = selectionBlockId(selection);
74
+ const itemId = selectionItemId(selection);
75
+ const t2 = blockId != null;
76
+ let t3;
77
+ if ($[4] !== t2) {
78
+ t3 = { enabled: t2 };
79
+ $[4] = t2;
80
+ $[5] = t3;
81
+ } else t3 = $[5];
82
+ const { data: bundle } = useReadQuery(reads.blockBundle(blockId), t3);
83
+ const blockDef = useBlockDefinition(bundle?.block.type);
84
+ bundle?.repeatableItems;
85
+ let t4;
86
+ if ($[6] !== bundle?.repeatableItems) {
87
+ t4 = bundle?.repeatableItems ?? [];
88
+ $[6] = bundle?.repeatableItems;
89
+ $[7] = t4;
90
+ } else t4 = $[7];
91
+ let t5;
92
+ if ($[8] !== t4) {
93
+ t5 = new Map(t4.map(_temp));
94
+ $[8] = t4;
95
+ $[9] = t5;
96
+ } else t5 = $[9];
97
+ const itemsMap = t5;
98
+ const items = [];
99
+ for (let item_0 = itemId ? itemsMap.get(itemId) : void 0; item_0;) {
100
+ items.unshift(item_0);
101
+ item_0 = item_0.parentItemId ? itemsMap.get(item_0.parentItemId) : void 0;
102
+ }
103
+ const schema = blockDef && itemId ? getSchemaForItem(blockDef.contentSchema, itemId, itemsMap) : blockDef?.contentSchema;
104
+ const records = [];
105
+ if ((selection?.type === "record" || selection?.type === "record-field") && isBlockPlacement(selection)) {
106
+ const placer = itemId ? items.at(-1) : bundle?.block;
107
+ let t6;
108
+ if ($[10] !== selection.recordId) {
109
+ t6 = (record) => record.id === selection.recordId;
110
+ $[10] = selection.recordId;
111
+ $[11] = t6;
112
+ } else t6 = $[11];
113
+ const outer = placedRecords(placer?.references, selection.fieldName).find(t6);
114
+ if (outer) records.push(outer);
115
+ const { nested } = selection;
116
+ const linked = nested && placedRecords(outer?.references, nested.fieldName).find((record_0) => record_0.id === nested.recordId);
117
+ if (linked) records.push(linked);
118
+ }
119
+ const recordSchema = useCollectionDefinition(records.at(-1)?.collectionId)?.contentSchema;
120
+ if (!selection) return;
121
+ return editorSelectionOf({
122
+ selection,
123
+ block: blockDef,
124
+ items,
125
+ schema,
126
+ records,
127
+ recordSchema
128
+ });
129
+ }
130
+ function _temp(item) {
131
+ return [item.id, item];
132
+ }
133
+
134
+ //#endregion
135
+ export { editorSelectionOf, useEditorSelection };
@@ -6,7 +6,7 @@ import { Visitor, parseSync } from "vite-plus";
6
6
  //#region src/features/vite/hostedChecks.ts
7
7
  /**
8
8
  * Set by Camox's revision and release sandboxes: a hosted project's build checks the code an
9
- * agent may have written (ADR 0021). `apps/landing` and dev mode never set it.
9
+ * agent may have written (ADR 0021). Dev mode never sets it.
10
10
  */
11
11
  const HOSTED_BUILD_ENV = "CAMOX_INTERNAL_HOSTED";
12
12
  const sdkRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
@@ -73,7 +73,8 @@ function resolveCamoxSourceImport(id) {
73
73
  "camox/_internal/rendererClient": resolve(sdkRoot, "src/features/runtime/rendererClient.tsx"),
74
74
  "camox/_internal/studioServer": resolve(sdkRoot, "src/features/runtime/studioServer.tsx"),
75
75
  "camox/_internal/runtime": resolve(sdkRoot, "src/features/runtime/runtime.ts"),
76
- "camox/worker": resolve(sdkRoot, "src/features/runtime/worker.ts")
76
+ "camox/worker": resolve(sdkRoot, "src/features/runtime/worker.ts"),
77
+ "camox/_internal/workerRuntime": resolve(sdkRoot, "src/features/runtime/workerRuntime.ts")
77
78
  }[id];
78
79
  if (!sourceImport || !existsSync(sourceImport)) return;
79
80
  return sourceImport;
@@ -0,0 +1,74 @@
1
+ import { sdkBundleEntryFile } from "../sdkBundle/specifiers.js";
2
+ import { SDK_BUNDLE_BUILD_MANIFEST, sdkBundlePath } from "../sdkBundle/sdkBundleName.js";
3
+ import { SDK_BUNDLE_MANIFEST } from "../sdkBundle/buildSdkBundle.js";
4
+ import { buildsAgainstSdkBundle, ensureLocalSdkBundle } from "../sdkBundle/localSdkBundle.js";
5
+ import { createRequire } from "node:module";
6
+ import { readFileSync, readdirSync } from "node:fs";
7
+ import { dirname, join, posix, resolve } from "node:path";
8
+
9
+ //#region src/features/vite/sdkBundleBuild.ts
10
+ /**
11
+ * The SDK Bundle a build builds against, from the local cache, or undefined when the switch is
12
+ * off and the build is whole-app.
13
+ */
14
+ async function sdkBundleBuildState({ root, settings, mayBuild }) {
15
+ if (!buildsAgainstSdkBundle()) return;
16
+ let built = false;
17
+ const bundle = await ensureLocalSdkBundle({
18
+ root,
19
+ settings,
20
+ mayBuild,
21
+ onBuild: () => {
22
+ built = true;
23
+ }
24
+ });
25
+ const names = {
26
+ browser: bundle.browser.name,
27
+ workerd: bundle.workerd.name
28
+ };
29
+ const stylesheet = readdirSync(bundle.browser.dir).find((file) => /^camox-studio-.*\.css$/.test(file));
30
+ if (!stylesheet) throw new Error(`Camox: the SDK Bundle ${names.browser} has no Studio stylesheet.`);
31
+ const websiteRequire = createRequire(join(root, "package.json"));
32
+ /** What each target holds: its entry files by specifier, and by the file a path specifier names. */
33
+ const held = (dir) => {
34
+ const { entries } = JSON.parse(readFileSync(join(dir, SDK_BUNDLE_MANIFEST), "utf8"));
35
+ const byFile = /* @__PURE__ */ new Map();
36
+ for (const specifier of Object.keys(entries)) if (/\.[cm]?js$/.test(specifier)) byFile.set(websiteRequire.resolve(specifier), sdkBundleEntryFile(specifier));
37
+ return {
38
+ entries,
39
+ byFile
40
+ };
41
+ };
42
+ const holds = {
43
+ browser: held(bundle.browser.dir),
44
+ workerd: held(bundle.workerd.dir)
45
+ };
46
+ return {
47
+ names,
48
+ built,
49
+ studioStylesheetUrl: sdkBundlePath(names.browser) + stylesheet,
50
+ manifest: {
51
+ fileName: SDK_BUNDLE_BUILD_MANIFEST,
52
+ source: `${JSON.stringify(names)}\n`
53
+ },
54
+ resolve(id, importer, consumer) {
55
+ const target = consumer === "client" ? "browser" : "workerd";
56
+ const { entries, byFile } = holds[target];
57
+ const file = Object.hasOwn(entries, id) ? entries[id] : id.startsWith(".") && importer ? byFile.get(resolve(dirname(importer), id)) : void 0;
58
+ if (!file) return;
59
+ return {
60
+ id: sdkBundlePath(names[target]) + file,
61
+ external: "absolute"
62
+ };
63
+ },
64
+ relativizeWorkerImports(code, fileName) {
65
+ const prefix = sdkBundlePath(names.workerd);
66
+ if (!code.includes(prefix)) return;
67
+ const toRoot = posix.relative(posix.dirname(fileName), "") || ".";
68
+ return code.replaceAll(new RegExp(`(["'\`])${prefix.replaceAll("/", "\\/")}`, "g"), `$1${toRoot}${prefix}`);
69
+ }
70
+ };
71
+ }
72
+
73
+ //#endregion
74
+ export { sdkBundleBuildState };
@@ -5,17 +5,20 @@ import { join, relative } from "node:path";
5
5
  /** Where the client build puts the files it names by their content's hash. */
6
6
  const HASHED_DIRS = ["assets", "_chunks"];
7
7
  const IMMUTABLE = "public, max-age=31536000, immutable";
8
+ /** Where a build against an SDK Bundle serves its files, under the bundle's name (ADR 0029). */
9
+ const SDK_BUNDLE_DIR = "_camox/sdk";
8
10
  /** Workers Static Assets reads at most this many rules. */
9
11
  const MAX_RULES = 100;
10
12
  /**
11
13
  * The `_headers` file Workers Static Assets serves the client build with. Files under
12
14
  * `/assets/` and `/_chunks/` are named by their content's hash, so browsers keep them for good;
13
15
  * the files there named otherwise, the entry chunks pages load by a fixed name and whatever the
14
- * app's public directory puts there, keep the default, which revalidates. The app's own
15
- * `_headers` comes first, as it wrote it.
16
+ * app's public directory puts there, keep the default, which revalidates. A build against an
17
+ * SDK Bundle has its files under `/_camox/sdk/<name>/`, which never change under a name either.
18
+ * The app's own `_headers` comes first, as it wrote it.
16
19
  */
17
- function staticAssetHeaders({ ownHeaders, unhashedFiles }) {
18
- const rules = [...HASHED_DIRS.map((dir) => `/${dir}/*\n Cache-Control: ${IMMUTABLE}`), ...[...unhashedFiles].sort().map((file) => `/${file}\n ! Cache-Control`)];
20
+ function staticAssetHeaders({ ownHeaders, unhashedFiles, sdkBundle = false }) {
21
+ const rules = [...(sdkBundle ? [...HASHED_DIRS, SDK_BUNDLE_DIR] : HASHED_DIRS).map((dir) => `/${dir}/*\n Cache-Control: ${IMMUTABLE}`), ...[...unhashedFiles].sort().map((file) => `/${file}\n ! Cache-Control`)];
19
22
  const own = ownHeaders?.trimEnd();
20
23
  const ownRules = own ? own.split("\n").filter((line) => /^\S/.test(line) && !line.startsWith("#")) : [];
21
24
  if (ownRules.length + rules.length > MAX_RULES) throw new Error(`Camox: _headers would hold ${ownRules.length + rules.length} rules, past Workers Static Assets' ${MAX_RULES}. Move the public directory's files out of /assets/ and /_chunks/, or drop rules from public/_headers.`);
@@ -29,8 +29,6 @@ export interface CamoxPluginOptions {
29
29
  apiUrl?: string;
30
30
  /** URL of the Camox authentication backend (default: https://app.camox.ai) */
31
31
  authenticationUrl?: string;
32
- /** Show Tanstack query devtools (default: false) */
33
- enableTanstackDevtools?: boolean;
34
32
  /** Enable experimental features (default: false). */
35
33
  enableExperimentalFeatures?: boolean;
36
34
  /** Disable automatic code generation (route and layout files) (default: false) */
@@ -10,6 +10,7 @@ import { installDevSignInLink } from "./devSignIn.js";
10
10
  import { HOSTED_BUILD_ENV, forbiddenImport, misnamedBlock, readHostedProject } from "./hostedChecks.js";
11
11
  import { generateIconTypes } from "./iconGeneration.js";
12
12
  import { generateLayoutFiles, watchLayoutFiles } from "./layoutGeneration.js";
13
+ import { sdkBundleBuildState } from "./sdkBundleBuild.js";
13
14
  import { publicFilesInHashedDirs, readOwnHeaders, staticAssetHeaders, unhashedEntries } from "./staticAssetHeaders.js";
14
15
  import { buildClientThenWorker } from "./workerBuild.js";
15
16
  import { VIRTUAL_CAMOX_APP, syncDefinitions, syncDefinitionsToApi } from "./definitionsSync.js";
@@ -185,7 +186,6 @@ function camox(options) {
185
186
  const projectUuid = projectIdOf(options);
186
187
  const apiUrl = options._internal?.apiUrl ?? PRODUCTION_API_URL;
187
188
  const authenticationUrl = options._internal?.authenticationUrl ?? DEFAULT_AUTHENTICATION_URL;
188
- const enableTanstackDevtools = options._internal?.enableTanstackDevtools ?? false;
189
189
  const disableCodeGen = options._internal?.disableCodeGen ?? false;
190
190
  const runtimeBasePath = options._internal?.runtimeBasePath;
191
191
  let isBuild = false;
@@ -200,6 +200,8 @@ function camox(options) {
200
200
  let localDeployToken;
201
201
  let blockChunks = {};
202
202
  let devServer;
203
+ /** Set when the build builds against an SDK Bundle. */
204
+ let sdkBundle;
203
205
  return {
204
206
  name: "camox",
205
207
  enforce: "pre",
@@ -209,6 +211,10 @@ function camox(options) {
209
211
  const forbidden = forbiddenImport(hostedProject, id, importer);
210
212
  if (forbidden) this.error(forbidden);
211
213
  }
214
+ if (sdkBundle) {
215
+ const external = sdkBundle.resolve(id, importer, this.environment.config.consumer);
216
+ if (external) return external;
217
+ }
212
218
  const runtimeId = resolveRuntimeDevId(id, importer);
213
219
  if (runtimeId) return runtimeId;
214
220
  if (id === VIRTUAL_STUDIO_CSS) return RESOLVED_VIRTUAL_STUDIO_CSS;
@@ -216,6 +222,7 @@ function camox(options) {
216
222
  },
217
223
  async load(id, loadOptions) {
218
224
  const stylesDir = resolve(sdkRoot, !isBuild && isSdkSource ? ".dev" : "dist");
225
+ if (id === RESOLVED_VIRTUAL_STUDIO_CSS && sdkBundle) return `export default ${JSON.stringify(sdkBundle.studioStylesheetUrl)};`;
219
226
  if (id === RESOLVED_VIRTUAL_STUDIO_CSS) {
220
227
  const cssPath = resolve(stylesDir, "studio.css");
221
228
  if (isBuild) {
@@ -257,7 +264,19 @@ function camox(options) {
257
264
  }
258
265
  return transformArtifactRuntime(code, id);
259
266
  },
267
+ renderChunk(code, chunk) {
268
+ if (!sdkBundle || this.environment.config.consumer !== "server") return;
269
+ const relative = sdkBundle.relativizeWorkerImports(code, chunk.fileName);
270
+ return relative === void 0 ? void 0 : {
271
+ code: relative,
272
+ map: null
273
+ };
274
+ },
260
275
  generateBundle(_outputOptions, bundle) {
276
+ if (sdkBundle && this.environment.config.consumer === "server") this.emitFile({
277
+ type: "asset",
278
+ ...sdkBundle.manifest
279
+ });
261
280
  if (this.environment.name !== "client") return;
262
281
  blockChunks = collectBlockChunks(bundle, resolvedConfig.root);
263
282
  if (!isBuild) return;
@@ -267,7 +286,8 @@ function camox(options) {
267
286
  fileName: "_headers",
268
287
  source: staticAssetHeaders({
269
288
  ownHeaders: readOwnHeaders(publicDir),
270
- unhashedFiles: [...unhashedEntries(bundle), ...publicFilesInHashedDirs(publicDir)]
289
+ unhashedFiles: [...unhashedEntries(bundle), ...publicFilesInHashedDirs(publicDir)],
290
+ sdkBundle: sdkBundle !== void 0
271
291
  })
272
292
  });
273
293
  },
@@ -279,6 +299,16 @@ function camox(options) {
279
299
  isDryRun = isRelease && release === "dry-run";
280
300
  if (env.command === "serve" && !env.isPreview) localDeployToken = localDeployTokenOf(apiUrl, options);
281
301
  if (!isBuild) localAuth = readAuthTokenForUrl(authenticationUrl);
302
+ if (isBuild) sdkBundle = await sdkBundleBuildState({
303
+ root: resolve(_config.root ?? process.cwd()),
304
+ settings: {
305
+ iconIds,
306
+ telemetryDisabled: !!options.disableTelemetry,
307
+ experimentalFeatures: options._internal?.enableExperimentalFeatures ?? false,
308
+ apiUrl
309
+ },
310
+ mayBuild: !(isRelease && process.env["CAMOX_INTERNAL_HOSTED"] === "1")
311
+ });
282
312
  return {
283
313
  ...isBuild && {
284
314
  base: "./",
@@ -296,7 +326,6 @@ function camox(options) {
296
326
  define: {
297
327
  __CAMOX_ICON_IDS__: JSON.stringify(iconIds),
298
328
  __CAMOX_TELEMETRY_DISABLED__: JSON.stringify(!!options.disableTelemetry),
299
- __ENABLE_TANSTACK_DEVTOOLS__: JSON.stringify(enableTanstackDevtools),
300
329
  __CAMOX_ENABLE_EXPERIMENTAL_FEATURES__: JSON.stringify(options._internal?.enableExperimentalFeatures ?? false),
301
330
  __CAMOX_API_URL__: JSON.stringify(apiUrl),
302
331
  __CAMOX_AUTHENTICATION_URL__: JSON.stringify(authenticationUrl),
@@ -361,6 +390,7 @@ function camox(options) {
361
390
  else if (localDeployToken) target = "local Live";
362
391
  else if (!isBuild && !localAuth) target = "authentication required";
363
392
  config.logger.info(`${mode} Camox app (${target})`, { timestamp: true });
393
+ if (sdkBundle?.built) config.logger.info(`Built the SDK Bundle ${sdkBundle.names.browser} (browser) and ${sdkBundle.names.workerd} (workerd) into its cache`, { timestamp: true });
364
394
  },
365
395
  configureServer(server) {
366
396
  devServer = server;