openlearn-next 0.3.9 → 0.3.10
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 +9 -5
- package/cli-cleaner.mjs +72 -27
- package/cli-data.mjs +10 -0
- package/cli-doctor.mjs +471 -12
- package/cli.mjs +282 -159
- package/dist/assets/{BatchPickerModal-C7hweQi7.js → BatchPickerModal-BhIFzqMZ.js} +1 -1
- package/dist/assets/{ClassesView-f7h7q1me.js → ClassesView-BWzkFR-C.js} +1 -1
- package/dist/assets/{CloudDriveModal-y1cqaYjU.js → CloudDriveModal-Dy_toaqC.js} +1 -1
- package/dist/assets/{CourseManagement-Bwfdk7Hi.js → CourseManagement-DTIt-D1_.js} +1 -1
- package/dist/assets/{CourseWizardModal-DwKB919Y.js → CourseWizardModal-DEx5wbVt.js} +1 -1
- package/dist/assets/{Dashboard-7ElGQEot.js → Dashboard-DrD2ZUG2.js} +1 -1
- package/dist/assets/{ExportWeightModal-Dor_2k5X.js → ExportWeightModal-D3bGjA61.js} +1 -1
- package/dist/assets/{HelpView-Blq21AX9.js → HelpView-DOQ_FtPQ.js} +1 -1
- package/dist/assets/{ImportLessonsModal-BNb6POIc.js → ImportLessonsModal-R3T0dy5k.js} +1 -1
- package/dist/assets/{InteractiveCoursewareViewer-DZQNSStZ.js → InteractiveCoursewareViewer-CL1yz-wN.js} +1 -1
- package/dist/assets/{InteractiveWhiteboard-B-6-PHls.js → InteractiveWhiteboard-Cua0Ksff.js} +1 -1
- package/dist/assets/{LazyCourseware-YQTwc8Xc.js → LazyCourseware-CjwoXy7i.js} +1 -1
- package/dist/assets/{LazyWhiteboard-DNwlH8kJ.js → LazyWhiteboard-C1kc33rY.js} +1 -1
- package/dist/assets/{LessonEditorView-Biw7dyfp.js → LessonEditorView-K0YB64km.js} +1 -1
- package/dist/assets/{LiveClassroomView-D2ZyINSZ.js → LiveClassroomView-D4IQGl2d.js} +1 -1
- package/dist/assets/{NotificationDetailModal-TJSWdZgO.js → NotificationDetailModal-C7n-UNti.js} +1 -1
- package/dist/assets/{PluginView-B2m35ykl.js → PluginView-4aUL9Ma3.js} +4 -4
- package/dist/assets/{QuizGeneratorModal-XNW646aZ.js → QuizGeneratorModal-Cwv80A2_.js} +1 -1
- package/dist/assets/{StudentAssignmentView-C7qnbBVV.js → StudentAssignmentView-DTFVSpw7.js} +1 -1
- package/dist/assets/{StudentLessonView-DItUU7Zd.js → StudentLessonView-CETCftTC.js} +1 -1
- package/dist/assets/{StudentPreviewModal-CjxvwHUc.js → StudentPreviewModal-CIQx5-dG.js} +1 -1
- package/dist/assets/{StudentView-Bg6tAP5x.js → StudentView-DtWzM17Q.js} +1 -1
- package/dist/assets/{SystemResourceLibraryModal-Cf2XJ6sf.js → SystemResourceLibraryModal-Bv3wwXDH.js} +1 -1
- package/dist/assets/{TeacherView-Baq66mut.js → TeacherView-BEXx-Vwl.js} +1 -1
- package/dist/assets/{index-KP_ep5bf.js → index-PM-cSFwO.js} +1 -1
- package/dist/assets/{index-B7B4Vq2l.js → index-kqgYec9C.js} +3 -3
- package/dist/index.html +1 -1
- package/dist/server.cjs +41 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,12 +11,16 @@
|
|
|
11
11
|
### 一键运行(无需 clone 项目)
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
#
|
|
15
|
-
npx openlearn-next
|
|
14
|
+
# 推荐使用 @latest 强制校验最新发布版本,规避本地历史缓存版本漂移:
|
|
15
|
+
npx openlearn-next@latest
|
|
16
16
|
|
|
17
|
-
# 自定义端口 /
|
|
18
|
-
npx openlearn-next -p 3000
|
|
19
|
-
|
|
17
|
+
# 自定义端口 / 启动后自动唤起浏览器 (-o) / 临时沙盒演示模式 (--demo)
|
|
18
|
+
npx openlearn-next@latest -p 3000 -o
|
|
19
|
+
npx openlearn-next@latest --demo -o
|
|
20
|
+
|
|
21
|
+
# 运维诊断与一键自愈 / 在线冷备
|
|
22
|
+
npx openlearn-next@latest doctor --fix
|
|
23
|
+
npx openlearn-next@latest backup
|
|
20
24
|
```
|
|
21
25
|
|
|
22
26
|
### 全局安装
|
package/cli-cleaner.mjs
CHANGED
|
@@ -1,7 +1,61 @@
|
|
|
1
1
|
import { existsSync, readdirSync, rmSync, unlinkSync, statSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join, resolve, dirname } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 获取本机所有潜在的 NPX 缓存根目录 (兼容 Linux, macOS 与 Windows)
|
|
10
|
+
* @param {string} [customDir]
|
|
11
|
+
* @returns {string[]}
|
|
12
|
+
*/
|
|
13
|
+
export function getNpxCacheDirs(customDir) {
|
|
14
|
+
if (customDir) return [resolve(customDir)];
|
|
15
|
+
const dirs = [join(os.homedir(), '.npm', '_npx')];
|
|
16
|
+
if (process.platform === 'win32' || process.env.LOCALAPPDATA) {
|
|
17
|
+
if (process.env.LOCALAPPDATA) {
|
|
18
|
+
dirs.push(join(process.env.LOCALAPPDATA, 'npm-cache', '_npx'));
|
|
19
|
+
}
|
|
20
|
+
dirs.push(join(os.homedir(), 'AppData', 'Local', 'npm-cache', '_npx'));
|
|
21
|
+
dirs.push(join(os.homedir(), 'AppData', 'Roaming', 'npm-cache', '_npx'));
|
|
22
|
+
}
|
|
23
|
+
return [...new Set(dirs)].filter((d) => existsSync(d));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 扫描当前系统中已缓存的 openlearn-next NPX 包实体(供治理与防版本漂移诊断使用)
|
|
28
|
+
* @param {Object} [options]
|
|
29
|
+
* @param {string} [options.customNpxDir]
|
|
30
|
+
* @returns {Array<{ dirPath: string, hash: string, version: string }>}
|
|
31
|
+
*/
|
|
32
|
+
export function scanNpxCacheEntries(options = {}) {
|
|
33
|
+
const dirs = getNpxCacheDirs(options.customNpxDir);
|
|
34
|
+
const found = [];
|
|
35
|
+
for (const npxDir of dirs) {
|
|
36
|
+
try {
|
|
37
|
+
const entries = readdirSync(npxDir);
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const entryPath = join(npxDir, entry);
|
|
40
|
+
try {
|
|
41
|
+
if (statSync(entryPath).isDirectory()) {
|
|
42
|
+
const targetPackage = join(entryPath, 'node_modules', 'openlearn-next');
|
|
43
|
+
if (existsSync(targetPackage)) {
|
|
44
|
+
let ver = 'unknown';
|
|
45
|
+
try {
|
|
46
|
+
const pkgData = JSON.parse(readFileSync(join(targetPackage, 'package.json'), 'utf-8'));
|
|
47
|
+
ver = pkgData.version || 'unknown';
|
|
48
|
+
} catch {}
|
|
49
|
+
found.push({ dirPath: entryPath, hash: entry, version: ver });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch {}
|
|
53
|
+
}
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
|
|
5
59
|
/**
|
|
6
60
|
* 安全清理 openlearn-next 在本地的各类运行与包缓存
|
|
7
61
|
*
|
|
@@ -29,34 +83,14 @@ export function runClean(options = {}) {
|
|
|
29
83
|
|
|
30
84
|
// ── 1. 清理 NPX 历史包缓存 ─────────────────────────────────────────────
|
|
31
85
|
if (isAll || isOnlyNpx || isDefault) {
|
|
32
|
-
const
|
|
33
|
-
|
|
86
|
+
const cachedEntries = scanNpxCacheEntries(options);
|
|
87
|
+
for (const item of cachedEntries) {
|
|
34
88
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
if (statSync(entryPath).isDirectory()) {
|
|
40
|
-
const targetPackage = join(entryPath, 'node_modules', 'openlearn-next');
|
|
41
|
-
if (existsSync(targetPackage)) {
|
|
42
|
-
let ver = 'unknown';
|
|
43
|
-
try {
|
|
44
|
-
const pkgData = JSON.parse(readFileSync(join(targetPackage, 'package.json'), 'utf-8'));
|
|
45
|
-
ver = pkgData.version || 'unknown';
|
|
46
|
-
} catch {
|
|
47
|
-
// ignore JSON parse error
|
|
48
|
-
}
|
|
49
|
-
rmSync(entryPath, { recursive: true, force: true });
|
|
50
|
-
results.npxCleaned++;
|
|
51
|
-
log(`✓ 已清理 NPX 旧版缓存: ~/.npm/_npx/${entry} (openlearn-next@${ver})`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} catch (err) {
|
|
55
|
-
// 单个目录清理失败不阻断整体流程
|
|
56
|
-
}
|
|
57
|
-
}
|
|
89
|
+
rmSync(item.dirPath, { recursive: true, force: true });
|
|
90
|
+
results.npxCleaned++;
|
|
91
|
+
log(`✓ 已清理 NPX 旧版缓存: ${item.dirPath} (openlearn-next@${item.version})`);
|
|
58
92
|
} catch (err) {
|
|
59
|
-
|
|
93
|
+
// 单个目录清理失败不阻断整体流程
|
|
60
94
|
}
|
|
61
95
|
}
|
|
62
96
|
if (results.npxCleaned === 0 && !options.silent) {
|
|
@@ -97,7 +131,18 @@ export function runClean(options = {}) {
|
|
|
97
131
|
}
|
|
98
132
|
log(`ℹ 数据库已重置,下次启动将全新自动初始化。`);
|
|
99
133
|
} else {
|
|
100
|
-
// (b)
|
|
134
|
+
// (b) 默认安全清理:先安全 Checkpoint 预写日志至主库,保护未落盘事务,再清理 WAL/SHM
|
|
135
|
+
if (existsSync(dbPath) && existsSync(walPath)) {
|
|
136
|
+
try {
|
|
137
|
+
const Database = require('better-sqlite3');
|
|
138
|
+
const db = new Database(dbPath);
|
|
139
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
140
|
+
db.close();
|
|
141
|
+
log(`✓ 已安全归档并截断 SQLite 预写日志 (WAL Checkpoint 事务落盘)`);
|
|
142
|
+
} catch {
|
|
143
|
+
// 若数据库为 mock 测试文件或已锁定,安全保留主库并降级清理
|
|
144
|
+
}
|
|
145
|
+
}
|
|
101
146
|
if (existsSync(walPath)) {
|
|
102
147
|
unlinkSync(walPath);
|
|
103
148
|
results.tempCleaned.push(walPath);
|
package/cli-data.mjs
CHANGED
|
@@ -36,6 +36,8 @@ export async function runBackup(outputFile, options = {}) {
|
|
|
36
36
|
? path.resolve(outputFile)
|
|
37
37
|
: path.resolve(process.cwd(), `openlearn_backup_${timestamp}.db`);
|
|
38
38
|
|
|
39
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
40
|
+
|
|
39
41
|
try {
|
|
40
42
|
const Database = (await import('better-sqlite3')).default;
|
|
41
43
|
const db = new Database(dbPath, { readonly: true });
|
|
@@ -156,6 +158,14 @@ export async function runResetAdmin(newPassword = 'admin', options = {}) {
|
|
|
156
158
|
const Database = (await import('better-sqlite3')).default;
|
|
157
159
|
const db = new Database(dbPath);
|
|
158
160
|
|
|
161
|
+
const tableCheck = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='users'").get();
|
|
162
|
+
if (!tableCheck) {
|
|
163
|
+
const msg = `数据库尚未初始化 users 数据表 (${dbPath})。请先启动一次平台初始化数据表。`;
|
|
164
|
+
log(`ℹ ${msg}`);
|
|
165
|
+
db.close();
|
|
166
|
+
return { ok: false, error: msg };
|
|
167
|
+
}
|
|
168
|
+
|
|
159
169
|
const updateRes = db.prepare('UPDATE users SET password_hash = ? WHERE username = ?').run(hash, 'admin');
|
|
160
170
|
|
|
161
171
|
if (updateRes.changes === 0) {
|