@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.
@@ -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":"AAgBA,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;AAoDD;;;;;;;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"}
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"}
@@ -2,13 +2,18 @@
2
2
  * 模块职责:确定并创建运行时目录布局
3
3
  * 依赖方向:依赖 util/fs、platform/detect
4
4
  * 生命周期:启动时解析一次,之后只读
5
- * 注意事项:全程不使用 `process.cwd()` —— Windows 服务、开机自启或 pm2 启动时
6
- * 工作目录并非项目目录。启动时把八个目录一次性定死成绝对路径,
7
- * 其余模块只准通过 `app.paths` 取。
5
+ * 注意事项:优先级:显式参数 > `YZNG_HOME` > 便携模式标记 > 当前目录。
6
+ * 启动时把八个目录一次性定死成绝对路径,其余模块只准通过 `app.paths` 取。
8
7
  *
9
- * 优先级:显式参数 > `YZNG_HOME` 环境变量 > 便携模式标记 > 系统默认位置。
10
- * 便携模式(安装目录下有 `.portable` 文件)让 ZIP 解压即用、
11
- * 换机拷走整个文件夹就能迁移 —— Windows 用户最常见的诉求。
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) ?? defaultHome());
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":"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"}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/platform/paths.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yunzai-ng/core",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Yunzai NG 内核:插件宿主、统一事件模型、中间件管线、存储与渲染抽象",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",
@@ -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
+ })
@@ -2,13 +2,18 @@
2
2
  * 模块职责:确定并创建运行时目录布局
3
3
  * 依赖方向:依赖 util/fs、platform/detect
4
4
  * 生命周期:启动时解析一次,之后只读
5
- * 注意事项:全程不使用 `process.cwd()` —— Windows 服务、开机自启或 pm2 启动时
6
- * 工作目录并非项目目录。启动时把八个目录一次性定死成绝对路径,
7
- * 其余模块只准通过 `app.paths` 取。
5
+ * 注意事项:优先级:显式参数 > `YZNG_HOME` > 便携模式标记 > 当前目录。
6
+ * 启动时把八个目录一次性定死成绝对路径,其余模块只准通过 `app.paths` 取。
8
7
  *
9
- * 优先级:显式参数 > `YZNG_HOME` 环境变量 > 便携模式标记 > 系统默认位置。
10
- * 便携模式(安装目录下有 `.portable` 文件)让 ZIP 解压即用、
11
- * 换机拷走整个文件夹就能迁移 —— Windows 用户最常见的诉求。
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) ?? defaultHome())
145
+ const home = explicit ? resolve(process.cwd(), explicit) : (findPortableRoot(runtime) ?? autoHome())
101
146
 
102
147
  return Object.freeze({
103
148
  home,