openlearn-next 0.3.6 → 0.3.7
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/cli-cleaner.mjs +131 -0
- package/cli.mjs +54 -12
- package/dist/assets/{BatchPickerModal-C98sjlMF.js → BatchPickerModal-7SGYP69j.js} +1 -1
- package/dist/assets/{ClassesView-BPR46qdP.js → ClassesView-DlykFThl.js} +1 -1
- package/dist/assets/{CloudDriveModal-muU449iB.js → CloudDriveModal-Bu8HsM7n.js} +1 -1
- package/dist/assets/{CourseManagement-BRucWTbo.js → CourseManagement-C780bMgl.js} +1 -1
- package/dist/assets/{CourseWizardModal-DTSK7VGp.js → CourseWizardModal-DFQqYI2e.js} +1 -1
- package/dist/assets/{Dashboard-COtcZSPW.js → Dashboard-DZS7rPzE.js} +1 -1
- package/dist/assets/{ExportWeightModal-Dd7qjZuJ.js → ExportWeightModal-BmHMQzDA.js} +1 -1
- package/dist/assets/{HelpView-CCmQYk87.js → HelpView-DNBtzj9s.js} +1 -1
- package/dist/assets/{ImportLessonsModal-cNlJ5wwh.js → ImportLessonsModal-D98bqG93.js} +1 -1
- package/dist/assets/{InteractiveCoursewareViewer-Cok6OVNk.js → InteractiveCoursewareViewer-Bu5lBr1Z.js} +1 -1
- package/dist/assets/{InteractiveWhiteboard-6y5vg8O_.js → InteractiveWhiteboard-BYQ-nyrj.js} +1 -1
- package/dist/assets/{LazyCourseware-wkNa0dIq.js → LazyCourseware-DhgJCf1I.js} +1 -1
- package/dist/assets/{LazyWhiteboard-Bjh_fdbw.js → LazyWhiteboard-C1sVfdFD.js} +1 -1
- package/dist/assets/{LessonEditorView-B_b0F9Cw.js → LessonEditorView-HuuFFnNJ.js} +1 -1
- package/dist/assets/{LiveClassroomView-vm0rs4p7.js → LiveClassroomView-BRGIGTze.js} +1 -1
- package/dist/assets/{NotificationDetailModal-B3_95dku.js → NotificationDetailModal-Bs5EVWer.js} +1 -1
- package/dist/assets/{PluginView-CZhQ0Ciy.js → PluginView-BQj-2SI8.js} +1 -1
- package/dist/assets/{QuizGeneratorModal-__riUCfV.js → QuizGeneratorModal-BYaKf7LZ.js} +1 -1
- package/dist/assets/{StudentAssignmentView-BAL4_PJC.js → StudentAssignmentView-BhvuQKZs.js} +1 -1
- package/dist/assets/{StudentLessonView-fXKI0uN9.js → StudentLessonView-B8IfZguN.js} +1 -1
- package/dist/assets/{StudentPreviewModal-DHgqiEHm.js → StudentPreviewModal-CQUH7R89.js} +1 -1
- package/dist/assets/{StudentView-e7zUBtTA.js → StudentView-C5iJdzhc.js} +1 -1
- package/dist/assets/{SystemResourceLibraryModal-0MIIi5fd.js → SystemResourceLibraryModal-DiUpZ3yP.js} +1 -1
- package/dist/assets/{TeacherView-HxGS1psV.js → TeacherView-e-0NyEqE.js} +1 -1
- package/dist/assets/{index-CCKb8B5h.js → index-CV2NRMHT.js} +3 -3
- package/dist/assets/{index-CWqYoxFP.js → index-DnBjcpig.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/server.cjs +1 -1
- package/package.json +2 -1
package/cli-cleaner.mjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { existsSync, readdirSync, rmSync, unlinkSync, statSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve, dirname } from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 安全清理 openlearn-next 在本地的各类运行与包缓存
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} options
|
|
9
|
+
* @param {boolean} [options.all] 是否全量清理(NPX 缓存 + 运行缓存 + 重置数据库)
|
|
10
|
+
* @param {boolean} [options.npx] 是否仅清理 NPX 缓存
|
|
11
|
+
* @param {boolean} [options.db] 是否仅清理/重置本地数据库
|
|
12
|
+
* @param {string} [options.customDataDir] 自定义数据目录(可选,主要供测试用)
|
|
13
|
+
* @param {string} [options.customNpxDir] 自定义 NPX 缓存目录(可选,主要供测试用)
|
|
14
|
+
* @param {boolean} [options.silent] 是否静默输出
|
|
15
|
+
* @returns {{ npxCleaned: number, dbReset: boolean, tempCleaned: string[] }}
|
|
16
|
+
*/
|
|
17
|
+
export function runClean(options = {}) {
|
|
18
|
+
const log = options.silent ? () => {} : (msg) => console.log(`[openlearn-next] ${msg}`);
|
|
19
|
+
const results = {
|
|
20
|
+
npxCleaned: 0,
|
|
21
|
+
dbReset: false,
|
|
22
|
+
tempCleaned: [],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const isAll = Boolean(options.all);
|
|
26
|
+
const isOnlyNpx = Boolean(options.npx) && !isAll;
|
|
27
|
+
const isOnlyDb = Boolean(options.db) && !isAll;
|
|
28
|
+
const isDefault = !isOnlyNpx && !isOnlyDb && !isAll;
|
|
29
|
+
|
|
30
|
+
// ── 1. 清理 NPX 历史包缓存 ─────────────────────────────────────────────
|
|
31
|
+
if (isAll || isOnlyNpx || isDefault) {
|
|
32
|
+
const npxDir = options.customNpxDir ? resolve(options.customNpxDir) : join(os.homedir(), '.npm', '_npx');
|
|
33
|
+
if (existsSync(npxDir)) {
|
|
34
|
+
try {
|
|
35
|
+
const entries = readdirSync(npxDir);
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const entryPath = join(npxDir, entry);
|
|
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
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
log(`⚠ 读取 NPX 缓存目录失败: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (results.npxCleaned === 0 && !options.silent) {
|
|
63
|
+
log(`ℹ 未发现残留的 openlearn-next NPX 包缓存。`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── 2. 清理本地运行与数据库缓存 ─────────────────────────────────────────
|
|
68
|
+
if (isAll || isOnlyDb || isDefault) {
|
|
69
|
+
const dataDir = options.customDataDir
|
|
70
|
+
? resolve(options.customDataDir)
|
|
71
|
+
: (process.env.OPENLEARN_DB_PATH
|
|
72
|
+
? dirname(resolve(process.env.OPENLEARN_DB_PATH))
|
|
73
|
+
: join(os.homedir(), 'openlearn-next'));
|
|
74
|
+
|
|
75
|
+
const dbPath = process.env.OPENLEARN_DB_PATH
|
|
76
|
+
? resolve(process.env.OPENLEARN_DB_PATH)
|
|
77
|
+
: join(dataDir, 'data.db');
|
|
78
|
+
|
|
79
|
+
const walPath = `${dbPath}-wal`;
|
|
80
|
+
const shmPath = `${dbPath}-shm`;
|
|
81
|
+
|
|
82
|
+
if (existsSync(dataDir)) {
|
|
83
|
+
// (a) 数据库重置模式
|
|
84
|
+
if (isAll || isOnlyDb) {
|
|
85
|
+
if (existsSync(dbPath)) {
|
|
86
|
+
unlinkSync(dbPath);
|
|
87
|
+
results.dbReset = true;
|
|
88
|
+
log(`✓ 已重置本地数据库: ${dbPath}`);
|
|
89
|
+
}
|
|
90
|
+
if (existsSync(walPath)) {
|
|
91
|
+
unlinkSync(walPath);
|
|
92
|
+
log(`✓ 已清理 SQLite 预写日志: ${walPath}`);
|
|
93
|
+
}
|
|
94
|
+
if (existsSync(shmPath)) {
|
|
95
|
+
unlinkSync(shmPath);
|
|
96
|
+
log(`✓ 已清理 SQLite 共享内存: ${shmPath}`);
|
|
97
|
+
}
|
|
98
|
+
log(`ℹ 数据库已重置,下次启动将全新自动初始化。`);
|
|
99
|
+
} else {
|
|
100
|
+
// (b) 默认安全清理:清理 WAL 与临时运行日志,保留主库数据
|
|
101
|
+
if (existsSync(walPath)) {
|
|
102
|
+
unlinkSync(walPath);
|
|
103
|
+
results.tempCleaned.push(walPath);
|
|
104
|
+
log(`✓ 已清理 SQLite 临时预写日志: ${walPath}`);
|
|
105
|
+
}
|
|
106
|
+
if (existsSync(shmPath)) {
|
|
107
|
+
unlinkSync(shmPath);
|
|
108
|
+
results.tempCleaned.push(shmPath);
|
|
109
|
+
log(`✓ 已清理 SQLite 临时共享内存: ${shmPath}`);
|
|
110
|
+
}
|
|
111
|
+
if (!options.silent) {
|
|
112
|
+
log(`ℹ 本地数据库核心数据已保留 (${dbPath})。如需完全重置数据库,请添加 --db 或 --all 参数。`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// (c) 清理临时子目录
|
|
117
|
+
const tempFolders = ['temp', 'scratch', 'cache'];
|
|
118
|
+
for (const folder of tempFolders) {
|
|
119
|
+
const folderPath = join(dataDir, folder);
|
|
120
|
+
if (existsSync(folderPath)) {
|
|
121
|
+
rmSync(folderPath, { recursive: true, force: true });
|
|
122
|
+
results.tempCleaned.push(folderPath);
|
|
123
|
+
log(`✓ 已清理临时数据目录: ${folderPath}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
log(`缓存清理完成。`);
|
|
130
|
+
return results;
|
|
131
|
+
}
|
package/cli.mjs
CHANGED
|
@@ -3,29 +3,71 @@
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
|
-
import { mkdirSync } from 'node:fs';
|
|
6
|
+
import { mkdirSync, readFileSync } from 'node:fs';
|
|
7
7
|
import os from 'node:os';
|
|
8
|
+
import { runClean } from './cli-cleaner.mjs';
|
|
8
9
|
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
|
|
11
12
|
const args = process.argv.slice(2);
|
|
12
|
-
let port = null;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
console.log(`
|
|
14
|
+
// ── Check Version & Help ───────────────────────────────────────────────────
|
|
15
|
+
if (args.includes('-v') || args.includes('--version')) {
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf-8'));
|
|
18
|
+
console.log(`openlearn-next v${pkg.version}`);
|
|
19
|
+
} catch {
|
|
20
|
+
console.log('openlearn-next (version unknown)');
|
|
21
|
+
}
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
26
|
+
console.log(`Usage: npx openlearn-next [command] [options]
|
|
27
|
+
|
|
28
|
+
Commands:
|
|
29
|
+
clean, clean-cache 清理 NPX 包缓存与本地临时运行数据
|
|
30
|
+
选项:
|
|
31
|
+
--npx 仅清理 ~/.npm/_npx 中的 openlearn-next 历史包缓存
|
|
32
|
+
--db 重置本地 SQLite 数据库 (下次启动全新自动初始化)
|
|
33
|
+
--all 清理全部(NPX 历史包 + 运行临时日志 + 重置数据库)
|
|
19
34
|
|
|
20
35
|
Options:
|
|
21
|
-
-p, --port <port>
|
|
22
|
-
-
|
|
36
|
+
-p, --port <port> 监听端口 (默认: 9000)
|
|
37
|
+
-v, --version 显示版本号
|
|
38
|
+
-h, --help 显示此帮助信息
|
|
39
|
+
--clean, --clean-cache 快捷清理缓存并退出
|
|
23
40
|
|
|
24
41
|
Environment:
|
|
25
|
-
OPENLEARN_DB_PATH
|
|
26
|
-
GEMINI_API_KEY
|
|
42
|
+
OPENLEARN_DB_PATH SQLite database path (默认: ~/openlearn-next/data.db)
|
|
43
|
+
GEMINI_API_KEY Optional fallback AI key; configured via admin dashboard
|
|
27
44
|
`);
|
|
28
|
-
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Handle Clean Command ───────────────────────────────────────────────────
|
|
49
|
+
const firstArg = args[0];
|
|
50
|
+
const isCleanCommand =
|
|
51
|
+
firstArg === 'clean' ||
|
|
52
|
+
firstArg === 'clean-cache' ||
|
|
53
|
+
args.includes('--clean') ||
|
|
54
|
+
args.includes('--clean-cache');
|
|
55
|
+
|
|
56
|
+
if (isCleanCommand) {
|
|
57
|
+
const cleanOptions = {
|
|
58
|
+
all: args.includes('--all'),
|
|
59
|
+
npx: args.includes('--npx'),
|
|
60
|
+
db: args.includes('--db'),
|
|
61
|
+
};
|
|
62
|
+
runClean(cleanOptions);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let port = null;
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < args.length; i++) {
|
|
69
|
+
if (args[i] === '-p' || args[i] === '--port') {
|
|
70
|
+
port = args[++i];
|
|
29
71
|
}
|
|
30
72
|
}
|
|
31
73
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e}from"./vendor-react-0Lg0vA-4.js";import{m as p}from"./index-
|
|
1
|
+
import{j as e}from"./vendor-react-0Lg0vA-4.js";import{m as p}from"./index-CV2NRMHT.js";import"./vendor-icons-Dq6MhgH9.js";import"./vendor-pdf-Cp-cYteE.js";import"./vendor-utils-BEhuP0Zq.js";import"./vendor-motion-DjY2czvL.js";import"./vendor-pptx-B_NheIKl.js";import"./vendor-charts-CRXa4AlU.js";function w({batchPicker:o,setBatchPicker:t,batchPickerLesson:l,setBatchPickerLesson:a,batchPickerDate:i,setBatchPickerDate:n,batchPickerTargetClass:d,setBatchPickerTargetClass:c,lessons:x,classes:u,expandedClassId:m,confirmBatchPicker:h,lang:r}){return o?e.jsx("div",{className:"fixed inset-0 bg-gray-900/40 backdrop-blur-sm flex items-center justify-center p-6 z-50",children:e.jsxs(p.div,{initial:{opacity:0,scale:.95},animate:{opacity:1,scale:1},className:"bg-white border text-gray-900 border-gray-200 rounded-2xl shadow-2xl w-full max-w-md flex flex-col overflow-hidden",children:[e.jsxs("div",{className:"p-4 border-b border-gray-100 flex items-center justify-between bg-gray-50/80",children:[e.jsx("h2",{className:"font-bold text-gray-800 text-base",children:o==="schedule"?r==="zh"?"批量排课":"Batch Schedule":o==="lockedLesson"?r==="zh"?"批量设置锁定课程":"Batch Lock Lesson":r==="zh"?"批量转班":"Batch Transfer"}),e.jsx("button",{onClick:()=>t(null),className:"text-gray-400 hover:text-gray-600 text-lg font-bold p-1 hover:bg-gray-200 rounded",children:"×"})]}),e.jsxs("div",{className:"p-5 space-y-4",children:[(o==="schedule"||o==="lockedLesson")&&e.jsxs("div",{className:"space-y-1.5",children:[e.jsx("label",{className:"text-xs font-semibold text-gray-600",children:r==="zh"?"选择课程":"Select Lesson"}),e.jsxs("select",{value:l,onChange:s=>a(s.target.value),className:"w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500",children:[e.jsx("option",{value:"",children:r==="zh"?"— 请选择 —":"— Select —"}),x.map(s=>e.jsx("option",{value:s.id,children:s.title},s.id))]})]}),o==="schedule"&&e.jsxs("div",{className:"space-y-1.5",children:[e.jsx("label",{className:"text-xs font-semibold text-gray-600",children:r==="zh"?"上课日期":"Schedule Date"}),e.jsx("input",{type:"date",value:i,onChange:s=>n(s.target.value),className:"w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"})]}),o==="transfer"&&e.jsxs("div",{className:"space-y-1.5",children:[e.jsx("label",{className:"text-xs font-semibold text-gray-600",children:r==="zh"?"选择目标班级":"Select Target Class"}),e.jsxs("select",{value:d,onChange:s=>c(s.target.value),className:"w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500",children:[e.jsx("option",{value:"",children:r==="zh"?"— 请选择 —":"— Select —"}),u.filter(s=>s.id!==m).map(s=>e.jsx("option",{value:s.id,children:s.name},s.id))]})]})]}),e.jsxs("div",{className:"p-4 border-t border-gray-100 flex justify-end gap-2",children:[e.jsx("button",{onClick:()=>t(null),className:"px-4 py-2 text-sm rounded-lg border border-gray-200 text-gray-600 hover:bg-gray-100 cursor-pointer",children:r==="zh"?"取消":"Cancel"}),e.jsx("button",{onClick:h,className:"px-4 py-2 text-sm rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 cursor-pointer",children:r==="zh"?"确认":"Confirm"})]})]})}):null}export{w as BatchPickerModal};
|