@jxsuite/server 0.28.4 → 0.29.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/server",
3
- "version": "0.28.4",
3
+ "version": "0.29.0",
4
4
  "description": "Jx development server with live reload, server-side proxy, and studio integration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,13 +27,13 @@
27
27
  "test:coverage": "bun test --isolate --coverage"
28
28
  },
29
29
  "dependencies": {
30
- "@anthropic-ai/claude-agent-sdk": "^0.3.173",
30
+ "@anthropic-ai/claude-agent-sdk": "^0.3.175",
31
31
  "@anthropic-ai/sdk": "^0.104.1",
32
- "@jxsuite/compiler": "^0.28.4",
33
- "@jxsuite/create": "^0.28.4",
34
- "@jxsuite/parser": "^0.28.4",
35
- "@jxsuite/runtime": "^0.28.4",
36
- "@jxsuite/schema": "^0.28.4",
32
+ "@jxsuite/compiler": "^0.29.0",
33
+ "@jxsuite/create": "^0.29.0",
34
+ "@jxsuite/parser": "^0.29.0",
35
+ "@jxsuite/runtime": "^0.29.0",
36
+ "@jxsuite/schema": "^0.29.0",
37
37
  "@modelcontextprotocol/sdk": "^1.29.0",
38
38
  "chokidar": "^5.0.0",
39
39
  "zod": "^4.4.3"
@@ -136,7 +136,7 @@ export function createSession(
136
136
 
137
137
  const stream = query({ options: queryOpts, prompt: message });
138
138
  session.query = stream;
139
- processStream(session, stream);
139
+ void processStream(session, stream);
140
140
 
141
141
  return { id };
142
142
  }
@@ -177,7 +177,7 @@ export function sendMessage(id: string, message: string) {
177
177
 
178
178
  const stream = query({ options: queryOpts, prompt: message });
179
179
  session.query = stream;
180
- processStream(session, stream);
180
+ void processStream(session, stream);
181
181
  }
182
182
 
183
183
  /**
package/src/code-api.ts CHANGED
@@ -21,6 +21,11 @@ interface OxcDiagnostic {
21
21
  [key: string]: unknown;
22
22
  }
23
23
 
24
+ interface CodeApiBody {
25
+ code?: string;
26
+ args?: string[];
27
+ }
28
+
24
29
  const OXLINT_BIN = resolve(
25
30
  import.meta.dir,
26
31
  "../../node_modules/.bin",
@@ -43,10 +48,10 @@ function unwrapFormatted(formatted: string) {
43
48
  const lines = formatted.split("\n");
44
49
  // Remove first line (function header) and last non-empty line (closing brace)
45
50
  let end = lines.length - 1;
46
- while (end > 0 && lines[end].trim() === "") {
51
+ while (end > 0 && lines[end]!.trim() === "") {
47
52
  end -= 1;
48
53
  }
49
- if (lines[end].trim() === "}") {
54
+ if (lines[end]!.trim() === "}") {
50
55
  end -= 1;
51
56
  }
52
57
  const bodyLines = lines.slice(1, end + 1);
@@ -90,9 +95,9 @@ export async function handleCodeApi(req: Request, url: URL) {
90
95
  return null;
91
96
  }
92
97
 
93
- let body;
98
+ let body: CodeApiBody;
94
99
  try {
95
- body = await req.json();
100
+ body = (await req.json()) as CodeApiBody;
96
101
  } catch {
97
102
  return new Response("Invalid JSON", { status: 400 });
98
103
  }
@@ -162,8 +167,8 @@ export async function handleCodeApi(req: Request, url: URL) {
162
167
  const output = await new Response(proc.stdout).text();
163
168
  await proc.exited;
164
169
 
165
- const parsed = JSON.parse(output);
166
- const adjusted = adjustDiagnostics(parsed.diagnostics || [], headerLen);
170
+ const parsed = JSON.parse(output) as { diagnostics?: OxcDiagnostic[] };
171
+ const adjusted = adjustDiagnostics(parsed.diagnostics ?? [], headerLen);
167
172
  return Response.json({ diagnostics: adjusted });
168
173
  } catch (error) {
169
174
  return Response.json({
package/src/resolve.ts CHANGED
@@ -6,7 +6,34 @@ import { existsSync, readFileSync } from "node:fs";
6
6
  import { createRequire } from "node:module";
7
7
  import { loadContentTypes } from "@jxsuite/compiler/content-loader";
8
8
  import type { DynamicClass } from "@jxsuite/runtime/types";
9
- import type { JxClassDef } from "@jxsuite/schema/types";
9
+ import type { JxClassDef, ProjectConfig } from "@jxsuite/schema/types";
10
+
11
+ interface ModuleNamespace {
12
+ default?: Record<string, unknown>;
13
+ [key: string]: unknown;
14
+ }
15
+
16
+ interface ClassInstance {
17
+ resolve?: () => unknown;
18
+ value?: unknown;
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ interface ResolveBody {
23
+ $src?: string;
24
+ $prototype?: string;
25
+ $export?: string;
26
+ $base?: string;
27
+ [key: string]: unknown;
28
+ }
29
+
30
+ interface ServerFunctionBody {
31
+ $src?: string;
32
+ $export?: string;
33
+ $base?: string;
34
+ arguments?: Record<string, unknown>;
35
+ [key: string]: unknown;
36
+ }
10
37
 
11
38
  /**
12
39
  * Lazy-load project context (project.json + content types) for class instantiation.
@@ -24,7 +51,7 @@ async function loadProjectContext(projectRoot: string) {
24
51
  return null;
25
52
  }
26
53
  try {
27
- const config = JSON.parse(readFileSync(projectJsonPath, "utf8"));
54
+ const config = JSON.parse(readFileSync(projectJsonPath, "utf8")) as ProjectConfig;
28
55
  const contentTypes = config.contentTypes
29
56
  ? await loadContentTypes(projectRoot, config)
30
57
  : new Map();
@@ -46,9 +73,9 @@ export async function handleResolve(
46
73
  root: string,
47
74
  activeProjectRoot: string | null = null,
48
75
  ) {
49
- let body;
76
+ let body: ResolveBody;
50
77
  try {
51
- body = await req.json();
78
+ body = (await req.json()) as ResolveBody;
52
79
  } catch {
53
80
  return new Response("Invalid JSON body", { status: 400 });
54
81
  }
@@ -114,14 +141,16 @@ export async function handleResolve(
114
141
  // Hybrid mode: redirect to the JS implementation
115
142
  const implPath = resolve(dirname(moduleAbsPath), classDef.$implementation);
116
143
  const exportName = xport ?? classDef.title ?? $prototype;
117
- const mod = await import(implPath);
118
- const ExportedClass = mod[exportName] ?? mod.default?.[exportName];
144
+ const mod = (await import(implPath)) as ModuleNamespace;
145
+ const ExportedClass =
146
+ exportName === undefined ? undefined : (mod[exportName] ?? mod.default?.[exportName]);
119
147
  if (typeof ExportedClass !== "function") {
120
148
  return new Response(`Export "${exportName}" not found in "${classDef.$implementation}"`, {
121
149
  status: 500,
122
150
  });
123
151
  }
124
- const instance = new ExportedClass(config);
152
+ const ClassCtor = ExportedClass as new (config: unknown) => ClassInstance;
153
+ const instance = new ClassCtor(config);
125
154
  const value =
126
155
  typeof instance.resolve === "function"
127
156
  ? await instance.resolve()
@@ -165,9 +194,9 @@ export async function handleResolve(
165
194
  * @param {string} root
166
195
  */
167
196
  export async function handleServerFunction(req: Request, root: string) {
168
- let body;
197
+ let body: ServerFunctionBody;
169
198
  try {
170
- body = await req.json();
199
+ body = (await req.json()) as ServerFunctionBody;
171
200
  } catch {
172
201
  return new Response("Invalid JSON body", { status: 400 });
173
202
  }
@@ -192,9 +221,9 @@ export async function handleServerFunction(req: Request, root: string) {
192
221
  });
193
222
  }
