diff --git a/content/posts/kimi-cli-install-win.md b/content/posts/kimi-cli-install-win.md index 8c92609..8856572 100644 --- a/content/posts/kimi-cli-install-win.md +++ b/content/posts/kimi-cli-install-win.md @@ -280,7 +280,7 @@ Remove-Item "$env:LOCALAPPDATA\Microsoft\WindowsApps\python.exe" -ErrorAction Si --- -> 如果还有问题,欢迎在评论区留言,或在 [GitHub](https://github.com/your-repo) 提 Issue。 +> 如果还有问题,欢迎在评论区留言,或在 [GitHub](https://github.com/yang12535/blog) 提 Issue。 --- diff --git a/content/posts/win-restore-workflow.md b/content/posts/win-restore-workflow.md index 9745b32..e503ed4 100644 --- a/content/posts/win-restore-workflow.md +++ b/content/posts/win-restore-workflow.md @@ -207,6 +207,15 @@ if (-not (Test-Path $nodeExe)) { Write-Ok "Node.js 已存在,跳过安装" } +# 关键:Node.js MSI 安装后注册表 PATH 在当前会话不生效,必须立即显式添加 +# 否则后续 Bun 安装(install.js 内部调用 node)会因找不到 node 而失败 +if (Test-Path $nodeDir) { + Add-ToPath -Dir $nodeDir + Write-Ok "已确保 Node.js 目录在当前会话 PATH 中" +} else { + Write-Warn "Node.js 目录不存在,跳过 PATH 添加: $nodeDir" +} + # 显式定位 npm.cmd(绕过 PS5.1 Restricted 执行策略) $npmCmd = Get-Command "$nodeDir\npm.cmd" -ErrorAction SilentlyContinue if (-not $npmCmd) { diff --git a/package.json b/package.json index 20d689f..fe07f74 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "test:coverage": "jest --coverage", "prepare": "husky", "check:frontmatter": "node scripts/validate-frontmatter.js", - "check:links": "node scripts/check-links.js", + "check:links": "node scripts/check-links.js --warn-external", "check:bom": "node scripts/check-bom.js", "check:all": "npm run check:frontmatter && npm run check:links && npm run check:bom" }, diff --git a/scripts/check-links.js b/scripts/check-links.js index df6ed9a..c86abb7 100644 --- a/scripts/check-links.js +++ b/scripts/check-links.js @@ -65,44 +65,60 @@ async function checkExternalLinks(links, concurrency = 5, timeoutMs = 10000) { return results; } -function checkSingleUrl(url, timeoutMs) { +function checkSingleUrl(url, timeoutMs, retries = 2) { return new Promise((resolve) => { const client = url.startsWith('https:') ? https : http; - function makeRequest(method) { + function makeRequest(method, attempt = 0) { + let settled = false; const req = client.request(url, { method, timeout: timeoutMs }, (res) => { const status = res.statusCode; // 跟随重定向 if (status >= 300 && status < 400 && res.headers.location) { const redirectUrl = new URL(res.headers.location, url).toString(); res.resume(); + settled = true; resolve({ status: 'redirect', redirectTo: redirectUrl, finalStatus: status }); return; } // 某些服务器不支持 HEAD,降级到 GET if ((status === 403 || status === 405) && method === 'HEAD') { res.resume(); - makeRequest('GET'); + makeRequest('GET', attempt); return; } if (status >= 200 && status < 400) { res.resume(); + settled = true; resolve({ status: 'ok', finalStatus: status }); } else { res.resume(); + settled = true; resolve({ status: 'error', finalStatus: status, error: `HTTP ${status}` }); } }); req.on('timeout', () => { req.destroy(); - resolve({ status: 'timeout', error: '请求超时' }); + if (settled) return; + settled = true; + if (attempt < retries) { + setTimeout(() => makeRequest(method, attempt + 1), 500); + } else { + resolve({ status: 'timeout', error: '请求超时' }); + } }); req.on('error', (err) => { + if (settled) return; + settled = true; if (method === 'HEAD') { // HEAD 请求失败,尝试 GET - makeRequest('GET'); + makeRequest('GET', attempt); + return; + } + if (attempt < retries) { + setTimeout(() => makeRequest(method, attempt + 1), 500); } else { - resolve({ status: 'error', error: err.message }); + resolve({ status: 'error', error: `[${err.code || 'UNKNOWN'}] ${err.message}` }); } }); req.end(); @@ -137,6 +153,13 @@ function resolveRelativeLink(url, baseFile) { if (!resolved.startsWith(rootDir + path.sep)) { return null; } + // 构建资源fallback:如果根目录不存在,尝试 src/ 目录(src/assets 会在构建时复制到 dist/assets) + if (!fs.existsSync(resolved) && clean.startsWith('/assets/')) { + const srcResolved = path.resolve(rootDir, 'src', clean.slice(1)); + if (srcResolved.startsWith(rootDir + path.sep) && fs.existsSync(srcResolved)) { + return srcResolved; + } + } return resolved; } if (clean.startsWith('./') || clean.startsWith('../')) { @@ -146,6 +169,17 @@ function resolveRelativeLink(url, baseFile) { if (!resolved.startsWith(rootDir + path.sep)) { return null; } + // 构建资源fallback:如果从 content/posts 解析的 assets 路径不存在,尝试 src/ 目录 + if (!fs.existsSync(resolved) && clean.includes('assets/')) { + // 更通用的方式:把相对路径中指向 assets 的部分映射到 src/assets + const assetsMatch = clean.match(/(?:\.\.\/)*assets\/.*$/); + if (assetsMatch) { + const srcFallback = path.resolve(rootDir, 'src', assetsMatch[0].replace(/^(\.\.\/)+/, '')); + if (srcFallback.startsWith(rootDir + path.sep) && fs.existsSync(srcFallback)) { + return srcFallback; + } + } + } return resolved; } return null; @@ -160,7 +194,12 @@ function findLineNumber(content, url) { } async function main() { + const warnExternal = process.argv.includes('--warn-external'); + log(`${COLORS.cyan}🔗 链接检查工具${COLORS.reset}`); + if (warnExternal) { + log(`${COLORS.yellow}⚠️ 外部链接超时/重置将降级为警告(国内网络环境适配)${COLORS.reset}`); + } log(''); if (!fs.existsSync(POSTS_DIR)) { @@ -214,6 +253,7 @@ async function main() { // 检查外部链接 const brokenExternal = []; + const warnExternalLinks = []; const okExternal = []; if (externalLinks.length > 0) { log(`${COLORS.cyan}🌐 正在检查外部链接...${COLORS.reset}`); @@ -223,6 +263,8 @@ async function main() { okExternal.push(r); } else if (r.status === 'redirect') { okExternal.push(r); // 重定向视为可用 + } else if (warnExternal && (r.error === '请求超时' || r.error?.includes('ECONNRESET') || r.error?.includes('ETIMEDOUT') || r.error?.includes('ENOTFOUND'))) { + warnExternalLinks.push(r); // 网络层错误降级为警告 } else { brokenExternal.push(r); } @@ -264,6 +306,13 @@ async function main() { log(` ${COLORS.green}✔${COLORS.reset} ${link.file}:${link.line} → ${link.url}${COLORS.gray}${statusStr}${COLORS.reset}`); } } + if (warnExternalLinks.length > 0) { + log(` ${COLORS.yellow}⚠️ 网络不稳定/超时 (${warnExternalLinks.length})${COLORS.reset}`); + for (const link of warnExternalLinks) { + log(` ${COLORS.yellow}⚠${COLORS.reset} ${link.file}:${link.line} → ${link.url}`); + log(` ${COLORS.yellow}原因: ${link.error}${COLORS.reset}`); + } + } if (brokenExternal.length > 0) { hasError = true; log(` ${COLORS.red}❌ 不可访问 (${brokenExternal.length})${COLORS.reset}`); @@ -279,12 +328,17 @@ async function main() { const totalBroken = brokenRelative.length + brokenExternal.length; log(`${COLORS.cyan}📊 汇总${COLORS.reset}`); log(` 相对链接: ${relativeLinks.length} (${COLORS.green}${okRelative.length} 有效${COLORS.reset}${brokenRelative.length > 0 ? `, ${COLORS.red}${brokenRelative.length} 断链${COLORS.reset}` : ''})`); - log(` 外部链接: ${externalLinks.length} (${COLORS.green}${okExternal.length} 可访问${COLORS.reset}${brokenExternal.length > 0 ? `, ${COLORS.red}${brokenExternal.length} 不可访问${COLORS.reset}` : ''})`); + const externalWarnStr = warnExternalLinks.length > 0 ? `, ${COLORS.yellow}${warnExternalLinks.length} 超时/重置${COLORS.reset}` : ''; + const externalBrokenStr = brokenExternal.length > 0 ? `, ${COLORS.red}${brokenExternal.length} 不可访问${COLORS.reset}` : ''; + log(` 外部链接: ${externalLinks.length} (${COLORS.green}${okExternal.length} 可访问${COLORS.reset}${externalWarnStr}${externalBrokenStr})`); log(''); if (hasError) { log(`${COLORS.red}❌ 发现 ${totalBroken} 个断链,请修复${COLORS.reset}`); process.exit(1); + } else if (warnExternalLinks.length > 0) { + log(`${COLORS.green}🎉 所有链接检查通过!${COLORS.reset} ${COLORS.yellow}(注意: ${warnExternalLinks.length} 个外部链接因网络不稳定未确认)${COLORS.reset}`); + process.exit(0); } else { log(`${COLORS.green}🎉 所有链接检查通过!${COLORS.reset}`); process.exit(0);