@isentinel/rojo-utils 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CLAUDE.md +40 -0
  2. package/coverage/base.css +224 -0
  3. package/coverage/block-navigation.js +87 -0
  4. package/coverage/clover.xml +388 -0
  5. package/coverage/coverage-final.json +7 -0
  6. package/coverage/favicon.png +0 -0
  7. package/coverage/index.html +191 -0
  8. package/coverage/loader.ts.html +220 -0
  9. package/coverage/mount-collector.ts.html +301 -0
  10. package/coverage/path-mapper.ts.html +220 -0
  11. package/coverage/prettify.css +1 -0
  12. package/coverage/prettify.js +2 -0
  13. package/coverage/rojo-resolver.ts.html +1900 -0
  14. package/coverage/rojo-tree.ts.html +559 -0
  15. package/coverage/sort-arrow-sprite.png +0 -0
  16. package/coverage/sorter.js +210 -0
  17. package/coverage/tree-mapper.ts.html +247 -0
  18. package/dist/index.d.mts +175 -0
  19. package/dist/index.d.mts.map +1 -0
  20. package/dist/index.mjs +551 -0
  21. package/dist/index.mjs.map +1 -0
  22. package/eslint.config.ts +23 -0
  23. package/out-tsc/index.d.ts +10 -0
  24. package/out-tsc/index.d.ts.map +1 -0
  25. package/out-tsc/index.js +6 -0
  26. package/out-tsc/loader.d.ts +3 -0
  27. package/out-tsc/loader.d.ts.map +1 -0
  28. package/out-tsc/loader.js +37 -0
  29. package/out-tsc/mount-collector.d.ts +10 -0
  30. package/out-tsc/mount-collector.d.ts.map +1 -0
  31. package/out-tsc/mount-collector.js +37 -0
  32. package/out-tsc/path-mapper.d.ts +4 -0
  33. package/out-tsc/path-mapper.d.ts.map +1 -0
  34. package/out-tsc/path-mapper.js +34 -0
  35. package/out-tsc/rojo-resolver.d.ts +110 -0
  36. package/out-tsc/rojo-resolver.d.ts.map +1 -0
  37. package/out-tsc/rojo-resolver.js +448 -0
  38. package/out-tsc/rojo-tree.d.ts +15 -0
  39. package/out-tsc/rojo-tree.d.ts.map +1 -0
  40. package/out-tsc/rojo-tree.js +106 -0
  41. package/out-tsc/tree-mapper.d.ts +4 -0
  42. package/out-tsc/tree-mapper.d.ts.map +1 -0
  43. package/out-tsc/tree-mapper.js +34 -0
  44. package/out-tsc/tsconfig.tsbuildinfo +1 -0
  45. package/out-tsc/types.d.ts +26 -0
  46. package/out-tsc/types.d.ts.map +1 -0
  47. package/out-tsc/types.js +1 -0
  48. package/out-tsc/vitest/eslint.config.d.ts +3 -0
  49. package/out-tsc/vitest/eslint.config.d.ts.map +1 -0
  50. package/out-tsc/vitest/eslint.shared.d.ts +13 -0
  51. package/out-tsc/vitest/eslint.shared.d.ts.map +1 -0
  52. package/out-tsc/vitest/libs/rojo-utils/eslint.config.d.ts +3 -0
  53. package/out-tsc/vitest/libs/rojo-utils/eslint.config.d.ts.map +1 -0
  54. package/out-tsc/vitest/libs/rojo-utils/src/loader.spec.d.ts +2 -0
  55. package/out-tsc/vitest/libs/rojo-utils/src/loader.spec.d.ts.map +1 -0
  56. package/out-tsc/vitest/libs/rojo-utils/src/mount-collector.spec.d.ts +2 -0
  57. package/out-tsc/vitest/libs/rojo-utils/src/mount-collector.spec.d.ts.map +1 -0
  58. package/out-tsc/vitest/libs/rojo-utils/src/path-mapper.spec.d.ts +2 -0
  59. package/out-tsc/vitest/libs/rojo-utils/src/path-mapper.spec.d.ts.map +1 -0
  60. package/out-tsc/vitest/libs/rojo-utils/src/rojo-resolver.spec.d.ts +2 -0
  61. package/out-tsc/vitest/libs/rojo-utils/src/rojo-resolver.spec.d.ts.map +1 -0
  62. package/out-tsc/vitest/libs/rojo-utils/src/rojo-tree.spec.d.ts +2 -0
  63. package/out-tsc/vitest/libs/rojo-utils/src/rojo-tree.spec.d.ts.map +1 -0
  64. package/out-tsc/vitest/libs/rojo-utils/src/tree-mapper.spec.d.ts +2 -0
  65. package/out-tsc/vitest/libs/rojo-utils/src/tree-mapper.spec.d.ts.map +1 -0
  66. package/out-tsc/vitest/libs/rojo-utils/test/setup/jest-extended.d.ts +2 -0
  67. package/out-tsc/vitest/libs/rojo-utils/test/setup/jest-extended.d.ts.map +1 -0
  68. package/out-tsc/vitest/libs/rojo-utils/tsconfig.spec.tsbuildinfo +1 -0
  69. package/out-tsc/vitest/libs/rojo-utils/tsdown.config.d.ts +3 -0
  70. package/out-tsc/vitest/libs/rojo-utils/tsdown.config.d.ts.map +1 -0
  71. package/out-tsc/vitest/libs/rojo-utils/vitest.config.d.ts +3 -0
  72. package/out-tsc/vitest/libs/rojo-utils/vitest.config.d.ts.map +1 -0
  73. package/out-tsc/vitest/src/loader.spec.d.ts +2 -0
  74. package/out-tsc/vitest/src/loader.spec.d.ts.map +1 -0
  75. package/out-tsc/vitest/src/mount-collector.spec.d.ts +2 -0
  76. package/out-tsc/vitest/src/mount-collector.spec.d.ts.map +1 -0
  77. package/out-tsc/vitest/src/path-mapper.spec.d.ts +2 -0
  78. package/out-tsc/vitest/src/path-mapper.spec.d.ts.map +1 -0
  79. package/out-tsc/vitest/src/rojo-resolver.spec.d.ts +2 -0
  80. package/out-tsc/vitest/src/rojo-resolver.spec.d.ts.map +1 -0
  81. package/out-tsc/vitest/src/rojo-tree.spec.d.ts +2 -0
  82. package/out-tsc/vitest/src/rojo-tree.spec.d.ts.map +1 -0
  83. package/out-tsc/vitest/src/tree-mapper.spec.d.ts +2 -0
  84. package/out-tsc/vitest/src/tree-mapper.spec.d.ts.map +1 -0
  85. package/out-tsc/vitest/test/setup/jest-extended.d.ts +2 -0
  86. package/out-tsc/vitest/test/setup/jest-extended.d.ts.map +1 -0
  87. package/out-tsc/vitest/tsconfig.spec.tsbuildinfo +1 -0
  88. package/out-tsc/vitest/tsdown.config.d.ts +3 -0
  89. package/out-tsc/vitest/tsdown.config.d.ts.map +1 -0
  90. package/out-tsc/vitest/vitest.config.d.ts +3 -0
  91. package/out-tsc/vitest/vitest.config.d.ts.map +1 -0
  92. package/package.json +48 -0
  93. package/project.json +25 -0
  94. package/src/index.ts +25 -0
  95. package/src/loader.spec.ts +240 -0
  96. package/src/loader.ts +45 -0
  97. package/src/mount-collector.spec.ts +272 -0
  98. package/src/mount-collector.ts +72 -0
  99. package/src/path-mapper.spec.ts +201 -0
  100. package/src/path-mapper.ts +45 -0
  101. package/src/rojo-resolver.spec.ts +956 -0
  102. package/src/rojo-resolver.ts +605 -0
  103. package/src/rojo-tree.spec.ts +622 -0
  104. package/src/rojo-tree.ts +162 -0
  105. package/src/tree-mapper.spec.ts +147 -0
  106. package/src/tree-mapper.ts +54 -0
  107. package/src/types.ts +29 -0
  108. package/test/setup/jest-extended.ts +4 -0
  109. package/test/setup/vitest-jest-extended.d.ts +1 -0
  110. package/tsconfig.json +13 -0
  111. package/tsconfig.lib.json +17 -0
  112. package/tsconfig.lib.tsbuildinfo +1 -0
  113. package/tsconfig.spec.json +26 -0
  114. package/tsdown.config.ts +15 -0
  115. package/vitest.config.ts +20 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,551 @@
