Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,82 @@ jobs:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
body_path: .github/release-notes/${{ github.ref_name }}.md

build-windows:
name: Build for Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'

- name: Install pnpm
uses: pnpm/action-setup@v6

- name: Get pnpm store directory
shell: pwsh
run: |
"STORE_PATH=$(pnpm store path --silent)" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Setup pnpm cache
uses: actions/cache@v5
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build frontend
working-directory: ./anycode
run: pnpm build

- name: Verify frontend build
working-directory: ./anycode-backend
shell: pwsh
run: |
if (-not (Test-Path "dist")) {
Write-Error "Error: dist directory is missing"
}
$files = Get-ChildItem -Path dist -Recurse -File
if ($files.Count -eq 0) {
Write-Error "Error: dist directory is empty"
}
Write-Host "Frontend build verified. Total files: $($files.Count)"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.96.0

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: |
anycode-backend -> target
- name: Build Windows binary
working-directory: ./anycode-backend
run: cargo build --release

- name: Package as zip
working-directory: ./anycode-backend
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist-release | Out-Null
Copy-Item target/release/anycode.exe dist-release/anycode.exe
Compress-Archive -Path dist-release/anycode.exe -DestinationPath anycode-windows-x86_64.zip -Force
- name: Upload to release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: anycode-backend/anycode-windows-x86_64.zip
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
body_path: .github/release-notes/${{ github.ref_name }}.md
80 changes: 80 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,83 @@ jobs:
working-directory: ./anycode-backend
run: |
./target/x86_64-unknown-linux-musl/release/anycode --help
test-build-windows:
name: Test Build Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'

- name: Install pnpm
uses: pnpm/action-setup@v6

- name: Get pnpm store directory
shell: pwsh
run: |
"STORE_PATH=$(pnpm store path --silent)" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Setup pnpm cache
uses: actions/cache@v5
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build frontend
working-directory: ./anycode
run: pnpm build

- name: Verify frontend build
working-directory: ./anycode-backend
shell: pwsh
run: |
if (-not (Test-Path "dist")) {
Write-Error "Error: dist directory is missing"
}
$files = Get-ChildItem -Path dist -Recurse -File
if ($files.Count -eq 0) {
Write-Error "Error: dist directory is empty"
}
Write-Host "Frontend build verified. Total files: $($files.Count)"
- name: Run core library tests
working-directory: ./anycode-base
run: pnpm test -- --run

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.96.0

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: |
anycode-backend -> target
- name: Run Rust backend tests
working-directory: ./anycode-backend
run: cargo test

- name: Build Windows binary
working-directory: ./anycode-backend
run: cargo build --release

- name: Check binary output
working-directory: ./anycode-backend
shell: pwsh
run: |
Get-Item target/release/anycode.exe | Format-List FullName,Length,LastWriteTime
- name: Test binary help
working-directory: ./anycode-backend
run: .\target\release\anycode.exe --help
7 changes: 3 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ anycode-react/dist/Component.js
awesome.md
promo.md
promo_ru.md




.cargo-home/
.cargo-home-sparse/
.pnpm-store/v11/index.db
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,65 @@ The project consists of several packages:
## Installation
### From releases

One-shot installer:
#### macOS and Linux one-shot installer:
```bash
curl -fsSL https://raw.githubusercontent.com/anycode-ade/anycode/main/install.sh | sh
```

#### Windows PowerShell installer (x64):
```powershell
irm https://raw.githubusercontent.com/anycode-ade/anycode/main/install.ps1 | iex
```

Windows installer details:
- Installs `anycode.exe` into `%USERPROFILE%\AppData\Local\anycode\bin` by default
- Adds that directory to the user `PATH` if needed
- Supports overrides via `ANYCODE_VERSION`, `ANYCODE_INSTALL_DIR`, and `ANYCODE_REPO`
- Current release installer targets Windows x64; Windows ARM64 can still build from source

If you want a specific release:
```powershell
$env:ANYCODE_VERSION="v0.0.24"
irm https://raw.githubusercontent.com/anycode-ade/anycode/main/install.ps1 | iex
```

## Development

1. **Start frontend:**
Prerequisites:
- Node.js 24+
- `pnpm`
- Rust 1.96.0+

1. **Install workspace dependencies:**
```bash
pnpm install
```

2. **Start frontend:**
```bash
cd anycode
pnpm build
pnpm dev
```

2. **Start rust backend:**
3. **Build frontend assets for the backend:**
```bash
cd anycode
pnpm build
```

4. **Start Rust backend:**
```bash
cd anycode-backend
cargo run --release
```

3. **Open your browser** and navigate to the frontend URL
5. **Open your browser** and navigate to the frontend URL

### Windows notes

- Run the commands in PowerShell, Windows Terminal, or another shell with `cargo`, `node`, and `pnpm` on `PATH`.
- The backend already uses `cmd.exe` as the integrated terminal shell on Windows.
- Release-based installation on Windows depends on the published `anycode-windows-x86_64.zip` asset.

## Contributing

