@upstart.gg/vite-plugins 0.0.38 → 0.0.40

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 (64) hide show
  1. package/dist/upstart-editor-api.d.ts +79 -0
  2. package/dist/upstart-editor-api.d.ts.map +1 -0
  3. package/dist/upstart-editor-api.js +208 -0
  4. package/dist/upstart-editor-api.js.map +1 -0
  5. package/dist/vite-plugin-upstart-attrs.d.ts +3 -3
  6. package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -1
  7. package/dist/vite-plugin-upstart-attrs.js +227 -25
  8. package/dist/vite-plugin-upstart-attrs.js.map +1 -1
  9. package/dist/vite-plugin-upstart-branding/plugin.d.ts +17 -0
  10. package/dist/vite-plugin-upstart-branding/plugin.d.ts.map +1 -0
  11. package/dist/vite-plugin-upstart-branding/plugin.js +41 -0
  12. package/dist/vite-plugin-upstart-branding/plugin.js.map +1 -0
  13. package/dist/vite-plugin-upstart-branding/runtime.d.ts +10 -0
  14. package/dist/vite-plugin-upstart-branding/runtime.d.ts.map +1 -0
  15. package/dist/vite-plugin-upstart-branding/runtime.js +118 -0
  16. package/dist/vite-plugin-upstart-branding/runtime.js.map +1 -0
  17. package/dist/vite-plugin-upstart-branding/types.d.ts +14 -0
  18. package/dist/vite-plugin-upstart-branding/types.d.ts.map +1 -0
  19. package/dist/vite-plugin-upstart-branding/types.js +1 -0
  20. package/dist/vite-plugin-upstart-editor/plugin.d.ts +3 -3
  21. package/dist/vite-plugin-upstart-editor/plugin.d.ts.map +1 -1
  22. package/dist/vite-plugin-upstart-editor/plugin.js +3 -16
  23. package/dist/vite-plugin-upstart-editor/plugin.js.map +1 -1
  24. package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +25 -11
  25. package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -1
  26. package/dist/vite-plugin-upstart-editor/runtime/error-handler.d.ts +5 -0
  27. package/dist/vite-plugin-upstart-editor/runtime/error-handler.d.ts.map +1 -0
  28. package/dist/vite-plugin-upstart-editor/runtime/error-handler.js +16 -0
  29. package/dist/vite-plugin-upstart-editor/runtime/error-handler.js.map +1 -0
  30. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +1 -1
  31. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -1
  32. package/dist/vite-plugin-upstart-editor/runtime/index.d.ts +2 -1
  33. package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -1
  34. package/dist/vite-plugin-upstart-editor/runtime/index.js +42 -7
  35. package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
  36. package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts +6 -1
  37. package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -1
  38. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +423 -129
  39. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -1
  40. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +18 -10
  41. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
  42. package/dist/vite-plugin-upstart-theme.d.ts +3 -3
  43. package/dist/vite-plugin-upstart-theme.d.ts.map +1 -1
  44. package/dist/vite-plugin-upstart-theme.js +1 -3
  45. package/dist/vite-plugin-upstart-theme.js.map +1 -1
  46. package/package.json +12 -4
  47. package/src/tests/upstart-editor-api.test.ts +98 -174
  48. package/src/tests/vite-plugin-upstart-attrs.test.ts +408 -105
  49. package/src/tests/vite-plugin-upstart-branding.test.ts +90 -0
  50. package/src/tests/vite-plugin-upstart-editor.test.ts +1 -2
  51. package/src/upstart-editor-api.ts +90 -29
  52. package/src/vite-plugin-upstart-attrs.ts +376 -38
  53. package/src/vite-plugin-upstart-branding/plugin.ts +59 -0
  54. package/src/vite-plugin-upstart-branding/runtime.ts +128 -0
  55. package/src/vite-plugin-upstart-branding/types.ts +10 -0
  56. package/src/vite-plugin-upstart-editor/plugin.ts +4 -19
  57. package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +25 -12
  58. package/src/vite-plugin-upstart-editor/runtime/error-handler.ts +12 -0
  59. package/src/vite-plugin-upstart-editor/runtime/hover-overlay.ts +1 -1
  60. package/src/vite-plugin-upstart-editor/runtime/index.ts +39 -5
  61. package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +518 -141
  62. package/src/vite-plugin-upstart-editor/runtime/types.ts +18 -4
  63. package/src/vite-plugin-upstart-theme.ts +0 -3
  64. package/src/vite-plugin-upstart-editor/PLAN.md +0 -1391
