dsh-plugin-update 0.2.0 → 0.3.0

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/dist/host.js CHANGED
@@ -10,13 +10,126 @@ import {
10
10
  resolveUpdateConfig
11
11
  } from "./config.js";
12
12
  import { manualCommand } from "./commands.js";
13
- import { validVersion } from "./service.js";
14
- import { createUpdateDiskPorts, createUpdateExecutor } from "./store.js";
13
+ import { buildDiag } from "./diag.js";
14
+ import { isVersionAllowedInChannel, validRequestId } from "./service.js";
15
+ import { createUpdateDiskPorts, createUpdateExecutor, createUpdateQueuePorts } from "./store.js";
15
16
  import { createUpdateReader, defaultHomeDir, resolveProfileName, resolveTargetPackage } from "./reader.js";
17
+ import {
18
+ QUEUE_INTENT_TTL_MS,
19
+ cancelEnqueuedInQueue,
20
+ emptyQueueState,
21
+ enqueueInQueue,
22
+ isHeadOfQueue,
23
+ normalizeQueueState,
24
+ pruneExpiredIntents,
25
+ queuePositionOf,
26
+ releaseOwnerInQueue,
27
+ setOwnerIfFree,
28
+ visibleQueueFor
29
+ } from "./queue.js";
16
30
  import { buildPhoneName, buildPhoneNames as buildPhoneNames2, resolveUpdateConfig as resolveUpdateConfig2 } from "./config.js";
17
31
  import { parseEventListManifest, checkEventFields, checkEventCounts } from "./gate.js";
18
32
  import { containingPackage, defaultHomeDir as defaultHomeDir2, profileNameValid, registrySpec, resolveTargetPackage as resolveTargetPackage2 } from "./reader.js";
33
+ import {
34
+ COPY_BUDGET_CHARS,
35
+ DETAIL_MAX_CHARS,
36
+ DIAG_DROP_ORDER,
37
+ DIAG_INLINE_BUDGET_BYTES,
38
+ DIAG_NEVER_DROP_KEYS,
39
+ ENDORSED_REGISTRY_HOSTS,
40
+ KNOWN_GAP_FILE_URL_PREFIX,
41
+ REDACTED_PATH,
42
+ REDACTED_SECRET,
43
+ REDACTION_RULE_NAMES,
44
+ diagByteLength,
45
+ enforceDiagBudget,
46
+ resolveRegistryHost,
47
+ sanitizeDetail,
48
+ sanitizeForCopy,
49
+ truncateToWordBoundary
50
+ } from "./redaction.js";
51
+ import {
52
+ DIAG_ACTIONS,
53
+ DIAG_KEYS,
54
+ DIAG_KNOWN_TYPES,
55
+ DIAG_STAGE_EXPECT,
56
+ DIAG_STAGES,
57
+ DIAG_VERSION,
58
+ buildDiag as buildDiag2,
59
+ deriveAction,
60
+ deriveRouteMethod,
61
+ deriveStage,
62
+ enforceBudget
63
+ } from "./diag.js";
64
+ import {
65
+ CHANGELOG_ALL_CATEGORIES,
66
+ CHANGELOG_FILENAME,
67
+ CHANGELOG_FOLDED,
68
+ CHANGELOG_MAX_BULLETS_PER_SECTION,
69
+ CHANGELOG_MAX_BULLET_CHARS,
70
+ CHANGELOG_MAX_CHARS,
71
+ CHANGELOG_MAX_ENTRIES,
72
+ CHANGELOG_MUST_SHOW,
73
+ CHANGELOG_NEUTRAL_HINT,
74
+ CHANGELOG_NEUTRAL_LINE,
75
+ changelogForUpdate,
76
+ hasVisibleSections,
77
+ isUnreleasedVersion,
78
+ parseChangelog,
79
+ renderChangelogHTML,
80
+ renderChangelogNeutral,
81
+ renderChangelogSection,
82
+ selectChangelogEntries
83
+ } from "./changelog.js";
84
+ import {
85
+ CHANGELOG_DEFAULT_REGISTRY,
86
+ CHANGELOG_FETCH_MAX_BYTES,
87
+ CHANGELOG_TAR_MAX_BYTES,
88
+ CHANGELOG_TARBALL_MAX_BYTES,
89
+ extractChangelogFromTar,
90
+ fetchReleaseChangelogText,
91
+ readInstalledChangelogText
92
+ } from "./changelog-io.js";
19
93
  import { createUpdateDiskPorts as createUpdateDiskPorts2, createUpdateExecutor as createUpdateExecutor2, pathsForUpdate, resolveCliEntry } from "./store.js";
