@linxin666/dsh-client-ui-skin-center 0.1.3 → 0.1.4
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/index.js +96 -34
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -23,22 +23,52 @@ 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 directory whose @linxin666/
|
|
27
|
+
* scoped subdir actually holds skin packages (dsh-skins carrier or
|
|
28
|
+
* dsh-client-ui-skin-* packages). pnpm's virtual store realpaths packages
|
|
29
|
+
* into node_modules/.pnpm/<pkg>@<ver>/node_modules/<name>, so a plain
|
|
30
|
+
* '../../' from the skin-center package can never see its siblings there —
|
|
31
|
+
* this anchor finds the node_modules root that owns the scoped dir.
|
|
32
|
+
* @param fromDir - the realpathed package dir to walk up from.
|
|
33
|
+
* @returns the anchor dir, or null when no scoped skin dir is found.
|
|
34
|
+
*/
|
|
35
|
+
function findScopedAnchor(fromDir) {
|
|
36
|
+
let current = fromDir;
|
|
37
|
+
for (;;) {
|
|
38
|
+
const scoped = join(current, "@linxin666");
|
|
39
|
+
try {
|
|
40
|
+
for (const entry of readdirSync(scoped)) {
|
|
41
|
+
if (entry === "dsh-skins") return current;
|
|
42
|
+
if (entry.startsWith("dsh-client-ui-skin-") && entry !== "dsh-client-ui-skin-center") return current;
|
|
43
|
+
}
|
|
44
|
+
} catch {}
|
|
45
|
+
const parent = dirname(current);
|
|
46
|
+
if (parent === current) return null;
|
|
47
|
+
current = parent;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
26
51
|
* Resolve the directory that holds the skin packages (each a dir carrying a
|
|
27
|
-
* skin.json).
|
|
28
|
-
* - monorepo
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
52
|
+
* skin.json). Candidates, in order:
|
|
53
|
+
* - monorepo / flat npm layout: new URL('../../', import.meta.url)
|
|
54
|
+
* (packages/skins/ or node_modules/@linxin666/);
|
|
55
|
+
* - pnpm virtual-store layout: the nearest @linxin666/ scoped dir found by
|
|
56
|
+
* walking up from this package's realpathed location;
|
|
57
|
+
* - the legacy '../../../skins/' spelling (which pointed at
|
|
58
|
+
* node_modules/skins/ under npm — the ENOENT of
|
|
59
|
+
* zhu1090093659/dsh-web-ui#21/#33/#34), kept as a fallback.
|
|
60
|
+
* DSH_SKINS_DIR overrides everything (tests use it).
|
|
34
61
|
*/
|
|
35
62
|
function resolveSkinsDir() {
|
|
36
63
|
const fromEnv = process.env.DSH_SKINS_DIR;
|
|
37
64
|
if (fromEnv !== void 0 && fromEnv !== "") return fromEnv;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
65
|
+
const here = fileURLToPath(import.meta.url);
|
|
66
|
+
const candidates = [
|
|
67
|
+
fileURLToPath(new URL("../../", import.meta.url)),
|
|
68
|
+
findScopedAnchor(dirname(here)),
|
|
69
|
+
fileURLToPath(new URL("../../../skins/", import.meta.url))
|
|
70
|
+
].filter((candidate) => candidate !== null);
|
|
71
|
+
for (const candidate of candidates) if (listSkinDirCandidates(candidate).length > 0) return candidate;
|
|
42
72
|
return candidates[0];
|
|
43
73
|
}
|
|
44
74
|
/** The skin-package root for this install (see resolveSkinsDir). */
|
|
@@ -52,12 +82,11 @@ const DEFAULT_PROFILE = process.env.DSH_SKIN_PROFILE ?? "web";
|
|
|
52
82
|
* Parse the switch-relevant fields of one skin.json. Returns null for
|
|
53
83
|
* anything that is not a valid skin so it is simply skipped — never walking
|
|
54
84
|
* outside the skins tree (the id is validated before any path use).
|
|
55
|
-
* @param
|
|
56
|
-
* @param skinsDir - the skins root (defaults to the resolved install layout).
|
|
85
|
+
* @param absDir - absolute path of the candidate skin directory.
|
|
57
86
|
*/
|
|
58
|
-
function readSkinMeta(
|
|
87
|
+
function readSkinMeta(absDir) {
|
|
59
88
|
try {
|
|
60
|
-
const meta = JSON.parse(readFileSync(join(
|
|
89
|
+
const meta = JSON.parse(readFileSync(join(absDir, "skin.json"), "utf8"));
|
|
61
90
|
if (typeof meta !== "object" || meta === null) return null;
|
|
62
91
|
const record = meta;
|
|
63
92
|
if (typeof record.id !== "string" || !/^[a-z0-9-]+$/.test(record.id)) return null;
|
|
@@ -78,18 +107,17 @@ function readSkinMeta(dir, skinsDir = SKINS_DIR) {
|
|
|
78
107
|
}
|
|
79
108
|
}
|
|
80
109
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* @
|
|
89
|
-
* @returns skin id -> switch metadata.
|
|
110
|
+
* Enumerate every candidate skin directory under a skins root. Two shapes:
|
|
111
|
+
* - direct subdirectories carrying a skin.json (monorepo packages/skins/<id>,
|
|
112
|
+
* and per-skin npm packages @linxin666/dsh-client-ui-skin-<id>);
|
|
113
|
+
* - the bundled-skins carrier: @linxin666/dsh-skins/skins/<id> (skin assets
|
|
114
|
+
* shipped inside the dsh-skins aggregate so npm needs no per-skin
|
|
115
|
+
* package names). Directories without a skin.json are skipped.
|
|
116
|
+
* @param skinsDir - the skins root.
|
|
117
|
+
* @returns absolute candidate dirs (possibly empty).
|
|
90
118
|
*/
|
|
91
|
-
function
|
|
92
|
-
const out =
|
|
119
|
+
function listSkinDirCandidates(skinsDir) {
|
|
120
|
+
const out = [];
|
|
93
121
|
let entries;
|
|
94
122
|
try {
|
|
95
123
|
entries = readdirSync(skinsDir);
|
|
@@ -97,12 +125,46 @@ function loadRegistry(skinsDir = SKINS_DIR) {
|
|
|
97
125
|
return out;
|
|
98
126
|
}
|
|
99
127
|
for (const dir of entries) {
|
|
100
|
-
const
|
|
128
|
+
const candidate = join(skinsDir, dir);
|
|
129
|
+
if (statSync(join(candidate, "skin.json"), { throwIfNoEntry: false })) {
|
|
130
|
+
out.push(candidate);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (dir === "dsh-skins") {
|
|
134
|
+
const bundled = join(candidate, "skins");
|
|
135
|
+
let subdirs;
|
|
136
|
+
try {
|
|
137
|
+
subdirs = readdirSync(bundled);
|
|
138
|
+
} catch {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
for (const sub of subdirs) {
|
|
142
|
+
const subDir = join(bundled, sub);
|
|
143
|
+
if (statSync(join(subDir, "skin.json"), { throwIfNoEntry: false })) out.push(subDir);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Derive the skin registry from each skin dir's skin.json — the single
|
|
151
|
+
* source of truth (skin.json already carries package/wiring.id/bundleWired).
|
|
152
|
+
* Replaces the CLI's hand-maintained SKINS dictionary, so adding a skin
|
|
153
|
+
* needs no code change here. Candidate dirs come from
|
|
154
|
+
* listSkinDirCandidates (direct skin dirs + the dsh-skins bundled carrier).
|
|
155
|
+
* The root is injectable so tests can point at either install layout.
|
|
156
|
+
* @param skinsDir - the skins root (defaults to the resolved install layout).
|
|
157
|
+
* @returns skin id -> switch metadata.
|
|
158
|
+
*/
|
|
159
|
+
function loadRegistry(skinsDir = SKINS_DIR) {
|
|
160
|
+
const out = {};
|
|
161
|
+
for (const dir of listSkinDirCandidates(skinsDir)) {
|
|
162
|
+
const meta = readSkinMeta(dir);
|
|
101
163
|
if (meta === null || meta.wiring === void 0 || meta.package === void 0) continue;
|
|
102
164
|
out[meta.id] = {
|
|
103
165
|
pkg: meta.package,
|
|
104
166
|
id: meta.wiring.id,
|
|
105
|
-
dir
|
|
167
|
+
dir,
|
|
106
168
|
bundleWired: meta.wiring.bundleWired === true
|
|
107
169
|
};
|
|
108
170
|
}
|
|
@@ -486,17 +548,17 @@ function postRoute(path, run) {
|
|
|
486
548
|
* skin.json. The id is validated against this map (never used as a raw
|
|
487
549
|
* path) so the bundle route cannot be walked off the skins tree. The root
|
|
488
550
|
* resolves per install layout (monorepo packages/skins/, npm
|
|
489
|
-
* node_modules/@linxin666/)
|
|
551
|
+
* node_modules/@linxin666/) and candidates include the bundled dsh-skins
|
|
552
|
+
* carrier (npm layout) — see skin-switch resolveSkinsDir /
|
|
553
|
+
* listSkinDirCandidates.
|
|
490
554
|
* @returns skin id -> directory name.
|
|
491
555
|
*/
|
|
492
556
|
function skinDirectories() {
|
|
493
557
|
const out = /* @__PURE__ */ new Map();
|
|
494
|
-
for (const dir of
|
|
495
|
-
const metaFile = join(SKINS_DIR, dir, "skin.json");
|
|
496
|
-
if (!statSync(metaFile, { throwIfNoEntry: false })) continue;
|
|
558
|
+
for (const dir of listSkinDirCandidates(SKINS_DIR)) {
|
|
497
559
|
let meta;
|
|
498
560
|
try {
|
|
499
|
-
meta = JSON.parse(readFileSync(
|
|
561
|
+
meta = JSON.parse(readFileSync(join(dir, "skin.json"), "utf8"));
|
|
500
562
|
} catch {
|
|
501
563
|
continue;
|
|
502
564
|
}
|
|
@@ -545,7 +607,7 @@ function bundleRoute() {
|
|
|
545
607
|
});
|
|
546
608
|
return;
|
|
547
609
|
}
|
|
548
|
-
const bundle = join(
|
|
610
|
+
const bundle = join(dir, "lib", "client.js");
|
|
549
611
|
if (!statSync(bundle, { throwIfNoEntry: false })) {
|
|
550
612
|
json(res, 404, {
|
|
551
613
|
ok: false,
|
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.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|