create-linkdesk-plugin 0.1.0 → 0.1.2
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/LICENSE +21 -0
- package/README.md +55 -39
- package/index.js +136 -111
- package/package.json +1 -1
- package/template/.vscode/settings.json +8 -5
- package/template/CHANGELOG.md +11 -0
- package/template/README.md +48 -0
- package/template/gitignore +8 -0
- package/template/i18n/en.json +7 -3
- package/template/package.json +20 -17
- package/template/plugin.json +41 -38
- package/template/resources/icon.svg +23 -0
- package/template/src/index.css +30 -23
- package/template/src/index.tsx +35 -29
- package/template/tsconfig.json +14 -14
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Encaron
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,39 +1,55 @@
|
|
|
1
|
-
# create-linkdesk-plugin
|
|
2
|
-
|
|
3
|
-
LinkDesk 插件脚手架——一行命令生成你的第一个插件项目(对标 `yo code`)。
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npm create linkdesk-plugin my-cool-plugin
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
不带名字则交互式询问:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm create linkdesk-plugin
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## 生成什么
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
├──
|
|
22
|
-
├── .
|
|
23
|
-
├──
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
>
|
|
39
|
-
|
|
1
|
+
# create-linkdesk-plugin
|
|
2
|
+
|
|
3
|
+
LinkDesk 插件脚手架——一行命令生成你的第一个插件项目(对标 `yo code`)。
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm create linkdesk-plugin my-cool-plugin
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
不带名字则交互式询问:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm create linkdesk-plugin
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 生成什么
|
|
16
|
+
|
|
17
|
+
生成物的形状**与官方插件一致**——README / CHANGELOG / resources / i18n 一个不少:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
my-cool-plugin/
|
|
21
|
+
├── plugin.json # 插件清单(JSONC:可注释/尾逗号,字段分节示范,VS Code $schema 校验)
|
|
22
|
+
├── package.json # scripts: dev / dev:real / build / publish / validate / lint
|
|
23
|
+
├── tsconfig.json # jsx: react-jsx + window.linkdesk.* 类型(@linkdesk/plugin-sdk)
|
|
24
|
+
├── .gitignore # node_modules / dist / *.linkdesk-plugin
|
|
25
|
+
├── README.md # 说明——市场「详情」页签的数据源 + 目录契约表
|
|
26
|
+
├── CHANGELOG.md # 更新日志——市场「更改日志」页签的数据源
|
|
27
|
+
├── .vscode/settings.json # plugin.json 按 jsonc 识别(注释不标红)
|
|
28
|
+
├── resources/
|
|
29
|
+
│ └── icon.svg # 图标占位图——换成你自己的
|
|
30
|
+
├── src/
|
|
31
|
+
│ ├── index.tsx # 视图组件 default 导出——壳以 { isActive, tabId?, sourceId? } 渲染
|
|
32
|
+
│ └── index.css # 样式示例——颜色 / 字号走 var(--xxx),间距走 4px 节奏
|
|
33
|
+
└── i18n/
|
|
34
|
+
└── en.json # 英文译文(key = 中文原文;不建 zh.json)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> **不预建空文件夹**(git 本来也不记录空目录)——「东西该放哪」写在生成的 `README.md` 的目录契约表里,
|
|
38
|
+
> 用文字说清比用空夹暗示更清楚。
|
|
39
|
+
|
|
40
|
+
## 命令
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd my-cool-plugin
|
|
44
|
+
npm install
|
|
45
|
+
|
|
46
|
+
npm run dev # 浏览器热重载预览(改代码即时生效)
|
|
47
|
+
npm run dev:real # 真机环——直写 {userData}/plugins/<id> + CDP reload(真 IPC / 串口 / LSP 类插件用)
|
|
48
|
+
npm run validate # 校验 plugin.json
|
|
49
|
+
npm run build # 产出 <pluginId>.linkdesk-plugin——可装进 LinkDesk / 发布
|
|
50
|
+
npm run lint # 门禁自检(硬编码颜色 / 字号 / 间距网格 / eslint 规则)
|
|
51
|
+
npm run publish # 一键发布(建 GitHub Release + 上传 + 更新 catalog)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
> 说明:插件作者工作流(dev 热预览 / build / 发布全链路)的完整文档见
|
|
55
|
+
> [00-第三方作者旅程](../../docs/02-Electron架构/E6_插件生态与发布/05-文档与发布/00-第三方作者旅程.md)。
|
package/index.js
CHANGED
|
@@ -1,111 +1,136 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* create-linkdesk-plugin——LinkDesk 插件脚手架 CLI(纯 Node,零依赖,对标 yo code)。
|
|
4
|
-
*
|
|
5
|
-
* 用法:
|
|
6
|
-
* npm create linkdesk-plugin my-cool-plugin # 直接给名字(kebab-case,非交互)
|
|
7
|
-
* npm create linkdesk-plugin # 不带参数 → 交互式询问插件名
|
|
8
|
-
*
|
|
9
|
-
* 行为:把同目录 template/ 复制到 <cwd>/<name>,把占位符替换成真实值,打印下一步提示。
|
|
10
|
-
* 占位符:{{pluginName}} {{displayName}} {{author}}(递归替换所有模板文件)。
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-linkdesk-plugin——LinkDesk 插件脚手架 CLI(纯 Node,零依赖,对标 yo code)。
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* npm create linkdesk-plugin my-cool-plugin # 直接给名字(kebab-case,非交互)
|
|
7
|
+
* npm create linkdesk-plugin # 不带参数 → 交互式询问插件名
|
|
8
|
+
*
|
|
9
|
+
* 行为:把同目录 template/ 复制到 <cwd>/<name>,把占位符替换成真实值,打印下一步提示。
|
|
10
|
+
* 占位符:{{pluginName}} {{displayName}} {{author}} {{date}}(递归替换所有模板文件)。
|
|
11
|
+
* {{date}} 注入 CHANGELOG.md 的初始段标题——格式必须是 `## v<版本>(YYYY-MM-DD)`,
|
|
12
|
+
* 那是市场「更改日志」页签切段的解析依据(见 docs/02-Electron架构/.../插件规范化层/02)。
|
|
13
|
+
*
|
|
14
|
+
* 生成产物契约:见 docs/02-Electron架构/E6_插件生态与发布/02-插件开发工具链/01-create-linkdesk-plugin脚手架.md。
|
|
15
|
+
*/
|
|
16
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import { createInterface } from "node:readline";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
const TEMPLATE_DIR = join(dirname(fileURLToPath(import.meta.url)), "template");
|
|
23
|
+
|
|
24
|
+
/** kebab-case——同时满足插件 id / viewsContainers key / npm 包名惯例(SAFE_PLUGIN_ID 的形状子集) */
|
|
25
|
+
const NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
26
|
+
|
|
27
|
+
/** 本地日期 YYYY-MM-DD(不用 toISOString——那是 UTC,跨时区会差一天) */
|
|
28
|
+
function todayLocal() {
|
|
29
|
+
const d = new Date();
|
|
30
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
31
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** my-cool-plugin → My Cool Plugin */
|
|
35
|
+
function toDisplayName(name) {
|
|
36
|
+
return name
|
|
37
|
+
.split("-")
|
|
38
|
+
.map((s) => (s ? s[0].toUpperCase() + s.slice(1) : s))
|
|
39
|
+
.join(" ");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 作者默认值 = git config user.name;读不到(无 git/无配置)→ "you"(作者生成后自改) */
|
|
43
|
+
function gitUserName() {
|
|
44
|
+
try {
|
|
45
|
+
const r = spawnSync("git", ["config", "user.name"], { encoding: "utf8", timeout: 3000 });
|
|
46
|
+
const v = (r.stdout || "").trim();
|
|
47
|
+
return v || "you";
|
|
48
|
+
} catch {
|
|
49
|
+
return "you";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 交互式单问——返回去除首尾空白的答案 */
|
|
54
|
+
function ask(question) {
|
|
55
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
rl.question(question, (answer) => {
|
|
58
|
+
rl.close();
|
|
59
|
+
resolve(answer.trim());
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function replacePlaceholders(file, values) {
|
|
65
|
+
let text = readFileSync(file, "utf8");
|
|
66
|
+
for (const [key, value] of Object.entries(values)) {
|
|
67
|
+
text = text.split(`{{${key}}}`).join(value);
|
|
68
|
+
}
|
|
69
|
+
writeFileSync(file, text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 递归替换目录内全部文件(模板全是文本文件,无需跳过二进制) */
|
|
73
|
+
function walkReplace(dir, values) {
|
|
74
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
75
|
+
const full = join(dir, entry.name);
|
|
76
|
+
if (entry.isDirectory()) walkReplace(full, values);
|
|
77
|
+
else replacePlaceholders(full, values);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function main() {
|
|
82
|
+
let name = (process.argv[2] || "").trim();
|
|
83
|
+
if (!name) {
|
|
84
|
+
name = await ask("插件名(kebab-case,如 my-cool-plugin): ");
|
|
85
|
+
}
|
|
86
|
+
name = name.trim();
|
|
87
|
+
if (!NAME_RE.test(name)) {
|
|
88
|
+
console.error(`✖ 插件名须为 kebab-case(小写字母/数字/连字符),收到:${JSON.stringify(name)}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const target = join(process.cwd(), name);
|
|
93
|
+
if (existsSync(target) && readdirSync(target).length > 0) {
|
|
94
|
+
console.error(`✖ ${name}/ 已存在且非空——换个名字,或清空后重跑`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
mkdirSync(target, { recursive: true });
|
|
99
|
+
cpSync(TEMPLATE_DIR, target, { recursive: true });
|
|
100
|
+
|
|
101
|
+
// 🔥 模板里存的是 `gitignore`(无点),生成时才改名为 `.gitignore`。
|
|
102
|
+
// 原因:**npm 打包恒定丢弃名为 `.gitignore` 的文件**(npm-packlist 排除表;实测
|
|
103
|
+
// `template/.gitignoreprobe` 与 `template/probe.txt` 都能进 tarball,唯独 `.gitignore` 不能)。
|
|
104
|
+
// 若模板里直接放 `.gitignore`,仓内生成(读模板目录)一切正常,**但发布后的
|
|
105
|
+
// `npm create linkdesk-plugin` 生成的工程会没有 .gitignore**——作者第一次 `git add .`
|
|
106
|
+
// 就把 node_modules/ 和 dist/ 全提交了。生成物契约见 check-scaffold.mjs 断言 8。
|
|
107
|
+
const tplGitignore = join(target, "gitignore");
|
|
108
|
+
if (existsSync(tplGitignore)) renameSync(tplGitignore, join(target, ".gitignore"));
|
|
109
|
+
|
|
110
|
+
const values = {
|
|
111
|
+
pluginName: name,
|
|
112
|
+
displayName: toDisplayName(name),
|
|
113
|
+
author: gitUserName(),
|
|
114
|
+
date: todayLocal(),
|
|
115
|
+
};
|
|
116
|
+
walkReplace(target, values);
|
|
117
|
+
|
|
118
|
+
console.log("");
|
|
119
|
+
console.log(`✔ ${name}/ 已创建`);
|
|
120
|
+
console.log("");
|
|
121
|
+
console.log(" 接下来:");
|
|
122
|
+
console.log(` cd ${name}`);
|
|
123
|
+
console.log(" npm install");
|
|
124
|
+
console.log(" npm run dev # 浏览器热重载预览(改代码即时生效)");
|
|
125
|
+
console.log(" npm run validate # 校验 plugin.json($schema / 字段 / i18n 文件)");
|
|
126
|
+
console.log(" npm run build # 打包出 <pluginId>.linkdesk-plugin,可装进 LinkDesk / 发布");
|
|
127
|
+
console.log("");
|
|
128
|
+
console.log(" 然后:先读 README.md —— 目录该放哪、三条纪律、怎么发布都在里面。");
|
|
129
|
+
console.log(" plugin.json 的 name / description / author 是你的身份信息,src/index.tsx 是插件本体。");
|
|
130
|
+
console.log(" 完整插件能力(侧栏视图 / 命令 / 设置 / 协议……)见 docs/03-插件制造/。");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
main().catch((err) => {
|
|
134
|
+
console.error(err);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|
package/package.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
{
|
|
2
|
+
// 让 VS Code 按 JSONC 解析 plugin.json —— 它允许 // 注释与尾逗号(对标 VS Code 自己的 package.json)。
|
|
3
|
+
// 没有这一行,VS Code 会按严格 JSON 解析,本模板 plugin.json 里的注释会被标成满屏红波浪线。
|
|
4
|
+
// 这只是编辑器提示,不影响运行:LinkDesk 与 SDK 本来就按 jsonc 解析。
|
|
5
|
+
"files.associations": {
|
|
6
|
+
"plugin.json": "jsonc"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# {{displayName}}
|
|
2
|
+
|
|
3
|
+
> 一句话:这个插件做什么。(写在最上面——LinkDesk 市场的「详情」页签显示的就是本文件)
|
|
4
|
+
|
|
5
|
+
<!-- 有场景封面时,把图放到 resources/cover.svg,再取消下面这行的注释:
|
|
6
|
+
 -->
|
|
7
|
+
|
|
8
|
+
## 怎么用
|
|
9
|
+
|
|
10
|
+
安装后在 LinkDesk 里怎么打开、点哪里、看到什么。写清楚「第一次用的人怎么走通」。
|
|
11
|
+
|
|
12
|
+
## 目录说明——东西该放哪
|
|
13
|
+
|
|
14
|
+
不必预建空文件夹(git 也不记录空目录)。**到需要时再建,位置按下表。**
|
|
15
|
+
|
|
16
|
+
| 路径 | 放什么 | 什么时候有 |
|
|
17
|
+
|:--|:--|:--|
|
|
18
|
+
| `plugin.json` | 插件清单 | **必有** |
|
|
19
|
+
| `README.md` | 说明——市场**详情**页签的数据源 | 强烈建议 |
|
|
20
|
+
| `CHANGELOG.md` | 更新日志——市场**更改日志**页签的数据源 | 强烈建议 |
|
|
21
|
+
| `resources/` | 资产:`icon.svg` / `cover.svg` / README 里引用的图 | 有图就有 |
|
|
22
|
+
| `i18n/` | `en.json`(key = 中文原文;**不建 zh.json**) | 有 UI 文案就有 |
|
|
23
|
+
| `themes/` · `languages/` · `snippets/` | 数据型插件的载荷 | 数据型才有 |
|
|
24
|
+
| `src/index.tsx` | 入口(`plugin.json` 的 `entry`) | 视图插件必有 |
|
|
25
|
+
| `src/views/` | 侧栏 / 面板视图组件(`contributes.views` 的 render 指向的文件) | 有视图时 |
|
|
26
|
+
| `src/components/` | 本插件内部复用的组件 | 需要时 |
|
|
27
|
+
| `src/services/` | 域逻辑 / IPC 封装 / 数据层 | 需要时 |
|
|
28
|
+
| `src/styles/` | **多份** CSS 时统一放这(单份且与入口同夹也可) | 需要时 |
|
|
29
|
+
| `src/__tests__/` | 单元测试(要测就自己 `npm i -D vitest`,脚手架不预装) | 需要时 |
|
|
30
|
+
|
|
31
|
+
> 🔴 **共享的东西不进这里**——跨插件复用的组件 / hook 走 `@linkdesk/ui`(壳提供的公共包),**不要在插件里再写一份**;只属于本插件的域逻辑才留本地。
|
|
32
|
+
> 🔴 **资产一律住 `resources/`,插件根不放散图**——能进安装包的是**被 README 引用过**或**被 `icon` / `marketIcon` 声明过**的文件,目录名本身没有魔法。
|
|
33
|
+
|
|
34
|
+
## 写这个插件的三条纪律
|
|
35
|
+
|
|
36
|
+
1. **颜色走主题变量**——CSS 里一律 `var(--xxx)`,**禁硬编码 hex**。理由:LinkDesk 支持整套主题替换,写死颜色 = 换主题后你的插件不跟着变。
|
|
37
|
+
2. **UI 文案走 `t()`**——`t("中文原文")`,英文放 `i18n/en.json`,**不建 `zh.json`**(中文 key 自带兜底)。**只加你真的用 `t()` 读过的 key**——没人读的 key 是死 key。代码标识符(`src/index.tsx` 这类)不是文案,别包进 `t()`。
|
|
38
|
+
3. **插件身份只来自 `plugin.json` 的声明字段**——需要什么能力就声明什么字段(`contributes` / `tabBehavior` / `icon` …),**不要靠目录名或文件位置让别人猜你的插件是什么**。
|
|
39
|
+
|
|
40
|
+
## 发布
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm run publish # 建 GitHub Release + 上传 .linkdesk-plugin + 更新 catalog
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
首次发布需要 GitHub token(跑一次会引导你填,存在本机)。只预览不动作:`npm run publish -- --dry-run`。
|
|
47
|
+
|
|
48
|
+
> 完整作者文档见 LinkDesk 仓库的 `docs/03-插件制造/`(API 契约 / 生命周期 / contributes / 分发 / UI 写法规约)。
|
package/template/i18n/en.json
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
{
|
|
2
|
-
"
|
|
3
|
-
|
|
1
|
+
{
|
|
2
|
+
"插件跑起来了 ✨": "Your plugin is running ✨",
|
|
3
|
+
"这是你的第一个 LinkDesk 插件。": "This is your first LinkDesk plugin.",
|
|
4
|
+
"是插件本体——改它,浏览器预览即时刷新。": "is the plugin itself — edit it and the browser preview refreshes live.",
|
|
5
|
+
"打包出分发文件,可装进 LinkDesk 或发布到市场。": "produces the distributable bundle — install it into LinkDesk or publish it to the marketplace.",
|
|
6
|
+
"目录该放哪、发布怎么做,都写在 README.md 里。": "Where things go and how to publish are all in README.md."
|
|
7
|
+
}
|
package/template/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{pluginName}}",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"description": "{{displayName}}——LinkDesk 插件(由 create-linkdesk-plugin 生成)",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"dev": "linkdesk-plugin-sdk dev",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "{{pluginName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "{{displayName}}——LinkDesk 插件(由 create-linkdesk-plugin 生成)",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "linkdesk-plugin-sdk dev",
|
|
9
|
+
"dev:real": "linkdesk-plugin-sdk dev --real",
|
|
10
|
+
"build": "linkdesk-plugin-sdk build",
|
|
11
|
+
"publish": "linkdesk-plugin-sdk publish",
|
|
12
|
+
"validate": "linkdesk-plugin-sdk validate",
|
|
13
|
+
"lint": "linkdesk-plugin-sdk lint"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@linkdesk/plugin-sdk": "^0.1.0",
|
|
17
|
+
"@types/react": "^18.3.12",
|
|
18
|
+
"typescript": "^5.6.3"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/template/plugin.json
CHANGED
|
@@ -1,38 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
3
|
-
// LinkDesk 插件清单 plugin.json(由 create-linkdesk-plugin 生成)
|
|
4
|
-
//
|
|
5
|
-
// · 本文件是 JSONC:可写注释、可尾逗号(LinkDesk 与 SDK 都按 jsonc 解析,对标 VS Code package.json)。
|
|
6
|
-
// · 完整字段以 plugin.schema.json 为准——VS Code 读 $schema 自动补全 + 校验。
|
|
7
|
-
// · $schema 指向本工程 node_modules/@linkdesk/plugin-sdk 随包拷贝(npm install 后生效)。
|
|
8
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
9
|
-
"$schema": "./node_modules/@linkdesk/plugin-sdk/schemas/plugin.schema.json",
|
|
10
|
-
|
|
11
|
-
// ── 插件身份 ──
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
// ──
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
{
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// LinkDesk 插件清单 plugin.json(由 create-linkdesk-plugin 生成)
|
|
4
|
+
//
|
|
5
|
+
// · 本文件是 JSONC:可写注释、可尾逗号(LinkDesk 与 SDK 都按 jsonc 解析,对标 VS Code package.json)。
|
|
6
|
+
// · 完整字段以 plugin.schema.json 为准——VS Code 读 $schema 自动补全 + 校验。
|
|
7
|
+
// · $schema 指向本工程 node_modules/@linkdesk/plugin-sdk 随包拷贝(npm install 后生效)。
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
9
|
+
"$schema": "./node_modules/@linkdesk/plugin-sdk/schemas/plugin.schema.json",
|
|
10
|
+
|
|
11
|
+
// ── 插件身份 ──
|
|
12
|
+
// 插件 ID 默认就是插件目录名({{pluginName}}),所以这里不写。仅当「目录名要改、但安装身份
|
|
13
|
+
// 不能变」时才取消下面这行的注释——它是覆盖值,不是必填项。
|
|
14
|
+
// "pluginId": "{{pluginName}}",
|
|
15
|
+
"name": "{{displayName}}", // 显示名——标签页 / 插件详情等 UI 出现处
|
|
16
|
+
"version": "0.1.0", // 语义化版本 x.y.z——市场更新比较靠它;+1 时务必同笔补 CHANGELOG.md 的新段
|
|
17
|
+
"description": "{{displayName}}——我的第一个 LinkDesk 插件", // 一句话描述(插件详情页展示)
|
|
18
|
+
"author": "{{author}}", // 作者名
|
|
19
|
+
"icon": "resources/icon.svg", // 图标——图标栏 / 标签页 / 市场里显示的就是它(resources/icon.svg 是占位图,换成你的)
|
|
20
|
+
|
|
21
|
+
// ── 入口(视图插件 = 此文件 default 导出一个 React 组件)──
|
|
22
|
+
"entry": "src/index.tsx",
|
|
23
|
+
|
|
24
|
+
// ── 出现位置:可作为主区标签页打开 ──
|
|
25
|
+
"appearsIn": { "tabBar": true },
|
|
26
|
+
"tabBehavior": { "singleton": true }, // 全局只开一个实例,避免重复标签
|
|
27
|
+
|
|
28
|
+
// ── 贡献点(contributes:全部可选,按需增删)──
|
|
29
|
+
"contributes": {
|
|
30
|
+
// 自带翻译:key=语言码, value=相对插件根的 JSON 文件。UI 文案用 t() 读这里;无需 zh.json——中文 key 原文自带兜底。
|
|
31
|
+
"i18n": { "en": "i18n/en.json" },
|
|
32
|
+
// ── 需要「侧栏 / 底部面板 / 辅助侧栏」分区视图时:取消注释,在 src/views/ 放对应组件,
|
|
33
|
+
// 容器 key 与 view id 用你的 pluginId 做前缀防撞(对标 plugins/panel-demo)──
|
|
34
|
+
// "viewsContainers": { "{{pluginName}}-sidebar": { "title": "{{displayName}}", "location": "sidebar" } },
|
|
35
|
+
// "views": {
|
|
36
|
+
// "{{pluginName}}-sidebar": [
|
|
37
|
+
// { "id": "main", "title": "{{displayName}}", "render": "src/views/MainView.tsx", "order": 0 }
|
|
38
|
+
// ]
|
|
39
|
+
// }
|
|
40
|
+
},
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
|
|
2
|
+
<!-- ⚠️ 占位图——请替换成你自己插件的图标(同名同位置覆盖即可,plugin.json 的 icon 已指向本文件)。
|
|
3
|
+
写法与官方插件一致:界面里以 <img src="linkdesk://<id>/resources/icon.svg"> 显示,
|
|
4
|
+
是独立文档 ⇒ 不能用 var(--xxx)(解析不到),颜色必须自含实色。 -->
|
|
5
|
+
<defs>
|
|
6
|
+
<linearGradient id="phi_bg" x1="0" y1="0" x2="1" y2="1">
|
|
7
|
+
<stop offset="0" stop-color="#64748B"/>
|
|
8
|
+
<stop offset="1" stop-color="#334155"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<linearGradient id="phi_glass" x1="0" y1="0" x2="0" y2="1">
|
|
11
|
+
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.28"/>
|
|
12
|
+
<stop offset="1" stop-color="#FFFFFF" stop-opacity="0.02"/>
|
|
13
|
+
</linearGradient>
|
|
14
|
+
</defs>
|
|
15
|
+
<!-- 中性灰品牌块:刻意不设计成有含义的图形——它的意思是「这里还没有图」 -->
|
|
16
|
+
<rect x="0" y="0" width="48" height="48" rx="10" fill="url(#phi_bg)"/>
|
|
17
|
+
<rect x="0" y="0" width="48" height="48" rx="10" fill="url(#phi_glass)"/>
|
|
18
|
+
<!-- 虚线框 + 加号 = 「把你的图放这儿」 -->
|
|
19
|
+
<rect x="13" y="13" width="22" height="22" rx="5" fill="none"
|
|
20
|
+
stroke="#FFFFFF" stroke-opacity="0.9" stroke-width="2"
|
|
21
|
+
stroke-dasharray="4 3" stroke-linecap="round"/>
|
|
22
|
+
<path d="M24 19.5v9M19.5 24h9" stroke="#FFFFFF" stroke-width="2.4" stroke-linecap="round"/>
|
|
23
|
+
</svg>
|
package/template/src/index.css
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
/* {{displayName}}
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
/* {{displayName}} 样式示例(`npm run lint` 会照着下面三条查)。
|
|
2
|
+
三条纪律:
|
|
3
|
+
· 颜色一律 var(--xxx),禁硬编码 hex——用户换主题时你的插件要跟着变;
|
|
4
|
+
· 字号一律 var(--font-size-*)——用户调全局字号时它才跟着缩放,裸 px 不会;
|
|
5
|
+
· padding / margin / gap 走 4px 节奏(4 的倍数)。 */
|
|
6
|
+
|
|
7
|
+
.starter {
|
|
8
|
+
padding: 24px;
|
|
9
|
+
font-family: inherit;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.starter__title {
|
|
13
|
+
margin: 0 0 8px;
|
|
14
|
+
color: var(--text);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.starter__text {
|
|
18
|
+
margin: 0 0 4px;
|
|
19
|
+
color: var(--text-muted);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.starter__hint {
|
|
23
|
+
margin: 0;
|
|
24
|
+
color: var(--text-muted);
|
|
25
|
+
font-size: var(--font-size-xs);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.starter__hint code {
|
|
29
|
+
font-family: var(--font-mono, monospace);
|
|
30
|
+
}
|
package/template/src/index.tsx
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{displayName}}——LinkDesk 插件主视图(由 create-linkdesk-plugin 生成)。
|
|
3
|
-
*
|
|
4
|
-
* 视图插件契约(E5.8,见 docs/03-插件制造/01-API契约.md):壳以 { isActive, tabId?, sourceId? }
|
|
5
|
-
* 渲染本文件 default 导出的组件:
|
|
6
|
-
* - isActive 本标签当前是否聚焦。keep-alive 下非聚焦标签仍在渲染,isActive 只用于
|
|
7
|
-
* gate「聚焦才跑」的副作用(如自动保存),切勿用它整块 blank 掉内容。
|
|
8
|
-
* - tabId 本标签页 id。
|
|
9
|
-
* - sourceId 上下文数据(文件路径 / 数据源等),编辑器类插件用它定位内容。
|
|
10
|
-
*
|
|
11
|
-
* 样式:LinkDesk 主题色一律走 CSS 变量 var(--xxx)(见 index.css 示例),禁硬编码 hex。
|
|
12
|
-
*
|
|
13
|
-
* 壳已 external react/react-dom/react-i18next/i18next——构建不会打进包,插件工程无需 npm i 它们。
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import "
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
/**
|
|
2
|
+
* {{displayName}}——LinkDesk 插件主视图(由 create-linkdesk-plugin 生成)。
|
|
3
|
+
*
|
|
4
|
+
* 视图插件契约(E5.8,见 docs/03-插件制造/01-API契约.md):壳以 { isActive, tabId?, sourceId? }
|
|
5
|
+
* 渲染本文件 default 导出的组件:
|
|
6
|
+
* - isActive 本标签当前是否聚焦。keep-alive 下非聚焦标签仍在渲染,isActive 只用于
|
|
7
|
+
* gate「聚焦才跑」的副作用(如自动保存),切勿用它整块 blank 掉内容。
|
|
8
|
+
* - tabId 本标签页 id。
|
|
9
|
+
* - sourceId 上下文数据(文件路径 / 数据源等),编辑器类插件用它定位内容。
|
|
10
|
+
*
|
|
11
|
+
* 样式:LinkDesk 主题色一律走 CSS 变量 var(--xxx)(见 index.css 示例),禁硬编码 hex。
|
|
12
|
+
* 文案:用 t() 读——key 就是中文原文,英文译文放 i18n/en.json(见 05-UI写法规约.md)。
|
|
13
|
+
* 壳已 external react/react-dom/react-i18next/i18next——构建不会打进包,插件工程无需 npm i 它们。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useTranslation } from "react-i18next";
|
|
17
|
+
import "./index.css";
|
|
18
|
+
|
|
19
|
+
export default function HelloPlugin(_props: { isActive?: boolean; tabId?: string; sourceId?: string }) {
|
|
20
|
+
const { t } = useTranslation();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="starter">
|
|
24
|
+
<h2 className="starter__title">{t("插件跑起来了 ✨")}</h2>
|
|
25
|
+
<p className="starter__text">{t("这是你的第一个 LinkDesk 插件。")}</p>
|
|
26
|
+
<p className="starter__hint">
|
|
27
|
+
<code>src/index.tsx</code> {t("是插件本体——改它,浏览器预览即时刷新。")}
|
|
28
|
+
</p>
|
|
29
|
+
<p className="starter__hint">
|
|
30
|
+
<code>npm run build</code> {t("打包出分发文件,可装进 LinkDesk 或发布到市场。")}
|
|
31
|
+
</p>
|
|
32
|
+
<p className="starter__hint">{t("目录该放哪、发布怎么做,都写在 README.md 里。")}</p>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
package/template/tsconfig.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"noEmit": true,
|
|
8
|
-
"jsx": "react-jsx",
|
|
9
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
10
|
-
"types": ["@linkdesk/plugin-sdk"],
|
|
11
|
-
"skipLibCheck": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src"]
|
|
14
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
10
|
+
"types": ["@linkdesk/plugin-sdk"],
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|