dshmarket 1.65.2 → 1.65.3

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/hot.js CHANGED
@@ -30,8 +30,6 @@ import { pathToFileURL } from 'node:url';
30
30
  import { asChannel } from './channels.js';
31
31
  import { asRegion, normalizeGithubProxy } from './regions.js';
32
32
  import { logEvent } from './log.js';
33
- import { writeFileAtomic } from './atomic-write.js';
34
- import { waitForLifecycle } from './lifecycle.js';
35
33
  import { declaredBundlePatchFile, entryArtifactExists } from './profile.js';
36
34
  /**
37
35
  * Profile-scoped resolution for hot-mount rows: turn a bare package name into
@@ -379,10 +377,7 @@ export function writeMarketState(profileDir, state) {
379
377
  const broken = Object.prototype.hasOwnProperty.call(state, 'brokenPlugins')
380
378
  ? state.brokenPlugins
381
379
  : onDisk.brokenPlugins;
382
- // Written atomically: a crash or a refused rename mid-write must not
383
- // leave a truncated state.json behind, because the next boot reads it as
384
- // the user‘s disable list and the toggle that produced it is gone (#582).
385
- const text = JSON.stringify({
380
+ writeFileSync(stateFile(profileDir), JSON.stringify({
386
381
  disabled: [...state.disabled],
387
382
  groups: state.groups,
388
383
  groupOrder: state.groupOrder,
@@ -406,8 +401,7 @@ export function writeMarketState(profileDir, state) {
406
401
  // Omitted while not saved, so a card that was never touched keeps
407
402
  // inheriting the composition's buildEnv on every boot.
408
403
  ...(state.buildEnv === undefined ? {} : { buildEnv: state.buildEnv }),
409
- });
410
- writeFileAtomic(stateFile(profileDir), text);
404
+ }));
411
405
  }
412
406
  /** Plugins the user switched off; skipped by the boot re-mount. */
413
407
  export function readDisabled(profileDir) {
@@ -455,54 +449,19 @@ function raceActivationTimeout(awaitable) {
455
449
  * @param packageName - package to unmount.
456
450
  * @returns true when a live hot mount was found and disposed.
457
451
  */
458
- const hotDisposalErrors = new Map();
459
- /**
460
- * Why the last disposal of this package failed, if it did.
461
- *
462
- * A rejected disposal leaves runtime state nobody can describe, so the toggle
463
- * must refuse to enable that package again until a disposal has been seen
464
- * through. The message is kept addressable for exactly that refusal.
465
- */
466
- export function hotMountDisposalError(packageName) {
467
- return hotDisposalErrors.get(packageName);
468
- }
469
- /**
470
- * Remove a live market hot mount.
471
- *
472
- * @param strict - a toggle caller: retain the handle when disposal fails (so
473
- * the failure stays addressable for a retry) and propagate the error instead
474
- * of reporting a removal that did not happen. Legacy callers keep best-effort
475
- * removal, which is what the boot replay and the recovery paths expect.
476
- */
477
- export async function hotUnmount(packageName, strict = false) {
452
+ export async function hotUnmount(packageName) {
478
453
  const handle = hotHandles.get(packageName);
479
454
  if (handle === undefined)
480
455
  return false;
481
- if (!strict) {
482
- hotHandles.delete(packageName);
483
- shimNames.delete(packageName);
484
- hotDisposalErrors.delete(packageName);
485
- }
456
+ hotHandles.delete(packageName);
457
+ shimNames.delete(packageName);
486
458
  try {
487
- if (strict)
488
- await waitForLifecycle(handle, () => handle.dispose(), `${packageName}: dispose`);
489
- else
490
- await handle.dispose();
491
- if (!strict || hotHandles.get(packageName) === handle) {
492
- hotHandles.delete(packageName);
493
- shimNames.delete(packageName);
494
- hotDisposalErrors.delete(packageName);
495
- }
459
+ await handle.dispose();
496
460
  logEvent('info', 'hot-unmount', `${packageName}: removed live`);
497
461
  return true;
498
462
  }
499
463
  catch (error) {
500
- const reason = `${packageName}: dispose failed — ${error instanceof Error ? error.message : String(error)}`;
501
- if (strict) {
502
- hotDisposalErrors.set(packageName, reason);
503
- throw new Error(reason);
504
- }
505
- logEvent('warn', 'hot-unmount', reason);
464
+ logEvent('warn', 'hot-unmount', `${packageName}: dispose failed — ${error instanceof Error ? error.message : String(error)}`);
506
465
  return false;
507
466
  }
508
467
  }
@@ -522,7 +481,6 @@ export async function hotMount(ctx, profileDir, packageName) {
522
481
  if (HotTree === null) {
523
482
  return {
524
483
  ok: false,
525
- outcome: 'deferred',
526
484
  reason: '宿主不支持热挂载(include 插件不可导入),需重启 / the host cannot hot-mount (include plugin unavailable); restart required',
527
485
  };
528
486
  }
@@ -550,7 +508,6 @@ export async function hotMount(ctx, profileDir, packageName) {
550
508
  if (rows === null) {
551
509
  return {
552
510
  ok: false,
553
- outcome: 'deferred',
554
511
  reason: 'bundle patch 含配置行/表达式,热挂载仅支持纯 insert,重启后生效 / the bundle patch contains config/expression rows; hot-mount only supports plain inserts — it activates on restart',
555
512
  };
556
513
  }
@@ -564,7 +521,6 @@ export async function hotMount(ctx, profileDir, packageName) {
564
521
  if (dsh === null || dsh.client === undefined || dsh.bundle !== undefined) {
565
522
  return {
566
523
  ok: false,
567
- outcome: 'failed',
568
524
  reason: '该包无 bundle patch 且未声明 dsh.client,没有可热挂载的内容 / no bundle patch and no dsh.client surface — nothing to hot-mount',
569
525
  };
570
526
  }
@@ -613,27 +569,15 @@ export async function hotMount(ctx, profileDir, packageName) {
613
569
  throw error;
614
570
  }
615
571
  hotHandles.set(packageName, handle);
616
- hotDisposalErrors.delete(packageName);
617
572
  ctx.logger?.info?.(`[dsh-market] hot-mounted ${packageName}`);
618
573
  logEvent('info', 'hot-mount', `${packageName}: live${shimNames.has(packageName) ? ' (client-only shim)' : ''}`);
619
- return { ok: true, outcome: 'live', reason: null };
574
+ return { ok: true, reason: null };
620
575
  }
621
576
  catch (error) {
622
577
  const message = error instanceof Error ? error.message : String(error);
623
- // The distinction the toggle needs: a timeout is the host failing to
624
- // settle, a rejection is the plugin failing. The first keeps the restart
625
- // contract the market has always shown; the second is evidence a durable
626
- // enable must not be published from.
627
- const deferred = error instanceof ActivationTimeout;
628
- ctx.logger?.warn(`[dsh-market] hot mount of ${packageName} failed: ${message}`);
629
- logEvent('warn', 'hot-mount', `${packageName}: ${deferred ? 'fell back to restart' : 'activation failed'} — ${message}`);
630
- return {
631
- ok: false,
632
- outcome: deferred ? 'deferred' : 'failed',
633
- reason: deferred
634
- ? `热挂载超时,需重启 — ${message} / hot-mount timed out — restart required: ${message}`
635
- : `热挂载失败 — ${message} / hot-mount failed: ${message}`,
636
- };
578
+ ctx.logger?.warn(`[dsh-market] hot mount of ${packageName} failed, restart required: ${message}`);
579
+ logEvent('warn', 'hot-mount', `${packageName}: fell back to restart — ${message}`);
580
+ return { ok: false, reason: `热挂载失败,重启后生效 — ${message} / hot-mount failed — restart required: ${message}` };
637
581
  }
638
582
  }
639
583
  /**
package/lib/patch.js CHANGED
@@ -489,7 +489,7 @@ function appendPatchEntry(patchPath, block) {
489
489
  return { ok: true, reason: null };
490
490
  }
491
491
  /** Disable one row: append `- id: X` + `disabled: true` (idempotent). */
492
- export function disableRow(patchPath, rowId, logChange = true) {
492
+ export function disableRow(patchPath, rowId) {
493
493
  return queuedWrite(async () => {
494
494
  if (!ROW_ID_RE.test(rowId)) {
495
495
  return { ok: false, reason: `行 id 含特殊字符,不支持写入补丁层 / row id ${rowId} cannot be written to the patch layer` };
@@ -498,20 +498,14 @@ export function disableRow(patchPath, rowId, logChange = true) {
498
498
  if (state.disables.includes(rowId))
499
499
  return { ok: true, reason: null };
500
500
  const result = appendPatchEntry(patchPath, rowBlock(rowId, true));
501
- if (result.ok && logChange)
501
+ if (result.ok)
502
502
  logEvent('info', 'patch', `disabled row ${rowId} in ${patchPath}`);
503
503
  return result;
504
504
  });
505
505
  }
506
- /**
507
- * Enable one row: remove the `disabled: true` block; force-enable with
508
- * `disabled: false` when a lower layer (bundle/home patch) holds it down.
509
- *
510
- * `logChange` is false for a scratch write — the toggle stages and validates
511
- * its rows on a copy first (#582), and a log line claiming a row was written
512
- * before anything was published is worse than no line at all.
513
- */
514
- export function enableRow(patchPath, rowId, logChange = true) {
506
+ /** Enable one row: remove the `disabled: true` block; force-enable with
507
+ * `disabled: false` when a lower layer (bundle/home patch) holds it down. */
508
+ export function enableRow(patchPath, rowId) {
515
509
  return queuedWrite(async () => {
516
510
  if (!ROW_ID_RE.test(rowId)) {
517
511
  return { ok: false, reason: `行 id 含特殊字符,不支持写入补丁层 / row id ${rowId} cannot be written to the patch layer` };
@@ -528,14 +522,13 @@ export function enableRow(patchPath, rowId, logChange = true) {
528
522
  })();
529
523
  if (blockRe.test(text)) {
530
524
  writeFileSync(patchPath, withPlaceholderRestored(text.replace(blockRe, '')));
531
- if (logChange)
532
- logEvent('info', 'patch', `enabled row ${rowId} in ${patchPath}`);
525
+ logEvent('info', 'patch', `enabled row ${rowId} in ${patchPath}`);
533
526
  return { ok: true, reason: null };
534
527
  }
535
528
  if (state.forced.includes(rowId))
536
529
  return { ok: true, reason: null };
537
530
  const result = appendPatchEntry(patchPath, rowBlock(rowId, false));
538
- if (result.ok && logChange)
531
+ if (result.ok)
539
532
  logEvent('info', 'patch', `force-enabled row ${rowId} in ${patchPath}`);
540
533
  return result;
541
534
  });