dsh-mobile-plugin 0.1.62 → 0.1.63

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.
@@ -32,16 +32,22 @@
32
32
  * (`cordis`、`dsh-tools`、`dsh-client-runtime`…),它们是 **DSH 运行时注入的、npm 上
33
33
  * 根本没有**。pnpm 8 起 `auto-install-peers` 默认开,会去 npm 上拉这些 peer 然后 404。
34
34
  * profile 里通常已经关掉了,但那是市场帮它写的 —— 换台机器不一定有,所以这里显式带上。
35
- * · `--config.minimum-release-age=0`:**只在强制更新时**带。pnpm 的「新发布先晾一会儿」
36
- * 门槛会把刚发的版本挡在外面,`force` 就是「我知道,装它」。
35
+ * · `--config.minimum-release-age=0`:**撞上 pnpm 的「新发布先晾一会儿」策略时才带**。
36
+ * 强制更新(`force`)一开始就带;平时不带,但**失败信息确实是这类策略时会带开关重试一次**
37
+ * —— 那道策略查的是**整份 lockfile**,别的插件刚发不久也会把整份锁文件判成「含未过期
38
+ * 条目」,于是我们自己的更新整整一天都做不了,而原因跟我们这个包毫无关系(2026-10-11
39
+ * 真机撞上的就是 `billion-context@0.1.192`)。市场那边对这种拒绝同样是带开关重试一次。
37
40
  * · `CI=1`:没有终端可交互。包管理器在无人值守的子进程里问问题是没法回答的。
38
41
  *
39
- * ## 装之前先看有没有 agent 在跑
42
+ * ## 装之前先看有没有 agent 在跑(但「有没有新版」排在它前面)
40
43
  *
41
44
  * 装是**就地替换插件文件**,正在干活的对话可能读到半个包。市场那条路有一道这样的闸
42
45
  * (`AGENTS_RUNNING`),这里也补上了 —— 判到有人跑就**不动盘**,如实报成「可再试」。
43
46
  * `force`(面板上的「强制更新」)是用户明说「我知道,装吧」的那一挡,会跳过它。
44
47
  *
48
+ * 顺序上**先答「有没有新版」**:没东西可装时拿一句「有对话在跑」挡在前面是误导,用户会以为
49
+ * 是那个原因,而真正的答案是「已经是最新」。
50
+ *
45
51
  * ## 边界(照实说)
46
52
  *