94
+ import { SKIPPED_FILE } from "./config.js";
95
+ import {
96
+ addSkipped,
97
+ clearSkipped,
98
+ createSkipDiskPorts,
99
+ isVersionSkipped,
100
+ MAX_SKIPPED_ENTRIES,
101
+ normalizeSkipped,
102
+ skipKey
103
+ } from "./store.js";
104
+ import {
105
+ compareReleaseVersions,
106
+ isPrereleaseVersion,
107
+ isVersionAllowedInChannel as isVersionAllowedInChannel2,
108
+ validReleaseVersion,
109
+ validVersion,
110
+ compareVersions
111
+ } from "./service.js";
112
+ import {
113
+ createUpdateQueuePorts as createUpdateQueuePorts2,
114
+ queuePathsForUpdate,
115
+ QUEUE_DIR_SEGMENT,
116
+ QUEUE_FILE,
117
+ QUEUE_LOCK_FILE,
118
+ QUEUE_LOCK_STALE_MS
119
+ } from "./store.js";
120
+ import {
121
+ QUEUE_INTENT_TTL_MS as QUEUE_INTENT_TTL_MS2,
122
+ cancelEnqueuedInQueue as cancelEnqueuedInQueue2,
123
+ emptyQueueState as emptyQueueState2,
124
+ enqueueInQueue as enqueueInQueue2,
125
+ isHeadOfQueue as isHeadOfQueue2,
126
+ normalizeQueueState as normalizeQueueState2,
127
+ pruneExpiredIntents as pruneExpiredIntents2,
128
+ queuePositionOf as queuePositionOf2,
129
+ releaseOwnerInQueue as releaseOwnerInQueue2,
130
+ setOwnerIfFree as setOwnerIfFree2,
131
+ visibleQueueFor as visibleQueueFor2
132
+ } from "./queue.js";
20
133
  let sharedReader = null;
21
134
  let sharedReaderKey = "";
22
135
  let sharedDesktopPnpm = null;
