syncrbx 1.0.0 → 1.1.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/converters.js CHANGED
@@ -68,6 +68,28 @@ function determineScriptType(filename) {
68
68
  return null;
69
69
  }
70
70
 
71
+ // Class a file becomes in Studio, judged by its name alone (the file may
72
+ // already be deleted). Returns ANY_CLASS for .model.json, whose class is inside
73
+ // the file, and null for files SyncRbx does not sync (README.md, meta files...).
74
+ const ANY_CLASS = '*';
75
+
76
+ function classFromFileName(filename) {
77
+ const lower = filename.toLowerCase();
78
+ if (lower.endsWith('.meta.json') || lower === 'meta.json') return null;
79
+ if (lower.endsWith('.model.json')) return ANY_CLASS;
80
+ if (lower.endsWith('.remoteevent')) return 'RemoteEvent';
81
+ if (lower.endsWith('.remotefunction')) return 'RemoteFunction';
82
+ if (lower.endsWith('.bindableevent')) return 'BindableEvent';
83
+ if (lower.endsWith('.bindablefunction')) return 'BindableFunction';
84
+ if (lower.endsWith('.txt')) return 'StringValue';
85
+ if (lower.endsWith('.json')) return 'ModuleScript';
86
+ return determineScriptType(lower);
87
+ }
88
+
89
+ function isInitFile(filename) {
90
+ return filename.startsWith('init.') && (filename.endsWith('.lua') || filename.endsWith('.luau'));
91
+ }
92
+
71
93
  // Phase 4 - #7: File checksum
72
94
  function fileChecksum(filePath) {
73
95
  try {
@@ -298,7 +320,8 @@ function instanceToFile(instanceData) {
298
320
  const name = instanceData.name || instanceData.Name || 'Untitled';
299
321
  const className = instanceData.className || instanceData.ClassName || 'Folder';
300
322
  const source = instanceData.source || (instanceData.Properties && instanceData.Properties.Source) || '';
301
- const value = instanceData.value || (instanceData.Properties && instanceData.Properties.Value) || '';
323
+ // ?? keeps false and 0, which || would turn into ''.
324
+ const value = instanceData.value ?? (instanceData.Properties && instanceData.Properties.Value) ?? '';
302
325
 
303
326
  if (className === 'Folder') {
304
327
  return { fileName: name, content: null, isDirectory: true };
@@ -359,24 +382,44 @@ function filePathToInstancePath(filePath, projectRoot) {
359
382
  // ---------------------------------------------------------------------------
360
383
  // Phase 2 - #4: Project config loader
361
384
  // ---------------------------------------------------------------------------
385
+ const PROJECT_CONFIG_FILE = 'syncrbx.project.json';
386
+
387
+ // Keeps only known Roblox services, without duplicates. Returns null when the
388
+ // value is not a list, meaning "not chosen yet".
389
+ function sanitizeServices(services) {
390
+ if (!Array.isArray(services)) return null;
391
+ return [...new Set(services.filter(s => typeof s === 'string' && ROOT_SERVICES.includes(s)))];
392
+ }
393
+
394
+ function readProjectConfigFile(projectRoot) {
395
+ const configPath = path.join(projectRoot, PROJECT_CONFIG_FILE);
396
+ if (!fs.existsSync(configPath)) return {};
397
+ try {
398
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'));
399
+ } catch (e) {
400
+ console.error(`[WARN] Error parsing ${PROJECT_CONFIG_FILE}: ${e.message}`);
401
+ return {};
402
+ }
403
+ }
404
+
362
405
  function loadProjectConfig(projectRoot) {
363
- const configPath = path.join(projectRoot, 'syncrbx.project.json');
364
406
  const defaults = {
365
407
  name: path.basename(projectRoot),
366
408
  port: 34872,
367
409
  ignore: ['*.conflict.bak', 'node_modules', '.git', '.vscode'],
368
410
  tree: null, // null = auto-detect from folder names
411
+ services: null, // null = the user has not chosen which services to sync
369
412
  };
413
+ const config = { ...defaults, ...readProjectConfigFile(projectRoot) };
414
+ config.services = sanitizeServices(config.services);
415
+ return config;
416
+ }
370
417
 
371
- if (fs.existsSync(configPath)) {
372
- try {
373
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
374
- return { ...defaults, ...config };
375
- } catch (e) {
376
- console.error(`[WARN] Error parsing syncrbx.project.json: ${e.message}`);
377
- }
378
- }
379
- return defaults;
418
+ // Saves the chosen services, keeping any other settings already in the file.
419
+ function saveProjectServices(projectRoot, services) {
420
+ const config = readProjectConfigFile(projectRoot);
421
+ config.services = services;
422
+ fs.writeFileSync(path.join(projectRoot, PROJECT_CONFIG_FILE), JSON.stringify(config, null, 2) + '\n', 'utf8');
380
423
  }
381
424
 
382
425
  module.exports = {
@@ -384,8 +427,11 @@ module.exports = {
384
427
  STARTER_PLAYER_CHILDREN,
385
428
  KNOWN_EXTENSIONS,
386
429
  VALUE_CLASSES,
430
+ ANY_CLASS,
387
431
  stripExtension,
388
432
  determineScriptType,
433
+ classFromFileName,
434
+ isInitFile,
389
435
  jsonToLuaTable,
390
436
  convertFile,
391
437
  processDirectory,
@@ -394,5 +440,8 @@ module.exports = {
394
440
  filePathToInstancePath,
395
441
  getMetaProperties,
396
442
  loadProjectConfig,
443
+ saveProjectServices,
444
+ sanitizeServices,
445
+ PROJECT_CONFIG_FILE,
397
446
  fileChecksum,
398
447
  };