1
+ import * as fs from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import path, { dirname, join, relative, resolve } from "node:path";
4
+ import { type } from "arktype";
5
+ //#region src/rojo-tree.ts
6
+ /**
7
+ * Like {@link resolveNestedProjects}, but also reports the absolute path of
8
+ * every nested project file inlined during resolution. Change-detection
9
+ * callers hash these so an edit to a nested `*.project.json` invalidates the
10
+ * build.
11
+ */
12
+ function resolveNestedProjectSources(tree, rootDirectory) {
13
+ const context = {
14
+ sources: /* @__PURE__ */ new Set(),
15
+ visited: /* @__PURE__ */ new Set()
16
+ };
17
+ const resolved = resolveTree(tree, rootDirectory, rootDirectory, context);
18
+ return {
19
+ projectFiles: [...context.sources],
20
+ tree: resolved
21
+ };
22
+ }
23
+ function resolveNestedProjects(tree, rootDirectory) {
24
+ return resolveNestedProjectSources(tree, rootDirectory).tree;
25
+ }
26
+ function collectPaths(node, result) {
27
+ for (const [key, value] of Object.entries(node)) if (key === "$path" && typeof value === "string") result.push(value.replaceAll("\\", "/"));
28
+ else if (typeof value === "object" && !Array.isArray(value) && !key.startsWith("$")) collectPaths(value, result);
29
+ }
30
+ function rebaseTreePaths(node, fromDirectory, toDirectory) {
31
+ const result = {};
32
+ for (const [key, value] of Object.entries(node)) {
33
+ if (key === "$path" && typeof value === "string") {
34
+ result[key] = relative(toDirectory, resolve(fromDirectory, value)).replaceAll("\\", "/");
35
+ continue;
36
+ }
37
+ if (key.startsWith("$") || typeof value !== "object" || Array.isArray(value)) {
38
+ result[key] = value;
39
+ continue;
40
+ }
41
+ result[key] = rebaseTreePaths(value, fromDirectory, toDirectory);
42
+ }
43
+ return result;
44
+ }
45
+ function nestedProjectPath(currentDirectory, value) {
46
+ if (value.endsWith(".project.json")) return join(currentDirectory, value);
47
+ const directoryDefault = join(currentDirectory, value, "default.project.json");
48
+ return existsSync(directoryDefault) ? directoryDefault : void 0;
49
+ }
50
+ function inlineNestedProject(projectPath, currentDirectory, originalRoot, context) {
51
+ const chain = new Set(context.visited);
52
+ chain.add(projectPath);
53
+ context.sources.add(projectPath);
54
+ let content;
55
+ try {
56
+ content = readFileSync(projectPath, "utf-8");
57
+ } catch (err) {
58
+ const relativePath = relative(currentDirectory, projectPath);
59
+ throw new Error(`Could not read nested Rojo project: ${relativePath}`, { cause: err });
60
+ }
61
+ let project;
62
+ try {
63
+ project = JSON.parse(content);
64
+ } catch (err) {
65
+ const relativePath = relative(currentDirectory, projectPath);
66
+ throw new Error(`Failed to parse nested Rojo project: ${relativePath}`, { cause: err });
67
+ }
68
+ return resolveTree(project.tree, dirname(projectPath), originalRoot, {
69
+ sources: context.sources,
70
+ visited: chain
71
+ });
72
+ }
73
+ function resolveRootRelativePath(currentDirectory, value, originalRoot) {
74
+ return relative(originalRoot, join(currentDirectory, value)).replaceAll("\\", "/");
75
+ }
76
+ function resolveTree(node, currentDirectory, originalRoot, context) {
77
+ const resolved = {};
78
+ for (const [key, value] of Object.entries(node)) {
79
+ if (key === "$path" && typeof value === "string") {
80
+ const projectPath = nestedProjectPath(currentDirectory, value);
81
+ if (projectPath === void 0) {
82
+ resolved[key] = resolveRootRelativePath(currentDirectory, value, originalRoot);
83
+ continue;
84
+ }
85
+ if (context.visited.has(projectPath)) throw new Error(`Circular project reference: ${value}`);
86
+ Object.assign(resolved, inlineNestedProject(projectPath, currentDirectory, originalRoot, context));
87
+ continue;
88
+ }
89
+ if (key.startsWith("$") || typeof value !== "object" || Array.isArray(value)) {
90
+ resolved[key] = value;
91
+ continue;
92
+ }
93
+ resolved[key] = resolveTree(value, currentDirectory, originalRoot, context);
94
+ }
95
+ return resolved;
96
+ }
97
+ //#endregion
98
+ //#region src/loader.ts
99
+ function loadRojoProject(projectPath) {
100
+ let content;
101
+ try {
102
+ content = readFileSync(projectPath, "utf-8");
103
+ } catch (err) {
104
+ throw new Error(`Could not read Rojo project: ${projectPath}`, { cause: err });
105
+ }
106
+ let parsed;
107
+ try {
108
+ parsed = JSON.parse(content);
109
+ } catch (err) {
110
+ throw new Error(`Failed to parse Rojo project: ${projectPath}`, { cause: err });
111
+ }
112
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`Rojo project must be a JSON object: ${projectPath}`);
113
+ const raw = parsed;
114
+ if (typeof raw["name"] !== "string" || raw["name"] === "") throw new Error(`Rojo project must have a non-empty "name" field: ${projectPath}`);
115
+ if (typeof raw["tree"] !== "object" || raw["tree"] === null || Array.isArray(raw["tree"])) throw new Error(`Rojo project must have a "tree" object: ${projectPath}`);
116
+ const rootDirectory = dirname(projectPath);
117
+ const tree = resolveNestedProjects(raw["tree"], rootDirectory);
118
+ return {
119
+ name: raw["name"],
120
+ raw,
121
+ servePort: typeof raw["servePort"] === "number" ? raw["servePort"] : void 0,
122
+ tree
123
+ };
124
+ }
125
+ //#endregion
126
+ //#region src/mount-collector.ts
127
+ const TrailingSlashPattern = /\/$/;
128
+ function collectMounts(node, currentDataModelPath, classify) {
129
+ const result = [];
130
+ walk(node, currentDataModelPath, classify, result);
131
+ return result;
132
+ }
133
+ function pruneAncestors(paths) {
134
+ return paths.filter((candidate) => {
135
+ return paths.every((other) => other === candidate || !candidate.startsWith(`${other}/`));
136
+ });
137
+ }
138
+ function addDirectoryMount(node, dataModelPath, classify, result) {
139
+ const rawPath = node.$path;
140
+ if (typeof rawPath !== "string") return;
141
+ if (rawPath.endsWith(".project.json")) return;
142
+ const fsPath = rawPath.replace(TrailingSlashPattern, "");
143
+ if (classify(fsPath) === "directory") result.push({
144
+ dataModelPath,
145
+ fsPath
146
+ });
147
+ }
148
+ function isTreeChild(value) {
149
+ return typeof value === "object" && value !== null && !Array.isArray(value);
150
+ }
151
+ function walk(node, currentDataModelPath, classify, result) {
152
+ for (const [key, value] of Object.entries(node)) {
153
+ if (key.startsWith("$") || !isTreeChild(value)) continue;
154
+ const childDataModelPath = currentDataModelPath === "" ? key : `${currentDataModelPath}/${key}`;
155
+ addDirectoryMount(value, childDataModelPath, classify, result);
156
+ walk(value, childDataModelPath, classify, result);
157
+ }
158
+ }
159
+ //#endregion
160
+ //#region src/tree-mapper.ts
161
+ const TRAILING_SLASH$1 = /\/$/;
162
+ function matchNodePath(childNode, targetPath, childDataModelPath) {
163
+ const nodePath = childNode.$path;
164
+ if (typeof nodePath !== "string") return;
165
+ const normalizedNodePath = nodePath.replace(TRAILING_SLASH$1, "");
166
+ if (normalizedNodePath === targetPath) return childDataModelPath;
167
+ if (targetPath.startsWith(`${normalizedNodePath}/`)) return `${childDataModelPath}/${targetPath.slice(normalizedNodePath.length + 1)}`;
168
+ }
169
+ function findInTree(node, targetPath, currentDataModelPath) {
170
+ for (const [key, value] of Object.entries(node)) {
171
+ if (key.startsWith("$") || typeof value !== "object") continue;
172
+ const childNode = value;
173
+ const childDataModelPath = currentDataModelPath === "" ? key : `${currentDataModelPath}/${key}`;
174
+ const pathMatch = matchNodePath(childNode, targetPath, childDataModelPath);
175
+ if (pathMatch !== void 0) return pathMatch;
176
+ const found = findInTree(childNode, targetPath, childDataModelPath);
177
+ if (found !== void 0) return found;
178
+ }
179
+ }
180
+ //#endregion
181
+ //#region src/path-mapper.ts
182
+ const TRAILING_SLASH = /\/$/;
183
+ function mapFsRootToDataModel(outDirectory, rojoTree) {
184
+ const normalized = outDirectory.replace(TRAILING_SLASH, "");
185
+ const result = findInTree(rojoTree, normalized, "");
186
+ if (result === void 0) {
187
+ const available = [];
188
+ collectPaths(rojoTree, available);
189
+ let message = `No Rojo tree mapping found for path: ${normalized}`;
190
+ if (available.length > 0) message += `\n\nAvailable $path entries: ${available.join(", ")}`;
191
+ throw new Error(message);
192
+ }
193
+ return result;
194
+ }
195
+ const LUAU_EXTENSIONS = /\.luau?$/;
196
+ const INIT_SUFFIX = /\/init$/;
197
+ function mapFsPathToDataModel(fsPath, rojoTree) {
198
+ const withoutInit = fsPath.replaceAll("\\", "/").replace(LUAU_EXTENSIONS, "").replace(INIT_SUFFIX, "");
199
+ const rootPath = rojoTree.$path;
200
+ if (typeof rootPath === "string") {
201
+ const normalizedRootPath = rootPath.replace(TRAILING_SLASH, "");
202
+ if (withoutInit === normalizedRootPath) return "";
203
+ if (withoutInit.startsWith(`${normalizedRootPath}/`)) return withoutInit.slice(normalizedRootPath.length + 1);
204
+ }
205
+ return findInTree(rojoTree, withoutInit, "");
206
+ }
207
+ //#endregion
208
+ //#region src/rojo-resolver.ts
209
+ const LUA_EXT = ".lua";
210
+ const LUAU_EXT = ".luau";
211
+ const ROJO_MODULE_EXTS = new Set([
212
+ ".json",
213
+ LUAU_EXT,
214
+ ".toml"
215
+ ]);
216
+ const ROJO_SCRIPT_EXTS = new Set([LUAU_EXT]);
217
+ const INIT_NAME = "init";
218
+ const SERVER_SUB_EXTENSION = ".server";
219
+ const CLIENT_SUB_EXTENSION = ".client";
220
+ const MODULE_SUB_EXTENSION = "";
221
+ const ROJO_FILE_REGEX = /^.+\.project\.json$/;
222
+ const ROJO_DEFAULT_NAME = "default.project.json";
223
+ const ROJO_OLD_NAME = "roblox-project.json";
224
+ const RbxType = {
225
+ LocalScript: 2,
226
+ ModuleScript: 0,
227
+ Script: 1,
228
+ Unknown: 3
229
+ };
230
+ const SUB_EXT_TYPE_MAP = new Map([
231
+ [CLIENT_SUB_EXTENSION, RbxType.LocalScript],
232
+ [MODULE_SUB_EXTENSION, RbxType.ModuleScript],
233
+ [SERVER_SUB_EXTENSION, RbxType.Script]
234
+ ]);
235
+ const DEFAULT_ISOLATED_CONTAINERS = [
236
+ ["StarterPack"],
237
+ ["StarterGui"],
238
+ ["StarterPlayer", "StarterPlayerScripts"],
239
+ ["StarterPlayer", "StarterCharacterScripts"],
240
+ ["StarterPlayer", "StarterCharacter"],
241
+ ["PluginDebugService"]
242
+ ];
243
+ const CLIENT_CONTAINERS = [
244
+ ["StarterPack"],
245
+ ["StarterGui"],
246
+ ["StarterPlayer"]
247
+ ];
248
+ const SERVER_CONTAINERS = [["ServerStorage"], ["ServerScriptService"]];
249
+ const FileRelation = {
250
+ InToIn: 3,
251
+ InToOut: 2,
252
+ OutToIn: 1,
253
+ OutToOut: 0
254
+ };
255
+ const NetworkType = {
256
+ Client: 1,
257
+ Server: 2,
258
+ Unknown: 0
259
+ };
260
+ function stripRojoExtensions(filePath) {
261
+ let stripped = filePath;
262
+ const extension = path.extname(stripped);
263
+ if (ROJO_MODULE_EXTS.has(extension)) {
264
+ stripped = stripped.slice(0, -extension.length);
265
+ if (ROJO_SCRIPT_EXTS.has(extension)) {
266
+ const subExtension = path.extname(stripped);
267
+ if (subExtension === SERVER_SUB_EXTENSION || subExtension === CLIENT_SUB_EXTENSION) stripped = stripped.slice(0, -subExtension.length);
268
+ }
269
+ }
270
+ return stripped;
271
+ }
272
+ function arrayStartsWith(a, b) {
273
+ const minLength = Math.min(a.length, b.length);
274
+ for (let index = 0; index < minLength; index++) if (a[index] !== b[index]) return false;
275
+ return true;
276
+ }
277
+ function isPathDescendantOf(filePath, directoryPath) {
278
+ return directoryPath === filePath || !path.relative(directoryPath, filePath).startsWith("..");
279
+ }
280
+ let rojoFileSchema;
281
+ function isValidRojoConfig(value) {
282
+ rojoFileSchema ??= type({
283
+ "name": "string",
284
+ "servePort?": "number",
285
+ "tree": "object"
286
+ });
287
+ return !(rojoFileSchema(value) instanceof type.errors);
288
+ }
289
+ function convertToLuau(filePath) {
290
+ const extension = path.extname(filePath);
291
+ if (extension === LUA_EXT) return filePath.slice(0, -extension.length) + LUAU_EXT;
292
+ return filePath;
293
+ }
294
+ const RbxPathParent = Symbol("Parent");
295
+ var RojoResolver = class RojoResolver {
296
+ rbxPath = new Array();
297
+ realpathCache = /* @__PURE__ */ new Map();
298
+ walkedConfigFilesInternal = /* @__PURE__ */ new Set();
299
+ walkedDirectoriesInternal = /* @__PURE__ */ new Set();
300
+ filePathToRbxPathMap = /* @__PURE__ */ new Map();
301
+ isolatedContainers = [...DEFAULT_ISOLATED_CONTAINERS];
302
+ partitions = new Array();
303
+ warnings = new Array();
304
+ isGame = false;
305
+ static findRojoConfigFilePath(projectPath) {
306
+ const warnings = new Array();
307
+ const defaultPath = path.join(projectPath, ROJO_DEFAULT_NAME);
308
+ if (fs.existsSync(defaultPath)) return {
309
+ path: defaultPath,
310
+ warnings
311
+ };
312
+ const candidates = new Array();
313
+ for (const fileName of fs.readdirSync(projectPath)) if (fileName !== ROJO_DEFAULT_NAME && (fileName === ROJO_OLD_NAME || ROJO_FILE_REGEX.test(fileName))) candidates.push(path.join(projectPath, fileName));
314
+ if (candidates.length > 1) warnings.push(`Multiple *.project.json files found, using ${candidates[0]}`);
315
+ return {
316
+ path: candidates[0],
317
+ warnings
318
+ };
319
+ }
320
+ static fromPath(rojoConfigFilePath) {
321
+ const resolver = new RojoResolver();
322
+ resolver.parseConfig(path.resolve(rojoConfigFilePath), true);
323
+ return resolver;
324
+ }
325
+ /**
326
+ * Restore a resolver from a {@link RojoResolverState} snapshot.
327
+ * @param state - The serialized resolver state to restore from.
328
+ * @returns A resolver equivalent to the one the state was captured from.
329
+ */
330
+ static fromState(state) {
331
+ const resolver = new RojoResolver();
332
+ resolver.partitions = state.partitions.map((partition) => {
333
+ return {
334
+ fsPath: partition.fsPath,
335
+ rbxPath: partition.rbxPath.slice()
336
+ };
337
+ });
338
+ const filePathToRbxPathMap = /* @__PURE__ */ new Map();
339
+ for (const [filePath, rbxPath] of state.filePathToRbxPathMap) filePathToRbxPathMap.set(filePath, rbxPath.slice());
340
+ resolver.filePathToRbxPathMap = filePathToRbxPathMap;
341
+ resolver.isolatedContainers = state.isolatedContainers.map((container) => {
342
+ return container.slice();
343
+ });
344
+ resolver.isGame = state.isGame;
345
+ resolver.warnings = state.warnings.slice();
346
+ for (const directory of state.walkedDirs) resolver.walkedDirectoriesInternal.add(directory);
347
+ for (const configFile of state.walkedConfigFiles) resolver.walkedConfigFilesInternal.add(configFile);
348
+ return resolver;
349
+ }
350
+ static fromTree(basePath, tree) {
351
+ const resolver = new RojoResolver();
352
+ resolver.parseTree(basePath, "", tree, true);
353
+ return resolver;
354
+ }
355
+ getFileRelation(fileRbxPath, moduleRbxPath) {
356
+ const fileContainer = this.getContainer(this.isolatedContainers, fileRbxPath);
357
+ const moduleContainer = this.getContainer(this.isolatedContainers, moduleRbxPath);
358
+ if (fileContainer && moduleContainer) {
359
+ if (fileContainer === moduleContainer) return FileRelation.InToIn;
360
+ return FileRelation.OutToIn;
361
+ }
362
+ if (fileContainer && !moduleContainer) return FileRelation.InToOut;
363
+ if (!fileContainer && moduleContainer) return FileRelation.OutToIn;
364
+ return FileRelation.OutToOut;
365
+ }
366
+ getNetworkType(rbxPath) {
367
+ if (this.getContainer(SERVER_CONTAINERS, rbxPath)) return NetworkType.Server;
368
+ if (this.getContainer(CLIENT_CONTAINERS, rbxPath)) return NetworkType.Client;
369
+ return NetworkType.Unknown;
370
+ }
371
+ getPartitions() {
372
+ return this.partitions;
373
+ }
374
+ getRbxPathFromFilePath(filePath) {
375
+ const resolved = convertToLuau(path.resolve(filePath));
376
+ const rbxPath = this.filePathToRbxPathMap.get(resolved);
377
+ if (rbxPath) return rbxPath;
378
+ const extension = path.extname(resolved);
379
+ for (const partition of this.partitions) if (isPathDescendantOf(resolved, partition.fsPath)) {
380
+ const stripped = stripRojoExtensions(resolved);
381
+ const relativePath = path.relative(partition.fsPath, stripped);
382
+ const relativeParts = relativePath === "" ? [] : relativePath.split(path.sep);
383
+ if (ROJO_SCRIPT_EXTS.has(extension) && relativeParts.at(-1) === INIT_NAME) relativeParts.pop();
384
+ return [...partition.rbxPath, ...relativeParts];
385
+ }
386
+ }
387
+ getRbxTypeFromFilePath(filePath) {
388
+ const resolved = convertToLuau(filePath);
389
+ const extension = path.extname(resolved);
390
+ const subExtension = path.extname(path.basename(resolved, extension));
391
+ if (ROJO_SCRIPT_EXTS.has(extension)) return SUB_EXT_TYPE_MAP.get(subExtension) ?? RbxType.Unknown;
392
+ return RbxType.ModuleScript;
393
+ }
394
+ /**
395
+ * Serialize this resolver to a {@link RojoResolverState} snapshot.
396
+ * @returns A serializable snapshot of this resolver's state.
397
+ */
398
+ getState() {
399
+ const filePathToRbxPathMap = new Array();
400
+ for (const [filePath, rbxPath] of this.filePathToRbxPathMap) filePathToRbxPathMap.push([filePath, rbxPath.slice()]);
401
+ return {
402
+ filePathToRbxPathMap,
403
+ isGame: this.isGame,
404
+ isolatedContainers: this.isolatedContainers.map((container) => container.slice()),
405
+ partitions: this.partitions.map((partition) => {
406
+ return {
407
+ fsPath: partition.fsPath,
408
+ rbxPath: partition.rbxPath.slice()
409
+ };
410
+ }),
411
+ walkedConfigFiles: [...this.walkedConfigFilesInternal],
412
+ walkedDirs: [...this.walkedDirectoriesInternal],
413
+ warnings: this.warnings.slice()
414
+ };
415
+ }
416
+ getWarnings() {
417
+ return this.warnings;
418
+ }
419
+ isIsolated(rbxPath) {
420
+ return this.getContainer(this.isolatedContainers, rbxPath) !== void 0;
421
+ }
422
+ static relative(rbxFrom, rbxTo) {
423
+ const maxLength = Math.max(rbxFrom.length, rbxTo.length);
424
+ let diffIndex = maxLength;
425
+ for (let index = 0; index < maxLength; index++) if (rbxFrom[index] !== rbxTo[index]) {
426
+ diffIndex = index;
427
+ break;
428
+ }
429
+ const result = new Array();
430
+ if (diffIndex < rbxFrom.length) for (let index = 0; index < rbxFrom.length - diffIndex; index++) result.push(RbxPathParent);
431
+ for (let index = diffIndex; index < rbxTo.length; index++) result.push(rbxTo[index]);
432
+ return result;
433
+ }
434
+ /**
435
+ * Create a synthetic RojoResolver for ProjectType.Package. Forces all imports
436
+ * to be relative.
437
+ * @param basePath - The base filesystem path the package resolves against.
438
+ * @returns A resolver that maps every file in the package relatively.
439
+ */
440
+ static synthetic(basePath) {
441
+ const resolver = new RojoResolver();
442
+ resolver.parseTree(basePath, "", { $path: basePath }, true);
443
+ return resolver;
444
+ }
445
+ get walkedConfigFiles() {
446
+ return this.walkedConfigFilesInternal;
447
+ }
448
+ get walkedDirectories() {
449
+ return this.walkedDirectoriesInternal;
450
+ }
451
+ cachedRealpath(targetPath) {
452
+ let resolved = this.realpathCache.get(targetPath);
453
+ if (resolved === void 0) {
454
+ resolved = fs.realpathSync(targetPath);
455
+ this.realpathCache.set(targetPath, resolved);
456
+ }
457
+ return resolved;
458
+ }
459
+ getContainer(from, rbxPath) {
460
+ if (this.isGame && rbxPath) {
461
+ for (const container of from) if (arrayStartsWith(rbxPath, container)) return container;
462
+ }
463
+ }
464
+ parseConfig(rojoConfigFilePath, doNotPush = false) {
465
+ if (!fs.existsSync(rojoConfigFilePath)) {
466
+ this.warn(`RojoResolver: Path does not exist "${rojoConfigFilePath}"`);
467
+ return;
468
+ }
469
+ const realPath = this.cachedRealpath(rojoConfigFilePath);
470
+ this.walkedConfigFilesInternal.add(realPath);
471
+ let configJson;
472
+ try {
473
+ configJson = JSON.parse(fs.readFileSync(realPath, "utf8"));
474
+ } catch {}
475
+ if (isValidRojoConfig(configJson)) this.parseTree(path.dirname(rojoConfigFilePath), configJson.name, configJson.tree, doNotPush);
476
+ else this.warn("RojoResolver: Invalid configuration!");
477
+ }
478
+ parsePath(itemPath) {
479
+ const luauPath = convertToLuau(itemPath);
480
+ if (ROJO_FILE_REGEX.test(path.basename(luauPath))) {
481
+ this.parseConfig(luauPath, true);
482
+ return;
483
+ }
484
+ const realPath = fs.existsSync(luauPath) ? this.cachedRealpath(luauPath) : luauPath;
485
+ const extension = path.extname(luauPath);
486
+ if (ROJO_MODULE_EXTS.has(extension)) this.filePathToRbxPathMap.set(luauPath, [...this.rbxPath]);
487
+ else {
488
+ const isDirectory = fs.existsSync(realPath) && fs.statSync(realPath).isDirectory();
489
+ if (isDirectory) this.walkedDirectoriesInternal.add(realPath);
490
+ if (isDirectory && fs.readdirSync(realPath).includes(ROJO_DEFAULT_NAME)) this.parseConfig(path.join(luauPath, ROJO_DEFAULT_NAME), true);
491
+ else {
492
+ this.partitions.unshift({
493
+ fsPath: luauPath,
494
+ rbxPath: [...this.rbxPath]
495
+ });
496
+ if (isDirectory) this.searchDirectory(luauPath);
497
+ }
498
+ }
499
+ }
500
+ parseTree(basePath, name, tree, doNotPush = false) {
501
+ if (!doNotPush) this.rbxPath.push(name);
502
+ if (tree.$path !== void 0) this.parsePath(path.resolve(basePath, typeof tree.$path === "string" ? tree.$path : tree.$path.optional));
503
+ if (tree.$className === "DataModel") this.isGame = true;
504
+ const childNames = Object.keys(tree).filter((value) => !value.startsWith("$"));
505
+ for (const childName of childNames) this.parseTree(basePath, childName, tree[childName]);
506
+ if (!doNotPush) this.rbxPath.pop();
507
+ }
508
+ searchChildren(directory, directoryEntries) {
509
+ const projectFiles = new Array();
510
+ const subDirectories = new Array();
511
+ for (const entry of directoryEntries) {
512
+ const childPath = path.join(directory, entry.name);
513
+ let isFile = entry.isFile();
514
+ let isDirectory = entry.isDirectory();
515
+ if (!isFile && !isDirectory) try {
516
+ const stat = fs.statSync(this.cachedRealpath(childPath));
517
+ isFile = stat.isFile();
518
+ isDirectory = stat.isDirectory();
519
+ } catch (err) {
520
+ this.warn(`RojoResolver: Failed to resolve "${childPath}" (${err.message})`);
521
+ continue;
522
+ }
523
+ if (isFile && ROJO_FILE_REGEX.test(entry.name)) projectFiles.push(childPath);
524
+ else if (isDirectory) subDirectories.push({
525
+ name: entry.name,
526
+ path: childPath
527
+ });
528
+ }
529
+ for (const childPath of projectFiles) this.parseConfig(childPath);
530
+ for (const { name, path: childPath } of subDirectories) this.searchDirectory(childPath, name);
531
+ }
532
+ searchDirectory(directory, item) {
533
+ const realPath = this.cachedRealpath(directory);
534
+ this.walkedDirectoriesInternal.add(realPath);
535
+ const directoryEntries = fs.readdirSync(directory, { withFileTypes: true });
536
+ if (directoryEntries.some((entry) => entry.name === ROJO_DEFAULT_NAME)) {
537
+ this.parseConfig(path.join(directory, ROJO_DEFAULT_NAME));
538
+ return;
539
+ }
540
+ if (item !== void 0) this.rbxPath.push(item);
541
+ this.searchChildren(directory, directoryEntries);
542
+ if (item !== void 0) this.rbxPath.pop();
543
+ }
544
+ warn(str) {
545
+ this.warnings.push(str);
546
+ }
547
+ };
548
+ //#endregion
549
+ export { FileRelation, NetworkType, RbxPathParent, RbxType, RojoResolver, collectMounts, collectPaths, findInTree, loadRojoProject, mapFsPathToDataModel, mapFsRootToDataModel, matchNodePath, pruneAncestors, rebaseTreePaths, resolveNestedProjectSources, resolveNestedProjects };
550
+
551
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["TRAILING_SLASH"],"sources":["../src/rojo-tree.ts","../src/loader.ts","../src/mount-collector.ts","../src/tree-mapper.ts","../src/path-mapper.ts","../src/rojo-resolver.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, relative, resolve } from \"node:path\";\n\nimport type { RojoTreeNode } from \"./types.ts\";\n\n/**\n * Mutable state threaded through {@link resolveTree} during a single\n * resolution.\n */\ninterface ResolveContext {\n\t/** Absolute paths of every nested project file inlined so far. */\n\tsources: Set<string>;\n\t/** Project files on the current chain, for circular-reference detection. */\n\tvisited: Set<string>;\n}\n\n/**\n * Like {@link resolveNestedProjects}, but also reports the absolute path of\n * every nested project file inlined during resolution. Change-detection\n * callers hash these so an edit to a nested `*.project.json` invalidates the\n * build.\n */\nexport function resolveNestedProjectSources(\n\ttree: RojoTreeNode,\n\trootDirectory: string,\n): { projectFiles: Array<string>; tree: RojoTreeNode } {\n\tconst context: ResolveContext = { sources: new Set<string>(), visited: new Set<string>() };\n\tconst resolved = resolveTree(tree, rootDirectory, rootDirectory, context);\n\treturn { projectFiles: [...context.sources], tree: resolved };\n}\n\nexport function resolveNestedProjects(tree: RojoTreeNode, rootDirectory: string): RojoTreeNode {\n\treturn resolveNestedProjectSources(tree, rootDirectory).tree;\n}\n\nexport function collectPaths(node: RojoTreeNode, result: Array<string>): void {\n\tfor (const [key, value] of Object.entries(node)) {\n\t\tif (key === \"$path\" && typeof value === \"string\") {\n\t\t\tresult.push(value.replaceAll(\"\\\\\", \"/\"));\n\t\t} else if (typeof value === \"object\" && !Array.isArray(value) && !key.startsWith(\"$\")) {\n\t\t\tcollectPaths(value as RojoTreeNode, result);\n\t\t}\n\t}\n}\n\nexport function rebaseTreePaths(\n\tnode: RojoTreeNode,\n\tfromDirectory: string,\n\ttoDirectory: string,\n): RojoTreeNode {\n\tconst result: RojoTreeNode = {};\n\n\tfor (const [key, value] of Object.entries(node)) {\n\t\tif (key === \"$path\" && typeof value === \"string\") {\n\t\t\tconst absolutePath = resolve(fromDirectory, value);\n\t\t\tresult[key] = relative(toDirectory, absolutePath).replaceAll(\"\\\\\", \"/\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (key.startsWith(\"$\") || typeof value !== \"object\" || Array.isArray(value)) {\n\t\t\tresult[key] = value;\n\t\t\tcontinue;\n\t\t}\n\n\t\tresult[key] = rebaseTreePaths(value as RojoTreeNode, fromDirectory, toDirectory);\n\t}\n\n\treturn result;\n}\n\nfunction nestedProjectPath(currentDirectory: string, value: string): string | undefined {\n\t// Resolve a `$path` string to the nested project file it should inline, or\n\t// undefined when the path is a plain source mount. Rojo treats a `$path`\n\t// pointing at a directory containing `default.project.json` as a nested\n\t// project (e.g. `$path: \"..\"` into a package root), so honor that alongside\n\t// explicit `*.project.json` references.\n\tif (value.endsWith(\".project.json\")) {\n\t\treturn join(currentDirectory, value);\n\t}\n\n\tconst directoryDefault = join(currentDirectory, value, \"default.project.json\");\n\treturn existsSync(directoryDefault) ? directoryDefault : undefined;\n}\n\nfunction inlineNestedProject(\n\tprojectPath: string,\n\tcurrentDirectory: string,\n\toriginalRoot: string,\n\tcontext: ResolveContext,\n): RojoTreeNode {\n\tconst chain = new Set(context.visited);\n\tchain.add(projectPath);\n\tcontext.sources.add(projectPath);\n\n\tlet content: string;\n\ttry {\n\t\tcontent = readFileSync(projectPath, \"utf-8\");\n\t} catch (err) {\n\t\tconst relativePath = relative(currentDirectory, projectPath);\n\t\tthrow new Error(`Could not read nested Rojo project: ${relativePath}`, { cause: err });\n\t}\n\n\tlet project: { tree: RojoTreeNode };\n\ttry {\n\t\tproject = JSON.parse(content) as { tree: RojoTreeNode };\n\t} catch (err) {\n\t\tconst relativePath = relative(currentDirectory, projectPath);\n\t\tthrow new Error(`Failed to parse nested Rojo project: ${relativePath}`, { cause: err });\n\t}\n\n\treturn resolveTree(project.tree, dirname(projectPath), originalRoot, {\n\t\tsources: context.sources,\n\t\tvisited: chain,\n\t});\n}\n\nfunction resolveRootRelativePath(\n\tcurrentDirectory: string,\n\tvalue: string,\n\toriginalRoot: string,\n): string {\n\tconst absolutePath = join(currentDirectory, value);\n\treturn relative(originalRoot, absolutePath).replaceAll(\"\\\\\", \"/\");\n}\n\nfunction resolveTree(\n\tnode: RojoTreeNode,\n\tcurrentDirectory: string,\n\toriginalRoot: string,\n\tcontext: ResolveContext,\n): RojoTreeNode {\n\tconst resolved: RojoTreeNode = {};\n\n\tfor (const [key, value] of Object.entries(node)) {\n\t\tif (key === \"$path\" && typeof value === \"string\") {\n\t\t\tconst projectPath = nestedProjectPath(currentDirectory, value);\n\t\t\tif (projectPath === undefined) {\n\t\t\t\tresolved[key] = resolveRootRelativePath(currentDirectory, value, originalRoot);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (context.visited.has(projectPath)) {\n\t\t\t\tthrow new Error(`Circular project reference: ${value}`);\n\t\t\t}\n\n\t\t\tObject.assign(\n\t\t\t\tresolved,\n\t\t\t\tinlineNestedProject(projectPath, currentDirectory, originalRoot, context),\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (key.startsWith(\"$\") || typeof value !== \"object\" || Array.isArray(value)) {\n\t\t\tresolved[key] = value;\n\t\t\tcontinue;\n\t\t}\n\n\t\tresolved[key] = resolveTree(value as RojoTreeNode, currentDirectory, originalRoot, context);\n\t}\n\n\treturn resolved;\n}\n","import { readFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nimport { resolveNestedProjects } from \"./rojo-tree.ts\";\nimport type { LoadedRojoProject, RojoTreeNode } from \"./types.ts\";\n\nexport function loadRojoProject(projectPath: string): LoadedRojoProject {\n\tlet content: string;\n\ttry {\n\t\tcontent = readFileSync(projectPath, \"utf-8\");\n\t} catch (err) {\n\t\tthrow new Error(`Could not read Rojo project: ${projectPath}`, { cause: err });\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(content);\n\t} catch (err) {\n\t\tthrow new Error(`Failed to parse Rojo project: ${projectPath}`, { cause: err });\n\t}\n\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new Error(`Rojo project must be a JSON object: ${projectPath}`);\n\t}\n\n\tconst raw = parsed as Record<string, unknown>;\n\n\tif (typeof raw[\"name\"] !== \"string\" || raw[\"name\"] === \"\") {\n\t\tthrow new Error(`Rojo project must have a non-empty \"name\" field: ${projectPath}`);\n\t}\n\n\tif (typeof raw[\"tree\"] !== \"object\" || raw[\"tree\"] === null || Array.isArray(raw[\"tree\"])) {\n\t\tthrow new Error(`Rojo project must have a \"tree\" object: ${projectPath}`);\n\t}\n\n\tconst rootDirectory = dirname(projectPath);\n\tconst tree = resolveNestedProjects(raw[\"tree\"] as RojoTreeNode, rootDirectory);\n\n\treturn {\n\t\tname: raw[\"name\"],\n\t\traw,\n\t\tservePort: typeof raw[\"servePort\"] === \"number\" ? raw[\"servePort\"] : undefined,\n\t\ttree,\n\t};\n}\n","import type { RojoTreeNode } from \"./types.ts\";\n\nexport type PathKind = \"directory\" | \"file\" | \"missing\";\n\nexport type PathClassifier = (fsPath: string) => PathKind;\n\nexport interface Mount {\n\tdataModelPath: string;\n\tfsPath: string;\n}\n\nconst TrailingSlashPattern = /\\/$/;\n\nexport function collectMounts(\n\tnode: RojoTreeNode,\n\tcurrentDataModelPath: string,\n\tclassify: PathClassifier,\n): Array<Mount> {\n\tconst result: Array<Mount> = [];\n\twalk(node, currentDataModelPath, classify, result);\n\treturn result;\n}\n\nexport function pruneAncestors(paths: Array<string>): Array<string> {\n\treturn paths.filter((candidate) => {\n\t\treturn paths.every((other) => other === candidate || !candidate.startsWith(`${other}/`));\n\t});\n}\n\nfunction addDirectoryMount(\n\tnode: RojoTreeNode,\n\tdataModelPath: string,\n\tclassify: PathClassifier,\n\tresult: Array<Mount>,\n): void {\n\tconst rawPath = node.$path;\n\tif (typeof rawPath !== \"string\") {\n\t\treturn;\n\t}\n\n\tif (rawPath.endsWith(\".project.json\")) {\n\t\treturn;\n\t}\n\n\tconst fsPath = rawPath.replace(TrailingSlashPattern, \"\");\n\tif (classify(fsPath) === \"directory\") {\n\t\tresult.push({ dataModelPath, fsPath });\n\t}\n}\n\nfunction isTreeChild(value: unknown): value is RojoTreeNode {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction walk(\n\tnode: RojoTreeNode,\n\tcurrentDataModelPath: string,\n\tclassify: PathClassifier,\n\tresult: Array<Mount>,\n): void {\n\tfor (const [key, value] of Object.entries(node)) {\n\t\tif (key.startsWith(\"$\") || !isTreeChild(value)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst childDataModelPath =\n\t\t\tcurrentDataModelPath === \"\" ? key : `${currentDataModelPath}/${key}`;\n\n\t\taddDirectoryMount(value, childDataModelPath, classify, result);\n\t\twalk(value, childDataModelPath, classify, result);\n\t}\n}\n","import type { RojoTreeNode } from \"./types.ts\";\n\nconst TRAILING_SLASH = /\\/$/;\n\nexport function matchNodePath(\n\tchildNode: RojoTreeNode,\n\ttargetPath: string,\n\tchildDataModelPath: string,\n): string | undefined {\n\tconst nodePath = childNode.$path;\n\tif (typeof nodePath !== \"string\") {\n\t\treturn undefined;\n\t}\n\n\tconst normalizedNodePath = nodePath.replace(TRAILING_SLASH, \"\");\n\tif (normalizedNodePath === targetPath) {\n\t\treturn childDataModelPath;\n\t}\n\n\tif (targetPath.startsWith(`${normalizedNodePath}/`)) {\n\t\tconst remainder = targetPath.slice(normalizedNodePath.length + 1);\n\t\treturn `${childDataModelPath}/${remainder}`;\n\t}\n\n\treturn undefined;\n}\n\nexport function findInTree(\n\tnode: RojoTreeNode,\n\ttargetPath: string,\n\tcurrentDataModelPath: string,\n): string | undefined {\n\tfor (const [key, value] of Object.entries(node)) {\n\t\tif (key.startsWith(\"$\") || typeof value !== \"object\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst childNode = value as RojoTreeNode;\n\t\tconst childDataModelPath =\n\t\t\tcurrentDataModelPath === \"\" ? key : `${currentDataModelPath}/${key}`;\n\n\t\tconst pathMatch = matchNodePath(childNode, targetPath, childDataModelPath);\n\t\tif (pathMatch !== undefined) {\n\t\t\treturn pathMatch;\n\t\t}\n\n\t\tconst found = findInTree(childNode, targetPath, childDataModelPath);\n\t\tif (found !== undefined) {\n\t\t\treturn found;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n","import { collectPaths } from \"./rojo-tree.ts\";\nimport { findInTree } from \"./tree-mapper.ts\";\nimport type { RojoTreeNode } from \"./types.ts\";\n\nconst TRAILING_SLASH = /\\/$/;\n\nexport function mapFsRootToDataModel(outDirectory: string, rojoTree: RojoTreeNode): string {\n\tconst normalized = outDirectory.replace(TRAILING_SLASH, \"\");\n\tconst result = findInTree(rojoTree, normalized, \"\");\n\tif (result === undefined) {\n\t\tconst available: Array<string> = [];\n\t\tcollectPaths(rojoTree, available);\n\n\t\tlet message = `No Rojo tree mapping found for path: ${normalized}`;\n\t\tif (available.length > 0) {\n\t\t\tmessage += `\\n\\nAvailable $path entries: ${available.join(\", \")}`;\n\t\t}\n\n\t\tthrow new Error(message);\n\t}\n\n\treturn result;\n}\n\nconst LUAU_EXTENSIONS = /\\.luau?$/;\nconst INIT_SUFFIX = /\\/init$/;\n\nexport function mapFsPathToDataModel(fsPath: string, rojoTree: RojoTreeNode): string | undefined {\n\tconst normalized = fsPath.replaceAll(\"\\\\\", \"/\").replace(LUAU_EXTENSIONS, \"\");\n\tconst withoutInit = normalized.replace(INIT_SUFFIX, \"\");\n\n\tconst rootPath = rojoTree.$path;\n\tif (typeof rootPath === \"string\") {\n\t\tconst normalizedRootPath = rootPath.replace(TRAILING_SLASH, \"\");\n\t\tif (withoutInit === normalizedRootPath) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tif (withoutInit.startsWith(`${normalizedRootPath}/`)) {\n\t\t\treturn withoutInit.slice(normalizedRootPath.length + 1);\n\t\t}\n\t}\n\n\treturn findInTree(rojoTree, withoutInit, \"\");\n}\n","import { type } from \"arktype\";\nimport type { Dirent } from \"node:fs\";\nimport * as fs from \"node:fs\";\nimport path from \"node:path\";\n\nconst LUA_EXT = \".lua\";\nconst LUAU_EXT = \".luau\";\nconst JSON_EXT = \".json\";\nconst TOML_EXT = \".toml\";\n\nconst ROJO_MODULE_EXTS = new Set([JSON_EXT, LUAU_EXT, TOML_EXT]);\nconst ROJO_SCRIPT_EXTS = new Set([LUAU_EXT]);\n\nconst INIT_NAME = \"init\";\n\nconst SERVER_SUB_EXTENSION = \".server\";\nconst CLIENT_SUB_EXTENSION = \".client\";\nconst MODULE_SUB_EXTENSION = \"\";\n\ninterface RojoTreeProperty {\n\tType: string;\n\tValue: unknown;\n}\n\ninterface RojoTreeMetadata {\n\t$className?: string;\n\t$ignoreUnknownInstances?: boolean;\n\t$path?: string | { optional: string };\n\t$properties?: Array<RojoTreeProperty>;\n}\n\ntype RojoTree = RojoTreeMembers & RojoTreeMetadata;\n\ninterface RojoTreeMembers {\n\t[name: string]: RojoTree;\n}\n\ninterface RojoFile {\n\tname: string;\n\tservePort?: number;\n\ttree: RojoTree;\n}\n\nconst ROJO_FILE_REGEX = /^.+\\.project\\.json$/;\nconst ROJO_DEFAULT_NAME = \"default.project.json\";\nconst ROJO_OLD_NAME = \"roblox-project.json\";\n\nexport const RbxType = {\n\tLocalScript: 2,\n\tModuleScript: 0,\n\tScript: 1,\n\tUnknown: 3,\n} as const;\n\nexport type RbxType = (typeof RbxType)[keyof typeof RbxType];\n\nconst SUB_EXT_TYPE_MAP = new Map<string, RbxType>([\n\t[CLIENT_SUB_EXTENSION, RbxType.LocalScript],\n\t[MODULE_SUB_EXTENSION, RbxType.ModuleScript],\n\t[SERVER_SUB_EXTENSION, RbxType.Script],\n]);\n\n/** Represents a roblox tree path. */\nexport type RbxPath = ReadonlyArray<string>;\nexport type RelativeRbxPath = ReadonlyArray<RbxPathParent | string>;\n\nexport interface PartitionInfo {\n\tfsPath: string;\n\trbxPath: RbxPath;\n}\n\nconst DEFAULT_ISOLATED_CONTAINERS: Array<RbxPath> = [\n\t[\"StarterPack\"],\n\t[\"StarterGui\"],\n\t[\"StarterPlayer\", \"StarterPlayerScripts\"],\n\t[\"StarterPlayer\", \"StarterCharacterScripts\"],\n\t[\"StarterPlayer\", \"StarterCharacter\"],\n\t[\"PluginDebugService\"],\n];\n\nconst CLIENT_CONTAINERS: Array<RbxPath> = [[\"StarterPack\"], [\"StarterGui\"], [\"StarterPlayer\"]];\nconst SERVER_CONTAINERS: Array<RbxPath> = [[\"ServerStorage\"], [\"ServerScriptService\"]];\n\nexport const FileRelation = {\n\tInToIn: 3,\n\tInToOut: 2,\n\tOutToIn: 1,\n\tOutToOut: 0,\n} as const;\n\nexport type FileRelation = (typeof FileRelation)[keyof typeof FileRelation];\n\nexport const NetworkType = {\n\tClient: 1,\n\tServer: 2,\n\tUnknown: 0,\n} as const;\n\nexport type NetworkType = (typeof NetworkType)[keyof typeof NetworkType];\n\n/** Serializable snapshot of a {@link RojoResolver} for disk caching. */\nexport interface RojoResolverState {\n\tfilePathToRbxPathMap: Array<[string, RbxPath]>;\n\tisGame: boolean;\n\tisolatedContainers: Array<RbxPath>;\n\tpartitions: Array<PartitionInfo>;\n\twalkedConfigFiles: Array<string>;\n\twalkedDirs: Array<string>;\n\twarnings: Array<string>;\n}\n\nfunction stripRojoExtensions(filePath: string): string {\n\tlet stripped = filePath;\n\tconst extension = path.extname(stripped);\n\tif (ROJO_MODULE_EXTS.has(extension)) {\n\t\tstripped = stripped.slice(0, -extension.length);\n\t\tif (ROJO_SCRIPT_EXTS.has(extension)) {\n\t\t\tconst subExtension = path.extname(stripped);\n\t\t\tif (subExtension === SERVER_SUB_EXTENSION || subExtension === CLIENT_SUB_EXTENSION) {\n\t\t\t\tstripped = stripped.slice(0, -subExtension.length);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn stripped;\n}\n\nfunction arrayStartsWith<T>(a: ReadonlyArray<T>, b: ReadonlyArray<T>): boolean {\n\tconst minLength = Math.min(a.length, b.length);\n\tfor (let index = 0; index < minLength; index++) {\n\t\tif (a[index] !== b[index]) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nfunction isPathDescendantOf(filePath: string, directoryPath: string): boolean {\n\treturn directoryPath === filePath || !path.relative(directoryPath, filePath).startsWith(\"..\");\n}\n\nlet rojoFileSchema: type.Any | undefined;\n\nfunction isValidRojoConfig(value: unknown): value is RojoFile {\n\t// Built lazily on first use (not at module top level) so consumers can\n\t// auto-mock this module without a hoisting TDZ on the arktype import, then\n\t// memoized so repeated tree-walk validations don't re-allocate the schema. We\n\t// validate only the fields the resolver reads; parseTree handles arbitrary\n\t// tree shapes defensively.\n\trojoFileSchema ??= type({\n\t\t\"name\": \"string\",\n\t\t\"servePort?\": \"number\",\n\t\t\"tree\": \"object\",\n\t});\n\treturn !(rojoFileSchema(value) instanceof type.errors);\n}\n\nfunction convertToLuau(filePath: string): string {\n\tconst extension = path.extname(filePath);\n\tif (extension === LUA_EXT) {\n\t\treturn filePath.slice(0, -extension.length) + LUAU_EXT;\n\t}\n\n\treturn filePath;\n}\n\nexport const RbxPathParent: unique symbol = Symbol(\"Parent\");\nexport type RbxPathParent = typeof RbxPathParent;\n\nexport class RojoResolver {\n\tprivate readonly rbxPath = new Array<string>();\n\tprivate readonly realpathCache = new Map<string, string>();\n\tprivate readonly walkedConfigFilesInternal = new Set<string>();\n\tprivate readonly walkedDirectoriesInternal = new Set<string>();\n\n\tprivate filePathToRbxPathMap = new Map<string, RbxPath>();\n\tprivate isolatedContainers = [...DEFAULT_ISOLATED_CONTAINERS];\n\tprivate partitions = new Array<PartitionInfo>();\n\tprivate warnings = new Array<string>();\n\n\tpublic isGame = false;\n\n\tpublic static findRojoConfigFilePath(projectPath: string): {\n\t\tpath: string | undefined;\n\t\twarnings: Array<string>;\n\t} {\n\t\tconst warnings = new Array<string>();\n\n\t\tconst defaultPath = path.join(projectPath, ROJO_DEFAULT_NAME);\n\t\tif (fs.existsSync(defaultPath)) {\n\t\t\treturn { path: defaultPath, warnings };\n\t\t}\n\n\t\tconst candidates = new Array<string | undefined>();\n\t\tfor (const fileName of fs.readdirSync(projectPath)) {\n\t\t\tif (\n\t\t\t\tfileName !== ROJO_DEFAULT_NAME &&\n\t\t\t\t(fileName === ROJO_OLD_NAME || ROJO_FILE_REGEX.test(fileName))\n\t\t\t) {\n\t\t\t\tcandidates.push(path.join(projectPath, fileName));\n\t\t\t}\n\t\t}\n\n\t\tif (candidates.length > 1) {\n\t\t\twarnings.push(`Multiple *.project.json files found, using ${candidates[0]}`);\n\t\t}\n\n\t\treturn { path: candidates[0], warnings };\n\t}\n\n\tpublic static fromPath(rojoConfigFilePath: string): RojoResolver {\n\t\tconst resolver = new RojoResolver();\n\t\tresolver.parseConfig(path.resolve(rojoConfigFilePath), true);\n\t\treturn resolver;\n\t}\n\n\t/**\n\t * Restore a resolver from a {@link RojoResolverState} snapshot.\n\t * @param state - The serialized resolver state to restore from.\n\t * @returns A resolver equivalent to the one the state was captured from.\n\t */\n\tpublic static fromState(state: RojoResolverState): RojoResolver {\n\t\tconst resolver = new RojoResolver();\n\n\t\tresolver.partitions = state.partitions.map((partition) => {\n\t\t\treturn { fsPath: partition.fsPath, rbxPath: partition.rbxPath.slice() };\n\t\t});\n\n\t\tconst filePathToRbxPathMap = new Map<string, RbxPath>();\n\t\tfor (const [filePath, rbxPath] of state.filePathToRbxPathMap) {\n\t\t\tfilePathToRbxPathMap.set(filePath, rbxPath.slice());\n\t\t}\n\n\t\tresolver.filePathToRbxPathMap = filePathToRbxPathMap;\n\t\tresolver.isolatedContainers = state.isolatedContainers.map((container) => {\n\t\t\treturn container.slice();\n\t\t},\n\t\t);\n\t\tresolver.isGame = state.isGame;\n\t\tresolver.warnings = state.warnings.slice();\n\n\t\tfor (const directory of state.walkedDirs) {\n\t\t\tresolver.walkedDirectoriesInternal.add(directory);\n\t\t}\n\n\t\tfor (const configFile of state.walkedConfigFiles) {\n\t\t\tresolver.walkedConfigFilesInternal.add(configFile);\n\t\t}\n\n\t\treturn resolver;\n\t}\n\n\tpublic static fromTree(basePath: string, tree: RojoTree): RojoResolver {\n\t\tconst resolver = new RojoResolver();\n\t\tresolver.parseTree(basePath, \"\", tree, true);\n\t\treturn resolver;\n\t}\n\n\tpublic getFileRelation(fileRbxPath: RbxPath, moduleRbxPath: RbxPath): FileRelation {\n\t\tconst fileContainer = this.getContainer(this.isolatedContainers, fileRbxPath);\n\t\tconst moduleContainer = this.getContainer(this.isolatedContainers, moduleRbxPath);\n\t\tif (fileContainer && moduleContainer) {\n\t\t\tif (fileContainer === moduleContainer) {\n\t\t\t\treturn FileRelation.InToIn;\n\t\t\t}\n\n\t\t\treturn FileRelation.OutToIn;\n\t\t}\n\n\t\tif (fileContainer && !moduleContainer) {\n\t\t\treturn FileRelation.InToOut;\n\t\t}\n\n\t\tif (!fileContainer && moduleContainer) {\n\t\t\treturn FileRelation.OutToIn;\n\t\t}\n\n\t\t// !fileContainer && !moduleContainer\n\t\treturn FileRelation.OutToOut;\n\t}\n\n\tpublic getNetworkType(rbxPath: RbxPath): NetworkType {\n\t\tif (this.getContainer(SERVER_CONTAINERS, rbxPath)) {\n\t\t\treturn NetworkType.Server;\n\t\t}\n\n\t\tif (this.getContainer(CLIENT_CONTAINERS, rbxPath)) {\n\t\t\treturn NetworkType.Client;\n\t\t}\n\n\t\treturn NetworkType.Unknown;\n\t}\n\n\tpublic getPartitions(): ReadonlyArray<PartitionInfo> {\n\t\treturn this.partitions;\n\t}\n\n\tpublic getRbxPathFromFilePath(filePath: string): RbxPath | undefined {\n\t\tconst resolved = convertToLuau(path.resolve(filePath));\n\n\t\tconst rbxPath = this.filePathToRbxPathMap.get(resolved);\n\t\tif (rbxPath) {\n\t\t\treturn rbxPath;\n\t\t}\n\n\t\tconst extension = path.extname(resolved);\n\t\tfor (const partition of this.partitions) {\n\t\t\tif (isPathDescendantOf(resolved, partition.fsPath)) {\n\t\t\t\tconst stripped = stripRojoExtensions(resolved);\n\t\t\t\tconst relativePath = path.relative(partition.fsPath, stripped);\n\t\t\t\tconst relativeParts = relativePath === \"\" ? [] : relativePath.split(path.sep);\n\t\t\t\tif (ROJO_SCRIPT_EXTS.has(extension) && relativeParts.at(-1) === INIT_NAME) {\n\t\t\t\t\trelativeParts.pop();\n\t\t\t\t}\n\n\t\t\t\treturn [...partition.rbxPath, ...relativeParts];\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tpublic getRbxTypeFromFilePath(filePath: string): RbxType {\n\t\tconst resolved = convertToLuau(filePath);\n\t\tconst extension = path.extname(resolved);\n\t\tconst subExtension = path.extname(path.basename(resolved, extension));\n\t\tif (ROJO_SCRIPT_EXTS.has(extension)) {\n\t\t\treturn SUB_EXT_TYPE_MAP.get(subExtension) ?? RbxType.Unknown;\n\t\t}\n\n\t\t// non-script exts cannot use .server, .client, etc.\n\t\treturn RbxType.ModuleScript;\n\t}\n\n\t/**\n\t * Serialize this resolver to a {@link RojoResolverState} snapshot.\n\t * @returns A serializable snapshot of this resolver's state.\n\t */\n\tpublic getState(): RojoResolverState {\n\t\tconst filePathToRbxPathMap = new Array<[string, RbxPath]>();\n\t\tfor (const [filePath, rbxPath] of this.filePathToRbxPathMap) {\n\t\t\tfilePathToRbxPathMap.push([filePath, rbxPath.slice()]);\n\t\t}\n\n\t\treturn {\n\t\t\tfilePathToRbxPathMap,\n\t\t\tisGame: this.isGame,\n\t\t\tisolatedContainers: this.isolatedContainers.map((container) => container.slice()),\n\t\t\tpartitions: this.partitions.map((partition) => {\n\t\t\t\treturn { fsPath: partition.fsPath, rbxPath: partition.rbxPath.slice() };\n\t\t\t}),\n\t\t\twalkedConfigFiles: [...this.walkedConfigFilesInternal],\n\t\t\twalkedDirs: [...this.walkedDirectoriesInternal],\n\t\t\twarnings: this.warnings.slice(),\n\t\t};\n\t}\n\n\tpublic getWarnings(): ReadonlyArray<string> {\n\t\treturn this.warnings;\n\t}\n\n\tpublic isIsolated(rbxPath: RbxPath): boolean {\n\t\treturn this.getContainer(this.isolatedContainers, rbxPath) !== undefined;\n\t}\n\n\tpublic static relative(rbxFrom: RbxPath, rbxTo: RbxPath): RelativeRbxPath {\n\t\tconst maxLength = Math.max(rbxFrom.length, rbxTo.length);\n\t\tlet diffIndex = maxLength;\n\t\tfor (let index = 0; index < maxLength; index++) {\n\t\t\tif (rbxFrom[index] !== rbxTo[index]) {\n\t\t\t\tdiffIndex = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tconst result = new Array<RbxPathParent | string>();\n\t\tif (diffIndex < rbxFrom.length) {\n\t\t\tfor (let index = 0; index < rbxFrom.length - diffIndex; index++) {\n\t\t\t\tresult.push(RbxPathParent);\n\t\t\t}\n\t\t}\n\n\t\tfor (let index = diffIndex; index < rbxTo.length; index++) {\n\t\t\t// eslint-disable-next-line ts/no-non-null-assertion -- Loop index\n\t\t\tresult.push(rbxTo[index]!);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Create a synthetic RojoResolver for ProjectType.Package. Forces all imports\n\t * to be relative.\n\t * @param basePath - The base filesystem path the package resolves against.\n\t * @returns A resolver that maps every file in the package relatively.\n\t */\n\tpublic static synthetic(basePath: string): RojoResolver {\n\t\tconst resolver = new RojoResolver();\n\t\tresolver.parseTree(basePath, \"\", { $path: basePath } as RojoTree, true);\n\t\treturn resolver;\n\t}\n\n\tpublic get walkedConfigFiles(): ReadonlySet<string> {\n\t\treturn this.walkedConfigFilesInternal;\n\t}\n\n\tpublic get walkedDirectories(): ReadonlySet<string> {\n\t\treturn this.walkedDirectoriesInternal;\n\t}\n\n\tprivate cachedRealpath(targetPath: string): string {\n\t\tlet resolved = this.realpathCache.get(targetPath);\n\t\tif (resolved === undefined) {\n\t\t\tresolved = fs.realpathSync(targetPath);\n\t\t\tthis.realpathCache.set(targetPath, resolved);\n\t\t}\n\n\t\treturn resolved;\n\t}\n\n\tprivate getContainer(from: Array<RbxPath>, rbxPath?: RbxPath): RbxPath | undefined {\n\t\tif (this.isGame && rbxPath) {\n\t\t\tfor (const container of from) {\n\t\t\t\tif (arrayStartsWith(rbxPath, container)) {\n\t\t\t\t\treturn container;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate parseConfig(rojoConfigFilePath: string, doNotPush = false): void {\n\t\tif (!fs.existsSync(rojoConfigFilePath)) {\n\t\t\tthis.warn(`RojoResolver: Path does not exist \"${rojoConfigFilePath}\"`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst realPath = this.cachedRealpath(rojoConfigFilePath);\n\t\tthis.walkedConfigFilesInternal.add(realPath);\n\n\t\tlet configJson: unknown;\n\t\ttry {\n\t\t\tconfigJson = JSON.parse(fs.readFileSync(realPath, \"utf8\"));\n\t\t} catch {\n\t\t\t// Malformed JSON: leave configJson undefined and fall through so it\n\t\t\t// is reported as an invalid configuration rather than crashing the\n\t\t\t// caller.\n\t\t}\n\n\t\tif (isValidRojoConfig(configJson)) {\n\t\t\tthis.parseTree(\n\t\t\t\tpath.dirname(rojoConfigFilePath),\n\t\t\t\tconfigJson.name,\n\t\t\t\tconfigJson.tree,\n\t\t\t\tdoNotPush,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.warn(\"RojoResolver: Invalid configuration!\");\n\t\t}\n\t}\n\n\tprivate parsePath(itemPath: string): void {\n\t\tconst luauPath = convertToLuau(itemPath);\n\n\t\t// A $path pointing straight at a *.project.json file embeds that nested\n\t\t// project at the current rbxPath (Rojo composes projects this way), the\n\t\t// same as a directory whose default.project.json is discovered below.\n\t\t// Without this, the .json extension routes the file into\n\t\t// filePathToRbxPathMap as an opaque leaf and the nested tree's mounts\n\t\t// (its partitions, its @rbxts mounts) are never read.\n\t\tif (ROJO_FILE_REGEX.test(path.basename(luauPath))) {\n\t\t\tthis.parseConfig(luauPath, true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst realPath = fs.existsSync(luauPath) ? this.cachedRealpath(luauPath) : luauPath;\n\t\tconst extension = path.extname(luauPath);\n\t\tif (ROJO_MODULE_EXTS.has(extension)) {\n\t\t\tthis.filePathToRbxPathMap.set(luauPath, [...this.rbxPath]);\n\t\t} else {\n\t\t\tconst isDirectory = fs.existsSync(realPath) && fs.statSync(realPath).isDirectory();\n\t\t\tif (isDirectory) {\n\t\t\t\tthis.walkedDirectoriesInternal.add(realPath);\n\t\t\t}\n\n\t\t\tif (isDirectory && fs.readdirSync(realPath).includes(ROJO_DEFAULT_NAME)) {\n\t\t\t\tthis.parseConfig(path.join(luauPath, ROJO_DEFAULT_NAME), true);\n\t\t\t} else {\n\t\t\t\tthis.partitions.unshift({\n\t\t\t\t\tfsPath: luauPath,\n\t\t\t\t\trbxPath: [...this.rbxPath],\n\t\t\t\t});\n\n\t\t\t\tif (isDirectory) {\n\t\t\t\t\tthis.searchDirectory(luauPath);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate parseTree(basePath: string, name: string, tree: RojoTree, doNotPush = false): void {\n\t\tif (!doNotPush) {\n\t\t\tthis.rbxPath.push(name);\n\t\t}\n\n\t\tif (tree.$path !== undefined) {\n\t\t\tthis.parsePath(\n\t\t\t\tpath.resolve(\n\t\t\t\t\tbasePath,\n\t\t\t\t\ttypeof tree.$path === \"string\" ? tree.$path : tree.$path.optional,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (tree.$className === \"DataModel\") {\n\t\t\tthis.isGame = true;\n\t\t}\n\n\t\tconst childNames = Object.keys(tree).filter((value) => !value.startsWith(\"$\"));\n\t\tfor (const childName of childNames) {\n\t\t\t// eslint-disable-next-line ts/no-non-null-assertion -- Object.keys ensures this is defined\n\t\t\tthis.parseTree(basePath, childName, tree[childName]!);\n\t\t}\n\n\t\tif (!doNotPush) {\n\t\t\tthis.rbxPath.pop();\n\t\t}\n\t}\n\n\tprivate searchChildren(directory: string, directoryEntries: Array<Dirent>): void {\n\t\t// Two-phase: parse every sibling *.project.json before recursing into\n\t\t// sibling directories. parsePath / parseConfig insert partitions via\n\t\t// unshift, and getRbxPathFromFilePath returns the first match, so\n\t\t// interleaving in raw entry order would shuffle partition precedence\n\t\t// for overlapping mounts.\n\t\tconst projectFiles = new Array<string>();\n\t\tconst subDirectories = new Array<{ name: string; path: string }>();\n\n\t\tfor (const entry of directoryEntries) {\n\t\t\tconst childPath = path.join(directory, entry.name);\n\t\t\tlet isFile = entry.isFile();\n\t\t\tlet isDirectory = entry.isDirectory();\n\t\t\t// Symlinks (both flags false), unknown-type entries on NFS/SMB/FUSE\n\t\t\t// mounts and some Windows network drives, and NTFS junctions on\n\t\t\t// Node versions that don't classify them: resolve and stat the\n\t\t\t// target so we still discover nested *.project.json files and\n\t\t\t// walkable directories. A broken symlink throws ENOENT here —\n\t\t\t// warn and skip rather than crash the whole walk.\n\t\t\tif (!isFile && !isDirectory) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stat = fs.statSync(this.cachedRealpath(childPath));\n\t\t\t\t\tisFile = stat.isFile();\n\t\t\t\t\tisDirectory = stat.isDirectory();\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.warn(\n\t\t\t\t\t\t`RojoResolver: Failed to resolve \"${childPath}\" (${(err as Error).message})`,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isFile && ROJO_FILE_REGEX.test(entry.name)) {\n\t\t\t\tprojectFiles.push(childPath);\n\t\t\t} else if (isDirectory) {\n\t\t\t\tsubDirectories.push({ name: entry.name, path: childPath });\n\t\t\t}\n\t\t}\n\n\t\tfor (const childPath of projectFiles) {\n\t\t\tthis.parseConfig(childPath);\n\t\t}\n\n\t\tfor (const { name, path: childPath } of subDirectories) {\n\t\t\tthis.searchDirectory(childPath, name);\n\t\t}\n\t}\n\n\tprivate searchDirectory(directory: string, item?: string): void {\n\t\tconst realPath = this.cachedRealpath(directory);\n\t\tthis.walkedDirectoriesInternal.add(realPath);\n\t\t// readdirSync transparently follows a symlink on the argument; the\n\t\t// result is equivalent to readdirSync(realpathSync(directory)).\n\t\tconst directoryEntries = fs.readdirSync(directory, { withFileTypes: true });\n\n\t\tif (directoryEntries.some((entry) => entry.name === ROJO_DEFAULT_NAME)) {\n\t\t\tthis.parseConfig(path.join(directory, ROJO_DEFAULT_NAME));\n\t\t\treturn;\n\t\t}\n\n\t\tif (item !== undefined) {\n\t\t\tthis.rbxPath.push(item);\n\t\t}\n\n\t\tthis.searchChildren(directory, directoryEntries);\n\n\t\tif (item !== undefined) {\n\t\t\tthis.rbxPath.pop();\n\t\t}\n\t}\n\n\tprivate warn(str: string): void {\n\t\tthis.warnings.push(str);\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { GLOB_TS } from "@isentinel/eslint-config";
2
+
3
+ import { halcyon } from "../../eslint.shared.ts";
4
+ import halcyonPlugin from "../../tools/eslint-rules/src/index.ts";
5
+
6
+ export default halcyon(
7
+ {
8
+ name: "packages/shared/rojo-utils",
9
+ roblox: false,
10
+ rules: {
11
+ "max-classes-per-file": "off",
12
+ "max-lines": "off",
13
+ "max-lines-per-function": "off",
14
+ },
15
+ type: "package",
16
+ },
17
+ {
18
+ name: "project/no-issue-references",
19
+ files: [GLOB_TS],
20
+ plugins: { halcyon: halcyonPlugin },
21
+ rules: { "halcyon/no-issue-references": "error" },
22
+ },
23
+ );