@langwatch/scenario 0.2.0 → 0.2.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.
@@ -30,6 +30,23 @@ var import_vitest = require("vitest");
30
30
  // src/events/event-bus.ts
31
31
  var import_rxjs = require("rxjs");
32
32
 
33
+ // src/config/load.ts
34
+ var import_promises = __toESM(require("fs/promises"));
35
+ var import_node_path = __toESM(require("path"));
36
+ var import_node_url = require("url");
37
+
38
+ // src/domain/core/config.ts
39
+ var import_zod = require("zod");
40
+ var scenarioProjectConfigSchema = import_zod.z.object({
41
+ defaultModel: import_zod.z.object({
42
+ model: import_zod.z.custom(),
43
+ temperature: import_zod.z.number().min(0).max(1).optional().default(0),
44
+ maxTokens: import_zod.z.number().optional()
45
+ }).optional(),
46
+ langwatchEndpoint: import_zod.z.string().optional(),
47
+ langwatchApiKey: import_zod.z.string().optional()
48
+ }).strict();
49
+
33
50
  // src/utils/logger.ts
34
51
  var Logger = class _Logger {
35
52
  constructor(context) {
@@ -41,21 +58,25 @@ var Logger = class _Logger {
41
58
  static create(context) {
42
59
  return new _Logger(context);
43
60
  }
61
+ getLogLevel() {
62
+ return env.SCENARIO_LOG_LEVEL ?? "INFO" /* INFO */;
63
+ }
64
+ getLogLevelIndex(level) {
65
+ return Object.values(LogLevel).indexOf(level);
66
+ }
44
67
  /**
45
68
  * Checks if logging should occur based on LOG_LEVEL env var
46
69
  */
47
70
  shouldLog(level) {
48
- const logLevel = (process.env.SCENARIO_LOG_LEVEL || "").toLowerCase();
49
- const levels = ["error", "warn", "info", "debug"];
50
- const currentLevelIndex = levels.indexOf(logLevel);
51
- const requestedLevelIndex = levels.indexOf(level);
71
+ const currentLevelIndex = this.getLogLevelIndex(this.getLogLevel());
72
+ const requestedLevelIndex = this.getLogLevelIndex(level);
52
73
  return currentLevelIndex >= 0 && requestedLevelIndex <= currentLevelIndex;
53
74
  }
54
75
  formatMessage(message) {
55
76
  return this.context ? `[${this.context}] ${message}` : message;
56
77
  }
57
78
  error(message, data) {
58
- if (this.shouldLog("error")) {
79
+ if (this.shouldLog("ERROR" /* ERROR */)) {
59
80
  const formattedMessage = this.formatMessage(message);
60
81
  if (data) {
61
82
  console.error(formattedMessage, data);
@@ -65,7 +86,7 @@ var Logger = class _Logger {
65
86
  }
66
87
  }
67
88
  warn(message, data) {
68
- if (this.shouldLog("warn")) {
89
+ if (this.shouldLog("WARN" /* WARN */)) {
69
90
  const formattedMessage = this.formatMessage(message);
70
91
  if (data) {
71
92
  console.warn(formattedMessage, data);
@@ -75,7 +96,7 @@ var Logger = class _Logger {
75
96
  }
76
97
  }
77
98
  info(message, data) {
78
- if (this.shouldLog("info")) {
99
+ if (this.shouldLog("INFO" /* INFO */)) {
79
100
  const formattedMessage = this.formatMessage(message);
80
101
  if (data) {
81
102
  console.info(formattedMessage, data);
@@ -85,7 +106,7 @@ var Logger = class _Logger {
85
106
  }
86
107
  }
87
108
  debug(message, data) {
88
- if (this.shouldLog("debug")) {
109
+ if (this.shouldLog("DEBUG" /* DEBUG */)) {
89
110
  const formattedMessage = this.formatMessage(message);
90
111
  if (data) {
91
112
  console.log(formattedMessage, data);
@@ -96,78 +117,149 @@ var Logger = class _Logger {
96
117
  }
97
118
  };
98
119
 
99
- // src/events/event-reporter.ts
100
- var EventReporter = class {
101
- eventsEndpoint;
102
- apiKey;
103
- logger = new Logger("scenario.events.EventReporter");
104
- constructor(config) {
105
- this.eventsEndpoint = new URL("/api/scenario-events", config.endpoint);
106
- this.apiKey = config.apiKey ?? "";
107
- if (!process.env.SCENARIO_DISABLE_SIMULATION_REPORT_INFO) {
108
- if (!this.apiKey) {
109
- console.log(
110
- "\u27A1\uFE0F LangWatch API key not configured, simulations will only output the final result"
111
- );
112
- console.log(
113
- "To visualize the conversations in real time, configure your LangWatch API key (via LANGWATCH_API_KEY, or scenario.config.js)"
114
- );
115
- } else {
116
- console.log(`simulation reporting is enabled, endpoint:(${this.eventsEndpoint}) api_key_configured:(${this.apiKey.length > 0 ? "true" : "false"})`);
117
- }
120
+ // src/config/env.ts
121
+ var import_zod2 = require("zod");
122
+
123
+ // src/config/log-levels.ts
124
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
125
+ LogLevel2["ERROR"] = "ERROR";
126
+ LogLevel2["WARN"] = "WARN";
127
+ LogLevel2["INFO"] = "INFO";
128
+ LogLevel2["DEBUG"] = "DEBUG";
129
+ return LogLevel2;
130
+ })(LogLevel || {});
131
+
132
+ // src/config/env.ts
133
+ var envSchema = import_zod2.z.object({
134
+ /**
135
+ * LangWatch API key for event reporting.
136
+ * If not provided, events will not be sent to LangWatch.
137
+ */
138
+ LANGWATCH_API_KEY: import_zod2.z.string().optional(),
139
+ /**
140
+ * LangWatch endpoint URL for event reporting.
141
+ * Defaults to the production LangWatch endpoint.
142
+ */
143
+ LANGWATCH_ENDPOINT: import_zod2.z.string().url().default("https://app.langwatch.ai"),
144
+ /**
145
+ * Disables simulation report info messages when set to any truthy value.
146
+ * Useful for CI/CD environments or when you want cleaner output.
147
+ */
148
+ SCENARIO_DISABLE_SIMULATION_REPORT_INFO: import_zod2.z.string().optional().transform((val) => Boolean(val)),
149
+ /**
150
+ * Node environment - affects logging and behavior.
151
+ * Defaults to 'development' if not specified.
152
+ */
153
+ NODE_ENV: import_zod2.z.enum(["development", "production", "test"]).default("development"),
154
+ /**
155
+ * Log level for the scenario package.
156
+ * Defaults to 'info' if not specified.
157
+ */
158
+ SCENARIO_LOG_LEVEL: import_zod2.z.nativeEnum(LogLevel).optional(),
159
+ /**
160
+ * Scenario batch run ID.
161
+ * If not provided, a random ID will be generated.
162
+ */
163
+ SCENARIO_BATCH_RUN_ID: import_zod2.z.string().optional()
164
+ });
165
+ var env = envSchema.parse(process.env);
166
+
167
+ // src/config/index.ts
168
+ var logger = new Logger("scenario.config");
169
+
170
+ // src/utils/ids.ts
171
+ var import_xksuid = require("xksuid");
172
+ function getBatchRunId() {
173
+ if (!env.SCENARIO_BATCH_RUN_ID) {
174
+ env.SCENARIO_BATCH_RUN_ID = `scenariobatchrun_${(0, import_xksuid.generate)()}`;
175
+ }
176
+ return env.SCENARIO_BATCH_RUN_ID;
177
+ }
178
+
179
+ // src/events/event-alert-message-logger.ts
180
+ var EventAlertMessageLogger = class _EventAlertMessageLogger {
181
+ static shownBatchIds = /* @__PURE__ */ new Set();
182
+ /**
183
+ * Shows a fancy greeting message about simulation reporting status.
184
+ * Only shows once per batch run to avoid spam.
185
+ */
186
+ handleGreeting() {
187
+ if (this.isGreetingDisabled()) {
188
+ return;
189
+ }
190
+ const batchRunId = getBatchRunId();
191
+ if (_EventAlertMessageLogger.shownBatchIds.has(batchRunId)) {
192
+ return;
118
193
  }
194
+ _EventAlertMessageLogger.shownBatchIds.add(batchRunId);
195
+ this.displayGreeting(batchRunId);
119
196
  }
120
197
  /**
121
- * Posts an event to the configured endpoint.
122
- * Logs success/failure but doesn't throw - event posting shouldn't break scenario execution.
198
+ * Shows a fancy message about how to watch the simulation.
199
+ * Called when a run started event is received with a session ID.
123
200
  */
124
- async postEvent(event) {
125
- this.logger.debug(`[${event.type}] Posting event`, {
126
- event
127
- });
128
- if (!this.eventsEndpoint) {
129
- this.logger.warn(
130
- "No LANGWATCH_ENDPOINT configured, skipping event posting"
131
- );
201
+ handleWatchMessage(params) {
202
+ if (this.isGreetingDisabled()) {
132
203
  return;
133
204
  }
134
- try {
135
- const response = await fetch(this.eventsEndpoint.href, {
136
- method: "POST",
137
- body: JSON.stringify(event),
138
- headers: {
139
- "Content-Type": "application/json",
140
- "X-Auth-Token": this.apiKey
141
- }
142
- });
143
- this.logger.debug(
144
- `[${event.type}] Event POST response status: ${response.status}`
205
+ this.displayWatchMessage(params);
206
+ }
207
+ isGreetingDisabled() {
208
+ return env.SCENARIO_DISABLE_SIMULATION_REPORT_INFO === true;
209
+ }
210
+ displayGreeting(batchRunId) {
211
+ const separator = "\u2500".repeat(60);
212
+ if (!env.LANGWATCH_API_KEY) {
213
+ console.log(`
214
+ ${separator}`);
215
+ console.log("\u{1F680} LangWatch Simulation Reporting");
216
+ console.log(`${separator}`);
217
+ console.log("\u27A1\uFE0F API key not configured");
218
+ console.log(" Simulations will only output final results");
219
+ console.log("");
220
+ console.log("\u{1F4A1} To visualize conversations in real time:");
221
+ console.log(" \u2022 Set LANGWATCH_API_KEY environment variable");
222
+ console.log(" \u2022 Or configure apiKey in scenario.config.js");
223
+ console.log("");
224
+ console.log(`\u{1F4E6} Batch Run ID: ${batchRunId}`);
225
+ console.log(`${separator}
226
+ `);
227
+ } else {
228
+ console.log(`
229
+ ${separator}`);
230
+ console.log("\u{1F680} LangWatch Simulation Reporting");
231
+ console.log(`${separator}`);
232
+ console.log("\u2705 Simulation reporting enabled");
233
+ console.log(` Endpoint: ${env.LANGWATCH_ENDPOINT}`);
234
+ console.log(
235
+ ` API Key: ${env.LANGWATCH_API_KEY.length > 0 ? "Configured" : "Not configured"}`
145
236
  );
146
- if (response.ok) {
147
- const data = await response.json();
148
- this.logger.debug(`[${event.type}] Event POST response:`, data);
149
- } else {
150
- const errorText = await response.text();
151
- this.logger.error(`[${event.type}] Event POST failed:`, {
152
- status: response.status,
153
- statusText: response.statusText,
154
- error: errorText,
155
- event
156
- });
157
- }
158
- } catch (error) {
159
- this.logger.error(`[${event.type}] Event POST error:`, {
160
- error,
161
- event,
162
- endpoint: this.eventsEndpoint
163
- });
237
+ console.log("");
238
+ console.log(`\u{1F4E6} Batch Run ID: ${batchRunId}`);
239
+ console.log(`${separator}
240
+ `);
164
241
  }
165
242
  }
243
+ displayWatchMessage(params) {
244
+ const separator = "\u2500".repeat(60);
245
+ const setUrl = params.setUrl;
246
+ const batchUrl = `${setUrl}/${getBatchRunId()}`;
247
+ console.log(`
248
+ ${separator}`);
249
+ console.log("\u{1F440} Watch Your Simulation Live");
250
+ console.log(`${separator}`);
251
+ console.log("\u{1F310} Open in your browser:");
252
+ console.log(` Scenario Set: ${setUrl}`);
253
+ console.log(` Batch Run: ${batchUrl}`);
254
+ console.log("");
255
+ console.log(`${separator}
256
+ `);
257
+ }
166
258
  };
167
259
 
168
260
  // src/events/schema.ts
169
261
  var import_core = require("@ag-ui/core");
170
- var import_zod = require("zod");
262
+ var import_zod3 = require("zod");
171
263
  var Verdict = /* @__PURE__ */ ((Verdict2) => {
172
264
  Verdict2["SUCCESS"] = "success";
173
265
  Verdict2["FAILURE"] = "failure";
@@ -183,70 +275,144 @@ var ScenarioRunStatus = /* @__PURE__ */ ((ScenarioRunStatus2) => {
183
275
  ScenarioRunStatus2["FAILED"] = "FAILED";
184
276
  return ScenarioRunStatus2;
185
277
  })(ScenarioRunStatus || {});
186
- var baseEventSchema = import_zod.z.object({
187
- type: import_zod.z.nativeEnum(import_core.EventType),
188
- timestamp: import_zod.z.number(),
189
- rawEvent: import_zod.z.any().optional()
278
+ var baseEventSchema = import_zod3.z.object({
279
+ type: import_zod3.z.nativeEnum(import_core.EventType),
280
+ timestamp: import_zod3.z.number(),
281
+ rawEvent: import_zod3.z.any().optional()
190
282
  });
191
- var batchRunIdSchema = import_zod.z.string();
192
- var scenarioRunIdSchema = import_zod.z.string();
193
- var scenarioIdSchema = import_zod.z.string();
283
+ var batchRunIdSchema = import_zod3.z.string();
284
+ var scenarioRunIdSchema = import_zod3.z.string();
285
+ var scenarioIdSchema = import_zod3.z.string();
194
286
  var baseScenarioEventSchema = baseEventSchema.extend({
195
287
  batchRunId: batchRunIdSchema,
196
288
  scenarioId: scenarioIdSchema,
197
289
  scenarioRunId: scenarioRunIdSchema,
198
- scenarioSetId: import_zod.z.string().optional().default("default")
290
+ scenarioSetId: import_zod3.z.string().optional().default("default")
199
291
  });
200
292
  var scenarioRunStartedSchema = baseScenarioEventSchema.extend({
201
- type: import_zod.z.literal("SCENARIO_RUN_STARTED" /* RUN_STARTED */),
202
- metadata: import_zod.z.object({
203
- name: import_zod.z.string().optional(),
204
- description: import_zod.z.string().optional()
293
+ type: import_zod3.z.literal("SCENARIO_RUN_STARTED" /* RUN_STARTED */),
294
+ metadata: import_zod3.z.object({
295
+ name: import_zod3.z.string().optional(),
296
+ description: import_zod3.z.string().optional()
205
297
  })
206
298
  });
207
- var scenarioResultsSchema = import_zod.z.object({
208
- verdict: import_zod.z.nativeEnum(Verdict),
209
- reasoning: import_zod.z.string().optional(),
210
- metCriteria: import_zod.z.array(import_zod.z.string()),
211
- unmetCriteria: import_zod.z.array(import_zod.z.string()),
212
- error: import_zod.z.string().optional()
299
+ var scenarioResultsSchema = import_zod3.z.object({
300
+ verdict: import_zod3.z.nativeEnum(Verdict),
301
+ reasoning: import_zod3.z.string().optional(),
302
+ metCriteria: import_zod3.z.array(import_zod3.z.string()),
303
+ unmetCriteria: import_zod3.z.array(import_zod3.z.string()),
304
+ error: import_zod3.z.string().optional()
213
305
  });
214
306
  var scenarioRunFinishedSchema = baseScenarioEventSchema.extend({
215
- type: import_zod.z.literal("SCENARIO_RUN_FINISHED" /* RUN_FINISHED */),
216
- status: import_zod.z.nativeEnum(ScenarioRunStatus),
307
+ type: import_zod3.z.literal("SCENARIO_RUN_FINISHED" /* RUN_FINISHED */),
308
+ status: import_zod3.z.nativeEnum(ScenarioRunStatus),
217
309
  results: scenarioResultsSchema.optional().nullable()
218
310
  });
219
311
  var scenarioMessageSnapshotSchema = import_core.MessagesSnapshotEventSchema.merge(
220
312
  baseScenarioEventSchema.extend({
221
- type: import_zod.z.literal("SCENARIO_MESSAGE_SNAPSHOT" /* MESSAGE_SNAPSHOT */)
313
+ type: import_zod3.z.literal("SCENARIO_MESSAGE_SNAPSHOT" /* MESSAGE_SNAPSHOT */)
222
314
  })
223
315
  );
224
- var scenarioEventSchema = import_zod.z.discriminatedUnion("type", [
316
+ var scenarioEventSchema = import_zod3.z.discriminatedUnion("type", [
225
317
  scenarioRunStartedSchema,
226
318
  scenarioRunFinishedSchema,
227
319
  scenarioMessageSnapshotSchema
228
320
  ]);
229
- var successSchema = import_zod.z.object({ success: import_zod.z.boolean() });
230
- var errorSchema = import_zod.z.object({ error: import_zod.z.string() });
231
- var stateSchema = import_zod.z.object({
232
- state: import_zod.z.object({
233
- messages: import_zod.z.array(import_zod.z.any()),
234
- status: import_zod.z.string()
321
+ var successSchema = import_zod3.z.object({ success: import_zod3.z.boolean() });
322
+ var errorSchema = import_zod3.z.object({ error: import_zod3.z.string() });
323
+ var stateSchema = import_zod3.z.object({
324
+ state: import_zod3.z.object({
325
+ messages: import_zod3.z.array(import_zod3.z.any()),
326
+ status: import_zod3.z.string()
235
327
  })
236
328
  });
237
- var runsSchema = import_zod.z.object({ runs: import_zod.z.array(import_zod.z.string()) });
238
- var eventsSchema = import_zod.z.object({ events: import_zod.z.array(scenarioEventSchema) });
329
+ var runsSchema = import_zod3.z.object({ runs: import_zod3.z.array(import_zod3.z.string()) });
330
+ var eventsSchema = import_zod3.z.object({ events: import_zod3.z.array(scenarioEventSchema) });
331
+
332
+ // src/events/event-reporter.ts
333
+ var EventReporter = class {
334
+ apiKey;
335
+ eventsEndpoint;
336
+ eventAlertMessageLogger;
337
+ logger = new Logger("scenario.events.EventReporter");
338
+ constructor(config) {
339
+ this.apiKey = config.apiKey ?? "";
340
+ this.eventsEndpoint = new URL("/api/scenario-events", config.endpoint);
341
+ this.eventAlertMessageLogger = new EventAlertMessageLogger();
342
+ this.eventAlertMessageLogger.handleGreeting();
343
+ }
344
+ /**
345
+ * Posts an event to the configured endpoint.
346
+ * Logs success/failure but doesn't throw - event posting shouldn't break scenario execution.
347
+ */
348
+ async postEvent(event) {
349
+ const result = {};
350
+ this.logger.debug(`[${event.type}] Posting event`, { event });
351
+ const processedEvent = this.processEventForApi(event);
352
+ try {
353
+ const response = await fetch(this.eventsEndpoint.href, {
354
+ method: "POST",
355
+ body: JSON.stringify(processedEvent),
356
+ headers: {
357
+ "Content-Type": "application/json",
358
+ "X-Auth-Token": this.apiKey
359
+ }
360
+ });
361
+ this.logger.debug(
362
+ `[${event.type}] Event POST response status: ${response.status}`
363
+ );
364
+ if (response.ok) {
365
+ const data = await response.json();
366
+ this.logger.debug(`[${event.type}] Event POST response:`, data);
367
+ result.setUrl = data.url;
368
+ } else {
369
+ const errorText = await response.text();
370
+ this.logger.error(`[${event.type}] Event POST failed:`, {
371
+ status: response.status,
372
+ statusText: response.statusText,
373
+ error: errorText,
374
+ event: JSON.stringify(processedEvent)
375
+ });
376
+ }
377
+ } catch (error) {
378
+ this.logger.error(`[${event.type}] Event POST error:`, {
379
+ error,
380
+ event: JSON.stringify(processedEvent),
381
+ endpoint: this.eventsEndpoint.href
382
+ });
383
+ }
384
+ return result;
385
+ }
386
+ /**
387
+ * Processes event data to ensure API compatibility.
388
+ * Converts message content objects to strings when needed.
389
+ */
390
+ processEventForApi(event) {
391
+ if (event.type === "SCENARIO_MESSAGE_SNAPSHOT" /* MESSAGE_SNAPSHOT */) {
392
+ return {
393
+ ...event,
394
+ messages: event.messages.map((message) => ({
395
+ ...message,
396
+ content: typeof message.content !== "string" ? JSON.stringify(message.content) : message.content
397
+ }))
398
+ };
399
+ }
400
+ return event;
401
+ }
402
+ };
239
403
 
240
404
  // src/events/event-bus.ts
241
405
  var EventBus = class _EventBus {
242
406
  static registry = /* @__PURE__ */ new Set();
243
407
  events$ = new import_rxjs.Subject();
244
408
  eventReporter;
409
+ eventAlertMessageLogger;
245
410
  processingPromise = null;
246
411
  logger = new Logger("scenario.events.EventBus");
247
412
  static globalListeners = [];
248
413
  constructor(config) {
249
414
  this.eventReporter = new EventReporter(config);
415
+ this.eventAlertMessageLogger = new EventAlertMessageLogger();
250
416
  _EventBus.registry.add(this);
251
417
  for (const listener of _EventBus.globalListeners) {
252
418
  listener(this);
@@ -278,22 +444,31 @@ var EventBus = class _EventBus {
278
444
  }
279
445
  this.processingPromise = new Promise((resolve, reject) => {
280
446
  this.events$.pipe(
447
+ // Post events and get results
281
448
  (0, import_rxjs.concatMap)(async (event) => {
282
- this.logger.debug(`[${event.type}] Processing event`, {
283
- event
284
- });
285
- await this.eventReporter.postEvent(event);
286
- return event;
449
+ this.logger.debug(`[${event.type}] Processing event`, { event });
450
+ const result = await this.eventReporter.postEvent(event);
451
+ return { event, result };
452
+ }),
453
+ // Handle watch messages reactively
454
+ (0, import_rxjs.tap)(({ event, result }) => {
455
+ if (event.type === "SCENARIO_RUN_STARTED" /* RUN_STARTED */ && result.setUrl) {
456
+ this.eventAlertMessageLogger.handleWatchMessage({
457
+ scenarioSetId: event.scenarioSetId,
458
+ scenarioRunId: event.scenarioRunId,
459
+ setUrl: result.setUrl
460
+ });
461
+ }
287
462
  }),
463
+ // Extract just the event for downstream processing
464
+ (0, import_rxjs.map)(({ event }) => event),
288
465
  (0, import_rxjs.catchError)((error) => {
289
466
  this.logger.error("Error in event stream:", error);
290
467
  return import_rxjs.EMPTY;
291
468
  })
292
469
  ).subscribe({
293
470
  next: (event) => {
294
- this.logger.debug(`[${event.type}] Event processed`, {
295
- event
296
- });
471
+ this.logger.debug(`[${event.type}] Event processed`, { event });
297
472
  if (event.type === "SCENARIO_RUN_FINISHED" /* RUN_FINISHED */) {
298
473
  resolve();
299
474
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  EventBus
3
- } from "../../chunk-ORWSJC5F.mjs";
3
+ } from "../../chunk-ZMHTHRDR.mjs";
4
4
  import "../../chunk-7P6ASYW6.mjs";
5
5
 
6
6
  // src/integrations/vitest/setup.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langwatch/scenario",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A TypeScript library for testing AI agents using scenarios",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -84,7 +84,7 @@
84
84
  "test": "vitest",
85
85
  "test:ci": "vitest run",
86
86
  "lint": "eslint .",
87
- "examples:vitest:run": "export SCENARIO_BATCH_ID=scenariobatch_$(uuidgen) && pnpm run buildpack && (cd examples/vitest && pnpm install) && pnpm -F vitest run test",
87
+ "examples:vitest:run": "export SCENARIO_BATCH_ID=scenariobatch_$(uuidgen) && pnpm run buildpack && (cd examples/vitest && pnpm install) && pnpm -F vitest-example run test",
88
88
  "generate:api-reference": "npx typedoc src --out api-reference-docs"
89
89
  }
90
90
  }