create-koishi-ce 1.4.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-B1-ko5wA.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-B1-ko5wA.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.4.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
  ];
@@ -198,11 +196,13 @@ function writePackageJson(rootDir, project, prod) {
198
196
  * 不会拉入 npm 官方全家桶形成第二份框架副本;市场安装亦不改写该声明
199
197
  * (installer 的 isGuardedRequest 护栏将 npm:@koishi-ce alias 与
200
198
  * workspace: 同等保护);
201
- * - koishi.yml 对齐官方实例的预写策略:控制台与基础插件全量预装(依赖
202
- * 数据库的保持 ~ 禁用);database-sqlite 已随模板预装,去掉 ~ 即用;
203
- * 本仓不再分发的 adapter 官方插件与未再分发的 database(mongo / mysql
204
- * / postgres)只以 ~ 禁用条目预写、不预装——loader 跳过禁用条目,装
205
- * 好后在控制台启用;
199
+ * - koishi.yml 采用配置页导出形态:插件键带 uid 实例后缀,分组带中文
200
+ * $label $collapsed 元数据;database-sqlite 默认启用,开箱即得数
201
+ * 据库;依赖数据库但非必需的插件(admin / bind / auth / broadcast /
202
+ * callme)保持 ~ 禁用;本仓不再分发的 adapter 官方插件只以 ~ 禁用条
203
+ * 目预写、不预装——loader 跳过禁用条目,装好后在控制台启用(mongo /
204
+ * mysql / postgres 等未再分发的 database 不再预写占位条目,市场装后
205
+ * 自动出现);
206
206
  * - 依赖版本统一 ^1.0.0 区间(安装时取最新 1.x),shim 版本例外(冻结线)。
207
207
  */
208
208
  /**
@@ -211,7 +211,7 @@ function writePackageJson(rootDir, project, prod) {
211
211
  * 恒存在)。
212
212
  */
213
213
  function locateTemplateDir() {
214
- const base = dirname(fileURLToPath(import.meta.url));
214
+ const base = import.meta.dir;
215
215
  for (const dir of [join(base, "template"), join(base, "../src/template")]) if (existsSync(dir)) return dir;
216
216
  throw new Error("create-koishi-ce 内置模板目录缺失(src/template)");
217
217
  }
@@ -239,7 +239,12 @@ function baseManifest() {
239
239
  scripts: {
240
240
  start: "koishi start",
241
241
  dev: "NODE_ENV=development koishi start",
242
- 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"
243
248
  },
244
249
  dependencies: {
245
250
  "@koishi-ce/koishi": "^1.0.0",
@@ -303,23 +308,28 @@ function baseManifest() {
303
308
  * 本 CLI 自身以 bun 为运行时(bin shebang),能执行即已具备 bun 环境。
304
309
  */
305
310
  function detectAgent() {
306
- const ua = process.env["npm_config_user_agent"] ?? "";
311
+ const ua = Bun.env["npm_config_user_agent"] ?? "";
307
312
  if (ua.startsWith("yarn")) return "yarn";
308
313
  if (ua.startsWith("pnpm")) return "pnpm";
309
314
  return "bun";
310
315
  }
311
316
  /** 静默执行命令探测其是否可用(如 git --version),失败即视为不可用 */
312
317
  function supports(command) {
313
- 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;
314
323
  }
315
324
  /** 读 git 全局配置单项(读不到 → 空串) */
316
325
  function gitConfig(key) {
317
- const res = spawnSync("git", [
326
+ const res = Bun.spawnSync([
327
+ "git",
318
328
  "config",
319
329
  "--get",
320
330
  key
321
- ], { encoding: "utf8" });
322
- return res.status === 0 ? res.stdout?.trim() ?? "" : "";
331
+ ]);
332
+ return res.success ? res.stdout.toString().trim() : "";
323
333
  }
324
334
  /** 递归清空目录内容(目录本身保留)。 */
325
335
  function emptyDir(root) {
@@ -473,12 +483,15 @@ async function scaffold() {
473
483
  async function initGit() {
474
484
  if (!argv.git || !supports(["git", "--version"])) return;
475
485
  const branch = gitConfig("init.defaultBranch") || "main";
476
- spawnSync("git", [
477
- "init",
478
- "-b",
479
- branch
480
- ], {
481
- stdio: "ignore",
486
+ Bun.spawnSync({
487
+ cmd: [
488
+ "git",
489
+ "init",
490
+ "-b",
491
+ branch
492
+ ],
493
+ stdout: "ignore",
494
+ stderr: "ignore",
482
495
  cwd: rootDir
483
496
  });
484
497
  console.log(pc.green(` 已初始化 git 仓库(分支 ${branch})。\n`));
@@ -492,15 +505,19 @@ async function install() {
492
505
  const agent = detectAgent();
493
506
  const startArgs = agent === "yarn" ? ["start"] : ["run", "start"];
494
507
  if (await confirm("现在安装依赖并启动吗?")) {
495
- if (spawnSync(agent, ["install"], {
496
- stdio: "inherit",
508
+ if (!Bun.spawnSync({
509
+ cmd: [agent, "install"],
510
+ stdout: "inherit",
511
+ stderr: "inherit",
497
512
  cwd: rootDir
498
- }).status !== 0) {
513
+ }).success) {
499
514
  console.log(pc.red(" 依赖安装失败,请检查上方日志。"));
500
515
  return;
501
516
  }
502
- spawnSync(agent, startArgs, {
503
- stdio: "inherit",
517
+ Bun.spawnSync({
518
+ cmd: [agent, ...startArgs],
519
+ stdout: "inherit",
520
+ stderr: "inherit",
504
521
  cwd: rootDir
505
522
  });
506
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.4.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");
@@ -92,51 +113,56 @@ test("内置模板静态文件齐备,koishi.yml 预配 market 镜像源", () =
92
113
  );
93
114
  });
94
115
 
95
- test("koishi.yml 预写策略对齐官方实例:CE 插件全量预装(依赖数据库者禁用),官方 adapter / 未分发 database 只写不装", () => {
116
+ test("koishi.yml 预写策略:sqlite 默认启用开箱数据库,非必需件与官方 adapter 保持 ~ 禁用", () => {
96
117
  const yml = templateFiles["koishi.yml"] ?? "";
97
- // 依赖数据库 / 暂无需启用的 CE 插件:预装但 ~ 禁用
118
+ // 配置页导出形态:插件键带 uid 实例后缀,分组带 $collapsed / $label 元数据
119
+ expect(yml).toMatch(/database-sqlite:[a-z0-9]{6}:/);
120
+ expect(yml).toContain("$collapsed: true");
121
+ expect(yml).toContain("$label: ");
122
+ // 依赖数据库但非必需 / 暂无需启用的 CE 插件:预装但 ~ 禁用
98
123
  for (const name of [
99
124
  "~admin",
100
125
  "~bind",
101
126
  "~broadcast",
102
127
  "~callme",
103
- "~analytics",
104
128
  "~auth",
105
- "~dataview",
106
- "~rate-limit",
107
129
  "~inspect",
108
130
  "~server-temp",
109
131
  "~mock",
110
132
  ]) {
111
133
  expect(yml).toContain(name);
112
134
  }
113
- // 无需数据库即可工作的 CE 插件:直接启用
135
+ // 随模板默认启用的 CE 插件(含 sqlite 数据库):条目存在且无 ~ 前缀
114
136
  for (const name of [
115
- "assets-local",
137
+ "database-sqlite",
138
+ "dataview",
139
+ "analytics",
140
+ "rate-limit",
116
141
  "echo",
117
- "theme-vanilla",
142
+ "assets-local",
118
143
  "status",
119
144
  "sandbox",
145
+ "theme-vanilla",
120
146
  ]) {
121
147
  expect(yml).toContain(name);
148
+ expect(yml).not.toContain(`~${name}`);
122
149
  }
123
- // database-sqlite 已随模板预装:依赖在册 + yml 保持 ~ 禁用(启用即可用)
150
+ // database-sqlite 依赖在册(随模板预装,启用即得数据库)
124
151
  expect(
125
152
  baseManifest().dependencies?.[
126
153
  "@koishi-ce/plugin-database-sqlite"
127
154
  ],
128
155
  ).toBe("^1.0.0");
129
- expect(yml).toContain("~database-sqlite");
130
- // 官方 adapter / 未再分发的 database 只以 ~ 禁用条目预写(未预装,市场装后启用)
156
+ // 官方 adapter 只以 ~ 禁用条目预写(未预装,市场装后启用);
157
+ // 未再分发的 database(mongo / mysql / postgres)不再预写占位条目
131
158
  for (const name of [
132
159
  "~adapter-discord",
133
160
  "~adapter-telegram",
134
161
  "~adapter-qq",
135
- "~database-mongo",
136
- "~database-postgres",
137
162
  ]) {
138
163
  expect(yml).toContain(name);
139
164
  }
165
+ expect(yml).not.toContain("database-mongo");
140
166
  // 模板依赖里不得出现官方 adapter / database 包名(只预写不预装)
141
167
  expect(JSON.stringify(baseManifest())).not.toContain(
142
168
  "adapter-",
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
  ];
@@ -18,9 +18,9 @@ bun run dev # 启动(开发模式,启用 HMR 热更新)
18
18
 
19
19
  ## 预装与预写
20
20
 
21
- - **已预装**:基础与控制台插件(@koishi-ce 全家桶)。其中依赖数据库的(admin / bind / broadcast / callme / analytics / auth / dataview / rate-limit 等)与暂无需启用的(inspect / server-temp / mock)在 `koishi.yml` 中以 `~` 前缀保持禁用——启用任一数据库插件后,去掉对应 `~` 即可。
22
- - **数据库开箱即用**:`@koishi-ce/plugin-database-sqlite` 已预装(`~database-sqlite` 保持禁用),无需市场安装,在配置页启用即可,数据落在 `data/koishi.db`。
23
- - **只预写、未预装**:adapter(discord / telegram / qq …)与数据库(mongo / mysql / postgres …)官方插件以 `~` 禁用条目预写在 `koishi.yml` 的 `group:adapter` / `group:storage`——loader 会跳过禁用条目,不安装也能正常启动;需要时在控制台「插件市场」搜索安装,回到配置页点击启用即可。
21
+ - **已预装**:基础与控制台插件(@koishi-ce 全家桶)。`koishi.yml` 为配置页导出形态:插件键带 uid 实例后缀,分组带中文 `$label` 标签。其中依赖数据库但非必需的(admin / bind / broadcast / callme / auth)与暂无需启用的(inspect / server-temp / mock)以 `~` 前缀保持禁用——数据库已默认启用,去掉对应 `~` 即可。
22
+ - **数据库开箱即用**:`@koishi-ce/plugin-database-sqlite` 已预装并默认启用,数据落在 `data/koishi.db`。
23
+ - **只预写、未预装**:adapter(discord / telegram / qq …)官方插件以 `~` 禁用条目预写在 `koishi.yml` 的 `group:adapter`——loader 会跳过禁用条目,不安装也能正常启动;需要时在控制台「插件市场」搜索安装,回到配置页点击启用即可(mongo / mysql / postgres 等其他数据库插件未预写占位,市场安装后自动出现条目)。
24
24
 
25
25
  ## 安装插件
26
26
 
@@ -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)
@@ -1,69 +1,76 @@
1
1
  plugins:
2
2
  group:server:
3
- server:
3
+ $collapsed: true
4
+ $label: 服务器
5
+ server:nhf2rf:
4
6
  port: 5140
5
7
  maxPort: 5149
6
- ~server-temp: {}
7
- group:basic:
8
- ~admin: {}
9
- ~bind: {}
10
- ~broadcast: {}
11
- ~callme: {}
12
- commands: {}
13
- echo: {}
14
- help: {}
15
- http: {}
16
- ~inspect: {}
17
- locales: {}
18
- proxy-agent: {}
19
- ~rate-limit: {}
8
+ ~server-temp:z9nmes: {}
9
+ ~adapter-satori:euyuu6: {}
10
+ group:storage:
11
+ $collapsed: true
12
+ $label: 持久化
13
+ assets-local:0bp951: {}
14
+ database-sqlite:60m3ri:
15
+ path: data/koishi.db
20
16
  group:console:
21
- actions: {}
22
- ~analytics: {}
23
- ~auth: {}
24
- config: {}
25
- console:
17
+ $collapsed: true
18
+ $label: 控制台
19
+ console:p179rv:
26
20
  open: true
27
- ~dataview: {}
28
- explorer: {}
29
- insight: {}
30
- logger: {}
31
- market:
21
+ ~auth:decpjz: {}
22
+ oobe:kw667y: {}
23
+ config:hrvlki: {}
24
+ market:n1dg4c:
32
25
  search:
33
26
  endpoint: https://registry.koishi.chat/index.json
34
- notifier: {}
35
- oobe: {}
36
- sandbox: {}
37
- status: {}
38
- theme-vanilla: {}
39
- group:storage:
40
- ~database-mongo:
41
- database: koishi
42
- ~database-mysql:
43
- database: koishi
44
- ~database-postgres:
45
- database: koishi
46
- ~database-sqlite:
47
- path: data/koishi.db
48
- assets-local: {}
27
+ explorer:5e7k13: {}
28
+ actions:ttgal7: {}
29
+ sandbox:bbkwu5: {}
30
+ status:hc51wq: {}
31
+ logger:akzyyp: {}
32
+ dataview:eklvzg: {}
33
+ analytics:smgmme: {}
34
+ insight:ozp3by: {}
35
+ notifier:30g7rg: {}
36
+ theme-vanilla:029kuh: {}
37
+ welcome:q7m2vx: {}
38
+ group:basic:
39
+ $collapsed: true
40
+ $label: 基础库
41
+ commands:lgrbci: {}
42
+ help:nrbz2h: {}
43
+ echo:t6r9sk: {}
44
+ ~admin:e0sr2y: {}
45
+ ~bind:zxx2sn: {}
46
+ ~broadcast:q8x3jd: {}
47
+ ~callme:m4v7wt: {}
48
+ ~inspect:2t107r: {}
49
+ locales:bi9lco: {}
50
+ http:rwe3ck: {}
51
+ proxy-agent:p6jj15: {}
52
+ rate-limit:cghjaw: {}
49
53
  group:adapter:
50
- ~adapter-dingtalk: {}
51
- ~adapter-discord: {}
52
- ~adapter-kook: {}
53
- ~adapter-lark: {}
54
- ~adapter-line: {}
55
- ~adapter-mail: {}
56
- ~adapter-matrix: {}
57
- ~adapter-qq: {}
58
- ~adapter-satori: {}
59
- ~adapter-slack: {}
60
- ~adapter-telegram: {}
61
- ~adapter-wechat-official: {}
62
- ~adapter-wecom: {}
63
- ~adapter-whatsapp: {}
64
- ~adapter-zulip: {}
54
+ $collapsed: true
55
+ $label: 适配器
56
+ ~adapter-qq:lr3f6b: {}
57
+ ~adapter-telegram:memc76: {}
58
+ ~adapter-discord:4n11rk: {}
59
+ ~adapter-kook:2s0qw0: {}
60
+ ~adapter-wecom:euh20s: {}
61
+ ~adapter-wechat-official:ufqejg: {}
62
+ ~adapter-dingtalk:76isk2: {}
63
+ ~adapter-lark:vtfsz0: {}
64
+ ~adapter-line:hfsd6d: {}
65
+ ~adapter-slack:ctdrr0: {}
66
+ ~adapter-whatsapp:xn68t0: {}
67
+ ~adapter-mail:cjnxrb: {}
68
+ ~adapter-matrix:phadis: {}
69
+ ~adapter-zulip:fg64tb: {}
65
70
  group:develop:
66
- $if: env.NODE_ENV === 'development'
67
- hmr:
68
- root: .
69
- ~mock: {}
71
+ $if: "env.NODE_ENV === 'development'"
72
+ $collapsed: true
73
+ $label: 开发
74
+ hmr:i3xkbw:
75
+ root: "."
76
+ ~mock:w2y5nh: {}
package/src/template.ts CHANGED
@@ -25,11 +25,13 @@
25
25
  * 不会拉入 npm 官方全家桶形成第二份框架副本;市场安装亦不改写该声明
26
26
  * (installer 的 isGuardedRequest 护栏将 npm:@koishi-ce alias 与
27
27
  * workspace: 同等保护);
28
- * - koishi.yml 对齐官方实例的预写策略:控制台与基础插件全量预装(依赖
29
- * 数据库的保持 ~ 禁用);database-sqlite 已随模板预装,去掉 ~ 即用;
30
- * 本仓不再分发的 adapter 官方插件与未再分发的 database(mongo / mysql
31
- * / postgres)只以 ~ 禁用条目预写、不预装——loader 跳过禁用条目,装
32
- * 好后在控制台启用;
28
+ * - koishi.yml 采用配置页导出形态:插件键带 uid 实例后缀,分组带中文
29
+ * $label $collapsed 元数据;database-sqlite 默认启用,开箱即得数
30
+ * 据库;依赖数据库但非必需的插件(admin / bind / auth / broadcast /
31
+ * callme)保持 ~ 禁用;本仓不再分发的 adapter 官方插件只以 ~ 禁用条
32
+ * 目预写、不预装——loader 跳过禁用条目,装好后在控制台启用(mongo /
33
+ * mysql / postgres 等未再分发的 database 不再预写占位条目,市场装后
34
+ * 自动出现);
33
35
  * - 依赖版本统一 ^1.0.0 区间(安装时取最新 1.x),shim 版本例外(冻结线)。
34
36
  */
35
37
  import {
@@ -37,8 +39,7 @@ import {
37
39
  readdirSync,
38
40
  readFileSync,
39
41
  } from "node:fs";
40
- import { dirname, join } from "node:path";
41
- import { fileURLToPath } from "node:url";
42
+ import { join } from "node:path";
42
43
  import type { Manifest } from "./manifest.ts";
43
44
 
44
45
  /**
@@ -47,7 +48,7 @@ import type { Manifest } from "./manifest.ts";
47
48
  * 恒存在)。
48
49
  */
49
50
  function locateTemplateDir(): string {
50
- const base = dirname(fileURLToPath(import.meta.url));
51
+ const base = import.meta.dir;
51
52
  for (const dir of [
52
53
  join(base, "template"),
53
54
  join(base, "../src/template"),
@@ -96,7 +97,20 @@ export function baseManifest(): Manifest {
96
97
  start: "koishi start",
97
98
  // bun run 走 Bun Shell,`NODE_ENV=...` 前缀天然跨平台,无需 cross-env
98
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 为演练形态)
99
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",
100
114
  },
101
115
  dependencies: {
102
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
  /** 递归清空目录内容(目录本身保留)。 */