create-koishi-ce 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,20 +34,26 @@ function paxRecord(key: string, value: string): string {
34
34
  let length = body.length + 2;
35
35
  for (;;) {
36
36
  const prefix = `${length} `;
37
- if (prefix.length + body.length === length) return `${prefix}${body}`;
37
+ if (prefix.length + body.length === length)
38
+ return `${prefix}${body}`;
38
39
  length = prefix.length + body.length;
39
40
  }
40
41
  }
41
42
 
42
43
  /** 收集条目父目录(含显式目录自身),保证目录条目先于引用内容出现 */
43
- function collectDirs(entries: readonly TarEntry[]): string[] {
44
+ function collectDirs(
45
+ entries: readonly TarEntry[],
46
+ ): string[] {
44
47
  const dirs = new Set<string>();
45
48
  for (const entry of entries) {
46
49
  if (entry.type === "dir" || entry.path.endsWith("/")) {
47
50
  dirs.add(entry.path.replace(/\/+$/, ""));
48
51
  continue;
49
52
  }
50
- let parent = entry.path.slice(0, entry.path.lastIndexOf("/"));
53
+ let parent = entry.path.slice(
54
+ 0,
55
+ entry.path.lastIndexOf("/"),
56
+ );
51
57
  while (parent) {
52
58
  dirs.add(parent);
53
59
  const index = parent.lastIndexOf("/");
@@ -78,7 +84,10 @@ function createHeader(
78
84
  header.set(encoder.encode("00"), 263);
79
85
  // 校验和:先以空格占位,再求和回填(八进制 6 位 + \0 + 空格)
80
86
  header.fill(0x20, 148, 156);
81
- const sum = header.reduce((total, byte) => total + byte, 0);
87
+ const sum = header.reduce(
88
+ (total, byte) => total + byte,
89
+ 0,
90
+ );
82
91
  const checksum = `${sum.toString(8).padStart(6, "0")}\0 `;
83
92
  header.set(encoder.encode(checksum), 148);
84
93
  return header;
@@ -111,7 +120,10 @@ function writeOctal(
111
120
  }
112
121
 
113
122
  /** 追加内容块并按 512 补齐 */
114
- function pushPadded(blocks: Uint8Array[], content: Uint8Array): void {
123
+ function pushPadded(
124
+ blocks: Uint8Array[],
125
+ content: Uint8Array,
126
+ ): void {
115
127
  blocks.push(content);
116
128
  const rest = content.length % BLOCK;
117
129
  if (rest) blocks.push(new Uint8Array(BLOCK - rest));
@@ -138,10 +150,14 @@ function pushEntry(
138
150
  // pax 扩展头自身也须有非空 name(node-tar 校验「path is
139
151
  // required」,空名会把整条 x 头判为无效并导致后续流错位);
140
152
  // PaxHeader/ 前缀与 node-tar pack 的命名约定一致
141
- blocks.push(createHeader(`PaxHeader/${name}`, "x", body.length));
153
+ blocks.push(
154
+ createHeader(`PaxHeader/${name}`, "x", body.length),
155
+ );
142
156
  pushPadded(blocks, body);
143
157
  }
144
- blocks.push(createHeader(name.slice(0, 100), type, size, link));
158
+ blocks.push(
159
+ createHeader(name.slice(0, 100), type, size, link),
160
+ );
145
161
  if (size) pushPadded(blocks, content);
146
162
  }
147
163
 
@@ -157,8 +173,12 @@ export async function tarPack(
157
173
  blocks.push(createHeader(`${dir}/`, "5", 0));
158
174
  }
159
175
  for (const entry of entries) {
160
- if (entry.type === "dir" || entry.path.endsWith("/")) continue;
161
- if (entry.type === "symlink" || entry.type === "hardlink") {
176
+ if (entry.type === "dir" || entry.path.endsWith("/"))
177
+ continue;
178
+ if (
179
+ entry.type === "symlink" ||
180
+ entry.type === "hardlink"
181
+ ) {
162
182
  const type = entry.type === "symlink" ? "2" : "1";
163
183
  pushEntry(
164
184
  blocks,
@@ -175,7 +195,10 @@ export async function tarPack(
175
195
  }
176
196
  blocks.push(new Uint8Array(BLOCK * 2)); // 归档结束标志(全零块)
177
197
 
178
- const total = blocks.reduce((sum, block) => sum + block.length, 0);
198
+ const total = blocks.reduce(
199
+ (sum, block) => sum + block.length,
200
+ 0,
201
+ );
179
202
  const raw = new Uint8Array(total);
180
203
  let cursor = 0;
181
204
  for (const block of blocks) {
@@ -3,7 +3,10 @@
3
3
 
4
4
  import { expect, test } from "bun:test";
5
5
  import { renderManifest } from "../index.ts";
6
- import { baseManifest, templateFiles } from "../template.ts";
6
+ import {
7
+ baseManifest,
8
+ templateFiles,
9
+ } from "../template.ts";
7
10
 
8
11
  test("内置模板依赖纯度:不含任何 @koishijs / koishi-plugin 官方生态包", () => {
9
12
  const { dependencies, devDependencies } = baseManifest();
@@ -16,10 +19,14 @@ test("内置模板依赖纯度:不含任何 @koishijs / koishi-plugin 官方
16
19
  "@koishijs/loader",
17
20
  ]);
18
21
  for (const key of Object.keys(dependencies ?? {})) {
19
- expect(guarded.has(key) || key.startsWith("@koishi-ce/")).toBe(true);
22
+ expect(
23
+ guarded.has(key) || key.startsWith("@koishi-ce/"),
24
+ ).toBe(true);
20
25
  }
21
26
  for (const key of Object.keys(devDependencies ?? {})) {
22
- expect(key === "bun-types" || key.startsWith("@koishi-ce/")).toBe(true);
27
+ expect(
28
+ key === "bun-types" || key.startsWith("@koishi-ce/"),
29
+ ).toBe(true);
23
30
  }
24
31
  });
25
32
 
@@ -29,10 +36,16 @@ test("内置模板 packageManager 钉 Bun,脚本用 Bun Shell 环境变量前
29
36
  scripts: Record<string, string>;
30
37
  };
31
38
  // 本项目只支持 Bun 运行时:packageManager 形如 bun@1.x.y
32
- expect(manifest["packageManager"] ?? "").toMatch(/^bun@\d+\.\d+\.\d+/);
39
+ expect(manifest["packageManager"] ?? "").toMatch(
40
+ /^bun@\d+\.\d+\.\d+/,
41
+ );
33
42
  // cross-env 已移除:bun run 走 Bun Shell,环境变量前缀天然跨平台
34
- expect(scripts["dev"]).toBe("NODE_ENV=development koishi start");
35
- expect(JSON.stringify(manifest)).not.toContain("cross-env");
43
+ expect(scripts["dev"]).toBe(
44
+ "NODE_ENV=development koishi start",
45
+ );
46
+ expect(JSON.stringify(manifest)).not.toContain(
47
+ "cross-env",
48
+ );
36
49
  });
37
50
 
38
51
  test("内置模板以 npm alias 钉住 koishi 裸名,版本保持 4.18.x 冻结线", () => {
@@ -40,7 +53,9 @@ test("内置模板以 npm alias 钉住 koishi 裸名,版本保持 4.18.x 冻
40
53
  expect(typeof request).toBe("string");
41
54
  // alias 形态 npm:@koishi-ce/koishi-shim@^4.18.x——版本若漂出 ^4 区间,
42
55
  // 上游插件的 peer `koishi ^4` 判定为不满足,market UI 会试图改写它
43
- expect(request ?? "").toMatch(/^npm:@koishi-ce\/koishi-shim@\^4\./);
56
+ expect(request ?? "").toMatch(
57
+ /^npm:@koishi-ce\/koishi-shim@\^4\./,
58
+ );
44
59
  });
45
60
 
46
61
  test("内置模板钉住 console / core / loader 上游 peer 名,防 Bun 自动装官方全家桶", () => {
@@ -68,49 +83,73 @@ test("内置模板静态文件齐备,koishi.yml 预配 market 镜像源", () =
68
83
  "tsconfig.json",
69
84
  "README.md",
70
85
  ]) {
71
- expect(templateFiles[file]?.length ?? 0).toBeGreaterThan(0);
86
+ expect(
87
+ templateFiles[file]?.length ?? 0,
88
+ ).toBeGreaterThan(0);
72
89
  }
73
90
  expect(templateFiles["koishi.yml"]).toContain(
74
91
  "endpoint: https://registry.koishi.chat/index.json",
75
92
  );
76
93
  });
77
94
 
78
- test("koishi.yml 预写策略对齐官方实例:CE 插件装而禁用,官方 adapter/database 只写不装", () => {
95
+ test("koishi.yml 预写策略对齐官方实例:CE 插件全量预装(依赖数据库者禁用),官方 adapter / 未分发 database 只写不装", () => {
79
96
  const yml = templateFiles["koishi.yml"] ?? "";
80
97
  // 依赖数据库 / 暂无需启用的 CE 插件:预装但 ~ 禁用
81
98
  for (const name of [
82
99
  "~admin",
83
100
  "~bind",
101
+ "~broadcast",
102
+ "~callme",
84
103
  "~analytics",
85
104
  "~auth",
86
105
  "~dataview",
87
106
  "~rate-limit",
88
107
  "~inspect",
89
108
  "~server-temp",
109
+ "~mock",
90
110
  ]) {
91
111
  expect(yml).toContain(name);
92
112
  }
93
113
  // 无需数据库即可工作的 CE 插件:直接启用
94
- for (const name of ["assets-local", "theme-vanilla", "status", "sandbox"]) {
114
+ for (const name of [
115
+ "assets-local",
116
+ "echo",
117
+ "theme-vanilla",
118
+ "status",
119
+ "sandbox",
120
+ ]) {
95
121
  expect(yml).toContain(name);
96
122
  }
97
- // 官方 adapter / database 只以 ~ 禁用条目预写(未预装,市场装后启用)
123
+ // database-sqlite 已随模板预装:依赖在册 + yml 保持 ~ 禁用(启用即可用)
124
+ expect(
125
+ baseManifest().dependencies?.[
126
+ "@koishi-ce/plugin-database-sqlite"
127
+ ],
128
+ ).toBe("^1.0.0");
129
+ expect(yml).toContain("~database-sqlite");
130
+ // 官方 adapter / 未再分发的 database 只以 ~ 禁用条目预写(未预装,市场装后启用)
98
131
  for (const name of [
99
132
  "~adapter-discord",
100
133
  "~adapter-telegram",
101
134
  "~adapter-qq",
102
- "~database-sqlite",
135
+ "~database-mongo",
103
136
  "~database-postgres",
104
137
  ]) {
105
138
  expect(yml).toContain(name);
106
139
  }
107
140
  // 模板依赖里不得出现官方 adapter / database 包名(只预写不预装)
108
- expect(JSON.stringify(baseManifest())).not.toContain("adapter-");
109
- expect(JSON.stringify(baseManifest())).not.toContain("database-");
141
+ expect(JSON.stringify(baseManifest())).not.toContain(
142
+ "adapter-",
143
+ );
144
+ expect(JSON.stringify(baseManifest())).not.toContain(
145
+ "@koishijs/plugin-database-",
146
+ );
110
147
  });
111
148
 
112
149
  test("renderManifest 渲染内置模板:常规改写生效,prod 模式保留 koishi alias", () => {
113
- const output = JSON.parse(renderManifest(baseManifest(), "my-app", false));
150
+ const output = JSON.parse(
151
+ renderManifest(baseManifest(), "my-app", false),
152
+ );
114
153
  expect(output.name).toBe("my-app");
115
154
  expect(output.private).toBe(true);
116
155
  expect(output.version).toBe("0.0.0");
@@ -120,14 +159,18 @@ test("renderManifest 渲染内置模板:常规改写生效,prod 模式保留
120
159
  "npm:@koishi-ce/koishi-shim@^4.18.11",
121
160
  );
122
161
 
123
- const prod = JSON.parse(renderManifest(baseManifest(), "my-app", true));
162
+ const prod = JSON.parse(
163
+ renderManifest(baseManifest(), "my-app", true),
164
+ );
124
165
  expect("workspaces" in prod).toBe(false);
125
166
  expect("devDependencies" in prod).toBe(false);
126
167
  // 四个上游名 alias 是运行时依赖,prod 模式下必须原样保留
127
- expect(prod.dependencies.koishi).toBe("npm:@koishi-ce/koishi-shim@^4.18.11");
128
- expect(prod.dependencies["@koishijs/plugin-console"]).toBe(
129
- "npm:@koishi-ce/console-shim@^5.30.11",
168
+ expect(prod.dependencies.koishi).toBe(
169
+ "npm:@koishi-ce/koishi-shim@^4.18.11",
130
170
  );
171
+ expect(
172
+ prod.dependencies["@koishijs/plugin-console"],
173
+ ).toBe("npm:@koishi-ce/console-shim@^5.30.11");
131
174
  expect(prod.dependencies["@koishijs/core"]).toBe(
132
175
  "npm:@koishi-ce/koishi-shim@4.18.11",
133
176
  );
package/src/index.ts CHANGED
@@ -23,24 +23,45 @@
23
23
  * - template.ts 内置 @koishi-ce 模板(静态文件与 baseManifest)
24
24
  */
25
25
  import { spawnSync } from "node:child_process";
26
- import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
26
+ import {
27
+ existsSync,
28
+ mkdirSync,
29
+ readdirSync,
30
+ writeFileSync,
31
+ } from "node:fs";
27
32
  import { basename, join, relative } from "node:path";
28
33
  import { parseArgs } from "node:util";
29
34
  import * as p from "@clack/prompts";
30
35
  import pc from "picocolors";
31
36
  import pkg from "../package.json" with { type: "json" };
32
- import { type Manifest, renderManifest } from "./manifest.ts";
33
- import { getLocalRegistry, readNpmrcRegistry } from "./registry.ts";
37
+ import {
38
+ type Manifest,
39
+ renderManifest,
40
+ } from "./manifest.ts";
41
+ import {
42
+ getLocalRegistry,
43
+ readNpmrcRegistry,
44
+ } from "./registry.ts";
34
45
  import { scaffoldRemote } from "./remote.ts";
35
46
  import { baseManifest, templateFiles } from "./template.ts";
36
- import { detectAgent, emptyDir, gitConfig, supports } from "./utils.ts";
47
+ import {
48
+ detectAgent,
49
+ emptyDir,
50
+ gitConfig,
51
+ supports,
52
+ } from "./utils.ts";
37
53
 
38
54
  const { version } = pkg;
39
55
 
40
56
  export type { Manifest };
41
57
  // 公共导出面:与拆分前保持一致(bin.ts 与测试文件按 ../index.ts 的既有
42
58
  // 导入路径原样可用);子模块的其余导出属于包内部实现,不在此暴露。
43
- export { detectAgent, getLocalRegistry, readNpmrcRegistry, renderManifest };
59
+ export {
60
+ detectAgent,
61
+ getLocalRegistry,
62
+ readNpmrcRegistry,
63
+ renderManifest,
64
+ };
44
65
 
45
66
  /** CLI 参数(node:util parseArgs 解析,别名映射见 bin 帮助文本) */
46
67
  interface Args {
@@ -92,7 +113,8 @@ async function getName(): Promise<string> {
92
113
  const answer = await p.text({
93
114
  message: "项目名:",
94
115
  initialValue: "koishi-app",
95
- validate: (value) => (value?.trim() ? undefined : "项目名不能为空"),
116
+ validate: (value) =>
117
+ value?.trim() ? undefined : "项目名不能为空",
96
118
  });
97
119
  if (p.isCancel(answer)) process.exit(0);
98
120
  const trimmed = answer.trim();
@@ -102,7 +124,10 @@ async function getName(): Promise<string> {
102
124
 
103
125
  /** 交互式确认框:返回用户是否选择了「是」(Ctrl+C 取消直接退出) */
104
126
  async function confirm(message: string) {
105
- const answer = await p.confirm({ message, initialValue: true });
127
+ const answer = await p.confirm({
128
+ message,
129
+ initialValue: true,
130
+ });
106
131
  if (p.isCancel(answer)) process.exit(0);
107
132
  return answer === true;
108
133
  }
@@ -121,7 +146,9 @@ async function prepare() {
121
146
  if (!files.length) return;
122
147
 
123
148
  if (!argv.forced && !argv.yes) {
124
- console.log(pc.yellow(` 目标目录 "${project}" 非空。`));
149
+ console.log(
150
+ pc.yellow(` 目标目录 "${project}" 非空。`),
151
+ );
125
152
  const yes = await confirm("清空现有文件并继续?");
126
153
  if (!yes) process.exit(0);
127
154
  }
@@ -134,12 +161,18 @@ async function prepare() {
134
161
  * 加上由 baseManifest() 渲染出的 package.json。纯本地写入,无网络请求。
135
162
  */
136
163
  function scaffoldBuiltin() {
137
- for (const [file, content] of Object.entries(templateFiles)) {
164
+ for (const [file, content] of Object.entries(
165
+ templateFiles,
166
+ )) {
138
167
  writeFileSync(join(rootDir, file), content);
139
168
  }
140
169
  writeFileSync(
141
170
  join(rootDir, "package.json"),
142
- renderManifest(baseManifest(), project, argv.prod === true),
171
+ renderManifest(
172
+ baseManifest(),
173
+ project,
174
+ argv.prod === true,
175
+ ),
143
176
  );
144
177
  }
145
178
 
@@ -149,7 +182,9 @@ function scaffoldBuiltin() {
149
182
  * scaffoldRemote),registry 取值 --registry 参数 > 本机 npm 配置 > 官方源。
150
183
  */
151
184
  async function scaffold() {
152
- console.log(pc.dim(" 正在 ") + project + pc.dim(" 中生成项目 ..."));
185
+ console.log(
186
+ pc.dim(" 正在 ") + project + pc.dim(" 中生成项目 ..."),
187
+ );
153
188
 
154
189
  if (argv.template) {
155
190
  const registry = (
@@ -181,8 +216,13 @@ async function scaffold() {
181
216
  async function initGit() {
182
217
  if (!argv.git || !supports(["git", "--version"])) return;
183
218
  const branch = gitConfig("init.defaultBranch") || "main";
184
- spawnSync("git", ["init", "-b", branch], { stdio: "ignore", cwd: rootDir });
185
- console.log(pc.green(` 已初始化 git 仓库(分支 ${branch})。\n`));
219
+ spawnSync("git", ["init", "-b", branch], {
220
+ stdio: "ignore",
221
+ cwd: rootDir,
222
+ });
223
+ console.log(
224
+ pc.green(` 已初始化 git 仓库(分支 ${branch})。\n`),
225
+ );
186
226
  }
187
227
 
188
228
  /**
@@ -194,7 +234,8 @@ async function install() {
194
234
  if (argv.yes) return;
195
235
 
196
236
  const agent = detectAgent();
197
- const startArgs = agent === "yarn" ? ["start"] : ["run", "start"];
237
+ const startArgs =
238
+ agent === "yarn" ? ["start"] : ["run", "start"];
198
239
  const yes = await confirm("现在安装依赖并启动吗?");
199
240
  if (yes) {
200
241
  const installed = spawnSync(agent, ["install"], {
@@ -202,19 +243,30 @@ async function install() {
202
243
  cwd: rootDir,
203
244
  });
204
245
  if (installed.status !== 0) {
205
- console.log(pc.red(" 依赖安装失败,请检查上方日志。"));
246
+ console.log(
247
+ pc.red(" 依赖安装失败,请检查上方日志。"),
248
+ );
206
249
  return;
207
250
  }
208
- spawnSync(agent, startArgs, { stdio: "inherit", cwd: rootDir });
251
+ spawnSync(agent, startArgs, {
252
+ stdio: "inherit",
253
+ cwd: rootDir,
254
+ });
209
255
  } else {
210
256
  console.log(pc.dim(" 稍后可以这样启动:\n"));
211
257
  if (rootDir !== cwd) {
212
258
  const related = relative(cwd, rootDir);
213
259
  console.log(pc.blue(` cd ${pc.bold(related)}`));
214
260
  }
215
- console.log(pc.blue(` ${agent === "yarn" ? "yarn" : `${agent} install`}`));
216
261
  console.log(
217
- pc.blue(` ${agent === "yarn" ? "yarn" : `${agent} run`} start`),
262
+ pc.blue(
263
+ ` ${agent === "yarn" ? "yarn" : `${agent} install`}`,
264
+ ),
265
+ );
266
+ console.log(
267
+ pc.blue(
268
+ ` ${agent === "yarn" ? "yarn" : `${agent} run`} start`,
269
+ ),
218
270
  );
219
271
  console.log();
220
272
  }
@@ -243,7 +295,9 @@ export async function start() {
243
295
  }
244
296
 
245
297
  console.log();
246
- console.log(` ${pc.bold("Create Koishi")} ${pc.blue(`v${version}`)}`);
298
+ console.log(
299
+ ` ${pc.bold("Create Koishi")} ${pc.blue(`v${version}`)}`,
300
+ );
247
301
  console.log();
248
302
 
249
303
  const name = await getName();
package/src/registry.ts CHANGED
@@ -16,10 +16,16 @@ import { join } from "node:path";
16
16
  * 从单个 .npmrc 文件提取 registry 配置项(非注释、非 scoped 的 registry= 行)。
17
17
  * 文件不存在或未配置时返回 undefined,读文件异常一律静默吞掉。
18
18
  */
19
- export function readNpmrcRegistry(file: string): string | undefined {
19
+ export function readNpmrcRegistry(
20
+ file: string,
21
+ ): string | undefined {
20
22
  try {
21
- for (const line of readFileSync(file, "utf8").split(/\r?\n/)) {
22
- const matched = /^\s*registry\s*=\s*(\S+)\s*$/.exec(line);
23
+ for (const line of readFileSync(file, "utf8").split(
24
+ /\r?\n/,
25
+ )) {
26
+ const matched = /^\s*registry\s*=\s*(\S+)\s*$/.exec(
27
+ line,
28
+ );
23
29
  const value = matched?.[1];
24
30
  if (value) return value;
25
31
  }
@@ -44,7 +50,10 @@ export function getLocalRegistry(
44
50
  readNpmrcRegistry(join(userHome, ".npmrc")),
45
51
  ];
46
52
  for (const candidate of candidates) {
47
- if (candidate?.startsWith("https://") || candidate?.startsWith("http://")) {
53
+ if (
54
+ candidate?.startsWith("https://") ||
55
+ candidate?.startsWith("http://")
56
+ ) {
48
57
  return candidate;
49
58
  }
50
59
  }
package/src/remote.ts CHANGED
@@ -16,7 +16,10 @@ import { readFileSync, writeFileSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
  import { downloadTemplate } from "giget";
18
18
  import pc from "picocolors";
19
- import { type Manifest, renderManifest } from "./manifest.ts";
19
+ import {
20
+ type Manifest,
21
+ renderManifest,
22
+ } from "./manifest.ts";
20
23
 
21
24
  /** registry 包元数据中本流程消费的字段 */
22
25
  interface RegistryMeta {
@@ -69,15 +72,25 @@ export async function scaffoldRemote({
69
72
  }: RemoteOptions) {
70
73
  try {
71
74
  const metaRes = await fetch(`${registry}/${template}`);
72
- if (!metaRes.ok) throw new HttpError(metaRes.status, metaRes.statusText);
75
+ if (!metaRes.ok)
76
+ throw new HttpError(
77
+ metaRes.status,
78
+ metaRes.statusText,
79
+ );
73
80
  const remote = (await metaRes.json()) as RegistryMeta;
74
81
  const version = remote["dist-tags"][ref];
75
82
  if (version === undefined) {
76
- throw new HttpError(404, `模板 ${template}@${ref} 不存在`);
83
+ throw new HttpError(
84
+ 404,
85
+ `模板 ${template}@${ref} 不存在`,
86
+ );
77
87
  }
78
88
  const url = remote.versions[version]?.dist?.tarball;
79
89
  if (url === undefined) {
80
- throw new HttpError(404, `模板 ${template}@${ref} 不存在`);
90
+ throw new HttpError(
91
+ 404,
92
+ `模板 ${template}@${ref} 不存在`,
93
+ );
81
94
  }
82
95
 
83
96
  // 解包交给 giget:provider 的 tar 字段是函数,giget 需要时才发起
@@ -93,7 +106,10 @@ export async function scaffoldRemote({
93
106
  tar: async () => {
94
107
  const tarballRes = await fetch(url);
95
108
  if (!tarballRes.ok || !tarballRes.body) {
96
- throw new HttpError(tarballRes.status, tarballRes.statusText);
109
+ throw new HttpError(
110
+ tarballRes.status,
111
+ tarballRes.statusText,
112
+ );
97
113
  }
98
114
  return tarballRes.body;
99
115
  },
@@ -113,8 +129,17 @@ export async function scaffoldRemote({
113
129
  }
114
130
 
115
131
  /** 把改写结果写回下载解包出的 package.json */
116
- function writePackageJson(rootDir: string, project: string, prod: boolean) {
132
+ function writePackageJson(
133
+ rootDir: string,
134
+ project: string,
135
+ prod: boolean,
136
+ ) {
117
137
  const filename = join(rootDir, "package.json");
118
- const meta = JSON.parse(readFileSync(filename, "utf8")) as Manifest;
119
- writeFileSync(filename, renderManifest(meta, project, prod));
138
+ const meta = JSON.parse(
139
+ readFileSync(filename, "utf8"),
140
+ ) as Manifest;
141
+ writeFileSync(
142
+ filename,
143
+ renderManifest(meta, project, prod),
144
+ );
120
145
  }
@@ -18,8 +18,9 @@ bun run dev # 启动(开发模式,启用 HMR 热更新)
18
18
 
19
19
  ## 预装与预写
20
20
 
21
- - **已预装**:基础与控制台插件(@koishi-ce 全家桶)。其中依赖数据库的(admin / bind / analytics / auth / dataview / rate-limit 等)与暂无需启用的(inspect / server-temp)在 `koishi.yml` 中以 `~` 前缀保持禁用——安装并启用任一数据库插件后,去掉对应 `~` 即可。
22
- - **只预写、未预装**:adapter(discord / telegram / qq …)与数据库(sqlite / mongo / postgres …)官方插件以 `~` 禁用条目预写在 `koishi.yml` 的 `group:adapter` / `group:storage`——loader 会跳过禁用条目,不安装也能正常启动;需要时在控制台「插件市场」搜索安装,回到配置页点击启用即可。
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 会跳过禁用条目,不安装也能正常启动;需要时在控制台「插件市场」搜索安装,回到配置页点击启用即可。
23
24
 
24
25
  ## 安装插件
25
26
 
@@ -7,7 +7,10 @@ plugins:
7
7
  group:basic:
8
8
  ~admin: {}
9
9
  ~bind: {}
10
+ ~broadcast: {}
11
+ ~callme: {}
10
12
  commands: {}
13
+ echo: {}
11
14
  help: {}
12
15
  http: {}
13
16
  ~inspect: {}
@@ -63,3 +66,4 @@ plugins:
63
66
  $if: env.NODE_ENV === 'development'
64
67
  hmr:
65
68
  root: .
69
+ ~mock: {}