Skip to content

feat(env): isolate dev/release symlinks with getEnvAwareName#1364

Merged
IceyLiu merged 2 commits intomainfrom
zynx/feat/symlink-env-isolation
Mar 17, 2026
Merged

feat(env): isolate dev/release symlinks with getEnvAwareName#1364
IceyLiu merged 2 commits intomainfrom
zynx/feat/symlink-env-isolation

Conversation

@piorpua
Copy link
Copy Markdown
Contributor

@piorpua piorpua commented Mar 17, 2026

Summary

  • Introduce src/common/appEnv.ts with getEnvAwareName(baseName) to centralise dev/release suffix logic — dev builds append -dev, release builds are unchanged
  • Dev builds now use ~/.aionui-dev / ~/.aionui-config-dev instead of sharing ~/.aionui / ~/.aionui-config with release builds, preventing the two environments from clobbering each other's symlinks when alternating startup
  • Apply getEnvAwareName consistently across all three call sites: getDataPath, getConfigPath, getUserExtensionsDir, and resolveStatesFile

Test plan

  • Run bun run test — all 589 tests pass
  • Run bunx tsc --noEmit — no type errors
  • Run bun run lint — no lint errors
  • Start dev build, verify ~/.aionui-dev symlink points to .../AionUi-Dev/aionui
  • Verify ~/.aionui-config-dev symlink points to .../AionUi-Dev/config
  • Verify ~/.aionui and ~/.aionui-config are not created or modified by dev build
  • Start release build, verify ~/.aionui and ~/.aionui-config are unaffected by previous dev run

Introduce getEnvAwareName() in src/common/appEnv.ts to centralise the
dev/release suffix logic. Dev builds now use ~/.aionui-dev and
~/.aionui-config-dev instead of sharing ~/.aionui and ~/.aionui-config
with release builds.

Previously, alternating between dev and release would cause
ensureCliSafeSymlink to detect a target mismatch, delete the existing
symlink and recreate it, so the two environments would clobber each
other's symlinks. Each environment now owns its own symlink name.

Files changed:
- src/common/appEnv.ts: new helper getEnvAwareName(baseName)
- src/process/utils.ts: getDataPath / getConfigPath use getEnvAwareName
- src/extensions/constants.ts: getUserExtensionsDir uses getEnvAwareName
- src/extensions/statePersistence.ts: resolveStatesFile uses getEnvAwareName
- tests/unit/extensions/extensionLoader.test.ts: add electron mock and
  update expected paths to .aionui-dev
@piorpua
Copy link
Copy Markdown
Contributor Author

piorpua commented Mar 17, 2026

Code Review:feat(env): isolate dev/release symlinks with getEnvAwareName (#1364)

变更概述

本 PR 新增 src/common/appEnv.ts,引入 getEnvAwareName(baseName) 函数集中管理 dev/release 环境后缀逻辑(dev 构建追加 -dev,release 不变)。将该函数应用到 getDataPathgetConfigPathgetUserExtensionsDirresolveStatesFile 四处调用点,使 dev 构建使用独立的 ~/.aionui-dev~/.aionui-config-dev 目录,彻底隔离两个环境的 symlink 冲突问题。


方案评估

结论:✅ 方案合理

将环境判断逻辑抽象为单一函数并统一应用于所有路径构造点,是正确的 DRY 做法,与项目现有架构(main process + extensions 都使用 Electron app API)一致。app.isPackaged 是 Electron 官方推荐的 dev/release 区分方式,没有引入不必要的复杂度。


问题清单

🟡 MEDIUM — getEnvAwareName 缺少独立单元测试,release 分支行为未被验证

文件src/common/appEnv.ts

问题说明getEnvAwareName 是本 PR 的核心逻辑,但没有对应的测试文件。extensionLoader.test.ts 中虽然设置了 isPackaged: false(dev 分支),但 isPackaged: true(release 分支,即函数返回原始 baseName 的路径)从未被任何测试覆盖。若将来不小心改动逻辑(如条件取反),release 构建路径会静默出错。

AGENTS.md 要求:新增功能必须包含对应的测试用例。

修复建议:新增 tests/unit/common/appEnv.test.ts

import { describe, expect, it, vi } from 'vitest';

vi.mock('electron', () => ({ app: { isPackaged: false } }));

describe('getEnvAwareName', () => {
  it('appends -dev suffix in dev builds', async () => {
    const { getEnvAwareName } = await import('../../../src/common/appEnv');
    expect(getEnvAwareName('.aionui')).toBe('.aionui-dev');
    expect(getEnvAwareName('.aionui-config')).toBe('.aionui-config-dev');
  });

  it('returns baseName unchanged in release builds', async () => {
    vi.resetModules();
    vi.doMock('electron', () => ({ app: { isPackaged: true } }));
    const { getEnvAwareName } = await import('../../../src/common/appEnv');
    expect(getEnvAwareName('.aionui')).toBe('.aionui');
    expect(getEnvAwareName('.aionui-config')).toBe('.aionui-config');
  });
});

🔵 LOW — src/common/appEnv.ts 未加入 vitest.config.tscoverage.include

文件vitest.config.ts,第 52–85 行

问题说明:AGENTS.md 明确要求"新增的源文件未加入 vitest.config.tscoverage.include"需指出。appEnv.ts 新增后未添加到覆盖率追踪列表,导致上述缺失测试无法被覆盖率报告检测到。

修复建议:在 coverage.include// Common 区块添加:

// Common
'src/common/chatLib.ts',
'src/common/update/models/VersionInfo.ts',
'src/common/appEnv.ts',   // ← 新增

🔵 LOW — statePersistence.ts JSDoc 注释描述的路径已过时

文件src/extensions/statePersistence.ts,第 28 行

问题代码

 * Stored at ~/.aionui/extension-states.json by default.

问题说明:本 PR 之后,dev 构建中该文件实际存储在 ~/.aionui-dev/extension-states.json,注释描述不再准确,容易误导阅读者。

修复建议

 * Stored at ~/.aionui/extension-states.json (release) or
 * ~/.aionui-dev/extension-states.json (dev) by default.

汇总

# 严重级别 文件 问题
1 🟡 MEDIUM src/common/appEnv.ts 缺少单元测试,release 分支未覆盖
2 🔵 LOW vitest.config.ts:52 appEnv.ts 未加入 coverage.include
3 🔵 LOW src/extensions/statePersistence.ts:28 JSDoc 注释路径描述未更新

结论

⚠️ 有条件批准 — 核心实现方案合理,代码质量良好,getEnvAwareName 抽象干净。建议补充 appEnv.ts 的单元测试(特别是 release 分支)并更新 coverage.include,处理后可合并。


本报告由本地 /pr-review 命令生成,包含完整项目上下文,无截断限制。

On Windows, resolveForComparison skipped fs.realpathSync entirely,
so symlinks pointing outside allowed directories were not caught by
isPathAllowed. Move the realpathSync call to operate on the raw input
path before pathApi.resolve(), making symlink safety checks consistent
across all platforms.

Also add unit tests for common/appEnv and include appEnv.ts in
coverage tracking, plus update the statePersistence JSDoc to reflect
both release and dev storage paths.
@IceyLiu IceyLiu merged commit 4682c29 into main Mar 17, 2026
15 of 17 checks passed
@piorpua piorpua deleted the zynx/feat/symlink-env-isolation branch March 17, 2026 10:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants