dsh-plugin-weapp-gateway 0.8.13 → 0.8.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/lib/index.mjs +1 -0
- package/lib/plugin-updates.mjs +103 -10
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
package/lib/plugin-updates.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const PLUGIN_PACKAGE_NAME = 'dsh-plugin-weapp-gateway'
|
|
2
2
|
const REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PLUGIN_PACKAGE_NAME}/latest`
|
|
3
|
+
const FAILED_INSTALL_STATES = new Set(['failed', 'cancelled', 'refused'])
|
|
3
4
|
|
|
4
5
|
function errorField(value) {
|
|
5
6
|
return typeof value === 'string' && value.trim() ? value.trim() : ''
|
|
@@ -22,6 +23,7 @@ export function formatPluginInstallError(error, depth = 0) {
|
|
|
22
23
|
if (detail) return `${code && code !== detail ? `${code}: ` : ''}${detail}`.slice(0, 1000)
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
if (code) return code
|
|
25
27
|
try {
|
|
26
28
|
const serialized = JSON.stringify(error, (key, value) => {
|
|
27
29
|
if (/stack|authorization|cookie|password|secret|token|credential|api.?key/i.test(key)) return undefined
|
|
@@ -29,7 +31,28 @@ export function formatPluginInstallError(error, depth = 0) {
|
|
|
29
31
|
})
|
|
30
32
|
if (serialized && serialized !== '{}') return serialized.slice(0, 1000)
|
|
31
33
|
} catch { /* use a generic message for cyclic or non-serializable errors */ }
|
|
32
|
-
return
|
|
34
|
+
return 'The plugin manager did not provide an error message'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function operationErrorCode(error, depth = 0) {
|
|
38
|
+
if (!error || typeof error !== 'object' || depth > 4) return ''
|
|
39
|
+
if (typeof error.code === 'string') return error.code
|
|
40
|
+
for (const nested of [error.error, error.cause, error.details]) {
|
|
41
|
+
const code = operationErrorCode(nested, depth + 1)
|
|
42
|
+
if (code) return code
|
|
43
|
+
}
|
|
44
|
+
return ''
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function failedOperation(operation) {
|
|
48
|
+
return FAILED_INSTALL_STATES.has(operation?.application) || Boolean(operation?.error)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function operationMessage(operation) {
|
|
52
|
+
if (operation?.error) return formatPluginInstallError(operation.error)
|
|
53
|
+
return typeof operation?.message === 'string' && operation.message.trim()
|
|
54
|
+
? operation.message.trim()
|
|
55
|
+
: `Plugin manager reported ${operation?.application || 'an unknown failure'}`
|
|
33
56
|
}
|
|
34
57
|
|
|
35
58
|
function parseVersion(value) {
|
|
@@ -97,11 +120,14 @@ export async function checkPluginUpdate({ currentVersion, fetchImpl = globalThis
|
|
|
97
120
|
}
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
export async function installLatestPlugin({ api, currentVersion, fetchImpl = globalThis.fetch, requestId }) {
|
|
123
|
+
export async function installLatestPlugin({ api, currentVersion, fetchImpl = globalThis.fetch, requestId, profileName }) {
|
|
101
124
|
const update = await checkPluginUpdate({ currentVersion, fetchImpl })
|
|
102
125
|
if (!update.updateAvailable) return { ...update, installed: false, reason: 'already-current' }
|
|
103
126
|
|
|
104
|
-
const
|
|
127
|
+
const profile = typeof profileName === 'string' && /^[A-Za-z0-9._-]{1,80}$/.test(profileName)
|
|
128
|
+
? profileName
|
|
129
|
+
: 'web'
|
|
130
|
+
const command = `dsh plugin --profile ${profile} add ${PLUGIN_PACKAGE_NAME}@${update.latestVersion}`
|
|
105
131
|
let inventory
|
|
106
132
|
try {
|
|
107
133
|
inventory = await api.plugins.inventory()
|
|
@@ -112,16 +138,83 @@ export async function installLatestPlugin({ api, currentVersion, fetchImpl = glo
|
|
|
112
138
|
return { ...update, installed: false, reason: 'plugin-manager-unavailable', command }
|
|
113
139
|
}
|
|
114
140
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
|
|
141
|
+
let bundles
|
|
142
|
+
try {
|
|
143
|
+
bundles = await api.plugins.listBundles()
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return { ...update, installed: false, reason: 'plugin-manager-unavailable', command, detail: error?.message }
|
|
146
|
+
}
|
|
147
|
+
if (!Array.isArray(bundles)) {
|
|
148
|
+
return { ...update, installed: false, reason: 'plugin-manager-unavailable', command }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const existing = bundles.find(bundle => bundle?.name === PLUGIN_PACKAGE_NAME && bundle.installed === true)
|
|
152
|
+
const installSpec = `${PLUGIN_PACKAGE_NAME}@${update.latestVersion}`
|
|
153
|
+
const installOptions = {
|
|
154
|
+
requestId,
|
|
155
|
+
saveExact: true,
|
|
156
|
+
...(typeof existing?.enabled === 'boolean' ? { enabled: existing.enabled } : {}),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let removed = false
|
|
160
|
+
if (existing) {
|
|
161
|
+
if (typeof api.plugins.removeBundle !== 'function') {
|
|
162
|
+
return { ...update, installed: false, reason: 'plugin-manager-unavailable', command }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let removal
|
|
166
|
+
try {
|
|
167
|
+
removal = await api.plugins.removeBundle(PLUGIN_PACKAGE_NAME)
|
|
168
|
+
} catch (error) {
|
|
169
|
+
removal = { application: 'failed', error }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Older hosts cannot hot-remove a running bundle; their package manager can still replace it for restart.
|
|
173
|
+
if (failedOperation(removal) && operationErrorCode(removal) !== 'stop-profile') {
|
|
174
|
+
return {
|
|
175
|
+
...update,
|
|
176
|
+
installed: true,
|
|
177
|
+
installation: {
|
|
178
|
+
...removal,
|
|
179
|
+
application: FAILED_INSTALL_STATES.has(removal?.application) ? removal.application : 'failed',
|
|
180
|
+
errorMessage: operationMessage(removal),
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
removed = !failedOperation(removal)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let installation
|
|
188
|
+
try {
|
|
189
|
+
installation = await api.plugins.installBundle(installSpec, installOptions)
|
|
190
|
+
} catch (error) {
|
|
191
|
+
installation = { application: 'failed', error }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let errorMessage = ''
|
|
195
|
+
if (failedOperation(installation) && removed) {
|
|
196
|
+
const previousSpec = typeof existing.source === 'string' && existing.source
|
|
197
|
+
? existing.source
|
|
198
|
+
: `${PLUGIN_PACKAGE_NAME}@${currentVersion}`
|
|
199
|
+
try {
|
|
200
|
+
const recovery = await api.plugins.installBundle(previousSpec, {
|
|
201
|
+
...(typeof existing.enabled === 'boolean' ? { enabled: existing.enabled } : {}),
|
|
202
|
+
})
|
|
203
|
+
errorMessage = failedOperation(recovery)
|
|
204
|
+
? `Update failed: ${operationMessage(installation)}. Restoring the previous version also failed: ${operationMessage(recovery)}`
|
|
205
|
+
: `Update failed: ${operationMessage(installation)}. The previous version was restored.`
|
|
206
|
+
installation = { ...installation, recovery }
|
|
207
|
+
} catch (error) {
|
|
208
|
+
errorMessage = `Update failed: ${operationMessage(installation)}. Restoring the previous version also failed: ${formatPluginInstallError(error)}`
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const detail = errorMessage || (installation?.error ? formatPluginInstallError(installation.error) : '')
|
|
120
213
|
return {
|
|
121
214
|
...update,
|
|
122
215
|
installed: true,
|
|
123
|
-
installation:
|
|
124
|
-
? { ...installation, errorMessage }
|
|
216
|
+
installation: detail && installation && typeof installation === 'object'
|
|
217
|
+
? { ...installation, errorMessage: detail }
|
|
125
218
|
: installation,
|
|
126
219
|
}
|
|
127
220
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-weapp-gateway",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.14",
|
|
4
4
|
"description": "Authenticated mobile gateway for DSH with Session V4 history, live permissions, inbox-based queue sync, built-in WeChat ClawBot, plugin management, LAN and Cloudflare Tunnel access, device pairing, per-session Agent presets, relay access, and speech input",
|
|
5
5
|
"main": "lib/index.mjs",
|
|
6
6
|
"files": [
|