diff --git a/README.md b/README.md
index dedd7bdf..61da93e8 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+
中文
+
# Counterscale

@@ -63,6 +65,8 @@ NOTE: _If this is your first time deploying Counterscale, it may take take a few
### Start Recording Web Traffic from Your Website(s)
+**Site ID:** There is no separate "get site ID" step. You choose any string that uniquely identifies this site (e.g. your domain `example.com`, a slug `my-blog`, or a UUID). Use the same site ID in your tracking code and in the dashboard when filtering by site.
+
You can load the tracking code using one of two methods:
#### 1. Script Loader (CDN)
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 00000000..6c699552
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,304 @@
+English
+
+# Counterscale
+
+
+
+
+[](https://github.com/benvinegar/counterscale/blob/master/LICENSE)
+[](https://codecov.io/gh/benvinegar/counterscale)
+
+Counterscale 是一款简单的网站分析追踪与仪表盘,可在 Cloudflare 上自托管部署。
+
+设计目标是易于部署与维护,运营成本趋近于零——即便在高流量下(Cloudflare [免费版](https://developers.cloudflare.com/workers/platform/pricing/#workers)理论上可支持约 10 万次/天的请求)。
+
+_Counterscale 由 [Modem——开发团队的自动分类 PM](https://modem.dev) 赞助。_
+
+## 许可证
+
+Counterscale 为免费开源软件,采用 MIT 许可证。详见:[LICENSE](LICENSE)。
+
+## 限制说明
+
+Counterscale 主要基于 Cloudflare Workers 与 [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/)。截至 2025 年 2 月,Workers Analytics Engine 的**最长保留期为 90 天**,因此 Counterscale 仅能展示最近 90 天的记录数据。我们同时支持将数据长期存储在 R2 桶中(使用 Apache Arrow 文件),该长期存储默认开启,可通过 CLI 关闭。
+
+## 安装
+
+### 环境要求
+
+* macOS 或 Linux
+* Node v20 及以上
+* 有效的 [Cloudflare](https://cloudflare.com) 账号(免费或付费均可)
+
+### Cloudflare 准备
+
+若尚未拥有账号,请[在此创建 Cloudflare 账号](https://dash.cloudflare.com/sign-up)并完成邮箱验证。
+
+1. 登录 Cloudflare 控制台,若尚未配置,请先设置 [Cloudflare Workers 子域名](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/)
+2. 为账号启用 [Cloudflare Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/) 测试版:进入「存储与数据库 > Analytics Engine」并点击「启用」按钮([截图](./docs/enable-analytics-engine.png))。随后弹出的「创建数据集」窗口可忽略并关闭。
+ - 说明:若首次使用 Workers,需先创建一个 Worker 才能启用 Analytics Engine。进入「Workers 与 Pages > 概览」,点击「创建 Worker」按钮([截图](./docs/create-worker.png))创建一个「Hello World」Worker(名称可任意,之后可删除)。
+3. 创建 [Cloudflare API 令牌](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/)。该令牌至少需要具备 `Account.Account Analytics` 权限([截图](./docs/api-token.png))。
+ - _警告:请保持该页面打开或将 API 令牌妥善保存(如密码管理器),关闭窗口后将无法再次查看该令牌,需重新创建。_
+
+### 部署 Counterscale
+
+首先登录 Cloudflare 并授权 Cloudflare CLI(Wrangler):
+
+```bash
+npx wrangler login
+```
+
+然后运行 Counterscale 安装程序:
+
+```bash
+npx @counterscale/cli@latest install
+```
+
+按提示操作。会要求输入之前创建的 Cloudflare API 令牌,并询问是否用密码保护仪表盘:
+
+- 选择 **是**(建议用于公开部署):需设置一个密码,访问分析仪表盘时需输入该密码。
+- 选择 **否**:仪表盘将公开可访问,无需认证。
+
+脚本完成后,服务端应用即已部署。可访问 `https://{部署时输出的子域名}.workers.dev` 进行验证。
+
+注意:_首次部署 Counterscale 时,Worker 子域名可能需要几分钟才能生效。_
+
+### 开始记录网站流量
+
+**站点 ID(site-id):** 不需要从别处「获取」,由你自己定义。任选一个能唯一标识该站点的字符串即可,例如域名 `example.com`、缩写 `my-blog` 或 UUID。追踪代码和仪表盘里筛选站点时使用同一个 site-id 即可。
+
+可通过两种方式加载追踪代码:
+
+#### 1. 脚本加载器(CDN)
+
+部署后,Counterscale 会在部署地址提供 `tracker.js`:
+
+```
+https://{部署时输出的子域名}.workers.dev/tracker.js
+```
+
+将以下代码片段复制到网站 HTML 中即可开始上报流量:
+
+```html
+
+```
+
+#### 2. 包/模块方式
+
+Counterscale 追踪器以 npm 包形式发布:
+
+```bash
+npm install @counterscale/tracker
+```
+
+使用站点 ID 和部署的上报端点 URL 初始化:
+
+```typescript
+import * as Counterscale from "@counterscale/tracker";
+
+Counterscale.init({
+ siteId: "your-unique-site-id",
+ reporterUrl: "https://{部署时输出的子域名}.workers.dev/collect",
+});
+```
+
+**可用方法**
+| 方法 | 参数 | 返回类型 | 说明 |
+|--------|------------|-------------|-------------|
+| `init(opts)` | `ClientOpts` | `void` | 使用站点配置初始化 Counterscale 客户端;若不存在则创建全局实例。 |
+| `isInitialized()` | 无 | `boolean` | 检查客户端是否已初始化。 |
+| `getInitializedClient()` | 无 | `Client \| undefined` | 返回已初始化的客户端实例,未初始化则返回 undefined。 |
+| `trackPageview(opts?)` | `TrackPageviewOpts?` | `void` | 记录一次页面浏览。需先初始化客户端;未提供时自动检测 URL 与 referrer。 |
+| `cleanup()` | 无 | `void` | 清理客户端实例并移除事件监听,将全局客户端设为 undefined。 |
+
+#### 3. 服务端模块
+
+若希望在服务端而非浏览器中上报分析数据,可使用 `/server` 模块:
+
+```bash
+npm install @counterscale/tracker
+```
+
+```typescript
+import * as Counterscale from "@counterscale/tracker/server";
+
+// 初始化追踪器
+Counterscale.init({
+ siteId: "your-unique-site-id",
+ reporterUrl:
+ "https://{部署时输出的子域名}.workers.dev/collect",
+ reportOnLocalhost: false, // 可选,默认 false
+ timeout: 2000, // 可选,默认 1000ms
+});
+
+// 记录一次页面浏览
+await Counterscale.trackPageview({
+ url: "https://example.com/page", // 或相对路径:'/page'
+ hostname: "example.com", // 使用相对 URL 时必填
+ referrer: "https://google.com",
+ utmSource: "social",
+ utmMedium: "twitter",
+});
+```
+
+**服务端模块方法**
+| 方法 | 参数 | 返回类型 | 说明 |
+|--------|------------|-------------|-------------|
+| `init(opts)` | `ServerClientOpts` | `void` | 初始化服务端追踪器。 |
+| `isInitialized()` | 无 | `boolean` | 检查追踪器是否已初始化。 |
+| `getInitializedClient()` | 无 | `ServerClient \| undefined` | 返回已初始化的服务端客户端实例。 |
+| `trackPageview(opts)` | `TrackPageviewOpts` | `Promise` | 记录一次页面浏览。需显式传入 URL 和 hostname。 |
+| `cleanup()` | 无 | `void` | 清理服务端客户端实例。 |
+
+服务端模块面向后端应用,与客户端版本区别如下:
+
+- 无依赖 DOM 的功能(自动追踪、浏览器埋点)
+- 使用 fetch API 而非 XMLHttpRequest
+- 需显式传入 URL 和 hostname
+- 采用「发出即忘」策略,追踪错误不会抛出异常
+
+## 升级
+
+大多数版本升级只需重新执行 CLI 安装命令:
+
+```bash
+npx @counterscale/cli@latest install
+
+# 或指定版本
+# npx @counterscale/cli@VERSION install
+```
+
+无需重新输入 API 密钥,数据会保留。
+
+Counterscale 使用[语义化版本](https://semver.org/)。升级主版本(如 2.x、3.x、4.x)时可能有额外步骤,请参考[发布说明](https://github.com/benvinegar/counterscale/releases)。
+
+## 故障排除
+
+若网站无法立即访问(例如「安全连接失败」),可能是 Cloudflare 尚未激活你的子域名(yoursubdomain.workers.dev)。该过程可能需要一分钟;可在 Cloudflare 控制台(Workers & Pages → counterscale)中查看新建 Worker 的状态。
+
+## 高级用法
+
+### 手动记录页面浏览
+
+初始化 Counterscale 追踪器时,将 `autoTrackPageviews` 设为 `false`,然后在需要记录时手动调用 `Counterscale.trackPageview()`。
+
+```typescript
+import * as Counterscale from "@counterscale/tracker";
+
+Counterscale.init({
+ siteId: "your-unique-site-id",
+ reporterUrl: "https://{部署时输出的子域名}.workers.dev/collect",
+ autoTrackPageviews: false, // <- 不要忘记此项
+});
+
+// ... 在发生页面浏览时
+Counterscale.trackPageview();
+```
+
+### 自定义域名
+
+部署地址可改为使用你拥有的自定义域名。详见[此处](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/)。
+
+## CLI 命令
+
+Counterscale 提供命令行工具(CLI)用于安装、配置和管理部署。
+
+### 可用命令
+
+#### `install`
+
+安装并将 Counterscale 部署到 Cloudflare 的主命令。
+
+```bash
+npx @counterscale/cli@latest install
+```
+
+选项:
+
+- `--advanced` - 启用高级模式,可自定义 Worker 名称与分析数据集
+- `--verbose` - 输出更多日志
+
+#### `auth`
+
+管理 Counterscale 部署的认证设置。
+
+```bash
+npx @counterscale/cli@latest auth [子命令]
+```
+
+子命令:
+
+- `enable` - 为部署启用认证
+- `disable` - 关闭认证
+- `roll` - 更新/轮换认证密码
+
+##### 示例
+
+启用认证:
+
+```bash
+npx @counterscale/cli@latest auth enable
+```
+
+关闭认证:
+
+```bash
+npx @counterscale/cli@latest auth disable
+```
+
+更新密码:
+
+```bash
+npx @counterscale/cli@latest auth roll
+```
+
+#### `storage`
+
+管理 Counterscale 部署的长期存储设置。
+
+```bash
+npx @counterscale/cli@latest storage [子命令]
+```
+
+子命令:
+
+- `enable` - 为部署启用存储
+- `disable` - 关闭存储
+
+##### 示例
+
+启用存储:
+
+```bash
+npx @counterscale/cli@latest storage enable
+```
+
+关闭存储:
+
+```bash
+npx @counterscale/cli@latest storage disable
+```
+
+## 开发
+
+如何参与开发请参阅 [Contributing](CONTRIBUTING.md)。
+
+## 说明
+
+### 数据库
+
+当前仅有一个「数据库」:Cloudflare Analytics Engine 数据集,通过 HTTP 与 Cloudflare API 通信。
+
+目前没有本地「测试」数据库。因此在本地开发时:
+
+- 写入不会生效(不会记录任何请求)
+- 读取来自生产环境的 Analytics Engine 数据集(本地开发看到的是生产数据)
+
+### 采样
+
+Cloudflare Analytics Engine 使用采样以在高流量下控制 ingestion/查询成本(与多数分析工具类似,可参考 [Google Analytics 关于采样](https://support.google.com/analytics/answer/2637192?hl=en#zippy=%2Cin-this-article))。更多关于 [CF AE 采样的说明](https://developers.cloudflare.com/analytics/analytics-engine/sampling/)可在此查看。
diff --git a/package.json b/package.json
index 7e6d9969..5bbbc0c9 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,8 @@
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
- "lint": "turbo run lint"
+ "lint": "turbo run lint",
+ "deploy": "pnpm --filter @counterscale/server run deploy"
},
"devDependencies": {
"eslint-config-prettier": "^9.1.0",
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 78084f91..98767287 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -35,7 +35,6 @@
"cli-highlight": "^2.1.11",
"figlet": "^1.7.0",
"inquirer": "^9.2.12",
- "wrangler": "^4.23.0",
"yargs": "^17.7.2",
"zx": "^8.8.1"
},
@@ -50,7 +49,8 @@
"rollup-plugin-node-externals": "^8.0.0",
"typescript": "^5.3.3",
"vite": "^7.2.2",
- "vitest": "^4.0.8"
+ "vitest": "^4.0.8",
+ "wrangler": "^4.23.0"
},
"overrides": {
"minimatch": "5.1.2",
diff --git a/packages/server/package.json b/packages/server/package.json
index f790946d..6a7c0049 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -21,8 +21,8 @@
"scripts": {
"dev": "react-router dev",
"build": "react-router build",
- "preview": "wrangler dev --var VERSION:`git rev-parse HEAD`",
- "deploy": "wrangler deploy --var VERSION:`git rev-parse HEAD`",
+ "preview": "wrangler dev --config wrangler.json --var VERSION:`git rev-parse HEAD`",
+ "deploy": "pnpm run build && wrangler deploy --config wrangler.json --var VERSION:`git rev-parse HEAD`",
"lint": "eslint .",
"test": "TZ=EST vitest run",
"test-ci": "TZ=EST vitest run --coverage",
diff --git a/packages/server/wrangler.toml b/packages/server/wrangler.toml
new file mode 100644
index 00000000..33398509
--- /dev/null
+++ b/packages/server/wrangler.toml
@@ -0,0 +1,21 @@
+# Wrangler 默认读取此文件;与 wrangler.json 保持一致
+main = "./workers/app.ts"
+name = "counterscale"
+compatibility_flags = ["nodejs_compat_v2"]
+compatibility_date = "2024-12-13"
+
+[assets]
+binding = "ASSETS"
+directory = "./build/client"
+
+[[analytics_engine_datasets]]
+binding = "WEB_COUNTER_AE"
+dataset = "metricsDataset"
+
+[[r2_buckets]]
+bucket_name = "counterscale-daily-rollups"
+preview_bucket_name = "counterscale-daily-rollups-dev"
+binding = "DAILY_ROLLUPS"
+
+[triggers]
+crons = ["0 2 * * *"]
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ea8c6dec..975312c2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -25,7 +25,7 @@ importers:
version: 3.4.2
turbo:
specifier: latest
- version: 2.6.1
+ version: 2.8.1
packages/cli:
dependencies:
@@ -53,9 +53,6 @@ importers:
inquirer:
specifier: ^9.2.12
version: 9.3.7
- wrangler:
- specifier: ^4.23.0
- version: 4.23.0(@cloudflare/workers-types@4.20250803.0)
yargs:
specifier: ^17.7.2
version: 17.7.2
@@ -96,6 +93,9 @@ importers:
vitest:
specifier: ^4.0.8
version: 4.0.8(@types/node@22.13.1)(jiti@2.5.1)(jsdom@23.2.0)(tsx@4.20.6)(yaml@2.7.0)
+ wrangler:
+ specifier: ^4.23.0
+ version: 4.23.0(@cloudflare/workers-types@4.20250803.0)
packages/eslint-config:
dependencies:
@@ -4290,38 +4290,38 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
- turbo-darwin-64@2.6.1:
- resolution: {integrity: sha512-Dm0HwhyZF4J0uLqkhUyCVJvKM9Rw7M03v3J9A7drHDQW0qAbIGBrUijQ8g4Q9Cciw/BXRRd8Uzkc3oue+qn+ZQ==}
+ turbo-darwin-64@2.8.1:
+ resolution: {integrity: sha512-FQ6Uqxty/H1Nvn1dpBe8KUlMRclTuiyNSc1PCeDL/ad7M9ykpWutB51YpMpf9ibTA32M6wLdIRf+D96W6hDAtQ==}
cpu: [x64]
os: [darwin]
- turbo-darwin-arm64@2.6.1:
- resolution: {integrity: sha512-U0PIPTPyxdLsrC3jN7jaJUwgzX5sVUBsKLO7+6AL+OASaa1NbT1pPdiZoTkblBAALLP76FM0LlnsVQOnmjYhyw==}
+ turbo-darwin-arm64@2.8.1:
+ resolution: {integrity: sha512-4bCcEpGP2/aSXmeN2gl5SuAmS1q5ykjubnFvSoXjQoCKtDOV+vc4CTl/DduZzUUutCVUWXjl8OyfIQ+DGCaV4A==}
cpu: [arm64]
os: [darwin]
- turbo-linux-64@2.6.1:
- resolution: {integrity: sha512-eM1uLWgzv89bxlK29qwQEr9xYWBhmO/EGiH22UGfq+uXr+QW1OvNKKMogSN65Ry8lElMH4LZh0aX2DEc7eC0Mw==}
+ turbo-linux-64@2.8.1:
+ resolution: {integrity: sha512-m99JRlWlEgXPR7mkThAbKh6jbTmWSOXM/c6rt8yd4Uxh0+wjq7+DYcQbead6aoOqmCP9akswZ8EXIv1ogKBblg==}
cpu: [x64]
os: [linux]
- turbo-linux-arm64@2.6.1:
- resolution: {integrity: sha512-MFFh7AxAQAycXKuZDrbeutfWM5Ep0CEZ9u7zs4Hn2FvOViTCzIfEhmuJou3/a5+q5VX1zTxQrKGy+4Lf5cdpsA==}
+ turbo-linux-arm64@2.8.1:
+ resolution: {integrity: sha512-AsPlza3AsavJdl2o7FE67qyv0aLfmT1XwFQGzvwpoAO6Bj7S4a03tpUchZKNuGjNAkKVProQRFnB7PgUAScFXA==}
cpu: [arm64]
os: [linux]
- turbo-windows-64@2.6.1:
- resolution: {integrity: sha512-buq7/VAN7KOjMYi4tSZT5m+jpqyhbRU2EUTTvp6V0Ii8dAkY2tAAjQN1q5q2ByflYWKecbQNTqxmVploE0LVwQ==}
+ turbo-windows-64@2.8.1:
+ resolution: {integrity: sha512-GdqNO6bYShRsr79B+2G/2ssjLEp9uBTvLBJSWRtRCiac/SEmv8T6RYv9hu+h5oGbFALtnKNp6BQBw78RJURsPw==}
cpu: [x64]
os: [win32]
- turbo-windows-arm64@2.6.1:
- resolution: {integrity: sha512-7w+AD5vJp3R+FB0YOj1YJcNcOOvBior7bcHTodqp90S3x3bLgpr7tE6xOea1e8JkP7GK6ciKVUpQvV7psiwU5Q==}
+ turbo-windows-arm64@2.8.1:
+ resolution: {integrity: sha512-n40E6IpkzrShRo3yMdRpgnn1/sAbGC6tZXwyNu8fe9RsufeD7KBiaoRSvw8xLyqV3pd2yoTL2rdCXq24MnTCWA==}
cpu: [arm64]
os: [win32]
- turbo@2.6.1:
- resolution: {integrity: sha512-qBwXXuDT3rA53kbNafGbT5r++BrhRgx3sAo0cHoDAeG9g1ItTmUMgltz3Hy7Hazy1ODqNpR+C7QwqL6DYB52yA==}
+ turbo@2.8.1:
+ resolution: {integrity: sha512-pbSMlRflA0RAuk/0jnAt8pzOYh1+sKaT8nVtcs75OFGVWD0evleQRmKtHJJV42QOhaC3Hx9mUUSOom/irasbjA==}
hasBin: true
type-check@0.4.0:
@@ -4586,6 +4586,7 @@ packages:
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
@@ -8848,32 +8849,32 @@ snapshots:
fsevents: 2.3.3
optional: true
- turbo-darwin-64@2.6.1:
+ turbo-darwin-64@2.8.1:
optional: true
- turbo-darwin-arm64@2.6.1:
+ turbo-darwin-arm64@2.8.1:
optional: true
- turbo-linux-64@2.6.1:
+ turbo-linux-64@2.8.1:
optional: true
- turbo-linux-arm64@2.6.1:
+ turbo-linux-arm64@2.8.1:
optional: true
- turbo-windows-64@2.6.1:
+ turbo-windows-64@2.8.1:
optional: true
- turbo-windows-arm64@2.6.1:
+ turbo-windows-arm64@2.8.1:
optional: true
- turbo@2.6.1:
+ turbo@2.8.1:
optionalDependencies:
- turbo-darwin-64: 2.6.1
- turbo-darwin-arm64: 2.6.1
- turbo-linux-64: 2.6.1
- turbo-linux-arm64: 2.6.1
- turbo-windows-64: 2.6.1
- turbo-windows-arm64: 2.6.1
+ turbo-darwin-64: 2.8.1
+ turbo-darwin-arm64: 2.8.1
+ turbo-linux-64: 2.8.1
+ turbo-linux-arm64: 2.8.1
+ turbo-windows-64: 2.8.1
+ turbo-windows-arm64: 2.8.1
type-check@0.4.0:
dependencies: