mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 19:47:08 +08:00
chore: 补充
This commit is contained in:
@@ -56,6 +56,7 @@ TraceMemo 有两种不同的接入方式。微信机器人是普通用户可以
|
|||||||
- [macOS 数据访问说明](./platform/macos.md)
|
- [macOS 数据访问说明](./platform/macos.md)
|
||||||
- [开发、测试与构建](./development/overview.md)
|
- [开发、测试与构建](./development/overview.md)
|
||||||
- [本地启动排障](./development/local-startup-troubleshooting.md)
|
- [本地启动排障](./development/local-startup-troubleshooting.md)
|
||||||
|
- [v2.2.0 品牌升级与本地数据兼容](./agent/release-notes-v2.2.0.md)
|
||||||
- [v2.1.9 API 鉴权迁移说明](./agent/release-notes-v2.1.9.md)
|
- [v2.1.9 API 鉴权迁移说明](./agent/release-notes-v2.1.9.md)
|
||||||
|
|
||||||
当前工作区版本:**2.2.0**。文档只描述当前代码已经实现的能力;版本兼容性、AI Provider 行为和媒体读取结果可能随系统、微信客户端和服务商变化。
|
当前工作区版本:**2.2.0**。文档只描述当前代码已经实现的能力;版本兼容性、AI Provider 行为和媒体读取结果可能随系统、微信客户端和服务商变化。
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# TraceMemo 2.2.0:品牌升级与本地数据兼容
|
||||||
|
|
||||||
|
2.2.0 将用户可见品牌从 WechatExplorer 升级为 TraceMemo(迹忆)。这次升级不搬迁或复制用户的 Application Support 数据;macOS 和 Windows 上的旧用户会继续使用原有数据根,以避免设置、密钥和本地索引失联。
|
||||||
|
|
||||||
|
## 升级后继续保留的内容
|
||||||
|
|
||||||
|
- 微信数据库连接设置和其他用户设置;
|
||||||
|
- AI Provider、模型配置和已加密 API Key;
|
||||||
|
- Local HTTP API Token;
|
||||||
|
- 微信数据库 Key 和图片解密 Key;
|
||||||
|
- Knowledge 本地索引,包括现有 SQLite、WAL 和 SHM 文件;
|
||||||
|
- 报告历史、防撤回归档和图片理解结果;
|
||||||
|
- Renderer Local Storage 和其他 Chromium session 数据;
|
||||||
|
- `~/.wechatexplorer/wechat-connector/accounts` 中的 Agent Hub 登录凭据。
|
||||||
|
|
||||||
|
应用启动时会在任何业务模块读取 Electron 路径或使用 `safeStorage` 之前选择数据根,并将 `userData` 与 `sessionData` 设置为同一目录:
|
||||||
|
|
||||||
|
- `WechatExplorer` 或 v2.1.9 使用的小写 `wechatexplorer` 目录包含有效用户资产时,继续使用对应旧目录;
|
||||||
|
- 只有 TraceMemo 新目录包含有效用户资产时,使用新目录;
|
||||||
|
- 多个旧目录或新旧目录同时包含有效用户资产时,不复制、不合并、不覆盖;两个大小写 legacy 目录均有效时确定性优先 `WechatExplorer` 并写入诊断日志;
|
||||||
|
- 两边都没有有效用户资产时,全新安装使用 TraceMemo 新目录。
|
||||||
|
|
||||||
|
仅有 `Local State`、Cache、Code Cache 或 GPUCache 等运行时文件,不会被判断为有效用户资产。
|
||||||
|
|
||||||
|
## 有意保留的兼容标识
|
||||||
|
|
||||||
|
内部 Electron runtime identity 在 macOS 上继续使用 `WechatExplorer`。前者用于兼容旧 `safeStorage` 密文,后者同时承担旧 `safeStorage` 与 WCDB 运行时兼容;它们都不是未完成的品牌替换。
|
||||||
|
|
||||||
|
以下历史标识也继续保留:
|
||||||
|
|
||||||
|
- bundle/app identifier:`com.wechatexplorer.app`;
|
||||||
|
- Reader Skill 目录和标识:`wechatexplorer-reader`;
|
||||||
|
- Agent 环境变量:`WECHATEXPLORER_API_TOKEN`;
|
||||||
|
- Agent Hub 凭据目录:`~/.wechatexplorer`;
|
||||||
|
- GitHub 仓库地址中的 `WechatExplorer`。
|
||||||
|
|
||||||
|
Local HTTP API 的 endpoint、默认端口 `6131`、Bearer Token 格式、加密方式和 rotation 行为没有因为品牌升级而改变。旧 Token 文件会从旧数据根继续读取,不会仅因升级而重新生成。
|
||||||
|
|
||||||
|
## Knowledge 与日志
|
||||||
|
|
||||||
|
Knowledge 不会被复制、移动或自动重建。旧用户继续直接使用原有 Knowledge 目录,因此不需要为了 v2.2.0 重新建立索引。
|
||||||
|
|
||||||
|
TraceMemo 的新诊断日志写入 TraceMemo 日志目录;WechatExplorer 历史日志保持原位置,不移动、不重命名、不删除。“打开诊断日志目录”会打开当前版本使用的 TraceMemo 日志。
|
||||||
|
|
||||||
|
更多安全边界见[数据、隐私与安全](../user-guide/privacy.md)和[API 安全](./api-security.md)。
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {
|
import { getUserDataRoots, LEGACY_USER_DATA_NAME, selectUserDataRoot } from './app-data-paths'
|
||||||
chooseUserDataRoot,
|
|
||||||
getUserDataRoots,
|
|
||||||
LEGACY_USER_DATA_NAME
|
|
||||||
} from './app-data-paths'
|
|
||||||
|
|
||||||
// This module must remain the first main-process import. Static imports in
|
// This module must remain the first main-process import. Static imports in
|
||||||
// settings/cache services can otherwise resolve Electron paths before the
|
// settings/cache services can otherwise resolve Electron paths before the
|
||||||
@@ -13,10 +9,11 @@ app.setName(process.platform === 'win32' ? 'WeFlow' : LEGACY_USER_DATA_NAME)
|
|||||||
|
|
||||||
const isolatedUserData = process.env['WXE_USER_DATA']
|
const isolatedUserData = process.env['WXE_USER_DATA']
|
||||||
const roots = getUserDataRoots(app.getPath('appData'))
|
const roots = getUserDataRoots(app.getPath('appData'))
|
||||||
const selectedUserData = chooseUserDataRoot({
|
const userDataSelection = selectUserDataRoot({
|
||||||
...roots,
|
...roots,
|
||||||
isolated: isolatedUserData
|
isolated: isolatedUserData
|
||||||
})
|
})
|
||||||
|
const selectedUserData = userDataSelection.selected
|
||||||
|
|
||||||
app.setPath('userData', selectedUserData)
|
app.setPath('userData', selectedUserData)
|
||||||
app.setPath('sessionData', selectedUserData)
|
app.setPath('sessionData', selectedUserData)
|
||||||
@@ -28,4 +25,4 @@ if (process.platform === 'darwin') {
|
|||||||
app.setPath('logs', path.join(app.getPath('home'), 'Library', 'Logs', 'TraceMemo'))
|
app.setPath('logs', path.join(app.getPath('home'), 'Library', 'Logs', 'TraceMemo'))
|
||||||
}
|
}
|
||||||
|
|
||||||
export { roots, selectedUserData }
|
export { roots, selectedUserData, userDataSelection }
|
||||||
|
|||||||
+155
-8
@@ -2,10 +2,12 @@ import fs from 'fs'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
export const LEGACY_USER_DATA_NAME = 'WechatExplorer'
|
export const LEGACY_USER_DATA_NAME = 'WechatExplorer'
|
||||||
|
export const LEGACY_PACKAGE_USER_DATA_NAME = 'wechatexplorer'
|
||||||
export const CURRENT_USER_DATA_NAME = 'tracememo'
|
export const CURRENT_USER_DATA_NAME = 'tracememo'
|
||||||
|
|
||||||
export interface UserDataRoots {
|
export interface UserDataRoots {
|
||||||
legacy: string
|
legacy: string
|
||||||
|
legacyPackage: string
|
||||||
current: string
|
current: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,6 +15,41 @@ export interface UserDataSelectionInput extends UserDataRoots {
|
|||||||
isolated?: string
|
isolated?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UserDataRootKind = 'isolated' | 'legacy-display' | 'legacy-package' | 'current'
|
||||||
|
|
||||||
|
export type UserDataSelectionReason =
|
||||||
|
| 'isolated-override'
|
||||||
|
| 'legacy-display-assets'
|
||||||
|
| 'legacy-package-assets'
|
||||||
|
| 'legacy-shared-assets'
|
||||||
|
| 'legacy-conflict-display-preferred'
|
||||||
|
| 'current-assets'
|
||||||
|
| 'clean-install'
|
||||||
|
|
||||||
|
export interface UserDataSelection {
|
||||||
|
selected: string
|
||||||
|
selectedKind: UserDataRootKind
|
||||||
|
reason: UserDataSelectionReason
|
||||||
|
directories: {
|
||||||
|
legacy: boolean
|
||||||
|
legacyPackage: boolean
|
||||||
|
current: boolean
|
||||||
|
}
|
||||||
|
assets: {
|
||||||
|
legacy: boolean
|
||||||
|
legacyPackage: boolean
|
||||||
|
current: boolean
|
||||||
|
}
|
||||||
|
legacyRootsEquivalent: boolean
|
||||||
|
legacyConflict: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserDataSelectionDependencies {
|
||||||
|
directoryExists: (root: string) => boolean
|
||||||
|
hasAssets: (root: string) => boolean
|
||||||
|
areSameDirectory: (first: string, second: string) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
function isNonEmptyFile(filePath: string): boolean {
|
function isNonEmptyFile(filePath: string): boolean {
|
||||||
try {
|
try {
|
||||||
const stat = fs.statSync(filePath)
|
const stat = fs.statSync(filePath)
|
||||||
@@ -37,7 +74,11 @@ function hasPersistentEntries(directoryPath: string): boolean {
|
|||||||
function hasDatabaseKey(directoryPath: string): boolean {
|
function hasDatabaseKey(directoryPath: string): boolean {
|
||||||
try {
|
try {
|
||||||
return fs.readdirSync(directoryPath, { withFileTypes: true }).some((entry) => {
|
return fs.readdirSync(directoryPath, { withFileTypes: true }).some((entry) => {
|
||||||
return entry.isFile() && entry.name.endsWith('.bin') && isNonEmptyFile(path.join(directoryPath, entry.name))
|
return (
|
||||||
|
entry.isFile() &&
|
||||||
|
entry.name.endsWith('.bin') &&
|
||||||
|
isNonEmptyFile(path.join(directoryPath, entry.name))
|
||||||
|
)
|
||||||
})
|
})
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
@@ -56,6 +97,26 @@ function hasKnowledgeDatabase(root: string): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isExistingDirectory(directoryPath: string): boolean {
|
||||||
|
try {
|
||||||
|
return fs.statSync(directoryPath).isDirectory()
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function areSameExistingDirectory(firstPath: string, secondPath: string): boolean {
|
||||||
|
try {
|
||||||
|
const first = fs.statSync(firstPath)
|
||||||
|
const second = fs.statSync(secondPath)
|
||||||
|
if (!first.isDirectory() || !second.isDirectory()) return false
|
||||||
|
if (first.ino && first.dev === second.dev && first.ino === second.ino) return true
|
||||||
|
return fs.realpathSync.native(firstPath) === fs.realpathSync.native(secondPath)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runtime-only Chromium files are deliberately excluded. A directory is a
|
* Runtime-only Chromium files are deliberately excluded. A directory is a
|
||||||
* valid data root only when it contains at least one user-owned marker.
|
* valid data root only when it contains at least one user-owned marker.
|
||||||
@@ -85,20 +146,106 @@ export function hasValidUserAssets(root: string): boolean {
|
|||||||
export function getUserDataRoots(appDataPath: string): UserDataRoots {
|
export function getUserDataRoots(appDataPath: string): UserDataRoots {
|
||||||
return {
|
return {
|
||||||
legacy: path.join(appDataPath, LEGACY_USER_DATA_NAME),
|
legacy: path.join(appDataPath, LEGACY_USER_DATA_NAME),
|
||||||
|
legacyPackage: path.join(appDataPath, LEGACY_PACKAGE_USER_DATA_NAME),
|
||||||
current: path.join(appDataPath, CURRENT_USER_DATA_NAME)
|
current: path.join(appDataPath, CURRENT_USER_DATA_NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select exactly one root. This intentionally does not copy, merge, delete or
|
* Select exactly one root. This intentionally does not copy, merge, delete or
|
||||||
* modify either directory. Legacy wins when both roots contain user assets so
|
* modify any directory. The visible-name legacy root remains first priority;
|
||||||
* a v2.1.9 upgrade remains deterministic and lossless.
|
* the lowercase v2.1.9 package-name root is the fallback on case-sensitive
|
||||||
|
* filesystems. If both distinct legacy roots contain assets, the visible-name
|
||||||
|
* root wins deterministically and the caller receives conflict diagnostics.
|
||||||
*/
|
*/
|
||||||
export function chooseUserDataRoot(input: UserDataSelectionInput): string {
|
export function selectUserDataRoot(
|
||||||
|
input: UserDataSelectionInput,
|
||||||
|
dependencies: UserDataSelectionDependencies = {
|
||||||
|
directoryExists: isExistingDirectory,
|
||||||
|
hasAssets: hasValidUserAssets,
|
||||||
|
areSameDirectory: areSameExistingDirectory
|
||||||
|
}
|
||||||
|
): UserDataSelection {
|
||||||
const isolated = input.isolated?.trim()
|
const isolated = input.isolated?.trim()
|
||||||
if (isolated) return path.resolve(isolated)
|
if (isolated) {
|
||||||
|
return {
|
||||||
|
selected: path.resolve(isolated),
|
||||||
|
selectedKind: 'isolated',
|
||||||
|
reason: 'isolated-override',
|
||||||
|
directories: { legacy: false, legacyPackage: false, current: false },
|
||||||
|
assets: { legacy: false, legacyPackage: false, current: false },
|
||||||
|
legacyRootsEquivalent: false,
|
||||||
|
legacyConflict: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasValidUserAssets(input.legacy)) return input.legacy
|
const directories = {
|
||||||
if (hasValidUserAssets(input.current)) return input.current
|
legacy: dependencies.directoryExists(input.legacy),
|
||||||
return input.current
|
legacyPackage: dependencies.directoryExists(input.legacyPackage),
|
||||||
|
current: dependencies.directoryExists(input.current)
|
||||||
|
}
|
||||||
|
const assets = {
|
||||||
|
legacy: dependencies.hasAssets(input.legacy),
|
||||||
|
legacyPackage: dependencies.hasAssets(input.legacyPackage),
|
||||||
|
current: dependencies.hasAssets(input.current)
|
||||||
|
}
|
||||||
|
const legacyRootsEquivalent =
|
||||||
|
directories.legacy &&
|
||||||
|
directories.legacyPackage &&
|
||||||
|
dependencies.areSameDirectory(input.legacy, input.legacyPackage)
|
||||||
|
|
||||||
|
if (assets.legacy) {
|
||||||
|
const legacyConflict = assets.legacyPackage && !legacyRootsEquivalent
|
||||||
|
return {
|
||||||
|
selected: input.legacy,
|
||||||
|
selectedKind: 'legacy-display',
|
||||||
|
reason: legacyConflict
|
||||||
|
? 'legacy-conflict-display-preferred'
|
||||||
|
: legacyRootsEquivalent
|
||||||
|
? 'legacy-shared-assets'
|
||||||
|
: 'legacy-display-assets',
|
||||||
|
directories,
|
||||||
|
assets,
|
||||||
|
legacyRootsEquivalent,
|
||||||
|
legacyConflict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assets.legacyPackage) {
|
||||||
|
return {
|
||||||
|
selected: input.legacyPackage,
|
||||||
|
selectedKind: 'legacy-package',
|
||||||
|
reason: 'legacy-package-assets',
|
||||||
|
directories,
|
||||||
|
assets,
|
||||||
|
legacyRootsEquivalent: false,
|
||||||
|
legacyConflict: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assets.current) {
|
||||||
|
return {
|
||||||
|
selected: input.current,
|
||||||
|
selectedKind: 'current',
|
||||||
|
reason: 'current-assets',
|
||||||
|
directories,
|
||||||
|
assets,
|
||||||
|
legacyRootsEquivalent: false,
|
||||||
|
legacyConflict: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
selected: input.current,
|
||||||
|
selectedKind: 'current',
|
||||||
|
reason: 'clean-install',
|
||||||
|
directories,
|
||||||
|
assets,
|
||||||
|
legacyRootsEquivalent: false,
|
||||||
|
legacyConflict: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function chooseUserDataRoot(input: UserDataSelectionInput): string {
|
||||||
|
return selectUserDataRoot(input).selected
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-1
@@ -1,4 +1,4 @@
|
|||||||
import './app-data-bootstrap'
|
import { userDataSelection } from './app-data-bootstrap'
|
||||||
import './preload-env'
|
import './preload-env'
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
@@ -513,6 +513,26 @@ app.whenReady().then(async () => {
|
|||||||
message: 'TraceMemo 启动',
|
message: 'TraceMemo 启动',
|
||||||
details: { build: BUILD_MARK, platform: process.platform, version: app.getVersion() }
|
details: { build: BUILD_MARK, platform: process.platform, version: app.getVersion() }
|
||||||
})
|
})
|
||||||
|
appLogger.write({
|
||||||
|
level: userDataSelection.legacyConflict ? 'warn' : 'info',
|
||||||
|
scope: 'app-data-bootstrap',
|
||||||
|
message: userDataSelection.legacyConflict
|
||||||
|
? '检测到两个独立的 legacy userData,已按兼容优先级选择 WechatExplorer'
|
||||||
|
: 'userData 路径选择完成',
|
||||||
|
details: {
|
||||||
|
selectedPath: userDataSelection.selected,
|
||||||
|
selectedKind: userDataSelection.selectedKind,
|
||||||
|
reason: userDataSelection.reason,
|
||||||
|
legacyExists: userDataSelection.directories.legacy,
|
||||||
|
legacyPackageExists: userDataSelection.directories.legacyPackage,
|
||||||
|
currentExists: userDataSelection.directories.current,
|
||||||
|
legacyAssets: userDataSelection.assets.legacy,
|
||||||
|
legacyPackageAssets: userDataSelection.assets.legacyPackage,
|
||||||
|
currentAssets: userDataSelection.assets.current,
|
||||||
|
legacyRootsEquivalent: userDataSelection.legacyRootsEquivalent,
|
||||||
|
legacyConflict: userDataSelection.legacyConflict
|
||||||
|
}
|
||||||
|
})
|
||||||
process.on('uncaughtException', (error) => {
|
process.on('uncaughtException', (error) => {
|
||||||
appLogger.write({
|
appLogger.write({
|
||||||
level: 'error',
|
level: 'error',
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
|
|||||||
import os from 'os'
|
import os from 'os'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||||
import { chooseUserDataRoot, getUserDataRoots, hasValidUserAssets } from '../../src/main/app-data-paths'
|
import {
|
||||||
|
chooseUserDataRoot,
|
||||||
|
getUserDataRoots,
|
||||||
|
hasValidUserAssets,
|
||||||
|
selectUserDataRoot,
|
||||||
|
type UserDataRoots
|
||||||
|
} from '../../src/main/app-data-paths'
|
||||||
|
|
||||||
let root = ''
|
let root = ''
|
||||||
|
|
||||||
@@ -23,6 +29,18 @@ function writeMarker(filePath: string): void {
|
|||||||
writeFileSync(filePath, 'fixture')
|
writeFileSync(filePath, 'fixture')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectWithAssets(
|
||||||
|
roots: UserDataRoots,
|
||||||
|
validRoots: string[],
|
||||||
|
legacyRootsEquivalent = false
|
||||||
|
): ReturnType<typeof selectUserDataRoot> {
|
||||||
|
return selectUserDataRoot(roots, {
|
||||||
|
directoryExists: (candidate) => validRoots.includes(candidate),
|
||||||
|
hasAssets: (candidate) => validRoots.includes(candidate),
|
||||||
|
areSameDirectory: () => legacyRootsEquivalent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
describe('app data compatibility root selection', () => {
|
describe('app data compatibility root selection', () => {
|
||||||
it('chooses the new root for a clean install', () => {
|
it('chooses the new root for a clean install', () => {
|
||||||
const roots = fixtureRoots()
|
const roots = fixtureRoots()
|
||||||
@@ -31,6 +49,66 @@ describe('app data compatibility root selection', () => {
|
|||||||
expect(chooseUserDataRoot(roots)).toBe(roots.current)
|
expect(chooseUserDataRoot(roots)).toBe(roots.current)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('chooses WechatExplorer when only the display-name legacy root exists', () => {
|
||||||
|
const roots = fixtureRoots()
|
||||||
|
const selection = selectWithAssets(roots, [roots.legacy])
|
||||||
|
expect(selection).toMatchObject({
|
||||||
|
selected: roots.legacy,
|
||||||
|
selectedKind: 'legacy-display',
|
||||||
|
reason: 'legacy-display-assets',
|
||||||
|
directories: { legacy: true, legacyPackage: false, current: false },
|
||||||
|
legacyConflict: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chooses wechatexplorer when only the v2.1.9 package-name legacy root exists', () => {
|
||||||
|
const roots = fixtureRoots()
|
||||||
|
const selection = selectWithAssets(roots, [roots.legacyPackage])
|
||||||
|
expect(selection).toMatchObject({
|
||||||
|
selected: roots.legacyPackage,
|
||||||
|
selectedKind: 'legacy-package',
|
||||||
|
reason: 'legacy-package-assets',
|
||||||
|
directories: { legacy: false, legacyPackage: true, current: false },
|
||||||
|
legacyConflict: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chooses WechatExplorer deterministically and reports a conflict when both exist', () => {
|
||||||
|
const roots = fixtureRoots()
|
||||||
|
const selection = selectWithAssets(roots, [roots.legacy, roots.legacyPackage])
|
||||||
|
expect(selection).toMatchObject({
|
||||||
|
selected: roots.legacy,
|
||||||
|
selectedKind: 'legacy-display',
|
||||||
|
reason: 'legacy-conflict-display-preferred',
|
||||||
|
directories: { legacy: true, legacyPackage: true, current: false },
|
||||||
|
legacyRootsEquivalent: false,
|
||||||
|
legacyConflict: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chooses tracememo when neither legacy root exists', () => {
|
||||||
|
const roots = fixtureRoots()
|
||||||
|
const selection = selectWithAssets(roots, [])
|
||||||
|
expect(selection).toMatchObject({
|
||||||
|
selected: roots.current,
|
||||||
|
selectedKind: 'current',
|
||||||
|
reason: 'clean-install',
|
||||||
|
directories: { legacy: false, legacyPackage: false, current: false },
|
||||||
|
legacyConflict: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not report a conflict when both legacy spellings resolve to one directory', () => {
|
||||||
|
const roots = fixtureRoots()
|
||||||
|
const selection = selectWithAssets(roots, [roots.legacy, roots.legacyPackage], true)
|
||||||
|
expect(selection).toMatchObject({
|
||||||
|
selected: roots.legacy,
|
||||||
|
reason: 'legacy-shared-assets',
|
||||||
|
legacyRootsEquivalent: true,
|
||||||
|
legacyConflict: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('ignores runtime-only Chromium files', () => {
|
it('ignores runtime-only Chromium files', () => {
|
||||||
const roots = fixtureRoots()
|
const roots = fixtureRoots()
|
||||||
mkdirSync(roots.current, { recursive: true })
|
mkdirSync(roots.current, { recursive: true })
|
||||||
|
|||||||
Reference in New Issue
Block a user