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/lib/bin.mjs +1 -1
- package/lib/index.d.ts +24 -16
- package/lib/index.mjs +1 -1
- package/lib/{src-DiZgIQu5.mjs → src-B1-ko5wA.mjs} +253 -172
- package/package.json +58 -63
- package/src/__tests__/manifest.test.ts +16 -5
- package/src/__tests__/registry.test.ts +34 -10
- package/src/__tests__/run-default.test.ts +96 -39
- package/src/__tests__/run-help.test.ts +18 -4
- package/src/__tests__/run-remote.test.ts +121 -56
- package/src/__tests__/tar-pack.ts +211 -0
- package/src/__tests__/template.test.ts +62 -19
- package/src/index.ts +127 -239
- package/src/manifest.ts +45 -0
- package/src/registry.ts +61 -0
- package/src/remote.ts +145 -0
- package/src/template/README.md +3 -2
- package/src/template/koishi.yml +4 -0
- package/src/template.ts +38 -18
- package/src/utils.ts +52 -0
package/package.json
CHANGED
|
@@ -1,64 +1,59 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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] =
|
|
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 {
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
51
|
-
|
|
52
|
-
|
|
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(
|
|
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 {
|
|
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
|
|
29
|
+
* 导入被测模块(其内部 cwd / argv 在模块顶层固化);@clack/prompts 与
|
|
23
30
|
* node:child_process 均 mock,分别提供可编程的问询答案与子进程记录,
|
|
24
31
|
* process.exit 替换为可捕获的异常,从而覆盖全部交互分支。
|
|
25
32
|
*/
|
|
26
33
|
|
|
27
|
-
const workspaceRoot = mkdtempSync(
|
|
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 的可编程答案队列(按
|
|
46
|
+
/** @clack/prompts 的可编程答案队列(按 prompt 类型分发) */
|
|
38
47
|
const nameAnswers: string[] = [];
|
|
39
48
|
const confirmAnswers: boolean[] = [];
|
|
40
49
|
|
|
41
|
-
mock.module("prompts", () => ({
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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<{
|
|
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")
|
|
67
|
-
|
|
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 =
|
|
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
|
|
100
|
+
...(options.cwd === undefined
|
|
101
|
+
? {}
|
|
102
|
+
: { cwd: options.cwd }),
|
|
84
103
|
});
|
|
85
104
|
// 依赖安装可编程;agent 启动不真实拉起
|
|
86
|
-
if (args[0] === "install")
|
|
87
|
-
|
|
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(
|
|
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 = [
|
|
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 } =
|
|
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([
|
|
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(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
230
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 {
|
|
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 = [
|
|
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(
|
|
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(
|
|
50
|
+
expect(output).toContain(
|
|
51
|
+
"用法:create-koishi-ce [名称] [选项]",
|
|
52
|
+
);
|
|
39
53
|
expect(output).toContain("--template");
|
|
40
54
|
expect(output).toContain("--prod");
|
|
41
55
|
// 帮助分支不应询问项目名(无 "项目名:" 提示输出)
|