msfs-layout-generator 0.2.1 → 0.3.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 (3) hide show
  1. package/README.MD +5 -1
  2. package/dist/cli.js +153 -4
  3. package/package.json +2 -1
package/README.MD CHANGED
@@ -4,7 +4,8 @@
4
4
  ![downloads](https://img.shields.io/npm/d18m/msfs-layout-generator) ![npm](https://img.shields.io/npm/v/msfs-layout-generator) ![types](https://img.shields.io/npm/types/msfs-layout-generator) ![License](https://img.shields.io/badge/License-MIT-yellow.svg)
5
5
 
6
6
  A tool for **Microsoft Flight Simulator (MSFS)** developers to automatically generate and update `layout.json` files for their add-ons.
7
-
7
+
8
+ ![terminal](.github/readme.png)
8
9
  ---
9
10
 
10
11
  ## ✨ Features
@@ -12,6 +13,9 @@ A tool for **Microsoft Flight Simulator (MSFS)** developers to automatically gen
12
13
  - **Automatic Layout Generation**
13
14
  Scans package directories and creates `layout.json` with file metadata
14
15
 
16
+ - **Watch changes in Package Directory**
17
+ Scans all changes in provided directory and updates `layout.json` accordingly
18
+
15
19
  - **Manifest Integration**
16
20
  Automatically updates `total_package_size` in `manifest.json`
17
21
  - **Multiple Processing Modes**
package/dist/cli.js CHANGED
@@ -40,6 +40,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
40
40
  const commander_1 = require("commander");
41
41
  const chalk_1 = __importDefault(require("chalk"));
42
42
  const path = __importStar(require("path"));
43
+ const fs = __importStar(require("fs"));
44
+ const chokidar_1 = __importDefault(require("chokidar"));
43
45
  const processLayout_1 = require("./utils/processLayout");
44
46
  // Define the program
45
47
  const program = new commander_1.Command();
@@ -65,6 +67,10 @@ const logger = {
65
67
  * msfs-layout "package1" "package2" "package3"
66
68
  *
67
69
  * @example
70
+ * // Watch directory for changes
71
+ * msfs-layout "./my-package" --watch
72
+ *
73
+ * @example
68
74
  * // Show help
69
75
  * msfs-layout --help
70
76
  */
@@ -77,6 +83,7 @@ program
77
83
  .option('-q, --quiet', 'Suppress non-essential output')
78
84
  .option('-d, --debug', 'Enable debug logging for troubleshooting')
79
85
  .option('--no-manifest-check', 'Skip manifest.json existence check')
86
+ .option('-w, --watch', 'Watch directory for changes and regenerate automatically')
80
87
  .action(async (directories, options) => {
81
88
  await handleAction(directories, options);
82
89
  })
@@ -97,15 +104,20 @@ ${chalk_1.default.bold('Examples:')}
97
104
  ${chalk_1.default.dim('# Quiet mode - minimal output')}
98
105
  msfs-layout ./my-package --quiet
99
106
 
107
+ ${chalk_1.default.dim('# Watch directory for changes')}
108
+ msfs-layout ./my-package --watch
109
+
100
110
  ${chalk_1.default.bold('Notes:')}
101
111
  • Each directory should contain a ${chalk_1.default.cyan('manifest.json')} file
102
112
  • Creates/updates ${chalk_1.default.cyan('layout.json')} in the same directory
103
113
  • Automatically excludes ${chalk_1.default.yellow('_CVT_')} directories
104
114
  • Updates ${chalk_1.default.cyan('total_package_size')} in manifest.json
115
+ • Watch mode works with a ${chalk_1.default.yellow('single directory')} only
116
+ • Use ${chalk_1.default.cyan('Ctrl+C')} to exit watch mode
105
117
  `);
106
118
  // Handle the main action
107
119
  async function handleAction(directories, options) {
108
- const { force, quiet, debug, manifestCheck } = options;
120
+ const { force, quiet, debug, manifestCheck, watch } = options;
109
121
  // Show header if not in quiet mode
110
122
  if (!quiet) {
111
123
  logger.header(`msfs-layout-generator`);
@@ -117,6 +129,16 @@ async function handleAction(directories, options) {
117
129
  logger.info('Use msfs-layout --help for usage information.');
118
130
  process.exit(1);
119
131
  }
132
+ if (watch && directories.length > 1) {
133
+ logger.error('Watch mode only supports a single directory.');
134
+ logger.info('Please specify only one directory when using --watch flag.');
135
+ process.exit(1);
136
+ }
137
+ // Handle watch mode
138
+ if (watch) {
139
+ await handleWatchMode(directories[0], { force, quiet, debug, manifestCheck });
140
+ return;
141
+ }
120
142
  const errors = [];
121
143
  const successes = [];
122
144
  let totalFilesProcessed = 0;
@@ -131,8 +153,8 @@ async function handleAction(directories, options) {
131
153
  logger.dim(` Debug: Resolved path: ${fullPath}`);
132
154
  }
133
155
  // Check if directory exists
134
- if (!require('fs').existsSync(fullPath)) {
135
- throw new Error(`Directory does not exist`);
156
+ if (!fs.existsSync(fullPath)) {
157
+ new Error(`Directory does not exist`);
136
158
  }
137
159
  // Run the main processing function
138
160
  const result = await (0, processLayout_1.doProcessLayoutFileCli)(fullPath, {
@@ -191,7 +213,134 @@ async function handleAction(directories, options) {
191
213
  process.exit(1);
192
214
  }
193
215
  }
194
- // Helper function to format file sizes
216
+ async function handleWatchMode(dir, options) {
217
+ const { quiet, debug, manifestCheck, watchInterval, watchDebounce } = options;
218
+ const fullPath = path.resolve(dir);
219
+ if (!fs.existsSync(fullPath)) {
220
+ logger.error(`Directory does not exist: ${fullPath}`);
221
+ process.exit(1);
222
+ }
223
+ if (!quiet) {
224
+ logger.info(`Watching: ${chalk_1.default.underline(fullPath)}`);
225
+ logger.info(`Press ${chalk_1.default.yellow('Ctrl+C')} to stop watching`);
226
+ console.log();
227
+ }
228
+ if (!quiet) {
229
+ logger.info(`Running initial processing...`);
230
+ }
231
+ try {
232
+ await (0, processLayout_1.doProcessLayoutFileCli)(fullPath, {
233
+ force: true,
234
+ quiet,
235
+ debug,
236
+ checkManifest: manifestCheck
237
+ });
238
+ console.log();
239
+ }
240
+ catch (error) {
241
+ logger.error(`Initial processing failed: ${error.message}`);
242
+ if (debug && error.stack) {
243
+ logger.dim(error.stack);
244
+ }
245
+ process.exit(1);
246
+ }
247
+ let debounceTimer;
248
+ let isProcessing = false;
249
+ let changeCount = 0;
250
+ const watcher = chokidar_1.default.watch(fullPath, {
251
+ ignored: [
252
+ path.join(fullPath, 'layout.json'),
253
+ path.join(fullPath, 'manifest.json')
254
+ ],
255
+ ignoreInitial: true,
256
+ persistent: true,
257
+ interval: parseInt(watchInterval),
258
+ depth: 99
259
+ });
260
+ const processChanges = async () => {
261
+ if (isProcessing) {
262
+ if (debug) {
263
+ logger.dim('Skipping - already processing');
264
+ }
265
+ return;
266
+ }
267
+ clearTimeout(debounceTimer);
268
+ debounceTimer = setTimeout(async () => {
269
+ isProcessing = true;
270
+ changeCount++;
271
+ try {
272
+ await (0, processLayout_1.doProcessLayoutFileCli)(fullPath, {
273
+ force: true,
274
+ quiet: true
275
+ });
276
+ }
277
+ catch (error) {
278
+ const timestamp = new Date().toLocaleTimeString();
279
+ if (!quiet) {
280
+ logger.error(`[${timestamp}] Failed to regenerate: ${error.message}`);
281
+ if (debug && error.stack) {
282
+ logger.dim(error.stack);
283
+ }
284
+ }
285
+ }
286
+ finally {
287
+ isProcessing = false;
288
+ }
289
+ }, parseInt(watchDebounce));
290
+ };
291
+ const timestamp = new Date().toLocaleTimeString();
292
+ watcher
293
+ .on('add', (filePath) => {
294
+ if (!quiet) {
295
+ logger.dim(`[${timestamp}] File added: ${path.relative(fullPath, filePath)}`);
296
+ }
297
+ processChanges();
298
+ })
299
+ .on('change', (filePath) => {
300
+ if (!quiet) {
301
+ logger.dim(`[${timestamp}] File changed: ${path.relative(fullPath, filePath)}`);
302
+ }
303
+ processChanges();
304
+ })
305
+ .on('unlink', (filePath) => {
306
+ if (!quiet) {
307
+ logger.dim(`[${timestamp}] File removed: ${path.relative(fullPath, filePath)}`);
308
+ }
309
+ processChanges();
310
+ })
311
+ .on('addDir', (dirPath) => {
312
+ if (!quiet) {
313
+ logger.dim(`[${timestamp}] Directory added: ${path.relative(fullPath, dirPath)}`);
314
+ }
315
+ processChanges();
316
+ })
317
+ .on('unlinkDir', (dirPath) => {
318
+ if (!quiet) {
319
+ logger.dim(`[${timestamp}] Directory removed: ${path.relative(fullPath, dirPath)}`);
320
+ }
321
+ processChanges();
322
+ })
323
+ .on('error', (error) => {
324
+ if (error instanceof Error) {
325
+ logger.error(`Watcher error: ${error.message}`);
326
+ }
327
+ });
328
+ process.on('SIGINT', async () => {
329
+ if (!quiet) {
330
+ console.log();
331
+ logger.info('Stopping watch mode...');
332
+ }
333
+ await watcher.close();
334
+ if (!quiet) {
335
+ logger.success(`Watch mode stopped`);
336
+ logger.info(`Total changes processed: ${changeCount}`);
337
+ }
338
+ process.exit(0);
339
+ });
340
+ await new Promise(() => {
341
+ // This promise never resolves keeping the process alive
342
+ });
343
+ }
195
344
  function formatFileSize(bytes) {
196
345
  if (bytes === 0)
197
346
  return '0 Bytes';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msfs-layout-generator",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Generate layout.json for MSFS community packages",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,6 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "chalk": "^5.6.2",
44
+ "chokidar": "^5.0.0",
44
45
  "commander": "^14.0.2",
45
46
  "glob": "^13.0.0"
46
47
  }