appium-session-recorder 0.0.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.
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/bun.lock +731 -0
- package/package.json +62 -0
- package/skills/appium-cli-selector-navigator/SKILL.md +349 -0
- package/src/cli/arg-parser.ts +311 -0
- package/src/cli/commands/drive.ts +147 -0
- package/src/cli/commands/index.ts +54 -0
- package/src/cli/commands/proxy.ts +41 -0
- package/src/cli/commands/screen.ts +73 -0
- package/src/cli/commands/selectors.ts +42 -0
- package/src/cli/commands/session.ts +64 -0
- package/src/cli/commands/types.ts +11 -0
- package/src/cli/index.ts +158 -0
- package/src/cli/prompts.ts +64 -0
- package/src/cli/response.ts +44 -0
- package/src/core/appium/client.ts +248 -0
- package/src/core/index.ts +5 -0
- package/src/core/selectors/generate-candidates.ts +155 -0
- package/src/core/selectors/score-candidates.ts +184 -0
- package/src/core/types.ts +79 -0
- package/src/core/xml/parse-source.ts +197 -0
- package/src/index.ts +7 -0
- package/src/server/appium-client.ts +24 -0
- package/src/server/index.ts +6 -0
- package/src/server/interaction-recorder.ts +74 -0
- package/src/server/proxy-middleware.ts +68 -0
- package/src/server/routes.ts +53 -0
- package/src/server/server.ts +43 -0
- package/src/server/types.ts +34 -0
- package/src/ui/bun.lock +311 -0
- package/src/ui/index.html +16 -0
- package/src/ui/package.json +20 -0
- package/src/ui/src/App.css +12 -0
- package/src/ui/src/App.tsx +41 -0
- package/src/ui/src/components/ActionCarousel.css +128 -0
- package/src/ui/src/components/ActionCarousel.tsx +92 -0
- package/src/ui/src/components/Inspector.css +314 -0
- package/src/ui/src/components/Inspector.tsx +265 -0
- package/src/ui/src/components/InteractionCard.css +159 -0
- package/src/ui/src/components/InteractionCard.tsx +60 -0
- package/src/ui/src/components/MainInspector.css +304 -0
- package/src/ui/src/components/MainInspector.tsx +304 -0
- package/src/ui/src/components/Stats.css +27 -0
- package/src/ui/src/components/Timeline.css +31 -0
- package/src/ui/src/components/Timeline.tsx +37 -0
- package/src/ui/src/hooks/useInteractions.ts +73 -0
- package/src/ui/src/index.tsx +11 -0
- package/src/ui/src/services/api.ts +41 -0
- package/src/ui/src/styles/tokens.css +126 -0
- package/src/ui/src/types.ts +34 -0
- package/src/ui/src/utils/__tests__/locators.test.ts +304 -0
- package/src/ui/src/utils/__tests__/xml-parser.test.ts +326 -0
- package/src/ui/src/utils/locators.ts +14 -0
- package/src/ui/src/utils/xml-parser.ts +45 -0
- package/src/ui/tsconfig.json +34 -0
- package/src/ui/tsconfig.node.json +11 -0
- package/src/ui/vite.config.ts +22 -0
- package/tests/cli/arg-parser.test.ts +397 -0
- package/tests/cli/drive-commands.test.ts +151 -0
- package/tests/cli/selectors-best.test.ts +42 -0
- package/tests/cli/session-commands.test.ts +53 -0
- package/tests/core/selector-candidates.test.ts +83 -0
- package/tests/core/selector-scoring.test.ts +75 -0
- package/tests/core/xml-parser.test.ts +56 -0
- package/tests/server/appium-client.test.ts +229 -0
- package/tests/server/interaction-recorder.test.ts +377 -0
- package/tests/server/proxy-middleware.test.ts +343 -0
- package/tests/server/routes.test.ts +305 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +16 -0
- package/vitest.ui.config.ts +15 -0
- package/workflow.gif +0 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseXmlSource } from '../xml-parser';
|
|
3
|
+
|
|
4
|
+
describe('parseXmlSource', () => {
|
|
5
|
+
describe('basic parsing', () => {
|
|
6
|
+
it('should parse empty XML', () => {
|
|
7
|
+
const xml = '<root></root>';
|
|
8
|
+
const result = parseXmlSource(xml);
|
|
9
|
+
|
|
10
|
+
expect(result).toHaveLength(1);
|
|
11
|
+
expect(result[0].type).toBe('root');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should parse XML with type attribute', () => {
|
|
15
|
+
const xml = '<root type="XCUIElementTypeApplication"></root>';
|
|
16
|
+
const result = parseXmlSource(xml);
|
|
17
|
+
|
|
18
|
+
expect(result[0].type).toBe('XCUIElementTypeApplication');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should use tagName when type attribute is missing', () => {
|
|
22
|
+
const xml = '<CustomElement></CustomElement>';
|
|
23
|
+
const result = parseXmlSource(xml);
|
|
24
|
+
|
|
25
|
+
expect(result[0].type).toBe('CustomElement');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('element attributes', () => {
|
|
30
|
+
it('should parse name attribute', () => {
|
|
31
|
+
const xml = '<element name="LoginButton"></element>';
|
|
32
|
+
const result = parseXmlSource(xml);
|
|
33
|
+
|
|
34
|
+
expect(result[0].name).toBe('LoginButton');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should parse label attribute', () => {
|
|
38
|
+
const xml = '<element label="Login"></element>';
|
|
39
|
+
const result = parseXmlSource(xml);
|
|
40
|
+
|
|
41
|
+
expect(result[0].label).toBe('Login');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should parse value attribute', () => {
|
|
45
|
+
const xml = '<element value="test@example.com"></element>';
|
|
46
|
+
const result = parseXmlSource(xml);
|
|
47
|
+
|
|
48
|
+
expect(result[0].value).toBe('test@example.com');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should parse enabled attribute', () => {
|
|
52
|
+
const xml = '<element enabled="true"></element>';
|
|
53
|
+
const result = parseXmlSource(xml);
|
|
54
|
+
|
|
55
|
+
expect(result[0].enabled).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should parse visible attribute', () => {
|
|
59
|
+
const xml = '<element visible="true"></element>';
|
|
60
|
+
const result = parseXmlSource(xml);
|
|
61
|
+
|
|
62
|
+
expect(result[0].visible).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should parse accessible attribute', () => {
|
|
66
|
+
const xml = '<element accessible="true"></element>';
|
|
67
|
+
const result = parseXmlSource(xml);
|
|
68
|
+
|
|
69
|
+
expect(result[0].accessible).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should default boolean attributes to false', () => {
|
|
73
|
+
const xml = '<element></element>';
|
|
74
|
+
const result = parseXmlSource(xml);
|
|
75
|
+
|
|
76
|
+
expect(result[0].enabled).toBe(false);
|
|
77
|
+
expect(result[0].visible).toBe(false);
|
|
78
|
+
expect(result[0].accessible).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should default string attributes to empty string', () => {
|
|
82
|
+
const xml = '<element></element>';
|
|
83
|
+
const result = parseXmlSource(xml);
|
|
84
|
+
|
|
85
|
+
expect(result[0].name).toBe('');
|
|
86
|
+
expect(result[0].label).toBe('');
|
|
87
|
+
expect(result[0].value).toBe('');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('coordinates', () => {
|
|
92
|
+
it('should parse x, y, width, height attributes', () => {
|
|
93
|
+
const xml = '<element x="100" y="200" width="50" height="30"></element>';
|
|
94
|
+
const result = parseXmlSource(xml);
|
|
95
|
+
|
|
96
|
+
expect(result[0].x).toBe(100);
|
|
97
|
+
expect(result[0].y).toBe(200);
|
|
98
|
+
expect(result[0].width).toBe(50);
|
|
99
|
+
expect(result[0].height).toBe(30);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should default coordinates to 0', () => {
|
|
103
|
+
const xml = '<element></element>';
|
|
104
|
+
const result = parseXmlSource(xml);
|
|
105
|
+
|
|
106
|
+
expect(result[0].x).toBe(0);
|
|
107
|
+
expect(result[0].y).toBe(0);
|
|
108
|
+
expect(result[0].width).toBe(0);
|
|
109
|
+
expect(result[0].height).toBe(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should handle negative coordinates', () => {
|
|
113
|
+
const xml = '<element x="-10" y="-20"></element>';
|
|
114
|
+
const result = parseXmlSource(xml);
|
|
115
|
+
|
|
116
|
+
expect(result[0].x).toBe(-10);
|
|
117
|
+
expect(result[0].y).toBe(-20);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('xpath generation', () => {
|
|
122
|
+
it('should generate xpath for root element', () => {
|
|
123
|
+
const xml = '<root></root>';
|
|
124
|
+
const result = parseXmlSource(xml);
|
|
125
|
+
|
|
126
|
+
expect(result[0].xpath).toBe('/root[1]');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should generate xpath with type attribute', () => {
|
|
130
|
+
const xml = '<root type="XCUIElementTypeApplication"></root>';
|
|
131
|
+
const result = parseXmlSource(xml);
|
|
132
|
+
|
|
133
|
+
expect(result[0].xpath).toBe('/XCUIElementTypeApplication[1]');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should generate nested xpath', () => {
|
|
137
|
+
const xml = `
|
|
138
|
+
<app type="Application">
|
|
139
|
+
<window type="Window">
|
|
140
|
+
<button type="Button"></button>
|
|
141
|
+
</window>
|
|
142
|
+
</app>
|
|
143
|
+
`;
|
|
144
|
+
const result = parseXmlSource(xml);
|
|
145
|
+
|
|
146
|
+
expect(result[0].xpath).toBe('/Application[1]');
|
|
147
|
+
expect(result[1].xpath).toBe('/Application[1]/Window[1]');
|
|
148
|
+
expect(result[2].xpath).toBe('/Application[1]/Window[1]/Button[1]');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should generate xpath with sibling indexing', () => {
|
|
152
|
+
const xml = `
|
|
153
|
+
<app type="Application">
|
|
154
|
+
<button type="Button"></button>
|
|
155
|
+
<button type="Button"></button>
|
|
156
|
+
<button type="Button"></button>
|
|
157
|
+
</app>
|
|
158
|
+
`;
|
|
159
|
+
const result = parseXmlSource(xml);
|
|
160
|
+
|
|
161
|
+
expect(result[1].xpath).toBe('/Application[1]/Button[1]');
|
|
162
|
+
expect(result[2].xpath).toBe('/Application[1]/Button[2]');
|
|
163
|
+
expect(result[3].xpath).toBe('/Application[1]/Button[3]');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle mixed child types', () => {
|
|
167
|
+
const xml = `
|
|
168
|
+
<app type="Application">
|
|
169
|
+
<button type="Button"></button>
|
|
170
|
+
<text type="Text"></text>
|
|
171
|
+
<button type="Button"></button>
|
|
172
|
+
</app>
|
|
173
|
+
`;
|
|
174
|
+
const result = parseXmlSource(xml);
|
|
175
|
+
|
|
176
|
+
expect(result[1].xpath).toBe('/Application[1]/Button[1]');
|
|
177
|
+
expect(result[2].xpath).toBe('/Application[1]/Text[1]');
|
|
178
|
+
expect(result[3].xpath).toBe('/Application[1]/Button[2]');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('nested elements', () => {
|
|
183
|
+
it('should parse all nested elements', () => {
|
|
184
|
+
const xml = `
|
|
185
|
+
<app type="Application">
|
|
186
|
+
<window type="Window">
|
|
187
|
+
<view type="View">
|
|
188
|
+
<button type="Button"></button>
|
|
189
|
+
<text type="Text"></text>
|
|
190
|
+
</view>
|
|
191
|
+
</window>
|
|
192
|
+
</app>
|
|
193
|
+
`;
|
|
194
|
+
const result = parseXmlSource(xml);
|
|
195
|
+
|
|
196
|
+
expect(result).toHaveLength(5);
|
|
197
|
+
expect(result.map(e => e.type)).toEqual([
|
|
198
|
+
'Application',
|
|
199
|
+
'Window',
|
|
200
|
+
'View',
|
|
201
|
+
'Button',
|
|
202
|
+
'Text',
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should preserve element order (depth-first)', () => {
|
|
207
|
+
const xml = `
|
|
208
|
+
<root>
|
|
209
|
+
<a>
|
|
210
|
+
<b></b>
|
|
211
|
+
</a>
|
|
212
|
+
<c></c>
|
|
213
|
+
</root>
|
|
214
|
+
`;
|
|
215
|
+
const result = parseXmlSource(xml);
|
|
216
|
+
|
|
217
|
+
expect(result.map(e => e.type)).toEqual(['root', 'a', 'b', 'c']);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('real-world Appium XML', () => {
|
|
222
|
+
it('should parse iOS page source', () => {
|
|
223
|
+
const xml = `
|
|
224
|
+
<AppiumAUT type="XCUIElementTypeApplication" name="MyApp" label="" enabled="true" visible="true">
|
|
225
|
+
<XCUIElementTypeWindow type="XCUIElementTypeWindow" enabled="true" visible="true" x="0" y="0" width="390" height="844">
|
|
226
|
+
<XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="0" width="390" height="844">
|
|
227
|
+
<XCUIElementTypeButton type="XCUIElementTypeButton" name="loginButton" label="Log In" enabled="true" visible="true" x="20" y="400" width="350" height="44">
|
|
228
|
+
</XCUIElementTypeButton>
|
|
229
|
+
</XCUIElementTypeOther>
|
|
230
|
+
</XCUIElementTypeWindow>
|
|
231
|
+
</AppiumAUT>
|
|
232
|
+
`;
|
|
233
|
+
const result = parseXmlSource(xml);
|
|
234
|
+
|
|
235
|
+
expect(result).toHaveLength(4);
|
|
236
|
+
|
|
237
|
+
const button = result.find(e => e.type === 'XCUIElementTypeButton');
|
|
238
|
+
expect(button).toBeDefined();
|
|
239
|
+
expect(button!.name).toBe('loginButton');
|
|
240
|
+
expect(button!.label).toBe('Log In');
|
|
241
|
+
expect(button!.enabled).toBe(true);
|
|
242
|
+
expect(button!.visible).toBe(true);
|
|
243
|
+
expect(button!.x).toBe(20);
|
|
244
|
+
expect(button!.y).toBe(400);
|
|
245
|
+
expect(button!.width).toBe(350);
|
|
246
|
+
expect(button!.height).toBe(44);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should parse Android page source', () => {
|
|
250
|
+
const xml = `
|
|
251
|
+
<hierarchy type="android.widget.FrameLayout">
|
|
252
|
+
<android.widget.LinearLayout type="android.widget.LinearLayout" x="0" y="0" width="1080" height="2400">
|
|
253
|
+
<android.widget.Button type="android.widget.Button" name="" label="" value="" enabled="true" visible="true" x="100" y="500" width="200" height="80">
|
|
254
|
+
</android.widget.Button>
|
|
255
|
+
</android.widget.LinearLayout>
|
|
256
|
+
</hierarchy>
|
|
257
|
+
`;
|
|
258
|
+
const result = parseXmlSource(xml);
|
|
259
|
+
|
|
260
|
+
expect(result).toHaveLength(3);
|
|
261
|
+
expect(result.map(e => e.type)).toEqual([
|
|
262
|
+
'android.widget.FrameLayout',
|
|
263
|
+
'android.widget.LinearLayout',
|
|
264
|
+
'android.widget.Button',
|
|
265
|
+
]);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('edge cases', () => {
|
|
270
|
+
it('should handle empty document', () => {
|
|
271
|
+
const xml = '';
|
|
272
|
+
// parseXmlSource expects valid XML, empty string may produce error
|
|
273
|
+
// In real usage, empty strings are filtered out before calling
|
|
274
|
+
const result = parseXmlSource(xml);
|
|
275
|
+
// DOMParser will create an error document or empty result
|
|
276
|
+
expect(Array.isArray(result)).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should handle deeply nested elements', () => {
|
|
280
|
+
const xml = `
|
|
281
|
+
<level1 type="L1">
|
|
282
|
+
<level2 type="L2">
|
|
283
|
+
<level3 type="L3">
|
|
284
|
+
<level4 type="L4">
|
|
285
|
+
<level5 type="L5">
|
|
286
|
+
</level5>
|
|
287
|
+
</level4>
|
|
288
|
+
</level3>
|
|
289
|
+
</level2>
|
|
290
|
+
</level1>
|
|
291
|
+
`;
|
|
292
|
+
const result = parseXmlSource(xml);
|
|
293
|
+
|
|
294
|
+
expect(result).toHaveLength(5);
|
|
295
|
+
expect(result[4].xpath).toBe('/L1[1]/L2[1]/L3[1]/L4[1]/L5[1]');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should handle elements with special characters in attributes', () => {
|
|
299
|
+
const xml = '<element name="Button & Text" label="Click <here>"></element>';
|
|
300
|
+
const result = parseXmlSource(xml);
|
|
301
|
+
|
|
302
|
+
expect(result[0].name).toBe('Button & Text');
|
|
303
|
+
expect(result[0].label).toBe('Click <here>');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should handle many sibling elements', () => {
|
|
307
|
+
let children = '';
|
|
308
|
+
for (let i = 0; i < 100; i++) {
|
|
309
|
+
children += `<item type="Item" name="item${i}"></item>`;
|
|
310
|
+
}
|
|
311
|
+
const xml = `<root>${children}</root>`;
|
|
312
|
+
const result = parseXmlSource(xml);
|
|
313
|
+
|
|
314
|
+
expect(result).toHaveLength(101); // root + 100 items
|
|
315
|
+
expect(result[100].xpath).toBe('/root[1]/Item[100]');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should include node reference in result', () => {
|
|
319
|
+
const xml = '<element name="TestElement"></element>';
|
|
320
|
+
const result = parseXmlSource(xml);
|
|
321
|
+
|
|
322
|
+
expect(result[0].node).toBeDefined();
|
|
323
|
+
expect(result[0].node instanceof Element).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { generateLegacyLocators } from '../../../core/selectors/generate-candidates';
|
|
2
|
+
import type { ParsedElement, Locator } from '../types';
|
|
3
|
+
|
|
4
|
+
export function generateLocators(element: ParsedElement): Locator[] {
|
|
5
|
+
return generateLegacyLocators({
|
|
6
|
+
type: element.type,
|
|
7
|
+
name: element.name,
|
|
8
|
+
label: element.label,
|
|
9
|
+
xpath: element.xpath,
|
|
10
|
+
}).map(locator => ({
|
|
11
|
+
strategy: locator.strategy,
|
|
12
|
+
value: locator.value,
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ParsedElement } from '../types';
|
|
2
|
+
|
|
3
|
+
export function parseXmlSource(xmlString: string): ParsedElement[] {
|
|
4
|
+
const parser = new DOMParser();
|
|
5
|
+
const doc = parser.parseFromString(xmlString, 'text/xml');
|
|
6
|
+
const elements: ParsedElement[] = [];
|
|
7
|
+
|
|
8
|
+
function traverse(node: Element, xpath = '', index = 0) {
|
|
9
|
+
if (node.nodeType !== 1) return;
|
|
10
|
+
|
|
11
|
+
const type = node.getAttribute('type') || node.tagName;
|
|
12
|
+
const x = parseInt(node.getAttribute('x') || '0');
|
|
13
|
+
const y = parseInt(node.getAttribute('y') || '0');
|
|
14
|
+
const width = parseInt(node.getAttribute('width') || '0');
|
|
15
|
+
const height = parseInt(node.getAttribute('height') || '0');
|
|
16
|
+
|
|
17
|
+
const currentXpath = xpath + '/' + type + '[' + (index + 1) + ']';
|
|
18
|
+
|
|
19
|
+
elements.push({
|
|
20
|
+
type,
|
|
21
|
+
name: node.getAttribute('name') || '',
|
|
22
|
+
label: node.getAttribute('label') || '',
|
|
23
|
+
value: node.getAttribute('value') || '',
|
|
24
|
+
enabled: node.getAttribute('enabled') === 'true',
|
|
25
|
+
visible: node.getAttribute('visible') === 'true',
|
|
26
|
+
accessible: node.getAttribute('accessible') === 'true',
|
|
27
|
+
x, y, width, height,
|
|
28
|
+
xpath: currentXpath,
|
|
29
|
+
node
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const childCounts: Record<string, number> = {};
|
|
33
|
+
for (const child of Array.from(node.children)) {
|
|
34
|
+
const childType = child.getAttribute('type') || child.tagName;
|
|
35
|
+
childCounts[childType] = (childCounts[childType] || 0);
|
|
36
|
+
traverse(child as Element, currentXpath, childCounts[childType]);
|
|
37
|
+
childCounts[childType]++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (doc.documentElement) {
|
|
42
|
+
traverse(doc.documentElement);
|
|
43
|
+
}
|
|
44
|
+
return elements;
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020",
|
|
7
|
+
"DOM",
|
|
8
|
+
"DOM.Iterable"
|
|
9
|
+
],
|
|
10
|
+
"jsx": "preserve",
|
|
11
|
+
"jsxImportSource": "solid-js",
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"paths": {
|
|
21
|
+
"@/*": [
|
|
22
|
+
"./src/*"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"include": [
|
|
27
|
+
"src"
|
|
28
|
+
],
|
|
29
|
+
"references": [
|
|
30
|
+
{
|
|
31
|
+
"path": "./tsconfig.node.json"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import solid from 'vite-plugin-solid';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [solid()],
|
|
7
|
+
root: '.',
|
|
8
|
+
base: '/_recorder/',
|
|
9
|
+
build: {
|
|
10
|
+
outDir: '../../dist/ui',
|
|
11
|
+
emptyOutDir: true,
|
|
12
|
+
},
|
|
13
|
+
server: {
|
|
14
|
+
port: 3000,
|
|
15
|
+
},
|
|
16
|
+
resolve: {
|
|
17
|
+
alias: {
|
|
18
|
+
'@': path.resolve(__dirname, './src'),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|