create-koishi-ce 1.3.1 → 1.5.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/lib/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { t as start } from "./src-Dh_67mkm.mjs";
2
+ import { t as start } from "./src-t_F40fpK.mjs";
3
3
  //#region src/bin.ts
4
4
  /**
5
5
  * create-koishi-ce 的 CLI 可执行入口:shebang 由 rolldown 原样保留到
package/lib/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as renderManifest, i as readNpmrcRegistry, n as detectAgent, r as getLocalRegistry, t as start } from "./src-Dh_67mkm.mjs";
1
+ import { a as renderManifest, i as readNpmrcRegistry, n as detectAgent, r as getLocalRegistry, t as start } from "./src-t_F40fpK.mjs";
2
2
  export { detectAgent, getLocalRegistry, readNpmrcRegistry, renderManifest, start };
@@ -11,7 +11,7 @@ import { fileURLToPath } from "node:url";
11
11
  var package_default = {
12
12
  name: "create-koishi-ce",
13
13
  description: "Setup a Koishi application",
14
- version: "1.3.1",
14
+ version: "1.5.0",
15
15
  type: "module",
16
16
  main: "./lib/index.mjs",
17
17
  module: "./lib/index.mjs",
@@ -198,9 +198,13 @@ function writePackageJson(rootDir, project, prod) {
198
198
  * 不会拉入 npm 官方全家桶形成第二份框架副本;市场安装亦不改写该声明
199
199
  * (installer 的 isGuardedRequest 护栏将 npm:@koishi-ce alias 与
200
200
  * workspace: 同等保护);
201
- * - koishi.yml 对齐官方实例的预写策略:控制台与基础插件全量预装(依赖
202
- * 数据库的保持 ~ 禁用);本仓不再分发的 adapter / database 官方插件只以
203
- * ~ 禁用条目预写、不预装——loader 跳过禁用条目,装好后在控制台启用;
201
+ * - koishi.yml 采用配置页导出形态:插件键带 uid 实例后缀,分组带中文
202
+ * $label $collapsed 元数据;database-sqlite 默认启用,开箱即得数
203
+ * 据库;依赖数据库但非必需的插件(admin / bind / auth / broadcast /
204
+ * callme)保持 ~ 禁用;本仓不再分发的 adapter 官方插件只以 ~ 禁用条
205
+ * 目预写、不预装——loader 跳过禁用条目,装好后在控制台启用(mongo /
206
+ * mysql / postgres 等未再分发的 database 不再预写占位条目,市场装后
207
+ * 自动出现);
204
208
  * - 依赖版本统一 ^1.0.0 区间(安装时取最新 1.x),shim 版本例外(冻结线)。
205
209
  */
206
210
  /**
@@ -247,10 +251,14 @@ function baseManifest() {
247
251
  "@koishi-ce/plugin-assets-local": "^1.0.0",
248
252
  "@koishi-ce/plugin-auth": "^1.0.0",
249
253
  "@koishi-ce/plugin-bind": "^1.0.0",
254
+ "@koishi-ce/plugin-broadcast": "^1.0.0",
255
+ "@koishi-ce/plugin-callme": "^1.0.0",
250
256
  "@koishi-ce/plugin-commands": "^1.0.0",
251
257
  "@koishi-ce/plugin-config": "^1.0.0",
252
258
  "@koishi-ce/plugin-console": "^1.0.0",
259
+ "@koishi-ce/plugin-database-sqlite": "^1.0.0",
253
260
  "@koishi-ce/plugin-dataview": "^1.0.0",
261
+ "@koishi-ce/plugin-echo": "^1.0.0",
254
262
  "@koishi-ce/plugin-explorer": "^1.0.0",
255
263
  "@koishi-ce/plugin-help": "^1.0.0",
256
264
  "@koishi-ce/plugin-http": "^1.0.0",
@@ -276,6 +284,7 @@ function baseManifest() {
276
284
  devDependencies: {
277
285
  "@koishi-ce/client": "^1.0.0",
278
286
  "@koishi-ce/plugin-hmr": "^1.0.0",
287
+ "@koishi-ce/plugin-mock": "^1.0.0",
279
288
  "@koishi-ce/scripts": "^1.0.0",
280
289
  "bun-types": "^1.4.0"
281
290
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-koishi-ce",
3
3
  "description": "Setup a Koishi application",
4
- "version": "1.3.1",
4
+ "version": "1.5.0",
5
5
  "type": "module",
6
6
  "main": "./lib/index.mjs",
7
7
  "module": "./lib/index.mjs",
@@ -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,
@@ -24,7 +31,9 @@ import { basename, join } from "node:path";
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;
@@ -46,7 +55,11 @@ mock.module("@clack/prompts", () => ({
46
55
  }));
47
56
 
48
57
  /** spawnSync 的调用记录与可编程行为 */
