openlearn-next 0.3.8 → 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.
Files changed (34) hide show
  1. package/README.md +9 -5
  2. package/cli-cleaner.mjs +72 -27
  3. package/cli-data.mjs +10 -0
  4. package/cli-doctor.mjs +471 -12
  5. package/cli.mjs +282 -159
  6. package/dist/assets/{BatchPickerModal-yr3TOeuu.js → BatchPickerModal-BhIFzqMZ.js} +1 -1
  7. package/dist/assets/{ClassesView-CJBu14nu.js → ClassesView-BWzkFR-C.js} +1 -1
  8. package/dist/assets/{CloudDriveModal-BYPpuwx4.js → CloudDriveModal-Dy_toaqC.js} +1 -1
  9. package/dist/assets/{CourseManagement-D7HitLiU.js → CourseManagement-DTIt-D1_.js} +1 -1
  10. package/dist/assets/{CourseWizardModal-CyMoK1Os.js → CourseWizardModal-DEx5wbVt.js} +1 -1
  11. package/dist/assets/{Dashboard-DjCWV3Qg.js → Dashboard-DrD2ZUG2.js} +1 -1
  12. package/dist/assets/{ExportWeightModal-B5Cj-pLX.js → ExportWeightModal-D3bGjA61.js} +1 -1
  13. package/dist/assets/{HelpView-CxTV4J6V.js → HelpView-DOQ_FtPQ.js} +1 -1
  14. package/dist/assets/{ImportLessonsModal-CtHd6jIE.js → ImportLessonsModal-R3T0dy5k.js} +1 -1
  15. package/dist/assets/{InteractiveCoursewareViewer-CiOJP-Y5.js → InteractiveCoursewareViewer-CL1yz-wN.js} +1 -1
  16. package/dist/assets/{InteractiveWhiteboard-DZy7dsGY.js → InteractiveWhiteboard-Cua0Ksff.js} +1 -1
  17. package/dist/assets/{LazyCourseware-BpWRWiC9.js → LazyCourseware-CjwoXy7i.js} +1 -1
  18. package/dist/assets/{LazyWhiteboard-GuLyGVWI.js → LazyWhiteboard-C1kc33rY.js} +1 -1
  19. package/dist/assets/{LessonEditorView-BPtkbV8_.js → LessonEditorView-K0YB64km.js} +1 -1
  20. package/dist/assets/{LiveClassroomView-Cx5jD5H8.js → LiveClassroomView-D4IQGl2d.js} +1 -1
  21. package/dist/assets/{NotificationDetailModal-DTwOttoS.js → NotificationDetailModal-C7n-UNti.js} +1 -1
  22. package/dist/assets/{PluginView-BB7sIiJf.js → PluginView-4aUL9Ma3.js} +4 -4
  23. package/dist/assets/{QuizGeneratorModal-YoT3qVTl.js → QuizGeneratorModal-Cwv80A2_.js} +1 -1
  24. package/dist/assets/{StudentAssignmentView-QIQtCByD.js → StudentAssignmentView-DTFVSpw7.js} +1 -1
  25. package/dist/assets/{StudentLessonView-ChP3ZGTo.js → StudentLessonView-CETCftTC.js} +1 -1
  26. package/dist/assets/{StudentPreviewModal-BIfgr0MU.js → StudentPreviewModal-CIQx5-dG.js} +1 -1
  27. package/dist/assets/{StudentView-Clqr7kxh.js → StudentView-DtWzM17Q.js} +1 -1
  28. package/dist/assets/{SystemResourceLibraryModal-Cy012GB8.js → SystemResourceLibraryModal-Bv3wwXDH.js} +1 -1
  29. package/dist/assets/{TeacherView-D-7hKKGD.js → TeacherView-BEXx-Vwl.js} +1 -1
  30. package/dist/assets/{index-BqJrKVHF.js → index-PM-cSFwO.js} +1 -1
  31. package/dist/assets/{index-yp4O4BPB.js → index-kqgYec9C.js} +3 -3
  32. package/dist/index.html +1 -1
  33. package/dist/server.cjs +47 -7
  34. package/package.json +2 -2
package/cli.mjs CHANGED
@@ -12,26 +12,167 @@ import { runBackup, runRestore, runResetAdmin, runPluginsList } from './cli-data
12
12
 
