openlearn-next 0.3.6 → 0.3.8
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-data.mjs +252 -0
- package/cli-doctor.mjs +189 -0
- package/cli.mjs +186 -16
- package/dist/assets/{BatchPickerModal-C98sjlMF.js → BatchPickerModal-yr3TOeuu.js} +1 -1
- package/dist/assets/{ClassesView-BPR46qdP.js → ClassesView-CJBu14nu.js} +1 -1
- package/dist/assets/{CloudDriveModal-muU449iB.js → CloudDriveModal-BYPpuwx4.js} +1 -1
- package/dist/assets/{CourseManagement-BRucWTbo.js → CourseManagement-D7HitLiU.js} +1 -1
- package/dist/assets/{CourseWizardModal-DTSK7VGp.js → CourseWizardModal-CyMoK1Os.js} +1 -1
- package/dist/assets/{Dashboard-COtcZSPW.js → Dashboard-DjCWV3Qg.js} +1 -1
- package/dist/assets/{ExportWeightModal-Dd7qjZuJ.js → ExportWeightModal-B5Cj-pLX.js} +1 -1
- package/dist/assets/{HelpView-CCmQYk87.js → HelpView-CxTV4J6V.js} +1 -1
- package/dist/assets/{ImportLessonsModal-cNlJ5wwh.js → ImportLessonsModal-CtHd6jIE.js} +1 -1
- package/dist/assets/{InteractiveCoursewareViewer-Cok6OVNk.js → InteractiveCoursewareViewer-CiOJP-Y5.js} +1 -1
- package/dist/assets/{InteractiveWhiteboard-6y5vg8O_.js → InteractiveWhiteboard-DZy7dsGY.js} +1 -1
- package/dist/assets/{LazyCourseware-wkNa0dIq.js → LazyCourseware-BpWRWiC9.js} +1 -1
- package/dist/assets/{LazyWhiteboard-Bjh_fdbw.js → LazyWhiteboard-GuLyGVWI.js} +1 -1
- package/dist/assets/{LessonEditorView-B_b0F9Cw.js → LessonEditorView-BPtkbV8_.js} +1 -1
- package/dist/assets/{LiveClassroomView-vm0rs4p7.js → LiveClassroomView-Cx5jD5H8.js} +1 -1
- package/dist/assets/{NotificationDetailModal-B3_95dku.js → NotificationDetailModal-DTwOttoS.js} +1 -1
- package/dist/assets/{PluginView-CZhQ0Ciy.js → PluginView-BB7sIiJf.js} +1 -1
- package/dist/assets/{QuizGeneratorModal-__riUCfV.js → QuizGeneratorModal-YoT3qVTl.js} +1 -1
- package/dist/assets/{StudentAssignmentView-BAL4_PJC.js → StudentAssignmentView-QIQtCByD.js} +1 -1
- package/dist/assets/{StudentLessonView-fXKI0uN9.js → StudentLessonView-ChP3ZGTo.js} +1 -1
- package/dist/assets/{StudentPreviewModal-DHgqiEHm.js → StudentPreviewModal-BIfgr0MU.js} +1 -1
- package/dist/assets/{StudentView-e7zUBtTA.js → StudentView-Clqr7kxh.js} +1 -1
- package/dist/assets/{SystemResourceLibraryModal-0MIIi5fd.js → SystemResourceLibraryModal-Cy012GB8.js} +1 -1
- package/dist/assets/{TeacherView-HxGS1psV.js → TeacherView-D-7hKKGD.js} +1 -1
- package/dist/assets/{index-CWqYoxFP.js → index-BqJrKVHF.js} +1 -1
- package/dist/assets/{index-CCKb8B5h.js → index-yp4O4BPB.js} +3 -3
- package/dist/index.html +1 -1
- package/dist/server.cjs +53 -5
- package/package.json +4 -1
package/cli.mjs
CHANGED
|
@@ -2,36 +2,168 @@
|
|
|
2
2
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { dirname, join } from 'node:path';
|
|
6
|
-
import { mkdirSync } from 'node:fs';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { mkdirSync, readFileSync, existsSync, rmSync } from 'node:fs';
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
|
|
9
|
+
import { runClean } from './cli-cleaner.mjs';
|
|
10
|
+
import { runDoctor } from './cli-doctor.mjs';
|
|
11
|
+
import { runBackup, runRestore, runResetAdmin, runPluginsList } from './cli-data.mjs';
|
|
12
|
+
|
|
9
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
14
|
|
|
11
15
|
const args = process.argv.slice(2);
|
|
12
|
-
|
|
16
|
+
const firstArg = args[0] || '';
|
|
17
|
+
|
|
18
|
+
// ── Check Version ────────────────────────────────────────────────────────────
|
|
19
|
+
if (args.includes('-v') || args.includes('--version')) {
|
|
20
|
+
try {
|
|
21
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf-8'));
|
|
22
|
+
console.log(`openlearn-next v${pkg.version}`);
|
|
23
|
+
} catch {
|
|
24
|
+
console.log('openlearn-next (version unknown)');
|
|
25
|
+
}
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Help Documentation ───────────────────────────────────────────────────────
|
|
30
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
31
|
+
console.log(`Usage: npx openlearn-next [command] [options]
|
|
32
|
+
|
|
33
|
+
Commands:
|
|
34
|
+
doctor 运行环境自检与健康诊断 (Node.js/硬件内存/存储权限/端口/网络)
|
|
35
|
+
backup [file] 一键数据库快照在线备份 (默认输出至当前目录 openlearn_backup_*.db)
|
|
36
|
+
restore <file> 从备份快照文件安全还原数据 (自动生成现有数据库回滚副本)
|
|
37
|
+
reset-admin [options] 重置系统管理员 (admin) 密码 (默认重置为 admin)
|
|
38
|
+
选项: --password, -P <pwd>
|
|
39
|
+
plugins [list] 查看当前已安装的平台插件清单与加载状态
|
|
40
|
+
clean, clean-cache 清理 NPX 包缓存与本地临时运行数据
|
|
41
|
+
选项:
|
|
42
|
+
--npx 仅清理 ~/.npm/_npx 中的 openlearn-next 历史包缓存
|
|
43
|
+
--db 重置本地 SQLite 数据库 (下次启动全新自动初始化)
|
|
44
|
+
--all 清理全部(NPX 历史包 + 运行临时日志 + 重置数据库)
|
|
45
|
+
|
|
46
|
+
Server Options:
|
|
47
|
+
-p, --port <port> 服务监听端口 (默认: 9000 或 process.env.PORT)
|
|
48
|
+
-H, --host <host> 绑定网络接口 IP (默认: 0.0.0.0 局域网全网监听)
|
|
49
|
+
-o, --open 服务就绪后自动唤起系统默认浏览器访问
|
|
50
|
+
--demo, --temp 临时演示沙盒模式:在系统临时目录创建一次性数据库,退出即自动销毁
|
|
51
|
+
--cors <origins> 允许跨域访问来源白名单 (多个以逗号分隔,如: "http://example.com,*")
|
|
52
|
+
--db-path <path> 自定义 SQLite 数据库文件绝对路径
|
|
53
|
+
-v, --version 显示版本号
|
|
54
|
+
-h, --help 显示此帮助信息
|
|
55
|
+
|
|
56
|
+
Environment Variables:
|
|
57
|
+
PORT HTTP 服务端口 (默认: 9000)
|
|
58
|
+
HOST 监听主机地址 (默认: 0.0.0.0)
|
|
59
|
+
OPENLEARN_DB_PATH SQLite 数据库绝对路径 (默认: ~/openlearn-next/data.db)
|
|
60
|
+
ALLOWED_ORIGINS CORS 白名单来源 (默认放行同源与开发环境)
|
|
61
|
+
GEMINI_API_KEY AI 助手兜底密钥 (推荐在管理员后台界面配置)
|
|
62
|
+
`);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Extract Options Helper ──────────────────────────────────────────────────
|
|
67
|
+
let customPort = null;
|
|
68
|
+
let customHost = null;
|
|
69
|
+
let customDbPath = null;
|
|
70
|
+
let customCors = null;
|
|
71
|
+
const isDemo = args.includes('--demo') || args.includes('--temp');
|
|
72
|
+
const isOpen = args.includes('-o') || args.includes('--open');
|
|
13
73
|
|
|
14
74
|
for (let i = 0; i < args.length; i++) {
|
|
15
75
|
if (args[i] === '-p' || args[i] === '--port') {
|
|
16
|
-
|
|
17
|
-
} else if (args[i] === '-
|
|
18
|
-
|
|
76
|
+
customPort = args[++i];
|
|
77
|
+
} else if (args[i] === '-H' || args[i] === '--host') {
|
|
78
|
+
customHost = args[++i];
|
|
79
|
+
} else if (args[i] === '--db-path') {
|
|
80
|
+
customDbPath = args[++i];
|
|
81
|
+
} else if (args[i] === '--cors') {
|
|
82
|
+
customCors = args[++i];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
19
85
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
86
|
+
if (customPort) process.env.PORT = customPort;
|
|
87
|
+
if (customHost) process.env.HOST = customHost;
|
|
88
|
+
if (customCors) process.env.ALLOWED_ORIGINS = customCors;
|
|
89
|
+
if (customDbPath) process.env.OPENLEARN_DB_PATH = resolve(customDbPath);
|
|
90
|
+
if (isOpen) process.env.OPEN_BROWSER = 'true';
|
|
23
91
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
92
|
+
// ── Command: clean / clean-cache ─────────────────────────────────────────────
|
|
93
|
+
const isCleanCommand =
|
|
94
|
+
firstArg === 'clean' ||
|
|
95
|
+
firstArg === 'clean-cache' ||
|
|
96
|
+
args.includes('--clean') ||
|
|
97
|
+
args.includes('--clean-cache');
|
|
98
|
+
|
|
99
|
+
if (isCleanCommand) {
|
|
100
|
+
const cleanOptions = {
|
|
101
|
+
all: args.includes('--all'),
|
|
102
|
+
npx: args.includes('--npx'),
|
|
103
|
+
db: args.includes('--db'),
|
|
104
|
+
};
|
|
105
|
+
runClean(cleanOptions);
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Command: doctor ──────────────────────────────────────────────────────────
|
|
110
|
+
if (firstArg === 'doctor' || args.includes('--doctor')) {
|
|
111
|
+
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 9000;
|
|
112
|
+
const res = await runDoctor({ port, dbPath: process.env.OPENLEARN_DB_PATH });
|
|
113
|
+
process.exit(res.ok ? 0 : 1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Command: backup ──────────────────────────────────────────────────────────
|
|
117
|
+
if (firstArg === 'backup' || args.includes('--backup')) {
|
|
118
|
+
const backupIdx = args.findIndex((a) => a === 'backup' || a === '--backup');
|
|
119
|
+
let targetFile = undefined;
|
|
120
|
+
if (backupIdx !== -1 && args[backupIdx + 1] && !args[backupIdx + 1].startsWith('-')) {
|
|
121
|
+
targetFile = args[backupIdx + 1];
|
|
122
|
+
}
|
|
123
|
+
const res = await runBackup(targetFile, { dbPath: process.env.OPENLEARN_DB_PATH });
|
|
124
|
+
process.exit(res.ok ? 0 : 1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Command: restore ─────────────────────────────────────────────────────────
|
|
128
|
+
if (firstArg === 'restore' || args.includes('--restore')) {
|
|
129
|
+
const restoreIdx = args.findIndex((a) => a === 'restore' || a === '--restore');
|
|
130
|
+
let sourceFile = undefined;
|
|
131
|
+
if (restoreIdx !== -1 && args[restoreIdx + 1] && !args[restoreIdx + 1].startsWith('-')) {
|
|
132
|
+
sourceFile = args[restoreIdx + 1];
|
|
29
133
|
}
|
|
134
|
+
const res = await runRestore(sourceFile, { dbPath: process.env.OPENLEARN_DB_PATH });
|
|
135
|
+
process.exit(res.ok ? 0 : 1);
|
|
30
136
|
}
|
|
31
137
|
|
|
32
|
-
|
|
138
|
+
// ── Command: reset-admin ─────────────────────────────────────────────────────
|
|
139
|
+
if (firstArg === 'reset-admin' || args.includes('--reset-admin')) {
|
|
140
|
+
let newPassword = 'admin';
|
|
141
|
+
const pwdIdx = args.findIndex((a) => a === '--password' || a === '-P');
|
|
142
|
+
if (pwdIdx !== -1 && args[pwdIdx + 1] && !args[pwdIdx + 1].startsWith('-')) {
|
|
143
|
+
newPassword = args[pwdIdx + 1];
|
|
144
|
+
} else if (args[1] && !args[1].startsWith('-')) {
|
|
145
|
+
newPassword = args[1];
|
|
146
|
+
}
|
|
147
|
+
const res = await runResetAdmin(newPassword, { dbPath: process.env.OPENLEARN_DB_PATH });
|
|
148
|
+
process.exit(res.ok ? 0 : 1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Command: plugins / plugins list ──────────────────────────────────────────
|
|
152
|
+
if (firstArg === 'plugins' || args.includes('--plugins')) {
|
|
153
|
+
const res = await runPluginsList({ dbPath: process.env.OPENLEARN_DB_PATH });
|
|
154
|
+
process.exit(res.ok ? 0 : 1);
|
|
155
|
+
}
|
|
33
156
|
|
|
34
|
-
|
|
157
|
+
// ── Normal Server Startup ────────────────────────────────────────────────────
|
|
158
|
+
let demoDir = null;
|
|
159
|
+
|
|
160
|
+
if (isDemo) {
|
|
161
|
+
demoDir = join(os.tmpdir(), `openlearn-demo-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`);
|
|
162
|
+
mkdirSync(demoDir, { recursive: true });
|
|
163
|
+
process.env.OPENLEARN_DB_PATH = join(demoDir, 'data.db');
|
|
164
|
+
console.log(`[openlearn-next] 临时沙盒模式已激活: ${demoDir}`);
|
|
165
|
+
console.log(`[openlearn-next] 提示: 退出程序时将自动销毁沙盒数据,不保留任何历史记录。`);
|
|
166
|
+
} else if (!process.env.OPENLEARN_DB_PATH) {
|
|
35
167
|
const dataDir = join(os.homedir(), 'openlearn-next');
|
|
36
168
|
mkdirSync(dataDir, { recursive: true });
|
|
37
169
|
process.env.OPENLEARN_DB_PATH = join(dataDir, 'data.db');
|
|
@@ -40,7 +172,11 @@ if (!process.env.OPENLEARN_DB_PATH) {
|
|
|
40
172
|
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
|
|
41
173
|
|
|
42
174
|
console.log(`[openlearn-next] PORT=${process.env.PORT || '9000'}`);
|
|
175
|
+
console.log(`[openlearn-next] HOST=${process.env.HOST || '0.0.0.0'}`);
|
|
43
176
|
console.log(`[openlearn-next] DB=${process.env.OPENLEARN_DB_PATH}`);
|
|
177
|
+
if (process.env.ALLOWED_ORIGINS) {
|
|
178
|
+
console.log(`[openlearn-next] CORS_ALLOWED=${process.env.ALLOWED_ORIGINS}`);
|
|
179
|
+
}
|
|
44
180
|
|
|
45
181
|
const serverPath = join(__dirname, 'dist', 'server.cjs');
|
|
46
182
|
const child = spawn('node', [serverPath], {
|
|
@@ -48,6 +184,40 @@ const child = spawn('node', [serverPath], {
|
|
|
48
184
|
env: { ...process.env },
|
|
49
185
|
});
|
|
50
186
|
|
|
187
|
+
let cleanedUp = false;
|
|
188
|
+
const cleanup = () => {
|
|
189
|
+
if (cleanedUp) return;
|
|
190
|
+
cleanedUp = true;
|
|
191
|
+
if (isDemo && demoDir && existsSync(demoDir)) {
|
|
192
|
+
try {
|
|
193
|
+
rmSync(demoDir, { recursive: true, force: true });
|
|
194
|
+
console.log('\n[openlearn-next] 临时沙盒环境清理完毕。');
|
|
195
|
+
} catch {}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
let isShuttingDown = false;
|
|
200
|
+
const handleSignal = (sig) => {
|
|
201
|
+
if (isShuttingDown) return;
|
|
202
|
+
isShuttingDown = true;
|
|
203
|
+
if (child && !child.killed) {
|
|
204
|
+
child.kill(sig);
|
|
205
|
+
// 3秒后强制保底退出
|
|
206
|
+
setTimeout(() => {
|
|
207
|
+
cleanup();
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}, 3000).unref();
|
|
210
|
+
} else {
|
|
211
|
+
cleanup();
|
|
212
|
+
process.exit(0);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
process.on('SIGINT', () => handleSignal('SIGINT'));
|
|
217
|
+
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
218
|
+
process.on('exit', cleanup);
|
|
219
|
+
|
|
51
220
|
child.on('exit', (code) => {
|
|
221
|
+
cleanup();
|
|
52
222
|
process.exit(code || 0);
|
|
53
223
|
});
|
|
@@ -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-yp4O4BPB.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};
|