☰
MooTool Next Compose 环境变量工具与 Electron 对齐:EnvEngine、会话恢复与 Vitest 对照实战
2026/10/16 2:29:45 网站建设 项目流程
  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载

环境变量工具(F08)是 MooTool 三款桌面产品线共有的核心系统工具,本文以next-compose/docs/fixtures/electron-next-envTools-vitest.md这份对照登记表为主干,系统梳理MooTool Next Compose如何把 Electron 版的「导出分区、previewDiff、环境文件更新、权限拒绝保持原文件、会话恢复」逐项搬进 Compose 桌面产品,并以单测(EnvEngineTest/EnvSessionMetadataTest/VariablesSessionRestoreTest)锁定行为。读完本文,你可以完整掌握 Compose 版环境变量引擎的存储路径、shell 钩子、原子写入、提权策略、导出格式与会话 wire 的来龙去脉,也能直接看懂这份对照表的每一行对应仓库中的哪段实现与测试。

一、这份对照表在讲什么

electron-next-envTools-vitest.md是next-compose/docs/fixtures/目录下的一张「Electron ↔ Compose 验证对照登记表」。它的作用不是复述 Electron 代码,而是给出一个可审计的契约:每一类行为,在 Electron 源文件里是什么、在 Compose 里由哪个测试证明等价。表内四条已落地登记 + 一条会话登记如下:

caseIdsourceProductsourceFileCompose 验证
export-sectionsMooTool Next Electronnext/src/features/variables/VariablesTool.tsxformatEnvironmentEnvEngineTest.formatExportUsesElectronSectionHeaders(EnvExportSections,运行时区为 Compose 产品名)
preview-diff同上编辑对话框variables.diff+ 保存前摘要EnvEngineTest.previewDiffDescribesScopeFileAndDelete
env-file-update同上next/src/shared/systemService.test.tsEnvEngineTest.updatesEnvironmentFileLikeElectronSystemServiceFixture(见 electron-next-systemService-vitest.md)
permission-denyCompose 行为—EnvEngineTest.systemWriteWithoutElevationKeepsOriginalFile
session-restoreCompose 会话tab/scope/query wireEnvSessionMetadataTest/VariablesSessionRestoreTest(DIFF-535)

末尾还登记了两个 diff 切片:DIFF-534(导出分区 + previewDiff 回归)与 DIFF-535(会话 metadata + 恢复)。也就是说,这张表就是 F08 在「Electron 参照 → Compose 实现 → 单测锁定」这条链路中的索引页。

二、先看 Electron 参照端:导出格式与系统服务

对照表前三行的 sourceFile 都指向 Electron 侧,理解它们才能看懂 Compose 对齐了什么。

2.1formatEnvironment的四段导出分区

Electron 的变量工具页在 VariablesTool.tsx 中定义formatEnvironment(snapshot):把快照按「持久用户 → 持久系统 → 当前进程 → Electron runtime」四个分区拼成一个文本,每个分区用一行------------标题分隔,条目格式为key=value:

function formatEnvironment(snapshot: EnvironmentSnapshot): string { return [ '------------Persistent user environment---------------', ...snapshot.user.map((entry) => `${entry.key}=${entry.value}`), '', '------------Persistent system environment---------------', ...snapshot.system.map((entry) => `${entry.key}=${entry.value}`), '', '------------Current process environment---------------', ...snapshot.environment.map((entry) => `${entry.key}=${entry.value}`), '', '------------Electron runtime---------------', ...snapshot.runtime.map((entry) => `${entry.key}=${entry.value}`) ].join('\n') }

exportValues()在 VariablesTool.tsx 通过window.mootool.saveTextFile把这段内容落盘,默认文件名为mootool-environment.txt。这就是 Compose 端EnvExportSections要对齐的「分区标题」。

2.2systemService.test.ts的环境文件更新链

