create-koishi-ce 1.3.0 → 1.4.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/package.json CHANGED
@@ -1,64 +1,59 @@
1
1
  {
2
- "name": "create-koishi-ce",
3
- "description": "Setup a Koishi application",
4
- "version": "1.3.0",
5
- "type": "module",
6
- "main": "./lib/index.mjs",
7
- "module": "./lib/index.mjs",
8
- "types": "./lib/index.d.ts",
9
- "bin": {
10
- "create-koishi-ce": "./lib/bin.mjs"
11
- },
12
- "files": [
13
- "lib",
14
- "src"
15
- ],
16
- "contributors": [
17
- "Shigma <shigma10826@gmail.com>",
18
- "Oppenheymu <oppenheymu@gmail.com>"
19
- ],
20
- "license": "MIT",
21
- "repository": {
22
- "type": "git",
23
- "url": "git+https://github.com/Koishi-CE/koishi.git",
24
- "directory": "apps/koishi-create"
25
- },
26
- "bugs": {
27
- "url": "https://github.com/Koishi-CE/koishi/issues"
28
- },
29
- "homepage": "https://koishi.chat",
30
- "scripts": {
31
- "build": "tsdown"
32
- },
33
- "keywords": [
34
- "bot",
35
- "chatbot",
36
- "koishi",
37
- "discord",
38
- "telegram",
39
- "create",
40
- "scaffold",
41
- "template",
42
- "generator",
43
- "boilerplate"
44
- ],
45
- "devDependencies": {
46
- "@types/prompts": "^2.4.9",
47
- "@types/yargs-parser": "^21.0.3"
48
- },
49
- "dependencies": {
50
- "kleur": "^4.1.5",
51
- "prompts": "^2.4.2",
52
- "tar": "^7.5.22",
53
- "yargs-parser": "^22.0.0"
54
- },
55
- "exports": {
56
- ".": {
57
- "source": "./src/index.ts",
58
- "types": "./lib/index.d.ts",
59
- "import": "./lib/index.mjs",
60
- "default": "./lib/index.mjs"
61
- },
62
- "./package.json": "./package.json"
63
- }
64
- }
2
+ "name": "create-koishi-ce",
3
+ "description": "Setup a Koishi application",
4
+ "version": "1.4.0",
5
+ "type": "module",
6
+ "main": "./lib/index.mjs",
7
+ "module": "./lib/index.mjs",
8
+ "types": "./lib/index.d.ts",
9
+ "bin": {
10
+ "create-koishi-ce": "./lib/bin.mjs"
11
+ },
12
+ "files": [
13
+ "lib",
14
+ "src"
15
+ ],
16
+ "contributors": [
17
+ "Shigma <shigma10826@gmail.com>",
18
+ "Oppenheymu <oppenheymu@gmail.com>"
19
+ ],
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/Koishi-CE/koishi.git",
24
+ "directory": "apps/koishi-create"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/Koishi-CE/koishi/issues"
28
+ },
29
+ "homepage": "https://koishi.chat",
30
+ "scripts": {
31
+ "build": "tsdown"
32
+ },
33
+ "keywords": [
34
+ "bot",
35
+ "chatbot",
36
+ "koishi",
37
+ "discord",
38
+ "telegram",
39
+ "create",
40
+ "scaffold",
41
+ "template",
42
+ "generator",
43
+ "boilerplate"
44
+ ],
45
+ "dependencies": {
46
+ "@clack/prompts": "^1.7.0",
47
+ "picocolors": "^1.1.1",
48
+ "giget": "^3.3.1"
49
+ },
50
+ "exports": {
51
+ ".": {
52
+ "source": "./src/index.ts",
53
+ "types": "./lib/index.d.ts",
54
+ "import": "./lib/index.mjs",
55
+ "default": "./lib/index.mjs"
56
+ },
57
+ "./package.json": "./package.json"
58
+ }
59
+ }
@@ -2,7 +2,11 @@
2
2
  // Copyright (c) 2026-present Koishi-CE contributors.
3
3
 
4
4
  import { expect, test } from "bun:test";
5
- import { detectAgent, type Manifest, renderManifest } from "../index.ts";
5
+ import {
6
+ detectAgent,
7
+ type Manifest,
8
+ renderManifest,
9
+ } from "../index.ts";
6
10
 
