@lovision/plugin-dev 1.0.0

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 (96) hide show
  1. package/README.md +49 -0
  2. package/dist/build.d.ts +16 -0
  3. package/dist/build.d.ts.map +1 -0
  4. package/dist/build.js +108 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +123 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/create-plugin.d.ts +19 -0
  11. package/dist/create-plugin.d.ts.map +1 -0
  12. package/dist/create-plugin.js +186 -0
  13. package/dist/create-plugin.js.map +1 -0
  14. package/dist/dev.d.ts +13 -0
  15. package/dist/dev.d.ts.map +1 -0
  16. package/dist/dev.js +206 -0
  17. package/dist/dev.js.map +1 -0
  18. package/dist/index.d.ts +13 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +7 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/publish.d.ts +15 -0
  23. package/dist/publish.d.ts.map +1 -0
  24. package/dist/publish.js +55 -0
  25. package/dist/publish.js.map +1 -0
  26. package/dist/shared.d.ts +93 -0
  27. package/dist/shared.d.ts.map +1 -0
  28. package/dist/shared.js +436 -0
  29. package/dist/shared.js.map +1 -0
  30. package/dist/templates/ai-layout-assistant/README.md.template +24 -0
  31. package/dist/templates/ai-layout-assistant/eslint.config.mjs +19 -0
  32. package/dist/templates/ai-layout-assistant/manifest.json.template +38 -0
  33. package/dist/templates/ai-layout-assistant/package.json.template +18 -0
  34. package/dist/templates/ai-layout-assistant/src/main.ts.template +345 -0
  35. package/dist/templates/ai-layout-assistant/tsconfig.json +14 -0
  36. package/dist/templates/ai-layout-assistant/ui.html.template +114 -0
  37. package/dist/templates/asset-browser/README.md.template +24 -0
  38. package/dist/templates/asset-browser/eslint.config.mjs +19 -0
  39. package/dist/templates/asset-browser/manifest.json.template +29 -0
  40. package/dist/templates/asset-browser/package.json.template +18 -0
  41. package/dist/templates/asset-browser/src/main.ts.template +177 -0
  42. package/dist/templates/asset-browser/tsconfig.json +14 -0
  43. package/dist/templates/asset-browser/ui.html.template +137 -0
  44. package/dist/templates/base/README.md.template +34 -0
  45. package/dist/templates/base/eslint.config.mjs +19 -0
  46. package/dist/templates/base/manifest.json.template +22 -0
  47. package/dist/templates/base/package.json.template +18 -0
  48. package/dist/templates/base/src/main.ts.template +20 -0
  49. package/dist/templates/base/tsconfig.json +14 -0
  50. package/dist/templates/batch-layout-organizer/README.md.template +24 -0
  51. package/dist/templates/batch-layout-organizer/eslint.config.mjs +19 -0
  52. package/dist/templates/batch-layout-organizer/manifest.json.template +31 -0
  53. package/dist/templates/batch-layout-organizer/package.json.template +18 -0
  54. package/dist/templates/batch-layout-organizer/src/main.ts.template +324 -0
  55. package/dist/templates/batch-layout-organizer/tsconfig.json +14 -0
  56. package/dist/templates/batch-layout-organizer/ui.html.template +116 -0
  57. package/dist/templates/data-filler-full/README.md.template +32 -0
  58. package/dist/templates/data-filler-full/eslint.config.mjs +19 -0
  59. package/dist/templates/data-filler-full/manifest.json.template +31 -0
  60. package/dist/templates/data-filler-full/package.json.template +18 -0
  61. package/dist/templates/data-filler-full/src/main.ts.template +412 -0
  62. package/dist/templates/data-filler-full/tsconfig.json +14 -0
  63. package/dist/templates/data-filler-full/ui.html.template +221 -0
  64. package/dist/templates/data-filler-lite/README.md.template +47 -0
  65. package/dist/templates/data-filler-lite/eslint.config.mjs +19 -0
  66. package/dist/templates/data-filler-lite/manifest.json.template +29 -0
  67. package/dist/templates/data-filler-lite/package.json.template +18 -0
  68. package/dist/templates/data-filler-lite/src/main.ts.template +222 -0
  69. package/dist/templates/data-filler-lite/tsconfig.json +14 -0
  70. package/dist/templates/data-filler-lite/ui.html.template +180 -0
  71. package/dist/templates/design-lint-panel/README.md.template +33 -0
  72. package/dist/templates/design-lint-panel/eslint.config.mjs +19 -0
  73. package/dist/templates/design-lint-panel/manifest.json.template +29 -0
  74. package/dist/templates/design-lint-panel/package.json.template +18 -0
  75. package/dist/templates/design-lint-panel/src/main.ts.template +221 -0
  76. package/dist/templates/design-lint-panel/tsconfig.json +14 -0
  77. package/dist/templates/design-lint-panel/ui.html.template +172 -0
  78. package/dist/templates/export-selection/README.md.template +26 -0
  79. package/dist/templates/export-selection/eslint.config.mjs +19 -0
  80. package/dist/templates/export-selection/manifest.json.template +31 -0
  81. package/dist/templates/export-selection/package.json.template +18 -0
  82. package/dist/templates/export-selection/src/main.ts.template +386 -0
  83. package/dist/templates/export-selection/tsconfig.json +14 -0
  84. package/dist/templates/export-selection/ui.html.template +163 -0
  85. package/dist/templates/review-submitter/README.md.template +24 -0
  86. package/dist/templates/review-submitter/eslint.config.mjs +19 -0
  87. package/dist/templates/review-submitter/manifest.json.template +35 -0
  88. package/dist/templates/review-submitter/package.json.template +18 -0
  89. package/dist/templates/review-submitter/src/main.ts.template +306 -0
  90. package/dist/templates/review-submitter/tsconfig.json +14 -0
  91. package/dist/templates/review-submitter/ui.html.template +114 -0
  92. package/dist/validate.d.ts +8 -0
  93. package/dist/validate.d.ts.map +1 -0
  94. package/dist/validate.js +42 -0
  95. package/dist/validate.js.map +1 -0
  96. package/package.json +46 -0
