create-koishi-ce 1.7.0 → 1.9.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
@@ -29,7 +29,7 @@ npm create koishi-ce my-app
29
29
 
30
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`、`@koishijs/client` / `@koishijs/components` 分别指向 `@koishi-ce/client-shim` / `@koishi-ce/components-shim`,社区插件(含把上游前端库写进 dependencies 的 webui 插件)由此全部解析到 CE 对应包;
31
31
  - **koishi.yml**:按 server(端口 5140-5149)/ basic / console(自动打开、市场指向 registry.koishi.chat 镜像)/ storage / adapter / develop(开发模式启用热重载)分组预写;官方 adapter 插件以 `~` 停用条目预写、不预装,装好后在控制台按需启用;数据库开箱即用——`database-sqlite` 默认启用,mongo / mysql / postgres 等其余 database 不预写占位条目,市场安装后自动出现;
32
- - **开发工具链**:devDependencies 预置 `@koishi-ce/client`、`@koishi-ce/plugin-hmr` 与 `@koishi-ce/scripts`(`koishi-scripts setup` 可在 external/ 下继续创建插件项目)。
32
+ - **开发工具链**:devDependencies 预置 `@koishi-ce/client`、`@koishi-ce/console-app`(宿主 SPA 源码,devMode 的 vite root)、`@koishi-ce/console-builder`(控制台 devMode 的 Vite 开发服务器)、`@koishi-ce/plugin-hmr` 与 `@koishi-ce/scripts`(`koishi-scripts setup` 可在 external/ 下继续创建插件项目)。
33
33
 
34
34
  不预装任何 adapter——官方 adapter 可后续从市场安装,peer 已被 alias 钉住不会形成双实例;数据库已由默认启用的 `database-sqlite` 覆盖,其他官方 database 同样可从市场安装。
35
35
 
@@ -68,7 +68,7 @@ Interactive flow: project name; confirm cleaning a non-empty target directory; c
68
68
 
69
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
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
- - **Toolchain** — devDependencies include `@koishi-ce/client`, `@koishi-ce/plugin-hmr` and `@koishi-ce/scripts` (`koishi-scripts setup` scaffolds further plugin projects under external/).
71
+ - **Toolchain** — devDependencies include `@koishi-ce/client`, `@koishi-ce/console-app` (the host SPA source serving as the devMode vite root), `@koishi-ce/console-builder` (the Vite dev server behind the console devMode), `@koishi-ce/plugin-hmr` and `@koishi-ce/scripts` (`koishi-scripts setup` scaffolds further plugin projects under external/).
72
72
 
73
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
 
package/lib/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { t as start } from "./src-Cj4uFpkr.mjs";
2
+ import { t as start } from "./src-rD4foyof.mjs";
3
3
  //#region src/bin.ts
4
4
  /**
5
5
  * create-koishi-ce 的 CLI 可执行入口:shebang 由 rolldown 原样保留到
package/lib/index.d.ts CHANGED
@@ -16,6 +16,7 @@ interface Manifest {
16
16
  workspaces?: unknown;
17
17
  dependencies?: Record<string, string>;
18
18
  devDependencies?: Record<string, string>;
19
+ overrides?: Record<string, string>;
19
20
  [key: string]: unknown;
20
21
  }
21
22
  /**
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-Cj4uFpkr.mjs";
1
+ import { a as renderManifest, i as readNpmrcRegistry, n as detectAgent, r as getLocalRegistry, t as start } from "./src-rD4foyof.mjs";
2
2
  export { detectAgent, getLocalRegistry, readNpmrcRegistry, renderManifest, start };
@@ -1,15 +1,15 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { basename, join, relative } from "node:path";
3
3
  import { parseArgs } from "node:util";
4
4
  import * as p from "@clack/prompts";
5
5
  import pc from "picocolors";
6
6
  import { getLocalRegistry, readNpmrcRegistry } from "@koishi-ce/utils";
7
- import { downloadTemplate } from "giget";
7
+ import { tmpdir } from "node:os";
8
8
  //#region package.json
9
9
  var package_default = {
10
10
  name: "create-koishi-ce",
11
11
  description: "Setup a Koishi application",
12
- version: "1.7.0",
12
+ version: "1.9.0",
13
13
  type: "module",
14
14
  main: "./lib/index.mjs",
15
15
  module: "./lib/index.mjs",
@@ -41,8 +41,7 @@ var package_default = {
41
41
  dependencies: {
42
42
  "@clack/prompts": "^1.7.0",
43
43
  "@koishi-ce/utils": "workspace:*",
44
- "picocolors": "^1.1.1",
45
- "giget": "^3.3.1"
44
+ "picocolors": "^1.1.1"
46
45
  },
47
46
  exports: {
48
47
  ".": {
@@ -75,7 +74,7 @@ function renderManifest(source, project, prod) {
75
74
  //#region src/remote.ts
76
75
  /**
77
76
  * 远程模板分支(--template <包名>)的下载-解包-改写全流程:拉取 npm
78
- * registry 包元数据解析目标版本,经 giget 下载 tarball 并解包到目标
77
+ * registry 包元数据解析目标版本,经 Bun.Archive 下载 tarball 并解包到目标
79
78
  * 目录,最后改写 package.json。HttpError 与 RegistryMeta 只在本流程
80
79
  * 消费,就近定义不另立文件;本机 registry 配置探测见 registry.ts。
81
80
  *
@@ -97,9 +96,8 @@ var HttpError = class extends Error {
97
96
  /**
98
97
  * 远程模板下载与解包:
99
98
  * 1. 拉取模板包元数据,按 dist-tags 解析目标版本(ref,默认 latest);
100
- * 2. 下载 tarball 并解包到目标目录(解包走 giget,其按 npm tarball 惯例
101
- * 剥离顶层 package/ 目录,等价于 strip: 1),网络错误统一以 HttpError
102
- * 提示后退出;
99
+ * 2. 下载 tarball 并解包到目标目录(Bun.Archive 负责 tar/gzip 解包,按 npm
100
+ * tarball 惯例剥离顶层 package/ 目录),网络错误统一以 HttpError 提示后退出;
103
101
  * 3. 最后改写 package.json。
104
102
  */
105
103
  async function scaffoldRemote({ registry, template, ref, prod, project, rootDir }) {
@@ -111,19 +109,21 @@ async function scaffoldRemote({ registry, template, ref, prod, project, rootDir
111
109
  if (version === void 0) throw new HttpError(404, `模板 ${template}@${ref} 不存在`);
112
110
  const url = remote.versions[version]?.dist?.tarball;
113
111
  if (url === void 0) throw new HttpError(404, `模板 ${template}@${ref} 不存在`);
114
- await downloadTemplate(template, {
115
- provider: "npm",
116
- providers: { npm: async () => ({
117
- name: template,
118
- version,
119
- tar: async () => {
120
- const tarballRes = await fetch(url);
121
- if (!tarballRes.ok || !tarballRes.body) throw new HttpError(tarballRes.status, tarballRes.statusText);
122
- return tarballRes.body;
123
- }
124
- }) },
125
- dir: rootDir
126
- });
112
+ const tarballRes = await fetch(url);
113
+ if (!tarballRes.ok || !tarballRes.body) throw new HttpError(tarballRes.status, tarballRes.statusText);
114
+ const archive = new Bun.Archive(await tarballRes.blob());
115
+ const tempDir = mkdtempSync(join(tmpdir(), "create-koishi-ce-"));
116
+ try {
117
+ await archive.extract(tempDir);
118
+ const packageDir = join(tempDir, "package");
119
+ if (!existsSync(packageDir)) throw new Error("远程模板归档缺少 package/ 目录");
120
+ for (const entry of readdirSync(packageDir)) renameSync(join(packageDir, entry), join(rootDir, entry));
121
+ } finally {
122
+ rmSync(tempDir, {
123
+ recursive: true,
124
+ force: true
125
+ });
126
+ }
127
127
  } catch (err) {
128
128
  if (!(err instanceof HttpError)) throw err;
129
129
  console.log(`${pc.red("error")} 请求失败:HTTP ${err.status} ${err.statusText}`);
@@ -197,6 +197,74 @@ const dotFiles = {
197
197
  /** 模板静态文件(相对项目根的路径 → 文件内容),package.json 另行渲染 */
198
198
  const templateFiles = Object.fromEntries(readdirSync(templateDir).filter((file) => !file.startsWith(".")).map((file) => [dotFiles[file] ?? file, readFileSync(join(templateDir, file), "utf8")]));
199
199
  /**
200
+ * 上游名 → CE 对应包的 overrides 兜底映射(值统一 npm:<目标>@^1.0.0,
201
+ * 经 buildUpstreamOverrides 包装)。
202
+ *
203
+ * 六行钉名依赖「落盘版本满足声明范围」:第三方插件的声明超出冻结线
204
+ * (如未来上游升线后的 ^6)或引用清单外的上游名(如 dependencies 直接
205
+ * 依赖 @koishijs/plugin-admin / @koishijs/utils)时,官方包仍会落盘。
206
+ * overrides 是包管理器级的强制重写——不看版本满足性,把整棵依赖树中对
207
+ * 这些上游名的解析一律改指 CE 对应包,与钉名构成双层防线(napuketto
208
+ * 事件 2026-09-19 后补)。
209
+ *
210
+ * 清单 = CE 已再分发且官方同名的包,2026-09-19 逐一 npm view 核实:
211
+ * - plugin-cron 不在列(上游是裸名社区包 koishi-plugin-cron,非 @koishijs
212
+ * 作用域);
213
+ * - plugin-welcome / plugin-theme-vanilla 不在列(CE 原创,上游无此名);
214
+ * - plugin-assets 的 CE 对应物是 @koishi-ce/assets(无 plugin 前缀)。
215
+ * 新增 CE 再分发包时须同步本表与 sandbox 生成器的同款清单
216
+ * (tooling/sandbox/manifest.ts,对账测试防漂移)。
217
+ */
218
+ const UPSTREAM_OVERRIDES = {
219
+ koishi: "@koishi-ce/koishi",
220
+ "@koishijs/core": "@koishi-ce/koishi",
221
+ "@koishijs/loader": "@koishi-ce/koishi",
222
+ "@koishijs/utils": "@koishi-ce/utils",
223
+ "@koishijs/i18n-utils": "@koishi-ce/i18n-utils",
224
+ "@koishijs/client": "@koishi-ce/client",
225
+ "@koishijs/components": "@koishi-ce/components",
226
+ "@koishijs/console": "@koishi-ce/console",
227
+ "@koishijs/registry": "@koishi-ce/registry",
228
+ "@koishijs/plugin-console": "@koishi-ce/plugin-console",
229
+ "@koishijs/plugin-assets": "@koishi-ce/assets"
230
+ };
231
+ for (const name of [
232
+ "actions",
233
+ "admin",
234
+ "analytics",
235
+ "assets-local",
236
+ "auth",
237
+ "bind",
238
+ "broadcast",
239
+ "callme",
240
+ "commands",
241
+ "config",
242
+ "database-sqlite",
243
+ "dataview",
244
+ "echo",
245
+ "explorer",
246
+ "help",
247
+ "hmr",
248
+ "insight",
249
+ "inspect",
250
+ "locales",
251
+ "logger",
252
+ "market",
253
+ "mock",
254
+ "notifier",
255
+ "oobe",
256
+ "proxy-agent",
257
+ "rate-limit",
258
+ "sandbox",
259
+ "server",
260
+ "server-temp",
261
+ "status"
262
+ ]) UPSTREAM_OVERRIDES[`@koishijs/plugin-${name}`] = `@koishi-ce/plugin-${name}`;
263
+ /** 生成模板 overrides 块:键为上游名,值统一 npm:<CE 包>@^1.0.0。 */
264
+ function buildUpstreamOverrides() {
265
+ return Object.fromEntries(Object.entries(UPSTREAM_OVERRIDES).map(([name, target]) => [name, `npm:${target}@^1.0.0`]));
266
+ }
267
+ /**
200
268
  * 内置模板的 package.json 基础内容(name/version 会被 renderManifest 覆写,
201
269
  * prod 模式下 workspaces 与 devDependencies 会被移除)。
202
270
  */
@@ -261,12 +329,15 @@ function baseManifest() {
261
329
  "@koishijs/client": "npm:@koishi-ce/client-shim@^5.30.11",
262
330
  "@koishijs/components": "npm:@koishi-ce/components-shim@^1.5.22"
263
331
  },
332
+ overrides: buildUpstreamOverrides(),
264
333
  devDependencies: {
265
334
  "@koishi-ce/client": "^1.0.0",
335
+ "@koishi-ce/console-app": "^1.0.0",
336
+ "@koishi-ce/console-builder": "^1.0.0",
266
337
  "@koishi-ce/plugin-hmr": "^1.0.0",
267
338
  "@koishi-ce/plugin-mock": "^1.0.0",
268
339
  "@koishi-ce/scripts": "^1.0.0",
269
- "bun-types": "^1.4.0"
340
+ "@types/bun": "^1.4.0"
270
341
  }
271
342
  };
272
343
  }
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.7.0",
4
+ "version": "1.9.0",
5
5
  "type": "module",
6
6
  "main": "./lib/index.mjs",
7
7
  "module": "./lib/index.mjs",
@@ -45,8 +45,7 @@
45
45
  "dependencies": {
46
46
  "@clack/prompts": "^1.7.0",
47
47
  "@koishi-ce/utils": "^1.1.0",
48
- "picocolors": "^1.1.1",
49
- "giget": "^3.3.1"
48
+ "picocolors": "^1.1.1"
50
49
  },
51
50
  "exports": {
52
51
  ".": {
@@ -18,7 +18,7 @@ import {
18
18
  rmSync,
19
19
  writeFileSync,
20
20
  } from "node:fs";
21
- import { homedir, tmpdir } from "node:os";
21
+ import { tmpdir } from "node:os";
22
22
  import { join } from "node:path";
23
23
  import { tarPack } from "./tar-pack.ts";
24
24
 
@@ -29,7 +29,7 @@ import { tarPack } from "./tar-pack.ts";
29
29
  * 场景(正常 / 元数据 404 / dist-tag 缺失 / tarball 404 / tarball 损坏);
30
30
  * tarball 由 __tests__/tar-pack.ts 的 tarPack 打包(ustar + gzip,与真实
31
31
  * npm tarball 同构),scaffoldRemote 的下载-解包-改写全链路真实执行
32
- * (解包走 giget,含超长名 pax 条目的端到端验证)。
32
+ * (解包走 Bun.Archive,含超长名 pax 条目的端到端验证)。
33
33
  *
34
34
  * 覆盖率口径说明:bun 的覆盖率对同一路径的多个 query 实例只统计最后
35
35
  * 求值的那份,本文件是 koishi-create 全部测试中最后加载的实例,因此
@@ -180,7 +180,7 @@ beforeAll(async () => {
180
180
  },
181
181
  {
182
182
  // 124 字符 > ustar 的 100 字节 name 上限:tarPack 前置 pax
183
- // 扩展头,giget(tar 库)经 pax 还原后同样能正确落盘
183
+ // 扩展头,Bun.Archive 经 pax 还原后同样能正确落盘
184
184
  path: `package/${longName}.txt`,
185
185
  data: encoder.encode("pax"),
186
186
  },
@@ -203,19 +203,6 @@ function reset(sc: typeof scenario): void {
203
203
  recursive: true,
204
204
  force: true,
205
205
  });
206
- // giget 缓存位置随平台而异(win32 为 tmpdir()/giget,POSIX 跟随
207
- // XDG_CACHE_HOME——默认 ~/.cache);逐用例清理,避免上一场景的缓存
208
- // (tarball-404 的缺失或 corrupt 的损坏归档)被 giget 的
209
- // 「下载失败回退缓存」吞掉
210
- const cacheRoot =
211
- process.env["XDG_CACHE_HOME"] ??
212
- join(homedir(), ".cache");
213
- for (const dir of [
214
- join(tmpdir(), "giget"),
215
- join(cacheRoot, "giget"),
216
- ]) {
217
- rmSync(dir, { recursive: true, force: true });
218
- }
219
206
  }
220
207
 
221
208
  describe("create-koishi-ce 远程模板", () => {
@@ -224,7 +211,7 @@ describe("create-koishi-ce 远程模板", () => {
224
211
  await start();
225
212
  const dir = join(workspaceRoot, "myapp");
226
213
  expect(existsSync(join(dir, "index.js"))).toBe(true);
227
- // 超长名文件经 pax 扩展头正常落盘(tarPack 打包 → giget 解包全链路)
214
+ // 超长名文件经 pax 扩展头正常落盘(tarPack 打包 → Bun.Archive 解包全链路)
228
215
  expect(
229
216
  readFileSync(join(dir, `${longName}.txt`), "utf8"),
230
217
  ).toBe("pax");
@@ -4,10 +4,10 @@
4
4
  /**
5
5
  * 测试专用 .tgz fixture 构造器(ustar + 超长名 pax 兜底 + gzip)。
6
6
  *
7
- * 生产代码的远程模板解包已改走 giget(见 src/index.ts scaffoldRemote),
7
+ * 生产代码的远程模板解包已改走 Bun.Archive(见 src/index.ts scaffoldRemote),
8
8
  * 这里只保留「打包」一侧供测试构造伪 registry 的 tarball 响应。tar
9
9
  * 归档格式稳定,此工具不随业务演进,故不再单独维护解析逻辑与单测
10
- * ——其正确性由 __tests__/run-remote.test.ts 的「打包 → giget 解包」
10
+ * ——其正确性由 __tests__/run-remote.test.ts 的「打包 → Bun.Archive 解包」
11
11
  * 端到端链路间接验证。
12
12
  */
13
13
 
@@ -5,6 +5,7 @@ import { expect, test } from "bun:test";
5
5
  import { renderManifest } from "../index.ts";
6
6
  import {
7
7
  baseManifest,
8
+ buildUpstreamOverrides,
8
9
  templateFiles,
9
10
  } from "../template.ts";
10
11
 
@@ -28,7 +29,7 @@ test("内置模板依赖纯度:不含任何 @koishijs / koishi-plugin 官方
28
29
  }
29
30
  for (const key of Object.keys(devDependencies ?? {})) {
30
31
  expect(
31
- key === "bun-types" || key.startsWith("@koishi-ce/"),
32
+ key === "@types/bun" || key.startsWith("@koishi-ce/"),
32
33
  ).toBe(true);
33
34
  }
34
35
  });
@@ -178,13 +179,14 @@ test("koishi.yml 预写策略:sqlite 默认启用开箱数据库,非必需
178
179
  expect(yml).toContain(name);
179
180
  }
180
181
  expect(yml).not.toContain("database-mongo");
181
- // 模板依赖里不得出现官方 adapter / database 包名(只预写不预装)
182
- expect(JSON.stringify(baseManifest())).not.toContain(
183
- "adapter-",
184
- );
185
- expect(JSON.stringify(baseManifest())).not.toContain(
186
- "@koishijs/plugin-database-",
187
- );
182
+ // 模板依赖里不得出现官方 adapter / database 包名(只预写不预装)。
183
+ // 断言只扫 dependencies——overrides 的键含官方名是重写所需,属预期
184
+ expect(
185
+ JSON.stringify(baseManifest().dependencies),
186
+ ).not.toContain("adapter-");
187
+ expect(
188
+ JSON.stringify(baseManifest().dependencies),
189
+ ).not.toContain("@koishijs/plugin-database-");
188
190
  });
189
191
 
190
192
  test("koishi.yml 预写条目与模板依赖对账:CE 插件必须在册,官方 adapter 只预写不预装", () => {
@@ -264,3 +266,65 @@ test("renderManifest 渲染内置模板:常规改写生效,prod 模式保留
264
266
  "npm:@koishi-ce/koishi-shim@^4.18.11",
265
267
  );
266
268
  });
269
+
270
+ test("内置模板 overrides 兜底层:41 个上游名强制重定向 CE 包", () => {
271
+ const table = baseManifest().overrides ?? {};
272
+ expect(Object.keys(table)).toHaveLength(41);
273
+ const keys = Object.keys(table);
274
+ // 核心九名(钉名六行之外由 overrides 补齐覆盖面)
275
+ expect(table["koishi"]).toBe(
276
+ "npm:@koishi-ce/koishi@^1.0.0",
277
+ );
278
+ expect(table["@koishijs/core"]).toBe(
279
+ "npm:@koishi-ce/koishi@^1.0.0",
280
+ );
281
+ expect(table["@koishijs/loader"]).toBe(
282
+ "npm:@koishi-ce/koishi@^1.0.0",
283
+ );
284
+ expect(table["@koishijs/utils"]).toBe(
285
+ "npm:@koishi-ce/utils@^1.0.0",
286
+ );
287
+ expect(table["@koishijs/i18n-utils"]).toBe(
288
+ "npm:@koishi-ce/i18n-utils@^1.0.0",
289
+ );
290
+ expect(table["@koishijs/client"]).toBe(
291
+ "npm:@koishi-ce/client@^1.0.0",
292
+ );
293
+ expect(table["@koishijs/components"]).toBe(
294
+ "npm:@koishi-ce/components@^1.0.0",
295
+ );
296
+ expect(table["@koishijs/console"]).toBe(
297
+ "npm:@koishi-ce/console@^1.0.0",
298
+ );
299
+ expect(table["@koishijs/registry"]).toBe(
300
+ "npm:@koishi-ce/registry@^1.0.0",
301
+ );
302
+ // CE 原创与上游裸名社区对应物不进清单(不拦官方生态的真实依赖)
303
+ expect(keys.includes("@koishijs/plugin-welcome")).toBe(
304
+ false,
305
+ );
306
+ expect(
307
+ keys.includes("@koishijs/plugin-theme-vanilla"),
308
+ ).toBe(false);
309
+ expect(keys.includes("@koishijs/plugin-cron")).toBe(
310
+ false,
311
+ );
312
+ // plugin-assets 的 CE 对应物无 plugin 前缀
313
+ expect(table["@koishijs/plugin-assets"]).toBe(
314
+ "npm:@koishi-ce/assets@^1.0.0",
315
+ );
316
+ // 全部值为 npm:@koishi-ce/ 前缀,键全为 @koishijs/ 上游名或 koishi 裸名
317
+ for (const key of keys) {
318
+ expect(
319
+ key === "koishi" || key.startsWith("@koishijs/"),
320
+ ).toBe(true);
321
+ expect(table[key]).toMatch(/^npm:@koishi-ce\//);
322
+ }
323
+ // buildUpstreamOverrides 与 baseManifest().overrides 同源
324
+ expect(buildUpstreamOverrides()).toEqual(table);
325
+ // renderManifest 全程透传 overrides(prod 模式也不删)
326
+ const prod = JSON.parse(
327
+ renderManifest(baseManifest(), "my-app", true),
328
+ );
329
+ expect(prod.overrides).toEqual(table);
330
+ });
package/src/manifest.ts CHANGED
@@ -18,6 +18,7 @@ export interface Manifest {
18
18
  workspaces?: unknown;
19
19
  dependencies?: Record<string, string>;
20
20
  devDependencies?: Record<string, string>;
21
+ overrides?: Record<string, string>;
21
22
  [key: string]: unknown;
22
23
  }
23
24
 
package/src/remote.ts CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  /**
5
5
  * 远程模板分支(--template <包名>)的下载-解包-改写全流程:拉取 npm
6
- * registry 包元数据解析目标版本,经 giget 下载 tarball 并解包到目标
6
+ * registry 包元数据解析目标版本,经 Bun.Archive 下载 tarball 并解包到目标
7
7
  * 目录,最后改写 package.json。HttpError 与 RegistryMeta 只在本流程
8
8
  * 消费,就近定义不另立文件;本机 registry 配置探测见 registry.ts。
9
9
  *
@@ -12,9 +12,17 @@
12
12
  * 经 RemoteOptions 显式传入,由 index.ts 的 scaffold 组装,避免子模块
13
13
  * 与顶层状态的隐式耦合。
14
14
  */
15
- import { readFileSync, writeFileSync } from "node:fs";
15
+ import {
16
+ existsSync,
17
+ mkdtempSync,
18
+ readdirSync,
19
+ readFileSync,
20
+ renameSync,
21
+ rmSync,
22
+ writeFileSync,
23
+ } from "node:fs";
24
+ import { tmpdir } from "node:os";
16
25
  import { join } from "node:path";
17
- import { downloadTemplate } from "giget";
18
26
  import pc from "picocolors";
19
27
  import {
20
28
  type Manifest,
@@ -57,9 +65,8 @@ interface RemoteOptions {
57
65
  /**
58
66
  * 远程模板下载与解包:
59
67
  * 1. 拉取模板包元数据,按 dist-tags 解析目标版本(ref,默认 latest);
60
- * 2. 下载 tarball 并解包到目标目录(解包走 giget,其按 npm tarball 惯例
61
- * 剥离顶层 package/ 目录,等价于 strip: 1),网络错误统一以 HttpError
62
- * 提示后退出;
68
+ * 2. 下载 tarball 并解包到目标目录(Bun.Archive 负责 tar/gzip 解包,按 npm
69
+ * tarball 惯例剥离顶层 package/ 目录),网络错误统一以 HttpError 提示后退出;
63
70
  * 3. 最后改写 package.json。
64
71
  */
65
72
  export async function scaffoldRemote({
@@ -93,30 +100,34 @@ export async function scaffoldRemote({
93
100
  );
94
101
  }
95
102
 
96
- // 解包交给 giget:provider 的 tar 字段是函数,giget 需要时才发起
97
- // 下载(HttpError 在下载阶段抛出,不经 giget 包装可直接识别);
98
- // giget 先把 tarball 落入本地缓存再解压到 rootDir,解压失败
99
- // (gzip 损坏 / 归档截断)走下方非 HttpError 分支原样上抛。
100
- await downloadTemplate(template, {
101
- provider: "npm",
102
- providers: {
103
- npm: async () => ({
104
- name: template,
105
- version,
106
- tar: async () => {
107
- const tarballRes = await fetch(url);
108
- if (!tarballRes.ok || !tarballRes.body) {
109
- throw new HttpError(
110
- tarballRes.status,
111
- tarballRes.statusText,
112
- );
113
- }
114
- return tarballRes.body;
115
- },
116
- }),
117
- },
118
- dir: rootDir,
119
- });
103
+ const tarballRes = await fetch(url);
104
+ if (!tarballRes.ok || !tarballRes.body) {
105
+ throw new HttpError(
106
+ tarballRes.status,
107
+ tarballRes.statusText,
108
+ );
109
+ }
110
+ const archive = new Bun.Archive(
111
+ await tarballRes.blob(),
112
+ );
113
+ const tempDir = mkdtempSync(
114
+ join(tmpdir(), "create-koishi-ce-"),
115
+ );
116
+ try {
117
+ await archive.extract(tempDir);
118
+ const packageDir = join(tempDir, "package");
119
+ if (!existsSync(packageDir)) {
120
+ throw new Error("远程模板归档缺少 package/ 目录");
121
+ }
122
+ for (const entry of readdirSync(packageDir)) {
123
+ renameSync(
124
+ join(packageDir, entry),
125
+ join(rootDir, entry),
126
+ );
127
+ }
128
+ } finally {
129
+ rmSync(tempDir, { recursive: true, force: true });
130
+ }
120
131
  } catch (err) {
121
132
  if (!(err instanceof HttpError)) throw err;
122
133
  console.log(
@@ -1,16 +1,23 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ESNext"],
7
- "types": ["bun-types"],
8
- "strict": true,
9
- "skipLibCheck": true,
10
- "noEmit": true,
11
- "allowImportingTsExtensions": true,
12
- "verbatimModuleSyntax": true,
13
- "erasableSyntaxOnly": true
14
- },
15
- "include": ["plugins/*/src", "external/*/src"]
16
- }
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": [
7
+ "ESNext"
8
+ ],
9
+ "types": [
10
+ "bun"
11
+ ],
12
+ "strict": true,
13
+ "skipLibCheck": true,
14
+ "noEmit": true,
15
+ "allowImportingTsExtensions": true,
16
+ "verbatimModuleSyntax": true,
17
+ "erasableSyntaxOnly": true
18
+ },
19
+ "include": [
20
+ "plugins/*/src",
21
+ "external/*/src"
22
+ ]
23
+ }
package/src/template.ts CHANGED
@@ -85,6 +85,88 @@ export const templateFiles: Record<string, string> =
85
85
  ]),
86
86
  );
87
87
 
88
+ /**
89
+ * 上游名 → CE 对应包的 overrides 兜底映射(值统一 npm:<目标>@^1.0.0,
90
+ * 经 buildUpstreamOverrides 包装)。
91
+ *
92
+ * 六行钉名依赖「落盘版本满足声明范围」:第三方插件的声明超出冻结线
93
+ * (如未来上游升线后的 ^6)或引用清单外的上游名(如 dependencies 直接
94
+ * 依赖 @koishijs/plugin-admin / @koishijs/utils)时,官方包仍会落盘。
95
+ * overrides 是包管理器级的强制重写——不看版本满足性,把整棵依赖树中对
96
+ * 这些上游名的解析一律改指 CE 对应包,与钉名构成双层防线(napuketto
97
+ * 事件 2026-09-19 后补)。
98
+ *
99
+ * 清单 = CE 已再分发且官方同名的包,2026-09-19 逐一 npm view 核实:
100
+ * - plugin-cron 不在列(上游是裸名社区包 koishi-plugin-cron,非 @koishijs
101
+ * 作用域);
102
+ * - plugin-welcome / plugin-theme-vanilla 不在列(CE 原创,上游无此名);
103
+ * - plugin-assets 的 CE 对应物是 @koishi-ce/assets(无 plugin 前缀)。
104
+ * 新增 CE 再分发包时须同步本表与 sandbox 生成器的同款清单
105
+ * (tooling/sandbox/manifest.ts,对账测试防漂移)。
106
+ */
107
+ const UPSTREAM_OVERRIDES: Record<string, string> = {
108
+ koishi: "@koishi-ce/koishi",
109
+ "@koishijs/core": "@koishi-ce/koishi",
110
+ "@koishijs/loader": "@koishi-ce/koishi",
111
+ "@koishijs/utils": "@koishi-ce/utils",
112
+ "@koishijs/i18n-utils": "@koishi-ce/i18n-utils",
113
+ "@koishijs/client": "@koishi-ce/client",
114
+ "@koishijs/components": "@koishi-ce/components",
115
+ "@koishijs/console": "@koishi-ce/console",
116
+ "@koishijs/registry": "@koishi-ce/registry",
117
+ "@koishijs/plugin-console": "@koishi-ce/plugin-console",
118
+ "@koishijs/plugin-assets": "@koishi-ce/assets",
119
+ };
120
+
121
+ // CE 与上游同名再分发的 plugin-*:机械一一对应
122
+ for (const name of [
123
+ "actions",
124
+ "admin",
125
+ "analytics",
126
+ "assets-local",
127
+ "auth",
128
+ "bind",
129
+ "broadcast",
130
+ "callme",
131
+ "commands",
132
+ "config",
133
+ "database-sqlite",
134
+ "dataview",
135
+ "echo",
136
+ "explorer",
137
+ "help",
138
+ "hmr",
139
+ "insight",
140
+ "inspect",
141
+ "locales",
142
+ "logger",
143
+ "market",
144
+ "mock",
145
+ "notifier",
146
+ "oobe",
147
+ "proxy-agent",
148
+ "rate-limit",
149
+ "sandbox",
150
+ "server",
151
+ "server-temp",
152
+ "status",
153
+ ]) {
154
+ UPSTREAM_OVERRIDES[`@koishijs/plugin-${name}`] =
155
+ `@koishi-ce/plugin-${name}`;
156
+ }
157
+
158
+ /** 生成模板 overrides 块:键为上游名,值统一 npm:<CE 包>@^1.0.0。 */
159
+ export function buildUpstreamOverrides(): Record<
160
+ string,
161
+ string
162
+ > {
163
+ return Object.fromEntries(
164
+ Object.entries(UPSTREAM_OVERRIDES).map(
165
+ ([name, target]) => [name, `npm:${target}@^1.0.0`],
166
+ ),
167
+ );
168
+ }
169
+
88
170
  /**
89
171
  * 内置模板的 package.json 基础内容(name/version 会被 renderManifest 覆写,
90
172
  * prod 模式下 workspaces 与 devDependencies 会被移除)。
@@ -187,12 +269,17 @@ export function baseManifest(): Manifest {
187
269
  "@koishijs/components":
188
270
  "npm:@koishi-ce/components-shim@^1.5.22",
189
271
  },
272
+ // overrides 兜底层(语义见 UPSTREAM_OVERRIDES 注释):不看版本
273
+ // 满足性强制重写整棵依赖树,拦住钉名覆盖面之外的上游声明
274
+ overrides: buildUpstreamOverrides(),
190
275
  devDependencies: {
191
276
  "@koishi-ce/client": "^1.0.0",
277
+ "@koishi-ce/console-app": "^1.0.0",
278
+ "@koishi-ce/console-builder": "^1.0.0",
192
279
  "@koishi-ce/plugin-hmr": "^1.0.0",
193
280
  "@koishi-ce/plugin-mock": "^1.0.0",
194
281
  "@koishi-ce/scripts": "^1.0.0",
195
- "bun-types": "^1.4.0",
282
+ "@types/bun": "^1.4.0",
196
283
  },
197
284
  };
198
285
  }