@goodandready/dsh-clinebot 0.3.10 → 0.3.11
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/CHANGELOG.md +6 -0
- package/lib/client.js +123 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ All notable changes to `@goodandready/dsh-clinebot` will be documented in this f
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.11] - 2026-09-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **One-Click In-App Update UI & Live Status Banner** (Gitea Issue #39): Rendered interactive update status bar in `PluginCard` / `SettingsPage`. Displays current installed version, live npm registry check indicator, and up-to-date status badge. Automatically reveals warning badge and 'Update Now' button (`update.btn`) when a newer version is released, triggering safe POST `/dsh-clinebot/update` with `x-dsh-plugin-update: 1` header and completion guidance.
|
|
12
|
+
- **Client Test Coverage Expansion**: Added component render assertions for one-click update UI elements and updated test state table to 41 passing unit tests.
|
|
13
|
+
|
|
8
14
|
## [0.3.10] - 2026-09-16
|
|
9
15
|
|
|
10
16
|
### Changed
|
package/lib/client.js
CHANGED
|
@@ -94,6 +94,8 @@ const en = {
|
|
|
94
94
|
'update.updating': 'Updating…',
|
|
95
95
|
'update.done': 'Successfully updated to v{version}! Please restart DSH.',
|
|
96
96
|
'update.failed': 'Update failed: {error}',
|
|
97
|
+
'update.checking': 'Checking for updates…',
|
|
98
|
+
'update.up_to_date': 'Up to date',
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
const zh = {
|
|
@@ -181,6 +183,8 @@ const en = {
|
|
|
181
183
|
'update.updating': '正在更新…',
|
|
182
184
|
'update.done': '更新成功至 v{version}!请重启 DSH 服务生效。',
|
|
183
185
|
'update.failed': '更新失败:{error}',
|
|
186
|
+
'update.checking': '正在检查更新…',
|
|
187
|
+
'update.up_to_date': '已是最新版本',
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
function makeT(dict, fallback) {
|
|
@@ -418,6 +422,18 @@ const en = {
|
|
|
418
422
|
const [msg, setMsg] = React.useState('')
|
|
419
423
|
const [smokeResult, setSmokeResult] = React.useState(null)
|
|
420
424
|
|
|
425
|
+
// Plugin in-app updater state
|
|
426
|
+
const [updateState, setUpdateState] = React.useState({
|
|
427
|
+
checking: false,
|
|
428
|
+
updating: false,
|
|
429
|
+
currentVersion: '0.3.10',
|
|
430
|
+
latestVersion: '',
|
|
431
|
+
updateAvailable: false,
|
|
432
|
+
canAutoUpdate: true,
|
|
433
|
+
error: '',
|
|
434
|
+
notice: '',
|
|
435
|
+
})
|
|
436
|
+
|
|
421
437
|
// Key input state
|
|
422
438
|
const [apiKeyInput, setApiKeyInput] = React.useState('')
|
|
423
439
|
const [showKey, setShowKey] = React.useState(false)
|
|
@@ -439,6 +455,58 @@ const en = {
|
|
|
439
455
|
load().catch((e) => setErr(String(e.message || e)))
|
|
440
456
|
}, [load])
|
|
441
457
|
|
|
458
|
+
const checkUpdate = React.useCallback(async () => {
|
|
459
|
+
setUpdateState((s) => ({ ...s, checking: true, error: '' }))
|
|
460
|
+
try {
|
|
461
|
+
const res = await fetch(`${ROUTE_PREFIX}/update`)
|
|
462
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
463
|
+
const data = await res.json().catch(() => ({}))
|
|
464
|
+
setUpdateState((s) => ({
|
|
465
|
+
...s,
|
|
466
|
+
checking: false,
|
|
467
|
+
currentVersion: data.currentVersion || s.currentVersion,
|
|
468
|
+
latestVersion: data.latestVersion || '',
|
|
469
|
+
updateAvailable: !!data.updateAvailable,
|
|
470
|
+
canAutoUpdate: data.canAutoUpdate !== false,
|
|
471
|
+
}))
|
|
472
|
+
} catch (_) {
|
|
473
|
+
setUpdateState((s) => ({ ...s, checking: false }))
|
|
474
|
+
}
|
|
475
|
+
}, [])
|
|
476
|
+
|
|
477
|
+
React.useEffect(() => {
|
|
478
|
+
checkUpdate()
|
|
479
|
+
}, [checkUpdate])
|
|
480
|
+
|
|
481
|
+
async function handleTriggerUpdate() {
|
|
482
|
+
if (updateState.updating) return
|
|
483
|
+
setUpdateState((s) => ({ ...s, updating: true, error: '', notice: '' }))
|
|
484
|
+
try {
|
|
485
|
+
const res = await fetch(`${ROUTE_PREFIX}/update`, {
|
|
486
|
+
method: 'POST',
|
|
487
|
+
headers: { 'x-dsh-plugin-update': '1' },
|
|
488
|
+
})
|
|
489
|
+
const data = await res.json().catch(() => ({}))
|
|
490
|
+
if (!res.ok || data.ok === false || data.error) {
|
|
491
|
+
throw new Error(data.error || `HTTP ${res.status}`)
|
|
492
|
+
}
|
|
493
|
+
const newVer = data.updatedVersion || updateState.latestVersion || updateState.currentVersion
|
|
494
|
+
setUpdateState((s) => ({
|
|
495
|
+
...s,
|
|
496
|
+
updating: false,
|
|
497
|
+
updateAvailable: false,
|
|
498
|
+
currentVersion: newVer,
|
|
499
|
+
notice: t('update.done', { version: newVer }),
|
|
500
|
+
}))
|
|
501
|
+
} catch (e) {
|
|
502
|
+
setUpdateState((s) => ({
|
|
503
|
+
...s,
|
|
504
|
+
updating: false,
|
|
505
|
+
error: t('update.failed', { error: e.message || String(e) }),
|
|
506
|
+
}))
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
442
510
|
React.useEffect(() => {
|
|
443
511
|
if (snapshotStatus === 'ready' && snapshot?.value && typeof snapshot.value === 'object') {
|
|
444
512
|
setDraft((prev) => (prev ? { ...prev, ...snapshot.value } : snapshot.value))
|
|
@@ -772,6 +840,60 @@ const en = {
|
|
|
772
840
|
)
|
|
773
841
|
),
|
|
774
842
|
|
|
843
|
+
// In-app Update Bar
|
|
844
|
+
React.createElement(
|
|
845
|
+
'div',
|
|
846
|
+
{
|
|
847
|
+
className: 'cb-row',
|
|
848
|
+
style: {
|
|
849
|
+
padding: '10px 14px',
|
|
850
|
+
borderRadius: '8px',
|
|
851
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
852
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
853
|
+
justifyContent: 'space-between',
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
React.createElement(
|
|
857
|
+
'div',
|
|
858
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' } },
|
|
859
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)', fontWeight: 500 } },
|
|
860
|
+
`v${updateState.currentVersion}`
|
|
861
|
+
),
|
|
862
|
+
updateState.checking
|
|
863
|
+
? React.createElement('span', { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: '12px' } },
|
|
864
|
+
t('update.checking')
|
|
865
|
+
)
|
|
866
|
+
: updateState.updateAvailable
|
|
867
|
+
? React.createElement('span', { className: 'cb-badge cb-badge-warn' },
|
|
868
|
+
t('update.available', { latestVersion: updateState.latestVersion, currentVersion: updateState.currentVersion })
|
|
869
|
+
)
|
|
870
|
+
: React.createElement('span', { className: 'cb-badge cb-badge-ok' },
|
|
871
|
+
'✓ ' + t('update.up_to_date')
|
|
872
|
+
)
|
|
873
|
+
),
|
|
874
|
+
updateState.updateAvailable
|
|
875
|
+
? React.createElement(
|
|
876
|
+
'button',
|
|
877
|
+
{
|
|
878
|
+
type: 'button',
|
|
879
|
+
className: 'cb-btn cb-btn-primary',
|
|
880
|
+
disabled: updateState.updating,
|
|
881
|
+
onClick: handleTriggerUpdate,
|
|
882
|
+
style: { padding: '5px 12px', fontSize: '12px' }
|
|
883
|
+
},
|
|
884
|
+
updateState.updating ? t('update.updating') : t('update.btn')
|
|
885
|
+
)
|
|
886
|
+
: null
|
|
887
|
+
),
|
|
888
|
+
|
|
889
|
+
// Update notice & error banners
|
|
890
|
+
updateState.notice
|
|
891
|
+
? React.createElement('div', { className: 'cb-alert-ok' }, '✓ ' + updateState.notice)
|
|
892
|
+
: null,
|
|
893
|
+
updateState.error
|
|
894
|
+
? React.createElement('div', { className: 'cb-alert-err' }, updateState.error)
|
|
895
|
+
: null,
|
|
896
|
+
|
|
775
897
|
// Notifications
|
|
776
898
|
err ? React.createElement('div', { className: 'cb-alert-bad' }, err) : null,
|
|
777
899
|
msg ? React.createElement('div', { className: 'cb-alert-ok' }, msg) : null,
|
|
@@ -814,7 +936,7 @@ const en = {
|
|
|
814
936
|
{
|
|
815
937
|
type: 'button',
|
|
816
938
|
className: 'cb-btn cb-btn-primary',
|
|
817
|
-
disabled: !!busy || !apiKeyInput.trim(),
|
|
939
|
+
disabled: !!busy || !String(apiKeyInput || '').trim(),
|
|
818
940
|
onClick: handleSaveKey,
|
|
819
941
|
},
|
|
820
942
|
busy === 'save-key' ? t('key.saving') : t('key.save')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
4
4
|
"description": "DeepSeek Harness companion for ClineBot / ClinePass: dynamic subscription models sync, quota exhaustion warnings, session metrics, dedicated settings page, live usage limits, and /cline slash-command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|