@twikoojs/edgeone-makers 0.0.1

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 ADDED
@@ -0,0 +1,126 @@
1
+ # @twikoojs/edgeone-makers
2
+
3
+ Twikoo 2.0 服务端适配器。业务逻辑在 `@twikoojs/common`,本包仅做平台入口与注入。
4
+
5
+ ## 部署(腾讯云 EdgeOne Makers)
6
+
7
+ **面向用户的方式**:下载 [`templates/edgeone-makers/twikoo-edgeone-makers.zip`](../../templates/edgeone-makers/twikoo-edgeone-makers.zip),
8
+ 在控制台「创建项目 → 直接上传」时上传该 ZIP 即可。完整步骤(含自定义域名与 HTTPS 证书)
9
+ 见[文档站](https://twikoo.js.org/backend)。
10
+
11
+ 那个 ZIP 是**最小部署包**(两个文件,约 1 KB),只声明依赖:
12
+
13
+ | ZIP 内路径 | 内容 |
14
+ | --- | --- |
15
+ | `cloud-functions/index.js` | 一行转发:`export { onRequest } from "@twikoojs/edgeone-makers"` |
16
+ | `package.json` | `dependencies: { "@twikoojs/edgeone-makers": "latest" }` |
17
+
18
+ **本包在流水线中的角色**:`npm run build` 产出 `dist/`(发布到 npm 的实现)与上面那个 ZIP。
19
+ 两者分工的依据是两条**实测确认**的平台行为(2026-09-24,真实项目):
20
+
21
+ 1. 部署包声明了依赖时,平台会执行 `npm install`
22
+ (构建日志:`[builder] InstallCommand: npm install` / `changed 1 package`);
23
+ 2. 平台的函数构建用打包器把函数打成**单文件**,能解析到的 `node_modules` 依赖会被内联
24
+ (构建日志:`[cli] ✨ Node functions build completed successfully`)。
25
+
26
+ 因此依赖写 `latest` 后,**升级 Twikoo 只需在项目里点「重新部署」**,无需重新下载部署包。
27
+
28
+ ### 平台契约(2026-09-24 在真实项目实测)
29
+
30
+ - 函数目录 `cloud-functions/`,入口导出 `onRequest(context)`,**必须返回 Web 标准 `Response`**
31
+ - `context` 实测键:`clientIp` / `env` / `geo` / `params` / `request` / `server` / `uuid`
32
+ - `context.clientIp` 可用;环境变量 `context.env` 与 `process.env` 高度重叠,入口以
33
+ `context.env` 为准合并进 `process.env`(`@twikoojs/common` 按 Node 惯例读后者)
34
+ - 运行时 **Node v20.19.3**;构建环境 Node v22.21.1(两者不同)
35
+ - 默认域名(`*.edgeone.cool`)**仅 3 小时限时预览**,不带校验参数直接访问返回 401,
36
+ 故生产必须绑定自定义域名
37
+ - **静态资源与函数路由冲突时静态资源优先**:根目录存在 `index.html` 时 `/` 返回网页而非函数
38
+ - **「构建产物」页只保留 `package.json` / `package-lock.json`** —— `cloud-functions/` 下的
39
+ 非入口文件不会落到运行时文件系统(函数运行时路径是 `/var/user/index.mjs`)
40
+ - 平台侧打包器**解析不到任何裸导入就直接构建失败**(实测报
41
+ `✘ [ERROR] Could not resolve "nodemailer"`)
42
+
43
+ ### 已知差异与待办
44
+
45
+ - **SMTP 桥接服务端尚未移植**:1.x 的 `cloud-functions/smtp.go`(Go 函数,挂 `/smtp` 路由)
46
+ 在本包中缺失,因此「自建 SMTP 桥接」通道暂不可用;SendGrid / MailChannels 的 HTTP 通道正常。
47
+
48
+ ## 构建
49
+
50
+ ```sh
51
+ npm run build # = node scripts/build-ip2region-data.mjs && tsdown && node scripts/build-zip.mjs
52
+ ```
53
+
54
+ **顺序不能换**:ip2region 数据必须在 tsdown **之前**生成,否则 `inline.ts` 的字面量
55
+ specifier 解析不到。
56
+
57
+ | 步骤 | 产物 |
58
+ | --- | --- |
59
+ | `build-ip2region-data.mjs` | `src/ip2region/generated/ip2region-data.js`(**不进 git**) |
60
+ | `tsdown` | `dist/cloud-functions/index.js`(单文件,约 7.4 MB,含内联数据) |
61
+ | `build-zip.mjs` | `templates/edgeone-makers/twikoo-edgeone-makers.zip` |
62
+
63
+ ### 为什么 ip2region 数据必须内联
64
+
65
+ `@imaegoo/node-ip2region` 的 `binarySearchSync` 靠 `fs` 随机读 8.33 MB 的 db,而平台上
66
+ 没有可读的兄弟数据文件。1.x 的做法是把 db gzip + base64 成独立模块、运行时按相对路径
67
+ **懒加载**;2.0 一开始照搬,但实测**不成立**:
68
+
69
+ > 平台「构建产物」页只保留 `package.json` / `package-lock.json`,`cloud-functions/generated/`
70
+ > 不会落到运行时文件系统 → `import("./generated/…")` 解析不到 → `ipRegion` 恒为空
71
+ > (被 `comment-dto` 的 `try/catch` 吞掉,**站长和访客都看不到任何报错**)。
72
+
73
+ 所以现在数据由打包器**内联进函数单文件**:
74
+
75
+ | 阶段 | 大小 |
76
+ | --- | --- |
77
+ | `data/ip2region.db` | 8.33 MB |
78
+ | gzip -9 | 4.54 MB |
79
+ | base64(4/3 膨胀) | 6.06 MB |
80
+
81
+ 代价是产物从 ~950 KB 涨到 ~7.4 MB(gzip 后约 226 KB),换来的是**真的能用**。
82
+ `getIp2RegionOverride()` 里的 specifier 必须是**字面量**,改成变量就会静默失去 IP 属地
83
+ (`src/ip2region/inline.ts` 里有详细说明;`loadIp2RegionOverride(specifier)` 那条参数化
84
+ 路径只服务于降级测试)。
85
+
86
+ 自检:
87
+
88
+ ```sh
89
+ npm run check:ip2region # 生成物存在、格式正确、能解压出合法 db 头部
90
+ npm run check:bundle # 依赖清单 + 产物自包含(裸导入)+ 体积区间
91
+ ```
92
+
93
+ ## 能力限制(能力矩阵)
94
+
95
+ - mail:受限(仅上述三通道;`restricted` 声明)
96
+ - domPurify:false(适配器启动时注入直通实现,内容原样存储)
97
+ - akismet / tencentTms:false
98
+ - ip2region:可用(**由 `setCustomLibs` 覆写满足**,依赖不必声明,见上)
99
+ - imageUpload / qqAvatar:可用
100
+
101
+ ## 测试
102
+
103
+ ```sh
104
+ npm test
105
+ ```
106
+
107
+ - `test/ip2region.test.ts`:内联查询器 vs 真实库的**大样本等价性**(85,000+ 个 IP,
108
+ 含全部 /16 块首地址、区间边界、各省代表性 IP,要求 100% 一致)
109
+ - `test/ip2region-inline.test.ts`:生成物 → 加载 → 注入 → common 取用的整条链路
110
+ (含 db 逐字节 sha256 比对、降级路径、进程级缓存)
111
+ - `test/main.test.ts`:契约核心事件 + 受限能力形态 + 产物门禁脚本
112
+
113
+ ## 平台核对清单(查阅日期 2026-09-24,除注明外均为真实部署实测)
114
+
115
+ - [x] 函数目录与路由规则:`cloud-functions/index.js` → `PATH: /`(控制台「函数」页路由表确认)
116
+ - [x] 静态资源优先:根目录存在 `index.html` 时 `/` 返回网页而非函数
117
+ - [x] `onRequest(context)` 契约与 Web `Response` 返回值
118
+ - [x] `context` 实际键:`clientIp` / `env` / `geo` / `params` / `request` / `server` / `uuid`
119
+ - [x] `context.clientIp` 可用(实测取到公网 IP)
120
+ - [x] 运行时 Node **v20.19.3**(构建环境为 v22.21.1)
121
+ - [x] 平台会 `npm install` 部署包声明的依赖,并把函数打成单文件
122
+ - [x] 「构建产物」只保留 `package.json` / `package-lock.json`(故兄弟数据文件必须内联)
123
+ - [x] `@edgeone/pages-blob` **平台不自带**,需由依赖提供
124
+ - [x] 默认域名仅 3 小时限时预览,生产必须绑定自定义域名
125
+ - [x] 端到端:`GET /` 健康检查、`GET_FUNC_VERSION`、`COMMENT_SUBMIT` 写入、`GET_COMMENTS_COUNT` 读取
126
+ - [ ] Go SMTP Bridge 服务端移植与协同构建实测(人工项;见「已知差异与待办」)