194
223
 
195
- let mod;
224
+ let mod: ModuleNamespace;
196
225
  try {
197
- mod = await import(moduleAbsPath);
226
+ mod = (await import(moduleAbsPath)) as ModuleNamespace;
198
227
  } catch (error) {
199
228
  return new Response(`Failed to import "${$src}": ${errorMessage(error)}`, {
200
229
  status: 500,
@@ -209,7 +238,7 @@ export async function handleServerFunction(req: Request, root: string) {
209
238
  }
210
239
 
211
240
  try {
212
- const result = await fn(args);
241
+ const result = await (fn as (args: Record<string, unknown>) => unknown)(args);
213
242
  return Response.json(result ?? null);
214
243
  } catch (error) {
215
244
  return Response.json({ error: errorMessage(error) }, { status: 500 });
package/src/server.ts CHANGED
@@ -30,6 +30,14 @@ import { existsSync, readFileSync } from "node:fs";
30
30
  * @param {string} urlPath - URL pathname (e.g. "/pages/@jxsuite/parser/Foo.class.json")
31
31
  * @returns {string | null} Absolute file path or null
32
32
  */
33
+ interface PackageJson {
34
+ exports?: Record<string, string | { import?: string; default?: string }>;
35
+ customElements?: string;
36
+ module?: string;
37
+ main?: string;
38
+ [key: string]: unknown;
39
+ }
40
+
33
41
  function resolveNpmPath(rootDir: string, urlPath: string) {
34
42
  let root = rootDir;
35
43
  let segments = urlPath.split("/").filter(Boolean);
@@ -49,7 +57,7 @@ function resolveNpmPath(rootDir: string, urlPath: string) {
49
57
  let start = -1;
50
58
  let isScoped = false;
51
59
  for (let i = 0; i < segments.length; i++) {
52
- if (segments[i].startsWith("@")) {
60
+ if (segments[i]!.startsWith("@")) {
53
61
  start = i;
54
62
  isScoped = true;
55
63
  break;
@@ -63,14 +71,14 @@ function resolveNpmPath(rootDir: string, urlPath: string) {
63
71
  if (start < 0 || start + 1 >= segments.length) {
64
72
  return null;
65
73
  }
66
- const scope = segments[start];
67
- const pkg = segments[start + 1];
74
+ const scope = segments[start]!;
75
+ const pkg = segments[start + 1]!;
68
76
  subpath = segments.slice(start + 2).join("/");
69
77
  pkgDir = join(root, "node_modules", scope, pkg);
70
78
  } else {
71
79
  // Unscoped: try each segment as a package name in node_modules
72
80
  for (let i = 0; i < segments.length; i++) {
73
- const candidate = join(root, "node_modules", segments[i]);
81
+ const candidate = join(root, "node_modules", segments[i]!);
74
82
  if (existsSync(join(candidate, "package.json"))) {
75
83
  start = i;
76
84
  pkgDir = candidate;
@@ -91,10 +99,11 @@ function resolveNpmPath(rootDir: string, urlPath: string) {
91
99
  // If there's a subpath, check package.json exports first
92
100
  if (subpath) {
93
101
  try {
94
- const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
102
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as PackageJson;
95
103
  const exportKey = `./${subpath}`;
96
- if (pkgJson.exports && pkgJson.exports[exportKey]) {
97
- const mapped = join(pkgDir, pkgJson.exports[exportKey]);
104
+ const exportVal = pkgJson.exports?.[exportKey];
105
+ if (typeof exportVal === "string") {
106
+ const mapped = join(pkgDir, exportVal);
98
107
  if (existsSync(mapped)) {
99
108
  return mapped;
100
109
  }
@@ -107,7 +116,7 @@ function resolveNpmPath(rootDir: string, urlPath: string) {
107
116
  }
108
117
  // CEM-relative: subpath may be relative to the custom elements manifest directory
109
118
  try {
110
- const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
119
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as PackageJson;
111
120
  if (pkgJson.customElements) {
112
121
  const cemDir = pkgJson.customElements.replace(/\/[^/]+$/, "");
113
122
  const cemRelative = join(pkgDir, cemDir, subpath);
@@ -120,7 +129,7 @@ function resolveNpmPath(rootDir: string, urlPath: string) {
120
129
 
121
130
  // Bare package (no subpath): resolve entry point
122
131
  try {
123
- const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
132
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as PackageJson;
124
133
  const exp = pkgJson.exports?.["."];
125
134
  const entry =
126
135
  (typeof exp === "object" ? (exp.import ?? exp.default) : exp) ??
@@ -236,7 +245,7 @@ export async function createDevServer(options: {
236
245
  if (enableStudio && path.startsWith("/__studio/")) {
237
246
  // Activate project — tells the server which project root to use for static file fallback
238
247
  if (path === "/__studio/activate" && req.method === "POST") {
239
- const body = await req.json();
248
+ const body = (await req.json()) as { root?: string };
240
249
  const raw = body.root || null;
241
250
  // Always store as absolute path
242
251
  activeProjectRoot = raw ? resolve(absRoot, raw) : null;
@@ -302,7 +311,7 @@ export async function createDevServer(options: {
302
311
  minify: false,
303
312
  });
304
313
  if (result.success && result.outputs.length > 0) {
305
- bundleCache.set(cacheKey, await result.outputs[0].text());
314
+ bundleCache.set(cacheKey, await result.outputs[0]!.text());
306
315
  }
307
316
  } catch (error) {
308
317
  console.error("Bundle failed for", resolved, error);
package/src/studio-api.ts CHANGED
@@ -15,10 +15,88 @@ import { buildProjectFormatRegistry } from "@jxsuite/compiler/format-host";
15
15
  import type { FormatRegistry } from "@jxsuite/schema/format-registry";
16
16
  import * as claude from "./claude-session.ts";
17
17
  import type { ClassJsonDef } from "./types.ts";
18
+ import type { ProjectConfig } from "@jxsuite/schema/types";
18
19
 
19
20
  /** Normalise a path to forward slashes (Windows `path` module returns backslashes). */
20
21
  const fwd = (p: string) => p.replaceAll("\\", "/");
21
22
 
23
+ interface PackageJson {
24
+ name?: string;
25
+ workspaces?: unknown;
26
+ dependencies?: Record<string, string>;
27
+ devDependencies?: Record<string, string>;
28
+ customElements?: string;
29
+ [key: string]: unknown;
30
+ }
31
+
32
+ /** A custom-element-manifest declaration (subset consumed here). */
33
+ interface CemDeclaration {
34
+ customElement?: boolean;
35
+ tagName?: string;
36
+ description?: string;
37
+ cssProperties?: unknown[];
38
+ events?: unknown[];
39
+ slots?: unknown[];
40
+ members?: { kind?: string; privacy?: string; [key: string]: unknown }[];
41
+ attributes?: {
42
+ name?: string;
43
+ default?: unknown;
44
+ description?: string;
45
+ type?: { text?: string };
46
+ }[];
47
+ [key: string]: unknown;
48
+ }
49
+
50
+ interface CemModule {
51
+ path?: string;
52
+ declarations?: CemDeclaration[];
53
+ }
54
+
55
+ interface Cem {
56
+ modules?: CemModule[];
57
+ }
58
+
59
+ /** A parsed Jx component document (subset consumed by component discovery). */
60
+ interface ComponentDoc {
61
+ $id?: string;
62
+ tagName?: string;
63
+ $elements?: unknown[];
64
+ state?: Record<string, unknown>;
65
+ [key: string]: unknown;
66
+ }
67
+
68
+ interface SlotDef {
69
+ name: string;
70
+ fallback?: unknown[];
71
+ }
72
+
73
+ /**
74
+ * Collect slot definitions (name + fallback children) from a parsed component tree. Whitespace-only
75
+ * names count as unnamed (""). Only static children arrays are walked.
76
+ */
77
+ function collectSlotDefs(node: unknown, out: SlotDef[] = []): SlotDef[] {
78
+ if (!node || typeof node !== "object" || Array.isArray(node)) {
79
+ return out;
80
+ }
81
+ const el = node as Record<string, unknown>;
82
+ if (el.tagName === "slot") {
83
+ const attrs = el.attributes as Record<string, unknown> | undefined;
84
+ const rawName = attrs?.name;
85
+ const name = typeof rawName === "string" ? rawName.trim() : "";
86
+ const { children } = el;
87
+ out.push({
88
+ name,
89
+ ...(Array.isArray(children) && children.length > 0 ? { fallback: children } : {}),
90
+ });
91
+ }
92
+ if (Array.isArray(el.children)) {
93
+ for (const c of el.children) {
94
+ collectSlotDefs(c, out);
95
+ }
96
+ }
97
+ return out;
98
+ }
99
+
22
100
  // ─── Format registry (per project root, invalidated on project.json change) ──
23
101
 
24
102
  const formatRegistryCache = new Map<string, { mtime: number; registry: FormatRegistry }>();
@@ -33,10 +111,10 @@ const formatRegistryCache = new Map<string, { mtime: number; registry: FormatReg
33
111
  async function getFormatRegistry(projectRoot: string): Promise<FormatRegistry> {
34
112
  const projectJsonPath = resolve(projectRoot, "project.json");
35
113
  let mtime = 0;
36
- let projectConfig;
114
+ let projectConfig: ProjectConfig | undefined;
37
115
  try {
38
116
  mtime = statSync(projectJsonPath).mtimeMs;
39
- projectConfig = JSON.parse(readFileSync(projectJsonPath, "utf8"));
117
+ projectConfig = JSON.parse(readFileSync(projectJsonPath, "utf8")) as ProjectConfig;
40
118
  } catch {
41
119
  projectConfig = undefined;
42
120
  }
@@ -111,14 +189,14 @@ export function parseGitStatus(out: string) {
111
189
  } else if (line.startsWith("# branch.ab ")) {
112
190
  const m = line.match(/\+(\d+) -(\d+)/);
113
191
  if (m) {
114
- ahead = Number.parseInt(m[1], 10);
115
- behind = Number.parseInt(m[2], 10);
192
+ ahead = Number.parseInt(m[1]!, 10);
193
+ behind = Number.parseInt(m[2]!, 10);
116
194
  }
117
195
  } else if (line.startsWith("1 ") || line.startsWith("2 ")) {
118
196
  const parts = line.split(" ");
119
- const [, xy] = parts;
120
- const [stagedCode] = xy;
121
- const [, unstagedCode] = xy;
197
+ const xy = parts[1]!;
198
+ const stagedCode = xy[0]!;
199
+ const unstagedCode = xy[1]!;
122
200
  let filePath;
123
201
  if (line.startsWith("2 ")) {
124
202
  const tabIdx = line.indexOf("\t");
@@ -169,7 +247,7 @@ export async function handleStudioApi(
169
247
  // Project metadata
170
248
  if (path === "/__studio/project" && req.method === "GET") {
171
249
  try {
172
- const pkg = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"));
250
+ const pkg = JSON.parse(await readFile(resolve(root, "package.json"), "utf8")) as PackageJson;
173
251
  return Response.json({
174
252
  name: pkg.name ?? basename(root),
175
253
  root,
@@ -211,12 +289,12 @@ export async function handleStudioApi(
211
289
  }
212
290
 
213
291
  let isSiteProject = false;
214
- let projectConfig = null;
292
+ let projectConfig: ProjectConfig | null = null;
215
293
  try {
216
- const raw = JSON.parse(await readFile(resolve(absDir, "project.json"), "utf8"));
294
+ const raw = JSON.parse(await readFile(resolve(absDir, "project.json"), "utf8")) as unknown;
217
295
  if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
218
296
  isSiteProject = true;
219
- projectConfig = raw;
297
+ projectConfig = raw as ProjectConfig;
220
298
  }
221
299
  } catch {}
222
300
 
@@ -245,7 +323,7 @@ export async function handleStudioApi(
245
323
  while (dir) {
246
324
  const candidate = resolve(dir, "project.json");
247
325
  if (existsSync(candidate)) {
248
- const config = JSON.parse(readFileSync(candidate, "utf8"));
326
+ const config = JSON.parse(readFileSync(candidate, "utf8")) as ProjectConfig;
249
327
  const relPath = fwd(dir);
250
328
  const absFile = filePath.startsWith("~")
251
329
  ? filePath.replace("~", process.env.HOME || "")
@@ -313,10 +391,10 @@ export async function handleStudioApi(
313
391
  }
314
392
  const fp = resolve(root, match);
315
393
  try {
316
- const raw = JSON.parse(await readFile(fp, "utf8"));
394
+ const raw = JSON.parse(await readFile(fp, "utf8")) as unknown;
317
395
  if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
318
396
  const projectDir = fwd(dirname(fp));
319
- sites.push({ config: raw, path: projectDir });
397
+ sites.push({ config: raw as ProjectConfig, path: projectDir });
320
398
  }
321
399
  } catch {}
322
400
  }
@@ -329,7 +407,13 @@ export async function handleStudioApi(
329
407
  // Create a new project
330
408
  if (path === "/__studio/create-project" && req.method === "POST") {
331
409
  try {
332
- const body = await req.json();
410
+ const body = (await req.json()) as {
411
+ name?: string;
412
+ description?: string;
413
+ url?: string;
414
+ adapter?: "static" | "cloudflare-pages" | "cloudflare-workers" | "node" | "bun";
415
+ directory?: string;
416
+ };
333
417
  const { name, description, url: siteUrl, adapter, directory } = body;
334
418
  if (!name || !directory) {
335
419
  return Response.json({ error: "name and directory are required" }, { status: 400 });
@@ -339,13 +423,15 @@ export async function handleStudioApi(
339
423
 
340
424
  const { generateProject } = await import("@jxsuite/create/generate");
341
425
  await generateProject(destPath, {
342
- adapter,
343
- description,
344
426
  name,
345
- url: siteUrl,
427
+ ...(adapter !== undefined ? { adapter } : {}),
428
+ ...(description !== undefined ? { description } : {}),
429
+ ...(siteUrl !== undefined ? { url: siteUrl } : {}),
346
430
  });
347
431
 
348
- const config = JSON.parse(await readFile(resolve(destPath, "project.json"), "utf8"));
432
+ const config = JSON.parse(
433
+ await readFile(resolve(destPath, "project.json"), "utf8"),
434
+ ) as ProjectConfig;
349
435
  const projectRoot = fwd(relative(root, destPath));
350
436
  return Response.json({ config, root: projectRoot });
351
437
  } catch (error) {
@@ -442,18 +528,19 @@ export async function handleStudioApi(
442
528
  }
443
529
  const fp = resolve(scanRoot, match);
444
530
  try {
445
- let content;
531
+ let content: ComponentDoc;
446
532
  if (match.endsWith(".json")) {
447
- content = JSON.parse(await readFile(fp, "utf8"));
533
+ content = JSON.parse(await readFile(fp, "utf8")) as ComponentDoc;
448
534
  } else {
449
535
  const entry = registry.byExtension(extname(match), "parse");
450
536
  if (!entry) {
451
537
  continue;
452
538
  }
453
539
  const source = await readFile(fp, "utf8");
454
- content = (await entry.call("parse", source)) as Record<string, unknown>;
540
+ content = (await entry.call("parse", source)) as ComponentDoc;
455
541
  }
456
542
  if (content.tagName && content.tagName.includes("-")) {
543
+ const slotDefs = collectSlotDefs(content);
457
544
  components.push({
458
545
  $id: content.$id || null,
459
546
  hasElements: Array.isArray(content.$elements) && content.$elements.length > 0,
@@ -484,6 +571,7 @@ export async function handleStudioApi(
484
571
  type: obj.type,
485
572
  };
486
573
  }),
574
+ ...(slotDefs.length > 0 ? { slots: slotDefs } : {}),
487
575
  source: "jx",
488
576
  tagName: content.tagName,
489
577
  });
@@ -495,7 +583,7 @@ export async function handleStudioApi(
495
583
  try {
496
584
  const projectPkgPath = resolve(scanRoot, "package.json");
497
585
  if (existsSync(projectPkgPath)) {
498
- const pkg = JSON.parse(await readFile(projectPkgPath, "utf8"));
586
+ const pkg = JSON.parse(await readFile(projectPkgPath, "utf8")) as PackageJson;
499
587
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
500
588
  for (const name of Object.keys(deps)) {
501
589
  try {
@@ -520,7 +608,7 @@ export async function handleStudioApi(
520
608
  if (!actualPath) {
521
609
  continue;
522
610
  }
523
- const depPkg = JSON.parse(await readFile(actualPath, "utf8"));
611
+ const depPkg = JSON.parse(await readFile(actualPath, "utf8")) as PackageJson;
524
612
  if (!depPkg.customElements) {
525
613
  continue;
526
614
  }
@@ -528,7 +616,7 @@ export async function handleStudioApi(
528
616
  if (!existsSync(cemPath)) {
529
617
  continue;
530
618
  }
531
- const cem = JSON.parse(await readFile(cemPath, "utf8"));
619
+ const cem = JSON.parse(await readFile(cemPath, "utf8")) as Cem;
532
620
  for (const mod of cem.modules || []) {
533
621
  for (const decl of mod.declarations || []) {
534
622
  if (decl.customElement && decl.tagName) {
@@ -539,17 +627,16 @@ export async function handleStudioApi(
539
627
  events: decl.events || [],
540
628
  hasElements: false,
541
629
  members: (decl.members || []).filter(
542
- (m: Record<string, unknown>) =>
543
- m.kind === "field" && m.privacy !== "private",
630
+ (m) => m.kind === "field" && m.privacy !== "private",
544
631
  ),
545
632
  modulePath: mod.path,
546
633
  package: name,
547
634
  path: null,
548
- props: (decl.attributes || []).map((a: Record<string, unknown>) => ({
635
+ props: (decl.attributes || []).map((a) => ({
549
636
  default: a.default,
550
637
  description: a.description || null,
551
638
  name: a.name,
552
- type: (a.type as Record<string, unknown> | undefined)?.text,
639
+ type: a.type?.text,
553
640
  })),
554
641
  slots: decl.slots || [],
555
642
  source: "npm",
@@ -580,17 +667,14 @@ export async function handleStudioApi(
580
667
  if (!existsSync(pkgPath)) {
581
668
  return Response.json([]);
582
669
  }
583
- const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
670
+ const pkg = JSON.parse(await readFile(pkgPath, "utf8")) as PackageJson;
584
671
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
585
- /**
586
- * @type {{
587
- * name: string;
588
- * version: string;
589
- * hasCem: boolean;
590
- * customElementsPath: string | null;
591
- * }[]}
592
- */
593
- const packages = [];
672
+ const packages: {
673
+ name: string;
674
+ version: string;
675
+ hasCem: boolean;
676
+ customElementsPath: string | null;
677
+ }[] = [];
594
678
  for (const [name, version] of Object.entries(deps)) {
595
679
  const depPkgPath = resolve(scanRoot, "node_modules", ...name.split("/"), "package.json");
596
680
  const fallbackPath = resolve(root, "node_modules", ...name.split("/"), "package.json");
@@ -603,12 +687,12 @@ export async function handleStudioApi(
603
687
  continue;
604
688
  }
605
689
  try {
606
- const depPkg = JSON.parse(await readFile(actualPath, "utf8"));
690
+ const depPkg = JSON.parse(await readFile(actualPath, "utf8")) as PackageJson;
607
691
  packages.push({
608
692
  customElementsPath: depPkg.customElements || null,
609
693
  hasCem: Boolean(depPkg.customElements),
610
694
  name,
611
- version: /** @type {string} */ version,
695
+ version,
612
696
  });
613
697
  } catch {}
614
698
  }
@@ -637,7 +721,7 @@ export async function handleStudioApi(
637
721
  if (!actualPath) {
638
722
  return Response.json({ cem: null });
639
723
  }
640
- const depPkg = JSON.parse(await readFile(actualPath, "utf8"));
724
+ const depPkg = JSON.parse(await readFile(actualPath, "utf8")) as PackageJson;
641
725
  if (!depPkg.customElements) {
642
726
  return Response.json({ cem: null });
643
727
  }
@@ -645,7 +729,7 @@ export async function handleStudioApi(
645
729
  if (!existsSync(cemPath)) {
646
730
  return Response.json({ cem: null });
647
731
  }
648
- const cem = JSON.parse(await readFile(cemPath, "utf8"));
732
+ const cem = JSON.parse(await readFile(cemPath, "utf8")) as Cem;
649
733
  return Response.json({ cem });
650
734
  } catch (error) {
651
735
  return Response.json({ error: errorMessage(error) }, { status: 500 });
@@ -655,7 +739,7 @@ export async function handleStudioApi(
655
739
  // Add an npm package
656
740
  if (path === "/__studio/packages/add" && req.method === "POST") {
657
741
  try {
658
- const body = await req.json();
742
+ const body = (await req.json()) as { name?: string; dir?: string; dev?: boolean };
659
743
  const { name } = body;
660
744
  if (!name || typeof name !== "string") {
661
745
  return Response.json({ error: "Missing name" }, { status: 400 });
@@ -688,7 +772,7 @@ export async function handleStudioApi(
688
772
  // Remove an npm package
689
773
  if (path === "/__studio/packages/remove" && req.method === "POST") {
690
774
  try {
691
- const body = await req.json();
775
+ const body = (await req.json()) as { name?: string; dir?: string };
692
776
  const { name } = body;
693
777
  if (!name || typeof name !== "string") {
694
778
  return Response.json({ error: "Missing name" }, { status: 400 });
@@ -808,9 +892,9 @@ export async function handleStudioApi(
808
892
 
809
893
  // Rename file
810
894
  if (path === "/__studio/file/rename" && req.method === "POST") {
811
- let body;
895
+ let body: { from?: string; to?: string };
812
896
  try {
813
- body = await req.json();
897
+ body = (await req.json()) as { from?: string; to?: string };
814
898
  } catch {
815
899
  return new Response("Invalid JSON", { status: 400 });
816
900
  }
@@ -841,9 +925,9 @@ export async function handleStudioApi(
841
925
 
842
926
  // Locate a file by name within the project root
843
927
  if (path === "/__studio/locate" && req.method === "POST") {
844
- let body;
928
+ let body: { name?: string };
845
929
  try {
846
- body = await req.json();
930
+ body = (await req.json()) as { name?: string };
847
931
  } catch {
848
932
  return new Response("Invalid JSON", { status: 400 });
849
933
  }
@@ -879,15 +963,16 @@ export async function handleStudioApi(
879
963
  // Dispatches parse/serialize through the project's format registry by import name.
880
964
  if (path === "/__studio/format" && req.method === "POST") {
881
965
  try {
882
- const body = await req.json();
883
- const { format, action, source, doc, options } = body as {
966
+ const body = (await req.json()) as {
884
967
  format?: string;
885
968
  action?: string;
886
969
  source?: string;
887
970
  doc?: Record<string, unknown>;
888
971
  options?: Record<string, unknown>;
972
+ dir?: string;
889
973
  };
890
- const dir = (body.dir as string | undefined) || activeProjectRoot || root;
974
+ const { format, action, source, doc, options } = body;
975
+ const dir = body.dir || activeProjectRoot || root;
891
976
  const projectRoot = isAbsolute(dir) ? dir : resolve(root, dir);
892
977
  assertAccessible(projectRoot, root, activeProjectRoot);
893
978
 
@@ -985,7 +1070,7 @@ export async function handleStudioApi(
985
1070
  if (moduleAbsPath.endsWith(".class.json")) {
986
1071
  try {
987
1072
  const content = readFileSync(moduleAbsPath, "utf8");
988
- const classDef = JSON.parse(content);
1073
+ const classDef = JSON.parse(content) as ClassJsonDef;
989
1074
  return Response.json({
990
1075
  schema: extractStudioSchema(classDef, moduleAbsPath),
991
1076
  });
@@ -1003,7 +1088,7 @@ export async function handleStudioApi(
1003
1088
  if (existsSync(classJsonPath)) {
1004
1089
  try {
1005
1090
  const content = readFileSync(classJsonPath, "utf8");
1006
- const classDef = JSON.parse(content);
1091
+ const classDef = JSON.parse(content) as ClassJsonDef;
1007
1092
  return Response.json({
1008
1093
  schema: extractStudioSchema(classDef, classJsonPath),
1009
1094
  });
@@ -1014,7 +1099,10 @@ export async function handleStudioApi(
1014
1099
 
1015
1100
  // Fallback: import JS module (backwards compat for classes without .class.json)
1016
1101
  try {
1017
- const mod = await import(moduleAbsPath);
1102
+ const mod = (await import(moduleAbsPath)) as {
1103
+ default?: Record<string, unknown>;
1104
+ [key: string]: unknown;
1105
+ };
1018
1106
  const ExportedClass = mod[exportName] ?? mod.default?.[exportName];
1019
1107
  if (typeof ExportedClass !== "function") {
1020
1108
  return Response.json({
@@ -1022,7 +1110,7 @@ export async function handleStudioApi(
1022
1110
  schema: null,
1023
1111
  });
1024
1112
  }
1025
- return Response.json({ schema: ExportedClass.schema ?? null });
1113
+ return Response.json({ schema: (ExportedClass as { schema?: unknown }).schema ?? null });
1026
1114
  } catch (error) {
1027
1115
  return Response.json({
1028
1116
  error: errorMessage(error),
@@ -1091,7 +1179,10 @@ export async function handleStudioApi(
1091
1179
  }
1092
1180
 
1093
1181
  if (gitCmd === "add-remote" && req.method === "POST") {
1094
- const { name, url: remoteUrl } = await req.json();
1182
+ const { name, url: remoteUrl } = (await req.json()) as {
1183
+ name?: string;
1184
+ url?: string;
1185
+ };
1095
1186
  if (!name || !remoteUrl) {
1096
1187
  return new Response("name and url required", { status: 400 });
1097
1188
  }
@@ -1108,9 +1199,9 @@ export async function handleStudioApi(
1108
1199
  continue;
1109
1200
  }
1110
1201
  const [name, head] = line.split("\t");
1111
- branches.push(name);
1202
+ branches.push(name!);
1112
1203
  if (head === "*") {
1113
- current = name;
1204
+ current = name!;
1114
1205
  }
1115
1206
  }
1116
1207
  return Response.json({ branches, current });
@@ -1131,7 +1222,7 @@ export async function handleStudioApi(
1131
1222
  }
1132
1223
 
1133
1224
  if (gitCmd === "stage" && req.method === "POST") {
1134
- const { files } = await req.json();
1225
+ const { files } = (await req.json()) as { files?: string[] };
1135
1226
  if (!Array.isArray(files) || files.length === 0) {
1136
1227
  return Response.json({ error: "Missing files" }, { status: 400 });
1137
1228
  }
@@ -1145,7 +1236,7 @@ export async function handleStudioApi(
1145
1236
  }
1146
1237
 
1147
1238
  if (gitCmd === "unstage" && req.method === "POST") {
1148
- const { files } = await req.json();
1239
+ const { files } = (await req.json()) as { files?: string[] };
1149
1240
  if (!Array.isArray(files) || files.length === 0) {
1150
1241
  return Response.json({ error: "Missing files" }, { status: 400 });
1151
1242
  }
@@ -1154,7 +1245,7 @@ export async function handleStudioApi(
1154
1245
  }
1155
1246
 
1156
1247
  if (gitCmd === "commit" && req.method === "POST") {
1157
- const { message } = await req.json();
1248
+ const { message } = (await req.json()) as { message?: string };
1158
1249
  if (!message || typeof message !== "string") {
1159
1250
  return Response.json({ error: "Missing message" }, { status: 400 });
1160
1251
  }
@@ -1169,11 +1260,11 @@ export async function handleStudioApi(
1169
1260
  }
1170
1261
 
1171
1262
  if (gitCmd === "push" && req.method === "POST") {
1172
- let body: Record<string, unknown> = {};
1263
+ let body: { setUpstream?: boolean } = {};
1173
1264
  try {
1174
- body = await req.json();
1265
+ body = (await req.json()) as { setUpstream?: boolean };
1175
1266
  } catch {}
1176
- const { setUpstream } = body as { setUpstream?: boolean };
1267
+ const { setUpstream } = body;
1177
1268
  if (setUpstream) {
1178
1269
  const branchRaw = await runGit(["rev-parse", "--abbrev-ref", "HEAD"]);
1179
1270
  const branch = branchRaw.trim();
@@ -1195,7 +1286,7 @@ export async function handleStudioApi(
1195
1286
  }
1196
1287
 
1197
1288
  if (gitCmd === "checkout" && req.method === "POST") {
1198
- const { branch } = await req.json();
1289
+ const { branch } = (await req.json()) as { branch?: string };
1199
1290
  if (!branch || typeof branch !== "string") {
1200
1291
  return Response.json({ error: "Missing branch" }, { status: 400 });
1201
1292
  }
@@ -1204,7 +1295,7 @@ export async function handleStudioApi(
1204
1295
  }
1205
1296
 
1206
1297
  if (gitCmd === "create-branch" && req.method === "POST") {
1207
- const { name } = await req.json();
1298
+ const { name } = (await req.json()) as { name?: string };
1208
1299
  if (!name || typeof name !== "string") {
1209
1300
  return Response.json({ error: "Missing name" }, { status: 400 });
1210
1301
  }
@@ -1238,7 +1329,7 @@ export async function handleStudioApi(
1238
1329
  }
1239
1330
 
1240
1331
  if (gitCmd === "discard" && req.method === "POST") {
1241
- const { files } = await req.json();
1332
+ const { files } = (await req.json()) as { files?: string[] };
1242
1333
  if (!Array.isArray(files) || files.length === 0) {
1243
1334
  return Response.json({ error: "Missing files" }, { status: 400 });
1244
1335
  }
@@ -1252,7 +1343,7 @@ export async function handleStudioApi(
1252
1343
  }
1253
1344
 
1254
1345
  if (gitCmd === "clone" && req.method === "POST") {
1255
- const { url: repoUrl } = await req.json();
1346
+ const { url: repoUrl } = (await req.json()) as { url?: string };
1256
1347
  if (!repoUrl || typeof repoUrl !== "string") {
1257
1348
  return Response.json({ error: "Missing url" }, { status: 400 });
1258
1349
  }
@@ -1284,11 +1375,13 @@ export async function handleStudioApi(
1284
1375
 
1285
1376
  if (path === "/__studio/ai/session" && req.method === "POST") {
1286
1377
  try {
1287
- const body = await req.json();
1378
+ const body = (await req.json()) as { message?: string; systemPrompt?: string };
1288
1379
  const projectDir = activeProjectRoot || root;
1289
- const result = claude.createSession(projectDir, body.message, {
1290
- systemPrompt: body.systemPrompt,
1291
- });
1380
+ const opts: { systemPrompt?: string } = {};
1381
+ if (body.systemPrompt !== undefined) {
1382
+ opts.systemPrompt = body.systemPrompt;
1383
+ }
1384
+ const result = claude.createSession(projectDir, body.message!, opts);
1292
1385
  return Response.json(result);
1293
1386
  } catch (error) {
1294
1387
  return Response.json(
@@ -1303,7 +1396,7 @@ export async function handleStudioApi(
1303
1396
  path.endsWith("/stream") &&
1304
1397
  req.method === "GET"
1305
1398
  ) {
1306
- const [id] = path.split("/").slice(4);
1399
+ const id = path.split("/").slice(4)[0]!;
1307
1400
  return claude.streamSession(id);
1308
1401
  }
1309
1402
 
@@ -1313,9 +1406,9 @@ export async function handleStudioApi(
1313
1406
  req.method === "POST"
1314
1407
  ) {
1315
1408
  try {
1316
- const [id] = path.split("/").slice(4);
1317
- const body = await req.json();
1318
- claude.sendMessage(id, body.message);
1409
+ const id = path.split("/").slice(4)[0]!;
1410
+ const body = (await req.json()) as { message?: string };
1411
+ claude.sendMessage(id, body.message!);
1319
1412
  return Response.json({ ok: true });
1320
1413
  } catch (error) {
1321
1414
  return Response.json(
@@ -1326,19 +1419,19 @@ export async function handleStudioApi(
1326
1419
  }
1327
1420
 
1328
1421
  if (path.startsWith("/__studio/ai/session/") && path.endsWith("/stop") && req.method === "POST") {
1329
- const [id] = path.split("/").slice(4);
1422
+ const id = path.split("/").slice(4)[0]!;
1330
1423
  claude.stopSession(id);
1331
1424
  return Response.json({ ok: true });
1332
1425
  }
1333
1426
 
1334
1427
  if (path.startsWith("/__studio/ai/session/") && req.method === "DELETE") {
1335
- const [id] = path.split("/").slice(4);
1428
+ const id = path.split("/").slice(4)[0]!;
1336
1429
  claude.deleteSession(id);
1337
1430
  return Response.json({ ok: true });
1338
1431
  }
1339
1432
 
1340
1433
  if (path.startsWith("/__studio/ai/session/") && req.method === "GET") {
1341
- const [id] = path.split("/").slice(4);
1434
+ const id = path.split("/").slice(4)[0]!;
1342
1435
  const info = claude.getSession(id);
1343
1436
  if (!info) {
1344
1437
  return Response.json({ error: "Not found" }, { status: 404 });
@@ -1369,7 +1462,7 @@ function extractStudioSchema(classDef: ClassJsonDef, classJsonPath: string) {
1369
1462
  try {
1370
1463
  const parentPath = resolve(dirname(classJsonPath), classDef.extends.$ref);
1371
1464
  const parentContent = readFileSync(parentPath, "utf8");
1372
- const parentDef = JSON.parse(parentContent);
1465
+ const parentDef = JSON.parse(parentContent) as ClassJsonDef;
1373
1466
  parentSchema = extractStudioSchema(parentDef, parentPath);
1374
1467
  } catch {
1375
1468
  // Parent not found — proceed without inheritance