dsh-apis-plugin 0.1.2 → 0.1.4
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/api-tools.js +15 -9
- package/client.js +18 -36
- package/package.json +1 -1
package/api-tools.js
CHANGED
|
@@ -14,7 +14,9 @@ function loadApiCfg(dir) {
|
|
|
14
14
|
try {
|
|
15
15
|
const cfg = { apis: [] }
|
|
16
16
|
let current = null
|
|
17
|
-
|
|
17
|
+
// 去掉 UTF-8 BOM(Windows 记事本默认会加),否则首个 key 解析失败
|
|
18
|
+
const text = readFileSync(join(dir, 'api.cfg'), 'utf8').replace(/^\uFEFF/, '')
|
|
19
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
18
20
|
const line = raw.trim()
|
|
19
21
|
if (!line || line.startsWith('#')) continue
|
|
20
22
|
const m = line.match(/^([\w-]+)\s*=\s*(.*)$/)
|
|
@@ -36,7 +38,7 @@ function loadApiCfg(dir) {
|
|
|
36
38
|
function loadApiDoc(dir) {
|
|
37
39
|
if (!dir) return ''
|
|
38
40
|
try {
|
|
39
|
-
return readFileSync(join(dir, 'api_doc.md'), 'utf8')
|
|
41
|
+
return readFileSync(join(dir, 'api_doc.md'), 'utf8').replace(/^\uFEFF/, '')
|
|
40
42
|
} catch {
|
|
41
43
|
return ''
|
|
42
44
|
}
|
|
@@ -104,12 +106,15 @@ export function defineApiPlugin(options) {
|
|
|
104
106
|
// config 覆盖优先,回退工厂默认值(兼容旧用户层保存值缺字段的情况)
|
|
105
107
|
const prefix = config.toolPrefix || toolPrefix
|
|
106
108
|
const domainName = config.domain || domain
|
|
107
|
-
// yml
|
|
109
|
+
// 部署侧静态接口(yml config.endpoints + 工厂默认),与 api.cfg 动态读取分离,
|
|
110
|
+
// 避免「加载API」重扫时把启动时的旧 api.cfg 列表拼回去(导致删掉的接口仍可请求)
|
|
108
111
|
const ymlApiDir = config.apiDir || apiDir
|
|
109
|
-
const
|
|
112
|
+
const staticEndpoints = [...new Set([...defaultEndpoints, ...(config.endpoints ?? [])])]
|
|
113
|
+
// 启动时以当前目录 api.cfg 为权威范围,∪ 静态接口作为 base
|
|
114
|
+
const bootEndpoints = [...new Set([...loadApiCfg(ymlApiDir).apis, ...staticEndpoints])]
|
|
110
115
|
|
|
111
116
|
// 以组合配置为 base 层注册 settings 命名空间,卸载插件时注册随 fiber 一并清理
|
|
112
|
-
const scope = ctx.settings.register(ns, Config, { base: { ...config, endpoints:
|
|
117
|
+
const scope = ctx.settings.register(ns, Config, { base: { ...config, endpoints: bootEndpoints } })
|
|
113
118
|
|
|
114
119
|
// 当前生效目录:用户层保存值优先(重启后仍生效),回退部署侧配置
|
|
115
120
|
let docDir = scope.get()?.apiDir || ymlApiDir
|
|
@@ -133,8 +138,9 @@ export function defineApiPlugin(options) {
|
|
|
133
138
|
}
|
|
134
139
|
if (next.scanToken !== lastScanToken) {
|
|
135
140
|
lastScanToken = next.scanToken
|
|
136
|
-
//
|
|
137
|
-
|
|
141
|
+
// 以最新 api.cfg 为权威范围 ∪ 静态接口,整体接管用户层 endpoints(去重;值未变时不提交,避免循环)
|
|
142
|
+
// 用最新读取结果而非启动时的快照,这样从 api.cfg 删掉的接口重扫后即失效
|
|
143
|
+
const merged = [...new Set([...loadApiCfg(dir).apis, ...staticEndpoints])]
|
|
138
144
|
const cur = Array.isArray(next.endpoints) ? next.endpoints : []
|
|
139
145
|
if (merged.join('\n') !== cur.join('\n')) scope.update({ endpoints: merged })
|
|
140
146
|
}
|
|
@@ -155,7 +161,7 @@ export function defineApiPlugin(options) {
|
|
|
155
161
|
const value = scope.get()
|
|
156
162
|
if (Array.isArray(value?.endpoints) && value.endpoints.length) return value.endpoints
|
|
157
163
|
} catch { /* scope.get 不可用时回退 */ }
|
|
158
|
-
return
|
|
164
|
+
return bootEndpoints
|
|
159
165
|
}
|
|
160
166
|
|
|
161
167
|
// 工具 1:查接口文档,让模型了解每个接口的参数与响应(prompt 按领域名拼接)
|
|
@@ -224,7 +230,7 @@ export function defineApiPlugin(options) {
|
|
|
224
230
|
},
|
|
225
231
|
})
|
|
226
232
|
|
|
227
|
-
console.log(`[${ns}] loaded; endpoints=${
|
|
233
|
+
console.log(`[${ns}] loaded; endpoints=${bootEndpoints.length}, doc sections=${docSections.length}`)
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
return { Config, inject, apply }
|
package/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// 浏览器半侧:在「插件配置」标签页为 dsh-apis-plugin
|
|
2
|
-
//
|
|
1
|
+
// 浏览器半侧:在「插件配置」标签页为 dsh-apis-plugin 命名空间注册一张展示卡片
|
|
2
|
+
// 接口的增删以 api.cfg 为唯一来源:这里只读展示列表,编辑请改 api.cfg 后点「加载API」重扫
|
|
3
|
+
// 样式与 DOM 结构对齐原生 PluginCard(li 卡片 + SVG 箭头 + footer)
|
|
3
4
|
window.__ModuleLoader__.load({
|
|
4
5
|
id: "dsh-apis-plugin",
|
|
5
6
|
factory: (require) => {
|
|
@@ -37,10 +38,9 @@ window.__ModuleLoader__.load({
|
|
|
37
38
|
.apis-rowLabelWide{width:auto;white-space:nowrap}
|
|
38
39
|
.apis-hr{width:100%;height:0;border:0;border-top:.5px solid var(--dsw-alias-border-l2);margin:0}
|
|
39
40
|
.apis-input{box-sizing:border-box;flex:1;min-width:0;font:inherit;color:var(--dsw-alias-label-primary);background:transparent;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:6px 10px}
|
|
40
|
-
.apis-
|
|
41
|
-
.apis-
|
|
42
|
-
.apis-
|
|
43
|
-
.apis-add:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
|
|
41
|
+
.apis-item{display:flex;align-items:center;gap:8px;min-width:0}
|
|
42
|
+
.apis-itemText{flex:1;min-width:0;font-size:13px;color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:6px 10px;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3)}
|
|
43
|
+
.apis-empty{color:var(--dsw-alias-label-tertiary);font-size:13px;padding:4px 0}
|
|
44
44
|
`;
|
|
45
45
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=dsh-apis-plugin]") === null) {
|
|
46
46
|
const tag = document.createElement("style");
|
|
@@ -81,7 +81,6 @@ window.__ModuleLoader__.load({
|
|
|
81
81
|
|
|
82
82
|
function ApisCard(props) {
|
|
83
83
|
const state = props.useApisCard((s) => s);
|
|
84
|
-
const [draft, setDraft] = react.useState(null); // string[],null 表示未编辑
|
|
85
84
|
const [dirDraft, setDirDraft] = react.useState(null); // 文档目录,null 表示未编辑
|
|
86
85
|
const [saving, setSaving] = react.useState(false);
|
|
87
86
|
const [loading, setLoading] = react.useState(false); // 加载API 扫描中
|
|
@@ -90,21 +89,18 @@ window.__ModuleLoader__.load({
|
|
|
90
89
|
const [open, setOpen] = react.useState(false); // 折叠态只显示标题行
|
|
91
90
|
const ready = state.status === "ready" && state.writable;
|
|
92
91
|
|
|
93
|
-
/**
|
|
94
|
-
const list =
|
|
95
|
-
const dirty = draft !== null;
|
|
92
|
+
/** 只读展示的接口列表:来自 api.cfg(服务端扫描回写) */
|
|
93
|
+
const list = (state.value?.endpoints ?? []).map(String);
|
|
96
94
|
/** 当前展示的文档目录:编辑中取 dirDraft,否则取已保存值 */
|
|
97
95
|
const savedDir = state.value?.apiDir ?? "";
|
|
98
96
|
const shownDir = dirDraft ?? savedDir;
|
|
99
97
|
const dirDirty = dirDraft !== null && dirDraft !== savedDir;
|
|
100
98
|
|
|
101
99
|
async function save() {
|
|
102
|
-
if (saving || !ready ||
|
|
100
|
+
if (saving || !ready || !dirDirty) return;
|
|
103
101
|
setSaving(true);
|
|
104
|
-
|
|
105
|
-
if (dirty) await scope.set("endpoints", draft);
|
|
102
|
+
await scope.set("apiDir", dirDraft); // 只存目录,扫描交给「加载API」
|
|
106
103
|
setSaving(false);
|
|
107
|
-
setDraft(null);
|
|
108
104
|
setDirDraft(null);
|
|
109
105
|
setSaved(true); // 显示「已保存」提示,2 秒后消失
|
|
110
106
|
setTimeout(() => setSaved(false), 2000);
|
|
@@ -125,21 +121,12 @@ window.__ModuleLoader__.load({
|
|
|
125
121
|
}
|
|
126
122
|
}
|
|
127
123
|
|
|
128
|
-
//
|
|
124
|
+
// 只读单行:接口N 标签 + 路径文本
|
|
129
125
|
const row = (v, i) => react_jsx_runtime.jsxs("div", {
|
|
130
|
-
className: "apis-
|
|
126
|
+
className: "apis-item",
|
|
131
127
|
children: [
|
|
132
128
|
react_jsx_runtime.jsx("div", { className: "apis-rowLabel", children: `接口${i + 1}` }),
|
|
133
|
-
react_jsx_runtime.jsx("
|
|
134
|
-
value: v, disabled: !ready || saving,
|
|
135
|
-
onChange: (e) => setDraft(list.map((x, j) => (j === i ? e.target.value : x))),
|
|
136
|
-
className: "apis-input",
|
|
137
|
-
}),
|
|
138
|
-
react_jsx_runtime.jsx("button", {
|
|
139
|
-
type: "button", disabled: !ready || saving, "aria-label": `删除接口${i + 1}`,
|
|
140
|
-
onClick: () => setDraft(list.filter((_, j) => j !== i)),
|
|
141
|
-
className: "apis-remove", children: "✕",
|
|
142
|
-
}),
|
|
129
|
+
react_jsx_runtime.jsx("span", { className: "apis-itemText", title: v, children: v }),
|
|
143
130
|
],
|
|
144
131
|
});
|
|
145
132
|
|
|
@@ -156,7 +143,7 @@ window.__ModuleLoader__.load({
|
|
|
156
143
|
children: [
|
|
157
144
|
react_jsx_runtime.jsxs("div", { className: "apis-headText", children: [
|
|
158
145
|
react_jsx_runtime.jsx("span", { className: "apis-name", children: "通用接口管理插件" }),
|
|
159
|
-
react_jsx_runtime.jsx("span", { className: "apis-description", children: "
|
|
146
|
+
react_jsx_runtime.jsx("span", { className: "apis-description", children: "接口列表(来源:api.cfg)" }),
|
|
160
147
|
] }),
|
|
161
148
|
react_jsx_runtime.jsx(Chevron, { open }),
|
|
162
149
|
],
|
|
@@ -181,22 +168,17 @@ window.__ModuleLoader__.load({
|
|
|
181
168
|
] }),
|
|
182
169
|
react_jsx_runtime.jsx("hr", { className: "apis-hr" }),
|
|
183
170
|
!state.writable && react_jsx_runtime.jsx("span", { className: "apis-description", children: "只读" }),
|
|
184
|
-
list.map(row),
|
|
185
|
-
react_jsx_runtime.jsx("button", {
|
|
186
|
-
type: "button", disabled: !ready || saving,
|
|
187
|
-
onClick: () => setDraft([...list, ""]),
|
|
188
|
-
className: "apis-add", children: "+ 添加接口",
|
|
189
|
-
}),
|
|
171
|
+
list.length ? list.map(row) : react_jsx_runtime.jsx("span", { className: "apis-empty", children: "暂无接口:请在 API 目录下的 api.cfg 中配置 apis,然后点击「加载API」" }),
|
|
190
172
|
react_jsx_runtime.jsxs("div", { className: "apis-footer", children: [
|
|
191
173
|
// 保存成功后的短暂提示(占按钮行左侧)
|
|
192
174
|
react_jsx_runtime.jsx("span", { className: "apis-saved", children: saved ? "已保存" : "" }),
|
|
193
175
|
react_jsx_runtime.jsx("button", {
|
|
194
|
-
type: "button", disabled:
|
|
195
|
-
onClick: () =>
|
|
176
|
+
type: "button", disabled: !dirDirty || saving,
|
|
177
|
+
onClick: () => setDirDraft(null),
|
|
196
178
|
className: "apis-btn apis-discard", children: "放弃修改",
|
|
197
179
|
}),
|
|
198
180
|
react_jsx_runtime.jsx("button", {
|
|
199
|
-
type: "button", disabled: !ready ||
|
|
181
|
+
type: "button", disabled: !ready || !dirDirty || saving,
|
|
200
182
|
onClick: save, children: saving ? "保存中…" : "保存",
|
|
201
183
|
className: "apis-btn apis-save",
|
|
202
184
|
}),
|