dsh-plugin-marketplace 0.2.3 → 0.2.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/client.js +35 -5
- package/index.js +46 -4
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -64,6 +64,7 @@ window.__ModuleLoader__.load({
|
|
|
64
64
|
empty: "没有找到插件",
|
|
65
65
|
install: "安装方式",
|
|
66
66
|
installHint: "在 profile 目录执行 pnpm 安装,然后在 cordis.patch.yml 挂载:",
|
|
67
|
+
installHintBundle: "标准 bundle 插件:dsh plugin add 后由 bundle 层自动挂载,无需手动改 cordis.patch.yml:",
|
|
67
68
|
readme: "README 摘要",
|
|
68
69
|
readmeEmpty: "(该仓库没有 README)",
|
|
69
70
|
openRepo: "打开 GitHub 仓库 ↗",
|
|
@@ -94,6 +95,7 @@ window.__ModuleLoader__.load({
|
|
|
94
95
|
empty: "No plugins found",
|
|
95
96
|
install: "Install",
|
|
96
97
|
installHint: "Run pnpm in your profile dir, then mount in cordis.patch.yml:",
|
|
98
|
+
installHintBundle: "Standard bundle plugin: dsh plugin add auto-mounts it via its bundle layer — no manual cordis.patch.yml edit needed:",
|
|
97
99
|
readme: "README summary",
|
|
98
100
|
readmeEmpty: "(no README in this repo)",
|
|
99
101
|
openRepo: "Open GitHub repo ↗",
|
|
@@ -163,6 +165,24 @@ window.__ModuleLoader__.load({
|
|
|
163
165
|
for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
164
166
|
return new TextDecoder("utf-8").decode(bytes);
|
|
165
167
|
}
|
|
168
|
+
// npm metadata (bundle detection for the install hint); CORS-enabled, cached.
|
|
169
|
+
var npmCache = new Map(); // pkg -> {isBundle} | null
|
|
170
|
+
async function fetchNpmInfo(pkg) {
|
|
171
|
+
if (npmCache.has(pkg)) return npmCache.get(pkg);
|
|
172
|
+
var promise = fetch("https://registry.npmjs.org/" + encodeURIComponent(pkg) + "/latest", {
|
|
173
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" }
|
|
174
|
+
}).then(function (res) {
|
|
175
|
+
if (!res.ok) return null;
|
|
176
|
+
return res.json();
|
|
177
|
+
}).then(function (data) {
|
|
178
|
+
if (!data || !data.dsh) return { isBundle: false };
|
|
179
|
+
return { isBundle: typeof data.dsh.bundle === "object" && data.dsh.bundle !== null && typeof data.dsh.bundle.patch === "string" && data.dsh.bundle.patch.length > 0 };
|
|
180
|
+
}).catch(function () {
|
|
181
|
+
return null;
|
|
182
|
+
});
|
|
183
|
+
npmCache.set(pkg, promise);
|
|
184
|
+
return promise;
|
|
185
|
+
}
|
|
166
186
|
|
|
167
187
|
// ── components ────────────────────────────────────────────────────────
|
|
168
188
|
function PluginCard(props) {
|
|
@@ -224,11 +244,13 @@ window.__ModuleLoader__.load({
|
|
|
224
244
|
h("a", { className: "__mp_link", href: "https://www.npmjs.com/search?q=" + encodeURIComponent(installName), target: "_blank", rel: "noreferrer" }, props.t("openNpm"))
|
|
225
245
|
),
|
|
226
246
|
h("div", { className: "__mp_detailTitle" }, props.t("install")),
|
|
227
|
-
h("div", { className: "__mp_readme" }, props.t("installHint")),
|
|
247
|
+
h("div", { className: "__mp_readme" }, props.bundleInfo === true ? props.t("installHintBundle") : props.t("installHint")),
|
|
228
248
|
h("code", { className: "__mp_code" },
|
|
229
249
|
"dsh plugin --profile web add " + installName + "\n" +
|
|
230
|
-
|
|
231
|
-
|
|
250
|
+
(props.bundleInfo === true
|
|
251
|
+
? "# standard bundle: auto-mounted via dsh.profile.bundles"
|
|
252
|
+
: "# then in $DSH_HOME/profiles/web/cordis.patch.yml:\n" +
|
|
253
|
+
"- insert:\n - id: " + installName + "\n name: '" + installName + "'")
|
|
232
254
|
),
|
|
233
255
|
btn,
|
|
234
256
|
statusNode,
|
|
@@ -260,7 +282,7 @@ window.__ModuleLoader__.load({
|
|
|
260
282
|
var t = props.t;
|
|
261
283
|
var scope = props.scope;
|
|
262
284
|
var api = props.api;
|
|
263
|
-
var state = react.useState({ q: "", sort: "stars", page: 1, items: [], loading: false, error: null, total: 0, open: null, readme: null, readmeLoading: false, readmeError: false, readmeRateLimited: false });
|
|
285
|
+
var state = react.useState({ q: "", sort: "stars", page: 1, items: [], loading: false, error: null, total: 0, open: null, readme: null, readmeLoading: false, readmeError: false, readmeRateLimited: false, bundleInfo: null });
|
|
264
286
|
var s = state[0], set = state[1];
|
|
265
287
|
// install-state subscription (stable pattern: useState + subscribe, never
|
|
266
288
|
// depend on getSnapshot() reference identity).
|
|
@@ -342,6 +364,14 @@ window.__ModuleLoader__.load({
|
|
|
342
364
|
return Object.assign({}, prev, { readmeLoading: false, readmeError: !rateLimited, readmeRateLimited: rateLimited });
|
|
343
365
|
});
|
|
344
366
|
});
|
|
367
|
+
// npm metadata: does the target package declare dsh.bundle.patch?
|
|
368
|
+
// (determines whether the manual cordis.patch.yml row is needed)
|
|
369
|
+
fetchNpmInfo(plugin.fullName.split("/")[1]).then(function (info) {
|
|
370
|
+
set(function (prev) {
|
|
371
|
+
if (!prev.open || prev.open.fullName !== plugin.fullName) return prev;
|
|
372
|
+
return Object.assign({}, prev, { bundleInfo: info ? Boolean(info.isBundle) : null });
|
|
373
|
+
});
|
|
374
|
+
});
|
|
345
375
|
}, [s.open]);
|
|
346
376
|
var submit = function (e) {
|
|
347
377
|
e.preventDefault();
|
|
@@ -364,7 +394,7 @@ window.__ModuleLoader__.load({
|
|
|
364
394
|
var open = s.open && s.open.fullName === p.fullName;
|
|
365
395
|
return h("div", { key: p.fullName, className: "__mp_item" },
|
|
366
396
|
h(PluginCard, { plugin: p, t: t, onOpen: function () { openDetail(p); } }),
|
|
367
|
-
open ? h(DetailPanel, { plugin: s.open, t: t, readme: s.readme, readmeLoading: s.readmeLoading, readmeError: s.readmeError, readmeRateLimited: s.readmeRateLimited, lang: s.open.lang, installState: installState && installState.status === "ready" ? installState.value.installState : null, onInstall: onInstall, ghError: t("ghError"), explainState: installState && installState.status === "ready" ? installState.value.aiExplainResult : null, explainError: s.explainError, onExplain: onExplain }) : null
|
|
397
|
+
open ? h(DetailPanel, { plugin: s.open, t: t, readme: s.readme, readmeLoading: s.readmeLoading, readmeError: s.readmeError, readmeRateLimited: s.readmeRateLimited, lang: s.open.lang, installState: installState && installState.status === "ready" ? installState.value.installState : null, onInstall: onInstall, ghError: t("ghError"), explainState: installState && installState.status === "ready" ? installState.value.aiExplainResult : null, explainError: s.explainError, onExplain: onExplain, bundleInfo: s.bundleInfo }) : null
|
|
368
398
|
);
|
|
369
399
|
})
|
|
370
400
|
),
|
package/index.js
CHANGED
|
@@ -109,21 +109,63 @@ function apply(ctx, config) {
|
|
|
109
109
|
return process.argv[1];
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/**
|
|
112
|
+
/** Read the profile manifest (its `dsh.profile.bundles` list), or null. */
|
|
113
|
+
function profileManifest(profile) {
|
|
114
|
+
try {
|
|
115
|
+
const dshHome = process.env.DSH_HOME || join(os.homedir(), ".dsh");
|
|
116
|
+
return JSON.parse(readFileSync(join(dshHome, "profiles", profile, "package.json"), "utf8"));
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Whether the installed package declares dsh.bundle.patch (auto-mounted via its bundle layer). */
|
|
123
|
+
function isBundlePackage(pkg, profile) {
|
|
124
|
+
try {
|
|
125
|
+
const dshHome = process.env.DSH_HOME || join(os.homedir(), ".dsh");
|
|
126
|
+
const manifest = JSON.parse(readFileSync(join(dshHome, "profiles", profile, "node_modules", pkg, "package.json"), "utf8"));
|
|
127
|
+
return typeof manifest?.dsh?.bundle?.patch === "string" && manifest.dsh.bundle.patch.length > 0;
|
|
128
|
+
} catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Append a mount row for `pkg` to cordis.patch.yml (idempotent).
|
|
135
|
+
* Bundle plugins (already in `dsh.profile.bundles`, or whose installed
|
|
136
|
+
* package declares `dsh.bundle.patch`) are mounted by their own bundle
|
|
137
|
+
* layer — adding a manual row would create a duplicate loader entry and
|
|
138
|
+
* abort boot, so they are skipped here.
|
|
139
|
+
*/
|
|
113
140
|
function ensureMounted(pkg, profile) {
|
|
114
141
|
const dshHome = process.env.DSH_HOME || join(os.homedir(), ".dsh");
|
|
115
142
|
const patchPath = join(dshHome, "profiles", profile, "cordis.patch.yml");
|
|
116
143
|
if (!existsSync(patchPath)) return " (patch file not found; mount manually)";
|
|
144
|
+
const bundles = profileManifest(profile)?.dsh?.profile?.bundles ?? [];
|
|
145
|
+
if (bundles.includes(pkg)) return " (already mounted via dsh.profile.bundles)";
|
|
146
|
+
if (isBundlePackage(pkg, profile)) return " (bundle plugin: auto-mounted via its own dsh.bundle layer)";
|
|
117
147
|
const id = pkg.replace(/^@[^/]+\//, "").replace(/[^a-z0-9-]/g, "-") || pkg;
|
|
118
148
|
let src = readFileSync(patchPath, "utf8");
|
|
119
149
|
if (src.includes(`name: '${pkg}'`) || src.includes(`name: "${pkg}"`)) return " (already mounted)";
|
|
120
|
-
// Append under the first `- insert:` list; match its indentation (4 spaces
|
|
121
|
-
// is the convention used by dsh profiles).
|
|
122
|
-
const insertAt = src.search(/^- insert:\s*$/m);
|
|
123
150
|
const row = `\n - id: ${id}\n name: '${pkg}'`;
|
|
151
|
+
// A flow-style empty array (`[]`, optionally after comment lines) is a
|
|
152
|
+
// valid empty patch: replace it with the block-style insert list.
|
|
153
|
+
// Appending after `[]` would produce two top-level documents in one
|
|
154
|
+
// stream and crash boot with a YAML parse error.
|
|
155
|
+
const emptyArray = /^(\s*(?:#[^\n]*\n?)*)\[\s*\]\s*$/.exec(src);
|
|
156
|
+
if (emptyArray) {
|
|
157
|
+
writeFileSync(patchPath, `${emptyArray[1]}- insert:${row}\n`, "utf8");
|
|
158
|
+
return " (mounted in cordis.patch.yml)";
|
|
159
|
+
}
|
|
160
|
+
// Insert under the first `- insert:` list; match its indentation (4
|
|
161
|
+
// spaces is the convention used by dsh profiles).
|
|
162
|
+
const insertAt = src.search(/^- insert:\s*$/m);
|
|
124
163
|
if (insertAt >= 0) {
|
|
125
164
|
src = src.slice(0, insertAt + "- insert:".length) + row + src.slice(insertAt + "- insert:".length);
|
|
126
165
|
} else {
|
|
166
|
+
// A non-empty flow-style array cannot be safely extended with string
|
|
167
|
+
// surgery — refuse rather than corrupt the file.
|
|
168
|
+
if (/^\s*\[/.test(src)) return " (patch file uses a flow-style array; mount manually)";
|
|
127
169
|
src += `\n- insert:${row}\n`;
|
|
128
170
|
}
|
|
129
171
|
writeFileSync(patchPath, src, "utf8");
|