@jxsuite/server 0.28.2 → 0.28.4
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/build.ts +18 -7
- package/src/claude-session.ts +50 -41
- package/src/code-api.ts +34 -24
- package/src/resolve.ts +54 -34
- package/src/server.ts +59 -31
- package/src/studio-api.ts +353 -221
- package/src/watch.ts +26 -16
package/src/server.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* proxying, and studio filesystem integration as a single createDevServer() call.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { join, resolve } from "node:path";
|
|
17
17
|
import { buildAll } from "./build.ts";
|
|
18
18
|
import { createWatcher, injectSSE } from "./watch.ts";
|
|
19
19
|
import { handleResolve, handleServerFunction } from "./resolve.ts";
|
|
@@ -30,15 +30,18 @@ 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
|
-
function resolveNpmPath(
|
|
33
|
+
function resolveNpmPath(rootDir: string, urlPath: string) {
|
|
34
|
+
let root = rootDir;
|
|
34
35
|
let segments = urlPath.split("/").filter(Boolean);
|
|
35
36
|
|
|
36
37
|
// If "node_modules" appears in the path, use everything before it as a subdirectory
|
|
37
|
-
//
|
|
38
|
-
//
|
|
38
|
+
// Prefix and everything after as the package specifier.
|
|
39
|
+
// E.g. /examples/demo/node_modules/@scope/pkg → root=root/examples/demo, pkg=@scope/pkg
|
|
39
40
|
const nmIdx = segments.indexOf("node_modules");
|
|
40
|
-
if (nmIdx
|
|
41
|
-
if (nmIdx > 0)
|
|
41
|
+
if (nmIdx !== -1) {
|
|
42
|
+
if (nmIdx > 0) {
|
|
43
|
+
root = join(root, ...segments.slice(0, nmIdx));
|
|
44
|
+
}
|
|
42
45
|
segments = segments.slice(nmIdx + 1);
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -53,11 +56,13 @@ function resolveNpmPath(root: string, urlPath: string) {
|
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
let pkgDir
|
|
57
|
-
let subpath
|
|
59
|
+
let pkgDir = "";
|
|
60
|
+
let subpath = "";
|
|
58
61
|
|
|
59
62
|
if (isScoped) {
|
|
60
|
-
if (start < 0 || start + 1 >= segments.length)
|
|
63
|
+
if (start < 0 || start + 1 >= segments.length) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
61
66
|
const scope = segments[start];
|
|
62
67
|
const pkg = segments[start + 1];
|
|
63
68
|
subpath = segments.slice(start + 2).join("/");
|
|
@@ -73,11 +78,15 @@ function resolveNpmPath(root: string, urlPath: string) {
|
|
|
73
78
|
break;
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
|
-
if (start < 0)
|
|
81
|
+
if (start < 0) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
const pkgJsonPath = join(pkgDir, "package.json");
|
|
80
|
-
if (!existsSync(pkgJsonPath))
|
|
87
|
+
if (!existsSync(pkgJsonPath)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
81
90
|
|
|
82
91
|
// If there's a subpath, check package.json exports first
|
|
83
92
|
if (subpath) {
|
|
@@ -86,19 +95,25 @@ function resolveNpmPath(root: string, urlPath: string) {
|
|
|
86
95
|
const exportKey = `./${subpath}`;
|
|
87
96
|
if (pkgJson.exports && pkgJson.exports[exportKey]) {
|
|
88
97
|
const mapped = join(pkgDir, pkgJson.exports[exportKey]);
|
|
89
|
-
if (existsSync(mapped))
|
|
98
|
+
if (existsSync(mapped)) {
|
|
99
|
+
return mapped;
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
} catch {}
|
|
92
103
|
// Fall back to direct path
|
|
93
104
|
const direct = join(pkgDir, subpath);
|
|
94
|
-
if (existsSync(direct))
|
|
105
|
+
if (existsSync(direct)) {
|
|
106
|
+
return direct;
|
|
107
|
+
}
|
|
95
108
|
// CEM-relative: subpath may be relative to the custom elements manifest directory
|
|
96
109
|
try {
|
|
97
110
|
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
98
111
|
if (pkgJson.customElements) {
|
|
99
112
|
const cemDir = pkgJson.customElements.replace(/\/[^/]+$/, "");
|
|
100
113
|
const cemRelative = join(pkgDir, cemDir, subpath);
|
|
101
|
-
if (existsSync(cemRelative))
|
|
114
|
+
if (existsSync(cemRelative)) {
|
|
115
|
+
return cemRelative;
|
|
116
|
+
}
|
|
102
117
|
}
|
|
103
118
|
} catch {}
|
|
104
119
|
}
|
|
@@ -113,7 +128,9 @@ function resolveNpmPath(root: string, urlPath: string) {
|
|
|
113
128
|
pkgJson.main;
|
|
114
129
|
if (entry && typeof entry === "string") {
|
|
115
130
|
const resolved = join(pkgDir, entry);
|
|
116
|
-
if (existsSync(resolved))
|
|
131
|
+
if (existsSync(resolved)) {
|
|
132
|
+
return resolved;
|
|
133
|
+
}
|
|
117
134
|
}
|
|
118
135
|
} catch {}
|
|
119
136
|
|
|
@@ -162,7 +179,9 @@ export async function createDevServer(options: {
|
|
|
162
179
|
middleware,
|
|
163
180
|
} = options;
|
|
164
181
|
|
|
165
|
-
if (!root)
|
|
182
|
+
if (!root) {
|
|
183
|
+
throw new Error("@jxsuite/server: root is required");
|
|
184
|
+
}
|
|
166
185
|
const absRoot = resolve(root);
|
|
167
186
|
|
|
168
187
|
// ─── Build pipeline ─────────────────────────────────────────────────────────
|
|
@@ -177,11 +196,11 @@ export async function createDevServer(options: {
|
|
|
177
196
|
if (watch !== false) {
|
|
178
197
|
const watchOpts = typeof watch === "object" ? watch : {};
|
|
179
198
|
const watcher = createWatcher(absRoot, builds, watchOpts);
|
|
180
|
-
handleSSE = watcher
|
|
199
|
+
({ handleSSE } = watcher);
|
|
181
200
|
}
|
|
182
201
|
|
|
183
202
|
// Bundle cache for npm packages (bare specifier → bundled JS)
|
|
184
|
-
const bundleCache
|
|
203
|
+
const bundleCache = new Map<string, string>();
|
|
185
204
|
|
|
186
205
|
// Active studio project root (set via /__studio/activate, used for static file fallback)
|
|
187
206
|
let activeProjectRoot: string | null = null;
|
|
@@ -189,13 +208,14 @@ export async function createDevServer(options: {
|
|
|
189
208
|
// ─── HTTP server ────────────────────────────────────────────────────────────
|
|
190
209
|
|
|
191
210
|
const server = Bun.serve({
|
|
192
|
-
port,
|
|
193
|
-
|
|
194
211
|
async fetch(req) {
|
|
195
212
|
const url = new URL(req.url);
|
|
196
213
|
let path = decodeURIComponent(url.pathname);
|
|
197
|
-
if (path.endsWith("/"))
|
|
198
|
-
|
|
214
|
+
if (path.endsWith("/")) {
|
|
215
|
+
path += "index.html";
|
|
216
|
+
} else if (path === "") {
|
|
217
|
+
path = "/index.html";
|
|
218
|
+
}
|
|
199
219
|
|
|
200
220
|
// SSE live reload
|
|
201
221
|
if (handleSSE && path === "/__reload") {
|
|
@@ -207,7 +227,7 @@ export async function createDevServer(options: {
|
|
|
207
227
|
return handleResolve(req, absRoot, activeProjectRoot);
|
|
208
228
|
}
|
|
209
229
|
|
|
210
|
-
//
|
|
230
|
+
// Timing: "server" function proxy
|
|
211
231
|
if (path === "/__jx_server__" && req.method === "POST") {
|
|
212
232
|
return handleServerFunction(req, absRoot);
|
|
213
233
|
}
|
|
@@ -224,16 +244,22 @@ export async function createDevServer(options: {
|
|
|
224
244
|
}
|
|
225
245
|
|
|
226
246
|
const codeRes = await handleCodeApi(req, url);
|
|
227
|
-
if (codeRes)
|
|
247
|
+
if (codeRes) {
|
|
248
|
+
return codeRes;
|
|
249
|
+
}
|
|
228
250
|
|
|
229
251
|
const res = await handleStudioApi(req, url, absRoot, activeProjectRoot);
|
|
230
|
-
if (res)
|
|
252
|
+
if (res) {
|
|
253
|
+
return res;
|
|
254
|
+
}
|
|
231
255
|
}
|
|
232
256
|
|
|
233
257
|
// Custom middleware
|
|
234
258
|
if (middleware) {
|
|
235
259
|
const res = await middleware(req, url);
|
|
236
|
-
if (res)
|
|
260
|
+
if (res) {
|
|
261
|
+
return res;
|
|
262
|
+
}
|
|
237
263
|
}
|
|
238
264
|
|
|
239
265
|
// Static files
|
|
@@ -248,16 +274,16 @@ export async function createDevServer(options: {
|
|
|
248
274
|
}
|
|
249
275
|
}
|
|
250
276
|
|
|
251
|
-
const file = Bun.file(resolve(absRoot,
|
|
277
|
+
const file = Bun.file(resolve(absRoot, `.${path}`));
|
|
252
278
|
if (!(await file.exists())) {
|
|
253
279
|
// Try resolving relative to active studio project root
|
|
254
280
|
if (activeProjectRoot) {
|
|
255
|
-
const projectFile = Bun.file(resolve(activeProjectRoot,
|
|
281
|
+
const projectFile = Bun.file(resolve(activeProjectRoot, `.${path}`));
|
|
256
282
|
if (await projectFile.exists()) {
|
|
257
283
|
return new Response(projectFile);
|
|
258
284
|
}
|
|
259
285
|
// Mirror production: public/ contents are served at root
|
|
260
|
-
const publicFile = Bun.file(resolve(activeProjectRoot, "public",
|
|
286
|
+
const publicFile = Bun.file(resolve(activeProjectRoot, "public", `.${path}`));
|
|
261
287
|
if (await publicFile.exists()) {
|
|
262
288
|
return new Response(publicFile);
|
|
263
289
|
}
|
|
@@ -278,8 +304,8 @@ export async function createDevServer(options: {
|
|
|
278
304
|
if (result.success && result.outputs.length > 0) {
|
|
279
305
|
bundleCache.set(cacheKey, await result.outputs[0].text());
|
|
280
306
|
}
|
|
281
|
-
} catch (
|
|
282
|
-
console.error("Bundle failed for", resolved,
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error("Bundle failed for", resolved, error);
|
|
283
309
|
}
|
|
284
310
|
}
|
|
285
311
|
const bundled = bundleCache.get(cacheKey);
|
|
@@ -303,6 +329,8 @@ export async function createDevServer(options: {
|
|
|
303
329
|
|
|
304
330
|
return new Response(file);
|
|
305
331
|
},
|
|
332
|
+
|
|
333
|
+
port,
|
|
306
334
|
});
|
|
307
335
|
|
|
308
336
|
console.log(`\n@jxsuite/server listening on http://localhost:${server.port}`);
|