@pygmalionjs/pygmalion 0.5.3 → 0.5.6
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-lib/{FrozenRoutePreview-C9535oYO.js → FrozenRoutePreview-ZQp4jiaX.js} +1770 -1663
- package/dist-lib/pygmalion.js +3552 -3455
- package/dist-lib/testing.js +1 -1
- package/node/storyboard-environment.mjs +134 -0
- package/node/storyboard.mjs +8 -0
- package/package.json +1 -1
- package/types.d.ts +7 -0
package/dist-lib/testing.js
CHANGED
|
@@ -1384,6 +1384,140 @@ export function storyboardEnvironmentBootstrapSource() {
|
|
|
1384
1384
|
});
|
|
1385
1385
|
}
|
|
1386
1386
|
|
|
1387
|
+
// Request outcomes and request recording share one interception point: the
|
|
1388
|
+
// frame's own traffic is both what the editor asks about and what a condition
|
|
1389
|
+
// acts on, so recording is the enumeration and conditions are the answer.
|
|
1390
|
+
const network = plainRecord(environment.network);
|
|
1391
|
+
if (network) {
|
|
1392
|
+
const conditions = Array.isArray(network.conditions)
|
|
1393
|
+
? network.conditions.filter((condition) => {
|
|
1394
|
+
const record = plainRecord(condition);
|
|
1395
|
+
return (
|
|
1396
|
+
record &&
|
|
1397
|
+
typeof record.match === 'string' &&
|
|
1398
|
+
record.match.length > 0 &&
|
|
1399
|
+
plainRecord(record.outcome) &&
|
|
1400
|
+
['fail', 'empty', 'slow'].includes(record.outcome.kind)
|
|
1401
|
+
);
|
|
1402
|
+
})
|
|
1403
|
+
: [];
|
|
1404
|
+
const recorded = network.record === true ? [] : null;
|
|
1405
|
+
if (recorded) {
|
|
1406
|
+
Object.defineProperty(globalThis, '__PYGMALION_REQUESTS__', {
|
|
1407
|
+
configurable: true,
|
|
1408
|
+
value: recorded,
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
const methodOf = (init, fallback) =>
|
|
1412
|
+
String(init?.method ?? fallback ?? 'GET').toUpperCase();
|
|
1413
|
+
const matchFor = (url, method) =>
|
|
1414
|
+
conditions.find(
|
|
1415
|
+
(condition) =>
|
|
1416
|
+
url.includes(condition.match) &&
|
|
1417
|
+
(condition.method == null ||
|
|
1418
|
+
String(condition.method).toUpperCase() === method),
|
|
1419
|
+
) ?? null;
|
|
1420
|
+
const wait = (ms) =>
|
|
1421
|
+
new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0)));
|
|
1422
|
+
|
|
1423
|
+
if (typeof globalThis.fetch === 'function') {
|
|
1424
|
+
const originalFetch = globalThis.fetch.bind(globalThis);
|
|
1425
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
1426
|
+
configurable: true,
|
|
1427
|
+
writable: true,
|
|
1428
|
+
async value(input, init) {
|
|
1429
|
+
const url = String(
|
|
1430
|
+
typeof input === 'string' ? input : (input?.url ?? input),
|
|
1431
|
+
);
|
|
1432
|
+
const method = methodOf(init, input?.method);
|
|
1433
|
+
if (recorded) recorded.push({ url, method });
|
|
1434
|
+
const condition = matchFor(url, method);
|
|
1435
|
+
if (!condition) return originalFetch(input, init);
|
|
1436
|
+
const outcome = condition.outcome;
|
|
1437
|
+
if (outcome.kind === 'slow') {
|
|
1438
|
+
await wait(outcome.delayMs);
|
|
1439
|
+
return originalFetch(input, init);
|
|
1440
|
+
}
|
|
1441
|
+
if (outcome.kind === 'empty') {
|
|
1442
|
+
// A well-formed response with nothing in it — the empty state is a
|
|
1443
|
+
// success path, so failing here would exercise the wrong branch.
|
|
1444
|
+
return new Response(String(outcome.body ?? ''), {
|
|
1445
|
+
status: Number(outcome.status ?? 200),
|
|
1446
|
+
headers: { 'content-type': 'application/json' },
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
const status = Number(outcome.status ?? 0);
|
|
1450
|
+
if (status > 0) {
|
|
1451
|
+
return new Response(null, {
|
|
1452
|
+
status,
|
|
1453
|
+
statusText: String(outcome.message ?? 'Pygmalion request condition'),
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
throw new TypeError(
|
|
1457
|
+
String(outcome.message ?? 'Pygmalion request condition: failed'),
|
|
1458
|
+
);
|
|
1459
|
+
},
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const XHR = globalThis.XMLHttpRequest;
|
|
1464
|
+
if (typeof XHR === 'function' && XHR.prototype) {
|
|
1465
|
+
const originalOpen = XHR.prototype.open;
|
|
1466
|
+
const originalSend = XHR.prototype.send;
|
|
1467
|
+
const state = new WeakMap();
|
|
1468
|
+
Object.defineProperty(XHR.prototype, 'open', {
|
|
1469
|
+
configurable: true,
|
|
1470
|
+
value(method, url, ...rest) {
|
|
1471
|
+
state.set(this, {
|
|
1472
|
+
url: String(url),
|
|
1473
|
+
method: String(method ?? 'GET').toUpperCase(),
|
|
1474
|
+
});
|
|
1475
|
+
return originalOpen.call(this, method, url, ...rest);
|
|
1476
|
+
},
|
|
1477
|
+
});
|
|
1478
|
+
Object.defineProperty(XHR.prototype, 'send', {
|
|
1479
|
+
configurable: true,
|
|
1480
|
+
value(body) {
|
|
1481
|
+
const opened = state.get(this);
|
|
1482
|
+
if (!opened) return originalSend.call(this, body);
|
|
1483
|
+
if (recorded) recorded.push({ ...opened });
|
|
1484
|
+
const condition = matchFor(opened.url, opened.method);
|
|
1485
|
+
if (!condition) return originalSend.call(this, body);
|
|
1486
|
+
const outcome = condition.outcome;
|
|
1487
|
+
if (outcome.kind === 'slow') {
|
|
1488
|
+
wait(outcome.delayMs).then(() => originalSend.call(this, body));
|
|
1489
|
+
return undefined;
|
|
1490
|
+
}
|
|
1491
|
+
// The event order matters to callers: readyState settles, then the
|
|
1492
|
+
// terminal event fires, the same way the real object behaves.
|
|
1493
|
+
const fail = () => {
|
|
1494
|
+
Object.defineProperty(this, 'readyState', {
|
|
1495
|
+
configurable: true,
|
|
1496
|
+
value: 4,
|
|
1497
|
+
});
|
|
1498
|
+
if (outcome.kind === 'empty') {
|
|
1499
|
+
Object.defineProperties(this, {
|
|
1500
|
+
status: { configurable: true, value: Number(outcome.status ?? 200) },
|
|
1501
|
+
responseText: { configurable: true, value: String(outcome.body ?? '') },
|
|
1502
|
+
response: { configurable: true, value: String(outcome.body ?? '') },
|
|
1503
|
+
});
|
|
1504
|
+
this.dispatchEvent(new Event('load'));
|
|
1505
|
+
} else {
|
|
1506
|
+
Object.defineProperty(this, 'status', {
|
|
1507
|
+
configurable: true,
|
|
1508
|
+
value: Number(outcome.status ?? 0),
|
|
1509
|
+
});
|
|
1510
|
+
this.dispatchEvent(new Event('error'));
|
|
1511
|
+
}
|
|
1512
|
+
this.dispatchEvent(new Event('loadend'));
|
|
1513
|
+
};
|
|
1514
|
+
setTimeout(fail, 0);
|
|
1515
|
+
return undefined;
|
|
1516
|
+
},
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1387
1521
|
const permissions = plainRecord(environment.permissions);
|
|
1388
1522
|
if (permissions && navigator.permissions?.query) {
|
|
1389
1523
|
const originalQuery = navigator.permissions.query.bind(navigator.permissions);
|
package/node/storyboard.mjs
CHANGED
|
@@ -50,3 +50,11 @@ export {
|
|
|
50
50
|
validateRoutePreviewArtifactBundle,
|
|
51
51
|
validateRoutePreviewArtifactV3,
|
|
52
52
|
} from './route-preview-artifact-v3.mjs';
|
|
53
|
+
|
|
54
|
+
// The source scan, so a consumer's own gate can ask the route question outside
|
|
55
|
+
// a running Vite server: the scan lists the routes the codebase can render and
|
|
56
|
+
// `enumerateRouteCandidates` turns the ones with no screen case into demands.
|
|
57
|
+
export {
|
|
58
|
+
PYGMALION_STORYBOARD_ENVIRONMENT_QUERY,
|
|
59
|
+
scanStoryboardEnvironment,
|
|
60
|
+
} from './storyboard-environment.mjs';
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -1488,6 +1488,13 @@ export interface InteractiveStateOption {
|
|
|
1488
1488
|
label: string;
|
|
1489
1489
|
/** Absent or empty marks the base state — the screen as captured. */
|
|
1490
1490
|
steps?: readonly DesignScreenInteraction[];
|
|
1491
|
+
/**
|
|
1492
|
+
* Boot condition for states no gesture can reach — a failed request, an empty
|
|
1493
|
+
* result, a stalled stream. Merged into the frame's environment, which already
|
|
1494
|
+
* belongs to the recipe identity, so the existing supply path reproduces it.
|
|
1495
|
+
* The base is the option with neither steps nor a condition.
|
|
1496
|
+
*/
|
|
1497
|
+
environment?: StoryboardEnvironment;
|
|
1491
1498
|
/**
|
|
1492
1499
|
* Recognizes this option in the CAPTURED tree, making the base a property of
|
|
1493
1500
|
* the frame instead of the axis. Declare it on every option (each with its own
|