@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,29 @@
1
+ {
2
+ "id": "__PLUGIN_ID__",
3
+ "name": {
4
+ "en": "Data Filler Lite",
5
+ "zh-CN": "Data Filler Lite"
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.style",
16
+ "notify",
17
+ "selection:read",
18
+ "ui:modal"
19
+ ],
20
+ "commands": [
21
+ {
22
+ "id": "run",
23
+ "name": {
24
+ "en": "__COMMAND_NAME_EN__",
25
+ "zh-CN": "__COMMAND_NAME_ZH__"
26
+ }
27
+ }
28
+ ]
29
+ }
@@ -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
+ }
@@ -0,0 +1,222 @@
1
+ import { definePlugin } from "@lovision/plugin-sdk";
2
+
3
+ type DataRow = Record<string, string>;
4
+
5
+ type ApplyPayload = {
6
+ text: string;
7
+ };
8
+
9
+ type UserDecision =
10
+ | {
11
+ kind: "apply";
12
+ payload: unknown;
13
+ }
14
+ | {
15
+ kind: "close";
16
+ };
17
+
18
+ definePlugin({
19
+ apiVersion: "1.0",
20
+ version: "0.1.0",
21
+ command: async (ctx) => {
22
+ const session = await ctx.ui.show({
23
+ entry: "./ui.html",
24
+ title: "Data Filler Lite",
25
+ width: 560,
26
+ height: 520,
27
+ });
28
+
29
+ let closed = false;
30
+ const userDecision = new Promise<UserDecision>((resolve) => {
31
+ session.on("close", () => {
32
+ closed = true;
33
+ resolve({ kind: "close" });
34
+ });
35
+ session.on("apply", (payload) => {
36
+ resolve({ kind: "apply", payload });
37
+ });
38
+ });
39
+
40
+ await session.postMessage({
41
+ type: "ready",
42
+ payload: {
43
+ message:
44
+ "Paste CSV or JSON. Selection is used first; empty selection falls back to the first top-level node.",
45
+ },
46
+ });
47
+
48
+ const decision = await userDecision;
49
+ if (decision.kind === "close") {
50
+ await ctx.notify.send("Data Filler Lite closed without applying data.", {
51
+ kind: "info",
52
+ });
53
+ return { applied: 0, closed: true, ok: true };
54
+ }
55
+
56
+ try {
57
+ const payload = readApplyPayload(decision.payload);
58
+ const rows = parseRows(payload.text);
59
+ const targetIds = await resolveTargetNodeIds(ctx);
60
+ if (targetIds.length === 0) {
61
+ throw new Error("No selected or top-level canvas node is available.");
62
+ }
63
+
64
+ const updates = targetIds.map((id, index) => ({
65
+ id,
66
+ changes: {
67
+ name: nameForRow(rows[index % rows.length], index),
68
+ },
69
+ }));
70
+ const result = await ctx.nodes.update(updates);
71
+ if (!closed) {
72
+ await session.postMessage({
73
+ type: "applied",
74
+ payload: {
75
+ applied: updates.length,
76
+ newVersion: result.newVersion,
77
+ },
78
+ });
79
+ }
80
+ await ctx.notify.send(`Data Filler Lite applied ${updates.length} row(s).`, {
81
+ kind: "success",
82
+ });
83
+ return {
84
+ applied: updates.length,
85
+ newVersion: result.newVersion,
86
+ ok: true,
87
+ };
88
+ } catch (error) {
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ if (!closed) {
91
+ await session.postMessage({
92
+ type: "error",
93
+ payload: { message },
94
+ });
95
+ }
96
+ await ctx.notify.send(`Data Filler Lite failed: ${message}`, {
97
+ kind: "error",
98
+ });
99
+ return { error: message, ok: false };
100
+ }
101
+ },
102
+ });
103
+
104
+ function readApplyPayload(payload: unknown): ApplyPayload {
105
+ if (!isRecord(payload) || typeof payload.text !== "string") {
106
+ throw new Error("Apply payload must include a text field.");
107
+ }
108
+ return { text: payload.text };
109
+ }
110
+
111
+ async function resolveTargetNodeIds(ctx: {
112
+ document: { snapshot(): Promise<{ root: { children: Array<{ id: string }> } }> };
113
+ selection: { get(): Promise<string[]> };
114
+ }): Promise<string[]> {
115
+ const selection = await ctx.selection.get();
116
+ if (selection.length > 0) {
117
+ return selection;
118
+ }
119
+
120
+ const snapshot = await ctx.document.snapshot();
121
+ const firstTopLevel = snapshot.root.children[0];
122
+ return firstTopLevel ? [firstTopLevel.id] : [];
123
+ }
124
+
125
+ function parseRows(input: string): DataRow[] {
126
+ const text = input.trim();
127
+ if (text.length === 0) {
128
+ throw new Error("Paste CSV or JSON before applying.");
129
+ }
130
+
131
+ if (text.startsWith("{") || text.startsWith("[")) {
132
+ return parseJsonRows(text);
133
+ }
134
+
135
+ return parseCsvRows(text);
136
+ }
137
+
138
+ function parseJsonRows(text: string): DataRow[] {
139
+ const parsed = JSON.parse(text) as unknown;
140
+ const values = Array.isArray(parsed) ? parsed : [parsed];
141
+ const rows = values.map((value) => coerceRow(value));
142
+ if (rows.length === 0) {
143
+ throw new Error("JSON input must contain at least one row.");
144
+ }
145
+ return rows;
146
+ }
147
+
148
+ function parseCsvRows(text: string): DataRow[] {
149
+ const lines = text
150
+ .split(/\r?\n/)
151
+ .map((line) => line.trim())
152
+ .filter((line) => line.length > 0);
153
+ if (lines.length < 2) {
154
+ throw new Error("CSV input needs a header row and at least one data row.");
155
+ }
156
+
157
+ const headers = parseCsvLine(lines[0]).map((header) => header.trim());
158
+ if (headers.length === 0 || headers.some((header) => header.length === 0)) {
159
+ throw new Error("CSV headers must not be empty.");
160
+ }
161
+
162
+ return lines.slice(1).map((line) => {
163
+ const values = parseCsvLine(line);
164
+ const row: DataRow = {};
165
+ headers.forEach((header, index) => {
166
+ row[header] = values[index] ?? "";
167
+ });
168
+ return row;
169
+ });
170
+ }
171
+
172
+ function parseCsvLine(line: string): string[] {
173
+ const values: string[] = [];
174
+ let current = "";
175
+ let quoted = false;
176
+
177
+ for (const char of line) {
178
+ if (char === '"') {
179
+ quoted = !quoted;
180
+ continue;
181
+ }
182
+ if (char === "," && !quoted) {
183
+ values.push(current.trim());
184
+ current = "";
185
+ continue;
186
+ }
187
+ current += char;
188
+ }
189
+
190
+ values.push(current.trim());
191
+ return values;
192
+ }
193
+
194
+ function coerceRow(value: unknown): DataRow {
195
+ if (!isRecord(value)) {
196
+ throw new Error("Each JSON row must be an object.");
197
+ }
198
+ const row: DataRow = {};
199
+ for (const [key, cell] of Object.entries(value)) {
200
+ row[key] = cell == null ? "" : String(cell);
201
+ }
202
+ return row;
203
+ }
204
+
205
+ function nameForRow(row: DataRow | undefined, index: number): string {
206
+ if (!row) {
207
+ return `Data Row ${index + 1}`;
208
+ }
209
+ const preferred = row.name ?? row.title ?? row.label;
210
+ if (preferred && preferred.trim().length > 0) {
211
+ return preferred.trim();
212
+ }
213
+ const summary = Object.values(row)
214
+ .map((value) => value.trim())
215
+ .filter((value) => value.length > 0)
216
+ .join(" / ");
217
+ return summary.length > 0 ? summary : `Data Row ${index + 1}`;
218
+ }
219
+
220
+ function isRecord(value: unknown): value is Record<string, unknown> {
221
+ return typeof value === "object" && value !== null && !Array.isArray(value);
222
+ }
@@ -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,180 @@
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>Data Filler Lite</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
+ textarea {
25
+ box-sizing: border-box;
26
+ min-height: 12rem;
27
+ width: 100%;
28
+ resize: vertical;
29
+ color: FieldText;
30
+ background: Field;
31
+ }
32
+
33
+ .actions {
34
+ display: flex;
35
+ flex-wrap: wrap;
36
+ gap: 0.5rem;
37
+ }
38
+
39
+ .status {
40
+ min-height: 1.5rem;
41
+ }
42
+
43
+ pre {
44
+ max-height: 8rem;
45
+ overflow: auto;
46
+ padding: 0.75rem;
47
+ color: CanvasText;
48
+ background: Canvas;
49
+ border: 1px solid ButtonBorder;
50
+ }
51
+ </style>
52
+ </head>
53
+ <body>
54
+ <main>
55
+ <header>
56
+ <h1>Data Filler Lite</h1>
57
+ <p>
58
+ Paste CSV or JSON, preview the parsed rows, then apply. Selection is
59
+ used first; empty selection falls back to the first top-level node for
60
+ demo verification.
61
+ </p>
62
+ </header>
63
+
64
+ <label>
65
+ Data
66
+ <textarea id="data-input" spellcheck="false">name,role
67
+ Hero Card,Landing
68
+ CTA Button,Marketing</textarea>
69
+ </label>
70
+
71
+ <div class="actions">
72
+ <button id="preview-button" type="button">Preview</button>
73
+ <button id="apply-button" type="button">Apply to nodes</button>
74
+ <button id="reject-button" type="button">Trigger unhandled rejection</button>
75
+ </div>
76
+
77
+ <p id="status" class="status" role="status">Waiting for host...</p>
78
+ <pre id="preview" aria-label="Preview"></pre>
79
+ </main>
80
+
81
+ <script>
82
+ const uiId = globalThis.__LOVISION_PLUGIN_UI_ID__;
83
+ const input = document.getElementById("data-input");
84
+ const status = document.getElementById("status");
85
+ const preview = document.getElementById("preview");
86
+
87
+ function post(type, payload) {
88
+ parent.postMessage(
89
+ {
90
+ type: "plugin-ui-message",
91
+ direction: "ui-to-main",
92
+ uiId,
93
+ message: { type, payload },
94
+ },
95
+ "*",
96
+ );
97
+ }
98
+
99
+ function parseRows(text) {
100
+ const trimmed = text.trim();
101
+ if (!trimmed) {
102
+ throw new Error("Paste CSV or JSON before applying.");
103
+ }
104
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
105
+ const parsed = JSON.parse(trimmed);
106
+ return Array.isArray(parsed) ? parsed : [parsed];
107
+ }
108
+ const lines = trimmed
109
+ .split(/\r?\n/)
110
+ .map((line) => line.trim())
111
+ .filter(Boolean);
112
+ const headers = splitCsvLine(lines[0] || "");
113
+ return lines.slice(1).map((line) => {
114
+ const values = splitCsvLine(line);
115
+ return Object.fromEntries(
116
+ headers.map((header, index) => [header, values[index] || ""]),
117
+ );
118
+ });
119
+ }
120
+
121
+ function splitCsvLine(line) {
122
+ const values = [];
123
+ let current = "";
124
+ let quoted = false;
125
+ for (const char of line) {
126
+ if (char === '"') {
127
+ quoted = !quoted;
128
+ } else if (char === "," && !quoted) {
129
+ values.push(current.trim());
130
+ current = "";
131
+ } else {
132
+ current += char;
133
+ }
134
+ }
135
+ values.push(current.trim());
136
+ return values;
137
+ }
138
+
139
+ document.getElementById("preview-button").addEventListener("click", () => {
140
+ try {
141
+ const rows = parseRows(input.value);
142
+ preview.textContent = JSON.stringify(rows, null, 2);
143
+ status.textContent = `Previewed ${rows.length} row(s).`;
144
+ } catch (error) {
145
+ status.textContent = error instanceof Error ? error.message : String(error);
146
+ }
147
+ });
148
+
149
+ document.getElementById("apply-button").addEventListener("click", () => {
150
+ status.textContent = "Applying...";
151
+ post("apply", { text: input.value });
152
+ });
153
+
154
+ document.getElementById("reject-button").addEventListener("click", () => {
155
+ Promise.reject(new Error("Data Filler Lite unhandled rejection demo"));
156
+ });
157
+
158
+ window.addEventListener("message", (event) => {
159
+ const envelope = event.data;
160
+ if (
161
+ !envelope ||
162
+ envelope.type !== "plugin-ui-message" ||
163
+ envelope.direction !== "main-to-ui" ||
164
+ envelope.uiId !== uiId
165
+ ) {
166
+ return;
167
+ }
168
+
169
+ const message = envelope.message;
170
+ if (message.type === "ready") {
171
+ status.textContent = message.payload.message;
172
+ } else if (message.type === "applied") {
173
+ status.textContent = `Applied ${message.payload.applied} node(s).`;
174
+ } else if (message.type === "error") {
175
+ status.textContent = message.payload.message;
176
+ }
177
+ });
178
+ </script>
179
+ </body>
180
+ </html>
@@ -0,0 +1,33 @@
1
+ # Design Lint Panel
2
+
3
+ Design Lint Panel is a panel-first Instinct UI plugin example generated by `plugin-dev create --template design-lint-panel`.
4
+
5
+ It uses a single `ui.html` resource with an inline browser bridge. The iframe UI never calls the Host Facade directly; it only posts `plugin-ui-message` envelopes to the main worker. The main worker owns all `document`, `viewport`, `nodes`, and `notify` calls.
6
+
7
+ ## Scripts
8
+
9
+ - `dev` - start the local HTTPS sideload server and print `manifestUrl`
10
+ - `build` - emit a formal-install bundle under `dist/`
11
+ - `validate` - run the same manifest + bundle validation path used by the host
12
+ - `lint` - run the default plugin ESLint setup
13
+
14
+ ## First Run
15
+
16
+ 1. Install dependencies with your preferred package manager.
17
+ 2. Run `dev`.
18
+ 3. Open the editor shell Plugin Development panel.
19
+ 4. Add by URL with the printed `manifestUrl`.
20
+ 5. Run `Open Design Lint Panel`.
21
+ 6. Click `Run lint` to scan the current page.
22
+ 7. Click `Focus first issue` to focus the first finding.
23
+ 8. Click `Fix default names` to prefix default or blank node names with `Lint/`.
24
+
25
+ ## Rules
26
+
27
+ - `unnamed-node` - non-root node name is blank or one of `Rectangle`, `Frame`, or `Text`
28
+ - `empty-text-node` - text node content is empty
29
+ - `non-positive-size` - non-root node width or height is not positive
30
+
31
+ ## Formal Install
32
+
33
+ Run `build`, then upload the generated `dist/*.bundle.json` as a Formal install. The installed plugin should appear in Installed and MainMenu, and the same panel UI should open 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,29 @@
1
+ {
2
+ "id": "__PLUGIN_ID__",
3
+ "name": {
4
+ "en": "Design Lint Panel",
5
+ "zh-CN": "Design Lint Panel"
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.text",
16
+ "notify",
17
+ "ui:panel",
18
+ "viewport:write"
19
+ ],
20
+ "commands": [
21
+ {
22
+ "id": "run",
23
+ "name": {
24
+ "en": "__COMMAND_NAME_EN__",
25
+ "zh-CN": "__COMMAND_NAME_ZH__"
26
+ }
27
+ }
28
+ ]
29
+ }
@@ -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
+ }