@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.
Files changed (58) hide show
  1. package/dist/vite-plugin-upstart-attrs.d.ts +29 -0
  2. package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -0
  3. package/dist/vite-plugin-upstart-attrs.js +323 -0
  4. package/dist/vite-plugin-upstart-attrs.js.map +1 -0
  5. package/dist/vite-plugin-upstart-editor/plugin.d.ts +15 -0
  6. package/dist/vite-plugin-upstart-editor/plugin.d.ts.map +1 -0
  7. package/dist/vite-plugin-upstart-editor/plugin.js +55 -0
  8. package/dist/vite-plugin-upstart-editor/plugin.js.map +1 -0
  9. package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts +12 -0
  10. package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts.map +1 -0
  11. package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +57 -0
  12. package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -0
  13. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts +12 -0
  14. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts.map +1 -0
  15. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +91 -0
  16. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -0
  17. package/dist/vite-plugin-upstart-editor/runtime/index.d.ts +22 -0
  18. package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -0
  19. package/dist/vite-plugin-upstart-editor/runtime/index.js +62 -0
  20. package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -0
  21. package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts +15 -0
  22. package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -0
  23. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +292 -0
  24. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -0
  25. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +126 -0
  26. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -0
  27. package/dist/vite-plugin-upstart-editor/runtime/types.js +1 -0
  28. package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts +15 -0
  29. package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts.map +1 -0
  30. package/dist/vite-plugin-upstart-editor/runtime/utils.js +26 -0
  31. package/dist/vite-plugin-upstart-editor/runtime/utils.js.map +1 -0
  32. package/dist/vite-plugin-upstart-theme.d.ts +22 -0
  33. package/dist/vite-plugin-upstart-theme.d.ts.map +1 -0
  34. package/dist/vite-plugin-upstart-theme.js +179 -0
  35. package/dist/vite-plugin-upstart-theme.js.map +1 -0
  36. package/package.json +63 -0
  37. package/src/tests/fixtures/routes/default-layout.tsx +10 -0
  38. package/src/tests/fixtures/routes/dynamic-route.tsx +10 -0
  39. package/src/tests/fixtures/routes/missing-attributes.tsx +8 -0
  40. package/src/tests/fixtures/routes/missing-path.tsx +9 -0
  41. package/src/tests/fixtures/routes/valid-full.tsx +15 -0
  42. package/src/tests/fixtures/routes/valid-minimal.tsx +10 -0
  43. package/src/tests/fixtures/routes/with-comments.tsx +12 -0
  44. package/src/tests/fixtures/routes/with-nested-objects.tsx +15 -0
  45. package/src/tests/upstart-editor-api.test.ts +367 -0
  46. package/src/tests/vite-plugin-upstart-attrs.test.ts +1189 -0
  47. package/src/tests/vite-plugin-upstart-editor.test.ts +81 -0
  48. package/src/upstart-editor-api.ts +204 -0
  49. package/src/vite-plugin-upstart-attrs.ts +708 -0
  50. package/src/vite-plugin-upstart-editor/PLAN.md +1391 -0
  51. package/src/vite-plugin-upstart-editor/plugin.ts +73 -0
  52. package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +80 -0
  53. package/src/vite-plugin-upstart-editor/runtime/hover-overlay.ts +135 -0
  54. package/src/vite-plugin-upstart-editor/runtime/index.ts +90 -0
  55. package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +401 -0
  56. package/src/vite-plugin-upstart-editor/runtime/types.ts +120 -0
  57. package/src/vite-plugin-upstart-editor/runtime/utils.ts +34 -0
  58. package/src/vite-plugin-upstart-theme.ts +314 -0
