@upstart.gg/vite-plugins 0.1.41 → 0.1.42
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/upstart-editor-api.d.ts +59 -1
- package/dist/upstart-editor-api.d.ts.map +1 -1
- package/dist/upstart-editor-api.js +273 -4
- package/dist/upstart-editor-api.js.map +1 -1
- package/dist/vite-plugin-upstart-attrs.d.ts +2 -1
- package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-attrs.js +91 -4
- package/dist/vite-plugin-upstart-attrs.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/array-controls.js +342 -0
- package/dist/vite-plugin-upstart-editor/runtime/array-controls.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +29 -2
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/form-guard.js +80 -0
- package/dist/vite-plugin-upstart-editor/runtime/form-guard.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +2 -1
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js +26 -4
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +40 -36
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +20 -4
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/tests/vite-plugin-upstart-attrs.test.ts +412 -0
- package/src/upstart-editor-api.ts +314 -5
- package/src/vite-plugin-upstart-attrs.ts +154 -4
- package/src/vite-plugin-upstart-editor/runtime/array-controls.ts +478 -0
- package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +43 -0
- package/src/vite-plugin-upstart-editor/runtime/form-guard.ts +121 -0
- package/src/vite-plugin-upstart-editor/runtime/hover-overlay.ts +6 -1
- package/src/vite-plugin-upstart-editor/runtime/index.ts +20 -4
- package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +49 -58
- package/src/vite-plugin-upstart-editor/runtime/types.ts +31 -4
|
@@ -2,6 +2,13 @@ import { EditableEntry } from "./vite-plugin-upstart-attrs.js";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/upstart-editor-api.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Escape user-typed text so it can be safely written as the BODY of a JS string
|
|
7
|
+
* literal delimited by `quote` (" ' or `). Escapes the delimiter, backslashes,
|
|
8
|
+
* line terminators, and — for template literals — `${` interpolation starts.
|
|
9
|
+
* The surrounding quotes themselves are NOT included.
|
|
10
|
+
*/
|
|
11
|
+
declare function escapeStringLiteralBody(value: string, quote: string): string;
|
|
5
12
|
declare const payloadEditText: z.ZodObject<{
|
|
6
13
|
action: z.ZodLiteral<"editText">;
|
|
7
14
|
language: z.ZodString;
|
|
@@ -22,6 +29,30 @@ declare const payloadEditClassName: z.ZodObject<{
|
|
|
22
29
|
className: z.ZodString;
|
|
23
30
|
}, z.core.$strip>;
|
|
24
31
|
type PayloadEditClassName = z.infer<typeof payloadEditClassName>;
|
|
32
|
+
declare const payloadEditImage: z.ZodObject<{
|
|
33
|
+
action: z.ZodLiteral<"editImage">;
|
|
34
|
+
id: z.ZodString;
|
|
35
|
+
src: z.ZodString;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
type PayloadEditImage = z.infer<typeof payloadEditImage>;
|
|
38
|
+
declare const payloadArrayItemAdd: z.ZodObject<{
|
|
39
|
+
action: z.ZodLiteral<"arrayItemAdd">;
|
|
40
|
+
arrayId: z.ZodString;
|
|
41
|
+
content: z.ZodDefault<z.ZodString>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
type PayloadArrayItemAdd = z.infer<typeof payloadArrayItemAdd>;
|
|
44
|
+
declare const payloadArrayItemDelete: z.ZodObject<{
|
|
45
|
+
action: z.ZodLiteral<"arrayItemDelete">;
|
|
46
|
+
arrayId: z.ZodString;
|
|
47
|
+
index: z.ZodNumber;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
type PayloadArrayItemDelete = z.infer<typeof payloadArrayItemDelete>;
|
|
50
|
+
declare const payloadArraySet: z.ZodObject<{
|
|
51
|
+
action: z.ZodLiteral<"arraySet">;
|
|
52
|
+
arrayId: z.ZodString;
|
|
53
|
+
items: z.ZodArray<z.ZodString>;
|
|
54
|
+
}, z.core.$strip>;
|
|
55
|
+
type PayloadArraySet = z.infer<typeof payloadArraySet>;
|
|
25
56
|
interface EditableRegistry {
|
|
26
57
|
version: number;
|
|
27
58
|
generatedAt: string;
|
|
@@ -68,6 +99,33 @@ declare class UpstartEditorAPI {
|
|
|
68
99
|
* Edit the className of an element
|
|
69
100
|
*/
|
|
70
101
|
editClassName(params: PayloadEditClassName): Promise<EditResult>;
|
|
102
|
+
/**
|
|
103
|
+
* Edit the `src` of an <img> element directly in the source TSX file.
|
|
104
|
+
* The byte range points at the string literal between the quotes, so we only
|
|
105
|
+
* rewrite the path itself. Copying the asset into the project is handled by
|
|
106
|
+
* the caller (the sandbox server, which has bucket access).
|
|
107
|
+
*/
|
|
108
|
+
editImage(params: PayloadEditImage): Promise<EditResult>;
|
|
109
|
+
/**
|
|
110
|
+
* Add a new element to an inline array literal (e.g. `["a", "b"]`), used by the
|
|
111
|
+
* editor's "+" control on editable .map() lists. The inserted element matches
|
|
112
|
+
* the quote style of the existing string elements and reuses their separator so
|
|
113
|
+
* multi-line indentation is preserved.
|
|
114
|
+
*/
|
|
115
|
+
arrayItemAdd(params: PayloadArrayItemAdd): Promise<EditResult>;
|
|
116
|
+
/**
|
|
117
|
+
* Delete the element at `index` from an inline array literal. Refuses to remove
|
|
118
|
+
* the last remaining element (which would leave an empty, un-addressable array).
|
|
119
|
+
*/
|
|
120
|
+
arrayItemDelete(params: PayloadArrayItemDelete): Promise<EditResult>;
|
|
121
|
+
/**
|
|
122
|
+
* Replace the entire contents of an inline array literal with `items`, preserving
|
|
123
|
+
* the source's quote style and multi-line indentation. Used by the editor's
|
|
124
|
+
* batched ✓ apply so a whole add/delete session is a single edit + rebuild.
|
|
125
|
+
*/
|
|
126
|
+
arraySet(params: PayloadArraySet): Promise<EditResult>;
|
|
127
|
+
/** Parse "<relativeFile>:<offset>" into an absolute path + numeric offset. */
|
|
128
|
+
private resolveArrayId;
|
|
71
129
|
private applyEdit;
|
|
72
130
|
private applyMixedTextEdit;
|
|
73
131
|
/**
|
|
@@ -84,5 +142,5 @@ declare class UpstartEditorAPI {
|
|
|
84
142
|
getElementsByFile(file: string): Record<string, EditableEntry>;
|
|
85
143
|
}
|
|
86
144
|
//#endregion
|
|
87
|
-
export { EditResult, EditableRegistry, PayloadEditClassName, PayloadEditText, PayloadEditTextDirect, UpstartEditorAPI, payloadEditClassName, payloadEditText, payloadEditTextDirect };
|
|
145
|
+
export { EditResult, EditableRegistry, PayloadArrayItemAdd, PayloadArrayItemDelete, PayloadArraySet, PayloadEditClassName, PayloadEditImage, PayloadEditText, PayloadEditTextDirect, UpstartEditorAPI, escapeStringLiteralBody, payloadArrayItemAdd, payloadArrayItemDelete, payloadArraySet, payloadEditClassName, payloadEditImage, payloadEditText, payloadEditTextDirect };
|
|
88
146
|
//# sourceMappingURL=upstart-editor-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upstart-editor-api.d.ts","names":[],"sources":["../src/upstart-editor-api.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"upstart-editor-api.d.ts","names":[],"sources":["../src/upstart-editor-api.ts"],"mappings":";;;;;;AAaA;;;;iBAAgB,uBAAA,CAAwB,KAAA,UAAe,KAAA;AAAA,cAoE1C,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;KAWhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,cAEhC,qBAAA,EAAqB,CAAA,CAAA,SAAA;;;;;KAMtB,qBAAA,GAAwB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,cAEtC,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;;KAMrB,oBAAA,GAAuB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,cAErC,gBAAA,EAAgB,CAAA,CAAA,SAAA;;;;;KAOjB,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,gBAAA;AAAA,cAGjC,mBAAA,EAAmB,CAAA,CAAA,SAAA;;;;;KAOpB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,cAEpC,sBAAA,EAAsB,CAAA,CAAA,SAAA;;;;;KAMvB,sBAAA,GAAyB,CAAA,CAAE,KAAA,QAAa,sBAAA;AAAA,cAKvC,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;KAMhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,UAE5B,gBAAA;EACf,OAAA;EACA,WAAA;EACA,QAAA,EAAU,MAAA,SAAe,aAAA;AAAA;AAAA,KAGf,UAAA;EAEN,OAAA;EACA,KAAA;EACA,QAAA;AAAA;EAGA,OAAA;EACA,KAAA;EACA,QAAA;AAAA;AAAA,cAGO,gBAAA;EAAA,QACH,QAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;EAER,WAAA,CAAY,WAAA,UAAqB,YAAA;;;;EAQ3B,YAAA,CAAA,GAAgB,OAAA;;;AA/ExB;EAuFE,WAAA,CAAA,GAAe,gBAAA;;;;EAOf,WAAA,CAAY,QAAA,EAAU,gBAAA;EA9F2B;;;AAEnD;;EAqGQ,QAAA,CAAS,MAAA,EAAQ,eAAA,GAAkB,OAAA,CAAQ,UAAA;;;;;EAyD3C,cAAA,CAAe,MAAA,EAAQ,qBAAA,GAAwB,OAAA,CAAQ,UAAA;EA9J9B;;;EA8LzB,aAAA,CAAc,MAAA,EAAQ,oBAAA,GAAuB,OAAA,CAAQ,UAAA;;;;;;;EAgCrD,SAAA,CAAU,MAAA,EAAQ,gBAAA,GAAmB,OAAA,CAAQ,UAAA;;;;;;;EAgC7C,YAAA,CAAa,MAAA,EAAQ,mBAAA,GAAsB,OAAA,CAAQ,UAAA;EAxP/C;;;;EAoSJ,eAAA,CAAgB,MAAA,EAAQ,sBAAA,GAAyB,OAAA,CAAQ,UAAA;EApS5B;;;;AAErC;EAgVQ,QAAA,CAAS,MAAA,EAAQ,eAAA,GAAkB,OAAA,CAAQ,UAAA;;UAiDzC,cAAA;EAAA,QAYM,SAAA;EAAA,QAwEA,kBAAA;;;;EA4Bd,UAAA,CAAW,EAAA,WAAa,aAAA;EAjfG;;;EAwf3B,iBAAA,CAAkB,IAAA,yBAA6B,MAAA,SAAe,aAAA;;;;EAiB9D,iBAAA,CAAkB,IAAA,WAAe,MAAA,SAAe,aAAA;AAAA"}
|
|
@@ -1,8 +1,62 @@
|
|
|
1
1
|
import MagicString from "magic-string";
|
|
2
|
+
import { parseSync } from "oxc-parser";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import z from "zod";
|
|
5
6
|
//#region src/upstart-editor-api.ts
|
|
7
|
+
/**
|
|
8
|
+
* Escape user-typed text so it can be safely written as the BODY of a JS string
|
|
9
|
+
* literal delimited by `quote` (" ' or `). Escapes the delimiter, backslashes,
|
|
10
|
+
* line terminators, and — for template literals — `${` interpolation starts.
|
|
11
|
+
* The surrounding quotes themselves are NOT included.
|
|
12
|
+
*/
|
|
13
|
+
function escapeStringLiteralBody(value, quote) {
|
|
14
|
+
let out = value.replace(/\\/g, "\\\\");
|
|
15
|
+
if (quote === "`") out = out.replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
16
|
+
else out = out.split(quote).join("\\" + quote);
|
|
17
|
+
return out.replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Locate the inline ArrayExpression that starts at `offset` by re-parsing the file.
|
|
21
|
+
* Returns its bounds and element nodes (elisions become null), or null if not found.
|
|
22
|
+
*/
|
|
23
|
+
function findArrayExpressionAt(code, filePath, offset) {
|
|
24
|
+
const ast = parseSync(filePath, code, { sourceType: "module" });
|
|
25
|
+
if (!ast.program) return null;
|
|
26
|
+
let found = null;
|
|
27
|
+
const visit = (node) => {
|
|
28
|
+
if (found || !node || typeof node !== "object") return;
|
|
29
|
+
if (Array.isArray(node)) {
|
|
30
|
+
for (const child of node) visit(child);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const n = node;
|
|
34
|
+
if (n.type === "ArrayExpression" && n.start === offset) {
|
|
35
|
+
found = n;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const key in n) {
|
|
39
|
+
if (key === "type" || key === "start" || key === "end") continue;
|
|
40
|
+
const value = n[key];
|
|
41
|
+
if (value && typeof value === "object") visit(value);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
visit(ast.program);
|
|
45
|
+
if (!found) return null;
|
|
46
|
+
return {
|
|
47
|
+
start: found.start,
|
|
48
|
+
end: found.end,
|
|
49
|
+
elements: found.elements ?? []
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Infer the quote char used by the array's string literals (defaults to "). */
|
|
53
|
+
function inferArrayQuote(code, elements) {
|
|
54
|
+
for (const el of elements) if (el.type === "Literal" && typeof el.value === "string") {
|
|
55
|
+
const q = code[el.start];
|
|
56
|
+
if (q === "\"" || q === "'" || q === "`") return q;
|
|
57
|
+
}
|
|
58
|
+
return "\"";
|
|
59
|
+
}
|
|
6
60
|
const payloadEditText = z.object({
|
|
7
61
|
action: z.literal("editText"),
|
|
8
62
|
language: z.string().length(2).regex(/^[a-z]{2}$/),
|
|
@@ -20,6 +74,26 @@ const payloadEditClassName = z.object({
|
|
|
20
74
|
id: z.string().min(1),
|
|
21
75
|
className: z.string()
|
|
22
76
|
});
|
|
77
|
+
const payloadEditImage = z.object({
|
|
78
|
+
action: z.literal("editImage"),
|
|
79
|
+
id: z.string().min(1),
|
|
80
|
+
src: z.string().min(1)
|
|
81
|
+
});
|
|
82
|
+
const payloadArrayItemAdd = z.object({
|
|
83
|
+
action: z.literal("arrayItemAdd"),
|
|
84
|
+
arrayId: z.string().min(1),
|
|
85
|
+
content: z.string().default("New item")
|
|
86
|
+
});
|
|
87
|
+
const payloadArrayItemDelete = z.object({
|
|
88
|
+
action: z.literal("arrayItemDelete"),
|
|
89
|
+
arrayId: z.string().min(1),
|
|
90
|
+
index: z.number().int().min(0)
|
|
91
|
+
});
|
|
92
|
+
const payloadArraySet = z.object({
|
|
93
|
+
action: z.literal("arraySet"),
|
|
94
|
+
arrayId: z.string().min(1),
|
|
95
|
+
items: z.array(z.string()).min(1)
|
|
96
|
+
});
|
|
23
97
|
var UpstartEditorAPI = class {
|
|
24
98
|
registry = null;
|
|
25
99
|
projectRoot;
|
|
@@ -139,7 +213,8 @@ var UpstartEditorAPI = class {
|
|
|
139
213
|
error: `Element ${id} is not a text element (type: ${entry.type})`
|
|
140
214
|
};
|
|
141
215
|
if (entry.type === "mixed-text") return this.applyMixedTextEdit(id, entry, content);
|
|
142
|
-
|
|
216
|
+
const finalContent = entry.quote ? escapeStringLiteralBody(content, entry.quote) : content;
|
|
217
|
+
return this.applyEdit(id, entry, finalContent);
|
|
143
218
|
}
|
|
144
219
|
/**
|
|
145
220
|
* Edit the className of an element
|
|
@@ -171,6 +246,199 @@ var UpstartEditorAPI = class {
|
|
|
171
246
|
return this.applyEdit(id, entry, newClassName);
|
|
172
247
|
}
|
|
173
248
|
/**
|
|
249
|
+
* Edit the `src` of an <img> element directly in the source TSX file.
|
|
250
|
+
* The byte range points at the string literal between the quotes, so we only
|
|
251
|
+
* rewrite the path itself. Copying the asset into the project is handled by
|
|
252
|
+
* the caller (the sandbox server, which has bucket access).
|
|
253
|
+
*/
|
|
254
|
+
async editImage(params) {
|
|
255
|
+
const parsed = payloadEditImage.safeParse(params);
|
|
256
|
+
if (!parsed.success) return {
|
|
257
|
+
success: false,
|
|
258
|
+
error: `Invalid payload: ${parsed.error.message}`
|
|
259
|
+
};
|
|
260
|
+
const { id, src } = parsed.data;
|
|
261
|
+
if (!this.registry) try {
|
|
262
|
+
await this.loadRegistry();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
error: `Failed to load registry: ${err}`
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const entry = this.registry.elements[id];
|
|
270
|
+
if (!entry) return {
|
|
271
|
+
success: false,
|
|
272
|
+
error: `Element ${id} not found in registry`
|
|
273
|
+
};
|
|
274
|
+
if (entry.type !== "image") return {
|
|
275
|
+
success: false,
|
|
276
|
+
error: `Element ${id} is not an image element (type: ${entry.type})`
|
|
277
|
+
};
|
|
278
|
+
return this.applyEdit(id, entry, src);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Add a new element to an inline array literal (e.g. `["a", "b"]`), used by the
|
|
282
|
+
* editor's "+" control on editable .map() lists. The inserted element matches
|
|
283
|
+
* the quote style of the existing string elements and reuses their separator so
|
|
284
|
+
* multi-line indentation is preserved.
|
|
285
|
+
*/
|
|
286
|
+
async arrayItemAdd(params) {
|
|
287
|
+
const parsed = payloadArrayItemAdd.safeParse(params);
|
|
288
|
+
if (!parsed.success) return {
|
|
289
|
+
success: false,
|
|
290
|
+
error: `Invalid payload: ${parsed.error.message}`
|
|
291
|
+
};
|
|
292
|
+
const { arrayId, content } = parsed.data;
|
|
293
|
+
const loc = this.resolveArrayId(arrayId);
|
|
294
|
+
if (!loc) return {
|
|
295
|
+
success: false,
|
|
296
|
+
error: `Invalid arrayId: ${arrayId}`
|
|
297
|
+
};
|
|
298
|
+
try {
|
|
299
|
+
const code = await fs.readFile(loc.filePath, "utf-8");
|
|
300
|
+
const arr = findArrayExpressionAt(code, loc.relativeFile, loc.offset);
|
|
301
|
+
if (!arr) return {
|
|
302
|
+
success: false,
|
|
303
|
+
error: `Array not found at ${arrayId}. The file may have been modified.`
|
|
304
|
+
};
|
|
305
|
+
const els = arr.elements.filter((e) => e !== null);
|
|
306
|
+
const quote = inferArrayQuote(code, els);
|
|
307
|
+
const literal = `${quote}${escapeStringLiteralBody(content, quote)}${quote}`;
|
|
308
|
+
const s = new MagicString(code);
|
|
309
|
+
if (els.length === 0) s.appendLeft(arr.start + 1, literal);
|
|
310
|
+
else {
|
|
311
|
+
const last = els[els.length - 1];
|
|
312
|
+
const sep = els.length >= 2 ? code.slice(els[els.length - 2].end, last.start) : ", ";
|
|
313
|
+
s.appendLeft(last.end, `${sep}${literal}`);
|
|
314
|
+
}
|
|
315
|
+
await fs.writeFile(loc.filePath, s.toString());
|
|
316
|
+
this.registry = null;
|
|
317
|
+
return {
|
|
318
|
+
success: true,
|
|
319
|
+
filePath: loc.filePath
|
|
320
|
+
};
|
|
321
|
+
} catch (err) {
|
|
322
|
+
return {
|
|
323
|
+
success: false,
|
|
324
|
+
error: String(err)
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Delete the element at `index` from an inline array literal. Refuses to remove
|
|
330
|
+
* the last remaining element (which would leave an empty, un-addressable array).
|
|
331
|
+
*/
|
|
332
|
+
async arrayItemDelete(params) {
|
|
333
|
+
const parsed = payloadArrayItemDelete.safeParse(params);
|
|
334
|
+
if (!parsed.success) return {
|
|
335
|
+
success: false,
|
|
336
|
+
error: `Invalid payload: ${parsed.error.message}`
|
|
337
|
+
};
|
|
338
|
+
const { arrayId, index } = parsed.data;
|
|
339
|
+
const loc = this.resolveArrayId(arrayId);
|
|
340
|
+
if (!loc) return {
|
|
341
|
+
success: false,
|
|
342
|
+
error: `Invalid arrayId: ${arrayId}`
|
|
343
|
+
};
|
|
344
|
+
try {
|
|
345
|
+
const code = await fs.readFile(loc.filePath, "utf-8");
|
|
346
|
+
const arr = findArrayExpressionAt(code, loc.relativeFile, loc.offset);
|
|
347
|
+
if (!arr) return {
|
|
348
|
+
success: false,
|
|
349
|
+
error: `Array not found at ${arrayId}. The file may have been modified.`
|
|
350
|
+
};
|
|
351
|
+
const els = arr.elements.filter((e) => e !== null);
|
|
352
|
+
if (index >= els.length) return {
|
|
353
|
+
success: false,
|
|
354
|
+
error: `Index ${index} out of range for array at ${arrayId}`
|
|
355
|
+
};
|
|
356
|
+
if (els.length <= 1) return {
|
|
357
|
+
success: false,
|
|
358
|
+
error: "Cannot delete the last remaining array item"
|
|
359
|
+
};
|
|
360
|
+
const s = new MagicString(code);
|
|
361
|
+
const el = els[index];
|
|
362
|
+
if (index > 0) s.remove(els[index - 1].end, el.end);
|
|
363
|
+
else s.remove(el.start, els[1].start);
|
|
364
|
+
await fs.writeFile(loc.filePath, s.toString());
|
|
365
|
+
this.registry = null;
|
|
366
|
+
return {
|
|
367
|
+
success: true,
|
|
368
|
+
filePath: loc.filePath
|
|
369
|
+
};
|
|
370
|
+
} catch (err) {
|
|
371
|
+
return {
|
|
372
|
+
success: false,
|
|
373
|
+
error: String(err)
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Replace the entire contents of an inline array literal with `items`, preserving
|
|
379
|
+
* the source's quote style and multi-line indentation. Used by the editor's
|
|
380
|
+
* batched ✓ apply so a whole add/delete session is a single edit + rebuild.
|
|
381
|
+
*/
|
|
382
|
+
async arraySet(params) {
|
|
383
|
+
const parsed = payloadArraySet.safeParse(params);
|
|
384
|
+
if (!parsed.success) return {
|
|
385
|
+
success: false,
|
|
386
|
+
error: `Invalid payload: ${parsed.error.message}`
|
|
387
|
+
};
|
|
388
|
+
const { arrayId, items } = parsed.data;
|
|
389
|
+
const loc = this.resolveArrayId(arrayId);
|
|
390
|
+
if (!loc) return {
|
|
391
|
+
success: false,
|
|
392
|
+
error: `Invalid arrayId: ${arrayId}`
|
|
393
|
+
};
|
|
394
|
+
try {
|
|
395
|
+
const code = await fs.readFile(loc.filePath, "utf-8");
|
|
396
|
+
const arr = findArrayExpressionAt(code, loc.relativeFile, loc.offset);
|
|
397
|
+
if (!arr) return {
|
|
398
|
+
success: false,
|
|
399
|
+
error: `Array not found at ${arrayId}. The file may have been modified.`
|
|
400
|
+
};
|
|
401
|
+
const quote = inferArrayQuote(code, arr.elements.filter((e) => e !== null));
|
|
402
|
+
const literal = (v) => `${quote}${escapeStringLiteralBody(v, quote)}${quote}`;
|
|
403
|
+
const innerStart = arr.start + 1;
|
|
404
|
+
const innerEnd = arr.end - 1;
|
|
405
|
+
const innerSrc = code.slice(innerStart, innerEnd);
|
|
406
|
+
let inner;
|
|
407
|
+
if (innerSrc.includes("\n")) {
|
|
408
|
+
const itemIndent = innerSrc.match(/\n([ \t]*)\S/)?.[1] ?? " ";
|
|
409
|
+
const outerIndent = innerSrc.match(/\n([ \t]*)$/)?.[1] ?? "";
|
|
410
|
+
inner = `\n${items.map((it) => itemIndent + literal(it)).join(",\n")},\n${outerIndent}`;
|
|
411
|
+
} else inner = items.map(literal).join(", ");
|
|
412
|
+
const s = new MagicString(code);
|
|
413
|
+
if (innerStart === innerEnd) s.appendLeft(innerStart, inner);
|
|
414
|
+
else s.overwrite(innerStart, innerEnd, inner);
|
|
415
|
+
await fs.writeFile(loc.filePath, s.toString());
|
|
416
|
+
this.registry = null;
|
|
417
|
+
return {
|
|
418
|
+
success: true,
|
|
419
|
+
filePath: loc.filePath
|
|
420
|
+
};
|
|
421
|
+
} catch (err) {
|
|
422
|
+
return {
|
|
423
|
+
success: false,
|
|
424
|
+
error: String(err)
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/** Parse "<relativeFile>:<offset>" into an absolute path + numeric offset. */
|
|
429
|
+
resolveArrayId(arrayId) {
|
|
430
|
+
const sep = arrayId.lastIndexOf(":");
|
|
431
|
+
if (sep < 0) return null;
|
|
432
|
+
const relativeFile = arrayId.slice(0, sep);
|
|
433
|
+
const offset = Number(arrayId.slice(sep + 1));
|
|
434
|
+
if (!relativeFile || !Number.isInteger(offset) || offset < 0) return null;
|
|
435
|
+
return {
|
|
436
|
+
filePath: path.join(this.projectRoot, relativeFile),
|
|
437
|
+
relativeFile,
|
|
438
|
+
offset
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
174
442
|
* Apply an edit to a source file
|
|
175
443
|
*/
|
|
176
444
|
async applyEdit(id, entry, newContent) {
|
|
@@ -181,7 +449,7 @@ var UpstartEditorAPI = class {
|
|
|
181
449
|
let actualStart = entry.startOffset;
|
|
182
450
|
let actualEnd = entry.endOffset;
|
|
183
451
|
if (currentContent !== entry.originalContent) {
|
|
184
|
-
const searchIndex = code.indexOf(entry.originalContent);
|
|
452
|
+
const searchIndex = entry.originalContent !== "" ? code.indexOf(entry.originalContent) : -1;
|
|
185
453
|
if (searchIndex === -1) return {
|
|
186
454
|
success: false,
|
|
187
455
|
error: `Original content "${entry.originalContent}" not found in file ${entry.file}. The file may have been modified.`
|
|
@@ -190,7 +458,8 @@ var UpstartEditorAPI = class {
|
|
|
190
458
|
actualEnd = searchIndex + entry.originalContent.length;
|
|
191
459
|
}
|
|
192
460
|
const s = new MagicString(code);
|
|
193
|
-
s.
|
|
461
|
+
if (actualStart === actualEnd) s.appendLeft(actualStart, newContent);
|
|
462
|
+
else s.overwrite(actualStart, actualEnd, newContent);
|
|
194
463
|
await fs.writeFile(filePath, s.toString());
|
|
195
464
|
const lengthDiff = newContent.length - entry.originalContent.length;
|
|
196
465
|
entry.startOffset = actualStart;
|
|
@@ -260,6 +529,6 @@ var UpstartEditorAPI = class {
|
|
|
260
529
|
}
|
|
261
530
|
};
|
|
262
531
|
//#endregion
|
|
263
|
-
export { UpstartEditorAPI, payloadEditClassName, payloadEditText, payloadEditTextDirect };
|
|
532
|
+
export { UpstartEditorAPI, escapeStringLiteralBody, payloadArrayItemAdd, payloadArrayItemDelete, payloadArraySet, payloadEditClassName, payloadEditImage, payloadEditText, payloadEditTextDirect };
|
|
264
533
|
|
|
265
534
|
//# sourceMappingURL=upstart-editor-api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upstart-editor-api.js","names":[],"sources":["../src/upstart-editor-api.ts"],"sourcesContent":["import MagicString from \"magic-string\";\nimport fs from \"fs/promises\";\nimport path from \"path\";\nimport z from \"zod\";\nimport type { EditableEntry } from \"./vite-plugin-upstart-attrs\";\n\nexport const payloadEditText = z.object({\n action: z.literal(\"editText\"),\n language: z\n .string()\n .length(2)\n .regex(/^[a-z]{2}$/),\n namespace: z.string().regex(/^[a-z0-9_-]+$/),\n key: z.string().regex(/^[a-zA-Z0-9_.-]+$/),\n content: z.string(),\n});\n\nexport type PayloadEditText = z.infer<typeof payloadEditText>;\n\nexport const payloadEditTextDirect = z.object({\n action: z.literal(\"editTextDirect\"),\n id: z.string().min(1),\n content: z.string(),\n});\n\nexport type PayloadEditTextDirect = z.infer<typeof payloadEditTextDirect>;\n\nexport const payloadEditClassName = z.object({\n action: z.literal(\"editClassName\"),\n id: z.string().min(1),\n className: z.string(),\n});\n\nexport type PayloadEditClassName = z.infer<typeof payloadEditClassName>;\n\nexport interface EditableRegistry {\n version: number;\n generatedAt: string;\n elements: Record<string, EditableEntry>;\n}\n\nexport type EditResult =\n | {\n success: true;\n error?: never;\n filePath: string;\n }\n | {\n success: false;\n error: string;\n filePath?: never;\n };\n\nexport class UpstartEditorAPI {\n private registry: EditableRegistry | null = null;\n private projectRoot: string;\n private registryPath: string;\n\n constructor(projectRoot: string, registryPath: string) {\n this.projectRoot = projectRoot;\n this.registryPath = registryPath;\n }\n\n /**\n * Load the registry from disk\n */\n async loadRegistry(): Promise<void> {\n const content = await fs.readFile(this.registryPath, \"utf-8\");\n this.registry = JSON.parse(content);\n }\n\n /**\n * Get the current registry (for testing/debugging)\n */\n getRegistry(): EditableRegistry | null {\n return this.registry;\n }\n\n /**\n * Set the registry directly (for testing)\n */\n setRegistry(registry: EditableRegistry): void {\n this.registry = registry;\n }\n\n /**\n * Edit a translation value in an i18next locale file.\n * Auto-detects flat keys (e.g. \"nav.home\" as literal key) vs nested keys (e.g. nav -> home).\n * Only updates existing keys — returns an error if the key is not found.\n */\n async editText(params: PayloadEditText): Promise<EditResult> {\n const parsed = payloadEditText.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { language, namespace, key, content: newContent } = parsed.data;\n const filePath = path.join(this.projectRoot, \"app\", \"locales\", language, `${namespace}.json`);\n\n let raw: string;\n try {\n raw = await fs.readFile(filePath, \"utf-8\");\n } catch (err) {\n return { success: false, error: `Failed to read locale file: ${filePath}` };\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(raw);\n } catch (err) {\n return { success: false, error: `Failed to parse locale file: ${filePath}` };\n }\n\n // Strategy 1: check for flat/literal key at top level\n if (key in data && typeof data[key] === \"string\") {\n data[key] = newContent;\n } else {\n // Strategy 2: nested traversal via dot notation\n const parts = key.split(\".\");\n let current: Record<string, unknown> = data;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (current[part] == null || typeof current[part] !== \"object\") {\n return { success: false, error: `Key \"${key}\" not found in locale file ${filePath}` };\n }\n current = current[part] as Record<string, unknown>;\n }\n\n const leafKey = parts[parts.length - 1];\n if (!(leafKey in current) || typeof current[leafKey] !== \"string\") {\n return { success: false, error: `Key \"${key}\" not found in locale file ${filePath}` };\n }\n current[leafKey] = newContent;\n }\n\n try {\n await fs.writeFile(filePath, JSON.stringify(data, null, 2) + \"\\n\");\n } catch (err) {\n return { success: false, error: `Failed to write locale file: ${filePath}` };\n }\n\n return { success: true, filePath };\n }\n\n /**\n * Edit a plain-text or rich-text JSX node directly in the source TSX file.\n * For rich-text entries, `content` should be the inner JSX/HTML of the element's children.\n */\n async editTextDirect(params: PayloadEditTextDirect): Promise<EditResult> {\n const parsed = payloadEditTextDirect.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { id, content } = parsed.data;\n if (!this.registry) {\n try {\n await this.loadRegistry();\n } catch (err) {\n return { success: false, error: `Failed to load registry: ${err}` };\n }\n }\n const entry = this.registry!.elements[id];\n if (!entry) {\n return { success: false, error: `Element ${id} not found in registry` };\n }\n if (entry.type !== \"text\" && entry.type !== \"rich-text\" && entry.type !== \"mixed-text\") {\n return { success: false, error: `Element ${id} is not a text element (type: ${entry.type})` };\n }\n if (entry.type === \"mixed-text\") {\n return this.applyMixedTextEdit(id, entry, content);\n }\n return this.applyEdit(id, entry, content);\n }\n\n /**\n * Edit the className of an element\n */\n async editClassName(params: PayloadEditClassName): Promise<EditResult> {\n const parsed = payloadEditClassName.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { id, className: newClassName } = parsed.data;\n if (!this.registry) {\n try {\n await this.loadRegistry();\n } catch (err) {\n return { success: false, error: `Failed to load registry: ${err}` };\n }\n }\n\n const entry = this.registry!.elements[id];\n if (!entry) {\n return { success: false, error: `Element ${id} not found in registry` };\n }\n\n if (entry.type !== \"className\") {\n return { success: false, error: `Element ${id} is not a className element (type: ${entry.type})` };\n }\n\n return this.applyEdit(id, entry, newClassName);\n }\n\n /**\n * Apply an edit to a source file\n */\n private async applyEdit(id: string, entry: EditableEntry, newContent: string): Promise<EditResult> {\n const filePath = path.join(this.projectRoot, entry.file);\n\n try {\n const code = await fs.readFile(filePath, \"utf-8\");\n\n // Verify content at expected location\n const currentContent = code.slice(entry.startOffset, entry.endOffset);\n\n let actualStart = entry.startOffset;\n let actualEnd = entry.endOffset;\n\n if (currentContent !== entry.originalContent) {\n // Content has shifted - try to find it by searching\n const searchIndex = code.indexOf(entry.originalContent);\n if (searchIndex === -1) {\n return {\n success: false,\n error: `Original content \"${entry.originalContent}\" not found in file ${entry.file}. The file may have been modified.`,\n };\n }\n actualStart = searchIndex;\n actualEnd = searchIndex + entry.originalContent.length;\n }\n\n // Apply the edit using MagicString\n const s = new MagicString(code);\n s.overwrite(actualStart, actualEnd, newContent);\n\n // Write the modified file\n await fs.writeFile(filePath, s.toString());\n\n // Calculate the length difference for offset adjustments\n const lengthDiff = newContent.length - entry.originalContent.length;\n\n // Update the registry entry\n entry.startOffset = actualStart;\n entry.endOffset = actualStart + newContent.length;\n entry.originalContent = newContent;\n\n // Shift all subsequent entries in the same file\n for (const [otherId, otherEntry] of Object.entries(this.registry!.elements)) {\n if (otherId !== id && otherEntry.file === entry.file && otherEntry.startOffset > actualStart) {\n otherEntry.startOffset += lengthDiff;\n otherEntry.endOffset += lengthDiff;\n }\n }\n\n // Save the updated registry\n await fs.writeFile(this.registryPath, JSON.stringify(this.registry, null, 2));\n\n return { success: true, filePath };\n } catch (err) {\n return { success: false, error: String(err) };\n }\n }\n\n /**\n * Reconstruct JSX children from a user-edited template and the original expression segments,\n * then write the result back to the TSX source file.\n *\n * `template` uses `{{N}}` placeholders for expressions, e.g.:\n * \"© {{0}} Alex's Kitchen. All rights reserved.\"\n * The server replaces each placeholder with the original expression source from the registry.\n */\n private async applyMixedTextEdit(id: string, entry: EditableEntry, template: string): Promise<EditResult> {\n const exprSegments = (entry.segments ?? []).filter((s) => s.type === \"expr\");\n\n // Split template by {{N}} placeholders — odd indices are expression indices\n const parts = template.split(/\\{\\{(\\d+)\\}\\}/);\n\n // Reconstruct inner JSX: text parts interleaved with original expression sources\n let inner = \"\";\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n inner += parts[i];\n } else {\n const exprSeg = exprSegments[parseInt(parts[i])];\n inner += exprSeg ? exprSeg.raw : `{{${parts[i]}}}`;\n }\n }\n\n // Preserve leading/trailing JSX whitespace (indentation) from the original source\n const leadingWS = entry.originalContent.match(/^(\\s*)/)?.[1] ?? \"\";\n const trailingWS = entry.originalContent.match(/(\\s*)$/)?.[1] ?? \"\";\n const newContent = leadingWS + inner + trailingWS;\n\n return this.applyEdit(id, entry, newContent);\n }\n\n /**\n * Get element info by ID\n */\n getElement(id: string): EditableEntry | undefined {\n return this.registry?.elements[id];\n }\n\n /**\n * Get all elements of a specific type\n */\n getElementsByType(type: \"text\" | \"className\"): Record<string, EditableEntry> {\n if (!this.registry) {\n return {};\n }\n\n const result: Record<string, EditableEntry> = {};\n for (const [id, entry] of Object.entries(this.registry.elements)) {\n if (entry.type === type) {\n result[id] = entry;\n }\n }\n return result;\n }\n\n /**\n * Get all elements in a specific file\n */\n getElementsByFile(file: string): Record<string, EditableEntry> {\n if (!this.registry) {\n return {};\n }\n\n const result: Record<string, EditableEntry> = {};\n for (const [id, entry] of Object.entries(this.registry.elements)) {\n if (entry.file === file) {\n result[id] = entry;\n }\n }\n return result;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"upstart-editor-api.js","names":[],"sources":["../src/upstart-editor-api.ts"],"sourcesContent":["import MagicString from \"magic-string\";\nimport { parseSync } from \"oxc-parser\";\nimport fs from \"fs/promises\";\nimport path from \"path\";\nimport z from \"zod\";\nimport type { EditableEntry } from \"./vite-plugin-upstart-attrs\";\n\n/**\n * Escape user-typed text so it can be safely written as the BODY of a JS string\n * literal delimited by `quote` (\" ' or `). Escapes the delimiter, backslashes,\n * line terminators, and — for template literals — `${` interpolation starts.\n * The surrounding quotes themselves are NOT included.\n */\nexport function escapeStringLiteralBody(value: string, quote: string): string {\n let out = value.replace(/\\\\/g, \"\\\\\\\\\");\n if (quote === \"`\") {\n out = out.replace(/`/g, \"\\\\`\").replace(/\\$\\{/g, \"\\\\${\");\n } else {\n out = out.split(quote).join(\"\\\\\" + quote);\n }\n return out.replace(/\\n/g, \"\\\\n\").replace(/\\r/g, \"\\\\r\");\n}\n\ninterface AstNode {\n type: string;\n start: number;\n end: number;\n [key: string]: unknown;\n}\n\n/**\n * Locate the inline ArrayExpression that starts at `offset` by re-parsing the file.\n * Returns its bounds and element nodes (elisions become null), or null if not found.\n */\nfunction findArrayExpressionAt(\n code: string,\n filePath: string,\n offset: number,\n): { start: number; end: number; elements: (AstNode | null)[] } | null {\n const ast = parseSync(filePath, code, { sourceType: \"module\" });\n if (!ast.program) return null;\n\n let found: AstNode | null = null;\n const visit = (node: unknown): void => {\n if (found || !node || typeof node !== \"object\") return;\n if (Array.isArray(node)) {\n for (const child of node) visit(child);\n return;\n }\n const n = node as AstNode;\n if (n.type === \"ArrayExpression\" && n.start === offset) {\n found = n;\n return;\n }\n for (const key in n) {\n if (key === \"type\" || key === \"start\" || key === \"end\") continue;\n const value = n[key];\n if (value && typeof value === \"object\") visit(value);\n }\n };\n visit(ast.program);\n\n if (!found) return null;\n return {\n start: (found as AstNode).start,\n end: (found as AstNode).end,\n elements: ((found as AstNode).elements as (AstNode | null)[]) ?? [],\n };\n}\n\n/** Infer the quote char used by the array's string literals (defaults to \"). */\nfunction inferArrayQuote(code: string, elements: AstNode[]): string {\n for (const el of elements) {\n if (el.type === \"Literal\" && typeof el.value === \"string\") {\n const q = code[el.start];\n if (q === '\"' || q === \"'\" || q === \"`\") return q;\n }\n }\n return '\"';\n}\n\nexport const payloadEditText = z.object({\n action: z.literal(\"editText\"),\n language: z\n .string()\n .length(2)\n .regex(/^[a-z]{2}$/),\n namespace: z.string().regex(/^[a-z0-9_-]+$/),\n key: z.string().regex(/^[a-zA-Z0-9_.-]+$/),\n content: z.string(),\n});\n\nexport type PayloadEditText = z.infer<typeof payloadEditText>;\n\nexport const payloadEditTextDirect = z.object({\n action: z.literal(\"editTextDirect\"),\n id: z.string().min(1),\n content: z.string(),\n});\n\nexport type PayloadEditTextDirect = z.infer<typeof payloadEditTextDirect>;\n\nexport const payloadEditClassName = z.object({\n action: z.literal(\"editClassName\"),\n id: z.string().min(1),\n className: z.string(),\n});\n\nexport type PayloadEditClassName = z.infer<typeof payloadEditClassName>;\n\nexport const payloadEditImage = z.object({\n action: z.literal(\"editImage\"),\n id: z.string().min(1),\n // New src value to write into the <img> source, e.g. \"/images/fashion-10.webp\"\n src: z.string().min(1),\n});\n\nexport type PayloadEditImage = z.infer<typeof payloadEditImage>;\n\n// arrayId is \"<relativeFile>:<arrayStartOffset>\" — identifies the inline array literal.\nexport const payloadArrayItemAdd = z.object({\n action: z.literal(\"arrayItemAdd\"),\n arrayId: z.string().min(1),\n // Default content for the inserted element (the editor sends \"New item\").\n content: z.string().default(\"New item\"),\n});\n\nexport type PayloadArrayItemAdd = z.infer<typeof payloadArrayItemAdd>;\n\nexport const payloadArrayItemDelete = z.object({\n action: z.literal(\"arrayItemDelete\"),\n arrayId: z.string().min(1),\n index: z.number().int().min(0),\n});\n\nexport type PayloadArrayItemDelete = z.infer<typeof payloadArrayItemDelete>;\n\n// Replace the entire contents of an inline array literal in one edit — used by the\n// editor's batched \"apply\" (✓) control so a whole add/delete session is a single\n// source change + rebuild instead of one per item.\nexport const payloadArraySet = z.object({\n action: z.literal(\"arraySet\"),\n arrayId: z.string().min(1),\n items: z.array(z.string()).min(1),\n});\n\nexport type PayloadArraySet = z.infer<typeof payloadArraySet>;\n\nexport interface EditableRegistry {\n version: number;\n generatedAt: string;\n elements: Record<string, EditableEntry>;\n}\n\nexport type EditResult =\n | {\n success: true;\n error?: never;\n filePath: string;\n }\n | {\n success: false;\n error: string;\n filePath?: never;\n };\n\nexport class UpstartEditorAPI {\n private registry: EditableRegistry | null = null;\n private projectRoot: string;\n private registryPath: string;\n\n constructor(projectRoot: string, registryPath: string) {\n this.projectRoot = projectRoot;\n this.registryPath = registryPath;\n }\n\n /**\n * Load the registry from disk\n */\n async loadRegistry(): Promise<void> {\n const content = await fs.readFile(this.registryPath, \"utf-8\");\n this.registry = JSON.parse(content);\n }\n\n /**\n * Get the current registry (for testing/debugging)\n */\n getRegistry(): EditableRegistry | null {\n return this.registry;\n }\n\n /**\n * Set the registry directly (for testing)\n */\n setRegistry(registry: EditableRegistry): void {\n this.registry = registry;\n }\n\n /**\n * Edit a translation value in an i18next locale file.\n * Auto-detects flat keys (e.g. \"nav.home\" as literal key) vs nested keys (e.g. nav -> home).\n * Only updates existing keys — returns an error if the key is not found.\n */\n async editText(params: PayloadEditText): Promise<EditResult> {\n const parsed = payloadEditText.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { language, namespace, key, content: newContent } = parsed.data;\n const filePath = path.join(this.projectRoot, \"app\", \"locales\", language, `${namespace}.json`);\n\n let raw: string;\n try {\n raw = await fs.readFile(filePath, \"utf-8\");\n } catch (err) {\n return { success: false, error: `Failed to read locale file: ${filePath}` };\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(raw);\n } catch (err) {\n return { success: false, error: `Failed to parse locale file: ${filePath}` };\n }\n\n // Strategy 1: check for flat/literal key at top level\n if (key in data && typeof data[key] === \"string\") {\n data[key] = newContent;\n } else {\n // Strategy 2: nested traversal via dot notation\n const parts = key.split(\".\");\n let current: Record<string, unknown> = data;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (current[part] == null || typeof current[part] !== \"object\") {\n return { success: false, error: `Key \"${key}\" not found in locale file ${filePath}` };\n }\n current = current[part] as Record<string, unknown>;\n }\n\n const leafKey = parts[parts.length - 1];\n if (!(leafKey in current) || typeof current[leafKey] !== \"string\") {\n return { success: false, error: `Key \"${key}\" not found in locale file ${filePath}` };\n }\n current[leafKey] = newContent;\n }\n\n try {\n await fs.writeFile(filePath, JSON.stringify(data, null, 2) + \"\\n\");\n } catch (err) {\n return { success: false, error: `Failed to write locale file: ${filePath}` };\n }\n\n return { success: true, filePath };\n }\n\n /**\n * Edit a plain-text or rich-text JSX node directly in the source TSX file.\n * For rich-text entries, `content` should be the inner JSX/HTML of the element's children.\n */\n async editTextDirect(params: PayloadEditTextDirect): Promise<EditResult> {\n const parsed = payloadEditTextDirect.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { id, content } = parsed.data;\n if (!this.registry) {\n try {\n await this.loadRegistry();\n } catch (err) {\n return { success: false, error: `Failed to load registry: ${err}` };\n }\n }\n const entry = this.registry!.elements[id];\n if (!entry) {\n return { success: false, error: `Element ${id} not found in registry` };\n }\n if (entry.type !== \"text\" && entry.type !== \"rich-text\" && entry.type !== \"mixed-text\") {\n return { success: false, error: `Element ${id} is not a text element (type: ${entry.type})` };\n }\n if (entry.type === \"mixed-text\") {\n return this.applyMixedTextEdit(id, entry, content);\n }\n // Text entries that map to a JS string literal (e.g. inline array items) must be\n // escaped for the literal's quote style before being written between the quotes.\n const finalContent = entry.quote ? escapeStringLiteralBody(content, entry.quote) : content;\n return this.applyEdit(id, entry, finalContent);\n }\n\n /**\n * Edit the className of an element\n */\n async editClassName(params: PayloadEditClassName): Promise<EditResult> {\n const parsed = payloadEditClassName.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { id, className: newClassName } = parsed.data;\n if (!this.registry) {\n try {\n await this.loadRegistry();\n } catch (err) {\n return { success: false, error: `Failed to load registry: ${err}` };\n }\n }\n\n const entry = this.registry!.elements[id];\n if (!entry) {\n return { success: false, error: `Element ${id} not found in registry` };\n }\n\n if (entry.type !== \"className\") {\n return { success: false, error: `Element ${id} is not a className element (type: ${entry.type})` };\n }\n\n return this.applyEdit(id, entry, newClassName);\n }\n\n /**\n * Edit the `src` of an <img> element directly in the source TSX file.\n * The byte range points at the string literal between the quotes, so we only\n * rewrite the path itself. Copying the asset into the project is handled by\n * the caller (the sandbox server, which has bucket access).\n */\n async editImage(params: PayloadEditImage): Promise<EditResult> {\n const parsed = payloadEditImage.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { id, src } = parsed.data;\n if (!this.registry) {\n try {\n await this.loadRegistry();\n } catch (err) {\n return { success: false, error: `Failed to load registry: ${err}` };\n }\n }\n\n const entry = this.registry!.elements[id];\n if (!entry) {\n return { success: false, error: `Element ${id} not found in registry` };\n }\n\n if (entry.type !== \"image\") {\n return { success: false, error: `Element ${id} is not an image element (type: ${entry.type})` };\n }\n\n return this.applyEdit(id, entry, src);\n }\n\n /**\n * Add a new element to an inline array literal (e.g. `[\"a\", \"b\"]`), used by the\n * editor's \"+\" control on editable .map() lists. The inserted element matches\n * the quote style of the existing string elements and reuses their separator so\n * multi-line indentation is preserved.\n */\n async arrayItemAdd(params: PayloadArrayItemAdd): Promise<EditResult> {\n const parsed = payloadArrayItemAdd.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { arrayId, content } = parsed.data;\n const loc = this.resolveArrayId(arrayId);\n if (!loc) return { success: false, error: `Invalid arrayId: ${arrayId}` };\n\n try {\n const code = await fs.readFile(loc.filePath, \"utf-8\");\n const arr = findArrayExpressionAt(code, loc.relativeFile, loc.offset);\n if (!arr) {\n return { success: false, error: `Array not found at ${arrayId}. The file may have been modified.` };\n }\n const els = arr.elements.filter((e): e is AstNode => e !== null);\n const quote = inferArrayQuote(code, els);\n const literal = `${quote}${escapeStringLiteralBody(content, quote)}${quote}`;\n\n const s = new MagicString(code);\n if (els.length === 0) {\n s.appendLeft(arr.start + 1, literal);\n } else {\n const last = els[els.length - 1];\n // Reuse the separator between the last two elements (keeps multi-line\n // indentation); fall back to \", \" for a single-element array.\n const sep = els.length >= 2 ? code.slice(els[els.length - 2].end, last.start) : \", \";\n s.appendLeft(last.end, `${sep}${literal}`);\n }\n\n await fs.writeFile(loc.filePath, s.toString());\n // Element count changed — drop the cached registry so it reloads after the\n // dev pipeline re-transforms the file.\n this.registry = null;\n return { success: true, filePath: loc.filePath };\n } catch (err) {\n return { success: false, error: String(err) };\n }\n }\n\n /**\n * Delete the element at `index` from an inline array literal. Refuses to remove\n * the last remaining element (which would leave an empty, un-addressable array).\n */\n async arrayItemDelete(params: PayloadArrayItemDelete): Promise<EditResult> {\n const parsed = payloadArrayItemDelete.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { arrayId, index } = parsed.data;\n const loc = this.resolveArrayId(arrayId);\n if (!loc) return { success: false, error: `Invalid arrayId: ${arrayId}` };\n\n try {\n const code = await fs.readFile(loc.filePath, \"utf-8\");\n const arr = findArrayExpressionAt(code, loc.relativeFile, loc.offset);\n if (!arr) {\n return { success: false, error: `Array not found at ${arrayId}. The file may have been modified.` };\n }\n const els = arr.elements.filter((e): e is AstNode => e !== null);\n if (index >= els.length) {\n return { success: false, error: `Index ${index} out of range for array at ${arrayId}` };\n }\n if (els.length <= 1) {\n return { success: false, error: \"Cannot delete the last remaining array item\" };\n }\n\n const s = new MagicString(code);\n const el = els[index];\n if (index > 0) {\n // Remove the separator before the element + the element itself.\n s.remove(els[index - 1].end, el.end);\n } else {\n // First element: remove it + the separator after it.\n s.remove(el.start, els[1].start);\n }\n\n await fs.writeFile(loc.filePath, s.toString());\n this.registry = null;\n return { success: true, filePath: loc.filePath };\n } catch (err) {\n return { success: false, error: String(err) };\n }\n }\n\n /**\n * Replace the entire contents of an inline array literal with `items`, preserving\n * the source's quote style and multi-line indentation. Used by the editor's\n * batched ✓ apply so a whole add/delete session is a single edit + rebuild.\n */\n async arraySet(params: PayloadArraySet): Promise<EditResult> {\n const parsed = payloadArraySet.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { arrayId, items } = parsed.data;\n const loc = this.resolveArrayId(arrayId);\n if (!loc) return { success: false, error: `Invalid arrayId: ${arrayId}` };\n\n try {\n const code = await fs.readFile(loc.filePath, \"utf-8\");\n const arr = findArrayExpressionAt(code, loc.relativeFile, loc.offset);\n if (!arr) {\n return { success: false, error: `Array not found at ${arrayId}. The file may have been modified.` };\n }\n const els = arr.elements.filter((e): e is AstNode => e !== null);\n const quote = inferArrayQuote(code, els);\n const literal = (v: string) => `${quote}${escapeStringLiteralBody(v, quote)}${quote}`;\n\n const innerStart = arr.start + 1;\n const innerEnd = arr.end - 1;\n const innerSrc = code.slice(innerStart, innerEnd);\n\n let inner: string;\n if (innerSrc.includes(\"\\n\")) {\n // Multi-line: reuse the item indentation and the closing-bracket indent,\n // and keep a trailing comma (matches the common prettier/biome style).\n const itemIndent = innerSrc.match(/\\n([ \\t]*)\\S/)?.[1] ?? \" \";\n const outerIndent = innerSrc.match(/\\n([ \\t]*)$/)?.[1] ?? \"\";\n inner = `\\n${items.map((it) => itemIndent + literal(it)).join(\",\\n\")},\\n${outerIndent}`;\n } else {\n inner = items.map(literal).join(\", \");\n }\n\n const s = new MagicString(code);\n if (innerStart === innerEnd) {\n s.appendLeft(innerStart, inner);\n } else {\n s.overwrite(innerStart, innerEnd, inner);\n }\n await fs.writeFile(loc.filePath, s.toString());\n this.registry = null;\n return { success: true, filePath: loc.filePath };\n } catch (err) {\n return { success: false, error: String(err) };\n }\n }\n\n /** Parse \"<relativeFile>:<offset>\" into an absolute path + numeric offset. */\n private resolveArrayId(arrayId: string): { filePath: string; relativeFile: string; offset: number } | null {\n const sep = arrayId.lastIndexOf(\":\");\n if (sep < 0) return null;\n const relativeFile = arrayId.slice(0, sep);\n const offset = Number(arrayId.slice(sep + 1));\n if (!relativeFile || !Number.isInteger(offset) || offset < 0) return null;\n return { filePath: path.join(this.projectRoot, relativeFile), relativeFile, offset };\n }\n\n /**\n * Apply an edit to a source file\n */\n private async applyEdit(id: string, entry: EditableEntry, newContent: string): Promise<EditResult> {\n const filePath = path.join(this.projectRoot, entry.file);\n\n try {\n const code = await fs.readFile(filePath, \"utf-8\");\n\n // Verify content at expected location\n const currentContent = code.slice(entry.startOffset, entry.endOffset);\n\n let actualStart = entry.startOffset;\n let actualEnd = entry.endOffset;\n\n if (currentContent !== entry.originalContent) {\n // Content has shifted - try to find it by searching. An empty original\n // can't be located by search (indexOf(\"\") === 0 would be a false match),\n // so treat it as not found rather than inserting at offset 0.\n const searchIndex = entry.originalContent !== \"\" ? code.indexOf(entry.originalContent) : -1;\n if (searchIndex === -1) {\n return {\n success: false,\n error: `Original content \"${entry.originalContent}\" not found in file ${entry.file}. The file may have been modified.`,\n };\n }\n actualStart = searchIndex;\n actualEnd = searchIndex + entry.originalContent.length;\n }\n\n // Apply the edit using MagicString. A zero-length range (e.g. an empty\n // string literal \"\") cannot be overwritten — insert the content instead.\n const s = new MagicString(code);\n if (actualStart === actualEnd) {\n s.appendLeft(actualStart, newContent);\n } else {\n s.overwrite(actualStart, actualEnd, newContent);\n }\n\n // Write the modified file\n await fs.writeFile(filePath, s.toString());\n\n // Calculate the length difference for offset adjustments\n const lengthDiff = newContent.length - entry.originalContent.length;\n\n // Update the registry entry\n entry.startOffset = actualStart;\n entry.endOffset = actualStart + newContent.length;\n entry.originalContent = newContent;\n\n // Shift all subsequent entries in the same file\n for (const [otherId, otherEntry] of Object.entries(this.registry!.elements)) {\n if (otherId !== id && otherEntry.file === entry.file && otherEntry.startOffset > actualStart) {\n otherEntry.startOffset += lengthDiff;\n otherEntry.endOffset += lengthDiff;\n }\n }\n\n // Save the updated registry\n await fs.writeFile(this.registryPath, JSON.stringify(this.registry, null, 2));\n\n return { success: true, filePath };\n } catch (err) {\n return { success: false, error: String(err) };\n }\n }\n\n /**\n * Reconstruct JSX children from a user-edited template and the original expression segments,\n * then write the result back to the TSX source file.\n *\n * `template` uses `{{N}}` placeholders for expressions, e.g.:\n * \"© {{0}} Alex's Kitchen. All rights reserved.\"\n * The server replaces each placeholder with the original expression source from the registry.\n */\n private async applyMixedTextEdit(id: string, entry: EditableEntry, template: string): Promise<EditResult> {\n const exprSegments = (entry.segments ?? []).filter((s) => s.type === \"expr\");\n\n // Split template by {{N}} placeholders — odd indices are expression indices\n const parts = template.split(/\\{\\{(\\d+)\\}\\}/);\n\n // Reconstruct inner JSX: text parts interleaved with original expression sources\n let inner = \"\";\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n inner += parts[i];\n } else {\n const exprSeg = exprSegments[parseInt(parts[i])];\n inner += exprSeg ? exprSeg.raw : `{{${parts[i]}}}`;\n }\n }\n\n // Preserve leading/trailing JSX whitespace (indentation) from the original source\n const leadingWS = entry.originalContent.match(/^(\\s*)/)?.[1] ?? \"\";\n const trailingWS = entry.originalContent.match(/(\\s*)$/)?.[1] ?? \"\";\n const newContent = leadingWS + inner + trailingWS;\n\n return this.applyEdit(id, entry, newContent);\n }\n\n /**\n * Get element info by ID\n */\n getElement(id: string): EditableEntry | undefined {\n return this.registry?.elements[id];\n }\n\n /**\n * Get all elements of a specific type\n */\n getElementsByType(type: \"text\" | \"className\"): Record<string, EditableEntry> {\n if (!this.registry) {\n return {};\n }\n\n const result: Record<string, EditableEntry> = {};\n for (const [id, entry] of Object.entries(this.registry.elements)) {\n if (entry.type === type) {\n result[id] = entry;\n }\n }\n return result;\n }\n\n /**\n * Get all elements in a specific file\n */\n getElementsByFile(file: string): Record<string, EditableEntry> {\n if (!this.registry) {\n return {};\n }\n\n const result: Record<string, EditableEntry> = {};\n for (const [id, entry] of Object.entries(this.registry.elements)) {\n if (entry.file === file) {\n result[id] = entry;\n }\n }\n return result;\n }\n}\n"],"mappings":"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"}
|
|
@@ -12,11 +12,12 @@ interface EditableSegment {
|
|
|
12
12
|
}
|
|
13
13
|
interface EditableEntry {
|
|
14
14
|
file: string;
|
|
15
|
-
type: "text" | "rich-text" | "className" | "mixed-text";
|
|
15
|
+
type: "text" | "rich-text" | "className" | "mixed-text" | "image";
|
|
16
16
|
startOffset: number;
|
|
17
17
|
endOffset: number;
|
|
18
18
|
originalContent: string;
|
|
19
19
|
segments?: EditableSegment[];
|
|
20
|
+
quote?: string;
|
|
20
21
|
context: {
|
|
21
22
|
parentTag: string;
|
|
22
23
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-upstart-attrs.d.ts","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"mappings":";;;;UAgBU,OAAA;EACR,OAAA;EACA,YAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"vite-plugin-upstart-attrs.d.ts","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"mappings":";;;;UAgBU,OAAA;EACR,OAAA;EACA,YAAA;AAAA;AAAA,UA4Ce,eAAA;EACf,IAAA;EAGA,GAAA;AAAA;AAAA,UAIe,aAAA;EACf,IAAA;EACA,IAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EAEA,QAAA,GAAW,eAAA;EAIX,KAAA;EACA,OAAA;IAAW,SAAA;EAAA;AAAA;AAAA,iBAcG,WAAA,CAAA,GAAe,MAAA,SAAe,aAAA;AAAA,iBAK9B,aAAA,CAAA;AAAA,cAeH,aAAA,EAAa,UAAA,CAAA,gBAAA,CAAA,OAAA;AAAA,iBA8FV,gBAAA,CAAiB,IAAA,UAAc,QAAA;;OAAf,eAAA,CAAA,SAAA;AAAA;AAAA,cAiY/B,QAAA"}
|