@@ -0,0 +1,324 @@
1
+ import { definePlugin } from "@lovision/plugin-sdk";
2
+
3
+ type Vec2 = {
4
+ x: number;
5
+ y: number;
6
+ };
7
+
8
+ type Size = {
9
+ height: number;
10
+ width: number;
11
+ };
12
+
13
+ type SceneNodeSnapshot = {
14
+ children: SceneNodeSnapshot[];
15
+ id: string;
16
+ name: string;
17
+ parentId: string | null;
18
+ position: Vec2;
19
+ size: Size;
20
+ type: string;
21
+ };
22
+
23
+ type SceneSnapshot = {
24
+ root: SceneNodeSnapshot;
25
+ version: number;
26
+ };
27
+
28
+ type NodeCreateSpec = {
29
+ name?: string;
30
+ parentId?: string | null;
31
+ position?: Vec2;
32
+ size?: Size;
33
+ type: string;
34
+ };
35
+
36
+ type MutationResult = {
37
+ newVersion: number;
38
+ };
39
+
40
+ type NodesApi = {
41
+ create(
42
+ specs: NodeCreateSpec[],
43
+ opts?: { expectedVersion?: number },
44
+ ): Promise<MutationResult>;
45
+ group(params: {
46
+ expectedVersion?: number;
47
+ name?: string;
48
+ nodeIds: string[];
49
+ parentId?: string | null;
50
+ }): Promise<{ groupId: string; newVersion: number }>;
51
+ reparent(params: {
52
+ expectedVersion?: number;
53
+ index?: number;
54
+ nodeIds: string[];
55
+ parentId: string | null;
56
+ preserveAbsolutePosition?: boolean;
57
+ }): Promise<MutationResult>;
58
+ reorder(params: {
59
+ expectedVersion?: number;
60
+ index: number;
61
+ nodeIds: string[];
62
+ parentId?: string | null;
63
+ }): Promise<MutationResult>;
64
+ };
65
+
66
+ type OrganizerContext = {
67
+ document: {
68
+ snapshot(): Promise<SceneSnapshot>;
69
+ };
70
+ nodes: NodesApi;
71
+ notify: {
72
+ send(
73
+ message: string,
74
+ options?: { kind?: "error" | "info" | "success" | "warning" },
75
+ ): Promise<void>;
76
+ };
77
+ selection: {
78
+ get(): Promise<string[]>;
79
+ };
80
+ viewport: {
81
+ focusNode(nodeId: string): Promise<void>;
82
+ };
83
+ };
84
+
85
+ type OrganizerMessage =
86
+ | {
87
+ type: "analysis";
88
+ payload: {
89
+ message: string;
90
+ targetNames: string[];
91
+ totalNodes: number;
92
+ };
93
+ }
94
+ | {
95
+ type: "error";
96
+ payload: {
97
+ message: string;
98
+ };
99
+ }
100
+ | {
101
+ type: "organized";
102
+ payload: {
103
+ childCount: number;
104
+ groupId: string;
105
+ newVersion: number;
106
+ };
107
+ }
108
+ | {
109
+ type: "ready";
110
+ payload: {
111
+ message: string;
112
+ };
113
+ };
114
+
115
+ const GROUP_NAME = "Batch Layout Group";
116
+
117
+ definePlugin({
118
+ apiVersion: "1.0",
119
+ version: "0.1.0",
120
+ command: async (ctx) => {
121
+ const session = await ctx.ui.show({
122
+ mode: "panel",
123
+ entry: "./ui.html",
124
+ title: "Batch Layout Organizer",
125
+ width: 420,
126
+ });
127
+
128
+ let closed = false;
129
+ const postToUi = async (message: OrganizerMessage): Promise<void> => {
130
+ if (closed) return;
131
+ await session.postMessage(message);
132
+ };
133
+ const panelClosed = new Promise<void>((resolve) => {
134
+ session.on("close", () => {
135
+ closed = true;
136
+ void ctx.notify.send("Batch Layout Organizer closed.", {
137
+ kind: "info",
138
+ });
139
+ resolve();
140
+ });
141
+ });
142
+
143
+ session.on("analyze-canvas", async () => {
144
+ await handleUiAction(postToUi, async () => {
145
+ const analysis = await analyzeScene(ctx);
146
+ await postToUi({
147
+ type: "analysis",
148
+ payload: analysis,
149
+ });
150
+ });
151
+ });
152
+
153
+ session.on("organize-layout", async () => {
154
+ await handleUiAction(postToUi, async () => {
155
+ const result = await organizeLayout(ctx);
156
+ await postToUi({
157
+ type: "organized",
158
+ payload: result,
159
+ });
160
+ await ctx.notify.send(
161
+ `Batch Layout Organizer organized ${result.childCount} node(s).`,
162
+ { kind: "success" },
163
+ );
164
+ });
165
+ });
166
+
167
+ await postToUi({
168
+ type: "ready",
169
+ payload: {
170
+ message:
171
+ "Ready. Analyze the scene, then organize a batch into a grouped hierarchy.",
172
+ },
173
+ });
174
+
175
+ await panelClosed;
176
+ return { closed: true, ok: true };
177
+ },
178
+ });
179
+
180
+ async function handleUiAction(
181
+ postToUi: (message: OrganizerMessage) => Promise<void>,
182
+ action: () => Promise<unknown>,
183
+ ): Promise<void> {
184
+ try {
185
+ await action();
186
+ } catch (error) {
187
+ const message = error instanceof Error ? error.message : String(error);
188
+ await postToUi({ type: "error", payload: { message } });
189
+ }
190
+ }
191
+
192
+ async function analyzeScene(ctx: OrganizerContext): Promise<{
193
+ message: string;
194
+ targetNames: string[];
195
+ totalNodes: number;
196
+ }> {
197
+ const snapshot = await ctx.document.snapshot();
198
+ const selection = await ctx.selection.get();
199
+ const nodes = collectNodes(snapshot.root).filter(
200
+ (node) => node.id !== snapshot.root.id,
201
+ );
202
+ const targetNames = resolveCandidateNodes(snapshot, selection)
203
+ .slice(0, 3)
204
+ .map((node) => node.name);
205
+ const message =
206
+ targetNames.length >= 3
207
+ ? "Found at least three candidate nodes."
208
+ : "Scene is sparse; Organize layout will create three demo cards first.";
209
+ return {
210
+ message,
211
+ targetNames,
212
+ totalNodes: nodes.length,
213
+ };
214
+ }
215
+
216
+ async function organizeLayout(ctx: OrganizerContext): Promise<{
217
+ childCount: number;
218
+ groupId: string;
219
+ newVersion: number;
220
+ }> {
221
+ const prepared = await prepareTargets(ctx);
222
+ const firstId = prepared.targetIds[0];
223
+ const secondId = prepared.targetIds[1];
224
+ const promotedId = prepared.targetIds[2];
225
+ if (!firstId || !secondId || !promotedId) {
226
+ throw new Error("Batch Layout Organizer needs at least three nodes.");
227
+ }
228
+
229
+ const groupResult = await ctx.nodes.group({
230
+ nodeIds: [firstId, secondId],
231
+ name: GROUP_NAME,
232
+ expectedVersion: prepared.version,
233
+ });
234
+ const reparentResult = await ctx.nodes.reparent({
235
+ nodeIds: [promotedId],
236
+ parentId: groupResult.groupId,
237
+ index: 2,
238
+ preserveAbsolutePosition: true,
239
+ expectedVersion: groupResult.newVersion,
240
+ });
241
+ const reorderResult = await ctx.nodes.reorder({
242
+ nodeIds: [promotedId],
243
+ parentId: groupResult.groupId,
244
+ index: 0,
245
+ expectedVersion: reparentResult.newVersion,
246
+ });
247
+ await ctx.viewport.focusNode(groupResult.groupId);
248
+ return {
249
+ childCount: 3,
250
+ groupId: groupResult.groupId,
251
+ newVersion: reorderResult.newVersion,
252
+ };
253
+ }
254
+
255
+ async function prepareTargets(ctx: OrganizerContext): Promise<{
256
+ targetIds: string[];
257
+ version: number;
258
+ }> {
259
+ const snapshot = await ctx.document.snapshot();
260
+ const selection = await ctx.selection.get();
261
+ const candidates = resolveCandidateNodes(snapshot, selection);
262
+ if (candidates.length >= 3) {
263
+ return {
264
+ targetIds: candidates.slice(0, 3).map((node) => node.id),
265
+ version: snapshot.version,
266
+ };
267
+ }
268
+
269
+ const runId = Date.now().toString(36);
270
+ const cardNames = [
271
+ `Batch Layout Card A ${runId}`,
272
+ `Batch Layout Card B ${runId}`,
273
+ `Batch Layout Card C ${runId}`,
274
+ ];
275
+ await ctx.nodes.create(
276
+ cardNames.map((name, index) => ({
277
+ type: "shape",
278
+ name,
279
+ parentId: null,
280
+ position: { x: 120 + index * 120, y: 360 },
281
+ size: { width: 96, height: 72 },
282
+ })),
283
+ { expectedVersion: snapshot.version },
284
+ );
285
+
286
+ const afterCreate = await ctx.document.snapshot();
287
+ const created = collectNodes(afterCreate.root)
288
+ .filter((node) => cardNames.includes(node.name))
289
+ .sort((left, right) => left.name.localeCompare(right.name));
290
+ if (created.length < 3) {
291
+ throw new Error("Could not find the demo cards after creation.");
292
+ }
293
+ return {
294
+ targetIds: created.slice(0, 3).map((node) => node.id),
295
+ version: afterCreate.version,
296
+ };
297
+ }
298
+
299
+ function resolveCandidateNodes(
300
+ snapshot: SceneSnapshot,
301
+ selection: string[],
302
+ ): SceneNodeSnapshot[] {
303
+ const allNodes = collectNodes(snapshot.root).filter(
304
+ (node) => node.id !== snapshot.root.id,
305
+ );
306
+ const selected = selection
307
+ .map((nodeId) => allNodes.find((node) => node.id === nodeId))
308
+ .filter((node): node is SceneNodeSnapshot => node !== undefined);
309
+ if (selected.length >= 3) {
310
+ return selected;
311
+ }
312
+ return snapshot.root.children.filter((node) => node.name !== GROUP_NAME);
313
+ }
314
+
315
+ function collectNodes(
316
+ node: SceneNodeSnapshot,
317
+ out: SceneNodeSnapshot[] = [],
318
+ ): SceneNodeSnapshot[] {
319
+ out.push(node);
320
+ for (const child of node.children) {
321
+ collectNodes(child, out);
322
+ }
323
+ return out;
324
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext", "WebWorker"],
4
+ "module": "Preserve",
5
+ "moduleResolution": "bundler",
6
+ "target": "ESNext",
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "allowImportingTsExtensions": true,
10
+ "verbatimModuleSyntax": true,
11
+ "noEmit": true
12
+ },
13
+ "include": ["src/**/*"]
14
+ }
@@ -0,0 +1,116 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Batch Layout Organizer</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light dark;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ color: CanvasText;
15
+ background: Canvas;
16
+ }
17
+
18
+ main {
19
+ display: grid;
20
+ gap: 1rem;
21
+ padding: 1rem;
22
+ }
23
+
24
+ .actions {
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ gap: 0.5rem;
28
+ }
29
+
30
+ .status {
31
+ min-height: 1.5rem;
32
+ }
33
+
34
+ pre {
35
+ max-height: 10rem;
36
+ overflow: auto;
37
+ padding: 0.75rem;
38
+ color: CanvasText;
39
+ background: Canvas;
40
+ border: 1px solid ButtonBorder;
41
+ }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <main>
46
+ <header>
47
+ <h1>Batch Layout Organizer</h1>
48
+ <p>
49
+ Analyze candidate nodes, then organize a batch into a grouped
50
+ hierarchy through the plugin main worker.
51
+ </p>
52
+ </header>
53
+
54
+ <div class="actions">
55
+ <button id="analyze-button" type="button">Analyze canvas</button>
56
+ <button id="organize-button" type="button">Organize layout</button>
57
+ </div>
58
+
59
+ <p id="status" class="status" role="status">Waiting for host...</p>
60
+ <pre id="details" aria-label="Details"></pre>
61
+ </main>
62
+
63
+ <script>
64
+ const uiId = globalThis.__LOVISION_PLUGIN_UI_ID__;
65
+ const status = document.getElementById("status");
66
+ const details = document.getElementById("details");
67
+
68
+ function post(type, payload) {
69
+ parent.postMessage(
70
+ {
71
+ type: "plugin-ui-message",
72
+ direction: "ui-to-main",
73
+ uiId,
74
+ message: { type, payload },
75
+ },
76
+ "*",
77
+ );
78
+ }
79
+
80
+ document.getElementById("analyze-button").addEventListener("click", () => {
81
+ status.textContent = "Analyzing...";
82
+ post("analyze-canvas", {});
83
+ });
84
+
85
+ document.getElementById("organize-button").addEventListener("click", () => {
86
+ status.textContent = "Organizing...";
87
+ post("organize-layout", {});
88
+ });
89
+
90
+ window.addEventListener("message", (event) => {
91
+ const envelope = event.data;
92
+ if (
93
+ !envelope ||
94
+ envelope.type !== "plugin-ui-message" ||
95
+ envelope.direction !== "main-to-ui" ||
96
+ envelope.uiId !== uiId
97
+ ) {
98
+ return;
99
+ }
100
+
101
+ const message = envelope.message;
102
+ if (message.type === "ready") {
103
+ status.textContent = message.payload.message;
104
+ } else if (message.type === "analysis") {
105
+ status.textContent = `${message.payload.message} Found ${message.payload.totalNodes} node(s).`;
106
+ details.textContent = JSON.stringify(message.payload.targetNames, null, 2);
107
+ } else if (message.type === "organized") {
108
+ status.textContent = `Organized ${message.payload.childCount} node(s).`;
109
+ details.textContent = JSON.stringify(message.payload, null, 2);
110
+ } else if (message.type === "error") {
111
+ status.textContent = message.payload.message;
112
+ }
113
+ });
114
+ </script>
115
+ </body>
116
+ </html>
@@ -0,0 +1,32 @@
1
+ # Data Filler
2
+
3
+ This Step 27 acceptance template is the full Data Filler demo. The UI sends field mapping intent to the plugin main worker; the worker reads the scene snapshot, resolves text nodes, writes content with `ctx.nodes.setText`, and updates node properties with `ctx.nodes.update`.
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Copy the printed `manifestUrl=...`, open the editor shell, then use Dev Panel -> Development -> Add by URL.
13
+
14
+ ## Use
15
+
16
+ Run **Open Data Filler**, paste CSV or JSON, set the field mapping, click **Preview mapping**, then click **Apply mapping**.
17
+
18
+ The default mapping is:
19
+
20
+ - Text field: `title`
21
+ - Name field: `name`
22
+ - Opacity field: `opacity`
23
+
24
+ If the current canvas has no text nodes, the plugin creates one demo text node and then writes through `ctx.nodes.setText`; it does not use node name as a substitute for text content.
25
+
26
+ ## Formal install
27
+
28
+ ```bash
29
+ npm run build
30
+ ```
31
+
32
+ Upload the generated `dist/*.bundle.json` through Dev Panel -> Formal install, confirm the install dialog, then run the command from MainMenu.
@@ -0,0 +1,19 @@
1
+ import pluginSdkConfig from "@lovision/plugin-sdk/eslint-config";
2
+ import tsParser from "@typescript-eslint/parser";
3
+
4
+ const pluginFiles = ["src/**/*.ts"];
5
+
6
+ export default [
7
+ {
8
+ files: pluginFiles,
9
+ languageOptions: {
10
+ parser: tsParser,
11
+ ecmaVersion: "latest",
12
+ sourceType: "module",
13
+ },
14
+ },
15
+ ...pluginSdkConfig.map((entry) => ({
16
+ ...entry,
17
+ files: pluginFiles,
18
+ })),
19
+ ];
@@ -0,0 +1,31 @@
1
+ {
2
+ "id": "__PLUGIN_ID__",
3
+ "name": {
4
+ "en": "Data Filler",
5
+ "zh-CN": "Data Filler"
6
+ },
7
+ "version": "0.1.0",
8
+ "apiVersion": "1.0",
9
+ "editorType": ["design"],
10
+ "main": "./dist/main.js",
11
+ "ui": "./ui.html",
12
+ "documentAccess": "current-page",
13
+ "permissions": [
14
+ "document:read",
15
+ "document:write.create",
16
+ "document:write.style",
17
+ "document:write.text",
18
+ "notify",
19
+ "selection:read",
20
+ "ui:modal"
21
+ ],
22
+ "commands": [
23
+ {
24
+ "id": "run",
25
+ "name": {
26
+ "en": "__COMMAND_NAME_EN__",
27
+ "zh-CN": "__COMMAND_NAME_ZH__"
28
+ }
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "plugin-dev dev",
7
+ "build": "plugin-dev build",
8
+ "validate": "plugin-dev validate",
9
+ "lint": "eslint src --max-warnings=0"
10
+ },
11
+ "devDependencies": {
12
+ "@lovision/plugin-dev": "__PLUGIN_DEV_SPEC__",
13
+ "@lovision/plugin-sdk": "__PLUGIN_SDK_SPEC__"__LOCAL_SUPPORT_DEPS__,
14
+ "@typescript-eslint/parser": "^8.30.0",
15
+ "eslint": "^9.25.1",
16
+ "typescript": "^5.8.3"
17
+ }__LOCAL_OVERRIDES__
18
+ }