openlearn-next 0.3.9 → 0.3.12
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-b8bsoxzd.js} +1 -1
- package/dist/assets/{ClassesView-f7h7q1me.js → ClassesView-3G9F2-T1.js} +1 -1
- package/dist/assets/{CloudDriveModal-y1cqaYjU.js → CloudDriveModal-Ruedbe3T.js} +1 -1
- package/dist/assets/{CourseManagement-Bwfdk7Hi.js → CourseManagement-DUvQsAJI.js} +1 -1
- package/dist/assets/{CourseWizardModal-DwKB919Y.js → CourseWizardModal-CtcOsfxX.js} +1 -1
- package/dist/assets/{Dashboard-7ElGQEot.js → Dashboard-DQFRN4SK.js} +1 -1
- package/dist/assets/{ExportWeightModal-Dor_2k5X.js → ExportWeightModal-BnAmAzm0.js} +1 -1
- package/dist/assets/{HelpView-Blq21AX9.js → HelpView-p-BdI2ec.js} +1 -1
- package/dist/assets/{ImportLessonsModal-BNb6POIc.js → ImportLessonsModal-C_0VI5zO.js} +1 -1
- package/dist/assets/{InteractiveCoursewareViewer-DZQNSStZ.js → InteractiveCoursewareViewer-Cp8ptdxK.js} +1 -1
- package/dist/assets/{InteractiveWhiteboard-B-6-PHls.js → InteractiveWhiteboard-CUI_ccOy.js} +1 -1
- package/dist/assets/{LazyCourseware-YQTwc8Xc.js → LazyCourseware-C0Nre0Ds.js} +1 -1
- package/dist/assets/{LazyWhiteboard-DNwlH8kJ.js → LazyWhiteboard-D8OvZiRS.js} +1 -1
- package/dist/assets/{LessonEditorView-Biw7dyfp.js → LessonEditorView-wYeBs0Ro.js} +1 -1
- package/dist/assets/{LiveClassroomView-D2ZyINSZ.js → LiveClassroomView-D9Qrc-Q0.js} +1 -1
- package/dist/assets/{NotificationDetailModal-TJSWdZgO.js → NotificationDetailModal-DRdt0tOJ.js} +1 -1
- package/dist/assets/{PluginView-B2m35ykl.js → PluginView-LMpjhq4N.js} +4 -4
- package/dist/assets/{QuizGeneratorModal-XNW646aZ.js → QuizGeneratorModal-BLKd1rAk.js} +1 -1
- package/dist/assets/{StudentAssignmentView-C7qnbBVV.js → StudentAssignmentView-DQGlFuWt.js} +1 -1
- package/dist/assets/{StudentLessonView-DItUU7Zd.js → StudentLessonView-BZ-dR871.js} +1 -1
- package/dist/assets/{StudentPreviewModal-CjxvwHUc.js → StudentPreviewModal-BgUVPr_t.js} +1 -1
- package/dist/assets/{StudentView-Bg6tAP5x.js → StudentView-Ci5S9iot.js} +1 -1
- package/dist/assets/{SystemResourceLibraryModal-Cf2XJ6sf.js → SystemResourceLibraryModal-DnA2Op8S.js} +1 -1
- package/dist/assets/{TeacherView-Baq66mut.js → TeacherView-_esXu640.js} +1 -1
- package/dist/assets/{index-KP_ep5bf.js → index-BNpZ_4yC.js} +1 -1
- package/dist/assets/{index-B7B4Vq2l.js → index-DMpWx3Re.js} +3 -3
- package/dist/index.html +1 -1
- package/dist/server.cjs +1249 -12
- 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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
// ──
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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> 允许跨域访问来源白名单 (
|
|
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
|
-
|
|
64
|
-
}
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
65
206
|
|
|
66
|
-
// ──
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
} else if (
|
|
78
|
-
|
|
79
|
-
} else if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
}
|
|
210
|
-
}
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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-
|
|
1
|
+
import{j as e}from"./vendor-react-0Lg0vA-4.js";import{m as p}from"./index-DMpWx3Re.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};
|