| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- #!/usr/bin/env bun
- /**
- * Transform Tauri/Desktop config files with Kilo branding
- *
- * This script handles Tauri configuration files (JSON, TOML, Rust) by:
- * 1. Taking upstream's version as the base
- * 2. Applying predictable Kilo branding transforms
- *
- * Handles:
- * - tauri.conf.json / tauri.prod.conf.json
- * - Cargo.toml / Cargo.lock
- * - Rust source files (*.rs)
- */
- import { $ } from "bun"
- import { info, success, warn, debug } from "../utils/logger"
- import { defaultConfig } from "../utils/config"
- import { oursHasKilocodeChanges } from "../utils/git"
- export interface TauriTransformResult {
- file: string
- action: "transformed" | "skipped" | "failed" | "flagged"
- replacements: number
- dryRun: boolean
- }
- export interface TauriTransformOptions {
- dryRun?: boolean
- verbose?: boolean
- }
- interface TauriReplacement {
- pattern: RegExp
- replacement: string
- description: string
- fileTypes?: string[] // Only apply to these file extensions
- }
- // Tauri-specific replacements
- const TAURI_REPLACEMENTS: TauriReplacement[] = [
- // JSON config - product names
- {
- pattern: /"productName":\s*"OpenCode[^"]*"/g,
- replacement: '"productName": "Kilo"',
- description: "Product name in JSON",
- fileTypes: [".json"],
- },
- {
- pattern: /"title":\s*"OpenCode[^"]*"/g,
- replacement: '"title": "Kilo"',
- description: "Title in JSON",
- fileTypes: [".json"],
- },
- // JSON config - identifiers
- {
- pattern: /ai\.opencode\.desktop\.dev/g,
- replacement: "ai.kilo.desktop.dev",
- description: "Dev identifier",
- },
- {
- pattern: /ai\.opencode\.desktop/g,
- replacement: "ai.kilo.desktop",
- description: "Prod identifier",
- },
- // Binary names
- {
- pattern: /opencode-cli/g,
- replacement: "kilo-cli",
- description: "CLI binary name",
- },
- {
- pattern: /"mainBinaryName":\s*"[Oo]pen[Cc]ode"/g,
- replacement: '"mainBinaryName": "Kilo"',
- description: "Main binary name",
- fileTypes: [".json"],
- },
- // GitHub references
- {
- pattern: /github\.com\/anomalyco\/opencode/g,
- replacement: "github.com/Kilo-Org/kilocode",
- description: "GitHub URL",
- },
- {
- pattern: /anomalyco\/opencode/g,
- replacement: "Kilo-Org/kilocode",
- description: "GitHub repo",
- },
- // Cargo.toml specific
- {
- pattern: /name\s*=\s*"opencode-desktop"/g,
- replacement: 'name = "kilo-desktop"',
- description: "Cargo package name",
- fileTypes: [".toml"],
- },
- {
- pattern: /authors\s*=\s*\["OpenCode"\]/g,
- replacement: 'authors = ["Kilo"]',
- description: "Cargo authors",
- fileTypes: [".toml"],
- },
- {
- pattern: /name\s*=\s*"opencode_lib"/g,
- replacement: 'name = "kilo_lib"',
- description: "Cargo lib name",
- fileTypes: [".toml"],
- },
- // Rust source specific
- {
- pattern: /opencode\.db/g,
- replacement: "kilo.db",
- description: "Database filename",
- fileTypes: [".rs"],
- },
- {
- pattern: /opencode\.settings\.dat/g,
- replacement: "kilo.settings.dat",
- description: "Settings file name",
- fileTypes: [".rs"],
- },
- {
- pattern: /"\.opencode\/bin"/g,
- replacement: '".kilo/bin"',
- description: "CLI install dir",
- fileTypes: [".rs"],
- },
- {
- pattern: /CLI_BINARY_NAME\s*=\s*"opencode"/g,
- replacement: 'CLI_BINARY_NAME = "kilo"',
- description: "CLI binary constant",
- fileTypes: [".rs"],
- },
- {
- pattern: /opencode_lib::run/g,
- replacement: "kilo_lib::run",
- description: "Lib run call",
- fileTypes: [".rs"],
- },
- {
- pattern: /killall opencode-cli/g,
- replacement: "killall kilo-cli",
- description: "Killall command",
- fileTypes: [".rs"],
- },
- // Domain
- {
- pattern: /opencode\.ai/g,
- replacement: "kilo.ai",
- description: "Domain",
- },
- // Environment variables (exclude OPENCODE_API_KEY)
- {
- pattern: /OPENCODE_(?!API_KEY)([A-Z_]+)/g,
- replacement: "KILO_$1",
- description: "Env variable",
- fileTypes: [".rs"],
- },
- {
- pattern: /__OPENCODE__/g,
- replacement: "__KILO__",
- description: "Window global",
- fileTypes: [".rs", ".tsx"],
- },
- {
- pattern: /OPENCODE_PORT/g,
- replacement: "KILO_PORT",
- description: "Port env var",
- },
- ]
- /**
- * Check if a file is a Tauri config file
- */
- export function isTauriFile(file: string): boolean {
- const patterns = defaultConfig.tauriFiles
- return patterns.some((pattern) => {
- const regex = new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$")
- return regex.test(file)
- })
- }
- /**
- * Get file extension
- */
- function getExtension(file: string): string {
- const match = file.match(/\.[^.]+$/)
- return match ? match[0] : ""
- }
- /**
- * Apply Tauri-specific transforms to content
- */
- export function applyTauriTransforms(
- content: string,
- file: string,
- verbose = false,
- ): { result: string; replacements: number } {
- const ext = getExtension(file)
- let result = content
- let total = 0
- for (const { pattern, replacement, description, fileTypes } of TAURI_REPLACEMENTS) {
- // Skip if this replacement is for specific file types and doesn't match
- if (fileTypes && !fileTypes.includes(ext)) {
- continue
- }
- pattern.lastIndex = 0
- if (pattern.test(result)) {
- pattern.lastIndex = 0
- const before = result
- result = result.replace(pattern, replacement)
- if (before !== result) {
- total++
- if (verbose) debug(` ${description}`)
- }
- }
- }
- return { result, replacements: total }
- }
- /**
- * Transform a single Tauri file
- */
- export async function transformTauriFile(
- file: string,
- options: TauriTransformOptions = {},
- ): Promise<TauriTransformResult> {
- if (options.dryRun) {
- info(`[DRY-RUN] Would transform Tauri file: ${file}`)
- return { file, action: "transformed", replacements: 0, dryRun: true }
- }
- // If our version has kilocode_change markers, flag for manual resolution
- if (await oursHasKilocodeChanges(file)) {
- warn(`${file} has kilocode_change markers — skipping auto-transform, needs manual resolution`)
- return { file, action: "flagged", replacements: 0, dryRun: false }
- }
- try {
- // Take upstream's version first
- await $`git checkout --theirs ${file}`.quiet().nothrow()
- await $`git add ${file}`.quiet().nothrow()
- // Read content
- const content = await Bun.file(file).text()
- // Apply transforms
- const { result, replacements } = applyTauriTransforms(content, file, options.verbose)
- // Write back if changed
- if (replacements > 0) {
- await Bun.write(file, result)
- await $`git add ${file}`.quiet().nothrow()
- }
- success(`Transformed Tauri file ${file}: ${replacements} replacements`)
- return { file, action: "transformed", replacements, dryRun: false }
- } catch (err) {
- warn(`Failed to transform Tauri file ${file}: ${err}`)
- return { file, action: "failed", replacements: 0, dryRun: false }
- }
- }
- /**
- * Transform conflicted Tauri files
- */
- export async function transformConflictedTauri(
- files: string[],
- options: TauriTransformOptions = {},
- ): Promise<TauriTransformResult[]> {
- const results: TauriTransformResult[] = []
- for (const file of files) {
- if (!isTauriFile(file)) {
- debug(`Skipping ${file} - not a Tauri file`)
- results.push({ file, action: "skipped", replacements: 0, dryRun: options.dryRun ?? false })
- continue
- }
- const result = await transformTauriFile(file, options)
- results.push(result)
- }
- return results
- }
- /**
- * Transform all Tauri files (pre-merge, on opencode branch)
- */
- export async function transformAllTauri(options: TauriTransformOptions = {}): Promise<TauriTransformResult[]> {
- const { Glob } = await import("bun")
- const results: TauriTransformResult[] = []
- const patterns = defaultConfig.tauriFiles
- for (const pattern of patterns) {
- const glob = new Glob(pattern)
- for await (const path of glob.scan({ absolute: false })) {
- const file = Bun.file(path)
- if (!(await file.exists())) continue
- try {
- const content = await file.text()
- const { result, replacements } = applyTauriTransforms(content, path, options.verbose)
- if (replacements > 0 && !options.dryRun) {
- await Bun.write(path, result)
- success(`Transformed Tauri ${path}: ${replacements} replacements`)
- } else if (options.dryRun && replacements > 0) {
- info(`[DRY-RUN] Would transform Tauri ${path}: ${replacements} replacements`)
- }
- results.push({ file: path, action: "transformed", replacements, dryRun: options.dryRun ?? false })
- } catch (err) {
- warn(`Failed to transform Tauri ${path}: ${err}`)
- results.push({ file: path, action: "failed", replacements: 0, dryRun: options.dryRun ?? false })
- }
- }
- }
- return results
- }
- // CLI entry point
- if (import.meta.main) {
- const args = process.argv.slice(2)
- const dryRun = args.includes("--dry-run")
- const verbose = args.includes("--verbose")
- const files = args.filter((a) => !a.startsWith("--"))
- if (files.length === 0) {
- info("Usage: transform-tauri.ts [--dry-run] [--verbose] <file1> <file2> ...")
- process.exit(1)
- }
- if (dryRun) {
- info("Running in dry-run mode")
- }
- const results = await transformConflictedTauri(files, { dryRun, verbose })
- const transformed = results.filter((r) => r.action === "transformed")
- const total = results.reduce((sum, r) => sum + r.replacements, 0)
- console.log()
- success(`Transformed ${transformed.length} Tauri files with ${total} replacements`)
- if (dryRun) {
- info("Run without --dry-run to apply changes")
- }
- }
|