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 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 / database 插件以 `~` 停用条目预写,可按需启用;
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 database——官方版本可后续从市场安装,peer 已被 alias 钉住不会形成双实例。
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 / database plugins are prewritten as `~`-disabled entries.
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 or database is preinstalled — official ones can be installed from the market later; their peers are already pinned by the aliases.
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { t as start } from "./src-t_F40fpK.mjs";
2
+ import { t as start } from "./src-C3GEQDVG.mjs";
3
3
  //#region src/bin.ts
4
4
  /**
5
5
  * create-koishi-ce 的 CLI 可执行入口:shebang 由 rolldown 原样保留到
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 { type Manifest, detectAgent, getLocalRegistry, readNpmrcRegistry, renderManifest, start };
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-t_F40fpK.mjs";
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, dirname, join, relative } from "node:path";
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.5.0",
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
- process.env["npm_config_registry"],
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 = dirname(fileURLToPath(import.meta.url));
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 = process.env["npm_config_user_agent"] ?? "";
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(command[0] ?? "", command.slice(1), { stdio: "ignore" }).status === 0;
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("git", [
326
+ const res = Bun.spawnSync([
327
+ "git",
320
328
  "config",
321
329
  "--get",
322
330
  key
323
- ], { encoding: "utf8" });
324
- return res.status === 0 ? res.stdout?.trim() ?? "" : "";
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("git", [
479
- "init",
480
- "-b",
481
- branch
482
- ], {
483
- stdio: "ignore",
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(agent, ["install"], {
498
- stdio: "inherit",
508
+ if (!Bun.spawnSync({
509
+ cmd: [agent, "install"],
510
+ stdout: "inherit",
511
+ stderr: "inherit",
499
512
  cwd: rootDir
500
- }).status !== 0) {
513
+ }).success) {
501
514
  console.log(pc.red(" 依赖安装失败,请检查上方日志。"));
502
515
  return;
503
516
  }
504
- spawnSync(agent, startArgs, {
505
- stdio: "inherit",
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-koishi-ce",
3
3
  "description": "Setup a Koishi application",
4
- "version": "1.5.0",
4
+ "version": "1.6.0",
5
5
  "type": "module",
6
6
  "main": "./lib/index.mjs",
7
7
  "module": "./lib/index.mjs",
@@ -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
- process.env[key] =
49
+ Bun.env[key] =
50
50
  "npm/10.9.2 node/v22.14.0 x64 workspaces/false";
51
51
  expect(detectAgent()).toBe("bun");
52
- process.env[key] = "yarn/1.22.22 npm/? node/v22.14.0 x64";
52
+ Bun.env[key] = "yarn/1.22.22 npm/? node/v22.14.0 x64";
53
53
  expect(detectAgent()).toBe("yarn");
54
- process.env[key] = "pnpm/10.12.1 npm/? node/v22.14.0 x64";
54
+ Bun.env[key] = "pnpm/10.12.1 npm/? node/v22.14.0 x64";
55
55
  expect(detectAgent()).toBe("pnpm");
56
- delete process.env[key];
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
- process.env[key] = "https://registry.example.com/";
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
- process.env[key] = "ftp://not-a-registry/";
53
+ Bun.env[key] = "ftp://not-a-registry/";
54
54
  expect(
55
55
  getLocalRegistry(isolated, isolated),
56
56
  ).toBeUndefined();
57
- delete process.env[key];
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 process.env[key];
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 process.env;
43
- const previousUserAgent = process.env[userAgentKey];
44
- delete process.env[userAgentKey];
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
- // 本文件求值时 node:child_process 恒为真实实现(本仓测试中唯一的
66
- // child_process mock 注册点),持有真实引用供未知命令透传;mock
67
- // 命名空间改写会波及其它测试文件经活绑定发起的调用(如 setup 的
68
- // git init、release 的子进程测试),透传保证它们拿到真实行为
69
- const realSpawnSync = spawnSync;
65
+ // 本文件求值时 Bun.spawnSync 恒为真实实现(本仓测试中唯一的子进程 mock
66
+ // 注册点),持有真实引用供未知命令透传;mock 改写会波及其它测试文件经
67
+ // 全局命名空间发起的调用(如 setup 的 git init、release 的子进程测试),
68
+ // 透传保证它们拿到真实行为
69
+ const realSpawnSync = Bun.spawnSync;
70
70
 
71
- mock.module("node:child_process", () => ({
72
- spawnSync: (
73
- cmd: string,
74
- args: readonly string[],
75
- options: { cwd?: string },
76
- ) => {
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
- ) {
84
- const key = args[2];
85
- const table: Record<string, string> = {
86
- "user.name": "Tester",
87
- "user.email": "t@e.st",
88
- "init.defaultBranch": "trunk",
89
- };
90
- const value =
91
- key !== undefined ? table[key] : undefined;
92
- return value === undefined
93
- ? { status: 1, stdout: "" }
94
- : { status: 0, stdout: `${value}\n` };
95
- }
96
- // exactOptionalPropertyTypes:cwd 未传时不落键,避免显式写入 undefined
97
- spawnLog.push({
98
- cmd,
99
- args: [...args],
100
- ...(options.cwd === undefined
101
- ? {}
102
- : { cwd: options.cwd }),
103
- });
104
- // 依赖安装可编程;agent 启动不真实拉起
105
- if (args[0] === "install")
106
- return { status: installExit };
107
- if (cmd === "bun" && args[0] === "run")
108
- return { status: 0 };
109
- // 其余命令透传真实执行(副作用均在各自测试的临时目录内)
110
- return realSpawnSync(
111
- cmd,
112
- args,
113
- options as Parameters<typeof spawnSync>[2],
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
- process.env[userAgentKey] = previousUserAgent;
210
+ Bun.env[userAgentKey] = previousUserAgent;
160
211
  } else {
161
- delete process.env[userAgentKey];
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("git", ["init", "-b", branch], {
220
- stdio: "ignore",
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(agent, ["install"], {
242
- stdio: "inherit",
242
+ const installed = Bun.spawnSync({
243
+ cmd: [agent, "install"],
244
+ stdout: "inherit",
245
+ stderr: "inherit",
243
246
  cwd: rootDir,
244
247
  });
245
- if (installed.status !== 0) {
248
+ if (!installed.success) {
246
249
  console.log(
247
250
  pc.red(" 依赖安装失败,请检查上方日志。"),
248
251
  );
249
252
  return;
250
253
  }
251
- spawnSync(agent, startArgs, {
252
- stdio: "inherit",
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
- process.env["npm_config_registry"],
48
+ Bun.env["npm_config_registry"],
49
49
  readNpmrcRegistry(join(cwd, ".npmrc")),
50
50
  readNpmrcRegistry(join(userHome, ".npmrc")),
51
51
  ];
@@ -30,4 +30,18 @@ bun run dev # 启动(开发模式,启用 HMR 热更新)
30
30
 
31
31
  ## 自定义插件
32
32
 
33
- `plugins/` 目录下创建插件包(可用 `bun run new <名称>` 生成骨架),在 `koishi.yml` 中以相对路径引用(如 `./plugins/my-plugin`)即可启用;Bun 直接加载 TypeScript 源码,无需预编译。
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)
@@ -34,6 +34,7 @@ plugins:
34
34
  insight:ozp3by: {}
35
35
  notifier:30g7rg: {}
36
36
  theme-vanilla:029kuh: {}
37
+ welcome:q7m2vx: {}
37
38
  group:basic:
38
39
  $collapsed: true
39
40
  $label: 基础库
package/src/template.ts CHANGED
@@ -39,8 +39,7 @@ import {
39
39
  readdirSync,
40
40
  readFileSync,
41
41
  } from "node:fs";
42
- import { dirname, join } from "node:path";
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 = dirname(fileURLToPath(import.meta.url));
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 = process.env["npm_config_user_agent"] ?? "";
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
- spawnSync(command[0] ?? "", command.slice(1), {
31
- stdio: "ignore",
32
- }).status === 0
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("git", ["config", "--get", key], {
39
- encoding: "utf8",
40
- });
41
- return res.status === 0 ? (res.stdout?.trim() ?? "") : "";
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
  /** 递归清空目录内容(目录本身保留)。 */