@pygmalionjs/pygmalion 0.6.33 → 0.6.35
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/pygmalion.js +5865 -5779
- package/dist-lib/types/editor/captureSupply.d.ts +2 -0
- package/dist-lib/types/editor/projectBootGate.d.ts +28 -0
- package/dist-lib/types/editor/projectRuntime.d.ts +3 -0
- package/dist-lib/types/editor/routePreviewStatus.d.ts +2 -1
- package/node/dev-mirror.mjs +24 -11
- package/node/preview-artifact-plugin.mjs +25 -4
- package/node/preview-artifact-store.mjs +14 -2
- package/package.json +1 -1
|
@@ -27,6 +27,8 @@ export interface CaptureSupplyFrame {
|
|
|
27
27
|
recipe?: RoutePreviewFrameRecipe;
|
|
28
28
|
}
|
|
29
29
|
export interface CaptureSupplyPlan {
|
|
30
|
+
/** Canvas that owned the viewport when this plan was built. */
|
|
31
|
+
activeCanvas?: string;
|
|
30
32
|
/** Chunks of the active canvas, nearest first. */
|
|
31
33
|
active: CaptureSupplyFrame[][];
|
|
32
34
|
/** Chunks of the other canvases, in the order the frames were given. */
|
|
@@ -27,6 +27,34 @@ export declare function mirrorBootRetryDelayMs(attempt: number): number;
|
|
|
27
27
|
export interface MirrorGateStatus {
|
|
28
28
|
state: string;
|
|
29
29
|
}
|
|
30
|
+
/** The idle status fields that can identify an immutable source before checkout. */
|
|
31
|
+
export interface IdleMirrorSourceStatus extends MirrorGateStatus {
|
|
32
|
+
sourceRef: string | null;
|
|
33
|
+
}
|
|
34
|
+
/** One listed ref whose exact revision can start a lazy checkout. */
|
|
35
|
+
export interface IdleMirrorListedSourceRef {
|
|
36
|
+
name: string;
|
|
37
|
+
fullName?: string;
|
|
38
|
+
sourceRevision?: string;
|
|
39
|
+
}
|
|
40
|
+
/** The ref-list fields needed to resolve an idle named source. */
|
|
41
|
+
export interface IdleMirrorSourceRefs {
|
|
42
|
+
refs: readonly IdleMirrorListedSourceRef[];
|
|
43
|
+
defaultRef: string | null;
|
|
44
|
+
worktreeRef?: string | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns an immutable source revision that can safely create the first lazy
|
|
48
|
+
* preview demand.
|
|
49
|
+
*
|
|
50
|
+
* An idle mirror has no checked-out `commit` yet. When it was configured with
|
|
51
|
+
* a full Git object id, however, that ref already names the exact revision the
|
|
52
|
+
* catalog endpoint will verify after preparation. A named ref can also create
|
|
53
|
+
* that first demand when the ref-list endpoint supplies its full object id.
|
|
54
|
+
* The abbreviated display commit is intentionally ignored: it can be
|
|
55
|
+
* ambiguous and cannot satisfy the exact source lease.
|
|
56
|
+
*/
|
|
57
|
+
export declare function idleMirrorSourceRevision(status: IdleMirrorSourceStatus, sourceRefs?: IdleMirrorSourceRefs): string | null;
|
|
30
58
|
export interface SettleMirrorStatusOptions<S extends MirrorGateStatus> {
|
|
31
59
|
/** Status of the initial request; returned as-is when already settled. */
|
|
32
60
|
initial: S;
|
|
@@ -22,7 +22,10 @@ export interface PygmalionDevMirrorStatus {
|
|
|
22
22
|
/** One revision the mirror can be repointed at. */
|
|
23
23
|
export interface PygmalionSourceRef {
|
|
24
24
|
name: string;
|
|
25
|
+
/** Abbreviated commit used for compact branch-tip labels. */
|
|
25
26
|
commit: string;
|
|
27
|
+
/** Full immutable object id used for preview and artifact identity. */
|
|
28
|
+
sourceRevision?: string;
|
|
26
29
|
fullName: string;
|
|
27
30
|
kind: 'local' | 'remote';
|
|
28
31
|
}
|
|
@@ -5,6 +5,7 @@ export interface RoutePreviewArtifactStatusInput {
|
|
|
5
5
|
resolution: RoutePreviewArtifactResolution;
|
|
6
6
|
captureStatus?: RoutePreviewArtifactV3Status;
|
|
7
7
|
diagnostic?: string;
|
|
8
|
+
liveInteractionReady?: boolean;
|
|
8
9
|
}
|
|
9
10
|
export interface RoutePreviewArtifactStatusPresentation {
|
|
10
11
|
title: string;
|
|
@@ -45,4 +46,4 @@ export declare function resolveStaticRoutePreviewSurface({ hasScreenshot, hasDom
|
|
|
45
46
|
preferDom?: boolean;
|
|
46
47
|
}): StaticRoutePreviewSurface;
|
|
47
48
|
/** Turns artifact freshness and capture state into one unambiguous frame badge. */
|
|
48
|
-
export declare function resolveRoutePreviewArtifactStatus({ stale, resolution, captureStatus, diagnostic, }: RoutePreviewArtifactStatusInput): RoutePreviewArtifactStatusPresentation | null;
|
|
49
|
+
export declare function resolveRoutePreviewArtifactStatus({ stale, resolution, captureStatus, diagnostic, liveInteractionReady, }: RoutePreviewArtifactStatusInput): RoutePreviewArtifactStatusPresentation | null;
|
package/node/dev-mirror.mjs
CHANGED
|
@@ -350,15 +350,21 @@ function escapeRegExp(value) {
|
|
|
350
350
|
* of asking for a ref string.
|
|
351
351
|
*/
|
|
352
352
|
export async function listSourceRefs(repoRoot, remote = 'origin') {
|
|
353
|
-
const format =
|
|
353
|
+
const format =
|
|
354
|
+
'%(refname:short)%09%(objectname:short=8)%09%(objectname)%09%(refname)';
|
|
354
355
|
const read = async (...patterns) => {
|
|
355
356
|
const raw = await git(repoRoot, 'for-each-ref', `--format=${format}`, ...patterns);
|
|
356
357
|
if (!raw) return [];
|
|
357
358
|
return raw
|
|
358
359
|
.split('\n')
|
|
359
360
|
.map((line) => line.split('\t'))
|
|
360
|
-
.filter((parts) => parts.length ===
|
|
361
|
-
.map(([name, commit, fullName]) => ({
|
|
361
|
+
.filter((parts) => parts.length === 4 && parts[0])
|
|
362
|
+
.map(([name, commit, sourceRevision, fullName]) => ({
|
|
363
|
+
name,
|
|
364
|
+
commit,
|
|
365
|
+
sourceRevision,
|
|
366
|
+
fullName,
|
|
367
|
+
}));
|
|
362
368
|
};
|
|
363
369
|
const remotes = (await read(`refs/remotes/${remote}`))
|
|
364
370
|
// `%(refname:short)` may abbreviate refs/remotes/origin/HEAD to just
|
|
@@ -375,15 +381,19 @@ export async function listSourceRefs(repoRoot, remote = 'origin') {
|
|
|
375
381
|
// `dev@a0d5fdbf → 3e8cca5b`, an invitation to go backwards.
|
|
376
382
|
const remoteTipPrefix = `refs/remotes/${remote}/`;
|
|
377
383
|
const remoteTips = new Map(
|
|
378
|
-
remotes.map((item) => [item.fullName.slice(remoteTipPrefix.length), item
|
|
384
|
+
remotes.map((item) => [item.fullName.slice(remoteTipPrefix.length), item]),
|
|
379
385
|
);
|
|
380
|
-
const locals = (await read('refs/heads')).map((item) =>
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
386
|
+
const locals = (await read('refs/heads')).map((item) => {
|
|
387
|
+
const remoteTip = remoteTips.get(item.name);
|
|
388
|
+
return {
|
|
389
|
+
...item,
|
|
390
|
+
// A branch the remote does not have resolves locally (the fetch fails and
|
|
391
|
+
// becomes a warning), so its own tip is the honest answer there.
|
|
392
|
+
commit: remoteTip?.commit ?? item.commit,
|
|
393
|
+
sourceRevision: remoteTip?.sourceRevision ?? item.sourceRevision,
|
|
394
|
+
kind: 'local',
|
|
395
|
+
};
|
|
396
|
+
});
|
|
387
397
|
const seen = new Set();
|
|
388
398
|
return [...locals, ...remotes].filter((item) => {
|
|
389
399
|
if (seen.has(item.name)) return false;
|
|
@@ -843,6 +853,9 @@ export async function acquireExactDevMirrorSourceLease({
|
|
|
843
853
|
return {
|
|
844
854
|
sourceRoot: exactSourceRoot,
|
|
845
855
|
sourceRevision: expectedRevision,
|
|
856
|
+
// Synchronization and reaping honor the heartbeat lease until release,
|
|
857
|
+
// so retention may safely inspect this root while the caller holds it.
|
|
858
|
+
sourceRootStable: true,
|
|
846
859
|
release,
|
|
847
860
|
};
|
|
848
861
|
} catch (error) {
|
|
@@ -788,6 +788,12 @@ export function pygmalionPreviewArtifactPlugin({
|
|
|
788
788
|
return { sourceRevision, sourceRoot: lease.sourceRoot };
|
|
789
789
|
}
|
|
790
790
|
|
|
791
|
+
function retentionSourceContextFromLease(lease, sourceRevision) {
|
|
792
|
+
return lease?.sourceRootStable === true
|
|
793
|
+
? sourceContextFromLease(lease, sourceRevision)
|
|
794
|
+
: undefined;
|
|
795
|
+
}
|
|
796
|
+
|
|
791
797
|
/**
|
|
792
798
|
* Refines a conservative cache decision only while the requested checkout is
|
|
793
799
|
* pinned. Fingerprint hits, recipe mismatches, and frames without observed
|
|
@@ -872,6 +878,11 @@ export function pygmalionPreviewArtifactPlugin({
|
|
|
872
878
|
const progress = startCaptureProgress();
|
|
873
879
|
try {
|
|
874
880
|
const generated = await withCaptureLease(sourceRevision, async (lease) => {
|
|
881
|
+
const sourceContext = sourceContextFromLease(lease, sourceRevision);
|
|
882
|
+
const retentionSourceContext = retentionSourceContextFromLease(
|
|
883
|
+
lease,
|
|
884
|
+
sourceRevision,
|
|
885
|
+
);
|
|
875
886
|
const captured = await generateArtifact(
|
|
876
887
|
{
|
|
877
888
|
namespace,
|
|
@@ -889,10 +900,10 @@ export function pygmalionPreviewArtifactPlugin({
|
|
|
889
900
|
'Generated preview artifact identity does not match the request.',
|
|
890
901
|
);
|
|
891
902
|
}
|
|
892
|
-
const observedDependenciesByFrame =
|
|
903
|
+
const observedDependenciesByFrame = sourceContext
|
|
893
904
|
? await artifactStore.observeArtifactDependencies(
|
|
894
905
|
captured,
|
|
895
|
-
|
|
906
|
+
sourceContext.sourceRoot,
|
|
896
907
|
)
|
|
897
908
|
: undefined;
|
|
898
909
|
await validateSourceLease(lease, sourceRevision);
|
|
@@ -900,6 +911,9 @@ export function pygmalionPreviewArtifactPlugin({
|
|
|
900
911
|
await artifactStore.publishArtifact(captured, {
|
|
901
912
|
sourceRevision,
|
|
902
913
|
recordRevision: true,
|
|
914
|
+
...(retentionSourceContext
|
|
915
|
+
? { sourceContext: retentionSourceContext }
|
|
916
|
+
: {}),
|
|
903
917
|
...(observedDependenciesByFrame
|
|
904
918
|
? { observedDependenciesByFrame }
|
|
905
919
|
: {}),
|
|
@@ -941,6 +955,10 @@ export function pygmalionPreviewArtifactPlugin({
|
|
|
941
955
|
try {
|
|
942
956
|
return await withCaptureLease(sourceRevision, async (lease) => {
|
|
943
957
|
const sourceContext = sourceContextFromLease(lease, sourceRevision);
|
|
958
|
+
const retentionSourceContext = retentionSourceContextFromLease(
|
|
959
|
+
lease,
|
|
960
|
+
sourceRevision,
|
|
961
|
+
);
|
|
944
962
|
const before = await artifactStore.readFrameSelection(
|
|
945
963
|
namespace,
|
|
946
964
|
sourceRevision,
|
|
@@ -976,10 +994,10 @@ export function pygmalionPreviewArtifactPlugin({
|
|
|
976
994
|
'Generated preview artifact frames do not match the request.',
|
|
977
995
|
);
|
|
978
996
|
}
|
|
979
|
-
const observedDependenciesByFrame =
|
|
997
|
+
const observedDependenciesByFrame = sourceContext
|
|
980
998
|
? await artifactStore.observeArtifactDependencies(
|
|
981
999
|
captured,
|
|
982
|
-
|
|
1000
|
+
sourceContext.sourceRoot,
|
|
983
1001
|
)
|
|
984
1002
|
: undefined;
|
|
985
1003
|
await validateSourceLease(lease, sourceRevision);
|
|
@@ -988,6 +1006,9 @@ export function pygmalionPreviewArtifactPlugin({
|
|
|
988
1006
|
sourceRevision,
|
|
989
1007
|
recordRevision: false,
|
|
990
1008
|
frameRequests: remaining,
|
|
1009
|
+
...(retentionSourceContext
|
|
1010
|
+
? { sourceContext: retentionSourceContext }
|
|
1011
|
+
: {}),
|
|
991
1012
|
...(observedDependenciesByFrame
|
|
992
1013
|
? { observedDependenciesByFrame }
|
|
993
1014
|
: {}),
|
|
@@ -596,7 +596,10 @@ export function createPreviewArtifactStore({
|
|
|
596
596
|
* is what keeps a concurrent reader from losing a bundle it is about to
|
|
597
597
|
* fetch.
|
|
598
598
|
*/
|
|
599
|
-
const evictToBudget = async (
|
|
599
|
+
const evictToBudget = async (
|
|
600
|
+
manifest,
|
|
601
|
+
{ at, sourceContext, admittedFrameObjects = new Set() } = {},
|
|
602
|
+
) => {
|
|
600
603
|
const referenced = new Map();
|
|
601
604
|
const remember = async (hash) => {
|
|
602
605
|
if (!referenced.has(hash)) referenced.set(hash, await objectByteSize(hash));
|
|
@@ -627,7 +630,13 @@ export function createPreviewArtifactStore({
|
|
|
627
630
|
candidates.push({
|
|
628
631
|
kind: 'frame',
|
|
629
632
|
entry,
|
|
630
|
-
|
|
633
|
+
// A caller that supplied normalized observations recorded from the
|
|
634
|
+
// artifact's own snapshot has already paid to produce this object.
|
|
635
|
+
// Protect that admission from older unknown entries without rereading
|
|
636
|
+
// a source root that may not be physically pinned.
|
|
637
|
+
rank: admittedFrameObjects.has(entry.object)
|
|
638
|
+
? EVICTION_RANK.valid
|
|
639
|
+
: EVICTION_RANK[await classifyFrameEntry(entry, sourceContext)],
|
|
631
640
|
// Protection within a rank, never across it: the last copy of a frame
|
|
632
641
|
// is worth more than a surplus copy, but an invalid sole entry is still
|
|
633
642
|
// garbage and goes before a valid one.
|
|
@@ -872,6 +881,7 @@ export function createPreviewArtifactStore({
|
|
|
872
881
|
...(stored?.frames ?? []),
|
|
873
882
|
].map((entry) => entry.object),
|
|
874
883
|
);
|
|
884
|
+
const admittedFrameObjects = new Set();
|
|
875
885
|
if (recordRevision && sourceRevision) {
|
|
876
886
|
const wholeObject = await writeObject(artifact);
|
|
877
887
|
const nextRevision = {
|
|
@@ -922,6 +932,7 @@ export function createPreviewArtifactStore({
|
|
|
922
932
|
generation,
|
|
923
933
|
...(observed ? { sourceFiles: observed } : {}),
|
|
924
934
|
};
|
|
935
|
+
if (observed) admittedFrameObjects.add(object);
|
|
925
936
|
const alreadyRecorded = frames.some(
|
|
926
937
|
(entry) =>
|
|
927
938
|
entry.id === nextFrame.id &&
|
|
@@ -941,6 +952,7 @@ export function createPreviewArtifactStore({
|
|
|
941
952
|
const trimmed = await evictToBudget(manifest, {
|
|
942
953
|
at,
|
|
943
954
|
sourceContext: exactSourceContext,
|
|
955
|
+
admittedFrameObjects,
|
|
944
956
|
});
|
|
945
957
|
manifest = trimmed.dropped
|
|
946
958
|
? { ...trimmed.manifest, lastEviction: trimmed.dropped }
|