@jxsuite/server 0.28.5 → 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 +7 -7
- package/src/claude-session.ts +2 -2
- package/src/code-api.ts +11 -6
- package/src/resolve.ts +41 -12
- package/src/server.ts +20 -11
- package/src/studio-api.ts +174 -81
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/server",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
30
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.175",
|
|
31
31
|
"@anthropic-ai/sdk": "^0.104.1",
|
|
32
|
-
"@jxsuite/compiler": "^0.
|
|
33
|
-
"@jxsuite/create": "^0.
|
|
34
|
-
"@jxsuite/parser": "^0.
|
|
35
|
-
"@jxsuite/runtime": "^0.
|
|
36
|
-
"@jxsuite/schema": "^0.
|
|
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"
|
package/src/claude-session.ts
CHANGED
|
@@ -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]
|
|
51
|
+
while (end > 0 && lines[end]!.trim() === "") {
|
|
47
52
|
end -= 1;
|
|
48
53
|
}
|
|
49
|
-
if (lines[end]
|
|
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
|
|
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 =
|
|
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
|
|
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]
|
|
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
|
-
|
|
97
|
-
|
|
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]
|
|
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]
|
|
115
|
-
behind = Number.parseInt(m[2]
|
|
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
|
|
120
|
-
const
|
|
121
|
-
const
|
|
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
|
-
|
|
427
|
+
...(adapter !== undefined ? { adapter } : {}),
|
|
428
|
+
...(description !== undefined ? { description } : {}),
|
|
429
|
+
...(siteUrl !== undefined ? { url: siteUrl } : {}),
|
|
346
430
|
});
|
|
347
431
|
|
|
348
|
-
const config = JSON.parse(
|
|
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
|
|
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
|
|
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
|
|
635
|
+
props: (decl.attributes || []).map((a) => ({
|
|
549
636
|
default: a.default,
|
|
550
637
|
description: a.description || null,
|
|
551
638
|
name: a.name,
|
|
552
|
-
type:
|
|
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
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
1290
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|