@posthog/browser-common 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.js +1 -1
- package/dist/config.mjs +1 -1
- package/dist/types/compression.d.ts +2 -2
- package/dist/types/surveys.d.ts +53 -53
- package/dist/utils/autocapture-utils.js +67 -11
- package/dist/utils/autocapture-utils.mjs +67 -11
- package/dist/utils/event-utils.js +1 -0
- package/dist/utils/event-utils.mjs +1 -0
- package/package.json +4 -4
package/dist/config.js
CHANGED
package/dist/config.mjs
CHANGED
package/dist/types/surveys.d.ts
CHANGED
|
@@ -1,89 +1,89 @@
|
|
|
1
1
|
import type { PropertyMatchType, SurveyAppearance as CoreSurveyAppearance, SurveyEventWithFilters, SurveyQuestionTranslation, SurveyTranslation, SurveyValidationRule } from '@posthog/core';
|
|
2
2
|
export type { PropertyFilters, PropertyMatchType, PropertyOperator, SurveyEventWithFilters } from '@posthog/core';
|
|
3
3
|
export declare const SurveyEventType: {
|
|
4
|
-
readonly Activation:
|
|
5
|
-
readonly Cancellation:
|
|
4
|
+
readonly Activation: 'events';
|
|
5
|
+
readonly Cancellation: 'cancelEvents';
|
|
6
6
|
};
|
|
7
7
|
export type SurveyEventType = (typeof SurveyEventType)[keyof typeof SurveyEventType];
|
|
8
8
|
export declare const SurveyWidgetType: {
|
|
9
|
-
readonly Button:
|
|
10
|
-
readonly Tab:
|
|
11
|
-
readonly Selector:
|
|
9
|
+
readonly Button: 'button';
|
|
10
|
+
readonly Tab: 'tab';
|
|
11
|
+
readonly Selector: 'selector';
|
|
12
12
|
};
|
|
13
13
|
export type SurveyWidgetType = (typeof SurveyWidgetType)[keyof typeof SurveyWidgetType];
|
|
14
14
|
export declare const SurveyPosition: {
|
|
15
|
-
readonly TopLeft:
|
|
16
|
-
readonly TopRight:
|
|
17
|
-
readonly TopCenter:
|
|
18
|
-
readonly MiddleLeft:
|
|
19
|
-
readonly MiddleRight:
|
|
20
|
-
readonly MiddleCenter:
|
|
21
|
-
readonly Left:
|
|
22
|
-
readonly Center:
|
|
23
|
-
readonly Right:
|
|
24
|
-
readonly NextToTrigger:
|
|
15
|
+
readonly TopLeft: 'top_left';
|
|
16
|
+
readonly TopRight: 'top_right';
|
|
17
|
+
readonly TopCenter: 'top_center';
|
|
18
|
+
readonly MiddleLeft: 'middle_left';
|
|
19
|
+
readonly MiddleRight: 'middle_right';
|
|
20
|
+
readonly MiddleCenter: 'middle_center';
|
|
21
|
+
readonly Left: 'left';
|
|
22
|
+
readonly Center: 'center';
|
|
23
|
+
readonly Right: 'right';
|
|
24
|
+
readonly NextToTrigger: 'next_to_trigger';
|
|
25
25
|
};
|
|
26
26
|
export type SurveyPosition = (typeof SurveyPosition)[keyof typeof SurveyPosition];
|
|
27
27
|
export declare const SurveyTabPosition: {
|
|
28
|
-
readonly Top:
|
|
29
|
-
readonly Left:
|
|
30
|
-
readonly Right:
|
|
31
|
-
readonly Bottom:
|
|
28
|
+
readonly Top: 'top';
|
|
29
|
+
readonly Left: 'left';
|
|
30
|
+
readonly Right: 'right';
|
|
31
|
+
readonly Bottom: 'bottom';
|
|
32
32
|
};
|
|
33
33
|
export type SurveyTabPosition = (typeof SurveyTabPosition)[keyof typeof SurveyTabPosition];
|
|
34
34
|
export declare const SurveyType: {
|
|
35
|
-
readonly Popover:
|
|
36
|
-
readonly API:
|
|
37
|
-
readonly Widget:
|
|
38
|
-
readonly ExternalSurvey:
|
|
35
|
+
readonly Popover: 'popover';
|
|
36
|
+
readonly API: 'api';
|
|
37
|
+
readonly Widget: 'widget';
|
|
38
|
+
readonly ExternalSurvey: 'external_survey';
|
|
39
39
|
};
|
|
40
40
|
export type SurveyType = (typeof SurveyType)[keyof typeof SurveyType];
|
|
41
41
|
export declare const SurveyQuestionType: {
|
|
42
|
-
readonly Open:
|
|
43
|
-
readonly MultipleChoice:
|
|
44
|
-
readonly SingleChoice:
|
|
45
|
-
readonly Rating:
|
|
46
|
-
readonly Link:
|
|
42
|
+
readonly Open: 'open';
|
|
43
|
+
readonly MultipleChoice: 'multiple_choice';
|
|
44
|
+
readonly SingleChoice: 'single_choice';
|
|
45
|
+
readonly Rating: 'rating';
|
|
46
|
+
readonly Link: 'link';
|
|
47
47
|
};
|
|
48
48
|
export type SurveyQuestionType = (typeof SurveyQuestionType)[keyof typeof SurveyQuestionType];
|
|
49
49
|
export declare const SurveyQuestionBranchingType: {
|
|
50
|
-
readonly NextQuestion:
|
|
51
|
-
readonly End:
|
|
52
|
-
readonly ResponseBased:
|
|
53
|
-
readonly SpecificQuestion:
|
|
50
|
+
readonly NextQuestion: 'next_question';
|
|
51
|
+
readonly End: 'end';
|
|
52
|
+
readonly ResponseBased: 'response_based';
|
|
53
|
+
readonly SpecificQuestion: 'specific_question';
|
|
54
54
|
};
|
|
55
55
|
export type SurveyQuestionBranchingType = (typeof SurveyQuestionBranchingType)[keyof typeof SurveyQuestionBranchingType];
|
|
56
56
|
export declare const SurveySchedule: {
|
|
57
|
-
readonly Once:
|
|
58
|
-
readonly Recurring:
|
|
59
|
-
readonly Always:
|
|
57
|
+
readonly Once: 'once';
|
|
58
|
+
readonly Recurring: 'recurring';
|
|
59
|
+
readonly Always: 'always';
|
|
60
60
|
};
|
|
61
61
|
export type SurveySchedule = (typeof SurveySchedule)[keyof typeof SurveySchedule];
|
|
62
62
|
export declare const SurveyEventName: {
|
|
63
|
-
readonly SHOWN:
|
|
64
|
-
readonly DISMISSED:
|
|
65
|
-
readonly SENT:
|
|
66
|
-
readonly ABANDONED:
|
|
63
|
+
readonly SHOWN: 'survey shown';
|
|
64
|
+
readonly DISMISSED: 'survey dismissed';
|
|
65
|
+
readonly SENT: 'survey sent';
|
|
66
|
+
readonly ABANDONED: 'survey abandoned';
|
|
67
67
|
};
|
|
68
68
|
export type SurveyEventName = (typeof SurveyEventName)[keyof typeof SurveyEventName];
|
|
69
69
|
export declare const SurveyEventProperties: {
|
|
70
|
-
readonly SURVEY_ID:
|
|
71
|
-
readonly SURVEY_NAME:
|
|
72
|
-
readonly SURVEY_RESPONSE:
|
|
73
|
-
readonly SURVEY_ITERATION:
|
|
74
|
-
readonly SURVEY_ITERATION_START_DATE:
|
|
75
|
-
readonly SURVEY_PARTIALLY_COMPLETED:
|
|
76
|
-
readonly SURVEY_SUBMISSION_ID:
|
|
77
|
-
readonly SURVEY_QUESTIONS:
|
|
78
|
-
readonly SURVEY_COMPLETED:
|
|
79
|
-
readonly PRODUCT_TOUR_ID:
|
|
80
|
-
readonly SURVEY_LAST_SEEN_DATE:
|
|
81
|
-
readonly SURVEY_LANGUAGE:
|
|
70
|
+
readonly SURVEY_ID: '$survey_id';
|
|
71
|
+
readonly SURVEY_NAME: '$survey_name';
|
|
72
|
+
readonly SURVEY_RESPONSE: '$survey_response';
|
|
73
|
+
readonly SURVEY_ITERATION: '$survey_iteration';
|
|
74
|
+
readonly SURVEY_ITERATION_START_DATE: '$survey_iteration_start_date';
|
|
75
|
+
readonly SURVEY_PARTIALLY_COMPLETED: '$survey_partially_completed';
|
|
76
|
+
readonly SURVEY_SUBMISSION_ID: '$survey_submission_id';
|
|
77
|
+
readonly SURVEY_QUESTIONS: '$survey_questions';
|
|
78
|
+
readonly SURVEY_COMPLETED: '$survey_completed';
|
|
79
|
+
readonly PRODUCT_TOUR_ID: '$product_tour_id';
|
|
80
|
+
readonly SURVEY_LAST_SEEN_DATE: '$survey_last_seen_date';
|
|
81
|
+
readonly SURVEY_LANGUAGE: '$survey_language';
|
|
82
82
|
};
|
|
83
83
|
export type SurveyEventProperties = (typeof SurveyEventProperties)[keyof typeof SurveyEventProperties];
|
|
84
84
|
export declare const DisplaySurveyType: {
|
|
85
|
-
readonly Popover:
|
|
86
|
-
readonly Inline:
|
|
85
|
+
readonly Popover: 'popover';
|
|
86
|
+
readonly Inline: 'inline';
|
|
87
87
|
};
|
|
88
88
|
export type DisplaySurveyType = (typeof DisplaySurveyType)[keyof typeof DisplaySurveyType];
|
|
89
89
|
export type SurveyQuestionDescriptionContentType = 'html' | 'text';
|
|
@@ -138,7 +138,17 @@ const DEFAULT_CONTENT_IGNORELIST = [
|
|
|
138
138
|
'previous',
|
|
139
139
|
'prev',
|
|
140
140
|
'>',
|
|
141
|
-
'<'
|
|
141
|
+
'<',
|
|
142
|
+
'→',
|
|
143
|
+
'←',
|
|
144
|
+
'›',
|
|
145
|
+
'‹',
|
|
146
|
+
'»',
|
|
147
|
+
'«',
|
|
148
|
+
'▶',
|
|
149
|
+
'◀',
|
|
150
|
+
'❯',
|
|
151
|
+
'❮'
|
|
142
152
|
];
|
|
143
153
|
const DEFAULT_CONTENT_IGNORELIST_WITH_STEPPERS = [
|
|
144
154
|
...DEFAULT_CONTENT_IGNORELIST,
|
|
@@ -147,10 +157,27 @@ const DEFAULT_CONTENT_IGNORELIST_WITH_STEPPERS = [
|
|
|
147
157
|
'−',
|
|
148
158
|
'–'
|
|
149
159
|
];
|
|
150
|
-
const MAX_CONTENT_IGNORELIST_ENTRIES = 10;
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
const MAX_CONTENT_IGNORELIST_ENTRIES = DEFAULT_CONTENT_IGNORELIST_WITH_STEPPERS.length + 10;
|
|
161
|
+
const INTERACTIVE_TAGS = [
|
|
162
|
+
'button',
|
|
163
|
+
'a',
|
|
164
|
+
'input',
|
|
165
|
+
'select',
|
|
166
|
+
'textarea',
|
|
167
|
+
'label'
|
|
168
|
+
];
|
|
169
|
+
const INTERACTIVE_ROLES = [
|
|
170
|
+
'button',
|
|
171
|
+
'link',
|
|
172
|
+
'tab',
|
|
173
|
+
'menuitem',
|
|
174
|
+
'option'
|
|
175
|
+
];
|
|
176
|
+
const matchesContentKeyword = (text, keyword)=>{
|
|
177
|
+
if (!/[a-z0-9]/i.test(keyword)) return text === keyword;
|
|
178
|
+
return (0, core_namespaceObject.includes)(DEFAULT_CONTENT_IGNORELIST_WITH_STEPPERS, keyword) ? new RegExp(`\\b${keyword}\\b`).test(text) : text.includes(keyword);
|
|
179
|
+
};
|
|
180
|
+
function shouldIgnoreByContent(contentIgnorelist, { safeText, ariaLabel }) {
|
|
154
181
|
let keywords;
|
|
155
182
|
if (true === contentIgnorelist) keywords = DEFAULT_CONTENT_IGNORELIST;
|
|
156
183
|
else {
|
|
@@ -161,7 +188,40 @@ function shouldIgnoreByContent(contentIgnorelist, elementsWithText) {
|
|
|
161
188
|
}
|
|
162
189
|
keywords = contentIgnorelist.map((k)=>k.toLowerCase());
|
|
163
190
|
}
|
|
164
|
-
return
|
|
191
|
+
return keywords.some((keyword)=>matchesContentKeyword(safeText, keyword) || matchesContentKeyword(ariaLabel, keyword));
|
|
192
|
+
}
|
|
193
|
+
const isInteractiveElement = (el)=>(0, core_namespaceObject.includes)(INTERACTIVE_TAGS, el.tagName.toLowerCase()) || (0, core_namespaceObject.includes)(INTERACTIVE_ROLES, (el.getAttribute('role') || '').toLowerCase());
|
|
194
|
+
function clickedControlText(el, targetElementList) {
|
|
195
|
+
for (const candidate of targetElementList)if (isInteractiveElement(candidate)) return controlLabelText(candidate);
|
|
196
|
+
for (const candidate of targetElementList){
|
|
197
|
+
try {
|
|
198
|
+
if (external_globals_js_namespaceObject.window?.getComputedStyle(candidate).getPropertyValue('cursor') !== 'pointer') break;
|
|
199
|
+
} catch {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
const label = controlLabelText(candidate);
|
|
203
|
+
if (label.safeText || label.ariaLabel) return label;
|
|
204
|
+
}
|
|
205
|
+
return controlLabelText(el);
|
|
206
|
+
}
|
|
207
|
+
function controlLabelText(control) {
|
|
208
|
+
let text = '';
|
|
209
|
+
let firstAriaLabel = '';
|
|
210
|
+
const collect = (node)=>{
|
|
211
|
+
if ((0, external_element_utils_js_namespaceObject.isElementNode)(node) && shouldCaptureElement(node) && !isSensitiveElement(node)) {
|
|
212
|
+
text += ` ${getSafeText(node)}`;
|
|
213
|
+
firstAriaLabel = firstAriaLabel || node.getAttribute('aria-label') || '';
|
|
214
|
+
(0, external_general_utils_js_namespaceObject.each)(node.childNodes, collect);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
collect(control);
|
|
218
|
+
text = (0, core_namespaceObject.trim)(text.replace(/\s+/g, ' '));
|
|
219
|
+
const safeText = (shouldCaptureValue(text) ? text : '').toLowerCase();
|
|
220
|
+
const ariaLabel = control.getAttribute('aria-label') || (safeText ? '' : firstAriaLabel);
|
|
221
|
+
return {
|
|
222
|
+
safeText,
|
|
223
|
+
ariaLabel: (0, core_namespaceObject.trim)(ariaLabel).toLowerCase()
|
|
224
|
+
};
|
|
165
225
|
}
|
|
166
226
|
const DEFAULT_DEAD_CLICK_IGNORE_LIST = [
|
|
167
227
|
'.ph-no-deadclick',
|
|
@@ -215,11 +275,7 @@ function shouldCaptureRageclick(el, _config) {
|
|
|
215
275
|
if (false === selectorIgnoreList) return false;
|
|
216
276
|
if (ignoreTextSelection && isTextSelectionTarget(el)) return false;
|
|
217
277
|
const { targetElementList } = getElementAndParentsForElement(el, false);
|
|
218
|
-
|
|
219
|
-
safeText: getSafeText(element).toLowerCase(),
|
|
220
|
-
ariaLabel: element.getAttribute('aria-label')?.toLowerCase().trim() || ''
|
|
221
|
-
}));
|
|
222
|
-
if (shouldIgnoreByContent(contentIgnorelist, elementsWithText)) return false;
|
|
278
|
+
if (contentIgnorelist && shouldIgnoreByContent(contentIgnorelist, clickedControlText(el, targetElementList))) return false;
|
|
223
279
|
return !checkIfElementsMatchCSSSelector(targetElementList, selectorIgnoreList);
|
|
224
280
|
}
|
|
225
281
|
const cannotCheckForAutocapture = (el)=>!el || (0, external_element_utils_js_namespaceObject.isTag)(el, 'html') || !(0, external_element_utils_js_namespaceObject.isElementNode)(el);
|
|
@@ -84,7 +84,17 @@ const DEFAULT_CONTENT_IGNORELIST = [
|
|
|
84
84
|
'previous',
|
|
85
85
|
'prev',
|
|
86
86
|
'>',
|
|
87
|
-
'<'
|
|
87
|
+
'<',
|
|
88
|
+
'→',
|
|
89
|
+
'←',
|
|
90
|
+
'›',
|
|
91
|
+
'‹',
|
|
92
|
+
'»',
|
|
93
|
+
'«',
|
|
94
|
+
'▶',
|
|
95
|
+
'◀',
|
|
96
|
+
'❯',
|
|
97
|
+
'❮'
|
|
88
98
|
];
|
|
89
99
|
const DEFAULT_CONTENT_IGNORELIST_WITH_STEPPERS = [
|
|
90
100
|
...DEFAULT_CONTENT_IGNORELIST,
|
|
@@ -93,10 +103,27 @@ const DEFAULT_CONTENT_IGNORELIST_WITH_STEPPERS = [
|
|
|
93
103
|
'−',
|
|
94
104
|
'–'
|
|
95
105
|
];
|
|
96
|
-
const MAX_CONTENT_IGNORELIST_ENTRIES = 10;
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
const MAX_CONTENT_IGNORELIST_ENTRIES = DEFAULT_CONTENT_IGNORELIST_WITH_STEPPERS.length + 10;
|
|
107
|
+
const INTERACTIVE_TAGS = [
|
|
108
|
+
'button',
|
|
109
|
+
'a',
|
|
110
|
+
'input',
|
|
111
|
+
'select',
|
|
112
|
+
'textarea',
|
|
113
|
+
'label'
|
|
114
|
+
];
|
|
115
|
+
const INTERACTIVE_ROLES = [
|
|
116
|
+
'button',
|
|
117
|
+
'link',
|
|
118
|
+
'tab',
|
|
119
|
+
'menuitem',
|
|
120
|
+
'option'
|
|
121
|
+
];
|
|
122
|
+
const matchesContentKeyword = (text, keyword)=>{
|
|
123
|
+
if (!/[a-z0-9]/i.test(keyword)) return text === keyword;
|
|
124
|
+
return includes(DEFAULT_CONTENT_IGNORELIST_WITH_STEPPERS, keyword) ? new RegExp(`\\b${keyword}\\b`).test(text) : text.includes(keyword);
|
|
125
|
+
};
|
|
126
|
+
function shouldIgnoreByContent(contentIgnorelist, { safeText, ariaLabel }) {
|
|
100
127
|
let keywords;
|
|
101
128
|
if (true === contentIgnorelist) keywords = DEFAULT_CONTENT_IGNORELIST;
|
|
102
129
|
else {
|
|
@@ -107,7 +134,40 @@ function shouldIgnoreByContent(contentIgnorelist, elementsWithText) {
|
|
|
107
134
|
}
|
|
108
135
|
keywords = contentIgnorelist.map((k)=>k.toLowerCase());
|
|
109
136
|
}
|
|
110
|
-
return
|
|
137
|
+
return keywords.some((keyword)=>matchesContentKeyword(safeText, keyword) || matchesContentKeyword(ariaLabel, keyword));
|
|
138
|
+
}
|
|
139
|
+
const isInteractiveElement = (el)=>includes(INTERACTIVE_TAGS, el.tagName.toLowerCase()) || includes(INTERACTIVE_ROLES, (el.getAttribute('role') || '').toLowerCase());
|
|
140
|
+
function clickedControlText(el, targetElementList) {
|
|
141
|
+
for (const candidate of targetElementList)if (isInteractiveElement(candidate)) return controlLabelText(candidate);
|
|
142
|
+
for (const candidate of targetElementList){
|
|
143
|
+
try {
|
|
144
|
+
if (external_globals_mjs_window?.getComputedStyle(candidate).getPropertyValue('cursor') !== 'pointer') break;
|
|
145
|
+
} catch {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
const label = controlLabelText(candidate);
|
|
149
|
+
if (label.safeText || label.ariaLabel) return label;
|
|
150
|
+
}
|
|
151
|
+
return controlLabelText(el);
|
|
152
|
+
}
|
|
153
|
+
function controlLabelText(control) {
|
|
154
|
+
let text = '';
|
|
155
|
+
let firstAriaLabel = '';
|
|
156
|
+
const collect = (node)=>{
|
|
157
|
+
if (isElementNode(node) && shouldCaptureElement(node) && !isSensitiveElement(node)) {
|
|
158
|
+
text += ` ${getSafeText(node)}`;
|
|
159
|
+
firstAriaLabel = firstAriaLabel || node.getAttribute('aria-label') || '';
|
|
160
|
+
each(node.childNodes, collect);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
collect(control);
|
|
164
|
+
text = trim(text.replace(/\s+/g, ' '));
|
|
165
|
+
const safeText = (shouldCaptureValue(text) ? text : '').toLowerCase();
|
|
166
|
+
const ariaLabel = control.getAttribute('aria-label') || (safeText ? '' : firstAriaLabel);
|
|
167
|
+
return {
|
|
168
|
+
safeText,
|
|
169
|
+
ariaLabel: trim(ariaLabel).toLowerCase()
|
|
170
|
+
};
|
|
111
171
|
}
|
|
112
172
|
const DEFAULT_DEAD_CLICK_IGNORE_LIST = [
|
|
113
173
|
'.ph-no-deadclick',
|
|
@@ -161,11 +221,7 @@ function shouldCaptureRageclick(el, _config) {
|
|
|
161
221
|
if (false === selectorIgnoreList) return false;
|
|
162
222
|
if (ignoreTextSelection && isTextSelectionTarget(el)) return false;
|
|
163
223
|
const { targetElementList } = getElementAndParentsForElement(el, false);
|
|
164
|
-
|
|
165
|
-
safeText: getSafeText(element).toLowerCase(),
|
|
166
|
-
ariaLabel: element.getAttribute('aria-label')?.toLowerCase().trim() || ''
|
|
167
|
-
}));
|
|
168
|
-
if (shouldIgnoreByContent(contentIgnorelist, elementsWithText)) return false;
|
|
224
|
+
if (contentIgnorelist && shouldIgnoreByContent(contentIgnorelist, clickedControlText(el, targetElementList))) return false;
|
|
169
225
|
return !checkIfElementsMatchCSSSelector(targetElementList, selectorIgnoreList);
|
|
170
226
|
}
|
|
171
227
|
const cannotCheckForAutocapture = (el)=>!el || isTag(el, 'html') || !isElementNode(el);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/browser-common",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Internal shared browser utilities and extension primitives for PostHog Browser SDKs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -73,13 +73,13 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@posthog/core": "^1.
|
|
77
|
-
"@posthog/types": "^1.
|
|
76
|
+
"@posthog/core": "^1.57.0",
|
|
77
|
+
"@posthog/types": "^1.415.1"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@rslib/core": "0.23.2",
|
|
81
81
|
"jsdom": "^26.1.0",
|
|
82
|
-
"typescript": "
|
|
82
|
+
"typescript": "7.0.2",
|
|
83
83
|
"vitest": "^1.6.1",
|
|
84
84
|
"@posthog-tooling/tsconfig-base": "1.1.1"
|
|
85
85
|
},
|