@linkdesk/plugin-sdk 0.1.10 → 0.1.12
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 +63 -0
- package/dev-host/dev-main.tsx +47 -47
- package/dev-host/index.html +50 -50
- package/dev-host/linkdesk-mock.generated.ts +3 -0
- package/dev-host/mock.ts +72 -72
- package/dist/bin.js +16 -16
- package/package.json +68 -68
- package/schemas/icon-theme.schema.json +97 -97
- package/schemas/plugin.schema.json +682 -680
package/README.md
CHANGED
|
@@ -11,6 +11,69 @@ LinkDesk 插件作者 SDK——对标 `@types/vscode`:装一个包,拿到 **
|
|
|
11
11
|
npm install -D @linkdesk/plugin-sdk
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
## API 速查表
|
|
15
|
+
|
|
16
|
+
<!-- BEGIN API-CHEATSHEET -->
|
|
17
|
+
|
|
18
|
+
> 自动生成,**勿手改**——由 `scripts/generate-api-cheatsheet.mjs` 从 `@linkdesk/contracts` 的 `linkdesk.d.ts` 现读产出,
|
|
19
|
+
> `npm run check` 机械盯漂。完整签名与逐方法说明见 `linkdesk.d.ts` 本体(IDE 里可直接跳转)。
|
|
20
|
+
|
|
21
|
+
**15 个域接口 → 46 个命名空间 / 243 个方法**,全部经 `window.linkdesk.<命名空间>.<方法>` 调用。(另含 1 个废弃别名 `config`,方法不重复计入)
|
|
22
|
+
|
|
23
|
+
| 命名空间 | 方法数 | 方法 | 说明 |
|
|
24
|
+
|:--|:--:|:--|:--|
|
|
25
|
+
| `commands` | 5 | `execute` `executeCommand` `registerCommand` `unregisterCommands` `getCommands` | 命令——对标 VS Code vscode.commands |
|
|
26
|
+
| `configuration` | 15 | `get` `set` `getSchema` `onChange` `getConfigurationContributions` `inspectConfiguration` `getUserSettings` `onDidChangeConfiguration` `onPluginLifecycleChange` `consumeSettingsGroup` `onRequestSettingsGroup` `consumeScrollToSetting` `onRequestScrollToSetting` `consumeOpenKeybindings` `onRequestOpenKeybindings` | 配置—新名——对标 VS Code vscode.workspace.getConfiguration |
|
|
27
|
+
| `config` | 15 | (废弃别名 → `configuration`) | @deprecated E3j #75——向后兼容别名,新代码用 configuration |
|
|
28
|
+
| `theme` | 11 | `getCurrent` `getAvailable` `apply` `listRecipes` `getActive` `getEffectiveTokens` `setRecipe` `setColorway` `resetAppearance` `resetMix` `getBaselineSeeds` | —— |
|
|
29
|
+
| `language` | 5 | `getCurrent` `getAvailable` `set` `getInitial` `onChange` | —— |
|
|
30
|
+
| `appearance` | 2 | `importImage` `revealStorage` | E5.8#50.11:外观资产——本地选图拷贝入库(受控来源——用户任选路径不能 file:// 直读) |
|
|
31
|
+
| `tabs` | 8 | `create` `openOrFocus` `focus` `close` `focusBySourceId` `updateLabelBySourceId` `closeBySourceId` `onDidChangeActiveTab` | —— |
|
|
32
|
+
| `keybindings` | 12 | `getKeybindings` `getConflicts` `registerKeybinding` `saveUserKeybindings` `removeKeybindingForCommand` `resetKeybindingToDefault` `findKeybindingForCommand` `setKeybindingCaptureActive` `keyboardEventToKeyString` `onChange` `syncToMainProcess`° `onForwardedEvent`° | —— |
|
|
33
|
+
| `notifications` | 1 | `show` | 通知——插件弹通知(E6#72:唯一通知面 = 铃铛宽通知面板,右下窄卡链路已整删),对标 VS Code vscod… |
|
|
34
|
+
| `menu` | 2 | `registerItems` `getItems` | E5#69:菜单——插件声明式读写 |
|
|
35
|
+
| `contextKey` | 1 | `set` | E5#70:ContextKey——插件 SET 状态供壳 when 子句读 |
|
|
36
|
+
| `dialog` | 5 | `confirm` `alert` `open` `openFile` `confirmContent` | E5#67:弹窗——确认/提示/文件选择 |
|
|
37
|
+
| `quickPick` | 1 | `show` | E5.7#63:插件 quickPick 选择器——池内本地桥(零 IPC,QuickPickHost 渲染) |
|
|
38
|
+
| `quickPickHost` | 6 | `registerHost` `onShow` `select` `highlight` `close` `itemAction` | E5.7#63:QuickPick 宿主渲染桥——池 QuickPickHost 消费(壳 preload 无此面) |
|
|
39
|
+
| `dialogHost` | 4 | `onShow` `current` `confirm` `cancel` | E5.7#17:Dialog 哑渲染订阅——池 DialogHost 消费(壳 preload 无此面) |
|
|
40
|
+
| `floatingPanelHost` | 2 | `onShow` `action` | E5.8#37(Phase 8 类型 B):悬浮面板哑渲染订阅——池 FloatingPanelHost 消费(壳 p… |
|
|
41
|
+
| `serial` | 11 | `listPorts` `getStatus` `openPort` `closePort` `sendData` `sendText` `setDtr` `setRts` `onData` `onStats` `onSystem` | 串口——读/写/监听,对标 VS Code SerialPort API |
|
|
42
|
+
| `clipboard` | 3 | `readText` `writeText` `writeFileList` | 剪贴板——读/写系统剪贴板 |
|
|
43
|
+
| `p2p` | 2 | `send` `on` | E5#65:p2p 插件间定向推流——和 bridge.broadcast 同模式(fire-and-forget) |
|
|
44
|
+
| `events` | 4 | `on` `emit` `heartbeat`° `notifyTheme`° | 通用事件订阅 + 发布——插件间数据管道 |
|
|
45
|
+
| `pluginState` | 3 | `get` `set` `onChange` | E5#71:插件持久化存储——集中缓存 + 文件持久化 |
|
|
46
|
+
| `workspace` | 8 | `getFolders` `getActive` `setActive` `openFolder` `addFolder` `removeFolder` `onDidChangeFolders` `onDidChangeActiveWorkspace` | 工作区——池 preload 注入(壳侧经 WorkspaceService 直用) |
|
|
47
|
+
| `filesystem` | 12 | `readTextFile` `writeTextFile` `exists` `createDir` `copy` `rename` `remove` `listDir` `readBinaryFile` `writeBinaryFile` `watch` `readdir`° | 文件系统——插件读写(路径校验由主进程执行) |
|
|
48
|
+
| `path` | 6 | `appDataDir`° `normalize` `join` `basename` `dirname` `extname` | 路径工具——壳/池双端注入(editor/file-tree 池插件消费 normalize/join 等) |
|
|
49
|
+
| `env` | 1 | `get` | 环境信息——对标 VS Code ExtensionContext |
|
|
50
|
+
| `search` | 1 | `searchFiles` | E5.6#11.5a:文件搜索——全文搜索/替换(IPC 到壳/主进程执行) |
|
|
51
|
+
| `encoding` | 3 | `detect` `decode` `encode` | E5.6#11.5a:编码检测/转换(主进程 EncodingService) |
|
|
52
|
+
| `decorations` | 4 | `registerProvider` `unregisterProvider` `getDecoration` `onDidChange` | E5.7#60:文件装饰——池内本地注册表(零 IPC) |
|
|
53
|
+
| `fileAssociation` | 1 | `getPluginFor` | E5.7#50:文件关联——扩展名→插件 ID(主进程 FileAssociationService 直答) |
|
|
54
|
+
| `langDef` | 1 | `get` | E5.7#49:langDef——语言定义注册表(主进程直答) |
|
|
55
|
+
| `lsp` | 4 | `spawn` `write` `dispose` `onData` | E5.6#14-lsp:LSP 桥——自动补全/F12/诊断/重命名 |
|
|
56
|
+
| `protocol` | 3 | `listProtocols` `getActiveProtocolId` `setActiveProtocolId` | E5.7#49:protocol——协议注册表(主进程直答) |
|
|
57
|
+
| `viewContainer` | 4 | `getViewContainer` `getViews` `getView` `registerView` | E5.7#58:viewContainer——真 IPC 查询/更新(问壳侧注册表) |
|
|
58
|
+
| `plugins` | 13 | `resolvePath` `resolveEntry`° `listDirs`° `listAll`° `listDisabledDirs`° `readManifest`° `readAllManifests`° `packageDownload`° `packageExtract`° `packageCancel`° `packageUpdateCheck`° `packageStageUpdate`° `packageCommitUpdate`° | 插件发现——双端注入:resolvePath 双端同面 |
|
|
59
|
+
| `pluginManager` | 13 | `list` `enable` `disable` `uninstall` `install` `installWithProgress`° `reinstall` `getDisabled` `getUninstalled` `isDisabled` `update`° `checkUpdates`° `notifyManifestChanged`° | 插件管理——桥接 IpcBridgeHandler → loader 函数 |
|
|
60
|
+
| `bridge` ⚠️ | 4 | `onRequest` `respond` `broadcast` `notifyConfigChanged` | 壳↔插件通信中继——壳 preload 独有 |
|
|
61
|
+
| `pool` | 31 | `pushLayout` `onReady` `toggleDevTools` `onSidebarAction` `onTabAction` `onTabBarRects` `onDragPosition` `pushAdsorbHint` `onAdsorbIndex` `pushQuickPick` `onQuickPickAction` `pushDialog` `onDialogAction` `pushFloatingPanel` `onFloatingPanelAction` `onMemoryPressure` `createWindow` `closeWindow` `onWindowClosed` `onWindowBoundsChanged` `onLayout` `ready` `sidebarAction` `tabAction` `tabBarRects` `dragPosition` `onAdsorbHint` `adsorbIndex` `registerBeforeClose` `unregisterBeforeClose` `beforeClose` | 池控制——壳 preload:推送布局 + 注册池→壳动作回调 |
|
|
62
|
+
| `window` | 11 | `minimize` `maximize` `unmaximize` `close` `setZoom` `toggleDevTools` `isMaximized` `onMaximizeChange` `setAlwaysOnTop` `isAlwaysOnTop` `onAlwaysOnTopChange` | 窗口控制——TitleBar 按钮映射,双端注入(11 方法同通道,共享模块 electron/window-name… |
|
|
63
|
+
| `shell` | 6 | `showItemInFolder` `openInTerminal` `pluginLocation` `openPluginFolder` `startDrag` `relaunch`° | 壳级命令——revealInOS / openInTerminal / startDrag / relaunch,双端… |
|
|
64
|
+
| `hotExit` ⚠️ | 3 | `save` `load` `clear` | 热退出暂存——编辑器未保存内容落盘(E5.7#53) |
|
|
65
|
+
| `getFilePath` | 0 | (顶层函数)`getFilePath: (file: File) => string;` | OS 拖入文件路径获取——双端注入 |
|
|
66
|
+
| `panel` | 2 | `reveal` `revealFloating` | —— |
|
|
67
|
+
| `settings` | 3 | `list` `getActive` `setActive` | —— |
|
|
68
|
+
| `factorySlots` | 4 | `listRoles` `list` `getActive` `setActive` | —— |
|
|
69
|
+
| `app` | 1 | `getVersion` | app 命名空间——只读产品身份 |
|
|
70
|
+
| `update` | 1 | `getState` | update 命名空间——只读更新状态(供「关于」类插件读宿主版本/更新态) |
|
|
71
|
+
|
|
72
|
+
⚠️ = 契约可选命名空间(只在一侧注入):`bridge` `hotExit`——调用前先判断是否存在,另一侧为 `undefined`。
|
|
73
|
+
° = 契约标 `?` 的成员:只在一侧 preload 注入(绝大多数是壳侧独有),**插件跑在池里**——调用前先判存在。
|
|
74
|
+
|
|
75
|
+
<!-- END API-CHEATSHEET -->
|
|
76
|
+
|
|
14
77
|
## 使用
|
|
15
78
|
|
|
16
79
|
### 1. tsconfig——让 `window.linkdesk.` 有类型
|
package/dev-host/dev-main.tsx
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dev 宿主入口(E6#23.5/#24b)——纯浏览器最小壳:顶部 dev 条 + React root 渲染正在开发的插件。
|
|
3
|
-
*
|
|
4
|
-
* 接线(均由 `linkdesk-plugin-sdk dev` 的 vite 配置注入,见 src/dev-server.ts):
|
|
5
|
-
* - `__linkdesk_dev_entry__`:resolve.alias 虚拟 id → 插件根 plugin.json 的 entry 绝对路径。
|
|
6
|
-
* 静态 import → 入口是模块图常规成员 → vite 标准 HMR 全链(改码即热更)。
|
|
7
|
-
* - `__LINKDESK_DEV_I18N__`:define 字面量 = { 语言码: { translation: {…} } }(现读插件
|
|
8
|
-
* contributes.i18n 声明的各语言 JSON)。
|
|
9
|
-
* - `__LINKDESK_DEV_PLUGIN_ID__`:define 字面量 = pluginId(顶部 dev 条展示)。
|
|
10
|
-
*
|
|
11
|
-
* 契约:宿主把插件当「激活标签」渲染({ isActive: true })——对标壳渲染视图插件的语义;
|
|
12
|
-
* keep-alive / tabId / sourceId 在纯浏览器 dev 无对应物(那是壳标签页系统的概念)。
|
|
13
|
-
* i18n:lng = 插件第一个声明的语言(通常 en);key 缺失时 i18next 返回 key 本身——中文 key 即中文兜底。
|
|
14
|
-
*/
|
|
15
|
-
import { createRoot } from "react-dom/client";
|
|
16
|
-
import i18n from "i18next";
|
|
17
|
-
import { initReactI18next, I18nextProvider } from "react-i18next";
|
|
18
|
-
import { injectDevMockApi } from "./mock.js";
|
|
19
|
-
import PluginView from "__linkdesk_dev_entry__";
|
|
20
|
-
|
|
21
|
-
declare const __LINKDESK_DEV_I18N__: Record<string, { translation: Record<string, unknown> }>;
|
|
22
|
-
declare const __LINKDESK_DEV_PLUGIN_ID__: string;
|
|
23
|
-
|
|
24
|
-
const resources = __LINKDESK_DEV_I18N__;
|
|
25
|
-
const lng = Object.keys(resources)[0] ?? "en";
|
|
26
|
-
|
|
27
|
-
void i18n.use(initReactI18next).init({
|
|
28
|
-
resources,
|
|
29
|
-
lng,
|
|
30
|
-
fallbackLng: false,
|
|
31
|
-
interpolation: { escapeValue: false },
|
|
32
|
-
react: { useSuspense: false },
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
injectDevMockApi();
|
|
36
|
-
|
|
37
|
-
const pluginIdEl = document.getElementById("ld-plugin-id");
|
|
38
|
-
if (pluginIdEl) pluginIdEl.textContent = __LINKDESK_DEV_PLUGIN_ID__;
|
|
39
|
-
|
|
40
|
-
const rootEl = document.getElementById("ld-root");
|
|
41
|
-
if (rootEl) {
|
|
42
|
-
createRoot(rootEl).render(
|
|
43
|
-
<I18nextProvider i18n={i18n}>
|
|
44
|
-
<PluginView isActive />
|
|
45
|
-
</I18nextProvider>,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dev 宿主入口(E6#23.5/#24b)——纯浏览器最小壳:顶部 dev 条 + React root 渲染正在开发的插件。
|
|
3
|
+
*
|
|
4
|
+
* 接线(均由 `linkdesk-plugin-sdk dev` 的 vite 配置注入,见 src/dev-server.ts):
|
|
5
|
+
* - `__linkdesk_dev_entry__`:resolve.alias 虚拟 id → 插件根 plugin.json 的 entry 绝对路径。
|
|
6
|
+
* 静态 import → 入口是模块图常规成员 → vite 标准 HMR 全链(改码即热更)。
|
|
7
|
+
* - `__LINKDESK_DEV_I18N__`:define 字面量 = { 语言码: { translation: {…} } }(现读插件
|
|
8
|
+
* contributes.i18n 声明的各语言 JSON)。
|
|
9
|
+
* - `__LINKDESK_DEV_PLUGIN_ID__`:define 字面量 = pluginId(顶部 dev 条展示)。
|
|
10
|
+
*
|
|
11
|
+
* 契约:宿主把插件当「激活标签」渲染({ isActive: true })——对标壳渲染视图插件的语义;
|
|
12
|
+
* keep-alive / tabId / sourceId 在纯浏览器 dev 无对应物(那是壳标签页系统的概念)。
|
|
13
|
+
* i18n:lng = 插件第一个声明的语言(通常 en);key 缺失时 i18next 返回 key 本身——中文 key 即中文兜底。
|
|
14
|
+
*/
|
|
15
|
+
import { createRoot } from "react-dom/client";
|
|
16
|
+
import i18n from "i18next";
|
|
17
|
+
import { initReactI18next, I18nextProvider } from "react-i18next";
|
|
18
|
+
import { injectDevMockApi } from "./mock.js";
|
|
19
|
+
import PluginView from "__linkdesk_dev_entry__";
|
|
20
|
+
|
|
21
|
+
declare const __LINKDESK_DEV_I18N__: Record<string, { translation: Record<string, unknown> }>;
|
|
22
|
+
declare const __LINKDESK_DEV_PLUGIN_ID__: string;
|
|
23
|
+
|
|
24
|
+
const resources = __LINKDESK_DEV_I18N__;
|
|
25
|
+
const lng = Object.keys(resources)[0] ?? "en";
|
|
26
|
+
|
|
27
|
+
void i18n.use(initReactI18next).init({
|
|
28
|
+
resources,
|
|
29
|
+
lng,
|
|
30
|
+
fallbackLng: false,
|
|
31
|
+
interpolation: { escapeValue: false },
|
|
32
|
+
react: { useSuspense: false },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
injectDevMockApi();
|
|
36
|
+
|
|
37
|
+
const pluginIdEl = document.getElementById("ld-plugin-id");
|
|
38
|
+
if (pluginIdEl) pluginIdEl.textContent = __LINKDESK_DEV_PLUGIN_ID__;
|
|
39
|
+
|
|
40
|
+
const rootEl = document.getElementById("ld-root");
|
|
41
|
+
if (rootEl) {
|
|
42
|
+
createRoot(rootEl).render(
|
|
43
|
+
<I18nextProvider i18n={i18n}>
|
|
44
|
+
<PluginView isActive />
|
|
45
|
+
</I18nextProvider>,
|
|
46
|
+
);
|
|
47
|
+
}
|
package/dev-host/index.html
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>LinkDesk dev 宿主</title>
|
|
7
|
-
<style>
|
|
8
|
-
/* dev 宿主页 = SDK 工具 UI(非壳/非产品),中性配色 + 兜底主题变量让插件 var(--xxx) 不至全黑。
|
|
9
|
-
固定值允许——不违反壳硬约束 16(那约束壳 UI;此页不属于壳渲染树)。 */
|
|
10
|
-
:root {
|
|
11
|
-
--text: #e8e8ea;
|
|
12
|
-
--text-muted: #9a9aa2;
|
|
13
|
-
--border: #2c2c33;
|
|
14
|
-
--bg: #16161a;
|
|
15
|
-
--bg-alt: #1f1f25;
|
|
16
|
-
--accent: #4cc38a;
|
|
17
|
-
}
|
|
18
|
-
* { box-sizing: border-box; }
|
|
19
|
-
html, body { margin: 0; height: 100%; }
|
|
20
|
-
body {
|
|
21
|
-
background: var(--bg);
|
|
22
|
-
color: var(--text);
|
|
23
|
-
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
24
|
-
}
|
|
25
|
-
#ld-topbar {
|
|
26
|
-
position: sticky; top: 0; z-index: 10;
|
|
27
|
-
display: flex; align-items: center; gap: 8px;
|
|
28
|
-
padding: 5px 12px; font-size: 12px;
|
|
29
|
-
background: var(--bg-alt); border-bottom: 1px solid var(--border);
|
|
30
|
-
user-select: none;
|
|
31
|
-
}
|
|
32
|
-
#ld-topbar .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
|
|
33
|
-
#ld-topbar .sep { opacity: 0.4; }
|
|
34
|
-
#ld-topbar code { color: #9fd0ff; font-family: ui-monospace, "Cascadia Code", Consolas, monospace; }
|
|
35
|
-
#ld-topbar .hint { color: var(--text-muted); margin-left: auto; }
|
|
36
|
-
#ld-root { padding: 0; }
|
|
37
|
-
</style>
|
|
38
|
-
</head>
|
|
39
|
-
<body>
|
|
40
|
-
<div id="ld-topbar">
|
|
41
|
-
<span class="dot"></span>
|
|
42
|
-
<span>LinkDesk dev 宿主</span>
|
|
43
|
-
<span class="sep">·</span>
|
|
44
|
-
<code id="ld-plugin-id"></code>
|
|
45
|
-
<span class="hint">本地预览 · 无 Electron/IPC · 改码 HMR</span>
|
|
46
|
-
</div>
|
|
47
|
-
<div id="ld-root"></div>
|
|
48
|
-
<script type="module" src="/dev-main.tsx"></script>
|
|
49
|
-
</body>
|
|
50
|
-
</html>
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>LinkDesk dev 宿主</title>
|
|
7
|
+
<style>
|
|
8
|
+
/* dev 宿主页 = SDK 工具 UI(非壳/非产品),中性配色 + 兜底主题变量让插件 var(--xxx) 不至全黑。
|
|
9
|
+
固定值允许——不违反壳硬约束 16(那约束壳 UI;此页不属于壳渲染树)。 */
|
|
10
|
+
:root {
|
|
11
|
+
--text: #e8e8ea;
|
|
12
|
+
--text-muted: #9a9aa2;
|
|
13
|
+
--border: #2c2c33;
|
|
14
|
+
--bg: #16161a;
|
|
15
|
+
--bg-alt: #1f1f25;
|
|
16
|
+
--accent: #4cc38a;
|
|
17
|
+
}
|
|
18
|
+
* { box-sizing: border-box; }
|
|
19
|
+
html, body { margin: 0; height: 100%; }
|
|
20
|
+
body {
|
|
21
|
+
background: var(--bg);
|
|
22
|
+
color: var(--text);
|
|
23
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
24
|
+
}
|
|
25
|
+
#ld-topbar {
|
|
26
|
+
position: sticky; top: 0; z-index: 10;
|
|
27
|
+
display: flex; align-items: center; gap: 8px;
|
|
28
|
+
padding: 5px 12px; font-size: 12px;
|
|
29
|
+
background: var(--bg-alt); border-bottom: 1px solid var(--border);
|
|
30
|
+
user-select: none;
|
|
31
|
+
}
|
|
32
|
+
#ld-topbar .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
|
|
33
|
+
#ld-topbar .sep { opacity: 0.4; }
|
|
34
|
+
#ld-topbar code { color: #9fd0ff; font-family: ui-monospace, "Cascadia Code", Consolas, monospace; }
|
|
35
|
+
#ld-topbar .hint { color: var(--text-muted); margin-left: auto; }
|
|
36
|
+
#ld-root { padding: 0; }
|
|
37
|
+
</style>
|
|
38
|
+
</head>
|
|
39
|
+
<body>
|
|
40
|
+
<div id="ld-topbar">
|
|
41
|
+
<span class="dot"></span>
|
|
42
|
+
<span>LinkDesk dev 宿主</span>
|
|
43
|
+
<span class="sep">·</span>
|
|
44
|
+
<code id="ld-plugin-id"></code>
|
|
45
|
+
<span class="hint">本地预览 · 无 Electron/IPC · 改码 HMR</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div id="ld-root"></div>
|
|
48
|
+
<script type="module" src="/dev-main.tsx"></script>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
|
@@ -363,4 +363,7 @@ export const linkdeskMock: Record<string, unknown> = {
|
|
|
363
363
|
app: {
|
|
364
364
|
getVersion: async (..._args: unknown[]) => { console.info("[linkdesk-mock] app.getVersion", ..._args); return ""; },
|
|
365
365
|
},
|
|
366
|
+
update: {
|
|
367
|
+
getState: async (..._args: unknown[]) => { console.info("[linkdesk-mock] update.getState", ..._args); },
|
|
368
|
+
},
|
|
366
369
|
};
|
package/dev-host/mock.ts
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dev 宿主 mock 注入锚点(E6#23.5c 位置定案)。`injectDevMockApi()` 签名与调用点不变。
|
|
3
|
-
*
|
|
4
|
-
* 注入体(E6#27 落地替换,2026-09-07):
|
|
5
|
-
* - 方法树 = 自动生成 [linkdesk-mock.generated.ts](./linkdesk-mock.generated.ts)——与 linkdesk.d.ts
|
|
6
|
-
* 同一标注源 `src/core/api/linkdesk-api.ts`(杜绝 mock/preload 双份漂移)。每个方法 = 类型驱动
|
|
7
|
-
* 中性默认 + 调用即打 `[linkdesk-mock] <path>` 日志(mock vs 真 IPC 可感知,不静默假成功)。
|
|
8
|
-
* - #27b 兜底层 = 递归 Proxy:树内路径(契约里真实方法/命名空间)返回真子树/方法;**树外路径**
|
|
9
|
-
* (契约里不存在的方法名,如拼写错)**调用时抛错**「不在 linkdesk API 契约」而非静默 undefined /
|
|
10
|
-
* `undefined is not a function`——单源同构下,mock 缺 = 生产也缺,早抛早纠正。
|
|
11
|
-
*
|
|
12
|
-
* 真数据/真行为(串口/文件系统/IPC)不属于浏览器 mock——走真实 preload(装进 LinkDesk)或
|
|
13
|
-
* `linkdesk-plugin-sdk dev --real` 真机环(E6#28.5)。
|
|
14
|
-
*/
|
|
15
|
-
import { linkdeskMock } from "./linkdesk-mock.generated.js";
|
|
16
|
-
|
|
17
|
-
/** 常见 introspection 键——不当作 linkdesk API 路径,返回 undefined(防 console/JSON/运行时探测误触 throw-stub) */
|
|
18
|
-
const SKIP_INTRINSIC = new Set([
|
|
19
|
-
"then",
|
|
20
|
-
"toJSON",
|
|
21
|
-
"toString",
|
|
22
|
-
"valueOf",
|
|
23
|
-
"constructor",
|
|
24
|
-
"__proto__",
|
|
25
|
-
"hasOwnProperty",
|
|
26
|
-
"isPrototypeOf",
|
|
27
|
-
"propertyIsEnumerable",
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
/** 树外路径 call-stub——访问不抛(feature-detect 可安全探测),调用才抛(#27b)。
|
|
31
|
-
* 自身是 Proxy:继续取属性返回更深 stub(`linkdesk.foo.bar()` 整链报完整 dotted path,
|
|
32
|
-
* 而非裸 TypeError「not a function」);symbol/intrinsic 键返回 undefined 防运行时探测误触。 */
|
|
33
|
-
function missingApiStub(dotted: string): () => never {
|
|
34
|
-
const stub = () => {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`[dev] window.linkdesk.${dotted} 不是 linkdesk API 方法——dev mock 与 src/core/api/linkdesk-api.ts` +
|
|
37
|
-
` 同源自动生成(方法集与生产一致),契约里没有它 = 生产环境调用同样报错。请核对方法名/拼写。` +
|
|
38
|
-
`确需真实 IPC 行为 → linkdesk-plugin-sdk dev --real(E6#28.5 真机环)。`,
|
|
39
|
-
);
|
|
40
|
-
};
|
|
41
|
-
return new Proxy(stub, {
|
|
42
|
-
get(_t, prop) {
|
|
43
|
-
if (typeof prop === "symbol" || SKIP_INTRINSIC.has(String(prop))) return undefined;
|
|
44
|
-
return missingApiStub(`${dotted}.${String(prop)}`);
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** 递归 Proxy:树内路径返回真子树(命名空间再包一层 / 方法叶子直返),树外路径调用抛错 */
|
|
50
|
-
function wrapNode(node: Record<string, unknown>, path: string[]): Record<string, unknown> {
|
|
51
|
-
return new Proxy(node, {
|
|
52
|
-
get(t, prop) {
|
|
53
|
-
if (typeof prop === "symbol") return undefined;
|
|
54
|
-
const key = String(prop);
|
|
55
|
-
if (SKIP_INTRINSIC.has(key)) return undefined;
|
|
56
|
-
if (key in t) {
|
|
57
|
-
const v = t[key];
|
|
58
|
-
if (typeof v === "function") return v; // 方法叶子(自打 [linkdesk-mock] 调用日志)
|
|
59
|
-
if (v !== null && typeof v === "object") return wrapNode(v as Record<string, unknown>, [...path, key]);
|
|
60
|
-
return v;
|
|
61
|
-
}
|
|
62
|
-
return missingApiStub([...path, key].join("."));
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function injectDevMockApi(): void {
|
|
68
|
-
if (typeof window === "undefined") return;
|
|
69
|
-
if ((window as unknown as Record<string, unknown>).linkdesk) return;
|
|
70
|
-
|
|
71
|
-
(window as unknown as Record<string, unknown>).linkdesk = wrapNode(linkdeskMock, []);
|
|
72
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dev 宿主 mock 注入锚点(E6#23.5c 位置定案)。`injectDevMockApi()` 签名与调用点不变。
|
|
3
|
+
*
|
|
4
|
+
* 注入体(E6#27 落地替换,2026-09-07):
|
|
5
|
+
* - 方法树 = 自动生成 [linkdesk-mock.generated.ts](./linkdesk-mock.generated.ts)——与 linkdesk.d.ts
|
|
6
|
+
* 同一标注源 `src/core/api/linkdesk-api.ts`(杜绝 mock/preload 双份漂移)。每个方法 = 类型驱动
|
|
7
|
+
* 中性默认 + 调用即打 `[linkdesk-mock] <path>` 日志(mock vs 真 IPC 可感知,不静默假成功)。
|
|
8
|
+
* - #27b 兜底层 = 递归 Proxy:树内路径(契约里真实方法/命名空间)返回真子树/方法;**树外路径**
|
|
9
|
+
* (契约里不存在的方法名,如拼写错)**调用时抛错**「不在 linkdesk API 契约」而非静默 undefined /
|
|
10
|
+
* `undefined is not a function`——单源同构下,mock 缺 = 生产也缺,早抛早纠正。
|
|
11
|
+
*
|
|
12
|
+
* 真数据/真行为(串口/文件系统/IPC)不属于浏览器 mock——走真实 preload(装进 LinkDesk)或
|
|
13
|
+
* `linkdesk-plugin-sdk dev --real` 真机环(E6#28.5)。
|
|
14
|
+
*/
|
|
15
|
+
import { linkdeskMock } from "./linkdesk-mock.generated.js";
|
|
16
|
+
|
|
17
|
+
/** 常见 introspection 键——不当作 linkdesk API 路径,返回 undefined(防 console/JSON/运行时探测误触 throw-stub) */
|
|
18
|
+
const SKIP_INTRINSIC = new Set([
|
|
19
|
+
"then",
|
|
20
|
+
"toJSON",
|
|
21
|
+
"toString",
|
|
22
|
+
"valueOf",
|
|
23
|
+
"constructor",
|
|
24
|
+
"__proto__",
|
|
25
|
+
"hasOwnProperty",
|
|
26
|
+
"isPrototypeOf",
|
|
27
|
+
"propertyIsEnumerable",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/** 树外路径 call-stub——访问不抛(feature-detect 可安全探测),调用才抛(#27b)。
|
|
31
|
+
* 自身是 Proxy:继续取属性返回更深 stub(`linkdesk.foo.bar()` 整链报完整 dotted path,
|
|
32
|
+
* 而非裸 TypeError「not a function」);symbol/intrinsic 键返回 undefined 防运行时探测误触。 */
|
|
33
|
+
function missingApiStub(dotted: string): () => never {
|
|
34
|
+
const stub = () => {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`[dev] window.linkdesk.${dotted} 不是 linkdesk API 方法——dev mock 与 src/core/api/linkdesk-api.ts` +
|
|
37
|
+
` 同源自动生成(方法集与生产一致),契约里没有它 = 生产环境调用同样报错。请核对方法名/拼写。` +
|
|
38
|
+
`确需真实 IPC 行为 → linkdesk-plugin-sdk dev --real(E6#28.5 真机环)。`,
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
return new Proxy(stub, {
|
|
42
|
+
get(_t, prop) {
|
|
43
|
+
if (typeof prop === "symbol" || SKIP_INTRINSIC.has(String(prop))) return undefined;
|
|
44
|
+
return missingApiStub(`${dotted}.${String(prop)}`);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 递归 Proxy:树内路径返回真子树(命名空间再包一层 / 方法叶子直返),树外路径调用抛错 */
|
|
50
|
+
function wrapNode(node: Record<string, unknown>, path: string[]): Record<string, unknown> {
|
|
51
|
+
return new Proxy(node, {
|
|
52
|
+
get(t, prop) {
|
|
53
|
+
if (typeof prop === "symbol") return undefined;
|
|
54
|
+
const key = String(prop);
|
|
55
|
+
if (SKIP_INTRINSIC.has(key)) return undefined;
|
|
56
|
+
if (key in t) {
|
|
57
|
+
const v = t[key];
|
|
58
|
+
if (typeof v === "function") return v; // 方法叶子(自打 [linkdesk-mock] 调用日志)
|
|
59
|
+
if (v !== null && typeof v === "object") return wrapNode(v as Record<string, unknown>, [...path, key]);
|
|
60
|
+
return v;
|
|
61
|
+
}
|
|
62
|
+
return missingApiStub([...path, key].join("."));
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function injectDevMockApi(): void {
|
|
68
|
+
if (typeof window === "undefined") return;
|
|
69
|
+
if ((window as unknown as Record<string, unknown>).linkdesk) return;
|
|
70
|
+
|
|
71
|
+
(window as unknown as Record<string, unknown>).linkdesk = wrapNode(linkdeskMock, []);
|
|
72
|
+
}
|
package/dist/bin.js
CHANGED
|
@@ -25,22 +25,22 @@ const VITE_CONFIG_FILES = [
|
|
|
25
25
|
"vite.config.mjs",
|
|
26
26
|
"vite.config.cjs",
|
|
27
27
|
];
|
|
28
|
-
const USAGE = `linkdesk-plugin-sdk <command>
|
|
29
|
-
|
|
30
|
-
命令:
|
|
31
|
-
dev 在插件工程根起 dev 宿主(E6#24)——读 plugin.json → Vite dev server(端口 1421)
|
|
32
|
-
→ 浏览器打开纯前端预览 + HMR。只支持带 entry 的视图插件(脚手架 tab 形态)
|
|
33
|
-
dev --real E6#28.5 真机环——watch 作者源码 → 隔离单插件 build → 直写 {userData}/plugins/<id>
|
|
34
|
-
→ CDP reload「LinkDesk Pool」(LinkDesk 须以 --remote-debugging-port=9222 启动)。
|
|
35
|
-
真 IPC/串口/LSP 类插件的秒级真机调试(壳零新代码;LINKDESK_USER_PLUGINS_DIR /
|
|
36
|
-
LINKDESK_CDP_PORT 可覆盖)
|
|
37
|
-
build 在插件工程根构建 .linkdesk-plugin(读 plugin.json → Vite build → zip)
|
|
38
|
-
publish 一键发布(E6#26)——自动链路:建 GitHub Release → 上传 .linkdesk-plugin → 更新工程
|
|
39
|
-
origin 仓库根 marketplace.json(多市场源模型)。发前预览确认;--yes 跳过(CI);
|
|
40
|
-
--dry-run 只预览不碰网络。token:env LINKDESK_GITHUB_TOKEN,或首跑交互输入存入本机
|
|
41
|
-
validate 校验 plugin.json(参数 = 路径,默认 ./plugin.json)
|
|
42
|
-
lint E6#54d 门禁(eslint 12 规则 + 三 check 双轨,全 WARN 永不 fail;知情绕行 =
|
|
43
|
-
eslint-disable 注释)。参数 = 工程根,默认 process.cwd()
|
|
28
|
+
const USAGE = `linkdesk-plugin-sdk <command>
|
|
29
|
+
|
|
30
|
+
命令:
|
|
31
|
+
dev 在插件工程根起 dev 宿主(E6#24)——读 plugin.json → Vite dev server(端口 1421)
|
|
32
|
+
→ 浏览器打开纯前端预览 + HMR。只支持带 entry 的视图插件(脚手架 tab 形态)
|
|
33
|
+
dev --real E6#28.5 真机环——watch 作者源码 → 隔离单插件 build → 直写 {userData}/plugins/<id>
|
|
34
|
+
→ CDP reload「LinkDesk Pool」(LinkDesk 须以 --remote-debugging-port=9222 启动)。
|
|
35
|
+
真 IPC/串口/LSP 类插件的秒级真机调试(壳零新代码;LINKDESK_USER_PLUGINS_DIR /
|
|
36
|
+
LINKDESK_CDP_PORT 可覆盖)
|
|
37
|
+
build 在插件工程根构建 .linkdesk-plugin(读 plugin.json → Vite build → zip)
|
|
38
|
+
publish 一键发布(E6#26)——自动链路:建 GitHub Release → 上传 .linkdesk-plugin → 更新工程
|
|
39
|
+
origin 仓库根 marketplace.json(多市场源模型)。发前预览确认;--yes 跳过(CI);
|
|
40
|
+
--dry-run 只预览不碰网络。token:env LINKDESK_GITHUB_TOKEN,或首跑交互输入存入本机
|
|
41
|
+
validate 校验 plugin.json(参数 = 路径,默认 ./plugin.json)
|
|
42
|
+
lint E6#54d 门禁(eslint 12 规则 + 三 check 双轨,全 WARN 永不 fail;知情绕行 =
|
|
43
|
+
eslint-disable 注释)。参数 = 工程根,默认 process.cwd()
|
|
44
44
|
`;
|
|
45
45
|
async function cmdLint(root) {
|
|
46
46
|
const report = await runPluginLint(root);
|
package/package.json
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@linkdesk/plugin-sdk",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "@linkdesk/plugin-sdk——LinkDesk 插件作者 SDK(对标 @types/vscode):window.linkdesk.* 类型 + 一键 build(.linkdesk-plugin) + dev 宿主 + plugin.json 验证 + eslint 门禁(E6#54d)。作者 npm i 后零壳源码依赖。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"default": "./dist/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./eslint": {
|
|
14
|
-
"types": "./dist/eslint/index.d.ts",
|
|
15
|
-
"default": "./dist/eslint/index.js"
|
|
16
|
-
},
|
|
17
|
-
"./package.json": "./package.json"
|
|
18
|
-
},
|
|
19
|
-
"files": [
|
|
20
|
-
"dist",
|
|
21
|
-
"schemas",
|
|
22
|
-
"dev-host"
|
|
23
|
-
],
|
|
24
|
-
"sideEffects": false,
|
|
25
|
-
"bin": {
|
|
26
|
-
"linkdesk-plugin-sdk": "./dist/bin.js"
|
|
27
|
-
},
|
|
28
|
-
"publishConfig": {
|
|
29
|
-
"access": "public"
|
|
30
|
-
},
|
|
31
|
-
"engines": {
|
|
32
|
-
"node": ">=20.0.0"
|
|
33
|
-
},
|
|
34
|
-
"scripts": {
|
|
35
|
-
"build": "tsc -p tsconfig.build.json",
|
|
36
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
37
|
-
"prepack": "tsc -p tsconfig.build.json"
|
|
38
|
-
},
|
|
39
|
-
"dependencies": {
|
|
40
|
-
"@linkdesk/contracts": "^0.1.1",
|
|
41
|
-
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
42
|
-
"@typescript-eslint/parser": "^8.0.0",
|
|
43
|
-
"@vitejs/plugin-react": "^4.3.4",
|
|
44
|
-
"ajv": "^8.17.1",
|
|
45
|
-
"eslint": "^9.0.0",
|
|
46
|
-
"eslint-import-resolver-typescript": "^4.4.5",
|
|
47
|
-
"eslint-plugin-import-x": "^4.17.1",
|
|
48
|
-
"eslint-plugin-react-hooks": "^7.1.1",
|
|
49
|
-
"i18next": "^26.3.6",
|
|
50
|
-
"jsonc-parser": "^3.3.1",
|
|
51
|
-
"jszip": "^3.10.1",
|
|
52
|
-
"react": "^18.3.1",
|
|
53
|
-
"react-dom": "^18.3.1",
|
|
54
|
-
"react-i18next": "^17.0.10",
|
|
55
|
-
"vite": "^6.0.0"
|
|
56
|
-
},
|
|
57
|
-
"devDependencies": {
|
|
58
|
-
"@types/node": "^24.0.0",
|
|
59
|
-
"typescript": "^5.9.0"
|
|
60
|
-
},
|
|
61
|
-
"keywords": [
|
|
62
|
-
"linkdesk",
|
|
63
|
-
"plugin",
|
|
64
|
-
"sdk",
|
|
65
|
-
"vite"
|
|
66
|
-
],
|
|
67
|
-
"license": "MIT"
|
|
68
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@linkdesk/plugin-sdk",
|
|
3
|
+
"version": "0.1.12",
|
|
4
|
+
"description": "@linkdesk/plugin-sdk——LinkDesk 插件作者 SDK(对标 @types/vscode):window.linkdesk.* 类型 + 一键 build(.linkdesk-plugin) + dev 宿主 + plugin.json 验证 + eslint 门禁(E6#54d)。作者 npm i 后零壳源码依赖。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./eslint": {
|
|
14
|
+
"types": "./dist/eslint/index.d.ts",
|
|
15
|
+
"default": "./dist/eslint/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"schemas",
|
|
22
|
+
"dev-host"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"bin": {
|
|
26
|
+
"linkdesk-plugin-sdk": "./dist/bin.js"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20.0.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -p tsconfig.build.json",
|
|
36
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
37
|
+
"prepack": "tsc -p tsconfig.build.json"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@linkdesk/contracts": "^0.1.1",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
42
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
43
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
44
|
+
"ajv": "^8.17.1",
|
|
45
|
+
"eslint": "^9.0.0",
|
|
46
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
47
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
48
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
49
|
+
"i18next": "^26.3.6",
|
|
50
|
+
"jsonc-parser": "^3.3.1",
|
|
51
|
+
"jszip": "^3.10.1",
|
|
52
|
+
"react": "^18.3.1",
|
|
53
|
+
"react-dom": "^18.3.1",
|
|
54
|
+
"react-i18next": "^17.0.10",
|
|
55
|
+
"vite": "^6.0.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^24.0.0",
|
|
59
|
+
"typescript": "^5.9.0"
|
|
60
|
+
},
|
|
61
|
+
"keywords": [
|
|
62
|
+
"linkdesk",
|
|
63
|
+
"plugin",
|
|
64
|
+
"sdk",
|
|
65
|
+
"vite"
|
|
66
|
+
],
|
|
67
|
+
"license": "MIT"
|
|
68
|
+
}
|