7
11
  test("renderManifest 基础改写:替换项目名、标记 private、版本归零", () => {
8
12
  const source: Manifest = {
@@ -11,7 +15,9 @@ test("renderManifest 基础改写:替换项目名、标记 private、版本归
11
15
  workspaces: ["koishi-app/*"],
12
16
  scripts: { start: "koishi start" },
13
17
  };
14
- const output = JSON.parse(renderManifest(source, "my-app", false));
18
+ const output = JSON.parse(
19
+ renderManifest(source, "my-app", false),
20
+ );
15
21
  expect(output.name).toBe("my-app");
16
22
  expect(output.private).toBe(true);
17
23
  expect(output.version).toBe("0.0.0");
@@ -19,7 +25,9 @@ test("renderManifest 基础改写:替换项目名、标记 private、版本归
19
25
  expect(output.scripts).toEqual({ start: "koishi start" });
20
26
  expect(output.workspaces).toEqual(["koishi-app/*"]);
21
27
  // 与模板一致的两空格缩进 + 结尾换行
22
- expect(renderManifest(source, "my-app", false).endsWith("}\n")).toBe(true);
28
+ expect(
29
+ renderManifest(source, "my-app", false).endsWith("}\n"),
30
+ ).toBe(true);
23
31
  });
24
32
 
25
33
  test("renderManifest prod 模式:删除 workspaces 与 devDependencies", () => {
@@ -28,7 +36,9 @@ test("renderManifest prod 模式:删除 workspaces 与 devDependencies", () =>
28
36
  workspaces: ["koishi-app/*"],
29
37
  devDependencies: { koishi: "^4.18.11" },
30
38
  };
31
- const output = JSON.parse(renderManifest(source, "my-app", true));
39
+ const output = JSON.parse(
40
+ renderManifest(source, "my-app", true),
41
+ );
32
42
  expect(output.name).toBe("my-app");
33
43
  expect("workspaces" in output).toBe(false);
34
44
  expect("devDependencies" in output).toBe(false);
@@ -36,7 +46,8 @@ test("renderManifest prod 模式:删除 workspaces 与 devDependencies", () =>
36
46
 
37
47
  test("detectAgent:yarn / pnpm 跟随探测,其余一律 bun", () => {
38
48
  const key = "npm_config_user_agent";
39
- process.env[key] = "npm/10.9.2 node/v22.14.0 x64 workspaces/false";
49
+ process.env[key] =
50
+ "npm/10.9.2 node/v22.14.0 x64 workspaces/false";
40
51
  expect(detectAgent()).toBe("bun");
41
52
  process.env[key] = "yarn/1.22.22 npm/? node/v22.14.0 x64";
42
53
  expect(detectAgent()).toBe("yarn");
@@ -2,10 +2,17 @@
2
2
  // Copyright (c) 2026-present Koishi-CE contributors.
3
3
 
4
4
  import { expect, test } from "bun:test";
5
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
5
+ import {
6
+ mkdtempSync,
7
+ rmSync,
8
+ writeFileSync,
9
+ } from "node:fs";
6
10
  import { tmpdir } from "node:os";
7
11
  import { join } from "node:path";
8
- import { getLocalRegistry, readNpmrcRegistry } from "../index.ts";
12
+ import {
13
+ getLocalRegistry,
14
+ readNpmrcRegistry,
15
+ } from "../index.ts";
9
16
 
10
17
  test("readNpmrcRegistry:提取首个 registry= 行,忽略注释与空行", () => {
11
18
  const dir = mkdtempSync(join(tmpdir(), "ckc-npmrc-"));
@@ -14,13 +21,17 @@ test("readNpmrcRegistry:提取首个 registry= 行,忽略注释与空行", (
14
21
  file,
15
22
  "# 注释行\n; 另一种注释\n\nstrict-ssl=false\nregistry=https://registry.npmmirror.com/\r\n@scope:registry=https://example.com/\n",
16
23
  );
17
- expect(readNpmrcRegistry(file)).toBe("https://registry.npmmirror.com/");
24
+ expect(readNpmrcRegistry(file)).toBe(
25
+ "https://registry.npmmirror.com/",
26
+ );
18
27
  rmSync(dir, { recursive: true, force: true });
19
28
  });
20
29
 
21
30
  test("readNpmrcRegistry:文件不存在或未配置时返回 undefined 而不抛错", () => {
22
31
  expect(
23
- readNpmrcRegistry(join(tmpdir(), "ckc-不存在", ".npmrc")),
32
+ readNpmrcRegistry(
33
+ join(tmpdir(), "ckc-不存在", ".npmrc"),
34
+ ),
24
35
  ).toBeUndefined();
25
36
  const dir = mkdtempSync(join(tmpdir(), "ckc-npmrc-"));
26
37
  const file = join(dir, ".npmrc");
@@ -31,14 +42,18 @@ test("readNpmrcRegistry:文件不存在或未配置时返回 undefined 而不
31
42
 
32
43
  test("getLocalRegistry:环境变量优先且校验协议,非法值跳过", () => {
33
44
  const key = "npm_config_registry";
34
- const isolated = mkdtempSync(join(tmpdir(), "ckc-npmrc-"));
45
+ const isolated = mkdtempSync(
46
+ join(tmpdir(), "ckc-npmrc-"),
47
+ );
35
48
  process.env[key] = "https://registry.example.com/";
36
49
  expect(getLocalRegistry(isolated, isolated)).toBe(
37
50
  "https://registry.example.com/",
38
51
  );
39
52
  // 非 http(s) 的值视为未配置,继续走后续候选
40
53
  process.env[key] = "ftp://not-a-registry/";
41
- expect(getLocalRegistry(isolated, isolated)).toBeUndefined();
54
+ expect(
55
+ getLocalRegistry(isolated, isolated),
56
+ ).toBeUndefined();
42
57
  delete process.env[key];
43
58
  rmSync(isolated, { recursive: true, force: true });
44
59
  });
@@ -47,11 +62,20 @@ test("getLocalRegistry:项目 .npmrc 优先于用户级 .npmrc,均无配置
47
62
  const key = "npm_config_registry";
48
63
  delete process.env[key];
49
64
  const dir = mkdtempSync(join(tmpdir(), "ckc-npmrc-"));
50
- const emptyHome = mkdtempSync(join(tmpdir(), "ckc-home-"));
51
- writeFileSync(join(dir, ".npmrc"), "registry=http://localhost:4873/\n");
52
- expect(getLocalRegistry(dir, emptyHome)).toBe("http://localhost:4873/");
65
+ const emptyHome = mkdtempSync(
66
+ join(tmpdir(), "ckc-home-"),
67
+ );
68
+ writeFileSync(
69
+ join(dir, ".npmrc"),
70
+ "registry=http://localhost:4873/\n",
71
+ );
72
+ expect(getLocalRegistry(dir, emptyHome)).toBe(
73
+ "http://localhost:4873/",
74
+ );
53
75
  // 目录与用户级均无 .npmrc 时返回 undefined,主流程回落官方源
54
- expect(getLocalRegistry(emptyHome, emptyHome)).toBeUndefined();
76
+ expect(
77
+ getLocalRegistry(emptyHome, emptyHome),
78
+ ).toBeUndefined();
55
79
  rmSync(dir, { recursive: true, force: true });
56
80
  rmSync(emptyHome, { recursive: true, force: true });
57
81
  });
@@ -1,7 +1,14 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2026-present Koishi-CE contributors.
3
3
 
4
- import { afterAll, beforeAll, describe, expect, it, mock } from "bun:test";
4
+ import {
5
+ afterAll,
6
+ beforeAll,
7
+ describe,
8
+ expect,
9
+ it,
10
+ mock,
11
+ } from "bun:test";
5
12
  import { spawnSync } from "node:child_process";
6
13
  import {
7
14
  existsSync,
@@ -19,12 +26,14 @@ import { basename, join } from "node:path";
19
26
  * start() 默认(内置模板)主流程的端到端测试。
20
27
  *
21
28
  * 手段:把 process.argv 指为无位置参数 + --git,chdir 到临时目录后动态
22
- * 导入被测模块(其内部 cwd / argv 在模块顶层固化);prompts 与
29
+ * 导入被测模块(其内部 cwd / argv 在模块顶层固化);@clack/prompts 与
23
30
  * node:child_process 均 mock,分别提供可编程的问询答案与子进程记录,
24
31
  * process.exit 替换为可捕获的异常,从而覆盖全部交互分支。
25
32
  */
26
33
 
27
- const workspaceRoot = mkdtempSync(join(tmpdir(), "ckc-run-default-"));
34
+ const workspaceRoot = mkdtempSync(
35
+ join(tmpdir(), "ckc-run-default-"),
36
+ );
28
37
  const previousCwd = process.cwd();
29
38
  const previousArgv = process.argv.slice();
30
39
  const previousExit = process.exit;
@@ -34,21 +43,23 @@ const hadUserAgent = userAgentKey in process.env;
34
43
  const previousUserAgent = process.env[userAgentKey];
35
44
  delete process.env[userAgentKey];
36
45
 
37
- /** prompts 的可编程答案队列(按 type 分发) */
46
+ /** @clack/prompts 的可编程答案队列(按 prompt 类型分发) */
38
47
  const nameAnswers: string[] = [];
39
48
  const confirmAnswers: boolean[] = [];
40
49
 
41
- mock.module("prompts", () => ({
42
- default: async (question: { type?: string }) => {
43
- if (question.type === "confirm") {
44
- return { yes: confirmAnswers.shift() ?? false };
45
- }
46
- return { name: nameAnswers.shift() ?? "" };
47
- },
50
+ mock.module("@clack/prompts", () => ({
51
+ text: async () => nameAnswers.shift() ?? "",
52
+ confirm: async () => confirmAnswers.shift() ?? false,
53
+ isCancel: (value: unknown) => typeof value === "symbol",
54
+ cancel: () => {},
48
55
  }));
49
56
 
50
57
  /** spawnSync 的调用记录与可编程行为 */
51
- const spawnLog: Array<{ cmd: string; args: string[]; cwd?: string }> = [];
58
+ const spawnLog: Array<{
59
+ cmd: string;
60
+ args: string[];
61
+ cwd?: string;
62
+ }> = [];
52
63
  let installExit = 0;
53
64
 
54
65
  // 本文件求值时 node:child_process 恒为真实实现(本仓测试中唯一的
@@ -63,15 +74,21 @@ mock.module("node:child_process", () => ({
63
74
  args: readonly string[],
64
75
  options: { cwd?: string },
65
76
  ) => {
66
- if (cmd === "git" && args[0] === "--version") return { status: 0 };
67
- if (cmd === "git" && args[0] === "config" && args[1] === "--get") {
77
+ if (cmd === "git" && args[0] === "--version")
78
+ return { status: 0 };
79
+ if (
80
+ cmd === "git" &&
81
+ args[0] === "config" &&
82
+ args[1] === "--get"
83
+ ) {
68
84
  const key = args[2];
69
85
  const table: Record<string, string> = {
70
86
  "user.name": "Tester",
71
87
  "user.email": "t@e.st",
72
88
  "init.defaultBranch": "trunk",
73
89
  };
74
- const value = key !== undefined ? table[key] : undefined;
90
+ const value =
91
+ key !== undefined ? table[key] : undefined;
75
92
  return value === undefined
76
93
  ? { status: 1, stdout: "" }
77
94
  : { status: 0, stdout: `${value}\n` };
@@ -80,13 +97,21 @@ mock.module("node:child_process", () => ({
80
97
  spawnLog.push({
81
98
  cmd,
82
99
  args: [...args],
83
- ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
100
+ ...(options.cwd === undefined
101
+ ? {}
102
+ : { cwd: options.cwd }),
84
103
  });
85
104
  // 依赖安装可编程;agent 启动不真实拉起
86
- if (args[0] === "install") return { status: installExit };
87
- if (cmd === "bun" && args[0] === "run") return { status: 0 };
105
+ if (args[0] === "install")
106
+ return { status: installExit };
107
+ if (cmd === "bun" && args[0] === "run")
108
+ return { status: 0 };
88
109
  // 其余命令透传真实执行(副作用均在各自测试的临时目录内)
89
- return realSpawnSync(cmd, args, options as Parameters<typeof spawnSync>[2]);
110
+ return realSpawnSync(
111
+ cmd,
112
+ args,
113
+ options as Parameters<typeof spawnSync>[2],
114
+ );
90
115
  },
91
116
  }));
92
117
 
@@ -104,13 +129,16 @@ process.exit = ((code?: number) => {
104
129
  throw new ExitError(code ?? 0);
105
130
  }) as typeof process.exit;
106
131
 
107
- process.argv = [process.argv[0] ?? "bun", "create-koishi-ce", "--git"];
132
+ process.argv = [
133
+ process.argv[0] ?? "bun",
134
+ "create-koishi-ce",
135
+ "--git",
136
+ ];
108
137
  process.chdir(workspaceRoot);
109
138
  // query 强制独立实例(说明与写法见 run-help.test.ts)
110
139
  const specifier = "../index.ts?default-run";
111
- const { start, getLocalRegistry, readNpmrcRegistry } = (await import(
112
- specifier
113
- )) as typeof import("../index.ts");
140
+ const { start, getLocalRegistry, readNpmrcRegistry } =
141
+ (await import(specifier)) as typeof import("../index.ts");
114
142
  // cwd 已在被测模块内固化,立即切回,缩小对进程全局的影响窗口
115
143
  process.chdir(previousCwd);
116
144
 
@@ -152,20 +180,32 @@ function expectTemplateFiles(dir: string): void {
152
180
  ) as Record<string, unknown>;
153
181
  expect(manifest["private"]).toBe(true);
154
182
  expect(manifest["version"]).toBe("0.0.0");
155
- expect(manifest["workspaces"]).toEqual(["plugins/*", "external/*"]);
183
+ expect(manifest["workspaces"]).toEqual([
184
+ "plugins/*",
185
+ "external/*",
186
+ ]);
156
187
  }
157
188
 
158
189
  describe("create-koishi-ce 默认模板主流程", () => {
159
190
  // 补齐本实例下纯函数分支的行覆盖(registry 既有测试走的是共享实例)
160
191
  it("registry 探测纯函数在本实例同样可用", () => {
161
192
  const npmrc = join(workspaceRoot, ".npmrc");
162
- writeFileSync(npmrc, "registry=https://registry.example.com/\n");
163
- expect(readNpmrcRegistry(npmrc)).toBe("https://registry.example.com/");
164
- expect(readNpmrcRegistry(join(workspaceRoot, "absent"))).toBeUndefined();
165
- expect(getLocalRegistry(workspaceRoot, workspaceRoot)).toBe(
193
+ writeFileSync(
194
+ npmrc,
195
+ "registry=https://registry.example.com/\n",
196
+ );
197
+ expect(readNpmrcRegistry(npmrc)).toBe(
166
198
  "https://registry.example.com/",
167
199
  );
168
- expect(getLocalRegistry(workspaceRoot, workspaceRoot)).toBeDefined();
200
+ expect(
201
+ readNpmrcRegistry(join(workspaceRoot, "absent")),
202
+ ).toBeUndefined();
203
+ expect(
204
+ getLocalRegistry(workspaceRoot, workspaceRoot),
205
+ ).toBe("https://registry.example.com/");
206
+ expect(
207
+ getLocalRegistry(workspaceRoot, workspaceRoot),
208
+ ).toBeDefined();
169
209
  rmSync(npmrc, { force: true });
170
210
  });
171
211
 
@@ -178,7 +218,10 @@ describe("create-koishi-ce 默认模板主流程", () => {
178
218
  expect(
179
219
  (
180
220
  JSON.parse(
181
- readFileSync(join(workspaceRoot, "package.json"), "utf8"),
221
+ readFileSync(
222
+ join(workspaceRoot, "package.json"),
223
+ "utf8",
224
+ ),
182
225
  ) as Record<string, unknown>
183
226
  )["name"],
184
227
  ).toBe(basename(workspaceRoot));
@@ -196,7 +239,10 @@ describe("create-koishi-ce 默认模板主流程", () => {
196
239
  expect(
197
240
  (
198
241
  JSON.parse(
199
- readFileSync(join(workspaceRoot, "app-a", "package.json"), "utf8"),
242
+ readFileSync(
243
+ join(workspaceRoot, "app-a", "package.json"),
244
+ "utf8",
245
+ ),
200
246
  ) as Record<string, unknown>
201
247
  )["name"],
202
248
  ).toBe("app-a");
@@ -226,8 +272,12 @@ describe("create-koishi-ce 默认模板主流程", () => {
226
272
  writeFileSync(join(dir, "occupier.txt"), "保留我");
227
273
  nameAnswers.push("app-b");
228
274
  confirmAnswers.push(false);
229
- await expect(start()).rejects.toThrow("process.exit(0)");
230
- expect(readFileSync(join(dir, "occupier.txt"), "utf8")).toBe("保留我");
275
+ await expect(start()).rejects.toThrow(
276
+ "process.exit(0)",
277
+ );
278
+ expect(
279
+ readFileSync(join(dir, "occupier.txt"), "utf8"),
280
+ ).toBe("保留我");
231
281
  });
232
282
 
233
283
  it("用户确认清空:emptyDir 后重建模板", async () => {
@@ -235,14 +285,18 @@ describe("create-koishi-ce 默认模板主流程", () => {
235
285
  nameAnswers.push("app-b");
236
286
  confirmAnswers.push(true);
237
287
  await start();
238
- expect(existsSync(join(dir, "occupier.txt"))).toBe(false);
288
+ expect(existsSync(join(dir, "occupier.txt"))).toBe(
289
+ false,
290
+ );
239
291
  expectTemplateFiles(dir);
240
292
  logs.length = 0;
241
293
  });
242
294
 
243
295
  it("项目名问询被取消(空白输入):直接退出", async () => {
244
296
  nameAnswers.push(" ");
245
- await expect(start()).rejects.toThrow("process.exit(0)");
297
+ await expect(start()).rejects.toThrow(
298
+ "process.exit(0)",
299
+ );
246
300
  });
247
301
 
248
302
  it("依赖安装失败:提示后不再尝试启动", async () => {
@@ -268,11 +322,14 @@ describe("create-koishi-ce 默认模板主流程", () => {
268
322
  expect(output).toContain("cd");
269
323
  expect(output).toContain("bun install");
270
324
  expect(output).toContain("bun run start");
271
- expect(existsSync(join(workspaceRoot, "sub", "app-c", "koishi.yml"))).toBe(
272
- true,
273
- );
274
325
  expect(
275
- readdirSync(join(workspaceRoot, "sub", "app-c")).length,
326
+ existsSync(
327
+ join(workspaceRoot, "sub", "app-c", "koishi.yml"),
328
+ ),
329
+ ).toBe(true);
330
+ expect(
331
+ readdirSync(join(workspaceRoot, "sub", "app-c"))
332
+ .length,
276
333
  ).toBeGreaterThan(0);
277
334
  });
278
335
  });
@@ -1,7 +1,13 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2026-present Koishi-CE contributors.
3
3
 
4
- import { afterAll, beforeAll, describe, expect, it } from "bun:test";
4
+ import {
5
+ afterAll,
6
+ beforeAll,
7
+ describe,
8
+ expect,
9
+ it,
10
+ } from "bun:test";
5
11
 
6
12
  /**
7
13
  * start() 的 --help 分支:打印用法后直接返回(不产生任何写盘)。
@@ -11,7 +17,11 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test";
11
17
  const logs: string[] = [];
12
18
  const originalLog = console.log;
13
19
 
14
- process.argv = [process.argv[0] ?? "bun", "create-koishi-ce", "--help"];
20
+ process.argv = [
21
+ process.argv[0] ?? "bun",
22
+ "create-koishi-ce",
23
+ "--help",
24
+ ];
15
25
 
16
26
  // 以 query 强制独立实例:index.ts 顶层固化 argv / cwd,而 bun test 的
17
27
  // 模块注册表跨测试文件共享(不带 query 会拿到既有测试文件已加载的
@@ -19,7 +29,9 @@ process.argv = [process.argv[0] ?? "bun", "create-koishi-ce", "--help"];
19
29
  // 形式交给运行时(file URL 形式的 query 不参与缓存键;TS 亦不解析
20
30
  // 带 query 的说明符),故经变量动态构造,类型以 typeof import() 收紧
21
31
  const specifier = "../index.ts?help";
22
- const { start } = (await import(specifier)) as typeof import("../index.ts");
32
+ const { start } = (await import(
33
+ specifier
34
+ )) as typeof import("../index.ts");
23
35
 
24
36
  beforeAll(() => {
25
37
  console.log = (...args: unknown[]) => {
@@ -35,7 +47,9 @@ describe("create-koishi-ce --help", () => {
35
47
  it("打印用法说明后即返回", async () => {
36
48
  await start();
37
49
  const output = logs.join("\n");
38
- expect(output).toContain("用法:create-koishi-ce [名称] [选项]");
50
+ expect(output).toContain(
51
+ "用法:create-koishi-ce [名称] [选项]",
52
+ );
39
53
  expect(output).toContain("--template");
40
54
  expect(output).toContain("--prod");
41
55
  // 帮助分支不应询问项目名(无 "项目名:" 提示输出)