@pygmalionjs/pygmalion 0.6.31 → 0.6.33

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/node/vite.mjs CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  PYGMALION_DEV_PREFIX,
5
5
  PYGMALION_WORKTREE_SOURCE_REF,
6
6
  pygmalionDevMirrorPlugin,
7
+ resolveExactCheckoutSourceRoot,
7
8
  } from './dev-mirror.mjs';
8
9
  import {
9
10
  PYGMALION_INSPECT_CONTROL,
@@ -68,6 +69,7 @@ import {
68
69
  DEFAULT_PYGMALION_PREVIEW_ARTIFACT_FILE,
69
70
  PYGMALION_PREVIEW_ARTIFACT_ENDPOINT,
70
71
  PYGMALION_PREVIEW_CAPTURE_PROGRESS_SUFFIX,
72
+ PYGMALION_REVISION_CATALOG_SUFFIX,
71
73
  pygmalionPreviewArtifactPlugin,
72
74
  } from './preview-artifact-plugin.mjs';
73
75
  import { createPreviewCaptureWorkerChannel } from './preview-capture-worker.mjs';
@@ -233,6 +235,17 @@ export function createPygmalionVitePlugins(config) {
233
235
  // worker script; the function wins when both are present so existing
234
236
  // configurations keep their exact behavior.
235
237
  let generateArtifact = project.preview.generateArtifact;
238
+ const revisionCatalog =
239
+ project.preview.catalog === false
240
+ ? false
241
+ : optionalObject(project.preview.catalog);
242
+ const revisionCatalogFile =
243
+ revisionCatalog === false
244
+ ? undefined
245
+ : optionalNonEmptyString(
246
+ revisionCatalog.file,
247
+ 'preview.catalog.file',
248
+ );
236
249
  let captureWorkerChannel = null;
237
250
  if (!generateArtifact && project.preview.captureWorker) {
238
251
  const captureWorker = optionalObject(project.preview.captureWorker);
@@ -255,15 +268,47 @@ export function createPygmalionVitePlugins(config) {
255
268
  generateArtifact = captureWorkerChannel.generateArtifact;
256
269
  }
257
270
 
258
- if (project.preview.artifactFile || generateArtifact) {
271
+ if (project.preview.artifactFile || generateArtifact || revisionCatalogFile) {
259
272
  plugins.push(
260
273
  pygmalionPreviewArtifactPlugin({
261
274
  root: project.appRoot,
262
275
  artifactFile: project.preview.artifactFile,
263
276
  endpoint: project.preview.artifactEndpoint,
277
+ catalogFile: revisionCatalogFile,
278
+ catalogEndpoint:
279
+ revisionCatalog === false ? undefined : revisionCatalog.endpoint,
264
280
  generateArtifact,
265
281
  acquireLease: (label) => devMirror?.acquireLease(label) ?? null,
266
- sourceRoot: () => devMirror?.activeAppRoot?.() ?? project.appRoot,
282
+ acquireSourceLease: async (sourceRevision, label) => {
283
+ if (devMirror?.acquireSourceLease) {
284
+ return devMirror.acquireSourceLease(sourceRevision, label);
285
+ }
286
+ const current = resolveGitSourceState(project.projectRoot);
287
+ if (current.dirty || current.revision !== sourceRevision) return null;
288
+ const exactSourceRoot = await resolveExactCheckoutSourceRoot({
289
+ checkoutRoot: project.projectRoot,
290
+ sourceRoot: project.appRoot,
291
+ expectedRevision: sourceRevision,
292
+ });
293
+ if (!exactSourceRoot) return null;
294
+ return {
295
+ sourceRoot: exactSourceRoot,
296
+ sourceRevision: current.revision,
297
+ async validate() {
298
+ const latest = resolveGitSourceState(project.projectRoot);
299
+ if (latest.dirty || latest.revision !== sourceRevision) {
300
+ return false;
301
+ }
302
+ const latestSourceRoot = await resolveExactCheckoutSourceRoot({
303
+ checkoutRoot: project.projectRoot,
304
+ sourceRoot: project.appRoot,
305
+ expectedRevision: sourceRevision,
306
+ });
307
+ return latestSourceRoot === exactSourceRoot;
308
+ },
309
+ async release() {},
310
+ };
311
+ },
267
312
  requestRuntime: () => devMirror?.requestRuntime?.(),
268
313
  // No mirror plugin means no checkout to prepare, so generation is
269
314
  // always allowed to proceed as it did before.
@@ -442,6 +487,7 @@ export {
442
487
  DEFAULT_PYGMALION_PREVIEW_ARTIFACT_FILE,
443
488
  PYGMALION_PREVIEW_ARTIFACT_ENDPOINT,
444
489
  PYGMALION_PREVIEW_CAPTURE_PROGRESS_SUFFIX,
490
+ PYGMALION_REVISION_CATALOG_SUFFIX,
445
491
  pygmalionPreviewArtifactPlugin,
446
492
  createPreviewCaptureWorkerChannel,
447
493
  DEFAULT_QA_CAPTURE_MAX_FRAMES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pygmalionjs/pygmalion",
3
- "version": "0.6.31",
3
+ "version": "0.6.33",
4
4
  "description": "Code-backed DOM design sandbox and visual QA editor",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
package/vite.d.ts CHANGED
@@ -22,9 +22,18 @@ export interface PygmalionInventoryConfig {
22
22
  outputRoot?: string;
23
23
  /**
24
24
  * Generator-owned paths relative to the host application root. The dedicated
25
- * mirror restores only these paths before checking for unexpected changes.
25
+ * mirror restores these paths before synchronization and rejects any change
26
+ * the generator leaves outside them.
26
27
  */
27
28
  outputs?: string[];
29
+ /**
30
+ * Ignored runtime/configuration files the generator must not change. Paths
31
+ * are relative to appRoot; each entry must name a file, not a directory.
32
+ * Pygmalion compares bounded content signatures immediately around generation
33
+ * without walking .git, node_modules, or other unrelated trees. At most 64
34
+ * files totaling 16 MiB may be declared.
35
+ */
36
+ runtimeInputs?: string[];
28
37
  generate?: (context: PygmalionInventoryContext) => void | Promise<void>;
29
38
  }
30
39
 
@@ -71,6 +80,16 @@ export interface PygmalionPreviewCaptureWorkerConfig {
71
80
  jobTimeoutMs?: number;
72
81
  }
73
82
 
83
+ export interface PygmalionRevisionCatalogConfig {
84
+ /**
85
+ * JSON catalog generated inside each source checkout, relative to appRoot.
86
+ * The mirror inventory step should own this file and stamp its exact commit.
87
+ */
88
+ file: string;
89
+ /** Defaults to `${artifactEndpoint}/catalog`. */
90
+ endpoint?: string;
91
+ }
92
+
74
93
  export interface PygmalionPreviewCaptureProgressEvent {
75
94
  phase: 'queued' | 'preparing' | 'capturing' | 'retrying' | 'finalizing';
76
95
  completed: number;
@@ -83,6 +102,15 @@ export interface PygmalionPreviewCaptureGeneratorOptions {
83
102
  onProgress?: (progress: PygmalionPreviewCaptureProgressEvent) => void;
84
103
  }
85
104
 
105
+ export interface PygmalionPreviewFrameRequest {
106
+ id: string;
107
+ fingerprint?: string;
108
+ /** Identity of the complete capture recipe represented by `fingerprint`. */
109
+ recipeFingerprint?: string;
110
+ /** Opaque JSON recipe forwarded unchanged to the host capture generator. */
111
+ recipe?: Readonly<Record<string, unknown>>;
112
+ }
113
+
86
114
  export interface PygmalionPreviewConfig {
87
115
  configFile?: string;
88
116
  viteConfig?: string;
@@ -104,7 +132,7 @@ export interface PygmalionPreviewConfig {
104
132
  request: {
105
133
  namespace: string;
106
134
  sourceRevision: string;
107
- frames?: readonly { id: string; fingerprint?: string }[];
135
+ frames?: readonly PygmalionPreviewFrameRequest[];
108
136
  captureBaseUrl?: string;
109
137
  },
110
138
  options?: PygmalionPreviewCaptureGeneratorOptions,
@@ -114,6 +142,8 @@ export interface PygmalionPreviewConfig {
114
142
  * `generateArtifact` is absent. When both are set, `generateArtifact` wins.
115
143
  */
116
144
  captureWorker?: PygmalionPreviewCaptureWorkerConfig;
145
+ /** Exact-checkout screen catalog served before preview artifacts are probed. */
146
+ catalog?: false | PygmalionRevisionCatalogConfig;
117
147
  }
118
148
 
119
149
  export interface PygmalionQaConfig
@@ -186,17 +216,21 @@ export declare function createPygmalionVitePlugins(
186
216
 
187
217
  export declare const PYGMALION_PREVIEW_ARTIFACT_ENDPOINT: string;
188
218
  export declare const PYGMALION_PREVIEW_CAPTURE_PROGRESS_SUFFIX: string;
219
+ export declare const PYGMALION_REVISION_CATALOG_SUFFIX: string;
189
220
  export declare const DEFAULT_PYGMALION_PREVIEW_ARTIFACT_FILE: string;
190
221
  export declare function pygmalionPreviewArtifactPlugin(options?: {
191
222
  root?: string;
192
223
  artifactFile?: string;
193
224
  endpoint?: string;
225
+ /** Requires `acquireSourceLease`; the catalog is never read without one. */
226
+ catalogFile?: string;
227
+ catalogEndpoint?: string;
194
228
  disabled?: () => boolean;
195
229
  generateArtifact?: (
196
230
  request: {
197
231
  namespace: string;
198
232
  sourceRevision: string;
199
- frames?: readonly { id: string; fingerprint?: string }[];
233
+ frames?: readonly PygmalionPreviewFrameRequest[];
200
234
  captureBaseUrl?: string;
201
235
  },
202
236
  options?: PygmalionPreviewCaptureGeneratorOptions,
@@ -207,13 +241,36 @@ export declare function pygmalionPreviewArtifactPlugin(options?: {
207
241
  | { release(): void | Promise<void> }
208
242
  | null
209
243
  | Promise<{ release(): void | Promise<void> } | null>;
244
+ /** Holds an exact checkout for catalog reads and the whole artifact capture. */
245
+ acquireSourceLease?: (
246
+ sourceRevision: string,
247
+ label: string,
248
+ ) =>
249
+ | {
250
+ sourceRoot: string;
251
+ sourceRevision: string;
252
+ /** Revalidates direct worktrees that cannot be held by a mirror lease. */
253
+ validate?(): boolean | Promise<boolean>;
254
+ release(): void | Promise<void>;
255
+ }
256
+ | null
257
+ | Promise<{
258
+ sourceRoot: string;
259
+ sourceRevision: string;
260
+ /** Revalidates direct worktrees that cannot be held by a mirror lease. */
261
+ validate?(): boolean | Promise<boolean>;
262
+ release(): void | Promise<void>;
263
+ } | null>;
264
+ requestRuntime?: () => void;
265
+ runtimePrepared?: () => boolean;
266
+ artifactStoreMaxBytes?: number;
210
267
  }): PluginOption;
211
268
  export interface PygmalionPreviewCaptureWorkerChannel {
212
269
  generateArtifact(
213
270
  request: {
214
271
  namespace: string;
215
272
  sourceRevision: string;
216
- frames?: readonly { id: string; fingerprint?: string }[];
273
+ frames?: readonly PygmalionPreviewFrameRequest[];
217
274
  captureBaseUrl?: string;
218
275
  },
219
276
  options?: PygmalionPreviewCaptureGeneratorOptions,