@mpgd/cli 0.33.1 → 0.33.2
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/asset-pack-verify.js +74 -17
- package/package.json +12 -12
- package/templates/phaser-game/package.json +1 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
2
3
|
import { lstat, open, opendir, realpath, stat } from 'node:fs/promises';
|
|
3
4
|
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
5
|
import { verifyZipV1Archive, ZipDecodeError, } from '@mpgd/phaser-assets/archive-validation';
|
|
@@ -6,6 +7,7 @@ import { defaultArchiveWorkerLimits } from '@mpgd/phaser-assets/archives';
|
|
|
6
7
|
import { validatePhaserPackDeliveryManifest, } from '@mpgd/phaser-assets/pack-format';
|
|
7
8
|
const DEFAULT_MANIFEST_BYTE_CAP = 32 * 1024 * 1024;
|
|
8
9
|
const DEFAULT_VERIFY_TIMEOUT_MS = 120_000;
|
|
10
|
+
const FIFO_WRITER_GRACE_MS = 1000;
|
|
9
11
|
/** The watchdog arms one millisecond after the deadline. Leave one
|
|
10
12
|
* millisecond of headroom so the largest accepted timeout plus that offset
|
|
11
13
|
* still fits the platform timer range instead of clamping to 1 ms. */
|
|
@@ -255,10 +257,10 @@ const inventoryRoot = async (root, deadline) => {
|
|
|
255
257
|
return { files, bytes, largest, truncated };
|
|
256
258
|
};
|
|
257
259
|
const readManifestCapped = async (manifestPath, cap, deadline) => {
|
|
258
|
-
// Open through the deadline
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
const handle = await deadline.race(open(resolve(manifestPath),
|
|
260
|
+
// Open through the deadline. POSIX O_NONBLOCK keeps FIFO open/read calls
|
|
261
|
+
// bounded; Windows retains the stream fallback under the same deadline.
|
|
262
|
+
const flags = process.platform === 'win32' ? 'r' : constants.O_RDONLY | constants.O_NONBLOCK;
|
|
263
|
+
const handle = await deadline.race(open(resolve(manifestPath), flags));
|
|
262
264
|
const chunks = [];
|
|
263
265
|
let received = 0;
|
|
264
266
|
let stalled = false;
|
|
@@ -292,30 +294,85 @@ const readManifestCapped = async (manifestPath, cap, deadline) => {
|
|
|
292
294
|
throw new Error(`Delivery manifest changed while reading (expected ${expectedBytes} bytes, received more)`);
|
|
293
295
|
}
|
|
294
296
|
}
|
|
297
|
+
else if (process.platform !== 'win32') {
|
|
298
|
+
// A blocking FIFO read can remain pending after stream.destroy() on
|
|
299
|
+
// some Linux hosts. O_NONBLOCK bounds each read: EAGAIN means a writer
|
|
300
|
+
// is connected but has no data; a later zero-byte read is its EOF.
|
|
301
|
+
const buffer = Buffer.allocUnsafe(Math.min(STREAM_CHUNK_BYTES, cap + 1));
|
|
302
|
+
const isFifo = info.isFIFO();
|
|
303
|
+
// A writer can open and close between read polls without ever yielding
|
|
304
|
+
// EAGAIN. Bound the initial connection wait so that empty EOF cannot
|
|
305
|
+
// consume the default two-minute verification budget.
|
|
306
|
+
const writerGraceUntil = performance.now() + Math.min(FIFO_WRITER_GRACE_MS, deadline.remainingMs());
|
|
307
|
+
let writerObserved = false;
|
|
308
|
+
while (true) {
|
|
309
|
+
deadline.sample();
|
|
310
|
+
let bytesRead;
|
|
311
|
+
let wouldBlock = false;
|
|
312
|
+
try {
|
|
313
|
+
({ bytesRead } = await deadline.race(handle.read(buffer, 0, buffer.byteLength, null)));
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (typeof error !== 'object'
|
|
317
|
+
|| error === null
|
|
318
|
+
|| !('code' in error)
|
|
319
|
+
|| (error.code !== 'EAGAIN' && error.code !== 'EWOULDBLOCK')) {
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
writerObserved = true;
|
|
323
|
+
wouldBlock = true;
|
|
324
|
+
bytesRead = 0;
|
|
325
|
+
}
|
|
326
|
+
if (bytesRead === 0) {
|
|
327
|
+
// Other sources have ordinary EOF. An unconnected FIFO waits only
|
|
328
|
+
// for its bounded startup grace; an observed writer has hung up.
|
|
329
|
+
if (!wouldBlock
|
|
330
|
+
&& (!isFifo || writerObserved || performance.now() >= writerGraceUntil)) {
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
const waitMs = Math.min(10, Math.max(1, deadline.remainingMs()));
|
|
334
|
+
await deadline.race(new Promise((resume) => setTimeout(resume, waitMs)));
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
writerObserved = true;
|
|
338
|
+
received += bytesRead;
|
|
339
|
+
if (received > cap) {
|
|
340
|
+
throw new Error(`Delivery manifest exceeds ${cap} bytes`);
|
|
341
|
+
}
|
|
342
|
+
chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
|
|
343
|
+
deadline.sample();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
295
346
|
else {
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
// rejected race.
|
|
347
|
+
// Windows non-regular sources retain the stream watchdog. A pending
|
|
348
|
+
// read must be destroyed at the deadline, then the race can return
|
|
349
|
+
// without waiting for that read to settle.
|
|
300
350
|
const stream = handle.createReadStream({
|
|
301
351
|
highWaterMark: STREAM_CHUNK_BYTES,
|
|
302
352
|
autoClose: false,
|
|
303
353
|
});
|
|
304
354
|
const timer = deadline.armStream(stream);
|
|
305
355
|
try {
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
356
|
+
await deadline.race((async () => {
|
|
357
|
+
for await (const chunk of stream) {
|
|
358
|
+
const view = chunk;
|
|
359
|
+
received += view.byteLength;
|
|
360
|
+
if (received > cap) {
|
|
361
|
+
stream.destroy();
|
|
362
|
+
throw new Error(`Delivery manifest exceeds ${cap} bytes`);
|
|
363
|
+
}
|
|
364
|
+
chunks.push(Buffer.from(view));
|
|
365
|
+
deadline.sample();
|
|
312
366
|
}
|
|
313
|
-
|
|
314
|
-
deadline.sample();
|
|
315
|
-
}
|
|
367
|
+
})());
|
|
316
368
|
}
|
|
317
369
|
finally {
|
|
370
|
+
// Destroy may not wake a pending FIFO read until its writer closes.
|
|
371
|
+
// The raced deadline returns the report without waiting for that read.
|
|
318
372
|
clearTimeout(timer);
|
|
373
|
+
if (!stream.destroyed) {
|
|
374
|
+
stream.destroy();
|
|
375
|
+
}
|
|
319
376
|
}
|
|
320
377
|
}
|
|
321
378
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/cli",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.2",
|
|
4
4
|
"description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -53,18 +53,18 @@
|
|
|
53
53
|
"@types/node": "^24.0.0",
|
|
54
54
|
"ttsc": "0.30.4",
|
|
55
55
|
"typescript": "7.0.2",
|
|
56
|
-
"@mpgd/adapter-
|
|
57
|
-
"@mpgd/adapter-
|
|
58
|
-
"@mpgd/adapter-
|
|
59
|
-
"@mpgd/adapter-
|
|
60
|
-
"@mpgd/adapter-verse8": "0.3.
|
|
61
|
-
"@mpgd/analytics": "0.3.
|
|
56
|
+
"@mpgd/adapter-capacitor": "0.4.12",
|
|
57
|
+
"@mpgd/adapter-ait": "0.12.4",
|
|
58
|
+
"@mpgd/adapter-browser": "0.7.5",
|
|
59
|
+
"@mpgd/adapter-devvit": "0.9.10",
|
|
60
|
+
"@mpgd/adapter-verse8": "0.3.8",
|
|
61
|
+
"@mpgd/analytics": "0.3.14",
|
|
62
62
|
"@mpgd/bridge": "0.9.1",
|
|
63
|
-
"@mpgd/
|
|
64
|
-
"@mpgd/
|
|
65
|
-
"@mpgd/
|
|
66
|
-
"@mpgd/
|
|
67
|
-
"@mpgd/
|
|
63
|
+
"@mpgd/catalog": "0.7.4",
|
|
64
|
+
"@mpgd/game-services": "0.15.3",
|
|
65
|
+
"@mpgd/platform": "0.12.2",
|
|
66
|
+
"@mpgd/i18n": "0.6.3",
|
|
67
|
+
"@mpgd/target-config": "0.15.2"
|
|
68
68
|
},
|
|
69
69
|
"main": "./dist/index.js",
|
|
70
70
|
"types": "./dist/index.d.ts",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@ait-co/polyfill": "^0.1.19",
|
|
59
59
|
"@apps-in-toss/web-framework": "3.5.0",
|
|
60
|
+
"@capacitor/core": "8.5.2",
|
|
60
61
|
"@mpgd/adapter-ait": "__MPGD_DEPENDENCY_VERSION_ADAPTER_AIT__",
|
|
61
62
|
"@mpgd/adapter-browser": "__MPGD_DEPENDENCY_VERSION_ADAPTER_BROWSER__",
|
|
62
63
|
"@mpgd/adapter-capacitor": "__MPGD_DEPENDENCY_VERSION_ADAPTER_CAPACITOR__",
|