itismyskillmarket 1.3.8 → 1.3.10
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/dist/index.js +33 -4
- package/gui/app.js +18 -0
- package/gui/index.html +1 -1
- package/gui/style.css +3 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -103,7 +103,21 @@ async function isSkillInstalled(skillId) {
|
|
|
103
103
|
// src/commands/npm.ts
|
|
104
104
|
import https from "https";
|
|
105
105
|
import { URL as URL2 } from "url";
|
|
106
|
-
async function fetchNpmPackage(packageName) {
|
|
106
|
+
async function fetchNpmPackage(packageName, retries = 1) {
|
|
107
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
108
|
+
try {
|
|
109
|
+
const result = await fetchNpmPackageOnce(packageName);
|
|
110
|
+
if (result !== null) return result;
|
|
111
|
+
} catch {
|
|
112
|
+
if (attempt === retries) return null;
|
|
113
|
+
}
|
|
114
|
+
if (attempt < retries) {
|
|
115
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
async function fetchNpmPackageOnce(packageName) {
|
|
107
121
|
return new Promise((resolve2, reject) => {
|
|
108
122
|
const isScoped = packageName.startsWith("@");
|
|
109
123
|
let encodedName;
|
|
@@ -123,6 +137,11 @@ async function fetchNpmPackage(packageName) {
|
|
|
123
137
|
const url = new URL2(`https://registry.npmjs.org/${encodedName}`);
|
|
124
138
|
const req = https.get(url.toString(), { timeout: 1e4 }, (res) => {
|
|
125
139
|
let data = "";
|
|
140
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
141
|
+
res.resume();
|
|
142
|
+
resolve2(null);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
126
145
|
res.on("data", (chunk) => {
|
|
127
146
|
data += chunk;
|
|
128
147
|
});
|
|
@@ -1607,6 +1626,7 @@ API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
|
|
|
1607
1626
|
setCache(cacheKey, searchResult, 3e4);
|
|
1608
1627
|
}
|
|
1609
1628
|
const { packages, total } = searchResult;
|
|
1629
|
+
let fetchErrors = 0;
|
|
1610
1630
|
const skillDetails = await throttledMap(packages, async (pkgName) => {
|
|
1611
1631
|
try {
|
|
1612
1632
|
const pkgCacheKey = `pkg:${pkgName}`;
|
|
@@ -1615,7 +1635,10 @@ API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
|
|
|
1615
1635
|
info = await fetchNpmPackage(pkgName);
|
|
1616
1636
|
if (info) setCache(pkgCacheKey, info, 3e4);
|
|
1617
1637
|
}
|
|
1618
|
-
if (!info)
|
|
1638
|
+
if (!info) {
|
|
1639
|
+
fetchErrors++;
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1619
1642
|
const latestVersion = info["dist-tags"]?.latest || "unknown";
|
|
1620
1643
|
const pkg = info.versions?.[latestVersion];
|
|
1621
1644
|
const meta = pkg?.skillmarket;
|
|
@@ -1631,12 +1654,13 @@ API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
|
|
|
1631
1654
|
repository: pkg?.repository?.url || ""
|
|
1632
1655
|
};
|
|
1633
1656
|
} catch {
|
|
1657
|
+
fetchErrors++;
|
|
1634
1658
|
return null;
|
|
1635
1659
|
}
|
|
1636
1660
|
}, 3);
|
|
1637
1661
|
const skills = skillDetails.filter(Boolean);
|
|
1638
1662
|
const totalPages = Math.ceil(total / limit) || 1;
|
|
1639
|
-
jsonResponse(res, 200, { skills, page, totalPages, total });
|
|
1663
|
+
jsonResponse(res, 200, { skills, page, totalPages, total, fetchErrors });
|
|
1640
1664
|
} catch (err) {
|
|
1641
1665
|
jsonResponse(res, 500, {
|
|
1642
1666
|
error: String(err),
|
|
@@ -1779,7 +1803,12 @@ function serveStaticFile(res, filePath) {
|
|
|
1779
1803
|
const content = readFileSync(filePath);
|
|
1780
1804
|
const ext = extname(filePath);
|
|
1781
1805
|
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
1782
|
-
res.writeHead(200, {
|
|
1806
|
+
res.writeHead(200, {
|
|
1807
|
+
"Content-Type": mime,
|
|
1808
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
1809
|
+
"Pragma": "no-cache",
|
|
1810
|
+
"Expires": "0"
|
|
1811
|
+
});
|
|
1783
1812
|
res.end(content);
|
|
1784
1813
|
}
|
|
1785
1814
|
async function handleRequest(req, res) {
|
package/gui/app.js
CHANGED
|
@@ -135,6 +135,7 @@ async function loadSkills() {
|
|
|
135
135
|
|
|
136
136
|
renderSkills(data.skills || data, container);
|
|
137
137
|
renderPagination(data.page, data.totalPages || 1);
|
|
138
|
+
renderFetchWarning(data.fetchErrors);
|
|
138
139
|
} catch (err) {
|
|
139
140
|
container.innerHTML = `<div class="loading">Error: ${err.message}</div>`;
|
|
140
141
|
}
|
|
@@ -191,6 +192,23 @@ function createSkillCard(skill, isInstalled) {
|
|
|
191
192
|
`;
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
// -----------------------------------------------------------------------------
|
|
196
|
+
// 网络错误提示
|
|
197
|
+
// -----------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
function renderFetchWarning(fetchErrors) {
|
|
200
|
+
const existing = document.getElementById('fetch-warning');
|
|
201
|
+
if (existing) existing.remove();
|
|
202
|
+
|
|
203
|
+
if (!fetchErrors || fetchErrors === 0) return;
|
|
204
|
+
|
|
205
|
+
const warning = document.createElement('div');
|
|
206
|
+
warning.id = 'fetch-warning';
|
|
207
|
+
warning.style.cssText = 'background: #664400; color: #ffcc00; padding: 8px 16px; border-radius: 6px; margin-bottom: 16px; font-size: 0.9rem;';
|
|
208
|
+
warning.textContent = `⚠ ${fetchErrors} skill(s) failed to load details from npm registry. Refresh to retry.`;
|
|
209
|
+
document.querySelector('.view-header').after(warning);
|
|
210
|
+
}
|
|
211
|
+
|
|
194
212
|
// -----------------------------------------------------------------------------
|
|
195
213
|
// 分页
|
|
196
214
|
// -----------------------------------------------------------------------------
|
package/gui/index.html
CHANGED
package/gui/style.css
CHANGED
|
@@ -109,6 +109,7 @@ nav {
|
|
|
109
109
|
.main-content {
|
|
110
110
|
flex: 1;
|
|
111
111
|
overflow-y: auto;
|
|
112
|
+
min-height: 0; /* flex 子项必须显式允许收缩 */
|
|
112
113
|
padding: 30px;
|
|
113
114
|
}
|
|
114
115
|
|
|
@@ -118,6 +119,8 @@ nav {
|
|
|
118
119
|
|
|
119
120
|
.view.active {
|
|
120
121
|
display: block;
|
|
122
|
+
overflow-y: auto;
|
|
123
|
+
min-height: 0;
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
.view-header {
|