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/client/client.js +129 -58
- package/lib/hot.js +11 -67
- package/lib/patch.js +7 -14
- package/lib/routes.js +305 -232
- package/lib/themes.js +44 -121
- package/lib/types/hot.d.ts +9 -38
- package/lib/types/patch.d.ts +4 -10
- package/lib/types/themes.d.ts +0 -15
- package/package.json +1 -1
- package/src/client/Market.module.css +5 -2
- package/src/client/MarketSection.tsx +99 -42
- package/src/client/optional-primitives.ts +90 -0
- package/src/hot.ts +16 -79
- package/src/patch.ts +7 -13
- package/src/routes.ts +293 -242
- package/src/themes.ts +39 -116
- package/lib/atomic-write.js +0 -47
- package/lib/toggle-state.js +0 -135
- package/lib/types/atomic-write.d.ts +0 -5
- package/lib/types/toggle-state.d.ts +0 -18
- package/src/atomic-write.ts +0 -35
- package/src/toggle-state.ts +0 -123
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
shimNames.delete(packageName);
|
|
484
|
-
hotDisposalErrors.delete(packageName);
|
|
485
|
-
}
|
|
456
|
+
hotHandles.delete(packageName);
|
|
457
|
+
shimNames.delete(packageName);
|
|
486
458
|
try {
|
|
487
|
-
|
|
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
|
-
|
|
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,
|
|
574
|
+
return { ok: true, reason: null };
|
|
620
575
|
}
|
|
621
576
|
catch (error) {
|
|
622
577
|
const message = error instanceof Error ? error.message : String(error);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
|
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
|
|
501
|
+
if (result.ok)
|
|
502
502
|
logEvent('info', 'patch', `disabled row ${rowId} in ${patchPath}`);
|
|
503
503
|
return result;
|
|
504
504
|
});
|
|
505
505
|
}
|
|
506
|
-
/**
|
|
507
|
-
*
|
|
508
|
-
|
|
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
|
-
|
|
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
|
|
531
|
+
if (result.ok)
|
|
539
532
|
logEvent('info', 'patch', `force-enabled row ${rowId} in ${patchPath}`);
|
|
540
533
|
return result;
|
|
541
534
|
});
|