@jxsuite/server 0.21.4 → 0.22.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 +5 -5
- package/src/resolve.js +55 -7
- package/src/server.js +1 -1
- package/src/studio-api.js +22 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Jx development server with live reload, server-side proxy, and studio integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"test:coverage": "bun test --isolate --coverage"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
29
|
-
"@anthropic-ai/sdk": "^0.
|
|
30
|
-
"@jxsuite/compiler": "^0.
|
|
28
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.156",
|
|
29
|
+
"@anthropic-ai/sdk": "^0.100.1",
|
|
30
|
+
"@jxsuite/compiler": "^0.22.0",
|
|
31
31
|
"@jxsuite/create": "^0.19.0",
|
|
32
|
-
"@jxsuite/parser": "^0.
|
|
32
|
+
"@jxsuite/parser": "^0.22.0",
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
34
34
|
"chokidar": "^5.0.0",
|
|
35
35
|
"zod": "^4.4.3"
|
package/src/resolve.js
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
/** Resolve.js — Generic $src module proxy + timing: "server" function proxy */
|
|
2
2
|
|
|
3
3
|
import { resolve, relative, dirname } from "node:path";
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
4
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { loadContentTypes } from "@jxsuite/compiler/content-loader";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Lazy-load project context (project.json + content types) for class instantiation.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} projectRoot
|
|
12
|
+
* @returns {Promise<{
|
|
13
|
+
* config: Record<string, unknown>;
|
|
14
|
+
* contentTypes: Map<string, unknown[]>;
|
|
15
|
+
* root: string;
|
|
16
|
+
* } | null>}
|
|
17
|
+
*/
|
|
18
|
+
async function loadProjectContext(projectRoot) {
|
|
19
|
+
const projectJsonPath = resolve(projectRoot, "project.json");
|
|
20
|
+
if (!existsSync(projectJsonPath)) return null;
|
|
21
|
+
try {
|
|
22
|
+
const config = JSON.parse(readFileSync(projectJsonPath, "utf8"));
|
|
23
|
+
const contentTypes = config.contentTypes
|
|
24
|
+
? await loadContentTypes(projectRoot, config)
|
|
25
|
+
: new Map();
|
|
26
|
+
return { config, contentTypes, root: projectRoot };
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
5
31
|
|
|
6
32
|
/**
|
|
7
33
|
* Handle POST /**jx_resolve** — proxy $prototype + $src entries.
|
|
8
34
|
*
|
|
9
35
|
* @param {Request} req
|
|
10
36
|
* @param {string} root
|
|
37
|
+
* @param {string | null} [activeProjectRoot]
|
|
11
38
|
*/
|
|
12
|
-
export async function handleResolve(req, root) {
|
|
39
|
+
export async function handleResolve(req, root, activeProjectRoot = null) {
|
|
13
40
|
let body;
|
|
14
41
|
try {
|
|
15
42
|
body = await req.json();
|
|
@@ -22,12 +49,25 @@ export async function handleResolve(req, root) {
|
|
|
22
49
|
|
|
23
50
|
let moduleAbsPath;
|
|
24
51
|
try {
|
|
25
|
-
if ($
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
52
|
+
if ($src.startsWith("./") || $src.startsWith("../")) {
|
|
53
|
+
// Relative path
|
|
54
|
+
if ($base) {
|
|
55
|
+
const docUrlPath = new URL($base).pathname;
|
|
56
|
+
const docDir = docUrlPath.slice(0, docUrlPath.lastIndexOf("/") + 1);
|
|
57
|
+
moduleAbsPath = resolve(resolve(root, "." + docDir), $src);
|
|
58
|
+
} else {
|
|
59
|
+
moduleAbsPath = resolve(activeProjectRoot || root, $src);
|
|
60
|
+
}
|
|
29
61
|
} else {
|
|
30
|
-
|
|
62
|
+
// npm/bare specifier — use createRequire from project root, fall back to server package
|
|
63
|
+
const projectRoot = activeProjectRoot || root;
|
|
64
|
+
const projRequire = createRequire(resolve(projectRoot, "package.json"));
|
|
65
|
+
try {
|
|
66
|
+
moduleAbsPath = projRequire.resolve($src);
|
|
67
|
+
} catch {
|
|
68
|
+
const serverRequire = createRequire(import.meta.url);
|
|
69
|
+
moduleAbsPath = serverRequire.resolve($src);
|
|
70
|
+
}
|
|
31
71
|
}
|
|
32
72
|
} catch (/** @type {unknown} */ e) {
|
|
33
73
|
return new Response(
|
|
@@ -54,6 +94,14 @@ export async function handleResolve(req, root) {
|
|
|
54
94
|
const content = readFileSync(moduleAbsPath, "utf8");
|
|
55
95
|
const classDef = JSON.parse(content);
|
|
56
96
|
|
|
97
|
+
// Inject project context for classes that need it
|
|
98
|
+
const projectRoot = activeProjectRoot || root;
|
|
99
|
+
const projectCtx = await loadProjectContext(projectRoot);
|
|
100
|
+
if (projectCtx) {
|
|
101
|
+
config._project = projectCtx;
|
|
102
|
+
}
|
|
103
|
+
config._document = { route: { _pathParams: {} }, state: {} };
|
|
104
|
+
|
|
57
105
|
if (classDef.$implementation) {
|
|
58
106
|
// Hybrid mode: redirect to the JS implementation
|
|
59
107
|
const implPath = resolve(dirname(moduleAbsPath), classDef.$implementation);
|
package/src/server.js
CHANGED
|
@@ -196,7 +196,7 @@ export async function createDevServer(options) {
|
|
|
196
196
|
|
|
197
197
|
// $prototype + $src proxy
|
|
198
198
|
if (path === "/__jx_resolve__" && req.method === "POST") {
|
|
199
|
-
return handleResolve(req, absRoot);
|
|
199
|
+
return handleResolve(req, absRoot, activeProjectRoot);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
// timing: "server" function proxy
|
package/src/studio-api.js
CHANGED
|
@@ -11,7 +11,6 @@ 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 { loadContentTypes, queryContentType, findEntry } from "@jxsuite/compiler/content-loader";
|
|
15
14
|
import * as claude from "./claude-session.js";
|
|
16
15
|
|
|
17
16
|
/** Normalise a path to forward slashes (Windows `path` module returns backslashes). */
|
|
@@ -800,12 +799,29 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
800
799
|
|
|
801
800
|
let moduleAbsPath;
|
|
802
801
|
try {
|
|
803
|
-
if (
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
802
|
+
if (src.startsWith("./") || src.startsWith("../")) {
|
|
803
|
+
// Relative path
|
|
804
|
+
if (base) {
|
|
805
|
+
const docUrlPath = new URL(base).pathname;
|
|
806
|
+
const docDir = docUrlPath.slice(0, docUrlPath.lastIndexOf("/") + 1);
|
|
807
|
+
moduleAbsPath = resolve(resolve(root, "." + docDir), src);
|
|
808
|
+
} else {
|
|
809
|
+
moduleAbsPath = resolve(activeProjectRoot || root, src);
|
|
810
|
+
if (!existsSync(moduleAbsPath) && activeProjectRoot) {
|
|
811
|
+
moduleAbsPath = resolve(root, src);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
807
814
|
} else {
|
|
808
|
-
|
|
815
|
+
// npm/bare specifier — use createRequire from project root, fall back to server package
|
|
816
|
+
const projectRoot = activeProjectRoot || root;
|
|
817
|
+
const { createRequire } = await import("node:module");
|
|
818
|
+
const projRequire = createRequire(resolve(projectRoot, "package.json"));
|
|
819
|
+
try {
|
|
820
|
+
moduleAbsPath = projRequire.resolve(src);
|
|
821
|
+
} catch {
|
|
822
|
+
const serverRequire = createRequire(import.meta.url);
|
|
823
|
+
moduleAbsPath = serverRequire.resolve(src);
|
|
824
|
+
}
|
|
809
825
|
}
|
|
810
826
|
} catch (/** @type {unknown} */ e) {
|
|
811
827
|
return Response.json({
|
|
@@ -1067,50 +1083,6 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
|
|
|
1067
1083
|
}
|
|
1068
1084
|
}
|
|
1069
1085
|
|
|
1070
|
-
// ─── Content Type Resolution ──────────────────────────────────────────────
|
|
1071
|
-
|
|
1072
|
-
if (path === "/__studio/content" && req.method === "POST") {
|
|
1073
|
-
try {
|
|
1074
|
-
const body = await req.json();
|
|
1075
|
-
const { $prototype, contentType, id, filter, sort, limit } = body;
|
|
1076
|
-
const projectRoot = activeProjectRoot || root;
|
|
1077
|
-
|
|
1078
|
-
let projectConfig;
|
|
1079
|
-
try {
|
|
1080
|
-
projectConfig = JSON.parse(readFileSync(resolve(projectRoot, "project.json"), "utf8"));
|
|
1081
|
-
} catch {
|
|
1082
|
-
return Response.json({ error: "No project.json found" }, { status: 404 });
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
if (!projectConfig.contentTypes?.[contentType]) {
|
|
1086
|
-
return Response.json({ error: `Content type "${contentType}" not found` }, { status: 404 });
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
const contentTypes = await loadContentTypes(projectRoot, projectConfig);
|
|
1090
|
-
const entries = contentTypes.get(contentType);
|
|
1091
|
-
if (!entries || entries.length === 0) {
|
|
1092
|
-
return Response.json($prototype === "ContentCollection" ? [] : null);
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
if ($prototype === "ContentCollection") {
|
|
1096
|
-
const result = queryContentType(entries, { filter, sort, limit });
|
|
1097
|
-
return Response.json(result);
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
if ($prototype === "ContentEntry") {
|
|
1101
|
-
const entry = findEntry(entries, id);
|
|
1102
|
-
return Response.json(entry ?? null);
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
return Response.json({ error: "Invalid $prototype" }, { status: 400 });
|
|
1106
|
-
} catch (/** @type {unknown} */ e) {
|
|
1107
|
-
return Response.json(
|
|
1108
|
-
{ error: /** @type {{ message?: string }} */ (e).message },
|
|
1109
|
-
{ status: 500 },
|
|
1110
|
-
);
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
1086
|
// ─── AI Assistant ─────────────────────────────────────────────────────────
|
|
1115
1087
|
|
|
1116
1088
|
if (path === "/__studio/ai/auth-status" && req.method === "GET") {
|