@42ailab/42plugin 0.1.17 → 0.1.18
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/package.json +1 -1
- package/src/config.ts +4 -3
- package/src/db.ts +138 -3
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// 数据目录(支持环境变量覆盖,用于测试)
|
|
9
9
|
const dataDir =
|
|
10
|
-
process.
|
|
10
|
+
process.env.PLUGIN_DATA_DIR ||
|
|
11
|
+
(process.platform === 'win32'
|
|
11
12
|
? path.join(process.env.APPDATA || os.homedir(), '42plugin')
|
|
12
|
-
: path.join(os.homedir(), '.42plugin');
|
|
13
|
+
: path.join(os.homedir(), '.42plugin'));
|
|
13
14
|
|
|
14
15
|
export const config = {
|
|
15
16
|
// 目录
|
package/src/db.ts
CHANGED
|
@@ -24,9 +24,53 @@ const PROGRESS_THRESHOLD = 1024 * 1024; // 1MB
|
|
|
24
24
|
|
|
25
25
|
let db: Database | null = null;
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* 检查目录权限,返回友好的错误信息
|
|
29
|
+
*/
|
|
30
|
+
async function checkDirectoryPermissions(dir: string): Promise<void> {
|
|
31
|
+
try {
|
|
32
|
+
// 检查目录是否存在
|
|
33
|
+
const stat = await fs.stat(dir);
|
|
34
|
+
|
|
35
|
+
// 检查是否可写
|
|
36
|
+
await fs.access(dir, fs.constants.W_OK);
|
|
37
|
+
|
|
38
|
+
// 检查所有者(仅在非 Windows 系统)
|
|
39
|
+
if (process.platform !== 'win32') {
|
|
40
|
+
const expectedUid = process.getuid?.();
|
|
41
|
+
if (expectedUid !== undefined && stat.uid !== expectedUid && stat.uid === 0) {
|
|
42
|
+
throw new Error('OWNED_BY_ROOT');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
47
|
+
// 目录不存在,稍后会创建
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if ((error as Error).message === 'OWNED_BY_ROOT' ||
|
|
52
|
+
(error as NodeJS.ErrnoException).code === 'EACCES') {
|
|
53
|
+
const homeDir = os.homedir();
|
|
54
|
+
throw new Error(
|
|
55
|
+
`数据目录权限错误: ${dir}\n\n` +
|
|
56
|
+
`这通常是因为之前使用了 sudo 运行 CLI 导致的。\n` +
|
|
57
|
+
`请执行以下命令修复权限:\n\n` +
|
|
58
|
+
` sudo chown -R $(whoami) ${path.join(homeDir, '.42plugin')}\n`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
27
66
|
async function getDb(): Promise<Database> {
|
|
28
67
|
if (!db) {
|
|
29
|
-
|
|
68
|
+
const dir = path.dirname(config.dbPath);
|
|
69
|
+
|
|
70
|
+
// 检查权限
|
|
71
|
+
await checkDirectoryPermissions(dir);
|
|
72
|
+
|
|
73
|
+
await fs.mkdir(dir, { recursive: true });
|
|
30
74
|
db = new Database(config.dbPath);
|
|
31
75
|
initSchema();
|
|
32
76
|
}
|
|
@@ -34,6 +78,9 @@ async function getDb(): Promise<Database> {
|
|
|
34
78
|
}
|
|
35
79
|
|
|
36
80
|
function initSchema(): void {
|
|
81
|
+
// 检查并执行数据库迁移
|
|
82
|
+
migrateIfNeeded();
|
|
83
|
+
|
|
37
84
|
db!.run(`
|
|
38
85
|
CREATE TABLE IF NOT EXISTS projects (
|
|
39
86
|
id TEXT PRIMARY KEY,
|
|
@@ -75,6 +122,82 @@ function initSchema(): void {
|
|
|
75
122
|
`);
|
|
76
123
|
}
|
|
77
124
|
|
|
125
|
+
/**
|
|
126
|
+
* 数据库迁移:处理旧版本 schema 升级
|
|
127
|
+
*/
|
|
128
|
+
function migrateIfNeeded(): void {
|
|
129
|
+
try {
|
|
130
|
+
// 检查 plugin_cache 表是否存在
|
|
131
|
+
const tableExists = db!.prepare(`
|
|
132
|
+
SELECT name FROM sqlite_master
|
|
133
|
+
WHERE type='table' AND name='plugin_cache'
|
|
134
|
+
`).get();
|
|
135
|
+
|
|
136
|
+
if (!tableExists) {
|
|
137
|
+
// 表不存在,无需迁移
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 获取表结构
|
|
142
|
+
const columns = db!.prepare('PRAGMA table_info(plugin_cache)').all() as { name: string }[];
|
|
143
|
+
const columnNames = columns.map(c => c.name);
|
|
144
|
+
|
|
145
|
+
// 检查是否是旧版本 schema(有 downloaded_at 但没有 cached_at)
|
|
146
|
+
const hasDownloadedAt = columnNames.includes('downloaded_at');
|
|
147
|
+
const hasCachedAt = columnNames.includes('cached_at');
|
|
148
|
+
|
|
149
|
+
if (hasDownloadedAt && !hasCachedAt) {
|
|
150
|
+
console.log('检测到旧版本数据库,正在迁移...');
|
|
151
|
+
|
|
152
|
+
// 开始事务
|
|
153
|
+
db!.run('BEGIN TRANSACTION');
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
// 1. 重命名旧表
|
|
157
|
+
db!.run('ALTER TABLE plugin_cache RENAME TO plugin_cache_old');
|
|
158
|
+
|
|
159
|
+
// 2. 创建新表
|
|
160
|
+
db!.run(`
|
|
161
|
+
CREATE TABLE plugin_cache (
|
|
162
|
+
full_name TEXT NOT NULL,
|
|
163
|
+
version TEXT NOT NULL,
|
|
164
|
+
type TEXT NOT NULL,
|
|
165
|
+
cache_path TEXT NOT NULL,
|
|
166
|
+
checksum TEXT NOT NULL,
|
|
167
|
+
size_bytes INTEGER NOT NULL,
|
|
168
|
+
cached_at TEXT NOT NULL,
|
|
169
|
+
PRIMARY KEY (full_name, version)
|
|
170
|
+
)
|
|
171
|
+
`);
|
|
172
|
+
|
|
173
|
+
// 3. 迁移数据(downloaded_at → cached_at)
|
|
174
|
+
db!.run(`
|
|
175
|
+
INSERT INTO plugin_cache (full_name, version, type, cache_path, checksum, size_bytes, cached_at)
|
|
176
|
+
SELECT full_name, version, type, cache_path, checksum, COALESCE(size_bytes, 0), downloaded_at
|
|
177
|
+
FROM plugin_cache_old
|
|
178
|
+
`);
|
|
179
|
+
|
|
180
|
+
// 4. 删除旧表
|
|
181
|
+
db!.run('DROP TABLE plugin_cache_old');
|
|
182
|
+
|
|
183
|
+
// 提交事务
|
|
184
|
+
db!.run('COMMIT');
|
|
185
|
+
|
|
186
|
+
console.log('数据库迁移完成');
|
|
187
|
+
} catch (error) {
|
|
188
|
+
// 回滚事务
|
|
189
|
+
db!.run('ROLLBACK');
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
// 迁移失败不应阻止程序运行,只记录警告
|
|
195
|
+
if (config.debug) {
|
|
196
|
+
console.warn('数据库迁移检查失败:', error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
78
201
|
// ============================================================================
|
|
79
202
|
// 项目管理
|
|
80
203
|
// ============================================================================
|
|
@@ -295,15 +418,27 @@ interface Secrets {
|
|
|
295
418
|
|
|
296
419
|
async function readSecrets(): Promise<Secrets> {
|
|
297
420
|
try {
|
|
421
|
+
// 先检查目录权限
|
|
422
|
+
await checkDirectoryPermissions(path.dirname(SECRETS_FILE));
|
|
423
|
+
|
|
298
424
|
const content = await fs.readFile(SECRETS_FILE, 'utf-8');
|
|
299
425
|
return JSON.parse(content);
|
|
300
|
-
} catch {
|
|
426
|
+
} catch (error) {
|
|
427
|
+
// 权限错误会直接抛出友好提示
|
|
428
|
+
if ((error as Error).message.includes('数据目录权限错误')) {
|
|
429
|
+
throw error;
|
|
430
|
+
}
|
|
301
431
|
return {};
|
|
302
432
|
}
|
|
303
433
|
}
|
|
304
434
|
|
|
305
435
|
async function writeSecrets(secrets: Secrets): Promise<void> {
|
|
306
|
-
|
|
436
|
+
const dir = path.dirname(SECRETS_FILE);
|
|
437
|
+
|
|
438
|
+
// 检查目录权限
|
|
439
|
+
await checkDirectoryPermissions(dir);
|
|
440
|
+
|
|
441
|
+
await fs.mkdir(dir, { recursive: true });
|
|
307
442
|
await fs.writeFile(SECRETS_FILE, JSON.stringify(secrets, null, 2), { mode: 0o600 });
|
|
308
443
|
}
|
|
309
444
|
|