@vibe-flags/core 0.1.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.
@@ -0,0 +1,647 @@
1
+ import { LitElement as b, html as a, nothing as u, css as w } from "lit";
2
+ import { customElement as x, property as f, state as h } from "lit/decorators.js";
3
+ var m = Object.getOwnPropertyDescriptor, k = (r, e, t, s) => {
4
+ for (var o = s > 1 ? void 0 : s ? m(e, t) : e, i = r.length - 1, n; i >= 0; i--)
5
+ (n = r[i]) && (o = n(o) || o);
6
+ return o;
7
+ };
8
+ let y = class extends b {
9
+ render() {
10
+ return a`<slot></slot>`;
11
+ }
12
+ };
13
+ y = k([
14
+ x("vibe-flags")
15
+ ], y);
16
+ const g = "vibe-flags:";
17
+ class S extends EventTarget {
18
+ constructor() {
19
+ super(...arguments), this.configs = /* @__PURE__ */ new Map(), this.state = {}, this.listening = !1, this.onStorageEvent = (e) => {
20
+ if (!e.key?.startsWith(g)) return;
21
+ const t = e.key.slice(g.length);
22
+ if (this.configs.has(t))
23
+ try {
24
+ const s = e.newValue ? JSON.parse(e.newValue) : this.configs.get(t).default;
25
+ this.state[t] = s, this.dispatch(t);
26
+ } catch {
27
+ }
28
+ };
29
+ }
30
+ register(e) {
31
+ this.configs.set(e.key, e);
32
+ const t = this.readFromStorage(e.key);
33
+ this.state[e.key] = t ?? e.default, this.listening || (this.listening = !0, typeof window < "u" && window.addEventListener("storage", this.onStorageEvent)), this.dispatch(e.key);
34
+ }
35
+ unregister(e) {
36
+ this.configs.delete(e), delete this.state[e], this.dispatch();
37
+ }
38
+ get(e) {
39
+ return this.state[e];
40
+ }
41
+ set(e, t) {
42
+ const s = this.configs.get(e);
43
+ s && (s.type === "boolean" && typeof t != "boolean" || s.type === "select" && (typeof t != "string" || !s.options.includes(t)) || (this.state[e] = t, this.writeToStorage(e, t), this.dispatch(e)));
44
+ }
45
+ getAll() {
46
+ return { ...this.state };
47
+ }
48
+ getConfig() {
49
+ return Array.from(this.configs.values());
50
+ }
51
+ getConfigForKey(e) {
52
+ return this.configs.get(e);
53
+ }
54
+ reset() {
55
+ for (const [e, t] of this.configs)
56
+ this.state[e] = t.default, this.removeFromStorage(e);
57
+ this.dispatch();
58
+ }
59
+ readFromStorage(e) {
60
+ if (typeof window > "u") return null;
61
+ try {
62
+ const t = localStorage.getItem(g + e);
63
+ return t === null ? null : JSON.parse(t);
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+ writeToStorage(e, t) {
69
+ if (!(typeof window > "u"))
70
+ try {
71
+ localStorage.setItem(g + e, JSON.stringify(t));
72
+ } catch {
73
+ }
74
+ }
75
+ removeFromStorage(e) {
76
+ if (!(typeof window > "u"))
77
+ try {
78
+ localStorage.removeItem(g + e);
79
+ } catch {
80
+ }
81
+ }
82
+ dispatch(e) {
83
+ const t = { key: e, state: this.getAll() }, s = new CustomEvent("vibe-flags-changed", {
84
+ detail: t,
85
+ bubbles: !0
86
+ });
87
+ this.dispatchEvent(s), typeof window < "u" && window.dispatchEvent(new CustomEvent("vibe-flags-changed", { detail: t }));
88
+ }
89
+ }
90
+ const p = new S();
91
+ var C = Object.defineProperty, F = Object.getOwnPropertyDescriptor, d = (r, e, t, s) => {
92
+ for (var o = s > 1 ? void 0 : s ? F(e, t) : e, i = r.length - 1, n; i >= 0; i--)
93
+ (n = r[i]) && (o = (s ? n(e, t, o) : n(o)) || o);
94
+ return s && o && C(e, t, o), o;
95
+ };
96
+ let l = class extends b {
97
+ constructor() {
98
+ super(...arguments), this.key = "", this.description = "", this.type = "boolean", this.value = "true", this.defaultValue = "", this.options = "", this.isMatch = !1, this.registered = !1, this.onFlagChange = () => {
99
+ this.evaluate();
100
+ };
101
+ }
102
+ connectedCallback() {
103
+ super.connectedCallback(), window.addEventListener("vibe-flags-changed", this.onFlagChange), this.registerFlag(), this.evaluate();
104
+ }
105
+ disconnectedCallback() {
106
+ super.disconnectedCallback(), window.removeEventListener("vibe-flags-changed", this.onFlagChange);
107
+ }
108
+ willUpdate(r) {
109
+ ["key", "description", "type", "defaultValue", "options"].some((t) => r.has(t)) && this.registerFlag();
110
+ }
111
+ registerFlag() {
112
+ if (!this.key) return;
113
+ const r = this.type === "select" ? {
114
+ key: this.key,
115
+ type: "select",
116
+ default: this.defaultValue || this.parseOptions()[0] || "",
117
+ options: this.parseOptions(),
118
+ label: this.description || void 0
119
+ } : {
120
+ key: this.key,
121
+ type: "boolean",
122
+ default: this.defaultValue ? this.defaultValue === "true" : !1,
123
+ label: this.description || void 0
124
+ };
125
+ p.register(r), this.registered = !0, this.evaluate();
126
+ }
127
+ parseOptions() {
128
+ return this.options.split(",").map((r) => r.trim()).filter(Boolean);
129
+ }
130
+ evaluate() {
131
+ const r = p.get(this.key);
132
+ if (r === void 0) {
133
+ this.isMatch = !1;
134
+ return;
135
+ }
136
+ this.isMatch = String(r) === this.value;
137
+ }
138
+ render() {
139
+ return this.isMatch ? a`<slot></slot>` : u;
140
+ }
141
+ };
142
+ d([
143
+ f({ type: String })
144
+ ], l.prototype, "key", 2);
145
+ d([
146
+ f({ type: String })
147
+ ], l.prototype, "description", 2);
148
+ d([
149
+ f({ type: String })
150
+ ], l.prototype, "type", 2);
151
+ d([
152
+ f({ type: String })
153
+ ], l.prototype, "value", 2);
154
+ d([
155
+ f({ type: String, attribute: "default" })
156
+ ], l.prototype, "defaultValue", 2);
157
+ d([
158
+ f({ type: String })
159
+ ], l.prototype, "options", 2);
160
+ d([
161
+ h()
162
+ ], l.prototype, "isMatch", 2);
163
+ l = d([
164
+ x("vibe-flag")
165
+ ], l);
166
+ const $ = w`
167
+ :host {
168
+ --vf-bg: #ffffff;
169
+ --vf-bg-muted: #f5f5f5;
170
+ --vf-bg-hover: #f0f0f0;
171
+ --vf-border: #e5e5e5;
172
+ --vf-text: #0a0a0a;
173
+ --vf-text-muted: #737373;
174
+ --vf-primary: #171717;
175
+ --vf-primary-fg: #fafafa;
176
+ --vf-accent: #6366f1;
177
+ --vf-accent-fg: #ffffff;
178
+ --vf-destructive: #ef4444;
179
+ --vf-radius: 6px;
180
+ --vf-radius-lg: 8px;
181
+ --vf-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
182
+ 'Helvetica Neue', Arial, sans-serif;
183
+ --vf-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
184
+ 0 1px 2px -1px rgba(0, 0, 0, 0.1);
185
+ --vf-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
186
+ 0 4px 6px -4px rgba(0, 0, 0, 0.1);
187
+ --vf-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
188
+ 0 8px 10px -6px rgba(0, 0, 0, 0.1);
189
+ }
190
+ `;
191
+ var O = Object.defineProperty, E = Object.getOwnPropertyDescriptor, v = (r, e, t, s) => {
192
+ for (var o = s > 1 ? void 0 : s ? E(e, t) : e, i = r.length - 1, n; i >= 0; i--)
193
+ (n = r[i]) && (o = (s ? n(e, t, o) : n(o)) || o);
194
+ return s && o && O(e, t, o), o;
195
+ };
196
+ let c = class extends b {
197
+ constructor() {
198
+ super(...arguments), this.open = !1, this.flags = {}, this.configs = [], this.onFlagChange = () => {
199
+ this.syncFromStore();
200
+ };
201
+ }
202
+ connectedCallback() {
203
+ super.connectedCallback(), window.addEventListener("vibe-flags-changed", this.onFlagChange), this.syncFromStore();
204
+ }
205
+ disconnectedCallback() {
206
+ super.disconnectedCallback(), window.removeEventListener("vibe-flags-changed", this.onFlagChange);
207
+ }
208
+ syncFromStore() {
209
+ this.configs = p.getConfig(), this.flags = p.getAll();
210
+ }
211
+ toggleSidebar() {
212
+ this.open = !this.open;
213
+ }
214
+ closeSidebar() {
215
+ this.open = !1;
216
+ }
217
+ onToggle(r) {
218
+ const e = this.flags[r];
219
+ p.set(r, !e);
220
+ }
221
+ onSelect(r, e) {
222
+ const t = e.target;
223
+ p.set(r, t.value);
224
+ }
225
+ onReset() {
226
+ p.reset();
227
+ }
228
+ renderFlagIcon() {
229
+ return a`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>`;
230
+ }
231
+ renderCloseIcon() {
232
+ return a`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
233
+ }
234
+ renderChevronDown() {
235
+ return a`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`;
236
+ }
237
+ renderBooleanFlag(r) {
238
+ const e = this.flags[r.key] === !0;
239
+ return a`
240
+ <div class="flag-item">
241
+ <div class="flag-row">
242
+ <div class="flag-info">
243
+ <div class="flag-label">${r.label || r.key}</div>
244
+ <div class="flag-key">${r.key}</div>
245
+ </div>
246
+ <label class="toggle">
247
+ <input
248
+ type="checkbox"
249
+ .checked=${e}
250
+ @change=${() => this.onToggle(r.key)}
251
+ />
252
+ <span class="toggle-track"></span>
253
+ <span class="toggle-thumb"></span>
254
+ </label>
255
+ </div>
256
+ </div>
257
+ `;
258
+ }
259
+ renderSelectFlag(r) {
260
+ if (r.type !== "select") return u;
261
+ const e = this.flags[r.key];
262
+ return a`
263
+ <div class="flag-item">
264
+ <div class="flag-row">
265
+ <div class="flag-info">
266
+ <div class="flag-label">${r.label || r.key}</div>
267
+ <div class="flag-key">${r.key}</div>
268
+ </div>
269
+ <div class="select-wrapper">
270
+ <select
271
+ class="select"
272
+ .value=${e}
273
+ @change=${(t) => this.onSelect(r.key, t)}
274
+ >
275
+ ${r.options.map(
276
+ (t) => a`<option value=${t} ?selected=${t === e}>
277
+ ${t}
278
+ </option>`
279
+ )}
280
+ </select>
281
+ <span class="select-chevron">${this.renderChevronDown()}</span>
282
+ </div>
283
+ </div>
284
+ </div>
285
+ `;
286
+ }
287
+ render() {
288
+ return a`
289
+ ${this.open ? u : a`
290
+ <button class="fab" @click=${this.toggleSidebar} aria-label="Toggle feature flags">
291
+ ${this.renderFlagIcon()}
292
+ </button>
293
+ `}
294
+
295
+ <div class="backdrop ${this.open ? "open" : ""}" @click=${this.closeSidebar}></div>
296
+
297
+ <div class="sidebar ${this.open ? "open" : ""}">
298
+ <div class="header">
299
+ <h2>
300
+ ${this.renderFlagIcon()}
301
+ VibeFlags
302
+ <span class="badge">${this.configs.length}</span>
303
+ </h2>
304
+ <button class="close-btn" @click=${this.closeSidebar} aria-label="Close">
305
+ ${this.renderCloseIcon()}
306
+ </button>
307
+ </div>
308
+
309
+ <div class="flags">
310
+ ${this.configs.length === 0 ? a`<div class="empty">No flags configured</div>` : this.configs.map(
311
+ (r) => r.type === "boolean" ? this.renderBooleanFlag(r) : this.renderSelectFlag(r)
312
+ )}
313
+ </div>
314
+
315
+ <div class="footer">
316
+ <button class="reset-btn" @click=${this.onReset}>
317
+ Reset all to defaults
318
+ </button>
319
+ </div>
320
+ </div>
321
+ `;
322
+ }
323
+ };
324
+ c.styles = [
325
+ $,
326
+ w`
327
+ * {
328
+ box-sizing: border-box;
329
+ margin: 0;
330
+ padding: 0;
331
+ }
332
+
333
+ .fab {
334
+ position: fixed;
335
+ right: 0;
336
+ top: 50%;
337
+ transform: translateY(-50%);
338
+ z-index: 99999;
339
+ width: 40px;
340
+ height: 44px;
341
+ border: 1px solid var(--vf-border);
342
+ border-right: none;
343
+ border-radius: var(--vf-radius-lg) 0 0 var(--vf-radius-lg);
344
+ background: var(--vf-bg);
345
+ color: var(--vf-text);
346
+ cursor: pointer;
347
+ display: flex;
348
+ align-items: center;
349
+ justify-content: center;
350
+ box-shadow: var(--vf-shadow-lg);
351
+ transition: all 0.2s ease;
352
+ font-family: var(--vf-font);
353
+ }
354
+
355
+ .fab:hover {
356
+ width: 46px;
357
+ background: var(--vf-bg-muted);
358
+ }
359
+
360
+ .fab svg {
361
+ width: 20px;
362
+ height: 20px;
363
+ flex-shrink: 0;
364
+ }
365
+
366
+ .backdrop {
367
+ position: fixed;
368
+ inset: 0;
369
+ z-index: 100000;
370
+ background: rgba(0, 0, 0, 0.4);
371
+ opacity: 0;
372
+ transition: opacity 0.3s ease;
373
+ pointer-events: none;
374
+ }
375
+
376
+ .backdrop.open {
377
+ opacity: 1;
378
+ pointer-events: auto;
379
+ }
380
+
381
+ .sidebar {
382
+ position: fixed;
383
+ top: 0;
384
+ right: 0;
385
+ bottom: 0;
386
+ z-index: 100001;
387
+ width: 340px;
388
+ max-width: 90vw;
389
+ background: var(--vf-bg);
390
+ border-left: 1px solid var(--vf-border);
391
+ box-shadow: var(--vf-shadow-xl);
392
+ transform: translateX(100%);
393
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
394
+ display: flex;
395
+ flex-direction: column;
396
+ font-family: var(--vf-font);
397
+ color: var(--vf-text);
398
+ }
399
+
400
+ .sidebar.open {
401
+ transform: translateX(0);
402
+ }
403
+
404
+ .header {
405
+ display: flex;
406
+ align-items: center;
407
+ justify-content: space-between;
408
+ padding: 16px 20px;
409
+ border-bottom: 1px solid var(--vf-border);
410
+ flex-shrink: 0;
411
+ }
412
+
413
+ .header h2 {
414
+ font-size: 16px;
415
+ font-weight: 600;
416
+ letter-spacing: -0.025em;
417
+ display: flex;
418
+ align-items: center;
419
+ gap: 8px;
420
+ }
421
+
422
+ .header h2 svg {
423
+ width: 18px;
424
+ height: 18px;
425
+ color: var(--vf-accent);
426
+ }
427
+
428
+ .badge {
429
+ font-size: 11px;
430
+ font-weight: 500;
431
+ padding: 2px 7px;
432
+ border-radius: 9999px;
433
+ background: var(--vf-bg-muted);
434
+ color: var(--vf-text-muted);
435
+ border: 1px solid var(--vf-border);
436
+ }
437
+
438
+ .close-btn {
439
+ width: 32px;
440
+ height: 32px;
441
+ border: none;
442
+ border-radius: var(--vf-radius);
443
+ background: transparent;
444
+ color: var(--vf-text-muted);
445
+ cursor: pointer;
446
+ display: flex;
447
+ align-items: center;
448
+ justify-content: center;
449
+ transition: all 0.15s ease;
450
+ }
451
+
452
+ .close-btn:hover {
453
+ background: var(--vf-bg-muted);
454
+ color: var(--vf-text);
455
+ }
456
+
457
+ .close-btn svg {
458
+ width: 16px;
459
+ height: 16px;
460
+ }
461
+
462
+ .flags {
463
+ flex: 1;
464
+ overflow-y: auto;
465
+ padding: 8px 0;
466
+ }
467
+
468
+ .flag-item {
469
+ padding: 12px 20px;
470
+ transition: background 0.1s ease;
471
+ }
472
+
473
+ .flag-item:hover {
474
+ background: var(--vf-bg-muted);
475
+ }
476
+
477
+ .flag-item + .flag-item {
478
+ border-top: 1px solid var(--vf-border);
479
+ }
480
+
481
+ .flag-label {
482
+ font-size: 13px;
483
+ font-weight: 500;
484
+ margin-bottom: 2px;
485
+ }
486
+
487
+ .flag-key {
488
+ font-size: 11px;
489
+ color: var(--vf-text-muted);
490
+ font-family: 'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas,
491
+ monospace;
492
+ }
493
+
494
+ .flag-row {
495
+ display: flex;
496
+ align-items: center;
497
+ justify-content: space-between;
498
+ gap: 12px;
499
+ }
500
+
501
+ .flag-info {
502
+ min-width: 0;
503
+ flex: 1;
504
+ }
505
+
506
+ /* Toggle switch */
507
+ .toggle {
508
+ position: relative;
509
+ width: 40px;
510
+ height: 22px;
511
+ flex-shrink: 0;
512
+ }
513
+
514
+ .toggle input {
515
+ opacity: 0;
516
+ width: 0;
517
+ height: 0;
518
+ position: absolute;
519
+ }
520
+
521
+ .toggle-track {
522
+ position: absolute;
523
+ inset: 0;
524
+ border-radius: 11px;
525
+ background: var(--vf-border);
526
+ cursor: pointer;
527
+ transition: background 0.2s ease;
528
+ }
529
+
530
+ .toggle input:checked + .toggle-track {
531
+ background: var(--vf-primary);
532
+ }
533
+
534
+ .toggle-thumb {
535
+ position: absolute;
536
+ top: 2px;
537
+ left: 2px;
538
+ width: 18px;
539
+ height: 18px;
540
+ border-radius: 50%;
541
+ background: white;
542
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
543
+ transition: transform 0.2s ease;
544
+ pointer-events: none;
545
+ }
546
+
547
+ .toggle input:checked ~ .toggle-thumb {
548
+ transform: translateX(18px);
549
+ }
550
+
551
+ /* Select dropdown */
552
+ .select-wrapper {
553
+ position: relative;
554
+ flex-shrink: 0;
555
+ }
556
+
557
+ .select {
558
+ appearance: none;
559
+ -webkit-appearance: none;
560
+ font-size: 13px;
561
+ font-family: var(--vf-font);
562
+ padding: 6px 30px 6px 10px;
563
+ border: 1px solid var(--vf-border);
564
+ border-radius: var(--vf-radius);
565
+ background: var(--vf-bg);
566
+ color: var(--vf-text);
567
+ cursor: pointer;
568
+ outline: none;
569
+ min-width: 100px;
570
+ transition: border-color 0.15s ease;
571
+ }
572
+
573
+ .select:hover {
574
+ border-color: var(--vf-text-muted);
575
+ }
576
+
577
+ .select:focus {
578
+ border-color: var(--vf-accent);
579
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.15);
580
+ }
581
+
582
+ .select-chevron {
583
+ position: absolute;
584
+ right: 8px;
585
+ top: 50%;
586
+ transform: translateY(-50%);
587
+ pointer-events: none;
588
+ color: var(--vf-text-muted);
589
+ }
590
+
591
+ .select-chevron svg {
592
+ width: 14px;
593
+ height: 14px;
594
+ }
595
+
596
+ /* Footer */
597
+ .footer {
598
+ padding: 12px 20px;
599
+ border-top: 1px solid var(--vf-border);
600
+ flex-shrink: 0;
601
+ }
602
+
603
+ .reset-btn {
604
+ width: 100%;
605
+ padding: 8px 16px;
606
+ font-size: 13px;
607
+ font-weight: 500;
608
+ font-family: var(--vf-font);
609
+ border: 1px solid var(--vf-border);
610
+ border-radius: var(--vf-radius);
611
+ background: var(--vf-bg);
612
+ color: var(--vf-text);
613
+ cursor: pointer;
614
+ transition: all 0.15s ease;
615
+ }
616
+
617
+ .reset-btn:hover {
618
+ background: var(--vf-bg-muted);
619
+ border-color: var(--vf-text-muted);
620
+ }
621
+
622
+ .empty {
623
+ padding: 40px 20px;
624
+ text-align: center;
625
+ color: var(--vf-text-muted);
626
+ font-size: 13px;
627
+ }
628
+ `
629
+ ];
630
+ v([
631
+ h()
632
+ ], c.prototype, "open", 2);
633
+ v([
634
+ h()
635
+ ], c.prototype, "flags", 2);
636
+ v([
637
+ h()
638
+ ], c.prototype, "configs", 2);
639
+ c = v([
640
+ x("vibe-toolbar")
641
+ ], c);
642
+ export {
643
+ l as VibeFlag,
644
+ y as VibeFlags,
645
+ c as VibeToolbar,
646
+ p as flagStore
647
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@vibe-flags/core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/vibe-flags.js",
6
+ "module": "./dist/vibe-flags.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/vibe-flags.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./cdn": "./dist/vibe-flags.cdn.mjs",
14
+ "./cdn/iife": "./dist/vibe-flags.cdn.js"
15
+ },
16
+ "unpkg": "./dist/vibe-flags.cdn.js",
17
+ "jsdelivr": "./dist/vibe-flags.cdn.js",
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "sideEffects": true,
22
+ "peerDependencies": {
23
+ "lit": "^3.0.0 || ^4.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "@open-wc/testing": "^4.0.0",
27
+ "jsdom": "^29.0.1",
28
+ "lit": "^3.2.0",
29
+ "typescript": "^5.7.0",
30
+ "vite": "^6.0.0",
31
+ "vite-plugin-dts": "^4.5.0",
32
+ "vitest": "^3.0.0"
33
+ },
34
+ "scripts": {
35
+ "dev": "vite",
36
+ "build": "tsc && vite build && vite build --config vite.cdn.config.ts",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest",
39
+ "prepublishOnly": "pnpm build"
40
+ }
41
+ }