pug-site-core 3.0.28 → 3.0.30
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/lib/dbConnection.js +179 -0
- package/lib/devServer.js +39 -1
- package/package.json +2 -2
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据库连接辅助文件
|
|
3
|
+
* 用于在本地 dev 模式下通过 Cloudflare API 连接远程 D1 数据库
|
|
4
|
+
*
|
|
5
|
+
* 配置方式:
|
|
6
|
+
* 1. 通过环境变量配置(推荐)
|
|
7
|
+
* - D1_ACCOUNT_ID 或 CF_ACCOUNT_ID: Cloudflare 账户 ID
|
|
8
|
+
* - D1_DATABASE_ID: D1 数据库 ID
|
|
9
|
+
* - CLOUDFLARE_API_TOKEN 或 CF_API_TOKEN: Cloudflare API Token
|
|
10
|
+
*
|
|
11
|
+
* 2. 通过 SITE_CONFIGS_JSON 配置(在项目的 config.js 中)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Cloudflare D1 API 基础 URL
|
|
15
|
+
const CLOUDFLARE_API_BASE = 'https://api.cloudflare.com/client/v4';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 创建 D1 远程连接
|
|
19
|
+
* @param {string} accountId - Cloudflare 账户 ID
|
|
20
|
+
* @param {string} databaseId - D1 数据库 ID
|
|
21
|
+
* @param {string} apiToken - Cloudflare API Token
|
|
22
|
+
*/
|
|
23
|
+
async function createD1RemoteConnection(accountId, databaseId, apiToken) {
|
|
24
|
+
if (!accountId || !databaseId || !apiToken) {
|
|
25
|
+
throw new Error('缺少必要的 D1 连接配置:accountId、databaseId 和 apiToken');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(`正在连接到 Cloudflare D1 数据库: ${databaseId}`);
|
|
29
|
+
|
|
30
|
+
// 创建 D1 远程适配器
|
|
31
|
+
return createD1RemoteAdapter(accountId, databaseId, apiToken);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 创建 D1 远程适配器
|
|
36
|
+
* 通过 Cloudflare REST API 调用 D1 数据库
|
|
37
|
+
*/
|
|
38
|
+
function createD1RemoteAdapter(accountId, databaseId, apiToken) {
|
|
39
|
+
const apiUrl = `${CLOUDFLARE_API_BASE}/accounts/${accountId}/d1/database/${databaseId}/query`;
|
|
40
|
+
|
|
41
|
+
// 执行查询的通用函数
|
|
42
|
+
const executeQuery = async (sql, params = []) => {
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(apiUrl, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: {
|
|
47
|
+
'Authorization': `Bearer ${apiToken}`,
|
|
48
|
+
'Content-Type': 'application/json',
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
sql: sql,
|
|
52
|
+
params: params.length > 0 ? params : undefined,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const errorText = await response.text();
|
|
58
|
+
throw new Error(`D1 API 错误 (${response.status}): ${errorText}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const data = await response.json();
|
|
62
|
+
|
|
63
|
+
if (!data.success) {
|
|
64
|
+
throw new Error(`D1 查询失败: ${JSON.stringify(data.errors || data)}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return data;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('D1 查询错误:', error);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
prepare: (sql) => {
|
|
76
|
+
// 创建一个可以链式调用的对象
|
|
77
|
+
// 支持两种调用方式:
|
|
78
|
+
// 1. prepare(sql).bind(...args).all() / first() / run()
|
|
79
|
+
// 2. prepare(sql).all() / first() / run() (当没有参数时)
|
|
80
|
+
const prepared = {
|
|
81
|
+
bind: (...args) => {
|
|
82
|
+
return {
|
|
83
|
+
all: async () => {
|
|
84
|
+
const data = await executeQuery(sql, args);
|
|
85
|
+
const results = data.result && data.result[0] ? data.result[0].results : [];
|
|
86
|
+
return { results };
|
|
87
|
+
},
|
|
88
|
+
first: async () => {
|
|
89
|
+
const data = await executeQuery(sql, args);
|
|
90
|
+
const results = data.result && data.result[0] ? data.result[0].results : [];
|
|
91
|
+
return results.length > 0 ? results[0] : null;
|
|
92
|
+
},
|
|
93
|
+
run: async () => {
|
|
94
|
+
const data = await executeQuery(sql, args);
|
|
95
|
+
const meta = data.result && data.result[0] ? data.result[0].meta : {};
|
|
96
|
+
return { success: true, meta };
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
// 支持直接调用 all()、first()、run()(当没有参数时)
|
|
101
|
+
all: async () => {
|
|
102
|
+
const data = await executeQuery(sql, []);
|
|
103
|
+
const results = data.result && data.result[0] ? data.result[0].results : [];
|
|
104
|
+
return { results };
|
|
105
|
+
},
|
|
106
|
+
first: async () => {
|
|
107
|
+
const data = await executeQuery(sql, []);
|
|
108
|
+
const results = data.result && data.result[0] ? data.result[0].results : [];
|
|
109
|
+
return results.length > 0 ? results[0] : null;
|
|
110
|
+
},
|
|
111
|
+
run: async () => {
|
|
112
|
+
const data = await executeQuery(sql, []);
|
|
113
|
+
const meta = data.result && data.result[0] ? data.result[0].meta : {};
|
|
114
|
+
return { success: true, meta };
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return prepared;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 从配置中获取数据库连接信息
|
|
125
|
+
* @param {string} configPath - 项目 config.js 的路径
|
|
126
|
+
*/
|
|
127
|
+
async function getD1Config(configPath) {
|
|
128
|
+
// 优先从环境变量读取
|
|
129
|
+
const accountId = process.env.D1_ACCOUNT_ID || process.env.CF_ACCOUNT_ID;
|
|
130
|
+
const databaseId = process.env.D1_DATABASE_ID;
|
|
131
|
+
const apiToken = process.env.CLOUDFLARE_API_TOKEN || process.env.CF_API_TOKEN;
|
|
132
|
+
|
|
133
|
+
if (accountId && databaseId && apiToken) {
|
|
134
|
+
console.log('使用环境变量配置 D1 数据库连接');
|
|
135
|
+
return { accountId, databaseId, apiToken };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 如果环境变量未配置,尝试从项目的 SITE_CONFIGS_JSON 读取
|
|
139
|
+
try {
|
|
140
|
+
const { config, SITE_CONFIGS_JSON } = await import(configPath);
|
|
141
|
+
const siteName = config.siteConfig?.siteName || 'default';
|
|
142
|
+
|
|
143
|
+
console.log(`尝试从 SITE_CONFIGS_JSON 读取配置,站点名称: ${siteName}`);
|
|
144
|
+
|
|
145
|
+
// 检查是否有 SITE_CONFIGS_JSON 配置
|
|
146
|
+
if (SITE_CONFIGS_JSON && SITE_CONFIGS_JSON[siteName]) {
|
|
147
|
+
const siteConfig = SITE_CONFIGS_JSON[siteName];
|
|
148
|
+
console.log(`找到 ${siteName} 的配置`);
|
|
149
|
+
return {
|
|
150
|
+
accountId: siteConfig.cf_account_id,
|
|
151
|
+
databaseId: siteConfig.d1_database_id,
|
|
152
|
+
apiToken: siteConfig.cf_api_token,
|
|
153
|
+
};
|
|
154
|
+
} else {
|
|
155
|
+
console.warn(`SITE_CONFIGS_JSON 中未找到 ${siteName} 的配置`);
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// 忽略配置读取错误
|
|
159
|
+
console.warn('读取 SITE_CONFIGS_JSON 配置失败:', error.message);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 创建数据库连接
|
|
167
|
+
* @param {string} configPath - 项目 config.js 的路径
|
|
168
|
+
*/
|
|
169
|
+
export async function getDatabaseConnection(configPath) {
|
|
170
|
+
const config = await getD1Config(configPath);
|
|
171
|
+
|
|
172
|
+
if (!config) {
|
|
173
|
+
throw new Error('未配置 D1 数据库连接信息。请设置环境变量或配置 SITE_CONFIGS_JSON');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log(`使用账户 ID: ${config.accountId}, 数据库 ID: ${config.databaseId}`);
|
|
177
|
+
|
|
178
|
+
return await createD1RemoteConnection(config.accountId, config.databaseId, config.apiToken);
|
|
179
|
+
}
|
package/lib/devServer.js
CHANGED
|
@@ -19,6 +19,7 @@ import { Worker } from "worker_threads";
|
|
|
19
19
|
import { paths } from "./paths.js";
|
|
20
20
|
import { injectDebugScript } from "./debug/pugDebug.js";
|
|
21
21
|
import { setupDebugRoutes } from "./debug/debugRouter.js";
|
|
22
|
+
import { getDatabaseConnection } from "./dbConnection.js";
|
|
22
23
|
|
|
23
24
|
const { config } = await import(paths.config);
|
|
24
25
|
const pagsTemplatePath = config.devServer.isDebug ? paths.template.debugPages : paths.template.pages;
|
|
@@ -27,6 +28,32 @@ const port = await getIdleProt(config.devServer.port);
|
|
|
27
28
|
process.env._port = port;
|
|
28
29
|
process.env._localIp = localIp;
|
|
29
30
|
|
|
31
|
+
// 初始化数据库连接(如果配置了)- 在模块加载时初始化一次
|
|
32
|
+
let dbConnection = null;
|
|
33
|
+
if (config.devServer?.db) {
|
|
34
|
+
try {
|
|
35
|
+
if (typeof config.devServer.db === 'function') {
|
|
36
|
+
dbConnection = await config.devServer.db();
|
|
37
|
+
console.log('数据库连接已初始化(dev 模式)');
|
|
38
|
+
} else if (config.devServer.db === true) {
|
|
39
|
+
// 如果设置为 true,使用内置的数据库连接功能
|
|
40
|
+
try {
|
|
41
|
+
dbConnection = await getDatabaseConnection(paths.config);
|
|
42
|
+
console.log('✅ D1 数据库连接成功(使用内置连接)');
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('❌ 数据库连接失败:', error.message);
|
|
45
|
+
console.warn('将使用 null 作为数据库连接');
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
dbConnection = config.devServer.db;
|
|
49
|
+
console.log('使用已配置的数据库连接(dev 模式)');
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('初始化数据库连接失败:', error);
|
|
53
|
+
console.warn('将使用 null 作为数据库连接');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
30
57
|
function createServer() {
|
|
31
58
|
const app = express();
|
|
32
59
|
const server = http.createServer(app);
|
|
@@ -251,7 +278,18 @@ async function matchRouter(url, language, device) {
|
|
|
251
278
|
let data = await getJsonData(jsonPath);
|
|
252
279
|
return data;
|
|
253
280
|
};
|
|
254
|
-
|
|
281
|
+
|
|
282
|
+
// 创建 params 对象,包含 env 属性以模拟线上环境(Cloudflare Workers)
|
|
283
|
+
let params = {
|
|
284
|
+
url,
|
|
285
|
+
language,
|
|
286
|
+
device,
|
|
287
|
+
getR2Data,
|
|
288
|
+
env: {
|
|
289
|
+
db: dbConnection // 使用预初始化的数据库连接,模拟线上环境的 this.env.db
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
255
293
|
if(config.abtest && config.abtest.enabled && abtestRouter && abtestRouter[config.abtest.curVariant]){
|
|
256
294
|
router.unshift(...abtestRouter[config.abtest.curVariant]);
|
|
257
295
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pug-site-core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.30",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"ws": "^8.18.0"
|
|
53
53
|
},
|
|
54
54
|
"license": "ISC",
|
|
55
|
-
"description": "feat:
|
|
55
|
+
"description": "feat: 添加 dev 模式下的 D1 数据库连接支持",
|
|
56
56
|
"files": [
|
|
57
57
|
"lib/",
|
|
58
58
|
"index.js"
|