Documentation
Migration Guide
Guide for migrating between different compiler implementations and SDK versions in the ad-blocking repository.
Overview
This guide helps you migrate between:
- Different rules compiler implementations (TypeScript, .NET, Python, Rust, PowerShell, Shell)
- API SDK implementations (TypeScript, .NET, Rust)
- Versions within the same implementation
- Configuration format changes
Rules Compiler Migration
Migrating Between Compiler Implementations
All rules compilers share the same configuration schema, making migration straightforward.
From Any Compiler to Another
Step 1: Verify Configuration Compatibility
Your existing configuration file works across all compilers:
# This configuration works with ALL compilers
name: My Filter List
description: Works everywhere
version: "1.0.0"
sources:
- name: EasyList
source: https://easylist.to/easylist/easylist.txt
type: adblock
transformations:
- Validate
- Deduplicate
- InsertFinalNewLine
Step 2: Install Target Compiler
# TypeScript (Deno)
curl -fsSL https://deno.land/install.sh | sh
cd src/rules-compiler-typescript
deno cache src/mod.ts
# .NET
cd src/rules-compiler-dotnet
dotnet restore RulesCompiler.slnx
# Python
cd src/rules-compiler-python
pip install -e .
# Rust
cd src/rules-compiler-rust
cargo build --release
# PowerShell
# Already installed if using PowerShell 7+
Step 3: Test with Same Configuration
# Test that output is identical
# TypeScript
deno task compile -- -c config.yaml -o output-ts.txt
# .NET
dotnet run --project src/RulesCompiler.Console -- -c config.yaml -o output-dotnet.txt
# Python
rules-compiler -c config.yaml -o output-python.txt
# Rust
cargo run -- -c config.yaml -o output-rust.txt
# Compare outputs (should be identical)
diff output-ts.txt output-dotnet.txt
Step 4: Update Build Scripts
# Before (TypeScript)
deno task compile
# After (.NET)
dotnet run --project src/RulesCompiler.Console
Migration Decision Matrix
| Current | Migrate To | Reason | Difficulty |
|---|---|---|---|
| Shell Scripts | TypeScript | Better error handling, testing | Easy |
| TypeScript | .NET | Library API, validation, DI | Easy |
| Any | Rust | Performance, single binary | Easy |
| .NET | Python | Lighter weight, scripting | Easy |
| Any | PowerShell | Windows automation | Easy |
Specific Migration Scenarios
From Node.js/npm to Deno (TypeScript)
Before (Node.js):
{
"scripts": {
"compile": "node compile.js"
},
"dependencies": {
"@jk-com/adblock-compiler": "^0.6.0"
}
}
After (Deno):
{
"tasks": {
"compile": "deno run --allow-all src/mod.ts"
},
"imports": {
"@jk-com/adblock-compiler": "jsr:@jk-com/adblock-compiler@^0.6.0"
}
}
Migration Steps:
- Remove
node_modulesandpackage-lock.json - Install Deno
- Update import statements to use npm: specifier
- Update scripts to use
deno runinstead ofnode - Add necessary permissions to deno.json
From Jest to Deno Test
Before (Jest):
// tests/compiler.test.js
const { compile } = require('../src/compiler');
describe('Compiler', () => {
test('should compile rules', () => {
const result = compile('config.yaml');
expect(result.success).toBe(true);
});
});
After (Deno Test):
// tests/compiler.test.ts
import { assertEquals } from 'https://deno.land/std@0.203.0/assert/mod.ts';
import { compile } from '../src/compiler.ts';
Deno.test('should compile rules', async () => {
const result = await compile('config.yaml');
assertEquals(result.success, true);
});
Migration Steps:
- Rename
.test.jsto.test.ts - Replace Jest imports with Deno std/assert
- Replace
describe/testwithDeno.test - Replace
expectwithassertEquals/assert* - Run:
deno test --allow-all tests/
From .NET Framework to .NET 10
Before (.NET Framework 4.8):
<TargetFramework>net48</TargetFramework>
<PackageReference Include="System.Configuration" />
After (.NET 10):
<TargetFramework>net10.0</TargetFramework>
<PackageReference Include="Microsoft.Extensions.Configuration" />
Migration Steps:
- Update target framework in .csproj
- Replace System.Configuration with Microsoft.Extensions.Configuration
- Update package references to latest versions
- Test compilation and runtime
- Update CI/CD pipelines
From Python 2 to Python 3.9+
Before (Python 2):
print "Compiling rules..."
from ConfigParser import ConfigParser
After (Python 3.9+):
print("Compiling rules...")
from configparser import ConfigParser
Migration Steps:
- Update print statements to functions
- Fix imports (ConfigParser → configparser)
- Update string handling (unicode, bytes)
- Use type hints
- Run:
python -m pytest
API SDK Migration
Migrating Between SDK Implementations
Common API Patterns
All SDKs follow similar patterns:
TypeScript:
const client = AdGuardDnsClient.withApiKey('key');
const devices = await client.devices.listDevices();
.NET:
var config = new Configuration { ApiKey = "key" };
var api = new DevicesApi(config);
var devices = await api.ListDevicesAsync();
Rust:
let mut config = Configuration::new();
config.bearer_access_token = Some("key".to_string());
let devices = devices_api::list_devices(&config).await?;
From .NET to TypeScript SDK
Before (.NET):
using AdGuard.ApiClient;
using AdGuard.ApiClient.Api;
var config = new Configuration
{
ApiKey = new Dictionary<string, string>
{
{ "Authorization", "ApiKey your-key" }
}
};
var devicesApi = new DevicesApi(config);
var devices = await devicesApi.ListDevicesAsync();
foreach (var device in devices)
{
Console.WriteLine($"{device.Name}: {device.Id}");
}
After (TypeScript):
import { AdGuardDnsClient } from './src/index.ts';
const client = AdGuardDnsClient.withApiKey('your-key');
const devices = await client.devices.listDevices();
for (const device of devices) {
console.log(`${device.name}: ${device.id}`);
}
Key Differences:
| Feature | .NET | TypeScript |
|---|---|---|
| Naming | PascalCase | camelCase |
| Async | async/await |
async/await |
| Collections | IEnumerable<T> |
Array<T> |
| Errors | ApiException |
ApiError |
| DI | Built-in | Manual |
From TypeScript to Rust SDK
Before (TypeScript):
const client = AdGuardDnsClient.withApiKey('key');
const limits = await client.account.getAccountLimits();
console.log(`Devices: ${limits.devices.used}/${limits.devices.limit}`);
After (Rust):
use adguard_api_lib::apis::configuration::Configuration;
use adguard_api_lib::apis::account_api;
let mut config = Configuration::new();
config.bearer_access_token = Some("key".to_string());
let limits = account_api::get_account_limits(&config).await?;
println!("Devices: {}/{}", limits.devices.used, limits.devices.limit);
Key Differences:
| Feature | TypeScript | Rust |
|---|---|---|
| Error Handling | try/catch | Result<T, E> |
| Async | Promise | Future |
| Null Values | undefined / null |
Option<T> |
| Type System | Structural | Nominal |
API Version Migration
From API v1.0 to v1.11
Breaking Changes:
- Authentication Header Format
# Before (v1.0)
Authorization: Bearer your-token
# After (v1.11)
Authorization: ApiKey your-api-key
- Timestamp Format
// Before (v1.0) - seconds
const timestamp = Math.floor(Date.now() / 1000);
// After (v1.11) - milliseconds
const timestamp = Date.now();
- Device Types
// Before (v1.0)
deviceType: 'mobile'
// After (v1.11)
deviceType: 'IOS' | 'ANDROID' | 'WINDOWS' | 'MAC' | etc.
Migration Steps:
- Update authentication method
- Convert timestamps to milliseconds
- Update device type values
- Test all API endpoints
- Update error handling for new status codes
Configuration Format Migration
From JSON to YAML
Before (JSON):
{
"name": "My Filter",
"sources": [
{
"name": "EasyList",
"source": "https://easylist.to/easylist/easylist.txt",
"type": "adblock"
}
],
"transformations": ["Deduplicate", "Validate"]
}
After (YAML):
name: My Filter
sources:
- name: EasyList
source: https://easylist.to/easylist/easylist.txt
type: adblock
transformations:
- Deduplicate
- Validate
Auto-conversion:
# JSON to YAML
python -c 'import json, yaml, sys; yaml.dump(json.load(sys.stdin), sys.stdout)' < config.json > config.yaml
# YAML to JSON
python -c 'import json, yaml, sys; json.dump(yaml.safe_load(sys.stdin), sys.stdout, indent=2)' < config.yaml > config.json
From YAML to TOML
Before (YAML):
name: My Filter
sources:
- name: EasyList
source: https://example.com/list.txt
transformations:
- Deduplicate
After (TOML):
name = "My Filter"
transformations = ["Deduplicate"]
[[sources]]
name = "EasyList"
source = "https://example.com/list.txt"
Key Differences:
- TOML uses
=instead of: - Arrays of tables use
[[table]]syntax - Strings must be quoted
- No complex nested structures
Environment Variable Migration
From Legacy to Current Format
API Keys
Before:
export ADGUARD_API_TOKEN="your-token"
export ADGUARD_API_URL="https://api.adguard-dns.io"
After:
export ADGUARD_AdGuard__ApiKey="your-api-key"
export ADGUARD_AdGuard__BaseUrl="https://api.adguard-dns.io"
Compiler Configuration
Before:
export CONFIG_FILE="config.json"
export OUTPUT_FILE="output.txt"
After:
export CONFIG_PATH="config.yaml"
export RULES_DIR="./rules"
Cross-Platform Environment Variables
Windows (CMD):
set ADGUARD_AdGuard__ApiKey=your-key
Windows (PowerShell):
$env:ADGUARD_AdGuard__ApiKey="your-key"
Linux/macOS:
export ADGUARD_AdGuard__ApiKey="your-key"
Docker Migration
From Single Stage to Multi-Stage Build
Before:
FROM mcr.microsoft.com/dotnet/sdk:10.0
WORKDIR /app
COPY . .
RUN dotnet publish -c Release
ENTRYPOINT ["dotnet", "bin/Release/net10.0/App.dll"]
After:
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/runtime:10.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "App.dll"]
Benefits:
- Smaller final image size
- Better layer caching
- Separation of build and runtime dependencies
From Docker Compose v2 to v3
Before (v2):
version: '2'
services:
compiler:
build: .
volumes:
- ./rules:/app/rules
mem_limit: 2g
After (v3):
version: '3.8'
services:
compiler:
build: .
volumes:
- ./rules:/app/rules
deploy:
resources:
limits:
memory: 2G
CI/CD Migration
From Travis CI to GitHub Actions
Before (Travis CI - .travis.yml):
language: node_js
node_js:
- 18
script:
- npm test
- npm run build
After (GitHub Actions - .github/workflows/test.yml):
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v1
- run: deno task test
- run: deno task build
From Jenkins to GitLab CI
Before (Jenkinsfile):
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'dotnet test'
}
}
}
}
After (GitLab CI - .gitlab-ci.yml):
test:
image: mcr.microsoft.com/dotnet/sdk:10.0
script:
- dotnet test
Testing Framework Migration
From mocha to Deno test
Before (mocha):
const assert = require('assert');
const { myFunction } = require('../src/index');
describe('myFunction', () => {
it('should return true', () => {
assert.strictEqual(myFunction(), true);
});
});
After (Deno test):
import { assertEquals } from 'https://deno.land/std@0.203.0/assert/mod.ts';
import { myFunction } from '../src/index.ts';
Deno.test('myFunction should return true', () => {
assertEquals(myFunction(), true);
});
From MSTest to xUnit
Before (MSTest):
[TestClass]
public class MyTests
{
[TestMethod]
public void TestMethod()
{
Assert.IsTrue(true);
}
}
After (xUnit):
public class MyTests
{
[Fact]
public void TestMethod()
{
Assert.True(true);
}
}
Rollback Strategies
Safe Migration Process
- Create Backup:
# Backup configuration
cp config.yaml config.yaml.backup
# Backup rules
cp -r data/ data.backup/
# Backup database/state if applicable
tar czf backup-$(date +%Y%m%d).tar.gz config/ data/
- Test in Parallel:
# Run both old and new implementations
old-compiler -c config.yaml -o output-old.txt
new-compiler -c config.yaml -o output-new.txt
# Compare outputs
diff output-old.txt output-new.txt
- Gradual Rollout:
# Use feature flags or environment variables
if [ "$USE_NEW_COMPILER" = "true" ]; then
new-compiler -c config.yaml
else
old-compiler -c config.yaml
fi
- Rollback Plan:
# Quick rollback script
#!/bin/bash
echo "Rolling back to previous version..."
git checkout HEAD~1
docker-compose down
docker-compose up -d
echo "Rollback complete"
Migration Checklist
Pre-Migration
- Backup all configuration files
- Backup existing data/output
- Document current setup
- Test current implementation
- Review new implementation docs
During Migration
- Install new dependencies
- Convert configuration if needed
- Update environment variables
- Test new implementation
- Compare outputs
- Update scripts/automation
- Update CI/CD pipelines
Post-Migration
- Verify all features work
- Monitor for issues
- Update documentation
- Train team if needed
- Remove old dependencies
- Update README/docs
Troubleshooting Migration Issues
Configuration Not Working
# Validate configuration with new compiler
new-compiler -c config.yaml --validate
# Check for format-specific issues
yamllint config.yaml # For YAML
jsonlint config.json # For JSON
Output Differences
# Check transformation order
# Transformations are applied in fixed order regardless of config
# Check for version differences in hostlist-compiler
deno run jsr:@jk-com/adblock-compiler --version
# Enable debug output
new-compiler -c config.yaml -d
Performance Regression
# Profile old vs new
time old-compiler -c config.yaml
time new-compiler -c config.yaml
# Check resource usage
/usr/bin/time -v new-compiler -c config.yaml
# Enable performance monitoring
new-compiler -c config.yaml --verbose
Getting Help
If you encounter issues during migration:
- Check the Troubleshooting Guide
- Review component-specific documentation
- Open an issue on GitHub with:
- Source and target implementations
- Configuration files (sanitized)
- Error messages
- Steps to reproduce
Related Documentation
- TypeScript Rules Compiler Guide
- TypeScript API SDK Guide
- Deployment Guide
- Configuration Reference
- Compiler Comparison
License
GPLv3 - See LICENSE for details.