49
- const spawnLog: Array<{ cmd: string; args: string[]; cwd?: string }> = [];
58
+ const spawnLog: Array<{
59
+ cmd: string;
60
+ args: string[];
61
+ cwd?: string;
62
+ }> = [];
50
63
  let installExit = 0;
51
64
 
52
65
  // 本文件求值时 node:child_process 恒为真实实现(本仓测试中唯一的
@@ -61,15 +74,21 @@ mock.module("node:child_process", () => ({
61
74
  args: readonly string[],
62
75
  options: { cwd?: string },
63
76
  ) => {
64
- if (cmd === "git" && args[0] === "--version") return { status: 0 };
65
- 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
+ ) {
66
84
  const key = args[2];
67
85
  const table: Record<string, string> = {
68
86
  "user.name": "Tester",
69
87
  "user.email": "t@e.st",
70
88
  "init.defaultBranch": "trunk",
71
89
  };
72
- const value = key !== undefined ? table[key] : undefined;
90
+ const value =
91
+ key !== undefined ? table[key] : undefined;
73
92
  return value === undefined
74
93
  ? { status: 1, stdout: "" }
75
94
  : { status: 0, stdout: `${value}\n` };
@@ -78,13 +97,21 @@ mock.module("node:child_process", () => ({
78
97
  spawnLog.push({
79
98
  cmd,
80
99
  args: [...args],
81
- ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
100
+ ...(options.cwd === undefined
101
+ ? {}
102
+ : { cwd: options.cwd }),
82
103
  });
83
104
  // 依赖安装可编程;agent 启动不真实拉起
84
- if (args[0] === "install") return { status: installExit };
85
- 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 };
86
109
  // 其余命令透传真实执行(副作用均在各自测试的临时目录内)
87
- 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
+ );
88
115
  },
89
116
  }));
90
117
 
@@ -102,13 +129,16 @@ process.exit = ((code?: number) => {
102
129
  throw new ExitError(code ?? 0);
103
130
  }) as typeof process.exit;
104
131
 
105
- 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
+ ];
106
137
  process.chdir(workspaceRoot);
107
138
  // query 强制独立实例(说明与写法见 run-help.test.ts)
108
139
  const specifier = "../index.ts?default-run";
109
- const { start, getLocalRegistry, readNpmrcRegistry } = (await import(
110
- specifier
111
- )) as typeof import("../index.ts");
140
+ const { start, getLocalRegistry, readNpmrcRegistry } =
141
+ (await import(specifier)) as typeof import("../index.ts");
112
142
  // cwd 已在被测模块内固化,立即切回,缩小对进程全局的影响窗口
113
143
  process.chdir(previousCwd);
114
144
 
@@ -150,20 +180,32 @@ function expectTemplateFiles(dir: string): void {
150
180
  ) as Record<string, unknown>;
151
181
  expect(manifest["private"]).toBe(true);
152
182
  expect(manifest["version"]).toBe("0.0.0");
153
- expect(manifest["workspaces"]).toEqual(["plugins/*", "external/*"]);
183
+ expect(manifest["workspaces"]).toEqual([
184
+ "plugins/*",
185
+ "external/*",
186
+ ]);
154
187
  }
155
188
 