13
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
14
 
15
- const args = process.argv.slice(2);
16
- const firstArg = args[0] || '';
15
+ /**
16
+ * 健壮的 CLI 参数解析器 (Zero External Dependencies)
17
+ * 支持 --flag=value 展开、任意位置放置子命令与选项、参数缺失校验
18
+ */
19
+ export function parseCliArgs(rawArgs) {
20
+ const normalized = [];
21
+ for (const arg of rawArgs) {
22
+ if (arg.startsWith('--') && arg.includes('=')) {
23
+ const eqIdx = arg.indexOf('=');
24
+ normalized.push(arg.slice(0, eqIdx), arg.slice(eqIdx + 1));
25
+ } else {
26
+ normalized.push(arg);
27
+ }
28
+ }
17
29
 
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)');
30
+ const KNOWN_COMMANDS = new Set([
31
+ 'doctor',
32
+ 'backup',
33
+ 'restore',
34
+ 'reset-admin',
35
+ 'plugins',
36
+ 'clean',
37
+ 'clean-cache',
38
+ ]);
39
+
40
+ const ALIAS_COMMAND_MAP = {
41
+ '--doctor': 'doctor',
42
+ '--backup': 'backup',
43
+ '--restore': 'restore',
44
+ '--reset-admin': 'reset-admin',
45
+ '--plugins': 'plugins',
46
+ '--clean': 'clean',
47
+ '--clean-cache': 'clean',
48
+ };
49
+
50
+ let command = null;
51
+ const positionalArgs = [];
52
+ const flags = {
53
+ version: false,
54
+ help: false,
55
+ open: false,
56
+ demo: false,
57
+ fix: false,
58
+ all: false,
59
+ npx: false,
60
+ db: false,
61
+ port: null,
62
+ host: null,
63
+ dbPath: null,
64
+ cors: null,
65
+ password: null,
66
+ };
67
+
68
+ for (let i = 0; i < normalized.length; i++) {
69
+ const token = normalized[i];
70
+
71
+ if (token === '-v' || token === '--version') {
72
+ flags.version = true;
73
+ } else if (token === '-h' || token === '--help') {
74
+ flags.help = true;
75
+ } else if (token === '-o' || token === '--open') {
76
+ flags.open = true;
77
+ } else if (token === '--demo' || token === '--temp') {
78
+ flags.demo = true;
79
+ } else if (token === '--fix') {
80
+ flags.fix = true;
81
+ } else if (token === '--all') {
82
+ flags.all = true;
83
+ } else if (token === '--npx') {
84
+ flags.npx = true;
85
+ } else if (token === '--db') {
86
+ flags.db = true;
87
+ } else if (token === '-p' || token === '--port') {
88
+ const val = normalized[++i];
89
+ if (!val || val.startsWith('-')) {
90
+ throw new Error('选项 -p/--port 必须指定有效的端口数值');
91
+ }
92
+ flags.port = val;
93
+ } else if (token === '-H' || token === '--host') {
94
+ const val = normalized[++i];
95
+ if (!val || val.startsWith('-')) {
96
+ throw new Error('选项 -H/--host 必须指定有效的主机地址');
97
+ }
98
+ flags.host = val;
99
+ } else if (token === '--db-path') {
100
+ const val = normalized[++i];
101
+ if (!val || val.startsWith('-')) {
102
+ throw new Error('选项 --db-path 必须指定数据库文件路径');
103
+ }
104
+ flags.dbPath = val;
105
+ } else if (token === '--cors') {
106
+ const val = normalized[++i];
107
+ if (!val || val.startsWith('-')) {
108
+ throw new Error('选项 --cors 必须指定跨域白名单');
109
+ }
110
+ flags.cors = val;
111
+ } else if (token === '-P' || token === '--password') {
112
+ const val = normalized[++i];
113
+ if (!val || val.startsWith('-')) {
114
+ throw new Error('选项 -P/--password 必须指定密码内容');
115
+ }
116
+ flags.password = val;
117
+ } else if (ALIAS_COMMAND_MAP[token]) {
118
+ if (!command) {
119
+ command = ALIAS_COMMAND_MAP[token];
120
+ }
121
+ } else if (KNOWN_COMMANDS.has(token)) {
122
+ if (!command) {
123
+ command = token === 'clean-cache' ? 'clean' : token;
124
+ }
125
+ } else if (token.startsWith('-')) {
126
+ throw new Error(`未知选项: "${token}"。请运行 npx openlearn-next --help 查看支持的选项。`);
127
+ } else {
128
+ positionalArgs.push(token);
129
+ }
130
+ }
131
+
132
+ // 校验未知位置参数(防手误误启服务)
133
+ if (!command && positionalArgs.length > 0) {
134
+ const potentialCmd = positionalArgs[0];
135
+ throw new Error(`未知命令: "${potentialCmd}"。请运行 npx openlearn-next --help 查看可用命令。`);
25
136
  }