@@ -0,0 +1,81 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { upstartEditor } from "../vite-plugin-upstart-editor/plugin";
3
+ import type { UpstartEditorPluginOptions } from "../vite-plugin-upstart-editor/runtime/types";
4
+
5
+ const MAIN_ID = "/project/src/main.tsx";
6
+ const OTHER_ID = "/project/src/other.tsx";
7
+
8
+ type EditorPlugin = {
9
+ name?: string;
10
+ transform?: ((code: string, id: string) => unknown) | { handler: (code: string, id: string) => unknown };
11
+ };
12
+
13
+ function getPlugin(options: UpstartEditorPluginOptions): EditorPlugin {
14
+ const plugin = upstartEditor.vite(options) as unknown;
15
+ return (Array.isArray(plugin) ? plugin[0] : plugin) as EditorPlugin;
16
+ }
17
+
18
+ function runTransform(plugin: EditorPlugin, code: string, id: string): { code: string } | null {
19
+ const transform = plugin.transform;
20
+ if (!transform) {
21
+ throw new Error("Expected transform hook to be defined");
22
+ }
23
+
24
+ const handler = typeof transform === "function" ? transform : transform.handler;
25
+ const result = handler(code, id);
26
+
27
+ if (typeof result === "string") {
28
+ return { code: result };
29
+ }
30
+
31
+ if (result && typeof result === "object" && "code" in result) {
32
+ return { code: String((result as { code?: unknown }).code ?? "") };
33
+ }
34
+
35
+ return null;
36
+ }
37
+
38
+ describe("upstart-editor plugin", () => {
39
+ test("returns disabled plugin when not enabled", () => {
40
+ const plugin = getPlugin({ enabled: false });
41
+ expect(plugin.name).toBe("upstart-editor-disabled");
42
+ });
43
+
44
+ test("injects runtime init into entry file", () => {
45
+ const plugin = getPlugin({ enabled: true, autoInject: true });
46
+
47
+ const result = runTransform(plugin, "console.log('app');", MAIN_ID);
48
+
49
+ expect(result).not.toBeNull();
50
+ expect(result?.code).toContain("initUpstartEditor");
51
+ expect(result?.code).toContain("DOMContentLoaded");
52
+ });
53
+
54
+ test("does not inject into non-entry files", () => {
55
+ const plugin = getPlugin({ enabled: true, autoInject: true });
56
+
57
+ const result = runTransform(plugin, "console.log('app');", OTHER_ID);
58
+
59
+ expect(result).toBeNull();
60
+ });
61
+
62
+ test("does not inject when initUpstartEditor is already present", () => {
63
+ const plugin = getPlugin({ enabled: true, autoInject: true });
64
+
65
+ const result = runTransform(
66
+ plugin,
67
+ "import { initUpstartEditor } from 'x'; initUpstartEditor();",
68
+ MAIN_ID,
69
+ );
70
+
71
+ expect(result).toBeNull();
72
+ });
73
+
74
+ test("does not inject when autoInject is false", () => {
75
+ const plugin = getPlugin({ enabled: true, autoInject: false });
76
+
77
+ const result = runTransform(plugin, "console.log('app');", MAIN_ID);
78
+
79
+ expect(result).toBeNull();
80
+ });
81
+ });
@@ -0,0 +1,204 @@
1
+ import MagicString from "magic-string";
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ import type { EditableEntry } from "./vite-plugin-upstart-attrs";
5
+
6
+ export interface EditableRegistry {
7
+ version: number;
8
+ generatedAt: string;
9
+ elements: Record<string, EditableEntry>;
10
+ }
11
+
12
+ export interface EditResult {
13
+ success: boolean;
14
+ error?: string;
15
+ }
16
+
17
+ export class UpstartEditorAPI {
18
+ private registry: EditableRegistry | null = null;
19
+ private projectRoot: string;
20
+ private registryPath: string;
21
+
22
+ constructor(projectRoot: string, registryPath: string) {
23
+ this.projectRoot = projectRoot;
24
+ this.registryPath = registryPath;
25
+ }
26
+
27
+ /**
28
+ * Load the registry from disk
29
+ */
30
+ async loadRegistry(): Promise<void> {
31
+ const content = await fs.readFile(this.registryPath, "utf-8");
32
+ this.registry = JSON.parse(content);
33
+ }
34
+
35
+ /**
36
+ * Get the current registry (for testing/debugging)
37
+ */
38
+ getRegistry(): EditableRegistry | null {
39
+ return this.registry;
40
+ }
41
+
42
+ /**
43
+ * Set the registry directly (for testing)
44
+ */
45
+ setRegistry(registry: EditableRegistry): void {
46
+ this.registry = registry;
47
+ }
48
+
49
+ /**
50
+ * Edit the text content of an element
51
+ */
52
+ async editText(id: string, newContent: string): Promise<EditResult> {
53
+ if (!this.registry) {
54
+ try {
55
+ await this.loadRegistry();
56
+ } catch (err) {
57
+ return { success: false, error: `Failed to load registry: ${err}` };
58
+ }
59
+ }
60
+
61
+ const entry = this.registry!.elements[id];
62
+ if (!entry) {
63
+ return { success: false, error: `Element ${id} not found in registry` };
64
+ }
65
+
66
+ if (entry.type !== "text") {
67
+ return { success: false, error: `Element ${id} is not a text element (type: ${entry.type})` };
68
+ }
69
+
70
+ return this.applyEdit(id, entry, newContent);
71
+ }
72
+
73
+ /**
74
+ * Edit the className of an element
75
+ */
76
+ async editClassName(id: string, newClassName: string): Promise<EditResult> {
77
+ if (!this.registry) {
78
+ try {
79
+ await this.loadRegistry();
80
+ } catch (err) {
81
+ return { success: false, error: `Failed to load registry: ${err}` };
82
+ }
83
+ }
84
+
85
+ const entry = this.registry!.elements[id];
86
+ if (!entry) {
87
+ return { success: false, error: `Element ${id} not found in registry` };
88
+ }
89
+
90
+ if (entry.type !== "className") {
91
+ return { success: false, error: `Element ${id} is not a className element (type: ${entry.type})` };
92
+ }
93
+
94
+ return this.applyEdit(id, entry, newClassName);
95
+ }
96
+
97
+ /**
98
+ * Apply an edit to a source file
99
+ */
100
+ private async applyEdit(
101
+ id: string,
102
+ entry: EditableEntry,
103
+ newContent: string
104
+ ): Promise<EditResult> {
105
+ const filePath = path.join(this.projectRoot, entry.file);
106
+
107
+ try {
108
+ const code = await fs.readFile(filePath, "utf-8");
109
+
110
+ // Verify content at expected location
111
+ const currentContent = code.slice(entry.startOffset, entry.endOffset);
112
+
113
+ let actualStart = entry.startOffset;
114
+ let actualEnd = entry.endOffset;
115
+
116
+ if (currentContent !== entry.originalContent) {
117
+ // Content has shifted - try to find it by searching
118
+ const searchIndex = code.indexOf(entry.originalContent);
119
+ if (searchIndex === -1) {
120
+ return {
121
+ success: false,
122
+ error: `Original content "${entry.originalContent}" not found in file ${entry.file}. The file may have been modified.`,
123
+ };
124
+ }
125
+ actualStart = searchIndex;
126
+ actualEnd = searchIndex + entry.originalContent.length;
127
+ }
128
+
129
+ // Apply the edit using MagicString
130
+ const s = new MagicString(code);
131
+ s.overwrite(actualStart, actualEnd, newContent);
132
+
133
+ // Write the modified file
134
+ await fs.writeFile(filePath, s.toString());
135
+
136
+ // Calculate the length difference for offset adjustments
137
+ const lengthDiff = newContent.length - entry.originalContent.length;
138
+
139
+ // Update the registry entry
140
+ entry.startOffset = actualStart;
141
+ entry.endOffset = actualStart + newContent.length;
142
+ entry.originalContent = newContent;
143
+
144
+ // Shift all subsequent entries in the same file
145
+ for (const [otherId, otherEntry] of Object.entries(this.registry!.elements)) {
146
+ if (otherId !== id && otherEntry.file === entry.file && otherEntry.startOffset > actualStart) {
147
+ otherEntry.startOffset += lengthDiff;
148
+ otherEntry.endOffset += lengthDiff;
149
+ }
150
+ }
151
+
152
+ // Save the updated registry
153
+ await fs.writeFile(
154
+ this.registryPath,
155
+ JSON.stringify(this.registry, null, 2)
156
+ );
157
+
158
+ return { success: true };
159
+ } catch (err) {
160
+ return { success: false, error: String(err) };
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Get element info by ID
166
+ */
167
+ getElement(id: string): EditableEntry | undefined {
168
+ return this.registry?.elements[id];
169
+ }
170
+
171
+ /**
172
+ * Get all elements of a specific type
173
+ */
174
+ getElementsByType(type: "text" | "className"): Record<string, EditableEntry> {
175
+ if (!this.registry) {
176
+ return {};
177
+ }
178
+
179
+ const result: Record<string, EditableEntry> = {};
180
+ for (const [id, entry] of Object.entries(this.registry.elements)) {
181
+ if (entry.type === type) {
182
+ result[id] = entry;
183
+ }
184
+ }
185
+ return result;
186
+ }
187
+
188
+ /**
189
+ * Get all elements in a specific file
190
+ */
191
+ getElementsByFile(file: string): Record<string, EditableEntry> {
192
+ if (!this.registry) {
193
+ return {};
194
+ }
195
+
196
+ const result: Record<string, EditableEntry> = {};
197
+ for (const [id, entry] of Object.entries(this.registry.elements)) {
198
+ if (entry.file === file) {
199
+ result[id] = entry;
200
+ }
201
+ }
202
+ return result;
203
+ }
204
+ }