create-koishi-ce 1.5.0 → 1.6.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/README.md +6 -6
- package/lib/bin.mjs +1 -1
- package/lib/index.d.ts +6 -6
- package/lib/index.mjs +1 -1
- package/lib/{src-t_F40fpK.mjs → src-C3GEQDVG.mjs} +38 -23
- package/package.json +1 -1
- package/src/__tests__/manifest.test.ts +4 -4
- package/src/__tests__/registry.test.ts +4 -4
- package/src/__tests__/run-default.test.ts +107 -56
- package/src/__tests__/tar-pack.ts +1 -3
- package/src/__tests__/template.test.ts +21 -0
- package/src/index.ts +13 -8
- package/src/registry.ts +1 -1
- package/src/template/README.md +15 -1
- package/src/template/koishi.yml +1 -0
- package/src/template.ts +15 -3
- package/src/utils.ts +13 -11
package/README.md
CHANGED
|
@@ -27,11 +27,11 @@ npm create koishi-ce my-app
|
|
|
27
27
|
|
|
28
28
|
## 生成的项目
|
|
29
29
|
|
|
30
|
-
- **package.json**:`type: module`、`packageManager` 钉 Bun、workspaces 含 `plugins/*` 与 `external/*`;dependencies 预装全套 `@koishi-ce` 常用插件(console、config、market、auth、admin、help、sandbox 等),并预置四行 npm alias 钉住上游名——`koishi` / `@koishijs/core` / `@koishijs/loader` 指向 `@koishi-ce/koishi-shim`、`@koishijs/plugin-console` 指向 `@koishi-ce/console-shim`,社区插件由此全部解析到 CE 框架;
|
|
31
|
-
- **koishi.yml**:按 server(端口 5140-5149)/ basic / console(自动打开、市场指向 registry.koishi.chat 镜像)/ storage / adapter / develop(开发模式启用热重载)分组预写;官方 adapter
|
|
30
|
+
- **package.json**:`type: module`、`packageManager` 钉 Bun、workspaces 含 `plugins/*` 与 `external/*`;dependencies 预装全套 `@koishi-ce` 常用插件(console、config、market、auth、admin、help、sandbox、database-sqlite 等),并预置四行 npm alias 钉住上游名——`koishi` / `@koishijs/core` / `@koishijs/loader` 指向 `@koishi-ce/koishi-shim`、`@koishijs/plugin-console` 指向 `@koishi-ce/console-shim`,社区插件由此全部解析到 CE 框架;
|
|
31
|
+
- **koishi.yml**:按 server(端口 5140-5149)/ basic / console(自动打开、市场指向 registry.koishi.chat 镜像)/ storage / adapter / develop(开发模式启用热重载)分组预写;官方 adapter 插件以 `~` 停用条目预写、不预装,装好后在控制台按需启用;数据库开箱即用——`database-sqlite` 默认启用,mongo / mysql / postgres 等其余 database 不预写占位条目,市场安装后自动出现;
|
|
32
32
|
- **开发工具链**:devDependencies 预置 `@koishi-ce/client`、`@koishi-ce/plugin-hmr` 与 `@koishi-ce/scripts`(`koishi-scripts setup` 可在 external/ 下继续创建插件项目)。
|
|
33
33
|
|
|
34
|
-
不预装任何 adapter
|
|
34
|
+
不预装任何 adapter——官方 adapter 可后续从市场安装,peer 已被 alias 钉住不会形成双实例;数据库已由默认启用的 `database-sqlite` 覆盖,其他官方 database 同样可从市场安装。
|
|
35
35
|
|
|
36
36
|
## 许可证
|
|
37
37
|
|
|
@@ -66,11 +66,11 @@ Interactive flow: project name; confirm cleaning a non-empty target directory; c
|
|
|
66
66
|
|
|
67
67
|
## Generated project
|
|
68
68
|
|
|
69
|
-
- **package.json** — `type: module`, `packageManager` pinned to Bun, workspaces covering `plugins/*` and `external/*`; dependencies preinstall the common `@koishi-ce` plugin set (console, config, market, auth, admin, help, sandbox, etc.) plus the four npm-alias lines pinning the upstream names (`koishi` / `@koishijs/core` / `@koishijs/loader` to `@koishi-ce/koishi-shim`, `@koishijs/plugin-console` to `@koishi-ce/console-shim`), so community plugins all resolve to the CE framework.
|
|
70
|
-
- **koishi.yml** — grouped prewrites for server (ports 5140-5149), basic, console (auto-open, market pointed at the registry.koishi.chat mirror), storage, adapter and develop (HMR in development mode); official adapter
|
|
69
|
+
- **package.json** — `type: module`, `packageManager` pinned to Bun, workspaces covering `plugins/*` and `external/*`; dependencies preinstall the common `@koishi-ce` plugin set (console, config, market, auth, admin, help, sandbox, database-sqlite, etc.) plus the four npm-alias lines pinning the upstream names (`koishi` / `@koishijs/core` / `@koishijs/loader` to `@koishi-ce/koishi-shim`, `@koishijs/plugin-console` to `@koishi-ce/console-shim`), so community plugins all resolve to the CE framework.
|
|
70
|
+
- **koishi.yml** — grouped prewrites for server (ports 5140-5149), basic, console (auto-open, market pointed at the registry.koishi.chat mirror), storage, adapter and develop (HMR in development mode); official adapter plugins are prewritten as `~`-disabled entries without being installed — enable them in the console after installing; the database works out of the box (`database-sqlite` enabled by default), while other official databases (mongo / mysql / postgres, ...) leave no placeholder entries and appear automatically once installed from the market.
|
|
71
71
|
- **Toolchain** — devDependencies include `@koishi-ce/client`, `@koishi-ce/plugin-hmr` and `@koishi-ce/scripts` (`koishi-scripts setup` scaffolds further plugin projects under external/).
|
|
72
72
|
|
|
73
|
-
No adapter
|
|
73
|
+
No adapter is preinstalled — official adapters can be installed from the market later, with their peers already pinned by the aliases; the database is covered by the default-enabled `database-sqlite`, and other official databases are equally one market install away.
|
|
74
74
|
|
|
75
75
|
## License
|
|
76
76
|
|
package/lib/bin.mjs
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -21,20 +21,20 @@ interface Manifest {
|
|
|
21
21
|
* 改写模板的 package.json(纯函数,导出供单测):替换项目名、标记
|
|
22
22
|
* private、版本归零。
|
|
23
23
|
*/
|
|
24
|
-
declare function renderManifest(source: Manifest, project: string, prod: boolean): string;
|
|
24
|
+
export declare function renderManifest(source: Manifest, project: string, prod: boolean): string;
|
|
25
25
|
//#endregion
|
|
26
26
|
//#region src/registry.d.ts
|
|
27
27
|
/**
|
|
28
28
|
* 从单个 .npmrc 文件提取 registry 配置项(非注释、非 scoped 的 registry= 行)。
|
|
29
29
|
* 文件不存在或未配置时返回 undefined,读文件异常一律静默吞掉。
|
|
30
30
|
*/
|
|
31
|
-
declare function readNpmrcRegistry(file: string): string | undefined;
|
|
31
|
+
export declare function readNpmrcRegistry(file: string): string | undefined;
|
|
32
32
|
/**
|
|
33
33
|
* 读取本机 npm registry 配置(优先级对齐 npm 自身:环境变量 > 项目
|
|
34
34
|
* .npmrc > 用户 ~/.npmrc)。任何一步都拿不到时返回 undefined,由调用方
|
|
35
35
|
* 回落官方源。
|
|
36
36
|
*/
|
|
37
|
-
declare function getLocalRegistry(cwd?: string, userHome?: string): string | undefined;
|
|
37
|
+
export declare function getLocalRegistry(cwd?: string, userHome?: string): string | undefined;
|
|
38
38
|
//#endregion
|
|
39
39
|
//#region src/utils.d.ts
|
|
40
40
|
/**
|
|
@@ -42,13 +42,13 @@ declare function getLocalRegistry(cwd?: string, userHome?: string): string | und
|
|
|
42
42
|
* 生态习惯;其余场景(npm、bun 及探测不到 user-agent)一律走 bun——
|
|
43
43
|
* 本 CLI 自身以 bun 为运行时(bin shebang),能执行即已具备 bun 环境。
|
|
44
44
|
*/
|
|
45
|
-
declare function detectAgent(): string;
|
|
45
|
+
export declare function detectAgent(): string;
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region src/index.d.ts
|
|
48
48
|
/**
|
|
49
49
|
* CLI 主流程:--help 打印用法后即返回;否则依次执行
|
|
50
50
|
* 项目名询问 → prepare(目录准备)→ scaffold(模板解包)→ initGit → install。
|
|
51
51
|
*/
|
|
52
|
-
declare function start(): Promise<void>;
|
|
52
|
+
export declare function start(): Promise<void>;
|
|
53
53
|
//#endregion
|
|
54
|
-
export {
|
|
54
|
+
export type { Manifest };
|
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-
|
|
1
|
+
import { a as renderManifest, i as readNpmrcRegistry, n as detectAgent, r as getLocalRegistry, t as start } from "./src-C3GEQDVG.mjs";
|
|
2
2
|
export { detectAgent, getLocalRegistry, readNpmrcRegistry, renderManifest, start };
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename,
|
|
2
|
+
import { basename, join, relative } from "node:path";
|
|
4
3
|
import { parseArgs } from "node:util";
|
|
5
4
|
import * as p from "@clack/prompts";
|
|
6
5
|
import pc from "picocolors";
|
|
7
6
|
import { homedir } from "node:os";
|
|
8
7
|
import { downloadTemplate } from "giget";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
8
|
//#region package.json
|
|
11
9
|
var package_default = {
|
|
12
10
|
name: "create-koishi-ce",
|
|
13
11
|
description: "Setup a Koishi application",
|
|
14
|
-
version: "1.
|
|
12
|
+
version: "1.6.0",
|
|
15
13
|
type: "module",
|
|
16
14
|
main: "./lib/index.mjs",
|
|
17
15
|
module: "./lib/index.mjs",
|
|
@@ -100,7 +98,7 @@ function readNpmrcRegistry(file) {
|
|
|
100
98
|
*/
|
|
101
99
|
function getLocalRegistry(cwd = process.cwd(), userHome = homedir()) {
|
|
102
100
|
const candidates = [
|
|
103
|
-
|
|
101
|
+
Bun.env["npm_config_registry"],
|
|
104
102
|
readNpmrcRegistry(join(cwd, ".npmrc")),
|
|
105
103
|
readNpmrcRegistry(join(userHome, ".npmrc"))
|
|
106
104
|
];
|
|
@@ -213,7 +211,7 @@ function writePackageJson(rootDir, project, prod) {
|
|
|
213
211
|
* 恒存在)。
|
|
214
212
|
*/
|
|
215
213
|
function locateTemplateDir() {
|
|
216
|
-
const base =
|
|
214
|
+
const base = import.meta.dir;
|
|
217
215
|
for (const dir of [join(base, "template"), join(base, "../src/template")]) if (existsSync(dir)) return dir;
|
|
218
216
|
throw new Error("create-koishi-ce 内置模板目录缺失(src/template)");
|
|
219
217
|
}
|
|
@@ -241,7 +239,12 @@ function baseManifest() {
|
|
|
241
239
|
scripts: {
|
|
242
240
|
start: "koishi start",
|
|
243
241
|
dev: "NODE_ENV=development koishi start",
|
|
244
|
-
new: "koishi-scripts setup"
|
|
242
|
+
new: "koishi-scripts setup",
|
|
243
|
+
clone: "koishi-scripts clone",
|
|
244
|
+
build: "koishi-scripts build",
|
|
245
|
+
"release:version": "koishi-scripts version",
|
|
246
|
+
"release:dryrun": "bun run release:version && bun run build && koishi-scripts publish --dry-run",
|
|
247
|
+
release: "bun run release:version && bun run build && koishi-scripts publish"
|
|
245
248
|
},
|
|
246
249
|
dependencies: {
|
|
247
250
|
"@koishi-ce/koishi": "^1.0.0",
|
|
@@ -305,23 +308,28 @@ function baseManifest() {
|
|
|
305
308
|
* 本 CLI 自身以 bun 为运行时(bin shebang),能执行即已具备 bun 环境。
|
|
306
309
|
*/
|
|
307
310
|
function detectAgent() {
|
|
308
|
-
const ua =
|
|
311
|
+
const ua = Bun.env["npm_config_user_agent"] ?? "";
|
|
309
312
|
if (ua.startsWith("yarn")) return "yarn";
|
|
310
313
|
if (ua.startsWith("pnpm")) return "pnpm";
|
|
311
314
|
return "bun";
|
|
312
315
|
}
|
|
313
316
|
/** 静默执行命令探测其是否可用(如 git --version),失败即视为不可用 */
|
|
314
317
|
function supports(command) {
|
|
315
|
-
return spawnSync(
|
|
318
|
+
return Bun.spawnSync({
|
|
319
|
+
cmd: [...command],
|
|
320
|
+
stdout: "ignore",
|
|
321
|
+
stderr: "ignore"
|
|
322
|
+
}).success;
|
|
316
323
|
}
|
|
317
324
|
/** 读 git 全局配置单项(读不到 → 空串) */
|
|
318
325
|
function gitConfig(key) {
|
|
319
|
-
const res = spawnSync(
|
|
326
|
+
const res = Bun.spawnSync([
|
|
327
|
+
"git",
|
|
320
328
|
"config",
|
|
321
329
|
"--get",
|
|
322
330
|
key
|
|
323
|
-
]
|
|
324
|
-
return res.
|
|
331
|
+
]);
|
|
332
|
+
return res.success ? res.stdout.toString().trim() : "";
|
|
325
333
|
}
|
|
326
334
|
/** 递归清空目录内容(目录本身保留)。 */
|
|
327
335
|
function emptyDir(root) {
|
|
@@ -475,12 +483,15 @@ async function scaffold() {
|
|
|
475
483
|
async function initGit() {
|
|
476
484
|
if (!argv.git || !supports(["git", "--version"])) return;
|
|
477
485
|
const branch = gitConfig("init.defaultBranch") || "main";
|
|
478
|
-
spawnSync(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
486
|
+
Bun.spawnSync({
|
|
487
|
+
cmd: [
|
|
488
|
+
"git",
|
|
489
|
+
"init",
|
|
490
|
+
"-b",
|
|
491
|
+
branch
|
|
492
|
+
],
|
|
493
|
+
stdout: "ignore",
|
|
494
|
+
stderr: "ignore",
|
|
484
495
|
cwd: rootDir
|
|
485
496
|
});
|
|
486
497
|
console.log(pc.green(` 已初始化 git 仓库(分支 ${branch})。\n`));
|
|
@@ -494,15 +505,19 @@ async function install() {
|
|
|
494
505
|
const agent = detectAgent();
|
|
495
506
|
const startArgs = agent === "yarn" ? ["start"] : ["run", "start"];
|
|
496
507
|
if (await confirm("现在安装依赖并启动吗?")) {
|
|
497
|
-
if (spawnSync(
|
|
498
|
-
|
|
508
|
+
if (!Bun.spawnSync({
|
|
509
|
+
cmd: [agent, "install"],
|
|
510
|
+
stdout: "inherit",
|
|
511
|
+
stderr: "inherit",
|
|
499
512
|
cwd: rootDir
|
|
500
|
-
}).
|
|
513
|
+
}).success) {
|
|
501
514
|
console.log(pc.red(" 依赖安装失败,请检查上方日志。"));
|
|
502
515
|
return;
|
|
503
516
|
}
|
|
504
|
-
spawnSync(
|
|
505
|
-
|
|
517
|
+
Bun.spawnSync({
|
|
518
|
+
cmd: [agent, ...startArgs],
|
|
519
|
+
stdout: "inherit",
|
|
520
|
+
stderr: "inherit",
|
|
506
521
|
cwd: rootDir
|
|
507
522
|
});
|
|
508
523
|
} else {
|
package/package.json
CHANGED
|
@@ -46,13 +46,13 @@ test("renderManifest prod 模式:删除 workspaces 与 devDependencies", () =>
|
|
|
46
46
|
|
|
47
47
|
test("detectAgent:yarn / pnpm 跟随探测,其余一律 bun", () => {
|
|
48
48
|
const key = "npm_config_user_agent";
|
|
49
|
-
|
|
49
|
+
Bun.env[key] =
|
|
50
50
|
"npm/10.9.2 node/v22.14.0 x64 workspaces/false";
|
|
51
51
|
expect(detectAgent()).toBe("bun");
|
|
52
|
-
|
|
52
|
+
Bun.env[key] = "yarn/1.22.22 npm/? node/v22.14.0 x64";
|
|
53
53
|
expect(detectAgent()).toBe("yarn");
|
|
54
|
-
|
|
54
|
+
Bun.env[key] = "pnpm/10.12.1 npm/? node/v22.14.0 x64";
|
|
55
55
|
expect(detectAgent()).toBe("pnpm");
|
|
56
|
-
delete
|
|
56
|
+
delete Bun.env[key];
|
|
57
57
|
expect(detectAgent()).toBe("bun");
|
|
58
58
|
});
|
|
@@ -45,22 +45,22 @@ test("getLocalRegistry:环境变量优先且校验协议,非法值跳过", (
|
|
|
45
45
|
const isolated = mkdtempSync(
|
|
46
46
|
join(tmpdir(), "ckc-npmrc-"),
|
|
47
47
|
);
|
|
48
|
-
|
|
48
|
+
Bun.env[key] = "https://registry.example.com/";
|
|
49
49
|
expect(getLocalRegistry(isolated, isolated)).toBe(
|
|
50
50
|
"https://registry.example.com/",
|
|
51
51
|
);
|
|
52
52
|
// 非 http(s) 的值视为未配置,继续走后续候选
|
|
53
|
-
|
|
53
|
+
Bun.env[key] = "ftp://not-a-registry/";
|
|
54
54
|
expect(
|
|
55
55
|
getLocalRegistry(isolated, isolated),
|
|
56
56
|
).toBeUndefined();
|
|
57
|
-
delete
|
|
57
|
+
delete Bun.env[key];
|
|
58
58
|
rmSync(isolated, { recursive: true, force: true });
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
test("getLocalRegistry:项目 .npmrc 优先于用户级 .npmrc,均无配置时返回 undefined", () => {
|
|
62
62
|
const key = "npm_config_registry";
|
|
63
|
-
delete
|
|
63
|
+
delete Bun.env[key];
|
|
64
64
|
const dir = mkdtempSync(join(tmpdir(), "ckc-npmrc-"));
|
|
65
65
|
const emptyHome = mkdtempSync(
|
|
66
66
|
join(tmpdir(), "ckc-home-"),
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
expect,
|
|
9
9
|
it,
|
|
10
10
|
mock,
|
|
11
|
+
spyOn,
|
|
11
12
|
} from "bun:test";
|
|
12
|
-
import { spawnSync } from "node:child_process";
|
|
13
13
|
import {
|
|
14
14
|
existsSync,
|
|
15
15
|
mkdirSync,
|
|
@@ -39,9 +39,9 @@ const previousArgv = process.argv.slice();
|
|
|
39
39
|
const previousExit = process.exit;
|
|
40
40
|
// 保持无 user-agent 的探测环境(detectAgent 走 bun 分支)
|
|
41
41
|
const userAgentKey = "npm_config_user_agent";
|
|
42
|
-
const hadUserAgent = userAgentKey in
|
|
43
|
-
const previousUserAgent =
|
|
44
|
-
delete
|
|
42
|
+
const hadUserAgent = userAgentKey in Bun.env;
|
|
43
|
+
const previousUserAgent = Bun.env[userAgentKey];
|
|
44
|
+
delete Bun.env[userAgentKey];
|
|
45
45
|
|
|
46
46
|
/** @clack/prompts 的可编程答案队列(按 prompt 类型分发) */
|
|
47
47
|
const nameAnswers: string[] = [];
|
|
@@ -62,58 +62,109 @@ const spawnLog: Array<{
|
|
|
62
62
|
}> = [];
|
|
63
63
|
let installExit = 0;
|
|
64
64
|
|
|
65
|
-
// 本文件求值时
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
const realSpawnSync = spawnSync;
|
|
65
|
+
// 本文件求值时 Bun.spawnSync 恒为真实实现(本仓测试中唯一的子进程 mock
|
|
66
|
+
// 注册点),持有真实引用供未知命令透传;mock 改写会波及其它测试文件经
|
|
67
|
+
// 全局命名空间发起的调用(如 setup 的 git init、release 的子进程测试),
|
|
68
|
+
// 透传保证它们拿到真实行为
|
|
69
|
+
const realSpawnSync = Bun.spawnSync;
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
71
|
+
/** 构造 SyncSubprocess 形态的假结果(补全 resourceUsage / pid 等必要字段) */
|
|
72
|
+
function fakeResult(exitCode: number, stdout = "") {
|
|
73
|
+
return {
|
|
74
|
+
exitCode,
|
|
75
|
+
success: exitCode === 0,
|
|
76
|
+
stdout,
|
|
77
|
+
stderr: "",
|
|
78
|
+
} as unknown as ReturnType<typeof Bun.spawnSync>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Bun.spawnSync 调用选项中被本 mock 关心的子集(透传时原样保留) */
|
|
82
|
+
interface SpawnOptionsLike {
|
|
83
|
+
cmd?: readonly string[];
|
|
84
|
+
cwd?: string;
|
|
85
|
+
stdout?: "ignore" | "inherit" | "pipe";
|
|
86
|
+
stderr?: "ignore" | "inherit" | "pipe";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 把 Bun.spawnSync 的两种调用形态归一为 { cmd, args, cwd, passthrough }。
|
|
91
|
+
* invoke 为 unknown 数组(重载联合无法静态表达),内部运行时收窄。
|
|
92
|
+
*/
|
|
93
|
+
function normalize(invoke: readonly unknown[]): {
|
|
94
|
+
cmd: string;
|
|
95
|
+
args: string[];
|
|
96
|
+
cwd?: string;
|
|
97
|
+
passthrough: () => ReturnType<typeof Bun.spawnSync>;
|
|
98
|
+
} {
|
|
99
|
+
const first = invoke[0];
|
|
100
|
+
const options = (
|
|
101
|
+
Array.isArray(first) ? (invoke[1] ?? {}) : first
|
|
102
|
+
) as SpawnOptionsLike;
|
|
103
|
+
const argv = (
|
|
104
|
+
Array.isArray(first) ? first : (options.cmd ?? [])
|
|
105
|
+
) as readonly string[];
|
|
106
|
+
const cmd = argv[0] ?? "";
|
|
107
|
+
const args = [...argv.slice(1)];
|
|
108
|
+
return {
|
|
109
|
+
cmd,
|
|
110
|
+
args,
|
|
111
|
+
...(options.cwd === undefined
|
|
112
|
+
? {}
|
|
113
|
+
: { cwd: options.cwd }),
|
|
114
|
+
passthrough: () =>
|
|
115
|
+
realSpawnSync({
|
|
116
|
+
cmd: [cmd, ...args],
|
|
117
|
+
...(options.stdout === undefined
|
|
118
|
+
? {}
|
|
119
|
+
: { stdout: options.stdout }),
|
|
120
|
+
...(options.stderr === undefined
|
|
121
|
+
? {}
|
|
122
|
+
: { stderr: options.stderr }),
|
|
123
|
+
...(options.cwd === undefined
|
|
124
|
+
? {}
|
|
125
|
+
: { cwd: options.cwd }),
|
|
126
|
+
}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** 拦截 Bun.spawnSync:git 探测 / 配置读取走桩,其余记录或透传真实执行 */
|
|
131
|
+
spyOn(Bun, "spawnSync").mockImplementation(((
|
|
132
|
+
...invoke: readonly unknown[]
|
|
133
|
+
) => {
|
|
134
|
+
const { cmd, args, cwd, passthrough } = normalize(invoke);
|
|
135
|
+
if (cmd === "git" && args[0] === "--version")
|
|
136
|
+
return fakeResult(0);
|
|
137
|
+
if (
|
|
138
|
+
cmd === "git" &&
|
|
139
|
+
args[0] === "config" &&
|
|
140
|
+
args[1] === "--get"
|
|
141
|
+
) {
|
|
142
|
+
const key = args[2];
|
|
143
|
+
const table: Record<string, string> = {
|
|
144
|
+
"user.name": "Tester",
|
|
145
|
+
"user.email": "t@e.st",
|
|
146
|
+
"init.defaultBranch": "trunk",
|
|
147
|
+
};
|
|
148
|
+
const value =
|
|
149
|
+
key !== undefined ? table[key] : undefined;
|
|
150
|
+
return fakeResult(
|
|
151
|
+
value === undefined ? 1 : 0,
|
|
152
|
+
value === undefined ? "" : `${value}\n`,
|
|
114
153
|
);
|
|
115
|
-
}
|
|
116
|
-
|
|
154
|
+
}
|
|
155
|
+
// exactOptionalPropertyTypes:cwd 未传时不落键,避免显式写入 undefined
|
|
156
|
+
spawnLog.push({
|
|
157
|
+
cmd,
|
|
158
|
+
args,
|
|
159
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
160
|
+
});
|
|
161
|
+
// 依赖安装可编程;agent 启动不真实拉起
|
|
162
|
+
if (args[0] === "install") return fakeResult(installExit);
|
|
163
|
+
if (cmd === "bun" && args[0] === "run")
|
|
164
|
+
return fakeResult(0);
|
|
165
|
+
// 其余命令透传真实执行(副作用均在各自测试的临时目录内)
|
|
166
|
+
return passthrough();
|
|
167
|
+
}) as never);
|
|
117
168
|
|
|
118
169
|
/** process.exit 替换为异常,供断言「直接退出」的分支 */
|
|
119
170
|
// erasableSyntaxOnly 禁用参数属性,改为显式字段声明
|
|
@@ -156,9 +207,9 @@ afterAll(() => {
|
|
|
156
207
|
process.exit = previousExit;
|
|
157
208
|
process.argv = previousArgv;
|
|
158
209
|
if (hadUserAgent) {
|
|
159
|
-
|
|
210
|
+
Bun.env[userAgentKey] = previousUserAgent;
|
|
160
211
|
} else {
|
|
161
|
-
delete
|
|
212
|
+
delete Bun.env[userAgentKey];
|
|
162
213
|
}
|
|
163
214
|
rmSync(workspaceRoot, { recursive: true, force: true });
|
|
164
215
|
});
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
* 端到端链路间接验证。
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { gzipSync } from "node:zlib";
|
|
15
|
-
|
|
16
14
|
const BLOCK = 512;
|
|
17
15
|
const encoder = new TextEncoder();
|
|
18
16
|
|
|
@@ -207,5 +205,5 @@ export async function tarPack(
|
|
|
207
205
|
}
|
|
208
206
|
|
|
209
207
|
// gzip 压缩为 .tgz(与 npm registry 的 tarball 同构)
|
|
210
|
-
return gzipSync(raw);
|
|
208
|
+
return Bun.gzipSync(raw);
|
|
211
209
|
}
|
|
@@ -48,6 +48,27 @@ test("内置模板 packageManager 钉 Bun,脚本用 Bun Shell 环境变量前
|
|
|
48
48
|
);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
test("内置模板提供插件开发全链脚本入口(koishi-scripts 子命令形态)", () => {
|
|
52
|
+
const { scripts } = baseManifest() as unknown as {
|
|
53
|
+
scripts: Record<string, string>;
|
|
54
|
+
};
|
|
55
|
+
// 插件开发链 = devDep @koishi-ce/scripts 的 koishi-scripts CLI 子命令;
|
|
56
|
+
// new 用主命令名 setup(new/create/init 是 koishi-scripts 的别名)
|
|
57
|
+
expect(scripts["new"]).toBe("koishi-scripts setup");
|
|
58
|
+
expect(scripts["clone"]).toBe("koishi-scripts clone");
|
|
59
|
+
expect(scripts["build"]).toBe("koishi-scripts build");
|
|
60
|
+
expect(scripts["release:version"]).toBe(
|
|
61
|
+
"koishi-scripts version",
|
|
62
|
+
);
|
|
63
|
+
// release 链 = version → build → publish 三环串联(前环自引用脚本)
|
|
64
|
+
expect(scripts["release"]).toBe(
|
|
65
|
+
"bun run release:version && bun run build && koishi-scripts publish",
|
|
66
|
+
);
|
|
67
|
+
expect(scripts["release:dryrun"]).toBe(
|
|
68
|
+
"bun run release:version && bun run build && koishi-scripts publish --dry-run",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
51
72
|
test("内置模板以 npm alias 钉住 koishi 裸名,版本保持 4.18.x 冻结线", () => {
|
|
52
73
|
const request = baseManifest().dependencies?.["koishi"];
|
|
53
74
|
expect(typeof request).toBe("string");
|
package/src/index.ts
CHANGED
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
* - remote.ts --template 远程模板的下载-解包-改写全流程
|
|
23
23
|
* - template.ts 内置 @koishi-ce 模板(静态文件与 baseManifest)
|
|
24
24
|
*/
|
|
25
|
-
import { spawnSync } from "node:child_process";
|
|
26
25
|
import {
|
|
27
26
|
existsSync,
|
|
28
27
|
mkdirSync,
|
|
@@ -216,8 +215,10 @@ async function scaffold() {
|
|
|
216
215
|
async function initGit() {
|
|
217
216
|
if (!argv.git || !supports(["git", "--version"])) return;
|
|
218
217
|
const branch = gitConfig("init.defaultBranch") || "main";
|
|
219
|
-
spawnSync(
|
|
220
|
-
|
|
218
|
+
Bun.spawnSync({
|
|
219
|
+
cmd: ["git", "init", "-b", branch],
|
|
220
|
+
stdout: "ignore",
|
|
221
|
+
stderr: "ignore",
|
|
221
222
|
cwd: rootDir,
|
|
222
223
|
});
|
|
223
224
|
console.log(
|
|
@@ -238,18 +239,22 @@ async function install() {
|
|
|
238
239
|
agent === "yarn" ? ["start"] : ["run", "start"];
|
|
239
240
|
const yes = await confirm("现在安装依赖并启动吗?");
|
|
240
241
|
if (yes) {
|
|
241
|
-
const installed = spawnSync(
|
|
242
|
-
|
|
242
|
+
const installed = Bun.spawnSync({
|
|
243
|
+
cmd: [agent, "install"],
|
|
244
|
+
stdout: "inherit",
|
|
245
|
+
stderr: "inherit",
|
|
243
246
|
cwd: rootDir,
|
|
244
247
|
});
|
|
245
|
-
if (installed.
|
|
248
|
+
if (!installed.success) {
|
|
246
249
|
console.log(
|
|
247
250
|
pc.red(" 依赖安装失败,请检查上方日志。"),
|
|
248
251
|
);
|
|
249
252
|
return;
|
|
250
253
|
}
|
|
251
|
-
spawnSync(
|
|
252
|
-
|
|
254
|
+
Bun.spawnSync({
|
|
255
|
+
cmd: [agent, ...startArgs],
|
|
256
|
+
stdout: "inherit",
|
|
257
|
+
stderr: "inherit",
|
|
253
258
|
cwd: rootDir,
|
|
254
259
|
});
|
|
255
260
|
} else {
|
package/src/registry.ts
CHANGED
|
@@ -45,7 +45,7 @@ export function getLocalRegistry(
|
|
|
45
45
|
userHome: string = homedir(),
|
|
46
46
|
): string | undefined {
|
|
47
47
|
const candidates = [
|
|
48
|
-
|
|
48
|
+
Bun.env["npm_config_registry"],
|
|
49
49
|
readNpmrcRegistry(join(cwd, ".npmrc")),
|
|
50
50
|
readNpmrcRegistry(join(userHome, ".npmrc")),
|
|
51
51
|
];
|
package/src/template/README.md
CHANGED
|
@@ -30,4 +30,18 @@ bun run dev # 启动(开发模式,启用 HMR 热更新)
|
|
|
30
30
|
|
|
31
31
|
## 自定义插件
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
手动在 `plugins/` 目录下创建插件包,或经脚手架进入 `external/` 工作区:
|
|
34
|
+
|
|
35
|
+
- `bun run new <名称>` —— 生成插件骨架到 `external/`(`--console` 附带控制台前端扩展)
|
|
36
|
+
- `bun run clone <仓库>` —— 克隆已有插件源码到 `external/` 本地联动调试
|
|
37
|
+
|
|
38
|
+
两种来源的插件都在 `koishi.yml` 中以相对路径引用(如 `./plugins/my-plugin`、`./external/my-plugin`)即可启用;Bun 直接加载 TypeScript 源码,无需预编译。
|
|
39
|
+
|
|
40
|
+
## 插件构建与发布
|
|
41
|
+
|
|
42
|
+
针对 `external/` 工作区的插件项目:
|
|
43
|
+
|
|
44
|
+
- `bun run build` —— 串行构建 `external/` 下全部可构建项目
|
|
45
|
+
- `bun run release:version` —— 消费 pending changeset 提升版本号
|
|
46
|
+
- `bun run release:dryrun` —— 完整发布链演练(version → build → publish --dry-run)
|
|
47
|
+
- `bun run release` —— 完整发布链(version → build → publish)
|
package/src/template/koishi.yml
CHANGED
package/src/template.ts
CHANGED
|
@@ -39,8 +39,7 @@ import {
|
|
|
39
39
|
readdirSync,
|
|
40
40
|
readFileSync,
|
|
41
41
|
} from "node:fs";
|
|
42
|
-
import {
|
|
43
|
-
import { fileURLToPath } from "node:url";
|
|
42
|
+
import { join } from "node:path";
|
|
44
43
|
import type { Manifest } from "./manifest.ts";
|
|
45
44
|
|
|
46
45
|
/**
|
|
@@ -49,7 +48,7 @@ import type { Manifest } from "./manifest.ts";
|
|
|
49
48
|
* 恒存在)。
|
|
50
49
|
*/
|
|
51
50
|
function locateTemplateDir(): string {
|
|
52
|
-
const base =
|
|
51
|
+
const base = import.meta.dir;
|
|
53
52
|
for (const dir of [
|
|
54
53
|
join(base, "template"),
|
|
55
54
|
join(base, "../src/template"),
|
|
@@ -98,7 +97,20 @@ export function baseManifest(): Manifest {
|
|
|
98
97
|
start: "koishi start",
|
|
99
98
|
// bun run 走 Bun Shell,`NODE_ENV=...` 前缀天然跨平台,无需 cross-env
|
|
100
99
|
dev: "NODE_ENV=development koishi start",
|
|
100
|
+
// 插件开发链(devDep @koishi-ce/scripts 的 koishi-scripts CLI,操作
|
|
101
|
+
// 本工作区 plugins/ 与 external/;纯运行 bot 的项目用不到这些入口,
|
|
102
|
+
// prod 模式删 devDependencies 后它们自然失效):
|
|
103
|
+
// new 生成插件骨架到 external/,clone 克隆已有插件仓库本地联动,
|
|
104
|
+
// build / release* 构建并发布 external/ 下的插件(version → build
|
|
105
|
+
// → publish 三环,dry-run 为演练形态)
|
|
101
106
|
new: "koishi-scripts setup",
|
|
107
|
+
clone: "koishi-scripts clone",
|
|
108
|
+
build: "koishi-scripts build",
|
|
109
|
+
"release:version": "koishi-scripts version",
|
|
110
|
+
"release:dryrun":
|
|
111
|
+
"bun run release:version && bun run build && koishi-scripts publish --dry-run",
|
|
112
|
+
release:
|
|
113
|
+
"bun run release:version && bun run build && koishi-scripts publish",
|
|
102
114
|
},
|
|
103
115
|
dependencies: {
|
|
104
116
|
"@koishi-ce/koishi": "^1.0.0",
|
package/src/utils.ts
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* prepare 步骤调用,也是除 manifest.ts / registry.ts 之外可脱离交互
|
|
9
9
|
* 直接单测的函数集合。
|
|
10
10
|
*/
|
|
11
|
-
import { spawnSync } from "node:child_process";
|
|
12
11
|
import { readdirSync, rmSync } from "node:fs";
|
|
13
12
|
import { join } from "node:path";
|
|
14
13
|
|
|
@@ -18,7 +17,7 @@ import { join } from "node:path";
|
|
|
18
17
|
* 本 CLI 自身以 bun 为运行时(bin shebang),能执行即已具备 bun 环境。
|
|
19
18
|
*/
|
|
20
19
|
export function detectAgent(): string {
|
|
21
|
-
const ua =
|
|
20
|
+
const ua = Bun.env["npm_config_user_agent"] ?? "";
|
|
22
21
|
if (ua.startsWith("yarn")) return "yarn";
|
|
23
22
|
if (ua.startsWith("pnpm")) return "pnpm";
|
|
24
23
|
return "bun";
|
|
@@ -26,19 +25,22 @@ export function detectAgent(): string {
|
|
|
26
25
|
|
|
27
26
|
/** 静默执行命令探测其是否可用(如 git --version),失败即视为不可用 */
|
|
28
27
|
export function supports(command: readonly string[]) {
|
|
29
|
-
return (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
);
|
|
28
|
+
return Bun.spawnSync({
|
|
29
|
+
cmd: [...command],
|
|
30
|
+
stdout: "ignore",
|
|
31
|
+
stderr: "ignore",
|
|
32
|
+
}).success;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
/** 读 git 全局配置单项(读不到 → 空串) */
|
|
37
36
|
export function gitConfig(key: string): string {
|
|
38
|
-
const res = spawnSync(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
const res = Bun.spawnSync([
|
|
38
|
+
"git",
|
|
39
|
+
"config",
|
|
40
|
+
"--get",
|
|
41
|
+
key,
|
|
42
|
+
]);
|
|
43
|
+
return res.success ? res.stdout.toString().trim() : "";
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/** 递归清空目录内容(目录本身保留)。 */
|