对照表的env-file-update行指向 systemService.test.ts。该用例给定一段含注释、export JAVA_HOME='/old jdk'、OTHER="value"的文本,验证三件事:

  • updateEnvironmentContent(source, 'JAVA_HOME', "/opt/jdk's", true)把单引号值安全转义为'/opt/jdk'\''s',注释与其他行原样保留;
  • parseEnvironmentContent能解析回JAVA_HOME=/opt/jdk's与OTHER=value;
  • 传入value === undefined时删除该键,得到# keep this comment\nOTHER="value"\n。

这些纯函数位于 systemService.ts,配套的 IPC 边界校验normalizeEnvironmentVariableInput/normalizeDeleteEnvironmentVariableInput(同样在该测试文件中)负责在进入主进程前拒绝非法键名(如BAD-NAME)与含换行的非法值——两个产品线在这一层保持了同一套安全约束。

三、Compose 侧核心实现:EnvEngine 与 EnvExportSections

Compose 版把这些行为收敛进一个可单测的纯 Kotlin 引擎 EnvEngine.kt,配套类型EnvTab/EnvDisplayScope/EnvPersistScope/EnvErrorCode、快照与写入结果均在同一文件。

3.1 产品独立的路径与 shell 钩子

EnvStoreConfig.production(directories)(EnvEngine.kt)决定各平台路径:

平台用户文件系统文件shell profile
macOS数据目录data/environment/etc/zshenv~/.zshenv
Linux数据目录data/environment/etc/environment~/.profile
Windows数据目录data/environment空(走注册表)~/.unused-compose-env-profile

数据目录来自AppDirectories:macOS 为~/Library/Application Support/com.rememberber.mootool.next.compose/data,Linux 走 XDG 数据目录,Windows 为%APPDATA%/MooToolNextCompose/data(见 AppPaths.kt)。这一点与 Electron 有本质区别:Electron 写~/.MooTool/environment并在 profile 里插入# >>> MooTool environment >>>钩子;Compose 明确不读写~/.MooTool,其 shell 钩子常量是隔离标记:

const val SHELL_HOOK_BEGIN = "# >>> MooTool Next Compose environment >>>" const val SHELL_HOOK_END = "# <<< MooTool Next Compose environment <<<"

ensureShellHook(EnvEngine.kt)在 profile 里追加:

# >>> MooTool Next Compose environment >>> [ -f '<userFile>' ] && . '<userFile>' # <<< MooTool Next Compose environment <<<

正因为标记不同,两个产品线可以同时安装、互不覆盖。这一设计决策的完整理由见 DIFF-016:产品线独立要求自有路径与标记,规格禁止用进程 Map 冒充系统设置。

3.2 解析、序列化与安全校验