47
53
  * · **只动 profile 的依赖树**(`pnpm add` 会顺手把 profile 的 `package.json` 里的依赖声明
@@ -317,17 +323,39 @@ async function installLatest({
317
323
  }
318
324
 
319
325
  const spec = `${name}@${version}`;
326
+ const releaseAgeFlag = '--config.minimum-release-age=0';
320
327
  const args = picked.manager === 'pnpm'
321
- ? ['add', spec, '--config.auto-install-peers=false', ...(force ? ['--config.minimum-release-age=0'] : [])]
328
+ ? ['add', spec, '--config.auto-install-peers=false', ...(force ? [releaseAgeFlag] : [])]
322
329
  : ['install', spec, '--no-audit', '--no-fund'];
323
- const command = [picked.manager, ...args].join(' ');
330
+ let command = [picked.manager, ...args].join(' ');
324
331
  onEvent({ phase: 'install-started', command, manager: picked.kind });
325
332
 
326
- const result = await run(picked.command, [...picked.prefix, ...args], {
333
+ const spawnAdd = extra => run(picked.command, [...picked.prefix, ...args, ...extra], {
327
334
  cwd: profileDir,
328
335
  env: { ...childEnv, ...picked.env },
329
336
  timeoutMs,
330
337
  });
338
+ let result = await spawnAdd([]);
339
+ let output = result.output;
340
+ /**
341
+ * 撞上 pnpm 的「新发布先晾一会儿」策略:**带上开关重试一次**。
342
+ *
343
+ * 为什么非重试不可:那道策略查的是**整份 lockfile**,不是我们要装的这个包。这台机器上
344
+ * 实测过一次 —— 另一个插件(`billion-context@0.1.192`)刚发不久,整份 lockfile 就被判
345
+ * 「含未过期的条目」而拒绝,于是我们自己的更新整整一天都做不了,而原因跟我们这个包毫无
346
+ * 关系。市场那边对这种拒绝也是带开关重试一次(`RELEASE_AGE_OVERRIDE`)。
347
+ *
348
+ * 只在**第一次没带开关**、且失败信息确实是这类策略时才重试:别的失败(网络、权限、盘满)
349
+ * 重试一次只会把同一条错误再说一遍,还多花一次等待。
350
+ */
351
+ const releaseAgeLike = /MINIMUM_RELEASE_AGE|minimumReleaseAge|minimum-release-age|release age/i;
352
+ const retried = !result.ok && picked.manager === 'pnpm' && !force && releaseAgeLike.test(output);
353
+ if (retried) {
354
+ onEvent({ phase: 'release-age-retry' });
355
+ result = await spawnAdd([releaseAgeFlag]);
356
+ command = [picked.manager, ...args, releaseAgeFlag].join(' ');
357
+ output = `${output}\n--- 撞上 pnpm 的发布时长策略,带上 ${releaseAgeFlag} 重试一次 ---\n${result.output}`;
358
+ }
331
359
  const after = installedVersion(profileNodeModules, name);
332
360
  if (!result.ok) {
333
361
  return {
@@ -338,7 +366,8 @@ async function installLatest({
338
366
  source: declaredSource(profileDir, name),
339
367
  command,
340
368
  manager: picked.kind,
341
- output: result.output,
369
+ retried,
370
+ output,
342
371
  // 超时与「真的失败了」分开说:前者可以再试,后者多半要看输出。
343
372
  failure: {
344
373
  code: result.code === null ? 'INSTALL_ABORTED' : 'INSTALL_FAILED',
@@ -356,7 +385,8 @@ async function installLatest({
356
385
  source: declaredSource(profileDir, name),
357
386
  command,
358
387
  manager: picked.kind,
359
- output: result.output,
388
+ retried,
389
+ output,
360
390
  failure: changed
361
391
  ? null
362
392
  : { code: 'VERSION_UNCHANGED', message: `${command} 跑完了,但盘上还是 ${after ?? '读不到版本'},这一次不算数` },
@@ -382,6 +412,22 @@ async function installLatest({
382
412
  async function installLatestIfNeeded(options = {}) {
383
413
  const { profileNodeModules, force = false, name = DEFAULT_NAME, agents = null } = options;
384
414
  const beforeVersion = installedVersion(profileNodeModules, name);
415
+ const check = await checkForUpdate({ installedVersion: beforeVersion, source: declaredSource(path.dirname(profileNodeModules), name) });
416
+ // 「有没有新版」排在「有没有人在跑」**前面**:没东西可装的时候,拿一句「有对话在跑」挡住
417
+ // 真正的答案(已经是最新)是误导 —— 2026-10-11 真机上就是这么被绊了一下。
418
+ if (!force && !check.updateAvailable) {
419
+ return {
420
+ installed: false,
421
+ skipped: true,
422
+ beforeVersion,
423
+ installedVersion: beforeVersion,
424
+ source: check.source,
425
+ command: null,
426
+ output: '',
427
+ failure: null,
428
+ check,
429
+ };
430
+ }
385
431
  if (!force) {
386
432
  const running = runningAgentIds(agents);
387
433
  if (running.length > 0) {
@@ -390,11 +436,12 @@ async function installLatestIfNeeded(options = {}) {
390
436
  skipped: false,
391
437
  beforeVersion,
392
438
  installedVersion: beforeVersion,
393
- source: declaredSource(path.dirname(profileNodeModules), name),
439
+ source: check.source,
394
440
  command: null,
395
441
  output: '',
396
442
  runningAgents: running,
397
443
  retryable: true,
444
+ check,
398
445
  failure: {
399
446
  code: 'AGENTS_RUNNING',
400
447
  message:
@@ -405,20 +452,6 @@ async function installLatestIfNeeded(options = {}) {
405
452
  };
406
453
  }
407
454
  }
408
- const check = await checkForUpdate({ installedVersion: beforeVersion, source: declaredSource(path.dirname(profileNodeModules), name) });
409
- if (!force && !check.updateAvailable) {
410
- return {
411
- installed: false,
412
- skipped: true,
413
- beforeVersion,
414
- installedVersion: beforeVersion,
415
- source: check.source,
416
- command: null,
417
- output: '',
418
- failure: null,
419
- check,
420
- };
421
- }
422
455
  const result = await installLatest({ ...options, version: check.latestVersion });
423
456
  return { ...result, check };
424
457
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile-plugin",
3
- "version": "0.1.62",
3
+ "version": "0.1.63",
4
4
  "description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (0.2.0-rc.2 compatible, peers resolved by the host)",
5
5
  "license": "MIT",
6
6
  "type": "module",