@capgo/cli
Version:
A CLI to upload to capgo servers
87 lines (86 loc) • 4.06 kB
TypeScript
/** Stable shape written to disk; bump schemaVersion if breaking changes are made. */
export interface BuildOutputRecord {
schemaVersion: 1;
jobId: string;
appId: string;
platform: 'ios' | 'android';
buildMode: 'debug' | 'release';
status: string;
outputUrl: string | null;
qrCodeAscii: string | null;
qrCodePngPath: string | null;
finishedAt: string;
}
export interface WriteBuildOutputRecordInput {
jobId: string;
appId: string;
platform: 'ios' | 'android';
buildMode: 'debug' | 'release';
status: string;
outputUrl: string | null;
}
/**
* Write a build-output record to `recordPath` (JSON) and, when a URL is available,
* a PNG QR code to `<recordPath>.qr.png`. Returns the parsed record exactly as
* written so callers can log a summary without re-reading the file.
*
* Failures rendering the PNG are non-fatal — the JSON is always written. The
* record's `qrCodePngPath` field is null when the PNG could not be produced.
*
* Hardening (hostile-review 2026-06-12): the record and PNG paths are unlinked
* before writing so a pre-planted symlink is replaced instead of followed; the
* record is written with mode 0600 (outputUrl is a signed download URL); a
* created parent directory gets mode 0700; and when the record lives under the
* shared tmpdir, a parent directory that is a symlink or owned by another user
* is refused.
*/
export declare function writeBuildOutputRecord(recordPath: string, input: WriteBuildOutputRecordInput, onWarn?: (msg: string) => void): Promise<BuildOutputRecord>;
/**
* Returns a deterministic temp-file path for the build output record for a
* given (appId, platform) pair. Both the build hand-off (command emit) and
* the confirm (record read) call this helper so that the path is never passed
* back and forth across an MCP boundary.
*
* appId is sanitized: all `/` and `\` characters are replaced with `_`, and
* any `..` sequences are replaced with `_`, to prevent path traversal.
*
* The record lives in a per-user `capgo-build-records-<uid>` subdirectory
* (created with mode 0700 by `writeBuildOutputRecord`) so that on shared-tmp
* systems another user can neither pre-create nor read the record file
* (hostile-review 2026-06-12).
*/
export declare function defaultBuildRecordPath(appId: string, platform: 'ios' | 'android'): string;
/**
* Remove a build output record and its companion QR PNG. Absent paths are a
* no-op. Called before a new build hand-off so a record left behind by an
* earlier build can never be read as the new build's result.
*/
export declare function removeBuildOutputRecord(recordPath: string): Promise<void>;
/**
* Thrown by `readBuildOutputRecord` when the record file EXISTS but cannot be
* used: unreadable (permissions), a symbolic link, not valid JSON (e.g. a
* truncated write), or an unexpected shape. Distinct from the `null` return
* (no record yet) so callers polling for a build result can surface the
* failure instead of waiting forever.
*/
export declare class BuildRecordReadError extends Error {
readonly recordPath: string;
constructor(message: string, recordPath: string);
}
/**
* Read a build output record from `path`.
*
* Returns `null` ONLY when the file does not exist yet (ENOENT — the build has
* not finished). Every other failure mode (unreadable file, a symlink at the
* record path, malformed JSON, missing/wrong-type fields) throws
* `BuildRecordReadError`: a present-but-corrupt record is a surfaced failure,
* never "still waiting".
*
* Shape rules (hostile-review 2026-06-12):
* - every record owes the `jobId`/`status`/`outputUrl` trio (forward-tolerant
* baseline for future schemaVersions);
* - a `schemaVersion: 1` record is validated strictly against the full
* `BuildOutputRecord` shape — the v1 writer has always emitted it, and
* checkBuild correlates `appId`/`platform` against the build it launched.
*/
export declare function readBuildOutputRecord(path: string): Promise<BuildOutputRecord | null>;