Skip to main content

Deno

· 7 min read
Piero Proietti
penguins-eggs author
Deutsch English Español  Français Italiano Polska  Portuguese  Български  Русский  Українська  やまと  中国  فارسی 

Executive Summary

Goal: Migrate penguins-eggs from Node.js + OCLIF to Deno + Cliffy while maintaining full compatibility and improving performance.

Key Benefits:

  • 🚀 Startup Performance: 5-10x faster (1000ms → 100-200ms)
  • 💾 Memory Usage: 50-70% reduction (120MB → 40MB)
  • 📦 Binary Size: Smaller single binary (~50-80MB vs 200MB)
  • 🔒 Security: Granular permissions by default
  • 🛠️ Tooling: Built-in TypeScript, testing, formatting, linting
  • 📚 Dependencies: No more npm/node_modules hell

Current Architecture Analysis

Current Stack:

  • Language: TypeScript
  • Runtime: Node.js 18+
  • CLI Framework: OCLIF v4.2.7
  • UI: Ink v5.0.1 (React for terminal)
  • Package Manager: pnpm
  • Dependencies: 20+ packages including axios, inquirer, js-yaml, mustache

Core Dependencies to Migrate:

// Current (Node.js)
"@oclif/core": "^4.2.7"
"ink": "^5.0.1"
"inquirer": "^9.3.7"
"axios": "^1.7.9"
"js-yaml": "^4.1.0"
"mustache": "^4.2.0"
"react": "^18.3.1"

// Target (Deno)
"@cliffy/command" // CLI framework
"@cliffy/prompt" // Interactive prompts
"@std/yaml" // YAML parsing
"@std/http" // HTTP client
"mustache" // Template engine (compatible)

Migration Strategy: Gradual Transition

Phase 1: Foundation Setup (Week 1-2)

Goal: Establish Deno infrastructure alongside existing Node.js code

1.1 Project Structure Setup

penguins-eggs/
├── src/ # 📁 Existing Node.js code
├── deno-src/ # 📁 New Deno implementation
├── shared/ # 📁 Common utilities
├── scripts/ # 📁 Build and migration scripts
├── package.json # 📦 Node.js dependencies
├── deno.json # 🦕 Deno configuration
├── deps.ts # 📦 Centralized Deno dependencies
└── import_map.json # 🗺️ Import mappings

1.2 Deno Configuration

// deno.json
{
"tasks": {
"dev": "deno run --allow-all --watch deno-src/main.ts",
"build": "deno compile --allow-all --output dist/eggs deno-src/main.ts",
"test": "deno test --allow-all",
"fmt": "deno fmt",
"lint": "deno lint",
"check": "deno check deno-src/main.ts"
},
"compilerOptions": {
"strict": true,
"lib": ["deno.window"]
},
"imports": {
"@cliffy/": "https://deno.land/x/cliffy@v1.0.0-rc.3/",
"@std/": "https://deno.land/std@0.220.0/"
}
}

1.3 Dependencies Management

// deps.ts - Centralized dependency management
export { Command, EnumType } from "@cliffy/command";
export { Input, Select, Toggle, Confirm } from "@cliffy/prompt";
export { Table, Cell } from "@cliffy/table";
export { colors } from "@cliffy/ansi/colors";
export { parse as parseYaml, stringify as stringifyYaml } from "@std/yaml";
export { ensureDir, exists, copy } from "@std/fs";
export { join, dirname, basename } from "@std/path";
export { render } from "https://deno.land/x/mustache@v0.3.0/mod.ts";

Phase 2: Core Infrastructure (Week 3-4)

Goal: Migrate essential utilities and services

2.1 Configuration Service

// deno-src/services/ConfigService.ts
import { parseYaml, stringifyYaml, ensureDir, exists } from "../deps.ts";

export class ConfigService {
private configPath = "/etc/penguins-eggs.d/eggs.yaml";

async load(): Promise<EggsConfig> {
if (await exists(this.configPath)) {
const content = await Deno.readTextFile(this.configPath);
return parseYaml(content) as EggsConfig;
}
return this.getDefaults();
}

async save(config: EggsConfig): Promise<void> {
await ensureDir(dirname(this.configPath));
const content = stringifyYaml(config);
await Deno.writeTextFile(this.configPath, content);
}
}

