create-linkdesk-plugin 0.1.12 → 0.1.14

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/README.md CHANGED
@@ -30,7 +30,7 @@ my-cool-plugin/
30
30
  ├── .github/workflows/ci.yml # CI that runs `npm run verify` on every push
31
31
  ├── scripts/ci-verify.mjs # the strict tier CI runs (lint + tests + declaration self-checks)
32
32
  ├── vitest.config.ts # test config (jsdom + globals; @linkdesk/ui is inlined so its CSS import resolves)
33
- ├── vitest.setup.ts # test runtime ground — mocks window.linkdesk (no Electron preload under vitest)
33
+ ├── vitest.setup.ts # one-line pointer to the shared test ground in @linkdesk/plugin-sdk (the window.linkdesk mock)
34
34
  ├── .vscode/settings.json # plugin.json is treated as jsonc (comments do not light up red)
35
35
  ├── resources/
36
36
  │ └── icon.svg # placeholder icon — replace it with your own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-linkdesk-plugin",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "LinkDesk 插件脚手架——`npm create linkdesk-plugin@latest my-cool-plugin` 一行生成你的第一个插件项目(对标 yo code)。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,6 +47,15 @@ npm run publish # ⑧ publish to your own GitHub repo (first step of listing)
47
47
  - **This project is its own git repo** (the scaffold ran `git init -b main` plus one initial commit). `publish` uses `origin` to create the Release ⇒ pushing to GitHub is just two commands: `git remote add origin <your repo>` + `git push -u origin main`.
48
48
  - **Keep the version numbers in step**: `plugin.json`'s `version` and `package.json`'s `version` **must match**; and every bump needs a matching `## v<new version>(YYYY-MM-DD)` section in `CHANGELOG.md`, or the plugin's detail page will show "no changelog provided for this version".
49
49
 
50
+ ## 6. Tests in this repo
51
+
52
+ - **The toolchain is preinstalled** (`vitest` / `jsdom` / `@testing-library/react` are already declared) — write a `*.test.ts` file and run `npm run test`.
53
+ - **The shared test ground comes from the SDK**: the minimal `window.linkdesk` mock lives in `@linkdesk/plugin-sdk/vitest-setup`, and this repo's `vitest.setup.ts` is a **one-line pointer** to it. **Do not paste a second copy in here** — a copied mock drifts from the shared one and nothing would notice.
54
+ - **Pure logic should be tested**: a `src/**/*.ts` unit that touches neither React nor `window.linkdesk` should get a `src/__tests__/<same name>.test.ts`. Views and hooks are worth testing **when it pays off** — nothing measures that today.
55
+ - **Plugin-specific stubs stay in your own test files**: if your code calls something the shared mock does not cover (say `window.linkdesk.serial`), set that stub up with `vi.fn()` in the test. Do not ask the shared mock to grow a branch for your plugin.
56
+ - **Declarative plugins** (pure JSON / themes / language packs) have **no testable units** — their gate is the structure and declaration checks in `npm run verify`. "It has no tests" is not a defect there.
57
+ - **Older projects can migrate whenever they like**: replacing the full mock body with the one-line pointer plus one `npm install` is the whole move (upside: mock upgrades then ride along with the SDK).
58
+
50
59
  ---
51
60
 
52
61
  > Author: {{author}} | Generated: {{date}}
@@ -18,7 +18,7 @@ You do not need to pre-create empty folders (git does not track them). **Create
18
18
  | `plugin.json` | The plugin manifest | **Always** |
19
19
  | `README.md` | Description — the data source for the marketplace **Details** tab | Strongly recommended |
20
20
  | `CHANGELOG.md` | Release notes — the data source for the marketplace **Changelog** tab | Strongly recommended |
21
- | `resources/` | Assets: `icon.svg` / `cover.svg` / images referenced from the README | Once you have images |
21
+ | `resources/` | Assets — **three preset placeholders, each with a job comment inside**: `icon.svg` (small in-app icon) / `icon-bar.svg` (Icon Bar single-colour glyph) / `cover.svg` (README cover), plus any images referenced from the README | Always (replace the placeholders) |
22
22
  | `i18n/` | `en.json` (key = the source string; **do not create `zh.json`**) | Once you have UI text |
23
23
  | `themes/` · `languages/` · `snippets/` | Payloads for data-only plugins | Data-only plugins |
24
24
  | `src/index.tsx` | Entry (the `entry` in `plugin.json`) | Always for view plugins |
@@ -26,7 +26,7 @@ You do not need to pre-create empty folders (git does not track them). **Create
26
26
  | `src/components/` | Components reused inside this plugin | When needed |
27
27
  | `src/services/` | Domain logic / IPC wrappers / data layer | When needed |
28
28
  | `src/styles/` | **Multiple** CSS files — keep them together here (a single file next to the entry is fine too) | When needed |
