roport 2.0.0 → 2.0.1
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/dist/index.js +829 -112
- package/dist/index.js.map +1 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -443,21 +443,21 @@ var require_PathUtils = __commonJS({
|
|
|
443
443
|
exports2.PathUtils = void 0;
|
|
444
444
|
var PathUtils = class {
|
|
445
445
|
static SEPARATOR = "/";
|
|
446
|
-
static normalize(
|
|
447
|
-
return
|
|
446
|
+
static normalize(path10) {
|
|
447
|
+
return path10.replace(/\\/g, this.SEPARATOR);
|
|
448
448
|
}
|
|
449
449
|
static join(...parts) {
|
|
450
450
|
return parts.map((p) => this.normalize(p)).join(this.SEPARATOR).replace(/\/+/g, this.SEPARATOR);
|
|
451
451
|
}
|
|
452
|
-
static getParent(
|
|
453
|
-
const normalized = this.normalize(
|
|
452
|
+
static getParent(path10) {
|
|
453
|
+
const normalized = this.normalize(path10);
|
|
454
454
|
const lastIndex = normalized.lastIndexOf(this.SEPARATOR);
|
|
455
455
|
if (lastIndex === -1)
|
|
456
456
|
return "";
|
|
457
457
|
return normalized.substring(0, lastIndex);
|
|
458
458
|
}
|
|
459
|
-
static getName(
|
|
460
|
-
const normalized = this.normalize(
|
|
459
|
+
static getName(path10) {
|
|
460
|
+
const normalized = this.normalize(path10);
|
|
461
461
|
const lastIndex = normalized.lastIndexOf(this.SEPARATOR);
|
|
462
462
|
return normalized.substring(lastIndex + 1);
|
|
463
463
|
}
|
|
@@ -494,61 +494,564 @@ var require_dist = __commonJS({
|
|
|
494
494
|
});
|
|
495
495
|
|
|
496
496
|
// src/cli/index.ts
|
|
497
|
-
var
|
|
497
|
+
var import_commander4 = require("commander");
|
|
498
498
|
|
|
499
499
|
// src/cli/build.ts
|
|
500
500
|
var import_commander = require("commander");
|
|
501
|
-
var
|
|
501
|
+
var import_path3 = __toESM(require("path"));
|
|
502
|
+
|
|
503
|
+
// src/build/Builder.ts
|
|
504
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
505
|
+
var import_path2 = __toESM(require("path"));
|
|
506
|
+
|
|
507
|
+
// src/schema/SchemaLoader.ts
|
|
508
|
+
var import_axios = __toESM(require("axios"));
|
|
502
509
|
var import_promises = __toESM(require("fs/promises"));
|
|
503
|
-
var
|
|
510
|
+
var import_path = __toESM(require("path"));
|
|
511
|
+
var SchemaLoader = class {
|
|
512
|
+
dumpUrl = "https://raw.githubusercontent.com/CloneTrooper1019/Roblox-Client-Tracker/roblox/API-Dump.json";
|
|
513
|
+
cachePath;
|
|
514
|
+
apiDump = null;
|
|
515
|
+
constructor(cacheDir) {
|
|
516
|
+
this.cachePath = import_path.default.join(cacheDir, "api-dump.json");
|
|
517
|
+
}
|
|
518
|
+
async load() {
|
|
519
|
+
try {
|
|
520
|
+
const data = await import_promises.default.readFile(this.cachePath, "utf-8");
|
|
521
|
+
this.apiDump = JSON.parse(data);
|
|
522
|
+
console.log("[Roport] Loaded API Dump from cache");
|
|
523
|
+
} catch (e) {
|
|
524
|
+
console.log("[Roport] Cache not found, fetching API Dump...");
|
|
525
|
+
await this.update();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async update() {
|
|
529
|
+
try {
|
|
530
|
+
const response = await import_axios.default.get(this.dumpUrl);
|
|
531
|
+
this.apiDump = response.data;
|
|
532
|
+
await import_promises.default.mkdir(import_path.default.dirname(this.cachePath), { recursive: true });
|
|
533
|
+
await import_promises.default.writeFile(this.cachePath, JSON.stringify(this.apiDump));
|
|
534
|
+
console.log("[Roport] API Dump updated and cached");
|
|
535
|
+
} catch (e) {
|
|
536
|
+
console.error("[Roport] Failed to fetch API Dump", e);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
getDump() {
|
|
540
|
+
return this.apiDump;
|
|
541
|
+
}
|
|
542
|
+
getPropertyType(className, propName) {
|
|
543
|
+
if (!this.apiDump) return null;
|
|
544
|
+
let currentClass = className;
|
|
545
|
+
while (currentClass && currentClass !== "<<ROOT>>") {
|
|
546
|
+
const classDef = this.apiDump.Classes.find((c) => c.Name === currentClass);
|
|
547
|
+
if (!classDef) break;
|
|
548
|
+
const prop = classDef.Members.find((m) => m.Name === propName && m.MemberType === "Property");
|
|
549
|
+
if (prop) {
|
|
550
|
+
return prop.ValueType.Name;
|
|
551
|
+
}
|
|
552
|
+
currentClass = classDef.Superclass;
|
|
553
|
+
}
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
getEnumValue(enumName, itemName) {
|
|
557
|
+
if (!this.apiDump) return 0;
|
|
558
|
+
const enumDef = this.apiDump.Enums.find((e) => e.Name === enumName);
|
|
559
|
+
if (!enumDef) return 0;
|
|
560
|
+
const item = enumDef.Items.find((i) => i.Name === itemName);
|
|
561
|
+
return item ? item.Value : 0;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
// src/codecs/Encoder/Vector3.ts
|
|
566
|
+
function encodeVector3(name, val) {
|
|
567
|
+
const x = val?.v?.[0] ?? 0;
|
|
568
|
+
const y = val?.v?.[1] ?? 0;
|
|
569
|
+
const z = val?.v?.[2] ?? 0;
|
|
570
|
+
return `<Vector3 name="${name}">
|
|
571
|
+
<X>${x}</X>
|
|
572
|
+
<Y>${y}</Y>
|
|
573
|
+
<Z>${z}</Z>
|
|
574
|
+
</Vector3>`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/codecs/Encoder/Vector2.ts
|
|
578
|
+
function encodeVector2(name, val) {
|
|
579
|
+
const x = val?.v?.[0] ?? 0;
|
|
580
|
+
const y = val?.v?.[1] ?? 0;
|
|
581
|
+
return `<Vector2 name="${name}">
|
|
582
|
+
<X>${x}</X>
|
|
583
|
+
<Y>${y}</Y>
|
|
584
|
+
</Vector2>`;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/codecs/Encoder/CFrame.ts
|
|
588
|
+
function encodeCFrame(name, val) {
|
|
589
|
+
const v = val?.v || [];
|
|
590
|
+
const x = v[0] ?? 0;
|
|
591
|
+
const y = v[1] ?? 0;
|
|
592
|
+
const z = v[2] ?? 0;
|
|
593
|
+
const r00 = v[3] ?? 1;
|
|
594
|
+
const r01 = v[4] ?? 0;
|
|
595
|
+
const r02 = v[5] ?? 0;
|
|
596
|
+
const r10 = v[6] ?? 0;
|
|
597
|
+
const r11 = v[7] ?? 1;
|
|
598
|
+
const r12 = v[8] ?? 0;
|
|
599
|
+
const r20 = v[9] ?? 0;
|
|
600
|
+
const r21 = v[10] ?? 0;
|
|
601
|
+
const r22 = v[11] ?? 1;
|
|
602
|
+
return `<CoordinateFrame name="${name}">
|
|
603
|
+
<X>${x}</X>
|
|
604
|
+
<Y>${y}</Y>
|
|
605
|
+
<Z>${z}</Z>
|
|
606
|
+
<R00>${r00}</R00>
|
|
607
|
+
<R01>${r01}</R01>
|
|
608
|
+
<R02>${r02}</R02>
|
|
609
|
+
<R10>${r10}</R10>
|
|
610
|
+
<R11>${r11}</R11>
|
|
611
|
+
<R12>${r12}</R12>
|
|
612
|
+
<R20>${r20}</R20>
|
|
613
|
+
<R21>${r21}</R21>
|
|
614
|
+
<R22>${r22}</R22>
|
|
615
|
+
</CoordinateFrame>`;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/codecs/Encoder/Color3.ts
|
|
619
|
+
function encodeColor3(name, val) {
|
|
620
|
+
const r = val?.v?.[0] ?? 0;
|
|
621
|
+
const g = val?.v?.[1] ?? 0;
|
|
622
|
+
const b = val?.v?.[2] ?? 0;
|
|
623
|
+
return `<Color3 name="${name}">
|
|
624
|
+
<R>${r}</R>
|
|
625
|
+
<G>${g}</G>
|
|
626
|
+
<B>${b}</B>
|
|
627
|
+
</Color3>`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/codecs/Encoder/UDim.ts
|
|
631
|
+
function encodeUDim(name, val) {
|
|
632
|
+
const s = val?.v?.[0] ?? 0;
|
|
633
|
+
const o = val?.v?.[1] ?? 0;
|
|
634
|
+
return `<UDim name="${name}">
|
|
635
|
+
<S>${s}</S>
|
|
636
|
+
<O>${o}</O>
|
|
637
|
+
</UDim>`;
|
|
638
|
+
}
|
|
639
|
+
function encodeUDim2(name, val) {
|
|
640
|
+
const xs = val?.v?.[0] ?? 0;
|
|
641
|
+
const xo = val?.v?.[1] ?? 0;
|
|
642
|
+
const ys = val?.v?.[2] ?? 0;
|
|
643
|
+
const yo = val?.v?.[3] ?? 0;
|
|
644
|
+
return `<UDim2 name="${name}">
|
|
645
|
+
<XS>${xs}</XS>
|
|
646
|
+
<XO>${xo}</XO>
|
|
647
|
+
<YS>${ys}</YS>
|
|
648
|
+
<YO>${yo}</YO>
|
|
649
|
+
</UDim2>`;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/codecs/Encoder/Rect.ts
|
|
653
|
+
function encodeRect(name, val) {
|
|
654
|
+
const x0 = val?.v?.[0] ?? 0;
|
|
655
|
+
const y0 = val?.v?.[1] ?? 0;
|
|
656
|
+
const x1 = val?.v?.[2] ?? 0;
|
|
657
|
+
const y1 = val?.v?.[3] ?? 0;
|
|
658
|
+
return `<Rect2D name="${name}">
|
|
659
|
+
<min>
|
|
660
|
+
<X>${x0}</X>
|
|
661
|
+
<Y>${y0}</Y>
|
|
662
|
+
</min>
|
|
663
|
+
<max>
|
|
664
|
+
<X>${x1}</X>
|
|
665
|
+
<Y>${y1}</Y>
|
|
666
|
+
</max>
|
|
667
|
+
</Rect2D>`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/codecs/Encoder/NumberRange.ts
|
|
671
|
+
function encodeNumberRange(name, val) {
|
|
672
|
+
const min = val?.v?.[0] ?? 0;
|
|
673
|
+
const max = val?.v?.[1] ?? 0;
|
|
674
|
+
return `<NumberRange name="${name}">${min} ${max}</NumberRange>`;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/codecs/Encoder/Ray.ts
|
|
678
|
+
function encodeRay(name, val) {
|
|
679
|
+
const ox = val?.v?.[0] ?? 0;
|
|
680
|
+
const oy = val?.v?.[1] ?? 0;
|
|
681
|
+
const oz = val?.v?.[2] ?? 0;
|
|
682
|
+
const dx = val?.v?.[3] ?? 0;
|
|
683
|
+
const dy = val?.v?.[4] ?? 0;
|
|
684
|
+
const dz = val?.v?.[5] ?? 0;
|
|
685
|
+
return `<Ray name="${name}">
|
|
686
|
+
<origin>
|
|
687
|
+
<X>${ox}</X>
|
|
688
|
+
<Y>${oy}</Y>
|
|
689
|
+
<Z>${oz}</Z>
|
|
690
|
+
</origin>
|
|
691
|
+
<direction>
|
|
692
|
+
<X>${dx}</X>
|
|
693
|
+
<Y>${dy}</Y>
|
|
694
|
+
<Z>${dz}</Z>
|
|
695
|
+
</direction>
|
|
696
|
+
</Ray>`;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/codecs/Encoder/Enum.ts
|
|
700
|
+
function encodeEnum(name, val, enumValue) {
|
|
701
|
+
const value = enumValue !== void 0 ? enumValue : 0;
|
|
702
|
+
return `<token name="${name}">${value}</token>`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/build/Builder.ts
|
|
706
|
+
var Builder = class {
|
|
707
|
+
constructor(rootPath) {
|
|
708
|
+
this.rootPath = rootPath;
|
|
709
|
+
this.schemaLoader = new SchemaLoader(import_path2.default.join(rootPath, ".roport", "cache"));
|
|
710
|
+
}
|
|
711
|
+
schemaLoader;
|
|
712
|
+
async build(outputPath) {
|
|
713
|
+
console.log(`[Builder] Starting build from ${this.rootPath}...`);
|
|
714
|
+
await this.schemaLoader.load();
|
|
715
|
+
const projectPath = import_path2.default.join(this.rootPath, "default.project.json");
|
|
716
|
+
let projectJson = null;
|
|
717
|
+
try {
|
|
718
|
+
const projectContent = await import_promises2.default.readFile(projectPath, "utf-8");
|
|
719
|
+
projectJson = JSON.parse(projectContent);
|
|
720
|
+
} catch {
|
|
721
|
+
}
|
|
722
|
+
let roots = [];
|
|
723
|
+
if (projectJson && projectJson.tree) {
|
|
724
|
+
console.log("[Builder] Found default.project.json");
|
|
725
|
+
for (const [key, value] of Object.entries(projectJson.tree)) {
|
|
726
|
+
if (key.startsWith("$")) continue;
|
|
727
|
+
const nodeDef = value;
|
|
728
|
+
const nodePath = nodeDef.$path ? import_path2.default.resolve(this.rootPath, nodeDef.$path) : null;
|
|
729
|
+
const className = nodeDef.$className || key;
|
|
730
|
+
if (nodePath) {
|
|
731
|
+
try {
|
|
732
|
+
const builtNode = await this.buildNode(nodePath);
|
|
733
|
+
if (builtNode) {
|
|
734
|
+
builtNode.className = className;
|
|
735
|
+
builtNode.name = key;
|
|
736
|
+
roots.push(builtNode);
|
|
737
|
+
}
|
|
738
|
+
} catch (e) {
|
|
739
|
+
console.warn(`[Builder] Missing path for ${key}: ${nodePath}`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
} else {
|
|
744
|
+
console.log("[Builder] using directory scan...");
|
|
745
|
+
const root = await this.buildNode(this.rootPath);
|
|
746
|
+
if (root) roots.push(root);
|
|
747
|
+
}
|
|
748
|
+
if (roots.length === 0) throw new Error("No buildable content found");
|
|
749
|
+
let xmlContent = `<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
|
|
750
|
+
`;
|
|
751
|
+
for (const root of roots) {
|
|
752
|
+
xmlContent += this.generateXmlInner(root, 1);
|
|
753
|
+
}
|
|
754
|
+
xmlContent += `</roblox>`;
|
|
755
|
+
await import_promises2.default.writeFile(outputPath, xmlContent);
|
|
756
|
+
console.log(`[Builder] Build written to ${outputPath}`);
|
|
757
|
+
}
|
|
758
|
+
async buildNode(dirPath) {
|
|
759
|
+
try {
|
|
760
|
+
await import_promises2.default.access(dirPath);
|
|
761
|
+
} catch {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
const metaPath = import_path2.default.join(dirPath, "__meta.json");
|
|
765
|
+
let meta = {};
|
|
766
|
+
try {
|
|
767
|
+
const metaContent = await import_promises2.default.readFile(metaPath, "utf-8");
|
|
768
|
+
meta = JSON.parse(metaContent);
|
|
769
|
+
} catch (e) {
|
|
770
|
+
meta = {
|
|
771
|
+
class: "Folder",
|
|
772
|
+
name: import_path2.default.basename(dirPath),
|
|
773
|
+
properties: {}
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
const node = {
|
|
777
|
+
className: meta.class || "Folder",
|
|
778
|
+
name: meta.name || import_path2.default.basename(dirPath),
|
|
779
|
+
properties: meta.properties || {},
|
|
780
|
+
children: []
|
|
781
|
+
};
|
|
782
|
+
const entries = await import_promises2.default.readdir(dirPath, { withFileTypes: true });
|
|
783
|
+
for (const entry of entries) {
|
|
784
|
+
const entryPath = import_path2.default.join(dirPath, entry.name);
|
|
785
|
+
if (entry.isDirectory()) {
|
|
786
|
+
if (entry.name === ".roport_trash" || entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "out") continue;
|
|
787
|
+
const childNode = await this.buildNode(entryPath);
|
|
788
|
+
if (childNode) {
|
|
789
|
+
node.children.push(childNode);
|
|
790
|
+
}
|
|
791
|
+
} else if (entry.isFile()) {
|
|
792
|
+
if (entry.name.endsWith(".luau")) {
|
|
793
|
+
const scriptNode = await this.scriptToNode(entryPath);
|
|
794
|
+
if (scriptNode) node.children.push(scriptNode);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return node;
|
|
799
|
+
}
|
|
800
|
+
async scriptToNode(filePath) {
|
|
801
|
+
const name = import_path2.default.basename(filePath, ".luau").replace(/\.(server|client)$/, "");
|
|
802
|
+
let className = "ModuleScript";
|
|
803
|
+
if (filePath.endsWith(".server.luau")) className = "Script";
|
|
804
|
+
else if (filePath.endsWith(".client.luau")) className = "LocalScript";
|
|
805
|
+
const source = await import_promises2.default.readFile(filePath, "utf-8");
|
|
806
|
+
return {
|
|
807
|
+
className,
|
|
808
|
+
name,
|
|
809
|
+
properties: {
|
|
810
|
+
Source: source
|
|
811
|
+
// Special handling for XML needed
|
|
812
|
+
},
|
|
813
|
+
children: []
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
generateXml(node, indentLevel = 0) {
|
|
817
|
+
if (indentLevel === 0) {
|
|
818
|
+
let xml = `<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
|
|
819
|
+
`;
|
|
820
|
+
xml += this.generateXmlInner(node, 1);
|
|
821
|
+
xml += `</roblox>`;
|
|
822
|
+
return xml;
|
|
823
|
+
}
|
|
824
|
+
return this.generateXmlInner(node, indentLevel);
|
|
825
|
+
}
|
|
826
|
+
generateXmlInner(node, indentLevel) {
|
|
827
|
+
const indent = " ".repeat(indentLevel);
|
|
828
|
+
let xml = `${indent}<Item class="${node.className}" referent="RBX${Math.random().toString(36).substr(2, 9)}">
|
|
829
|
+
`;
|
|
830
|
+
xml += `${indent} <Properties>
|
|
831
|
+
`;
|
|
832
|
+
xml += `${indent} <string name="Name">${node.name}</string>
|
|
833
|
+
`;
|
|
834
|
+
for (const [propName, propValue] of Object.entries(node.properties)) {
|
|
835
|
+
if (propName === "Source") {
|
|
836
|
+
const safeSource = propValue.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
837
|
+
xml += `${indent} <ProtectedString name="Source">${safeSource}</ProtectedString>
|
|
838
|
+
`;
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
const type = this.schemaLoader.getPropertyType(node.className, propName) || "string";
|
|
842
|
+
if (type === "Vector3") xml += `${indent} ${encodeVector3(propName, propValue)}
|
|
843
|
+
`;
|
|
844
|
+
else if (type === "Vector2") xml += `${indent} ${encodeVector2(propName, propValue)}
|
|
845
|
+
`;
|
|
846
|
+
else if (type === "CFrame") xml += `${indent} ${encodeCFrame(propName, propValue)}
|
|
847
|
+
`;
|
|
848
|
+
else if (type === "Color3") xml += `${indent} ${encodeColor3(propName, propValue)}
|
|
849
|
+
`;
|
|
850
|
+
else if (type === "UDim") xml += `${indent} ${encodeUDim(propName, propValue)}
|
|
851
|
+
`;
|
|
852
|
+
else if (type === "UDim2") xml += `${indent} ${encodeUDim2(propName, propValue)}
|
|
853
|
+
`;
|
|
854
|
+
else if (type === "Rect") xml += `${indent} ${encodeRect(propName, propValue)}
|
|
855
|
+
`;
|
|
856
|
+
else if (type === "NumberRange") xml += `${indent} ${encodeNumberRange(propName, propValue)}
|
|
857
|
+
`;
|
|
858
|
+
else if (type === "Ray") xml += `${indent} ${encodeRay(propName, propValue)}
|
|
859
|
+
`;
|
|
860
|
+
else if (type === "string") xml += `${indent} <string name="${propName}">${propValue}</string>
|
|
861
|
+
`;
|
|
862
|
+
else if (type === "bool") xml += `${indent} <bool name="${propName}">${propValue}</bool>
|
|
863
|
+
`;
|
|
864
|
+
else if (type === "double" || type === "float") xml += `${indent} <float name="${propName}">${propValue}</float>
|
|
865
|
+
`;
|
|
866
|
+
else if (type === "int" || type === "int64") xml += `${indent} <int64 name="${propName}">${propValue}</int64>
|
|
867
|
+
`;
|
|
868
|
+
else {
|
|
869
|
+
if (typeof propValue === "string" && propValue.startsWith("Enum.")) {
|
|
870
|
+
const parts = propValue.split(".");
|
|
871
|
+
if (parts.length === 3) {
|
|
872
|
+
const val = this.schemaLoader.getEnumValue(parts[1], parts[2]);
|
|
873
|
+
xml += `${indent} ${encodeEnum(propName, propValue, val)}
|
|
874
|
+
`;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
xml += `${indent} </Properties>
|
|
880
|
+
`;
|
|
881
|
+
for (const child of node.children) {
|
|
882
|
+
xml += this.generateXmlInner(child, indentLevel + 1);
|
|
883
|
+
}
|
|
884
|
+
xml += `${indent}</Item>
|
|
885
|
+
`;
|
|
886
|
+
return xml;
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
// src/cli/build.ts
|
|
891
|
+
var buildCommand = new import_commander.Command("build").description("Build the project into a Roblox Model/Place file").argument("[dir]", "Project directory", ".").option("-o, --output <path>", "Output file path", "game.rbxmx").action(async (dir, options) => {
|
|
504
892
|
const cwd = process.cwd();
|
|
505
|
-
const
|
|
506
|
-
const outputPath =
|
|
507
|
-
console.log(`[Roport] Building ${
|
|
893
|
+
const rootPath = import_path3.default.resolve(cwd, dir);
|
|
894
|
+
const outputPath = import_path3.default.resolve(cwd, options.output);
|
|
895
|
+
console.log(`[Roport] Building from ${rootPath} to ${outputPath}...`);
|
|
508
896
|
try {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
897
|
+
const builder = new Builder(rootPath);
|
|
898
|
+
await builder.build(outputPath);
|
|
899
|
+
} catch (e) {
|
|
900
|
+
console.error("[Roport] Build failed:", e);
|
|
512
901
|
process.exit(1);
|
|
513
902
|
}
|
|
514
|
-
console.log(`[Roport] Parsing project structure...`);
|
|
515
|
-
console.log(`[Roport] Compiling scripts...`);
|
|
516
|
-
console.log(`[Roport] Serializing to XML...`);
|
|
517
|
-
console.log(`[Roport] Build complete: ${outputPath}`);
|
|
518
903
|
});
|
|
519
904
|
|
|
520
905
|
// src/cli/serve.ts
|
|
521
906
|
var import_commander2 = require("commander");
|
|
522
|
-
var
|
|
907
|
+
var import_path8 = __toESM(require("path"));
|
|
523
908
|
|
|
524
909
|
// src/server/Server.ts
|
|
525
|
-
var
|
|
910
|
+
var import_path7 = __toESM(require("path"));
|
|
911
|
+
var import_fs2 = __toESM(require("fs"));
|
|
526
912
|
|
|
527
913
|
// src/net/ConnectionManager.ts
|
|
528
914
|
var import_ws = require("ws");
|
|
529
915
|
var import_events = require("events");
|
|
916
|
+
var import_http = __toESM(require("http"));
|
|
917
|
+
var import_express = __toESM(require("express"));
|
|
918
|
+
var import_cors = __toESM(require("cors"));
|
|
919
|
+
|
|
920
|
+
// src/ai/TreeSnapshot.ts
|
|
921
|
+
var import_fs = __toESM(require("fs"));
|
|
922
|
+
var import_path4 = __toESM(require("path"));
|
|
923
|
+
var TreeSnapshot = class {
|
|
924
|
+
constructor(rootPath) {
|
|
925
|
+
this.rootPath = rootPath;
|
|
926
|
+
}
|
|
927
|
+
async generate() {
|
|
928
|
+
const nodes = [];
|
|
929
|
+
if (!import_fs.default.existsSync(this.rootPath)) return nodes;
|
|
930
|
+
const entries = import_fs.default.readdirSync(this.rootPath);
|
|
931
|
+
for (const entry of entries) {
|
|
932
|
+
const fullPath = import_path4.default.join(this.rootPath, entry);
|
|
933
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
934
|
+
if (import_fs.default.statSync(fullPath).isDirectory()) {
|
|
935
|
+
const node = await this.scanRecursive(fullPath);
|
|
936
|
+
if (node) {
|
|
937
|
+
nodes.push(node);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return nodes;
|
|
942
|
+
}
|
|
943
|
+
async scanRecursive(dirPath) {
|
|
944
|
+
const metaPath = import_path4.default.join(dirPath, "__meta.json");
|
|
945
|
+
let meta = {};
|
|
946
|
+
if (import_fs.default.existsSync(metaPath)) {
|
|
947
|
+
try {
|
|
948
|
+
meta = JSON.parse(import_fs.default.readFileSync(metaPath, "utf-8"));
|
|
949
|
+
} catch (e) {
|
|
950
|
+
console.warn(`[Roport] Bad meta at ${dirPath}`);
|
|
951
|
+
}
|
|
952
|
+
} else {
|
|
953
|
+
meta = { class: "Folder", name: import_path4.default.basename(dirPath), id: "implicit-" + import_path4.default.basename(dirPath) };
|
|
954
|
+
}
|
|
955
|
+
const node = {
|
|
956
|
+
name: meta.name || import_path4.default.basename(dirPath),
|
|
957
|
+
className: meta.class || "Folder",
|
|
958
|
+
id: meta.id || meta.guid || "gen-" + Math.random(),
|
|
959
|
+
properties: meta.properties,
|
|
960
|
+
children: [],
|
|
961
|
+
filePath: dirPath
|
|
962
|
+
};
|
|
963
|
+
const entries = import_fs.default.readdirSync(dirPath);
|
|
964
|
+
for (const entry of entries) {
|
|
965
|
+
const fullPath = import_path4.default.join(dirPath, entry);
|
|
966
|
+
if (entry.startsWith(".") || entry === "__meta.json") continue;
|
|
967
|
+
const stat = import_fs.default.statSync(fullPath);
|
|
968
|
+
if (stat.isDirectory()) {
|
|
969
|
+
const child = await this.scanRecursive(fullPath);
|
|
970
|
+
if (child) {
|
|
971
|
+
node.children?.push(child);
|
|
972
|
+
}
|
|
973
|
+
} else if (entry.endsWith(".luau")) {
|
|
974
|
+
const name = import_path4.default.basename(entry, ".luau").replace(/\.(server|client)$/, "");
|
|
975
|
+
let className = "ModuleScript";
|
|
976
|
+
if (entry.endsWith(".server.luau")) className = "Script";
|
|
977
|
+
else if (entry.endsWith(".client.luau")) className = "LocalScript";
|
|
978
|
+
node.children?.push({
|
|
979
|
+
name,
|
|
980
|
+
className,
|
|
981
|
+
id: "script-" + name,
|
|
982
|
+
// Placeholder ID
|
|
983
|
+
children: [],
|
|
984
|
+
filePath: fullPath
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return node;
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
// src/net/ConnectionManager.ts
|
|
530
993
|
var ConnectionManager = class extends import_events.EventEmitter {
|
|
531
994
|
wss;
|
|
995
|
+
httpServer;
|
|
996
|
+
app;
|
|
532
997
|
clients = /* @__PURE__ */ new Set();
|
|
533
998
|
port = 34872;
|
|
534
|
-
|
|
999
|
+
messageQueue = [];
|
|
1000
|
+
pollWaiters = [];
|
|
1001
|
+
workspacePath;
|
|
1002
|
+
constructor(workspacePath) {
|
|
535
1003
|
super();
|
|
1004
|
+
this.workspacePath = workspacePath;
|
|
1005
|
+
this.app = (0, import_express.default)();
|
|
1006
|
+
this.config();
|
|
536
1007
|
this.startServer();
|
|
537
1008
|
}
|
|
1009
|
+
config() {
|
|
1010
|
+
this.app.use((0, import_cors.default)());
|
|
1011
|
+
this.app.use(import_express.default.json({ limit: "50mb" }));
|
|
1012
|
+
this.app.get("/", (req, res) => {
|
|
1013
|
+
res.send("Roport Sync Server");
|
|
1014
|
+
});
|
|
1015
|
+
this.app.get("/status", (req, res) => {
|
|
1016
|
+
res.json({ status: "running", version: "2.0.0" });
|
|
1017
|
+
});
|
|
1018
|
+
this.app.get("/tree", async (req, res) => {
|
|
1019
|
+
const snapshot = new TreeSnapshot(this.workspacePath);
|
|
1020
|
+
const tree = await snapshot.generate();
|
|
1021
|
+
res.json(tree);
|
|
1022
|
+
});
|
|
1023
|
+
this.app.get("/api/poll", (req, res) => {
|
|
1024
|
+
const messages = this.messageQueue;
|
|
1025
|
+
const sending = [...this.messageQueue];
|
|
1026
|
+
this.messageQueue = [];
|
|
1027
|
+
res.json({ events: sending.map((x) => x.event) });
|
|
1028
|
+
});
|
|
1029
|
+
this.app.post("/api/push", (req, res) => {
|
|
1030
|
+
try {
|
|
1031
|
+
const event = req.body;
|
|
1032
|
+
this.emit("event", event);
|
|
1033
|
+
res.json({ success: true });
|
|
1034
|
+
} catch (e) {
|
|
1035
|
+
console.error(e);
|
|
1036
|
+
res.status(500).json({ error: "Failed to process" });
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
538
1040
|
startServer() {
|
|
539
|
-
this.
|
|
540
|
-
this.wss.
|
|
541
|
-
|
|
1041
|
+
this.httpServer = import_http.default.createServer(this.app);
|
|
1042
|
+
this.wss = new import_ws.WebSocketServer({ server: this.httpServer });
|
|
1043
|
+
this.httpServer.listen(this.port, () => {
|
|
1044
|
+
console.log(`[Roport] Sync Server (HTTP/WS) listening on port ${this.port}`);
|
|
542
1045
|
});
|
|
543
1046
|
this.wss.on("connection", (ws) => {
|
|
544
|
-
console.log("[Roport] Client connected");
|
|
1047
|
+
console.log("[Roport] Client connected (WS)");
|
|
545
1048
|
this.clients.add(ws);
|
|
546
1049
|
this.setupHeartbeat(ws);
|
|
547
1050
|
ws.on("message", (message) => {
|
|
548
1051
|
this.handleMessage(ws, message);
|
|
549
1052
|
});
|
|
550
1053
|
ws.on("close", () => {
|
|
551
|
-
console.log("[Roport] Client disconnected");
|
|
1054
|
+
console.log("[Roport] Client disconnected (WS)");
|
|
552
1055
|
this.clients.delete(ws);
|
|
553
1056
|
});
|
|
554
1057
|
ws.on("error", (err) => {
|
|
@@ -581,43 +1084,8 @@ var ConnectionManager = class extends import_events.EventEmitter {
|
|
|
581
1084
|
client.send(payload);
|
|
582
1085
|
}
|
|
583
1086
|
});
|
|
584
|
-
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
// src/net/ApiServer.ts
|
|
588
|
-
var import_express = __toESM(require("express"));
|
|
589
|
-
var import_cors = __toESM(require("cors"));
|
|
590
|
-
var ApiServer = class {
|
|
591
|
-
app;
|
|
592
|
-
port = 3e3;
|
|
593
|
-
// API port different from WS
|
|
594
|
-
constructor() {
|
|
595
|
-
this.app = (0, import_express.default)();
|
|
596
|
-
this.config();
|
|
597
|
-
this.routes();
|
|
598
|
-
this.start();
|
|
599
|
-
}
|
|
600
|
-
config() {
|
|
601
|
-
this.app.use((0, import_cors.default)());
|
|
602
|
-
this.app.use(import_express.default.json());
|
|
603
|
-
}
|
|
604
|
-
routes() {
|
|
605
|
-
this.app.get("/status", (req, res) => {
|
|
606
|
-
res.json({ status: "running", version: "0.1.0" });
|
|
607
|
-
});
|
|
608
|
-
this.app.get("/tree", async (req, res) => {
|
|
609
|
-
res.json([
|
|
610
|
-
{ name: "Workspace", className: "Workspace", id: "workspace-guid", children: [
|
|
611
|
-
{ name: "Baseplate", className: "Part", id: "bp-guid" }
|
|
612
|
-
] },
|
|
613
|
-
{ name: "ReplicatedStorage", className: "ReplicatedStorage", id: "rs-guid", children: [] }
|
|
614
|
-
]);
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
start() {
|
|
618
|
-
this.app.listen(this.port, () => {
|
|
619
|
-
console.log(`[Roport] API Server running on http://localhost:${this.port}`);
|
|
620
|
-
});
|
|
1087
|
+
if (this.messageQueue.length > 1e3) this.messageQueue.shift();
|
|
1088
|
+
this.messageQueue.push({ id: Date.now().toString(), event });
|
|
621
1089
|
}
|
|
622
1090
|
};
|
|
623
1091
|
|
|
@@ -643,7 +1111,7 @@ var FileWatcher = class extends import_events2.EventEmitter {
|
|
|
643
1111
|
persistent: true,
|
|
644
1112
|
ignoreInitial: true
|
|
645
1113
|
});
|
|
646
|
-
this.watcher.on("add", (
|
|
1114
|
+
this.watcher.on("add", (path10) => this.emit("add", path10)).on("change", (path10) => this.emit("change", path10)).on("unlink", (path10) => this.emit("unlink", path10)).on("addDir", (path10) => this.emit("addDir", path10)).on("unlinkDir", (path10) => this.emit("unlinkDir", path10)).on("error", (error) => console.error(`[Roport] Watcher error: ${error}`));
|
|
647
1115
|
console.log(`[Roport] FileWatcher started on ${this.rootPath}`);
|
|
648
1116
|
}
|
|
649
1117
|
close() {
|
|
@@ -655,26 +1123,26 @@ var FileWatcher = class extends import_events2.EventEmitter {
|
|
|
655
1123
|
var PathMapper = class {
|
|
656
1124
|
pathToGuid = /* @__PURE__ */ new Map();
|
|
657
1125
|
guidToPath = /* @__PURE__ */ new Map();
|
|
658
|
-
set(
|
|
659
|
-
this.pathToGuid.set(
|
|
660
|
-
this.guidToPath.set(guid,
|
|
1126
|
+
set(path10, guid) {
|
|
1127
|
+
this.pathToGuid.set(path10, guid);
|
|
1128
|
+
this.guidToPath.set(guid, path10);
|
|
661
1129
|
}
|
|
662
|
-
removeByPath(
|
|
663
|
-
const guid = this.pathToGuid.get(
|
|
1130
|
+
removeByPath(path10) {
|
|
1131
|
+
const guid = this.pathToGuid.get(path10);
|
|
664
1132
|
if (guid) {
|
|
665
|
-
this.pathToGuid.delete(
|
|
1133
|
+
this.pathToGuid.delete(path10);
|
|
666
1134
|
this.guidToPath.delete(guid);
|
|
667
1135
|
}
|
|
668
1136
|
}
|
|
669
1137
|
removeByGuid(guid) {
|
|
670
|
-
const
|
|
671
|
-
if (
|
|
1138
|
+
const path10 = this.guidToPath.get(guid);
|
|
1139
|
+
if (path10) {
|
|
672
1140
|
this.guidToPath.delete(guid);
|
|
673
|
-
this.pathToGuid.delete(
|
|
1141
|
+
this.pathToGuid.delete(path10);
|
|
674
1142
|
}
|
|
675
1143
|
}
|
|
676
|
-
resolveGuid(
|
|
677
|
-
return this.pathToGuid.get(
|
|
1144
|
+
resolveGuid(path10) {
|
|
1145
|
+
return this.pathToGuid.get(path10);
|
|
678
1146
|
}
|
|
679
1147
|
resolvePath(guid) {
|
|
680
1148
|
return this.guidToPath.get(guid);
|
|
@@ -684,16 +1152,16 @@ var PathMapper = class {
|
|
|
684
1152
|
await this.scanDir(rootPath);
|
|
685
1153
|
}
|
|
686
1154
|
async scanDir(dir) {
|
|
687
|
-
const
|
|
688
|
-
const
|
|
1155
|
+
const fs7 = require("fs/promises");
|
|
1156
|
+
const path10 = require("path");
|
|
689
1157
|
try {
|
|
690
|
-
const entries = await
|
|
1158
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
691
1159
|
for (const entry of entries) {
|
|
692
|
-
const fullPath =
|
|
693
|
-
if (entry.isDirectory()) {
|
|
1160
|
+
const fullPath = path10.join(dir, entry.name);
|
|
1161
|
+
if (entry.isDirectory() && !fullPath.includes("node_modules") && !fullPath.includes(".git") && !fullPath.includes(".roport_trash")) {
|
|
694
1162
|
try {
|
|
695
|
-
const metaPath =
|
|
696
|
-
const metaContent = await
|
|
1163
|
+
const metaPath = path10.join(fullPath, "__meta.json");
|
|
1164
|
+
const metaContent = await fs7.readFile(metaPath, "utf-8");
|
|
697
1165
|
const meta = JSON.parse(metaContent);
|
|
698
1166
|
if (meta.id) {
|
|
699
1167
|
this.set(fullPath, meta.id);
|
|
@@ -711,7 +1179,7 @@ var PathMapper = class {
|
|
|
711
1179
|
|
|
712
1180
|
// src/sync/OpReconciler.ts
|
|
713
1181
|
var import_common = __toESM(require_dist());
|
|
714
|
-
var
|
|
1182
|
+
var import_path5 = __toESM(require("path"));
|
|
715
1183
|
var OpReconciler = class {
|
|
716
1184
|
constructor(pathMapper) {
|
|
717
1185
|
this.pathMapper = pathMapper;
|
|
@@ -725,11 +1193,12 @@ var OpReconciler = class {
|
|
|
725
1193
|
const existingPath = this.pathMapper.resolvePath(meta.id);
|
|
726
1194
|
const isNew = !existingPath;
|
|
727
1195
|
if (isNew) {
|
|
728
|
-
const parentDir =
|
|
1196
|
+
const parentDir = import_path5.default.dirname(import_path5.default.dirname(filePath));
|
|
729
1197
|
const parentGuid = this.pathMapper.resolveGuid(parentDir) || "NULL";
|
|
730
1198
|
ops.push({
|
|
731
1199
|
id: `op-${Date.now()}`,
|
|
732
1200
|
type: import_common.OpType.CreateInstance,
|
|
1201
|
+
rev: meta.rev,
|
|
733
1202
|
payload: {
|
|
734
1203
|
guid: meta.id,
|
|
735
1204
|
className: meta.class,
|
|
@@ -738,11 +1207,12 @@ var OpReconciler = class {
|
|
|
738
1207
|
properties: meta.properties || {}
|
|
739
1208
|
}
|
|
740
1209
|
});
|
|
741
|
-
this.pathMapper.set(
|
|
1210
|
+
this.pathMapper.set(import_path5.default.dirname(filePath), meta.id);
|
|
742
1211
|
} else {
|
|
743
1212
|
ops.push({
|
|
744
1213
|
id: `op-${Date.now()}`,
|
|
745
1214
|
type: import_common.OpType.SetProperties,
|
|
1215
|
+
rev: meta.rev,
|
|
746
1216
|
payload: {
|
|
747
1217
|
guid: meta.id,
|
|
748
1218
|
properties: meta.properties || {}
|
|
@@ -751,11 +1221,28 @@ var OpReconciler = class {
|
|
|
751
1221
|
}
|
|
752
1222
|
return ops;
|
|
753
1223
|
}
|
|
1224
|
+
reconcileScript(filePath, content) {
|
|
1225
|
+
const dirPath = import_path5.default.dirname(filePath);
|
|
1226
|
+
const guid = this.pathMapper.resolveGuid(dirPath);
|
|
1227
|
+
if (!guid) {
|
|
1228
|
+
return [];
|
|
1229
|
+
}
|
|
1230
|
+
return [{
|
|
1231
|
+
id: `op-${Date.now()}`,
|
|
1232
|
+
type: import_common.OpType.SetProperties,
|
|
1233
|
+
payload: {
|
|
1234
|
+
guid,
|
|
1235
|
+
properties: {
|
|
1236
|
+
Source: content
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}];
|
|
1240
|
+
}
|
|
754
1241
|
};
|
|
755
1242
|
|
|
756
1243
|
// src/sync/Writer.ts
|
|
757
|
-
var
|
|
758
|
-
var
|
|
1244
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
1245
|
+
var import_path6 = __toESM(require("path"));
|
|
759
1246
|
var import_common2 = __toESM(require_dist());
|
|
760
1247
|
var Writer = class {
|
|
761
1248
|
constructor(rootPath, pathMapper) {
|
|
@@ -765,7 +1252,7 @@ var Writer = class {
|
|
|
765
1252
|
async applyOp(op) {
|
|
766
1253
|
switch (op.type) {
|
|
767
1254
|
case import_common2.OpType.SetProperties:
|
|
768
|
-
await this.handleSetProperties(op.payload);
|
|
1255
|
+
await this.handleSetProperties(op.payload, op.baseRev);
|
|
769
1256
|
break;
|
|
770
1257
|
case import_common2.OpType.CreateInstance:
|
|
771
1258
|
await this.handleCreate(op.payload);
|
|
@@ -778,24 +1265,40 @@ var Writer = class {
|
|
|
778
1265
|
break;
|
|
779
1266
|
}
|
|
780
1267
|
}
|
|
781
|
-
async handleSetProperties(payload) {
|
|
1268
|
+
async handleSetProperties(payload, baseRev) {
|
|
782
1269
|
const { guid, properties } = payload;
|
|
783
1270
|
const filePath = this.pathMapper.resolvePath(guid);
|
|
784
1271
|
if (!filePath) {
|
|
785
1272
|
console.warn(`[Writer] Unknown GUID ${guid} for SetProperties`);
|
|
786
1273
|
return;
|
|
787
1274
|
}
|
|
788
|
-
const metaPath =
|
|
1275
|
+
const metaPath = import_path6.default.join(filePath, "__meta.json");
|
|
789
1276
|
try {
|
|
790
1277
|
let meta = {};
|
|
791
1278
|
try {
|
|
792
|
-
const content = await
|
|
1279
|
+
const content = await import_promises3.default.readFile(metaPath, "utf-8");
|
|
793
1280
|
meta = JSON.parse(content);
|
|
794
1281
|
} catch (e) {
|
|
795
1282
|
}
|
|
1283
|
+
if (baseRev !== void 0 && meta.rev !== void 0) {
|
|
1284
|
+
if (baseRev < meta.rev) {
|
|
1285
|
+
console.warn(`[Writer] REJECTED write to ${guid}: Stale revision (Base: ${baseRev}, Curr: ${meta.rev})`);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if ("Source" in properties) {
|
|
1290
|
+
const scriptName = this.getScriptFileName(meta.class);
|
|
1291
|
+
if (scriptName) {
|
|
1292
|
+
const sourcePath = import_path6.default.join(filePath, scriptName);
|
|
1293
|
+
await import_promises3.default.writeFile(sourcePath, properties.Source);
|
|
1294
|
+
console.log(`[Writer] Updated Script Source: ${sourcePath}`);
|
|
1295
|
+
delete properties.Source;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
796
1298
|
meta.properties = { ...meta.properties, ...properties };
|
|
1299
|
+
meta.rev = (meta.rev || 0) + 1;
|
|
797
1300
|
await this.writeMeta(metaPath, meta);
|
|
798
|
-
console.log(`[Writer] Updated ${metaPath}`);
|
|
1301
|
+
console.log(`[Writer] Updated ${metaPath} (Rev: ${meta.rev})`);
|
|
799
1302
|
} catch (e) {
|
|
800
1303
|
console.error(`[Writer] Failed to write properties for ${guid}`, e);
|
|
801
1304
|
}
|
|
@@ -807,31 +1310,64 @@ var Writer = class {
|
|
|
807
1310
|
const resolved = this.pathMapper.resolvePath(parentGuid);
|
|
808
1311
|
if (resolved) parentPath = resolved;
|
|
809
1312
|
}
|
|
810
|
-
const dirPath =
|
|
1313
|
+
const dirPath = import_path6.default.join(parentPath, name);
|
|
811
1314
|
try {
|
|
812
|
-
await
|
|
1315
|
+
await import_promises3.default.mkdir(dirPath, { recursive: true });
|
|
1316
|
+
let props = properties || {};
|
|
1317
|
+
if ("Source" in props) {
|
|
1318
|
+
const scriptName = this.getScriptFileName(className);
|
|
1319
|
+
if (scriptName) {
|
|
1320
|
+
await import_promises3.default.writeFile(import_path6.default.join(dirPath, scriptName), props.Source);
|
|
1321
|
+
props = { ...props };
|
|
1322
|
+
delete props.Source;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
813
1325
|
const meta = {
|
|
814
1326
|
id: guid,
|
|
815
1327
|
class: className,
|
|
816
1328
|
name,
|
|
817
|
-
properties:
|
|
1329
|
+
properties: props
|
|
818
1330
|
};
|
|
819
|
-
await this.writeMeta(
|
|
1331
|
+
await this.writeMeta(import_path6.default.join(dirPath, "__meta.json"), meta);
|
|
1332
|
+
if (parentGuid) {
|
|
1333
|
+
await this.updateParentOrder(parentGuid, guid, "add");
|
|
1334
|
+
}
|
|
820
1335
|
this.pathMapper.set(dirPath, guid);
|
|
821
1336
|
console.log(`[Writer] Created ${dirPath}`);
|
|
822
1337
|
} catch (e) {
|
|
823
1338
|
console.error(`[Writer] Failed to create ${dirPath}`, e);
|
|
824
1339
|
}
|
|
825
1340
|
}
|
|
1341
|
+
getScriptFileName(className) {
|
|
1342
|
+
switch (className) {
|
|
1343
|
+
case "Script":
|
|
1344
|
+
return "source.server.luau";
|
|
1345
|
+
case "LocalScript":
|
|
1346
|
+
return "source.client.luau";
|
|
1347
|
+
case "ModuleScript":
|
|
1348
|
+
return "source.luau";
|
|
1349
|
+
default:
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
826
1353
|
async handleDelete(payload) {
|
|
827
1354
|
const { guid } = payload;
|
|
828
1355
|
const filePath = this.pathMapper.resolvePath(guid);
|
|
829
1356
|
if (!filePath) return;
|
|
830
|
-
if (
|
|
1357
|
+
if (import_path6.default.relative(this.rootPath, filePath) === "") return;
|
|
1358
|
+
const parentDir = import_path6.default.dirname(filePath);
|
|
1359
|
+
const parentGuid = this.pathMapper.resolveGuid(parentDir);
|
|
831
1360
|
try {
|
|
832
|
-
|
|
1361
|
+
const trashRoot = import_path6.default.join(this.rootPath, ".roport_trash");
|
|
1362
|
+
const trashDir = import_path6.default.join(trashRoot, Date.now().toString());
|
|
1363
|
+
await import_promises3.default.mkdir(trashDir, { recursive: true });
|
|
1364
|
+
const trashPath = import_path6.default.join(trashDir, import_path6.default.basename(filePath));
|
|
1365
|
+
await import_promises3.default.rename(filePath, trashPath);
|
|
833
1366
|
this.pathMapper.removeByGuid(guid);
|
|
834
|
-
|
|
1367
|
+
if (parentGuid) {
|
|
1368
|
+
await this.updateParentOrder(parentGuid, guid, "remove");
|
|
1369
|
+
}
|
|
1370
|
+
console.log(`[Writer] Soft deleted ${filePath} -> ${trashPath}`);
|
|
835
1371
|
} catch (e) {
|
|
836
1372
|
console.error(`[Writer] Failed to delete ${filePath}`, e);
|
|
837
1373
|
}
|
|
@@ -845,19 +1381,91 @@ var Writer = class {
|
|
|
845
1381
|
if (resolved) newParentPath = resolved;
|
|
846
1382
|
}
|
|
847
1383
|
if (!oldPath) return;
|
|
848
|
-
const
|
|
849
|
-
const
|
|
1384
|
+
const oldParentDir = import_path6.default.dirname(oldPath);
|
|
1385
|
+
const oldParentGuid = this.pathMapper.resolveGuid(oldParentDir);
|
|
1386
|
+
const dirName = import_path6.default.basename(oldPath);
|
|
1387
|
+
const newPath = import_path6.default.join(newParentPath, dirName);
|
|
850
1388
|
try {
|
|
851
|
-
await
|
|
1389
|
+
await import_promises3.default.rename(oldPath, newPath);
|
|
852
1390
|
this.pathMapper.removeByGuid(guid);
|
|
853
1391
|
this.pathMapper.set(newPath, guid);
|
|
1392
|
+
if (oldParentGuid) {
|
|
1393
|
+
await this.updateParentOrder(oldParentGuid, guid, "remove");
|
|
1394
|
+
}
|
|
1395
|
+
if (newParentGuid) {
|
|
1396
|
+
await this.updateParentOrder(newParentGuid, guid, "add");
|
|
1397
|
+
}
|
|
854
1398
|
console.log(`[Writer] Moved ${oldPath} -> ${newPath}`);
|
|
855
1399
|
} catch (e) {
|
|
856
1400
|
console.error(`[Writer] Failed to move`, e);
|
|
857
1401
|
}
|
|
858
1402
|
}
|
|
859
|
-
async writeMeta(
|
|
860
|
-
await
|
|
1403
|
+
async writeMeta(path10, data) {
|
|
1404
|
+
await import_promises3.default.writeFile(path10, JSON.stringify(data, null, 2));
|
|
1405
|
+
}
|
|
1406
|
+
async updateParentOrder(parentGuid, childGuid, operation) {
|
|
1407
|
+
if (!parentGuid || parentGuid === "game" || parentGuid === "NULL") {
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
const parentPath = this.pathMapper.resolvePath(parentGuid);
|
|
1411
|
+
if (!parentPath) return;
|
|
1412
|
+
const metaPath = import_path6.default.join(parentPath, "__meta.json");
|
|
1413
|
+
try {
|
|
1414
|
+
let meta = {};
|
|
1415
|
+
try {
|
|
1416
|
+
const content = await import_promises3.default.readFile(metaPath, "utf-8");
|
|
1417
|
+
meta = JSON.parse(content);
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
let order = meta.order || [];
|
|
1422
|
+
if (operation === "add") {
|
|
1423
|
+
if (!order.includes(childGuid)) {
|
|
1424
|
+
order.push(childGuid);
|
|
1425
|
+
}
|
|
1426
|
+
} else if (operation === "remove") {
|
|
1427
|
+
order = order.filter((id) => id !== childGuid);
|
|
1428
|
+
}
|
|
1429
|
+
meta.order = order;
|
|
1430
|
+
meta.rev = (meta.rev || 0) + 1;
|
|
1431
|
+
await this.writeMeta(metaPath, meta);
|
|
1432
|
+
} catch (e) {
|
|
1433
|
+
console.error(`[Writer] Failed to update order for ${parentGuid}`, e);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
// src/server/ApiServer.ts
|
|
1439
|
+
var import_express2 = __toESM(require("express"));
|
|
1440
|
+
var import_cors2 = __toESM(require("cors"));
|
|
1441
|
+
var ApiServer = class {
|
|
1442
|
+
app;
|
|
1443
|
+
port = 3e3;
|
|
1444
|
+
treeSnapshot;
|
|
1445
|
+
constructor(workspacePath) {
|
|
1446
|
+
this.app = (0, import_express2.default)();
|
|
1447
|
+
this.app.use((0, import_cors2.default)());
|
|
1448
|
+
this.app.use(import_express2.default.json());
|
|
1449
|
+
this.treeSnapshot = new TreeSnapshot(workspacePath);
|
|
1450
|
+
this.setupRoutes();
|
|
1451
|
+
}
|
|
1452
|
+
setupRoutes() {
|
|
1453
|
+
this.app.get("/tree", async (req, res) => {
|
|
1454
|
+
try {
|
|
1455
|
+
const tree = await this.treeSnapshot.generate();
|
|
1456
|
+
res.json(tree);
|
|
1457
|
+
} catch (e) {
|
|
1458
|
+
res.status(500).json({ error: "Failed to generate tree" });
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
this.app.get("/health", (req, res) => {
|
|
1462
|
+
res.json({ status: "ok", version: "2.0.0" });
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
start() {
|
|
1466
|
+
this.app.listen(this.port, () => {
|
|
1467
|
+
console.log(`[ApiServer] Listening on http://localhost:${this.port}`);
|
|
1468
|
+
});
|
|
861
1469
|
}
|
|
862
1470
|
};
|
|
863
1471
|
|
|
@@ -865,28 +1473,38 @@ var Writer = class {
|
|
|
865
1473
|
var RoportServer = class {
|
|
866
1474
|
constructor(workspacePath) {
|
|
867
1475
|
this.workspacePath = workspacePath;
|
|
868
|
-
this.connectionManager = new ConnectionManager();
|
|
869
|
-
this.apiServer = new ApiServer();
|
|
1476
|
+
this.connectionManager = new ConnectionManager(workspacePath);
|
|
870
1477
|
this.pathMapper = new PathMapper();
|
|
871
1478
|
this.fileWatcher = new FileWatcher(workspacePath);
|
|
872
1479
|
this.opReconciler = new OpReconciler(this.pathMapper);
|
|
873
1480
|
this.writer = new Writer(workspacePath, this.pathMapper);
|
|
1481
|
+
this.apiServer = new ApiServer(workspacePath);
|
|
874
1482
|
}
|
|
875
1483
|
connectionManager;
|
|
876
|
-
apiServer;
|
|
877
1484
|
fileWatcher;
|
|
878
1485
|
pathMapper;
|
|
879
1486
|
opReconciler;
|
|
880
1487
|
writer;
|
|
1488
|
+
apiServer;
|
|
881
1489
|
async start() {
|
|
882
1490
|
console.log(`[Roport] Starting server in ${this.workspacePath}`);
|
|
1491
|
+
this.apiServer.start();
|
|
883
1492
|
console.log("[Roport] Rebuilding index...");
|
|
884
1493
|
await this.pathMapper.rebuildIndex(this.workspacePath);
|
|
1494
|
+
this.fileWatcher.on("addDir", (dirPath) => {
|
|
1495
|
+
});
|
|
1496
|
+
this.fileWatcher.on("unlinkDir", (dirPath) => {
|
|
1497
|
+
this.pathMapper.removeByPath(dirPath);
|
|
1498
|
+
});
|
|
885
1499
|
this.fileWatcher.on("change", async (filePath) => {
|
|
886
1500
|
if (filePath.endsWith("__meta.json")) {
|
|
887
1501
|
try {
|
|
888
|
-
const content =
|
|
1502
|
+
const content = import_fs2.default.readFileSync(filePath, "utf-8");
|
|
889
1503
|
const meta = JSON.parse(content);
|
|
1504
|
+
const dirPath = import_path7.default.dirname(filePath);
|
|
1505
|
+
if (meta.id) {
|
|
1506
|
+
this.pathMapper.set(dirPath, meta.id);
|
|
1507
|
+
}
|
|
890
1508
|
const ops = this.opReconciler.reconcileMeta(filePath, meta);
|
|
891
1509
|
ops.forEach((op) => {
|
|
892
1510
|
this.connectionManager.broadcast({
|
|
@@ -898,6 +1516,20 @@ var RoportServer = class {
|
|
|
898
1516
|
} catch (e) {
|
|
899
1517
|
console.error(`[Roport] Failed to process meta change ${filePath}`, e);
|
|
900
1518
|
}
|
|
1519
|
+
} else if (filePath.endsWith(".luau")) {
|
|
1520
|
+
try {
|
|
1521
|
+
const content = import_fs2.default.readFileSync(filePath, "utf-8");
|
|
1522
|
+
const ops = this.opReconciler.reconcileScript(filePath, content);
|
|
1523
|
+
ops.forEach((op) => {
|
|
1524
|
+
this.connectionManager.broadcast({
|
|
1525
|
+
channel: "dm",
|
|
1526
|
+
topic: "events",
|
|
1527
|
+
data: op
|
|
1528
|
+
});
|
|
1529
|
+
});
|
|
1530
|
+
} catch (e) {
|
|
1531
|
+
console.error(`[Roport] Failed to process script change ${filePath}`, e);
|
|
1532
|
+
}
|
|
901
1533
|
}
|
|
902
1534
|
});
|
|
903
1535
|
this.connectionManager.on("event", async (event) => {
|
|
@@ -919,15 +1551,100 @@ var RoportServer = class {
|
|
|
919
1551
|
|
|
920
1552
|
// src/cli/serve.ts
|
|
921
1553
|
var serveCommand = new import_commander2.Command("serve").description("Start the Roport sync server").argument("[dir]", "Project directory", ".").action(async (dir) => {
|
|
922
|
-
const workspacePath =
|
|
1554
|
+
const workspacePath = import_path8.default.resolve(process.cwd(), dir);
|
|
923
1555
|
const server = new RoportServer(workspacePath);
|
|
924
1556
|
await server.start();
|
|
925
1557
|
process.stdin.resume();
|
|
926
1558
|
});
|
|
927
1559
|
|
|
1560
|
+
// src/cli/init.ts
|
|
1561
|
+
var import_commander3 = require("commander");
|
|
1562
|
+
var import_path9 = __toESM(require("path"));
|
|
1563
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
1564
|
+
var initCommand = new import_commander3.Command("init").description("Initialize a new Roport project in the current directory").option("-f, --force", "Overwrite existing files", false).action(async (options) => {
|
|
1565
|
+
const cwd = process.cwd();
|
|
1566
|
+
console.log(`[Roport] Initializing project in ${cwd}...`);
|
|
1567
|
+
const dirs = [
|
|
1568
|
+
"src/Workspace",
|
|
1569
|
+
"src/ReplicatedStorage",
|
|
1570
|
+
"src/ServerScriptService",
|
|
1571
|
+
"src/ServerStorage",
|
|
1572
|
+
"src/StarterGui",
|
|
1573
|
+
"src/StarterPack",
|
|
1574
|
+
"src/StarterPlayer",
|
|
1575
|
+
"src/Lighting",
|
|
1576
|
+
"src/SoundService"
|
|
1577
|
+
];
|
|
1578
|
+
try {
|
|
1579
|
+
for (const dir of dirs) {
|
|
1580
|
+
const fullPath = import_path9.default.join(cwd, dir);
|
|
1581
|
+
await import_promises4.default.mkdir(fullPath, { recursive: true });
|
|
1582
|
+
}
|
|
1583
|
+
const projectJson = {
|
|
1584
|
+
"name": import_path9.default.basename(cwd),
|
|
1585
|
+
"tree": {
|
|
1586
|
+
"$className": "DataModel",
|
|
1587
|
+
"Workspace": {
|
|
1588
|
+
"$className": "Workspace",
|
|
1589
|
+
"$path": "src/Workspace"
|
|
1590
|
+
},
|
|
1591
|
+
"ReplicatedStorage": {
|
|
1592
|
+
"$className": "ReplicatedStorage",
|
|
1593
|
+
"$path": "src/ReplicatedStorage"
|
|
1594
|
+
},
|
|
1595
|
+
"ServerScriptService": {
|
|
1596
|
+
"$className": "ServerScriptService",
|
|
1597
|
+
"$path": "src/ServerScriptService"
|
|
1598
|
+
},
|
|
1599
|
+
"ServerStorage": {
|
|
1600
|
+
"$className": "ServerStorage",
|
|
1601
|
+
"$path": "src/ServerStorage"
|
|
1602
|
+
},
|
|
1603
|
+
"StarterGui": {
|
|
1604
|
+
"$className": "StarterGui",
|
|
1605
|
+
"$path": "src/StarterGui"
|
|
1606
|
+
},
|
|
1607
|
+
"StarterPack": {
|
|
1608
|
+
"$className": "StarterPack",
|
|
1609
|
+
"$path": "src/StarterPack"
|
|
1610
|
+
},
|
|
1611
|
+
"StarterPlayer": {
|
|
1612
|
+
"$className": "StarterPlayer",
|
|
1613
|
+
"$path": "src/StarterPlayer"
|
|
1614
|
+
},
|
|
1615
|
+
"Lighting": {
|
|
1616
|
+
"$className": "Lighting",
|
|
1617
|
+
"$path": "src/Lighting"
|
|
1618
|
+
},
|
|
1619
|
+
"SoundService": {
|
|
1620
|
+
"$className": "SoundService",
|
|
1621
|
+
"$path": "src/SoundService"
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
await import_promises4.default.writeFile(
|
|
1626
|
+
import_path9.default.join(cwd, "default.project.json"),
|
|
1627
|
+
JSON.stringify(projectJson, null, 2)
|
|
1628
|
+
);
|
|
1629
|
+
const gitignore = `
|
|
1630
|
+
node_modules/
|
|
1631
|
+
.roport_trash/
|
|
1632
|
+
*.rbxl
|
|
1633
|
+
*.rbxlx
|
|
1634
|
+
`.trim();
|
|
1635
|
+
await import_promises4.default.writeFile(import_path9.default.join(cwd, ".gitignore"), gitignore);
|
|
1636
|
+
console.log("[Roport] Project initialized successfully!");
|
|
1637
|
+
console.log('Run "roport serve" to start the sync server.');
|
|
1638
|
+
} catch (e) {
|
|
1639
|
+
console.error("[Roport] Failed to initialize project:", e);
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
|
|
928
1643
|
// src/cli/index.ts
|
|
929
|
-
var program = new
|
|
930
|
-
program.name("roport").description("Roport V2 CLI - AI-First Roblox Development").version("0.
|
|
1644
|
+
var program = new import_commander4.Command();
|
|
1645
|
+
program.name("roport").description("Roport V2 CLI - AI-First Roblox Development").version("2.0.0");
|
|
931
1646
|
program.addCommand(buildCommand);
|
|
932
1647
|
program.addCommand(serveCommand);
|
|
1648
|
+
program.addCommand(initCommand);
|
|
933
1649
|
program.parse(process.argv);
|
|
1650
|
+
//# sourceMappingURL=index.js.map
|