@@ -0,0 +1,90 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { upstartBranding } from "../vite-plugin-upstart-branding/plugin";
3
+ import type { UpstartBrandingPluginOptions } from "../vite-plugin-upstart-branding/types";
4
+
5
+ const MAIN_ID = "/project/src/entry.client.tsx";
6
+ const OTHER_ID = "/project/src/other.tsx";
7
+
8
+ type BrandingPlugin = {
9
+ name?: string;
10
+ transform?: ((code: string, id: string) => unknown) | { handler: (code: string, id: string) => unknown };
11
+ };
12
+
13
+ function getPlugin(options: UpstartBrandingPluginOptions): BrandingPlugin {
14
+ const plugin = upstartBranding.vite(options) as unknown;
15
+ return (Array.isArray(plugin) ? plugin[0] : plugin) as BrandingPlugin;
16
+ }
17
+
18
+ function runTransform(plugin: BrandingPlugin, 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-branding plugin", () => {
39
+ test("returns disabled plugin when not enabled", () => {
40
+ const plugin = getPlugin({ enabled: false });
41
+ expect(plugin.name).toBe("upstart-branding-disabled");
42
+ });
43
+
44
+ test("returns disabled plugin with default options", () => {
45
+ const plugin = getPlugin({});
46
+ expect(plugin.name).toBe("upstart-branding-disabled");
47
+ });
48
+
49
+ test("injects runtime init into entry file", () => {
50
+ const plugin = getPlugin({ enabled: true });
51
+
52
+ const result = runTransform(plugin, "console.log('app');", MAIN_ID);
53
+
54
+ expect(result).not.toBeNull();
55
+ expect(result?.code).toContain("initUpstartBranding");
56
+ expect(result?.code).toContain("requestIdleCallback");
57
+ });
58
+
59
+ test("does not inject into non-entry files", () => {
60
+ const plugin = getPlugin({ enabled: true });
61
+
62
+ const result = runTransform(plugin, "console.log('app');", OTHER_ID);
63
+
64
+ expect(result).toBeNull();
65
+ });
66
+
67
+ test("does not inject when initUpstartBranding is already present", () => {
68
+ const plugin = getPlugin({ enabled: true });
69
+
70
+ const result = runTransform(
71
+ plugin,
72
+ "import { initUpstartBranding } from 'x'; initUpstartBranding();",
73
+ MAIN_ID,
74
+ );
75
+
76
+ expect(result).toBeNull();
77
+ });
78
+
79
+ test("skips node_modules files", () => {
80
+ const plugin = getPlugin({ enabled: true });
81
+
82
+ const result = runTransform(
83
+ plugin,
84
+ "console.log('app');",
85
+ "/project/node_modules/some-pkg/entry.client.tsx",
86
+ );
87
+
88
+ expect(result).toBeNull();
89
+ });
90
+ });
@@ -2,7 +2,7 @@ import { describe, expect, test } from "vitest";
2
2
  import { upstartEditor } from "../vite-plugin-upstart-editor/plugin";
3
3
  import type { UpstartEditorPluginOptions } from "../vite-plugin-upstart-editor/runtime/types";
4
4
 
5
- const MAIN_ID = "/project/src/main.tsx";
5
+ const MAIN_ID = "/project/src/entry.client.tsx";
6
6
  const OTHER_ID = "/project/src/other.tsx";
7
7
 
