feat: prefer remote MCP OAuth installation
This commit is contained in:
@@ -109,7 +109,7 @@ const commonBootstrapReplacements = new Map([
|
||||
]);
|
||||
const bootstrapWindowsName = "dingtalk-approval-bootstrap.ps1";
|
||||
const bootstrapPosixName = "dingtalk-approval-bootstrap.sh";
|
||||
renderTemplate("distribution/bootstrap-windows.template.ps1", join(out, bootstrapWindowsName), commonBootstrapReplacements);
|
||||
renderTemplate("distribution/bootstrap-windows.template.ps1", join(out, bootstrapWindowsName), commonBootstrapReplacements, { utf8Bom: true });
|
||||
renderTemplate("distribution/bootstrap-posix.template.sh", join(out, bootstrapPosixName), commonBootstrapReplacements);
|
||||
execFileSync("sh", ["-n", join(out, bootstrapPosixName)]);
|
||||
const artifacts = {
|
||||
@@ -139,7 +139,6 @@ writeFileSync(join(out, "manifest.sig"), `${JSON.stringify({
|
||||
schemaVersion: 1, algorithm: "Ed25519", keyId, signature,
|
||||
}, null, 2)}\n`, "utf8");
|
||||
|
||||
const template = readFileSync(resolve("distribution/agent-install.template.md"), "utf8");
|
||||
const replacements = new Map([
|
||||
["{{VERSION}}", version], ["{{MANIFEST_URL}}", `${downloadBaseUrl}/manifest.json`],
|
||||
["{{MANIFEST_SIGNATURE_URL}}", `${downloadBaseUrl}/manifest.sig`], ["{{GATEWAY_URL}}", gatewayUrl],
|
||||
@@ -153,13 +152,14 @@ const replacements = new Map([
|
||||
["{{BOOTSTRAP_POSIX_URL}}", artifacts.bootstrapPosix.url], ["{{BOOTSTRAP_POSIX_SHA256}}", artifacts.bootstrapPosix.sha256],
|
||||
["{{BOOTSTRAP_POSIX_URLS_POSIX}}", artifactUrls(artifacts.bootstrapPosix).join(" ")],
|
||||
["{{BOOTSTRAP_POSIX_MIRROR_URLS_MARKDOWN}}", (artifacts.bootstrapPosix.mirrors ?? []).map((value) => ` - \`${value}\``).join("\n") || " - 未配置"],
|
||||
["{{COMPAT_INSTALL_URL}}", `${downloadBaseUrl}/compat-install.md`],
|
||||
["{{COMPAT_INSTALL_MIRROR_URLS_MARKDOWN}}", downloadMirrorBaseUrls.length
|
||||
? downloadMirrorBaseUrls.map((value) => ` - \`${value}/compat-install.md\``).join("\n") : " - 未配置"],
|
||||
["{{NODE_VERSION}}", nodeRuntime.version], ["{{NODE_MINIMUM_VERSION}}", nodeRuntime.minimumVersion],
|
||||
["{{RELEASE_KEY_ID}}", keyId],
|
||||
]);
|
||||
let prompt = template;
|
||||
for (const [from, to] of replacements) prompt = prompt.replaceAll(from, to);
|
||||
if (/\{\{[^}]+\}\}/.test(prompt)) throw new Error("安装指令仍包含未替换占位符");
|
||||
writeFileSync(join(out, "install.md"), prompt, "utf8");
|
||||
renderTemplate("distribution/agent-install.template.md", join(out, "install.md"), replacements);
|
||||
renderTemplate("distribution/agent-compat-install.template.md", join(out, "compat-install.md"), replacements);
|
||||
const checksums = Object.values(artifacts).map((value) => `${value.sha256} ${basename(new URL(value.url).pathname)}`);
|
||||
checksums.push(`${sha256(join(out, "manifest.json"))} manifest.json`, `${sha256(join(out, "manifest.sig"))} manifest.sig`);
|
||||
writeFileSync(join(out, "SHA256SUMS"), `${checksums.join("\n")}\n`, "utf8");
|
||||
@@ -190,11 +190,11 @@ function lockedVersion(name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function renderTemplate(source, target, replacements) {
|
||||
function renderTemplate(source, target, replacements, options = {}) {
|
||||
let body = readFileSync(resolve(source), "utf8");
|
||||
for (const [from, to] of replacements) body = body.replaceAll(from, String(to));
|
||||
if (/\{\{[^}]+\}\}/.test(body)) throw new Error(`${source} 仍包含未替换占位符`);
|
||||
writeFileSync(target, body, "utf8");
|
||||
writeFileSync(target, options.utf8Bom ? `\uFEFF${body}` : body, "utf8");
|
||||
}
|
||||
|
||||
function validateNodeRuntime(value) {
|
||||
|
||||
@@ -24,7 +24,7 @@ if (gateway.protocol !== "https:" || gateway.pathname !== "/mcp" || gateway.sear
|
||||
throw new Error("manifest.gatewayUrl 必须是无 query/hash 的 HTTPS /mcp 地址");
|
||||
}
|
||||
const artifactNames = [...new Set(Object.values(manifest.artifacts ?? {}).map((item) => basename(String(item.url))))];
|
||||
const members = ["SHA256SUMS", "install.md", "manifest.json", "manifest.sig", ...artifactNames];
|
||||
const members = ["SHA256SUMS", "install.md", "compat-install.md", "manifest.json", "manifest.sig", ...artifactNames];
|
||||
execFileSync("tar", [
|
||||
"--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner", "--format=ustar",
|
||||
"-czf", bundle, "-C", releaseDir, ...members,
|
||||
|
||||
@@ -87,18 +87,34 @@ try {
|
||||
if (!installer.startsWith("#!/usr/bin/env node\n") || /from ["']\.\//.test(installer)) throw new Error("首次安装器必须是无相对依赖的独立 Node 脚本");
|
||||
const install = readFileSync(join(releaseDir, "install.md"), "utf8");
|
||||
if (install.includes("{{") || !install.includes(`版本:\`${version}\``)
|
||||
|| !install.includes("唯一通用安装入口") || !install.includes("postInstall")
|
||||
|| !install.includes("onboarding.firstRun=true") || !install.includes("系统无需预装 Node.js")
|
||||
|| !install.includes("至少 10 分钟超时") || !install.includes("Git Bash")
|
||||
|| /--host|WorkBuddy|Codex|Claude/.test(install)) {
|
||||
throw new Error("install.md 版本、占位符或通用安装引导不正确");
|
||||
|| !install.includes(manifest.gatewayUrl) || !install.includes("Streamable HTTP") || !install.includes("OAuth")
|
||||
|| !install.includes("compat-install.md") || !install.includes("不要安装 Node.js、npm、本地 Bridge")
|
||||
|| /PowerShell|Git Bash|postInstall|loginCommand|npm install -g|--host|WorkBuddy|Codex|Claude/.test(install)) {
|
||||
throw new Error("install.md 不是有效的远程 MCP 优先安装引导");
|
||||
}
|
||||
const windowsBootstrap = readFileSync(join(releaseDir, artifactName(manifest.artifacts.bootstrapWindows, "bootstrapWindows")), "utf8");
|
||||
const compatibilityInstall = readFileSync(join(releaseDir, "compat-install.md"), "utf8");
|
||||
if (compatibilityInstall.includes("{{") || !compatibilityInstall.includes(`版本:\`${version}\``)
|
||||
|| !compatibilityInstall.includes("仅当当前 Agent 明确不支持远程 Streamable HTTP MCP 或标准 OAuth")
|
||||
|| !compatibilityInstall.includes("postInstall") || !compatibilityInstall.includes("onboarding.firstRun=true")
|
||||
|| !compatibilityInstall.includes("系统无需预装 Node.js") || !compatibilityInstall.includes("Git Bash")
|
||||
|| /TimeoutSec 300|--max-time 300|--timeout=300/.test(compatibilityInstall)
|
||||
|| /--host|WorkBuddy|Codex|Claude/.test(compatibilityInstall)) {
|
||||
throw new Error("compat-install.md 版本、占位符或兼容安装引导不正确");
|
||||
}
|
||||
const windowsBootstrapBytes = readFileSync(join(releaseDir, artifactName(manifest.artifacts.bootstrapWindows, "bootstrapWindows")));
|
||||
if (windowsBootstrapBytes[0] !== 0xef || windowsBootstrapBytes[1] !== 0xbb || windowsBootstrapBytes[2] !== 0xbf) {
|
||||
throw new Error("Windows PowerShell 引导缺少 UTF-8 BOM");
|
||||
}
|
||||
const windowsBootstrap = windowsBootstrapBytes.toString("utf8").replace(/^\uFEFF/, "");
|
||||
const posixBootstrapPath = join(releaseDir, artifactName(manifest.artifacts.bootstrapPosix, "bootstrapPosix"));
|
||||
const posixBootstrap = readFileSync(posixBootstrapPath, "utf8");
|
||||
if (windowsBootstrap.includes("{{") || posixBootstrap.includes("{{")
|
||||
|| !windowsBootstrap.includes("Save-DingDownload") || !windowsBootstrap.includes("Expand-Archive")
|
||||
|| !windowsBootstrap.includes("curl.exe") || !windowsBootstrap.includes("--max-time 90")
|
||||
|| !windowsBootstrap.includes(`-e "const [a,b]=process.versions.node.split('.').map(Number)`)
|
||||
|| windowsBootstrap.includes("TimeoutSec 300")
|
||||
|| !windowsBootstrap.includes("Write-DingProgress") || !posixBootstrap.includes("ding_progress")
|
||||
|| !posixBootstrap.includes("--max-time 90") || /--max-time 300|--timeout=300/.test(posixBootstrap)
|
||||
|| !posixBootstrap.startsWith("#!/bin/sh\nset -eu\n") || !posixBootstrap.includes("tar -xzf")
|
||||
|| /latest-v|npm install -g/.test(`${windowsBootstrap}\n${posixBootstrap}`)) {
|
||||
throw new Error("无 Node 安装引导无效或仍包含不安全的动态版本来源");
|
||||
|
||||
Reference in New Issue
Block a user