@stacksee/analytics 0.13.2 → 0.14.0

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.
@@ -5,6 +5,8 @@ export { BentoClientProvider } from './bento/client.js';
5
5
  export type { BentoClientConfig } from './bento/client.js';
6
6
  export { PirschClientProvider } from './pirsch/client.js';
7
7
  export type { PirschClientConfig } from './pirsch/client.js';
8
+ export { VisitorsClientProvider } from './visitors/client.js';
9
+ export type { VisitorsClientConfig } from './visitors/client.js';
8
10
  export { ProxyProvider } from './proxy/client.js';
9
11
  export type { ProxyProviderConfig } from './proxy/client.js';
10
12
  export type { ProxyBatchConfig, ProxyRetryConfig, ProxyEvent, ProxyPayload, ProxyTrackEvent, ProxyIdentifyEvent, ProxyPageViewEvent, ProxyResetEvent, } from './proxy/types.js';
@@ -1,10 +1,10 @@
1
- var f = Object.defineProperty;
2
- var p = (o, h, e) => h in o ? f(o, h, { enumerable: !0, configurable: !0, writable: !0, value: e }) : o[h] = e;
3
- var r = (o, h, e) => p(o, typeof h != "symbol" ? h + "" : h, e);
4
- import { B as u } from "../base.provider-AfFL5W_P.js";
5
- import { i as a } from "../client-DTHZYkxx.js";
6
- import { P as z } from "../client-DTHZYkxx.js";
7
- class w extends u {
1
+ var g = Object.defineProperty;
2
+ var p = (o, l, e) => l in o ? g(o, l, { enumerable: !0, configurable: !0, writable: !0, value: e }) : o[l] = e;
3
+ var r = (o, l, e) => p(o, typeof l != "symbol" ? l + "" : l, e);
4
+ import { B as f } from "../base.provider-AfFL5W_P.js";
5
+ import { i as n } from "../client-DTHZYkxx.js";
6
+ import { P as C } from "../client-DTHZYkxx.js";
7
+ class w extends f {
8
8
  constructor(e) {
9
9
  super({ debug: e.debug, enabled: e.enabled });
10
10
  r(this, "name", "Bento-Client");
@@ -16,7 +16,7 @@ class w extends u {
16
16
  }
17
17
  async initialize() {
18
18
  if (this.isEnabled() && !this.initialized) {
19
- if (!a()) {
19
+ if (!n()) {
20
20
  this.log("Skipping initialization - not in browser environment");
21
21
  return;
22
22
  }
@@ -63,7 +63,7 @@ class w extends u {
63
63
  this.log("Identified user", { userId: e, email: s, traits: i });
64
64
  }
65
65
  track(e, i) {
66
- var t, n, l;
66
+ var t, a, h;
67
67
  if (!this.isEnabled() || !this.initialized || !this.bento) return;
68
68
  const s = {
69
69
  ...e.properties,
@@ -86,14 +86,14 @@ class w extends u {
86
86
  ...(i == null ? void 0 : i.utm) && { utm: i.utm },
87
87
  // Include user email and traits as regular event properties
88
88
  ...((t = i == null ? void 0 : i.user) == null ? void 0 : t.email) && { user_email: i.user.email },
89
- ...((n = i == null ? void 0 : i.user) == null ? void 0 : n.traits) && { user_traits: i.user.traits },
90
- ...((l = i == null ? void 0 : i.user) == null ? void 0 : l.userId) && { visitor: i.user.userId }
89
+ ...((a = i == null ? void 0 : i.user) == null ? void 0 : a.traits) && { user_traits: i.user.traits },
90
+ ...((h = i == null ? void 0 : i.user) == null ? void 0 : h.userId) && { visitor: i.user.userId }
91
91
  };
92
92
  this.bento.track(e.action, s), this.log("Tracked event", { event: e, context: i });
93
93
  }
94
94
  pageView(e, i) {
95
95
  var s;
96
- if (!(!this.isEnabled() || !this.initialized || !this.bento || !a())) {
96
+ if (!(!this.isEnabled() || !this.initialized || !this.bento || !n())) {
97
97
  if (this.bento.view(), e || i != null && i.page) {
98
98
  const t = {
99
99
  ...e,
@@ -118,7 +118,7 @@ class w extends u {
118
118
  }
119
119
  pageLeave(e, i) {
120
120
  var t;
121
- if (!this.isEnabled() || !this.initialized || !this.bento || !a())
121
+ if (!this.isEnabled() || !this.initialized || !this.bento || !n())
122
122
  return;
123
123
  const s = {
124
124
  ...e,
@@ -139,7 +139,7 @@ class w extends u {
139
139
  this.bento.track("$pageleave", s), this.log("Tracked page leave", { properties: e, context: i });
140
140
  }
141
141
  reset() {
142
- !this.isEnabled() || !this.initialized || !this.bento || !a() || this.log(
142
+ !this.isEnabled() || !this.initialized || !this.bento || !n() || this.log(
143
143
  "Reset user session - Note: Bento doesn't have a native reset method"
144
144
  );
145
145
  }
@@ -156,7 +156,7 @@ class w extends u {
156
156
  * ```
157
157
  */
158
158
  tag(e) {
159
- !this.isEnabled() || !this.initialized || !this.bento || !a() || (this.bento.tag(e), this.log("Added tag to user", { tag: e }));
159
+ !this.isEnabled() || !this.initialized || !this.bento || !n() || (this.bento.tag(e), this.log("Added tag to user", { tag: e }));
160
160
  }
161
161
  /**
162
162
  * Get the current user's email address
@@ -172,7 +172,7 @@ class w extends u {
172
172
  * ```
173
173
  */
174
174
  getEmail() {
175
- return !this.isEnabled() || !this.initialized || !this.bento || !a() ? null : this.bento.getEmail();
175
+ return !this.isEnabled() || !this.initialized || !this.bento || !n() ? null : this.bento.getEmail();
176
176
  }
177
177
  /**
178
178
  * Get the current user's name
@@ -188,7 +188,7 @@ class w extends u {
188
188
  * ```
189
189
  */
190
190
  getName() {
191
- return !this.isEnabled() || !this.initialized || !this.bento || !a() ? null : this.bento.getName();
191
+ return !this.isEnabled() || !this.initialized || !this.bento || !n() ? null : this.bento.getName();
192
192
  }
193
193
  // ============================================================================
194
194
  // Survey Methods
@@ -209,7 +209,7 @@ class w extends u {
209
209
  * ```
210
210
  */
211
211
  showSurveyForm(e, i, s = "popup") {
212
- !this.isEnabled() || !this.initialized || !this.bento || !a() || (this.bento.showSurveyForm(e, i, s), this.log("Showed survey form", { surveyId: i, type: s }));
212
+ !this.isEnabled() || !this.initialized || !this.bento || !n() || (this.bento.showSurveyForm(e, i, s), this.log("Showed survey form", { surveyId: i, type: s }));
213
213
  }
214
214
  /**
215
215
  * Validate an email address using Bento's spam check
@@ -226,7 +226,7 @@ class w extends u {
226
226
  * ```
227
227
  */
228
228
  async spamCheck(e) {
229
- if (!this.isEnabled() || !this.initialized || !this.bento || !a())
229
+ if (!this.isEnabled() || !this.initialized || !this.bento || !n())
230
230
  return !1;
231
231
  try {
232
232
  const i = await this.bento.spamCheck(e);
@@ -247,7 +247,7 @@ class w extends u {
247
247
  * ```
248
248
  */
249
249
  showChat() {
250
- !this.isEnabled() || !this.initialized || !this.bento || !a() || (this.bento.showChat ? (this.bento.showChat(), this.log("Showed chat widget")) : console.warn(
250
+ !this.isEnabled() || !this.initialized || !this.bento || !n() || (this.bento.showChat ? (this.bento.showChat(), this.log("Showed chat widget")) : console.warn(
251
251
  "[Bento-Client] Chat not available. Make sure chat is enabled in your Bento settings."
252
252
  ));
253
253
  }
@@ -260,7 +260,7 @@ class w extends u {
260
260
  * ```
261
261
  */
262
262
  hideChat() {
263
- !this.isEnabled() || !this.initialized || !this.bento || !a() || (this.bento.hideChat ? (this.bento.hideChat(), this.log("Hid chat widget")) : console.warn(
263
+ !this.isEnabled() || !this.initialized || !this.bento || !n() || (this.bento.hideChat ? (this.bento.hideChat(), this.log("Hid chat widget")) : console.warn(
264
264
  "[Bento-Client] Chat not available. Make sure chat is enabled in your Bento settings."
265
265
  ));
266
266
  }
@@ -273,12 +273,12 @@ class w extends u {
273
273
  * ```
274
274
  */
275
275
  openChat() {
276
- !this.isEnabled() || !this.initialized || !this.bento || !a() || (this.bento.openChat ? (this.bento.openChat(), this.log("Opened chat widget")) : console.warn(
276
+ !this.isEnabled() || !this.initialized || !this.bento || !n() || (this.bento.openChat ? (this.bento.openChat(), this.log("Opened chat widget")) : console.warn(
277
277
  "[Bento-Client] Chat not available. Make sure chat is enabled in your Bento settings."
278
278
  ));
279
279
  }
280
280
  }
281
- class m extends u {
281
+ class m extends f {
282
282
  constructor(e) {
283
283
  super({ debug: e.debug, enabled: e.enabled });
284
284
  r(this, "name", "Pirsch-Client");
@@ -289,7 +289,7 @@ class m extends u {
289
289
  }
290
290
  async initialize() {
291
291
  if (this.isEnabled() && !this.initialized) {
292
- if (!a()) {
292
+ if (!n()) {
293
293
  this.log("Skipping initialization - not in browser environment");
294
294
  return;
295
295
  }
@@ -319,7 +319,7 @@ class m extends u {
319
319
  }), this.log("Identified user via event", { userId: e, traits: i }));
320
320
  }
321
321
  async track(e, i) {
322
- var t, n;
322
+ var t, a;
323
323
  if (!this.isEnabled() || !this.initialized || !this.client) return;
324
324
  const s = {
325
325
  ...e.properties,
@@ -334,33 +334,33 @@ class m extends u {
334
334
  ...(i == null ? void 0 : i.device) && { device: i.device },
335
335
  ...(i == null ? void 0 : i.utm) && { utm: i.utm },
336
336
  ...((t = i == null ? void 0 : i.user) == null ? void 0 : t.email) && { user_email: i.user.email },
337
- ...((n = i == null ? void 0 : i.user) == null ? void 0 : n.traits) && { user_traits: i.user.traits }
337
+ ...((a = i == null ? void 0 : i.user) == null ? void 0 : a.traits) && { user_traits: i.user.traits }
338
338
  };
339
339
  try {
340
340
  await this.client.event(e.action, 0, s), this.log("Tracked event", { event: e, context: i });
341
- } catch (l) {
342
- console.error("[Pirsch-Client] Failed to track event:", l);
341
+ } catch (h) {
342
+ console.error("[Pirsch-Client] Failed to track event:", h);
343
343
  }
344
344
  }
345
345
  pageView(e, i) {
346
- var l, g;
347
- if (!this.isEnabled() || !this.initialized || !this.client || !a())
346
+ var h, d;
347
+ if (!this.isEnabled() || !this.initialized || !this.client || !n())
348
348
  return;
349
349
  const s = e && Object.keys(e).length > 0 ? Object.fromEntries(
350
350
  Object.entries(e).filter(
351
- ([, d]) => typeof d == "string" || typeof d == "number" || typeof d == "boolean"
351
+ ([, u]) => typeof u == "string" || typeof u == "number" || typeof u == "boolean"
352
352
  )
353
353
  ) : void 0, t = {
354
- ...((l = i == null ? void 0 : i.page) == null ? void 0 : l.url) && { url: i.page.url },
355
- ...((g = i == null ? void 0 : i.page) == null ? void 0 : g.title) && { title: i.page.title },
354
+ ...((h = i == null ? void 0 : i.page) == null ? void 0 : h.url) && { url: i.page.url },
355
+ ...((d = i == null ? void 0 : i.page) == null ? void 0 : d.title) && { title: i.page.title },
356
356
  ...s && { tags: s }
357
- }, n = Object.keys(t).length > 0 ? t : void 0;
358
- this.client.hit(n).catch((d) => {
359
- console.error("[Pirsch-Client] Failed to track page view:", d);
357
+ }, a = Object.keys(t).length > 0 ? t : void 0;
358
+ this.client.hit(a).catch((u) => {
359
+ console.error("[Pirsch-Client] Failed to track page view:", u);
360
360
  }), this.log("Tracked page view", { properties: e, context: i });
361
361
  }
362
362
  pageLeave(e, i) {
363
- if (!this.isEnabled() || !this.initialized || !this.client || !a())
363
+ if (!this.isEnabled() || !this.initialized || !this.client || !n())
364
364
  return;
365
365
  const s = {
366
366
  ...e,
@@ -374,14 +374,127 @@ class m extends u {
374
374
  }), this.log("Tracked page leave", { properties: e, context: i });
375
375
  }
376
376
  reset() {
377
- !this.isEnabled() || !this.initialized || !this.client || !a() || (this.client.event("session_reset", 0, {}).catch((e) => {
377
+ !this.isEnabled() || !this.initialized || !this.client || !n() || (this.client.event("session_reset", 0, {}).catch((e) => {
378
378
  console.error("[Pirsch-Client] Failed to track session reset:", e);
379
379
  }), this.log("Reset user session"));
380
380
  }
381
381
  }
382
- class v extends u {
382
+ class v extends f {
383
383
  constructor(e) {
384
- var i, s, t, n, l;
384
+ super({ debug: e.debug, enabled: e.enabled });
385
+ r(this, "name", "Visitors-Client");
386
+ r(this, "visitors");
387
+ r(this, "initialized", !1);
388
+ r(this, "config");
389
+ r(this, "scriptLoaded", !1);
390
+ this.config = e;
391
+ }
392
+ async initialize() {
393
+ if (this.isEnabled() && !this.initialized) {
394
+ if (!n()) {
395
+ this.log("Skipping initialization - not in browser environment");
396
+ return;
397
+ }
398
+ if (!this.config.token || typeof this.config.token != "string")
399
+ throw new Error("Visitors requires a token");
400
+ try {
401
+ this.scriptLoaded || await this.loadScript(), await this.waitForVisitors(), this.visitors = window.visitors, this.initialized = !0, this.log("Initialized successfully", this.config);
402
+ } catch (e) {
403
+ throw console.error("[Visitors-Client] Failed to initialize:", e), e;
404
+ }
405
+ }
406
+ }
407
+ async loadScript() {
408
+ return new Promise((e, i) => {
409
+ if (document.querySelector(
410
+ 'script[src*="cdn.visitors.now"]'
411
+ )) {
412
+ this.scriptLoaded = !0, e();
413
+ return;
414
+ }
415
+ const t = document.createElement("script");
416
+ t.src = "https://cdn.visitors.now/v.js", t.setAttribute("data-token", this.config.token), t.async = !0, t.defer = !0, t.onload = () => {
417
+ this.scriptLoaded = !0, e();
418
+ }, t.onerror = () => {
419
+ i(new Error("Failed to load Visitors script"));
420
+ }, document.head.appendChild(t);
421
+ });
422
+ }
423
+ async waitForVisitors(e = 50, i = 100) {
424
+ for (let s = 0; s < e; s++) {
425
+ if (window.visitors)
426
+ return;
427
+ await new Promise((t) => setTimeout(t, i));
428
+ }
429
+ throw new Error("Visitors SDK not available after loading script");
430
+ }
431
+ identify(e, i) {
432
+ if (!this.isEnabled() || !this.initialized || !this.visitors) return;
433
+ const s = { id: e };
434
+ if (i)
435
+ for (const [t, a] of Object.entries(i))
436
+ (typeof a == "string" || typeof a == "number") && (s[t] = a);
437
+ this.visitors.identify(s), this.log("Identified user", { userId: e, traits: i });
438
+ }
439
+ track(e, i) {
440
+ var t, a;
441
+ if (!this.isEnabled() || !this.initialized || !this.visitors) return;
442
+ const s = {};
443
+ if (e.properties)
444
+ for (const [h, d] of Object.entries(e.properties))
445
+ (typeof d == "string" || typeof d == "number") && (s[h] = d);
446
+ e.category && (s.category = e.category), (t = i == null ? void 0 : i.page) != null && t.path && (s.page_path = i.page.path), (a = i == null ? void 0 : i.page) != null && a.title && (s.page_title = i.page.title), this.visitors.track(e.action, s), this.log("Tracked event", { event: e, context: i });
447
+ }
448
+ pageView(e, i) {
449
+ this.log("Page view - handled automatically by Visitors script", {
450
+ properties: e,
451
+ context: i
452
+ });
453
+ }
454
+ pageLeave(e, i) {
455
+ var t;
456
+ if (!this.isEnabled() || !this.initialized || !this.visitors || !n())
457
+ return;
458
+ const s = {};
459
+ if ((t = i == null ? void 0 : i.page) != null && t.path && (s.page_path = i.page.path), e)
460
+ for (const [a, h] of Object.entries(e))
461
+ (typeof h == "string" || typeof h == "number") && (s[a] = h);
462
+ this.visitors.track("page_leave", s), this.log("Tracked page leave", { properties: e, context: i });
463
+ }
464
+ reset() {
465
+ !this.isEnabled() || !this.initialized || !this.visitors || !n() || this.log("Reset user session - Note: Visitors does not have a native reset method");
466
+ }
467
+ // ============================================================================
468
+ // Stripe Revenue Attribution
469
+ // ============================================================================
470
+ /**
471
+ * Returns the current visitor ID from the `visitor` cookie set by the
472
+ * visitors.now script. Pass this value in your Stripe checkout session
473
+ * metadata so revenue is attributed to the correct visitor.
474
+ *
475
+ * Requires persist mode to be enabled in your visitors.now project settings.
476
+ *
477
+ * @example Server-side Stripe checkout creation:
478
+ * ```typescript
479
+ * // Client-side: get visitor ID and send to your server
480
+ * const visitorId = visitorsProvider.getVisitorId();
481
+ *
482
+ * // Server-side: include in Stripe checkout session
483
+ * const session = await stripe.checkout.sessions.create({
484
+ * // ...
485
+ * metadata: { visitor: visitorId },
486
+ * });
487
+ * ```
488
+ */
489
+ getVisitorId() {
490
+ if (!n()) return null;
491
+ const e = document.cookie.split("; ").find((i) => i.startsWith("visitor="));
492
+ return e ? decodeURIComponent(e.split("=")[1]) : null;
493
+ }
494
+ }
495
+ class k extends f {
496
+ constructor(e) {
497
+ var i, s, t, a, h;
385
498
  super({ debug: e.debug, enabled: e.enabled });
386
499
  r(this, "name", "Proxy");
387
500
  r(this, "config");
@@ -392,7 +505,7 @@ class v extends u {
392
505
  r(this, "retryAttempts");
393
506
  r(this, "retryBackoff");
394
507
  r(this, "retryInitialDelay");
395
- this.config = e, this.batchSize = ((i = e.batch) == null ? void 0 : i.size) ?? 10, this.batchInterval = ((s = e.batch) == null ? void 0 : s.interval) ?? 2e3, this.retryAttempts = ((t = e.retry) == null ? void 0 : t.attempts) ?? 3, this.retryBackoff = ((n = e.retry) == null ? void 0 : n.backoff) ?? "exponential", this.retryInitialDelay = ((l = e.retry) == null ? void 0 : l.initialDelay) ?? 1e3, typeof window < "u" && (window.addEventListener("beforeunload", () => {
508
+ this.config = e, this.batchSize = ((i = e.batch) == null ? void 0 : i.size) ?? 10, this.batchInterval = ((s = e.batch) == null ? void 0 : s.interval) ?? 2e3, this.retryAttempts = ((t = e.retry) == null ? void 0 : t.attempts) ?? 3, this.retryBackoff = ((a = e.retry) == null ? void 0 : a.backoff) ?? "exponential", this.retryInitialDelay = ((h = e.retry) == null ? void 0 : h.initialDelay) ?? 1e3, typeof window < "u" && (window.addEventListener("beforeunload", () => {
396
509
  this.flush(!0);
397
510
  }), document.addEventListener("visibilitychange", () => {
398
511
  document.visibilityState === "hidden" && this.flush(!0);
@@ -480,7 +593,7 @@ class v extends u {
480
593
  } catch (s) {
481
594
  if (i < this.retryAttempts) {
482
595
  const t = this.calculateRetryDelay(i);
483
- return this.log(`Retry attempt ${i + 1} after ${t}ms`, { error: s }), await new Promise((n) => setTimeout(n, t)), this.sendWithRetry(e, i + 1);
596
+ return this.log(`Retry attempt ${i + 1} after ${t}ms`, { error: s }), await new Promise((a) => setTimeout(a, t)), this.sendWithRetry(e, i + 1);
484
597
  }
485
598
  throw console.error("[Proxy] Failed to send events after retries:", s), s;
486
599
  }
@@ -519,9 +632,10 @@ class v extends u {
519
632
  }
520
633
  }
521
634
  export {
522
- u as BaseAnalyticsProvider,
635
+ f as BaseAnalyticsProvider,
523
636
  w as BentoClientProvider,
524
637
  m as PirschClientProvider,
525
- z as PostHogClientProvider,
526
- v as ProxyProvider
638
+ C as PostHogClientProvider,
639
+ k as ProxyProvider,
640
+ v as VisitorsClientProvider
527
641
  };
@@ -0,0 +1,67 @@
1
+ import { BaseEvent, EventContext } from '../../core/events/types.js';
2
+ import { BaseAnalyticsProvider } from '../base.provider.js';
3
+ interface VisitorsClient {
4
+ track(event: string, properties?: Record<string, string | number>): void;
5
+ identify(traits: {
6
+ id: string;
7
+ email?: string;
8
+ name?: string;
9
+ [key: string]: string | number | undefined;
10
+ }): void;
11
+ }
12
+ export interface VisitorsClientConfig {
13
+ /**
14
+ * Your Visitors project token from the dashboard
15
+ */
16
+ token: string;
17
+ /**
18
+ * Enable debug logging
19
+ */
20
+ debug?: boolean;
21
+ /**
22
+ * Enable/disable the provider
23
+ */
24
+ enabled?: boolean;
25
+ }
26
+ declare global {
27
+ interface Window {
28
+ visitors?: VisitorsClient;
29
+ }
30
+ }
31
+ export declare class VisitorsClientProvider extends BaseAnalyticsProvider {
32
+ name: string;
33
+ private visitors?;
34
+ private initialized;
35
+ private config;
36
+ private scriptLoaded;
37
+ constructor(config: VisitorsClientConfig);
38
+ initialize(): Promise<void>;
39
+ private loadScript;
40
+ private waitForVisitors;
41
+ identify(userId: string, traits?: Record<string, unknown>): void;
42
+ track(event: BaseEvent, context?: EventContext): void;
43
+ pageView(properties?: Record<string, unknown>, context?: EventContext): void;
44
+ pageLeave(properties?: Record<string, unknown>, context?: EventContext): void;
45
+ reset(): void;
46
+ /**
47
+ * Returns the current visitor ID from the `visitor` cookie set by the
48
+ * visitors.now script. Pass this value in your Stripe checkout session
49
+ * metadata so revenue is attributed to the correct visitor.
50
+ *
51
+ * Requires persist mode to be enabled in your visitors.now project settings.
52
+ *
53
+ * @example Server-side Stripe checkout creation:
54
+ * ```typescript
55
+ * // Client-side: get visitor ID and send to your server
56
+ * const visitorId = visitorsProvider.getVisitorId();
57
+ *
58
+ * // Server-side: include in Stripe checkout session
59
+ * const session = await stripe.checkout.sessions.create({
60
+ * // ...
61
+ * metadata: { visitor: visitorId },
62
+ * });
63
+ * ```
64
+ */
65
+ getVisitorId(): string | null;
66
+ }
67
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stacksee/analytics",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "A highly typed, provider-agnostic analytics library for TypeScript applications",
5
5
  "type": "module",
6
6
  "exports": {
@@ -79,6 +79,8 @@
79
79
  "test:watch": "vitest",
80
80
  "test:e2e": "node e2e/pirsch.test.js",
81
81
  "test:e2e:server": "node e2e/test-app/server.js",
82
+ "test:e2e:visitors": "node e2e/visitors.test.js",
83
+ "test:e2e:visitors:server": "node e2e/visitors-test-app/server.js",
82
84
  "build": "vite build",
83
85
  "dev": "pnpm --filter @stacksee/docs dev",
84
86
  "build:docs": "pnpm --filter @stacksee/docs build",