26
- process.exit(0);
137
+
138
+ // 兼容 plugins list 子参数
139
+ const subcommandArgs = command === 'plugins' && positionalArgs[0] === 'list'
140
+ ? positionalArgs.slice(1)
141
+ : positionalArgs;
142
+
143
+ return { command, subcommandArgs, flags };
27
144
  }
28
145
 
29
- // ── Help Documentation ───────────────────────────────────────────────────────
30
- if (args.includes('-h') || args.includes('--help')) {
31
- console.log(`Usage: npx openlearn-next [command] [options]
146
+ // ── CLI Main Runner ─────────────────────────────────────────────────────────
147
+ export async function runCli(argv = process.argv.slice(2)) {
148
+ let parsed;
149
+ try {
150
+ parsed = parseCliArgs(argv);
151
+ } catch (err) {
152
+ console.error(`[openlearn-next] 错误: ${err.message}`);
153
+ process.exit(1);
154
+ }
155
+
156
+ const { command, subcommandArgs, flags } = parsed;
157
+
158
+ // ── Check Version ──────────────────────────────────────────────────────────
159
+ if (flags.version) {
160
+ try {
161
+ const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf-8'));
162
+ console.log(`openlearn-next v${pkg.version}`);
163
+ } catch {
164
+ console.log('openlearn-next (version unknown)');
165
+ }
166
+ process.exit(0);
167
+ }
168
+
169
+ // ── Help Documentation ─────────────────────────────────────────────────────
170
+ if (flags.help) {
171
+ console.log(`Usage: npx openlearn-next [command] [options]
32
172
 
33
173
  Commands:
34
- doctor 运行环境自检与健康诊断 (Node.js/硬件内存/存储权限/端口/网络)
174
+ doctor [options] 运行环境自检、防版本漂移体检与自动修复
175
+ 选项: --fix (一键自动修复版本漂移与目录权限)
35
176
  backup [file] 一键数据库快照在线备份 (默认输出至当前目录 openlearn_backup_*.db)
36
177
  restore <file> 从备份快照文件安全还原数据 (自动生成现有数据库回滚副本)
37
178
  reset-admin [options] 重置系统管理员 (admin) 密码 (默认重置为 admin)
@@ -44,11 +185,11 @@ Commands:
44
185
  --all 清理全部(NPX 历史包 + 运行临时日志 + 重置数据库)
45
186
 
46
187
  Server Options:
47
- -p, --port <port> 服务监听端口 (默认: 9000 或 process.env.PORT)
48
- -H, --host <host> 绑定网络接口 IP (默认: 0.0.0.0 局域网全网监听)
188
+ -p, --port <port> 服务监听端口 (支持 --port=9000, 默认: 9000 或 process.env.PORT)
189
+ -H, --host <host> 绑定网络接口 IP (支持 --host=0.0.0.0, 默认: 0.0.0.0)
49
190
  -o, --open 服务就绪后自动唤起系统默认浏览器访问
50
191
  --demo, --temp 临时演示沙盒模式:在系统临时目录创建一次性数据库,退出即自动销毁
51
- --cors <origins> 允许跨域访问来源白名单 (多个以逗号分隔,如: "http://example.com,*")
192
+ --cors <origins> 允许跨域访问来源白名单 (支持通配符,如: "http://example.com,*")
52
193
  --db-path <path> 自定义 SQLite 数据库文件绝对路径
53
194
  -v, --version 显示版本号
54
195
  -h, --help 显示此帮助信息
@@ -60,164 +201,146 @@ Environment Variables:
60
201
  ALLOWED_ORIGINS CORS 白名单来源 (默认放行同源与开发环境)
61
202
  GEMINI_API_KEY AI 助手兜底密钥 (推荐在管理员后台界面配置)
62
203
  `);
63
- process.exit(0);
64
- }
204
+ process.exit(0);
205
+ }
65
206
 
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');
73
-
74
- for (let i = 0; i < args.length; i++) {
75
- if (args[i] === '-p' || args[i] === '--port') {
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];
207
+ // ── Environment Setup ─────────────────────────────────────────────────────
208
+ if (flags.port) process.env.PORT = flags.port;
209
+ if (flags.host) process.env.HOST = flags.host;
210
+ if (flags.cors) process.env.ALLOWED_ORIGINS = flags.cors;
211
+ if (flags.open) process.env.OPEN_BROWSER = 'true';
212
+
213
+ let demoDir = null;
214
+ if (flags.demo) {
215
+ demoDir = join(os.tmpdir(), `openlearn-demo-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`);
216
+ mkdirSync(demoDir, { recursive: true });
217
+ process.env.OPENLEARN_DB_PATH = join(demoDir, 'data.db');
218
+ } else if (flags.dbPath) {
219
+ process.env.OPENLEARN_DB_PATH = resolve(flags.dbPath);
220
+ } else if (!process.env.OPENLEARN_DB_PATH) {
221
+ const dataDir = join(os.homedir(), 'openlearn-next');
222
+ mkdirSync(dataDir, { recursive: true });
223
+ process.env.OPENLEARN_DB_PATH = join(dataDir, 'data.db');
83
224
  }
84
- }
85
225
 
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';
91
-
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
- }
226
+ // ── Command Dispatcher ────────────────────────────────────────────────────
227
+ if (command === 'doctor') {
228
+ const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 9000;
229
+ const res = await runDoctor({
230
+ port,
231
+ dbPath: process.env.OPENLEARN_DB_PATH,
232
+ fix: flags.fix,
233
+ });
234
+ process.exit(res.ok ? 0 : 1);
235
+ }
108
236
 
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
- }
237
+ if (command === 'backup') {
238
+ const targetFile = subcommandArgs[0];
239
+ const res = await runBackup(targetFile, { dbPath: process.env.OPENLEARN_DB_PATH });
240
+ process.exit(res.ok ? 0 : 1);
241
+ }
115
242
 
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];
243
+ if (command === 'restore') {
244
+ const sourceFile = subcommandArgs[0];
245
+ const res = await runRestore(sourceFile, { dbPath: process.env.OPENLEARN_DB_PATH });
246
+ process.exit(res.ok ? 0 : 1);
122
247
  }
123
- const res = await runBackup(targetFile, { dbPath: process.env.OPENLEARN_DB_PATH });
124
- process.exit(res.ok ? 0 : 1);
125
- }
126
248
 
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];
249
+ if (command === 'reset-admin') {
250
+ const newPassword = flags.password || subcommandArgs[0] || 'admin';
251
+ const res = await runResetAdmin(newPassword, { dbPath: process.env.OPENLEARN_DB_PATH });
252
+ process.exit(res.ok ? 0 : 1);
133
253
  }
134
- const res = await runRestore(sourceFile, { dbPath: process.env.OPENLEARN_DB_PATH });
135
- process.exit(res.ok ? 0 : 1);
136
- }
137
254
 
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
- }
255
+ if (command === 'plugins') {
256
+ const res = await runPluginsList({ dbPath: process.env.OPENLEARN_DB_PATH });
257
+ process.exit(res.ok ? 0 : 1);
258
+ }
150
259
 
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
- }
260
+ if (command === 'clean') {
261
+ const cleanOptions = {
262
+ all: flags.all,
263
+ npx: flags.npx,
264
+ db: flags.db,
265
+ };
266
+ runClean(cleanOptions);
267
+ process.exit(0);
268
+ }
156
269
 
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) {
167
- const dataDir = join(os.homedir(), 'openlearn-next');
168
- mkdirSync(dataDir, { recursive: true });
169
- process.env.OPENLEARN_DB_PATH = join(dataDir, 'data.db');
170
- }
270
+ // ── Normal Server Startup ──────────────────────────────────────────────────
271
+ if (flags.demo) {
272
+ console.log(`[openlearn-next] 临时沙盒模式已激活: ${demoDir}`);
273
+ console.log(`[openlearn-next] 提示: 退出程序时将自动销毁沙盒数据,不保留任何历史记录。`);
274
+ }
171
275
 
172
- process.env.NODE_ENV = process.env.NODE_ENV || 'production';
276
+ process.env.NODE_ENV = process.env.NODE_ENV || 'production';
173
277
 
174
- console.log(`[openlearn-next] PORT=${process.env.PORT || '9000'}`);
175
- console.log(`[openlearn-next] HOST=${process.env.HOST || '0.0.0.0'}`);
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
- }
278
+ console.log(`[openlearn-next] PORT=${process.env.PORT || '9000'}`);
279
+ console.log(`[openlearn-next] HOST=${process.env.HOST || '0.0.0.0'}`);
280
+ console.log(`[openlearn-next] DB=${process.env.OPENLEARN_DB_PATH}`);
281
+ if (process.env.ALLOWED_ORIGINS) {
282
+ console.log(`[openlearn-next] CORS_ALLOWED=${process.env.ALLOWED_ORIGINS}`);
283
+ }
180
284
 
181
- const serverPath = join(__dirname, 'dist', 'server.cjs');
182
- const child = spawn('node', [serverPath], {
183
- stdio: 'inherit',
184
- env: { ...process.env },
185
- });
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(() => {
285
+ const serverPath = join(__dirname, 'dist', 'server.cjs');
286
+ if (!existsSync(serverPath)) {
287
+ console.error(`[openlearn-next] 错误: 服务执行产物未找到: ${serverPath}`);
288
+ console.error(`[openlearn-next] 请先执行 'pnpm build' 构建产物,或通过 npm/npx 重新安装。`);
289
+ process.exit(1);
290
+ }
291
+
292
+ const child = spawn('node', [serverPath], {
293
+ stdio: 'inherit',
294
+ env: { ...process.env },
295
+ });
296
+
297
+ let cleanedUp = false;
298
+ const cleanup = () => {
299
+ if (cleanedUp) return;
300
+ cleanedUp = true;
301
+ if (flags.demo && demoDir && existsSync(demoDir)) {
302
+ try {
303
+ rmSync(demoDir, { recursive: true, force: true });
304
+ console.log('\n[openlearn-next] 临时沙盒环境清理完毕。');
305
+ } catch {}
306
+ }
307
+ };
308
+
309
+ let isShuttingDown = false;
310
+ const handleSignal = (sig) => {
311
+ if (isShuttingDown) return;
312
+ isShuttingDown = true;
313
+ if (child && !child.killed) {
314
+ child.kill(sig);
315
+ // 保底 35 秒强制退出(留足 server 端 30 秒优雅关机缓冲时间)
316
+ setTimeout(() => {
317
+ cleanup();
318
+ process.exit(0);
319
+ }, 35000).unref();
320
+ } else {
207
321
  cleanup();
208
322
  process.exit(0);
209
- }, 3000).unref();
210
- } else {
323
+ }
324
+ };
325
+
326
+ process.on('SIGINT', () => handleSignal('SIGINT'));
327
+ process.on('SIGTERM', () => handleSignal('SIGTERM'));
328
+ process.on('exit', cleanup);
329
+
330
+ child.on('exit', (code) => {
211
331
  cleanup();
212
- process.exit(0);
213
- }
214
- };
332
+ process.exit(code || 0);
333
+ });
334
+ }
335
+
336
+ // 仅在作为主入口脚本执行时唤起
337
+ const isMain =
338
+ process.argv[1] &&
339
+ (process.argv[1].endsWith('cli.mjs') ||
340
+ process.argv[1].endsWith('openlearn-next') ||
341
+ (existsSync(process.argv[1]) && resolve(process.argv[1]) === fileURLToPath(import.meta.url)));
215
342
 
216
- process.on('SIGINT', () => handleSignal('SIGINT'));
217
- process.on('SIGTERM', () => handleSignal('SIGTERM'));
218
- process.on('exit', cleanup);
343
+ if (isMain) {
344
+ runCli();
345
+ }
219
346
 
220
- child.on('exit', (code) => {
221
- cleanup();
222
- process.exit(code || 0);
223
- });
@@ -1 +1 @@
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};
1
+ import{j as e}from"./vendor-react-0Lg0vA-4.js";import{m as p}from"./index-kqgYec9C.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};