dsh-reload-buttons 0.1.4 → 0.1.5

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/index.js CHANGED
@@ -25,6 +25,8 @@ export const name = 'dsh-reload-buttons'
25
25
  export const inject = []
26
26
 
27
27
  const ROUTE = '/dsh-reload-buttons/api/restart'
28
+ /** 探测路由:客户端点重启前先确认宿主半边在,失败时才能给出可读原因。 */
29
+ const PING_ROUTE = '/dsh-reload-buttons/api/ping'
28
30
  const WORKER = fileURLToPath(new URL('./assets/restart-app.mjs', import.meta.url))
29
31
 
30
32
  /** 桌面端=宿主跑在 Electron 的 Node 模式里。 */
@@ -94,34 +96,50 @@ function sendJson(res, code, value) {
94
96
  res.end(JSON.stringify(value))
95
97
  }
96
98
 
97
- /** 注册重启路由。 */
99
+ /** 注册探测路由与重启路由。 */
98
100
  export function apply(ctx) {
99
101
  const mount = (hostCtx) => {
100
102
  const webServer = hostCtx
101
103
  && (hostCtx.webServer || (typeof hostCtx.get === 'function' ? hostCtx.get('webServer') : undefined))
102
104
  if (!webServer || typeof webServer.register !== 'function') return
103
- ctx.effect(() => webServer.register({
104
- kind: 'exact',
105
- path: ROUTE,
106
- handler: (req, res) => {
107
- if (!isLocal(req)) return sendJson(res, 403, { ok: false, error: '只接受本机请求' })
108
- if (req.method !== 'POST') return sendJson(res, 405, { ok: false, error: '只支持 POST' })
109
- if (isDesktop()) {
110
- let handedOff = false
111
- try {
112
- handedOff = handOffRestart(webServer.port)
113
- } catch {
114
- handedOff = false
105
+ ctx.effect(() => {
106
+ // 探测优先:客户端点重启前先问一句宿主在不在,失败时才能给出可读原因。
107
+ const offPing = webServer.register({
108
+ kind: 'exact',
109
+ path: PING_ROUTE,
110
+ handler: (req, res) => {
111
+ if (!isLocal(req)) return sendJson(res, 403, { ok: false, error: '只接受本机请求' })
112
+ if (req.method !== 'GET') return sendJson(res, 405, { ok: false, error: '只支持 GET' })
113
+ sendJson(res, 200, { ok: true })
114
+ },
115
+ })
116
+ const offRestart = webServer.register({
117
+ kind: 'exact',
118
+ path: ROUTE,
119
+ handler: (req, res) => {
120
+ if (!isLocal(req)) return sendJson(res, 403, { ok: false, error: '只接受本机请求' })
121
+ if (req.method !== 'POST') return sendJson(res, 405, { ok: false, error: '只支持 POST' })
122
+ if (isDesktop()) {
123
+ let handedOff = false
124
+ try {
125
+ handedOff = handOffRestart(webServer.port)
126
+ } catch {
127
+ handedOff = false
128
+ }
129
+ sendJson(res, 200, { ok: true, mode: handedOff ? 'relaunch' : 'exit' })
130
+ // 脚本没起来就别干等:退回内核自己退出(会弹恢复框,但至少不会卡死)
131
+ if (!handedOff) setTimeout(shutdownAndExit, 400)
132
+ return
115
133
  }
116
- sendJson(res, 200, { ok: true, mode: handedOff ? 'relaunch' : 'exit' })
117
- // 脚本没起来就别干等:退回内核自己退出(会弹恢复框,但至少不会卡死)
118
- if (!handedOff) setTimeout(shutdownAndExit, 400)
119
- return
120
- }
121
- sendJson(res, 200, { ok: true, mode: 'exit' })
122
- setTimeout(shutdownAndExit, 400)
123
- },
124
- }), 'dsh-reload-buttons: restart route')
134
+ sendJson(res, 200, { ok: true, mode: 'exit' })
135
+ setTimeout(shutdownAndExit, 400)
136
+ },
137
+ })
138
+ return () => {
139
+ offPing()
140
+ offRestart()
141
+ }
142
+ }, 'dsh-reload-buttons: restart routes')
125
143
  }
126
144
  // apply 期直接取 ctx.get('webServer') 往往还没就绪,优先放进 inject 回调等它到位;
127
145
  // 桩 ctx(冒烟、测试)没有 inject,退回直接取(拿不到就什么也不做,不影响加载)。
package/lib/client.js CHANGED
@@ -32,6 +32,7 @@ module.exports = __toCommonJS(entry_exports);
32
32
  var import_react = require("react");
33
33
  var import_jsx_runtime = require("react/jsx-runtime");
34
34
  var ROUTE = "/dsh-reload-buttons/api/restart";
35
+ var PING_ROUTE = "/dsh-reload-buttons/api/ping";
35
36
  function api(path) {
36
37
  const relative = String(path).replace(/^\/+/, "");
37
38
  if (typeof document === "undefined") return "/" + relative;
@@ -108,9 +109,26 @@ async function probeBootBundles() {
108
109
  return { ok: results.every((r) => r.ok), total: results.length, bad: results.filter((r) => !r.ok) };
109
110
  }
110
111
  async function requestRestart() {
112
+ let alive = false;
111
113
  try {
112
- await fetch(api(ROUTE), { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
113
- } catch (e) {
114
+ const res = await fetch(api(PING_ROUTE), { method: "GET", cache: "no-store" });
115
+ alive = res.ok;
116
+ } catch {
117
+ alive = false;
118
+ }
119
+ if (!alive) return { ok: false, reason: "宿主半边没响应(插件可能没装全,或宿主已停止)" };
120
+ try {
121
+ const res = await fetch(api(ROUTE), { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
122
+ if (res.ok) return { ok: true };
123
+ let reason = "HTTP " + res.status;
124
+ try {
125
+ const data = await res.json();
126
+ if (data && data.error) reason = data.error;
127
+ } catch {
128
+ }
129
+ return { ok: false, reason };
130
+ } catch {
131
+ return { ok: true };
114
132
  }
115
133
  }
116
134
  function overlay(text, ms) {
@@ -148,7 +166,7 @@ function announceRefreshed() {
148
166
  return;
149
167
  }
150
168
  if (!stamp || Date.now() - stamp > 2e4) return;
151
- overlay("\u754C\u9762\u5DF2\u5237\u65B0", 1800);
169
+ overlay("界面已刷新", 1800);
152
170
  }
153
171
  var modulesRef;
154
172
  function moduleSystem() {
@@ -185,11 +203,14 @@ function freshGraph() {
185
203
  async function resyncPlugins() {
186
204
  const modules = moduleSystem();
187
205
  const entries = modules && modules.entries;
188
- if (!entries || typeof entries.sync !== "function") return false;
206
+ if (!entries || typeof entries.sync !== "function") return { ok: false, reason: "当前界面不支持就地刷新" };
189
207
  const graph = await freshGraph();
190
- if (!graph) return false;
208
+ if (!graph) return { ok: false, reason: "没拿到宿主的插件清单" };
191
209
  await entries.sync(graph);
192
- return true;
210
+ const state = entries.state && typeof entries.state.getSnapshot === "function" ? entries.state.getSnapshot() : null;
211
+ const failure = state && Array.isArray(state.failures) ? state.failures[0] : void 0;
212
+ if (failure) return { ok: false, reason: "插件加载失败:" + (failure.message || failure.id) };
213
+ return { ok: true };
193
214
  }
194
215
  function HeaderButtons() {
195
216
  const [restarting, setRestarting] = (0, import_react.useState)(false);
@@ -208,29 +229,31 @@ function HeaderButtons() {
208
229
  sessionStorage.setItem(REFRESH_FLAG, String(Date.now()));
209
230
  } catch {
210
231
  }
211
- overlay("\u6B63\u5728\u5237\u65B0\u2026");
232
+ overlay("正在刷新…");
212
233
  await new Promise((resolve) => {
213
234
  setTimeout(resolve, 180);
214
235
  });
215
236
  window.location.reload();
216
237
  return;
217
238
  }
218
- const hide = overlay("\u6B63\u5728\u5237\u65B0\u2026");
219
- let resynced = false;
239
+ const hide = overlay("正在刷新…");
240
+ let resynced = { ok: false, reason: "未知原因" };
220
241
  try {
221
242
  resynced = await resyncPlugins();
222
- } catch {
223
- resynced = false;
243
+ } catch (error) {
244
+ resynced = { ok: false, reason: String(error && error.message || error) };
224
245
  }
225
246
  hide();
226
- overlay(resynced ? "\u754C\u9762\u5DF2\u5237\u65B0" : "\u5237\u65B0\u5931\u8D25\uFF1A\u6E05\u5355\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u7528\u300C\u91CD\u542F\u300D", 2200);
247
+ overlay(resynced.ok ? "界面已刷新" : "刷新失败:" + resynced.reason + ";请用「重启」", 2600);
227
248
  setChecking(false);
228
249
  }
229
250
  async function restart() {
230
251
  if (restarting) return;
231
- if (!window.confirm("\u91CD\u542F\u5E94\u7528\uFF1F\u754C\u9762\u4F1A\u5173\u6389\uFF0C\u51E0\u79D2\u540E\u81EA\u52A8\u91CD\u5F00\uFF1B\u82E5\u5F39\u51FA\u300C\u5E94\u7528\u65E0\u6CD5\u4F7F\u7528\u300D\u7A97\u53E3\uFF0C\u70B9\u5176\u4E2D\u7684\u300C\u91CD\u542F\u300D\u5373\u53EF\u3002")) return;
252
+ if (!window.confirm("重启应用?界面会关掉:桌面版会在几秒后自动重开;命令行启动(dsh web)的进程会直接退出,需要你手动再启动。若弹出「应用无法使用」窗口,点其中的「重启」即可。")) return;
232
253
  setRestarting(true);
233
- await requestRestart();
254
+ const result = await requestRestart();
255
+ setRestarting(false);
256
+ if (result && result.ok === false) overlay("重启失败:" + result.reason, 3200);
234
257
  }
235
258
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: wrap, children: [
236
259
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -238,8 +261,8 @@ function HeaderButtons() {
238
261
  {
239
262
  type: "button",
240
263
  style: Object.assign({}, btn, checking ? { opacity: 0.35, cursor: "default" } : null),
241
- title: "\u5237\u65B0\u754C\u9762",
242
- "aria-label": "\u5237\u65B0\u754C\u9762",
264
+ title: "刷新界面",
265
+ "aria-label": "刷新界面",
243
266
  onClick: () => void reload(),
244
267
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconRefresh, {})
245
268
  }
@@ -249,8 +272,8 @@ function HeaderButtons() {
249
272
  {
250
273
  type: "button",
251
274
  style: Object.assign({}, btn, restarting ? { opacity: 0.35, cursor: "default" } : null),
252
- title: "\u91CD\u542F\u5E94\u7528\uFF08\u5173\u6389\u754C\u9762\uFF0C\u51E0\u79D2\u540E\u81EA\u52A8\u91CD\u5F00\uFF09",
253
- "aria-label": "\u91CD\u542F\u5E94\u7528",
275
+ title: "重启应用(桌面版自动重开;命令行版需手动再启动)",
276
+ "aria-label": "重启应用",
254
277
  onClick: () => void restart(),
255
278
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconPower, {})
256
279
  }
package/package.json CHANGED
@@ -1,18 +1,16 @@
1
1
  {
2
2
  "name": "dsh-reload-buttons",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "在 DSH 界面左上角放两个按钮:刷新界面、重启应用。",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "exports": {
8
8
  ".": "./index.js",
9
- "./invariant": "./invariant.js",
10
9
  "./client": "./lib/client.js",
11
10
  "./package.json": "./package.json"
12
11
  },
13
12
  "files": [
14
13
  "index.js",
15
- "invariant.js",
16
14
  "cordis.patch.yml",
17
15
  "src",
18
16
  "lib/client.js",
@@ -48,7 +46,7 @@
48
46
  "build:client": "node scripts/build-client.mjs",
49
47
  "check:client": "node scripts/build-client.mjs --check",
50
48
  "smoke": "node scripts/smoke.mjs",
51
- "check": "node --check index.js && node --check invariant.js && node --check assets/restart-app.mjs"
49
+ "check": "node --check index.js && node --check assets/restart-app.mjs"
52
50
  },
53
51
  "devDependencies": {
54
52
  "esbuild": "^0.28.1"
@@ -34,6 +34,8 @@ const bundled = await build({
34
34
  platform: 'browser',
35
35
  jsx: 'automatic',
36
36
  target: ['chrome120'],
37
+ // 保留中文字面量:产物要能按原文核对文案,别让构建把中文转成 \uXXXX
38
+ charset: 'utf8',
37
39
  // react 由 DSH 客户端模块表提供,不能打进产物
38
40
  external: ['react', 'react/jsx-runtime'],
39
41
  write: false,
package/scripts/smoke.mjs CHANGED
@@ -28,7 +28,6 @@ const fail = (label, why) => {
28
28
  /** 1. 包结构 */
29
29
  const REQUIRED = [
30
30
  'index.js',
31
- 'invariant.js',
32
31
  'cordis.patch.yml',
33
32
  'assets/restart-app.mjs',
34
33
  'src/entry.jsx',
@@ -74,11 +73,14 @@ try {
74
73
  }
75
74
 
76
75
  const ROUTE = '/' + pkg.name + '/api/restart'
77
- if (routes.length !== 1) fail('宿主半边', '应注册 1 条路由,实际 ' + routes.length)
78
- else if (routes[0].path !== ROUTE) fail('宿主半边', '路由路径是 ' + routes[0].path + ',应为 ' + ROUTE)
79
- else if (routes[0].kind !== 'exact') fail('宿主半边', '路由 kind 是 ' + routes[0].kind + ',应为 exact')
76
+ const PING = '/' + pkg.name + '/api/ping'
77
+ const routeOf = (path) => routes.find((route) => route.path === path)
78
+ if (routes.length !== 2) fail('宿主半边', '应注册 2 条路由,实际 ' + routes.length)
79
+ else if (!routeOf(PING)) fail('宿主半边', '缺探测路由 ' + PING)
80
+ else if (!routeOf(ROUTE)) fail('宿主半边', '缺重启路由 ' + ROUTE)
81
+ else if (routeOf(PING).kind !== 'exact' || routeOf(ROUTE).kind !== 'exact') fail('宿主半边', '路由 kind 应为 exact')
80
82
  else if (effects.length !== 1) fail('宿主半边', '应登记 1 个 effect,实际 ' + effects.length)
81
- else ok('宿主半边', 'POST ' + ROUTE + ' 已注册(kind=exact)')
83
+ else ok('宿主半边', 'GET ' + PING + '、POST ' + ROUTE + ' 已注册(kind=exact)')
82
84
 
83
85
  /** 3. 路由鉴权 */
84
86
  function call(handler, req) {
@@ -98,19 +100,23 @@ function call(handler, req) {
98
100
  return res
99
101
  }
100
102
 
101
- if (routes.length === 1) {
102
- const handler = routes[0].handler
103
+ if (routes.length === 2) {
104
+ const restart = routeOf(ROUTE).handler
105
+ const ping = routeOf(PING).handler
103
106
  const cases = [
104
- { label: '非回环地址被拒', req: { socket: { remoteAddress: '10.1.2.3' }, method: 'POST' }, want: 403 },
105
- { label: 'GET 被拒', req: { socket: { remoteAddress: '127.0.0.1' }, method: 'GET' }, want: 405 },
106
- { label: '::1 本机照常走 POST 分支', req: { socket: { remoteAddress: '::1' }, method: 'GET' }, want: 405 },
107
- { label: 'IPv4 映射地址被认作本机', req: { socket: { remoteAddress: '::ffff:127.0.0.1' }, method: 'GET' }, want: 405 },
107
+ { label: '非回环地址被拒', handler: restart, req: { socket: { remoteAddress: '10.1.2.3' }, method: 'POST' }, want: 403 },
108
+ { label: 'GET 打重启被拒', handler: restart, req: { socket: { remoteAddress: '127.0.0.1' }, method: 'GET' }, want: 405 },
109
+ { label: '::1 本机照常走 POST 分支', handler: restart, req: { socket: { remoteAddress: '::1' }, method: 'GET' }, want: 405 },
110
+ { label: 'IPv4 映射地址被认作本机', handler: restart, req: { socket: { remoteAddress: '::ffff:127.0.0.1' }, method: 'GET' }, want: 405 },
111
+ { label: '探测路由非回环被拒', handler: ping, req: { socket: { remoteAddress: '10.1.2.3' }, method: 'GET' }, want: 403 },
112
+ { label: '探测路由 POST 被拒', handler: ping, req: { socket: { remoteAddress: '127.0.0.1' }, method: 'POST' }, want: 405 },
113
+ { label: '探测路由本机 GET 放行', handler: ping, req: { socket: { remoteAddress: '127.0.0.1' }, method: 'GET' }, want: 200 },
108
114
  ]
109
115
  let passed = 0
110
116
  for (const item of cases) {
111
117
  let res
112
118
  try {
113
- res = call(handler, item.req)
119
+ res = call(item.handler, item.req)
114
120
  } catch (error) {
115
121
  fail('路由鉴权', item.label + ':handler 抛错 ' + error.message)
116
122
  continue
@@ -127,7 +133,11 @@ const idMatch = /^\s*id:\s*"([^"]+)"/m.exec(client)
127
133
  if (!idMatch) fail('客户端产物', '外壳里找不到 id')
128
134
  else if (idMatch[1] !== pkg.name) fail('客户端产物', '外壳 id 是 ' + idMatch[1] + ',应等于包名 ' + pkg.name)
129
135
  else if (!client.includes(ROUTE)) fail('客户端产物', '产物里没有路由 ' + ROUTE + '(源码改了但没重新构建)')
136
+ else if (!client.includes(PING)) fail('客户端产物', '产物里没有探测路由 ' + PING + '(源码改了但没重新构建)')
130
137
  else if (!client.includes(pkg.name + ':refreshed')) fail('客户端产物', '产物里没有刷新标记(源码改了但没重新构建)')
138
+ else if (!client.includes('getSnapshot')) fail('客户端产物', '产物里没有读取替换结果(源码改了但没重新构建)')
139
+ else if (!client.includes('命令行启动')) fail('客户端产物', '产物里没有区分命令行版的文案(源码改了但没重新构建)')
140
+ else if (client.includes('关掉界面,几秒后自动重开')) fail('客户端产物', '产物里还带着旧的确认框文案')
131
141
  else ok('客户端产物', 'id=' + pkg.name + ',' + Buffer.byteLength(client, 'utf8') + ' 字节')
132
142
 
133
143
  console.log('')
package/src/entry.jsx CHANGED
@@ -15,6 +15,7 @@
15
15
  import { useState } from 'react'
16
16
 
17
17
  const ROUTE = '/dsh-reload-buttons/api/restart'
18
+ const PING_ROUTE = '/dsh-reload-buttons/api/ping'
18
19
 
19
20
  /** 把路由拼成当前页面挂载点下的路径(锚 document.baseURI,兼容子路径部署)。 */
20
21
  function api(path) {
@@ -75,11 +76,32 @@ async function probeBootBundles() {
75
76
  return { ok: results.every((r) => r.ok), total: results.length, bad: results.filter((r) => !r.ok) }
76
77
  }
77
78
 
78
- /** 请求宿主重启。界面随壳一起关掉,所以连接断掉是预期,不当错误。 */
79
+ /**
80
+ * 请求宿主重启:先探一次宿主在不在,再发重启请求。
81
+ * 返回 { ok, reason }。界面随壳一起关掉时连接会断,那是预期,按成功算。
82
+ */
79
83
  async function requestRestart() {
84
+ let alive = false
85
+ try {
86
+ const res = await fetch(api(PING_ROUTE), { method: 'GET', cache: 'no-store' })
87
+ alive = res.ok
88
+ } catch {
89
+ alive = false
90
+ }
91
+ if (!alive) return { ok: false, reason: '宿主半边没响应(插件可能没装全,或宿主已停止)' }
80
92
  try {
81
- await fetch(api(ROUTE), { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
82
- } catch (e) { /* 界面壳被关掉时连接会断,属预期 */ }
93
+ const res = await fetch(api(ROUTE), { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
94
+ if (res.ok) return { ok: true }
95
+ let reason = 'HTTP ' + res.status
96
+ try {
97
+ const data = await res.json()
98
+ if (data && data.error) reason = data.error
99
+ } catch { /* 回包不是 JSON 就用状态码 */ }
100
+ return { ok: false, reason }
101
+ } catch {
102
+ // 桌面端重启时壳先关,连接断在这里属预期:请求已经送到宿主。
103
+ return { ok: true }
104
+ }
83
105
  }
84
106
 
85
107
  /** 顶部浮层:先提示「正在刷新…」,重载回来后再给一句「界面已刷新」,让人知道这一下确实生效了。 */
@@ -156,15 +178,22 @@ function freshGraph() {
156
178
  })
157
179
  }
158
180
 
159
- /** 清单过期时:按宿主当前图就地对齐插件条目——不重载整页、不关应用。 */
181
+ /**
182
+ * 清单过期时:按宿主当前图就地对齐插件条目——不重载整页、不关应用。
183
+ * 返回 { ok, reason }:sync 只管执行,某个条目加载失败时它把失败记在内部状态里,
184
+ * 不读这个状态就会把「替换失败」报成「已刷新」。
185
+ */
160
186
  async function resyncPlugins() {
161
187
  const modules = moduleSystem()
162
188
  const entries = modules && modules.entries
163
- if (!entries || typeof entries.sync !== 'function') return false
189
+ if (!entries || typeof entries.sync !== 'function') return { ok: false, reason: '当前界面不支持就地刷新' }
164
190
  const graph = await freshGraph()
165
- if (!graph) return false
191
+ if (!graph) return { ok: false, reason: '没拿到宿主的插件清单' }
166
192
  await entries.sync(graph)
167
- return true
193
+ const state = entries.state && typeof entries.state.getSnapshot === 'function' ? entries.state.getSnapshot() : null
194
+ const failure = state && Array.isArray(state.failures) ? state.failures[0] : undefined
195
+ if (failure) return { ok: false, reason: '插件加载失败:' + (failure.message || failure.id) }
196
+ return { ok: true }
168
197
  }
169
198
 
170
199
  /** 左上一行两个按钮。 */
@@ -191,22 +220,24 @@ function HeaderButtons() {
191
220
  }
192
221
  // 清单过期:整页重载会白屏(旧 rev 已 404)。改为就地重载插件,同行样给可见反馈。
193
222
  const hide = overlay('正在刷新…')
194
- let resynced = false
223
+ let resynced = { ok: false, reason: '未知原因' }
195
224
  try {
196
225
  resynced = await resyncPlugins()
197
- } catch {
198
- resynced = false
226
+ } catch (error) {
227
+ resynced = { ok: false, reason: String((error && error.message) || error) }
199
228
  }
200
229
  hide()
201
- overlay(resynced ? '界面已刷新' : '刷新失败:清单已过期,请用「重启」', 2200)
230
+ overlay(resynced.ok ? '界面已刷新' : '刷新失败:' + resynced.reason + ';请用「重启」', 2600)
202
231
  setChecking(false)
203
232
  }
204
233
 
205
234
  async function restart() {
206
235
  if (restarting) return
207
- if (!window.confirm('重启应用?界面会关掉,几秒后自动重开;若弹出「应用无法使用」窗口,点其中的「重启」即可。')) return
236
+ if (!window.confirm('重启应用?界面会关掉:桌面版会在几秒后自动重开;命令行启动(dsh web)的进程会直接退出,需要你手动再启动。若弹出「应用无法使用」窗口,点其中的「重启」即可。')) return
208
237
  setRestarting(true)
209
- await requestRestart()
238
+ const result = await requestRestart()
239
+ setRestarting(false)
240
+ if (result && result.ok === false) overlay('重启失败:' + result.reason, 3200)
210
241
  }
211
242
 
212
243
  return (
@@ -223,7 +254,7 @@ function HeaderButtons() {
223
254
  <button
224
255
  type="button"
225
256
  style={Object.assign({}, btn, restarting ? { opacity: 0.35, cursor: 'default' } : null)}
226
- title="重启应用(关掉界面,几秒后自动重开)"
257
+ title="重启应用(桌面版自动重开;命令行版需手动再启动)"
227
258
  aria-label="重启应用"
228
259
  onClick={() => void restart()}
229
260
  >
package/invariant.js DELETED
@@ -1,10 +0,0 @@
1
- const PACKAGE_NAME = 'dsh-reload-buttons'
2
-
3
- /** Cordis companion plugin name. */
4
- export const name = 'dsh-reload-buttons-invariant'
5
- /** Service required before the companion can reserve package ownership. */
6
- export const inject = ['invariants']
7
-
8
- const install = () => {}
9
-
10
- export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))