/** * Plugin Types * * Types for the Factiii plugin system. */ import type { FactiiiConfig } from './config.js'; import type { DeployOptions } from './cli.js'; /** * Deployment stages */ export type Stage = 'dev' | 'staging' | 'prod'; /** * Server OS types * Used to identify which operating system a server runs */ export type ServerOS = 'mac' | 'ubuntu' | 'windows' | 'amazon-linux' | 'alpine'; /** * Package managers by OS */ export type PackageManager = 'brew' | 'apt' | 'choco' | 'winget' | 'dnf' | 'apk'; /** * Service managers by OS */ export type ServiceManager = 'launchctl' | 'systemd' | 'windows-service'; /** * Fix severity levels */ export type Severity = 'critical' | 'warning' | 'info'; /** * How a stage can be reached */ export type ReachVia = 'local'; /** * Reachability check result - discriminated union for type safety */ export type Reachability = { reachable: true; via: ReachVia; } | { reachable: false; reason: string; }; /** * Prod safety levels for plugin commands */ export type ProdSafetyLevel = 'safe' | 'caution' | 'destructive'; /** * Command categories for grouping in help */ export type CommandCategory = 'db' | 'ops' | 'backup' | 'aws'; /** * Command option definition */ export interface CommandOption { flags: string; description: string; defaultValue?: string | boolean | string[]; } /** * Result of a plugin command execution */ export interface CommandResult { success: boolean; message?: string; error?: string; } /** * Plugin command definition */ export interface PluginCommand { /** Command name (e.g., 'seed', 'migrate') */ name: string; /** Human-readable description */ description: string; /** Category for grouping */ category: CommandCategory; /** Which stages this command supports (default: ['dev', 'staging', 'prod']) */ stages?: Stage[]; /** Safety level on prod - 'destructive' requires --force */ prodSafety: ProdSafetyLevel; /** If true, always execute locally on the dev machine (skip SSH/workflow routing) */ localOnly?: boolean; /** Command-specific options */ options?: CommandOption[]; /** Execute the command (always runs on the dev machine). */ execute: (stage: Stage, options: Record, config: FactiiiConfig, rootDir: string) => Promise; /** * Optional: remote command string to exec on the stage's server via the * shared SSH tunnel. When set and the stage is staging/prod, * `executePluginCommand` opens `ssh-tunnel-` once, runs this * string through `tunnelExec`, and streams the output back to dev. * Typical shape: * (stage, opts, cfg) => `sudo docker compose -f ~/.factiii/${cfg.name}/docker-compose.yml logs --tail 30 ${cfg.name}-${stage}` * Commands without a `remoteCmd` and a staging/prod target fail fast with * a clear "not migrated to dev-direct yet" error instead of silently * reaching for a server-side stack CLI that no longer exists. */ remoteCmd?: (stage: Stage, options: Record, config: FactiiiConfig) => string; } /** * A fix definition that can detect and resolve issues */ export interface Fix { id: string; stage: Stage; severity: Severity; description: string; plugin?: string; /** Optional: Only run this fix on specific OS types */ os?: ServerOS | ServerOS[]; /** * Optional: Only run this fix for specific deployment targets * Used to differentiate staging vs prod secrets within dev-stage fix runs * Example: A fix with targetStage: 'staging' only runs when deploying to staging */ targetStage?: 'staging' | 'prod'; /** * Optional: If true and this fix fails, skip all remaining fixes in this * and later stages. Use for prerequisites like credential sync where * nothing else can run without it. */ blocking?: boolean; /** * Optional: ids of fixes that must succeed before this one runs. * * The DAG runner (utils/dag-runner.ts) orders fixes by these edges, runs * siblings with no shared prereqs in parallel, and marks a fix as `skipped` * (not `failed`) if any of its requires are skipped/failed — so errors * collect cleanly instead of cascading into false "broken" reports. * * Typical chains: * vault-password-file → vault-unlocked → staging-ssh-key-to-disk * staging-ssh-key-to-disk → [every staging fix that needs server state] */ requires?: string[]; /** * Optional: named resource this fix holds. Fixes sharing any id in their * serializeOn lists run serially with respect to each other even when they * have no direct `requires` edge. Use for shared mutable resources: the * single SSH tunnel, interactive prompts, a lock file, etc. * * Example: every staging fix sets `serializeOn: ['ssh-staging']` so they * share one tunnel until a multiplexing channel layer lands later. */ serializeOn?: string[]; scan: (config: FactiiiConfig, rootDir: string) => Promise; fix?: ((config: FactiiiConfig, rootDir: string) => Promise) | null; manualFix: string; } /** * Plugin categories */ export type PluginCategory = 'pipeline' | 'server' | 'framework' | 'addon'; /** * Plugin metadata for listing */ export interface PluginMetadata { id: string; category: string; name: string; version: string; } /** * Result of a deploy/undeploy operation */ export interface DeployResult { success: boolean; message?: string; error?: string; } /** * Options for ensuring server is ready */ export interface EnsureServerReadyOptions { commitHash?: string; branch?: string; repoUrl?: string; } /** * SSH command result */ export interface SSHResult { success: boolean; output?: string; error?: string; } /** * Server software check results */ export interface ServerSoftwareChecks { git: boolean; docker: boolean; dockerCompose: boolean; node: boolean; } /** * Server environment detection result */ export interface ServerEnvironment { os: ServerOS | 'unknown'; packageManager: PackageManager | null; hasHomebrew: boolean; hasApt: boolean; hasYum: boolean; hasDnf: boolean; hasChoco: boolean; hasApk: boolean; } /** * Server dependency installation results */ export interface DependencyInstallResult { needed: boolean; installed: boolean; error: string | null; } /** * Server dependencies installation results */ export interface InstallDependenciesResult { success: boolean; error?: string; serverEnv?: ServerEnvironment; results: { node: DependencyInstallResult; git: DependencyInstallResult; docker: DependencyInstallResult; pnpm: DependencyInstallResult; }; } /** * Server connectivity check result */ export interface ConnectivityResult { ssh: boolean; error?: string; } /** * Repository check result */ export interface RepoCheckResult { exists: boolean; branch?: string; } /** * Config validation result on server */ export interface ConfigValidationResult { expectedServices: number; actualServices: number; nginxMatches: boolean | null; dockerComposeUpToDate: boolean | null; } /** * Comprehensive server scan result */ export interface ServerScanResult { environment: string; ssh: boolean; git: boolean; docker: boolean; dockerCompose: boolean; node: boolean; repo: boolean; branch: string | null; repoName: string; configValidation: ConfigValidationResult | null; error?: string; } /** * Server basics setup result */ export interface ServerBasicsResult { gitInstalled: boolean; dockerInstalled: boolean; repoCloned: boolean; repoExists: boolean; configMismatch: boolean; } /** * Base interface for all plugin classes (static side) */ export interface PluginStatic { readonly id: string; readonly name: string; readonly category: PluginCategory; readonly version: string; readonly fixes: Fix[]; readonly requiredEnvVars: string[]; readonly configSchema: Record; readonly autoConfigSchema: Record; shouldLoad(rootDir: string, config: FactiiiConfig): Promise; detectConfig?(rootDir: string): Promise>; } /** * Base interface for plugin instances */ export interface PluginInstance { config: FactiiiConfig; deploy(config: FactiiiConfig, environment: string): Promise; undeploy(config: FactiiiConfig, environment: string): Promise; } /** * Pipeline plugin static interface */ export interface PipelinePluginStatic extends PluginStatic { readonly category: 'pipeline'; readonly commands?: PluginCommand[]; canReach(stage: Stage, config: FactiiiConfig): Reachability; requiresFullRepo?(environment: string): boolean; generateWorkflows?(rootDir: string): Promise; triggerWorkflow?(workflowName: string, inputs?: Record): Promise; } /** * Pipeline plugin instance interface */ export interface PipelinePluginInstance extends PluginInstance { /** * Deploy to a stage - handles routing based on canReach() * * This is the main entry point for deployments. The pipeline plugin * checks canReach() to determine how to reach the stage: * - 'local': Execute deployment directly * - 'workflow': Trigger a workflow (e.g., GitHub Actions) * - Not reachable: Return error with reason */ deployStage(stage: Stage, options: DeployOptions): Promise; /** * Scan a stage - handles routing based on canReach() * * Returns { handled: true } if pipeline ran scan remotely (via SSH or workflow). * Returns { handled: false } if caller should run scan locally. */ scanStage(stage: Stage, options: Record): Promise<{ handled: boolean; }>; /** * Fix a stage - handles routing based on canReach() * * Returns { handled: true } if pipeline ran fix remotely (via SSH or workflow). * Returns { handled: false } if caller should run fix locally. */ fixStage(stage: Stage, options: Record): Promise<{ handled: boolean; }>; } /** * Server plugin static interface * * Server plugins represent OS types (mac, ubuntu, windows, etc.) * and handle OS-specific package management and commands. */ export interface ServerPluginStatic extends PluginStatic { readonly category: 'server'; /** The OS this server plugin handles */ readonly os: ServerOS; /** Package manager for this OS */ readonly packageManager: PackageManager; /** Service manager for this OS */ readonly serviceManager: ServiceManager; sshExec?(envConfig: { host: string; ssh_user?: string; }, command: string): Promise; } /** * Server plugin instance interface */ export interface ServerPluginInstance extends PluginInstance { ensureServerReady(config: FactiiiConfig, environment: string, options?: EnsureServerReadyOptions): Promise; } /** * Framework plugin static interface */ export interface FrameworkPluginStatic extends PluginStatic { readonly category: 'framework'; } /** * Addon plugin static interface */ export interface AddonPluginStatic extends PluginStatic { readonly category: 'addon'; } /** * Union type for any plugin class */ export type AnyPluginStatic = PipelinePluginStatic | ServerPluginStatic | FrameworkPluginStatic | AddonPluginStatic; /** * External plugin export interface * * External packages (like @factiii/auth) can export a `stackPlugin` object * conforming to this interface. Stack will dynamically load and use these * scanfixes instead of its own inline fallbacks. * * Example in @factiii/auth: * export const stackPlugin: ExternalPluginExport = { fixes: [...] } */ export interface ExternalPluginExport { fixes: Fix[]; } /** * Constructor type for plugin classes */ export interface PluginConstructor { new (config: FactiiiConfig): T; readonly id: string; readonly name: string; readonly category: PluginCategory; readonly version: string; readonly fixes: Fix[]; readonly requiredEnvVars: string[]; shouldLoad(rootDir: string, config: FactiiiConfig): Promise; } //# sourceMappingURL=plugin.d.ts.map