msfs-layout-generator 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +39 -27
- package/dist/index.d.ts +7 -3
- package/dist/index.js +6 -2
- package/dist/utils/getAllFiles.js +16 -7
- package/dist/utils/processLayout.d.ts +1 -1
- package/dist/utils/processLayout.js +7 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -84,6 +84,8 @@ program
|
|
|
84
84
|
.option('-d, --debug', 'Enable debug logging for troubleshooting')
|
|
85
85
|
.option('--no-manifest-check', 'Skip manifest.json existence check')
|
|
86
86
|
.option('-w, --watch', 'Watch directory for changes and regenerate automatically')
|
|
87
|
+
.option('--watch-debounce <ms>', 'Debounce delay in watch mode (default: 750ms)', '750')
|
|
88
|
+
.option('--watch-interval <ms>', 'Polling interval in watch mode when polling is used (default: 100)', '100')
|
|
87
89
|
.action(async (directories, options) => {
|
|
88
90
|
await handleAction(directories, options);
|
|
89
91
|
})
|
|
@@ -244,8 +246,11 @@ async function handleWatchMode(dir, options) {
|
|
|
244
246
|
}
|
|
245
247
|
process.exit(1);
|
|
246
248
|
}
|
|
249
|
+
const debounceMs = Number.parseInt(watchDebounce ?? '750', 10);
|
|
250
|
+
const intervalMs = Number.parseInt(watchInterval ?? '100', 10);
|
|
247
251
|
let debounceTimer;
|
|
248
252
|
let isProcessing = false;
|
|
253
|
+
let hasPendingChanges = false;
|
|
249
254
|
let changeCount = 0;
|
|
250
255
|
const watcher = chokidar_1.default.watch(fullPath, {
|
|
251
256
|
ignored: [
|
|
@@ -254,39 +259,46 @@ async function handleWatchMode(dir, options) {
|
|
|
254
259
|
],
|
|
255
260
|
ignoreInitial: true,
|
|
256
261
|
persistent: true,
|
|
257
|
-
interval:
|
|
262
|
+
interval: Number.isFinite(intervalMs) ? intervalMs : 100,
|
|
258
263
|
depth: 99
|
|
259
264
|
});
|
|
260
|
-
const
|
|
265
|
+
const processQueuedChanges = async () => {
|
|
261
266
|
if (isProcessing) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
267
|
+
// Keep a rerun request so events arriving during a write/copy are not lost.
|
|
268
|
+
hasPendingChanges = true;
|
|
265
269
|
return;
|
|
266
270
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
isProcessing = true;
|
|
272
|
+
try {
|
|
273
|
+
do {
|
|
274
|
+
hasPendingChanges = false;
|
|
275
|
+
changeCount++;
|
|
272
276
|
await (0, processLayout_1.doProcessLayoutFileCli)(fullPath, {
|
|
273
277
|
force: true,
|
|
274
278
|
quiet: true
|
|
275
279
|
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
} while (hasPendingChanges);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
284
|
+
if (!quiet) {
|
|
285
|
+
logger.error(`[${timestamp}] Failed to regenerate: ${error.message}`);
|
|
286
|
+
if (debug && error.stack) {
|
|
287
|
+
logger.dim(error.stack);
|
|
284
288
|
}
|
|
285
289
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
isProcessing = false;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
const scheduleProcessing = () => {
|
|
296
|
+
if (debounceTimer) {
|
|
297
|
+
clearTimeout(debounceTimer);
|
|
298
|
+
}
|
|
299
|
+
debounceTimer = setTimeout(() => {
|
|
300
|
+
void processQueuedChanges();
|
|
301
|
+
}, Number.isFinite(debounceMs) ? debounceMs : 750);
|
|
290
302
|
};
|
|
291
303
|
const timestamp = new Date().toLocaleTimeString();
|
|
292
304
|
watcher
|
|
@@ -294,31 +306,31 @@ async function handleWatchMode(dir, options) {
|
|
|
294
306
|
if (!quiet) {
|
|
295
307
|
logger.dim(`[${timestamp}] File added: ${path.relative(fullPath, filePath)}`);
|
|
296
308
|
}
|
|
297
|
-
|
|
309
|
+
scheduleProcessing();
|
|
298
310
|
})
|
|
299
311
|
.on('change', (filePath) => {
|
|
300
312
|
if (!quiet) {
|
|
301
313
|
logger.dim(`[${timestamp}] File changed: ${path.relative(fullPath, filePath)}`);
|
|
302
314
|
}
|
|
303
|
-
|
|
315
|
+
scheduleProcessing();
|
|
304
316
|
})
|
|
305
317
|
.on('unlink', (filePath) => {
|
|
306
318
|
if (!quiet) {
|
|
307
319
|
logger.dim(`[${timestamp}] File removed: ${path.relative(fullPath, filePath)}`);
|
|
308
320
|
}
|
|
309
|
-
|
|
321
|
+
scheduleProcessing();
|
|
310
322
|
})
|
|
311
323
|
.on('addDir', (dirPath) => {
|
|
312
324
|
if (!quiet) {
|
|
313
325
|
logger.dim(`[${timestamp}] Directory added: ${path.relative(fullPath, dirPath)}`);
|
|
314
326
|
}
|
|
315
|
-
|
|
327
|
+
scheduleProcessing();
|
|
316
328
|
})
|
|
317
329
|
.on('unlinkDir', (dirPath) => {
|
|
318
330
|
if (!quiet) {
|
|
319
331
|
logger.dim(`[${timestamp}] Directory removed: ${path.relative(fullPath, dirPath)}`);
|
|
320
332
|
}
|
|
321
|
-
|
|
333
|
+
scheduleProcessing();
|
|
322
334
|
})
|
|
323
335
|
.on('error', (error) => {
|
|
324
336
|
if (error instanceof Error) {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { processLayout } from "./utils/processLayout";
|
|
|
2
2
|
/**
|
|
3
3
|
* Process an MSFS package directory to generate/update layout.json (simple API).
|
|
4
4
|
*
|
|
5
|
-
* This is a convenience wrapper around {@link processLayout} that
|
|
6
|
-
*
|
|
5
|
+
* This is a convenience wrapper around {@link processLayout} that throws on errors.
|
|
6
|
+
* You can pass the same processing options except `returnResult`.
|
|
7
7
|
*
|
|
8
8
|
* @param packageDir - Path to the MSFS package directory (must contain manifest.json)
|
|
9
9
|
* @returns Promise that resolves when layout.json has been generated
|
|
@@ -16,6 +16,10 @@ import { processLayout } from "./utils/processLayout";
|
|
|
16
16
|
* await generateLayout("F:\\fs20\\Community\\my-package");
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
|
+
* // Overwrite existing layout.json
|
|
20
|
+
* await generateLayout("./my-package", { force: true });
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
19
23
|
* // With error handling
|
|
20
24
|
* try {
|
|
21
25
|
* await generateLayout("./my-package");
|
|
@@ -24,6 +28,6 @@ import { processLayout } from "./utils/processLayout";
|
|
|
24
28
|
* console.error("Failed:", error.message);
|
|
25
29
|
* }
|
|
26
30
|
*/
|
|
27
|
-
export declare const generateLayout: (
|
|
31
|
+
export declare const generateLayout: (packageDir: string, options?: Omit<import("./types").ProcessOptions, "returnResult">) => Promise<void>;
|
|
28
32
|
export { processLayout };
|
|
29
33
|
export type { Content, Layout, Manifest, ProcessOptions, ProcessResult } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "processLayout", { enumerable: true, get: functio
|
|
|
6
6
|
/**
|
|
7
7
|
* Process an MSFS package directory to generate/update layout.json (simple API).
|
|
8
8
|
*
|
|
9
|
-
* This is a convenience wrapper around {@link processLayout} that
|
|
10
|
-
*
|
|
9
|
+
* This is a convenience wrapper around {@link processLayout} that throws on errors.
|
|
10
|
+
* You can pass the same processing options except `returnResult`.
|
|
11
11
|
*
|
|
12
12
|
* @param packageDir - Path to the MSFS package directory (must contain manifest.json)
|
|
13
13
|
* @returns Promise that resolves when layout.json has been generated
|
|
@@ -20,6 +20,10 @@ Object.defineProperty(exports, "processLayout", { enumerable: true, get: functio
|
|
|
20
20
|
* await generateLayout("F:\\fs20\\Community\\my-package");
|
|
21
21
|
*
|
|
22
22
|
* @example
|
|
23
|
+
* // Overwrite existing layout.json
|
|
24
|
+
* await generateLayout("./my-package", { force: true });
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
23
27
|
* // With error handling
|
|
24
28
|
* try {
|
|
25
29
|
* await generateLayout("./my-package");
|
|
@@ -41,22 +41,31 @@ const getAllFiles = async (dirPath) => {
|
|
|
41
41
|
const files = [];
|
|
42
42
|
async function readDirectory(currentPath) {
|
|
43
43
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
44
|
+
const directs = await (0, promises_1.readdir)(currentPath, { withFileTypes: true });
|
|
45
|
+
for (const dirent of directs) {
|
|
46
|
+
const itemPath = path.join(currentPath, dirent.name);
|
|
47
|
+
if (dirent.isDirectory()) {
|
|
48
48
|
await readDirectory(itemPath);
|
|
49
49
|
}
|
|
50
|
-
else {
|
|
50
|
+
else if (dirent.isFile()) {
|
|
51
51
|
files.push(itemPath);
|
|
52
52
|
}
|
|
53
|
+
else if (dirent.isSymbolicLink()) {
|
|
54
|
+
const stats = await (0, promises_1.stat)(itemPath);
|
|
55
|
+
if (stats.isDirectory()) {
|
|
56
|
+
await readDirectory(itemPath);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
files.push(itemPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
53
62
|
}
|
|
54
63
|
}
|
|
55
64
|
catch (e) {
|
|
56
65
|
if (e instanceof Error) {
|
|
57
|
-
throw new _errors_1.ReadingDirError(`Failed to read directory ${
|
|
66
|
+
throw new _errors_1.ReadingDirError(`Failed to read directory ${currentPath}: ${e.message}.`);
|
|
58
67
|
}
|
|
59
|
-
throw new _errors_1.ReadingDirError(`Failed to read directory. Path: ${
|
|
68
|
+
throw new _errors_1.ReadingDirError(`Failed to read directory. Path: ${currentPath}`);
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
71
|
await readDirectory(dirPath);
|
|
@@ -42,5 +42,5 @@ import { ProcessOptions, ProcessResult } from "../types";
|
|
|
42
42
|
* });
|
|
43
43
|
*/
|
|
44
44
|
export declare const processLayout: (packageDir: string, options?: ProcessOptions) => Promise<void | ProcessResult>;
|
|
45
|
-
export declare const doProcessLayoutFile: (
|
|
45
|
+
export declare const doProcessLayoutFile: (packageDir: string, options?: Omit<ProcessOptions, "returnResult">) => Promise<void>;
|
|
46
46
|
export declare const doProcessLayoutFileCli: (packageDir: string, options?: Omit<ProcessOptions, "returnResult">) => Promise<ProcessResult>;
|
|
@@ -202,15 +202,16 @@ const processLayout = async (packageDir, options = {}) => {
|
|
|
202
202
|
throw new Error(error);
|
|
203
203
|
}
|
|
204
204
|
for (const file of allFiles) {
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
const relativePath = path.relative(packageDir, file).split(path.sep).join('/');
|
|
206
|
+
// Windows MAX_PATH checks should only be enforced on Windows hosts.
|
|
207
|
+
if (process.platform === 'win32' && file.length > 259) {
|
|
207
208
|
hasLongPath = true;
|
|
209
|
+
excludedCount++;
|
|
208
210
|
if (debug) {
|
|
209
211
|
log(`Skipping long path: ${file}`, 'info');
|
|
210
212
|
}
|
|
211
213
|
continue;
|
|
212
214
|
}
|
|
213
|
-
const relativePath = path.relative(packageDir, file).split(path.sep).join('/');
|
|
214
215
|
const isExcluded = (0, doExcludeFile_1.doExcludeFile)(relativePath);
|
|
215
216
|
try {
|
|
216
217
|
const stats = await (0, promises_1.stat)(file);
|
|
@@ -230,9 +231,8 @@ const processLayout = async (packageDir, options = {}) => {
|
|
|
230
231
|
layout.content.push(content);
|
|
231
232
|
}
|
|
232
233
|
catch (error) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
234
|
+
const msg = `Error processing file ${file}: ${error.message}`;
|
|
235
|
+
log(msg, debug ? 'info' : 'warn');
|
|
236
236
|
excludedCount++;
|
|
237
237
|
}
|
|
238
238
|
}
|
|
@@ -309,7 +309,7 @@ const processLayout = async (packageDir, options = {}) => {
|
|
|
309
309
|
}
|
|
310
310
|
};
|
|
311
311
|
exports.processLayout = processLayout;
|
|
312
|
-
const doProcessLayoutFile = (
|
|
312
|
+
const doProcessLayoutFile = (packageDir, options = {}) => (0, exports.processLayout)(packageDir, { ...options, returnResult: false });
|
|
313
313
|
exports.doProcessLayoutFile = doProcessLayoutFile;
|
|
314
314
|
const doProcessLayoutFileCli = (packageDir, options = {}) => (0, exports.processLayout)(packageDir, { ...options, returnResult: true });
|
|
315
315
|
exports.doProcessLayoutFileCli = doProcessLayoutFileCli;
|