| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| // The environment a Maka fixture window launches into. | |
| // | |
| // Extracted from apps/desktop/e2e/fixtures.ts, which worked it out first and | |
| // remains a consumer. Two things now launch fixture windows — the Playwright | |
| // E2E suite and the migration contract harness in scripts/ — and both need the | |
| // same isolation, so the rule lives in one place instead of being restated | |
| // (and, as the harness proved, restated wrongly). | |
| /** | |
| * Keys deleted from the inherited environment before launch. | |
| * | |
| * Deny-list rather than allow-list: Electron relies on undocumented platform | |
| * env (macOS CoreFoundation / X11 / sandbox session) that an allow-list would | |
| * silently drop and break the launch. | |
| * | |
| * Inheriting `process.env` wholesale leaks: | |
| * - provider keys, which auto-bootstrap a connection and change the fixture; | |
| * - `VITE_DEV_SERVER_URL`, which loads the dev server instead of the built | |
| * bundle — so the run silently measures something other than the build | |
| * under test; | |
| * - any `MAKA_E2E_*` the operator's shell happens to export, which silently | |
| * changes locale, platform, theme, or window visibility. | |
| * @param {string} key | |
| */ | |
| function isDeniedEnvKey(key) { | |
| return ( | |
| key === 'VITE_DEV_SERVER_URL' || | |
| key.startsWith('MAKA_E2E') || | |
| /_API_KEY$/.test(key) || | |
| /_API_TOKEN$/.test(key) || | |
| /_API_SECRET$/.test(key) | |
| ); | |
| } | |
| /** | |
| * @param {string} userDataDir | |
| * @param {string} homeDir | |
| * @param {{ | |
| * scenario?: string, | |
| * locale?: 'zh-CN' | 'zh-TW' | 'en', | |
| * platform?: 'darwin' | 'win32' | 'linux', | |
| * theme?: 'light' | 'dark', | |
| * timezone?: string, | |
| * showWindow?: boolean, | |
| * }} [options] | |
| * @returns {NodeJS.ProcessEnv} | |
| */ | |
| export function buildFixtureEnv(userDataDir, homeDir, options = {}) { | |
| const env = { ...process.env }; | |
| for (const key of Object.keys(env)) { | |
| if (isDeniedEnvKey(key)) delete env[key]; | |
| } | |
| env.MAKA_E2E = '1'; | |
| // The login-shell PATH probe must not make command resolution depend on the | |
| // developer or CI account. This builder owns the launched environment, so it | |
| // owns the deterministic skip flag (unlike relying on TERM, which is unset | |
| // under xvfb). | |
| env.MAKA_SKIP_SHELL_ENV = '1'; | |
| env.MAKA_E2E_USER_DATA_DIR = userDataDir; | |
| // Sandbox the home directory. User-scope skill discovery reads | |
| // `~/.maka/skills` and `~/.agents/skills` via `os.homedir()`, and the Skills | |
| // panel can now DELETE from those (#1517) — so without this a run would | |
| // enumerate, and could remove, the developer's own installed skills. | |
| // Overriding HOME sandboxes every consumer of the home dir at once, rather | |
| // than plumbing an override through each skills API and hoping none is | |
| // missed; `os.homedir()` returns $HOME on POSIX and %USERPROFILE% on | |
| // Windows, so both are set. userData is pinned separately above, so this | |
| // does not move the app's data dir. | |
| env.HOME = homeDir; | |
| env.USERPROFILE = homeDir; | |
| if (options.scenario) env.MAKA_E2E_FIXTURE = options.scenario; | |
| if (options.locale) env.MAKA_E2E_FIXTURE_LOCALE = options.locale; | |
| if (options.platform) env.MAKA_E2E_FIXTURE_PLATFORM = options.platform; | |
| if (options.theme) env.MAKA_E2E_FIXTURE_THEME = options.theme; | |
| // Absolute times render through the host zone, so a capture taken in | |
| // Asia/Shanghai reads as a diff when the same build is captured in UTC. The | |
| // fixture already owns an IANA override for exactly this; callers that | |
| // compare captures pin it, and the E2E suite does not, because its | |
| // assertions never read a wall-clock string. | |
| if (options.timezone) env.MAKA_E2E_FIXTURE_TIMEZONE = options.timezone; | |
| // Windows launch hidden so a run never steals the developer's focus; a | |
| // caller that needs the compositor (hit testing, real input) or a throttled | |
| // headless display (see isCiLinuxDisplay) asks for a visible window | |
| // explicitly. The decision stays with the caller: this builder is a pure | |
| // function of its arguments, so a test asserting "hidden run stays hidden" | |
| // means the same thing on a laptop and on a CI runner. | |
| if (options.showWindow) env.MAKA_E2E_SHOW_WINDOW = '1'; | |
| return env; | |
| } | |
| /** | |
| * Whether the current host is a Linux CI display, where a fixture window must | |
| * be shown even for captures: under xvfb a hidden window's compositor is | |
| * throttled to ~1fps — content-visibility turns never inflate and frame-paced | |
| * protocols crawl. Only that isolated virtual display gets a visible window; | |
| * nobody is watching it. | |
| * | |
| * This is the one ambient read the launch environment needs, kept out of | |
| * `buildFixtureEnv` so the builder stays a pure function. Callers compose it: | |
| * `showWindow: wantVisible || isCiLinuxDisplay()`. | |
| * | |
| * @param {NodeJS.ProcessEnv} [env] | |
| * @param {NodeJS.Platform} [platform] | |
| */ | |
| export function isCiLinuxDisplay(env = process.env, platform = process.platform) { | |
| return Boolean(env.CI) && platform === 'linux'; | |
| } | |
| /** | |
| * Extra Electron arguments a launch needs when its window will be revealed | |
| * inactively. | |
| * | |
| * Electron 43 defaults to native Wayland when XDG_SESSION_TYPE=wayland, where | |
| * BrowserWindow.showInactive() is unsupported — the window may simply not | |
| * appear, which puts back the ~1fps compositor throttling and the geometry | |
| * failures that asking for a visible window exists to avoid. Keep those | |
| * launches on XWayland; every other launch retains Electron's platform | |
| * default. | |
| * | |
| * Returns only the extra arguments, so each launcher composes it with its own: | |
| * `['.', ...inactiveWindowPlatformArgs(), `--user-data-dir=${dir}`]`. | |
| * | |
| * @param {NodeJS.ProcessEnv} [env] | |
| * @param {NodeJS.Platform} [platform] | |
| */ | |
| export function inactiveWindowPlatformArgs(env = process.env, platform = process.platform) { | |
| return platform === 'linux' && env.XDG_SESSION_TYPE?.toLowerCase() === 'wayland' | |
| ? ['--ozone-platform=x11'] | |
| : []; | |
| } |