@upstart.gg/vite-plugins 0.0.37
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/vite-plugin-upstart-attrs.d.ts +29 -0
- package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-attrs.js +323 -0
- package/dist/vite-plugin-upstart-attrs.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/plugin.d.ts +15 -0
- package/dist/vite-plugin-upstart-editor/plugin.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/plugin.js +55 -0
- package/dist/vite-plugin-upstart-editor/plugin.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts +12 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +57 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts +12 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +91 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts +22 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.js +62 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts +15 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +292 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +126 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.js +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts +15 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.js +26 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.js.map +1 -0
- package/dist/vite-plugin-upstart-theme.d.ts +22 -0
- package/dist/vite-plugin-upstart-theme.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-theme.js +179 -0
- package/dist/vite-plugin-upstart-theme.js.map +1 -0
- package/package.json +63 -0
- package/src/tests/fixtures/routes/default-layout.tsx +10 -0
- package/src/tests/fixtures/routes/dynamic-route.tsx +10 -0
- package/src/tests/fixtures/routes/missing-attributes.tsx +8 -0
- package/src/tests/fixtures/routes/missing-path.tsx +9 -0
- package/src/tests/fixtures/routes/valid-full.tsx +15 -0
- package/src/tests/fixtures/routes/valid-minimal.tsx +10 -0
- package/src/tests/fixtures/routes/with-comments.tsx +12 -0
- package/src/tests/fixtures/routes/with-nested-objects.tsx +15 -0
- package/src/tests/upstart-editor-api.test.ts +367 -0
- package/src/tests/vite-plugin-upstart-attrs.test.ts +1189 -0
- package/src/tests/vite-plugin-upstart-editor.test.ts +81 -0
- package/src/upstart-editor-api.ts +204 -0
- package/src/vite-plugin-upstart-attrs.ts +708 -0
- package/src/vite-plugin-upstart-editor/PLAN.md +1391 -0
- package/src/vite-plugin-upstart-editor/plugin.ts +73 -0
- package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +80 -0
- package/src/vite-plugin-upstart-editor/runtime/hover-overlay.ts +135 -0
- package/src/vite-plugin-upstart-editor/runtime/index.ts +90 -0
- package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +401 -0
- package/src/vite-plugin-upstart-editor/runtime/types.ts +120 -0
- package/src/vite-plugin-upstart-editor/runtime/utils.ts +34 -0
- package/src/vite-plugin-upstart-theme.ts +314 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { UpstartEditorAPI, EditableRegistry } from "../upstart-editor-api";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import os from "os";
|
|
6
|
+
|
|
7
|
+
describe("UpstartEditorAPI", () => {
|
|
8
|
+
let tempDir: string;
|
|
9
|
+
let api: UpstartEditorAPI;
|
|
10
|
+
let registryPath: string;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
// Create a temporary directory for test files
|
|
14
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "upstart-test-"));
|
|
15
|
+
registryPath = path.join(tempDir, "registry.json");
|
|
16
|
+
api = new UpstartEditorAPI(tempDir, registryPath);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
// Clean up temporary directory
|
|
21
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("editText", () => {
|
|
25
|
+
test("should edit text content at correct offset", async () => {
|
|
26
|
+
// Create a test source file
|
|
27
|
+
const sourceContent = `<div>Hello World</div>`;
|
|
28
|
+
const sourcePath = path.join(tempDir, "test.tsx");
|
|
29
|
+
await fs.writeFile(sourcePath, sourceContent);
|
|
30
|
+
|
|
31
|
+
// Create registry
|
|
32
|
+
const registry: EditableRegistry = {
|
|
33
|
+
version: 1,
|
|
34
|
+
generatedAt: new Date().toISOString(),
|
|
35
|
+
elements: {
|
|
36
|
+
"test.tsx:1": {
|
|
37
|
+
file: "test.tsx",
|
|
38
|
+
type: "text",
|
|
39
|
+
startOffset: 5,
|
|
40
|
+
endOffset: 16,
|
|
41
|
+
originalContent: "Hello World",
|
|
42
|
+
context: { parentTag: "div" },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
await fs.writeFile(registryPath, JSON.stringify(registry));
|
|
47
|
+
|
|
48
|
+
// Edit the text
|
|
49
|
+
const result = await api.editText("test.tsx:1", "Hello Universe");
|
|
50
|
+
|
|
51
|
+
expect(result.success).toBe(true);
|
|
52
|
+
|
|
53
|
+
// Verify file was updated
|
|
54
|
+
const updatedContent = await fs.readFile(sourcePath, "utf-8");
|
|
55
|
+
expect(updatedContent).toBe("<div>Hello Universe</div>");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("should update registry after edit", async () => {
|
|
59
|
+
const sourceContent = `<div>Original</div>`;
|
|
60
|
+
const sourcePath = path.join(tempDir, "test.tsx");
|
|
61
|
+
await fs.writeFile(sourcePath, sourceContent);
|
|
62
|
+
|
|
63
|
+
const registry: EditableRegistry = {
|
|
64
|
+
version: 1,
|
|
65
|
+
generatedAt: new Date().toISOString(),
|
|
66
|
+
elements: {
|
|
67
|
+
"test.tsx:1": {
|
|
68
|
+
file: "test.tsx",
|
|
69
|
+
type: "text",
|
|
70
|
+
startOffset: 5,
|
|
71
|
+
endOffset: 13,
|
|
72
|
+
originalContent: "Original",
|
|
73
|
+
context: { parentTag: "div" },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
await fs.writeFile(registryPath, JSON.stringify(registry));
|
|
78
|
+
|
|
79
|
+
await api.editText("test.tsx:1", "Modified");
|
|
80
|
+
|
|
81
|
+
// Read updated registry
|
|
82
|
+
const updatedRegistry = JSON.parse(await fs.readFile(registryPath, "utf-8"));
|
|
83
|
+
expect(updatedRegistry.elements["test.tsx:1"].originalContent).toBe("Modified");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("should shift subsequent offsets after edit", async () => {
|
|
87
|
+
const sourceContent = `<div>First</div><span>Second</span>`;
|
|
88
|
+
const sourcePath = path.join(tempDir, "test.tsx");
|
|
89
|
+
await fs.writeFile(sourcePath, sourceContent);
|
|
90
|
+
|
|
91
|
+
const registry: EditableRegistry = {
|
|
92
|
+
version: 1,
|
|
93
|
+
generatedAt: new Date().toISOString(),
|
|
94
|
+
elements: {
|
|
95
|
+
"test.tsx:1": {
|
|
96
|
+
file: "test.tsx",
|
|
97
|
+
type: "text",
|
|
98
|
+
startOffset: 5,
|
|
99
|
+
endOffset: 10,
|
|
100
|
+
originalContent: "First",
|
|
101
|
+
context: { parentTag: "div" },
|
|
102
|
+
},
|
|
103
|
+
"test.tsx:2": {
|
|
104
|
+
file: "test.tsx",
|
|
105
|
+
type: "text",
|
|
106
|
+
startOffset: 22,
|
|
107
|
+
endOffset: 28,
|
|
108
|
+
originalContent: "Second",
|
|
109
|
+
context: { parentTag: "span" },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
await fs.writeFile(registryPath, JSON.stringify(registry));
|
|
114
|
+
|
|
115
|
+
// Edit first element (adding 5 characters: "First" -> "First!!!!!")
|
|
116
|
+
await api.editText("test.tsx:1", "First!!!!!");
|
|
117
|
+
|
|
118
|
+
// Read updated registry
|
|
119
|
+
const updatedRegistry = JSON.parse(await fs.readFile(registryPath, "utf-8"));
|
|
120
|
+
|
|
121
|
+
// Second element offset should have shifted by 5
|
|
122
|
+
expect(updatedRegistry.elements["test.tsx:2"].startOffset).toBe(27);
|
|
123
|
+
expect(updatedRegistry.elements["test.tsx:2"].endOffset).toBe(33);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("should fail if element not found", async () => {
|
|
127
|
+
const registry: EditableRegistry = {
|
|
128
|
+
version: 1,
|
|
129
|
+
generatedAt: new Date().toISOString(),
|
|
130
|
+
elements: {},
|
|
131
|
+
};
|
|
132
|
+
await fs.writeFile(registryPath, JSON.stringify(registry));
|
|
133
|
+
|
|
134
|
+
const result = await api.editText("nonexistent:1", "New text");
|
|
135
|
+
|
|
136
|
+
expect(result.success).toBe(false);
|
|
137
|
+
expect(result.error).toContain("not found");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("should fail if element is not text type", async () => {
|
|
141
|
+
const registry: EditableRegistry = {
|
|
142
|
+
version: 1,
|
|
143
|
+
generatedAt: new Date().toISOString(),
|
|
144
|
+
elements: {
|
|
145
|
+
"test.tsx:1": {
|
|
146
|
+
file: "test.tsx",
|
|
147
|
+
type: "className",
|
|
148
|
+
startOffset: 5,
|
|
149
|
+
endOffset: 10,
|
|
150
|
+
originalContent: "px-4",
|
|
151
|
+
context: { parentTag: "div" },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
await fs.writeFile(registryPath, JSON.stringify(registry));
|
|
156
|
+
|
|
157
|
+
const result = await api.editText("test.tsx:1", "New text");
|
|
158
|
+
|
|
159
|
+
expect(result.success).toBe(false);
|
|
160
|
+
expect(result.error).toContain("not a text element");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("should find content by search if offset has shifted", async () => {
|
|
164
|
+
// Simulate a file where content has shifted (e.g., due to manual edit)
|
|
165
|
+
const sourceContent = `\n\n<div>Hello World</div>`;
|
|
166
|
+
const sourcePath = path.join(tempDir, "test.tsx");
|
|
167
|
+
await fs.writeFile(sourcePath, sourceContent);
|
|
168
|
+
|
|
169
|
+
// Registry has old offsets (before the newlines were added)
|
|
170
|
+
const registry: EditableRegistry = {
|
|
171
|
+
version: 1,
|
|
172
|
+
generatedAt: new Date().toISOString(),
|
|
173
|
+
elements: {
|
|
174
|
+
"test.tsx:1": {
|
|
175
|
+
file: "test.tsx",
|
|
176
|
+
type: "text",
|
|
177
|
+
startOffset: 5, // Old offset (now content is at offset 7)
|
|
178
|
+
endOffset: 16,
|
|
179
|
+
originalContent: "Hello World",
|
|
180
|
+
context: { parentTag: "div" },
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
await fs.writeFile(registryPath, JSON.stringify(registry));
|
|
185
|
+
|
|
186
|
+
// Should still find and edit the content
|
|
187
|
+
const result = await api.editText("test.tsx:1", "Hello Universe");
|
|
188
|
+
|
|
189
|
+
expect(result.success).toBe(true);
|
|
190
|
+
|
|
191
|
+
const updatedContent = await fs.readFile(sourcePath, "utf-8");
|
|
192
|
+
expect(updatedContent).toBe("\n\n<div>Hello Universe</div>");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("should fail if original content no longer exists", async () => {
|
|
196
|
+
const sourceContent = `<div>Completely Different</div>`;
|
|
197
|
+
const sourcePath = path.join(tempDir, "test.tsx");
|
|
198
|
+
await fs.writeFile(sourcePath, sourceContent);
|
|
199
|
+
|
|
200
|
+
const registry: EditableRegistry = {
|
|
201
|
+
version: 1,
|
|
202
|
+
generatedAt: new Date().toISOString(),
|
|
203
|
+
elements: {
|
|
204
|
+
"test.tsx:1": {
|
|
205
|
+
file: "test.tsx",
|
|
206
|
+
type: "text",
|
|
207
|
+
startOffset: 5,
|
|
208
|
+
endOffset: 16,
|
|
209
|
+
originalContent: "Hello World",
|
|
210
|
+
context: { parentTag: "div" },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
await fs.writeFile(registryPath, JSON.stringify(registry));
|
|
215
|
+
|
|
216
|
+
const result = await api.editText("test.tsx:1", "New content");
|
|
217
|
+
|
|
218
|
+
expect(result.success).toBe(false);
|
|
219
|
+
expect(result.error).toContain("not found in file");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("editClassName", () => {
|
|
224
|
+
test("should edit className at correct offset", async () => {
|
|
225
|
+
const sourceContent = `<div className="px-4 py-2">Hello</div>`;
|
|
226
|
+
const sourcePath = path.join(tempDir, "test.tsx");
|
|
227
|
+
await fs.writeFile(sourcePath, sourceContent);
|
|
228
|
+
|
|
229
|
+
const registry: EditableRegistry = {
|
|
230
|
+
version: 1,
|
|
231
|
+
generatedAt: new Date().toISOString(),
|
|
232
|
+
elements: {
|
|
233
|
+
"test.tsx:1": {
|
|
234
|
+
file: "test.tsx",
|
|
235
|
+
type: "className",
|
|
236
|
+
startOffset: 16,
|
|
237
|
+
endOffset: 24,
|
|
238
|
+
originalContent: "px-4 py-2",
|
|
239
|
+
context: { parentTag: "div" },
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
await fs.writeFile(registryPath, JSON.stringify(registry));
|
|
244
|
+
|
|
245
|
+
const result = await api.editClassName("test.tsx:1", "px-8 py-4 bg-blue-500");
|
|
246
|
+
|
|
247
|
+
expect(result.success).toBe(true);
|
|
248
|
+
|
|
249
|
+
const updatedContent = await fs.readFile(sourcePath, "utf-8");
|
|
250
|
+
expect(updatedContent).toBe(`<div className="px-8 py-4 bg-blue-500">Hello</div>`);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("should fail if element is not className type", async () => {
|
|
254
|
+
const registry: EditableRegistry = {
|
|
255
|
+
version: 1,
|
|
256
|
+
generatedAt: new Date().toISOString(),
|
|
257
|
+
elements: {
|
|
258
|
+
"test.tsx:1": {
|
|
259
|
+
file: "test.tsx",
|
|
260
|
+
type: "text",
|
|
261
|
+
startOffset: 5,
|
|
262
|
+
endOffset: 10,
|
|
263
|
+
originalContent: "Hello",
|
|
264
|
+
context: { parentTag: "div" },
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
await fs.writeFile(registryPath, JSON.stringify(registry));
|
|
269
|
+
|
|
270
|
+
const result = await api.editClassName("test.tsx:1", "new-class");
|
|
271
|
+
|
|
272
|
+
expect(result.success).toBe(false);
|
|
273
|
+
expect(result.error).toContain("not a className element");
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe("helper methods", () => {
|
|
278
|
+
test("getElement should return entry by ID", async () => {
|
|
279
|
+
const registry: EditableRegistry = {
|
|
280
|
+
version: 1,
|
|
281
|
+
generatedAt: new Date().toISOString(),
|
|
282
|
+
elements: {
|
|
283
|
+
"test.tsx:1": {
|
|
284
|
+
file: "test.tsx",
|
|
285
|
+
type: "text",
|
|
286
|
+
startOffset: 5,
|
|
287
|
+
endOffset: 10,
|
|
288
|
+
originalContent: "Hello",
|
|
289
|
+
context: { parentTag: "div" },
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
api.setRegistry(registry);
|
|
294
|
+
|
|
295
|
+
const entry = api.getElement("test.tsx:1");
|
|
296
|
+
expect(entry?.originalContent).toBe("Hello");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("getElementsByType should filter by type", async () => {
|
|
300
|
+
const registry: EditableRegistry = {
|
|
301
|
+
version: 1,
|
|
302
|
+
generatedAt: new Date().toISOString(),
|
|
303
|
+
elements: {
|
|
304
|
+
"test.tsx:1": {
|
|
305
|
+
file: "test.tsx",
|
|
306
|
+
type: "text",
|
|
307
|
+
startOffset: 5,
|
|
308
|
+
endOffset: 10,
|
|
309
|
+
originalContent: "Hello",
|
|
310
|
+
context: { parentTag: "div" },
|
|
311
|
+
},
|
|
312
|
+
"test.tsx:2": {
|
|
313
|
+
file: "test.tsx",
|
|
314
|
+
type: "className",
|
|
315
|
+
startOffset: 20,
|
|
316
|
+
endOffset: 25,
|
|
317
|
+
originalContent: "px-4",
|
|
318
|
+
context: { parentTag: "div" },
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
api.setRegistry(registry);
|
|
323
|
+
|
|
324
|
+
const textElements = api.getElementsByType("text");
|
|
325
|
+
const classElements = api.getElementsByType("className");
|
|
326
|
+
|
|
327
|
+
expect(Object.keys(textElements).length).toBe(1);
|
|
328
|
+
expect(Object.keys(classElements).length).toBe(1);
|
|
329
|
+
expect(textElements["test.tsx:1"]).toBeDefined();
|
|
330
|
+
expect(classElements["test.tsx:2"]).toBeDefined();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("getElementsByFile should filter by file", async () => {
|
|
334
|
+
const registry: EditableRegistry = {
|
|
335
|
+
version: 1,
|
|
336
|
+
generatedAt: new Date().toISOString(),
|
|
337
|
+
elements: {
|
|
338
|
+
"file1.tsx:1": {
|
|
339
|
+
file: "file1.tsx",
|
|
340
|
+
type: "text",
|
|
341
|
+
startOffset: 5,
|
|
342
|
+
endOffset: 10,
|
|
343
|
+
originalContent: "Hello",
|
|
344
|
+
context: { parentTag: "div" },
|
|
345
|
+
},
|
|
346
|
+
"file2.tsx:1": {
|
|
347
|
+
file: "file2.tsx",
|
|
348
|
+
type: "text",
|
|
349
|
+
startOffset: 5,
|
|
350
|
+
endOffset: 10,
|
|
351
|
+
originalContent: "World",
|
|
352
|
+
context: { parentTag: "div" },
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
api.setRegistry(registry);
|
|
357
|
+
|
|
358
|
+
const file1Elements = api.getElementsByFile("file1.tsx");
|
|
359
|
+
const file2Elements = api.getElementsByFile("file2.tsx");
|
|
360
|
+
|
|
361
|
+
expect(Object.keys(file1Elements).length).toBe(1);
|
|
362
|
+
expect(Object.keys(file2Elements).length).toBe(1);
|
|
363
|
+
expect(file1Elements["file1.tsx:1"]).toBeDefined();
|
|
364
|
+
expect(file2Elements["file2.tsx:1"]).toBeDefined();
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|