brew-tui 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/build/{brewbar-installer-BAHS6EEZ.js → brewbar-installer-WJZKSELD.js} +3 -3
- package/build/brewbar-installer-WJZKSELD.js.map +1 -0
- package/build/{brewfile-manager-3SERRYNC.js → brewfile-manager-OITSKEHY.js} +4 -4
- package/build/{chunk-U2DRWB7A.js → chunk-5MYWF5D7.js} +2 -2
- package/build/{chunk-MSXH66I2.js → chunk-6NA4INJS.js} +31 -3
- package/build/chunk-6NA4INJS.js.map +1 -0
- package/build/chunk-EHIBIFCB.js +54 -0
- package/build/chunk-EHIBIFCB.js.map +1 -0
- package/build/{chunk-LXF72RCD.js → chunk-KSIAKLE2.js} +3 -3
- package/build/{chunk-4I344KQX.js → chunk-PVSE6XO7.js} +42 -2
- package/build/chunk-PVSE6XO7.js.map +1 -0
- package/build/chunk-QPXROTAP.js +679 -0
- package/build/chunk-QPXROTAP.js.map +1 -0
- package/build/{chunk-KVCVIRWI.js → chunk-Z2VN4VYQ.js} +2 -2
- package/build/compliance-checker-7NMFKWTI.js +12 -0
- package/build/{history-logger-PBDOLKNJ.js → history-logger-ZGYRAFON.js} +3 -3
- package/build/index.js +449 -678
- package/build/index.js.map +1 -1
- package/build/{snapshot-RAPGMAJF.js → snapshot-YWIOFQ5H.js} +7 -3
- package/build/{sync-engine-CAFK4LHA.js → sync-engine-DIYXV66P.js} +7 -5
- package/package.json +4 -4
- package/build/brewbar-installer-BAHS6EEZ.js.map +0 -1
- package/build/chunk-4I344KQX.js.map +0 -1
- package/build/chunk-AIAZQJKL.js +0 -299
- package/build/chunk-AIAZQJKL.js.map +0 -1
- package/build/chunk-MSXH66I2.js.map +0 -1
- package/build/chunk-UWS4A4F5.js +0 -25
- package/build/chunk-UWS4A4F5.js.map +0 -1
- package/build/compliance-checker-X7P623UF.js +0 -12
- /package/build/{brewfile-manager-3SERRYNC.js.map → brewfile-manager-OITSKEHY.js.map} +0 -0
- /package/build/{chunk-U2DRWB7A.js.map → chunk-5MYWF5D7.js.map} +0 -0
- /package/build/{chunk-LXF72RCD.js.map → chunk-KSIAKLE2.js.map} +0 -0
- /package/build/{chunk-KVCVIRWI.js.map → chunk-Z2VN4VYQ.js.map} +0 -0
- /package/build/{compliance-checker-X7P623UF.js.map → compliance-checker-7NMFKWTI.js.map} +0 -0
- /package/build/{history-logger-PBDOLKNJ.js.map → history-logger-ZGYRAFON.js.map} +0 -0
- /package/build/{snapshot-RAPGMAJF.js.map → snapshot-YWIOFQ5H.js.map} +0 -0
- /package/build/{sync-engine-CAFK4LHA.js.map → sync-engine-DIYXV66P.js.map} +0 -0
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SNAPSHOT_RETENTION_LIMIT,
|
|
2
3
|
captureSnapshot,
|
|
3
4
|
deleteSnapshot,
|
|
4
5
|
getLatestSnapshot,
|
|
5
6
|
loadSnapshots,
|
|
7
|
+
pruneSnapshots,
|
|
6
8
|
saveSnapshot
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-PVSE6XO7.js";
|
|
10
|
+
import "./chunk-EHIBIFCB.js";
|
|
9
11
|
import "./chunk-KDHEUNRI.js";
|
|
10
12
|
export {
|
|
13
|
+
SNAPSHOT_RETENTION_LIMIT,
|
|
11
14
|
captureSnapshot,
|
|
12
15
|
deleteSnapshot,
|
|
13
16
|
getLatestSnapshot,
|
|
14
17
|
loadSnapshots,
|
|
18
|
+
pruneSnapshots,
|
|
15
19
|
saveSnapshot
|
|
16
20
|
};
|
|
17
|
-
//# sourceMappingURL=snapshot-
|
|
21
|
+
//# sourceMappingURL=snapshot-YWIOFQ5H.js.map
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyConflictResolutions,
|
|
3
|
-
getMachineId,
|
|
4
3
|
loadSyncConfig,
|
|
5
4
|
saveSyncConfig,
|
|
6
5
|
sync
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import
|
|
6
|
+
} from "./chunk-QPXROTAP.js";
|
|
7
|
+
import "./chunk-PVSE6XO7.js";
|
|
8
|
+
import {
|
|
9
|
+
getMachineId
|
|
10
|
+
} from "./chunk-EHIBIFCB.js";
|
|
11
|
+
import "./chunk-6NA4INJS.js";
|
|
10
12
|
import "./chunk-KDHEUNRI.js";
|
|
11
13
|
export {
|
|
12
14
|
applyConflictResolutions,
|
|
@@ -15,4 +17,4 @@ export {
|
|
|
15
17
|
saveSyncConfig,
|
|
16
18
|
sync
|
|
17
19
|
};
|
|
18
|
-
//# sourceMappingURL=sync-engine-
|
|
20
|
+
//# sourceMappingURL=sync-engine-DIYXV66P.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brew-tui",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Brew-TUI — Visual TUI for Homebrew package management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
],
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/
|
|
18
|
+
"url": "git+https://github.com/MoLinesDesigns/Brew-TUI.git"
|
|
19
19
|
},
|
|
20
20
|
"bugs": {
|
|
21
|
-
"url": "https://github.com/
|
|
21
|
+
"url": "https://github.com/MoLinesDesigns/Brew-TUI/issues"
|
|
22
22
|
},
|
|
23
|
-
"homepage": "https://github.com/
|
|
23
|
+
"homepage": "https://github.com/MoLinesDesigns/Brew-TUI#readme",
|
|
24
24
|
"author": "MoLines Designs",
|
|
25
25
|
"scripts": {
|
|
26
26
|
"dev": "tsx src/index.tsx",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/brewbar-installer.ts"],"sourcesContent":["import { rm, access, readFile } from 'node:fs/promises';\nimport { createWriteStream } from 'node:fs';\nimport { createHash, randomUUID } from 'node:crypto';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { pipeline } from 'node:stream/promises';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { t } from '../i18n/index.js';\nimport { fetchWithTimeout } from './fetch-timeout.js';\n\nconst execFileAsync = promisify(execFile);\nconst BREWBAR_APP_PATH = '/Applications/BrewBar.app';\nconst DOWNLOAD_URL = 'https://github.com/MoLinesGitHub/Brew-TUI/releases/latest/download/BrewBar.app.zip';\nconst MAX_SIZE = 200 * 1024 * 1024; // 200 MB\n\nexport async function isBrewBarInstalled(): Promise<boolean> {\n try {\n await access(BREWBAR_APP_PATH);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function installBrewBar(isPro: boolean, force = false): Promise<void> {\n // macOS only\n if (process.platform !== 'darwin') {\n throw new Error(t('cli_brewbarMacOnly'));\n }\n\n // Pro check\n if (!isPro) {\n throw new Error(t('cli_brewbarProRequired'));\n }\n\n // Already installed check\n if (!force && await isBrewBarInstalled()) {\n throw new Error(t('cli_brewbarAlreadyInstalled'));\n }\n\n console.log(t('cli_brewbarInstalling'));\n\n // EP-013: Use unique temp path\n const TMP_ZIP = join(tmpdir(), 'BrewBar-' + randomUUID() + '.zip');\n\n // Download zip (120s timeout for large binary)\n const res = await fetchWithTimeout(DOWNLOAD_URL, {}, 120_000);\n if (!res.ok || !res.body) {\n throw new Error(t('cli_brewbarDownloadFailed', { error: `HTTP ${res.status}` }));\n }\n\n // Reject downloads larger than 200 MB (from Content-Length header)\n const contentLength = Number(res.headers.get('content-length') ?? '0');\n if (contentLength > MAX_SIZE) {\n throw new Error(t('cli_brewbarDownloadFailed', { error: 'Download exceeds 200 MB size limit' }));\n }\n\n // EP-005: Track downloaded bytes during the stream\n let downloadedBytes = 0;\n\n // Write to tmp file with byte counting\n const fileStream = createWriteStream(TMP_ZIP);\n const transformedBody = new ReadableStream({\n async start(controller) {\n const bodyReader = (res.body as ReadableStream<Uint8Array>).getReader();\n try {\n while (true) {\n const { done, value } = await bodyReader.read();\n if (done) break;\n downloadedBytes += value.length;\n if (downloadedBytes > MAX_SIZE) {\n controller.error(new Error('Download exceeds 200 MB limit'));\n return;\n }\n controller.enqueue(value);\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n }\n },\n });\n await pipeline(transformedBody as unknown as NodeJS.ReadableStream, fileStream);\n\n // SEG-001: SHA-256 integrity check with proper error handling\n let expectedHash: string | null = null;\n try {\n const checksumRes = await fetchWithTimeout(`${DOWNLOAD_URL}.sha256`, {}, 15_000);\n if (checksumRes.ok) {\n const text = await checksumRes.text();\n // EP-009: Validate split result is defined\n const hash = text.trim().split(/\\s+/)[0];\n // EP-010: Validate hash format\n if (hash && /^[0-9a-f]{64}$/i.test(hash)) {\n expectedHash = hash.toLowerCase();\n }\n }\n } catch {\n /* checksum file not available */\n }\n\n if (expectedHash) {\n const fileBuffer = await readFile(TMP_ZIP);\n const actual = createHash('sha256').update(fileBuffer).digest('hex');\n if (actual !== expectedHash) {\n await rm(TMP_ZIP, { force: true }).catch(() => {});\n throw new Error(t('cli_brewbarDownloadFailed', { error: 'SHA-256 mismatch: binary may have been tampered with' }));\n }\n } else {\n // NUEVO-003: Treat missing checksum as fatal — don't install unverified binaries\n await rm(TMP_ZIP, { force: true }).catch(() => {});\n throw new Error(t('cli_brewbarDownloadFailed', { error: 'SHA-256 checksum unavailable — cannot verify download integrity' }));\n }\n\n // Remove old app if force reinstall\n if (force && await isBrewBarInstalled()) {\n await rm(BREWBAR_APP_PATH, { recursive: true, force: true });\n }\n\n // Unzip to /Applications\n try {\n await execFileAsync('ditto', ['-xk', TMP_ZIP, '/Applications/']);\n } catch (err) {\n throw new Error(t('cli_brewbarDownloadFailed', { error: err instanceof Error ? err.message : String(err) }), { cause: err });\n } finally {\n // Clean up tmp zip\n await rm(TMP_ZIP, { force: true }).catch(() => {});\n }\n}\n\n/// Launches BrewBar detached from the parent process so it survives terminal close.\n/// `open -g -a` runs the app in the background without bringing it to foreground.\nexport async function launchBrewBar(): Promise<void> {\n if (process.platform !== 'darwin') return;\n if (!await isBrewBarInstalled()) return;\n try {\n await execFileAsync('open', ['-g', '-a', BREWBAR_APP_PATH]);\n } catch {\n // Non-fatal: BrewBar may already be running, or LaunchServices may need a moment.\n }\n}\n\nexport async function uninstallBrewBar(): Promise<void> {\n if (!await isBrewBarInstalled()) {\n throw new Error(t('cli_brewbarNotInstalled'));\n }\n\n await rm(BREWBAR_APP_PATH, { recursive: true, force: true });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/state-snapshot/snapshot.ts","../src/lib/brew-cli.ts"],"sourcesContent":["import { readFile, writeFile, rename, readdir, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { execBrew } from '../brew-cli.js';\nimport { SNAPSHOTS_DIR, ensureDataDirs } from '../data-dir.js';\nimport { logger } from '../../utils/logger.js';\n\nexport interface BrewSnapshot {\n capturedAt: string;\n label?: string;\n formulae: Array<{ name: string; version: string; pinned: boolean }>;\n casks: Array<{ name: string; version: string }>;\n taps: string[];\n}\n\nfunction isValidSnapshot(v: unknown): v is BrewSnapshot {\n if (!v || typeof v !== 'object') return false;\n const s = v as Record<string, unknown>;\n return (\n typeof s['capturedAt'] === 'string' &&\n Array.isArray(s['formulae']) &&\n Array.isArray(s['casks']) &&\n Array.isArray(s['taps'])\n );\n}\n\n/** Parse `brew list --versions --formula` or `brew list --cask --versions`.\n * Each line: `name version1 version2...` — last version is the current install. */\nfunction parseVersionsList(output: string): Array<{ name: string; version: string }> {\n return output\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => {\n const parts = line.split(/\\s+/);\n const name = parts[0] ?? '';\n const version = parts[parts.length - 1] ?? '';\n return { name, version };\n })\n .filter((e) => e.name !== '');\n}\n\n/** Parse `brew tap` — one tap per line. */\nfunction parseTapsList(output: string): string[] {\n return output\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n}\n\n/** Parse `brew list --pinned` — one formula per line. */\nfunction parsePinnedList(output: string): Set<string> {\n return new Set(\n output\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean),\n );\n}\n\nexport async function captureSnapshot(): Promise<BrewSnapshot> {\n const [formulaeRaw, casksRaw, tapsRaw, pinnedRaw] = await Promise.all([\n execBrew(['list', '--versions', '--formula']),\n execBrew(['list', '--cask', '--versions']),\n execBrew(['tap']),\n execBrew(['list', '--pinned']),\n ]);\n\n const pinned = parsePinnedList(pinnedRaw);\n const formulae = parseVersionsList(formulaeRaw).map((f) => ({\n ...f,\n pinned: pinned.has(f.name),\n }));\n const casks = parseVersionsList(casksRaw);\n const taps = parseTapsList(tapsRaw);\n\n return {\n capturedAt: new Date().toISOString(),\n formulae,\n casks,\n taps,\n };\n}\n\n/** Sanitize a label for safe use in a filename. */\nfunction sanitizeLabel(label: string): string {\n const clean = label.replace(/[^A-Za-z0-9_-]/g, '_');\n return clean.length > 0 ? clean : 'auto';\n}\n\n/** Convert an ISO 8601 timestamp to a filename-safe string by replacing `:` and `.` with `-`. */\nfunction timestampToFilename(iso: string): string {\n return iso.replace(/[:.]/g, '-');\n}\n\nexport async function saveSnapshot(s: BrewSnapshot, label?: string): Promise<void> {\n await ensureDataDirs();\n\n const effectiveLabel = label ? sanitizeLabel(label) : 'auto';\n const filename = `${timestampToFilename(s.capturedAt)}-${effectiveLabel}.json`;\n const filePath = join(SNAPSHOTS_DIR, filename);\n const tmpPath = filePath + '.tmp';\n\n const payload: BrewSnapshot = label ? { ...s, label } : s;\n\n await writeFile(tmpPath, JSON.stringify(payload, null, 2), { encoding: 'utf-8', mode: 0o600 });\n await rename(tmpPath, filePath);\n}\n\nexport async function loadSnapshots(): Promise<BrewSnapshot[]> {\n let entries: string[];\n try {\n entries = await readdir(SNAPSHOTS_DIR);\n } catch {\n return [];\n }\n\n const jsonFiles = entries.filter((f) => f.endsWith('.json'));\n const snapshots: BrewSnapshot[] = [];\n\n for (const filename of jsonFiles) {\n try {\n const raw = await readFile(join(SNAPSHOTS_DIR, filename), 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (!isValidSnapshot(parsed)) {\n logger.warn('Skipping corrupt snapshot file', { filename });\n continue;\n }\n snapshots.push(parsed);\n } catch {\n logger.warn('Failed to read snapshot file', { filename });\n }\n }\n\n return snapshots.sort((a, b) => b.capturedAt.localeCompare(a.capturedAt));\n}\n\nexport async function deleteSnapshot(capturedAt: string): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(SNAPSHOTS_DIR);\n } catch {\n return;\n }\n\n for (const filename of entries.filter((f) => f.endsWith('.json'))) {\n try {\n const raw = await readFile(join(SNAPSHOTS_DIR, filename), 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (isValidSnapshot(parsed) && parsed.capturedAt === capturedAt) {\n await unlink(join(SNAPSHOTS_DIR, filename));\n return;\n }\n } catch {\n // Skip unreadable files\n }\n }\n}\n\nexport async function getLatestSnapshot(): Promise<BrewSnapshot | null> {\n const all = await loadSnapshots();\n return all[0] ?? null;\n}\n","import { spawn } from 'node:child_process';\n\nconst DEFAULT_TIMEOUT_MS = 30_000; // 30 seconds for instant commands\nconst STREAM_IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes idle timeout for streaming\n\nexport async function execBrew(args: string[], timeoutMs = DEFAULT_TIMEOUT_MS): Promise<string> {\n return new Promise((resolve, reject) => {\n const proc = spawn('brew', args, { env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: '1' } });\n let stdout = '';\n let stderr = '';\n let killed = false;\n\n // EP-012: Timeout with AbortController pattern\n const timer = setTimeout(() => {\n killed = true;\n proc.kill();\n reject(new Error(`brew ${args.join(' ')} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n proc.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });\n proc.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });\n proc.on('close', (code) => {\n clearTimeout(timer);\n if (killed) return;\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(new Error(stderr.trim() || `brew ${args.join(' ')} exited with code ${code}`));\n }\n });\n proc.on('error', (err) => {\n clearTimeout(timer);\n if (killed) return;\n reject(new Error(`Failed to run brew: ${err.message}`));\n });\n });\n}\n\nexport async function* streamBrew(args: string[]): AsyncGenerator<string> {\n const proc = spawn('brew', args, {\n env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: '1' },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let buffer = '';\n const lines: string[] = [];\n let done = false;\n let exitError: string | null = null;\n let lastOutputAt = Date.now();\n\n const push = (chunk: Buffer) => {\n lastOutputAt = Date.now();\n buffer += chunk.toString();\n const parts = buffer.split('\\n');\n buffer = parts.pop() ?? '';\n for (const line of parts) {\n if (line.trim()) lines.push(line);\n }\n };\n\n proc.stdout.on('data', push);\n proc.stderr.on('data', push);\n\n proc.on('close', (code) => {\n if (buffer.trim()) lines.push(buffer.trim());\n done = true;\n if (code !== 0) {\n exitError = `brew ${args.join(' ')} exited with code ${code}`;\n }\n });\n\n proc.on('error', (err) => {\n done = true;\n exitError = err.message;\n });\n\n try {\n while (!done || lines.length > 0) {\n if (lines.length > 0) {\n yield lines.shift()!;\n } else if (!done) {\n // EP-012: Kill process if idle for too long\n if (Date.now() - lastOutputAt > STREAM_IDLE_TIMEOUT_MS) {\n proc.kill();\n throw new Error(`brew ${args.join(' ')} timed out: no output for ${STREAM_IDLE_TIMEOUT_MS / 1000}s`);\n }\n await new Promise((r) => setTimeout(r, 100));\n }\n }\n } finally {\n if (!done) {\n proc.kill();\n }\n }\n\n // Throw after all lines have been yielded so the consumer sees\n // brew's stderr output in the stream before the error surfaces.\n if (exitError) {\n throw new Error(exitError);\n }\n}\n"],"mappings":"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","names":[]}
|
package/build/chunk-AIAZQJKL.js
DELETED
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
captureSnapshot
|
|
3
|
-
} from "./chunk-4I344KQX.js";
|
|
4
|
-
import {
|
|
5
|
-
DATA_DIR
|
|
6
|
-
} from "./chunk-UWS4A4F5.js";
|
|
7
|
-
import {
|
|
8
|
-
logger
|
|
9
|
-
} from "./chunk-KDHEUNRI.js";
|
|
10
|
-
|
|
11
|
-
// src/lib/sync/sync-engine.ts
|
|
12
|
-
import { readFile as readFile2, writeFile as writeFile2, rename as rename2 } from "fs/promises";
|
|
13
|
-
import { join as join2 } from "path";
|
|
14
|
-
import { hostname } from "os";
|
|
15
|
-
|
|
16
|
-
// src/lib/sync/crypto.ts
|
|
17
|
-
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
18
|
-
var ENCRYPTION_SECRET = "brew-tui-sync-aes256gcm-v1";
|
|
19
|
-
var SCRYPT_SALT = "brew-tui-sync-salt-v1";
|
|
20
|
-
var _derivedKey = null;
|
|
21
|
-
function deriveEncryptionKey() {
|
|
22
|
-
if (!_derivedKey) {
|
|
23
|
-
_derivedKey = scryptSync(ENCRYPTION_SECRET, SCRYPT_SALT, 32, {
|
|
24
|
-
N: 16384,
|
|
25
|
-
r: 8,
|
|
26
|
-
p: 1
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
return _derivedKey;
|
|
30
|
-
}
|
|
31
|
-
function encryptPayload(data) {
|
|
32
|
-
const key = deriveEncryptionKey();
|
|
33
|
-
const iv = randomBytes(12);
|
|
34
|
-
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
35
|
-
const plaintext = JSON.stringify(data);
|
|
36
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
37
|
-
const tag = cipher.getAuthTag();
|
|
38
|
-
return {
|
|
39
|
-
encrypted: ciphertext.toString("base64"),
|
|
40
|
-
iv: iv.toString("base64"),
|
|
41
|
-
tag: tag.toString("base64")
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function decryptPayload(encrypted, iv, tag) {
|
|
45
|
-
const key = deriveEncryptionKey();
|
|
46
|
-
const decipher = createDecipheriv(
|
|
47
|
-
"aes-256-gcm",
|
|
48
|
-
key,
|
|
49
|
-
Buffer.from(iv, "base64")
|
|
50
|
-
);
|
|
51
|
-
decipher.setAuthTag(Buffer.from(tag, "base64"));
|
|
52
|
-
const plaintext = Buffer.concat([
|
|
53
|
-
decipher.update(Buffer.from(encrypted, "base64")),
|
|
54
|
-
decipher.final()
|
|
55
|
-
]);
|
|
56
|
-
return JSON.parse(plaintext.toString("utf-8"));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/lib/sync/backends/icloud-backend.ts
|
|
60
|
-
import { readFile, writeFile, rename, mkdir, stat } from "fs/promises";
|
|
61
|
-
import { homedir } from "os";
|
|
62
|
-
import { join } from "path";
|
|
63
|
-
var ICLOUD_BASE = join(
|
|
64
|
-
homedir(),
|
|
65
|
-
"Library",
|
|
66
|
-
"Mobile Documents",
|
|
67
|
-
"com~apple~CloudDocs"
|
|
68
|
-
);
|
|
69
|
-
var ICLOUD_SYNC_DIR = join(ICLOUD_BASE, "BrewTUI");
|
|
70
|
-
var ICLOUD_SYNC_PATH = join(ICLOUD_SYNC_DIR, "sync.json");
|
|
71
|
-
async function isICloudAvailable() {
|
|
72
|
-
try {
|
|
73
|
-
await stat(ICLOUD_BASE);
|
|
74
|
-
return true;
|
|
75
|
-
} catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function isValidEnvelope(v) {
|
|
80
|
-
if (!v || typeof v !== "object") return false;
|
|
81
|
-
const obj = v;
|
|
82
|
-
return obj["schemaVersion"] === 1 && typeof obj["encrypted"] === "string" && typeof obj["iv"] === "string" && typeof obj["tag"] === "string" && typeof obj["updatedAt"] === "string";
|
|
83
|
-
}
|
|
84
|
-
async function readSyncEnvelope() {
|
|
85
|
-
try {
|
|
86
|
-
const raw = await readFile(ICLOUD_SYNC_PATH, "utf-8");
|
|
87
|
-
const parsed = JSON.parse(raw);
|
|
88
|
-
if (!isValidEnvelope(parsed)) {
|
|
89
|
-
logger.warn("sync: invalid envelope structure in iCloud file");
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
return parsed;
|
|
93
|
-
} catch (err) {
|
|
94
|
-
if (err instanceof Error && err.code === "ENOENT") {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
logger.warn("sync: could not read iCloud envelope", { error: String(err) });
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async function writeSyncEnvelope(envelope) {
|
|
102
|
-
await mkdir(ICLOUD_SYNC_DIR, { recursive: true });
|
|
103
|
-
const tmpPath = ICLOUD_SYNC_PATH + ".tmp";
|
|
104
|
-
await writeFile(tmpPath, JSON.stringify(envelope, null, 2), {
|
|
105
|
-
encoding: "utf-8",
|
|
106
|
-
mode: 384
|
|
107
|
-
});
|
|
108
|
-
await rename(tmpPath, ICLOUD_SYNC_PATH);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// src/lib/sync/sync-engine.ts
|
|
112
|
-
var SYNC_CONFIG_PATH = join2(DATA_DIR, "sync-config.json");
|
|
113
|
-
var MACHINE_ID_PATH = join2(DATA_DIR, "machine-id");
|
|
114
|
-
async function loadSyncConfig() {
|
|
115
|
-
try {
|
|
116
|
-
const raw = await readFile2(SYNC_CONFIG_PATH, "utf-8");
|
|
117
|
-
return JSON.parse(raw);
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
async function saveSyncConfig(config) {
|
|
123
|
-
const tmpPath = SYNC_CONFIG_PATH + ".tmp";
|
|
124
|
-
await writeFile2(tmpPath, JSON.stringify(config, null, 2), {
|
|
125
|
-
encoding: "utf-8",
|
|
126
|
-
mode: 384
|
|
127
|
-
});
|
|
128
|
-
await rename2(tmpPath, SYNC_CONFIG_PATH);
|
|
129
|
-
}
|
|
130
|
-
async function getMachineId() {
|
|
131
|
-
try {
|
|
132
|
-
const id = (await readFile2(MACHINE_ID_PATH, "utf-8")).trim();
|
|
133
|
-
if (id) return id;
|
|
134
|
-
} catch {
|
|
135
|
-
}
|
|
136
|
-
return hostname();
|
|
137
|
-
}
|
|
138
|
-
function detectConflicts(localSnapshot, otherMachines, localMachineId) {
|
|
139
|
-
const conflicts = [];
|
|
140
|
-
const localFormulaMap = new Map(localSnapshot.formulae.map((f) => [f.name, f.version]));
|
|
141
|
-
const localCaskMap = new Map(localSnapshot.casks.map((c) => [c.name, c.version]));
|
|
142
|
-
for (const machine of otherMachines) {
|
|
143
|
-
if (machine.machineId === localMachineId) continue;
|
|
144
|
-
for (const remoteFormula of machine.snapshot.formulae) {
|
|
145
|
-
const localVersion = localFormulaMap.get(remoteFormula.name);
|
|
146
|
-
if (localVersion !== void 0 && localVersion !== remoteFormula.version) {
|
|
147
|
-
conflicts.push({
|
|
148
|
-
packageName: remoteFormula.name,
|
|
149
|
-
packageType: "formula",
|
|
150
|
-
localVersion,
|
|
151
|
-
remoteMachine: machine.machineName,
|
|
152
|
-
remoteVersion: remoteFormula.version
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
for (const remoteCask of machine.snapshot.casks) {
|
|
157
|
-
const localVersion = localCaskMap.get(remoteCask.name);
|
|
158
|
-
if (localVersion !== void 0 && localVersion !== remoteCask.version) {
|
|
159
|
-
conflicts.push({
|
|
160
|
-
packageName: remoteCask.name,
|
|
161
|
-
packageType: "cask",
|
|
162
|
-
localVersion,
|
|
163
|
-
remoteMachine: machine.machineName,
|
|
164
|
-
remoteVersion: remoteCask.version
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return conflicts;
|
|
170
|
-
}
|
|
171
|
-
async function writeEnvelope(payload) {
|
|
172
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
173
|
-
const { encrypted, iv, tag } = encryptPayload(payload);
|
|
174
|
-
const envelope = {
|
|
175
|
-
schemaVersion: 1,
|
|
176
|
-
encrypted,
|
|
177
|
-
iv,
|
|
178
|
-
tag,
|
|
179
|
-
updatedAt: now
|
|
180
|
-
};
|
|
181
|
-
await writeSyncEnvelope(envelope);
|
|
182
|
-
return now;
|
|
183
|
-
}
|
|
184
|
-
function mergePayload(existing, localState) {
|
|
185
|
-
return {
|
|
186
|
-
machines: {
|
|
187
|
-
...existing.machines,
|
|
188
|
-
[localState.machineId]: localState
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
async function sync(isPro, currentBrewfile) {
|
|
193
|
-
if (!isPro) {
|
|
194
|
-
throw new Error("Pro license required");
|
|
195
|
-
}
|
|
196
|
-
const available = await isICloudAvailable();
|
|
197
|
-
if (!available) {
|
|
198
|
-
return {
|
|
199
|
-
success: false,
|
|
200
|
-
conflicts: [],
|
|
201
|
-
resolvedCount: 0,
|
|
202
|
-
error: "iCloud Drive not available"
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
let existingPayload = null;
|
|
206
|
-
try {
|
|
207
|
-
const envelope = await readSyncEnvelope();
|
|
208
|
-
if (envelope) {
|
|
209
|
-
existingPayload = decryptPayload(envelope.encrypted, envelope.iv, envelope.tag);
|
|
210
|
-
}
|
|
211
|
-
} catch (err) {
|
|
212
|
-
logger.warn("sync: could not decrypt existing payload, starting fresh", { error: String(err) });
|
|
213
|
-
existingPayload = null;
|
|
214
|
-
}
|
|
215
|
-
const snapshot = await captureSnapshot();
|
|
216
|
-
const machineId = await getMachineId();
|
|
217
|
-
const machineName = hostname();
|
|
218
|
-
const localState = {
|
|
219
|
-
machineId,
|
|
220
|
-
machineName,
|
|
221
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
222
|
-
snapshot,
|
|
223
|
-
...currentBrewfile ? { brewfile: currentBrewfile } : {}
|
|
224
|
-
};
|
|
225
|
-
const otherMachines = existingPayload ? Object.values(existingPayload.machines).filter((m) => m.machineId !== machineId) : [];
|
|
226
|
-
const conflicts = detectConflicts(snapshot, otherMachines, machineId);
|
|
227
|
-
const basePayload = existingPayload ?? { machines: {} };
|
|
228
|
-
const mergedPayload = mergePayload(basePayload, localState);
|
|
229
|
-
if (conflicts.length > 0) {
|
|
230
|
-
await writeEnvelope(mergedPayload);
|
|
231
|
-
return {
|
|
232
|
-
success: false,
|
|
233
|
-
conflicts,
|
|
234
|
-
resolvedCount: 0
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
const now = await writeEnvelope(mergedPayload);
|
|
238
|
-
const existingConfig = await loadSyncConfig();
|
|
239
|
-
await saveSyncConfig({
|
|
240
|
-
enabled: true,
|
|
241
|
-
machineId,
|
|
242
|
-
machineName,
|
|
243
|
-
...existingConfig ?? {},
|
|
244
|
-
lastSync: now
|
|
245
|
-
});
|
|
246
|
-
logger.info("sync: completed successfully", { machineId, machines: Object.keys(mergedPayload.machines).length });
|
|
247
|
-
return {
|
|
248
|
-
success: true,
|
|
249
|
-
conflicts: [],
|
|
250
|
-
resolvedCount: 0
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
async function applyConflictResolutions(payload, resolutions, localMachineId) {
|
|
254
|
-
const updatedPayload = {
|
|
255
|
-
machines: { ...payload.machines }
|
|
256
|
-
};
|
|
257
|
-
for (const { conflict, resolution } of resolutions) {
|
|
258
|
-
if (resolution !== "use-remote") continue;
|
|
259
|
-
const localMachine = updatedPayload.machines[localMachineId];
|
|
260
|
-
if (!localMachine) {
|
|
261
|
-
logger.warn("sync: cannot apply resolution, local machine missing in payload", { localMachineId });
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (conflict.packageType === "formula") {
|
|
265
|
-
updatedPayload.machines[localMachineId] = {
|
|
266
|
-
...localMachine,
|
|
267
|
-
snapshot: {
|
|
268
|
-
...localMachine.snapshot,
|
|
269
|
-
formulae: localMachine.snapshot.formulae.map(
|
|
270
|
-
(f) => f.name === conflict.packageName ? { ...f, version: conflict.remoteVersion } : f
|
|
271
|
-
)
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
} else {
|
|
275
|
-
updatedPayload.machines[localMachineId] = {
|
|
276
|
-
...localMachine,
|
|
277
|
-
snapshot: {
|
|
278
|
-
...localMachine.snapshot,
|
|
279
|
-
casks: localMachine.snapshot.casks.map(
|
|
280
|
-
(c) => c.name === conflict.packageName ? { ...c, version: conflict.remoteVersion } : c
|
|
281
|
-
)
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
await writeEnvelope(updatedPayload);
|
|
287
|
-
logger.info("sync: conflict resolutions applied", { count: resolutions.length });
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export {
|
|
291
|
-
decryptPayload,
|
|
292
|
-
readSyncEnvelope,
|
|
293
|
-
loadSyncConfig,
|
|
294
|
-
saveSyncConfig,
|
|
295
|
-
getMachineId,
|
|
296
|
-
sync,
|
|
297
|
-
applyConflictResolutions
|
|
298
|
-
};
|
|
299
|
-
//# sourceMappingURL=chunk-AIAZQJKL.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/sync/sync-engine.ts","../src/lib/sync/crypto.ts","../src/lib/sync/backends/icloud-backend.ts"],"sourcesContent":["import { readFile, writeFile, rename } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { hostname } from 'node:os';\nimport { encryptPayload, decryptPayload } from './crypto.js';\nimport {\n readSyncEnvelope,\n writeSyncEnvelope,\n isICloudAvailable,\n} from './backends/icloud-backend.js';\nimport { captureSnapshot } from '../state-snapshot/snapshot.js';\nimport { DATA_DIR } from '../data-dir.js';\nimport { logger } from '../../utils/logger.js';\nimport type {\n SyncConfig,\n SyncPayload,\n SyncConflict,\n SyncResult,\n MachineState,\n SyncEnvelope,\n} from './types.js';\nimport type { BrewfileSchema } from '../brewfile/types.js';\n\nconst SYNC_CONFIG_PATH = join(DATA_DIR, 'sync-config.json');\nconst MACHINE_ID_PATH = join(DATA_DIR, 'machine-id');\n\n// ── Config I/O ──────────────────────────────────────────────────────────────\n\nexport async function loadSyncConfig(): Promise<SyncConfig | null> {\n try {\n const raw = await readFile(SYNC_CONFIG_PATH, 'utf-8');\n return JSON.parse(raw) as SyncConfig;\n } catch {\n return null;\n }\n}\n\nexport async function saveSyncConfig(config: SyncConfig): Promise<void> {\n const tmpPath = SYNC_CONFIG_PATH + '.tmp';\n await writeFile(tmpPath, JSON.stringify(config, null, 2), {\n encoding: 'utf-8',\n mode: 0o600,\n });\n await rename(tmpPath, SYNC_CONFIG_PATH);\n}\n\n// ── Machine ID ───────────────────────────────────────────────────────────────\n\nexport async function getMachineId(): Promise<string> {\n try {\n const id = (await readFile(MACHINE_ID_PATH, 'utf-8')).trim();\n if (id) return id;\n } catch { /* machine-id created by polar-api on first activation */ }\n return hostname(); // Fallback: hostname if machine-id not yet created\n}\n\n// ── Conflict detection ───────────────────────────────────────────────────────\n\nfunction detectConflicts(\n localSnapshot: { formulae: Array<{ name: string; version: string }>; casks: Array<{ name: string; version: string }> },\n otherMachines: MachineState[],\n localMachineId: string,\n): SyncConflict[] {\n const conflicts: SyncConflict[] = [];\n\n const localFormulaMap = new Map(localSnapshot.formulae.map((f) => [f.name, f.version]));\n const localCaskMap = new Map(localSnapshot.casks.map((c) => [c.name, c.version]));\n\n for (const machine of otherMachines) {\n if (machine.machineId === localMachineId) continue;\n\n // Check formula conflicts: same package, different version on both machines\n for (const remoteFormula of machine.snapshot.formulae) {\n const localVersion = localFormulaMap.get(remoteFormula.name);\n if (localVersion !== undefined && localVersion !== remoteFormula.version) {\n conflicts.push({\n packageName: remoteFormula.name,\n packageType: 'formula',\n localVersion,\n remoteMachine: machine.machineName,\n remoteVersion: remoteFormula.version,\n });\n }\n }\n\n // Check cask conflicts\n for (const remoteCask of machine.snapshot.casks) {\n const localVersion = localCaskMap.get(remoteCask.name);\n if (localVersion !== undefined && localVersion !== remoteCask.version) {\n conflicts.push({\n packageName: remoteCask.name,\n packageType: 'cask',\n localVersion,\n remoteMachine: machine.machineName,\n remoteVersion: remoteCask.version,\n });\n }\n }\n }\n\n return conflicts;\n}\n\n// ── Merge ────────────────────────────────────────────────────────────────────\n\nasync function writeEnvelope(payload: SyncPayload): Promise<string> {\n const now = new Date().toISOString();\n const { encrypted, iv, tag } = encryptPayload(payload);\n const envelope: SyncEnvelope = {\n schemaVersion: 1,\n encrypted,\n iv,\n tag,\n updatedAt: now,\n };\n await writeSyncEnvelope(envelope);\n return now;\n}\n\nfunction mergePayload(existing: SyncPayload, localState: MachineState): SyncPayload {\n return {\n machines: {\n ...existing.machines,\n [localState.machineId]: localState,\n },\n };\n}\n\n// ── Main sync function ───────────────────────────────────────────────────────\n\nexport async function sync(\n isPro: boolean,\n currentBrewfile?: BrewfileSchema,\n): Promise<SyncResult> {\n if (!isPro) {\n throw new Error('Pro license required');\n }\n\n const available = await isICloudAvailable();\n if (!available) {\n return {\n success: false,\n conflicts: [],\n resolvedCount: 0,\n error: 'iCloud Drive not available',\n };\n }\n\n let existingPayload: SyncPayload | null = null;\n\n try {\n const envelope = await readSyncEnvelope();\n if (envelope) {\n existingPayload = decryptPayload(envelope.encrypted, envelope.iv, envelope.tag);\n }\n } catch (err) {\n logger.warn('sync: could not decrypt existing payload, starting fresh', { error: String(err) });\n existingPayload = null;\n }\n\n // Capture current local state\n const snapshot = await captureSnapshot();\n const machineId = await getMachineId();\n const machineName = hostname();\n\n const localState: MachineState = {\n machineId,\n machineName,\n updatedAt: new Date().toISOString(),\n snapshot,\n ...(currentBrewfile ? { brewfile: currentBrewfile } : {}),\n };\n\n // Detect conflicts against other machines in the payload\n const otherMachines = existingPayload\n ? Object.values(existingPayload.machines).filter((m) => m.machineId !== machineId)\n : [];\n\n const conflicts = detectConflicts(snapshot, otherMachines, machineId);\n\n // Always write the local machine state to the payload, even when conflicts\n // exist, so that applyConflictResolutions() has a local entry to update.\n // Without this, the iCloud envelope keeps only remote machines, and\n // resolution updates are silently dropped (they require localMachine to exist).\n const basePayload: SyncPayload = existingPayload ?? { machines: {} };\n const mergedPayload = mergePayload(basePayload, localState);\n\n if (conflicts.length > 0) {\n // Persist local state, then surface conflicts so the user can resolve them.\n await writeEnvelope(mergedPayload);\n return {\n success: false,\n conflicts,\n resolvedCount: 0,\n };\n }\n\n const now = await writeEnvelope(mergedPayload);\n\n // Update local sync config\n const existingConfig = await loadSyncConfig();\n await saveSyncConfig({\n enabled: true,\n machineId,\n machineName,\n ...(existingConfig ?? {}),\n lastSync: now,\n });\n\n logger.info('sync: completed successfully', { machineId, machines: Object.keys(mergedPayload.machines).length });\n\n return {\n success: true,\n conflicts: [],\n resolvedCount: 0,\n };\n}\n\n// ── Conflict resolution ──────────────────────────────────────────────────────\n\nexport async function applyConflictResolutions(\n payload: SyncPayload,\n resolutions: Array<{ conflict: SyncConflict; resolution: 'use-local' | 'use-remote' }>,\n localMachineId: string,\n): Promise<void> {\n // Work on a mutable copy\n const updatedPayload: SyncPayload = {\n machines: { ...payload.machines },\n };\n\n for (const { conflict, resolution } of resolutions) {\n if (resolution !== 'use-remote') continue;\n // Re-read latest local machine on every iteration so consecutive resolutions\n // build on top of each other instead of overwriting prior changes.\n const localMachine = updatedPayload.machines[localMachineId];\n if (!localMachine) {\n logger.warn('sync: cannot apply resolution, local machine missing in payload', { localMachineId });\n continue;\n }\n if (conflict.packageType === 'formula') {\n updatedPayload.machines[localMachineId] = {\n ...localMachine,\n snapshot: {\n ...localMachine.snapshot,\n formulae: localMachine.snapshot.formulae.map((f) =>\n f.name === conflict.packageName\n ? { ...f, version: conflict.remoteVersion }\n : f,\n ),\n },\n };\n } else {\n updatedPayload.machines[localMachineId] = {\n ...localMachine,\n snapshot: {\n ...localMachine.snapshot,\n casks: localMachine.snapshot.casks.map((c) =>\n c.name === conflict.packageName\n ? { ...c, version: conflict.remoteVersion }\n : c,\n ),\n },\n };\n }\n }\n\n await writeEnvelope(updatedPayload);\n logger.info('sync: conflict resolutions applied', { count: resolutions.length });\n}\n","import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';\nimport type { SyncPayload } from './types.js';\n\n// Cross-machine sync encryption — shared secret (no machine binding by design,\n// the same user's machines must decrypt each other's payloads).\nconst ENCRYPTION_SECRET = 'brew-tui-sync-aes256gcm-v1';\nconst SCRYPT_SALT = 'brew-tui-sync-salt-v1';\n\n// Lazy derivation — scryptSync is CPU-intensive and should not block on import.\nlet _derivedKey: Buffer | null = null;\n\nfunction deriveEncryptionKey(): Buffer {\n if (!_derivedKey) {\n _derivedKey = scryptSync(ENCRYPTION_SECRET, SCRYPT_SALT, 32, {\n N: 16384,\n r: 8,\n p: 1,\n });\n }\n return _derivedKey;\n}\n\nexport function encryptPayload(data: SyncPayload): { encrypted: string; iv: string; tag: string } {\n const key = deriveEncryptionKey();\n const iv = randomBytes(12); // 96-bit IV for GCM\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n\n const plaintext = JSON.stringify(data);\n const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()]);\n const tag = cipher.getAuthTag();\n\n return {\n encrypted: ciphertext.toString('base64'),\n iv: iv.toString('base64'),\n tag: tag.toString('base64'),\n };\n}\n\nexport function decryptPayload(encrypted: string, iv: string, tag: string): SyncPayload {\n const key = deriveEncryptionKey();\n const decipher = createDecipheriv(\n 'aes-256-gcm',\n key,\n Buffer.from(iv, 'base64'),\n );\n decipher.setAuthTag(Buffer.from(tag, 'base64'));\n\n const plaintext = Buffer.concat([\n decipher.update(Buffer.from(encrypted, 'base64')),\n decipher.final(),\n ]);\n\n return JSON.parse(plaintext.toString('utf-8')) as SyncPayload;\n}\n","import { readFile, writeFile, rename, mkdir, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { logger } from '../../../utils/logger.js';\nimport type { SyncEnvelope } from '../types.js';\n\nconst ICLOUD_BASE = join(\n homedir(),\n 'Library', 'Mobile Documents', 'com~apple~CloudDocs',\n);\nexport const ICLOUD_SYNC_DIR = join(ICLOUD_BASE, 'BrewTUI');\nexport const ICLOUD_SYNC_PATH = join(ICLOUD_SYNC_DIR, 'sync.json');\n\nexport async function isICloudAvailable(): Promise<boolean> {\n try {\n await stat(ICLOUD_BASE);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isValidEnvelope(v: unknown): v is SyncEnvelope {\n if (!v || typeof v !== 'object') return false;\n const obj = v as Record<string, unknown>;\n return (\n obj['schemaVersion'] === 1 &&\n typeof obj['encrypted'] === 'string' &&\n typeof obj['iv'] === 'string' &&\n typeof obj['tag'] === 'string' &&\n typeof obj['updatedAt'] === 'string'\n );\n}\n\nexport async function readSyncEnvelope(): Promise<SyncEnvelope | null> {\n try {\n const raw = await readFile(ICLOUD_SYNC_PATH, 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (!isValidEnvelope(parsed)) {\n logger.warn('sync: invalid envelope structure in iCloud file');\n return null;\n }\n return parsed;\n } catch (err: unknown) {\n // File does not exist yet — expected on first sync\n if (\n err instanceof Error &&\n (err as NodeJS.ErrnoException).code === 'ENOENT'\n ) {\n return null;\n }\n logger.warn('sync: could not read iCloud envelope', { error: String(err) });\n return null;\n }\n}\n\nexport async function writeSyncEnvelope(envelope: SyncEnvelope): Promise<void> {\n await mkdir(ICLOUD_SYNC_DIR, { recursive: true });\n const tmpPath = ICLOUD_SYNC_PATH + '.tmp';\n await writeFile(tmpPath, JSON.stringify(envelope, null, 2), {\n encoding: 'utf-8',\n mode: 0o600,\n });\n await rename(tmpPath, ICLOUD_SYNC_PATH);\n}\n"],"mappings":"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","names":["readFile","writeFile","rename","join","join","readFile","writeFile","rename"]}
|