@pygmalionjs/pygmalion 0.4.0 → 0.5.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.
- package/dist-lib/FrozenRoutePreview-efexOSqR.js +9416 -0
- package/dist-lib/pygmalion.js +9498 -6873
- package/dist-lib/style.css +1 -1
- package/dist-lib/testing.js +1 -1
- package/node/component-branches.mjs +108 -0
- package/node/dev-view.vite.mjs +45 -1
- package/node/preview-artifact-plugin.mjs +20 -1
- package/node/preview-artifact-store.mjs +483 -29
- package/node/route-dependency-digest.mjs +151 -0
- package/node/route-preview-artifact-v3.mjs +10 -5
- package/node/source-graph.mjs +258 -0
- package/node/storyboard-capture-runtime.mjs +25 -2
- package/node/storyboard-environment.mjs +97 -10
- package/node/vite.mjs +11 -0
- package/package.json +2 -1
- package/types.d.ts +495 -0
- package/vite.d.ts +31 -0
- package/dist-lib/FrozenRoutePreview-B6xc8Ami.js +0 -8059
|
@@ -2,7 +2,11 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import ts from 'typescript';
|
|
5
|
-
import { buildSourceGraph } from './source-graph.mjs';
|
|
5
|
+
import { buildSourceGraph, findSourceUsage } from './source-graph.mjs';
|
|
6
|
+
import {
|
|
7
|
+
componentBranchesMiddleware,
|
|
8
|
+
PYGMALION_COMPONENT_BRANCHES_CONTROL,
|
|
9
|
+
} from './component-branches.mjs';
|
|
6
10
|
import {
|
|
7
11
|
canonicalizeStoryboardExecutions,
|
|
8
12
|
PYGMALION_STORYBOARD_CANONICAL_CONTROL,
|
|
@@ -10,6 +14,9 @@ import {
|
|
|
10
14
|
|
|
11
15
|
export const PYGMALION_STORYBOARD_ENVIRONMENT_CONTROL =
|
|
12
16
|
'/__pygmalion-storyboard/environment';
|
|
17
|
+
export const PYGMALION_SOURCE_USAGE_CONTROL =
|
|
18
|
+
'/__pygmalion-storyboard/source-usage';
|
|
19
|
+
const MAX_SOURCE_USAGE_REQUEST = 100;
|
|
13
20
|
export const PYGMALION_STORYBOARD_ENVIRONMENT_QUERY =
|
|
14
21
|
'__pygmalion_environment';
|
|
15
22
|
|
|
@@ -1285,12 +1292,29 @@ export function storyboardEnvironmentBootstrapSource() {
|
|
|
1285
1292
|
value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
1286
1293
|
const environment = plainRecord(decode(encoded));
|
|
1287
1294
|
if (!environment) return;
|
|
1295
|
+
// The Node capture runtime installs the host screen preset with an init
|
|
1296
|
+
// script before any page script runs; in-editor session walks reach the
|
|
1297
|
+
// same global through the boot URL so fixtures that read it at module or
|
|
1298
|
+
// mount time (auth role overrides, static mic levels) behave identically.
|
|
1299
|
+
const screenPreset = plainRecord(environment.screenPreset);
|
|
1300
|
+
if (screenPreset) {
|
|
1301
|
+
Object.defineProperty(globalThis, '__PYGMALION_SCREEN_PRESET__', {
|
|
1302
|
+
configurable: true,
|
|
1303
|
+
value: screenPreset,
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
// 'sandbox' virtualizes the whole store per document: reads of undeclared
|
|
1307
|
+
// keys see a pristine profile and writes never reach the shared origin
|
|
1308
|
+
// storage. Hidden preview instances run with it so a walk that persists UI
|
|
1309
|
+
// state (a collapsed sidebar, a dismissed notice) cannot poison sibling
|
|
1310
|
+
// instances or the designer's own tab. Declared entries seed the sandbox.
|
|
1311
|
+
const sandboxed = environment.storageIsolation === 'sandbox';
|
|
1288
1312
|
const storageOverlays = new Map();
|
|
1289
1313
|
const addStorageOverlay = (storage, entries) => {
|
|
1290
1314
|
const record = plainRecord(entries);
|
|
1291
|
-
if (!storage || !record) return;
|
|
1315
|
+
if (!storage || (!record && !sandboxed)) return;
|
|
1292
1316
|
const overlay = {};
|
|
1293
|
-
for (const [key, value] of Object.entries(record)) {
|
|
1317
|
+
for (const [key, value] of Object.entries(record ?? {})) {
|
|
1294
1318
|
if (
|
|
1295
1319
|
typeof key === 'string' &&
|
|
1296
1320
|
key.length <= 512 &&
|
|
@@ -1315,9 +1339,11 @@ export function storyboardEnvironmentBootstrapSource() {
|
|
|
1315
1339
|
value(key) {
|
|
1316
1340
|
const normalizedKey = String(key);
|
|
1317
1341
|
const overlay = storageOverlays.get(this);
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1342
|
+
if (overlay && Object.hasOwn(overlay, normalizedKey)) {
|
|
1343
|
+
return overlay[normalizedKey];
|
|
1344
|
+
}
|
|
1345
|
+
if (overlay && sandboxed) return null;
|
|
1346
|
+
return originalGetItem.call(this, normalizedKey);
|
|
1321
1347
|
},
|
|
1322
1348
|
},
|
|
1323
1349
|
setItem: {
|
|
@@ -1325,7 +1351,7 @@ export function storyboardEnvironmentBootstrapSource() {
|
|
|
1325
1351
|
value(key, value) {
|
|
1326
1352
|
const normalizedKey = String(key);
|
|
1327
1353
|
const overlay = storageOverlays.get(this);
|
|
1328
|
-
if (overlay && Object.hasOwn(overlay, normalizedKey)) {
|
|
1354
|
+
if (overlay && (sandboxed || Object.hasOwn(overlay, normalizedKey))) {
|
|
1329
1355
|
overlay[normalizedKey] = String(value);
|
|
1330
1356
|
return;
|
|
1331
1357
|
}
|
|
@@ -1337,7 +1363,7 @@ export function storyboardEnvironmentBootstrapSource() {
|
|
|
1337
1363
|
value(key) {
|
|
1338
1364
|
const normalizedKey = String(key);
|
|
1339
1365
|
const overlay = storageOverlays.get(this);
|
|
1340
|
-
if (overlay && Object.hasOwn(overlay, normalizedKey)) {
|
|
1366
|
+
if (overlay && (sandboxed || Object.hasOwn(overlay, normalizedKey))) {
|
|
1341
1367
|
overlay[normalizedKey] = null;
|
|
1342
1368
|
return;
|
|
1343
1369
|
}
|
|
@@ -1351,6 +1377,7 @@ export function storyboardEnvironmentBootstrapSource() {
|
|
|
1351
1377
|
if (overlay) {
|
|
1352
1378
|
for (const key of Object.keys(overlay)) overlay[key] = null;
|
|
1353
1379
|
}
|
|
1380
|
+
if (sandboxed) return;
|
|
1354
1381
|
return originalClear.call(this);
|
|
1355
1382
|
},
|
|
1356
1383
|
},
|
|
@@ -1441,17 +1468,25 @@ export function storyboardEnvironmentBootstrapSource() {
|
|
|
1441
1468
|
})();`;
|
|
1442
1469
|
}
|
|
1443
1470
|
|
|
1471
|
+
export const PYGMALION_STORYBOARD_RUNTIME_ATTRIBUTE =
|
|
1472
|
+
'data-pygmalion-storyboard-runtime';
|
|
1473
|
+
|
|
1444
1474
|
export function pygmalionStoryboardRuntimePlugin() {
|
|
1445
1475
|
return {
|
|
1446
1476
|
name: 'pygmalion-storyboard-runtime',
|
|
1447
1477
|
enforce: 'pre',
|
|
1448
1478
|
transformIndexHtml: {
|
|
1449
1479
|
order: 'pre',
|
|
1450
|
-
handler() {
|
|
1480
|
+
handler(html) {
|
|
1481
|
+
// Composed stacks include this plugin twice (the storyboard plugin
|
|
1482
|
+
// embeds it and hosts also add it directly). Vite applies each hook's
|
|
1483
|
+
// tags before the next hook runs, so a copy already in the document
|
|
1484
|
+
// means another instance injected first — one bootstrap is enough.
|
|
1485
|
+
if (html?.includes(PYGMALION_STORYBOARD_RUNTIME_ATTRIBUTE)) return;
|
|
1451
1486
|
return [
|
|
1452
1487
|
{
|
|
1453
1488
|
tag: 'script',
|
|
1454
|
-
attrs: {
|
|
1489
|
+
attrs: { [PYGMALION_STORYBOARD_RUNTIME_ATTRIBUTE]: '' },
|
|
1455
1490
|
children: storyboardEnvironmentBootstrapSource(),
|
|
1456
1491
|
injectTo: 'head-prepend',
|
|
1457
1492
|
},
|
|
@@ -1484,6 +1519,47 @@ async function readExecutionResults(request) {
|
|
|
1484
1519
|
return parsed.executions;
|
|
1485
1520
|
}
|
|
1486
1521
|
|
|
1522
|
+
/**
|
|
1523
|
+
* Serves symbol-level usage for the files a catalog sheet traces to.
|
|
1524
|
+
*
|
|
1525
|
+
* A batch, not one request per file: the scan is a whole-project pass, so
|
|
1526
|
+
* asking 23 times both wastes it and leaves a long tail of entries whose
|
|
1527
|
+
* answer has not arrived — which reads as "unused" exactly when it is least
|
|
1528
|
+
* true.
|
|
1529
|
+
*/
|
|
1530
|
+
export function sourceUsageMiddleware(root, { sourceDirectory = 'src' } = {}) {
|
|
1531
|
+
const projectRoot = path.resolve(root);
|
|
1532
|
+
return async (request, response, next) => {
|
|
1533
|
+
if (request.method !== 'GET') {
|
|
1534
|
+
next();
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
try {
|
|
1538
|
+
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
1539
|
+
const files = (url.searchParams.get('files') ?? '')
|
|
1540
|
+
.split(',')
|
|
1541
|
+
.map((file) => file.trim())
|
|
1542
|
+
.filter(Boolean);
|
|
1543
|
+
if (files.length === 0) {
|
|
1544
|
+
sendJson(response, 400, { error: 'Expected a files parameter.' });
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
if (files.length > MAX_SOURCE_USAGE_REQUEST) {
|
|
1548
|
+
sendJson(response, 413, {
|
|
1549
|
+
error: `At most ${MAX_SOURCE_USAGE_REQUEST} files can be requested at once.`,
|
|
1550
|
+
});
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
const usage = await findSourceUsage(projectRoot, files, { sourceDirectory });
|
|
1554
|
+
sendJson(response, 200, { usage });
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
sendJson(response, 400, {
|
|
1557
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1487
1563
|
function sendJson(response, statusCode, body) {
|
|
1488
1564
|
response.statusCode = statusCode;
|
|
1489
1565
|
response.setHeader('content-type', 'application/json; charset=utf-8');
|
|
@@ -1527,6 +1603,17 @@ export function pygmalionStoryboardPlugin(options) {
|
|
|
1527
1603
|
});
|
|
1528
1604
|
}
|
|
1529
1605
|
});
|
|
1606
|
+
// Component branch discovery — the editor's "discovered branches" list
|
|
1607
|
+
// and the host's dimension-declaration gate read the same manifest.
|
|
1608
|
+
server.middlewares.use(
|
|
1609
|
+
PYGMALION_COMPONENT_BRANCHES_CONTROL,
|
|
1610
|
+
componentBranchesMiddleware(root),
|
|
1611
|
+
);
|
|
1612
|
+
// Symbol-level usage — what the catalog storyboard means by "used by".
|
|
1613
|
+
server.middlewares.use(
|
|
1614
|
+
PYGMALION_SOURCE_USAGE_CONTROL,
|
|
1615
|
+
sourceUsageMiddleware(root, { sourceDirectory }),
|
|
1616
|
+
);
|
|
1530
1617
|
server.middlewares.use(
|
|
1531
1618
|
canonicalEndpoint,
|
|
1532
1619
|
async (request, response, next) => {
|
package/node/vite.mjs
CHANGED
|
@@ -208,6 +208,12 @@ export function createPygmalionVitePlugins(config) {
|
|
|
208
208
|
|
|
209
209
|
const project = resolvePygmalionProject(config);
|
|
210
210
|
const plugins = [
|
|
211
|
+
// The environment bootstrap must run in the editor host too, not only in
|
|
212
|
+
// the capture preview server: in-editor hidden instances (flow sessions,
|
|
213
|
+
// sweeps, live boots) carry declarative environments and the storage
|
|
214
|
+
// sandbox in their boot URL. Without the query parameter the bootstrap
|
|
215
|
+
// is a no-op, so ordinary app tabs are unaffected.
|
|
216
|
+
pygmalionStoryboardRuntimePlugin(),
|
|
211
217
|
pygmalionStoryboardPlugin({
|
|
212
218
|
root: project.appRoot,
|
|
213
219
|
sourceDirectory: project.sourceDirectory,
|
|
@@ -368,6 +374,11 @@ export function createPygmalionVitePlugins(config) {
|
|
|
368
374
|
return plugins;
|
|
369
375
|
}
|
|
370
376
|
|
|
377
|
+
export {
|
|
378
|
+
PYGMALION_COMPONENT_BRANCHES_CONTROL,
|
|
379
|
+
scanComponentBranches,
|
|
380
|
+
scanComponentBranchesFile,
|
|
381
|
+
} from './component-branches.mjs';
|
|
371
382
|
export {
|
|
372
383
|
PYGMALION_DEV_CONTROL,
|
|
373
384
|
PYGMALION_DEV_PREFIX,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pygmalionjs/pygmalion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Code-backed DOM design sandbox and visual QA editor",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"dist-lib",
|
|
35
|
+
"node/component-branches.mjs",
|
|
35
36
|
"node/design-session.mjs",
|
|
36
37
|
"node/dev-mirror.mjs",
|
|
37
38
|
"node/dev-view.vite.mjs",
|