29
- | `src/__tests__/` | Unit tests (run `npm i -D vitest` yourself if you want them — the scaffold does not preinstall test tooling) | When needed |
29
+ | `src/__tests__/` | Unit tests — **test tooling is preinstalled** (`vitest` / `jsdom` / `@testing-library/react` are already in `devDependencies`); the `window.linkdesk` mock is **not** in this repo — `vitest.setup.ts` is a one-line pointer to the shared ground in `@linkdesk/plugin-sdk`, so just write tests and run `npm run test` | When needed |
30
30
 
31
31
  > 🔴 **Shared things do not belong here** — components/hooks reused across plugins come from `@linkdesk/ui` (the public package the shell provides; it is already declared in `package.json` as `"latest"`, which resolves to the shell's current version line when you install — pin it to a specific shell version if you need a floor). The shell supplies that one instance at runtime, so **do not import its css** and **do not write a second copy inside your plugin**. Only logic that belongs to this plugin stays local.
32
32
  > 🔴 **Assets always live in `resources/` — no loose images in the plugin root.** What gets into the install package is what is **referenced by the README** or **declared by `icon` / `marketIcon`**; the directory name itself has no magic.
@@ -37,6 +37,8 @@ You do not need to pre-create empty folders (git does not track them). **Create
37
37
  2. **UI text goes through `t()`** — `t("source string")`, with English in `i18n/en.json` and **no `zh.json`** (the source string is the key and is its own fallback). **Only add keys you actually read with `t()`** — an unread key is a dead key. Code identifiers (`src/index.tsx` and friends) are not copy — do not wrap them in `t()`.
38
38
  3. **Plugin identity comes only from declared fields in `plugin.json`** — declare whatever capability you need (`contributes` / `tabBehavior` / `icon` …). **Never make other people guess what your plugin is from a directory name or file location.**
39
39
 
40
+ **Dev preview note:** the dev host (`npm run dev`) gives your view a **fixed-height root container** (`#ld-root`) — write `height: 100%` on your root element with confidence and it fills the preview. You never need ResizeObserver self-healing or a "just in case" fallback layer; if your panel still renders collapsed or transparent in the preview, report it — don't code around it.
41
+
40
42
  ## Publishing
41
43
 
42
44
  ```bash
@@ -22,7 +22,13 @@
22
22
  "minAppVersion": "0.2.13",
23
23
  "description": "{{displayName}}——我的第一个 LinkDesk 插件", // 一句话描述(插件详情页展示)
24
24
  "author": "{{author}}", // 作者名
25
- "icon": "resources/icon.svg", // 图标——图标栏 / 标签页 / 市场里显示的就是它(resources/icon.svg 是占位图,换成你的)
25
+ // ── 图标——三图模型(完整规则见作者面 06 号《plugin.json 规范》§marketIcon)──
26
+ // icon = 界面小图标:标签页 [+] 菜单 / 欢迎页等处(要进 42px 图标栏需再声明 appearsIn.iconBar;
27
+ // ⚠️ 图标栏会对图强制单色染色——进图标栏的图要画单色线稿剪影,resources/icon-bar.svg 是预置占位)。
28
+ // marketIcon = 市场彩色身份图:市场列表行与详情页头部用(可选;缺省回落 icon)。
29
+ // cover = README 场景封面:只住 README 说明区,不是 manifest 字段(resources/cover.svg 已预置)。
30
+ "icon": "resources/icon.svg", // 占位图,换成你的
31
+ // "marketIcon": "resources/icon.svg", // 市场彩色身份图(可选;缺省回落 icon)——市场要彩色展示时取消注释
26
32
 
27
33
  // ── 入口(视图插件 = 此文件 default 导出一个 React 组件)──
28
34
  "entry": "src/index.tsx",
@@ -33,6 +39,16 @@
33
39
 
34
40
  // ── 贡献点(contributes:全部可选,按需增删)──
35
41
  "contributes": {
42
+ // ── 贡献点速查(每键一行,只指路不展开——完整语法与示例见作者面 03 号《contributes 规范》,落位见 17 号《区域地图》)──
43
+ // commands —— 注册命令:命令面板 / 右键菜单 / 快捷键 / 标题栏按钮的公共落脚点(id 用 "<pluginId>." 前缀防撞)
44
+ // keybindings —— 给命令绑快捷键:keys + command + when(上下文旗子,条件满足才触发)
45
+ // menus —— 菜单位声明(右键菜单走声明式,禁止手写右键菜单)
46
+ // configuration —— 声明设置项:设置页自动渲染一个分组(另见 20 号《我的插件加一条配置项》)
47
+ // titleBar —— 标题栏按钮:right[] / left[],icon + command(+ when)
48
+ // viewsContainers / views —— 侧栏 / 底部面板的容器与视图(下面注释里有现成示例;容器 API 详见 08 号)
49
+ // themes / languages —— 主题包 / 语言包(数据型插件的载荷)
50
+ // floatingPanel —— 把你已有的某个视图声明成「可在悬浮面板打开」
51
+ // ⚠️ statusBar 不是 contributes 的键——它是 plugin.json **顶层**的 statusBar[] 数组(写进 contributes 无效,见 17 号 ⑦ 状态栏)
36
52
  // 自带翻译:key=语言码, value=相对插件根的 JSON 文件。UI 文案用 t() 读这里;无需 zh.json——中文 key 原文自带兜底。
37
53
  "i18n": { "en": "i18n/en.json" },
38
54
  // ── 需要「侧栏 / 底部面板 / 辅助侧栏」分区视图时:取消注释,在 src/views/ 放对应组件,
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="640" height="320" viewBox="0 0 640 320">
2
+ <!-- README 场景封面占位——替换成你插件的真实截图/场景图(同名覆盖即可),
3
+ 在 README.md 顶部引用(模板 README 里已有注释好的引用行,取消注释即用):
4
+ ![<插件名> cover](resources/cover.svg)
5
+ ⚠️ 这张图只住 README(市场详情页「详情」页签的说明区)——它不是市场列表/详情头的身份图,
6
+ 那个位置是 marketIcon(见 plugin.json 注释 / 作者面 06 号 §marketIcon);
7
+ README 放图的完整规则见作者面 12 号《README 说明区媒体契约》。 -->
8
+ <rect x="0" y="0" width="640" height="320" rx="12" fill="#1E293B"/>
9
+ <rect x="170" y="110" width="300" height="100" rx="8" fill="none" stroke="#475569" stroke-width="2" stroke-dasharray="7 5"/>
10
+ <text x="320" y="166" fill="#64748B" font-family="sans-serif" font-size="17" text-anchor="middle">cover.svg — your scene here</text>
11
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2
+ <!-- 图标栏 Type-1 单色线稿剪影占位——替换成你自己插件的剪影(同名覆盖即可)。
3
+ 用法:plugin.json 里 "appearsIn": { "iconBar": "top" } + "icon": "resources/icon-bar.svg"。
4
+ ⚠️ 图标栏会对图强制单色染色——别放彩色图(彩色进来会糊成一团);
5
+ 这是独立 SVG 文档 ⇒ 不能用 var(--xxx)(解析不到),颜色必须自含实色。
6
+ 市场里要彩色身份图不归这张管——那是 marketIcon 的职责(见 plugin.json 注释 / 作者面 06 号 §marketIcon)。 -->
7
+ <rect x="4.5" y="4.5" width="15" height="15" rx="3.5" stroke="#9CA3AF" stroke-width="1.8" stroke-dasharray="3.5 2.5" stroke-linecap="round"/>
8
+ <path d="M12 9v6M9 12h6" stroke="#9CA3AF" stroke-width="1.8" stroke-linecap="round"/>
9
+ </svg>
@@ -1,7 +1,10 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
2
2
  <!-- ⚠️ 占位图——请替换成你自己插件的图标(同名同位置覆盖即可,plugin.json 的 icon 已指向本文件)。
3
3
  写法与官方插件一致:界面里以 <img src="linkdesk://<id>/resources/icon.svg"> 显示,
4
- 是独立文档 ⇒ 不能用 var(--xxx)(解析不到),颜色必须自含实色。 -->
4
+ 是独立文档 ⇒ 不能用 var(--xxx)(解析不到),颜色必须自含实色。
5
+ 三图模型——本文件 = 界面小图标(要进图标栏需 appearsIn.iconBar,图标栏会强制单色染色,
6
+ 进图标栏请换 resources/icon-bar.svg 那样的单色剪影);市场彩色身份图 = marketIcon;
7
+ README 场景封面 = resources/cover.svg。各管各的,详见作者面 06 号《plugin.json 规范》§marketIcon。 -->
5
8
  <defs>
6
9
  <linearGradient id="phi_bg" x1="0" y1="0" x2="1" y2="1">
7
10
  <stop offset="0" stop-color="#64748B"/>
@@ -1,96 +1,3 @@
1
- /**
2
- * vitest setup——mock window.linkdesk API。
3
- * 测试跑在 Node.js/jsdom,没有 Electron preload 注入的 window.linkdesk。
4
- * 迁移到 linkdesk.* API 后,插件代码直接依赖它——测试环境需提供最小 mock。
5
- *
6
- * 🔴 **本文件是壳仓 `vitest.setup.ts` 的逐字副本**(除本头注五条)。
7
- * 它不是「配置」,是插件测试的**运行时地基**——下半部的六个命名空间与 `__ldkConfigStore`
8
- * 少了任何一个,凡碰 `window.linkdesk` 的测试都会报错、或更糟:静默走错分支。
9
- * 改壳仓那份时把这里一起改(两处同源)。「由 @linkdesk/plugin-sdk 提供共享版本、
10
- * 本文件改成一行 re-export」是可预见的收敛方向——那时这五条注记一并删掉。
11
- */
12
-
13
- // path 纯函数——直接实现,不走 IPC
14
- const pathMock = {
15
- normalize: (p: string) => p.replace(/\\/g, "/"),
16
- join: (...parts: string[]) =>
17
- parts.map((p) => String(p).replace(/\\/g, "/")).join("/").replace(/\/+/g, "/"),
18
- basename: (p: string) => {
19
- const s = p.replace(/\\/g, "/").split("/");
20
- return s[s.length - 1] || "";
21
- },
22
- dirname: (p: string) => {
23
- const s = p.replace(/\\/g, "/").split("/");
24
- s.pop();
25
- return s.join("/") || ".";
26
- },
27
- extname: (p: string) => {
28
- const b = p.replace(/\\/g, "/").split("/").pop() || "";
29
- const i = b.lastIndexOf(".");
30
- return i > 0 ? b.slice(i) : "";
31
- },
32
- };
33
-
34
- // 测试全局窄类型 cast——替代 (globalThis as any)(__ldkConfigStore 由本文件声明、测试文件消费)
35
- type TestGlobal = { window?: Window; __ldkConfigStore?: Map<string, unknown> };
36
- const _g = globalThis as TestGlobal;
37
-
38
- // configuration——默认返回 null,测试中按需 mock。
39
- // __ldkConfigStore 暴露给测试——测试可直接设置值控制 get() 返回。
40
- const _configStore = (_g.__ldkConfigStore = new Map<string, unknown>());
41
- const configurationMock = {
42
- get: async (key: string) => _configStore.get(key) ?? null,
43
- set: async (key: string, v: unknown) => { _configStore.set(key, v); },
44
- onChange: (_key: string, _cb: (v: unknown) => void) => {
45
- return () => {}; // no-op unsubscribe
46
- },
47
- };
48
-
49
- // workspace——默认返回空工作区
50
- const workspaceMock = {
51
- getFolders: async () => [] as { uri: string; name: string }[],
52
- getActive: async () => undefined as string | undefined,
53
- };
54
-
55
- // filesystem——可替换的最小 stub。测试可覆盖 lk.filesystem.xxx = vi.fn() 按需定制
56
- const filesystemMock = {
57
- readTextFile: async (_p: string) => "",
58
- writeTextFile: async (_p: string, _d: string) => {},
59
- readBinaryFile: async (_p: string) => new Uint8Array(),
60
- writeBinaryFile: async (_p: string, _d: Uint8Array) => {},
61
- listDir: async (_p: string) => [] as { path: string; name: string; isDirectory: boolean; isFile: boolean }[],
62
- exists: async (_p: string) => false,
63
- mkdir: async (_p: string) => {},
64
- copy: async (_src: string, _dest: string) => {},
65
- remove: async (_p: string) => {},
66
- watch: async (_dirPath: string, _onEvent: (e: unknown) => void) => {
67
- return () => {}; // unsubscribe
68
- },
69
- };
70
-
71
- // tabs——最小 stub
72
- const tabsMock = {
73
- create: async (_type: string, _opts?: Record<string, unknown>) => "tab-1",
74
- openOrFocus: async (_type: string, _opts?: Record<string, unknown>) => "tab-1",
75
- focus: async (_tabId: string) => {},
76
- close: async (_tabId: string) => {},
77
- focusBySourceId: async (_sourceId: string) => {},
78
- updateLabelBySourceId: async (_sourceId: string, _label: string) => {},
79
- closeBySourceId: async (_sourceId: string) => {},
80
- };
81
-
82
- // 最小 mock——故意不满足 LinkDeskAPI 全契约(测试按需覆盖),经 linkdesk?: object 窄口赋值
83
- _g.window = _g.window ?? ({} as Window);
84
- (_g.window as Window & { linkdesk?: object }).linkdesk = {
85
- path: pathMock,
86
- configuration: configurationMock,
87
- config: configurationMock,
88
- workspace: workspaceMock,
89
- filesystem: filesystemMock,
90
- tabs: tabsMock,
91
- // event stubs
92
- events: {
93
- on: () => () => {},
94
- emit: () => {},
95
- },
96
- };
1
+ // Shared test ground: the minimal `window.linkdesk` mock ships with the SDK (`@linkdesk/plugin-sdk/vitest-setup`).
2
+ // One line on purpose — plugin-specific stubs belong in your own test files (`vi.fn()`), not in here.
3
+ import "@linkdesk/plugin-sdk/vitest-setup";