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 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(path5) {
447
- return path5.replace(/\\/g, this.SEPARATOR);
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(path5) {
453
- const normalized = this.normalize(path5);
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(path5) {
460
- const normalized = this.normalize(path5);
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 import_commander3 = require("commander");
497
+ var import_commander4 = require("commander");
498
498
 
499
499
  // src/cli/build.ts
500
500
  var import_commander = require("commander");
501
- var import_path = __toESM(require("path"));
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 buildCommand = new import_commander.Command("build").description("Build the project into a Roblox Model/Place file").argument("[project]", "Path to project.json file", "default.project.json").option("-o, --output <path>", "Output file path", "game.rbxl").action(async (projectFile, options) => {
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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 projectPath = import_path.default.resolve(cwd, projectFile);
506
- const outputPath = import_path.default.resolve(cwd, options.output);
507
- console.log(`[Roport] Building ${projectPath} to ${outputPath}...`);
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
- await import_promises.default.access(projectPath);
510
- } catch {
511
- console.error(`[Roport] Error: Project file not found at ${projectPath}`);
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 import_path4 = __toESM(require("path"));
907
+ var import_path8 = __toESM(require("path"));
523
908
 
524
909
  // src/server/Server.ts
525
- var import_fs = __toESM(require("fs"));
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
- constructor() {
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.wss = new import_ws.WebSocketServer({ port: this.port });
540
- this.wss.on("listening", () => {
541
- console.log(`[Roport] WebSocket Server listening on port ${this.port}`);
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", (path5) => this.emit("add", path5)).on("change", (path5) => this.emit("change", path5)).on("unlink", (path5) => this.emit("unlink", path5)).on("addDir", (path5) => this.emit("addDir", path5)).on("unlinkDir", (path5) => this.emit("unlinkDir", path5)).on("error", (error) => console.error(`[Roport] Watcher error: ${error}`));
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(path5, guid) {
659
- this.pathToGuid.set(path5, guid);
660
- this.guidToPath.set(guid, path5);
1126
+ set(path10, guid) {
1127
+ this.pathToGuid.set(path10, guid);
1128
+ this.guidToPath.set(guid, path10);
661
1129
  }
662
- removeByPath(path5) {
663
- const guid = this.pathToGuid.get(path5);
1130
+ removeByPath(path10) {
1131
+ const guid = this.pathToGuid.get(path10);
664
1132
  if (guid) {
665
- this.pathToGuid.delete(path5);
1133
+ this.pathToGuid.delete(path10);
666
1134
  this.guidToPath.delete(guid);
667
1135
  }
668
1136
  }
669
1137
  removeByGuid(guid) {
670
- const path5 = this.guidToPath.get(guid);
671
- if (path5) {
1138
+ const path10 = this.guidToPath.get(guid);
1139
+ if (path10) {
672
1140
  this.guidToPath.delete(guid);
673
- this.pathToGuid.delete(path5);
1141
+ this.pathToGuid.delete(path10);
674
1142
  }
675
1143
  }
676
- resolveGuid(path5) {
677
- return this.pathToGuid.get(path5);
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 fs4 = require("fs/promises");
688
- const path5 = require("path");
1155
+ const fs7 = require("fs/promises");
1156
+ const path10 = require("path");
689
1157
  try {
690
- const entries = await fs4.readdir(dir, { withFileTypes: true });
1158
+ const entries = await fs7.readdir(dir, { withFileTypes: true });
691
1159
  for (const entry of entries) {
692
- const fullPath = path5.join(dir, entry.name);
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 = path5.join(fullPath, "__meta.json");
696
- const metaContent = await fs4.readFile(metaPath, "utf-8");
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 import_path2 = __toESM(require("path"));
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 = import_path2.default.dirname(import_path2.default.dirname(filePath));
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(import_path2.default.dirname(filePath), meta.id);
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 import_promises2 = __toESM(require("fs/promises"));
758
- var import_path3 = __toESM(require("path"));
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 = import_path3.default.join(filePath, "__meta.json");
1275
+ const metaPath = import_path6.default.join(filePath, "__meta.json");
789
1276
  try {
790
1277
  let meta = {};
791
1278
  try {
792
- const content = await import_promises2.default.readFile(metaPath, "utf-8");
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 = import_path3.default.join(parentPath, name);
1313
+ const dirPath = import_path6.default.join(parentPath, name);
811
1314
  try {
812
- await import_promises2.default.mkdir(dirPath, { recursive: true });
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: properties || {}
1329
+ properties: props
818
1330
  };
819
- await this.writeMeta(import_path3.default.join(dirPath, "__meta.json"), meta);
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 (import_path3.default.relative(this.rootPath, filePath) === "") return;
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
- await import_promises2.default.rm(filePath, { recursive: true, force: true });
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
- console.log(`[Writer] Deleted ${filePath}`);
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 dirName = import_path3.default.basename(oldPath);
849
- const newPath = import_path3.default.join(newParentPath, dirName);
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 import_promises2.default.rename(oldPath, newPath);
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(path5, data) {
860
- await import_promises2.default.writeFile(path5, JSON.stringify(data, null, 2));
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 = import_fs.default.readFileSync(filePath, "utf-8");
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 = import_path4.default.resolve(process.cwd(), dir);
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 import_commander3.Command();
930
- program.name("roport").description("Roport V2 CLI - AI-First Roblox Development").version("0.1.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