openlearn-next 0.3.5 → 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.
Files changed (31) hide show
  1. package/cli-cleaner.mjs +131 -0
  2. package/cli.mjs +54 -12
  3. package/dist/assets/{BatchPickerModal-Dtoea1V3.js → BatchPickerModal-7SGYP69j.js} +1 -1
  4. package/dist/assets/{ClassesView-B-jRQxdS.js → ClassesView-DlykFThl.js} +1 -1
  5. package/dist/assets/{CloudDriveModal-BWTV8a8T.js → CloudDriveModal-Bu8HsM7n.js} +1 -1
  6. package/dist/assets/{CourseManagement-CCfPc_CY.js → CourseManagement-C780bMgl.js} +1 -1
  7. package/dist/assets/{CourseWizardModal-D4cBfjGs.js → CourseWizardModal-DFQqYI2e.js} +1 -1
  8. package/dist/assets/{Dashboard-gsxGW7q1.js → Dashboard-DZS7rPzE.js} +1 -1
  9. package/dist/assets/{ExportWeightModal-BbjHHoCT.js → ExportWeightModal-BmHMQzDA.js} +1 -1
  10. package/dist/assets/{HelpView--MQfJiFF.js → HelpView-DNBtzj9s.js} +1 -1
  11. package/dist/assets/{ImportLessonsModal-AAOtANpB.js → ImportLessonsModal-D98bqG93.js} +1 -1
  12. package/dist/assets/{InteractiveCoursewareViewer-Tes2QD6f.js → InteractiveCoursewareViewer-Bu5lBr1Z.js} +1 -1
  13. package/dist/assets/{InteractiveWhiteboard-B6ErhtNE.js → InteractiveWhiteboard-BYQ-nyrj.js} +1 -1
  14. package/dist/assets/{LazyCourseware-BsZu3p5U.js → LazyCourseware-DhgJCf1I.js} +1 -1
  15. package/dist/assets/{LazyWhiteboard-CKnCRtFY.js → LazyWhiteboard-C1sVfdFD.js} +1 -1
  16. package/dist/assets/{LessonEditorView-VzUJZUSD.js → LessonEditorView-HuuFFnNJ.js} +1 -1
  17. package/dist/assets/{LiveClassroomView-CTeUIrDr.js → LiveClassroomView-BRGIGTze.js} +1 -1
  18. package/dist/assets/{NotificationDetailModal-CSTLco-A.js → NotificationDetailModal-Bs5EVWer.js} +1 -1
  19. package/dist/assets/{PluginView-NuFAl1cx.js → PluginView-BQj-2SI8.js} +1 -1
  20. package/dist/assets/{QuizGeneratorModal-CfRWi15M.js → QuizGeneratorModal-BYaKf7LZ.js} +1 -1
  21. package/dist/assets/{StudentAssignmentView-DvWlg_x-.js → StudentAssignmentView-BhvuQKZs.js} +1 -1
  22. package/dist/assets/{StudentLessonView-BSlGMvNX.js → StudentLessonView-B8IfZguN.js} +1 -1
  23. package/dist/assets/{StudentPreviewModal-CspHoFJZ.js → StudentPreviewModal-CQUH7R89.js} +1 -1
  24. package/dist/assets/{StudentView-PgJn1UEY.js → StudentView-C5iJdzhc.js} +1 -1
  25. package/dist/assets/{SystemResourceLibraryModal-BIijjMl0.js → SystemResourceLibraryModal-DiUpZ3yP.js} +1 -1
  26. package/dist/assets/{TeacherView-BAx0ID4U.js → TeacherView-e-0NyEqE.js} +1 -1
  27. package/dist/assets/{index-afBTqYFz.js → index-CV2NRMHT.js} +3 -3
  28. package/dist/assets/{index-BAmfY-YR.js → index-DnBjcpig.js} +1 -1
  29. package/dist/index.html +1 -1
  30. package/dist/server.cjs +2 -2
  31. package/package.json +3 -2
@@ -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
- for (let i = 0; i < args.length; i++) {
15
- if (args[i] === '-p' || args[i] === '--port') {
16
- port = args[++i];
17
- } else if (args[i] === '-h' || args[i] === '--help') {
18
- console.log(`Usage: npx openlearn-next [options]
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> Port to listen on (default: 9000)
22
- -h, --help Show this help
36
+ -p, --port <port> 监听端口 (默认: 9000)
37
+ -v, --version 显示版本号
38
+ -h, --help 显示此帮助信息
39
+ --clean, --clean-cache 快捷清理缓存并退出
23
40
 
24
41
  Environment:
25
- OPENLEARN_DB_PATH SQLite database path (default: ~/openlearn-next/data.db)
26
- GEMINI_API_KEY Optional. Fallback AI key; AI features are configured via AI Providers in the admin dashboard
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
- process.exit(0);
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-afBTqYFz.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};
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};