@vscode/component-explorer-cli 0.2.1-163 → 0.2.1-165

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.
Files changed (89) hide show
  1. package/README.md +90 -0
  2. package/dist/_virtual/_build-info.js +1 -1
  3. package/dist/commands/mcpCommand.js +6 -1
  4. package/dist/commands/mcpCommand.js.map +1 -1
  5. package/dist/commands/serveCommand.d.ts +4 -1
  6. package/dist/commands/serveCommand.d.ts.map +1 -1
  7. package/dist/commands/serveCommand.js +91 -25
  8. package/dist/commands/serveCommand.js.map +1 -1
  9. package/dist/daemon/DaemonService.d.ts +5 -208
  10. package/dist/daemon/DaemonService.d.ts.map +1 -1
  11. package/dist/daemon/DaemonService.js +228 -304
  12. package/dist/daemon/DaemonService.js.map +1 -1
  13. package/dist/daemon/client.d.ts +76 -5
  14. package/dist/daemon/client.d.ts.map +1 -1
  15. package/dist/daemon/daemonRpc.test.d.ts +2 -0
  16. package/dist/daemon/daemonRpc.test.d.ts.map +1 -0
  17. package/dist/daemon/endpointServer.d.ts +4 -0
  18. package/dist/daemon/endpointServer.d.ts.map +1 -0
  19. package/dist/daemon/endpointServer.js +24 -0
  20. package/dist/daemon/endpointServer.js.map +1 -0
  21. package/dist/daemon/inProcessClient.d.ts +3 -9
  22. package/dist/daemon/inProcessClient.d.ts.map +1 -1
  23. package/dist/daemon/inProcessClient.js +29 -30
  24. package/dist/daemon/inProcessClient.js.map +1 -1
  25. package/dist/daemon/lifecycle.d.ts +4 -5
  26. package/dist/daemon/lifecycle.d.ts.map +1 -1
  27. package/dist/daemon/lifecycle.js +3 -2
  28. package/dist/daemon/lifecycle.js.map +1 -1
  29. package/dist/daemon/pipeClient.d.ts +2 -3
  30. package/dist/daemon/pipeClient.d.ts.map +1 -1
  31. package/dist/daemon/pipeClient.js +93 -195
  32. package/dist/daemon/pipeClient.js.map +1 -1
  33. package/dist/daemon/pipeName.d.ts +2 -2
  34. package/dist/daemon/pipeName.d.ts.map +1 -1
  35. package/dist/daemon/pipeName.js +6 -4
  36. package/dist/daemon/pipeName.js.map +1 -1
  37. package/dist/daemon/pipeServer.d.ts +2 -2
  38. package/dist/daemon/pipeServer.d.ts.map +1 -1
  39. package/dist/daemon/pipeServer.js +79 -70
  40. package/dist/daemon/pipeServer.js.map +1 -1
  41. package/dist/daemon/registerDaemonConnection.d.ts +7 -0
  42. package/dist/daemon/registerDaemonConnection.d.ts.map +1 -0
  43. package/dist/daemon/registerDaemonConnection.js +37 -0
  44. package/dist/daemon/registerDaemonConnection.js.map +1 -0
  45. package/dist/daemon/transports.test.d.ts +2 -0
  46. package/dist/daemon/transports.test.d.ts.map +1 -0
  47. package/dist/daemon/types.d.ts +1655 -8
  48. package/dist/daemon/types.d.ts.map +1 -1
  49. package/dist/daemon/types.js +4 -0
  50. package/dist/daemon/types.js.map +1 -0
  51. package/dist/daemon/version.d.ts +1 -5
  52. package/dist/daemon/version.d.ts.map +1 -1
  53. package/dist/daemon/version.js +2 -6
  54. package/dist/daemon/version.js.map +1 -1
  55. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/inspectionHost-BA6ZmC_P.js +2523 -0
  56. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/inspectionHost-BA6ZmC_P.js.map +1 -0
  57. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/src-DYVSK_tz.js +1871 -0
  58. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/src-DYVSK_tz.js.map +1 -0
  59. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/webSocketTransport-Ct4OIdG9.js +220 -0
  60. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/webSocketTransport-Ct4OIdG9.js.map +1 -0
  61. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/node.js +721 -0
  62. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/node.js.map +1 -0
  63. package/dist/index.d.ts +0 -2
  64. package/dist/js-component-explorer/packages/common/dist/daemonRpc.js +165 -0
  65. package/dist/js-component-explorer/packages/common/dist/daemonRpc.js.map +1 -0
  66. package/dist/mcp/DaemonAccessor.d.ts +2 -3
  67. package/dist/mcp/DaemonAccessor.d.ts.map +1 -1
  68. package/dist/mcp/McpServer.d.ts +1 -1
  69. package/dist/mcp/McpServer.d.ts.map +1 -1
  70. package/dist/mcp/McpServer.js +69 -58
  71. package/dist/mcp/McpServer.js.map +1 -1
  72. package/dist/renderManifest.d.ts +70 -5
  73. package/dist/server/serverConfig.d.ts +0 -2
  74. package/dist/server/serverConfig.d.ts.map +1 -1
  75. package/dist/server/serverConfig.js.map +1 -1
  76. package/dist/server/viteServer.d.ts +0 -2
  77. package/dist/server/viteServer.d.ts.map +1 -1
  78. package/dist/server/viteServer.js.map +1 -1
  79. package/package.json +9 -8
  80. package/dist/formatValue.js +0 -96
  81. package/dist/formatValue.js.map +0 -1
  82. package/dist/js-component-explorer/packages/simple-api/dist/chunk-FJ7AVNQE.js +0 -138
  83. package/dist/js-component-explorer/packages/simple-api/dist/chunk-FJ7AVNQE.js.map +0 -1
  84. package/dist/js-component-explorer/packages/simple-api/dist/chunk-TTRCY65Z.js +0 -27
  85. package/dist/js-component-explorer/packages/simple-api/dist/chunk-TTRCY65Z.js.map +0 -1
  86. package/dist/js-component-explorer/packages/simple-api/dist/chunk-WNXMRXWV.js +0 -28
  87. package/dist/js-component-explorer/packages/simple-api/dist/chunk-WNXMRXWV.js.map +0 -1
  88. package/dist/js-component-explorer/packages/simple-api/dist/express.js +0 -104
  89. package/dist/js-component-explorer/packages/simple-api/dist/express.js.map +0 -1