2.2 Shell Service

// deno-src/services/ShellService.ts
export class ShellService {
async run(cmd: string[], options?: Deno.CommandOptions): Promise<Deno.CommandOutput> {
const command = new Deno.Command(cmd[0], {
args: cmd.slice(1),
...options
});
return await command.output();
}

async checkCommand(command: string): Promise<boolean> {
try {
const result = await this.run(["which", command]);
return result.success;
} catch {
return false;
}
}
}

2.3 Progress UI Components

// deno-src/ui/Progress.ts
import { colors } from "../deps.ts";

export class ProgressBar {
private current = 0;
private total = 100;
private width = 40;

update(current: number, message?: string) {
this.current = current;
const percent = Math.round((current / this.total) * 100);
const filled = Math.round((current / this.total) * this.width);
const empty = this.width - filled;

const bar = colors.green("█".repeat(filled)) + colors.gray("░".repeat(empty));
const line = `\r${bar} ${percent}% ${message || ""}`;

Deno.stdout.writeSync(new TextEncoder().encode(line));

if (current >= this.total) {
console.log(); // New line when complete
}
}
}

Phase 3: Command Migration (Week 5-8)

Goal: Migrate commands one by one, starting with simple ones

3.1 Basic Commands (Week 5)

Priority Order:

  1. eggs version - Simple, no dependencies
  2. eggs status - System info gathering
  3. eggs config - Configuration management
// deno-src/commands/version.ts
import { Command } from "../deps.ts";

export const versionCommand = new Command()
.name("version")
.description("Show eggs version")
.action(() => {
console.log("penguins-eggs 11.0.0-deno");
});

3.2 Interactive Commands (Week 6)

  1. eggs dad - Configuration TUI
  2. eggs mom - Help system
// deno-src/commands/dad.ts
import { Command, Input, Select, Confirm } from "../deps.ts";
import { ConfigService } from "../services/ConfigService.ts";

export const dadCommand = new Command()
.name("dad")
.description("Configuration helper")
.option("-d, --default", "Reset to defaults")
.action(async (options) => {
const configService = new ConfigService();

if (options.default) {
await configService.save(configService.getDefaults());
console.log("Configuration reset to defaults");
return;
}

// Interactive configuration
const config = await configService.load();

const compression = await Select.prompt({
message: "Select compression method:",
options: [
{ name: "Fast (zstd)", value: "zstd" },
{ name: "Standard (xz)", value: "xz" },
{ name: "Maximum (xz -Xbcj)", value: "max" }
],
default: config.compression
});

config.compression = compression;
await configService.save(config);
});

3.3 Utility Commands (Week 7)

  1. eggs tools clean - System cleanup
  2. eggs tools yolk - Package management
  3. eggs kill - ISO cleanup

3.4 Wardrobe System (Week 8)

  1. eggs wardrobe get
  2. eggs wardrobe wear
  3. eggs wardrobe list

Phase 4: Core Features (Week 9-12)

Goal: Migrate the most complex and critical functionality

4.1 ISO Production (Week 9-10)

The heart of penguins-eggs:

// deno-src/commands/produce.ts
import { Command, EnumType } from "../deps.ts";
import { ISOBuilder } from "../services/ISOBuilder.ts";
import { ProgressBar } from "../ui/Progress.ts";

const compressionType = new EnumType(["zstd", "xz", "max"]);

export const produceCommand = new Command()
.name("produce")
.description("Produce a live image from your system")
.type("compression", compressionType)
.option("-c, --clone", "Include user data")
.option("-C, --cryptedclone", "Include encrypted user data")
.option("--compression <type:compression>", "Compression method", { default: "zstd" })
.option("--basename <name>", "ISO basename")
.option("--theme <theme>", "Theme for live system")
.action(async (options) => {
const progress = new ProgressBar();
const builder = new ISOBuilder(options);

progress.update(0, "Initializing...");
await builder.initialize();

progress.update(20, "Copying system files...");
await builder.copySystem();

progress.update(60, "Creating squashfs...");
await builder.createSquashfs();

progress.update(80, "Building ISO...");
await builder.buildISO();

progress.update(100, "Complete!");
});

4.2 Installation System (Week 11)

  1. eggs install / eggs krill - System installer

4.3 Network Features (Week 12)

  1. eggs cuckoo - PXE server
  2. eggs syncto / eggs syncfrom - LUKS encryption

Phase 5: Advanced Features (Week 13-16)

Goal: Complete feature parity and optimization

5.1 Calamares Integration

  • Configuration generation
  • Theme management
  • Multi-architecture support

5.2 Package Detection & Distribution Support

// deno-src/services/DistroDetector.ts
export class DistroDetector {
async detect(): Promise<DistroInfo> {
const osRelease = await Deno.readTextFile("/etc/os-release");
// Parse and detect distribution
}

async getPackageManager(): Promise<PackageManager> {
// Detect apt, pacman, dnf, zypper, etc.
}
}

5.3 Build System Integration

  • Replace perrisbrewery with Deno compile
  • Create native packages for different distributions
  • CI/CD pipeline updates

Migration Checklist

Development Environment

  • Set up Deno development environment
  • Configure VS Code with Deno extension
  • Create development scripts and tasks
  • Set up testing framework

Core Infrastructure

  • Migrate configuration system
  • Implement shell service
  • Create progress UI components
  • Set up error handling and logging

Command Migration

  • Migrate simple commands (version, status, config)
  • Migrate interactive commands (dad, mom)
  • Migrate utility commands (tools, kill)
  • Migrate wardrobe system
  • Migrate core ISO production
  • Migrate installation system
  • Migrate network features

Testing & Quality Assurance

  • Unit tests for all services
  • Integration tests for commands
  • Cross-platform testing
  • Performance benchmarks
  • Memory usage analysis

Documentation & Distribution

  • Update documentation
  • Create migration guide
  • Update build scripts
  • Package for distributions
  • Update CI/CD pipelines

Performance Expectations

MetricNode.js (Current)Deno (Target)Improvement
Cold Start1000-1500ms100-200ms5-10x faster
Memory Usage80-120MB20-40MB50-70% less
Binary Size~200MB50-80MB60-75% smaller
Dependencies20+ packagesBuilt-in + fewSimplified

Risk Mitigation

Technical Risks

  1. Dependency Compatibility: Some Node.js packages may not have Deno equivalents

    • Mitigation: Identify alternatives early, contribute to Deno ecosystem if needed
  2. Platform Compatibility: Ensure all Linux distributions are supported

    • Mitigation: Extensive testing on target distributions
  3. Performance Regressions: Some operations might be slower in Deno

    • Mitigation: Benchmark critical operations, optimize bottlenecks

Business Risks

  1. User Adoption: Users may resist change

    • Mitigation: Gradual rollout, maintain Node.js version until Deno is stable
  2. Distribution Packaging: Need to update all distribution packages

    • Mitigation: Coordinate with distribution maintainers

Timeline Summary

PhaseDurationMilestone
Phase 1Week 1-2Foundation & Setup
Phase 2Week 3-4Core Infrastructure
Phase 3Week 5-8Command Migration
Phase 4Week 9-12Core Features
Phase 5Week 13-16Advanced Features

Total Duration: 16 weeks (~4 months)


Success Metrics

  1. Functional Parity: All current features working in Deno version
  2. Performance: 5x+ faster startup, 50%+ less memory usage
  3. User Experience: Seamless migration for end users
  4. Maintainability: Cleaner codebase, better developer experience
  5. Distribution: Available in all currently supported Linux distributions

Next Steps

  1. Week 1: Set up development environment and create PoC
  2. Week 2: Migrate first simple command (version/status)
  3. Week 3: Establish CI/CD pipeline for Deno
  4. Week 4: Begin core service migrations

Ready to start the journey from Node.js to Deno! 🚀