156
189
  describe("create-koishi-ce 默认模板主流程", () => {
157
190
  // 补齐本实例下纯函数分支的行覆盖(registry 既有测试走的是共享实例)
158
191
  it("registry 探测纯函数在本实例同样可用", () => {
159
192
  const npmrc = join(workspaceRoot, ".npmrc");
160
- writeFileSync(npmrc, "registry=https://registry.example.com/\n");
161
- expect(readNpmrcRegistry(npmrc)).toBe("https://registry.example.com/");
162
- expect(readNpmrcRegistry(join(workspaceRoot, "absent"))).toBeUndefined();
163
- expect(getLocalRegistry(workspaceRoot, workspaceRoot)).toBe(
193
+ writeFileSync(
194
+ npmrc,
195
+ "registry=https://registry.example.com/\n",
196
+ );
197
+ expect(readNpmrcRegistry(npmrc)).toBe(
164
198
  "https://registry.example.com/",
165
199
  );
166
- 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();
167
209
  rmSync(npmrc, { force: true });
168
210
  });
169
211
 
@@ -176,7 +218,10 @@ describe("create-koishi-ce 默认模板主流程", () => {
176
218
  expect(
177
219
  (
178
220
  JSON.parse(
179
- readFileSync(join(workspaceRoot, "package.json"), "utf8"),
221
+ readFileSync(
222
+ join(workspaceRoot, "package.json"),
223
+ "utf8",
224
+ ),
180
225
  ) as Record<string, unknown>
181
226
  )["name"],
182
227
  ).toBe(basename(workspaceRoot));
@@ -194,7 +239,10 @@ describe("create-koishi-ce 默认模板主流程", () => {
194
239
  expect(
195
240
  (
196
241
  JSON.parse(
197
- readFileSync(join(workspaceRoot, "app-a", "package.json"), "utf8"),
242
+ readFileSync(
243
+ join(workspaceRoot, "app-a", "package.json"),
244
+ "utf8",
245
+ ),
198
246
  ) as Record<string, unknown>
199
247
  )["name"],
200
248
  ).toBe("app-a");
@@ -224,8 +272,12 @@ describe("create-koishi-ce 默认模板主流程", () => {
224
272
  writeFileSync(join(dir, "occupier.txt"), "保留我");
225
273
  nameAnswers.push("app-b");
226
274
  confirmAnswers.push(false);
227
- await expect(start()).rejects.toThrow("process.exit(0)");
228
- 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("保留我");
229
281
  });
230
282
 
231
283
  it("用户确认清空:emptyDir 后重建模板", async () => {
@@ -233,14 +285,18 @@ describe("create-koishi-ce 默认模板主流程", () => {
233
285
  nameAnswers.push("app-b");
234
286
  confirmAnswers.push(true);
235
287
  await start();
236
- expect(existsSync(join(dir, "occupier.txt"))).toBe(false);
288
+ expect(existsSync(join(dir, "occupier.txt"))).toBe(
289
+ false,
290
+ );
237
291
  expectTemplateFiles(dir);
238
292
  logs.length = 0;
239
293
  });
240
294
 
241
295
  it("项目名问询被取消(空白输入):直接退出", async () => {
242
296
  nameAnswers.push(" ");
243
- await expect(start()).rejects.toThrow("process.exit(0)");
297
+ await expect(start()).rejects.toThrow(
298
+ "process.exit(0)",
299
+ );
244
300
  });
245
301
 
246
302
  it("依赖安装失败:提示后不再尝试启动", async () => {
@@ -266,11 +322,14 @@ describe("create-koishi-ce 默认模板主流程", () => {
266
322
  expect(output).toContain("cd");
267
323
  expect(output).toContain("bun install");
268
324
  expect(output).toContain("bun run start");
269
- expect(existsSync(join(workspaceRoot, "sub", "app-c", "koishi.yml"))).toBe(
270
- true,
271
- );
272
325
  expect(
273
- 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,
274
333
  ).toBeGreaterThan(0);
275
334
  });
276
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
  // 帮助分支不应询问项目名(无 "项目名:" 提示输出)
@@ -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 {
6
13
  existsSync,
7
14
  mkdirSync,
@@ -31,14 +38,20 @@ import { tarPack } from "./tar-pack.ts";
31
38
  * utils / remote)的行覆盖完整。
32
39
  */
33
40
 
34
- const workspaceRoot = mkdtempSync(join(tmpdir(), "ckc-run-remote-"));
41
+ const workspaceRoot = mkdtempSync(
42
+ join(tmpdir(), "ckc-run-remote-"),
43
+ );
35
44
  const previousCwd = process.cwd();
36
45
  const previousArgv = process.argv.slice();
37
46
  const previousExit = process.exit;
38
47
 
39
48
  /** 伪 registry 的场景开关 */
40
- let scenario: "ok" | "meta-404" | "missing-ref" | "tarball-404" | "corrupt" =
41
- "ok";
49
+ let scenario:
50
+ | "ok"
51
+ | "meta-404"
52
+ | "missing-ref"
53
+ | "tarball-404"
54
+ | "corrupt" = "ok";
42
55
 
43
56
  /** 预先打包好的模板 tarball(package/ 前缀目录,strip:1 解包) */
44
57
  let tarball: Uint8Array;
@@ -55,7 +68,10 @@ const registry = Bun.serve({
55
68
  const url = new URL(request.url);
56
69
  if (url.pathname === "/fake-tpl") {
57
70
  if (scenario === "meta-404") {
58
- return new Response(null, { status: 404, statusText: "Not Found" });
71
+ return new Response(null, {
72
+ status: 404,
73
+ statusText: "Not Found",
74
+ });
59
75
  }
60
76
  if (scenario === "missing-ref") {
61
77
  return Response.json({
@@ -76,10 +92,15 @@ const registry = Bun.serve({
76
92
  }
77
93
  if (url.pathname === "/fake-tpl/-/fake-tpl-2.0.0.tgz") {
78
94
  if (scenario === "tarball-404") {
79
- return new Response(null, { status: 404, statusText: "Not Found" });
95
+ return new Response(null, {
96
+ status: 404,
97
+ statusText: "Not Found",
98
+ });
80
99
  }
81
100
  if (scenario === "corrupt") {
82
- return new Response("definitely not a tarball", { status: 200 });
101
+ return new Response("definitely not a tarball", {
102
+ status: 200,
103
+ });
83
104
  }
84
105
  return new Response(tarball);
85
106
  }
@@ -127,9 +148,8 @@ process.argv = [
127
148
  process.chdir(workspaceRoot);
128
149
  // query 强制独立实例(说明见 run-help.test.ts)
129
150
  const specifier = "../index.ts?remote-run";
130
- const { start, getLocalRegistry, readNpmrcRegistry } = (await import(
131
- specifier
132
- )) as typeof import("../index.ts");
151
+ const { start, getLocalRegistry, readNpmrcRegistry } =
152
+ (await import(specifier)) as typeof import("../index.ts");
133
153
  process.chdir(previousCwd);
134
154
 
135
155
  const logs: string[] = [];
@@ -179,11 +199,17 @@ afterAll(() => {
179
199
  function reset(sc: typeof scenario): void {
180
200
  scenario = sc;
181
201
  logs.length = 0;
182
- rmSync(join(workspaceRoot, "myapp"), { recursive: true, force: true });
202
+ rmSync(join(workspaceRoot, "myapp"), {
203
+ recursive: true,
204
+ force: true,
205
+ });
183
206
  // giget 把 tarball 缓存到系统临时目录(win32 为 tmpdir()/giget,不受
184
207
  // XDG_CACHE_HOME 影响);逐用例清理,避免上一场景的缓存(tarball-404
185
208
  // 的缺失或 corrupt 的损坏归档)被 giget 的「下载失败回退缓存」吞掉
186
- rmSync(join(tmpdir(), "giget"), { recursive: true, force: true });
209
+ rmSync(join(tmpdir(), "giget"), {
210
+ recursive: true,
211
+ force: true,
212
+ });
187
213
  }
188
214
 
189
215
  describe("create-koishi-ce 远程模板", () => {
@@ -193,7 +219,9 @@ describe("create-koishi-ce 远程模板", () => {
193
219
  const dir = join(workspaceRoot, "myapp");
194
220
  expect(existsSync(join(dir, "index.js"))).toBe(true);
195
221
  // 超长名文件经 pax 扩展头正常落盘(tarPack 打包 → giget 解包全链路)
196
- expect(readFileSync(join(dir, `${longName}.txt`), "utf8")).toBe("pax");
222
+ expect(
223
+ readFileSync(join(dir, `${longName}.txt`), "utf8"),
224
+ ).toBe("pax");
197
225
  const manifest = JSON.parse(
198
226
  readFileSync(join(dir, "package.json"), "utf8"),
199
227
  ) as Record<string, unknown>;
@@ -207,19 +235,27 @@ describe("create-koishi-ce 远程模板", () => {
207
235
 
208
236
  it("模板包元数据 404:HttpError 提示后以码 1 退出", async () => {
209
237
  reset("meta-404");
210
- await expect(start()).rejects.toThrow("process.exit(1)");
238
+ await expect(start()).rejects.toThrow(
239
+ "process.exit(1)",
240
+ );
211
241
  expect(logs.join("\n")).toContain("请求失败:HTTP 404");
212
242
  });
213
243
 
214
244
  it("dist-tag 引用不存在:以码 1 退出", async () => {
215
245
  reset("missing-ref");
216
- await expect(start()).rejects.toThrow("process.exit(1)");
217
- expect(logs.join("\n")).toContain("模板 fake-tpl@beta 不存在");
246
+ await expect(start()).rejects.toThrow(
247
+ "process.exit(1)",
248
+ );
249
+ expect(logs.join("\n")).toContain(
250
+ "模板 fake-tpl@beta 不存在",
251
+ );
218
252
  });
219
253
 
220
254
  it("tarball 下载 404:以码 1 退出", async () => {
221
255
  reset("tarball-404");
222
- await expect(start()).rejects.toThrow("process.exit(1)");
256
+ await expect(start()).rejects.toThrow(
257
+ "process.exit(1)",
258
+ );
223
259
  expect(logs.join("\n")).toContain("请求失败:HTTP 404");
224
260
  });
225
261
 
@@ -239,23 +275,36 @@ describe("create-koishi-ce 远程模板", () => {
239
275
  writeFileSync(join(dir, "occupier.txt"), "占位");
240
276
  confirmAnswers.push(true);
241
277
  await start();
242
- expect(existsSync(join(dir, "occupier.txt"))).toBe(false);
278
+ expect(existsSync(join(dir, "occupier.txt"))).toBe(
279
+ false,
280
+ );
243
281
  expect(existsSync(join(dir, "index.js"))).toBe(true);
244
282
  });
245
283
 
246
284
  it("registry 探测纯函数(补本实例的行覆盖)", () => {
247
285
  const npmrc = join(workspaceRoot, ".npmrc");
248
- writeFileSync(npmrc, "registry=https://registry.example.com/\n");
249
- expect(readNpmrcRegistry(npmrc)).toBe("https://registry.example.com/");
250
- expect(
251
- readNpmrcRegistry(join(workspaceRoot, "absent.npmrc")),
252
- ).toBeUndefined();
253
- expect(getLocalRegistry(workspaceRoot, workspaceRoot)).toBe(
286
+ writeFileSync(
287
+ npmrc,
288
+ "registry=https://registry.example.com/\n",
289
+ );
290
+ expect(readNpmrcRegistry(npmrc)).toBe(
254
291
  "https://registry.example.com/",
255
292
  );
293
+ expect(
294
+ readNpmrcRegistry(
295
+ join(workspaceRoot, "absent.npmrc"),
296
+ ),
297
+ ).toBeUndefined();
298
+ expect(
299
+ getLocalRegistry(workspaceRoot, workspaceRoot),
300
+ ).toBe("https://registry.example.com/");
256
301
  rmSync(npmrc, { force: true });
257
- expect(getLocalRegistry(workspaceRoot, workspaceRoot)).toBeUndefined();
302
+ expect(
303
+ getLocalRegistry(workspaceRoot, workspaceRoot),
304
+ ).toBeUndefined();
258
305
  // 临时目录内容核对后由 afterAll 统一清理
259
- expect(readdirSync(workspaceRoot).length).toBeGreaterThan(0);
306
+ expect(
307
+ readdirSync(workspaceRoot).length,
308
+ ).toBeGreaterThan(0);
260
309
  });
261
310
  });