@@ -1,4 +1,3 @@
1
- import { z } from 'zod';
2
1
  import '../vscode-observables/observables/dist/observableInternal/index.js';
3
2
  import { observableValue } from '../vscode-observables/observables/dist/observableInternal/observables/observableValue.js';
4
3
  import '../vscode-observables/observables/dist/observableInternal/debugLocation.js';
@@ -7,8 +6,8 @@ import { derived } from '../vscode-observables/observables/dist/observableIntern
7
6
  import { waitForState } from '../vscode-observables/observables/dist/observableInternal/utils/utilsCancellation.js';
8
7
  import '../vscode-observables/observables/dist/observableInternal/utils/utils.js';
9
8
  import '../vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js';
10
- import { createApiFactory } from '../js-component-explorer/packages/simple-api/dist/chunk-FJ7AVNQE.js';
11
- import { AsyncStream } from '../js-component-explorer/packages/simple-api/dist/chunk-TTRCY65Z.js';
9
+ import { nt as defaultInterfaceTarget } from '../external/linkrpc/typescript/packages/linkrpc/dist/chunks/src-DYVSK_tz.js';
10
+ import { daemonRpcInterface } from '../js-component-explorer/packages/common/dist/daemonRpc.js';
12
11
  import { ExplorerSession } from '../explorerSession.js';
13
12
  import '../config.js';
14
13
  import { RelativePath } from '../path.js';
@@ -21,7 +20,6 @@ import { FileApprovalStore } from './approvalStore.js';
21
20
  import { VisualCache } from '../visualCache.js';
22
21
  import { contentHash } from '../screenshotCache.js';
23
22
  import { WorktreePool } from '../WorktreePool.js';
24
- import { pluginProtocolVersionText, daemonApiVersionText } from './version.js';
25
23
  export { isCompatibleVersion, parseVersion } from './version.js';
26
24
  import { ViteServerConfig } from '../server/serverConfig.js';
27
25
 
