apiskill 0.1.5 → 0.1.6
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 +10 -0
- package/README.zh.md +12 -0
- package/dist/assets/index-B_wAJLu_.js +324 -0
- package/dist/assets/index-CHOQ6xhP.css +1 -0
- package/dist/index.html +2 -2
- package/docs/cli.md +13 -0
- package/docs/cli.zh.md +13 -0
- package/docs/web.md +2 -0
- package/docs/web.zh.md +2 -0
- package/package.json +3 -3
- package/scripts/apiskill-cli.mjs +46 -2
- package/scripts/lib/cache-paths.mjs +94 -3
- package/scripts/test-shared-cache.mjs +42 -7
- package/src/App.tsx +29 -0
- package/src/CacheSettingsDialog.tsx +166 -0
- package/src/DocumentVersionManager.tsx +47 -1
- package/src/styles.css +119 -0
- package/src/swagger.ts +2 -0
- package/vite.config.ts +92 -1
- package/dist/assets/index-CV6e3HAP.js +0 -299
- package/dist/assets/index-slFOragU.css +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { AlertCircle, FolderCog, RotateCcw, Save, X } from 'lucide-react';
|
|
3
|
+
import { CACHE_SETTINGS_API_URL } from './swagger';
|
|
4
|
+
|
|
5
|
+
export type CacheSettings = {
|
|
6
|
+
activeCacheDir: string;
|
|
7
|
+
defaultCacheDir: string;
|
|
8
|
+
configuredCacheDir: string;
|
|
9
|
+
builtInCacheDir: string;
|
|
10
|
+
environmentCacheDir: string;
|
|
11
|
+
environmentOverride: boolean;
|
|
12
|
+
effectiveNextCacheDir: string;
|
|
13
|
+
source: 'environment' | 'config' | 'default';
|
|
14
|
+
configPath: string;
|
|
15
|
+
restartRequired: boolean;
|
|
16
|
+
activeMatchesDefault: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function CacheSettingsDialog({
|
|
20
|
+
settings,
|
|
21
|
+
onSettingsChange,
|
|
22
|
+
}: {
|
|
23
|
+
settings?: CacheSettings;
|
|
24
|
+
onSettingsChange: (settings: CacheSettings) => void;
|
|
25
|
+
}) {
|
|
26
|
+
const [open, setOpen] = useState(false);
|
|
27
|
+
const [cacheDir, setCacheDir] = useState('');
|
|
28
|
+
const [loading, setLoading] = useState(false);
|
|
29
|
+
const [message, setMessage] = useState('');
|
|
30
|
+
const [error, setError] = useState('');
|
|
31
|
+
|
|
32
|
+
async function openDialog() {
|
|
33
|
+
setOpen(true);
|
|
34
|
+
setMessage('');
|
|
35
|
+
setError('');
|
|
36
|
+
setLoading(true);
|
|
37
|
+
try {
|
|
38
|
+
const next = await loadCacheSettings();
|
|
39
|
+
onSettingsChange(next);
|
|
40
|
+
setCacheDir(next.defaultCacheDir);
|
|
41
|
+
} catch (loadError) {
|
|
42
|
+
setError(loadError instanceof Error ? loadError.message : '缓存设置读取失败');
|
|
43
|
+
} finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function saveSettings(reset = false) {
|
|
49
|
+
setLoading(true);
|
|
50
|
+
setMessage('');
|
|
51
|
+
setError('');
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(CACHE_SETTINGS_API_URL, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: { 'content-type': 'application/json' },
|
|
56
|
+
body: JSON.stringify(reset ? { reset: true } : { cacheDir }),
|
|
57
|
+
});
|
|
58
|
+
const payload = await response.json();
|
|
59
|
+
if (!response.ok) throw new Error(payload.error || '缓存设置保存失败');
|
|
60
|
+
const next = payload as CacheSettings;
|
|
61
|
+
onSettingsChange(next);
|
|
62
|
+
setCacheDir(next.defaultCacheDir);
|
|
63
|
+
setMessage(reset ? '已恢复内置默认地址,现有缓存文件未改动' : '默认缓存地址已保存,现有缓存文件未改动');
|
|
64
|
+
} catch (saveError) {
|
|
65
|
+
setError(saveError instanceof Error ? saveError.message : '缓存设置保存失败');
|
|
66
|
+
} finally {
|
|
67
|
+
setLoading(false);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<>
|
|
73
|
+
<button className="secondary-button cache-settings-trigger" onClick={openDialog} title="设置接口文档默认缓存地址">
|
|
74
|
+
<FolderCog size={16} />
|
|
75
|
+
缓存设置
|
|
76
|
+
</button>
|
|
77
|
+
|
|
78
|
+
{open ? (
|
|
79
|
+
<div className="modal-backdrop" role="dialog" aria-modal="true" aria-labelledby="cache-settings-title">
|
|
80
|
+
<div className="cache-settings-dialog">
|
|
81
|
+
<div className="import-dialog-header">
|
|
82
|
+
<div>
|
|
83
|
+
<p className="eyebrow">Cache Settings</p>
|
|
84
|
+
<h2 id="cache-settings-title">接口文档缓存设置</h2>
|
|
85
|
+
</div>
|
|
86
|
+
<button className="icon-secondary-button" onClick={() => setOpen(false)} aria-label="关闭">
|
|
87
|
+
<X size={17} />
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div className="cache-settings-body">
|
|
92
|
+
{error ? <div className="version-message error">{error}</div> : null}
|
|
93
|
+
{message ? <div className="version-message">{message}</div> : null}
|
|
94
|
+
|
|
95
|
+
<div className="cache-location-row">
|
|
96
|
+
<span>当前会话缓存位置</span>
|
|
97
|
+
<code>{settings?.activeCacheDir || (loading ? '读取中...' : '-')}</code>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<label className="cache-settings-field">
|
|
101
|
+
默认缓存地址
|
|
102
|
+
<input
|
|
103
|
+
value={cacheDir}
|
|
104
|
+
onChange={(event) => setCacheDir(event.target.value)}
|
|
105
|
+
placeholder="例如 ~/.apiskill/cache"
|
|
106
|
+
autoComplete="off"
|
|
107
|
+
disabled={loading}
|
|
108
|
+
/>
|
|
109
|
+
</label>
|
|
110
|
+
|
|
111
|
+
<div className="cache-settings-notice">
|
|
112
|
+
<AlertCircle size={18} />
|
|
113
|
+
<div>
|
|
114
|
+
<strong>修改地址不会移动或删除已有缓存文件</strong>
|
|
115
|
+
<p>新的 CLI、MCP 进程会读取该默认地址;Web 端重启后切换。需要保留的版本可在版本管理中使用“更新缓存地址”复制过去。</p>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{settings?.environmentOverride ? (
|
|
120
|
+
<div className="cache-settings-notice warning">
|
|
121
|
+
<AlertCircle size={18} />
|
|
122
|
+
<div>
|
|
123
|
+
<strong>环境变量正在覆盖默认设置</strong>
|
|
124
|
+
<p>当前 `APISKILL_CACHE_DIR` 为 {settings.environmentCacheDir}。只要该变量存在,重启后仍优先使用它。</p>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
) : null}
|
|
128
|
+
|
|
129
|
+
<dl className="cache-settings-details">
|
|
130
|
+
<div>
|
|
131
|
+
<dt>内置默认地址</dt>
|
|
132
|
+
<dd>{settings?.builtInCacheDir || '-'}</dd>
|
|
133
|
+
</div>
|
|
134
|
+
<div>
|
|
135
|
+
<dt>配置文件</dt>
|
|
136
|
+
<dd>{settings?.configPath || '-'}</dd>
|
|
137
|
+
</div>
|
|
138
|
+
</dl>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="import-dialog-actions cache-settings-actions">
|
|
142
|
+
<button className="secondary-button" onClick={() => void saveSettings(true)} disabled={loading}>
|
|
143
|
+
<RotateCcw size={16} />
|
|
144
|
+
恢复默认
|
|
145
|
+
</button>
|
|
146
|
+
<div>
|
|
147
|
+
<button className="secondary-button" onClick={() => setOpen(false)} disabled={loading}>取消</button>
|
|
148
|
+
<button className="primary-button" onClick={() => void saveSettings(false)} disabled={loading || !cacheDir.trim()}>
|
|
149
|
+
<Save size={16} />
|
|
150
|
+
{loading ? '保存中' : '保存设置'}
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
) : null}
|
|
157
|
+
</>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function loadCacheSettings(): Promise<CacheSettings> {
|
|
162
|
+
const response = await fetch(CACHE_SETTINGS_API_URL, { cache: 'no-store' });
|
|
163
|
+
const payload = await response.json();
|
|
164
|
+
if (!response.ok) throw new Error(payload.error || '缓存设置读取失败');
|
|
165
|
+
return payload as CacheSettings;
|
|
166
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { Check, Download, Pencil, RefreshCcw, Server, Trash2, X } from 'lucide-react';
|
|
2
|
+
import { Check, Download, FolderSync, Pencil, RefreshCcw, Server, Trash2, X } from 'lucide-react';
|
|
3
|
+
import type { CacheSettings } from './CacheSettingsDialog';
|
|
3
4
|
import { VERSION_EXPORT_API_URL } from './swagger';
|
|
4
5
|
|
|
5
6
|
export type DocumentVersionMeta = {
|
|
@@ -14,6 +15,7 @@ export type DocumentVersionMeta = {
|
|
|
14
15
|
schemas?: number;
|
|
15
16
|
environmentName?: string;
|
|
16
17
|
environmentBaseUrl?: string;
|
|
18
|
+
savedPath?: string;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
export function DocumentVersionManager({
|
|
@@ -25,6 +27,8 @@ export function DocumentVersionManager({
|
|
|
25
27
|
onPrepareUpdate,
|
|
26
28
|
onUpdateEnvironment,
|
|
27
29
|
onDelete,
|
|
30
|
+
cacheSettings,
|
|
31
|
+
onSyncCache,
|
|
28
32
|
}: {
|
|
29
33
|
versions: DocumentVersionMeta[];
|
|
30
34
|
selectedVersionId: string;
|
|
@@ -34,6 +38,12 @@ export function DocumentVersionManager({
|
|
|
34
38
|
onPrepareUpdate: (version: DocumentVersionMeta) => Promise<void> | void;
|
|
35
39
|
onUpdateEnvironment: (versionId: string, environmentName: string, environmentBaseUrl: string) => Promise<void>;
|
|
36
40
|
onDelete: (versionId: string) => Promise<void>;
|
|
41
|
+
cacheSettings?: CacheSettings;
|
|
42
|
+
onSyncCache: (versionId: string) => Promise<{
|
|
43
|
+
status: 'synced' | 'already-current';
|
|
44
|
+
targetCacheDir: string;
|
|
45
|
+
targetDocumentPath: string;
|
|
46
|
+
}>;
|
|
37
47
|
}) {
|
|
38
48
|
const [open, setOpen] = useState(false);
|
|
39
49
|
const [editingVersionId, setEditingVersionId] = useState('');
|
|
@@ -118,6 +128,38 @@ export function DocumentVersionManager({
|
|
|
118
128
|
document.body.removeChild(anchor);
|
|
119
129
|
}
|
|
120
130
|
|
|
131
|
+
async function syncVersionCache(version: DocumentVersionMeta) {
|
|
132
|
+
const versionId = version.versionId;
|
|
133
|
+
if (!versionId) return;
|
|
134
|
+
if (!cacheSettings) {
|
|
135
|
+
setMessage('缓存设置尚未读取,请稍后重试');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const sourceLocation = version.savedPath || cacheSettings.activeCacheDir;
|
|
140
|
+
const targetLocation = cacheSettings.defaultCacheDir;
|
|
141
|
+
if (cacheSettings.activeMatchesDefault) {
|
|
142
|
+
window.alert(`当前缓存位置:\n${sourceLocation}\n\n该版本已位于默认缓存地址:\n${targetLocation}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const confirmed = window.confirm(
|
|
147
|
+
`当前缓存位置:\n${sourceLocation}\n\n默认缓存地址:\n${targetLocation}\n\n确认将该文档版本复制到默认缓存地址吗?源文件不会被移动或删除;目标中同版本内容将被更新。`,
|
|
148
|
+
);
|
|
149
|
+
if (!confirmed) return;
|
|
150
|
+
|
|
151
|
+
setBusyVersionId(versionId);
|
|
152
|
+
setMessage('');
|
|
153
|
+
try {
|
|
154
|
+
const result = await onSyncCache(versionId);
|
|
155
|
+
setMessage(result.status === 'already-current' ? '该版本已位于默认缓存地址' : `已同步到 ${result.targetDocumentPath}`);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
setMessage(error instanceof Error ? error.message : '更新缓存地址失败');
|
|
158
|
+
} finally {
|
|
159
|
+
setBusyVersionId('');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
121
163
|
return (
|
|
122
164
|
<div className="version-manager">
|
|
123
165
|
<button className="version-manager-trigger" onClick={() => setOpen(true)} disabled={disabled || !versions.length}>
|
|
@@ -231,6 +273,10 @@ export function DocumentVersionManager({
|
|
|
231
273
|
<Download size={15} />
|
|
232
274
|
导出
|
|
233
275
|
</button>
|
|
276
|
+
<button className="secondary-button" onClick={() => void syncVersionCache(version)} disabled={busy || !versionId}>
|
|
277
|
+
<FolderSync size={15} />
|
|
278
|
+
更新缓存地址
|
|
279
|
+
</button>
|
|
234
280
|
<button className="danger-button" onClick={() => deleteVersion(version)} disabled={busy || !versionId}>
|
|
235
281
|
<Trash2 size={15} />
|
|
236
282
|
删除
|
package/src/styles.css
CHANGED
|
@@ -75,7 +75,10 @@ p {
|
|
|
75
75
|
.topbar-actions {
|
|
76
76
|
display: flex;
|
|
77
77
|
align-items: center;
|
|
78
|
+
justify-content: flex-end;
|
|
79
|
+
flex-wrap: wrap;
|
|
78
80
|
gap: 14px;
|
|
81
|
+
min-width: 0;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
.version-switcher {
|
|
@@ -1542,6 +1545,110 @@ td code {
|
|
|
1542
1545
|
box-shadow: 0 18px 60px rgba(18, 25, 35, 0.22);
|
|
1543
1546
|
}
|
|
1544
1547
|
|
|
1548
|
+
.cache-settings-dialog {
|
|
1549
|
+
width: min(680px, 100%);
|
|
1550
|
+
max-height: calc(100vh - 40px);
|
|
1551
|
+
overflow: auto;
|
|
1552
|
+
border: 1px solid #d7dde4;
|
|
1553
|
+
border-radius: 8px;
|
|
1554
|
+
background: #fff;
|
|
1555
|
+
box-shadow: 0 18px 60px rgba(18, 25, 35, 0.22);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
.cache-settings-body {
|
|
1559
|
+
display: grid;
|
|
1560
|
+
gap: 16px;
|
|
1561
|
+
padding: 18px 20px;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
.cache-location-row,
|
|
1565
|
+
.cache-settings-details > div {
|
|
1566
|
+
display: grid;
|
|
1567
|
+
grid-template-columns: 150px minmax(0, 1fr);
|
|
1568
|
+
align-items: start;
|
|
1569
|
+
gap: 14px;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
.cache-location-row > span,
|
|
1573
|
+
.cache-settings-details dt {
|
|
1574
|
+
color: #657181;
|
|
1575
|
+
font-size: 12px;
|
|
1576
|
+
font-weight: 800;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
.cache-location-row code,
|
|
1580
|
+
.cache-settings-details dd {
|
|
1581
|
+
min-width: 0;
|
|
1582
|
+
margin: 0;
|
|
1583
|
+
overflow-wrap: anywhere;
|
|
1584
|
+
color: #1d2a36;
|
|
1585
|
+
font-size: 12px;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
.cache-settings-field {
|
|
1589
|
+
display: grid;
|
|
1590
|
+
gap: 7px;
|
|
1591
|
+
color: #334152;
|
|
1592
|
+
font-size: 12px;
|
|
1593
|
+
font-weight: 800;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
.cache-settings-field input {
|
|
1597
|
+
width: 100%;
|
|
1598
|
+
min-height: 40px;
|
|
1599
|
+
padding: 0 11px;
|
|
1600
|
+
border: 1px solid #bdc8d4;
|
|
1601
|
+
border-radius: 6px;
|
|
1602
|
+
outline: 0;
|
|
1603
|
+
color: #15202c;
|
|
1604
|
+
background: #fff;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
.cache-settings-field input:focus {
|
|
1608
|
+
border-color: #1b6b65;
|
|
1609
|
+
box-shadow: 0 0 0 3px rgba(27, 107, 101, 0.12);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
.cache-settings-notice {
|
|
1613
|
+
display: grid;
|
|
1614
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
1615
|
+
gap: 10px;
|
|
1616
|
+
padding: 12px 0;
|
|
1617
|
+
border-block: 1px solid #dbe7e4;
|
|
1618
|
+
color: #285c55;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
.cache-settings-notice.warning {
|
|
1622
|
+
border-color: #ead9ad;
|
|
1623
|
+
color: #765b16;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
.cache-settings-notice strong {
|
|
1627
|
+
font-size: 13px;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
.cache-settings-notice p {
|
|
1631
|
+
margin: 4px 0 0;
|
|
1632
|
+
color: #657181;
|
|
1633
|
+
font-size: 12px;
|
|
1634
|
+
line-height: 1.55;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
.cache-settings-details {
|
|
1638
|
+
display: grid;
|
|
1639
|
+
gap: 9px;
|
|
1640
|
+
margin: 0;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
.cache-settings-actions {
|
|
1644
|
+
justify-content: space-between;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
.cache-settings-actions > div {
|
|
1648
|
+
display: flex;
|
|
1649
|
+
gap: 8px;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1545
1652
|
.api-config-dialog {
|
|
1546
1653
|
width: 80vw;
|
|
1547
1654
|
min-width: 1000px;
|
|
@@ -2260,6 +2367,18 @@ td code {
|
|
|
2260
2367
|
gap: 8px;
|
|
2261
2368
|
}
|
|
2262
2369
|
|
|
2370
|
+
.cache-location-row,
|
|
2371
|
+
.cache-settings-details > div {
|
|
2372
|
+
grid-template-columns: 1fr;
|
|
2373
|
+
gap: 5px;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
.cache-settings-actions,
|
|
2377
|
+
.cache-settings-actions > div {
|
|
2378
|
+
display: grid;
|
|
2379
|
+
width: 100%;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2263
2382
|
.endpoint-title-line {
|
|
2264
2383
|
align-items: flex-start;
|
|
2265
2384
|
flex-direction: column;
|
package/src/swagger.ts
CHANGED
|
@@ -19,6 +19,8 @@ export const VERSION_SELECT_API_URL = '/api/openapi/version-select';
|
|
|
19
19
|
export const VERSION_META_API_URL = '/api/openapi/version-meta';
|
|
20
20
|
export const VERSION_DELETE_API_URL = '/api/openapi/version-delete';
|
|
21
21
|
export const VERSION_EXPORT_API_URL = '/api/openapi/version-export';
|
|
22
|
+
export const VERSION_SYNC_CACHE_API_URL = '/api/openapi/version-sync-cache';
|
|
23
|
+
export const CACHE_SETTINGS_API_URL = '/api/cache-settings';
|
|
22
24
|
export const MOCK_START_API_URL = '/api/mock/start';
|
|
23
25
|
export const MOCK_STATUS_API_URL = '/api/mock/status';
|
|
24
26
|
export const MOCK_STOP_API_URL = '/api/mock/stop';
|
package/vite.config.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
openApiStats,
|
|
16
16
|
} from './src/manualApiConfig';
|
|
17
17
|
import { startMockServer } from './scripts/lib/mock-server.mjs';
|
|
18
|
-
import { getDefaultCacheDir } from './scripts/lib/cache-paths.mjs';
|
|
18
|
+
import { getCacheSettings, getDefaultCacheDir, resetDefaultCacheDir, setDefaultCacheDir } from './scripts/lib/cache-paths.mjs';
|
|
19
19
|
|
|
20
20
|
const appRootDir = process.env.APISKILL_ROOT || process.cwd();
|
|
21
21
|
const cacheDir = getDefaultCacheDir();
|
|
@@ -91,6 +91,26 @@ function openapiImportPlugin() {
|
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
server.middlewares.use('/api/cache-settings', async (req, res) => {
|
|
95
|
+
try {
|
|
96
|
+
if (req.method === 'GET') {
|
|
97
|
+
sendJson(res, 200, webCacheSettings());
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (req.method !== 'POST') {
|
|
101
|
+
sendJson(res, 405, { error: 'Method Not Allowed' });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const body = (await readJsonBody(req)) as { cacheDir?: unknown; reset?: boolean };
|
|
106
|
+
if (body.reset) await resetDefaultCacheDir();
|
|
107
|
+
else await setDefaultCacheDir(requiredText(body.cacheDir, '请输入默认缓存地址'));
|
|
108
|
+
sendJson(res, 200, webCacheSettings());
|
|
109
|
+
} catch (error) {
|
|
110
|
+
sendJson(res, 400, { error: error instanceof Error ? error.message : 'Save cache settings failed' });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
94
114
|
server.middlewares.use('/api/openapi/version-select', async (req, res) => {
|
|
95
115
|
if (req.method !== 'POST') {
|
|
96
116
|
sendJson(res, 405, { error: 'Method Not Allowed' });
|
|
@@ -197,6 +217,66 @@ function openapiImportPlugin() {
|
|
|
197
217
|
}
|
|
198
218
|
});
|
|
199
219
|
|
|
220
|
+
server.middlewares.use('/api/openapi/version-sync-cache', async (req, res) => {
|
|
221
|
+
if (req.method !== 'POST') {
|
|
222
|
+
sendJson(res, 405, { error: 'Method Not Allowed' });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const body = (await readJsonBody(req)) as { versionId?: string };
|
|
228
|
+
const versionId = requiredText(body.versionId, '请选择要同步的文档版本');
|
|
229
|
+
assertSafeVersionId(versionId);
|
|
230
|
+
const settings = getCacheSettings();
|
|
231
|
+
const targetCacheDir = settings.defaultCacheDir;
|
|
232
|
+
const payload = await readCachedDocument(versionId);
|
|
233
|
+
const sourceDocumentPath = typeof payload.meta?.savedPath === 'string'
|
|
234
|
+
? payload.meta.savedPath
|
|
235
|
+
: resolve(versionsDir, `${versionId}.json`);
|
|
236
|
+
|
|
237
|
+
if (resolve(targetCacheDir) === resolve(cacheDir)) {
|
|
238
|
+
sendJson(res, 200, {
|
|
239
|
+
status: 'already-current',
|
|
240
|
+
versionId,
|
|
241
|
+
sourceCacheDir: cacheDir,
|
|
242
|
+
sourceDocumentPath,
|
|
243
|
+
targetCacheDir,
|
|
244
|
+
targetDocumentPath: sourceDocumentPath,
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const targetVersionsDir = resolve(targetCacheDir, 'versions');
|
|
250
|
+
const targetDocumentPath = resolve(targetVersionsDir, `${versionId}.json`);
|
|
251
|
+
const targetMetaPath = resolve(targetVersionsDir, `${versionId}.meta.json`);
|
|
252
|
+
const syncedAt = new Date().toISOString();
|
|
253
|
+
const targetMeta = {
|
|
254
|
+
...(payload.meta ?? {}),
|
|
255
|
+
versionId,
|
|
256
|
+
savedFile: basename(targetDocumentPath),
|
|
257
|
+
savedPath: targetDocumentPath,
|
|
258
|
+
cacheSyncedAt: syncedAt,
|
|
259
|
+
cacheSyncedFrom: sourceDocumentPath,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
await mkdir(targetVersionsDir, { recursive: true });
|
|
263
|
+
await writeFile(targetDocumentPath, JSON.stringify(payload.document, null, 2));
|
|
264
|
+
await writeFile(targetMetaPath, JSON.stringify(targetMeta, null, 2));
|
|
265
|
+
await writeFile(resolve(targetCacheDir, 'latest-import.json'), JSON.stringify(targetMeta, null, 2));
|
|
266
|
+
sendJson(res, 200, {
|
|
267
|
+
status: 'synced',
|
|
268
|
+
versionId,
|
|
269
|
+
sourceCacheDir: cacheDir,
|
|
270
|
+
sourceDocumentPath,
|
|
271
|
+
targetCacheDir,
|
|
272
|
+
targetDocumentPath,
|
|
273
|
+
syncedAt,
|
|
274
|
+
});
|
|
275
|
+
} catch (error) {
|
|
276
|
+
sendJson(res, 400, { error: error instanceof Error ? error.message : 'Sync version cache failed' });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
200
280
|
server.middlewares.use('/api/openapi/auth-check', async (req, res) => {
|
|
201
281
|
if (req.method !== 'POST') {
|
|
202
282
|
sendJson(res, 405, { error: 'Method Not Allowed' });
|
|
@@ -536,6 +616,17 @@ function openapiImportPlugin() {
|
|
|
536
616
|
};
|
|
537
617
|
}
|
|
538
618
|
|
|
619
|
+
function webCacheSettings() {
|
|
620
|
+
const settings = getCacheSettings();
|
|
621
|
+
return {
|
|
622
|
+
...settings,
|
|
623
|
+
activeCacheDir: cacheDir,
|
|
624
|
+
effectiveNextCacheDir: settings.cacheDir,
|
|
625
|
+
restartRequired: resolve(cacheDir) !== resolve(settings.cacheDir),
|
|
626
|
+
activeMatchesDefault: resolve(cacheDir) === resolve(settings.defaultCacheDir),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
539
630
|
async function readCachedDocument(versionId?: string) {
|
|
540
631
|
if (versionId) {
|
|
541
632
|
assertSafeVersionId(versionId);
|