diff --git a/scripts/termux/validate_installed.py b/scripts/termux/validate_installed.py index eaea44a772..7d6336e271 100644 --- a/scripts/termux/validate_installed.py +++ b/scripts/termux/validate_installed.py @@ -1,16 +1,13 @@ """Exercise a freshly installed Termux bundle with networking disabled.""" from __future__ import annotations -import fcntl import json import os from pathlib import Path -import pty import re import select import struct import subprocess -import termios import tempfile import time @@ -42,6 +39,10 @@ def stop_child_tree(child: subprocess.Popen) -> None: def tui_smoke(launcher: Path, env: dict[str, str], cwd: Path) -> None: + import fcntl + import pty + import termios + master, slave = pty.openpty() fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack("HHHH", 40, 120, 0, 0)) child = subprocess.Popen( diff --git a/tests-js/desktop-mac-usage-descriptions.test.ts b/tests-js/desktop-mac-usage-descriptions.test.ts index 62c606c70d..bd8e9c980e 100644 --- a/tests-js/desktop-mac-usage-descriptions.test.ts +++ b/tests-js/desktop-mac-usage-descriptions.test.ts @@ -1,7 +1,7 @@ /** * Regression for #54551: macOS Info.plist privacy usage descriptions * declared by the Desktop electron-builder config - * (`apps/desktop/package.json -> build.mac.extendInfo`) must pin every + * (`apps/desktop/electron-builder.config.cjs -> mac.extendInfo`) must pin every * `NS*UsageDescription` key the renderer relies on. * * Each entry is a key/value pair that lands in the packaged Hermes.app's @@ -20,7 +20,7 @@ * Why this test lives in tests-js/, not tests/*.py * ------------------------------------------------- * - * `AGENTS.md:1319-1329` requires assertions about `package.json` and JS-side + * `AGENTS.md` requires assertions about JS-side packaging * artifacts to live in the JS/Vitest suite: the CI change classifier can * skip Python coverage on a JS-only PR (the classifier's `python` lane is * skipped when all paths match `_FRONTEND` or `_PY_SKIP`, both of which @@ -47,51 +47,61 @@ */ import assert from 'node:assert/strict' -import fs from 'node:fs' +import { createRequire } from 'node:module' import path from 'node:path' import { test } from 'vitest' const REPO_ROOT = path.resolve(__dirname, '..') -const DESKTOP_PKG = path.join(REPO_ROOT, 'apps', 'desktop', 'package.json') + +const DESKTOP_CONFIG = path.join( + REPO_ROOT, + 'apps', + 'desktop', + 'electron-builder.config.cjs' +) + +const require = createRequire(import.meta.url) interface UsageDescriptionRow { key: string reason: string } -function desktopPkg(): Record { - assert.ok(fs.existsSync(DESKTOP_PKG), `missing ${DESKTOP_PKG}`) - - return JSON.parse(fs.readFileSync(DESKTOP_PKG, 'utf-8')) +interface DesktopBuilderMacConfig { + extendInfo?: object } -function extendInfo(): Record { - const pkg = desktopPkg() - const build = (pkg.build ?? {}) as Record - const mac = (build.mac ?? {}) as Record - assert.ok( - typeof mac.extendInfo === 'object' && - mac.extendInfo !== null && - !Array.isArray(mac.extendInfo), - 'build.mac.extendInfo is missing or invalid in apps/desktop/package.json' - ) - const extend = mac.extendInfo as Record +interface DesktopBuilderConfig { + mac?: DesktopBuilderMacConfig +} + +const desktopBuilderConfig: DesktopBuilderConfig = require(DESKTOP_CONFIG) + +function usageDescriptions(): Map { + const raw = desktopBuilderConfig.mac?.extendInfo + assert.ok( + typeof raw === 'object' && raw !== null && !Array.isArray(raw), + 'mac.extendInfo is missing or invalid in apps/desktop/electron-builder.config.cjs' + ) + const result = new Map() + + // electron-builder accepts arbitrary plist scalars in extendInfo, so only + // narrow the usage-description subset this contract owns. + for (const [key, value] of Object.entries(raw)) { + if (!key.startsWith('NS') || !key.endsWith('UsageDescription')) { + continue + } - // Narrow to Record with a runtime guard — the value type - // for NS*UsageDescription is string, but electron-builder's `extendInfo` - // accepts arbitrary plist scalars (bool, number, array, object) and we want - // a clean assertion error here, not a downstream `value.trim is not a - // function` crash in the whitespace test. - for (const [key, value] of Object.entries(extend)) { assert.equal( typeof value, 'string', - `\`${key}\` in build.mac.extendInfo must be a string (got ${typeof value})` + `\`${key}\` in mac.extendInfo must be a string (got ${typeof value})` ) + result.set(key, value) } - return extend as Record + return result } // Each entry: Info.plist key and a plain-language reason. Catches silent @@ -146,15 +156,14 @@ const EXPECTED_USAGE_DESCRIPTIONS: UsageDescriptionRow[] = [ ] test.each(EXPECTED_USAGE_DESCRIPTIONS)( - '`$key` is declared in build.mac.extendInfo', + '`$key` is declared in mac.extendInfo', ({ key, reason }) => { - const info = extendInfo() - const value = info[key] + const info = usageDescriptions() assert.ok( - value !== undefined, + info.has(key), `Info.plist privacy usage description \`${key}\` is missing from ` + - 'apps/desktop/package.json build.mac.extendInfo. macOS will surface ' + + 'apps/desktop/electron-builder.config.cjs mac.extendInfo. macOS will surface ' + 'a misleading system prompt or silently deny the related API.\n' + `Reason: ${reason}` ) @@ -162,13 +171,13 @@ test.each(EXPECTED_USAGE_DESCRIPTIONS)( ) test('every extendInfo value is free of leading/trailing whitespace and newlines', () => { - const info = extendInfo() + const info = usageDescriptions() - for (const [key, value] of Object.entries(info)) { + for (const [key, value] of info) { assert.equal( value, value.trim(), - `\`${key}\` in build.mac.extendInfo has leading/trailing whitespace: ` + + `\`${key}\` in mac.extendInfo has leading/trailing whitespace: ` + JSON.stringify(value) ) // electron-builder writes strings as-is; newlines would render as