@jxsuite/server 0.23.2 → 0.24.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 +9 -6
- package/src/{build.js → build.ts} +17 -2
- package/src/{claude-session.js → claude-session.ts} +44 -43
- package/src/{code-api.js → code-api.ts} +19 -18
- package/src/{resolve.js → resolve.ts} +28 -40
- package/src/{server.js → server.ts} +25 -17
- package/src/{studio-api.js → studio-api.ts} +122 -168
- package/src/types.ts +61 -0
- package/src/{watch.js → watch.ts} +14 -11
|
@@ -11,10 +11,11 @@ import { resolve, relative, basename, dirname, isAbsolute } from "node:path";
|
|
|
11
11
|
import { readdir, stat, readFile, writeFile, rename, unlink, mkdir } from "node:fs/promises";
|
|
12
12
|
import { readFileSync, existsSync } from "node:fs";
|
|
13
13
|
import { transpileJxMarkdown } from "@jxsuite/parser/transpile";
|
|
14
|
-
import * as claude from "./claude-session.
|
|
14
|
+
import * as claude from "./claude-session.ts";
|
|
15
|
+
import type { ClassJsonDef } from "./types.ts";
|
|
15
16
|
|
|
16
17
|
/** Normalise a path to forward slashes (Windows `path` module returns backslashes). */
|
|
17
|
-
const fwd = (
|
|
18
|
+
const fwd = (p: string) => p.replaceAll("\\", "/");
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Check that a path is under either the server root OR the active project root. This allows file
|
|
@@ -24,7 +25,7 @@ const fwd = (/** @type {string} */ p) => p.replaceAll("\\", "/");
|
|
|
24
25
|
* @param {string} root
|
|
25
26
|
* @param {string | null} activeProjectRoot
|
|
26
27
|
*/
|
|
27
|
-
function assertAccessible(filePath, root, activeProjectRoot) {
|
|
28
|
+
function assertAccessible(filePath: string, root: string, activeProjectRoot: string | null) {
|
|
28
29
|
const rel = relative(root, filePath);
|
|
29
30
|
if (!rel.startsWith("..") && !rel.startsWith("/")) return;
|
|
30
31
|
if (activeProjectRoot) {
|
|
@@ -34,8 +35,15 @@ function assertAccessible(filePath, root, activeProjectRoot) {
|
|
|
34
35
|
throw new Error("Path outside project root");
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const statusMap: Record<string, string> = {
|
|
39
|
+
M: "M",
|
|
40
|
+
T: "T",
|
|
41
|
+
A: "A",
|
|
42
|
+
D: "D",
|
|
43
|
+
R: "R",
|
|
44
|
+
C: "C",
|
|
45
|
+
U: "U",
|
|
46
|
+
};
|
|
39
47
|
|
|
40
48
|
/**
|
|
41
49
|
* Parse raw `git status --porcelain=v2 --branch` output into structured data.
|
|
@@ -50,7 +58,7 @@ const statusMap = { M: "M", T: "T", A: "A", D: "D", R: "R", C: "C", U: "U" };
|
|
|
50
58
|
* remotes?: string[];
|
|
51
59
|
* }}
|
|
52
60
|
*/
|
|
53
|
-
export function parseGitStatus(out) {
|
|
61
|
+
export function parseGitStatus(out: string) {
|
|
54
62
|
let branch = "";
|
|
55
63
|
let ahead = 0;
|
|
56
64
|
let behind = 0;
|
|
@@ -108,7 +116,12 @@ export function parseGitStatus(out) {
|
|
|
108
116
|
* @param {string | null} [activeProjectRoot]
|
|
109
117
|
* @returns {Promise<Response | null>}
|
|
110
118
|
*/
|
|
111
|
-
export async function handleStudioApi(
|
|
119
|
+
export async function handleStudioApi(
|
|
120
|
+
req: Request,
|
|
121
|
+
url: URL,
|
|
122
|
+
root: string,
|
|
123
|
+
activeProjectRoot: string | null = null,
|
|
124
|
+
) {
|
|
112
125
|
const path = url.pathname;
|
|
113
126
|
|
|
114
127
|
// Project metadata
|
|
@@ -131,11 +144,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
131
144
|
const absDir = isAbsolute(dir) ? dir : resolve(root, dir);
|
|
132
145
|
try {
|
|
133
146
|
assertAccessible(absDir, root, activeProjectRoot);
|
|
134
|
-
} catch (
|
|
135
|
-
return Response.json(
|
|
136
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
137
|
-
{ status: 400 },
|
|
138
|
-
);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
return Response.json({ error: (e as Error).message }, { status: 400 });
|
|
139
149
|
}
|
|
140
150
|
try {
|
|
141
151
|
const projectRoot = fwd(absDir);
|
|
@@ -167,11 +177,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
167
177
|
} catch {}
|
|
168
178
|
|
|
169
179
|
return Response.json({ isSiteProject, projectConfig, directories, projectRoot });
|
|
170
|
-
} catch (
|
|
171
|
-
return Response.json(
|
|
172
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
173
|
-
{ status: 500 },
|
|
174
|
-
);
|
|
180
|
+
} catch (e) {
|
|
181
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
175
182
|
}
|
|
176
183
|
}
|
|
177
184
|
|
|
@@ -205,11 +212,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
205
212
|
dir = parent;
|
|
206
213
|
}
|
|
207
214
|
return Response.json({ sitePath: null });
|
|
208
|
-
} catch (
|
|
209
|
-
return Response.json(
|
|
210
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
211
|
-
{ status: 500 },
|
|
212
|
-
);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
213
217
|
}
|
|
214
218
|
}
|
|
215
219
|
|
|
@@ -230,11 +234,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
230
234
|
}
|
|
231
235
|
} catch {}
|
|
232
236
|
return Response.json({ path: null });
|
|
233
|
-
} catch (
|
|
234
|
-
return Response.json(
|
|
235
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
236
|
-
{ status: 500 },
|
|
237
|
-
);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
238
239
|
}
|
|
239
240
|
}
|
|
240
241
|
|
|
@@ -260,11 +261,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
260
261
|
} catch {}
|
|
261
262
|
}
|
|
262
263
|
return Response.json(sites);
|
|
263
|
-
} catch (
|
|
264
|
-
return Response.json(
|
|
265
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
266
|
-
{ status: 500 },
|
|
267
|
-
);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
268
266
|
}
|
|
269
267
|
}
|
|
270
268
|
|
|
@@ -285,11 +283,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
285
283
|
const config = JSON.parse(await readFile(resolve(destPath, "project.json"), "utf8"));
|
|
286
284
|
const projectRoot = fwd(relative(root, destPath));
|
|
287
285
|
return Response.json({ root: projectRoot, config });
|
|
288
|
-
} catch (
|
|
289
|
-
return Response.json(
|
|
290
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
291
|
-
{ status: 500 },
|
|
292
|
-
);
|
|
286
|
+
} catch (e) {
|
|
287
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
293
288
|
}
|
|
294
289
|
}
|
|
295
290
|
|
|
@@ -300,15 +295,12 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
300
295
|
const absDir = isAbsolute(dir) ? dir : resolve(root, dir);
|
|
301
296
|
try {
|
|
302
297
|
assertAccessible(absDir, root, activeProjectRoot);
|
|
303
|
-
} catch (
|
|
304
|
-
return Response.json(
|
|
305
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
306
|
-
{ status: 400 },
|
|
307
|
-
);
|
|
298
|
+
} catch (e) {
|
|
299
|
+
return Response.json({ error: (e as Error).message }, { status: 400 });
|
|
308
300
|
}
|
|
309
301
|
|
|
310
302
|
/** Report a path relative to the active project root (or server root as fallback). */
|
|
311
|
-
const reportRelative = (
|
|
303
|
+
const reportRelative = (fp: string) => {
|
|
312
304
|
if (activeProjectRoot) {
|
|
313
305
|
const rel = relative(activeProjectRoot, fp);
|
|
314
306
|
if (!rel.startsWith("..")) return fwd(rel);
|
|
@@ -352,11 +344,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
352
344
|
});
|
|
353
345
|
}
|
|
354
346
|
return Response.json(files);
|
|
355
|
-
} catch (
|
|
356
|
-
return Response.json(
|
|
357
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
358
|
-
{ status: 500 },
|
|
359
|
-
);
|
|
347
|
+
} catch (e) {
|
|
348
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
360
349
|
}
|
|
361
350
|
}
|
|
362
351
|
|
|
@@ -366,11 +355,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
366
355
|
const scanRoot = isAbsolute(dir) ? dir : resolve(root, dir);
|
|
367
356
|
try {
|
|
368
357
|
assertAccessible(scanRoot, root, activeProjectRoot);
|
|
369
|
-
} catch (
|
|
370
|
-
return Response.json(
|
|
371
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
372
|
-
{ status: 400 },
|
|
373
|
-
);
|
|
358
|
+
} catch (e) {
|
|
359
|
+
return Response.json({ error: (e as Error).message }, { status: 400 });
|
|
374
360
|
}
|
|
375
361
|
try {
|
|
376
362
|
const glob = new Bun.Glob("**/*.{json,md}");
|
|
@@ -406,14 +392,16 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
406
392
|
// Shorthand: "key": "value" or "key": 0 etc.
|
|
407
393
|
if (typeof d !== "object") return true;
|
|
408
394
|
// Full form: skip computed/handler/prototype entries
|
|
409
|
-
|
|
395
|
+
const obj = d as Record<string, unknown>;
|
|
396
|
+
return !obj.$prototype && !obj.$handler && !obj.$compute;
|
|
410
397
|
})
|
|
411
398
|
.map(([name, d]) => {
|
|
412
|
-
if (typeof d !== "object") {
|
|
399
|
+
if (typeof d !== "object" || d === null) {
|
|
413
400
|
// Shorthand: infer type from value
|
|
414
401
|
return { name, type: typeof d, default: d };
|
|
415
402
|
}
|
|
416
|
-
|
|
403
|
+
const obj = d as Record<string, unknown>;
|
|
404
|
+
return { name, type: obj.type, default: obj.default, format: obj.format };
|
|
417
405
|
}),
|
|
418
406
|
hasElements: Array.isArray(content.$elements) && content.$elements.length > 0,
|
|
419
407
|
});
|
|
@@ -464,16 +452,14 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
464
452
|
source: "npm",
|
|
465
453
|
package: name,
|
|
466
454
|
description: decl.description || null,
|
|
467
|
-
props: (decl.attributes || []).map(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}),
|
|
474
|
-
),
|
|
455
|
+
props: (decl.attributes || []).map((a: Record<string, unknown>) => ({
|
|
456
|
+
name: a.name,
|
|
457
|
+
type: (a.type as Record<string, unknown> | undefined)?.text,
|
|
458
|
+
default: a.default,
|
|
459
|
+
description: a.description || null,
|
|
460
|
+
})),
|
|
475
461
|
members: (decl.members || []).filter(
|
|
476
|
-
(
|
|
462
|
+
(m: Record<string, unknown>) =>
|
|
477
463
|
m.kind === "field" && m.privacy !== "private",
|
|
478
464
|
),
|
|
479
465
|
slots: decl.slots || [],
|
|
@@ -490,11 +476,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
490
476
|
} catch {} // skip if no project package.json
|
|
491
477
|
|
|
492
478
|
return Response.json(components);
|
|
493
|
-
} catch (
|
|
494
|
-
return Response.json(
|
|
495
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
496
|
-
{ status: 500 },
|
|
497
|
-
);
|
|
479
|
+
} catch (e) {
|
|
480
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
498
481
|
}
|
|
499
482
|
}
|
|
500
483
|
|
|
@@ -538,11 +521,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
538
521
|
} catch {}
|
|
539
522
|
}
|
|
540
523
|
return Response.json(packages);
|
|
541
|
-
} catch (
|
|
542
|
-
return Response.json(
|
|
543
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
544
|
-
{ status: 500 },
|
|
545
|
-
);
|
|
524
|
+
} catch (e) {
|
|
525
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
546
526
|
}
|
|
547
527
|
}
|
|
548
528
|
|
|
@@ -567,11 +547,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
567
547
|
if (!existsSync(cemPath)) return Response.json({ cem: null });
|
|
568
548
|
const cem = JSON.parse(await readFile(cemPath, "utf8"));
|
|
569
549
|
return Response.json({ cem });
|
|
570
|
-
} catch (
|
|
571
|
-
return Response.json(
|
|
572
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
573
|
-
{ status: 500 },
|
|
574
|
-
);
|
|
550
|
+
} catch (e) {
|
|
551
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
575
552
|
}
|
|
576
553
|
}
|
|
577
554
|
|
|
@@ -596,11 +573,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
596
573
|
);
|
|
597
574
|
}
|
|
598
575
|
return Response.json({ ok: true });
|
|
599
|
-
} catch (
|
|
600
|
-
return Response.json(
|
|
601
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
602
|
-
{ status: 500 },
|
|
603
|
-
);
|
|
576
|
+
} catch (e) {
|
|
577
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
604
578
|
}
|
|
605
579
|
}
|
|
606
580
|
|
|
@@ -623,11 +597,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
623
597
|
);
|
|
624
598
|
}
|
|
625
599
|
return Response.json({ ok: true });
|
|
626
|
-
} catch (
|
|
627
|
-
return Response.json(
|
|
628
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
629
|
-
{ status: 500 },
|
|
630
|
-
);
|
|
600
|
+
} catch (e) {
|
|
601
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
631
602
|
}
|
|
632
603
|
}
|
|
633
604
|
|
|
@@ -638,21 +609,21 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
638
609
|
const abs = fp.startsWith("~") ? fp.replace("~", process.env.HOME || "") : fp;
|
|
639
610
|
try {
|
|
640
611
|
assertAccessible(abs, root, activeProjectRoot);
|
|
641
|
-
} catch (
|
|
642
|
-
return new Response(
|
|
612
|
+
} catch (e) {
|
|
613
|
+
return new Response((e as Error).message, { status: 400 });
|
|
643
614
|
}
|
|
644
615
|
try {
|
|
645
|
-
return Response.json(
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
616
|
+
return Response.json(
|
|
617
|
+
{
|
|
618
|
+
content: await readFile(abs, "utf8"),
|
|
619
|
+
path: fp,
|
|
620
|
+
},
|
|
621
|
+
{ headers: { "Cache-Control": "public, max-age=5" } },
|
|
622
|
+
);
|
|
623
|
+
} catch (e) {
|
|
624
|
+
return (e as NodeJS.ErrnoException).code === "ENOENT"
|
|
651
625
|
? new Response("Not found", { status: 404 })
|
|
652
|
-
: Response.json(
|
|
653
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
654
|
-
{ status: 500 },
|
|
655
|
-
);
|
|
626
|
+
: Response.json({ error: (e as Error).message }, { status: 500 });
|
|
656
627
|
}
|
|
657
628
|
}
|
|
658
629
|
|
|
@@ -663,18 +634,15 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
663
634
|
const abs = resolve(root, fp);
|
|
664
635
|
try {
|
|
665
636
|
assertAccessible(abs, root, activeProjectRoot);
|
|
666
|
-
} catch (
|
|
667
|
-
return new Response(
|
|
637
|
+
} catch (e) {
|
|
638
|
+
return new Response((e as Error).message, { status: 400 });
|
|
668
639
|
}
|
|
669
640
|
try {
|
|
670
641
|
await mkdir(dirname(abs), { recursive: true });
|
|
671
642
|
await writeFile(abs, await req.text(), "utf8");
|
|
672
643
|
return Response.json({ ok: true, path: fwd(relative(root, abs)) });
|
|
673
|
-
} catch (
|
|
674
|
-
return Response.json(
|
|
675
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
676
|
-
{ status: 500 },
|
|
677
|
-
);
|
|
644
|
+
} catch (e) {
|
|
645
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
678
646
|
}
|
|
679
647
|
}
|
|
680
648
|
|
|
@@ -685,19 +653,16 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
685
653
|
const abs = resolve(root, fp);
|
|
686
654
|
try {
|
|
687
655
|
assertAccessible(abs, root, activeProjectRoot);
|
|
688
|
-
} catch (
|
|
689
|
-
return new Response(
|
|
656
|
+
} catch (e) {
|
|
657
|
+
return new Response((e as Error).message, { status: 400 });
|
|
690
658
|
}
|
|
691
659
|
try {
|
|
692
660
|
await mkdir(dirname(abs), { recursive: true });
|
|
693
661
|
const buffer = await req.arrayBuffer();
|
|
694
662
|
await Bun.write(abs, new Uint8Array(buffer));
|
|
695
663
|
return Response.json({ ok: true, path: fwd(relative(root, abs)) });
|
|
696
|
-
} catch (
|
|
697
|
-
return Response.json(
|
|
698
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
699
|
-
{ status: 500 },
|
|
700
|
-
);
|
|
664
|
+
} catch (e) {
|
|
665
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
701
666
|
}
|
|
702
667
|
}
|
|
703
668
|
|
|
@@ -708,19 +673,16 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
708
673
|
const abs = resolve(root, fp);
|
|
709
674
|
try {
|
|
710
675
|
assertAccessible(abs, root, activeProjectRoot);
|
|
711
|
-
} catch (
|
|
712
|
-
return new Response(
|
|
676
|
+
} catch (e) {
|
|
677
|
+
return new Response((e as Error).message, { status: 400 });
|
|
713
678
|
}
|
|
714
679
|
try {
|
|
715
680
|
await unlink(abs);
|
|
716
681
|
return Response.json({ ok: true, path: fwd(relative(root, abs)) });
|
|
717
|
-
} catch (
|
|
718
|
-
return
|
|
682
|
+
} catch (e) {
|
|
683
|
+
return (e as NodeJS.ErrnoException).code === "ENOENT"
|
|
719
684
|
? new Response("Not found", { status: 404 })
|
|
720
|
-
: Response.json(
|
|
721
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
722
|
-
{ status: 500 },
|
|
723
|
-
);
|
|
685
|
+
: Response.json({ error: (e as Error).message }, { status: 500 });
|
|
724
686
|
}
|
|
725
687
|
}
|
|
726
688
|
|
|
@@ -739,8 +701,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
739
701
|
try {
|
|
740
702
|
assertAccessible(absFrom, root, activeProjectRoot);
|
|
741
703
|
assertAccessible(absTo, root, activeProjectRoot);
|
|
742
|
-
} catch (
|
|
743
|
-
return new Response(
|
|
704
|
+
} catch (e) {
|
|
705
|
+
return new Response((e as Error).message, { status: 400 });
|
|
744
706
|
}
|
|
745
707
|
try {
|
|
746
708
|
await mkdir(dirname(absTo), { recursive: true });
|
|
@@ -750,11 +712,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
750
712
|
from: fwd(relative(root, absFrom)),
|
|
751
713
|
to: fwd(relative(root, absTo)),
|
|
752
714
|
});
|
|
753
|
-
} catch (
|
|
754
|
-
return Response.json(
|
|
755
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
756
|
-
{ status: 500 },
|
|
757
|
-
);
|
|
715
|
+
} catch (e) {
|
|
716
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
758
717
|
}
|
|
759
718
|
}
|
|
760
719
|
|
|
@@ -782,11 +741,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
782
741
|
path: matches[0],
|
|
783
742
|
...(matches.length > 1 ? { alternatives: matches } : {}),
|
|
784
743
|
});
|
|
785
|
-
} catch (
|
|
786
|
-
return Response.json(
|
|
787
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
788
|
-
{ status: 500 },
|
|
789
|
-
);
|
|
744
|
+
} catch (e) {
|
|
745
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
790
746
|
}
|
|
791
747
|
}
|
|
792
748
|
|
|
@@ -823,10 +779,10 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
823
779
|
moduleAbsPath = serverRequire.resolve(src);
|
|
824
780
|
}
|
|
825
781
|
}
|
|
826
|
-
} catch (
|
|
782
|
+
} catch (e) {
|
|
827
783
|
return Response.json({
|
|
828
784
|
schema: null,
|
|
829
|
-
error:
|
|
785
|
+
error: (e as Error).message,
|
|
830
786
|
});
|
|
831
787
|
}
|
|
832
788
|
|
|
@@ -836,10 +792,10 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
836
792
|
const content = readFileSync(moduleAbsPath, "utf8");
|
|
837
793
|
const classDef = JSON.parse(content);
|
|
838
794
|
return Response.json({ schema: extractStudioSchema(classDef, moduleAbsPath) });
|
|
839
|
-
} catch (
|
|
795
|
+
} catch (e) {
|
|
840
796
|
return Response.json({
|
|
841
797
|
schema: null,
|
|
842
|
-
error:
|
|
798
|
+
error: (e as Error).message,
|
|
843
799
|
});
|
|
844
800
|
}
|
|
845
801
|
}
|
|
@@ -865,10 +821,10 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
865
821
|
return Response.json({ schema: null, error: `Export "${exportName}" not found` });
|
|
866
822
|
}
|
|
867
823
|
return Response.json({ schema: ExportedClass.schema ?? null });
|
|
868
|
-
} catch (
|
|
824
|
+
} catch (e) {
|
|
869
825
|
return Response.json({
|
|
870
826
|
schema: null,
|
|
871
|
-
error:
|
|
827
|
+
error: (e as Error).message,
|
|
872
828
|
});
|
|
873
829
|
}
|
|
874
830
|
}
|
|
@@ -879,7 +835,7 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
879
835
|
const cwd = activeProjectRoot || root;
|
|
880
836
|
const gitCmd = path.slice("/__studio/git/".length);
|
|
881
837
|
|
|
882
|
-
const runGit = async (
|
|
838
|
+
const runGit = async (args: string[]) => {
|
|
883
839
|
const proc = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
884
840
|
const exitCode = await proc.exited;
|
|
885
841
|
const stdout = await new Response(proc.stdout).text();
|
|
@@ -913,9 +869,12 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
913
869
|
runGit(["remote"]),
|
|
914
870
|
]);
|
|
915
871
|
const status = parseGitStatus(out);
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
872
|
+
const fullStatus = {
|
|
873
|
+
...status,
|
|
874
|
+
isRepo: true,
|
|
875
|
+
remotes: remotesOut.trim().split("\n").filter(Boolean),
|
|
876
|
+
};
|
|
877
|
+
return Response.json(fullStatus);
|
|
919
878
|
}
|
|
920
879
|
|
|
921
880
|
if (gitCmd === "init" && req.method === "POST") {
|
|
@@ -991,11 +950,11 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
991
950
|
}
|
|
992
951
|
|
|
993
952
|
if (gitCmd === "push" && req.method === "POST") {
|
|
994
|
-
let body = {};
|
|
953
|
+
let body: Record<string, unknown> = {};
|
|
995
954
|
try {
|
|
996
955
|
body = await req.json();
|
|
997
956
|
} catch {}
|
|
998
|
-
const { setUpstream } =
|
|
957
|
+
const { setUpstream } = body as { setUpstream?: boolean };
|
|
999
958
|
if (setUpstream) {
|
|
1000
959
|
const branch = (await runGit(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
1001
960
|
await runGit(["push", "-u", "origin", branch]);
|
|
@@ -1075,11 +1034,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
1075
1034
|
if (exitCode !== 0) throw new Error(stderr || `git clone exited with ${exitCode}`);
|
|
1076
1035
|
return Response.json({ ok: true, root: dest });
|
|
1077
1036
|
}
|
|
1078
|
-
} catch (
|
|
1079
|
-
return Response.json(
|
|
1080
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
1081
|
-
{ status: 500 },
|
|
1082
|
-
);
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
return Response.json({ error: (e as Error).message }, { status: 500 });
|
|
1083
1039
|
}
|
|
1084
1040
|
}
|
|
1085
1041
|
|
|
@@ -1098,8 +1054,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
1098
1054
|
systemPrompt: body.systemPrompt,
|
|
1099
1055
|
});
|
|
1100
1056
|
return Response.json(result);
|
|
1101
|
-
} catch (
|
|
1102
|
-
return Response.json({ error: e.message }, { status: 500 });
|
|
1057
|
+
} catch (e) {
|
|
1058
|
+
return Response.json({ error: e instanceof Error ? e.message : String(e) }, { status: 500 });
|
|
1103
1059
|
}
|
|
1104
1060
|
}
|
|
1105
1061
|
|
|
@@ -1122,8 +1078,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
1122
1078
|
const body = await req.json();
|
|
1123
1079
|
claude.sendMessage(id, body.message);
|
|
1124
1080
|
return Response.json({ ok: true });
|
|
1125
|
-
} catch (
|
|
1126
|
-
return Response.json({ error: e.message }, { status: 500 });
|
|
1081
|
+
} catch (e) {
|
|
1082
|
+
return Response.json({ error: e instanceof Error ? e.message : String(e) }, { status: 500 });
|
|
1127
1083
|
}
|
|
1128
1084
|
}
|
|
1129
1085
|
|
|
@@ -1162,7 +1118,7 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
1162
1118
|
* required: string[];
|
|
1163
1119
|
* }}
|
|
1164
1120
|
*/
|
|
1165
|
-
function extractStudioSchema(classDef, classJsonPath) {
|
|
1121
|
+
function extractStudioSchema(classDef: ClassJsonDef, classJsonPath: string) {
|
|
1166
1122
|
// If extends.$ref points to a parent, recursively merge
|
|
1167
1123
|
let parentSchema = null;
|
|
1168
1124
|
if (classDef.extends && typeof classDef.extends === "object" && classDef.extends.$ref) {
|
|
@@ -1178,10 +1134,8 @@ function extractStudioSchema(classDef, classJsonPath) {
|
|
|
1178
1134
|
|
|
1179
1135
|
const params = classDef.$defs?.parameters ?? {};
|
|
1180
1136
|
const fields = classDef.$defs?.fields ?? {};
|
|
1181
|
-
|
|
1182
|
-
const
|
|
1183
|
-
/** @type {string[]} */
|
|
1184
|
-
const required = [];
|
|
1137
|
+
const properties: Record<string, Record<string, unknown>> = {};
|
|
1138
|
+
const required: string[] = [];
|
|
1185
1139
|
|
|
1186
1140
|
// Start with parent properties (child overrides)
|
|
1187
1141
|
if (parentSchema?.properties) {
|
|
@@ -1194,8 +1148,7 @@ function extractStudioSchema(classDef, classJsonPath) {
|
|
|
1194
1148
|
// Build properties from parameters (constructor config surface)
|
|
1195
1149
|
for (const [key, param] of Object.entries(params)) {
|
|
1196
1150
|
const id = param.identifier ?? key;
|
|
1197
|
-
|
|
1198
|
-
const prop = {};
|
|
1151
|
+
const prop: Record<string, unknown> = {};
|
|
1199
1152
|
if (param.type && typeof param.type === "object") Object.assign(prop, param.type);
|
|
1200
1153
|
if (param.description) prop.description = param.description;
|
|
1201
1154
|
if (param.examples) prop.examples = param.examples;
|
|
@@ -1208,8 +1161,7 @@ function extractStudioSchema(classDef, classJsonPath) {
|
|
|
1208
1161
|
if (field.role !== "field") continue;
|
|
1209
1162
|
if (field.access === "private") continue;
|
|
1210
1163
|
const id = field.identifier ?? key;
|
|
1211
|
-
|
|
1212
|
-
const prop = {};
|
|
1164
|
+
const prop: Record<string, unknown> = {};
|
|
1213
1165
|
if (field.type && typeof field.type === "object") Object.assign(prop, field.type);
|
|
1214
1166
|
if (field.description) prop.description = field.description;
|
|
1215
1167
|
if (field.default !== undefined) prop.default = field.default;
|
|
@@ -1221,8 +1173,7 @@ function extractStudioSchema(classDef, classJsonPath) {
|
|
|
1221
1173
|
|
|
1222
1174
|
// Determine required from constructor parameters that have no default
|
|
1223
1175
|
const ctorParams = classDef.$defs?.constructor?.parameters ?? [];
|
|
1224
|
-
|
|
1225
|
-
const requiredSet = new Set(required);
|
|
1176
|
+
const requiredSet: Set<string> = new Set(required);
|
|
1226
1177
|
for (const p of ctorParams) {
|
|
1227
1178
|
const name = p.$ref ? p.$ref.split("/").pop() : (p.identifier ?? p.name);
|
|
1228
1179
|
if (name && properties[name] && properties[name].default === undefined) {
|
|
@@ -1230,9 +1181,12 @@ function extractStudioSchema(classDef, classJsonPath) {
|
|
|
1230
1181
|
}
|
|
1231
1182
|
}
|
|
1232
1183
|
|
|
1184
|
+
const resolveMethod = classDef.$defs?.methods?.resolve;
|
|
1185
|
+
|
|
1233
1186
|
return {
|
|
1234
1187
|
description: classDef.description ?? classDef.title,
|
|
1235
1188
|
properties,
|
|
1236
1189
|
required: [...requiredSet],
|
|
1190
|
+
...(resolveMethod?.returns ? { returns: resolveMethod.returns } : {}),
|
|
1237
1191
|
};
|
|
1238
1192
|
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface BuildEntry {
|
|
2
|
+
entrypoints: string[];
|
|
3
|
+
outdir: string;
|
|
4
|
+
match?: ((path: string) => boolean) | RegExp;
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ClassJsonParam {
|
|
9
|
+
$ref?: string;
|
|
10
|
+
identifier?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ClassJsonField {
|
|
15
|
+
role?: string;
|
|
16
|
+
access?: string;
|
|
17
|
+
scope?: string;
|
|
18
|
+
identifier?: string;
|
|
19
|
+
initializer?: unknown;
|
|
20
|
+
default?: unknown;
|
|
21
|
+
type?: Record<string, unknown>;
|
|
22
|
+
description?: string;
|
|
23
|
+
examples?: unknown[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ClassJsonMethod {
|
|
27
|
+
identifier?: string;
|
|
28
|
+
role?: string;
|
|
29
|
+
scope?: string;
|
|
30
|
+
body?: string | string[];
|
|
31
|
+
getter?: { body: string };
|
|
32
|
+
setter?: { parameters?: ClassJsonParam[]; body: string };
|
|
33
|
+
parameters?: ClassJsonParam[];
|
|
34
|
+
returns?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ClassJsonParameterDef {
|
|
38
|
+
identifier?: string;
|
|
39
|
+
type?: Record<string, unknown>;
|
|
40
|
+
format?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
examples?: unknown[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ClassJsonDef {
|
|
46
|
+
title?: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
$implementation?: string;
|
|
49
|
+
extends?: { $ref?: string };
|
|
50
|
+
$defs?: {
|
|
51
|
+
fields?: Record<string, ClassJsonField>;
|
|
52
|
+
constructor?: {
|
|
53
|
+
role?: string;
|
|
54
|
+
$prototype?: string;
|
|
55
|
+
body?: string | string[];
|
|
56
|
+
parameters?: ClassJsonParam[];
|
|
57
|
+
};
|
|
58
|
+
methods?: Record<string, ClassJsonMethod>;
|
|
59
|
+
parameters?: Record<string, ClassJsonParameterDef>;
|
|
60
|
+
};
|
|
61
|
+
}
|