@echo off setlocal EnableExtensions DisableDelayedExpansion set "DFCODE_INSTALL_SELF=%~f0" set "INSTALL_BASE=%~dp0" setlocal EnableDelayedExpansion pushd "!INSTALL_BASE!" 2>nul set "SCRIPT_VERSION=2026.09.23.1-cmd" set "NODE_PINNED=v24.20.0" set "NODE_MIRROR=https://cdn.npmmirror.com/binaries/node" set "WORK_DIR=!TEMP!\dfcode-install-!RANDOM!-!RANDOM!" mkdir "!WORK_DIR!" >nul 2>&1 if errorlevel 1 goto :fail set "WORK_OWNED=1" set "DFCODE_INSTALL_CONTRACT=!WORK_DIR!\contract.cjs" set "DFCODE_INSTALL_EXPECTED=!WORK_DIR!\expected.txt" set "DFCODE_INSTALL_LOG=!WORK_DIR!.log" echo ============================================================ echo DFCode CMD installer !SCRIPT_VERSION! echo ============================================================ where curl.exe >nul 2>&1 if errorlevel 1 ( echo [X] curl.exe was not found. Use Windows 10 1803 or newer. goto :fail ) call :ensure_node if errorlevel 1 goto :fail node -e "const fs=require('fs');const s=fs.readFileSync(process.env.DFCODE_INSTALL_SELF,'utf8').replace(/\r\n/g,'\n');const body=s.split('\n// DFCODE_WINDOWS_CONTRACT\n').pop();fs.writeFileSync(process.env.DFCODE_INSTALL_CONTRACT,body);" if errorlevel 1 goto :fail node "!DFCODE_INSTALL_CONTRACT!" if errorlevel 1 goto :fail set "EXPECTED_VERSION=" for /f "usebackq delims=" %%V in ("!DFCODE_INSTALL_EXPECTED!") do set "EXPECTED_VERSION=%%V" if not defined EXPECTED_VERSION goto :fail call :verify_persistent_path if errorlevel 1 ( echo [X] DFCode is installed, but a fresh CMD cannot run the expected version. echo [X] Check that Node.js and !NPM_PREFIX! are in your user Path, then retry. goto :fail ) echo [OK] Environment ready. DFCode !EXPECTED_VERSION! echo 1^) cd /d D:\projects\my-app echo 2^) dfcode echo 3^) Type /login and complete enterprise login. echo 4^) Type /models to choose a model. set "RC=0" echo [OK] Installation log: !DFCODE_INSTALL_LOG! goto :end :ensure_node where node >nul 2>&1 if not errorlevel 1 goto :check_node echo [!] Node.js was not found. Trying winget Node.js LTS. where winget.exe >nul 2>&1 if not errorlevel 1 ( winget install -e --id OpenJS.NodeJS.LTS --architecture x64 --accept-source-agreements --accept-package-agreements set "PATH=!ProgramFiles!\nodejs;!LOCALAPPDATA!\Programs\nodejs;!APPDATA!\npm;!PATH!" where node >nul 2>&1 if not errorlevel 1 goto :check_node ) echo [!] winget failed or Node.js is still unavailable. Trying the Node.js ZIP. set "NODE_TAG=x64" set "NODE_ARCHIVE=node-!NODE_PINNED!-win-!NODE_TAG!.zip" set "NODE_BASE=!NODE_MIRROR!/!NODE_PINNED!" set "NODE_TMP=!WORK_DIR!\node" set "NODE_TARGET=!LOCALAPPDATA!\Programs\dfcode-nodejs-!NODE_PINNED!-!NODE_TAG!" if not exist "!NODE_TMP!" mkdir "!NODE_TMP!" >nul 2>&1 curl.exe -fL --retry 2 --connect-timeout 15 --max-time 600 "!NODE_BASE!/!NODE_ARCHIVE!" -o "!NODE_TMP!\!NODE_ARCHIVE!" if errorlevel 1 ( echo [X] Could not download !NODE_BASE!/!NODE_ARCHIVE!. exit /b 1 ) curl.exe -fL --retry 2 --connect-timeout 15 --max-time 600 "https://nodejs.org/dist/!NODE_PINNED!/SHASUMS256.txt" -o "!NODE_TMP!\SHASUMS256.txt" if errorlevel 1 ( echo [X] Could not download the Node.js checksum file. exit /b 1 ) set "EXPECTED_SHA=" for /f "tokens=1,2" %%A in ('findstr /C:"!NODE_ARCHIVE!" "!NODE_TMP!\SHASUMS256.txt"') do if "%%B"=="!NODE_ARCHIVE!" set "EXPECTED_SHA=%%A" if not defined EXPECTED_SHA ( echo [X] Node.js checksum was not found. exit /b 1 ) set "ACTUAL_SHA=" for /f "skip=1 delims=" %%A in ('certutil -hashfile "!NODE_TMP!\!NODE_ARCHIVE!" SHA256 2^>nul') do if not defined ACTUAL_SHA set "ACTUAL_SHA=%%A" set "ACTUAL_SHA=!ACTUAL_SHA: =!" if /I not "!ACTUAL_SHA!"=="!EXPECTED_SHA!" ( echo [X] Node.js checksum verification failed. exit /b 1 ) where tar.exe >nul 2>&1 if errorlevel 1 ( echo [X] tar.exe was not found. Use Windows 10 1803 or newer. exit /b 1 ) if exist "!NODE_TMP!\node-!NODE_PINNED!-win-!NODE_TAG!" rd /s /q "!NODE_TMP!\node-!NODE_PINNED!-win-!NODE_TAG!" tar.exe -xf "!NODE_TMP!\!NODE_ARCHIVE!" -C "!NODE_TMP!" if errorlevel 1 exit /b 1 if exist "!NODE_TARGET!" ( echo [X] !NODE_TARGET! already exists. Check its PATH before reinstalling. exit /b 1 ) if not exist "!LOCALAPPDATA!\Programs" mkdir "!LOCALAPPDATA!\Programs" >nul 2>&1 move "!NODE_TMP!\node-!NODE_PINNED!-win-!NODE_TAG!" "!NODE_TARGET!" >nul if errorlevel 1 exit /b 1 set "PATH=!NODE_TARGET!;!APPDATA!\npm;!PATH!" call :add_user_path "!NODE_TARGET!" if errorlevel 1 exit /b 1 call :add_user_path "!APPDATA!\npm" if errorlevel 1 exit /b 1 :check_node where node >nul 2>&1 if errorlevel 1 ( echo [X] Node.js is still unavailable. Open a new CMD window and retry. exit /b 1 ) set "NODE_MAJOR=" for /f "tokens=1 delims=." %%M in ('node -v 2^>nul') do set "NODE_MAJOR=%%M" set "NODE_MAJOR=!NODE_MAJOR:v=!" if not defined NODE_MAJOR exit /b 1 if !NODE_MAJOR! LSS 18 ( echo [X] Node.js !NODE_MAJOR! is too old. DFCode requires Node.js 18+. exit /b 1 ) node -e "if(process.arch==='x64')process.exit(0);process.exit(1)" if errorlevel 1 ( echo [X] DFCode Windows requires x64 Node.js. ARM64 and x86 Node.js are unsupported. exit /b 1 ) where npm.cmd >nul 2>&1 if errorlevel 1 ( echo [X] npm.cmd was not found. exit /b 1 ) call :refresh_npm_path echo [OK] Node.js major version: !NODE_MAJOR! exit /b 0 :refresh_npm_path set "NPM_PREFIX=" for /f "usebackq delims=" %%P in (`call npm.cmd prefix -g 2^>nul`) do if not defined NPM_PREFIX set "NPM_PREFIX=%%P" if defined NPM_PREFIX set "PATH=!NPM_PREFIX!;!APPDATA!\npm;!PATH!" exit /b 0 :add_user_path setlocal DisableDelayedExpansion set "PATH_TO_ADD=%~1" set "USER_PATH=" set "USER_PATH_KIND=REG_EXPAND_SZ" for /f "tokens=2,*" %%A in ('reg query HKCU\Environment /v Path 2^>nul ^| findstr /I /C:" Path "') do ( set "USER_PATH_KIND=%%A" set "USER_PATH=%%B" ) setlocal EnableDelayedExpansion if not defined USER_PATH ( reg.exe add HKCU\Environment /v Path /t !USER_PATH_KIND! /d "!PATH_TO_ADD!" /f >nul 2>&1 if errorlevel 1 exit /b 1 exit /b 0 ) if /I not "!USER_PATH_KIND!"=="REG_SZ" if /I not "!USER_PATH_KIND!"=="REG_EXPAND_SZ" exit /b 1 echo(;!USER_PATH!;| findstr /I /L /C:";!PATH_TO_ADD!;" >nul if not errorlevel 1 exit /b 0 reg.exe add HKCU\Environment /v Path /t !USER_PATH_KIND! /d "!USER_PATH!;!PATH_TO_ADD!" /f >nul 2>&1 if errorlevel 1 exit /b 1 exit /b 0 :verify_persistent_path set "PERSISTED_PATH=" for /f "tokens=2,*" %%A in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path 2^>nul ^| findstr /I /C:" Path "') do set "PERSISTED_PATH=%%B" for /f "tokens=2,*" %%A in ('reg query HKCU\Environment /v Path 2^>nul ^| findstr /I /C:" Path "') do set "PERSISTED_PATH=!PERSISTED_PATH!;%%B" node -e "const cp=require('child_process');const env={...process.env};env.Path=env.PERSISTED_PATH.replace(/%%([^%%]+)%%/g,(_,k)=>env[k]||'%%'+k+'%%');delete env.PATH;const r=cp.spawnSync(process.env.ComSpec||'cmd.exe',['/d','/s','/c','dfcode --version'],{encoding:'utf8',env,timeout:20000,stdio:['ignore','pipe','pipe']});if(r.error||r.status)process.exit(1);if(r.stdout.trim()===process.env.EXPECTED_VERSION)process.exit(0);process.exit(1);" exit /b !errorlevel! :fail set "RC=1" :end if "!WORK_OWNED!"=="1" if exist "!WORK_DIR!" rd /s /q "!WORK_DIR!" popd 2>nul echo. if not "!RC!"=="0" echo Installer failed. Error code: !RC!. if "!DFCODE_NO_PAUSE!"=="1" exit /b !RC! echo Press any key to close . . . pause >nul exit /b !RC! // DFCODE_WINDOWS_CONTRACT /* Embedded in both Windows installers. Keep the copies in sync; tests check them. */ "use strict"; /* eslint-disable @typescript-eslint/no-require-imports -- Standalone Node 18 CommonJS embedded in Windows installers. */ const fs = require("node:fs"); const path = require("node:path"); const cp = require("node:child_process"); const { performance } = require("node:perf_hooks"); const OFFICIAL = "https://registry.npmjs.org"; const MIRRORS = [ "https://repo.huaweicloud.com/repository/npm", "https://mirrors.cloud.tencent.com/npm", ]; const VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/; function validVersion(value) { if (typeof value !== "string") return false; const match = value.match(VERSION); return Boolean( match && (!match[4] || match[4] .split(".") .every((x) => !/^\d+$/.test(x) || x === "0" || !x.startsWith("0"))), ); } function compareVersion(left, right) { const a = left.match(VERSION), b = right.match(VERSION); for (let i = 1; i <= 3; i++) { const x = BigInt(a[i]), y = BigInt(b[i]); if (x !== y) return x > y ? 1 : -1; } if (!a[4] || !b[4]) return a[4] ? -1 : b[4] ? 1 : 0; const x = a[4].split("."), y = b[4].split("."); for (let i = 0; i < Math.max(x.length, y.length); i++) { if (x[i] === undefined || y[i] === undefined) return x[i] === undefined ? -1 : 1; if (x[i] === y[i]) continue; const xn = /^\d+$/.test(x[i]), yn = /^\d+$/.test(y[i]); if (xn && yn) return BigInt(x[i]) > BigInt(y[i]) ? 1 : -1; if (xn !== yn) return xn ? -1 : 1; return x[i] > y[i] ? 1 : -1; } return 0; } function log(text) { if (process.env.DFCODE_INSTALL_LOG) fs.appendFileSync(process.env.DFCODE_INSTALL_LOG, text); } function run(command, args, timeout = 180000, phase = command) { if (timeout <= 0) return Promise.resolve({ ok: false, stdout: "", text: "Official registry budget exhausted.", timedOut: true, }); if (process.env.DFCODE_INSTALL_KEEP_LOCK === "1") return Promise.resolve({ ok: false, stdout: "", text: "A timed-out process could not be stopped.", }); // Command names are internal constants. Quoting a bare PATH command makes // cmd.exe resolve npm.cmd's %~dp0 against cwd instead of its installation. // Keep the command token plain; every argument remains independently quoted. const line = [command, ...args.map((x) => '"' + x + '"')].join(" "); const started = performance.now(); log( `\n[phase] ${phase} timeout_ms=${Math.ceil(timeout)}\n${command} ${args.join(" ")}\n`, ); return new Promise((resolve) => { const child = cp.spawn( process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", line], { windowsHide: true, windowsVerbatimArguments: true, stdio: ["ignore", "pipe", "pipe"], }, ); let stdout = "", stderr = "", failed = false, finished = false, timedOut = false; const progress = setInterval(() => { const message = `[*] ${phase}\uff1a\u5df2\u7b49\u5f85 ${Math.floor((performance.now() - started) / 1000)} \u79d2\uff0c\u4ecd\u5728\u5904\u7406\u3002`; console.log(message); log(message + "\n"); }, 15000); const finish = (code) => { if (finished) return; finished = true; clearTimeout(timer); clearInterval(progress); const text = stdout + stderr; const elapsedMs = Math.round(performance.now() - started); log( `\n[exit] code=${code} elapsed_ms=${elapsedMs} timed_out=${timedOut}\n`, ); resolve({ ok: !failed && code === 0, stdout, text, timedOut, elapsedMs, exitCode: code, }); }; const stop = (reason) => { if (failed) return; failed = true; stderr += reason + "\n"; log(reason + "\n"); // Kill the cmd/npm/binary tree before releasing the install lock. if (child.pid) { const killed = cp.spawnSync( "taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true, timeout: 10000, stdio: "ignore" }, ); if (killed.status !== 0) { process.env.DFCODE_INSTALL_KEEP_LOCK = "1"; child.kill(); child.stdout.destroy(); child.stderr.destroy(); finish(1); } } }; const timer = setTimeout(() => { timedOut = true; stop(phase + " timed out."); }, timeout); child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (data) => { if (failed) return; stdout += data; log(data); if (stdout.length + stderr.length > 4 * 1024 * 1024) stop("Command output exceeded 4 MiB."); }); child.stderr.on("data", (data) => { if (failed) return; stderr += data; log(data); if (stdout.length + stderr.length > 4 * 1024 * 1024) stop("Command output exceeded 4 MiB."); }); child.on("error", (error) => { failed = true; stderr += error.message; log(error.message + "\n"); finish(1); }); child.on("close", (code, signal) => { // Ctrl+C can be a signal or Windows STATUS_CONTROL_C_EXIT. Never treat // cancellation as permission to launch another installer on a mirror. if ( !failed && (signal || [130, 143, 3221225786, -1073741510].includes(code)) ) { process.env.DFCODE_INSTALL_KEEP_LOCK = "1"; failed = true; stderr += "Installation was interrupted.\n"; log("Installation was interrupted.\n"); } finish(code); }); }); } function fetchOptions(officialPhase, timeout) { return officialPhase ? [ "--fetch-retries=2", "--fetch-retry-factor=10", "--fetch-retry-mintimeout=10000", "--fetch-retry-maxtimeout=60000", "--fetch-timeout=" + Math.max(1, Math.min(300000, Math.ceil(timeout))), ] : [ "--fetch-retries=1", "--fetch-retry-mintimeout=1000", "--fetch-retry-maxtimeout=3000", "--fetch-timeout=20000", ]; } async function metadata( spec, registry, timeout = 45000, officialPhase = false, ) { const r = await run( "npm.cmd", [ "view", spec, "--json", "--registry=" + registry, ...fetchOptions(officialPhase, timeout), ], timeout, (officialPhase ? "\u5b98\u65b9\u6e90\u7248\u672c\u67e5\u8be2" : "\u955c\u50cf\u5b89\u5168\u6821\u9a8c") + " " + spec, ); if (!r.ok) { checkLocalInstallFailure(r); const error = new Error( `\u65e0\u6cd5\u8bfb\u53d6 ${spec} \u7684\u7248\u672c\u4fe1\u606f\uff08${registry}\uff0c${r.timedOut ? "\u7b49\u5f85\u8d85\u65f6" : "\u9000\u51fa\u7801 " + r.exitCode}\uff09\u3002\u8bf7\u67e5\u770b\u65e5\u5fd7\u4e2d\u7684 npm \u9519\u8bef\u3002`, ); const code = npmErrorCode(r.text); if (code && !networkFailure(r) && !["E404", "ETARGET"].includes(code)) error.code = "DFCODE_LOCAL_INSTALL_ERROR"; throw error; } let data; try { data = JSON.parse(r.stdout); } catch { throw new Error("Registry returned invalid JSON for " + spec + "."); } if ( !data || typeof data !== "object" || Array.isArray(data) || !validVersion(data.version) ) throw new Error("Registry returned invalid metadata for " + spec + "."); return data; } async function binaryVersion() { const r = await run("dfcode", ["--version"], 20000); const text = r.stdout.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").trim(); return r.ok && validVersion(text) ? text : null; } function checkLocalInstallFailure(result) { if (result.ok) return; const text = result.text.replace(/\x1b\[[0-9;]*[A-Za-z]/g, ""); const code = npmErrorCode(text); if (!["EBUSY", "EPERM", "EACCES", "ENOSPC"].includes(code)) return; const advice = code === "EBUSY" ? "\u672c\u5730\u6587\u4ef6\u6b63\u5728\u88ab\u4f7f\u7528\uff0cWindows \u65e0\u6cd5\u66ff\u6362\u5b83\u3002" + recoveryAdvice() : code === "ENOSPC" ? "\u5b89\u88c5\u76d8\u6216\u4e34\u65f6\u76ee\u5f55\u6240\u5728\u78c1\u76d8\u7a7a\u95f4\u4e0d\u8db3\uff0c\u8bf7\u91ca\u653e\u7a7a\u95f4\u540e\u91cd\u8bd5\u3002" : "\u672c\u5730\u6587\u4ef6\u8bbf\u95ee\u5931\u8d25\uff0c\u53ef\u80fd\u662f\u5360\u7528\u3001\u76ee\u5f55\u6743\u9650\u6216\u5b89\u5168\u8f6f\u4ef6\u62e6\u622a\u3002\u8bf7\u68c0\u67e5\u9519\u8bef\u8def\u5f84\u3002" + recoveryAdvice(); const location = text.match(/^\s*npm (?:error|ERR!) path (.+)$/m)?.[1]; const error = new Error( `\u5347\u7ea7\u672a\u5b8c\u6210\uff08${code}\uff09\uff1a${advice}${location ? "\n\u8def\u5f84\uff1a" + location.trim() : ""}\n\u8fd9\u662f\u672c\u5730\u6587\u4ef6\u95ee\u9898\uff0c\u4e0d\u4f1a\u5207\u6362\u955c\u50cf\u91cd\u8bd5\u3002`, ); error.code = "DFCODE_LOCAL_INSTALL_ERROR"; throw error; } function recoveryAdvice() { return ( "\n1. \u4fdd\u5b58\u5de5\u4f5c\uff0c\u6b63\u5e38\u9000\u51fa\u4f7f\u7528\u8be5\u5b89\u88c5\u76ee\u5f55\u7684 DFCode \u7ec8\u7aef\u3001Studio \u6216 IDE \u4efb\u52a1\uff1b\u5176\u4ed6\u5b89\u88c5\u76ee\u5f55\u7684\u4f1a\u8bdd\u4e0d\u5fc5\u5173\u95ed\u3002" + "\n2. \u91cd\u65b0\u8fd0\u884c\u540c\u4e00\u6761\u5b89\u88c5\u547d\u4ee4\uff0c\u65e0\u9700\u5148\u5378\u8f7d\u6216\u6e05\u7a7a\u7f13\u5b58\u3002" + "\n3. \u56de\u5230\u539f\u9879\u76ee\u76ee\u5f55\uff0c\u6267\u884c dfcode -c \u7ee7\u7eed\u6700\u8fd1\u7684\u4f1a\u8bdd\uff1b\u6216\u542f\u52a8 dfcode \u540e\u8f93\u5165 /sessions \u9009\u62e9\u5386\u53f2\u4f1a\u8bdd\u3002" + "\n\u5b89\u88c5\u5668\u4e0d\u4f1a\u5220\u9664\u5386\u53f2\u4f1a\u8bdd\uff0c\u4e5f\u4e0d\u4f1a\u81ea\u52a8\u7ed3\u675f\u4f60\u7684\u8fdb\u7a0b\u3002\u6b63\u5728\u6267\u884c\u7684\u4efb\u52a1\u8bf7\u5148\u59a5\u5584\u505c\u6b62\u3002" ); } function canonicalWindowsPath(filename) { const win = path.win32; let resolved; try { resolved = fs.realpathSync.native(filename); } catch (error) { if (error.code !== "ENOENT" && error.code !== "ENOTDIR") throw error; const parent = win.dirname(filename); resolved = parent === filename ? filename : win.join(canonicalWindowsPath(parent), win.basename(filename)); } return win .normalize( resolved.replace(/^\\\\\?\\UNC\\/i, "\\\\").replace(/^\\\\\?\\/, ""), ) .replace(/[\\/]+$/, ""); } async function checkRunningDfcode() { const rootResult = await run( "npm.cmd", ["root", "-g"], 20000, "\u68c0\u67e5\u5b89\u88c5\u76ee\u5f55", ); const root = rootResult.stdout.trim(); if (!rootResult.ok || !path.win32.isAbsolute(root) || /[\r\n]/.test(root)) throw new Error( "\u65e0\u6cd5\u786e\u5b9a npm \u5168\u5c40\u5b89\u88c5\u76ee\u5f55\uff0c\u5347\u7ea7\u5c1a\u672a\u5f00\u59cb\u3002\u8bf7\u8fd0\u884c npm root -g \u68c0\u67e5\u5f53\u524d Node.js \u73af\u5883\u3002", ); const packageRoot = path.win32.join(root, "dfcode"); const directories = [packageRoot]; for (const name of ["dfcode-windows-x64", "dfcode-windows-x64-baseline"]) { directories.push( path.win32.join(packageRoot, "node_modules", name), path.win32.join(root, name), ); } const scopes = directories.map((dir) => canonicalWindowsPath(dir).toLowerCase(), ); // Optional diagnostic only: file probes below remain available when CIM or // PowerShell is restricted. No process-name-only veto and no user process kill. const script = "$ErrorActionPreference='Stop'; [Console]::OutputEncoding=New-Object Text.UTF8Encoding($false); ConvertTo-Json -Compress -InputObject @(Get-CimInstance Win32_Process -Filter \"Name = 'dfcode.exe'\" | Select-Object ProcessId,ExecutablePath)"; const result = await run( "powershell.exe", [ "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64"), ], 20000, "\u68c0\u67e5\u76f8\u5173 DFCode \u8fdb\u7a0b", ); let processes = []; try { if (result.ok) { const data = JSON.parse(result.stdout.replace(/^\uFEFF/, "")); if (Array.isArray(data)) processes = data; } } catch { /* File probes are the fallback, not a blanket installation veto. */ } const affected = processes.filter((item) => { if ( typeof item?.ExecutablePath !== "string" || !path.win32.isAbsolute(item.ExecutablePath) ) return false; try { const filename = canonicalWindowsPath(item.ExecutablePath).toLowerCase(); return scopes.some((dir) => filename.startsWith(dir + "\\")); } catch { return false; } }); if (affected.length) { throw new Error( "\u5347\u7ea7\u5c1a\u672a\u5f00\u59cb\uff1a\u5f53\u524d\u5b89\u88c5\u76ee\u5f55\u4e2d\u7684 DFCode \u6b63\u5728\u8fd0\u884c\uff0cWindows \u65e0\u6cd5\u66ff\u6362\u76f8\u5173\u7a0b\u5e8f\u6587\u4ef6\u3002\n" + affected .map((item) => `PID: ${item.ProcessId} ${item.ExecutablePath}`) .join("\n") + recoveryAdvice(), ); } for (const dir of directories) { const filename = path.win32.join(dir, "bin", "dfcode.exe"); // r+ requests write access without truncating, writing or renaming anything. // Opening is also the existence check: existsSync would hide EACCES. let handle; try { handle = fs.openSync(filename, "r+"); } catch (error) { if (error.code === "ENOENT" || error.code === "ENOTDIR") continue; throw new Error( `\u5347\u7ea7\u5c1a\u672a\u5f00\u59cb\uff1a\u5b89\u88c5\u6587\u4ef6\u88ab\u5360\u7528\u6216\u4e0d\u53ef\u5199\uff08${error.code}\uff09\u3002\n\u8def\u5f84\uff1a${filename}` + recoveryAdvice(), ); } finally { if (handle !== undefined) fs.closeSync(handle); } } } function npmErrorCode(text) { return [ ...text .replace(/\x1b\[[0-9;]*[A-Za-z]/g, "") .matchAll(/^\s*npm (?:error|ERR!) code (\S+)\s*$/gm), ].at(-1)?.[1]; } function networkFailure(result) { const codes = "ECONNRESET|ECONNREFUSED|ECONNABORTED|ETIMEDOUT|ESOCKETTIMEDOUT|ERR_SOCKET_TIMEOUT|EAI_AGAIN|EAI_FAIL|ENETUNREACH|EHOSTUNREACH|ENOTFOUND|FETCH_ERROR|E408|E429|E50[0-9]"; const code = npmErrorCode(result.text); if (code) return new RegExp("^(?:" + codes + ")$").test(code); return ( result.timedOut || new RegExp("^(?:" + codes + ")\\b", "m").test(result.text) ); } async function mirrorContract(officialRoot, target, mirrorRegistry) { const mirrorRoot = await metadata("dfcode@" + target, mirrorRegistry); if ( mirrorRoot.version !== target || !officialRoot.dist?.integrity || mirrorRoot.dist?.integrity !== officialRoot.dist.integrity ) throw new Error( "Mirror root package does not match the official " + target + " release.", ); const platformNames = ["dfcode-windows-x64", "dfcode-windows-x64-baseline"]; for (const name of platformNames) { const version = officialRoot.optionalDependencies?.[name]; if ( version !== target || mirrorRoot.optionalDependencies?.[name] !== version ) throw new Error( "Release is missing the exact Windows platform dependency " + name + "@" + target + ".", ); const official = await metadata(name + "@" + version, OFFICIAL); const mirror = await metadata(name + "@" + version, mirrorRegistry); if ( official.version !== version || mirror.version !== version || !official.dist?.integrity || mirror.dist?.integrity !== official.dist.integrity ) throw new Error( "Mirror platform package does not match " + name + "@" + version + ".", ); } } function acquireLock() { const base = path.join( process.env.LOCALAPPDATA || process.env.TEMP, "dfcode", ); fs.mkdirSync(base, { recursive: true }); const filename = path.join(base, "npm-installer.lock"); // Do not remove an existing lock: the other installer may still be running. // A leftover lock after a killed process requires an explicit operator check. let handle; try { handle = fs.openSync(filename, "wx"); } catch (error) { if (error.code === "EEXIST") throw new Error( "Another DFCode installation may be running. Close it before retrying. If it was interrupted, check and remove " + filename + ".", ); throw error; } fs.writeFileSync( handle, JSON.stringify({ pid: process.pid, started: new Date().toISOString() }) + "\n", ); return () => { fs.closeSync(handle); if (process.env.DFCODE_INSTALL_KEEP_LOCK !== "1") fs.unlinkSync(filename); }; } async function main() { const checkInterrupted = () => { if (process.env.DFCODE_INSTALL_KEEP_LOCK === "1") throw new Error( "Installation was interrupted or its process tree could not be stopped. No further installation will be started; check the retained installer lock before retrying.", ); }; checkInterrupted(); const pin = process.env.DFCODE_VERSION || ""; const tag = process.env.DFCODE_DIST_TAG || "latest"; if (pin && !validVersion(pin)) throw new Error( "DFCODE_VERSION must be an exact semantic version, for example 3.3.17 or 3.3.18-beta.1.", ); if (!pin && !["latest", "stable", "beta"].includes(tag)) throw new Error("DFCODE_DIST_TAG must be latest, stable, or beta."); if (process.arch !== "x64") throw new Error( "DFCode Windows currently requires x64 Node.js. Native ARM64 and x86 packages are not published.", ); const officialDeadline = performance.now() + 600000; const remaining = () => Math.max(0, officialDeadline - performance.now()); console.log( "[*] \u5b98\u65b9\u6e90\u4f18\u5148\uff1a\u7248\u672c\u67e5\u8be2\u3001\u5b89\u88c5\u548c\u91cd\u8bd5\u5408\u8ba1\u6700\u591a\u7b49\u5f85 10 \u5206\u949f\u3002", ); const release = await metadata( "dfcode@" + (pin || tag), OFFICIAL, remaining(), true, ); if (pin && release.version !== pin) throw new Error( "Official registry did not return the requested version " + pin + ".", ); const target = release.version; console.log("[OK] Target DFCode version: " + target); const unlock = acquireLock(); try { const before = await binaryVersion(); checkInterrupted(); if (!pin && before && compareVersion(before, target) > 0) { console.log("[OK] Keeping newer working DFCode " + before + "."); fs.writeFileSync(process.env.DFCODE_INSTALL_EXPECTED, before); return; } if (before !== target) { await checkRunningDfcode(); checkInterrupted(); const installOfficial = () => { const timeout = remaining(); return run( "npm.cmd", [ "install", "-g", "dfcode@" + target, "--include=optional", "--registry=" + OFFICIAL, ...fetchOptions(true, timeout), ], timeout, "\u5b98\u65b9\u6e90\u5b89\u88c5", ); }; let installed = await installOfficial(); checkInterrupted(); checkLocalInstallFailure(installed); if ( !installed.ok && networkFailure(installed) && !installed.timedOut && remaining() > 0 ) { console.log( "[!] \u5b98\u65b9\u6e90\u6682\u65f6\u51fa\u73b0\u7f51\u7edc\u9519\u8bef\uff0c\u5c06\u5728\u5269\u4f59\u65f6\u95f4\u5185\u91cd\u8bd5\u4e00\u6b21\uff1b\u4e0d\u4f1a\u91cd\u65b0\u8ba1\u7b97 10 \u5206\u949f\u3002", ); installed = await installOfficial(); checkInterrupted(); checkLocalInstallFailure(installed); } let actual = installed.ok ? await binaryVersion() : null; checkInterrupted(); if (installed.ok && actual !== target) throw new Error( `npm \u5b89\u88c5\u5df2\u5b8c\u6210\uff0c\u4f46\u547d\u4ee4\u8fd0\u884c\u7248\u672c\u4e0d\u7b26\uff1a\u9884\u671f ${target}\uff0c\u5b9e\u9645 ${actual || "\u65e0\u6cd5\u542f\u52a8"}\u3002\u8bf7\u68c0\u67e5 PATH \u4e2d\u7684\u65e7\u5b89\u88c5\u53ca\u65e5\u5fd7\uff1b\u4e0d\u4f1a\u901a\u8fc7\u5207\u6362\u955c\u50cf\u4fee\u590d\u672c\u5730\u542f\u52a8\u95ee\u9898\u3002`, ); if (!installed.ok && !networkFailure(installed)) throw new Error( `\u5b98\u65b9\u6e90\u5b89\u88c5\u672a\u5b8c\u6210\uff08\u9000\u51fa\u7801 ${installed.exitCode}\uff09\u3002\u65e5\u5fd7\u672a\u663e\u793a\u53ef\u91cd\u8bd5\u7684\u7f51\u7edc\u9519\u8bef\uff0c\u8bf7\u68c0\u67e5 npm \u9519\u8bef\uff1b\u672a\u5207\u6362\u955c\u50cf\u3002`, ); if (!installed.ok) { console.log( `[!] \u5b98\u65b9\u6e90${installed.timedOut ? "\u7b49\u5f85\u9884\u7b97\u5df2\u8017\u5c3d" : "\u7f51\u7edc\u91cd\u8bd5\u5931\u8d25"}\uff0c\u5f00\u59cb\u6309\u534e\u4e3a\u4e91\u3001\u817e\u8baf\u4e91\u987a\u5e8f\u9a8c\u8bc1\u540c\u4e00\u7248\u672c\u3002`, ); let lastMirrorError = null; for (const mirror of MIRRORS) { try { console.log( "[!] Checking " + mirror + " for the same release and its Windows binaries.", ); await mirrorContract(release, target, mirror); checkInterrupted(); installed = await run( "npm.cmd", [ "install", "-g", "dfcode@" + target, "--include=optional", "--registry=" + mirror, "--fetch-retries=1", "--fetch-retry-mintimeout=1000", "--fetch-retry-maxtimeout=3000", "--fetch-timeout=30000", ], 180000, "\u955c\u50cf\u5b89\u88c5 " + mirror, ); checkInterrupted(); checkLocalInstallFailure(installed); actual = installed.ok ? await binaryVersion() : null; checkInterrupted(); if (installed.ok && actual !== target) { const error = new Error( `\u955c\u50cf\u5b89\u88c5\u540e\u8fd0\u884c\u7248\u672c\u4e0d\u7b26\uff1a\u9884\u671f ${target}\uff0c\u5b9e\u9645 ${actual || "\u65e0\u6cd5\u542f\u52a8"}\u3002\u8bf7\u68c0\u67e5 PATH \u548c\u65e5\u5fd7\u3002`, ); error.code = "DFCODE_LOCAL_INSTALL_ERROR"; throw error; } if (!installed.ok && !networkFailure(installed)) { const error = new Error( `\u955c\u50cf\u5b89\u88c5\u9047\u5230\u975e\u7f51\u7edc\u9519\u8bef\uff08\u9000\u51fa\u7801 ${installed.exitCode}\uff09\uff0c\u8bf7\u67e5\u770b\u65e5\u5fd7\u3002`, ); error.code = "DFCODE_LOCAL_INSTALL_ERROR"; throw error; } if (installed.ok && actual === target) break; } catch (error) { checkInterrupted(); if (error.code === "DFCODE_LOCAL_INSTALL_ERROR") throw error; lastMirrorError = error; console.log( "[!] " + mirror + " is unavailable for this release; continuing to the next mirror.", ); installed = { ok: false }; } } if ((!installed.ok || actual !== target) && lastMirrorError) throw lastMirrorError; } if (!installed.ok) throw new Error( "Installation failed. The requested version was not installed.", ); } const actual = await binaryVersion(); if (actual !== target) throw new Error( "DFCode binary verification failed: expected " + target + ", got " + (actual || "no working binary") + ".", ); fs.writeFileSync(process.env.DFCODE_INSTALL_EXPECTED, target); console.log("[OK] Verified DFCode binary: " + actual); } finally { unlock(); } } module.exports = { validVersion, compareVersion, mirrorContract, main }; if (require.main === module) { main().catch((error) => { console.error("[X] " + error.message); if (process.env.DFCODE_INSTALL_LOG) console.error("[X] Log: " + process.env.DFCODE_INSTALL_LOG); process.exitCode = 1; }); }