brew-tui 1.2.2 → 1.2.3
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/build/{brewbar-installer-BKE6Z7OI.js → brewbar-installer-2OHLRM27.js} +33 -1
- package/build/brewbar-installer-2OHLRM27.js.map +1 -0
- package/build/index.js +8 -8
- package/build/{version-check-FKY5HGSI.js → version-check-NS6RPUSU.js} +2 -2
- package/package.json +3 -2
- package/build/brewbar-installer-BKE6Z7OI.js.map +0 -1
- /package/build/{version-check-FKY5HGSI.js.map → version-check-NS6RPUSU.js.map} +0 -0
|
@@ -27,6 +27,30 @@ async function isBrewBarInstalled() {
|
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
async function isBrewBarRunning() {
|
|
31
|
+
if (process.platform !== "darwin") return false;
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await execFileAsync("pgrep", ["-x", "BrewBar"]);
|
|
34
|
+
return stdout.trim().length > 0;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function quitBrewBar() {
|
|
40
|
+
if (process.platform !== "darwin") return;
|
|
41
|
+
try {
|
|
42
|
+
await execFileAsync("osascript", ["-e", 'tell application "BrewBar" to quit']);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
for (let i = 0; i < 15; i++) {
|
|
46
|
+
if (!await isBrewBarRunning()) return;
|
|
47
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await execFileAsync("pkill", ["-x", "BrewBar"]);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
30
54
|
async function installBrewBar(isPro, force = false) {
|
|
31
55
|
if (process.platform !== "darwin") {
|
|
32
56
|
throw new Error(t("cli_brewbarMacOnly"));
|
|
@@ -94,6 +118,10 @@ async function installBrewBar(isPro, force = false) {
|
|
|
94
118
|
});
|
|
95
119
|
throw new Error(t("cli_brewbarDownloadFailed", { error: "SHA-256 checksum unavailable \u2014 cannot verify download integrity" }));
|
|
96
120
|
}
|
|
121
|
+
const wasRunning = await isBrewBarRunning();
|
|
122
|
+
if (wasRunning) {
|
|
123
|
+
await quitBrewBar();
|
|
124
|
+
}
|
|
97
125
|
if (force && await isBrewBarInstalled()) {
|
|
98
126
|
await rm(BREWBAR_APP_PATH, { recursive: true, force: true });
|
|
99
127
|
}
|
|
@@ -105,6 +133,9 @@ async function installBrewBar(isPro, force = false) {
|
|
|
105
133
|
await rm(TMP_ZIP, { force: true }).catch(() => {
|
|
106
134
|
});
|
|
107
135
|
}
|
|
136
|
+
if (wasRunning) {
|
|
137
|
+
await launchBrewBar();
|
|
138
|
+
}
|
|
108
139
|
}
|
|
109
140
|
async function launchBrewBar() {
|
|
110
141
|
if (process.platform !== "darwin") return;
|
|
@@ -123,7 +154,8 @@ async function uninstallBrewBar() {
|
|
|
123
154
|
export {
|
|
124
155
|
installBrewBar,
|
|
125
156
|
isBrewBarInstalled,
|
|
157
|
+
isBrewBarRunning,
|
|
126
158
|
launchBrewBar,
|
|
127
159
|
uninstallBrewBar
|
|
128
160
|
};
|
|
129
|
-
//# sourceMappingURL=brewbar-installer-
|
|
161
|
+
//# sourceMappingURL=brewbar-installer-2OHLRM27.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\n/// Returns true if the BrewBar process is currently running.\n/// Used by the installer to decide whether to quit + relaunch after an update.\nexport async function isBrewBarRunning(): Promise<boolean> {\n if (process.platform !== 'darwin') return false;\n try {\n const { stdout } = await execFileAsync('pgrep', ['-x', 'BrewBar']);\n // pgrep prints one PID per line when it finds matches; stdout vacío == no proceso.\n return stdout.trim().length > 0;\n } catch {\n // pgrep exits 1 when no match; that means \"not running\", not a failure.\n return false;\n }\n}\n\n/// Asks BrewBar to quit gracefully (LSUIElement → no dialogs), then falls back\n/// to pkill if it hasn't exited within 3 s. Required before reemplazar el bundle:\n/// `ditto -xk` sobre una app en ejecución deja un BrewBar viejo con un Info.plist\n/// nuevo, lo cual confunde el monitor de last-action y los watchers FSEvents.\nasync function quitBrewBar(): Promise<void> {\n if (process.platform !== 'darwin') return;\n try {\n await execFileAsync('osascript', ['-e', 'tell application \"BrewBar\" to quit']);\n } catch {\n /* osascript falla si la app no está registrada; pasamos a pkill */\n }\n for (let i = 0; i < 15; i++) {\n if (!(await isBrewBarRunning())) return;\n await new Promise((r) => setTimeout(r, 200));\n }\n try {\n await execFileAsync('pkill', ['-x', 'BrewBar']);\n } catch {\n /* nada que matar */\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 // Si BrewBar está corriendo, cerrarla antes de tocar el bundle. Sin esto\n // `ditto -xk` sobreescribe los recursos de un proceso vivo y la app queda\n // en estado degradado hasta el próximo lanzamiento.\n const wasRunning = await isBrewBarRunning();\n if (wasRunning) {\n await quitBrewBar();\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 // Si BrewBar estaba corriendo antes de la actualización, relanzarla para\n // que el usuario vuelva a ver el ícono en la menubar sin pasos manuales.\n if (wasRunning) {\n await launchBrewBar();\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
|
@@ -4668,7 +4668,7 @@ function AccountView() {
|
|
|
4668
4668
|
status === "pro" || status === "team" || status === "expired" ? `v ${t("hint_revalidate")} ` : "",
|
|
4669
4669
|
revalidating ? t("account_revalidating") : "",
|
|
4670
4670
|
" ",
|
|
4671
|
-
t("app_version", { version: "1.2.
|
|
4671
|
+
t("app_version", { version: "1.2.3" })
|
|
4672
4672
|
] }) })
|
|
4673
4673
|
] });
|
|
4674
4674
|
}
|
|
@@ -6176,7 +6176,7 @@ async function reportError(err, context = {}) {
|
|
|
6176
6176
|
const config = await resolveConfig();
|
|
6177
6177
|
if (!config.enabled || !config.endpoint) return;
|
|
6178
6178
|
const machineId = await getMachineId();
|
|
6179
|
-
const version = true ? "1.2.
|
|
6179
|
+
const version = true ? "1.2.3" : "unknown";
|
|
6180
6180
|
await postReport(buildReport("error", err, context, machineId, version), config);
|
|
6181
6181
|
}
|
|
6182
6182
|
async function installCrashReporter() {
|
|
@@ -6185,7 +6185,7 @@ async function installCrashReporter() {
|
|
|
6185
6185
|
if (!config.enabled || !config.endpoint) return;
|
|
6186
6186
|
_installed = true;
|
|
6187
6187
|
const machineId = await getMachineId();
|
|
6188
|
-
const version = true ? "1.2.
|
|
6188
|
+
const version = true ? "1.2.3" : "unknown";
|
|
6189
6189
|
process.on("uncaughtException", (err) => {
|
|
6190
6190
|
void postReport(buildReport("fatal", err, { kind: "uncaughtException" }, machineId, version), config);
|
|
6191
6191
|
});
|
|
@@ -6200,7 +6200,7 @@ import { jsx as jsx39 } from "react/jsx-runtime";
|
|
|
6200
6200
|
var [, , command, arg] = process.argv;
|
|
6201
6201
|
async function runCli() {
|
|
6202
6202
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
6203
|
-
process.stdout.write("1.2.
|
|
6203
|
+
process.stdout.write("1.2.3\n");
|
|
6204
6204
|
return;
|
|
6205
6205
|
}
|
|
6206
6206
|
await ensureDataDirs();
|
|
@@ -6339,7 +6339,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
|
|
|
6339
6339
|
if (command === "install-brewbar") {
|
|
6340
6340
|
await useLicenseStore.getState().initialize();
|
|
6341
6341
|
const isPro = useLicenseStore.getState().isPro();
|
|
6342
|
-
const { installBrewBar } = await import("./brewbar-installer-
|
|
6342
|
+
const { installBrewBar } = await import("./brewbar-installer-2OHLRM27.js");
|
|
6343
6343
|
try {
|
|
6344
6344
|
console.log(t("cli_brewbarInstalling"));
|
|
6345
6345
|
await installBrewBar(isPro, arg === "--force");
|
|
@@ -6351,7 +6351,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
|
|
|
6351
6351
|
return;
|
|
6352
6352
|
}
|
|
6353
6353
|
if (command === "uninstall-brewbar") {
|
|
6354
|
-
const { uninstallBrewBar } = await import("./brewbar-installer-
|
|
6354
|
+
const { uninstallBrewBar } = await import("./brewbar-installer-2OHLRM27.js");
|
|
6355
6355
|
try {
|
|
6356
6356
|
await uninstallBrewBar();
|
|
6357
6357
|
console.log(t("cli_brewbarUninstalled"));
|
|
@@ -6382,8 +6382,8 @@ async function ensureBrewBarRunning() {
|
|
|
6382
6382
|
if (process.platform !== "darwin") return;
|
|
6383
6383
|
await useLicenseStore.getState().initialize();
|
|
6384
6384
|
if (!useLicenseStore.getState().isPro()) return;
|
|
6385
|
-
const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-
|
|
6386
|
-
const { checkBrewBarVersion } = await import("./version-check-
|
|
6385
|
+
const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-2OHLRM27.js");
|
|
6386
|
+
const { checkBrewBarVersion } = await import("./version-check-NS6RPUSU.js");
|
|
6387
6387
|
try {
|
|
6388
6388
|
if (!await isBrewBarInstalled()) {
|
|
6389
6389
|
console.log(t("cli_brewbarInstalling"));
|
|
@@ -6,7 +6,7 @@ var execFileAsync = promisify(execFile);
|
|
|
6
6
|
var BREWBAR_INFO_PLIST = "/Applications/BrewBar.app/Contents/Info.plist";
|
|
7
7
|
var CONTRACT_VERSION = 1;
|
|
8
8
|
function expectedVersion() {
|
|
9
|
-
return "1.2.
|
|
9
|
+
return "1.2.3";
|
|
10
10
|
}
|
|
11
11
|
async function readBrewBarVersion() {
|
|
12
12
|
try {
|
|
@@ -61,4 +61,4 @@ export {
|
|
|
61
61
|
expectedVersion,
|
|
62
62
|
readBrewBarVersion
|
|
63
63
|
};
|
|
64
|
-
//# sourceMappingURL=version-check-
|
|
64
|
+
//# sourceMappingURL=version-check-NS6RPUSU.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brew-tui",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Brew-TUI — Visual TUI for Homebrew package management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"lint": "eslint src/",
|
|
31
31
|
"typecheck": "tsc --noEmit",
|
|
32
32
|
"validate": "npm run typecheck && npm run test && npm run build && npm run lint",
|
|
33
|
-
"
|
|
33
|
+
"check:brewbar-release": "node scripts/check-brewbar-release.mjs",
|
|
34
|
+
"prepublishOnly": "npm run validate && npm run check:brewbar-release",
|
|
34
35
|
"prepare": "husky"
|
|
35
36
|
},
|
|
36
37
|
"keywords": [
|
|
@@ -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/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":[]}
|
|
File without changes
|