roport 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/builder.js DELETED
@@ -1,563 +0,0 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const chalk = require('chalk');
4
- const IgnoreManager = require('./ignore');
5
-
6
- class RbxmxBuilder {
7
- constructor() {
8
- this.referentCount = 0;
9
- this.ignoreManager = null;
10
- }
11
-
12
- generateReferent() {
13
- return `RBX${this.referentCount++}`;
14
- }
15
-
16
- async writeProperties(write, properties) {
17
- await write(' <Properties>\n');
18
- for (const [name, value] of Object.entries(properties)) {
19
- if (name === 'Source') {
20
- await write(` <ProtectedString name="Source"><![CDATA[${value}]]></ProtectedString>\n`);
21
- continue;
22
- }
23
-
24
- if (name === 'Name') {
25
- await write(` <string name="Name">${this.escapeXml(value)}</string>\n`);
26
- continue;
27
- }
28
-
29
- if (typeof value === 'boolean') {
30
- await write(` <bool name="${name}">${value}</bool>\n`);
31
- } else if (typeof value === 'number') {
32
- await write(` <double name="${name}">${value}</double>\n`);
33
- } else if (typeof value === 'string') {
34
- await write(` <string name="${name}">${this.escapeXml(value)}</string>\n`);
35
- } else if (typeof value === 'object' && value !== null && value.$type) {
36
- switch (value.$type) {
37
- case 'Vector3':
38
- await write(` <Vector3 name="${name}">
39
- <X>${value.value[0]}</X>
40
- <Y>${value.value[1]}</Y>
41
- <Z>${value.value[2]}</Z>
42
- </Vector3>\n`);
43
- break;
44
- case 'Vector2':
45
- await write(` <Vector2 name="${name}">
46
- <X>${value.value[0]}</X>
47
- <Y>${value.value[1]}</Y>
48
- </Vector2>\n`);
49
- break;
50
- case 'Color3':
51
- await write(` <Color3 name="${name}">
52
- <R>${value.value[0]}</R>
53
- <G>${value.value[1]}</G>
54
- <B>${value.value[2]}</B>
55
- </Color3>\n`);
56
- break;
57
- case 'UDim2':
58
- await write(` <UDim2 name="${name}">
59
- <XS>${value.value[0]}</XS>
60
- <XO>${value.value[1]}</XO>
61
- <YS>${value.value[2]}</YS>
62
- <YO>${value.value[3]}</YO>
63
- </UDim2>\n`);
64
- break;
65
- case 'Enum':
66
- await write(` <token name="${name}">${value.value}</token>\n`);
67
- break;
68
- case 'CFrame':
69
- // value: [x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22]
70
- const cf = value.value;
71
- await write(` <CoordinateFrame name="${name}">
72
- <X>${cf[0]}</X><Y>${cf[1]}</Y><Z>${cf[2]}</Z>
73
- <R00>${cf[3]}</R00><R01>${cf[4]}</R01><R02>${cf[5]}</R02>
74
- <R10>${cf[6]}</R10><R11>${cf[7]}</R11><R12>${cf[8]}</R12>
75
- <R20>${cf[9]}</R20><R21>${cf[10]}</R21><R22>${cf[11]}</R22>
76
- </CoordinateFrame>\n`);
77
- break;
78
- case 'BrickColor':
79
- await write(` <int name="${name}">${value.value}</int>\n`);
80
- break;
81
- case 'Rect':
82
- // value: [minX, minY, maxX, maxY]
83
- await write(` <Rect2D name="${name}">
84
- <min><X>${value.value[0]}</X><Y>${value.value[1]}</Y></min>
85
- <max><X>${value.value[2]}</X><Y>${value.value[3]}</Y></max>
86
- </Rect2D>\n`);
87
- break;
88
- case 'NumberRange':
89
- // value: [min, max]
90
- await write(` <NumberRange name="${name}">${value.value[0]} ${value.value[1]}</NumberRange>\n`);
91
- break;
92
- case 'NumberSequence':
93
- // value: string "0 0.5 0 1 1 0" or array
94
- const nsVal = Array.isArray(value.value) ? value.value.join(' ') : value.value;
95
- await write(` <NumberSequence name="${name}">${nsVal}</NumberSequence>\n`);
96
- break;
97
- case 'ColorSequence':
98
- // value: string or array
99
- const csVal = Array.isArray(value.value) ? value.value.join(' ') : value.value;
100
- await write(` <ColorSequence name="${name}">${csVal}</ColorSequence>\n`);
101
- break;
102
- case 'PhysicalProperties':
103
- const pp = value.value;
104
- await write(` <PhysicalProperties name="${name}">
105
- <CustomPhysics>true</CustomPhysics>
106
- <Density>${pp.Density}</Density>
107
- <Friction>${pp.Friction}</Friction>
108
- <Elasticity>${pp.Elasticity}</Elasticity>
109
- <FrictionWeight>${pp.FrictionWeight}</FrictionWeight>
110
- <ElasticityWeight>${pp.ElasticityWeight}</ElasticityWeight>
111
- </PhysicalProperties>\n`);
112
- break;
113
- case 'Font':
114
- const font = value.value;
115
- await write(` <Font name="${name}">
116
- <Family><url>${font.family}</url></Family>
117
- <Weight>${font.weight}</Weight>
118
- <Style>${font.style}</Style>
119
- </Font>\n`);
120
- break;
121
- case 'Ray':
122
- const ray = value.value; // [ox, oy, oz, dx, dy, dz]
123
- await write(` <Ray name="${name}">
124
- <origin><X>${ray[0]}</X><Y>${ray[1]}</Y><Z>${ray[2]}</Z></origin>
125
- <direction><X>${ray[3]}</X><Y>${ray[4]}</Y><Z>${ray[5]}</Z></direction>
126
- </Ray>\n`);
127
- break;
128
- case 'Axes':
129
- await write(` <Axes name="${name}"><axes>${value.value}</axes></Axes>\n`);
130
- break;
131
- case 'Faces':
132
- await write(` <Faces name="${name}"><faces>${value.value}</faces></Faces>\n`);
133
- break;
134
- case 'Ref':
135
- // value: string (referent ID)
136
- const refVal = value.value || 'null';
137
- await write(` <Ref name="${name}">${refVal}</Ref>\n`);
138
- break;
139
- case 'BinaryString':
140
- // value: base64 string
141
- await write(` <BinaryString name="${name}"><![CDATA[${value.value}]]></BinaryString>\n`);
142
- break;
143
- case 'UDim':
144
- // value: [scale, offset]
145
- await write(` <UDim name="${name}">
146
- <S>${value.value[0]}</S>
147
- <O>${value.value[1]}</O>
148
- </UDim>\n`);
149
- break;
150
- case 'Vector3int16':
151
- // value: [x, y, z]
152
- await write(` <Vector3int16 name="${name}">
153
- <X>${value.value[0]}</X>
154
- <Y>${value.value[1]}</Y>
155
- <Z>${value.value[2]}</Z>
156
- </Vector3int16>\n`);
157
- break;
158
- case 'SharedString':
159
- // value: base64 string
160
- await write(` <SharedString name="${name}">${value.value}</SharedString>\n`);
161
- break;
162
- case 'ProtectedString':
163
- await write(` <ProtectedString name="${name}"><![CDATA[${value.value}]]></ProtectedString>\n`);
164
- break;
165
- }
166
- }
167
- }
168
- await write(' </Properties>\n');
169
- }
170
-
171
- escapeXml(unsafe) {
172
- return String(unsafe).replace(/[<>&'"]/g, function (c) {
173
- switch (c) {
174
- case '<': return '&lt;';
175
- case '>': return '&gt;';
176
- case '&': return '&amp;';
177
- case '\'': return '&apos;';
178
- case '"': return '&quot;';
179
- }
180
- });
181
- }
182
-
183
- buildProperties(props) {
184
- let xml = ' <Properties>\n';
185
- for (const [name, value] of Object.entries(props)) {
186
- if (name === 'Name') {
187
- xml += ` <string name="Name">${this.escapeXml(value)}</string>\n`;
188
- } else if (typeof value === 'boolean') {
189
- xml += ` <bool name="${name}">${value}</bool>\n`;
190
- } else if (typeof value === 'number') {
191
- xml += ` <double name="${name}">${value}</double>\n`;
192
- } else if (typeof value === 'string') {
193
- xml += ` <string name="${name}">${this.escapeXml(value)}</string>\n`;
194
- }
195
- // TODO: Add more types (Vector3, Color3, etc) as needed
196
- }
197
- xml += ' </Properties>\n';
198
- return xml;
199
- }
200
-
201
- async processNode(node, name, currentPath, write, initOverrideFile = null) {
202
- let className = node.$className || node.type || 'Folder';
203
- // Allow manual referent override for linking
204
- const referent = node.$referent || this.generateReferent();
205
-
206
- // Determine properties
207
- const properties = node.$properties || {};
208
- properties.Name = name;
209
-
210
- // Handle $path
211
- let childrenProcessor = async () => {}; // Deferred children processing
212
-
213
- if (node.$path) {
214
- const filePath = path.resolve(currentPath, node.$path);
215
- if (await fs.pathExists(filePath)) {
216
- const stat = await fs.stat(filePath);
217
-
218
- if (stat.isDirectory()) {
219
- // Check for init scripts in this directory to transform the container
220
- const initServer = path.join(filePath, 'init.server.lua');
221
- const initClient = path.join(filePath, 'init.client.lua');
222
- const initModule = path.join(filePath, 'init.lua');
223
- const initMeta = path.join(filePath, 'init.meta.json');
224
-
225
- let initContent = null;
226
-
227
- if (initOverrideFile) {
228
- // Use the provided file as the source of truth for this container
229
- if (initOverrideFile.endsWith('.server.lua') || initOverrideFile.endsWith('.server.luau')) {
230
- className = 'Script';
231
- initContent = await fs.readFile(initOverrideFile, 'utf8');
232
- } else if (initOverrideFile.endsWith('.client.lua') || initOverrideFile.endsWith('.client.luau')) {
233
- className = 'LocalScript';
234
- initContent = await fs.readFile(initOverrideFile, 'utf8');
235
- } else if (initOverrideFile.endsWith('.lua') || initOverrideFile.endsWith('.luau')) {
236
- className = 'ModuleScript';
237
- initContent = await fs.readFile(initOverrideFile, 'utf8');
238
- } else if (initOverrideFile.endsWith('.model.json')) {
239
- try {
240
- const modelData = await fs.readJson(initOverrideFile);
241
- className = modelData.className || modelData.ClassName || modelData.type || className;
242
- if (modelData.properties) Object.assign(properties, modelData.properties);
243
- } catch (e) {}
244
- }
245
- } else {
246
- // Standard init file check
247
- if (await fs.pathExists(initServer)) {
248
- className = 'Script';
249
- initContent = await fs.readFile(initServer, 'utf8');
250
- } else if (await fs.pathExists(initClient)) {
251
- className = 'LocalScript';
252
- initContent = await fs.readFile(initClient, 'utf8');
253
- } else if (await fs.pathExists(initModule)) {
254
- className = 'ModuleScript';
255
- initContent = await fs.readFile(initModule, 'utf8');
256
- }
257
-
258
- if (await fs.pathExists(initMeta)) {
259
- try {
260
- const meta = await fs.readJson(initMeta);
261
- if (meta.className) className = meta.className;
262
- if (meta.properties) Object.assign(properties, meta.properties);
263
- } catch (e) {}
264
- }
265
- }
266
-
267
- if (initContent !== null) {
268
- properties.Source = initContent;
269
- }
270
-
271
- // Process directory children
272
- childrenProcessor = async () => {
273
- const files = await fs.readdir(filePath);
274
- const entries = {};
275
-
276
- // Group files by name to handle File+Folder merging
277
- for (const file of files) {
278
- if (file.startsWith('.')) continue;
279
-
280
- if (this.ignoreManager && this.ignoreManager.isIgnored(path.join(filePath, file))) continue;
281
-
282
- // Determine base name
283
- let baseName = file;
284
- if (file.endsWith('.server.lua')) baseName = file.slice(0, -11);
285
- else if (file.endsWith('.client.lua')) baseName = file.slice(0, -11);
286
- else if (file.endsWith('.lua')) baseName = file.slice(0, -4);
287
- else if (file.endsWith('.server.luau')) baseName = file.slice(0, -12);
288
- else if (file.endsWith('.client.luau')) baseName = file.slice(0, -12);
289
- else if (file.endsWith('.luau')) baseName = file.slice(0, -5);
290
- else if (file.endsWith('.model.json')) baseName = file.slice(0, -11);
291
- else if (file.endsWith('.json')) baseName = file.slice(0, -5);
292
- else if (file.endsWith('.txt')) baseName = file.slice(0, -4);
293
-
294
- // Skip init files as they are handled above
295
- if (baseName === 'init' || file === 'init.meta.json') continue;
296
-
297
- if (!entries[baseName]) entries[baseName] = {};
298
-
299
- const childPath = path.join(filePath, file);
300
- const childStat = await fs.stat(childPath);
301
-
302
- if (childStat.isDirectory()) {
303
- entries[baseName].folder = file;
304
- } else {
305
- entries[baseName].file = file;
306
- }
307
- }
308
-
309
- for (const [childName, entry] of Object.entries(entries)) {
310
- if (entry.folder && entry.file) {
311
- // Merge: Process folder, but use file as init override
312
- await this.processNode({ $path: entry.folder }, childName, filePath, write, path.join(filePath, entry.file));
313
- } else if (entry.folder) {
314
- // Just a folder
315
- await this.processNode({ $path: entry.folder }, childName, filePath, write);
316
- } else if (entry.file) {
317
- // Just a file - process inline
318
- const file = entry.file;
319
- const childPath = path.join(filePath, file);
320
-
321
- let childClass = 'Folder';
322
- let content = '';
323
- let childProps = {};
324
-
325
- if (file.endsWith('.server.lua') || file.endsWith('.server.luau')) childClass = 'Script';
326
- else if (file.endsWith('.client.lua') || file.endsWith('.client.luau')) childClass = 'LocalScript';
327
- else if (file.endsWith('.lua') || file.endsWith('.luau')) childClass = 'ModuleScript';
328
- else if (file.endsWith('.txt')) childClass = 'StringValue';
329
- else if (file.endsWith('.model.json')) {
330
- try {
331
- const modelData = await fs.readJson(childPath);
332
- childClass = modelData.className || modelData.ClassName || modelData.type || 'Folder';
333
- childProps = modelData.properties || {};
334
- } catch (e) {}
335
- }
336
- else if (file.endsWith('.json')) childClass = 'ModuleScript';
337
-
338
- if (childClass !== 'Folder' || file.endsWith('.model.json')) {
339
- if (file.endsWith('.json') && !file.endsWith('.model.json')) {
340
- const jsonContent = await fs.readFile(childPath, 'utf8');
341
- content = `return game:GetService("HttpService"):JSONDecode([[${jsonContent}]])`;
342
- } else if (!file.endsWith('.model.json')) {
343
- content = await fs.readFile(childPath, 'utf8');
344
- }
345
-
346
- const childRef = childProps.$referent || this.generateReferent();
347
- await write(` <Item class="${childClass}" referent="${childRef}">\n`);
348
-
349
- // Prepare properties
350
- childProps.Name = childName;
351
- if (childClass.includes('Script')) {
352
- childProps.Source = content;
353
- } else if (childClass === 'StringValue') {
354
- childProps.Value = content;
355
- }
356
-
357
- await this.writeProperties(write, childProps);
358
- await write(` </Item>\n`);
359
- }
360
- }
361
- }
362
- };
363
- } else {
364
- // It's a file mapped directly (e.g. a script)
365
- const ext = path.extname(filePath);
366
- if (ext === '.lua' || ext === '.luau') {
367
- const content = await fs.readFile(filePath, 'utf8');
368
- properties.Source = content;
369
- if (className === 'Folder') {
370
- if (filePath.endsWith('.server.lua')) className = 'Script';
371
- else if (filePath.endsWith('.client.lua')) className = 'LocalScript';
372
- else className = 'ModuleScript';
373
- }
374
- } else if (filePath.endsWith('.model.json')) {
375
- try {
376
- const modelData = await fs.readJson(filePath);
377
- className = modelData.className || modelData.ClassName || modelData.type || className;
378
- if (modelData.properties) Object.assign(properties, modelData.properties);
379
- } catch (e) {}
380
- }
381
- }
382
- }
383
- }
384
-
385
- await write(` <Item class="${className}" referent="${referent}">\n`);
386
-
387
- await this.writeProperties(write, properties);
388
-
389
- // Process children
390
- await childrenProcessor();
391
-
392
- // Handle nested tree nodes
393
- for (const key in node) {
394
- if (!key.startsWith('$') && typeof node[key] === 'object') {
395
- await this.processNode(node[key], key, currentPath, write);
396
- }
397
- }
398
-
399
- await write(` </Item>\n`);
400
- }
401
-
402
- async build(projectPath, outputPath) {
403
- console.log(chalk.blue(`Building project from ${projectPath}...`));
404
-
405
- this.ignoreManager = new IgnoreManager(projectPath);
406
-
407
- const configPath = path.join(projectPath, 'default.project.json');
408
- if (!await fs.pathExists(configPath)) {
409
- throw new Error('default.project.json not found');
410
- }
411
-
412
- const config = await fs.readJson(configPath);
413
- const rootName = config.name || 'Project';
414
- const tree = config.tree;
415
-
416
- const stream = fs.createWriteStream(outputPath, { encoding: 'utf8' });
417
-
418
- // Helper to handle backpressure
419
- const write = (chunk) => {
420
- return new Promise((resolve, reject) => {
421
- if (!stream.write(chunk)) {
422
- stream.once('drain', resolve);
423
- } else {
424
- resolve();
425
- }
426
- stream.once('error', reject);
427
- });
428
- };
429
-
430
- try {
431
- await write('<roblox version="4">\n');
432
- await this.processNode(tree, rootName, projectPath, write);
433
- await write('</roblox>');
434
- } finally {
435
- stream.end();
436
- }
437
-
438
- console.log(chalk.green(`Build complete: ${outputPath}`));
439
- }
440
-
441
- async processNodeSourcemap(node, name, currentPath) {
442
- let className = node.$className || 'Folder';
443
- const children = [];
444
- const filePaths = [];
445
-
446
- // Handle $path
447
- if (node.$path) {
448
- const filePath = path.resolve(currentPath, node.$path);
449
- if (await fs.pathExists(filePath)) {
450
- filePaths.push(filePath);
451
- const stat = await fs.stat(filePath);
452
-
453
- if (stat.isDirectory()) {
454
- // Check for init scripts
455
- const initServer = path.join(filePath, 'init.server.lua');
456
- const initClient = path.join(filePath, 'init.client.lua');
457
- const initModule = path.join(filePath, 'init.lua');
458
-
459
- if (await fs.pathExists(initServer)) {
460
- className = 'Script';
461
- filePaths.push(initServer);
462
- } else if (await fs.pathExists(initClient)) {
463
- className = 'LocalScript';
464
- filePaths.push(initClient);
465
- } else if (await fs.pathExists(initModule)) {
466
- className = 'ModuleScript';
467
- filePaths.push(initModule);
468
- }
469
-
470
- // Process directory children
471
- const files = await fs.readdir(filePath);
472
- for (const file of files) {
473
- if (file.startsWith('.')) continue;
474
-
475
- const childPath = path.join(filePath, file);
476
- if (this.ignoreManager && this.ignoreManager.isIgnored(childPath)) continue;
477
-
478
- const childStat = await fs.stat(childPath);
479
-
480
- if (childStat.isDirectory()) {
481
- children.push(await this.processNodeSourcemap({ $path: file }, file, filePath));
482
- } else {
483
- const ext = path.extname(file);
484
- const basename = path.basename(file, ext);
485
-
486
- if (basename.startsWith('init') && (ext === '.lua' || ext === '.luau')) continue;
487
-
488
- let childClass = 'Folder';
489
- if (file.endsWith('.server.lua') || file.endsWith('.server.luau')) childClass = 'Script';
490
- else if (file.endsWith('.client.lua') || file.endsWith('.client.luau')) childClass = 'LocalScript';
491
- else if (file.endsWith('.lua') || file.endsWith('.luau')) childClass = 'ModuleScript';
492
- else if (file.endsWith('.txt')) childClass = 'StringValue';
493
- else if (file.endsWith('.json')) childClass = 'ModuleScript';
494
-
495
- if (childClass !== 'Folder') {
496
- children.push({
497
- name: basename.replace('.server', '').replace('.client', ''),
498
- className: childClass,
499
- filePaths: [childPath]
500
- });
501
- }
502
- }
503
- }
504
- } else {
505
- // File mapped directly
506
- const ext = path.extname(filePath);
507
- if (ext === '.lua' || ext === '.luau') {
508
- if (className === 'Folder') {
509
- if (filePath.endsWith('.server.lua')) className = 'Script';
510
- else if (filePath.endsWith('.client.lua')) className = 'LocalScript';
511
- else className = 'ModuleScript';
512
- }
513
- }
514
- }
515
- }
516
- }
517
-
518
- // Handle nested tree nodes
519
- for (const key in node) {
520
- if (!key.startsWith('$') && typeof node[key] === 'object') {
521
- children.push(await this.processNodeSourcemap(node[key], key, currentPath));
522
- }
523
- }
524
-
525
- const result = {
526
- name: name,
527
- className: className,
528
- filePaths: filePaths
529
- };
530
-
531
- if (children.length > 0) {
532
- result.children = children;
533
- }
534
-
535
- return result;
536
- }
537
-
538
- async sourcemap(projectPath, outputPath) {
539
- console.log(chalk.blue(`Generating sourcemap from ${projectPath}...`));
540
-
541
- this.ignoreManager = new IgnoreManager(projectPath);
542
-
543
- const configPath = path.join(projectPath, 'default.project.json');
544
- if (!await fs.pathExists(configPath)) {
545
- throw new Error('default.project.json not found');
546
- }
547
-
548
- const config = await fs.readJson(configPath);
549
- const rootName = config.name || 'Project';
550
- const tree = config.tree;
551
-
552
- const map = await this.processNodeSourcemap(tree, rootName, projectPath);
553
-
554
- if (outputPath) {
555
- await fs.outputJson(outputPath, map, { spaces: 2 });
556
- console.log(chalk.green(`Sourcemap generated: ${outputPath}`));
557
- } else {
558
- console.log(JSON.stringify(map, null, 2));
559
- }
560
- }
561
- }
562
-
563
- module.exports = new RbxmxBuilder();
package/src/context.js DELETED
@@ -1,76 +0,0 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const chalk = require('chalk');
4
- const IgnoreManager = require('./ignore');
5
-
6
- class ContextGenerator {
7
- constructor() {
8
- this.ignoreManager = null;
9
- }
10
-
11
- async generate(dir, options = {}) {
12
- const { maxDepth = 5, includeSource = false } = options;
13
- this.ignoreManager = new IgnoreManager(dir);
14
- let output = `# Project Context: ${path.basename(dir)}\n\n`;
15
-
16
- output += await this.traverse(dir, 0, maxDepth, includeSource);
17
-
18
- return output;
19
- }
20
-
21
- async traverse(currentPath, depth, maxDepth, includeSource) {
22
- if (depth > maxDepth) return '';
23
-
24
- let output = '';
25
- const items = await fs.readdir(currentPath);
26
-
27
- // Sort: Directories first, then files
28
- items.sort((a, b) => {
29
- const aPath = path.join(currentPath, a);
30
- const bPath = path.join(currentPath, b);
31
- const aStat = fs.statSync(aPath);
32
- const bStat = fs.statSync(bPath);
33
- if (aStat.isDirectory() && !bStat.isDirectory()) return -1;
34
- if (!aStat.isDirectory() && bStat.isDirectory()) return 1;
35
- return a.localeCompare(b);
36
- });
37
-
38
- for (const item of items) {
39
- if (this.ignoreManager.isIgnored(path.join(currentPath, item))) continue;
40
-
41
- const fullPath = path.join(currentPath, item);
42
- const stat = await fs.stat(fullPath);
43
- const indent = ' '.repeat(depth);
44
-
45
- if (stat.isDirectory()) {
46
- output += `${indent}- 📂 **${item}/**\n`;
47
- output += await this.traverse(fullPath, depth + 1, maxDepth, includeSource);
48
- } else {
49
- const ext = path.extname(item);
50
- let icon = '📄';
51
- if (ext === '.lua' || ext === '.luau') icon = '📜';
52
- if (ext === '.json') icon = '⚙️';
53
- if (ext === '.model.json') icon = '📦';
54
-
55
- output += `${indent}- ${icon} ${item}\n`;
56
-
57
- if (includeSource && (ext === '.lua' || ext === '.luau' || ext === '.model.json' || item === 'default.project.json')) {
58
- try {
59
- const content = await fs.readFile(fullPath, 'utf8');
60
- // Truncate if too long
61
- if (content.length > 2000) {
62
- output += `${indent} \`\`\`${ext.slice(1)}\n${content.slice(0, 2000)}\n... (truncated)\n \`\`\`\n`;
63
- } else {
64
- output += `${indent} \`\`\`${ext.slice(1)}\n${content}\n \`\`\`\n`;
65
- }
66
- } catch (e) {
67
- output += `${indent} *(Error reading file)*\n`;
68
- }
69
- }
70
- }
71
- }
72
- return output;
73
- }
74
- }
75
-
76
- module.exports = new ContextGenerator();