@pygmalionjs/pygmalion 0.2.38 → 0.2.39

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.
@@ -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
+ }
@@ -626,7 +626,7 @@ function withFrames(bundle, frames) {
626
626
  * requested one. Requests without a fingerprint accept whatever is stored, which
627
627
  * keeps the whole-bundle callers of earlier versions working.
628
628
  */
629
- export function selectRoutePreviewArtifactFrames(bundle, wanted) {
629
+ export function selectRoutePreviewArtifactFrames(bundle, wanted, options = {}) {
630
630
  if (!isPlainRecord(bundle) || !isPlainRecord(bundle.frames)) {
631
631
  throw new TypeError('Route preview artifact is invalid.');
632
632
  }
@@ -649,6 +649,7 @@ export function selectRoutePreviewArtifactFrames(bundle, wanted) {
649
649
  const fingerprint = isPlainRecord(request) ? request.fingerprint : undefined;
650
650
  if (fingerprint != null && frame.fingerprint !== fingerprint) {
651
651
  stale.push(id);
652
+ if (options.includeStale === true) frames[id] = frame;
652
653
  continue;
653
654
  }
654
655
  frames[id] = frame;
@@ -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.38",
3
+ "version": "0.2.39",
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 },
321
322
  ): {
322
323
  bundle: RoutePreviewArtifactBundleV3;
323
324
  missing: string[];
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,