brew-tui 0.9.1 → 0.9.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 +11 -8
- package/build/{brewbar-installer-V6R7BORH.js → brewbar-installer-GWJ76J6G.js} +1 -2
- package/build/brewbar-installer-GWJ76J6G.js.map +1 -0
- package/build/index.js +9 -8
- package/build/index.js.map +1 -1
- package/build/{version-check-X3HTR3HM.js → version-check-LHQYDFDA.js} +2 -2
- package/package.json +1 -1
- package/build/brewbar-installer-V6R7BORH.js.map +0 -1
- /package/build/{version-check-X3HTR3HM.js.map → version-check-LHQYDFDA.js.map} +0 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://nodejs.org/)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://github.com/MoLinesDesigns/homebrew-tap)
|
|
9
|
-
[]()
|
|
10
10
|
|
|
11
11
|
A keyboard-driven terminal UI for Homebrew, with a native macOS menu bar companion that watches updates in the background. No daemons, no middleware — both tools call `brew` directly.
|
|
12
12
|
|
|
@@ -112,14 +112,17 @@ brew-tui delete-account # Remove all local data (~/.brew-tui/)
|
|
|
112
112
|
|
|
113
113
|
### Keyboard Navigation
|
|
114
114
|
|
|
115
|
-
> **Heads up — keyboard model changed in 0.9.0.** Numbers `1`–`0` no
|
|
116
|
-
> jump between views; they now run the numbered actions in the footer
|
|
117
|
-
> current view.
|
|
118
|
-
>
|
|
115
|
+
> **Heads up — keyboard model changed in 0.9.0 / 0.9.1.** Numbers `1`–`0` no
|
|
116
|
+
> longer jump between views; they now run the numbered actions in the footer
|
|
117
|
+
> of the current view. The side menu opens automatically on launch (0.9.1) so
|
|
118
|
+
> arrows operate it from the first frame; press `m` to close or reopen it.
|
|
119
|
+
> The blinking orange `M` in the menu indicator marks the toggle. Old per-view
|
|
120
|
+
> letter shortcuts (`i`, `u`, `r`, …) still work as aliases.
|
|
119
121
|
|
|
120
122
|
| Key | Action |
|
|
121
123
|
|-----|--------|
|
|
122
|
-
| `
|
|
124
|
+
| `↑` / `↓` + `Enter` | Operate the side menu (active by default on launch) |
|
|
125
|
+
| `m` | Toggle the side menu (close / reopen) |
|
|
123
126
|
| `1`-`9` | Run the matching numbered action in the current view's footer |
|
|
124
127
|
| `↑` / `↓` (or `j` / `k`) | Move within a list |
|
|
125
128
|
| `Enter` | Open / confirm the highlighted item |
|
|
@@ -190,7 +193,7 @@ Views (React/Ink) --> Stores (Zustand) --> brew-api --> Parsers --> brew CLI (sp
|
|
|
190
193
|
- ESM-only, TypeScript strict mode, built with [tsup](https://github.com/egoist/tsup)
|
|
191
194
|
- All streaming operations (install, upgrade) use AsyncGenerators yielding lines in real time
|
|
192
195
|
- Package names validated via regex before passing to `spawn` (no shell injection)
|
|
193
|
-
-
|
|
196
|
+
- 434 tests across 59 suites (Vitest)
|
|
194
197
|
|
|
195
198
|
---
|
|
196
199
|
|
|
@@ -242,7 +245,7 @@ cd Brew-TUI
|
|
|
242
245
|
npm install
|
|
243
246
|
npm run dev # Run with tsx (requires interactive TTY)
|
|
244
247
|
npm run typecheck # tsc --noEmit
|
|
245
|
-
npm run test # vitest (
|
|
248
|
+
npm run test # vitest (434 tests)
|
|
246
249
|
npm run lint # eslint
|
|
247
250
|
npm run build # Production bundle via tsup
|
|
248
251
|
```
|
|
@@ -35,7 +35,6 @@ async function installBrewBar(isPro, force = false) {
|
|
|
35
35
|
if (!force && await isBrewBarInstalled()) {
|
|
36
36
|
throw new Error(t("cli_brewbarAlreadyInstalled"));
|
|
37
37
|
}
|
|
38
|
-
console.log(t("cli_brewbarInstalling"));
|
|
39
38
|
const TMP_ZIP = join(tmpdir(), "BrewBar-" + randomUUID() + ".zip");
|
|
40
39
|
const res = await fetchWithTimeout(DOWNLOAD_URL, {}, 12e4);
|
|
41
40
|
if (!res.ok || !res.body) {
|
|
@@ -125,4 +124,4 @@ export {
|
|
|
125
124
|
launchBrewBar,
|
|
126
125
|
uninstallBrewBar
|
|
127
126
|
};
|
|
128
|
-
//# sourceMappingURL=brewbar-installer-
|
|
127
|
+
//# sourceMappingURL=brewbar-installer-GWJ76J6G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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/MoLinesDesigns/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 // 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":[]}
|
package/build/index.js
CHANGED
|
@@ -4725,7 +4725,7 @@ function AccountView() {
|
|
|
4725
4725
|
status === "pro" || status === "team" || status === "expired" ? `v ${t("hint_revalidate")} ` : "",
|
|
4726
4726
|
revalidating ? t("account_revalidating") : "",
|
|
4727
4727
|
" ",
|
|
4728
|
-
t("app_version", { version: "0.9.
|
|
4728
|
+
t("app_version", { version: "0.9.2" })
|
|
4729
4729
|
] }) })
|
|
4730
4730
|
] });
|
|
4731
4731
|
}
|
|
@@ -6133,7 +6133,7 @@ async function reportError(err, context = {}) {
|
|
|
6133
6133
|
const config = await resolveConfig();
|
|
6134
6134
|
if (!config.enabled || !config.endpoint) return;
|
|
6135
6135
|
const machineId = await getMachineId();
|
|
6136
|
-
const version = true ? "0.9.
|
|
6136
|
+
const version = true ? "0.9.2" : "unknown";
|
|
6137
6137
|
await postReport(buildReport("error", err, context, machineId, version), config);
|
|
6138
6138
|
}
|
|
6139
6139
|
async function installCrashReporter() {
|
|
@@ -6142,7 +6142,7 @@ async function installCrashReporter() {
|
|
|
6142
6142
|
if (!config.enabled || !config.endpoint) return;
|
|
6143
6143
|
_installed = true;
|
|
6144
6144
|
const machineId = await getMachineId();
|
|
6145
|
-
const version = true ? "0.9.
|
|
6145
|
+
const version = true ? "0.9.2" : "unknown";
|
|
6146
6146
|
process.on("uncaughtException", (err) => {
|
|
6147
6147
|
void postReport(buildReport("fatal", err, { kind: "uncaughtException" }, machineId, version), config);
|
|
6148
6148
|
});
|
|
@@ -6157,7 +6157,7 @@ import { jsx as jsx38 } from "react/jsx-runtime";
|
|
|
6157
6157
|
var [, , command, arg] = process.argv;
|
|
6158
6158
|
async function runCli() {
|
|
6159
6159
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
6160
|
-
process.stdout.write("0.9.
|
|
6160
|
+
process.stdout.write("0.9.2\n");
|
|
6161
6161
|
return;
|
|
6162
6162
|
}
|
|
6163
6163
|
await ensureDataDirs();
|
|
@@ -6296,8 +6296,9 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
|
|
|
6296
6296
|
if (command === "install-brewbar") {
|
|
6297
6297
|
await useLicenseStore.getState().initialize();
|
|
6298
6298
|
const isPro = useLicenseStore.getState().isPro();
|
|
6299
|
-
const { installBrewBar } = await import("./brewbar-installer-
|
|
6299
|
+
const { installBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
|
|
6300
6300
|
try {
|
|
6301
|
+
console.log(t("cli_brewbarInstalling"));
|
|
6301
6302
|
await installBrewBar(isPro, arg === "--force");
|
|
6302
6303
|
console.log(t("cli_brewbarInstalled"));
|
|
6303
6304
|
} catch (err) {
|
|
@@ -6307,7 +6308,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
|
|
|
6307
6308
|
return;
|
|
6308
6309
|
}
|
|
6309
6310
|
if (command === "uninstall-brewbar") {
|
|
6310
|
-
const { uninstallBrewBar } = await import("./brewbar-installer-
|
|
6311
|
+
const { uninstallBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
|
|
6311
6312
|
try {
|
|
6312
6313
|
await uninstallBrewBar();
|
|
6313
6314
|
console.log(t("cli_brewbarUninstalled"));
|
|
@@ -6338,8 +6339,8 @@ async function ensureBrewBarRunning() {
|
|
|
6338
6339
|
if (process.platform !== "darwin") return;
|
|
6339
6340
|
await useLicenseStore.getState().initialize();
|
|
6340
6341
|
if (!useLicenseStore.getState().isPro()) return;
|
|
6341
|
-
const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-
|
|
6342
|
-
const { checkBrewBarVersion } = await import("./version-check-
|
|
6342
|
+
const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
|
|
6343
|
+
const { checkBrewBarVersion } = await import("./version-check-LHQYDFDA.js");
|
|
6343
6344
|
try {
|
|
6344
6345
|
if (!await isBrewBarInstalled()) {
|
|
6345
6346
|
console.log(t("cli_brewbarInstalling"));
|