@icyfenix-dmla/cli 2026.5.2-2339 → 2026.5.3-1019
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/package.json +7 -6
- package/src/commands/data.js +66 -9
- package/src/server/routes/sandbox.js +2 -2
- package/src/server/sandbox.js +3 -2
- package/version.json +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@icyfenix-dmla/cli",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.3-1019",
|
|
4
4
|
"description": "DMLA 沙箱服务命令行工具",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -22,13 +22,14 @@
|
|
|
22
22
|
"README.md"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"
|
|
25
|
+
"@icyfenix-dmla/install": "*",
|
|
26
|
+
"adm-zip": "^0.5.17",
|
|
26
27
|
"chalk": "^5.3.0",
|
|
27
|
-
"
|
|
28
|
-
"dockerode": "^5.0.0",
|
|
29
|
-
"express": "^4.21.2",
|
|
28
|
+
"commander": "^12.1.0",
|
|
30
29
|
"cors": "^2.8.5",
|
|
31
|
-
"
|
|
30
|
+
"dockerode": "^5.0.0",
|
|
31
|
+
"enquirer": "^2.4.1",
|
|
32
|
+
"express": "^4.21.2"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"jest": "^29.7.0"
|
package/src/commands/data.js
CHANGED
|
@@ -10,6 +10,7 @@ import path from 'path'
|
|
|
10
10
|
import os from 'os'
|
|
11
11
|
import { spawn } from 'child_process'
|
|
12
12
|
import { execSync } from 'child_process'
|
|
13
|
+
import AdmZip from 'adm-zip'
|
|
13
14
|
|
|
14
15
|
// 配置文件路径
|
|
15
16
|
const DMLA_CONFIG_DIR = path.join(os.homedir(), '.dmla')
|
|
@@ -27,7 +28,11 @@ const DATASETS = [
|
|
|
27
28
|
size: '247MB',
|
|
28
29
|
format: 'git',
|
|
29
30
|
targetDir: 'datasets/tiny-imagenet-200',
|
|
30
|
-
source: 'ModelScope (icyfenix)'
|
|
31
|
+
source: 'ModelScope (icyfenix)',
|
|
32
|
+
// git clone 后需要解压的 zip 文件
|
|
33
|
+
zipFile: 'tiny-imagenet-200.zip',
|
|
34
|
+
// zip 内部的顶层目录名(解压后需要将此目录内容移到上层)
|
|
35
|
+
zipInnerDir: 'tiny-imagenet-200'
|
|
31
36
|
},
|
|
32
37
|
{
|
|
33
38
|
id: 'cifar-10',
|
|
@@ -526,6 +531,58 @@ async function downloadDataset(dataPath, dataset) {
|
|
|
526
531
|
console.log()
|
|
527
532
|
console.log(chalk.green('下载完成'))
|
|
528
533
|
|
|
534
|
+
// 解压数据集内的 zip 文件(如果有)
|
|
535
|
+
if (dataset.zipFile) {
|
|
536
|
+
const zipPath = path.join(targetDir, dataset.zipFile)
|
|
537
|
+
|
|
538
|
+
if (fs.existsSync(zipPath)) {
|
|
539
|
+
console.log()
|
|
540
|
+
console.log(chalk.gray(`解压 ${dataset.zipFile}...`))
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
const zip = new AdmZip(zipPath)
|
|
544
|
+
|
|
545
|
+
// 解压到临时目录
|
|
546
|
+
const tempDir = path.join(targetDir, '_extract_temp')
|
|
547
|
+
zip.extractAllTo(tempDir, true)
|
|
548
|
+
|
|
549
|
+
// 将 zip 内部目录内容移到目标目录
|
|
550
|
+
const innerDir = dataset.zipInnerDir
|
|
551
|
+
? path.join(tempDir, dataset.zipInnerDir)
|
|
552
|
+
: tempDir
|
|
553
|
+
|
|
554
|
+
if (fs.existsSync(innerDir)) {
|
|
555
|
+
// 移动内部目录的所有内容到目标目录
|
|
556
|
+
const items = fs.readdirSync(innerDir)
|
|
557
|
+
for (const item of items) {
|
|
558
|
+
const srcPath = path.join(innerDir, item)
|
|
559
|
+
const destPath = path.join(targetDir, item)
|
|
560
|
+
|
|
561
|
+
// 如果目标已存在且不是 zip 文件,跳过
|
|
562
|
+
if (fs.existsSync(destPath) && item !== dataset.zipFile) {
|
|
563
|
+
continue
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
fs.cpSync(srcPath, destPath, { recursive: true, force: true })
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// 清理临时目录
|
|
570
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
571
|
+
|
|
572
|
+
// 删除 zip 文件
|
|
573
|
+
fs.rmSync(zipPath, { force: true })
|
|
574
|
+
|
|
575
|
+
console.log(chalk.green('解压完成'))
|
|
576
|
+
} else {
|
|
577
|
+
console.log(chalk.yellow(` ⚠ zip 内部目录 ${dataset.zipInnerDir} 不存在`))
|
|
578
|
+
}
|
|
579
|
+
} catch (err) {
|
|
580
|
+
console.log(chalk.red(`解压失败: ${err.message}`))
|
|
581
|
+
console.log(chalk.yellow(`请手动解压: ${zipPath}`))
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
529
586
|
} else {
|
|
530
587
|
// 原有的 curl/wget 下载逻辑(保留兼容性)
|
|
531
588
|
const cacheDir = path.join(dataPath, 'cache', 'downloads')
|
|
@@ -569,17 +626,17 @@ async function downloadDataset(dataPath, dataset) {
|
|
|
569
626
|
console.log(chalk.gray('正在解压...'))
|
|
570
627
|
|
|
571
628
|
if (dataset.format === 'zip') {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
fs.rmSync(unzipDir, { recursive: true, force: true })
|
|
629
|
+
try {
|
|
630
|
+
const zip = new AdmZip(downloadFile)
|
|
631
|
+
zip.extractAllTo(targetDir, true) // overwrite = true
|
|
632
|
+
console.log(chalk.green('解压完成'))
|
|
633
|
+
} catch (err) {
|
|
634
|
+
console.log(chalk.red(`解压失败: ${err.message}`))
|
|
635
|
+
throw err
|
|
580
636
|
}
|
|
581
637
|
|
|
582
638
|
} else if (dataset.format === 'tar.gz') {
|
|
639
|
+
// tar.gz 文件仍使用系统命令(adm-zip 不支持)
|
|
583
640
|
execSync(`tar -xzf "${downloadFile}" -C "${path.join(dataPath, 'datasets')}"`, { stdio: 'inherit' })
|
|
584
641
|
}
|
|
585
642
|
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* 沙箱 API 路由
|
|
3
3
|
*/
|
|
4
4
|
import { Router } from 'express'
|
|
5
|
+
import Docker from 'dockerode'
|
|
5
6
|
import sandbox, { runPythonCode, checkImageExists, checkGPUAvailable, checkCUDACompatibility } from '../sandbox.js'
|
|
6
7
|
|
|
7
8
|
const { SANDBOX_CONFIG } = sandbox
|
|
9
|
+
const docker = new Docker()
|
|
8
10
|
|
|
9
11
|
const router = Router()
|
|
10
12
|
|
|
@@ -147,8 +149,6 @@ router.get('/progress', async (req, res) => {
|
|
|
147
149
|
try {
|
|
148
150
|
// 进度文件在容器内的 /workspace/progress.json
|
|
149
151
|
// 需要通过 docker exec 读取
|
|
150
|
-
const Docker = require('dockerode').default || require('dockerode')
|
|
151
|
-
const docker = new Docker()
|
|
152
152
|
|
|
153
153
|
// 查找运行中的沙箱容器
|
|
154
154
|
const containers = await docker.listContainers({ filters: { status: ['running'] } })
|
package/src/server/sandbox.js
CHANGED
|
@@ -471,12 +471,13 @@ export async function runPythonCode(code, useGpu = false, imageOverride = null,
|
|
|
471
471
|
container = await docker.createContainer(containerConfig)
|
|
472
472
|
log(`Container created: ${container.id}`)
|
|
473
473
|
|
|
474
|
-
//
|
|
474
|
+
// 设置超时(使用动态计算的超时时间,unlimited 时为 24 小时)
|
|
475
|
+
const containerTimeoutMs = timeoutSeconds * 1000 + 10000 // 转换为毫秒,额外 10 秒用于清理
|
|
475
476
|
const timeoutPromise = new Promise((_, reject) => {
|
|
476
477
|
timeoutId = setTimeout(() => {
|
|
477
478
|
log('Execution timeout triggered')
|
|
478
479
|
reject(new Error('Execution timeout'))
|
|
479
|
-
},
|
|
480
|
+
}, containerTimeoutMs)
|
|
480
481
|
})
|
|
481
482
|
|
|
482
483
|
// 启动容器
|
package/version.json
CHANGED