@@ -108,7 +106,6 @@ class DaemonService {
108
106
  _worktreePool;
109
107
  approvals;
110
108
  visualCache;
111
- api;
112
109
  constructor(_config, _pipeName, _logger, _browserFactory, approvalStorePath, idleTimeoutMs) {
113
110
  this._config = _config;
114
111
  this._pipeName = _pipeName;
@@ -120,350 +117,265 @@ class DaemonService {
120
117
  if (_config.worktreePool) {
121
118
  this._worktreePool = new WorktreePool(_config.worktreePool.maxSlots, _config.repo.worktreeRootPath());
122
119
  }
123
- this.api = this._buildApi();
124
120
  }
125
121
  static async create(config, logger, pipeName, options) {
126
122
  const browserFactory = new PlaywrightBrowserPageFactory(options?.headed ?? false, 30_000, logger);
127
123
  const approvalStorePath = config.screenshotDir.join(new RelativePath('approvals.json'));
128
124
  const idleTimeoutMs = options?.idleTimeoutMs ?? 5 * 60 * 1000; // 5 minutes default
129
125
  const svc = new DaemonService(config, pipeName, logger, browserFactory, approvalStorePath, idleTimeoutMs);
130
- for (const sessionConfig of config.sessions) {
131
- svc._sessionConfigs.set(sessionConfig.name, sessionConfig);
132
- await svc._setupSession(sessionConfig);
126
+ try {
127
+ for (const sessionConfig of config.sessions) {
128
+ svc._sessionConfigs.set(sessionConfig.name, sessionConfig);
129
+ await svc._setupSession(sessionConfig);
130
+ }
131
+ }
132
+ catch (error) {
133
+ await svc.dispose();
134
+ throw error;
133
135
  }
134
136
  return svc;
135
137
  }
136
138
  // -- API definition ------------------------------------------------------
137
- _buildApi() {
138
- const { createApi, createMethod } = createApiFactory();
139
- return createApi({
140
- fixtures: createApi({
141
- list: createMethod({
142
- args: {
143
- sessionName: z.string(),
144
- sourceTreeId: z.string(),
145
- },
146
- }, async (args) => {
147
- this._activityTracker.reportActivity();
148
- this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
149
- const session = this.getSession(args.sessionName);
150
- const result = await session.explorer.listFixtures();
151
- this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
152
- return result;
153
- }),
154
- }),
155
- screenshots: createApi({
156
- take: createMethod({
157
- args: {
158
- fixtureId: z.string(),
159
- sourceTreeId: z.string(),
160
- sessionName: z.string(),
161
- includeImage: z.boolean().optional(),
162
- stabilityCheck: z.boolean().optional(),
163
- reloadBeforeRender: z.boolean().optional(),
164
- input: z.unknown().optional(),
165
- disposeAfter: z.boolean().optional(),
166
- },
167
- }, async (args) => {
168
- this._activityTracker.reportActivity();
169
- const session = this.getSession(args.sessionName);
170
- this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
171
- if (args.stabilityCheck) {
172
- const result = await session.explorer.screenshotFixtureStabilityCheck(args.fixtureId, {
173
- reloadBeforeRender: args.reloadBeforeRender,
174
- input: args.input,
175
- disposeAfter: args.disposeAfter,
176
- });
177
- this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
178
- const hashes = result.screenshots.map(s => contentHash(s.image));
179
- const isStable = hashes[0] === hashes[1] && hashes[1] === hashes[2];
180
- const stabilityScreenshots = result.screenshots.map((s, i) => ({
181
- hash: hashes[i],
182
- delayMs: s.delayMs,
183
- image: (args.includeImage ?? true)
184
- ? Buffer.from(s.image).toString('base64')
185
- : undefined,
186
- }));
187
- return {
188
- hash: hashes[hashes.length - 1],
189
- sourceTreeId: args.sourceTreeId,
190
- image: (args.includeImage ?? true)
191
- ? Buffer.from(result.screenshots[result.screenshots.length - 1].image).toString('base64')
192
- : undefined,
193
- hasError: result.hasError,
194
- error: result.error,
195
- events: result.events.length > 0 ? result.events : undefined,
196
- output: result.output,
197
- isStable,
198
- stabilityScreenshots,
199
- previousDispose: result.previousDispose,
200
- currentDispose: result.currentDispose,
201
- };
202
- }
203
- const result = await session.explorer.screenshotFixture(args.fixtureId, {
139
+ registerRpc(connection, context) {
140
+ const registration = connection.register(defaultInterfaceTarget(daemonRpcInterface), {
141
+ fixturesList: async (args) => {
142
+ this._activityTracker.reportActivity();
143
+ this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
144
+ const session = this.getSession(args.sessionName);
145
+ const result = await session.explorer.listFixtures();
146
+ this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
147
+ return result;
148
+ },
149
+ screenshotsTake: async (args) => {
150
+ this._activityTracker.reportActivity();
151
+ const session = this.getSession(args.sessionName);
152
+ this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
153
+ if (args.stabilityCheck) {
154
+ const result = await session.explorer.screenshotFixtureStabilityCheck(args.fixtureId, {
204
155
  reloadBeforeRender: args.reloadBeforeRender,
205
156
  input: args.input,
206
157
  disposeAfter: args.disposeAfter,
207
158
  });
208
159
  this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
160
+ const hashes = result.screenshots.map(s => contentHash(s.image));
161
+ const isStable = hashes[0] === hashes[1] && hashes[1] === hashes[2];
162
+ const stabilityScreenshots = result.screenshots.map((s, i) => ({
163
+ hash: hashes[i],
164
+ delayMs: s.delayMs,
165
+ image: (args.includeImage ?? true)
166
+ ? Buffer.from(s.image).toString('base64')
167
+ : undefined,
168
+ }));
209
169
  return {
210
- hash: result.image ? contentHash(result.image) : undefined,
170
+ hash: hashes[hashes.length - 1],
211
171
  sourceTreeId: args.sourceTreeId,
212
- image: (args.includeImage ?? true) && result.image
213
- ? Buffer.from(result.image).toString('base64')
172
+ image: (args.includeImage ?? true)
173
+ ? Buffer.from(result.screenshots[result.screenshots.length - 1].image).toString('base64')
214
174
  : undefined,
215
175
  hasError: result.hasError,
216
176
  error: result.error,
217
- events: result.events?.length > 0 ? result.events : undefined,
177
+ events: result.events.length > 0 ? result.events : undefined,
218
178
  output: result.output,
179
+ isStable,
180
+ stabilityScreenshots,
219
181
  previousDispose: result.previousDispose,
220
182
  currentDispose: result.currentDispose,
221
183
  };
222
- }),
223
- takeBatch: createMethod({
224
- args: {
225
- fixtureIds: z.array(z.string()),
226
- sourceTreeId: z.string(),
227
- sessionName: z.string(),
228
- includeImages: z.boolean().optional(),
229
- reloadBetweenFixtures: z.boolean().optional(),
230
- input: z.unknown().optional(),
231
- },
232
- }, async (args) => {
233
- this._activityTracker.reportActivity();
234
- const session = this.getSession(args.sessionName);
184
+ }
185
+ const result = await session.explorer.screenshotFixture(args.fixtureId, {
186
+ reloadBeforeRender: args.reloadBeforeRender,
187
+ input: args.input,
188
+ disposeAfter: args.disposeAfter,
189
+ });
190
+ this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
191
+ return {
192
+ hash: result.image ? contentHash(result.image) : undefined,
193
+ sourceTreeId: args.sourceTreeId,
194
+ image: (args.includeImage ?? true) && result.image
195
+ ? Buffer.from(result.image).toString('base64')
196
+ : undefined,
197
+ hasError: result.hasError,
198
+ error: result.error,
199
+ events: result.events?.length > 0 ? result.events : undefined,
200
+ output: result.output,
201
+ previousDispose: result.previousDispose,
202
+ currentDispose: result.currentDispose,
203
+ };
204
+ },
205
+ screenshotsTakeBatch: async (args) => {
206
+ this._activityTracker.reportActivity();
207
+ const session = this.getSession(args.sessionName);
208
+ this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
209
+ const includeImages = args.includeImages ?? false;
210
+ this._logger.trace(`takeBatch: ${args.fixtureIds.length} fixtures, session=${args.sessionName}`);
211
+ const startTime = Date.now();
212
+ const screenshots = [];
213
+ for (let i = 0; i < args.fixtureIds.length; i++) {
214
+ const fixtureId = args.fixtureIds[i];
215
+ const result = await session.explorer.screenshotFixture(fixtureId, {
216
+ reloadOnError: true,
217
+ // Reload between fixtures (skip before the first one — the page is already fresh)
218
+ reloadBeforeRender: args.reloadBetweenFixtures && i > 0,
219
+ input: args.input,
220
+ });
235
221
  this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
236
- const includeImages = args.includeImages ?? false;
237
- this._logger.trace(`takeBatch: ${args.fixtureIds.length} fixtures, session=${args.sessionName}`);
238
- const startTime = Date.now();
239
- const screenshots = [];
240
- for (let i = 0; i < args.fixtureIds.length; i++) {
241
- const fixtureId = args.fixtureIds[i];
242
- const result = await session.explorer.screenshotFixture(fixtureId, {
243
- reloadOnError: true,
244
- // Reload between fixtures (skip before the first one — the page is already fresh)
245
- reloadBeforeRender: args.reloadBetweenFixtures && i > 0,
246
- input: args.input,
247
- });
248
- this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
249
- screenshots.push({
250
- fixtureId,
251
- hash: result.image ? contentHash(result.image) : undefined,
252
- image: includeImages && result.image
253
- ? Buffer.from(result.image).toString('base64')
254
- : undefined,
255
- hasError: result.hasError,
256
- error: result.error,
257
- events: result.events.length > 0 ? result.events : undefined,
258
- output: result.output,
259
- });
260
- }
261
- this._logger.trace(`takeBatch: done in ${Date.now() - startTime}ms`);
262
- return { sourceTreeId: args.sourceTreeId, screenshots };
263
- }),
264
- compare: createMethod({
265
- args: {
266
- fixtureId: z.string(),
267
- baselineSessionName: z.string(),
268
- baselineSourceTreeId: z.string(),
269
- currentSessionName: z.string(),
270
- currentSourceTreeId: z.string(),
271
- includeImages: z.boolean().optional(),
272
- },
273
- }, async (args) => {
274
- this._activityTracker.reportActivity();
275
- const baselineSession = this.getSession(args.baselineSessionName);
276
- const currentSession = this.getSession(args.currentSessionName);
277
- this.assertSourceTreeId(args.baselineSessionName, args.baselineSourceTreeId);
278
- this.assertSourceTreeId(args.currentSessionName, args.currentSourceTreeId);
279
- const [baselineResult, baselineListResult] = await Promise.all([
280
- baselineSession.explorer.screenshotFixture(args.fixtureId),
281
- baselineSession.explorer.listFixtures(),
282
- ]);
283
- this.assertSourceTreeId(args.baselineSessionName, args.baselineSourceTreeId);
284
- const [currentResult, currentListResult] = await Promise.all([
285
- currentSession.explorer.screenshotFixture(args.fixtureId),
286
- currentSession.explorer.listFixtures(),
287
- ]);
288
- this.assertSourceTreeId(args.currentSessionName, args.currentSourceTreeId);
289
- const baselineHash = baselineResult.image ? contentHash(baselineResult.image) : undefined;
290
- const currentHash = currentResult.image ? contentHash(currentResult.image) : undefined;
291
- const includeImages = args.includeImages ?? false;
292
- const currentFixture = currentListResult.fixtures.find(f => f.fixtureId === args.fixtureId);
293
- const baselineFixture = baselineListResult.fixtures.find(f => f.fixtureId === args.fixtureId);
294
- const labels = currentFixture?.labels;
295
- const labelsChanged = baselineFixture && !arraysEqual(baselineFixture.labels, currentFixture?.labels ?? []);
296
- return {
297
- match: baselineHash === currentHash,
298
- baselineHash,
299
- currentHash,
300
- baselineImage: includeImages && baselineResult.image
301
- ? Buffer.from(baselineResult.image).toString('base64') : undefined,
302
- currentImage: includeImages && currentResult.image
303
- ? Buffer.from(currentResult.image).toString('base64') : undefined,
304
- baselineHasError: baselineResult.hasError,
305
- baselineError: baselineResult.error,
306
- baselineEvents: baselineResult.events.length > 0 ? baselineResult.events : undefined,
307
- baselineOutput: baselineResult.output,
308
- currentHasError: currentResult.hasError,
309
- currentError: currentResult.error,
310
- currentEvents: currentResult.events.length > 0 ? currentResult.events : undefined,
311
- currentOutput: currentResult.output,
312
- labels,
313
- labelsBefore: labelsChanged ? baselineFixture.labels : undefined,
314
- approval: (baselineHash !== undefined && currentHash !== undefined && baselineHash !== currentHash)
315
- ? this.approvals.lookup({
316
- fixtureId: args.fixtureId,
317
- originalHash: baselineHash,
318
- modifiedHash: currentHash,
319
- }) ?? undefined
222
+ screenshots.push({
223
+ fixtureId,
224
+ hash: result.image ? contentHash(result.image) : undefined,
225
+ image: includeImages && result.image
226
+ ? Buffer.from(result.image).toString('base64')
320
227
  : undefined,
321
- };
322
- }),
323
- }),
324
- approvals: createApi({
325
- approve: createMethod({
326
- args: {
327
- fixtureId: z.string(),
328
- originalHash: z.string(),
329
- modifiedHash: z.string(),
330
- comment: z.string(),
331
- },
332
- }, async (args) => {
333
- this._activityTracker.reportActivity();
334
- this.approvals.approve(args);
335
- }),
336
- revoke: createMethod({
337
- args: {
338
- fixtureId: z.string(),
339
- originalHash: z.string(),
340
- modifiedHash: z.string(),
341
- },
342
- }, async (args) => {
343
- this._activityTracker.reportActivity();
344
- this.approvals.revoke(args);
345
- }),
346
- list: createMethod({
347
- args: { fixtureId: z.string().optional() },
348
- }, async (args) => {
349
- this._activityTracker.reportActivity();
350
- return this.approvals.list(args.fixtureId);
351
- }),
352
- lookup: createMethod({
353
- args: {
354
- fixtureId: z.string(),
355
- originalHash: z.string(),
356
- modifiedHash: z.string(),
357
- },
358
- }, async (args) => {
359
- this._activityTracker.reportActivity();
360
- return this.approvals.lookup(args) ?? null;
361
- }),
362
- }),
363
- visualReview: createApi({
364
- getStatus: createMethod({
365
- args: {
366
- fixtureId: z.string(),
367
- expectedVisualDescriptions: z.array(z.string()),
368
- screenshotHash: z.string(),
369
- },
370
- }, async (args) => {
371
- this._activityTracker.reportActivity();
372
- return this.visualCache.getReviewStatus(args.fixtureId, args.expectedVisualDescriptions, args.screenshotHash);
373
- }),
374
- approve: createMethod({
375
- args: {
376
- fixtureId: z.string(),
377
- expectedVisualDescriptions: z.array(z.string()),
378
- screenshotHash: z.string(),
379
- comment: z.string(),
380
- },
381
- }, async (args) => {
382
- this._activityTracker.reportActivity();
383
- this.visualCache.approve(args.fixtureId, args.expectedVisualDescriptions, args.screenshotHash, args.comment);
384
- }),
385
- }),
386
- evaluate: createMethod({
387
- args: {
388
- sessionName: z.string(),
389
- sourceTreeId: z.string(),
390
- expression: z.string(),
391
- fixtureId: z.string().optional(),
392
- },
393
- }, async (args) => {
228
+ hasError: result.hasError,
229
+ error: result.error,
230
+ events: result.events.length > 0 ? result.events : undefined,
231
+ output: result.output,
232
+ });
233
+ }
234
+ this._logger.trace(`takeBatch: done in ${Date.now() - startTime}ms`);
235
+ return { sourceTreeId: args.sourceTreeId, screenshots };
236
+ },
237
+ screenshotsCompare: async (args) => {
238
+ this._activityTracker.reportActivity();
239
+ const baselineSession = this.getSession(args.baselineSessionName);
240
+ const currentSession = this.getSession(args.currentSessionName);
241
+ this.assertSourceTreeId(args.baselineSessionName, args.baselineSourceTreeId);
242
+ this.assertSourceTreeId(args.currentSessionName, args.currentSourceTreeId);
243
+ const [baselineResult, baselineListResult] = await Promise.all([
244
+ baselineSession.explorer.screenshotFixture(args.fixtureId),
245
+ baselineSession.explorer.listFixtures(),
246
+ ]);
247
+ this.assertSourceTreeId(args.baselineSessionName, args.baselineSourceTreeId);
248
+ const [currentResult, currentListResult] = await Promise.all([
249
+ currentSession.explorer.screenshotFixture(args.fixtureId),
250
+ currentSession.explorer.listFixtures(),
251
+ ]);
252
+ this.assertSourceTreeId(args.currentSessionName, args.currentSourceTreeId);
253
+ const baselineHash = baselineResult.image ? contentHash(baselineResult.image) : undefined;
254
+ const currentHash = currentResult.image ? contentHash(currentResult.image) : undefined;
255
+ const includeImages = args.includeImages ?? false;
256
+ const currentFixture = currentListResult.fixtures.find(f => f.fixtureId === args.fixtureId);
257
+ const baselineFixture = baselineListResult.fixtures.find(f => f.fixtureId === args.fixtureId);
258
+ const labels = currentFixture?.labels;
259
+ const labelsChanged = baselineFixture && !arraysEqual(baselineFixture.labels, currentFixture?.labels ?? []);
260
+ return {
261
+ match: baselineHash === currentHash,
262
+ baselineHash,
263
+ currentHash,
264
+ baselineImage: includeImages && baselineResult.image
265
+ ? Buffer.from(baselineResult.image).toString('base64') : undefined,
266
+ currentImage: includeImages && currentResult.image
267
+ ? Buffer.from(currentResult.image).toString('base64') : undefined,
268
+ baselineHasError: baselineResult.hasError,
269
+ baselineError: baselineResult.error,
270
+ baselineEvents: baselineResult.events.length > 0 ? baselineResult.events : undefined,
271
+ baselineOutput: baselineResult.output,
272
+ currentHasError: currentResult.hasError,
273
+ currentError: currentResult.error,
274
+ currentEvents: currentResult.events.length > 0 ? currentResult.events : undefined,
275
+ currentOutput: currentResult.output,
276
+ labels,
277
+ labelsBefore: labelsChanged ? baselineFixture.labels : undefined,
278
+ approval: (baselineHash !== undefined && currentHash !== undefined && baselineHash !== currentHash)
279
+ ? this.approvals.lookup({
280
+ fixtureId: args.fixtureId,
281
+ originalHash: baselineHash,
282
+ modifiedHash: currentHash,
283
+ }) ?? undefined
284
+ : undefined,
285
+ };
286
+ },
287
+ approvalsApprove: async (args) => {
288
+ this._activityTracker.reportActivity();
289
+ this.approvals.approve(args);
290
+ },
291
+ approvalsRevoke: async (args) => {
292
+ this._activityTracker.reportActivity();
293
+ this.approvals.revoke(args);
294
+ },
295
+ approvalsList: async (args) => {
296
+ this._activityTracker.reportActivity();
297
+ return this.approvals.list(args.fixtureId);
298
+ },
299
+ approvalsLookup: async (args) => {
300
+ this._activityTracker.reportActivity();
301
+ return this.approvals.lookup(args) ?? null;
302
+ },
303
+ visualReviewGetStatus: async (args) => {
304
+ this._activityTracker.reportActivity();
305
+ return this.visualCache.getReviewStatus(args.fixtureId, args.expectedVisualDescriptions, args.screenshotHash);
306
+ },
307
+ visualReviewApprove: async (args) => {
308
+ this._activityTracker.reportActivity();
309
+ this.visualCache.approve(args.fixtureId, args.expectedVisualDescriptions, args.screenshotHash, args.comment);
310
+ },
311
+ evaluate: async (args) => {
394
312
  this._activityTracker.reportActivity();
395
313
  const session = this.getSession(args.sessionName);
396
314
  this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
397
315
  const result = await session.explorer.evaluateJs(args.expression, args.fixtureId);
398
316
  this.assertSourceTreeId(args.sessionName, args.sourceTreeId);
399
317
  return { result };
400
- }),
401
- setBrowserVisibility: createMethod({
402
- args: { visible: z.boolean() },
403
- }, async (args, ctx) => {
318
+ },
319
+ setBrowserVisibility: async (args) => {
404
320
  this._activityTracker.reportActivity();
405
- this._logger.debug(`Set browser visibility: ${args.visible} (client=${ctx.clientName})`);
321
+ this._logger.debug(`Set browser visibility: ${args.visible} (client=${context.clientName})`);
406
322
  await this._browserFactory.setHeaded(args.visible);
407
323
  for (const session of this._sessions.values()) {
408
324
  session.explorer.closePage();
409
325
  }
410
326
  return { visible: args.visible };
411
- }),
412
- events: createMethod({ args: {} }, async () => {
413
- return AsyncStream.fromIterable(this.eventStream());
414
- }),
415
- sessions: createMethod({ args: {} }, async (_args, ctx) => {
327
+ },
328
+ events: async (_args, _ctx, stream) => {
329
+ const iterator = this.eventStream()[Symbol.asyncIterator]();
330
+ const onAbort = () => { void iterator.return?.(); };
331
+ stream.signal.addEventListener('abort', onAbort, { once: true });
332
+ try {
333
+ while (!stream.signal.aborted) {
334
+ const next = await iterator.next();
335
+ if (next.done) {
336
+ break;
337
+ }
338
+ await stream.send(next.value);
339
+ }
340
+ }
341
+ finally {
342
+ stream.signal.removeEventListener('abort', onAbort);
343
+ await iterator.return?.();
344
+ }
345
+ },
346
+ sessions: async () => {
416
347
  this._activityTracker.reportActivity();
417
- this._logger.trace(`API: sessions (client=${ctx.clientName}, eventListeners=${this._eventListeners.size})`);
348
+ this._logger.trace(`API: sessions (client=${context.clientName}, eventListeners=${this._eventListeners.size})`);
418
349
  return this.getSessionInfos();
419
- }),
420
- version: createMethod({ args: {} }, async () => {
421
- this._activityTracker.reportActivity();
422
- return {
423
- daemonApiVersion: daemonApiVersionText,
424
- pluginProtocolVersion: pluginProtocolVersionText,
425
- };
426
- }),
427
- restartSession: createMethod({
428
- args: { sessionName: z.string() },
429
- }, async (args, ctx) => {
350
+ },
351
+ restartSession: async (args) => {
430
352
  this._activityTracker.reportActivity();
431
- this._logger.debug(`Restart session "${args.sessionName}" requested (client=${ctx.clientName})`);
353
+ this._logger.debug(`Restart session "${args.sessionName}" requested (client=${context.clientName})`);
432
354
  await this._restartSession(args.sessionName);
433
355
  return this.getSessionInfos();
434
- }),
435
- openSession: createMethod({
436
- args: {
437
- name: z.string(),
438
- ref: z.string(),
439
- },
440
- }, async (args, ctx) => {
356
+ },
357
+ openSession: async (args) => {
441
358
  this._activityTracker.reportActivity();
442
- this._logger.log(`Open session "${args.name}" @ ${args.ref} requested (client=${ctx.clientName})`);
359
+ this._logger.log(`Open session "${args.name}" @ ${args.ref} requested (client=${context.clientName})`);
443
360
  return this._openDynamicSession(args.name, args.ref);
444
- }),
445
- closeSession: createMethod({
446
- args: { name: z.string() },
447
- }, async (args, ctx) => {
361
+ },
362
+ closeSession: async (args) => {
448
363
  this._activityTracker.reportActivity();
449
- this._logger.log(`Close session "${args.name}" requested (client=${ctx.clientName})`);
364
+ this._logger.log(`Close session "${args.name}" requested (client=${context.clientName})`);
450
365
  return this._closeDynamicSession(args.name);
451
- }),
452
- updateSessionRef: createMethod({
453
- args: {
454
- name: z.string(),
455
- ref: z.string(),
456
- },
457
- }, async (args, ctx) => {
366
+ },
367
+ updateSessionRef: async (args) => {
458
368
  this._activityTracker.reportActivity();
459
- this._logger.log(`Update session ref "${args.name}" → ${args.ref} requested (client=${ctx.clientName})`);
369
+ this._logger.log(`Update session ref "${args.name}" → ${args.ref} requested (client=${context.clientName})`);
460
370
  return this._updateDynamicSessionRef(args.name, args.ref);
461
- }),
462
- shutdown: createMethod({ args: {} }, async (_args, ctx) => {
463
- this._logger.debug(`Shutdown requested via API (client=${ctx.clientName})`);
464
- this.requestShutdown();
465
- }),
371
+ },
372
+ shutdown: async () => {
373
+ this._logger.debug(`Shutdown requested via API (client=${context.clientName})`);
374
+ // Let LinkRPC serialize the reply before shutting down its transport.
375
+ setImmediate(() => this.requestShutdown());
376
+ },
466
377
  });
378
+ return registration;
467
379
  }
468
380
  // -- Session access ------------------------------------------------------
469
381
  getSession(name) {
@@ -551,6 +463,9 @@ class DaemonService {
551
463
  let resolve;
552
464
  let done = false;
553
465
  const listener = (event) => {
466
+ if (done) {
467
+ return;
468
+ }
554
469
  if (event.type !== 'log') {
555
470
  self._logger.trace(`Event stream: ${event.type}`);
556
471
  }
@@ -568,6 +483,9 @@ class DaemonService {
568
483
  self._activityTracker.setActive(true);
569
484
  self._logger.debug(`Event stream opened (listeners: ${self._eventListeners.size})`);
570
485
  const onShutdown = () => {
486
+ if (done) {
487
+ return;
488
+ }
571
489
  done = true;
572
490
  cleanup();
573
491
  if (resolve) {
@@ -578,9 +496,16 @@ class DaemonService {
578
496
  };
579
497
  self._shutdownResolvers.push(onShutdown);
580
498
  const cleanup = () => {
499
+ if (!self._eventListeners.has(listener)) {
500
+ return;
501
+ }
581
502
  self._eventListeners.delete(listener);
582
503
  self._eventListenerCount.set(self._eventListeners.size, undefined);
583
504
  self._activityTracker.setActive(false);
505
+ const shutdownIndex = self._shutdownResolvers.indexOf(onShutdown);
506
+ if (shutdownIndex >= 0) {
507
+ self._shutdownResolvers.splice(shutdownIndex, 1);
508
+ }
584
509
  self._logger.debug(`Event stream closed (listeners: ${self._eventListeners.size})`);
585
510
  };
586
511
  return {
@@ -676,10 +601,11 @@ class DaemonService {
676
601
  // -- Shutdown ------------------------------------------------------------
677
602
  requestShutdown() {
678
603
  this._shutdownRequested = true;
679
- for (const resolve of this._shutdownResolvers) {
604
+ const resolvers = this._shutdownResolvers;
605
+ this._shutdownResolvers = [];
606
+ for (const resolve of resolvers) {
680
607
  resolve();
681
608
  }
682
- this._shutdownResolvers = [];
683
609
  }
684
610
  async dispose() {
685
611
  this.requestShutdown();
@@ -733,8 +659,6 @@ class DaemonService {
733
659
  daemonConfig: this._pipeName ? {
734
660
  pipeName: this._pipeName,
735
661
  sessionName,
736
- daemonApiVersion: daemonApiVersionText,
737
- pluginProtocolVersion: pluginProtocolVersionText,
738
662
  } : undefined,
739
663
  };
740
664
  }
@@ -991,5 +915,5 @@ function arraysEqual(a, b) {
991
915
  return true;
992
916
  }
993
917
 
994
- export { ActivityTracker, DaemonService, SourceTreeChangedError, daemonApiVersionText, pluginProtocolVersionText };
918
+ export { ActivityTracker, DaemonService, SourceTreeChangedError };
995
919
  //# sourceMappingURL=DaemonService.js.map