@@ -96,11 +209,12 @@ async function getSharedReader(pluginId, config, overrides = {}) {
96
209
  const osHome = overrides.osHome ?? homedir();
97
210
  const homeDirDefault = defaultHomeDir(env, osHome);
98
211
  const targetPackageName = overrides.targetPackageName ?? config.targetPackageName;
212
+ const releaseChannel = overrides.releaseChannel ?? config.releaseChannel ?? "stable";
99
213
  const explicitTargetDir = typeof overrides.targetPackageDir === "string" && overrides.targetPackageDir ? String(overrides.targetPackageDir) : "";
100
214
  const loaded = await resolveTargetPackage(targetPackageName, explicitTargetDir ? { targetPackageDir: explicitTargetDir } : {}).catch(
101
215
  () => null
102
216
  );
103
- const runningVersion = overrides.runningVersion ?? (loaded && validVersion(loaded.manifest.version) ? String(loaded.manifest.version) : null);
217
+ const runningVersion = overrides.runningVersion ?? (loaded && isVersionAllowedInChannel(loaded.manifest.version, releaseChannel) ? String(loaded.manifest.version) : null);
104
218
  if (!runningVersion) throw unknownProfile();
105
219
  let profileDirInput;
106
220
  if (overrides.profileDir) {
@@ -117,9 +231,116 @@ async function getSharedReader(pluginId, config, overrides = {}) {
117
231
  const confirmationTtlMs = overrides.confirmationTtlMs ?? config.confirmationTtlMs;
118
232
  const installTimeoutMs = overrides.installTimeoutMs ?? config.installTimeoutMs;
119
233
  const registryUrl = overrides.registryUrl ?? config.registryUrl;
120
- const key = `${pluginId}\0${config.prefix}\0${runningVersion}\0${profileDirInput}\0${overrides.profileName ?? ""}\0${homeDirInput}\0${overrides.runInstall ? "exec" : ""}\0${environmentKind}\0${targetPackageName}\0${explicitTargetDir}`;
234
+ const key = `${pluginId}\0${config.prefix}\0${runningVersion}\0${profileDirInput}\0${overrides.profileName ?? ""}\0${homeDirInput}\0${overrides.runInstall ? "exec" : ""}\0${environmentKind}\0${targetPackageName}\0${explicitTargetDir}\0${releaseChannel}`;
121
235
  if (sharedReader && sharedReaderKey === key) return sharedReader;
122
236
  const disk = overrides.readJob && overrides.writeJob ? null : createUpdateDiskPorts(homeDirInput, pluginId, profileDirInput);
237
+ const queueTtlMs = typeof overrides.queueTtlMs === "number" && Number.isFinite(overrides.queueTtlMs) && overrides.queueTtlMs > 0 ? overrides.queueTtlMs : QUEUE_INTENT_TTL_MS;
238
+ const queueDisk = overrides.readQueue && overrides.writeQueue ? null : disk ? createUpdateQueuePorts(homeDirInput, profileDirInput) : null;
239
+ const queueNow = overrides.now ?? Date.now;
240
+ async function readQueueBestEffort() {
241
+ try {
242
+ if (overrides.readQueue) return normalizeQueueState(await overrides.readQueue());
243
+ if (queueDisk) return normalizeQueueState(await queueDisk.readQueue());
244
+ } catch {
245
+ }
246
+ return emptyQueueState();
247
+ }
248
+ async function writeQueueBestEffort(state) {
249
+ try {
250
+ if (overrides.writeQueue) await overrides.writeQueue(normalizeQueueState(state));
251
+ else if (queueDisk) await queueDisk.writeQueue(state);
252
+ } catch {
253
+ }
254
+ }
255
+ async function loadQueuePruned() {
256
+ const raw = await readQueueBestEffort();
257
+ const pruned = pruneExpiredIntents(raw, queueNow(), queueTtlMs);
258
+ if (pruned !== raw) await writeQueueBestEffort(pruned);
259
+ return pruned;
260
+ }
261
+ const baseTryAcquire = overrides.tryAcquireLock ?? disk?.tryAcquireLock;
262
+ const baseRelease = overrides.releaseLock ?? disk?.releaseLock;
263
+ const globalTryAcquire = overrides.tryAcquireGlobalLock ? (lockId) => overrides.tryAcquireGlobalLock(lockId, pluginId, { timeoutMs: installTimeoutMs }) : queueDisk ? (lockId) => queueDisk.tryAcquireGlobalLock(lockId, pluginId, { timeoutMs: installTimeoutMs }) : void 0;
264
+ const globalRelease = overrides.releaseGlobalLock ?? queueDisk?.releaseGlobalLock;
265
+ async function combinedTryAcquire(lockId) {
266
+ try {
267
+ if (globalTryAcquire && !await globalTryAcquire(lockId)) return false;
268
+ if (baseTryAcquire && !await baseTryAcquire(lockId)) {
269
+ if (globalRelease) {
270
+ try {
271
+ await globalRelease(lockId);
272
+ } catch {
273
+ }
274
+ }
275
+ return false;
276
+ }
277
+ return true;
278
+ } catch {
279
+ return false;
280
+ }
281
+ }
282
+ async function combinedRelease(lockId) {
283
+ try {
284
+ if (baseRelease) await baseRelease(lockId);
285
+ } catch {
286
+ }
287
+ try {
288
+ if (globalRelease) await globalRelease(lockId);
289
+ } catch {
290
+ }
291
+ try {
292
+ const current = await readQueueBestEffort();
293
+ const next = releaseOwnerInQueue(current, pluginId, lockId);
294
+ if (next.released) await writeQueueBestEffort(next.state);
295
+ } catch {
296
+ }
297
+ }
298
+ let memJobFallback = null;
299
+ async function mirroredWriteJob(job) {
300
+ if (overrides.writeJob) await overrides.writeJob(job);
301
+ else memJobFallback = job;
302
+ try {
303
+ if (!job) return;
304
+ const current = await readQueueBestEffort();
305
+ const pruned = pruneExpiredIntents(current, queueNow(), queueTtlMs);
306
+ if (job.state === "installing" || job.state === "verifying") {
307
+ let next = pruned;
308
+ if (next.owner && next.owner.jobId === job.id) {
309
+ next = {
310
+ version: 1,
311
+ owner: {
312
+ ...next.owner,
313
+ requestId: job.requestId ?? next.owner.requestId,
314
+ targetVersion: job.targetVersion ?? next.owner.targetVersion
315
+ },
316
+ waiting: next.waiting.filter((e) => !(e.pluginId === pluginId && (e.requestId ?? null) === (job.requestId ?? null)))
317
+ };
318
+ } else {
319
+ let base = next;
320
+ const age = queueNow() - (typeof base.owner?.startedAt === "number" ? base.owner.startedAt : 0);
321
+ if (base.owner && age > installTimeoutMs) base = { version: 1, owner: null, waiting: base.waiting };
322
+ next = setOwnerIfFree(base, {
323
+ pluginId,
324
+ jobId: job.id,
325
+ requestId: job.requestId,
326
+ targetVersion: job.targetVersion,
327
+ startedAt: queueNow()
328
+ }).state;
329
+ }
330
+ if (next !== pruned) await writeQueueBestEffort(next);
331
+ else if (pruned !== current) await writeQueueBestEffort(pruned);
332
+ } else {
333
+ const released = releaseOwnerInQueue(pruned, pluginId, job.id);
334
+ const out = released.released ? released.state : pruned;
335
+ if (out !== current) await writeQueueBestEffort(out);
336
+ }
337
+ } catch {
338
+ }
339
+ }
340
+ async function mirroredReadJob() {
341
+ if (overrides.readJob) return await overrides.readJob() ?? null;
342
+ return memJobFallback;
343
+ }
123
344
  const defaultRun = createUpdateExecutor({
124
345
  profileName: overrides.profileName ?? null,
125
346
  environmentKind,
@@ -137,32 +358,37 @@ async function getSharedReader(pluginId, config, overrides = {}) {
137
358
  pluginId,
138
359
  log: emitInstallLog
139
360
  });
140
- sharedReader = createUpdateReader({
141
- runningVersion,
142
- profileDir: profileDirInput,
143
- pluginId,
144
- profileName: overrides.profileName ?? void 0,
145
- homeDir: overrides.homeDir,
146
- targetPackageDir: explicitTargetDir || void 0,
147
- env,
148
- osHome,
149
- fetchImpl: overrides.fetchImpl,
150
- now: overrides.now,
151
- randomId: overrides.randomId,
152
- nodeVersion: overrides.nodeVersion,
153
- environmentKind,
154
- targetPackageName,
155
- registryUrl,
156
- checkTimeoutMs,
157
- confirmationTtlMs,
158
- readInstalled: overrides.readInstalled,
159
- readJob: overrides.readJob,
160
- writeJob: overrides.writeJob,
161
- tryAcquireLock: overrides.tryAcquireLock ?? disk?.tryAcquireLock,
162
- releaseLock: overrides.releaseLock ?? disk?.releaseLock,
163
- backupJob: overrides.backupJob ?? disk?.backupJob,
164
- runInstall: overrides.runInstall ?? defaultRun
165
- });
361
+ sharedReader = Object.assign(
362
+ createUpdateReader({
363
+ runningVersion,
364
+ profileDir: profileDirInput,
365
+ pluginId,
366
+ profileName: overrides.profileName ?? void 0,
367
+ homeDir: overrides.homeDir,
368
+ targetPackageDir: explicitTargetDir || void 0,
369
+ env,
370
+ osHome,
371
+ fetchImpl: overrides.fetchImpl,
372
+ now: overrides.now,
373
+ randomId: overrides.randomId,
374
+ nodeVersion: overrides.nodeVersion,
375
+ environmentKind,
376
+ targetPackageName,
377
+ registryUrl,
378
+ checkTimeoutMs,
379
+ confirmationTtlMs,
380
+ releaseChannel,
381
+ readInstalled: overrides.readInstalled,
382
+ readJob: mirroredReadJob,
383
+ writeJob: mirroredWriteJob,
384
+ tryAcquireLock: combinedTryAcquire,
385
+ releaseLock: combinedRelease,
386
+ backupJob: overrides.backupJob ?? disk?.backupJob,
387
+ runInstall: overrides.runInstall ?? defaultRun
388
+ }),
389
+ // 跨插件队列的面板侧读写口(#15):与本次解析出的使用范围绑定,面板经电话可选参数消费。
390
+ { readQueuePruned: loadQueuePruned, writeQueueState: writeQueueBestEffort, queueTtlMs, queueNow }
391
+ );
166
392
  sharedReaderKey = key;
167
393
  return sharedReader;
168
394
  }
@@ -210,6 +436,22 @@ async function snapWithManual(reader, snapshot, config) {
210
436
  return { snapshot, manual: null };
211
437
  }
212
438
  }
439
+ async function readEnvForPanel(reader) {
440
+ try {
441
+ if (typeof reader.readEnv !== "function") return null;
442
+ const env = await reader.readEnv();
443
+ if (!env || typeof env !== "object") return null;
444
+ return {
445
+ profileName: typeof env.profileName === "string" && env.profileName ? env.profileName : null,
446
+ environmentKind: typeof env.environmentKind === "string" && env.environmentKind ? env.environmentKind : null
447
+ };
448
+ } catch {
449
+ return null;
450
+ }
451
+ }
452
+ function wantsEnv(args) {
453
+ return !!args && args["includeEnv"] === true;
454
+ }
213
455
  let phoneLogCtx = null;
214
456
  function emitInstallLog(level, event, fields) {
215
457
  try {
@@ -217,7 +459,19 @@ function emitInstallLog(level, event, fields) {
217
459
  } catch {
218
460
  }
219
461
  }
220
- function loggedPhone(method, kind, pluginId, fn) {
462
+ function queueArgsOf(args) {
463
+ return {
464
+ includeQueue: !!args && args["includeQueue"] === true,
465
+ showOthers: !!args && args["showOthers"] === true
466
+ };
467
+ }
468
+ function checkExpiredError() {
469
+ return Object.assign(new Error("check-expired"), { code: "check-expired" });
470
+ }
471
+ function updateBusyError() {
472
+ return Object.assign(new Error("update-busy"), { code: "update-busy" });
473
+ }
474
+ function loggedPhone(method, kind, pluginId, fn, diagCtx) {
221
475
  return async function(args) {
222
476
  const t0 = Date.now();
223
477
  const emit = (level, event, fields) => {
@@ -226,15 +480,47 @@ function loggedPhone(method, kind, pluginId, fn) {
226
480
  } catch {
227
481
  }
228
482
  };
483
+ const safeArgs = args && typeof args === "object" ? args : {};
229
484
  try {
230
- const out = await fn(args);
485
+ const out = await fn(safeArgs);
231
486
  const snapshot = out && out.snapshot ? out.snapshot : out;
232
487
  emit("info", "host.call", { method, latencyMs: Date.now() - t0, ok: true, kind, pluginId });
233
- return { ok: true, snapshot, manual: out && out.snapshot ? out.manual ?? null : null, receipt: out && out.receipt ? out.receipt : null };
488
+ return {
489
+ ok: true,
490
+ snapshot,
491
+ manual: out && out.snapshot ? out.manual ?? null : null,
492
+ receipt: out && out.receipt ? out.receipt : null,
493
+ // 新增选填(#15):调用方没要时不带该键,老调用形状不变。
494
+ ...out && out.queue ? { queue: out.queue } : {},
495
+ // 新增选填:使用范围一栏(只有调用方传了 includeEnv 才带,老调用形状不变)。
496
+ ...out && out.env ? { env: out.env } : {}
497
+ };
234
498
  } catch (error) {
235
499
  const payload = toUpdateErrorPayload(error);
236
500
  emit("warn", "host.call.fail", { method, kind, errorHash: hash8(String(error?.message || payload.error)), pluginId });
237
- return { ok: false, ...payload };
501
+ let diag;
502
+ try {
503
+ const cfg = diagCtx?.config;
504
+ const overrides = diagCtx?.readerOverrides ?? {};
505
+ const envRaw = overrides?.environmentKind;
506
+ const runningRaw = overrides?.runningVersion;
507
+ const built = buildDiag({
508
+ errorCode: payload.error,
509
+ phoneKind: kind,
510
+ error,
511
+ args: safeArgs,
512
+ targetPackageName: cfg?.targetPackageName,
513
+ registryUrl: cfg?.registryUrl,
514
+ runningVersion: typeof runningRaw === "string" ? runningRaw : void 0,
515
+ latestVersion: void 0,
516
+ environmentKind: typeof envRaw === "string" ? envRaw : void 0,
517
+ latencyMs: Date.now() - t0
518
+ });
519
+ if (built && typeof built === "object") diag = built;
520
+ } catch {
521
+ diag = void 0;
522
+ }
523
+ return { ok: false, ...payload, ...diag ? { diag } : {} };
238
524
  }
239
525
  };
240
526
  }
@@ -252,7 +538,12 @@ function createHostUpdate(deps = {}, configInput) {
252
538
  ...readerOverrides,
253
539
  profileDir: args && args.profileDir ? String(args.profileDir) : readerOverrides.profileDir
254
540
  });
255
- return snapWithManual(reader, await reader.status(), config);
541
+ const out = await snapWithManual(reader, await reader.status(), config);
542
+ const env = wantsEnv(args) ? { env: await readEnvForPanel(reader) } : {};
543
+ const { includeQueue, showOthers } = queueArgsOf(args);
544
+ if (!includeQueue) return { ...out, ...env };
545
+ const requestId = args && typeof args.requestId === "string" ? args.requestId : void 0;
546
+ return { ...out, ...env, queue: visibleQueueFor(await reader.readQueuePruned(), pluginId, showOthers, requestId) };
256
547
  }
257
548
  async function readCheck(args) {
258
549
  const reader = await getSharedReader(pluginId, config, {
@@ -261,21 +552,85 @@ function createHostUpdate(deps = {}, configInput) {
261
552
  });
262
553
  const result = await reader.check();
263
554
  const withManual = await snapWithManual(reader, result.snapshot, config);
264
- return { snapshot: withManual.snapshot, manual: withManual.manual, receipt: result.receipt ?? null };
555
+ const env = wantsEnv(args) ? { env: await readEnvForPanel(reader) } : {};
556
+ const { includeQueue, showOthers } = queueArgsOf(args);
557
+ if (!includeQueue) return { snapshot: withManual.snapshot, manual: withManual.manual, receipt: result.receipt ?? null, ...env };
558
+ const requestId = args && typeof args.requestId === "string" ? args.requestId : void 0;
559
+ return {
560
+ snapshot: withManual.snapshot,
561
+ manual: withManual.manual,
562
+ receipt: result.receipt ?? null,
563
+ ...env,
564
+ queue: visibleQueueFor(await reader.readQueuePruned(), pluginId, showOthers, requestId)
565
+ };
265
566
  }
266
567
  async function runInstall(args) {
267
568
  const checkId = args && typeof args.checkId === "string" ? args.checkId : "";
268
569
  const requestId = args && typeof args.requestId === "string" ? args.requestId : "";
570
+ const { includeQueue, showOthers } = queueArgsOf(args);
571
+ const enqueueOnly = !!args && args["enqueueOnly"] === true;
572
+ const cancelQueued = !!args && args["cancelQueued"] === true;
269
573
  const reader = await getSharedReader(pluginId, config, {
270
574
  ...readerOverrides,
271
575
  profileDir: args && args.profileDir ? String(args.profileDir) : readerOverrides.profileDir
272
576
  });
273
- return snapWithManual(reader, await reader.install({ checkId, requestId }), config);
577
+ const envPatch = wantsEnv(args) ? { env: await readEnvForPanel(reader) } : {};
578
+ if (cancelQueued) {
579
+ if (!validRequestId(requestId)) throw checkExpiredError();
580
+ const current = await reader.readQueuePruned();
581
+ const next = cancelEnqueuedInQueue(current, pluginId, requestId);
582
+ if (next.removed) await reader.writeQueueState(next.state);
583
+ const out = await snapWithManual(reader, await reader.status(), config);
584
+ return { ...out, ...envPatch, queue: visibleQueueFor(next.state, pluginId, showOthers, requestId) };
585
+ }
586
+ if (enqueueOnly) {
587
+ if (!validRequestId(requestId)) throw checkExpiredError();
588
+ const current = await reader.readQueuePruned();
589
+ const placed2 = enqueueInQueue(current, { pluginId, requestId, targetVersion: null, enqueuedAt: reader.queueNow() });
590
+ if (placed2.state !== current) await reader.writeQueueState(placed2.state);
591
+ const out = await snapWithManual(reader, await reader.status(), config);
592
+ return { ...out, ...envPatch, queue: visibleQueueFor(placed2.state, pluginId, showOthers, requestId) };
593
+ }
594
+ if (!checkId || !validRequestId(requestId)) {
595
+ const out = await snapWithManual(reader, await reader.install({ checkId, requestId }), config);
596
+ if (!includeQueue) return { ...out, ...envPatch };
597
+ return { ...out, ...envPatch, queue: visibleQueueFor(await reader.readQueuePruned(), pluginId, showOthers, requestId) };
598
+ }
599
+ const before = await reader.readQueuePruned();
600
+ const placed = enqueueInQueue(before, { pluginId, requestId, targetVersion: null, enqueuedAt: reader.queueNow() });
601
+ if (placed.state !== before) await reader.writeQueueState(placed.state);
602
+ if (queuePositionOf(placed.state, pluginId, requestId) !== 0 && !isHeadOfQueue(placed.state, pluginId, requestId)) {
603
+ throw updateBusyError();
604
+ }
605
+ try {
606
+ const out = await snapWithManual(reader, await reader.install({ checkId, requestId }), config);
607
+ if (!includeQueue) return { ...out, ...envPatch };
608
+ return { ...out, ...envPatch, queue: visibleQueueFor(await reader.readQueuePruned(), pluginId, showOthers, requestId) };
609
+ } catch (error) {
610
+ if (error?.code !== "update-busy") {
611
+ try {
612
+ const current = await reader.readQueuePruned();
613
+ const next = cancelEnqueuedInQueue(current, pluginId, requestId);
614
+ if (next.removed) await reader.writeQueueState(next.state);
615
+ } catch {
616
+ }
617
+ }
618
+ throw error;
619
+ }
274
620
  }
275
621
  const handlers = {
276
- [phoneNames.updateStatus]: loggedPhone(phoneNames.updateStatus, "update-status", pluginId, readStatus),
277
- [phoneNames.updateCheck]: loggedPhone(phoneNames.updateCheck, "update-check", pluginId, readCheck),
278
- [phoneNames.updateInstall]: loggedPhone(phoneNames.updateInstall, "update-install", pluginId, runInstall)
622
+ [phoneNames.updateStatus]: loggedPhone(phoneNames.updateStatus, "update-status", pluginId, readStatus, {
623
+ config,
624
+ readerOverrides
625
+ }),
626
+ [phoneNames.updateCheck]: loggedPhone(phoneNames.updateCheck, "update-check", pluginId, readCheck, {
627
+ config,
628
+ readerOverrides
629
+ }),
630
+ [phoneNames.updateInstall]: loggedPhone(phoneNames.updateInstall, "update-install", pluginId, runInstall, {
631
+ config,
632
+ readerOverrides
633
+ })
279
634
  };
280
635
  return { phoneNames, handlers };
281
636
  }
@@ -288,22 +643,105 @@ function __resetSharedUpdateReaderForTests() {
288
643
  sharedDesktopPnpm = null;
289
644
  }
290
645
  export {
646
+ CHANGELOG_ALL_CATEGORIES,
647
+ CHANGELOG_DEFAULT_REGISTRY,
648
+ CHANGELOG_FETCH_MAX_BYTES,
649
+ CHANGELOG_FILENAME,
650
+ CHANGELOG_FOLDED,
651
+ CHANGELOG_MAX_BULLETS_PER_SECTION,
652
+ CHANGELOG_MAX_BULLET_CHARS,
653
+ CHANGELOG_MAX_CHARS,
654
+ CHANGELOG_MAX_ENTRIES,
655
+ CHANGELOG_MUST_SHOW,
656
+ CHANGELOG_NEUTRAL_HINT,
657
+ CHANGELOG_NEUTRAL_LINE,
658
+ CHANGELOG_TARBALL_MAX_BYTES,
659
+ CHANGELOG_TAR_MAX_BYTES,
660
+ COPY_BUDGET_CHARS,
661
+ DETAIL_MAX_CHARS,
662
+ DIAG_ACTIONS,
663
+ DIAG_DROP_ORDER,
664
+ DIAG_INLINE_BUDGET_BYTES,
665
+ DIAG_KEYS,
666
+ DIAG_KNOWN_TYPES,
667
+ DIAG_NEVER_DROP_KEYS,
668
+ DIAG_STAGES,
669
+ DIAG_STAGE_EXPECT,
670
+ DIAG_VERSION,
671
+ ENDORSED_REGISTRY_HOSTS,
672
+ KNOWN_GAP_FILE_URL_PREFIX,
673
+ MAX_SKIPPED_ENTRIES,
674
+ QUEUE_DIR_SEGMENT,
675
+ QUEUE_FILE,
676
+ QUEUE_INTENT_TTL_MS2 as QUEUE_INTENT_TTL_MS,
677
+ QUEUE_LOCK_FILE,
678
+ QUEUE_LOCK_STALE_MS,
679
+ REDACTED_PATH,
680
+ REDACTED_SECRET,
681
+ REDACTION_RULE_NAMES,
682
+ SKIPPED_FILE,
291
683
  __resetSharedUpdateReaderForTests,
684
+ addSkipped,
685
+ buildDiag2 as buildDiag,
292
686
  buildPhoneName,
293
687
  buildPhoneNames2 as buildPhoneNames,
688
+ cancelEnqueuedInQueue2 as cancelEnqueuedInQueue,
689
+ changelogForUpdate,
294
690
  checkEventCounts,
295
691
  checkEventFields,
692
+ clearSkipped,
693
+ compareReleaseVersions,
694
+ compareVersions,
296
695
  containingPackage,
297
696
  createHostUpdate,
697
+ createSkipDiskPorts,
298
698
  createUpdateDiskPorts2 as createUpdateDiskPorts,
299
699
  createUpdateExecutor2 as createUpdateExecutor,
700
+ createUpdateQueuePorts2 as createUpdateQueuePorts,
300
701
  defaultHomeDir2 as defaultHomeDir,
702
+ deriveAction,
703
+ deriveRouteMethod,
704
+ deriveStage,
301
705
  detectEnvironmentKind,
706
+ diagByteLength,
707
+ emptyQueueState2 as emptyQueueState,
708
+ enforceDiagBudget,
709
+ enforceBudget as enforceDiagBudgetForPhone,
710
+ enqueueInQueue2 as enqueueInQueue,
711
+ extractChangelogFromTar,
712
+ fetchReleaseChangelogText,
713
+ hasVisibleSections,
714
+ isHeadOfQueue2 as isHeadOfQueue,
715
+ isPrereleaseVersion,
716
+ isUnreleasedVersion,
717
+ isVersionAllowedInChannel2 as isVersionAllowedInChannel,
718
+ isVersionSkipped,
719
+ normalizeQueueState2 as normalizeQueueState,
720
+ normalizeSkipped,
721
+ parseChangelog,
302
722
  parseEventListManifest,
303
723
  pathsForUpdate,
304
724
  profileNameValid,
725
+ pruneExpiredIntents2 as pruneExpiredIntents,
726
+ queuePathsForUpdate,
727
+ queuePositionOf2 as queuePositionOf,
728
+ readInstalledChangelogText,
305
729
  registrySpec,
730
+ releaseOwnerInQueue2 as releaseOwnerInQueue,
731
+ renderChangelogHTML,
732
+ renderChangelogNeutral,
733
+ renderChangelogSection,
306
734
  resolveCliEntry,
735
+ resolveRegistryHost,
307
736
  resolveTargetPackage2 as resolveTargetPackage,
308
- resolveUpdateConfig2 as resolveUpdateConfig
737
+ resolveUpdateConfig2 as resolveUpdateConfig,
738
+ sanitizeDetail,
739
+ sanitizeForCopy,
740
+ selectChangelogEntries,
741
+ setOwnerIfFree2 as setOwnerIfFree,
742
+ skipKey,
743
+ truncateToWordBoundary,
744
+ validReleaseVersion,
745
+ validVersion,
746
+ visibleQueueFor2 as visibleQueueFor
309
747
  };