docs: 重构 README 新手使用说明,添加一键安装脚本文档
This commit is contained in:
133
README.md
133
README.md
@@ -17,26 +17,94 @@
|
||||
|
||||
## 新手使用说明
|
||||
|
||||
如果你是第一次使用命令行工具,请按以下步骤操作。
|
||||
如果你是第一次使用命令行工具,请按以下步骤操作。我们提供了一键安装脚本,尽量让你少输入命令。
|
||||
|
||||
### 准备工作
|
||||
### 安装前准备
|
||||
|
||||
- **PowerPoint**:需要安装 Microsoft PowerPoint(Office 2016 或更新版本)
|
||||
- **网络连接**:安装过程中需要下载工具
|
||||
- **理解命令行工具**:你需要理解命令行的基本操作,包括文件路径、参数传递等。
|
||||
- **解压项目**:把下载的 `pptopic.zip` 解压到任意位置,例如桌面
|
||||
|
||||
### PowerShell的基本知识
|
||||
### 一键安装(推荐)
|
||||
|
||||
PowerShell在Windows中是自带的,所以一般情况下无需额外安装。如果你不知道如何打开,可以在开始菜单搜索 "PowerShell",通常就能看见,点击后即可打开。
|
||||
项目根目录下有一个 `setup.bat`,它会自动完成:
|
||||
|
||||
在PowerShell中跳转目录,只需要输入 `cd` 加上目录路径即可。目录路径可以从资源管理器中在地址栏中复制。如果你的目录路径中包含空格,需要在路径中添加引号。
|
||||
1. 安装 `uv`(Python 包管理器)
|
||||
2. 安装 Python 3.13
|
||||
3. 安装 `pngquant`(图片压缩工具,强烈推荐)
|
||||
4. 安装 `pptopic`
|
||||
5. 验证安装结果
|
||||
|
||||
#### 方法 A:双击运行(最简单)
|
||||
|
||||
1. 打开解压后的 `pptopic` 文件夹
|
||||
2. 找到 `setup.bat` 文件
|
||||
3. **双击** `setup.bat`
|
||||
4. 等待安装完成,窗口会显示版本信息
|
||||
|
||||
> 如果 Windows 提示“Windows 已保护你的电脑”,请点击“更多信息” → “仍要运行”。
|
||||
|
||||
#### 方法 B:右键在 PowerShell 中运行
|
||||
|
||||
如果双击运行被系统拦截,可以按以下步骤:
|
||||
|
||||
1. 打开解压后的 `pptopic` 文件夹
|
||||
2. 在文件夹**空白处**按住 `Shift` 键,同时点击**鼠标右键**
|
||||
3. 选择“在此处打开 PowerShell 窗口”(Windows 11 可能显示为“在终端中打开”)
|
||||
4. 在弹出的蓝色窗口中输入以下命令,然后按回车:
|
||||
|
||||
```powershell
|
||||
# 这是一个例子
|
||||
cd "C:\Users\User SomeX\Desktop\OneFolder"
|
||||
.\setup.bat
|
||||
```
|
||||
|
||||
### 第一步:安装 uv
|
||||
等待安装完成即可。
|
||||
|
||||
#### 安装完成后
|
||||
|
||||
如果看到类似下面的输出,说明安装成功:
|
||||
|
||||
```
|
||||
--- uv 版本 ---
|
||||
uv 0.11.20
|
||||
|
||||
--- pngquant 版本 ---
|
||||
2.17.0
|
||||
|
||||
--- pptopic 版本 ---
|
||||
pptopic 0.3.2
|
||||
```
|
||||
|
||||
然后你可以直接在当前窗口使用 pptopic:
|
||||
|
||||
```powershell
|
||||
# 导出 PPTX 为长图
|
||||
pptopic export presentation.pptx
|
||||
|
||||
# 工作中常用命令:导出 PPTX 为长图并优化图片
|
||||
# 其中,29999 是微信接受的最大高度
|
||||
pptopic export presentation.pptx --optimize --max-height 29999 -o result.png
|
||||
```
|
||||
|
||||
### 一键安装脚本的参数(可选)
|
||||
|
||||
如果你不想安装 pngquant,可以使用以下方式运行:
|
||||
|
||||
```powershell
|
||||
# 在 PowerShell 中直接运行 setup.ps1,跳过 pngquant
|
||||
powershell -ExecutionPolicy Bypass -File .\setup.ps1 -SkipPngquant
|
||||
```
|
||||
|
||||
其他可用参数:
|
||||
|
||||
- `-SkipPngquant`:跳过 pngquant 安装
|
||||
- `-Force`:强制重新安装 uv 和 pngquant
|
||||
- `-SkipPptopicInstall`:只安装环境,不安装 pptopic
|
||||
|
||||
### 手动安装(备用)
|
||||
|
||||
如果一键安装脚本在你的电脑上无法运行,可以按以下步骤手动安装。
|
||||
|
||||
#### 第一步:安装 uv
|
||||
|
||||
`uv` 是一个 Python 包管理器,用来安装 pptopic 及其依赖。
|
||||
|
||||
@@ -46,7 +114,7 @@ cd "C:\Users\User SomeX\Desktop\OneFolder"
|
||||
cd "<你的解压目录>\pptopic"
|
||||
```
|
||||
|
||||
赋予PowerShell执行权限:
|
||||
赋予 PowerShell 执行权限:
|
||||
|
||||
```powershell
|
||||
Set-ExecutionPolicy Bypass -Scope CurrentUser
|
||||
@@ -70,7 +138,7 @@ uv --version
|
||||
> - 如果你已安装 [scoop](https://scoop.sh/),也可以直接 `scoop install uv`。
|
||||
> - 如果想安装特定版本的 uv,可以在运行脚本前设置环境变量:`$env:UV_INSTALLER_VERSION = "0.11.20"`
|
||||
|
||||
### 第二步:安装 Python
|
||||
#### 第二步:安装 Python
|
||||
|
||||
使用 uv 安装 Python 3.13 或更新版本:
|
||||
|
||||
@@ -78,9 +146,9 @@ uv --version
|
||||
uv python install 3.13
|
||||
```
|
||||
|
||||
### 第三步(可选):安装 pngquant 图片优化工具
|
||||
#### 第三步(可选):安装 pngquant 图片优化工具
|
||||
|
||||
虽然是可选安装,但我超级建议你安装,因为ppt导出后,图片通常较大,不进行图片无损压缩,会导致文件大小过大。
|
||||
虽然是可选安装,但我超级建议你安装,因为 ppt 导出后,图片通常较大,不进行图片无损压缩,会导致文件大小过大。
|
||||
|
||||
如果你需要对导出的图片进行压缩优化,运行:
|
||||
|
||||
@@ -101,13 +169,13 @@ pngquant --version
|
||||
> - 自定义安装目录:`.\install-pngquant.ps1 -InstallDir "D:\Tools\pngquant"`
|
||||
> - 强制重新安装:`.\install-pngquant.ps1 -Force`
|
||||
|
||||
### 第四步:安装pptopic
|
||||
#### 第四步:安装 pptopic
|
||||
|
||||
```powershell
|
||||
uv tool install -e .
|
||||
```
|
||||
|
||||
### 第五步:开始使用
|
||||
#### 第五步:开始使用
|
||||
|
||||
```powershell
|
||||
# 导出 PPTX 为长图
|
||||
@@ -117,11 +185,11 @@ pptopic export presentation.pptx
|
||||
pptopic optimize image.png
|
||||
|
||||
# 工作中常用命令:导出 PPTX 为长图并优化图片
|
||||
# 其中,29999是微信接受的最大高度
|
||||
# 其中,29999 是微信接受的最大高度
|
||||
pptopic export presentation.pptx --optimize --max-height 29999 -o result.png
|
||||
```
|
||||
|
||||
至此安装完成。如果任何步骤遇到问题,请参考下方详细说明。
|
||||
至此安装完成。
|
||||
|
||||
## 一般安装说明
|
||||
|
||||
@@ -222,6 +290,37 @@ scoop install pngquant
|
||||
|
||||
或从 [pngquant.org](https://pngquant.org/) 下载 Windows 版本手动配置。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 运行 `setup.bat` 时提示“无法加载文件,因为在此系统上禁止运行脚本”
|
||||
|
||||
这说明当前电脑通过组策略严格限制了脚本执行。`setup.bat` 已经尝试用 `-ExecutionPolicy Bypass` 绕过,但仍可能被拦截。
|
||||
|
||||
解决方法:使用 **方法 B**,在 PowerShell 窗口中手动运行:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\setup.ps1
|
||||
```
|
||||
|
||||
不需要以管理员身份运行,本安装脚本只修改当前用户的环境变量。
|
||||
|
||||
### 安装完成后关闭窗口,新窗口中找不到 `pptopic` 命令
|
||||
|
||||
请重新运行一次 `setup.bat`。如果问题依旧,请检查系统环境变量中的 PATH 是否包含以下目录:
|
||||
|
||||
- `%USERPROFILE%\.local\bin`(uv 和 pptopic 的位置)
|
||||
- `%APPDATA%\pngquant`(pngquant 的位置)
|
||||
|
||||
### 不想安装 pngquant 怎么办?
|
||||
|
||||
可以运行:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\setup.ps1 -SkipPngquant
|
||||
```
|
||||
|
||||
但强烈建议安装,否则导出的长图文件会比较大。
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
24
setup.bat
Normal file
24
setup.bat
Normal file
@@ -0,0 +1,24 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo.
|
||||
echo ========================================
|
||||
echo pptopic 一键安装程序
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 即将自动安装:uv、Python 3.13、pngquant 和 pptopic
|
||||
echo 安装过程中请勿关闭此窗口...
|
||||
echo.
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File "%~dp0setup.ps1" %*
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo.
|
||||
echo 安装过程中出现错误,请查看上方提示。
|
||||
echo.
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 按任意键关闭此窗口...
|
||||
pause >nul
|
||||
281
setup.ps1
Normal file
281
setup.ps1
Normal file
@@ -0,0 +1,281 @@
|
||||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
pptopic 一键安装脚本
|
||||
|
||||
.DESCRIPTION
|
||||
自动完成以下安装步骤:
|
||||
1. 安装 uv(Python 包管理器)
|
||||
2. 安装 Python 3.13
|
||||
3. 安装 pngquant(可选但推荐)
|
||||
4. 安装 pptopic
|
||||
5. 验证安装结果
|
||||
|
||||
本脚本会自动处理 PowerShell 执行策略和当前会话 PATH 刷新,
|
||||
安装完成后无需手动重启 PowerShell。
|
||||
|
||||
.PARAMETER SkipPngquant
|
||||
跳过 pngquant 安装。
|
||||
|
||||
.PARAMETER Force
|
||||
强制重新安装 uv 和 pngquant。
|
||||
|
||||
.PARAMETER SkipPptopicInstall
|
||||
只安装环境,不安装 pptopic(调试用)。
|
||||
|
||||
.EXAMPLE
|
||||
.\setup.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\setup.ps1 -SkipPngquant
|
||||
#>
|
||||
|
||||
param(
|
||||
[switch]$SkipPngquant,
|
||||
[switch]$Force,
|
||||
[switch]$SkipPptopicInstall
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$InformationPreference = "Continue"
|
||||
|
||||
$ProjectRoot = $PSScriptRoot
|
||||
if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
|
||||
$ProjectRoot = (Get-Location).Path
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 工具函数
|
||||
# ============================================================
|
||||
|
||||
function Test-CommandExists {
|
||||
param([string]$Name)
|
||||
try {
|
||||
$cmd = Get-Command $Name -ErrorAction Stop
|
||||
return $cmd.Source
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Add-ToCurrentPath {
|
||||
param([string]$LiteralPath)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($LiteralPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
$currentPaths = $env:Path -split ';' | ForEach-Object { $_.TrimEnd('\') }
|
||||
$normalizedPath = $LiteralPath.TrimEnd('\')
|
||||
|
||||
if ($normalizedPath -in $currentPaths) {
|
||||
return
|
||||
}
|
||||
|
||||
if (-not (Test-Path $LiteralPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
$env:Path = "$LiteralPath;$env:Path"
|
||||
Write-Information "已临时将 $LiteralPath 加入当前会话 PATH"
|
||||
}
|
||||
|
||||
function Get-UvInstallDir {
|
||||
# uv-installer.ps1 的安装位置优先级
|
||||
$candidates = @()
|
||||
if ($env:XDG_BIN_HOME) {
|
||||
$candidates += $env:XDG_BIN_HOME
|
||||
}
|
||||
if ($env:XDG_DATA_HOME) {
|
||||
$candidates += (Join-Path $env:XDG_DATA_HOME "../bin")
|
||||
}
|
||||
$candidates += (Join-Path $HOME ".local\bin")
|
||||
|
||||
foreach ($candidate in $candidates) {
|
||||
$resolved = $null
|
||||
try {
|
||||
$resolved = (Resolve-Path $candidate -ErrorAction SilentlyContinue).Path
|
||||
} catch {
|
||||
$resolved = $candidate
|
||||
}
|
||||
if ($resolved -and (Test-Path $resolved)) {
|
||||
return $resolved
|
||||
}
|
||||
}
|
||||
|
||||
# 默认返回最后一个候选
|
||||
return Join-Path $HOME ".local\bin"
|
||||
}
|
||||
|
||||
function Invoke-Step {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Title,
|
||||
[Parameter(Mandatory)][scriptblock]$Action
|
||||
)
|
||||
|
||||
Write-Information ""
|
||||
Write-Information "========================================"
|
||||
Write-Information " $Title"
|
||||
Write-Information "========================================"
|
||||
|
||||
try {
|
||||
& $Action
|
||||
} catch {
|
||||
Write-Information ""
|
||||
Write-Information "失败:$_"
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 主流程
|
||||
# ============================================================
|
||||
|
||||
Write-Information ""
|
||||
Write-Information "========================================"
|
||||
Write-Information " pptopic 一键安装程序"
|
||||
Write-Information "========================================"
|
||||
Write-Information ""
|
||||
Write-Information "项目目录:$ProjectRoot"
|
||||
|
||||
if ($SkipPngquant) {
|
||||
Write-Information "已指定 -SkipPngquant,将跳过 pngquant 安装。"
|
||||
}
|
||||
if ($Force) {
|
||||
Write-Information "已指定 -Force,将强制重新安装 uv / pngquant。"
|
||||
}
|
||||
|
||||
# 第一步:安装 uv
|
||||
Invoke-Step -Title "步骤 1/5:安装 uv" -Action {
|
||||
$uvPath = Test-CommandExists -Name "uv"
|
||||
|
||||
if ($uvPath -and -not $Force) {
|
||||
Write-Information "uv 已存在:$uvPath"
|
||||
} else {
|
||||
$installer = Join-Path $ProjectRoot "uv-installer.ps1"
|
||||
if (-not (Test-Path $installer)) {
|
||||
throw "找不到 uv 安装脚本:$installer"
|
||||
}
|
||||
|
||||
Write-Information "正在运行 uv-installer.ps1..."
|
||||
& $installer
|
||||
}
|
||||
|
||||
# uv-installer.ps1 会修改注册表 PATH,但当前会话不会立即生效,需要手动加入
|
||||
$uvInstallDir = Get-UvInstallDir
|
||||
Add-ToCurrentPath -LiteralPath $uvInstallDir
|
||||
|
||||
# 再次验证
|
||||
$uvPath = Test-CommandExists -Name "uv"
|
||||
if (-not $uvPath) {
|
||||
throw "uv 安装后仍无法在当前会话中找到,请尝试重启终端后重试。"
|
||||
}
|
||||
|
||||
Write-Information "uv 路径:$uvPath"
|
||||
}
|
||||
|
||||
# 第二步:安装 Python 3.13
|
||||
Invoke-Step -Title "步骤 2/5:安装 Python 3.13" -Action {
|
||||
Write-Information "正在使用 uv 安装 Python 3.13..."
|
||||
uv python install 3.13
|
||||
Write-Information "Python 安装完成。"
|
||||
}
|
||||
|
||||
# 第三步:安装 pngquant
|
||||
if (-not $SkipPngquant) {
|
||||
Invoke-Step -Title "步骤 3/5:安装 pngquant" -Action {
|
||||
$pngquantPath = Test-CommandExists -Name "pngquant"
|
||||
|
||||
if ($pngquantPath -and -not $Force) {
|
||||
Write-Information "pngquant 已存在:$pngquantPath"
|
||||
} else {
|
||||
$installer = Join-Path $ProjectRoot "install-pngquant.ps1"
|
||||
if (-not (Test-Path $installer)) {
|
||||
throw "找不到 pngquant 安装脚本:$installer"
|
||||
}
|
||||
|
||||
if ($Force) {
|
||||
Write-Information "正在运行 install-pngquant.ps1 -Force..."
|
||||
& $installer -Force
|
||||
} else {
|
||||
Write-Information "正在运行 install-pngquant.ps1..."
|
||||
& $installer
|
||||
}
|
||||
}
|
||||
|
||||
# install-pngquant.ps1 内部已刷新当前会话 PATH,这里再做一次保险
|
||||
Add-ToCurrentPath -LiteralPath "$env:APPDATA\pngquant"
|
||||
|
||||
$pngquantPath = Test-CommandExists -Name "pngquant"
|
||||
if (-not $pngquantPath) {
|
||||
throw "pngquant 安装后仍无法在当前会话中找到,请尝试重启终端后重试。"
|
||||
}
|
||||
|
||||
Write-Information "pngquant 路径:$pngquantPath"
|
||||
}
|
||||
} else {
|
||||
Write-Information ""
|
||||
Write-Information "========================================"
|
||||
Write-Information " 步骤 3/5:跳过 pngquant 安装"
|
||||
Write-Information "========================================"
|
||||
}
|
||||
|
||||
# 第四步:安装 pptopic
|
||||
if (-not $SkipPptopicInstall) {
|
||||
Invoke-Step -Title "步骤 4/5:安装 pptopic" -Action {
|
||||
$pyproject = Join-Path $ProjectRoot "pyproject.toml"
|
||||
if (-not (Test-Path $pyproject)) {
|
||||
throw "找不到 pyproject.toml:$pyproject"
|
||||
}
|
||||
|
||||
Write-Information "正在使用 uv tool install -e $ProjectRoot 安装 pptopic..."
|
||||
uv tool install -e $ProjectRoot
|
||||
Write-Information "pptopic 安装完成。"
|
||||
}
|
||||
} else {
|
||||
Write-Information ""
|
||||
Write-Information "========================================"
|
||||
Write-Information " 步骤 4/5:跳过 pptopic 安装"
|
||||
Write-Information "========================================"
|
||||
}
|
||||
|
||||
# 第五步:验证
|
||||
Invoke-Step -Title "步骤 5/5:验证安装" -Action {
|
||||
Write-Information ""
|
||||
Write-Information "--- uv 版本 ---"
|
||||
uv --version
|
||||
|
||||
if (-not $SkipPngquant) {
|
||||
Write-Information ""
|
||||
Write-Information "--- pngquant 版本 ---"
|
||||
pngquant --version
|
||||
}
|
||||
|
||||
if (-not $SkipPptopicInstall) {
|
||||
Write-Information ""
|
||||
Write-Information "--- pptopic 版本 ---"
|
||||
pptopic version
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 完成
|
||||
# ============================================================
|
||||
|
||||
Write-Information ""
|
||||
Write-Information "========================================"
|
||||
Write-Information " pptopic 一键安装完成!"
|
||||
Write-Information "========================================"
|
||||
Write-Information ""
|
||||
|
||||
if (-not $SkipPptopicInstall) {
|
||||
Write-Information "你可以立即在当前窗口使用以下命令:"
|
||||
Write-Information " pptopic export 你的文件.pptx"
|
||||
Write-Information " pptopic export 你的文件.pptx --optimize --max-height 29999 -o result.png"
|
||||
}
|
||||
|
||||
Write-Information ""
|
||||
Write-Information "提示:本脚本已自动刷新当前 PowerShell 会话的 PATH,无需重启终端。"
|
||||
Write-Information "如果关闭此窗口后在新窗口中找不到命令,请重新运行一次 setup.bat。"
|
||||
Write-Information ""
|
||||
161
uv-installer.ps1
161
uv-installer.ps1
@@ -33,6 +33,90 @@ param (
|
||||
[Parameter(HelpMessage = "Print Help")]
|
||||
[switch]$Help
|
||||
)
|
||||
function WebProxyFromUrl {
|
||||
param([string]$ProxyUrl)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ProxyUrl)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
try {
|
||||
# Parse the proxy URL
|
||||
$uri = [System.Uri]$ProxyUrl
|
||||
|
||||
# Create WebProxy instance
|
||||
$webProxy = New-Object System.Net.WebProxy($uri)
|
||||
|
||||
# Set credentials if provided in URL
|
||||
if (-not [string]::IsNullOrEmpty($uri.UserInfo)) {
|
||||
$userInfo = $uri.UserInfo.Split(':')
|
||||
$username = [System.Uri]::UnescapeDataString($userInfo[0])
|
||||
$password = if ($null -eq $userInfo[1]) { "" } else { [System.Uri]::UnescapeDataString($userInfo[1]) }
|
||||
$webProxy.Credentials = New-Object System.Net.NetworkCredential($username, $password)
|
||||
}
|
||||
|
||||
return $webProxy
|
||||
}
|
||||
catch {
|
||||
Write-Verbose("Failed to parse proxy URL '$ProxyUrl': $($_.Exception.Message)")
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function WebProxyFromEnvironment {
|
||||
$httpsProxy = [System.Environment]::GetEnvironmentVariable("HTTPS_PROXY")
|
||||
$allProxy = [System.Environment]::GetEnvironmentVariable("ALL_PROXY")
|
||||
$proxyUrl = if (-not [string]::IsNullOrWhiteSpace($httpsProxy)) { $httpsProxy } else { $allProxy }
|
||||
$webProxy = WebProxyFromUrl -ProxyUrl $proxyUrl
|
||||
return $webProxy
|
||||
}
|
||||
|
||||
# Downloads a URL to a local file using HttpClient with:
|
||||
# * a configurable per-request timeout (WebClient has none)
|
||||
# * proxy support from HTTPS_PROXY / ALL_PROXY
|
||||
# * bearer auth, attached ONLY when the target host is a trusted first-party
|
||||
# (github.com / astral.sh). This prevents leaking UV_GITHUB_TOKEN to
|
||||
# third-party mirrors configured in $ArtifactDownloadUrls.
|
||||
function Invoke-HttpDownload {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Url,
|
||||
[Parameter(Mandatory = $true)][string]$Destination,
|
||||
[int]$TimeoutSec = 30,
|
||||
[switch]$IncludeAuth
|
||||
)
|
||||
|
||||
$handler = New-Object System.Net.Http.HttpClientHandler
|
||||
$proxy = WebProxyFromEnvironment
|
||||
if ($null -ne $proxy) {
|
||||
$handler.Proxy = $proxy
|
||||
$handler.UseProxy = $true
|
||||
}
|
||||
|
||||
$client = New-Object System.Net.Http.HttpClient($handler)
|
||||
$client.Timeout = [TimeSpan]::FromSeconds($TimeoutSec)
|
||||
$client.DefaultRequestHeaders.Add("User-Agent", "PowerShell/uv-installer")
|
||||
|
||||
if ($IncludeAuth -and $auth_token -and ($Url -match 'github\.com|astral\.sh')) {
|
||||
$client.DefaultRequestHeaders.Add("Authorization", "Bearer $auth_token")
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $client.GetAsync($Url, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).Result
|
||||
if (-not $response.IsSuccessStatusCode) {
|
||||
throw "HTTP $([int]$response.StatusCode) downloading $Url"
|
||||
}
|
||||
$stream = $response.Content.ReadAsStreamAsync().Result
|
||||
$fileStream = [System.IO.File]::Create($Destination)
|
||||
try {
|
||||
$stream.CopyTo($fileStream)
|
||||
} finally {
|
||||
$fileStream.Dispose()
|
||||
}
|
||||
} finally {
|
||||
$client.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Get-LatestVersion {
|
||||
if ($env:UV_INSTALLER_VERSION) {
|
||||
return $env:UV_INSTALLER_VERSION
|
||||
@@ -41,10 +125,22 @@ function Get-LatestVersion {
|
||||
$fallback_version = "0.11.20"
|
||||
|
||||
try {
|
||||
$http = New-Object System.Net.Http.HttpClient
|
||||
$http.Timeout = [TimeSpan]::FromSeconds(5)
|
||||
$handler = New-Object System.Net.Http.HttpClientHandler
|
||||
$proxy = WebProxyFromEnvironment
|
||||
if ($null -ne $proxy) {
|
||||
$handler.Proxy = $proxy
|
||||
$handler.UseProxy = $true
|
||||
}
|
||||
|
||||
$http = New-Object System.Net.Http.HttpClient($handler)
|
||||
# Bumped from 5s -> 10s. The API call is small but in proxy/restricted
|
||||
# networks 5s was almost always too tight and forced the fallback version.
|
||||
$http.Timeout = [TimeSpan]::FromSeconds(10)
|
||||
$http.DefaultRequestHeaders.Add("User-Agent", "PowerShell/uv-installer")
|
||||
$http.DefaultRequestHeaders.Add("Accept", "application/vnd.github+json")
|
||||
if ($auth_token) {
|
||||
$http.DefaultRequestHeaders.Add("Authorization", "Bearer $auth_token")
|
||||
}
|
||||
$response = $http.GetStringAsync("https://api.github.com/repos/astral-sh/uv/releases/latest").Result
|
||||
$json = $response | ConvertFrom-Json
|
||||
$version = $json.tag_name
|
||||
@@ -60,6 +156,9 @@ function Get-LatestVersion {
|
||||
}
|
||||
|
||||
$app_name = 'uv'
|
||||
# NOTE: $auth_token must be defined *before* Get-LatestVersion / Invoke-HttpDownload
|
||||
# are invoked, since both reference it.
|
||||
$auth_token = $env:UV_GITHUB_TOKEN
|
||||
$app_version = Get-LatestVersion
|
||||
if ($env:UV_DOWNLOAD_URL) {
|
||||
$ArtifactDownloadUrls = @($env:UV_DOWNLOAD_URL)
|
||||
@@ -72,6 +171,10 @@ if ($env:UV_DOWNLOAD_URL) {
|
||||
$installer_base_url = $env:UV_INSTALLER_GITHUB_BASE_URL
|
||||
$ArtifactDownloadUrls = @("$installer_base_url/astral-sh/uv/releases/download/$app_version")
|
||||
} else {
|
||||
# Default download sources, ordered by priority.
|
||||
# Mirror entries exist to work around GitHub connectivity issues in mainland China.
|
||||
# If you are outside China or these mirrors become unavailable, you can safely
|
||||
# delete the two mirror lines and rely on the official URLs (first + last).
|
||||
$ArtifactDownloadUrls = @(
|
||||
"https://releases.astral.sh/github/uv/releases/download/$app_version",
|
||||
"https://mirror.ghproxy.com/https://github.com/astral-sh/uv/releases/download/$app_version",
|
||||
@@ -80,8 +183,6 @@ if ($env:UV_DOWNLOAD_URL) {
|
||||
)
|
||||
}
|
||||
|
||||
$auth_token = $env:UV_GITHUB_TOKEN
|
||||
|
||||
$receipt = @"
|
||||
{"binaries":["CARGO_DIST_BINS"],"binary_aliases":{},"cdylibs":["CARGO_DIST_DYLIBS"],"cstaticlibs":["CARGO_DIST_STATICLIBS"],"install_layout":"unspecified","install_prefix":"AXO_INSTALL_PREFIX","modify_path":true,"provider":{"source":"cargo-dist","version":"0.31.0"},"source":{"app_name":"uv","name":"uv","owner":"astral-sh","release_type":"github"},"version":"$app_version"}
|
||||
"@
|
||||
@@ -269,44 +370,6 @@ function Get-Arch() {
|
||||
}
|
||||
}
|
||||
|
||||
function WebProxyFromUrl {
|
||||
param([string]$ProxyUrl)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ProxyUrl)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
try {
|
||||
# Parse the proxy URL
|
||||
$uri = [System.Uri]$ProxyUrl
|
||||
|
||||
# Create WebProxy instance
|
||||
$webProxy = New-Object System.Net.WebProxy($uri)
|
||||
|
||||
# Set credentials if provided in URL
|
||||
if (-not [string]::IsNullOrEmpty($uri.UserInfo)) {
|
||||
$userInfo = $uri.UserInfo.Split(':')
|
||||
$username = [System.Uri]::UnescapeDataString($userInfo[0])
|
||||
$password = if ($null -eq $userInfo[1]) { "" } else { [System.Uri]::UnescapeDataString($userInfo[1]) }
|
||||
$webProxy.Credentials = New-Object System.Net.NetworkCredential($username, $password)
|
||||
}
|
||||
|
||||
return $webProxy
|
||||
}
|
||||
catch {
|
||||
Write-Verbose("Failed to parse proxy URL '$ProxyUrl': $($_.Exception.Message)")
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function WebProxyFromEnvironment {
|
||||
$httpsProxy = [System.Environment]::GetEnvironmentVariable("HTTPS_PROXY")
|
||||
$allProxy = [System.Environment]::GetEnvironmentVariable("ALL_PROXY")
|
||||
$proxyUrl = if (-not [string]::IsNullOrWhiteSpace($httpsProxy)) { $httpsProxy } else { $allProxy }
|
||||
$webProxy = WebProxyFromUrl -ProxyUrl $proxyUrl
|
||||
return $webProxy
|
||||
}
|
||||
|
||||
function Download($download_url, $platforms, $arch) {
|
||||
# Lookup what we expect this platform to look like
|
||||
$info = $platforms[$arch]
|
||||
@@ -324,15 +387,7 @@ function Download($download_url, $platforms, $arch) {
|
||||
$url = "$download_url/$artifact_name"
|
||||
Write-Verbose " from $url"
|
||||
Write-Verbose " to $dir_path"
|
||||
$wc = New-Object Net.Webclient
|
||||
$proxy = WebProxyFromEnvironment
|
||||
if ($null -ne $proxy) {
|
||||
$wc.Proxy = $proxy
|
||||
}
|
||||
if ($auth_token) {
|
||||
$wc.Headers["Authorization"] = "Bearer $auth_token"
|
||||
}
|
||||
$wc.downloadFile($url, $dir_path)
|
||||
Invoke-HttpDownload -Url $url -Destination $dir_path -TimeoutSec 30 -IncludeAuth
|
||||
|
||||
Write-Verbose "Unpacking to $tmp"
|
||||
|
||||
@@ -377,7 +432,7 @@ function Download($download_url, $platforms, $arch) {
|
||||
$updater_url = "$download_url/$updater_id"
|
||||
$out_name = "$tmp\uv-update.exe"
|
||||
|
||||
$wc.downloadFile($updater_url, $out_name)
|
||||
Invoke-HttpDownload -Url $updater_url -Destination $out_name -TimeoutSec 30 -IncludeAuth
|
||||
$bin_paths += $out_name
|
||||
}
|
||||
|
||||
@@ -549,7 +604,7 @@ function Invoke-Installer($artifacts, $platforms) {
|
||||
# .NET's APIs which actually do what you tell them (also apparently utf8NoBOM is the
|
||||
# default in newer .NETs but I'd rather not rely on that at this point).
|
||||
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
|
||||
[IO.File]::WriteAllLines("$receipt_home/uv-receipt.json", "$receipt", $Utf8NoBomEncoding)
|
||||
[IO.File]::WriteAllLines("$receipt_home\uv-receipt.json", "$receipt", $Utf8NoBomEncoding)
|
||||
}
|
||||
|
||||
# Respect the environment, but CLI takes precedence
|
||||
|
||||
Reference in New Issue
Block a user