@pygmalionjs/pygmalion 0.6.34 → 0.7.1
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/LICENSE +190 -0
- package/README.ko.md +309 -28
- package/README.md +37 -16
- package/dist-lib/CameraLayer-D2wrEPwG.js +415 -0
- package/dist-lib/frameLodRuntime-CU0IYaSu.js +272 -0
- package/dist-lib/pygmalion.js +11670 -16016
- package/dist-lib/routePreviewStatus-DTr-UJhV.js +64 -0
- package/dist-lib/{FrozenRoutePreview-BbF5Ov5K.js → runtime-DRkTU9eq.js} +9515 -5306
- package/dist-lib/testing.js +46 -6
- package/dist-lib/types/{App.d.ts → EditorShell.d.ts} +1 -1
- package/dist-lib/types/canvas/CameraLayer.d.ts +2 -1
- package/dist-lib/types/canvas/FrameLabelBanner.d.ts +1 -1
- package/dist-lib/types/canvas/FrameView.d.ts +1 -1
- package/dist-lib/types/canvas/FrozenRoutePreview.d.ts +1 -1
- package/dist-lib/types/canvas/SectionBoxes.d.ts +2 -1
- package/dist-lib/types/canvas/ShadowRoutePreview.d.ts +1 -1
- package/dist-lib/types/contract/errors.d.ts +30 -0
- package/dist-lib/types/contract/index.d.ts +5 -0
- package/dist-lib/types/contract/memoryTransport.d.ts +19 -0
- package/dist-lib/types/contract/paths.d.ts +57 -0
- package/dist-lib/types/contract/transport.d.ts +69 -0
- package/dist-lib/types/contract/types.d.ts +150 -0
- package/dist-lib/types/contract/validate.d.ts +15 -0
- package/dist-lib/types/contract/viteTransport.d.ts +10 -0
- package/dist-lib/types/core/context.d.ts +12 -0
- package/dist-lib/types/core/debug.d.ts +187 -0
- package/dist-lib/types/core/defaultRuntime.d.ts +3 -0
- package/dist-lib/types/core/hostBindings.d.ts +90 -0
- package/dist-lib/types/core/runtime.d.ts +36 -0
- package/dist-lib/types/core/runtimeContext.d.ts +13 -0
- package/dist-lib/types/editor/flowSessionScheduler.d.ts +21 -7
- package/dist-lib/types/editor/framePreviewKeys.d.ts +10 -10
- package/dist-lib/types/editor/host.d.ts +31 -26
- package/dist-lib/types/editor/previewBootstrap.d.ts +5 -15
- package/dist-lib/types/editor/previewCaptureProgress.d.ts +7 -2
- package/dist-lib/types/editor/projectBootGate.d.ts +17 -4
- package/dist-lib/types/editor/projectRuntime.d.ts +11 -95
- package/dist-lib/types/editor/revisionCatalog.d.ts +8 -9
- package/dist-lib/types/editor/scenarioCoverage.d.ts +2 -1
- package/dist-lib/types/editor/store.d.ts +17 -2
- package/dist-lib/types/editor/storyboardComposition.d.ts +1 -5
- package/dist-lib/types/editor/storyboardDiscovery.d.ts +6 -16
- package/dist-lib/types/editor/storyboardEnvironment.d.ts +2 -2
- package/dist-lib/types/editor/variantPrefetch.d.ts +3 -2
- package/dist-lib/types/editor/visualQa.d.ts +7 -2
- package/dist-lib/types/lib.d.ts +25 -204
- package/dist-lib/types/shell/ComponentStateControls.d.ts +1 -1
- package/dist-lib/types/testing.d.ts +5 -1
- package/node/component-branches.mjs +3 -3
- package/node/contract.d.mts +63 -0
- package/node/contract.mjs +175 -0
- package/node/design-session.mjs +4 -2
- package/node/dev-mirror.mjs +27 -14
- package/node/inspect-plugin.mjs +3 -2
- package/node/preview-artifact-plugin.mjs +13 -16
- package/node/preview-artifact-store.mjs +4 -2
- package/node/qa-capture-plugin.mjs +3 -2
- package/node/storyboard-canonical.mjs +2 -2
- package/node/storyboard-capture-runtime.mjs +2 -2
- package/node/storyboard-environment.mjs +9 -6
- package/node/vite.mjs +2 -13
- package/package.json +24 -4
- package/storyboard.d.ts +11 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import { PYGMALION_ENDPOINTS } from './contract.mjs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
// Component branch scanner — reads a component source file and extracts the
|
|
@@ -8,8 +9,7 @@ import path from 'node:path';
|
|
|
8
9
|
// gate) and feeds the editor's "discovered branches" list, so awareness is
|
|
9
10
|
// automatic while application stays declared.
|
|
10
11
|
|
|
11
|
-
export const PYGMALION_COMPONENT_BRANCHES_CONTROL =
|
|
12
|
-
'/__pygmalion-storyboard/component-branches';
|
|
12
|
+
export const PYGMALION_COMPONENT_BRANCHES_CONTROL = PYGMALION_ENDPOINTS.componentBranches;
|
|
13
13
|
|
|
14
14
|
const MAX_BRANCHES = 80;
|
|
15
15
|
|
|
@@ -77,7 +77,7 @@ export async function scanComponentBranchesFile(root, sourcePath) {
|
|
|
77
77
|
export function componentBranchesMiddleware(root) {
|
|
78
78
|
return async (req, res, next) => {
|
|
79
79
|
if (req.method !== 'GET') return next();
|
|
80
|
-
let sourcePath
|
|
80
|
+
let sourcePath;
|
|
81
81
|
try {
|
|
82
82
|
const url = new URL(req.url ?? '', 'http://localhost');
|
|
83
83
|
sourcePath = url.searchParams.get('source') ?? '';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Declarations for node/contract.mjs. Types here are anonymous literal shapes on
|
|
2
|
+
// purpose: the browser re-exports these values, and an emitted declaration that
|
|
3
|
+
// referenced a named type from this file would carry a relative path into the
|
|
4
|
+
// published bundle's types. node/public-declarations.test.mjs checks that every
|
|
5
|
+
// value declared here is exported by the module.
|
|
6
|
+
|
|
7
|
+
export declare const PYGMALION_ENDPOINTS: {
|
|
8
|
+
readonly devPrefix: '/__pygmalion-dev';
|
|
9
|
+
readonly devControl: '/__pygmalion-dev-control';
|
|
10
|
+
readonly sessionPrefix: '/__pygmalion-session';
|
|
11
|
+
readonly sessionControl: '/__pygmalion-design-session';
|
|
12
|
+
readonly inspectControl: '/__pygmalion-inspect';
|
|
13
|
+
readonly qaControl: '/__pygmalion-qa';
|
|
14
|
+
readonly qaCapture: '/__pygmalion-qa/capture';
|
|
15
|
+
readonly qaArtifactPrefix: '/__pygmalion-qa/artifacts/';
|
|
16
|
+
readonly previewArtifact: '/__pygmalion-route-preview/artifact';
|
|
17
|
+
readonly storyboardEnvironment: '/__pygmalion-storyboard/environment';
|
|
18
|
+
readonly storyboardCanonical: '/__pygmalion-storyboard/canonicalize';
|
|
19
|
+
readonly sourceUsage: '/__pygmalion-storyboard/source-usage';
|
|
20
|
+
readonly componentBranches: '/__pygmalion-storyboard/component-branches';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export declare const PYGMALION_PREVIEW_CAPTURE_PROGRESS_SUFFIX: '/progress';
|
|
24
|
+
export declare const PYGMALION_REVISION_CATALOG_SUFFIX: '/catalog';
|
|
25
|
+
export declare const PYGMALION_STORYBOARD_ENVIRONMENT_QUERY: '__pygmalion_environment';
|
|
26
|
+
export declare const PYGMALION_SOURCE_USAGE_MAX_FILES: 100;
|
|
27
|
+
|
|
28
|
+
export declare const PREVIEW_CAPTURE_PRIORITY: {
|
|
29
|
+
readonly selected: 0;
|
|
30
|
+
readonly canvas: 10;
|
|
31
|
+
readonly background: 20;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export declare function normalizePygmalionEndpoint(
|
|
35
|
+
value: string | undefined,
|
|
36
|
+
fallback: string,
|
|
37
|
+
name: string,
|
|
38
|
+
): string;
|
|
39
|
+
|
|
40
|
+
export declare function resolvePygmalionEndpoints(overrides?: {
|
|
41
|
+
devControl?: string;
|
|
42
|
+
sessionControl?: string;
|
|
43
|
+
inspectControl?: string;
|
|
44
|
+
qaCapture?: string;
|
|
45
|
+
qaArtifactPrefix?: string;
|
|
46
|
+
previewArtifact?: string | true | null;
|
|
47
|
+
previewCatalog?: string | true | null;
|
|
48
|
+
storyboardEnvironment?: string;
|
|
49
|
+
sourceUsage?: string;
|
|
50
|
+
componentBranches?: string;
|
|
51
|
+
}): {
|
|
52
|
+
readonly devControl: string;
|
|
53
|
+
readonly sessionControl: string;
|
|
54
|
+
readonly inspectControl: string;
|
|
55
|
+
readonly qaCapture: string;
|
|
56
|
+
readonly qaArtifactPrefix: string;
|
|
57
|
+
readonly previewArtifact: string | null;
|
|
58
|
+
readonly previewCatalog: string | null;
|
|
59
|
+
readonly previewCaptureProgress: string | null;
|
|
60
|
+
readonly storyboardEnvironment: string;
|
|
61
|
+
readonly sourceUsage: string;
|
|
62
|
+
readonly componentBranches: string;
|
|
63
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// The wire contract the browser editor and the Node plugins share.
|
|
2
|
+
//
|
|
3
|
+
// Every endpoint path used to be a string literal written twice: once in the
|
|
4
|
+
// plugin that serves it and once in the browser module that calls it, with a
|
|
5
|
+
// comment saying "a test pins the pair". This module is the one place the
|
|
6
|
+
// paths live. The plugins re-export their old names from it, and the browser
|
|
7
|
+
// imports it directly (Vite bundles it; the declarations beside it type it).
|
|
8
|
+
//
|
|
9
|
+
// Plain JavaScript on purpose: `node/` is published as-is, and `src/` is not
|
|
10
|
+
// in the package, so a TypeScript source of truth here would need a build
|
|
11
|
+
// step before every test run. Keep this file free of imports and of anything
|
|
12
|
+
// that needs Node or a DOM.
|
|
13
|
+
|
|
14
|
+
const QA_CONTROL = '/__pygmalion-qa';
|
|
15
|
+
const STORYBOARD_CONTROL = '/__pygmalion-storyboard';
|
|
16
|
+
|
|
17
|
+
/** Default paths, served by the Vite plugins and called by the editor. */
|
|
18
|
+
export const PYGMALION_ENDPOINTS = Object.freeze({
|
|
19
|
+
/** Proxied checkout of the mirror; frames load under `${devPrefix}/<sha>-<rev>/…`. */
|
|
20
|
+
devPrefix: '/__pygmalion-dev',
|
|
21
|
+
/** Mirror status, refresh, and the list of refs. */
|
|
22
|
+
devControl: '/__pygmalion-dev-control',
|
|
23
|
+
/** Proxied checkout of a design session. */
|
|
24
|
+
sessionPrefix: '/__pygmalion-session',
|
|
25
|
+
/** Open, check, preview, apply, and revert a design session. */
|
|
26
|
+
sessionControl: '/__pygmalion-design-session',
|
|
27
|
+
/** Source impact and write-back. */
|
|
28
|
+
inspectControl: '/__pygmalion-inspect',
|
|
29
|
+
/** Base of the visual QA service. */
|
|
30
|
+
qaControl: QA_CONTROL,
|
|
31
|
+
/** Baseline and changed captures for visual QA. */
|
|
32
|
+
qaCapture: `${QA_CONTROL}/capture`,
|
|
33
|
+
/** Prefix of the PNG artifacts a QA result links to; ends with a slash. */
|
|
34
|
+
qaArtifactPrefix: `${QA_CONTROL}/artifacts/`,
|
|
35
|
+
/** Preview artifacts: probe with GET, capture with POST. */
|
|
36
|
+
previewArtifact: '/__pygmalion-route-preview/artifact',
|
|
37
|
+
/** The generated storyboard environment manifest. */
|
|
38
|
+
storyboardEnvironment: `${STORYBOARD_CONTROL}/environment`,
|
|
39
|
+
/** Canonicalized storyboard executions (tooling only). */
|
|
40
|
+
storyboardCanonical: `${STORYBOARD_CONTROL}/canonicalize`,
|
|
41
|
+
/** Which frames use which source files. */
|
|
42
|
+
sourceUsage: `${STORYBOARD_CONTROL}/source-usage`,
|
|
43
|
+
/** Component branches scanned from one source file. */
|
|
44
|
+
componentBranches: `${STORYBOARD_CONTROL}/component-branches`,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/** Appended to the preview artifact endpoint for capture progress. */
|
|
48
|
+
export const PYGMALION_PREVIEW_CAPTURE_PROGRESS_SUFFIX = '/progress';
|
|
49
|
+
/** Appended to the preview artifact endpoint for the exact revision catalog. */
|
|
50
|
+
export const PYGMALION_REVISION_CATALOG_SUFFIX = '/catalog';
|
|
51
|
+
/** Query parameter that carries a storyboard environment into a previewed route. */
|
|
52
|
+
export const PYGMALION_STORYBOARD_ENVIRONMENT_QUERY = '__pygmalion_environment';
|
|
53
|
+
/** Most files one source-usage request may name. */
|
|
54
|
+
export const PYGMALION_SOURCE_USAGE_MAX_FILES = 100;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Where a capture request sits among captures waiting on the same identity.
|
|
58
|
+
* Smaller runs first. The editor sends a frame the designer selected ahead of
|
|
59
|
+
* a canvas being filled, and a canvas being filled ahead of one nobody is
|
|
60
|
+
* looking at; a request that says nothing sits with the canvas.
|
|
61
|
+
*/
|
|
62
|
+
export const PREVIEW_CAPTURE_PRIORITY = Object.freeze({
|
|
63
|
+
/** A frame the designer selected: whatever else is queued waits. */
|
|
64
|
+
selected: 0,
|
|
65
|
+
/** The canvas in front of the designer being filled. */
|
|
66
|
+
canvas: 10,
|
|
67
|
+
/** A canvas nobody is looking at yet. */
|
|
68
|
+
background: 20,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* An endpoint override or its default: a path that starts with "/" and does
|
|
73
|
+
* not end with one. `name` names the option in the error.
|
|
74
|
+
*/
|
|
75
|
+
export function normalizePygmalionEndpoint(value, fallback, name) {
|
|
76
|
+
const endpoint = value ?? fallback;
|
|
77
|
+
if (
|
|
78
|
+
typeof endpoint !== 'string' ||
|
|
79
|
+
!endpoint.startsWith('/') ||
|
|
80
|
+
endpoint.endsWith('/')
|
|
81
|
+
) {
|
|
82
|
+
throw new Error(`Pygmalion ${name} must start with "/" and must not end with "/"`);
|
|
83
|
+
}
|
|
84
|
+
return endpoint;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizePrefix(value, fallback, name) {
|
|
88
|
+
const prefix = value ?? fallback;
|
|
89
|
+
if (typeof prefix !== 'string' || !prefix.startsWith('/') || !prefix.endsWith('/')) {
|
|
90
|
+
throw new Error(`Pygmalion ${name} must start and end with "/"`);
|
|
91
|
+
}
|
|
92
|
+
return prefix;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeOptional(value, fallback, name) {
|
|
96
|
+
if (value === undefined || value === null) return null;
|
|
97
|
+
if (value === true) return fallback;
|
|
98
|
+
return normalizePygmalionEndpoint(value, fallback, name);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The full set of paths one side talks on, from one set of overrides.
|
|
103
|
+
*
|
|
104
|
+
* Both sides call this, so a host that moves a path configures it once. The
|
|
105
|
+
* preview artifact and catalog services are opt-in: they stay `null` unless
|
|
106
|
+
* the host names a path or passes `true` for the default, which keeps "no
|
|
107
|
+
* artifact endpoint configured" meaning what it did: nothing calls it. The
|
|
108
|
+
* catalog and progress paths derive from the artifact path unless named.
|
|
109
|
+
*/
|
|
110
|
+
export function resolvePygmalionEndpoints(overrides = {}) {
|
|
111
|
+
const previewArtifact = normalizeOptional(
|
|
112
|
+
overrides.previewArtifact,
|
|
113
|
+
PYGMALION_ENDPOINTS.previewArtifact,
|
|
114
|
+
'endpoints.previewArtifact',
|
|
115
|
+
);
|
|
116
|
+
const derivedCatalog =
|
|
117
|
+
previewArtifact == null ? null : `${previewArtifact}${PYGMALION_REVISION_CATALOG_SUFFIX}`;
|
|
118
|
+
const previewCatalog =
|
|
119
|
+
overrides.previewCatalog === undefined
|
|
120
|
+
? derivedCatalog
|
|
121
|
+
: normalizeOptional(
|
|
122
|
+
overrides.previewCatalog,
|
|
123
|
+
derivedCatalog ??
|
|
124
|
+
`${PYGMALION_ENDPOINTS.previewArtifact}${PYGMALION_REVISION_CATALOG_SUFFIX}`,
|
|
125
|
+
'endpoints.previewCatalog',
|
|
126
|
+
);
|
|
127
|
+
return Object.freeze({
|
|
128
|
+
devControl: normalizePygmalionEndpoint(
|
|
129
|
+
overrides.devControl,
|
|
130
|
+
PYGMALION_ENDPOINTS.devControl,
|
|
131
|
+
'endpoints.devControl',
|
|
132
|
+
),
|
|
133
|
+
sessionControl: normalizePygmalionEndpoint(
|
|
134
|
+
overrides.sessionControl,
|
|
135
|
+
PYGMALION_ENDPOINTS.sessionControl,
|
|
136
|
+
'endpoints.sessionControl',
|
|
137
|
+
),
|
|
138
|
+
inspectControl: normalizePygmalionEndpoint(
|
|
139
|
+
overrides.inspectControl,
|
|
140
|
+
PYGMALION_ENDPOINTS.inspectControl,
|
|
141
|
+
'endpoints.inspectControl',
|
|
142
|
+
),
|
|
143
|
+
qaCapture: normalizePygmalionEndpoint(
|
|
144
|
+
overrides.qaCapture,
|
|
145
|
+
PYGMALION_ENDPOINTS.qaCapture,
|
|
146
|
+
'endpoints.qaCapture',
|
|
147
|
+
),
|
|
148
|
+
qaArtifactPrefix: normalizePrefix(
|
|
149
|
+
overrides.qaArtifactPrefix,
|
|
150
|
+
PYGMALION_ENDPOINTS.qaArtifactPrefix,
|
|
151
|
+
'endpoints.qaArtifactPrefix',
|
|
152
|
+
),
|
|
153
|
+
previewArtifact,
|
|
154
|
+
previewCatalog,
|
|
155
|
+
previewCaptureProgress:
|
|
156
|
+
previewArtifact == null
|
|
157
|
+
? null
|
|
158
|
+
: `${previewArtifact}${PYGMALION_PREVIEW_CAPTURE_PROGRESS_SUFFIX}`,
|
|
159
|
+
storyboardEnvironment: normalizePygmalionEndpoint(
|
|
160
|
+
overrides.storyboardEnvironment,
|
|
161
|
+
PYGMALION_ENDPOINTS.storyboardEnvironment,
|
|
162
|
+
'endpoints.storyboardEnvironment',
|
|
163
|
+
),
|
|
164
|
+
sourceUsage: normalizePygmalionEndpoint(
|
|
165
|
+
overrides.sourceUsage,
|
|
166
|
+
PYGMALION_ENDPOINTS.sourceUsage,
|
|
167
|
+
'endpoints.sourceUsage',
|
|
168
|
+
),
|
|
169
|
+
componentBranches: normalizePygmalionEndpoint(
|
|
170
|
+
overrides.componentBranches,
|
|
171
|
+
PYGMALION_ENDPOINTS.componentBranches,
|
|
172
|
+
'endpoints.componentBranches',
|
|
173
|
+
),
|
|
174
|
+
});
|
|
175
|
+
}
|
package/node/design-session.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { PYGMALION_ENDPOINTS } from './contract.mjs';
|
|
2
3
|
import { execFile } from 'node:child_process';
|
|
3
4
|
import fsp from 'node:fs/promises';
|
|
4
5
|
import http from 'node:http';
|
|
@@ -15,8 +16,8 @@ import { resolvePygmalionPreviewViteCacheDir } from './preview-vite-cache.mjs';
|
|
|
15
16
|
|
|
16
17
|
const execFileAsync = promisify(execFile);
|
|
17
18
|
|
|
18
|
-
export const PYGMALION_SESSION_PREFIX =
|
|
19
|
-
export const PYGMALION_SESSION_CONTROL =
|
|
19
|
+
export const PYGMALION_SESSION_PREFIX = PYGMALION_ENDPOINTS.sessionPrefix;
|
|
20
|
+
export const PYGMALION_SESSION_CONTROL = PYGMALION_ENDPOINTS.sessionControl;
|
|
20
21
|
|
|
21
22
|
const DEFAULT_PREVIEW_PORT = 5220;
|
|
22
23
|
const READY_TIMEOUT_MS = 60_000;
|
|
@@ -1009,6 +1010,7 @@ export function pygmalionDesignSessionPlugin(options) {
|
|
|
1009
1010
|
`${error instanceof Error ? error.message : String(error)}; ${
|
|
1010
1011
|
restoreError instanceof Error ? restoreError.message : String(restoreError)
|
|
1011
1012
|
}`,
|
|
1013
|
+
{ cause: restoreError },
|
|
1012
1014
|
);
|
|
1013
1015
|
}
|
|
1014
1016
|
}
|
package/node/dev-mirror.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { PYGMALION_ENDPOINTS } from './contract.mjs';
|
|
2
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
4
|
import fsp from 'node:fs/promises';
|
|
4
5
|
import http from 'node:http';
|
|
@@ -11,8 +12,8 @@ import { resolvePygmalionPreviewViteCacheDir } from './preview-vite-cache.mjs';
|
|
|
11
12
|
|
|
12
13
|
const execFileAsync = promisify(execFile);
|
|
13
14
|
|
|
14
|
-
export const PYGMALION_DEV_PREFIX =
|
|
15
|
-
export const PYGMALION_DEV_CONTROL =
|
|
15
|
+
export const PYGMALION_DEV_PREFIX = PYGMALION_ENDPOINTS.devPrefix;
|
|
16
|
+
export const PYGMALION_DEV_CONTROL = PYGMALION_ENDPOINTS.devControl;
|
|
16
17
|
|
|
17
18
|
const DEFAULT_PREVIEW_PORT = 5197;
|
|
18
19
|
const READY_TIMEOUT_MS = 60_000;
|
|
@@ -195,7 +196,7 @@ function isPlainMirrorStamp(value) {
|
|
|
195
196
|
* regenerated their own inventory into the same directory.
|
|
196
197
|
*/
|
|
197
198
|
export async function devMirrorClaim(repoRoot, mirrorRoot, owner) {
|
|
198
|
-
let stamp
|
|
199
|
+
let stamp;
|
|
199
200
|
try {
|
|
200
201
|
const raw = await fsp.readFile(await mirrorClaimFile(repoRoot, mirrorRoot), 'utf8');
|
|
201
202
|
const parsed = JSON.parse(raw);
|
|
@@ -350,15 +351,21 @@ function escapeRegExp(value) {
|
|
|
350
351
|
* of asking for a ref string.
|
|
351
352
|
*/
|
|
352
353
|
export async function listSourceRefs(repoRoot, remote = 'origin') {
|
|
353
|
-
const format =
|
|
354
|
+
const format =
|
|
355
|
+
'%(refname:short)%09%(objectname:short=8)%09%(objectname)%09%(refname)';
|
|
354
356
|
const read = async (...patterns) => {
|
|
355
357
|
const raw = await git(repoRoot, 'for-each-ref', `--format=${format}`, ...patterns);
|
|
356
358
|
if (!raw) return [];
|
|
357
359
|
return raw
|
|
358
360
|
.split('\n')
|
|
359
361
|
.map((line) => line.split('\t'))
|
|
360
|
-
.filter((parts) => parts.length ===
|
|
361
|
-
.map(([name, commit, fullName]) => ({
|
|
362
|
+
.filter((parts) => parts.length === 4 && parts[0])
|
|
363
|
+
.map(([name, commit, sourceRevision, fullName]) => ({
|
|
364
|
+
name,
|
|
365
|
+
commit,
|
|
366
|
+
sourceRevision,
|
|
367
|
+
fullName,
|
|
368
|
+
}));
|
|
362
369
|
};
|
|
363
370
|
const remotes = (await read(`refs/remotes/${remote}`))
|
|
364
371
|
// `%(refname:short)` may abbreviate refs/remotes/origin/HEAD to just
|
|
@@ -375,15 +382,19 @@ export async function listSourceRefs(repoRoot, remote = 'origin') {
|
|
|
375
382
|
// `dev@a0d5fdbf → 3e8cca5b`, an invitation to go backwards.
|
|
376
383
|
const remoteTipPrefix = `refs/remotes/${remote}/`;
|
|
377
384
|
const remoteTips = new Map(
|
|
378
|
-
remotes.map((item) => [item.fullName.slice(remoteTipPrefix.length), item
|
|
385
|
+
remotes.map((item) => [item.fullName.slice(remoteTipPrefix.length), item]),
|
|
379
386
|
);
|
|
380
|
-
const locals = (await read('refs/heads')).map((item) =>
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
+
const locals = (await read('refs/heads')).map((item) => {
|
|
388
|
+
const remoteTip = remoteTips.get(item.name);
|
|
389
|
+
return {
|
|
390
|
+
...item,
|
|
391
|
+
// A branch the remote does not have resolves locally (the fetch fails and
|
|
392
|
+
// becomes a warning), so its own tip is the honest answer there.
|
|
393
|
+
commit: remoteTip?.commit ?? item.commit,
|
|
394
|
+
sourceRevision: remoteTip?.sourceRevision ?? item.sourceRevision,
|
|
395
|
+
kind: 'local',
|
|
396
|
+
};
|
|
397
|
+
});
|
|
387
398
|
const seen = new Set();
|
|
388
399
|
return [...locals, ...remotes].filter((item) => {
|
|
389
400
|
if (seen.has(item.name)) return false;
|
|
@@ -536,6 +547,7 @@ async function acquireDevMirrorReclaimMutex(lockPath, deadline) {
|
|
|
536
547
|
if (Date.now() >= deadline) {
|
|
537
548
|
throw new Error(
|
|
538
549
|
'dev screen refresh on another editor server is not finished',
|
|
550
|
+
{ cause: error },
|
|
539
551
|
);
|
|
540
552
|
}
|
|
541
553
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
@@ -634,6 +646,7 @@ export async function withDevMirrorSyncLock(
|
|
|
634
646
|
if (Date.now() >= deadline) {
|
|
635
647
|
throw new Error(
|
|
636
648
|
'dev screen refresh on another editor server is not finished',
|
|
649
|
+
{ cause: error },
|
|
637
650
|
);
|
|
638
651
|
}
|
|
639
652
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
package/node/inspect-plugin.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import { PYGMALION_ENDPOINTS } from './contract.mjs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import MagicString from 'magic-string';
|
|
4
5
|
import ts from 'typescript';
|
|
@@ -24,7 +25,7 @@ export { writeSourceOperations } from './source-operations.mjs';
|
|
|
24
25
|
const CSS_MODULE_IMPORT_RE =
|
|
25
26
|
/import\s+([A-Za-z_$][\w$]*)\s+from\s+['"]([^'"]+\.module\.scss)['"]\s*;?/g;
|
|
26
27
|
|
|
27
|
-
export const PYGMALION_INSPECT_CONTROL =
|
|
28
|
+
export const PYGMALION_INSPECT_CONTROL = PYGMALION_ENDPOINTS.inspectControl;
|
|
28
29
|
|
|
29
30
|
function jsxScriptKind(componentFile) {
|
|
30
31
|
if (/\.tsx$/.test(componentFile)) return ts.ScriptKind.TSX;
|
|
@@ -59,7 +60,7 @@ const HOVER_EVENT_ATTRIBUTES = new Set([
|
|
|
59
60
|
|
|
60
61
|
const FOCUS_EVENT_ATTRIBUTES = new Set(['onFocus']);
|
|
61
62
|
|
|
62
|
-
function primitiveJsxAttributeValue(attribute,
|
|
63
|
+
function primitiveJsxAttributeValue(attribute, _sourceFile) {
|
|
63
64
|
if (!attribute.initializer) return true;
|
|
64
65
|
if (ts.isStringLiteral(attribute.initializer)) return attribute.initializer.text;
|
|
65
66
|
if (!ts.isJsxExpression(attribute.initializer) || !attribute.initializer.expression) {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
PREVIEW_CAPTURE_PRIORITY,
|
|
4
|
+
PYGMALION_ENDPOINTS,
|
|
5
|
+
PYGMALION_PREVIEW_CAPTURE_PROGRESS_SUFFIX,
|
|
6
|
+
PYGMALION_REVISION_CATALOG_SUFFIX,
|
|
7
|
+
} from './contract.mjs';
|
|
2
8
|
import fs from 'node:fs/promises';
|
|
3
9
|
import {
|
|
4
10
|
mergeRoutePreviewArtifactV3,
|
|
@@ -7,10 +13,12 @@ import {
|
|
|
7
13
|
} from './route-preview-artifact-v3.mjs';
|
|
8
14
|
import { createPreviewArtifactStore } from './preview-artifact-store.mjs';
|
|
9
15
|
|
|
10
|
-
export const PYGMALION_PREVIEW_ARTIFACT_ENDPOINT =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
export const PYGMALION_PREVIEW_ARTIFACT_ENDPOINT = PYGMALION_ENDPOINTS.previewArtifact;
|
|
17
|
+
export {
|
|
18
|
+
PYGMALION_PREVIEW_CAPTURE_PROGRESS_SUFFIX,
|
|
19
|
+
PYGMALION_REVISION_CATALOG_SUFFIX,
|
|
20
|
+
PREVIEW_CAPTURE_PRIORITY,
|
|
21
|
+
} from './contract.mjs';
|
|
14
22
|
export const DEFAULT_PYGMALION_PREVIEW_ARTIFACT_FILE =
|
|
15
23
|
'artifacts/pygmalion-route-previews.json';
|
|
16
24
|
|
|
@@ -24,17 +32,6 @@ const MAX_FRAME_REQUEST_COUNT = 2_048;
|
|
|
24
32
|
// client from parking megabytes in memory.
|
|
25
33
|
const MAX_FRAME_REQUEST_BODY_BYTES = 4 * 1024 * 1024;
|
|
26
34
|
const MAX_REVISION_CATALOG_BYTES = 16 * 1024 * 1024;
|
|
27
|
-
/**
|
|
28
|
-
* Where a request sits among captures waiting on the same identity. Smaller
|
|
29
|
-
* runs first. The editor sends a frame the designer selected ahead of a
|
|
30
|
-
* canvas being filled, and a canvas being filled ahead of one nobody is
|
|
31
|
-
* looking at; a request that says nothing sits with the canvas.
|
|
32
|
-
*/
|
|
33
|
-
export const PREVIEW_CAPTURE_PRIORITY = Object.freeze({
|
|
34
|
-
selected: 0,
|
|
35
|
-
canvas: 10,
|
|
36
|
-
background: 20,
|
|
37
|
-
});
|
|
38
35
|
const DEFAULT_CAPTURE_PRIORITY = PREVIEW_CAPTURE_PRIORITY.canvas;
|
|
39
36
|
const MAX_CAPTURE_PRIORITY = 1_000;
|
|
40
37
|
const CAPTURE_PROGRESS_PHASES = new Set([
|
|
@@ -762,7 +759,7 @@ export function pygmalionPreviewArtifactPlugin({
|
|
|
762
759
|
|
|
763
760
|
async function validateSourceLease(lease, sourceRevision) {
|
|
764
761
|
if (typeof lease?.validate !== 'function') return;
|
|
765
|
-
let current
|
|
762
|
+
let current;
|
|
766
763
|
try {
|
|
767
764
|
current = (await lease.validate()) === true;
|
|
768
765
|
} catch {
|
|
@@ -167,7 +167,9 @@ async function acquireFilesystemReclaimMutex(lockPath, deadline) {
|
|
|
167
167
|
// Recovery of this guard would recreate the same ABA race one level down.
|
|
168
168
|
// A dead guard therefore fails closed until it is removed manually.
|
|
169
169
|
if (Date.now() >= deadline) {
|
|
170
|
-
throw new Error('Timed out waiting for the preview artifact store lock.'
|
|
170
|
+
throw new Error('Timed out waiting for the preview artifact store lock.', {
|
|
171
|
+
cause: error,
|
|
172
|
+
});
|
|
171
173
|
}
|
|
172
174
|
await new Promise((resolve) => setTimeout(resolve, LOCK_POLL_MS));
|
|
173
175
|
} finally {
|
|
@@ -1371,7 +1373,7 @@ export function createPreviewArtifactStore({
|
|
|
1371
1373
|
// seen a request yet and so cannot name one.
|
|
1372
1374
|
if (namespace === undefined) {
|
|
1373
1375
|
const dropped = [];
|
|
1374
|
-
let names
|
|
1376
|
+
let names;
|
|
1375
1377
|
try {
|
|
1376
1378
|
names = await fs.readdir(path.join(root, 'namespaces'));
|
|
1377
1379
|
} catch {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { PYGMALION_ENDPOINTS } from './contract.mjs';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs/promises';
|
|
4
5
|
import os from 'node:os';
|
|
@@ -6,8 +7,8 @@ import path from 'node:path';
|
|
|
6
7
|
import pixelmatch from 'pixelmatch';
|
|
7
8
|
import pngjs from 'pngjs';
|
|
8
9
|
|
|
9
|
-
export const PYGMALION_QA_CAPTURE_ENDPOINT =
|
|
10
|
-
export const PYGMALION_QA_ARTIFACT_PREFIX =
|
|
10
|
+
export const PYGMALION_QA_CAPTURE_ENDPOINT = PYGMALION_ENDPOINTS.qaCapture;
|
|
11
|
+
export const PYGMALION_QA_ARTIFACT_PREFIX = PYGMALION_ENDPOINTS.qaArtifactPrefix;
|
|
11
12
|
export const DEFAULT_QA_CAPTURE_MAX_FRAMES = 128;
|
|
12
13
|
|
|
13
14
|
const DEFAULT_TIMEOUT_MS = 180_000;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { PYGMALION_ENDPOINTS } from './contract.mjs';
|
|
2
3
|
import { availableParallelism, totalmem } from 'node:os';
|
|
3
4
|
|
|
4
|
-
export const PYGMALION_STORYBOARD_CANONICAL_CONTROL =
|
|
5
|
-
'/__pygmalion-storyboard/canonicalize';
|
|
5
|
+
export const PYGMALION_STORYBOARD_CANONICAL_CONTROL = PYGMALION_ENDPOINTS.storyboardCanonical;
|
|
6
6
|
|
|
7
7
|
const MAX_SNAPSHOT_BYTES = 12 * 1024 * 1024;
|
|
8
8
|
const AUTO_ID_PATTERN =
|
|
@@ -487,6 +487,7 @@ export async function runStoryboardInteraction(page, interaction) {
|
|
|
487
487
|
} catch (error) {
|
|
488
488
|
throw new Error(
|
|
489
489
|
`"${interaction.label}" interaction failed: ${messageOf(error)}`,
|
|
490
|
+
{ cause: error },
|
|
490
491
|
);
|
|
491
492
|
}
|
|
492
493
|
await page.waitForTimeout(settleMs);
|
|
@@ -557,7 +558,7 @@ async function assertionFailure(page, assertion) {
|
|
|
557
558
|
export async function runStoryboardAssertion(page, assertion) {
|
|
558
559
|
const timeoutMs = assertion.timeoutMs ?? 2_000;
|
|
559
560
|
const startedAt = Date.now();
|
|
560
|
-
let failure
|
|
561
|
+
let failure;
|
|
561
562
|
do {
|
|
562
563
|
failure = await assertionFailure(page, assertion);
|
|
563
564
|
if (!failure || Date.now() - startedAt >= timeoutMs) break;
|
|
@@ -1583,7 +1584,6 @@ export async function captureStoryboardCase({
|
|
|
1583
1584
|
if (!warm) {
|
|
1584
1585
|
context = await atCaptureStage('setup', () =>
|
|
1585
1586
|
browser.newContext({
|
|
1586
|
-
viewport,
|
|
1587
1587
|
deviceScaleFactor: resolveScreenshotScale(contextOptions),
|
|
1588
1588
|
colorScheme: 'light',
|
|
1589
1589
|
reducedMotion: 'reduce',
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
PYGMALION_ENDPOINTS,
|
|
4
|
+
PYGMALION_SOURCE_USAGE_MAX_FILES,
|
|
5
|
+
PYGMALION_STORYBOARD_ENVIRONMENT_QUERY,
|
|
6
|
+
} from './contract.mjs';
|
|
2
7
|
import fs from 'node:fs/promises';
|
|
3
8
|
import path from 'node:path';
|
|
4
9
|
import ts from 'typescript';
|
|
@@ -13,12 +18,10 @@ import {
|
|
|
13
18
|
} from './storyboard-canonical.mjs';
|
|
14
19
|
|
|
15
20
|
export const PYGMALION_STORYBOARD_ENVIRONMENT_CONTROL =
|
|
16
|
-
|
|
17
|
-
export const PYGMALION_SOURCE_USAGE_CONTROL =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export const PYGMALION_STORYBOARD_ENVIRONMENT_QUERY =
|
|
21
|
-
'__pygmalion_environment';
|
|
21
|
+
PYGMALION_ENDPOINTS.storyboardEnvironment;
|
|
22
|
+
export const PYGMALION_SOURCE_USAGE_CONTROL = PYGMALION_ENDPOINTS.sourceUsage;
|
|
23
|
+
const MAX_SOURCE_USAGE_REQUEST = PYGMALION_SOURCE_USAGE_MAX_FILES;
|
|
24
|
+
export { PYGMALION_STORYBOARD_ENVIRONMENT_QUERY };
|
|
22
25
|
|
|
23
26
|
const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx']);
|
|
24
27
|
const IGNORED_DIRECTORIES = new Set([
|
package/node/vite.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { normalizePygmalionEndpoint } from './contract.mjs';
|
|
2
3
|
import {
|
|
3
4
|
PYGMALION_DEV_CONTROL,
|
|
4
5
|
PYGMALION_DEV_PREFIX,
|
|
@@ -110,19 +111,7 @@ function optionalNonEmptyString(value, name) {
|
|
|
110
111
|
return value.trim();
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
const endpoint = value ?? fallback;
|
|
115
|
-
if (
|
|
116
|
-
typeof endpoint !== 'string' ||
|
|
117
|
-
!endpoint.startsWith('/') ||
|
|
118
|
-
endpoint.endsWith('/')
|
|
119
|
-
) {
|
|
120
|
-
throw new Error(
|
|
121
|
-
`Pygmalion ${name} must start with "/" and must not end with "/"`,
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
return endpoint;
|
|
125
|
-
}
|
|
114
|
+
const normalizeEndpoint = normalizePygmalionEndpoint;
|
|
126
115
|
|
|
127
116
|
export function definePygmalionProject(config) {
|
|
128
117
|
if (!config || typeof config !== 'object') {
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pygmalionjs/pygmalion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Code-backed DOM design sandbox and visual QA editor",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jud1thDev/pygmalion.git"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22.18"
|
|
12
|
+
},
|
|
6
13
|
"publishConfig": {
|
|
7
14
|
"registry": "https://registry.npmjs.org",
|
|
8
15
|
"access": "public"
|
|
@@ -33,6 +40,10 @@
|
|
|
33
40
|
"types": "./runtime.d.ts",
|
|
34
41
|
"default": "./node/runtime.mjs"
|
|
35
42
|
},
|
|
43
|
+
"./contract": {
|
|
44
|
+
"types": "./node/contract.d.mts",
|
|
45
|
+
"default": "./node/contract.mjs"
|
|
46
|
+
},
|
|
36
47
|
"./testing": {
|
|
37
48
|
"types": "./dist-lib/types/testing.d.ts",
|
|
38
49
|
"default": "./dist-lib/testing.js"
|
|
@@ -42,6 +53,8 @@
|
|
|
42
53
|
"files": [
|
|
43
54
|
"dist-lib",
|
|
44
55
|
"docs/screen-state-contract.md",
|
|
56
|
+
"node/contract.mjs",
|
|
57
|
+
"node/contract.d.mts",
|
|
45
58
|
"node/component-branches.mjs",
|
|
46
59
|
"node/automatic-motion-runtime.mjs",
|
|
47
60
|
"node/automatic-pseudo-runtime.mjs",
|
|
@@ -83,11 +96,13 @@
|
|
|
83
96
|
"build": "tsc && vite build",
|
|
84
97
|
"build:lib": "vite build --config vite.lib.config.ts && npm run build:types",
|
|
85
98
|
"test": "node --test node/*.test.mjs src/*.test.mjs src/**/*.test.mjs",
|
|
99
|
+
"lint": "eslint .",
|
|
86
100
|
"lint:language": "node scripts/lint-language.mjs",
|
|
87
101
|
"typecheck": "tsc --noEmit",
|
|
88
|
-
"prepublishOnly": "npm run lint:language && npm test && npm run typecheck && npm run build:lib",
|
|
102
|
+
"prepublishOnly": "npm run lint && npm run lint:language && npm test && npm run typecheck && npm run build:lib",
|
|
89
103
|
"preview": "vite preview",
|
|
90
|
-
"build:types": "node scripts/emit-types.mjs"
|
|
104
|
+
"build:types": "node scripts/emit-types.mjs",
|
|
105
|
+
"release": "node scripts/release.mjs"
|
|
91
106
|
},
|
|
92
107
|
"peerDependencies": {
|
|
93
108
|
"react": "^19.0.0",
|
|
@@ -108,14 +123,19 @@
|
|
|
108
123
|
"typescript": "^5.8.0"
|
|
109
124
|
},
|
|
110
125
|
"devDependencies": {
|
|
126
|
+
"@eslint/js": "^10.0.1",
|
|
111
127
|
"@tailwindcss/vite": "^4.0.0",
|
|
112
128
|
"@types/react": "^19.0.0",
|
|
113
129
|
"@types/react-dom": "^19.0.0",
|
|
114
130
|
"@vitejs/plugin-react": "^4.3.0",
|
|
131
|
+
"eslint": "^10.10.0",
|
|
132
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
133
|
+
"globals": "^17.12.0",
|
|
115
134
|
"happy-dom": "^20.11.1",
|
|
116
135
|
"react": "^19.0.0",
|
|
117
136
|
"react-dom": "^19.0.0",
|
|
118
137
|
"tailwindcss": "^4.0.0",
|
|
138
|
+
"typescript-eslint": "^8.70.0",
|
|
119
139
|
"vite": "^6.0.0"
|
|
120
140
|
}
|
|
121
141
|
}
|