Expand Down
8 changes: 8 additions & 0 deletions anycode-backend/src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ mod tests {
use tokio::time::{Duration, timeout};

#[tokio::test]
#[cfg_attr(
target_os = "windows",
ignore = "portable-pty shell echo is flaky on Windows CI runners"
)]
async fn test_terminal_echo() -> Result<()> {
let (tx, mut rx) = mpsc::channel::<String>(10);

Expand Down Expand Up @@ -244,6 +248,10 @@ mod tests {
}

#[tokio::test]
#[cfg_attr(
target_os = "windows",
ignore = "portable-pty unicode echo is flaky on Windows CI runners"
)]
async fn test_terminal_unicode() -> Result<()> {
let (tx, mut rx) = mpsc::channel::<String>(10);

Expand Down
118 changes: 118 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
param(
[string]$Version = $env:ANYCODE_VERSION,
[string]$InstallDir = $env:ANYCODE_INSTALL_DIR,
[string]$Repo = $(if ($env:ANYCODE_REPO) { $env:ANYCODE_REPO } else { "anycode-ade/anycode" }),
[string]$ArchivePath = $env:ANYCODE_ARCHIVE_PATH
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

if ([string]::IsNullOrWhiteSpace($Version)) {
$Version = "latest"
}

if ([string]::IsNullOrWhiteSpace($InstallDir)) {
$InstallDir = Join-Path $HOME "AppData\Local\anycode\bin"
}

function Get-WindowsArchitecture {
$runtimeType = [System.Type]::GetType("System.Runtime.InteropServices.RuntimeInformation")
if ($null -ne $runtimeType) {
$osArchProperty = $runtimeType.GetProperty("OSArchitecture")
if ($null -ne $osArchProperty) {
return $osArchProperty.GetValue($null).ToString()
}
}

$processArch = $env:PROCESSOR_ARCHITEW6432
if ([string]::IsNullOrWhiteSpace($processArch)) {
$processArch = $env:PROCESSOR_ARCHITECTURE
}

switch ($processArch.ToUpperInvariant()) {
"AMD64" { return "X64" }
"X86" { return "X86" }
"ARM64" { return "Arm64" }
default { return $processArch }
}
}

$arch = Get-WindowsArchitecture
switch ($arch) {
"X64" { $asset = "anycode-windows-x86_64.zip" }
"Arm64" { throw "Windows ARM64 releases are not published yet. Build from source instead." }
default {
throw "Unsupported Windows architecture: $arch"
}
}

if ([string]::IsNullOrWhiteSpace($ArchivePath)) {
if ($Version -eq "latest") {
$downloadUrl = "https://github.com/$Repo/releases/latest/download/$asset"
} else {
$downloadUrl = "https://github.com/$Repo/releases/download/$Version/$asset"
}
} else {
$ArchivePath = [System.IO.Path]::GetFullPath($ArchivePath)
if (-not (Test-Path $ArchivePath)) {
throw "Archive not found: $ArchivePath"
}
}

$tmpRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("anycode-install-" + [System.Guid]::NewGuid().ToString("N"))
$tempArchivePath = Join-Path $tmpRoot $asset
$extractDir = Join-Path $tmpRoot "extract"

New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
New-Item -ItemType Directory -Force -Path $tmpRoot | Out-Null
New-Item -ItemType Directory -Force -Path $extractDir | Out-Null

try {
if ([string]::IsNullOrWhiteSpace($ArchivePath)) {
Write-Host "Downloading $asset from $downloadUrl"
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempArchivePath
} else {
Write-Host "Using local archive $ArchivePath"
Copy-Item -Path $ArchivePath -Destination $tempArchivePath -Force
}

Expand-Archive -Path $tempArchivePath -DestinationPath $extractDir -Force

$sourceExe = Join-Path $extractDir "anycode.exe"
if (-not (Test-Path $sourceExe)) {
throw "Archive did not contain expected binary: anycode.exe"
}

$installExe = Join-Path $InstallDir "anycode.exe"
Copy-Item -Path $sourceExe -Destination $installExe -Force

Write-Host "Installed anycode to $installExe"

$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
$pathEntries = @()
if (-not [string]::IsNullOrWhiteSpace($userPath)) {
$pathEntries = $userPath.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)
}

$pathContainsInstallDir = $pathEntries | Where-Object { $_.TrimEnd('\') -ieq $InstallDir.TrimEnd('\') }
if (-not $pathContainsInstallDir) {
$newUserPath = if ([string]::IsNullOrWhiteSpace($userPath)) {
$InstallDir
} else {
"$userPath;$InstallDir"
}

[Environment]::SetEnvironmentVariable("Path", $newUserPath, "User")
$env:Path = "$env:Path;$InstallDir"

Write-Host "Added $InstallDir to the user PATH."
Write-Host "Open a new terminal to use 'anycode'."
} else {
Write-Host "$InstallDir is already in PATH."
}
} finally {
if (Test-Path $tmpRoot) {
Remove-Item -LiteralPath $tmpRoot -Recurse -Force
}
}