Files
Codeman/src/types/update.ts
T
arkonandClaude Fable 5 c1458d8ab8 feat(self-update): launchd-daemon supervisor — rootless restart on headless Macs
A KeepAlive system-level LaunchDaemon (the right setup for headless Macs,
where no GUI login means LaunchAgents never start) is now detected as
supervisor 'launchd-daemon': the updater kills the server PID (passed via
--server-pid) and launchd respawns it on the new dist/ — no root needed.
Detection requires the daemon plist to be bootstrapped AND KeepAlive=true.

Also: on boot, a 'completed-needs-manual-restart' status auto-completes
when the running version matches the staged target, so the stale
'restart Codeman to apply' instruction no longer lingers in the UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:08:36 +02:00

101 lines
3.5 KiB
TypeScript

/**
* @fileoverview Types for the in-app self-updater.
*
* Codeman can update itself from the web UI (App Settings → Updates). The flow
* is driven by a detached `scripts/self-update.sh` that outlives the service
* restart it triggers, and a status file at `~/.codeman/update-status.json`
* (see `dataPath('update-status.json')`) that the browser polls across the
* restart boundary.
*
* Backend logic: `src/web/self-update.ts`. Routes: `src/web/routes/system-routes.ts`
* (`/api/system/update/check`, `POST /api/system/update`, `/api/system/update/status`).
*
* @module types/update
*/
/**
* Which init system supervises the running server (decides how we restart it).
* `launchd-daemon` = a KeepAlive system-level LaunchDaemon (headless Macs, no GUI
* login): restart works by killing the server and letting launchd respawn it.
*/
export type SupervisorKind = 'systemd' | 'launchd' | 'launchd-daemon' | 'none';
/** How Codeman was installed — only `git` installs can self-update in place. */
export type InstallKind = 'git' | 'npm' | 'unknown';
/**
* Lifecycle of a single update run. `idle`/`completed`/`failed`/
* `completed-needs-manual-restart` are terminal; the rest are in-flight.
*/
export type UpdatePhase =
| 'idle'
| 'queued'
| 'preparing'
| 'stashing'
| 'fetching'
| 'checkout'
| 'installing'
| 'building'
| 'restarting'
| 'completed'
| 'completed-needs-manual-restart'
| 'failed';
/** Persisted update progress, written atomically by the updater + boot reconcile. */
export interface UpdateStatus {
/** Nonce identifying this run; guards boot-reconcile against stale/foreign status. */
updateId: string;
phase: UpdatePhase;
/** Human-readable one-liner for the UI. */
message: string;
/** Version the server was on when the update started. */
fromVersion: string;
/** Target version (parsed from the release tag). */
toVersion?: string;
/** Target git tag, e.g. `codeman@0.9.4`. */
toTag?: string;
/** Commit the repo was on before the update, for rollback. */
prevSha?: string;
/** Name of the stash holding local changes (when the tree was dirty), else null. */
stashRef?: string | null;
supervisor?: SupervisorKind;
/** epoch ms — update start (freshness guard for boot reconcile). */
startedAt: number;
/** epoch ms — last write. */
updatedAt: number;
/** Populated on failure. */
error?: string;
/** Shown for the `none` supervisor — the command the user must run by hand. */
manualRestartCommand?: string;
}
/** Describes the running install — drives whether/how the Updates UI is shown. */
export interface InstallInfo {
installKind: InstallKind;
installDir: string;
/** Current git branch, or `HEAD` when detached (e.g. pinned to a release tag). */
branch?: string;
/** Uncommitted local changes present (true → updater will auto-stash). */
dirty: boolean;
supervisor: SupervisorKind;
currentVersion: string;
/** False when `CODEMAN_DISABLE_SELF_UPDATE=1`. */
selfUpdateEnabled: boolean;
}
/** Result of "check for updates" — current vs. latest release. */
export interface UpdateCheckResult {
currentVersion: string;
latestVersion: string | null;
latestTag: string | null;
updateAvailable: boolean;
/** Release notes (markdown) when available from the GitHub API. */
notes?: string | null;
/** Link to the release page. */
htmlUrl?: string | null;
/** epoch ms of the check. */
checkedAt: number;
source: 'github-api' | 'git-ls-remote' | 'none';
error?: string;
}