8
8
  type EditorPlugin = {
@@ -48,7 +48,6 @@ describe("upstart-editor plugin", () => {
48
48
 
49
49
  expect(result).not.toBeNull();
50
50
  expect(result?.code).toContain("initUpstartEditor");
51
- expect(result?.code).toContain("DOMContentLoaded");
52
51
  });
53
52
 
54
53
  test("does not inject into non-entry files", () => {
@@ -1,18 +1,47 @@
1
1
  import MagicString from "magic-string";
2
2
  import fs from "fs/promises";
3
3
  import path from "path";
4
+ import z from "zod";
4
5
  import type { EditableEntry } from "./vite-plugin-upstart-attrs";
5
6
 
7
+ export const payloadEditText = z.object({
8
+ action: z.literal("editText"),
9
+ language: z
10
+ .string()
11
+ .length(2)
12
+ .regex(/^[a-z]{2}$/),
13
+ namespace: z.string().regex(/^[a-z0-9_-]+$/),
14
+ key: z.string().regex(/^[a-zA-Z0-9_.-]+$/),
15
+ content: z.string(),
16
+ });
17
+
18
+ export type PayloadEditText = z.infer<typeof payloadEditText>;
19
+
20
+ export const payloadEditClassName = z.object({
21
+ action: z.literal("editClassName"),
22
+ id: z.string().min(1),
23
+ className: z.string(),
24
+ });
25
+
26
+ export type PayloadEditClassName = z.infer<typeof payloadEditClassName>;
27
+
6
28
  export interface EditableRegistry {
7
29
  version: number;
8
30
  generatedAt: string;
9
31
  elements: Record<string, EditableEntry>;
10
32
  }
11
33
 
12
- export interface EditResult {
13
- success: boolean;
14
- error?: string;
15
- }
34
+ export type EditResult =
35
+ | {
36
+ success: true;
37
+ error?: never;
38
+ filePath: string;
39
+ }
40
+ | {
41
+ success: false;
42
+ error: string;
43
+ filePath?: never;
44
+ };
16
45
 
17
46
  export class UpstartEditorAPI {
18
47
  private registry: EditableRegistry | null = null;
@@ -47,33 +76,72 @@ export class UpstartEditorAPI {
47
76
  }
48
77
 
49
78
  /**
50
- * Edit the text content of an element
79
+ * Edit a translation value in an i18next locale file.
80
+ * Auto-detects flat keys (e.g. "nav.home" as literal key) vs nested keys (e.g. nav -> home).
81
+ * Only updates existing keys — returns an error if the key is not found.
51
82
  */
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
- }
83
+ async editText(params: PayloadEditText): Promise<EditResult> {
84
+ const parsed = payloadEditText.safeParse(params);
85
+ if (!parsed.success) {
86
+ return { success: false, error: `Invalid payload: ${parsed.error.message}` };
59
87
  }
88
+ const { language, namespace, key, content: newContent } = parsed.data;
89
+ const filePath = path.join(this.projectRoot, "app", "locales", language, `${namespace}.json`);
60
90
 
61
- const entry = this.registry!.elements[id];
62
- if (!entry) {
63
- return { success: false, error: `Element ${id} not found in registry` };
91
+ let raw: string;
92
+ try {
93
+ raw = await fs.readFile(filePath, "utf-8");
94
+ } catch (err) {
95
+ return { success: false, error: `Failed to read locale file: ${filePath}` };
64
96
  }
65
97
 
66
- if (entry.type !== "text") {
67
- return { success: false, error: `Element ${id} is not a text element (type: ${entry.type})` };
98
+ let data: Record<string, unknown>;
99
+ try {
100
+ data = JSON.parse(raw);
101
+ } catch (err) {
102
+ return { success: false, error: `Failed to parse locale file: ${filePath}` };
68
103
  }
69
104
 
70
- return this.applyEdit(id, entry, newContent);
105
+ // Strategy 1: check for flat/literal key at top level
106
+ if (key in data && typeof data[key] === "string") {
107
+ data[key] = newContent;
108
+ } else {
109
+ // Strategy 2: nested traversal via dot notation
110
+ const parts = key.split(".");
111
+ let current: Record<string, unknown> = data;
112
+ for (let i = 0; i < parts.length - 1; i++) {
113
+ const part = parts[i];
114
+ if (current[part] == null || typeof current[part] !== "object") {
115
+ return { success: false, error: `Key "${key}" not found in locale file ${filePath}` };
116
+ }
117
+ current = current[part] as Record<string, unknown>;
118
+ }
119
+
120
+ const leafKey = parts[parts.length - 1];
121
+ if (!(leafKey in current) || typeof current[leafKey] !== "string") {
122
+ return { success: false, error: `Key "${key}" not found in locale file ${filePath}` };
123
+ }
124
+ current[leafKey] = newContent;
125
+ }
126
+
127
+ try {
128
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
129
+ } catch (err) {
130
+ return { success: false, error: `Failed to write locale file: ${filePath}` };
131
+ }
132
+
133
+ return { success: true, filePath };
71
134
  }
72
135
 
73
136
  /**
74
137
  * Edit the className of an element
75
138
  */
76
- async editClassName(id: string, newClassName: string): Promise<EditResult> {
139
+ async editClassName(params: PayloadEditClassName): Promise<EditResult> {
140
+ const parsed = payloadEditClassName.safeParse(params);
141
+ if (!parsed.success) {
142
+ return { success: false, error: `Invalid payload: ${parsed.error.message}` };
143
+ }
144
+ const { id, className: newClassName } = parsed.data;
77
145
  if (!this.registry) {
78
146
  try {
79
147
  await this.loadRegistry();
@@ -97,11 +165,7 @@ export class UpstartEditorAPI {
97
165
  /**
98
166
  * Apply an edit to a source file
99
167
  */
100
- private async applyEdit(
101
- id: string,
102
- entry: EditableEntry,
103
- newContent: string
104
- ): Promise<EditResult> {
168
+ private async applyEdit(id: string, entry: EditableEntry, newContent: string): Promise<EditResult> {
105
169
  const filePath = path.join(this.projectRoot, entry.file);
106
170
 
107
171
  try {
@@ -150,12 +214,9 @@ export class UpstartEditorAPI {
150
214
  }
151
215
 
152
216
  // Save the updated registry
153
- await fs.writeFile(
154
- this.registryPath,
155
- JSON.stringify(this.registry, null, 2)
156
- );
217
+ await fs.writeFile(this.registryPath, JSON.stringify(this.registry, null, 2));
157
218
 
158
- return { success: true };
219
+ return { success: true, filePath };
159
220
  } catch (err) {
160
221
  return { success: false, error: String(err) };
161
222
  }