@pygmalionjs/pygmalion 0.2.38 → 0.2.40
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/README.md +7 -0
- package/dist-lib/{App-DTeXtV9b.js → App-CiVUoaTo.js} +4619 -4449
- package/dist-lib/pygmalion.js +1105 -1039
- package/dist-lib/testing.js +1 -1
- package/node/design-session.mjs +6 -0
- package/node/dev-mirror.mjs +8 -2
- package/node/dev-view.vite.mjs +4 -0
- package/node/preview-artifact-plugin.mjs +0 -0
- package/node/preview-artifact-store.mjs +653 -0
- package/node/preview-vite-cache.mjs +26 -0
- package/node/route-dependency-digest.mjs +62 -0
- package/node/route-preview-artifact-v3.mjs +44 -16
- package/node/storyboard.mjs +2 -0
- package/node/vite.mjs +6 -0
- package/package.json +4 -1
- package/storyboard.d.ts +1 -0
- package/types.d.ts +4 -0
- package/vite.d.ts +12 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function normalizedRoutePath(value) {
|
|
2
|
+
if (typeof value !== 'string' || !value.trim()) return null;
|
|
3
|
+
try {
|
|
4
|
+
const pathname = new URL(value.trim(), 'http://pygmalion.invalid').pathname;
|
|
5
|
+
return `/${pathname.split('/').filter(Boolean).join('/')}`;
|
|
6
|
+
} catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function matches(pattern, route) {
|
|
12
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
13
|
+
const segment = pattern[index];
|
|
14
|
+
if (segment === '*' || (segment.startsWith(':') && segment.endsWith('*'))) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (route[index] == null) return false;
|
|
18
|
+
if (!segment.startsWith(':') && segment !== route[index]) return false;
|
|
19
|
+
}
|
|
20
|
+
return route.length === pattern.length;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Resolves a concrete route against exact, `:param`, and `*` manifest paths. */
|
|
24
|
+
export function resolvePreviewRouteDependencyDigest(route, entries) {
|
|
25
|
+
const normalized = normalizedRoutePath(route);
|
|
26
|
+
if (!normalized || !Array.isArray(entries)) return undefined;
|
|
27
|
+
const routeSegments = normalized.split('/').filter(Boolean);
|
|
28
|
+
const candidates = [];
|
|
29
|
+
for (const [order, entry] of entries.entries()) {
|
|
30
|
+
if (
|
|
31
|
+
typeof entry?.path !== 'string' ||
|
|
32
|
+
typeof entry?.dependencyDigest !== 'string' ||
|
|
33
|
+
!entry.dependencyDigest
|
|
34
|
+
) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const pattern = normalizedRoutePath(entry.path);
|
|
38
|
+
if (!pattern) continue;
|
|
39
|
+
if (pattern === normalized) return entry.dependencyDigest;
|
|
40
|
+
const segments = pattern.split('/').filter(Boolean);
|
|
41
|
+
if (!matches(segments, routeSegments)) continue;
|
|
42
|
+
candidates.push({
|
|
43
|
+
digest: entry.dependencyDigest,
|
|
44
|
+
literalCount: segments.filter(
|
|
45
|
+
(segment) => !segment.startsWith(':') && segment !== '*',
|
|
46
|
+
).length,
|
|
47
|
+
wildcardCount: segments.filter(
|
|
48
|
+
(segment) => segment === '*' || segment.endsWith('*'),
|
|
49
|
+
).length,
|
|
50
|
+
length: segments.length,
|
|
51
|
+
order,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
candidates.sort(
|
|
55
|
+
(left, right) =>
|
|
56
|
+
right.literalCount - left.literalCount ||
|
|
57
|
+
left.wildcardCount - right.wildcardCount ||
|
|
58
|
+
right.length - left.length ||
|
|
59
|
+
right.order - left.order,
|
|
60
|
+
);
|
|
61
|
+
return candidates[0]?.digest;
|
|
62
|
+
}
|
|
@@ -398,9 +398,9 @@ export function createRoutePreviewArtifactV3(input) {
|
|
|
398
398
|
{
|
|
399
399
|
status: rawCapture.status,
|
|
400
400
|
viewport: copyViewport(rawCapture.viewport),
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
401
|
+
// Content identity within the source revision that captured this frame.
|
|
402
|
+
// Store selections preserve the revision as per-frame provenance when
|
|
403
|
+
// frames from separate revisions share one response bundle.
|
|
404
404
|
...(rawCapture.fingerprint == null
|
|
405
405
|
? {}
|
|
406
406
|
: { fingerprint: String(rawCapture.fingerprint) }),
|
|
@@ -490,6 +490,10 @@ export function validateRoutePreviewArtifactV3(bundle) {
|
|
|
490
490
|
errors.push(`Frame "${frameId}" fingerprint is invalid.`);
|
|
491
491
|
continue;
|
|
492
492
|
}
|
|
493
|
+
if (!validOptionalIdentifier(rawFrame.sourceRevision)) {
|
|
494
|
+
errors.push(`Frame "${frameId}" source revision is invalid.`);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
493
497
|
const diagnostics = rawFrame.diagnostics;
|
|
494
498
|
if (
|
|
495
499
|
!Array.isArray(diagnostics) ||
|
|
@@ -622,11 +626,11 @@ function withFrames(bundle, frames) {
|
|
|
622
626
|
/**
|
|
623
627
|
* Picks the frames a consumer asked for and reports what it must still capture.
|
|
624
628
|
*
|
|
625
|
-
* A frame is usable when it exists and its stored fingerprint
|
|
626
|
-
* requested
|
|
627
|
-
*
|
|
629
|
+
* A frame is usable when it exists and its stored revision and fingerprint match
|
|
630
|
+
* the requested provenance. Callers that omit either constraint retain the
|
|
631
|
+
* looser compatibility behavior of earlier versions.
|
|
628
632
|
*/
|
|
629
|
-
export function selectRoutePreviewArtifactFrames(bundle, wanted) {
|
|
633
|
+
export function selectRoutePreviewArtifactFrames(bundle, wanted, options = {}) {
|
|
630
634
|
if (!isPlainRecord(bundle) || !isPlainRecord(bundle.frames)) {
|
|
631
635
|
throw new TypeError('Route preview artifact is invalid.');
|
|
632
636
|
}
|
|
@@ -647,8 +651,14 @@ export function selectRoutePreviewArtifactFrames(bundle, wanted) {
|
|
|
647
651
|
continue;
|
|
648
652
|
}
|
|
649
653
|
const fingerprint = isPlainRecord(request) ? request.fingerprint : undefined;
|
|
650
|
-
|
|
654
|
+
const frameSourceRevision = frame.sourceRevision ?? bundle.sourceRevision;
|
|
655
|
+
if (
|
|
656
|
+
(options.sourceRevision != null &&
|
|
657
|
+
frameSourceRevision !== options.sourceRevision) ||
|
|
658
|
+
(fingerprint != null && frame.fingerprint !== fingerprint)
|
|
659
|
+
) {
|
|
651
660
|
stale.push(id);
|
|
661
|
+
if (options.includeStale === true) frames[id] = frame;
|
|
652
662
|
continue;
|
|
653
663
|
}
|
|
654
664
|
frames[id] = frame;
|
|
@@ -673,7 +683,23 @@ export function mergeRoutePreviewArtifactV3(base, incoming) {
|
|
|
673
683
|
if (base.namespace !== incoming.namespace) {
|
|
674
684
|
throw new TypeError('Route preview artifacts belong to different namespaces.');
|
|
675
685
|
}
|
|
676
|
-
const
|
|
686
|
+
const mixedRevisions = base.sourceRevision !== incoming.sourceRevision;
|
|
687
|
+
const preserveRevision = (frames, sourceRevision) =>
|
|
688
|
+
Object.fromEntries(
|
|
689
|
+
Object.entries(frames).map(([id, frame]) => [
|
|
690
|
+
id,
|
|
691
|
+
mixedRevisions &&
|
|
692
|
+
isPlainRecord(frame) &&
|
|
693
|
+
frame.sourceRevision == null &&
|
|
694
|
+
sourceRevision != null
|
|
695
|
+
? { ...frame, sourceRevision }
|
|
696
|
+
: frame,
|
|
697
|
+
]),
|
|
698
|
+
);
|
|
699
|
+
const frames = {
|
|
700
|
+
...preserveRevision(base.frames, base.sourceRevision),
|
|
701
|
+
...preserveRevision(incoming.frames, incoming.sourceRevision),
|
|
702
|
+
};
|
|
677
703
|
if (Object.keys(frames).length > ROUTE_PREVIEW_ARTIFACT_V3_LIMITS.frameCount) {
|
|
678
704
|
throw new RangeError('Route preview artifact has too many frames.');
|
|
679
705
|
}
|
|
@@ -691,13 +717,15 @@ export function mergeRoutePreviewArtifactV3(base, incoming) {
|
|
|
691
717
|
return withFrames(
|
|
692
718
|
{
|
|
693
719
|
namespace: incoming.namespace,
|
|
694
|
-
//
|
|
695
|
-
//
|
|
696
|
-
...(
|
|
697
|
-
?
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
720
|
+
// A mixed bundle has no honest bundle-wide revision. Its frames carry
|
|
721
|
+
// their own provenance instead, including known revisions on both sides.
|
|
722
|
+
...(mixedRevisions
|
|
723
|
+
? {}
|
|
724
|
+
: incoming.sourceRevision == null
|
|
725
|
+
? base.sourceRevision == null
|
|
726
|
+
? {}
|
|
727
|
+
: { sourceRevision: base.sourceRevision }
|
|
728
|
+
: { sourceRevision: incoming.sourceRevision }),
|
|
701
729
|
assets,
|
|
702
730
|
},
|
|
703
731
|
frames,
|
package/node/storyboard.mjs
CHANGED
|
@@ -42,9 +42,11 @@ export {
|
|
|
42
42
|
export {
|
|
43
43
|
ROUTE_PREVIEW_ARTIFACT_V3_LIMITS,
|
|
44
44
|
createRoutePreviewArtifactV3,
|
|
45
|
+
mergeRoutePreviewArtifactV3,
|
|
45
46
|
reconstructRoutePreviewArtifactScreenshotV3,
|
|
46
47
|
reconstructRoutePreviewArtifactSnapshotV3,
|
|
47
48
|
reconstructRoutePreviewArtifactSnapshotsV3,
|
|
49
|
+
selectRoutePreviewArtifactFrames,
|
|
48
50
|
validateRoutePreviewArtifactBundle,
|
|
49
51
|
validateRoutePreviewArtifactV3,
|
|
50
52
|
} from './route-preview-artifact-v3.mjs';
|
package/node/vite.mjs
CHANGED
|
@@ -55,12 +55,15 @@ import {
|
|
|
55
55
|
import {
|
|
56
56
|
ROUTE_PREVIEW_ARTIFACT_V3_LIMITS,
|
|
57
57
|
createRoutePreviewArtifactV3,
|
|
58
|
+
mergeRoutePreviewArtifactV3,
|
|
58
59
|
reconstructRoutePreviewArtifactScreenshotV3,
|
|
59
60
|
reconstructRoutePreviewArtifactSnapshotV3,
|
|
60
61
|
reconstructRoutePreviewArtifactSnapshotsV3,
|
|
62
|
+
selectRoutePreviewArtifactFrames,
|
|
61
63
|
validateRoutePreviewArtifactBundle,
|
|
62
64
|
validateRoutePreviewArtifactV3,
|
|
63
65
|
} from './route-preview-artifact-v3.mjs';
|
|
66
|
+
import { resolvePreviewRouteDependencyDigest } from './route-dependency-digest.mjs';
|
|
64
67
|
import {
|
|
65
68
|
DEFAULT_PYGMALION_PREVIEW_ARTIFACT_FILE,
|
|
66
69
|
PYGMALION_PREVIEW_ARTIFACT_ENDPOINT,
|
|
@@ -366,9 +369,12 @@ export {
|
|
|
366
369
|
validateRoutePreviewArtifactV2,
|
|
367
370
|
ROUTE_PREVIEW_ARTIFACT_V3_LIMITS,
|
|
368
371
|
createRoutePreviewArtifactV3,
|
|
372
|
+
mergeRoutePreviewArtifactV3,
|
|
369
373
|
reconstructRoutePreviewArtifactScreenshotV3,
|
|
370
374
|
reconstructRoutePreviewArtifactSnapshotV3,
|
|
371
375
|
reconstructRoutePreviewArtifactSnapshotsV3,
|
|
376
|
+
selectRoutePreviewArtifactFrames,
|
|
377
|
+
resolvePreviewRouteDependencyDigest,
|
|
372
378
|
validateRoutePreviewArtifactBundle,
|
|
373
379
|
validateRoutePreviewArtifactV3,
|
|
374
380
|
DEFAULT_PYGMALION_PREVIEW_ARTIFACT_FILE,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pygmalionjs/pygmalion",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.40",
|
|
4
4
|
"description": "Code-backed DOM design sandbox and visual QA editor",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -37,9 +37,12 @@
|
|
|
37
37
|
"node/dev-view.vite.mjs",
|
|
38
38
|
"node/inspect-plugin.mjs",
|
|
39
39
|
"node/preview-artifact-plugin.mjs",
|
|
40
|
+
"node/preview-artifact-store.mjs",
|
|
41
|
+
"node/preview-vite-cache.mjs",
|
|
40
42
|
"node/qa-capture-plugin.mjs",
|
|
41
43
|
"node/route-preview-artifact-v2.mjs",
|
|
42
44
|
"node/route-preview-artifact-v3.mjs",
|
|
45
|
+
"node/route-dependency-digest.mjs",
|
|
43
46
|
"node/source-revision.mjs",
|
|
44
47
|
"node/source-diff.mjs",
|
|
45
48
|
"node/source-graph.mjs",
|
package/storyboard.d.ts
CHANGED
|
@@ -318,6 +318,7 @@ export interface RoutePreviewArtifactFrameRequest {
|
|
|
318
318
|
export declare function selectRoutePreviewArtifactFrames(
|
|
319
319
|
bundle: unknown,
|
|
320
320
|
wanted: readonly (RoutePreviewArtifactFrameRequest | string)[],
|
|
321
|
+
options?: { includeStale?: boolean; sourceRevision?: string },
|
|
321
322
|
): {
|
|
322
323
|
bundle: RoutePreviewArtifactBundleV3;
|
|
323
324
|
missing: string[];
|
package/types.d.ts
CHANGED
|
@@ -994,6 +994,8 @@ export interface RoutePreviewArtifactV3ScreenshotAsset {
|
|
|
994
994
|
export interface RoutePreviewArtifactV3Frame {
|
|
995
995
|
readonly status: RoutePreviewArtifactV3Status;
|
|
996
996
|
readonly viewport: RoutePreviewArtifactV3Viewport;
|
|
997
|
+
readonly sourceRevision?: string;
|
|
998
|
+
readonly fingerprint?: string;
|
|
997
999
|
readonly snapshot?: RoutePreviewArtifactBundleV2['frames'][string];
|
|
998
1000
|
readonly screenshot?: RoutePreviewArtifactV3ScreenshotReference;
|
|
999
1001
|
readonly diagnostics: readonly RoutePreviewArtifactV3Diagnostic[];
|
|
@@ -1945,6 +1947,8 @@ export declare function PygmalionEditor(props: {
|
|
|
1945
1947
|
registry?: ComponentRegistry;
|
|
1946
1948
|
tokens?: TokenDef[];
|
|
1947
1949
|
initialPages?: InitialPageDef[];
|
|
1950
|
+
/** Canvas shown on first mount. Useful when an earlier catalog canvas is expensive to render. */
|
|
1951
|
+
initialCanvas?: string;
|
|
1948
1952
|
/** Explicit external-design to registered-code component mappings. */
|
|
1949
1953
|
componentConnections?: readonly DesignComponentConnectionDef[];
|
|
1950
1954
|
/** Named frame widths the application supports — rendered as one-click buttons beside the W/H boxes. */
|
package/vite.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export interface PygmalionPreviewConfig {
|
|
|
76
76
|
generateArtifact?: (request: {
|
|
77
77
|
namespace: string;
|
|
78
78
|
sourceRevision: string;
|
|
79
|
+
frames?: readonly { id: string; fingerprint?: string }[];
|
|
79
80
|
captureBaseUrl?: string;
|
|
80
81
|
}) => unknown | Promise<unknown>;
|
|
81
82
|
}
|
|
@@ -158,8 +159,15 @@ export declare function pygmalionPreviewArtifactPlugin(options?: {
|
|
|
158
159
|
generateArtifact?: (request: {
|
|
159
160
|
namespace: string;
|
|
160
161
|
sourceRevision: string;
|
|
162
|
+
frames?: readonly { id: string; fingerprint?: string }[];
|
|
161
163
|
captureBaseUrl?: string;
|
|
162
164
|
}) => unknown | Promise<unknown>;
|
|
165
|
+
acquireLease?: (
|
|
166
|
+
reason: string,
|
|
167
|
+
) =>
|
|
168
|
+
| { release(): void | Promise<void> }
|
|
169
|
+
| null
|
|
170
|
+
| Promise<{ release(): void | Promise<void> } | null>;
|
|
163
171
|
}): PluginOption;
|
|
164
172
|
export interface PygmalionGitSourceState {
|
|
165
173
|
commit: string;
|
|
@@ -511,6 +519,10 @@ export {
|
|
|
511
519
|
validateRoutePreviewArtifactBundle,
|
|
512
520
|
validateRoutePreviewArtifactV3,
|
|
513
521
|
} from './storyboard';
|
|
522
|
+
export declare function resolvePreviewRouteDependencyDigest(
|
|
523
|
+
route: string,
|
|
524
|
+
entries: readonly { path?: string; dependencyDigest?: string }[],
|
|
525
|
+
): string | undefined;
|
|
514
526
|
export type {
|
|
515
527
|
RoutePreviewArtifactBundleV3,
|
|
516
528
|
RoutePreviewArtifactFrameRequest,
|