@linxin666/dsh-client-ui-skin-center 0.1.4 → 0.1.6
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/client.js +38 -38
- package/lib/index.js +72 -33
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -464,8 +464,8 @@ window.__ModuleLoader__.load({
|
|
|
464
464
|
}
|
|
465
465
|
};
|
|
466
466
|
//#endregion
|
|
467
|
-
//#region \0dsh-css:/
|
|
468
|
-
const css = "body[data-dsh-skin-center] .
|
|
467
|
+
//#region \0dsh-css:/home/runner/work/dsh-web-ui/dsh-web-ui/packages/skins/skin-center/src/client/skin-center.module.css.mjs
|
|
468
|
+
const css = "body[data-dsh-skin-center] .aIQHxa_pluginCard{border:1px solid var(--dsw-alias-border-l1,#e2e8f0);background:var(--dsw-alias-bg-layer-2,#fff);border-radius:8px;list-style:none;overflow:hidden}body[data-dsh-skin-center] .aIQHxa_cardHeader{width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;padding:11px 14px;transition:background .12s;display:flex}body[data-dsh-skin-center] .aIQHxa_cardHeader:hover{background:var(--dsw-alias-bg-layer-1,#f1f5f9)}body[data-dsh-skin-center] .aIQHxa_cardHeader:active{background:var(--dsw-alias-bg-layer-3,#e6ecf4)}body[data-dsh-skin-center] .aIQHxa_cardHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .aIQHxa_headText{flex-direction:column;flex:1;gap:3px;min-width:0;display:flex}body[data-dsh-skin-center] .aIQHxa_pluginName{color:var(--dsw-alias-label-primary,#172a45);align-items:baseline;gap:8px;font-size:13.5px;font-weight:600;display:flex}body[data-dsh-skin-center] .aIQHxa_cardDescription{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.4}body[data-dsh-skin-center] .aIQHxa_chevron,body[data-dsh-skin-center] .aIQHxa_chevronOpen{color:var(--dsw-alias-label-secondary,#6b7280);flex:none;margin-left:10px;font-size:12px;transition:transform .12s}body[data-dsh-skin-center] .aIQHxa_chevronOpen{transform:rotate(180deg)}body[data-dsh-skin-center] .aIQHxa_cardBody{border-top:1px solid var(--dsw-alias-border-l1,#e2e8f0);flex-direction:column;gap:12px;padding:12px 14px 14px;display:flex}body[data-dsh-skin-center] .aIQHxa_head{flex-direction:column;gap:6px;display:flex}body[data-dsh-skin-center] .aIQHxa_titleBadge{color:var(--dsw-alias-label-secondary,#6b7280);font-size:11px;font-weight:500}body[data-dsh-skin-center] .aIQHxa_intro{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12.5px;line-height:1.55}body[data-dsh-skin-center] .aIQHxa_themeRow{align-items:center;gap:8px;margin-top:2px;display:flex}body[data-dsh-skin-center] .aIQHxa_themeLabel{color:var(--dsw-alias-label-secondary,#6b7280);margin-right:2px;font-size:12px}body[data-dsh-skin-center] .aIQHxa_themeButton{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#172a45);cursor:pointer;border-radius:6px;padding:5px 10px;font-size:12px;line-height:1;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .aIQHxa_themeButton:hover{border-color:var(--dsw-alias-border-l4,#94a3b8)}body[data-dsh-skin-center] .aIQHxa_themeButton:active{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .aIQHxa_themeButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .aIQHxa_themeButtonActive{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .aIQHxa_list{flex-direction:column;gap:10px;display:flex}body[data-dsh-skin-center] .aIQHxa_card{border:1px solid var(--dsw-alias-border-l1,#e2e8f0);background:var(--dsw-alias-bg-layer-2,#fff);border-radius:10px;flex-direction:column;gap:8px;padding:12px 14px;display:flex}body[data-dsh-skin-center] .aIQHxa_cardHead{align-items:center;gap:10px;min-width:0;display:flex}body[data-dsh-skin-center] .aIQHxa_swatch{width:14px;height:14px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;flex:none}body[data-dsh-skin-center] .aIQHxa_cardName{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:13.5px;font-weight:600;overflow:hidden}body[data-dsh-skin-center] .aIQHxa_cardTagline{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.45}body[data-dsh-skin-center] .aIQHxa_badge{letter-spacing:.02em;border-radius:999px;flex:none;min-width:0;margin-left:auto;padding:2px 8px;font-size:11px;font-weight:600}body[data-dsh-skin-center] .aIQHxa_badgeActive{color:var(--dsw-alias-state-success-primary,#0f6b3a);background:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .aIQHxa_badgeTrying{color:var(--dsw-alias-brand-primary,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e2edfc)}body[data-dsh-skin-center] .aIQHxa_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .aIQHxa_button{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#172a45);cursor:pointer;border-radius:7px;padding:6px 12px;font-size:12px;line-height:1;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .aIQHxa_button:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary,#2b7cd9);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .aIQHxa_button:active:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .aIQHxa_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .aIQHxa_buttonPrimary{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-fill,#2b7cd9);color:var(--dsw-alias-label-primary-foreground,#fff)}body[data-dsh-skin-center] .aIQHxa_buttonPrimary:hover:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-hover,#1e63b8);color:var(--dsw-alias-label-primary-foreground,#fff)}body[data-dsh-skin-center] .aIQHxa_buttonPrimary:active:not(:disabled),body[data-dsh-skin-center] .aIQHxa_buttonPrimary:focus-visible:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-hover,#1e63b8)}body[data-dsh-skin-center] .aIQHxa_buttonGhost{background:0 0;border-color:#0000}body[data-dsh-skin-center] .aIQHxa_button:disabled{opacity:.55;cursor:default}body[data-dsh-skin-center] .aIQHxa_error{color:var(--dsw-alias-state-error-primary,#b42318);font-size:12px}body[data-dsh-skin-center] .aIQHxa_backgroundRow{flex-direction:column;gap:6px;padding:8px 0;display:flex}body[data-dsh-skin-center] .aIQHxa_backgroundHead{align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .aIQHxa_backgroundLabel{color:var(--dsw-alias-label-primary,#172a45);font-size:12.5px;font-weight:600}body[data-dsh-skin-center] .aIQHxa_backgroundValue{font-variant-numeric:tabular-nums;color:var(--dsw-alias-brand-primary,#2b7cd9);flex:none;margin-left:auto;font-size:12px}body[data-dsh-skin-center] .aIQHxa_backgroundRange{background:var(--dsw-alias-bg-layer-3,#e2e8f0);-webkit-appearance:none;appearance:none;cursor:pointer;border-radius:999px;width:100%;height:4px;margin:0}body[data-dsh-skin-center] .aIQHxa_backgroundRange::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:14px;height:14px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .aIQHxa_backgroundRange::-moz-range-thumb{border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:12px;height:12px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .aIQHxa_backgroundRange:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .aIQHxa_backgroundHint{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.5}body[data-dsh-skin-center] .aIQHxa_backgroundHintMuted{color:var(--dsw-alias-label-tertiary,#9aa4b5);font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){body[data-dsh-skin-center] .aIQHxa_cardHeader,body[data-dsh-skin-center] .aIQHxa_themeButton,body[data-dsh-skin-center] .aIQHxa_button,body[data-dsh-skin-center] .aIQHxa_chevron,body[data-dsh-skin-center] .aIQHxa_chevronOpen{transition:none}}";
|
|
469
469
|
const tagId = "@linxin666/dsh-client-ui-skin-center/skin-center.module.css";
|
|
470
470
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
471
471
|
const tag = document.createElement("style");
|
|
@@ -475,42 +475,42 @@ window.__ModuleLoader__.load({
|
|
|
475
475
|
document.head.appendChild(tag);
|
|
476
476
|
}
|
|
477
477
|
var skin_center_module_css_default = {
|
|
478
|
-
"actions": "
|
|
479
|
-
"backgroundHead": "
|
|
480
|
-
"backgroundHint": "
|
|
481
|
-
"backgroundHintMuted": "
|
|
482
|
-
"backgroundLabel": "
|
|
483
|
-
"backgroundRange": "
|
|
484
|
-
"backgroundRow": "
|
|
485
|
-
"backgroundValue": "
|
|
486
|
-
"badge": "
|
|
487
|
-
"badgeActive": "
|
|
488
|
-
"badgeTrying": "
|
|
489
|
-
"button": "
|
|
490
|
-
"buttonGhost": "
|
|
491
|
-
"buttonPrimary": "
|
|
492
|
-
"card": "
|
|
493
|
-
"cardBody": "
|
|
494
|
-
"cardDescription": "
|
|
495
|
-
"cardHead": "
|
|
496
|
-
"cardHeader": "
|
|
497
|
-
"cardName": "
|
|
498
|
-
"cardTagline": "
|
|
499
|
-
"chevron": "
|
|
500
|
-
"chevronOpen": "
|
|
501
|
-
"error": "
|
|
502
|
-
"head": "
|
|
503
|
-
"headText": "
|
|
504
|
-
"intro": "
|
|
505
|
-
"list": "
|
|
506
|
-
"pluginCard": "
|
|
507
|
-
"pluginName": "
|
|
508
|
-
"swatch": "
|
|
509
|
-
"themeButton": "
|
|
510
|
-
"themeButtonActive": "
|
|
511
|
-
"themeLabel": "
|
|
512
|
-
"themeRow": "
|
|
513
|
-
"titleBadge": "
|
|
478
|
+
"actions": "aIQHxa_actions",
|
|
479
|
+
"backgroundHead": "aIQHxa_backgroundHead",
|
|
480
|
+
"backgroundHint": "aIQHxa_backgroundHint",
|
|
481
|
+
"backgroundHintMuted": "aIQHxa_backgroundHintMuted",
|
|
482
|
+
"backgroundLabel": "aIQHxa_backgroundLabel",
|
|
483
|
+
"backgroundRange": "aIQHxa_backgroundRange",
|
|
484
|
+
"backgroundRow": "aIQHxa_backgroundRow",
|
|
485
|
+
"backgroundValue": "aIQHxa_backgroundValue",
|
|
486
|
+
"badge": "aIQHxa_badge",
|
|
487
|
+
"badgeActive": "aIQHxa_badgeActive",
|
|
488
|
+
"badgeTrying": "aIQHxa_badgeTrying",
|
|
489
|
+
"button": "aIQHxa_button",
|
|
490
|
+
"buttonGhost": "aIQHxa_buttonGhost",
|
|
491
|
+
"buttonPrimary": "aIQHxa_buttonPrimary",
|
|
492
|
+
"card": "aIQHxa_card",
|
|
493
|
+
"cardBody": "aIQHxa_cardBody",
|
|
494
|
+
"cardDescription": "aIQHxa_cardDescription",
|
|
495
|
+
"cardHead": "aIQHxa_cardHead",
|
|
496
|
+
"cardHeader": "aIQHxa_cardHeader",
|
|
497
|
+
"cardName": "aIQHxa_cardName",
|
|
498
|
+
"cardTagline": "aIQHxa_cardTagline",
|
|
499
|
+
"chevron": "aIQHxa_chevron",
|
|
500
|
+
"chevronOpen": "aIQHxa_chevronOpen",
|
|
501
|
+
"error": "aIQHxa_error",
|
|
502
|
+
"head": "aIQHxa_head",
|
|
503
|
+
"headText": "aIQHxa_headText",
|
|
504
|
+
"intro": "aIQHxa_intro",
|
|
505
|
+
"list": "aIQHxa_list",
|
|
506
|
+
"pluginCard": "aIQHxa_pluginCard",
|
|
507
|
+
"pluginName": "aIQHxa_pluginName",
|
|
508
|
+
"swatch": "aIQHxa_swatch",
|
|
509
|
+
"themeButton": "aIQHxa_themeButton",
|
|
510
|
+
"themeButtonActive": "aIQHxa_themeButtonActive",
|
|
511
|
+
"themeLabel": "aIQHxa_themeLabel",
|
|
512
|
+
"themeRow": "aIQHxa_themeRow",
|
|
513
|
+
"titleBadge": "aIQHxa_titleBadge"
|
|
514
514
|
};
|
|
515
515
|
//#endregion
|
|
516
516
|
//#region src/client/SkinCenter.tsx
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
2
|
import z from "schemastery";
|
|
3
|
-
import { lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, rmdirSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -23,14 +23,14 @@ import { fileURLToPath } from "node:url";
|
|
|
23
23
|
* @module @linxin666/dsh-client-ui-skin-center/skin-switch
|
|
24
24
|
*/
|
|
25
25
|
/**
|
|
26
|
-
* Walk up from a file location to the nearest
|
|
27
|
-
*
|
|
26
|
+
* Walk up from a file location to the nearest @linxin666/ scoped dir
|
|
27
|
+
* whose entries actually hold skin packages (dsh-skins carrier or
|
|
28
28
|
* dsh-client-ui-skin-* packages). pnpm's virtual store realpaths packages
|
|
29
29
|
* into node_modules/.pnpm/<pkg>@<ver>/node_modules/<name>, so a plain
|
|
30
30
|
* '../../' from the skin-center package can never see its siblings there —
|
|
31
|
-
* this anchor finds the
|
|
31
|
+
* this anchor finds the scoped dir that owns them.
|
|
32
32
|
* @param fromDir - the realpathed package dir to walk up from.
|
|
33
|
-
* @returns the
|
|
33
|
+
* @returns the scoped skin dir (the skins root), or null when none is found.
|
|
34
34
|
*/
|
|
35
35
|
function findScopedAnchor(fromDir) {
|
|
36
36
|
let current = fromDir;
|
|
@@ -38,8 +38,8 @@ function findScopedAnchor(fromDir) {
|
|
|
38
38
|
const scoped = join(current, "@linxin666");
|
|
39
39
|
try {
|
|
40
40
|
for (const entry of readdirSync(scoped)) {
|
|
41
|
-
if (entry === "dsh-skins") return
|
|
42
|
-
if (entry.startsWith("dsh-client-ui-skin-") && entry !== "dsh-client-ui-skin-center") return
|
|
41
|
+
if (entry === "dsh-skins") return scoped;
|
|
42
|
+
if (entry.startsWith("dsh-client-ui-skin-") && entry !== "dsh-client-ui-skin-center") return scoped;
|
|
43
43
|
}
|
|
44
44
|
} catch {}
|
|
45
45
|
const parent = dirname(current);
|
|
@@ -58,15 +58,18 @@ function findScopedAnchor(fromDir) {
|
|
|
58
58
|
* node_modules/skins/ under npm — the ENOENT of
|
|
59
59
|
* zhu1090093659/dsh-web-ui#21/#33/#34), kept as a fallback.
|
|
60
60
|
* DSH_SKINS_DIR overrides everything (tests use it).
|
|
61
|
+
* @param fromUrl - the module URL to resolve from (defaults to this module's
|
|
62
|
+
* own import.meta.url); injectable so tests can place the module inside a
|
|
63
|
+
* simulated install layout and exercise the real candidate chain.
|
|
61
64
|
*/
|
|
62
|
-
function resolveSkinsDir() {
|
|
65
|
+
function resolveSkinsDir(fromUrl = import.meta.url) {
|
|
63
66
|
const fromEnv = process.env.DSH_SKINS_DIR;
|
|
64
67
|
if (fromEnv !== void 0 && fromEnv !== "") return fromEnv;
|
|
65
|
-
const here = fileURLToPath(
|
|
68
|
+
const here = fileURLToPath(fromUrl);
|
|
66
69
|
const candidates = [
|
|
67
|
-
fileURLToPath(new URL("../../",
|
|
70
|
+
fileURLToPath(new URL("../../", fromUrl)),
|
|
68
71
|
findScopedAnchor(dirname(here)),
|
|
69
|
-
fileURLToPath(new URL("../../../skins/",
|
|
72
|
+
fileURLToPath(new URL("../../../skins/", fromUrl))
|
|
70
73
|
].filter((candidate) => candidate !== null);
|
|
71
74
|
for (const candidate of candidates) if (listSkinDirCandidates(candidate).length > 0) return candidate;
|
|
72
75
|
return candidates[0];
|
|
@@ -124,25 +127,23 @@ function listSkinDirCandidates(skinsDir) {
|
|
|
124
127
|
} catch {
|
|
125
128
|
return out;
|
|
126
129
|
}
|
|
130
|
+
const isDir = (p) => statSync(p, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
127
131
|
for (const dir of entries) {
|
|
128
132
|
const candidate = join(skinsDir, dir);
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (statSync(join(subDir, "skin.json"), { throwIfNoEntry: false })) out.push(subDir);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
133
|
+
if (!isDir(candidate)) continue;
|
|
134
|
+
if (statSync(join(candidate, "skin.json"), { throwIfNoEntry: false })) out.push(candidate);
|
|
135
|
+
}
|
|
136
|
+
const bundled = join(skinsDir, "dsh-skins", "skins");
|
|
137
|
+
let subdirs;
|
|
138
|
+
try {
|
|
139
|
+
subdirs = readdirSync(bundled);
|
|
140
|
+
} catch {
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
for (const sub of subdirs) {
|
|
144
|
+
const subDir = join(bundled, sub);
|
|
145
|
+
if (!isDir(subDir)) continue;
|
|
146
|
+
if (statSync(join(subDir, "skin.json"), { throwIfNoEntry: false })) out.push(subDir);
|
|
146
147
|
}
|
|
147
148
|
return out;
|
|
148
149
|
}
|
|
@@ -161,6 +162,10 @@ function loadRegistry(skinsDir = SKINS_DIR) {
|
|
|
161
162
|
for (const dir of listSkinDirCandidates(skinsDir)) {
|
|
162
163
|
const meta = readSkinMeta(dir);
|
|
163
164
|
if (meta === null || meta.wiring === void 0 || meta.package === void 0) continue;
|
|
165
|
+
if (out[meta.id] !== void 0) {
|
|
166
|
+
console.warn("[skin-center] duplicate skin id \"" + meta.id + "\": keeping " + out[meta.id].dir + ", ignoring " + dir);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
164
169
|
out[meta.id] = {
|
|
165
170
|
pkg: meta.package,
|
|
166
171
|
id: meta.wiring.id,
|
|
@@ -299,9 +304,12 @@ function ensureSymlink(entry, profileModulesDir) {
|
|
|
299
304
|
} catch {}
|
|
300
305
|
if (stat) if (stat.isSymbolicLink()) {
|
|
301
306
|
if (readlinkSync(target) === entry.dir) return false;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
else
|
|
307
|
+
if (process.platform === "win32" && stat.isDirectory()) rmdirSync(target);
|
|
308
|
+
else unlinkSync(target);
|
|
309
|
+
} else if (stat.isDirectory()) {
|
|
310
|
+
if (isSkinPackageDir(target, entry)) return false;
|
|
311
|
+
throw new Error(target + " exists as a directory but does not look like " + entry.pkg + " — refusing to treat it as installed");
|
|
312
|
+
} else throw new Error(target + " exists and is not a symlink or directory — refusing to touch it");
|
|
305
313
|
mkdirSync(dirname(target), { recursive: true });
|
|
306
314
|
try {
|
|
307
315
|
symlinkSync(entry.dir, target);
|
|
@@ -312,6 +320,24 @@ function ensureSymlink(entry, profileModulesDir) {
|
|
|
312
320
|
}
|
|
313
321
|
return true;
|
|
314
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Whether an existing directory at a profile link path really is the target
|
|
325
|
+
* skin's installed package (skin.json id + package match). Keeps the
|
|
326
|
+
* npm-install-layout pass-through from silently accepting an unrelated
|
|
327
|
+
* directory left over at the link path.
|
|
328
|
+
* @param dir - the directory to inspect.
|
|
329
|
+
* @param entry - the expected skin.
|
|
330
|
+
*/
|
|
331
|
+
function isSkinPackageDir(dir, entry) {
|
|
332
|
+
try {
|
|
333
|
+
const meta = JSON.parse(readFileSync(join(dir, "skin.json"), "utf8"));
|
|
334
|
+
if (typeof meta !== "object" || meta === null) return false;
|
|
335
|
+
const record = meta;
|
|
336
|
+
return record.id === entry.id.replace(/^ui-skin-/, "") && record.package === entry.pkg;
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
315
341
|
/** Windows/privilege code points where symlinkSync fails. */
|
|
316
342
|
const SYMLINK_PRIVILEGE_CODES = [
|
|
317
343
|
"EPERM",
|
|
@@ -343,8 +369,9 @@ function symlinkFriendly(caller, fn) {
|
|
|
343
369
|
function checkInstalled(entry, profileModulesDir) {
|
|
344
370
|
let ok = false;
|
|
345
371
|
try {
|
|
346
|
-
const
|
|
347
|
-
|
|
372
|
+
const target = join(profileModulesDir, entry.pkg);
|
|
373
|
+
const stat = lstatSync(target);
|
|
374
|
+
ok = stat.isSymbolicLink() || stat.isDirectory() && isSkinPackageDir(target, entry);
|
|
348
375
|
} catch {}
|
|
349
376
|
return ok ? null : `${entry.pkg} 未安装到 profile;先用 dsh-skin install ${entry.id.replace(/^ui-skin-/, "")}(或 dsh plugin --profile ${DEFAULT_PROFILE} add ${entry.dir})安装,否则加载会失败。`;
|
|
350
377
|
}
|
|
@@ -553,7 +580,15 @@ function postRoute(path, run) {
|
|
|
553
580
|
* listSkinDirCandidates.
|
|
554
581
|
* @returns skin id -> directory name.
|
|
555
582
|
*/
|
|
583
|
+
/** Memoized id -> dir map; invalidated when the skins root (or the bundled
|
|
584
|
+
* carrier dir) changes on disk, so a skin added mid-session still appears
|
|
585
|
+
* without restarting. */
|
|
586
|
+
let directoriesCache = null;
|
|
556
587
|
function skinDirectories() {
|
|
588
|
+
const rootStat = statSync(SKINS_DIR, { throwIfNoEntry: false });
|
|
589
|
+
const carrierStat = statSync(join(SKINS_DIR, "dsh-skins", "skins"), { throwIfNoEntry: false });
|
|
590
|
+
const key = `${rootStat?.mtimeMs ?? -1}|${carrierStat?.mtimeMs ?? -1}`;
|
|
591
|
+
if (directoriesCache !== null && directoriesCache.key === key) return directoriesCache.map;
|
|
557
592
|
const out = /* @__PURE__ */ new Map();
|
|
558
593
|
for (const dir of listSkinDirCandidates(SKINS_DIR)) {
|
|
559
594
|
let meta;
|
|
@@ -564,6 +599,10 @@ function skinDirectories() {
|
|
|
564
599
|
}
|
|
565
600
|
if (typeof meta.id === "string" && /^[a-z0-9-]+$/.test(meta.id)) out.set(meta.id, dir);
|
|
566
601
|
}
|
|
602
|
+
directoriesCache = {
|
|
603
|
+
key,
|
|
604
|
+
map: out
|
|
605
|
+
};
|
|
567
606
|
return out;
|
|
568
607
|
}
|
|
569
608
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-client-ui-skin-center",
|
|
3
3
|
"description": "In-GUI skin center for the dsh web GUI: lists the official default plus every installed skin (qq98 / ths / xp / blue-fantasy / dragon-heir / minecraft), tries it on live inside the real GUI (executes the actual client bundle, light/dark preview), exits with a full restore, and applies in one click through the host /api/skin-center API (dsh-skin use, hot-reloaded, no restart)",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|