EnvEngine.parseContent用正则^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$匹配赋值行,unquote还原单双引号('\''转义、\"与\\反转义),未知行一律跳过但不删除。updateContent逐行扫描,把目标键的所有赋值替换为一条新的序列化行,其他行(含注释、非赋值文本)原样保留;若键不存在则追加,最后统一以\n结尾(EnvEngine.kt)。

序列化分两种模式(EnvEngine.kt):

// shellExport = true(用户作用域,或 macOS 的系统作用域) "export $key='${value.replace("'", "'\\''")}'" // 否则 "$key=\"${value.replace("\\", "\\\\").replace("\"", "\\\"")}\""

校验规则与 Electron 完全一致:键名必须匹配^[A-Za-z_][A-Za-z0-9_]*$;值长度上限 65535,且不得包含\0、\r、\n(validateName/validateValue,EnvEngine.kt)。非法输入抛EnvException并携带EnvErrorCode(INVALID_NAME/INVALID_VALUE等)。

3.3 导出分区与运行时信息

EnvExportSections(EnvEngine.kt)把四个分区标题抽成常量,其中前三个标题与 ElectronformatEnvironment逐字相同,第四个刻意不同:

object EnvExportSections { const val USER = "------------Persistent user environment---------------" const val SYSTEM = "------------Persistent system environment---------------" const val PROCESS = "------------Current process environment---------------" const val RUNTIME_COMPOSE = "------------Compose runtime---------------" }

formatExport(EnvEngine.kt)按 user → system → process → runtime 顺序输出。runtime 分区由runtimeEntries(EnvEngine.kt)生成:先是mootool.product(ProductIdentity.DISPLAY_NAME= "MooTool Next Compose",见 ProductIdentity.kt)、mootool.version、mootool.id,再是 JVM 与 OS 属性(java.version、os.name、user.home、file.encoding等)。Electron 的 runtime 区则显示process.versions.electron/chrome/node与process.cwd(见 systemService.ts)。Compose 没有 Electron 运行时字段,所以用产品名/JVM/OS 代替——这正是 DIFF-016 强调的「Electron 运行时字段在 JVM 桌面产品中没有对应物」。

3.4 写入流程:diff → 备份 → 原子写入 → 提权

EnvEngine.set/delete收敛到私有write(EnvEngine.kt),完整链路是:

  1. 校验键名与值;
  2. snapshot(config)取写入前快照,previewDiff生成「scope + 目标文件 + 旧值 → 新值/删除」摘要;
  3. 备份:若目标文件已存在,先复制到data/backups/environment/environment-<scope>-<UTC 时间戳>.bak(backupIfPresent);
  4. 更新内容后以tmp-<pid>临时文件 +ATOMIC_MOVE原子落盘;用户文件写完后把权限收窄为仅 owner 可读写(atomicWrite);
  5. 用户作用域:追加/校验 shell 钩子;macOS 额外尝试launchctl setenv/unsetenv(失败静默忽略);
  6. 系统作用域:直接写,若抛权限类 IOException 且允许提权,则走writeElevated——macOS 用osascript do shell script ... with administrator privileges,Linux 用pkexec复制并chmod 644(EnvEngine.kt);若allowElevation = false,则抛出EnvErrorCode.PERMISSION;
  7. 返回写入后快照 + 备份路径 + diff。

注意第 4 步的关键差异:Compose 绝不改写当前 JVM 的System.getenv()。Electron 在写完后会通过syncCurrentProcessEnvironment把process.env[key]原地更新(systemService.ts),Compose 则明确拒绝这种行为,理由见 DIFF-016:「规格禁止用进程 Map 冒充系统设置」。用户保存的用户/系统变量只在重新登录、新终端或已加载 Compose shell 钩子的会话中生效。

Windows 分支单独走 PowerShell:用户作用域[Environment]::SetEnvironmentVariable(...,[EnvironmentVariableTarget]::User),系统作用域通过Start-Process -Verb RunAs提权;读取则用GetEnvironmentVariables+ Sort + UTF-8 输出解析(readWindows/writeWindows,EnvEngine.kt)。该分支在本机(Intel macOS)未实测,属于已知限制。

四、四条对照登记逐一拆解

下面把对照表每一行映射到 Compose 单测 EnvEngineTest.kt 的具体断言,证明「登记 ≠ 口头承诺,测试确实锁住了行为」。

4.1 export-sections:formatExportUsesElectronSectionHeaders

EnvEngineTest.kt 构造一个含 user/system/process/runtime 四项的快照,断言EnvEngine.formatExport输出依次包含EnvExportSections.USER、SYSTEM、PROCESS、RUNTIME_COMPOSE,条目呈现为U=1、PATH=/bin,并且不包含"Electron runtime"。这条测试锁死两件事:分区标题与 Electron 对齐(前三段逐字一致),以及运行时区必须使用 Compose 产品名而非 Electron 字样。

4.2 preview-diff:previewDiffDescribesScopeFileAndDelete

EnvEngineTest.kt 验证EnvEngine.previewDiff的输出结构:更新时包含作用域名(user)、目标文件(/data/environment)、旧值(FOO: bar)与箭头(→ baz);删除一个不存在的键时输出MISSING: (absent)与→ (delete)。该摘要正是VariablesScreen编辑对话框里variables.diff区域展示的保存前预览(见 VariablesScreen.kt),对应 Electron 编辑对话框中同样的variables.diff+ 保存前摘要。

4.3 env-file-update:updatesEnvironmentFileLikeElectronSystemServiceFixture

EnvEngineTest.kt 使用与 ElectronsystemService.test.ts完全相同的输入文本(# keep this comment+export JAVA_HOME='/old jdk'+OTHER="value"),验证:

  • 更新含单引号的值时,输出export JAVA_HOME='/opt/jdk'\''s',注释与其他行原样保留;
  • EnvEngine.parseContent可解析回两个条目;
  • 以value = null调用时删除该键,保留注释。

updatesEnvironmentFileLikeElectronSystemServiceFixture这个名字直接表明它是「对齐 Electron fixture」的回归测试;对照表的env-file-update行同时挂到 electron-next-systemService-vitest.md,后者把env-update一行也登记为同一个测试。配套用例parsesAndUpdatesWhileKeepingUnknownLines(EnvEngineTest.kt)还覆盖了重复赋值取最后一个、非赋值文本保留、单引号值往返、非法键名1ABC抛INVALID_NAME、含换行的值抛INVALID_VALUE等边界。

userFileRoundTripCreatesBackupAndDoesNotMutateProcessMap(EnvEngineTest.kt)是这条链路的完整闭环:真实写临时文件 → 断言备份文件存在 → 断言.zshenv里含SHELL_HOOK_BEGIN且不含 Electron 标记# >>> MooTool environment >>>→ 断言System.getenv()前后逐项相等(进程 Map 未被改写)→ 断言 runtime 含java.version与mootool.product=…Compose…→ 删除后导出文本含"Compose runtime"且不含"Electron runtime"。

4.4 permission-deny:systemWriteWithoutElevationKeepsOriginalFile

这是表格中sourceProduct标为「Compose 行为」的一行,代表 Compose 自己的新增约束,没有 Electron 参照。测试 EnvEngineTest.kt 构造一个setWritable(false)的锁目录,以allowElevation = false写入系统作用域,断言:抛出EnvException且code == EnvErrorCode.PERMISSION、目标文件不存在(没有创建半成品)、随后snapshot(config).system仍为空(原文件保持原状)。这印证了 DIFF-016 中「系统作用域在无权限时按需提权;拒绝时保持原文件」的实现承诺。

五、会话恢复:session-restore 登记的 tab/scope/query wire

对照表最后一行session-restore不指向 Electron,而是 Compose 自己的会话子系统,登记于 DIFF-535:F08 缺与 F11/F15 同级的可单测 metadata/restore 模块,本轮补上。

5.1 EnvSessionMetadata:wire 规范化

EnvSessionMetadata.kt 定义了持久化到会话 JSON 的 wire 值常量:environment/runtime(tab),user/system/process(scope)。normalizeTab把任何未知输入回退为environment;normalizeScope把未知输入回退为process。测试 EnvSessionMetadataTest.kt 覆盖大小写归一("Runtime"→runtime、"User"→user)与空串回退(""→process)。

5.2 EnvSessionRestore + VariablesSession:重载即恢复

VariablesSession(SessionManager.kt)持有tab/scope/query及 transient 状态(snapshot、loading、saving、error、notice、editor 字段、lastBackup、lastDiff)。EnvSessionRestore.apply(EnvSessionRestore.kt)把 wire 字符串映射回枚举,并重置全部 transient 状态:snapshot = null、loading = false、saving = false、error = ""、notice = ""、editorOpen = false、deleteKey = ""、lastBackup = ""、lastDiff = ""。

会话的读写由SessionManager.variablesSession()/persistVariables()承担:加载时从 store 反序列化VariablesSessionSnapshot(tab/scope/query三个字段,均带默认值),保存时序列化当前状态(SessionManager.kt)。VariablesScreen在切 Tab、切 scope、输入搜索词、增删改时都会调用persist(),确保 UI 状态持续落盘。

测试 VariablesSessionRestoreTest.kt 把 session 置为 Runtime/System/PATH/已载入快照/loading/error,然后restore(VariablesSessionSnapshot("environment", "user", "JAVA")),断言恢复为EnvTab.Environment、EnvDisplayScope.User、query = "JAVA"、snapshot == null、loading == false、error == ""。

5.3 会话链路与安全边界

DIFF-535明确说明 F08 是「无通用历史」工具:不把变量值写入会话 JSON 或通用历史,VariablesSessionSnapshot只含 tab/scope/query,避免密钥类环境变量进入 SQLite 或历史库(见 DIFF-016「受影响范围」与 feature-parity.md 中「variables 不声明通用历史」的注册说明)。相比之下,EnvSnapshot这类临时快照只在内存中刷新(VariablesScreen.refresh走EnvWiringPresentation.runSnapshot,见 EnvWiringPresentation.kt)。

六、从表格到运行:如何查看、构建与验证

本产品是独立的 Compose Multiplatform Desktop 工程(见 README.md),与 Electron 版互不依赖,需 JDK 21。开发运行与测试:

cd next-compose export JAVA_HOME="$(/usr/libexec/java_home -v 21)" # macOS;Linux 请指向已安装的 JDK 21 ./scripts/check-toolchain.sh ./scripts/check-core.sh MOOTOOL_COMPOSE_DATA_DIR="$PWD/local.properties.d/dev-profile" ./scripts/run.sh

跑 F08 相关单测(EnvEngineTest、EnvSessionMetadataTest、VariablesSessionRestoreTest):

./gradlew :composeApp:desktopTest --offline

测试结果在composeApp/build/reports/tests/desktopTest/index.html可查看。三个 diff 文档各自记录过完整运行结论:F08 验收当轮desktopTest100/100(见 2026-09-09-f08/results.md),DIFF-534 记为841/841,DIFF-535 记为851/851(均为 JDK 21、offline 运行,另有默认跳过的公网 GET/multipart smoke)。这些是开发切片快照而非发布保证,具体以你本地运行结果为准。

界面侧:运行后在工具列表(⌘/Ctrl+K)中进入「环境变量」,顶部 Tab 为 Environment/Runtime,Environment 下有 user/system/process 作用域切换,编辑对话框中展示variables.diff保存前摘要。Compose 导出默认文件名为mootool-next-compose-environment.txt(见 VariablesScreen.kt),区别于 Electron 的mootool-environment.txt。

七、局限与后续切片

从 DIFF-016「受影响范围」与 results.md「未测」可以明确列出本功能的已知边界:

  • 新终端不会自动看到用户变量,除非已加载 Compose shell 钩子;macOS 会额外尝试launchctl setenv(失败忽略);
  • 系统文件在 macOS 为/etc/zshenv、Linux 为/etc/environment,但并非所有发行版的唯一全局环境来源;
  • Windows 用户/系统走 PowerShell 注册表目标,在 Intel macOS 本机未实测;
  • 不把变量值写入会话 JSON 或通用历史(防密钥进 SQLite);
  • 未做项还包括窗口截图、系统提权对话框的界面验收,以及新终端实际加载钩子的手工验证。

这些边界在后续切片(如 P7 三平台安装验收)中才会逐步收敛,阅读对照表与 DIFF 记录时应一并留意。

八、小结:一份可审计的 parity 登记

electron-next-envTools-vitest.md这类 fixture 的价值在于:它把「Electron 参照、Compose 实现、单测锁定」三件事压进同一张表。EnvEngineTest用与 Electron 相同的 fixture 文本验证解析/序列化兼容,用formatExportUsesElectronSectionHeaders锁定分区标题逐字对齐,用systemWriteWithoutElevationKeepsOriginalFile记录 Compose 自己的权限语义;EnvSessionMetadataTest/VariablesSessionRestoreTest则覆盖会话 wire 与恢复。如果你要为本产品新增功能或回归检查 F08,正确姿势是:先读这张对照表定位 caseId,再打开EnvEngineTest对应的测试方法,最后回看 DIFF-534、DIFF-535 与 DIFF-016 的行为描述。这条从「文档登记 → 测试断言 → diff 决策」的证据链,正是整个 next-compose 切片工程可复现、可审计的研发方式的缩影。

  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载
上一篇:开源制造执行系统openMES:企业数字化转型的智能引擎
下一篇:数字记忆守护者:5分钟拯救你的B站缓存视频

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询