Calendar and contacts became core in9c309c5a. A FRESH install gets radicale from machine-setup and officer-caldav from CORE_PROCESSES; an existing one gets neither from a git pull, because a pull changes nothing outside the repository. So without this, pulling that commit leaves /calendar mounted, rendering, and talking to nothing — the silent half-working failure this codebase keeps getting bitten by, and the same shape as the plugin permission collision fixed inee321f49. Best-effort and AFTER serve(), never blocking: a held apt lock, an unreachable mirror or a machine without passwordless sudo must not stop Officer starting. Uses the existing os-deps machinery rather than a second spelling of "install a package" — including `sudo -n`, which fails fast instead of hanging on a prompt no one can answer under PM2. When it cannot install, it logs the exact command to run by hand. addCoreProcess is new and deliberately narrow. The platform normally never writes a core entry — it does not know the core list, and duplicating CORE_PROCESSES here would be a second copy to drift. The exception is exactly this: a commit that MOVES something into the core set, which an existing install has no other way to learn about. Verified on this machine rather than assumed: radicale was absent, the guard installed it (3.1.8), and a second run reported `present` and wrote nothing. The install path is not unit-tested on purpose — a test that really ran apt-get would modify the machine running it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
25 lines
1.4 KiB
TypeScript
25 lines
1.4 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { radicaleInstalled } from './caldav-bootstrap';
|
|
|
|
// What this guard exists for: calendar and contacts moved from an app-store entry to CORE on
|
|
// 2026-08-19. A fresh install gets `radicale` from machine-setup and `officer-caldav` from
|
|
// CORE_PROCESSES. An existing one gets NEITHER from `git pull`, because a pull changes nothing outside
|
|
// the repository — so /calendar would mount, render, and talk to nothing.
|
|
//
|
|
// The install side is deliberately not tested here: it shells out to a package manager, and a test that
|
|
// really ran `apt-get install` would be a test that modifies the machine running it. What IS worth
|
|
// pinning is that the detector asks the same question the sidecar will ask.
|
|
|
|
describe('radicale detection', () => {
|
|
test('answers the same question the sidecar asks — is `radicale` on PATH', () => {
|
|
// `radicale.ts` spawns the bare name `radicale`, so PATH lookup is the whole contract. Asserting the
|
|
// boolean matches Bun.which rather than asserting a fixed value, because this machine may or may not
|
|
// have it and a test that only passes on one of those is worse than no test.
|
|
expect(radicaleInstalled()).toBe(Bun.which('radicale') !== null);
|
|
});
|
|
|
|
test('is a boolean, never a throw — it runs on the boot path', () => {
|
|
expect(typeof radicaleInstalled()).toBe('boolean');
|
|
});
|
|
});
|