@jxrstudios/jxr 1.0.4 → 1.0.5
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/README.md +6 -2
- package/bin/jxr.js +57 -83
- package/dist/deployer.d.ts +8 -12
- package/dist/deployer.d.ts.map +1 -1
- package/dist/deployer.js +69 -106
- package/dist/deployer.js.map +1 -1
- package/dist/enhanced-transpiler.d.ts +36 -0
- package/dist/enhanced-transpiler.d.ts.map +1 -0
- package/dist/enhanced-transpiler.js +272 -0
- package/dist/enhanced-transpiler.js.map +1 -0
- package/dist/entry-point-detection.d.ts +22 -0
- package/dist/entry-point-detection.d.ts.map +1 -0
- package/dist/entry-point-detection.js +415 -0
- package/dist/entry-point-detection.js.map +1 -0
- package/dist/index.d.ts +19 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1017 -126
- package/dist/index.js.map +1 -1
- package/dist/jxr-server-manager.d.ts +32 -0
- package/dist/jxr-server-manager.d.ts.map +1 -0
- package/dist/jxr-server-manager.js +353 -0
- package/dist/jxr-server-manager.js.map +1 -0
- package/dist/runtime.d.ts +9 -9
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +3 -3
- package/package.json +9 -2
- package/src/deployer.ts +231 -0
- package/src/enhanced-transpiler.ts +331 -0
- package/src/entry-point-detection.ts +470 -0
- package/src/index.ts +63 -0
- package/src/jxr-server-manager.ts +410 -0
- package/src/module-resolver.ts +520 -0
- package/src/moq-transport.ts +267 -0
- package/src/runtime.ts +188 -0
- package/src/web-crypto.ts +279 -0
- package/src/worker-pool.ts +321 -0
- package/zzz_react_template/App.tsx +160 -0
- package/zzz_react_template/index.css +16 -0
- package/zzz_react_template/index.html +12 -0
- package/zzz_react_template/main.tsx +10 -0
- package/zzz_react_template/package.json +25 -0
package/dist/index.js
CHANGED
|
@@ -548,28 +548,28 @@ var VirtualFS = class {
|
|
|
548
548
|
}
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
|
-
write(
|
|
552
|
-
const existing = this.files.get(
|
|
553
|
-
const language = this.detectLanguage(
|
|
551
|
+
write(path3, content) {
|
|
552
|
+
const existing = this.files.get(path3);
|
|
553
|
+
const language = this.detectLanguage(path3);
|
|
554
554
|
const file = {
|
|
555
|
-
path,
|
|
555
|
+
path: path3,
|
|
556
556
|
content,
|
|
557
557
|
language,
|
|
558
558
|
lastModified: Date.now(),
|
|
559
559
|
size: new TextEncoder().encode(content).byteLength,
|
|
560
560
|
dirty: true
|
|
561
561
|
};
|
|
562
|
-
this.files.set(
|
|
562
|
+
this.files.set(path3, file);
|
|
563
563
|
this.emit(file, existing ? "update" : "create");
|
|
564
564
|
return file;
|
|
565
565
|
}
|
|
566
|
-
read(
|
|
567
|
-
return this.files.get(
|
|
566
|
+
read(path3) {
|
|
567
|
+
return this.files.get(path3) ?? null;
|
|
568
568
|
}
|
|
569
|
-
delete(
|
|
570
|
-
const file = this.files.get(
|
|
569
|
+
delete(path3) {
|
|
570
|
+
const file = this.files.get(path3);
|
|
571
571
|
if (!file) return false;
|
|
572
|
-
this.files.delete(
|
|
572
|
+
this.files.delete(path3);
|
|
573
573
|
this.emit(file, "delete");
|
|
574
574
|
return true;
|
|
575
575
|
}
|
|
@@ -577,8 +577,8 @@ var VirtualFS = class {
|
|
|
577
577
|
const all = Array.from(this.files.values());
|
|
578
578
|
return prefix ? all.filter((f) => f.path.startsWith(prefix)) : all;
|
|
579
579
|
}
|
|
580
|
-
exists(
|
|
581
|
-
return this.files.has(
|
|
580
|
+
exists(path3) {
|
|
581
|
+
return this.files.has(path3);
|
|
582
582
|
}
|
|
583
583
|
onChange(handler) {
|
|
584
584
|
this.changeHandlers.add(handler);
|
|
@@ -622,13 +622,13 @@ var VirtualFS = class {
|
|
|
622
622
|
}
|
|
623
623
|
toJSON() {
|
|
624
624
|
const result = {};
|
|
625
|
-
for (const [
|
|
626
|
-
result[
|
|
625
|
+
for (const [path3, file] of Array.from(this.files.entries())) {
|
|
626
|
+
result[path3] = file.content;
|
|
627
627
|
}
|
|
628
628
|
return result;
|
|
629
629
|
}
|
|
630
|
-
detectLanguage(
|
|
631
|
-
const ext =
|
|
630
|
+
detectLanguage(path3) {
|
|
631
|
+
const ext = path3.split(".").pop()?.toLowerCase();
|
|
632
632
|
const map = {
|
|
633
633
|
tsx: "tsx",
|
|
634
634
|
ts: "ts",
|
|
@@ -761,26 +761,26 @@ var ModuleCache = class {
|
|
|
761
761
|
constructor(maxSize = 200) {
|
|
762
762
|
this.maxSize = maxSize;
|
|
763
763
|
}
|
|
764
|
-
set(
|
|
764
|
+
set(path3, module) {
|
|
765
765
|
if (this.cache.size >= this.maxSize) {
|
|
766
766
|
const oldest = Array.from(this.cache.keys())[0];
|
|
767
767
|
const old = this.cache.get(oldest);
|
|
768
768
|
if (old?.objectUrl) URL.revokeObjectURL(old.objectUrl);
|
|
769
769
|
this.cache.delete(oldest);
|
|
770
770
|
}
|
|
771
|
-
this.cache.set(
|
|
771
|
+
this.cache.set(path3, module);
|
|
772
772
|
}
|
|
773
|
-
get(
|
|
774
|
-
const module = this.cache.get(
|
|
773
|
+
get(path3) {
|
|
774
|
+
const module = this.cache.get(path3);
|
|
775
775
|
if (!module) return null;
|
|
776
|
-
this.cache.delete(
|
|
777
|
-
this.cache.set(
|
|
776
|
+
this.cache.delete(path3);
|
|
777
|
+
this.cache.set(path3, module);
|
|
778
778
|
return module;
|
|
779
779
|
}
|
|
780
|
-
invalidate(
|
|
781
|
-
const module = this.cache.get(
|
|
780
|
+
invalidate(path3) {
|
|
781
|
+
const module = this.cache.get(path3);
|
|
782
782
|
if (module?.objectUrl) URL.revokeObjectURL(module.objectUrl);
|
|
783
|
-
this.cache.delete(
|
|
783
|
+
this.cache.delete(path3);
|
|
784
784
|
}
|
|
785
785
|
clear() {
|
|
786
786
|
for (const module of Array.from(this.cache.values())) {
|
|
@@ -942,17 +942,17 @@ var JXRRuntime = class {
|
|
|
942
942
|
this.startMetricsBroadcast();
|
|
943
943
|
}
|
|
944
944
|
/** Resolve and transform a module from the VirtualFS */
|
|
945
|
-
async resolveModule(
|
|
946
|
-
const cached = this.cache.get(
|
|
945
|
+
async resolveModule(path3) {
|
|
946
|
+
const cached = this.cache.get(path3);
|
|
947
947
|
if (cached) return cached.transformed;
|
|
948
|
-
const file = this.vfs.read(
|
|
949
|
-
if (!file) throw new Error(`Module not found: ${
|
|
948
|
+
const file = this.vfs.read(path3);
|
|
949
|
+
if (!file) throw new Error(`Module not found: ${path3}`);
|
|
950
950
|
const startTime = performance.now();
|
|
951
|
-
const transformed = this.transformer.transform(file.content,
|
|
951
|
+
const transformed = this.transformer.transform(file.content, path3);
|
|
952
952
|
const transformMs = performance.now() - startTime;
|
|
953
953
|
const objectUrl = this.transformer.createObjectUrl(transformed);
|
|
954
|
-
this.cache.set(
|
|
955
|
-
path,
|
|
954
|
+
this.cache.set(path3, {
|
|
955
|
+
path: path3,
|
|
956
956
|
source: file.content,
|
|
957
957
|
transformed,
|
|
958
958
|
objectUrl,
|
|
@@ -1033,65 +1033,1018 @@ var JXRRuntime = class {
|
|
|
1033
1033
|
};
|
|
1034
1034
|
var jxrRuntime = new JXRRuntime();
|
|
1035
1035
|
|
|
1036
|
+
// src/enhanced-transpiler.ts
|
|
1037
|
+
import * as Babel from "@babel/standalone";
|
|
1038
|
+
var EnhancedTranspiler = class {
|
|
1039
|
+
transformCache = /* @__PURE__ */ new Map();
|
|
1040
|
+
dependencyGraph = /* @__PURE__ */ new Map();
|
|
1041
|
+
options;
|
|
1042
|
+
constructor(options = {}) {
|
|
1043
|
+
this.options = options;
|
|
1044
|
+
}
|
|
1045
|
+
transpileTypeScript(code, filename, options = {}) {
|
|
1046
|
+
const mergedOptions = { ...this.options, ...options, filename };
|
|
1047
|
+
const cacheKey = `${filename}:${code}:${JSON.stringify(mergedOptions)}`;
|
|
1048
|
+
if (mergedOptions.cache !== false && this.transformCache.has(cacheKey)) {
|
|
1049
|
+
return { ...this.transformCache.get(cacheKey), cached: true };
|
|
1050
|
+
}
|
|
1051
|
+
try {
|
|
1052
|
+
const result = Babel.transform(code, {
|
|
1053
|
+
filename,
|
|
1054
|
+
presets: [
|
|
1055
|
+
["react", { runtime: "automatic" }],
|
|
1056
|
+
"typescript",
|
|
1057
|
+
...mergedOptions.presets || []
|
|
1058
|
+
],
|
|
1059
|
+
plugins: [
|
|
1060
|
+
// Handle import/export transformations - pass filename for context
|
|
1061
|
+
this.createImportTransformer(filename),
|
|
1062
|
+
...mergedOptions.plugins || []
|
|
1063
|
+
],
|
|
1064
|
+
sourceMaps: true,
|
|
1065
|
+
sourceFileName: filename
|
|
1066
|
+
});
|
|
1067
|
+
const transformed = {
|
|
1068
|
+
code: result.code || code,
|
|
1069
|
+
map: result.map,
|
|
1070
|
+
cached: false
|
|
1071
|
+
};
|
|
1072
|
+
if (mergedOptions.cache !== false) {
|
|
1073
|
+
this.transformCache.set(cacheKey, transformed);
|
|
1074
|
+
}
|
|
1075
|
+
return transformed;
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
console.error("[EnhancedTranspiler] Transpilation error:", error);
|
|
1078
|
+
return {
|
|
1079
|
+
code,
|
|
1080
|
+
error,
|
|
1081
|
+
cached: false
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
transpileJSX(code, filename, options = {}) {
|
|
1086
|
+
const mergedOptions = { ...this.options, ...options, filename };
|
|
1087
|
+
const cacheKey = `jsx:${filename}:${code}:${JSON.stringify(mergedOptions)}`;
|
|
1088
|
+
if (mergedOptions.cache !== false && this.transformCache.has(cacheKey)) {
|
|
1089
|
+
return { ...this.transformCache.get(cacheKey), cached: true };
|
|
1090
|
+
}
|
|
1091
|
+
try {
|
|
1092
|
+
const result = Babel.transform(code, {
|
|
1093
|
+
filename,
|
|
1094
|
+
presets: [
|
|
1095
|
+
["react", { runtime: "automatic" }],
|
|
1096
|
+
...mergedOptions.presets || []
|
|
1097
|
+
],
|
|
1098
|
+
plugins: mergedOptions.plugins || [],
|
|
1099
|
+
sourceMaps: true,
|
|
1100
|
+
sourceFileName: filename
|
|
1101
|
+
});
|
|
1102
|
+
console.log("[v0] Transpiled JSX for", filename);
|
|
1103
|
+
console.log("[v0] Output (first 500 chars):", result.code?.substring(0, 500));
|
|
1104
|
+
const transformed = {
|
|
1105
|
+
code: result.code || code,
|
|
1106
|
+
map: result.map,
|
|
1107
|
+
cached: false
|
|
1108
|
+
};
|
|
1109
|
+
if (mergedOptions.cache !== false) {
|
|
1110
|
+
this.transformCache.set(cacheKey, transformed);
|
|
1111
|
+
}
|
|
1112
|
+
return transformed;
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
console.error("[EnhancedTranspiler] JSX transformation error:", error);
|
|
1115
|
+
return {
|
|
1116
|
+
code,
|
|
1117
|
+
error,
|
|
1118
|
+
cached: false
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
createImportTransformer(filename) {
|
|
1123
|
+
const self = this;
|
|
1124
|
+
return function importTransformer() {
|
|
1125
|
+
return {
|
|
1126
|
+
visitor: {
|
|
1127
|
+
ImportDeclaration(path3) {
|
|
1128
|
+
const source = path3.node.source.value;
|
|
1129
|
+
if (!source.startsWith("./") && !source.startsWith("../") && !source.startsWith("@/")) {
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const resolvedPath = self.resolveImportPath(filename, source);
|
|
1133
|
+
if (resolvedPath) {
|
|
1134
|
+
console.log(`[Transpiler] Rewriting: ${filename} imports "${source}" \u2192 "${resolvedPath}"`);
|
|
1135
|
+
path3.node.source.value = resolvedPath;
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
ExportNamedDeclaration(path3) {
|
|
1139
|
+
if (path3.node.source) {
|
|
1140
|
+
const source = path3.node.source.value;
|
|
1141
|
+
if (!source.startsWith("./") && !source.startsWith("../") && !source.startsWith("@/")) {
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const resolvedPath = self.resolveImportPath(filename, source);
|
|
1145
|
+
if (resolvedPath) {
|
|
1146
|
+
console.log(`[Transpiler] Rewriting export: ${filename} exports from "${source}" \u2192 "${resolvedPath}"`);
|
|
1147
|
+
path3.node.source.value = resolvedPath;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
ExportAllDeclaration(path3) {
|
|
1152
|
+
if (path3.node.source) {
|
|
1153
|
+
const source = path3.node.source.value;
|
|
1154
|
+
if (!source.startsWith("./") && !source.startsWith("../") && !source.startsWith("@/")) {
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
const resolvedPath = self.resolveImportPath(filename, source);
|
|
1158
|
+
if (resolvedPath) {
|
|
1159
|
+
console.log(`[Transpiler] Rewriting export all: ${filename} exports from "${source}" \u2192 "${resolvedPath}"`);
|
|
1160
|
+
path3.node.source.value = resolvedPath;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
resolveImportPath(fromFile, importPath) {
|
|
1169
|
+
const normalizedFromFile = fromFile.startsWith("/") ? fromFile.substring(1) : fromFile;
|
|
1170
|
+
if (importPath.startsWith("@/")) {
|
|
1171
|
+
return ("/src/" + importPath.substring(2)).replace(/\.(tsx|ts|jsx|js)$/, "");
|
|
1172
|
+
}
|
|
1173
|
+
if (importPath.startsWith("./") || importPath.startsWith("../")) {
|
|
1174
|
+
const fromDir = normalizedFromFile.split("/").slice(0, -1);
|
|
1175
|
+
const importParts = importPath.split("/");
|
|
1176
|
+
let currentPath = [...fromDir];
|
|
1177
|
+
for (const part of importParts) {
|
|
1178
|
+
if (part === "..") {
|
|
1179
|
+
currentPath.pop();
|
|
1180
|
+
} else if (part !== ".") {
|
|
1181
|
+
currentPath.push(part);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return "/" + currentPath.join("/").replace(/\.(tsx|ts|jsx|js)$/, "");
|
|
1185
|
+
}
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
// Track dependencies for cache invalidation
|
|
1189
|
+
trackDependencies(filename, dependencies) {
|
|
1190
|
+
this.dependencyGraph.set(filename, new Set(dependencies));
|
|
1191
|
+
}
|
|
1192
|
+
// Invalidate cache entries that depend on changed files
|
|
1193
|
+
invalidateCache(changedFiles) {
|
|
1194
|
+
const toInvalidate = /* @__PURE__ */ new Set();
|
|
1195
|
+
for (const [file, deps] of this.dependencyGraph) {
|
|
1196
|
+
for (const changedFile of changedFiles) {
|
|
1197
|
+
if (deps.has(changedFile)) {
|
|
1198
|
+
toInvalidate.add(file);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
for (const file of toInvalidate) {
|
|
1203
|
+
for (const [key] of this.transformCache) {
|
|
1204
|
+
if (key.startsWith(`${file}:`)) {
|
|
1205
|
+
this.transformCache.delete(key);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
// Clear all cache
|
|
1211
|
+
clearCache() {
|
|
1212
|
+
this.transformCache.clear();
|
|
1213
|
+
this.dependencyGraph.clear();
|
|
1214
|
+
}
|
|
1215
|
+
invalidateFile(filename) {
|
|
1216
|
+
for (const key of this.transformCache.keys()) {
|
|
1217
|
+
if (key.startsWith(filename)) {
|
|
1218
|
+
this.transformCache.delete(key);
|
|
1219
|
+
console.log("[Transpiler] Invalidated cache for", filename);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
const dependents = this.dependencyGraph.get(filename);
|
|
1223
|
+
if (dependents) {
|
|
1224
|
+
for (const dependent of dependents) {
|
|
1225
|
+
this.invalidateFile(dependent);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
// Get cache statistics
|
|
1230
|
+
getCacheStats() {
|
|
1231
|
+
return {
|
|
1232
|
+
size: this.transformCache.size,
|
|
1233
|
+
hitRate: 0
|
|
1234
|
+
// Could be implemented with usage tracking
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
// Extract dependencies from code for tracking
|
|
1238
|
+
extractDependencies(code) {
|
|
1239
|
+
const dependencies = [];
|
|
1240
|
+
try {
|
|
1241
|
+
const importRegex = /import\s+(?:[\w\s{},*]*\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
1242
|
+
let match;
|
|
1243
|
+
while ((match = importRegex.exec(code)) !== null) {
|
|
1244
|
+
dependencies.push(match[1]);
|
|
1245
|
+
}
|
|
1246
|
+
const exportRegex = /export\s+(?:[\w\s{},*]*\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
1247
|
+
while ((match = exportRegex.exec(code)) !== null) {
|
|
1248
|
+
dependencies.push(match[1]);
|
|
1249
|
+
}
|
|
1250
|
+
const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
1251
|
+
while ((match = dynamicImportRegex.exec(code)) !== null) {
|
|
1252
|
+
dependencies.push(match[1]);
|
|
1253
|
+
}
|
|
1254
|
+
const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
1255
|
+
while ((match = requireRegex.exec(code)) !== null) {
|
|
1256
|
+
dependencies.push(match[1]);
|
|
1257
|
+
}
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
console.warn("[EnhancedTranspiler] Failed to extract dependencies:", error);
|
|
1260
|
+
}
|
|
1261
|
+
return [...new Set(dependencies)];
|
|
1262
|
+
}
|
|
1263
|
+
// Validate transpiled code
|
|
1264
|
+
validateTranspiledCode(code) {
|
|
1265
|
+
const errors = [];
|
|
1266
|
+
try {
|
|
1267
|
+
new Function(code);
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
errors.push(`Syntax error: ${error.message}`);
|
|
1270
|
+
}
|
|
1271
|
+
if (code.includes("undefined") && code.includes("import")) {
|
|
1272
|
+
errors.push("Possible import resolution issue detected");
|
|
1273
|
+
}
|
|
1274
|
+
if (code.includes("require(") && !code.includes("module.exports")) {
|
|
1275
|
+
errors.push("CommonJS require() detected in ES module");
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
valid: errors.length === 0,
|
|
1279
|
+
errors
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
// src/entry-point-detection.ts
|
|
1285
|
+
function generateId() {
|
|
1286
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
|
1287
|
+
}
|
|
1288
|
+
var COMMON_ENTRY_POINTS = [
|
|
1289
|
+
"src/App.tsx",
|
|
1290
|
+
"src/App.ts",
|
|
1291
|
+
"App.tsx",
|
|
1292
|
+
"App.ts",
|
|
1293
|
+
"app/page.tsx",
|
|
1294
|
+
// Next.js
|
|
1295
|
+
"app/page.ts",
|
|
1296
|
+
"src/main.tsx",
|
|
1297
|
+
"src/main.ts",
|
|
1298
|
+
"main.tsx",
|
|
1299
|
+
"main.ts",
|
|
1300
|
+
"index.tsx",
|
|
1301
|
+
"index.ts",
|
|
1302
|
+
"src/index.tsx",
|
|
1303
|
+
"src/index.ts"
|
|
1304
|
+
];
|
|
1305
|
+
var COMPONENT_INDICATORS = [
|
|
1306
|
+
"App",
|
|
1307
|
+
"Main",
|
|
1308
|
+
"Page",
|
|
1309
|
+
"Index",
|
|
1310
|
+
"Home"
|
|
1311
|
+
];
|
|
1312
|
+
function findOrCreateEntryPoint(files) {
|
|
1313
|
+
console.log("\u{1F50D} Entry point detection: Starting with", files.length, "files");
|
|
1314
|
+
console.log("\u{1F4C1} Available files:", files.map((f) => f.path));
|
|
1315
|
+
const existingEntry = findExistingEntryPoint(files);
|
|
1316
|
+
if (existingEntry) {
|
|
1317
|
+
console.log("\u{1F3AF} Found existing entry point:", existingEntry);
|
|
1318
|
+
return {
|
|
1319
|
+
entryPoint: existingEntry,
|
|
1320
|
+
files,
|
|
1321
|
+
createdEntry: false
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
const componentEntry = findComponentEntryPoint(files);
|
|
1325
|
+
if (componentEntry) {
|
|
1326
|
+
console.log("\u{1F527} Found component that could be entry point:", componentEntry);
|
|
1327
|
+
const componentFile = files.find((f) => f.path === componentEntry);
|
|
1328
|
+
if (componentFile) {
|
|
1329
|
+
console.log("\u{1F4DD} Creating app wrapper for component:", componentEntry);
|
|
1330
|
+
const appWrapperFile = createAppWrapperForComponent(componentFile);
|
|
1331
|
+
const updatedFiles2 = [...files, appWrapperFile];
|
|
1332
|
+
return {
|
|
1333
|
+
entryPoint: appWrapperFile.path,
|
|
1334
|
+
files: updatedFiles2,
|
|
1335
|
+
createdEntry: true
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
console.log("\u{1F4DD} Creating default entry point");
|
|
1340
|
+
const entryPointFile = createDefaultEntryPoint(files);
|
|
1341
|
+
const updatedFiles = [...files, entryPointFile];
|
|
1342
|
+
return {
|
|
1343
|
+
entryPoint: entryPointFile.path,
|
|
1344
|
+
files: updatedFiles,
|
|
1345
|
+
createdEntry: true
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function isBootstrapFile(file) {
|
|
1349
|
+
const c = file.content;
|
|
1350
|
+
return /\bcreateRoot\s*\(/.test(c) || /ReactDOM\.render\s*\(/.test(c) || /\bhydrateRoot\s*\(/.test(c);
|
|
1351
|
+
}
|
|
1352
|
+
function extractComponentFromBootstrap(bootstrapFile, allFiles) {
|
|
1353
|
+
const importRe = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
1354
|
+
let match;
|
|
1355
|
+
while ((match = importRe.exec(bootstrapFile.content)) !== null) {
|
|
1356
|
+
const importPath = match[2];
|
|
1357
|
+
if (!importPath.startsWith(".") && !importPath.startsWith("@/")) continue;
|
|
1358
|
+
const bootstrapDir = bootstrapFile.path.replace(/\/[^/]+$/, "");
|
|
1359
|
+
let resolvedBase;
|
|
1360
|
+
if (importPath.startsWith("@/")) {
|
|
1361
|
+
resolvedBase = "src/" + importPath.slice(2);
|
|
1362
|
+
} else {
|
|
1363
|
+
const parts = bootstrapDir.split("/").filter(Boolean);
|
|
1364
|
+
for (const seg of importPath.split("/")) {
|
|
1365
|
+
if (seg === "..") parts.pop();
|
|
1366
|
+
else if (seg !== ".") parts.push(seg);
|
|
1367
|
+
}
|
|
1368
|
+
resolvedBase = parts.join("/");
|
|
1369
|
+
}
|
|
1370
|
+
const extensions = [".tsx", ".ts", ".jsx", ".js", ""];
|
|
1371
|
+
for (const ext of extensions) {
|
|
1372
|
+
const candidate = resolvedBase + ext;
|
|
1373
|
+
const found = allFiles.find(
|
|
1374
|
+
(f) => f.path === candidate || f.path === "/" + candidate || f.path.replace(/^\//, "") === candidate
|
|
1375
|
+
);
|
|
1376
|
+
if (found) {
|
|
1377
|
+
console.log(`\u{1F517} Bootstrap ${bootstrapFile.path} imports component from ${found.path}`);
|
|
1378
|
+
return found.path;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
function findExistingEntryPoint(files) {
|
|
1385
|
+
for (const entryPath of COMMON_ENTRY_POINTS) {
|
|
1386
|
+
const found = files.find(
|
|
1387
|
+
(f) => f.path === entryPath || f.path.endsWith(entryPath) || f.path.replace(/^\//, "") === entryPath.replace(/^\//, "")
|
|
1388
|
+
);
|
|
1389
|
+
if (found) {
|
|
1390
|
+
if (isBootstrapFile(found)) {
|
|
1391
|
+
console.log(`\u26A1 Skipping bootstrap file as entry point: ${found.path}`);
|
|
1392
|
+
const componentPath = extractComponentFromBootstrap(found, files);
|
|
1393
|
+
if (componentPath) {
|
|
1394
|
+
return componentPath;
|
|
1395
|
+
}
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
return found.path;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return null;
|
|
1402
|
+
}
|
|
1403
|
+
function findComponentEntryPoint(files) {
|
|
1404
|
+
for (const file of files) {
|
|
1405
|
+
const fileName = file.path.split("/").pop()?.replace(/\.(tsx?|jsx?)$/, "");
|
|
1406
|
+
if (fileName && COMPONENT_INDICATORS.includes(fileName)) {
|
|
1407
|
+
return file.path;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
const jsxFiles = files.filter(
|
|
1411
|
+
(f) => f.path.endsWith(".tsx") || f.path.endsWith(".jsx")
|
|
1412
|
+
);
|
|
1413
|
+
if (jsxFiles.length > 0) {
|
|
1414
|
+
for (const file of jsxFiles) {
|
|
1415
|
+
if (file.content.includes("export default") || file.content.includes("function") || file.content.includes("const")) {
|
|
1416
|
+
return file.path;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return jsxFiles[0].path;
|
|
1420
|
+
}
|
|
1421
|
+
const tsFiles = files.filter((f) => f.path.endsWith(".ts"));
|
|
1422
|
+
if (tsFiles.length > 0) {
|
|
1423
|
+
return tsFiles[0].path;
|
|
1424
|
+
}
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
function toValidIdentifier(name) {
|
|
1428
|
+
return name.replace(/[-_]/g, " ").replace(/\s+/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("").replace(/[^a-zA-Z0-9_$]/g, "").replace(/^[0-9]/, "_$&") || "Component";
|
|
1429
|
+
}
|
|
1430
|
+
function createAppWrapperForComponent(componentFile) {
|
|
1431
|
+
const rawComponentName = componentFile.path.split("/").pop()?.replace(/\.(tsx?|jsx?)$/, "") || "Component";
|
|
1432
|
+
const componentName = toValidIdentifier(rawComponentName);
|
|
1433
|
+
const componentPath = componentFile.path.replace(/\.(tsx?|jsx?)$/, "");
|
|
1434
|
+
const content = `import React from 'react'
|
|
1435
|
+
import ${componentName} from '${componentPath}'
|
|
1436
|
+
|
|
1437
|
+
// App wrapper created by preview system for component: ${componentName}
|
|
1438
|
+
export default function App() {
|
|
1439
|
+
return (
|
|
1440
|
+
<div style={{
|
|
1441
|
+
padding: '20px',
|
|
1442
|
+
fontFamily: 'system-ui, sans-serif',
|
|
1443
|
+
minHeight: '100vh',
|
|
1444
|
+
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
|
1445
|
+
}}>
|
|
1446
|
+
<div style={{
|
|
1447
|
+
maxWidth: '800px',
|
|
1448
|
+
margin: '0 auto',
|
|
1449
|
+
background: 'white',
|
|
1450
|
+
borderRadius: '12px',
|
|
1451
|
+
padding: '30px',
|
|
1452
|
+
boxShadow: '0 10px 30px rgba(0,0,0,0.1)'
|
|
1453
|
+
}}>
|
|
1454
|
+
<h1 style={{
|
|
1455
|
+
color: '#333',
|
|
1456
|
+
marginBottom: '10px',
|
|
1457
|
+
fontSize: '28px',
|
|
1458
|
+
fontWeight: '700'
|
|
1459
|
+
}}>
|
|
1460
|
+
DamascusAI Preview
|
|
1461
|
+
</h1>
|
|
1462
|
+
<p style={{
|
|
1463
|
+
color: '#666',
|
|
1464
|
+
marginBottom: '30px',
|
|
1465
|
+
fontSize: '16px'
|
|
1466
|
+
}}>
|
|
1467
|
+
Sovereign generation system active - Component: ${componentName}
|
|
1468
|
+
</p>
|
|
1469
|
+
|
|
1470
|
+
<div style={{
|
|
1471
|
+
border: '2px dashed #e2e8f0',
|
|
1472
|
+
borderRadius: '8px',
|
|
1473
|
+
padding: '20px',
|
|
1474
|
+
background: '#f8fafc'
|
|
1475
|
+
}}>
|
|
1476
|
+
<h2 style={{
|
|
1477
|
+
color: '#4a5568',
|
|
1478
|
+
marginBottom: '15px',
|
|
1479
|
+
fontSize: '18px'
|
|
1480
|
+
}}>
|
|
1481
|
+
Component: <code>${componentName}</code>
|
|
1482
|
+
</h2>
|
|
1483
|
+
<${componentName} />
|
|
1484
|
+
</div>
|
|
1485
|
+
|
|
1486
|
+
<div style={{
|
|
1487
|
+
marginTop: '20px',
|
|
1488
|
+
padding: '15px',
|
|
1489
|
+
background: '#edf2f7',
|
|
1490
|
+
borderRadius: '6px',
|
|
1491
|
+
fontSize: '14px',
|
|
1492
|
+
color: '#4a5568'
|
|
1493
|
+
}}>
|
|
1494
|
+
<strong>Generated by:</strong> DamascusAI Sovereign Stack
|
|
1495
|
+
</div>
|
|
1496
|
+
</div>
|
|
1497
|
+
</div>
|
|
1498
|
+
)
|
|
1499
|
+
}
|
|
1500
|
+
`;
|
|
1501
|
+
const now = Date.now();
|
|
1502
|
+
return {
|
|
1503
|
+
id: generateId(),
|
|
1504
|
+
path: "/App.tsx",
|
|
1505
|
+
content,
|
|
1506
|
+
language: "typescript",
|
|
1507
|
+
createdAt: now,
|
|
1508
|
+
updatedAt: now
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
function createDefaultEntryPoint(files) {
|
|
1512
|
+
console.log("\u{1F4DD} Creating default App.tsx entry point");
|
|
1513
|
+
const components = findAvailableComponents(files);
|
|
1514
|
+
const sanitizedComponents = components.map((comp) => ({
|
|
1515
|
+
...comp,
|
|
1516
|
+
name: toValidIdentifier(comp.name)
|
|
1517
|
+
}));
|
|
1518
|
+
const imports = sanitizedComponents.map(
|
|
1519
|
+
(comp) => `import ${comp.name} from '${comp.path.replace(/\.(tsx?|jsx?)$/, "")}'`
|
|
1520
|
+
).join("\n");
|
|
1521
|
+
const componentUsage = sanitizedComponents.map(
|
|
1522
|
+
(comp) => ` <${comp.name} />`
|
|
1523
|
+
).join("\n");
|
|
1524
|
+
const content = `${imports}
|
|
1525
|
+
import React from 'react'
|
|
1526
|
+
|
|
1527
|
+
// Default entry point created by preview system
|
|
1528
|
+
export default function App() {
|
|
1529
|
+
return (
|
|
1530
|
+
<div style={{
|
|
1531
|
+
padding: '20px',
|
|
1532
|
+
fontFamily: 'system-ui, sans-serif',
|
|
1533
|
+
minHeight: '100vh',
|
|
1534
|
+
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
|
1535
|
+
}}>
|
|
1536
|
+
<div style={{
|
|
1537
|
+
maxWidth: '800px',
|
|
1538
|
+
margin: '0 auto',
|
|
1539
|
+
background: 'white',
|
|
1540
|
+
borderRadius: '12px',
|
|
1541
|
+
padding: '30px',
|
|
1542
|
+
boxShadow: '0 10px 30px rgba(0,0,0,0.1)'
|
|
1543
|
+
}}>
|
|
1544
|
+
<h1 style={{
|
|
1545
|
+
color: '#333',
|
|
1546
|
+
marginBottom: '10px',
|
|
1547
|
+
fontSize: '28px',
|
|
1548
|
+
fontWeight: '700'
|
|
1549
|
+
}}>
|
|
1550
|
+
\u{1F680} Damascus AI Preview
|
|
1551
|
+
</h1>
|
|
1552
|
+
<p style={{
|
|
1553
|
+
color: '#666',
|
|
1554
|
+
marginBottom: '30px',
|
|
1555
|
+
fontSize: '16px'
|
|
1556
|
+
}}>
|
|
1557
|
+
Sovereign generation system active
|
|
1558
|
+
</p>
|
|
1559
|
+
|
|
1560
|
+
<div style={{
|
|
1561
|
+
border: '2px dashed #e2e8f0',
|
|
1562
|
+
borderRadius: '8px',
|
|
1563
|
+
padding: '20px',
|
|
1564
|
+
background: '#f8fafc'
|
|
1565
|
+
}}>
|
|
1566
|
+
<h2 style={{
|
|
1567
|
+
color: '#4a5568',
|
|
1568
|
+
marginBottom: '15px',
|
|
1569
|
+
fontSize: '18px'
|
|
1570
|
+
}}>
|
|
1571
|
+
Generated Components:
|
|
1572
|
+
</h2>
|
|
1573
|
+
<div style={{ marginTop: '20px' }}>
|
|
1574
|
+
${componentUsage}
|
|
1575
|
+
</div>
|
|
1576
|
+
</div>
|
|
1577
|
+
|
|
1578
|
+
<div style={{
|
|
1579
|
+
marginTop: '20px',
|
|
1580
|
+
padding: '15px',
|
|
1581
|
+
background: '#edf2f7',
|
|
1582
|
+
borderRadius: '6px',
|
|
1583
|
+
fontSize: '14px',
|
|
1584
|
+
color: '#4a5568'
|
|
1585
|
+
}}>
|
|
1586
|
+
<strong>Generated by:</strong> DamascusAI Sovereign Stack
|
|
1587
|
+
</div>
|
|
1588
|
+
</div>
|
|
1589
|
+
</div>
|
|
1590
|
+
)
|
|
1591
|
+
}
|
|
1592
|
+
`;
|
|
1593
|
+
const now = Date.now();
|
|
1594
|
+
return {
|
|
1595
|
+
id: generateId(),
|
|
1596
|
+
path: "/App.tsx",
|
|
1597
|
+
content,
|
|
1598
|
+
language: "typescript",
|
|
1599
|
+
createdAt: now,
|
|
1600
|
+
updatedAt: now
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
function findAvailableComponents(files) {
|
|
1604
|
+
const components = [];
|
|
1605
|
+
for (const file of files) {
|
|
1606
|
+
if (file.path.endsWith(".tsx") || file.path.endsWith(".jsx")) {
|
|
1607
|
+
const fileName = file.path.split("/").pop()?.replace(/\.(tsx?|jsx?)$/, "");
|
|
1608
|
+
if (fileName && fileName !== "App" && fileName !== "index") {
|
|
1609
|
+
if (file.content.includes("export") || file.content.includes("return")) {
|
|
1610
|
+
components.push({
|
|
1611
|
+
name: fileName,
|
|
1612
|
+
path: file.path
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
if (components.length === 0) {
|
|
1619
|
+
components.push({
|
|
1620
|
+
name: "GeneratedContent",
|
|
1621
|
+
path: "/placeholder"
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
return components.slice(0, 3);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
// src/jxr-server-manager.ts
|
|
1628
|
+
import { readFile, readdir, watch } from "fs/promises";
|
|
1629
|
+
import path from "path";
|
|
1630
|
+
import http from "http";
|
|
1631
|
+
var JXRServerManager = class {
|
|
1632
|
+
runtime;
|
|
1633
|
+
transpiler;
|
|
1634
|
+
server = null;
|
|
1635
|
+
config;
|
|
1636
|
+
projectFiles = [];
|
|
1637
|
+
entryPoint = "src/App.tsx";
|
|
1638
|
+
watchers = /* @__PURE__ */ new Map();
|
|
1639
|
+
debounceTimer = null;
|
|
1640
|
+
pendingChanges = /* @__PURE__ */ new Map();
|
|
1641
|
+
clients = /* @__PURE__ */ new Set();
|
|
1642
|
+
constructor(config = {}) {
|
|
1643
|
+
this.config = {
|
|
1644
|
+
port: config.port || 3e3,
|
|
1645
|
+
host: config.host || "localhost",
|
|
1646
|
+
srcDir: config.srcDir || "src",
|
|
1647
|
+
enableHMR: config.enableHMR !== false,
|
|
1648
|
+
debounceMs: config.debounceMs || 300
|
|
1649
|
+
};
|
|
1650
|
+
this.runtime = new JXRRuntime();
|
|
1651
|
+
this.transpiler = new EnhancedTranspiler();
|
|
1652
|
+
}
|
|
1653
|
+
async initialize() {
|
|
1654
|
+
await this.runtime.init();
|
|
1655
|
+
await this.loadProjectFiles();
|
|
1656
|
+
this.setupEntryPoint();
|
|
1657
|
+
if (this.config.enableHMR) {
|
|
1658
|
+
this.startFileWatching();
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
async loadProjectFiles() {
|
|
1662
|
+
const srcPath = path.resolve(process.cwd(), this.config.srcDir);
|
|
1663
|
+
this.projectFiles = [];
|
|
1664
|
+
async function readDirRecursive(dir, base, files, runtime) {
|
|
1665
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
1666
|
+
const fullPath = path.join(dir, entry.name);
|
|
1667
|
+
const relativePath = path.join(base, entry.name);
|
|
1668
|
+
if (entry.isDirectory()) {
|
|
1669
|
+
await readDirRecursive(fullPath, relativePath, files, runtime);
|
|
1670
|
+
} else if (/\.(tsx?|jsx?|css)$/.test(entry.name)) {
|
|
1671
|
+
const content = await readFile(fullPath, "utf-8");
|
|
1672
|
+
const vfsPath = "/" + relativePath.replace(/\\/g, "/");
|
|
1673
|
+
runtime.vfs.write(vfsPath, content);
|
|
1674
|
+
files.push({
|
|
1675
|
+
id: Math.random().toString(36).slice(2),
|
|
1676
|
+
path: relativePath.replace(/\\/g, "/"),
|
|
1677
|
+
content,
|
|
1678
|
+
language: entry.name.endsWith(".tsx") || entry.name.endsWith(".ts") ? "typescript" : "javascript",
|
|
1679
|
+
createdAt: Date.now(),
|
|
1680
|
+
updatedAt: Date.now()
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
try {
|
|
1686
|
+
await readDirRecursive(srcPath, this.config.srcDir, this.projectFiles, this.runtime);
|
|
1687
|
+
console.log(`\u{1F4C1} Loaded ${this.projectFiles.length} files into VirtualFS`);
|
|
1688
|
+
} catch (err) {
|
|
1689
|
+
console.error("Error loading files:", err);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
setupEntryPoint() {
|
|
1693
|
+
const result = findOrCreateEntryPoint(this.projectFiles);
|
|
1694
|
+
this.entryPoint = result.entryPoint;
|
|
1695
|
+
if (result.createdEntry) {
|
|
1696
|
+
const entryFile = result.files.find((f) => f.path === this.entryPoint);
|
|
1697
|
+
if (entryFile) {
|
|
1698
|
+
this.runtime.vfs.write("/" + entryFile.path, entryFile.content);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
console.log(`\u{1F3AF} Entry point: ${this.entryPoint}`);
|
|
1702
|
+
}
|
|
1703
|
+
startFileWatching() {
|
|
1704
|
+
const srcPath = path.resolve(process.cwd(), this.config.srcDir);
|
|
1705
|
+
const watchDir = async (dir) => {
|
|
1706
|
+
try {
|
|
1707
|
+
const watcher = watch(dir, { recursive: true });
|
|
1708
|
+
this.watchers.set(dir, watcher);
|
|
1709
|
+
for await (const event of watcher) {
|
|
1710
|
+
if (event.filename && /\.(tsx?|jsx?|css)$/.test(event.filename)) {
|
|
1711
|
+
this.handleFileChange(event.filename);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
console.error(`Watch error for ${dir}:`, err);
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
watchDir(srcPath);
|
|
1719
|
+
console.log(`\u{1F440} Watching ${this.config.srcDir} for changes...`);
|
|
1720
|
+
}
|
|
1721
|
+
handleFileChange(filename) {
|
|
1722
|
+
const fullPath = path.resolve(process.cwd(), this.config.srcDir, filename);
|
|
1723
|
+
const relativePath = path.join(this.config.srcDir, filename).replace(/\\/g, "/");
|
|
1724
|
+
readFile(fullPath, "utf-8").then((content) => {
|
|
1725
|
+
const file = {
|
|
1726
|
+
id: Math.random().toString(36).slice(2),
|
|
1727
|
+
path: relativePath,
|
|
1728
|
+
content,
|
|
1729
|
+
language: filename.endsWith(".tsx") || filename.endsWith(".ts") ? "typescript" : "javascript",
|
|
1730
|
+
createdAt: Date.now(),
|
|
1731
|
+
updatedAt: Date.now()
|
|
1732
|
+
};
|
|
1733
|
+
this.pendingChanges.set(relativePath, file);
|
|
1734
|
+
this.scheduleReload();
|
|
1735
|
+
}).catch((err) => console.error(`Error reading ${filename}:`, err));
|
|
1736
|
+
}
|
|
1737
|
+
scheduleReload() {
|
|
1738
|
+
if (this.debounceTimer) {
|
|
1739
|
+
clearTimeout(this.debounceTimer);
|
|
1740
|
+
}
|
|
1741
|
+
this.debounceTimer = setTimeout(() => {
|
|
1742
|
+
this.processPendingChanges();
|
|
1743
|
+
}, this.config.debounceMs);
|
|
1744
|
+
}
|
|
1745
|
+
async processPendingChanges() {
|
|
1746
|
+
if (this.pendingChanges.size === 0) return;
|
|
1747
|
+
console.log(`\u{1F504} Processing ${this.pendingChanges.size} file changes...`);
|
|
1748
|
+
for (const [path3, file] of this.pendingChanges) {
|
|
1749
|
+
this.runtime.vfs.write("/" + path3, file.content);
|
|
1750
|
+
const existingIndex = this.projectFiles.findIndex((f) => f.path === path3);
|
|
1751
|
+
if (existingIndex >= 0) {
|
|
1752
|
+
this.projectFiles[existingIndex] = file;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
this.pendingChanges.clear();
|
|
1756
|
+
this.broadcastReload();
|
|
1757
|
+
}
|
|
1758
|
+
broadcastReload() {
|
|
1759
|
+
const message = JSON.stringify({ type: "reload", timestamp: Date.now() });
|
|
1760
|
+
this.clients.forEach((client) => {
|
|
1761
|
+
client.write(`data: ${message}
|
|
1762
|
+
|
|
1763
|
+
`);
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
async start() {
|
|
1767
|
+
this.server = http.createServer(async (req, res) => {
|
|
1768
|
+
const url = new URL(req.url || "/", `http://${this.config.host}:${this.config.port}`);
|
|
1769
|
+
if (url.pathname === "/__hmr" && this.config.enableHMR) {
|
|
1770
|
+
res.writeHead(200, {
|
|
1771
|
+
"Content-Type": "text/event-stream",
|
|
1772
|
+
"Cache-Control": "no-cache",
|
|
1773
|
+
"Connection": "keep-alive",
|
|
1774
|
+
"Access-Control-Allow-Origin": "*"
|
|
1775
|
+
});
|
|
1776
|
+
this.clients.add(res);
|
|
1777
|
+
req.on("close", () => {
|
|
1778
|
+
this.clients.delete(res);
|
|
1779
|
+
});
|
|
1780
|
+
res.write(`data: ${JSON.stringify({ type: "connected" })}
|
|
1781
|
+
|
|
1782
|
+
`);
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
if (url.pathname === "/__health") {
|
|
1786
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1787
|
+
res.end(JSON.stringify({ status: "ok", runtime: "JXR", version: this.runtime.version }));
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
if (url.pathname === "/") {
|
|
1791
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1792
|
+
res.end(this.generateHTML());
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
if (url.pathname.match(/\.(tsx?|jsx?|ts|js)$/) || url.pathname.startsWith("/src/")) {
|
|
1796
|
+
try {
|
|
1797
|
+
let vfsPath = url.pathname;
|
|
1798
|
+
let file = this.runtime.vfs.read(vfsPath);
|
|
1799
|
+
if (!file && !vfsPath.match(/\.(tsx?|jsx?|ts|js|css)$/)) {
|
|
1800
|
+
const extensions = [".tsx", ".ts", ".jsx", ".js", ".css"];
|
|
1801
|
+
for (const ext of extensions) {
|
|
1802
|
+
file = this.runtime.vfs.read(vfsPath + ext);
|
|
1803
|
+
if (file) {
|
|
1804
|
+
vfsPath = vfsPath + ext;
|
|
1805
|
+
break;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
if (!file) {
|
|
1810
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1811
|
+
res.end(`// Module not found: ${url.pathname}`);
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (vfsPath.endsWith(".css")) {
|
|
1815
|
+
const escapedCSS = file.content.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
1816
|
+
const js = `
|
|
1817
|
+
const style = document.createElement('style');
|
|
1818
|
+
style.textContent = \`${escapedCSS}\`;
|
|
1819
|
+
document.head.appendChild(style);
|
|
1820
|
+
export default \`${escapedCSS}\`;
|
|
1821
|
+
`;
|
|
1822
|
+
res.writeHead(200, {
|
|
1823
|
+
"Content-Type": "application/javascript",
|
|
1824
|
+
"Cache-Control": "no-cache"
|
|
1825
|
+
});
|
|
1826
|
+
res.end(js);
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
const result = this.transpiler.transpileTypeScript(file.content, vfsPath);
|
|
1830
|
+
if (result.error) {
|
|
1831
|
+
console.error(`Transform error for ${url.pathname}:`, result.error);
|
|
1832
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
1833
|
+
res.end(`// Transform error: ${result.error.message}`);
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
res.writeHead(200, {
|
|
1837
|
+
"Content-Type": "application/javascript",
|
|
1838
|
+
"Cache-Control": "no-cache"
|
|
1839
|
+
});
|
|
1840
|
+
res.end(result.code);
|
|
1841
|
+
} catch (err) {
|
|
1842
|
+
console.error(`Error serving ${url.pathname}:`, err);
|
|
1843
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
1844
|
+
res.end(`// Error: ${err?.message || String(err)}`);
|
|
1845
|
+
}
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
res.writeHead(404);
|
|
1849
|
+
res.end("Not found");
|
|
1850
|
+
});
|
|
1851
|
+
return new Promise((resolve, reject) => {
|
|
1852
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
1853
|
+
console.log(`\u{1F680} JXR server running on http://${this.config.host}:${this.config.port}/`);
|
|
1854
|
+
if (this.config.enableHMR) {
|
|
1855
|
+
console.log(`\u{1F525} HMR enabled (debounce: ${this.config.debounceMs}ms)`);
|
|
1856
|
+
}
|
|
1857
|
+
resolve();
|
|
1858
|
+
});
|
|
1859
|
+
this.server.on("error", reject);
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
async stop() {
|
|
1863
|
+
this.watchers.clear();
|
|
1864
|
+
if (this.debounceTimer) {
|
|
1865
|
+
clearTimeout(this.debounceTimer);
|
|
1866
|
+
this.debounceTimer = null;
|
|
1867
|
+
}
|
|
1868
|
+
this.clients.forEach((client) => {
|
|
1869
|
+
try {
|
|
1870
|
+
client.end();
|
|
1871
|
+
} catch {
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
this.clients.clear();
|
|
1875
|
+
if (this.server) {
|
|
1876
|
+
await new Promise((resolve) => {
|
|
1877
|
+
this.server.close(() => resolve());
|
|
1878
|
+
});
|
|
1879
|
+
this.server = null;
|
|
1880
|
+
}
|
|
1881
|
+
this.runtime.dispose();
|
|
1882
|
+
console.log("\u{1F44B} JXR server stopped");
|
|
1883
|
+
}
|
|
1884
|
+
generateHTML() {
|
|
1885
|
+
const hmrScript = this.config.enableHMR ? `
|
|
1886
|
+
<script>
|
|
1887
|
+
// HMR Client
|
|
1888
|
+
const evtSource = new EventSource('/__hmr');
|
|
1889
|
+
evtSource.onmessage = (e) => {
|
|
1890
|
+
const data = JSON.parse(e.data);
|
|
1891
|
+
if (data.type === 'reload') {
|
|
1892
|
+
console.log('[JXR] Reloading...');
|
|
1893
|
+
location.reload();
|
|
1894
|
+
}
|
|
1895
|
+
};
|
|
1896
|
+
evtSource.onerror = () => console.log('[JXR] HMR connection lost');
|
|
1897
|
+
<\/script>` : "";
|
|
1898
|
+
const importMap = {
|
|
1899
|
+
"imports": {
|
|
1900
|
+
"react": "https://esm.sh/react@19.2.4",
|
|
1901
|
+
"react/jsx-runtime": "https://esm.sh/react@19.2.4/jsx-runtime",
|
|
1902
|
+
"react/jsx-dev-runtime": "https://esm.sh/react@19.2.4/jsx-dev-runtime",
|
|
1903
|
+
"react-dom/client": "https://esm.sh/react-dom@19.2.4/client"
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
const hasMainFile = this.projectFiles.some(
|
|
1907
|
+
(f) => f.path === "src/main.tsx" || f.path === "src/main.ts" || f.path === "src/index.tsx" || f.path === "src/index.ts"
|
|
1908
|
+
);
|
|
1909
|
+
if (hasMainFile) {
|
|
1910
|
+
return `<!DOCTYPE html>
|
|
1911
|
+
<html lang="en">
|
|
1912
|
+
<head>
|
|
1913
|
+
<meta charset="UTF-8">
|
|
1914
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1915
|
+
<title>JXR App</title>
|
|
1916
|
+
<script type="importmap">
|
|
1917
|
+
${JSON.stringify(importMap)}
|
|
1918
|
+
<\/script>
|
|
1919
|
+
</head>
|
|
1920
|
+
<body>
|
|
1921
|
+
<div id="root"></div>
|
|
1922
|
+
<script type="module" src="/src/main.tsx"><\/script>
|
|
1923
|
+
${hmrScript}
|
|
1924
|
+
</body>
|
|
1925
|
+
</html>`;
|
|
1926
|
+
}
|
|
1927
|
+
return `<!DOCTYPE html>
|
|
1928
|
+
<html lang="en">
|
|
1929
|
+
<head>
|
|
1930
|
+
<meta charset="UTF-8">
|
|
1931
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1932
|
+
<title>JXR App</title>
|
|
1933
|
+
<script type="importmap">
|
|
1934
|
+
${JSON.stringify(importMap)}
|
|
1935
|
+
<\/script>
|
|
1936
|
+
</head>
|
|
1937
|
+
<body>
|
|
1938
|
+
<div id="root"></div>
|
|
1939
|
+
<script type="module">
|
|
1940
|
+
import { createRoot } from 'react-dom/client';
|
|
1941
|
+
import App from '/${this.entryPoint}';
|
|
1942
|
+
|
|
1943
|
+
const root = createRoot(document.getElementById('root'));
|
|
1944
|
+
root.render(App());
|
|
1945
|
+
<\/script>
|
|
1946
|
+
${hmrScript}
|
|
1947
|
+
</body>
|
|
1948
|
+
</html>`;
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
|
|
1036
1952
|
// src/deployer.ts
|
|
1953
|
+
import { readFile as readFile2, stat } from "fs/promises";
|
|
1954
|
+
import path2 from "path";
|
|
1955
|
+
import { spawn } from "child_process";
|
|
1037
1956
|
var JXRDeployer = class {
|
|
1038
1957
|
apiKey;
|
|
1039
|
-
apiUrl;
|
|
1040
1958
|
projectId;
|
|
1041
1959
|
constructor(apiKey, projectId) {
|
|
1042
1960
|
this.apiKey = apiKey;
|
|
1043
|
-
this.apiUrl = "https://api.jxrstudios.online/v1";
|
|
1044
1961
|
this.projectId = projectId || this.generateProjectId();
|
|
1045
1962
|
}
|
|
1046
1963
|
/**
|
|
1047
1964
|
* Deploy the current project to JXR infrastructure
|
|
1048
|
-
* @param projectPath Path to the built project (dist/ folder)
|
|
1049
|
-
* @param config Deployment configuration
|
|
1050
1965
|
*/
|
|
1051
1966
|
async deploy(projectPath, config = {}) {
|
|
1052
1967
|
const env = config.environment || "production";
|
|
1053
1968
|
const branch = config.branch || "main";
|
|
1969
|
+
const pid = config.projectId || this.projectId;
|
|
1054
1970
|
console.log(`\u{1F680} Deploying to JXR ${env}...`);
|
|
1971
|
+
const logs = [];
|
|
1055
1972
|
try {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
}
|
|
1064
|
-
const deployment = await this.triggerDeployment({
|
|
1065
|
-
projectId: config.projectId || this.projectId,
|
|
1066
|
-
environment: env,
|
|
1067
|
-
uploadId: uploadResult.uploadId
|
|
1068
|
-
});
|
|
1069
|
-
const result = await this.pollDeployment(deployment.deploymentId);
|
|
1973
|
+
await stat(projectPath);
|
|
1974
|
+
logs.push(`\u{1F4C1} Deploying from: ${projectPath}`);
|
|
1975
|
+
const tarballPath = await this.createTarball(projectPath);
|
|
1976
|
+
logs.push(`\u{1F4E6} Created tarball: ${tarballPath}`);
|
|
1977
|
+
const uploadResult = await this.uploadTarball(tarballPath, pid, env, branch);
|
|
1978
|
+
logs.push(`\u2601\uFE0F Uploaded to JXR`);
|
|
1979
|
+
const url = `https://${pid}.jxr.dev`;
|
|
1980
|
+
logs.push(`\u{1F310} Live at: ${url}`);
|
|
1070
1981
|
return {
|
|
1071
|
-
success:
|
|
1072
|
-
url
|
|
1073
|
-
deploymentId:
|
|
1982
|
+
success: true,
|
|
1983
|
+
url,
|
|
1984
|
+
deploymentId: uploadResult.deploymentId,
|
|
1074
1985
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1075
|
-
logs
|
|
1986
|
+
logs
|
|
1076
1987
|
};
|
|
1077
1988
|
} catch (error) {
|
|
1989
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
1990
|
+
logs.push(`\u274C Error: ${errorMsg}`);
|
|
1078
1991
|
return {
|
|
1079
1992
|
success: false,
|
|
1080
1993
|
url: "",
|
|
1081
1994
|
deploymentId: "",
|
|
1082
1995
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1083
|
-
logs
|
|
1996
|
+
logs
|
|
1084
1997
|
};
|
|
1085
1998
|
}
|
|
1086
1999
|
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Create a tarball of the build directory
|
|
2002
|
+
*/
|
|
2003
|
+
async createTarball(projectPath) {
|
|
2004
|
+
const tarballPath = path2.join(process.cwd(), ".jxr-deploy.tar.gz");
|
|
2005
|
+
return new Promise((resolve, reject) => {
|
|
2006
|
+
const tar = spawn("tar", ["-czf", tarballPath, "-C", projectPath, "."]);
|
|
2007
|
+
tar.on("close", (code) => {
|
|
2008
|
+
if (code === 0) {
|
|
2009
|
+
resolve(tarballPath);
|
|
2010
|
+
} else {
|
|
2011
|
+
reject(new Error(`tar exited with code ${code}`));
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
tar.on("error", reject);
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Upload tarball to JXR API
|
|
2019
|
+
*/
|
|
2020
|
+
async uploadTarball(tarballPath, projectId, environment, branch) {
|
|
2021
|
+
const formData = new FormData();
|
|
2022
|
+
const fileContent = await readFile2(tarballPath);
|
|
2023
|
+
const blob = new Blob([fileContent]);
|
|
2024
|
+
formData.append("build", blob, "build.tar.gz");
|
|
2025
|
+
formData.append("projectId", projectId);
|
|
2026
|
+
formData.append("environment", environment);
|
|
2027
|
+
formData.append("branch", branch);
|
|
2028
|
+
const response = await fetch("https://jxrstudios.workers.dev/v1/deployments", {
|
|
2029
|
+
method: "POST",
|
|
2030
|
+
headers: {
|
|
2031
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
2032
|
+
},
|
|
2033
|
+
body: formData
|
|
2034
|
+
});
|
|
2035
|
+
if (!response.ok) {
|
|
2036
|
+
throw new Error(`Upload failed: ${response.statusText}`);
|
|
2037
|
+
}
|
|
2038
|
+
const result = await response.json();
|
|
2039
|
+
return { deploymentId: result.deploymentId };
|
|
2040
|
+
}
|
|
1087
2041
|
/**
|
|
1088
2042
|
* Get deployment status
|
|
1089
2043
|
*/
|
|
1090
2044
|
async getStatus(deploymentId) {
|
|
1091
|
-
const response = await fetch(
|
|
2045
|
+
const response = await fetch(`https://jxrstudios.workers.dev/v1/deployments/${deploymentId}`, {
|
|
1092
2046
|
headers: {
|
|
1093
|
-
"Authorization": `Bearer ${this.apiKey}
|
|
1094
|
-
"Content-Type": "application/json"
|
|
2047
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
1095
2048
|
}
|
|
1096
2049
|
});
|
|
1097
2050
|
if (!response.ok) {
|
|
@@ -1104,7 +2057,7 @@ var JXRDeployer = class {
|
|
|
1104
2057
|
*/
|
|
1105
2058
|
async listDeployments(projectId) {
|
|
1106
2059
|
const pid = projectId || this.projectId;
|
|
1107
|
-
const response = await fetch(
|
|
2060
|
+
const response = await fetch(`https://jxrstudios.workers.dev/v1/projects/${pid}/deployments`, {
|
|
1108
2061
|
headers: {
|
|
1109
2062
|
"Authorization": `Bearer ${this.apiKey}`
|
|
1110
2063
|
}
|
|
@@ -1118,11 +2071,10 @@ var JXRDeployer = class {
|
|
|
1118
2071
|
* Rollback to a previous deployment
|
|
1119
2072
|
*/
|
|
1120
2073
|
async rollback(deploymentId) {
|
|
1121
|
-
const response = await fetch(
|
|
2074
|
+
const response = await fetch(`https://jxrstudios.workers.dev/v1/deployments/${deploymentId}/rollback`, {
|
|
1122
2075
|
method: "POST",
|
|
1123
2076
|
headers: {
|
|
1124
|
-
"Authorization": `Bearer ${this.apiKey}
|
|
1125
|
-
"Content-Type": "application/json"
|
|
2077
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
1126
2078
|
}
|
|
1127
2079
|
});
|
|
1128
2080
|
if (!response.ok) {
|
|
@@ -1137,70 +2089,6 @@ var JXRDeployer = class {
|
|
|
1137
2089
|
logs: ["Rollback successful"]
|
|
1138
2090
|
};
|
|
1139
2091
|
}
|
|
1140
|
-
/**
|
|
1141
|
-
* Delete a deployment
|
|
1142
|
-
*/
|
|
1143
|
-
async deleteDeployment(deploymentId) {
|
|
1144
|
-
const response = await fetch(`${this.apiUrl}/deployments/${deploymentId}`, {
|
|
1145
|
-
method: "DELETE",
|
|
1146
|
-
headers: {
|
|
1147
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
1148
|
-
}
|
|
1149
|
-
});
|
|
1150
|
-
if (!response.ok) {
|
|
1151
|
-
throw new Error(`Delete failed: ${response.statusText}`);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
// ─── Private Methods ────────────────────────────────────────────────────────
|
|
1155
|
-
async uploadBuild(projectPath, options) {
|
|
1156
|
-
const formData = new FormData();
|
|
1157
|
-
formData.append("projectId", options.projectId);
|
|
1158
|
-
formData.append("environment", options.environment);
|
|
1159
|
-
formData.append("branch", options.branch);
|
|
1160
|
-
const response = await fetch(`${this.apiUrl}/uploads`, {
|
|
1161
|
-
method: "POST",
|
|
1162
|
-
headers: {
|
|
1163
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
1164
|
-
},
|
|
1165
|
-
body: formData
|
|
1166
|
-
});
|
|
1167
|
-
if (!response.ok) {
|
|
1168
|
-
return { success: false, uploadId: "", error: response.statusText };
|
|
1169
|
-
}
|
|
1170
|
-
const data = await response.json();
|
|
1171
|
-
return { success: true, uploadId: data.uploadId };
|
|
1172
|
-
}
|
|
1173
|
-
async triggerDeployment(options) {
|
|
1174
|
-
const response = await fetch(`${this.apiUrl}/deployments`, {
|
|
1175
|
-
method: "POST",
|
|
1176
|
-
headers: {
|
|
1177
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
1178
|
-
"Content-Type": "application/json"
|
|
1179
|
-
},
|
|
1180
|
-
body: JSON.stringify({
|
|
1181
|
-
projectId: options.projectId,
|
|
1182
|
-
environment: options.environment,
|
|
1183
|
-
uploadId: options.uploadId
|
|
1184
|
-
})
|
|
1185
|
-
});
|
|
1186
|
-
if (!response.ok) {
|
|
1187
|
-
throw new Error(`Failed to trigger deployment: ${response.statusText}`);
|
|
1188
|
-
}
|
|
1189
|
-
return response.json();
|
|
1190
|
-
}
|
|
1191
|
-
async pollDeployment(deploymentId, maxAttempts = 60) {
|
|
1192
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
1193
|
-
const status = await this.getStatus(deploymentId);
|
|
1194
|
-
if (status.status === "deployed") {
|
|
1195
|
-
return { status: "deployed", url: status.url, logs: [] };
|
|
1196
|
-
}
|
|
1197
|
-
if (status.status === "failed") {
|
|
1198
|
-
return { status: "failed", logs: [status.error || "Deployment failed"] };
|
|
1199
|
-
}
|
|
1200
|
-
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1201
|
-
}
|
|
1202
|
-
throw new Error("Deployment timed out");
|
|
1203
|
-
}
|
|
1204
2092
|
generateProjectId() {
|
|
1205
2093
|
const timestamp = Date.now().toString(36);
|
|
1206
2094
|
const random = Math.random().toString(36).substring(2, 8);
|
|
@@ -1213,15 +2101,18 @@ var jxrDeployer = new JXRDeployer(
|
|
|
1213
2101
|
);
|
|
1214
2102
|
export {
|
|
1215
2103
|
DEFAULT_PROJECT_FILES,
|
|
2104
|
+
EnhancedTranspiler,
|
|
1216
2105
|
ImportMapBuilder,
|
|
1217
2106
|
JSXTransformer,
|
|
1218
2107
|
JXRCrypto,
|
|
1219
2108
|
JXRDeployer,
|
|
1220
2109
|
JXRRuntime,
|
|
2110
|
+
JXRServerManager,
|
|
1221
2111
|
MoQTransport,
|
|
1222
2112
|
ModuleCache,
|
|
1223
2113
|
VirtualFS,
|
|
1224
2114
|
WorkerPool,
|
|
2115
|
+
findOrCreateEntryPoint,
|
|
1225
2116
|
jxrCrypto,
|
|
1226
2117
|
jxrDeployer,
|
|
1227
2118
|
jxrRuntime
|