@yunzai-ng/core 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/platform/paths.d.ts
CHANGED
|
@@ -6,6 +6,16 @@ export interface ResolvePathsOptions {
|
|
|
6
6
|
/** 内核包所在目录;缺省由 `import.meta.url` 推导 */
|
|
7
7
|
runtime?: string;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* 0.1.1 及更早的默认位置上是否还留着一个实例
|
|
11
|
+
*
|
|
12
|
+
* 供 CLI 提示使用:默认位置自 0.2.0 起改为当前目录,装过旧版的机器上那个实例仍在原处,
|
|
13
|
+
* 而使用者多半以为「配置全没了」。内核只负责报出位置,是否搬家由使用者决定 ——
|
|
14
|
+
* 自动迁移会在两个目录都有内容时无从判断该以谁为准。
|
|
15
|
+
* @param home 本次实际使用的主目录
|
|
16
|
+
* @returns 旧实例所在目录;不存在、或恰好就是本次所用的目录时 undefined
|
|
17
|
+
*/
|
|
18
|
+
export declare function legacyInstance(home: string): string | undefined;
|
|
9
19
|
/**
|
|
10
20
|
* 解析目录布局
|
|
11
21
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/platform/paths.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/platform/paths.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAapD,gBAAgB;AAChB,MAAM,WAAW,mBAAmB;IAClC,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AA6ED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI/D;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,GAAE,mBAAwB,GAAG,YAAY,CAgBzE;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAW5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjG"}
|
package/dist/platform/paths.js
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
* 模块职责:确定并创建运行时目录布局
|
|
3
3
|
* 依赖方向:依赖 util/fs、platform/detect
|
|
4
4
|
* 生命周期:启动时解析一次,之后只读
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 其余模块只准通过 `app.paths` 取。
|
|
5
|
+
* 注意事项:优先级:显式参数 > `YZNG_HOME` > 便携模式标记 > 当前目录。
|
|
6
|
+
* 启动时把八个目录一次性定死成绝对路径,其余模块只准通过 `app.paths` 取。
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* **默认落在当前目录**:解压或克隆到一个文件夹、在其中 `yzng init`,数据就在
|
|
9
|
+
* 眼前,拷走整个文件夹即完成迁移。代价是本模块必须读 `process.cwd()`,而以
|
|
10
|
+
* Windows 服务、开机自启或 pm2 启动时工作目录并非项目目录 —— 那些场景必须
|
|
11
|
+
* 显式给出 `YZNG_HOME` 或启动器的工作目录,否则数据会落在启动器所在之处。
|
|
12
|
+
*
|
|
13
|
+
* 0.1.1 及更早的默认位置是系统目录(Windows 的 `%LOCALAPPDATA%\YunzaiNG` 等)。
|
|
14
|
+
* 此处**不**为其保留静默回落 —— 那会使「默认在当前目录」在任何装过旧版的机器上
|
|
15
|
+
* 都不成立。改由 {@link legacyInstance} 把旧实例报给 CLI 显式提示:换目录应当是
|
|
16
|
+
* 使用者看得见的一步,而不是内核悄悄替他挑一个。
|
|
12
17
|
*/
|
|
13
18
|
import { homedir } from "node:os";
|
|
14
19
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
@@ -49,6 +54,8 @@ function findPortableRoot(runtimeDir) {
|
|
|
49
54
|
}
|
|
50
55
|
/**
|
|
51
56
|
* 系统默认的数据根目录
|
|
57
|
+
*
|
|
58
|
+
* 已不再是缺省取值,仅由 {@link legacyInstance} 用于查找旧实例,见文件头第 3 条。
|
|
52
59
|
* @returns 绝对路径
|
|
53
60
|
*/
|
|
54
61
|
function defaultHome() {
|
|
@@ -70,6 +77,42 @@ function defaultHome() {
|
|
|
70
77
|
return join(xdg, "yunzai-ng");
|
|
71
78
|
return join(home, APP_DIR_NAME_UNIX);
|
|
72
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* 判断一个目录里是否已有实例
|
|
82
|
+
*
|
|
83
|
+
* 以 `config/` 是否存在为准:`ensurePaths` 建的七个目录中只有它必定装着文件
|
|
84
|
+
* (`ConfigStore` 会把缺省配置落盘),而空的 `data/`、`logs/` 无从区分
|
|
85
|
+
* 「一个实例」与「随手建的空目录」。
|
|
86
|
+
* @param dir 待判断的目录
|
|
87
|
+
* @returns 是否已有实例
|
|
88
|
+
*/
|
|
89
|
+
function hasInstance(dir) {
|
|
90
|
+
return existsSync(join(dir, "config"));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 未显式指定、也无便携标记时的主目录
|
|
94
|
+
*
|
|
95
|
+
* 见文件头第 2、3 条。
|
|
96
|
+
* @returns 绝对路径
|
|
97
|
+
*/
|
|
98
|
+
function autoHome() {
|
|
99
|
+
return process.cwd();
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 0.1.1 及更早的默认位置上是否还留着一个实例
|
|
103
|
+
*
|
|
104
|
+
* 供 CLI 提示使用:默认位置自 0.2.0 起改为当前目录,装过旧版的机器上那个实例仍在原处,
|
|
105
|
+
* 而使用者多半以为「配置全没了」。内核只负责报出位置,是否搬家由使用者决定 ——
|
|
106
|
+
* 自动迁移会在两个目录都有内容时无从判断该以谁为准。
|
|
107
|
+
* @param home 本次实际使用的主目录
|
|
108
|
+
* @returns 旧实例所在目录;不存在、或恰好就是本次所用的目录时 undefined
|
|
109
|
+
*/
|
|
110
|
+
export function legacyInstance(home) {
|
|
111
|
+
const legacy = defaultHome();
|
|
112
|
+
if (resolve(legacy) === resolve(home))
|
|
113
|
+
return undefined;
|
|
114
|
+
return hasInstance(legacy) ? legacy : undefined;
|
|
115
|
+
}
|
|
73
116
|
/**
|
|
74
117
|
* 解析目录布局
|
|
75
118
|
*
|
|
@@ -81,7 +124,7 @@ function defaultHome() {
|
|
|
81
124
|
export function resolvePaths(opts = {}) {
|
|
82
125
|
const runtime = opts.runtime ? resolve(opts.runtime) : detectRuntimeDir();
|
|
83
126
|
const explicit = opts.home ?? process.env.YZNG_HOME;
|
|
84
|
-
const home = explicit ? resolve(process.cwd(), explicit) : (findPortableRoot(runtime) ??
|
|
127
|
+
const home = explicit ? resolve(process.cwd(), explicit) : (findPortableRoot(runtime) ?? autoHome());
|
|
85
128
|
return Object.freeze({
|
|
86
129
|
home,
|
|
87
130
|
config: join(home, "config"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/platform/paths.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/platform/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAE5C,gBAAgB;AAChB,MAAM,eAAe,GAAG,WAAW,CAAA;AAEnC,uBAAuB;AACvB,MAAM,YAAY,GAAG,UAAU,CAAA;AAE/B,uBAAuB;AACvB,MAAM,iBAAiB,GAAG,YAAY,CAAA;AAUtC;;;;;GAKG;AACH,SAAS,gBAAgB;IACvB,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACpD,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AAClC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,UAAkB;IAC1C,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;IAC1G,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;YAAE,OAAO,GAAG,CAAA;IACxD,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;GAKG;AACH,SAAS,WAAW;IAClB,MAAM,IAAI,GAAG,cAAc,EAAE,CAAA;IAC7B,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IAEtB,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;QAC9F,OAAO,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IACjC,CAAC;IACD,IAAI,IAAI,CAAC,EAAE,KAAK,OAAO,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,qBAAqB,EAAE,YAAY,CAAC,CAAA;IACnE,CAAC;IACD,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC1B,0CAA0C;QAC1C,OAAO,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAA;IACtC,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAA;IACrC,IAAI,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;IACzD,OAAO,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAA;AACtC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA;AACxC,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ;IACf,OAAO,OAAO,CAAC,GAAG,EAAE,CAAA;AACtB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAC5B,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IACvD,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,OAA4B,EAAE;IACzD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAA;IAEzE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAA;IACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAA;IAEpG,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI;QACJ,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC;QAC5B,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;QACxB,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;QAC9B,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC;QAC1B,OAAO;KACR,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAmB;IACnD,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;QACrB,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;QACvB,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;QACrB,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;QACrB,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;QACrB,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC;QACxB,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC;KACvB,CAAC,CAAA;IACF,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAmB,EAAE,MAAc,EAAE,GAAW,EAAE,MAAc;IACvF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,MAAM,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC,CAAA;AACtD,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:主目录探测顺序的测试
|
|
3
|
+
* 依赖方向:测试文件,依赖 platform/paths.ts
|
|
4
|
+
* 生命周期:每个用例一个临时目录,用后即弃
|
|
5
|
+
* 注意事项:本文件固定的是「数据放在哪里」这一条 —— 它错了的表现不是报错,而是
|
|
6
|
+
* 使用者的配置、账号与历史记录**看起来凭空消失**(实则仍在旧位置)。
|
|
7
|
+
* 因此这里既覆盖「新装落在当前目录」,也覆盖「既有安装不被静默搬家」。
|
|
8
|
+
*
|
|
9
|
+
* 用 `process.chdir` 而非注入一个 cwd 参数:被测对象正是「读取进程工作目录」
|
|
10
|
+
* 这一行为,参数化会把它测成一个纯函数,那样恰好绕过了要测的东西。
|
|
11
|
+
* vitest 的 forks 池令每个测试文件独占一个进程,chdir 不会波及其他文件。
|
|
12
|
+
*/
|
|
13
|
+
import { mkdtemp, mkdir, writeFile } from "node:fs/promises"
|
|
14
|
+
import { tmpdir } from "node:os"
|
|
15
|
+
import { join, resolve } from "node:path"
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest"
|
|
17
|
+
import { legacyInstance, resolvePaths } from "./paths.js"
|
|
18
|
+
|
|
19
|
+
/** 进入用例前的工作目录与环境变量,用后还原 */
|
|
20
|
+
let origin: string
|
|
21
|
+
let env: NodeJS.ProcessEnv
|
|
22
|
+
/** 本用例的临时根目录 */
|
|
23
|
+
let base: string
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 推出 `defaultHome()` 在本平台会给出的位置
|
|
27
|
+
*
|
|
28
|
+
* 仅 Windows 与 Linux 可经环境变量改写;macOS 固定在 `~/Library`,无从在测试中挪动,
|
|
29
|
+
* 故相应用例在该平台上跳过。
|
|
30
|
+
* @param root 充当 `LOCALAPPDATA` / `XDG_DATA_HOME` 的目录
|
|
31
|
+
* @returns 系统默认位置;本平台无法改写时 undefined
|
|
32
|
+
*/
|
|
33
|
+
function legacyHomeFor(root: string): string | undefined {
|
|
34
|
+
if (process.platform === "win32") return join(root, "YunzaiNG")
|
|
35
|
+
if (process.platform === "linux") return join(root, "yunzai-ng")
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
beforeEach(async () => {
|
|
40
|
+
origin = process.cwd()
|
|
41
|
+
env = { ...process.env }
|
|
42
|
+
base = await mkdtemp(join(tmpdir(), "yzng-paths-"))
|
|
43
|
+
// 三者都会参与探测,逐一清掉,否则用例会读到开发机上真实的取值
|
|
44
|
+
delete process.env["YZNG_HOME"]
|
|
45
|
+
delete process.env["LOCALAPPDATA"]
|
|
46
|
+
delete process.env["XDG_DATA_HOME"]
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
process.chdir(origin)
|
|
51
|
+
process.env = env
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe("主目录探测", () => {
|
|
55
|
+
it("什么都不给时落在当前目录", async () => {
|
|
56
|
+
const dir = join(base, "新装")
|
|
57
|
+
await mkdir(dir)
|
|
58
|
+
process.chdir(dir)
|
|
59
|
+
|
|
60
|
+
// resolve 而非直接比较:macOS 的 /var 是 /private/var 的符号链接,
|
|
61
|
+
// mkdtemp 给出前者而 cwd 给出后者
|
|
62
|
+
expect(resolvePaths().home).toBe(resolve(process.cwd()))
|
|
63
|
+
expect(resolvePaths().config).toBe(join(resolve(process.cwd()), "config"))
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it("显式参数为相对路径时按当前目录解析", async () => {
|
|
67
|
+
process.chdir(base)
|
|
68
|
+
const got = resolvePaths({ home: "./实例甲" }).home
|
|
69
|
+
|
|
70
|
+
expect(got).toBe(join(resolve(base), "实例甲"))
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it("YZNG_HOME 压过当前目录", async () => {
|
|
74
|
+
const other = join(base, "另一处")
|
|
75
|
+
await mkdir(other)
|
|
76
|
+
process.chdir(base)
|
|
77
|
+
process.env["YZNG_HOME"] = other
|
|
78
|
+
|
|
79
|
+
expect(resolvePaths().home).toBe(resolve(other))
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it("显式参数压过 YZNG_HOME", async () => {
|
|
83
|
+
const a = join(base, "参数")
|
|
84
|
+
const b = join(base, "环境变量")
|
|
85
|
+
await mkdir(a)
|
|
86
|
+
await mkdir(b)
|
|
87
|
+
process.chdir(base)
|
|
88
|
+
process.env["YZNG_HOME"] = b
|
|
89
|
+
|
|
90
|
+
expect(resolvePaths({ home: a }).home).toBe(resolve(a))
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("便携标记压过当前目录", async () => {
|
|
94
|
+
const install = join(base, "便携根")
|
|
95
|
+
// runtime 传 <install>/node_modules/@yunzai-ng/core,与真实安装的层级一致:
|
|
96
|
+
// findPortableRoot 自该处上溯三级才回到 install
|
|
97
|
+
const runtime = join(install, "node_modules", "@yunzai-ng", "core")
|
|
98
|
+
await mkdir(runtime, { recursive: true })
|
|
99
|
+
await writeFile(join(install, ".portable"), "")
|
|
100
|
+
const cwd = join(base, "别处")
|
|
101
|
+
await mkdir(cwd)
|
|
102
|
+
process.chdir(cwd)
|
|
103
|
+
|
|
104
|
+
expect(resolvePaths({ runtime }).home).toBe(resolve(install))
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe("旧默认位置上的实例", () => {
|
|
109
|
+
/** 在 `dir` 下造出一个看起来已在用的实例 */
|
|
110
|
+
const makeInstance = async (dir: string): Promise<void> => {
|
|
111
|
+
await mkdir(join(dir, "config"), { recursive: true })
|
|
112
|
+
await writeFile(join(dir, "config", "core.yaml"), "server:\n port: 2536\n")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 把系统默认位置指到临时目录下
|
|
117
|
+
* @returns 系统默认位置;本平台无法改写时 undefined
|
|
118
|
+
*/
|
|
119
|
+
const pointLegacyAt = (): string | undefined => {
|
|
120
|
+
const root = join(base, "系统默认位置")
|
|
121
|
+
const legacy = legacyHomeFor(root)
|
|
122
|
+
if (legacy === undefined) return undefined
|
|
123
|
+
if (process.platform === "win32") process.env["LOCALAPPDATA"] = root
|
|
124
|
+
else process.env["XDG_DATA_HOME"] = root
|
|
125
|
+
return legacy
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
it.skipIf(legacyHomeFor("x") === undefined)("旧位置有实例也不影响主目录 —— 不做静默回落", async () => {
|
|
129
|
+
const legacy = pointLegacyAt()
|
|
130
|
+
if (legacy === undefined) return
|
|
131
|
+
await makeInstance(legacy)
|
|
132
|
+
const cwd = join(base, "空目录")
|
|
133
|
+
await mkdir(cwd)
|
|
134
|
+
process.chdir(cwd)
|
|
135
|
+
|
|
136
|
+
// 回落会让「默认在当前目录」在任何装过旧版的机器上都不成立
|
|
137
|
+
expect(resolvePaths().home).toBe(resolve(cwd))
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it.skipIf(legacyHomeFor("x") === undefined)("legacyInstance 报出旧实例,供 CLI 提示", async () => {
|
|
141
|
+
const legacy = pointLegacyAt()
|
|
142
|
+
if (legacy === undefined) return
|
|
143
|
+
await makeInstance(legacy)
|
|
144
|
+
const cwd = join(base, "新家")
|
|
145
|
+
await mkdir(cwd)
|
|
146
|
+
|
|
147
|
+
expect(legacyInstance(cwd)).toBe(legacy)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it.skipIf(legacyHomeFor("x") === undefined)("旧位置没有实例时不提示", async () => {
|
|
151
|
+
const legacy = pointLegacyAt()
|
|
152
|
+
if (legacy === undefined) return
|
|
153
|
+
// 目录存在但没有 config/:这是"随手建过一个空目录",不是一个实例
|
|
154
|
+
await mkdir(legacy, { recursive: true })
|
|
155
|
+
|
|
156
|
+
expect(legacyInstance(join(base, "新家"))).toBeUndefined()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it.skipIf(legacyHomeFor("x") === undefined)("本次用的就是旧位置时不提示 —— 没有可搬的家", async () => {
|
|
160
|
+
const legacy = pointLegacyAt()
|
|
161
|
+
if (legacy === undefined) return
|
|
162
|
+
await makeInstance(legacy)
|
|
163
|
+
|
|
164
|
+
expect(legacyInstance(legacy)).toBeUndefined()
|
|
165
|
+
})
|
|
166
|
+
})
|
package/src/platform/paths.ts
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
* 模块职责:确定并创建运行时目录布局
|
|
3
3
|
* 依赖方向:依赖 util/fs、platform/detect
|
|
4
4
|
* 生命周期:启动时解析一次,之后只读
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 其余模块只准通过 `app.paths` 取。
|
|
5
|
+
* 注意事项:优先级:显式参数 > `YZNG_HOME` > 便携模式标记 > 当前目录。
|
|
6
|
+
* 启动时把八个目录一次性定死成绝对路径,其余模块只准通过 `app.paths` 取。
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* **默认落在当前目录**:解压或克隆到一个文件夹、在其中 `yzng init`,数据就在
|
|
9
|
+
* 眼前,拷走整个文件夹即完成迁移。代价是本模块必须读 `process.cwd()`,而以
|
|
10
|
+
* Windows 服务、开机自启或 pm2 启动时工作目录并非项目目录 —— 那些场景必须
|
|
11
|
+
* 显式给出 `YZNG_HOME` 或启动器的工作目录,否则数据会落在启动器所在之处。
|
|
12
|
+
*
|
|
13
|
+
* 0.1.1 及更早的默认位置是系统目录(Windows 的 `%LOCALAPPDATA%\YunzaiNG` 等)。
|
|
14
|
+
* 此处**不**为其保留静默回落 —— 那会使「默认在当前目录」在任何装过旧版的机器上
|
|
15
|
+
* 都不成立。改由 {@link legacyInstance} 把旧实例报给 CLI 显式提示:换目录应当是
|
|
16
|
+
* 使用者看得见的一步,而不是内核悄悄替他挑一个。
|
|
12
17
|
*/
|
|
13
18
|
import { homedir } from "node:os"
|
|
14
19
|
import { dirname, isAbsolute, join, resolve } from "node:path"
|
|
@@ -63,6 +68,8 @@ function findPortableRoot(runtimeDir: string): string | undefined {
|
|
|
63
68
|
|
|
64
69
|
/**
|
|
65
70
|
* 系统默认的数据根目录
|
|
71
|
+
*
|
|
72
|
+
* 已不再是缺省取值,仅由 {@link legacyInstance} 用于查找旧实例,见文件头第 3 条。
|
|
66
73
|
* @returns 绝对路径
|
|
67
74
|
*/
|
|
68
75
|
function defaultHome(): string {
|
|
@@ -85,6 +92,44 @@ function defaultHome(): string {
|
|
|
85
92
|
return join(home, APP_DIR_NAME_UNIX)
|
|
86
93
|
}
|
|
87
94
|
|
|
95
|
+
/**
|
|
96
|
+
* 判断一个目录里是否已有实例
|
|
97
|
+
*
|
|
98
|
+
* 以 `config/` 是否存在为准:`ensurePaths` 建的七个目录中只有它必定装着文件
|
|
99
|
+
* (`ConfigStore` 会把缺省配置落盘),而空的 `data/`、`logs/` 无从区分
|
|
100
|
+
* 「一个实例」与「随手建的空目录」。
|
|
101
|
+
* @param dir 待判断的目录
|
|
102
|
+
* @returns 是否已有实例
|
|
103
|
+
*/
|
|
104
|
+
function hasInstance(dir: string): boolean {
|
|
105
|
+
return existsSync(join(dir, "config"))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 未显式指定、也无便携标记时的主目录
|
|
110
|
+
*
|
|
111
|
+
* 见文件头第 2、3 条。
|
|
112
|
+
* @returns 绝对路径
|
|
113
|
+
*/
|
|
114
|
+
function autoHome(): string {
|
|
115
|
+
return process.cwd()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 0.1.1 及更早的默认位置上是否还留着一个实例
|
|
120
|
+
*
|
|
121
|
+
* 供 CLI 提示使用:默认位置自 0.2.0 起改为当前目录,装过旧版的机器上那个实例仍在原处,
|
|
122
|
+
* 而使用者多半以为「配置全没了」。内核只负责报出位置,是否搬家由使用者决定 ——
|
|
123
|
+
* 自动迁移会在两个目录都有内容时无从判断该以谁为准。
|
|
124
|
+
* @param home 本次实际使用的主目录
|
|
125
|
+
* @returns 旧实例所在目录;不存在、或恰好就是本次所用的目录时 undefined
|
|
126
|
+
*/
|
|
127
|
+
export function legacyInstance(home: string): string | undefined {
|
|
128
|
+
const legacy = defaultHome()
|
|
129
|
+
if (resolve(legacy) === resolve(home)) return undefined
|
|
130
|
+
return hasInstance(legacy) ? legacy : undefined
|
|
131
|
+
}
|
|
132
|
+
|
|
88
133
|
/**
|
|
89
134
|
* 解析目录布局
|
|
90
135
|
*
|
|
@@ -97,7 +142,7 @@ export function resolvePaths(opts: ResolvePathsOptions = {}): RuntimePaths {
|
|
|
97
142
|
const runtime = opts.runtime ? resolve(opts.runtime) : detectRuntimeDir()
|
|
98
143
|
|
|
99
144
|
const explicit = opts.home ?? process.env.YZNG_HOME
|
|
100
|
-
const home = explicit ? resolve(process.cwd(), explicit) : (findPortableRoot(runtime) ??
|
|
145
|
+
const home = explicit ? resolve(process.cwd(), explicit) : (findPortableRoot(runtime) ?? autoHome())
|
|
101
146
|
|
|
102
147
|
return Object.freeze({
|
|
103
148
|
home,
|