"use strict";
// src/orchestrator/deploy/FlyAdapter.ts
// v1.18.1 — Wraps `fly` (flyctl) CLI.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlyAdapter = void 0;
const child_process_1 = require("child_process");
const util_1 = require("util");
const child_process_2 = require("child_process");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const execFileP = (0, util_1.promisify)(child_process_2.execFile);
class FlyAdapter {
    name = 'fly';
    async detect() {
        try {
            await execFileP('fly', ['version'], { timeout: 5_000 });
            return true;
        }
        catch {
            // Try `flyctl` as alternate binary name.
            try {
                await execFileP('flyctl', ['version'], { timeout: 5_000 });
                return true;
            }
            catch {
                return false;
            }
        }
    }
    async preflight(opts) {
        const issues = [];
        const root = opts.cwd || process.cwd();
        const flyToml = path.join(root, 'fly.toml');
        if (!fs.existsSync(flyToml)) {
            issues.push({
                severity: 'warning',
                message: 'no_fly_toml',
                hint: 'Run `fly launch` to scaffold fly.toml + connect a Fly app',
            });
        }
        else {
            try {
                const src = fs.readFileSync(flyToml, 'utf-8');
                if (!/^\s*app\s*=/m.test(src)) {
                    issues.push({
                        severity: 'error',
                        message: 'fly_toml_missing_app',
                        hint: 'Add `app = "your-app-name"` at the top of fly.toml',
                    });
                }
            }
            catch (err) {
                issues.push({
                    severity: 'error',
                    message: `fly_toml_invalid: ${err instanceof Error ? err.message : String(err)}`,
                });
            }
        }
        if (process.env.CI && !process.env.FLY_API_TOKEN) {
            issues.push({
                severity: 'warning',
                message: 'ci_without_token',
                hint: 'Set FLY_API_TOKEN env in CI for non-interactive deploy',
            });
        }
        const fatal = issues.some(i => i.severity === 'error');
        return { ok: !fatal, issues };
    }
    async deploy(opts) {
        const start = Date.now();
        const args = ['deploy', '--yes'];
        if (opts.target)
            args.push('--app', opts.target);
        // Fly doesn't have a first-class preview; closest is --strategy=canary.
        if (opts.preview)
            args.push('--strategy', 'canary');
        return new Promise((resolve) => {
            let stdout = '';
            let stderr = '';
            let timedOut = false;
            const timeout = opts.timeoutMs ?? 600_000;
            // Resolve binary: prefer 'fly', fall back to 'flyctl'.
            const bin = process.env.OPENLIFE_FLY_BIN || 'fly';
            const child = (0, child_process_1.spawn)(bin, args, {
                cwd: opts.cwd || process.cwd(),
                env: process.env,
                stdio: ['ignore', 'pipe', 'pipe'],
            });
            const t = setTimeout(() => {
                timedOut = true;
                try {
                    child.kill('SIGTERM');
                }
                catch { /* */ }
            }, timeout);
            if (typeof t.unref === 'function')
                t.unref();
            child.stdout?.on('data', (c) => { stdout += String(c); });
            child.stderr?.on('data', (c) => { stderr += String(c); });
            child.on('exit', (code) => {
                clearTimeout(t);
                const exitCode = typeof code === 'number' ? code : -1;
                const urlMatch = (stdout + stderr).match(/https?:\/\/[a-zA-Z0-9.-]+\.fly\.dev[^\s]*/);
                resolve({
                    ok: exitCode === 0 && !timedOut,
                    provider: this.name,
                    url: urlMatch ? urlMatch[0] : undefined,
                    durationMs: Date.now() - start,
                    exitCode,
                    error: timedOut ? `timeout_after_${timeout}ms` : (exitCode !== 0 ? `exit_${exitCode}: ${stderr.slice(0, 300)}` : undefined),
                });
            });
            child.on('error', (err) => {
                clearTimeout(t);
                resolve({
                    ok: false,
                    provider: this.name,
                    durationMs: Date.now() - start,
                    error: `spawn_error: ${err.message}`,
                });
            });
        });
    }
    /**
     * v1.19.0 — Fly has no alias swap. Promote = redeploy with target app.
     * The convention is one Fly app per environment (e.g. `myapp-staging`,
     * `myapp-prod`); `to` resolves to the target app name.
     */
    async promote(opts) {
        const start = Date.now();
        const bin = process.env.OPENLIFE_FLY_BIN || 'fly';
        // Convention: `to` is the literal app name. Caller decides naming.
        const args = ['deploy', '--yes', '--app', opts.to];
        return new Promise((resolve) => {
            let stdout = '';
            let stderr = '';
            let timedOut = false;
            const timeout = opts.timeoutMs ?? 600_000;
            const child = (0, child_process_1.spawn)(bin, args, {
                cwd: opts.cwd || process.cwd(),
                env: process.env,
                stdio: ['ignore', 'pipe', 'pipe'],
            });
            const t = setTimeout(() => {
                timedOut = true;
                try {
                    child.kill('SIGTERM');
                }
                catch { /* */ }
            }, timeout);
            if (typeof t.unref === 'function')
                t.unref();
            child.stdout?.on('data', (c) => { stdout += String(c); });
            child.stderr?.on('data', (c) => { stderr += String(c); });
            child.on('exit', (code) => {
                clearTimeout(t);
                const exitCode = typeof code === 'number' ? code : -1;
                const urlMatch = (stdout + stderr).match(/https?:\/\/[a-zA-Z0-9.-]+\.fly\.dev[^\s]*/);
                resolve({
                    ok: exitCode === 0 && !timedOut,
                    provider: this.name,
                    url: urlMatch ? urlMatch[0] : undefined,
                    durationMs: Date.now() - start,
                    exitCode,
                    error: timedOut
                        ? `timeout_after_${timeout}ms`
                        : (exitCode !== 0 ? `promote_failed: exit_${exitCode}: ${stderr.slice(0, 300)}` : undefined),
                });
            });
            child.on('error', (err) => {
                clearTimeout(t);
                resolve({
                    ok: false,
                    provider: this.name,
                    durationMs: Date.now() - start,
                    error: `spawn_error: ${err.message}`,
                });
            });
        });
    }
    async rollback(opts) {
        const start = Date.now();
        if (!opts.to) {
            return {
                ok: false,
                provider: this.name,
                durationMs: 0,
                error: 'rollback_requires_id: pass --to <release-version>. List via `fly releases`.',
            };
        }
        const bin = process.env.OPENLIFE_FLY_BIN || 'fly';
        try {
            const { stdout } = await execFileP(bin, ['releases', 'rollback', opts.to], {
                cwd: opts.cwd || process.cwd(),
                timeout: 180_000,
            });
            return {
                ok: true,
                provider: this.name,
                deploymentId: opts.to,
                url: String(stdout).split('\n').find(l => /https?:\/\/.+\.fly\.dev/.test(l)),
                durationMs: Date.now() - start,
                exitCode: 0,
            };
        }
        catch (err) {
            const e = err;
            return {
                ok: false,
                provider: this.name,
                durationMs: Date.now() - start,
                exitCode: typeof e.code === 'number' ? e.code : -1,
                error: `rollback_failed: ${(e.stderr || e.message || '').slice(0, 300)}`,
            };
        }
    }
}
exports.FlyAdapter = FlyAdapter;
